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::duplicate_nome(dep.nome(), list.as_str()));
2319 }
2320 target.push(dep);
2321 Ok(())
2322 }
2323
2324 /// Substrate-canonical per-`Caixa` `:limits` M2 typed-slot outer-
2325 /// composite Lunatic-per-process wasm32-sandboxing-composite optional-
2326 /// composite-reference accessor every consumer of the top-level
2327 /// manifest's per-Servico [`LimitsSpec`] outer-composite reader keys
2328 /// off — returns the author-declared `:limits` typed composite
2329 /// verbatim as an `Option<&LimitsSpec>` reference over the same
2330 /// backing storage the raw `self.limits.as_ref()` field access
2331 /// borrows from, with `None` naming the "no `:limits` block
2332 /// authored — every per-axis Lunatic-sandbox cap defers to the
2333 /// wasm-engine-default arm named on the per-axis
2334 /// [`LimitsSpec::memory`] / [`LimitsSpec::fuel`] /
2335 /// [`LimitsSpec::wall_clock`] / [`LimitsSpec::cpu`] scalar-accessor
2336 /// docstrings" partition every downstream Servico-M2-overlay
2337 /// emitter treats as "emit nothing" and the sibling
2338 /// [`crate::StandardLayout::verify`] per-`:limits` shape gate
2339 /// treats as "skip the per-axis
2340 /// [`crate::LimitsError::MemoryZero`] / `MemoryBelowWasm32Page` /
2341 /// `FuelZero` / `WallClockZero` / `CpuZero` refusal cascade".
2342 ///
2343 /// The outer `:limits` slot carries the M2 Servico-runtime typed
2344 /// composite — the load-bearing container of every Lunatic-shaped
2345 /// per-process wasm32-sandbox cap axis every long-running wasm
2346 /// component's runtime dispatches on (INSPIRATIONS §III.1 —
2347 /// Lunatic per-process linear-memory / fuel / wall-clock /
2348 /// millicore cap primitives translated onto pleme-io's typed
2349 /// `:limits :memory` / `:limits :fuel` / `:limits :wall-clock` /
2350 /// `:limits :cpu` sub-slot axes; CAIXA-SDLC §II — the typed-M2
2351 /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
2352 /// chart both fan on). Every per-`:limits` axis threads through a
2353 /// lifted per-slot accessor on the [`LimitsSpec`] type: the
2354 /// [`LimitsSpec::memory`] wasm32 linear-memory byte-cap scalar
2355 /// accessor, the [`LimitsSpec::fuel`] wasmtime fuel-cap scalar
2356 /// accessor, the [`LimitsSpec::wall_clock`] per-call wall-clock
2357 /// deadline scalar accessor, and the [`LimitsSpec::cpu`]
2358 /// K8s-millicore soft-CPU-share scalar accessor. Every downstream
2359 /// consumer that reaches for a limits axis first passes through
2360 /// this outer accessor onto the composite and then dispatches
2361 /// onto the per-axis accessor — the two-level dispatch means
2362 /// every per-`:limits` reader now routes through a typed dispatch
2363 /// on the substrate primitive at both altitudes.
2364 ///
2365 /// Prior to this lift the `.limits` `Option<LimitsSpec>` composite
2366 /// was accessed inline at three production sites — the
2367 /// [`crate::StandardLayout::verify`] per-`:limits` shape gate's
2368 /// `if let Some(l) = &caixa.limits { … }` traversal head
2369 /// (caixa-core/src/layout.rs:882, which drives the per-axis
2370 /// refusal cascade on the composite: the `LimitsError::MemoryZero`
2371 /// / `MemoryBelowWasm32Page` / `MemoryExceedsWasm32Max` /
2372 /// `FuelZero` / `FuelExceedsMax` / `WallClockZero` /
2373 /// `WallClockExceedsMax` / `CpuZero` / `CpuExceedsMax` refusals
2374 /// [`LimitsSpec::validate`] fans onto), the
2375 /// [`crate::render::servico_m2_overlay`] per-Servico M2 overlay
2376 /// emitter's `if let Some(limits) = &caixa.limits { … }` traversal
2377 /// head (caixa-core/src/render.rs:18504, which drives the
2378 /// `M2_KEY_LIMITS`-keyed `limits.is_empty()`-gated `serde_yaml`
2379 /// projection every `caixa-helm` / `caixa-flux` Servico values-
2380 /// block emitter fans on), and the
2381 /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
2382 /// set enumerator's `self.limits.is_some()` presence probe
2383 /// (caixa-core/src/manifest.rs:1788, which drives the
2384 /// `M2_AUTHOR_KEY_LIMITS` kebab-case author-label push every
2385 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2386 /// gate reads) — three open-coded outer-field accesses that
2387 /// expressed no compile-time link back to the typed slot at the
2388 /// [`Caixa`] altitude. A future extension of the `:limits` outer
2389 /// axis to a richer author surface (a multi-`:limits` list the M4
2390 /// CR materializer resolves per-CR at admission time so a Servico
2391 /// can expose a compute-heavy + IO-heavy limits pair, a per-
2392 /// cluster `:limits-overrides` slot the operator pins so a
2393 /// cluster-specific policy can tighten a caixa-declared cap
2394 /// without re-authoring the `caixa.lisp`, a promotion of the
2395 /// plain `Option<LimitsSpec>` to a richer
2396 /// `{static, dynamic}` partition once the wasm-engine's runtime-
2397 /// resolved dynamic-cap surface lands) would have had to be
2398 /// threaded through all three open-coded copies in lockstep or
2399 /// one consumer would silently disagree with the peers on which
2400 /// limits composite a given Caixa resolves to — the layout gate's
2401 /// per-axis bracket-dispatch seed reading the raw slot while the
2402 /// peer `servico_m2_overlay` emitter read an operator-resolved
2403 /// slot would silently split the build-time sandbox-shape gate
2404 /// from the runtime `ComputeUnit` CR emission gate, a three-
2405 /// consumer split at the layout gate, the M2 overlay emitter, and
2406 /// the declared-slot enumerator far from the source `caixa.lisp`
2407 /// with no field naming the limits-drift root cause. Lifting the
2408 /// resolution rule to a typed method on the substrate primitive
2409 /// means every downstream consumer of the caixa's per-`Caixa`
2410 /// Lunatic-sandboxing outer-composite surface reaches for exactly
2411 /// one typed dispatch — the resolver's accept-set migrates as a
2412 /// unit on any future axis addition.
2413 ///
2414 /// First outer top-level [`Caixa`] `Option<&Composite>`-return
2415 /// composite-reference accessor — opens the outer-`Caixa`
2416 /// `Option<&Composite>` composite-reference projection pattern the
2417 /// sibling per-`Caixa` `:behavior` [`crate::BehaviorSpec`] /
2418 /// `:politicas` [`crate::aplicacao::MeshPolicy`] / `:placement`
2419 /// [`crate::aplicacao::Placement`] / `:entrada`
2420 /// [`crate::aplicacao::Entrada`] future outer-composite lifts
2421 /// fold on. Peer of the M3 mesh-slot outer-composite family the
2422 /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2423 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2424 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2425 /// accessors already close on the outer [`crate::AplicacaoSpec`]
2426 /// altitude — extends that "one typed dispatch on the substrate
2427 /// primitive, thin projections at each consumer" discipline onto
2428 /// the outer top-level [`Caixa`] altitude, opening the M2 Servico-
2429 /// runtime slot family's outer-composite axis. Returns
2430 /// `Option<&LimitsSpec>` (not the owning composite by copy or
2431 /// clone) because every downstream consumer of the limits
2432 /// composite treats it as a read-only per-axis dispatch source —
2433 /// the reference-view is the narrowest borrow that supports every
2434 /// present + roadmapped consumer (per-axis accessor dispatch,
2435 /// `.is_empty()`-gated overlay projection, presence-probe early
2436 /// return on the "author-omitted `:limits` ⇒ engine-default
2437 /// applies" partition) without cloning the composite through
2438 /// every consumer's fast path. The `Option` half of the return-
2439 /// type preserves the load-bearing "author-omitted `:limits` ⇒
2440 /// engine-default applies" partition (not a default composite the
2441 /// downstream must reject on emptiness) — the accessor projects
2442 /// the raw `Option<LimitsSpec>` slot's presence bit through the
2443 /// reference-return unchanged. Named `limits()` to match the
2444 /// storage field's name verbatim and the tatara-lisp author-
2445 /// surface term (`:limits`) the field's own docstring already
2446 /// carries.
2447 #[must_use]
2448 pub const fn limits(&self) -> Option<&LimitsSpec> {
2449 self.limits.as_ref()
2450 }
2451
2452 /// Substrate-canonical per-`Caixa` `:behavior` M2 typed-slot outer-
2453 /// composite OTP-`gen_server`-shaped callback-table optional-
2454 /// composite-reference accessor every consumer of the top-level
2455 /// manifest's per-Servico [`BehaviorSpec`] outer-composite reader
2456 /// keys off — returns the author-declared `:behavior` typed
2457 /// composite verbatim as an `Option<&BehaviorSpec>` reference over
2458 /// the same backing storage the raw `self.behavior.as_ref()` field
2459 /// access borrows from, with `None` naming the "no `:behavior`
2460 /// block authored — every per-callback OTP-shaped hook defers to
2461 /// the wasm-engine's runtime default arm named on the per-axis
2462 /// [`BehaviorSpec::on_init`] / [`BehaviorSpec::on_call`] /
2463 /// [`BehaviorSpec::on_cast`] / [`BehaviorSpec::on_info`] /
2464 /// [`BehaviorSpec::on_state_change`] /
2465 /// [`BehaviorSpec::on_terminate`] scalar-accessor docstrings"
2466 /// partition every downstream Servico-M2-overlay emitter treats as
2467 /// "emit nothing" and the sibling [`crate::StandardLayout::verify`]
2468 /// per-`:behavior` shape gate treats as "skip the per-arm
2469 /// [`crate::behavior::BehaviorError`] refusal cascade + the
2470 /// per-callback on-disk `MissingEntry` existence check".
2471 ///
2472 /// The outer `:behavior` slot carries the M2 Servico-runtime typed
2473 /// composite — the load-bearing container of every OTP-shaped
2474 /// per-Servico lifecycle-callback path axis every long-running wasm
2475 /// component's runtime dispatches on (INSPIRATIONS §II.3 — Erlang/
2476 /// OTP `gen_server:init/1` / `handle_call/3` / `handle_cast/2` /
2477 /// `handle_info/2` / `code_change/3` / `terminate/2` primitives
2478 /// translated onto pleme-io's typed `:behavior :on-init` /
2479 /// `:on-call` / `:on-cast` / `:on-info` / `:on-state-change` /
2480 /// `:on-terminate` sub-slot axes; CAIXA-SDLC §II — the typed-M2
2481 /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
2482 /// chart both fan on). Every per-`:behavior` axis threads through a
2483 /// lifted per-callback accessor on the [`BehaviorSpec`] type
2484 /// (9b4ecde / d66c702 / 156ddbe / 99616ac / 4846cef / 701add7).
2485 /// Every downstream consumer that reaches for a behavior axis
2486 /// first passes through this outer accessor onto the composite
2487 /// and then dispatches onto the per-callback accessor — the
2488 /// two-level dispatch means every per-`:behavior` reader now
2489 /// routes through a typed dispatch on the substrate primitive at
2490 /// both altitudes.
2491 ///
2492 /// Composes cross-slot with the M2 `:upgrade-from` gate: the
2493 /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
2494 /// cross-slot composition gate at [`crate::StandardLayout::verify`]
2495 /// keys the "per-version `:state-change` instruction must have a
2496 /// `:on-state-change` callback" precondition off this accessor's
2497 /// composite (the callback-side counterpart to the
2498 /// `:upgrade-from :instructions :state-change :script` refusal at
2499 /// the appup-side). Threading that gate's traversal input through
2500 /// this accessor closes the cross-slot invariant on the substrate
2501 /// primitive, not on the raw field.
2502 ///
2503 /// Prior to this lift the `.behavior` `Option<BehaviorSpec>`
2504 /// composite was accessed inline at four production sites — the
2505 /// [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
2506 /// `if let Some(b) = &caixa.behavior { … }` traversal head
2507 /// (caixa-core/src/layout.rs:896, which drives the per-arm
2508 /// `BehaviorError` refusal cascade + the per-callback on-disk
2509 /// [`crate::LayoutError::MissingEntry`] existence check under
2510 /// [`crate::render::LAYOUT_MISSING_ENTRY_KIND_BEHAVIOR_CALLBACK`]),
2511 /// the [`crate::upgrade::validate_upgrade_from_against_behavior`]
2512 /// cross-slot composition gate's `caixa.behavior.as_ref()`
2513 /// traversal-input feed (caixa-core/src/layout.rs:1008, which
2514 /// drives the `:state-change` ↔ `:on-state-change` precondition
2515 /// refusal), the [`crate::render::servico_m2_overlay`] per-Servico
2516 /// M2 overlay emitter's `if let Some(behavior) = &caixa.behavior
2517 /// { … }` traversal head (caixa-core/src/render.rs:18513, which
2518 /// drives the `M2_KEY_BEHAVIOR`-keyed `behavior.is_empty()`-gated
2519 /// `serde_yaml` projection every `caixa-helm` / `caixa-flux`
2520 /// Servico values-block emitter fans on), and the
2521 /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
2522 /// set enumerator's `self.behavior.is_some()` presence probe
2523 /// (caixa-core/src/manifest.rs:1919, which drives the
2524 /// `M2_AUTHOR_KEY_BEHAVIOR` kebab-case author-label push every
2525 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2526 /// gate reads) — four open-coded outer-field accesses that
2527 /// expressed no compile-time link back to the typed slot at the
2528 /// [`Caixa`] altitude. A future extension of the `:behavior`
2529 /// outer axis to a richer author surface (a per-callback overlay
2530 /// resolver the operator materializes at admission time so a
2531 /// cluster-specific policy can inject a per-callback tracing
2532 /// interceptor without re-authoring the `caixa.lisp`, a promotion
2533 /// of the plain `Option<BehaviorSpec>` to a richer `{static,
2534 /// dynamic}` partition once a runtime-resolved behavior-swap
2535 /// surface lands, the M4 per-callback middleware chain the
2536 /// caixa-operator's per-Servico admission webhook keys off) would
2537 /// have had to be threaded through all four open-coded copies in
2538 /// lockstep or one consumer would silently disagree with the
2539 /// peers on which behavior composite a given Caixa resolves to —
2540 /// the layout gate's per-callback existence-check seed reading
2541 /// the raw slot while the peer `servico_m2_overlay` emitter read
2542 /// an operator-resolved slot would silently split the build-time
2543 /// callback-shape gate from the runtime `ComputeUnit` CR emission
2544 /// gate from the cross-slot `:state-change` composition gate from
2545 /// the M2 declared-slot enumerator, a four-consumer split far
2546 /// from the source `caixa.lisp` with no field naming the
2547 /// behavior-drift root cause. Lifting the resolution rule to a
2548 /// typed method on the substrate primitive means every downstream
2549 /// consumer of the caixa's per-`Caixa` OTP-callback-table outer-
2550 /// composite surface reaches for exactly one typed dispatch — the
2551 /// resolver's accept-set migrates as a unit on any future axis
2552 /// addition.
2553 ///
2554 /// Second outer top-level [`Caixa`] `Option<&Composite>`-return
2555 /// composite-reference accessor — sibling to the opening
2556 /// [`Self::limits`] (b2bd9d7) accessor on the outer-`Caixa`
2557 /// `Option<&Composite>` composite-reference sub-family, extends
2558 /// the "one typed dispatch on the substrate primitive, thin
2559 /// projections at each consumer" discipline onto the second of
2560 /// the three M2 Servico-runtime slots. The remaining
2561 /// `Option<&Composite>` axes at the outer top-level [`Caixa`]
2562 /// altitude — the M3 mesh-slot family (`:politicas`,
2563 /// `:placement`, `:entrada` — already closed on the inner
2564 /// [`crate::AplicacaoSpec`] altitude via 534dc21 / 9abb8f0 /
2565 /// d32111c) — remain the future sibling lifts on the outer
2566 /// top-level projection. Returns `Option<&BehaviorSpec>` (not
2567 /// the owning composite by copy or clone) because every
2568 /// downstream consumer of the behavior composite treats it as a
2569 /// read-only per-callback dispatch source — the reference-view is
2570 /// the narrowest borrow that supports every present + roadmapped
2571 /// consumer (per-callback accessor dispatch, `.is_empty()`-gated
2572 /// overlay projection, presence-probe early return on the
2573 /// "author-omitted `:behavior` ⇒ runtime-default applies"
2574 /// partition, cross-slot `:state-change` composition input)
2575 /// without cloning the composite through every consumer's fast
2576 /// path. The `Option` half of the return-type preserves the
2577 /// load-bearing "author-omitted `:behavior` ⇒ runtime-default
2578 /// applies" partition (not a default composite the downstream
2579 /// must reject on emptiness) — the accessor projects the raw
2580 /// `Option<BehaviorSpec>` slot's presence bit through the
2581 /// reference-return unchanged. Named `behavior()` to match the
2582 /// storage field's name verbatim and the tatara-lisp author-
2583 /// surface term (`:behavior`) the field's own docstring already
2584 /// carries.
2585 #[must_use]
2586 pub const fn behavior(&self) -> Option<&crate::BehaviorSpec> {
2587 self.behavior.as_ref()
2588 }
2589
2590 /// Substrate-canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
2591 /// composite MESH-COMPOSITION-shaped mesh-policy optional-composite-
2592 /// reference accessor every consumer of the top-level manifest's
2593 /// per-Aplicacao [`crate::aplicacao::MeshPolicy`] outer-composite
2594 /// reader keys off — returns the author-declared `:politicas` typed
2595 /// composite verbatim as an `Option<&MeshPolicy>` reference over the
2596 /// same backing storage the raw `self.politicas.as_ref()` field
2597 /// access borrows from, with `None` naming the "no `:politicas`
2598 /// block authored — every per-axis mesh-policy scalar defers to the
2599 /// cluster-default arm named on the per-axis
2600 /// [`crate::aplicacao::MeshPolicy::timeout`] /
2601 /// [`crate::aplicacao::MeshPolicy::retries`] /
2602 /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] /
2603 /// [`crate::aplicacao::MeshPolicy::mtls_required`] /
2604 /// [`crate::aplicacao::MeshPolicy::rate_limit`] scalar-accessor
2605 /// docstrings" partition every downstream caixa-mesh /
2606 /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2607 /// "emit no per-`:politicas` overlay" and the sibling
2608 /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2609 /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2610 /// arm.
2611 ///
2612 /// The outer `:politicas` slot carries the M3 mesh-slot per-
2613 /// Aplicacao typed composite — the load-bearing container of every
2614 /// mesh-level policy axis every Cilium NetworkPolicy / Gateway API
2615 /// v1.x HTTPRoute / future M4 per-edge policy overlay emitter fans
2616 /// on (MESH-COMPOSITION §III.2 — the Aplicacao's typed mesh-policy
2617 /// composite; §V — the "no infinite blocking" per-call deadline +
2618 /// "sandboxing-by-default" mTLS-enforcement CSE invariants; §III.3
2619 /// — the typed inter-Servico contrato-edge overlay the per-`(:de,
2620 /// :para)` mesh renderer keys off). Every per-`:politicas` axis
2621 /// threads through a lifted per-slot accessor on the
2622 /// [`crate::aplicacao::MeshPolicy`] type: the
2623 /// [`crate::aplicacao::MeshPolicy::mtls_required`] (c0110f1) Cilium
2624 /// mTLS-enforcement toggle, the
2625 /// [`crate::aplicacao::MeshPolicy::retries`] (bdfb399) transient-
2626 /// failure retry budget, the [`crate::aplicacao::MeshPolicy::timeout`]
2627 /// (7073d0f) Gateway-API per-call deadline, the
2628 /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] (b0e741a)
2629 /// Envoy-outlier-detection composite. Every downstream consumer
2630 /// that reaches for a mesh-policy axis first passes through this
2631 /// outer accessor onto the composite and then dispatches onto the
2632 /// per-axis accessor — the two-level dispatch means every per-
2633 /// `:politicas` reader now routes through a typed dispatch on the
2634 /// substrate primitive at both altitudes.
2635 ///
2636 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2637 /// seed: the Aplicacao-view builder folds the outer `Option`'s
2638 /// author-omitted arm onto the [`crate::aplicacao::MeshPolicy::default`]
2639 /// cluster-default, so the peer inner [`crate::AplicacaoSpec::politicas`]
2640 /// (534dc21) `&MeshPolicy`-return accessor observes a typed
2641 /// composite whether or not the author declared the outer slot.
2642 /// The outer accessor preserves the "author-omitted vs authored-
2643 /// empty" partition the inner accessor's `is_empty()`-gated
2644 /// renderer overlay collapses — routing the presence bit through
2645 /// this accessor keeps the [`Self::declared_mesh_slots`] M3 kind-
2646 /// coherence enumerator's `M3_AUTHOR_KEY_POLITICAS` push separate
2647 /// from the inner `MeshPolicy::is_empty()`-gated overlay elision.
2648 ///
2649 /// Prior to this lift the `.politicas` `Option<MeshPolicy>`
2650 /// composite was accessed inline at two production sites — the
2651 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2652 /// `self.politicas.clone().unwrap_or_default()` traversal head
2653 /// (caixa-core/src/manifest.rs:1899, which drives the fold onto
2654 /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2655 /// arm the inner [`crate::AplicacaoSpec::politicas`] accessor
2656 /// then observes), and the [`Self::declared_mesh_slots`] M3
2657 /// declared-slot-set enumerator's `self.politicas.is_some()`
2658 /// presence probe (caixa-core/src/manifest.rs:1961, which drives
2659 /// the `M3_AUTHOR_KEY_POLITICAS` kebab-case author-label push
2660 /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2661 /// coherence gate reads) — two open-coded outer-field accesses
2662 /// that expressed no compile-time link back to the typed slot at
2663 /// the [`Caixa`] altitude. A future extension of the `:politicas`
2664 /// outer axis to a richer author surface (a per-cluster
2665 /// `:politicas-overrides` slot the operator materializes at
2666 /// admission time so a cluster-specific policy can tighten the
2667 /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2668 /// promotion of the plain `Option<MeshPolicy>` to a richer
2669 /// `{static, dynamic}` partition once the M4 per-edge
2670 /// contrato-scoped policy-override surface lands, the M5 traffic-
2671 /// shaping composition the caixa-operator's per-Aplicacao mesh
2672 /// admission webhook keys off) would have had to be threaded
2673 /// through both open-coded copies in lockstep or the Aplicacao-
2674 /// composition seed's default-fold arm would silently disagree
2675 /// with the M3 declared-slot enumerator on which policy composite
2676 /// a given Caixa resolves to — the seed reading an operator-
2677 /// resolved slot while the enumerator's presence probe read the
2678 /// raw slot would silently split the build-time mesh-artifact
2679 /// emission gate from the M3 declared-slot enumerator's kind-
2680 /// coherence gate, a two-consumer split far from the source
2681 /// `caixa.lisp` with no field naming the policy-drift root cause.
2682 /// Lifting the resolution rule to a typed method on the substrate
2683 /// primitive means every downstream consumer of the caixa's per-
2684 /// `Caixa` MESH-COMPOSITION mesh-policy outer-composite surface
2685 /// reaches for exactly one typed dispatch — the resolver's
2686 /// accept-set migrates as a unit on any future axis addition.
2687 ///
2688 /// Third outer top-level [`Caixa`] `Option<&Composite>`-return
2689 /// composite-reference accessor — sibling to the opening
2690 /// [`Self::limits`] (b2bd9d7) and [`Self::behavior`] (35d8b52)
2691 /// accessors on the outer-`Caixa` `Option<&Composite>` composite-
2692 /// reference sub-family, extends the "one typed dispatch on the
2693 /// substrate primitive, thin projections at each consumer"
2694 /// discipline onto the first of the three M3 mesh-slot axes.
2695 /// Peer of the closed inner mesh-slot outer-composite family the
2696 /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2697 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2698 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2699 /// accessor pins already close on the inner [`crate::AplicacaoSpec`]
2700 /// altitude — opens the outer top-level [`Caixa`] altitude's M3
2701 /// mesh-slot arm of the composite-reference family the remaining
2702 /// two axes (`:placement`, `:entrada`) fold onto in future
2703 /// sibling lifts. Returns `Option<&MeshPolicy>` (not the owning
2704 /// composite by copy or clone) because every downstream consumer
2705 /// of the mesh-policy composite treats it as a read-only per-axis
2706 /// dispatch source — the reference-view is the narrowest borrow
2707 /// that supports every present + roadmapped consumer (per-axis
2708 /// accessor dispatch, `.is_empty()`-gated overlay projection,
2709 /// presence-probe early return on the "author-omitted `:politicas`
2710 /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2711 /// seed's default-fold arm) without cloning the composite through
2712 /// every consumer's fast path. The `Option` half of the return-
2713 /// type preserves the load-bearing "author-omitted `:politicas` ⇒
2714 /// cluster-default applies" partition (not a default composite
2715 /// the downstream must reject on emptiness) — the accessor
2716 /// projects the raw `Option<MeshPolicy>` slot's presence bit
2717 /// through the reference-return unchanged. Named `politicas()` to
2718 /// match the storage field's name verbatim and the tatara-lisp
2719 /// author-surface term (`:politicas`) the field's own docstring
2720 /// already carries.
2721 #[must_use]
2722 pub const fn politicas(&self) -> Option<&crate::aplicacao::MeshPolicy> {
2723 self.politicas.as_ref()
2724 }
2725
2726 /// Substrate-canonical per-`Caixa` `:placement` M3 mesh-slot outer-
2727 /// composite MESH-COMPOSITION-shaped distribution optional-composite-
2728 /// reference accessor every consumer of the top-level manifest's
2729 /// per-Aplicacao [`crate::aplicacao::Placement`] outer-composite
2730 /// reader keys off — returns the author-declared `:placement` typed
2731 /// composite verbatim as an `Option<&Placement>` reference over the
2732 /// same backing storage the raw `self.placement.as_ref()` field
2733 /// access borrows from, with `None` naming the "no `:placement`
2734 /// block authored — every per-axis placement scalar defers to the
2735 /// cluster-default arm named on the per-axis
2736 /// [`crate::aplicacao::Placement::estrategia`] /
2737 /// [`crate::aplicacao::Placement::clusters`] /
2738 /// [`crate::aplicacao::Placement::affinity`] /
2739 /// [`crate::aplicacao::Placement::shard_key`] scalar-accessor
2740 /// docstrings" partition every downstream caixa-mesh /
2741 /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2742 /// "emit no per-`:placement` overlay" and the sibling
2743 /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2744 /// the [`crate::aplicacao::Placement::default`] cluster-default arm.
2745 ///
2746 /// The outer `:placement` slot carries the M3 mesh-slot per-
2747 /// Aplicacao typed distribution composite — the load-bearing
2748 /// container of every where-does-this-Aplicacao-run axis every
2749 /// caixa-mesh programs.yaml per-cluster distribution overlay /
2750 /// caixa-flux per-Aplicacao GitRepository/HelmRelease fan-out /
2751 /// future M4 per-Aplicacao Akka-style cluster-sharding entity-id
2752 /// resolver emitter fans on (MESH-COMPOSITION §II.4 — the
2753 /// Aplicacao's typed distribution composite; §V CSE invariants —
2754 /// "distribution is a first-class typed composite, not a runtime
2755 /// scheduler hint" the per-axis scalars enforce; §III.3 — the
2756 /// typed inter-Servico contrato-edge overlay the per-cluster
2757 /// mesh renderer keys off). Every per-`:placement` axis threads
2758 /// through a lifted per-slot accessor on the
2759 /// [`crate::aplicacao::Placement`] type: the
2760 /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
2761 /// MESH-COMPOSITION distribution-strategy scalar, the
2762 /// [`crate::aplicacao::Placement::clusters`] (a6e18d7) per-cluster
2763 /// distribution-target slice, the [`crate::aplicacao::Placement::affinity`]
2764 /// M3-Adaptive-compression-hint optional-scalar, and the
2765 /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) Akka-cluster-
2766 /// sharding extractor-expression optional-scalar. Every downstream
2767 /// consumer that reaches for a placement axis first passes through
2768 /// this outer accessor onto the composite and then dispatches onto
2769 /// the per-axis accessor — the two-level dispatch means every per-
2770 /// `:placement` reader now routes through a typed dispatch on the
2771 /// substrate primitive at both altitudes.
2772 ///
2773 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2774 /// seed: the Aplicacao-view builder folds the outer `Option`'s
2775 /// author-omitted arm onto the [`crate::aplicacao::Placement::default`]
2776 /// cluster-default, so the peer inner [`crate::AplicacaoSpec::placement`]
2777 /// (9abb8f0) `&Placement`-return accessor observes a typed composite
2778 /// whether or not the author declared the outer slot. The outer
2779 /// accessor preserves the "author-omitted vs authored-empty" partition
2780 /// the inner accessor collapses at the cluster-default fold —
2781 /// routing the presence bit through this accessor keeps the
2782 /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2783 /// `M3_AUTHOR_KEY_PLACEMENT` push separate from the inner
2784 /// [`crate::AplicacaoSpec::validate_placement`]-gated overlay
2785 /// dispatch.
2786 ///
2787 /// Prior to this lift the `.placement` `Option<Placement>`
2788 /// composite was accessed inline at two production sites — the
2789 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2790 /// `self.placement.clone().unwrap_or_default()` traversal head
2791 /// (caixa-core/src/manifest.rs:2036, which drives the fold onto
2792 /// the [`crate::aplicacao::Placement::default`] cluster-default
2793 /// arm the inner [`crate::AplicacaoSpec::placement`] accessor
2794 /// then observes), and the [`Self::declared_mesh_slots`] M3
2795 /// declared-slot-set enumerator's `self.placement.is_some()`
2796 /// presence probe (caixa-core/src/manifest.rs:2100, which drives
2797 /// the `M3_AUTHOR_KEY_PLACEMENT` kebab-case author-label push
2798 /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2799 /// coherence gate reads) — two open-coded outer-field accesses
2800 /// that expressed no compile-time link back to the typed slot at
2801 /// the [`Caixa`] altitude. A future extension of the `:placement`
2802 /// outer axis to a richer author surface (a per-cluster
2803 /// `:placement-overrides` slot the operator materializes at
2804 /// admission time so a cluster-specific placement can tighten the
2805 /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2806 /// per-tenant placement-alias table the M4
2807 /// `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer resolves
2808 /// per-CR at admission time, a promotion of the plain
2809 /// `Option<Placement>` to a richer `{static, dynamic}` partition
2810 /// once Orleans-style virtual-actor dynamic placement comes into
2811 /// typed scope) would have had to be threaded through both open-
2812 /// coded copies in lockstep or the Aplicacao-composition seed's
2813 /// default-fold arm would silently disagree with the M3 declared-
2814 /// slot enumerator on which distribution composite a given Caixa
2815 /// resolves to — the seed reading an operator-resolved slot while
2816 /// the enumerator's presence probe read the raw slot would
2817 /// silently split the build-time distribution-artifact emission
2818 /// gate from the M3 declared-slot enumerator's kind-coherence
2819 /// gate, a two-consumer split far from the source `caixa.lisp`
2820 /// with no field naming the distribution-drift root cause.
2821 /// Lifting the resolution rule to a typed method on the substrate
2822 /// primitive means every downstream consumer of the caixa's per-
2823 /// `Caixa` MESH-COMPOSITION distribution outer-composite surface
2824 /// reaches for exactly one typed dispatch — the resolver's
2825 /// accept-set migrates as a unit on any future axis addition.
2826 ///
2827 /// Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
2828 /// composite-reference accessor — sibling to the opening
2829 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) M2-
2830 /// Servico-runtime pair and the peer [`Self::politicas`] (5d23d29)
2831 /// M3-mesh-slot arm on the outer-`Caixa` `Option<&Composite>`
2832 /// composite-reference sub-family, folds on the "one typed
2833 /// dispatch on the substrate primitive, thin projections at each
2834 /// consumer" discipline extended onto the second of the three M3
2835 /// mesh-slot axes. Peer of the closed inner mesh-slot outer-
2836 /// composite family the sibling
2837 /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2838 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2839 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2840 /// accessor pins already close on the inner
2841 /// [`crate::AplicacaoSpec`] altitude — folds on the outer top-
2842 /// level [`Caixa`] altitude's M3 mesh-slot arm the sibling
2843 /// [`Self::politicas`] opened, extending the discipline onto the
2844 /// second of the three M3 mesh-slot axes. The remaining M3
2845 /// mesh-slot axis (`:entrada`) folds onto this accessor's
2846 /// discipline in the final sibling lift, closing the outer top-
2847 /// level [`Caixa`] `Option<&Composite>` M3 mesh-slot sub-family.
2848 /// Returns `Option<&Placement>` (not the owning composite by copy
2849 /// or clone) because every downstream consumer of the placement
2850 /// composite treats it as a read-only per-axis dispatch source —
2851 /// the reference-view is the narrowest borrow that supports every
2852 /// present + roadmapped consumer (per-axis accessor dispatch,
2853 /// serde composite-serialization on the programs.yaml overlay,
2854 /// presence-probe early return on the "author-omitted `:placement`
2855 /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2856 /// seed's default-fold arm) without cloning the composite through
2857 /// every consumer's fast path. The `Option` half of the return-
2858 /// type preserves the load-bearing "author-omitted `:placement` ⇒
2859 /// cluster-default applies" partition (not a default composite
2860 /// the downstream must reject on emptiness) — the accessor
2861 /// projects the raw `Option<Placement>` slot's presence bit
2862 /// through the reference-return unchanged. Named `placement()` to
2863 /// match the storage field's name verbatim and the tatara-lisp
2864 /// author-surface term (`:placement`) the field's own docstring
2865 /// already carries.
2866 #[must_use]
2867 pub const fn placement(&self) -> Option<&crate::aplicacao::Placement> {
2868 self.placement.as_ref()
2869 }
2870
2871 /// Substrate-canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
2872 /// composite MESH-COMPOSITION-shaped external-gateway optional-
2873 /// composite-reference accessor every consumer of the top-level
2874 /// manifest's per-Aplicacao [`crate::aplicacao::Entrada`] outer-
2875 /// composite reader keys off — returns the author-declared
2876 /// `:entrada` typed composite verbatim as an `Option<&Entrada>`
2877 /// reference over the same backing storage the raw
2878 /// `self.entrada.as_ref()` field access borrows from, with `None`
2879 /// naming the "no `:entrada` block authored — this Aplicacao is
2880 /// cluster-internal, no `Gateway`/`HTTPRoute` fan-out emitted"
2881 /// partition every downstream caixa-mesh Gateway-API artifact
2882 /// emitter treats as "emit no gateway-listener + no `HTTPRoute`
2883 /// backend for this Aplicacao" and the sibling
2884 /// [`Self::aplicacao_view`] Aplicacao-composition seed forwards
2885 /// verbatim (unlike the peer `:politicas` / `:placement` arms,
2886 /// `:entrada` has no cluster-default fold — an omitted `:entrada`
2887 /// stays `None` on the projected [`crate::AplicacaoSpec`] and the
2888 /// peer inner [`crate::AplicacaoSpec::entrada`] accessor observes
2889 /// the same `Option<&Entrada>` presence bit unchanged).
2890 ///
2891 /// The outer `:entrada` slot carries the M3 mesh-slot per-
2892 /// Aplicacao typed external-gateway composite — the load-bearing
2893 /// container of every how-does-the-outside-world-reach-this-
2894 /// Aplicacao axis every caixa-mesh `Gateway`/`HTTPRoute` fan-out
2895 /// emitter fans on (MESH-COMPOSITION §II.5 — the Aplicacao's typed
2896 /// external-entry composite; §V CSE invariants — "the external
2897 /// gateway is a first-class typed composite, not a per-Servico
2898 /// ingress annotation" the per-axis scalars enforce; §III.4 — the
2899 /// typed hostname + backend-Servico pair the per-cluster Gateway-
2900 /// API renderer keys off). Every per-`:entrada` axis threads
2901 /// through a lifted per-slot accessor on the
2902 /// [`crate::aplicacao::Entrada`] type: the
2903 /// [`crate::aplicacao::Entrada::host`] Gateway-API `Listener.hostname`
2904 /// scalar, the [`crate::aplicacao::Entrada::para`] backend-Servico
2905 /// caixa-name scalar, the [`crate::aplicacao::Entrada::paths`]
2906 /// per-rule `HTTPPathMatch` list, the [`crate::aplicacao::Entrada::port`]
2907 /// backend `trigger.service.port` scalar, and the
2908 /// [`crate::aplicacao::Entrada::resolved_paths`] URL-path fallback
2909 /// resolver every HTTPRoute-aware renderer consumes. Every
2910 /// downstream consumer that reaches for an entry axis first passes
2911 /// through this outer accessor onto the composite and then
2912 /// dispatches onto the per-axis accessor — the two-level dispatch
2913 /// means every per-`:entrada` reader now routes through a typed
2914 /// dispatch on the substrate primitive at both altitudes.
2915 ///
2916 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2917 /// seed: the Aplicacao-view builder forwards the outer `Option`
2918 /// arm verbatim (no default fold — `:entrada` is inherently
2919 /// optional; a cluster-internal Aplicacao has no external gateway
2920 /// at all, not "an external gateway that defaults to nothing"), so
2921 /// the peer inner [`crate::AplicacaoSpec::entrada`] (d32111c)
2922 /// `Option<&Entrada>`-return accessor observes the same presence
2923 /// bit whether or not the author declared the outer slot. Routing
2924 /// the presence bit through this accessor keeps the
2925 /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2926 /// `M3_AUTHOR_KEY_ENTRADA` push separate from the inner
2927 /// [`crate::AplicacaoSpec::validate_entrada`]-gated
2928 /// hostname/backend/path emission dispatch.
2929 ///
2930 /// Prior to this lift the `.entrada` `Option<Entrada>` composite
2931 /// was accessed inline at two production sites — the
2932 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2933 /// `self.entrada.clone()` traversal head (caixa-core/src/manifest.rs:2182,
2934 /// which drives the forward onto the peer inner
2935 /// [`crate::AplicacaoSpec::entrada`] accessor the caixa-mesh
2936 /// Gateway-API fan-out then observes), and the
2937 /// [`Self::declared_mesh_slots`] M3 declared-slot-set enumerator's
2938 /// `self.entrada.is_some()` presence probe (caixa-core/src/manifest.rs:2248,
2939 /// which drives the `M3_AUTHOR_KEY_ENTRADA` kebab-case author-
2940 /// label push every [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
2941 /// kind-coherence gate reads) — two open-coded outer-field
2942 /// accesses that expressed no compile-time link back to the typed
2943 /// slot at the [`Caixa`] altitude. A future extension of the
2944 /// `:entrada` outer axis to a richer author surface (a per-cluster
2945 /// `:entrada-overrides` slot the operator materializes at admission
2946 /// time so a cluster-specific hostname can pin the caixa-declared
2947 /// bound without re-authoring the `caixa.lisp`, a per-tenant
2948 /// gateway-alias table the M4 `mesh.pleme.io/v1alpha1/Aplicacao`
2949 /// CR materializer resolves per-CR at admission time, a promotion
2950 /// of the plain `Option<Entrada>` to a richer
2951 /// `{public, private, internal}` partition once Cilium-identity-
2952 /// scoped internal gateways come into typed scope) would have had
2953 /// to be threaded through both open-coded copies in lockstep or the
2954 /// Aplicacao-composition seed's forward arm would silently
2955 /// disagree with the M3 declared-slot enumerator on which external-
2956 /// gateway composite a given Caixa resolves to — the seed reading
2957 /// an operator-resolved slot while the enumerator's presence probe
2958 /// read the raw slot would silently split the build-time gateway-
2959 /// artifact emission gate from the M3 declared-slot enumerator's
2960 /// kind-coherence gate, a two-consumer split far from the source
2961 /// `caixa.lisp` with no field naming the entry-drift root cause.
2962 /// Lifting the resolution rule to a typed method on the substrate
2963 /// primitive means every downstream consumer of the caixa's per-
2964 /// `Caixa` MESH-COMPOSITION external-gateway outer-composite
2965 /// surface reaches for exactly one typed dispatch — the resolver's
2966 /// accept-set migrates as a unit on any future axis addition.
2967 ///
2968 /// Fifth and final outer top-level [`Caixa`] `Option<&Composite>`-
2969 /// return composite-reference accessor — closes the outer-`Caixa`
2970 /// `Option<&Composite>` composite-reference sub-family opened by
2971 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) on the
2972 /// M2 Servico-runtime arm and extended onto the M3 mesh-slot arm
2973 /// by [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074),
2974 /// folds on the "one typed dispatch on the substrate primitive,
2975 /// thin projections at each consumer" discipline extended onto the
2976 /// third and final M3 mesh-slot axis. Peer of the closed inner
2977 /// mesh-slot outer-composite family the sibling
2978 /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2979 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2980 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2981 /// accessor pins already close on the inner
2982 /// [`crate::AplicacaoSpec`] altitude — this lift closes the mirror
2983 /// sub-family on the outer top-level [`Caixa`] altitude, so both
2984 /// altitudes of the outer-composite reference-return discipline
2985 /// (per-`Caixa` outer-slot presence + per-`AplicacaoSpec` inner-
2986 /// slot presence) now carry the full five-arm accept-set behind a
2987 /// typed dispatch on the substrate primitive. Returns
2988 /// `Option<&Entrada>` (not the owning composite by copy or clone)
2989 /// because every downstream consumer of the entrada composite
2990 /// treats it as a read-only per-axis dispatch source — the
2991 /// reference-view is the narrowest borrow that supports every
2992 /// present + roadmapped consumer (per-axis accessor dispatch,
2993 /// serde composite-serialization on the programs.yaml overlay,
2994 /// presence-probe early return on the "author-omitted `:entrada`
2995 /// ⇒ cluster-internal Aplicacao" partition, `Aplicacao`-composition
2996 /// seed's forward arm) without cloning the composite through every
2997 /// consumer's fast path. The `Option` half of the return-type
2998 /// preserves the load-bearing "author-omitted `:entrada` ⇒
2999 /// cluster-internal Aplicacao" partition (not a default composite
3000 /// the downstream must reject on emptiness — a cluster-internal
3001 /// Aplicacao has no external gateway at all, not "a default gateway
3002 /// that emits nothing"); the accessor projects the raw
3003 /// `Option<Entrada>` slot's presence bit through the reference-
3004 /// return unchanged. Named `entrada()` to match the storage field's
3005 /// name verbatim and the tatara-lisp author-surface term
3006 /// (`:entrada`) the field's own docstring already carries.
3007 #[must_use]
3008 pub const fn entrada(&self) -> Option<&crate::aplicacao::Entrada> {
3009 self.entrada.as_ref()
3010 }
3011
3012 /// Substrate-canonical per-`Caixa` `:ci` slot accessor — returns the
3013 /// author-declared typed CI run (`canteiro_types::CiRun`) verbatim as
3014 /// an `Option<&CiRun>`, borrowed from the typed slot's own
3015 /// `Option<CiRun>` storage. `None` when the slot is absent (every
3016 /// non-`Acao` kind, and an `Acao` caixa that hasn't declared `:ci`
3017 /// yet — the latter is caught by [`crate::LayoutError::MissingCi`],
3018 /// not silently accepted).
3019 ///
3020 /// Named `ci()` to match the storage field's name and the
3021 /// tatara-lisp author surface (`:ci`); mirrors the sibling
3022 /// `Option<&Composite>` accessors on this same `Caixa` altitude
3023 /// ([`Self::limits`], [`Self::behavior`], [`Self::politicas`],
3024 /// [`Self::placement`], [`Self::entrada`]) — one typed dispatch on
3025 /// the substrate primitive rather than an open-coded `self.ci.as_ref()`
3026 /// at every consumer.
3027 #[must_use]
3028 pub const fn ci(&self) -> Option<&canteiro_types::CiRun> {
3029 self.ci.as_ref()
3030 }
3031
3032 /// Substrate-canonical per-`Caixa` `:estrategia` M2 supervisor-tree-
3033 /// slot flat-spread OTP-shaped sibling-restart-strategy discriminant
3034 /// accessor every consumer of the top-level manifest's per-Supervisor
3035 /// restart-strategy axis keys off — returns the author-declared
3036 /// `:estrategia` variant verbatim as an `Option<RestartStrategy>`,
3037 /// `Copy`-projected from the typed slot's own
3038 /// `Option<crate::supervisor::RestartStrategy>` storage. Optional
3039 /// (`:estrategia` is a flat-spread supervisor-only slot every
3040 /// non-`Supervisor`-kind `defcaixa` carries as `None` by
3041 /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
3042 /// still omit to defer to [`RestartStrategy::default`] —
3043 /// [`RestartStrategy::OneForOne`] — through the [`Self::supervisor_view`]
3044 /// `unwrap_or_default()` fold; a returned `None` degenerates to the
3045 /// [`SupervisorSpec::default`]-inherited strategy without any silent
3046 /// promotion to a fresh explicit variant at the accessor boundary).
3047 ///
3048 /// The `:estrategia` slot carries the M2 typed OTP-shaped sibling-
3049 /// restart-strategy discriminant every substrate-side per-Supervisor
3050 /// dispatch fans on (INSPIRATIONS §II.2 — OTP `supervisor:strategy`
3051 /// closed-set `one_for_one | one_for_all | rest_for_one |
3052 /// simple_one_for_one` algebra translated onto pleme-io's typed
3053 /// [`RestartStrategy`] enum; CAIXA-SDLC §II — the M2 supervisor-tree
3054 /// slot algebra the operator's hierarchical reconciliation scheduler
3055 /// fans on). The slot is *flat-spread* on the outer top-level `Caixa`
3056 /// (per the field-shape docstring at caixa-core/src/manifest.rs — "The
3057 /// supervisor slots are flat on Caixa (vs nested under a
3058 /// `SupervisorSpec` sub-form) to keep tatara-lisp authoring at one
3059 /// level of nesting"), so the accessor's altitude is the outer
3060 /// [`Caixa`] surface rather than the composed [`SupervisorSpec`]
3061 /// altitude the sibling [`crate::supervisor::SupervisorSpec::estrategia`]
3062 /// (eafb619) accessor keys off. The two typed axes — the outer
3063 /// author-surface `Option<RestartStrategy>` on the [`Caixa`] altitude
3064 /// (author-omitted arm carried as `None`) and the inner post-
3065 /// composition `RestartStrategy` on the [`SupervisorSpec`] altitude
3066 /// (`Option` collapsed through the [`Self::supervisor_view`]
3067 /// `unwrap_or_default()` fold) — now share one accessor discipline for
3068 /// the shared substrate concept "the author-declared OTP-shaped
3069 /// sibling-restart-strategy variant that partitions the downstream
3070 /// per-Supervisor renderer's per-arm fan-out"; the outer-altitude
3071 /// `None` arm is the pre-composition presence bit every declared-slot
3072 /// enumerator ([`Self::declared_supervisor_slots`]) reads, and the
3073 /// inner-altitude non-`Option` `RestartStrategy` is the post-
3074 /// composition partition-dispatch input every strategy-arm consumer
3075 /// ([`SupervisorSpec::validate`], the future wasm-operator's per-
3076 /// Supervisor sibling-restart branch, the future M4
3077 /// `mesh.pleme.io/v1alpha1/Supervisor` CR materializer's admission
3078 /// webhook) fans on.
3079 ///
3080 /// Prior to this lift the `.estrategia` field was accessed inline at
3081 /// two production sites in `caixa-core/src/manifest.rs` — the
3082 /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`
3083 /// presence-probe arm at `if self.estrategia.is_some()` (which drives
3084 /// the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
3085 /// coherence gate's per-slot label push) and the [`Self::supervisor_view`]
3086 /// `SupervisorSpec` construction site at `estrategia:
3087 /// self.estrategia.unwrap_or_default()` (which composes the flat-
3088 /// spread outer author-surface `Option<RestartStrategy>` onto the
3089 /// inner post-composition [`SupervisorSpec`] `RestartStrategy` field
3090 /// the [`SupervisorSpec::estrategia`] accessor keys off) — two open-
3091 /// coded field-accesses that expressed no compile-time link back to
3092 /// the typed slot. A future extension of the outer `:estrategia` axis
3093 /// to a richer author surface (a per-cluster strategy override the
3094 /// operator pins through a future `:estrategia-overrides` overlay the
3095 /// MESH-COMPOSITION §III.2 supervision-canary roadmap acknowledges,
3096 /// a per-tenant strategy-alias table the M4 CR materializer resolves
3097 /// per-CR, a per-Supervisor dynamic strategy derivation the future
3098 /// adaptive-supervision engine computes from child-failure-history
3099 /// topology, a per-child-cohort strategy split the future
3100 /// `RestForCohort` extension the INSPIRATIONS.md §II.2 Erlang/OTP
3101 /// absorption roadmap acknowledges, a promotion of the plain
3102 /// `Option<RestartStrategy>` to a richer
3103 /// `AuthorDeclaredStrategy { declared, overlay }` newtype once the
3104 /// operator-resolved overlay lands) would have had to be threaded
3105 /// through both open-coded copies in lockstep or the enumerator's
3106 /// presence probe and the composition site's `unwrap_or_default()`
3107 /// fold would silently disagree on which strategy a given [`Caixa`]
3108 /// resolves to (an author's `:estrategia OneForAll` would satisfy
3109 /// the enumerator's presence probe while the composition site
3110 /// silently rendered a stale `OneForOne`, or vice versa). Lifting
3111 /// the resolution rule to a typed method on the substrate primitive
3112 /// means every downstream consumer of the caixa's per-`Caixa` outer-
3113 /// altitude sibling-restart-strategy surface reaches for exactly one
3114 /// typed dispatch — the resolver's accept-set migrates as a unit on
3115 /// any future axis addition.
3116 ///
3117 /// First outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
3118 /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
3119 /// axes — opens the outer-`Caixa` `Option<Copy>` flat-spread
3120 /// projection pattern the sibling per-`Caixa` `:max-restarts`
3121 /// `Option<u32>` and (through the future duration-newtype landing)
3122 /// `:restart-window` `Option<Duration>` future outer-scalar lifts
3123 /// fold on. Peer of the inner-altitude [`crate::supervisor::SupervisorSpec::estrategia`]
3124 /// (eafb619) `Copy`-return sibling-restart-strategy scalar accessor on
3125 /// the post-composition [`SupervisorSpec`] altitude — same "one
3126 /// typed dispatch on the substrate primitive, thin projections at
3127 /// each consumer" discipline extended onto the pre-composition outer
3128 /// author-surface [`Caixa`] altitude for the same OTP-shaped
3129 /// sibling-restart-strategy axis. Peer of the closed outer-`Caixa`
3130 /// `Option<&Composite>` composite-reference family the sibling
3131 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
3132 /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
3133 /// [`Self::entrada`] (e4128e4) accessor pins already carry on the
3134 /// outer `Option<&Composite>` altitude — extends the outer-`Caixa`
3135 /// typed-slot accessor discipline onto the flat-spread M2 supervisor-
3136 /// tree `Option<Copy>`-discriminant sub-family the sibling M3
3137 /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
3138 /// `PlacementStrategy` `Copy`-composite-enum scalar accessor already
3139 /// pins on the inner-altitude per-`:placement` composite. Named
3140 /// `estrategia()` to match the storage field's name and the
3141 /// per-[`SupervisorSpec`] peer [`crate::supervisor::SupervisorSpec::estrategia`]
3142 /// / per-[`crate::aplicacao::Placement`] peer
3143 /// [`crate::aplicacao::Placement::estrategia`] method-name discipline
3144 /// verbatim; the accessor's identity name maps onto the canonical
3145 /// OTP-shape supervision vocabulary the [`RestartStrategy`] enum's
3146 /// docstring already carries.
3147 #[must_use]
3148 pub const fn estrategia(&self) -> Option<crate::supervisor::RestartStrategy> {
3149 self.estrategia
3150 }
3151
3152 /// Substrate-canonical per-`Caixa` `:max-restarts` M2 supervisor-tree-
3153 /// slot flat-spread OTP-`MaxIntensity`-shaped restart-budget-count
3154 /// scalar accessor every consumer of the top-level manifest's per-
3155 /// Supervisor `:max-restarts` restart-budget-count axis keys off —
3156 /// returns the author-declared `:max-restarts` typed `Option<u32>`
3157 /// verbatim, `Copy`-projected from the typed slot's own `Option<u32>`
3158 /// storage (`u32` is `Copy`, so `Option<u32>` is `Copy` and the
3159 /// accessor returns by value; no borrow of `&self` past the call).
3160 /// Optional (`:max-restarts` is a flat-spread supervisor-only slot
3161 /// every non-`Supervisor`-kind `defcaixa` carries as `None` by
3162 /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
3163 /// still omit to defer to the [`Self::supervisor_view`]
3164 /// `unwrap_or(5)` fold's OTP-canonical `{intensity, 5, 60}` default).
3165 ///
3166 /// The `:max-restarts` slot carries the M2 typed Erlang/OTP-shaped
3167 /// `MaxIntensity` restart-budget count that pairs with the sibling
3168 /// `:restart-window` `Period` to form the `MaxIntensity / Period`
3169 /// restart-intensity ratio the supervisor trips its own escalation on
3170 /// (INSPIRATIONS §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}`
3171 /// worker-supervisor default; RUNTIME-PATTERNS §II.2; CAIXA-SDLC §II
3172 /// — the M2 supervisor-tree slot algebra the operator's hierarchical
3173 /// reconciliation scheduler fans on). The slot is *flat-spread* on
3174 /// the outer top-level `Caixa` (per the field-shape docstring at
3175 /// caixa-core/src/manifest.rs — "The supervisor slots are flat on
3176 /// Caixa (vs nested under a `SupervisorSpec` sub-form)"), so the
3177 /// accessor's altitude is the outer [`Caixa`] surface rather than the
3178 /// composed [`SupervisorSpec`] altitude the sibling
3179 /// [`crate::supervisor::SupervisorSpec::max_restarts`] accessor keys
3180 /// off. The two typed axes — the outer author-surface `Option<u32>`
3181 /// on the [`Caixa`] altitude (author-omitted arm carried as `None`)
3182 /// and the inner post-composition `u32` on the [`SupervisorSpec`]
3183 /// altitude (`Option` collapsed through the [`Self::supervisor_view`]
3184 /// `unwrap_or(5)` fold) — now share one accessor discipline for the
3185 /// shared substrate concept "the author-declared OTP-shaped
3186 /// restart-budget count every downstream per-Supervisor consumer's
3187 /// restart-intensity budget-vs-count comparator fans on".
3188 ///
3189 /// Prior to this lift the `.max_restarts` field was accessed inline
3190 /// at two production sites in `caixa-core/src/manifest.rs` — the
3191 /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`
3192 /// presence-probe arm at `if self.max_restarts.is_some()` (which
3193 /// drives the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
3194 /// kind-coherence gate's per-slot label push) and the
3195 /// [`Self::supervisor_view`] `SupervisorSpec` construction site at
3196 /// `max_restarts: self.max_restarts.unwrap_or(5)` (which composes the
3197 /// flat-spread outer author-surface `Option<u32>` onto the inner
3198 /// post-composition [`SupervisorSpec`] `u32` field the
3199 /// [`SupervisorSpec::max_restarts`] accessor keys off) — two open-
3200 /// coded field-accesses that expressed no compile-time link back to
3201 /// the typed slot. A future extension of the outer `:max-restarts`
3202 /// axis to a richer author surface (a per-cluster restart-budget
3203 /// override the operator pins through a future `:max-restarts-overrides`
3204 /// overlay the MESH-COMPOSITION §III.2 supervision-canary roadmap
3205 /// acknowledges, a per-tenant restart-budget-alias table the M4 CR
3206 /// materializer resolves per-CR, a per-Supervisor dynamic restart-
3207 /// budget derivation the future adaptive-supervision engine computes
3208 /// from child-failure-history topology, a promotion of the plain
3209 /// `Option<u32>` count to a richer `{MaxR, MaxT}` per-child-cohort
3210 /// restart-budget-partition once the INSPIRATIONS §II.2 Erlang/OTP
3211 /// per-child-cohort roadmap lands) would have had to be threaded
3212 /// through both open-coded copies in lockstep or the enumerator's
3213 /// presence probe and the composition site's `unwrap_or(5)` fold
3214 /// would silently disagree on which restart-budget a given [`Caixa`]
3215 /// resolves to (an author's `:max-restarts 10` would satisfy the
3216 /// enumerator's presence probe while the composition site silently
3217 /// composed the OTP-canonical `5`, or vice versa). Lifting the
3218 /// resolution rule to a typed method on the substrate primitive means
3219 /// every downstream consumer of the caixa's per-`Caixa` outer-altitude
3220 /// restart-budget-count surface reaches for exactly one typed dispatch
3221 /// — the resolver's accept-set migrates as a unit on any future axis
3222 /// addition.
3223 ///
3224 /// Second outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
3225 /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
3226 /// axes — folds on the outer-`Caixa` `Option<Copy>` flat-spread
3227 /// projection pattern the sibling per-`Caixa`
3228 /// [`Self::estrategia`] (ed04d3c) accessor opened, extends the
3229 /// sub-family onto the sibling `Option<u32>` restart-budget-count arm.
3230 /// Peer of the inner-altitude
3231 /// [`crate::supervisor::SupervisorSpec::max_restarts`] `u32` accessor
3232 /// on the post-composition [`SupervisorSpec`] altitude — same "one
3233 /// typed dispatch on the substrate primitive, thin projections at
3234 /// each consumer" discipline extended onto the pre-composition outer
3235 /// author-surface [`Caixa`] altitude for the same OTP-`MaxIntensity`-
3236 /// shaped restart-budget-count axis. Named `max_restarts()` to match
3237 /// the storage field's name and the per-[`SupervisorSpec`] peer
3238 /// [`crate::supervisor::SupervisorSpec::max_restarts`] method-name
3239 /// discipline verbatim; the accessor's identity maps onto the
3240 /// canonical OTP-shape supervision vocabulary the `:max-restarts`
3241 /// field's docstring already carries.
3242 #[must_use]
3243 pub const fn max_restarts(&self) -> Option<u32> {
3244 self.max_restarts
3245 }
3246
3247 /// Substrate-canonical per-`Caixa` `:restart-window` M2 supervisor-
3248 /// tree-slot flat-spread OTP-`Period`-shaped restart-intensity-
3249 /// denominator raw-duration-string scalar accessor every consumer of
3250 /// the top-level manifest's per-Supervisor `:restart-window` sliding-
3251 /// window axis keys off — returns the author-declared `:restart-window`
3252 /// typed `Option<String>` verbatim as an `Option<&str>`, borrowed
3253 /// from the typed slot's own `Option<String>` storage. `None` when
3254 /// the slot is absent (the canonical "never reset — every restart
3255 /// across the supervisor's lifetime counts against the sibling
3256 /// `:max-restarts` budget" sentinel every non-`Supervisor`-kind
3257 /// `defcaixa` carries by `#[serde(default)]` and every
3258 /// `Supervisor`-kind `defcaixa` may still omit to defer to the
3259 /// [`Self::supervisor_view`] `restart_window: None` composition
3260 /// through the [`crate::supervisor::duration_codec::parse`] soft-
3261 /// swallow `.and_then(|s| … .ok())` fold).
3262 ///
3263 /// The `:restart-window` slot carries the raw M2 typed Erlang/OTP-
3264 /// shaped `Period` sliding-observation-interval duration string that
3265 /// pairs with the sibling `:max-restarts` `MaxIntensity` restart-
3266 /// budget count to form the `MaxIntensity / Period` restart-intensity
3267 /// ratio the supervisor trips its own escalation on (INSPIRATIONS
3268 /// §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}` worker-
3269 /// supervisor default; RUNTIME-PATTERNS §II.2). The outer-`Caixa`
3270 /// slot stores the raw duration string (`"60s"`, `"5m"`, `"500ms"`)
3271 /// authored under `:restart-window` — the typed [`SupervisorSpec`]
3272 /// holds an `Option<Duration>` routed through the shared
3273 /// [`crate::supervisor::duration_codec`] via `with = "duration_codec"`
3274 /// — so the outer altitude's accessor returns `Option<&str>` (raw
3275 /// authoring surface) while the inner altitude's
3276 /// [`crate::supervisor::SupervisorSpec::restart_window`] returns
3277 /// `Option<Duration>` (parsed typed surface). The parse-refusal arm
3278 /// is closed by the sibling [`Self::validate_restart_window`] gate
3279 /// that surfaces [`ManifestError::RestartWindowMalformed`] naming
3280 /// the offending value; the view-construction path
3281 /// [`Self::supervisor_view`] soft-swallows the same parse error to
3282 /// `None` to keep the view best-effort.
3283 ///
3284 /// Prior to this lift the `.restart_window` field was accessed inline
3285 /// at three production sites in `caixa-core/src/manifest.rs` — the
3286 /// [`Self::declared_supervisor_slots`]
3287 /// `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` presence-probe arm at
3288 /// `if self.restart_window.is_some()` (which drives the
3289 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
3290 /// coherence gate's per-slot label push), the
3291 /// [`Self::validate_restart_window`] `let Some(s) =
3292 /// self.restart_window.as_deref()` empty-and-shape gate binding
3293 /// (which folds the raw string through the shared
3294 /// [`crate::supervisor::duration_codec::parse`] to surface
3295 /// [`ManifestError::RestartWindowMalformed`] naming the offending
3296 /// value), and the [`Self::supervisor_view`] `self.restart_window
3297 /// .as_deref().and_then(…)` view-construction fold (which composes
3298 /// the flat-spread outer author-surface `Option<String>` onto the
3299 /// inner post-composition [`SupervisorSpec`] `Option<Duration>`
3300 /// field the [`SupervisorSpec::restart_window`] accessor keys off) —
3301 /// three open-coded field-accesses that expressed no compile-time
3302 /// link back to the typed slot. A future extension of the outer
3303 /// `:restart-window` axis to a richer author surface (a per-cluster
3304 /// window override, a per-tenant window-alias table, a per-Supervisor
3305 /// dynamic window derivation the future adaptive-supervision engine
3306 /// computes from child-failure-history topology, a promotion of the
3307 /// plain `Option<String>` raw duration to a typed `Option<Duration>`
3308 /// once the future author-surface parser lands at the [`Caixa`]
3309 /// altitude and the raw-string form is retired) would have had to be
3310 /// threaded through every open-coded copy in lockstep or the three
3311 /// consumers would silently disagree on which raw string a given
3312 /// [`Caixa`] resolves to. Lifting the resolution rule to a typed
3313 /// method on the substrate primitive means every downstream consumer
3314 /// of the caixa's per-`Caixa` outer-altitude restart-window raw-
3315 /// string surface reaches for exactly one typed dispatch — the
3316 /// resolver's accept-set migrates as a unit on any future axis
3317 /// addition.
3318 ///
3319 /// Third outer top-level [`Caixa`] supervisor-tree-slot flat-spread
3320 /// accessor — folds on the outer-`Caixa` M2 supervisor-tree flat-
3321 /// spread projection pattern the sibling per-`Caixa`
3322 /// [`Self::estrategia`] (ed04d3c) `Option<Copy>` and
3323 /// [`Self::max_restarts`] `Option<Copy>` accessors opened, extends
3324 /// the sub-family onto the sibling `Option<&str>` raw-duration-
3325 /// string arm (the outer altitude's raw-string form; the inner
3326 /// altitude's parsed [`Duration`] form is the peer
3327 /// [`crate::supervisor::SupervisorSpec::restart_window`] accessor).
3328 /// Peer of the sibling per-`Caixa` `Option<&str>`-return scalar
3329 /// accessors ([`Self::licenca`] / [`Self::repositorio`] /
3330 /// [`Self::descricao`] / [`Self::edicao`]) on the universal-axis
3331 /// outer scalar-projection family the outer-`Caixa` `Option<&str>`
3332 /// sub-family already carries — same "one typed dispatch on the
3333 /// substrate primitive, thin projections at each consumer"
3334 /// discipline extended onto the M2 supervisor-tree flat-spread
3335 /// `Option<&str>` raw-duration-string arm. Named `restart_window()`
3336 /// to match the storage field's name and the per-[`SupervisorSpec`]
3337 /// peer [`crate::supervisor::SupervisorSpec::restart_window`]
3338 /// method-name discipline verbatim; the accessor's identity maps
3339 /// onto the canonical OTP-shape supervision vocabulary the
3340 /// `:restart-window` field's docstring already carries.
3341 #[must_use]
3342 pub const fn restart_window(&self) -> Option<&str> {
3343 match &self.restart_window {
3344 Some(s) => Some(s.as_str()),
3345 None => None,
3346 }
3347 }
3348
3349 /// Substrate-canonical per-`Caixa` `:upgrade-from` M2 typed-slot
3350 /// outer-composite OTP-appup-shaped per-prior-version migration-
3351 /// entry-list slice accessor every consumer of the top-level
3352 /// manifest's per-Servico hot-upgrade-block `&[UpgradeFromEntry]`
3353 /// slice-view keys off — returns the author-declared `:upgrade-from`
3354 /// typed `Vec<UpgradeFromEntry>` verbatim as a
3355 /// `&[UpgradeFromEntry]` slice-view over the same backing buffer
3356 /// the raw `self.upgrade_from.as_slice()` field access borrows
3357 /// from. Empty-slice-carrying (the "no hot-upgrade path declared"
3358 /// arm every `defcaixa` without an `:upgrade-from` block carries;
3359 /// the [`Self::from_lisp`] derive folds an omitted `:upgrade-from`
3360 /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
3361 /// parse definitionally carries a `Vec<UpgradeFromEntry>` slot —
3362 /// possibly empty — and the returned `&[UpgradeFromEntry]`
3363 /// degenerates to an empty slice on that arm without any silent
3364 /// `None` collapse).
3365 ///
3366 /// The outer `:upgrade-from` slot carries the M2 typed OTP-appup
3367 /// migration block — the load-bearing container of every per-
3368 /// prior-`:versao` migration-instruction list the wasm-operator
3369 /// dispatches on at hot-upgrade time (INSPIRATIONS §II.4 — OTP
3370 /// `.appup` per-prior-version `LoadModule | StateChange |
3371 /// SoftPurge | Purge | Restart` instruction algebra translated
3372 /// onto pleme-io's typed `:upgrade-from :from` + `:instructions`
3373 /// entry list; CAIXA-SDLC §II — the typed-M2 slot algebra the
3374 /// operator's hot-upgrade dispatch fans on). Every per-entry axis
3375 /// threads through a lifted per-entry accessor on the
3376 /// [`UpgradeFromEntry`] type: the
3377 /// [`UpgradeFromEntry::prior_versao`] SemVer-shaped previous-
3378 /// version scalar accessor and the
3379 /// [`UpgradeFromEntry::instructions`] `&[UpgradeInstruction]`-
3380 /// return per-entry instruction-list accessor (0137e5a). Every
3381 /// downstream consumer of the hot-upgrade path first passes
3382 /// through this outer accessor onto the slice and then dispatches
3383 /// per-entry through the inner accessors — the two-level dispatch
3384 /// means every per-`:upgrade-from` reader now routes through a
3385 /// typed dispatch on the substrate primitive at both altitudes.
3386 ///
3387 /// Prior to this lift the `.upgrade_from` `Vec<UpgradeFromEntry>`
3388 /// slot was accessed inline at production sites across three
3389 /// files — the [`Self::declared_servico_slots`] M2 declared-slot
3390 /// enumerator's `self.upgrade_from.is_empty()` presence probe
3391 /// (caixa-core/src/manifest.rs, which drives the
3392 /// `M2_AUTHOR_KEY_UPGRADE_FROM` kebab-case author-label push every
3393 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
3394 /// gate reads), the [`crate::StandardLayout::verify`] per-
3395 /// `:upgrade-from` three-stage validation pass (caixa-core/src/
3396 /// layout.rs, which fans onto the
3397 /// [`crate::upgrade::validate_upgrade_from`] per-entry shape +
3398 /// cross-entry duplicate gate, the
3399 /// [`crate::upgrade::validate_upgrade_from_against_versao`]
3400 /// SemVer-precedence cross-slot gate, the
3401 /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
3402 /// `:state-change` ↔ `:on-state-change` cross-slot composition
3403 /// gate, and the per-instruction script-path existence-probe walk
3404 /// that reads each entry's [`UpgradeFromEntry::instructions`] to
3405 /// resolve every declared migration script against the layout
3406 /// root), and the [`crate::render::servico_m2_overlay`] per-
3407 /// Servico M2 overlay emitter's `!caixa.upgrade_from.is_empty()`
3408 /// presence gate + `serde_yaml::to_value(&caixa.upgrade_from)`
3409 /// projection (caixa-core/src/render.rs, which drives the
3410 /// `M2_KEY_UPGRADE_FROM`-keyed `serde_yaml` projection every
3411 /// `caixa-helm` / `caixa-flux` Servico values-block emitter fans
3412 /// on and lands as the ComputeUnit CR's `spec.upgradeFrom` field).
3413 /// A future extension of the outer `:upgrade-from` axis (a per-
3414 /// cluster `:upgrade-overrides` overlay the wasm-engine operator
3415 /// resolves at admission time so a cluster-specific migration
3416 /// policy can tighten a caixa-declared step without re-authoring
3417 /// the `caixa.lisp`, promotion of the plain
3418 /// `Vec<UpgradeFromEntry>` to a richer `{static, dynamic}`
3419 /// partition once runtime-resolved hot-upgrade instructions land,
3420 /// per-entry priority annotation once multi-strategy fan-out
3421 /// lands) would have had to be threaded through all six open-
3422 /// coded copies in lockstep or one consumer would silently
3423 /// disagree with the peers on which upgrade slice a given Caixa
3424 /// resolves to — a six-consumer split at the enumerator, the
3425 /// three-stage validate pass, the script-path probe walk, and the
3426 /// M2 overlay emitter, far from the source `caixa.lisp` with no
3427 /// field naming the upgrade-drift root cause. Lifting the
3428 /// resolution rule to a typed method on the substrate primitive
3429 /// means every downstream consumer of the caixa's per-`Caixa`
3430 /// OTP-appup outer-slice surface reaches for exactly one typed
3431 /// dispatch — the resolver's accept-set migrates as a unit on any
3432 /// future axis addition.
3433 ///
3434 /// First outer top-level [`Caixa`] `&[Composite]`-return slice
3435 /// accessor for M2 / M3 typed-slot vec-carry axes — opens the
3436 /// outer-`Caixa` `&[Composite]` composite-slice projection
3437 /// pattern the sibling `:children`
3438 /// [`crate::supervisor::ChildSpec`] / `:membros`
3439 /// [`crate::aplicacao::Membro`] / `:contratos`
3440 /// [`crate::aplicacao::WitContract`] future outer-composite-slice
3441 /// lifts fold on. Peer of the closed outer-`Caixa` scalar
3442 /// `Option<&Composite>` composite-reference family the sibling
3443 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
3444 /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
3445 /// [`Self::entrada`] (e4128e4) accessors closed on the outer
3446 /// `Option<&Composite>` altitude, extended here to the outer-
3447 /// `Caixa` `&[Composite]` vec-carry altitude. Peer at the inner
3448 /// altitude of [`crate::upgrade::UpgradeFromEntry::instructions`]
3449 /// (0137e5a) — same "one typed dispatch on the substrate
3450 /// primitive, thin projections at each consumer" discipline
3451 /// folded onto the outer top-level [`Caixa`] altitude, opening the
3452 /// M2 vec-carry slot family's outer-composite-slice axis. Sibling
3453 /// in shape to the peer outer-`Caixa` `&[Dep]`-return
3454 /// [`Self::deps`] (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and
3455 /// `&[String]`-return [`Self::autores`] (b5d813f) /
3456 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`]
3457 /// (8a36c23) / [`Self::exe`] (65d9527) / [`Self::servicos`]
3458 /// (611f78b) slice-accessors on the sibling outer-`Caixa` scalar-
3459 /// element vec-carry axes — folds the "outer [`Caixa`] `&[T]`
3460 /// slice" projection pattern onto the sibling M2 typed-composite-
3461 /// element axis (`UpgradeFromEntry` composite, matching the
3462 /// per-inner [`UpgradeFromEntry::instructions`] element type at a
3463 /// different altitude).
3464 ///
3465 /// Returns `&[UpgradeFromEntry]` (not `&Vec<UpgradeFromEntry>`)
3466 /// because every downstream consumer of the hot-upgrade list
3467 /// treats it as a read-only sequence — the slice-view is the
3468 /// narrowest borrow that supports every present + roadmapped
3469 /// consumer (`.iter()`, `.len()`, `.is_empty()`, `serde` slice-
3470 /// serialization through
3471 /// `serde_yaml::to_value(&[UpgradeFromEntry])`) without leaking
3472 /// the backing `Vec`'s grow/push/reserve surface no consumer of
3473 /// the typed view reaches for (the storage-side `Vec` remains
3474 /// reachable through the `pub upgrade_from` field for the
3475 /// mutation-carrying serde round-trip and per-test fixture-
3476 /// mutation paths). Named `upgrade_from()` to match the storage
3477 /// field's `snake_case` name; the kebab-case author-surface tag
3478 /// `:upgrade-from` is the same axis after tatara-lisp's
3479 /// kebab↔snake fold and the accessor's identity maps onto the
3480 /// canonical CAIXA-SDLC §II vocabulary the slot's docstring
3481 /// already carries.
3482 #[must_use]
3483 pub const fn upgrade_from(&self) -> &[UpgradeFromEntry] {
3484 self.upgrade_from.as_slice()
3485 }
3486
3487 /// Substrate-canonical per-`Caixa` `:children` M2 supervisor-tree-
3488 /// slot outer-composite OTP-shaped per-supervisor static-child-list
3489 /// slice accessor every consumer of the top-level manifest's per-
3490 /// Supervisor `&[ChildSpec]` slice-view keys off — returns the
3491 /// author-declared `:children` typed `Vec<crate::supervisor::ChildSpec>`
3492 /// verbatim as a `&[crate::supervisor::ChildSpec]` slice-view over
3493 /// the same backing buffer the raw `self.children.as_slice()` field
3494 /// access borrows from. Empty-slice-carrying (the "no static children
3495 /// declared" arm every non-`Supervisor`-kind `defcaixa` carries by
3496 /// #[serde(default)] and every `SimpleOneForOne` supervisor carries
3497 /// by [`crate::supervisor::SupervisorError::SimpleOneForOneWithStaticChildren`]
3498 /// gate; the returned `&[ChildSpec]` degenerates to an empty slice
3499 /// on those arms without any silent `None` collapse).
3500 ///
3501 /// The outer `:children` slot carries the M2 typed OTP-supervisor
3502 /// static-child list — the load-bearing container of every per-
3503 /// child `{caixa, versao, restart}` triple the wasm-operator's
3504 /// hierarchical reconciler dispatches on at supervisor-tree
3505 /// materialization time (INSPIRATIONS §II.2 — OTP `supervisor:init/1`
3506 /// static-child list translated onto pleme-io's typed
3507 /// [`crate::supervisor::ChildSpec`] entry list; CAIXA-SDLC §II —
3508 /// the typed-M2 slot algebra the operator's per-supervisor fan-out
3509 /// dispatch fans on). Every per-child axis threads through a lifted
3510 /// per-entry accessor on the [`crate::supervisor::ChildSpec`] type:
3511 /// the [`crate::supervisor::ChildSpec::nome`] DNS-1123-label
3512 /// child-caixa-identity scalar accessor, the peer versao SemVer-2
3513 /// version-requirement scalar accessor, and the
3514 /// [`crate::supervisor::ChildSpec::restart`] `Copy`-composite-enum
3515 /// per-child post-exit restart-decision-policy discriminant
3516 /// accessor (dfb4a81). Every downstream consumer of the supervisor-
3517 /// tree path first passes through this outer accessor onto the
3518 /// slice and then dispatches per-child through the inner accessors
3519 /// — the two-level dispatch means every per-`:children` reader now
3520 /// routes through a typed dispatch on the substrate primitive at
3521 /// both altitudes.
3522 ///
3523 /// Prior to this lift the `.children` `Vec<ChildSpec>` slot was
3524 /// accessed inline at three production sites across two files —
3525 /// the [`Self::declared_supervisor_slots`] supervisor-tree
3526 /// declared-slot enumerator's `!self.children.is_empty()` presence
3527 /// probe (caixa-core/src/manifest.rs, which drives the
3528 /// `SUPERVISOR_AUTHOR_KEY_CHILDREN` kebab-case author-label push
3529 /// every [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
3530 /// kind-coherence gate reads), the [`Self::supervisor_view`]
3531 /// per-supervisor typed-view composer's `self.children.clone()`
3532 /// per-child fold-in path (caixa-core/src/manifest.rs, which
3533 /// materializes the typed [`crate::supervisor::SupervisorSpec`]
3534 /// view every [`crate::StandardLayout::verify`] Supervisor-arm gate
3535 /// dispatches on), and the [`crate::StandardLayout::verify`] per-
3536 /// `:children :caixa` self-parent refusal probe's
3537 /// `&caixa.children`-borrowed
3538 /// [`crate::supervisor::validate_no_self_supervision`] input
3539 /// (caixa-core/src/layout.rs, which pins the "no child names the
3540 /// supervisor's own `:nome`" cross-slot coherence gate). A future
3541 /// extension of the outer `:children` axis (a per-cluster
3542 /// `:children-overrides` overlay the wasm-engine operator resolves
3543 /// at admission time so a cluster-specific child-set can tighten
3544 /// a caixa-declared list without re-authoring the `caixa.lisp`,
3545 /// promotion of the plain `Vec<ChildSpec>` to a richer
3546 /// `{static, dynamic}` partition once Erlang/OTP's
3547 /// `simple_one_for_one`-shaped dynamic-child slot lands as a typed
3548 /// axis, per-child priority annotation once multi-strategy fan-out
3549 /// lands) would have had to be threaded through all three open-
3550 /// coded copies in lockstep or one consumer would silently
3551 /// disagree with the peers on which child slice a given Caixa
3552 /// resolves to — the enumerator's presence probe reading the raw
3553 /// slot while the peer view-composer's fold-in path read an
3554 /// operator-resolved slot would silently split the paired
3555 /// declared-slot enumerator and typed-view composition, and the
3556 /// [`crate::supervisor::validate_no_self_supervision`] self-parent
3557 /// refusal probe reading a third borrow would silently drift the
3558 /// cross-slot coherence gate's traversal input from the two peers,
3559 /// a three-consumer split at the enumerator, the view composer,
3560 /// and the self-parent gate far from the source `caixa.lisp` with
3561 /// no field naming the child-set-drift root cause. Lifting the
3562 /// resolution rule to a typed method on the substrate primitive
3563 /// means every downstream consumer of the caixa's per-`Caixa`
3564 /// OTP-supervisor outer-slice surface reaches for exactly one
3565 /// typed dispatch — the resolver's accept-set migrates as a unit
3566 /// on any future axis addition.
3567 ///
3568 /// Second outer top-level [`Caixa`] `&[Composite]`-return slice
3569 /// accessor for M2 / M3 typed-slot vec-carry axes — folds on the
3570 /// outer-`Caixa` `&[Composite]` composite-slice sub-family the
3571 /// sibling [`Self::upgrade_from`] (2a1f907) accessor opened, peer
3572 /// at the outer altitude of the closed inner-`SupervisorSpec`
3573 /// [`crate::SupervisorSpec::children`] (bc92bce) accessor on the
3574 /// same OTP-supervisor static-child-list axis — same "byte-equal,
3575 /// borrow-shared" outer-accessor discipline extended onto the
3576 /// second outer-`Caixa` `&[Composite]` vec-carry axis. Sibling in
3577 /// shape to the peer outer-`Caixa` `&[Dep]`-return [`Self::deps`]
3578 /// (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and `&[String]`-return
3579 /// [`Self::autores`] (b5d813f) / [`Self::etiquetas`] (78c7d3c) /
3580 /// [`Self::bibliotecas`] (8a36c23) / [`Self::exe`] (65d9527) /
3581 /// [`Self::servicos`] (611f78b) slice-accessors on the sibling
3582 /// outer-`Caixa` scalar-element vec-carry axes — folds the "outer
3583 /// [`Caixa`] `&[T]` slice" projection pattern onto the sibling
3584 /// M2 typed-composite-element axis
3585 /// ([`crate::supervisor::ChildSpec`] composite, matching the
3586 /// per-inner [`crate::SupervisorSpec::children`] element type at a
3587 /// different altitude).
3588 ///
3589 /// Returns `&[crate::supervisor::ChildSpec]` (not
3590 /// `&Vec<ChildSpec>`) because every downstream consumer of the
3591 /// child list treats it as a read-only sequence — the slice-view
3592 /// is the narrowest borrow that supports every present +
3593 /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`, the
3594 /// [`crate::supervisor::validate_no_self_supervision`] `&[ChildSpec]`
3595 /// input, `serde` slice-serialization) without leaking the backing
3596 /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3597 /// reaches for (the storage-side `Vec` remains reachable through
3598 /// the `pub children` field for the mutation-carrying serde round-
3599 /// trip and per-test fixture-mutation paths, including the
3600 /// [`Self::supervisor_view`] fold-in path that clones the slot
3601 /// into the typed view). Named `children()` to match the storage
3602 /// field's name verbatim and the tatara-lisp author-surface term
3603 /// (`:children`) the field's own docstring already carries; the
3604 /// accessor's identity maps onto the canonical OTP supervision
3605 /// vocabulary the [`Caixa::children`] field's docstring already
3606 /// reaches for ("Static children of a supervisor").
3607 #[must_use]
3608 pub const fn children(&self) -> &[crate::supervisor::ChildSpec] {
3609 self.children.as_slice()
3610 }
3611
3612 /// Substrate-canonical per-`Caixa` `:membros` M3 mesh-slot outer-
3613 /// composite MESH-COMPOSITION-shaped per-Aplicacao member-list slice
3614 /// accessor every consumer of the top-level manifest's per-Aplicacao
3615 /// `&[crate::aplicacao::Membro]` slice-view keys off — returns the
3616 /// author-declared `:membros` typed `Vec<crate::aplicacao::Membro>`
3617 /// verbatim as a `&[crate::aplicacao::Membro]` slice-view over the
3618 /// same backing buffer the raw `self.membros.as_slice()` field access
3619 /// borrows from. Empty-slice-carrying (the "no members declared" arm
3620 /// every non-`Aplicacao`-kind `defcaixa` carries by `#[serde(default)]`
3621 /// and every partially-authored Aplicacao carries before the
3622 /// [`crate::AplicacaoError::MembrosEmpty`] gate fires; the returned
3623 /// `&[Membro]` degenerates to an empty slice on those arms without any
3624 /// silent `None` collapse).
3625 ///
3626 /// The outer `:membros` slot carries the M3 typed MESH-COMPOSITION
3627 /// per-Aplicacao member list — the load-bearing container of every
3628 /// per-member `{caixa, versao}` pair the caixa-mesh renderer's
3629 /// per-Aplicacao program-emission dispatch fans on at mesh-artifact
3630 /// materialization time (MESH-COMPOSITION §III.1 — the typed graph's
3631 /// vertex set the `:contratos` `:de`/`:para` edges resolve against and
3632 /// the `:entrada :para` external-gateway destination validates
3633 /// against; CAIXA-SDLC §II — the typed-M3 slot algebra the operator's
3634 /// per-Aplicacao fan-out dispatch fans on). Every per-member axis
3635 /// threads through a lifted per-entry accessor on the
3636 /// [`crate::aplicacao::Membro`] type: the
3637 /// [`crate::aplicacao::Membro::nome`] DNS-1123-label member-caixa-
3638 /// identity scalar accessor (4a32abf) and the peer
3639 /// [`crate::aplicacao::Membro::versao_requirement`] SemVer-2
3640 /// version-requirement scalar accessor (a40b0e3). Every downstream
3641 /// consumer of the mesh-graph path first passes through this outer
3642 /// accessor onto the slice and then dispatches per-member through
3643 /// the inner accessors — the two-level dispatch means every per-
3644 /// `:membros` reader now routes through a typed dispatch on the
3645 /// substrate primitive at both altitudes.
3646 ///
3647 /// Prior to this lift the `.membros` `Vec<Membro>` slot was accessed
3648 /// inline at three production sites across two files — the
3649 /// [`Self::declared_mesh_slots`] mesh-slot declared-slot
3650 /// enumerator's `!self.membros.is_empty()` presence probe
3651 /// (caixa-core/src/manifest.rs, which drives the
3652 /// `M3_AUTHOR_KEY_MEMBROS` kebab-case author-label push every
3653 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3654 /// gate reads), the [`Self::aplicacao_view`] per-Aplicacao typed-view
3655 /// composer's `self.membros.clone()` per-member fold-in path
3656 /// (caixa-core/src/manifest.rs, which materializes the typed
3657 /// [`crate::aplicacao::AplicacaoSpec`] view every
3658 /// [`crate::StandardLayout::verify`] Aplicacao-arm gate dispatches
3659 /// on), and the [`crate::StandardLayout::verify`] per-`:membros
3660 /// :caixa` self-membership refusal probe's `&caixa.membros`-borrowed
3661 /// [`crate::aplicacao::validate_no_self_membership`] input
3662 /// (caixa-core/src/layout.rs, which pins the "no member names the
3663 /// Aplicacao's own `:nome`" cross-slot coherence gate). A future
3664 /// extension of the outer `:membros` axis (a per-cluster
3665 /// `:membros-overrides` overlay the wasm-engine operator resolves at
3666 /// admission time so a cluster-specific member-set can tighten a
3667 /// caixa-declared list without re-authoring the `caixa.lisp`,
3668 /// promotion of the plain `Vec<Membro>` to a richer
3669 /// `{static, dynamic}` partition once runtime-resolved Aplicacao
3670 /// members land as a typed axis, per-member priority annotation once
3671 /// multi-strategy fan-out lands) would have had to be threaded
3672 /// through all three open-coded copies in lockstep or one consumer
3673 /// would silently disagree with the peers on which member slice a
3674 /// given Caixa resolves to — the enumerator's presence probe reading
3675 /// the raw slot while the peer view-composer's fold-in path read an
3676 /// operator-resolved slot would silently split the paired
3677 /// declared-slot enumerator and typed-view composition, and the
3678 /// [`crate::aplicacao::validate_no_self_membership`] self-membership
3679 /// refusal probe reading a third borrow would silently drift the
3680 /// cross-slot coherence gate's traversal input from the two peers, a
3681 /// three-consumer split at the enumerator, the view composer, and
3682 /// the self-membership gate far from the source `caixa.lisp` with no
3683 /// field naming the member-set-drift root cause. Lifting the
3684 /// resolution rule to a typed method on the substrate primitive
3685 /// means every downstream consumer of the caixa's per-`Caixa`
3686 /// MESH-COMPOSITION outer-slice surface reaches for exactly one
3687 /// typed dispatch — the resolver's accept-set migrates as a unit on
3688 /// any future axis addition.
3689 ///
3690 /// Third outer top-level [`Caixa`] `&[Composite]`-return slice
3691 /// accessor for M2 / M3 typed-slot vec-carry axes — opens the outer-
3692 /// `Caixa` M3 mesh-slot arm of the `&[Composite]` composite-slice
3693 /// sub-family the sibling M2 [`Self::upgrade_from`] (2a1f907) /
3694 /// [`Self::children`] (c17b51e) accessors opened for the M2 vec-carry
3695 /// altitude. Peer at the outer altitude of the closed inner-
3696 /// [`crate::AplicacaoSpec::membros`] (6c77e36) accessor on the same
3697 /// MESH-COMPOSITION per-Aplicacao member-list axis — the two
3698 /// altitudes now share the same "byte-equal, borrow-shared" outer-
3699 /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3700 /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3701 /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3702 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3703 /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3704 /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3705 /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3706 /// pattern onto the sibling M3 typed-composite-element axis
3707 /// ([`crate::aplicacao::Membro`] composite, matching the per-inner
3708 /// [`crate::AplicacaoSpec::membros`] element type at a different
3709 /// altitude).
3710 ///
3711 /// Returns `&[crate::aplicacao::Membro]` (not `&Vec<Membro>`)
3712 /// because every downstream consumer of the member list treats it
3713 /// as a read-only sequence — the slice-view is the narrowest borrow
3714 /// that supports every present + roadmapped consumer (`.iter()`,
3715 /// `.len()`, `.is_empty()`, the
3716 /// [`crate::aplicacao::validate_no_self_membership`] `&[Membro]`
3717 /// input, `serde` slice-serialization) without leaking the backing
3718 /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3719 /// reaches for (the storage-side `Vec` remains reachable through the
3720 /// `pub membros` field for the mutation-carrying serde round-trip
3721 /// and per-test fixture-mutation paths, including the
3722 /// [`Self::aplicacao_view`] fold-in path that clones the slot into
3723 /// the typed view). Named `membros()` to match the storage field's
3724 /// name verbatim and the tatara-lisp author-surface term
3725 /// (`:membros`) the field's own docstring already carries; the
3726 /// accessor's identity maps onto the canonical MESH-COMPOSITION
3727 /// vocabulary the [`Caixa::membros`] field's docstring already
3728 /// reaches for ("Member Servicos that make up this Aplicacao").
3729 #[must_use]
3730 pub const fn membros(&self) -> &[crate::aplicacao::Membro] {
3731 self.membros.as_slice()
3732 }
3733
3734 /// Substrate-canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
3735 /// composite MESH-COMPOSITION-shaped per-Aplicacao WIT-typed
3736 /// inter-Servico contract-list slice accessor every consumer of the
3737 /// top-level manifest's per-Aplicacao `&[crate::aplicacao::WitContract]`
3738 /// slice-view keys off — returns the author-declared `:contratos`
3739 /// typed `Vec<crate::aplicacao::WitContract>` verbatim as a
3740 /// `&[crate::aplicacao::WitContract]` slice-view over the same
3741 /// backing buffer the raw `self.contratos.as_slice()` field access
3742 /// borrows from. Empty-slice-carrying (the "no contracts declared"
3743 /// arm every non-`Aplicacao`-kind `defcaixa` carries by
3744 /// `#[serde(default)]` and every leaf Aplicacao carrying only a
3745 /// single member with no inter-Servico edge carries; the returned
3746 /// `&[WitContract]` degenerates to an empty slice on those arms
3747 /// without any silent `None` collapse).
3748 ///
3749 /// The outer `:contratos` slot carries the M3 typed MESH-COMPOSITION
3750 /// per-Aplicacao WIT-typed inter-Servico edge list — the load-bearing
3751 /// container of every per-edge `{de, para, wit, endpoint | subject |
3752 /// slot}` quadruple the caixa-mesh renderer's per-Aplicacao
3753 /// `CiliumNetworkPolicy` fan-out (one L7 policy per edge —
3754 /// MESH-COMPOSITION §III.2 point 2) and per-`(:de, :para)`
3755 /// adjacency-list seed dispatch on at mesh-artifact materialization
3756 /// time (MESH-COMPOSITION §III.1 — the typed graph's edge set the
3757 /// `:membros` vertex set resolves against, closed by the
3758 /// [`crate::AplicacaoError::ContractoUnknownMember`] / cycle-refusal
3759 /// gates in §III.3; CAIXA-SDLC §II — the typed-M3 slot algebra the
3760 /// operator's per-Aplicacao fan-out dispatch fans on). Every
3761 /// per-edge axis threads through a lifted per-entry accessor on the
3762 /// [`crate::aplicacao::WitContract`] type: the peer `de` / `para`
3763 /// DNS-1123-label member-caixa-name endpoint scalar accessors, the
3764 /// [`crate::aplicacao::WitContract::endpoint`] (7020470) HTTP-shape
3765 /// / [`crate::aplicacao::WitContract::subject`] (90de675)
3766 /// NATS-pub-sub-shape / [`crate::aplicacao::WitContract::slot`]
3767 /// (ed22b66) `wasi:keyvalue/store`-shape payload-carrier accessors,
3768 /// and the WIT-world discriminant. Every downstream consumer of the
3769 /// mesh-graph edge path first passes through this outer accessor
3770 /// onto the slice and then dispatches per-contract through the
3771 /// inner accessors — the two-level dispatch means every
3772 /// per-`:contratos` reader now routes through a typed dispatch on
3773 /// the substrate primitive at both altitudes.
3774 ///
3775 /// Prior to this lift the `.contratos` `Vec<WitContract>` slot was
3776 /// accessed inline at two production sites in
3777 /// caixa-core/src/manifest.rs — the [`Self::declared_mesh_slots`]
3778 /// mesh-slot declared-slot enumerator's
3779 /// `!self.contratos.is_empty()` presence probe (which drives the
3780 /// `M3_AUTHOR_KEY_CONTRATOS` kebab-case author-label push every
3781 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3782 /// gate reads) and the [`Self::aplicacao_view`] per-Aplicacao
3783 /// typed-view composer's `self.contratos.clone()` per-contract
3784 /// fold-in path (which materializes the typed
3785 /// [`crate::aplicacao::AplicacaoSpec`] view every
3786 /// [`crate::StandardLayout::verify`] Aplicacao-arm gate and every
3787 /// downstream `caixa-mesh` renderer dispatches on). A future
3788 /// extension of the outer `:contratos` axis (a per-cluster
3789 /// `:contratos-overrides` overlay the wasm-engine operator resolves
3790 /// at admission time so a cluster-specific edge-set can tighten a
3791 /// caixa-declared list without re-authoring the `caixa.lisp`,
3792 /// promotion of the plain `Vec<WitContract>` to a richer
3793 /// `{static, dynamic}` partition once runtime-resolved contract
3794 /// edges land, per-edge policy annotation once the M4 per-edge
3795 /// policy overlay axis lands) would have had to be threaded through
3796 /// both open-coded copies in lockstep or one consumer would
3797 /// silently disagree with the peer on which edge slice a given
3798 /// Caixa resolves to — the enumerator's presence probe reading the
3799 /// raw slot while the peer view-composer's fold-in path read an
3800 /// operator-resolved slot would silently split the paired
3801 /// declared-slot enumerator and typed-view composition, a
3802 /// two-consumer split at the enumerator and the view composer far
3803 /// from the source `caixa.lisp` with no field naming the edge-set-
3804 /// drift root cause. Lifting the resolution rule to a typed method
3805 /// on the substrate primitive means every downstream consumer of
3806 /// the caixa's per-`Caixa` MESH-COMPOSITION outer-slice surface
3807 /// reaches for exactly one typed dispatch — the resolver's
3808 /// accept-set migrates as a unit on any future axis addition.
3809 ///
3810 /// Fourth and final outer top-level [`Caixa`] `&[Composite]`-return
3811 /// slice accessor for M2 / M3 typed-slot vec-carry axes — closes
3812 /// the outer-`Caixa` `&[Composite]` composite-slice sub-family the
3813 /// sibling M2 [`Self::upgrade_from`] (2a1f907) / [`Self::children`]
3814 /// (c17b51e) accessors opened and the M3 [`Self::membros`]
3815 /// (0f26987) accessor folded on, and closes the outer-`Caixa` M3
3816 /// mesh-slot arm of the composite-slice sub-family the sibling
3817 /// [`Self::membros`] accessor opened for the M3 vec-carry altitude.
3818 /// Peer at the outer altitude of the closed inner-
3819 /// [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
3820 /// same MESH-COMPOSITION per-Aplicacao contract-list axis — the two
3821 /// altitudes now share the same "byte-equal, borrow-shared" outer-
3822 /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3823 /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3824 /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3825 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3826 /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3827 /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3828 /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3829 /// pattern onto the sibling M3 typed-composite-element axis
3830 /// ([`crate::aplicacao::WitContract`] composite, matching the
3831 /// per-inner [`crate::AplicacaoSpec::contratos`] element type at a
3832 /// different altitude).
3833 ///
3834 /// Returns `&[crate::aplicacao::WitContract]` (not
3835 /// `&Vec<WitContract>`) because every downstream consumer of the
3836 /// contract list treats it as a read-only sequence — the slice-view
3837 /// is the narrowest borrow that supports every present + roadmapped
3838 /// consumer (`.iter()`, `.len()`, `.is_empty()`, per-edge WIT-world
3839 /// discriminant dispatch, `serde` slice-serialization) without
3840 /// leaking the backing `Vec`'s grow/push/reserve surface no
3841 /// consumer of the typed view reaches for (the storage-side `Vec`
3842 /// remains reachable through the `pub contratos` field for the
3843 /// mutation-carrying serde round-trip and per-test fixture-mutation
3844 /// paths, including the [`Self::aplicacao_view`] fold-in path that
3845 /// clones the slot into the typed view). Named `contratos()` to
3846 /// match the storage field's name verbatim and the tatara-lisp
3847 /// author-surface term (`:contratos`) the field's own docstring
3848 /// already carries; the accessor's identity maps onto the canonical
3849 /// MESH-COMPOSITION vocabulary the [`Caixa::contratos`] field's
3850 /// docstring already reaches for ("WIT-typed inter-Servico
3851 /// contracts").
3852 #[must_use]
3853 pub const fn contratos(&self) -> &[crate::aplicacao::WitContract] {
3854 self.contratos.as_slice()
3855 }
3856
3857 /// Compose the Aplicacao-related flat slots into a single typed
3858 /// [`crate::aplicacao::AplicacaoSpec`] for validation +
3859 /// downstream renderer consumption. Returns `None` when the
3860 /// caixa isn't a `:kind Aplicacao`.
3861 #[must_use]
3862 pub fn aplicacao_view(&self) -> Option<crate::aplicacao::AplicacaoSpec> {
3863 if !self.kind().is_aplicacao() {
3864 return None;
3865 }
3866 Some(crate::aplicacao::AplicacaoSpec {
3867 membros: self.membros().to_vec(),
3868 contratos: self.contratos().to_vec(),
3869 politicas: self.politicas().cloned().unwrap_or_default(),
3870 placement: self.placement().cloned().unwrap_or_default(),
3871 entrada: self.entrada().cloned(),
3872 })
3873 }
3874
3875 /// The kebab-case `:slot` tags of every M3 mesh slot this caixa
3876 /// *declares* a value on, in canonical declaration order
3877 /// (`:membros` → `:contratos` → `:politicas` → `:placement` →
3878 /// `:entrada`). A slot counts as declared when its backing field
3879 /// carries a value — a non-empty `Vec`, or a `Some(...)`.
3880 ///
3881 /// The M3 mesh slots compose the typed graph of a `:kind Aplicacao`
3882 /// (MESH-COMPOSITION §III.1). [`Self::aplicacao_view`] only folds
3883 /// them into a validatable [`crate::aplicacao::AplicacaoSpec`] when
3884 /// the kind matches (returns `None` otherwise), and the caixa-mesh /
3885 /// caixa-flux / caixa-helm renderers only emit them for an
3886 /// Aplicacao. On any *other* kind a declared mesh slot is the
3887 /// manifest field's documented "ignored otherwise" (see the
3888 /// `:membros` … `:entrada` field docs): it silently passes
3889 /// [`Caixa::from_lisp`] and then vanishes — never validated, never
3890 /// rendered — far from the source caixa.lisp.
3891 /// [`crate::StandardLayout::verify`] consults this to reject that
3892 /// silent-drop at caixa-build time
3893 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]), mirroring the
3894 /// `SupervisorOwnsCode` / `AplicacaoOwnsCode` kind-coherence gates:
3895 /// a slot foreign to the kind is a build error, not a silent drop.
3896 ///
3897 /// Lifted as a typed method (rather than an inline disjunction at
3898 /// the verify call site) so the mesh-slot set lives in one place —
3899 /// a future M4 axis added to the Aplicacao surface (per-edge policy
3900 /// overlay, distributed-app takeover config) is one push here, and
3901 /// every consumer reaching for "which mesh slots are set" (the
3902 /// verify gate, a future `feira lint` kind-coherence advisory)
3903 /// inherits the canonical order without rolling its own.
3904 ///
3905 /// Each per-arm kebab-case label is routed through the peer
3906 /// [`crate::M3_AUTHOR_KEY_MEMBROS`] /
3907 /// [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
3908 /// [`crate::M3_AUTHOR_KEY_POLITICAS`] /
3909 /// [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
3910 /// [`crate::M3_AUTHOR_KEY_ENTRADA`] consts declared next to the
3911 /// [`crate::M3_KEY_PLACEMENT`] renderer-side wire-key peer, so both
3912 /// halves of every M3 top-level mesh slot's dual axis (author-facing
3913 /// kebab-case label + renderer-side artifact key) route through one
3914 /// canonical declaration per arm — same discipline the peer
3915 /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`]
3916 /// / [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot consts
3917 /// (f49c8b0) establish on the sibling per-Servico M2 top-level slot
3918 /// axis, extended here to close the M3 mesh-slot author-facing-label
3919 /// axis so both altitudes of the typed-slot algebra
3920 /// (per-Servico M2 + per-Aplicacao M3) share the same
3921 /// "one canonical byte-string per arm, next to the axis" discipline.
3922 #[must_use]
3923 pub fn declared_mesh_slots(&self) -> Vec<&'static str> {
3924 let mut slots = Vec::new();
3925 if !self.membros().is_empty() {
3926 slots.push(crate::render::M3_AUTHOR_KEY_MEMBROS);
3927 }
3928 if !self.contratos().is_empty() {
3929 slots.push(crate::render::M3_AUTHOR_KEY_CONTRATOS);
3930 }
3931 if self.politicas().is_some() {
3932 slots.push(crate::render::M3_AUTHOR_KEY_POLITICAS);
3933 }
3934 if self.placement().is_some() {
3935 slots.push(crate::render::M3_AUTHOR_KEY_PLACEMENT);
3936 }
3937 if self.entrada().is_some() {
3938 slots.push(crate::render::M3_AUTHOR_KEY_ENTRADA);
3939 }
3940 slots
3941 }
3942
3943 /// The kebab-case `:slot` tags of every supervisor-tree slot this
3944 /// caixa *declares* a value on, in canonical declaration order
3945 /// (`:estrategia` → `:max-restarts` → `:restart-window` →
3946 /// `:children`). A slot counts as declared when its backing field
3947 /// carries a value — a `Some(...)`, or a non-empty `Vec`.
3948 ///
3949 /// The supervisor-tree slots compose the typed OTP supervisor of a
3950 /// `:kind Supervisor` (INSPIRATIONS §II.2; the `:estrategia` +
3951 /// `:children` field docs above). [`Self::supervisor_view`] only
3952 /// folds them into a validatable [`SupervisorSpec`] when the kind
3953 /// matches (returns `None` otherwise), and the wasm-operator's
3954 /// hierarchical reconciler only consumes them for a Supervisor. On
3955 /// any *other* kind a declared supervisor slot is the manifest
3956 /// field's documented "ignored otherwise" (see the `:estrategia` …
3957 /// `:children` field docs): it silently passes [`Caixa::from_lisp`]
3958 /// and then vanishes — never validated, never reconciled — far from
3959 /// the source caixa.lisp. [`crate::StandardLayout::verify`] consults
3960 /// this to reject that silent-drop at caixa-build time
3961 /// ([`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]), the
3962 /// exact mirror of the [`Self::declared_mesh_slots`] /
3963 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] gate on the
3964 /// Aplicacao-only slot set: a slot foreign to the kind is a build
3965 /// error, not a silent drop.
3966 #[must_use]
3967 pub fn declared_supervisor_slots(&self) -> Vec<&'static str> {
3968 let mut slots = Vec::new();
3969 if self.estrategia().is_some() {
3970 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA);
3971 }
3972 if self.max_restarts().is_some() {
3973 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS);
3974 }
3975 if self.restart_window().is_some() {
3976 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW);
3977 }
3978 if !self.children().is_empty() {
3979 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN);
3980 }
3981 slots
3982 }
3983
3984 /// The kebab-case `:slot` tags of every M2 Servico-runtime slot this
3985 /// caixa *declares* a value on, in canonical declaration order
3986 /// (`:limits` → `:behavior` → `:upgrade-from`). A slot counts as
3987 /// declared when its backing field carries a value — a `Some(...)`,
3988 /// or a non-empty `Vec`.
3989 ///
3990 /// The M2 slots configure the runtime of a long-running wasm
3991 /// component, i.e. a `:kind Servico`: `:limits` is Lunatic
3992 /// per-process sandboxing (INSPIRATIONS §III.1), `:behavior` is the
3993 /// OTP `gen_server` callback set (§II.3), `:upgrade-from` is the OTP
3994 /// appup hot-code-reload table (§II.4). The caixa-helm / caixa-flux
3995 /// renderers gate on [`crate::require_kind`]`(_, Servico)` and only
3996 /// emit these slots for a Servico; on any *other* kind a declared M2
3997 /// slot is the manifest field's documented "ignored otherwise": its
3998 /// well-formedness is checked by [`crate::StandardLayout::verify`]
3999 /// but the value is never rendered into a chart / programs.yaml entry
4000 /// — it silently passes [`Caixa::from_lisp`] + `feira build` and then
4001 /// vanishes, far from the source caixa.lisp.
4002 /// [`crate::StandardLayout::verify`] consults this to reject that
4003 /// silent-drop at caixa-build time
4004 /// ([`crate::LayoutError::ServicoSlotsOnNonServico`]), the exact
4005 /// mirror of the [`Self::declared_mesh_slots`] /
4006 /// [`Self::declared_supervisor_slots`] gates on the peer
4007 /// kind-exclusive slot sets: a slot foreign to the kind is a build
4008 /// error, not a silent drop.
4009 ///
4010 /// Each per-arm kebab-case label is routed through the peer
4011 /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
4012 /// [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts declared next to the
4013 /// [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
4014 /// [`crate::M2_KEY_UPGRADE_FROM`] renderer-side wire-key peers, so
4015 /// both halves of the M2 top-level slot's dual axis (author-facing
4016 /// kebab-case label + renderer-side camelCase overlay-container wire
4017 /// key) route through one canonical declaration per arm — same
4018 /// discipline the peer [`crate::M2_BEHAVIOR_AUTHOR_KEY_ON_*`] sub-slot
4019 /// author-label consts (889dc18) establish on the sibling
4020 /// per-callback axis inside the `:behavior` overlay block.
4021 #[must_use]
4022 pub fn declared_servico_slots(&self) -> Vec<&'static str> {
4023 let mut slots = Vec::new();
4024 if self.limits().is_some() {
4025 slots.push(crate::render::M2_AUTHOR_KEY_LIMITS);
4026 }
4027 if self.behavior().is_some() {
4028 slots.push(crate::render::M2_AUTHOR_KEY_BEHAVIOR);
4029 }
4030 if !self.upgrade_from().is_empty() {
4031 slots.push(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM);
4032 }
4033 slots
4034 }
4035
4036 /// The kebab-case `:slot` tags of every code-surface slot this caixa
4037 /// declares a value on that its [`CaixaKind`] doesn't natively own,
4038 /// in canonical declaration order (`:exe` → `:servicos`). A
4039 /// code-surface slot is owned by exactly one kind: `:exe` by
4040 /// [`CaixaKind::Binario`] (the nix-built executable surface), and
4041 /// `:servicos` by [`CaixaKind::Servico`] (the wasm component +
4042 /// `ComputeUnit` daemon surface).
4043 ///
4044 /// Each is silently ignored when declared on the wrong kind: the
4045 /// caixa-helm / caixa-flux / caixa-flake renderers gate on
4046 /// [`crate::require_kind`]`(_, <owning-kind>)`, so on any *other*
4047 /// code-running kind a declared `:exe` / `:servicos` is the manifest
4048 /// field's documented "ignored otherwise" — its path is checked for
4049 /// existence by the layout's `bibliotecas`/`exe`/`servicos` loops
4050 /// (which run after [`Caixa::from_lisp`]), but the value is never
4051 /// rendered into a build target or programs.yaml entry. It silently
4052 /// passes [`Caixa::from_lisp`] + `feira build`, far from the source
4053 /// caixa.lisp, with no field naming which slot is foreign.
4054 ///
4055 /// [`crate::StandardLayout::verify`] consults this to reject that
4056 /// silent-drop at caixa-build time
4057 /// ([`crate::LayoutError::ForeignCodeSlot`]), beside the M2
4058 /// servico-runtime, supervisor-tree, and M3 mesh kind-coherence
4059 /// gates ([`Self::declared_servico_slots`] /
4060 /// [`Self::declared_supervisor_slots`] /
4061 /// [`Self::declared_mesh_slots`]): the fourth kind ↔ slot algebra
4062 /// axis to be closed on the typed surface. The Supervisor /
4063 /// Aplicacao "no code at all" cases ([`crate::LayoutError::SupervisorOwnsCode`]
4064 /// / [`crate::LayoutError::AplicacaoOwnsCode`]) keep their dedicated
4065 /// diagnostics — they fire ahead of this gate on the same `verify`
4066 /// pass, so for Supervisor / Aplicacao the `OwnCode` arm always wins
4067 /// and this method is moot. For Biblioteca / Binario / Servico, this
4068 /// gate fires when a code-running kind declares another code-running
4069 /// kind's exclusive code surface.
4070 ///
4071 /// `:bibliotecas` is deliberately excluded — a Binario or Servico
4072 /// may legitimately ship a `lib/` helper that the underlying
4073 /// substrate (the nix flake for Binario, the wasm component build
4074 /// for Servico) bundles into its build, so the slot's
4075 /// declared-on-wrong-kind cardinality isn't a structural error on
4076 /// either code-running kind. A Biblioteca declaring `:bibliotecas`
4077 /// is the native case (the slot's owning kind). Supervisor /
4078 /// Aplicacao declaring `:bibliotecas` is gated upstream by
4079 /// [`crate::LayoutError::SupervisorOwnsCode`] /
4080 /// [`crate::LayoutError::AplicacaoOwnsCode`].
4081 ///
4082 /// Lifted as a typed method (rather than an inline disjunction at
4083 /// the verify call site) so the foreign-code-slot set lives in one
4084 /// place — a future kind that gains its own code-surface slot is
4085 /// one push here, and every consumer reaching for "which code
4086 /// surfaces are foreign to this kind" (the verify gate, a future
4087 /// `feira lint` kind-coherence advisory, the future `app-operator`'s
4088 /// per-caixa build-target classifier) inherits the canonical order
4089 /// without rolling its own.
4090 #[must_use]
4091 pub fn declared_foreign_code_slots(&self) -> Vec<&'static str> {
4092 let mut slots = Vec::new();
4093 if !self.exe().is_empty() && !self.kind().requires_exe() {
4094 slots.push(":exe");
4095 }
4096 if !self.servicos().is_empty() && !self.kind().requires_servicos() {
4097 slots.push(":servicos");
4098 }
4099 slots
4100 }
4101
4102 /// Validate every entry of `:deps` and `:deps-dev` through
4103 /// [`Dep::validate`] — closing the parity loop with the per-axis
4104 /// `:versao` gates already wired into the typed-graph
4105 /// ([`crate::AplicacaoSpec::validate_membros`] for `:membros`,
4106 /// 9888b13) and typed supervisor tree
4107 /// ([`crate::SupervisorSpec::validate`] for `:children`, b38ff3a).
4108 ///
4109 /// Until this gate landed `:deps :versao` and `:deps-dev :versao`
4110 /// were the only `:versao` axes still untyped past
4111 /// [`Caixa::from_lisp`]: the derive macro stored the requirement
4112 /// as a String without parsing it, so a malformed-but-non-empty
4113 /// requirement (`"^bad-version"`, `"^^0.1"`, `"v0.1"`, `"not-a-req"`)
4114 /// silently passed parse and the `semver::Error` surfaced at
4115 /// lacre-resolve time, far from the source caixa.lisp, with no
4116 /// field naming which `:deps` entry carried the typo. Lifting the
4117 /// gate here makes the four `:versao` typed surfaces (`:deps`,
4118 /// `:deps-dev`, `:membros`, `:children`) structurally equivalent —
4119 /// every requirement string past `validate_deps` is round-trippable
4120 /// through [`crate::parse_requirement`] without re-checking at the
4121 /// resolver layer.
4122 ///
4123 /// Both lists run through the same per-entry validator so a typo
4124 /// in `:deps-dev` surfaces with the same diagnostic as one in
4125 /// `:deps` — neither axis is a second-class citizen of the typed
4126 /// surface.
4127 ///
4128 /// Within each list, [`DepError::DuplicateNome`] closes the
4129 /// set-not-multiset discipline on the `:nome` axis: two entries
4130 /// naming the same caixa carry two `:versao` / `:fonte` / feature
4131 /// triples that the caixa-resolver's lacre pipeline collapses to one
4132 /// via its `HashMap`-keyed-by-`:nome` consumption — the second entry
4133 /// silently overwrites the first at `concrete_versao`-resolve time
4134 /// (the same "second wins / one silently overwrites the other"
4135 /// shape the peer typed-graph duplicate gates already close on every
4136 /// other Vec-shaped authoring surface that keys by name). The
4137 /// duplicate check fires per-list and runs *after* each per-entry
4138 /// [`Dep::validate`] call so a malformed-and-duplicated entry
4139 /// surfaces its narrower per-entry diagnostic
4140 /// ([`DepError::NomeInvalid`], [`DepError::VersaoInvalid`],
4141 /// [`DepError::FonteRepoEmpty`], …) before the cross-entry duplicate
4142 /// diagnostic — the canonical "per-entry shape before cross-entry
4143 /// uniqueness" precedence the peer `:children :caixa`
4144 /// ([`crate::SupervisorSpec::validate`]), `:membros :caixa`
4145 /// ([`crate::AplicacaoSpec::validate_membros`]), `:contratos`
4146 /// ([`crate::AplicacaoSpec::validate`]), `:placement :clusters`
4147 /// ([`crate::AplicacaoSpec::validate_placement`]),
4148 /// `:entrada :paths` ([`crate::AplicacaoSpec::validate`]),
4149 /// `:upgrade-from :from` ([`crate::upgrade::validate_upgrade_from`]),
4150 /// and the within-`:upgrade-from`-entry per-instruction-class
4151 /// singularity gates ([`crate::UpgradeError::DuplicateLoadModule`],
4152 /// [`crate::UpgradeError::DuplicateStateChange`],
4153 /// [`crate::UpgradeError::DuplicateCleanup`]) all establish.
4154 ///
4155 /// Cross-list (`:deps` ↔ `:deps-dev`) coincidence is *not* gated
4156 /// here: Cargo's `[dependencies]` + `[dev-dependencies]` accept the
4157 /// same name in both tables (the dev table's pin overrides the
4158 /// runtime table's pin in test/dev contexts), and caixa's surface
4159 /// mirrors that convention until a deliberate choice retires the
4160 /// override pattern. Only within-list duplicates are structurally
4161 /// incoherent — those are what this gate closes.
4162 ///
4163 /// Compound per-`Caixa` entry gate on the dep-graph axis: folds the
4164 /// two standalone dep-list validators — the per-entry + within-list
4165 /// duplicate-`:nome` walk (the [`Dep::validate`] +
4166 /// [`crate::render::insert_first_seen`] cascade this method opened
4167 /// on) and the cross-slot self-edge gate
4168 /// ([`crate::dep::validate_no_self_dep`]) — onto one substrate
4169 /// primitive on [`Caixa`]. The two arms run in the same canonical
4170 /// order the layout pipeline
4171 /// ([`crate::layout::StandardLayout::verify`], the `feira build`
4172 /// author-time gate) has always sequenced them (per-entry +
4173 /// cross-entry duplicate → cross-slot self-edge), so the fold is
4174 /// byte-for-byte equivalent to the pre-fold two-block cascade at
4175 /// that call site (pinned by the paired
4176 /// `validate_deps_folds_per_entry_arm_matches_gate` /
4177 /// `validate_deps_folds_self_edge_arm_matches_gate` equivalence
4178 /// pins and the `validate_deps_per_entry_arm_fires_before_self_edge_arm`
4179 /// ordering pin). Self-contained on `&self` — resolves its three
4180 /// inputs ([`Self::deps`], [`Self::deps_dev`], [`Self::nome`])
4181 /// through the substrate primitives' own accessor family, the same
4182 /// posture every peer per-slot compound gate
4183 /// ([`crate::AplicacaoSpec::validate_contratos`],
4184 /// [`crate::MeshPolicy::validate`],
4185 /// [`crate::SupervisorSpec::validate_children`],
4186 /// [`Self::validate_upgrade_from`]) already carries.
4187 ///
4188 /// Prior to this lift [`crate::dep::validate_no_self_dep`] lived
4189 /// only open-coded at the layout wire-up site
4190 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs)
4191 /// as a standalone two-arg dispatch immediately after this method's
4192 /// per-entry + cross-entry walk, both wrapped through the same
4193 /// [`crate::LayoutError::DepsViolation`] envelope: every future
4194 /// consumer that wanted to gate the dep-graph as a whole — the
4195 /// deferred `caixa.pleme.io/v1alpha1/Caixa` CR materializer's
4196 /// per-CR admission webhook re-checking `:deps` / `:deps-dev` after
4197 /// a per-entry patch, a future `feira validate --deps` per-caixa
4198 /// admission verb, a per-`:deps` overlay resolver a per-cluster
4199 /// overlay lift would materialize (each the deferred consumer this
4200 /// method's peer [`Self::deps`] / [`Self::deps_dev`] accessors'
4201 /// docstrings already name) — was structurally forced to either
4202 /// re-inline the two-dispatch cascade in lockstep with the layout
4203 /// wire-up (the duplication the PRIME DIRECTIVE names as a bug) or
4204 /// call the whole [`crate::layout::StandardLayout::verify`] pipeline
4205 /// and pay every peer per-Caixa gate to re-check one slot. Post-fold
4206 /// each such consumer reaches the two-arm compound gate through one
4207 /// call on the substrate primitive.
4208 pub fn validate_deps(&self) -> Result<(), DepError> {
4209 for &list in crate::dep::DepList::ALL {
4210 let mut seen = std::collections::HashSet::new();
4211 for dep in self.deps_of(list) {
4212 dep.validate()?;
4213 crate::render::insert_first_seen(&mut seen, dep.nome(), || {
4214 DepError::duplicate_nome(dep.nome(), list.as_str())
4215 })?;
4216 }
4217 }
4218 crate::dep::validate_no_self_dep(self.deps(), self.deps_dev(), self.nome())?;
4219 Ok(())
4220 }
4221
4222 /// Run a per-slot typed validator on `self` and, on the per-arm
4223 /// parser-side error arm, thread the error into a paired
4224 /// [`crate::LayoutError`] wrap under `self.nome()`. Substrate
4225 /// primitive folding the 18 self-similar layout-pipeline wire-up
4226 /// sites at [`crate::layout::StandardLayout::verify`] that carry
4227 /// the identical
4228 /// `caixa.validate_<slot>().map_err(|err| crate::LayoutError::<slot>_violation(caixa, err))?;`
4229 /// cascade onto one dispatch. Each of the eighteen sites (`:nome`,
4230 /// `:nome`-chart-name-budget, `:versao`, `:deps`, `:etiquetas`,
4231 /// `:autores`, `:repositorio`, `:descricao`, `:licenca`, `:edicao`,
4232 /// `:bibliotecas`/`:exe`/`:servicos` code-path shape, `:limits`,
4233 /// `:behavior`, `:upgrade-from`, `:restart-window`, per-Supervisor
4234 /// shape, per-Aplicacao shape, per-Acao shape) carried the same
4235 /// four-line "run a per-slot typed validator on `caixa` and, on the
4236 /// per-arm parser-side error arm, thread it into the paired
4237 /// [`crate::LayoutError`] one-slot envelope through the substrate-
4238 /// canonical `layout_violation_ctors!` family (131ca0d)" cascade,
4239 /// differing only in the two names bound at each site — the
4240 /// validator (`Caixa::validate_deps` / `validate_nome` / ...) and
4241 /// the paired ctor (`LayoutError::deps_violation` / ...). Eighteen
4242 /// consumers, one identical shape, one substrate primitive on
4243 /// [`Caixa`] closing the duplication the PRIME DIRECTIVE names as
4244 /// a bug — on the second half of the per-slot cascade the peer
4245 /// substrate primitives on the [`crate::LayoutError`]-wrap side
4246 /// (the `layout_violation_ctors!` macro 131ca0d, the
4247 /// `layout_slot_kind_ctors!` macro 0419438, the `layout_nome_only_ctors!`
4248 /// macro 3fe3dd7, the [`crate::LayoutError::missing_entry`] ctor
4249 /// 1b09f9d, the [`crate::layout::StandardLayout::probe_declared_entry`]
4250 /// primitive fda1e35) each closed on their sibling envelopes; the
4251 /// first half of the cascade (the per-slot compound gates
4252 /// [`Self::validate_deps`] b5dd55e, [`Self::validate_limits`]
4253 /// baa4688, [`Self::validate_behavior`] 0d2877a,
4254 /// [`Self::validate_upgrade_from`] d6801df,
4255 /// [`Self::validate_aplicacao_shape`] 949a7a0,
4256 /// [`Self::validate_supervisor_shape`] 4c70105,
4257 /// [`Self::validate_acao_shape`] 5d6df54,
4258 /// [`Self::validate_kind_slot_coherence`] f0d286e,
4259 /// [`Self::validate_no_code_kind_coherence`] 3bbf6a2,
4260 /// [`Self::validate_ci_kind_coherence`] 9b55beb,
4261 /// [`Self::validate_required_kind_slot`] 9c385d8) each closed on
4262 /// their per-slot compound gates.
4263 ///
4264 /// Composes the [`crate::layout::LayoutError`] wrap and the per-slot
4265 /// typed validator through two typed callables: `gate` runs on
4266 /// `self` and yields a per-slot error `E`; on the `Err(E)` arm
4267 /// `wrap` re-wraps that error under `self` into a
4268 /// [`crate::layout::LayoutError`]. The `Ok(())` arm passes through
4269 /// verbatim as the fold's identity element — byte-equal to the
4270 /// pre-lift `Result::map_err` short-circuit at the `?;` marker
4271 /// every wire-up site formerly carried. Every future consumer that
4272 /// wants to run one of the per-slot gates and thread its error
4273 /// through the layout wrap (the deferred
4274 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's admission-
4275 /// webhook per-slot re-check, a future `feira validate --<slot>`
4276 /// per-caixa admission verb, an overlay resolver re-running one
4277 /// gate after a per-slot patch) reaches the two-callable dispatch
4278 /// through one call rather than re-inlining the four-line cascade
4279 /// in lockstep with the pre-existing 18 wire-ups. The two callables
4280 /// reach the primitive as first-class type-checked references
4281 /// rather than the pre-lift `.map_err(|err| CTOR(caixa, err))`
4282 /// closure body — so a mismatch between the validator's `E` type
4283 /// and the ctor's `E` bound trips at the wire-up site (compile-
4284 /// time) rather than at the closure body (also compile-time, but
4285 /// with a diagnostic pointing at the closure expression rather
4286 /// than the two named callables).
4287 pub fn run_layout_gate<E, W>(
4288 &self,
4289 gate: impl FnOnce(&Caixa) -> Result<(), E>,
4290 wrap: W,
4291 ) -> Result<(), crate::LayoutError>
4292 where
4293 W: FnOnce(&Caixa, E) -> crate::LayoutError,
4294 {
4295 gate(self).map_err(|err| wrap(self, err))
4296 }
4297
4298 /// Run one arm of the cross-family kind ↔ owned-slot-family
4299 /// coherence cascade on `self`: on a caixa whose [`Self::kind`] does
4300 /// not own the typed-slot family named by `is_owner`, refuse when
4301 /// the paired `accumulator` reports any declared slot in that
4302 /// family; otherwise pass. Substrate primitive folding the three
4303 /// self-similar four-line
4304 /// `if !self.kind().is_<owner>() { let slots = self.declared_<family>_slots();
4305 /// if !slots.is_empty() { return Err(<wrap>(self, slots)); } }`
4306 /// arms at [`Self::validate_kind_slot_coherence`] onto one dispatch.
4307 /// Three consumers (M3 mesh — Aplicacao-owner, supervisor-tree —
4308 /// Supervisor-owner, M2 Servico-runtime — Servico-owner), one
4309 /// identical shape, one substrate primitive on [`Caixa`] closing
4310 /// the duplication the PRIME DIRECTIVE names as a bug on the
4311 /// outer kind-coherence arm shape — peer with the substrate
4312 /// primitives on the two adjacent halves of the same three-arm
4313 /// cascade the sibling [`Self::declared_mesh_slots`] /
4314 /// [`Self::declared_supervisor_slots`] /
4315 /// [`Self::declared_servico_slots`] accumulator family closes on
4316 /// the inner slot-set enumerator axis and the sibling
4317 /// [`crate::layout::layout_slot_kind_ctors!`] macro (0419438)
4318 /// closes on the inner wrap-envelope ctor axis. Each of the three
4319 /// [`Self::validate_kind_slot_coherence`] arms now reads through
4320 /// one call across every altitude of the per-arm cascade:
4321 /// one dispatch on this primitive for the outer guard shape, one
4322 /// dispatch on `Self::declared_<family>_slots` for the accumulator,
4323 /// one dispatch on `crate::LayoutError::<family>_on_non_<owner>`
4324 /// for the wrap ctor.
4325 ///
4326 /// Composes the outer owner-kind guard, the per-family accumulator,
4327 /// and the per-family wrap ctor through three typed callables:
4328 /// `is_owner` runs on `&self.kind()` (a `&CaixaKind` borrow so the
4329 /// `gen_platform::IsVariant`-derived `fn(&CaixaKind) -> bool`
4330 /// per-arm predicates — [`crate::CaixaKind::is_aplicacao`] /
4331 /// [`crate::CaixaKind::is_supervisor`] / [`crate::CaixaKind::is_servico`]
4332 /// — pass verbatim as function references), `accumulator` runs on
4333 /// `&self` and yields the
4334 /// per-family declared-slot list, and `wrap` runs on `(&self,
4335 /// Vec<&'static str>)` and yields the per-family
4336 /// [`crate::LayoutError`] wrap. The `is_owner` short-circuit fires
4337 /// before the accumulator dispatch (so the owner kind of each
4338 /// family passes without invoking `accumulator`, byte-equal to the
4339 /// pre-lift `if !self.kind().is_<owner>() { … }` outer guard's
4340 /// short-circuit — pinned by
4341 /// `run_kind_owned_slot_family_gate_owner_kind_short_circuits_before_accumulator`),
4342 /// and the accumulator's `is_empty` short-circuit fires before the
4343 /// wrap dispatch (so a non-owner kind with no declared slot in that
4344 /// family passes without invoking `wrap`, byte-equal to the pre-lift
4345 /// `if !<slots>.is_empty() { … }` inner guard's short-circuit —
4346 /// pinned by
4347 /// `run_kind_owned_slot_family_gate_empty_accumulator_short_circuits_before_wrap`).
4348 /// The wrap ctor is `FnOnce(&Caixa, Vec<&'static str>) ->
4349 /// crate::LayoutError` — matching the [`crate::layout::layout_slot_kind_ctors!`]
4350 /// macro's per-variant `fn(&Caixa, Vec<&'static str>) -> LayoutError`
4351 /// substrate-canonical ctor shape verbatim, so
4352 /// [`crate::LayoutError::mesh_slots_on_non_aplicacao`] /
4353 /// [`crate::LayoutError::supervisor_slots_on_non_supervisor`] /
4354 /// [`crate::LayoutError::servico_slots_on_non_servico`] pass as
4355 /// function references without a closure wrap. A mismatch between
4356 /// the ctor's signature and this bound trips at the wire-up site
4357 /// (compile-time) rather than at a closure body.
4358 ///
4359 /// The sibling [`crate::LayoutError::ForeignCodeSlot`] gate on the
4360 /// code-surface family sits outside this primitive because
4361 /// [`Self::declared_foreign_code_slots`] bakes the per-arm kind-
4362 /// check into the accumulator itself (each arm's
4363 /// `!self.kind().requires_<slot>()` guard fires inside the
4364 /// accumulator, not around it), so the code-surface arm carries no
4365 /// outer `is_owner`-shaped guard and its dispatch reads through
4366 /// [`Self::validate_foreign_code_kind_coherence`] verbatim without
4367 /// this primitive — the same posture the `_no_code_` /
4368 /// `_ci_kind_` coherence axes take on their respective per-arm
4369 /// shapes. The primitive here is specific to the "outer
4370 /// non-owner-kind guard + inner accumulator + inner emptiness
4371 /// guard + wrap" arm shape that fires three times in
4372 /// [`Self::validate_kind_slot_coherence`].
4373 ///
4374 /// Every future consumer that wants to gate one kind-owned slot
4375 /// family as a unit outside the composed cascade (the deferred
4376 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's admission-
4377 /// webhook per-family re-check after a per-slot patch, a future
4378 /// `feira validate --<family>-coherence` per-caixa admission verb,
4379 /// a per-`Caixa` overlay resolver rejecting a kind-foreign patch
4380 /// on one family) reaches the four-line arm through one call
4381 /// rather than re-inlining the outer-guard + accumulator +
4382 /// emptiness-guard + wrap cascade in lockstep with the pre-existing
4383 /// three arms. Every future kind-owned typed-slot family (an
4384 /// `Actor`-owned per-virtual-actor grain slot the M5 Orleans-
4385 /// inspired kind reaches through, a per-Aplicacao overlay slot the
4386 /// M4 CR materializer consults) folds onto
4387 /// [`Self::validate_kind_slot_coherence`] as one additional
4388 /// dispatch on this primitive rather than a fourth open-coded
4389 /// four-line block.
4390 pub fn run_kind_owned_slot_family_gate<F, A, W>(
4391 &self,
4392 is_owner: F,
4393 accumulator: A,
4394 wrap: W,
4395 ) -> Result<(), crate::LayoutError>
4396 where
4397 F: FnOnce(&crate::CaixaKind) -> bool,
4398 A: FnOnce(&Caixa) -> Vec<&'static str>,
4399 W: FnOnce(&Caixa, Vec<&'static str>) -> crate::LayoutError,
4400 {
4401 if is_owner(&self.kind()) {
4402 return Ok(());
4403 }
4404 let slots = accumulator(self);
4405 if slots.is_empty() {
4406 return Ok(());
4407 }
4408 Err(wrap(self, slots))
4409 }
4410
4411 /// Reject `:nome` values the K8s apiserver would refuse at admission
4412 /// time. The top-level Caixa identity flows directly into every
4413 /// substrate-side artifact's `metadata.name` axis: the
4414 /// `lareira-<nome>` Helm chart name ([`caixa-helm::lib::chart_name`]),
4415 /// the programs.yaml `name:` entry the `lareira-fleet-programs`
4416 /// aggregator keys ComputeUnit derivation off
4417 /// ([`caixa-flux::lib::programs_yaml_entry`]), the
4418 /// `LABEL_APLICACAO` label value carried on every Aplicacao-owned
4419 /// pod and the per-`:contratos` CiliumNetworkPolicy `metadata.name`
4420 /// (`<aplicacao>-<de>-to-<para>`) and the per-`:entrada`
4421 /// `<aplicacao>-<para>` HTTPRoute `metadata.name`
4422 /// ([`caixa-mesh::lib::cilium_network_policies`],
4423 /// [`caixa-mesh::lib::gateway_routes`]), and the default
4424 /// `lib/<nome>.lisp` / `exe/<nome>` layout paths
4425 /// ([`crate::StandardLayout::verify`]). Each K8s apiserver-side
4426 /// schema enforces the DNS-1123 label rule on admission; a
4427 /// structurally invalid `:nome` (`"MyApp"` — the canonical
4428 /// "I copied the display name verbatim" footgun, `"my_app"` — the
4429 /// Python-/Postgres-leak, `"team.app"` — `:nome` is a single label
4430 /// not a subdomain, `"-app"` / `"app-"` — DNS-1123 boundary
4431 /// violations, `"my app"` — the paste-from-doc footgun, `"café"` —
4432 /// IDN must be pre-encoded as Punycode, the 64-byte UUID-shaped
4433 /// over-cap slug) silently passed [`Caixa::from_lisp`] and the
4434 /// failure surfaced at `kubectl apply` time as a `metadata.name:
4435 /// Invalid value` rejection on whichever derived artifact admitted
4436 /// first, far from the source `caixa.lisp` and without any field
4437 /// naming the offending `:nome`.
4438 ///
4439 /// Thin wrapper around [`crate::render::is_dns_1123_label`] (the
4440 /// substrate-side predicate the per-axis name gates already share:
4441 /// `:membros :caixa` 3f9d7a0, `:placement :clusters` 6cbb900,
4442 /// `:children :caixa` 31bfa43) that maps the shared parser-shaped
4443 /// reason into the [`ManifestError::NomeInvalid`] variant, so the
4444 /// diagnostic is self-locating (the offending `:nome` is named
4445 /// verbatim) and the author can grep their `caixa.lisp` for
4446 /// `:nome "<value>"` and fix it in one edit. Same diagnostic shape
4447 /// every per-axis sibling gate already exposes
4448 /// ([`crate::AplicacaoError::MembroCaixaInvalid`],
4449 /// [`crate::AplicacaoError::PlacementClusterInvalid`],
4450 /// [`crate::SupervisorError::ChildCaixaInvalid`]).
4451 ///
4452 /// Empty `:nome` (which [`Caixa::from_lisp`] does not reject — the
4453 /// derive macro stores the raw String) is gated by the narrower
4454 /// [`ManifestError::NomeEmpty`] arm before the predicate is
4455 /// consulted, mirroring the empty-first cascade every per-axis
4456 /// name gate already uses (e.g. `MembroCaixaEmpty` before
4457 /// `MembroCaixaInvalid`, `EmptyChildName` before `ChildCaixaInvalid`).
4458 pub fn validate_nome(&self) -> Result<(), ManifestError> {
4459 // Routes through the shared
4460 // [`crate::render::require_valid_dns_1123_label`] gate the peer
4461 // name axes each land on so drift between the eight axes'
4462 // accepted DNS-1123-label sets is structurally impossible.
4463 let nome = self.nome();
4464 crate::render::require_valid_dns_1123_label(
4465 nome,
4466 || ManifestError::NomeEmpty,
4467 |reason| ManifestError::NomeInvalid {
4468 nome: nome.to_string(),
4469 reason,
4470 },
4471 )
4472 }
4473
4474 /// Reject `:nome` values whose joint length with the canonical
4475 /// [`crate::LAREIRA_CHART_NAME_PREFIX`] (`"lareira-"`) overflows
4476 /// the K8s DNS-1123 label cap [`crate::DNS_1123_LABEL_MAX_LEN`]
4477 /// (63 bytes). Every per-Servico / per-Aplicacao renderer the
4478 /// substrate carries materializes the caixa's `:nome` through the
4479 /// canonical [`crate::lareira_chart_name`] helper (f7320d7) into a
4480 /// `lareira-<nome>` artifact that lands as a K8s `metadata.name` /
4481 /// Helm chart name / `HelmRelease` `release_name`: `caixa-helm`'s
4482 /// `ChartDir.name` + `Chart.yaml::name`
4483 /// (caixa-helm/src/lib.rs:207), `caixa-flux`'s `cluster_bundle`
4484 /// `HelmRelease` `chart:` slot (caixa-flux/src/lib.rs:329),
4485 /// `caixa-tatara`'s `process_for_aplicacao` `release_name` +
4486 /// `oci://<registry>/lareira-<nome>` chart ref
4487 /// (caixa-tatara/src/lib.rs:124,178). Helm's own `Chart.yaml::name`
4488 /// admission rule strict-parses against DNS-1123-label, the Helm
4489 /// operator's tracking-secret name is derived from `release_name`
4490 /// and is itself DNS-1123-label-bounded, and the rendered chart's
4491 /// K8s object `metadata.name` axes embed the chart name as a
4492 /// prefix — every one fails admission on a > 63-byte chart name.
4493 ///
4494 /// The per-axis [`Self::validate_nome`] gate (6c992f8) already
4495 /// caps `:nome` itself at 63 bytes via [`is_dns_1123_label`], so a
4496 /// `:nome` of 56–63 bytes silently passed validate (the inner
4497 /// DNS-1123 check accepts the bare `:nome`) but produced a
4498 /// `lareira-<nome>` of 64–71 bytes that the apiserver / `helm lint`
4499 /// rejected at admission — far from the source `caixa.lisp`, with
4500 /// no field naming the overflow root cause. The
4501 /// [`lareira_chart_name`] helper's own doc comment
4502 /// (caixa-core/src/render.rs:3198) explicitly deferred the fix:
4503 /// "the M4 admission webhook will pin the joint-length invariant
4504 /// when it lands". This gate lands the invariant at the
4505 /// manifest-validate layer rather than waiting for the apiserver
4506 /// — the same fail-at-the-source posture every peer per-axis
4507 /// value-shape gate (DNS-1123 on `:nome`, SemVer-2 on `:versao`,
4508 /// SPDX-expression-shape on `:licenca`, 4-digit decimal year on
4509 /// `:edicao`, etc.) takes.
4510 ///
4511 /// Thin wrapper around
4512 /// [`crate::render::is_lareira_chart_name_shape`] (the
4513 /// substrate-side predicate that composes [`lareira_chart_name`] +
4514 /// [`is_dns_1123_label`] via the lifted
4515 /// [`crate::LAREIRA_CHART_NAME_NOME_MAX_LEN`] budget); maps the
4516 /// shared parser-shaped reason into the
4517 /// [`ManifestError::NomeChartNameBudgetExceeded`] variant so the
4518 /// diagnostic is self-locating (the offending `:nome` is named
4519 /// verbatim alongside the rendered chart name and the budget) and
4520 /// the author can shorten in one edit. The gate runs across every
4521 /// `:kind` — `:nome` is the substrate-wide identity axis any
4522 /// future renderer the substrate adds can derive a
4523 /// `lareira-<nome>` artifact from, and uniform enforcement closes
4524 /// the drift footgun where a future kind grows a chart-emitting
4525 /// render path while the validate cascade doesn't catch it.
4526 ///
4527 /// Runs *after* [`Self::validate_nome`] so the narrower
4528 /// `NomeEmpty` / `NomeInvalid` shape diagnostics fire first — a
4529 /// structurally-malformed `:nome` (empty, uppercase, underscore,
4530 /// dot, leading/trailing hyphen, Unicode, > 63 bytes) surfaces its
4531 /// specific shape error rather than the chart-name-budget error,
4532 /// preserving the legitimate "well-shaped `:nome` that happens to
4533 /// overflow the joint cap" arm for this gate.
4534 pub fn validate_nome_chart_name_budget(&self) -> Result<(), ManifestError> {
4535 let nome = self.nome();
4536 crate::render::is_lareira_chart_name_shape(nome).map_err(|reason| {
4537 ManifestError::NomeChartNameBudgetExceeded {
4538 nome: nome.to_string(),
4539 reason,
4540 }
4541 })
4542 }
4543
4544 /// Reject `:versao` values that don't parse as [`semver::Version`].
4545 /// The top-level Caixa version flows directly into every
4546 /// substrate-side artifact that carries a "this is which version of
4547 /// the caixa" axis: the `lareira-<nome>` Helm chart's `Chart.yaml`
4548 /// `version:` + `appVersion:` axes ([`caixa-helm::lib`] —
4549 /// SemVer-2-strict at `helm template` / `helm install` time per
4550 /// https://helm.sh/docs/topics/charts/#charts-and-versioning), the
4551 /// `feira publish` Zig-style `v<versao>` git tag
4552 /// ([`caixa-flux::lib::programs_yaml_entry`] / the
4553 /// `caixa-publish.yml` reusable workflow), the programs.yaml entry's
4554 /// `versao:` value the `lareira-fleet-programs` aggregator carries
4555 /// onto each rendered ComputeUnit, the OCI image's `:v<versao>` /
4556 /// `:latest` tags the substrate's `wasi-service-flake` builds with
4557 /// `skopeo push`, the lacre closure's pinned versions
4558 /// ([`caixa-resolver`] keys `concrete_versao`), and the
4559 /// `:upgrade-from :from` references peers in this exact `versao`
4560 /// shape (`semver::Version`, not `VersionReq`). Each consumer
4561 /// expects a strict three-part `MAJOR.MINOR.PATCH` (optionally
4562 /// `-prerelease` and/or `+build`); a structurally invalid `:versao`
4563 /// (`"0.1"` — missing patch, the canonical "I shortened it" footgun;
4564 /// `"v0.1.0"` — the git-tag-shape-leaking-into-versao typo;
4565 /// `"latest"` / `"main"` — the "I confused it with a docker tag"
4566 /// footgun; `"^0.1"` / `"~0.1.2"` — the requirement-shape leaking
4567 /// into the version field a peer `:deps :versao` accepts;
4568 /// `"0.1.0.0"` — the four-part Java/Microsoft convention DNS
4569 /// SemVer-2 forbids) silently passed [`Caixa::from_lisp`] (the
4570 /// derive macro stores the raw String) and the failure surfaced at
4571 /// the *first* downstream consumer that strict-parses it: at
4572 /// `helm install` time as a chart-version rejection, at
4573 /// `feira publish` time as a malformed git tag, at lacre-resolve
4574 /// time as a `semver::Error` not naming the offending caixa, at
4575 /// `feira upgrade --to <versao>` time as an unresolvable
4576 /// `:upgrade-from :from` match — far from the source `caixa.lisp`
4577 /// and without any field naming the offending `:versao`.
4578 ///
4579 /// Thin wrapper around [`semver::Version::parse`] — the same parser
4580 /// [`crate::CaixaVersion::parse`] (the typed `:versao` accessor)
4581 /// and [`crate::UpgradeFromEntry::validate`] (the peer
4582 /// `:upgrade-from :from` axis, 26da2c7) consume. Maps the
4583 /// `semver::Error` reason into the [`ManifestError::VersaoInvalid`]
4584 /// variant, carrying the offending `:versao` verbatim + a
4585 /// parser-shaped reason naming the specific violation, so the
4586 /// diagnostic is self-locating (the author can grep their
4587 /// `caixa.lisp` for `:versao "<value>"` and fix it in one edit).
4588 /// Same diagnostic shape as [`ManifestError::NomeInvalid`]
4589 /// (6c992f8) and [`crate::UpgradeError::FromInvalid`]
4590 /// (b0c8389) on the peer axes. With this gate, the typed `:versao`
4591 /// surfaces — top-level `:versao`, `:upgrade-from :from` — are
4592 /// now structurally equivalent (every value past validate is
4593 /// round-trippable through [`semver::Version::parse`] without
4594 /// re-checking at the renderer, resolver, or operator hot-upgrade
4595 /// layer), peer with the four `:versao` requirement axes (`:deps`,
4596 /// `:deps-dev`, `:membros`, `:children`) the prior commits
4597 /// (2420c44, 9888b13, b38ff3a) wired through `parse_requirement`.
4598 ///
4599 /// Empty `:versao` (which [`Caixa::from_lisp`] does not reject —
4600 /// the derive macro stores the raw String) is gated by the
4601 /// narrower [`ManifestError::VersaoEmpty`] arm before the parser is
4602 /// consulted, mirroring the empty-first cascade every per-axis
4603 /// version gate already uses (e.g. `MembroVersaoEmpty` before
4604 /// `MembroVersaoInvalid`, `EmptyChildVersion` before
4605 /// `ChildVersaoInvalid`, `NomeEmpty` before `NomeInvalid`).
4606 pub fn validate_versao(&self) -> Result<(), ManifestError> {
4607 let versao = self.versao();
4608 if versao.is_empty() {
4609 return Err(ManifestError::VersaoEmpty);
4610 }
4611 semver::Version::parse(versao).map_err(|e| ManifestError::VersaoInvalid {
4612 versao: versao.to_string(),
4613 reason: e.to_string(),
4614 })?;
4615 Ok(())
4616 }
4617
4618 /// Compound per-`Caixa` entry gate on the M2 `:upgrade-from` slot:
4619 /// folds the three [`crate::upgrade`] top-level validators — the
4620 /// per-entry shape + cross-entry duplicate-`:from` gate
4621 /// ([`crate::upgrade::validate_upgrade_from`]), the cross-slot
4622 /// `:from < :versao` SemVer-2 precedence gate
4623 /// ([`crate::upgrade::validate_upgrade_from_against_versao`]), and the
4624 /// cross-slot `:state-change` ↔ `:on-state-change` composition gate
4625 /// ([`crate::upgrade::validate_upgrade_from_against_behavior`]) — onto
4626 /// one substrate primitive on [`Caixa`]. The three dispatches run in
4627 /// the same order the layout pipeline
4628 /// ([`crate::layout::StandardLayout::verify`], the `feira build`
4629 /// author-time gate) has always sequenced them, so the fold is
4630 /// byte-for-byte equivalent to the pre-fold three-block cascade at
4631 /// that call site (pinned by the per-arm
4632 /// `validate_upgrade_from_folds_per_entry_arm_matches_gate` /
4633 /// `_folds_versao_arm_matches_gate` / `_folds_behavior_arm_matches_gate`
4634 /// equivalence pins and by the cross-arm
4635 /// `validate_upgrade_from_per_entry_arm_fires_before_versao_arm` /
4636 /// `_versao_arm_fires_before_behavior_arm` ordering pins).
4637 ///
4638 /// Prior to this lift the three [`crate::upgrade`] top-level validators
4639 /// lived only open-coded at the layout wire-up site
4640 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
4641 /// each threaded through the same `self.upgrade_from()` slice and each
4642 /// paired with the same [`crate::LayoutError::UpgradeViolation`]-wrap
4643 /// envelope: every future consumer that wanted to gate `:upgrade-from`
4644 /// as a whole — the deferred `caixa.pleme.io/v1alpha1/Caixa` CR
4645 /// materializer's per-CR admission webhook re-checking `:upgrade-from`
4646 /// after a per-`(:from … :instructions …)` patch, a future `feira
4647 /// validate --upgrade` per-caixa admission verb, a per-`:upgrade-from`
4648 /// overlay resolver a per-cluster overlay lift would materialize —
4649 /// was structurally forced to either re-inline the three-dispatch
4650 /// cascade in lockstep with the layout wire-up (the duplication the
4651 /// PRIME DIRECTIVE names as a bug) or call the whole
4652 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
4653 /// peer per-Caixa gate to re-check one slot. Post-fold each such
4654 /// consumer reaches the three-arm compound gate through one call on
4655 /// the substrate primitive.
4656 ///
4657 /// The three arms together name one contract with three axes:
4658 ///
4659 /// - **per-entry + cross-entry graph-edge invariant** — every entry's
4660 /// `:from` parses as SemVer-2 and every per-instruction / within-
4661 /// entry ordering / singularity gate on each entry's
4662 /// `:instructions` list passes, and no two entries share the same
4663 /// parsed `:from` (the wasm-operator's OTP appup
4664 /// `release_handler:install_release/1` analog picks at most one
4665 /// matching block per running version — two entries with the same
4666 /// parsed semver are an ambiguous edge in the typed upgrade graph).
4667 /// - **cross-slot reachability invariant** — every entry's `:from`
4668 /// is strictly less than the caixa's own `:versao` under SemVer-2
4669 /// precedence. An entry whose `:from >= :versao` is structurally
4670 /// unreachable by the operator's `:from`-match dispatch (the
4671 /// operator loads the current `:versao` and matches the *running*
4672 /// version against each entry's `:from`; an entry whose `:from >=
4673 /// :versao` is never reached because the operator never runs a
4674 /// version >= the current one that it could then upgrade *to* the
4675 /// current one).
4676 /// - **cross-slot composition invariant** — every entry carrying a
4677 /// `(:state-change …)` instruction has a `:behavior
4678 /// :on-state-change` callback declared on the same caixa. The
4679 /// per-version migration script is the `gen_server:code_change/3`
4680 /// analog and the runtime hook it is delivered through during hot
4681 /// upgrade is the `:on-state-change` callback (the upgrade.rs
4682 /// module doc pins the composition verbatim: "Composes with the
4683 /// `:behavior :on-state-change` callback to deliver state migration
4684 /// during hot upgrades").
4685 ///
4686 /// All three axes must hold together — every consumer's
4687 /// `:upgrade-from` accept-set past this compound gate is the same
4688 /// set the `feira build` author-time gate admits.
4689 ///
4690 /// The per-slot compound entry gate discipline lifted here onto the
4691 /// M2 `:upgrade-from` axis is the sibling of the peer per-kind
4692 /// compound entry gates ([`crate::render::require_supervisor_view`]
4693 /// / [`crate::render::require_aplicacao_view`] /
4694 /// [`crate::render::require_v0_servico_shape`]) that fold every
4695 /// per-kind cascade at the per-kind altitude, and of the peer
4696 /// per-slot compound gates ([`crate::AplicacaoSpec::validate_contratos`],
4697 /// [`crate::MeshPolicy::validate`],
4698 /// [`crate::SupervisorSpec::validate_children`]) that fold every
4699 /// structural axis on their slot onto one substrate primitive.
4700 /// Extended here to the last unlifted compound-cascade wire-up at
4701 /// the layout-pipeline altitude — the three-dispatch M2
4702 /// `:upgrade-from` cascade that lived only open-coded at the layout
4703 /// wire-up site.
4704 ///
4705 /// The per-instruction script-path on-disk existence-probe walk that
4706 /// [`crate::layout::StandardLayout::verify`] runs immediately after
4707 /// this gate (which resolves each entry's `:instructions
4708 /// (:state-change :script)` against the layout root) stays open-coded
4709 /// at the layout wire-up site — that arm needs the filesystem oracle
4710 /// on the [`crate::LayoutInvariants`] trait, not the pure per-Caixa
4711 /// typed-shape surface this compound gate folds. Same posture the
4712 /// peer [`Self::validate_code_paths`] takes on the sibling code-path
4713 /// axes: the typed-shape gate fires on the per-Caixa surface, the
4714 /// on-disk existence check fires on the [`crate::StandardLayout`]
4715 /// surface.
4716 ///
4717 /// # Errors
4718 ///
4719 /// Returns [`crate::UpgradeError::FromInvalid`] /
4720 /// [`crate::UpgradeError::ModuleEmpty`] /
4721 /// [`crate::UpgradeError::ModuleInvalid`] /
4722 /// [`crate::UpgradeError::EmptyScript`] /
4723 /// [`crate::UpgradeError::AbsoluteScript`] /
4724 /// [`crate::UpgradeError::ParentEscapeScript`] /
4725 /// [`crate::UpgradeError::NonLispExtensionScript`] /
4726 /// [`crate::UpgradeError::RestartNotExclusive`] /
4727 /// [`crate::UpgradeError::StateChangeWithoutPriorLoad`] /
4728 /// [`crate::UpgradeError::PurgeWithoutPriorLoad`] /
4729 /// [`crate::UpgradeError::StateChangeAfterCleanup`] /
4730 /// [`crate::UpgradeError::DuplicateLoadModule`] /
4731 /// [`crate::UpgradeError::DuplicateStateChange`] /
4732 /// [`crate::UpgradeError::DuplicateCleanup`] /
4733 /// [`crate::UpgradeError::DuplicateFrom`] on the per-entry +
4734 /// cross-entry axis; [`crate::UpgradeError::FromNotBeforeVersao`] on
4735 /// the cross-slot `:from ↔ :versao` axis;
4736 /// [`crate::UpgradeError::StateChangeWithoutOnStateChangeCallback`]
4737 /// on the cross-slot `:state-change ↔ :on-state-change` axis.
4738 pub fn validate_upgrade_from(&self) -> Result<(), crate::UpgradeError> {
4739 crate::upgrade::validate_upgrade_from(self.upgrade_from())?;
4740 crate::upgrade::validate_upgrade_from_against_versao(self.upgrade_from(), self.versao())?;
4741 crate::upgrade::validate_upgrade_from_against_behavior(
4742 self.upgrade_from(),
4743 self.behavior(),
4744 )?;
4745 Ok(())
4746 }
4747
4748 /// Compound per-`Caixa` entry gate on the M2 `:limits` slot — folds
4749 /// the [`crate::LimitsSpec::validate`] four-axis cascade (`:memory`
4750 /// wasm32 zero-floor / below-page / above-cap / non-page-multiple;
4751 /// `:fuel` zero-floor / cap; `:wall-clock` zero-floor / cap; `:cpu`
4752 /// zero-floor / cap) onto one substrate primitive on [`Caixa`]. The
4753 /// `#[serde(default)]` absent-slot arm (`limits: None`, the
4754 /// canonical "no bound declared — engine-default applies" author
4755 /// shape [`crate::LimitsSpec::is_empty`]'s per-axis `None` cascade
4756 /// reads) is the fold's identity element and passes trivially; the
4757 /// present-slot arm (`limits: Some(l)`) dispatches to
4758 /// [`crate::LimitsSpec::validate`] verbatim, threading its per-axis
4759 /// [`crate::LimitsError`] Display through untouched.
4760 ///
4761 /// Prior to this lift the M2 `:limits` slot lived only wired
4762 /// open-coded at the layout wire-up site
4763 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
4764 /// through the `if let Some(l) = caixa.limits() { l.validate() … }`
4765 /// three-line `Option::None → Ok(()) | Some(_) → …` unwrap-and-
4766 /// dispatch pattern paired with the same
4767 /// [`crate::LayoutError::LimitsViolation`]-wrap envelope: every
4768 /// future consumer that wanted to gate `:limits` as a whole — the
4769 /// deferred `caixa.pleme.io/v1alpha1/Caixa` CR materializer's
4770 /// per-CR admission webhook re-checking `:limits` after a per-
4771 /// `{:memory, :fuel, :wall-clock, :cpu}` patch (the exact case the
4772 /// [`Self::limits`] accessor docstring names as the second
4773 /// consumer of the slot), a future `feira validate --limits` per-
4774 /// caixa admission verb, a per-`:limits` overlay resolver a per-
4775 /// cluster `:limits-overrides` overlay lift would materialize — was
4776 /// structurally forced to either re-inline the two-line
4777 /// `Option::None → Ok(()) | Some(_) → …` unwrap-and-dispatch
4778 /// pattern in lockstep with the layout wire-up (the duplication the
4779 /// PRIME DIRECTIVE names as a bug) or call the whole
4780 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
4781 /// peer per-Caixa gate ([`Self::validate_nome`],
4782 /// [`Self::validate_versao`], [`Self::validate_deps`],
4783 /// [`Self::validate_etiquetas`], [`Self::validate_autores`],
4784 /// [`Self::validate_repositorio`], [`Self::validate_descricao`],
4785 /// [`Self::validate_licenca`], [`Self::validate_edicao`],
4786 /// [`Self::validate_upgrade_from`], [`Self::validate_code_paths`],
4787 /// plus the per-kind `require_supervisor_view` /
4788 /// `require_aplicacao_view` gates, plus the on-disk existence
4789 /// walks) to re-check one slot. Post-lift each such consumer
4790 /// reaches the [`crate::LimitsSpec::validate`] four-axis cascade
4791 /// (and its identity-element on the absent slot) through one call
4792 /// on the substrate primitive.
4793 ///
4794 /// The per-slot compound entry-gate discipline lifted here onto the
4795 /// M2 `:limits` axis is the sibling of the peer per-slot compound
4796 /// gates ([`crate::AplicacaoSpec::validate_contratos`],
4797 /// [`crate::MeshPolicy::validate`],
4798 /// [`crate::SupervisorSpec::validate_children`],
4799 /// [`Self::validate_upgrade_from`], [`Self::validate_deps`]) that
4800 /// fold every structural + cross-slot axis on their slot onto one
4801 /// substrate primitive. Extended here to the M2 `:limits` slot, the
4802 /// first of the two M2 typed slots (`:limits`, `:behavior`) whose
4803 /// per-Caixa compound-gate wire-up still lived open-coded at the
4804 /// layout altitude after the [`Self::validate_upgrade_from`] lift
4805 /// (d6801df) closed the sibling M2 slot's cascade.
4806 ///
4807 /// # Errors
4808 ///
4809 /// Returns every [`crate::LimitsError`] variant on the present-slot
4810 /// arm — verbatim from [`crate::LimitsSpec::validate`]. Passes
4811 /// trivially on the absent-slot arm (`limits: None`, the fold's
4812 /// identity element).
4813 pub fn validate_limits(&self) -> Result<(), crate::LimitsError> {
4814 match self.limits() {
4815 Some(l) => l.validate(),
4816 None => Ok(()),
4817 }
4818 }
4819
4820 /// Compound per-`Caixa` entry gate on the M2 `:behavior` slot's
4821 /// pure typed-shape surface — folds the
4822 /// [`crate::BehaviorSpec::validate`] six-slot value-shape cascade
4823 /// (each declared `:on-init` / `:on-call` / `:on-cast` / `:on-info`
4824 /// / `:on-state-change` / `:on-terminate` callback-path is
4825 /// non-empty / relative / no-`..`-parent-escape / terminating-
4826 /// `.lisp`-extension, routed through the shared
4827 /// [`crate::render::require_sandboxed_lisp_path`] arm-set) onto one
4828 /// substrate primitive on [`Caixa`]. The `#[serde(default)]`
4829 /// absent-slot arm (`behavior: None`, the canonical "no callback
4830 /// declared — the runtime falls back to the wasm-engine's default
4831 /// callback per arm" author shape [`crate::BehaviorSpec::is_empty`]'s
4832 /// per-slot `None` cascade reads) is the fold's identity element
4833 /// and passes trivially; the present-slot arm (`behavior: Some(b)`)
4834 /// dispatches to [`crate::BehaviorSpec::validate`] verbatim,
4835 /// threading its per-slot [`crate::BehaviorError`] Display through
4836 /// untouched.
4837 ///
4838 /// Scope note — the on-disk callback-path existence walk paired
4839 /// with the value-shape gate at
4840 /// [`crate::layout::StandardLayout::verify`] stays open-coded at
4841 /// the layout altitude, because it needs the
4842 /// [`crate::layout::LayoutInvariants`] filesystem oracle
4843 /// ([`crate::layout::LayoutInvariants::exists`]) that the pure
4844 /// per-Caixa typed-shape surface this compound gate folds onto has
4845 /// no reference to. Same posture the peer M2 `:upgrade-from`
4846 /// per-Caixa compound gate ([`Self::validate_upgrade_from`]
4847 /// d6801df) already carries: the pure typed-shape surface folds
4848 /// onto the substrate primitive; the per-instruction script-path
4849 /// existence probe on the paired axis (there `:state-change
4850 /// :script`; here `:on-*`) stays at the layout altitude.
4851 ///
4852 /// Prior to this lift the pure value-shape surface of the M2
4853 /// `:behavior` slot lived only wired open-coded at the layout
4854 /// wire-up site ([`crate::layout::StandardLayout::verify`],
4855 /// caixa-core/src/layout.rs), through the
4856 /// `if let Some(b) = caixa.behavior() { b.validate() … }`
4857 /// unwrap-and-dispatch pattern paired with the same
4858 /// [`crate::LayoutError::BehaviorViolation`]-wrap envelope: every
4859 /// future consumer that wanted to gate the `:behavior` slot's
4860 /// value-shape as a whole — the deferred
4861 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's per-CR
4862 /// admission webhook re-checking `:behavior` after a per-`{:on-init,
4863 /// :on-call, :on-cast, :on-info, :on-state-change, :on-terminate}`
4864 /// patch (the exact case the peer `:on-*` accessor docstrings on
4865 /// [`crate::BehaviorSpec`] already name as deferred consumers of
4866 /// the slot), a future `feira validate --behavior` per-caixa
4867 /// admission verb, a per-`:behavior` overlay resolver a future
4868 /// per-cluster callback-overlay lift would materialize — was
4869 /// structurally forced to either re-inline the two-line
4870 /// `Option::None → Ok(()) | Some(_) → …` unwrap-and-dispatch
4871 /// pattern in lockstep with the layout wire-up (the duplication the
4872 /// PRIME DIRECTIVE names as a bug) or call the whole
4873 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
4874 /// peer per-Caixa gate ([`Self::validate_nome`],
4875 /// [`Self::validate_versao`], [`Self::validate_deps`],
4876 /// [`Self::validate_etiquetas`], [`Self::validate_autores`],
4877 /// [`Self::validate_repositorio`], [`Self::validate_descricao`],
4878 /// [`Self::validate_licenca`], [`Self::validate_edicao`],
4879 /// [`Self::validate_limits`], [`Self::validate_upgrade_from`],
4880 /// [`Self::validate_code_paths`], plus the per-kind
4881 /// `require_supervisor_view` / `require_aplicacao_view` gates, plus
4882 /// the on-disk existence walks) to re-check one slot. Post-lift
4883 /// each such consumer reaches the [`crate::BehaviorSpec::validate`]
4884 /// six-slot cascade (and its identity-element on the absent slot)
4885 /// through one call on the substrate primitive.
4886 ///
4887 /// The per-slot compound entry-gate discipline lifted here onto the
4888 /// M2 `:behavior` axis is the sibling of the peer per-slot compound
4889 /// gates ([`crate::AplicacaoSpec::validate_contratos`],
4890 /// [`crate::MeshPolicy::validate`],
4891 /// [`crate::SupervisorSpec::validate_children`],
4892 /// [`Self::validate_upgrade_from`], [`Self::validate_deps`],
4893 /// [`Self::validate_limits`]) that fold every structural + cross-
4894 /// slot axis on their slot onto one substrate primitive. Extended
4895 /// here to the M2 `:behavior` slot, the last of the four M2 typed
4896 /// slots (`:limits`, `:behavior`, `:upgrade-from`, plus the
4897 /// supervisor-only `:children` peer) whose per-Caixa compound-gate
4898 /// wire-up still lived open-coded at the layout altitude after the
4899 /// [`Self::validate_limits`] lift (baa4688) closed the sibling M2
4900 /// `:limits` slot's cascade. With this lift the "one named per-slot
4901 /// / per-Caixa compound gate per typed slot folding every structural
4902 /// axis on that slot (plus the `Option::None` identity element for
4903 /// the `Option`-shaped slots) onto one substrate primitive"
4904 /// discipline spans every M2 typed slot uniformly, so a reader who
4905 /// has learned any peer M2 gate reads `:behavior` without a per-
4906 /// slot exception carve-out.
4907 ///
4908 /// # Errors
4909 ///
4910 /// Returns every [`crate::BehaviorError`] variant on the present-
4911 /// slot arm — verbatim from [`crate::BehaviorSpec::validate`].
4912 /// Passes trivially on the absent-slot arm (`behavior: None`, the
4913 /// fold's identity element).
4914 pub fn validate_behavior(&self) -> Result<(), crate::BehaviorError> {
4915 match self.behavior() {
4916 Some(b) => b.validate(),
4917 None => Ok(()),
4918 }
4919 }
4920
4921 /// Reject `:restart-window` values the shared
4922 /// [`crate::supervisor::duration_codec::parse`] refuses. The flat
4923 /// `restart_window: Option<String>` slot on [`Caixa`] is stored
4924 /// raw by the derive macro (the typed [`SupervisorSpec`] holds an
4925 /// `Option<Duration>` routed through the shared codec via `with =
4926 /// "duration_codec"`); the inline `Caixa → SupervisorSpec`
4927 /// view-construction path ([`Self::supervisor_view`]) folds the
4928 /// raw string through the same shared codec and soft-swallows the
4929 /// parse error as `None` to keep the view best-effort. Without
4930 /// this gate a malformed `:restart-window` (`"1.5s"` — the
4931 /// fractional-seconds drift class; `"1.0s"` — the decimal-shaped
4932 /// integer drift; `"0.5m"` — the unit-fraction drift; `"+30s"` /
4933 /// `"-30s"` — the leading-sign drift; `"30x"` — the unknown-unit
4934 /// footgun; `"abc"` — pure garbage; `""` — the empty-after-trim
4935 /// edge case) silently produced a `SupervisorSpec` with
4936 /// `restart_window: None`, indistinguishable from the canonical
4937 /// "omit the slot to express no reset" authoring shape — Erlang/OTP's
4938 /// `MaxIntensity / Period` invariant turns into a never-reset
4939 /// supervisor far from the source `caixa.lisp`, with no field
4940 /// naming the offending `:restart-window`. Lifting the gate to a
4941 /// Caixa-level validator mirrors the trajectory of the peer
4942 /// per-axis identity gates ([`Self::validate_nome`] 6c992f8,
4943 /// [`Self::validate_versao`] 1fdaa02, [`Self::validate_deps`]
4944 /// a7f0d8c) and the ABSORPTION-ROADMAP.md M2.2 test pin
4945 /// (line 196: "reject invalid `:restart-window` (non-duration)").
4946 ///
4947 /// Thin wrapper around [`crate::supervisor::duration_codec::parse`]
4948 /// (the shared codec backing `:supervisor :restart-window` as
4949 /// serde-routed on [`SupervisorSpec`], `:politicas :timeout`, and
4950 /// `:politicas :circuit-breaker :window` — all three covered by
4951 /// the integer-magnitude gate 1c55a2a). Maps the codec's parse
4952 /// error verbatim into the [`ManifestError::RestartWindowMalformed`]
4953 /// variant, carrying the offending raw string + a parser-shaped
4954 /// reason naming the canonical authoring form, so the diagnostic
4955 /// is self-locating (the author can grep their `caixa.lisp` for
4956 /// `:restart-window "<value>"` and fix it in one edit) and
4957 /// uniform with every other manifest-level validate diagnostic.
4958 /// With this gate the four `:restart-window`-shaped surfaces (the
4959 /// flat raw string on [`Caixa`], the typed `Option<Duration>` on
4960 /// [`SupervisorSpec`], the two `MeshPolicy` peer durations) are
4961 /// now structurally equivalent — every value past the codec is in
4962 /// one accepted set, by construction.
4963 ///
4964 /// `None` (the canonical "omit the slot to express no reset"
4965 /// shape) is accepted trivially — the gate is a no-op when the
4966 /// author didn't author a window. The empty string is rejected by
4967 /// the shared codec (its digit-only gate refuses an empty
4968 /// magnitude), surfacing the same `RestartWindowMalformed`
4969 /// diagnostic as every other rejected non-canonical shape.
4970 pub fn validate_restart_window(&self) -> Result<(), ManifestError> {
4971 let Some(s) = self.restart_window() else {
4972 return Ok(());
4973 };
4974 crate::supervisor::duration_codec::parse(s)
4975 .map(|_| ())
4976 .map_err(|reason| ManifestError::RestartWindowMalformed {
4977 restart_window: s.to_string(),
4978 reason,
4979 })
4980 }
4981
4982 /// Compound per-`Caixa` entry gate on the Aplicacao-kind mesh-slot
4983 /// family — folds the paired [`crate::AplicacaoSpec::validate`]
4984 /// typed-shape cascade (per-slot gates on `:membros`, `:contratos`,
4985 /// `:entrada`, `:placement`, `:politicas`, in that declared order)
4986 /// plus the cross-slot self-edge gate
4987 /// ([`crate::aplicacao::validate_no_self_membership`], the
4988 /// `:membros :caixa` ≠ `:nome` invariant the typed view cannot
4989 /// enforce on its own because it carries the membros but not the
4990 /// parent `:nome`) onto one substrate primitive on [`Caixa`]. On
4991 /// non-Aplicacao kinds the fold is the identity element — the paired
4992 /// [`Self::aplicacao_view`] accessor returns `None` off the
4993 /// Aplicacao arm (peer with the [`Self::validate_limits`] /
4994 /// [`Self::validate_behavior`] M2 `Option`-arm identity element),
4995 /// so the gate passes trivially without touching the mesh slots.
4996 ///
4997 /// Prior to this lift the paired cascade lived only wired open-coded
4998 /// at the layout wire-up site
4999 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
5000 /// as the three-line `let view = caixa.aplicacao_view().expect(...);
5001 /// view.validate() … validate_no_self_membership(...) …` pattern
5002 /// paired with two `.map_err(|err| LayoutError::AplicacaoViolation
5003 /// { caixa, issue })` wraps — every future consumer that wanted to
5004 /// gate the Aplicacao-shape cascade as a whole (the deferred
5005 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's per-CR
5006 /// admission webhook re-checking `:membros` / `:contratos` after a
5007 /// per-slot patch, a future `feira validate --aplicacao` per-caixa
5008 /// admission verb, a per-Aplicacao overlay resolver) was structurally
5009 /// forced to either re-inline the two-dispatch cascade in lockstep
5010 /// with the layout wire-up (the duplication the PRIME DIRECTIVE
5011 /// names as a bug) or call the whole
5012 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
5013 /// peer per-Caixa gate to re-check one slot family. Post-fold each
5014 /// such consumer reaches the two-arm compound gate through one call
5015 /// on the substrate primitive.
5016 ///
5017 /// Peer to the [`crate::render::require_aplicacao_view`] compound
5018 /// entry gate every per-Aplicacao *renderer* routes through
5019 /// (3aefefb folded `validate_no_self_membership` onto the renderer
5020 /// path) — this gate mirrors the same fold on the *layout* path, so
5021 /// the two consumers of the Aplicacao-shape cascade (the author-time
5022 /// gate and every per-Aplicacao renderer) share one substrate
5023 /// primitive rather than two open-coded cascades kept in lockstep.
5024 /// Same lift discipline the peer per-slot compound gates
5025 /// ([`Self::validate_upgrade_from`] d6801df, [`Self::validate_deps`]
5026 /// b5dd55e, [`Self::validate_limits`] baa4688,
5027 /// [`Self::validate_behavior`] 0d2877a) each carry.
5028 ///
5029 /// # Errors
5030 ///
5031 /// Returns every [`crate::AplicacaoError`] variant on the present-
5032 /// kind arm — the typed-shape cascade's per-slot arms first
5033 /// (matching [`crate::AplicacaoSpec::validate`]'s declared order),
5034 /// then the cross-slot self-edge arm
5035 /// ([`crate::AplicacaoError::MembroIsSelfAplicacao`]). Passes
5036 /// trivially on non-Aplicacao kinds (the fold's identity element).
5037 pub fn validate_aplicacao_shape(&self) -> Result<(), crate::AplicacaoError> {
5038 let Some(view) = self.aplicacao_view() else {
5039 return Ok(());
5040 };
5041 view.validate()?;
5042 crate::aplicacao::validate_no_self_membership(self.membros(), self.nome())?;
5043 Ok(())
5044 }
5045
5046 /// Compound per-`Caixa` entry gate on the Supervisor-kind
5047 /// supervision-tree slot family — folds the paired
5048 /// [`crate::SupervisorSpec::validate`] typed-shape cascade
5049 /// (`:estrategia` ↔ `:children` invariants, `:max-restarts` /
5050 /// `:restart-window` bounds, per-child DNS-1123 `:caixa` names,
5051 /// semver-valid `:versao` constraints, the set-not-multiset
5052 /// duplicate-child gate) plus the cross-slot self-edge gate
5053 /// ([`crate::supervisor::validate_no_self_supervision`], the
5054 /// `:children :caixa` ≠ `:nome` invariant the typed view cannot
5055 /// enforce on its own because it carries the children but not the
5056 /// parent `:nome`) onto one substrate primitive on [`Caixa`]. On
5057 /// non-Supervisor kinds the fold is the identity element — the paired
5058 /// [`Self::supervisor_view`] accessor returns `None` off the
5059 /// Supervisor arm (peer with the [`Self::validate_limits`] /
5060 /// [`Self::validate_behavior`] M2 `Option`-arm identity element and
5061 /// the sibling per-Aplicacao [`Self::validate_aplicacao_shape`]),
5062 /// so the gate passes trivially without touching the supervision-tree
5063 /// slots.
5064 ///
5065 /// Prior to this lift the paired cascade lived only wired open-coded
5066 /// at the layout wire-up site
5067 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
5068 /// as the three-line `let view = caixa.supervisor_view().expect(...);
5069 /// view.validate() … validate_no_self_supervision(...) …` pattern
5070 /// paired with two `.map_err(|err| LayoutError::SupervisorViolation
5071 /// { caixa, issue })` wraps — every future consumer that wanted to
5072 /// gate the Supervisor-shape cascade as a whole (the wasm-operator's
5073 /// hierarchical reconciliation scheduler re-checking `:children` /
5074 /// `:estrategia` after a per-slot patch, the M4
5075 /// `mesh.pleme.io/v1alpha1/Supervisor` CR materializer's admission
5076 /// webhook, a future `feira validate --supervisor` per-caixa
5077 /// admission verb, a per-Supervisor overlay resolver) was structurally
5078 /// forced to either re-inline the two-dispatch cascade in lockstep
5079 /// with the layout wire-up (the duplication the PRIME DIRECTIVE
5080 /// names as a bug) or call the whole
5081 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
5082 /// peer per-Caixa gate to re-check one slot family. Post-fold each
5083 /// such consumer reaches the two-arm compound gate through one call
5084 /// on the substrate primitive.
5085 ///
5086 /// Peer to the [`crate::render::require_supervisor_view`] compound
5087 /// entry gate every per-Supervisor *renderer* would route through
5088 /// (which already folds the same `spec.validate()` +
5089 /// `validate_no_self_supervision` two-arm cascade behind its
5090 /// `require_kind` + `validate_restart_window` prelude) — this gate
5091 /// mirrors the same fold on the *layout* path, so the two consumers
5092 /// of the Supervisor-shape cascade (the author-time gate and every
5093 /// per-Supervisor renderer) share one substrate primitive rather
5094 /// than two open-coded cascades kept in lockstep. Same lift
5095 /// discipline the peer per-slot compound gates
5096 /// ([`Self::validate_aplicacao_shape`] 949a7a0,
5097 /// [`Self::validate_upgrade_from`] d6801df,
5098 /// [`Self::validate_deps`] b5dd55e, [`Self::validate_limits`]
5099 /// baa4688, [`Self::validate_behavior`] 0d2877a) each carry.
5100 ///
5101 /// # Errors
5102 ///
5103 /// Returns every [`crate::SupervisorError`] variant on the present-
5104 /// kind arm — the typed-shape cascade's per-slot arms first
5105 /// (matching [`crate::SupervisorSpec::validate`]'s declared order),
5106 /// then the cross-slot self-edge arm
5107 /// ([`crate::SupervisorError::ChildSupervisesSelf`]). Passes
5108 /// trivially on non-Supervisor kinds (the fold's identity element).
5109 pub fn validate_supervisor_shape(&self) -> Result<(), crate::SupervisorError> {
5110 let Some(view) = self.supervisor_view() else {
5111 return Ok(());
5112 };
5113 view.validate()?;
5114 crate::supervisor::validate_no_self_supervision(self.children(), self.nome())?;
5115 Ok(())
5116 }
5117
5118 /// Compound per-`Caixa` entry gate on the Acao-kind `:ci` slot
5119 /// family — folds the [`crate::decompose_ci`] typed decompose gate
5120 /// (`canteiro_types::decompose` refusing every illegal
5121 /// [`canteiro_types::CiRun`] shape: duplicate node name, dependency
5122 /// on an undeclared node, dependency cycle) onto one substrate
5123 /// primitive on [`Caixa`]. On non-`Acao` kinds the fold is the
5124 /// identity element — the paired [`Self::kind`] `is_acao()` guard
5125 /// short-circuits before the decompose gate ever fires (peer with
5126 /// the [`Self::validate_aplicacao_shape`] /
5127 /// [`Self::validate_supervisor_shape`] typed-view identity element
5128 /// and the [`Self::validate_limits`] / [`Self::validate_behavior`]
5129 /// M2 `Option`-arm identity element), so the gate passes trivially
5130 /// without touching the `:ci` slot. An `:kind Acao` caixa with
5131 /// `ci = None` is also an identity-element pass: the presence gate
5132 /// is the sibling axis owned by [`crate::LayoutError::MissingCi`] /
5133 /// [`crate::require_ci`] / [`crate::MissingCiSlot`], not by the
5134 /// decompose gate — a caixa that carries no `:ci` slot has no run
5135 /// to decompose. Same split the peer per-Servico
5136 /// [`crate::LayoutError::ServicoWithoutServicos`] presence gate and
5137 /// per-Binario [`crate::LayoutError::BinarioWithoutExe`] presence
5138 /// gate keep from their sibling per-slot shape gates, so the two
5139 /// axes stay separately diagnosable at the layout altitude.
5140 ///
5141 /// Prior to this lift the decompose gate lived only wired
5142 /// open-coded at the [`caixa_actions::validate`] renderer-side
5143 /// entry gate (routed through the substrate-canonical
5144 /// [`crate::require_acao_view`] compound helper) — the *layout*
5145 /// pipeline ([`crate::layout::StandardLayout::verify`], caixa-core/
5146 /// src/layout.rs) only checked `:ci` *presence* via
5147 /// [`crate::LayoutError::MissingCi`], so a `:kind Acao` caixa
5148 /// carrying a structurally illegal `:ci` (a duplicate node name, a
5149 /// dependency on an undeclared node, a dependency cycle) passed
5150 /// `feira build` cleanly and surfaced the diagnostic only when
5151 /// [`caixa_actions::validate`] later refused it — far from the
5152 /// source `caixa.lisp` on the author-time gate side. Every future
5153 /// consumer that wanted to gate the Acao-shape cascade as a whole
5154 /// (a per-`Acao` CR materializer's admission webhook re-checking
5155 /// `:ci` after a per-node patch, a future `feira validate --acao`
5156 /// per-caixa admission verb, a per-`Acao` overlay resolver
5157 /// rejecting an added / renamed node against a cluster-local
5158 /// snapshot) was structurally forced to either re-inline the
5159 /// decompose dispatch in lockstep with the renderer-side wire-up
5160 /// (the duplication the PRIME DIRECTIVE names as a bug) or call
5161 /// the whole [`caixa_actions::validate`] renderer and pay the
5162 /// per-node accumulation to re-check one slot. Post-fold each such
5163 /// consumer reaches the decompose gate through one call on the
5164 /// substrate primitive.
5165 ///
5166 /// Peer to the [`crate::require_acao_view`] compound entry gate
5167 /// every per-`Acao` *renderer* routes through (which already folds
5168 /// the same `require_ci + decompose_ci` two-arm cascade behind its
5169 /// `require_kind` prelude) — this gate mirrors the same fold on
5170 /// the *layout* path, so the two consumers of the Acao-shape
5171 /// cascade (the author-time gate and every per-`Acao` renderer)
5172 /// share one substrate primitive rather than two open-coded
5173 /// cascades kept in lockstep. Same lift discipline the peer
5174 /// per-kind compound gates ([`Self::validate_aplicacao_shape`]
5175 /// 949a7a0, [`Self::validate_supervisor_shape`] 4c70105,
5176 /// [`Self::validate_upgrade_from`] d6801df, [`Self::validate_deps`]
5177 /// b5dd55e, [`Self::validate_limits`] baa4688,
5178 /// [`Self::validate_behavior`] 0d2877a) each carry. Closes the
5179 /// last per-kind asymmetry: with this lift the four typed
5180 /// named-caixa kinds (`Servico` / `Aplicacao` / `Supervisor` /
5181 /// `Acao`) each carry a compound per-`Caixa` shape gate on the
5182 /// substrate, and the layout pipeline routes through the same one
5183 /// substrate primitive per kind rather than four open-coded
5184 /// cascades.
5185 ///
5186 /// # Errors
5187 ///
5188 /// Returns the [`crate::CiDecomposeFailure`] typed view on the
5189 /// present-slot arm — the caixa's `:nome` alongside the borrowed
5190 /// [`canteiro_types::DecomposeError`] source (`DuplicateNode` /
5191 /// `UnknownDep` / `Cycle`) verbatim, so a consumer that fans on
5192 /// the specific arm reaches for `err.source` directly rather than
5193 /// re-parsing the Display bytes. Passes trivially on non-`Acao`
5194 /// kinds and on `:kind Acao` caixas with absent `:ci` (the fold's
5195 /// two identity-element arms).
5196 pub fn validate_acao_shape(&self) -> Result<(), crate::CiDecomposeFailure> {
5197 if !self.kind().is_acao() {
5198 return Ok(());
5199 }
5200 let Some(ci) = self.ci() else {
5201 return Ok(());
5202 };
5203 crate::render::decompose_ci(self, ci).map(|_| ())
5204 }
5205
5206 /// Compound per-`Caixa` kind ↔ typed-slot coherence gate on the
5207 /// three "declared but ignored" typed-slot families — M3 mesh
5208 /// (`:membros` / `:contratos` / `:politicas` / `:placement` /
5209 /// `:entrada`, owned by `:kind Aplicacao`, MESH-COMPOSITION §III.1),
5210 /// supervisor-tree (`:estrategia` / `:max-restarts` /
5211 /// `:restart-window` / `:children`, owned by `:kind Supervisor`,
5212 /// INSPIRATIONS §II.2), and M2 Servico-runtime (`:limits` /
5213 /// `:behavior` / `:upgrade-from`, owned by `:kind Servico`,
5214 /// INSPIRATIONS §III.1 / §II.3 / §II.4). Folds the three sibling
5215 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5216 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5217 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
5218 /// gates — each pre-lift a self-similar five-line
5219 /// `if !caixa.kind().is_<owner>() { let slots = caixa.declared_
5220 /// <family>_slots(); if !slots.is_empty() { return
5221 /// Err(LayoutError::<family>_on_non_<owner>(caixa, slots)); } }`
5222 /// block at [`crate::layout::StandardLayout::verify`] — onto one
5223 /// substrate primitive on [`Caixa`]. Every arm passes as an
5224 /// identity element on the owner kind (the paired
5225 /// [`Self::kind`] `is_<owner>()` guard short-circuits before the
5226 /// per-family `declared_*_slots` gate fires) and on non-owner
5227 /// kinds carrying no declared slot in that family (the
5228 /// [`Vec::is_empty`] check short-circuits before the wrap fires),
5229 /// so a bare no-code caixa on any kind passes the fold trivially
5230 /// on all three arms.
5231 ///
5232 /// Prior to this lift the three-arm cascade lived only wired
5233 /// open-coded at the layout wire-up site
5234 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/
5235 /// layout.rs) as three self-similar five-line blocks paired with
5236 /// three [`crate::LayoutError::mesh_slots_on_non_aplicacao`] /
5237 /// [`crate::LayoutError::supervisor_slots_on_non_supervisor`] /
5238 /// [`crate::LayoutError::servico_slots_on_non_servico`] ctor
5239 /// dispatches (each of which the peer
5240 /// [`crate::layout::layout_slot_kind_ctors!`] macro already folds
5241 /// onto one substrate primitive per typed variant, 0419438) —
5242 /// every future consumer that wanted to gate the whole
5243 /// kind-coherence cascade as a unit (the deferred
5244 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's admission
5245 /// webhook re-checking every typed-slot family after a per-slot
5246 /// patch, a future `feira validate --kind-coherence` per-caixa
5247 /// admission verb, a per-`Caixa` overlay resolver rejecting a
5248 /// kind-foreign patch against a cluster-local snapshot) was
5249 /// structurally forced to either re-inline the three-block
5250 /// cascade in lockstep with the layout wire-up (the duplication
5251 /// the PRIME DIRECTIVE names as a bug) or call the whole
5252 /// [`crate::layout::StandardLayout::verify`] pipeline and pay
5253 /// every peer per-`Caixa` gate to re-check three slot families.
5254 /// Post-fold each such consumer reaches the three-arm cascade
5255 /// through one call on the substrate primitive.
5256 ///
5257 /// Diagnostic order matches the pre-fold layout wire-up
5258 /// canonical sequence — mesh → supervisor → servico — pinned by
5259 /// the load-bearing
5260 /// `validate_kind_slot_coherence_mesh_arm_fires_before_supervisor_arm`
5261 /// / `_supervisor_arm_fires_before_servico_arm` ordering pins
5262 /// below. The three arms enumerate every typed-slot family the
5263 /// substrate carries whose "declared but ignored" footgun is
5264 /// gated at the layout altitude by a `{ caixa, kind, slots }`
5265 /// wrap variant — the peer
5266 /// [`crate::LayoutError::ForeignCodeSlot`] gate on the
5267 /// code-surface family sits outside this fold because
5268 /// [`Self::declared_foreign_code_slots`] bakes the kind-check
5269 /// into the helper (so the layout wire-up carries no outer
5270 /// `if !caixa.kind().is_<owner>()` guard), and the peer
5271 /// [`crate::LayoutError::CiOnNonAcao`] gate on the `:ci` axis
5272 /// carries a distinct `{ caixa, kind }` wrap shape (no `slots`
5273 /// field — `:ci` is a single `Option` not a `Vec`-of-named-slots)
5274 /// and rides on its own peer substrate primitive
5275 /// [`Self::validate_ci_kind_coherence`] (the direct sibling to
5276 /// this fold on the `:ci` axis) — the two folds share the same
5277 /// altitude and diagnostic order at the layout wire-up site but
5278 /// keep their distinct envelope shapes, so no consumer of
5279 /// `CiOnNonAcao` sees a variant rename.
5280 ///
5281 /// Peer to the per-kind compound entry gates every substrate
5282 /// primitive on the M2/M3 typed-slot family already carries
5283 /// ([`Self::validate_deps`] b5dd55e, [`Self::validate_limits`]
5284 /// baa4688, [`Self::validate_behavior`] 0d2877a,
5285 /// [`Self::validate_upgrade_from`] d6801df,
5286 /// [`Self::validate_aplicacao_shape`] 949a7a0,
5287 /// [`Self::validate_supervisor_shape`] 4c70105,
5288 /// [`Self::validate_acao_shape`] 5d6df54): the author-time gate
5289 /// axis on the *per-slot* algebra now shares one substrate
5290 /// primitive per compound gate, and this lift closes the
5291 /// symmetric axis on the *cross-family* kind ↔ slot coherence
5292 /// algebra so the layout pipeline routes the three self-similar
5293 /// gates through one substrate primitive rather than three
5294 /// open-coded blocks. Every future kind that adds its own
5295 /// exclusive typed-slot family (an `Actor`-owned per-virtual-
5296 /// actor grain slot the M5 Orleans-inspired kind reaches
5297 /// through, a per-Aplicacao overlay slot the M4 CR materializer
5298 /// consults) folds onto this compound gate as one arm addition
5299 /// rather than a fourth open-coded block at the wire-up site.
5300 ///
5301 /// # Errors
5302 ///
5303 /// Returns the first [`crate::LayoutError`] variant surfacing
5304 /// under the canonical mesh → supervisor → servico order:
5305 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] on a non-
5306 /// Aplicacao caixa with a declared M3 mesh slot,
5307 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] on a
5308 /// non-Supervisor caixa with a declared supervisor-tree slot,
5309 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] on a
5310 /// non-Servico caixa with a declared M2 slot. Passes trivially
5311 /// on the owner kind of each family and on non-owner kinds
5312 /// carrying no declared slot in that family (the fold's identity
5313 /// element on both axes).
5314 pub fn validate_kind_slot_coherence(&self) -> Result<(), crate::LayoutError> {
5315 // Each of the three arms routes through the shared
5316 // [`Self::run_kind_owned_slot_family_gate`] substrate primitive
5317 // — the outer non-owner-kind guard + inner accumulator + inner
5318 // emptiness-guard + wrap arm shape now lands on one dispatch
5319 // per family rather than a four-line open-coded block in
5320 // lockstep across all three arms. Canonical mesh → supervisor
5321 // → servico order preserved (the primitive short-circuits
5322 // arm-by-arm; the outer `?;` cascade at this altitude threads
5323 // the first surfaced arm's error verbatim). Each of the three
5324 // ctors ([`crate::LayoutError::mesh_slots_on_non_aplicacao`] /
5325 // [`crate::LayoutError::supervisor_slots_on_non_supervisor`] /
5326 // [`crate::LayoutError::servico_slots_on_non_servico`]) was
5327 // already lifted onto the substrate by the peer
5328 // [`crate::layout::layout_slot_kind_ctors!`] macro, so each arm
5329 // routes through the same substrate-canonical
5330 // `Self::<variant> { caixa, kind, slots }` wrap per arm as the
5331 // pre-lift open-coded blocks — byte-equal, pinned by the
5332 // paired `validate_kind_slot_coherence_folds_<family>_arm_matches_gate`
5333 // equivalence pins and the peer
5334 // `validate_kind_slot_coherence_{mesh,supervisor}_arm_fires_before_<next>_arm`
5335 // ordering pins.
5336 self.run_kind_owned_slot_family_gate(
5337 crate::CaixaKind::is_aplicacao,
5338 Caixa::declared_mesh_slots,
5339 crate::LayoutError::mesh_slots_on_non_aplicacao,
5340 )?;
5341 self.run_kind_owned_slot_family_gate(
5342 crate::CaixaKind::is_supervisor,
5343 Caixa::declared_supervisor_slots,
5344 crate::LayoutError::supervisor_slots_on_non_supervisor,
5345 )?;
5346 self.run_kind_owned_slot_family_gate(
5347 crate::CaixaKind::is_servico,
5348 Caixa::declared_servico_slots,
5349 crate::LayoutError::servico_slots_on_non_servico,
5350 )?;
5351 Ok(())
5352 }
5353
5354 /// Compound per-`Caixa` kind ↔ code-surface coherence gate on
5355 /// the three no-code kinds — `Supervisor` (supervises other
5356 /// caixas, INSPIRATIONS §II.2), `Aplicacao` (composes Servicos,
5357 /// MESH-COMPOSITION §III.1), and `Acao` (owns a typed CI run,
5358 /// CANTEIRO §7.1-C). Each carries no code of its own, so
5359 /// declaring any of `:bibliotecas` / `:exe` / `:servicos`
5360 /// silently passes the layout's path-existence loops (the paths
5361 /// still resolve on disk) and then vanishes downstream — the
5362 /// per-kind renderers gate emission on
5363 /// [`crate::render::require_kind`] and only emit the code
5364 /// surface for its owning kind, so a declared code slot on a
5365 /// no-code kind is the manifest field's documented "ignored
5366 /// otherwise" footgun.
5367 ///
5368 /// Pre-lift each of the three arms lived as a self-similar
5369 /// `if !caixa.kind().is_<no-code-kind>() { … } else if has_code
5370 /// { return Err(LayoutError::<kind>_owns_code(caixa)); }` block
5371 /// at [`crate::layout::StandardLayout::verify`] — three
5372 /// consumers, three identical shapes. Every future consumer
5373 /// that wanted to gate the whole code-surface coherence cascade
5374 /// as a unit (the deferred `caixa.pleme.io/v1alpha1/Caixa` CR
5375 /// materializer's admission webhook re-checking after a
5376 /// per-slot patch, a future `feira validate --no-code-kind`
5377 /// per-caixa admission verb, a per-`Caixa` overlay resolver
5378 /// rejecting a kind-foreign patch) was structurally forced to
5379 /// either re-inline the three-block cascade in lockstep with
5380 /// the layout wire-up (the duplication the PRIME DIRECTIVE
5381 /// names as a bug) or call the whole
5382 /// [`crate::layout::StandardLayout::verify`] pipeline. Post-fold
5383 /// each such consumer reaches the three-arm cascade through
5384 /// one call.
5385 ///
5386 /// Mirror of the sibling [`Self::validate_kind_slot_coherence`]
5387 /// fold (f0d286e) on the author-time typed-slot coherence axis:
5388 /// that gate closes the "non-owner kind declares owner-only
5389 /// typed slots" three-arm cascade on the M2 / supervisor-tree /
5390 /// M3 slot families; this gate closes the reciprocal
5391 /// "no-code kind declares code" three-arm cascade on the
5392 /// `:bibliotecas` / `:exe` / `:servicos` code surface. Together
5393 /// the two folds route every kind ↔ author-shape coherence
5394 /// diagnostic at the layout altitude through one substrate
5395 /// primitive per axis.
5396 ///
5397 /// The gate carries two identity elements:
5398 /// - **`has_code == false`** — any kind (including the three
5399 /// no-code kinds) that declares no code passes the paired
5400 /// `!has_code` short-circuit before every per-arm dispatch.
5401 /// - **Code-owning kinds** (`Biblioteca` owning
5402 /// `:bibliotecas`, `Binario` owning `:exe`, `Servico` owning
5403 /// `:servicos`) — the three no-code arm-firing predicates
5404 /// short-circuit on every code-owning kind, so the gate
5405 /// passes trivially regardless of what code they declare.
5406 /// Foreign-code-slot violations on a code-owning kind (e.g.
5407 /// `:kind Servico` declaring `:exe`) surface through the
5408 /// sibling [`crate::LayoutError::ForeignCodeSlot`] gate on
5409 /// [`Self::declared_foreign_code_slots`], not through this
5410 /// gate.
5411 ///
5412 /// Unlike the sibling cross-family
5413 /// [`Self::validate_kind_slot_coherence`], the three arms of
5414 /// this fold are mutually exclusive by construction — `:kind`
5415 /// is a single-valued [`CaixaKind`] discriminator so at most
5416 /// one arm can fire per caixa — and no cross-arm ordering pin
5417 /// is meaningful (the pre-fold three-block cascade at the
5418 /// wire-up site was already unreachable past the first
5419 /// matching arm).
5420 ///
5421 /// Peer to the per-kind compound entry gates every substrate
5422 /// primitive on the M2/M3 typed-slot family already carries
5423 /// ([`Self::validate_deps`] b5dd55e, [`Self::validate_limits`]
5424 /// baa4688, [`Self::validate_behavior`] 0d2877a,
5425 /// [`Self::validate_upgrade_from`] d6801df,
5426 /// [`Self::validate_aplicacao_shape`] 949a7a0,
5427 /// [`Self::validate_supervisor_shape`] 4c70105,
5428 /// [`Self::validate_acao_shape`] 5d6df54,
5429 /// [`Self::validate_kind_slot_coherence`] f0d286e): the
5430 /// author-time gate axis on the *per-slot* and *cross-family
5431 /// typed-slot* algebras each share one substrate primitive per
5432 /// compound gate, and this lift closes the third axis on the
5433 /// *code-surface* algebra so the layout pipeline routes all
5434 /// three coherence axes through one substrate primitive rather
5435 /// than nine open-coded blocks. Every future no-code kind
5436 /// (an `Actor` virtual-actor arm the M5 Orleans-inspired kind
5437 /// reaches through if it lands as a no-code composer, a future
5438 /// `Namespace` grouping kind) folds onto this compound gate
5439 /// as one arm addition rather than a fourth open-coded block
5440 /// at the wire-up site.
5441 ///
5442 /// # Errors
5443 ///
5444 /// Returns the [`crate::LayoutError`] variant naming the
5445 /// offending no-code kind:
5446 /// [`crate::LayoutError::SupervisorOwnsCode`] on a `:kind
5447 /// Supervisor` caixa with any declared code,
5448 /// [`crate::LayoutError::AplicacaoOwnsCode`] on a `:kind
5449 /// Aplicacao` caixa with any declared code,
5450 /// [`crate::LayoutError::AcaoOwnsCode`] on a `:kind Acao` caixa
5451 /// with any declared code. Passes trivially on every kind with
5452 /// no declared code and on every code-owning kind regardless
5453 /// of declared code (the fold's two identity-element arms).
5454 pub fn validate_no_code_kind_coherence(&self) -> Result<(), crate::LayoutError> {
5455 let has_code =
5456 !self.bibliotecas().is_empty() || !self.exe().is_empty() || !self.servicos().is_empty();
5457 if !has_code {
5458 return Ok(());
5459 }
5460 if self.kind().is_supervisor() {
5461 return Err(crate::LayoutError::supervisor_owns_code(self));
5462 }
5463 if self.kind().is_aplicacao() {
5464 return Err(crate::LayoutError::aplicacao_owns_code(self));
5465 }
5466 if self.kind().is_acao() {
5467 return Err(crate::LayoutError::acao_owns_code(self));
5468 }
5469 Ok(())
5470 }
5471
5472 /// Compound per-`Caixa` kind ↔ `:ci` coherence gate — the `Acao`
5473 /// axis-only companion to the sibling three-arm
5474 /// [`Self::validate_kind_slot_coherence`] fold (f0d286e) on the
5475 /// M3 mesh / supervisor-tree / M2 Servico-runtime typed-slot
5476 /// families. `:ci` carries a typed CI run
5477 /// ([`canteiro_types::CiRun`], CANTEIRO §7.1-C) that only the
5478 /// `caixa-actions` renderer decomposes + validates and only for a
5479 /// `:kind Acao`. On any *other* kind a declared `:ci` is the
5480 /// manifest field's documented "ignored otherwise" — it silently
5481 /// passes verify and then vanishes (never decomposed, never
5482 /// rendered), far from the source `caixa.lisp`.
5483 ///
5484 /// Pre-lift the arm lived as a self-similar
5485 /// `if caixa.ci().is_some() && !caixa.kind().is_acao() { return
5486 /// Err(LayoutError::CiOnNonAcao { caixa: caixa.nome().to_string(),
5487 /// kind: caixa.kind() }); }` block at
5488 /// [`crate::layout::StandardLayout::verify`] — one consumer today
5489 /// but every future consumer that wanted to gate the `:ci`
5490 /// coherence axis as a unit (the deferred
5491 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's admission
5492 /// webhook re-checking after a per-slot patch, a future
5493 /// `feira validate --ci-coherence` per-caixa admission verb, a
5494 /// per-`Caixa` overlay resolver rejecting a kind-foreign `:ci`
5495 /// patch) was structurally forced to either re-inline the
5496 /// two-condition guard in lockstep with the layout wire-up (the
5497 /// duplication the PRIME DIRECTIVE names as a bug) or call the
5498 /// whole [`crate::layout::StandardLayout::verify`] pipeline.
5499 /// Post-fold each such consumer reaches the arm through one call.
5500 ///
5501 /// Peer of the sibling three-arm
5502 /// [`Self::validate_kind_slot_coherence`] fold (f0d286e) — that
5503 /// gate carries the M3 mesh / supervisor-tree / M2 Servico-runtime
5504 /// axes under a uniform `{ caixa, kind, slots }` envelope
5505 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5506 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5507 /// [`crate::LayoutError::ServicoSlotsOnNonServico`]). The `:ci`
5508 /// axis stays on its own primitive because
5509 /// [`crate::LayoutError::CiOnNonAcao`] carries a distinct
5510 /// `{ caixa, kind }` wrap shape (no `slots` field — `:ci` is a
5511 /// single `Option` not a `Vec`-of-named-slots) whose reshape
5512 /// onto the sibling `{ caixa, kind, slots }` envelope would
5513 /// force a variant rename touching every consumer of
5514 /// `CiOnNonAcao`; the two folds share the same
5515 /// author-time-vs-renderer split and diagnostic altitude, and
5516 /// route through peer substrate primitives on the same
5517 /// [`Caixa`] surface.
5518 ///
5519 /// Peer to the per-kind compound entry gates every substrate
5520 /// primitive on the M2/M3 typed-slot family already carries
5521 /// ([`Self::validate_deps`] b5dd55e, [`Self::validate_limits`]
5522 /// baa4688, [`Self::validate_behavior`] 0d2877a,
5523 /// [`Self::validate_upgrade_from`] d6801df,
5524 /// [`Self::validate_aplicacao_shape`] 949a7a0,
5525 /// [`Self::validate_supervisor_shape`] 4c70105,
5526 /// [`Self::validate_acao_shape`] 5d6df54,
5527 /// [`Self::validate_kind_slot_coherence`] f0d286e,
5528 /// [`Self::validate_no_code_kind_coherence`] 3bbf6a2): every
5529 /// author-time coherence axis on the typed [`Caixa`] surface now
5530 /// routes through one substrate primitive per axis rather than
5531 /// an open-coded block at the layout wire-up site.
5532 ///
5533 /// The gate carries two identity elements:
5534 /// - **`ci().is_none()`** — a caixa that declares no `:ci`
5535 /// passes the first short-circuit before every per-arm
5536 /// dispatch, on every kind. The canonical shape of the four
5537 /// non-`Acao` kinds (`Biblioteca` / `Binario` / `Servico` /
5538 /// `Supervisor` / `Aplicacao`) is `ci = None` — the arm
5539 /// never fires on a well-shaped fixture.
5540 /// - **`:kind Acao`** — the owner-kind arm short-circuits on
5541 /// every `Acao` caixa regardless of its `:ci` shape; a
5542 /// malformed `:ci` on an `Acao` surfaces through the peer
5543 /// [`Self::validate_acao_shape`] compound decompose gate
5544 /// (5d6df54), not through this coherence gate.
5545 ///
5546 /// # Errors
5547 ///
5548 /// Returns [`crate::LayoutError::CiOnNonAcao`] naming the
5549 /// offending caixa's nome + kind on any non-`Acao` caixa with
5550 /// `:ci` declared. Passes trivially on every kind that declares
5551 /// no `:ci` and on every `:kind Acao` caixa regardless of
5552 /// declared `:ci` (the fold's two identity-element arms).
5553 pub fn validate_ci_kind_coherence(&self) -> Result<(), crate::LayoutError> {
5554 if self.ci().is_some() && !self.kind().is_acao() {
5555 return Err(crate::LayoutError::CiOnNonAcao {
5556 caixa: self.nome().to_string(),
5557 kind: self.kind(),
5558 });
5559 }
5560 Ok(())
5561 }
5562
5563 /// Compound per-`Caixa` kind ↔ code-surface coherence gate on the
5564 /// two exclusive code-surface slots — `:exe` (owned only by
5565 /// [`crate::CaixaKind::Binario`], the nix-built executable surface)
5566 /// and `:servicos` (owned only by [`crate::CaixaKind::Servico`],
5567 /// the wasm-component + `ComputeUnit` daemon surface). The
5568 /// `caixa-helm` / `caixa-flux` / `caixa-flake` renderers gate
5569 /// emission on [`crate::render::require_kind`]`(_, <owning-kind>)`
5570 /// and only emit the slot for its owning kind — so on any *other*
5571 /// code-running kind a declared `:exe` / `:servicos` is the
5572 /// manifest field's documented "ignored otherwise": the path is
5573 /// validated by the per-kind path-existence loops in
5574 /// [`crate::layout::StandardLayout::verify`], but the value is
5575 /// never rendered into a build target or programs.yaml entry —
5576 /// it silently passes `feira build` and then vanishes, far from
5577 /// the source `caixa.lisp`, with no field naming which slot is
5578 /// foreign.
5579 ///
5580 /// Pre-lift the arm lived as a self-similar four-line `let
5581 /// foreign_code_slots = caixa.declared_foreign_code_slots(); if
5582 /// !foreign_code_slots.is_empty() { return
5583 /// Err(LayoutError::foreign_code_slot(caixa, foreign_code_slots));
5584 /// }` block at [`crate::layout::StandardLayout::verify`] — one
5585 /// consumer today but every future consumer that wanted to gate
5586 /// the code-surface coherence axis as a unit (the deferred
5587 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's admission
5588 /// webhook re-checking after a per-slot patch, a future
5589 /// `feira validate --foreign-code` per-caixa admission verb, a
5590 /// per-`Caixa` overlay resolver rejecting a kind-foreign code-
5591 /// slot patch) was structurally forced to either re-inline the
5592 /// two-condition guard in lockstep with the layout wire-up (the
5593 /// duplication the PRIME DIRECTIVE names as a bug) or call the
5594 /// whole [`crate::layout::StandardLayout::verify`] pipeline.
5595 /// Post-fold each such consumer reaches the arm through one call.
5596 ///
5597 /// Peer of the sibling three-arm
5598 /// [`Self::validate_kind_slot_coherence`] fold (f0d286e) — that
5599 /// gate carries the M3 mesh / supervisor-tree / M2 Servico-runtime
5600 /// axes under the uniform `{ caixa, kind, slots }` envelope
5601 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5602 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5603 /// [`crate::LayoutError::ServicoSlotsOnNonServico`]); this gate
5604 /// carries the code-surface axis under the same
5605 /// `{ caixa, kind, slots }` envelope
5606 /// ([`crate::LayoutError::ForeignCodeSlot`]). The two folds share
5607 /// the envelope shape but stay separate primitives because the
5608 /// per-arm predicate differs: the cross-family fold rides on the
5609 /// outer `!self.kind().is_<owner>()` guard *paired* with a
5610 /// per-family `declared_<family>_slots` accumulator, while this
5611 /// fold's per-arm kind-check is baked into
5612 /// [`Self::declared_foreign_code_slots`] itself (each arm's
5613 /// `!self.kind().requires_<slot>()` guard fires inside the
5614 /// accumulator, not around it) — so a `:kind Binario` declaring
5615 /// `:servicos` and a `:kind Servico` declaring `:exe` are both
5616 /// caught by one accumulator sweep rather than by two independent
5617 /// arm dispatches. Peer with [`Self::validate_ci_kind_coherence`]
5618 /// (9b55beb) which carries the `:ci` axis on its own primitive
5619 /// for the same "distinct per-arm predicate shape, shared
5620 /// diagnostic altitude" reason.
5621 ///
5622 /// Peer to the per-kind and per-slot compound entry gates every
5623 /// substrate primitive on the M2/M3 typed-slot family already
5624 /// carries ([`Self::validate_deps`] b5dd55e,
5625 /// [`Self::validate_limits`] baa4688,
5626 /// [`Self::validate_behavior`] 0d2877a,
5627 /// [`Self::validate_upgrade_from`] d6801df,
5628 /// [`Self::validate_aplicacao_shape`] 949a7a0,
5629 /// [`Self::validate_supervisor_shape`] 4c70105,
5630 /// [`Self::validate_acao_shape`] 5d6df54,
5631 /// [`Self::validate_kind_slot_coherence`] f0d286e,
5632 /// [`Self::validate_no_code_kind_coherence`] 3bbf6a2,
5633 /// [`Self::validate_ci_kind_coherence`] 9b55beb): every
5634 /// author-time coherence axis on the typed [`Caixa`] surface now
5635 /// routes through one substrate primitive per axis rather than an
5636 /// open-coded block at the layout wire-up site. This closes the
5637 /// last open-coded kind ↔ slot coherence gate at the layout
5638 /// altitude — every kind-coherence diagnostic is now a substrate
5639 /// primitive.
5640 ///
5641 /// The gate carries three identity elements:
5642 /// - **Code-owning kinds on their native slot** — a
5643 /// [`crate::CaixaKind::Binario`] declaring `:exe`, a
5644 /// [`crate::CaixaKind::Servico`] declaring `:servicos` — each
5645 /// arm's `!requires_<slot>()` predicate short-circuits inside
5646 /// [`Self::declared_foreign_code_slots`], so the accumulator
5647 /// returns an empty `Vec` and the outer `is_empty` short-
5648 /// circuits before the wrap fires.
5649 /// - **Bare caixas** — a caixa with no declared code on any kind
5650 /// passes the same accumulator's `is_empty` short-circuit on
5651 /// every arm.
5652 /// - **No-code kinds** ([`crate::CaixaKind::Supervisor`] /
5653 /// [`crate::CaixaKind::Aplicacao`] / [`crate::CaixaKind::Acao`])
5654 /// declaring code — dominated upstream by the sibling
5655 /// [`Self::validate_no_code_kind_coherence`] (3bbf6a2) which
5656 /// surfaces [`crate::LayoutError::SupervisorOwnsCode`] /
5657 /// [`crate::LayoutError::AplicacaoOwnsCode`] /
5658 /// [`crate::LayoutError::AcaoOwnsCode`] first at the layout
5659 /// wire-up site, so this gate never fires on a no-code kind
5660 /// through the layout pipeline. A standalone caller reaching
5661 /// this primitive without the sibling `_no_code_` gate first
5662 /// would see a no-code kind's declared `:exe` / `:servicos`
5663 /// surface `ForeignCodeSlot` here (the two folds partition the
5664 /// diagnostic responsibility along the "declared no-code slot"
5665 /// axis: no-code kinds get `OwnsCode`, code-running kinds get
5666 /// `ForeignCodeSlot`), and the layout wire-up's canonical
5667 /// `_no_code_` → `_foreign_code_` ordering keeps the
5668 /// [`crate::LayoutError::SupervisorOwnsCode`] / … arm the one
5669 /// that surfaces in the composed pipeline.
5670 ///
5671 /// Diagnostic order within the arm matches the pre-fold layout
5672 /// wire-up canonical sequence — `:exe` → `:servicos` — pinned by
5673 /// [`Self::declared_foreign_code_slots`]'s per-arm push order.
5674 ///
5675 /// # Errors
5676 ///
5677 /// Returns [`crate::LayoutError::ForeignCodeSlot`] naming the
5678 /// offending caixa's nome + kind + declared foreign-code slot
5679 /// list on any code-running kind ([`crate::CaixaKind::Biblioteca`]
5680 /// / [`crate::CaixaKind::Binario`] / [`crate::CaixaKind::Servico`])
5681 /// declaring another code-running kind's exclusive code surface.
5682 /// Passes trivially on every native-slot declaration (Binario
5683 /// with `:exe`, Servico with `:servicos`), on every bare caixa,
5684 /// and on every no-code kind (dominated upstream by the sibling
5685 /// [`Self::validate_no_code_kind_coherence`] `OwnsCode` gates —
5686 /// see the identity-element notes above).
5687 pub fn validate_foreign_code_kind_coherence(&self) -> Result<(), crate::LayoutError> {
5688 let foreign_code_slots = self.declared_foreign_code_slots();
5689 if !foreign_code_slots.is_empty() {
5690 return Err(crate::LayoutError::foreign_code_slot(
5691 self,
5692 foreign_code_slots,
5693 ));
5694 }
5695 Ok(())
5696 }
5697
5698 /// Compound per-`Caixa` required-slot gate on the three
5699 /// [`crate::CaixaKind`] arms whose sole payload is a canonical
5700 /// typed slot: `Binario`'s `:exe`, `Servico`'s `:servicos`,
5701 /// `Acao`'s `:ci`. Each arm refuses a caixa on its owner kind
5702 /// that declares no value in the corresponding required slot,
5703 /// so `feira build` (the canonical author-time gate) surfaces the
5704 /// self-locating "this kind needs this slot" diagnostic at the
5705 /// source `caixa.lisp` rather than deferring the failure to a
5706 /// downstream consumer (a nix build with no `:exe` to build, a
5707 /// programs.yaml fan-out with no `:servicos` to enumerate, a
5708 /// `caixa-actions` decompose with no `:ci` to walk).
5709 ///
5710 /// Pre-lift each of the three arms lived as a self-similar
5711 /// `if caixa.kind().requires_<slot>() && caixa.<slot>().is_<empty>() {
5712 /// return Err(LayoutError::<kind>_without_<slot>(caixa)); }`
5713 /// block at [`crate::layout::StandardLayout::verify`] — three
5714 /// consumers, three identical shapes, one substrate primitive on
5715 /// [`Caixa`] closing the duplication the PRIME DIRECTIVE names as
5716 /// a bug. Each of the three inner ctors
5717 /// ([`crate::LayoutError::binario_without_exe`] /
5718 /// [`crate::LayoutError::servico_without_servicos`] /
5719 /// [`crate::LayoutError::missing_ci`]) was already lifted onto
5720 /// the substrate by the peer [`crate::layout::layout_nome_only_ctors!`]
5721 /// macro, so the primitive routes through the same
5722 /// `Self::<variant>(caixa.nome().to_string())` tuple-literal
5723 /// wrap per arm as the pre-lift open-coded blocks.
5724 ///
5725 /// The paired `Biblioteca`-arm required-slot check
5726 /// ([`crate::LayoutError::MissingLib`]) stays open-coded at the
5727 /// layout wire-up site by design: it needs the filesystem oracle
5728 /// on [`crate::layout::LayoutInvariants`] to check the default
5729 /// `lib/<nome>.lisp` fallback path, which the pure per-`Caixa`
5730 /// typed-shape surface this fold rides on has no reference to.
5731 /// Same posture the peer [`Self::validate_no_code_kind_coherence`]
5732 /// fold takes on the on-disk existence loops.
5733 ///
5734 /// Diagnostic order at the primitive matches the pre-fold layout
5735 /// wire-up canonical sequence — `:exe` → `:servicos` → `:ci` —
5736 /// the same three-arm sweep the peer [`crate::CaixaKind`]
5737 /// discriminator carries at its `requires_*` accessors. Unlike
5738 /// the sibling cross-family [`Self::validate_kind_slot_coherence`]
5739 /// fold, the three arms of this fold are mutually exclusive by
5740 /// construction — `:kind` is a single-valued [`crate::CaixaKind`]
5741 /// discriminator so at most one arm can fire per caixa — and no
5742 /// cross-arm ordering pin is meaningful (the pre-fold three-block
5743 /// cascade at the wire-up site was already unreachable past the
5744 /// first matching arm).
5745 ///
5746 /// Peer to the per-kind and per-slot compound entry gates every
5747 /// substrate primitive on the M2/M3 typed-slot family already
5748 /// carries ([`Self::validate_deps`] b5dd55e,
5749 /// [`Self::validate_limits`] baa4688,
5750 /// [`Self::validate_behavior`] 0d2877a,
5751 /// [`Self::validate_upgrade_from`] d6801df,
5752 /// [`Self::validate_aplicacao_shape`] 949a7a0,
5753 /// [`Self::validate_supervisor_shape`] 4c70105,
5754 /// [`Self::validate_acao_shape`] 5d6df54,
5755 /// [`Self::validate_kind_slot_coherence`] f0d286e,
5756 /// [`Self::validate_no_code_kind_coherence`] 3bbf6a2,
5757 /// [`Self::validate_ci_kind_coherence`] 9b55beb): every
5758 /// author-time coherence axis on the typed [`Caixa`] surface
5759 /// now routes through one substrate primitive per axis rather
5760 /// than an open-coded block at the layout wire-up site.
5761 ///
5762 /// The gate carries two identity elements:
5763 /// - **Non-owner kinds** — each per-arm predicate is
5764 /// `self.kind().requires_<slot>()`, which returns `true` only
5765 /// for the owning kind ([`crate::CaixaKind::Binario`] on `:exe`,
5766 /// [`crate::CaixaKind::Servico`] on `:servicos`,
5767 /// [`crate::CaixaKind::Acao`] on `:ci`). Every non-owner kind
5768 /// passes each per-arm dispatch trivially.
5769 /// - **Owner kinds with the required slot present** — a
5770 /// [`crate::CaixaKind::Binario`] with a non-empty `:exe`, a
5771 /// [`crate::CaixaKind::Servico`] with a non-empty `:servicos`,
5772 /// an [`crate::CaixaKind::Acao`] with `ci = Some(_)` — passes
5773 /// its arm's `is_empty` / `is_none` short-circuit.
5774 ///
5775 /// # Errors
5776 ///
5777 /// Returns the [`crate::LayoutError`] variant naming the
5778 /// offending owner kind:
5779 /// [`crate::LayoutError::BinarioWithoutExe`] on a
5780 /// [`crate::CaixaKind::Binario`] caixa with no declared `:exe`,
5781 /// [`crate::LayoutError::ServicoWithoutServicos`] on a
5782 /// [`crate::CaixaKind::Servico`] caixa with no declared
5783 /// `:servicos`, [`crate::LayoutError::MissingCi`] on a
5784 /// [`crate::CaixaKind::Acao`] caixa with no declared `:ci`.
5785 /// Passes trivially on every non-owner kind and on every owner
5786 /// kind with its required slot present.
5787 pub fn validate_required_kind_slot(&self) -> Result<(), crate::LayoutError> {
5788 if self.kind().requires_exe() && self.exe().is_empty() {
5789 return Err(crate::LayoutError::binario_without_exe(self));
5790 }
5791 if self.kind().requires_servicos() && self.servicos().is_empty() {
5792 return Err(crate::LayoutError::servico_without_servicos(self));
5793 }
5794 if self.kind().requires_ci() && self.ci().is_none() {
5795 return Err(crate::LayoutError::missing_ci(self));
5796 }
5797 Ok(())
5798 }
5799
5800 /// Reject per-entry values on the three Caixa-level code-surface
5801 /// path lists (`:bibliotecas`, `:exe`, `:servicos`) that the
5802 /// layout checker's `root.join(p)` sandbox would silently subvert.
5803 /// Same three structural footguns the peer
5804 /// [`BehaviorSpec::validate`] (b0c8389) and
5805 /// [`crate::UpgradeInstruction::validate`] `StateChange` arm
5806 /// (26da2c7) already close on the M2 `:behavior :on-*` and
5807 /// `:upgrade-from :state-change :script` axes, here lifted onto
5808 /// the three top-level code-path axes through the shared
5809 /// [`is_sandboxed_relative_path`] predicate:
5810 ///
5811 /// - empty entry (`(:bibliotecas (""))` / `(:exe (""))` /
5812 /// `(:servicos (""))`): `PathBuf::new()` round-trips through
5813 /// [`Path::join`] as the base itself — `root.join("")` ==
5814 /// `root`, so the existence check (`self.exists(&root)`)
5815 /// trivially passes (the project root exists), and the layout
5816 /// silently treats the project root as a biblioteca / exe /
5817 /// servico entry. The `:bibliotecas` loop then hands the root
5818 /// to `tatara_lisp::read` at `feira build` time as if the root
5819 /// directory itself were a Lisp source file — a parse error
5820 /// far from the source `caixa.lisp` with no field naming the
5821 /// offending entry.
5822 /// - absolute path (`(:bibliotecas ("/etc/passwd"))`):
5823 /// [`Path::join`] *replaces* the base when the right-hand side
5824 /// is absolute, so `root.join("/etc/passwd")` resolves to
5825 /// `"/etc/passwd"` and escapes the project sandbox entirely.
5826 /// The existence check then silently consults whatever the
5827 /// escaped path resolves to — for `:bibliotecas`, the layout
5828 /// has no `starts_with`-fence (only `:exe` is fenced under
5829 /// `exe/` and `:servicos` under `servicos/`), so an absolute
5830 /// `:bibliotecas` entry that happens to resolve on disk
5831 /// silently passes. For `:exe` / `:servicos` the fence catches
5832 /// the absolute case downstream as `ExeOutsideDir` /
5833 /// `ServicoOutsideDir` (or `MissingEntry` if the absolute path
5834 /// doesn't exist), but with a downstream-shaped diagnostic
5835 /// that names the resolved escape path rather than the
5836 /// authoring footgun at the source.
5837 /// - parent-escape (`(:bibliotecas ("../sibling/x.lisp"))` /
5838 /// `(:exe ("exe/../../escape.lisp"))`): a [`PathBuf`] with any
5839 /// [`std::path::Component::ParentDir`] anywhere round-trips
5840 /// through [`Path::join`] as a traversal above the caixa root.
5841 /// The `:exe` / `:servicos` `starts_with(<dir>)` fence is
5842 /// *component-aware* (not canonical-path-aware), so
5843 /// `root.join("exe/../../escape.lisp")` `starts_with(exe_dir)`
5844 /// is **true** even though the canonical resolution
5845 /// `{parent of root}/escape.lisp` lives outside the caixa root
5846 /// — the fence silently lets the parent-escape through, and
5847 /// the existence check passes if that escape-target happens
5848 /// to exist. Caught regardless of where the `..` sits
5849 /// (leading, mid-path, trailing) so the gate matches the peer
5850 /// predicate's full coverage.
5851 ///
5852 /// Same `Empty` → `Absolute` → `ParentEscape` arm-ordering every peer
5853 /// `is_sandboxed_relative_path` consumer follows (b0c8389 / 26da2c7);
5854 /// same per-slot diagnostic shape every peer per-axis path-gate
5855 /// exposes (`*Empty { slot }` / `*Absolute { slot, path }` /
5856 /// `*ParentEscape { slot, path }`). Cross-slot precedence is
5857 /// `:bibliotecas` → `:exe` → `:servicos` — the same declaration
5858 /// order [`Caixa::declared_foreign_code_slots`] uses for its
5859 /// canonical foreign-code-slot diagnostic, so a manifest with
5860 /// multiple malformed slots surfaces the lexicographically-earliest
5861 /// slot's diagnostic deterministically.
5862 ///
5863 /// Lifted to the typed surface as a Caixa-level validator (peer
5864 /// of [`Self::validate_nome`] / [`Self::validate_versao`] /
5865 /// [`Self::validate_deps`] / [`Self::validate_restart_window`])
5866 /// and wired into [`crate::StandardLayout::verify`] before the
5867 /// existence-check loops so the diagnostic names the offending
5868 /// slot at the source caixa.lisp rather than reporting a
5869 /// downstream `MissingEntry` / `ExeOutsideDir` /
5870 /// `ServicoOutsideDir` against the resolved sandbox-escape path.
5871 /// The fourth typed code-path surface — every author-supplied
5872 /// path on the manifest — is now structurally accept-shaped
5873 /// past validate, peer with `:behavior :on-*` and
5874 /// `:upgrade-from :state-change :script`.
5875 pub fn validate_code_paths(&self) -> Result<(), ManifestError> {
5876 /// Per-slot file-type contract for the three Caixa-level
5877 /// code-path surfaces (`:bibliotecas`, `:exe`, `:servicos`).
5878 /// Each variant names the predicate the per-entry file-type
5879 /// gate consults; [`Self::None`] opts the slot out of any
5880 /// file-type contract. Lifted as a typed local enum so the
5881 /// per-slot dispatch is exhaustive at the `match` — adding a
5882 /// future axis to the typed-substrate `:` slot set (the
5883 /// future `:assets` resource axis the M5 roadmap names, the
5884 /// future `:nix-flake` derivation axis the caixa-flake
5885 /// emitter consults) lands as one variant + one `match` arm,
5886 /// not a coordinated rewrite of every per-slot bool flag.
5887 ///
5888 /// Peer of the typed-substrate per-slot variant disciplines
5889 /// already established on this surface
5890 /// ([`crate::supervisor::RestartStrategy`] +
5891 /// [`crate::supervisor::RestartPolicy`] on the OTP-shape
5892 /// supervision-tree axis,
5893 /// [`crate::aplicacao::PlacementStrategy`] on the §III.1
5894 /// placement axis, [`crate::aplicacao::WitTarget`] on the
5895 /// `:contratos` payload-target axis): the typed `enum` is
5896 /// the substrate's single source of truth for the per-axis
5897 /// dispatch, and every consumer (the per-arm body here, the
5898 /// future feira-lint per-slot diagnostic renderer, the M4
5899 /// per-axis admission webhook) reaches for the same typed
5900 /// surface rather than re-deriving the partition from inline
5901 /// flag combinations.
5902 enum CodePathFileType {
5903 /// `:exe` — nix-build derivation output, no terminating-
5904 /// extension contract (the canonical `"exe/<name>"`
5905 /// fixtures the layout's `ExeOutsideDir` error message
5906 /// documents carry no extension by convention).
5907 None,
5908 /// `:bibliotecas` — tatara-lisp source files the
5909 /// `feira build` loop reads through `tatara_lisp::read`
5910 /// at parse time. Routes to [`is_lisp_extension`].
5911 LispSource,
5912 /// `:servicos` — ComputeUnit-CR YAML files the
5913 /// caixa-helm / caixa-flux renderers consume through
5914 /// `serde_yaml::from_str`. Routes to
5915 /// [`is_computeunit_yaml_extension`].
5916 ComputeUnitYaml,
5917 }
5918
5919 // The per-slot [`CodePathFileType`] selects which axes carry the
5920 // lifted file-type predicate. `:bibliotecas` is the tatara-lisp
5921 // source axis (the `feira build` loop at
5922 // `caixa-feira/src/cmd/build.rs:33` reads each entry through
5923 // `tatara_lisp::read` at parse time) — the lifted
5924 // [`is_lisp_extension`] predicate gates the `.lisp` extension.
5925 // `:exe` is the nix-built executable surface (per the canonical
5926 // `"exe/<name>"`-shaped fixtures the layout's `ExeOutsideDir`
5927 // error message documents and every in-tree
5928 // `caixa_with_code_paths` positive control uses) — its file-type
5929 // contract is "nix-build derivation output", not a typed source
5930 // file, so [`CodePathFileType::None`] opts the slot out of any
5931 // file-type gate. `:servicos` is the `.computeunit.yaml`
5932 // ComputeUnit-CR axis (the peer caixa-helm / caixa-flux
5933 // renderers consume each entry through `serde_yaml::from_str` as
5934 // a typed `ComputeUnit` CR) — the lifted
5935 // [`is_computeunit_yaml_extension`] predicate gates the compound
5936 // `.computeunit.yaml` suffix. All three axes are surfaced through
5937 // the same iteration so the sandbox-shape + duplicate gates
5938 // apply uniformly; the typed file-type dispatch fires per-slot
5939 // exactly where the downstream consumer's accepted set demands
5940 // it. The third file-type variant ([`ComputeUnitYaml`]) is the
5941 // compounding lift on the peer 64772a9 `:bibliotecas`
5942 // `.lisp`-gate trajectory — the second of the three code-path
5943 // axes to land on a typed compound-suffix gate, with the same
5944 // self-locating per-slot diagnostic shape every peer per-axis
5945 // file-type lift uses (`*NonLispExtension { slot, path }` /
5946 // `*NonComputeUnitYamlExtension { slot, path }`).
5947 for (slot, list, file_type) in [
5948 (
5949 ":bibliotecas",
5950 &self.bibliotecas,
5951 CodePathFileType::LispSource,
5952 ),
5953 (":exe", &self.exe, CodePathFileType::None),
5954 (
5955 ":servicos",
5956 &self.servicos,
5957 CodePathFileType::ComputeUnitYaml,
5958 ),
5959 ] {
5960 // Per-slot set-not-multiset gate on the typed code-path axis.
5961 // Every peer Vec-shaped author-supplied list past validate is
5962 // a set, not a multiset: `:membros :caixa`
5963 // ([`crate::AplicacaoError::MembroDuplicate`]), `:placement
5964 // :clusters` ([`crate::AplicacaoError::PlacementClusterDuplicate`]),
5965 // `:entrada :paths` ([`crate::AplicacaoError::EntradaPathDuplicate`]),
5966 // `:contratos` ([`crate::AplicacaoError::ContratoDuplicate`]),
5967 // `:children :caixa` ([`crate::SupervisorError::DuplicateChild`]),
5968 // `:deps` / `:deps-dev` `:nome` ([`crate::DepError::DuplicateNome`]
5969 // per 359fba5), `:upgrade-from :from` ([`crate::UpgradeError::DuplicateFrom`]),
5970 // `:etiquetas` ([`ManifestError::EtiquetaDuplicate`] per 360a499),
5971 // `:autores` ([`ManifestError::AutorDuplicate`] per 86c769b) —
5972 // the three code-path lists are the last Vec-shaped author-
5973 // supplied slots on the typed Caixa surface still admitting a
5974 // duplicate entry silently. Scope is per-list (`:bibliotecas`
5975 // duplicates are flagged within `:bibliotecas`, not across
5976 // `:bibliotecas` ↔ `:exe`) — the same per-list scope `:deps`
5977 // ↔ `:deps-dev` use (a `:nome` present in both lists is a
5978 // legitimate dev-vs-runtime shape on the dep axis, fenced
5979 // separately by [`crate::dep::validate_no_self_dep`]). On the
5980 // code-path axis a cross-slot collision is structurally
5981 // impossible by the layout's `starts_with(<exe|servicos>_dir)`
5982 // fence — `:exe` and `:servicos` entries are confined to their
5983 // own directory trees, so the only way a string could appear
5984 // on two code-path lists is the (rare, structurally invalid)
5985 // case where `:bibliotecas` carries an `"exe/<x>"` or
5986 // `"servicos/<x>.yaml"`-shaped path.
5987 //
5988 // Without the gate three authoring footguns silently passed:
5989 //
5990 // - `:bibliotecas ("lib/foo.lisp" "lib/foo.lisp")` — the
5991 // canonical copy-paste-the-wrong-file footgun. `feira
5992 // build` (`caixa-feira/src/cmd/build.rs:33`) walks the
5993 // list and re-parses the same file twice, wasting work
5994 // and silently masking the author's intent to declare a
5995 // *second* biblioteca.
5996 // - `:exe ("exe/cli" "exe/cli")` — the same footgun on the
5997 // Binario surface. The future `caixa-flake` `nix flake`
5998 // emitter that materializes each `:exe` entry as a flake
5999 // `packages.<exe-name>` derivation would collide on the
6000 // duplicate package name and surface a flake-eval error
6001 // far from the source `caixa.lisp`.
6002 // - `:servicos ("servicos/x.computeunit.yaml"
6003 // "servicos/x.computeunit.yaml")` — the same footgun on
6004 // the Servico surface. The peer `caixa-helm` / `caixa-flux`
6005 // renderers already refuse `:servicos.len() != 1` with
6006 // the narrower [`UnsupportedServicoCount`] diagnostic, but
6007 // that diagnostic surfaces "too many servicos" without
6008 // naming "duplicate entry" — the typed self-locating
6009 // "which entry is the duplicate" framing only lands at
6010 // this gate.
6011 //
6012 // Same `seen.insert(entry.as_str())` shape every peer per-list
6013 // duplicate gate uses (`:etiquetas` 360a499, `:autores`
6014 // 86c769b, `:deps` 359fba5) and the same "structural shape
6015 // checks fire before the duplicate check on the same entry"
6016 // ordering (a `(:bibliotecas ("" "lib/x.lisp" "lib/x.lisp"))`
6017 // shape surfaces the narrower [`Self::CodePathEmpty`] for the
6018 // empty entry first, not the duplicate on the later pair).
6019 let mut seen = std::collections::HashSet::new();
6020 for entry in list {
6021 let path = Path::new(entry);
6022 match is_sandboxed_relative_path(path) {
6023 Ok(()) => {}
6024 Err(PathShapeViolation::Empty) => {
6025 return Err(ManifestError::CodePathEmpty { slot });
6026 }
6027 Err(PathShapeViolation::Absolute) => {
6028 return Err(ManifestError::code_path_absolute(slot, path));
6029 }
6030 Err(PathShapeViolation::ParentEscape) => {
6031 return Err(ManifestError::code_path_parent_escape(slot, path));
6032 }
6033 }
6034 // The per-slot file-type gate dispatched through the
6035 // typed [`CodePathFileType`] selector above. Each variant
6036 // routes to the lifted predicate the downstream consumer
6037 // demands:
6038 //
6039 // - [`LispSource`] → [`is_lisp_extension`] for
6040 // `:bibliotecas` (the `feira build` loop's
6041 // `tatara_lisp::read` consumer);
6042 // - [`ComputeUnitYaml`] → [`is_computeunit_yaml_extension`]
6043 // for `:servicos` (the caixa-helm / caixa-flux
6044 // `serde_yaml::from_str` consumer's `ComputeUnit` CR
6045 // accepted set);
6046 // - [`None`] for `:exe` — the nix-build derivation-
6047 // output axis has no terminating-extension contract.
6048 //
6049 // Fires after the sandbox-shape arms so a path that is
6050 // *both* sandbox-escaping and wrong-extension surfaces
6051 // the more fundamental sandbox-shape diagnostic first
6052 // (mirrors the peer `EmptyPath` → `AbsolutePath` →
6053 // `ParentEscape` → `NonLispExtension` arm-ordering on
6054 // `:behavior :on-*` c97815a, and `EmptyScript` →
6055 // `AbsoluteScript` → `ParentEscapeScript` →
6056 // `NonLispExtensionScript` on
6057 // `:upgrade-from :state-change :script` 33cc830), and
6058 // before the duplicate gate so the narrower per-entry
6059 // file-type shape dominates the cross-entry uniqueness
6060 // diagnostic (a
6061 // `("servicos/x.yaml" "servicos/x.yaml")` shape on
6062 // `:servicos` surfaces
6063 // `CodePathNonComputeUnitYamlExtension` on the first
6064 // entry rather than `CodePathDuplicate` on the pair —
6065 // peer with the 64772a9 `:bibliotecas`
6066 // `("lib/x.txt" "lib/x.txt")` ordering).
6067 match file_type {
6068 CodePathFileType::None => {}
6069 CodePathFileType::LispSource => {
6070 if !is_lisp_extension(path) {
6071 return Err(ManifestError::code_path_non_lisp_extension(slot, path));
6072 }
6073 }
6074 CodePathFileType::ComputeUnitYaml => {
6075 if !is_computeunit_yaml_extension(path) {
6076 return Err(ManifestError::code_path_non_computeunit_yaml_extension(
6077 slot, path,
6078 ));
6079 }
6080 }
6081 }
6082 crate::render::insert_first_seen(&mut seen, entry.as_str(), || {
6083 ManifestError::code_path_duplicate(slot, path)
6084 })?;
6085 }
6086 }
6087 Ok(())
6088 }
6089
6090 /// Reject `:etiquetas` lists with an empty entry or with two entries
6091 /// agreeing on the same string. `:etiquetas` is the universal
6092 /// registry-search-tag axis on [`Caixa`] (every kind carries the
6093 /// `Vec<String>` slot) and lands verbatim as the Helm chart
6094 /// `Chart.yaml` `keywords:` array on every Servico (caixa-helm's
6095 /// `build_chart_yaml` at `caixa-helm/src/lib.rs:236` folds it through
6096 /// a [`std::collections::BTreeSet`] alongside the four substrate-
6097 /// fixed tags `lareira` / `wasm` / `tatara-lisp` / `caixa-servico`).
6098 /// Two authoring footguns silently passed validate without this gate:
6099 ///
6100 /// - Empty entry (`(:etiquetas (""))` — the canonical paste-from-
6101 /// blank-doc footgun) rendered as `keywords: ["", "caixa-servico",
6102 /// "lareira", "tatara-lisp", "wasm"]` in `Chart.yaml`. Helm's
6103 /// `chart.metadata.keywords` admits the value without a strict
6104 /// parser-side gate, but the empty keyword has no operational
6105 /// meaning — it indexes nothing in the future caixa-registry
6106 /// search axis and clutters the rendered chart with a no-op tag.
6107 /// - Duplicate entries (`(:etiquetas ("demo" "demo"))` — the
6108 /// copy-paste-the-wrong-tag footgun) silently passed validate
6109 /// and were silently dedup'd by caixa-helm's `BTreeSet` collect
6110 /// at chart render — a "second wins / one silently disappears"
6111 /// shape divergent from every peer typed-graph set gate
6112 /// ([`crate::AplicacaoError::MembroDuplicate`] on `:membros`,
6113 /// [`crate::AplicacaoError::PlacementClusterDuplicate`] on
6114 /// `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
6115 /// on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
6116 /// on `:contratos`, [`crate::DepError::DuplicateNome`] on
6117 /// `:deps` / `:deps-dev` per 359fba5, [`crate::UpgradeError::DuplicateFrom`]
6118 /// on `:upgrade-from`, the per-instruction-class singularity
6119 /// gates [`crate::UpgradeError::DuplicateLoadModule`] /
6120 /// [`crate::UpgradeError::DuplicateStateChange`] /
6121 /// [`crate::UpgradeError::DuplicateCleanup`]). The typed-graph
6122 /// discipline is uniform: every Vec-shaped author-supplied list
6123 /// past validate is set-not-multiset, by construction.
6124 ///
6125 /// Past the empty arm the gate enforces the chart-keyword shape
6126 /// predicate via [`crate::render::is_chart_keyword_shape`]: Cargo's
6127 /// crates.io `[package] keywords` grammar — 1..=20 bytes, starts
6128 /// with an ASCII letter, ASCII alphanumeric / `_` / `-`
6129 /// continuation. Closes the canonical paste-from-doc footguns the
6130 /// bare empty + duplicate arms left open: paste-from-aligned-doc
6131 /// whitespace (`" mesh"`, `"mesh "`), paste-from-multiline-doc
6132 /// newline (`"mesh\nhttp"` — the author pasted a multi-tag block
6133 /// into one entry instead of splitting), paste-from-Windows-CRLF-doc
6134 /// carriage return, CSV-list-separator confusion (`"mesh,http,grpc"`
6135 /// — the author meant three separate list entries), path-separator
6136 /// confusion (`"caixa/servico"`), namespace-suffix (`"http.1"`),
6137 /// leading-digit (`"1foo"`), kebab-leak (`"-foo"`), snake-leak
6138 /// (`"_foo"`), non-ASCII (`"café"`), and paste-from-binary-blob
6139 /// control bytes that would silently land as malformed search tags
6140 /// in the rendered Chart.yaml `keywords:` array and break the
6141 /// Artifact Hub keyword index lookup far from the source caixa.lisp.
6142 /// Mirrors the [`Self::validate_autores`] shape-predicate cascade
6143 /// established on the sibling universal-axis `Vec<String>` surface
6144 /// — the second universal-axis Vec<String> surface to land the
6145 /// empty-first-then-shape-then-duplicate per-entry cascade.
6146 ///
6147 /// Same empty-first cascade discipline every peer per-axis gate
6148 /// uses: the per-entry empty arm fires before the per-entry shape
6149 /// arm fires before the cross-entry duplicate arm, so an
6150 /// `("" "mesh" "mesh")` authoring shape surfaces the narrower
6151 /// [`ManifestError::EtiquetaEmpty`] (the structural "this entry
6152 /// has no value" defect) before either the shape or the duplicate
6153 /// diagnostic. Walks the list in declaration order so the
6154 /// first-collision diagnostic surfaces the lexicographically-
6155 /// earliest offending position, peer with every other duplicate
6156 /// gate on this surface.
6157 ///
6158 /// Universal-axis (every kind carries `:etiquetas`), so wired at the
6159 /// caixa-build gate alongside the peer universal gates
6160 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6161 /// [`Self::validate_deps`] / [`Self::validate_code_paths`] — before
6162 /// the kind-coherence gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]
6163 /// / [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6164 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6165 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
6166 /// slot sets. The future caixa-registry search axis can reach for
6167 /// `caixa.etiquetas` knowing every entry is a non-empty distinct
6168 /// chart-keyword-shaped string without re-deriving the precondition.
6169 pub fn validate_etiquetas(&self) -> Result<(), ManifestError> {
6170 let mut seen = std::collections::HashSet::new();
6171 for etiqueta in self.etiquetas() {
6172 if etiqueta.is_empty() {
6173 return Err(ManifestError::EtiquetaEmpty);
6174 }
6175 crate::render::is_chart_keyword_shape(etiqueta).map_err(|reason| {
6176 ManifestError::EtiquetaInvalid {
6177 etiqueta: etiqueta.clone(),
6178 reason,
6179 }
6180 })?;
6181 crate::render::insert_first_seen(&mut seen, etiqueta.as_str(), || {
6182 ManifestError::EtiquetaDuplicate {
6183 etiqueta: etiqueta.clone(),
6184 }
6185 })?;
6186 }
6187 Ok(())
6188 }
6189
6190 /// Reject `:autores` lists with an empty entry or with two entries
6191 /// agreeing on the same string. `:autores` is the universal
6192 /// maintainer-axis on [`Caixa`] (every kind carries the
6193 /// `Vec<String>` slot) and lands verbatim as the Helm chart
6194 /// `Chart.yaml` `maintainers:` array on every Servico (caixa-helm's
6195 /// `build_chart_yaml` at `caixa-helm/src/lib.rs:251` maps each entry
6196 /// to a `Maintainer { name, email: None }` without dedup). Two
6197 /// authoring footguns silently passed validate without this gate:
6198 ///
6199 /// - Empty entry (`(:autores (""))` — the canonical paste-from-
6200 /// blank-doc footgun) rendered as
6201 /// `maintainers: [{name: "", email: null}]` in `Chart.yaml`. The
6202 /// empty maintainer name has no operational meaning — it
6203 /// identifies no one in the substrate's authorship index and
6204 /// clutters the rendered chart with a no-op maintainer.
6205 /// - Duplicate entries (`(:autores ("pleme-io" "pleme-io"))` —
6206 /// the copy-paste-the-wrong-author footgun) silently passed
6207 /// validate and rendered as two identical maintainer entries.
6208 /// Unlike the [`Self::validate_etiquetas`] peer (caixa-helm's
6209 /// `BTreeSet`-collect on `:etiquetas` silently dedups the
6210 /// rendered `keywords:` array at chart-render time), the
6211 /// `maintainers:` rendering has *no* dedup — duplicate `:autores`
6212 /// entries stack verbatim in the chart, divergent from every
6213 /// peer typed-graph set gate ([`crate::AplicacaoError::MembroDuplicate`]
6214 /// on `:membros`, [`crate::AplicacaoError::PlacementClusterDuplicate`]
6215 /// on `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
6216 /// on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
6217 /// on `:contratos`, [`crate::DepError::DuplicateNome`] on
6218 /// `:deps` / `:deps-dev`, [`crate::UpgradeError::DuplicateFrom`]
6219 /// on `:upgrade-from`, [`ManifestError::EtiquetaDuplicate`] on
6220 /// `:etiquetas`).
6221 ///
6222 /// Past the empty arm the gate enforces the chart-maintainer-name
6223 /// shape predicate via [`crate::render::is_chart_maintainer_name_shape`]:
6224 /// the structural single-line printable-UTF-8 floor every realistic
6225 /// Helm chart maintainer name carries — 1..=128 bytes, no leading
6226 /// or trailing whitespace, no ASCII control characters anywhere,
6227 /// Unicode bytes accepted. Closes the canonical paste-from-doc
6228 /// footguns the bare empty + duplicate arms left open:
6229 /// paste-from-aligned-doc whitespace (`" pleme-io"`, `"pleme-io "`),
6230 /// paste-from-multiline-doc newline (`"alice\nbob"` — the author
6231 /// pasted a multi-line block of author records into one `:autores`
6232 /// entry instead of splitting into one entry per author),
6233 /// paste-from-Windows-CRLF-doc carriage return, tab-from-aligned-doc,
6234 /// and the paste-from-binary-blob control bytes that would silently
6235 /// land as YAML-illegal byte sequences in the rendered Chart.yaml
6236 /// `maintainers:` array. Mirrors the shape-predicate cascade
6237 /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
6238 /// [`Self::validate_edicao`] / [`Self::validate_repositorio`]
6239 /// establish past their own empty arms on the sibling universal-axis
6240 /// `Option<String>` surfaces — the first universal-axis Vec<String>
6241 /// surface to land the empty-first-then-shape-then-duplicate per-entry
6242 /// cascade.
6243 ///
6244 /// Same empty-first cascade discipline every peer per-axis gate
6245 /// uses: the per-entry empty arm fires before the per-entry shape
6246 /// arm before the cross-entry duplicate arm. Walks the list in
6247 /// declaration order so the first-collision diagnostic surfaces the
6248 /// lexicographically-earliest offending position, peer with every
6249 /// other duplicate gate on this surface.
6250 ///
6251 /// Universal-axis (every kind carries `:autores`), so wired at the
6252 /// caixa-build gate alongside the peer universal gates
6253 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6254 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
6255 /// [`Self::validate_code_paths`] — before the kind-coherence gates
6256 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
6257 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6258 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6259 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
6260 /// slot sets.
6261 pub fn validate_autores(&self) -> Result<(), ManifestError> {
6262 let mut seen = std::collections::HashSet::new();
6263 for autor in self.autores() {
6264 if autor.is_empty() {
6265 return Err(ManifestError::AutorEmpty);
6266 }
6267 crate::render::is_chart_maintainer_name_shape(autor).map_err(|reason| {
6268 ManifestError::AutorInvalid {
6269 autor: autor.clone(),
6270 reason,
6271 }
6272 })?;
6273 crate::render::insert_first_seen(&mut seen, autor.as_str(), || {
6274 ManifestError::AutorDuplicate {
6275 autor: autor.clone(),
6276 }
6277 })?;
6278 }
6279 Ok(())
6280 }
6281
6282 /// Reject `:repositorio` values whose shape the shared
6283 /// [`crate::render::is_git_repo_url`] predicate refuses. The flat
6284 /// `repositorio: Option<String>` slot on [`Caixa`] is the
6285 /// universal git-shaped homepage axis every kind carries — the
6286 /// substrate routes the same string through two load-bearing
6287 /// consumers:
6288 ///
6289 /// - [`caixa-helm`] folds it verbatim into the rendered
6290 /// `lareira-<nome>` Helm chart's `Chart.yaml` `home:` field
6291 /// (`build_chart_yaml` at `caixa-helm/src/lib.rs:268`) and into
6292 /// the chart `README.md` `repo = …` interpolation
6293 /// (`caixa-helm/src/lib.rs:359`).
6294 /// - [`caixa-flux`] folds it verbatim into the standalone
6295 /// `ClusterBundleOpts::for_caixa` `git_url:` field
6296 /// (`caixa-flux/src/lib.rs:293`), which becomes the `FluxCD`
6297 /// `GitRepository.spec.url` the cluster's source-controller
6298 /// polls — the load-bearing deploy-time axis.
6299 ///
6300 /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
6301 /// substitute a placeholder when the slot is absent (`None` → the
6302 /// fallback fires); a `Some("")` *skips the fallback* and silently
6303 /// passes the empty string through to `Chart.yaml home: ""` /
6304 /// `GitRepository url: ""` — Helm's chart lint and `FluxCD`'s source
6305 /// controller both reject the empty URL far from the source
6306 /// `caixa.lisp`, with no field naming the offending `:repositorio`.
6307 /// Similarly a malformed `:repositorio` (whitespace, control char,
6308 /// missing `:` separator, leading `-`) silently lands in the
6309 /// rendered artifacts and breaks at `git clone` / `helm template`
6310 /// / `flux reconcile` time.
6311 ///
6312 /// Thin wrapper around [`crate::render::is_git_repo_url`] — the
6313 /// same shared predicate the peer [`crate::DepSource::validate`]
6314 /// routes the `:fonte (:tipo git :repo …)` axis through. With this
6315 /// gate the two `git URL`-shaped surfaces on the typed Caixa
6316 /// (`:repositorio` here, `:deps :fonte :repo` peer) are
6317 /// structurally equivalent: every value past validate is
6318 /// guaranteed-acceptable by the predicate's union of constraints
6319 /// (non-empty, length-bounded, no leading `-`, no whitespace, no
6320 /// control chars, ASCII only, no leading `:`, contains a `:`
6321 /// separator). The predicate accepts every documented authoring
6322 /// shape — `github:org/repo` shorthand, `https://host/path`,
6323 /// `ssh://[user@]host/path`, `git://host/path`, `git@host:path`
6324 /// scp-style SSH, `file:///path` — and refuses the canonical
6325 /// paste-from-blank-doc / paste-from-multiline-doc / CLI-arg-
6326 /// injection footguns at validate time. Maps the predicate's
6327 /// `String` reason verbatim into the
6328 /// [`ManifestError::RepositorioInvalid`] variant, carrying the
6329 /// offending value + parser-shaped reason so the diagnostic is
6330 /// self-locating (the author can grep their `caixa.lisp` for
6331 /// `:repositorio "<value>"` and fix it in one edit).
6332 ///
6333 /// `None` (the canonical "omit the slot to express no published
6334 /// homepage" shape) is accepted trivially — the gate is a no-op
6335 /// when the author didn't declare a value. `Some("")` is gated by
6336 /// the narrower [`ManifestError::RepositorioEmpty`] arm before the
6337 /// shape predicate is consulted, mirroring the empty-first cascade
6338 /// every peer per-axis identity gate uses
6339 /// ([`ManifestError::NomeEmpty`] → [`ManifestError::NomeInvalid`],
6340 /// [`ManifestError::VersaoEmpty`] → [`ManifestError::VersaoInvalid`],
6341 /// [`crate::DepError::FonteRepoEmpty`] →
6342 /// [`crate::DepError::FonteRepoInvalid`]).
6343 ///
6344 /// Universal-axis (every kind carries `:repositorio`), so wired at
6345 /// the caixa-build gate alongside the peer universal gates
6346 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6347 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
6348 /// [`Self::validate_autores`] / [`Self::validate_code_paths`] —
6349 /// before the kind-coherence gates
6350 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
6351 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6352 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6353 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
6354 /// specific slot sets.
6355 pub fn validate_repositorio(&self) -> Result<(), ManifestError> {
6356 let Some(s) = self.repositorio() else {
6357 return Ok(());
6358 };
6359 if s.is_empty() {
6360 return Err(ManifestError::RepositorioEmpty);
6361 }
6362 is_git_repo_url(s).map_err(|reason| ManifestError::RepositorioInvalid {
6363 repositorio: s.to_string(),
6364 reason,
6365 })
6366 }
6367
6368 /// Reject `:descricao` values that are the empty string. The flat
6369 /// `descricao: Option<String>` slot on [`Caixa`] is the universal
6370 /// free-form-prose homepage axis every kind carries — the
6371 /// substrate routes the same string through two load-bearing
6372 /// consumers in the [`caixa-helm`] renderer:
6373 ///
6374 /// - `build_chart_yaml` folds it verbatim into the rendered
6375 /// `lareira-<nome>` Helm chart's `Chart.yaml` `description:`
6376 /// field (`caixa-helm/src/lib.rs:232-235`).
6377 /// - `build_readme` folds it verbatim into the rendered chart
6378 /// `README.md` header (`caixa-helm/src/lib.rs:333-336`).
6379 ///
6380 /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
6381 /// substitute a `caixa.nome`-derived placeholder when the slot is
6382 /// absent (`None` → the fallback fires); a `Some("")` *skips the
6383 /// fallback* and silently passes the empty string through to
6384 /// `Chart.yaml description: ""` / a blank chart `README.md`
6385 /// header. Helm's chart spec requires a non-empty `description:`
6386 /// field on `apiVersion: v2` charts (`helm lint` surfaces it as
6387 /// `WARNING [chart.metadata.description]: description is required`),
6388 /// so the empty `Some("")` silently lands in the rendered
6389 /// artifacts and breaks at `helm lint` / `helm install` time far
6390 /// from the source `caixa.lisp`, with no field naming the
6391 /// offending `:descricao`.
6392 ///
6393 /// `None` (the canonical "omit the slot to defer to the renderer's
6394 /// `caixa.nome`-derived fallback" shape) is accepted trivially —
6395 /// the gate is a no-op when the author didn't declare a value.
6396 /// `Some("")` is gated by the narrower
6397 /// [`ManifestError::DescricaoEmpty`] arm, mirroring the empty-arm
6398 /// shape every peer per-axis empty gate uses
6399 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
6400 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
6401 /// [`ManifestError::RepositorioEmpty`]).
6402 ///
6403 /// Universal-axis (every kind carries `:descricao`), so wired at
6404 /// the caixa-build gate alongside the peer universal gates
6405 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6406 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
6407 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
6408 /// [`Self::validate_code_paths`] — before the kind-coherence
6409 /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
6410 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6411 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6412 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
6413 /// specific slot sets.
6414 ///
6415 /// Past the empty arm the gate enforces the chart-description
6416 /// shape predicate via [`crate::render::is_chart_description_shape`]:
6417 /// the structural single-line UTF-8 floor every realistic chart
6418 /// description in the wild matches — 1..=512 bytes, no leading
6419 /// or trailing whitespace, no ASCII control characters anywhere
6420 /// (`0x00..=0x1F` plus `0x7F` DEL — banning tab, newline,
6421 /// carriage return, and every other control byte), Unicode
6422 /// continuation bytes accepted (the canonical fixtures carry
6423 /// `→` and `—`). Closes the canonical paste-from-doc footguns
6424 /// the bare empty-arm gate left open: paste-from-aligned-doc
6425 /// leading / trailing whitespace (`" Checkout flow."`,
6426 /// `"Checkout flow. "`), paste-from-multiline-doc newline
6427 /// (`"Checkout\nflow."`), paste-from-Windows-CRLF-doc CR
6428 /// (`"Checkout\rflow."`), tab-from-aligned-doc
6429 /// (`"Checkout\tflow."`), and paste-from-binary-blob NUL / BEL /
6430 /// ESC / DEL bytes. Mirrors the shape-predicate cascade
6431 /// [`Self::validate_repositorio`] / [`Self::validate_licenca`] /
6432 /// [`Self::validate_edicao`] establish past their own empty arms
6433 /// on the sibling universal-axis `Option<String>` Caixa-level
6434 /// value-shape surfaces.
6435 ///
6436 /// The empty-first cascade discipline mirrors every peer per-axis
6437 /// identity gate: [`ManifestError::DescricaoEmpty`] runs before
6438 /// [`ManifestError::DescricaoInvalid`], so the narrower empty
6439 /// diagnostic surfaces on `Some("")` rather than the broader
6440 /// shape-predicate diagnostic — peer with how
6441 /// [`ManifestError::LicencaEmpty`] runs before
6442 /// [`ManifestError::LicencaInvalid`],
6443 /// [`ManifestError::EdicaoEmpty`] runs before
6444 /// [`ManifestError::EdicaoInvalid`],
6445 /// [`ManifestError::RepositorioEmpty`] runs before
6446 /// [`ManifestError::RepositorioInvalid`].
6447 pub fn validate_descricao(&self) -> Result<(), ManifestError> {
6448 let Some(s) = self.descricao() else {
6449 return Ok(());
6450 };
6451 if s.is_empty() {
6452 return Err(ManifestError::DescricaoEmpty);
6453 }
6454 crate::render::is_chart_description_shape(s).map_err(|reason| {
6455 ManifestError::DescricaoInvalid {
6456 descricao: s.to_string(),
6457 reason,
6458 }
6459 })?;
6460 Ok(())
6461 }
6462
6463 /// Reject `:licenca` values that are the empty string. The flat
6464 /// `licenca: Option<String>` slot on [`Caixa`] is the universal
6465 /// SPDX-shaped license-expression axis every kind carries — the
6466 /// substrate routes the same string through the [`caixa-helm`]
6467 /// renderer's `build_readme` which folds it verbatim into the
6468 /// rendered `lareira-<nome>` Helm chart's `README.md` `## License`
6469 /// section (`caixa-helm/src/lib.rs:361`) via
6470 /// `caixa.licenca.clone().unwrap_or_else(|| "MIT".into())`. The
6471 /// fallback only fires on `None`; a `Some("")` *skips the
6472 /// fallback* and silently passes the empty string through to a
6473 /// chart `README.md` whose `License` section renders as the bare
6474 /// trailing period (`.\n`) — peer footgun with the
6475 /// `Some("")`-skips-`unwrap_or_else` shape the
6476 /// [`Self::validate_descricao`] and [`Self::validate_repositorio`]
6477 /// gates close on the sibling free-form-prose and git-URL axes.
6478 ///
6479 /// `None` (the canonical "omit the slot to defer to the
6480 /// renderer's `MIT` fallback" shape every existing fixture
6481 /// carries) is accepted trivially — the gate is a no-op when the
6482 /// author didn't declare a value. `Some("")` is gated by the
6483 /// narrower [`ManifestError::LicencaEmpty`] arm, mirroring the
6484 /// empty-arm shape every peer per-axis empty gate uses
6485 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
6486 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
6487 /// [`ManifestError::RepositorioEmpty`],
6488 /// [`ManifestError::DescricaoEmpty`]).
6489 ///
6490 /// Universal-axis (every kind carries `:licenca`), so wired at
6491 /// the caixa-build gate alongside the peer universal gates
6492 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6493 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
6494 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
6495 /// [`Self::validate_descricao`] / [`Self::validate_code_paths`]
6496 /// — before the kind-coherence gates
6497 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
6498 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6499 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6500 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
6501 /// specific slot sets.
6502 ///
6503 /// Past the empty arm the gate enforces the SPDX-expression shape
6504 /// predicate via [`crate::render::is_spdx_expression_shape`]: the
6505 /// structural alphabet floor every realistic SPDX expression in
6506 /// the wild uses — ASCII alphanumeric plus `.`, `-`, `+`, `(`,
6507 /// `)`, `:` (the `DocumentRef-…:LicenseRef-…` separator), and a
6508 /// single ASCII space (token separator). Closes the canonical
6509 /// paste-from-doc footguns the bare empty-arm gate left open:
6510 /// paste-from-doc whitespace (`"MIT "`, `" MIT"`), paste-from-
6511 /// multiline-doc CRLF (`"MIT\n"`), tab-from-aligned-doc
6512 /// (`"MIT\tOR Apache-2.0"`), non-ASCII smart-quote paste,
6513 /// underscore-instead-of-hyphen typo (`"Apache_2.0"`),
6514 /// comma-instead-of-`OR`-keyword colloquial idiom (`"MIT,
6515 /// Apache-2.0"`), slash-dual-license colloquial idiom (`"MIT/
6516 /// Apache-2.0"`), and semicolon-list-separator confusion
6517 /// (`"MIT; Apache-2.0"`). Mirrors the shape-predicate cascade
6518 /// [`Self::validate_repositorio`] / [`Self::validate_edicao`]
6519 /// establish past their own empty arms.
6520 ///
6521 /// The empty-first cascade discipline mirrors every peer per-axis
6522 /// identity gate: [`ManifestError::LicencaEmpty`] runs before
6523 /// [`ManifestError::LicencaInvalid`], so the narrower empty
6524 /// diagnostic surfaces on `Some("")` rather than the broader
6525 /// shape-predicate diagnostic — peer with how
6526 /// [`ManifestError::EdicaoEmpty`] runs before
6527 /// [`ManifestError::EdicaoInvalid`],
6528 /// [`ManifestError::RepositorioEmpty`] runs before
6529 /// [`ManifestError::RepositorioInvalid`].
6530 ///
6531 /// A future tightening on this axis can extend the alphabet
6532 /// floor into a full SPDX expression parser + license-id
6533 /// allowlist (rejecting alphabet-valid values that don't name a
6534 /// real SPDX license identifier — e.g., `"NotAReal"` is
6535 /// alphabet-valid but no `NotAReal` license-id exists). That
6536 /// parser only becomes meaningful past a real SPDX-spec
6537 /// dependency; this gate establishes the structural floor by
6538 /// refusing every non-SPDX-alphabet value at validate time.
6539 pub fn validate_licenca(&self) -> Result<(), ManifestError> {
6540 let Some(s) = self.licenca() else {
6541 return Ok(());
6542 };
6543 if s.is_empty() {
6544 return Err(ManifestError::LicencaEmpty);
6545 }
6546 crate::render::is_spdx_expression_shape(s).map_err(|reason| {
6547 ManifestError::LicencaInvalid {
6548 licenca: s.to_string(),
6549 reason,
6550 }
6551 })?;
6552 Ok(())
6553 }
6554
6555 /// Reject `:edicao` values that are the empty string. The flat
6556 /// `edicao: Option<String>` slot on [`Caixa`] is the universal
6557 /// language-edition axis every kind carries — it determines the
6558 /// tatara-lisp macro surface + compatibility flags the substrate
6559 /// applies when building a caixa, and lands verbatim in the
6560 /// `Caixa::template` author-time scaffold (the canonical
6561 /// `:edicao "2026"` line every `feira init` emits via
6562 /// [`Caixa::template`] at `caixa-core/src/manifest.rs:1193`) and
6563 /// in every renderer-side fixture (`caixa-helm/src/lib.rs:375`,
6564 /// `caixa-flux/src/lib.rs:445`, `caixa-mesh/src/lib.rs:629`,
6565 /// `caixa-core/src/render.rs:2510`) via
6566 /// `edicao: Some("2026".into())`.
6567 ///
6568 /// `None` (the canonical "omit the slot to defer to the
6569 /// substrate's default edition" shape every existing
6570 /// [`caixa-resolver`] integration test fixture carries via
6571 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
6572 /// is accepted trivially — the gate is a no-op when the author
6573 /// didn't declare a value. `Some("")` is gated by the narrower
6574 /// [`ManifestError::EdicaoEmpty`] arm, mirroring the empty-arm
6575 /// shape every peer per-axis empty gate uses
6576 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
6577 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
6578 /// [`ManifestError::RepositorioEmpty`],
6579 /// [`ManifestError::DescricaoEmpty`], [`ManifestError::LicencaEmpty`]).
6580 ///
6581 /// Universal-axis (every kind carries `:edicao`), so wired at
6582 /// the caixa-build gate alongside the peer universal gates
6583 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6584 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
6585 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
6586 /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
6587 /// [`Self::validate_code_paths`] — before the kind-coherence
6588 /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
6589 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6590 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6591 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
6592 /// specific slot sets.
6593 ///
6594 /// Past the empty arm the gate enforces the canonical year-shape
6595 /// predicate: every documented tatara-lisp edition is a 4-digit
6596 /// ASCII decimal year (`"2026"` is the only edition currently
6597 /// minted; future-introduced siblings will follow the same
6598 /// shape, peer with Cargo's `[package] edition` grammar which
6599 /// every value Cargo has ever accepted matches — `"2015"`,
6600 /// `"2018"`, `"2021"`, `"2024"`). Any value that's not exactly
6601 /// 4 ASCII decimal bytes is rejected with the narrower
6602 /// [`ManifestError::EdicaoInvalid`] arm, mirroring the
6603 /// shape-predicate cascade [`Self::validate_repositorio`]
6604 /// establishes past its own empty arm
6605 /// ([`ManifestError::RepositorioEmpty`] →
6606 /// [`ManifestError::RepositorioInvalid`]). Closes the canonical
6607 /// paste-from-doc footguns the bare empty-arm gate left open:
6608 ///
6609 /// - leading / trailing whitespace from a paste-from-doc
6610 /// (`"2026 "`, `" 2026"`)
6611 /// - control characters / CRLF from a paste-from-multiline-doc
6612 /// (`"2026\n"`)
6613 /// - non-ASCII look-alikes from a fullwidth keyboard
6614 /// (`"2026"`) which would silently land as a non-ASCII
6615 /// string in the rendered caixa.lisp
6616 /// - free-form non-year values (`"x"`, `"latest"`,
6617 /// `"nightly"`) that have no operational meaning on the
6618 /// substrate's build-time edition selector
6619 /// - leading non-digit prefixes (`"v2026"`, `"e2026"`,
6620 /// `"r2026"`) — common version-tag idioms that don't apply
6621 /// to the year-shaped edition axis
6622 /// - decimal-shaped values (`"2026.1"`, `"2026.0"`) — every
6623 /// edition is a year, not a fractional version
6624 /// - wrong-length numeric values (`"26"`, `"202"`, `"20260"`,
6625 /// `"00026"`) that don't name a year
6626 ///
6627 /// `None` (the canonical "omit the slot to defer to the
6628 /// substrate's default edition" shape every existing
6629 /// [`caixa-resolver`] integration test fixture carries via
6630 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
6631 /// is accepted trivially — the gate is a no-op when the author
6632 /// didn't declare a value. The empty-first cascade discipline
6633 /// mirrors every peer per-axis identity gate:
6634 /// [`ManifestError::EdicaoEmpty`] runs before
6635 /// [`ManifestError::EdicaoInvalid`], so the narrower empty
6636 /// diagnostic surfaces on `Some("")` rather than the broader
6637 /// shape-predicate diagnostic — peer with how
6638 /// [`ManifestError::NomeEmpty`] runs before
6639 /// [`ManifestError::NomeInvalid`],
6640 /// [`ManifestError::VersaoEmpty`] runs before
6641 /// [`ManifestError::VersaoInvalid`],
6642 /// [`ManifestError::RepositorioEmpty`] runs before
6643 /// [`ManifestError::RepositorioInvalid`].
6644 ///
6645 /// A future tightening on this axis can extend the shape
6646 /// predicate into a known-edition allowlist (rejecting
6647 /// year-shaped values that don't name a tatara-lisp edition
6648 /// the substrate actually understands — e.g., `"1999"` is
6649 /// year-shaped but no `1999` edition exists). That allowlist
6650 /// only becomes meaningful past the introduction of a sibling
6651 /// edition to `"2026"`; this gate establishes the structural
6652 /// floor by refusing every non-year-shaped value at validate
6653 /// time.
6654 pub fn validate_edicao(&self) -> Result<(), ManifestError> {
6655 let Some(s) = self.edicao() else {
6656 return Ok(());
6657 };
6658 if s.is_empty() {
6659 return Err(ManifestError::EdicaoEmpty);
6660 }
6661 if s.len() != 4 || !s.bytes().all(|b| b.is_ascii_digit()) {
6662 return Err(ManifestError::EdicaoInvalid {
6663 edicao: s.to_string(),
6664 reason: "must be a 4-digit ASCII decimal year (canonical \"2026\")".to_string(),
6665 });
6666 }
6667 Ok(())
6668 }
6669
6670 /// Compose the supervisor-related flat slots into a single
6671 /// [`SupervisorSpec`] for validation. Returns `None` when the
6672 /// caixa isn't a `:kind Supervisor`.
6673 ///
6674 /// The flat representation in [`Caixa`] keeps tatara-lisp authoring
6675 /// simple (one form, no nested `:supervisor (…)` block); this view
6676 /// is the "typed shape" the operator + supervisor reconciler
6677 /// consume.
6678 #[must_use]
6679 pub fn supervisor_view(&self) -> Option<SupervisorSpec> {
6680 if !self.kind().is_supervisor() {
6681 return None;
6682 }
6683 // Fold through the shared `supervisor::duration_codec::parse`
6684 // — the same parser the serde-routed `with = "duration_codec"`
6685 // on `SupervisorSpec::restart_window`, the `:politicas
6686 // :timeout` codec, and the `:politicas :circuit-breaker
6687 // :window` codec all consume. The prior inline f64-shaped
6688 // duplicate (`parse_window_inline`) admitted every magnitude
6689 // the integer-magnitude gate (1c55a2a) rejects on the three
6690 // serde-routed siblings — `"1.5s"`, `"1.0s"`, `"0.5m"`,
6691 // `"+30s"`, `"-30s"` — and silently dropped malformed input as
6692 // `None` (i.e. "no reset"), divergent from the shared codec's
6693 // integer-magnitude discipline by construction. The fold
6694 // closes the divergence: every value the typed
6695 // `SupervisorSpec` carries past `supervisor_view` is in the
6696 // shared codec's accepted set. The `.ok()` here preserves the
6697 // existing soft-swallow shape on this view-construction path;
6698 // the new [`Caixa::validate_restart_window`] (sibling of
6699 // [`Self::validate_nome`] / [`Self::validate_versao`]) names
6700 // the offending raw string at build time so authoring tools
6701 // (`feira lint`, the future layout-side wire-up) surface a
6702 // self-locating diagnostic instead of a silently dropped
6703 // window.
6704 let restart_window = self
6705 .restart_window()
6706 .and_then(|s| crate::supervisor::duration_codec::parse(s).ok());
6707 Some(SupervisorSpec {
6708 // Route the author-omitted `:estrategia` arm through the
6709 // substrate-canonical
6710 // [`crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT`] typed
6711 // `pub const` rather than the transitively-derived
6712 // [`RestartStrategy::default`] route the prior
6713 // `.unwrap_or_default()` fold reached for — one source of
6714 // truth for the Erlang/OTP `one_for_one` half of Learn You
6715 // Some Erlang's `{one_for_one, intensity, 5, 60}` worker-
6716 // supervisor canonical default that also backs the
6717 // [`crate::supervisor::Default for RestartStrategy`] impl
6718 // and the [`crate::supervisor::Default for SupervisorSpec`]
6719 // impl's struct-literal `estrategia` field, all now routed
6720 // through the same lifted constant. Prior to the lift the
6721 // composition site carried `.unwrap_or_default()` with no
6722 // compile-time link back to the shared OTP-canonical
6723 // default that the peer paired
6724 // `.unwrap_or(SUPERVISOR_MAX_RESTARTS_DEFAULT)` (b698ec0)
6725 // arm on the sibling `:max-restarts` axis routes through —
6726 // so a future rebrand of the OTP-canonical strategy default
6727 // (a widening to `rest_for_one` once the substrate
6728 // discovers startup-order-coupled child cohorts as the more
6729 // common shape, a per-cluster overlay the operator pins
6730 // through the MESH-COMPOSITION §III.2 supervision-canary
6731 // `:estrategia-overrides` roadmap slot) would have had to
6732 // migrate the paired `MaxIntensity` + `Period` halves
6733 // through the lifted constants and the `one_for_one` half
6734 // through a `RestartStrategy::default()` route in lockstep
6735 // or the three halves of the same OTP-canonical default
6736 // would silently drift out of pairing. Byte-parity against
6737 // the lifted constant closes the split. Pinned by
6738 // [`supervisor_view_estrategia_fallback_routes_through_lifted_default`]
6739 // in the tests module.
6740 estrategia: self
6741 .estrategia()
6742 .unwrap_or(crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT),
6743 // Route the author-omitted `:max-restarts` arm through the
6744 // substrate-canonical [`crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT`]
6745 // typed `pub const` rather than the raw `5` literal — one
6746 // source of truth for the Erlang/OTP-canonical
6747 // `{intensity, 5, 60}` `MaxIntensity` default that also
6748 // backs the serde-side wire-format author-omitted arm on
6749 // [`crate::supervisor::SupervisorSpec::max_restarts`] via
6750 // `#[serde(default = "default_max_restarts")]` and the
6751 // [`Default for SupervisorSpec`] impl's struct-literal
6752 // default field. Prior to the lift the composition site
6753 // carried a raw `5` with no compile-time link back to the
6754 // serde-side default, so a future rebrand of the OTP-
6755 // canonical default (a tightening to Elixir's `3`, a
6756 // widening to a per-cluster overlay the operator pins
6757 // through the MESH-COMPOSITION §III.2 supervision-canary
6758 // `:supervisor :max-restarts-overrides` roadmap slot)
6759 // would have had to be threaded through both open-coded
6760 // copies in lockstep or the wire-format author-omitted arm
6761 // and this view-construction author-omitted arm would
6762 // silently disagree on which restart-budget an omitted
6763 // `:max-restarts` resolves to. Pinned by
6764 // [`supervisor_view_max_restarts_fallback_routes_through_lifted_default`]
6765 // in the tests module.
6766 max_restarts: self
6767 .max_restarts()
6768 .unwrap_or(crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT),
6769 restart_window,
6770 children: self.children().to_vec(),
6771 })
6772 }
6773
6774 /// A minimal starter manifest emitted by `feira init`.
6775 #[must_use]
6776 pub fn template(nome: &str) -> String {
6777 format!(
6778 "(defcaixa\n \
6779 :nome {nome:?}\n \
6780 :versao \"0.1.0\"\n \
6781 :kind Biblioteca\n \
6782 :edicao \"2026\"\n \
6783 :descricao \"FIXME — describe this caixa\"\n \
6784 :autores ()\n \
6785 :etiquetas ()\n \
6786 :deps ()\n \
6787 :deps-dev ()\n \
6788 :bibliotecas (\"lib/{nome}.lisp\"))\n"
6789 )
6790 }
6791
6792 /// Serialize to a canonical `caixa.lisp` source — suitable for writing
6793 /// back after mutation (e.g. `feira add`).
6794 ///
6795 /// Goes through serde JSON → canonical Sexp → per-field pretty print.
6796 /// The derive-macro `compile_from_sexp` path is the inverse, so any
6797 /// `Caixa` round-trips through `to_lisp` + `from_lisp`.
6798 #[must_use]
6799 pub fn to_lisp(&self) -> String {
6800 let json = serde_json::to_value(self).expect("Caixa serialize");
6801 let sexp = tatara_lisp::domain::json_to_sexp(&json);
6802 let tatara_lisp::Sexp::List(items) = sexp else {
6803 return format!("(defcaixa {sexp})\n");
6804 };
6805 let mut out = String::from("(defcaixa");
6806 let mut i = 0;
6807 while i + 1 < items.len() {
6808 out.push_str("\n ");
6809 out.push_str(&items[i].to_string());
6810 out.push(' ');
6811 out.push_str(&items[i + 1].to_string());
6812 i += 2;
6813 }
6814 out.push_str(")\n");
6815 out
6816 }
6817}
6818
6819/// Errors raised by top-level [`Caixa`] validators that don't fit
6820/// the per-axis [`DepError`] / [`crate::AplicacaoError`] /
6821/// [`crate::SupervisorError`] / [`crate::LayoutError`] families —
6822/// the Caixa's own identity axes (`:nome`, `:versao`) that flow
6823/// through every substrate-side artifact's `metadata.name` /
6824/// version derivation.
6825///
6826/// A future top-level sum (the M4 `CaixaError` the [`DepError`]
6827/// doc-comment anticipates) can hold one of each per-axis error
6828/// family without reshaping individual diagnostics; this enum is
6829/// the first such per-Caixa-identity family.
6830#[derive(Debug, Error, PartialEq, Eq)]
6831pub enum ManifestError {
6832 #[error(
6833 ":nome is empty (every caixa must name itself; the value flows \
6834 into every K8s artifact's `metadata.name` derivation and into \
6835 the default `lib/<nome>.lisp` / `exe/<nome>` layout paths)"
6836 )]
6837 NomeEmpty,
6838 #[error(
6839 ":nome {nome:?} is not a valid DNS-1123 label: {reason} (the K8s \
6840 apiserver enforces this rule on every `metadata.name` the \
6841 caixa's substrate-side renderers derive from `:nome` — the \
6842 `lareira-<nome>` Helm chart name, the programs.yaml entry \
6843 name, the `LABEL_APLICACAO` label value, the `<aplicacao>-<de>-to-<para>` \
6844 CiliumNetworkPolicy name, the `<aplicacao>-<para>` HTTPRoute \
6845 name; use a lowercase alphanumeric + hyphen identifier like \
6846 `\"checkout\"` or `\"cart-v2\"`)"
6847 )]
6848 NomeInvalid { nome: String, reason: String },
6849 #[error(
6850 ":nome {nome:?} overflows the joint-length budget on the canonical \
6851 `lareira-<nome>` chart-name shape: {reason} (every per-Servico / \
6852 per-Aplicacao renderer the substrate carries — `caixa-helm`'s \
6853 `Chart.yaml::name`, `caixa-flux`'s `cluster_bundle` HelmRelease \
6854 `chart:` slot, `caixa-tatara`'s `release_name` + \
6855 `oci://<registry>/lareira-<nome>` chart ref — derives the same \
6856 joint name through the canonical `lareira_chart_name` helper, and \
6857 Helm's `Chart.yaml::name` admission rule + the K8s apiserver's \
6858 DNS-1123 label cap on every chart-name-derived `metadata.name` \
6859 reject any joint name exceeding 63 bytes; the narrower \
6860 `:nome` shape (`NomeInvalid`) gates the bare-`:nome` budget, this \
6861 arm gates the chart-name budget downstream renderers inherit)"
6862 )]
6863 NomeChartNameBudgetExceeded { nome: String, reason: String },
6864 #[error(
6865 ":versao is empty (every caixa must pin its own version; the value flows \
6866 into the `lareira-<nome>` Helm chart's `Chart.yaml` version + appVersion, \
6867 the `feira publish` `v<versao>` git tag, the OCI image's `:v<versao>` / \
6868 `:latest` tags, the lacre closure's `concrete_versao`, and the \
6869 `:upgrade-from :from` peers — use a SemVer-2 literal like `\"0.1.0\"`)"
6870 )]
6871 VersaoEmpty,
6872 #[error(
6873 ":versao {versao:?} is not a valid SemVer-2 version: {reason} (the substrate \
6874 consumes this string as `semver::Version` — three-part `MAJOR.MINOR.PATCH` \
6875 with optional `-prerelease` and `+build` — across every artifact derived \
6876 from `:versao`: the `lareira-<nome>` Helm chart's `Chart.yaml` version + \
6877 appVersion (Helm SemVer-2-strict), the `feira publish` `v<versao>` git tag, \
6878 the OCI image's `:v<versao>` tag, the lacre closure's `concrete_versao`, \
6879 and the `:upgrade-from :from` peers that match against this exact shape; \
6880 use a literal like `\"0.1.0\"`, `\"0.2.0-rc.1\"`, or `\"1.0.0+build.42\"` — \
6881 not a git-tag-shape like `\"v0.1.0\"`, a docker-tag-shape like `\"latest\"`, \
6882 a requirement-shape like `\"^0.1\"`, or a four-part `\"0.1.0.0\"`)"
6883 )]
6884 VersaoInvalid { versao: String, reason: String },
6885 #[error(
6886 ":restart-window {restart_window:?} is not a valid duration: {reason} (the \
6887 substrate consumes this string through the shared \
6888 `supervisor::duration_codec` — the same parser routed via `with = \
6889 \"duration_codec\"` onto the typed `SupervisorSpec::restart_window`, \
6890 `:politicas :timeout`, and `:politicas :circuit-breaker :window` slots; \
6891 the canonical authoring form is `<integer><unit>` where the unit is one \
6892 of `ms` / `s` / `m` / `h` and the magnitude has no decimal point and no \
6893 leading `+` / `-` sign — e.g. `\"60s\"`, `\"5m\"`, `\"1h\"`, `\"500ms\"`. \
6894 Without this gate a malformed `:restart-window` silently produced a \
6895 supervisor with `restart_window: None` (\"never reset\"), turning OTP's \
6896 `MaxIntensity / Period` invariant into a never-reset supervisor far from \
6897 the source `caixa.lisp`; the gate moves the diagnostic to the manifest \
6898 layer with the offending value named verbatim. Omit the slot entirely to \
6899 express \"no reset\"; carry a positive integer duration to express the \
6900 sliding window)"
6901 )]
6902 RestartWindowMalformed {
6903 restart_window: String,
6904 reason: String,
6905 },
6906 #[error(
6907 "{slot} entry is an empty path string — every {slot} entry must name \
6908 a file relative to the caixa root; omit the entry to omit the file \
6909 (the layout checker's `root.join(\"\")` resolves to the caixa root \
6910 itself, so an empty entry silently aliases the project root as a \
6911 declared {slot} file, then fails downstream at parse / existence \
6912 time with a diagnostic that names the root rather than the offending \
6913 entry)"
6914 )]
6915 CodePathEmpty { slot: &'static str },
6916 #[error(
6917 "{slot} entry {} is an absolute path — entries must be relative to \
6918 the caixa root, since `Path::join` replaces the base with an absolute \
6919 right-hand side and `root.join(\"/abs/...\")` resolves to \"/abs/...\" \
6920 outside the caixa root sandbox; rewrite the entry as a relative path \
6921 under the caixa root (e.g. `\"lib/<name>.lisp\"`, `\"exe/<name>\"`, \
6922 `\"servicos/<name>.computeunit.yaml\"`)",
6923 path.display()
6924 )]
6925 CodePathAbsolute { slot: &'static str, path: PathBuf },
6926 #[error(
6927 "{slot} entry {} contains a `..` component — entries must not traverse \
6928 above the caixa root (the layout's `starts_with(<dir>)` fence on \
6929 `:exe` / `:servicos` is component-aware, not canonical-path-aware, \
6930 so a mid-path `..` silently traverses the sandbox; `:bibliotecas` \
6931 has no such fence, so a leading `..` escapes unconditionally if the \
6932 resolved target happens to exist)",
6933 path.display()
6934 )]
6935 CodePathParentEscape { slot: &'static str, path: PathBuf },
6936 #[error(
6937 "{slot} entry {} does not terminate in the `.lisp` extension — every \
6938 `:bibliotecas` entry is a tatara-lisp source file the `feira build` \
6939 loop reads through `tatara_lisp::read` at parse time, so any other \
6940 extension (`.rs`, `.txt`, `.lisp.bak`) or no-extension shape is \
6941 structurally a parser error far from the source caixa.lisp, with \
6942 no field naming the offending `:bibliotecas` entry. Pin a relative \
6943 path under the caixa root whose terminating extension is \
6944 lowercase-`.lisp` (e.g. `\"lib/<name>.lisp\"`, \
6945 `\"lib/handlers.lisp\"`) — the same file-type contract the peer \
6946 `:behavior :on-*` (c97815a) and `:upgrade-from :state-change :script` \
6947 (33cc830) axes already carry through the same lifted \
6948 `is_lisp_extension` predicate",
6949 path.display()
6950 )]
6951 CodePathNonLispExtension { slot: &'static str, path: PathBuf },
6952 #[error(
6953 "{slot} entry {} does not terminate in the `.computeunit.yaml` \
6954 compound suffix — every `:servicos` entry is a typed `ComputeUnit` \
6955 CR YAML file the peer caixa-helm / caixa-flux renderers consume \
6956 through `serde_yaml::from_str` at chart / FluxCD bundle render \
6957 time, so any other extension (`.yaml`, `.yml`, `.json`, the \
6958 off-by-one-segment `.computeunit-yaml`, the editor-backup \
6959 `.computeunit.yaml.bak`) or no-extension shape is structurally a \
6960 YAML-parser error / `ComputeUnit` schema-mismatch far from the \
6961 source caixa.lisp, with no field naming the offending `:servicos` \
6962 entry. Pin a relative path under the caixa root whose terminating \
6963 compound suffix is lowercase-`.computeunit.yaml` (e.g. \
6964 `\"servicos/<name>.computeunit.yaml\"`, \
6965 `\"servicos/hello-rio.computeunit.yaml\"`) — the same file-type \
6966 contract the sibling `:bibliotecas` axis (64772a9) already carries \
6967 on the tatara-lisp-source axis through the peer lifted \
6968 `is_lisp_extension` predicate, here on the compound-suffix axis \
6969 `Path::extension` can't express on its own through the lifted \
6970 `is_computeunit_yaml_extension` predicate",
6971 path.display()
6972 )]
6973 CodePathNonComputeUnitYamlExtension { slot: &'static str, path: PathBuf },
6974 #[error(
6975 "{slot} entry {} appears more than once (the code-path list is \
6976 a set, not a multiset; every peer Vec-shaped author-supplied \
6977 list past validate is set-not-multiset — `:membros :caixa`, \
6978 `:placement :clusters`, `:entrada :paths`, `:contratos`, \
6979 `:children :caixa`, `:deps` / `:deps-dev` `:nome`, \
6980 `:upgrade-from :from`, `:etiquetas`, `:autores` — and the three \
6981 code-path lists are the last Vec-shaped author-supplied slots on \
6982 the typed Caixa surface still admitting a duplicate entry. \
6983 `:bibliotecas` duplicates re-parse the same file at \
6984 `feira build` time and silently mask the author's intent to \
6985 declare a *second* biblioteca; `:exe` duplicates collide on the \
6986 flake `packages.<name>` derivation key at the future \
6987 `caixa-flake` materializer; `:servicos` duplicates surface as the \
6988 narrower [`caixa-helm`] / [`caixa-flux`] `UnsupportedServicoCount` \
6989 rejection far from the source `caixa.lisp`. Drop the duplicate \
6990 or rename it to the actual second file intended)",
6991 path.display()
6992 )]
6993 CodePathDuplicate { slot: &'static str, path: PathBuf },
6994 #[error(
6995 ":etiquetas entry is empty (every tag must carry a non-empty \
6996 registry-search identifier; the empty entry has no operational \
6997 meaning — it indexes nothing in the future caixa-registry search \
6998 axis and clutters the rendered Helm `Chart.yaml` `keywords:` array \
6999 with a no-op tag; omit the entry to express \"no tag on this \
7000 position\")"
7001 )]
7002 EtiquetaEmpty,
7003 #[error(
7004 ":etiquetas entry {etiqueta:?} appears more than once (the \
7005 registry-search tag set is a set, not a multiset; duplicate \
7006 entries are silently dedup'd by caixa-helm's `BTreeSet` collect \
7007 at chart render — a \"second wins / one silently disappears\" \
7008 shape divergent from every peer typed-graph set gate \
7009 (`:membros :caixa`, `:placement :clusters`, `:entrada :paths`, \
7010 `:contratos`, `:deps :nome`, `:upgrade-from :from`); drop the \
7011 duplicate or rename it to the actual tag intended)"
7012 )]
7013 EtiquetaDuplicate { etiqueta: String },
7014 #[error(
7015 ":etiquetas entry {etiqueta:?} is not a valid chart-keyword shape: \
7016 {reason} (the substrate consumes this string through the shared \
7017 `crate::render::is_chart_keyword_shape` predicate — the same \
7018 Cargo crates.io `[package] keywords` grammar entry shape: 1..=20 \
7019 bytes, starts with an ASCII letter, ASCII alphanumeric / `_` / `-` \
7020 continuation. The canonical authoring shapes are short kebab-case \
7021 identifiers like `\"mesh\"`, `\"wasm\"`, `\"tatara-lisp\"`, \
7022 `\"hello-world\"`, `\"caixa-servico\"`, `\"infrastructure\"`. \
7023 Without this gate a malformed `:etiquetas` entry (paste-from-doc \
7024 leading / trailing whitespace `\" mesh\"` / `\"mesh \"`; \
7025 paste-from-multiline-doc newline `\"mesh\\nhttp\"`; \
7026 paste-from-Windows-CRLF-doc CR; CSV-list-separator confusion \
7027 `\"mesh,http,grpc\"` — the author meant to author three separate \
7028 list entries; path-separator confusion `\"caixa/servico\"`; \
7029 namespace-suffix `\"http.1\"`; leading-digit `\"1foo\"`; \
7030 kebab-leak `\"-foo\"`; snake-leak `\"_foo\"`; non-ASCII \
7031 `\"café\"` — every legitimate search tag is strict ASCII; \
7032 paste-from-binary-blob NUL / BEL / ESC / DEL byte) silently \
7033 passed `from_lisp` + `validate_etiquetas` + \
7034 `StandardLayout::verify` and landed in the rendered \
7035 `lareira-<nome>` Helm chart's `Chart.yaml keywords:` array as a \
7036 malformed search tag — Artifact Hub's keyword index + the future \
7037 caixa-registry's keyword index would either silently drop the \
7038 tag or fail to index it far from the source caixa.lisp; the gate \
7039 moves the diagnostic to the manifest layer with the offending \
7040 value named verbatim)"
7041 )]
7042 EtiquetaInvalid { etiqueta: String, reason: String },
7043 #[error(
7044 ":autores entry is empty (every maintainer must carry a non-empty \
7045 identifier; the empty entry has no operational meaning — it \
7046 identifies no one in the substrate's authorship index and renders \
7047 as `maintainers: [{{name: \"\", email: null}}]` in the Helm chart's \
7048 `Chart.yaml`, a no-op maintainer the substrate cannot route to; \
7049 omit the entry to express \"no maintainer on this position\")"
7050 )]
7051 AutorEmpty,
7052 #[error(
7053 ":autores entry {autor:?} appears more than once (the maintainer \
7054 set is a set, not a multiset; unlike `:etiquetas`, caixa-helm's \
7055 `maintainers:` rendering does *no* dedup — duplicate entries \
7056 stack verbatim in `Chart.yaml` as two identical \
7057 `Maintainer {{ name, email: None }}` records, divergent from every \
7058 peer typed-graph set gate (`:etiquetas`, `:membros :caixa`, \
7059 `:placement :clusters`, `:entrada :paths`, `:contratos`, \
7060 `:deps :nome`, `:upgrade-from :from`); drop the duplicate or \
7061 rename it to the actual author intended)"
7062 )]
7063 AutorDuplicate { autor: String },
7064 #[error(
7065 ":autores entry {autor:?} is not a valid chart-maintainer-name shape: \
7066 {reason} (the substrate consumes this string through the shared \
7067 `crate::render::is_chart_maintainer_name_shape` predicate — the same \
7068 single-line-UTF-8 floor every realistic chart maintainer name carries: \
7069 1..=128 bytes, no leading or trailing whitespace, no ASCII control \
7070 characters anywhere, Unicode bytes accepted. The canonical authoring \
7071 shapes are short single-line identifiers like `\"pleme-io\"`, \
7072 `\"Pleme Contributors\"`, `\"alice <alice@example.com>\"`, \
7073 `\"François Dupont\"`. Without this gate a malformed `:autores` entry \
7074 (paste-from-aligned-doc leading whitespace `\" pleme-io\"` / trailing \
7075 whitespace `\"pleme-io \"`; paste-from-multiline-doc newline \
7076 `\"alice\\nbob\"` — the author pasted a multi-line block of author \
7077 records into one entry instead of splitting into one entry per author; \
7078 paste-from-Windows-CRLF-doc carriage return `\"alice\\rbob\"`; \
7079 tab-from-aligned-doc `\"Pleme\\tContributors\"`; paste-from-binary-blob \
7080 NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
7081 `validate_autores` + `StandardLayout::verify` and landed in the \
7082 rendered `lareira-<nome>` Helm chart's `Chart.yaml maintainers:` array \
7083 as a YAML-illegal multi-line scalar or a silently-trimmed whitespace \
7084 round-trip — every chart-aware UI (`helm list`, `helm search`, \
7085 Artifact Hub maintainer index) would render the maintainer name in a \
7086 single-line column far from the source caixa.lisp; the gate moves the \
7087 diagnostic to the manifest layer with the offending value named \
7088 verbatim)"
7089 )]
7090 AutorInvalid { autor: String, reason: String },
7091 #[error(
7092 ":repositorio is the empty string (every published caixa names its \
7093 git source via a non-empty `:repositorio` locator — the value \
7094 flows verbatim into the rendered `lareira-<nome>` Helm chart's \
7095 `Chart.yaml` `home:` field via `caixa-helm` and into the FluxCD \
7096 `GitRepository.spec.url` via `caixa-flux`'s \
7097 `ClusterBundleOpts::for_caixa`; both consumers' \
7098 `Option::unwrap_or_else` fallbacks only fire when the slot is \
7099 `None`, so an empty `Some(\"\")` silently lands as `home: \"\"` / \
7100 `url: \"\"` in the rendered artifacts and breaks at `helm \
7101 template` / FluxCD source-controller reconcile time far from the \
7102 source caixa.lisp; omit the slot entirely to defer to the \
7103 renderer's `https://github.com/pleme-io/<nome>` / \
7104 `caixa.nome`-derived fallback, or carry a canonical authoring \
7105 shape like `\"github:org/repo\"`, `\"https://host/path\"`, \
7106 `\"ssh://[user@]host/path\"`, `\"git@host:path\"`, or \
7107 `\"file:///path\"`)"
7108 )]
7109 RepositorioEmpty,
7110 #[error(
7111 ":repositorio {repositorio:?} is not a valid git repo URL: {reason} \
7112 (the substrate consumes this string through the shared \
7113 `crate::render::is_git_repo_url` predicate — the same parser the \
7114 peer `:deps :fonte (:tipo git :repo …)` axis routes its `:repo` \
7115 value through via `DepSource::validate`; the canonical authoring \
7116 shapes are `\"github:org/repo\"` shorthand, `\"https://host/path\"` \
7117 / `\"ssh://[user@]host/path\"` / `\"git://host/path\"` / \
7118 `\"file:///path\"` URL schemes, or the `\"git@host:path\"` \
7119 scp-style SSH form. Without this gate a malformed `:repositorio` \
7120 (whitespace from a paste-from-doc; control characters / CRLF \
7121 from a paste-from-multiline-doc; a leading `-` from a \
7122 CLI-argument-injection footgun; a missing `:` separator from a \
7123 bare `org/repo` shape git treats as a relative filesystem path) \
7124 silently landed in the rendered `Chart.yaml home:` and the \
7125 FluxCD `GitRepository.spec.url` and broke at `git clone` / \
7126 FluxCD reconcile time far from the source caixa.lisp; the gate \
7127 moves the diagnostic to the manifest layer with the offending \
7128 value named verbatim)"
7129 )]
7130 RepositorioInvalid { repositorio: String, reason: String },
7131 #[error(
7132 ":descricao is the empty string (every published caixa names \
7133 its purpose via a non-empty `:descricao` summary — the value \
7134 flows verbatim into the rendered `lareira-<nome>` Helm \
7135 chart's `Chart.yaml` `description:` field via `caixa-helm`'s \
7136 `build_chart_yaml` and into the chart `README.md` header via \
7137 `build_readme`; both consumers' `Option::unwrap_or_else` \
7138 `caixa.nome`-derived fallbacks only fire when the slot is \
7139 `None`, so an empty `Some(\"\")` silently lands as \
7140 `description: \"\"` / a blank `README.md` header in the \
7141 rendered artifacts and breaks at `helm lint` time \
7142 (`WARNING [chart.metadata.description]: description is \
7143 required` on `apiVersion: v2` charts) far from the source \
7144 caixa.lisp; omit the slot entirely to defer to the \
7145 renderer's `\"Generated chart for caixa Servico <nome>\"` / \
7146 `\"caixa Servico <nome>\"` fallbacks, or carry a non-empty \
7147 summary like `\"Canonical Rust→wasm32-wasip2 caixa \
7148 Servico.\"`)"
7149 )]
7150 DescricaoEmpty,
7151 #[error(
7152 ":descricao {descricao:?} is not a valid chart-description shape: \
7153 {reason} (the substrate consumes this string through the shared \
7154 `crate::render::is_chart_description_shape` predicate — the same \
7155 single-line-UTF-8 floor every realistic chart description carries: \
7156 1..=512 bytes, no leading or trailing whitespace, no ASCII control \
7157 characters anywhere, Unicode prose bytes accepted. The canonical \
7158 authoring shapes are short single-line summaries like `\"Canonical \
7159 Rust→wasm32-wasip2 caixa Servico.\"`, `\"Checkout flow.\"`, \
7160 `\"AWS provider caixa for tatara-lisp\"`. Without this gate a \
7161 malformed `:descricao` (paste-from-aligned-doc leading whitespace \
7162 `\" Checkout flow.\"` / trailing whitespace `\"Checkout flow. \"`; \
7163 paste-from-multiline-doc newline `\"Checkout\\nflow.\"`; \
7164 paste-from-Windows-CRLF-doc carriage return `\"Checkout\\rflow.\"`; \
7165 tab-from-aligned-doc `\"Checkout\\tflow.\"`; paste-from-binary-blob \
7166 NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
7167 `validate_descricao` + `StandardLayout::verify` and landed in the \
7168 rendered `lareira-<nome>` Helm chart's `Chart.yaml description:` \
7169 field + `README.md` header paragraph as a YAML-illegal multi-line \
7170 scalar or a silently-trimmed whitespace round-trip — every \
7171 chart-aware UI (`helm list`, `helm search`, Artifact Hub) would \
7172 render the description in a single-line column far from the source \
7173 caixa.lisp; the gate moves the diagnostic to the manifest layer \
7174 with the offending value named verbatim)"
7175 )]
7176 DescricaoInvalid { descricao: String, reason: String },
7177 #[error(
7178 ":licenca is the empty string (every published caixa names \
7179 its license via a non-empty `:licenca` SPDX expression — the \
7180 value flows verbatim into the rendered `lareira-<nome>` Helm \
7181 chart's `README.md` `## License` section via `caixa-helm`'s \
7182 `build_readme` at `caixa-helm/src/lib.rs:361`; the consumer's \
7183 `Option::unwrap_or_else(|| \"MIT\".into())` `MIT` fallback \
7184 only fires when the slot is `None`, so an empty `Some(\"\")` \
7185 silently lands as a bare trailing period in the rendered \
7186 chart `README.md` `License` section far from the source \
7187 caixa.lisp; omit the slot entirely to defer to the \
7188 renderer's `MIT` fallback, or carry a canonical SPDX \
7189 expression like `\"MIT\"`, `\"Apache-2.0\"`, \
7190 `\"Apache-2.0 OR MIT\"`)"
7191 )]
7192 LicencaEmpty,
7193 #[error(
7194 ":licenca {licenca:?} is not a valid SPDX expression shape: {reason} \
7195 (the substrate consumes this string through the shared \
7196 `crate::render::is_spdx_expression_shape` predicate — the same \
7197 alphabet-floor parser every peer per-axis value-shape gate routes \
7198 its value through; the canonical authoring shapes are single \
7199 license identifiers like `\"MIT\"`, `\"Apache-2.0\"`, `\"BSD-3-Clause\"`, \
7200 compound expressions like `\"Apache-2.0 OR MIT\"`, \
7201 `\"MIT AND BSD-3-Clause\"`, `\"(MIT OR Apache-2.0) AND ISC\"`, \
7202 license-with-exception forms like `\"Apache-2.0 WITH LLVM-exception\"`, \
7203 `+`-suffix variants like `\"GPL-2.0+\"`, and user-defined references \
7204 like `\"LicenseRef-MyLicense\"` / \
7205 `\"DocumentRef-doc:LicenseRef-MyLicense\"`. Without this gate a \
7206 malformed `:licenca` (paste-from-doc whitespace `\"MIT \"` / \
7207 `\" MIT\"`; paste-from-multiline-doc CRLF `\"MIT\\n\"`; \
7208 tab-from-aligned-doc `\"MIT\\tOR Apache-2.0\"`; non-ASCII byte from \
7209 a smart-quote paste; underscore-instead-of-hyphen typo \
7210 `\"Apache_2.0\"`; comma-instead-of-`OR`-keyword colloquial idiom \
7211 `\"MIT, Apache-2.0\"`; slash-dual-license colloquial idiom \
7212 `\"MIT/Apache-2.0\"`; semicolon-list-separator confusion \
7213 `\"MIT; Apache-2.0\"`) silently landed in the rendered chart \
7214 `README.md` `## License` section + a future SPDX-aware \
7215 `Chart.yaml license:` emitter would refuse the value at \
7216 `helm lint` time far from the source caixa.lisp; the gate moves \
7217 the diagnostic to the manifest layer with the offending value \
7218 named verbatim)"
7219 )]
7220 LicencaInvalid { licenca: String, reason: String },
7221 #[error(
7222 ":edicao is the empty string (every published caixa names \
7223 its language edition via a non-empty `:edicao` value — the \
7224 edition determines the tatara-lisp macro surface + \
7225 compatibility flags the substrate applies when building \
7226 the caixa; the canonical `Caixa::template` scaffold every \
7227 `feira init` emits carries `:edicao \"2026\"` verbatim and \
7228 every renderer-side fixture (`caixa-helm`, `caixa-flux`, \
7229 `caixa-mesh`) carries `edicao: Some(\"2026\".into())` by \
7230 construction, so an empty `Some(\"\")` silently lands as a \
7231 bare `(:edicao \"\")` line in the rendered `caixa.lisp` and \
7232 a future renderer-side consumer that folds it through \
7233 `Option::unwrap_or_else` will skip the fallback and pass the \
7234 empty edition through to the substrate's build-time edition \
7235 selector far from the source caixa.lisp; omit the slot \
7236 entirely to defer to the substrate's default edition, or \
7237 carry a canonical edition like `\"2026\"`)"
7238 )]
7239 EdicaoEmpty,
7240 #[error(
7241 ":edicao {edicao:?} is not a valid edition: {reason} (every \
7242 documented tatara-lisp edition is a 4-digit ASCII decimal \
7243 year — `\"2026\"` is the only edition currently minted; \
7244 future-introduced siblings will follow the same shape, peer \
7245 with Cargo's `[package] edition` grammar which every value \
7246 Cargo has ever accepted matches: `\"2015\"`, `\"2018\"`, \
7247 `\"2021\"`, `\"2024\"`. Without this gate the canonical \
7248 paste-from-doc footguns silently passed: a trailing space \
7249 (`\"2026 \"`) from a paste-from-doc, a CRLF (`\"2026\\n\"`) \
7250 from a paste-from-multiline-doc, a fullwidth-keyboard \
7251 look-alike (`\"2026\"`), a free-form non-year value \
7252 (`\"x\"`, `\"latest\"`, `\"nightly\"`), a leading non-digit \
7253 version-tag prefix (`\"v2026\"`, `\"e2026\"`), a \
7254 decimal-shaped pseudo-version (`\"2026.1\"`), or a \
7255 wrong-length numeric value (`\"26\"`, `\"202\"`, \
7256 `\"20260\"`) all landed as `(:edicao \"<garbage>\")` in the \
7257 rendered caixa.lisp and broke at the substrate's \
7258 build-time edition selector far from the source caixa.lisp; \
7259 omit the slot entirely to defer to the substrate's default \
7260 edition, or carry a canonical 4-digit ASCII decimal year \
7261 like `\"2026\"`)"
7262 )]
7263 EdicaoInvalid { edicao: String, reason: String },
7264}
7265
7266// Fold the five `Err(ManifestError::CodePath{Absolute,ParentEscape,
7267// NonLispExtension,NonComputeUnitYamlExtension,Duplicate} { slot,
7268// path: path.to_path_buf() })` four-line struct-variant wire-up sites at
7269// [`Caixa::validate_code_path_lists`]'s per-slot per-entry cascade onto
7270// one substrate-primitive family on the `ManifestError` envelope — the
7271// five open-coded ctor sites remaining on the `:bibliotecas` / `:exe` /
7272// `:servicos` code-path-list value-shape trajectory this envelope carries,
7273// and the family sibling of the peer [`crate::behavior::behavior_slot_path_ctors!`]
7274// (67c31ec) two-slot `{ slot: &'static str, path: PathBuf }` envelope on
7275// the [`crate::BehaviorError`] surface that keys off the exact same
7276// `(slot: &'static str, path: &Path)` argument tuple.
7277//
7278// The five wire-up sites this fold closes are the sandbox-shape
7279// absolute-path arm (`return Err(ManifestError::CodePathAbsolute { slot,
7280// path: path.to_path_buf() })` on the [`is_sandboxed_relative_path`]
7281// `PathShapeViolation::Absolute` branch), the sandbox-shape
7282// parent-escape arm (`return Err(ManifestError::CodePathParentEscape {
7283// slot, path: path.to_path_buf() })` on the sibling
7284// `PathShapeViolation::ParentEscape` branch), the LispSource
7285// terminating-extension arm (`return Err(ManifestError::CodePathNonLispExtension {
7286// slot, path: path.to_path_buf() })` on the `!is_lisp_extension(path)`
7287// branch of the `:bibliotecas` file-type gate), the ComputeUnitYaml
7288// compound-suffix arm (`return Err(ManifestError::CodePathNonComputeUnitYamlExtension
7289// { slot, path: path.to_path_buf() })` on the
7290// `!is_computeunit_yaml_extension(path)` branch of the `:servicos`
7291// file-type gate), and the cross-entry duplicate arm
7292// (`ManifestError::CodePathDuplicate { slot, path: path.to_path_buf() }`
7293// inside the closure passed to [`crate::render::insert_first_seen`]) —
7294// each opened the identical `ManifestError::CodePath* { slot,
7295// path: path.to_path_buf() }` four-line struct-literal against the same
7296// `(slot: &'static str, path: &Path)` local tuple, the exact "same
7297// block re-inlined at every consumer" shape the PRIME DIRECTIVE names
7298// as a bug. The variant discriminator is the only thing that varies
7299// between the five sites; the rest of the struct-literal is a
7300// byte-for-byte re-inline.
7301//
7302// The macro below generates one `#[must_use]` inherent constructor per
7303// variant of shape `fn <ctor>(slot: &'static str, path: &std::path::Path)
7304// -> Self`, so every wire-up site collapses onto one dispatch:
7305// `ManifestError::<ctor>(slot, path)`, byte-equal to the pre-lift
7306// struct-literal on the same `(&'static str, &Path)` fixture. The
7307// uniform two-field construction (`slot` verbatim as `&'static str`,
7308// `path.to_path_buf()`) is spelled once — inside the macro — rather
7309// than at every wire-up site. The `slot` parameter stays `&'static str`
7310// (not `&str`) so every arm continues to carry a program-lifetime
7311// `:bibliotecas` / `:exe` / `:servicos` author-key label — one of the
7312// three `&'static str` literals threaded through the outer per-slot
7313// iterator at [`Caixa::validate_code_path_lists`] — matching the
7314// enum-field type. A runtime-borrowed `&str` would silently downgrade
7315// the label lifetime and let a caller stash a non-`'static` borrow into
7316// the returned error. The `&Path` parameter accepts both
7317// `&Path` and `&PathBuf` (via Deref coercion), so every existing
7318// wire-up — each already binds `let path = Path::new(entry);` from the
7319// per-entry loop — threads through the ctor without a pre-conversion.
7320//
7321// Every future consumer that wants to construct one of these five
7322// variants outside the five in-crate wire-up sites (a deferred
7323// `feira validate --code-paths` per-caixa admission verb re-checking
7324// each declared `:bibliotecas` / `:exe` / `:servicos` entry against the
7325// same sandbox-shape + file-type + duplicate cascade, a future
7326// caixa-registry per-lacre code-path re-validator at lacre-resolve
7327// time, a per-`Caixa` overlay resolver rejecting an author-supplied
7328// code-path against a cluster-local snapshot) now reaches each variant
7329// through one call rather than re-inlining the four-line struct-literal
7330// in lockstep with the five in-crate wire-up sites.
7331macro_rules! manifest_code_path_slot_path_ctors {
7332 ($($ctor:ident => $variant:ident),* $(,)?) => {
7333 impl ManifestError {
7334 $(
7335 #[doc = concat!(
7336 "Construct a [`ManifestError::",
7337 stringify!($variant),
7338 "`] naming the offending `:bibliotecas` / `:exe` / ",
7339 "`:servicos` code-path list `slot` label and the ",
7340 "offending entry `path`. Folds the uniform `Self::",
7341 stringify!($variant),
7342 " { slot, path: path.to_path_buf() }` two-field ",
7343 "struct-literal onto one substrate primitive so ",
7344 "every wire-up on this variant at ",
7345 "[`Caixa::validate_code_path_lists`] reads through ",
7346 "one dispatch rather than the pre-lift four-line ",
7347 "open-coded block. The `slot` label threads verbatim ",
7348 "from the outer per-slot iterator (one of the three ",
7349 "code-path author-key `&'static str` consts) and the ",
7350 "`path` from the per-entry inner iterator's ",
7351 "`Path::new(entry)` binding."
7352 )]
7353 #[must_use]
7354 pub fn $ctor(slot: &'static str, path: &std::path::Path) -> Self {
7355 Self::$variant {
7356 slot,
7357 path: path.to_path_buf(),
7358 }
7359 }
7360 )*
7361 }
7362 };
7363}
7364
7365manifest_code_path_slot_path_ctors! {
7366 code_path_absolute => CodePathAbsolute,
7367 code_path_parent_escape => CodePathParentEscape,
7368 code_path_non_lisp_extension => CodePathNonLispExtension,
7369 code_path_non_computeunit_yaml_extension => CodePathNonComputeUnitYamlExtension,
7370 code_path_duplicate => CodePathDuplicate,
7371}
7372
7373#[cfg(test)]
7374mod tests {
7375 use super::*;
7376
7377 #[test]
7378 fn template_round_trips() {
7379 let src = Caixa::template("demo");
7380 let c = Caixa::from_lisp(&src).expect("template must parse");
7381 assert_eq!(c.nome, "demo");
7382 assert_eq!(c.versao, "0.1.0");
7383 assert_eq!(c.kind, CaixaKind::Biblioteca);
7384 assert_eq!(c.bibliotecas, vec!["lib/demo.lisp".to_string()]);
7385 assert!(c.deps.is_empty());
7386 assert!(c.deps_dev.is_empty());
7387 }
7388
7389 #[test]
7390 fn caixa_universal_axis_scalar_accessor_pair_is_const_fn() {
7391 // Fail-before-pass-after pin on [`Caixa::nome`] +
7392 // [`Caixa::versao`]'s `const`-eval-surface posture. Each
7393 // accessor projects the top-level manifest's per-`:nome` /
7394 // per-`:versao` [`String`] storage through the `pub const fn`
7395 // [`String::as_str`] (const-stable since Rust 1.87, well within
7396 // the workspace MSRV) — any future accidental downgrade to
7397 // non-`const` fails the corresponding `<name>_via_const_fn`
7398 // wrapper at caixa-core build time with E0015 (`cannot call
7399 // non-const method`), strictly stronger than a runtime
7400 // `assert!`. Sibling of the peer per-M2/M3-slot `String → &str`
7401 // scalar-accessor family pins on the sibling `const`-eval-
7402 // surface passes ([`crate::CaixaVersion::as_str`] at the
7403 // typed-newtype wrapper, [`crate::aplicacao::Membro::nome`] /
7404 // [`crate::aplicacao::Membro::versao_requirement`] at the M3
7405 // membership axis, [`crate::aplicacao::Entrada::hostname`] /
7406 // [`crate::aplicacao::Entrada::destination`] at the M3 ingress
7407 // axis, [`crate::supervisor::ChildSpec::nome`] /
7408 // [`crate::supervisor::ChildSpec::versao_requirement`] at the
7409 // M2 supervisor-tree axis,
7410 // [`crate::upgrade::UpgradeFromEntry::prior_versao`] at the M2
7411 // upgrade axis, [`crate::dep::Dep::nome`] /
7412 // [`crate::dep::Dep::versao_requirement`] at the dep-graph
7413 // axis, and the per-`:contratos`
7414 // [`crate::aplicacao::WitContract::source`] /
7415 // [`crate::aplicacao::WitContract::destination`] /
7416 // [`crate::aplicacao::WitContract::world_ref`] trio the
7417 // sibling pin at 279823b already anchors).
7418 const fn nome_via_const_fn(c: &Caixa) -> &str {
7419 c.nome()
7420 }
7421 const fn versao_via_const_fn(c: &Caixa) -> &str {
7422 c.versao()
7423 }
7424 let src = Caixa::template("demo");
7425 let c = Caixa::from_lisp(&src).expect("template must parse");
7426 assert_eq!(nome_via_const_fn(&c), c.nome());
7427 assert_eq!(versao_via_const_fn(&c), c.versao());
7428 assert_eq!(c.nome(), "demo");
7429 assert_eq!(c.versao(), "0.1.0");
7430 }
7431
7432 #[test]
7433 fn caixa_option_string_scalar_accessor_family_is_const_fn() {
7434 // Fail-before-pass-after pin on the five per-`Caixa`
7435 // `Option<String> → Option<&str>` scalar accessors
7436 // ([`Caixa::licenca`] / [`Caixa::repositorio`] /
7437 // [`Caixa::descricao`] / [`Caixa::edicao`] on the top-level
7438 // manifest's optional universal-axis surface, plus
7439 // [`Caixa::restart_window`] on the M2 supervisor-tree
7440 // per-`SupervisorSpec` peer raw-window-string projection axis).
7441 // Each accessor destructures the typed slot's `Option<String>`
7442 // storage through the `match &self.<field> { Some(s) =>
7443 // Some(s.as_str()), None => None }` shape — routing through
7444 // [`String::as_str`] (const-stable since Rust 1.87, well within
7445 // the workspace MSRV) rather than the non-const
7446 // [`Option::as_deref`] the pre-lift bodies carried — and any
7447 // future accidental downgrade to non-`const` fails the
7448 // corresponding `<name>_via_const_fn` wrapper at caixa-core
7449 // build time with E0015 (`cannot call non-const method`),
7450 // strictly stronger than a runtime `assert!` and strictly
7451 // stronger than a module-scope `const _: () = assert!(…)` pin
7452 // (which cannot be formed on a `&Caixa` fixture because the
7453 // type's `String` / `Option<String>` carriers rule out
7454 // `const`-context value construction; the `const fn` wrapper
7455 // is the load-bearing shape that side-steps the destructor-in-
7456 // const restriction on the value axis while still pinning the
7457 // `const`-fn posture on the callee — mirror of the sibling
7458 // [`caixa_universal_axis_scalar_accessor_pair_is_const_fn`]
7459 // pin's discipline verbatim on the peer non-`Option`
7460 // `String → &str` axis at the same struct).
7461 //
7462 // Peer of the sibling per-M2/M3-slot `Option<String> →
7463 // Option<&str>` accessor family pin
7464 // [`m3_option_string_scalar_accessor_family_is_const_fn`] on
7465 // the M3 mesh-slot atom axes ([`WitContract::endpoint`] /
7466 // [`WitContract::subject`] / [`WitContract::slot`] on the
7467 // per-`:contratos` payload-carrier trio,
7468 // [`Placement::shard_key`] / [`Placement::affinity`] on the
7469 // per-`:placement` optional-scalar pair).
7470 const fn licenca_via_const_fn(c: &Caixa) -> Option<&str> {
7471 c.licenca()
7472 }
7473 const fn repositorio_via_const_fn(c: &Caixa) -> Option<&str> {
7474 c.repositorio()
7475 }
7476 const fn descricao_via_const_fn(c: &Caixa) -> Option<&str> {
7477 c.descricao()
7478 }
7479 const fn edicao_via_const_fn(c: &Caixa) -> Option<&str> {
7480 c.edicao()
7481 }
7482 const fn restart_window_via_const_fn(c: &Caixa) -> Option<&str> {
7483 c.restart_window()
7484 }
7485 // Sweep both the `Some`-carrying arm (author-declared slot,
7486 // the byte-string projection payload) and the `None`-carrying
7487 // arm (author-omitted slot, the default-path projection) on
7488 // every accessor so the `const fn` wrapper family pins each
7489 // axis's canonical two-arm partition through the same const
7490 // dispatch as the runtime path.
7491 let mut c1 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7492 c1.licenca = Some("MIT".to_string());
7493 c1.repositorio = Some("https://github.com/pleme-io/demo".to_string());
7494 c1.descricao = Some("demo caixa".to_string());
7495 c1.edicao = Some("2024".to_string());
7496 c1.restart_window = Some("60s".to_string());
7497 assert_eq!(licenca_via_const_fn(&c1), c1.licenca());
7498 assert_eq!(repositorio_via_const_fn(&c1), c1.repositorio());
7499 assert_eq!(descricao_via_const_fn(&c1), c1.descricao());
7500 assert_eq!(edicao_via_const_fn(&c1), c1.edicao());
7501 assert_eq!(restart_window_via_const_fn(&c1), c1.restart_window());
7502 assert_eq!(c1.licenca(), Some("MIT"));
7503 assert_eq!(c1.repositorio(), Some("https://github.com/pleme-io/demo"));
7504 assert_eq!(c1.descricao(), Some("demo caixa"));
7505 assert_eq!(c1.edicao(), Some("2024"));
7506 assert_eq!(c1.restart_window(), Some("60s"));
7507 let mut c2 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7508 c2.licenca = None;
7509 c2.repositorio = None;
7510 c2.descricao = None;
7511 c2.edicao = None;
7512 c2.restart_window = None;
7513 assert_eq!(licenca_via_const_fn(&c2), None);
7514 assert_eq!(repositorio_via_const_fn(&c2), None);
7515 assert_eq!(descricao_via_const_fn(&c2), None);
7516 assert_eq!(edicao_via_const_fn(&c2), None);
7517 assert_eq!(restart_window_via_const_fn(&c2), None);
7518 }
7519
7520 #[test]
7521 fn caixa_outer_copy_return_accessor_pair_is_const_fn() {
7522 // Fail-before-pass-after pin on the two outer-[`Caixa`]
7523 // `Copy`-return accessors — [`Caixa::kind`] on the required
7524 // [`CaixaKind`] enum-discriminant axis and [`Caixa::estrategia`]
7525 // on the M2 supervisor-tree flat-spread `Option<RestartStrategy>`
7526 // axis. Both accessors project a `Copy`-carrier field
7527 // (`CaixaKind: Copy` at caixa-core/src/kind.rs:17,
7528 // `RestartStrategy: Copy` at caixa-core/src/supervisor.rs:33 →
7529 // `Option<RestartStrategy>: Copy`) by value through a bare
7530 // `self.<field>` field-access — no dispatch, no destructor, no
7531 // heap. Any future accidental downgrade to non-`const` fails
7532 // the corresponding `<name>_via_const_fn` wrapper at caixa-core
7533 // build time with E0015 (`cannot call non-const method`),
7534 // strictly stronger than a runtime `assert!` and strictly
7535 // stronger than a module-scope `const _: () = assert!(…)` pin
7536 // (which cannot be formed on a `&Caixa` fixture because the
7537 // type's `String` / `Vec` / `Option<Composite>` carriers rule
7538 // out `const`-context value construction; the `const fn`
7539 // wrapper is the load-bearing shape that side-steps the
7540 // destructor-in-const restriction on the value axis while still
7541 // pinning the `const`-fn posture on the callee — mirror of the
7542 // sibling [`caixa_universal_axis_scalar_accessor_pair_is_const_fn`]
7543 // + [`caixa_option_string_scalar_accessor_family_is_const_fn`]
7544 // pins' discipline verbatim on the peer outer-`Caixa`
7545 // `String → &str` + `Option<String> → Option<&str>` axes at the
7546 // same struct).
7547 //
7548 // Peer of the sibling per-M2/M3-slot `Copy`-return accessor pin
7549 // family on the inner-altitude nested-spec typed-slot
7550 // discriminator axes: [`crate::supervisor::SupervisorSpec::estrategia`]
7551 // + [`crate::supervisor::ChildSpec::restart`] on the M2
7552 // supervisor-tree axis (pinned at 152c868), and
7553 // [`crate::aplicacao::Placement::estrategia`] +
7554 // [`crate::aplicacao::Entrada::port`] on the M3 mesh-slot axis
7555 // (pinned at bafa004) — the outer-`Caixa` altitude is the last
7556 // unlifted altitude for the `Copy`-return-accessor family.
7557 const fn kind_via_const_fn(c: &Caixa) -> CaixaKind {
7558 c.kind()
7559 }
7560 const fn estrategia_via_const_fn(c: &Caixa) -> Option<crate::supervisor::RestartStrategy> {
7561 c.estrategia()
7562 }
7563 // Sweep every arm of both discriminant partitions the accessors
7564 // fan on — every [`CaixaKind`] variant the six-arm required
7565 // discriminant carries (Biblioteca / Binario / Servico /
7566 // Supervisor / Aplicacao / Acao) and both arms of the
7567 // [`Option<RestartStrategy>`] flat-spread supervisor-tree slot
7568 // (`Some(<strategy>)` on an author-declared supervisor and
7569 // `None` on the author-omitted default arm every non-Supervisor
7570 // caixa carries by `#[serde(default)]`) — so the `const fn`
7571 // wrapper family pins the closed-set partition through the
7572 // same const dispatch as the runtime path.
7573 let mut c1 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7574 c1.kind = CaixaKind::Servico;
7575 c1.estrategia = Some(crate::supervisor::RestartStrategy::OneForAll);
7576 assert_eq!(kind_via_const_fn(&c1), c1.kind());
7577 assert_eq!(estrategia_via_const_fn(&c1), c1.estrategia());
7578 assert_eq!(c1.kind(), CaixaKind::Servico);
7579 assert_eq!(
7580 c1.estrategia(),
7581 Some(crate::supervisor::RestartStrategy::OneForAll)
7582 );
7583 let mut c2 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7584 c2.kind = CaixaKind::Aplicacao;
7585 c2.estrategia = None;
7586 assert_eq!(kind_via_const_fn(&c2), CaixaKind::Aplicacao);
7587 assert_eq!(estrategia_via_const_fn(&c2), None);
7588 // Anchor the remaining discriminant arms so any future
7589 // reordering of [`CaixaKind`]'s six-variant enum surfaces
7590 // through the wrapper dispatch, not just through the direct
7591 // method call.
7592 for kind in [
7593 CaixaKind::Biblioteca,
7594 CaixaKind::Binario,
7595 CaixaKind::Servico,
7596 CaixaKind::Supervisor,
7597 CaixaKind::Aplicacao,
7598 CaixaKind::Acao,
7599 ] {
7600 let mut c = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7601 c.kind = kind;
7602 assert_eq!(kind_via_const_fn(&c), kind);
7603 }
7604 }
7605
7606 #[test]
7607 fn caixa_outer_string_slice_return_accessor_family_is_const_fn() {
7608 // Fail-before-pass-after pin on the five outer-[`Caixa`]
7609 // `Vec<String> → &[String]` slice-return accessors on the
7610 // universal-axis surface — [`Caixa::autores`] / [`Caixa::etiquetas`]
7611 // / [`Caixa::bibliotecas`] / [`Caixa::exe`] / [`Caixa::servicos`].
7612 // Each body is a bare `self.<field>.as_slice()` dispatch through
7613 // [`Vec::as_slice`] (const-stable since Rust 1.87, well within
7614 // the workspace MSRV). Any future accidental downgrade to
7615 // non-`const` fails the corresponding `<name>_via_const_fn`
7616 // wrapper at caixa-core build time with E0015 (`cannot call
7617 // non-const method`) — mirror of the sibling
7618 // [`caixa_outer_copy_return_accessor_pair_is_const_fn`] pin's
7619 // discipline on the peer outer-`Caixa` `Copy`-return accessor
7620 // axis, and peer of the sibling composite-carrier slice-return
7621 // pin below on the peer outer-`Caixa` composite-slice axis.
7622 const fn autores_via_const_fn(c: &Caixa) -> &[String] {
7623 c.autores()
7624 }
7625 const fn etiquetas_via_const_fn(c: &Caixa) -> &[String] {
7626 c.etiquetas()
7627 }
7628 const fn bibliotecas_via_const_fn(c: &Caixa) -> &[String] {
7629 c.bibliotecas()
7630 }
7631 const fn exe_via_const_fn(c: &Caixa) -> &[String] {
7632 c.exe()
7633 }
7634 const fn servicos_via_const_fn(c: &Caixa) -> &[String] {
7635 c.servicos()
7636 }
7637 // Sweep the empty arm (`autores` / `etiquetas` / `exe` /
7638 // `servicos` — the template's `Vec::new()` default) and the
7639 // populated arm (mutated below) on every accessor so the
7640 // `const fn` wrapper family pins each axis's two-arm partition
7641 // through the same const dispatch as the runtime path.
7642 // [`Caixa::template`] seeds `lib/demo.lisp` into `:bibliotecas`,
7643 // so that arm's "empty" fixture is the populated arm the
7644 // mutation sweep covers.
7645 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7646 assert!(autores_via_const_fn(&c_empty).is_empty());
7647 assert!(etiquetas_via_const_fn(&c_empty).is_empty());
7648 assert!(exe_via_const_fn(&c_empty).is_empty());
7649 assert!(servicos_via_const_fn(&c_empty).is_empty());
7650 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7651 c_full.autores = vec!["ada".to_string(), "erlang".to_string()];
7652 c_full.etiquetas = vec!["compounding".to_string()];
7653 c_full.bibliotecas = vec!["lib/one.lisp".to_string(), "lib/two.lisp".to_string()];
7654 c_full.exe = vec!["exe/cli.lisp".to_string()];
7655 c_full.servicos = vec!["servicos/one.computeunit.yaml".to_string()];
7656 assert_eq!(autores_via_const_fn(&c_full), c_full.autores());
7657 assert_eq!(autores_via_const_fn(&c_full), &["ada", "erlang"]);
7658 assert_eq!(etiquetas_via_const_fn(&c_full), c_full.etiquetas());
7659 assert_eq!(etiquetas_via_const_fn(&c_full), &["compounding"]);
7660 assert_eq!(bibliotecas_via_const_fn(&c_full), c_full.bibliotecas());
7661 assert_eq!(
7662 bibliotecas_via_const_fn(&c_full),
7663 &["lib/one.lisp", "lib/two.lisp"]
7664 );
7665 assert_eq!(exe_via_const_fn(&c_full), c_full.exe());
7666 assert_eq!(exe_via_const_fn(&c_full), &["exe/cli.lisp"]);
7667 assert_eq!(servicos_via_const_fn(&c_full), c_full.servicos());
7668 assert_eq!(
7669 servicos_via_const_fn(&c_full),
7670 &["servicos/one.computeunit.yaml"]
7671 );
7672 }
7673
7674 #[test]
7675 fn caixa_outer_composite_slice_return_accessor_family_is_const_fn() {
7676 // Fail-before-pass-after pin on the six outer-[`Caixa`] composite-
7677 // carrier `Vec<T> → &[T]` slice-return accessors — [`Caixa::deps`]
7678 // / [`Caixa::deps_dev`] on the dep-graph axis,
7679 // [`Caixa::upgrade_from`] on the M2 appup axis, [`Caixa::children`]
7680 // on the M2 supervisor-tree axis, and [`Caixa::membros`] /
7681 // [`Caixa::contratos`] on the M3 mesh-slot axis. Each body is a
7682 // bare `self.<field>.as_slice()` dispatch through
7683 // [`Vec::as_slice`] (const-stable since Rust 1.87, well within
7684 // the workspace MSRV) — peer of the sibling `String`-payload
7685 // slice-return pin above on the peer outer-`Caixa` universal-
7686 // axis surface, and peer of the sibling inner-composite-
7687 // altitude reference-return pin family
7688 // [`crate::aplicacao::tests::m3_aplicacao_spec_reference_return_accessor_family_is_const_fn`]
7689 // + [`crate::supervisor::tests::supervisor_children_slice_return_accessor_is_const_fn`]
7690 // + [`crate::upgrade::tests::upgrade_from_entry_instructions_slice_return_accessor_is_const_fn`]
7691 // (all pinned at 0b23e0f).
7692 const fn deps_via_const_fn(c: &Caixa) -> &[Dep] {
7693 c.deps()
7694 }
7695 const fn deps_dev_via_const_fn(c: &Caixa) -> &[Dep] {
7696 c.deps_dev()
7697 }
7698 const fn upgrade_from_via_const_fn(c: &Caixa) -> &[UpgradeFromEntry] {
7699 c.upgrade_from()
7700 }
7701 const fn children_via_const_fn(c: &Caixa) -> &[crate::supervisor::ChildSpec] {
7702 c.children()
7703 }
7704 const fn membros_via_const_fn(c: &Caixa) -> &[crate::aplicacao::Membro] {
7705 c.membros()
7706 }
7707 const fn contratos_via_const_fn(c: &Caixa) -> &[crate::aplicacao::WitContract] {
7708 c.contratos()
7709 }
7710 // Empty-arm sweep on all six composite-carrier axes — every
7711 // `Caixa::template` starts with `Vec::new()` on each.
7712 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7713 assert!(deps_via_const_fn(&c_empty).is_empty());
7714 assert!(deps_dev_via_const_fn(&c_empty).is_empty());
7715 assert!(upgrade_from_via_const_fn(&c_empty).is_empty());
7716 assert!(children_via_const_fn(&c_empty).is_empty());
7717 assert!(membros_via_const_fn(&c_empty).is_empty());
7718 assert!(contratos_via_const_fn(&c_empty).is_empty());
7719 // Populate `:membros` / `:contratos` directly via struct literals
7720 // — the parser-side validation path fans on `:kind`-gated cross-
7721 // slot invariants irrelevant to the accessor dispatch under test.
7722 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7723 c_full.membros = vec![
7724 crate::aplicacao::Membro {
7725 caixa: "demo-a".to_string(),
7726 versao: "^0.1.0".to_string(),
7727 },
7728 crate::aplicacao::Membro {
7729 caixa: "demo-b".to_string(),
7730 versao: "^0.2.0".to_string(),
7731 },
7732 ];
7733 c_full.contratos = vec![crate::aplicacao::WitContract {
7734 de: "demo-a".to_string(),
7735 para: "demo-b".to_string(),
7736 wit: "wasi:http/proxy".to_string(),
7737 endpoint: Some("/edge".to_string()),
7738 subject: None,
7739 slot: None,
7740 }];
7741 assert_eq!(membros_via_const_fn(&c_full), c_full.membros());
7742 assert_eq!(contratos_via_const_fn(&c_full), c_full.contratos());
7743 assert_eq!(membros_via_const_fn(&c_full).len(), 2);
7744 assert_eq!(contratos_via_const_fn(&c_full).len(), 1);
7745 // Alias-borrow check on the four remaining composite-carrier
7746 // slice-return arms — the wrapper's return borrow must alias the
7747 // caller's borrow so any future accessor re-routing that skips
7748 // the storage field surfaces through the assertion.
7749 assert!(std::ptr::eq(deps_via_const_fn(&c_full), c_full.deps()));
7750 assert!(std::ptr::eq(
7751 deps_dev_via_const_fn(&c_full),
7752 c_full.deps_dev()
7753 ));
7754 assert!(std::ptr::eq(
7755 upgrade_from_via_const_fn(&c_full),
7756 c_full.upgrade_from()
7757 ));
7758 assert!(std::ptr::eq(
7759 children_via_const_fn(&c_full),
7760 c_full.children()
7761 ));
7762 }
7763
7764 #[test]
7765 fn caixa_outer_option_composite_reference_return_accessor_family_is_const_fn() {
7766 // Fail-before-pass-after pin on the six outer-[`Caixa`]
7767 // `Option<Composite> → Option<&Composite>` reference-return
7768 // accessors — [`Caixa::limits`] / [`Caixa::behavior`] on the M2
7769 // Servico-runtime typed-slot axis, [`Caixa::politicas`] /
7770 // [`Caixa::placement`] / [`Caixa::entrada`] on the M3 mesh-slot
7771 // axis, and [`Caixa::ci`] on the Acao-kind typed-CI-run axis.
7772 // Each body is a bare `self.<field>.as_ref()` dispatch through
7773 // [`Option::as_ref`] (const-stable since Rust 1.83, well within
7774 // the workspace MSRV of 1.89). Any future accidental downgrade
7775 // to non-`const` fails the corresponding `<name>_via_const_fn`
7776 // wrapper at caixa-core build time with E0015 (`cannot call
7777 // non-const method`), strictly stronger than a runtime `assert!`
7778 // and strictly stronger than a module-scope `const _: () =
7779 // assert!(…)` pin (which cannot be formed on a `&Caixa` fixture
7780 // because the type's `String` / `Vec` / `Option<Composite>`
7781 // carriers rule out `const`-context value construction; the
7782 // `const fn` wrapper is the load-bearing shape that side-steps
7783 // the destructor-in-const restriction on the value axis while
7784 // still pinning the `const`-fn posture on the callee — mirror
7785 // of the sibling
7786 // [`caixa_outer_copy_return_accessor_pair_is_const_fn`] +
7787 // [`caixa_outer_string_slice_return_accessor_family_is_const_fn`] +
7788 // [`caixa_outer_composite_slice_return_accessor_family_is_const_fn`]
7789 // pins' discipline verbatim on the peer outer-`Caixa` axes at
7790 // the same struct).
7791 //
7792 // Closes the outer-`Caixa` `Option<&Composite>` composite-
7793 // reference-return sub-family — the last unlifted altitude on
7794 // the outer-`Caixa` accessor-family const-eval surface after
7795 // the sibling `Copy`-return / universal-axis-`&str` /
7796 // `Option<&str>` / `&[String]` / composite-`&[T]` pins already
7797 // closed the sibling arms at 866d1d5 / 29c5d7e / 0650f64 /
7798 // 231a968 (the last of these pins the `Vec<T> → &[T]`
7799 // composite-slice arm the six accessors here close as their
7800 // `Option<Composite> → Option<&Composite>` peer). Peer of the
7801 // sibling inner-altitude nested-spec composite-reference-return
7802 // pin family — [`crate::AplicacaoSpec::politicas`] /
7803 // [`crate::AplicacaoSpec::placement`] /
7804 // [`crate::AplicacaoSpec::entrada`] on the inner
7805 // [`crate::AplicacaoSpec`] altitude (already `pub const fn`
7806 // per 0b23e0f), and the outer-`Caixa` altitude here now carries
7807 // the same shape so both altitudes of the reference-return
7808 // discipline (per-`Caixa` outer-slot presence + per-
7809 // `AplicacaoSpec` inner-slot presence) route through one typed
7810 // const dispatch on the substrate primitive.
7811 const fn limits_via_const_fn(c: &Caixa) -> Option<&LimitsSpec> {
7812 c.limits()
7813 }
7814 const fn behavior_via_const_fn(c: &Caixa) -> Option<&crate::BehaviorSpec> {
7815 c.behavior()
7816 }
7817 const fn politicas_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::MeshPolicy> {
7818 c.politicas()
7819 }
7820 const fn placement_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::Placement> {
7821 c.placement()
7822 }
7823 const fn entrada_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::Entrada> {
7824 c.entrada()
7825 }
7826 const fn ci_via_const_fn(c: &Caixa) -> Option<&canteiro_types::CiRun> {
7827 c.ci()
7828 }
7829 // Both-arm sweep on every accessor: the `None` author-omitted
7830 // arm (template default — no M2/M3/CI slot declared) and the
7831 // `Some(<composite>)` authored arm (mutated below via struct-
7832 // literal seeds, side-stepping the parser-side `:kind`-gated
7833 // cross-slot invariants irrelevant to the accessor dispatch
7834 // under test). Both arms route through the `const fn` wrapper
7835 // family so the two-arm `Option` partition is pinned through
7836 // the same const dispatch as the runtime path.
7837 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7838 assert!(limits_via_const_fn(&c_empty).is_none());
7839 assert!(behavior_via_const_fn(&c_empty).is_none());
7840 assert!(politicas_via_const_fn(&c_empty).is_none());
7841 assert!(placement_via_const_fn(&c_empty).is_none());
7842 assert!(entrada_via_const_fn(&c_empty).is_none());
7843 assert!(ci_via_const_fn(&c_empty).is_none());
7844 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7845 c_full.limits = Some(LimitsSpec::default());
7846 c_full.behavior = Some(crate::BehaviorSpec::default());
7847 c_full.politicas = Some(crate::aplicacao::MeshPolicy::default());
7848 c_full.placement = Some(crate::aplicacao::Placement::default());
7849 c_full.entrada = Some(crate::aplicacao::Entrada {
7850 host: "demo.quero.cloud".to_string(),
7851 para: "demo".to_string(),
7852 paths: Vec::new(),
7853 port: crate::aplicacao::DEFAULT_SERVICO_PORT,
7854 });
7855 c_full.ci = Some(canteiro_types::CiRun {
7856 workspace: "pleme-io".into(),
7857 repo: "caixa".into(),
7858 nodes: vec![],
7859 });
7860 assert!(limits_via_const_fn(&c_full).is_some());
7861 assert!(behavior_via_const_fn(&c_full).is_some());
7862 assert!(politicas_via_const_fn(&c_full).is_some());
7863 assert!(placement_via_const_fn(&c_full).is_some());
7864 assert!(entrada_via_const_fn(&c_full).is_some());
7865 assert!(ci_via_const_fn(&c_full).is_some());
7866 // Alias-borrow check on every arm: the wrapper's inner-`Option`
7867 // reference must alias the caller's borrow so any future accessor
7868 // re-routing that skips the storage field surfaces through the
7869 // assertion.
7870 assert!(std::ptr::eq(
7871 limits_via_const_fn(&c_full).unwrap(),
7872 c_full.limits().unwrap()
7873 ));
7874 assert!(std::ptr::eq(
7875 behavior_via_const_fn(&c_full).unwrap(),
7876 c_full.behavior().unwrap()
7877 ));
7878 assert!(std::ptr::eq(
7879 politicas_via_const_fn(&c_full).unwrap(),
7880 c_full.politicas().unwrap()
7881 ));
7882 assert!(std::ptr::eq(
7883 placement_via_const_fn(&c_full).unwrap(),
7884 c_full.placement().unwrap()
7885 ));
7886 assert!(std::ptr::eq(
7887 entrada_via_const_fn(&c_full).unwrap(),
7888 c_full.entrada().unwrap()
7889 ));
7890 assert!(std::ptr::eq(
7891 ci_via_const_fn(&c_full).unwrap(),
7892 c_full.ci().unwrap()
7893 ));
7894 }
7895
7896 #[test]
7897 fn register_populates_registry() {
7898 Caixa::register().expect("first register call in this test process must succeed");
7899 let kws = tatara_lisp::domain::registered_keywords();
7900 assert!(kws.contains(&"defcaixa"));
7901 }
7902
7903 #[test]
7904 fn to_lisp_round_trips() {
7905 let src = Caixa::template("demo");
7906 let c1 = Caixa::from_lisp(&src).unwrap();
7907 let emitted = c1.to_lisp();
7908 let c2 = Caixa::from_lisp(&emitted).expect("emitted lisp parses back");
7909 assert_eq!(c1, c2);
7910 }
7911
7912 // ── DialetoEstrangeiro carries a single typed axis ────────────────────
7913 //
7914 // The compounding pin: the variant stores only the typed
7915 // [`crate::dialeto::CaixaDialeto`], and every user-facing byte-string
7916 // (canonical keyword, description, consumer) routes through the enum's
7917 // own accessors at Display time. Prior to that closure the variant
7918 // carried each accessor's return value as a stored `&'static str`
7919 // snapshot alongside `dialeto`; a caller could construct the variant
7920 // with a snapshot that drifted from what `dialeto`'s accessors would
7921 // return, and every downstream user-facing projection would silently
7922 // disagree with the classification. Storing only the axis makes the
7923 // drift structurally impossible.
7924
7925 #[test]
7926 fn dialeto_estrangeiro_variant_carries_only_the_typed_dialeto_axis() {
7927 // Single-field construction is the whole compounding shape — a
7928 // future re-introduction of a snapshot field (a `palavra_canonica:
7929 // &'static str`, a stored `descricao:`, a stored `consumidor:`)
7930 // would re-open the drift surface and this construction would fail
7931 // to compile with "missing field" until every snapshot was seeded
7932 // at the call site again. The compile-time guarantee is the
7933 // invariant; the assertion below only witnesses that the
7934 // construction is well-formed after the closure.
7935 let err = LeituraError::DialetoEstrangeiro {
7936 dialeto: crate::dialeto::CaixaDialeto::Molde,
7937 };
7938 assert!(matches!(
7939 err,
7940 LeituraError::DialetoEstrangeiro {
7941 dialeto: crate::dialeto::CaixaDialeto::Molde,
7942 }
7943 ));
7944 }
7945
7946 #[test]
7947 fn dialeto_estrangeiro_display_routes_through_typed_dialeto_accessors() {
7948 // For every foreign-dialect classification the variant surfaces —
7949 // [`crate::dialeto::CaixaDialeto::Molde`] and
7950 // [`crate::dialeto::CaixaDialeto::MoldePosicional`], the two
7951 // variants [`Caixa::from_lisp`] raises this error for — the
7952 // rendered [`std::fmt::Display`] byte-string must interpolate each
7953 // typed accessor's return verbatim. A future re-introduction of a
7954 // stored `&'static str` snapshot alongside `dialeto` that Display
7955 // read instead of the accessor would fail this pin as soon as the
7956 // two disagreed; a future accessor rebrand (a per-dialect
7957 // consumer rename, a canonical-keyword shift once the substrate
7958 // migration named in [`crate::dialeto`] completes) reaches every
7959 // consumer through one typed dispatch and this pin verifies the
7960 // display path is one of them.
7961 for d in [
7962 crate::dialeto::CaixaDialeto::Molde,
7963 crate::dialeto::CaixaDialeto::MoldePosicional,
7964 ] {
7965 let rendered = LeituraError::DialetoEstrangeiro { dialeto: d }.to_string();
7966 assert!(
7967 rendered.contains(d.palavra_canonica()),
7968 "Display must interpolate `dialeto.palavra_canonica()` \
7969 verbatim — a stored snapshot would silently drift from \
7970 the typed accessor. dialect: {d}, rendered: {rendered:?}"
7971 );
7972 assert!(
7973 rendered.contains(d.descricao()),
7974 "Display must interpolate `dialeto.descricao()` verbatim. \
7975 dialect: {d}, rendered: {rendered:?}"
7976 );
7977 assert!(
7978 rendered.contains(d.consumidor()),
7979 "Display must interpolate `dialeto.consumidor()` verbatim. \
7980 dialect: {d}, rendered: {rendered:?}"
7981 );
7982 }
7983 }
7984
7985 #[test]
7986 fn from_lisp_rejects_molde_dialect_via_typed_variant() {
7987 // The end-to-end pin the compounding closure defends: a
7988 // Molde-dialect source lands as [`LeituraError::DialetoEstrangeiro`]
7989 // carrying [`crate::dialeto::CaixaDialeto::Molde`], and the
7990 // rendered Display byte-string names the Molde accessors'
7991 // returns verbatim. Any future path that constructed the variant
7992 // with a mismatched snapshot (a stored `palavra_canonica:
7993 // "defcaixa"` on a `Molde` classification) would land Display
7994 // pointing at `defcaixa` while the typed axis said `Molde` — the
7995 // exact drift the closure removes.
7996 let src = r#"
7997 (defcaixa
7998 :name "x"
7999 :kind :Biblioteca
8000 :ecosystem :rust-single-crate
8001 :package {:name "x" :version "0.1.0"})
8002 "#;
8003 let err = Caixa::from_lisp(src).expect_err("Molde dialect must not parse as Pacote");
8004 match err {
8005 LeituraError::DialetoEstrangeiro { dialeto } => {
8006 assert_eq!(dialeto, crate::dialeto::CaixaDialeto::Molde);
8007 let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
8008 assert!(rendered.contains(dialeto.palavra_canonica()));
8009 assert!(rendered.contains(dialeto.consumidor()));
8010 assert!(rendered.contains(dialeto.descricao()));
8011 }
8012 other => panic!("expected DialetoEstrangeiro, got {other:?}"),
8013 }
8014 }
8015
8016 #[test]
8017 fn from_lisp_rejects_molde_posicional_dialect_via_typed_variant() {
8018 // Coverage pin for the [`crate::dialeto::CaixaDialeto::MoldePosicional`]
8019 // arm of the [`Caixa::from_lisp`] foreign-dialect gate — the
8020 // positional-arity `defmolde` form written under a `(defcaixa …)`
8021 // head (`(defcaixa todoku-go :kind :Biblioteca :ecosystem :go
8022 // …)`). Pre-lift this arm rode the same `foreign =>` wildcard
8023 // the [`crate::dialeto::CaixaDialeto::Molde`] sibling arm rode,
8024 // so no test exercised the positional-arity path through
8025 // `Caixa::from_lisp` specifically; the sibling
8026 // [`from_lisp_rejects_molde_dialect_via_typed_variant`] only
8027 // covered [`crate::dialeto::CaixaDialeto::Molde`]. Post-lift the
8028 // two arms route through the lifted
8029 // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
8030 // typed predicate — the same predicate the pre-lift `foreign =>`
8031 // wildcard resolved to today — and this pin makes the
8032 // positional-arity arm's byte-shape at the gate explicit rather
8033 // than implied by wildcard-absorption. A future regression that
8034 // silently reordered [`crate::dialeto::CaixaDialeto::is_molde_family`]'s
8035 // arm-set (dropped [`crate::dialeto::CaixaDialeto::MoldePosicional`]
8036 // from the two-arity closure) would fail this pin at caixa-core
8037 // test time rather than surfacing far from the change as a
8038 // `caixa.lisp` carrying a `(defcaixa todoku-go :ecosystem :go
8039 // …)` silently parsing past the derive.
8040 let src = r#"
8041 (defcaixa todoku-go
8042 :kind :Biblioteca
8043 :ecosystem :go
8044 :package {:name "todoku-go" :version "0.3.0"})
8045 "#;
8046 let err =
8047 Caixa::from_lisp(src).expect_err("MoldePosicional dialect must not parse as Pacote");
8048 match err {
8049 LeituraError::DialetoEstrangeiro { dialeto } => {
8050 assert_eq!(
8051 dialeto,
8052 crate::dialeto::CaixaDialeto::MoldePosicional,
8053 "DialetoEstrangeiro must carry the MoldePosicional \
8054 variant verbatim — the positional-arity `defmolde` \
8055 form under a `(defcaixa …)` head is the \
8056 `MoldePosicional` arm's canonical byte-shape"
8057 );
8058 let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
8059 assert!(
8060 rendered.contains(dialeto.palavra_canonica()),
8061 "Display must interpolate `dialeto.palavra_canonica()` \
8062 verbatim on the MoldePosicional arm; rendered: \
8063 {rendered:?}"
8064 );
8065 assert!(
8066 rendered.contains(dialeto.consumidor()),
8067 "Display must interpolate `dialeto.consumidor()` \
8068 verbatim on the MoldePosicional arm; rendered: \
8069 {rendered:?}"
8070 );
8071 assert!(
8072 rendered.contains(dialeto.descricao()),
8073 "Display must interpolate `dialeto.descricao()` \
8074 verbatim on the MoldePosicional arm; rendered: \
8075 {rendered:?}"
8076 );
8077 }
8078 other => panic!("expected DialetoEstrangeiro, got {other:?}"),
8079 }
8080 }
8081
8082 #[test]
8083 fn from_lisp_dialect_gate_dispatches_through_caixa_dialeto_is_molde_family_predicate() {
8084 // Load-bearing byte-parity pin: for every arm in
8085 // [`crate::dialeto::CaixaDialeto::ALL`], the
8086 // [`Caixa::from_lisp`] foreign-dialect gate's DialetoEstrangeiro
8087 // partition must agree with the lifted
8088 // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
8089 // typed predicate — i.e. from_lisp raises
8090 // [`LeituraError::DialetoEstrangeiro`] carrying `d` iff
8091 // `d.is_molde_family()` returns `true`, and does NOT raise
8092 // [`LeituraError::DialetoEstrangeiro`] on any arm where the
8093 // predicate returns `false` (the arm's source falls through to
8094 // the derive — parses cleanly on
8095 // [`crate::dialeto::CaixaDialeto::Pacote`], surfaces a
8096 // [`LeituraError::Leitura`] on
8097 // [`crate::dialeto::CaixaDialeto::Desconhecido`]).
8098 //
8099 // Pre-lift the gate hand-rolled a three-arm match
8100 // (`Pacote => {}`, `Desconhecido => {}`, `foreign => Err(…)`)
8101 // whose `foreign =>` wildcard expressed no compile-time link
8102 // back to the substrate primitive's arm-family; a future fifth
8103 // dialect the [`crate::dialeto`] module doc's "third dialect"
8104 // hazard actualises would fall silently onto the wildcard
8105 // regardless of whether it belonged to the `defmolde` family or
8106 // to a distinct `defcaixa`-family. Post-lift the partition
8107 // resolves through
8108 // [`crate::dialeto::CaixaDialeto::is_molde_family`]'s single
8109 // typed dispatch, and this pin refuses any future regression
8110 // that silently split the from_lisp partition from the typed
8111 // predicate — the two paths now migrate as one on any future
8112 // arm addition.
8113 //
8114 // Sibling in shape to the peer
8115 // [`crate::dialeto::tests::caixa_dialeto_is_molde_family_agrees_with_palavra_canonica_defmolde_projection`]
8116 // (e9d2315) that pins the same byte-parity between
8117 // [`crate::dialeto::CaixaDialeto::is_molde_family`] and the
8118 // sibling [`crate::dialeto::CaixaDialeto::palavra_canonica`]
8119 // `== "defmolde"` classifier — extends the discipline from the
8120 // two paths within the [`crate::dialeto`] primitive onto the
8121 // third external consumer of the `defmolde`-family partition
8122 // (the [`Caixa::from_lisp`] gate that raises
8123 // [`LeituraError::DialetoEstrangeiro`]).
8124 let fixtures: &[(crate::dialeto::CaixaDialeto, &str)] = &[
8125 (
8126 crate::dialeto::CaixaDialeto::Pacote,
8127 r#"
8128 (defcaixa
8129 :nome "checkout"
8130 :versao "0.1.0"
8131 :kind Biblioteca
8132 :edicao "2026"
8133 :descricao "canonical Pacote source"
8134 :autores ()
8135 :etiquetas ()
8136 :deps ()
8137 :deps-dev ()
8138 :bibliotecas ("lib/checkout.lisp"))
8139 "#,
8140 ),
8141 (
8142 crate::dialeto::CaixaDialeto::Molde,
8143 r#"
8144 (defcaixa
8145 :name "base64"
8146 :kind :Biblioteca
8147 :ecosystem :rust-single-crate
8148 :package {:name "base64" :version "0.22.1"}
8149 :workflows [:auto-release])
8150 "#,
8151 ),
8152 (
8153 crate::dialeto::CaixaDialeto::MoldePosicional,
8154 r#"
8155 (defcaixa todoku-go
8156 :kind :Biblioteca
8157 :ecosystem :go
8158 :package {:name "todoku-go" :version "0.3.0"})
8159 "#,
8160 ),
8161 (
8162 crate::dialeto::CaixaDialeto::Desconhecido,
8163 r#"(defcaixa :licenca "MIT")"#,
8164 ),
8165 ];
8166
8167 // Coverage: every arm in [`crate::dialeto::CaixaDialeto::ALL`]
8168 // must appear in the fixture table so the pin's arm-set stays
8169 // synchronised with the enum's arm-set. Fails at test time if a
8170 // future fifth arm added to [`crate::dialeto::CaixaDialeto`]
8171 // (with a corresponding `is_molde_family` return) forgot to
8172 // extend this fixture table with a canonical source for the new
8173 // arm — the pin cannot cover an arm it has no source for.
8174 for &expected in crate::dialeto::CaixaDialeto::ALL {
8175 assert!(
8176 fixtures.iter().any(|(d, _)| *d == expected),
8177 "fixture table must carry a canonical source for every \
8178 CaixaDialeto arm; missing: {expected:?}"
8179 );
8180 }
8181
8182 for &(expected_dialect, src) in fixtures {
8183 let classified = crate::dialeto::classify(src.trim()).unwrap_or_else(|err| {
8184 panic!(
8185 "fixture source for {expected_dialect:?} must classify \
8186 cleanly, got err: {err:?}"
8187 )
8188 });
8189 assert_eq!(
8190 classified, expected_dialect,
8191 "fixture source for {expected_dialect:?} must classify as \
8192 {expected_dialect:?} (drift here defeats the byte-parity \
8193 pin below — a source labelled for one arm but classifying \
8194 as another would silently satisfy or violate the pin for \
8195 the wrong reason)"
8196 );
8197
8198 let outcome = Caixa::from_lisp(src);
8199 match (expected_dialect.is_molde_family(), &outcome) {
8200 (true, Err(LeituraError::DialetoEstrangeiro { dialeto })) => {
8201 assert_eq!(
8202 *dialeto, expected_dialect,
8203 "DialetoEstrangeiro must carry the same typed arm \
8204 the classifier returned — a drift here would let \
8205 from_lisp raise the error while pointing at the \
8206 wrong dialect (e.g. rejecting a \
8207 MoldePosicional source as Molde). arm: \
8208 {expected_dialect:?}"
8209 );
8210 }
8211 (true, other) => panic!(
8212 "arm {expected_dialect:?} has is_molde_family() = true \
8213 so from_lisp must raise DialetoEstrangeiro carrying \
8214 {expected_dialect:?}; got: {other:?}"
8215 ),
8216 (false, Err(LeituraError::DialetoEstrangeiro { dialeto })) => panic!(
8217 "arm {expected_dialect:?} has is_molde_family() = false \
8218 so from_lisp must NOT raise DialetoEstrangeiro; got \
8219 one carrying: {dialeto:?}. This means the typed \
8220 predicate and the from_lisp partition disagree on \
8221 this arm — exactly the drift this pin refuses."
8222 ),
8223 (false, _) => {
8224 // A non-molde arm's source falls through to the
8225 // derive: Pacote sources parse to Ok(_); Desconhecido
8226 // sources surface as LeituraError::Leitura from the
8227 // derive's own unknown-keyword rejection. Either
8228 // shape is acceptable here — the pin's promise is
8229 // narrower: "no DialetoEstrangeiro on
8230 // is_molde_family() == false".
8231 }
8232 }
8233 }
8234 }
8235
8236 // ── M2 typed-substrate slot tests (limits, behavior, upgrade-from, supervisor) ──
8237
8238 #[test]
8239 fn limits_round_trip_via_json() {
8240 use crate::LimitsSpec;
8241 use std::time::Duration;
8242 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8243 c.limits = Some(LimitsSpec {
8244 memory: Some(64 * 1024 * 1024),
8245 fuel: Some(1_000_000),
8246 wall_clock: Some(Duration::from_secs(30)),
8247 cpu: Some(500),
8248 });
8249 let json = serde_json::to_string(&c).unwrap();
8250 assert!(json.contains("\"limits\""));
8251 assert!(json.contains("\"64MiB\""));
8252 assert!(json.contains("\"30s\""));
8253 assert!(json.contains("\"500m\""));
8254 let back: Caixa = serde_json::from_str(&json).unwrap();
8255 assert_eq!(c.limits, back.limits);
8256 }
8257
8258 #[test]
8259 fn behavior_round_trip_via_json() {
8260 use crate::BehaviorSpec;
8261 use std::path::PathBuf;
8262 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8263 c.behavior = Some(BehaviorSpec {
8264 on_init: Some(PathBuf::from("lib/init.lisp")),
8265 on_call: Some(PathBuf::from("lib/handlers.lisp")),
8266 ..Default::default()
8267 });
8268 let json = serde_json::to_string(&c).unwrap();
8269 let back: Caixa = serde_json::from_str(&json).unwrap();
8270 assert_eq!(c.behavior, back.behavior);
8271 }
8272
8273 #[test]
8274 fn upgrade_from_round_trip_via_json() {
8275 use crate::{UpgradeFromEntry, UpgradeInstruction};
8276 use std::path::PathBuf;
8277 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8278 c.upgrade_from = vec![UpgradeFromEntry {
8279 from: "0.1.0".into(),
8280 instructions: vec![
8281 UpgradeInstruction::LoadModule {
8282 module: "demo".into(),
8283 },
8284 UpgradeInstruction::StateChange {
8285 script: PathBuf::from("lib/migrations/v01-to-v02.lisp"),
8286 },
8287 UpgradeInstruction::SoftPurge {
8288 module: "demo-old".into(),
8289 },
8290 ],
8291 }];
8292 let json = serde_json::to_string(&c).unwrap();
8293 let back: Caixa = serde_json::from_str(&json).unwrap();
8294 assert_eq!(c.upgrade_from, back.upgrade_from);
8295 }
8296
8297 #[test]
8298 fn supervisor_view_returns_typed_shape() {
8299 use crate::{ChildSpec, RestartPolicy, RestartStrategy};
8300 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
8301 c.kind = CaixaKind::Supervisor;
8302 c.bibliotecas.clear();
8303 c.estrategia = Some(RestartStrategy::OneForOne);
8304 c.max_restarts = Some(5);
8305 c.restart_window = Some("60s".into());
8306 c.children = vec![ChildSpec {
8307 caixa: "worker".into(),
8308 versao: "^0.1".into(),
8309 restart: RestartPolicy::Permanent,
8310 }];
8311 let view = c.supervisor_view().expect("Supervisor kind has a view");
8312 assert_eq!(view.estrategia, RestartStrategy::OneForOne);
8313 assert_eq!(view.max_restarts, 5);
8314 assert_eq!(
8315 view.restart_window,
8316 Some(std::time::Duration::from_secs(60))
8317 );
8318 assert_eq!(view.children.len(), 1);
8319 view.validate().unwrap();
8320 }
8321
8322 #[test]
8323 fn supervisor_view_none_for_non_supervisor_kinds() {
8324 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8325 assert!(c.supervisor_view().is_none());
8326 }
8327
8328 #[test]
8329 fn declared_mesh_slots_empty_for_bare_caixa() {
8330 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8331 assert!(c.declared_mesh_slots().is_empty());
8332 }
8333
8334 #[test]
8335 fn declared_mesh_slots_reports_only_set_slots_in_canonical_order() {
8336 use crate::{Entrada, Membro};
8337 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8338 // Set a non-adjacent pair (:membros + :entrada) to pin that the
8339 // canonical declaration order is preserved regardless of which
8340 // subset is populated.
8341 c.membros = vec![Membro {
8342 caixa: "a".into(),
8343 versao: "^0.1".into(),
8344 }];
8345 c.entrada = Some(Entrada {
8346 host: "x.example.com".into(),
8347 para: "a".into(),
8348 paths: vec![],
8349 port: 8080,
8350 });
8351 assert_eq!(
8352 c.declared_mesh_slots(),
8353 vec![
8354 crate::render::M3_AUTHOR_KEY_MEMBROS,
8355 crate::render::M3_AUTHOR_KEY_ENTRADA,
8356 ]
8357 );
8358 }
8359
8360 #[test]
8361 fn m3_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
8362 // Scalar-value pin: the five author-facing kebab-case labels the
8363 // `(defcaixa … :<slot> (…))` surface admits on the M3 top-level
8364 // mesh slot axis, one arm per typed slot. Mirrors the peer
8365 // scalar-value pin the sibling
8366 // [`crate::M2_AUTHOR_KEY_LIMITS`] /
8367 // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
8368 // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] M2 top-level slot consts
8369 // carry (f49c8b0), so both altitudes of the typed-slot algebra
8370 // (per-Servico M2 + per-Aplicacao M3) share the same
8371 // "one canonical byte-string per arm" discipline. A future
8372 // rebrand (`:membros` → `:members`, `:contratos` → `:contracts`,
8373 // `:politicas` → `:policies`, `:placement` → `:distribution`,
8374 // `:entrada` → `:ingress`) lands as an edit to exactly one const,
8375 // and every consumer that reaches for the label picks it up at
8376 // build time rather than at runtime as a downstream mismatch.
8377 assert_eq!(crate::render::M3_AUTHOR_KEY_MEMBROS, ":membros");
8378 assert_eq!(crate::render::M3_AUTHOR_KEY_CONTRATOS, ":contratos");
8379 assert_eq!(crate::render::M3_AUTHOR_KEY_POLITICAS, ":politicas");
8380 assert_eq!(crate::render::M3_AUTHOR_KEY_PLACEMENT, ":placement");
8381 assert_eq!(crate::render::M3_AUTHOR_KEY_ENTRADA, ":entrada");
8382 }
8383
8384 #[test]
8385 fn declared_mesh_slots_route_through_lifted_m3_author_key_consts() {
8386 // Production-through-const pin: the five per-arm labels the
8387 // [`Caixa::declared_mesh_slots`] tagger pushes onto its return
8388 // `Vec` route through the lifted
8389 // [`crate::M3_AUTHOR_KEY_MEMBROS`] /
8390 // [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
8391 // [`crate::M3_AUTHOR_KEY_POLITICAS`] /
8392 // [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
8393 // [`crate::M3_AUTHOR_KEY_ENTRADA`] consts, in canonical
8394 // declaration order. A future re-order or drift at the tagger
8395 // (a rename that reaches the tagger but not the const, or vice
8396 // versa) surfaces here at build time rather than at runtime as
8397 // a [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
8398 // `slots: <stale-kebab-case>` diagnostic far from the rename's
8399 // commit. Mirror of the peer
8400 // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
8401 // pin (f49c8b0) on the sibling per-Servico M2 top-level slot
8402 // axis.
8403 use crate::{Entrada, Membro, MeshPolicy, Placement, PlacementStrategy, WitContract};
8404 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8405 c.membros = vec![Membro {
8406 caixa: "a".into(),
8407 versao: "^0.1".into(),
8408 }];
8409 c.contratos = vec![WitContract {
8410 de: "a".into(),
8411 para: "a".into(),
8412 wit: "wasi:http/proxy".into(),
8413 endpoint: Some("/x".into()),
8414 subject: None,
8415 slot: None,
8416 }];
8417 c.politicas = Some(MeshPolicy::default());
8418 c.placement = Some(Placement {
8419 estrategia: PlacementStrategy::Replicated,
8420 clusters: vec!["rio".into()],
8421 affinity: None,
8422 shard_key: None,
8423 });
8424 c.entrada = Some(Entrada {
8425 host: "x.example.com".into(),
8426 para: "a".into(),
8427 paths: vec![],
8428 port: 8080,
8429 });
8430 assert_eq!(
8431 c.declared_mesh_slots(),
8432 vec![
8433 crate::render::M3_AUTHOR_KEY_MEMBROS,
8434 crate::render::M3_AUTHOR_KEY_CONTRATOS,
8435 crate::render::M3_AUTHOR_KEY_POLITICAS,
8436 crate::render::M3_AUTHOR_KEY_PLACEMENT,
8437 crate::render::M3_AUTHOR_KEY_ENTRADA,
8438 ]
8439 );
8440 }
8441
8442 #[test]
8443 fn declared_supervisor_slots_empty_for_bare_caixa() {
8444 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8445 assert!(c.declared_supervisor_slots().is_empty());
8446 }
8447
8448 #[test]
8449 fn declared_supervisor_slots_reports_only_set_slots_in_canonical_order() {
8450 use crate::RestartStrategy;
8451 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8452 // Set a non-adjacent pair (:estrategia + :restart-window) to pin
8453 // that the canonical declaration order is preserved regardless
8454 // of which subset is populated.
8455 c.estrategia = Some(RestartStrategy::OneForOne);
8456 c.restart_window = Some("60s".into());
8457 assert_eq!(
8458 c.declared_supervisor_slots(),
8459 vec![
8460 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
8461 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
8462 ]
8463 );
8464 }
8465
8466 #[test]
8467 fn supervisor_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
8468 // Scalar-value pin: the four author-facing kebab-case labels the
8469 // `(defcaixa … :<slot> (…))` surface admits on the Supervisor
8470 // supervision-tree slot axis, one arm per typed slot. Mirrors the
8471 // peer scalar-value pins the sibling
8472 // [`crate::render::M2_AUTHOR_KEY_LIMITS`] /
8473 // [`crate::render::M2_AUTHOR_KEY_BEHAVIOR`] /
8474 // [`crate::render::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot
8475 // consts and [`crate::render::M3_AUTHOR_KEY_MEMBROS`] etc.
8476 // top-level M3 slot consts carry, so all three kind-scoped
8477 // typed-slot-family author-facing-label axes route through one
8478 // canonical per-arm declaration. A future rebrand
8479 // (`:estrategia` → `:strategy` for English uniformity,
8480 // `:max-restarts` → `:max-intensity` matching Erlang/OTP's
8481 // `MaxIntensity` name, `:restart-window` → `:period` matching
8482 // OTP's `Period` name, `:children` → `:workers` matching Elixir
8483 // idiom) lands as an edit to exactly one const, and every
8484 // consumer that reaches for the label picks it up at build time
8485 // rather than at runtime as a downstream mismatch.
8486 assert_eq!(
8487 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
8488 ":estrategia"
8489 );
8490 assert_eq!(
8491 crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
8492 ":max-restarts"
8493 );
8494 assert_eq!(
8495 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
8496 ":restart-window"
8497 );
8498 assert_eq!(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN, ":children");
8499 }
8500
8501 #[test]
8502 fn declared_supervisor_slots_route_through_lifted_supervisor_author_key_consts() {
8503 // Production-through-const pin: the four per-arm labels the
8504 // [`Caixa::declared_supervisor_slots`] tagger pushes onto its
8505 // return `Vec` route through the lifted
8506 // [`crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`] /
8507 // [`crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`] /
8508 // [`crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW`] /
8509 // [`crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN`] consts, in
8510 // canonical declaration order. A future re-order or drift at the
8511 // tagger (a rename that reaches the tagger but not the const, or
8512 // vice versa) surfaces here at build time rather than at runtime
8513 // as a [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
8514 // `slots: <stale-kebab-case>` diagnostic far from the rename's
8515 // commit. Mirror of the peer
8516 // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
8517 // (f49c8b0) and
8518 // [`declared_mesh_slots_route_through_lifted_m3_author_key_consts`]
8519 // (882f498) pins on the sibling M2 / M3 top-level slot axes.
8520 use crate::{ChildSpec, RestartPolicy, RestartStrategy};
8521 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8522 c.estrategia = Some(RestartStrategy::OneForOne);
8523 c.max_restarts = Some(5);
8524 c.restart_window = Some("60s".into());
8525 c.children = vec![ChildSpec {
8526 caixa: "worker".into(),
8527 versao: "^0.1".into(),
8528 restart: RestartPolicy::Permanent,
8529 }];
8530 assert_eq!(
8531 c.declared_supervisor_slots(),
8532 vec![
8533 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
8534 crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
8535 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
8536 crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN,
8537 ]
8538 );
8539 }
8540
8541 #[test]
8542 fn declared_servico_slots_empty_for_bare_caixa() {
8543 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8544 assert!(c.declared_servico_slots().is_empty());
8545 }
8546
8547 #[test]
8548 fn declared_servico_slots_reports_only_set_slots_in_canonical_order() {
8549 use crate::{UpgradeFromEntry, UpgradeInstruction};
8550 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8551 // Set a non-adjacent pair (:limits + :upgrade-from) to pin that
8552 // the canonical declaration order is preserved regardless of
8553 // which subset is populated.
8554 c.limits = Some(crate::LimitsSpec {
8555 fuel: Some(1_000_000),
8556 ..Default::default()
8557 });
8558 c.upgrade_from = vec![UpgradeFromEntry {
8559 from: "0.1.0".into(),
8560 instructions: vec![UpgradeInstruction::Restart],
8561 }];
8562 assert_eq!(
8563 c.declared_servico_slots(),
8564 vec![
8565 crate::render::M2_AUTHOR_KEY_LIMITS,
8566 crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
8567 ]
8568 );
8569 }
8570
8571 #[test]
8572 fn m2_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
8573 // Scalar-value pin: the three author-facing kebab-case labels
8574 // the `(defcaixa … :<slot> (…))` surface admits on the M2
8575 // top-level slot axis, one arm per typed slot. Mirrors the peer
8576 // scalar-value pin the sibling renderer-side
8577 // [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
8578 // [`crate::M2_KEY_UPGRADE_FROM`] camelCase overlay-container
8579 // consts carry, so both halves of the M2 top-level slot dual
8580 // axis (author-facing kebab-case label + renderer-side
8581 // camelCase overlay-container wire key) route through one
8582 // canonical per-arm declaration. A future rebrand
8583 // (`:limits` → `:sandbox` matching Lunatic per-process
8584 // terminology INSPIRATIONS §III.1, `:behavior` → `:gen-server`
8585 // matching Erlang's verbatim name, `:upgrade-from` → `:appup`
8586 // matching Erlang's verbatim appup name) lands as an edit to
8587 // exactly one const, and every consumer that reaches for the
8588 // label picks it up at build time rather than at runtime as a
8589 // downstream mismatch.
8590 assert_eq!(crate::render::M2_AUTHOR_KEY_LIMITS, ":limits");
8591 assert_eq!(crate::render::M2_AUTHOR_KEY_BEHAVIOR, ":behavior");
8592 assert_eq!(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM, ":upgrade-from");
8593 }
8594
8595 #[test]
8596 fn declared_servico_slots_route_through_lifted_m2_author_key_consts() {
8597 // Production-through-const pin: the three per-arm labels the
8598 // [`Caixa::declared_servico_slots`] tagger pushes onto its
8599 // return `Vec` route through the lifted
8600 // [`crate::M2_AUTHOR_KEY_LIMITS`] /
8601 // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
8602 // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts, in canonical
8603 // declaration order. A future re-order or drift at the tagger
8604 // (a rename that reaches the tagger but not the const, or vice
8605 // versa) surfaces here at build time rather than at runtime as
8606 // a [`crate::LayoutError::ServicoSlotsOnNonServico`]
8607 // `slots: <stale-kebab-case>` diagnostic far from the rename's
8608 // commit. Mirror of the peer
8609 // [`crate::behavior::BehaviorSpec::declared_slots`] production
8610 // tagger pin (889dc18) on the sibling per-callback axis.
8611 use crate::{BehaviorSpec, UpgradeFromEntry, UpgradeInstruction};
8612 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8613 c.limits = Some(crate::LimitsSpec {
8614 fuel: Some(1_000_000),
8615 ..Default::default()
8616 });
8617 c.behavior = Some(BehaviorSpec {
8618 on_init: Some(PathBuf::from("lib/init.lisp")),
8619 ..Default::default()
8620 });
8621 c.upgrade_from = vec![UpgradeFromEntry {
8622 from: "0.1.0".into(),
8623 instructions: vec![UpgradeInstruction::Restart],
8624 }];
8625 assert_eq!(
8626 c.declared_servico_slots(),
8627 vec![
8628 crate::render::M2_AUTHOR_KEY_LIMITS,
8629 crate::render::M2_AUTHOR_KEY_BEHAVIOR,
8630 crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
8631 ]
8632 );
8633 }
8634
8635 #[test]
8636 fn existing_manifests_unaffected_by_new_optional_slots() {
8637 // Regression test: a caixa.lisp authored before M2 typed slots
8638 // should still parse + serialize cleanly. The bare `defcaixa`
8639 // emitted by `Caixa::template` has none of the new fields.
8640 let src = Caixa::template("legacy");
8641 let c = Caixa::from_lisp(&src).unwrap();
8642 assert!(c.limits.is_none());
8643 assert!(c.behavior.is_none());
8644 assert!(c.upgrade_from.is_empty());
8645 assert!(c.estrategia.is_none());
8646 assert!(c.children.is_empty());
8647
8648 // And to_lisp emits a manifest with the new slots in the
8649 // empty/default state — round-trippable.
8650 let emitted = c.to_lisp();
8651 let back = Caixa::from_lisp(&emitted).unwrap();
8652 assert_eq!(c, back);
8653 }
8654
8655 #[test]
8656 fn validate_deps_accepts_canonical_caixa() {
8657 // Positive control: the bare template — zero deps, zero
8658 // deps_dev — passes the gate trivially. A future axis added to
8659 // `Dep::validate` mustn't regress an empty-deps caixa to a
8660 // build error.
8661 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8662 c.validate_deps().unwrap();
8663 }
8664
8665 #[test]
8666 fn validate_deps_rejects_invalid_versao_in_deps() {
8667 // Fail-before-pass-after pin: a malformed `:deps :versao`
8668 // surfaces at validate_deps() time, not at lacre-resolve time.
8669 // Mirrors `rejects_invalid_membro_versao_requirement` and
8670 // `validate_rejects_invalid_child_versao_requirement` on the
8671 // other two `:versao` axes.
8672 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8673 c.deps = vec![Dep::simple("caixa-teia", "^bad-version")];
8674 let err = c.validate_deps().unwrap_err();
8675 assert!(
8676 matches!(
8677 err,
8678 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
8679 if nome == "caixa-teia" && versao == "^bad-version"
8680 ),
8681 "got {err:?}"
8682 );
8683 }
8684
8685 #[test]
8686 fn validate_deps_rejects_invalid_versao_in_deps_dev() {
8687 // Parity pin: `:deps-dev` must run through the same per-entry
8688 // validator as `:deps` — a typo in either axis surfaces the
8689 // same diagnostic. Without this leg, `:deps-dev` would be a
8690 // second-class citizen of the typed surface and an author
8691 // could land a build that passes validate_deps but fails at
8692 // `feira lock`-time when the dev-dep is resolved for a test
8693 // build.
8694 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8695 c.deps_dev = vec![Dep::simple("tatara-check", "^^0.1")];
8696 let err = c.validate_deps().unwrap_err();
8697 assert!(
8698 matches!(
8699 err,
8700 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
8701 if nome == "tatara-check" && versao == "^^0.1"
8702 ),
8703 "got {err:?}"
8704 );
8705 }
8706
8707 #[test]
8708 fn validate_deps_runs_deps_before_deps_dev() {
8709 // Order pin: when both lists carry typos, the `:deps`
8710 // diagnostic surfaces first. The author's mental model is
8711 // "runtime deps are load-bearing; dev deps are scaffolding";
8712 // surfacing the runtime axis first matches that hierarchy.
8713 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8714 c.deps = vec![Dep::simple("runtime-dep", "^bad-runtime")];
8715 c.deps_dev = vec![Dep::simple("dev-dep", "^bad-dev")];
8716 let err = c.validate_deps().unwrap_err();
8717 assert!(
8718 matches!(
8719 err,
8720 crate::dep::DepError::VersaoInvalid { ref nome, .. }
8721 if nome == "runtime-dep"
8722 ),
8723 "expected `:deps` typo to surface first, got {err:?}"
8724 );
8725 }
8726
8727 #[test]
8728 fn validate_deps_accepts_canonical_versao_forms_in_both_lists() {
8729 // Positive control sweep across both lists. Pin every
8730 // canonical Cargo-shaped form so a future tightening of the
8731 // accepted set surfaces here as a test failure (parity with
8732 // `accepts_canonical_membro_versao_forms` and
8733 // `validate_accepts_canonical_child_versao_forms`).
8734 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8735 c.deps = vec![
8736 Dep::simple("caret", "^0.1"),
8737 Dep::simple("tilde", "~0.1.2"),
8738 Dep::simple("exact", "0.1.0"),
8739 Dep::simple("wildcard", "*"),
8740 Dep::simple("multi-range", ">=0.1, <2"),
8741 ];
8742 c.deps_dev = vec![
8743 Dep::simple("dev-caret", "^0.1"),
8744 Dep::simple("dev-wildcard", "*"),
8745 ];
8746 c.validate_deps().unwrap();
8747 }
8748
8749 #[test]
8750 fn validate_deps_diagnostic_carries_offending_dep() {
8751 // Diagnostic-shape pin: the error names the offending entry's
8752 // `:nome` + `:versao` verbatim and carries a non-empty
8753 // `reason` from `semver::VersionReq::parse`, so a `feira lint`
8754 // run can render the diagnostic without re-parsing.
8755 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8756 c.deps = vec![Dep::simple("caixa-teia", "not-a-req")];
8757 let err = c.validate_deps().unwrap_err();
8758 let crate::dep::DepError::VersaoInvalid {
8759 nome,
8760 versao,
8761 reason,
8762 } = err
8763 else {
8764 panic!("expected VersaoInvalid, got other variant");
8765 };
8766 assert_eq!(nome, "caixa-teia");
8767 assert_eq!(versao, "not-a-req");
8768 assert!(
8769 !reason.is_empty(),
8770 "VersaoInvalid `reason` must carry the parser's wording verbatim"
8771 );
8772 }
8773
8774 #[test]
8775 fn validate_deps_rejects_ambiguous_fonte_in_deps_dev() {
8776 // Cross-axis pin: `validate_deps` walks both :deps and
8777 // :deps-dev through `Dep::validate`, and the new fonte gate
8778 // (`:tag` + `:branch` both set — the canonical "pin drift"
8779 // footgun) must surface from the :deps-dev arm with the
8780 // offending entry's :nome named. Pin the :deps-dev arm
8781 // explicitly so a future shortcut that only walks :deps
8782 // surfaces here as a regression.
8783 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8784 c.deps_dev = vec![Dep {
8785 nome: "dev-only".into(),
8786 versao: "^0.1".into(),
8787 fonte: Some(crate::DepSource::Git {
8788 repo: "github:p/x".into(),
8789 tag: Some("v1".into()),
8790 rev: None,
8791 branch: Some("main".into()),
8792 }),
8793 opcional: false,
8794 caracteristicas: vec![],
8795 }];
8796 let err = c.validate_deps().unwrap_err();
8797 let crate::dep::DepError::FontePinAmbiguous { nome, pins } = err else {
8798 panic!("expected FontePinAmbiguous from :deps-dev walk");
8799 };
8800 assert_eq!(nome, "dev-only");
8801 assert!(pins.contains(":tag") && pins.contains(":branch"));
8802 }
8803
8804 #[test]
8805 fn validate_deps_rejects_empty_repo_in_deps() {
8806 // Parity pin on the :deps arm: an empty :repo on the runtime
8807 // deps list surfaces the same FonteRepoEmpty diagnostic the
8808 // dep.rs per-entry tests pin, naming the offending entry.
8809 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8810 c.deps = vec![Dep {
8811 nome: "runtime".into(),
8812 versao: "^0.1".into(),
8813 fonte: Some(crate::DepSource::Git {
8814 repo: String::new(),
8815 tag: Some("v1".into()),
8816 rev: None,
8817 branch: None,
8818 }),
8819 opcional: false,
8820 caracteristicas: vec![],
8821 }];
8822 let err = c.validate_deps().unwrap_err();
8823 assert!(
8824 matches!(
8825 err,
8826 crate::dep::DepError::FonteRepoEmpty { ref nome }
8827 if nome == "runtime"
8828 ),
8829 "got {err:?}"
8830 );
8831 }
8832
8833 // ── validate_deps: within-list :nome set-not-multiset gate ─────────
8834
8835 #[test]
8836 fn validate_deps_rejects_duplicate_nome_in_deps() {
8837 // Fail-before-pass-after pin: two `:deps` entries naming the same
8838 // caixa carry two `:versao` / `:fonte` / feature triples that the
8839 // caixa-resolver's lacre pipeline collapses (the second silently
8840 // overwrites the first at `concrete_versao`-resolve time). The
8841 // gate surfaces the duplicate at validate-time, naming the
8842 // offending caixa + the list, before the resolver-side silent
8843 // drop. Mirrors the peer typed-graph duplicate gates
8844 // (`DuplicateChildCaixa`, `MembroDuplicate`, `DuplicateFrom`, …).
8845 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8846 c.deps = vec![
8847 Dep::simple("caixa-teia", "^0.1"),
8848 Dep::simple("caixa-teia", "^0.2"),
8849 ];
8850 let err = c.validate_deps().unwrap_err();
8851 assert!(
8852 matches!(
8853 err,
8854 crate::dep::DepError::DuplicateNome { ref nome, list }
8855 if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
8856 ),
8857 "got {err:?}"
8858 );
8859 }
8860
8861 #[test]
8862 fn validate_deps_rejects_duplicate_nome_in_deps_dev() {
8863 // Parity pin: `:deps-dev` runs through the same per-list
8864 // duplicate check as `:deps` — neither axis is a second-class
8865 // citizen of the set-not-multiset discipline.
8866 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8867 c.deps_dev = vec![
8868 Dep::simple("tatara-check", "*"),
8869 Dep::simple("tatara-check", "^0.1"),
8870 ];
8871 let err = c.validate_deps().unwrap_err();
8872 assert!(
8873 matches!(
8874 err,
8875 crate::dep::DepError::DuplicateNome { ref nome, list }
8876 if nome == "tatara-check" && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
8877 ),
8878 "got {err:?}"
8879 );
8880 }
8881
8882 #[test]
8883 fn validate_deps_accepts_cross_list_same_nome() {
8884 // The Cargo `[dependencies]` + `[dev-dependencies]` override
8885 // convention is preserved: a name appearing in *both* lists is
8886 // valid (the dev-pin overrides at test/dev time). Only
8887 // within-list duplicates are structurally incoherent — pin the
8888 // permissive cross-list semantics so a future shortcut that
8889 // collapses the two seen-sets into one surfaces here as a test
8890 // failure.
8891 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8892 c.deps = vec![Dep::simple("caixa-teia", "^0.1")];
8893 c.deps_dev = vec![Dep::simple("caixa-teia", "^0.2")];
8894 c.validate_deps().unwrap();
8895 }
8896
8897 #[test]
8898 fn validate_deps_accepts_distinct_nome_in_both_lists() {
8899 // Positive control: distinct names within each list pass — the
8900 // gate's identity element on the canonical authoring shape.
8901 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8902 c.deps = vec![
8903 Dep::simple("caixa-teia", "^0.1"),
8904 Dep::simple("pleme-mesh", "*"),
8905 ];
8906 c.deps_dev = vec![
8907 Dep::simple("tatara-check", "*"),
8908 Dep::simple("dev-shim", "^0.1"),
8909 ];
8910 c.validate_deps().unwrap();
8911 }
8912
8913 #[test]
8914 fn validate_deps_per_entry_validate_fires_before_duplicate_in_deps() {
8915 // Diagnostic-precedence pin: a malformed `:versao` on the
8916 // duplicating entry surfaces its narrower `VersaoInvalid`
8917 // diagnostic first, before the cross-entry duplicate gate fires
8918 // — the canonical "per-entry shape before cross-entry uniqueness"
8919 // precedence every peer set-not-multiset gate establishes
8920 // (`*_invalid_fires_before_duplicate_check` pins on
8921 // `SupervisorSpec::validate`, `AplicacaoSpec::validate_membros`,
8922 // `validate_upgrade_from`).
8923 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8924 c.deps = vec![
8925 Dep::simple("caixa-teia", "^0.1"),
8926 Dep::simple("caixa-teia", "^bad-version"),
8927 ];
8928 let err = c.validate_deps().unwrap_err();
8929 assert!(
8930 matches!(
8931 err,
8932 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
8933 if nome == "caixa-teia" && versao == "^bad-version"
8934 ),
8935 "expected VersaoInvalid to surface before DuplicateNome, got {err:?}"
8936 );
8937 }
8938
8939 #[test]
8940 fn validate_deps_duplicate_diagnostic_names_first_collision() {
8941 // First-collision determinism pin: with three entries naming the
8942 // same caixa, the first colliding pair surfaces — not the last.
8943 // Mirrors the peer first-collision posture on every
8944 // duplicate-target gate
8945 // (`validate_upgrade_from_duplicate_diagnostic_names_second_collision`
8946 // — the second entry is the first collision; this gate uses the
8947 // same shape: the second entry's `:nome` lands in the diagnostic
8948 // because `seen.insert(first.nome)` already populated the set).
8949 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8950 c.deps = vec![
8951 Dep::simple("caixa-teia", "^0.1"),
8952 Dep::simple("caixa-teia", "^0.2"),
8953 Dep::simple("caixa-teia", "^0.3"),
8954 ];
8955 let err = c.validate_deps().unwrap_err();
8956 // The diagnostic carries the offending caixa name; the
8957 // implementation surfaces on the *second* entry (the first
8958 // collision), so the test pins the `:nome` value.
8959 assert!(
8960 matches!(
8961 err,
8962 crate::dep::DepError::DuplicateNome { ref nome, list }
8963 if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
8964 ),
8965 "got {err:?}"
8966 );
8967 }
8968
8969 #[test]
8970 fn validate_deps_duplicate_in_deps_fires_before_duplicate_in_deps_dev() {
8971 // Cross-list precedence pin: when both lists carry duplicates,
8972 // the `:deps` diagnostic surfaces first — same author-mental-
8973 // model ordering the `validate_deps_runs_deps_before_deps_dev`
8974 // pin establishes for malformed `:versao` (runtime axis before
8975 // dev axis).
8976 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8977 c.deps = vec![
8978 Dep::simple("runtime-dep", "^0.1"),
8979 Dep::simple("runtime-dep", "^0.2"),
8980 ];
8981 c.deps_dev = vec![Dep::simple("dev-dep", "*"), Dep::simple("dev-dep", "^0.1")];
8982 let err = c.validate_deps().unwrap_err();
8983 assert!(
8984 matches!(
8985 err,
8986 crate::dep::DepError::DuplicateNome { ref nome, list }
8987 if nome == "runtime-dep" && list == crate::render::DEP_AUTHOR_KEY_DEPS
8988 ),
8989 "expected :deps duplicate to surface before :deps-dev duplicate, got {err:?}"
8990 );
8991 }
8992
8993 #[test]
8994 fn validate_deps_empty_lists_pass_duplicate_gate() {
8995 // Empty-set identity pin: the bare template (zero deps, zero
8996 // deps_dev) passes the duplicate gate as the gate's identity
8997 // element. A future tighten that conflates "empty" with
8998 // "missing" would regress this baseline.
8999 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9000 c.validate_deps().unwrap();
9001 }
9002
9003 #[test]
9004 fn validate_deps_duplicate_diagnostic_carries_list_tag() {
9005 // Diagnostic-shape pin: the `list:` field tags which list the
9006 // duplicate landed in (`:deps` vs `:deps-dev`) verbatim, so a
9007 // `feira lint` run can route the author to the right block in
9008 // their caixa.lisp without re-deriving the list from context.
9009 // Same self-locating shape every peer per-axis diagnostic
9010 // already exposes.
9011 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9012 c.deps_dev = vec![
9013 Dep::simple("dev-thing", "*"),
9014 Dep::simple("dev-thing", "^0.1"),
9015 ];
9016 let err = c.validate_deps().unwrap_err();
9017 let crate::dep::DepError::DuplicateNome { nome, list } = err else {
9018 panic!("expected DuplicateNome from :deps-dev walk");
9019 };
9020 assert_eq!(nome, "dev-thing");
9021 assert_eq!(list, crate::render::DEP_AUTHOR_KEY_DEPS_DEV);
9022 }
9023
9024 // ── validate_deps: per-entry :caracteristicas set-discipline gate ──
9025
9026 #[test]
9027 fn validate_deps_surfaces_caracteristicas_duplicate_in_deps_list() {
9028 // Thread-through pin on `:deps`: the per-entry
9029 // `Dep::validate_caracteristicas` gate fires inside
9030 // `Caixa::validate_deps`'s linear walk, so a malformed feature
9031 // list on any `:deps` entry surfaces as a `DepError` from
9032 // `validate_deps` — the same reachability shape every per-entry
9033 // `Dep::validate` arm threads through. Without this pin a future
9034 // shortcut that skips the per-entry `Dep::validate` call on the
9035 // cross-entry-uniqueness path would mask the within-entry
9036 // `:caracteristicas` gates.
9037 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9038 c.deps = vec![Dep {
9039 nome: "caixa-teia".into(),
9040 versao: "^0.1".into(),
9041 fonte: None,
9042 opcional: false,
9043 caracteristicas: vec!["http".into(), "http".into()],
9044 }];
9045 let err = c.validate_deps().unwrap_err();
9046 let crate::dep::DepError::CaracteristicaDuplicate {
9047 nome,
9048 caracteristica,
9049 } = err
9050 else {
9051 panic!("expected CaracteristicaDuplicate from :deps walk, got {err:?}");
9052 };
9053 assert_eq!(nome, "caixa-teia");
9054 assert_eq!(caracteristica, "http");
9055 }
9056
9057 #[test]
9058 fn validate_deps_surfaces_caracteristicas_empty_in_deps_dev_list() {
9059 // Peer thread-through pin on `:deps-dev`: same reachability as
9060 // the `:deps` arm above, on the dev-only authoring axis. Pins
9061 // that the `validate_deps` walk visits both lists' per-entry
9062 // gates uniformly. The empty-feature arm carries here so both
9063 // new `:caracteristicas` arms are surfaced via at least one
9064 // `validate_deps` thread-through.
9065 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9066 c.deps_dev = vec![Dep {
9067 nome: "caixa-teia".into(),
9068 versao: "^0.1".into(),
9069 fonte: None,
9070 opcional: false,
9071 caracteristicas: vec![String::new()],
9072 }];
9073 let err = c.validate_deps().unwrap_err();
9074 let crate::dep::DepError::CaracteristicaEmpty { nome } = err else {
9075 panic!("expected CaracteristicaEmpty from :deps-dev walk, got {err:?}");
9076 };
9077 assert_eq!(nome, "caixa-teia");
9078 }
9079
9080 #[test]
9081 fn validate_deps_surfaces_caracteristicas_invalid_in_deps_list() {
9082 // Thread-through pin on `:deps`: the per-entry
9083 // `Dep::validate_caracteristicas` value-shape gate (lifted via
9084 // `crate::render::is_cargo_feature_name`) fires inside
9085 // `Caixa::validate_deps`'s linear walk on the `:deps` list, so
9086 // a structurally invalid feature name on any `:deps` entry
9087 // surfaces as `DepError::CaracteristicaInvalid` from
9088 // `validate_deps` — the same reachability shape every per-entry
9089 // `Dep::validate` arm threads through. Without this pin a
9090 // future shortcut that skips the per-entry `Dep::validate` call
9091 // on the cross-entry-uniqueness path would mask the within-
9092 // entry `:caracteristicas` value-shape gate.
9093 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9094 c.deps = vec![Dep {
9095 nome: "caixa-teia".into(),
9096 versao: "^0.1".into(),
9097 fonte: None,
9098 opcional: false,
9099 caracteristicas: vec!["+http".into()],
9100 }];
9101 let err = c.validate_deps().unwrap_err();
9102 let crate::dep::DepError::CaracteristicaInvalid {
9103 nome,
9104 caracteristica,
9105 ..
9106 } = err
9107 else {
9108 panic!("expected CaracteristicaInvalid from :deps walk, got {err:?}");
9109 };
9110 assert_eq!(nome, "caixa-teia");
9111 assert_eq!(caracteristica, "+http");
9112 }
9113
9114 #[test]
9115 fn validate_deps_surfaces_caracteristicas_invalid_in_deps_dev_list() {
9116 // Peer thread-through pin on `:deps-dev`: same reachability as
9117 // the `:deps` arm above, on the dev-only authoring axis. The
9118 // `http/json` shape carries here so the segment-separator
9119 // diagnostic (the canonical Cargo `dep/feat` namespaced-dep
9120 // confusion footgun) is surfaced via the cross-entry walk too —
9121 // pinning that the `:deps-dev` list visits the same per-entry
9122 // value-shape gate as the `:deps` list.
9123 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9124 c.deps_dev = vec![Dep {
9125 nome: "caixa-teia".into(),
9126 versao: "^0.1".into(),
9127 fonte: None,
9128 opcional: false,
9129 caracteristicas: vec!["http/json".into()],
9130 }];
9131 let err = c.validate_deps().unwrap_err();
9132 let crate::dep::DepError::CaracteristicaInvalid {
9133 nome,
9134 caracteristica,
9135 ..
9136 } = err
9137 else {
9138 panic!("expected CaracteristicaInvalid from :deps-dev walk, got {err:?}");
9139 };
9140 assert_eq!(nome, "caixa-teia");
9141 assert_eq!(caracteristica, "http/json");
9142 }
9143
9144 #[test]
9145 fn to_lisp_preserves_deps() {
9146 let src = r#"
9147(defcaixa
9148 :nome "x"
9149 :versao "0.1.0"
9150 :kind Biblioteca
9151 :deps ((:nome "a" :versao "^0.1")
9152 (:nome "b" :versao "*" :fonte (:tipo git :repo "github:o/b" :tag "v1"))))
9153"#;
9154 let c1 = Caixa::from_lisp(src).unwrap();
9155 let emitted = c1.to_lisp();
9156 let c2 = Caixa::from_lisp(&emitted).expect("round trip");
9157 assert_eq!(c1.deps, c2.deps);
9158 }
9159
9160 // ── Caixa::validate_nome — top-level :nome value-shape gate ─────────
9161
9162 fn caixa_with_nome(nome: &str) -> Caixa {
9163 let mut c = Caixa::from_lisp(&Caixa::template("placeholder")).unwrap();
9164 c.nome = nome.to_string();
9165 c
9166 }
9167
9168 #[test]
9169 fn validate_nome_accepts_canonical_template() {
9170 // Positive control: the bare `feira init`-style template's
9171 // `:nome` ("demo") is a canonical DNS-1123 label; the gate must
9172 // not regress this baseline shape. A future tightening of the
9173 // accepted set surfaces here as a test failure first.
9174 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9175 c.validate_nome().unwrap();
9176 }
9177
9178 #[test]
9179 fn validate_nome_accepts_canonical_forms() {
9180 // Positive-set sweep: each realistic caixa-name shape the K8s
9181 // apiserver accepts as a `metadata.name` label must pass —
9182 // single-word, hyphen-joined, version-suffixed, single-char,
9183 // two-char, digit-start (DNS-1123 allows this; the stricter
9184 // DNS-1035 Service-name rule doesn't), version-suffix-bearing.
9185 // Mirrors `accepts_canonical_membro_caixa_forms` (3f9d7a0) on
9186 // the peer member-name axis.
9187 for nome in [
9188 "checkout",
9189 "cart-v2",
9190 "a",
9191 "db",
9192 "3rd-party-shim",
9193 "payment-retry",
9194 "0",
9195 ] {
9196 caixa_with_nome(nome)
9197 .validate_nome()
9198 .unwrap_or_else(|e| panic!("canonical :nome {nome:?} must validate, got {e:?}"));
9199 }
9200 }
9201
9202 #[test]
9203 fn validate_nome_rejects_empty() {
9204 // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
9205 // an empty `:nome` (the derive macro stores the raw String);
9206 // the gate's empty arm names the offending axis with a narrower
9207 // diagnostic than the `NomeInvalid` parse arm would emit.
9208 let c = caixa_with_nome("");
9209 let err = c.validate_nome().unwrap_err();
9210 assert_eq!(err, ManifestError::NomeEmpty);
9211 }
9212
9213 #[test]
9214 fn validate_nome_rejects_uppercase() {
9215 // The canonical "I copied the TitleCase display name verbatim"
9216 // footgun. The K8s apiserver rejects `metadata.name: MyApp` at
9217 // admission on every derived artifact (Helm chart, ComputeUnit,
9218 // CNP, HTTPRoute, label values); the gate moves the diagnostic
9219 // to the source `caixa.lisp` and the reason suggests the
9220 // lowercased fix verbatim.
9221 let c = caixa_with_nome("MyApp");
9222 let err = c.validate_nome().unwrap_err();
9223 let ManifestError::NomeInvalid { nome, reason } = err else {
9224 panic!("expected NomeInvalid for uppercase :nome");
9225 };
9226 assert_eq!(nome, "MyApp");
9227 assert!(
9228 reason.contains("uppercase") && reason.contains("myapp"),
9229 "diagnostic must name the violation + the lowercased fix, got {reason:?}"
9230 );
9231 }
9232
9233 #[test]
9234 fn validate_nome_rejects_underscore() {
9235 // The Python-/Postgres-style `snake_case` leak. DNS-1123 forbids
9236 // `_`; the apiserver rejects on admission across every derived
9237 // artifact. Same fixture pinned for `:membros :caixa` (3f9d7a0)
9238 // and `:children :caixa` (31bfa43).
9239 let c = caixa_with_nome("my_app");
9240 let err = c.validate_nome().unwrap_err();
9241 assert!(
9242 matches!(
9243 err,
9244 ManifestError::NomeInvalid { ref nome, ref reason }
9245 if nome == "my_app" && reason.contains('_')
9246 ),
9247 "got {err:?}"
9248 );
9249 }
9250
9251 #[test]
9252 fn validate_nome_rejects_dot() {
9253 // A `:nome` is a single DNS-1123 label, not a subdomain. The
9254 // "I want to namespace with `.`" footgun the gate redirects to
9255 // `-` via the shared predicate's reason wording.
9256 let c = caixa_with_nome("team.app");
9257 let err = c.validate_nome().unwrap_err();
9258 assert!(
9259 matches!(
9260 err,
9261 ManifestError::NomeInvalid { ref nome, ref reason }
9262 if nome == "team.app" && reason.contains('.')
9263 ),
9264 "got {err:?}"
9265 );
9266 }
9267
9268 #[test]
9269 fn validate_nome_rejects_leading_hyphen() {
9270 // DNS-1123 boundary rule: the label must start with an ASCII
9271 // alphanumeric. Pin the leading-`-` arm explicitly.
9272 let c = caixa_with_nome("-app");
9273 let err = c.validate_nome().unwrap_err();
9274 assert!(
9275 matches!(
9276 err,
9277 ManifestError::NomeInvalid { ref nome, .. } if nome == "-app"
9278 ),
9279 "got {err:?}"
9280 );
9281 }
9282
9283 #[test]
9284 fn validate_nome_rejects_trailing_hyphen() {
9285 // Symmetric arm of the boundary rule, pinned separately so a
9286 // future relaxation that only checks the leading position
9287 // surfaces here. Mirrors `rejects_membro_caixa_with_trailing_hyphen`
9288 // and `_with_trailing_hyphen` on the supervisor / aplicacao
9289 // axes.
9290 let c = caixa_with_nome("app-");
9291 let err = c.validate_nome().unwrap_err();
9292 assert!(
9293 matches!(
9294 err,
9295 ManifestError::NomeInvalid { ref nome, .. } if nome == "app-"
9296 ),
9297 "got {err:?}"
9298 );
9299 }
9300
9301 #[test]
9302 fn validate_nome_rejects_unicode() {
9303 // IDN must be pre-encoded as Punycode (`xn--…`); raw Unicode
9304 // bytes are rejected by the K8s apiserver on every name axis.
9305 let c = caixa_with_nome("café");
9306 let err = c.validate_nome().unwrap_err();
9307 assert!(
9308 matches!(
9309 err,
9310 ManifestError::NomeInvalid { ref nome, .. } if nome == "café"
9311 ),
9312 "got {err:?}"
9313 );
9314 }
9315
9316 #[test]
9317 fn validate_nome_rejects_whitespace() {
9318 // The paste-from-sketch / paste-from-spec footgun. Internal
9319 // whitespace is rejected by every K8s name axis.
9320 let c = caixa_with_nome("my app");
9321 let err = c.validate_nome().unwrap_err();
9322 assert!(
9323 matches!(
9324 err,
9325 ManifestError::NomeInvalid { ref nome, .. } if nome == "my app"
9326 ),
9327 "got {err:?}"
9328 );
9329 }
9330
9331 #[test]
9332 fn validate_nome_rejects_too_long() {
9333 // 64-byte boundary pin: the K8s apiserver rejects any
9334 // `metadata.name` over 63 bytes at admission; the diagnostic
9335 // names both the 63-byte cap and the actual length so the
9336 // author can shorten in one edit. Mirrors `_too_long` on the
9337 // peer member-/cluster-/child-name axes.
9338 let over = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN + 1);
9339 let c = caixa_with_nome(&over);
9340 let err = c.validate_nome().unwrap_err();
9341 let ManifestError::NomeInvalid { nome, reason } = err else {
9342 panic!("expected NomeInvalid for over-cap :nome");
9343 };
9344 assert_eq!(nome.len(), crate::DNS_1123_LABEL_MAX_LEN + 1);
9345 assert!(
9346 reason.contains("63") && reason.contains("64"),
9347 "diagnostic must name the cap + actual length, got {reason:?}"
9348 );
9349 }
9350
9351 #[test]
9352 fn nome_max_length_validates() {
9353 // The 63-byte cap exactly — the boundary-accepting case pinned
9354 // alongside `validate_nome_rejects_too_long` so a future cap
9355 // shift surfaces both arms simultaneously. Mirrors
9356 // `membro_caixa_max_length_validates`,
9357 // `placement_cluster_max_length_validates`,
9358 // `child_caixa_max_length_validates`.
9359 let at_cap = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
9360 caixa_with_nome(&at_cap).validate_nome().unwrap();
9361 }
9362
9363 #[test]
9364 fn nome_empty_takes_precedence_over_invalid() {
9365 // Order pin: the empty arm fires before the predicate is
9366 // consulted. Empty < invalid in self-locating-ness — the
9367 // narrower `NomeEmpty` diagnostic doesn't carry a useless
9368 // `nome: ""` reference into the parser-shaped reason. Mirrors
9369 // `membro_caixa_empty_takes_precedence_over_invalid` on the
9370 // peer axis (3f9d7a0).
9371 let c = caixa_with_nome("");
9372 assert_eq!(c.validate_nome().unwrap_err(), ManifestError::NomeEmpty);
9373 }
9374
9375 #[test]
9376 fn nome_invalid_diagnostic_carries_offending_nome() {
9377 // Diagnostic-shape pin: the error names the offending `:nome`
9378 // verbatim with a non-empty parser-shaped reason, so a `feira
9379 // lint` run can render the diagnostic without re-parsing.
9380 // Mirrors `membro_caixa_invalid_diagnostic_carries_offending_caixa`.
9381 let c = caixa_with_nome("MyApp");
9382 let err = c.validate_nome().unwrap_err();
9383 let ManifestError::NomeInvalid { nome, reason } = err else {
9384 panic!("expected NomeInvalid variant");
9385 };
9386 assert_eq!(nome, "MyApp");
9387 assert!(
9388 !reason.is_empty(),
9389 "NomeInvalid `reason` must carry the predicate's wording verbatim"
9390 );
9391 }
9392
9393 // ── Caixa::validate_nome_chart_name_budget — joint-length on `:nome` ──
9394 //
9395 // The bare-`:nome` axis [`Caixa::validate_nome`] caps at 63 bytes
9396 // via DNS-1123; this second-axis gate caps the joint
9397 // `lareira-<nome>` chart name at the same 63-byte ceiling. The
9398 // canonical [`crate::lareira_chart_name`] helper's doc comment
9399 // (f7320d7, caixa-core/src/render.rs:3198) explicitly deferred:
9400 // "the M4 admission webhook will pin the joint-length invariant
9401 // when it lands". These tests pin it at the manifest-validate
9402 // layer instead, fail-before-pass-after on the 56-byte boundary.
9403
9404 #[test]
9405 fn validate_nome_chart_name_budget_accepts_canonical_template() {
9406 // Positive control: the bare `feira init`-style template's
9407 // `:nome` ("demo") sits far below the cap; the gate must not
9408 // regress this baseline. Same shape every peer
9409 // value-shape-gate baseline pin uses.
9410 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9411 c.validate_nome_chart_name_budget().unwrap();
9412 }
9413
9414 #[test]
9415 fn validate_nome_chart_name_budget_accepts_canonical_fixtures() {
9416 // Positive-set sweep across the canonical author surface every
9417 // in-tree fixture uses (`hello-rio`, `cart`, `checkout`,
9418 // `worker`, the `checkout-aplicacao` example members, the
9419 // `example-attest` caixa-tatara fixture). Every value sits
9420 // far below the 55-byte per-`:nome` budget. Same shape every
9421 // peer per-axis baseline pin uses.
9422 for nome in [
9423 "hello-rio",
9424 "cart",
9425 "checkout",
9426 "worker",
9427 "example-attest",
9428 "demo",
9429 "a",
9430 ] {
9431 caixa_with_nome(nome)
9432 .validate_nome_chart_name_budget()
9433 .unwrap_or_else(|e| {
9434 panic!("canonical :nome {nome:?} must pass chart-name budget, got {e:?}")
9435 });
9436 }
9437 }
9438
9439 #[test]
9440 fn validate_nome_chart_name_budget_accepts_nome_at_cap() {
9441 // Boundary-accepting case at the 55-byte per-`:nome` budget —
9442 // the joint chart name is exactly 63 bytes, the DNS-1123 label
9443 // cap. Pinned alongside the rejecting-arm test so a future cap
9444 // shift surfaces both arms simultaneously. Mirrors
9445 // `nome_max_length_validates` on the peer bare-`:nome` axis.
9446 let at_cap = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN);
9447 caixa_with_nome(&at_cap)
9448 .validate_nome_chart_name_budget()
9449 .unwrap();
9450 }
9451
9452 #[test]
9453 fn validate_nome_chart_name_budget_rejects_nome_one_over_cap() {
9454 // Fail-before-pass-after pin on the 56-byte boundary: the
9455 // smallest `:nome` length that overflows the joint chart-name
9456 // cap. The inner [`is_dns_1123_label`] gate
9457 // (`Caixa::validate_nome`) accepts it (56 ≤ 63), so prior to
9458 // this gate it silently passed the manifest-validate cascade
9459 // and surfaced as a `helm lint` / apiserver rejection on the
9460 // rendered chart name far from the source `caixa.lisp`, with
9461 // no field naming the overflow. With this gate the diagnostic
9462 // names the offending `:nome` verbatim alongside the rendered
9463 // chart name and the budget, so the author can shorten in one
9464 // edit. Mirrors `validate_nome_rejects_too_long` on the peer
9465 // bare-`:nome` axis.
9466 let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
9467 let c = caixa_with_nome(&over);
9468 let err = c.validate_nome_chart_name_budget().unwrap_err();
9469 let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
9470 panic!("expected NomeChartNameBudgetExceeded for over-budget :nome");
9471 };
9472 assert_eq!(nome.len(), crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
9473 assert_eq!(nome, over);
9474 assert!(
9475 reason.contains("63") && reason.contains("64") && reason.contains("55"),
9476 "diagnostic must name the DNS-1123 cap (63), the actual chart-name length (64), \
9477 and the per-`:nome` budget (55), got {reason:?}"
9478 );
9479 }
9480
9481 #[test]
9482 fn validate_nome_chart_name_budget_rejects_nome_at_bare_dns_cap() {
9483 // The 63-byte `:nome` boundary — passes the bare-`:nome`
9484 // [`is_dns_1123_label`] cap exactly, but produces a 71-byte
9485 // joint chart name that overflows the DNS-1123 label cap
9486 // structurally. The most stringent fail-before-pass-after
9487 // surface: every `:nome` in the 56..=63-byte range passed the
9488 // prior cascade and broke at admission.
9489 let bare_max = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
9490 let c = caixa_with_nome(&bare_max);
9491 // The bare-`:nome` gate accepts the 63-byte length.
9492 c.validate_nome().unwrap();
9493 // The new joint-length gate rejects it.
9494 let err = c.validate_nome_chart_name_budget().unwrap_err();
9495 assert!(
9496 matches!(
9497 err,
9498 ManifestError::NomeChartNameBudgetExceeded { ref nome, .. }
9499 if nome.len() == crate::DNS_1123_LABEL_MAX_LEN
9500 ),
9501 "got {err:?}"
9502 );
9503 }
9504
9505 #[test]
9506 fn validate_nome_chart_name_budget_diagnostic_carries_offending_chart_name() {
9507 // Diagnostic-shape pin: the rendered `lareira-<nome>` chart
9508 // name appears verbatim in the diagnostic so the author sees
9509 // exactly the string the apiserver / `helm lint` would have
9510 // rejected — no re-derivation required to grep the source.
9511 // Peer with `nome_invalid_diagnostic_carries_offending_nome`
9512 // on the bare-`:nome` axis.
9513 let over = "x".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 5);
9514 let c = caixa_with_nome(&over);
9515 let err = c.validate_nome_chart_name_budget().unwrap_err();
9516 let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
9517 panic!("expected NomeChartNameBudgetExceeded variant");
9518 };
9519 assert_eq!(nome, over);
9520 let expected_chart = crate::lareira_chart_name(&over);
9521 assert!(
9522 reason.contains(&expected_chart),
9523 "diagnostic must carry the rendered chart name {expected_chart:?} verbatim, \
9524 got {reason:?}"
9525 );
9526 assert!(
9527 reason.contains("lareira-"),
9528 "diagnostic must name the canonical chart-name prefix verbatim, got {reason:?}"
9529 );
9530 }
9531
9532 #[test]
9533 fn validate_nome_chart_name_budget_runs_after_nome_shape_via_layout_verify() {
9534 // Order pin on the layout cascade: the narrower
9535 // `NomeInvalid` (bare-DNS-1123 shape) fires before the
9536 // joint-length budget. A structurally-malformed `:nome` (here:
9537 // uppercase) surfaces its specific shape error rather than
9538 // the chart-name-budget error, even when the joint length
9539 // would also overflow — the narrower diagnostic is more
9540 // self-locating. Mirrors the cascade-precedence pins peer
9541 // gates already use (e.g. `EntradaParaEmpty` before
9542 // `EntradaParaInvalid`).
9543 let over = "A".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
9544 let c = caixa_with_nome(&over);
9545 // The bare-shape gate fires first.
9546 let err = c.validate_nome().unwrap_err();
9547 assert!(
9548 matches!(err, ManifestError::NomeInvalid { .. }),
9549 "bare-shape gate must fire before chart-name-budget gate; got {err:?}"
9550 );
9551 // And the layout verify cascade surfaces that diagnostic, not
9552 // the budget arm. Inject a path-exists oracle so the cascade
9553 // gets past the manifest-presence check and into the
9554 // value-shape gates.
9555 let layout = crate::StandardLayout::new().with_path_exists(|_| true);
9556 let err = crate::LayoutInvariants::verify(
9557 &layout,
9558 &c,
9559 std::path::Path::new("/tmp/caixa-test-fake-root"),
9560 )
9561 .unwrap_err();
9562 let issue = err.to_string();
9563 assert!(
9564 issue.contains("DNS-1123") || issue.contains("uppercase"),
9565 "layout cascade must surface the bare-DNS-1123 diagnostic on a \
9566 structurally-malformed :nome, not the chart-name-budget diagnostic; got {issue:?}"
9567 );
9568 }
9569
9570 #[test]
9571 fn layout_verify_routes_chart_name_budget_through_nome_violation() {
9572 // Cross-axis envelope pin: the layout cascade wraps both
9573 // bare-`:nome` and joint-length-`:nome` failures through the
9574 // same [`LayoutError::NomeViolation`] envelope, since both
9575 // arms are on the `:nome` axis. The user's diagnostic stays
9576 // self-locating ("which axis"), and a future consumer that
9577 // dispatches on the layout-error variant (e.g. a `feira lint`
9578 // exit-code mapping) sees a single per-axis envelope. The
9579 // wrapped `issue:` carries the full inner diagnostic.
9580 let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
9581 let c = caixa_with_nome(&over);
9582 // The bare-shape gate accepts.
9583 c.validate_nome().unwrap();
9584 let layout = crate::StandardLayout::new().with_path_exists(|_| true);
9585 let err = crate::LayoutInvariants::verify(
9586 &layout,
9587 &c,
9588 std::path::Path::new("/tmp/caixa-test-fake-root"),
9589 )
9590 .unwrap_err();
9591 let crate::LayoutError::NomeViolation { caixa, issue } = err else {
9592 panic!("expected LayoutError::NomeViolation, got {err:?}");
9593 };
9594 assert_eq!(caixa, over);
9595 assert!(
9596 issue.contains("lareira-") && issue.contains("63") && issue.contains("55"),
9597 "wrapped issue must carry the joint-length diagnostic verbatim, got {issue:?}"
9598 );
9599 }
9600
9601 // ── Caixa::validate_versao — top-level :versao value-shape gate ─────
9602
9603 fn caixa_with_versao(versao: &str) -> Caixa {
9604 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9605 c.versao = versao.to_string();
9606 c
9607 }
9608
9609 #[test]
9610 fn validate_versao_accepts_canonical_template() {
9611 // Positive control: the bare `feira init`-style template's
9612 // `:versao` ("0.1.0") is a canonical SemVer-2 literal; the gate
9613 // must not regress this baseline shape. A future tightening of
9614 // the accepted set surfaces here as a test failure first.
9615 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9616 c.validate_versao().unwrap();
9617 }
9618
9619 #[test]
9620 fn validate_versao_accepts_canonical_forms() {
9621 // Positive-set sweep: each realistic SemVer-2 shape the
9622 // substrate's downstream consumers accept must pass — bare
9623 // MAJOR.MINOR.PATCH, pre-release tags (`-rc.1`, `-alpha.0`),
9624 // build metadata (`+build.42`), the combined form, and the
9625 // `0.0.0` boundary case. Mirrors `accepts_canonical_forms` on
9626 // the peer `:nome` axis (6c992f8).
9627 for versao in [
9628 "0.1.0",
9629 "0.0.0",
9630 "1.0.0",
9631 "0.2.0-rc.1",
9632 "1.0.0-alpha.0",
9633 "1.0.0+build.42",
9634 "1.0.0-rc.1+build.42",
9635 "10.20.30",
9636 ] {
9637 caixa_with_versao(versao)
9638 .validate_versao()
9639 .unwrap_or_else(|e| {
9640 panic!("canonical :versao {versao:?} must validate, got {e:?}")
9641 });
9642 }
9643 }
9644
9645 #[test]
9646 fn validate_versao_rejects_empty() {
9647 // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
9648 // an empty `:versao` (the derive macro stores the raw String);
9649 // the gate's empty arm names the offending axis with a narrower
9650 // diagnostic than the `VersaoInvalid` parse arm would emit.
9651 // Mirrors `validate_nome_rejects_empty` (6c992f8).
9652 let c = caixa_with_versao("");
9653 let err = c.validate_versao().unwrap_err();
9654 assert_eq!(err, ManifestError::VersaoEmpty);
9655 }
9656
9657 #[test]
9658 fn validate_versao_rejects_git_tag_shape() {
9659 // The canonical "I copied the git tag verbatim" footgun —
9660 // `feira publish` *emits* `v<versao>` git tags, so a leaked
9661 // `v0.1.0` in `:versao` would render as `vv0.1.0` and silently
9662 // shift every downstream consumer's version axis. `semver`
9663 // rejects the leading `v` at parse time; the gate moves the
9664 // diagnostic to the source `caixa.lisp`.
9665 let c = caixa_with_versao("v0.1.0");
9666 let err = c.validate_versao().unwrap_err();
9667 let ManifestError::VersaoInvalid { versao, reason } = err else {
9668 panic!("expected VersaoInvalid for git-tag-shape :versao");
9669 };
9670 assert_eq!(versao, "v0.1.0");
9671 assert!(
9672 !reason.is_empty(),
9673 "VersaoInvalid `reason` must carry the parser's wording, got {reason:?}"
9674 );
9675 }
9676
9677 #[test]
9678 fn validate_versao_rejects_missing_patch() {
9679 // The canonical "I shortened it" footgun — SemVer-2 requires
9680 // three parts. Cargo's `version =` field accepts the shortened
9681 // form as a requirement, conflating the two leaks across the
9682 // typed `:deps :versao` vs top-level `:versao` axes; the gate
9683 // pins the top-level axis to the strict three-part shape.
9684 let c = caixa_with_versao("0.1");
9685 let err = c.validate_versao().unwrap_err();
9686 assert!(
9687 matches!(
9688 err,
9689 ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1"
9690 ),
9691 "got {err:?}"
9692 );
9693 }
9694
9695 #[test]
9696 fn validate_versao_rejects_requirement_shape() {
9697 // The canonical "I leaked a requirement into a version" footgun —
9698 // the typed `:deps :versao` / `:membros :versao` axes accept
9699 // `^0.1` (a `VersionReq`); the top-level `:versao` requires a
9700 // concrete `Version`. Without this gate the two typed surfaces
9701 // would silently overlap, and a top-level `^0.1` would surface
9702 // at `helm install` time as a Chart.yaml version rejection far
9703 // from the source `caixa.lisp`.
9704 let c = caixa_with_versao("^0.1");
9705 let err = c.validate_versao().unwrap_err();
9706 assert!(
9707 matches!(
9708 err,
9709 ManifestError::VersaoInvalid { ref versao, .. } if versao == "^0.1"
9710 ),
9711 "got {err:?}"
9712 );
9713 }
9714
9715 #[test]
9716 fn validate_versao_rejects_docker_tag_shape() {
9717 // The "I confused it with a docker tag" footgun — `latest`,
9718 // `main`, `stable` parse as identifiers, not SemVer-2 versions.
9719 // SemVer rejects at parse time; the gate moves the diagnostic
9720 // to the source `caixa.lisp`.
9721 for bad in ["latest", "main", "stable"] {
9722 let c = caixa_with_versao(bad);
9723 let err = c.validate_versao().unwrap_err();
9724 assert!(
9725 matches!(
9726 err,
9727 ManifestError::VersaoInvalid { ref versao, .. } if versao == bad
9728 ),
9729 "got {err:?} for {bad:?}"
9730 );
9731 }
9732 }
9733
9734 #[test]
9735 fn validate_versao_rejects_four_part_form() {
9736 // The Java/Microsoft "MAJOR.MINOR.PATCH.BUILD" convention
9737 // SemVer-2 forbids. A leak from a non-SemVer ecosystem; the
9738 // semver crate rejects the extra `.0` at parse time.
9739 let c = caixa_with_versao("0.1.0.0");
9740 let err = c.validate_versao().unwrap_err();
9741 assert!(
9742 matches!(
9743 err,
9744 ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1.0.0"
9745 ),
9746 "got {err:?}"
9747 );
9748 }
9749
9750 #[test]
9751 fn versao_empty_takes_precedence_over_invalid() {
9752 // Order pin: the empty arm fires before the parser is consulted.
9753 // Empty < invalid in self-locating-ness — the narrower
9754 // `VersaoEmpty` diagnostic doesn't carry a useless `versao: ""`
9755 // reference into the parser-shaped reason. Mirrors
9756 // `nome_empty_takes_precedence_over_invalid` (6c992f8) on the
9757 // peer axis.
9758 let c = caixa_with_versao("");
9759 assert_eq!(c.validate_versao().unwrap_err(), ManifestError::VersaoEmpty);
9760 }
9761
9762 #[test]
9763 fn versao_invalid_diagnostic_carries_offending_versao() {
9764 // Diagnostic-shape pin: the error names the offending `:versao`
9765 // verbatim with a non-empty parser-shaped reason, so a `feira
9766 // lint` run can render the diagnostic without re-parsing.
9767 // Mirrors `nome_invalid_diagnostic_carries_offending_nome`.
9768 let c = caixa_with_versao("v0.1.0");
9769 let err = c.validate_versao().unwrap_err();
9770 let ManifestError::VersaoInvalid { versao, reason } = err else {
9771 panic!("expected VersaoInvalid variant");
9772 };
9773 assert_eq!(versao, "v0.1.0");
9774 assert!(
9775 !reason.is_empty(),
9776 "VersaoInvalid `reason` must carry the parser's wording verbatim"
9777 );
9778 }
9779
9780 #[test]
9781 fn validate_versao_accepts_what_upgrade_from_from_accepts() {
9782 // Parity pin: every shape `UpgradeFromEntry::validate` accepts
9783 // for `:upgrade-from :from` must also pass `validate_versao` —
9784 // the two `:versao`-typed surfaces (top-level `:versao`,
9785 // `:upgrade-from :from`) consume the *same* `semver::Version`
9786 // parser, so they must agree on the accepted set. Without this
9787 // pin, a future tightening of one axis could silently diverge
9788 // from the other. Mirrors the `:versao` requirement-axis
9789 // parity (`:deps`/`:deps-dev`/`:membros`/`:children`) the prior
9790 // commits established.
9791 for versao in ["0.1.0", "0.2.0-rc.1", "1.0.0+build.42"] {
9792 // From the canonical UpgradeFromEntry round-trip fixture
9793 // (`upgrade::tests::round_trip_load_module` peers).
9794 let entry = crate::UpgradeFromEntry {
9795 from: versao.to_string(),
9796 instructions: Vec::new(),
9797 };
9798 entry
9799 .validate()
9800 .unwrap_or_else(|e| panic!(":from {versao:?} must validate, got {e:?}"));
9801 caixa_with_versao(versao)
9802 .validate_versao()
9803 .unwrap_or_else(|e| {
9804 panic!(":versao {versao:?} must validate, got {e:?} — peer axis diverges")
9805 });
9806 }
9807 }
9808
9809 // ── Caixa::validate_restart_window — supervisor restart-window
9810 // folds through the shared `supervisor::duration_codec` ────────
9811
9812 fn caixa_with_restart_window(window: Option<&str>) -> Caixa {
9813 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
9814 c.kind = CaixaKind::Supervisor;
9815 c.restart_window = window.map(str::to_string);
9816 c
9817 }
9818
9819 #[test]
9820 fn validate_restart_window_accepts_none() {
9821 // The canonical "omit the slot to express no reset" shape — a
9822 // `None` raw string is the absence of the typed
9823 // `:restart-window` slot, which is exactly the SupervisorSpec
9824 // "never reset" semantics. The gate must be a no-op here; a
9825 // future tightening that rejected `None` would force every
9826 // supervisor caixa to authoring-time pin a window even when
9827 // the OTP semantics call for none.
9828 caixa_with_restart_window(None)
9829 .validate_restart_window()
9830 .unwrap();
9831 }
9832
9833 #[test]
9834 fn validate_restart_window_accepts_canonical_forms() {
9835 // Positive-set sweep across the canonical authoring units the
9836 // shared `supervisor::duration_codec::parse` accepts —
9837 // matches the codec-side `parse_accepts_integer_canonical_units`
9838 // pin in supervisor::tests so a future codec-side tightening
9839 // surfaces simultaneously on both axes.
9840 for window in ["60s", "5m", "1h", "500ms", "30", "0s"] {
9841 caixa_with_restart_window(Some(window))
9842 .validate_restart_window()
9843 .unwrap_or_else(|e| {
9844 panic!("canonical :restart-window {window:?} must validate, got {e:?}")
9845 });
9846 }
9847 }
9848
9849 #[test]
9850 fn validate_restart_window_rejects_fractional_seconds() {
9851 // Fail-before-pass-after pin: the `"1.5s"` drift class (parses
9852 // as f64 to 1.5 → renders back as `"1500ms"` on first
9853 // serialize). Prior to the fold + this gate, the inline
9854 // `parse_window_inline` accepted f64 magnitudes and silently
9855 // produced a `Duration::from_secs_f64(1.5)`, divergent from
9856 // the shared codec's integer-magnitude discipline on the
9857 // serde-routed siblings. The gate now surfaces a self-locating
9858 // diagnostic at the manifest layer.
9859 let err = caixa_with_restart_window(Some("1.5s"))
9860 .validate_restart_window()
9861 .unwrap_err();
9862 let ManifestError::RestartWindowMalformed {
9863 restart_window,
9864 reason,
9865 } = err
9866 else {
9867 panic!("expected RestartWindowMalformed for fractional seconds");
9868 };
9869 assert_eq!(restart_window, "1.5s");
9870 assert!(
9871 reason.contains("\"1.5\"") && reason.contains("not a non-negative integer"),
9872 "diagnostic must carry shared-codec wording, got {reason:?}"
9873 );
9874 }
9875
9876 #[test]
9877 fn validate_restart_window_rejects_decimal_shaped_integer() {
9878 // The `"1.0s"` class — numerically `1s` exactly, but the
9879 // canonical form is `"1s"` not `"1.0s"`. Decimal-shape leak
9880 // gets the same canonical-form diagnostic.
9881 let err = caixa_with_restart_window(Some("1.0s"))
9882 .validate_restart_window()
9883 .unwrap_err();
9884 assert!(
9885 matches!(
9886 err,
9887 ManifestError::RestartWindowMalformed { ref restart_window, .. }
9888 if restart_window == "1.0s"
9889 ),
9890 "got {err:?}"
9891 );
9892 }
9893
9894 #[test]
9895 fn validate_restart_window_rejects_half_unit_minute() {
9896 // `"0.5m"` is the unit-fraction footgun — author writes a
9897 // human-readable half-minute, the prior inline parser silently
9898 // produced `Duration::from_secs_f64(30.0)` and serde
9899 // re-emitted as `"30s"`, rewriting author intent. The gate
9900 // closes the loop at the manifest layer.
9901 let err = caixa_with_restart_window(Some("0.5m"))
9902 .validate_restart_window()
9903 .unwrap_err();
9904 let ManifestError::RestartWindowMalformed {
9905 restart_window,
9906 reason,
9907 } = err
9908 else {
9909 panic!("expected RestartWindowMalformed");
9910 };
9911 assert_eq!(restart_window, "0.5m");
9912 assert!(
9913 reason.contains("\"30s\""),
9914 "diagnostic must point at the canonical-form remediation, got {reason:?}"
9915 );
9916 }
9917
9918 #[test]
9919 fn validate_restart_window_rejects_leading_sign() {
9920 // `"+30s"` and `"-30s"` both round-tripped through f64 cleanly
9921 // on the prior parser (`+30` parses as `30.0`; `-30` parsed
9922 // and was caught by the `num < 0.0` arm which silently
9923 // returned `None`, dropping the author-supplied window). The
9924 // shared codec's digit-only gate rejects both with a unified
9925 // canonical-form diagnostic; the manifest-layer wrapper names
9926 // the offending value.
9927 for bad in ["+30s", "-30s"] {
9928 let err = caixa_with_restart_window(Some(bad))
9929 .validate_restart_window()
9930 .unwrap_err();
9931 assert!(
9932 matches!(
9933 err,
9934 ManifestError::RestartWindowMalformed { ref restart_window, .. }
9935 if restart_window == bad
9936 ),
9937 "got {err:?} for {bad:?}"
9938 );
9939 }
9940 }
9941
9942 #[test]
9943 fn validate_restart_window_rejects_unknown_unit() {
9944 // `"30x"` — the typo / wrong-unit footgun. The shared codec's
9945 // unit dispatch surfaces an `unknown duration unit` reason;
9946 // the manifest-layer wrapper names the offending value.
9947 let err = caixa_with_restart_window(Some("30x"))
9948 .validate_restart_window()
9949 .unwrap_err();
9950 let ManifestError::RestartWindowMalformed {
9951 restart_window,
9952 reason,
9953 } = err
9954 else {
9955 panic!("expected RestartWindowMalformed for unknown unit");
9956 };
9957 assert_eq!(restart_window, "30x");
9958 assert!(
9959 reason.contains("unknown duration unit"),
9960 "diagnostic must carry shared-codec unit-rejection wording, got {reason:?}"
9961 );
9962 }
9963
9964 #[test]
9965 fn validate_restart_window_rejects_garbage() {
9966 // Pure non-numeric magnitude (`"abc"`) falls through to the
9967 // shared codec's narrower `"bad duration magnitude"` arm. Same
9968 // diagnostic shape as the codec-side
9969 // `parse_garbage_still_falls_through_to_bad_magnitude` pin.
9970 let err = caixa_with_restart_window(Some("abc"))
9971 .validate_restart_window()
9972 .unwrap_err();
9973 let ManifestError::RestartWindowMalformed {
9974 restart_window,
9975 reason,
9976 } = err
9977 else {
9978 panic!("expected RestartWindowMalformed for garbage");
9979 };
9980 assert_eq!(restart_window, "abc");
9981 assert!(
9982 reason.contains("bad duration magnitude"),
9983 "diagnostic must carry shared-codec garbage-rejection wording, got {reason:?}"
9984 );
9985 }
9986
9987 #[test]
9988 fn validate_restart_window_rejects_empty_string() {
9989 // The empty-after-trim edge case — distinct from the `None`
9990 // canonical "omit the slot" shape. The shared codec's
9991 // digit-only gate refuses an empty magnitude; the manifest
9992 // layer names the offending `""` so the author can grep for
9993 // the literal empty value in their `caixa.lisp` and either
9994 // remove the slot (the canonical "no reset" shape) or pin a
9995 // positive duration.
9996 let err = caixa_with_restart_window(Some(""))
9997 .validate_restart_window()
9998 .unwrap_err();
9999 assert!(
10000 matches!(
10001 err,
10002 ManifestError::RestartWindowMalformed { ref restart_window, .. }
10003 if restart_window.is_empty()
10004 ),
10005 "got {err:?}"
10006 );
10007 }
10008
10009 #[test]
10010 fn validate_restart_window_diagnostic_carries_offending_value() {
10011 // Diagnostic-shape pin (peer with
10012 // `nome_invalid_diagnostic_carries_offending_nome` /
10013 // `versao_invalid_diagnostic_carries_offending_versao`): the
10014 // error names the offending raw `:restart-window` verbatim
10015 // with a non-empty shared-codec-shaped reason, so a `feira
10016 // lint` run can render the diagnostic without re-parsing.
10017 let err = caixa_with_restart_window(Some("1.5s"))
10018 .validate_restart_window()
10019 .unwrap_err();
10020 let ManifestError::RestartWindowMalformed {
10021 restart_window,
10022 reason,
10023 } = err
10024 else {
10025 panic!("expected RestartWindowMalformed variant");
10026 };
10027 assert_eq!(restart_window, "1.5s");
10028 assert!(
10029 !reason.is_empty(),
10030 "RestartWindowMalformed `reason` must carry the codec's wording verbatim"
10031 );
10032 }
10033
10034 #[test]
10035 fn supervisor_view_folds_through_shared_codec_on_canonical_form() {
10036 // Behavioral parity pin after the fold (`parse_window_inline`
10037 // deletion): the canonical `"60s"` still produces
10038 // `Duration::from_secs(60)` on the typed view — the fold is
10039 // semantically equivalent to the prior inline parser on the
10040 // accepted set. Mirrors the pre-fold `supervisor_view_returns_typed_shape`
10041 // pin, narrowed to the parser-side contract.
10042 let c = caixa_with_restart_window(Some("60s"));
10043 let view = c.supervisor_view().expect("Supervisor kind has a view");
10044 assert_eq!(
10045 view.restart_window,
10046 Some(std::time::Duration::from_secs(60))
10047 );
10048 }
10049
10050 #[test]
10051 fn supervisor_view_soft_swallows_what_validate_rejects() {
10052 // Parity pin between the view-construction path and the
10053 // manifest-level validator: the same `"1.5s"` that surfaces
10054 // `RestartWindowMalformed` at `validate_restart_window` time
10055 // becomes `restart_window: None` on the typed view (the fold
10056 // preserves the existing best-effort shape of `supervisor_view`).
10057 // The contract is: a layout-verifier / `feira lint` flow that
10058 // cares about the malformed-window axis MUST consult
10059 // `validate_restart_window` — relying solely on the view's
10060 // `None` swallows the diagnostic silently. This pin makes the
10061 // expectation a typed invariant.
10062 let c = caixa_with_restart_window(Some("1.5s"));
10063 let view = c.supervisor_view().expect("Supervisor kind has a view");
10064 assert_eq!(
10065 view.restart_window, None,
10066 "view-construction path soft-swallows the parse error to None"
10067 );
10068 // And the manifest-level validator does NOT soft-swallow:
10069 assert!(
10070 matches!(
10071 c.validate_restart_window().unwrap_err(),
10072 ManifestError::RestartWindowMalformed { ref restart_window, .. }
10073 if restart_window == "1.5s"
10074 ),
10075 "validator must surface the offending value",
10076 );
10077 }
10078
10079 // ── validate_code_paths — per-entry shape on :bibliotecas / :exe / :servicos ──
10080
10081 fn caixa_with_code_paths(bibliotecas: Vec<&str>, exe: Vec<&str>, servicos: Vec<&str>) -> Caixa {
10082 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10083 c.bibliotecas = bibliotecas.into_iter().map(String::from).collect();
10084 c.exe = exe.into_iter().map(String::from).collect();
10085 c.servicos = servicos.into_iter().map(String::from).collect();
10086 c
10087 }
10088
10089 #[test]
10090 fn validate_code_paths_accepts_canonical_template() {
10091 // The bare `Caixa::template` shape is the gate's identity element
10092 // on the canonical authoring shape — `:bibliotecas
10093 // ("lib/demo.lisp")` + empty `:exe` + empty `:servicos`. Pins
10094 // that the gate is non-disruptive against every existing caixa.
10095 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10096 c.validate_code_paths().unwrap();
10097 }
10098
10099 #[test]
10100 fn validate_code_paths_accepts_explicit_relative_paths_on_every_slot() {
10101 // Positive control sweep: a canonical-shaped path on every slot
10102 // passes. Mirrors the peer
10103 // `behavior::validate_every_slot_relative_is_ok` pin.
10104 let c = caixa_with_code_paths(
10105 vec!["lib/demo.lisp", "lib/helpers.lisp"],
10106 vec!["exe/demo", "exe/tool"],
10107 vec!["servicos/demo.computeunit.yaml"],
10108 );
10109 c.validate_code_paths().unwrap();
10110 }
10111
10112 #[test]
10113 fn validate_code_paths_accepts_all_empty_lists() {
10114 // The empty-list identity element: every Caixa with no declared
10115 // code paths trivially passes (Supervisor / Aplicacao kinds rely
10116 // on this — the OwnCode gate already rejected them before the
10117 // path-shape gate runs in the layout, but the validator itself
10118 // must accept the empty shape).
10119 let c = caixa_with_code_paths(vec![], vec![], vec![]);
10120 c.validate_code_paths().unwrap();
10121 }
10122
10123 #[test]
10124 fn validate_code_paths_rejects_empty_bibliotecas_entry() {
10125 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
10126 let err = c.validate_code_paths().unwrap_err();
10127 assert!(
10128 matches!(
10129 err,
10130 ManifestError::CodePathEmpty {
10131 slot: ":bibliotecas"
10132 }
10133 ),
10134 "got {err:?}",
10135 );
10136 }
10137
10138 #[test]
10139 fn validate_code_paths_rejects_empty_exe_entry() {
10140 let c = caixa_with_code_paths(vec![], vec![""], vec![]);
10141 let err = c.validate_code_paths().unwrap_err();
10142 assert!(
10143 matches!(err, ManifestError::CodePathEmpty { slot: ":exe" }),
10144 "got {err:?}",
10145 );
10146 }
10147
10148 #[test]
10149 fn validate_code_paths_rejects_empty_servicos_entry() {
10150 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
10151 let err = c.validate_code_paths().unwrap_err();
10152 assert!(
10153 matches!(err, ManifestError::CodePathEmpty { slot: ":servicos" }),
10154 "got {err:?}",
10155 );
10156 }
10157
10158 #[test]
10159 fn validate_code_paths_rejects_absolute_bibliotecas_entry() {
10160 // `:bibliotecas` has no `starts_with(<dir>)` fence downstream,
10161 // so an absolute path that resolves on disk silently passes the
10162 // layout's existence check — the canonical sandbox-escape on
10163 // the biblioteca axis.
10164 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
10165 let err = c.validate_code_paths().unwrap_err();
10166 let ManifestError::CodePathAbsolute { slot, path } = err else {
10167 panic!("expected CodePathAbsolute, got {err:?}");
10168 };
10169 assert_eq!(slot, ":bibliotecas");
10170 assert_eq!(path, PathBuf::from("/etc/passwd"));
10171 }
10172
10173 #[test]
10174 fn validate_code_paths_rejects_absolute_exe_entry() {
10175 let c = caixa_with_code_paths(vec![], vec!["/usr/bin/env"], vec![]);
10176 let err = c.validate_code_paths().unwrap_err();
10177 let ManifestError::CodePathAbsolute { slot, path } = err else {
10178 panic!("expected CodePathAbsolute, got {err:?}");
10179 };
10180 assert_eq!(slot, ":exe");
10181 assert_eq!(path, PathBuf::from("/usr/bin/env"));
10182 }
10183
10184 #[test]
10185 fn validate_code_paths_rejects_absolute_servicos_entry() {
10186 let c = caixa_with_code_paths(vec![], vec![], vec!["/var/servicos/x.yaml"]);
10187 let err = c.validate_code_paths().unwrap_err();
10188 let ManifestError::CodePathAbsolute { slot, path } = err else {
10189 panic!("expected CodePathAbsolute, got {err:?}");
10190 };
10191 assert_eq!(slot, ":servicos");
10192 assert_eq!(path, PathBuf::from("/var/servicos/x.yaml"));
10193 }
10194
10195 #[test]
10196 fn validate_code_paths_rejects_parent_escape_bibliotecas_leading() {
10197 // Canonical "I want a lib from a sibling caixa" footgun on the
10198 // biblioteca axis. `:bibliotecas` has no `starts_with` fence
10199 // downstream, so a leading `..` traverses to the parent of the
10200 // caixa root with no diagnostic at layout time if the resolved
10201 // target exists.
10202 let c = caixa_with_code_paths(vec!["../sibling/x.lisp"], vec![], vec![]);
10203 let err = c.validate_code_paths().unwrap_err();
10204 let ManifestError::CodePathParentEscape { slot, path } = err else {
10205 panic!("expected CodePathParentEscape, got {err:?}");
10206 };
10207 assert_eq!(slot, ":bibliotecas");
10208 assert_eq!(path, PathBuf::from("../sibling/x.lisp"));
10209 }
10210
10211 #[test]
10212 fn validate_code_paths_rejects_parent_escape_exe_mid_path() {
10213 // Mid-path `..` defeats the layout's component-aware
10214 // `starts_with(exe_dir)` fence — `root.join("exe/../../escape")`
10215 // `starts_with(<root>/exe)` is true, but the canonical resolution
10216 // lives outside the caixa root. Caught regardless of where the
10217 // `..` sits — mirrors the peer
10218 // `behavior::validate_rejects_parent_escape_mid_path` pin.
10219 let c = caixa_with_code_paths(vec![], vec!["exe/../../escape"], vec![]);
10220 let err = c.validate_code_paths().unwrap_err();
10221 let ManifestError::CodePathParentEscape { slot, path } = err else {
10222 panic!("expected CodePathParentEscape, got {err:?}");
10223 };
10224 assert_eq!(slot, ":exe");
10225 assert_eq!(path, PathBuf::from("exe/../../escape"));
10226 }
10227
10228 #[test]
10229 fn validate_code_paths_rejects_parent_escape_servicos_trailing() {
10230 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/foo/../../escape.yaml"]);
10231 let err = c.validate_code_paths().unwrap_err();
10232 let ManifestError::CodePathParentEscape { slot, path } = err else {
10233 panic!("expected CodePathParentEscape, got {err:?}");
10234 };
10235 assert_eq!(slot, ":servicos");
10236 assert_eq!(path, PathBuf::from("servicos/foo/../../escape.yaml"));
10237 }
10238
10239 #[test]
10240 fn validate_code_paths_cross_slot_precedence_bibliotecas_before_exe_before_servicos() {
10241 // Cross-slot precedence pin: `:bibliotecas` → `:exe` →
10242 // `:servicos`. A manifest with malformed entries on all three
10243 // surfaces surfaces the `:bibliotecas` defect first, mirroring
10244 // the canonical declaration order
10245 // `Caixa::declared_foreign_code_slots` already establishes for
10246 // the foreign-code-slot diagnostic.
10247 let c = caixa_with_code_paths(vec![""], vec![""], vec![""]);
10248 let err = c.validate_code_paths().unwrap_err();
10249 assert!(
10250 matches!(
10251 err,
10252 ManifestError::CodePathEmpty {
10253 slot: ":bibliotecas"
10254 }
10255 ),
10256 "got {err:?}",
10257 );
10258 }
10259
10260 #[test]
10261 fn validate_code_paths_within_slot_precedence_empty_before_absolute_before_parent_escape() {
10262 // Within-slot precedence pin: empty → absolute → parent-escape,
10263 // matching the [`PathShapeViolation`] arm-ordering every peer
10264 // `is_sandboxed_relative_path` caller follows (b0c8389
10265 // BehaviorSpec, 26da2c7 UpgradeInstruction::StateChange). A
10266 // `:bibliotecas` list whose first entry is empty *and* whose
10267 // later entries are absolute/parent-escape surfaces the empty
10268 // arm first, on the lexicographically-earliest offending entry.
10269 let c = caixa_with_code_paths(vec!["", "/etc/passwd", "../escape.lisp"], vec![], vec![]);
10270 let err = c.validate_code_paths().unwrap_err();
10271 assert!(
10272 matches!(
10273 err,
10274 ManifestError::CodePathEmpty {
10275 slot: ":bibliotecas"
10276 }
10277 ),
10278 "got {err:?}",
10279 );
10280 }
10281
10282 #[test]
10283 fn validate_code_paths_first_offender_per_slot_wins() {
10284 // Within a single slot, the first declaration-order offender
10285 // surfaces — pins that the gate is left-to-right deterministic
10286 // (peer of every `*_first_collision_*` pin on duplicate gates).
10287 let c = caixa_with_code_paths(
10288 vec!["lib/ok.lisp", "/etc/escape", "../also-escape"],
10289 vec![],
10290 vec![],
10291 );
10292 let err = c.validate_code_paths().unwrap_err();
10293 let ManifestError::CodePathAbsolute { slot, path } = err else {
10294 panic!("expected CodePathAbsolute, got {err:?}");
10295 };
10296 assert_eq!(slot, ":bibliotecas");
10297 assert_eq!(path, PathBuf::from("/etc/escape"));
10298 }
10299
10300 #[test]
10301 fn validate_code_paths_diagnostic_carries_offending_slot_and_path() {
10302 // Diagnostic-shape pin (peer with
10303 // `nome_invalid_diagnostic_carries_offending_nome` /
10304 // `versao_invalid_diagnostic_carries_offending_versao`): the
10305 // error's Display surfaces both the offending `:slot` tag and
10306 // the offending path verbatim, so a `feira lint` run can render
10307 // the diagnostic without re-parsing.
10308 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
10309 let rendered = c.validate_code_paths().unwrap_err().to_string();
10310 assert!(
10311 rendered.contains(":bibliotecas"),
10312 "diagnostic must name the offending slot: {rendered}",
10313 );
10314 assert!(
10315 rendered.contains("/etc/passwd"),
10316 "diagnostic must quote the offending path: {rendered}",
10317 );
10318 }
10319
10320 #[test]
10321 fn validate_code_paths_rejects_duplicate_bibliotecas_entry() {
10322 // Canonical copy-paste-the-wrong-file footgun on the biblioteca
10323 // axis. Without the gate `feira build` re-parses the same lib
10324 // twice, wasting work and silently masking the author's intent
10325 // to declare a *second* biblioteca.
10326 let c = caixa_with_code_paths(vec!["lib/demo.lisp", "lib/demo.lisp"], vec![], vec![]);
10327 let err = c.validate_code_paths().unwrap_err();
10328 let ManifestError::CodePathDuplicate { slot, path } = err else {
10329 panic!("expected CodePathDuplicate, got {err:?}");
10330 };
10331 assert_eq!(slot, ":bibliotecas");
10332 assert_eq!(path, PathBuf::from("lib/demo.lisp"));
10333 }
10334
10335 #[test]
10336 fn validate_code_paths_rejects_duplicate_exe_entry() {
10337 // Same footgun on the Binario surface. The future `caixa-flake`
10338 // emitter that materializes each `:exe` entry as a flake
10339 // `packages.<name>` derivation would collide on the duplicate
10340 // package key — surfaced here at the typed-validate layer with a
10341 // self-locating diagnostic instead.
10342 let c = caixa_with_code_paths(vec![], vec!["exe/cli", "exe/cli"], vec![]);
10343 let err = c.validate_code_paths().unwrap_err();
10344 let ManifestError::CodePathDuplicate { slot, path } = err else {
10345 panic!("expected CodePathDuplicate, got {err:?}");
10346 };
10347 assert_eq!(slot, ":exe");
10348 assert_eq!(path, PathBuf::from("exe/cli"));
10349 }
10350
10351 #[test]
10352 fn validate_code_paths_rejects_duplicate_servicos_entry() {
10353 // Same footgun on the Servico surface. The peer caixa-helm /
10354 // caixa-flux renderers refuse `:servicos.len() != 1` with the
10355 // narrower `UnsupportedServicoCount` diagnostic, but that
10356 // diagnostic surfaces "too many servicos" without naming
10357 // "duplicate entry" — the typed self-locating framing only lands
10358 // at this gate.
10359 let c = caixa_with_code_paths(
10360 vec![],
10361 vec![],
10362 vec![
10363 "servicos/demo.computeunit.yaml",
10364 "servicos/demo.computeunit.yaml",
10365 ],
10366 );
10367 let err = c.validate_code_paths().unwrap_err();
10368 let ManifestError::CodePathDuplicate { slot, path } = err else {
10369 panic!("expected CodePathDuplicate, got {err:?}");
10370 };
10371 assert_eq!(slot, ":servicos");
10372 assert_eq!(path, PathBuf::from("servicos/demo.computeunit.yaml"));
10373 }
10374
10375 #[test]
10376 fn validate_code_paths_accepts_same_path_across_slots() {
10377 // Per-list scope pin: a `:bibliotecas` entry that happens to
10378 // collide with an `:exe` or `:servicos` entry as a *string* is
10379 // not a duplicate by this gate (each list gets its own HashSet),
10380 // mirroring the peer `:deps` ↔ `:deps-dev` per-list scope
10381 // (a `:nome` present in both lists is a legitimate dev-vs-runtime
10382 // shape on the dep axis). The structural `starts_with(<exe |
10383 // servicos>_dir)` fence at layout time prevents the realistic
10384 // cross-slot collision case from existing on disk, but the gate's
10385 // per-list scope is correct independent of that downstream fence.
10386 let c = caixa_with_code_paths(
10387 vec!["lib/x.lisp"],
10388 vec!["exe/x"],
10389 vec!["servicos/x.computeunit.yaml"],
10390 );
10391 c.validate_code_paths().unwrap();
10392 }
10393
10394 #[test]
10395 fn validate_code_paths_duplicate_fires_after_structural_checks_on_same_slot() {
10396 // Within-slot ordering pin: structural defects (empty / absolute
10397 // / parent-escape) fire before the duplicate gate on the same
10398 // slot. A `:bibliotecas ("" "lib/x.lisp" "lib/x.lisp")` shape
10399 // surfaces the narrower `CodePathEmpty` for the empty entry
10400 // first, not the duplicate on the later pair — same arm-ordering
10401 // every peer per-list duplicate gate uses (`:etiquetas` 360a499,
10402 // `:autores` 86c769b, `:deps` 359fba5).
10403 let c = caixa_with_code_paths(vec!["", "lib/x.lisp", "lib/x.lisp"], vec![], vec![]);
10404 let err = c.validate_code_paths().unwrap_err();
10405 assert!(
10406 matches!(
10407 err,
10408 ManifestError::CodePathEmpty {
10409 slot: ":bibliotecas"
10410 }
10411 ),
10412 "got {err:?}",
10413 );
10414 }
10415
10416 #[test]
10417 fn validate_code_paths_duplicate_in_bibliotecas_fires_before_duplicate_in_exe() {
10418 // Cross-slot ordering pin on the duplicate arm: `:bibliotecas`
10419 // duplicates surface before `:exe` duplicates, matching the
10420 // canonical `:bibliotecas` → `:exe` → `:servicos` declaration
10421 // order every peer per-slot diagnostic on this surface follows.
10422 let c = caixa_with_code_paths(
10423 vec!["lib/x.lisp", "lib/x.lisp"],
10424 vec!["exe/y", "exe/y"],
10425 vec![],
10426 );
10427 let err = c.validate_code_paths().unwrap_err();
10428 let ManifestError::CodePathDuplicate { slot, path } = err else {
10429 panic!("expected CodePathDuplicate, got {err:?}");
10430 };
10431 assert_eq!(slot, ":bibliotecas");
10432 assert_eq!(path, PathBuf::from("lib/x.lisp"));
10433 }
10434
10435 #[test]
10436 fn validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path() {
10437 // Diagnostic-shape pin (peer with
10438 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
10439 // on the structural arm): the duplicate-arm Display surfaces both
10440 // the offending `:slot` tag and the offending path verbatim, so a
10441 // `feira lint` run can render the diagnostic without re-parsing.
10442 let c = caixa_with_code_paths(
10443 vec![],
10444 vec![],
10445 vec![
10446 "servicos/demo.computeunit.yaml",
10447 "servicos/demo.computeunit.yaml",
10448 ],
10449 );
10450 let rendered = c.validate_code_paths().unwrap_err().to_string();
10451 assert!(
10452 rendered.contains(":servicos"),
10453 "diagnostic must name the offending slot: {rendered}",
10454 );
10455 assert!(
10456 rendered.contains("servicos/demo.computeunit.yaml"),
10457 "diagnostic must quote the offending path: {rendered}",
10458 );
10459 }
10460
10461 // ── validate_code_paths — `.lisp` extension gate on :bibliotecas ──
10462 //
10463 // The lifted [`crate::render::is_lisp_extension`] predicate (33cc830)
10464 // now gates `:bibliotecas` entries on the tatara-lisp-source file-type
10465 // contract. The `feira build` loop (`caixa-feira/src/cmd/build.rs:33`)
10466 // reads every declared `:bibliotecas` entry through `tatara_lisp::read`
10467 // at parse time — the same downstream consumer the peer `:behavior
10468 // :on-*` (c97815a, [`crate::BehaviorError::NonLispExtension`]) and
10469 // `:upgrade-from :state-change :script` (33cc830,
10470 // [`crate::UpgradeError::NonLispExtensionScript`]) axes route through.
10471 // `:exe` and `:servicos` are deliberately excluded — `:exe` is the
10472 // nix-built executable surface (`"exe/<name>"` shape per the canonical
10473 // [`crate::LayoutError::ExeOutsideDir`] error message and every
10474 // in-tree `caixa_with_code_paths` positive control), and `:servicos`
10475 // is the `.computeunit.yaml` ComputeUnit-CR axis.
10476
10477 #[test]
10478 fn validate_code_paths_rejects_no_extension_bibliotecas_entry() {
10479 // Canonical "I dragged the wrong file from the workspace tree"
10480 // footgun on the biblioteca axis. Without the gate `feira build`
10481 // hands the extensionless path to `tatara_lisp::read` and fails
10482 // with a parser-shaped diagnostic far from the source caixa.lisp,
10483 // with no field naming the offending `:bibliotecas` entry.
10484 for relpath in ["lib/demo", "demo", "lib/handlers/inner"] {
10485 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
10486 let err = c.validate_code_paths().unwrap_err();
10487 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
10488 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
10489 };
10490 assert_eq!(slot, ":bibliotecas");
10491 assert_eq!(path, PathBuf::from(relpath));
10492 }
10493 }
10494
10495 #[test]
10496 fn validate_code_paths_rejects_wrong_extension_bibliotecas_entry() {
10497 // Wrong-extension sweep across common authoring footguns. Same
10498 // sweep posture as the peer
10499 // `behavior::validate_rejects_wrong_extension` (c97815a) and
10500 // `upgrade::tests::state_change_rejects_wrong_extension_script`
10501 // (33cc830) cases.
10502 for relpath in [
10503 "lib/demo.rs",
10504 "lib/demo.txt",
10505 "lib/demo.md",
10506 "lib/demo.json",
10507 "lib/demo.yaml",
10508 "lib/demo.toml",
10509 "lib/demo.lisp.bak",
10510 "lib/demo.lispx",
10511 "lib/demo.lis",
10512 ] {
10513 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
10514 let err = c.validate_code_paths().unwrap_err();
10515 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
10516 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
10517 };
10518 assert_eq!(slot, ":bibliotecas");
10519 assert_eq!(path, PathBuf::from(relpath));
10520 }
10521 }
10522
10523 #[test]
10524 fn validate_code_paths_rejects_case_folded_extension_bibliotecas_entry() {
10525 // Case-sensitivity sweep — pins the strict lowercase `.lisp`
10526 // contract. An uppercase `.LISP` shape that the layout's existence
10527 // check would (case-insensitively, on case-insensitive volumes)
10528 // match the on-disk file still mismatches the canonical form the
10529 // codec emits, breaking the THEORY.md §V.2.7 render-determinism
10530 // contract. Mirrors the peer
10531 // `behavior::validate_rejects_case_folded_extension` (c97815a) and
10532 // `upgrade::tests::state_change_rejects_case_folded_extension_script`
10533 // (33cc830) sweeps.
10534 for relpath in [
10535 "lib/demo.LISP",
10536 "lib/demo.Lisp",
10537 "lib/demo.LiSp",
10538 "lib/demo.lISP",
10539 ] {
10540 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
10541 let err = c.validate_code_paths().unwrap_err();
10542 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
10543 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
10544 };
10545 assert_eq!(slot, ":bibliotecas");
10546 assert_eq!(path, PathBuf::from(relpath));
10547 }
10548 }
10549
10550 #[test]
10551 fn validate_code_paths_accepts_canonical_lisp_shapes() {
10552 // Positive-control sweep through every canonical authoring shape
10553 // every in-tree fixture and the `Caixa::template` scaffold use.
10554 // Mirrors the peer `behavior::validate_accepts_canonical_lisp_paths`
10555 // (c97815a) and the lifted predicate's own
10556 // `is_lisp_extension_accepts_canonical_shapes` sweep in render.rs
10557 // (33cc830).
10558 for relpath in [
10559 "lib/demo.lisp",
10560 "lib/handlers.lisp",
10561 "lib/migrations/v01-to-v02.lisp",
10562 "demo.lisp",
10563 "a.lisp",
10564 "./lib/demo.lisp",
10565 "lib/./handlers.lisp",
10566 "lib/migrations/v.0.1.lisp",
10567 ] {
10568 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
10569 c.validate_code_paths()
10570 .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
10571 }
10572 }
10573
10574 #[test]
10575 fn validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos() {
10576 // The file-type gate is per-slot — only `:bibliotecas` carries the
10577 // tatara-lisp-source contract. An extensionless `:exe` entry
10578 // (`exe/demo`) and a `.computeunit.yaml` `:servicos` entry are the
10579 // canonical shapes every in-tree fixture uses, and must continue
10580 // to pass validate. Pins that a future tightening that broadens
10581 // the `.lisp` gate to either axis surfaces as a test failure
10582 // rather than as a silent breaking change to existing valid
10583 // manifests.
10584 let c = caixa_with_code_paths(
10585 vec![],
10586 vec!["exe/demo", "exe/tool"],
10587 vec!["servicos/demo.computeunit.yaml"],
10588 );
10589 c.validate_code_paths().unwrap();
10590 }
10591
10592 #[test]
10593 fn validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension() {
10594 // Cross-arm precedence pin: a `:bibliotecas` entry that is *both*
10595 // sandbox-escaping and non-`.lisp` surfaces the more fundamental
10596 // sandbox-shape diagnostic first (the `.lisp` remediation would
10597 // be misleading when the offending path can never resolve under
10598 // the caixa root anyway). Mirrors the peer
10599 // `EmptyPath` → `AbsolutePath` → `ParentEscape` → `NonLispExtension`
10600 // ordering on `:behavior :on-*` (c97815a) and `EmptyScript` →
10601 // `AbsoluteScript` → `ParentEscapeScript` → `NonLispExtensionScript`
10602 // on `:upgrade-from :state-change :script` (33cc830).
10603 //
10604 // Empty wins (the strictly-smaller-scope structural arm).
10605 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
10606 assert!(
10607 matches!(
10608 c.validate_code_paths().unwrap_err(),
10609 ManifestError::CodePathEmpty {
10610 slot: ":bibliotecas"
10611 }
10612 ),
10613 "empty must win over non-lisp-extension",
10614 );
10615 // Absolute wins (the path can't resolve under the caixa root).
10616 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
10617 let err = c.validate_code_paths().unwrap_err();
10618 let ManifestError::CodePathAbsolute { slot, .. } = err else {
10619 panic!("absolute must win over non-lisp-extension, got {err:?}");
10620 };
10621 assert_eq!(slot, ":bibliotecas");
10622 // ParentEscape wins (the path escapes the caixa root).
10623 let c = caixa_with_code_paths(vec!["../sibling/x.txt"], vec![], vec![]);
10624 let err = c.validate_code_paths().unwrap_err();
10625 let ManifestError::CodePathParentEscape { slot, .. } = err else {
10626 panic!("parent-escape must win over non-lisp-extension, got {err:?}");
10627 };
10628 assert_eq!(slot, ":bibliotecas");
10629 }
10630
10631 #[test]
10632 fn validate_code_paths_non_lisp_extension_precedes_duplicate() {
10633 // Within-slot precedence pin: the per-entry file-type shape gate
10634 // fires before the cross-entry duplicate gate, so the narrower
10635 // structural defect dominates the uniqueness diagnostic. A
10636 // `("lib/x.txt" "lib/x.txt")` shape surfaces
10637 // `CodePathNonLispExtension` on the first entry rather than
10638 // `CodePathDuplicate` on the pair — same posture every per-entry
10639 // shape-gate-precedes-duplicate cascade follows on this surface
10640 // (the empty / absolute / parent-escape arms already precede the
10641 // duplicate arm; the lifted file-type arm joins that set).
10642 let c = caixa_with_code_paths(vec!["lib/x.txt", "lib/x.txt"], vec![], vec![]);
10643 let err = c.validate_code_paths().unwrap_err();
10644 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
10645 panic!("expected CodePathNonLispExtension, got {err:?}");
10646 };
10647 assert_eq!(slot, ":bibliotecas");
10648 assert_eq!(path, PathBuf::from("lib/x.txt"));
10649 }
10650
10651 #[test]
10652 fn validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path() {
10653 // Diagnostic-shape pin (peer with
10654 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
10655 // on the sandbox-shape arms and
10656 // `validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path`
10657 // on the duplicate arm): the file-type-arm Display surfaces both
10658 // the offending `:slot` tag, the offending path verbatim, and the
10659 // expected `.lisp` extension named in the remediation text, so a
10660 // `feira lint` run can render the diagnostic without re-parsing.
10661 let c = caixa_with_code_paths(vec!["lib/demo.rs"], vec![], vec![]);
10662 let rendered = c.validate_code_paths().unwrap_err().to_string();
10663 assert!(
10664 rendered.contains(":bibliotecas"),
10665 "diagnostic must name the offending slot: {rendered}",
10666 );
10667 assert!(
10668 rendered.contains("lib/demo.rs"),
10669 "diagnostic must quote the offending path: {rendered}",
10670 );
10671 assert!(
10672 rendered.contains(".lisp"),
10673 "diagnostic must name the expected extension: {rendered}",
10674 );
10675 }
10676
10677 // ── validate_code_paths — `.computeunit.yaml` compound-suffix gate on :servicos ──
10678 //
10679 // The lifted [`crate::render::is_computeunit_yaml_extension`] predicate
10680 // now gates `:servicos` entries on the ComputeUnit-CR YAML file-type
10681 // contract. The peer caixa-helm / caixa-flux renderers consume each
10682 // `:servicos` entry through `serde_yaml::from_str` as a typed
10683 // `ComputeUnit` CR — same downstream-consumer-shape lift as the peer
10684 // `:bibliotecas` `.lisp` gate (64772a9), here on the compound-suffix
10685 // axis `Path::extension` can't express on its own.
10686
10687 #[test]
10688 fn validate_code_paths_rejects_no_extension_servicos_entry() {
10689 // Canonical "I dragged the wrong file from the workspace tree"
10690 // footgun on the Servico axis. Without the gate the peer
10691 // caixa-helm / caixa-flux renderers hand the extensionless path
10692 // to `serde_yaml::from_str` and fail with a parser-shaped
10693 // diagnostic far from the source caixa.lisp, with no field
10694 // naming the offending `:servicos` entry.
10695 for relpath in ["servicos/demo", "demo", "servicos/sub/nested"] {
10696 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
10697 let err = c.validate_code_paths().unwrap_err();
10698 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
10699 panic!(
10700 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
10701 got {err:?}"
10702 );
10703 };
10704 assert_eq!(slot, ":servicos");
10705 assert_eq!(path, PathBuf::from(relpath));
10706 }
10707 }
10708
10709 #[test]
10710 fn validate_code_paths_rejects_wrong_extension_servicos_entry() {
10711 // Wrong-extension sweep across common authoring footguns on the
10712 // Servico axis. Bare `.yaml` is the canonical "I forgot the
10713 // `.computeunit` segment" typo; the off-by-one-segment shapes
10714 // (`.computeunit-yaml` / `.computeunit_yaml`) silently pass the
10715 // bare `Path::extension` view but mismatch the typed compound
10716 // suffix the renderers' `serde_yaml::from_str` consumer demands.
10717 // Same sweep-posture as the peer
10718 // `validate_code_paths_rejects_wrong_extension_bibliotecas_entry`
10719 // (64772a9) on the sibling tatara-lisp-source axis.
10720 for relpath in [
10721 "servicos/demo.yaml",
10722 "servicos/demo.yml",
10723 "servicos/demo.json",
10724 "servicos/demo.toml",
10725 "servicos/demo.txt",
10726 "servicos/demo.computeunit.yaml.bak",
10727 "servicos/demo.computeunit.yam",
10728 "servicos/demo.computeunit",
10729 "servicos/demo-computeunit.yaml",
10730 "servicos/demo_computeunit.yaml",
10731 ] {
10732 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
10733 let err = c.validate_code_paths().unwrap_err();
10734 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
10735 panic!(
10736 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
10737 got {err:?}"
10738 );
10739 };
10740 assert_eq!(slot, ":servicos");
10741 assert_eq!(path, PathBuf::from(relpath));
10742 }
10743 }
10744
10745 #[test]
10746 fn validate_code_paths_rejects_case_folded_extension_servicos_entry() {
10747 // Case-sensitivity sweep — pins the strict lowercase
10748 // `.computeunit.yaml` contract. A case-folded shape that the
10749 // layout's existence check would (case-insensitively, on
10750 // case-insensitive volumes) match the on-disk file still
10751 // mismatches the canonical form the codec emits, breaking the
10752 // THEORY.md §V.2.7 render-determinism contract. Mirrors the peer
10753 // `validate_code_paths_rejects_case_folded_extension_bibliotecas_entry`
10754 // (64772a9) sweep on the sibling tatara-lisp-source axis.
10755 for relpath in [
10756 "servicos/demo.ComputeUnit.yaml",
10757 "servicos/demo.COMPUTEUNIT.yaml",
10758 "servicos/demo.computeunit.YAML",
10759 "servicos/demo.computeunit.Yaml",
10760 "servicos/demo.COMPUTEUNIT.YAML",
10761 ] {
10762 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
10763 let err = c.validate_code_paths().unwrap_err();
10764 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
10765 panic!(
10766 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
10767 got {err:?}"
10768 );
10769 };
10770 assert_eq!(slot, ":servicos");
10771 assert_eq!(path, PathBuf::from(relpath));
10772 }
10773 }
10774
10775 #[test]
10776 fn validate_code_paths_rejects_empty_stem_servicos_entry() {
10777 // Degenerate hidden-file shape: a file name exactly equal to the
10778 // suffix (`.computeunit.yaml` — no stem preceding the suffix) is
10779 // the structural "Servico declared with no identity" footgun.
10780 // The substrate identifies each ComputeUnit by the file-stem
10781 // segment that precedes `.computeunit.yaml` (the rendered
10782 // `lareira-<stem>` Helm chart, the per-Servico `metadata.name`,
10783 // the M3 `:contratos` membership lookup), so an empty stem
10784 // leaves the Servico unidentifiable. Pinned at the typed-axis
10785 // level so a future regression that drops the `name.len() >
10786 // SUFFIX.len()` bound at the predicate surfaces here, not
10787 // piecemeal as a `lareira-` chart-name collision at render time.
10788 for relpath in ["servicos/.computeunit.yaml"] {
10789 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
10790 let err = c.validate_code_paths().unwrap_err();
10791 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
10792 panic!(
10793 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
10794 got {err:?}"
10795 );
10796 };
10797 assert_eq!(slot, ":servicos");
10798 assert_eq!(path, PathBuf::from(relpath));
10799 }
10800 }
10801
10802 #[test]
10803 fn validate_code_paths_accepts_canonical_computeunit_yaml_shapes() {
10804 // Positive-control sweep through every canonical authoring shape
10805 // every in-tree fixture and the `Caixa::template` scaffold use.
10806 // Mirrors the peer
10807 // `validate_code_paths_accepts_canonical_lisp_shapes` (64772a9)
10808 // and the lifted predicate's own
10809 // `computeunit_yaml_extension_accepts_canonical_shapes` sweep in
10810 // render.rs.
10811 for relpath in [
10812 "servicos/demo.computeunit.yaml",
10813 "servicos/hello-rio.computeunit.yaml",
10814 "servicos/my-service.computeunit.yaml",
10815 "servicos/a.computeunit.yaml",
10816 "./servicos/demo.computeunit.yaml",
10817 "servicos/./demo.computeunit.yaml",
10818 "servicos/sub/nested.computeunit.yaml",
10819 "servicos/v0.1.computeunit.yaml",
10820 ] {
10821 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
10822 c.validate_code_paths()
10823 .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
10824 }
10825 }
10826
10827 #[test]
10828 fn validate_code_paths_non_computeunit_yaml_extension_does_not_fire_on_bibliotecas_or_exe() {
10829 // The file-type gate is per-slot — only `:servicos` carries the
10830 // ComputeUnit-CR YAML contract. A canonical `.lisp` `:bibliotecas`
10831 // entry and an extensionless `:exe` entry are the canonical
10832 // shapes every in-tree fixture uses, and must continue to pass
10833 // validate. Peer of
10834 // `validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos`
10835 // (64772a9) — together pin that the typed
10836 // [`CodePathFileType`] dispatch is exhaustively per-slot, with no
10837 // cross-axis leakage in either direction.
10838 let c = caixa_with_code_paths(
10839 vec!["lib/demo.lisp"],
10840 vec!["exe/demo", "exe/tool"],
10841 vec!["servicos/demo.computeunit.yaml"],
10842 );
10843 c.validate_code_paths().unwrap();
10844 }
10845
10846 #[test]
10847 fn validate_code_paths_sandbox_shape_arms_precede_non_computeunit_yaml_extension() {
10848 // Cross-arm precedence pin: a `:servicos` entry that is *both*
10849 // sandbox-escaping and wrong-extension surfaces the more
10850 // fundamental sandbox-shape diagnostic first (the
10851 // `.computeunit.yaml` remediation would be misleading when the
10852 // offending path can never resolve under the caixa root
10853 // anyway). Mirrors the peer
10854 // `validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension`
10855 // (64772a9) ordering on the sibling `:bibliotecas` axis and the
10856 // peer `EmptyPath` → `AbsolutePath` → `ParentEscape` →
10857 // `NonComputeUnitYamlExtension` arm-ordering the dispatch
10858 // table establishes.
10859 //
10860 // Empty wins (the strictly-smaller-scope structural arm).
10861 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
10862 assert!(
10863 matches!(
10864 c.validate_code_paths().unwrap_err(),
10865 ManifestError::CodePathEmpty { slot: ":servicos" }
10866 ),
10867 "empty must win over non-computeunit-yaml-extension",
10868 );
10869 // Absolute wins (the path can't resolve under the caixa root).
10870 let c = caixa_with_code_paths(vec![], vec![], vec!["/etc/foo.yaml"]);
10871 let err = c.validate_code_paths().unwrap_err();
10872 let ManifestError::CodePathAbsolute { slot, .. } = err else {
10873 panic!("absolute must win over non-computeunit-yaml-extension, got {err:?}");
10874 };
10875 assert_eq!(slot, ":servicos");
10876 // ParentEscape wins (the path escapes the caixa root).
10877 let c = caixa_with_code_paths(vec![], vec![], vec!["../sibling/x.yaml"]);
10878 let err = c.validate_code_paths().unwrap_err();
10879 let ManifestError::CodePathParentEscape { slot, .. } = err else {
10880 panic!("parent-escape must win over non-computeunit-yaml-extension, got {err:?}");
10881 };
10882 assert_eq!(slot, ":servicos");
10883 }
10884
10885 #[test]
10886 fn validate_code_paths_non_computeunit_yaml_extension_precedes_duplicate() {
10887 // Within-slot precedence pin: the per-entry file-type shape gate
10888 // fires before the cross-entry duplicate gate, so the narrower
10889 // structural defect dominates the uniqueness diagnostic. A
10890 // `("servicos/x.yaml" "servicos/x.yaml")` shape surfaces
10891 // `CodePathNonComputeUnitYamlExtension` on the first entry
10892 // rather than `CodePathDuplicate` on the pair — same posture
10893 // every per-entry shape-gate-precedes-duplicate cascade follows
10894 // on this surface, peer of the 64772a9 `:bibliotecas`
10895 // `("lib/x.txt" "lib/x.txt")` ordering.
10896 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/x.yaml", "servicos/x.yaml"]);
10897 let err = c.validate_code_paths().unwrap_err();
10898 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
10899 panic!("expected CodePathNonComputeUnitYamlExtension, got {err:?}");
10900 };
10901 assert_eq!(slot, ":servicos");
10902 assert_eq!(path, PathBuf::from("servicos/x.yaml"));
10903 }
10904
10905 #[test]
10906 fn validate_code_paths_non_computeunit_yaml_extension_diagnostic_carries_offending_slot_and_path()
10907 {
10908 // Diagnostic-shape pin (peer with
10909 // `validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path`
10910 // on the sibling tatara-lisp-source axis): the file-type-arm
10911 // Display surfaces both the offending `:slot` tag, the
10912 // offending path verbatim, and the expected
10913 // `.computeunit.yaml` compound suffix named in the remediation
10914 // text, so a `feira lint` run can render the diagnostic without
10915 // re-parsing.
10916 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.yaml"]);
10917 let rendered = c.validate_code_paths().unwrap_err().to_string();
10918 assert!(
10919 rendered.contains(":servicos"),
10920 "diagnostic must name the offending slot: {rendered}",
10921 );
10922 assert!(
10923 rendered.contains("servicos/demo.yaml"),
10924 "diagnostic must quote the offending path: {rendered}",
10925 );
10926 assert!(
10927 rendered.contains(".computeunit.yaml"),
10928 "diagnostic must name the expected compound suffix: {rendered}",
10929 );
10930 }
10931
10932 // ── validate_etiquetas — universal-axis registry-search-tag shape ──
10933
10934 fn caixa_with_etiquetas(etiquetas: Vec<&str>) -> Caixa {
10935 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10936 c.etiquetas = etiquetas.into_iter().map(String::from).collect();
10937 c
10938 }
10939
10940 #[test]
10941 fn validate_etiquetas_accepts_empty_list() {
10942 // The empty-list identity: every caixa with no declared tags
10943 // trivially passes — `Caixa::template` emits `:etiquetas ()`,
10944 // so the gate is non-disruptive against every existing manifest.
10945 let c = caixa_with_etiquetas(vec![]);
10946 c.validate_etiquetas().unwrap();
10947 }
10948
10949 #[test]
10950 fn validate_etiquetas_accepts_canonical_forms() {
10951 // Positive control sweep: a canonical-shaped non-empty distinct
10952 // tag list passes, mirroring the example checkout-aplicacao
10953 // (`:etiquetas ("example" "aplicacao" "mesh" "ecommerce" "demo")`)
10954 // and the hello-rio fixture (`("hello-world" "wasm" "rust")`).
10955 let c = caixa_with_etiquetas(vec!["example", "aplicacao", "mesh", "ecommerce", "demo"]);
10956 c.validate_etiquetas().unwrap();
10957 }
10958
10959 #[test]
10960 fn validate_etiquetas_rejects_empty_entry() {
10961 // Canonical paste-from-blank-doc footgun. Without the gate the
10962 // empty entry rendered as `keywords: [""]` in `Chart.yaml`, a
10963 // no-op tag indexing nothing in the future caixa-registry.
10964 let c = caixa_with_etiquetas(vec![""]);
10965 let err = c.validate_etiquetas().unwrap_err();
10966 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
10967 }
10968
10969 #[test]
10970 fn validate_etiquetas_rejects_duplicate_entry() {
10971 // Canonical copy-paste-the-wrong-tag footgun. Without the gate
10972 // the duplicate was silently dedup'd by caixa-helm's BTreeSet
10973 // collect at chart render — a "second wins / one silently
10974 // disappears" shape divergent from every peer typed-graph set
10975 // gate. The duplicate-arm names the offending tag verbatim.
10976 let c = caixa_with_etiquetas(vec!["demo", "demo"]);
10977 let err = c.validate_etiquetas().unwrap_err();
10978 let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
10979 panic!("expected EtiquetaDuplicate, got {err:?}");
10980 };
10981 assert_eq!(etiqueta, "demo");
10982 }
10983
10984 #[test]
10985 fn validate_etiquetas_empty_takes_precedence_over_duplicate() {
10986 // Empty-first cascade pin: `("" "demo" "demo")` surfaces
10987 // `EtiquetaEmpty` not `EtiquetaDuplicate` — the narrower
10988 // structural "this entry has no value" defect dominates the
10989 // cross-entry uniqueness diagnostic. Mirrors the peer
10990 // empty-before-duplicate cascades on `:caracteristicas`
10991 // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
10992 // fc3b4d5) and `:membros :caixa` (`MembroCaixaEmpty` before
10993 // `MembroDuplicate`).
10994 let c = caixa_with_etiquetas(vec!["", "demo", "demo"]);
10995 let err = c.validate_etiquetas().unwrap_err();
10996 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
10997 }
10998
10999 #[test]
11000 fn validate_etiquetas_duplicate_reports_first_collision() {
11001 // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
11002 // duplicate (the lexicographically-earliest offending position
11003 // — the second `"a"` at index 2 collides with the first `"a"`
11004 // at index 0), not the later `"b"` collision at index 3,
11005 // peer with every other first-collision diagnostic posture on
11006 // this surface (`validate_load_singularity_reports_first_collision`,
11007 // `validate_cleanup_singularity_reports_first_collision`).
11008 let c = caixa_with_etiquetas(vec!["a", "b", "a", "b"]);
11009 let err = c.validate_etiquetas().unwrap_err();
11010 let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
11011 panic!("expected EtiquetaDuplicate, got {err:?}");
11012 };
11013 assert_eq!(etiqueta, "a");
11014 }
11015
11016 #[test]
11017 fn validate_etiquetas_case_sensitive() {
11018 // Case-sensitivity pin: `("Foo" "foo")` is two distinct entries,
11019 // mirroring the peer `:membros :caixa` / `:children :caixa`
11020 // exact-string-match discipline. The shape gate this routine
11021 // landed (`is_chart_keyword_shape`, Cargo crates.io keyword
11022 // grammar) accepts mixed case — crates.io's keyword rule is
11023 // "case-insensitive" at the index layer but admits mixed case
11024 // at the entry layer (the canonical Helm chart `keywords:`
11025 // shape is lowercase by convention, but the grammar admits
11026 // uppercase). Case-sensitivity at the duplicate-set layer
11027 // remains structural — two distinct strings are two distinct
11028 // entries.
11029 let c = caixa_with_etiquetas(vec!["Foo", "foo"]);
11030 c.validate_etiquetas().unwrap();
11031 }
11032
11033 #[test]
11034 fn validate_etiquetas_diagnostic_carries_offending_tag() {
11035 // Diagnostic-shape pin (peer with
11036 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`):
11037 // the error's Display surfaces the offending tag verbatim, so a
11038 // `feira lint` run can render the diagnostic without re-parsing
11039 // and the author can grep their caixa.lisp for the offending
11040 // value.
11041 let c = caixa_with_etiquetas(vec!["demo", "demo"]);
11042 let rendered = c.validate_etiquetas().unwrap_err().to_string();
11043 assert!(
11044 rendered.contains(":etiquetas"),
11045 "diagnostic must name the offending slot: {rendered}",
11046 );
11047 assert!(
11048 rendered.contains("demo"),
11049 "diagnostic must quote the offending tag: {rendered}",
11050 );
11051 }
11052
11053 #[test]
11054 fn validate_etiquetas_rejects_leading_whitespace_entry() {
11055 // Canonical paste-from-aligned-doc footgun. Without the shape
11056 // gate `" mesh"` silently passed validate and landed as a
11057 // YAML plain-style scalar with leading whitespace in the
11058 // rendered Chart.yaml `keywords:` array — every YAML 1.2
11059 // dumper trims leading whitespace from plain-style scalars,
11060 // so the authored space round-tripped inconsistently back
11061 // through `caixa.lisp`. Mirrors the peer
11062 // `validate_autores_rejects_leading_whitespace_entry`.
11063 let c = caixa_with_etiquetas(vec![" mesh"]);
11064 let err = c.validate_etiquetas().unwrap_err();
11065 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11066 panic!("expected EtiquetaInvalid, got {err:?}");
11067 };
11068 assert_eq!(etiqueta, " mesh");
11069 assert!(reason.contains("whitespace"), "got: {reason}");
11070 }
11071
11072 #[test]
11073 fn validate_etiquetas_rejects_embedded_newline_entry() {
11074 // Canonical paste-from-multiline-doc footgun — the author
11075 // pasted a multi-tag block into one `:etiquetas` entry
11076 // instead of splitting into one entry per tag. Without the
11077 // shape gate `"mesh\nhttp"` silently passed validate and
11078 // landed as a YAML-illegal multi-line scalar in the rendered
11079 // Chart.yaml `keywords:` array.
11080 let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
11081 let err = c.validate_etiquetas().unwrap_err();
11082 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11083 panic!("expected EtiquetaInvalid, got {err:?}");
11084 };
11085 assert_eq!(etiqueta, "mesh\nhttp");
11086 assert!(reason.contains("newline"), "got: {reason}");
11087 }
11088
11089 #[test]
11090 fn validate_etiquetas_rejects_embedded_comma_entry() {
11091 // Canonical CSV-list-separator-confusion footgun: the author
11092 // confused the CSV-style separator convention with the
11093 // `:etiquetas` list grammar. Without the shape gate
11094 // `"mesh,http,grpc"` silently passed validate and landed as a
11095 // single malformed search tag in the rendered Chart.yaml
11096 // `keywords:` array — Artifact Hub's keyword index would
11097 // either silently drop the tag or index it as
11098 // `mesh,http,grpc` instead of three separate tags.
11099 let c = caixa_with_etiquetas(vec!["mesh,http,grpc"]);
11100 let err = c.validate_etiquetas().unwrap_err();
11101 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11102 panic!("expected EtiquetaInvalid, got {err:?}");
11103 };
11104 assert_eq!(etiqueta, "mesh,http,grpc");
11105 assert!(reason.contains('`'), "got: {reason}");
11106 assert!(reason.contains(','), "got: {reason}");
11107 }
11108
11109 #[test]
11110 fn validate_etiquetas_rejects_embedded_slash_entry() {
11111 // Canonical path-separator-confusion footgun: the author
11112 // confused namespace-path notation with the keyword grammar.
11113 let c = caixa_with_etiquetas(vec!["caixa/servico"]);
11114 let err = c.validate_etiquetas().unwrap_err();
11115 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11116 panic!("expected EtiquetaInvalid, got {err:?}");
11117 };
11118 assert_eq!(etiqueta, "caixa/servico");
11119 assert!(reason.contains('/'), "got: {reason}");
11120 }
11121
11122 #[test]
11123 fn validate_etiquetas_rejects_leading_digit_entry() {
11124 // Canonical paste-from-numbered-list footgun: the author
11125 // copied `1. mesh` from a numbered doc and the `1` leaked
11126 // into the tag.
11127 let c = caixa_with_etiquetas(vec!["1mesh"]);
11128 let err = c.validate_etiquetas().unwrap_err();
11129 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11130 panic!("expected EtiquetaInvalid, got {err:?}");
11131 };
11132 assert_eq!(etiqueta, "1mesh");
11133 assert!(reason.contains("digit"), "got: {reason}");
11134 }
11135
11136 #[test]
11137 fn validate_etiquetas_rejects_leading_hyphen_entry() {
11138 // Canonical kebab-leak footgun.
11139 let c = caixa_with_etiquetas(vec!["-foo"]);
11140 let err = c.validate_etiquetas().unwrap_err();
11141 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11142 panic!("expected EtiquetaInvalid, got {err:?}");
11143 };
11144 assert_eq!(etiqueta, "-foo");
11145 assert!(reason.contains('-'), "got: {reason}");
11146 }
11147
11148 #[test]
11149 fn validate_etiquetas_rejects_non_ascii_entry() {
11150 // Canonical paste-from-Unicode-doc footgun. Every legitimate
11151 // search tag is strict ASCII; raw non-ASCII silently
11152 // round-trips inconsistently across NFC/NFD normalization on
11153 // APFS / case-folding filesystems and breaks the Artifact Hub
11154 // keyword search index lookup.
11155 let c = caixa_with_etiquetas(vec!["café"]);
11156 let err = c.validate_etiquetas().unwrap_err();
11157 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11158 panic!("expected EtiquetaInvalid, got {err:?}");
11159 };
11160 assert_eq!(etiqueta, "café");
11161 assert!(reason.contains("non-ASCII"), "got: {reason}");
11162 }
11163
11164 #[test]
11165 fn validate_etiquetas_rejects_period_entry() {
11166 // Canonical namespace-confusion / version-suffix footgun
11167 // (`"http.1"` / `"v1.0"`): Cargo's crates.io keyword grammar
11168 // excludes `.` from the continuation set even though the
11169 // sibling `:caracteristicas` axis (Cargo's feature-name
11170 // grammar) admits it. Tighter than the sibling axis, peer
11171 // with Cargo's own crates.io keyword shape.
11172 let c = caixa_with_etiquetas(vec!["http.1"]);
11173 let err = c.validate_etiquetas().unwrap_err();
11174 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11175 panic!("expected EtiquetaInvalid, got {err:?}");
11176 };
11177 assert_eq!(etiqueta, "http.1");
11178 assert!(reason.contains('.'), "got: {reason}");
11179 }
11180
11181 #[test]
11182 fn validate_etiquetas_empty_takes_precedence_over_shape() {
11183 // Per-entry empty-first cascade pin: an entry that is both
11184 // empty *and* shape-invalid surfaces `EtiquetaEmpty` (the
11185 // narrower "this entry has no value" structural defect
11186 // dominates the broader shape-predicate diagnostic). The
11187 // empty arm fires before the shape predicate is consulted,
11188 // mirroring the peer `validate_autores_empty_takes_precedence_over_shape`
11189 // cascade established on the sibling universal-axis Vec<String>
11190 // surface.
11191 let c = caixa_with_etiquetas(vec![""]);
11192 let err = c.validate_etiquetas().unwrap_err();
11193 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
11194 }
11195
11196 #[test]
11197 fn validate_etiquetas_shape_takes_precedence_over_duplicate() {
11198 // Per-entry shape-before-cross-entry-duplicate cascade pin: an
11199 // entry that is malformed surfaces `EtiquetaInvalid` even when
11200 // a later entry would have collided on duplicate. The
11201 // per-entry shape arm fires inside the same loop iteration as
11202 // the empty arm, before the seen-set insert at end-of-iteration
11203 // — structural per-entry defects dominate the cross-entry
11204 // uniqueness diagnostic. Mirrors the peer
11205 // `validate_autores_shape_takes_precedence_over_duplicate`.
11206 let c = caixa_with_etiquetas(vec!["mesh\nhttp", "mesh\nhttp"]);
11207 let err = c.validate_etiquetas().unwrap_err();
11208 assert!(
11209 matches!(err, ManifestError::EtiquetaInvalid { .. }),
11210 "got {err:?}",
11211 );
11212 }
11213
11214 #[test]
11215 fn validate_etiquetas_invalid_diagnostic_names_offending_slot_and_value() {
11216 // Diagnostic-shape pin on the new shape arm (peer with
11217 // `validate_autores_invalid_diagnostic_names_offending_slot_and_value`):
11218 // the rendered Display surfaces both the offending slot name
11219 // and the offending value verbatim, so a `feira lint` run
11220 // points the author at the exact `:etiquetas` entry to fix.
11221 let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
11222 let rendered = c.validate_etiquetas().unwrap_err().to_string();
11223 assert!(
11224 rendered.contains(":etiquetas"),
11225 "diagnostic must name the offending slot: {rendered}",
11226 );
11227 assert!(
11228 rendered.contains("mesh\\nhttp"),
11229 "diagnostic must quote the offending value (debug-escaped): {rendered}",
11230 );
11231 }
11232
11233 #[test]
11234 fn validate_etiquetas_rejects_at_21_byte_boundary() {
11235 // The 20-byte cap pin — boundary-exceeding case rejected,
11236 // boundary-accepting case passes. Mirrors the peer
11237 // `chart_keyword_shape_rejects_at_21_byte_boundary` substrate-
11238 // side pin, surfaced at the per-axis caller so the cap
11239 // propagates through validate end-to-end. Constructed as a
11240 // single all-`a` token so only the cap arm fires.
11241 let max_ok = "a".repeat(20);
11242 let c = caixa_with_etiquetas(vec![max_ok.as_str()]);
11243 c.validate_etiquetas().unwrap();
11244 let too_long = "a".repeat(21);
11245 let c = caixa_with_etiquetas(vec![too_long.as_str()]);
11246 let err = c.validate_etiquetas().unwrap_err();
11247 let ManifestError::EtiquetaInvalid { reason, .. } = err else {
11248 panic!("expected EtiquetaInvalid, got {err:?}");
11249 };
11250 assert!(reason.contains("20"), "got: {reason}");
11251 assert!(reason.contains("21"), "got: {reason}");
11252 }
11253
11254 #[test]
11255 fn validate_etiquetas_accepts_canonical_shaped_forms() {
11256 // Positive control sweep: every canonical-shaped tag from the
11257 // hello-rio / checkout-aplicacao / pangea-tatara-akeyless
11258 // example fixtures plus the substrate-fixed tags caixa-helm
11259 // unions in at chart render. Drift between this list and the
11260 // substrate-side `chart_keyword_shape_accepts_canonical_forms`
11261 // sweep surfaces here — one source of truth for the rule.
11262 let c = caixa_with_etiquetas(vec![
11263 "example",
11264 "aplicacao",
11265 "mesh",
11266 "ecommerce",
11267 "demo",
11268 "infrastructure",
11269 "aws",
11270 "akeyless",
11271 "pangea-native",
11272 "hello-world",
11273 "wasm",
11274 "rust",
11275 "tatara-lisp",
11276 "caixa-servico",
11277 "lareira",
11278 ]);
11279 c.validate_etiquetas().unwrap();
11280 }
11281
11282 // ── validate_autores — universal-axis maintainer shape ────────────
11283
11284 fn caixa_with_autores(autores: Vec<&str>) -> Caixa {
11285 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11286 c.autores = autores.into_iter().map(String::from).collect();
11287 c
11288 }
11289
11290 #[test]
11291 fn validate_autores_accepts_empty_list() {
11292 // The empty-list identity: `Caixa::template` emits `:autores ()`,
11293 // so the gate is non-disruptive against every existing manifest.
11294 let c = caixa_with_autores(vec![]);
11295 c.validate_autores().unwrap();
11296 }
11297
11298 #[test]
11299 fn validate_autores_accepts_canonical_forms() {
11300 // Positive control sweep: every canonical-shaped non-empty
11301 // distinct maintainer list passes — the hello-rio / checkout-
11302 // aplicacao fixtures' `:autores ("pleme-io")` shape, plus the
11303 // multi-author shape downstream packaging surfaces emit.
11304 let c = caixa_with_autores(vec!["pleme-io"]);
11305 c.validate_autores().unwrap();
11306 let c = caixa_with_autores(vec!["alice <alice@example.com>", "bob <bob@example.com>"]);
11307 c.validate_autores().unwrap();
11308 }
11309
11310 #[test]
11311 fn validate_autores_rejects_empty_entry() {
11312 // Canonical paste-from-blank-doc footgun. Without the gate the
11313 // empty entry rendered as `maintainers: [{name: "", email: null}]`
11314 // in `Chart.yaml`, a no-op maintainer the substrate cannot route
11315 // to.
11316 let c = caixa_with_autores(vec![""]);
11317 let err = c.validate_autores().unwrap_err();
11318 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
11319 }
11320
11321 #[test]
11322 fn validate_autores_rejects_duplicate_entry() {
11323 // Canonical copy-paste-the-wrong-author footgun. Unlike the
11324 // `:etiquetas` peer (caixa-helm's `BTreeSet` collect silently
11325 // dedups the rendered `keywords:` array), the `maintainers:`
11326 // rendering has *no* dedup — duplicates stack verbatim. The
11327 // duplicate-arm names the offending author verbatim.
11328 let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
11329 let err = c.validate_autores().unwrap_err();
11330 let ManifestError::AutorDuplicate { autor } = err else {
11331 panic!("expected AutorDuplicate, got {err:?}");
11332 };
11333 assert_eq!(autor, "pleme-io");
11334 }
11335
11336 #[test]
11337 fn validate_autores_empty_takes_precedence_over_duplicate() {
11338 // Empty-first cascade pin: `("" "pleme-io" "pleme-io")` surfaces
11339 // `AutorEmpty` not `AutorDuplicate` — the narrower structural
11340 // "this entry has no value" defect dominates the cross-entry
11341 // uniqueness diagnostic. Mirrors the peer empty-before-duplicate
11342 // cascades on `:etiquetas` (`EtiquetaEmpty` before
11343 // `EtiquetaDuplicate`, 360a499), `:caracteristicas`
11344 // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
11345 // fc3b4d5), and `:membros :caixa` (`MembroCaixaEmpty` before
11346 // `MembroDuplicate`).
11347 let c = caixa_with_autores(vec!["", "pleme-io", "pleme-io"]);
11348 let err = c.validate_autores().unwrap_err();
11349 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
11350 }
11351
11352 #[test]
11353 fn validate_autores_duplicate_reports_first_collision() {
11354 // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
11355 // duplicate (the lexicographically-earliest offending position
11356 // — the second `"a"` at index 2 collides with the first `"a"`
11357 // at index 0), not the later `"b"` collision at index 3,
11358 // peer with every other first-collision diagnostic posture on
11359 // this surface.
11360 let c = caixa_with_autores(vec!["a", "b", "a", "b"]);
11361 let err = c.validate_autores().unwrap_err();
11362 let ManifestError::AutorDuplicate { autor } = err else {
11363 panic!("expected AutorDuplicate, got {err:?}");
11364 };
11365 assert_eq!(autor, "a");
11366 }
11367
11368 #[test]
11369 fn validate_autores_case_sensitive() {
11370 // Case-sensitivity pin: `("Pleme-io" "pleme-io")` is two distinct
11371 // entries, mirroring the peer `:etiquetas` / `:membros :caixa`
11372 // / `:children :caixa` exact-string-match discipline.
11373 let c = caixa_with_autores(vec!["Pleme-io", "pleme-io"]);
11374 c.validate_autores().unwrap();
11375 }
11376
11377 #[test]
11378 fn validate_autores_diagnostic_carries_offending_author() {
11379 // Diagnostic-shape pin (peer with
11380 // `validate_etiquetas_diagnostic_carries_offending_tag`): the
11381 // error's Display surfaces the offending author verbatim, so a
11382 // `feira lint` run can render the diagnostic without re-parsing
11383 // and the author can grep their caixa.lisp for the offending
11384 // value.
11385 let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
11386 let rendered = c.validate_autores().unwrap_err().to_string();
11387 assert!(
11388 rendered.contains(":autores"),
11389 "diagnostic must name the offending slot: {rendered}",
11390 );
11391 assert!(
11392 rendered.contains("pleme-io"),
11393 "diagnostic must quote the offending author: {rendered}",
11394 );
11395 }
11396
11397 #[test]
11398 fn validate_autores_rejects_leading_whitespace_entry() {
11399 // Canonical paste-from-aligned-doc footgun. Without the shape
11400 // gate `" pleme-io"` silently passed validate and landed as a
11401 // YAML plain-style scalar with leading whitespace in the
11402 // rendered Chart.yaml `maintainers:` array — every YAML 1.2
11403 // dumper trims leading whitespace from plain-style scalars, so
11404 // the authored space round-tripped inconsistently back through
11405 // `caixa.lisp`. Mirrors the peer
11406 // `validate_descricao_rejects_leading_whitespace`.
11407 let c = caixa_with_autores(vec![" pleme-io"]);
11408 let err = c.validate_autores().unwrap_err();
11409 let ManifestError::AutorInvalid { autor, reason } = err else {
11410 panic!("expected AutorInvalid, got {err:?}");
11411 };
11412 assert_eq!(autor, " pleme-io");
11413 assert!(reason.contains("whitespace"), "got: {reason}");
11414 }
11415
11416 #[test]
11417 fn validate_autores_rejects_trailing_whitespace_entry() {
11418 // Canonical paste-from-doc footgun.
11419 let c = caixa_with_autores(vec!["pleme-io "]);
11420 let err = c.validate_autores().unwrap_err();
11421 let ManifestError::AutorInvalid { autor, reason } = err else {
11422 panic!("expected AutorInvalid, got {err:?}");
11423 };
11424 assert_eq!(autor, "pleme-io ");
11425 assert!(reason.contains("whitespace"), "got: {reason}");
11426 }
11427
11428 #[test]
11429 fn validate_autores_rejects_embedded_newline_entry() {
11430 // Canonical paste-from-multiline-doc footgun — the author
11431 // pasted a multi-line block of author records into one
11432 // `:autores` entry instead of splitting into one entry per
11433 // author. Without the shape gate `"alice\nbob"` silently
11434 // passed validate and landed as a YAML-illegal multi-line
11435 // scalar in the rendered Chart.yaml `maintainers:` array.
11436 let c = caixa_with_autores(vec!["alice\nbob"]);
11437 let err = c.validate_autores().unwrap_err();
11438 let ManifestError::AutorInvalid { autor, reason } = err else {
11439 panic!("expected AutorInvalid, got {err:?}");
11440 };
11441 assert_eq!(autor, "alice\nbob");
11442 assert!(reason.contains("newline"), "got: {reason}");
11443 }
11444
11445 #[test]
11446 fn validate_autores_rejects_embedded_carriage_return_entry() {
11447 // Canonical paste-from-Windows-CRLF-doc footgun.
11448 let c = caixa_with_autores(vec!["alice\rbob"]);
11449 let err = c.validate_autores().unwrap_err();
11450 let ManifestError::AutorInvalid { autor, reason } = err else {
11451 panic!("expected AutorInvalid, got {err:?}");
11452 };
11453 assert_eq!(autor, "alice\rbob");
11454 assert!(reason.contains("carriage return"), "got: {reason}");
11455 }
11456
11457 #[test]
11458 fn validate_autores_rejects_embedded_tab_entry() {
11459 // Canonical tab-from-aligned-doc footgun.
11460 let c = caixa_with_autores(vec!["Pleme\tContributors"]);
11461 let err = c.validate_autores().unwrap_err();
11462 let ManifestError::AutorInvalid { autor, reason } = err else {
11463 panic!("expected AutorInvalid, got {err:?}");
11464 };
11465 assert_eq!(autor, "Pleme\tContributors");
11466 assert!(reason.contains("tab"), "got: {reason}");
11467 }
11468
11469 #[test]
11470 fn validate_autores_rejects_embedded_control_bytes_entry() {
11471 // Paste-from-binary-blob footguns: NUL, BEL, ESC, DEL all
11472 // surface the same control-byte arm.
11473 for entry in [
11474 "alice\x00bob",
11475 "alice\x07bob",
11476 "alice\x1bbob",
11477 "alice\x7fbob",
11478 ] {
11479 let c = caixa_with_autores(vec![entry]);
11480 let err = c.validate_autores().unwrap_err();
11481 let ManifestError::AutorInvalid { autor, reason } = err else {
11482 panic!("expected AutorInvalid for {entry:?}, got {err:?}");
11483 };
11484 assert_eq!(autor, entry);
11485 assert!(
11486 reason.contains("control character"),
11487 "{entry:?} reason: {reason}",
11488 );
11489 }
11490 }
11491
11492 #[test]
11493 fn validate_autores_accepts_unicode_entry() {
11494 // Unicode positive control: realistic maintainer names carry
11495 // Unicode (`François`, `日本語`, `naïve`). The predicate must
11496 // round-trip Unicode losslessly, peer with the
11497 // `chart_maintainer_name_shape_accepts_unicode` substrate-side
11498 // sweep.
11499 let c = caixa_with_autores(vec![
11500 "François Dupont",
11501 "日本語の名前",
11502 "naïve <naive@example.com>",
11503 ]);
11504 c.validate_autores().unwrap();
11505 }
11506
11507 #[test]
11508 fn validate_autores_empty_takes_precedence_over_shape() {
11509 // Per-entry empty-first cascade pin: an entry that is both
11510 // empty *and* shape-invalid surfaces `AutorEmpty` (the narrower
11511 // "this entry has no value" structural defect dominates the
11512 // broader shape-predicate diagnostic). The empty arm fires
11513 // before the shape predicate is consulted, mirroring the peer
11514 // `validate_repositorio_empty_takes_precedence_over_shape`
11515 // cascade on the universal `Option<String>` siblings — and now
11516 // established on the Vec<String> per-entry surface.
11517 let c = caixa_with_autores(vec![""]);
11518 let err = c.validate_autores().unwrap_err();
11519 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
11520 }
11521
11522 #[test]
11523 fn validate_autores_shape_takes_precedence_over_duplicate() {
11524 // Per-entry shape-before-cross-entry-duplicate cascade pin: an
11525 // entry that is malformed surfaces `AutorInvalid` even when a
11526 // later entry would have collided on duplicate. The per-entry
11527 // shape arm fires inside the same loop iteration as the empty
11528 // arm, before the seen-set insert at end-of-iteration —
11529 // structural per-entry defects dominate the cross-entry
11530 // uniqueness diagnostic.
11531 let c = caixa_with_autores(vec!["alice\nbob", "alice\nbob"]);
11532 let err = c.validate_autores().unwrap_err();
11533 assert!(
11534 matches!(err, ManifestError::AutorInvalid { .. }),
11535 "got {err:?}",
11536 );
11537 }
11538
11539 #[test]
11540 fn validate_autores_invalid_diagnostic_names_offending_slot_and_value() {
11541 // Diagnostic-shape pin on the new shape arm (peer with
11542 // `validate_descricao_invalid_diagnostic_carries_offending_value`):
11543 // the rendered Display surfaces both the offending slot name
11544 // and the offending value verbatim, so a `feira lint` run
11545 // points the author at the exact `:autores` entry to fix.
11546 let c = caixa_with_autores(vec!["alice\nbob"]);
11547 let rendered = c.validate_autores().unwrap_err().to_string();
11548 assert!(
11549 rendered.contains(":autores"),
11550 "diagnostic must name the offending slot: {rendered}",
11551 );
11552 assert!(
11553 rendered.contains("alice\\nbob"),
11554 "diagnostic must quote the offending value (debug-escaped): {rendered}",
11555 );
11556 }
11557
11558 #[test]
11559 fn validate_autores_rejects_at_129_byte_boundary() {
11560 // The 128-byte cap pin — boundary-exceeding case rejected,
11561 // boundary-accepting case passes. Mirrors the peer
11562 // `chart_maintainer_name_shape_rejects_at_129_byte_boundary`
11563 // substrate-side pin, surfaced at the per-axis caller so the
11564 // cap propagates through validate end-to-end. Constructed as
11565 // a single all-`a` token so only the cap arm fires.
11566 let max_ok = "a".repeat(128);
11567 let c = caixa_with_autores(vec![max_ok.as_str()]);
11568 c.validate_autores().unwrap();
11569 let too_long = "a".repeat(129);
11570 let c = caixa_with_autores(vec![too_long.as_str()]);
11571 let err = c.validate_autores().unwrap_err();
11572 let ManifestError::AutorInvalid { reason, .. } = err else {
11573 panic!("expected AutorInvalid, got {err:?}");
11574 };
11575 assert!(reason.contains("128"), "got: {reason}");
11576 assert!(reason.contains("129"), "got: {reason}");
11577 }
11578
11579 // ── validate_repositorio — universal-axis git-repo-URL shape ──────
11580
11581 fn caixa_with_repositorio(repositorio: Option<&str>) -> Caixa {
11582 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11583 c.repositorio = repositorio.map(String::from);
11584 c
11585 }
11586
11587 #[test]
11588 fn validate_repositorio_accepts_none() {
11589 // The omit-the-slot identity: `:repositorio` is optional. The
11590 // gate is a no-op when the author didn't declare a value —
11591 // every caixa without a `:repositorio` line trivially passes,
11592 // and the substrate-side renderers fall back to their
11593 // documented placeholder (`caixa-helm`'s `home: None`,
11594 // `caixa-flux`'s `https://github.com/pleme-io/<nome>` derived
11595 // URL). Mirrors the peer `validate_restart_window_accepts_none`
11596 // posture on the other `Option<String>` Caixa slot.
11597 let c = caixa_with_repositorio(None);
11598 c.validate_repositorio().unwrap();
11599 }
11600
11601 #[test]
11602 fn validate_repositorio_accepts_canonical_forms() {
11603 // Positive control sweep across every documented `:repositorio`
11604 // authoring shape — the same union the shared
11605 // `crate::render::is_git_repo_url` predicate accepts and the
11606 // peer `:deps :fonte :repo` axis already routes through.
11607 // Covers the `github:` shorthand (the canonical pleme-io
11608 // convention used in the `:repositorio` field of every
11609 // manifest fixture across `caixa-helm` / `caixa-mesh` and the
11610 // `examples/`), the `https://…` URL the README quickstart uses,
11611 // the `ssh://`, `git://`, `git@host:path` scp-style SSH, and
11612 // `file://` URL schemes the shared predicate documents.
11613 for repo in [
11614 "github:pleme-io/hello-rio",
11615 "github:pleme-io/checkout",
11616 "https://github.com/pleme-io/hello-rio",
11617 "ssh://git@github.com/pleme-io/hello-rio.git",
11618 "git://github.com/pleme-io/hello-rio.git",
11619 "git@github.com:pleme-io/hello-rio.git",
11620 "file:///srv/pleme/hello-rio",
11621 ] {
11622 let c = caixa_with_repositorio(Some(repo));
11623 c.validate_repositorio()
11624 .unwrap_or_else(|err| panic!("canonical {repo:?} must pass: {err:?}"));
11625 }
11626 }
11627
11628 #[test]
11629 fn validate_repositorio_rejects_empty_some() {
11630 // Canonical paste-from-blank-doc footgun. The narrower
11631 // [`ManifestError::RepositorioEmpty`] arm fires before the
11632 // shape predicate is consulted, mirroring the empty-first
11633 // cascade every peer per-axis identity gate uses
11634 // (`NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
11635 // `FonteRepoEmpty` → `FonteRepoInvalid`). Without this gate
11636 // the empty `Some("")` silently passed the renderer's
11637 // `Option::unwrap_or_else(|| <fallback>)` (which only fires
11638 // on `None`) and landed as `home: ""` in `Chart.yaml` /
11639 // `url: ""` in the FluxCD `GitRepository`.
11640 let c = caixa_with_repositorio(Some(""));
11641 let err = c.validate_repositorio().unwrap_err();
11642 assert!(
11643 matches!(err, ManifestError::RepositorioEmpty),
11644 "got {err:?}",
11645 );
11646 }
11647
11648 #[test]
11649 fn validate_repositorio_rejects_whitespace() {
11650 // Paste-from-doc whitespace footgun. The shared
11651 // `is_git_repo_url` predicate refuses any whitespace byte; a
11652 // trailing space in a `:repositorio` value silently broke
11653 // `git clone '<value> '` at clone time. The diagnostic names
11654 // the offending value verbatim.
11655 let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio "));
11656 let err = c.validate_repositorio().unwrap_err();
11657 let ManifestError::RepositorioInvalid { repositorio, .. } = err else {
11658 panic!("expected RepositorioInvalid, got {err:?}");
11659 };
11660 assert_eq!(repositorio, "github:pleme-io/hello-rio ");
11661 }
11662
11663 #[test]
11664 fn validate_repositorio_rejects_control_char() {
11665 // Paste-from-multiline-doc CRLF footgun — control characters
11666 // at the URL boundary are a class of subprocess-arg injection
11667 // and break git's URL parser at every porcelain entry point.
11668 let c = caixa_with_repositorio(Some("https://example.com/repo\n"));
11669 let err = c.validate_repositorio().unwrap_err();
11670 assert!(
11671 matches!(err, ManifestError::RepositorioInvalid { .. }),
11672 "got {err:?}",
11673 );
11674 }
11675
11676 #[test]
11677 fn validate_repositorio_rejects_leading_dash() {
11678 // Canonical CLI-argument-injection footgun: `git clone <repo>`
11679 // interprets a leading `-` as a CLI flag, so a
11680 // `-upload-pack=…` value escapes the subprocess argument
11681 // boundary. The shared predicate refuses every leading-`-`
11682 // shape at validate time.
11683 let c = caixa_with_repositorio(Some("-upload-pack=evil"));
11684 let err = c.validate_repositorio().unwrap_err();
11685 assert!(
11686 matches!(err, ManifestError::RepositorioInvalid { .. }),
11687 "got {err:?}",
11688 );
11689 }
11690
11691 #[test]
11692 fn validate_repositorio_rejects_missing_colon_separator() {
11693 // The bare `org/repo` ambiguity footgun — `git clone` reads
11694 // a no-`:` form as a relative filesystem path rather than the
11695 // GitHub-shorthand expansion the author probably intended.
11696 // The shared predicate refuses every shape without a `:`
11697 // separator.
11698 let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
11699 let err = c.validate_repositorio().unwrap_err();
11700 assert!(
11701 matches!(err, ManifestError::RepositorioInvalid { .. }),
11702 "got {err:?}",
11703 );
11704 }
11705
11706 #[test]
11707 fn validate_repositorio_rejects_fragment_anchor() {
11708 // Paste-from-browser-address-bar footgun on the
11709 // `:repositorio` axis — an author copies a GitHub permalink
11710 // to a README section / line-permalink and forgets to trim
11711 // the `#fragment` tail. The shared `is_git_repo_url`
11712 // predicate refuses the byte at the URL-grammar layer
11713 // (libcurl strips the fragment before opening the
11714 // transport, so the byte rides verbatim into the rendered
11715 // `Chart.yaml` `home:` and FluxCD `GitRepository` `url:`
11716 // fields but is silently dropped on the wire — two
11717 // manifest variants whose values differ only in their
11718 // fragment anchor lock to two distinct rendered artifacts
11719 // for the byte-identical clone, defeating the THEORY.md
11720 // §V.2 render-determinism contract on the `:repositorio`
11721 // axis the peer `:fonte :repo` axis already closes).
11722 let c = caixa_with_repositorio(Some("https://github.com/pleme-io/hello-rio#readme"));
11723 let err = c.validate_repositorio().unwrap_err();
11724 let ManifestError::RepositorioInvalid {
11725 repositorio,
11726 reason,
11727 } = err
11728 else {
11729 panic!("expected RepositorioInvalid, got {err:?}");
11730 };
11731 assert_eq!(repositorio, "https://github.com/pleme-io/hello-rio#readme");
11732 assert!(
11733 reason.contains("must not contain `#`"),
11734 "reason must surface the fragment-`#` arm, got {reason:?}"
11735 );
11736 }
11737
11738 #[test]
11739 fn validate_repositorio_rejects_query_string() {
11740 // Paste-from-browser-address-bar footgun on the
11741 // `:repositorio` axis (peer with the a68f818 fragment-`#`
11742 // arm on the same axis). An author copies a GitHub tab
11743 // deep-link out of the address bar and forgets to trim
11744 // the `?tab=…` query tail. The shared `is_git_repo_url`
11745 // predicate refuses the byte at the URL-grammar layer
11746 // (GitHub / GitLab / Bitbucket silently ignore the
11747 // `?query` tail and serve the same repo regardless, so
11748 // the byte rides verbatim into the rendered `Chart.yaml`
11749 // `home:` and FluxCD `GitRepository` `url:` fields but
11750 // is silently masked at the wire — two manifest variants
11751 // whose values differ only in their query tail lock to
11752 // two distinct rendered artifacts for the byte-identical
11753 // clone, defeating the THEORY.md §V.2 render-determinism
11754 // contract on the `:repositorio` axis the peer `:fonte
11755 // :repo` axis already closes).
11756 let c = caixa_with_repositorio(Some(
11757 "https://github.com/pleme-io/hello-rio?tab=readme-ov-file",
11758 ));
11759 let err = c.validate_repositorio().unwrap_err();
11760 let ManifestError::RepositorioInvalid {
11761 repositorio,
11762 reason,
11763 } = err
11764 else {
11765 panic!("expected RepositorioInvalid, got {err:?}");
11766 };
11767 assert_eq!(
11768 repositorio,
11769 "https://github.com/pleme-io/hello-rio?tab=readme-ov-file"
11770 );
11771 assert!(
11772 reason.contains("must not contain `?`"),
11773 "reason must surface the query-`?` arm, got {reason:?}"
11774 );
11775 }
11776
11777 #[test]
11778 fn validate_repositorio_rejects_embedded_backslash() {
11779 // Windows-file-path-confusion footgun on the `:repositorio`
11780 // axis (peer with the prior fragment-`#` / query-`?` arms on
11781 // the same axis, and peer with the new dep-level `:fonte :repo`
11782 // backslash arm on the URL-grammar trajectory). An author
11783 // pastes a Windows Explorer address-bar `file:///C:\Users\me\
11784 // hello-rio` into the `:repositorio` slot, expecting the
11785 // `lareira-<nome>` chart's `home:` field and the FluxCD
11786 // `GitRepository` `url:` field to render the canonical local
11787 // file-URI. The shared `is_git_repo_url` predicate refuses
11788 // the byte at the URL-grammar layer (libcurl silently
11789 // translates `\` → `/` on some platforms and refuses it on
11790 // others, so the byte rides verbatim into the rendered
11791 // artifacts but is silently rewritten or rejected at the wire
11792 // — two manifest variants whose values differ only in
11793 // backslash-vs-forward-slash lock to two distinct rendered
11794 // artifacts for the byte-identical clone, defeating the
11795 // THEORY.md §V.2 render-determinism contract on the
11796 // `:repositorio` axis the peer `:fonte :repo` axis already
11797 // closes).
11798 let c = caixa_with_repositorio(Some("file:///C:\\Users\\me\\hello-rio"));
11799 let err = c.validate_repositorio().unwrap_err();
11800 let ManifestError::RepositorioInvalid {
11801 repositorio,
11802 reason,
11803 } = err
11804 else {
11805 panic!("expected RepositorioInvalid, got {err:?}");
11806 };
11807 assert_eq!(repositorio, "file:///C:\\Users\\me\\hello-rio");
11808 assert!(
11809 reason.contains("must not contain `\\`"),
11810 "reason must surface the backslash-`\\` arm, got {reason:?}"
11811 );
11812 }
11813
11814 #[test]
11815 fn validate_repositorio_rejects_uri_template_placeholder() {
11816 // URI Template (RFC 6570) placeholder footgun on the
11817 // `:repositorio` axis (peer with the prior fragment-`#` /
11818 // query-`?` / backslash-`\` arms on the same axis, and peer
11819 // with the new dep-level `:fonte :repo` `{` / `}` arm on the
11820 // URL-grammar trajectory). An author pastes a quick-start
11821 // README snippet / OpenAPI `servers:` URL / Helm chart
11822 // `home:` template carrying unresolved `{org}` / `{repo}`
11823 // placeholders into the `:repositorio` slot, expecting the
11824 // substrate to resolve the placeholder downstream. The
11825 // shared `is_git_repo_url` predicate refuses the byte at the
11826 // URL-grammar layer (libcurl percent-encodes `{` / `}` to
11827 // `%7B` / `%7D` on the wire, so the byte round-trips
11828 // inconsistently between the rendered `Chart.yaml home:` /
11829 // FluxCD `GitRepository url:` and the resolver's `git clone`
11830 // invocation, defeating the THEORY.md §V.2 render-
11831 // determinism contract on the `:repositorio` axis the peer
11832 // `:fonte :repo` axis already closes; every git porcelain
11833 // entry-point additionally fetches a nonexistent literal-
11834 // `{placeholder}`-named path far from the source caixa.lisp).
11835 let c = caixa_with_repositorio(Some("https://github.com/{org}/hello-rio"));
11836 let err = c.validate_repositorio().unwrap_err();
11837 let ManifestError::RepositorioInvalid {
11838 repositorio,
11839 reason,
11840 } = err
11841 else {
11842 panic!("expected RepositorioInvalid, got {err:?}");
11843 };
11844 assert_eq!(repositorio, "https://github.com/{org}/hello-rio");
11845 assert!(
11846 reason.contains("must not contain `{`"),
11847 "reason must surface the open-brace `{{` arm, got {reason:?}"
11848 );
11849 assert!(
11850 reason.contains("URI Template") || reason.contains("RFC 6570"),
11851 "reason must name the RFC 6570 URI Template grammar, got {reason:?}"
11852 );
11853 }
11854
11855 #[test]
11856 fn validate_repositorio_empty_takes_precedence_over_shape() {
11857 // Empty-first cascade pin: the empty `Some("")` surfaces the
11858 // narrower `RepositorioEmpty` not the shape-predicate-wrapped
11859 // `RepositorioInvalid`, mirroring the peer
11860 // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
11861 // `FonteRepoEmpty` → `FonteRepoInvalid` cascades. The shared
11862 // `is_git_repo_url` predicate also rejects the empty input
11863 // (defensively, with its own `"must not be empty"` reason),
11864 // but the manifest-layer empty arm runs first to surface the
11865 // narrower diagnostic verbatim.
11866 let c = caixa_with_repositorio(Some(""));
11867 let err = c.validate_repositorio().unwrap_err();
11868 assert!(
11869 matches!(err, ManifestError::RepositorioEmpty),
11870 "got {err:?}",
11871 );
11872 }
11873
11874 #[test]
11875 fn validate_repositorio_diagnostic_carries_offending_value() {
11876 // Diagnostic-shape pin (peer with
11877 // `validate_autores_diagnostic_carries_offending_author`): the
11878 // error's Display surfaces the offending value + slot name
11879 // verbatim, so a `feira lint` run can render the diagnostic
11880 // without re-parsing and the author can grep their caixa.lisp
11881 // for the offending `:repositorio` value.
11882 let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
11883 let rendered = c.validate_repositorio().unwrap_err().to_string();
11884 assert!(
11885 rendered.contains(":repositorio"),
11886 "diagnostic must name the offending slot: {rendered}",
11887 );
11888 assert!(
11889 rendered.contains("pleme-io/hello-rio"),
11890 "diagnostic must quote the offending value: {rendered}",
11891 );
11892 }
11893
11894 // ── validate_descricao — universal-axis Chart.yaml description shape ──
11895
11896 fn caixa_with_descricao(descricao: Option<&str>) -> Caixa {
11897 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11898 c.descricao = descricao.map(String::from);
11899 c
11900 }
11901
11902 #[test]
11903 fn validate_descricao_accepts_none() {
11904 // The omit-the-slot identity: `:descricao` is optional. The
11905 // gate is a no-op when the author didn't declare a value —
11906 // every caixa without a `:descricao` line trivially passes,
11907 // and the substrate-side renderers fall back to their
11908 // documented `caixa.nome`-derived placeholder. Mirrors the
11909 // peer `validate_repositorio_accepts_none` posture on the
11910 // sibling `Option<String>` Caixa slot.
11911 let c = caixa_with_descricao(None);
11912 c.validate_descricao().unwrap();
11913 }
11914
11915 #[test]
11916 fn validate_descricao_accepts_canonical_summary() {
11917 // Positive control: the canonical pleme-io descricao shape —
11918 // a short free-form prose summary — passes the gate. Covers
11919 // the fixture shapes the `caixa-helm` / `caixa-flux` /
11920 // `caixa-mesh` test fixtures use (`"Canonical Rust→wasm32-
11921 // wasip2 caixa Servico."`, `"Checkout flow."`).
11922 for desc in [
11923 "Canonical Rust→wasm32-wasip2 caixa Servico.",
11924 "Checkout flow.",
11925 "AWS provider caixa for tatara-lisp",
11926 "FIXME — describe this caixa",
11927 "x",
11928 ] {
11929 let c = caixa_with_descricao(Some(desc));
11930 c.validate_descricao()
11931 .unwrap_or_else(|err| panic!("canonical {desc:?} must pass: {err:?}"));
11932 }
11933 }
11934
11935 #[test]
11936 fn validate_descricao_rejects_empty_some() {
11937 // Canonical paste-from-blank-doc footgun. Without this gate
11938 // the empty `Some("")` silently passed the renderer's
11939 // `Option::unwrap_or_else(|| <fallback>)` (which only fires
11940 // on `None`) and landed as `description: ""` in `Chart.yaml`
11941 // and a blank `README.md` header. Mirrors the peer
11942 // [`ManifestError::RepositorioEmpty`] empty-arm on the
11943 // sibling `Option<String>` Caixa slot.
11944 let c = caixa_with_descricao(Some(""));
11945 let err = c.validate_descricao().unwrap_err();
11946 assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
11947 }
11948
11949 #[test]
11950 fn validate_descricao_rejects_leading_whitespace() {
11951 // Paste-from-aligned-doc footgun: a leading ASCII space the
11952 // bare empty-arm gate accepted, the shape predicate now
11953 // refuses. The diagnostic carries the offending value
11954 // verbatim (with the leading space preserved) so the author
11955 // can grep their caixa.lisp for the exact `:descricao` line
11956 // and fix the round-trip-inconsistent leading whitespace.
11957 // Mirrors the peer
11958 // `validate_licenca_rejects_leading_whitespace` arm on the
11959 // sibling `:licenca` axis.
11960 let c = caixa_with_descricao(Some(" Checkout flow."));
11961 let err = c.validate_descricao().unwrap_err();
11962 let ManifestError::DescricaoInvalid { descricao, reason } = err else {
11963 panic!("expected DescricaoInvalid, got {err:?}");
11964 };
11965 assert_eq!(descricao, " Checkout flow.");
11966 assert!(reason.contains("whitespace"), "got: {reason:?}");
11967 }
11968
11969 #[test]
11970 fn validate_descricao_rejects_trailing_whitespace() {
11971 // Paste-from-doc footgun: a trailing ASCII space the bare
11972 // empty-arm gate accepted, the shape predicate now refuses.
11973 let c = caixa_with_descricao(Some("Checkout flow. "));
11974 let err = c.validate_descricao().unwrap_err();
11975 let ManifestError::DescricaoInvalid { descricao, reason } = err else {
11976 panic!("expected DescricaoInvalid, got {err:?}");
11977 };
11978 assert_eq!(descricao, "Checkout flow. ");
11979 assert!(reason.contains("whitespace"), "got: {reason:?}");
11980 }
11981
11982 #[test]
11983 fn validate_descricao_rejects_embedded_newline() {
11984 // Paste-from-multiline-doc footgun: an embedded LF the bare
11985 // empty-arm gate accepted, the shape predicate now refuses.
11986 // Without this gate the embedded newline silently landed in
11987 // the rendered Chart.yaml as a multi-line YAML block scalar,
11988 // and every chart-aware UI (`helm list`, `helm search`,
11989 // Artifact Hub) renders the description in a single-line
11990 // column so the embedded newline is silently dropped at
11991 // every downstream consumer.
11992 let c = caixa_with_descricao(Some("Checkout\nflow."));
11993 let err = c.validate_descricao().unwrap_err();
11994 assert!(
11995 matches!(err, ManifestError::DescricaoInvalid { .. }),
11996 "got {err:?}",
11997 );
11998 assert!(err.to_string().contains("newline"), "got {err}");
11999 }
12000
12001 #[test]
12002 fn validate_descricao_rejects_embedded_carriage_return() {
12003 // Paste-from-Windows-CRLF-doc footgun.
12004 let c = caixa_with_descricao(Some("Checkout\rflow."));
12005 let err = c.validate_descricao().unwrap_err();
12006 assert!(
12007 matches!(err, ManifestError::DescricaoInvalid { .. }),
12008 "got {err:?}",
12009 );
12010 assert!(err.to_string().contains("carriage return"), "got {err}");
12011 }
12012
12013 #[test]
12014 fn validate_descricao_rejects_embedded_tab() {
12015 // Tab-from-aligned-doc footgun.
12016 let c = caixa_with_descricao(Some("Checkout\tflow."));
12017 let err = c.validate_descricao().unwrap_err();
12018 assert!(
12019 matches!(err, ManifestError::DescricaoInvalid { .. }),
12020 "got {err:?}",
12021 );
12022 assert!(err.to_string().contains("tab"), "got {err}");
12023 }
12024
12025 #[test]
12026 fn validate_descricao_rejects_embedded_control_bytes() {
12027 // Paste-from-binary-blob footgun: every other control byte
12028 // (NUL, BEL, ESC, DEL) is refused at validate time. Mirrors
12029 // the peer SPDX-expression control-byte arm.
12030 for s in [
12031 "Checkout\x00flow.",
12032 "Checkout\x07flow.",
12033 "Checkout\x1bflow.",
12034 "Checkout\x7fflow.",
12035 ] {
12036 let c = caixa_with_descricao(Some(s));
12037 let err = c.validate_descricao().unwrap_err();
12038 assert!(
12039 matches!(err, ManifestError::DescricaoInvalid { .. }),
12040 "{s:?} got {err:?}",
12041 );
12042 assert!(
12043 err.to_string().contains("control character"),
12044 "{s:?} got {err}",
12045 );
12046 }
12047 }
12048
12049 #[test]
12050 fn validate_descricao_accepts_unicode_prose() {
12051 // Positive control: Unicode prose is accepted — the
12052 // canonical fixtures carry `→` (U+2192) and `—` (U+2014),
12053 // and `Caixa::template`'s `"FIXME — describe this caixa"`
12054 // scaffold every `feira init` emits must continue to pass.
12055 for s in [
12056 "Canonical Rust→wasm32-wasip2 caixa Servico.",
12057 "FIXME — describe this caixa",
12058 "Caixa pour le projet tâche",
12059 "日本語の説明",
12060 ] {
12061 let c = caixa_with_descricao(Some(s));
12062 c.validate_descricao()
12063 .unwrap_or_else(|err| panic!("Unicode {s:?} must pass: {err:?}"));
12064 }
12065 }
12066
12067 #[test]
12068 fn validate_descricao_empty_takes_precedence_over_shape() {
12069 // Cascade pin: a `Some("")` surfaces the narrower
12070 // `DescricaoEmpty` arm, not the broader `DescricaoInvalid`
12071 // shape-predicate arm. Mirrors the peer
12072 // `validate_licenca_empty_takes_precedence_over_shape` pin
12073 // on the sibling `:licenca` axis.
12074 let c = caixa_with_descricao(Some(""));
12075 let err = c.validate_descricao().unwrap_err();
12076 assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
12077 }
12078
12079 #[test]
12080 fn validate_descricao_invalid_diagnostic_carries_offending_value_and_slot() {
12081 // Diagnostic-shape pin: the error's Display surfaces both
12082 // the `:descricao` slot name and the offending value
12083 // verbatim, so a `feira lint` run can render the diagnostic
12084 // without re-parsing and the author can grep their caixa.lisp
12085 // for the offending `:descricao` line. Mirrors the peer
12086 // `validate_licenca_invalid_diagnostic_carries_offending_value_and_slot`
12087 // pin (ee2e888) on the sibling `:licenca` axis.
12088 // The `{descricao:?}` Debug format escapes embedded control
12089 // bytes; the quoted offending value surfaces as
12090 // `"Checkout\nflow."` (literal backslash-n) in the rendered
12091 // diagnostic. The author can grep their caixa.lisp for the
12092 // literal `Checkout` summary prefix.
12093 let c = caixa_with_descricao(Some("Checkout\nflow."));
12094 let rendered = c.validate_descricao().unwrap_err().to_string();
12095 assert!(
12096 rendered.contains(":descricao"),
12097 "diagnostic must name the offending slot: {rendered}",
12098 );
12099 assert!(
12100 rendered.contains("Checkout\\nflow."),
12101 "diagnostic must quote the offending value (debug-escaped): {rendered}",
12102 );
12103 }
12104
12105 #[test]
12106 fn validate_descricao_template_passes() {
12107 // Round-trip pin: the bare `Caixa::template` shape carries
12108 // `:descricao "FIXME — describe this caixa"` (a non-empty
12109 // sentinel), so the template-derived Caixa passes the gate by
12110 // construction. A future template-shape change that omits or
12111 // empties `:descricao` would surface here as a regression.
12112 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12113 c.validate_descricao().unwrap();
12114 }
12115
12116 #[test]
12117 fn validate_descricao_diagnostic_names_offending_slot() {
12118 // Diagnostic-shape pin (peer with
12119 // `validate_repositorio_diagnostic_carries_offending_value`):
12120 // the error's Display surfaces the `:descricao` slot name
12121 // verbatim, so a `feira lint` run can render the diagnostic
12122 // without re-parsing and the author can grep their caixa.lisp
12123 // for the offending `:descricao` line.
12124 let c = caixa_with_descricao(Some(""));
12125 let rendered = c.validate_descricao().unwrap_err().to_string();
12126 assert!(
12127 rendered.contains(":descricao"),
12128 "diagnostic must name the offending slot: {rendered}",
12129 );
12130 }
12131
12132 // ── validate_licenca — universal-axis chart README license shape ──
12133
12134 fn caixa_with_licenca(licenca: Option<&str>) -> Caixa {
12135 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12136 c.licenca = licenca.map(String::from);
12137 c
12138 }
12139
12140 #[test]
12141 fn validate_licenca_accepts_none() {
12142 // The omit-the-slot identity: `:licenca` is optional. The
12143 // gate is a no-op when the author didn't declare a value —
12144 // every caixa without a `:licenca` line trivially passes,
12145 // and the substrate-side `caixa-helm` renderer falls back to
12146 // the documented `"MIT"` placeholder. Mirrors the peer
12147 // `validate_descricao_accepts_none` posture on the sibling
12148 // `Option<String>` Caixa slot.
12149 let c = caixa_with_licenca(None);
12150 c.validate_licenca().unwrap();
12151 }
12152
12153 #[test]
12154 fn validate_licenca_accepts_canonical_expressions() {
12155 // Positive control: every canonical SPDX expression shape
12156 // pleme-io carries in its existing fixtures + the canonical
12157 // SPDX dual-license / with-exception / `+`-suffix / grouped /
12158 // user-defined-reference shapes all pass the gate. Covers
12159 // the single-license, `OR`-compound, `AND`-compound,
12160 // `WITH`-exception, parenthesis-grouped, `+`-suffix, and
12161 // `LicenseRef-` / `DocumentRef-:LicenseRef-` shapes — every
12162 // production the SPDX 2.1 expression grammar admits that
12163 // sits within the alphabet floor the
12164 // `is_spdx_expression_shape` predicate enforces.
12165 for lic in [
12166 "MIT",
12167 "Apache-2.0",
12168 "Apache-2.0 OR MIT",
12169 "Apache-2.0 AND MIT",
12170 "BSD-3-Clause",
12171 "MPL-2.0",
12172 "GPL-3.0-or-later",
12173 "GPL-2.0+",
12174 "Apache-2.0 WITH LLVM-exception",
12175 "(MIT OR Apache-2.0) AND BSD-3-Clause",
12176 "(MIT OR Apache-2.0) AND BSD-3-Clause AND ISC",
12177 "LicenseRef-MyLicense",
12178 "DocumentRef-spdx-tool:LicenseRef-MIT-Style",
12179 "x",
12180 ] {
12181 let c = caixa_with_licenca(Some(lic));
12182 c.validate_licenca()
12183 .unwrap_or_else(|err| panic!("canonical {lic:?} must pass: {err:?}"));
12184 }
12185 }
12186
12187 #[test]
12188 fn validate_licenca_rejects_trailing_whitespace() {
12189 // Paste-from-doc whitespace footgun. A trailing space in the
12190 // `:licenca` value would silently break a downstream SPDX
12191 // parser that splits on exact `AND` / `OR` / `WITH` keyword
12192 // boundaries. The shape predicate refuses every trailing
12193 // whitespace byte by construction. Peer with
12194 // `validate_repositorio_rejects_whitespace` and
12195 // `validate_edicao_rejects_trailing_whitespace`.
12196 let c = caixa_with_licenca(Some("MIT "));
12197 let err = c.validate_licenca().unwrap_err();
12198 let ManifestError::LicencaInvalid { licenca, .. } = err else {
12199 panic!("expected LicencaInvalid, got {err:?}");
12200 };
12201 assert_eq!(licenca, "MIT ");
12202 }
12203
12204 #[test]
12205 fn validate_licenca_rejects_leading_whitespace() {
12206 // Symmetric paste-from-doc whitespace footgun on the leading
12207 // boundary — the gate refuses every shape that starts with a
12208 // space byte by construction. Peer with
12209 // `validate_edicao_rejects_leading_whitespace`.
12210 let c = caixa_with_licenca(Some(" MIT"));
12211 let err = c.validate_licenca().unwrap_err();
12212 assert!(
12213 matches!(err, ManifestError::LicencaInvalid { .. }),
12214 "got {err:?}",
12215 );
12216 }
12217
12218 #[test]
12219 fn validate_licenca_rejects_control_char() {
12220 // Paste-from-multiline-doc CRLF footgun — control characters
12221 // at the value boundary land as a malformed line in the
12222 // rendered chart `README.md` `## License` section. Peer with
12223 // `validate_repositorio_rejects_control_char` and
12224 // `validate_edicao_rejects_control_char`.
12225 for lic in ["MIT\n", "MIT\r\n", "MIT\rApache-2.0"] {
12226 let c = caixa_with_licenca(Some(lic));
12227 let err = c.validate_licenca().unwrap_err();
12228 assert!(
12229 matches!(err, ManifestError::LicencaInvalid { .. }),
12230 "expected LicencaInvalid on {lic:?}, got {err:?}",
12231 );
12232 }
12233 }
12234
12235 #[test]
12236 fn validate_licenca_rejects_tab() {
12237 // Tab-from-aligned-doc footgun — SPDX expressions use a
12238 // single ASCII space between tokens; a tab breaks every
12239 // downstream SPDX parser that splits on exact `" "`
12240 // boundaries.
12241 let c = caixa_with_licenca(Some("MIT\tOR Apache-2.0"));
12242 let err = c.validate_licenca().unwrap_err();
12243 assert!(
12244 matches!(err, ManifestError::LicencaInvalid { .. }),
12245 "got {err:?}",
12246 );
12247 }
12248
12249 #[test]
12250 fn validate_licenca_rejects_non_ascii() {
12251 // Smart-quote / non-ASCII paste footgun — SPDX identifiers
12252 // are ASCII per the `idstring = 1*(ALPHA / DIGIT / "-" /
12253 // ".")` production. The shape predicate refuses every
12254 // non-ASCII byte by construction; peer with
12255 // `validate_edicao_rejects_non_ascii_lookalike`.
12256 for lic in ["MIT\u{a0}OR Apache-2.0", "MIT\u{2013}1.0", "Café-1.0"] {
12257 let c = caixa_with_licenca(Some(lic));
12258 let err = c.validate_licenca().unwrap_err();
12259 assert!(
12260 matches!(err, ManifestError::LicencaInvalid { .. }),
12261 "expected LicencaInvalid on {lic:?}, got {err:?}",
12262 );
12263 }
12264 }
12265
12266 #[test]
12267 fn validate_licenca_rejects_underscore() {
12268 // Underscore-instead-of-hyphen typo footgun — `Apache_2.0` /
12269 // `MIT_Style` / `BSD_3_Clause` are familiar shapes from
12270 // snake-case identifier conventions that don't apply to the
12271 // SPDX `idstring` grammar (which admits only `ALPHA / DIGIT /
12272 // "-" / "."`). The shape predicate refuses every underscore
12273 // byte by construction.
12274 for lic in ["Apache_2.0", "MIT_Style", "BSD_3_Clause"] {
12275 let c = caixa_with_licenca(Some(lic));
12276 let err = c.validate_licenca().unwrap_err();
12277 assert!(
12278 matches!(err, ManifestError::LicencaInvalid { .. }),
12279 "expected LicencaInvalid on {lic:?}, got {err:?}",
12280 );
12281 }
12282 }
12283
12284 #[test]
12285 fn validate_licenca_rejects_comma_separator() {
12286 // Comma-instead-of-`OR`-keyword colloquial idiom footgun —
12287 // SPDX expressions compose multiple licenses via `AND` / `OR`
12288 // keywords, not the comma separator. The shape predicate
12289 // refuses every comma byte by construction.
12290 for lic in ["MIT, Apache-2.0", "MIT,Apache-2.0"] {
12291 let c = caixa_with_licenca(Some(lic));
12292 let err = c.validate_licenca().unwrap_err();
12293 assert!(
12294 matches!(err, ManifestError::LicencaInvalid { .. }),
12295 "expected LicencaInvalid on {lic:?}, got {err:?}",
12296 );
12297 }
12298 }
12299
12300 #[test]
12301 fn validate_licenca_rejects_slash_dual_license() {
12302 // Slash-dual-license colloquial idiom footgun — the
12303 // `MIT/Apache-2.0` shape is common in Cargo's pre-SPDX
12304 // `package.license` field but non-SPDX; the SPDX equivalent
12305 // is `MIT OR Apache-2.0`. The shape predicate refuses every
12306 // forward-slash byte by construction.
12307 for lic in ["MIT/Apache-2.0", "MIT/BSD-3-Clause"] {
12308 let c = caixa_with_licenca(Some(lic));
12309 let err = c.validate_licenca().unwrap_err();
12310 assert!(
12311 matches!(err, ManifestError::LicencaInvalid { .. }),
12312 "expected LicencaInvalid on {lic:?}, got {err:?}",
12313 );
12314 }
12315 }
12316
12317 #[test]
12318 fn validate_licenca_rejects_semicolon_separator() {
12319 // Semicolon-list-separator confusion footgun — adjacent to
12320 // the comma-separator idiom, every list-separator-belongs-
12321 // to-list-grammar confusion lands here.
12322 let c = caixa_with_licenca(Some("MIT; Apache-2.0"));
12323 let err = c.validate_licenca().unwrap_err();
12324 assert!(
12325 matches!(err, ManifestError::LicencaInvalid { .. }),
12326 "got {err:?}",
12327 );
12328 }
12329
12330 #[test]
12331 fn validate_licenca_empty_takes_precedence_over_shape() {
12332 // Empty-first cascade pin: the empty `Some("")` surfaces the
12333 // narrower `LicencaEmpty` not the shape-predicate-wrapped
12334 // `LicencaInvalid`, mirroring the peer
12335 // `validate_edicao_empty_takes_precedence_over_shape` and
12336 // `validate_repositorio_empty_takes_precedence_over_shape`
12337 // (`RepositorioEmpty` → `RepositorioInvalid`), `NomeEmpty` →
12338 // `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid` cascades.
12339 // The shape predicate also refuses the empty input
12340 // (defensively — `"must not be empty"`), but the manifest-
12341 // layer empty arm runs first to surface the narrower
12342 // diagnostic verbatim.
12343 let c = caixa_with_licenca(Some(""));
12344 let err = c.validate_licenca().unwrap_err();
12345 assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
12346 }
12347
12348 #[test]
12349 fn validate_licenca_invalid_diagnostic_carries_offending_value() {
12350 // Diagnostic-shape pin on the shape-predicate arm (peer with
12351 // `validate_edicao_invalid_diagnostic_carries_offending_value`
12352 // and `validate_repositorio_diagnostic_carries_offending_value`):
12353 // the error's Display surfaces the offending value + slot
12354 // name verbatim, so a `feira lint` run can render the
12355 // diagnostic without re-parsing and the author can grep
12356 // their caixa.lisp for the offending `:licenca` value.
12357 let c = caixa_with_licenca(Some("Apache_2.0"));
12358 let rendered = c.validate_licenca().unwrap_err().to_string();
12359 assert!(
12360 rendered.contains(":licenca"),
12361 "diagnostic must name the offending slot: {rendered}",
12362 );
12363 assert!(
12364 rendered.contains("Apache_2.0"),
12365 "diagnostic must quote the offending value: {rendered}",
12366 );
12367 }
12368
12369 #[test]
12370 fn validate_licenca_rejects_empty_some() {
12371 // Canonical paste-from-blank-doc footgun. Without this gate
12372 // the empty `Some("")` silently passed the renderer's
12373 // `Option::unwrap_or_else(|| "MIT".into())` (which only
12374 // fires on `None`) and landed as a bare trailing period in
12375 // the rendered chart `README.md` `## License` section.
12376 // Mirrors the peer [`ManifestError::DescricaoEmpty`] empty-
12377 // arm on the sibling `Option<String>` Caixa slot.
12378 let c = caixa_with_licenca(Some(""));
12379 let err = c.validate_licenca().unwrap_err();
12380 assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
12381 }
12382
12383 #[test]
12384 fn validate_licenca_template_passes() {
12385 // Round-trip pin: the bare `Caixa::template` shape (whether
12386 // it carries `:licenca` or omits it) passes the gate by
12387 // construction. A future template-shape change that
12388 // introduced `(:licenca "")` would surface here as a
12389 // regression. Mirrors the peer
12390 // `validate_descricao_template_passes` pin.
12391 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12392 c.validate_licenca().unwrap();
12393 }
12394
12395 #[test]
12396 fn validate_licenca_diagnostic_names_offending_slot() {
12397 // Diagnostic-shape pin (peer with
12398 // `validate_descricao_diagnostic_names_offending_slot`):
12399 // the error's Display surfaces the `:licenca` slot name
12400 // verbatim, so a `feira lint` run can render the diagnostic
12401 // without re-parsing and the author can grep their caixa.lisp
12402 // for the offending `:licenca` line.
12403 let c = caixa_with_licenca(Some(""));
12404 let rendered = c.validate_licenca().unwrap_err().to_string();
12405 assert!(
12406 rendered.contains(":licenca"),
12407 "diagnostic must name the offending slot: {rendered}",
12408 );
12409 }
12410
12411 // ── Caixa::licenca — outer top-level Option<&str> scalar accessor ──
12412
12413 #[test]
12414 fn licenca_returns_licenca_byte_string_verbatim_across_permutations() {
12415 // The canonical per-`Caixa` `:licenca` SPDX-expression scalar
12416 // pin: [`Caixa::licenca`] must return the `:licenca` typed
12417 // byte-string verbatim as an `Option<&str>`, byte-equal to the
12418 // raw `self.licenca.as_deref()` access across every
12419 // representative value in the accept-set — `None` (the "omit
12420 // the slot to defer to the caixa-helm renderer's `MIT`
12421 // fallback" arm every existing fixture without a `:licenca`
12422 // line carries), `Some("")` (a past-the-guard sentinel that
12423 // pins the accessor doesn't perform a silent
12424 // `Some("") → None` collapse on the empty arm — validate
12425 // rejects `Some("")` through `LicencaEmpty` but the accessor
12426 // must ship the raw slot verbatim so a validate-time gate
12427 // regression surfaces at the caixa-helm emit boundary rather
12428 // than being silently absorbed into the fallback), `Some("MIT")`
12429 // (the canonical single-license shape every `feira init`
12430 // template scaffolds), `Some("Apache-2.0 OR MIT")` (the
12431 // canonical `OR`-compound shape the peer
12432 // `validate_licenca_accepts_canonical_expressions` positive
12433 // sweep exercises), `Some("(MIT OR Apache-2.0) AND
12434 // BSD-3-Clause")` (the canonical parenthesis-grouped shape),
12435 // `Some("MIT ")` / `Some(" MIT")` / `Some("MIT\n")` /
12436 // `Some("Apache_2.0")` / `Some("MIT,Apache-2.0")` (past-the-
12437 // guard sentinels — validate rejects each through
12438 // `LicencaInvalid` but the accessor must ship the raw slot
12439 // verbatim).
12440 //
12441 // First outer top-level [`Caixa`] `Option<&str>`-return scalar
12442 // accessor pin on the substrate primitive — opens the "outer
12443 // [`Caixa`] `Option<&str>` scalar" projection pattern the
12444 // sibling per-`Caixa` `:descricao` / `:repositorio` / `:edicao`
12445 // future lifts fold on. Sibling in shape to the peer per-`:placement`
12446 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
12447 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
12448 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
12449 // axes, extended onto the outer top-level [`Caixa`] universal-
12450 // axis surface. Pins against a future silent detour that
12451 // returned an owned `Option<String>` (which would type-check
12452 // but silently allocate on every accessor call, breaking the
12453 // zero-cost projection every peer sibling accessor carries), a
12454 // `Some("") → None` collapse (which would silently absorb the
12455 // `LicencaEmpty` refusal case at the accessor boundary and the
12456 // caixa-helm emit path would silently fall back to `"MIT"` on
12457 // a struct-literal `Caixa { licenca: Some(""), .. }`), or a
12458 // `None → Some("MIT")` collapse (which would silently reify
12459 // the caixa-helm renderer's `"MIT"` fallback at the accessor
12460 // boundary and every downstream consumer keying off the
12461 // `Option::is_none()` discriminator would lose the "author
12462 // omitted the slot" signal).
12463 for licenca in [
12464 None,
12465 Some(""),
12466 Some("MIT"),
12467 Some("Apache-2.0 OR MIT"),
12468 Some("(MIT OR Apache-2.0) AND BSD-3-Clause"),
12469 Some("MIT "),
12470 Some(" MIT"),
12471 Some("MIT\n"),
12472 Some("Apache_2.0"),
12473 Some("MIT,Apache-2.0"),
12474 ] {
12475 let c = caixa_with_licenca(licenca);
12476 assert_eq!(
12477 c.licenca(),
12478 licenca,
12479 "Caixa::licenca must return :licenca verbatim (got {:?}, \
12480 expected {licenca:?})",
12481 c.licenca(),
12482 );
12483 assert_eq!(
12484 c.licenca(),
12485 c.licenca.as_deref(),
12486 "Caixa::licenca must byte-equal the raw \
12487 `self.licenca.as_deref()` field access across every \
12488 value in the Option<&str> accept-set",
12489 );
12490 }
12491 }
12492
12493 #[test]
12494 fn validate_licenca_empty_arm_routes_through_accessor() {
12495 // Composition pin: [`Caixa::validate_licenca`]'s empty-arm gate
12496 // must key off [`Caixa::licenca`], not the raw
12497 // `self.licenca.as_deref()` field access. Structurally: a
12498 // `Caixa { licenca: Some(""), .. }` must surface the
12499 // `LicencaEmpty` refusal exactly, and a
12500 // `Caixa { licenca: Some("MIT"), .. }` (the canonical
12501 // single-license form) must pass validate. The pair jointly
12502 // pins the accessor + validate-gate composition: any future
12503 // silent detour that had the accessor return `None` on the
12504 // empty arm (a `.filter(|s| !s.is_empty())` collapse) would
12505 // silently absorb the `LicencaEmpty` refusal at the accessor
12506 // boundary and the validate gate would accept a struct-literal
12507 // `Caixa { licenca: Some(""), .. }` — the composition pin
12508 // catches that at caixa-core build time.
12509 //
12510 // Peer of the per-`:politicas :circuit-breaker`
12511 // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
12512 // accessor-composition pin
12513 // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
12514 // on the sibling per-M3-mesh-slot required-`u32` axis — same
12515 // "the validate / shape-gate predicate must route through the
12516 // substrate-primitive typed dispatch" discipline extended onto
12517 // the outer top-level [`Caixa`] universal-axis
12518 // `Option<&str>`-composition surface.
12519 let c = caixa_with_licenca(Some(""));
12520 assert!(
12521 matches!(c.validate_licenca(), Err(ManifestError::LicencaEmpty)),
12522 "validate_licenca must reject licenca == Some(\"\") with \
12523 LicencaEmpty — the accessor and the validate gate must \
12524 route through the same substrate-primitive typed dispatch \
12525 on the :licenca empty arm",
12526 );
12527 let c = caixa_with_licenca(Some("MIT"));
12528 assert!(
12529 c.validate_licenca().is_ok(),
12530 "validate_licenca must accept licenca == Some(\"MIT\") \
12531 (the canonical single-license SPDX shape)",
12532 );
12533 }
12534
12535 #[test]
12536 fn licenca_projects_option_str_by_borrow() {
12537 // The by-borrow pin: [`Caixa::licenca`] returns
12538 // `Option<&str>` by borrow — the `&str` borrows the underlying
12539 // `String` storage of the `Option<String>` slot and the
12540 // accessor must not allocate a fresh `String` on every call.
12541 // Peer of the per-`:placement`
12542 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
12543 // borrow pin on the peer per-M3-mesh-slot
12544 // `Option<&str>`-return axis, extended onto the outer top-
12545 // level [`Caixa`] universal-axis `Option<&str>` shape — the
12546 // accessor's returned `&str` must borrow from `&self` (the
12547 // returned reference's lifetime is tied to `&self`), and
12548 // calling the accessor twice on the same [`Caixa`] must yield
12549 // the same `Option<&str>` verbatim (idempotent, no side
12550 // effects on `&self`).
12551 //
12552 // Pins against a future silent detour that returned an owned
12553 // `Option<String>` (which would type-check but silently
12554 // allocate on every call, breaking the zero-cost projection
12555 // every peer sibling accessor carries), or a one-arm-only
12556 // accessor that returned a saturating value on some sentinel
12557 // input (breaking the pass-through invariant the sibling
12558 // required-scalar accessors carry).
12559 for licenca in [None, Some(""), Some("MIT"), Some("Apache-2.0 OR MIT")] {
12560 let c = caixa_with_licenca(licenca);
12561 let first = c.licenca();
12562 let second = c.licenca();
12563 assert_eq!(
12564 first, second,
12565 "Caixa::licenca must be idempotent — two successive \
12566 calls on the same &self must return the same \
12567 Option<&str>",
12568 );
12569 assert_eq!(
12570 first, licenca,
12571 "Caixa::licenca must return :licenca verbatim by \
12572 borrow — got {first:?}, expected {licenca:?}",
12573 );
12574 }
12575 }
12576
12577 // ── Caixa::repositorio — outer top-level Option<&str> scalar accessor ──
12578
12579 #[test]
12580 fn repositorio_returns_repositorio_byte_string_verbatim_across_permutations() {
12581 // The canonical per-`Caixa` `:repositorio` git-repo-URL scalar
12582 // pin: [`Caixa::repositorio`] must return the `:repositorio`
12583 // typed byte-string verbatim as an `Option<&str>`, byte-equal
12584 // to the raw `self.repositorio.as_deref()` access across every
12585 // representative value in the accept-set — `None` (the "omit
12586 // the slot to defer to the per-renderer placeholder" arm every
12587 // existing fixture without a `:repositorio` line carries),
12588 // `Some("")` (a past-the-guard sentinel that pins the accessor
12589 // doesn't perform a silent `Some("") → None` collapse on the
12590 // empty arm — validate rejects `Some("")` through
12591 // `RepositorioEmpty` but the accessor must ship the raw slot
12592 // verbatim so a validate-time gate regression surfaces at the
12593 // caixa-helm / caixa-flux emit boundary rather than being
12594 // silently absorbed into the per-renderer fallback),
12595 // `Some("github:pleme-io/hello-rio")` (the canonical `github:`
12596 // shorthand every existing manifest fixture across
12597 // `caixa-helm` / `caixa-mesh` and the `examples/` uses),
12598 // `Some("https://github.com/pleme-io/checkout")` (the canonical
12599 // `https://` URL the README quickstart uses),
12600 // `Some("ssh://git@github.com/pleme-io/checkout.git")` /
12601 // `Some("git://github.com/pleme-io/checkout.git")` /
12602 // `Some("git@github.com:pleme-io/checkout.git")` /
12603 // `Some("file:///opt/mirrors/pleme-io/checkout")` (every non-
12604 // github scheme the shared `is_git_repo_url` predicate
12605 // documents), and five past-the-guard sentinels for the
12606 // `RepositorioInvalid` refusal cases (`Some("pleme-io/checkout")`
12607 // missing-colon, `Some("-upload-pack=evil")` leading-dash, /
12608 // `Some("github:pleme-io/checkout?ref=main")` query-string, /
12609 // `Some("github:pleme-io/checkout#main")` fragment-anchor, /
12610 // `Some("github:pleme-io/{tpl}")` URI-template-placeholder — the
12611 // sentinels pin the accessor doesn't silently absorb the
12612 // refusal cases into a fallback).
12613 //
12614 // Second outer top-level [`Caixa`] `Option<&str>`-return scalar
12615 // accessor pin on the substrate primitive — sibling of the peer
12616 // [`Caixa::licenca`] (6d5bc28) pin
12617 // (`licenca_returns_licenca_byte_string_verbatim_across_permutations`)
12618 // that opened the "outer [`Caixa`] `Option<&str>` scalar"
12619 // projection pin pattern this pin folds on. Sibling in shape to
12620 // the peer per-`:placement`
12621 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
12622 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
12623 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
12624 // axes, extended onto the outer top-level [`Caixa`] universal-
12625 // axis surface. Pins against a future silent detour that
12626 // returned an owned `Option<String>` (which would type-check
12627 // but silently allocate on every accessor call, breaking the
12628 // zero-cost projection every peer sibling accessor carries), a
12629 // `Some("") → None` collapse (which would silently absorb the
12630 // `RepositorioEmpty` refusal case at the accessor boundary and
12631 // the caixa-helm `Chart.yaml` `home:` fold would silently
12632 // render a `home: null` / omitted field on a struct-literal
12633 // `Caixa { repositorio: Some(""), .. }`), or a
12634 // `None → Some(<default>)` collapse (which would silently reify
12635 // the per-renderer fallback at the accessor boundary and every
12636 // downstream consumer keying off the `Option::is_none()`
12637 // discriminator would lose the "author omitted the slot"
12638 // signal).
12639 for repositorio in [
12640 None,
12641 Some(""),
12642 Some("github:pleme-io/hello-rio"),
12643 Some("https://github.com/pleme-io/checkout"),
12644 Some("ssh://git@github.com/pleme-io/checkout.git"),
12645 Some("git://github.com/pleme-io/checkout.git"),
12646 Some("git@github.com:pleme-io/checkout.git"),
12647 Some("file:///opt/mirrors/pleme-io/checkout"),
12648 Some("pleme-io/checkout"),
12649 Some("-upload-pack=evil"),
12650 Some("github:pleme-io/checkout?ref=main"),
12651 Some("github:pleme-io/checkout#main"),
12652 Some("github:pleme-io/{tpl}"),
12653 ] {
12654 let c = caixa_with_repositorio(repositorio);
12655 assert_eq!(
12656 c.repositorio(),
12657 repositorio,
12658 "Caixa::repositorio must return :repositorio verbatim \
12659 (got {:?}, expected {repositorio:?})",
12660 c.repositorio(),
12661 );
12662 assert_eq!(
12663 c.repositorio(),
12664 c.repositorio.as_deref(),
12665 "Caixa::repositorio must byte-equal the raw \
12666 `self.repositorio.as_deref()` field access across every \
12667 value in the Option<&str> accept-set",
12668 );
12669 }
12670 }
12671
12672 #[test]
12673 fn validate_repositorio_empty_arm_routes_through_accessor() {
12674 // Composition pin: [`Caixa::validate_repositorio`]'s empty-arm
12675 // gate must key off [`Caixa::repositorio`], not the raw
12676 // `self.repositorio.as_deref()` field access. Structurally: a
12677 // `Caixa { repositorio: Some(""), .. }` must surface the
12678 // `RepositorioEmpty` refusal exactly, and a
12679 // `Caixa { repositorio: Some("github:pleme-io/hello-rio"), .. }`
12680 // (the canonical `github:` shorthand form) must pass validate.
12681 // The pair jointly pins the accessor + validate-gate
12682 // composition: any future silent detour that had the accessor
12683 // return `None` on the empty arm (a `.filter(|s| !s.is_empty())`
12684 // collapse) would silently absorb the `RepositorioEmpty` refusal
12685 // at the accessor boundary and the validate gate would accept a
12686 // struct-literal `Caixa { repositorio: Some(""), .. }` — the
12687 // composition pin catches that at caixa-core build time.
12688 //
12689 // Peer of the [`Caixa::licenca`] (6d5bc28)
12690 // `validate_licenca_empty_arm_routes_through_accessor`
12691 // composition pin on the sibling outer top-level [`Caixa`]
12692 // `Option<&str>` universal-axis surface — same "the validate /
12693 // shape-gate predicate must route through the substrate-
12694 // primitive typed dispatch" discipline extended onto the second
12695 // outer top-level [`Caixa`] universal-axis `Option<&str>`-
12696 // composition surface.
12697 let c = caixa_with_repositorio(Some(""));
12698 assert!(
12699 matches!(
12700 c.validate_repositorio(),
12701 Err(ManifestError::RepositorioEmpty),
12702 ),
12703 "validate_repositorio must reject repositorio == Some(\"\") \
12704 with RepositorioEmpty — the accessor and the validate gate \
12705 must route through the same substrate-primitive typed \
12706 dispatch on the :repositorio empty arm",
12707 );
12708 let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio"));
12709 assert!(
12710 c.validate_repositorio().is_ok(),
12711 "validate_repositorio must accept repositorio == \
12712 Some(\"github:pleme-io/hello-rio\") (the canonical \
12713 `github:` shorthand git-repo-URL shape)",
12714 );
12715 }
12716
12717 #[test]
12718 fn repositorio_projects_option_str_by_borrow() {
12719 // The by-borrow pin: [`Caixa::repositorio`] returns
12720 // `Option<&str>` by borrow — the `&str` borrows the underlying
12721 // `String` storage of the `Option<String>` slot and the
12722 // accessor must not allocate a fresh `String` on every call.
12723 // Peer of the per-`:placement`
12724 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) and the
12725 // [`Caixa::licenca`] (6d5bc28) by-borrow pins on the peer
12726 // `Option<&str>`-return axes, extended onto the second outer
12727 // top-level [`Caixa`] universal-axis `Option<&str>` shape —
12728 // the accessor's returned `&str` must borrow from `&self` (the
12729 // returned reference's lifetime is tied to `&self`), and
12730 // calling the accessor twice on the same [`Caixa`] must yield
12731 // the same `Option<&str>` verbatim (idempotent, no side effects
12732 // on `&self`).
12733 //
12734 // Pins against a future silent detour that returned an owned
12735 // `Option<String>` (which would type-check but silently
12736 // allocate on every call, breaking the zero-cost projection
12737 // every peer sibling accessor carries), or a one-arm-only
12738 // accessor that returned a saturating value on some sentinel
12739 // input (breaking the pass-through invariant the sibling
12740 // required-scalar accessors carry).
12741 for repositorio in [
12742 None,
12743 Some(""),
12744 Some("github:pleme-io/hello-rio"),
12745 Some("https://github.com/pleme-io/checkout"),
12746 ] {
12747 let c = caixa_with_repositorio(repositorio);
12748 let first = c.repositorio();
12749 let second = c.repositorio();
12750 assert_eq!(
12751 first, second,
12752 "Caixa::repositorio must be idempotent — two successive \
12753 calls on the same &self must return the same \
12754 Option<&str>",
12755 );
12756 assert_eq!(
12757 first, repositorio,
12758 "Caixa::repositorio must return :repositorio verbatim by \
12759 borrow — got {first:?}, expected {repositorio:?}",
12760 );
12761 }
12762 }
12763
12764 // ── Caixa::canonical_git_url — resolved-git-URL composer ──────────
12765
12766 #[test]
12767 fn canonical_git_url_returns_repositorio_verbatim_on_some_arm() {
12768 // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] must
12769 // return the author-declared `:repositorio` byte-string verbatim
12770 // on the `Some` arm — no scheme rewrite, no trailing-slash
12771 // canonicalization, no `github:` → `https://github.com/`
12772 // desugaring. The resolved-URL composer is the projection of
12773 // the raw [`Caixa::repositorio`] `Option<&str>` accessor onto
12774 // the `String`-return arity every substrate-side field-fill
12775 // consumer keys off; on the `Some` arm the projection is
12776 // `str::to_owned` verbatim, so every accept-set value the
12777 // sibling `repositorio_returns_repositorio_byte_string_verbatim_
12778 // across_permutations` pin covers (`https://…`, `github:…`,
12779 // `ssh://…`, `git://…`, `git@…`, `file://…`, and the past-the-
12780 // guard sentinel `pleme-io/…`) must survive the accessor
12781 // byte-equal. Pins against a future silent detour that rewrote
12782 // the `github:` shorthand to the `https://github.com/` full URL
12783 // at the accessor boundary (which would silently split the
12784 // resolved-URL surface from the raw [`Caixa::repositorio`]
12785 // accessor's documented pass-through invariant), or a trailing-
12786 // slash normalization (which would silently break the
12787 // FluxCD `GitRepository` `spec.url` byte-exact match every
12788 // downstream consumer keys the source-controller reconcile off).
12789 for repositorio in [
12790 "github:pleme-io/hello-rio",
12791 "https://github.com/pleme-io/checkout",
12792 "ssh://git@github.com/pleme-io/checkout.git",
12793 "git://github.com/pleme-io/checkout.git",
12794 "git@github.com:pleme-io/checkout.git",
12795 "file:///opt/mirrors/pleme-io/checkout",
12796 ] {
12797 let c = caixa_with_repositorio(Some(repositorio));
12798 assert_eq!(
12799 c.canonical_git_url(),
12800 repositorio,
12801 "Caixa::canonical_git_url on the Some arm must return \
12802 :repositorio verbatim (got {:?}, expected {repositorio:?})",
12803 c.canonical_git_url(),
12804 );
12805 }
12806 }
12807
12808 #[test]
12809 fn canonical_git_url_falls_back_to_pleme_org_url_on_none_arm() {
12810 // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] on the
12811 // `None` arm must emit the substrate's canonical pleme-org github
12812 // URL derived from `caixa.nome()` — `https://github.com/<org>/
12813 // <nome>` with `<org>` bound to [`crate::DEFAULT_PLEME_GIT_ORG`]
12814 // and `<nome>` bound to the typed [`Caixa::nome`] accessor. This
12815 // is the exact byte-image of the prior inline
12816 // [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_url`
12817 // composer at caixa-flux/src/lib.rs:2080 that every prior caller
12818 // re-derived open-coded. Pins against a future silent detour
12819 // that migrated the `<org>` segment to a different constant (a
12820 // fork rebranding that split off a new
12821 // `DEFAULT_PLEME_GIT_ORG_MIRROR` const the accessor would need
12822 // to migrate onto), a scheme change (`https://` → `git://` or
12823 // `ssh://`), or a per-`Caixa` `.canonical_git_url_prefix`
12824 // override (which would break the substrate-wide single-source-
12825 // of-truth guarantee this method encodes).
12826 let c = caixa_with_repositorio(None);
12827 let expected = format!(
12828 "https://github.com/{org}/{nome}",
12829 org = crate::DEFAULT_PLEME_GIT_ORG,
12830 nome = c.nome(),
12831 );
12832 assert_eq!(
12833 c.canonical_git_url(),
12834 expected,
12835 "Caixa::canonical_git_url on the None arm must fold through \
12836 the substrate's canonical pleme-org github URL fallback \
12837 `https://github.com/<DEFAULT_PLEME_GIT_ORG>/<nome>` — got \
12838 {:?}, expected {expected:?}",
12839 c.canonical_git_url(),
12840 );
12841 }
12842
12843 #[test]
12844 fn canonical_git_url_byte_matches_manual_composition() {
12845 // Byte-parity pin: [`Caixa::canonical_git_url`] must render
12846 // byte-identically to the manual open-coded
12847 // `caixa.repositorio().map(str::to_owned).unwrap_or_else(||
12848 // format!("https://github.com/{org}/{nome}", ...))` composition
12849 // every prior substrate-side caller re-derived. Guards the
12850 // paired-site convergence just applied at caixa-flux's
12851 // [`ClusterBundleOpts::for_caixa`] `git_url` composer (which
12852 // now routes through this accessor): a future implementation of
12853 // this method that reordered the format arguments, swapped the
12854 // `<org>` constant for a different one, or interposed a
12855 // canonicalization pass on the `Some` arm surfaces here as a
12856 // caixa-core build-time test failure rather than as a downstream
12857 // FluxCD `GitRepository` reconcile mismatch far from this
12858 // method's source.
12859 for repositorio in [
12860 None,
12861 Some("github:pleme-io/hello-rio"),
12862 Some("https://github.com/pleme-io/checkout"),
12863 Some("ssh://git@github.com/pleme-io/checkout.git"),
12864 ] {
12865 let c = caixa_with_repositorio(repositorio);
12866 let manual = c.repositorio().map_or_else(
12867 || {
12868 format!(
12869 "https://github.com/{org}/{nome}",
12870 org = crate::DEFAULT_PLEME_GIT_ORG,
12871 nome = c.nome(),
12872 )
12873 },
12874 str::to_owned,
12875 );
12876 assert_eq!(
12877 c.canonical_git_url(),
12878 manual,
12879 "Caixa::canonical_git_url must byte-equal the manual \
12880 open-coded `repositorio().map(str::to_owned)\
12881 .unwrap_or_else(|| format!(...))` composition across \
12882 every representative :repositorio input — got {:?}, \
12883 expected {manual:?}",
12884 c.canonical_git_url(),
12885 );
12886 }
12887 }
12888
12889 // ── Caixa::publish_tag — resolved-publish-tag composer ───────────
12890
12891 #[test]
12892 fn publish_tag_composes_prefix_and_versao_on_all_shapes() {
12893 // Fail-before-pass-after pin: [`Caixa::publish_tag`] must compose
12894 // [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] against the caixa's typed
12895 // [`Caixa::versao`] byte-string across every SemVer-2 shape the
12896 // sibling [`validate_versao_accepts_canonical_forms`] positive-set
12897 // sweep documents — bare MAJOR.MINOR.PATCH, pre-release tags
12898 // (`-rc.1`), build metadata (`+build.42`), the combined form, and
12899 // the `0.0.0` boundary case. Every accept-set value the peer
12900 // validate gate lets through must survive the resolved-tag
12901 // projection byte-equal.
12902 for versao in [
12903 "0.1.0",
12904 "0.0.0",
12905 "1.0.0",
12906 "1.2.3-rc.1",
12907 "1.2.3+build.42",
12908 "1.2.3-rc.1+build.42",
12909 ] {
12910 let c = caixa_with_versao(versao);
12911 let expected = format!(
12912 "{prefix}{versao}",
12913 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
12914 );
12915 assert_eq!(
12916 c.publish_tag(),
12917 expected,
12918 "Caixa::publish_tag must compose \
12919 DEFAULT_PUBLISH_TAG_PREFIX ({prefix:?}) against \
12920 :versao ({versao:?}) verbatim — got {got:?}, \
12921 expected {expected:?}",
12922 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
12923 got = c.publish_tag(),
12924 );
12925 }
12926 }
12927
12928 #[test]
12929 fn publish_tag_starts_with_default_publish_tag_prefix() {
12930 // Prefix-shape pin: every [`Caixa::publish_tag`] emission must
12931 // begin with the canonical [`crate::DEFAULT_PUBLISH_TAG_PREFIX`]
12932 // byte-string on every input, guarding a hypothetical future
12933 // implementation that migrated the prefix segment to an inline
12934 // literal (`"v"`) that would silently drift from any rebrand of
12935 // the lifted constant. Peer to the sibling caixa-flux
12936 // `cluster_bundle_default_git_tag_uses_lifted_caixa_core_prefix`
12937 // test which pins the same prefix invariant at the reader-side
12938 // `GitRefSpec::Tag` emit site.
12939 for versao in ["0.0.0", "0.1.0", "1.2.3-rc.1", "9.9.9+build.1"] {
12940 let c = caixa_with_versao(versao);
12941 let tag = c.publish_tag();
12942 assert!(
12943 tag.starts_with(crate::DEFAULT_PUBLISH_TAG_PREFIX),
12944 "Caixa::publish_tag emission {tag:?} must start with \
12945 the lifted crate::DEFAULT_PUBLISH_TAG_PREFIX \
12946 ({prefix:?})",
12947 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
12948 );
12949 }
12950 }
12951
12952 #[test]
12953 fn publish_tag_byte_matches_manual_composition() {
12954 // Byte-parity pin: [`Caixa::publish_tag`] must render byte-
12955 // identically to the manual open-coded
12956 // `format!("{prefix}{versao}", prefix =
12957 // caixa_core::DEFAULT_PUBLISH_TAG_PREFIX, versao =
12958 // caixa.versao())` composition every prior substrate-side
12959 // caller re-derived. Guards the paired-site convergence just
12960 // applied at caixa-flux's [`ClusterBundleOpts::for_caixa`]
12961 // `git_ref` composer (which now routes through this accessor):
12962 // a future implementation of this method that reordered the
12963 // format arguments, swapped the `<prefix>` constant for a
12964 // different one, or interposed a canonicalization pass on the
12965 // `:versao` axis surfaces here as a caixa-core build-time test
12966 // failure rather than as a downstream FluxCD `GitRepository`
12967 // reconcile mismatch far from this method's source.
12968 for versao in [
12969 "0.1.0",
12970 "0.0.0",
12971 "1.2.3-rc.1",
12972 "1.2.3+build.42",
12973 "1.2.3-rc.1+build.42",
12974 ] {
12975 let c = caixa_with_versao(versao);
12976 let manual = format!(
12977 "{prefix}{versao}",
12978 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
12979 versao = c.versao(),
12980 );
12981 assert_eq!(
12982 c.publish_tag(),
12983 manual,
12984 "Caixa::publish_tag must byte-equal the manual \
12985 open-coded `format!(\"{{prefix}}{{versao}}\", ...)` \
12986 composition across every representative :versao input \
12987 — got {got:?}, expected {manual:?}",
12988 got = c.publish_tag(),
12989 );
12990 }
12991 }
12992
12993 // ── Caixa::lareira_chart_name — resolved-chart-name composer ─────
12994
12995 #[test]
12996 fn lareira_chart_name_composes_prefix_and_nome_on_all_shapes() {
12997 // Fail-before-pass-after pin: [`Caixa::lareira_chart_name`] must
12998 // compose [`crate::LAREIRA_CHART_NAME_PREFIX`] against the caixa's
12999 // typed [`Caixa::nome`] byte-string across every DNS-1123 shape
13000 // the sibling [`validate_nome_accepts_canonical_forms`] positive-
13001 // set sweep documents — single-word, hyphen-joined, version-
13002 // suffixed, single-char, two-char, digit-start, retry-suffixed.
13003 // Every accept-set value the peer validate gate lets through must
13004 // survive the resolved-chart-name projection byte-equal.
13005 for nome in [
13006 "checkout",
13007 "cart-v2",
13008 "a",
13009 "db",
13010 "3rd-party-shim",
13011 "payment-retry",
13012 "0",
13013 ] {
13014 let c = caixa_with_nome(nome);
13015 let expected = format!("{prefix}{nome}", prefix = crate::LAREIRA_CHART_NAME_PREFIX);
13016 assert_eq!(
13017 c.lareira_chart_name(),
13018 expected,
13019 "Caixa::lareira_chart_name must compose \
13020 LAREIRA_CHART_NAME_PREFIX ({prefix:?}) against \
13021 :nome ({nome:?}) verbatim — got {got:?}, \
13022 expected {expected:?}",
13023 prefix = crate::LAREIRA_CHART_NAME_PREFIX,
13024 got = c.lareira_chart_name(),
13025 );
13026 }
13027 }
13028
13029 #[test]
13030 fn lareira_chart_name_starts_with_lifted_prefix() {
13031 // Prefix-shape pin: every [`Caixa::lareira_chart_name`] emission
13032 // must begin with the canonical
13033 // [`crate::LAREIRA_CHART_NAME_PREFIX`] byte-string on every
13034 // input, guarding a hypothetical future implementation that
13035 // migrated the prefix segment to an inline literal (`"lareira-"`)
13036 // that would silently drift from any rebrand of the lifted
13037 // constant. Peer to the sibling
13038 // [`publish_tag_starts_with_default_publish_tag_prefix`] pin on
13039 // the co-resident resolved-publish-tag composer's prefix axis.
13040 for nome in ["checkout", "cart", "a", "payment-retry", "0"] {
13041 let c = caixa_with_nome(nome);
13042 let chart = c.lareira_chart_name();
13043 assert!(
13044 chart.starts_with(crate::LAREIRA_CHART_NAME_PREFIX),
13045 "Caixa::lareira_chart_name emission {chart:?} must start \
13046 with the lifted crate::LAREIRA_CHART_NAME_PREFIX \
13047 ({prefix:?})",
13048 prefix = crate::LAREIRA_CHART_NAME_PREFIX,
13049 );
13050 }
13051 }
13052
13053 #[test]
13054 fn lareira_chart_name_byte_matches_canonical_helper_composition() {
13055 // Byte-parity pin: [`Caixa::lareira_chart_name`] must render
13056 // byte-identically to the manual open-coded
13057 // `caixa_core::lareira_chart_name(caixa.nome())` two-step
13058 // composition every prior substrate-side caller re-derived.
13059 // Guards the paired-site convergence just applied at caixa-helm's
13060 // [`render_chart_for_servico_with`] `ChartDir.name` composer,
13061 // caixa-flux's [`cluster_bundle`] per-CR `chart_name` binding,
13062 // and caixa-tatara's [`process_for_aplicacao`] `release_name`
13063 // composer (all of which now route through this accessor): a
13064 // future implementation of this method that reordered the
13065 // composition arguments, swapped the `<prefix>` constant for a
13066 // different one, or interposed a canonicalization pass on the
13067 // `:nome` axis surfaces here as a caixa-core build-time test
13068 // failure rather than as a downstream Helm chart-render / FluxCD
13069 // reconcile / tatara Process-CR mismatch far from this method's
13070 // source.
13071 for nome in [
13072 "checkout",
13073 "cart-v2",
13074 "a",
13075 "db",
13076 "3rd-party-shim",
13077 "payment-retry",
13078 ] {
13079 let c = caixa_with_nome(nome);
13080 let manual = crate::lareira_chart_name(c.nome());
13081 assert_eq!(
13082 c.lareira_chart_name(),
13083 manual,
13084 "Caixa::lareira_chart_name must byte-equal the manual \
13085 open-coded `caixa_core::lareira_chart_name(caixa.nome())` \
13086 composition across every representative :nome input — \
13087 got {got:?}, expected {manual:?}",
13088 got = c.lareira_chart_name(),
13089 );
13090 }
13091 }
13092
13093 // ── Caixa::oci_chart_ref — resolved-OCI-chart-ref composer ────────
13094
13095 #[test]
13096 fn oci_chart_ref_composes_scheme_and_lareira_chart_name_on_all_shapes() {
13097 // Fail-before-pass-after pin: [`Caixa::oci_chart_ref`] must
13098 // compose [`crate::OCI_SCHEME_PREFIX`] + the caller-supplied
13099 // `registry` + [`crate::lareira_chart_name`]-of-[`Caixa::nome`]
13100 // across the full paired `(registry, :nome)` accept-set — every
13101 // representative registry the substrate-side emitters carry
13102 // (`ghcr.io/pleme-io/charts`, the canonical CAIXA-SDLC §II
13103 // ArtifactHub-tier registry; `ghcr.io/pleme-io`, the bare-org
13104 // arm the sibling `oci_chart_ref_pins_byte_shape_against_prior_
13105 // inline_format` render-side pin exercises; `registry.example.
13106 // com`, an off-org shape; `localhost:5000`, the local-dev shape
13107 // every `feira chart` iteration path lands under) × every DNS-
13108 // 1123 `:nome` shape the peer `validate_nome_accepts_canonical_
13109 // forms` positive-set sweep documents (single-word, hyphen-
13110 // joined, single-char, two-char, digit-start, retry-suffixed).
13111 // Every accept-set pair the peer validate gates let through must
13112 // survive the resolved-OCI-ref projection byte-equal.
13113 for registry in [
13114 "ghcr.io/pleme-io/charts",
13115 "ghcr.io/pleme-io",
13116 "registry.example.com",
13117 "localhost:5000",
13118 ] {
13119 for nome in [
13120 "checkout",
13121 "cart-v2",
13122 "a",
13123 "db",
13124 "3rd-party-shim",
13125 "payment-retry",
13126 "0",
13127 ] {
13128 let c = caixa_with_nome(nome);
13129 let expected = format!(
13130 "{scheme}{registry}/{chart}",
13131 scheme = crate::OCI_SCHEME_PREFIX,
13132 chart = crate::lareira_chart_name(nome),
13133 );
13134 assert_eq!(
13135 c.oci_chart_ref(registry),
13136 expected,
13137 "Caixa::oci_chart_ref must compose \
13138 OCI_SCHEME_PREFIX ({scheme:?}) + registry ({registry:?}) + \
13139 lareira_chart_name(:nome ({nome:?})) verbatim — got {got:?}, \
13140 expected {expected:?}",
13141 scheme = crate::OCI_SCHEME_PREFIX,
13142 got = c.oci_chart_ref(registry),
13143 );
13144 }
13145 }
13146 }
13147
13148 #[test]
13149 fn oci_chart_ref_starts_with_lifted_scheme_prefix() {
13150 // Scheme-prefix-shape pin: every [`Caixa::oci_chart_ref`]
13151 // emission must begin with the canonical
13152 // [`crate::OCI_SCHEME_PREFIX`] byte-string on every input, guarding
13153 // a hypothetical future implementation that migrated the scheme
13154 // segment to an inline literal (`"oci://"`) that would silently
13155 // drift from any rebrand of the lifted constant. Peer to the
13156 // sibling [`publish_tag_starts_with_default_publish_tag_prefix`]
13157 // + [`lareira_chart_name_starts_with_lifted_prefix`] pins on the
13158 // co-resident resolved-publish-tag / resolved-chart-name
13159 // composers' prefix axes.
13160 for registry in [
13161 "ghcr.io/pleme-io/charts",
13162 "ghcr.io/pleme-io",
13163 "localhost:5000",
13164 ] {
13165 for nome in ["checkout", "cart", "a", "payment-retry", "0"] {
13166 let c = caixa_with_nome(nome);
13167 let ref_ = c.oci_chart_ref(registry);
13168 assert!(
13169 ref_.starts_with(crate::OCI_SCHEME_PREFIX),
13170 "Caixa::oci_chart_ref emission {ref_:?} must start \
13171 with the lifted crate::OCI_SCHEME_PREFIX ({scheme:?}) \
13172 — registry ({registry:?}), :nome ({nome:?})",
13173 scheme = crate::OCI_SCHEME_PREFIX,
13174 );
13175 }
13176 }
13177 }
13178
13179 #[test]
13180 fn oci_chart_ref_byte_matches_canonical_helper_composition() {
13181 // Byte-parity pin: [`Caixa::oci_chart_ref`] must render byte-
13182 // identically to the manual open-coded
13183 // `caixa_core::oci_chart_ref(registry, caixa.nome())` two-step
13184 // composition every prior substrate-side caller re-derived.
13185 // Guards the paired-site convergence just applied at caixa-
13186 // tatara's [`derive_chart_ref`] helper (which now routes through
13187 // this accessor): a future implementation of this method that
13188 // reordered the composition arguments, swapped the `<scheme>`
13189 // constant for a different one, migrated the `<chart>` segment
13190 // off the paired [`crate::lareira_chart_name`] composer, or
13191 // interposed a canonicalization pass on either input axis
13192 // surfaces here as a caixa-core build-time test failure rather
13193 // than as a downstream `helm install` / FluxCD OCI-source
13194 // reconcile / tatara `Process`-CR mismatch far from this
13195 // method's source. Sibling to the peer
13196 // [`lareira_chart_name_byte_matches_canonical_helper_composition`]
13197 // / [`publish_tag_byte_matches_manual_composition`] /
13198 // [`canonical_git_url_byte_matches_manual_composition`] byte-
13199 // parity pins that carry the same discipline on the co-resident
13200 // resolved-chart-name / resolved-publish-tag / resolved-git-URL
13201 // composers.
13202 for registry in [
13203 "ghcr.io/pleme-io/charts",
13204 "ghcr.io/pleme-io",
13205 "registry.example.com",
13206 "localhost:5000",
13207 ] {
13208 for nome in [
13209 "checkout",
13210 "cart-v2",
13211 "a",
13212 "db",
13213 "3rd-party-shim",
13214 "payment-retry",
13215 ] {
13216 let c = caixa_with_nome(nome);
13217 let manual = crate::oci_chart_ref(registry, c.nome());
13218 assert_eq!(
13219 c.oci_chart_ref(registry),
13220 manual,
13221 "Caixa::oci_chart_ref must byte-equal the manual \
13222 open-coded `caixa_core::oci_chart_ref(registry, \
13223 caixa.nome())` composition across every representative \
13224 (registry, :nome) pair — registry ({registry:?}), \
13225 :nome ({nome:?}), got {got:?}, expected {manual:?}",
13226 got = c.oci_chart_ref(registry),
13227 );
13228 }
13229 }
13230 }
13231
13232 // ── Caixa::descricao — outer top-level Option<&str> scalar accessor ──
13233
13234 #[test]
13235 fn descricao_returns_descricao_byte_string_verbatim_across_permutations() {
13236 // The canonical per-`Caixa` `:descricao` free-form-prose scalar
13237 // pin: [`Caixa::descricao`] must return the `:descricao` typed
13238 // byte-string verbatim as an `Option<&str>`, byte-equal to the
13239 // raw `self.descricao.as_deref()` access across every
13240 // representative value in the accept-set — `None` (the "omit
13241 // the slot to defer to the per-renderer `caixa.nome`-derived
13242 // fallback" arm every existing fixture without a `:descricao`
13243 // line carries), `Some("")` (a past-the-guard sentinel that
13244 // pins the accessor doesn't perform a silent `Some("") → None`
13245 // collapse on the empty arm — validate rejects `Some("")`
13246 // through `DescricaoEmpty` but the accessor must ship the raw
13247 // slot verbatim so a validate-time gate regression surfaces at
13248 // the caixa-helm / caixa-feira emit boundary rather than being
13249 // silently absorbed into the per-renderer `caixa.nome`-derived
13250 // fallback), `Some("Checkout flow.")` (the canonical one-line
13251 // prose descriptor the peer
13252 // `validate_descricao_accepts_canonical_value` positive sweep
13253 // exercises), `Some("Canonical Rust→wasm32-wasip2 caixa
13254 // Servico.")` (the multi-byte Unicode continuation-byte shape
13255 // the `hello-rio` fixture carries), `Some("→ — · ✓")` (a
13256 // multi-glyph Unicode shape the peer
13257 // `is_chart_description_shape` predicate accepts), and five
13258 // past-the-guard sentinels for the `DescricaoInvalid` refusal
13259 // cases (`Some(" Checkout flow.")` leading-whitespace,
13260 // `Some("Checkout flow. ")` trailing-whitespace,
13261 // `Some("Checkout\nflow.")` embedded-LF,
13262 // `Some("Checkout\tflow.")` embedded-TAB, and
13263 // `Some("Checkout\x00flow.")` embedded-NUL — the sentinels pin
13264 // the accessor doesn't silently absorb the refusal cases into
13265 // a fallback).
13266 //
13267 // Third outer top-level [`Caixa`] `Option<&str>`-return scalar
13268 // accessor pin on the substrate primitive — sibling of the peer
13269 // [`Caixa::licenca`] (6d5bc28) and [`Caixa::repositorio`]
13270 // (cc7332d) pins that opened the "outer [`Caixa`]
13271 // `Option<&str>` scalar" projection pin pattern this pin folds
13272 // on. Sibling in shape to the peer per-`:placement`
13273 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
13274 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
13275 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
13276 // axes, extended onto the outer top-level [`Caixa`] universal-
13277 // axis surface. Pins against a future silent detour that
13278 // returned an owned `Option<String>` (which would type-check
13279 // but silently allocate on every accessor call, breaking the
13280 // zero-cost projection every peer sibling accessor carries), a
13281 // `Some("") → None` collapse (which would silently absorb the
13282 // `DescricaoEmpty` refusal case at the accessor boundary and
13283 // the caixa-helm `Chart.yaml` `description:` fold would
13284 // silently render a `caixa.nome`-derived fallback on a
13285 // struct-literal `Caixa { descricao: Some(""), .. }`), or a
13286 // `None → Some(<default>)` collapse (which would silently
13287 // reify the per-renderer `caixa.nome`-derived fallback at the
13288 // accessor boundary and every downstream consumer keying off
13289 // the `Option::is_none()` discriminator would lose the "author
13290 // omitted the slot" signal).
13291 for descricao in [
13292 None,
13293 Some(""),
13294 Some("Checkout flow."),
13295 Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
13296 Some("→ — · ✓"),
13297 Some(" Checkout flow."),
13298 Some("Checkout flow. "),
13299 Some("Checkout\nflow."),
13300 Some("Checkout\tflow."),
13301 Some("Checkout\x00flow."),
13302 ] {
13303 let c = caixa_with_descricao(descricao);
13304 assert_eq!(
13305 c.descricao(),
13306 descricao,
13307 "Caixa::descricao must return :descricao verbatim (got \
13308 {:?}, expected {descricao:?})",
13309 c.descricao(),
13310 );
13311 assert_eq!(
13312 c.descricao(),
13313 c.descricao.as_deref(),
13314 "Caixa::descricao must byte-equal the raw \
13315 `self.descricao.as_deref()` field access across every \
13316 value in the Option<&str> accept-set",
13317 );
13318 }
13319 }
13320
13321 #[test]
13322 fn validate_descricao_empty_arm_routes_through_accessor() {
13323 // Composition pin: [`Caixa::validate_descricao`]'s empty-arm
13324 // gate must key off [`Caixa::descricao`], not the raw
13325 // `self.descricao.as_deref()` field access. Structurally: a
13326 // `Caixa { descricao: Some(""), .. }` must surface the
13327 // `DescricaoEmpty` refusal exactly, and a
13328 // `Caixa { descricao: Some("Checkout flow."), .. }` (the
13329 // canonical one-line-prose form) must pass validate. The pair
13330 // jointly pins the accessor + validate-gate composition: any
13331 // future silent detour that had the accessor return `None` on
13332 // the empty arm (a `.filter(|s| !s.is_empty())` collapse) would
13333 // silently absorb the `DescricaoEmpty` refusal at the accessor
13334 // boundary and the validate gate would accept a struct-literal
13335 // `Caixa { descricao: Some(""), .. }` — the composition pin
13336 // catches that at caixa-core build time.
13337 //
13338 // Peer of the [`Caixa::licenca`] (6d5bc28)
13339 // `validate_licenca_empty_arm_routes_through_accessor` and
13340 // [`Caixa::repositorio`] (cc7332d)
13341 // `validate_repositorio_empty_arm_routes_through_accessor`
13342 // composition pins on the sibling outer top-level [`Caixa`]
13343 // `Option<&str>` universal-axis surface — same "the validate /
13344 // shape-gate predicate must route through the substrate-
13345 // primitive typed dispatch" discipline extended onto the third
13346 // outer top-level [`Caixa`] universal-axis `Option<&str>`-
13347 // composition surface.
13348 let c = caixa_with_descricao(Some(""));
13349 assert!(
13350 matches!(c.validate_descricao(), Err(ManifestError::DescricaoEmpty),),
13351 "validate_descricao must reject descricao == Some(\"\") \
13352 with DescricaoEmpty — the accessor and the validate gate \
13353 must route through the same substrate-primitive typed \
13354 dispatch on the :descricao empty arm",
13355 );
13356 let c = caixa_with_descricao(Some("Checkout flow."));
13357 assert!(
13358 c.validate_descricao().is_ok(),
13359 "validate_descricao must accept descricao == \
13360 Some(\"Checkout flow.\") (the canonical one-line-prose \
13361 chart-description shape)",
13362 );
13363 }
13364
13365 #[test]
13366 fn descricao_projects_option_str_by_borrow() {
13367 // The by-borrow pin: [`Caixa::descricao`] returns
13368 // `Option<&str>` by borrow — the `&str` borrows the underlying
13369 // `String` storage of the `Option<String>` slot and the
13370 // accessor must not allocate a fresh `String` on every call.
13371 // Peer of the [`Caixa::licenca`] (6d5bc28) and
13372 // [`Caixa::repositorio`] (cc7332d) by-borrow pins on the peer
13373 // outer top-level [`Caixa`] `Option<&str>`-return axes, and of
13374 // the per-`:placement`
13375 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
13376 // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
13377 // return axis, extended onto the third outer top-level
13378 // [`Caixa`] universal-axis `Option<&str>` shape — the
13379 // accessor's returned `&str` must borrow from `&self` (the
13380 // returned reference's lifetime is tied to `&self`), and
13381 // calling the accessor twice on the same [`Caixa`] must yield
13382 // the same `Option<&str>` verbatim (idempotent, no side
13383 // effects on `&self`).
13384 //
13385 // Pins against a future silent detour that returned an owned
13386 // `Option<String>` (which would type-check but silently
13387 // allocate on every call, breaking the zero-cost projection
13388 // every peer sibling accessor carries), or a one-arm-only
13389 // accessor that returned a saturating value on some sentinel
13390 // input (breaking the pass-through invariant the sibling
13391 // required-scalar accessors carry).
13392 for descricao in [
13393 None,
13394 Some(""),
13395 Some("Checkout flow."),
13396 Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
13397 ] {
13398 let c = caixa_with_descricao(descricao);
13399 let first = c.descricao();
13400 let second = c.descricao();
13401 assert_eq!(
13402 first, second,
13403 "Caixa::descricao must be idempotent — two successive \
13404 calls on the same &self must return the same \
13405 Option<&str>",
13406 );
13407 assert_eq!(
13408 first, descricao,
13409 "Caixa::descricao must return :descricao verbatim by \
13410 borrow — got {first:?}, expected {descricao:?}",
13411 );
13412 }
13413 }
13414
13415 // ── validate_edicao — universal-axis language-edition shape ──
13416
13417 fn caixa_with_edicao(edicao: Option<&str>) -> Caixa {
13418 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13419 c.edicao = edicao.map(String::from);
13420 c
13421 }
13422
13423 #[test]
13424 fn validate_edicao_accepts_none() {
13425 // The omit-the-slot identity: `:edicao` is optional. The
13426 // gate is a no-op when the author didn't declare a value —
13427 // every caixa without an `:edicao` line trivially passes,
13428 // and the substrate-side build pipeline falls back to the
13429 // documented default edition. Mirrors the peer
13430 // `validate_licenca_accepts_none` posture on the sibling
13431 // `Option<String>` Caixa slot.
13432 let c = caixa_with_edicao(None);
13433 c.validate_edicao().unwrap();
13434 }
13435
13436 #[test]
13437 fn validate_edicao_accepts_canonical_value() {
13438 // Positive control: the canonical `"2026"` edition every
13439 // existing renderer-side fixture (`caixa-helm`, `caixa-flux`,
13440 // `caixa-mesh`) carries by construction passes the gate.
13441 // Future-introduced sibling editions (`"2027"`, `"2030"`,
13442 // `"2049"`) that match the same 4-digit ASCII decimal year
13443 // shape must also trivially pass — the structural shape
13444 // predicate accepts every well-formed year regardless of
13445 // whether the substrate yet understands the specific value
13446 // (a future known-edition allowlist tightens that).
13447 for ed in ["2026", "2027", "2030", "2049"] {
13448 let c = caixa_with_edicao(Some(ed));
13449 c.validate_edicao()
13450 .unwrap_or_else(|err| panic!("canonical {ed:?} must pass: {err:?}"));
13451 }
13452 }
13453
13454 #[test]
13455 fn validate_edicao_rejects_empty_some() {
13456 // Canonical paste-from-blank-doc footgun. Without this gate
13457 // the empty `Some("")` silently lands as `(:edicao "")` in
13458 // the rendered caixa.lisp and a future renderer-side
13459 // consumer's `Option::unwrap_or_else` (which only fires on
13460 // `None`) skips its fallback. Mirrors the peer
13461 // [`ManifestError::LicencaEmpty`] empty-arm on the sibling
13462 // `Option<String>` Caixa slot.
13463 let c = caixa_with_edicao(Some(""));
13464 let err = c.validate_edicao().unwrap_err();
13465 assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
13466 }
13467
13468 #[test]
13469 fn validate_edicao_rejects_free_form_non_year() {
13470 // Free-form non-year footgun: the bare `"x"` / `"latest"` /
13471 // `"nightly"` shapes carry no operational meaning on the
13472 // substrate's build-time edition selector. Until this gate
13473 // landed the bare empty-arm check let every such value
13474 // through and broke far from the source caixa.lisp. Peer
13475 // with the shape-predicate cascade
13476 // `validate_repositorio_rejects_missing_colon_separator`
13477 // establishes past its own empty arm.
13478 for ed in ["x", "latest", "nightly", "stable"] {
13479 let c = caixa_with_edicao(Some(ed));
13480 let err = c.validate_edicao().unwrap_err();
13481 assert!(
13482 matches!(err, ManifestError::EdicaoInvalid { .. }),
13483 "expected EdicaoInvalid on {ed:?}, got {err:?}",
13484 );
13485 }
13486 }
13487
13488 #[test]
13489 fn validate_edicao_rejects_trailing_whitespace() {
13490 // Paste-from-doc whitespace footgun. A trailing space in
13491 // the `:edicao` value would silently break the substrate's
13492 // build-time edition match-table lookup at the rendered
13493 // artifact's edition-selector consumer. The shape predicate
13494 // refuses every whitespace byte by construction (any byte
13495 // outside `0-9` fails `is_ascii_digit`). Peer with
13496 // `validate_repositorio_rejects_whitespace`.
13497 let c = caixa_with_edicao(Some("2026 "));
13498 let err = c.validate_edicao().unwrap_err();
13499 let ManifestError::EdicaoInvalid { edicao, .. } = err else {
13500 panic!("expected EdicaoInvalid, got {err:?}");
13501 };
13502 assert_eq!(edicao, "2026 ");
13503 }
13504
13505 #[test]
13506 fn validate_edicao_rejects_leading_whitespace() {
13507 // Symmetric paste-from-doc whitespace footgun on the leading
13508 // boundary — the gate refuses every shape with a non-digit
13509 // byte by construction.
13510 let c = caixa_with_edicao(Some(" 2026"));
13511 let err = c.validate_edicao().unwrap_err();
13512 assert!(
13513 matches!(err, ManifestError::EdicaoInvalid { .. }),
13514 "got {err:?}",
13515 );
13516 }
13517
13518 #[test]
13519 fn validate_edicao_rejects_control_char() {
13520 // Paste-from-multiline-doc CRLF footgun — control characters
13521 // at the value boundary break the substrate's build-time
13522 // edition-selector parser. Peer with
13523 // `validate_repositorio_rejects_control_char`.
13524 let c = caixa_with_edicao(Some("2026\n"));
13525 let err = c.validate_edicao().unwrap_err();
13526 assert!(
13527 matches!(err, ManifestError::EdicaoInvalid { .. }),
13528 "got {err:?}",
13529 );
13530 }
13531
13532 #[test]
13533 fn validate_edicao_rejects_non_ascii_lookalike() {
13534 // Fullwidth-keyboard look-alike footgun — `"2026"` is
13535 // the U+FF12 U+FF10 U+FF12 U+FF16 sequence (CJK fullwidth
13536 // digits), 4 codepoints but 12 UTF-8 bytes; the substrate's
13537 // edition selector wants an ASCII year, and the gate
13538 // refuses every non-ASCII shape by construction (length in
13539 // bytes is 12 ≠ 4, *and* every byte falls outside
13540 // `is_ascii_digit`'s `0-9` range).
13541 let c = caixa_with_edicao(Some("2026"));
13542 let err = c.validate_edicao().unwrap_err();
13543 assert!(
13544 matches!(err, ManifestError::EdicaoInvalid { .. }),
13545 "got {err:?}",
13546 );
13547 }
13548
13549 #[test]
13550 fn validate_edicao_rejects_version_tag_prefix() {
13551 // Common version-tag idiom footgun — `"v2026"` / `"e2026"`
13552 // / `"r2026"` are familiar shapes from git-tag / Rust
13553 // edition / release-tag conventions that don't apply to
13554 // the year-shaped edition axis. The shape predicate refuses
13555 // every leading non-digit prefix.
13556 for ed in ["v2026", "e2026", "r2026"] {
13557 let c = caixa_with_edicao(Some(ed));
13558 let err = c.validate_edicao().unwrap_err();
13559 assert!(
13560 matches!(err, ManifestError::EdicaoInvalid { .. }),
13561 "expected EdicaoInvalid on {ed:?}, got {err:?}",
13562 );
13563 }
13564 }
13565
13566 #[test]
13567 fn validate_edicao_rejects_decimal_shape() {
13568 // Decimal-shaped pseudo-version footgun — `"2026.1"` /
13569 // `"2026.0"` are familiar shapes from semver / float
13570 // conventions that don't apply to the year-shaped edition
13571 // axis. The shape predicate refuses every non-digit byte
13572 // (`.` falls outside `is_ascii_digit`).
13573 for ed in ["2026.1", "2026.0", "2026.0.1"] {
13574 let c = caixa_with_edicao(Some(ed));
13575 let err = c.validate_edicao().unwrap_err();
13576 assert!(
13577 matches!(err, ManifestError::EdicaoInvalid { .. }),
13578 "expected EdicaoInvalid on {ed:?}, got {err:?}",
13579 );
13580 }
13581 }
13582
13583 #[test]
13584 fn validate_edicao_rejects_wrong_length_numeric() {
13585 // Wrong-length numeric footgun — `"26"` (truncated) /
13586 // `"202"` (truncated) / `"20260"` (extra digit) / `"00026"`
13587 // (zero-padded too wide) all parse as integers but don't
13588 // name a 4-digit year. The shape predicate refuses every
13589 // value whose length isn't exactly 4 bytes.
13590 for ed in ["26", "202", "20260", "00026", "9"] {
13591 let c = caixa_with_edicao(Some(ed));
13592 let err = c.validate_edicao().unwrap_err();
13593 assert!(
13594 matches!(err, ManifestError::EdicaoInvalid { .. }),
13595 "expected EdicaoInvalid on {ed:?}, got {err:?}",
13596 );
13597 }
13598 }
13599
13600 #[test]
13601 fn validate_edicao_empty_takes_precedence_over_shape() {
13602 // Empty-first cascade pin: the empty `Some("")` surfaces
13603 // the narrower `EdicaoEmpty` not the shape-predicate-
13604 // wrapped `EdicaoInvalid`, mirroring the peer
13605 // `validate_repositorio_empty_takes_precedence_over_shape`
13606 // (`RepositorioEmpty` → `RepositorioInvalid`),
13607 // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` →
13608 // `VersaoInvalid`, `FonteRepoEmpty` → `FonteRepoInvalid`
13609 // cascades. The shape predicate also refuses the empty
13610 // input (defensively — `s.len() != 4`), but the
13611 // manifest-layer empty arm runs first to surface the
13612 // narrower diagnostic verbatim.
13613 let c = caixa_with_edicao(Some(""));
13614 let err = c.validate_edicao().unwrap_err();
13615 assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
13616 }
13617
13618 #[test]
13619 fn validate_edicao_template_passes() {
13620 // Round-trip pin: the bare `Caixa::template` shape (which
13621 // carries `:edicao "2026"` verbatim) passes the gate by
13622 // construction. A future template-shape change that
13623 // introduced `(:edicao "")` or a non-year value would
13624 // surface here as a regression. Mirrors the peer
13625 // `validate_licenca_template_passes` pin.
13626 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13627 c.validate_edicao().unwrap();
13628 }
13629
13630 #[test]
13631 fn validate_edicao_diagnostic_names_offending_slot() {
13632 // Diagnostic-shape pin (peer with
13633 // `validate_licenca_diagnostic_names_offending_slot`): the
13634 // error's Display surfaces the `:edicao` slot name verbatim,
13635 // so a `feira lint` run can render the diagnostic without
13636 // re-parsing and the author can grep their caixa.lisp for
13637 // the offending `:edicao` line.
13638 let c = caixa_with_edicao(Some(""));
13639 let rendered = c.validate_edicao().unwrap_err().to_string();
13640 assert!(
13641 rendered.contains(":edicao"),
13642 "diagnostic must name the offending slot: {rendered}",
13643 );
13644 }
13645
13646 #[test]
13647 fn validate_edicao_invalid_diagnostic_carries_offending_value() {
13648 // Diagnostic-shape pin on the shape-predicate arm (peer
13649 // with `validate_repositorio_diagnostic_carries_offending_value`):
13650 // the error's Display surfaces the offending value + slot
13651 // name verbatim, so a `feira lint` run can render the
13652 // diagnostic without re-parsing and the author can grep
13653 // their caixa.lisp for the offending `:edicao` value.
13654 let c = caixa_with_edicao(Some("v2026"));
13655 let rendered = c.validate_edicao().unwrap_err().to_string();
13656 assert!(
13657 rendered.contains(":edicao"),
13658 "diagnostic must name the offending slot: {rendered}",
13659 );
13660 assert!(
13661 rendered.contains("v2026"),
13662 "diagnostic must quote the offending value: {rendered}",
13663 );
13664 }
13665
13666 // ── Caixa::edicao — outer top-level Option<&str> scalar accessor ──
13667
13668 #[test]
13669 fn edicao_returns_edicao_byte_string_verbatim_across_permutations() {
13670 // The canonical per-`Caixa` `:edicao` language-edition scalar
13671 // pin: [`Caixa::edicao`] must return the `:edicao` typed
13672 // byte-string verbatim as an `Option<&str>`, byte-equal to the
13673 // raw `self.edicao.as_deref()` access across every representative
13674 // value in the accept-set — `None` (the "omit the slot to defer
13675 // to the substrate's default edition" arm every existing
13676 // [`caixa-resolver`] fixture without an `:edicao` line carries),
13677 // `Some("")` (a past-the-guard sentinel that pins the accessor
13678 // doesn't perform a silent `Some("") → None` collapse on the
13679 // empty arm — validate rejects `Some("")` through `EdicaoEmpty`
13680 // but the accessor must ship the raw slot verbatim so a
13681 // validate-time gate regression surfaces at any future edition-
13682 // aware consumer's boundary rather than being silently absorbed
13683 // into the substrate's default edition), `Some("2026")` (the
13684 // canonical 4-digit-ASCII-decimal-year shape every `feira init`
13685 // template scaffolds via [`Caixa::template`] and every
13686 // renderer-side fixture at `caixa-helm/src/lib.rs:978` /
13687 // `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208`
13688 // carries by construction), `Some("2018")` / `Some("2021")` /
13689 // `Some("2024")` (canonical 4-digit-ASCII-decimal-year shapes
13690 // peer with Cargo's `[package] edition` grammar every future-
13691 // introduced sibling to `"2026"` will follow), and eight
13692 // past-the-guard sentinels for the `EdicaoInvalid` refusal cases
13693 // (`Some("2026 ")` trailing-whitespace, `Some(" 2026")` leading-
13694 // whitespace, `Some("2026\n")` embedded-LF, `Some("2026")`
13695 // fullwidth-non-ASCII-lookalike, `Some("v2026")` version-tag-
13696 // prefix, `Some("2026.1")` decimal-shape, `Some("26")` wrong-
13697 // length-numeric, `Some("latest")` free-form-non-year — the
13698 // sentinels pin the accessor doesn't silently absorb the
13699 // refusal cases into a substrate-default-edition fallback).
13700 //
13701 // Fourth and final outer top-level [`Caixa`] `Option<&str>`-
13702 // return scalar accessor pin on the substrate primitive —
13703 // sibling of the peer [`Caixa::licenca`] (6d5bc28),
13704 // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
13705 // (3f16e2f) pins that opened the "outer [`Caixa`]
13706 // `Option<&str>` scalar" projection pin pattern this pin folds
13707 // on. Sibling in shape to the peer per-`:placement`
13708 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
13709 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
13710 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
13711 // axes, extended onto the outer top-level [`Caixa`] universal-
13712 // axis surface's last unlifted `Option<String>` slot. Pins
13713 // against a future silent detour that returned an owned
13714 // `Option<String>` (which would type-check but silently
13715 // allocate on every accessor call, breaking the zero-cost
13716 // projection every peer sibling accessor carries), a
13717 // `Some("") → None` collapse (which would silently absorb the
13718 // `EdicaoEmpty` refusal case at the accessor boundary and any
13719 // future edition-aware consumer would silently fall back to
13720 // the substrate's default edition on a struct-literal
13721 // `Caixa { edicao: Some(""), .. }`), or a
13722 // `None → Some("2026")` collapse (which would silently reify
13723 // the substrate's default edition at the accessor boundary
13724 // and every downstream consumer keying off the
13725 // `Option::is_none()` discriminator would lose the "author
13726 // omitted the slot" signal).
13727 for edicao in [
13728 None,
13729 Some(""),
13730 Some("2026"),
13731 Some("2018"),
13732 Some("2021"),
13733 Some("2024"),
13734 Some("2026 "),
13735 Some(" 2026"),
13736 Some("2026\n"),
13737 Some("2026"),
13738 Some("v2026"),
13739 Some("2026.1"),
13740 Some("26"),
13741 Some("latest"),
13742 ] {
13743 let c = caixa_with_edicao(edicao);
13744 assert_eq!(
13745 c.edicao(),
13746 edicao,
13747 "Caixa::edicao must return :edicao verbatim (got {:?}, \
13748 expected {edicao:?})",
13749 c.edicao(),
13750 );
13751 assert_eq!(
13752 c.edicao(),
13753 c.edicao.as_deref(),
13754 "Caixa::edicao must byte-equal the raw \
13755 `self.edicao.as_deref()` field access across every \
13756 value in the Option<&str> accept-set",
13757 );
13758 }
13759 }
13760
13761 #[test]
13762 fn validate_edicao_empty_arm_routes_through_accessor() {
13763 // Composition pin: [`Caixa::validate_edicao`]'s empty-arm gate
13764 // must key off [`Caixa::edicao`], not the raw
13765 // `self.edicao.as_deref()` field access. Structurally: a
13766 // `Caixa { edicao: Some(""), .. }` must surface the
13767 // `EdicaoEmpty` refusal exactly, and a
13768 // `Caixa { edicao: Some("2026"), .. }` (the canonical
13769 // 4-digit-ASCII-decimal-year form) must pass validate. The
13770 // pair jointly pins the accessor + validate-gate composition:
13771 // any future silent detour that had the accessor return `None`
13772 // on the empty arm (a `.filter(|s| !s.is_empty())` collapse)
13773 // would silently absorb the `EdicaoEmpty` refusal at the
13774 // accessor boundary and the validate gate would accept a
13775 // struct-literal `Caixa { edicao: Some(""), .. }` — the
13776 // composition pin catches that at caixa-core build time.
13777 //
13778 // Peer of the [`Caixa::licenca`] (6d5bc28)
13779 // `validate_licenca_empty_arm_routes_through_accessor`,
13780 // [`Caixa::repositorio`] (cc7332d)
13781 // `validate_repositorio_empty_arm_routes_through_accessor`,
13782 // and [`Caixa::descricao`] (3f16e2f)
13783 // `validate_descricao_empty_arm_routes_through_accessor`
13784 // composition pins on the sibling outer top-level [`Caixa`]
13785 // `Option<&str>` universal-axis surface — same "the validate /
13786 // shape-gate predicate must route through the substrate-
13787 // primitive typed dispatch" discipline extended onto the
13788 // fourth and final outer top-level [`Caixa`] universal-axis
13789 // `Option<&str>`-composition surface, closing the accessor-
13790 // composition family.
13791 let c = caixa_with_edicao(Some(""));
13792 assert!(
13793 matches!(c.validate_edicao(), Err(ManifestError::EdicaoEmpty)),
13794 "validate_edicao must reject edicao == Some(\"\") with \
13795 EdicaoEmpty — the accessor and the validate gate must \
13796 route through the same substrate-primitive typed dispatch \
13797 on the :edicao empty arm",
13798 );
13799 let c = caixa_with_edicao(Some("2026"));
13800 assert!(
13801 c.validate_edicao().is_ok(),
13802 "validate_edicao must accept edicao == Some(\"2026\") \
13803 (the canonical 4-digit-ASCII-decimal-year shape)",
13804 );
13805 }
13806
13807 #[test]
13808 fn edicao_projects_option_str_by_borrow() {
13809 // The by-borrow pin: [`Caixa::edicao`] returns
13810 // `Option<&str>` by borrow — the `&str` borrows the underlying
13811 // `String` storage of the `Option<String>` slot and the
13812 // accessor must not allocate a fresh `String` on every call.
13813 // Peer of the [`Caixa::licenca`] (6d5bc28),
13814 // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
13815 // (3f16e2f) by-borrow pins on the peer outer top-level
13816 // [`Caixa`] `Option<&str>`-return axes, and of the
13817 // per-`:placement`
13818 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
13819 // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
13820 // return axis, extended onto the fourth and final outer top-
13821 // level [`Caixa`] universal-axis `Option<&str>` shape — the
13822 // accessor's returned `&str` must borrow from `&self` (the
13823 // returned reference's lifetime is tied to `&self`), and
13824 // calling the accessor twice on the same [`Caixa`] must yield
13825 // the same `Option<&str>` verbatim (idempotent, no side
13826 // effects on `&self`).
13827 //
13828 // Pins against a future silent detour that returned an owned
13829 // `Option<String>` (which would type-check but silently
13830 // allocate on every call, breaking the zero-cost projection
13831 // every peer sibling accessor carries), or a one-arm-only
13832 // accessor that returned a saturating value on some sentinel
13833 // input (breaking the pass-through invariant the sibling
13834 // required-scalar accessors carry).
13835 for edicao in [None, Some(""), Some("2026"), Some("2018")] {
13836 let c = caixa_with_edicao(edicao);
13837 let first = c.edicao();
13838 let second = c.edicao();
13839 assert_eq!(
13840 first, second,
13841 "Caixa::edicao must be idempotent — two successive \
13842 calls on the same &self must return the same \
13843 Option<&str>",
13844 );
13845 assert_eq!(
13846 first, edicao,
13847 "Caixa::edicao must return :edicao verbatim by \
13848 borrow — got {first:?}, expected {edicao:?}",
13849 );
13850 }
13851 }
13852
13853 #[test]
13854 fn nome_returns_nome_byte_string_verbatim_across_permutations() {
13855 // The canonical per-`Caixa` `:nome` universal-axis DNS-1123-
13856 // label caixa-identity scalar pin: [`Caixa::nome`] must return
13857 // the `:nome` typed `String` verbatim as `&str`, byte-equal to
13858 // the raw field access across every representative value in
13859 // the accept-set — the canonical `"demo"` template baseline
13860 // (the same `feira init`-scaffolded default the sibling
13861 // `validate_nome_accepts_canonical_template` positive-control
13862 // gate pins), plus every sibling per-typed-slot atom accessor's
13863 // canonical positive-arm byte-string (`"catalog"` per
13864 // [`crate::aplicacao::Membro::nome`], `"cart"` per the peer
13865 // per-`:contratos` `:de`, `"hello-rio"` per the canonical
13866 // `caixa-helm`/`caixa-flux` cross-crate integration-test
13867 // fixture, `"checkout"` per the M3 mesh-slot Aplicacao
13868 // canonical example), plus every past-the-guard sentinel for
13869 // the `NomeEmpty` / `NomeInvalid` / `NomeChartNameBudgetExceeded`
13870 // refusal cases (`""`, `"Bad_Name"`, `"a"` × 56 — 56 bytes fits
13871 // the bare DNS-1123 63-byte cap but overflows the joint
13872 // `lareira-<nome>` chart-name budget the sibling
13873 // [`Caixa::validate_nome_chart_name_budget`] gate closes on).
13874 //
13875 // The past-the-guard sentinels pin the accessor doesn't
13876 // silently absorb the refusal cases into a template-derived
13877 // fallback (a future `.nome().is_empty().then(|| "demo")`
13878 // collapse would silently absorb the `NomeEmpty` refusal at
13879 // the accessor boundary and the validate gate would accept a
13880 // struct-literal `Caixa { nome: "".into(), .. }` — the pin
13881 // catches that at caixa-core build time).
13882 //
13883 // First outer top-level [`Caixa`] `&str`-return required-
13884 // scalar accessor pin — opens the "outer [`Caixa`] `&str`
13885 // required-scalar" projection pattern the sibling per-`Caixa`
13886 // `:versao` future lift folds on. Sibling in shape to the peer
13887 // per-`:membros` [`crate::aplicacao::Membro::nome`] (4a32abf)
13888 // required-`String`-carry accessor pin on the sibling per-
13889 // sub-struct required-axis, extended onto the outer top-level
13890 // [`Caixa`] universal-axis required-`String`-carry axis.
13891 for nome in [
13892 "demo",
13893 "catalog",
13894 "cart",
13895 "hello-rio",
13896 "checkout",
13897 "",
13898 "Bad_Name",
13899 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
13900 ] {
13901 let c = caixa_with_nome(nome);
13902 assert_eq!(
13903 c.nome(),
13904 nome,
13905 "Caixa::nome must return :nome verbatim (got {}, \
13906 expected {nome})",
13907 c.nome(),
13908 );
13909 assert_eq!(
13910 c.nome(),
13911 c.nome.as_str(),
13912 "Caixa::nome must byte-equal the raw .nome field \
13913 access across every value in the String accept-set",
13914 );
13915 }
13916 }
13917
13918 #[test]
13919 fn validate_nome_empty_arm_routes_through_accessor() {
13920 // Composition pin: [`Caixa::validate_nome`]'s empty-arm must
13921 // key off [`Caixa::nome`], not the raw `.nome` field access.
13922 // Structurally: a `Caixa { nome: "".into(), .. }` must surface
13923 // the `NomeEmpty` refusal exactly, and the canonical `"demo"`
13924 // template baseline (the peer positive-arm the sibling
13925 // `validate_nome_accepts_canonical_template` gate carves out)
13926 // must pass validate. The pair jointly pins the accessor +
13927 // validate-gate composition: any future silent detour that
13928 // had the accessor return a fresh `"demo"` on the empty arm
13929 // (a `.nome().is_empty().then(|| "demo")` fallback collapse)
13930 // would silently absorb the `NomeEmpty` refusal at the
13931 // accessor boundary and the validate gate would accept a
13932 // struct-literal `Caixa { nome: "".into(), .. }` — the
13933 // composition pin catches that at caixa-core build time.
13934 //
13935 // Peer of the sibling per-`Caixa`
13936 // `validate_licenca_empty_arm_routes_through_accessor` (6d5bc28)
13937 // / `validate_repositorio_empty_arm_routes_through_accessor`
13938 // (cc7332d) / `validate_descricao_empty_arm_routes_through_accessor`
13939 // (3f16e2f) / `validate_edicao_empty_arm_routes_through_accessor`
13940 // (2641cbd) composition pins on the sibling outer top-level
13941 // [`Caixa`] `Option<&str>` axes — same "the validate /
13942 // shape-gate predicate must route through the substrate-
13943 // primitive typed dispatch" discipline extended onto the peer
13944 // outer top-level [`Caixa`] required-`&str` composition axis.
13945 let c = caixa_with_nome("");
13946 assert!(
13947 matches!(c.validate_nome(), Err(ManifestError::NomeEmpty)),
13948 "validate_nome must reject nome == \"\" with NomeEmpty — \
13949 the accessor and the validate gate must route through the \
13950 same substrate-primitive typed dispatch on the :nome \
13951 empty-arm",
13952 );
13953 let c = caixa_with_nome("demo");
13954 assert!(
13955 c.validate_nome().is_ok(),
13956 "validate_nome must accept nome == \"demo\" (the canonical \
13957 DNS-1123-label template baseline)",
13958 );
13959 }
13960
13961 #[test]
13962 fn nome_projects_str_by_borrow() {
13963 // The by-borrow pin: [`Caixa::nome`] returns `&str` by borrow
13964 // — the `&str` borrows the underlying `String` storage of the
13965 // required `nome` slot and the accessor must not allocate a
13966 // fresh `String` on every call. Peer of the [`Caixa::licenca`]
13967 // (6d5bc28) / [`Caixa::repositorio`] (cc7332d) /
13968 // [`Caixa::descricao`] (3f16e2f) / [`Caixa::edicao`] (2641cbd)
13969 // by-borrow pins on the peer outer top-level [`Caixa`]
13970 // `Option<&str>`-return axes, extended onto the first outer
13971 // top-level [`Caixa`] required-`&str`-return axis — the
13972 // accessor's returned `&str` must borrow from `&self` (the
13973 // returned reference's lifetime is tied to `&self`), and
13974 // calling the accessor twice on the same [`Caixa`] must yield
13975 // the same `&str` verbatim (idempotent, no side effects on
13976 // `&self`).
13977 //
13978 // Pins against a future silent detour that returned an owned
13979 // `String` (which would type-check but silently allocate on
13980 // every call, breaking the zero-cost projection every peer
13981 // sibling accessor carries), an accidental
13982 // `.nome.to_lowercase()` detour that returned a fresh
13983 // allocation through an already-DNS-1123-lowercase-only
13984 // string (breaking a future `const fn` regression), or a
13985 // one-arm-only accessor that returned a canonicalized value
13986 // on some sentinel input (breaking the pass-through invariant
13987 // the sibling required-scalar accessors carry).
13988 for nome in ["demo", "catalog", "hello-rio", "checkout"] {
13989 let c = caixa_with_nome(nome);
13990 let first = c.nome();
13991 let second = c.nome();
13992 assert_eq!(
13993 first, second,
13994 "Caixa::nome must be idempotent — two successive calls \
13995 on the same &self must return the same &str",
13996 );
13997 assert_eq!(
13998 first, nome,
13999 "Caixa::nome must return :nome verbatim by borrow — \
14000 got {first}, expected {nome}",
14001 );
14002 }
14003 }
14004
14005 #[test]
14006 fn versao_returns_versao_byte_string_verbatim_across_permutations() {
14007 // The canonical per-`Caixa` `:versao` universal-axis SemVer-2
14008 // pinned-version scalar pin: [`Caixa::versao`] must return the
14009 // `:versao` typed `String` verbatim as `&str`, byte-equal to the
14010 // raw `.versao` field access across every representative value
14011 // in the accept-set — the canonical `"0.1.0"` template baseline
14012 // (the same `feira init`-scaffolded default the sibling
14013 // `validate_versao_accepts_canonical_template` positive-control
14014 // gate pins), plus every canonical SemVer-2 shape the sibling
14015 // `validate_versao_accepts_canonical_forms` positive-arm sweep
14016 // covers (`"0.0.0"`, `"1.0.0"`, `"0.2.0-rc.1"`,
14017 // `"1.0.0-alpha.0"`, `"1.0.0+build.42"`, `"1.0.0-rc.1+build.42"`,
14018 // `"10.20.30"`), plus every past-the-guard sentinel for the
14019 // `VersaoEmpty` / `VersaoInvalid` refusal cases (`""` the empty
14020 // arm, `"v0.1.0"` the git-tag-shape-leak footgun, `"0.1"` the
14021 // missing-patch footgun, `"^0.1"` the requirement-shape-leak
14022 // footgun, `"0.1.0.0"` the four-part-Java-convention footgun,
14023 // `"latest"` the docker-tag-shape footgun — the sentinels pin
14024 // the accessor doesn't silently absorb the refusal cases into a
14025 // template-derived fallback like `"0.1.0"`).
14026 //
14027 // The past-the-guard sentinels pin the accessor doesn't silently
14028 // absorb the refusal cases into a template-derived fallback (a
14029 // future `.versao().is_empty().then(|| "0.1.0")` collapse would
14030 // silently absorb the `VersaoEmpty` refusal at the accessor
14031 // boundary and the validate gate would accept a struct-literal
14032 // `Caixa { versao: "".into(), .. }` — the pin catches that at
14033 // caixa-core build time).
14034 //
14035 // Second outer top-level [`Caixa`] `&str`-return required-scalar
14036 // accessor pin — folds on the "outer [`Caixa`] `&str` required-
14037 // scalar" projection pattern the sibling per-`Caixa`
14038 // [`Caixa::nome`] (e6b7d97) opened. Sibling in shape to the peer
14039 // per-`:membros` [`crate::aplicacao::Membro::versao_requirement`]
14040 // (4127bb6) / per-`:children`
14041 // [`crate::supervisor::ChildSpec::versao_requirement`] (2c053c8)
14042 // / per-`:upgrade-from`
14043 // [`crate::UpgradeFromEntry::prior_versao`] (75d27a8) per-sub-
14044 // struct `:versao`-shaped `&str`-return accessor pins on the
14045 // sibling per-typed-slot version-carrier axes, extended onto the
14046 // second outer top-level [`Caixa`] universal-axis required-
14047 // `String`-carry axis so the two universal-axis identity-
14048 // carrying scalars every `defcaixa` form supplies (`:nome` +
14049 // `:versao`) share the same "one typed dispatch per axis" pin
14050 // discipline.
14051 for versao in [
14052 "0.1.0",
14053 "0.0.0",
14054 "1.0.0",
14055 "0.2.0-rc.1",
14056 "1.0.0-alpha.0",
14057 "1.0.0+build.42",
14058 "1.0.0-rc.1+build.42",
14059 "10.20.30",
14060 "",
14061 "v0.1.0",
14062 "0.1",
14063 "^0.1",
14064 "0.1.0.0",
14065 "latest",
14066 ] {
14067 let c = caixa_with_versao(versao);
14068 assert_eq!(
14069 c.versao(),
14070 versao,
14071 "Caixa::versao must return :versao verbatim (got {}, \
14072 expected {versao})",
14073 c.versao(),
14074 );
14075 assert_eq!(
14076 c.versao(),
14077 c.versao.as_str(),
14078 "Caixa::versao must byte-equal the raw .versao field \
14079 access across every value in the String accept-set",
14080 );
14081 }
14082 }
14083
14084 #[test]
14085 fn validate_versao_empty_arm_routes_through_accessor() {
14086 // Composition pin: [`Caixa::validate_versao`]'s empty-arm gate
14087 // must key off [`Caixa::versao`], not the raw `.versao` field
14088 // access. Structurally: a `Caixa { versao: "".into(), .. }` must
14089 // surface the `VersaoEmpty` refusal exactly, and the canonical
14090 // `"0.1.0"` template baseline (the peer positive-arm the sibling
14091 // `validate_versao_accepts_canonical_template` gate carves out)
14092 // must pass validate. The pair jointly pins the accessor +
14093 // validate-gate composition: any future silent detour that had
14094 // the accessor return a fresh `"0.1.0"` on the empty arm
14095 // (a `.versao().is_empty().then(|| "0.1.0")` fallback collapse)
14096 // would silently absorb the `VersaoEmpty` refusal at the
14097 // accessor boundary and the validate gate would accept a
14098 // struct-literal `Caixa { versao: "".into(), .. }` — the
14099 // composition pin catches that at caixa-core build time.
14100 //
14101 // Peer of the sibling per-`Caixa`
14102 // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97)
14103 // composition pin on the sibling outer top-level [`Caixa`]
14104 // required-`&str` universal-axis surface — same "the validate /
14105 // shape-gate predicate must route through the substrate-
14106 // primitive typed dispatch" discipline extended onto the peer
14107 // outer top-level [`Caixa`] required-`&str` universal-axis
14108 // pinned-version composition axis, closing the second
14109 // coordinate of the "one canonical typed dispatch per per-Caixa
14110 // required-`&str` universal-axis" discipline.
14111 let c = caixa_with_versao("");
14112 assert!(
14113 matches!(c.validate_versao(), Err(ManifestError::VersaoEmpty)),
14114 "validate_versao must reject versao == \"\" with VersaoEmpty — \
14115 the accessor and the validate gate must route through the \
14116 same substrate-primitive typed dispatch on the :versao \
14117 empty-arm",
14118 );
14119 let c = caixa_with_versao("0.1.0");
14120 assert!(
14121 c.validate_versao().is_ok(),
14122 "validate_versao must accept versao == \"0.1.0\" (the \
14123 canonical SemVer-2 template baseline)",
14124 );
14125 }
14126
14127 #[test]
14128 fn versao_projects_str_by_borrow() {
14129 // The by-borrow pin: [`Caixa::versao`] returns `&str` by borrow
14130 // — the `&str` borrows the underlying `String` storage of the
14131 // required `versao` slot and the accessor must not allocate a
14132 // fresh `String` on every call. Peer of the [`Caixa::nome`]
14133 // (e6b7d97) by-borrow pin on the sibling outer top-level
14134 // [`Caixa`] required-`&str`-return axis, extended onto the
14135 // second outer top-level [`Caixa`] required-`&str`-return
14136 // universal-axis pinned-version surface — the accessor's
14137 // returned `&str` must borrow from `&self` (the returned
14138 // reference's lifetime is tied to `&self`), and calling the
14139 // accessor twice on the same [`Caixa`] must yield the same
14140 // `&str` verbatim (idempotent, no side effects on `&self`).
14141 //
14142 // Pins against a future silent detour that returned an owned
14143 // `String` (which would type-check but silently allocate on
14144 // every call, breaking the zero-cost projection every peer
14145 // sibling accessor carries), an accidental
14146 // `semver::Version::parse(&self.versao).unwrap().to_string()`
14147 // detour that returned a canonicalized fresh allocation through
14148 // an already-canonical byte-string (breaking a future `const fn`
14149 // regression and silently absorbing the `VersaoInvalid` refusal
14150 // at the accessor boundary), or a one-arm-only accessor that
14151 // returned a canonicalized value on some sentinel input
14152 // (breaking the pass-through invariant the sibling required-
14153 // scalar accessors carry).
14154 for versao in ["0.1.0", "1.0.0", "0.2.0-rc.1", "1.0.0+build.42"] {
14155 let c = caixa_with_versao(versao);
14156 let first = c.versao();
14157 let second = c.versao();
14158 assert_eq!(
14159 first, second,
14160 "Caixa::versao must be idempotent — two successive \
14161 calls on the same &self must return the same &str",
14162 );
14163 assert_eq!(
14164 first, versao,
14165 "Caixa::versao must return :versao verbatim by borrow \
14166 — got {first}, expected {versao}",
14167 );
14168 }
14169 }
14170
14171 fn caixa_with_kind(kind: CaixaKind) -> Caixa {
14172 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14173 c.kind = kind;
14174 c
14175 }
14176
14177 #[test]
14178 fn kind_returns_kind_variant_verbatim_across_permutations() {
14179 // The canonical per-`Caixa` `:kind` universal-axis closed-set-
14180 // enum discriminant pin: [`Caixa::kind`] must return the `:kind`
14181 // typed [`CaixaKind`] variant verbatim by `Copy`, byte-equal to
14182 // the raw `.kind` field access across every variant in the
14183 // closed accept-set (`Biblioteca` — the library kind that
14184 // exports lisp forms; `Binario` — the nix-built executable kind
14185 // under `exe/`; `Servico` — the wasm-component daemon kind
14186 // under `servicos/`; `Supervisor` — the OTP-shaped hierarchical
14187 // reconciliation kind; `Aplicacao` — the M3 typed-mesh
14188 // composition kind).
14189 //
14190 // Pins against a future silent detour that re-derived the kind
14191 // from a peer axis (an accidental fallback to
14192 // `if !servicos.is_empty() { Servico } else if
14193 // !membros.is_empty() { Aplicacao } else { Biblioteca }`
14194 // collapse that read the code-surface / mesh-slot columns into
14195 // the kind discriminator), a variant remap the operator
14196 // authors on one consumer without the other, or a stale-derive
14197 // detour that substituted [`CaixaKind::Biblioteca`] as the
14198 // default when the field held any other variant (which would
14199 // silently collapse the distinction between "author explicitly
14200 // declared `:kind Servico`" and "author declared any other
14201 // kind" every downstream renderer-dispatch site depends on).
14202 //
14203 // First outer top-level [`Caixa`] `Copy`-return required-enum-
14204 // discriminant accessor pin — opens the "outer [`Caixa`]
14205 // `Copy`-return required-discriminant" projection pattern.
14206 // Sibling in shape to the peer per-`:supervisor`
14207 // [`crate::supervisor::SupervisorSpec::estrategia`] (eafb619),
14208 // per-`:placement` [`crate::aplicacao::Placement::estrategia`]
14209 // (921fe1b), and per-`:children`
14210 // [`crate::supervisor::ChildSpec::restart`] (dfb4a81)
14211 // `Copy`-return closed-set-enum discriminant accessor pins on
14212 // the sibling nested-spec typed-slot discriminator axes,
14213 // extended here to the outer top-level [`Caixa`] universal-
14214 // axis surface.
14215 for kind in [
14216 CaixaKind::Biblioteca,
14217 CaixaKind::Binario,
14218 CaixaKind::Servico,
14219 CaixaKind::Supervisor,
14220 CaixaKind::Aplicacao,
14221 ] {
14222 let c = caixa_with_kind(kind);
14223 assert_eq!(
14224 c.kind(),
14225 kind,
14226 "Caixa::kind must return :kind verbatim (got {:?}, \
14227 expected {kind:?})",
14228 c.kind(),
14229 );
14230 assert_eq!(
14231 c.kind(),
14232 c.kind,
14233 "Caixa::kind accessor and .kind field access must \
14234 byte-equal — the accessor is the substrate-primitive \
14235 typed dispatch every downstream kind-gate consumer \
14236 must route through",
14237 );
14238 }
14239 }
14240
14241 #[test]
14242 fn require_kind_reads_through_lifted_kind_accessor() {
14243 // Two-consumer coherence pin: the [`crate::render::require_kind`]
14244 // entry-gate predicate (the canonical two-line
14245 // `require_kind(caixa, Servico)?` prelude every per-Servico /
14246 // per-Aplicacao renderer runs at its entry-point) and the
14247 // sibling [`crate::render::KindMismatch`] error carrier's
14248 // `actual:` field (which names the offending caixa's variant
14249 // in the diagnostic) must both key off the lifted accessor, so
14250 // any future rebrand on the typed slot's reader shape lands at
14251 // exactly one place. Pins the two-site coherence by exercising
14252 // every off-diagonal `(actual, expected)` pair across the
14253 // closed accept-set — the `KindMismatch { actual, expected }`
14254 // surfaced on the mismatch arm must byte-equal the pair the
14255 // accessor returns for each side.
14256 //
14257 // Peer of the sibling per-`:placement`
14258 // `validate_placement_reads_through_lifted_estrategia_accessor`
14259 // (921fe1b) two-arm consumer-coherence pin on the M3 mesh-slot
14260 // `Copy`-return discriminant axis — same "the entry-gate
14261 // predicate and the error carrier's `actual:` field must route
14262 // through the substrate-primitive typed dispatch" discipline
14263 // extended onto the outer top-level [`Caixa`] universal-axis
14264 // discriminant surface.
14265 for expected in [
14266 CaixaKind::Biblioteca,
14267 CaixaKind::Binario,
14268 CaixaKind::Servico,
14269 CaixaKind::Supervisor,
14270 CaixaKind::Aplicacao,
14271 ] {
14272 for actual in [
14273 CaixaKind::Biblioteca,
14274 CaixaKind::Binario,
14275 CaixaKind::Servico,
14276 CaixaKind::Supervisor,
14277 CaixaKind::Aplicacao,
14278 ] {
14279 let c = caixa_with_kind(actual);
14280 let result = crate::render::require_kind(&c, expected);
14281 if expected == actual {
14282 assert!(
14283 result.is_ok(),
14284 "require_kind must accept when actual == expected \
14285 (actual={actual:?}, expected={expected:?})",
14286 );
14287 } else {
14288 let err = result.expect_err("require_kind must reject when actual != expected");
14289 assert_eq!(
14290 err.actual,
14291 c.kind(),
14292 "KindMismatch.actual must byte-equal Caixa::kind() \
14293 — the error carrier's `actual:` field reads \
14294 through the lifted accessor",
14295 );
14296 assert_eq!(
14297 err.expected, expected,
14298 "KindMismatch.expected must byte-equal the \
14299 expected variant passed to require_kind",
14300 );
14301 }
14302 }
14303 }
14304 }
14305
14306 #[test]
14307 fn aplicacao_view_kind_gate_routes_through_accessor() {
14308 // Composition pin: [`Caixa::aplicacao_view`]'s kind-gate arm
14309 // must key off [`Caixa::kind`], not the raw `.kind` field
14310 // access. Structurally: a `Caixa { kind: X, .. }` for any
14311 // non-`Aplicacao` variant must fold to `None` on the
14312 // `aplicacao_view` composer (the "kind mismatch → no typed
14313 // view" contract every downstream Aplicacao consumer keys off
14314 // via `?`), and a `Caixa { kind: Aplicacao, .. }` must fold to
14315 // `Some(_)`. The pair jointly pins the accessor + view-gate
14316 // composition: any future silent detour that had the accessor
14317 // return a fresh [`CaixaKind::Aplicacao`] on some sentinel
14318 // input would silently absorb the kind-mismatch case at the
14319 // accessor boundary and every per-Aplicacao renderer would
14320 // silently render a non-Aplicacao caixa's mesh slots — the
14321 // composition pin catches that at caixa-core build time.
14322 //
14323 // Peer of the sibling per-`Caixa`
14324 // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97) /
14325 // `validate_versao_empty_arm_routes_through_accessor` (20c0539)
14326 // composition pins on the sibling outer top-level [`Caixa`]
14327 // required-`&str` universal-axis surfaces — same "the
14328 // composer / validate gate must route through the substrate-
14329 // primitive typed dispatch" discipline extended onto the
14330 // outer top-level [`Caixa`] `Copy`-return required-
14331 // discriminant composition axis.
14332 for kind in [
14333 CaixaKind::Biblioteca,
14334 CaixaKind::Binario,
14335 CaixaKind::Servico,
14336 CaixaKind::Supervisor,
14337 ] {
14338 let c = caixa_with_kind(kind);
14339 assert!(
14340 c.aplicacao_view().is_none(),
14341 "aplicacao_view must return None on non-Aplicacao \
14342 kind {kind:?} — the composer's kind-gate must route \
14343 through Caixa::kind()",
14344 );
14345 }
14346 let c = caixa_with_kind(CaixaKind::Aplicacao);
14347 assert!(
14348 c.aplicacao_view().is_some(),
14349 "aplicacao_view must return Some on kind Aplicacao — \
14350 the composer's kind-gate must accept the matching arm \
14351 through Caixa::kind()",
14352 );
14353 }
14354
14355 #[test]
14356 fn supervisor_view_kind_gate_routes_through_accessor() {
14357 // Composition pin (mirror of the sibling
14358 // `aplicacao_view_kind_gate_routes_through_accessor` on the
14359 // second `_view` composer): [`Caixa::supervisor_view`]'s kind-
14360 // gate arm must key off [`Caixa::kind`], not the raw `.kind`
14361 // field access. A `Caixa { kind: X, .. }` for any non-
14362 // `Supervisor` variant must fold to `None` on the
14363 // `supervisor_view` composer, and a `Caixa { kind:
14364 // Supervisor, .. }` must fold to `Some(_)`. Same peer
14365 // composition pin discipline on the second `_view` composer
14366 // axis.
14367 for kind in [
14368 CaixaKind::Biblioteca,
14369 CaixaKind::Binario,
14370 CaixaKind::Servico,
14371 CaixaKind::Aplicacao,
14372 ] {
14373 let c = caixa_with_kind(kind);
14374 assert!(
14375 c.supervisor_view().is_none(),
14376 "supervisor_view must return None on non-Supervisor \
14377 kind {kind:?} — the composer's kind-gate must route \
14378 through Caixa::kind()",
14379 );
14380 }
14381 let mut c = caixa_with_kind(CaixaKind::Supervisor);
14382 // A Supervisor caixa needs a strategy + at least one child to
14383 // fold to a Some(_) that also validates; the composer itself
14384 // requires only the kind arm, so bare kind flip is enough to
14385 // pin the `Some(_)` return, but we populate the minimum
14386 // supervisor shape so a future strengthening of the composer
14387 // to reject an empty spec doesn't false-positive this pin.
14388 c.estrategia = Some(crate::supervisor::RestartStrategy::OneForOne);
14389 c.children = vec![crate::supervisor::ChildSpec {
14390 caixa: "child".into(),
14391 versao: "^0.1".into(),
14392 restart: crate::supervisor::RestartPolicy::Permanent,
14393 }];
14394 assert!(
14395 c.supervisor_view().is_some(),
14396 "supervisor_view must return Some on kind Supervisor — \
14397 the composer's kind-gate must accept the matching arm \
14398 through Caixa::kind()",
14399 );
14400 }
14401
14402 #[test]
14403 fn kind_projects_by_copy() {
14404 // The by-`Copy` pin: [`Caixa::kind`] returns a fresh
14405 // [`CaixaKind`] by `Copy` — the accessor must not borrow from
14406 // `&self` (the returned value is owned, `Copy`-projected from
14407 // the underlying [`CaixaKind`] storage; two calls on the same
14408 // [`Caixa`] must yield byte-equal values). Peer of the peer
14409 // per-`:placement` `Placement::estrategia` / per-`:supervisor`
14410 // `SupervisorSpec::estrategia` / per-`:children`
14411 // `ChildSpec::restart` `Copy`-return discriminant accessor
14412 // pins on the sibling nested-spec typed-slot discriminator
14413 // axes, extended onto the first outer top-level [`Caixa`]
14414 // required-`Copy`-return axis — pins against a future silent
14415 // detour that returned `&CaixaKind` (which would type-check
14416 // but silently constrain every consumer's callsite to a
14417 // borrow-shaped dispatch, breaking the zero-cost `Copy`
14418 // projection every peer sibling accessor carries).
14419 for kind in [
14420 CaixaKind::Biblioteca,
14421 CaixaKind::Binario,
14422 CaixaKind::Servico,
14423 CaixaKind::Supervisor,
14424 CaixaKind::Aplicacao,
14425 ] {
14426 let c = caixa_with_kind(kind);
14427 let first: CaixaKind = c.kind();
14428 let second: CaixaKind = c.kind();
14429 assert_eq!(
14430 first, second,
14431 "Caixa::kind must be idempotent — two successive \
14432 calls on the same &self must return the same \
14433 CaixaKind variant",
14434 );
14435 assert_eq!(
14436 first, kind,
14437 "Caixa::kind must return :kind verbatim by Copy — \
14438 got {first:?}, expected {kind:?}",
14439 );
14440 }
14441 }
14442
14443 // ── Caixa::autores — outer top-level &[T] slice accessor ──────────
14444
14445 #[test]
14446 fn autores_returns_autores_slice_verbatim_across_permutations() {
14447 // The canonical per-`Caixa` `:autores` universal-axis maintainer-
14448 // name-list slice pin: [`Caixa::autores`] must return the
14449 // `:autores` typed [`Vec<String>`] list verbatim as a
14450 // `&[String]`, byte-equal to the raw `self.autores.as_slice()`
14451 // access across every representative value in the accept-set —
14452 // `[]` (the "no maintainers declared" arm every existing
14453 // fixture without an `:autores` line carries), `[""]` (a past-
14454 // the-guard sentinel that pins the accessor doesn't perform a
14455 // silent `[""] → []` collapse on the empty-entry arm — validate
14456 // rejects `[""]` through `AutorEmpty` but the accessor must
14457 // ship the raw slot verbatim so a validate-time gate regression
14458 // surfaces at the caixa-helm emit boundary rather than being
14459 // silently absorbed into a maintainer-drop), `["pleme-io"]` (the
14460 // canonical single-maintainer form every `feira init` template
14461 // scaffolds), `["alice", "bob"]` (a canonical multi-maintainer
14462 // form), `["alice <alice@example.com>", "bob <bob@example.com>"]`
14463 // (the canonical RFC-5322 `<name> <email>` form the
14464 // `is_chart_maintainer_name_shape` predicate accepts), and
14465 // `["pleme-io", "pleme-io"]` (a past-the-guard duplicate
14466 // sentinel — validate rejects through `AutorDuplicate` but the
14467 // accessor must ship the raw slot verbatim).
14468 //
14469 // First outer top-level [`Caixa`] `&[T]`-return slice accessor
14470 // pin on the substrate primitive — opens the "outer [`Caixa`]
14471 // `&[T]` slice" projection pattern the sibling per-`Caixa`
14472 // `:etiquetas` / `:deps` / `:deps-dev` / `:exe` / `:bibliotecas`
14473 // / `:servicos` / `:upgrade-from` / `:children` future lifts
14474 // fold on. Sibling in shape to the peer per-`:supervisor`
14475 // [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
14476 // per-`:placement` [`crate::aplicacao::Placement::clusters`]
14477 // (a6e18d7), per-`:membros`
14478 // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
14479 // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
14480 // (0dcc926), and per-`:upgrade-from :instructions`
14481 // [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
14482 // `&[T]`-return slice accessor pins on the sibling per-M2 /
14483 // per-M3 typed-slot list axes, extended onto the outer top-
14484 // level [`Caixa`] universal-axis surface. Pins against a future
14485 // silent detour that returned an owned `Vec<String>` (which
14486 // would type-check but silently clone on every accessor call,
14487 // breaking the zero-cost projection every peer sibling slice
14488 // accessor carries), a `[""] → []` collapse (which would
14489 // silently absorb the `AutorEmpty` refusal case at the accessor
14490 // boundary), or a `["a", "a"] → ["a"]` dedup collapse (which
14491 // would silently absorb the `AutorDuplicate` refusal case at
14492 // the accessor boundary and the caixa-helm `maintainers:` fold
14493 // would silently render a dedupped list on a struct-literal
14494 // `Caixa { autores: vec!["a".into(), "a".into()], .. }`).
14495 for autores in [
14496 vec![],
14497 vec![""],
14498 vec!["pleme-io"],
14499 vec!["alice", "bob"],
14500 vec!["alice <alice@example.com>", "bob <bob@example.com>"],
14501 vec!["pleme-io", "pleme-io"],
14502 ] {
14503 let c = caixa_with_autores(autores.clone());
14504 let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
14505 assert_eq!(
14506 c.autores(),
14507 expected.as_slice(),
14508 "Caixa::autores must return :autores verbatim (got {:?}, \
14509 expected {expected:?})",
14510 c.autores(),
14511 );
14512 assert_eq!(
14513 c.autores(),
14514 c.autores.as_slice(),
14515 "Caixa::autores must byte-equal the raw \
14516 `self.autores.as_slice()` field access across every \
14517 value in the Vec<String> accept-set",
14518 );
14519 }
14520 }
14521
14522 #[test]
14523 fn validate_autores_empty_entry_arm_routes_through_accessor() {
14524 // Composition pin: [`Caixa::validate_autores`]'s per-entry
14525 // empty-arm gate must key off [`Caixa::autores`], not the raw
14526 // `&self.autores` field-borrow walk. Structurally: a
14527 // `Caixa { autores: vec!["".into()], .. }` must surface the
14528 // `AutorEmpty` refusal exactly, and a
14529 // `Caixa { autores: vec!["pleme-io".into()], .. }` (the
14530 // canonical single-maintainer form) must pass validate. The
14531 // pair jointly pins the accessor + validate-gate composition:
14532 // any future silent detour that had the accessor return an
14533 // empty slice on the `[""]` arm (a
14534 // `.iter().filter(|s| !s.is_empty()).collect()` collapse)
14535 // would silently absorb the `AutorEmpty` refusal at the
14536 // accessor boundary and the validate gate would accept a
14537 // struct-literal `Caixa { autores: vec!["".into()], .. }` —
14538 // the composition pin catches that at caixa-core build time.
14539 //
14540 // Peer of the per-`Caixa` [`Caixa::validate_licenca`] (6d5bc28)
14541 // accessor-composition pin
14542 // (`validate_licenca_empty_arm_routes_through_accessor`) on the
14543 // sibling `Option<&str>`-composition axis and the
14544 // per-`:politicas :circuit-breaker`
14545 // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
14546 // accessor-composition pin
14547 // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
14548 // on the sibling required-`u32`-composition axis — same "the
14549 // validate / shape-gate predicate must route through the
14550 // substrate-primitive typed dispatch" discipline extended onto
14551 // the outer top-level [`Caixa`] universal-axis `&[T]`-
14552 // composition surface.
14553 let c = caixa_with_autores(vec![""]);
14554 assert!(
14555 matches!(c.validate_autores(), Err(ManifestError::AutorEmpty)),
14556 "validate_autores must reject autores == vec![\"\"] with \
14557 AutorEmpty — the accessor and the validate gate must \
14558 route through the same substrate-primitive typed dispatch \
14559 on the :autores per-entry empty arm",
14560 );
14561 let c = caixa_with_autores(vec!["pleme-io"]);
14562 assert!(
14563 c.validate_autores().is_ok(),
14564 "validate_autores must accept autores == vec![\"pleme-io\"] \
14565 (the canonical single-maintainer shape every `feira init` \
14566 template scaffolds)",
14567 );
14568 }
14569
14570 #[test]
14571 fn autores_projects_slice_by_borrow() {
14572 // The by-borrow pin: [`Caixa::autores`] returns `&[String]` by
14573 // borrow — the returned slice borrows the underlying
14574 // `Vec<String>` storage of the `:autores` slot and the
14575 // accessor must not clone the backing `Vec` on every call.
14576 // Peer of the per-`:membros`
14577 // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36) /
14578 // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
14579 // (0dcc926) / per-`:placement`
14580 // [`crate::aplicacao::Placement::clusters`] (a6e18d7) /
14581 // per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
14582 // (bc92bce) by-borrow pins on the sibling per-M2 / per-M3
14583 // typed-slot `&[T]`-return axes, extended onto the outer top-
14584 // level [`Caixa`] universal-axis `&[String]` shape — the
14585 // accessor's returned slice must borrow from `&self` (the
14586 // returned reference's lifetime is tied to `&self`), and
14587 // calling the accessor twice on the same [`Caixa`] must yield
14588 // slices that are pointer-equal (the underlying byte-buffer is
14589 // the storage `Vec`'s allocation, not a fresh copy) as well as
14590 // value-equal (idempotent, no side effects on `&self`).
14591 //
14592 // Pins against a future silent detour that returned an owned
14593 // `Vec<String>` (which would type-check but silently clone on
14594 // every call, breaking the zero-cost projection every peer
14595 // sibling slice accessor carries), a `&Vec<String>` return
14596 // (which would leak the backing `Vec`'s grow/push/reserve
14597 // surface no downstream consumer reaches for), or a one-arm-
14598 // only accessor that returned a saturating value on some
14599 // sentinel input (breaking the pass-through invariant the
14600 // sibling slice accessors carry).
14601 for autores in [
14602 vec![],
14603 vec!["pleme-io"],
14604 vec!["alice", "bob"],
14605 vec!["pleme-io", "pleme-io"],
14606 ] {
14607 let c = caixa_with_autores(autores.clone());
14608 let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
14609 let first = c.autores();
14610 let second = c.autores();
14611 assert_eq!(
14612 first, second,
14613 "Caixa::autores must be idempotent — two successive \
14614 calls on the same &self must return the same \
14615 &[String]",
14616 );
14617 assert_eq!(
14618 first.as_ptr(),
14619 second.as_ptr(),
14620 "Caixa::autores must borrow the underlying Vec<String> \
14621 storage — two successive calls must return slices \
14622 with the same backing pointer (a fresh Vec<String> \
14623 clone would change the pointer on every call)",
14624 );
14625 assert_eq!(
14626 first,
14627 expected.as_slice(),
14628 "Caixa::autores must return :autores verbatim by \
14629 borrow — got {first:?}, expected {expected:?}",
14630 );
14631 }
14632 }
14633
14634 // ── Caixa::etiquetas — outer top-level &[T] slice accessor ────────
14635
14636 #[test]
14637 fn etiquetas_returns_etiquetas_slice_verbatim_across_permutations() {
14638 // The canonical per-`Caixa` `:etiquetas` universal-axis
14639 // registry-search-tag-list slice pin: [`Caixa::etiquetas`] must
14640 // return the `:etiquetas` typed [`Vec<String>`] list verbatim
14641 // as a `&[String]`, byte-equal to the raw
14642 // `self.etiquetas.as_slice()` access across every representative
14643 // value in the accept-set — `[]` (the "no tags declared" arm
14644 // every existing fixture without an `:etiquetas` line carries),
14645 // `[""]` (a past-the-guard sentinel that pins the accessor
14646 // doesn't perform a silent `[""] → []` collapse on the empty-
14647 // entry arm — validate rejects `[""]` through `EtiquetaEmpty`
14648 // but the accessor must ship the raw slot verbatim so a
14649 // validate-time gate regression surfaces at the caixa-helm emit
14650 // boundary rather than being silently absorbed into a keyword-
14651 // drop), `["demo"]` (the canonical single-tag form every
14652 // `feira init` template scaffolds), `["example", "aplicacao",
14653 // "mesh", "ecommerce", "demo"]` (the canonical multi-tag form
14654 // the checkout-aplicacao fixture emits), and `["demo", "demo"]`
14655 // (a past-the-guard duplicate sentinel — validate rejects
14656 // through `EtiquetaDuplicate` but the accessor must ship the
14657 // raw slot verbatim so the caixa-helm `BTreeSet::collect` dedup
14658 // at chart-render time isn't silently promoted into the
14659 // accessor boundary and struct-literal
14660 // `Caixa { etiquetas: vec!["demo".into(), "demo".into()], .. }`
14661 // fixtures continue to expose the duplicate at the accessor).
14662 //
14663 // Second outer top-level [`Caixa`] `&[T]`-return slice accessor
14664 // pin on the substrate primitive — folds on the "outer
14665 // [`Caixa`] `&[T]` slice" projection pattern
14666 // `autores_returns_autores_slice_verbatim_across_permutations`
14667 // (b5d813f) opened, sibling in shape and idiom. Pins against a
14668 // future silent detour that returned an owned `Vec<String>`
14669 // (which would type-check but silently clone on every accessor
14670 // call, breaking the zero-cost projection every peer sibling
14671 // slice accessor carries), a `[""] → []` collapse (which would
14672 // silently absorb the `EtiquetaEmpty` refusal case at the
14673 // accessor boundary), or a `["a", "a"] → ["a"]` dedup collapse
14674 // (which would silently absorb the `EtiquetaDuplicate` refusal
14675 // case at the accessor boundary — the caixa-helm chart-render
14676 // `BTreeSet::collect` dedup is downstream of the accessor and
14677 // must not be silently promoted into it).
14678 for etiquetas in [
14679 vec![],
14680 vec![""],
14681 vec!["demo"],
14682 vec!["example", "aplicacao", "mesh", "ecommerce", "demo"],
14683 vec!["demo", "demo"],
14684 ] {
14685 let c = caixa_with_etiquetas(etiquetas.clone());
14686 let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
14687 assert_eq!(
14688 c.etiquetas(),
14689 expected.as_slice(),
14690 "Caixa::etiquetas must return :etiquetas verbatim (got \
14691 {:?}, expected {expected:?})",
14692 c.etiquetas(),
14693 );
14694 assert_eq!(
14695 c.etiquetas(),
14696 c.etiquetas.as_slice(),
14697 "Caixa::etiquetas must byte-equal the raw \
14698 `self.etiquetas.as_slice()` field access across every \
14699 value in the Vec<String> accept-set",
14700 );
14701 }
14702 }
14703
14704 #[test]
14705 fn validate_etiquetas_empty_entry_arm_routes_through_accessor() {
14706 // Composition pin: [`Caixa::validate_etiquetas`]'s per-entry
14707 // empty-arm gate must key off [`Caixa::etiquetas`], not the raw
14708 // `&self.etiquetas` field-borrow walk. Structurally: a
14709 // `Caixa { etiquetas: vec!["".into()], .. }` must surface the
14710 // `EtiquetaEmpty` refusal exactly, and a
14711 // `Caixa { etiquetas: vec!["demo".into()], .. }` (the canonical
14712 // single-tag form) must pass validate. The pair jointly pins
14713 // the accessor + validate-gate composition: any future silent
14714 // detour that had the accessor return an empty slice on the
14715 // `[""]` arm (a
14716 // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
14717 // silently absorb the `EtiquetaEmpty` refusal at the accessor
14718 // boundary and the validate gate would accept a struct-literal
14719 // `Caixa { etiquetas: vec!["".into()], .. }` — the composition
14720 // pin catches that at caixa-core build time.
14721 //
14722 // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
14723 // through_accessor` (b5d813f) accessor-composition pin on the
14724 // sibling `&[T]`-composition axis — same "the validate / shape-
14725 // gate predicate must route through the substrate-primitive
14726 // typed dispatch" discipline extended onto the sibling outer
14727 // top-level [`Caixa`] `&[T]`-composition surface.
14728 let c = caixa_with_etiquetas(vec![""]);
14729 assert!(
14730 matches!(c.validate_etiquetas(), Err(ManifestError::EtiquetaEmpty)),
14731 "validate_etiquetas must reject etiquetas == vec![\"\"] \
14732 with EtiquetaEmpty — the accessor and the validate gate \
14733 must route through the same substrate-primitive typed \
14734 dispatch on the :etiquetas per-entry empty arm",
14735 );
14736 let c = caixa_with_etiquetas(vec!["demo"]);
14737 assert!(
14738 c.validate_etiquetas().is_ok(),
14739 "validate_etiquetas must accept etiquetas == vec![\"demo\"] \
14740 (the canonical single-tag shape every `feira init` \
14741 template scaffolds)",
14742 );
14743 }
14744
14745 #[test]
14746 fn etiquetas_projects_slice_by_borrow() {
14747 // The by-borrow pin: [`Caixa::etiquetas`] returns `&[String]`
14748 // by borrow — the returned slice borrows the underlying
14749 // `Vec<String>` storage of the `:etiquetas` slot and the
14750 // accessor must not clone the backing `Vec` on every call.
14751 // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
14752 // (b5d813f) by-borrow pin on the sibling outer top-level
14753 // [`Caixa`] `&[String]`-return axis — the accessor's returned
14754 // slice must borrow from `&self` (the returned reference's
14755 // lifetime is tied to `&self`), and calling the accessor twice
14756 // on the same [`Caixa`] must yield slices that are pointer-
14757 // equal (the underlying byte-buffer is the storage `Vec`'s
14758 // allocation, not a fresh copy) as well as value-equal
14759 // (idempotent, no side effects on `&self`).
14760 //
14761 // Pins against a future silent detour that returned an owned
14762 // `Vec<String>` (which would type-check but silently clone on
14763 // every call, breaking the zero-cost projection every peer
14764 // sibling slice accessor carries), a `&Vec<String>` return
14765 // (which would leak the backing `Vec`'s grow/push/reserve
14766 // surface no downstream consumer reaches for), or a one-arm-
14767 // only accessor that returned a saturating value on some
14768 // sentinel input (breaking the pass-through invariant the
14769 // sibling slice accessors carry).
14770 for etiquetas in [
14771 vec![],
14772 vec!["demo"],
14773 vec!["example", "aplicacao", "mesh"],
14774 vec!["demo", "demo"],
14775 ] {
14776 let c = caixa_with_etiquetas(etiquetas.clone());
14777 let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
14778 let first = c.etiquetas();
14779 let second = c.etiquetas();
14780 assert_eq!(
14781 first, second,
14782 "Caixa::etiquetas must be idempotent — two successive \
14783 calls on the same &self must return the same \
14784 &[String]",
14785 );
14786 assert_eq!(
14787 first.as_ptr(),
14788 second.as_ptr(),
14789 "Caixa::etiquetas must borrow the underlying \
14790 Vec<String> storage — two successive calls must \
14791 return slices with the same backing pointer (a fresh \
14792 Vec<String> clone would change the pointer on every \
14793 call)",
14794 );
14795 assert_eq!(
14796 first,
14797 expected.as_slice(),
14798 "Caixa::etiquetas must return :etiquetas verbatim by \
14799 borrow — got {first:?}, expected {expected:?}",
14800 );
14801 }
14802 }
14803
14804 // ── Caixa::bibliotecas — outer top-level &[T] slice accessor ──────
14805
14806 #[test]
14807 fn bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations() {
14808 // The canonical per-`Caixa` `:bibliotecas` universal-axis
14809 // library-source-path-list slice pin: [`Caixa::bibliotecas`]
14810 // must return the `:bibliotecas` typed [`Vec<String>`] list
14811 // verbatim as a `&[String]`, byte-equal to the raw
14812 // `self.bibliotecas.as_slice()` access across every
14813 // representative value in the accept-set — `[]` (the "no
14814 // libraries declared" arm every `:kind` other than `Biblioteca`
14815 // + every `Biblioteca` relying on the canonical
14816 // `lib/<nome>.lisp` implicit-default path carries; the
14817 // layout's [`crate::LayoutInvariants`] `MissingLib` arm-gate
14818 // fires exactly on this empty-slot + `Biblioteca`-kind
14819 // combination), `[""]` (a past-the-guard sentinel that pins
14820 // the accessor doesn't perform a silent `[""] → []` collapse
14821 // on the empty-entry arm — validate rejects `[""]` through
14822 // `CodePathEmpty { slot: ":bibliotecas" }` but the accessor
14823 // must ship the raw slot verbatim so a validate-time gate
14824 // regression surfaces at the `feira build` phase-1 parse
14825 // boundary rather than being silently absorbed into a
14826 // library-drop), `["lib/demo.lisp"]` (the canonical single-
14827 // entry form `Caixa::template` scaffolds and every `feira init`
14828 // template emits), `["lib/demo.lisp", "lib/helpers.lisp"]`
14829 // (the canonical multi-library form the
14830 // `validate_code_paths_accepts_explicit_relative_paths_on_
14831 // every_slot` fixture emits), and `["lib/foo.lisp",
14832 // "lib/foo.lisp"]` (a past-the-guard duplicate sentinel —
14833 // validate rejects through `CodePathDuplicate { slot:
14834 // ":bibliotecas" }` per the per-slot set-not-multiset gate,
14835 // but the accessor must ship the raw slot verbatim so the
14836 // `feira build` `for entry in caixa.bibliotecas()` parse walk
14837 // sees the duplicate at the accessor boundary and struct-
14838 // literal `Caixa { bibliotecas: vec!["lib/foo.lisp".into(),
14839 // "lib/foo.lisp".into()], .. }` fixtures continue to expose
14840 // the duplicate at the accessor).
14841 //
14842 // Third outer top-level [`Caixa`] `&[T]`-return slice accessor
14843 // pin on the substrate primitive — folds on the "outer
14844 // [`Caixa`] `&[T]` slice" projection pattern
14845 // `autores_returns_autores_slice_verbatim_across_permutations`
14846 // (b5d813f) opened and
14847 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
14848 // (78c7d3c) folded on, sibling in shape and idiom. Pins
14849 // against a future silent detour that returned an owned
14850 // `Vec<String>` (which would type-check but silently clone on
14851 // every accessor call, breaking the zero-cost projection
14852 // every peer sibling slice accessor carries), a `[""] → []`
14853 // collapse (which would silently absorb the `CodePathEmpty`
14854 // refusal case at the accessor boundary), or a `["lib/foo.lisp",
14855 // "lib/foo.lisp"] → ["lib/foo.lisp"]` dedup collapse (which
14856 // would silently absorb the `CodePathDuplicate` refusal case
14857 // at the accessor boundary — the per-slot set-not-multiset
14858 // gate is downstream of the accessor and must not be silently
14859 // promoted into it).
14860 for bibliotecas in [
14861 vec![],
14862 vec![""],
14863 vec!["lib/demo.lisp"],
14864 vec!["lib/demo.lisp", "lib/helpers.lisp"],
14865 vec!["lib/foo.lisp", "lib/foo.lisp"],
14866 ] {
14867 let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
14868 let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
14869 assert_eq!(
14870 c.bibliotecas(),
14871 expected.as_slice(),
14872 "Caixa::bibliotecas must return :bibliotecas verbatim \
14873 (got {:?}, expected {expected:?})",
14874 c.bibliotecas(),
14875 );
14876 assert_eq!(
14877 c.bibliotecas(),
14878 c.bibliotecas.as_slice(),
14879 "Caixa::bibliotecas must byte-equal the raw \
14880 `self.bibliotecas.as_slice()` field access across \
14881 every value in the Vec<String> accept-set",
14882 );
14883 }
14884 }
14885
14886 #[test]
14887 fn validate_code_paths_bibliotecas_empty_arm_routes_through_accessor() {
14888 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
14889 // empty-arm gate on the `:bibliotecas` slot must key off
14890 // [`Caixa::bibliotecas`], not a divergent raw
14891 // `&self.bibliotecas` field-borrow walk. Structurally: a
14892 // `Caixa { bibliotecas: vec!["".into()], .. }` must surface
14893 // the `CodePathEmpty { slot: ":bibliotecas" }` refusal
14894 // exactly, and a `Caixa { bibliotecas: vec!["lib/demo.lisp".
14895 // into()], .. }` (the canonical single-library form
14896 // `Caixa::template` scaffolds) must pass validate. The pair
14897 // jointly pins the accessor + validate-gate composition: any
14898 // future silent detour that had the accessor return an empty
14899 // slice on the `[""]` arm (a `.iter().filter(|s|
14900 // !s.is_empty()).collect()` collapse) would silently absorb
14901 // the `CodePathEmpty` refusal at the accessor boundary and
14902 // the validate gate would accept a struct-literal
14903 // `Caixa { bibliotecas: vec!["".into()], .. }` — the
14904 // composition pin catches that at caixa-core build time.
14905 //
14906 // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
14907 // through_accessor` (b5d813f) and
14908 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
14909 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
14910 // composition axes — same "the validate / shape-gate
14911 // predicate must route through the substrate-primitive typed
14912 // dispatch" discipline extended onto the sibling outer top-
14913 // level [`Caixa`] `&[T]`-composition surface. Nominally the
14914 // in-tree `validate_code_paths` production body still keys
14915 // off the internal `[(":bibliotecas", &self.bibliotecas,
14916 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
14917 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
14918 // (the tuple's homogeneous slice-typed shape blocks a per-
14919 // element accessor swap in isolation — a future companion
14920 // lift for `:exe` and `:servicos` on the same outer-`Caixa`
14921 // `&[T]` slice-accessor axis closes that tuple onto the
14922 // triple of typed dispatches as a unit); the composition pin
14923 // catches any future accessor-side silent filter drop against
14924 // that eventual tuple-closure regardless of whether the
14925 // `:bibliotecas` slot is threaded through the accessor or the
14926 // raw field access at the tuple's construction site.
14927 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
14928 assert!(
14929 matches!(
14930 c.validate_code_paths(),
14931 Err(ManifestError::CodePathEmpty {
14932 slot: ":bibliotecas"
14933 })
14934 ),
14935 "validate_code_paths must reject bibliotecas == vec![\"\"] \
14936 with CodePathEmpty {{ slot: \":bibliotecas\" }} — the \
14937 accessor and the validate gate must route through the \
14938 same substrate-primitive typed dispatch on the \
14939 :bibliotecas per-entry empty arm",
14940 );
14941 let c = caixa_with_code_paths(vec!["lib/demo.lisp"], vec![], vec![]);
14942 assert!(
14943 c.validate_code_paths().is_ok(),
14944 "validate_code_paths must accept bibliotecas == \
14945 vec![\"lib/demo.lisp\"] (the canonical single-library \
14946 shape every `feira init` template scaffolds)",
14947 );
14948 }
14949
14950 #[test]
14951 fn bibliotecas_projects_slice_by_borrow() {
14952 // The by-borrow pin: [`Caixa::bibliotecas`] returns
14953 // `&[String]` by borrow — the returned slice borrows the
14954 // underlying `Vec<String>` storage of the `:bibliotecas` slot
14955 // and the accessor must not clone the backing `Vec` on every
14956 // call. Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
14957 // (b5d813f) and `etiquetas_projects_slice_by_borrow` (78c7d3c)
14958 // by-borrow pins on the sibling outer top-level [`Caixa`]
14959 // `&[String]`-return axes — the accessor's returned slice
14960 // must borrow from `&self` (the returned reference's lifetime
14961 // is tied to `&self`), and calling the accessor twice on the
14962 // same [`Caixa`] must yield slices that are pointer-equal
14963 // (the underlying byte-buffer is the storage `Vec`'s
14964 // allocation, not a fresh copy) as well as value-equal
14965 // (idempotent, no side effects on `&self`).
14966 //
14967 // Pins against a future silent detour that returned an owned
14968 // `Vec<String>` (which would type-check but silently clone on
14969 // every call, breaking the zero-cost projection every peer
14970 // sibling slice accessor carries), a `&Vec<String>` return
14971 // (which would leak the backing `Vec`'s grow/push/reserve
14972 // surface no downstream consumer reaches for), or a one-arm-
14973 // only accessor that returned a saturating value on some
14974 // sentinel input (breaking the pass-through invariant the
14975 // sibling slice accessors carry).
14976 for bibliotecas in [
14977 vec![],
14978 vec!["lib/demo.lisp"],
14979 vec!["lib/demo.lisp", "lib/helpers.lisp"],
14980 vec!["lib/foo.lisp", "lib/foo.lisp"],
14981 ] {
14982 let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
14983 let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
14984 let first = c.bibliotecas();
14985 let second = c.bibliotecas();
14986 assert_eq!(
14987 first, second,
14988 "Caixa::bibliotecas must be idempotent — two \
14989 successive calls on the same &self must return the \
14990 same &[String]",
14991 );
14992 assert_eq!(
14993 first.as_ptr(),
14994 second.as_ptr(),
14995 "Caixa::bibliotecas must borrow the underlying \
14996 Vec<String> storage — two successive calls must \
14997 return slices with the same backing pointer (a \
14998 fresh Vec<String> clone would change the pointer on \
14999 every call)",
15000 );
15001 assert_eq!(
15002 first,
15003 expected.as_slice(),
15004 "Caixa::bibliotecas must return :bibliotecas verbatim \
15005 by borrow — got {first:?}, expected {expected:?}",
15006 );
15007 }
15008 }
15009
15010 // ── Caixa::exe — outer top-level &[T] slice accessor ──────────────
15011
15012 #[test]
15013 fn exe_returns_exe_slice_verbatim_across_permutations() {
15014 // The canonical per-`Caixa` `:exe` universal-axis
15015 // nix-built-executable-entry-path-list slice pin: [`Caixa::exe`]
15016 // must return the `:exe` typed [`Vec<String>`] list verbatim as
15017 // a `&[String]`, byte-equal to the raw `self.exe.as_slice()`
15018 // access across every representative value in the accept-set —
15019 // `[]` (the "no executable declared" arm every `:kind` other
15020 // than `Binario` carries; the layout's [`crate::LayoutInvariants`]
15021 // `BinarioWithoutExe` arm-gate fires exactly on this empty-slot
15022 // + `Binario`-kind combination), `[""]` (a past-the-guard
15023 // sentinel that pins the accessor doesn't perform a silent
15024 // `[""] → []` collapse on the empty-entry arm — validate rejects
15025 // `[""]` through `CodePathEmpty { slot: ":exe" }` but the
15026 // accessor must ship the raw slot verbatim so a validate-time
15027 // gate regression surfaces at the layout / `feira nix` boundary
15028 // rather than being silently absorbed into an executable-drop),
15029 // `["exe/cli"]` (the canonical single-entry Binario form every
15030 // in-tree `caixa_with_code_paths` positive control uses),
15031 // `["exe/cli", "exe/serve"]` (the canonical multi-executable
15032 // form the `validate_code_paths_accepts_explicit_relative_paths_
15033 // on_every_slot` fixture emits), and `["exe/cli", "exe/cli"]`
15034 // (a past-the-guard duplicate sentinel — validate rejects
15035 // through `CodePathDuplicate { slot: ":exe" }` per the per-slot
15036 // set-not-multiset gate, but the accessor must ship the raw
15037 // slot verbatim so struct-literal `Caixa { exe: vec!["exe/cli".
15038 // into(), "exe/cli".into()], .. }` fixtures continue to expose
15039 // the duplicate at the accessor).
15040 //
15041 // Fourth outer top-level [`Caixa`] `&[T]`-return slice accessor
15042 // pin on the substrate primitive — folds on the "outer
15043 // [`Caixa`] `&[T]` slice" projection pattern
15044 // `autores_returns_autores_slice_verbatim_across_permutations`
15045 // (b5d813f) opened,
15046 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
15047 // (78c7d3c) folded on, and
15048 // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
15049 // (8a36c23) closed the universal-axis text-tag family of.
15050 // Opens the outer-`Caixa` foreign-code-slot `&[T]` sub-family
15051 // the sibling `:servicos` future lift closes onto. Pins against
15052 // a future silent detour that returned an owned `Vec<String>`
15053 // (which would type-check but silently clone on every accessor
15054 // call, breaking the zero-cost projection every peer sibling
15055 // slice accessor carries), a `[""] → []` collapse (which would
15056 // silently absorb the `CodePathEmpty` refusal case at the
15057 // accessor boundary), or an `["exe/cli", "exe/cli"] →
15058 // ["exe/cli"]` dedup collapse (which would silently absorb the
15059 // `CodePathDuplicate` refusal case at the accessor boundary —
15060 // the per-slot set-not-multiset gate is downstream of the
15061 // accessor and must not be silently promoted into it).
15062 for exe in [
15063 vec![],
15064 vec![""],
15065 vec!["exe/cli"],
15066 vec!["exe/cli", "exe/serve"],
15067 vec!["exe/cli", "exe/cli"],
15068 ] {
15069 let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
15070 let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
15071 assert_eq!(
15072 c.exe(),
15073 expected.as_slice(),
15074 "Caixa::exe must return :exe verbatim (got {:?}, \
15075 expected {expected:?})",
15076 c.exe(),
15077 );
15078 assert_eq!(
15079 c.exe(),
15080 c.exe.as_slice(),
15081 "Caixa::exe must byte-equal the raw \
15082 `self.exe.as_slice()` field access across every value \
15083 in the Vec<String> accept-set",
15084 );
15085 }
15086 }
15087
15088 #[test]
15089 fn validate_code_paths_exe_empty_arm_routes_through_accessor() {
15090 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
15091 // empty-arm gate on the `:exe` slot must key off
15092 // [`Caixa::exe`], not a divergent raw `&self.exe` field-borrow
15093 // walk. Structurally: a `Caixa { exe: vec!["".into()], .. }`
15094 // must surface the `CodePathEmpty { slot: ":exe" }` refusal
15095 // exactly, and a `Caixa { exe: vec!["exe/cli".into()], .. }`
15096 // (the canonical single-executable form every in-tree
15097 // `caixa_with_code_paths` positive control uses) must pass
15098 // validate. The pair jointly pins the accessor + validate-gate
15099 // composition: any future silent detour that had the accessor
15100 // return an empty slice on the `[""]` arm (a
15101 // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
15102 // silently absorb the `CodePathEmpty` refusal at the accessor
15103 // boundary and the validate gate would accept a struct-literal
15104 // `Caixa { exe: vec!["".into()], .. }` — the composition pin
15105 // catches that at caixa-core build time.
15106 //
15107 // Peer of the per-`Caixa`
15108 // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
15109 // (8a36c23), `validate_autores_empty_arm_routes_through_accessor`
15110 // (b5d813f), and
15111 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
15112 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
15113 // composition axes — same "the validate / shape-gate predicate
15114 // must route through the substrate-primitive typed dispatch"
15115 // discipline extended onto the sibling outer top-level [`Caixa`]
15116 // `&[T]`-composition surface. Nominally the in-tree
15117 // `validate_code_paths` production body still keys off the
15118 // internal `[(":bibliotecas", &self.bibliotecas,
15119 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
15120 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
15121 // (the tuple's homogeneous slice-typed shape blocks a per-
15122 // element accessor swap in isolation — a future companion lift
15123 // for `:servicos` on the same outer-`Caixa` `&[T]` slice-
15124 // accessor axis closes that tuple onto the triple of typed
15125 // dispatches as a unit); the composition pin catches any future
15126 // accessor-side silent filter drop against that eventual tuple-
15127 // closure regardless of whether the `:exe` slot is threaded
15128 // through the accessor or the raw field access at the tuple's
15129 // construction site.
15130 let c = caixa_with_code_paths(vec![], vec![""], vec![]);
15131 assert!(
15132 matches!(
15133 c.validate_code_paths(),
15134 Err(ManifestError::CodePathEmpty { slot: ":exe" })
15135 ),
15136 "validate_code_paths must reject exe == vec![\"\"] \
15137 with CodePathEmpty {{ slot: \":exe\" }} — the \
15138 accessor and the validate gate must route through the \
15139 same substrate-primitive typed dispatch on the \
15140 :exe per-entry empty arm",
15141 );
15142 let c = caixa_with_code_paths(vec![], vec!["exe/cli"], vec![]);
15143 assert!(
15144 c.validate_code_paths().is_ok(),
15145 "validate_code_paths must accept exe == vec![\"exe/cli\"] \
15146 (the canonical single-executable shape every in-tree \
15147 `caixa_with_code_paths` positive control uses)",
15148 );
15149 }
15150
15151 #[test]
15152 fn exe_projects_slice_by_borrow() {
15153 // The by-borrow pin: [`Caixa::exe`] returns `&[String]` by
15154 // borrow — the returned slice borrows the underlying
15155 // `Vec<String>` storage of the `:exe` slot and the accessor
15156 // must not clone the backing `Vec` on every call. Peer of the
15157 // per-`Caixa` `autores_projects_slice_by_borrow` (b5d813f),
15158 // `etiquetas_projects_slice_by_borrow` (78c7d3c), and
15159 // `bibliotecas_projects_slice_by_borrow` (8a36c23) by-borrow
15160 // pins on the sibling outer top-level [`Caixa`] `&[String]`-
15161 // return axes — the accessor's returned slice must borrow from
15162 // `&self` (the returned reference's lifetime is tied to
15163 // `&self`), and calling the accessor twice on the same
15164 // [`Caixa`] must yield slices that are pointer-equal (the
15165 // underlying byte-buffer is the storage `Vec`'s allocation,
15166 // not a fresh copy) as well as value-equal (idempotent, no
15167 // side effects on `&self`).
15168 //
15169 // Pins against a future silent detour that returned an owned
15170 // `Vec<String>` (which would type-check but silently clone on
15171 // every call, breaking the zero-cost projection every peer
15172 // sibling slice accessor carries), a `&Vec<String>` return
15173 // (which would leak the backing `Vec`'s grow/push/reserve
15174 // surface no downstream consumer reaches for), or a one-arm-
15175 // only accessor that returned a saturating value on some
15176 // sentinel input (breaking the pass-through invariant the
15177 // sibling slice accessors carry).
15178 for exe in [
15179 vec![],
15180 vec!["exe/cli"],
15181 vec!["exe/cli", "exe/serve"],
15182 vec!["exe/cli", "exe/cli"],
15183 ] {
15184 let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
15185 let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
15186 let first = c.exe();
15187 let second = c.exe();
15188 assert_eq!(
15189 first, second,
15190 "Caixa::exe must be idempotent — two successive calls \
15191 on the same &self must return the same &[String]",
15192 );
15193 assert_eq!(
15194 first.as_ptr(),
15195 second.as_ptr(),
15196 "Caixa::exe must borrow the underlying Vec<String> \
15197 storage — two successive calls must return slices \
15198 with the same backing pointer (a fresh Vec<String> \
15199 clone would change the pointer on every call)",
15200 );
15201 assert_eq!(
15202 first,
15203 expected.as_slice(),
15204 "Caixa::exe must return :exe verbatim by borrow — \
15205 got {first:?}, expected {expected:?}",
15206 );
15207 }
15208 }
15209
15210 // ── Caixa::servicos — outer top-level &[T] slice accessor ─────────
15211
15212 #[test]
15213 fn servicos_returns_servicos_slice_verbatim_across_permutations() {
15214 // The canonical per-`Caixa` `:servicos` universal-axis
15215 // ComputeUnit-CR-YAML-entry-path-list slice pin:
15216 // [`Caixa::servicos`] must return the `:servicos` typed
15217 // [`Vec<String>`] list verbatim as a `&[String]`, byte-equal to
15218 // the raw `self.servicos.as_slice()` access across every
15219 // representative value in the accept-set — `[]` (the "no
15220 // ComputeUnit-CR declared" arm every `:kind` other than
15221 // `Servico` carries; the layout's [`crate::LayoutInvariants`]
15222 // `ServicoWithoutServicos` arm-gate fires exactly on this
15223 // empty-slot + `Servico`-kind combination), `[""]` (a past-the-
15224 // guard sentinel that pins the accessor doesn't perform a
15225 // silent `[""] → []` collapse on the empty-entry arm — validate
15226 // rejects `[""]` through `CodePathEmpty { slot: ":servicos" }`
15227 // but the accessor must ship the raw slot verbatim so a
15228 // validate-time gate regression surfaces at the layout /
15229 // per-Servico renderer boundary rather than being silently
15230 // absorbed into a component-drop),
15231 // `["servicos/demo.computeunit.yaml"]` (the canonical
15232 // singleton V0-shape every in-tree `caixa_with_code_paths`
15233 // positive control uses; the same shape
15234 // [`crate::require_single_servico`] admits),
15235 // `["servicos/a.computeunit.yaml", "servicos/b.computeunit.
15236 // yaml"]` (a past-the-guard `len != 1` sentinel — the V0
15237 // singularity gate rejects through `ServicoCountMismatch
15238 // { count: 2 }` but the accessor must ship the raw slot
15239 // verbatim so struct-literal `Caixa { servicos: vec![...,
15240 // ...], .. }` fixtures continue to expose the count at the
15241 // accessor), and `["servicos/a.computeunit.yaml",
15242 // "servicos/a.computeunit.yaml"]` (a past-the-guard duplicate
15243 // sentinel — validate rejects through
15244 // `CodePathDuplicate { slot: ":servicos" }` per the per-slot
15245 // set-not-multiset gate, but the accessor must ship the raw
15246 // slot verbatim so struct-literal fixtures continue to expose
15247 // the duplicate at the accessor).
15248 //
15249 // Fifth and final outer top-level [`Caixa`] `&[T]`-return
15250 // slice accessor pin on the substrate primitive — folds on the
15251 // "outer [`Caixa`] `&[T]` slice" projection pattern
15252 // `autores_returns_autores_slice_verbatim_across_permutations`
15253 // (b5d813f) opened,
15254 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
15255 // (78c7d3c) folded on,
15256 // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
15257 // (8a36c23) closed the universal-axis text-tag family of, and
15258 // `exe_returns_exe_slice_verbatim_across_permutations`
15259 // (65d9527) opened the foreign-code-slot sub-family of. Closes
15260 // the outer-`Caixa` foreign-code-slot `&[T]` sub-family — the
15261 // trio of code-surface list slots (`:bibliotecas` + `:exe` +
15262 // `:servicos`) now each carries a substrate-canonical slice
15263 // accessor. Pins against a future silent detour that returned
15264 // an owned `Vec<String>` (which would type-check but silently
15265 // clone on every accessor call, breaking the zero-cost
15266 // projection every peer sibling slice accessor carries), a
15267 // `[""] → []` collapse (which would silently absorb the
15268 // `CodePathEmpty` refusal case at the accessor boundary), an
15269 // `[a, a] → [a]` dedup collapse (which would silently absorb
15270 // the `CodePathDuplicate` refusal case at the accessor
15271 // boundary — the per-slot set-not-multiset gate is downstream
15272 // of the accessor and must not be silently promoted into it),
15273 // or a `[a, b] → [a]` singleton collapse (which would silently
15274 // absorb the V0 `ServicoCountMismatch` refusal case at the
15275 // accessor boundary — the V0 singularity gate is downstream of
15276 // the accessor and must not be silently promoted into it).
15277 for servicos in [
15278 vec![],
15279 vec![""],
15280 vec!["servicos/demo.computeunit.yaml"],
15281 vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
15282 vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
15283 ] {
15284 let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
15285 let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
15286 assert_eq!(
15287 c.servicos(),
15288 expected.as_slice(),
15289 "Caixa::servicos must return :servicos verbatim (got \
15290 {:?}, expected {expected:?})",
15291 c.servicos(),
15292 );
15293 assert_eq!(
15294 c.servicos(),
15295 c.servicos.as_slice(),
15296 "Caixa::servicos must byte-equal the raw \
15297 `self.servicos.as_slice()` field access across every \
15298 value in the Vec<String> accept-set",
15299 );
15300 }
15301 }
15302
15303 #[test]
15304 fn validate_code_paths_servicos_empty_arm_routes_through_accessor() {
15305 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
15306 // empty-arm gate on the `:servicos` slot must key off
15307 // [`Caixa::servicos`], not a divergent raw `&self.servicos`
15308 // field-borrow walk. Structurally: a `Caixa { servicos:
15309 // vec!["".into()], .. }` must surface the `CodePathEmpty
15310 // { slot: ":servicos" }` refusal exactly, and a `Caixa
15311 // { servicos: vec!["servicos/demo.computeunit.yaml".into()],
15312 // .. }` (the canonical singleton V0-shape every in-tree
15313 // `caixa_with_code_paths` positive control uses) must pass
15314 // validate. The pair jointly pins the accessor + validate-gate
15315 // composition: any future silent detour that had the accessor
15316 // return an empty slice on the `[""]` arm (a `.iter().filter
15317 // (|s| !s.is_empty()).collect()` collapse) would silently
15318 // absorb the `CodePathEmpty` refusal at the accessor boundary
15319 // and the validate gate would accept a struct-literal
15320 // `Caixa { servicos: vec!["".into()], .. }` — the composition
15321 // pin catches that at caixa-core build time.
15322 //
15323 // Peer of the per-`Caixa`
15324 // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
15325 // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
15326 // (65d9527), `validate_autores_empty_arm_routes_through_accessor`
15327 // (b5d813f), and
15328 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
15329 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
15330 // composition axes — same "the validate / shape-gate predicate
15331 // must route through the substrate-primitive typed dispatch"
15332 // discipline extended onto the sibling outer top-level
15333 // [`Caixa`] `&[T]`-composition surface, closing the trio of
15334 // code-surface accessor-composition pins on the same axis.
15335 // Nominally the in-tree `validate_code_paths` production body
15336 // still keys off the internal
15337 // `[(":bibliotecas", &self.bibliotecas,
15338 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
15339 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
15340 // (the tuple's homogeneous `&Vec<String>`-typed shape blocks a
15341 // per-element accessor swap in isolation — a future companion
15342 // lift promotes the tuple's element type to `&[String]` and
15343 // threads the triple of typed dispatches through as a unit);
15344 // the composition pin catches any future accessor-side silent
15345 // filter drop against that eventual tuple-closure regardless
15346 // of whether the `:servicos` slot is threaded through the
15347 // accessor or the raw field access at the tuple's construction
15348 // site.
15349 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
15350 assert!(
15351 matches!(
15352 c.validate_code_paths(),
15353 Err(ManifestError::CodePathEmpty { slot: ":servicos" })
15354 ),
15355 "validate_code_paths must reject servicos == vec![\"\"] \
15356 with CodePathEmpty {{ slot: \":servicos\" }} — the \
15357 accessor and the validate gate must route through the \
15358 same substrate-primitive typed dispatch on the \
15359 :servicos per-entry empty arm",
15360 );
15361 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.computeunit.yaml"]);
15362 assert!(
15363 c.validate_code_paths().is_ok(),
15364 "validate_code_paths must accept servicos == \
15365 vec![\"servicos/demo.computeunit.yaml\"] (the canonical \
15366 singleton V0-shape every in-tree `caixa_with_code_paths` \
15367 positive control uses)",
15368 );
15369 }
15370
15371 #[test]
15372 fn servicos_projects_slice_by_borrow() {
15373 // The by-borrow pin: [`Caixa::servicos`] returns `&[String]` by
15374 // borrow — the returned slice borrows the underlying
15375 // `Vec<String>` storage of the `:servicos` slot and the
15376 // accessor must not clone the backing `Vec` on every call.
15377 // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
15378 // (b5d813f), `etiquetas_projects_slice_by_borrow` (78c7d3c),
15379 // `bibliotecas_projects_slice_by_borrow` (8a36c23), and
15380 // `exe_projects_slice_by_borrow` (65d9527) by-borrow pins on
15381 // the sibling outer top-level [`Caixa`] `&[String]`-return
15382 // axes — the accessor's returned slice must borrow from
15383 // `&self` (the returned reference's lifetime is tied to
15384 // `&self`), and calling the accessor twice on the same
15385 // [`Caixa`] must yield slices that are pointer-equal (the
15386 // underlying byte-buffer is the storage `Vec`'s allocation,
15387 // not a fresh copy) as well as value-equal (idempotent, no
15388 // side effects on `&self`).
15389 //
15390 // Pins against a future silent detour that returned an owned
15391 // `Vec<String>` (which would type-check but silently clone on
15392 // every call, breaking the zero-cost projection every peer
15393 // sibling slice accessor carries), a `&Vec<String>` return
15394 // (which would leak the backing `Vec`'s grow/push/reserve
15395 // surface no downstream consumer reaches for), or a one-arm-
15396 // only accessor that returned a saturating value on some
15397 // sentinel input (breaking the pass-through invariant the
15398 // sibling slice accessors carry).
15399 for servicos in [
15400 vec![],
15401 vec!["servicos/demo.computeunit.yaml"],
15402 vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
15403 vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
15404 ] {
15405 let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
15406 let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
15407 let first = c.servicos();
15408 let second = c.servicos();
15409 assert_eq!(
15410 first, second,
15411 "Caixa::servicos must be idempotent — two successive \
15412 calls on the same &self must return the same &[String]",
15413 );
15414 assert_eq!(
15415 first.as_ptr(),
15416 second.as_ptr(),
15417 "Caixa::servicos must borrow the underlying \
15418 Vec<String> storage — two successive calls must \
15419 return slices with the same backing pointer (a fresh \
15420 Vec<String> clone would change the pointer on every \
15421 call)",
15422 );
15423 assert_eq!(
15424 first,
15425 expected.as_slice(),
15426 "Caixa::servicos must return :servicos verbatim by \
15427 borrow — got {first:?}, expected {expected:?}",
15428 );
15429 }
15430 }
15431
15432 // ── Caixa::deps — outer top-level &[Dep] slice accessor ───────────
15433
15434 fn caixa_with_deps(deps: Vec<Dep>) -> Caixa {
15435 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15436 c.deps = deps;
15437 c
15438 }
15439
15440 #[test]
15441 fn deps_returns_deps_slice_verbatim_across_permutations() {
15442 // The canonical per-`Caixa` `:deps` universal-axis runtime-
15443 // dependency-declaration-list slice pin: [`Caixa::deps`] must
15444 // return the `:deps` typed [`Vec<Dep>`] list verbatim as a
15445 // `&[Dep]`, element-equal to the raw `self.deps.as_slice()`
15446 // access across every representative value in the accept-set —
15447 // `[]` (the "no runtime deps declared" arm every existing
15448 // fixture without a `:deps` line carries; the
15449 // [`Caixa::template`] scaffold emits `:deps ()`), a canonical
15450 // single-entry list (the shape most consumer caixas carry), a
15451 // canonical two-entry list (the multi-dep runtime closure), and
15452 // two past-the-guard sentinels — a `[""]`-`:nome` entry
15453 // ([`Self::validate_deps`] rejects through `NomeEmpty` /
15454 // `NomeInvalid` but the accessor must ship the raw slot
15455 // verbatim) and a `[a, a]` duplicate (validate rejects through
15456 // `DuplicateNome { list: ":deps" }` but the accessor must ship
15457 // the raw slot verbatim so struct-literal fixtures continue to
15458 // expose the duplicate at the accessor).
15459 //
15460 // First outer top-level [`Caixa`] `&[Dep]`-return slice accessor
15461 // pin on the substrate primitive — opens the outer-`Caixa`
15462 // dependency-slot `&[Dep]` sub-family the sibling `:deps-dev`
15463 // future lift closes on. Peer of the closed outer-`Caixa`
15464 // foreign-code-slot `&[String]` sub-family
15465 // (`bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
15466 // 8a36c23, `exe_returns_exe_slice_verbatim_across_permutations`
15467 // 65d9527, `servicos_returns_servicos_slice_verbatim_across_permutations`
15468 // 611f78b) and the outer-`Caixa` universal-axis text-tag family
15469 // (`autores_returns_autores_slice_verbatim_across_permutations`
15470 // b5d813f, `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
15471 // 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice"
15472 // projection pattern onto a novel element-type axis (`Dep`
15473 // composite vs the prior sibling family's `String` scalar).
15474 // Pins against a future silent detour that returned an owned
15475 // `Vec<Dep>` (which would type-check but silently clone on every
15476 // accessor call, breaking the zero-cost projection every peer
15477 // sibling slice accessor carries), a `[""] → []` collapse (which
15478 // would silently absorb the `NomeEmpty` refusal case at the
15479 // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
15480 // would silently absorb the `DuplicateNome` refusal case at the
15481 // accessor boundary).
15482 for deps in [
15483 vec![],
15484 vec![Dep::simple("", "^0.1")],
15485 vec![Dep::simple("caixa-teia", "^0.1")],
15486 vec![
15487 Dep::simple("caixa-teia", "^0.1"),
15488 Dep::simple("caixa-core", "^0.1"),
15489 ],
15490 vec![
15491 Dep::simple("caixa-teia", "^0.1"),
15492 Dep::simple("caixa-teia", "^0.2"),
15493 ],
15494 ] {
15495 let c = caixa_with_deps(deps.clone());
15496 assert_eq!(
15497 c.deps(),
15498 deps.as_slice(),
15499 "Caixa::deps must return :deps verbatim (got {:?}, \
15500 expected {deps:?})",
15501 c.deps(),
15502 );
15503 assert_eq!(
15504 c.deps(),
15505 c.deps.as_slice(),
15506 "Caixa::deps must element-equal the raw \
15507 `self.deps.as_slice()` field access across every \
15508 value in the Vec<Dep> accept-set",
15509 );
15510 }
15511 }
15512
15513 #[test]
15514 fn validate_deps_duplicate_arm_routes_through_accessor() {
15515 // Composition pin: [`Caixa::validate_deps`]'s within-`:deps`
15516 // duplicate-`:nome` gate must key off [`Caixa::deps`], not the
15517 // raw `&self.deps` field-borrow walk. Structurally: a `Caixa
15518 // { deps: vec![Dep::simple("d", "^0.1"), Dep::simple("d",
15519 // "^0.2")], .. }` must surface the `DuplicateNome { list:
15520 // ":deps" }` refusal exactly, and a `Caixa { deps: vec![
15521 // Dep::simple("d", "^0.1")], .. }` (the canonical single-entry
15522 // form) must pass validate. The pair jointly pins the accessor +
15523 // validate-gate composition: any future silent detour that had
15524 // the accessor return a dedupped slice on the `[a, a]` arm (a
15525 // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
15526 // would silently absorb the `DuplicateNome` refusal at the
15527 // accessor boundary and the validate gate would accept a
15528 // struct-literal `Caixa` carrying the drift — the composition
15529 // pin catches that at caixa-core build time.
15530 //
15531 // Peer of the per-`Caixa`
15532 // `validate_autores_empty_entry_arm_routes_through_accessor`
15533 // (b5d813f), `validate_etiquetas_empty_entry_arm_routes_through_accessor`
15534 // (78c7d3c), `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
15535 // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
15536 // (65d9527), and `validate_code_paths_servicos_empty_arm_routes_through_accessor`
15537 // (611f78b) accessor-composition pins on the sibling `&[T]`-
15538 // composition axes — same "the validate gate must route through
15539 // the substrate-primitive typed dispatch" discipline extended
15540 // onto the sibling outer top-level [`Caixa`] `&[Dep]`-
15541 // composition surface, opening the outer-`Caixa` dependency-slot
15542 // arm of the composition-pin family.
15543 let c = caixa_with_deps(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
15544 let err = c.validate_deps().unwrap_err();
15545 assert!(
15546 matches!(
15547 err,
15548 DepError::DuplicateNome { ref nome, list } if nome == "d"
15549 && list == crate::render::DEP_AUTHOR_KEY_DEPS
15550 ),
15551 "validate_deps must reject deps == \
15552 vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
15553 DuplicateNome {{ nome: \"d\", list: \":deps\" }} — the \
15554 accessor and the validate gate must route through the \
15555 same substrate-primitive typed dispatch on the :deps \
15556 within-list duplicate arm (got {err:?})",
15557 );
15558 let c = caixa_with_deps(vec![Dep::simple("d", "^0.1")]);
15559 assert!(
15560 c.validate_deps().is_ok(),
15561 "validate_deps must accept deps == vec![Dep(\"d\",\"^0.1\")] \
15562 (the canonical single-entry form)",
15563 );
15564 }
15565
15566 #[test]
15567 fn deps_projects_slice_by_borrow() {
15568 // The by-borrow pin: [`Caixa::deps`] returns `&[Dep]` by borrow
15569 // — the returned slice borrows the underlying `Vec<Dep>` storage
15570 // of the `:deps` slot and the accessor must not clone the
15571 // backing `Vec` on every call. Peer of the per-`Caixa`
15572 // `autores_projects_slice_by_borrow` (b5d813f),
15573 // `etiquetas_projects_slice_by_borrow` (78c7d3c),
15574 // `bibliotecas_projects_slice_by_borrow` (8a36c23),
15575 // `exe_projects_slice_by_borrow` (65d9527), and
15576 // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
15577 // on the sibling outer top-level [`Caixa`] `&[String]`-return
15578 // axes — the accessor's returned slice must borrow from `&self`
15579 // (the returned reference's lifetime is tied to `&self`), and
15580 // calling the accessor twice on the same [`Caixa`] must yield
15581 // slices that are pointer-equal (the underlying byte-buffer is
15582 // the storage `Vec`'s allocation, not a fresh copy) as well as
15583 // value-equal (idempotent, no side effects on `&self`).
15584 //
15585 // Pins against a future silent detour that returned an owned
15586 // `Vec<Dep>` (which would type-check but silently clone on
15587 // every call), a `&Vec<Dep>` return (which would leak the
15588 // backing `Vec`'s grow/push/reserve surface no downstream
15589 // consumer reaches for), or a one-arm-only accessor that
15590 // returned a saturating value on some sentinel input.
15591 for deps in [
15592 vec![],
15593 vec![Dep::simple("caixa-teia", "^0.1")],
15594 vec![
15595 Dep::simple("caixa-teia", "^0.1"),
15596 Dep::simple("caixa-core", "^0.1"),
15597 ],
15598 ] {
15599 let c = caixa_with_deps(deps.clone());
15600 let first = c.deps();
15601 let second = c.deps();
15602 assert_eq!(
15603 first, second,
15604 "Caixa::deps must be idempotent — two successive calls \
15605 on the same &self must return the same &[Dep]",
15606 );
15607 assert_eq!(
15608 first.as_ptr(),
15609 second.as_ptr(),
15610 "Caixa::deps must borrow the underlying Vec<Dep> \
15611 storage — two successive calls must return slices \
15612 with the same backing pointer (a fresh Vec<Dep> clone \
15613 would change the pointer on every call)",
15614 );
15615 assert_eq!(
15616 first,
15617 deps.as_slice(),
15618 "Caixa::deps must return :deps verbatim by borrow — \
15619 got {first:?}, expected {deps:?}",
15620 );
15621 }
15622 }
15623
15624 // ── Caixa::deps_dev — outer top-level &[Dep] slice accessor ──────
15625
15626 fn caixa_with_deps_dev(deps_dev: Vec<Dep>) -> Caixa {
15627 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15628 c.deps_dev = deps_dev;
15629 c
15630 }
15631
15632 #[test]
15633 fn deps_dev_returns_deps_dev_slice_verbatim_across_permutations() {
15634 // The canonical per-`Caixa` `:deps-dev` universal-axis dev-only-
15635 // dependency-declaration-list slice pin: [`Caixa::deps_dev`]
15636 // must return the `:deps-dev` typed [`Vec<Dep>`] list verbatim as
15637 // a `&[Dep]`, element-equal to the raw `self.deps_dev.as_slice()`
15638 // access across every representative value in the accept-set —
15639 // `[]` (the "no dev deps declared" arm every existing fixture
15640 // without a `:deps-dev` line carries; the [`Caixa::template`]
15641 // scaffold emits `:deps-dev ()`), a canonical single-entry list
15642 // (the shape most consumer caixas carry — a `tatara-check` dev
15643 // pin), a canonical two-entry list (the multi-dev-dep closure),
15644 // and two past-the-guard sentinels — a `[""]`-`:nome` entry
15645 // ([`Self::validate_deps`] rejects through `NomeEmpty` /
15646 // `NomeInvalid` but the accessor must ship the raw slot
15647 // verbatim) and a `[a, a]` duplicate (validate rejects through
15648 // `DuplicateNome { list: ":deps-dev" }` but the accessor must
15649 // ship the raw slot verbatim so struct-literal fixtures continue
15650 // to expose the duplicate at the accessor).
15651 //
15652 // Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor
15653 // pin on the substrate primitive — closes the outer-`Caixa`
15654 // dependency-slot `&[Dep]` sub-family the sibling
15655 // `deps_returns_deps_slice_verbatim_across_permutations`
15656 // (ad34b4e) opened on. Folds the "outer [`Caixa`] `&[Dep]`
15657 // slice" projection pattern onto the sibling dev-dep axis —
15658 // pins against a future silent detour that returned an owned
15659 // `Vec<Dep>` (which would type-check but silently clone on every
15660 // accessor call, breaking the zero-cost projection every peer
15661 // sibling slice accessor carries), a `[""] → []` collapse (which
15662 // would silently absorb the `NomeEmpty` refusal case at the
15663 // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
15664 // would silently absorb the `DuplicateNome` refusal case at the
15665 // accessor boundary).
15666 for deps_dev in [
15667 vec![],
15668 vec![Dep::simple("", "^0.1")],
15669 vec![Dep::simple("tatara-check", "^0.1")],
15670 vec![
15671 Dep::simple("tatara-check", "^0.1"),
15672 Dep::simple("caixa-lint", "^0.1"),
15673 ],
15674 vec![
15675 Dep::simple("tatara-check", "^0.1"),
15676 Dep::simple("tatara-check", "^0.2"),
15677 ],
15678 ] {
15679 let c = caixa_with_deps_dev(deps_dev.clone());
15680 assert_eq!(
15681 c.deps_dev(),
15682 deps_dev.as_slice(),
15683 "Caixa::deps_dev must return :deps-dev verbatim (got \
15684 {:?}, expected {deps_dev:?})",
15685 c.deps_dev(),
15686 );
15687 assert_eq!(
15688 c.deps_dev(),
15689 c.deps_dev.as_slice(),
15690 "Caixa::deps_dev must element-equal the raw \
15691 `self.deps_dev.as_slice()` field access across every \
15692 value in the Vec<Dep> accept-set",
15693 );
15694 }
15695 }
15696
15697 #[test]
15698 fn validate_deps_duplicate_deps_dev_arm_routes_through_accessor() {
15699 // Composition pin: [`Caixa::validate_deps`]'s within-`:deps-dev`
15700 // duplicate-`:nome` gate must key off [`Caixa::deps_dev`], not
15701 // the raw `&self.deps_dev` field-borrow walk. Structurally: a
15702 // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1"),
15703 // Dep::simple("d", "^0.2")], .. }` must surface the
15704 // `DuplicateNome { list: ":deps-dev" }` refusal exactly, and a
15705 // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1")], .. }` (the
15706 // canonical single-entry form) must pass validate. The pair
15707 // jointly pins the accessor + validate-gate composition: any
15708 // future silent detour that had the accessor return a dedupped
15709 // slice on the `[a, a]` arm (a
15710 // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
15711 // would silently absorb the `DuplicateNome` refusal at the
15712 // accessor boundary and the validate gate would accept a
15713 // struct-literal `Caixa` carrying the drift — the composition
15714 // pin catches that at caixa-core build time.
15715 //
15716 // Peer of `validate_deps_duplicate_arm_routes_through_accessor`
15717 // (ad34b4e) on the sibling `:deps` axis — same "the validate
15718 // gate must route through the substrate-primitive typed
15719 // dispatch" discipline folded onto the sibling `:deps-dev`
15720 // axis, closing the two-list dep-graph composition-pin family.
15721 // The `:deps-dev` diagnostic must carry the
15722 // `DEP_AUTHOR_KEY_DEPS_DEV` list-tag (not
15723 // `DEP_AUTHOR_KEY_DEPS`) so the emitted error names the
15724 // offending list unambiguously.
15725 let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
15726 let err = c.validate_deps().unwrap_err();
15727 assert!(
15728 matches!(
15729 err,
15730 DepError::DuplicateNome { ref nome, list } if nome == "d"
15731 && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
15732 ),
15733 "validate_deps must reject deps_dev == \
15734 vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
15735 DuplicateNome {{ nome: \"d\", list: \":deps-dev\" }} — the \
15736 accessor and the validate gate must route through the \
15737 same substrate-primitive typed dispatch on the :deps-dev \
15738 within-list duplicate arm (got {err:?})",
15739 );
15740 let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1")]);
15741 assert!(
15742 c.validate_deps().is_ok(),
15743 "validate_deps must accept deps_dev == \
15744 vec![Dep(\"d\",\"^0.1\")] (the canonical single-entry form)",
15745 );
15746 }
15747
15748 #[test]
15749 fn deps_dev_projects_slice_by_borrow() {
15750 // The by-borrow pin: [`Caixa::deps_dev`] returns `&[Dep]` by
15751 // borrow — the returned slice borrows the underlying `Vec<Dep>`
15752 // storage of the `:deps-dev` slot and the accessor must not
15753 // clone the backing `Vec` on every call. Peer of
15754 // `deps_projects_slice_by_borrow` (ad34b4e) on the sibling
15755 // `:deps` axis, and of the per-`Caixa`
15756 // `autores_projects_slice_by_borrow` (b5d813f),
15757 // `etiquetas_projects_slice_by_borrow` (78c7d3c),
15758 // `bibliotecas_projects_slice_by_borrow` (8a36c23),
15759 // `exe_projects_slice_by_borrow` (65d9527), and
15760 // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
15761 // on the sibling outer top-level [`Caixa`] `&[String]`-return
15762 // axes — the accessor's returned slice must borrow from `&self`
15763 // (the returned reference's lifetime is tied to `&self`), and
15764 // calling the accessor twice on the same [`Caixa`] must yield
15765 // slices that are pointer-equal (the underlying byte-buffer is
15766 // the storage `Vec`'s allocation, not a fresh copy) as well as
15767 // value-equal (idempotent, no side effects on `&self`).
15768 //
15769 // Pins against a future silent detour that returned an owned
15770 // `Vec<Dep>` (which would type-check but silently clone on
15771 // every call), a `&Vec<Dep>` return (which would leak the
15772 // backing `Vec`'s grow/push/reserve surface no downstream
15773 // consumer reaches for), or a one-arm-only accessor that
15774 // returned a saturating value on some sentinel input.
15775 for deps_dev in [
15776 vec![],
15777 vec![Dep::simple("tatara-check", "^0.1")],
15778 vec![
15779 Dep::simple("tatara-check", "^0.1"),
15780 Dep::simple("caixa-lint", "^0.1"),
15781 ],
15782 ] {
15783 let c = caixa_with_deps_dev(deps_dev.clone());
15784 let first = c.deps_dev();
15785 let second = c.deps_dev();
15786 assert_eq!(
15787 first, second,
15788 "Caixa::deps_dev must be idempotent — two successive \
15789 calls on the same &self must return the same &[Dep]",
15790 );
15791 assert_eq!(
15792 first.as_ptr(),
15793 second.as_ptr(),
15794 "Caixa::deps_dev must borrow the underlying Vec<Dep> \
15795 storage — two successive calls must return slices \
15796 with the same backing pointer (a fresh Vec<Dep> clone \
15797 would change the pointer on every call)",
15798 );
15799 assert_eq!(
15800 first,
15801 deps_dev.as_slice(),
15802 "Caixa::deps_dev must return :deps-dev verbatim by \
15803 borrow — got {first:?}, expected {deps_dev:?}",
15804 );
15805 }
15806 }
15807
15808 // ── Caixa::limits — outer top-level Option<&LimitsSpec> composite-reference accessor ──
15809
15810 fn caixa_with_limits(limits: Option<crate::LimitsSpec>) -> Caixa {
15811 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15812 c.limits = limits;
15813 c
15814 }
15815
15816 #[test]
15817 fn limits_returns_limits_option_ref_verbatim_across_permutations() {
15818 // The canonical per-`Caixa` `:limits` M2 typed-slot outer-
15819 // composite optional-composite-reference-shape pin:
15820 // [`Caixa::limits`] must return the `:limits` typed
15821 // `Option<LimitsSpec>` verbatim as an `Option<&LimitsSpec>`
15822 // reference over the same backing storage the raw
15823 // `self.limits.as_ref()` field access borrows from, byte-equal
15824 // across every representative fixture in the accept-set — the
15825 // author-omitted `None` shape (the "engine-default applies"
15826 // partition every downstream Servico M2 overlay emitter treats
15827 // as "emit nothing"), the empty-composite `Some(LimitsSpec {
15828 // .. default })` shape ([`LimitsSpec::is_empty`] holds — every
15829 // per-axis cap is `None`, so the peer M2 overlay emitter's
15830 // `.is_empty()`-gated projection still emits nothing but the
15831 // outer presence-bit is `Some`, so [`Caixa::declared_servico_slots`]
15832 // still pushes the `M2_AUTHOR_KEY_LIMITS` label), a single-axis
15833 // fixture (only `:memory` set — the canonical shape most
15834 // memory-heavy Servicos carry), and a fully-populated composite
15835 // (every per-axis cap set — the canonical shape a
15836 // sandboxed-by-default Servico carries).
15837 //
15838 // Pins against a future silent detour that returned a fresh-
15839 // cloned [`LimitsSpec`] copy (which would type-check via the
15840 // `Clone` impl but silently break every downstream caller that
15841 // relied on the reference sharing the composite's backing
15842 // identity), a reference to an operator-resolved overlay (the
15843 // future per-cluster `:limits-overrides` slot — its resolution
15844 // must land at exactly this accessor body, not silently divert
15845 // the raw slot away from a second consumer), a
15846 // `None` → `Some(LimitsSpec::default)` cluster-default
15847 // projection (which would collapse the load-bearing
15848 // "author-omitted `:limits` ⇒ engine-default applies" partition
15849 // the peer [`crate::render::servico_m2_overlay`] emitter and
15850 // the peer [`Caixa::declared_servico_slots`] enumerator both
15851 // read), or an axis-shuffled projection (a future detour that
15852 // swapped `memory` and `fuel` through the accessor would
15853 // silently split the paired [`crate::StandardLayout::verify`]
15854 // per-`:limits` shape gate's traversal input from the peer
15855 // `servico_m2_overlay` emitter's projection input).
15856 //
15857 // First outer top-level [`Caixa`] `Option<&Composite>`-return
15858 // composite-reference accessor pin on the substrate primitive
15859 // — opens the outer-`Caixa` `Option<&Composite>` composite-
15860 // reference projection pattern the sibling `:behavior`
15861 // [`crate::BehaviorSpec`] / `:politicas`
15862 // [`crate::aplicacao::MeshPolicy`] / `:placement`
15863 // [`crate::aplicacao::Placement`] / `:entrada`
15864 // [`crate::aplicacao::Entrada`] future outer-composite lifts
15865 // fold on. Peer of the closed M3 outer-composite family the
15866 // sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
15867 // [`crate::AplicacaoSpec::placement`] (9abb8f0) /
15868 // [`crate::AplicacaoSpec::entrada`] (d32111c) composite-
15869 // reference accessor pins already carry on the outer
15870 // [`crate::AplicacaoSpec`] altitude — extends the outer-
15871 // accessor byte-equal-projection discipline onto the outer
15872 // top-level [`Caixa`] M2 Servico-runtime slot altitude.
15873 use crate::LimitsSpec;
15874 use std::time::Duration;
15875 let fixtures: Vec<Option<LimitsSpec>> = vec![
15876 None,
15877 Some(LimitsSpec::default()),
15878 Some(LimitsSpec {
15879 memory: Some(64 * 1024 * 1024),
15880 ..Default::default()
15881 }),
15882 Some(LimitsSpec {
15883 memory: Some(64 * 1024 * 1024),
15884 fuel: Some(1_000_000),
15885 wall_clock: Some(Duration::from_secs(30)),
15886 cpu: Some(500),
15887 }),
15888 ];
15889 for limits in fixtures {
15890 let c = caixa_with_limits(limits.clone());
15891 assert_eq!(
15892 c.limits(),
15893 limits.as_ref(),
15894 "Caixa::limits must return :limits verbatim (got {:?}, \
15895 expected {:?})",
15896 c.limits(),
15897 limits.as_ref(),
15898 );
15899 match (c.limits(), c.limits.as_ref()) {
15900 (Some(a), Some(b)) => assert!(
15901 std::ptr::eq(a, b),
15902 "Caixa::limits accessor and self.limits.as_ref() \
15903 field access must borrow the same backing storage \
15904 — the accessor is the substrate-primitive typed \
15905 dispatch every downstream Servico-M2-overlay \
15906 composite consumer must route through, and a \
15907 reference-identity split would silently break \
15908 every consumer that relied on the borrow sharing \
15909 the composite's storage",
15910 ),
15911 (None, None) => {}
15912 _ => panic!(
15913 "Caixa::limits presence bit must byte-equal \
15914 self.limits.is_some() — a presence-bit drift would \
15915 silently split the paired StandardLayout::verify \
15916 per-`:limits` shape gate's traversal head from \
15917 the peer render::servico_m2_overlay M2 overlay \
15918 emitter's traversal head from the peer \
15919 Caixa::declared_servico_slots M2 declared-slot \
15920 enumerator's presence probe",
15921 ),
15922 }
15923 assert_eq!(
15924 c.limits().is_some(),
15925 c.limits.is_some(),
15926 "Caixa::limits().is_some() must byte-equal \
15927 self.limits.is_some() — a presence-bit drift would \
15928 silently split every downstream Option<&LimitsSpec> \
15929 consumer's partition on the engine-default arm",
15930 );
15931 }
15932 }
15933
15934 #[test]
15935 fn declared_servico_slots_limits_arm_routes_through_accessor() {
15936 // Composition pin: [`Caixa::declared_servico_slots`]'s
15937 // `:limits` presence-probe arm must key off [`Caixa::limits`],
15938 // not the raw `self.limits.is_some()` field-probe. Structurally:
15939 // a `Caixa { limits: Some(LimitsSpec::default()), .. }` must
15940 // still push `M2_AUTHOR_KEY_LIMITS` onto the declared-slot list
15941 // (the presence bit is `Some`, so the M2 kind-coherence gate
15942 // must surface the slot as "declared" even when every per-axis
15943 // cap is unset), and a `Caixa { limits: None, .. }` must NOT
15944 // push the label (the "author omitted the slot entirely"
15945 // partition). The pair jointly pins the accessor + declared-
15946 // slot enumerator composition: any future silent detour that
15947 // had the accessor collapse `Some(LimitsSpec::default())` to
15948 // `None` (a `.filter(|l| !l.is_empty())` projection) would
15949 // silently absorb the "declared but empty" arm at the
15950 // accessor boundary and the [`crate::LayoutError::ServicoSlotsOnNonServico`]
15951 // kind-coherence gate would silently accept a
15952 // struct-literal `Caixa` carrying the drift.
15953 //
15954 // Peer of the sibling per-`Caixa`
15955 // `validate_deps_duplicate_arm_routes_through_accessor` (ad34b4e)
15956 // and `validate_deps_duplicate_deps_dev_arm_routes_through_accessor`
15957 // (f7fd81e) accessor-composition pins on the sibling `:deps` /
15958 // `:deps-dev` outer-`&[Dep]`-composition axes — same "the
15959 // enumerator gate must route through the substrate-primitive
15960 // typed dispatch" discipline extended onto the outer top-level
15961 // [`Caixa`] `Option<&LimitsSpec>`-composition surface, opening
15962 // the outer-`Caixa` M2 Servico-runtime-slot arm of the
15963 // composition-pin family.
15964 use crate::LimitsSpec;
15965 let c = caixa_with_limits(Some(LimitsSpec::default()));
15966 let slots = c.declared_servico_slots();
15967 assert!(
15968 slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
15969 "declared_servico_slots must push M2_AUTHOR_KEY_LIMITS \
15970 when `:limits` is Some (even for LimitsSpec::default()) \
15971 — the accessor and the enumerator gate must route through \
15972 the same substrate-primitive typed dispatch on the outer \
15973 :limits presence bit (got slots={slots:?})",
15974 );
15975 let c = caixa_with_limits(None);
15976 let slots = c.declared_servico_slots();
15977 assert!(
15978 !slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
15979 "declared_servico_slots must NOT push M2_AUTHOR_KEY_LIMITS \
15980 when `:limits` is None — the author-omitted arm must \
15981 route through the accessor's None-return unchanged (got \
15982 slots={slots:?})",
15983 );
15984 }
15985
15986 #[test]
15987 fn servico_m2_overlay_limits_arm_routes_through_accessor() {
15988 // Composition pin: [`crate::render::servico_m2_overlay`]'s
15989 // per-`:limits` M2 overlay emit arm must key off
15990 // [`Caixa::limits`], not the raw `&caixa.limits` field-borrow.
15991 // Structurally: a `Caixa { limits: Some(LimitsSpec { memory:
15992 // Some(64 MiB), .. default }), .. }` must surface the
15993 // `M2_KEY_LIMITS` key with the per-axis
15994 // `memory: "64MiB"` sub-mapping in the overlay, a `Caixa {
15995 // limits: Some(LimitsSpec::default()), .. }` must omit the
15996 // key entirely (the `.is_empty()`-gated inner arm elides an
15997 // empty composite even when the outer presence bit is `Some`),
15998 // and a `Caixa { limits: None, .. }` must also omit the key
15999 // (the "author omitted the slot entirely" partition). The
16000 // three-fixture family jointly pins the accessor + M2 overlay
16001 // emitter composition: any future silent detour that had the
16002 // accessor return a fresh-cloned copy on the `Some` arm (a
16003 // `LimitsSpec::clone()` projection) would silently break the
16004 // reference-identity pin the peer per-axis
16005 // `serde_yaml::to_value(limits)` projection reads from.
16006 use crate::LimitsSpec;
16007 use crate::render::{M2_KEY_LIMITS, servico_m2_overlay};
16008 let c = caixa_with_limits(Some(LimitsSpec {
16009 memory: Some(64 * 1024 * 1024),
16010 ..Default::default()
16011 }));
16012 let overlay = servico_m2_overlay(&c).unwrap();
16013 assert!(
16014 overlay.contains_key(M2_KEY_LIMITS),
16015 "servico_m2_overlay must surface M2_KEY_LIMITS when \
16016 `:limits` carries a non-empty composite — the accessor \
16017 and the M2 overlay emitter must route through the same \
16018 substrate-primitive typed dispatch on the outer :limits \
16019 composite (got overlay={overlay:?})",
16020 );
16021 let c = caixa_with_limits(Some(LimitsSpec::default()));
16022 let overlay = servico_m2_overlay(&c).unwrap();
16023 assert!(
16024 !overlay.contains_key(M2_KEY_LIMITS),
16025 "servico_m2_overlay must omit M2_KEY_LIMITS when \
16026 `:limits` is Some(LimitsSpec::default()) — the empty \
16027 composite's `.is_empty()`-gated inner arm must elide \
16028 the key regardless of the outer presence bit (got \
16029 overlay={overlay:?})",
16030 );
16031 let c = caixa_with_limits(None);
16032 let overlay = servico_m2_overlay(&c).unwrap();
16033 assert!(
16034 !overlay.contains_key(M2_KEY_LIMITS),
16035 "servico_m2_overlay must omit M2_KEY_LIMITS when \
16036 `:limits` is None — the author-omitted arm must route \
16037 through the accessor's None-return unchanged (got \
16038 overlay={overlay:?})",
16039 );
16040 }
16041
16042 #[test]
16043 fn limits_projects_option_ref_by_borrow() {
16044 // The by-borrow pin: [`Caixa::limits`] returns
16045 // `Option<&LimitsSpec>` by borrow — the returned reference
16046 // borrows the underlying `Option<LimitsSpec>` storage of the
16047 // `:limits` slot and the accessor must not clone the backing
16048 // composite on every call. Peer of the sibling
16049 // `deps_projects_slice_by_borrow` (ad34b4e) /
16050 // `deps_dev_projects_slice_by_borrow` (f7fd81e) by-borrow pins
16051 // on the outer top-level [`Caixa`] `&[Dep]`-return axes —
16052 // extended here to the outer [`Caixa`] `Option<&Composite>`-
16053 // return axis: the accessor's returned reference must borrow
16054 // from `&self` (the returned reference's lifetime is tied to
16055 // `&self`), and calling the accessor twice on the same
16056 // [`Caixa`] must yield references that are pointer-equal (the
16057 // underlying byte-buffer is the storage `LimitsSpec`'s
16058 // allocation, not a fresh copy) as well as value-equal
16059 // (idempotent, no side effects on `&self`).
16060 //
16061 // Pins against a future silent detour that returned an owned
16062 // `LimitsSpec` (which would type-check via the `Clone` impl
16063 // but silently clone on every call), a `&LimitsSpec` panic-
16064 // return on the `None` arm (which would collapse the load-
16065 // bearing `Option` presence-bit into a runtime panic), or a
16066 // one-arm-only accessor that returned a saturating composite
16067 // on some sentinel input.
16068 use crate::LimitsSpec;
16069 use std::time::Duration;
16070 for limits in [
16071 Some(LimitsSpec::default()),
16072 Some(LimitsSpec {
16073 memory: Some(64 * 1024 * 1024),
16074 fuel: Some(1_000_000),
16075 wall_clock: Some(Duration::from_secs(30)),
16076 cpu: Some(500),
16077 }),
16078 ] {
16079 let c = caixa_with_limits(limits.clone());
16080 let first = c.limits().unwrap();
16081 let second = c.limits().unwrap();
16082 assert_eq!(
16083 first, second,
16084 "Caixa::limits must be idempotent — two successive \
16085 calls on the same &self must return the same \
16086 &LimitsSpec",
16087 );
16088 assert!(
16089 std::ptr::eq(first, second),
16090 "Caixa::limits must borrow the underlying \
16091 Option<LimitsSpec> storage — two successive calls \
16092 must return references with the same backing pointer \
16093 (a fresh LimitsSpec clone would change the pointer \
16094 on every call)",
16095 );
16096 assert_eq!(
16097 Some(first),
16098 limits.as_ref(),
16099 "Caixa::limits must return :limits verbatim by borrow \
16100 — got {first:?}, expected {:?}",
16101 limits.as_ref(),
16102 );
16103 }
16104 let c = caixa_with_limits(None);
16105 assert!(
16106 c.limits().is_none(),
16107 "Caixa::limits must return None when :limits is absent — \
16108 the author-omitted arm must project through the \
16109 accessor's Option::None unchanged",
16110 );
16111 }
16112
16113 // ── Caixa::behavior — outer top-level Option<&BehaviorSpec> composite-reference accessor ──
16114
16115 fn caixa_with_behavior(behavior: Option<crate::BehaviorSpec>) -> Caixa {
16116 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16117 c.behavior = behavior;
16118 c
16119 }
16120
16121 #[test]
16122 fn behavior_returns_behavior_option_ref_verbatim_across_permutations() {
16123 // The canonical per-`Caixa` `:behavior` M2 typed-slot outer-
16124 // composite optional-composite-reference-shape pin:
16125 // [`Caixa::behavior`] must return the `:behavior` typed
16126 // `Option<BehaviorSpec>` verbatim as an `Option<&BehaviorSpec>`
16127 // reference over the same backing storage the raw
16128 // `self.behavior.as_ref()` field access borrows from, byte-equal
16129 // across every representative fixture in the accept-set — the
16130 // author-omitted `None` shape (the "runtime-default applies"
16131 // partition every downstream Servico M2 overlay emitter treats
16132 // as "emit nothing"), the empty-composite `Some(BehaviorSpec {
16133 // .. default })` shape ([`BehaviorSpec::is_empty`] holds —
16134 // every per-callback path is `None`, so the peer M2 overlay
16135 // emitter's `.is_empty()`-gated projection still emits nothing
16136 // but the outer presence-bit is `Some`, so
16137 // [`Caixa::declared_servico_slots`] still pushes the
16138 // `M2_AUTHOR_KEY_BEHAVIOR` label), a single-callback fixture
16139 // (only `:on-state-change` set — the canonical shape a caixa
16140 // that only wires the hot-upgrade migration path carries), and
16141 // a fully-populated composite (every per-callback path set —
16142 // the canonical shape a fully-instrumented gen_server-shaped
16143 // Servico carries).
16144 //
16145 // Peer of the sibling
16146 // `limits_returns_limits_option_ref_verbatim_across_permutations`
16147 // (b2bd9d7) opening fixture-family + reference-identity +
16148 // presence-bit tetrad pin on the outer top-level [`Caixa`]
16149 // `Option<&Composite>`-return sub-family — extended here to the
16150 // second axis of that sub-family so both of the currently-lifted
16151 // M2 Servico-runtime `Option<&Composite>` slots (`:limits` /
16152 // `:behavior`) carry the same "byte-equal, borrow-shared,
16153 // presence-bit-preserved" outer-accessor discipline.
16154 //
16155 // Pins against a future silent detour that returned a fresh-
16156 // cloned [`crate::BehaviorSpec`] copy (which would type-check
16157 // via the `Clone` impl but silently break every downstream
16158 // caller that relied on the reference sharing the composite's
16159 // backing identity), a reference to an operator-resolved
16160 // overlay (a future per-cluster `:behavior-overrides` slot —
16161 // its resolution must land at exactly this accessor body, not
16162 // silently divert the raw slot away from a second consumer), a
16163 // `None` → `Some(BehaviorSpec::default)` cluster-default
16164 // projection (which would collapse the load-bearing
16165 // "author-omitted `:behavior` ⇒ runtime-default applies"
16166 // partition the peer [`crate::render::servico_m2_overlay`]
16167 // emitter, the peer [`Caixa::declared_servico_slots`]
16168 // enumerator, and the cross-slot
16169 // [`crate::upgrade::validate_upgrade_from_against_behavior`]
16170 // gate all read), or a callback-shuffled projection (a future
16171 // detour that swapped `on_init` and `on_terminate` through the
16172 // accessor would silently split the paired
16173 // [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
16174 // traversal input from the peer `servico_m2_overlay` emitter's
16175 // projection input from the cross-slot `:state-change`
16176 // composition gate's traversal input).
16177 use crate::BehaviorSpec;
16178 use std::path::PathBuf;
16179 let fixtures: Vec<Option<BehaviorSpec>> = vec![
16180 None,
16181 Some(BehaviorSpec::default()),
16182 Some(BehaviorSpec {
16183 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
16184 ..Default::default()
16185 }),
16186 Some(BehaviorSpec {
16187 on_init: Some(PathBuf::from("lib/init.lisp")),
16188 on_call: Some(PathBuf::from("lib/handlers.lisp")),
16189 on_cast: Some(PathBuf::from("lib/handlers.lisp")),
16190 on_info: Some(PathBuf::from("lib/handlers.lisp")),
16191 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
16192 on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
16193 }),
16194 ];
16195 for behavior in fixtures {
16196 let c = caixa_with_behavior(behavior.clone());
16197 assert_eq!(
16198 c.behavior(),
16199 behavior.as_ref(),
16200 "Caixa::behavior must return :behavior verbatim (got \
16201 {:?}, expected {:?})",
16202 c.behavior(),
16203 behavior.as_ref(),
16204 );
16205 match (c.behavior(), c.behavior.as_ref()) {
16206 (Some(a), Some(b)) => assert!(
16207 std::ptr::eq(a, b),
16208 "Caixa::behavior accessor and self.behavior.as_ref() \
16209 field access must borrow the same backing storage \
16210 — the accessor is the substrate-primitive typed \
16211 dispatch every downstream Servico-M2-overlay \
16212 composite consumer must route through, and a \
16213 reference-identity split would silently break \
16214 every consumer that relied on the borrow sharing \
16215 the composite's storage",
16216 ),
16217 (None, None) => {}
16218 _ => panic!(
16219 "Caixa::behavior presence bit must byte-equal \
16220 self.behavior.is_some() — a presence-bit drift \
16221 would silently split the paired \
16222 StandardLayout::verify per-`:behavior` shape \
16223 gate's traversal head from the peer \
16224 render::servico_m2_overlay M2 overlay emitter's \
16225 traversal head from the cross-slot \
16226 validate_upgrade_from_against_behavior \
16227 composition gate's traversal head from the peer \
16228 Caixa::declared_servico_slots M2 declared-slot \
16229 enumerator's presence probe",
16230 ),
16231 }
16232 assert_eq!(
16233 c.behavior().is_some(),
16234 c.behavior.is_some(),
16235 "Caixa::behavior().is_some() must byte-equal \
16236 self.behavior.is_some() — a presence-bit drift would \
16237 silently split every downstream Option<&BehaviorSpec> \
16238 consumer's partition on the runtime-default arm",
16239 );
16240 }
16241 }
16242
16243 #[test]
16244 fn declared_servico_slots_behavior_arm_routes_through_accessor() {
16245 // Composition pin: [`Caixa::declared_servico_slots`]'s
16246 // `:behavior` presence-probe arm must key off
16247 // [`Caixa::behavior`], not the raw `self.behavior.is_some()`
16248 // field-probe. Structurally: a `Caixa { behavior:
16249 // Some(BehaviorSpec::default()), .. }` must still push
16250 // `M2_AUTHOR_KEY_BEHAVIOR` onto the declared-slot list (the
16251 // presence bit is `Some`, so the M2 kind-coherence gate must
16252 // surface the slot as "declared" even when every per-callback
16253 // path is unset), and a `Caixa { behavior: None, .. }` must
16254 // NOT push the label (the "author omitted the slot entirely"
16255 // partition). The pair jointly pins the accessor + declared-
16256 // slot enumerator composition: any future silent detour that
16257 // had the accessor collapse `Some(BehaviorSpec::default())`
16258 // to `None` (a `.filter(|b| !b.is_empty())` projection) would
16259 // silently absorb the "declared but empty" arm at the
16260 // accessor boundary and the
16261 // [`crate::LayoutError::ServicoSlotsOnNonServico`]
16262 // kind-coherence gate would silently accept a struct-literal
16263 // `Caixa` carrying the drift.
16264 //
16265 // Peer of the sibling
16266 // `declared_servico_slots_limits_arm_routes_through_accessor`
16267 // (b2bd9d7) composition pin on the sibling `:limits` outer-
16268 // `Option<&LimitsSpec>` arm of the same
16269 // [`Caixa::declared_servico_slots`] M2 declared-slot
16270 // enumerator's traversal — same "the enumerator gate must
16271 // route through the substrate-primitive typed dispatch"
16272 // discipline extended onto the outer top-level [`Caixa`]
16273 // `Option<&BehaviorSpec>`-composition surface.
16274 use crate::BehaviorSpec;
16275 let c = caixa_with_behavior(Some(BehaviorSpec::default()));
16276 let slots = c.declared_servico_slots();
16277 assert!(
16278 slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
16279 "declared_servico_slots must push M2_AUTHOR_KEY_BEHAVIOR \
16280 when `:behavior` is Some (even for BehaviorSpec::default()) \
16281 — the accessor and the enumerator gate must route through \
16282 the same substrate-primitive typed dispatch on the outer \
16283 :behavior presence bit (got slots={slots:?})",
16284 );
16285 let c = caixa_with_behavior(None);
16286 let slots = c.declared_servico_slots();
16287 assert!(
16288 !slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
16289 "declared_servico_slots must NOT push M2_AUTHOR_KEY_BEHAVIOR \
16290 when `:behavior` is None — the author-omitted arm must \
16291 route through the accessor's None-return unchanged (got \
16292 slots={slots:?})",
16293 );
16294 }
16295
16296 #[test]
16297 fn servico_m2_overlay_behavior_arm_routes_through_accessor() {
16298 // Composition pin: [`crate::render::servico_m2_overlay`]'s
16299 // per-`:behavior` M2 overlay emit arm must key off
16300 // [`Caixa::behavior`], not the raw `&caixa.behavior`
16301 // field-borrow. Structurally: a `Caixa { behavior:
16302 // Some(BehaviorSpec { on_state_change: Some(...), .. default
16303 // }), .. }` must surface the `M2_KEY_BEHAVIOR` key with the
16304 // per-callback `onStateChange` sub-mapping in the overlay, a
16305 // `Caixa { behavior: Some(BehaviorSpec::default()), .. }`
16306 // must omit the key entirely (the `.is_empty()`-gated inner
16307 // arm elides an empty composite even when the outer presence
16308 // bit is `Some`), and a `Caixa { behavior: None, .. }` must
16309 // also omit the key (the "author omitted the slot entirely"
16310 // partition). The three-fixture family jointly pins the
16311 // accessor + M2 overlay emitter composition: any future
16312 // silent detour that had the accessor return a fresh-cloned
16313 // copy on the `Some` arm (a `BehaviorSpec::clone()`
16314 // projection) would silently break the reference-identity
16315 // pin the peer per-callback `serde_yaml::to_value(behavior)`
16316 // projection reads from.
16317 //
16318 // Peer of the sibling
16319 // `servico_m2_overlay_limits_arm_routes_through_accessor`
16320 // (b2bd9d7) composition pin on the sibling `:limits` outer-
16321 // `Option<&LimitsSpec>` arm of the same
16322 // [`crate::render::servico_m2_overlay`] M2 overlay emitter's
16323 // traversal — same "the emitter must route through the
16324 // substrate-primitive typed dispatch on the outer composite"
16325 // discipline extended onto the outer top-level [`Caixa`]
16326 // `Option<&BehaviorSpec>`-composition surface.
16327 use crate::BehaviorSpec;
16328 use crate::render::{M2_KEY_BEHAVIOR, servico_m2_overlay};
16329 use std::path::PathBuf;
16330 let c = caixa_with_behavior(Some(BehaviorSpec {
16331 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
16332 ..Default::default()
16333 }));
16334 let overlay = servico_m2_overlay(&c).unwrap();
16335 assert!(
16336 overlay.contains_key(M2_KEY_BEHAVIOR),
16337 "servico_m2_overlay must surface M2_KEY_BEHAVIOR when \
16338 `:behavior` carries a non-empty composite — the accessor \
16339 and the M2 overlay emitter must route through the same \
16340 substrate-primitive typed dispatch on the outer :behavior \
16341 composite (got overlay={overlay:?})",
16342 );
16343 let c = caixa_with_behavior(Some(BehaviorSpec::default()));
16344 let overlay = servico_m2_overlay(&c).unwrap();
16345 assert!(
16346 !overlay.contains_key(M2_KEY_BEHAVIOR),
16347 "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
16348 `:behavior` is Some(BehaviorSpec::default()) — the empty \
16349 composite's `.is_empty()`-gated inner arm must elide the \
16350 key regardless of the outer presence bit (got \
16351 overlay={overlay:?})",
16352 );
16353 let c = caixa_with_behavior(None);
16354 let overlay = servico_m2_overlay(&c).unwrap();
16355 assert!(
16356 !overlay.contains_key(M2_KEY_BEHAVIOR),
16357 "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
16358 `:behavior` is None — the author-omitted arm must route \
16359 through the accessor's None-return unchanged (got \
16360 overlay={overlay:?})",
16361 );
16362 }
16363
16364 #[test]
16365 fn behavior_projects_option_ref_by_borrow() {
16366 // The by-borrow pin: [`Caixa::behavior`] returns
16367 // `Option<&BehaviorSpec>` by borrow — the returned reference
16368 // borrows the underlying `Option<BehaviorSpec>` storage of the
16369 // `:behavior` slot and the accessor must not clone the backing
16370 // composite on every call. Peer of the sibling
16371 // `limits_projects_option_ref_by_borrow` (b2bd9d7) by-borrow
16372 // pin on the outer top-level [`Caixa`] `Option<&Composite>`-
16373 // return sub-family — extended here to the second axis of the
16374 // same sub-family: the accessor's returned reference must
16375 // borrow from `&self` (the returned reference's lifetime is
16376 // tied to `&self`), and calling the accessor twice on the same
16377 // [`Caixa`] must yield references that are pointer-equal (the
16378 // underlying byte-buffer is the storage `BehaviorSpec`'s
16379 // allocation, not a fresh copy) as well as value-equal
16380 // (idempotent, no side effects on `&self`).
16381 //
16382 // Pins against a future silent detour that returned an owned
16383 // `BehaviorSpec` (which would type-check via the `Clone` impl
16384 // but silently clone on every call), a `&BehaviorSpec` panic-
16385 // return on the `None` arm (which would collapse the load-
16386 // bearing `Option` presence-bit into a runtime panic), or a
16387 // one-arm-only accessor that returned a saturating composite
16388 // on some sentinel input.
16389 use crate::BehaviorSpec;
16390 use std::path::PathBuf;
16391 for behavior in [
16392 Some(BehaviorSpec::default()),
16393 Some(BehaviorSpec {
16394 on_init: Some(PathBuf::from("lib/init.lisp")),
16395 on_call: Some(PathBuf::from("lib/handlers.lisp")),
16396 on_cast: Some(PathBuf::from("lib/handlers.lisp")),
16397 on_info: Some(PathBuf::from("lib/handlers.lisp")),
16398 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
16399 on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
16400 }),
16401 ] {
16402 let c = caixa_with_behavior(behavior.clone());
16403 let first = c.behavior().unwrap();
16404 let second = c.behavior().unwrap();
16405 assert_eq!(
16406 first, second,
16407 "Caixa::behavior must be idempotent — two successive \
16408 calls on the same &self must return the same \
16409 &BehaviorSpec",
16410 );
16411 assert!(
16412 std::ptr::eq(first, second),
16413 "Caixa::behavior must borrow the underlying \
16414 Option<BehaviorSpec> storage — two successive calls \
16415 must return references with the same backing pointer \
16416 (a fresh BehaviorSpec clone would change the pointer \
16417 on every call)",
16418 );
16419 assert_eq!(
16420 Some(first),
16421 behavior.as_ref(),
16422 "Caixa::behavior must return :behavior verbatim by \
16423 borrow — got {first:?}, expected {:?}",
16424 behavior.as_ref(),
16425 );
16426 }
16427 let c = caixa_with_behavior(None);
16428 assert!(
16429 c.behavior().is_none(),
16430 "Caixa::behavior must return None when :behavior is absent \
16431 — the author-omitted arm must project through the \
16432 accessor's Option::None unchanged",
16433 );
16434 }
16435
16436 // ── Caixa::politicas — outer top-level Option<&MeshPolicy> composite-reference accessor ──
16437
16438 fn caixa_aplicacao_with_politicas(politicas: Option<crate::aplicacao::MeshPolicy>) -> Caixa {
16439 use crate::aplicacao::{Membro, WitContract};
16440 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16441 c.kind = CaixaKind::Aplicacao;
16442 c.membros = vec![Membro {
16443 caixa: "a".into(),
16444 versao: "^0.1".into(),
16445 }];
16446 c.contratos = vec![WitContract {
16447 de: "a".into(),
16448 para: "a".into(),
16449 wit: "wasi:http/proxy".into(),
16450 endpoint: Some("/x".into()),
16451 subject: None,
16452 slot: None,
16453 }];
16454 c.politicas = politicas;
16455 c
16456 }
16457
16458 #[test]
16459 fn politicas_returns_politicas_option_ref_verbatim_across_permutations() {
16460 // The canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
16461 // composite optional-composite-reference-shape pin:
16462 // [`Caixa::politicas`] must return the `:politicas` typed
16463 // `Option<MeshPolicy>` verbatim as an `Option<&MeshPolicy>`
16464 // reference over the same backing storage the raw
16465 // `self.politicas.as_ref()` field access borrows from,
16466 // byte-equal across every representative fixture in the
16467 // accept-set — the author-omitted `None` shape (the "cluster-
16468 // default applies" partition every downstream mesh-artifact
16469 // emitter treats as "emit no `:politicas` overlay"), the
16470 // empty-composite `Some(MeshPolicy { .. default })` shape
16471 // ([`crate::aplicacao::MeshPolicy::is_empty`] holds — every
16472 // per-axis mesh-policy scalar is `None`, so the peer inner
16473 // [`crate::AplicacaoSpec::politicas`] `.is_empty()`-gated
16474 // caixa-mesh overlay elides every per-axis emit but the outer
16475 // presence-bit is `Some`, so [`Caixa::declared_mesh_slots`]
16476 // still pushes the `M3_AUTHOR_KEY_POLITICAS` label), a
16477 // single-axis fixture (only `:timeout` set — the canonical
16478 // shape a latency-sensitive Aplicacao carries), and a
16479 // fully-populated composite (every per-axis mesh-policy
16480 // scalar set — the canonical shape a fully-governed
16481 // Aplicacao carries).
16482 //
16483 // Pins against a future silent detour that returned a fresh-
16484 // cloned [`crate::aplicacao::MeshPolicy`] copy (which would
16485 // type-check via the `Clone` impl but silently break every
16486 // downstream caller that relied on the reference sharing the
16487 // composite's backing identity), a reference to an operator-
16488 // resolved overlay (the future per-cluster
16489 // `:politicas-overrides` slot — its resolution must land at
16490 // exactly this accessor body, not silently divert the raw
16491 // slot away from the peer [`Caixa::declared_mesh_slots`]
16492 // enumerator's presence probe), a
16493 // `None` → `Some(MeshPolicy::default)` cluster-default
16494 // projection (which would collapse the load-bearing
16495 // "author-omitted `:politicas` ⇒ cluster-default applies"
16496 // partition the peer [`Caixa::declared_mesh_slots`]
16497 // enumerator and the peer [`Caixa::aplicacao_view`]
16498 // Aplicacao-composition seed both read), or an axis-shuffled
16499 // projection (a future detour that swapped `timeout` and
16500 // `retries` through the accessor would silently split the
16501 // paired [`Caixa::aplicacao_view`] seed's fold input from the
16502 // sibling M3 mesh-artifact emitter's projection input).
16503 //
16504 // Third outer top-level [`Caixa`] `Option<&Composite>`-return
16505 // composite-reference accessor pin on the substrate primitive
16506 // — peer of the sibling
16507 // `limits_returns_limits_option_ref_verbatim_across_permutations`
16508 // (b2bd9d7) and
16509 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
16510 // (35d8b52) opening tetrad pins on the outer top-level
16511 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
16512 // here to the first of the three M3 mesh-slot axes so the
16513 // opening third of the outer `Option<&Composite>` sub-family
16514 // carries the same "byte-equal, borrow-shared, presence-bit-
16515 // preserved" outer-accessor discipline.
16516 use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
16517 use std::time::Duration;
16518 let fixtures: Vec<Option<MeshPolicy>> = vec![
16519 None,
16520 Some(MeshPolicy::default()),
16521 Some(MeshPolicy {
16522 timeout: Some(Duration::from_secs(30)),
16523 ..Default::default()
16524 }),
16525 Some(MeshPolicy {
16526 timeout: Some(Duration::from_secs(30)),
16527 retries: Some(3),
16528 circuit_breaker: Some(CircuitBreaker {
16529 max_failures: 5,
16530 window: Duration::from_secs(60),
16531 }),
16532 mtls_required: Some(true),
16533 rate_limit: Some(RateLimit {
16534 rate: 100,
16535 window: Duration::from_secs(1),
16536 }),
16537 }),
16538 ];
16539 for politicas in fixtures {
16540 let c = caixa_aplicacao_with_politicas(politicas.clone());
16541 assert_eq!(
16542 c.politicas(),
16543 politicas.as_ref(),
16544 "Caixa::politicas must return :politicas verbatim (got \
16545 {:?}, expected {:?})",
16546 c.politicas(),
16547 politicas.as_ref(),
16548 );
16549 match (c.politicas(), c.politicas.as_ref()) {
16550 (Some(a), Some(b)) => assert!(
16551 std::ptr::eq(a, b),
16552 "Caixa::politicas accessor and self.politicas.as_ref() \
16553 field access must borrow the same backing storage \
16554 — the accessor is the substrate-primitive typed \
16555 dispatch every downstream Aplicacao-mesh-overlay \
16556 composite consumer must route through, and a \
16557 reference-identity split would silently break \
16558 every consumer that relied on the borrow sharing \
16559 the composite's storage",
16560 ),
16561 (None, None) => {}
16562 _ => panic!(
16563 "Caixa::politicas presence bit must byte-equal \
16564 self.politicas.is_some() — a presence-bit drift \
16565 would silently split the paired \
16566 Caixa::aplicacao_view Aplicacao-composition seed's \
16567 traversal head from the peer \
16568 Caixa::declared_mesh_slots M3 declared-slot \
16569 enumerator's presence probe",
16570 ),
16571 }
16572 assert_eq!(
16573 c.politicas().is_some(),
16574 c.politicas.is_some(),
16575 "Caixa::politicas().is_some() must byte-equal \
16576 self.politicas.is_some() — a presence-bit drift would \
16577 silently split every downstream Option<&MeshPolicy> \
16578 consumer's partition on the cluster-default arm",
16579 );
16580 }
16581 }
16582
16583 #[test]
16584 fn declared_mesh_slots_politicas_arm_routes_through_accessor() {
16585 // Composition pin: [`Caixa::declared_mesh_slots`]'s
16586 // `:politicas` presence-probe arm must key off
16587 // [`Caixa::politicas`], not the raw `self.politicas.is_some()`
16588 // field-probe. Structurally: a `Caixa { politicas:
16589 // Some(MeshPolicy::default()), .. }` must still push
16590 // `M3_AUTHOR_KEY_POLITICAS` onto the declared-slot list (the
16591 // presence bit is `Some`, so the M3 kind-coherence gate must
16592 // surface the slot as "declared" even when every per-axis
16593 // scalar is unset), and a `Caixa { politicas: None, .. }` must
16594 // NOT push the label (the "author omitted the slot entirely"
16595 // partition). The pair jointly pins the accessor + declared-
16596 // slot enumerator composition: any future silent detour that
16597 // had the accessor collapse `Some(MeshPolicy::default())` to
16598 // `None` (a `.filter(|p| !p.is_empty())` projection) would
16599 // silently absorb the "declared but empty" arm at the
16600 // accessor boundary and the
16601 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
16602 // coherence gate would silently accept a struct-literal
16603 // `Caixa` carrying the drift.
16604 //
16605 // Peer of the sibling
16606 // `declared_servico_slots_limits_arm_routes_through_accessor`
16607 // (b2bd9d7) and
16608 // `declared_servico_slots_behavior_arm_routes_through_accessor`
16609 // (35d8b52) composition pins on the sibling `:limits` /
16610 // `:behavior` outer-`Option<&Composite>` arms of the peer
16611 // [`Caixa::declared_servico_slots`] M2 declared-slot
16612 // enumerator's traversal — same "the enumerator gate must
16613 // route through the substrate-primitive typed dispatch"
16614 // discipline extended onto the outer top-level [`Caixa`] M3
16615 // mesh-slot family so the [`Caixa::declared_mesh_slots`]
16616 // enumerator carries the same routing invariant as its M2
16617 // sibling.
16618 use crate::aplicacao::MeshPolicy;
16619 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
16620 let slots = c.declared_mesh_slots();
16621 assert!(
16622 slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
16623 "declared_mesh_slots must push M3_AUTHOR_KEY_POLITICAS \
16624 when `:politicas` is Some (even for MeshPolicy::default()) \
16625 — the accessor and the enumerator gate must route through \
16626 the same substrate-primitive typed dispatch on the outer \
16627 :politicas presence bit (got slots={slots:?})",
16628 );
16629 let c = caixa_aplicacao_with_politicas(None);
16630 let slots = c.declared_mesh_slots();
16631 assert!(
16632 !slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
16633 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_POLITICAS \
16634 when `:politicas` is None — the author-omitted arm must \
16635 route through the accessor's None-return unchanged (got \
16636 slots={slots:?})",
16637 );
16638 }
16639
16640 #[test]
16641 fn aplicacao_view_politicas_arm_folds_through_accessor() {
16642 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:politicas`
16643 // Aplicacao-composition seed must fold through
16644 // [`Caixa::politicas`], not the raw
16645 // `self.politicas.clone().unwrap_or_default()` field-borrow.
16646 // Structurally: a `Caixa { politicas: Some(MeshPolicy {
16647 // timeout: Some(30s), .. default }), kind: Aplicacao, .. }`
16648 // must surface a projected [`crate::AplicacaoSpec`] whose
16649 // `politicas().timeout()` field byte-equals the outer
16650 // composite's `timeout` scalar (the fold must project the
16651 // authored composite verbatim), a `Caixa { politicas:
16652 // Some(MeshPolicy::default()), kind: Aplicacao, .. }` must
16653 // surface an [`crate::AplicacaoSpec`] whose `politicas()`
16654 // byte-equals [`crate::aplicacao::MeshPolicy::default`] (the
16655 // fold's empty-composite arm collapses to the same default the
16656 // author-omitted arm does), and a `Caixa { politicas: None,
16657 // kind: Aplicacao, .. }` must surface an
16658 // [`crate::AplicacaoSpec`] whose `politicas()` byte-equals
16659 // [`crate::aplicacao::MeshPolicy::default`] (the "author
16660 // omitted the slot entirely" arm folds through the
16661 // `unwrap_or_default` onto the cluster-default). The triad
16662 // jointly pins the accessor + Aplicacao-composition seed
16663 // composition: any future silent detour that had the accessor
16664 // divert the raw slot away from the seed's fold (an operator-
16665 // resolved overlay's default-fold arm silently differing from
16666 // the raw slot's default-fold arm) would silently split the
16667 // build-time mesh-artifact emission gate from the caixa-mesh
16668 // renderer's Aplicacao-view input at the composition boundary.
16669 use crate::aplicacao::MeshPolicy;
16670 use std::time::Duration;
16671 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy {
16672 timeout: Some(Duration::from_secs(30)),
16673 ..Default::default()
16674 }));
16675 let view = c.aplicacao_view().unwrap();
16676 assert_eq!(
16677 view.politicas().timeout(),
16678 Some(Duration::from_secs(30)),
16679 "Caixa::aplicacao_view must fold the authored :politicas \
16680 :timeout scalar through the accessor verbatim onto the \
16681 projected AplicacaoSpec — a future silent detour at the \
16682 seed's fold arm would surface here as a projected-scalar \
16683 drift (got {:?})",
16684 view.politicas().timeout(),
16685 );
16686 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
16687 let view = c.aplicacao_view().unwrap();
16688 assert_eq!(
16689 view.politicas(),
16690 &MeshPolicy::default(),
16691 "Caixa::aplicacao_view must fold Some(MeshPolicy::default()) \
16692 through the accessor onto MeshPolicy::default — the empty- \
16693 composite arm collapses to the same default the author- \
16694 omitted arm does (got {:?})",
16695 view.politicas(),
16696 );
16697 let c = caixa_aplicacao_with_politicas(None);
16698 let view = c.aplicacao_view().unwrap();
16699 assert_eq!(
16700 view.politicas(),
16701 &MeshPolicy::default(),
16702 "Caixa::aplicacao_view must fold None through the accessor's \
16703 unwrap_or_default onto MeshPolicy::default — the author- \
16704 omitted arm must route through the accessor's None-return \
16705 unchanged (got {:?})",
16706 view.politicas(),
16707 );
16708 }
16709
16710 #[test]
16711 fn politicas_projects_option_ref_by_borrow() {
16712 // The by-borrow pin: [`Caixa::politicas`] returns
16713 // `Option<&MeshPolicy>` by borrow — the returned reference
16714 // borrows the underlying `Option<MeshPolicy>` storage of the
16715 // `:politicas` slot and the accessor must not clone the
16716 // backing composite on every call. Peer of the sibling
16717 // `limits_projects_option_ref_by_borrow` (b2bd9d7) and
16718 // `behavior_projects_option_ref_by_borrow` (35d8b52) by-borrow
16719 // pins on the outer top-level [`Caixa`]
16720 // `Option<&Composite>`-return sub-family — extended here to
16721 // the third axis of the same sub-family: the accessor's
16722 // returned reference must borrow from `&self` (the returned
16723 // reference's lifetime is tied to `&self`), and calling the
16724 // accessor twice on the same [`Caixa`] must yield references
16725 // that are pointer-equal (the underlying byte-buffer is the
16726 // storage `MeshPolicy`'s allocation, not a fresh copy) as
16727 // well as value-equal (idempotent, no side effects on
16728 // `&self`).
16729 //
16730 // Pins against a future silent detour that returned an owned
16731 // `MeshPolicy` (which would type-check via the `Clone` impl
16732 // but silently clone on every call), a `&MeshPolicy` panic-
16733 // return on the `None` arm (which would collapse the load-
16734 // bearing `Option` presence-bit into a runtime panic), or a
16735 // one-arm-only accessor that returned a saturating composite
16736 // on some sentinel input.
16737 use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
16738 use std::time::Duration;
16739 for politicas in [
16740 Some(MeshPolicy::default()),
16741 Some(MeshPolicy {
16742 timeout: Some(Duration::from_secs(30)),
16743 retries: Some(3),
16744 circuit_breaker: Some(CircuitBreaker {
16745 max_failures: 5,
16746 window: Duration::from_secs(60),
16747 }),
16748 mtls_required: Some(true),
16749 rate_limit: Some(RateLimit {
16750 rate: 100,
16751 window: Duration::from_secs(1),
16752 }),
16753 }),
16754 ] {
16755 let c = caixa_aplicacao_with_politicas(politicas.clone());
16756 let first = c.politicas().unwrap();
16757 let second = c.politicas().unwrap();
16758 assert_eq!(
16759 first, second,
16760 "Caixa::politicas must be idempotent — two successive \
16761 calls on the same &self must return the same \
16762 &MeshPolicy",
16763 );
16764 assert!(
16765 std::ptr::eq(first, second),
16766 "Caixa::politicas must borrow the underlying \
16767 Option<MeshPolicy> storage — two successive calls \
16768 must return references with the same backing pointer \
16769 (a fresh MeshPolicy clone would change the pointer on \
16770 every call)",
16771 );
16772 assert_eq!(
16773 Some(first),
16774 politicas.as_ref(),
16775 "Caixa::politicas must return :politicas verbatim by \
16776 borrow — got {first:?}, expected {:?}",
16777 politicas.as_ref(),
16778 );
16779 }
16780 let c = caixa_aplicacao_with_politicas(None);
16781 assert!(
16782 c.politicas().is_none(),
16783 "Caixa::politicas must return None when :politicas is \
16784 absent — the author-omitted arm must project through the \
16785 accessor's Option::None unchanged",
16786 );
16787 }
16788
16789 // ── Caixa::placement — outer top-level Option<&Placement> composite-reference accessor ──
16790
16791 fn caixa_aplicacao_with_placement(placement: Option<crate::aplicacao::Placement>) -> Caixa {
16792 use crate::aplicacao::{Membro, WitContract};
16793 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16794 c.kind = CaixaKind::Aplicacao;
16795 c.membros = vec![Membro {
16796 caixa: "a".into(),
16797 versao: "^0.1".into(),
16798 }];
16799 c.contratos = vec![WitContract {
16800 de: "a".into(),
16801 para: "a".into(),
16802 wit: "wasi:http/proxy".into(),
16803 endpoint: Some("/x".into()),
16804 subject: None,
16805 slot: None,
16806 }];
16807 c.placement = placement;
16808 c
16809 }
16810
16811 #[test]
16812 fn placement_returns_placement_option_ref_verbatim_across_permutations() {
16813 // The canonical per-`Caixa` `:placement` M3 mesh-slot outer-
16814 // composite optional-composite-reference-shape pin:
16815 // [`Caixa::placement`] must return the `:placement` typed
16816 // `Option<Placement>` verbatim as an `Option<&Placement>`
16817 // reference over the same backing storage the raw
16818 // `self.placement.as_ref()` field access borrows from,
16819 // byte-equal across every representative fixture in the
16820 // accept-set — the author-omitted `None` shape (the
16821 // "cluster-default applies" partition every downstream mesh-
16822 // artifact emitter treats as "emit no `:placement` overlay"),
16823 // the empty-composite `Some(Placement { .. default })` shape
16824 // (`estrategia: SingleNode`, empty clusters, no shard-key /
16825 // affinity — the outer presence-bit is `Some` so
16826 // [`Caixa::declared_mesh_slots`] still pushes the
16827 // `M3_AUTHOR_KEY_PLACEMENT` label), a single-axis
16828 // `Replicated`-on-two-clusters fixture (the canonical shape a
16829 // stateless HTTP Aplicacao carries), and a fully-populated
16830 // `Sharded`-with-shard-key-and-affinity fixture (the canonical
16831 // shape a stateful Akka-style cluster-sharding Aplicacao
16832 // carries).
16833 //
16834 // Pins against a future silent detour that returned a fresh-
16835 // cloned [`crate::aplicacao::Placement`] copy (which would
16836 // type-check via the `Clone` impl but silently break every
16837 // downstream caller that relied on the reference sharing the
16838 // composite's backing identity), a reference to an operator-
16839 // resolved overlay (the future per-cluster
16840 // `:placement-overrides` slot — its resolution must land at
16841 // exactly this accessor body, not silently divert the raw
16842 // slot away from the peer [`Caixa::declared_mesh_slots`]
16843 // enumerator's presence probe), a `None` →
16844 // `Some(Placement::default)` cluster-default projection (which
16845 // would collapse the load-bearing "author-omitted `:placement`
16846 // ⇒ cluster-default applies" partition the peer
16847 // [`Caixa::declared_mesh_slots`] enumerator and the peer
16848 // [`Caixa::aplicacao_view`] Aplicacao-composition seed both
16849 // read), or an axis-shuffled projection (a future detour that
16850 // swapped `clusters` and `affinity` through the accessor would
16851 // silently split the paired [`Caixa::aplicacao_view`] seed's
16852 // fold input from the sibling M3 mesh-artifact emitter's
16853 // projection input).
16854 //
16855 // Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
16856 // composite-reference accessor pin on the substrate primitive
16857 // — peer of the sibling
16858 // `limits_returns_limits_option_ref_verbatim_across_permutations`
16859 // (b2bd9d7),
16860 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
16861 // (35d8b52), and
16862 // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
16863 // (5d23d29) opening triad pins on the outer top-level
16864 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
16865 // here to the second of the three M3 mesh-slot axes so the
16866 // opening four-fifths of the outer `Option<&Composite>` sub-
16867 // family carries the same "byte-equal, borrow-shared,
16868 // presence-bit-preserved" outer-accessor discipline.
16869 use crate::aplicacao::{Placement, PlacementStrategy};
16870 let fixtures: Vec<Option<Placement>> = vec![
16871 None,
16872 Some(Placement::default()),
16873 Some(Placement {
16874 estrategia: PlacementStrategy::Replicated,
16875 clusters: vec!["rio".into(), "sao-paulo".into()],
16876 affinity: None,
16877 shard_key: None,
16878 }),
16879 Some(Placement {
16880 estrategia: PlacementStrategy::Sharded,
16881 clusters: vec!["rio".into(), "sao-paulo".into(), "brasilia".into()],
16882 affinity: Some("data-locality".into()),
16883 shard_key: Some("$tenantId".into()),
16884 }),
16885 ];
16886 for placement in fixtures {
16887 let c = caixa_aplicacao_with_placement(placement.clone());
16888 assert_eq!(
16889 c.placement(),
16890 placement.as_ref(),
16891 "Caixa::placement must return :placement verbatim (got \
16892 {:?}, expected {:?})",
16893 c.placement(),
16894 placement.as_ref(),
16895 );
16896 match (c.placement(), c.placement.as_ref()) {
16897 (Some(a), Some(b)) => assert!(
16898 std::ptr::eq(a, b),
16899 "Caixa::placement accessor and self.placement.as_ref() \
16900 field access must borrow the same backing storage \
16901 — the accessor is the substrate-primitive typed \
16902 dispatch every downstream Aplicacao-distribution- \
16903 overlay composite consumer must route through, and \
16904 a reference-identity split would silently break \
16905 every consumer that relied on the borrow sharing \
16906 the composite's storage",
16907 ),
16908 (None, None) => {}
16909 _ => panic!(
16910 "Caixa::placement presence bit must byte-equal \
16911 self.placement.is_some() — a presence-bit drift \
16912 would silently split the paired \
16913 Caixa::aplicacao_view Aplicacao-composition seed's \
16914 traversal head from the peer \
16915 Caixa::declared_mesh_slots M3 declared-slot \
16916 enumerator's presence probe",
16917 ),
16918 }
16919 assert_eq!(
16920 c.placement().is_some(),
16921 c.placement.is_some(),
16922 "Caixa::placement().is_some() must byte-equal \
16923 self.placement.is_some() — a presence-bit drift would \
16924 silently split every downstream Option<&Placement> \
16925 consumer's partition on the cluster-default arm",
16926 );
16927 }
16928 }
16929
16930 #[test]
16931 fn declared_mesh_slots_placement_arm_routes_through_accessor() {
16932 // Composition pin: [`Caixa::declared_mesh_slots`]'s
16933 // `:placement` presence-probe arm must key off
16934 // [`Caixa::placement`], not the raw `self.placement.is_some()`
16935 // field-probe. Structurally: a `Caixa { placement:
16936 // Some(Placement::default()), .. }` must still push
16937 // `M3_AUTHOR_KEY_PLACEMENT` onto the declared-slot list (the
16938 // presence bit is `Some`, so the M3 kind-coherence gate must
16939 // surface the slot as "declared" even when every per-axis
16940 // scalar defers to the cluster-default arm), and a `Caixa {
16941 // placement: None, .. }` must NOT push the label (the "author
16942 // omitted the slot entirely" partition). The pair jointly pins
16943 // the accessor + declared-slot enumerator composition: any
16944 // future silent detour that had the accessor collapse
16945 // `Some(Placement::default())` to `None` (a `.filter(|p|
16946 // p.clusters().is_empty().not())` projection) would silently
16947 // absorb the "declared but empty" arm at the accessor boundary
16948 // and the [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
16949 // kind-coherence gate would silently accept a struct-literal
16950 // `Caixa` carrying the drift.
16951 //
16952 // Peer of the sibling
16953 // `declared_servico_slots_limits_arm_routes_through_accessor`
16954 // (b2bd9d7),
16955 // `declared_servico_slots_behavior_arm_routes_through_accessor`
16956 // (35d8b52), and
16957 // `declared_mesh_slots_politicas_arm_routes_through_accessor`
16958 // (5d23d29) composition pins on the sibling `:limits` /
16959 // `:behavior` / `:politicas` outer-`Option<&Composite>` arms
16960 // — same "the enumerator gate must route through the
16961 // substrate-primitive typed dispatch" discipline extended onto
16962 // the second of the three M3 mesh-slot axes so the
16963 // [`Caixa::declared_mesh_slots`] enumerator carries the same
16964 // routing invariant on the `:placement` arm as the peer
16965 // `:politicas` arm.
16966 use crate::aplicacao::Placement;
16967 let c = caixa_aplicacao_with_placement(Some(Placement::default()));
16968 let slots = c.declared_mesh_slots();
16969 assert!(
16970 slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
16971 "declared_mesh_slots must push M3_AUTHOR_KEY_PLACEMENT \
16972 when `:placement` is Some (even for Placement::default()) \
16973 — the accessor and the enumerator gate must route through \
16974 the same substrate-primitive typed dispatch on the outer \
16975 :placement presence bit (got slots={slots:?})",
16976 );
16977 let c = caixa_aplicacao_with_placement(None);
16978 let slots = c.declared_mesh_slots();
16979 assert!(
16980 !slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
16981 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_PLACEMENT \
16982 when `:placement` is None — the author-omitted arm must \
16983 route through the accessor's None-return unchanged (got \
16984 slots={slots:?})",
16985 );
16986 }
16987
16988 #[test]
16989 fn aplicacao_view_placement_arm_folds_through_accessor() {
16990 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:placement`
16991 // Aplicacao-composition seed must fold through
16992 // [`Caixa::placement`], not the raw
16993 // `self.placement.clone().unwrap_or_default()` field-borrow.
16994 // Structurally: a `Caixa { placement: Some(Placement {
16995 // estrategia: Replicated, clusters: ["rio"], .. default }),
16996 // kind: Aplicacao, .. }` must surface a projected
16997 // [`crate::AplicacaoSpec`] whose `placement().estrategia()` +
16998 // `placement().clusters()` byte-equal the outer composite's
16999 // authored values (the fold must project the authored
17000 // composite verbatim), a `Caixa { placement:
17001 // Some(Placement::default()), kind: Aplicacao, .. }` must
17002 // surface an [`crate::AplicacaoSpec`] whose `placement()`
17003 // byte-equals [`crate::aplicacao::Placement::default`] (the
17004 // fold's empty-composite arm collapses to the same default
17005 // the author-omitted arm does), and a `Caixa { placement:
17006 // None, kind: Aplicacao, .. }` must surface an
17007 // [`crate::AplicacaoSpec`] whose `placement()` byte-equals
17008 // [`crate::aplicacao::Placement::default`] (the "author
17009 // omitted the slot entirely" arm folds through the
17010 // `unwrap_or_default` onto the cluster-default). The triad
17011 // jointly pins the accessor + Aplicacao-composition seed
17012 // composition: any future silent detour that had the accessor
17013 // divert the raw slot away from the seed's fold (an operator-
17014 // resolved overlay's default-fold arm silently differing from
17015 // the raw slot's default-fold arm) would silently split the
17016 // build-time distribution-artifact emission gate from the
17017 // caixa-mesh renderer's Aplicacao-view input at the
17018 // composition boundary.
17019 use crate::aplicacao::{Placement, PlacementStrategy};
17020 let c = caixa_aplicacao_with_placement(Some(Placement {
17021 estrategia: PlacementStrategy::Replicated,
17022 clusters: vec!["rio".into()],
17023 affinity: None,
17024 shard_key: None,
17025 }));
17026 let view = c.aplicacao_view().unwrap();
17027 assert_eq!(
17028 view.placement().estrategia(),
17029 PlacementStrategy::Replicated,
17030 "Caixa::aplicacao_view must fold the authored :placement \
17031 :estrategia scalar through the accessor verbatim onto the \
17032 projected AplicacaoSpec — a future silent detour at the \
17033 seed's fold arm would surface here as a projected-scalar \
17034 drift (got {:?})",
17035 view.placement().estrategia(),
17036 );
17037 assert_eq!(
17038 view.placement().clusters(),
17039 &["rio"],
17040 "Caixa::aplicacao_view must fold the authored :placement \
17041 :clusters list through the accessor verbatim onto the \
17042 projected AplicacaoSpec — a future silent detour at the \
17043 seed's fold arm would surface here as a projected-list \
17044 drift (got {:?})",
17045 view.placement().clusters(),
17046 );
17047 let c = caixa_aplicacao_with_placement(Some(Placement::default()));
17048 let view = c.aplicacao_view().unwrap();
17049 assert_eq!(
17050 view.placement(),
17051 &Placement::default(),
17052 "Caixa::aplicacao_view must fold Some(Placement::default()) \
17053 through the accessor onto Placement::default — the empty- \
17054 composite arm collapses to the same default the author- \
17055 omitted arm does (got {:?})",
17056 view.placement(),
17057 );
17058 let c = caixa_aplicacao_with_placement(None);
17059 let view = c.aplicacao_view().unwrap();
17060 assert_eq!(
17061 view.placement(),
17062 &Placement::default(),
17063 "Caixa::aplicacao_view must fold None through the accessor's \
17064 unwrap_or_default onto Placement::default — the author- \
17065 omitted arm must route through the accessor's None-return \
17066 unchanged (got {:?})",
17067 view.placement(),
17068 );
17069 }
17070
17071 #[test]
17072 fn placement_projects_option_ref_by_borrow() {
17073 // The by-borrow pin: [`Caixa::placement`] returns
17074 // `Option<&Placement>` by borrow — the returned reference
17075 // borrows the underlying `Option<Placement>` storage of the
17076 // `:placement` slot and the accessor must not clone the
17077 // backing composite on every call. Peer of the sibling
17078 // `limits_projects_option_ref_by_borrow` (b2bd9d7),
17079 // `behavior_projects_option_ref_by_borrow` (35d8b52), and
17080 // `politicas_projects_option_ref_by_borrow` (5d23d29) by-borrow
17081 // pins on the outer top-level [`Caixa`]
17082 // `Option<&Composite>`-return sub-family — extended here to
17083 // the fourth axis of the same sub-family: the accessor's
17084 // returned reference must borrow from `&self` (the returned
17085 // reference's lifetime is tied to `&self`), and calling the
17086 // accessor twice on the same [`Caixa`] must yield references
17087 // that are pointer-equal (the underlying byte-buffer is the
17088 // storage `Placement`'s allocation, not a fresh copy) as well
17089 // as value-equal (idempotent, no side effects on `&self`).
17090 //
17091 // Pins against a future silent detour that returned an owned
17092 // `Placement` (which would type-check via the `Clone` impl
17093 // but silently clone on every call), a `&Placement` panic-
17094 // return on the `None` arm (which would collapse the load-
17095 // bearing `Option` presence-bit into a runtime panic), or a
17096 // one-arm-only accessor that returned a saturating composite
17097 // on some sentinel input.
17098 use crate::aplicacao::{Placement, PlacementStrategy};
17099 for placement in [
17100 Some(Placement::default()),
17101 Some(Placement {
17102 estrategia: PlacementStrategy::Sharded,
17103 clusters: vec!["rio".into(), "sao-paulo".into()],
17104 affinity: Some("data-locality".into()),
17105 shard_key: Some("$tenantId".into()),
17106 }),
17107 ] {
17108 let c = caixa_aplicacao_with_placement(placement.clone());
17109 let first = c.placement().unwrap();
17110 let second = c.placement().unwrap();
17111 assert_eq!(
17112 first, second,
17113 "Caixa::placement must be idempotent — two successive \
17114 calls on the same &self must return the same \
17115 &Placement",
17116 );
17117 assert!(
17118 std::ptr::eq(first, second),
17119 "Caixa::placement must borrow the underlying \
17120 Option<Placement> storage — two successive calls \
17121 must return references with the same backing pointer \
17122 (a fresh Placement clone would change the pointer on \
17123 every call)",
17124 );
17125 assert_eq!(
17126 Some(first),
17127 placement.as_ref(),
17128 "Caixa::placement must return :placement verbatim by \
17129 borrow — got {first:?}, expected {:?}",
17130 placement.as_ref(),
17131 );
17132 }
17133 let c = caixa_aplicacao_with_placement(None);
17134 assert!(
17135 c.placement().is_none(),
17136 "Caixa::placement must return None when :placement is \
17137 absent — the author-omitted arm must project through the \
17138 accessor's Option::None unchanged",
17139 );
17140 }
17141
17142 // ── Caixa::entrada — outer top-level Option<&Entrada> composite-reference accessor ──
17143
17144 fn caixa_aplicacao_with_entrada(entrada: Option<crate::aplicacao::Entrada>) -> Caixa {
17145 use crate::aplicacao::{Membro, WitContract};
17146 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17147 c.kind = CaixaKind::Aplicacao;
17148 c.membros = vec![Membro {
17149 caixa: "a".into(),
17150 versao: "^0.1".into(),
17151 }];
17152 c.contratos = vec![WitContract {
17153 de: "a".into(),
17154 para: "a".into(),
17155 wit: "wasi:http/proxy".into(),
17156 endpoint: Some("/x".into()),
17157 subject: None,
17158 slot: None,
17159 }];
17160 c.entrada = entrada;
17161 c
17162 }
17163
17164 #[test]
17165 fn entrada_returns_entrada_option_ref_verbatim_across_permutations() {
17166 // The canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
17167 // composite optional-composite-reference-shape pin:
17168 // [`Caixa::entrada`] must return the `:entrada` typed
17169 // `Option<Entrada>` verbatim as an `Option<&Entrada>`
17170 // reference over the same backing storage the raw
17171 // `self.entrada.as_ref()` field access borrows from,
17172 // byte-equal across every representative fixture in the
17173 // accept-set — the author-omitted `None` shape (the
17174 // "cluster-internal Aplicacao" partition every downstream
17175 // Gateway-API emitter treats as "emit no listener + no
17176 // HTTPRoute"), a bare-`host`/`para` minimum-composite fixture
17177 // (empty `paths` — the resolved-paths fallback the peer
17178 // [`crate::aplicacao::Entrada::resolved_paths`] cascade folds
17179 // onto the substrate catch-all), and a fully-populated
17180 // multi-path-with-non-default-port fixture (the canonical
17181 // shape a public HTTP Aplicacao carries).
17182 //
17183 // Pins against a future silent detour that returned a fresh-
17184 // cloned [`crate::aplicacao::Entrada`] copy (which would
17185 // type-check via the `Clone` impl but silently break every
17186 // downstream caller that relied on the reference sharing the
17187 // composite's backing identity), a reference to an operator-
17188 // resolved overlay (the future per-cluster
17189 // `:entrada-overrides` slot — its resolution must land at
17190 // exactly this accessor body, not silently divert the raw
17191 // slot away from the peer [`Caixa::declared_mesh_slots`]
17192 // enumerator's presence probe), or an axis-shuffled projection
17193 // (a future detour that swapped `host` and `para` through the
17194 // accessor would silently split the paired
17195 // [`Caixa::aplicacao_view`] seed's forward input from the
17196 // sibling M3 gateway-artifact emitter's projection input).
17197 //
17198 // Fifth and final outer top-level [`Caixa`]
17199 // `Option<&Composite>`-return composite-reference accessor pin
17200 // on the substrate primitive — peer of the sibling
17201 // `limits_returns_limits_option_ref_verbatim_across_permutations`
17202 // (b2bd9d7),
17203 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
17204 // (35d8b52),
17205 // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
17206 // (5d23d29), and
17207 // `placement_returns_placement_option_ref_verbatim_across_permutations`
17208 // (4fb8074) opening tetrad pins on the outer top-level
17209 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
17210 // here to the third and final M3 mesh-slot axis so the closed
17211 // outer `Option<&Composite>` sub-family carries the same
17212 // "byte-equal, borrow-shared, presence-bit-preserved" outer-
17213 // accessor discipline across all five arms.
17214 use crate::aplicacao::Entrada;
17215 let fixtures: Vec<Option<Entrada>> = vec![
17216 None,
17217 Some(Entrada {
17218 host: "checkout.quero.cloud".into(),
17219 para: "gateway".into(),
17220 paths: Vec::new(),
17221 port: crate::DEFAULT_SERVICO_PORT,
17222 }),
17223 Some(Entrada {
17224 host: "api.pleme.io".into(),
17225 para: "public-api".into(),
17226 paths: vec!["/v1".into(), "/v2".into()],
17227 port: 8080,
17228 }),
17229 ];
17230 for entrada in fixtures {
17231 let c = caixa_aplicacao_with_entrada(entrada.clone());
17232 assert_eq!(
17233 c.entrada(),
17234 entrada.as_ref(),
17235 "Caixa::entrada must return :entrada verbatim (got \
17236 {:?}, expected {:?})",
17237 c.entrada(),
17238 entrada.as_ref(),
17239 );
17240 match (c.entrada(), c.entrada.as_ref()) {
17241 (Some(a), Some(b)) => assert!(
17242 std::ptr::eq(a, b),
17243 "Caixa::entrada accessor and self.entrada.as_ref() \
17244 field access must borrow the same backing storage \
17245 — the accessor is the substrate-primitive typed \
17246 dispatch every downstream Aplicacao-external- \
17247 gateway composite consumer must route through, and \
17248 a reference-identity split would silently break \
17249 every consumer that relied on the borrow sharing \
17250 the composite's storage",
17251 ),
17252 (None, None) => {}
17253 _ => panic!(
17254 "Caixa::entrada presence bit must byte-equal \
17255 self.entrada.is_some() — a presence-bit drift \
17256 would silently split the paired \
17257 Caixa::aplicacao_view Aplicacao-composition seed's \
17258 traversal head from the peer \
17259 Caixa::declared_mesh_slots M3 declared-slot \
17260 enumerator's presence probe",
17261 ),
17262 }
17263 assert_eq!(
17264 c.entrada().is_some(),
17265 c.entrada.is_some(),
17266 "Caixa::entrada().is_some() must byte-equal \
17267 self.entrada.is_some() — a presence-bit drift would \
17268 silently split every downstream Option<&Entrada> \
17269 consumer's partition on the cluster-internal arm",
17270 );
17271 }
17272 }
17273
17274 #[test]
17275 fn declared_mesh_slots_entrada_arm_routes_through_accessor() {
17276 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:entrada`
17277 // presence-probe arm must key off [`Caixa::entrada`], not the
17278 // raw `self.entrada.is_some()` field-probe. Structurally: a
17279 // `Caixa { entrada: Some(Entrada { host: "...", para: "...",
17280 // paths: [], port: DEFAULT_SERVICO_PORT }), .. }` must push
17281 // `M3_AUTHOR_KEY_ENTRADA` onto the declared-slot list (the
17282 // presence bit is `Some`, so the M3 kind-coherence gate must
17283 // surface the slot as "declared" even when every per-axis
17284 // scalar defers to the substrate catch-all / default port),
17285 // and a `Caixa { entrada: None, .. }` must NOT push the label
17286 // (the "author omitted the slot entirely" partition). The pair
17287 // jointly pins the accessor + declared-slot enumerator
17288 // composition: any future silent detour that had the accessor
17289 // collapse `Some(Entrada { paths: [], .. })` to `None` (a
17290 // `.filter(|e| !e.paths.is_empty())` projection) would silently
17291 // absorb the "declared but empty-paths" arm at the accessor
17292 // boundary and the
17293 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
17294 // coherence gate would silently accept a struct-literal
17295 // `Caixa` carrying the drift.
17296 //
17297 // Peer of the sibling
17298 // `declared_servico_slots_limits_arm_routes_through_accessor`
17299 // (b2bd9d7),
17300 // `declared_servico_slots_behavior_arm_routes_through_accessor`
17301 // (35d8b52),
17302 // `declared_mesh_slots_politicas_arm_routes_through_accessor`
17303 // (5d23d29), and
17304 // `declared_mesh_slots_placement_arm_routes_through_accessor`
17305 // (4fb8074) composition pins on the sibling `:limits` /
17306 // `:behavior` / `:politicas` / `:placement` outer-
17307 // `Option<&Composite>` arms — same "the enumerator gate must
17308 // route through the substrate-primitive typed dispatch"
17309 // discipline extended onto the third and final M3 mesh-slot
17310 // axis so the [`Caixa::declared_mesh_slots`] enumerator now
17311 // carries the routing invariant on every M3 mesh-slot arm.
17312 use crate::aplicacao::Entrada;
17313 let c = caixa_aplicacao_with_entrada(Some(Entrada {
17314 host: "checkout.quero.cloud".into(),
17315 para: "gateway".into(),
17316 paths: Vec::new(),
17317 port: crate::DEFAULT_SERVICO_PORT,
17318 }));
17319 let slots = c.declared_mesh_slots();
17320 assert!(
17321 slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
17322 "declared_mesh_slots must push M3_AUTHOR_KEY_ENTRADA when \
17323 `:entrada` is Some (even for empty-paths / default-port) \
17324 — the accessor and the enumerator gate must route through \
17325 the same substrate-primitive typed dispatch on the outer \
17326 :entrada presence bit (got slots={slots:?})",
17327 );
17328 let c = caixa_aplicacao_with_entrada(None);
17329 let slots = c.declared_mesh_slots();
17330 assert!(
17331 !slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
17332 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_ENTRADA \
17333 when `:entrada` is None — the author-omitted arm must \
17334 route through the accessor's None-return unchanged (got \
17335 slots={slots:?})",
17336 );
17337 }
17338
17339 #[test]
17340 fn aplicacao_view_entrada_arm_folds_through_accessor() {
17341 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:entrada`
17342 // Aplicacao-composition seed must fold through
17343 // [`Caixa::entrada`], not the raw `self.entrada.clone()` field-
17344 // borrow. Structurally: a `Caixa { entrada: Some(Entrada {
17345 // host: "api.pleme.io", para: "public-api", paths: ["/v1"],
17346 // port: 8080 }), kind: Aplicacao, .. }` must surface a projected
17347 // [`crate::AplicacaoSpec`] whose `entrada().unwrap()` byte-
17348 // equals the outer composite's authored value (the fold must
17349 // project the authored composite verbatim), and a `Caixa {
17350 // entrada: None, kind: Aplicacao, .. }` must surface an
17351 // [`crate::AplicacaoSpec`] whose `entrada()` is `None` (the
17352 // "author omitted the slot entirely" arm folds through the
17353 // accessor's `Option::cloned` onto the same `None` presence
17354 // bit — unlike the peer `:politicas` / `:placement` arms
17355 // `:entrada` has no cluster-default fold, the omitted arm
17356 // stays omitted). The pair jointly pins the accessor +
17357 // Aplicacao-composition seed composition: any future silent
17358 // detour that had the accessor divert the raw slot away from
17359 // the seed's fold (an operator-resolved overlay's forward arm
17360 // silently differing from the raw slot's forward arm) would
17361 // silently split the build-time gateway-artifact emission gate
17362 // from the caixa-mesh renderer's Aplicacao-view input at the
17363 // composition boundary.
17364 use crate::aplicacao::Entrada;
17365 let authored = Entrada {
17366 host: "api.pleme.io".into(),
17367 para: "public-api".into(),
17368 paths: vec!["/v1".into()],
17369 port: 8080,
17370 };
17371 let c = caixa_aplicacao_with_entrada(Some(authored.clone()));
17372 let view = c.aplicacao_view().unwrap();
17373 assert_eq!(
17374 view.entrada(),
17375 Some(&authored),
17376 "Caixa::aplicacao_view must fold the authored :entrada \
17377 composite through the accessor verbatim onto the \
17378 projected AplicacaoSpec — a future silent detour at the \
17379 seed's fold arm would surface here as a projected- \
17380 composite drift (got {:?})",
17381 view.entrada(),
17382 );
17383 let c = caixa_aplicacao_with_entrada(None);
17384 let view = c.aplicacao_view().unwrap();
17385 assert!(
17386 view.entrada().is_none(),
17387 "Caixa::aplicacao_view must fold None through the \
17388 accessor's Option::cloned onto None — the author- \
17389 omitted arm must route through the accessor's None-return \
17390 unchanged (got {:?})",
17391 view.entrada(),
17392 );
17393 }
17394
17395 #[test]
17396 fn entrada_projects_option_ref_by_borrow() {
17397 // The by-borrow pin: [`Caixa::entrada`] returns
17398 // `Option<&Entrada>` by borrow — the returned reference
17399 // borrows the underlying `Option<Entrada>` storage of the
17400 // `:entrada` slot and the accessor must not clone the backing
17401 // composite on every call. Peer of the sibling
17402 // `limits_projects_option_ref_by_borrow` (b2bd9d7),
17403 // `behavior_projects_option_ref_by_borrow` (35d8b52),
17404 // `politicas_projects_option_ref_by_borrow` (5d23d29), and
17405 // `placement_projects_option_ref_by_borrow` (4fb8074) by-
17406 // borrow pins on the outer top-level [`Caixa`]
17407 // `Option<&Composite>`-return sub-family — extended here to
17408 // the fifth and final axis of the same sub-family, closing
17409 // the discipline: the accessor's returned reference must
17410 // borrow from `&self` (the returned reference's lifetime is
17411 // tied to `&self`), and calling the accessor twice on the
17412 // same [`Caixa`] must yield references that are pointer-equal
17413 // (the underlying byte-buffer is the storage `Entrada`'s
17414 // allocation, not a fresh copy) as well as value-equal
17415 // (idempotent, no side effects on `&self`).
17416 //
17417 // Pins against a future silent detour that returned an owned
17418 // `Entrada` (which would type-check via the `Clone` impl but
17419 // silently clone on every call), a `&Entrada` panic-return on
17420 // the `None` arm (which would collapse the load-bearing
17421 // `Option` presence-bit into a runtime panic), or a one-arm-
17422 // only accessor that returned a saturating composite on some
17423 // sentinel input.
17424 use crate::aplicacao::Entrada;
17425 for entrada in [
17426 Some(Entrada {
17427 host: "checkout.quero.cloud".into(),
17428 para: "gateway".into(),
17429 paths: Vec::new(),
17430 port: crate::DEFAULT_SERVICO_PORT,
17431 }),
17432 Some(Entrada {
17433 host: "api.pleme.io".into(),
17434 para: "public-api".into(),
17435 paths: vec!["/v1".into(), "/v2".into()],
17436 port: 8080,
17437 }),
17438 ] {
17439 let c = caixa_aplicacao_with_entrada(entrada.clone());
17440 let first = c.entrada().unwrap();
17441 let second = c.entrada().unwrap();
17442 assert_eq!(
17443 first, second,
17444 "Caixa::entrada must be idempotent — two successive \
17445 calls on the same &self must return the same &Entrada",
17446 );
17447 assert!(
17448 std::ptr::eq(first, second),
17449 "Caixa::entrada must borrow the underlying \
17450 Option<Entrada> storage — two successive calls must \
17451 return references with the same backing pointer (a \
17452 fresh Entrada clone would change the pointer on every \
17453 call)",
17454 );
17455 assert_eq!(
17456 Some(first),
17457 entrada.as_ref(),
17458 "Caixa::entrada must return :entrada verbatim by \
17459 borrow — got {first:?}, expected {:?}",
17460 entrada.as_ref(),
17461 );
17462 }
17463 let c = caixa_aplicacao_with_entrada(None);
17464 assert!(
17465 c.entrada().is_none(),
17466 "Caixa::entrada must return None when :entrada is absent \
17467 — the author-omitted arm must project through the \
17468 accessor's Option::None unchanged",
17469 );
17470 }
17471
17472 // ── Caixa::estrategia — outer top-level Option<RestartStrategy> flat-spread supervisor-tree accessor ──
17473
17474 fn caixa_with_estrategia(estrategia: Option<crate::supervisor::RestartStrategy>) -> Caixa {
17475 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17476 c.estrategia = estrategia;
17477 c
17478 }
17479
17480 #[test]
17481 fn estrategia_returns_estrategia_option_verbatim_across_permutations() {
17482 // The canonical per-`Caixa` `:estrategia` M2 supervisor-tree-slot
17483 // flat-spread `Option<RestartStrategy>`-return `Copy`-composite-
17484 // enum-arm scalar shape pin: [`Caixa::estrategia`] must return
17485 // the `:estrategia` typed `Option<crate::supervisor::RestartStrategy>`
17486 // verbatim as an `Option<RestartStrategy>` `Copy`-projected value
17487 // over the same discriminant the raw `self.estrategia` field
17488 // access carries, byte-equal across every representative fixture
17489 // in the accept-set — the author-omitted `None` shape (the
17490 // "defer to [`RestartStrategy::default`] through the
17491 // [`Self::supervisor_view`] `unwrap_or_default()` fold" partition
17492 // every non-`Supervisor`-kind `defcaixa` carries by
17493 // `#[serde(default)]`), and each of the four closed-set variants
17494 // [`RestartStrategy::OneForOne`] / [`RestartStrategy::OneForAll`]
17495 // / [`RestartStrategy::RestForOne`] /
17496 // [`RestartStrategy::SimpleOneForOne`] the author-declared arm
17497 // partitions on.
17498 //
17499 // Pins against a future silent detour that re-derived the
17500 // strategy from a peer axis (an accidental fallback to
17501 // `if children.is_empty() { SimpleOneForOne } else { OneForOne }`
17502 // collapse that read the outer `:children` list-length axis into
17503 // the strategy discriminator at the accessor boundary), a
17504 // stale-derive detour that substituted [`RestartStrategy::default`]
17505 // when the outer `Option` held `None` (which would silently
17506 // collapse the load-bearing "author explicitly declared
17507 // `:estrategia OneForOne`" vs "author omitted the slot and
17508 // inherited the default" partition the [`Self::declared_supervisor_slots`]
17509 // presence-probe reads — the enumerator gate would still push
17510 // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` on the omitted arm, silently
17511 // splitting the paired [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
17512 // kind-coherence gate's traversal head from the
17513 // [`Self::supervisor_view`] `unwrap_or_default()` fold's
17514 // composition head), a reference to an operator-resolved overlay
17515 // (the future per-cluster `:estrategia-overrides` slot — its
17516 // resolution must land at exactly this accessor body, not
17517 // silently divert the raw slot away from a second consumer), or
17518 // an axis-remap projection (a future detour that mapped
17519 // `OneForAll` through the accessor onto `OneForOne` would
17520 // silently split every downstream sibling-restart-strategy
17521 // consumer's per-arm fan-out).
17522 //
17523 // First outer top-level [`Caixa`] `Option<Copy>`-return
17524 // supervisor-tree-slot flat-spread accessor pin on the substrate
17525 // primitive — opens the outer-`Caixa` `Option<Copy>` flat-spread
17526 // projection pattern the sibling per-`Caixa` `:max-restarts` /
17527 // `:restart-window` future outer-scalar pins fold on. Peer of
17528 // the inner-altitude
17529 // `supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
17530 // (eafb619) pin on the post-composition [`SupervisorSpec`]
17531 // altitude — same "the substrate-primitive accessor must byte-
17532 // equal the raw field access verbatim across every author-
17533 // declared value" discipline extended onto the pre-composition
17534 // outer author-surface [`Caixa`] altitude. Peer of the closed
17535 // outer-`Caixa` `Option<&Composite>` composite-reference family
17536 // the sibling `limits` / `behavior` / `politicas` / `placement` /
17537 // `entrada`
17538 // `..._returns_..._option_ref_verbatim_across_permutations` pins
17539 // already carry on the outer `Option<&Composite>` altitude.
17540 use crate::supervisor::RestartStrategy;
17541 let fixtures: Vec<Option<RestartStrategy>> = vec![
17542 None,
17543 Some(RestartStrategy::OneForOne),
17544 Some(RestartStrategy::OneForAll),
17545 Some(RestartStrategy::RestForOne),
17546 Some(RestartStrategy::SimpleOneForOne),
17547 ];
17548 for estrategia in fixtures {
17549 let c = caixa_with_estrategia(estrategia);
17550 assert_eq!(
17551 c.estrategia(),
17552 estrategia,
17553 "Caixa::estrategia must return :estrategia verbatim (got \
17554 {:?}, expected {:?})",
17555 c.estrategia(),
17556 estrategia,
17557 );
17558 assert_eq!(
17559 c.estrategia(),
17560 c.estrategia,
17561 "Caixa::estrategia accessor and self.estrategia field \
17562 access must byte-equal — the accessor is the substrate-\
17563 primitive typed dispatch every downstream supervisor-\
17564 tree flat-spread consumer must route through, and a \
17565 discriminant split would silently break every consumer \
17566 that relied on the accessor sharing the field's own \
17567 Option<Copy> shape",
17568 );
17569 assert_eq!(
17570 c.estrategia().is_some(),
17571 c.estrategia.is_some(),
17572 "Caixa::estrategia().is_some() must byte-equal \
17573 self.estrategia.is_some() — a presence-bit drift would \
17574 silently split the paired Caixa::declared_supervisor_slots \
17575 presence-probe arm from the Caixa::supervisor_view \
17576 unwrap_or_default() fold's composition input",
17577 );
17578 }
17579 }
17580
17581 #[test]
17582 fn declared_supervisor_slots_estrategia_arm_routes_through_accessor() {
17583 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
17584 // `:estrategia` presence-probe arm must key off
17585 // [`Caixa::estrategia`], not the raw `self.estrategia.is_some()`
17586 // field-probe. Structurally: every `Caixa { estrategia:
17587 // Some(RestartStrategy::_), .. }` variant must push
17588 // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` onto the declared-slot list
17589 // (the presence bit is `Some` for every closed-set variant, so
17590 // the M2 supervisor-tree kind-coherence gate must surface the
17591 // slot as "declared" regardless of which variant the author
17592 // picked), and a `Caixa { estrategia: None, .. }` must NOT push
17593 // the label (the "author omitted the slot entirely, deferring
17594 // to [`RestartStrategy::default`] through the supervisor_view
17595 // fold" partition). The pair jointly pins the accessor +
17596 // declared-slot enumerator composition: any future silent detour
17597 // that had the accessor collapse `Some(RestartStrategy::default())`
17598 // to `None` (a `.filter(|e| *e != RestartStrategy::default())`
17599 // projection) would silently absorb the "declared but default-
17600 // valued" arm at the accessor boundary and the
17601 // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
17602 // coherence gate would silently accept a struct-literal `Caixa`
17603 // carrying the drift.
17604 //
17605 // Peer of the sibling per-`Caixa`
17606 // `declared_servico_slots_limits_arm_routes_through_accessor`
17607 // (b2bd9d7) accessor-composition pin on the sibling outer-`Caixa`
17608 // `Option<&LimitsSpec>` composition axis — same "the enumerator
17609 // gate must route through the substrate-primitive typed
17610 // dispatch" discipline extended onto the flat-spread M2
17611 // supervisor-tree `Option<RestartStrategy>`-composition surface,
17612 // opening the outer-`Caixa` supervisor-tree-slot arm of the
17613 // composition-pin family.
17614 use crate::supervisor::RestartStrategy;
17615 for estrategia in [
17616 RestartStrategy::OneForOne,
17617 RestartStrategy::OneForAll,
17618 RestartStrategy::RestForOne,
17619 RestartStrategy::SimpleOneForOne,
17620 ] {
17621 let c = caixa_with_estrategia(Some(estrategia));
17622 let slots = c.declared_supervisor_slots();
17623 assert!(
17624 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
17625 "declared_supervisor_slots must push \
17626 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is \
17627 Some({estrategia:?}) — the accessor and the enumerator \
17628 gate must route through the same substrate-primitive \
17629 typed dispatch on the outer :estrategia presence bit \
17630 (got slots={slots:?})",
17631 );
17632 }
17633 let c = caixa_with_estrategia(None);
17634 let slots = c.declared_supervisor_slots();
17635 assert!(
17636 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
17637 "declared_supervisor_slots must NOT push \
17638 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is None \
17639 — the author-omitted arm must route through the accessor's \
17640 None-return unchanged (got slots={slots:?})",
17641 );
17642 }
17643
17644 #[test]
17645 fn supervisor_view_estrategia_arm_routes_through_accessor() {
17646 // Composition pin: [`Caixa::supervisor_view`]'s per-`:estrategia`
17647 // [`SupervisorSpec`] construction arm must key off
17648 // [`Caixa::estrategia`]'s `unwrap_or_default()` fold, not the raw
17649 // `self.estrategia.unwrap_or_default()` field-fold. Structurally:
17650 // for every `:kind Supervisor` `Caixa` carrying an author-
17651 // declared `Some(RestartStrategy::_)` variant, the composed
17652 // [`SupervisorSpec`]'s `.estrategia` field must byte-equal the
17653 // outer accessor's declared variant unchanged; and for a
17654 // `:kind Supervisor` `Caixa` carrying `None`, the composed
17655 // [`SupervisorSpec`]'s `.estrategia` field must byte-equal
17656 // [`RestartStrategy::default`] (the [`RestartStrategy::OneForOne`]
17657 // arm the flat-spread `unwrap_or_default()` fold projects to on
17658 // the author-omitted arm — this is the *composition* between the
17659 // outer `Option<RestartStrategy>` accessor's presence-bit
17660 // surface and the inner post-composition non-`Option`
17661 // [`SupervisorSpec::estrategia`] altitude). The pair jointly
17662 // pins the accessor + supervisor_view composition: any future
17663 // silent detour that had the accessor promote `None` to
17664 // `Some(RestartStrategy::default())` (a `.or_else(|| Some(RestartStrategy::default()))`
17665 // projection) would silently collapse the two arms into one at
17666 // the accessor boundary and the [`Self::declared_supervisor_slots`]
17667 // presence probe would silently drift from the composition site.
17668 //
17669 // Peer of the sibling M2 supervisor-slot post-composition
17670 // `validate_reads_through_lifted_estrategia_accessor` (eafb619)
17671 // pin on the [`SupervisorSpec::validate`] altitude — this pin
17672 // extends that inner-altitude accessor-routing discipline onto
17673 // the pre-composition outer author-surface [`Caixa`] altitude,
17674 // pinning the composition edge between the flat-spread outer
17675 // `Option<RestartStrategy>` and the composed [`SupervisorSpec`]
17676 // `RestartStrategy` axes.
17677 use crate::CaixaKind;
17678 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
17679 for estrategia in [
17680 RestartStrategy::OneForOne,
17681 RestartStrategy::OneForAll,
17682 RestartStrategy::RestForOne,
17683 RestartStrategy::SimpleOneForOne,
17684 ] {
17685 let mut c = caixa_with_estrategia(Some(estrategia));
17686 c.kind = CaixaKind::Supervisor;
17687 // Route the `SimpleOneForOne ↔ non-SimpleOneForOne` fixture-
17688 // shape partition through the [`gen_platform::IsVariant`]
17689 // derive-generated
17690 // [`RestartStrategy::is_simple_one_for_one`] predicate rather
17691 // than the raw `matches!(estrategia, RestartStrategy::
17692 // SimpleOneForOne)` open-coded pattern-match — same closed-
17693 // set-typed-enum arm-discriminator dispatch discipline the
17694 // sibling [`crate::upgrade::UpgradeInstruction::is_restart`]
17695 // convergence (915a934) extended onto its two paired positive
17696 // / negated `matches!` sites and the peer
17697 // [`crate::aplicacao::PlacementStrategy`] `IsVariant`-derived
17698 // predicate convergence (766ec63) extended onto the M3 mesh-
17699 // slot per-`:placement` distribution-strategy discriminator
17700 // axis. See the sibling `supervisor::tests::
17701 // round_trip_all_strategies` and
17702 // `supervisor::tests::supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
17703 // fixtures — the three sites (all test-only,
17704 // acknowledged in 915a934's Prior-commits footnote as the
17705 // outstanding follow-up) now consult one typed dispatch on
17706 // the substrate primitive.
17707 c.children = if estrategia.is_simple_one_for_one() {
17708 Vec::new()
17709 } else {
17710 vec![ChildSpec {
17711 caixa: "worker".into(),
17712 versao: "^0.1".into(),
17713 restart: RestartPolicy::Permanent,
17714 }]
17715 };
17716 let view = c.supervisor_view().expect(
17717 "supervisor_view must materialize a SupervisorSpec for a \
17718 :kind Supervisor Caixa carrying a Some(:estrategia) slot",
17719 );
17720 assert_eq!(
17721 view.estrategia(),
17722 c.estrategia().unwrap(),
17723 "supervisor_view must carry the outer Caixa::estrategia() \
17724 declared variant onto the composed SupervisorSpec.estrategia \
17725 field verbatim on the Some arm (got {:?}, expected {:?})",
17726 view.estrategia(),
17727 c.estrategia().unwrap(),
17728 );
17729 }
17730 // The author-omitted arm: outer `None` → composed
17731 // `RestartStrategy::default()` through the flat-spread
17732 // `unwrap_or_default()` fold.
17733 let mut c = caixa_with_estrategia(None);
17734 c.kind = CaixaKind::Supervisor;
17735 // Populate children so the sibling supervisor slots are coherent
17736 // for the [`Self::supervisor_view`] projection; the `:estrategia`
17737 // arm still defers to [`RestartStrategy::default`] on the
17738 // author-omitted arm even when the sibling slots carry values.
17739 c.children = vec![ChildSpec {
17740 caixa: "worker".into(),
17741 versao: "^0.1".into(),
17742 restart: RestartPolicy::Permanent,
17743 }];
17744 let view = c.supervisor_view().expect(
17745 "supervisor_view must materialize a SupervisorSpec for a \
17746 :kind Supervisor Caixa carrying a None `:estrategia` slot",
17747 );
17748 assert_eq!(
17749 view.estrategia(),
17750 RestartStrategy::default(),
17751 "supervisor_view must project the outer Caixa::estrategia() \
17752 None arm onto RestartStrategy::default() through the flat-\
17753 spread unwrap_or_default() fold (got {:?}, expected {:?})",
17754 view.estrategia(),
17755 RestartStrategy::default(),
17756 );
17757 assert!(
17758 c.estrategia().is_none(),
17759 "Caixa::estrategia() must remain None on the author-omitted \
17760 arm — the supervisor_view fold must not mutate the outer \
17761 flat-spread presence bit",
17762 );
17763 }
17764
17765 #[test]
17766 fn estrategia_projects_option_by_copy() {
17767 // The by-`Copy` pin: [`Caixa::estrategia`] returns
17768 // `Option<RestartStrategy>` by value (`RestartStrategy: Copy`) —
17769 // the accessor does not borrow `&self` past the call (no
17770 // lifetime on the return type), and calling the accessor twice
17771 // on the same [`Caixa`] must yield discriminant-equal values
17772 // (idempotent, no side effects on `&self`). Peer of the sibling
17773 // outer-`Caixa` `Option<&Composite>` by-borrow
17774 // `limits_projects_option_ref_by_borrow` (b2bd9d7) /
17775 // `behavior_projects_option_ref_by_borrow` (35d8b52) /
17776 // `politicas_projects_option_ref_by_borrow` (5d23d29) /
17777 // `placement_projects_option_ref_by_borrow` (4fb8074) /
17778 // `entrada_projects_option_ref_by_borrow` (e4128e4) by-borrow
17779 // pins on the outer-`Caixa` `Option<&Composite>`-return axes —
17780 // extended here to the outer-`Caixa` `Option<Copy>`-return
17781 // flat-spread axis. The `Copy` discipline replaces the pointer-
17782 // equality claim the by-borrow siblings pin (a fresh `Copy` of a
17783 // `Copy` discriminant is definitionally the same discriminant, so
17784 // the axis reduces to discriminant equality).
17785 //
17786 // Pins against a future silent detour that returned a fresh
17787 // `Option<&RestartStrategy>` (which would type-check but silently
17788 // introduce a borrow of `&self` past the call, collapsing the
17789 // load-bearing "no lifetime on the return type" `Copy` projection
17790 // the flat-spread axis's `Option<Copy>` shape carries), a stale-
17791 // read side effect that flipped the outer discriminant on
17792 // successive calls, or an axis-remap projection that returned a
17793 // different variant than the field storage.
17794 use crate::supervisor::RestartStrategy;
17795 for estrategia in [
17796 Some(RestartStrategy::OneForOne),
17797 Some(RestartStrategy::OneForAll),
17798 Some(RestartStrategy::RestForOne),
17799 Some(RestartStrategy::SimpleOneForOne),
17800 ] {
17801 let c = caixa_with_estrategia(estrategia);
17802 let first = c.estrategia();
17803 let second = c.estrategia();
17804 assert_eq!(
17805 first, second,
17806 "Caixa::estrategia must be idempotent — two successive \
17807 calls on the same &self must return the same \
17808 Option<RestartStrategy>",
17809 );
17810 assert_eq!(
17811 first, estrategia,
17812 "Caixa::estrategia must return :estrategia verbatim by \
17813 Copy — got {first:?}, expected {estrategia:?}",
17814 );
17815 }
17816 let c = caixa_with_estrategia(None);
17817 assert!(
17818 c.estrategia().is_none(),
17819 "Caixa::estrategia must return None when :estrategia is \
17820 absent — the author-omitted arm must project through the \
17821 accessor's Option::None unchanged",
17822 );
17823 }
17824
17825 // ── Caixa::max_restarts / Caixa::restart_window —
17826 // outer top-level M2 supervisor-tree-slot flat-spread accessors
17827 // (Option<u32> / Option<&str>) folding on the ed04d3c
17828 // Caixa::estrategia Option<Copy> sub-family ─────────────────────
17829
17830 fn caixa_with_max_restarts(max_restarts: Option<u32>) -> Caixa {
17831 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17832 c.max_restarts = max_restarts;
17833 c
17834 }
17835
17836 fn caixa_supervisor_with_max_restarts_and_window(
17837 max_restarts: Option<u32>,
17838 restart_window: Option<&str>,
17839 ) -> Caixa {
17840 use crate::CaixaKind;
17841 use crate::supervisor::{ChildSpec, RestartPolicy};
17842 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
17843 c.kind = CaixaKind::Supervisor;
17844 c.max_restarts = max_restarts;
17845 c.restart_window = restart_window.map(str::to_string);
17846 c.children = vec![ChildSpec {
17847 caixa: "worker".into(),
17848 versao: "^0.1".into(),
17849 restart: RestartPolicy::Permanent,
17850 }];
17851 c
17852 }
17853
17854 #[test]
17855 fn max_restarts_returns_max_restarts_option_verbatim_across_permutations() {
17856 // Value-shape pin: [`Caixa::max_restarts`] returns the
17857 // `:max-restarts` typed `Option<u32>` verbatim, `Copy`-projected
17858 // from the typed slot's own storage, byte-equal across the
17859 // author-omitted `None` arm (the "defer to the
17860 // [`Self::supervisor_view`] `unwrap_or(5)` OTP-canonical
17861 // `{intensity, 5, 60}` default" partition every
17862 // non-`Supervisor`-kind caixa carries by `#[serde(default)]`)
17863 // and each of the representative fixtures in the accept-set —
17864 // `0` (the zero-floor arm the peer
17865 // [`crate::supervisor::SupervisorSpec::validate`]
17866 // [`crate::SupervisorError::ZeroMaxRestarts`] gate refuses on
17867 // the post-composition altitude — the accessor must ship the
17868 // raw slot verbatim so struct-literal fixtures continue to
17869 // expose the zero at the accessor boundary), the OTP-canonical
17870 // `5` default (`{intensity, 5, 60}` worker-supervisor from
17871 // Learn You Some Erlang), `1000` (the
17872 // [`SUPERVISOR_MAX_RESTARTS_MAX`] cap the peer post-composition
17873 // upper-bound gate accepts on the boundary), `u32::MAX` (a
17874 // past-the-cap sentinel that the substrate-primitive accessor
17875 // must still ship verbatim). Second outer top-level
17876 // [`Caixa`] `Option<Copy>`-return supervisor-tree flat-spread
17877 // pin — folds on the sibling
17878 // `estrategia_returns_estrategia_option_verbatim_across_permutations`
17879 // (ed04d3c) pin's `Option<Copy>` shape, extending the sub-family
17880 // onto the sibling `Option<u32>` restart-budget-count arm.
17881 let fixtures: Vec<Option<u32>> = vec![None, Some(0), Some(5), Some(1000), Some(u32::MAX)];
17882 for max_restarts in fixtures {
17883 let c = caixa_with_max_restarts(max_restarts);
17884 assert_eq!(
17885 c.max_restarts(),
17886 max_restarts,
17887 "Caixa::max_restarts must return :max-restarts verbatim \
17888 (got {:?}, expected {max_restarts:?})",
17889 c.max_restarts(),
17890 );
17891 assert_eq!(
17892 c.max_restarts(),
17893 c.max_restarts,
17894 "Caixa::max_restarts accessor and self.max_restarts \
17895 field access must byte-equal — a presence-bit or count \
17896 drift would silently split the paired \
17897 Caixa::declared_supervisor_slots presence-probe arm \
17898 from the Caixa::supervisor_view unwrap_or(5) fold's \
17899 composition input",
17900 );
17901 }
17902 }
17903
17904 #[test]
17905 fn max_restarts_projects_option_by_copy() {
17906 // The by-`Copy` pin: [`Caixa::max_restarts`] returns
17907 // `Option<u32>` by value (`u32: Copy`) — the accessor does not
17908 // borrow `&self` past the call (no lifetime on the return type),
17909 // and calling the accessor twice on the same [`Caixa`] must
17910 // yield equal values (idempotent, no side effects). Peer of the
17911 // sibling `estrategia_projects_option_by_copy` (ed04d3c) pin on
17912 // the outer-`Caixa` `Option<Copy>`-return flat-spread axis.
17913 for max_restarts in [Some(0u32), Some(5), Some(1000), Some(u32::MAX), None] {
17914 let c = caixa_with_max_restarts(max_restarts);
17915 let first = c.max_restarts();
17916 let second = c.max_restarts();
17917 assert_eq!(
17918 first, second,
17919 "Caixa::max_restarts must be idempotent — two successive \
17920 calls on the same &self must return the same Option<u32>",
17921 );
17922 assert_eq!(
17923 first, max_restarts,
17924 "Caixa::max_restarts must return :max-restarts verbatim \
17925 by Copy — got {first:?}, expected {max_restarts:?}",
17926 );
17927 }
17928 }
17929
17930 #[test]
17931 fn declared_supervisor_slots_max_restarts_arm_routes_through_accessor() {
17932 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
17933 // `:max-restarts` presence-probe arm must key off
17934 // [`Caixa::max_restarts`], not the raw
17935 // `self.max_restarts.is_some()` field-probe. Structurally: every
17936 // `Caixa { max_restarts: Some(_), .. }` variant must push
17937 // `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS` onto the declared-slot
17938 // list (the presence bit is `Some` for every representative
17939 // count, so the M2 kind-coherence gate must surface the slot as
17940 // "declared"), and a `Caixa { max_restarts: None, .. }` must
17941 // NOT push the label. Peer of the sibling
17942 // `declared_supervisor_slots_estrategia_arm_routes_through_accessor`
17943 // (ed04d3c) composition pin — same routing-through-accessor
17944 // discipline extended onto the sibling flat-spread `Option<u32>`
17945 // arm.
17946 for max_restarts in [0u32, 5, 1000, u32::MAX] {
17947 let c = caixa_with_max_restarts(Some(max_restarts));
17948 let slots = c.declared_supervisor_slots();
17949 assert!(
17950 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
17951 "declared_supervisor_slots must push \
17952 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` \
17953 is Some({max_restarts}) — the accessor and the \
17954 enumerator gate must route through the same \
17955 substrate-primitive typed dispatch on the outer \
17956 :max-restarts presence bit (got slots={slots:?})",
17957 );
17958 }
17959 let c = caixa_with_max_restarts(None);
17960 let slots = c.declared_supervisor_slots();
17961 assert!(
17962 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
17963 "declared_supervisor_slots must NOT push \
17964 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` is \
17965 None — the author-omitted arm must route through the \
17966 accessor's None-return unchanged (got slots={slots:?})",
17967 );
17968 }
17969
17970 #[test]
17971 fn supervisor_view_max_restarts_arm_routes_through_accessor() {
17972 // Composition pin: [`Caixa::supervisor_view`]'s per-`:max-restarts`
17973 // [`SupervisorSpec`] construction arm must key off
17974 // [`Caixa::max_restarts`]'s `unwrap_or(5)` fold, not the raw
17975 // `self.max_restarts.unwrap_or(5)` field-fold. Structurally: for
17976 // every `:kind Supervisor` `Caixa` carrying an author-declared
17977 // `Some(n)`, the composed [`SupervisorSpec`]'s `.max_restarts()`
17978 // must byte-equal `n`; and for a `:kind Supervisor` `Caixa`
17979 // carrying `None`, the composed [`SupervisorSpec`]'s
17980 // `.max_restarts()` must byte-equal the OTP-canonical `5`. Peer
17981 // of the sibling
17982 // `supervisor_view_estrategia_arm_routes_through_accessor`
17983 // (ed04d3c) composition pin.
17984 for max_restarts in [1u32, 5, 1000] {
17985 let c = caixa_supervisor_with_max_restarts_and_window(Some(max_restarts), None);
17986 let view = c.supervisor_view().expect(
17987 "supervisor_view must materialize a SupervisorSpec for a \
17988 :kind Supervisor Caixa carrying a Some(:max-restarts)",
17989 );
17990 assert_eq!(
17991 view.max_restarts(),
17992 max_restarts,
17993 "supervisor_view must carry the outer \
17994 Caixa::max_restarts() Some arm onto the composed \
17995 SupervisorSpec.max_restarts field verbatim (got {}, \
17996 expected {max_restarts})",
17997 view.max_restarts(),
17998 );
17999 }
18000 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
18001 let view = c.supervisor_view().expect(
18002 "supervisor_view must materialize a SupervisorSpec for a \
18003 :kind Supervisor Caixa carrying a None :max-restarts",
18004 );
18005 assert_eq!(
18006 view.max_restarts(),
18007 5,
18008 "supervisor_view must project the outer \
18009 Caixa::max_restarts() None arm onto the OTP-canonical \
18010 {{intensity, 5, 60}} default (5) through the flat-spread \
18011 unwrap_or(5) fold (got {})",
18012 view.max_restarts(),
18013 );
18014 assert!(
18015 c.max_restarts().is_none(),
18016 "Caixa::max_restarts() must remain None on the author-\
18017 omitted arm — the supervisor_view fold must not mutate \
18018 the outer flat-spread presence bit",
18019 );
18020 }
18021
18022 #[test]
18023 fn supervisor_view_estrategia_fallback_routes_through_lifted_default() {
18024 // Composition pin: [`Caixa::supervisor_view`]'s author-omitted
18025 // `:estrategia` arm must degrade onto the substrate-canonical
18026 // [`crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT`] typed
18027 // `pub const` — the Erlang/OTP-canonical `one_for_one` strategy
18028 // half of Learn You Some Erlang's `{one_for_one, intensity, 5, 60}`
18029 // worker-supervisor default — rather than the transitively-
18030 // derived [`crate::supervisor::RestartStrategy::default`] route
18031 // the prior `.unwrap_or_default()` fold reached for. Prior to the
18032 // lift the composition site carried `.unwrap_or_default()` with
18033 // no compile-time link back to the shared OTP-canonical strategy
18034 // default that the paired [`crate::supervisor::Default for
18035 // RestartStrategy`] impl and the [`crate::supervisor::Default for
18036 // SupervisorSpec`] impl's struct-literal `estrategia` field both
18037 // (now) route through the same lifted constant — so a future
18038 // rebrand of the OTP-canonical strategy default (an OTP
18039 // `rest_for_one` widening once the substrate discovers startup-
18040 // order-coupled child cohorts as the more common worker-
18041 // supervisor shape, a per-cluster overlay the operator pins
18042 // through the MESH-COMPOSITION §III.2 supervision-canary
18043 // `:estrategia-overrides` roadmap slot) would have had to migrate
18044 // the paired `MaxIntensity` + `Period` halves through the lifted
18045 // constants and the `one_for_one` half through a
18046 // `RestartStrategy::default()` route in lockstep or a
18047 // `:kind Supervisor` caixa carrying an author-omitted
18048 // `:estrategia` slot would silently resolve to a `SupervisorSpec`
18049 // whose `estrategia` disagreed with the paired
18050 // `SupervisorSpec::default()` view. Byte-parity against the
18051 // lifted constant closes the split. Peer of the sibling
18052 // [`supervisor_view_max_restarts_fallback_routes_through_lifted_default`]
18053 // composition pin on the paired `MaxIntensity` half + the
18054 // [`crate::supervisor::restart_strategy_default_routes_through_lifted_default`]
18055 // + [`crate::supervisor::supervisor_spec_default_estrategia_routes_through_lifted_default`]
18056 // pins on the sibling entry points onto the shared substrate
18057 // constant.
18058 use crate::CaixaKind;
18059 use crate::supervisor::{ChildSpec, RestartPolicy};
18060 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
18061 c.kind = CaixaKind::Supervisor;
18062 c.estrategia = None;
18063 c.children = vec![ChildSpec {
18064 caixa: "worker".into(),
18065 versao: "^0.1".into(),
18066 restart: RestartPolicy::Permanent,
18067 }];
18068 let view = c.supervisor_view().expect(
18069 "supervisor_view must materialize a SupervisorSpec for a \
18070 :kind Supervisor Caixa carrying a None :estrategia",
18071 );
18072 assert_eq!(
18073 view.estrategia(),
18074 crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT,
18075 "supervisor_view must degrade the outer \
18076 Caixa::estrategia() None arm onto the lifted \
18077 SUPERVISOR_ESTRATEGIA_DEFAULT typed pub const (got {:?}, \
18078 expected {:?})",
18079 view.estrategia(),
18080 crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT,
18081 );
18082 }
18083
18084 #[test]
18085 fn supervisor_view_max_restarts_fallback_routes_through_lifted_default() {
18086 // Composition pin: [`Caixa::supervisor_view`]'s author-omitted
18087 // `:max-restarts` arm must degrade onto the substrate-canonical
18088 // [`crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT`] typed
18089 // `pub const` — the Erlang/OTP-canonical `{intensity, 5, 60}`
18090 // `MaxIntensity` default — rather than a raw `5` literal. Prior
18091 // to the lift the composition site carried an inline
18092 // `.unwrap_or(5)` with no compile-time link back to the shared
18093 // OTP-canonical default that the serde-side
18094 // `#[serde(default = "default_max_restarts")]` wire-format arm
18095 // and the [`Default for crate::supervisor::SupervisorSpec`]
18096 // struct-literal default arm both key off — so a future rebrand
18097 // of the OTP-canonical default (Elixir's `Supervisor` `3`
18098 // default, a per-cluster overlay the operator pins through the
18099 // MESH-COMPOSITION §III.2 supervision-canary
18100 // `:supervisor :max-restarts-overrides` roadmap slot) would
18101 // have had to be threaded through both the serde-side helper
18102 // and this view-construction arm in lockstep or a `:kind
18103 // Supervisor` caixa carrying `:max-restarts ()` would silently
18104 // resolve to a `SupervisorSpec` whose `max_restarts` disagreed
18105 // with the same fixture's serde-side `SupervisorSpec` view (an
18106 // author-omitted slot round-tripping through
18107 // `SupervisorSpec::default()` to the lifted constant, then
18108 // splitting to a stale literal past `supervisor_view`).
18109 // Byte-parity against the lifted constant closes the split.
18110 // Peer of the sibling
18111 // [`crate::supervisor::default_max_restarts_helper_routes_through_lifted_default`]
18112 // + [`crate::supervisor::supervisor_spec_default_max_restarts_routes_through_lifted_default`]
18113 // composition pins that close the same routing on the two
18114 // sibling entry points onto the shared substrate constant.
18115 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
18116 let view = c.supervisor_view().expect(
18117 "supervisor_view must materialize a SupervisorSpec for a \
18118 :kind Supervisor Caixa carrying a None :max-restarts",
18119 );
18120 assert_eq!(
18121 view.max_restarts(),
18122 crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT,
18123 "supervisor_view must degrade the outer \
18124 Caixa::max_restarts() None arm onto the lifted \
18125 SUPERVISOR_MAX_RESTARTS_DEFAULT typed pub const (got {}, \
18126 expected {})",
18127 view.max_restarts(),
18128 crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT,
18129 );
18130 }
18131
18132 #[test]
18133 fn restart_window_returns_restart_window_option_verbatim_across_permutations() {
18134 // Value-shape pin: [`Caixa::restart_window`] returns the
18135 // `:restart-window` typed `Option<String>` verbatim as an
18136 // `Option<&str>`, borrowed from the typed slot's own storage,
18137 // byte-equal across the author-omitted `None` arm and each of
18138 // the representative fixtures in the accept-set — the canonical
18139 // `"60s"` from `{intensity, 5, 60}`, the sibling
18140 // canonical-magnitude forms (`"5m"` / `"1h"` / `"500ms"` / `"30"`
18141 // / `"0s"`) the shared codec's positive-set sweep pin covers,
18142 // plus a past-the-guard sentinel (`"1.5s"` — the fractional-
18143 // seconds drift the sibling [`Self::validate_restart_window`]
18144 // gate refuses; the accessor must ship the raw slot verbatim
18145 // so struct-literal fixtures continue to expose the drift at
18146 // the accessor boundary). Third outer top-level [`Caixa`]
18147 // supervisor-tree flat-spread pin — extends the sub-family onto
18148 // the sibling `Option<&str>` raw-duration-string arm.
18149 for window in [
18150 None,
18151 Some("60s"),
18152 Some("5m"),
18153 Some("1h"),
18154 Some("500ms"),
18155 Some("1.5s"),
18156 Some(""),
18157 ] {
18158 let c = caixa_with_restart_window(window);
18159 assert_eq!(
18160 c.restart_window(),
18161 window,
18162 "Caixa::restart_window must return :restart-window \
18163 verbatim as Option<&str> (got {:?}, expected {window:?})",
18164 c.restart_window(),
18165 );
18166 assert_eq!(
18167 c.restart_window(),
18168 c.restart_window.as_deref(),
18169 "Caixa::restart_window accessor and \
18170 self.restart_window.as_deref() field access must \
18171 byte-equal — a byte-level drift would silently split \
18172 the paired Caixa::declared_supervisor_slots \
18173 presence-probe arm from the \
18174 Caixa::validate_restart_window shared-codec gate and \
18175 the Caixa::supervisor_view soft-swallowing fold",
18176 );
18177 }
18178 }
18179
18180 #[test]
18181 fn restart_window_projects_slice_by_borrow() {
18182 // The by-borrow pin: [`Caixa::restart_window`] returns
18183 // `Option<&str>` by borrow — the returned string slice borrows
18184 // the underlying `Option<String>` storage of the `:restart-window`
18185 // slot and the accessor must not clone on every call. Peer of
18186 // the sibling outer top-level [`Caixa`] `Option<&str>`-return
18187 // by-borrow pins on the universal-axis scalar family
18188 // (`licenca_projects_option_ref_by_borrow` /
18189 // `descricao_projects_option_ref_by_borrow` and siblings) —
18190 // extended onto the M2 supervisor-tree flat-spread
18191 // `Option<&str>` raw-duration-string axis.
18192 for window in [None, Some("60s"), Some("5m"), Some("")] {
18193 let c = caixa_with_restart_window(window);
18194 let first = c.restart_window();
18195 let second = c.restart_window();
18196 assert_eq!(
18197 first, second,
18198 "Caixa::restart_window must be idempotent — two \
18199 successive calls on the same &self must return the \
18200 same Option<&str>",
18201 );
18202 if let (Some(a), Some(b)) = (first, second) {
18203 assert_eq!(
18204 a.as_ptr(),
18205 b.as_ptr(),
18206 "Caixa::restart_window must borrow the underlying \
18207 String storage — two successive Some-arm calls must \
18208 return slices with the same backing pointer (a fresh \
18209 String clone would change the pointer on every call)",
18210 );
18211 }
18212 assert_eq!(
18213 first, window,
18214 "Caixa::restart_window must return :restart-window \
18215 verbatim by borrow — got {first:?}, expected {window:?}",
18216 );
18217 }
18218 }
18219
18220 #[test]
18221 fn declared_supervisor_slots_restart_window_arm_routes_through_accessor() {
18222 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
18223 // `:restart-window` presence-probe arm must key off
18224 // [`Caixa::restart_window`], not the raw
18225 // `self.restart_window.is_some()` field-probe. Structurally:
18226 // every `Caixa { restart_window: Some(_), .. }` must push
18227 // `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` onto the declared-slot
18228 // list, and a `Caixa { restart_window: None, .. }` must NOT
18229 // push the label. Peer of the sibling
18230 // `declared_supervisor_slots_max_restarts_arm_routes_through_accessor`
18231 // routing pin.
18232 for window in ["60s", "5m", "1h", "500ms", "1.5s", ""] {
18233 let c = caixa_with_restart_window(Some(window));
18234 let slots = c.declared_supervisor_slots();
18235 assert!(
18236 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
18237 "declared_supervisor_slots must push \
18238 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when \
18239 `:restart-window` is Some({window:?}) — the accessor \
18240 and the enumerator gate must route through the same \
18241 substrate-primitive typed dispatch on the outer \
18242 :restart-window presence bit (got slots={slots:?})",
18243 );
18244 }
18245 let c = caixa_with_restart_window(None);
18246 let slots = c.declared_supervisor_slots();
18247 assert!(
18248 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
18249 "declared_supervisor_slots must NOT push \
18250 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when `:restart-window` \
18251 is None — the author-omitted arm must route through the \
18252 accessor's None-return unchanged (got slots={slots:?})",
18253 );
18254 }
18255
18256 #[test]
18257 fn validate_restart_window_arm_routes_through_accessor() {
18258 // Composition pin: [`Caixa::validate_restart_window`]'s
18259 // shared-codec fold arm must key off [`Caixa::restart_window`],
18260 // not the raw `self.restart_window.as_deref()` field-projection.
18261 // Structurally: (1) `None` → `Ok(())` (the "omit the slot to
18262 // express no reset" canonical shape); (2) a canonical `Some`
18263 // arm (`"60s"`) → `Ok(())`; (3) a codec-rejected `Some` arm
18264 // (`"1.5s"`) → `Err(RestartWindowMalformed { restart_window,
18265 // .. })` carrying the offending raw string verbatim. The three
18266 // arms jointly pin that the validator's raw-string binding is
18267 // the accessor's return, not a peer projection — any future
18268 // silent detour that had the accessor collapse `Some("")` to
18269 // `None` would silently absorb the empty-after-trim refusal
18270 // case at the accessor boundary.
18271 caixa_with_restart_window(None)
18272 .validate_restart_window()
18273 .expect("None :restart-window must validate through the accessor");
18274 caixa_with_restart_window(Some("60s"))
18275 .validate_restart_window()
18276 .expect("canonical :restart-window \"60s\" must validate through the accessor");
18277 let err = caixa_with_restart_window(Some("1.5s"))
18278 .validate_restart_window()
18279 .expect_err("fractional-seconds :restart-window must fail through the accessor");
18280 assert!(
18281 matches!(
18282 err,
18283 ManifestError::RestartWindowMalformed { ref restart_window, .. }
18284 if restart_window == "1.5s"
18285 ),
18286 "validator must carry the offending raw string verbatim \
18287 from the accessor's borrowed &str (got {err:?})",
18288 );
18289 }
18290
18291 #[test]
18292 fn supervisor_view_restart_window_arm_routes_through_accessor() {
18293 // Composition pin: [`Caixa::supervisor_view`]'s
18294 // per-`:restart-window` [`SupervisorSpec`] construction arm
18295 // must key off [`Caixa::restart_window`]'s soft-swallowing
18296 // `.and_then(|s| duration_codec::parse(s).ok())` fold, not the
18297 // raw `self.restart_window.as_deref().and_then(…)` field-fold.
18298 // Structurally: (1) `None` → `SupervisorSpec.restart_window ==
18299 // None` (the "never reset" sentinel); (2) canonical `Some("60s")`
18300 // → `SupervisorSpec.restart_window == Some(Duration::from_secs(60))`
18301 // (the shared codec's canonical parse); (3) codec-rejected
18302 // `Some("1.5s")` → `SupervisorSpec.restart_window == None`
18303 // (the soft-swallow preserving the view's best-effort shape).
18304 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
18305 let view = c.supervisor_view().expect("Supervisor kind has a view");
18306 assert_eq!(
18307 view.restart_window(),
18308 None,
18309 "supervisor_view must project outer None :restart-window \
18310 onto None on the composed SupervisorSpec (never-reset \
18311 sentinel) through the accessor's None-return unchanged",
18312 );
18313
18314 let c = caixa_supervisor_with_max_restarts_and_window(None, Some("60s"));
18315 let view = c.supervisor_view().expect("Supervisor kind has a view");
18316 assert_eq!(
18317 view.restart_window(),
18318 Some(std::time::Duration::from_secs(60)),
18319 "supervisor_view must fold outer Some(\"60s\") through the \
18320 shared duration_codec into Duration::from_secs(60) on the \
18321 composed SupervisorSpec (accessor's Some(&str) → codec \
18322 parse → Some(Duration))",
18323 );
18324
18325 let c = caixa_supervisor_with_max_restarts_and_window(None, Some("1.5s"));
18326 let view = c.supervisor_view().expect("Supervisor kind has a view");
18327 assert_eq!(
18328 view.restart_window(),
18329 None,
18330 "supervisor_view must soft-swallow the shared-codec parse \
18331 failure to None (the view's best-effort shape the sibling \
18332 manifest-level validate_restart_window surfaces as \
18333 RestartWindowMalformed); the accessor's raw-string return \
18334 is the single input every downstream consumer keys off",
18335 );
18336 }
18337
18338 // ── Caixa::upgrade_from — outer top-level &[UpgradeFromEntry] composite-slice accessor ──
18339
18340 fn caixa_with_upgrade_from(upgrade_from: Vec<crate::upgrade::UpgradeFromEntry>) -> Caixa {
18341 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
18342 c.upgrade_from = upgrade_from;
18343 c
18344 }
18345
18346 #[test]
18347 fn upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations() {
18348 // The canonical per-`Caixa` `:upgrade-from` M2 typed-slot
18349 // outer-composite `&[UpgradeFromEntry]`-return slice-shape
18350 // pin: [`Caixa::upgrade_from`] must return the `:upgrade-from`
18351 // typed `Vec<UpgradeFromEntry>` verbatim as a
18352 // `&[UpgradeFromEntry]` slice-view over the same backing
18353 // buffer the raw `self.upgrade_from.as_slice()` field access
18354 // borrows from, element-equal across every representative
18355 // fixture in the accept-set — `[]` (the "no hot-upgrade path
18356 // declared" arm every `defcaixa` without an `:upgrade-from`
18357 // block carries; `#[serde(default)]` folds an omitted slot
18358 // onto `Vec::new()`), a canonical single-entry `Restart`
18359 // fixture (the shape most Servicos carry — a single prior
18360 // version with the fallback strategy), a canonical multi-
18361 // entry list carrying every typed instruction variant
18362 // (`LoadModule` / `StateChange` / `SoftPurge` / `Purge` /
18363 // `Restart`), and a past-the-guard sentinel — a duplicate-
18364 // `:from` `[(0.1.0, Restart), (0.1.0, Restart)]` entry pair
18365 // ([`crate::upgrade::validate_upgrade_from`] rejects through
18366 // `DuplicateFrom { from: "0.1.0" }` but the accessor must
18367 // ship the raw slot verbatim so struct-literal fixtures
18368 // continue to expose the duplicate at the accessor boundary).
18369 //
18370 // Pins against a future silent detour that returned an owned
18371 // `Vec<UpgradeFromEntry>` (which would type-check but silently
18372 // clone on every accessor call, breaking the zero-cost
18373 // projection every peer sibling slice accessor carries), a
18374 // `[dup, dup] → [dup]` dedup collapse (which would silently
18375 // absorb the `DuplicateFrom` refusal case at the accessor
18376 // boundary and the [`crate::StandardLayout::verify`] cross-
18377 // entry gate would silently accept a struct-literal `Caixa`
18378 // carrying the drift), a reference to an operator-resolved
18379 // overlay (the future per-cluster `:upgrade-overrides` slot
18380 // — its resolution must land at exactly this accessor body,
18381 // not silently divert the raw slot away from a second
18382 // consumer), or an axis-shuffled projection (a future detour
18383 // that reordered entries through the accessor would silently
18384 // split the paired [`crate::StandardLayout::verify`] per-
18385 // `:upgrade-from` shape gate's traversal input from the peer
18386 // [`crate::render::servico_m2_overlay`] emitter's projection
18387 // input, since the operator's hot-upgrade dispatch matches
18388 // per-`:from` and axis reordering would silently split the
18389 // per-entry script-path existence probe's iteration order
18390 // from the M2 overlay emitter's serialized-entry order).
18391 //
18392 // First outer top-level [`Caixa`] `&[Composite]`-return
18393 // slice accessor pin on the substrate primitive for M2 / M3
18394 // typed-slot vec-carry axes — opens the outer-`Caixa`
18395 // `&[Composite]` composite-slice projection pattern the
18396 // sibling `:children` [`crate::supervisor::ChildSpec`] /
18397 // `:membros` [`crate::aplicacao::Membro`] / `:contratos`
18398 // [`crate::aplicacao::WitContract`] future outer-composite-
18399 // slice pins fold on. Peer of the closed outer-`Caixa`
18400 // scalar `Option<&Composite>` composite-reference family the
18401 // sibling `limits` / `behavior` / `politicas` / `placement`
18402 // / `entrada` `..._returns_..._option_ref_verbatim_across_
18403 // permutations` pins closed (b2bd9d7 → e4128e4) — extends
18404 // the "byte-equal, borrow-shared" outer-accessor discipline
18405 // onto the outer-`Caixa` `&[Composite]` vec-carry altitude.
18406 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
18407 let fixtures: Vec<Vec<UpgradeFromEntry>> = vec![
18408 vec![],
18409 vec![UpgradeFromEntry {
18410 from: "0.0.1".into(),
18411 instructions: vec![UpgradeInstruction::Restart],
18412 }],
18413 vec![
18414 UpgradeFromEntry {
18415 from: "0.0.1".into(),
18416 instructions: vec![
18417 UpgradeInstruction::LoadModule {
18418 module: "demo".into(),
18419 },
18420 UpgradeInstruction::SoftPurge {
18421 module: "demo".into(),
18422 },
18423 ],
18424 },
18425 UpgradeFromEntry {
18426 from: "0.0.2".into(),
18427 instructions: vec![
18428 UpgradeInstruction::StateChange {
18429 script: "servicos/upgrade.lisp".into(),
18430 },
18431 UpgradeInstruction::Purge {
18432 module: "demo".into(),
18433 },
18434 UpgradeInstruction::Restart,
18435 ],
18436 },
18437 ],
18438 vec![
18439 UpgradeFromEntry {
18440 from: "0.1.0".into(),
18441 instructions: vec![UpgradeInstruction::Restart],
18442 },
18443 UpgradeFromEntry {
18444 from: "0.1.0".into(),
18445 instructions: vec![UpgradeInstruction::Restart],
18446 },
18447 ],
18448 ];
18449 for upgrade_from in fixtures {
18450 let c = caixa_with_upgrade_from(upgrade_from.clone());
18451 assert_eq!(
18452 c.upgrade_from(),
18453 upgrade_from.as_slice(),
18454 "Caixa::upgrade_from must return :upgrade-from \
18455 verbatim (got {:?}, expected {upgrade_from:?})",
18456 c.upgrade_from(),
18457 );
18458 assert_eq!(
18459 c.upgrade_from(),
18460 c.upgrade_from.as_slice(),
18461 "Caixa::upgrade_from must element-equal the raw \
18462 `self.upgrade_from.as_slice()` field access across \
18463 every value in the Vec<UpgradeFromEntry> accept-set",
18464 );
18465 assert_eq!(
18466 c.upgrade_from().is_empty(),
18467 c.upgrade_from.is_empty(),
18468 "Caixa::upgrade_from().is_empty() must byte-equal \
18469 self.upgrade_from.is_empty() — a presence-bit drift \
18470 would silently split the paired \
18471 Caixa::declared_servico_slots M2 declared-slot \
18472 enumerator's presence probe from the peer \
18473 crate::render::servico_m2_overlay M2 overlay \
18474 emitter's presence gate",
18475 );
18476 }
18477 }
18478
18479 #[test]
18480 fn declared_servico_slots_upgrade_from_arm_routes_through_accessor() {
18481 // Composition pin: [`Caixa::declared_servico_slots`]'s
18482 // `:upgrade-from` presence-probe arm must key off
18483 // [`Caixa::upgrade_from`], not the raw
18484 // `self.upgrade_from.is_empty()` field-probe. Structurally: a
18485 // `Caixa { upgrade_from: vec![UpgradeFromEntry { from: "0.0.1",
18486 // instructions: vec![Restart] }], .. }` must push
18487 // `M2_AUTHOR_KEY_UPGRADE_FROM` onto the declared-slot list
18488 // (the presence bit is non-empty, so the M2 kind-coherence
18489 // gate must surface the slot as "declared"), and a `Caixa {
18490 // upgrade_from: vec![], .. }` must NOT push the label (the
18491 // "author omitted the slot entirely" arm — the empty-slice
18492 // partition the serde-default folds onto). The pair jointly
18493 // pins the accessor + declared-slot enumerator composition:
18494 // any future silent detour that had the accessor collapse
18495 // `[Restart]` to `[]` (a `.filter(|e| !e.instructions.
18496 // is_empty())` projection) would silently absorb the
18497 // "declared but degenerate" arm at the accessor boundary and
18498 // the [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-
18499 // coherence gate would silently accept a struct-literal
18500 // `Caixa` carrying the drift.
18501 //
18502 // Peer of the sibling
18503 // `declared_servico_slots_limits_arm_routes_through_accessor`
18504 // (b2bd9d7) and
18505 // `declared_servico_slots_behavior_arm_routes_through_accessor`
18506 // (35d8b52) composition pins on the sibling `:limits` /
18507 // `:behavior` outer-`Option<&Composite>` arms — same "the
18508 // enumerator gate must route through the substrate-primitive
18509 // typed dispatch" discipline extended onto the third M2
18510 // Servico-runtime slot axis, closing the enumerator's routing
18511 // invariant on every M2 arm.
18512 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
18513 let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
18514 from: "0.0.1".into(),
18515 instructions: vec![UpgradeInstruction::Restart],
18516 }]);
18517 let slots = c.declared_servico_slots();
18518 assert!(
18519 slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
18520 "declared_servico_slots must push \
18521 M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
18522 non-empty — the accessor and the enumerator gate must \
18523 route through the same substrate-primitive typed \
18524 dispatch on the outer :upgrade-from presence bit (got \
18525 slots={slots:?})",
18526 );
18527 let c = caixa_with_upgrade_from(vec![]);
18528 let slots = c.declared_servico_slots();
18529 assert!(
18530 !slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
18531 "declared_servico_slots must NOT push \
18532 M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
18533 empty — the author-omitted arm must route through the \
18534 accessor's empty-slice return unchanged (got \
18535 slots={slots:?})",
18536 );
18537 }
18538
18539 #[test]
18540 fn servico_m2_overlay_upgrade_from_arm_routes_through_accessor() {
18541 // Composition pin: [`crate::render::servico_m2_overlay`]'s
18542 // per-`:upgrade-from` M2 overlay emit arm must key off
18543 // [`Caixa::upgrade_from`], not the raw
18544 // `!caixa.upgrade_from.is_empty()` presence gate + the
18545 // `serde_yaml::to_value(&caixa.upgrade_from)` projection.
18546 // Structurally: a `Caixa { upgrade_from: vec![UpgradeFromEntry
18547 // { from: "0.0.1", instructions: vec![Restart] }], .. }` must
18548 // surface the `M2_KEY_UPGRADE_FROM` key with a per-entry
18549 // sequence in the overlay (the emitter fans onto the serde
18550 // slice-serialization), and a `Caixa { upgrade_from: vec![],
18551 // .. }` must omit the key entirely (the empty-slice
18552 // partition — the `!.is_empty()` outer gate elides the key
18553 // when the author omitted the slot). The pair jointly pins
18554 // the accessor + M2 overlay emitter composition: any future
18555 // silent detour that had the accessor return a fresh-cloned
18556 // `Vec<UpgradeFromEntry>` copy would silently break the
18557 // reference-identity pin the peer per-entry
18558 // `serde_yaml::to_value(caixa.upgrade_from())` projection
18559 // reads from — the projection would clone once per accessor
18560 // call instead of borrowing the storage buffer verbatim.
18561 //
18562 // Peer of the sibling
18563 // `servico_m2_overlay_limits_arm_routes_through_accessor`
18564 // (b2bd9d7) and
18565 // `servico_m2_overlay_behavior_arm_routes_through_accessor`
18566 // (35d8b52) composition pins on the sibling `:limits` /
18567 // `:behavior` outer-`Option<&Composite>` arms — same "the
18568 // M2 overlay emitter must route through the substrate-
18569 // primitive typed dispatch" discipline extended onto the
18570 // third M2 Servico-runtime slot axis, closing the overlay
18571 // emitter's routing invariant on every M2 arm.
18572 use crate::render::{M2_KEY_UPGRADE_FROM, servico_m2_overlay};
18573 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
18574 let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
18575 from: "0.0.1".into(),
18576 instructions: vec![UpgradeInstruction::Restart],
18577 }]);
18578 let overlay = servico_m2_overlay(&c).unwrap();
18579 assert!(
18580 overlay.contains_key(M2_KEY_UPGRADE_FROM),
18581 "servico_m2_overlay must surface M2_KEY_UPGRADE_FROM when \
18582 `:upgrade-from` is non-empty — the accessor and the M2 \
18583 overlay emitter must route through the same substrate- \
18584 primitive typed dispatch on the outer :upgrade-from \
18585 slice (got overlay={overlay:?})",
18586 );
18587 let c = caixa_with_upgrade_from(vec![]);
18588 let overlay = servico_m2_overlay(&c).unwrap();
18589 assert!(
18590 !overlay.contains_key(M2_KEY_UPGRADE_FROM),
18591 "servico_m2_overlay must omit M2_KEY_UPGRADE_FROM when \
18592 `:upgrade-from` is empty — the empty-slice partition \
18593 must route through the accessor's empty-slice return \
18594 unchanged (got overlay={overlay:?})",
18595 );
18596 }
18597
18598 #[test]
18599 fn upgrade_from_projects_slice_by_borrow() {
18600 // The by-borrow pin: [`Caixa::upgrade_from`] returns
18601 // `&[UpgradeFromEntry]` by borrow — the returned slice
18602 // borrows the underlying `Vec<UpgradeFromEntry>` storage of
18603 // the `:upgrade-from` slot and the accessor must not clone
18604 // the backing `Vec` on every call. Peer of the sibling
18605 // outer top-level [`Caixa`] `&[T]`-return by-borrow pins
18606 // (`autores_projects_slice_by_borrow` b5d813f,
18607 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
18608 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
18609 // `exe_projects_slice_by_borrow` 65d9527,
18610 // `servicos_projects_slice_by_borrow` 611f78b,
18611 // `deps_projects_slice_by_borrow` ad34b4e,
18612 // `deps_dev_projects_slice_by_borrow` f7fd81e) on the
18613 // sibling outer top-level [`Caixa`] scalar-element `&[T]`
18614 // axes — extended here to the first outer-`Caixa`
18615 // composite-element `&[Composite]` axis: the accessor's
18616 // returned slice must borrow from `&self` (the returned
18617 // reference's lifetime is tied to `&self`), and calling the
18618 // accessor twice on the same [`Caixa`] must yield slices
18619 // that are pointer-equal (the underlying byte-buffer is the
18620 // storage `Vec`'s allocation, not a fresh copy) as well as
18621 // value-equal (idempotent, no side effects on `&self`).
18622 //
18623 // Pins against a future silent detour that returned an owned
18624 // `Vec<UpgradeFromEntry>` (which would type-check but
18625 // silently clone on every call), a `&Vec<UpgradeFromEntry>`
18626 // return (which would leak the backing `Vec`'s
18627 // grow/push/reserve surface no downstream consumer reaches
18628 // for), or a one-arm-only accessor that returned a
18629 // saturating value on some sentinel input.
18630 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
18631 for upgrade_from in [
18632 vec![],
18633 vec![UpgradeFromEntry {
18634 from: "0.0.1".into(),
18635 instructions: vec![UpgradeInstruction::Restart],
18636 }],
18637 vec![
18638 UpgradeFromEntry {
18639 from: "0.0.1".into(),
18640 instructions: vec![UpgradeInstruction::Restart],
18641 },
18642 UpgradeFromEntry {
18643 from: "0.0.2".into(),
18644 instructions: vec![UpgradeInstruction::SoftPurge {
18645 module: "demo".into(),
18646 }],
18647 },
18648 ],
18649 ] {
18650 let c = caixa_with_upgrade_from(upgrade_from.clone());
18651 let first = c.upgrade_from();
18652 let second = c.upgrade_from();
18653 assert_eq!(
18654 first, second,
18655 "Caixa::upgrade_from must be idempotent — two \
18656 successive calls on the same &self must return the \
18657 same &[UpgradeFromEntry]",
18658 );
18659 assert_eq!(
18660 first.as_ptr(),
18661 second.as_ptr(),
18662 "Caixa::upgrade_from must borrow the underlying \
18663 Vec<UpgradeFromEntry> storage — two successive calls \
18664 must return slices with the same backing pointer (a \
18665 fresh Vec<UpgradeFromEntry> clone would change the \
18666 pointer on every call)",
18667 );
18668 assert_eq!(
18669 first,
18670 upgrade_from.as_slice(),
18671 "Caixa::upgrade_from must return :upgrade-from \
18672 verbatim by borrow — got {first:?}, expected \
18673 {upgrade_from:?}",
18674 );
18675 }
18676 }
18677
18678 // ── Caixa::children — outer top-level &[ChildSpec] composite-slice accessor ──
18679
18680 fn caixa_with_children(children: Vec<crate::supervisor::ChildSpec>) -> Caixa {
18681 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
18682 c.children = children;
18683 c
18684 }
18685
18686 #[test]
18687 fn children_returns_children_slice_verbatim_across_permutations() {
18688 // The canonical per-`Caixa` `:children` M2 supervisor-tree-slot
18689 // outer-composite `&[ChildSpec]`-return slice-shape pin:
18690 // [`Caixa::children`] must return the `:children` typed
18691 // `Vec<ChildSpec>` verbatim as a `&[ChildSpec]` slice-view over
18692 // the same backing buffer the raw `self.children.as_slice()`
18693 // field access borrows from, element-equal across every
18694 // representative fixture in the accept-set — `[]` (the "no
18695 // static children declared" arm every non-`Supervisor`-kind
18696 // `defcaixa` carries by `#[serde(default)]` and every
18697 // `SimpleOneForOne` supervisor carries by cross-slot refusal),
18698 // a canonical single-child `Permanent` fixture (the shape
18699 // most `OneForOne` supervisors carry — a single long-running
18700 // worker child), a canonical multi-child list carrying every
18701 // typed restart-policy variant (`Permanent` / `Transient` /
18702 // `Temporary`), and a past-the-guard sentinel — a duplicate
18703 // `:caixa` `[("w", ...), ("w", ...)]` entry pair
18704 // ([`crate::SupervisorSpec::validate`] rejects through
18705 // `DuplicateChildNome { nome: "w" }` but the accessor must
18706 // ship the raw slot verbatim so struct-literal fixtures
18707 // continue to expose the duplicate at the accessor boundary).
18708 //
18709 // Pins against a future silent detour that returned an owned
18710 // `Vec<ChildSpec>` (which would type-check but silently clone
18711 // on every accessor call, breaking the zero-cost projection
18712 // every peer sibling slice accessor carries), a `[dup, dup] →
18713 // [dup]` dedup collapse (which would silently absorb the
18714 // `DuplicateChildNome` refusal case at the accessor boundary
18715 // and the [`crate::StandardLayout::verify`] cross-child gate
18716 // would silently accept a struct-literal `Caixa` carrying the
18717 // drift), a reference to an operator-resolved overlay (the
18718 // future per-cluster `:children-overrides` slot — its
18719 // resolution must land at exactly this accessor body, not
18720 // silently divert the raw slot away from a second consumer),
18721 // or an axis-shuffled projection (a future detour that
18722 // reordered children through the accessor would silently
18723 // split the paired [`crate::StandardLayout::verify`] per-
18724 // supervisor gate's traversal input from the peer
18725 // [`Self::supervisor_view`] fold-in path's clone-order input,
18726 // since the OTP `RestForOne` restart strategy dispatches on
18727 // declared child order and axis reordering would silently
18728 // split the operator's per-cluster restart-fan-out order
18729 // from the caixa.lisp source-order).
18730 //
18731 // Second outer top-level [`Caixa`] `&[Composite]`-return slice
18732 // accessor pin on the substrate primitive for M2 / M3 typed-
18733 // slot vec-carry axes — folds on the outer-`Caixa`
18734 // `&[Composite]` composite-slice sub-family the sibling
18735 // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
18736 // (2a1f907) pin opened, peer at the outer altitude of the
18737 // closed inner-`SupervisorSpec` `SupervisorSpec::children`
18738 // (bc92bce) accessor on the same OTP-supervisor static-child-
18739 // list axis.
18740 use crate::supervisor::{ChildSpec, RestartPolicy};
18741 let fixtures: Vec<Vec<ChildSpec>> = vec![
18742 vec![],
18743 vec![ChildSpec {
18744 caixa: "worker".into(),
18745 versao: "^0.1".into(),
18746 restart: RestartPolicy::Permanent,
18747 }],
18748 vec![
18749 ChildSpec {
18750 caixa: "worker-a".into(),
18751 versao: "^0.1".into(),
18752 restart: RestartPolicy::Permanent,
18753 },
18754 ChildSpec {
18755 caixa: "worker-b".into(),
18756 versao: "^0.1".into(),
18757 restart: RestartPolicy::Transient,
18758 },
18759 ChildSpec {
18760 caixa: "worker-c".into(),
18761 versao: "^0.1".into(),
18762 restart: RestartPolicy::Temporary,
18763 },
18764 ],
18765 vec![
18766 ChildSpec {
18767 caixa: "w".into(),
18768 versao: "^0.1".into(),
18769 restart: RestartPolicy::Permanent,
18770 },
18771 ChildSpec {
18772 caixa: "w".into(),
18773 versao: "^0.1".into(),
18774 restart: RestartPolicy::Permanent,
18775 },
18776 ],
18777 ];
18778 for children in fixtures {
18779 let c = caixa_with_children(children.clone());
18780 assert_eq!(
18781 c.children(),
18782 children.as_slice(),
18783 "Caixa::children must return :children verbatim \
18784 (got {:?}, expected {children:?})",
18785 c.children(),
18786 );
18787 assert_eq!(
18788 c.children(),
18789 c.children.as_slice(),
18790 "Caixa::children must element-equal the raw \
18791 `self.children.as_slice()` field access across \
18792 every value in the Vec<ChildSpec> accept-set",
18793 );
18794 assert_eq!(
18795 c.children().is_empty(),
18796 c.children.is_empty(),
18797 "Caixa::children().is_empty() must byte-equal \
18798 self.children.is_empty() — a presence-bit drift \
18799 would silently split the paired \
18800 Caixa::declared_supervisor_slots supervisor-tree \
18801 declared-slot enumerator's presence probe from the \
18802 peer Caixa::supervisor_view typed-view composer's \
18803 fold-in path",
18804 );
18805 }
18806 }
18807
18808 #[test]
18809 fn declared_supervisor_slots_children_arm_routes_through_accessor() {
18810 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
18811 // `:children` presence-probe arm must key off
18812 // [`Caixa::children`], not the raw
18813 // `!self.children.is_empty()` field-probe. Structurally: a
18814 // `Caixa { children: vec![ChildSpec { caixa: "w", versao:
18815 // "^0.1", restart: Permanent }], .. }` must push
18816 // `SUPERVISOR_AUTHOR_KEY_CHILDREN` onto the declared-slot list
18817 // (the presence bit is non-empty, so the supervisor-tree
18818 // kind-coherence gate must surface the slot as "declared"),
18819 // and a `Caixa { children: vec![], .. }` must NOT push the
18820 // label (the "author omitted the slot entirely" arm — the
18821 // empty-slice partition the serde-default folds onto). The
18822 // pair jointly pins the accessor + declared-slot enumerator
18823 // composition: any future silent detour that had the accessor
18824 // collapse `[Permanent]` to `[]` (a `.filter(|c| c.nome() !=
18825 // "__reserved__")` projection) would silently absorb the
18826 // "declared but degenerate" arm at the accessor boundary and
18827 // the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
18828 // kind-coherence gate would silently accept a struct-literal
18829 // `Caixa` carrying the drift.
18830 //
18831 // Peer of the sibling
18832 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
18833 // (2a1f907) on the M2 `:upgrade-from` composite-slice arm —
18834 // same "the enumerator gate must route through the substrate-
18835 // primitive typed dispatch" discipline extended onto the
18836 // supervisor-tree `:children` composite-slice arm.
18837 use crate::supervisor::{ChildSpec, RestartPolicy};
18838 let c = caixa_with_children(vec![ChildSpec {
18839 caixa: "w".into(),
18840 versao: "^0.1".into(),
18841 restart: RestartPolicy::Permanent,
18842 }]);
18843 let slots = c.declared_supervisor_slots();
18844 assert!(
18845 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
18846 "declared_supervisor_slots must push \
18847 SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
18848 non-empty — the accessor and the enumerator gate must \
18849 route through the same substrate-primitive typed \
18850 dispatch on the outer :children presence bit (got \
18851 slots={slots:?})",
18852 );
18853 let c = caixa_with_children(vec![]);
18854 let slots = c.declared_supervisor_slots();
18855 assert!(
18856 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
18857 "declared_supervisor_slots must NOT push \
18858 SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
18859 empty — the author-omitted arm must route through the \
18860 accessor's empty-slice return unchanged (got \
18861 slots={slots:?})",
18862 );
18863 }
18864
18865 #[test]
18866 fn supervisor_view_children_arm_routes_through_accessor() {
18867 // Composition pin: [`Caixa::supervisor_view`]'s per-`:children`
18868 // fold-in arm must key off [`Caixa::children`], not the raw
18869 // `self.children.clone()` field-clone. Structurally: a `Caixa {
18870 // kind: Supervisor, estrategia: Some(OneForOne), children:
18871 // vec![ChildSpec { caixa: "w", .. }], .. }` must fold the
18872 // per-child list through the accessor into the typed
18873 // [`SupervisorSpec`] view's `children` field verbatim — every
18874 // entry the accessor surfaces must land in the view's
18875 // `children` slot in the same order. The pair jointly pins the
18876 // accessor + view-composer composition: any future silent
18877 // detour that had the accessor return a fresh-cloned
18878 // `Vec<ChildSpec>` copy would silently break the reference-
18879 // identity pin the peer `supervisor_view` fold-in path reads
18880 // from — the fold would clone once more per accessor call
18881 // instead of borrowing the storage buffer verbatim once.
18882 //
18883 // Peer of the sibling
18884 // `supervisor_view_kind_gate_routes_through_accessor` (35d8b52-
18885 // family) composition pin on the peer kind-gate arm — same
18886 // "the view composer must route through the substrate-
18887 // primitive typed dispatch" discipline extended onto the
18888 // per-`:children` fold-in arm, closing the supervisor-view
18889 // composer's routing invariant on the composite-slice input.
18890 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
18891 let mut c = caixa_with_children(vec![
18892 ChildSpec {
18893 caixa: "worker-a".into(),
18894 versao: "^0.1".into(),
18895 restart: RestartPolicy::Permanent,
18896 },
18897 ChildSpec {
18898 caixa: "worker-b".into(),
18899 versao: "^0.1".into(),
18900 restart: RestartPolicy::Transient,
18901 },
18902 ]);
18903 c.kind = crate::CaixaKind::Supervisor;
18904 c.estrategia = Some(RestartStrategy::OneForOne);
18905 let view = c
18906 .supervisor_view()
18907 .expect("Supervisor kind must produce a supervisor_view");
18908 assert_eq!(
18909 view.children(),
18910 c.children(),
18911 "supervisor_view must fold Caixa::children verbatim into \
18912 SupervisorSpec::children — the accessor and the view \
18913 composer must route through the same substrate-primitive \
18914 typed dispatch on the outer :children slice (got view \
18915 children={:?}, expected {:?})",
18916 view.children(),
18917 c.children(),
18918 );
18919 }
18920
18921 #[test]
18922 fn children_projects_slice_by_borrow() {
18923 // The by-borrow pin: [`Caixa::children`] returns
18924 // `&[ChildSpec]` by borrow — the returned slice borrows the
18925 // underlying `Vec<ChildSpec>` storage of the `:children` slot
18926 // and the accessor must not clone the backing `Vec` on every
18927 // call. Peer of the sibling outer top-level [`Caixa`]
18928 // `&[T]`-return by-borrow pins (`autores_projects_slice_by_borrow`
18929 // b5d813f, `etiquetas_projects_slice_by_borrow` 78c7d3c,
18930 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
18931 // `exe_projects_slice_by_borrow` 65d9527,
18932 // `servicos_projects_slice_by_borrow` 611f78b,
18933 // `deps_projects_slice_by_borrow` ad34b4e,
18934 // `deps_dev_projects_slice_by_borrow` f7fd81e,
18935 // `upgrade_from_projects_slice_by_borrow` 2a1f907) on the
18936 // sibling outer top-level [`Caixa`] scalar-element and
18937 // composite-element `&[T]` axes — folds on the outer-`Caixa`
18938 // composite-element `&[Composite]` axis: the accessor's
18939 // returned slice must borrow from `&self` (the returned
18940 // reference's lifetime is tied to `&self`), and calling the
18941 // accessor twice on the same [`Caixa`] must yield slices
18942 // that are pointer-equal (the underlying byte-buffer is the
18943 // storage `Vec`'s allocation, not a fresh copy) as well as
18944 // value-equal (idempotent, no side effects on `&self`).
18945 //
18946 // Pins against a future silent detour that returned an owned
18947 // `Vec<ChildSpec>` (which would type-check but silently clone
18948 // on every call), a `&Vec<ChildSpec>` return (which would leak
18949 // the backing `Vec`'s grow/push/reserve surface no downstream
18950 // consumer reaches for), or a one-arm-only accessor that
18951 // returned a saturating value on some sentinel input.
18952 use crate::supervisor::{ChildSpec, RestartPolicy};
18953 for children in [
18954 vec![],
18955 vec![ChildSpec {
18956 caixa: "w".into(),
18957 versao: "^0.1".into(),
18958 restart: RestartPolicy::Permanent,
18959 }],
18960 vec![
18961 ChildSpec {
18962 caixa: "worker-a".into(),
18963 versao: "^0.1".into(),
18964 restart: RestartPolicy::Permanent,
18965 },
18966 ChildSpec {
18967 caixa: "worker-b".into(),
18968 versao: "^0.1".into(),
18969 restart: RestartPolicy::Transient,
18970 },
18971 ],
18972 ] {
18973 let c = caixa_with_children(children.clone());
18974 let first = c.children();
18975 let second = c.children();
18976 assert_eq!(
18977 first, second,
18978 "Caixa::children must be idempotent — two successive \
18979 calls on the same &self must return the same \
18980 &[ChildSpec]",
18981 );
18982 assert_eq!(
18983 first.as_ptr(),
18984 second.as_ptr(),
18985 "Caixa::children must borrow the underlying \
18986 Vec<ChildSpec> storage — two successive calls must \
18987 return slices with the same backing pointer (a fresh \
18988 Vec<ChildSpec> clone would change the pointer on \
18989 every call)",
18990 );
18991 assert_eq!(
18992 first,
18993 children.as_slice(),
18994 "Caixa::children must return :children verbatim by \
18995 borrow — got {first:?}, expected {children:?}",
18996 );
18997 }
18998 }
18999
19000 // ── Caixa::membros — outer top-level &[Membro] composite-slice accessor ──
19001
19002 fn caixa_aplicacao_with_membros(membros: Vec<crate::aplicacao::Membro>) -> Caixa {
19003 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19004 c.kind = CaixaKind::Aplicacao;
19005 c.membros = membros;
19006 c
19007 }
19008
19009 #[test]
19010 fn membros_returns_membros_slice_verbatim_across_permutations() {
19011 // The canonical per-`Caixa` `:membros` M3 mesh-slot outer-
19012 // composite `&[Membro]`-return slice-shape pin:
19013 // [`Caixa::membros`] must return the `:membros` typed
19014 // `Vec<Membro>` verbatim as a `&[Membro]` slice-view over the
19015 // same backing buffer the raw `self.membros.as_slice()` field
19016 // access borrows from, element-equal across every
19017 // representative fixture in the accept-set — `[]` (the "no
19018 // members declared" arm every non-`Aplicacao`-kind `defcaixa`
19019 // carries by `#[serde(default)]` and every partially-authored
19020 // Aplicacao carries before the
19021 // [`crate::AplicacaoError::MembrosEmpty`] gate fires), a
19022 // canonical single-member fixture (the shape a minimal
19023 // Aplicacao carries — one Servico wrapping one contained
19024 // computation), a canonical multi-member list carrying three
19025 // distinct entries (the canonical checkout-shape Aplicacao —
19026 // cart / pricing / auth — every canonical example carries), and
19027 // a past-the-guard sentinel — a duplicate `:caixa`
19028 // `[("cart", ...), ("cart", ...)]` entry pair
19029 // ([`crate::AplicacaoSpec::validate`] rejects through
19030 // `DuplicateMembro { nome: "cart" }` but the accessor must ship
19031 // the raw slot verbatim so struct-literal fixtures continue to
19032 // expose the duplicate at the accessor boundary).
19033 //
19034 // Pins against a future silent detour that returned an owned
19035 // `Vec<Membro>` (which would type-check but silently clone on
19036 // every accessor call, breaking the zero-cost projection every
19037 // peer sibling slice accessor carries), a `[dup, dup] → [dup]`
19038 // dedup collapse (which would silently absorb the
19039 // `DuplicateMembro` refusal case at the accessor boundary and
19040 // the [`crate::StandardLayout::verify`] cross-member gate would
19041 // silently accept a struct-literal `Caixa` carrying the drift),
19042 // a reference to an operator-resolved overlay (the future per-
19043 // cluster `:membros-overrides` slot — its resolution must land
19044 // at exactly this accessor body, not silently divert the raw
19045 // slot away from a second consumer), or an axis-shuffled
19046 // projection (a future detour that reordered members through
19047 // the accessor would silently split the paired
19048 // [`crate::StandardLayout::verify`] per-Aplicacao gate's
19049 // traversal input from the peer [`Self::aplicacao_view`] fold-
19050 // in path's clone-order input, since the canonical `:contratos`
19051 // `:de`/`:para` and `:entrada :para` cross-slot refusal probes
19052 // read the member set through the same slice).
19053 //
19054 // Third outer top-level [`Caixa`] `&[Composite]`-return slice
19055 // accessor pin on the substrate primitive for M2 / M3 typed-
19056 // slot vec-carry axes — opens the outer-`Caixa` M3 mesh-slot
19057 // arm of the `&[Composite]` composite-slice sub-family the
19058 // sibling M2 `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
19059 // (2a1f907) and
19060 // `children_returns_children_slice_verbatim_across_permutations`
19061 // (c17b51e) pins opened, peer at the outer altitude of the
19062 // closed inner-[`crate::AplicacaoSpec::membros`] (6c77e36)
19063 // accessor on the same MESH-COMPOSITION per-Aplicacao member-
19064 // list axis.
19065 use crate::aplicacao::Membro;
19066 let fixtures: Vec<Vec<Membro>> = vec![
19067 vec![],
19068 vec![Membro {
19069 caixa: "cart".into(),
19070 versao: "^0.1".into(),
19071 }],
19072 vec![
19073 Membro {
19074 caixa: "cart".into(),
19075 versao: "^0.1".into(),
19076 },
19077 Membro {
19078 caixa: "pricing".into(),
19079 versao: "^0.2".into(),
19080 },
19081 Membro {
19082 caixa: "auth".into(),
19083 versao: "^1.0".into(),
19084 },
19085 ],
19086 vec![
19087 Membro {
19088 caixa: "cart".into(),
19089 versao: "^0.1".into(),
19090 },
19091 Membro {
19092 caixa: "cart".into(),
19093 versao: "^0.1".into(),
19094 },
19095 ],
19096 ];
19097 for membros in fixtures {
19098 let c = caixa_aplicacao_with_membros(membros.clone());
19099 assert_eq!(
19100 c.membros(),
19101 membros.as_slice(),
19102 "Caixa::membros must return :membros verbatim \
19103 (got {:?}, expected {membros:?})",
19104 c.membros(),
19105 );
19106 assert_eq!(
19107 c.membros(),
19108 c.membros.as_slice(),
19109 "Caixa::membros must element-equal the raw \
19110 `self.membros.as_slice()` field access across every \
19111 value in the Vec<Membro> accept-set",
19112 );
19113 assert_eq!(
19114 c.membros().is_empty(),
19115 c.membros.is_empty(),
19116 "Caixa::membros().is_empty() must byte-equal \
19117 self.membros.is_empty() — a presence-bit drift would \
19118 silently split the paired Caixa::declared_mesh_slots \
19119 mesh declared-slot enumerator's presence probe from \
19120 the peer Caixa::aplicacao_view typed-view composer's \
19121 fold-in path",
19122 );
19123 }
19124 }
19125
19126 #[test]
19127 fn declared_mesh_slots_membros_arm_routes_through_accessor() {
19128 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:membros`
19129 // presence-probe arm must key off [`Caixa::membros`], not the
19130 // raw `!self.membros.is_empty()` field-probe. Structurally: a
19131 // `Caixa { membros: vec![Membro { caixa: "cart", versao:
19132 // "^0.1" }], .. }` must push `M3_AUTHOR_KEY_MEMBROS` onto the
19133 // declared-slot list (the presence bit is non-empty, so the
19134 // mesh kind-coherence gate must surface the slot as
19135 // "declared"), and a `Caixa { membros: vec![], .. }` must NOT
19136 // push the label (the "author omitted the slot entirely" arm
19137 // — the empty-slice partition the serde-default folds onto).
19138 // The pair jointly pins the accessor + declared-slot
19139 // enumerator composition: any future silent detour that had
19140 // the accessor collapse `[Membro { .. }]` to `[]` (a
19141 // `.filter(|m| m.nome() != "__reserved__")` projection) would
19142 // silently absorb the "declared but degenerate" arm at the
19143 // accessor boundary and the
19144 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
19145 // coherence gate would silently accept a struct-literal
19146 // `Caixa` carrying the drift.
19147 //
19148 // Peer of the sibling
19149 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
19150 // (2a1f907) and
19151 // `declared_supervisor_slots_children_arm_routes_through_accessor`
19152 // (c17b51e) composition pins on the M2 `:upgrade-from` /
19153 // `:children` composite-slice arms — same "the enumerator gate
19154 // must route through the substrate-primitive typed dispatch"
19155 // discipline extended onto the M3 `:membros` composite-slice
19156 // arm, opening the M3 arm of the declared-slot enumerator's
19157 // routing invariant.
19158 use crate::aplicacao::Membro;
19159 let c = caixa_aplicacao_with_membros(vec![Membro {
19160 caixa: "cart".into(),
19161 versao: "^0.1".into(),
19162 }]);
19163 let slots = c.declared_mesh_slots();
19164 assert!(
19165 slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
19166 "declared_mesh_slots must push M3_AUTHOR_KEY_MEMBROS when \
19167 `:membros` is non-empty — the accessor and the enumerator \
19168 gate must route through the same substrate-primitive \
19169 typed dispatch on the outer :membros presence bit (got \
19170 slots={slots:?})",
19171 );
19172 let c = caixa_aplicacao_with_membros(vec![]);
19173 let slots = c.declared_mesh_slots();
19174 assert!(
19175 !slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
19176 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_MEMBROS \
19177 when `:membros` is empty — the author-omitted arm must \
19178 route through the accessor's empty-slice return unchanged \
19179 (got slots={slots:?})",
19180 );
19181 }
19182
19183 #[test]
19184 fn aplicacao_view_membros_arm_routes_through_accessor() {
19185 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:membros`
19186 // fold-in arm must key off [`Caixa::membros`], not the raw
19187 // `self.membros.clone()` field-clone. Structurally: a `Caixa {
19188 // kind: Aplicacao, membros: vec![Membro { caixa: "cart", .. },
19189 // Membro { caixa: "pricing", .. }], .. }` must fold the per-
19190 // member list through the accessor into the typed
19191 // [`crate::AplicacaoSpec`] view's `membros` slot verbatim —
19192 // every entry the accessor surfaces must land in the view's
19193 // `membros` slot in the same order. The pair jointly pins the
19194 // accessor + view-composer composition: any future silent
19195 // detour that had the accessor return a fresh-cloned
19196 // `Vec<Membro>` copy would silently break the reference-
19197 // identity pin the peer `aplicacao_view` fold-in path reads
19198 // from — the fold would clone once more per accessor call
19199 // instead of borrowing the storage buffer verbatim once.
19200 //
19201 // Peer of the sibling
19202 // `aplicacao_view_politicas_arm_folds_through_accessor`
19203 // (5d23d29) /
19204 // `aplicacao_view_placement_arm_folds_through_accessor`
19205 // (4fb8074) /
19206 // `aplicacao_view_entrada_arm_folds_through_accessor` (e4128e4)
19207 // composition pins on the M3 `:politicas` / `:placement` /
19208 // `:entrada` outer-`Option<&Composite>` arms — extended here to
19209 // the M3 `:membros` outer-`&[Composite]` composite-slice arm,
19210 // closing the aplicacao-view composer's routing invariant on
19211 // the composite-slice input.
19212 use crate::aplicacao::Membro;
19213 let c = caixa_aplicacao_with_membros(vec![
19214 Membro {
19215 caixa: "cart".into(),
19216 versao: "^0.1".into(),
19217 },
19218 Membro {
19219 caixa: "pricing".into(),
19220 versao: "^0.2".into(),
19221 },
19222 ]);
19223 let view = c
19224 .aplicacao_view()
19225 .expect("Aplicacao kind must produce an aplicacao_view");
19226 assert_eq!(
19227 view.membros(),
19228 c.membros(),
19229 "aplicacao_view must fold Caixa::membros verbatim into \
19230 AplicacaoSpec::membros — the accessor and the view \
19231 composer must route through the same substrate-primitive \
19232 typed dispatch on the outer :membros slice (got view \
19233 membros={:?}, expected {:?})",
19234 view.membros(),
19235 c.membros(),
19236 );
19237 }
19238
19239 #[test]
19240 fn membros_projects_slice_by_borrow() {
19241 // The by-borrow pin: [`Caixa::membros`] returns `&[Membro]` by
19242 // borrow — the returned slice borrows the underlying
19243 // `Vec<Membro>` storage of the `:membros` slot and the
19244 // accessor must not clone the backing `Vec` on every call.
19245 // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
19246 // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
19247 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
19248 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
19249 // `exe_projects_slice_by_borrow` 65d9527,
19250 // `servicos_projects_slice_by_borrow` 611f78b,
19251 // `deps_projects_slice_by_borrow` ad34b4e,
19252 // `deps_dev_projects_slice_by_borrow` f7fd81e,
19253 // `upgrade_from_projects_slice_by_borrow` 2a1f907,
19254 // `children_projects_slice_by_borrow` c17b51e) on the sibling
19255 // outer top-level [`Caixa`] scalar-element and composite-
19256 // element `&[T]` axes — folds on the outer-`Caixa` M3 mesh-
19257 // slot composite-element `&[Composite]` axis: the accessor's
19258 // returned slice must borrow from `&self` (the returned
19259 // reference's lifetime is tied to `&self`), and calling the
19260 // accessor twice on the same [`Caixa`] must yield slices that
19261 // are pointer-equal (the underlying byte-buffer is the storage
19262 // `Vec`'s allocation, not a fresh copy) as well as value-equal
19263 // (idempotent, no side effects on `&self`).
19264 //
19265 // Pins against a future silent detour that returned an owned
19266 // `Vec<Membro>` (which would type-check but silently clone on
19267 // every call), a `&Vec<Membro>` return (which would leak the
19268 // backing `Vec`'s grow/push/reserve surface no downstream
19269 // consumer reaches for), or a one-arm-only accessor that
19270 // returned a saturating value on some sentinel input.
19271 use crate::aplicacao::Membro;
19272 for membros in [
19273 vec![],
19274 vec![Membro {
19275 caixa: "cart".into(),
19276 versao: "^0.1".into(),
19277 }],
19278 vec![
19279 Membro {
19280 caixa: "cart".into(),
19281 versao: "^0.1".into(),
19282 },
19283 Membro {
19284 caixa: "pricing".into(),
19285 versao: "^0.2".into(),
19286 },
19287 ],
19288 ] {
19289 let c = caixa_aplicacao_with_membros(membros.clone());
19290 let first = c.membros();
19291 let second = c.membros();
19292 assert_eq!(
19293 first, second,
19294 "Caixa::membros must be idempotent — two successive \
19295 calls on the same &self must return the same &[Membro]",
19296 );
19297 assert_eq!(
19298 first.as_ptr(),
19299 second.as_ptr(),
19300 "Caixa::membros must borrow the underlying Vec<Membro> \
19301 storage — two successive calls must return slices with \
19302 the same backing pointer (a fresh Vec<Membro> clone \
19303 would change the pointer on every call)",
19304 );
19305 assert_eq!(
19306 first,
19307 membros.as_slice(),
19308 "Caixa::membros must return :membros verbatim by borrow \
19309 — got {first:?}, expected {membros:?}",
19310 );
19311 }
19312 }
19313
19314 // ── Caixa::contratos — outer top-level &[WitContract] composite-slice accessor ──
19315
19316 fn caixa_aplicacao_with_contratos(contratos: Vec<crate::aplicacao::WitContract>) -> Caixa {
19317 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19318 c.kind = CaixaKind::Aplicacao;
19319 c.contratos = contratos;
19320 c
19321 }
19322
19323 fn contrato_http_for_test(
19324 de: &str,
19325 para: &str,
19326 endpoint: &str,
19327 ) -> crate::aplicacao::WitContract {
19328 crate::aplicacao::WitContract {
19329 de: de.into(),
19330 para: para.into(),
19331 wit: "wasi:http/proxy".into(),
19332 endpoint: Some(endpoint.into()),
19333 subject: None,
19334 slot: None,
19335 }
19336 }
19337
19338 #[test]
19339 fn contratos_returns_contratos_slice_verbatim_across_permutations() {
19340 // The canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
19341 // composite `&[WitContract]`-return slice-shape pin:
19342 // [`Caixa::contratos`] must return the `:contratos` typed
19343 // `Vec<WitContract>` verbatim as a `&[WitContract]` slice-view
19344 // over the same backing buffer the raw
19345 // `self.contratos.as_slice()` field access borrows from,
19346 // element-equal across every representative fixture in the
19347 // accept-set — `[]` (the "no contracts declared" arm every
19348 // non-`Aplicacao`-kind `defcaixa` carries by
19349 // `#[serde(default)]` and every leaf-Aplicacao with a single
19350 // member carries), a canonical single-edge fixture (the
19351 // minimal directed-graph shape: one HTTP-shape `(cart → catalog)`
19352 // edge), and a canonical multi-edge fixture with three distinct
19353 // edges (the checkout-shape Aplicacao's HTTP-fan pattern:
19354 // `(cart → catalog)`, `(cart → pricing)`, `(cart → auth)`).
19355 //
19356 // Pins against a future silent detour that returned an owned
19357 // `Vec<WitContract>` (which would type-check but silently clone
19358 // on every accessor call, breaking the zero-cost projection
19359 // every peer sibling slice accessor carries), an axis-shuffled
19360 // projection (a future detour that reordered edges through the
19361 // accessor would silently split the paired
19362 // [`crate::StandardLayout::verify`] per-Aplicacao gate's
19363 // traversal input from the peer [`Self::aplicacao_view`] fold-
19364 // in path's clone-order input, since every canonical
19365 // `caixa-mesh` renderer's per-`(:de, :para)` adjacency-list
19366 // seed dispatch reads the edge set through the same slice),
19367 // or a reference to an operator-resolved overlay (the future
19368 // per-cluster `:contratos-overrides` slot — its resolution
19369 // must land at exactly this accessor body, not silently divert
19370 // the raw slot away from a second consumer).
19371 //
19372 // Fourth outer top-level [`Caixa`] `&[Composite]`-return slice
19373 // accessor pin on the substrate primitive for M2 / M3 typed-
19374 // slot vec-carry axes — closes the outer-`Caixa`
19375 // `&[Composite]` composite-slice sub-family the sibling M2
19376 // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
19377 // (2a1f907) and
19378 // `children_returns_children_slice_verbatim_across_permutations`
19379 // (c17b51e) pins opened and the M3
19380 // `membros_returns_membros_slice_verbatim_across_permutations`
19381 // (0f26987) pin folded on, closing the outer-`Caixa` M3 mesh-
19382 // slot arm of the composite-slice sub-family. Peer at the outer
19383 // altitude of the closed inner-
19384 // [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
19385 // same MESH-COMPOSITION per-Aplicacao contract-list axis.
19386 let fixtures: Vec<Vec<crate::aplicacao::WitContract>> = vec![
19387 vec![],
19388 vec![contrato_http_for_test("cart", "catalog", "/items")],
19389 vec![
19390 contrato_http_for_test("cart", "catalog", "/items"),
19391 contrato_http_for_test("cart", "pricing", "/price"),
19392 contrato_http_for_test("cart", "auth", "/whoami"),
19393 ],
19394 ];
19395 for contratos in fixtures {
19396 let c = caixa_aplicacao_with_contratos(contratos.clone());
19397 assert_eq!(
19398 c.contratos(),
19399 contratos.as_slice(),
19400 "Caixa::contratos must return :contratos verbatim \
19401 (got {:?}, expected {contratos:?})",
19402 c.contratos(),
19403 );
19404 assert_eq!(
19405 c.contratos(),
19406 c.contratos.as_slice(),
19407 "Caixa::contratos must element-equal the raw \
19408 `self.contratos.as_slice()` field access across every \
19409 value in the Vec<WitContract> accept-set",
19410 );
19411 assert_eq!(
19412 c.contratos().is_empty(),
19413 c.contratos.is_empty(),
19414 "Caixa::contratos().is_empty() must byte-equal \
19415 self.contratos.is_empty() — a presence-bit drift would \
19416 silently split the paired Caixa::declared_mesh_slots \
19417 mesh declared-slot enumerator's presence probe from \
19418 the peer Caixa::aplicacao_view typed-view composer's \
19419 fold-in path",
19420 );
19421 }
19422 }
19423
19424 #[test]
19425 fn declared_mesh_slots_contratos_arm_routes_through_accessor() {
19426 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:contratos`
19427 // presence-probe arm must key off [`Caixa::contratos`], not the
19428 // raw `!self.contratos.is_empty()` field-probe. Structurally: a
19429 // `Caixa { contratos: vec![WitContract { .. }], .. }` must push
19430 // `M3_AUTHOR_KEY_CONTRATOS` onto the declared-slot list (the
19431 // presence bit is non-empty, so the mesh kind-coherence gate
19432 // must surface the slot as "declared"), and a `Caixa {
19433 // contratos: vec![], .. }` must NOT push the label (the "author
19434 // omitted the slot entirely" arm — the empty-slice partition
19435 // the serde-default folds onto). The pair jointly pins the
19436 // accessor + declared-slot enumerator composition: any future
19437 // silent detour that had the accessor collapse
19438 // `[WitContract { .. }]` to `[]` (a `.filter(|c| c.de() !=
19439 // "__reserved__")` projection) would silently absorb the
19440 // "declared but degenerate" arm at the accessor boundary and
19441 // the [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
19442 // coherence gate would silently accept a struct-literal
19443 // `Caixa` carrying the drift.
19444 //
19445 // Peer of the sibling
19446 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
19447 // (2a1f907),
19448 // `declared_supervisor_slots_children_arm_routes_through_accessor`
19449 // (c17b51e), and
19450 // `declared_mesh_slots_membros_arm_routes_through_accessor`
19451 // (0f26987) composition pins on the M2 `:upgrade-from` /
19452 // `:children` / M3 `:membros` composite-slice arms — same "the
19453 // enumerator gate must route through the substrate-primitive
19454 // typed dispatch" discipline extended onto the M3 `:contratos`
19455 // composite-slice arm, closing the M3 mesh-slot arm of the
19456 // declared-slot enumerator's routing invariant on the
19457 // composite-slice inputs.
19458 let c = caixa_aplicacao_with_contratos(vec![contrato_http_for_test(
19459 "cart", "catalog", "/items",
19460 )]);
19461 let slots = c.declared_mesh_slots();
19462 assert!(
19463 slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
19464 "declared_mesh_slots must push M3_AUTHOR_KEY_CONTRATOS when \
19465 `:contratos` is non-empty — the accessor and the enumerator \
19466 gate must route through the same substrate-primitive \
19467 typed dispatch on the outer :contratos presence bit (got \
19468 slots={slots:?})",
19469 );
19470 let c = caixa_aplicacao_with_contratos(vec![]);
19471 let slots = c.declared_mesh_slots();
19472 assert!(
19473 !slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
19474 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_CONTRATOS \
19475 when `:contratos` is empty — the author-omitted arm must \
19476 route through the accessor's empty-slice return unchanged \
19477 (got slots={slots:?})",
19478 );
19479 }
19480
19481 #[test]
19482 fn aplicacao_view_contratos_arm_routes_through_accessor() {
19483 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:contratos`
19484 // fold-in arm must key off [`Caixa::contratos`], not the raw
19485 // `self.contratos.clone()` field-clone. Structurally: a `Caixa
19486 // { kind: Aplicacao, contratos: vec![WitContract { de: "cart",
19487 // .. }, WitContract { de: "pricing", .. }], .. }` must fold the
19488 // per-edge list through the accessor into the typed
19489 // [`crate::AplicacaoSpec`] view's `contratos` slot verbatim —
19490 // every entry the accessor surfaces must land in the view's
19491 // `contratos` slot in the same order. The pair jointly pins
19492 // the accessor + view-composer composition: a future silent
19493 // detour that had the accessor shuffle or drop an edge would
19494 // silently split the paired declared-slot enumerator's
19495 // presence bit from the typed-view composer's edge-list, a
19496 // two-consumer split at the enumerator and the view composer
19497 // far from the source `caixa.lisp`.
19498 //
19499 // Peer of the sibling
19500 // `aplicacao_view_membros_arm_routes_through_accessor`
19501 // (0f26987) composition pin on the M3 `:membros` outer-
19502 // `&[Composite]` composite-slice arm, closing the aplicacao-
19503 // view composer's routing invariant on the composite-slice
19504 // inputs at the outer altitude.
19505 let c = caixa_aplicacao_with_contratos(vec![
19506 contrato_http_for_test("cart", "catalog", "/items"),
19507 contrato_http_for_test("cart", "pricing", "/price"),
19508 ]);
19509 let view = c
19510 .aplicacao_view()
19511 .expect("Aplicacao kind must produce an aplicacao_view");
19512 assert_eq!(
19513 view.contratos(),
19514 c.contratos(),
19515 "aplicacao_view must fold Caixa::contratos verbatim into \
19516 AplicacaoSpec::contratos — the accessor and the view \
19517 composer must route through the same substrate-primitive \
19518 typed dispatch on the outer :contratos slice (got view \
19519 contratos={:?}, expected {:?})",
19520 view.contratos(),
19521 c.contratos(),
19522 );
19523 }
19524
19525 #[test]
19526 fn contratos_projects_slice_by_borrow() {
19527 // The by-borrow pin: [`Caixa::contratos`] returns `&[WitContract]`
19528 // by borrow — the returned slice borrows the underlying
19529 // `Vec<WitContract>` storage of the `:contratos` slot and the
19530 // accessor must not clone the backing `Vec` on every call.
19531 // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
19532 // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
19533 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
19534 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
19535 // `exe_projects_slice_by_borrow` 65d9527,
19536 // `servicos_projects_slice_by_borrow` 611f78b,
19537 // `deps_projects_slice_by_borrow` ad34b4e,
19538 // `deps_dev_projects_slice_by_borrow` f7fd81e,
19539 // `upgrade_from_projects_slice_by_borrow` 2a1f907,
19540 // `children_projects_slice_by_borrow` c17b51e,
19541 // `membros_projects_slice_by_borrow` 0f26987) on the sibling
19542 // outer top-level [`Caixa`] scalar-element and composite-
19543 // element `&[T]` axes — closes the outer-`Caixa` M3 mesh-slot
19544 // composite-element `&[Composite]` axis on the by-borrow pin:
19545 // the accessor's returned slice must borrow from `&self` (the
19546 // returned reference's lifetime is tied to `&self`), and
19547 // calling the accessor twice on the same [`Caixa`] must yield
19548 // slices that are pointer-equal (the underlying byte-buffer is
19549 // the storage `Vec`'s allocation, not a fresh copy) as well as
19550 // value-equal (idempotent, no side effects on `&self`).
19551 //
19552 // Pins against a future silent detour that returned an owned
19553 // `Vec<WitContract>` (which would type-check but silently clone
19554 // on every call), a `&Vec<WitContract>` return (which would
19555 // leak the backing `Vec`'s grow/push/reserve surface no
19556 // downstream consumer reaches for), or a one-arm-only accessor
19557 // that returned a saturating value on some sentinel input.
19558 for contratos in [
19559 vec![],
19560 vec![contrato_http_for_test("cart", "catalog", "/items")],
19561 vec![
19562 contrato_http_for_test("cart", "catalog", "/items"),
19563 contrato_http_for_test("cart", "pricing", "/price"),
19564 ],
19565 ] {
19566 let c = caixa_aplicacao_with_contratos(contratos.clone());
19567 let first = c.contratos();
19568 let second = c.contratos();
19569 assert_eq!(
19570 first, second,
19571 "Caixa::contratos must be idempotent — two successive \
19572 calls on the same &self must return the same \
19573 &[WitContract]",
19574 );
19575 assert_eq!(
19576 first.as_ptr(),
19577 second.as_ptr(),
19578 "Caixa::contratos must borrow the underlying \
19579 Vec<WitContract> storage — two successive calls must \
19580 return slices with the same backing pointer (a fresh \
19581 Vec<WitContract> clone would change the pointer on \
19582 every call)",
19583 );
19584 assert_eq!(
19585 first,
19586 contratos.as_slice(),
19587 "Caixa::contratos must return :contratos verbatim by \
19588 borrow — got {first:?}, expected {contratos:?}",
19589 );
19590 }
19591 }
19592
19593 // ── drift-detection: Caixa top-level multi-word serde-derive-to-const identity ──
19594
19595 #[test]
19596 fn caixa_multi_word_serde_keys_match_lifted_top_level_key_consts() {
19597 // Load-bearing invariant: every multi-word top-level [`Caixa`]
19598 // serde-derived JSON key routes through a lifted `&'static str`
19599 // const. The Rust field names are `snake_case`
19600 // (`deps_dev` / `upgrade_from` / `max_restarts` /
19601 // `restart_window`); [`Caixa`]'s `#[serde(rename_all =
19602 // "camelCase")]` derive attribute maps each to the camelCase
19603 // byte-string the [`Caixa::to_lisp`] round-trip's
19604 // `serde_json::to_value(self)` step lands under before
19605 // `tatara_lisp::domain::json_to_sexp` re-projects the JSON keys
19606 // to the kebab-case `:deps-dev` / `:upgrade-from` /
19607 // `:max-restarts` / `:restart-window` author surface. Serialize
19608 // a fully-populated [`Caixa`] and pin that each canonical
19609 // byte-sequence appears verbatim in the JSON — a future
19610 // accidental `rename_all = "snake_case"` / `"kebab-case"` /
19611 // verbatim-field-name flip at the derive attribute (any of
19612 // which would silently break every [`Caixa::to_lisp`]
19613 // round-trip and the future M4 operator-side manifest ingest's
19614 // `Value::get(<key>)` navigation) surfaces here as a build-time
19615 // test failure at `manifest.rs`, not as an apply-time
19616 // `.get(<stale-canonical-const>)` returning `None` far from the
19617 // derive-attr drift's commit. Same discipline the sibling
19618 // `supervisor_spec_serde_keys_match_lifted_supervisor_key_consts`
19619 // (40cc4e5), `membro_serde_keys_match_lifted_membro_key_consts`
19620 // (ce80ca0), and `upgrade_from_entry_serde_keys_match_lifted_
19621 // m2_upgrade_from_key_consts` (36ffe65) pins established on the
19622 // sibling M2 supervision-tree, M3 [`Membro`] per-entry, and M2
19623 // [`UpgradeFromEntry`] per-entry axes — extended here to the
19624 // enclosing M0 [`Caixa`] top-level axis so the last of the four
19625 // multi-word top-level [`Caixa`] serde-derived JSON keys
19626 // (`depsDev`) joins the substrate's "one canonical byte-string
19627 // per typed serialized-key axis" discipline.
19628 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
19629 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
19630 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19631 c.deps_dev = vec![Dep::simple("tatara-check", "^0.1")];
19632 c.upgrade_from = vec![UpgradeFromEntry {
19633 from: "0.0.1".into(),
19634 instructions: vec![UpgradeInstruction::Restart],
19635 }];
19636 c.estrategia = Some(RestartStrategy::OneForOne);
19637 c.max_restarts = Some(3);
19638 c.restart_window = Some("60s".into());
19639 c.children = vec![ChildSpec {
19640 caixa: "child".into(),
19641 versao: "^0.1".into(),
19642 restart: RestartPolicy::Permanent,
19643 }];
19644 let json = serde_json::to_string(&c).unwrap();
19645 for key in [
19646 crate::render::CAIXA_KEY_DEPS_DEV,
19647 crate::render::M2_KEY_UPGRADE_FROM,
19648 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
19649 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
19650 ] {
19651 let quoted = format!("\"{key}\"");
19652 assert!(
19653 json.contains("ed),
19654 "serialized Caixa must carry the lifted top-level \
19655 multi-word byte-sequence {quoted} verbatim in the JSON \
19656 emission (got: {json})",
19657 );
19658 }
19659 }
19660
19661 #[test]
19662 fn caixa_top_level_multi_word_key_consts_are_pairwise_distinct() {
19663 // Cross-axis drift-detection pin: a future collapse of the four
19664 // canonical [`Caixa`] top-level multi-word byte-strings onto the
19665 // same value (e.g. an accidental copy-paste flip of
19666 // [`crate::render::CAIXA_KEY_DEPS_DEV`] to also read
19667 // `"upgradeFrom"`) would silently reroute every downstream
19668 // `Value::get(<key>)` probe on one axis onto the sibling axis's
19669 // top-level entry and pass every propagation-probe test that
19670 // expected only the stale axis's value. Peer of the sibling
19671 // four-way distinct pin on the `SUPERVISOR_KEY_*` tetrad
19672 // (40cc4e5) and the two-way pin on `MEMBRO_KEY_*` (ce80ca0).
19673 let all = [
19674 crate::render::CAIXA_KEY_DEPS_DEV,
19675 crate::render::M2_KEY_UPGRADE_FROM,
19676 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
19677 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
19678 ];
19679 for (i, a) in all.iter().enumerate() {
19680 for b in all.iter().skip(i + 1) {
19681 assert_ne!(
19682 a, b,
19683 "Caixa top-level multi-word key consts must be \
19684 pairwise-distinct canonical byte-sequences — got \
19685 `{a}` == `{b}`",
19686 );
19687 }
19688 }
19689 }
19690
19691 #[test]
19692 fn caixa_top_level_multi_word_key_consts_are_lower_camel_case_shape() {
19693 // Shape-pin: every [`Caixa`] top-level multi-word key const must
19694 // be a lowerCamelCase byte-sequence (no `snake_case`
19695 // underscores, no `kebab-case` hyphens, no leading colon, no
19696 // `PascalCase` leading capital, no whitespace / dots) — the
19697 // canonical shape the `#[serde(rename_all = "camelCase")]`
19698 // derive produces on [`Caixa`]. A future flip to a
19699 // non-camelCase attribute at the derive surfaces both here
19700 // (this test fails on the stale-constant shape) and at
19701 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
19702 // (that test fails on the mismatch between const and derive).
19703 // Peer with `membro_key_consts_are_lower_camel_case_shape`
19704 // (ce80ca0) and `supervisor_key_consts_are_lower_camel_case_shape`
19705 // (40cc4e5) on the sibling per-entry / supervisor-tree axes.
19706 for key in [
19707 crate::render::CAIXA_KEY_DEPS_DEV,
19708 crate::render::M2_KEY_UPGRADE_FROM,
19709 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
19710 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
19711 ] {
19712 assert!(
19713 !key.is_empty(),
19714 "Caixa top-level multi-word key const must be non-empty \
19715 (got {key:?})"
19716 );
19717 let first = key.chars().next().unwrap();
19718 assert!(
19719 first.is_ascii_lowercase(),
19720 "Caixa top-level multi-word key const must lead with an \
19721 ASCII-lowercase byte (got {key:?}, leads with {first:?})",
19722 );
19723 assert!(
19724 key.chars().all(|c| c.is_ascii_alphanumeric()),
19725 "Caixa top-level multi-word key const must be \
19726 ASCII-alphanumeric only — no `_` / `-` / `:` / `.` / \
19727 whitespace (got {key:?})",
19728 );
19729 }
19730 }
19731
19732 #[test]
19733 fn caixa_key_deps_dev_pins_canonical_camel_case_byte_string() {
19734 // Scalar-value pin: the byte-string the
19735 // [`crate::render::CAIXA_KEY_DEPS_DEV`] const resolves to,
19736 // asserted verbatim. A future rebrand (`depsDev` → `devDeps`
19737 // matching Cargo's verbatim `dev-dependencies` axis, `depsDev`
19738 // → `depsTest` matching a hypothetical per-test-target
19739 // vocabulary flip) lands as an edit to exactly one const AND
19740 // one derive attribute — the sibling
19741 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
19742 // pin already ties the const to the derive attribute, so a
19743 // rebrand that touches only one side of the pair fails at
19744 // caixa-core build time. Same "scalar-value pin per const"
19745 // discipline the sibling
19746 // `m2_top_level_author_key_consts_pin_canonical_kebab_case_labels`
19747 // (f49c8b0) and `contrato_key_consts_pin_canonical_camel_case_labels`
19748 // (ca463a4) pins carry on the peer M2 / M3 top-level slot axes.
19749 assert_eq!(crate::render::CAIXA_KEY_DEPS_DEV, "depsDev");
19750 }
19751
19752 #[test]
19753 fn caixa_key_deps_pins_canonical_byte_string() {
19754 // Scalar-value pin: the byte-string the
19755 // [`crate::render::CAIXA_KEY_DEPS`] const resolves to, asserted
19756 // verbatim. Peer of `caixa_key_deps_dev_pins_canonical_camel_case_byte_string`
19757 // on the two-list dep-graph serialized-key axis — the sibling
19758 // pin covers the multi-word `deps_dev → depsDev` camelCase
19759 // arm, this pin covers the single-word `deps → deps` no-op arm
19760 // (the [`crate::Caixa::deps`] field name carries no `_`, so the
19761 // `#[serde(rename_all = "camelCase")]` derive is a no-op on this
19762 // axis and the emitted JSON key equals the source-side field
19763 // name byte-for-byte). A future [`crate::Caixa::deps`] field
19764 // rename (`deps` → `dependencies` matching Cargo's verbatim
19765 // `[dependencies]` axis, `deps` → `runtime_deps` matching a
19766 // hypothetical per-runtime-target vocabulary flip) OR an added
19767 // `#[serde(rename = "…")]` explicit override lands as an edit
19768 // to exactly one const AND one derive-attr / field name — the
19769 // sibling `caixa_deps_serde_key_matches_lifted_caixa_key_deps`
19770 // pin ties the const to the emitted JSON key, so a rebrand
19771 // that touches only one side of the pair fails at caixa-core
19772 // build time.
19773 assert_eq!(crate::render::CAIXA_KEY_DEPS, "deps");
19774 }
19775
19776 #[test]
19777 fn caixa_deps_serde_key_matches_lifted_caixa_key_deps() {
19778 // Load-bearing invariant on the single-word `deps` top-level
19779 // axis: the byte-string [`crate::render::CAIXA_KEY_DEPS`] pins
19780 // must appear verbatim in the JSON [`Caixa::to_lisp`]'s
19781 // `serde_json::to_value(self)` step emits. Serialize a
19782 // populated [`Caixa`] whose `:deps` slot carries at least one
19783 // entry (the `#[serde(default)]` attribute on the field emits
19784 // an empty `[]` even without members, but a non-empty vec
19785 // additionally covers the codec's per-`Dep`-entry emission
19786 // path) and pin that `"deps"` appears verbatim in the JSON
19787 // emission — a future accidental `rename_all = "snake_case"` /
19788 // `"kebab-case"` flip at the derive attribute (or an added
19789 // `#[serde(rename = "…")]` explicit override on the field, or
19790 // a Rust field rename) would break every [`Caixa::to_lisp`]
19791 // round-trip and the future M4 operator-side manifest ingest's
19792 // `Value::get(CAIXA_KEY_DEPS)` navigation — surfaces here as a
19793 // build-time test failure at `manifest.rs`, not as an
19794 // apply-time `.get(<stale-canonical-const>)` returning `None`
19795 // far from the drift's commit. Peer of the sibling
19796 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
19797 // multi-word pin on the same M0 [`Caixa`] top-level
19798 // serialized-key axis, extended here to the single-word arm
19799 // the multi-word test's `rename_all = "camelCase"` sweep can't
19800 // reach (single-word `deps → deps` is a no-op the multi-word
19801 // pin's `\"depsDev\"` / `\"upgradeFrom\"` / `\"maxRestarts\"` /
19802 // `\"restartWindow\"` byte-scan can never observe).
19803 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19804 c.deps = vec![Dep::simple("caixa-core", "^0.1")];
19805 let json = serde_json::to_string(&c).unwrap();
19806 let quoted = format!("\"{}\"", crate::render::CAIXA_KEY_DEPS);
19807 assert!(
19808 json.contains("ed),
19809 "serialized Caixa must carry the lifted top-level `deps` \
19810 byte-sequence {quoted} verbatim in the JSON emission (got: \
19811 {json})",
19812 );
19813 }
19814
19815 #[test]
19816 fn caixa_dep_graph_two_list_key_consts_are_pairwise_distinct() {
19817 // Cross-axis drift-detection pin on the two-list dep-graph
19818 // renderer-side wire-key axis: a future collapse of the
19819 // canonical [`crate::render::CAIXA_KEY_DEPS`] /
19820 // [`crate::render::CAIXA_KEY_DEPS_DEV`] byte-strings onto the
19821 // same value (e.g. an accidental copy-paste flip of
19822 // `CAIXA_KEY_DEPS_DEV` to also read `"deps"`) would silently
19823 // reroute every downstream `Value::get(<key>)` probe on one
19824 // axis onto the sibling axis's dep-list and pass every
19825 // propagation-probe test that expected only the stale axis's
19826 // value — a dev-only dep would land in the runtime closure at
19827 // publish time, or a runtime dep would be excluded from the
19828 // published lacre. Peer of the sibling four-way distinct pin
19829 // on the top-level multi-word tetrad
19830 // (`caixa_top_level_multi_word_key_consts_are_pairwise_distinct`)
19831 // and the two-way pin on the sibling
19832 // [`DEP_AUTHOR_KEY_DEPS`] / [`DEP_AUTHOR_KEY_DEPS_DEV`]
19833 // author-facing arm (4da6fba's test), extended here to the
19834 // renderer-side wire-key arm of the same two-list dep-graph
19835 // axis so both halves of the "one canonical byte-string per
19836 // typed axis per (author, wire)" grid carry the same
19837 // distinct-ness discipline.
19838 assert_ne!(
19839 crate::render::CAIXA_KEY_DEPS,
19840 crate::render::CAIXA_KEY_DEPS_DEV,
19841 "CAIXA_KEY_DEPS and CAIXA_KEY_DEPS_DEV must be distinct \
19842 canonical byte-sequences on the two-list dep-graph \
19843 renderer-side wire-key axis"
19844 );
19845 }
19846
19847 // ── DepList / Caixa::push_dep pin ────────────────────────────────
19848 //
19849 // The compounding pin: the two-arm closed-set typed enum
19850 // [`crate::dep::DepList`] carries the runtime-closure `:deps`
19851 // (`Prod`) vs dev-only-closure `:deps-dev` (`Dev`) dispatch every
19852 // consumer of the top-level manifest's dep-mutation surface reads
19853 // through, and the typed dispatch [`Caixa::push_dep`] on the
19854 // substrate primitive folds the "select list → check within-list
19855 // dup → push" cascade onto one method call. Prior to this landing
19856 // the two axes lived across two `&'static str` constants
19857 // (`DEP_AUTHOR_KEY_DEPS`, `DEP_AUTHOR_KEY_DEPS_DEV`) with no closed-
19858 // set type carrying the pair; the `feira add` mutation site's
19859 // inline `if self.dev { &mut caixa.deps_dev } else { &mut
19860 // caixa.deps }` dispatch expressed no compile-time link back to
19861 // the substrate primitive, and a future third dep-list axis would
19862 // have silently split at every open-coded mutation site.
19863
19864 #[test]
19865 fn dep_list_as_str_routes_through_lifted_author_key_constants() {
19866 // Every arm returns the same `&'static str` the substrate's
19867 // canonical `DEP_AUTHOR_KEY_DEPS` / `DEP_AUTHOR_KEY_DEPS_DEV`
19868 // constants carry. A future rebrand on either constant reaches
19869 // the enum through one edit; a regression to inline literals
19870 // (e.g. `Prod => ":deps"`) would silently split the diagnostic
19871 // quotes from the wire-format constants every consumer routes
19872 // through and this pin flags it at build time.
19873 assert_eq!(
19874 crate::dep::DepList::Prod.as_str(),
19875 crate::render::DEP_AUTHOR_KEY_DEPS
19876 );
19877 assert_eq!(
19878 crate::dep::DepList::Dev.as_str(),
19879 crate::render::DEP_AUTHOR_KEY_DEPS_DEV
19880 );
19881 }
19882
19883 #[test]
19884 fn dep_list_display_routes_through_as_str() {
19885 // Same as-str-through-Display convergence discipline the
19886 // sibling closed-set typed enums carry — a `format!("{list}")`
19887 // call must land byte-for-byte on the accessor's return so a
19888 // future consumer that formats the enum for a diagnostic line
19889 // reaches the same wire-format constant the wire-format
19890 // producers do.
19891 assert_eq!(
19892 format!("{}", crate::dep::DepList::Prod),
19893 crate::dep::DepList::Prod.as_str()
19894 );
19895 assert_eq!(
19896 format!("{}", crate::dep::DepList::Dev),
19897 crate::dep::DepList::Dev.as_str()
19898 );
19899 }
19900
19901 #[test]
19902 fn dep_list_all_enumerates_every_variant_once() {
19903 // Exhaustive-iteration pin — every arm appears exactly once in
19904 // `ALL`, matching the closed set the compiler enforces on the
19905 // sibling `match self` arms. A future variant addition that
19906 // extends only one method's match without extending `ALL`
19907 // would silently drop the new arm from every consumer that
19908 // iterates the slice.
19909 let variants: &[crate::dep::DepList] = crate::dep::DepList::ALL;
19910 assert!(variants.contains(&crate::dep::DepList::Prod));
19911 assert!(variants.contains(&crate::dep::DepList::Dev));
19912 assert_eq!(variants.len(), 2);
19913 }
19914
19915 #[test]
19916 fn dep_list_from_wire_returns_prod_on_deps_wire_scalar() {
19917 // Reverse projection on the two-list dep-graph axis: the
19918 // author-surface wire tag the sibling `as_str` emitter walks
19919 // for `Prod` (`:deps` via `DEP_AUTHOR_KEY_DEPS`) parses back to
19920 // `Some(DepList::Prod)`. A regression that hand-rolled the
19921 // per-arm match without routing through the lifted
19922 // `DEP_AUTHOR_KEY_DEPS` const would silently disagree on any
19923 // future wire-tag rebrand and this pin flags it at build time.
19924 assert_eq!(
19925 crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS),
19926 Some(crate::dep::DepList::Prod)
19927 );
19928 }
19929
19930 #[test]
19931 fn dep_list_from_wire_returns_dev_on_deps_dev_wire_scalar() {
19932 // Peer of the `Prod`-arm pin on the dev-only axis: the
19933 // author-surface wire tag the sibling `as_str` emitter walks
19934 // for `Dev` (`:deps-dev` via `DEP_AUTHOR_KEY_DEPS_DEV`) parses
19935 // back to `Some(DepList::Dev)`. Same drift-detection posture
19936 // as the peer arm — the sibling method `match` arms are
19937 // compiler-checked exhaustive so a future variant addition
19938 // trips at build time.
19939 assert_eq!(
19940 crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS_DEV),
19941 Some(crate::dep::DepList::Dev)
19942 );
19943 }
19944
19945 #[test]
19946 fn dep_list_from_wire_returns_none_on_unknown_wire_scalar() {
19947 // Every input outside the closed-set arm-string set the
19948 // sibling `as_str` emitter walks lands on the terminal `None`
19949 // fallback — no silent-accept surface. Sweeps a set of
19950 // plausibly-adjacent scalars (unprefixed wire form, PascalCase
19951 // rebrand candidates, foreign wire tags, empty string) so a
19952 // future variant addition that widened one wire form without
19953 // extending the emitter's arm-set would trip the sibling
19954 // round-trip pin below rather than silently accepting the new
19955 // form here.
19956 for candidate in [
19957 "",
19958 "deps",
19959 "deps-dev",
19960 ":deps ",
19961 ":Deps",
19962 ":DEPS",
19963 ":build-dep",
19964 ":tool-dep",
19965 "prod",
19966 "dev",
19967 ] {
19968 assert_eq!(
19969 crate::dep::DepList::from_wire(candidate),
19970 None,
19971 "from_wire({candidate:?}) must return None; every input outside \
19972 the {{DEP_AUTHOR_KEY_DEPS, DEP_AUTHOR_KEY_DEPS_DEV}} accept-set \
19973 the sibling as_str emitter walks lands on the terminal fallback",
19974 );
19975 }
19976 }
19977
19978 #[test]
19979 fn dep_list_round_trips_through_as_str_and_from_wire() {
19980 // Load-bearing round-trip pin: every arm the `ALL` iteration
19981 // exposes survives the `as_str` → `from_wire` composition
19982 // byte-for-byte. Same discipline the sibling closed-set enums
19983 // carry — `CaixaKind` /
19984 // `RestartStrategy` / `RestartPolicy` /
19985 // `PlacementStrategy` — extended onto the two-list dep-graph
19986 // axis. A future variant addition that extends `ALL` +
19987 // `as_str` without extending `from_wire` (or vice versa)
19988 // trips at build time on this iteration because the compiler
19989 // enforces exhaustiveness on the sibling `match self` arms.
19990 for &list in crate::dep::DepList::ALL {
19991 assert_eq!(
19992 crate::dep::DepList::from_wire(list.as_str()),
19993 Some(list),
19994 "DepList::from_wire(as_str({list:?})) must round-trip to Some({list:?}) — \
19995 a silent split between the forward emitter and the reverse parser \
19996 would drift the two halves of the two-list dep-graph axis's typed dispatch",
19997 );
19998 }
19999 }
20000
20001 #[test]
20002 fn push_dep_routes_to_deps_slot_on_prod_arm() {
20003 // The `Prod` arm dispatches to the runtime-closure `:deps`
20004 // slot every downstream lacre-pipeline consumer resolves at
20005 // build time. A future arm that regressed to inline `&mut
20006 // self.deps_dev` on the `Prod` path would silently reroute
20007 // every runtime dep into the dev-only closure at publish time
20008 // — this pin refuses that regression.
20009 let src = Caixa::template("host");
20010 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20011 let before_deps = caixa.deps().len();
20012 let before_deps_dev = caixa.deps_dev().len();
20013 let dep = Dep {
20014 nome: "caixa-teia".to_string(),
20015 versao: "^0.1".to_string(),
20016 fonte: None,
20017 opcional: false,
20018 caracteristicas: Vec::new(),
20019 };
20020 caixa
20021 .push_dep(crate::dep::DepList::Prod, dep)
20022 .expect("first push into :deps succeeds");
20023 assert_eq!(caixa.deps().len(), before_deps + 1);
20024 assert_eq!(caixa.deps_dev().len(), before_deps_dev);
20025 assert_eq!(caixa.deps().last().unwrap().nome(), "caixa-teia");
20026 }
20027
20028 #[test]
20029 fn push_dep_routes_to_deps_dev_slot_on_dev_arm() {
20030 // Peer of the sibling `Prod`-arm dispatch pin — the `Dev` arm
20031 // must dispatch to the dev-only-closure `:deps-dev` slot every
20032 // downstream test-facing artifact resolver reads. A future
20033 // regression that inverted the two arms would silently route
20034 // every dev-only dep into the runtime closure at publish time
20035 // and this pin catches it before the drift ships.
20036 let src = Caixa::template("host");
20037 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20038 let dep = Dep {
20039 nome: "tatara-check".to_string(),
20040 versao: "*".to_string(),
20041 fonte: None,
20042 opcional: false,
20043 caracteristicas: Vec::new(),
20044 };
20045 caixa
20046 .push_dep(crate::dep::DepList::Dev, dep)
20047 .expect("first push into :deps-dev succeeds");
20048 assert!(caixa.deps().is_empty());
20049 assert_eq!(caixa.deps_dev().len(), 1);
20050 assert_eq!(caixa.deps_dev().last().unwrap().nome(), "tatara-check");
20051 }
20052
20053 #[test]
20054 fn push_dep_refuses_within_list_duplicate_nome_with_typed_error() {
20055 // Within-list dup check routes through the canonical
20056 // [`DepError::DuplicateNome`] carrier — the substrate's typed
20057 // diagnostic for the same axis [`Caixa::validate_deps`]'s
20058 // parse-time [`crate::render::insert_first_seen`] walk raises
20059 // on. Prior to the lift the mutation site's inline
20060 // `bail!("dep '{}' already declared", …)` string-diagnostic
20061 // path expressed no through-line back to the typed error;
20062 // routing every dep-list refusal through one carrier means an
20063 // author reading a `feira add` refusal and a `feira build`
20064 // refusal reaches for the same corrective surface without
20065 // switching diagnostic idioms.
20066 let src = Caixa::template("host");
20067 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20068 let dep = Dep {
20069 nome: "caixa-teia".to_string(),
20070 versao: "^0.1".to_string(),
20071 fonte: None,
20072 opcional: false,
20073 caracteristicas: Vec::new(),
20074 };
20075 caixa
20076 .push_dep(crate::dep::DepList::Prod, dep.clone())
20077 .expect("first push succeeds");
20078 let dup = Dep {
20079 nome: "caixa-teia".to_string(),
20080 versao: "^0.2".to_string(),
20081 fonte: None,
20082 opcional: false,
20083 caracteristicas: Vec::new(),
20084 };
20085 let err = caixa
20086 .push_dep(crate::dep::DepList::Prod, dup)
20087 .expect_err("second push with same :nome refuses");
20088 assert_eq!(
20089 err,
20090 DepError::DuplicateNome {
20091 nome: "caixa-teia".to_string(),
20092 list: crate::render::DEP_AUTHOR_KEY_DEPS,
20093 }
20094 );
20095 // The refused mutation must not corrupt the target list —
20096 // exactly one entry lives past the refusal, matching the
20097 // canonical single-source-of-truth invariant `Caixa::deps()`
20098 // carries.
20099 assert_eq!(caixa.deps().len(), 1);
20100 }
20101
20102 #[test]
20103 fn push_dep_refuses_dup_on_dev_list_arm_names_deps_dev_key() {
20104 // Peer of the sibling `Prod`-arm dup-refusal pin — the `Dev`
20105 // arm's refusal must carry `DEP_AUTHOR_KEY_DEPS_DEV` in the
20106 // `list` payload so a future author reading the refusal grep's
20107 // for the correct `:deps-dev` block in their `caixa.lisp`,
20108 // not the sibling `:deps` block the runtime closure resolves.
20109 let src = Caixa::template("host");
20110 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20111 let dep = Dep {
20112 nome: "tatara-check".to_string(),
20113 versao: "*".to_string(),
20114 fonte: None,
20115 opcional: false,
20116 caracteristicas: Vec::new(),
20117 };
20118 caixa
20119 .push_dep(crate::dep::DepList::Dev, dep.clone())
20120 .expect("first push succeeds");
20121 let err = caixa
20122 .push_dep(crate::dep::DepList::Dev, dep)
20123 .expect_err("second push with same :nome refuses");
20124 assert!(matches!(
20125 err,
20126 DepError::DuplicateNome {
20127 ref nome,
20128 list,
20129 } if nome == "tatara-check"
20130 && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
20131 ));
20132 }
20133
20134 #[test]
20135 fn push_dep_allows_same_nome_across_prod_and_dev_lists() {
20136 // The within-list dup check is scoped to the target arm — a
20137 // caixa may legitimately carry the same `:nome` under both
20138 // `:deps` and `:deps-dev` (though the substrate's peer
20139 // [`crate::Caixa::validate_deps`] walk still refuses the
20140 // shape at parse time; the mutation-site refusal is scoped to
20141 // the mutation-site's list to match the peer parse-time
20142 // per-list [`crate::render::insert_first_seen`] discipline).
20143 // The two arms hold independent seen-sets.
20144 let src = Caixa::template("host");
20145 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20146 let dep_prod = Dep {
20147 nome: "shared".to_string(),
20148 versao: "^0.1".to_string(),
20149 fonte: None,
20150 opcional: false,
20151 caracteristicas: Vec::new(),
20152 };
20153 let dep_dev = Dep {
20154 nome: "shared".to_string(),
20155 versao: "*".to_string(),
20156 fonte: None,
20157 opcional: false,
20158 caracteristicas: Vec::new(),
20159 };
20160 caixa
20161 .push_dep(crate::dep::DepList::Prod, dep_prod)
20162 .expect("push into :deps succeeds");
20163 caixa
20164 .push_dep(crate::dep::DepList::Dev, dep_dev)
20165 .expect("push same :nome into :deps-dev succeeds");
20166 assert_eq!(caixa.deps().len(), 1);
20167 assert_eq!(caixa.deps_dev().len(), 1);
20168 }
20169
20170 #[test]
20171 fn deps_of_prod_returns_the_deps_slot_verbatim() {
20172 // The `Prod` arm of the typed-dispatch [`Caixa::deps_of`] read
20173 // accessor must project onto the runtime-closure `:deps` slot —
20174 // element-equal and length-equal to the sibling per-slot
20175 // [`Caixa::deps`] accessor's return over every per-caixa fixture.
20176 // A future arm that regressed to `self.deps_dev()` on the `Prod`
20177 // path would silently reroute every downstream typed-dispatch
20178 // walker (the [`Caixa::validate_deps`] per-list
20179 // [`crate::render::insert_first_seen`] dedup walk, any future
20180 // per-axis-parametrised consumer) into the sibling dev-only
20181 // closure and this pin refuses that regression.
20182 let src = Caixa::template("host");
20183 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20184 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
20185 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 0);
20186 let dep = Dep {
20187 nome: "caixa-teia".to_string(),
20188 versao: "^0.1".to_string(),
20189 fonte: None,
20190 opcional: false,
20191 caracteristicas: Vec::new(),
20192 };
20193 caixa
20194 .push_dep(crate::dep::DepList::Prod, dep.clone())
20195 .expect("push into :deps succeeds");
20196 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
20197 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 1);
20198 assert_eq!(
20199 caixa.deps_of(crate::dep::DepList::Prod)[0].nome(),
20200 "caixa-teia"
20201 );
20202 }
20203
20204 #[test]
20205 fn deps_of_dev_returns_the_deps_dev_slot_verbatim() {
20206 // Peer of the sibling `Prod`-arm pin — the `Dev` arm of
20207 // [`Caixa::deps_of`] must project onto the dev-only-closure
20208 // `:deps-dev` slot, element-equal and length-equal to the
20209 // sibling per-slot [`Caixa::deps_dev`] accessor's return. A
20210 // future regression that inverted the two arms would silently
20211 // route every dev-list walker onto the runtime closure and this
20212 // pin catches it before the drift ships.
20213 let src = Caixa::template("host");
20214 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20215 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
20216 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 0);
20217 let dep = Dep {
20218 nome: "tatara-check".to_string(),
20219 versao: "*".to_string(),
20220 fonte: None,
20221 opcional: false,
20222 caracteristicas: Vec::new(),
20223 };
20224 caixa
20225 .push_dep(crate::dep::DepList::Dev, dep)
20226 .expect("push into :deps-dev succeeds");
20227 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
20228 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 1);
20229 assert_eq!(
20230 caixa.deps_of(crate::dep::DepList::Dev)[0].nome(),
20231 "tatara-check"
20232 );
20233 }
20234
20235 #[test]
20236 fn deps_of_exhaustive_over_dep_list_all_covers_the_two_slots() {
20237 // Composition pin: iterating [`crate::dep::DepList::ALL`] through
20238 // [`Caixa::deps_of`] must land on the same two-slot partition the
20239 // per-slot [`Caixa::deps`] / [`Caixa::deps_dev`] accessors
20240 // expose — the canonical dispatch a future per-axis-parametrised
20241 // walker (a future `feira app graph` per-list dep summary, a
20242 // future M4 per-cluster dev-closure-audit overlay the CR
20243 // materializer resolves per-CR) reads through. Prior to the
20244 // lift the two-block iteration lived open-coded at every walker,
20245 // so a future third dep-list axis (`:deps-build`, per CAIXA-SDLC
20246 // §I) would have had to grow a third block at every consumer.
20247 // A regression that dropped the `Dev` arm from `ALL` would flip
20248 // the collected pairs to `[(":deps", &[])]` alone and this pin
20249 // refuses that shape.
20250 let src = Caixa::template("host");
20251 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20252 let prod_dep = Dep {
20253 nome: "caixa-teia".to_string(),
20254 versao: "^0.1".to_string(),
20255 fonte: None,
20256 opcional: false,
20257 caracteristicas: Vec::new(),
20258 };
20259 let dev_dep = Dep {
20260 nome: "tatara-check".to_string(),
20261 versao: "*".to_string(),
20262 fonte: None,
20263 opcional: false,
20264 caracteristicas: Vec::new(),
20265 };
20266 caixa
20267 .push_dep(crate::dep::DepList::Prod, prod_dep)
20268 .expect("push into :deps succeeds");
20269 caixa
20270 .push_dep(crate::dep::DepList::Dev, dev_dep)
20271 .expect("push into :deps-dev succeeds");
20272 let collected: Vec<(&'static str, usize, &str)> = crate::dep::DepList::ALL
20273 .iter()
20274 .map(|&list| {
20275 let slice = caixa.deps_of(list);
20276 (list.as_str(), slice.len(), slice[0].nome())
20277 })
20278 .collect();
20279 assert_eq!(
20280 collected,
20281 vec![
20282 (crate::render::DEP_AUTHOR_KEY_DEPS, 1, "caixa-teia"),
20283 (crate::render::DEP_AUTHOR_KEY_DEPS_DEV, 1, "tatara-check"),
20284 ]
20285 );
20286 }
20287
20288 #[test]
20289 fn caixa_deps_of_is_const_fn() {
20290 // Fail-before-pass-after pin on [`Caixa::deps_of`]'s
20291 // `const`-eval-surface posture. The typed-dispatch read
20292 // accessor forwards through the sibling `pub const fn`
20293 // [`Caixa::deps`] / [`Caixa::deps_dev`] per-slot slice
20294 // accessors on the two [`crate::dep::DepList`] enum arms —
20295 // every operator in the body is already `const`-callable
20296 // (`DepList` is a plain `#[derive(Copy)]` closed-set
20297 // discriminator so the `match` arms are const-evaluable, and
20298 // each arm dispatches through the sibling `pub const fn`
20299 // slice accessor). Any future accidental downgrade to
20300 // non-`const` fails the `deps_of_via_const_fn` wrapper below
20301 // at caixa-core build time with E0015 (`cannot call non-const
20302 // method`), strictly stronger than a runtime `assert!` and
20303 // side-stepping the destructor-in-const restriction the
20304 // `Caixa` fixture's owning `String` / `Vec<Dep>` carriers
20305 // rule out on the direct-`const _: () = assert!(...)`
20306 // residence.
20307 //
20308 // Peer of the sibling outer-`Caixa` accessor family pins
20309 // ([`caixa_outer_string_slice_return_accessor_family_is_const_fn`]
20310 // on the `&[String]` universal-axis surface,
20311 // [`caixa_outer_composite_slice_return_accessor_family_is_const_fn`]
20312 // on the outer `&[T]` composite-slice surface,
20313 // [`caixa_outer_option_composite_reference_return_accessor_family_is_const_fn`]
20314 // on the outer `Option<&Composite>` surface) — this pin
20315 // extends the `const`-eval-surface discipline onto the outer-
20316 // `Caixa` typed-dispatch read surface on the [`DepList`]-keyed
20317 // dep-list axis, closing the outer-`Caixa` accessor family's
20318 // last unlifted `pub fn` on the read side.
20319 const fn deps_of_via_const_fn(c: &Caixa, list: crate::dep::DepList) -> &[Dep] {
20320 c.deps_of(list)
20321 }
20322 let src = Caixa::template("host");
20323 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20324 // Empty-list arm: both `Prod` and `Dev` degenerate to the
20325 // empty slice with no silent `None` collapse — the
20326 // `#[serde(default)]` `Vec::new()` fold every `defcaixa` form
20327 // that omits the slot lands on.
20328 assert!(deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod).is_empty());
20329 assert!(deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev).is_empty());
20330 assert_eq!(
20331 deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod),
20332 caixa.deps()
20333 );
20334 assert_eq!(
20335 deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev),
20336 caixa.deps_dev()
20337 );
20338 // Populated arms: each list carries its own entry, and the
20339 // wrapper / direct dispatches agree byte-for-byte on the
20340 // slice-view under both non-empty arms.
20341 let prod_dep = Dep {
20342 nome: "caixa-teia".to_string(),
20343 versao: "^0.1".to_string(),
20344 fonte: None,
20345 opcional: false,
20346 caracteristicas: Vec::new(),
20347 };
20348 let dev_dep = Dep {
20349 nome: "tatara-check".to_string(),
20350 versao: "*".to_string(),
20351 fonte: None,
20352 opcional: false,
20353 caracteristicas: Vec::new(),
20354 };
20355 caixa
20356 .push_dep(crate::dep::DepList::Prod, prod_dep)
20357 .expect("push into :deps succeeds");
20358 caixa
20359 .push_dep(crate::dep::DepList::Dev, dev_dep)
20360 .expect("push into :deps-dev succeeds");
20361 assert_eq!(
20362 deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod),
20363 caixa.deps()
20364 );
20365 assert_eq!(
20366 deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev),
20367 caixa.deps_dev()
20368 );
20369 assert_eq!(
20370 deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod)[0].nome(),
20371 "caixa-teia"
20372 );
20373 assert_eq!(
20374 deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev)[0].nome(),
20375 "tatara-check"
20376 );
20377 }
20378
20379 #[test]
20380 fn validate_deps_iterates_through_dep_list_all_via_deps_of() {
20381 // Composition pin: the [`Caixa::validate_deps`] parse-time gate
20382 // must route its per-list [`crate::render::insert_first_seen`]
20383 // dedup walk through [`Caixa::deps_of`] + [`crate::dep::DepList::ALL`]
20384 // rather than the pre-lift open-coded two-block iteration over
20385 // `self.deps()` + `self.deps_dev()`. A regression that dropped
20386 // one arm (e.g. hand-inlining `self.deps()` alone) would silently
20387 // stop refusing within-list dups on the sibling arm; a
20388 // regression that flipped the arm-to-list-key mapping
20389 // (`Dev => DEP_AUTHOR_KEY_DEPS`) would silently mislabel the
20390 // diagnostic surface. Both drifts surface here through a paired
20391 // duplicate-name refusal per arm plus an offending-list-key
20392 // check on the emitted [`DepError::DuplicateNome`] carrier.
20393 for &list in crate::dep::DepList::ALL {
20394 let src = Caixa::template("host");
20395 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20396 let dup = Dep {
20397 nome: "twin".to_string(),
20398 versao: "^0.1".to_string(),
20399 fonte: None,
20400 opcional: false,
20401 caracteristicas: Vec::new(),
20402 };
20403 match list {
20404 crate::dep::DepList::Prod => {
20405 caixa.deps.push(dup.clone());
20406 caixa.deps.push(dup);
20407 }
20408 crate::dep::DepList::Dev => {
20409 caixa.deps_dev.push(dup.clone());
20410 caixa.deps_dev.push(dup);
20411 }
20412 }
20413 let err = caixa
20414 .validate_deps()
20415 .expect_err("within-list duplicate :nome must refuse");
20416 assert_eq!(
20417 err,
20418 DepError::DuplicateNome {
20419 nome: "twin".to_string(),
20420 list: list.as_str(),
20421 },
20422 "validate_deps on {list} arm must emit \
20423 DepError::DuplicateNome carrying the arm's own \
20424 as_str() diagnostic — the arm-to-list-key mapping \
20425 flowed through DepList::ALL + Caixa::deps_of"
20426 );
20427 }
20428 }
20429
20430 #[test]
20431 fn caixa_licenca_default_pins_canonical_mit_byte() {
20432 // Bridge-arm pin: [`CAIXA_LICENCA_DEFAULT`] resolves to the
20433 // canonical SPDX-`"MIT"` byte today, the same license expression
20434 // every peer substrate-side consumer of the author-omitted
20435 // `:licenca` slot ([`caixa-helm`]'s `build_readme` fallback arm at
20436 // `caixa-helm/src/lib.rs`, the future M4
20437 // `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer's per-CR
20438 // `Chart.yaml annotations["artifacthub.io/license"]` emitter this
20439 // crate's [`Caixa::validate_licenca`] docstring roadmap already
20440 // names as the second consumer) fills into its per-consumer
20441 // README/annotation emit site. Pin the literal here (peer with the
20442 // [`crate::version::DEFAULT_PUBLISH_TAG_PREFIX`] /
20443 // [`crate::version::DEFAULT_GIT_REMOTE`] /
20444 // [`crate::version::DEFAULT_PLEME_GIT_ORG`] canonical-literal pins
20445 // on the sibling lifted-constant surfaces) so a future
20446 // substrate-side license-fallback rebrand surfaces here as a
20447 // coordinated edit-point: the sibling caixa-helm
20448 // `build_readme_license_line_routes_through_lifted_caixa_licenca_default`
20449 // pinning test already pins the equality at the renderer-emit
20450 // axis; this pin closes the second coordinate of the pair by
20451 // anchoring the lifted constant's current byte to the canonical
20452 // CAIXA-SDLC §I license scaffold's documented shape.
20453 assert_eq!(CAIXA_LICENCA_DEFAULT, "MIT");
20454 }
20455
20456 // ── Caixa::validate_upgrade_from — compound per-Caixa entry gate on ──
20457 // ── the M2 `:upgrade-from` slot: folds the three top-level ──
20458 // ── `crate::upgrade` validators (per-entry + cross-entry ──
20459 // ── duplicate-`:from`, cross-slot `:from < :versao` precedence, ──
20460 // ── cross-slot `:state-change` ↔ `:on-state-change` composition) ──
20461 // ── onto one substrate primitive. Byte-for-byte equivalent to the ──
20462 // ── pre-fold three-block cascade at ──
20463 // ── `crate::layout::StandardLayout::verify` under the same ──
20464 // ── canonical dispatch order. ──
20465
20466 #[test]
20467 fn validate_upgrade_from_folds_per_entry_arm_matches_gate() {
20468 // Fail-before-pass-after per-arm equivalence pin on the
20469 // per-entry + cross-entry axis: a fixture whose `:upgrade-from`
20470 // carries a per-entry-invalid `:from` (git-tag shape `"v0.1.0"`,
20471 // which `semver::Version::parse` rejects) surfaces the same
20472 // [`crate::UpgradeError`] through the compound gate
20473 // [`Caixa::validate_upgrade_from`] and the standalone per-entry
20474 // gate [`crate::upgrade::validate_upgrade_from`] on the same
20475 // [`Caixa::upgrade_from`] slice. Pins the fold — a silent
20476 // regression that de-folded the per-entry arm would surface here
20477 // as a mismatch between the two dispatches. Sibling in shape to
20478 // the peer per-slot-≡-standalone equivalence pins the
20479 // [`crate::AplicacaoSpec::validate_contratos`] /
20480 // [`crate::MeshPolicy::validate`] /
20481 // [`crate::SupervisorSpec::validate_children`] compound gates
20482 // each carry on their axes.
20483 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20484 c.upgrade_from = vec![crate::UpgradeFromEntry {
20485 from: "v0.1.0".into(),
20486 instructions: vec![crate::UpgradeInstruction::Restart],
20487 }];
20488 let via_method = c.validate_upgrade_from().unwrap_err();
20489 let via_standalone = crate::upgrade::validate_upgrade_from(c.upgrade_from()).unwrap_err();
20490 assert_eq!(
20491 via_method, via_standalone,
20492 "Caixa::validate_upgrade_from must surface the per-entry \
20493 axis's diagnostic byte-equal to the standalone \
20494 `crate::upgrade::validate_upgrade_from` on the same \
20495 upgrade_from() slice"
20496 );
20497 assert!(
20498 matches!(
20499 via_method,
20500 crate::UpgradeError::FromInvalid { ref from, .. } if from == "v0.1.0"
20501 ),
20502 "expected FromInvalid on the git-tag-shape `:from`, got {via_method:?}"
20503 );
20504 }
20505
20506 #[test]
20507 fn validate_upgrade_from_folds_versao_arm_matches_gate() {
20508 // Per-arm equivalence pin on the cross-slot `:from ↔ :versao`
20509 // precedence axis: a fixture with a well-formed `:from` (so the
20510 // per-entry arm passes) whose parsed semver is >= the caixa's
20511 // `:versao` under SemVer-2 precedence surfaces the same
20512 // [`crate::UpgradeError::FromNotBeforeVersao`] through both the
20513 // compound gate and the standalone
20514 // [`crate::upgrade::validate_upgrade_from_against_versao`] gate
20515 // keyed off the same `(upgrade_from, versao)` pair. Pins the
20516 // fold's second arm — reaching this arm through the compound
20517 // gate requires the per-entry arm to pass first, which itself
20518 // pins the per-arm cross-arm ordering.
20519 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20520 c.versao = "0.1.0".into();
20521 c.upgrade_from = vec![crate::UpgradeFromEntry {
20522 from: "0.2.0".into(),
20523 instructions: vec![crate::UpgradeInstruction::Restart],
20524 }];
20525 let via_method = c.validate_upgrade_from().unwrap_err();
20526 let via_standalone =
20527 crate::upgrade::validate_upgrade_from_against_versao(c.upgrade_from(), c.versao())
20528 .unwrap_err();
20529 assert_eq!(
20530 via_method, via_standalone,
20531 "Caixa::validate_upgrade_from must surface the \
20532 `:from >= :versao` diagnostic byte-equal to the standalone \
20533 `crate::upgrade::validate_upgrade_from_against_versao` on \
20534 the same (upgrade_from, versao) pair"
20535 );
20536 assert!(
20537 matches!(
20538 via_method,
20539 crate::UpgradeError::FromNotBeforeVersao { ref from, ref versao }
20540 if from == "0.2.0" && versao == "0.1.0"
20541 ),
20542 "expected FromNotBeforeVersao carrying the offending pair, got {via_method:?}"
20543 );
20544 }
20545
20546 #[test]
20547 fn validate_upgrade_from_folds_behavior_arm_matches_gate() {
20548 // Per-arm equivalence pin on the cross-slot `:state-change ↔
20549 // :on-state-change` composition axis: a fixture with a
20550 // well-formed `:from` strictly less than `:versao` (so the
20551 // per-entry and versao arms both pass) whose `:instructions`
20552 // list carries a `(:state-change …)` instruction with no
20553 // `:behavior :on-state-change` callback declared surfaces the
20554 // same [`crate::UpgradeError::StateChangeWithoutOnStateChangeCallback`]
20555 // through both the compound gate and the standalone
20556 // [`crate::upgrade::validate_upgrade_from_against_behavior`]
20557 // gate keyed off the same `(upgrade_from, behavior)` pair.
20558 // Reaching this arm through the compound gate requires both
20559 // prior arms to pass first — the ordering pin below pins the
20560 // per-arm dispatch order explicitly.
20561 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20562 c.versao = "0.2.0".into();
20563 c.behavior = None;
20564 c.upgrade_from = vec![crate::UpgradeFromEntry {
20565 from: "0.1.0".into(),
20566 instructions: vec![
20567 crate::UpgradeInstruction::LoadModule {
20568 module: "demo".into(),
20569 },
20570 crate::UpgradeInstruction::StateChange {
20571 script: std::path::PathBuf::from("lib/m.lisp"),
20572 },
20573 crate::UpgradeInstruction::SoftPurge {
20574 module: "demo-old".into(),
20575 },
20576 ],
20577 }];
20578 let via_method = c.validate_upgrade_from().unwrap_err();
20579 let via_standalone =
20580 crate::upgrade::validate_upgrade_from_against_behavior(c.upgrade_from(), c.behavior())
20581 .unwrap_err();
20582 assert_eq!(
20583 via_method, via_standalone,
20584 "Caixa::validate_upgrade_from must surface the \
20585 `:state-change` ↔ `:on-state-change` composition \
20586 diagnostic byte-equal to the standalone \
20587 `crate::upgrade::validate_upgrade_from_against_behavior` \
20588 on the same (upgrade_from, behavior) pair"
20589 );
20590 assert!(
20591 matches!(
20592 via_method,
20593 crate::UpgradeError::StateChangeWithoutOnStateChangeCallback {
20594 ref from,
20595 ref script,
20596 } if from == "0.1.0" && script == &std::path::PathBuf::from("lib/m.lisp")
20597 ),
20598 "expected StateChangeWithoutOnStateChangeCallback carrying \
20599 the offending (from, script) pair, got {via_method:?}"
20600 );
20601 }
20602
20603 #[test]
20604 fn validate_upgrade_from_per_entry_arm_fires_before_versao_arm() {
20605 // Cross-arm ordering pin between the first two arms of the
20606 // fold: a fixture carrying BOTH a per-entry-invalid `:from`
20607 // (`"v0.0.5"` — git-tag shape rejected by
20608 // [`crate::upgrade::validate_upgrade_from`]) AND a would-be
20609 // versao-precedence violation on a second entry (`"0.2.0" >=
20610 // :versao "0.1.0"`) surfaces the per-entry diagnostic first
20611 // through the compound gate. Sanity assertion: the second
20612 // entry alone under the same `:versao` trips the versao arm
20613 // on its own via the standalone
20614 // [`crate::upgrade::validate_upgrade_from_against_versao`], so
20615 // the per-entry-first surfacing is a real ordering property,
20616 // not a case where the versao arm silently accepts the
20617 // fixture. Pins the pre-fold layout wire-up's canonical
20618 // dispatch order (per-entry → versao → behavior) as a
20619 // property of the substrate primitive rather than a
20620 // convention of the layout call site.
20621 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20622 c.versao = "0.1.0".into();
20623 c.upgrade_from = vec![
20624 crate::UpgradeFromEntry {
20625 from: "v0.0.5".into(),
20626 instructions: vec![crate::UpgradeInstruction::Restart],
20627 },
20628 crate::UpgradeFromEntry {
20629 from: "0.2.0".into(),
20630 instructions: vec![crate::UpgradeInstruction::Restart],
20631 },
20632 ];
20633 let err = c.validate_upgrade_from().unwrap_err();
20634 assert!(
20635 matches!(
20636 err,
20637 crate::UpgradeError::FromInvalid { ref from, .. } if from == "v0.0.5"
20638 ),
20639 "per-entry arm must fire before versao arm — expected \
20640 FromInvalid on `v0.0.5`, got {err:?}"
20641 );
20642 // Sanity: the versao-violating second entry alone under the
20643 // same `:versao` trips the versao arm on its own — proves the
20644 // per-entry-first surfacing above is a real ordering property.
20645 let sanity = crate::upgrade::validate_upgrade_from_against_versao(
20646 &[crate::UpgradeFromEntry {
20647 from: "0.2.0".into(),
20648 instructions: vec![crate::UpgradeInstruction::Restart],
20649 }],
20650 "0.1.0",
20651 )
20652 .unwrap_err();
20653 assert!(
20654 matches!(sanity, crate::UpgradeError::FromNotBeforeVersao { .. }),
20655 "sanity: the versao-violating fixture alone must trip the \
20656 versao arm — got {sanity:?}"
20657 );
20658 }
20659
20660 #[test]
20661 fn validate_upgrade_from_versao_arm_fires_before_behavior_arm() {
20662 // Cross-arm ordering pin between the second and third arms of
20663 // the fold: a fixture carrying BOTH a versao-precedence
20664 // violation (`:from "0.2.0" >= :versao "0.1.0"`) AND a
20665 // would-be missing-callback violation (a `(:state-change …)`
20666 // instruction with no `:behavior :on-state-change`) surfaces
20667 // the versao diagnostic first through the compound gate.
20668 // Sanity assertion: the missing-callback fixture alone (with
20669 // the versao-precedence violation removed by bumping
20670 // `:versao` past `:from`) trips the behavior arm on its own
20671 // via the standalone
20672 // [`crate::upgrade::validate_upgrade_from_against_behavior`],
20673 // so the versao-first surfacing is a real ordering property,
20674 // not a case where the behavior arm silently accepts the
20675 // fixture.
20676 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20677 c.versao = "0.1.0".into();
20678 c.behavior = None;
20679 c.upgrade_from = vec![crate::UpgradeFromEntry {
20680 from: "0.2.0".into(),
20681 instructions: vec![
20682 crate::UpgradeInstruction::LoadModule {
20683 module: "demo".into(),
20684 },
20685 crate::UpgradeInstruction::StateChange {
20686 script: std::path::PathBuf::from("lib/m.lisp"),
20687 },
20688 ],
20689 }];
20690 let err = c.validate_upgrade_from().unwrap_err();
20691 assert!(
20692 matches!(
20693 err,
20694 crate::UpgradeError::FromNotBeforeVersao { ref from, .. } if from == "0.2.0"
20695 ),
20696 "versao arm must fire before behavior arm — expected \
20697 FromNotBeforeVersao on `0.2.0`, got {err:?}"
20698 );
20699 // Sanity: the same instructions under a `:versao` that
20700 // accepts the `:from` (so the versao arm passes) trips the
20701 // behavior arm — proves the versao-first surfacing above is a
20702 // real ordering property.
20703 let sanity = crate::upgrade::validate_upgrade_from_against_behavior(
20704 &[crate::UpgradeFromEntry {
20705 from: "0.2.0".into(),
20706 instructions: vec![
20707 crate::UpgradeInstruction::LoadModule {
20708 module: "demo".into(),
20709 },
20710 crate::UpgradeInstruction::StateChange {
20711 script: std::path::PathBuf::from("lib/m.lisp"),
20712 },
20713 ],
20714 }],
20715 None,
20716 )
20717 .unwrap_err();
20718 assert!(
20719 matches!(
20720 sanity,
20721 crate::UpgradeError::StateChangeWithoutOnStateChangeCallback { .. }
20722 ),
20723 "sanity: the missing-callback fixture alone must trip the \
20724 behavior arm — got {sanity:?}"
20725 );
20726 }
20727
20728 #[test]
20729 fn validate_upgrade_from_accepts_clean_fixture() {
20730 // Positive control: a well-formed `:upgrade-from` (single entry
20731 // with `:from` strictly less than `:versao`, no
20732 // `:state-change` instruction so the behavior arm is vacuous)
20733 // passes the compound gate cleanly. A future tightening of any
20734 // one arm's accepted set surfaces here as a test failure
20735 // first. Mirrors the peer `validate_versao_accepts_canonical_forms`
20736 // positive-control posture on the sibling per-Caixa gate.
20737 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20738 c.versao = "0.2.0".into();
20739 c.upgrade_from = vec![crate::UpgradeFromEntry {
20740 from: "0.1.0".into(),
20741 instructions: vec![crate::UpgradeInstruction::Restart],
20742 }];
20743 c.validate_upgrade_from()
20744 .expect("clean fixture must pass the compound `:upgrade-from` gate");
20745 }
20746
20747 #[test]
20748 fn validate_upgrade_from_accepts_empty_upgrade_from() {
20749 // Positive control on the empty-list arm: a caixa without any
20750 // `:upgrade-from` block (the default `Vec::new()`
20751 // `#[serde(default)]` folds an omitted slot onto) passes the
20752 // compound gate cleanly regardless of `:versao` or `:behavior`
20753 // — each of the three standalone validators is vacuous on the
20754 // empty entry list. Pins the identity element of the fold on
20755 // the empty-slot side.
20756 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20757 assert!(
20758 c.upgrade_from().is_empty(),
20759 "template caixa must carry an empty :upgrade-from — got {:?}",
20760 c.upgrade_from()
20761 );
20762 c.validate_upgrade_from()
20763 .expect("empty :upgrade-from must pass the compound gate cleanly");
20764 }
20765
20766 // ── Caixa::validate_limits — compound per-Caixa entry gate on ──
20767 // ── the M2 `:limits` slot: folds the ──
20768 // ── [`crate::LimitsSpec::validate`] four-axis cascade on the ──
20769 // ── present-slot arm and the `Option::None` identity element on ──
20770 // ── the absent-slot arm onto one substrate primitive. ──
20771 // ── Byte-for-byte equivalent to the pre-fold ──
20772 // ── `if let Some(l) = caixa.limits() { l.validate() }` ──
20773 // ── unwrap-and-dispatch pattern at ──
20774 // ── `crate::layout::StandardLayout::verify` (`layout.rs`). ──
20775
20776 #[test]
20777 fn validate_limits_folds_arm_matches_gate() {
20778 // Fail-before-pass-after per-arm equivalence pin on the
20779 // present-slot arm: a fixture whose `:limits` carries a
20780 // zero-floor-violating `:fuel` (`Some(0)`, which
20781 // [`crate::LimitsSpec::validate`] rejects through
20782 // [`crate::LimitsError::FuelZero`]) surfaces the same
20783 // [`crate::LimitsError`] byte-equal through both the compound
20784 // gate [`Caixa::validate_limits`] and the standalone
20785 // [`crate::LimitsSpec::validate`] gate on the same `LimitsSpec`
20786 // value. Pins the fold — a silent regression that de-folded
20787 // the present-slot arm would surface here as a mismatch
20788 // between the two dispatches. Sibling in shape to the peer
20789 // per-arm equivalence pins the
20790 // [`crate::AplicacaoSpec::validate_contratos`] /
20791 // [`crate::MeshPolicy::validate`] /
20792 // [`crate::SupervisorSpec::validate_children`] /
20793 // [`Caixa::validate_upgrade_from`] compound gates each carry
20794 // on their axes.
20795 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20796 let l = crate::LimitsSpec {
20797 memory: None,
20798 fuel: Some(0),
20799 wall_clock: None,
20800 cpu: None,
20801 };
20802 c.limits = Some(l);
20803 let via_method = c.validate_limits().unwrap_err();
20804 let via_standalone = l.validate().unwrap_err();
20805 assert_eq!(
20806 via_method, via_standalone,
20807 "Caixa::validate_limits must surface the present-slot \
20808 arm's diagnostic byte-equal to the standalone \
20809 `LimitsSpec::validate` on the same `LimitsSpec` value"
20810 );
20811 assert!(
20812 matches!(via_method, crate::LimitsError::FuelZero),
20813 "expected FuelZero on the zero-floor-violating `:fuel`, \
20814 got {via_method:?}"
20815 );
20816 }
20817
20818 #[test]
20819 fn validate_limits_accepts_none() {
20820 // Positive control on the absent-slot arm (the fold's identity
20821 // element): a caixa without any `:limits` block (the
20822 // canonical "no bound declared — engine-default applies"
20823 // author shape [`crate::LimitsSpec::is_empty`]'s per-axis
20824 // `None` cascade reads, and the shape the [`Caixa::template`]
20825 // scaffold emits by construction) passes the compound gate
20826 // cleanly, regardless of any per-axis defect a subsequent
20827 // `Some(_)` binding would surface. Pins the identity element
20828 // of the fold on the absent-slot side, matching the peer
20829 // `validate_upgrade_from_accepts_empty_upgrade_from` positive-
20830 // control posture on the sibling M2 slot.
20831 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20832 assert!(
20833 c.limits().is_none(),
20834 "template caixa must carry an absent :limits — got {:?}",
20835 c.limits()
20836 );
20837 c.validate_limits()
20838 .expect("absent :limits must pass the compound gate cleanly");
20839 }
20840
20841 #[test]
20842 fn validate_limits_accepts_clean_fixture() {
20843 // Positive control on the present-slot arm: a caixa whose
20844 // `:limits` is `Some(LimitsSpec::default())` (all four axes
20845 // `None` — every axis absent under the outer `Some(_)`
20846 // binding, so every present-slot arm on
20847 // [`crate::LimitsSpec::validate`] is vacuous) passes the
20848 // compound gate cleanly. A future tightening of any one axis
20849 // that surfaces a diagnostic on the all-`None` `LimitsSpec`
20850 // would land here as a test failure first. Pins the
20851 // present-slot arm's accept-shape on the canonical
20852 // "declared-but-empty" author fixture the
20853 // `limits_round_trip_via_json` peer already round-trips
20854 // (`caixa-core/src/manifest.rs:6971`).
20855 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20856 c.limits = Some(crate::LimitsSpec::default());
20857 c.validate_limits()
20858 .expect("Some(LimitsSpec::default()) must pass the compound gate cleanly");
20859 }
20860
20861 // ── Caixa::validate_behavior — compound per-Caixa entry gate on ──
20862 // ── the M2 `:behavior` slot's pure value-shape surface: folds ──
20863 // ── the [`crate::BehaviorSpec::validate`] six-slot cascade on ──
20864 // ── the present-slot arm and the `Option::None` identity ──
20865 // ── element on the absent-slot arm onto one substrate primitive.──
20866 // ── Byte-for-byte equivalent to the pre-fold ──
20867 // ── `if let Some(b) = caixa.behavior() { b.validate() }` ──
20868 // ── unwrap-and-dispatch pattern at ──
20869 // ── `crate::layout::StandardLayout::verify` (`layout.rs`). The ──
20870 // ── on-disk callback-path existence walk stays open-coded at ──
20871 // ── the layout altitude because it needs the ──
20872 // ── [`crate::layout::LayoutInvariants::exists`] filesystem ──
20873 // ── oracle the pure typed-shape surface has no reference to — ──
20874 // ── mirror of the peer M2 `:upgrade-from` per-instruction ──
20875 // ── script-path existence probe that stayed at the layout ──
20876 // ── altitude after the [`Caixa::validate_upgrade_from`] lift ──
20877 // ── (d6801df) for the same reason. ──
20878
20879 #[test]
20880 fn validate_behavior_folds_arm_matches_gate() {
20881 // Fail-before-pass-after per-arm equivalence pin on the
20882 // present-slot arm: a fixture whose `:behavior` carries an
20883 // absolute-path `:on-init` (`"/etc/passwd"`, which
20884 // [`crate::BehaviorSpec::validate`] rejects through
20885 // [`crate::BehaviorError::AbsolutePath`]) surfaces the same
20886 // [`crate::BehaviorError`] byte-equal through both the
20887 // compound gate [`Caixa::validate_behavior`] and the standalone
20888 // [`crate::BehaviorSpec::validate`] gate on the same
20889 // `BehaviorSpec` value. Pins the fold — a silent regression
20890 // that de-folded the present-slot arm would surface here as a
20891 // mismatch between the two dispatches. Sibling in shape to the
20892 // peer per-arm equivalence pins the
20893 // [`Caixa::validate_limits`] (baa4688),
20894 // [`Caixa::validate_upgrade_from`] (d6801df),
20895 // [`crate::MeshPolicy::validate`],
20896 // [`crate::AplicacaoSpec::validate_contratos`], and
20897 // [`crate::SupervisorSpec::validate_children`] compound gates
20898 // each carry on their axes.
20899 use crate::BehaviorSpec;
20900 use std::path::PathBuf;
20901 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20902 let b = BehaviorSpec {
20903 on_init: Some(PathBuf::from("/etc/passwd")),
20904 ..Default::default()
20905 };
20906 c.behavior = Some(b.clone());
20907 let via_method = c.validate_behavior().unwrap_err();
20908 let via_standalone = b.validate().unwrap_err();
20909 assert_eq!(
20910 via_method, via_standalone,
20911 "Caixa::validate_behavior must surface the present-slot \
20912 arm's diagnostic byte-equal to the standalone \
20913 `BehaviorSpec::validate` on the same `BehaviorSpec` value"
20914 );
20915 assert!(
20916 matches!(via_method, crate::BehaviorError::AbsolutePath { .. }),
20917 "expected AbsolutePath on the absolute `:on-init` path, \
20918 got {via_method:?}"
20919 );
20920 }
20921
20922 #[test]
20923 fn validate_behavior_accepts_none() {
20924 // Positive control on the absent-slot arm (the fold's identity
20925 // element): a caixa without any `:behavior` block (the
20926 // canonical "no callback declared — the runtime falls back to
20927 // the wasm-engine's default per arm" author shape
20928 // [`crate::BehaviorSpec::is_empty`]'s per-slot `None` cascade
20929 // reads, and the shape the [`Caixa::template`] scaffold emits
20930 // by construction) passes the compound gate cleanly,
20931 // regardless of any per-slot defect a subsequent `Some(_)`
20932 // binding would surface. Pins the identity element of the fold
20933 // on the absent-slot side, matching the peer
20934 // `validate_limits_accepts_none` (baa4688) and
20935 // `validate_upgrade_from_accepts_empty_upgrade_from` (d6801df)
20936 // positive-control postures on the sibling M2 slots.
20937 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20938 assert!(
20939 c.behavior().is_none(),
20940 "template caixa must carry an absent :behavior — got {:?}",
20941 c.behavior()
20942 );
20943 c.validate_behavior()
20944 .expect("absent :behavior must pass the compound gate cleanly");
20945 }
20946
20947 #[test]
20948 fn validate_behavior_accepts_clean_fixture() {
20949 // Positive control on the present-slot arm: a caixa whose
20950 // `:behavior` is `Some(BehaviorSpec::default())` (all six
20951 // slots `None` — every slot absent under the outer `Some(_)`
20952 // binding, so every present-slot arm on
20953 // [`crate::BehaviorSpec::validate`] is vacuous) passes the
20954 // compound gate cleanly. A future tightening of any one arm
20955 // that surfaces a diagnostic on the all-`None` `BehaviorSpec`
20956 // would land here as a test failure first. Pins the
20957 // present-slot arm's accept-shape on the canonical
20958 // "declared-but-empty" author fixture the sibling
20959 // `empty_behavior_round_trip` peer already round-trips
20960 // (`caixa-core/src/behavior.rs` tests). Mirror of the peer
20961 // `validate_limits_accepts_clean_fixture` (baa4688)
20962 // positive-control posture on the sibling M2 `:limits` slot.
20963 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20964 c.behavior = Some(crate::BehaviorSpec::default());
20965 c.validate_behavior()
20966 .expect("Some(BehaviorSpec::default()) must pass the compound gate cleanly");
20967 }
20968
20969 // ── Caixa::validate_deps — compound per-Caixa entry gate on the ──
20970 // ── dep-graph axis: folds the two standalone validators ──
20971 // ── (per-entry + within-list duplicate walk that this method ──
20972 // ── opened on, cross-slot self-edge via ──
20973 // ── `crate::dep::validate_no_self_dep`) onto one substrate ──
20974 // ── primitive. Byte-for-byte equivalent to the pre-fold ──
20975 // ── two-block cascade at ──
20976 // ── `crate::layout::StandardLayout::verify` under the same ──
20977 // ── canonical dispatch order (per-entry → self-edge). ──
20978
20979 #[test]
20980 fn validate_deps_folds_per_entry_arm_matches_gate() {
20981 // Fail-before-pass-after per-arm equivalence pin on the
20982 // per-entry + within-list duplicate axis: a fixture whose
20983 // `:deps` carries a per-entry-invalid `:versao` (`"^bad"`,
20984 // which [`crate::parse_requirement`] rejects) surfaces the
20985 // same [`crate::DepError`] through the compound gate
20986 // [`Caixa::validate_deps`] and the standalone per-entry walk
20987 // ([`Dep::validate`]) on the offending entry. Pins the
20988 // fold — a silent regression that de-folded the per-entry arm
20989 // would surface here as a mismatch between the two
20990 // dispatches. Sibling in shape to the peer
20991 // `validate_upgrade_from_folds_per_entry_arm_matches_gate`
20992 // per-arm equivalence pin (d6801df) on the M2
20993 // `:upgrade-from` compound gate's per-entry arm, extended
20994 // here onto the universal-axis `:deps` compound gate's
20995 // per-entry arm.
20996 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20997 c.deps = vec![Dep::simple("d", "^bad")];
20998 let via_method = c.validate_deps().unwrap_err();
20999 let via_standalone = c.deps()[0].validate().unwrap_err();
21000 assert_eq!(
21001 via_method, via_standalone,
21002 "Caixa::validate_deps must surface the per-entry arm's \
21003 diagnostic byte-equal to the standalone \
21004 `Dep::validate` on the same offending entry",
21005 );
21006 assert!(
21007 matches!(
21008 via_method,
21009 DepError::VersaoInvalid { ref nome, .. } if nome == "d"
21010 ),
21011 "expected VersaoInvalid on the malformed :versao, got {via_method:?}",
21012 );
21013 }
21014
21015 #[test]
21016 fn validate_deps_folds_self_edge_arm_matches_gate() {
21017 // Per-arm equivalence pin on the cross-slot self-edge axis:
21018 // a fixture whose `:deps` lists the caixa's own `:nome`
21019 // (a self-dep, which
21020 // [`crate::dep::validate_no_self_dep`] rejects as a
21021 // structurally-invalid one-node cycle in the lacre closure's
21022 // dep-graph) surfaces the same [`crate::DepError::DepIsSelf`]
21023 // through both the compound gate and the standalone
21024 // [`crate::dep::validate_no_self_dep`] gate keyed off the
21025 // same `(deps, deps_dev, nome)` triple. Pins the fold's
21026 // second arm — reaching this arm through the compound gate
21027 // requires the per-entry + within-list duplicate walk to
21028 // pass first, which itself pins one cross-arm ordering step.
21029 // Sibling in shape to the peer
21030 // `validate_upgrade_from_folds_versao_arm_matches_gate` /
21031 // `_folds_behavior_arm_matches_gate` cross-slot equivalence
21032 // pins (d6801df) on the M2 `:upgrade-from` compound gate's
21033 // cross-slot arms.
21034 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21035 c.deps = vec![Dep::simple("demo", "^0.1")];
21036 let via_method = c.validate_deps().unwrap_err();
21037 let via_standalone =
21038 crate::dep::validate_no_self_dep(c.deps(), c.deps_dev(), c.nome()).unwrap_err();
21039 assert_eq!(
21040 via_method, via_standalone,
21041 "Caixa::validate_deps must surface the cross-slot \
21042 self-edge diagnostic byte-equal to the standalone \
21043 `crate::dep::validate_no_self_dep` on the same \
21044 (deps, deps_dev, nome) triple",
21045 );
21046 assert!(
21047 matches!(
21048 via_method,
21049 DepError::DepIsSelf { ref nome, list }
21050 if nome == "demo" && list == crate::render::DEP_AUTHOR_KEY_DEPS
21051 ),
21052 "expected DepIsSelf carrying (nome=\"demo\", list=\":deps\"), got {via_method:?}",
21053 );
21054 }
21055
21056 #[test]
21057 fn validate_deps_per_entry_arm_fires_before_self_edge_arm() {
21058 // Cross-arm ordering pin between the two arms of the fold:
21059 // a fixture carrying BOTH a per-entry-invalid `:versao`
21060 // (`"^bad"` — [`crate::parse_requirement`] rejects the
21061 // requirement grammar) on a non-self-dep entry AND a
21062 // would-be self-edge violation on a second entry (the
21063 // caixa's own `:nome` "demo") surfaces the per-entry
21064 // diagnostic first through the compound gate. Sanity
21065 // assertion: the second entry alone under the same parent
21066 // `:nome` trips the self-edge arm on its own via the
21067 // standalone [`crate::dep::validate_no_self_dep`], so the
21068 // per-entry-first surfacing is a real ordering property,
21069 // not a case where the self-edge arm silently accepts the
21070 // fixture. Pins the pre-fold layout wire-up's canonical
21071 // dispatch order (per-entry + within-list duplicate →
21072 // self-edge) as a property of the substrate primitive
21073 // rather than a convention of the layout call site. Sibling
21074 // in shape to
21075 // `validate_upgrade_from_per_entry_arm_fires_before_versao_arm`
21076 // (d6801df) on the M2 `:upgrade-from` compound gate's
21077 // per-arm ordering property.
21078 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21079 c.deps = vec![
21080 Dep::simple("orquestra", "^bad"),
21081 Dep::simple("demo", "^0.1"),
21082 ];
21083 let err = c.validate_deps().unwrap_err();
21084 assert!(
21085 matches!(
21086 err,
21087 DepError::VersaoInvalid { ref nome, .. } if nome == "orquestra"
21088 ),
21089 "per-entry arm must fire before self-edge arm — expected \
21090 VersaoInvalid on \"orquestra\", got {err:?}",
21091 );
21092 // Sanity: the self-referential entry alone under the same
21093 // parent `:nome` trips the self-edge arm on its own — proves
21094 // the per-entry-first surfacing above is a real ordering
21095 // property, not a case where the self-edge arm silently
21096 // accepts the fixture.
21097 let sanity = crate::dep::validate_no_self_dep(&[Dep::simple("demo", "^0.1")], &[], "demo")
21098 .unwrap_err();
21099 assert!(
21100 matches!(sanity, DepError::DepIsSelf { ref nome, .. } if nome == "demo"),
21101 "sanity: the self-referential entry alone must trip the \
21102 self-edge arm — got {sanity:?}",
21103 );
21104 }
21105
21106 #[test]
21107 fn validate_deps_accepts_clean_fixture() {
21108 // Positive control: a well-formed dep-graph (one `:deps`
21109 // entry naming a non-self DNS-1123 nome + Cargo-shaped
21110 // requirement, one `:deps-dev` entry on a distinct non-self
21111 // nome) passes the compound gate cleanly. A future
21112 // tightening of either arm's accepted set surfaces here as
21113 // a test failure first. Mirrors the peer
21114 // `validate_upgrade_from_accepts_clean_fixture` positive-
21115 // control posture on the sibling per-Caixa compound gate.
21116 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21117 c.deps = vec![Dep::simple("caixa-teia", "^0.1")];
21118 c.deps_dev = vec![Dep::simple("caixa-lint", "^0.2")];
21119 c.validate_deps()
21120 .expect("clean fixture must pass the compound `:deps` gate");
21121 }
21122
21123 #[test]
21124 fn validate_deps_accepts_empty_deps_lists() {
21125 // Positive control on the empty-list arm: a caixa without
21126 // any `:deps` or `:deps-dev` entries (the default
21127 // `Vec::new()` `#[serde(default)]` folds an omitted slot
21128 // onto) passes the compound gate cleanly regardless of
21129 // `:nome` — both the per-entry walk and the self-edge walk
21130 // are vacuous on the empty entry list. Pins the identity
21131 // element of the fold on the empty-slot side, peer with the
21132 // `validate_upgrade_from_accepts_empty_upgrade_from` empty-
21133 // arm positive control (d6801df) on the sibling
21134 // `:upgrade-from` compound gate.
21135 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21136 assert!(
21137 c.deps().is_empty(),
21138 "template caixa must carry an empty :deps — got {:?}",
21139 c.deps(),
21140 );
21141 assert!(
21142 c.deps_dev().is_empty(),
21143 "template caixa must carry an empty :deps-dev — got {:?}",
21144 c.deps_dev(),
21145 );
21146 c.validate_deps()
21147 .expect("empty :deps / :deps-dev must pass the compound gate cleanly");
21148 }
21149
21150 // ── Caixa::validate_aplicacao_shape — compound per-Caixa gate ────────
21151
21152 /// Build a minimal well-formed Aplicacao fixture on top of the
21153 /// canonical template. Every arm of the compound gate then patches
21154 /// exactly one axis away from clean so its per-arm diagnostic
21155 /// surfaces without collateral noise from a peer slot.
21156 fn aplicacao_fixture(nome: &str) -> Caixa {
21157 use crate::aplicacao::{Membro, Placement, PlacementStrategy};
21158 let mut c = Caixa::from_lisp(&Caixa::template(nome)).unwrap();
21159 c.kind = CaixaKind::Aplicacao;
21160 c.bibliotecas = vec![];
21161 c.membros = vec![
21162 Membro {
21163 caixa: "checkout".into(),
21164 versao: "^0.1".into(),
21165 },
21166 Membro {
21167 caixa: "cart".into(),
21168 versao: "^0.1".into(),
21169 },
21170 ];
21171 // `:placement` defaults to `Replicated` with an empty
21172 // `:clusters` list which
21173 // [`crate::AplicacaoSpec::validate_placement`] refuses; every
21174 // per-strategy variant needs at least one named cluster (per
21175 // MESH-COMPOSITION §II.1). Pin a single-cluster `SingleNode`
21176 // placement so the typed-shape cascade passes cleanly and the
21177 // per-arm fixtures below can each patch exactly one axis.
21178 c.placement = Some(Placement {
21179 estrategia: PlacementStrategy::SingleNode,
21180 clusters: vec!["rio".into()],
21181 shard_key: None,
21182 affinity: None,
21183 });
21184 c
21185 }
21186
21187 #[test]
21188 fn validate_aplicacao_shape_folds_view_arm_matches_gate() {
21189 // Fail-before-pass-after per-arm equivalence pin on the
21190 // typed-shape cascade arm: a fixture whose typed
21191 // [`crate::AplicacaoSpec`] view fails
21192 // [`crate::AplicacaoSpec::validate`] (here — empty `:membros`,
21193 // which [`crate::AplicacaoSpec::validate_membros`] rejects as
21194 // [`crate::AplicacaoError::NoMembros`] at the first per-slot
21195 // gate) surfaces the same [`crate::AplicacaoError`] diagnostic
21196 // through both the compound gate
21197 // [`Caixa::validate_aplicacao_shape`] and the standalone
21198 // [`crate::AplicacaoSpec::validate`] on the same folded view.
21199 // Pins the fold — a silent regression that de-folded the
21200 // typed-shape arm would surface here as a mismatch between the
21201 // two dispatches. Sibling in shape to the peer
21202 // `validate_deps_folds_per_entry_arm_matches_gate` (b5dd55e) /
21203 // `validate_upgrade_from_folds_per_entry_arm_matches_gate`
21204 // (d6801df) per-arm equivalence pins on the sibling per-slot
21205 // compound gates.
21206 let mut c = aplicacao_fixture("demo");
21207 c.membros = vec![];
21208 let via_method = c.validate_aplicacao_shape().unwrap_err();
21209 let via_standalone = c.aplicacao_view().unwrap().validate().unwrap_err();
21210 assert_eq!(
21211 via_method, via_standalone,
21212 "Caixa::validate_aplicacao_shape must surface the typed-\
21213 shape arm's diagnostic byte-equal to the standalone \
21214 `AplicacaoSpec::validate` on the same folded view",
21215 );
21216 assert!(
21217 matches!(via_method, crate::AplicacaoError::NoMembros),
21218 "expected NoMembros on the empty :membros, got {via_method:?}",
21219 );
21220 }
21221
21222 #[test]
21223 fn validate_aplicacao_shape_folds_self_membership_arm_matches_gate() {
21224 // Per-arm equivalence pin on the cross-slot self-edge axis: a
21225 // fixture whose `:membros` names the Aplicacao's own `:nome`
21226 // (which [`crate::aplicacao::validate_no_self_membership`]
21227 // rejects as [`crate::AplicacaoError::MembroIsSelfAplicacao`],
21228 // a one-node lacre-closure recursion in the Aplicacao's
21229 // mesh-graph) surfaces the same
21230 // [`crate::AplicacaoError::MembroIsSelfAplicacao`] through both
21231 // the compound gate and the standalone
21232 // [`crate::aplicacao::validate_no_self_membership`] keyed off
21233 // the same `(membros, nome)` pair. Pins the fold's second arm
21234 // — reaching this arm through the compound gate requires the
21235 // typed-shape cascade to pass first, which itself pins one
21236 // cross-arm ordering step. Sibling in shape to the peer
21237 // `validate_deps_folds_self_edge_arm_matches_gate` (b5dd55e)
21238 // cross-slot equivalence pin on the sibling per-slot compound
21239 // gate.
21240 use crate::aplicacao::Membro;
21241 let mut c = aplicacao_fixture("demo");
21242 c.membros = vec![Membro {
21243 caixa: "demo".into(),
21244 versao: "^0.1".into(),
21245 }];
21246 let via_method = c.validate_aplicacao_shape().unwrap_err();
21247 let via_standalone =
21248 crate::aplicacao::validate_no_self_membership(c.membros(), c.nome()).unwrap_err();
21249 assert_eq!(
21250 via_method, via_standalone,
21251 "Caixa::validate_aplicacao_shape must surface the cross-\
21252 slot self-edge diagnostic byte-equal to the standalone \
21253 `aplicacao::validate_no_self_membership` on the same \
21254 (membros, nome) pair",
21255 );
21256 assert!(
21257 matches!(
21258 via_method,
21259 crate::AplicacaoError::MembroIsSelfAplicacao { ref caixa } if caixa == "demo"
21260 ),
21261 "expected MembroIsSelfAplicacao carrying (caixa=\"demo\"), \
21262 got {via_method:?}",
21263 );
21264 }
21265
21266 #[test]
21267 fn validate_aplicacao_shape_view_arm_fires_before_self_membership_arm() {
21268 // Cross-arm ordering pin between the two arms of the fold: a
21269 // fixture carrying BOTH a typed-shape violation (a `:contratos`
21270 // edge whose `:para` is not a declared member — rejected by
21271 // [`crate::AplicacaoSpec::validate_contratos`] as
21272 // [`crate::AplicacaoError::ContratoMemberMissing`]) AND a
21273 // would-be self-edge violation (a `:membros` entry naming the
21274 // caixa's own `:nome`) surfaces the typed-shape diagnostic
21275 // first through the compound gate. Sanity assertion: the
21276 // self-referential `:membros` entry alone under the same
21277 // parent `:nome` trips the self-edge arm on its own via the
21278 // standalone [`crate::aplicacao::validate_no_self_membership`],
21279 // so the typed-shape-first surfacing is a real ordering
21280 // property, not a case where the self-edge arm silently
21281 // accepts the fixture. Pins the pre-fold layout wire-up's
21282 // canonical dispatch order (typed-shape cascade → cross-slot
21283 // self-edge) as a property of the substrate primitive rather
21284 // than a convention of the layout call site. Sibling in shape
21285 // to `validate_deps_per_entry_arm_fires_before_self_edge_arm`
21286 // (b5dd55e) on the sibling per-slot compound gate's per-arm
21287 // ordering property.
21288 use crate::aplicacao::{Membro, WitContract};
21289 let mut c = aplicacao_fixture("demo");
21290 c.membros = vec![Membro {
21291 caixa: "demo".into(),
21292 versao: "^0.1".into(),
21293 }];
21294 c.contratos = vec![WitContract {
21295 de: "demo".into(),
21296 para: "orphan".into(),
21297 wit: "wasi:http/proxy".into(),
21298 endpoint: Some("/x".into()),
21299 subject: None,
21300 slot: None,
21301 }];
21302 let err = c.validate_aplicacao_shape().unwrap_err();
21303 assert!(
21304 matches!(
21305 err,
21306 crate::AplicacaoError::ContratoMemberMissing { ref caixa }
21307 if caixa == "orphan"
21308 ),
21309 "typed-shape arm must fire before self-edge arm — expected \
21310 ContratoMemberMissing on \"orphan\", got {err:?}",
21311 );
21312 // Sanity: the self-referential `:membros` entry alone under
21313 // the same parent `:nome` trips the self-edge arm on its own
21314 // — proves the typed-shape-first surfacing above is a real
21315 // ordering property, not a case where the self-edge arm
21316 // silently accepts the fixture.
21317 let sanity = crate::aplicacao::validate_no_self_membership(
21318 &[Membro {
21319 caixa: "demo".into(),
21320 versao: "^0.1".into(),
21321 }],
21322 "demo",
21323 )
21324 .unwrap_err();
21325 assert!(
21326 matches!(
21327 sanity,
21328 crate::AplicacaoError::MembroIsSelfAplicacao { ref caixa }
21329 if caixa == "demo"
21330 ),
21331 "sanity: the self-referential :membros entry alone must \
21332 trip the self-edge arm — got {sanity:?}",
21333 );
21334 }
21335
21336 #[test]
21337 fn validate_aplicacao_shape_accepts_non_aplicacao_kind() {
21338 // Positive control on the identity-element arm: every non-
21339 // Aplicacao kind passes the compound gate trivially — the
21340 // paired [`Caixa::aplicacao_view`] accessor returns `None`
21341 // off the Aplicacao arm (by construction, keyed on
21342 // `caixa.kind().is_aplicacao()`), so the fold short-circuits
21343 // to `Ok(())` without touching the mesh slots. Pins the
21344 // identity element on every non-Aplicacao kind — a future
21345 // refactor that made the mesh-slot cascade fire on the wrong
21346 // kind (say, on a `Servico` whose mesh slots happen to be
21347 // populated in a mis-authored manifest, which the peer
21348 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
21349 // coherence gate would refuse upstream anyway) surfaces here
21350 // as a test failure first. Peer with the
21351 // `validate_limits_accepts_none` / `validate_behavior_accepts_none`
21352 // identity-element pins on the sibling M2 `Option`-shaped
21353 // per-Caixa compound gates.
21354 for kind in [
21355 CaixaKind::Biblioteca,
21356 CaixaKind::Binario,
21357 CaixaKind::Servico,
21358 CaixaKind::Supervisor,
21359 CaixaKind::Acao,
21360 ] {
21361 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21362 c.kind = kind;
21363 assert!(
21364 c.aplicacao_view().is_none(),
21365 "aplicacao_view must return None off the Aplicacao arm \
21366 for kind {kind:?}",
21367 );
21368 c.validate_aplicacao_shape().expect(
21369 "non-Aplicacao kinds must pass the compound gate as the fold's identity element",
21370 );
21371 }
21372 }
21373
21374 #[test]
21375 fn validate_aplicacao_shape_accepts_clean_fixture() {
21376 // Positive control: a well-formed Aplicacao (two DNS-1123
21377 // members with valid semver constraints, no `:contratos` /
21378 // `:entrada` / `:placement` / `:politicas` set — every
21379 // per-slot gate accepts the vacuous / omitted arm) passes the
21380 // compound gate cleanly. A future tightening of either arm's
21381 // accepted set surfaces here as a test failure first. Mirrors
21382 // the peer `validate_deps_accepts_clean_fixture` (b5dd55e) /
21383 // `validate_upgrade_from_accepts_clean_fixture` (d6801df)
21384 // positive-control postures on the sibling per-Caixa
21385 // compound gates.
21386 let c = aplicacao_fixture("demo");
21387 c.validate_aplicacao_shape()
21388 .expect("clean Aplicacao fixture must pass the compound gate");
21389 }
21390
21391 // ── Caixa::validate_supervisor_shape — compound per-Caixa gate ───────
21392
21393 /// Build a minimal well-formed Supervisor fixture on top of the
21394 /// canonical template. Every arm of the compound gate then patches
21395 /// exactly one axis away from clean so its per-arm diagnostic
21396 /// surfaces without collateral noise from a peer slot. Peer of
21397 /// [`aplicacao_fixture`] on the sibling per-Aplicacao compound
21398 /// gate's pin family.
21399 fn supervisor_fixture(nome: &str) -> Caixa {
21400 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
21401 let mut c = Caixa::from_lisp(&Caixa::template(nome)).unwrap();
21402 c.kind = CaixaKind::Supervisor;
21403 // Supervisors don't run code — clear the biblioteca slot the
21404 // template seeds so the fold's per-arm diagnostics surface
21405 // without the peer `SupervisorOwnsCode` kind-coherence gate
21406 // firing upstream at the layout altitude.
21407 c.bibliotecas = vec![];
21408 // `:estrategia` defaults to `OneForOne` at the typed view level,
21409 // and `OneForOne` requires at least one `:children` entry — pin
21410 // a single-child `Permanent` worker so the typed-shape cascade
21411 // passes cleanly and the per-arm fixtures below can each patch
21412 // exactly one axis.
21413 c.estrategia = Some(RestartStrategy::OneForOne);
21414 c.children = vec![ChildSpec {
21415 caixa: "worker".into(),
21416 versao: "^0.1".into(),
21417 restart: RestartPolicy::Permanent,
21418 }];
21419 c
21420 }
21421
21422 #[test]
21423 fn validate_supervisor_shape_folds_view_arm_matches_gate() {
21424 // Fail-before-pass-after per-arm equivalence pin on the
21425 // typed-shape cascade arm: a fixture whose typed
21426 // [`crate::SupervisorSpec`] view fails
21427 // [`crate::SupervisorSpec::validate`] (here — a duplicate
21428 // `:children` `:caixa` entry, which
21429 // [`crate::SupervisorSpec::validate`]'s set-not-multiset gate
21430 // rejects as [`crate::SupervisorError::DuplicateChildCaixa`])
21431 // surfaces the same [`crate::SupervisorError`] diagnostic
21432 // through both the compound gate
21433 // [`Caixa::validate_supervisor_shape`] and the standalone
21434 // [`crate::SupervisorSpec::validate`] on the same folded view.
21435 // Pins the fold — a silent regression that de-folded the
21436 // typed-shape arm would surface here as a mismatch between the
21437 // two dispatches. Sibling in shape to the peer
21438 // `validate_aplicacao_shape_folds_view_arm_matches_gate`
21439 // (949a7a0) on the sibling per-Aplicacao compound gate.
21440 use crate::supervisor::{ChildSpec, RestartPolicy};
21441 let mut c = supervisor_fixture("demo");
21442 c.children = vec![
21443 ChildSpec {
21444 caixa: "worker".into(),
21445 versao: "^0.1".into(),
21446 restart: RestartPolicy::Permanent,
21447 },
21448 ChildSpec {
21449 caixa: "worker".into(),
21450 versao: "^0.1".into(),
21451 restart: RestartPolicy::Permanent,
21452 },
21453 ];
21454 let via_method = c.validate_supervisor_shape().unwrap_err();
21455 let via_standalone = c.supervisor_view().unwrap().validate().unwrap_err();
21456 assert_eq!(
21457 via_method, via_standalone,
21458 "Caixa::validate_supervisor_shape must surface the typed-\
21459 shape arm's diagnostic byte-equal to the standalone \
21460 `SupervisorSpec::validate` on the same folded view",
21461 );
21462 assert!(
21463 matches!(
21464 via_method,
21465 crate::SupervisorError::DuplicateChildCaixa { ref caixa }
21466 if caixa == "worker"
21467 ),
21468 "expected DuplicateChildCaixa on the duplicate 'worker' \
21469 child, got {via_method:?}",
21470 );
21471 }
21472
21473 #[test]
21474 fn validate_supervisor_shape_folds_self_supervision_arm_matches_gate() {
21475 // Per-arm equivalence pin on the cross-slot self-edge axis: a
21476 // fixture whose `:children :caixa` names the Supervisor's own
21477 // `:nome` (which
21478 // [`crate::supervisor::validate_no_self_supervision`] rejects
21479 // as [`crate::SupervisorError::ChildSupervisesSelf`], a
21480 // one-node reconciliation cycle in the supervisor's
21481 // supervision-tree) surfaces the same
21482 // [`crate::SupervisorError::ChildSupervisesSelf`] through both
21483 // the compound gate and the standalone
21484 // [`crate::supervisor::validate_no_self_supervision`] keyed
21485 // off the same `(children, nome)` pair. Pins the fold's
21486 // second arm — reaching this arm through the compound gate
21487 // requires the typed-shape cascade to pass first, which itself
21488 // pins one cross-arm ordering step. Sibling in shape to the
21489 // peer
21490 // `validate_aplicacao_shape_folds_self_membership_arm_matches_gate`
21491 // (949a7a0) cross-slot equivalence pin on the sibling
21492 // per-Aplicacao compound gate.
21493 use crate::supervisor::{ChildSpec, RestartPolicy};
21494 let mut c = supervisor_fixture("demo");
21495 c.children = vec![ChildSpec {
21496 caixa: "demo".into(),
21497 versao: "^0.1".into(),
21498 restart: RestartPolicy::Permanent,
21499 }];
21500 let via_method = c.validate_supervisor_shape().unwrap_err();
21501 let via_standalone =
21502 crate::supervisor::validate_no_self_supervision(c.children(), c.nome()).unwrap_err();
21503 assert_eq!(
21504 via_method, via_standalone,
21505 "Caixa::validate_supervisor_shape must surface the cross-\
21506 slot self-edge diagnostic byte-equal to the standalone \
21507 `supervisor::validate_no_self_supervision` on the same \
21508 (children, nome) pair",
21509 );
21510 assert!(
21511 matches!(
21512 via_method,
21513 crate::SupervisorError::ChildSupervisesSelf { ref caixa } if caixa == "demo"
21514 ),
21515 "expected ChildSupervisesSelf carrying (caixa=\"demo\"), \
21516 got {via_method:?}",
21517 );
21518 }
21519
21520 #[test]
21521 fn validate_supervisor_shape_view_arm_fires_before_self_supervision_arm() {
21522 // Cross-arm ordering pin between the two arms of the fold: a
21523 // fixture carrying BOTH a typed-shape violation (a per-child
21524 // empty `:caixa` name — rejected by
21525 // [`crate::SupervisorSpec::validate`] as
21526 // [`crate::SupervisorError::EmptyChildName`]) AND a would-be
21527 // self-edge violation (a `:children` entry naming the
21528 // supervisor's own `:nome`) surfaces the typed-shape
21529 // diagnostic first through the compound gate. Sanity
21530 // assertion: the self-referential `:children` entry alone
21531 // under the same parent `:nome` trips the self-edge arm on
21532 // its own via the standalone
21533 // [`crate::supervisor::validate_no_self_supervision`], so the
21534 // typed-shape-first surfacing is a real ordering property, not
21535 // a case where the self-edge arm silently accepts the fixture.
21536 // Pins the pre-fold layout wire-up's canonical dispatch order
21537 // (typed-shape cascade → cross-slot self-edge) as a property
21538 // of the substrate primitive rather than a convention of the
21539 // layout call site. Sibling in shape to
21540 // `validate_aplicacao_shape_view_arm_fires_before_self_membership_arm`
21541 // (949a7a0) on the sibling per-Aplicacao compound gate.
21542 use crate::supervisor::{ChildSpec, RestartPolicy};
21543 let mut c = supervisor_fixture("demo");
21544 c.children = vec![
21545 ChildSpec {
21546 caixa: String::new(),
21547 versao: "^0.1".into(),
21548 restart: RestartPolicy::Permanent,
21549 },
21550 ChildSpec {
21551 caixa: "demo".into(),
21552 versao: "^0.1".into(),
21553 restart: RestartPolicy::Permanent,
21554 },
21555 ];
21556 let err = c.validate_supervisor_shape().unwrap_err();
21557 assert!(
21558 matches!(err, crate::SupervisorError::EmptyChildName),
21559 "typed-shape arm must fire before self-edge arm — expected \
21560 EmptyChildName on the empty :caixa child, got {err:?}",
21561 );
21562 // Sanity: the self-referential `:children` entry alone under
21563 // the same parent `:nome` trips the self-edge arm on its own
21564 // — proves the typed-shape-first surfacing above is a real
21565 // ordering property, not a case where the self-edge arm
21566 // silently accepts the fixture.
21567 let sanity = crate::supervisor::validate_no_self_supervision(
21568 &[ChildSpec {
21569 caixa: "demo".into(),
21570 versao: "^0.1".into(),
21571 restart: RestartPolicy::Permanent,
21572 }],
21573 "demo",
21574 )
21575 .unwrap_err();
21576 assert!(
21577 matches!(
21578 sanity,
21579 crate::SupervisorError::ChildSupervisesSelf { ref caixa } if caixa == "demo"
21580 ),
21581 "sanity: the self-referential :children entry alone must \
21582 trip the self-edge arm — got {sanity:?}",
21583 );
21584 }
21585
21586 #[test]
21587 fn validate_supervisor_shape_accepts_non_supervisor_kind() {
21588 // Positive control on the identity-element arm: every non-
21589 // Supervisor kind passes the compound gate trivially — the
21590 // paired [`Caixa::supervisor_view`] accessor returns `None`
21591 // off the Supervisor arm (by construction, keyed on
21592 // `caixa.kind().is_supervisor()`), so the fold short-circuits
21593 // to `Ok(())` without touching the supervision-tree slots.
21594 // Pins the identity element on every non-Supervisor kind — a
21595 // future refactor that made the supervision-tree cascade fire
21596 // on the wrong kind (say, on a `Servico` whose supervision
21597 // slots happen to be populated in a mis-authored manifest,
21598 // which the peer
21599 // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
21600 // kind-coherence gate would refuse upstream anyway) surfaces
21601 // here as a test failure first. Peer with the
21602 // `validate_aplicacao_shape_accepts_non_aplicacao_kind`
21603 // (949a7a0) / `validate_limits_accepts_none` /
21604 // `validate_behavior_accepts_none` identity-element pins on
21605 // the sibling per-Caixa compound gates.
21606 for kind in [
21607 CaixaKind::Biblioteca,
21608 CaixaKind::Binario,
21609 CaixaKind::Servico,
21610 CaixaKind::Aplicacao,
21611 CaixaKind::Acao,
21612 ] {
21613 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21614 c.kind = kind;
21615 assert!(
21616 c.supervisor_view().is_none(),
21617 "supervisor_view must return None off the Supervisor \
21618 arm for kind {kind:?}",
21619 );
21620 c.validate_supervisor_shape().expect(
21621 "non-Supervisor kinds must pass the compound gate as the fold's identity element",
21622 );
21623 }
21624 }
21625
21626 #[test]
21627 fn validate_supervisor_shape_accepts_clean_fixture() {
21628 // Positive control: a well-formed Supervisor (single
21629 // DNS-1123-valid `Permanent` worker child under the
21630 // `OneForOne` strategy — the OTP MaxIntensity/Period defaults
21631 // accept the vacuous `:max-restarts` / `:restart-window`
21632 // arms) passes the compound gate cleanly. A future tightening
21633 // of either arm's accepted set surfaces here as a test
21634 // failure first. Mirrors the peer
21635 // `validate_aplicacao_shape_accepts_clean_fixture` (949a7a0)
21636 // positive-control posture on the sibling per-Caixa compound
21637 // gate.
21638 let c = supervisor_fixture("demo");
21639 c.validate_supervisor_shape()
21640 .expect("clean Supervisor fixture must pass the compound gate");
21641 }
21642
21643 // ── Caixa::validate_acao_shape — compound per-Caixa gate ─────────────
21644
21645 /// Build a minimal well-formed `:kind Acao` fixture with a valid
21646 /// two-node acyclic `:ci` slot. Every arm of the compound gate
21647 /// then patches exactly one axis away from clean so its per-arm
21648 /// diagnostic surfaces without collateral noise from a peer slot.
21649 /// Peer of [`supervisor_fixture`] / [`aplicacao_fixture`] on the
21650 /// sibling per-kind compound gates' pin families.
21651 fn acao_fixture(nome: &str) -> Caixa {
21652 let mut c = Caixa::from_lisp(&Caixa::template(nome)).unwrap();
21653 c.kind = CaixaKind::Acao;
21654 // Acaos don't run code — clear the biblioteca slot the template
21655 // seeds so the compound gate's per-arm diagnostics surface
21656 // without the peer `AcaoOwnsCode` kind-coherence gate firing
21657 // upstream at the layout altitude.
21658 c.bibliotecas = vec![];
21659 c.ci = Some(canteiro_types::CiRun {
21660 workspace: "pleme-io".into(),
21661 repo: "caixa".into(),
21662 nodes: vec![
21663 canteiro_types::CiNode::new(
21664 "build",
21665 canteiro_types::EnvClass::None,
21666 canteiro_types::ActionRef {
21667 name: "build".into(),
21668 command: "true".into(),
21669 args: vec![],
21670 },
21671 vec![],
21672 ),
21673 canteiro_types::CiNode::new(
21674 "test",
21675 canteiro_types::EnvClass::None,
21676 canteiro_types::ActionRef {
21677 name: "test".into(),
21678 command: "true".into(),
21679 args: vec![],
21680 },
21681 vec!["build".into()],
21682 ),
21683 ],
21684 });
21685 c
21686 }
21687
21688 #[test]
21689 fn validate_acao_shape_folds_decompose_arm_matches_gate() {
21690 // Fail-before-pass-after per-arm equivalence pin on the
21691 // decompose axis: a fixture whose `:ci` slot fails
21692 // [`canteiro_types::decompose`] (here — a minimal two-node
21693 // cycle `a → b → a`, which the sibling
21694 // [`crate::render::decompose_ci`] wraps as
21695 // [`crate::CiDecomposeFailure`] carrying
21696 // [`canteiro_types::DecomposeError::Cycle`]) surfaces the same
21697 // [`crate::CiDecomposeFailure`] diagnostic through both the
21698 // compound gate [`Caixa::validate_acao_shape`] and the
21699 // standalone [`crate::render::decompose_ci`] on the same
21700 // `(caixa, ci)` fixture. Pins the fold — a silent regression
21701 // that de-folded the decompose arm would surface here as a
21702 // mismatch between the two dispatches. Sibling in shape to the
21703 // peer `validate_supervisor_shape_folds_view_arm_matches_gate`
21704 // / `validate_aplicacao_shape_folds_view_arm_matches_gate` on
21705 // the sibling per-kind compound gates.
21706 //
21707 // [`crate::CiDecomposeFailure`] does not derive `PartialEq`
21708 // (its `#[source]` carrier [`canteiro_types::DecomposeError`]
21709 // does, but the wrapper deliberately does not), so the two
21710 // dispatches are compared through their field pair
21711 // (`nome` + `source`) rather than through `assert_eq!` on the
21712 // wrapper itself — every field on the wrapper is thereby
21713 // pinned byte-equal without depending on an implementation
21714 // detail of `CiDecomposeFailure`'s derive set.
21715 let mut c = acao_fixture("demo");
21716 c.ci = Some(canteiro_types::CiRun {
21717 workspace: "pleme-io".into(),
21718 repo: "caixa".into(),
21719 nodes: vec![
21720 canteiro_types::CiNode::new(
21721 "a",
21722 canteiro_types::EnvClass::None,
21723 canteiro_types::ActionRef {
21724 name: "a".into(),
21725 command: "true".into(),
21726 args: vec![],
21727 },
21728 vec!["b".into()],
21729 ),
21730 canteiro_types::CiNode::new(
21731 "b",
21732 canteiro_types::EnvClass::None,
21733 canteiro_types::ActionRef {
21734 name: "b".into(),
21735 command: "true".into(),
21736 args: vec![],
21737 },
21738 vec!["a".into()],
21739 ),
21740 ],
21741 });
21742 let via_method = c.validate_acao_shape().unwrap_err();
21743 let via_standalone =
21744 crate::render::decompose_ci(&c, c.ci().expect("fixture has a :ci")).unwrap_err();
21745 assert_eq!(
21746 via_method.nome, via_standalone.nome,
21747 "Caixa::validate_acao_shape must surface the decompose \
21748 failure's `nome` byte-equal to the standalone \
21749 `decompose_ci` on the same (caixa, ci) fixture",
21750 );
21751 assert_eq!(
21752 via_method.source, via_standalone.source,
21753 "Caixa::validate_acao_shape must surface the decompose \
21754 failure's `source` byte-equal to the standalone \
21755 `decompose_ci` on the same (caixa, ci) fixture",
21756 );
21757 assert_eq!(
21758 via_method.source,
21759 canteiro_types::DecomposeError::Cycle,
21760 "expected the two-node cycle `a → b → a` to surface as \
21761 DecomposeError::Cycle, got {source:?}",
21762 source = via_method.source,
21763 );
21764 }
21765
21766 #[test]
21767 fn validate_acao_shape_folds_duplicate_node_arm_matches_gate() {
21768 // Per-arm equivalence pin on the `DuplicateNode` decompose
21769 // arm — the sibling of `Cycle` on the substrate's
21770 // `canteiro_types::DecomposeError` enumeration. A fixture
21771 // whose `:ci` slot carries two nodes sharing one name
21772 // surfaces the same [`crate::CiDecomposeFailure`] through
21773 // both dispatches, pinned by field pair. The three
21774 // decompose arms (`DuplicateNode` / `UnknownDep` / `Cycle`)
21775 // together enumerate every failure mode
21776 // [`canteiro_types::decompose`] refuses, so the per-arm
21777 // pins collectively cover the whole decompose axis.
21778 let mut c = acao_fixture("demo");
21779 c.ci = Some(canteiro_types::CiRun {
21780 workspace: "pleme-io".into(),
21781 repo: "caixa".into(),
21782 nodes: vec![
21783 canteiro_types::CiNode::new(
21784 "twin",
21785 canteiro_types::EnvClass::None,
21786 canteiro_types::ActionRef {
21787 name: "twin".into(),
21788 command: "true".into(),
21789 args: vec![],
21790 },
21791 vec![],
21792 ),
21793 canteiro_types::CiNode::new(
21794 "twin",
21795 canteiro_types::EnvClass::None,
21796 canteiro_types::ActionRef {
21797 name: "twin".into(),
21798 command: "true".into(),
21799 args: vec![],
21800 },
21801 vec![],
21802 ),
21803 ],
21804 });
21805 let via_method = c.validate_acao_shape().unwrap_err();
21806 assert_eq!(
21807 via_method.source,
21808 canteiro_types::DecomposeError::DuplicateNode("twin".into()),
21809 "expected DuplicateNode on the two-\"twin\"-name fixture, \
21810 got {source:?}",
21811 source = via_method.source,
21812 );
21813 }
21814
21815 #[test]
21816 fn validate_acao_shape_folds_unknown_dep_arm_matches_gate() {
21817 // Per-arm equivalence pin on the `UnknownDep` decompose arm —
21818 // the third and last arm on `canteiro_types::DecomposeError`
21819 // after `Cycle` and `DuplicateNode`. A fixture whose `:ci`
21820 // slot names a `deps` entry no declared node satisfies
21821 // surfaces the same [`crate::CiDecomposeFailure`] through
21822 // both dispatches. Pins the third decompose arm at the
21823 // compound gate.
21824 let mut c = acao_fixture("demo");
21825 c.ci = Some(canteiro_types::CiRun {
21826 workspace: "pleme-io".into(),
21827 repo: "caixa".into(),
21828 nodes: vec![canteiro_types::CiNode::new(
21829 "orphan",
21830 canteiro_types::EnvClass::None,
21831 canteiro_types::ActionRef {
21832 name: "orphan".into(),
21833 command: "true".into(),
21834 args: vec![],
21835 },
21836 vec!["ghost".into()],
21837 )],
21838 });
21839 let via_method = c.validate_acao_shape().unwrap_err();
21840 assert_eq!(
21841 via_method.source,
21842 canteiro_types::DecomposeError::UnknownDep {
21843 node: "orphan".into(),
21844 dep: "ghost".into(),
21845 },
21846 "expected UnknownDep on the orphan-node-depends-on-ghost \
21847 fixture, got {source:?}",
21848 source = via_method.source,
21849 );
21850 }
21851
21852 #[test]
21853 fn validate_acao_shape_accepts_non_acao_kind() {
21854 // Positive control on the identity-element arm: every non-
21855 // Acao kind passes the compound gate trivially — the paired
21856 // `caixa.kind().is_acao()` guard short-circuits before the
21857 // decompose gate ever fires, so the fold returns `Ok(())`
21858 // without touching the `:ci` slot even when a non-Acao
21859 // fixture happens to declare one (the sibling
21860 // [`crate::LayoutError::CiOnNonAcao`] kind-coherence gate
21861 // catches that at the layout altitude anyway). Pins the
21862 // identity element on every non-Acao kind. Peer with the
21863 // `validate_supervisor_shape_accepts_non_supervisor_kind` /
21864 // `validate_aplicacao_shape_accepts_non_aplicacao_kind`
21865 // identity-element pins on the sibling per-Caixa compound
21866 // gates.
21867 for kind in [
21868 CaixaKind::Biblioteca,
21869 CaixaKind::Binario,
21870 CaixaKind::Servico,
21871 CaixaKind::Supervisor,
21872 CaixaKind::Aplicacao,
21873 ] {
21874 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21875 c.kind = kind;
21876 c.validate_acao_shape().expect(
21877 "non-Acao kinds must pass the compound gate as the fold's identity element",
21878 );
21879 }
21880 }
21881
21882 #[test]
21883 fn validate_acao_shape_accepts_absent_ci_slot() {
21884 // Positive control on the second identity-element arm: a
21885 // `:kind Acao` caixa with `ci = None` passes the compound
21886 // gate trivially — the presence gate is the sibling axis
21887 // owned by [`crate::LayoutError::MissingCi`] /
21888 // [`crate::require_ci`] / [`crate::MissingCiSlot`], not by
21889 // the decompose gate. A caixa that carries no `:ci` slot
21890 // has no run to decompose, so the fold's `let Some(ci) = …
21891 // else { return Ok(()) }` arm short-circuits before the
21892 // decompose gate fires. Pins that the two axes stay
21893 // separately diagnosable at the layout altitude — a future
21894 // regression that collapsed the presence gate onto the
21895 // shape gate here would land a
21896 // [`crate::CiDecomposeFailure`] on the wrong axis and
21897 // surface an off-target diagnostic at `feira build` time.
21898 let mut c = acao_fixture("demo");
21899 c.ci = None;
21900 c.validate_acao_shape().expect(
21901 "an :kind Acao caixa with absent :ci must pass the compound gate — \
21902 the presence gate is layout's MissingCi axis, not the decompose gate",
21903 );
21904 }
21905
21906 #[test]
21907 fn validate_acao_shape_accepts_clean_fixture() {
21908 // Positive control: a well-formed Acao (a two-node acyclic
21909 // `:ci` run with `test` depending on `build`) passes the
21910 // compound gate cleanly. A future tightening of the
21911 // decompose gate's accepted set surfaces here as a test
21912 // failure first. Mirrors the peer
21913 // `validate_supervisor_shape_accepts_clean_fixture` /
21914 // `validate_aplicacao_shape_accepts_clean_fixture`
21915 // positive-control posture on the sibling per-Caixa
21916 // compound gates.
21917 let c = acao_fixture("demo");
21918 c.validate_acao_shape()
21919 .expect("clean Acao fixture must pass the compound gate");
21920 }
21921
21922 fn bare_servico_fixture(nome: &str) -> Caixa {
21923 // A minimal Servico caixa with no code and no typed slots —
21924 // the cross-family fold's identity element on every arm.
21925 // Clears the biblioteca slot the template seeds so the
21926 // per-arm patches below can each add exactly one typed slot
21927 // without a peer `ServicoOwnsCode` / layout-side kind-gate
21928 // firing upstream.
21929 let mut c = Caixa::from_lisp(&Caixa::template(nome)).unwrap();
21930 c.kind = CaixaKind::Servico;
21931 c.bibliotecas = vec![];
21932 c.servicos = vec!["servicos/demo.computeunit.yaml".into()];
21933 c
21934 }
21935
21936 #[test]
21937 fn validate_kind_slot_coherence_folds_mesh_arm_matches_gate() {
21938 // Fail-before-pass-after per-arm equivalence pin on the M3
21939 // mesh-slot arm of the cross-family kind-coherence fold: a
21940 // non-Aplicacao caixa carrying a declared M3 mesh slot (here
21941 // a `:kind Servico` fixture with a single `:membros` entry —
21942 // the smallest possible M3 slot declaration on a foreign
21943 // kind) surfaces the same
21944 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] variant
21945 // through both the compound gate
21946 // [`Caixa::validate_kind_slot_coherence`] and the standalone
21947 // constructor [`crate::LayoutError::mesh_slots_on_non_aplicacao`]
21948 // dispatched on the same `declared_mesh_slots` list. Pins
21949 // the fold — a silent regression that de-folded the mesh
21950 // arm would surface here as a mismatch between the two
21951 // dispatches. Sibling in shape to the peer
21952 // `validate_aplicacao_shape_folds_view_arm_matches_gate` /
21953 // `validate_supervisor_shape_folds_view_arm_matches_gate` /
21954 // `validate_acao_shape_folds_decompose_arm_matches_gate`
21955 // per-arm equivalence pins on the sibling per-kind compound
21956 // gates.
21957 use crate::aplicacao::Membro;
21958 let mut c = bare_servico_fixture("demo");
21959 c.membros = vec![Membro {
21960 caixa: "cart".into(),
21961 versao: "^0.1".into(),
21962 }];
21963 let via_method = c.validate_kind_slot_coherence().unwrap_err();
21964 let via_standalone =
21965 crate::LayoutError::mesh_slots_on_non_aplicacao(&c, c.declared_mesh_slots());
21966 assert_eq!(
21967 via_method, via_standalone,
21968 "Caixa::validate_kind_slot_coherence must surface the M3 \
21969 mesh-slot arm's diagnostic byte-equal to the standalone \
21970 LayoutError::mesh_slots_on_non_aplicacao ctor on the same \
21971 declared_mesh_slots list",
21972 );
21973 }
21974
21975 #[test]
21976 fn validate_kind_slot_coherence_folds_supervisor_arm_matches_gate() {
21977 // Per-arm equivalence pin on the supervisor-tree arm — the
21978 // sibling of the mesh arm on the cross-family fold. A
21979 // non-Supervisor caixa carrying a declared supervisor slot
21980 // (a `:kind Servico` fixture with `:estrategia` set — the
21981 // smallest possible supervisor slot declaration on a
21982 // foreign kind) surfaces the same
21983 // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
21984 // variant through both dispatches, pinned by field pair
21985 // through `PartialEq`.
21986 use crate::supervisor::RestartStrategy;
21987 let mut c = bare_servico_fixture("demo");
21988 c.estrategia = Some(RestartStrategy::OneForOne);
21989 let via_method = c.validate_kind_slot_coherence().unwrap_err();
21990 let via_standalone = crate::LayoutError::supervisor_slots_on_non_supervisor(
21991 &c,
21992 c.declared_supervisor_slots(),
21993 );
21994 assert_eq!(
21995 via_method, via_standalone,
21996 "Caixa::validate_kind_slot_coherence must surface the \
21997 supervisor-tree arm's diagnostic byte-equal to the \
21998 standalone LayoutError::supervisor_slots_on_non_supervisor \
21999 ctor on the same declared_supervisor_slots list",
22000 );
22001 }
22002
22003 #[test]
22004 fn validate_kind_slot_coherence_folds_servico_arm_matches_gate() {
22005 // Per-arm equivalence pin on the M2 Servico-runtime arm —
22006 // the third and last arm on the cross-family fold. A
22007 // non-Servico caixa carrying a declared M2 slot (a `:kind
22008 // Biblioteca` fixture with `:limits` set — the smallest
22009 // possible M2 slot declaration on a foreign kind) surfaces
22010 // the same [`crate::LayoutError::ServicoSlotsOnNonServico`]
22011 // variant through both dispatches. The three arms together
22012 // enumerate every typed-slot family the substrate carries
22013 // whose "declared but ignored" footgun is gated at the
22014 // layout altitude by a `{ caixa, kind, slots }` wrap variant,
22015 // so the per-arm pins collectively cover the whole
22016 // cross-family kind-coherence axis.
22017 use crate::limits::LimitsSpec;
22018 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22019 c.kind = CaixaKind::Biblioteca;
22020 c.limits = Some(LimitsSpec {
22021 memory: Some(64 * 1024 * 1024),
22022 fuel: None,
22023 wall_clock: None,
22024 cpu: None,
22025 });
22026 let via_method = c.validate_kind_slot_coherence().unwrap_err();
22027 let via_standalone =
22028 crate::LayoutError::servico_slots_on_non_servico(&c, c.declared_servico_slots());
22029 assert_eq!(
22030 via_method, via_standalone,
22031 "Caixa::validate_kind_slot_coherence must surface the M2 \
22032 Servico-runtime arm's diagnostic byte-equal to the \
22033 standalone LayoutError::servico_slots_on_non_servico ctor \
22034 on the same declared_servico_slots list",
22035 );
22036 }
22037
22038 #[test]
22039 fn validate_kind_slot_coherence_mesh_arm_fires_before_supervisor_arm() {
22040 // Cross-arm ordering pin between the first two arms of the
22041 // fold: a fixture carrying BOTH a declared M3 mesh slot
22042 // (`:membros`) AND a declared supervisor-tree slot
22043 // (`:estrategia`) on a foreign kind (a `:kind Servico` here —
22044 // foreign to both the Aplicacao arm and the Supervisor arm)
22045 // surfaces the M3 mesh diagnostic first through the compound
22046 // gate. Pins the pre-fold layout wire-up's canonical
22047 // diagnostic sequence (mesh → supervisor → servico) as a
22048 // property of the substrate primitive rather than a
22049 // convention of the layout call site. A silent reordering
22050 // regression at the primitive would surface here as a
22051 // wrong-variant match before landing at a downstream
22052 // consumer's diagnostic-ordering expectation.
22053 use crate::aplicacao::Membro;
22054 use crate::supervisor::RestartStrategy;
22055 let mut c = bare_servico_fixture("demo");
22056 c.membros = vec![Membro {
22057 caixa: "cart".into(),
22058 versao: "^0.1".into(),
22059 }];
22060 c.estrategia = Some(RestartStrategy::OneForOne);
22061 let err = c.validate_kind_slot_coherence().unwrap_err();
22062 assert!(
22063 matches!(err, crate::LayoutError::MeshSlotsOnNonAplicacao { .. }),
22064 "expected MeshSlotsOnNonAplicacao to fire before \
22065 SupervisorSlotsOnNonSupervisor under the canonical \
22066 mesh → supervisor → servico order, got {err:?}",
22067 );
22068 }
22069
22070 #[test]
22071 fn validate_kind_slot_coherence_supervisor_arm_fires_before_servico_arm() {
22072 // Cross-arm ordering pin between the second and third arms
22073 // of the fold: a fixture carrying BOTH a declared
22074 // supervisor-tree slot (`:estrategia`) AND a declared M2 slot
22075 // (`:limits`) on a kind foreign to both (a `:kind Biblioteca`
22076 // here — foreign to both the Supervisor and the Servico
22077 // arms) surfaces the supervisor-tree diagnostic first
22078 // through the compound gate. Together with the peer
22079 // `_mesh_arm_fires_before_supervisor_arm` pin above this
22080 // pins the whole three-arm canonical order (mesh →
22081 // supervisor → servico) at the substrate primitive.
22082 use crate::limits::LimitsSpec;
22083 use crate::supervisor::RestartStrategy;
22084 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22085 c.kind = CaixaKind::Biblioteca;
22086 c.estrategia = Some(RestartStrategy::OneForOne);
22087 c.limits = Some(LimitsSpec {
22088 memory: Some(64 * 1024 * 1024),
22089 fuel: None,
22090 wall_clock: None,
22091 cpu: None,
22092 });
22093 let err = c.validate_kind_slot_coherence().unwrap_err();
22094 assert!(
22095 matches!(
22096 err,
22097 crate::LayoutError::SupervisorSlotsOnNonSupervisor { .. }
22098 ),
22099 "expected SupervisorSlotsOnNonSupervisor to fire before \
22100 ServicoSlotsOnNonServico under the canonical mesh → \
22101 supervisor → servico order, got {err:?}",
22102 );
22103 }
22104
22105 #[test]
22106 fn validate_kind_slot_coherence_accepts_owner_kind_on_every_arm() {
22107 // Positive control on the identity-element arm: the owner
22108 // kind of each typed-slot family passes the compound gate
22109 // even when it declares the full slot set that family owns.
22110 // Aplicacao with `:membros` populated passes the mesh arm;
22111 // Supervisor with `:estrategia` populated passes the
22112 // supervisor arm; Servico with `:limits` populated passes
22113 // the servico arm. Pins the fold's identity element on
22114 // every owner kind — a silent regression that dropped the
22115 // paired `!kind().is_<owner>()` short-circuit guard would
22116 // surface here as a false-positive rejection of every
22117 // native-slot declaration. Peer with the
22118 // `validate_<kind>_shape_accepts_non_<kind>_kind` identity-
22119 // element pins on the sibling per-Caixa compound gates.
22120 use crate::aplicacao::{Membro, Placement, PlacementStrategy};
22121 use crate::limits::LimitsSpec;
22122 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
22123
22124 let mut apli = Caixa::from_lisp(&Caixa::template("app")).unwrap();
22125 apli.kind = CaixaKind::Aplicacao;
22126 apli.bibliotecas = vec![];
22127 apli.membros = vec![Membro {
22128 caixa: "cart".into(),
22129 versao: "^0.1".into(),
22130 }];
22131 apli.placement = Some(Placement {
22132 estrategia: PlacementStrategy::SingleNode,
22133 clusters: vec!["rio".into()],
22134 shard_key: None,
22135 affinity: None,
22136 });
22137 apli.validate_kind_slot_coherence().expect(
22138 "an :kind Aplicacao caixa with declared M3 mesh slots must \
22139 pass the compound gate — Aplicacao is the mesh-slot family's \
22140 owner kind and the fold's identity element on that arm",
22141 );
22142
22143 let mut sup = Caixa::from_lisp(&Caixa::template("sup")).unwrap();
22144 sup.kind = CaixaKind::Supervisor;
22145 sup.bibliotecas = vec![];
22146 sup.estrategia = Some(RestartStrategy::OneForOne);
22147 sup.children = vec![ChildSpec {
22148 caixa: "worker".into(),
22149 versao: "^0.1".into(),
22150 restart: RestartPolicy::Permanent,
22151 }];
22152 sup.validate_kind_slot_coherence().expect(
22153 "an :kind Supervisor caixa with declared supervisor-tree slots \
22154 must pass the compound gate — Supervisor is the \
22155 supervisor-slot family's owner kind and the fold's identity \
22156 element on that arm",
22157 );
22158
22159 let mut svc = bare_servico_fixture("svc");
22160 svc.limits = Some(LimitsSpec {
22161 memory: Some(64 * 1024 * 1024),
22162 fuel: None,
22163 wall_clock: None,
22164 cpu: None,
22165 });
22166 svc.validate_kind_slot_coherence().expect(
22167 "an :kind Servico caixa with declared M2 slots must pass the \
22168 compound gate — Servico is the M2-slot family's owner kind \
22169 and the fold's identity element on that arm",
22170 );
22171 }
22172
22173 #[test]
22174 fn validate_kind_slot_coherence_accepts_bare_caixa_on_every_kind() {
22175 // Positive control on the second identity-element arm: a
22176 // bare caixa (no declared typed slots) passes the compound
22177 // gate on every kind. Pins the fold's identity element on
22178 // the empty-slot axis — the paired `Vec::is_empty` short-
22179 // circuit guard fires before the wrap dispatch on all three
22180 // arms, so a bare caixa of any kind surfaces no diagnostic.
22181 // A silent regression that dropped the emptiness guard
22182 // would surface here as a false-positive rejection of every
22183 // no-slot caixa across the whole kind axis.
22184 for kind in CaixaKind::ALL {
22185 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22186 c.kind = *kind;
22187 c.bibliotecas = vec![];
22188 c.validate_kind_slot_coherence().unwrap_or_else(|err| {
22189 panic!(
22190 "a bare :kind {kind:?} caixa (no declared typed slots) \
22191 must pass the compound gate — the fold's identity \
22192 element on the empty-slot axis is the paired \
22193 Vec::is_empty short-circuit guard, got {err:?}",
22194 )
22195 });
22196 }
22197 }
22198
22199 #[test]
22200 fn run_kind_owned_slot_family_gate_owner_kind_short_circuits_before_accumulator() {
22201 // Fail-before-pass-after identity-element pin on the owner-kind
22202 // arm of the substrate primitive: on a caixa whose kind IS the
22203 // owner of the family named by `is_owner`, the primitive
22204 // short-circuits before dispatching `accumulator` — pinned here
22205 // by a poison-pill accumulator that panics on call. If a
22206 // regression drops the `is_owner` short-circuit and always
22207 // invokes the accumulator, the poison panic surfaces here
22208 // rather than a spurious pass. Byte-equal to the pre-lift
22209 // `if !self.kind().is_<owner>() { … }` outer guard's
22210 // short-circuit at the pre-fold layout call site.
22211 let c = bare_servico_fixture("demo");
22212 c.run_kind_owned_slot_family_gate(
22213 CaixaKind::is_servico,
22214 |_| panic!("run_kind_owned_slot_family_gate must short-circuit before invoking accumulator on the owner kind"),
22215 |_, _| panic!("run_kind_owned_slot_family_gate must short-circuit before invoking wrap on the owner kind"),
22216 )
22217 .expect(
22218 "the owner kind of a slot family must pass the substrate \
22219 primitive as the fold's identity element on the outer \
22220 is_owner guard, without invoking accumulator or wrap",
22221 );
22222 }
22223
22224 #[test]
22225 fn run_kind_owned_slot_family_gate_empty_accumulator_short_circuits_before_wrap() {
22226 // Fail-before-pass-after identity-element pin on the empty-
22227 // accumulator arm: on a non-owner kind whose per-family
22228 // accumulator yields no declared slot, the primitive short-
22229 // circuits before dispatching `wrap` — pinned here by a
22230 // poison-pill wrap that panics on call. Byte-equal to the
22231 // pre-lift `if !<slots>.is_empty() { … }` inner emptiness
22232 // guard's short-circuit at the pre-fold layout call site.
22233 let c = bare_servico_fixture("demo");
22234 c.run_kind_owned_slot_family_gate(
22235 CaixaKind::is_aplicacao,
22236 Caixa::declared_mesh_slots,
22237 |_, _| panic!("run_kind_owned_slot_family_gate must short-circuit before invoking wrap on an empty accumulator"),
22238 )
22239 .expect(
22240 "a non-owner kind carrying no declared slot in the family \
22241 must pass the substrate primitive as the fold's identity \
22242 element on the inner emptiness guard, without invoking \
22243 wrap",
22244 );
22245 }
22246
22247 #[test]
22248 fn run_kind_owned_slot_family_gate_non_owner_non_empty_wraps_verbatim() {
22249 // Equivalence pin on the refusal arm: on a non-owner kind
22250 // whose accumulator yields a non-empty slot list, the primitive
22251 // returns the caller-supplied wrap byte-equal to the direct
22252 // ctor dispatch on the same `(caixa, slots)` pair. Pins the
22253 // three-argument route through — `is_owner` fires false, the
22254 // accumulator produces the slot list, and the wrap ctor
22255 // receives verbatim what a direct dispatch would receive.
22256 // Sibling of the peer per-arm equivalence pins on
22257 // [`Caixa::validate_kind_slot_coherence`].
22258 use crate::aplicacao::Membro;
22259 let mut c = bare_servico_fixture("demo");
22260 c.membros = vec![Membro {
22261 caixa: "cart".into(),
22262 versao: "^0.1".into(),
22263 }];
22264 let via_primitive = c
22265 .run_kind_owned_slot_family_gate(
22266 CaixaKind::is_aplicacao,
22267 Caixa::declared_mesh_slots,
22268 crate::LayoutError::mesh_slots_on_non_aplicacao,
22269 )
22270 .unwrap_err();
22271 let via_direct =
22272 crate::LayoutError::mesh_slots_on_non_aplicacao(&c, c.declared_mesh_slots());
22273 assert_eq!(
22274 via_primitive, via_direct,
22275 "Caixa::run_kind_owned_slot_family_gate must route the \
22276 non-owner-kind + non-empty-accumulator arm through the \
22277 caller-supplied wrap byte-equal to the direct ctor \
22278 dispatch on the same (caixa, slots) pair",
22279 );
22280 }
22281
22282 #[test]
22283 fn validate_kind_slot_coherence_routes_each_arm_through_run_kind_owned_slot_family_gate() {
22284 // Cross-primitive routing pin: every arm of the compound gate
22285 // [`Caixa::validate_kind_slot_coherence`] routes through the
22286 // substrate primitive [`Caixa::run_kind_owned_slot_family_gate`]
22287 // on its `(is_owner, accumulator, wrap)` triple. A silent
22288 // regression that de-folded one arm and re-inlined the four-
22289 // line block would surface here as a mismatch between the
22290 // compound-gate error and the direct-primitive-dispatch error
22291 // on the same fixture. Sibling of the peer
22292 // `probe_declared_entries_routes_miss_arm_through_probe_declared_entry`
22293 // cross-primitive routing pin on the layout-pipeline
22294 // existence-probe axis.
22295 use crate::aplicacao::Membro;
22296 use crate::limits::LimitsSpec;
22297 use crate::supervisor::RestartStrategy;
22298
22299 // Mesh arm — non-Aplicacao carrying a declared M3 slot.
22300 let mut mesh = bare_servico_fixture("demo");
22301 mesh.membros = vec![Membro {
22302 caixa: "cart".into(),
22303 versao: "^0.1".into(),
22304 }];
22305 let via_compound = mesh.validate_kind_slot_coherence().unwrap_err();
22306 let via_primitive = mesh
22307 .run_kind_owned_slot_family_gate(
22308 CaixaKind::is_aplicacao,
22309 Caixa::declared_mesh_slots,
22310 crate::LayoutError::mesh_slots_on_non_aplicacao,
22311 )
22312 .unwrap_err();
22313 assert_eq!(
22314 via_compound, via_primitive,
22315 "validate_kind_slot_coherence's mesh arm must route \
22316 byte-equal through the run_kind_owned_slot_family_gate \
22317 substrate primitive",
22318 );
22319
22320 // Supervisor arm — non-Supervisor carrying a declared
22321 // supervisor-tree slot on a kind foreign to both the Aplicacao
22322 // arm and this one.
22323 let mut sup = bare_servico_fixture("demo");
22324 sup.estrategia = Some(RestartStrategy::OneForOne);
22325 let via_compound = sup.validate_kind_slot_coherence().unwrap_err();
22326 let via_primitive = sup
22327 .run_kind_owned_slot_family_gate(
22328 CaixaKind::is_supervisor,
22329 Caixa::declared_supervisor_slots,
22330 crate::LayoutError::supervisor_slots_on_non_supervisor,
22331 )
22332 .unwrap_err();
22333 assert_eq!(
22334 via_compound, via_primitive,
22335 "validate_kind_slot_coherence's supervisor arm must route \
22336 byte-equal through the run_kind_owned_slot_family_gate \
22337 substrate primitive",
22338 );
22339
22340 // Servico arm — non-Servico carrying a declared M2 slot on a
22341 // kind foreign to every prior arm (Biblioteca — foreign to
22342 // both the Aplicacao mesh arm and the Supervisor supervisor
22343 // arm and the Servico M2 arm).
22344 let mut svc = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22345 svc.kind = CaixaKind::Biblioteca;
22346 svc.limits = Some(LimitsSpec {
22347 memory: Some(64 * 1024 * 1024),
22348 fuel: None,
22349 wall_clock: None,
22350 cpu: None,
22351 });
22352 let via_compound = svc.validate_kind_slot_coherence().unwrap_err();
22353 let via_primitive = svc
22354 .run_kind_owned_slot_family_gate(
22355 CaixaKind::is_servico,
22356 Caixa::declared_servico_slots,
22357 crate::LayoutError::servico_slots_on_non_servico,
22358 )
22359 .unwrap_err();
22360 assert_eq!(
22361 via_compound, via_primitive,
22362 "validate_kind_slot_coherence's servico arm must route \
22363 byte-equal through the run_kind_owned_slot_family_gate \
22364 substrate primitive",
22365 );
22366 }
22367
22368 #[test]
22369 fn validate_no_code_kind_coherence_folds_supervisor_arm_matches_gate() {
22370 // Fail-before-pass-after per-arm equivalence pin on the
22371 // Supervisor no-code arm of the reciprocal code-surface
22372 // fold: a `:kind Supervisor` caixa carrying a declared
22373 // `:bibliotecas` entry (the smallest possible code-surface
22374 // declaration on a no-code kind) surfaces the same
22375 // [`crate::LayoutError::SupervisorOwnsCode`] variant
22376 // through both the compound gate
22377 // [`Caixa::validate_no_code_kind_coherence`] and the
22378 // standalone constructor
22379 // [`crate::LayoutError::supervisor_owns_code`]. Pins the
22380 // fold — a silent regression that de-folded the Supervisor
22381 // arm would surface here as a mismatch between the two
22382 // dispatches. Sibling in shape to the peer
22383 // `validate_kind_slot_coherence_folds_supervisor_arm_matches_gate`
22384 // per-arm equivalence pin on the cross-family
22385 // typed-slot-coherence fold.
22386 let mut c = Caixa::from_lisp(&Caixa::template("sup")).unwrap();
22387 c.kind = CaixaKind::Supervisor;
22388 c.bibliotecas = vec!["lib/sup.lisp".into()];
22389 let via_method = c.validate_no_code_kind_coherence().unwrap_err();
22390 let via_standalone = crate::LayoutError::supervisor_owns_code(&c);
22391 assert_eq!(
22392 via_method, via_standalone,
22393 "Caixa::validate_no_code_kind_coherence must surface the \
22394 Supervisor arm's diagnostic byte-equal to the standalone \
22395 LayoutError::supervisor_owns_code ctor",
22396 );
22397 }
22398
22399 #[test]
22400 fn validate_no_code_kind_coherence_folds_aplicacao_arm_matches_gate() {
22401 // Per-arm equivalence pin on the Aplicacao no-code arm —
22402 // the sibling of the Supervisor arm on the code-surface
22403 // fold. A `:kind Aplicacao` caixa carrying a declared
22404 // `:exe` entry surfaces the same
22405 // [`crate::LayoutError::AplicacaoOwnsCode`] variant through
22406 // both dispatches. Uses the `:exe` code-surface axis (a
22407 // second axis distinct from the Supervisor arm's
22408 // `:bibliotecas` fixture) so the three per-arm pins
22409 // collectively exercise every arm of the `has_code`
22410 // disjunction (`:bibliotecas || :exe || :servicos`).
22411 let mut c = Caixa::from_lisp(&Caixa::template("app")).unwrap();
22412 c.kind = CaixaKind::Aplicacao;
22413 c.bibliotecas = vec![];
22414 c.exe = vec!["exe/app".into()];
22415 let via_method = c.validate_no_code_kind_coherence().unwrap_err();
22416 let via_standalone = crate::LayoutError::aplicacao_owns_code(&c);
22417 assert_eq!(
22418 via_method, via_standalone,
22419 "Caixa::validate_no_code_kind_coherence must surface the \
22420 Aplicacao arm's diagnostic byte-equal to the standalone \
22421 LayoutError::aplicacao_owns_code ctor",
22422 );
22423 }
22424
22425 #[test]
22426 fn validate_no_code_kind_coherence_folds_acao_arm_matches_gate() {
22427 // Per-arm equivalence pin on the Acao no-code arm — the
22428 // third and last arm on the code-surface fold. A `:kind
22429 // Acao` caixa carrying a declared `:servicos` entry
22430 // surfaces the same [`crate::LayoutError::AcaoOwnsCode`]
22431 // variant through both dispatches. Uses the `:servicos`
22432 // code-surface axis (the third distinct axis of the
22433 // `has_code` disjunction) so the three per-arm pins
22434 // collectively cover every arm of the code-surface
22435 // disjunction plus every no-code kind of the arm
22436 // dispatch.
22437 let mut c = Caixa::from_lisp(&Caixa::template("acao")).unwrap();
22438 c.kind = CaixaKind::Acao;
22439 c.bibliotecas = vec![];
22440 c.servicos = vec!["servicos/demo.computeunit.yaml".into()];
22441 let via_method = c.validate_no_code_kind_coherence().unwrap_err();
22442 let via_standalone = crate::LayoutError::acao_owns_code(&c);
22443 assert_eq!(
22444 via_method, via_standalone,
22445 "Caixa::validate_no_code_kind_coherence must surface the \
22446 Acao arm's diagnostic byte-equal to the standalone \
22447 LayoutError::acao_owns_code ctor",
22448 );
22449 }
22450
22451 #[test]
22452 fn validate_no_code_kind_coherence_accepts_owner_kind_on_every_code_axis() {
22453 // Positive control on the code-owning-kind identity
22454 // element: each of the three code-owning kinds
22455 // (`Biblioteca` owning `:bibliotecas`, `Binario` owning
22456 // `:exe`, `Servico` owning `:servicos`) passes the
22457 // compound gate cleanly when it declares its native code
22458 // surface. Pins the fold's second identity element — the
22459 // paired per-arm `is_<no-code-kind>()` short-circuit
22460 // fires on every code-owning kind, so a caixa with any
22461 // native code declaration on its owner kind surfaces no
22462 // diagnostic. A silent regression that dropped the paired
22463 // `is_<no-code-kind>()` short-circuit guard on any arm
22464 // would surface here as a false-positive rejection of the
22465 // corresponding owner kind. Peer with the
22466 // `validate_kind_slot_coherence_accepts_owner_kind_on_every_arm`
22467 // identity-element pin on the sibling cross-family fold.
22468 let mut bib = Caixa::from_lisp(&Caixa::template("bib")).unwrap();
22469 bib.kind = CaixaKind::Biblioteca;
22470 bib.bibliotecas = vec!["lib/bib.lisp".into()];
22471 bib.validate_no_code_kind_coherence().expect(
22472 "a :kind Biblioteca caixa with declared :bibliotecas must pass \
22473 the compound gate — Biblioteca owns the :bibliotecas code surface",
22474 );
22475
22476 let mut bin = Caixa::from_lisp(&Caixa::template("bin")).unwrap();
22477 bin.kind = CaixaKind::Binario;
22478 bin.bibliotecas = vec![];
22479 bin.exe = vec!["exe/bin".into()];
22480 bin.validate_no_code_kind_coherence().expect(
22481 "a :kind Binario caixa with declared :exe must pass the compound \
22482 gate — Binario owns the :exe code surface",
22483 );
22484
22485 let svc = bare_servico_fixture("svc");
22486 svc.validate_no_code_kind_coherence().expect(
22487 "a :kind Servico caixa with declared :servicos must pass the \
22488 compound gate — Servico owns the :servicos code surface",
22489 );
22490 }
22491
22492 #[test]
22493 fn validate_no_code_kind_coherence_accepts_bare_caixa_on_every_kind() {
22494 // Positive control on the has-no-code identity element:
22495 // a bare caixa (no declared code) passes the compound
22496 // gate on every kind — including the three no-code kinds
22497 // that would otherwise fire an OwnsCode diagnostic. Pins
22498 // the fold's first identity element — the paired
22499 // `!has_code` short-circuit fires before every per-arm
22500 // wrap dispatch, so a bare caixa of any kind surfaces no
22501 // diagnostic. A silent regression that dropped the
22502 // has_code guard would surface here as a false-positive
22503 // rejection of every no-code kind that declares no code.
22504 // Peer with the
22505 // `validate_kind_slot_coherence_accepts_bare_caixa_on_every_kind`
22506 // identity-element pin on the sibling cross-family fold.
22507 for kind in CaixaKind::ALL {
22508 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22509 c.kind = *kind;
22510 c.bibliotecas = vec![];
22511 c.exe = vec![];
22512 c.servicos = vec![];
22513 c.validate_no_code_kind_coherence().unwrap_or_else(|err| {
22514 panic!(
22515 "a bare :kind {kind:?} caixa (no declared code) must pass \
22516 the compound gate — the fold's first identity element is \
22517 the paired !has_code short-circuit, got {err:?}",
22518 )
22519 });
22520 }
22521 }
22522
22523 #[test]
22524 fn validate_ci_kind_coherence_folds_arm_matches_gate() {
22525 // Fail-before-pass-after per-arm equivalence pin on the
22526 // `:ci`-on-non-`Acao` arm: a `:kind Biblioteca` caixa
22527 // (the smallest non-`Acao` kind) carrying a declared
22528 // `:ci` slot surfaces the same
22529 // [`crate::LayoutError::CiOnNonAcao`] variant through the
22530 // compound gate [`Caixa::validate_ci_kind_coherence`] and
22531 // an inlined struct-literal wrap carrying `caixa.nome()`
22532 // + `caixa.kind()` verbatim. Pins the fold — a silent
22533 // regression that de-folded the arm would surface here as
22534 // a mismatch between the two dispatches. Sibling in shape
22535 // to the peer
22536 // `validate_no_code_kind_coherence_folds_supervisor_arm_matches_gate`
22537 // per-arm equivalence pin on the reciprocal
22538 // code-surface fold.
22539 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22540 c.kind = CaixaKind::Biblioteca;
22541 c.ci = Some(canteiro_types::CiRun {
22542 workspace: "pleme-io".into(),
22543 repo: "caixa".into(),
22544 nodes: vec![],
22545 });
22546 let via_method = c.validate_ci_kind_coherence().unwrap_err();
22547 let via_standalone = crate::LayoutError::CiOnNonAcao {
22548 caixa: c.nome().to_string(),
22549 kind: c.kind(),
22550 };
22551 assert_eq!(
22552 via_method, via_standalone,
22553 "Caixa::validate_ci_kind_coherence must surface the \
22554 :ci-on-non-Acao arm's diagnostic byte-equal to a \
22555 LayoutError::CiOnNonAcao struct literal carrying the \
22556 caixa's nome + kind",
22557 );
22558 }
22559
22560 #[test]
22561 fn validate_ci_kind_coherence_fold_names_offending_kind_on_every_non_acao_kind() {
22562 // Exhaustive per-kind sweep on the non-`Acao` arm: for each
22563 // of the five non-`Acao` kinds
22564 // (`Biblioteca` / `Binario` / `Servico` / `Supervisor` /
22565 // `Aplicacao`), a caixa carrying a declared `:ci` slot
22566 // surfaces the [`crate::LayoutError::CiOnNonAcao`]
22567 // variant naming the offending kind verbatim. A silent
22568 // regression that mistyped one arm's kind-projection
22569 // (e.g. always threading `CaixaKind::Biblioteca` regardless
22570 // of the caixa's actual kind) would surface here as a
22571 // mismatch on every kind past the first. Peer of the
22572 // `validate_no_code_kind_coherence_accepts_bare_caixa_on_every_kind`
22573 // exhaustive-sweep pin on the sibling code-surface fold.
22574 for kind in CaixaKind::ALL {
22575 if kind.is_acao() {
22576 continue;
22577 }
22578 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22579 c.kind = *kind;
22580 c.ci = Some(canteiro_types::CiRun {
22581 workspace: "pleme-io".into(),
22582 repo: "caixa".into(),
22583 nodes: vec![],
22584 });
22585 let err = c.validate_ci_kind_coherence().unwrap_err();
22586 match err {
22587 crate::LayoutError::CiOnNonAcao {
22588 caixa: got_caixa,
22589 kind: got_kind,
22590 } => {
22591 assert_eq!(
22592 got_caixa,
22593 c.nome(),
22594 "CiOnNonAcao must name the offending caixa's nome verbatim on kind {kind:?}",
22595 );
22596 assert_eq!(
22597 got_kind, *kind,
22598 "CiOnNonAcao must name the offending kind verbatim on kind {kind:?}",
22599 );
22600 }
22601 other => panic!(
22602 "expected CiOnNonAcao on :kind {kind:?} with declared :ci, got {other:?}",
22603 ),
22604 }
22605 }
22606 }
22607
22608 #[test]
22609 fn validate_ci_kind_coherence_accepts_acao_on_every_ci_shape() {
22610 // Positive control on the owner-kind identity element: an
22611 // `:kind Acao` caixa passes the coherence gate cleanly on
22612 // every `:ci` shape — the arm's paired
22613 // `!kind().is_acao()` short-circuit fires before the
22614 // dispatch, so the fold surfaces no diagnostic even on
22615 // fixtures whose `:ci` would fail the peer
22616 // [`Self::validate_acao_shape`] decompose gate (a
22617 // duplicate-node fixture, an unknown-dep fixture, a
22618 // cyclic fixture). Pins the fold's first identity element
22619 // — a silent regression that dropped the paired
22620 // `!kind().is_acao()` short-circuit guard would surface
22621 // here as a false-positive rejection of every `Acao`
22622 // caixa. Peer with the
22623 // `validate_no_code_kind_coherence_accepts_owner_kind_on_every_code_axis`
22624 // identity-element pin on the sibling code-surface fold.
22625 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22626 c.kind = CaixaKind::Acao;
22627 c.bibliotecas = vec![];
22628 c.ci = Some(canteiro_types::CiRun {
22629 workspace: "pleme-io".into(),
22630 repo: "caixa".into(),
22631 nodes: vec![],
22632 });
22633 c.validate_ci_kind_coherence().expect(
22634 "a :kind Acao caixa with declared :ci must pass the compound \
22635 coherence gate — Acao is the :ci-owning kind (a malformed \
22636 :ci on Acao surfaces via validate_acao_shape's decompose gate, \
22637 not via this kind-coherence gate)",
22638 );
22639 }
22640
22641 #[test]
22642 fn validate_ci_kind_coherence_accepts_absent_ci_on_every_kind() {
22643 // Positive control on the absent-`:ci` identity element:
22644 // a caixa with `ci = None` passes the coherence gate on
22645 // every kind — including `Acao`, whose absent `:ci`
22646 // fails a separate presence gate ([`crate::LayoutError::MissingCi`])
22647 // downstream at the layout altitude, not this coherence
22648 // gate. Pins the fold's second identity element — the
22649 // paired `ci().is_some()` short-circuit fires before every
22650 // per-arm dispatch, so a caixa with no declared `:ci`
22651 // surfaces no coherence diagnostic. A silent regression
22652 // that dropped the paired `ci().is_some()` short-circuit
22653 // would surface here as a false-positive rejection on
22654 // every non-`Acao` kind. Peer with the
22655 // `validate_no_code_kind_coherence_accepts_bare_caixa_on_every_kind`
22656 // identity-element pin on the sibling code-surface fold.
22657 for kind in CaixaKind::ALL {
22658 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22659 c.kind = *kind;
22660 c.ci = None;
22661 c.validate_ci_kind_coherence().unwrap_or_else(|err| {
22662 panic!(
22663 "a :kind {kind:?} caixa with no declared :ci must pass \
22664 the compound coherence gate — the fold's second identity \
22665 element is the paired ci().is_some() short-circuit, got \
22666 {err:?}",
22667 )
22668 });
22669 }
22670 }
22671
22672 #[test]
22673 fn validate_foreign_code_kind_coherence_folds_arm_matches_gate() {
22674 // Fail-before-pass-after equivalence pin on the compound
22675 // foreign-code-slot coherence fold: a `:kind Servico` caixa
22676 // carrying a declared `:exe` entry (the smallest possible
22677 // foreign-code-slot declaration on a code-running kind that
22678 // is not its owner — Servico owns `:servicos`, not `:exe`)
22679 // surfaces the same [`crate::LayoutError::ForeignCodeSlot`]
22680 // variant through both the compound gate
22681 // [`Caixa::validate_foreign_code_kind_coherence`] and the
22682 // standalone constructor
22683 // [`crate::LayoutError::foreign_code_slot`] dispatched on the
22684 // same `declared_foreign_code_slots` list. Pins the fold — a
22685 // silent regression that de-folded the arm would surface here
22686 // as a mismatch between the two dispatches. Sibling in shape
22687 // to the peer
22688 // `validate_kind_slot_coherence_folds_mesh_arm_matches_gate`
22689 // / `validate_ci_kind_coherence_folds_arm_matches_gate` /
22690 // `validate_no_code_kind_coherence_folds_supervisor_arm_matches_gate`
22691 // per-arm equivalence pins on the sibling kind-coherence folds.
22692 let mut c = bare_servico_fixture("demo");
22693 c.exe = vec!["exe/foreign".into()];
22694 let via_method = c.validate_foreign_code_kind_coherence().unwrap_err();
22695 let via_standalone =
22696 crate::LayoutError::foreign_code_slot(&c, c.declared_foreign_code_slots());
22697 assert_eq!(
22698 via_method, via_standalone,
22699 "Caixa::validate_foreign_code_kind_coherence must surface the \
22700 foreign-code-slot diagnostic byte-equal to the standalone \
22701 LayoutError::foreign_code_slot ctor on the same \
22702 declared_foreign_code_slots list",
22703 );
22704 }
22705
22706 #[test]
22707 fn validate_foreign_code_kind_coherence_exe_arm_precedes_servicos_arm() {
22708 // Cross-arm ordering pin on the fold's accumulator: a fixture
22709 // carrying BOTH a declared `:exe` AND a declared `:servicos`
22710 // on a kind foreign to both (a `:kind Biblioteca` here —
22711 // foreign to both the Binario arm and the Servico arm)
22712 // surfaces `:exe` first in the `ForeignCodeSlot`'s slots
22713 // list. Pins the canonical `:exe` → `:servicos` diagnostic
22714 // order [`Caixa::declared_foreign_code_slots`] establishes,
22715 // as a property of the substrate primitive rather than an
22716 // implicit accumulator convention. A silent reordering
22717 // regression at the accumulator would surface here as a
22718 // wrong-first-slot list before landing at a downstream
22719 // consumer's diagnostic-ordering expectation.
22720 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22721 c.kind = CaixaKind::Biblioteca;
22722 c.exe = vec!["exe/demo".into()];
22723 c.servicos = vec!["servicos/demo.computeunit.yaml".into()];
22724 let err = c.validate_foreign_code_kind_coherence().unwrap_err();
22725 let crate::LayoutError::ForeignCodeSlot { slots, .. } = &err else {
22726 panic!("expected ForeignCodeSlot variant, got {err:?}");
22727 };
22728 assert!(
22729 slots.starts_with(":exe"),
22730 "expected the :exe arm to precede the :servicos arm in the \
22731 ForeignCodeSlot slots list under the canonical :exe → :servicos \
22732 order, got slots = {slots:?}",
22733 );
22734 assert!(
22735 slots.contains(":servicos"),
22736 "expected the :servicos arm to also fire in the ForeignCodeSlot \
22737 slots list on a fixture carrying both foreign code surfaces, \
22738 got slots = {slots:?}",
22739 );
22740 }
22741
22742 #[test]
22743 fn validate_foreign_code_kind_coherence_accepts_native_slot_on_owner_kind() {
22744 // Positive control on the native-slot identity element: each
22745 // code-surface slot's owner kind passes the fold trivially
22746 // when it declares only its native code surface. `:kind
22747 // Binario` with a declared `:exe` and no `:servicos` passes
22748 // (the `!requires_exe()` guard short-circuits the arm inside
22749 // [`Caixa::declared_foreign_code_slots`], so the accumulator
22750 // returns empty); `:kind Servico` with a declared `:servicos`
22751 // and no `:exe` passes for the mirror reason. Pins the fold's
22752 // native-slot identity element on both arms — a silent
22753 // regression that dropped either per-arm `!requires_<slot>()`
22754 // predicate would surface here as a false-positive rejection
22755 // of every native-slot declaration on its owner kind. Peer
22756 // with the
22757 // `validate_kind_slot_coherence_accepts_owner_kind_on_every_arm`
22758 // identity-element pin on the sibling cross-family fold.
22759 let mut bin = Caixa::from_lisp(&Caixa::template("bin")).unwrap();
22760 bin.kind = CaixaKind::Binario;
22761 bin.bibliotecas = vec![];
22762 bin.exe = vec!["exe/bin".into()];
22763 bin.servicos = vec![];
22764 bin.validate_foreign_code_kind_coherence().expect(
22765 "a :kind Binario caixa with a declared native :exe and no \
22766 :servicos must pass the compound coherence gate — Binario is \
22767 the :exe slot's owner kind and the fold's native-slot identity \
22768 element on that arm",
22769 );
22770
22771 let mut svc = bare_servico_fixture("svc");
22772 svc.exe = vec![];
22773 svc.validate_foreign_code_kind_coherence().expect(
22774 "a :kind Servico caixa with a declared native :servicos and no \
22775 :exe must pass the compound coherence gate — Servico is the \
22776 :servicos slot's owner kind and the fold's native-slot identity \
22777 element on that arm",
22778 );
22779 }
22780
22781 #[test]
22782 fn validate_foreign_code_kind_coherence_accepts_bare_caixa_on_every_kind() {
22783 // Positive control on the empty-slot identity element: a
22784 // bare caixa (no declared `:exe` and no declared `:servicos`)
22785 // passes the compound gate on every kind. Pins the fold's
22786 // identity element on the empty-accumulator axis — the outer
22787 // `is_empty` short-circuit fires before the wrap dispatch on
22788 // every kind, so a bare caixa of any kind surfaces no
22789 // foreign-code-slot diagnostic. A silent regression that
22790 // dropped the emptiness guard would surface here as a
22791 // false-positive rejection of every no-code-slot caixa
22792 // across the whole kind axis. Peer with the
22793 // `validate_kind_slot_coherence_accepts_bare_caixa_on_every_kind`
22794 // identity-element pin on the sibling cross-family fold.
22795 for kind in CaixaKind::ALL {
22796 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22797 c.kind = *kind;
22798 c.bibliotecas = vec![];
22799 c.exe = vec![];
22800 c.servicos = vec![];
22801 c.validate_foreign_code_kind_coherence()
22802 .unwrap_or_else(|err| {
22803 panic!(
22804 "a bare :kind {kind:?} caixa (no declared :exe / \
22805 :servicos) must pass the compound coherence gate — \
22806 the fold's identity element on the empty-accumulator \
22807 axis is the outer Vec::is_empty short-circuit, got \
22808 {err:?}",
22809 )
22810 });
22811 }
22812 }
22813
22814 #[test]
22815 fn validate_required_kind_slot_folds_binario_arm_matches_gate() {
22816 // Fail-before-pass-after per-arm equivalence pin on the
22817 // `Binario` required-`:exe` arm of the required-slot fold:
22818 // a `:kind Binario` caixa carrying no declared `:exe` entry
22819 // surfaces the same
22820 // [`crate::LayoutError::BinarioWithoutExe`] variant through
22821 // both the compound gate
22822 // [`Caixa::validate_required_kind_slot`] and the standalone
22823 // constructor [`crate::LayoutError::binario_without_exe`].
22824 // Pins the fold — a silent regression that de-folded the
22825 // `Binario` arm would surface here as a mismatch between
22826 // the two dispatches. Sibling in shape to the peer
22827 // `validate_no_code_kind_coherence_folds_supervisor_arm_matches_gate`
22828 // per-arm equivalence pin on the reciprocal code-surface
22829 // fold.
22830 let mut c = Caixa::from_lisp(&Caixa::template("bin")).unwrap();
22831 c.kind = CaixaKind::Binario;
22832 c.bibliotecas = vec![];
22833 c.exe = vec![];
22834 let via_method = c.validate_required_kind_slot().unwrap_err();
22835 let via_standalone = crate::LayoutError::binario_without_exe(&c);
22836 assert_eq!(
22837 via_method, via_standalone,
22838 "Caixa::validate_required_kind_slot must surface the \
22839 Binario arm's diagnostic byte-equal to the standalone \
22840 LayoutError::binario_without_exe ctor",
22841 );
22842 }
22843
22844 #[test]
22845 fn validate_required_kind_slot_folds_servico_arm_matches_gate() {
22846 // Per-arm equivalence pin on the `Servico` required-
22847 // `:servicos` arm — the sibling of the Binario arm on the
22848 // required-slot fold. A `:kind Servico` caixa carrying no
22849 // declared `:servicos` entry surfaces the same
22850 // [`crate::LayoutError::ServicoWithoutServicos`] variant
22851 // through both dispatches.
22852 let mut c = Caixa::from_lisp(&Caixa::template("svc")).unwrap();
22853 c.kind = CaixaKind::Servico;
22854 c.bibliotecas = vec![];
22855 c.servicos = vec![];
22856 let via_method = c.validate_required_kind_slot().unwrap_err();
22857 let via_standalone = crate::LayoutError::servico_without_servicos(&c);
22858 assert_eq!(
22859 via_method, via_standalone,
22860 "Caixa::validate_required_kind_slot must surface the \
22861 Servico arm's diagnostic byte-equal to the standalone \
22862 LayoutError::servico_without_servicos ctor",
22863 );
22864 }
22865
22866 #[test]
22867 fn validate_required_kind_slot_folds_acao_arm_matches_gate() {
22868 // Per-arm equivalence pin on the `Acao` required-`:ci` arm
22869 // — the third and last arm on the required-slot fold. A
22870 // `:kind Acao` caixa carrying no declared `:ci` slot
22871 // surfaces the same [`crate::LayoutError::MissingCi`]
22872 // variant through both dispatches. The three per-arm pins
22873 // collectively cover every required-slot axis and every
22874 // owner kind of the arm dispatch.
22875 let mut c = Caixa::from_lisp(&Caixa::template("acao")).unwrap();
22876 c.kind = CaixaKind::Acao;
22877 c.bibliotecas = vec![];
22878 c.ci = None;
22879 let via_method = c.validate_required_kind_slot().unwrap_err();
22880 let via_standalone = crate::LayoutError::missing_ci(&c);
22881 assert_eq!(
22882 via_method, via_standalone,
22883 "Caixa::validate_required_kind_slot must surface the \
22884 Acao arm's diagnostic byte-equal to the standalone \
22885 LayoutError::missing_ci ctor",
22886 );
22887 }
22888
22889 #[test]
22890 fn validate_required_kind_slot_accepts_owner_kind_with_required_slot_present() {
22891 // Positive control on the owner-kind-with-slot-present
22892 // identity element: each of the three owner kinds
22893 // (`Binario` with a non-empty `:exe`, `Servico` with a
22894 // non-empty `:servicos`, `Acao` with `ci = Some(_)`)
22895 // passes the compound gate cleanly when it declares its
22896 // required slot. Pins the fold's second identity element
22897 // — the paired `is_empty` / `is_none` short-circuit fires
22898 // on every owner kind whose required slot is present, so
22899 // a caixa with its native required slot surfaces no
22900 // diagnostic. A silent regression that dropped the paired
22901 // `is_empty` / `is_none` short-circuit guard on any arm
22902 // would surface here as a false-positive rejection of the
22903 // corresponding owner kind. Peer with the
22904 // `validate_no_code_kind_coherence_accepts_owner_kind_on_every_code_axis`
22905 // identity-element pin on the sibling code-surface fold.
22906 let mut bin = Caixa::from_lisp(&Caixa::template("bin")).unwrap();
22907 bin.kind = CaixaKind::Binario;
22908 bin.bibliotecas = vec![];
22909 bin.exe = vec!["exe/bin".into()];
22910 bin.validate_required_kind_slot().expect(
22911 "a :kind Binario caixa with declared :exe must pass the \
22912 required-slot gate — Binario's required slot is present",
22913 );
22914
22915 let svc = bare_servico_fixture("svc");
22916 svc.validate_required_kind_slot().expect(
22917 "a :kind Servico caixa with declared :servicos must pass \
22918 the required-slot gate — Servico's required slot is present",
22919 );
22920
22921 let acao = acao_fixture("acao");
22922 acao.validate_required_kind_slot().expect(
22923 "a :kind Acao caixa with declared :ci must pass the \
22924 required-slot gate — Acao's required slot is present",
22925 );
22926 }
22927
22928 #[test]
22929 fn validate_required_kind_slot_accepts_non_owner_kinds() {
22930 // Positive control on the non-owner-kind identity element:
22931 // every kind that is not one of the three owner kinds
22932 // (`Binario` / `Servico` / `Acao`) passes the compound gate
22933 // trivially — each per-arm predicate is
22934 // `self.kind().requires_<slot>()`, which returns `true`
22935 // only for the owner kind of that arm, so a non-owner kind
22936 // short-circuits every per-arm dispatch. Bibliotheca,
22937 // Supervisor, and Aplicacao are the three non-owner kinds
22938 // this pin exercises — none of them owns a required slot in
22939 // this fold (`Biblioteca`'s `:bibliotecas` default-file
22940 // fallback stays on the layout-side `MissingLib` fs-oracle
22941 // gate outside this fold; `Supervisor`'s `:children` and
22942 // `Aplicacao`'s `:membros` are carried by
22943 // [`CaixaKind::requires_children`] /
22944 // [`CaixaKind::requires_membros`] without a paired
22945 // layout-side wire-up). A silent regression that swapped a
22946 // per-arm predicate for a non-`requires_*` guard would
22947 // surface here as a false-positive rejection of the
22948 // corresponding non-owner kind. Peer with the
22949 // `validate_ci_kind_coherence_accepts_absent_ci_on_every_kind`
22950 // identity-element pin on the sibling `:ci` fold.
22951 for kind in CaixaKind::ALL {
22952 if kind.requires_exe() || kind.requires_servicos() || kind.requires_ci() {
22953 continue;
22954 }
22955 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22956 c.kind = *kind;
22957 c.bibliotecas = vec![];
22958 c.exe = vec![];
22959 c.servicos = vec![];
22960 c.ci = None;
22961 c.validate_required_kind_slot().unwrap_or_else(|err| {
22962 panic!(
22963 "a :kind {kind:?} caixa (a non-owner kind on every \
22964 required-slot arm) must pass the compound gate — the \
22965 fold's identity element is the paired \
22966 `self.kind().requires_<slot>()` short-circuit, got \
22967 {err:?}",
22968 )
22969 });
22970 }
22971 }
22972
22973 // ── `manifest_code_path_slot_path_ctors!` — the paired `{ slot:
22974 // &'static str, path: PathBuf }` two-slot envelope on
22975 // `ManifestError`, strict sibling of the peer
22976 // [`crate::behavior::behavior_slot_path_ctors!`] (67c31ec) on the
22977 // sibling `BehaviorError` envelope's identical
22978 // `{ slot: &'static str, path: PathBuf }` two-slot shape.
22979 // Five-variant lift closing the five open-coded ctor sites
22980 // remaining on the `:bibliotecas` / `:exe` / `:servicos`
22981 // code-path-list value-shape trajectory this envelope carries.
22982
22983 #[test]
22984 fn code_path_absolute_ctor_matches_struct_literal_wrap() {
22985 let path = Path::new("/abs/lib/x.lisp");
22986 assert_eq!(
22987 ManifestError::code_path_absolute(":bibliotecas", path),
22988 ManifestError::CodePathAbsolute {
22989 slot: ":bibliotecas",
22990 path: path.to_path_buf(),
22991 },
22992 "generated code_path_absolute ctor must produce byte-equal \
22993 `ManifestError::CodePathAbsolute` to the pre-lift \
22994 struct-literal wrap on the same `(&'static str, &Path)` \
22995 fixture",
22996 );
22997 }
22998
22999 #[test]
23000 fn code_path_parent_escape_ctor_matches_struct_literal_wrap() {
23001 let path = Path::new("lib/../../etc/x.lisp");
23002 assert_eq!(
23003 ManifestError::code_path_parent_escape(":bibliotecas", path),
23004 ManifestError::CodePathParentEscape {
23005 slot: ":bibliotecas",
23006 path: path.to_path_buf(),
23007 },
23008 "generated code_path_parent_escape ctor must produce \
23009 byte-equal `ManifestError::CodePathParentEscape` to the \
23010 pre-lift struct-literal wrap on the same `(&'static str, \
23011 &Path)` fixture",
23012 );
23013 }
23014
23015 #[test]
23016 fn code_path_non_lisp_extension_ctor_matches_struct_literal_wrap() {
23017 let path = Path::new("lib/x.txt");
23018 assert_eq!(
23019 ManifestError::code_path_non_lisp_extension(":bibliotecas", path),
23020 ManifestError::CodePathNonLispExtension {
23021 slot: ":bibliotecas",
23022 path: path.to_path_buf(),
23023 },
23024 "generated code_path_non_lisp_extension ctor must produce \
23025 byte-equal `ManifestError::CodePathNonLispExtension` to \
23026 the pre-lift struct-literal wrap on the same \
23027 `(&'static str, &Path)` fixture",
23028 );
23029 }
23030
23031 #[test]
23032 fn code_path_non_computeunit_yaml_extension_ctor_matches_struct_literal_wrap() {
23033 let path = Path::new("servicos/x.yaml");
23034 assert_eq!(
23035 ManifestError::code_path_non_computeunit_yaml_extension(":servicos", path),
23036 ManifestError::CodePathNonComputeUnitYamlExtension {
23037 slot: ":servicos",
23038 path: path.to_path_buf(),
23039 },
23040 "generated code_path_non_computeunit_yaml_extension ctor \
23041 must produce byte-equal \
23042 `ManifestError::CodePathNonComputeUnitYamlExtension` to \
23043 the pre-lift struct-literal wrap on the same \
23044 `(&'static str, &Path)` fixture",
23045 );
23046 }
23047
23048 #[test]
23049 fn code_path_duplicate_ctor_matches_struct_literal_wrap() {
23050 let path = Path::new("lib/x.lisp");
23051 assert_eq!(
23052 ManifestError::code_path_duplicate(":bibliotecas", path),
23053 ManifestError::CodePathDuplicate {
23054 slot: ":bibliotecas",
23055 path: path.to_path_buf(),
23056 },
23057 "generated code_path_duplicate ctor must produce byte-equal \
23058 `ManifestError::CodePathDuplicate` to the pre-lift \
23059 struct-literal wrap on the same `(&'static str, &Path)` \
23060 fixture",
23061 );
23062 }
23063
23064 #[test]
23065 fn manifest_code_path_slot_path_ctors_route_slot_and_path_through_uniformly() {
23066 // Cross-axis routing pin: sweep the two constructor input axes
23067 // (`slot: &'static str`, `path: &Path`) through non-default
23068 // fixtures against every generated arm in the
23069 // [`manifest_code_path_slot_path_ctors!`] macro, so any
23070 // wrapper-side lowercase / trim / truncate / canonicalization at
23071 // codegen time — or a silent field re-name away from the
23072 // canonical `slot` / `path` axes on any one variant, or a `slot`
23073 // axis silently rerouted through `.to_string()` instead of
23074 // passed as `&'static str` verbatim, or a `path` axis silently
23075 // rerouted through `.canonicalize()` / `PathBuf::from(<lossy
23076 // string>)` instead of `.to_path_buf()` — surfaces here rather
23077 // than at a downstream diagnostic-shape mismatch. Peer of the
23078 // sibling
23079 // [`crate::behavior::tests::behavior_slot_path_ctors_route_slot_and_path_through_uniformly`]
23080 // pin (67c31ec) on the sibling `BehaviorError` envelope's
23081 // identical two-slot family.
23082 //
23083 // The `path` fixture carries three distinguishing traits at
23084 // once: a non-`root/`-relative leading segment (`weird/`), a
23085 // `..` component (a canonicalization trap that would collapse
23086 // to `weird/x.lisp` under `.canonicalize()`), and a mixed-case
23087 // extension (a lowercase-normalization trap that would collapse
23088 // `.LISP` to `.lisp` under any `to_ascii_lowercase()` codegen)
23089 // so a routing regression on any one of the three trap axes
23090 // surfaces at assert time. Similarly the `slot` fixture
23091 // sweeps the three canonical code-path author-key literals
23092 // (`:bibliotecas` / `:exe` / `:servicos`) so a silent lookup
23093 // against a per-variant const roster would surface here.
23094 let path = Path::new("weird/../nested/x.LISP");
23095 let cases: [(ManifestError, ManifestError); 5] = [
23096 (
23097 ManifestError::code_path_absolute(":bibliotecas", path),
23098 ManifestError::CodePathAbsolute {
23099 slot: ":bibliotecas",
23100 path: path.to_path_buf(),
23101 },
23102 ),
23103 (
23104 ManifestError::code_path_parent_escape(":exe", path),
23105 ManifestError::CodePathParentEscape {
23106 slot: ":exe",
23107 path: path.to_path_buf(),
23108 },
23109 ),
23110 (
23111 ManifestError::code_path_non_lisp_extension(":servicos", path),
23112 ManifestError::CodePathNonLispExtension {
23113 slot: ":servicos",
23114 path: path.to_path_buf(),
23115 },
23116 ),
23117 (
23118 ManifestError::code_path_non_computeunit_yaml_extension(":bibliotecas", path),
23119 ManifestError::CodePathNonComputeUnitYamlExtension {
23120 slot: ":bibliotecas",
23121 path: path.to_path_buf(),
23122 },
23123 ),
23124 (
23125 ManifestError::code_path_duplicate(":exe", path),
23126 ManifestError::CodePathDuplicate {
23127 slot: ":exe",
23128 path: path.to_path_buf(),
23129 },
23130 ),
23131 ];
23132 for (via_ctor, via_struct_literal) in cases {
23133 assert_eq!(
23134 via_ctor, via_struct_literal,
23135 "manifest_code_path_slot_path_ctors!-generated ctor \
23136 must pass `slot` verbatim onto the canonical \
23137 `&'static str` `slot` field and route `path` through \
23138 `.to_path_buf()` onto the canonical `PathBuf` `path` \
23139 field — a field-rename, silent-conversion, or \
23140 axis-swap regression surfaces here rather than at a \
23141 downstream diagnostic-shape mismatch",
23142 );
23143 }
23144 }
23145}