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::nome_invalid(nome, reason),
4468 )
4469 }
4470
4471 /// Reject `:nome` values whose joint length with the canonical
4472 /// [`crate::LAREIRA_CHART_NAME_PREFIX`] (`"lareira-"`) overflows
4473 /// the K8s DNS-1123 label cap [`crate::DNS_1123_LABEL_MAX_LEN`]
4474 /// (63 bytes). Every per-Servico / per-Aplicacao renderer the
4475 /// substrate carries materializes the caixa's `:nome` through the
4476 /// canonical [`crate::lareira_chart_name`] helper (f7320d7) into a
4477 /// `lareira-<nome>` artifact that lands as a K8s `metadata.name` /
4478 /// Helm chart name / `HelmRelease` `release_name`: `caixa-helm`'s
4479 /// `ChartDir.name` + `Chart.yaml::name`
4480 /// (caixa-helm/src/lib.rs:207), `caixa-flux`'s `cluster_bundle`
4481 /// `HelmRelease` `chart:` slot (caixa-flux/src/lib.rs:329),
4482 /// `caixa-tatara`'s `process_for_aplicacao` `release_name` +
4483 /// `oci://<registry>/lareira-<nome>` chart ref
4484 /// (caixa-tatara/src/lib.rs:124,178). Helm's own `Chart.yaml::name`
4485 /// admission rule strict-parses against DNS-1123-label, the Helm
4486 /// operator's tracking-secret name is derived from `release_name`
4487 /// and is itself DNS-1123-label-bounded, and the rendered chart's
4488 /// K8s object `metadata.name` axes embed the chart name as a
4489 /// prefix — every one fails admission on a > 63-byte chart name.
4490 ///
4491 /// The per-axis [`Self::validate_nome`] gate (6c992f8) already
4492 /// caps `:nome` itself at 63 bytes via [`is_dns_1123_label`], so a
4493 /// `:nome` of 56–63 bytes silently passed validate (the inner
4494 /// DNS-1123 check accepts the bare `:nome`) but produced a
4495 /// `lareira-<nome>` of 64–71 bytes that the apiserver / `helm lint`
4496 /// rejected at admission — far from the source `caixa.lisp`, with
4497 /// no field naming the overflow root cause. The
4498 /// [`lareira_chart_name`] helper's own doc comment
4499 /// (caixa-core/src/render.rs:3198) explicitly deferred the fix:
4500 /// "the M4 admission webhook will pin the joint-length invariant
4501 /// when it lands". This gate lands the invariant at the
4502 /// manifest-validate layer rather than waiting for the apiserver
4503 /// — the same fail-at-the-source posture every peer per-axis
4504 /// value-shape gate (DNS-1123 on `:nome`, SemVer-2 on `:versao`,
4505 /// SPDX-expression-shape on `:licenca`, 4-digit decimal year on
4506 /// `:edicao`, etc.) takes.
4507 ///
4508 /// Thin wrapper around
4509 /// [`crate::render::is_lareira_chart_name_shape`] (the
4510 /// substrate-side predicate that composes [`lareira_chart_name`] +
4511 /// [`is_dns_1123_label`] via the lifted
4512 /// [`crate::LAREIRA_CHART_NAME_NOME_MAX_LEN`] budget); maps the
4513 /// shared parser-shaped reason into the
4514 /// [`ManifestError::NomeChartNameBudgetExceeded`] variant so the
4515 /// diagnostic is self-locating (the offending `:nome` is named
4516 /// verbatim alongside the rendered chart name and the budget) and
4517 /// the author can shorten in one edit. The gate runs across every
4518 /// `:kind` — `:nome` is the substrate-wide identity axis any
4519 /// future renderer the substrate adds can derive a
4520 /// `lareira-<nome>` artifact from, and uniform enforcement closes
4521 /// the drift footgun where a future kind grows a chart-emitting
4522 /// render path while the validate cascade doesn't catch it.
4523 ///
4524 /// Runs *after* [`Self::validate_nome`] so the narrower
4525 /// `NomeEmpty` / `NomeInvalid` shape diagnostics fire first — a
4526 /// structurally-malformed `:nome` (empty, uppercase, underscore,
4527 /// dot, leading/trailing hyphen, Unicode, > 63 bytes) surfaces its
4528 /// specific shape error rather than the chart-name-budget error,
4529 /// preserving the legitimate "well-shaped `:nome` that happens to
4530 /// overflow the joint cap" arm for this gate.
4531 pub fn validate_nome_chart_name_budget(&self) -> Result<(), ManifestError> {
4532 let nome = self.nome();
4533 crate::render::is_lareira_chart_name_shape(nome)
4534 .map_err(|reason| ManifestError::nome_chart_name_budget_exceeded(nome, reason))
4535 }
4536
4537 /// Reject `:versao` values that don't parse as [`semver::Version`].
4538 /// The top-level Caixa version flows directly into every
4539 /// substrate-side artifact that carries a "this is which version of
4540 /// the caixa" axis: the `lareira-<nome>` Helm chart's `Chart.yaml`
4541 /// `version:` + `appVersion:` axes ([`caixa-helm::lib`] —
4542 /// SemVer-2-strict at `helm template` / `helm install` time per
4543 /// https://helm.sh/docs/topics/charts/#charts-and-versioning), the
4544 /// `feira publish` Zig-style `v<versao>` git tag
4545 /// ([`caixa-flux::lib::programs_yaml_entry`] / the
4546 /// `caixa-publish.yml` reusable workflow), the programs.yaml entry's
4547 /// `versao:` value the `lareira-fleet-programs` aggregator carries
4548 /// onto each rendered ComputeUnit, the OCI image's `:v<versao>` /
4549 /// `:latest` tags the substrate's `wasi-service-flake` builds with
4550 /// `skopeo push`, the lacre closure's pinned versions
4551 /// ([`caixa-resolver`] keys `concrete_versao`), and the
4552 /// `:upgrade-from :from` references peers in this exact `versao`
4553 /// shape (`semver::Version`, not `VersionReq`). Each consumer
4554 /// expects a strict three-part `MAJOR.MINOR.PATCH` (optionally
4555 /// `-prerelease` and/or `+build`); a structurally invalid `:versao`
4556 /// (`"0.1"` — missing patch, the canonical "I shortened it" footgun;
4557 /// `"v0.1.0"` — the git-tag-shape-leaking-into-versao typo;
4558 /// `"latest"` / `"main"` — the "I confused it with a docker tag"
4559 /// footgun; `"^0.1"` / `"~0.1.2"` — the requirement-shape leaking
4560 /// into the version field a peer `:deps :versao` accepts;
4561 /// `"0.1.0.0"` — the four-part Java/Microsoft convention DNS
4562 /// SemVer-2 forbids) silently passed [`Caixa::from_lisp`] (the
4563 /// derive macro stores the raw String) and the failure surfaced at
4564 /// the *first* downstream consumer that strict-parses it: at
4565 /// `helm install` time as a chart-version rejection, at
4566 /// `feira publish` time as a malformed git tag, at lacre-resolve
4567 /// time as a `semver::Error` not naming the offending caixa, at
4568 /// `feira upgrade --to <versao>` time as an unresolvable
4569 /// `:upgrade-from :from` match — far from the source `caixa.lisp`
4570 /// and without any field naming the offending `:versao`.
4571 ///
4572 /// Thin wrapper around [`semver::Version::parse`] — the same parser
4573 /// [`crate::CaixaVersion::parse`] (the typed `:versao` accessor)
4574 /// and [`crate::UpgradeFromEntry::validate`] (the peer
4575 /// `:upgrade-from :from` axis, 26da2c7) consume. Maps the
4576 /// `semver::Error` reason into the [`ManifestError::VersaoInvalid`]
4577 /// variant, carrying the offending `:versao` verbatim + a
4578 /// parser-shaped reason naming the specific violation, so the
4579 /// diagnostic is self-locating (the author can grep their
4580 /// `caixa.lisp` for `:versao "<value>"` and fix it in one edit).
4581 /// Same diagnostic shape as [`ManifestError::NomeInvalid`]
4582 /// (6c992f8) and [`crate::UpgradeError::FromInvalid`]
4583 /// (b0c8389) on the peer axes. With this gate, the typed `:versao`
4584 /// surfaces — top-level `:versao`, `:upgrade-from :from` — are
4585 /// now structurally equivalent (every value past validate is
4586 /// round-trippable through [`semver::Version::parse`] without
4587 /// re-checking at the renderer, resolver, or operator hot-upgrade
4588 /// layer), peer with the four `:versao` requirement axes (`:deps`,
4589 /// `:deps-dev`, `:membros`, `:children`) the prior commits
4590 /// (2420c44, 9888b13, b38ff3a) wired through `parse_requirement`.
4591 ///
4592 /// Empty `:versao` (which [`Caixa::from_lisp`] does not reject —
4593 /// the derive macro stores the raw String) is gated by the
4594 /// narrower [`ManifestError::VersaoEmpty`] arm before the parser is
4595 /// consulted, mirroring the empty-first cascade every per-axis
4596 /// version gate already uses (e.g. `MembroVersaoEmpty` before
4597 /// `MembroVersaoInvalid`, `EmptyChildVersion` before
4598 /// `ChildVersaoInvalid`, `NomeEmpty` before `NomeInvalid`).
4599 pub fn validate_versao(&self) -> Result<(), ManifestError> {
4600 let versao = self.versao();
4601 if versao.is_empty() {
4602 return Err(ManifestError::VersaoEmpty);
4603 }
4604 semver::Version::parse(versao)
4605 .map_err(|e| ManifestError::versao_invalid(versao, e.to_string()))?;
4606 Ok(())
4607 }
4608
4609 /// Compound per-`Caixa` entry gate on the M2 `:upgrade-from` slot:
4610 /// folds the three [`crate::upgrade`] top-level validators — the
4611 /// per-entry shape + cross-entry duplicate-`:from` gate
4612 /// ([`crate::upgrade::validate_upgrade_from`]), the cross-slot
4613 /// `:from < :versao` SemVer-2 precedence gate
4614 /// ([`crate::upgrade::validate_upgrade_from_against_versao`]), and the
4615 /// cross-slot `:state-change` ↔ `:on-state-change` composition gate
4616 /// ([`crate::upgrade::validate_upgrade_from_against_behavior`]) — onto
4617 /// one substrate primitive on [`Caixa`]. The three dispatches run in
4618 /// the same order the layout pipeline
4619 /// ([`crate::layout::StandardLayout::verify`], the `feira build`
4620 /// author-time gate) has always sequenced them, so the fold is
4621 /// byte-for-byte equivalent to the pre-fold three-block cascade at
4622 /// that call site (pinned by the per-arm
4623 /// `validate_upgrade_from_folds_per_entry_arm_matches_gate` /
4624 /// `_folds_versao_arm_matches_gate` / `_folds_behavior_arm_matches_gate`
4625 /// equivalence pins and by the cross-arm
4626 /// `validate_upgrade_from_per_entry_arm_fires_before_versao_arm` /
4627 /// `_versao_arm_fires_before_behavior_arm` ordering pins).
4628 ///
4629 /// Prior to this lift the three [`crate::upgrade`] top-level validators
4630 /// lived only open-coded at the layout wire-up site
4631 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
4632 /// each threaded through the same `self.upgrade_from()` slice and each
4633 /// paired with the same [`crate::LayoutError::UpgradeViolation`]-wrap
4634 /// envelope: every future consumer that wanted to gate `:upgrade-from`
4635 /// as a whole — the deferred `caixa.pleme.io/v1alpha1/Caixa` CR
4636 /// materializer's per-CR admission webhook re-checking `:upgrade-from`
4637 /// after a per-`(:from … :instructions …)` patch, a future `feira
4638 /// validate --upgrade` per-caixa admission verb, a per-`:upgrade-from`
4639 /// overlay resolver a per-cluster overlay lift would materialize —
4640 /// was structurally forced to either re-inline the three-dispatch
4641 /// cascade in lockstep with the layout wire-up (the duplication the
4642 /// PRIME DIRECTIVE names as a bug) or call the whole
4643 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
4644 /// peer per-Caixa gate to re-check one slot. Post-fold each such
4645 /// consumer reaches the three-arm compound gate through one call on
4646 /// the substrate primitive.
4647 ///
4648 /// The three arms together name one contract with three axes:
4649 ///
4650 /// - **per-entry + cross-entry graph-edge invariant** — every entry's
4651 /// `:from` parses as SemVer-2 and every per-instruction / within-
4652 /// entry ordering / singularity gate on each entry's
4653 /// `:instructions` list passes, and no two entries share the same
4654 /// parsed `:from` (the wasm-operator's OTP appup
4655 /// `release_handler:install_release/1` analog picks at most one
4656 /// matching block per running version — two entries with the same
4657 /// parsed semver are an ambiguous edge in the typed upgrade graph).
4658 /// - **cross-slot reachability invariant** — every entry's `:from`
4659 /// is strictly less than the caixa's own `:versao` under SemVer-2
4660 /// precedence. An entry whose `:from >= :versao` is structurally
4661 /// unreachable by the operator's `:from`-match dispatch (the
4662 /// operator loads the current `:versao` and matches the *running*
4663 /// version against each entry's `:from`; an entry whose `:from >=
4664 /// :versao` is never reached because the operator never runs a
4665 /// version >= the current one that it could then upgrade *to* the
4666 /// current one).
4667 /// - **cross-slot composition invariant** — every entry carrying a
4668 /// `(:state-change …)` instruction has a `:behavior
4669 /// :on-state-change` callback declared on the same caixa. The
4670 /// per-version migration script is the `gen_server:code_change/3`
4671 /// analog and the runtime hook it is delivered through during hot
4672 /// upgrade is the `:on-state-change` callback (the upgrade.rs
4673 /// module doc pins the composition verbatim: "Composes with the
4674 /// `:behavior :on-state-change` callback to deliver state migration
4675 /// during hot upgrades").
4676 ///
4677 /// All three axes must hold together — every consumer's
4678 /// `:upgrade-from` accept-set past this compound gate is the same
4679 /// set the `feira build` author-time gate admits.
4680 ///
4681 /// The per-slot compound entry gate discipline lifted here onto the
4682 /// M2 `:upgrade-from` axis is the sibling of the peer per-kind
4683 /// compound entry gates ([`crate::render::require_supervisor_view`]
4684 /// / [`crate::render::require_aplicacao_view`] /
4685 /// [`crate::render::require_v0_servico_shape`]) that fold every
4686 /// per-kind cascade at the per-kind altitude, and of the peer
4687 /// per-slot compound gates ([`crate::AplicacaoSpec::validate_contratos`],
4688 /// [`crate::MeshPolicy::validate`],
4689 /// [`crate::SupervisorSpec::validate_children`]) that fold every
4690 /// structural axis on their slot onto one substrate primitive.
4691 /// Extended here to the last unlifted compound-cascade wire-up at
4692 /// the layout-pipeline altitude — the three-dispatch M2
4693 /// `:upgrade-from` cascade that lived only open-coded at the layout
4694 /// wire-up site.
4695 ///
4696 /// The per-instruction script-path on-disk existence-probe walk that
4697 /// [`crate::layout::StandardLayout::verify`] runs immediately after
4698 /// this gate (which resolves each entry's `:instructions
4699 /// (:state-change :script)` against the layout root) stays open-coded
4700 /// at the layout wire-up site — that arm needs the filesystem oracle
4701 /// on the [`crate::LayoutInvariants`] trait, not the pure per-Caixa
4702 /// typed-shape surface this compound gate folds. Same posture the
4703 /// peer [`Self::validate_code_paths`] takes on the sibling code-path
4704 /// axes: the typed-shape gate fires on the per-Caixa surface, the
4705 /// on-disk existence check fires on the [`crate::StandardLayout`]
4706 /// surface.
4707 ///
4708 /// # Errors
4709 ///
4710 /// Returns [`crate::UpgradeError::FromInvalid`] /
4711 /// [`crate::UpgradeError::ModuleEmpty`] /
4712 /// [`crate::UpgradeError::ModuleInvalid`] /
4713 /// [`crate::UpgradeError::EmptyScript`] /
4714 /// [`crate::UpgradeError::AbsoluteScript`] /
4715 /// [`crate::UpgradeError::ParentEscapeScript`] /
4716 /// [`crate::UpgradeError::NonLispExtensionScript`] /
4717 /// [`crate::UpgradeError::RestartNotExclusive`] /
4718 /// [`crate::UpgradeError::StateChangeWithoutPriorLoad`] /
4719 /// [`crate::UpgradeError::PurgeWithoutPriorLoad`] /
4720 /// [`crate::UpgradeError::StateChangeAfterCleanup`] /
4721 /// [`crate::UpgradeError::DuplicateLoadModule`] /
4722 /// [`crate::UpgradeError::DuplicateStateChange`] /
4723 /// [`crate::UpgradeError::DuplicateCleanup`] /
4724 /// [`crate::UpgradeError::DuplicateFrom`] on the per-entry +
4725 /// cross-entry axis; [`crate::UpgradeError::FromNotBeforeVersao`] on
4726 /// the cross-slot `:from ↔ :versao` axis;
4727 /// [`crate::UpgradeError::StateChangeWithoutOnStateChangeCallback`]
4728 /// on the cross-slot `:state-change ↔ :on-state-change` axis.
4729 pub fn validate_upgrade_from(&self) -> Result<(), crate::UpgradeError> {
4730 crate::upgrade::validate_upgrade_from(self.upgrade_from())?;
4731 crate::upgrade::validate_upgrade_from_against_versao(self.upgrade_from(), self.versao())?;
4732 crate::upgrade::validate_upgrade_from_against_behavior(
4733 self.upgrade_from(),
4734 self.behavior(),
4735 )?;
4736 Ok(())
4737 }
4738
4739 /// Compound per-`Caixa` entry gate on the M2 `:limits` slot — folds
4740 /// the [`crate::LimitsSpec::validate`] four-axis cascade (`:memory`
4741 /// wasm32 zero-floor / below-page / above-cap / non-page-multiple;
4742 /// `:fuel` zero-floor / cap; `:wall-clock` zero-floor / cap; `:cpu`
4743 /// zero-floor / cap) onto one substrate primitive on [`Caixa`]. The
4744 /// `#[serde(default)]` absent-slot arm (`limits: None`, the
4745 /// canonical "no bound declared — engine-default applies" author
4746 /// shape [`crate::LimitsSpec::is_empty`]'s per-axis `None` cascade
4747 /// reads) is the fold's identity element and passes trivially; the
4748 /// present-slot arm (`limits: Some(l)`) dispatches to
4749 /// [`crate::LimitsSpec::validate`] verbatim, threading its per-axis
4750 /// [`crate::LimitsError`] Display through untouched.
4751 ///
4752 /// Prior to this lift the M2 `:limits` slot lived only wired
4753 /// open-coded at the layout wire-up site
4754 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
4755 /// through the `if let Some(l) = caixa.limits() { l.validate() … }`
4756 /// three-line `Option::None → Ok(()) | Some(_) → …` unwrap-and-
4757 /// dispatch pattern paired with the same
4758 /// [`crate::LayoutError::LimitsViolation`]-wrap envelope: every
4759 /// future consumer that wanted to gate `:limits` as a whole — the
4760 /// deferred `caixa.pleme.io/v1alpha1/Caixa` CR materializer's
4761 /// per-CR admission webhook re-checking `:limits` after a per-
4762 /// `{:memory, :fuel, :wall-clock, :cpu}` patch (the exact case the
4763 /// [`Self::limits`] accessor docstring names as the second
4764 /// consumer of the slot), a future `feira validate --limits` per-
4765 /// caixa admission verb, a per-`:limits` overlay resolver a per-
4766 /// cluster `:limits-overrides` overlay lift would materialize — was
4767 /// structurally forced to either re-inline the two-line
4768 /// `Option::None → Ok(()) | Some(_) → …` unwrap-and-dispatch
4769 /// pattern in lockstep with the layout wire-up (the duplication the
4770 /// PRIME DIRECTIVE names as a bug) or call the whole
4771 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
4772 /// peer per-Caixa gate ([`Self::validate_nome`],
4773 /// [`Self::validate_versao`], [`Self::validate_deps`],
4774 /// [`Self::validate_etiquetas`], [`Self::validate_autores`],
4775 /// [`Self::validate_repositorio`], [`Self::validate_descricao`],
4776 /// [`Self::validate_licenca`], [`Self::validate_edicao`],
4777 /// [`Self::validate_upgrade_from`], [`Self::validate_code_paths`],
4778 /// plus the per-kind `require_supervisor_view` /
4779 /// `require_aplicacao_view` gates, plus the on-disk existence
4780 /// walks) to re-check one slot. Post-lift each such consumer
4781 /// reaches the [`crate::LimitsSpec::validate`] four-axis cascade
4782 /// (and its identity-element on the absent slot) through one call
4783 /// on the substrate primitive.
4784 ///
4785 /// The per-slot compound entry-gate discipline lifted here onto the
4786 /// M2 `:limits` axis is the sibling of the peer per-slot compound
4787 /// gates ([`crate::AplicacaoSpec::validate_contratos`],
4788 /// [`crate::MeshPolicy::validate`],
4789 /// [`crate::SupervisorSpec::validate_children`],
4790 /// [`Self::validate_upgrade_from`], [`Self::validate_deps`]) that
4791 /// fold every structural + cross-slot axis on their slot onto one
4792 /// substrate primitive. Extended here to the M2 `:limits` slot, the
4793 /// first of the two M2 typed slots (`:limits`, `:behavior`) whose
4794 /// per-Caixa compound-gate wire-up still lived open-coded at the
4795 /// layout altitude after the [`Self::validate_upgrade_from`] lift
4796 /// (d6801df) closed the sibling M2 slot's cascade.
4797 ///
4798 /// # Errors
4799 ///
4800 /// Returns every [`crate::LimitsError`] variant on the present-slot
4801 /// arm — verbatim from [`crate::LimitsSpec::validate`]. Passes
4802 /// trivially on the absent-slot arm (`limits: None`, the fold's
4803 /// identity element).
4804 pub fn validate_limits(&self) -> Result<(), crate::LimitsError> {
4805 match self.limits() {
4806 Some(l) => l.validate(),
4807 None => Ok(()),
4808 }
4809 }
4810
4811 /// Compound per-`Caixa` entry gate on the M2 `:behavior` slot's
4812 /// pure typed-shape surface — folds the
4813 /// [`crate::BehaviorSpec::validate`] six-slot value-shape cascade
4814 /// (each declared `:on-init` / `:on-call` / `:on-cast` / `:on-info`
4815 /// / `:on-state-change` / `:on-terminate` callback-path is
4816 /// non-empty / relative / no-`..`-parent-escape / terminating-
4817 /// `.lisp`-extension, routed through the shared
4818 /// [`crate::render::require_sandboxed_lisp_path`] arm-set) onto one
4819 /// substrate primitive on [`Caixa`]. The `#[serde(default)]`
4820 /// absent-slot arm (`behavior: None`, the canonical "no callback
4821 /// declared — the runtime falls back to the wasm-engine's default
4822 /// callback per arm" author shape [`crate::BehaviorSpec::is_empty`]'s
4823 /// per-slot `None` cascade reads) is the fold's identity element
4824 /// and passes trivially; the present-slot arm (`behavior: Some(b)`)
4825 /// dispatches to [`crate::BehaviorSpec::validate`] verbatim,
4826 /// threading its per-slot [`crate::BehaviorError`] Display through
4827 /// untouched.
4828 ///
4829 /// Scope note — the on-disk callback-path existence walk paired
4830 /// with the value-shape gate at
4831 /// [`crate::layout::StandardLayout::verify`] stays open-coded at
4832 /// the layout altitude, because it needs the
4833 /// [`crate::layout::LayoutInvariants`] filesystem oracle
4834 /// ([`crate::layout::LayoutInvariants::exists`]) that the pure
4835 /// per-Caixa typed-shape surface this compound gate folds onto has
4836 /// no reference to. Same posture the peer M2 `:upgrade-from`
4837 /// per-Caixa compound gate ([`Self::validate_upgrade_from`]
4838 /// d6801df) already carries: the pure typed-shape surface folds
4839 /// onto the substrate primitive; the per-instruction script-path
4840 /// existence probe on the paired axis (there `:state-change
4841 /// :script`; here `:on-*`) stays at the layout altitude.
4842 ///
4843 /// Prior to this lift the pure value-shape surface of the M2
4844 /// `:behavior` slot lived only wired open-coded at the layout
4845 /// wire-up site ([`crate::layout::StandardLayout::verify`],
4846 /// caixa-core/src/layout.rs), through the
4847 /// `if let Some(b) = caixa.behavior() { b.validate() … }`
4848 /// unwrap-and-dispatch pattern paired with the same
4849 /// [`crate::LayoutError::BehaviorViolation`]-wrap envelope: every
4850 /// future consumer that wanted to gate the `:behavior` slot's
4851 /// value-shape as a whole — the deferred
4852 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's per-CR
4853 /// admission webhook re-checking `:behavior` after a per-`{:on-init,
4854 /// :on-call, :on-cast, :on-info, :on-state-change, :on-terminate}`
4855 /// patch (the exact case the peer `:on-*` accessor docstrings on
4856 /// [`crate::BehaviorSpec`] already name as deferred consumers of
4857 /// the slot), a future `feira validate --behavior` per-caixa
4858 /// admission verb, a per-`:behavior` overlay resolver a future
4859 /// per-cluster callback-overlay lift would materialize — was
4860 /// structurally forced to either re-inline the two-line
4861 /// `Option::None → Ok(()) | Some(_) → …` unwrap-and-dispatch
4862 /// pattern in lockstep with the layout wire-up (the duplication the
4863 /// PRIME DIRECTIVE names as a bug) or call the whole
4864 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
4865 /// peer per-Caixa gate ([`Self::validate_nome`],
4866 /// [`Self::validate_versao`], [`Self::validate_deps`],
4867 /// [`Self::validate_etiquetas`], [`Self::validate_autores`],
4868 /// [`Self::validate_repositorio`], [`Self::validate_descricao`],
4869 /// [`Self::validate_licenca`], [`Self::validate_edicao`],
4870 /// [`Self::validate_limits`], [`Self::validate_upgrade_from`],
4871 /// [`Self::validate_code_paths`], plus the per-kind
4872 /// `require_supervisor_view` / `require_aplicacao_view` gates, plus
4873 /// the on-disk existence walks) to re-check one slot. Post-lift
4874 /// each such consumer reaches the [`crate::BehaviorSpec::validate`]
4875 /// six-slot cascade (and its identity-element on the absent slot)
4876 /// through one call on the substrate primitive.
4877 ///
4878 /// The per-slot compound entry-gate discipline lifted here onto the
4879 /// M2 `:behavior` axis is the sibling of the peer per-slot compound
4880 /// gates ([`crate::AplicacaoSpec::validate_contratos`],
4881 /// [`crate::MeshPolicy::validate`],
4882 /// [`crate::SupervisorSpec::validate_children`],
4883 /// [`Self::validate_upgrade_from`], [`Self::validate_deps`],
4884 /// [`Self::validate_limits`]) that fold every structural + cross-
4885 /// slot axis on their slot onto one substrate primitive. Extended
4886 /// here to the M2 `:behavior` slot, the last of the four M2 typed
4887 /// slots (`:limits`, `:behavior`, `:upgrade-from`, plus the
4888 /// supervisor-only `:children` peer) whose per-Caixa compound-gate
4889 /// wire-up still lived open-coded at the layout altitude after the
4890 /// [`Self::validate_limits`] lift (baa4688) closed the sibling M2
4891 /// `:limits` slot's cascade. With this lift the "one named per-slot
4892 /// / per-Caixa compound gate per typed slot folding every structural
4893 /// axis on that slot (plus the `Option::None` identity element for
4894 /// the `Option`-shaped slots) onto one substrate primitive"
4895 /// discipline spans every M2 typed slot uniformly, so a reader who
4896 /// has learned any peer M2 gate reads `:behavior` without a per-
4897 /// slot exception carve-out.
4898 ///
4899 /// # Errors
4900 ///
4901 /// Returns every [`crate::BehaviorError`] variant on the present-
4902 /// slot arm — verbatim from [`crate::BehaviorSpec::validate`].
4903 /// Passes trivially on the absent-slot arm (`behavior: None`, the
4904 /// fold's identity element).
4905 pub fn validate_behavior(&self) -> Result<(), crate::BehaviorError> {
4906 match self.behavior() {
4907 Some(b) => b.validate(),
4908 None => Ok(()),
4909 }
4910 }
4911
4912 /// Reject `:restart-window` values the shared
4913 /// [`crate::supervisor::duration_codec::parse`] refuses. The flat
4914 /// `restart_window: Option<String>` slot on [`Caixa`] is stored
4915 /// raw by the derive macro (the typed [`SupervisorSpec`] holds an
4916 /// `Option<Duration>` routed through the shared codec via `with =
4917 /// "duration_codec"`); the inline `Caixa → SupervisorSpec`
4918 /// view-construction path ([`Self::supervisor_view`]) folds the
4919 /// raw string through the same shared codec and soft-swallows the
4920 /// parse error as `None` to keep the view best-effort. Without
4921 /// this gate a malformed `:restart-window` (`"1.5s"` — the
4922 /// fractional-seconds drift class; `"1.0s"` — the decimal-shaped
4923 /// integer drift; `"0.5m"` — the unit-fraction drift; `"+30s"` /
4924 /// `"-30s"` — the leading-sign drift; `"30x"` — the unknown-unit
4925 /// footgun; `"abc"` — pure garbage; `""` — the empty-after-trim
4926 /// edge case) silently produced a `SupervisorSpec` with
4927 /// `restart_window: None`, indistinguishable from the canonical
4928 /// "omit the slot to express no reset" authoring shape — Erlang/OTP's
4929 /// `MaxIntensity / Period` invariant turns into a never-reset
4930 /// supervisor far from the source `caixa.lisp`, with no field
4931 /// naming the offending `:restart-window`. Lifting the gate to a
4932 /// Caixa-level validator mirrors the trajectory of the peer
4933 /// per-axis identity gates ([`Self::validate_nome`] 6c992f8,
4934 /// [`Self::validate_versao`] 1fdaa02, [`Self::validate_deps`]
4935 /// a7f0d8c) and the ABSORPTION-ROADMAP.md M2.2 test pin
4936 /// (line 196: "reject invalid `:restart-window` (non-duration)").
4937 ///
4938 /// Thin wrapper around [`crate::supervisor::duration_codec::parse`]
4939 /// (the shared codec backing `:supervisor :restart-window` as
4940 /// serde-routed on [`SupervisorSpec`], `:politicas :timeout`, and
4941 /// `:politicas :circuit-breaker :window` — all three covered by
4942 /// the integer-magnitude gate 1c55a2a). Maps the codec's parse
4943 /// error verbatim into the [`ManifestError::RestartWindowMalformed`]
4944 /// variant, carrying the offending raw string + a parser-shaped
4945 /// reason naming the canonical authoring form, so the diagnostic
4946 /// is self-locating (the author can grep their `caixa.lisp` for
4947 /// `:restart-window "<value>"` and fix it in one edit) and
4948 /// uniform with every other manifest-level validate diagnostic.
4949 /// With this gate the four `:restart-window`-shaped surfaces (the
4950 /// flat raw string on [`Caixa`], the typed `Option<Duration>` on
4951 /// [`SupervisorSpec`], the two `MeshPolicy` peer durations) are
4952 /// now structurally equivalent — every value past the codec is in
4953 /// one accepted set, by construction.
4954 ///
4955 /// `None` (the canonical "omit the slot to express no reset"
4956 /// shape) is accepted trivially — the gate is a no-op when the
4957 /// author didn't author a window. The empty string is rejected by
4958 /// the shared codec (its digit-only gate refuses an empty
4959 /// magnitude), surfacing the same `RestartWindowMalformed`
4960 /// diagnostic as every other rejected non-canonical shape.
4961 pub fn validate_restart_window(&self) -> Result<(), ManifestError> {
4962 let Some(s) = self.restart_window() else {
4963 return Ok(());
4964 };
4965 crate::supervisor::duration_codec::parse(s)
4966 .map(|_| ())
4967 .map_err(|reason| ManifestError::restart_window_malformed(s, reason))
4968 }
4969
4970 /// Compound per-`Caixa` entry gate on the Aplicacao-kind mesh-slot
4971 /// family — folds the paired [`crate::AplicacaoSpec::validate`]
4972 /// typed-shape cascade (per-slot gates on `:membros`, `:contratos`,
4973 /// `:entrada`, `:placement`, `:politicas`, in that declared order)
4974 /// plus the cross-slot self-edge gate
4975 /// ([`crate::aplicacao::validate_no_self_membership`], the
4976 /// `:membros :caixa` ≠ `:nome` invariant the typed view cannot
4977 /// enforce on its own because it carries the membros but not the
4978 /// parent `:nome`) onto one substrate primitive on [`Caixa`]. On
4979 /// non-Aplicacao kinds the fold is the identity element — the paired
4980 /// [`Self::aplicacao_view`] accessor returns `None` off the
4981 /// Aplicacao arm (peer with the [`Self::validate_limits`] /
4982 /// [`Self::validate_behavior`] M2 `Option`-arm identity element),
4983 /// so the gate passes trivially without touching the mesh slots.
4984 ///
4985 /// Prior to this lift the paired cascade lived only wired open-coded
4986 /// at the layout wire-up site
4987 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
4988 /// as the three-line `let view = caixa.aplicacao_view().expect(...);
4989 /// view.validate() … validate_no_self_membership(...) …` pattern
4990 /// paired with two `.map_err(|err| LayoutError::AplicacaoViolation
4991 /// { caixa, issue })` wraps — every future consumer that wanted to
4992 /// gate the Aplicacao-shape cascade as a whole (the deferred
4993 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's per-CR
4994 /// admission webhook re-checking `:membros` / `:contratos` after a
4995 /// per-slot patch, a future `feira validate --aplicacao` per-caixa
4996 /// admission verb, a per-Aplicacao overlay resolver) was structurally
4997 /// forced to either re-inline the two-dispatch cascade in lockstep
4998 /// with the layout wire-up (the duplication the PRIME DIRECTIVE
4999 /// names as a bug) or call the whole
5000 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
5001 /// peer per-Caixa gate to re-check one slot family. Post-fold each
5002 /// such consumer reaches the two-arm compound gate through one call
5003 /// on the substrate primitive.
5004 ///
5005 /// Peer to the [`crate::render::require_aplicacao_view`] compound
5006 /// entry gate every per-Aplicacao *renderer* routes through
5007 /// (3aefefb folded `validate_no_self_membership` onto the renderer
5008 /// path) — this gate mirrors the same fold on the *layout* path, so
5009 /// the two consumers of the Aplicacao-shape cascade (the author-time
5010 /// gate and every per-Aplicacao renderer) share one substrate
5011 /// primitive rather than two open-coded cascades kept in lockstep.
5012 /// Same lift discipline the peer per-slot compound gates
5013 /// ([`Self::validate_upgrade_from`] d6801df, [`Self::validate_deps`]
5014 /// b5dd55e, [`Self::validate_limits`] baa4688,
5015 /// [`Self::validate_behavior`] 0d2877a) each carry.
5016 ///
5017 /// # Errors
5018 ///
5019 /// Returns every [`crate::AplicacaoError`] variant on the present-
5020 /// kind arm — the typed-shape cascade's per-slot arms first
5021 /// (matching [`crate::AplicacaoSpec::validate`]'s declared order),
5022 /// then the cross-slot self-edge arm
5023 /// ([`crate::AplicacaoError::MembroIsSelfAplicacao`]). Passes
5024 /// trivially on non-Aplicacao kinds (the fold's identity element).
5025 pub fn validate_aplicacao_shape(&self) -> Result<(), crate::AplicacaoError> {
5026 let Some(view) = self.aplicacao_view() else {
5027 return Ok(());
5028 };
5029 view.validate()?;
5030 crate::aplicacao::validate_no_self_membership(self.membros(), self.nome())?;
5031 Ok(())
5032 }
5033
5034 /// Compound per-`Caixa` entry gate on the Supervisor-kind
5035 /// supervision-tree slot family — folds the paired
5036 /// [`crate::SupervisorSpec::validate`] typed-shape cascade
5037 /// (`:estrategia` ↔ `:children` invariants, `:max-restarts` /
5038 /// `:restart-window` bounds, per-child DNS-1123 `:caixa` names,
5039 /// semver-valid `:versao` constraints, the set-not-multiset
5040 /// duplicate-child gate) plus the cross-slot self-edge gate
5041 /// ([`crate::supervisor::validate_no_self_supervision`], the
5042 /// `:children :caixa` ≠ `:nome` invariant the typed view cannot
5043 /// enforce on its own because it carries the children but not the
5044 /// parent `:nome`) onto one substrate primitive on [`Caixa`]. On
5045 /// non-Supervisor kinds the fold is the identity element — the paired
5046 /// [`Self::supervisor_view`] accessor returns `None` off the
5047 /// Supervisor arm (peer with the [`Self::validate_limits`] /
5048 /// [`Self::validate_behavior`] M2 `Option`-arm identity element and
5049 /// the sibling per-Aplicacao [`Self::validate_aplicacao_shape`]),
5050 /// so the gate passes trivially without touching the supervision-tree
5051 /// slots.
5052 ///
5053 /// Prior to this lift the paired cascade lived only wired open-coded
5054 /// at the layout wire-up site
5055 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/layout.rs),
5056 /// as the three-line `let view = caixa.supervisor_view().expect(...);
5057 /// view.validate() … validate_no_self_supervision(...) …` pattern
5058 /// paired with two `.map_err(|err| LayoutError::SupervisorViolation
5059 /// { caixa, issue })` wraps — every future consumer that wanted to
5060 /// gate the Supervisor-shape cascade as a whole (the wasm-operator's
5061 /// hierarchical reconciliation scheduler re-checking `:children` /
5062 /// `:estrategia` after a per-slot patch, the M4
5063 /// `mesh.pleme.io/v1alpha1/Supervisor` CR materializer's admission
5064 /// webhook, a future `feira validate --supervisor` per-caixa
5065 /// admission verb, a per-Supervisor overlay resolver) was structurally
5066 /// forced to either re-inline the two-dispatch cascade in lockstep
5067 /// with the layout wire-up (the duplication the PRIME DIRECTIVE
5068 /// names as a bug) or call the whole
5069 /// [`crate::layout::StandardLayout::verify`] pipeline and pay every
5070 /// peer per-Caixa gate to re-check one slot family. Post-fold each
5071 /// such consumer reaches the two-arm compound gate through one call
5072 /// on the substrate primitive.
5073 ///
5074 /// Peer to the [`crate::render::require_supervisor_view`] compound
5075 /// entry gate every per-Supervisor *renderer* would route through
5076 /// (which already folds the same `spec.validate()` +
5077 /// `validate_no_self_supervision` two-arm cascade behind its
5078 /// `require_kind` + `validate_restart_window` prelude) — this gate
5079 /// mirrors the same fold on the *layout* path, so the two consumers
5080 /// of the Supervisor-shape cascade (the author-time gate and every
5081 /// per-Supervisor renderer) share one substrate primitive rather
5082 /// than two open-coded cascades kept in lockstep. Same lift
5083 /// discipline the peer per-slot compound gates
5084 /// ([`Self::validate_aplicacao_shape`] 949a7a0,
5085 /// [`Self::validate_upgrade_from`] d6801df,
5086 /// [`Self::validate_deps`] b5dd55e, [`Self::validate_limits`]
5087 /// baa4688, [`Self::validate_behavior`] 0d2877a) each carry.
5088 ///
5089 /// # Errors
5090 ///
5091 /// Returns every [`crate::SupervisorError`] variant on the present-
5092 /// kind arm — the typed-shape cascade's per-slot arms first
5093 /// (matching [`crate::SupervisorSpec::validate`]'s declared order),
5094 /// then the cross-slot self-edge arm
5095 /// ([`crate::SupervisorError::ChildSupervisesSelf`]). Passes
5096 /// trivially on non-Supervisor kinds (the fold's identity element).
5097 pub fn validate_supervisor_shape(&self) -> Result<(), crate::SupervisorError> {
5098 let Some(view) = self.supervisor_view() else {
5099 return Ok(());
5100 };
5101 view.validate()?;
5102 crate::supervisor::validate_no_self_supervision(self.children(), self.nome())?;
5103 Ok(())
5104 }
5105
5106 /// Compound per-`Caixa` entry gate on the Acao-kind `:ci` slot
5107 /// family — folds the [`crate::decompose_ci`] typed decompose gate
5108 /// (`canteiro_types::decompose` refusing every illegal
5109 /// [`canteiro_types::CiRun`] shape: duplicate node name, dependency
5110 /// on an undeclared node, dependency cycle) onto one substrate
5111 /// primitive on [`Caixa`]. On non-`Acao` kinds the fold is the
5112 /// identity element — the paired [`Self::kind`] `is_acao()` guard
5113 /// short-circuits before the decompose gate ever fires (peer with
5114 /// the [`Self::validate_aplicacao_shape`] /
5115 /// [`Self::validate_supervisor_shape`] typed-view identity element
5116 /// and the [`Self::validate_limits`] / [`Self::validate_behavior`]
5117 /// M2 `Option`-arm identity element), so the gate passes trivially
5118 /// without touching the `:ci` slot. An `:kind Acao` caixa with
5119 /// `ci = None` is also an identity-element pass: the presence gate
5120 /// is the sibling axis owned by [`crate::LayoutError::MissingCi`] /
5121 /// [`crate::require_ci`] / [`crate::MissingCiSlot`], not by the
5122 /// decompose gate — a caixa that carries no `:ci` slot has no run
5123 /// to decompose. Same split the peer per-Servico
5124 /// [`crate::LayoutError::ServicoWithoutServicos`] presence gate and
5125 /// per-Binario [`crate::LayoutError::BinarioWithoutExe`] presence
5126 /// gate keep from their sibling per-slot shape gates, so the two
5127 /// axes stay separately diagnosable at the layout altitude.
5128 ///
5129 /// Prior to this lift the decompose gate lived only wired
5130 /// open-coded at the [`caixa_actions::validate`] renderer-side
5131 /// entry gate (routed through the substrate-canonical
5132 /// [`crate::require_acao_view`] compound helper) — the *layout*
5133 /// pipeline ([`crate::layout::StandardLayout::verify`], caixa-core/
5134 /// src/layout.rs) only checked `:ci` *presence* via
5135 /// [`crate::LayoutError::MissingCi`], so a `:kind Acao` caixa
5136 /// carrying a structurally illegal `:ci` (a duplicate node name, a
5137 /// dependency on an undeclared node, a dependency cycle) passed
5138 /// `feira build` cleanly and surfaced the diagnostic only when
5139 /// [`caixa_actions::validate`] later refused it — far from the
5140 /// source `caixa.lisp` on the author-time gate side. Every future
5141 /// consumer that wanted to gate the Acao-shape cascade as a whole
5142 /// (a per-`Acao` CR materializer's admission webhook re-checking
5143 /// `:ci` after a per-node patch, a future `feira validate --acao`
5144 /// per-caixa admission verb, a per-`Acao` overlay resolver
5145 /// rejecting an added / renamed node against a cluster-local
5146 /// snapshot) was structurally forced to either re-inline the
5147 /// decompose dispatch in lockstep with the renderer-side wire-up
5148 /// (the duplication the PRIME DIRECTIVE names as a bug) or call
5149 /// the whole [`caixa_actions::validate`] renderer and pay the
5150 /// per-node accumulation to re-check one slot. Post-fold each such
5151 /// consumer reaches the decompose gate through one call on the
5152 /// substrate primitive.
5153 ///
5154 /// Peer to the [`crate::require_acao_view`] compound entry gate
5155 /// every per-`Acao` *renderer* routes through (which already folds
5156 /// the same `require_ci + decompose_ci` two-arm cascade behind its
5157 /// `require_kind` prelude) — this gate mirrors the same fold on
5158 /// the *layout* path, so the two consumers of the Acao-shape
5159 /// cascade (the author-time gate and every per-`Acao` renderer)
5160 /// share one substrate primitive rather than two open-coded
5161 /// cascades kept in lockstep. Same lift discipline the peer
5162 /// per-kind compound gates ([`Self::validate_aplicacao_shape`]
5163 /// 949a7a0, [`Self::validate_supervisor_shape`] 4c70105,
5164 /// [`Self::validate_upgrade_from`] d6801df, [`Self::validate_deps`]
5165 /// b5dd55e, [`Self::validate_limits`] baa4688,
5166 /// [`Self::validate_behavior`] 0d2877a) each carry. Closes the
5167 /// last per-kind asymmetry: with this lift the four typed
5168 /// named-caixa kinds (`Servico` / `Aplicacao` / `Supervisor` /
5169 /// `Acao`) each carry a compound per-`Caixa` shape gate on the
5170 /// substrate, and the layout pipeline routes through the same one
5171 /// substrate primitive per kind rather than four open-coded
5172 /// cascades.
5173 ///
5174 /// # Errors
5175 ///
5176 /// Returns the [`crate::CiDecomposeFailure`] typed view on the
5177 /// present-slot arm — the caixa's `:nome` alongside the borrowed
5178 /// [`canteiro_types::DecomposeError`] source (`DuplicateNode` /
5179 /// `UnknownDep` / `Cycle`) verbatim, so a consumer that fans on
5180 /// the specific arm reaches for `err.source` directly rather than
5181 /// re-parsing the Display bytes. Passes trivially on non-`Acao`
5182 /// kinds and on `:kind Acao` caixas with absent `:ci` (the fold's
5183 /// two identity-element arms).
5184 pub fn validate_acao_shape(&self) -> Result<(), crate::CiDecomposeFailure> {
5185 if !self.kind().is_acao() {
5186 return Ok(());
5187 }
5188 let Some(ci) = self.ci() else {
5189 return Ok(());
5190 };
5191 crate::render::decompose_ci(self, ci).map(|_| ())
5192 }
5193
5194 /// Compound per-`Caixa` kind ↔ typed-slot coherence gate on the
5195 /// three "declared but ignored" typed-slot families — M3 mesh
5196 /// (`:membros` / `:contratos` / `:politicas` / `:placement` /
5197 /// `:entrada`, owned by `:kind Aplicacao`, MESH-COMPOSITION §III.1),
5198 /// supervisor-tree (`:estrategia` / `:max-restarts` /
5199 /// `:restart-window` / `:children`, owned by `:kind Supervisor`,
5200 /// INSPIRATIONS §II.2), and M2 Servico-runtime (`:limits` /
5201 /// `:behavior` / `:upgrade-from`, owned by `:kind Servico`,
5202 /// INSPIRATIONS §III.1 / §II.3 / §II.4). Folds the three sibling
5203 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5204 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5205 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
5206 /// gates — each pre-lift a self-similar five-line
5207 /// `if !caixa.kind().is_<owner>() { let slots = caixa.declared_
5208 /// <family>_slots(); if !slots.is_empty() { return
5209 /// Err(LayoutError::<family>_on_non_<owner>(caixa, slots)); } }`
5210 /// block at [`crate::layout::StandardLayout::verify`] — onto one
5211 /// substrate primitive on [`Caixa`]. Every arm passes as an
5212 /// identity element on the owner kind (the paired
5213 /// [`Self::kind`] `is_<owner>()` guard short-circuits before the
5214 /// per-family `declared_*_slots` gate fires) and on non-owner
5215 /// kinds carrying no declared slot in that family (the
5216 /// [`Vec::is_empty`] check short-circuits before the wrap fires),
5217 /// so a bare no-code caixa on any kind passes the fold trivially
5218 /// on all three arms.
5219 ///
5220 /// Prior to this lift the three-arm cascade lived only wired
5221 /// open-coded at the layout wire-up site
5222 /// ([`crate::layout::StandardLayout::verify`], caixa-core/src/
5223 /// layout.rs) as three self-similar five-line blocks paired with
5224 /// three [`crate::LayoutError::mesh_slots_on_non_aplicacao`] /
5225 /// [`crate::LayoutError::supervisor_slots_on_non_supervisor`] /
5226 /// [`crate::LayoutError::servico_slots_on_non_servico`] ctor
5227 /// dispatches (each of which the peer
5228 /// [`crate::layout::layout_slot_kind_ctors!`] macro already folds
5229 /// onto one substrate primitive per typed variant, 0419438) —
5230 /// every future consumer that wanted to gate the whole
5231 /// kind-coherence cascade as a unit (the deferred
5232 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's admission
5233 /// webhook re-checking every typed-slot family after a per-slot
5234 /// patch, a future `feira validate --kind-coherence` per-caixa
5235 /// admission verb, a per-`Caixa` overlay resolver rejecting a
5236 /// kind-foreign patch against a cluster-local snapshot) was
5237 /// structurally forced to either re-inline the three-block
5238 /// cascade in lockstep with the layout wire-up (the duplication
5239 /// the PRIME DIRECTIVE names as a bug) or call the whole
5240 /// [`crate::layout::StandardLayout::verify`] pipeline and pay
5241 /// every peer per-`Caixa` gate to re-check three slot families.
5242 /// Post-fold each such consumer reaches the three-arm cascade
5243 /// through one call on the substrate primitive.
5244 ///
5245 /// Diagnostic order matches the pre-fold layout wire-up
5246 /// canonical sequence — mesh → supervisor → servico — pinned by
5247 /// the load-bearing
5248 /// `validate_kind_slot_coherence_mesh_arm_fires_before_supervisor_arm`
5249 /// / `_supervisor_arm_fires_before_servico_arm` ordering pins
5250 /// below. The three arms enumerate every typed-slot family the
5251 /// substrate carries whose "declared but ignored" footgun is
5252 /// gated at the layout altitude by a `{ caixa, kind, slots }`
5253 /// wrap variant — the peer
5254 /// [`crate::LayoutError::ForeignCodeSlot`] gate on the
5255 /// code-surface family sits outside this fold because
5256 /// [`Self::declared_foreign_code_slots`] bakes the kind-check
5257 /// into the helper (so the layout wire-up carries no outer
5258 /// `if !caixa.kind().is_<owner>()` guard), and the peer
5259 /// [`crate::LayoutError::CiOnNonAcao`] gate on the `:ci` axis
5260 /// carries a distinct `{ caixa, kind }` wrap shape (no `slots`
5261 /// field — `:ci` is a single `Option` not a `Vec`-of-named-slots)
5262 /// and rides on its own peer substrate primitive
5263 /// [`Self::validate_ci_kind_coherence`] (the direct sibling to
5264 /// this fold on the `:ci` axis) — the two folds share the same
5265 /// altitude and diagnostic order at the layout wire-up site but
5266 /// keep their distinct envelope shapes, so no consumer of
5267 /// `CiOnNonAcao` sees a variant rename.
5268 ///
5269 /// Peer to the per-kind compound entry gates every substrate
5270 /// primitive on the M2/M3 typed-slot family already carries
5271 /// ([`Self::validate_deps`] b5dd55e, [`Self::validate_limits`]
5272 /// baa4688, [`Self::validate_behavior`] 0d2877a,
5273 /// [`Self::validate_upgrade_from`] d6801df,
5274 /// [`Self::validate_aplicacao_shape`] 949a7a0,
5275 /// [`Self::validate_supervisor_shape`] 4c70105,
5276 /// [`Self::validate_acao_shape`] 5d6df54): the author-time gate
5277 /// axis on the *per-slot* algebra now shares one substrate
5278 /// primitive per compound gate, and this lift closes the
5279 /// symmetric axis on the *cross-family* kind ↔ slot coherence
5280 /// algebra so the layout pipeline routes the three self-similar
5281 /// gates through one substrate primitive rather than three
5282 /// open-coded blocks. Every future kind that adds its own
5283 /// exclusive typed-slot family (an `Actor`-owned per-virtual-
5284 /// actor grain slot the M5 Orleans-inspired kind reaches
5285 /// through, a per-Aplicacao overlay slot the M4 CR materializer
5286 /// consults) folds onto this compound gate as one arm addition
5287 /// rather than a fourth open-coded block at the wire-up site.
5288 ///
5289 /// # Errors
5290 ///
5291 /// Returns the first [`crate::LayoutError`] variant surfacing
5292 /// under the canonical mesh → supervisor → servico order:
5293 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] on a non-
5294 /// Aplicacao caixa with a declared M3 mesh slot,
5295 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] on a
5296 /// non-Supervisor caixa with a declared supervisor-tree slot,
5297 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] on a
5298 /// non-Servico caixa with a declared M2 slot. Passes trivially
5299 /// on the owner kind of each family and on non-owner kinds
5300 /// carrying no declared slot in that family (the fold's identity
5301 /// element on both axes).
5302 pub fn validate_kind_slot_coherence(&self) -> Result<(), crate::LayoutError> {
5303 // Each of the three arms routes through the shared
5304 // [`Self::run_kind_owned_slot_family_gate`] substrate primitive
5305 // — the outer non-owner-kind guard + inner accumulator + inner
5306 // emptiness-guard + wrap arm shape now lands on one dispatch
5307 // per family rather than a four-line open-coded block in
5308 // lockstep across all three arms. Canonical mesh → supervisor
5309 // → servico order preserved (the primitive short-circuits
5310 // arm-by-arm; the outer `?;` cascade at this altitude threads
5311 // the first surfaced arm's error verbatim). Each of the three
5312 // ctors ([`crate::LayoutError::mesh_slots_on_non_aplicacao`] /
5313 // [`crate::LayoutError::supervisor_slots_on_non_supervisor`] /
5314 // [`crate::LayoutError::servico_slots_on_non_servico`]) was
5315 // already lifted onto the substrate by the peer
5316 // [`crate::layout::layout_slot_kind_ctors!`] macro, so each arm
5317 // routes through the same substrate-canonical
5318 // `Self::<variant> { caixa, kind, slots }` wrap per arm as the
5319 // pre-lift open-coded blocks — byte-equal, pinned by the
5320 // paired `validate_kind_slot_coherence_folds_<family>_arm_matches_gate`
5321 // equivalence pins and the peer
5322 // `validate_kind_slot_coherence_{mesh,supervisor}_arm_fires_before_<next>_arm`
5323 // ordering pins.
5324 self.run_kind_owned_slot_family_gate(
5325 crate::CaixaKind::is_aplicacao,
5326 Caixa::declared_mesh_slots,
5327 crate::LayoutError::mesh_slots_on_non_aplicacao,
5328 )?;
5329 self.run_kind_owned_slot_family_gate(
5330 crate::CaixaKind::is_supervisor,
5331 Caixa::declared_supervisor_slots,
5332 crate::LayoutError::supervisor_slots_on_non_supervisor,
5333 )?;
5334 self.run_kind_owned_slot_family_gate(
5335 crate::CaixaKind::is_servico,
5336 Caixa::declared_servico_slots,
5337 crate::LayoutError::servico_slots_on_non_servico,
5338 )?;
5339 Ok(())
5340 }
5341
5342 /// Compound per-`Caixa` kind ↔ code-surface coherence gate on
5343 /// the three no-code kinds — `Supervisor` (supervises other
5344 /// caixas, INSPIRATIONS §II.2), `Aplicacao` (composes Servicos,
5345 /// MESH-COMPOSITION §III.1), and `Acao` (owns a typed CI run,
5346 /// CANTEIRO §7.1-C). Each carries no code of its own, so
5347 /// declaring any of `:bibliotecas` / `:exe` / `:servicos`
5348 /// silently passes the layout's path-existence loops (the paths
5349 /// still resolve on disk) and then vanishes downstream — the
5350 /// per-kind renderers gate emission on
5351 /// [`crate::render::require_kind`] and only emit the code
5352 /// surface for its owning kind, so a declared code slot on a
5353 /// no-code kind is the manifest field's documented "ignored
5354 /// otherwise" footgun.
5355 ///
5356 /// Pre-lift each of the three arms lived as a self-similar
5357 /// `if !caixa.kind().is_<no-code-kind>() { … } else if has_code
5358 /// { return Err(LayoutError::<kind>_owns_code(caixa)); }` block
5359 /// at [`crate::layout::StandardLayout::verify`] — three
5360 /// consumers, three identical shapes. Every future consumer
5361 /// that wanted to gate the whole code-surface coherence cascade
5362 /// as a unit (the deferred `caixa.pleme.io/v1alpha1/Caixa` CR
5363 /// materializer's admission webhook re-checking after a
5364 /// per-slot patch, a future `feira validate --no-code-kind`
5365 /// per-caixa admission verb, a per-`Caixa` overlay resolver
5366 /// rejecting a kind-foreign patch) was structurally forced to
5367 /// either re-inline the three-block cascade in lockstep with
5368 /// the layout wire-up (the duplication the PRIME DIRECTIVE
5369 /// names as a bug) or call the whole
5370 /// [`crate::layout::StandardLayout::verify`] pipeline. Post-fold
5371 /// each such consumer reaches the three-arm cascade through
5372 /// one call.
5373 ///
5374 /// Mirror of the sibling [`Self::validate_kind_slot_coherence`]
5375 /// fold (f0d286e) on the author-time typed-slot coherence axis:
5376 /// that gate closes the "non-owner kind declares owner-only
5377 /// typed slots" three-arm cascade on the M2 / supervisor-tree /
5378 /// M3 slot families; this gate closes the reciprocal
5379 /// "no-code kind declares code" three-arm cascade on the
5380 /// `:bibliotecas` / `:exe` / `:servicos` code surface. Together
5381 /// the two folds route every kind ↔ author-shape coherence
5382 /// diagnostic at the layout altitude through one substrate
5383 /// primitive per axis.
5384 ///
5385 /// The gate carries two identity elements:
5386 /// - **`has_code == false`** — any kind (including the three
5387 /// no-code kinds) that declares no code passes the paired
5388 /// `!has_code` short-circuit before every per-arm dispatch.
5389 /// - **Code-owning kinds** (`Biblioteca` owning
5390 /// `:bibliotecas`, `Binario` owning `:exe`, `Servico` owning
5391 /// `:servicos`) — the three no-code arm-firing predicates
5392 /// short-circuit on every code-owning kind, so the gate
5393 /// passes trivially regardless of what code they declare.
5394 /// Foreign-code-slot violations on a code-owning kind (e.g.
5395 /// `:kind Servico` declaring `:exe`) surface through the
5396 /// sibling [`crate::LayoutError::ForeignCodeSlot`] gate on
5397 /// [`Self::declared_foreign_code_slots`], not through this
5398 /// gate.
5399 ///
5400 /// Unlike the sibling cross-family
5401 /// [`Self::validate_kind_slot_coherence`], the three arms of
5402 /// this fold are mutually exclusive by construction — `:kind`
5403 /// is a single-valued [`CaixaKind`] discriminator so at most
5404 /// one arm can fire per caixa — and no cross-arm ordering pin
5405 /// is meaningful (the pre-fold three-block cascade at the
5406 /// wire-up site was already unreachable past the first
5407 /// matching arm).
5408 ///
5409 /// Peer to the per-kind compound entry gates every substrate
5410 /// primitive on the M2/M3 typed-slot family already carries
5411 /// ([`Self::validate_deps`] b5dd55e, [`Self::validate_limits`]
5412 /// baa4688, [`Self::validate_behavior`] 0d2877a,
5413 /// [`Self::validate_upgrade_from`] d6801df,
5414 /// [`Self::validate_aplicacao_shape`] 949a7a0,
5415 /// [`Self::validate_supervisor_shape`] 4c70105,
5416 /// [`Self::validate_acao_shape`] 5d6df54,
5417 /// [`Self::validate_kind_slot_coherence`] f0d286e): the
5418 /// author-time gate axis on the *per-slot* and *cross-family
5419 /// typed-slot* algebras each share one substrate primitive per
5420 /// compound gate, and this lift closes the third axis on the
5421 /// *code-surface* algebra so the layout pipeline routes all
5422 /// three coherence axes through one substrate primitive rather
5423 /// than nine open-coded blocks. Every future no-code kind
5424 /// (an `Actor` virtual-actor arm the M5 Orleans-inspired kind
5425 /// reaches through if it lands as a no-code composer, a future
5426 /// `Namespace` grouping kind) folds onto this compound gate
5427 /// as one arm addition rather than a fourth open-coded block
5428 /// at the wire-up site.
5429 ///
5430 /// # Errors
5431 ///
5432 /// Returns the [`crate::LayoutError`] variant naming the
5433 /// offending no-code kind:
5434 /// [`crate::LayoutError::SupervisorOwnsCode`] on a `:kind
5435 /// Supervisor` caixa with any declared code,
5436 /// [`crate::LayoutError::AplicacaoOwnsCode`] on a `:kind
5437 /// Aplicacao` caixa with any declared code,
5438 /// [`crate::LayoutError::AcaoOwnsCode`] on a `:kind Acao` caixa
5439 /// with any declared code. Passes trivially on every kind with
5440 /// no declared code and on every code-owning kind regardless
5441 /// of declared code (the fold's two identity-element arms).
5442 pub fn validate_no_code_kind_coherence(&self) -> Result<(), crate::LayoutError> {
5443 let has_code =
5444 !self.bibliotecas().is_empty() || !self.exe().is_empty() || !self.servicos().is_empty();
5445 if !has_code {
5446 return Ok(());
5447 }
5448 if self.kind().is_supervisor() {
5449 return Err(crate::LayoutError::supervisor_owns_code(self));
5450 }
5451 if self.kind().is_aplicacao() {
5452 return Err(crate::LayoutError::aplicacao_owns_code(self));
5453 }
5454 if self.kind().is_acao() {
5455 return Err(crate::LayoutError::acao_owns_code(self));
5456 }
5457 Ok(())
5458 }
5459
5460 /// Compound per-`Caixa` kind ↔ `:ci` coherence gate — the `Acao`
5461 /// axis-only companion to the sibling three-arm
5462 /// [`Self::validate_kind_slot_coherence`] fold (f0d286e) on the
5463 /// M3 mesh / supervisor-tree / M2 Servico-runtime typed-slot
5464 /// families. `:ci` carries a typed CI run
5465 /// ([`canteiro_types::CiRun`], CANTEIRO §7.1-C) that only the
5466 /// `caixa-actions` renderer decomposes + validates and only for a
5467 /// `:kind Acao`. On any *other* kind a declared `:ci` is the
5468 /// manifest field's documented "ignored otherwise" — it silently
5469 /// passes verify and then vanishes (never decomposed, never
5470 /// rendered), far from the source `caixa.lisp`.
5471 ///
5472 /// Pre-lift the arm lived as a self-similar
5473 /// `if caixa.ci().is_some() && !caixa.kind().is_acao() { return
5474 /// Err(LayoutError::CiOnNonAcao { caixa: caixa.nome().to_string(),
5475 /// kind: caixa.kind() }); }` block at
5476 /// [`crate::layout::StandardLayout::verify`] — one consumer today
5477 /// but every future consumer that wanted to gate the `:ci`
5478 /// coherence axis as a unit (the deferred
5479 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's admission
5480 /// webhook re-checking after a per-slot patch, a future
5481 /// `feira validate --ci-coherence` per-caixa admission verb, a
5482 /// per-`Caixa` overlay resolver rejecting a kind-foreign `:ci`
5483 /// patch) was structurally forced to either re-inline the
5484 /// two-condition guard in lockstep with the layout wire-up (the
5485 /// duplication the PRIME DIRECTIVE names as a bug) or call the
5486 /// whole [`crate::layout::StandardLayout::verify`] pipeline.
5487 /// Post-fold each such consumer reaches the arm through one call.
5488 ///
5489 /// Peer of the sibling three-arm
5490 /// [`Self::validate_kind_slot_coherence`] fold (f0d286e) — that
5491 /// gate carries the M3 mesh / supervisor-tree / M2 Servico-runtime
5492 /// axes under a uniform `{ caixa, kind, slots }` envelope
5493 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5494 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5495 /// [`crate::LayoutError::ServicoSlotsOnNonServico`]). The `:ci`
5496 /// axis stays on its own primitive because
5497 /// [`crate::LayoutError::CiOnNonAcao`] carries a distinct
5498 /// `{ caixa, kind }` wrap shape (no `slots` field — `:ci` is a
5499 /// single `Option` not a `Vec`-of-named-slots) whose reshape
5500 /// onto the sibling `{ caixa, kind, slots }` envelope would
5501 /// force a variant rename touching every consumer of
5502 /// `CiOnNonAcao`; the two folds share the same
5503 /// author-time-vs-renderer split and diagnostic altitude, and
5504 /// route through peer substrate primitives on the same
5505 /// [`Caixa`] surface.
5506 ///
5507 /// Peer to the per-kind compound entry gates every substrate
5508 /// primitive on the M2/M3 typed-slot family already carries
5509 /// ([`Self::validate_deps`] b5dd55e, [`Self::validate_limits`]
5510 /// baa4688, [`Self::validate_behavior`] 0d2877a,
5511 /// [`Self::validate_upgrade_from`] d6801df,
5512 /// [`Self::validate_aplicacao_shape`] 949a7a0,
5513 /// [`Self::validate_supervisor_shape`] 4c70105,
5514 /// [`Self::validate_acao_shape`] 5d6df54,
5515 /// [`Self::validate_kind_slot_coherence`] f0d286e,
5516 /// [`Self::validate_no_code_kind_coherence`] 3bbf6a2): every
5517 /// author-time coherence axis on the typed [`Caixa`] surface now
5518 /// routes through one substrate primitive per axis rather than
5519 /// an open-coded block at the layout wire-up site.
5520 ///
5521 /// The gate carries two identity elements:
5522 /// - **`ci().is_none()`** — a caixa that declares no `:ci`
5523 /// passes the first short-circuit before every per-arm
5524 /// dispatch, on every kind. The canonical shape of the four
5525 /// non-`Acao` kinds (`Biblioteca` / `Binario` / `Servico` /
5526 /// `Supervisor` / `Aplicacao`) is `ci = None` — the arm
5527 /// never fires on a well-shaped fixture.
5528 /// - **`:kind Acao`** — the owner-kind arm short-circuits on
5529 /// every `Acao` caixa regardless of its `:ci` shape; a
5530 /// malformed `:ci` on an `Acao` surfaces through the peer
5531 /// [`Self::validate_acao_shape`] compound decompose gate
5532 /// (5d6df54), not through this coherence gate.
5533 ///
5534 /// # Errors
5535 ///
5536 /// Returns [`crate::LayoutError::CiOnNonAcao`] naming the
5537 /// offending caixa's nome + kind on any non-`Acao` caixa with
5538 /// `:ci` declared. Passes trivially on every kind that declares
5539 /// no `:ci` and on every `:kind Acao` caixa regardless of
5540 /// declared `:ci` (the fold's two identity-element arms).
5541 pub fn validate_ci_kind_coherence(&self) -> Result<(), crate::LayoutError> {
5542 if self.ci().is_some() && !self.kind().is_acao() {
5543 return Err(crate::LayoutError::CiOnNonAcao {
5544 caixa: self.nome().to_string(),
5545 kind: self.kind(),
5546 });
5547 }
5548 Ok(())
5549 }
5550
5551 /// Compound per-`Caixa` kind ↔ code-surface coherence gate on the
5552 /// two exclusive code-surface slots — `:exe` (owned only by
5553 /// [`crate::CaixaKind::Binario`], the nix-built executable surface)
5554 /// and `:servicos` (owned only by [`crate::CaixaKind::Servico`],
5555 /// the wasm-component + `ComputeUnit` daemon surface). The
5556 /// `caixa-helm` / `caixa-flux` / `caixa-flake` renderers gate
5557 /// emission on [`crate::render::require_kind`]`(_, <owning-kind>)`
5558 /// and only emit the slot for its owning kind — so on any *other*
5559 /// code-running kind a declared `:exe` / `:servicos` is the
5560 /// manifest field's documented "ignored otherwise": the path is
5561 /// validated by the per-kind path-existence loops in
5562 /// [`crate::layout::StandardLayout::verify`], but the value is
5563 /// never rendered into a build target or programs.yaml entry —
5564 /// it silently passes `feira build` and then vanishes, far from
5565 /// the source `caixa.lisp`, with no field naming which slot is
5566 /// foreign.
5567 ///
5568 /// Pre-lift the arm lived as a self-similar four-line `let
5569 /// foreign_code_slots = caixa.declared_foreign_code_slots(); if
5570 /// !foreign_code_slots.is_empty() { return
5571 /// Err(LayoutError::foreign_code_slot(caixa, foreign_code_slots));
5572 /// }` block at [`crate::layout::StandardLayout::verify`] — one
5573 /// consumer today but every future consumer that wanted to gate
5574 /// the code-surface coherence axis as a unit (the deferred
5575 /// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's admission
5576 /// webhook re-checking after a per-slot patch, a future
5577 /// `feira validate --foreign-code` per-caixa admission verb, a
5578 /// per-`Caixa` overlay resolver rejecting a kind-foreign code-
5579 /// slot patch) was structurally forced to either re-inline the
5580 /// two-condition guard in lockstep with the layout wire-up (the
5581 /// duplication the PRIME DIRECTIVE names as a bug) or call the
5582 /// whole [`crate::layout::StandardLayout::verify`] pipeline.
5583 /// Post-fold each such consumer reaches the arm through one call.
5584 ///
5585 /// Peer of the sibling three-arm
5586 /// [`Self::validate_kind_slot_coherence`] fold (f0d286e) — that
5587 /// gate carries the M3 mesh / supervisor-tree / M2 Servico-runtime
5588 /// axes under the uniform `{ caixa, kind, slots }` envelope
5589 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5590 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5591 /// [`crate::LayoutError::ServicoSlotsOnNonServico`]); this gate
5592 /// carries the code-surface axis under the same
5593 /// `{ caixa, kind, slots }` envelope
5594 /// ([`crate::LayoutError::ForeignCodeSlot`]). The two folds share
5595 /// the envelope shape but stay separate primitives because the
5596 /// per-arm predicate differs: the cross-family fold rides on the
5597 /// outer `!self.kind().is_<owner>()` guard *paired* with a
5598 /// per-family `declared_<family>_slots` accumulator, while this
5599 /// fold's per-arm kind-check is baked into
5600 /// [`Self::declared_foreign_code_slots`] itself (each arm's
5601 /// `!self.kind().requires_<slot>()` guard fires inside the
5602 /// accumulator, not around it) — so a `:kind Binario` declaring
5603 /// `:servicos` and a `:kind Servico` declaring `:exe` are both
5604 /// caught by one accumulator sweep rather than by two independent
5605 /// arm dispatches. Peer with [`Self::validate_ci_kind_coherence`]
5606 /// (9b55beb) which carries the `:ci` axis on its own primitive
5607 /// for the same "distinct per-arm predicate shape, shared
5608 /// diagnostic altitude" reason.
5609 ///
5610 /// Peer to the per-kind and per-slot compound entry gates every
5611 /// substrate primitive on the M2/M3 typed-slot family already
5612 /// carries ([`Self::validate_deps`] b5dd55e,
5613 /// [`Self::validate_limits`] baa4688,
5614 /// [`Self::validate_behavior`] 0d2877a,
5615 /// [`Self::validate_upgrade_from`] d6801df,
5616 /// [`Self::validate_aplicacao_shape`] 949a7a0,
5617 /// [`Self::validate_supervisor_shape`] 4c70105,
5618 /// [`Self::validate_acao_shape`] 5d6df54,
5619 /// [`Self::validate_kind_slot_coherence`] f0d286e,
5620 /// [`Self::validate_no_code_kind_coherence`] 3bbf6a2,
5621 /// [`Self::validate_ci_kind_coherence`] 9b55beb): every
5622 /// author-time coherence axis on the typed [`Caixa`] surface now
5623 /// routes through one substrate primitive per axis rather than an
5624 /// open-coded block at the layout wire-up site. This closes the
5625 /// last open-coded kind ↔ slot coherence gate at the layout
5626 /// altitude — every kind-coherence diagnostic is now a substrate
5627 /// primitive.
5628 ///
5629 /// The gate carries three identity elements:
5630 /// - **Code-owning kinds on their native slot** — a
5631 /// [`crate::CaixaKind::Binario`] declaring `:exe`, a
5632 /// [`crate::CaixaKind::Servico`] declaring `:servicos` — each
5633 /// arm's `!requires_<slot>()` predicate short-circuits inside
5634 /// [`Self::declared_foreign_code_slots`], so the accumulator
5635 /// returns an empty `Vec` and the outer `is_empty` short-
5636 /// circuits before the wrap fires.
5637 /// - **Bare caixas** — a caixa with no declared code on any kind
5638 /// passes the same accumulator's `is_empty` short-circuit on
5639 /// every arm.
5640 /// - **No-code kinds** ([`crate::CaixaKind::Supervisor`] /
5641 /// [`crate::CaixaKind::Aplicacao`] / [`crate::CaixaKind::Acao`])
5642 /// declaring code — dominated upstream by the sibling
5643 /// [`Self::validate_no_code_kind_coherence`] (3bbf6a2) which
5644 /// surfaces [`crate::LayoutError::SupervisorOwnsCode`] /
5645 /// [`crate::LayoutError::AplicacaoOwnsCode`] /
5646 /// [`crate::LayoutError::AcaoOwnsCode`] first at the layout
5647 /// wire-up site, so this gate never fires on a no-code kind
5648 /// through the layout pipeline. A standalone caller reaching
5649 /// this primitive without the sibling `_no_code_` gate first
5650 /// would see a no-code kind's declared `:exe` / `:servicos`
5651 /// surface `ForeignCodeSlot` here (the two folds partition the
5652 /// diagnostic responsibility along the "declared no-code slot"
5653 /// axis: no-code kinds get `OwnsCode`, code-running kinds get
5654 /// `ForeignCodeSlot`), and the layout wire-up's canonical
5655 /// `_no_code_` → `_foreign_code_` ordering keeps the
5656 /// [`crate::LayoutError::SupervisorOwnsCode`] / … arm the one
5657 /// that surfaces in the composed pipeline.
5658 ///
5659 /// Diagnostic order within the arm matches the pre-fold layout
5660 /// wire-up canonical sequence — `:exe` → `:servicos` — pinned by
5661 /// [`Self::declared_foreign_code_slots`]'s per-arm push order.
5662 ///
5663 /// # Errors
5664 ///
5665 /// Returns [`crate::LayoutError::ForeignCodeSlot`] naming the
5666 /// offending caixa's nome + kind + declared foreign-code slot
5667 /// list on any code-running kind ([`crate::CaixaKind::Biblioteca`]
5668 /// / [`crate::CaixaKind::Binario`] / [`crate::CaixaKind::Servico`])
5669 /// declaring another code-running kind's exclusive code surface.
5670 /// Passes trivially on every native-slot declaration (Binario
5671 /// with `:exe`, Servico with `:servicos`), on every bare caixa,
5672 /// and on every no-code kind (dominated upstream by the sibling
5673 /// [`Self::validate_no_code_kind_coherence`] `OwnsCode` gates —
5674 /// see the identity-element notes above).
5675 pub fn validate_foreign_code_kind_coherence(&self) -> Result<(), crate::LayoutError> {
5676 let foreign_code_slots = self.declared_foreign_code_slots();
5677 if !foreign_code_slots.is_empty() {
5678 return Err(crate::LayoutError::foreign_code_slot(
5679 self,
5680 foreign_code_slots,
5681 ));
5682 }
5683 Ok(())
5684 }
5685
5686 /// Compound per-`Caixa` required-slot gate on the three
5687 /// [`crate::CaixaKind`] arms whose sole payload is a canonical
5688 /// typed slot: `Binario`'s `:exe`, `Servico`'s `:servicos`,
5689 /// `Acao`'s `:ci`. Each arm refuses a caixa on its owner kind
5690 /// that declares no value in the corresponding required slot,
5691 /// so `feira build` (the canonical author-time gate) surfaces the
5692 /// self-locating "this kind needs this slot" diagnostic at the
5693 /// source `caixa.lisp` rather than deferring the failure to a
5694 /// downstream consumer (a nix build with no `:exe` to build, a
5695 /// programs.yaml fan-out with no `:servicos` to enumerate, a
5696 /// `caixa-actions` decompose with no `:ci` to walk).
5697 ///
5698 /// Pre-lift each of the three arms lived as a self-similar
5699 /// `if caixa.kind().requires_<slot>() && caixa.<slot>().is_<empty>() {
5700 /// return Err(LayoutError::<kind>_without_<slot>(caixa)); }`
5701 /// block at [`crate::layout::StandardLayout::verify`] — three
5702 /// consumers, three identical shapes, one substrate primitive on
5703 /// [`Caixa`] closing the duplication the PRIME DIRECTIVE names as
5704 /// a bug. Each of the three inner ctors
5705 /// ([`crate::LayoutError::binario_without_exe`] /
5706 /// [`crate::LayoutError::servico_without_servicos`] /
5707 /// [`crate::LayoutError::missing_ci`]) was already lifted onto
5708 /// the substrate by the peer [`crate::layout::layout_nome_only_ctors!`]
5709 /// macro, so the primitive routes through the same
5710 /// `Self::<variant>(caixa.nome().to_string())` tuple-literal
5711 /// wrap per arm as the pre-lift open-coded blocks.
5712 ///
5713 /// The paired `Biblioteca`-arm required-slot check
5714 /// ([`crate::LayoutError::MissingLib`]) stays open-coded at the
5715 /// layout wire-up site by design: it needs the filesystem oracle
5716 /// on [`crate::layout::LayoutInvariants`] to check the default
5717 /// `lib/<nome>.lisp` fallback path, which the pure per-`Caixa`
5718 /// typed-shape surface this fold rides on has no reference to.
5719 /// Same posture the peer [`Self::validate_no_code_kind_coherence`]
5720 /// fold takes on the on-disk existence loops.
5721 ///
5722 /// Diagnostic order at the primitive matches the pre-fold layout
5723 /// wire-up canonical sequence — `:exe` → `:servicos` → `:ci` —
5724 /// the same three-arm sweep the peer [`crate::CaixaKind`]
5725 /// discriminator carries at its `requires_*` accessors. Unlike
5726 /// the sibling cross-family [`Self::validate_kind_slot_coherence`]
5727 /// fold, the three arms of this fold are mutually exclusive by
5728 /// construction — `:kind` is a single-valued [`crate::CaixaKind`]
5729 /// discriminator so at most one arm can fire per caixa — and no
5730 /// cross-arm ordering pin is meaningful (the pre-fold three-block
5731 /// cascade at the wire-up site was already unreachable past the
5732 /// first matching arm).
5733 ///
5734 /// Peer to the per-kind and per-slot compound entry gates every
5735 /// substrate primitive on the M2/M3 typed-slot family already
5736 /// carries ([`Self::validate_deps`] b5dd55e,
5737 /// [`Self::validate_limits`] baa4688,
5738 /// [`Self::validate_behavior`] 0d2877a,
5739 /// [`Self::validate_upgrade_from`] d6801df,
5740 /// [`Self::validate_aplicacao_shape`] 949a7a0,
5741 /// [`Self::validate_supervisor_shape`] 4c70105,
5742 /// [`Self::validate_acao_shape`] 5d6df54,
5743 /// [`Self::validate_kind_slot_coherence`] f0d286e,
5744 /// [`Self::validate_no_code_kind_coherence`] 3bbf6a2,
5745 /// [`Self::validate_ci_kind_coherence`] 9b55beb): every
5746 /// author-time coherence axis on the typed [`Caixa`] surface
5747 /// now routes through one substrate primitive per axis rather
5748 /// than an open-coded block at the layout wire-up site.
5749 ///
5750 /// The gate carries two identity elements:
5751 /// - **Non-owner kinds** — each per-arm predicate is
5752 /// `self.kind().requires_<slot>()`, which returns `true` only
5753 /// for the owning kind ([`crate::CaixaKind::Binario`] on `:exe`,
5754 /// [`crate::CaixaKind::Servico`] on `:servicos`,
5755 /// [`crate::CaixaKind::Acao`] on `:ci`). Every non-owner kind
5756 /// passes each per-arm dispatch trivially.
5757 /// - **Owner kinds with the required slot present** — a
5758 /// [`crate::CaixaKind::Binario`] with a non-empty `:exe`, a
5759 /// [`crate::CaixaKind::Servico`] with a non-empty `:servicos`,
5760 /// an [`crate::CaixaKind::Acao`] with `ci = Some(_)` — passes
5761 /// its arm's `is_empty` / `is_none` short-circuit.
5762 ///
5763 /// # Errors
5764 ///
5765 /// Returns the [`crate::LayoutError`] variant naming the
5766 /// offending owner kind:
5767 /// [`crate::LayoutError::BinarioWithoutExe`] on a
5768 /// [`crate::CaixaKind::Binario`] caixa with no declared `:exe`,
5769 /// [`crate::LayoutError::ServicoWithoutServicos`] on a
5770 /// [`crate::CaixaKind::Servico`] caixa with no declared
5771 /// `:servicos`, [`crate::LayoutError::MissingCi`] on a
5772 /// [`crate::CaixaKind::Acao`] caixa with no declared `:ci`.
5773 /// Passes trivially on every non-owner kind and on every owner
5774 /// kind with its required slot present.
5775 pub fn validate_required_kind_slot(&self) -> Result<(), crate::LayoutError> {
5776 if self.kind().requires_exe() && self.exe().is_empty() {
5777 return Err(crate::LayoutError::binario_without_exe(self));
5778 }
5779 if self.kind().requires_servicos() && self.servicos().is_empty() {
5780 return Err(crate::LayoutError::servico_without_servicos(self));
5781 }
5782 if self.kind().requires_ci() && self.ci().is_none() {
5783 return Err(crate::LayoutError::missing_ci(self));
5784 }
5785 Ok(())
5786 }
5787
5788 /// Reject per-entry values on the three Caixa-level code-surface
5789 /// path lists (`:bibliotecas`, `:exe`, `:servicos`) that the
5790 /// layout checker's `root.join(p)` sandbox would silently subvert.
5791 /// Same three structural footguns the peer
5792 /// [`BehaviorSpec::validate`] (b0c8389) and
5793 /// [`crate::UpgradeInstruction::validate`] `StateChange` arm
5794 /// (26da2c7) already close on the M2 `:behavior :on-*` and
5795 /// `:upgrade-from :state-change :script` axes, here lifted onto
5796 /// the three top-level code-path axes through the shared
5797 /// [`is_sandboxed_relative_path`] predicate:
5798 ///
5799 /// - empty entry (`(:bibliotecas (""))` / `(:exe (""))` /
5800 /// `(:servicos (""))`): `PathBuf::new()` round-trips through
5801 /// [`Path::join`] as the base itself — `root.join("")` ==
5802 /// `root`, so the existence check (`self.exists(&root)`)
5803 /// trivially passes (the project root exists), and the layout
5804 /// silently treats the project root as a biblioteca / exe /
5805 /// servico entry. The `:bibliotecas` loop then hands the root
5806 /// to `tatara_lisp::read` at `feira build` time as if the root
5807 /// directory itself were a Lisp source file — a parse error
5808 /// far from the source `caixa.lisp` with no field naming the
5809 /// offending entry.
5810 /// - absolute path (`(:bibliotecas ("/etc/passwd"))`):
5811 /// [`Path::join`] *replaces* the base when the right-hand side
5812 /// is absolute, so `root.join("/etc/passwd")` resolves to
5813 /// `"/etc/passwd"` and escapes the project sandbox entirely.
5814 /// The existence check then silently consults whatever the
5815 /// escaped path resolves to — for `:bibliotecas`, the layout
5816 /// has no `starts_with`-fence (only `:exe` is fenced under
5817 /// `exe/` and `:servicos` under `servicos/`), so an absolute
5818 /// `:bibliotecas` entry that happens to resolve on disk
5819 /// silently passes. For `:exe` / `:servicos` the fence catches
5820 /// the absolute case downstream as `ExeOutsideDir` /
5821 /// `ServicoOutsideDir` (or `MissingEntry` if the absolute path
5822 /// doesn't exist), but with a downstream-shaped diagnostic
5823 /// that names the resolved escape path rather than the
5824 /// authoring footgun at the source.
5825 /// - parent-escape (`(:bibliotecas ("../sibling/x.lisp"))` /
5826 /// `(:exe ("exe/../../escape.lisp"))`): a [`PathBuf`] with any
5827 /// [`std::path::Component::ParentDir`] anywhere round-trips
5828 /// through [`Path::join`] as a traversal above the caixa root.
5829 /// The `:exe` / `:servicos` `starts_with(<dir>)` fence is
5830 /// *component-aware* (not canonical-path-aware), so
5831 /// `root.join("exe/../../escape.lisp")` `starts_with(exe_dir)`
5832 /// is **true** even though the canonical resolution
5833 /// `{parent of root}/escape.lisp` lives outside the caixa root
5834 /// — the fence silently lets the parent-escape through, and
5835 /// the existence check passes if that escape-target happens
5836 /// to exist. Caught regardless of where the `..` sits
5837 /// (leading, mid-path, trailing) so the gate matches the peer
5838 /// predicate's full coverage.
5839 ///
5840 /// Same `Empty` → `Absolute` → `ParentEscape` arm-ordering every peer
5841 /// `is_sandboxed_relative_path` consumer follows (b0c8389 / 26da2c7);
5842 /// same per-slot diagnostic shape every peer per-axis path-gate
5843 /// exposes (`*Empty { slot }` / `*Absolute { slot, path }` /
5844 /// `*ParentEscape { slot, path }`). Cross-slot precedence is
5845 /// `:bibliotecas` → `:exe` → `:servicos` — the same declaration
5846 /// order [`Caixa::declared_foreign_code_slots`] uses for its
5847 /// canonical foreign-code-slot diagnostic, so a manifest with
5848 /// multiple malformed slots surfaces the lexicographically-earliest
5849 /// slot's diagnostic deterministically.
5850 ///
5851 /// Lifted to the typed surface as a Caixa-level validator (peer
5852 /// of [`Self::validate_nome`] / [`Self::validate_versao`] /
5853 /// [`Self::validate_deps`] / [`Self::validate_restart_window`])
5854 /// and wired into [`crate::StandardLayout::verify`] before the
5855 /// existence-check loops so the diagnostic names the offending
5856 /// slot at the source caixa.lisp rather than reporting a
5857 /// downstream `MissingEntry` / `ExeOutsideDir` /
5858 /// `ServicoOutsideDir` against the resolved sandbox-escape path.
5859 /// The fourth typed code-path surface — every author-supplied
5860 /// path on the manifest — is now structurally accept-shaped
5861 /// past validate, peer with `:behavior :on-*` and
5862 /// `:upgrade-from :state-change :script`.
5863 pub fn validate_code_paths(&self) -> Result<(), ManifestError> {
5864 /// Per-slot file-type contract for the three Caixa-level
5865 /// code-path surfaces (`:bibliotecas`, `:exe`, `:servicos`).
5866 /// Each variant names the predicate the per-entry file-type
5867 /// gate consults; [`Self::None`] opts the slot out of any
5868 /// file-type contract. Lifted as a typed local enum so the
5869 /// per-slot dispatch is exhaustive at the `match` — adding a
5870 /// future axis to the typed-substrate `:` slot set (the
5871 /// future `:assets` resource axis the M5 roadmap names, the
5872 /// future `:nix-flake` derivation axis the caixa-flake
5873 /// emitter consults) lands as one variant + one `match` arm,
5874 /// not a coordinated rewrite of every per-slot bool flag.
5875 ///
5876 /// Peer of the typed-substrate per-slot variant disciplines
5877 /// already established on this surface
5878 /// ([`crate::supervisor::RestartStrategy`] +
5879 /// [`crate::supervisor::RestartPolicy`] on the OTP-shape
5880 /// supervision-tree axis,
5881 /// [`crate::aplicacao::PlacementStrategy`] on the §III.1
5882 /// placement axis, [`crate::aplicacao::WitTarget`] on the
5883 /// `:contratos` payload-target axis): the typed `enum` is
5884 /// the substrate's single source of truth for the per-axis
5885 /// dispatch, and every consumer (the per-arm body here, the
5886 /// future feira-lint per-slot diagnostic renderer, the M4
5887 /// per-axis admission webhook) reaches for the same typed
5888 /// surface rather than re-deriving the partition from inline
5889 /// flag combinations.
5890 enum CodePathFileType {
5891 /// `:exe` — nix-build derivation output, no terminating-
5892 /// extension contract (the canonical `"exe/<name>"`
5893 /// fixtures the layout's `ExeOutsideDir` error message
5894 /// documents carry no extension by convention).
5895 None,
5896 /// `:bibliotecas` — tatara-lisp source files the
5897 /// `feira build` loop reads through `tatara_lisp::read`
5898 /// at parse time. Routes to [`is_lisp_extension`].
5899 LispSource,
5900 /// `:servicos` — ComputeUnit-CR YAML files the
5901 /// caixa-helm / caixa-flux renderers consume through
5902 /// `serde_yaml::from_str`. Routes to
5903 /// [`is_computeunit_yaml_extension`].
5904 ComputeUnitYaml,
5905 }
5906
5907 // The per-slot [`CodePathFileType`] selects which axes carry the
5908 // lifted file-type predicate. `:bibliotecas` is the tatara-lisp
5909 // source axis (the `feira build` loop at
5910 // `caixa-feira/src/cmd/build.rs:33` reads each entry through
5911 // `tatara_lisp::read` at parse time) — the lifted
5912 // [`is_lisp_extension`] predicate gates the `.lisp` extension.
5913 // `:exe` is the nix-built executable surface (per the canonical
5914 // `"exe/<name>"`-shaped fixtures the layout's `ExeOutsideDir`
5915 // error message documents and every in-tree
5916 // `caixa_with_code_paths` positive control uses) — its file-type
5917 // contract is "nix-build derivation output", not a typed source
5918 // file, so [`CodePathFileType::None`] opts the slot out of any
5919 // file-type gate. `:servicos` is the `.computeunit.yaml`
5920 // ComputeUnit-CR axis (the peer caixa-helm / caixa-flux
5921 // renderers consume each entry through `serde_yaml::from_str` as
5922 // a typed `ComputeUnit` CR) — the lifted
5923 // [`is_computeunit_yaml_extension`] predicate gates the compound
5924 // `.computeunit.yaml` suffix. All three axes are surfaced through
5925 // the same iteration so the sandbox-shape + duplicate gates
5926 // apply uniformly; the typed file-type dispatch fires per-slot
5927 // exactly where the downstream consumer's accepted set demands
5928 // it. The third file-type variant ([`ComputeUnitYaml`]) is the
5929 // compounding lift on the peer 64772a9 `:bibliotecas`
5930 // `.lisp`-gate trajectory — the second of the three code-path
5931 // axes to land on a typed compound-suffix gate, with the same
5932 // self-locating per-slot diagnostic shape every peer per-axis
5933 // file-type lift uses (`*NonLispExtension { slot, path }` /
5934 // `*NonComputeUnitYamlExtension { slot, path }`).
5935 for (slot, list, file_type) in [
5936 (
5937 ":bibliotecas",
5938 &self.bibliotecas,
5939 CodePathFileType::LispSource,
5940 ),
5941 (":exe", &self.exe, CodePathFileType::None),
5942 (
5943 ":servicos",
5944 &self.servicos,
5945 CodePathFileType::ComputeUnitYaml,
5946 ),
5947 ] {
5948 // Per-slot set-not-multiset gate on the typed code-path axis.
5949 // Every peer Vec-shaped author-supplied list past validate is
5950 // a set, not a multiset: `:membros :caixa`
5951 // ([`crate::AplicacaoError::MembroDuplicate`]), `:placement
5952 // :clusters` ([`crate::AplicacaoError::PlacementClusterDuplicate`]),
5953 // `:entrada :paths` ([`crate::AplicacaoError::EntradaPathDuplicate`]),
5954 // `:contratos` ([`crate::AplicacaoError::ContratoDuplicate`]),
5955 // `:children :caixa` ([`crate::SupervisorError::DuplicateChild`]),
5956 // `:deps` / `:deps-dev` `:nome` ([`crate::DepError::DuplicateNome`]
5957 // per 359fba5), `:upgrade-from :from` ([`crate::UpgradeError::DuplicateFrom`]),
5958 // `:etiquetas` ([`ManifestError::EtiquetaDuplicate`] per 360a499),
5959 // `:autores` ([`ManifestError::AutorDuplicate`] per 86c769b) —
5960 // the three code-path lists are the last Vec-shaped author-
5961 // supplied slots on the typed Caixa surface still admitting a
5962 // duplicate entry silently. Scope is per-list (`:bibliotecas`
5963 // duplicates are flagged within `:bibliotecas`, not across
5964 // `:bibliotecas` ↔ `:exe`) — the same per-list scope `:deps`
5965 // ↔ `:deps-dev` use (a `:nome` present in both lists is a
5966 // legitimate dev-vs-runtime shape on the dep axis, fenced
5967 // separately by [`crate::dep::validate_no_self_dep`]). On the
5968 // code-path axis a cross-slot collision is structurally
5969 // impossible by the layout's `starts_with(<exe|servicos>_dir)`
5970 // fence — `:exe` and `:servicos` entries are confined to their
5971 // own directory trees, so the only way a string could appear
5972 // on two code-path lists is the (rare, structurally invalid)
5973 // case where `:bibliotecas` carries an `"exe/<x>"` or
5974 // `"servicos/<x>.yaml"`-shaped path.
5975 //
5976 // Without the gate three authoring footguns silently passed:
5977 //
5978 // - `:bibliotecas ("lib/foo.lisp" "lib/foo.lisp")` — the
5979 // canonical copy-paste-the-wrong-file footgun. `feira
5980 // build` (`caixa-feira/src/cmd/build.rs:33`) walks the
5981 // list and re-parses the same file twice, wasting work
5982 // and silently masking the author's intent to declare a
5983 // *second* biblioteca.
5984 // - `:exe ("exe/cli" "exe/cli")` — the same footgun on the
5985 // Binario surface. The future `caixa-flake` `nix flake`
5986 // emitter that materializes each `:exe` entry as a flake
5987 // `packages.<exe-name>` derivation would collide on the
5988 // duplicate package name and surface a flake-eval error
5989 // far from the source `caixa.lisp`.
5990 // - `:servicos ("servicos/x.computeunit.yaml"
5991 // "servicos/x.computeunit.yaml")` — the same footgun on
5992 // the Servico surface. The peer `caixa-helm` / `caixa-flux`
5993 // renderers already refuse `:servicos.len() != 1` with
5994 // the narrower [`UnsupportedServicoCount`] diagnostic, but
5995 // that diagnostic surfaces "too many servicos" without
5996 // naming "duplicate entry" — the typed self-locating
5997 // "which entry is the duplicate" framing only lands at
5998 // this gate.
5999 //
6000 // Same `seen.insert(entry.as_str())` shape every peer per-list
6001 // duplicate gate uses (`:etiquetas` 360a499, `:autores`
6002 // 86c769b, `:deps` 359fba5) and the same "structural shape
6003 // checks fire before the duplicate check on the same entry"
6004 // ordering (a `(:bibliotecas ("" "lib/x.lisp" "lib/x.lisp"))`
6005 // shape surfaces the narrower [`Self::CodePathEmpty`] for the
6006 // empty entry first, not the duplicate on the later pair).
6007 let mut seen = std::collections::HashSet::new();
6008 for entry in list {
6009 let path = Path::new(entry);
6010 match is_sandboxed_relative_path(path) {
6011 Ok(()) => {}
6012 Err(PathShapeViolation::Empty) => {
6013 return Err(ManifestError::code_path_empty(slot));
6014 }
6015 Err(PathShapeViolation::Absolute) => {
6016 return Err(ManifestError::code_path_absolute(slot, path));
6017 }
6018 Err(PathShapeViolation::ParentEscape) => {
6019 return Err(ManifestError::code_path_parent_escape(slot, path));
6020 }
6021 }
6022 // The per-slot file-type gate dispatched through the
6023 // typed [`CodePathFileType`] selector above. Each variant
6024 // routes to the lifted predicate the downstream consumer
6025 // demands:
6026 //
6027 // - [`LispSource`] → [`is_lisp_extension`] for
6028 // `:bibliotecas` (the `feira build` loop's
6029 // `tatara_lisp::read` consumer);
6030 // - [`ComputeUnitYaml`] → [`is_computeunit_yaml_extension`]
6031 // for `:servicos` (the caixa-helm / caixa-flux
6032 // `serde_yaml::from_str` consumer's `ComputeUnit` CR
6033 // accepted set);
6034 // - [`None`] for `:exe` — the nix-build derivation-
6035 // output axis has no terminating-extension contract.
6036 //
6037 // Fires after the sandbox-shape arms so a path that is
6038 // *both* sandbox-escaping and wrong-extension surfaces
6039 // the more fundamental sandbox-shape diagnostic first
6040 // (mirrors the peer `EmptyPath` → `AbsolutePath` →
6041 // `ParentEscape` → `NonLispExtension` arm-ordering on
6042 // `:behavior :on-*` c97815a, and `EmptyScript` →
6043 // `AbsoluteScript` → `ParentEscapeScript` →
6044 // `NonLispExtensionScript` on
6045 // `:upgrade-from :state-change :script` 33cc830), and
6046 // before the duplicate gate so the narrower per-entry
6047 // file-type shape dominates the cross-entry uniqueness
6048 // diagnostic (a
6049 // `("servicos/x.yaml" "servicos/x.yaml")` shape on
6050 // `:servicos` surfaces
6051 // `CodePathNonComputeUnitYamlExtension` on the first
6052 // entry rather than `CodePathDuplicate` on the pair —
6053 // peer with the 64772a9 `:bibliotecas`
6054 // `("lib/x.txt" "lib/x.txt")` ordering).
6055 match file_type {
6056 CodePathFileType::None => {}
6057 CodePathFileType::LispSource => {
6058 if !is_lisp_extension(path) {
6059 return Err(ManifestError::code_path_non_lisp_extension(slot, path));
6060 }
6061 }
6062 CodePathFileType::ComputeUnitYaml => {
6063 if !is_computeunit_yaml_extension(path) {
6064 return Err(ManifestError::code_path_non_computeunit_yaml_extension(
6065 slot, path,
6066 ));
6067 }
6068 }
6069 }
6070 crate::render::insert_first_seen(&mut seen, entry.as_str(), || {
6071 ManifestError::code_path_duplicate(slot, path)
6072 })?;
6073 }
6074 }
6075 Ok(())
6076 }
6077
6078 /// Reject `:etiquetas` lists with an empty entry or with two entries
6079 /// agreeing on the same string. `:etiquetas` is the universal
6080 /// registry-search-tag axis on [`Caixa`] (every kind carries the
6081 /// `Vec<String>` slot) and lands verbatim as the Helm chart
6082 /// `Chart.yaml` `keywords:` array on every Servico (caixa-helm's
6083 /// `build_chart_yaml` at `caixa-helm/src/lib.rs:236` folds it through
6084 /// a [`std::collections::BTreeSet`] alongside the four substrate-
6085 /// fixed tags `lareira` / `wasm` / `tatara-lisp` / `caixa-servico`).
6086 /// Two authoring footguns silently passed validate without this gate:
6087 ///
6088 /// - Empty entry (`(:etiquetas (""))` — the canonical paste-from-
6089 /// blank-doc footgun) rendered as `keywords: ["", "caixa-servico",
6090 /// "lareira", "tatara-lisp", "wasm"]` in `Chart.yaml`. Helm's
6091 /// `chart.metadata.keywords` admits the value without a strict
6092 /// parser-side gate, but the empty keyword has no operational
6093 /// meaning — it indexes nothing in the future caixa-registry
6094 /// search axis and clutters the rendered chart with a no-op tag.
6095 /// - Duplicate entries (`(:etiquetas ("demo" "demo"))` — the
6096 /// copy-paste-the-wrong-tag footgun) silently passed validate
6097 /// and were silently dedup'd by caixa-helm's `BTreeSet` collect
6098 /// at chart render — a "second wins / one silently disappears"
6099 /// shape divergent from every peer typed-graph set gate
6100 /// ([`crate::AplicacaoError::MembroDuplicate`] on `:membros`,
6101 /// [`crate::AplicacaoError::PlacementClusterDuplicate`] on
6102 /// `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
6103 /// on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
6104 /// on `:contratos`, [`crate::DepError::DuplicateNome`] on
6105 /// `:deps` / `:deps-dev` per 359fba5, [`crate::UpgradeError::DuplicateFrom`]
6106 /// on `:upgrade-from`, the per-instruction-class singularity
6107 /// gates [`crate::UpgradeError::DuplicateLoadModule`] /
6108 /// [`crate::UpgradeError::DuplicateStateChange`] /
6109 /// [`crate::UpgradeError::DuplicateCleanup`]). The typed-graph
6110 /// discipline is uniform: every Vec-shaped author-supplied list
6111 /// past validate is set-not-multiset, by construction.
6112 ///
6113 /// Past the empty arm the gate enforces the chart-keyword shape
6114 /// predicate via [`crate::render::is_chart_keyword_shape`]: Cargo's
6115 /// crates.io `[package] keywords` grammar — 1..=20 bytes, starts
6116 /// with an ASCII letter, ASCII alphanumeric / `_` / `-`
6117 /// continuation. Closes the canonical paste-from-doc footguns the
6118 /// bare empty + duplicate arms left open: paste-from-aligned-doc
6119 /// whitespace (`" mesh"`, `"mesh "`), paste-from-multiline-doc
6120 /// newline (`"mesh\nhttp"` — the author pasted a multi-tag block
6121 /// into one entry instead of splitting), paste-from-Windows-CRLF-doc
6122 /// carriage return, CSV-list-separator confusion (`"mesh,http,grpc"`
6123 /// — the author meant three separate list entries), path-separator
6124 /// confusion (`"caixa/servico"`), namespace-suffix (`"http.1"`),
6125 /// leading-digit (`"1foo"`), kebab-leak (`"-foo"`), snake-leak
6126 /// (`"_foo"`), non-ASCII (`"café"`), and paste-from-binary-blob
6127 /// control bytes that would silently land as malformed search tags
6128 /// in the rendered Chart.yaml `keywords:` array and break the
6129 /// Artifact Hub keyword index lookup far from the source caixa.lisp.
6130 /// Mirrors the [`Self::validate_autores`] shape-predicate cascade
6131 /// established on the sibling universal-axis `Vec<String>` surface
6132 /// — the second universal-axis Vec<String> surface to land the
6133 /// empty-first-then-shape-then-duplicate per-entry cascade.
6134 ///
6135 /// Same empty-first cascade discipline every peer per-axis gate
6136 /// uses: the per-entry empty arm fires before the per-entry shape
6137 /// arm fires before the cross-entry duplicate arm, so an
6138 /// `("" "mesh" "mesh")` authoring shape surfaces the narrower
6139 /// [`ManifestError::EtiquetaEmpty`] (the structural "this entry
6140 /// has no value" defect) before either the shape or the duplicate
6141 /// diagnostic. Walks the list in declaration order so the
6142 /// first-collision diagnostic surfaces the lexicographically-
6143 /// earliest offending position, peer with every other duplicate
6144 /// gate on this surface.
6145 ///
6146 /// Universal-axis (every kind carries `:etiquetas`), so wired at the
6147 /// caixa-build gate alongside the peer universal gates
6148 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6149 /// [`Self::validate_deps`] / [`Self::validate_code_paths`] — before
6150 /// the kind-coherence gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]
6151 /// / [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6152 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6153 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
6154 /// slot sets. The future caixa-registry search axis can reach for
6155 /// `caixa.etiquetas` knowing every entry is a non-empty distinct
6156 /// chart-keyword-shaped string without re-deriving the precondition.
6157 pub fn validate_etiquetas(&self) -> Result<(), ManifestError> {
6158 let mut seen = std::collections::HashSet::new();
6159 for etiqueta in self.etiquetas() {
6160 if etiqueta.is_empty() {
6161 return Err(ManifestError::EtiquetaEmpty);
6162 }
6163 crate::render::is_chart_keyword_shape(etiqueta)
6164 .map_err(|reason| ManifestError::etiqueta_invalid(etiqueta, reason))?;
6165 crate::render::insert_first_seen(&mut seen, etiqueta.as_str(), || {
6166 ManifestError::etiqueta_duplicate(etiqueta)
6167 })?;
6168 }
6169 Ok(())
6170 }
6171
6172 /// Reject `:autores` lists with an empty entry or with two entries
6173 /// agreeing on the same string. `:autores` is the universal
6174 /// maintainer-axis on [`Caixa`] (every kind carries the
6175 /// `Vec<String>` slot) and lands verbatim as the Helm chart
6176 /// `Chart.yaml` `maintainers:` array on every Servico (caixa-helm's
6177 /// `build_chart_yaml` at `caixa-helm/src/lib.rs:251` maps each entry
6178 /// to a `Maintainer { name, email: None }` without dedup). Two
6179 /// authoring footguns silently passed validate without this gate:
6180 ///
6181 /// - Empty entry (`(:autores (""))` — the canonical paste-from-
6182 /// blank-doc footgun) rendered as
6183 /// `maintainers: [{name: "", email: null}]` in `Chart.yaml`. The
6184 /// empty maintainer name has no operational meaning — it
6185 /// identifies no one in the substrate's authorship index and
6186 /// clutters the rendered chart with a no-op maintainer.
6187 /// - Duplicate entries (`(:autores ("pleme-io" "pleme-io"))` —
6188 /// the copy-paste-the-wrong-author footgun) silently passed
6189 /// validate and rendered as two identical maintainer entries.
6190 /// Unlike the [`Self::validate_etiquetas`] peer (caixa-helm's
6191 /// `BTreeSet`-collect on `:etiquetas` silently dedups the
6192 /// rendered `keywords:` array at chart-render time), the
6193 /// `maintainers:` rendering has *no* dedup — duplicate `:autores`
6194 /// entries stack verbatim in the chart, divergent from every
6195 /// peer typed-graph set gate ([`crate::AplicacaoError::MembroDuplicate`]
6196 /// on `:membros`, [`crate::AplicacaoError::PlacementClusterDuplicate`]
6197 /// on `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
6198 /// on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
6199 /// on `:contratos`, [`crate::DepError::DuplicateNome`] on
6200 /// `:deps` / `:deps-dev`, [`crate::UpgradeError::DuplicateFrom`]
6201 /// on `:upgrade-from`, [`ManifestError::EtiquetaDuplicate`] on
6202 /// `:etiquetas`).
6203 ///
6204 /// Past the empty arm the gate enforces the chart-maintainer-name
6205 /// shape predicate via [`crate::render::is_chart_maintainer_name_shape`]:
6206 /// the structural single-line printable-UTF-8 floor every realistic
6207 /// Helm chart maintainer name carries — 1..=128 bytes, no leading
6208 /// or trailing whitespace, no ASCII control characters anywhere,
6209 /// Unicode bytes accepted. Closes the canonical paste-from-doc
6210 /// footguns the bare empty + duplicate arms left open:
6211 /// paste-from-aligned-doc whitespace (`" pleme-io"`, `"pleme-io "`),
6212 /// paste-from-multiline-doc newline (`"alice\nbob"` — the author
6213 /// pasted a multi-line block of author records into one `:autores`
6214 /// entry instead of splitting into one entry per author),
6215 /// paste-from-Windows-CRLF-doc carriage return, tab-from-aligned-doc,
6216 /// and the paste-from-binary-blob control bytes that would silently
6217 /// land as YAML-illegal byte sequences in the rendered Chart.yaml
6218 /// `maintainers:` array. Mirrors the shape-predicate cascade
6219 /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
6220 /// [`Self::validate_edicao`] / [`Self::validate_repositorio`]
6221 /// establish past their own empty arms on the sibling universal-axis
6222 /// `Option<String>` surfaces — the first universal-axis Vec<String>
6223 /// surface to land the empty-first-then-shape-then-duplicate per-entry
6224 /// cascade.
6225 ///
6226 /// Same empty-first cascade discipline every peer per-axis gate
6227 /// uses: the per-entry empty arm fires before the per-entry shape
6228 /// arm before the cross-entry duplicate arm. Walks the list in
6229 /// declaration order so the first-collision diagnostic surfaces the
6230 /// lexicographically-earliest offending position, peer with every
6231 /// other duplicate gate on this surface.
6232 ///
6233 /// Universal-axis (every kind carries `:autores`), so wired at the
6234 /// caixa-build gate alongside the peer universal gates
6235 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6236 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
6237 /// [`Self::validate_code_paths`] — before the kind-coherence gates
6238 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
6239 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6240 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6241 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
6242 /// slot sets.
6243 pub fn validate_autores(&self) -> Result<(), ManifestError> {
6244 let mut seen = std::collections::HashSet::new();
6245 for autor in self.autores() {
6246 if autor.is_empty() {
6247 return Err(ManifestError::AutorEmpty);
6248 }
6249 crate::render::is_chart_maintainer_name_shape(autor)
6250 .map_err(|reason| ManifestError::autor_invalid(autor, reason))?;
6251 crate::render::insert_first_seen(&mut seen, autor.as_str(), || {
6252 ManifestError::autor_duplicate(autor)
6253 })?;
6254 }
6255 Ok(())
6256 }
6257
6258 /// Reject `:repositorio` values whose shape the shared
6259 /// [`crate::render::is_git_repo_url`] predicate refuses. The flat
6260 /// `repositorio: Option<String>` slot on [`Caixa`] is the
6261 /// universal git-shaped homepage axis every kind carries — the
6262 /// substrate routes the same string through two load-bearing
6263 /// consumers:
6264 ///
6265 /// - [`caixa-helm`] folds it verbatim into the rendered
6266 /// `lareira-<nome>` Helm chart's `Chart.yaml` `home:` field
6267 /// (`build_chart_yaml` at `caixa-helm/src/lib.rs:268`) and into
6268 /// the chart `README.md` `repo = …` interpolation
6269 /// (`caixa-helm/src/lib.rs:359`).
6270 /// - [`caixa-flux`] folds it verbatim into the standalone
6271 /// `ClusterBundleOpts::for_caixa` `git_url:` field
6272 /// (`caixa-flux/src/lib.rs:293`), which becomes the `FluxCD`
6273 /// `GitRepository.spec.url` the cluster's source-controller
6274 /// polls — the load-bearing deploy-time axis.
6275 ///
6276 /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
6277 /// substitute a placeholder when the slot is absent (`None` → the
6278 /// fallback fires); a `Some("")` *skips the fallback* and silently
6279 /// passes the empty string through to `Chart.yaml home: ""` /
6280 /// `GitRepository url: ""` — Helm's chart lint and `FluxCD`'s source
6281 /// controller both reject the empty URL far from the source
6282 /// `caixa.lisp`, with no field naming the offending `:repositorio`.
6283 /// Similarly a malformed `:repositorio` (whitespace, control char,
6284 /// missing `:` separator, leading `-`) silently lands in the
6285 /// rendered artifacts and breaks at `git clone` / `helm template`
6286 /// / `flux reconcile` time.
6287 ///
6288 /// Thin wrapper around [`crate::render::is_git_repo_url`] — the
6289 /// same shared predicate the peer [`crate::DepSource::validate`]
6290 /// routes the `:fonte (:tipo git :repo …)` axis through. With this
6291 /// gate the two `git URL`-shaped surfaces on the typed Caixa
6292 /// (`:repositorio` here, `:deps :fonte :repo` peer) are
6293 /// structurally equivalent: every value past validate is
6294 /// guaranteed-acceptable by the predicate's union of constraints
6295 /// (non-empty, length-bounded, no leading `-`, no whitespace, no
6296 /// control chars, ASCII only, no leading `:`, contains a `:`
6297 /// separator). The predicate accepts every documented authoring
6298 /// shape — `github:org/repo` shorthand, `https://host/path`,
6299 /// `ssh://[user@]host/path`, `git://host/path`, `git@host:path`
6300 /// scp-style SSH, `file:///path` — and refuses the canonical
6301 /// paste-from-blank-doc / paste-from-multiline-doc / CLI-arg-
6302 /// injection footguns at validate time. Maps the predicate's
6303 /// `String` reason verbatim into the
6304 /// [`ManifestError::RepositorioInvalid`] variant, carrying the
6305 /// offending value + parser-shaped reason so the diagnostic is
6306 /// self-locating (the author can grep their `caixa.lisp` for
6307 /// `:repositorio "<value>"` and fix it in one edit).
6308 ///
6309 /// `None` (the canonical "omit the slot to express no published
6310 /// homepage" shape) is accepted trivially — the gate is a no-op
6311 /// when the author didn't declare a value. `Some("")` is gated by
6312 /// the narrower [`ManifestError::RepositorioEmpty`] arm before the
6313 /// shape predicate is consulted, mirroring the empty-first cascade
6314 /// every peer per-axis identity gate uses
6315 /// ([`ManifestError::NomeEmpty`] → [`ManifestError::NomeInvalid`],
6316 /// [`ManifestError::VersaoEmpty`] → [`ManifestError::VersaoInvalid`],
6317 /// [`crate::DepError::FonteRepoEmpty`] →
6318 /// [`crate::DepError::FonteRepoInvalid`]).
6319 ///
6320 /// Universal-axis (every kind carries `:repositorio`), so wired at
6321 /// the caixa-build gate alongside the peer universal gates
6322 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6323 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
6324 /// [`Self::validate_autores`] / [`Self::validate_code_paths`] —
6325 /// before the kind-coherence gates
6326 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
6327 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6328 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6329 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
6330 /// specific slot sets.
6331 pub fn validate_repositorio(&self) -> Result<(), ManifestError> {
6332 let Some(s) = self.repositorio() else {
6333 return Ok(());
6334 };
6335 if s.is_empty() {
6336 return Err(ManifestError::RepositorioEmpty);
6337 }
6338 is_git_repo_url(s).map_err(|reason| ManifestError::repositorio_invalid(s, reason))
6339 }
6340
6341 /// Reject `:descricao` values that are the empty string. The flat
6342 /// `descricao: Option<String>` slot on [`Caixa`] is the universal
6343 /// free-form-prose homepage axis every kind carries — the
6344 /// substrate routes the same string through two load-bearing
6345 /// consumers in the [`caixa-helm`] renderer:
6346 ///
6347 /// - `build_chart_yaml` folds it verbatim into the rendered
6348 /// `lareira-<nome>` Helm chart's `Chart.yaml` `description:`
6349 /// field (`caixa-helm/src/lib.rs:232-235`).
6350 /// - `build_readme` folds it verbatim into the rendered chart
6351 /// `README.md` header (`caixa-helm/src/lib.rs:333-336`).
6352 ///
6353 /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
6354 /// substitute a `caixa.nome`-derived placeholder when the slot is
6355 /// absent (`None` → the fallback fires); a `Some("")` *skips the
6356 /// fallback* and silently passes the empty string through to
6357 /// `Chart.yaml description: ""` / a blank chart `README.md`
6358 /// header. Helm's chart spec requires a non-empty `description:`
6359 /// field on `apiVersion: v2` charts (`helm lint` surfaces it as
6360 /// `WARNING [chart.metadata.description]: description is required`),
6361 /// so the empty `Some("")` silently lands in the rendered
6362 /// artifacts and breaks at `helm lint` / `helm install` time far
6363 /// from the source `caixa.lisp`, with no field naming the
6364 /// offending `:descricao`.
6365 ///
6366 /// `None` (the canonical "omit the slot to defer to the renderer's
6367 /// `caixa.nome`-derived fallback" shape) is accepted trivially —
6368 /// the gate is a no-op when the author didn't declare a value.
6369 /// `Some("")` is gated by the narrower
6370 /// [`ManifestError::DescricaoEmpty`] arm, mirroring the empty-arm
6371 /// shape every peer per-axis empty gate uses
6372 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
6373 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
6374 /// [`ManifestError::RepositorioEmpty`]).
6375 ///
6376 /// Universal-axis (every kind carries `:descricao`), so wired at
6377 /// the caixa-build gate alongside the peer universal gates
6378 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6379 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
6380 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
6381 /// [`Self::validate_code_paths`] — before the kind-coherence
6382 /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
6383 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6384 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6385 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
6386 /// specific slot sets.
6387 ///
6388 /// Past the empty arm the gate enforces the chart-description
6389 /// shape predicate via [`crate::render::is_chart_description_shape`]:
6390 /// the structural single-line UTF-8 floor every realistic chart
6391 /// description in the wild matches — 1..=512 bytes, no leading
6392 /// or trailing whitespace, no ASCII control characters anywhere
6393 /// (`0x00..=0x1F` plus `0x7F` DEL — banning tab, newline,
6394 /// carriage return, and every other control byte), Unicode
6395 /// continuation bytes accepted (the canonical fixtures carry
6396 /// `→` and `—`). Closes the canonical paste-from-doc footguns
6397 /// the bare empty-arm gate left open: paste-from-aligned-doc
6398 /// leading / trailing whitespace (`" Checkout flow."`,
6399 /// `"Checkout flow. "`), paste-from-multiline-doc newline
6400 /// (`"Checkout\nflow."`), paste-from-Windows-CRLF-doc CR
6401 /// (`"Checkout\rflow."`), tab-from-aligned-doc
6402 /// (`"Checkout\tflow."`), and paste-from-binary-blob NUL / BEL /
6403 /// ESC / DEL bytes. Mirrors the shape-predicate cascade
6404 /// [`Self::validate_repositorio`] / [`Self::validate_licenca`] /
6405 /// [`Self::validate_edicao`] establish past their own empty arms
6406 /// on the sibling universal-axis `Option<String>` Caixa-level
6407 /// value-shape surfaces.
6408 ///
6409 /// The empty-first cascade discipline mirrors every peer per-axis
6410 /// identity gate: [`ManifestError::DescricaoEmpty`] runs before
6411 /// [`ManifestError::DescricaoInvalid`], so the narrower empty
6412 /// diagnostic surfaces on `Some("")` rather than the broader
6413 /// shape-predicate diagnostic — peer with how
6414 /// [`ManifestError::LicencaEmpty`] runs before
6415 /// [`ManifestError::LicencaInvalid`],
6416 /// [`ManifestError::EdicaoEmpty`] runs before
6417 /// [`ManifestError::EdicaoInvalid`],
6418 /// [`ManifestError::RepositorioEmpty`] runs before
6419 /// [`ManifestError::RepositorioInvalid`].
6420 pub fn validate_descricao(&self) -> Result<(), ManifestError> {
6421 let Some(s) = self.descricao() else {
6422 return Ok(());
6423 };
6424 if s.is_empty() {
6425 return Err(ManifestError::DescricaoEmpty);
6426 }
6427 crate::render::is_chart_description_shape(s)
6428 .map_err(|reason| ManifestError::descricao_invalid(s, reason))?;
6429 Ok(())
6430 }
6431
6432 /// Reject `:licenca` values that are the empty string. The flat
6433 /// `licenca: Option<String>` slot on [`Caixa`] is the universal
6434 /// SPDX-shaped license-expression axis every kind carries — the
6435 /// substrate routes the same string through the [`caixa-helm`]
6436 /// renderer's `build_readme` which folds it verbatim into the
6437 /// rendered `lareira-<nome>` Helm chart's `README.md` `## License`
6438 /// section (`caixa-helm/src/lib.rs:361`) via
6439 /// `caixa.licenca.clone().unwrap_or_else(|| "MIT".into())`. The
6440 /// fallback only fires on `None`; a `Some("")` *skips the
6441 /// fallback* and silently passes the empty string through to a
6442 /// chart `README.md` whose `License` section renders as the bare
6443 /// trailing period (`.\n`) — peer footgun with the
6444 /// `Some("")`-skips-`unwrap_or_else` shape the
6445 /// [`Self::validate_descricao`] and [`Self::validate_repositorio`]
6446 /// gates close on the sibling free-form-prose and git-URL axes.
6447 ///
6448 /// `None` (the canonical "omit the slot to defer to the
6449 /// renderer's `MIT` fallback" shape every existing fixture
6450 /// carries) is accepted trivially — the gate is a no-op when the
6451 /// author didn't declare a value. `Some("")` is gated by the
6452 /// narrower [`ManifestError::LicencaEmpty`] arm, mirroring the
6453 /// empty-arm shape every peer per-axis empty gate uses
6454 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
6455 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
6456 /// [`ManifestError::RepositorioEmpty`],
6457 /// [`ManifestError::DescricaoEmpty`]).
6458 ///
6459 /// Universal-axis (every kind carries `:licenca`), so wired at
6460 /// the caixa-build gate alongside the peer universal gates
6461 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6462 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
6463 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
6464 /// [`Self::validate_descricao`] / [`Self::validate_code_paths`]
6465 /// — before the kind-coherence gates
6466 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
6467 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6468 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6469 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
6470 /// specific slot sets.
6471 ///
6472 /// Past the empty arm the gate enforces the SPDX-expression shape
6473 /// predicate via [`crate::render::is_spdx_expression_shape`]: the
6474 /// structural alphabet floor every realistic SPDX expression in
6475 /// the wild uses — ASCII alphanumeric plus `.`, `-`, `+`, `(`,
6476 /// `)`, `:` (the `DocumentRef-…:LicenseRef-…` separator), and a
6477 /// single ASCII space (token separator). Closes the canonical
6478 /// paste-from-doc footguns the bare empty-arm gate left open:
6479 /// paste-from-doc whitespace (`"MIT "`, `" MIT"`), paste-from-
6480 /// multiline-doc CRLF (`"MIT\n"`), tab-from-aligned-doc
6481 /// (`"MIT\tOR Apache-2.0"`), non-ASCII smart-quote paste,
6482 /// underscore-instead-of-hyphen typo (`"Apache_2.0"`),
6483 /// comma-instead-of-`OR`-keyword colloquial idiom (`"MIT,
6484 /// Apache-2.0"`), slash-dual-license colloquial idiom (`"MIT/
6485 /// Apache-2.0"`), and semicolon-list-separator confusion
6486 /// (`"MIT; Apache-2.0"`). Mirrors the shape-predicate cascade
6487 /// [`Self::validate_repositorio`] / [`Self::validate_edicao`]
6488 /// establish past their own empty arms.
6489 ///
6490 /// The empty-first cascade discipline mirrors every peer per-axis
6491 /// identity gate: [`ManifestError::LicencaEmpty`] runs before
6492 /// [`ManifestError::LicencaInvalid`], so the narrower empty
6493 /// diagnostic surfaces on `Some("")` rather than the broader
6494 /// shape-predicate diagnostic — peer with how
6495 /// [`ManifestError::EdicaoEmpty`] runs before
6496 /// [`ManifestError::EdicaoInvalid`],
6497 /// [`ManifestError::RepositorioEmpty`] runs before
6498 /// [`ManifestError::RepositorioInvalid`].
6499 ///
6500 /// A future tightening on this axis can extend the alphabet
6501 /// floor into a full SPDX expression parser + license-id
6502 /// allowlist (rejecting alphabet-valid values that don't name a
6503 /// real SPDX license identifier — e.g., `"NotAReal"` is
6504 /// alphabet-valid but no `NotAReal` license-id exists). That
6505 /// parser only becomes meaningful past a real SPDX-spec
6506 /// dependency; this gate establishes the structural floor by
6507 /// refusing every non-SPDX-alphabet value at validate time.
6508 pub fn validate_licenca(&self) -> Result<(), ManifestError> {
6509 let Some(s) = self.licenca() else {
6510 return Ok(());
6511 };
6512 if s.is_empty() {
6513 return Err(ManifestError::LicencaEmpty);
6514 }
6515 crate::render::is_spdx_expression_shape(s)
6516 .map_err(|reason| ManifestError::licenca_invalid(s, reason))?;
6517 Ok(())
6518 }
6519
6520 /// Reject `:edicao` values that are the empty string. The flat
6521 /// `edicao: Option<String>` slot on [`Caixa`] is the universal
6522 /// language-edition axis every kind carries — it determines the
6523 /// tatara-lisp macro surface + compatibility flags the substrate
6524 /// applies when building a caixa, and lands verbatim in the
6525 /// `Caixa::template` author-time scaffold (the canonical
6526 /// `:edicao "2026"` line every `feira init` emits via
6527 /// [`Caixa::template`] at `caixa-core/src/manifest.rs:1193`) and
6528 /// in every renderer-side fixture (`caixa-helm/src/lib.rs:375`,
6529 /// `caixa-flux/src/lib.rs:445`, `caixa-mesh/src/lib.rs:629`,
6530 /// `caixa-core/src/render.rs:2510`) via
6531 /// `edicao: Some("2026".into())`.
6532 ///
6533 /// `None` (the canonical "omit the slot to defer to the
6534 /// substrate's default edition" shape every existing
6535 /// [`caixa-resolver`] integration test fixture carries via
6536 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
6537 /// is accepted trivially — the gate is a no-op when the author
6538 /// didn't declare a value. `Some("")` is gated by the narrower
6539 /// [`ManifestError::EdicaoEmpty`] arm, mirroring the empty-arm
6540 /// shape every peer per-axis empty gate uses
6541 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
6542 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
6543 /// [`ManifestError::RepositorioEmpty`],
6544 /// [`ManifestError::DescricaoEmpty`], [`ManifestError::LicencaEmpty`]).
6545 ///
6546 /// Universal-axis (every kind carries `:edicao`), so wired at
6547 /// the caixa-build gate alongside the peer universal gates
6548 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
6549 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
6550 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
6551 /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
6552 /// [`Self::validate_code_paths`] — before the kind-coherence
6553 /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
6554 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
6555 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
6556 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
6557 /// specific slot sets.
6558 ///
6559 /// Past the empty arm the gate enforces the canonical year-shape
6560 /// predicate: every documented tatara-lisp edition is a 4-digit
6561 /// ASCII decimal year (`"2026"` is the only edition currently
6562 /// minted; future-introduced siblings will follow the same
6563 /// shape, peer with Cargo's `[package] edition` grammar which
6564 /// every value Cargo has ever accepted matches — `"2015"`,
6565 /// `"2018"`, `"2021"`, `"2024"`). Any value that's not exactly
6566 /// 4 ASCII decimal bytes is rejected with the narrower
6567 /// [`ManifestError::EdicaoInvalid`] arm, mirroring the
6568 /// shape-predicate cascade [`Self::validate_repositorio`]
6569 /// establishes past its own empty arm
6570 /// ([`ManifestError::RepositorioEmpty`] →
6571 /// [`ManifestError::RepositorioInvalid`]). Closes the canonical
6572 /// paste-from-doc footguns the bare empty-arm gate left open:
6573 ///
6574 /// - leading / trailing whitespace from a paste-from-doc
6575 /// (`"2026 "`, `" 2026"`)
6576 /// - control characters / CRLF from a paste-from-multiline-doc
6577 /// (`"2026\n"`)
6578 /// - non-ASCII look-alikes from a fullwidth keyboard
6579 /// (`"2026"`) which would silently land as a non-ASCII
6580 /// string in the rendered caixa.lisp
6581 /// - free-form non-year values (`"x"`, `"latest"`,
6582 /// `"nightly"`) that have no operational meaning on the
6583 /// substrate's build-time edition selector
6584 /// - leading non-digit prefixes (`"v2026"`, `"e2026"`,
6585 /// `"r2026"`) — common version-tag idioms that don't apply
6586 /// to the year-shaped edition axis
6587 /// - decimal-shaped values (`"2026.1"`, `"2026.0"`) — every
6588 /// edition is a year, not a fractional version
6589 /// - wrong-length numeric values (`"26"`, `"202"`, `"20260"`,
6590 /// `"00026"`) that don't name a year
6591 ///
6592 /// `None` (the canonical "omit the slot to defer to the
6593 /// substrate's default edition" shape every existing
6594 /// [`caixa-resolver`] integration test fixture carries via
6595 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
6596 /// is accepted trivially — the gate is a no-op when the author
6597 /// didn't declare a value. The empty-first cascade discipline
6598 /// mirrors every peer per-axis identity gate:
6599 /// [`ManifestError::EdicaoEmpty`] runs before
6600 /// [`ManifestError::EdicaoInvalid`], so the narrower empty
6601 /// diagnostic surfaces on `Some("")` rather than the broader
6602 /// shape-predicate diagnostic — peer with how
6603 /// [`ManifestError::NomeEmpty`] runs before
6604 /// [`ManifestError::NomeInvalid`],
6605 /// [`ManifestError::VersaoEmpty`] runs before
6606 /// [`ManifestError::VersaoInvalid`],
6607 /// [`ManifestError::RepositorioEmpty`] runs before
6608 /// [`ManifestError::RepositorioInvalid`].
6609 ///
6610 /// A future tightening on this axis can extend the shape
6611 /// predicate into a known-edition allowlist (rejecting
6612 /// year-shaped values that don't name a tatara-lisp edition
6613 /// the substrate actually understands — e.g., `"1999"` is
6614 /// year-shaped but no `1999` edition exists). That allowlist
6615 /// only becomes meaningful past the introduction of a sibling
6616 /// edition to `"2026"`; this gate establishes the structural
6617 /// floor by refusing every non-year-shaped value at validate
6618 /// time.
6619 pub fn validate_edicao(&self) -> Result<(), ManifestError> {
6620 let Some(s) = self.edicao() else {
6621 return Ok(());
6622 };
6623 if s.is_empty() {
6624 return Err(ManifestError::EdicaoEmpty);
6625 }
6626 if s.len() != 4 || !s.bytes().all(|b| b.is_ascii_digit()) {
6627 return Err(ManifestError::edicao_invalid(
6628 s,
6629 "must be a 4-digit ASCII decimal year (canonical \"2026\")",
6630 ));
6631 }
6632 Ok(())
6633 }
6634
6635 /// Compose the supervisor-related flat slots into a single
6636 /// [`SupervisorSpec`] for validation. Returns `None` when the
6637 /// caixa isn't a `:kind Supervisor`.
6638 ///
6639 /// The flat representation in [`Caixa`] keeps tatara-lisp authoring
6640 /// simple (one form, no nested `:supervisor (…)` block); this view
6641 /// is the "typed shape" the operator + supervisor reconciler
6642 /// consume.
6643 #[must_use]
6644 pub fn supervisor_view(&self) -> Option<SupervisorSpec> {
6645 if !self.kind().is_supervisor() {
6646 return None;
6647 }
6648 // Fold through the shared `supervisor::duration_codec::parse`
6649 // — the same parser the serde-routed `with = "duration_codec"`
6650 // on `SupervisorSpec::restart_window`, the `:politicas
6651 // :timeout` codec, and the `:politicas :circuit-breaker
6652 // :window` codec all consume. The prior inline f64-shaped
6653 // duplicate (`parse_window_inline`) admitted every magnitude
6654 // the integer-magnitude gate (1c55a2a) rejects on the three
6655 // serde-routed siblings — `"1.5s"`, `"1.0s"`, `"0.5m"`,
6656 // `"+30s"`, `"-30s"` — and silently dropped malformed input as
6657 // `None` (i.e. "no reset"), divergent from the shared codec's
6658 // integer-magnitude discipline by construction. The fold
6659 // closes the divergence: every value the typed
6660 // `SupervisorSpec` carries past `supervisor_view` is in the
6661 // shared codec's accepted set. The `.ok()` here preserves the
6662 // existing soft-swallow shape on this view-construction path;
6663 // the new [`Caixa::validate_restart_window`] (sibling of
6664 // [`Self::validate_nome`] / [`Self::validate_versao`]) names
6665 // the offending raw string at build time so authoring tools
6666 // (`feira lint`, the future layout-side wire-up) surface a
6667 // self-locating diagnostic instead of a silently dropped
6668 // window.
6669 let restart_window = self
6670 .restart_window()
6671 .and_then(|s| crate::supervisor::duration_codec::parse(s).ok());
6672 Some(SupervisorSpec {
6673 // Route the author-omitted `:estrategia` arm through the
6674 // substrate-canonical
6675 // [`crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT`] typed
6676 // `pub const` rather than the transitively-derived
6677 // [`RestartStrategy::default`] route the prior
6678 // `.unwrap_or_default()` fold reached for — one source of
6679 // truth for the Erlang/OTP `one_for_one` half of Learn You
6680 // Some Erlang's `{one_for_one, intensity, 5, 60}` worker-
6681 // supervisor canonical default that also backs the
6682 // [`crate::supervisor::Default for RestartStrategy`] impl
6683 // and the [`crate::supervisor::Default for SupervisorSpec`]
6684 // impl's struct-literal `estrategia` field, all now routed
6685 // through the same lifted constant. Prior to the lift the
6686 // composition site carried `.unwrap_or_default()` with no
6687 // compile-time link back to the shared OTP-canonical
6688 // default that the peer paired
6689 // `.unwrap_or(SUPERVISOR_MAX_RESTARTS_DEFAULT)` (b698ec0)
6690 // arm on the sibling `:max-restarts` axis routes through —
6691 // so a future rebrand of the OTP-canonical strategy default
6692 // (a widening to `rest_for_one` once the substrate
6693 // discovers startup-order-coupled child cohorts as the more
6694 // common shape, a per-cluster overlay the operator pins
6695 // through the MESH-COMPOSITION §III.2 supervision-canary
6696 // `:estrategia-overrides` roadmap slot) would have had to
6697 // migrate the paired `MaxIntensity` + `Period` halves
6698 // through the lifted constants and the `one_for_one` half
6699 // through a `RestartStrategy::default()` route in lockstep
6700 // or the three halves of the same OTP-canonical default
6701 // would silently drift out of pairing. Byte-parity against
6702 // the lifted constant closes the split. Pinned by
6703 // [`supervisor_view_estrategia_fallback_routes_through_lifted_default`]
6704 // in the tests module.
6705 estrategia: self
6706 .estrategia()
6707 .unwrap_or(crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT),
6708 // Route the author-omitted `:max-restarts` arm through the
6709 // substrate-canonical [`crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT`]
6710 // typed `pub const` rather than the raw `5` literal — one
6711 // source of truth for the Erlang/OTP-canonical
6712 // `{intensity, 5, 60}` `MaxIntensity` default that also
6713 // backs the serde-side wire-format author-omitted arm on
6714 // [`crate::supervisor::SupervisorSpec::max_restarts`] via
6715 // `#[serde(default = "default_max_restarts")]` and the
6716 // [`Default for SupervisorSpec`] impl's struct-literal
6717 // default field. Prior to the lift the composition site
6718 // carried a raw `5` with no compile-time link back to the
6719 // serde-side default, so a future rebrand of the OTP-
6720 // canonical default (a tightening to Elixir's `3`, a
6721 // widening to a per-cluster overlay the operator pins
6722 // through the MESH-COMPOSITION §III.2 supervision-canary
6723 // `:supervisor :max-restarts-overrides` roadmap slot)
6724 // would have had to be threaded through both open-coded
6725 // copies in lockstep or the wire-format author-omitted arm
6726 // and this view-construction author-omitted arm would
6727 // silently disagree on which restart-budget an omitted
6728 // `:max-restarts` resolves to. Pinned by
6729 // [`supervisor_view_max_restarts_fallback_routes_through_lifted_default`]
6730 // in the tests module.
6731 max_restarts: self
6732 .max_restarts()
6733 .unwrap_or(crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT),
6734 restart_window,
6735 children: self.children().to_vec(),
6736 })
6737 }
6738
6739 /// A minimal starter manifest emitted by `feira init`.
6740 #[must_use]
6741 pub fn template(nome: &str) -> String {
6742 format!(
6743 "(defcaixa\n \
6744 :nome {nome:?}\n \
6745 :versao \"0.1.0\"\n \
6746 :kind Biblioteca\n \
6747 :edicao \"2026\"\n \
6748 :descricao \"FIXME — describe this caixa\"\n \
6749 :autores ()\n \
6750 :etiquetas ()\n \
6751 :deps ()\n \
6752 :deps-dev ()\n \
6753 :bibliotecas (\"lib/{nome}.lisp\"))\n"
6754 )
6755 }
6756
6757 /// Serialize to a canonical `caixa.lisp` source — suitable for writing
6758 /// back after mutation (e.g. `feira add`).
6759 ///
6760 /// Goes through serde JSON → canonical Sexp → per-field pretty print.
6761 /// The derive-macro `compile_from_sexp` path is the inverse, so any
6762 /// `Caixa` round-trips through `to_lisp` + `from_lisp`.
6763 #[must_use]
6764 pub fn to_lisp(&self) -> String {
6765 let json = serde_json::to_value(self).expect("Caixa serialize");
6766 let sexp = tatara_lisp::domain::json_to_sexp(&json);
6767 let tatara_lisp::Sexp::List(items) = sexp else {
6768 return format!("(defcaixa {sexp})\n");
6769 };
6770 let mut out = String::from("(defcaixa");
6771 let mut i = 0;
6772 while i + 1 < items.len() {
6773 out.push_str("\n ");
6774 out.push_str(&items[i].to_string());
6775 out.push(' ');
6776 out.push_str(&items[i + 1].to_string());
6777 i += 2;
6778 }
6779 out.push_str(")\n");
6780 out
6781 }
6782}
6783
6784/// Errors raised by top-level [`Caixa`] validators that don't fit
6785/// the per-axis [`DepError`] / [`crate::AplicacaoError`] /
6786/// [`crate::SupervisorError`] / [`crate::LayoutError`] families —
6787/// the Caixa's own identity axes (`:nome`, `:versao`) that flow
6788/// through every substrate-side artifact's `metadata.name` /
6789/// version derivation.
6790///
6791/// A future top-level sum (the M4 `CaixaError` the [`DepError`]
6792/// doc-comment anticipates) can hold one of each per-axis error
6793/// family without reshaping individual diagnostics; this enum is
6794/// the first such per-Caixa-identity family.
6795#[derive(Debug, Error, PartialEq, Eq)]
6796pub enum ManifestError {
6797 #[error(
6798 ":nome is empty (every caixa must name itself; the value flows \
6799 into every K8s artifact's `metadata.name` derivation and into \
6800 the default `lib/<nome>.lisp` / `exe/<nome>` layout paths)"
6801 )]
6802 NomeEmpty,
6803 #[error(
6804 ":nome {nome:?} is not a valid DNS-1123 label: {reason} (the K8s \
6805 apiserver enforces this rule on every `metadata.name` the \
6806 caixa's substrate-side renderers derive from `:nome` — the \
6807 `lareira-<nome>` Helm chart name, the programs.yaml entry \
6808 name, the `LABEL_APLICACAO` label value, the `<aplicacao>-<de>-to-<para>` \
6809 CiliumNetworkPolicy name, the `<aplicacao>-<para>` HTTPRoute \
6810 name; use a lowercase alphanumeric + hyphen identifier like \
6811 `\"checkout\"` or `\"cart-v2\"`)"
6812 )]
6813 NomeInvalid { nome: String, reason: String },
6814 #[error(
6815 ":nome {nome:?} overflows the joint-length budget on the canonical \
6816 `lareira-<nome>` chart-name shape: {reason} (every per-Servico / \
6817 per-Aplicacao renderer the substrate carries — `caixa-helm`'s \
6818 `Chart.yaml::name`, `caixa-flux`'s `cluster_bundle` HelmRelease \
6819 `chart:` slot, `caixa-tatara`'s `release_name` + \
6820 `oci://<registry>/lareira-<nome>` chart ref — derives the same \
6821 joint name through the canonical `lareira_chart_name` helper, and \
6822 Helm's `Chart.yaml::name` admission rule + the K8s apiserver's \
6823 DNS-1123 label cap on every chart-name-derived `metadata.name` \
6824 reject any joint name exceeding 63 bytes; the narrower \
6825 `:nome` shape (`NomeInvalid`) gates the bare-`:nome` budget, this \
6826 arm gates the chart-name budget downstream renderers inherit)"
6827 )]
6828 NomeChartNameBudgetExceeded { nome: String, reason: String },
6829 #[error(
6830 ":versao is empty (every caixa must pin its own version; the value flows \
6831 into the `lareira-<nome>` Helm chart's `Chart.yaml` version + appVersion, \
6832 the `feira publish` `v<versao>` git tag, the OCI image's `:v<versao>` / \
6833 `:latest` tags, the lacre closure's `concrete_versao`, and the \
6834 `:upgrade-from :from` peers — use a SemVer-2 literal like `\"0.1.0\"`)"
6835 )]
6836 VersaoEmpty,
6837 #[error(
6838 ":versao {versao:?} is not a valid SemVer-2 version: {reason} (the substrate \
6839 consumes this string as `semver::Version` — three-part `MAJOR.MINOR.PATCH` \
6840 with optional `-prerelease` and `+build` — across every artifact derived \
6841 from `:versao`: the `lareira-<nome>` Helm chart's `Chart.yaml` version + \
6842 appVersion (Helm SemVer-2-strict), the `feira publish` `v<versao>` git tag, \
6843 the OCI image's `:v<versao>` tag, the lacre closure's `concrete_versao`, \
6844 and the `:upgrade-from :from` peers that match against this exact shape; \
6845 use a literal like `\"0.1.0\"`, `\"0.2.0-rc.1\"`, or `\"1.0.0+build.42\"` — \
6846 not a git-tag-shape like `\"v0.1.0\"`, a docker-tag-shape like `\"latest\"`, \
6847 a requirement-shape like `\"^0.1\"`, or a four-part `\"0.1.0.0\"`)"
6848 )]
6849 VersaoInvalid { versao: String, reason: String },
6850 #[error(
6851 ":restart-window {restart_window:?} is not a valid duration: {reason} (the \
6852 substrate consumes this string through the shared \
6853 `supervisor::duration_codec` — the same parser routed via `with = \
6854 \"duration_codec\"` onto the typed `SupervisorSpec::restart_window`, \
6855 `:politicas :timeout`, and `:politicas :circuit-breaker :window` slots; \
6856 the canonical authoring form is `<integer><unit>` where the unit is one \
6857 of `ms` / `s` / `m` / `h` and the magnitude has no decimal point and no \
6858 leading `+` / `-` sign — e.g. `\"60s\"`, `\"5m\"`, `\"1h\"`, `\"500ms\"`. \
6859 Without this gate a malformed `:restart-window` silently produced a \
6860 supervisor with `restart_window: None` (\"never reset\"), turning OTP's \
6861 `MaxIntensity / Period` invariant into a never-reset supervisor far from \
6862 the source `caixa.lisp`; the gate moves the diagnostic to the manifest \
6863 layer with the offending value named verbatim. Omit the slot entirely to \
6864 express \"no reset\"; carry a positive integer duration to express the \
6865 sliding window)"
6866 )]
6867 RestartWindowMalformed {
6868 restart_window: String,
6869 reason: String,
6870 },
6871 #[error(
6872 "{slot} entry is an empty path string — every {slot} entry must name \
6873 a file relative to the caixa root; omit the entry to omit the file \
6874 (the layout checker's `root.join(\"\")` resolves to the caixa root \
6875 itself, so an empty entry silently aliases the project root as a \
6876 declared {slot} file, then fails downstream at parse / existence \
6877 time with a diagnostic that names the root rather than the offending \
6878 entry)"
6879 )]
6880 CodePathEmpty { slot: &'static str },
6881 #[error(
6882 "{slot} entry {} is an absolute path — entries must be relative to \
6883 the caixa root, since `Path::join` replaces the base with an absolute \
6884 right-hand side and `root.join(\"/abs/...\")` resolves to \"/abs/...\" \
6885 outside the caixa root sandbox; rewrite the entry as a relative path \
6886 under the caixa root (e.g. `\"lib/<name>.lisp\"`, `\"exe/<name>\"`, \
6887 `\"servicos/<name>.computeunit.yaml\"`)",
6888 path.display()
6889 )]
6890 CodePathAbsolute { slot: &'static str, path: PathBuf },
6891 #[error(
6892 "{slot} entry {} contains a `..` component — entries must not traverse \
6893 above the caixa root (the layout's `starts_with(<dir>)` fence on \
6894 `:exe` / `:servicos` is component-aware, not canonical-path-aware, \
6895 so a mid-path `..` silently traverses the sandbox; `:bibliotecas` \
6896 has no such fence, so a leading `..` escapes unconditionally if the \
6897 resolved target happens to exist)",
6898 path.display()
6899 )]
6900 CodePathParentEscape { slot: &'static str, path: PathBuf },
6901 #[error(
6902 "{slot} entry {} does not terminate in the `.lisp` extension — every \
6903 `:bibliotecas` entry is a tatara-lisp source file the `feira build` \
6904 loop reads through `tatara_lisp::read` at parse time, so any other \
6905 extension (`.rs`, `.txt`, `.lisp.bak`) or no-extension shape is \
6906 structurally a parser error far from the source caixa.lisp, with \
6907 no field naming the offending `:bibliotecas` entry. Pin a relative \
6908 path under the caixa root whose terminating extension is \
6909 lowercase-`.lisp` (e.g. `\"lib/<name>.lisp\"`, \
6910 `\"lib/handlers.lisp\"`) — the same file-type contract the peer \
6911 `:behavior :on-*` (c97815a) and `:upgrade-from :state-change :script` \
6912 (33cc830) axes already carry through the same lifted \
6913 `is_lisp_extension` predicate",
6914 path.display()
6915 )]
6916 CodePathNonLispExtension { slot: &'static str, path: PathBuf },
6917 #[error(
6918 "{slot} entry {} does not terminate in the `.computeunit.yaml` \
6919 compound suffix — every `:servicos` entry is a typed `ComputeUnit` \
6920 CR YAML file the peer caixa-helm / caixa-flux renderers consume \
6921 through `serde_yaml::from_str` at chart / FluxCD bundle render \
6922 time, so any other extension (`.yaml`, `.yml`, `.json`, the \
6923 off-by-one-segment `.computeunit-yaml`, the editor-backup \
6924 `.computeunit.yaml.bak`) or no-extension shape is structurally a \
6925 YAML-parser error / `ComputeUnit` schema-mismatch far from the \
6926 source caixa.lisp, with no field naming the offending `:servicos` \
6927 entry. Pin a relative path under the caixa root whose terminating \
6928 compound suffix is lowercase-`.computeunit.yaml` (e.g. \
6929 `\"servicos/<name>.computeunit.yaml\"`, \
6930 `\"servicos/hello-rio.computeunit.yaml\"`) — the same file-type \
6931 contract the sibling `:bibliotecas` axis (64772a9) already carries \
6932 on the tatara-lisp-source axis through the peer lifted \
6933 `is_lisp_extension` predicate, here on the compound-suffix axis \
6934 `Path::extension` can't express on its own through the lifted \
6935 `is_computeunit_yaml_extension` predicate",
6936 path.display()
6937 )]
6938 CodePathNonComputeUnitYamlExtension { slot: &'static str, path: PathBuf },
6939 #[error(
6940 "{slot} entry {} appears more than once (the code-path list is \
6941 a set, not a multiset; every peer Vec-shaped author-supplied \
6942 list past validate is set-not-multiset — `:membros :caixa`, \
6943 `:placement :clusters`, `:entrada :paths`, `:contratos`, \
6944 `:children :caixa`, `:deps` / `:deps-dev` `:nome`, \
6945 `:upgrade-from :from`, `:etiquetas`, `:autores` — and the three \
6946 code-path lists are the last Vec-shaped author-supplied slots on \
6947 the typed Caixa surface still admitting a duplicate entry. \
6948 `:bibliotecas` duplicates re-parse the same file at \
6949 `feira build` time and silently mask the author's intent to \
6950 declare a *second* biblioteca; `:exe` duplicates collide on the \
6951 flake `packages.<name>` derivation key at the future \
6952 `caixa-flake` materializer; `:servicos` duplicates surface as the \
6953 narrower [`caixa-helm`] / [`caixa-flux`] `UnsupportedServicoCount` \
6954 rejection far from the source `caixa.lisp`. Drop the duplicate \
6955 or rename it to the actual second file intended)",
6956 path.display()
6957 )]
6958 CodePathDuplicate { slot: &'static str, path: PathBuf },
6959 #[error(
6960 ":etiquetas entry is empty (every tag must carry a non-empty \
6961 registry-search identifier; the empty entry has no operational \
6962 meaning — it indexes nothing in the future caixa-registry search \
6963 axis and clutters the rendered Helm `Chart.yaml` `keywords:` array \
6964 with a no-op tag; omit the entry to express \"no tag on this \
6965 position\")"
6966 )]
6967 EtiquetaEmpty,
6968 #[error(
6969 ":etiquetas entry {etiqueta:?} appears more than once (the \
6970 registry-search tag set is a set, not a multiset; duplicate \
6971 entries are silently dedup'd by caixa-helm's `BTreeSet` collect \
6972 at chart render — a \"second wins / one silently disappears\" \
6973 shape divergent from every peer typed-graph set gate \
6974 (`:membros :caixa`, `:placement :clusters`, `:entrada :paths`, \
6975 `:contratos`, `:deps :nome`, `:upgrade-from :from`); drop the \
6976 duplicate or rename it to the actual tag intended)"
6977 )]
6978 EtiquetaDuplicate { etiqueta: String },
6979 #[error(
6980 ":etiquetas entry {etiqueta:?} is not a valid chart-keyword shape: \
6981 {reason} (the substrate consumes this string through the shared \
6982 `crate::render::is_chart_keyword_shape` predicate — the same \
6983 Cargo crates.io `[package] keywords` grammar entry shape: 1..=20 \
6984 bytes, starts with an ASCII letter, ASCII alphanumeric / `_` / `-` \
6985 continuation. The canonical authoring shapes are short kebab-case \
6986 identifiers like `\"mesh\"`, `\"wasm\"`, `\"tatara-lisp\"`, \
6987 `\"hello-world\"`, `\"caixa-servico\"`, `\"infrastructure\"`. \
6988 Without this gate a malformed `:etiquetas` entry (paste-from-doc \
6989 leading / trailing whitespace `\" mesh\"` / `\"mesh \"`; \
6990 paste-from-multiline-doc newline `\"mesh\\nhttp\"`; \
6991 paste-from-Windows-CRLF-doc CR; CSV-list-separator confusion \
6992 `\"mesh,http,grpc\"` — the author meant to author three separate \
6993 list entries; path-separator confusion `\"caixa/servico\"`; \
6994 namespace-suffix `\"http.1\"`; leading-digit `\"1foo\"`; \
6995 kebab-leak `\"-foo\"`; snake-leak `\"_foo\"`; non-ASCII \
6996 `\"café\"` — every legitimate search tag is strict ASCII; \
6997 paste-from-binary-blob NUL / BEL / ESC / DEL byte) silently \
6998 passed `from_lisp` + `validate_etiquetas` + \
6999 `StandardLayout::verify` and landed in the rendered \
7000 `lareira-<nome>` Helm chart's `Chart.yaml keywords:` array as a \
7001 malformed search tag — Artifact Hub's keyword index + the future \
7002 caixa-registry's keyword index would either silently drop the \
7003 tag or fail to index it far from the source caixa.lisp; the gate \
7004 moves the diagnostic to the manifest layer with the offending \
7005 value named verbatim)"
7006 )]
7007 EtiquetaInvalid { etiqueta: String, reason: String },
7008 #[error(
7009 ":autores entry is empty (every maintainer must carry a non-empty \
7010 identifier; the empty entry has no operational meaning — it \
7011 identifies no one in the substrate's authorship index and renders \
7012 as `maintainers: [{{name: \"\", email: null}}]` in the Helm chart's \
7013 `Chart.yaml`, a no-op maintainer the substrate cannot route to; \
7014 omit the entry to express \"no maintainer on this position\")"
7015 )]
7016 AutorEmpty,
7017 #[error(
7018 ":autores entry {autor:?} appears more than once (the maintainer \
7019 set is a set, not a multiset; unlike `:etiquetas`, caixa-helm's \
7020 `maintainers:` rendering does *no* dedup — duplicate entries \
7021 stack verbatim in `Chart.yaml` as two identical \
7022 `Maintainer {{ name, email: None }}` records, divergent from every \
7023 peer typed-graph set gate (`:etiquetas`, `:membros :caixa`, \
7024 `:placement :clusters`, `:entrada :paths`, `:contratos`, \
7025 `:deps :nome`, `:upgrade-from :from`); drop the duplicate or \
7026 rename it to the actual author intended)"
7027 )]
7028 AutorDuplicate { autor: String },
7029 #[error(
7030 ":autores entry {autor:?} is not a valid chart-maintainer-name shape: \
7031 {reason} (the substrate consumes this string through the shared \
7032 `crate::render::is_chart_maintainer_name_shape` predicate — the same \
7033 single-line-UTF-8 floor every realistic chart maintainer name carries: \
7034 1..=128 bytes, no leading or trailing whitespace, no ASCII control \
7035 characters anywhere, Unicode bytes accepted. The canonical authoring \
7036 shapes are short single-line identifiers like `\"pleme-io\"`, \
7037 `\"Pleme Contributors\"`, `\"alice <alice@example.com>\"`, \
7038 `\"François Dupont\"`. Without this gate a malformed `:autores` entry \
7039 (paste-from-aligned-doc leading whitespace `\" pleme-io\"` / trailing \
7040 whitespace `\"pleme-io \"`; paste-from-multiline-doc newline \
7041 `\"alice\\nbob\"` — the author pasted a multi-line block of author \
7042 records into one entry instead of splitting into one entry per author; \
7043 paste-from-Windows-CRLF-doc carriage return `\"alice\\rbob\"`; \
7044 tab-from-aligned-doc `\"Pleme\\tContributors\"`; paste-from-binary-blob \
7045 NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
7046 `validate_autores` + `StandardLayout::verify` and landed in the \
7047 rendered `lareira-<nome>` Helm chart's `Chart.yaml maintainers:` array \
7048 as a YAML-illegal multi-line scalar or a silently-trimmed whitespace \
7049 round-trip — every chart-aware UI (`helm list`, `helm search`, \
7050 Artifact Hub maintainer index) would render the maintainer name in a \
7051 single-line column far from the source caixa.lisp; the gate moves the \
7052 diagnostic to the manifest layer with the offending value named \
7053 verbatim)"
7054 )]
7055 AutorInvalid { autor: String, reason: String },
7056 #[error(
7057 ":repositorio is the empty string (every published caixa names its \
7058 git source via a non-empty `:repositorio` locator — the value \
7059 flows verbatim into the rendered `lareira-<nome>` Helm chart's \
7060 `Chart.yaml` `home:` field via `caixa-helm` and into the FluxCD \
7061 `GitRepository.spec.url` via `caixa-flux`'s \
7062 `ClusterBundleOpts::for_caixa`; both consumers' \
7063 `Option::unwrap_or_else` fallbacks only fire when the slot is \
7064 `None`, so an empty `Some(\"\")` silently lands as `home: \"\"` / \
7065 `url: \"\"` in the rendered artifacts and breaks at `helm \
7066 template` / FluxCD source-controller reconcile time far from the \
7067 source caixa.lisp; omit the slot entirely to defer to the \
7068 renderer's `https://github.com/pleme-io/<nome>` / \
7069 `caixa.nome`-derived fallback, or carry a canonical authoring \
7070 shape like `\"github:org/repo\"`, `\"https://host/path\"`, \
7071 `\"ssh://[user@]host/path\"`, `\"git@host:path\"`, or \
7072 `\"file:///path\"`)"
7073 )]
7074 RepositorioEmpty,
7075 #[error(
7076 ":repositorio {repositorio:?} is not a valid git repo URL: {reason} \
7077 (the substrate consumes this string through the shared \
7078 `crate::render::is_git_repo_url` predicate — the same parser the \
7079 peer `:deps :fonte (:tipo git :repo …)` axis routes its `:repo` \
7080 value through via `DepSource::validate`; the canonical authoring \
7081 shapes are `\"github:org/repo\"` shorthand, `\"https://host/path\"` \
7082 / `\"ssh://[user@]host/path\"` / `\"git://host/path\"` / \
7083 `\"file:///path\"` URL schemes, or the `\"git@host:path\"` \
7084 scp-style SSH form. Without this gate a malformed `:repositorio` \
7085 (whitespace from a paste-from-doc; control characters / CRLF \
7086 from a paste-from-multiline-doc; a leading `-` from a \
7087 CLI-argument-injection footgun; a missing `:` separator from a \
7088 bare `org/repo` shape git treats as a relative filesystem path) \
7089 silently landed in the rendered `Chart.yaml home:` and the \
7090 FluxCD `GitRepository.spec.url` and broke at `git clone` / \
7091 FluxCD reconcile time far from the source caixa.lisp; the gate \
7092 moves the diagnostic to the manifest layer with the offending \
7093 value named verbatim)"
7094 )]
7095 RepositorioInvalid { repositorio: String, reason: String },
7096 #[error(
7097 ":descricao is the empty string (every published caixa names \
7098 its purpose via a non-empty `:descricao` summary — the value \
7099 flows verbatim into the rendered `lareira-<nome>` Helm \
7100 chart's `Chart.yaml` `description:` field via `caixa-helm`'s \
7101 `build_chart_yaml` and into the chart `README.md` header via \
7102 `build_readme`; both consumers' `Option::unwrap_or_else` \
7103 `caixa.nome`-derived fallbacks only fire when the slot is \
7104 `None`, so an empty `Some(\"\")` silently lands as \
7105 `description: \"\"` / a blank `README.md` header in the \
7106 rendered artifacts and breaks at `helm lint` time \
7107 (`WARNING [chart.metadata.description]: description is \
7108 required` on `apiVersion: v2` charts) far from the source \
7109 caixa.lisp; omit the slot entirely to defer to the \
7110 renderer's `\"Generated chart for caixa Servico <nome>\"` / \
7111 `\"caixa Servico <nome>\"` fallbacks, or carry a non-empty \
7112 summary like `\"Canonical Rust→wasm32-wasip2 caixa \
7113 Servico.\"`)"
7114 )]
7115 DescricaoEmpty,
7116 #[error(
7117 ":descricao {descricao:?} is not a valid chart-description shape: \
7118 {reason} (the substrate consumes this string through the shared \
7119 `crate::render::is_chart_description_shape` predicate — the same \
7120 single-line-UTF-8 floor every realistic chart description carries: \
7121 1..=512 bytes, no leading or trailing whitespace, no ASCII control \
7122 characters anywhere, Unicode prose bytes accepted. The canonical \
7123 authoring shapes are short single-line summaries like `\"Canonical \
7124 Rust→wasm32-wasip2 caixa Servico.\"`, `\"Checkout flow.\"`, \
7125 `\"AWS provider caixa for tatara-lisp\"`. Without this gate a \
7126 malformed `:descricao` (paste-from-aligned-doc leading whitespace \
7127 `\" Checkout flow.\"` / trailing whitespace `\"Checkout flow. \"`; \
7128 paste-from-multiline-doc newline `\"Checkout\\nflow.\"`; \
7129 paste-from-Windows-CRLF-doc carriage return `\"Checkout\\rflow.\"`; \
7130 tab-from-aligned-doc `\"Checkout\\tflow.\"`; paste-from-binary-blob \
7131 NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
7132 `validate_descricao` + `StandardLayout::verify` and landed in the \
7133 rendered `lareira-<nome>` Helm chart's `Chart.yaml description:` \
7134 field + `README.md` header paragraph as a YAML-illegal multi-line \
7135 scalar or a silently-trimmed whitespace round-trip — every \
7136 chart-aware UI (`helm list`, `helm search`, Artifact Hub) would \
7137 render the description in a single-line column far from the source \
7138 caixa.lisp; the gate moves the diagnostic to the manifest layer \
7139 with the offending value named verbatim)"
7140 )]
7141 DescricaoInvalid { descricao: String, reason: String },
7142 #[error(
7143 ":licenca is the empty string (every published caixa names \
7144 its license via a non-empty `:licenca` SPDX expression — the \
7145 value flows verbatim into the rendered `lareira-<nome>` Helm \
7146 chart's `README.md` `## License` section via `caixa-helm`'s \
7147 `build_readme` at `caixa-helm/src/lib.rs:361`; the consumer's \
7148 `Option::unwrap_or_else(|| \"MIT\".into())` `MIT` fallback \
7149 only fires when the slot is `None`, so an empty `Some(\"\")` \
7150 silently lands as a bare trailing period in the rendered \
7151 chart `README.md` `License` section far from the source \
7152 caixa.lisp; omit the slot entirely to defer to the \
7153 renderer's `MIT` fallback, or carry a canonical SPDX \
7154 expression like `\"MIT\"`, `\"Apache-2.0\"`, \
7155 `\"Apache-2.0 OR MIT\"`)"
7156 )]
7157 LicencaEmpty,
7158 #[error(
7159 ":licenca {licenca:?} is not a valid SPDX expression shape: {reason} \
7160 (the substrate consumes this string through the shared \
7161 `crate::render::is_spdx_expression_shape` predicate — the same \
7162 alphabet-floor parser every peer per-axis value-shape gate routes \
7163 its value through; the canonical authoring shapes are single \
7164 license identifiers like `\"MIT\"`, `\"Apache-2.0\"`, `\"BSD-3-Clause\"`, \
7165 compound expressions like `\"Apache-2.0 OR MIT\"`, \
7166 `\"MIT AND BSD-3-Clause\"`, `\"(MIT OR Apache-2.0) AND ISC\"`, \
7167 license-with-exception forms like `\"Apache-2.0 WITH LLVM-exception\"`, \
7168 `+`-suffix variants like `\"GPL-2.0+\"`, and user-defined references \
7169 like `\"LicenseRef-MyLicense\"` / \
7170 `\"DocumentRef-doc:LicenseRef-MyLicense\"`. Without this gate a \
7171 malformed `:licenca` (paste-from-doc whitespace `\"MIT \"` / \
7172 `\" MIT\"`; paste-from-multiline-doc CRLF `\"MIT\\n\"`; \
7173 tab-from-aligned-doc `\"MIT\\tOR Apache-2.0\"`; non-ASCII byte from \
7174 a smart-quote paste; underscore-instead-of-hyphen typo \
7175 `\"Apache_2.0\"`; comma-instead-of-`OR`-keyword colloquial idiom \
7176 `\"MIT, Apache-2.0\"`; slash-dual-license colloquial idiom \
7177 `\"MIT/Apache-2.0\"`; semicolon-list-separator confusion \
7178 `\"MIT; Apache-2.0\"`) silently landed in the rendered chart \
7179 `README.md` `## License` section + a future SPDX-aware \
7180 `Chart.yaml license:` emitter would refuse the value at \
7181 `helm lint` time far from the source caixa.lisp; the gate moves \
7182 the diagnostic to the manifest layer with the offending value \
7183 named verbatim)"
7184 )]
7185 LicencaInvalid { licenca: String, reason: String },
7186 #[error(
7187 ":edicao is the empty string (every published caixa names \
7188 its language edition via a non-empty `:edicao` value — the \
7189 edition determines the tatara-lisp macro surface + \
7190 compatibility flags the substrate applies when building \
7191 the caixa; the canonical `Caixa::template` scaffold every \
7192 `feira init` emits carries `:edicao \"2026\"` verbatim and \
7193 every renderer-side fixture (`caixa-helm`, `caixa-flux`, \
7194 `caixa-mesh`) carries `edicao: Some(\"2026\".into())` by \
7195 construction, so an empty `Some(\"\")` silently lands as a \
7196 bare `(:edicao \"\")` line in the rendered `caixa.lisp` and \
7197 a future renderer-side consumer that folds it through \
7198 `Option::unwrap_or_else` will skip the fallback and pass the \
7199 empty edition through to the substrate's build-time edition \
7200 selector far from the source caixa.lisp; omit the slot \
7201 entirely to defer to the substrate's default edition, or \
7202 carry a canonical edition like `\"2026\"`)"
7203 )]
7204 EdicaoEmpty,
7205 #[error(
7206 ":edicao {edicao:?} is not a valid edition: {reason} (every \
7207 documented tatara-lisp edition is a 4-digit ASCII decimal \
7208 year — `\"2026\"` is the only edition currently minted; \
7209 future-introduced siblings will follow the same shape, peer \
7210 with Cargo's `[package] edition` grammar which every value \
7211 Cargo has ever accepted matches: `\"2015\"`, `\"2018\"`, \
7212 `\"2021\"`, `\"2024\"`. Without this gate the canonical \
7213 paste-from-doc footguns silently passed: a trailing space \
7214 (`\"2026 \"`) from a paste-from-doc, a CRLF (`\"2026\\n\"`) \
7215 from a paste-from-multiline-doc, a fullwidth-keyboard \
7216 look-alike (`\"2026\"`), a free-form non-year value \
7217 (`\"x\"`, `\"latest\"`, `\"nightly\"`), a leading non-digit \
7218 version-tag prefix (`\"v2026\"`, `\"e2026\"`), a \
7219 decimal-shaped pseudo-version (`\"2026.1\"`), or a \
7220 wrong-length numeric value (`\"26\"`, `\"202\"`, \
7221 `\"20260\"`) all landed as `(:edicao \"<garbage>\")` in the \
7222 rendered caixa.lisp and broke at the substrate's \
7223 build-time edition selector far from the source caixa.lisp; \
7224 omit the slot entirely to defer to the substrate's default \
7225 edition, or carry a canonical 4-digit ASCII decimal year \
7226 like `\"2026\"`)"
7227 )]
7228 EdicaoInvalid { edicao: String, reason: String },
7229}
7230
7231// Fold the five `Err(ManifestError::CodePath{Absolute,ParentEscape,
7232// NonLispExtension,NonComputeUnitYamlExtension,Duplicate} { slot,
7233// path: path.to_path_buf() })` four-line struct-variant wire-up sites at
7234// [`Caixa::validate_code_path_lists`]'s per-slot per-entry cascade onto
7235// one substrate-primitive family on the `ManifestError` envelope — the
7236// five open-coded ctor sites remaining on the `:bibliotecas` / `:exe` /
7237// `:servicos` code-path-list value-shape trajectory this envelope carries,
7238// and the family sibling of the peer [`crate::behavior::behavior_slot_path_ctors!`]
7239// (67c31ec) two-slot `{ slot: &'static str, path: PathBuf }` envelope on
7240// the [`crate::BehaviorError`] surface that keys off the exact same
7241// `(slot: &'static str, path: &Path)` argument tuple.
7242//
7243// The five wire-up sites this fold closes are the sandbox-shape
7244// absolute-path arm (`return Err(ManifestError::CodePathAbsolute { slot,
7245// path: path.to_path_buf() })` on the [`is_sandboxed_relative_path`]
7246// `PathShapeViolation::Absolute` branch), the sandbox-shape
7247// parent-escape arm (`return Err(ManifestError::CodePathParentEscape {
7248// slot, path: path.to_path_buf() })` on the sibling
7249// `PathShapeViolation::ParentEscape` branch), the LispSource
7250// terminating-extension arm (`return Err(ManifestError::CodePathNonLispExtension {
7251// slot, path: path.to_path_buf() })` on the `!is_lisp_extension(path)`
7252// branch of the `:bibliotecas` file-type gate), the ComputeUnitYaml
7253// compound-suffix arm (`return Err(ManifestError::CodePathNonComputeUnitYamlExtension
7254// { slot, path: path.to_path_buf() })` on the
7255// `!is_computeunit_yaml_extension(path)` branch of the `:servicos`
7256// file-type gate), and the cross-entry duplicate arm
7257// (`ManifestError::CodePathDuplicate { slot, path: path.to_path_buf() }`
7258// inside the closure passed to [`crate::render::insert_first_seen`]) —
7259// each opened the identical `ManifestError::CodePath* { slot,
7260// path: path.to_path_buf() }` four-line struct-literal against the same
7261// `(slot: &'static str, path: &Path)` local tuple, the exact "same
7262// block re-inlined at every consumer" shape the PRIME DIRECTIVE names
7263// as a bug. The variant discriminator is the only thing that varies
7264// between the five sites; the rest of the struct-literal is a
7265// byte-for-byte re-inline.
7266//
7267// The macro below generates one `#[must_use]` inherent constructor per
7268// variant of shape `fn <ctor>(slot: &'static str, path: &std::path::Path)
7269// -> Self`, so every wire-up site collapses onto one dispatch:
7270// `ManifestError::<ctor>(slot, path)`, byte-equal to the pre-lift
7271// struct-literal on the same `(&'static str, &Path)` fixture. The
7272// uniform two-field construction (`slot` verbatim as `&'static str`,
7273// `path.to_path_buf()`) is spelled once — inside the macro — rather
7274// than at every wire-up site. The `slot` parameter stays `&'static str`
7275// (not `&str`) so every arm continues to carry a program-lifetime
7276// `:bibliotecas` / `:exe` / `:servicos` author-key label — one of the
7277// three `&'static str` literals threaded through the outer per-slot
7278// iterator at [`Caixa::validate_code_path_lists`] — matching the
7279// enum-field type. A runtime-borrowed `&str` would silently downgrade
7280// the label lifetime and let a caller stash a non-`'static` borrow into
7281// the returned error. The `&Path` parameter accepts both
7282// `&Path` and `&PathBuf` (via Deref coercion), so every existing
7283// wire-up — each already binds `let path = Path::new(entry);` from the
7284// per-entry loop — threads through the ctor without a pre-conversion.
7285//
7286// Every future consumer that wants to construct one of these five
7287// variants outside the five in-crate wire-up sites (a deferred
7288// `feira validate --code-paths` per-caixa admission verb re-checking
7289// each declared `:bibliotecas` / `:exe` / `:servicos` entry against the
7290// same sandbox-shape + file-type + duplicate cascade, a future
7291// caixa-registry per-lacre code-path re-validator at lacre-resolve
7292// time, a per-`Caixa` overlay resolver rejecting an author-supplied
7293// code-path against a cluster-local snapshot) now reaches each variant
7294// through one call rather than re-inlining the four-line struct-literal
7295// in lockstep with the five in-crate wire-up sites.
7296macro_rules! manifest_code_path_slot_path_ctors {
7297 ($($ctor:ident => $variant:ident),* $(,)?) => {
7298 impl ManifestError {
7299 $(
7300 #[doc = concat!(
7301 "Construct a [`ManifestError::",
7302 stringify!($variant),
7303 "`] naming the offending `:bibliotecas` / `:exe` / ",
7304 "`:servicos` code-path list `slot` label and the ",
7305 "offending entry `path`. Folds the uniform `Self::",
7306 stringify!($variant),
7307 " { slot, path: path.to_path_buf() }` two-field ",
7308 "struct-literal onto one substrate primitive so ",
7309 "every wire-up on this variant at ",
7310 "[`Caixa::validate_code_path_lists`] reads through ",
7311 "one dispatch rather than the pre-lift four-line ",
7312 "open-coded block. The `slot` label threads verbatim ",
7313 "from the outer per-slot iterator (one of the three ",
7314 "code-path author-key `&'static str` consts) and the ",
7315 "`path` from the per-entry inner iterator's ",
7316 "`Path::new(entry)` binding."
7317 )]
7318 #[must_use]
7319 pub fn $ctor(slot: &'static str, path: &std::path::Path) -> Self {
7320 Self::$variant {
7321 slot,
7322 path: path.to_path_buf(),
7323 }
7324 }
7325 )*
7326 }
7327 };
7328}
7329
7330manifest_code_path_slot_path_ctors! {
7331 code_path_absolute => CodePathAbsolute,
7332 code_path_parent_escape => CodePathParentEscape,
7333 code_path_non_lisp_extension => CodePathNonLispExtension,
7334 code_path_non_computeunit_yaml_extension => CodePathNonComputeUnitYamlExtension,
7335 code_path_duplicate => CodePathDuplicate,
7336}
7337
7338// Fold the last `ManifestError::CodePathEmpty { slot: <&'static str> }` single-
7339// slot struct-variant wire-up site at [`Caixa::validate_code_path_lists`]'s
7340// per-slot [`PathShapeViolation::Empty`] arm onto one substrate primitive on
7341// `ManifestError` — the last open-coded single-slot `{ slot: &'static str }`
7342// struct-literal on the `:bibliotecas` / `:exe` / `:servicos` code-path-list
7343// value-shape trajectory this envelope carries, matching the peer five-variant
7344// [`manifest_code_path_slot_path_ctors!`] family fold (de11917, 5 variants on
7345// `{ slot: &'static str, path: PathBuf }`) already closed on the sibling
7346// two-slot envelope of the same `ManifestError`, and mirror-symmetric sibling
7347// of the peer [`crate::behavior::BehaviorError::empty_path`] (0e33b37,
7348// `EmptyPath { slot: &'static str }`) ctor on the sibling M2 `:behavior`
7349// envelope's identical one-slot shape. After this lift every wire-up on every
7350// `ManifestError` variant carried by [`Caixa::validate_code_path_lists`]'s
7351// per-slot [`PathShapeViolation`] cascade reads through one substrate-primitive
7352// ctor dispatch per typed variant rather than one macro closing four sites
7353// plus a hand-written empty-slot open-coding the fifth.
7354//
7355// A macro is not warranted on the one-variant envelope shape
7356// `{ slot: &'static str }` — unlike the peer five-variant
7357// `{ slot: &'static str, path: PathBuf }` shape the
7358// [`manifest_code_path_slot_path_ctors!`] macro closes — but the same
7359// substrate-primitive discipline applies: every future consumer that wants to
7360// construct a `CodePathEmpty` outside [`Caixa::validate_code_path_lists`] (a
7361// deferred `feira validate --code-paths` per-caixa admission verb re-checking
7362// each declared `:bibliotecas` / `:exe` / `:servicos` entry against the same
7363// sandbox-shape + file-type + duplicate cascade, a future caixa-registry
7364// per-lacre code-path re-validator at lacre-resolve time, a per-`Caixa`
7365// overlay resolver rejecting an author-supplied empty code-path against a
7366// cluster-local snapshot) reaches the variant through one call rather than
7367// re-inlining the one-line struct-literal in lockstep with the in-crate
7368// wire-up site.
7369//
7370// The `slot` parameter stays `&'static str` (not `&str`) so the constructor
7371// continues to carry a program-lifetime `:bibliotecas` / `:exe` / `:servicos`
7372// author-key label — one of the three `&'static str` literals threaded through
7373// the outer per-slot iterator at [`Caixa::validate_code_path_lists`] — matching
7374// the enum-field type and the peer [`manifest_code_path_slot_path_ctors!`]-
7375// generated arms' `slot: &'static str` parameter verbatim. A runtime-borrowed
7376// `&str` would silently downgrade the label lifetime and let a caller stash a
7377// non-`'static` borrow into the returned error. `const fn` preserves the
7378// zero-runtime-work property of the pre-lift struct-literal verbatim, matching
7379// the peer [`crate::behavior::BehaviorError::empty_path`] `const fn` on the
7380// sibling M2 envelope and the sibling
7381// [`crate::supervisor::supervisor_scalar_ctors!`] / peer
7382// [`crate::aplicacao::aplicacao_policy_scalar_ctors!`] `Copy`-scalar
7383// discipline on their sibling envelopes.
7384impl ManifestError {
7385 /// Construct a [`ManifestError::CodePathEmpty`] naming the offending
7386 /// `:bibliotecas` / `:exe` / `:servicos` code-path list `slot` label.
7387 /// Folds the uniform `Self::CodePathEmpty { slot }` one-field
7388 /// struct-literal onto one substrate primitive so the wire-up at
7389 /// [`Caixa::validate_code_path_lists`]'s per-slot
7390 /// [`PathShapeViolation::Empty`] arm on this variant reads through one
7391 /// dispatch rather than the pre-lift open-coded struct-literal block.
7392 /// Peer of the sibling [`ManifestError::code_path_absolute`] /
7393 /// [`ManifestError::code_path_parent_escape`] /
7394 /// [`ManifestError::code_path_non_lisp_extension`] /
7395 /// [`ManifestError::code_path_non_computeunit_yaml_extension`] /
7396 /// [`ManifestError::code_path_duplicate`] ctors the
7397 /// [`manifest_code_path_slot_path_ctors!`] macro closed on the paired
7398 /// two-slot `{ slot: &'static str, path: PathBuf }` envelope of the same
7399 /// `ManifestError`, and mirror-symmetric sibling of the peer
7400 /// [`crate::behavior::BehaviorError::empty_path`] ctor on the sibling M2
7401 /// `:behavior` envelope's identical one-slot shape — the per-slot
7402 /// [`PathShapeViolation`] cascade at [`Caixa::validate_code_path_lists`]
7403 /// now routes every arm through one substrate-primitive ctor per typed
7404 /// variant.
7405 #[must_use]
7406 pub const fn code_path_empty(slot: &'static str) -> Self {
7407 Self::CodePathEmpty { slot }
7408 }
7409}
7410
7411// Fold the ten `ManifestError::{Nome, NomeChartNameBudgetExceeded, Versao,
7412// Etiqueta, Autor, Repositorio, Descricao, Licenca, Edicao}Invalid +
7413// RestartWindowMalformed
7414// { <field>: <val>.to_string() | <val>.clone(), reason: <expr> }` wire-up
7415// sites at the per-axis [`Caixa::validate_*`] cascade onto one substrate-
7416// primitive family per typed variant — the direct sibling on the
7417// [`ManifestError`] envelope of the peer
7418// [`crate::aplicacao::aplicacao_field_reason_ctors!`] (981060b, 7 variants
7419// on `AplicacaoError` at `MembroCaixaInvalid` / `EntradaParaInvalid` /
7420// `EntradaHostInvalid` / `EntradaPathInvalid` / `PlacementClusterInvalid` /
7421// `PlacementAffinityInvalid` / `ShardKeyInvalid`) on the M3 mesh side, and
7422// of the peer [`crate::dep::dep_nome_axis_reason_ctors!`] (5621f8a,
7423// 3 variants on `DepError` at `VersaoInvalid` / `FonteRepoShape` /
7424// `CaracteristicaInvalid`) on the sibling `:deps` envelope's mirror-
7425// symmetric `{ nome: String, <axis>: String, reason: String }` three-slot
7426// shape (the `nome` axis added at the per-dep-owned altitude). Every one
7427// of the peer four-family `LayoutError` ctor set
7428// ([`crate::layout::layout_violation_ctors!`] 131ca0d — 16 variants on
7429// `{ caixa, issue }`, [`crate::layout::layout_slot_kind_ctors!`] 0419438
7430// — 4 variants on `{ caixa, kind, slots }`,
7431// [`crate::LayoutError::missing_entry`] 1b09f9d — 1 variant on
7432// `{ kind, path }`, [`crate::layout::layout_nome_only_ctors!`] 3fe3dd7 —
7433// 6 variants on `<Variant>(String)`) and the peer three
7434// [`crate::limits::limits_codec_value_*_ctors!`] codec families (81c856c)
7435// each carry the same discipline on their sibling envelopes.
7436//
7437// The ten variants share the identical `{ <field>: String,
7438// reason: String }` two-slot shape:
7439// - `NomeInvalid { nome, reason }` at [`Caixa::validate_nome`]
7440// (`|reason| ManifestError::NomeInvalid { nome: nome.to_string(),
7441// reason }` inside [`crate::render::require_valid_dns_1123_label`]'s
7442// `on_invalid` bracket-closure slot);
7443// - `NomeChartNameBudgetExceeded { nome, reason }` at
7444// [`Caixa::validate_nome_chart_name_budget`]
7445// (`|reason| ManifestError::NomeChartNameBudgetExceeded { nome:
7446// nome.to_string(), reason }` after
7447// [`crate::render::is_lareira_chart_name_shape`] rejects the offending
7448// `:nome`);
7449// - `VersaoInvalid { versao, reason }` at [`Caixa::validate_versao`]
7450// (`|e| ManifestError::VersaoInvalid { versao: versao.to_string(),
7451// reason: e.to_string() }` after [`semver::Version::parse`] rejects
7452// the offending `:versao`);
7453// - `EtiquetaInvalid { etiqueta, reason }` at
7454// [`Caixa::validate_etiquetas`]
7455// (`|reason| ManifestError::EtiquetaInvalid { etiqueta:
7456// etiqueta.clone(), reason }` after
7457// [`crate::render::is_chart_keyword_shape`] rejects the offending
7458// `:etiquetas` entry);
7459// - `AutorInvalid { autor, reason }` at [`Caixa::validate_autores`]
7460// (`|reason| ManifestError::AutorInvalid { autor: autor.clone(),
7461// reason }` after [`crate::render::is_chart_maintainer_name_shape`]
7462// rejects the offending `:autores` entry);
7463// - `RepositorioInvalid { repositorio, reason }` at
7464// [`Caixa::validate_repositorio`]
7465// (`|reason| ManifestError::RepositorioInvalid { repositorio:
7466// s.to_string(), reason }` after
7467// [`crate::render::is_git_repo_url`] rejects the offending
7468// `:repositorio`);
7469// - `DescricaoInvalid { descricao, reason }` at
7470// [`Caixa::validate_descricao`]
7471// (`|reason| ManifestError::DescricaoInvalid { descricao:
7472// s.to_string(), reason }` after
7473// [`crate::render::is_chart_description_shape`] rejects the offending
7474// `:descricao`);
7475// - `LicencaInvalid { licenca, reason }` at [`Caixa::validate_licenca`]
7476// (`|reason| ManifestError::LicencaInvalid { licenca: s.to_string(),
7477// reason }` after [`crate::render::is_spdx_expression_shape`] rejects
7478// the offending `:licenca`);
7479// - `EdicaoInvalid { edicao, reason }` at [`Caixa::validate_edicao`]
7480// (`return Err(ManifestError::EdicaoInvalid { edicao: s.to_string(),
7481// reason: "must be a 4-digit ASCII decimal year (canonical
7482// \"2026\")".to_string() })` on the direct year-shape arm);
7483// - `RestartWindowMalformed { restart_window, reason }` at
7484// [`Caixa::validate_restart_window`]
7485// (`|reason| ManifestError::RestartWindowMalformed { restart_window:
7486// s.to_string(), reason }` after
7487// [`crate::supervisor::duration_codec::parse`] rejects the offending
7488// `:restart-window` raw string).
7489//
7490// Each opened the identical four-line
7491// `ManifestError::<Variant> { <field>: <val>.to_string() | .clone(),
7492// reason: <expr> }` struct-literal against the caller-side `<field>: &str`
7493// / `<field>: &String` local — the exact "same block re-inlined at every
7494// consumer" shape the PRIME DIRECTIVE names as a bug, on the same altitude
7495// the peer `aplicacao_field_reason_ctors!` / `dep_nome_axis_reason_ctors!`
7496// / `LayoutError` / `LimitsError` / `BehaviorError` / `UpgradeError`
7497// families each closed on their sibling envelopes.
7498//
7499// The macro below generates one `#[must_use]` inherent constructor per
7500// variant of shape `fn <ctor>(<field>: &str, reason: impl Into<String>)
7501// -> Self`, collapsing every site onto one dispatch per arm:
7502// `ManifestError::<ctor>(<val>, <reason>)`, byte-equal to the pre-lift
7503// struct-literal on the same `(<field>, reason)` pair. The uniform
7504// two-field construction (`<field>: <field>.to_string()`,
7505// `reason: reason.into()`) is spelled once — inside the macro — rather
7506// than at every wire-up site. The `reason: impl Into<String>` bound
7507// accepts owned `String` (the parser-shaped reason every predicate
7508// returns via `Result<(), String>`; the `e.to_string()` result the
7509// `semver::Version::parse` arm passes; the literal `"…".to_string()` the
7510// `EdicaoInvalid` direct arm passes), `&str` literals, and `format!(…)`
7511// outputs verbatim so no wire-up site changes its per-arm diagnostic
7512// shape at the lift, matching the peer
7513// [`crate::aplicacao::aplicacao_field_reason_ctors!`] and
7514// [`crate::dep::dep_nome_axis_reason_ctors!`] bounds on the sibling
7515// two- and three-slot envelopes. The `<field>: &str` parameter accepts
7516// both `&str` (from the [`Caixa::nome`] / [`Caixa::versao`] /
7517// [`Caixa::repositorio`] / [`Caixa::descricao`] / [`Caixa::licenca`] /
7518// [`Caixa::edicao`] accessors) and `&String` (from the
7519// [`Caixa::etiquetas`] / [`Caixa::autores`] slice iterators) via Deref
7520// coercion, so every existing wire-up threads through the ctor without a
7521// pre-conversion. `#[must_use]` fires a compile warning at any wire-up
7522// that mistakenly discards the constructed error rather than routing it
7523// through `return Err(…)` / `.map_err(…)` / a closure return.
7524//
7525// Every future consumer that wants to construct one of these ten
7526// variants outside the current in-crate wire-up sites (a deferred
7527// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's per-manifest-axis
7528// admission validators re-checking each declared identity / metadata
7529// axis against a cluster-local snapshot, a future `feira validate
7530// --manifest` per-caixa admission verb re-running the same
7531// value-shape gates on demand, a per-lacre overlay resolver rejecting
7532// an author-supplied manifest override against a cluster-local snapshot
7533// the M4 CR materializer projects, a future
7534// `caixa-registry` per-lacre re-validator at lacre-resolve time
7535// re-checking each declared axis against the same predicates) now
7536// reaches each variant through one call rather than re-inlining the
7537// four-line struct-literal in lockstep with the ten in-crate wire-up
7538// sites.
7539macro_rules! manifest_field_reason_ctors {
7540 ($($ctor:ident => $variant:ident { $field:ident }),* $(,)?) => {
7541 impl ManifestError {
7542 $(
7543 #[doc = concat!(
7544 "Construct a [`ManifestError::",
7545 stringify!($variant),
7546 "`] naming the offending `",
7547 stringify!($field),
7548 "` under the given `reason`. Folds the uniform ",
7549 "`Self::",
7550 stringify!($variant),
7551 " { ",
7552 stringify!($field),
7553 ": ",
7554 stringify!($field),
7555 ".to_string(), reason: reason.into() }` two-slot ",
7556 "construction onto one substrate primitive so every ",
7557 "wire-up on this variant reads through one dispatch ",
7558 "rather than the pre-lift four-line struct-literal ",
7559 "block. `reason` accepts owned `String`, `&str` ",
7560 "literals, and `format!(…)` outputs through the ",
7561 "`impl Into<String>` bound; the `",
7562 stringify!($field),
7563 ": &str` parameter accepts both `&str` and `&String` ",
7564 "via Deref coercion."
7565 )]
7566 #[must_use]
7567 pub fn $ctor($field: &str, reason: impl Into<String>) -> Self {
7568 Self::$variant {
7569 $field: $field.to_string(),
7570 reason: reason.into(),
7571 }
7572 }
7573 )*
7574 }
7575 };
7576}
7577
7578manifest_field_reason_ctors! {
7579 nome_invalid => NomeInvalid { nome },
7580 nome_chart_name_budget_exceeded => NomeChartNameBudgetExceeded { nome },
7581 versao_invalid => VersaoInvalid { versao },
7582 etiqueta_invalid => EtiquetaInvalid { etiqueta },
7583 autor_invalid => AutorInvalid { autor },
7584 repositorio_invalid => RepositorioInvalid { repositorio },
7585 descricao_invalid => DescricaoInvalid { descricao },
7586 licenca_invalid => LicencaInvalid { licenca },
7587 edicao_invalid => EdicaoInvalid { edicao },
7588 restart_window_malformed => RestartWindowMalformed { restart_window },
7589}
7590
7591// Fold the two `ManifestError::{EtiquetaDuplicate, AutorDuplicate}
7592// { <field>: <val>.clone() }` single-`String`-slot wire-up sites at
7593// [`Caixa::validate_etiquetas`] and [`Caixa::validate_autores`] onto one
7594// substrate-primitive family per typed variant — the direct sibling on
7595// the [`ManifestError`] envelope of the peer
7596// [`crate::aplicacao::aplicacao_caixa_only_ctors!`] (d9f6867, 4 variants
7597// on `AplicacaoError` at `ContratoMemberMissing` / `MembroVersaoEmpty` /
7598// `MembroDuplicate` / `MembroIsSelfAplicacao` on the `{ caixa: String }`
7599// shape) and [`crate::aplicacao::aplicacao_path_only_ctors!`] (3ba8de6,
7600// 2 variants on `AplicacaoError` at `EntradaPathNotAbsolute` /
7601// `EntradaPathDuplicate` on the `{ path: String }` shape) on the sibling
7602// M3 mesh `AplicacaoError` envelope, and of the peer
7603// [`crate::supervisor::supervisor_caixa_only_ctors!`] (db09650, 3 variants
7604// on the sibling M2 `SupervisorError` envelope's `{ caixa: String }`
7605// shape), [`crate::dep::dep_nome_only_ctors!`] (792aa92, 5 variants on
7606// `DepError { nome: String }`), and [`crate::upgrade::upgrade_script_only_ctors!`]
7607// (7468ca9, 3 variants on `UpgradeError { script: PathBuf }`) folds on
7608// the sibling envelopes — the last two open-coded single-slot
7609// `{ <field>: String }` struct-literal sites on `ManifestError` fold
7610// onto one substrate primitive per typed variant, matching the
7611// "one substrate primitive per typed variant on the single-slot
7612// `{ <ident>: String }` envelope shape" fold discipline every peer
7613// per-Caixa-identity family already carries.
7614//
7615// Both wire-up sites — one at [`Caixa::validate_etiquetas`]'s per-entry
7616// [`crate::render::insert_first_seen`] dedup closure
7617// (`|| ManifestError::EtiquetaDuplicate { etiqueta: etiqueta.clone() }`
7618// against the per-`:etiquetas` `&String` loop head) and one at
7619// [`Caixa::validate_autores`]'s per-entry [`crate::render::insert_first_seen`]
7620// dedup closure (`|| ManifestError::AutorDuplicate
7621// { autor: autor.clone() }` against the per-`:autores` `&String` loop
7622// head) — opened the identical `ManifestError::<Variant>Duplicate
7623// { <field>: <val>.clone() }` three-line struct-literal against a
7624// caller-side `&String`, the exact "same block re-inlined at every
7625// consumer" shape the PRIME DIRECTIVE names as a bug. The two variants
7626// share one `{ <field>: String }` shape, so the fold routes each wire-up
7627// site through one dispatch per typed variant.
7628//
7629// The macro below generates one `#[must_use]` inherent constructor per
7630// variant of shape `fn <ctor>(<field>: &str) -> ManifestError`, so every
7631// wire-up site collapses onto one dispatch:
7632// `ManifestError::<ctor>(<&str>)`, byte-equal to the pre-lift
7633// struct-literal on the same `&str` fixture. The uniform one-field
7634// construction (`<field>: <field>.to_string()`) is spelled once — inside
7635// the macro — rather than at every wire-up site. The `<field>: &str`
7636// parameter accepts both `&str` and `&String` (via Deref coercion), so
7637// each existing dedup-closure wire-up threading `<val>.as_str()` — or a
7638// bare `&String` head — through the ctor routes through one dispatch
7639// without a pre-conversion, and the `.clone()` the pre-lift wire-up
7640// carried at the closure body folds into the ctor's canonical
7641// `.to_string()` (byte-equal on the same underlying bytes). Every
7642// constructor is `#[must_use]` so a caller who mistakenly discards the
7643// constructed error trips a compile warning at the wire-up site.
7644//
7645// Every future consumer that wants to construct one of these two
7646// variants outside the current in-crate wire-up sites — a deferred
7647// `caixa.pleme.io/v1alpha1/Caixa` CR materializer's admission webhook
7648// re-checking one added/renamed `:etiquetas` / `:autores` entry against
7649// the same dedup axis, a future `feira validate --etiquetas` /
7650// `--autores` per-caixa admission verb re-running the same per-entry
7651// dedup gate on demand, a per-lacre overlay resolver rejecting an
7652// author-supplied duplicate `:etiquetas` / `:autores` entry against a
7653// cluster-local snapshot the M4 CR materializer projects, a future
7654// `caixa-registry` per-lacre re-validator at lacre-resolve time
7655// re-checking each declared list against the same dedup predicate — now
7656// reaches each variant through one call rather than re-inlining the
7657// three-line struct-literal in lockstep with the two in-crate wire-up
7658// sites.
7659macro_rules! manifest_field_only_ctors {
7660 ($($ctor:ident => $variant:ident { $field:ident }),* $(,)?) => {
7661 impl ManifestError {
7662 $(
7663 #[doc = concat!(
7664 "Construct a [`ManifestError::",
7665 stringify!($variant),
7666 "`] naming the offending `",
7667 stringify!($field),
7668 "` entry. Folds the uniform `Self::",
7669 stringify!($variant),
7670 " { ",
7671 stringify!($field),
7672 ": ",
7673 stringify!($field),
7674 ".to_string() }` one-field struct-literal onto one ",
7675 "substrate primitive so every wire-up on this variant ",
7676 "reads through one dispatch rather than the pre-lift ",
7677 "three-line open-coded struct-literal block. The `",
7678 stringify!($field),
7679 ": &str` parameter accepts both `&str` and `&String` ",
7680 "via Deref coercion."
7681 )]
7682 #[must_use]
7683 pub fn $ctor($field: &str) -> Self {
7684 Self::$variant {
7685 $field: $field.to_string(),
7686 }
7687 }
7688 )*
7689 }
7690 };
7691}
7692
7693manifest_field_only_ctors! {
7694 etiqueta_duplicate => EtiquetaDuplicate { etiqueta },
7695 autor_duplicate => AutorDuplicate { autor },
7696}
7697
7698#[cfg(test)]
7699mod tests {
7700 use super::*;
7701
7702 #[test]
7703 fn template_round_trips() {
7704 let src = Caixa::template("demo");
7705 let c = Caixa::from_lisp(&src).expect("template must parse");
7706 assert_eq!(c.nome, "demo");
7707 assert_eq!(c.versao, "0.1.0");
7708 assert_eq!(c.kind, CaixaKind::Biblioteca);
7709 assert_eq!(c.bibliotecas, vec!["lib/demo.lisp".to_string()]);
7710 assert!(c.deps.is_empty());
7711 assert!(c.deps_dev.is_empty());
7712 }
7713
7714 #[test]
7715 fn caixa_universal_axis_scalar_accessor_pair_is_const_fn() {
7716 // Fail-before-pass-after pin on [`Caixa::nome`] +
7717 // [`Caixa::versao`]'s `const`-eval-surface posture. Each
7718 // accessor projects the top-level manifest's per-`:nome` /
7719 // per-`:versao` [`String`] storage through the `pub const fn`
7720 // [`String::as_str`] (const-stable since Rust 1.87, well within
7721 // the workspace MSRV) — any future accidental downgrade to
7722 // non-`const` fails the corresponding `<name>_via_const_fn`
7723 // wrapper at caixa-core build time with E0015 (`cannot call
7724 // non-const method`), strictly stronger than a runtime
7725 // `assert!`. Sibling of the peer per-M2/M3-slot `String → &str`
7726 // scalar-accessor family pins on the sibling `const`-eval-
7727 // surface passes ([`crate::CaixaVersion::as_str`] at the
7728 // typed-newtype wrapper, [`crate::aplicacao::Membro::nome`] /
7729 // [`crate::aplicacao::Membro::versao_requirement`] at the M3
7730 // membership axis, [`crate::aplicacao::Entrada::hostname`] /
7731 // [`crate::aplicacao::Entrada::destination`] at the M3 ingress
7732 // axis, [`crate::supervisor::ChildSpec::nome`] /
7733 // [`crate::supervisor::ChildSpec::versao_requirement`] at the
7734 // M2 supervisor-tree axis,
7735 // [`crate::upgrade::UpgradeFromEntry::prior_versao`] at the M2
7736 // upgrade axis, [`crate::dep::Dep::nome`] /
7737 // [`crate::dep::Dep::versao_requirement`] at the dep-graph
7738 // axis, and the per-`:contratos`
7739 // [`crate::aplicacao::WitContract::source`] /
7740 // [`crate::aplicacao::WitContract::destination`] /
7741 // [`crate::aplicacao::WitContract::world_ref`] trio the
7742 // sibling pin at 279823b already anchors).
7743 const fn nome_via_const_fn(c: &Caixa) -> &str {
7744 c.nome()
7745 }
7746 const fn versao_via_const_fn(c: &Caixa) -> &str {
7747 c.versao()
7748 }
7749 let src = Caixa::template("demo");
7750 let c = Caixa::from_lisp(&src).expect("template must parse");
7751 assert_eq!(nome_via_const_fn(&c), c.nome());
7752 assert_eq!(versao_via_const_fn(&c), c.versao());
7753 assert_eq!(c.nome(), "demo");
7754 assert_eq!(c.versao(), "0.1.0");
7755 }
7756
7757 #[test]
7758 fn caixa_option_string_scalar_accessor_family_is_const_fn() {
7759 // Fail-before-pass-after pin on the five per-`Caixa`
7760 // `Option<String> → Option<&str>` scalar accessors
7761 // ([`Caixa::licenca`] / [`Caixa::repositorio`] /
7762 // [`Caixa::descricao`] / [`Caixa::edicao`] on the top-level
7763 // manifest's optional universal-axis surface, plus
7764 // [`Caixa::restart_window`] on the M2 supervisor-tree
7765 // per-`SupervisorSpec` peer raw-window-string projection axis).
7766 // Each accessor destructures the typed slot's `Option<String>`
7767 // storage through the `match &self.<field> { Some(s) =>
7768 // Some(s.as_str()), None => None }` shape — routing through
7769 // [`String::as_str`] (const-stable since Rust 1.87, well within
7770 // the workspace MSRV) rather than the non-const
7771 // [`Option::as_deref`] the pre-lift bodies carried — and any
7772 // future accidental downgrade to non-`const` fails the
7773 // corresponding `<name>_via_const_fn` wrapper at caixa-core
7774 // build time with E0015 (`cannot call non-const method`),
7775 // strictly stronger than a runtime `assert!` and strictly
7776 // stronger than a module-scope `const _: () = assert!(…)` pin
7777 // (which cannot be formed on a `&Caixa` fixture because the
7778 // type's `String` / `Option<String>` carriers rule out
7779 // `const`-context value construction; the `const fn` wrapper
7780 // is the load-bearing shape that side-steps the destructor-in-
7781 // const restriction on the value axis while still pinning the
7782 // `const`-fn posture on the callee — mirror of the sibling
7783 // [`caixa_universal_axis_scalar_accessor_pair_is_const_fn`]
7784 // pin's discipline verbatim on the peer non-`Option`
7785 // `String → &str` axis at the same struct).
7786 //
7787 // Peer of the sibling per-M2/M3-slot `Option<String> →
7788 // Option<&str>` accessor family pin
7789 // [`m3_option_string_scalar_accessor_family_is_const_fn`] on
7790 // the M3 mesh-slot atom axes ([`WitContract::endpoint`] /
7791 // [`WitContract::subject`] / [`WitContract::slot`] on the
7792 // per-`:contratos` payload-carrier trio,
7793 // [`Placement::shard_key`] / [`Placement::affinity`] on the
7794 // per-`:placement` optional-scalar pair).
7795 const fn licenca_via_const_fn(c: &Caixa) -> Option<&str> {
7796 c.licenca()
7797 }
7798 const fn repositorio_via_const_fn(c: &Caixa) -> Option<&str> {
7799 c.repositorio()
7800 }
7801 const fn descricao_via_const_fn(c: &Caixa) -> Option<&str> {
7802 c.descricao()
7803 }
7804 const fn edicao_via_const_fn(c: &Caixa) -> Option<&str> {
7805 c.edicao()
7806 }
7807 const fn restart_window_via_const_fn(c: &Caixa) -> Option<&str> {
7808 c.restart_window()
7809 }
7810 // Sweep both the `Some`-carrying arm (author-declared slot,
7811 // the byte-string projection payload) and the `None`-carrying
7812 // arm (author-omitted slot, the default-path projection) on
7813 // every accessor so the `const fn` wrapper family pins each
7814 // axis's canonical two-arm partition through the same const
7815 // dispatch as the runtime path.
7816 let mut c1 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7817 c1.licenca = Some("MIT".to_string());
7818 c1.repositorio = Some("https://github.com/pleme-io/demo".to_string());
7819 c1.descricao = Some("demo caixa".to_string());
7820 c1.edicao = Some("2024".to_string());
7821 c1.restart_window = Some("60s".to_string());
7822 assert_eq!(licenca_via_const_fn(&c1), c1.licenca());
7823 assert_eq!(repositorio_via_const_fn(&c1), c1.repositorio());
7824 assert_eq!(descricao_via_const_fn(&c1), c1.descricao());
7825 assert_eq!(edicao_via_const_fn(&c1), c1.edicao());
7826 assert_eq!(restart_window_via_const_fn(&c1), c1.restart_window());
7827 assert_eq!(c1.licenca(), Some("MIT"));
7828 assert_eq!(c1.repositorio(), Some("https://github.com/pleme-io/demo"));
7829 assert_eq!(c1.descricao(), Some("demo caixa"));
7830 assert_eq!(c1.edicao(), Some("2024"));
7831 assert_eq!(c1.restart_window(), Some("60s"));
7832 let mut c2 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7833 c2.licenca = None;
7834 c2.repositorio = None;
7835 c2.descricao = None;
7836 c2.edicao = None;
7837 c2.restart_window = None;
7838 assert_eq!(licenca_via_const_fn(&c2), None);
7839 assert_eq!(repositorio_via_const_fn(&c2), None);
7840 assert_eq!(descricao_via_const_fn(&c2), None);
7841 assert_eq!(edicao_via_const_fn(&c2), None);
7842 assert_eq!(restart_window_via_const_fn(&c2), None);
7843 }
7844
7845 #[test]
7846 fn caixa_outer_copy_return_accessor_pair_is_const_fn() {
7847 // Fail-before-pass-after pin on the two outer-[`Caixa`]
7848 // `Copy`-return accessors — [`Caixa::kind`] on the required
7849 // [`CaixaKind`] enum-discriminant axis and [`Caixa::estrategia`]
7850 // on the M2 supervisor-tree flat-spread `Option<RestartStrategy>`
7851 // axis. Both accessors project a `Copy`-carrier field
7852 // (`CaixaKind: Copy` at caixa-core/src/kind.rs:17,
7853 // `RestartStrategy: Copy` at caixa-core/src/supervisor.rs:33 →
7854 // `Option<RestartStrategy>: Copy`) by value through a bare
7855 // `self.<field>` field-access — no dispatch, no destructor, no
7856 // heap. Any future accidental downgrade to non-`const` fails
7857 // the corresponding `<name>_via_const_fn` wrapper at caixa-core
7858 // build time with E0015 (`cannot call non-const method`),
7859 // strictly stronger than a runtime `assert!` and strictly
7860 // stronger than a module-scope `const _: () = assert!(…)` pin
7861 // (which cannot be formed on a `&Caixa` fixture because the
7862 // type's `String` / `Vec` / `Option<Composite>` carriers rule
7863 // out `const`-context value construction; the `const fn`
7864 // wrapper is the load-bearing shape that side-steps the
7865 // destructor-in-const restriction on the value axis while still
7866 // pinning the `const`-fn posture on the callee — mirror of the
7867 // sibling [`caixa_universal_axis_scalar_accessor_pair_is_const_fn`]
7868 // + [`caixa_option_string_scalar_accessor_family_is_const_fn`]
7869 // pins' discipline verbatim on the peer outer-`Caixa`
7870 // `String → &str` + `Option<String> → Option<&str>` axes at the
7871 // same struct).
7872 //
7873 // Peer of the sibling per-M2/M3-slot `Copy`-return accessor pin
7874 // family on the inner-altitude nested-spec typed-slot
7875 // discriminator axes: [`crate::supervisor::SupervisorSpec::estrategia`]
7876 // + [`crate::supervisor::ChildSpec::restart`] on the M2
7877 // supervisor-tree axis (pinned at 152c868), and
7878 // [`crate::aplicacao::Placement::estrategia`] +
7879 // [`crate::aplicacao::Entrada::port`] on the M3 mesh-slot axis
7880 // (pinned at bafa004) — the outer-`Caixa` altitude is the last
7881 // unlifted altitude for the `Copy`-return-accessor family.
7882 const fn kind_via_const_fn(c: &Caixa) -> CaixaKind {
7883 c.kind()
7884 }
7885 const fn estrategia_via_const_fn(c: &Caixa) -> Option<crate::supervisor::RestartStrategy> {
7886 c.estrategia()
7887 }
7888 // Sweep every arm of both discriminant partitions the accessors
7889 // fan on — every [`CaixaKind`] variant the six-arm required
7890 // discriminant carries (Biblioteca / Binario / Servico /
7891 // Supervisor / Aplicacao / Acao) and both arms of the
7892 // [`Option<RestartStrategy>`] flat-spread supervisor-tree slot
7893 // (`Some(<strategy>)` on an author-declared supervisor and
7894 // `None` on the author-omitted default arm every non-Supervisor
7895 // caixa carries by `#[serde(default)]`) — so the `const fn`
7896 // wrapper family pins the closed-set partition through the
7897 // same const dispatch as the runtime path.
7898 let mut c1 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7899 c1.kind = CaixaKind::Servico;
7900 c1.estrategia = Some(crate::supervisor::RestartStrategy::OneForAll);
7901 assert_eq!(kind_via_const_fn(&c1), c1.kind());
7902 assert_eq!(estrategia_via_const_fn(&c1), c1.estrategia());
7903 assert_eq!(c1.kind(), CaixaKind::Servico);
7904 assert_eq!(
7905 c1.estrategia(),
7906 Some(crate::supervisor::RestartStrategy::OneForAll)
7907 );
7908 let mut c2 = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7909 c2.kind = CaixaKind::Aplicacao;
7910 c2.estrategia = None;
7911 assert_eq!(kind_via_const_fn(&c2), CaixaKind::Aplicacao);
7912 assert_eq!(estrategia_via_const_fn(&c2), None);
7913 // Anchor the remaining discriminant arms so any future
7914 // reordering of [`CaixaKind`]'s six-variant enum surfaces
7915 // through the wrapper dispatch, not just through the direct
7916 // method call.
7917 for kind in [
7918 CaixaKind::Biblioteca,
7919 CaixaKind::Binario,
7920 CaixaKind::Servico,
7921 CaixaKind::Supervisor,
7922 CaixaKind::Aplicacao,
7923 CaixaKind::Acao,
7924 ] {
7925 let mut c = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7926 c.kind = kind;
7927 assert_eq!(kind_via_const_fn(&c), kind);
7928 }
7929 }
7930
7931 #[test]
7932 fn caixa_outer_string_slice_return_accessor_family_is_const_fn() {
7933 // Fail-before-pass-after pin on the five outer-[`Caixa`]
7934 // `Vec<String> → &[String]` slice-return accessors on the
7935 // universal-axis surface — [`Caixa::autores`] / [`Caixa::etiquetas`]
7936 // / [`Caixa::bibliotecas`] / [`Caixa::exe`] / [`Caixa::servicos`].
7937 // Each body is a bare `self.<field>.as_slice()` dispatch through
7938 // [`Vec::as_slice`] (const-stable since Rust 1.87, well within
7939 // the workspace MSRV). Any future accidental downgrade to
7940 // non-`const` fails the corresponding `<name>_via_const_fn`
7941 // wrapper at caixa-core build time with E0015 (`cannot call
7942 // non-const method`) — mirror of the sibling
7943 // [`caixa_outer_copy_return_accessor_pair_is_const_fn`] pin's
7944 // discipline on the peer outer-`Caixa` `Copy`-return accessor
7945 // axis, and peer of the sibling composite-carrier slice-return
7946 // pin below on the peer outer-`Caixa` composite-slice axis.
7947 const fn autores_via_const_fn(c: &Caixa) -> &[String] {
7948 c.autores()
7949 }
7950 const fn etiquetas_via_const_fn(c: &Caixa) -> &[String] {
7951 c.etiquetas()
7952 }
7953 const fn bibliotecas_via_const_fn(c: &Caixa) -> &[String] {
7954 c.bibliotecas()
7955 }
7956 const fn exe_via_const_fn(c: &Caixa) -> &[String] {
7957 c.exe()
7958 }
7959 const fn servicos_via_const_fn(c: &Caixa) -> &[String] {
7960 c.servicos()
7961 }
7962 // Sweep the empty arm (`autores` / `etiquetas` / `exe` /
7963 // `servicos` — the template's `Vec::new()` default) and the
7964 // populated arm (mutated below) on every accessor so the
7965 // `const fn` wrapper family pins each axis's two-arm partition
7966 // through the same const dispatch as the runtime path.
7967 // [`Caixa::template`] seeds `lib/demo.lisp` into `:bibliotecas`,
7968 // so that arm's "empty" fixture is the populated arm the
7969 // mutation sweep covers.
7970 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7971 assert!(autores_via_const_fn(&c_empty).is_empty());
7972 assert!(etiquetas_via_const_fn(&c_empty).is_empty());
7973 assert!(exe_via_const_fn(&c_empty).is_empty());
7974 assert!(servicos_via_const_fn(&c_empty).is_empty());
7975 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
7976 c_full.autores = vec!["ada".to_string(), "erlang".to_string()];
7977 c_full.etiquetas = vec!["compounding".to_string()];
7978 c_full.bibliotecas = vec!["lib/one.lisp".to_string(), "lib/two.lisp".to_string()];
7979 c_full.exe = vec!["exe/cli.lisp".to_string()];
7980 c_full.servicos = vec!["servicos/one.computeunit.yaml".to_string()];
7981 assert_eq!(autores_via_const_fn(&c_full), c_full.autores());
7982 assert_eq!(autores_via_const_fn(&c_full), &["ada", "erlang"]);
7983 assert_eq!(etiquetas_via_const_fn(&c_full), c_full.etiquetas());
7984 assert_eq!(etiquetas_via_const_fn(&c_full), &["compounding"]);
7985 assert_eq!(bibliotecas_via_const_fn(&c_full), c_full.bibliotecas());
7986 assert_eq!(
7987 bibliotecas_via_const_fn(&c_full),
7988 &["lib/one.lisp", "lib/two.lisp"]
7989 );
7990 assert_eq!(exe_via_const_fn(&c_full), c_full.exe());
7991 assert_eq!(exe_via_const_fn(&c_full), &["exe/cli.lisp"]);
7992 assert_eq!(servicos_via_const_fn(&c_full), c_full.servicos());
7993 assert_eq!(
7994 servicos_via_const_fn(&c_full),
7995 &["servicos/one.computeunit.yaml"]
7996 );
7997 }
7998
7999 #[test]
8000 fn caixa_outer_composite_slice_return_accessor_family_is_const_fn() {
8001 // Fail-before-pass-after pin on the six outer-[`Caixa`] composite-
8002 // carrier `Vec<T> → &[T]` slice-return accessors — [`Caixa::deps`]
8003 // / [`Caixa::deps_dev`] on the dep-graph axis,
8004 // [`Caixa::upgrade_from`] on the M2 appup axis, [`Caixa::children`]
8005 // on the M2 supervisor-tree axis, and [`Caixa::membros`] /
8006 // [`Caixa::contratos`] on the M3 mesh-slot axis. Each body is a
8007 // bare `self.<field>.as_slice()` dispatch through
8008 // [`Vec::as_slice`] (const-stable since Rust 1.87, well within
8009 // the workspace MSRV) — peer of the sibling `String`-payload
8010 // slice-return pin above on the peer outer-`Caixa` universal-
8011 // axis surface, and peer of the sibling inner-composite-
8012 // altitude reference-return pin family
8013 // [`crate::aplicacao::tests::m3_aplicacao_spec_reference_return_accessor_family_is_const_fn`]
8014 // + [`crate::supervisor::tests::supervisor_children_slice_return_accessor_is_const_fn`]
8015 // + [`crate::upgrade::tests::upgrade_from_entry_instructions_slice_return_accessor_is_const_fn`]
8016 // (all pinned at 0b23e0f).
8017 const fn deps_via_const_fn(c: &Caixa) -> &[Dep] {
8018 c.deps()
8019 }
8020 const fn deps_dev_via_const_fn(c: &Caixa) -> &[Dep] {
8021 c.deps_dev()
8022 }
8023 const fn upgrade_from_via_const_fn(c: &Caixa) -> &[UpgradeFromEntry] {
8024 c.upgrade_from()
8025 }
8026 const fn children_via_const_fn(c: &Caixa) -> &[crate::supervisor::ChildSpec] {
8027 c.children()
8028 }
8029 const fn membros_via_const_fn(c: &Caixa) -> &[crate::aplicacao::Membro] {
8030 c.membros()
8031 }
8032 const fn contratos_via_const_fn(c: &Caixa) -> &[crate::aplicacao::WitContract] {
8033 c.contratos()
8034 }
8035 // Empty-arm sweep on all six composite-carrier axes — every
8036 // `Caixa::template` starts with `Vec::new()` on each.
8037 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
8038 assert!(deps_via_const_fn(&c_empty).is_empty());
8039 assert!(deps_dev_via_const_fn(&c_empty).is_empty());
8040 assert!(upgrade_from_via_const_fn(&c_empty).is_empty());
8041 assert!(children_via_const_fn(&c_empty).is_empty());
8042 assert!(membros_via_const_fn(&c_empty).is_empty());
8043 assert!(contratos_via_const_fn(&c_empty).is_empty());
8044 // Populate `:membros` / `:contratos` directly via struct literals
8045 // — the parser-side validation path fans on `:kind`-gated cross-
8046 // slot invariants irrelevant to the accessor dispatch under test.
8047 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
8048 c_full.membros = vec![
8049 crate::aplicacao::Membro {
8050 caixa: "demo-a".to_string(),
8051 versao: "^0.1.0".to_string(),
8052 },
8053 crate::aplicacao::Membro {
8054 caixa: "demo-b".to_string(),
8055 versao: "^0.2.0".to_string(),
8056 },
8057 ];
8058 c_full.contratos = vec![crate::aplicacao::WitContract {
8059 de: "demo-a".to_string(),
8060 para: "demo-b".to_string(),
8061 wit: "wasi:http/proxy".to_string(),
8062 endpoint: Some("/edge".to_string()),
8063 subject: None,
8064 slot: None,
8065 }];
8066 assert_eq!(membros_via_const_fn(&c_full), c_full.membros());
8067 assert_eq!(contratos_via_const_fn(&c_full), c_full.contratos());
8068 assert_eq!(membros_via_const_fn(&c_full).len(), 2);
8069 assert_eq!(contratos_via_const_fn(&c_full).len(), 1);
8070 // Alias-borrow check on the four remaining composite-carrier
8071 // slice-return arms — the wrapper's return borrow must alias the
8072 // caller's borrow so any future accessor re-routing that skips
8073 // the storage field surfaces through the assertion.
8074 assert!(std::ptr::eq(deps_via_const_fn(&c_full), c_full.deps()));
8075 assert!(std::ptr::eq(
8076 deps_dev_via_const_fn(&c_full),
8077 c_full.deps_dev()
8078 ));
8079 assert!(std::ptr::eq(
8080 upgrade_from_via_const_fn(&c_full),
8081 c_full.upgrade_from()
8082 ));
8083 assert!(std::ptr::eq(
8084 children_via_const_fn(&c_full),
8085 c_full.children()
8086 ));
8087 }
8088
8089 #[test]
8090 fn caixa_outer_option_composite_reference_return_accessor_family_is_const_fn() {
8091 // Fail-before-pass-after pin on the six outer-[`Caixa`]
8092 // `Option<Composite> → Option<&Composite>` reference-return
8093 // accessors — [`Caixa::limits`] / [`Caixa::behavior`] on the M2
8094 // Servico-runtime typed-slot axis, [`Caixa::politicas`] /
8095 // [`Caixa::placement`] / [`Caixa::entrada`] on the M3 mesh-slot
8096 // axis, and [`Caixa::ci`] on the Acao-kind typed-CI-run axis.
8097 // Each body is a bare `self.<field>.as_ref()` dispatch through
8098 // [`Option::as_ref`] (const-stable since Rust 1.83, well within
8099 // the workspace MSRV of 1.89). Any future accidental downgrade
8100 // to non-`const` fails the corresponding `<name>_via_const_fn`
8101 // wrapper at caixa-core build time with E0015 (`cannot call
8102 // non-const method`), strictly stronger than a runtime `assert!`
8103 // and strictly stronger than a module-scope `const _: () =
8104 // assert!(…)` pin (which cannot be formed on a `&Caixa` fixture
8105 // because the type's `String` / `Vec` / `Option<Composite>`
8106 // carriers rule out `const`-context value construction; the
8107 // `const fn` wrapper is the load-bearing shape that side-steps
8108 // the destructor-in-const restriction on the value axis while
8109 // still pinning the `const`-fn posture on the callee — mirror
8110 // of the sibling
8111 // [`caixa_outer_copy_return_accessor_pair_is_const_fn`] +
8112 // [`caixa_outer_string_slice_return_accessor_family_is_const_fn`] +
8113 // [`caixa_outer_composite_slice_return_accessor_family_is_const_fn`]
8114 // pins' discipline verbatim on the peer outer-`Caixa` axes at
8115 // the same struct).
8116 //
8117 // Closes the outer-`Caixa` `Option<&Composite>` composite-
8118 // reference-return sub-family — the last unlifted altitude on
8119 // the outer-`Caixa` accessor-family const-eval surface after
8120 // the sibling `Copy`-return / universal-axis-`&str` /
8121 // `Option<&str>` / `&[String]` / composite-`&[T]` pins already
8122 // closed the sibling arms at 866d1d5 / 29c5d7e / 0650f64 /
8123 // 231a968 (the last of these pins the `Vec<T> → &[T]`
8124 // composite-slice arm the six accessors here close as their
8125 // `Option<Composite> → Option<&Composite>` peer). Peer of the
8126 // sibling inner-altitude nested-spec composite-reference-return
8127 // pin family — [`crate::AplicacaoSpec::politicas`] /
8128 // [`crate::AplicacaoSpec::placement`] /
8129 // [`crate::AplicacaoSpec::entrada`] on the inner
8130 // [`crate::AplicacaoSpec`] altitude (already `pub const fn`
8131 // per 0b23e0f), and the outer-`Caixa` altitude here now carries
8132 // the same shape so both altitudes of the reference-return
8133 // discipline (per-`Caixa` outer-slot presence + per-
8134 // `AplicacaoSpec` inner-slot presence) route through one typed
8135 // const dispatch on the substrate primitive.
8136 const fn limits_via_const_fn(c: &Caixa) -> Option<&LimitsSpec> {
8137 c.limits()
8138 }
8139 const fn behavior_via_const_fn(c: &Caixa) -> Option<&crate::BehaviorSpec> {
8140 c.behavior()
8141 }
8142 const fn politicas_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::MeshPolicy> {
8143 c.politicas()
8144 }
8145 const fn placement_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::Placement> {
8146 c.placement()
8147 }
8148 const fn entrada_via_const_fn(c: &Caixa) -> Option<&crate::aplicacao::Entrada> {
8149 c.entrada()
8150 }
8151 const fn ci_via_const_fn(c: &Caixa) -> Option<&canteiro_types::CiRun> {
8152 c.ci()
8153 }
8154 // Both-arm sweep on every accessor: the `None` author-omitted
8155 // arm (template default — no M2/M3/CI slot declared) and the
8156 // `Some(<composite>)` authored arm (mutated below via struct-
8157 // literal seeds, side-stepping the parser-side `:kind`-gated
8158 // cross-slot invariants irrelevant to the accessor dispatch
8159 // under test). Both arms route through the `const fn` wrapper
8160 // family so the two-arm `Option` partition is pinned through
8161 // the same const dispatch as the runtime path.
8162 let c_empty = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
8163 assert!(limits_via_const_fn(&c_empty).is_none());
8164 assert!(behavior_via_const_fn(&c_empty).is_none());
8165 assert!(politicas_via_const_fn(&c_empty).is_none());
8166 assert!(placement_via_const_fn(&c_empty).is_none());
8167 assert!(entrada_via_const_fn(&c_empty).is_none());
8168 assert!(ci_via_const_fn(&c_empty).is_none());
8169 let mut c_full = Caixa::from_lisp(&Caixa::template("demo")).expect("template must parse");
8170 c_full.limits = Some(LimitsSpec::default());
8171 c_full.behavior = Some(crate::BehaviorSpec::default());
8172 c_full.politicas = Some(crate::aplicacao::MeshPolicy::default());
8173 c_full.placement = Some(crate::aplicacao::Placement::default());
8174 c_full.entrada = Some(crate::aplicacao::Entrada {
8175 host: "demo.quero.cloud".to_string(),
8176 para: "demo".to_string(),
8177 paths: Vec::new(),
8178 port: crate::aplicacao::DEFAULT_SERVICO_PORT,
8179 });
8180 c_full.ci = Some(canteiro_types::CiRun {
8181 workspace: "pleme-io".into(),
8182 repo: "caixa".into(),
8183 nodes: vec![],
8184 });
8185 assert!(limits_via_const_fn(&c_full).is_some());
8186 assert!(behavior_via_const_fn(&c_full).is_some());
8187 assert!(politicas_via_const_fn(&c_full).is_some());
8188 assert!(placement_via_const_fn(&c_full).is_some());
8189 assert!(entrada_via_const_fn(&c_full).is_some());
8190 assert!(ci_via_const_fn(&c_full).is_some());
8191 // Alias-borrow check on every arm: the wrapper's inner-`Option`
8192 // reference must alias the caller's borrow so any future accessor
8193 // re-routing that skips the storage field surfaces through the
8194 // assertion.
8195 assert!(std::ptr::eq(
8196 limits_via_const_fn(&c_full).unwrap(),
8197 c_full.limits().unwrap()
8198 ));
8199 assert!(std::ptr::eq(
8200 behavior_via_const_fn(&c_full).unwrap(),
8201 c_full.behavior().unwrap()
8202 ));
8203 assert!(std::ptr::eq(
8204 politicas_via_const_fn(&c_full).unwrap(),
8205 c_full.politicas().unwrap()
8206 ));
8207 assert!(std::ptr::eq(
8208 placement_via_const_fn(&c_full).unwrap(),
8209 c_full.placement().unwrap()
8210 ));
8211 assert!(std::ptr::eq(
8212 entrada_via_const_fn(&c_full).unwrap(),
8213 c_full.entrada().unwrap()
8214 ));
8215 assert!(std::ptr::eq(
8216 ci_via_const_fn(&c_full).unwrap(),
8217 c_full.ci().unwrap()
8218 ));
8219 }
8220
8221 #[test]
8222 fn register_populates_registry() {
8223 Caixa::register().expect("first register call in this test process must succeed");
8224 let kws = tatara_lisp::domain::registered_keywords();
8225 assert!(kws.contains(&"defcaixa"));
8226 }
8227
8228 #[test]
8229 fn to_lisp_round_trips() {
8230 let src = Caixa::template("demo");
8231 let c1 = Caixa::from_lisp(&src).unwrap();
8232 let emitted = c1.to_lisp();
8233 let c2 = Caixa::from_lisp(&emitted).expect("emitted lisp parses back");
8234 assert_eq!(c1, c2);
8235 }
8236
8237 // ── DialetoEstrangeiro carries a single typed axis ────────────────────
8238 //
8239 // The compounding pin: the variant stores only the typed
8240 // [`crate::dialeto::CaixaDialeto`], and every user-facing byte-string
8241 // (canonical keyword, description, consumer) routes through the enum's
8242 // own accessors at Display time. Prior to that closure the variant
8243 // carried each accessor's return value as a stored `&'static str`
8244 // snapshot alongside `dialeto`; a caller could construct the variant
8245 // with a snapshot that drifted from what `dialeto`'s accessors would
8246 // return, and every downstream user-facing projection would silently
8247 // disagree with the classification. Storing only the axis makes the
8248 // drift structurally impossible.
8249
8250 #[test]
8251 fn dialeto_estrangeiro_variant_carries_only_the_typed_dialeto_axis() {
8252 // Single-field construction is the whole compounding shape — a
8253 // future re-introduction of a snapshot field (a `palavra_canonica:
8254 // &'static str`, a stored `descricao:`, a stored `consumidor:`)
8255 // would re-open the drift surface and this construction would fail
8256 // to compile with "missing field" until every snapshot was seeded
8257 // at the call site again. The compile-time guarantee is the
8258 // invariant; the assertion below only witnesses that the
8259 // construction is well-formed after the closure.
8260 let err = LeituraError::DialetoEstrangeiro {
8261 dialeto: crate::dialeto::CaixaDialeto::Molde,
8262 };
8263 assert!(matches!(
8264 err,
8265 LeituraError::DialetoEstrangeiro {
8266 dialeto: crate::dialeto::CaixaDialeto::Molde,
8267 }
8268 ));
8269 }
8270
8271 #[test]
8272 fn dialeto_estrangeiro_display_routes_through_typed_dialeto_accessors() {
8273 // For every foreign-dialect classification the variant surfaces —
8274 // [`crate::dialeto::CaixaDialeto::Molde`] and
8275 // [`crate::dialeto::CaixaDialeto::MoldePosicional`], the two
8276 // variants [`Caixa::from_lisp`] raises this error for — the
8277 // rendered [`std::fmt::Display`] byte-string must interpolate each
8278 // typed accessor's return verbatim. A future re-introduction of a
8279 // stored `&'static str` snapshot alongside `dialeto` that Display
8280 // read instead of the accessor would fail this pin as soon as the
8281 // two disagreed; a future accessor rebrand (a per-dialect
8282 // consumer rename, a canonical-keyword shift once the substrate
8283 // migration named in [`crate::dialeto`] completes) reaches every
8284 // consumer through one typed dispatch and this pin verifies the
8285 // display path is one of them.
8286 for d in [
8287 crate::dialeto::CaixaDialeto::Molde,
8288 crate::dialeto::CaixaDialeto::MoldePosicional,
8289 ] {
8290 let rendered = LeituraError::DialetoEstrangeiro { dialeto: d }.to_string();
8291 assert!(
8292 rendered.contains(d.palavra_canonica()),
8293 "Display must interpolate `dialeto.palavra_canonica()` \
8294 verbatim — a stored snapshot would silently drift from \
8295 the typed accessor. dialect: {d}, rendered: {rendered:?}"
8296 );
8297 assert!(
8298 rendered.contains(d.descricao()),
8299 "Display must interpolate `dialeto.descricao()` verbatim. \
8300 dialect: {d}, rendered: {rendered:?}"
8301 );
8302 assert!(
8303 rendered.contains(d.consumidor()),
8304 "Display must interpolate `dialeto.consumidor()` verbatim. \
8305 dialect: {d}, rendered: {rendered:?}"
8306 );
8307 }
8308 }
8309
8310 #[test]
8311 fn from_lisp_rejects_molde_dialect_via_typed_variant() {
8312 // The end-to-end pin the compounding closure defends: a
8313 // Molde-dialect source lands as [`LeituraError::DialetoEstrangeiro`]
8314 // carrying [`crate::dialeto::CaixaDialeto::Molde`], and the
8315 // rendered Display byte-string names the Molde accessors'
8316 // returns verbatim. Any future path that constructed the variant
8317 // with a mismatched snapshot (a stored `palavra_canonica:
8318 // "defcaixa"` on a `Molde` classification) would land Display
8319 // pointing at `defcaixa` while the typed axis said `Molde` — the
8320 // exact drift the closure removes.
8321 let src = r#"
8322 (defcaixa
8323 :name "x"
8324 :kind :Biblioteca
8325 :ecosystem :rust-single-crate
8326 :package {:name "x" :version "0.1.0"})
8327 "#;
8328 let err = Caixa::from_lisp(src).expect_err("Molde dialect must not parse as Pacote");
8329 match err {
8330 LeituraError::DialetoEstrangeiro { dialeto } => {
8331 assert_eq!(dialeto, crate::dialeto::CaixaDialeto::Molde);
8332 let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
8333 assert!(rendered.contains(dialeto.palavra_canonica()));
8334 assert!(rendered.contains(dialeto.consumidor()));
8335 assert!(rendered.contains(dialeto.descricao()));
8336 }
8337 other => panic!("expected DialetoEstrangeiro, got {other:?}"),
8338 }
8339 }
8340
8341 #[test]
8342 fn from_lisp_rejects_molde_posicional_dialect_via_typed_variant() {
8343 // Coverage pin for the [`crate::dialeto::CaixaDialeto::MoldePosicional`]
8344 // arm of the [`Caixa::from_lisp`] foreign-dialect gate — the
8345 // positional-arity `defmolde` form written under a `(defcaixa …)`
8346 // head (`(defcaixa todoku-go :kind :Biblioteca :ecosystem :go
8347 // …)`). Pre-lift this arm rode the same `foreign =>` wildcard
8348 // the [`crate::dialeto::CaixaDialeto::Molde`] sibling arm rode,
8349 // so no test exercised the positional-arity path through
8350 // `Caixa::from_lisp` specifically; the sibling
8351 // [`from_lisp_rejects_molde_dialect_via_typed_variant`] only
8352 // covered [`crate::dialeto::CaixaDialeto::Molde`]. Post-lift the
8353 // two arms route through the lifted
8354 // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
8355 // typed predicate — the same predicate the pre-lift `foreign =>`
8356 // wildcard resolved to today — and this pin makes the
8357 // positional-arity arm's byte-shape at the gate explicit rather
8358 // than implied by wildcard-absorption. A future regression that
8359 // silently reordered [`crate::dialeto::CaixaDialeto::is_molde_family`]'s
8360 // arm-set (dropped [`crate::dialeto::CaixaDialeto::MoldePosicional`]
8361 // from the two-arity closure) would fail this pin at caixa-core
8362 // test time rather than surfacing far from the change as a
8363 // `caixa.lisp` carrying a `(defcaixa todoku-go :ecosystem :go
8364 // …)` silently parsing past the derive.
8365 let src = r#"
8366 (defcaixa todoku-go
8367 :kind :Biblioteca
8368 :ecosystem :go
8369 :package {:name "todoku-go" :version "0.3.0"})
8370 "#;
8371 let err =
8372 Caixa::from_lisp(src).expect_err("MoldePosicional dialect must not parse as Pacote");
8373 match err {
8374 LeituraError::DialetoEstrangeiro { dialeto } => {
8375 assert_eq!(
8376 dialeto,
8377 crate::dialeto::CaixaDialeto::MoldePosicional,
8378 "DialetoEstrangeiro must carry the MoldePosicional \
8379 variant verbatim — the positional-arity `defmolde` \
8380 form under a `(defcaixa …)` head is the \
8381 `MoldePosicional` arm's canonical byte-shape"
8382 );
8383 let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
8384 assert!(
8385 rendered.contains(dialeto.palavra_canonica()),
8386 "Display must interpolate `dialeto.palavra_canonica()` \
8387 verbatim on the MoldePosicional arm; rendered: \
8388 {rendered:?}"
8389 );
8390 assert!(
8391 rendered.contains(dialeto.consumidor()),
8392 "Display must interpolate `dialeto.consumidor()` \
8393 verbatim on the MoldePosicional arm; rendered: \
8394 {rendered:?}"
8395 );
8396 assert!(
8397 rendered.contains(dialeto.descricao()),
8398 "Display must interpolate `dialeto.descricao()` \
8399 verbatim on the MoldePosicional arm; rendered: \
8400 {rendered:?}"
8401 );
8402 }
8403 other => panic!("expected DialetoEstrangeiro, got {other:?}"),
8404 }
8405 }
8406
8407 #[test]
8408 fn from_lisp_dialect_gate_dispatches_through_caixa_dialeto_is_molde_family_predicate() {
8409 // Load-bearing byte-parity pin: for every arm in
8410 // [`crate::dialeto::CaixaDialeto::ALL`], the
8411 // [`Caixa::from_lisp`] foreign-dialect gate's DialetoEstrangeiro
8412 // partition must agree with the lifted
8413 // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
8414 // typed predicate — i.e. from_lisp raises
8415 // [`LeituraError::DialetoEstrangeiro`] carrying `d` iff
8416 // `d.is_molde_family()` returns `true`, and does NOT raise
8417 // [`LeituraError::DialetoEstrangeiro`] on any arm where the
8418 // predicate returns `false` (the arm's source falls through to
8419 // the derive — parses cleanly on
8420 // [`crate::dialeto::CaixaDialeto::Pacote`], surfaces a
8421 // [`LeituraError::Leitura`] on
8422 // [`crate::dialeto::CaixaDialeto::Desconhecido`]).
8423 //
8424 // Pre-lift the gate hand-rolled a three-arm match
8425 // (`Pacote => {}`, `Desconhecido => {}`, `foreign => Err(…)`)
8426 // whose `foreign =>` wildcard expressed no compile-time link
8427 // back to the substrate primitive's arm-family; a future fifth
8428 // dialect the [`crate::dialeto`] module doc's "third dialect"
8429 // hazard actualises would fall silently onto the wildcard
8430 // regardless of whether it belonged to the `defmolde` family or
8431 // to a distinct `defcaixa`-family. Post-lift the partition
8432 // resolves through
8433 // [`crate::dialeto::CaixaDialeto::is_molde_family`]'s single
8434 // typed dispatch, and this pin refuses any future regression
8435 // that silently split the from_lisp partition from the typed
8436 // predicate — the two paths now migrate as one on any future
8437 // arm addition.
8438 //
8439 // Sibling in shape to the peer
8440 // [`crate::dialeto::tests::caixa_dialeto_is_molde_family_agrees_with_palavra_canonica_defmolde_projection`]
8441 // (e9d2315) that pins the same byte-parity between
8442 // [`crate::dialeto::CaixaDialeto::is_molde_family`] and the
8443 // sibling [`crate::dialeto::CaixaDialeto::palavra_canonica`]
8444 // `== "defmolde"` classifier — extends the discipline from the
8445 // two paths within the [`crate::dialeto`] primitive onto the
8446 // third external consumer of the `defmolde`-family partition
8447 // (the [`Caixa::from_lisp`] gate that raises
8448 // [`LeituraError::DialetoEstrangeiro`]).
8449 let fixtures: &[(crate::dialeto::CaixaDialeto, &str)] = &[
8450 (
8451 crate::dialeto::CaixaDialeto::Pacote,
8452 r#"
8453 (defcaixa
8454 :nome "checkout"
8455 :versao "0.1.0"
8456 :kind Biblioteca
8457 :edicao "2026"
8458 :descricao "canonical Pacote source"
8459 :autores ()
8460 :etiquetas ()
8461 :deps ()
8462 :deps-dev ()
8463 :bibliotecas ("lib/checkout.lisp"))
8464 "#,
8465 ),
8466 (
8467 crate::dialeto::CaixaDialeto::Molde,
8468 r#"
8469 (defcaixa
8470 :name "base64"
8471 :kind :Biblioteca
8472 :ecosystem :rust-single-crate
8473 :package {:name "base64" :version "0.22.1"}
8474 :workflows [:auto-release])
8475 "#,
8476 ),
8477 (
8478 crate::dialeto::CaixaDialeto::MoldePosicional,
8479 r#"
8480 (defcaixa todoku-go
8481 :kind :Biblioteca
8482 :ecosystem :go
8483 :package {:name "todoku-go" :version "0.3.0"})
8484 "#,
8485 ),
8486 (
8487 crate::dialeto::CaixaDialeto::Desconhecido,
8488 r#"(defcaixa :licenca "MIT")"#,
8489 ),
8490 ];
8491
8492 // Coverage: every arm in [`crate::dialeto::CaixaDialeto::ALL`]
8493 // must appear in the fixture table so the pin's arm-set stays
8494 // synchronised with the enum's arm-set. Fails at test time if a
8495 // future fifth arm added to [`crate::dialeto::CaixaDialeto`]
8496 // (with a corresponding `is_molde_family` return) forgot to
8497 // extend this fixture table with a canonical source for the new
8498 // arm — the pin cannot cover an arm it has no source for.
8499 for &expected in crate::dialeto::CaixaDialeto::ALL {
8500 assert!(
8501 fixtures.iter().any(|(d, _)| *d == expected),
8502 "fixture table must carry a canonical source for every \
8503 CaixaDialeto arm; missing: {expected:?}"
8504 );
8505 }
8506
8507 for &(expected_dialect, src) in fixtures {
8508 let classified = crate::dialeto::classify(src.trim()).unwrap_or_else(|err| {
8509 panic!(
8510 "fixture source for {expected_dialect:?} must classify \
8511 cleanly, got err: {err:?}"
8512 )
8513 });
8514 assert_eq!(
8515 classified, expected_dialect,
8516 "fixture source for {expected_dialect:?} must classify as \
8517 {expected_dialect:?} (drift here defeats the byte-parity \
8518 pin below — a source labelled for one arm but classifying \
8519 as another would silently satisfy or violate the pin for \
8520 the wrong reason)"
8521 );
8522
8523 let outcome = Caixa::from_lisp(src);
8524 match (expected_dialect.is_molde_family(), &outcome) {
8525 (true, Err(LeituraError::DialetoEstrangeiro { dialeto })) => {
8526 assert_eq!(
8527 *dialeto, expected_dialect,
8528 "DialetoEstrangeiro must carry the same typed arm \
8529 the classifier returned — a drift here would let \
8530 from_lisp raise the error while pointing at the \
8531 wrong dialect (e.g. rejecting a \
8532 MoldePosicional source as Molde). arm: \
8533 {expected_dialect:?}"
8534 );
8535 }
8536 (true, other) => panic!(
8537 "arm {expected_dialect:?} has is_molde_family() = true \
8538 so from_lisp must raise DialetoEstrangeiro carrying \
8539 {expected_dialect:?}; got: {other:?}"
8540 ),
8541 (false, Err(LeituraError::DialetoEstrangeiro { dialeto })) => panic!(
8542 "arm {expected_dialect:?} has is_molde_family() = false \
8543 so from_lisp must NOT raise DialetoEstrangeiro; got \
8544 one carrying: {dialeto:?}. This means the typed \
8545 predicate and the from_lisp partition disagree on \
8546 this arm — exactly the drift this pin refuses."
8547 ),
8548 (false, _) => {
8549 // A non-molde arm's source falls through to the
8550 // derive: Pacote sources parse to Ok(_); Desconhecido
8551 // sources surface as LeituraError::Leitura from the
8552 // derive's own unknown-keyword rejection. Either
8553 // shape is acceptable here — the pin's promise is
8554 // narrower: "no DialetoEstrangeiro on
8555 // is_molde_family() == false".
8556 }
8557 }
8558 }
8559 }
8560
8561 // ── M2 typed-substrate slot tests (limits, behavior, upgrade-from, supervisor) ──
8562
8563 #[test]
8564 fn limits_round_trip_via_json() {
8565 use crate::LimitsSpec;
8566 use std::time::Duration;
8567 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8568 c.limits = Some(LimitsSpec {
8569 memory: Some(64 * 1024 * 1024),
8570 fuel: Some(1_000_000),
8571 wall_clock: Some(Duration::from_secs(30)),
8572 cpu: Some(500),
8573 });
8574 let json = serde_json::to_string(&c).unwrap();
8575 assert!(json.contains("\"limits\""));
8576 assert!(json.contains("\"64MiB\""));
8577 assert!(json.contains("\"30s\""));
8578 assert!(json.contains("\"500m\""));
8579 let back: Caixa = serde_json::from_str(&json).unwrap();
8580 assert_eq!(c.limits, back.limits);
8581 }
8582
8583 #[test]
8584 fn behavior_round_trip_via_json() {
8585 use crate::BehaviorSpec;
8586 use std::path::PathBuf;
8587 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8588 c.behavior = Some(BehaviorSpec {
8589 on_init: Some(PathBuf::from("lib/init.lisp")),
8590 on_call: Some(PathBuf::from("lib/handlers.lisp")),
8591 ..Default::default()
8592 });
8593 let json = serde_json::to_string(&c).unwrap();
8594 let back: Caixa = serde_json::from_str(&json).unwrap();
8595 assert_eq!(c.behavior, back.behavior);
8596 }
8597
8598 #[test]
8599 fn upgrade_from_round_trip_via_json() {
8600 use crate::{UpgradeFromEntry, UpgradeInstruction};
8601 use std::path::PathBuf;
8602 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8603 c.upgrade_from = vec![UpgradeFromEntry {
8604 from: "0.1.0".into(),
8605 instructions: vec![
8606 UpgradeInstruction::LoadModule {
8607 module: "demo".into(),
8608 },
8609 UpgradeInstruction::StateChange {
8610 script: PathBuf::from("lib/migrations/v01-to-v02.lisp"),
8611 },
8612 UpgradeInstruction::SoftPurge {
8613 module: "demo-old".into(),
8614 },
8615 ],
8616 }];
8617 let json = serde_json::to_string(&c).unwrap();
8618 let back: Caixa = serde_json::from_str(&json).unwrap();
8619 assert_eq!(c.upgrade_from, back.upgrade_from);
8620 }
8621
8622 #[test]
8623 fn supervisor_view_returns_typed_shape() {
8624 use crate::{ChildSpec, RestartPolicy, RestartStrategy};
8625 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
8626 c.kind = CaixaKind::Supervisor;
8627 c.bibliotecas.clear();
8628 c.estrategia = Some(RestartStrategy::OneForOne);
8629 c.max_restarts = Some(5);
8630 c.restart_window = Some("60s".into());
8631 c.children = vec![ChildSpec {
8632 caixa: "worker".into(),
8633 versao: "^0.1".into(),
8634 restart: RestartPolicy::Permanent,
8635 }];
8636 let view = c.supervisor_view().expect("Supervisor kind has a view");
8637 assert_eq!(view.estrategia, RestartStrategy::OneForOne);
8638 assert_eq!(view.max_restarts, 5);
8639 assert_eq!(
8640 view.restart_window,
8641 Some(std::time::Duration::from_secs(60))
8642 );
8643 assert_eq!(view.children.len(), 1);
8644 view.validate().unwrap();
8645 }
8646
8647 #[test]
8648 fn supervisor_view_none_for_non_supervisor_kinds() {
8649 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8650 assert!(c.supervisor_view().is_none());
8651 }
8652
8653 #[test]
8654 fn declared_mesh_slots_empty_for_bare_caixa() {
8655 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8656 assert!(c.declared_mesh_slots().is_empty());
8657 }
8658
8659 #[test]
8660 fn declared_mesh_slots_reports_only_set_slots_in_canonical_order() {
8661 use crate::{Entrada, Membro};
8662 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8663 // Set a non-adjacent pair (:membros + :entrada) to pin that the
8664 // canonical declaration order is preserved regardless of which
8665 // subset is populated.
8666 c.membros = vec![Membro {
8667 caixa: "a".into(),
8668 versao: "^0.1".into(),
8669 }];
8670 c.entrada = Some(Entrada {
8671 host: "x.example.com".into(),
8672 para: "a".into(),
8673 paths: vec![],
8674 port: 8080,
8675 });
8676 assert_eq!(
8677 c.declared_mesh_slots(),
8678 vec![
8679 crate::render::M3_AUTHOR_KEY_MEMBROS,
8680 crate::render::M3_AUTHOR_KEY_ENTRADA,
8681 ]
8682 );
8683 }
8684
8685 #[test]
8686 fn m3_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
8687 // Scalar-value pin: the five author-facing kebab-case labels the
8688 // `(defcaixa … :<slot> (…))` surface admits on the M3 top-level
8689 // mesh slot axis, one arm per typed slot. Mirrors the peer
8690 // scalar-value pin the sibling
8691 // [`crate::M2_AUTHOR_KEY_LIMITS`] /
8692 // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
8693 // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] M2 top-level slot consts
8694 // carry (f49c8b0), so both altitudes of the typed-slot algebra
8695 // (per-Servico M2 + per-Aplicacao M3) share the same
8696 // "one canonical byte-string per arm" discipline. A future
8697 // rebrand (`:membros` → `:members`, `:contratos` → `:contracts`,
8698 // `:politicas` → `:policies`, `:placement` → `:distribution`,
8699 // `:entrada` → `:ingress`) lands as an edit to exactly one const,
8700 // and every consumer that reaches for the label picks it up at
8701 // build time rather than at runtime as a downstream mismatch.
8702 assert_eq!(crate::render::M3_AUTHOR_KEY_MEMBROS, ":membros");
8703 assert_eq!(crate::render::M3_AUTHOR_KEY_CONTRATOS, ":contratos");
8704 assert_eq!(crate::render::M3_AUTHOR_KEY_POLITICAS, ":politicas");
8705 assert_eq!(crate::render::M3_AUTHOR_KEY_PLACEMENT, ":placement");
8706 assert_eq!(crate::render::M3_AUTHOR_KEY_ENTRADA, ":entrada");
8707 }
8708
8709 #[test]
8710 fn declared_mesh_slots_route_through_lifted_m3_author_key_consts() {
8711 // Production-through-const pin: the five per-arm labels the
8712 // [`Caixa::declared_mesh_slots`] tagger pushes onto its return
8713 // `Vec` route through the lifted
8714 // [`crate::M3_AUTHOR_KEY_MEMBROS`] /
8715 // [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
8716 // [`crate::M3_AUTHOR_KEY_POLITICAS`] /
8717 // [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
8718 // [`crate::M3_AUTHOR_KEY_ENTRADA`] consts, in canonical
8719 // declaration order. A future re-order or drift at the tagger
8720 // (a rename that reaches the tagger but not the const, or vice
8721 // versa) surfaces here at build time rather than at runtime as
8722 // a [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
8723 // `slots: <stale-kebab-case>` diagnostic far from the rename's
8724 // commit. Mirror of the peer
8725 // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
8726 // pin (f49c8b0) on the sibling per-Servico M2 top-level slot
8727 // axis.
8728 use crate::{Entrada, Membro, MeshPolicy, Placement, PlacementStrategy, WitContract};
8729 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8730 c.membros = vec![Membro {
8731 caixa: "a".into(),
8732 versao: "^0.1".into(),
8733 }];
8734 c.contratos = vec![WitContract {
8735 de: "a".into(),
8736 para: "a".into(),
8737 wit: "wasi:http/proxy".into(),
8738 endpoint: Some("/x".into()),
8739 subject: None,
8740 slot: None,
8741 }];
8742 c.politicas = Some(MeshPolicy::default());
8743 c.placement = Some(Placement {
8744 estrategia: PlacementStrategy::Replicated,
8745 clusters: vec!["rio".into()],
8746 affinity: None,
8747 shard_key: None,
8748 });
8749 c.entrada = Some(Entrada {
8750 host: "x.example.com".into(),
8751 para: "a".into(),
8752 paths: vec![],
8753 port: 8080,
8754 });
8755 assert_eq!(
8756 c.declared_mesh_slots(),
8757 vec![
8758 crate::render::M3_AUTHOR_KEY_MEMBROS,
8759 crate::render::M3_AUTHOR_KEY_CONTRATOS,
8760 crate::render::M3_AUTHOR_KEY_POLITICAS,
8761 crate::render::M3_AUTHOR_KEY_PLACEMENT,
8762 crate::render::M3_AUTHOR_KEY_ENTRADA,
8763 ]
8764 );
8765 }
8766
8767 #[test]
8768 fn declared_supervisor_slots_empty_for_bare_caixa() {
8769 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8770 assert!(c.declared_supervisor_slots().is_empty());
8771 }
8772
8773 #[test]
8774 fn declared_supervisor_slots_reports_only_set_slots_in_canonical_order() {
8775 use crate::RestartStrategy;
8776 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8777 // Set a non-adjacent pair (:estrategia + :restart-window) to pin
8778 // that the canonical declaration order is preserved regardless
8779 // of which subset is populated.
8780 c.estrategia = Some(RestartStrategy::OneForOne);
8781 c.restart_window = Some("60s".into());
8782 assert_eq!(
8783 c.declared_supervisor_slots(),
8784 vec![
8785 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
8786 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
8787 ]
8788 );
8789 }
8790
8791 #[test]
8792 fn supervisor_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
8793 // Scalar-value pin: the four author-facing kebab-case labels the
8794 // `(defcaixa … :<slot> (…))` surface admits on the Supervisor
8795 // supervision-tree slot axis, one arm per typed slot. Mirrors the
8796 // peer scalar-value pins the sibling
8797 // [`crate::render::M2_AUTHOR_KEY_LIMITS`] /
8798 // [`crate::render::M2_AUTHOR_KEY_BEHAVIOR`] /
8799 // [`crate::render::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot
8800 // consts and [`crate::render::M3_AUTHOR_KEY_MEMBROS`] etc.
8801 // top-level M3 slot consts carry, so all three kind-scoped
8802 // typed-slot-family author-facing-label axes route through one
8803 // canonical per-arm declaration. A future rebrand
8804 // (`:estrategia` → `:strategy` for English uniformity,
8805 // `:max-restarts` → `:max-intensity` matching Erlang/OTP's
8806 // `MaxIntensity` name, `:restart-window` → `:period` matching
8807 // OTP's `Period` name, `:children` → `:workers` matching Elixir
8808 // idiom) lands as an edit to exactly one const, and every
8809 // consumer that reaches for the label picks it up at build time
8810 // rather than at runtime as a downstream mismatch.
8811 assert_eq!(
8812 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
8813 ":estrategia"
8814 );
8815 assert_eq!(
8816 crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
8817 ":max-restarts"
8818 );
8819 assert_eq!(
8820 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
8821 ":restart-window"
8822 );
8823 assert_eq!(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN, ":children");
8824 }
8825
8826 #[test]
8827 fn declared_supervisor_slots_route_through_lifted_supervisor_author_key_consts() {
8828 // Production-through-const pin: the four per-arm labels the
8829 // [`Caixa::declared_supervisor_slots`] tagger pushes onto its
8830 // return `Vec` route through the lifted
8831 // [`crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`] /
8832 // [`crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`] /
8833 // [`crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW`] /
8834 // [`crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN`] consts, in
8835 // canonical declaration order. A future re-order or drift at the
8836 // tagger (a rename that reaches the tagger but not the const, or
8837 // vice versa) surfaces here at build time rather than at runtime
8838 // as a [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
8839 // `slots: <stale-kebab-case>` diagnostic far from the rename's
8840 // commit. Mirror of the peer
8841 // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
8842 // (f49c8b0) and
8843 // [`declared_mesh_slots_route_through_lifted_m3_author_key_consts`]
8844 // (882f498) pins on the sibling M2 / M3 top-level slot axes.
8845 use crate::{ChildSpec, RestartPolicy, RestartStrategy};
8846 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8847 c.estrategia = Some(RestartStrategy::OneForOne);
8848 c.max_restarts = Some(5);
8849 c.restart_window = Some("60s".into());
8850 c.children = vec![ChildSpec {
8851 caixa: "worker".into(),
8852 versao: "^0.1".into(),
8853 restart: RestartPolicy::Permanent,
8854 }];
8855 assert_eq!(
8856 c.declared_supervisor_slots(),
8857 vec![
8858 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
8859 crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
8860 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
8861 crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN,
8862 ]
8863 );
8864 }
8865
8866 #[test]
8867 fn declared_servico_slots_empty_for_bare_caixa() {
8868 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8869 assert!(c.declared_servico_slots().is_empty());
8870 }
8871
8872 #[test]
8873 fn declared_servico_slots_reports_only_set_slots_in_canonical_order() {
8874 use crate::{UpgradeFromEntry, UpgradeInstruction};
8875 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8876 // Set a non-adjacent pair (:limits + :upgrade-from) to pin that
8877 // the canonical declaration order is preserved regardless of
8878 // which subset is populated.
8879 c.limits = Some(crate::LimitsSpec {
8880 fuel: Some(1_000_000),
8881 ..Default::default()
8882 });
8883 c.upgrade_from = vec![UpgradeFromEntry {
8884 from: "0.1.0".into(),
8885 instructions: vec![UpgradeInstruction::Restart],
8886 }];
8887 assert_eq!(
8888 c.declared_servico_slots(),
8889 vec![
8890 crate::render::M2_AUTHOR_KEY_LIMITS,
8891 crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
8892 ]
8893 );
8894 }
8895
8896 #[test]
8897 fn m2_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
8898 // Scalar-value pin: the three author-facing kebab-case labels
8899 // the `(defcaixa … :<slot> (…))` surface admits on the M2
8900 // top-level slot axis, one arm per typed slot. Mirrors the peer
8901 // scalar-value pin the sibling renderer-side
8902 // [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
8903 // [`crate::M2_KEY_UPGRADE_FROM`] camelCase overlay-container
8904 // consts carry, so both halves of the M2 top-level slot dual
8905 // axis (author-facing kebab-case label + renderer-side
8906 // camelCase overlay-container wire key) route through one
8907 // canonical per-arm declaration. A future rebrand
8908 // (`:limits` → `:sandbox` matching Lunatic per-process
8909 // terminology INSPIRATIONS §III.1, `:behavior` → `:gen-server`
8910 // matching Erlang's verbatim name, `:upgrade-from` → `:appup`
8911 // matching Erlang's verbatim appup name) lands as an edit to
8912 // exactly one const, and every consumer that reaches for the
8913 // label picks it up at build time rather than at runtime as a
8914 // downstream mismatch.
8915 assert_eq!(crate::render::M2_AUTHOR_KEY_LIMITS, ":limits");
8916 assert_eq!(crate::render::M2_AUTHOR_KEY_BEHAVIOR, ":behavior");
8917 assert_eq!(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM, ":upgrade-from");
8918 }
8919
8920 #[test]
8921 fn declared_servico_slots_route_through_lifted_m2_author_key_consts() {
8922 // Production-through-const pin: the three per-arm labels the
8923 // [`Caixa::declared_servico_slots`] tagger pushes onto its
8924 // return `Vec` route through the lifted
8925 // [`crate::M2_AUTHOR_KEY_LIMITS`] /
8926 // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
8927 // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts, in canonical
8928 // declaration order. A future re-order or drift at the tagger
8929 // (a rename that reaches the tagger but not the const, or vice
8930 // versa) surfaces here at build time rather than at runtime as
8931 // a [`crate::LayoutError::ServicoSlotsOnNonServico`]
8932 // `slots: <stale-kebab-case>` diagnostic far from the rename's
8933 // commit. Mirror of the peer
8934 // [`crate::behavior::BehaviorSpec::declared_slots`] production
8935 // tagger pin (889dc18) on the sibling per-callback axis.
8936 use crate::{BehaviorSpec, UpgradeFromEntry, UpgradeInstruction};
8937 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8938 c.limits = Some(crate::LimitsSpec {
8939 fuel: Some(1_000_000),
8940 ..Default::default()
8941 });
8942 c.behavior = Some(BehaviorSpec {
8943 on_init: Some(PathBuf::from("lib/init.lisp")),
8944 ..Default::default()
8945 });
8946 c.upgrade_from = vec![UpgradeFromEntry {
8947 from: "0.1.0".into(),
8948 instructions: vec![UpgradeInstruction::Restart],
8949 }];
8950 assert_eq!(
8951 c.declared_servico_slots(),
8952 vec![
8953 crate::render::M2_AUTHOR_KEY_LIMITS,
8954 crate::render::M2_AUTHOR_KEY_BEHAVIOR,
8955 crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
8956 ]
8957 );
8958 }
8959
8960 #[test]
8961 fn existing_manifests_unaffected_by_new_optional_slots() {
8962 // Regression test: a caixa.lisp authored before M2 typed slots
8963 // should still parse + serialize cleanly. The bare `defcaixa`
8964 // emitted by `Caixa::template` has none of the new fields.
8965 let src = Caixa::template("legacy");
8966 let c = Caixa::from_lisp(&src).unwrap();
8967 assert!(c.limits.is_none());
8968 assert!(c.behavior.is_none());
8969 assert!(c.upgrade_from.is_empty());
8970 assert!(c.estrategia.is_none());
8971 assert!(c.children.is_empty());
8972
8973 // And to_lisp emits a manifest with the new slots in the
8974 // empty/default state — round-trippable.
8975 let emitted = c.to_lisp();
8976 let back = Caixa::from_lisp(&emitted).unwrap();
8977 assert_eq!(c, back);
8978 }
8979
8980 #[test]
8981 fn validate_deps_accepts_canonical_caixa() {
8982 // Positive control: the bare template — zero deps, zero
8983 // deps_dev — passes the gate trivially. A future axis added to
8984 // `Dep::validate` mustn't regress an empty-deps caixa to a
8985 // build error.
8986 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8987 c.validate_deps().unwrap();
8988 }
8989
8990 #[test]
8991 fn validate_deps_rejects_invalid_versao_in_deps() {
8992 // Fail-before-pass-after pin: a malformed `:deps :versao`
8993 // surfaces at validate_deps() time, not at lacre-resolve time.
8994 // Mirrors `rejects_invalid_membro_versao_requirement` and
8995 // `validate_rejects_invalid_child_versao_requirement` on the
8996 // other two `:versao` axes.
8997 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8998 c.deps = vec![Dep::simple("caixa-teia", "^bad-version")];
8999 let err = c.validate_deps().unwrap_err();
9000 assert!(
9001 matches!(
9002 err,
9003 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
9004 if nome == "caixa-teia" && versao == "^bad-version"
9005 ),
9006 "got {err:?}"
9007 );
9008 }
9009
9010 #[test]
9011 fn validate_deps_rejects_invalid_versao_in_deps_dev() {
9012 // Parity pin: `:deps-dev` must run through the same per-entry
9013 // validator as `:deps` — a typo in either axis surfaces the
9014 // same diagnostic. Without this leg, `:deps-dev` would be a
9015 // second-class citizen of the typed surface and an author
9016 // could land a build that passes validate_deps but fails at
9017 // `feira lock`-time when the dev-dep is resolved for a test
9018 // build.
9019 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9020 c.deps_dev = vec![Dep::simple("tatara-check", "^^0.1")];
9021 let err = c.validate_deps().unwrap_err();
9022 assert!(
9023 matches!(
9024 err,
9025 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
9026 if nome == "tatara-check" && versao == "^^0.1"
9027 ),
9028 "got {err:?}"
9029 );
9030 }
9031
9032 #[test]
9033 fn validate_deps_runs_deps_before_deps_dev() {
9034 // Order pin: when both lists carry typos, the `:deps`
9035 // diagnostic surfaces first. The author's mental model is
9036 // "runtime deps are load-bearing; dev deps are scaffolding";
9037 // surfacing the runtime axis first matches that hierarchy.
9038 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9039 c.deps = vec![Dep::simple("runtime-dep", "^bad-runtime")];
9040 c.deps_dev = vec![Dep::simple("dev-dep", "^bad-dev")];
9041 let err = c.validate_deps().unwrap_err();
9042 assert!(
9043 matches!(
9044 err,
9045 crate::dep::DepError::VersaoInvalid { ref nome, .. }
9046 if nome == "runtime-dep"
9047 ),
9048 "expected `:deps` typo to surface first, got {err:?}"
9049 );
9050 }
9051
9052 #[test]
9053 fn validate_deps_accepts_canonical_versao_forms_in_both_lists() {
9054 // Positive control sweep across both lists. Pin every
9055 // canonical Cargo-shaped form so a future tightening of the
9056 // accepted set surfaces here as a test failure (parity with
9057 // `accepts_canonical_membro_versao_forms` and
9058 // `validate_accepts_canonical_child_versao_forms`).
9059 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9060 c.deps = vec![
9061 Dep::simple("caret", "^0.1"),
9062 Dep::simple("tilde", "~0.1.2"),
9063 Dep::simple("exact", "0.1.0"),
9064 Dep::simple("wildcard", "*"),
9065 Dep::simple("multi-range", ">=0.1, <2"),
9066 ];
9067 c.deps_dev = vec![
9068 Dep::simple("dev-caret", "^0.1"),
9069 Dep::simple("dev-wildcard", "*"),
9070 ];
9071 c.validate_deps().unwrap();
9072 }
9073
9074 #[test]
9075 fn validate_deps_diagnostic_carries_offending_dep() {
9076 // Diagnostic-shape pin: the error names the offending entry's
9077 // `:nome` + `:versao` verbatim and carries a non-empty
9078 // `reason` from `semver::VersionReq::parse`, so a `feira lint`
9079 // run can render the diagnostic without re-parsing.
9080 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9081 c.deps = vec![Dep::simple("caixa-teia", "not-a-req")];
9082 let err = c.validate_deps().unwrap_err();
9083 let crate::dep::DepError::VersaoInvalid {
9084 nome,
9085 versao,
9086 reason,
9087 } = err
9088 else {
9089 panic!("expected VersaoInvalid, got other variant");
9090 };
9091 assert_eq!(nome, "caixa-teia");
9092 assert_eq!(versao, "not-a-req");
9093 assert!(
9094 !reason.is_empty(),
9095 "VersaoInvalid `reason` must carry the parser's wording verbatim"
9096 );
9097 }
9098
9099 #[test]
9100 fn validate_deps_rejects_ambiguous_fonte_in_deps_dev() {
9101 // Cross-axis pin: `validate_deps` walks both :deps and
9102 // :deps-dev through `Dep::validate`, and the new fonte gate
9103 // (`:tag` + `:branch` both set — the canonical "pin drift"
9104 // footgun) must surface from the :deps-dev arm with the
9105 // offending entry's :nome named. Pin the :deps-dev arm
9106 // explicitly so a future shortcut that only walks :deps
9107 // surfaces here as a regression.
9108 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9109 c.deps_dev = vec![Dep {
9110 nome: "dev-only".into(),
9111 versao: "^0.1".into(),
9112 fonte: Some(crate::DepSource::Git {
9113 repo: "github:p/x".into(),
9114 tag: Some("v1".into()),
9115 rev: None,
9116 branch: Some("main".into()),
9117 }),
9118 opcional: false,
9119 caracteristicas: vec![],
9120 }];
9121 let err = c.validate_deps().unwrap_err();
9122 let crate::dep::DepError::FontePinAmbiguous { nome, pins } = err else {
9123 panic!("expected FontePinAmbiguous from :deps-dev walk");
9124 };
9125 assert_eq!(nome, "dev-only");
9126 assert!(pins.contains(":tag") && pins.contains(":branch"));
9127 }
9128
9129 #[test]
9130 fn validate_deps_rejects_empty_repo_in_deps() {
9131 // Parity pin on the :deps arm: an empty :repo on the runtime
9132 // deps list surfaces the same FonteRepoEmpty diagnostic the
9133 // dep.rs per-entry tests pin, naming the offending entry.
9134 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9135 c.deps = vec![Dep {
9136 nome: "runtime".into(),
9137 versao: "^0.1".into(),
9138 fonte: Some(crate::DepSource::Git {
9139 repo: String::new(),
9140 tag: Some("v1".into()),
9141 rev: None,
9142 branch: None,
9143 }),
9144 opcional: false,
9145 caracteristicas: vec![],
9146 }];
9147 let err = c.validate_deps().unwrap_err();
9148 assert!(
9149 matches!(
9150 err,
9151 crate::dep::DepError::FonteRepoEmpty { ref nome }
9152 if nome == "runtime"
9153 ),
9154 "got {err:?}"
9155 );
9156 }
9157
9158 // ── validate_deps: within-list :nome set-not-multiset gate ─────────
9159
9160 #[test]
9161 fn validate_deps_rejects_duplicate_nome_in_deps() {
9162 // Fail-before-pass-after pin: two `:deps` entries naming the same
9163 // caixa carry two `:versao` / `:fonte` / feature triples that the
9164 // caixa-resolver's lacre pipeline collapses (the second silently
9165 // overwrites the first at `concrete_versao`-resolve time). The
9166 // gate surfaces the duplicate at validate-time, naming the
9167 // offending caixa + the list, before the resolver-side silent
9168 // drop. Mirrors the peer typed-graph duplicate gates
9169 // (`DuplicateChildCaixa`, `MembroDuplicate`, `DuplicateFrom`, …).
9170 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9171 c.deps = vec![
9172 Dep::simple("caixa-teia", "^0.1"),
9173 Dep::simple("caixa-teia", "^0.2"),
9174 ];
9175 let err = c.validate_deps().unwrap_err();
9176 assert!(
9177 matches!(
9178 err,
9179 crate::dep::DepError::DuplicateNome { ref nome, list }
9180 if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
9181 ),
9182 "got {err:?}"
9183 );
9184 }
9185
9186 #[test]
9187 fn validate_deps_rejects_duplicate_nome_in_deps_dev() {
9188 // Parity pin: `:deps-dev` runs through the same per-list
9189 // duplicate check as `:deps` — neither axis is a second-class
9190 // citizen of the set-not-multiset discipline.
9191 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9192 c.deps_dev = vec![
9193 Dep::simple("tatara-check", "*"),
9194 Dep::simple("tatara-check", "^0.1"),
9195 ];
9196 let err = c.validate_deps().unwrap_err();
9197 assert!(
9198 matches!(
9199 err,
9200 crate::dep::DepError::DuplicateNome { ref nome, list }
9201 if nome == "tatara-check" && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
9202 ),
9203 "got {err:?}"
9204 );
9205 }
9206
9207 #[test]
9208 fn validate_deps_accepts_cross_list_same_nome() {
9209 // The Cargo `[dependencies]` + `[dev-dependencies]` override
9210 // convention is preserved: a name appearing in *both* lists is
9211 // valid (the dev-pin overrides at test/dev time). Only
9212 // within-list duplicates are structurally incoherent — pin the
9213 // permissive cross-list semantics so a future shortcut that
9214 // collapses the two seen-sets into one surfaces here as a test
9215 // failure.
9216 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9217 c.deps = vec![Dep::simple("caixa-teia", "^0.1")];
9218 c.deps_dev = vec![Dep::simple("caixa-teia", "^0.2")];
9219 c.validate_deps().unwrap();
9220 }
9221
9222 #[test]
9223 fn validate_deps_accepts_distinct_nome_in_both_lists() {
9224 // Positive control: distinct names within each list pass — the
9225 // gate's identity element on the canonical authoring shape.
9226 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9227 c.deps = vec![
9228 Dep::simple("caixa-teia", "^0.1"),
9229 Dep::simple("pleme-mesh", "*"),
9230 ];
9231 c.deps_dev = vec![
9232 Dep::simple("tatara-check", "*"),
9233 Dep::simple("dev-shim", "^0.1"),
9234 ];
9235 c.validate_deps().unwrap();
9236 }
9237
9238 #[test]
9239 fn validate_deps_per_entry_validate_fires_before_duplicate_in_deps() {
9240 // Diagnostic-precedence pin: a malformed `:versao` on the
9241 // duplicating entry surfaces its narrower `VersaoInvalid`
9242 // diagnostic first, before the cross-entry duplicate gate fires
9243 // — the canonical "per-entry shape before cross-entry uniqueness"
9244 // precedence every peer set-not-multiset gate establishes
9245 // (`*_invalid_fires_before_duplicate_check` pins on
9246 // `SupervisorSpec::validate`, `AplicacaoSpec::validate_membros`,
9247 // `validate_upgrade_from`).
9248 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9249 c.deps = vec![
9250 Dep::simple("caixa-teia", "^0.1"),
9251 Dep::simple("caixa-teia", "^bad-version"),
9252 ];
9253 let err = c.validate_deps().unwrap_err();
9254 assert!(
9255 matches!(
9256 err,
9257 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
9258 if nome == "caixa-teia" && versao == "^bad-version"
9259 ),
9260 "expected VersaoInvalid to surface before DuplicateNome, got {err:?}"
9261 );
9262 }
9263
9264 #[test]
9265 fn validate_deps_duplicate_diagnostic_names_first_collision() {
9266 // First-collision determinism pin: with three entries naming the
9267 // same caixa, the first colliding pair surfaces — not the last.
9268 // Mirrors the peer first-collision posture on every
9269 // duplicate-target gate
9270 // (`validate_upgrade_from_duplicate_diagnostic_names_second_collision`
9271 // — the second entry is the first collision; this gate uses the
9272 // same shape: the second entry's `:nome` lands in the diagnostic
9273 // because `seen.insert(first.nome)` already populated the set).
9274 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9275 c.deps = vec![
9276 Dep::simple("caixa-teia", "^0.1"),
9277 Dep::simple("caixa-teia", "^0.2"),
9278 Dep::simple("caixa-teia", "^0.3"),
9279 ];
9280 let err = c.validate_deps().unwrap_err();
9281 // The diagnostic carries the offending caixa name; the
9282 // implementation surfaces on the *second* entry (the first
9283 // collision), so the test pins the `:nome` value.
9284 assert!(
9285 matches!(
9286 err,
9287 crate::dep::DepError::DuplicateNome { ref nome, list }
9288 if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
9289 ),
9290 "got {err:?}"
9291 );
9292 }
9293
9294 #[test]
9295 fn validate_deps_duplicate_in_deps_fires_before_duplicate_in_deps_dev() {
9296 // Cross-list precedence pin: when both lists carry duplicates,
9297 // the `:deps` diagnostic surfaces first — same author-mental-
9298 // model ordering the `validate_deps_runs_deps_before_deps_dev`
9299 // pin establishes for malformed `:versao` (runtime axis before
9300 // dev axis).
9301 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9302 c.deps = vec![
9303 Dep::simple("runtime-dep", "^0.1"),
9304 Dep::simple("runtime-dep", "^0.2"),
9305 ];
9306 c.deps_dev = vec![Dep::simple("dev-dep", "*"), Dep::simple("dev-dep", "^0.1")];
9307 let err = c.validate_deps().unwrap_err();
9308 assert!(
9309 matches!(
9310 err,
9311 crate::dep::DepError::DuplicateNome { ref nome, list }
9312 if nome == "runtime-dep" && list == crate::render::DEP_AUTHOR_KEY_DEPS
9313 ),
9314 "expected :deps duplicate to surface before :deps-dev duplicate, got {err:?}"
9315 );
9316 }
9317
9318 #[test]
9319 fn validate_deps_empty_lists_pass_duplicate_gate() {
9320 // Empty-set identity pin: the bare template (zero deps, zero
9321 // deps_dev) passes the duplicate gate as the gate's identity
9322 // element. A future tighten that conflates "empty" with
9323 // "missing" would regress this baseline.
9324 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9325 c.validate_deps().unwrap();
9326 }
9327
9328 #[test]
9329 fn validate_deps_duplicate_diagnostic_carries_list_tag() {
9330 // Diagnostic-shape pin: the `list:` field tags which list the
9331 // duplicate landed in (`:deps` vs `:deps-dev`) verbatim, so a
9332 // `feira lint` run can route the author to the right block in
9333 // their caixa.lisp without re-deriving the list from context.
9334 // Same self-locating shape every peer per-axis diagnostic
9335 // already exposes.
9336 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9337 c.deps_dev = vec![
9338 Dep::simple("dev-thing", "*"),
9339 Dep::simple("dev-thing", "^0.1"),
9340 ];
9341 let err = c.validate_deps().unwrap_err();
9342 let crate::dep::DepError::DuplicateNome { nome, list } = err else {
9343 panic!("expected DuplicateNome from :deps-dev walk");
9344 };
9345 assert_eq!(nome, "dev-thing");
9346 assert_eq!(list, crate::render::DEP_AUTHOR_KEY_DEPS_DEV);
9347 }
9348
9349 // ── validate_deps: per-entry :caracteristicas set-discipline gate ──
9350
9351 #[test]
9352 fn validate_deps_surfaces_caracteristicas_duplicate_in_deps_list() {
9353 // Thread-through pin on `:deps`: the per-entry
9354 // `Dep::validate_caracteristicas` gate fires inside
9355 // `Caixa::validate_deps`'s linear walk, so a malformed feature
9356 // list on any `:deps` entry surfaces as a `DepError` from
9357 // `validate_deps` — the same reachability shape every per-entry
9358 // `Dep::validate` arm threads through. Without this pin a future
9359 // shortcut that skips the per-entry `Dep::validate` call on the
9360 // cross-entry-uniqueness path would mask the within-entry
9361 // `:caracteristicas` gates.
9362 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9363 c.deps = vec![Dep {
9364 nome: "caixa-teia".into(),
9365 versao: "^0.1".into(),
9366 fonte: None,
9367 opcional: false,
9368 caracteristicas: vec!["http".into(), "http".into()],
9369 }];
9370 let err = c.validate_deps().unwrap_err();
9371 let crate::dep::DepError::CaracteristicaDuplicate {
9372 nome,
9373 caracteristica,
9374 } = err
9375 else {
9376 panic!("expected CaracteristicaDuplicate from :deps walk, got {err:?}");
9377 };
9378 assert_eq!(nome, "caixa-teia");
9379 assert_eq!(caracteristica, "http");
9380 }
9381
9382 #[test]
9383 fn validate_deps_surfaces_caracteristicas_empty_in_deps_dev_list() {
9384 // Peer thread-through pin on `:deps-dev`: same reachability as
9385 // the `:deps` arm above, on the dev-only authoring axis. Pins
9386 // that the `validate_deps` walk visits both lists' per-entry
9387 // gates uniformly. The empty-feature arm carries here so both
9388 // new `:caracteristicas` arms are surfaced via at least one
9389 // `validate_deps` thread-through.
9390 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9391 c.deps_dev = vec![Dep {
9392 nome: "caixa-teia".into(),
9393 versao: "^0.1".into(),
9394 fonte: None,
9395 opcional: false,
9396 caracteristicas: vec![String::new()],
9397 }];
9398 let err = c.validate_deps().unwrap_err();
9399 let crate::dep::DepError::CaracteristicaEmpty { nome } = err else {
9400 panic!("expected CaracteristicaEmpty from :deps-dev walk, got {err:?}");
9401 };
9402 assert_eq!(nome, "caixa-teia");
9403 }
9404
9405 #[test]
9406 fn validate_deps_surfaces_caracteristicas_invalid_in_deps_list() {
9407 // Thread-through pin on `:deps`: the per-entry
9408 // `Dep::validate_caracteristicas` value-shape gate (lifted via
9409 // `crate::render::is_cargo_feature_name`) fires inside
9410 // `Caixa::validate_deps`'s linear walk on the `:deps` list, so
9411 // a structurally invalid feature name on any `:deps` entry
9412 // surfaces as `DepError::CaracteristicaInvalid` from
9413 // `validate_deps` — the same reachability shape every per-entry
9414 // `Dep::validate` arm threads through. Without this pin a
9415 // future shortcut that skips the per-entry `Dep::validate` call
9416 // on the cross-entry-uniqueness path would mask the within-
9417 // entry `:caracteristicas` value-shape gate.
9418 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9419 c.deps = vec![Dep {
9420 nome: "caixa-teia".into(),
9421 versao: "^0.1".into(),
9422 fonte: None,
9423 opcional: false,
9424 caracteristicas: vec!["+http".into()],
9425 }];
9426 let err = c.validate_deps().unwrap_err();
9427 let crate::dep::DepError::CaracteristicaInvalid {
9428 nome,
9429 caracteristica,
9430 ..
9431 } = err
9432 else {
9433 panic!("expected CaracteristicaInvalid from :deps walk, got {err:?}");
9434 };
9435 assert_eq!(nome, "caixa-teia");
9436 assert_eq!(caracteristica, "+http");
9437 }
9438
9439 #[test]
9440 fn validate_deps_surfaces_caracteristicas_invalid_in_deps_dev_list() {
9441 // Peer thread-through pin on `:deps-dev`: same reachability as
9442 // the `:deps` arm above, on the dev-only authoring axis. The
9443 // `http/json` shape carries here so the segment-separator
9444 // diagnostic (the canonical Cargo `dep/feat` namespaced-dep
9445 // confusion footgun) is surfaced via the cross-entry walk too —
9446 // pinning that the `:deps-dev` list visits the same per-entry
9447 // value-shape gate as the `:deps` list.
9448 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9449 c.deps_dev = vec![Dep {
9450 nome: "caixa-teia".into(),
9451 versao: "^0.1".into(),
9452 fonte: None,
9453 opcional: false,
9454 caracteristicas: vec!["http/json".into()],
9455 }];
9456 let err = c.validate_deps().unwrap_err();
9457 let crate::dep::DepError::CaracteristicaInvalid {
9458 nome,
9459 caracteristica,
9460 ..
9461 } = err
9462 else {
9463 panic!("expected CaracteristicaInvalid from :deps-dev walk, got {err:?}");
9464 };
9465 assert_eq!(nome, "caixa-teia");
9466 assert_eq!(caracteristica, "http/json");
9467 }
9468
9469 #[test]
9470 fn to_lisp_preserves_deps() {
9471 let src = r#"
9472(defcaixa
9473 :nome "x"
9474 :versao "0.1.0"
9475 :kind Biblioteca
9476 :deps ((:nome "a" :versao "^0.1")
9477 (:nome "b" :versao "*" :fonte (:tipo git :repo "github:o/b" :tag "v1"))))
9478"#;
9479 let c1 = Caixa::from_lisp(src).unwrap();
9480 let emitted = c1.to_lisp();
9481 let c2 = Caixa::from_lisp(&emitted).expect("round trip");
9482 assert_eq!(c1.deps, c2.deps);
9483 }
9484
9485 // ── Caixa::validate_nome — top-level :nome value-shape gate ─────────
9486
9487 fn caixa_with_nome(nome: &str) -> Caixa {
9488 let mut c = Caixa::from_lisp(&Caixa::template("placeholder")).unwrap();
9489 c.nome = nome.to_string();
9490 c
9491 }
9492
9493 #[test]
9494 fn validate_nome_accepts_canonical_template() {
9495 // Positive control: the bare `feira init`-style template's
9496 // `:nome` ("demo") is a canonical DNS-1123 label; the gate must
9497 // not regress this baseline shape. A future tightening of the
9498 // accepted set surfaces here as a test failure first.
9499 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9500 c.validate_nome().unwrap();
9501 }
9502
9503 #[test]
9504 fn validate_nome_accepts_canonical_forms() {
9505 // Positive-set sweep: each realistic caixa-name shape the K8s
9506 // apiserver accepts as a `metadata.name` label must pass —
9507 // single-word, hyphen-joined, version-suffixed, single-char,
9508 // two-char, digit-start (DNS-1123 allows this; the stricter
9509 // DNS-1035 Service-name rule doesn't), version-suffix-bearing.
9510 // Mirrors `accepts_canonical_membro_caixa_forms` (3f9d7a0) on
9511 // the peer member-name axis.
9512 for nome in [
9513 "checkout",
9514 "cart-v2",
9515 "a",
9516 "db",
9517 "3rd-party-shim",
9518 "payment-retry",
9519 "0",
9520 ] {
9521 caixa_with_nome(nome)
9522 .validate_nome()
9523 .unwrap_or_else(|e| panic!("canonical :nome {nome:?} must validate, got {e:?}"));
9524 }
9525 }
9526
9527 #[test]
9528 fn validate_nome_rejects_empty() {
9529 // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
9530 // an empty `:nome` (the derive macro stores the raw String);
9531 // the gate's empty arm names the offending axis with a narrower
9532 // diagnostic than the `NomeInvalid` parse arm would emit.
9533 let c = caixa_with_nome("");
9534 let err = c.validate_nome().unwrap_err();
9535 assert_eq!(err, ManifestError::NomeEmpty);
9536 }
9537
9538 #[test]
9539 fn validate_nome_rejects_uppercase() {
9540 // The canonical "I copied the TitleCase display name verbatim"
9541 // footgun. The K8s apiserver rejects `metadata.name: MyApp` at
9542 // admission on every derived artifact (Helm chart, ComputeUnit,
9543 // CNP, HTTPRoute, label values); the gate moves the diagnostic
9544 // to the source `caixa.lisp` and the reason suggests the
9545 // lowercased fix verbatim.
9546 let c = caixa_with_nome("MyApp");
9547 let err = c.validate_nome().unwrap_err();
9548 let ManifestError::NomeInvalid { nome, reason } = err else {
9549 panic!("expected NomeInvalid for uppercase :nome");
9550 };
9551 assert_eq!(nome, "MyApp");
9552 assert!(
9553 reason.contains("uppercase") && reason.contains("myapp"),
9554 "diagnostic must name the violation + the lowercased fix, got {reason:?}"
9555 );
9556 }
9557
9558 #[test]
9559 fn validate_nome_rejects_underscore() {
9560 // The Python-/Postgres-style `snake_case` leak. DNS-1123 forbids
9561 // `_`; the apiserver rejects on admission across every derived
9562 // artifact. Same fixture pinned for `:membros :caixa` (3f9d7a0)
9563 // and `:children :caixa` (31bfa43).
9564 let c = caixa_with_nome("my_app");
9565 let err = c.validate_nome().unwrap_err();
9566 assert!(
9567 matches!(
9568 err,
9569 ManifestError::NomeInvalid { ref nome, ref reason }
9570 if nome == "my_app" && reason.contains('_')
9571 ),
9572 "got {err:?}"
9573 );
9574 }
9575
9576 #[test]
9577 fn validate_nome_rejects_dot() {
9578 // A `:nome` is a single DNS-1123 label, not a subdomain. The
9579 // "I want to namespace with `.`" footgun the gate redirects to
9580 // `-` via the shared predicate's reason wording.
9581 let c = caixa_with_nome("team.app");
9582 let err = c.validate_nome().unwrap_err();
9583 assert!(
9584 matches!(
9585 err,
9586 ManifestError::NomeInvalid { ref nome, ref reason }
9587 if nome == "team.app" && reason.contains('.')
9588 ),
9589 "got {err:?}"
9590 );
9591 }
9592
9593 #[test]
9594 fn validate_nome_rejects_leading_hyphen() {
9595 // DNS-1123 boundary rule: the label must start with an ASCII
9596 // alphanumeric. Pin the leading-`-` arm explicitly.
9597 let c = caixa_with_nome("-app");
9598 let err = c.validate_nome().unwrap_err();
9599 assert!(
9600 matches!(
9601 err,
9602 ManifestError::NomeInvalid { ref nome, .. } if nome == "-app"
9603 ),
9604 "got {err:?}"
9605 );
9606 }
9607
9608 #[test]
9609 fn validate_nome_rejects_trailing_hyphen() {
9610 // Symmetric arm of the boundary rule, pinned separately so a
9611 // future relaxation that only checks the leading position
9612 // surfaces here. Mirrors `rejects_membro_caixa_with_trailing_hyphen`
9613 // and `_with_trailing_hyphen` on the supervisor / aplicacao
9614 // axes.
9615 let c = caixa_with_nome("app-");
9616 let err = c.validate_nome().unwrap_err();
9617 assert!(
9618 matches!(
9619 err,
9620 ManifestError::NomeInvalid { ref nome, .. } if nome == "app-"
9621 ),
9622 "got {err:?}"
9623 );
9624 }
9625
9626 #[test]
9627 fn validate_nome_rejects_unicode() {
9628 // IDN must be pre-encoded as Punycode (`xn--…`); raw Unicode
9629 // bytes are rejected by the K8s apiserver on every name axis.
9630 let c = caixa_with_nome("café");
9631 let err = c.validate_nome().unwrap_err();
9632 assert!(
9633 matches!(
9634 err,
9635 ManifestError::NomeInvalid { ref nome, .. } if nome == "café"
9636 ),
9637 "got {err:?}"
9638 );
9639 }
9640
9641 #[test]
9642 fn validate_nome_rejects_whitespace() {
9643 // The paste-from-sketch / paste-from-spec footgun. Internal
9644 // whitespace is rejected by every K8s name axis.
9645 let c = caixa_with_nome("my app");
9646 let err = c.validate_nome().unwrap_err();
9647 assert!(
9648 matches!(
9649 err,
9650 ManifestError::NomeInvalid { ref nome, .. } if nome == "my app"
9651 ),
9652 "got {err:?}"
9653 );
9654 }
9655
9656 #[test]
9657 fn validate_nome_rejects_too_long() {
9658 // 64-byte boundary pin: the K8s apiserver rejects any
9659 // `metadata.name` over 63 bytes at admission; the diagnostic
9660 // names both the 63-byte cap and the actual length so the
9661 // author can shorten in one edit. Mirrors `_too_long` on the
9662 // peer member-/cluster-/child-name axes.
9663 let over = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN + 1);
9664 let c = caixa_with_nome(&over);
9665 let err = c.validate_nome().unwrap_err();
9666 let ManifestError::NomeInvalid { nome, reason } = err else {
9667 panic!("expected NomeInvalid for over-cap :nome");
9668 };
9669 assert_eq!(nome.len(), crate::DNS_1123_LABEL_MAX_LEN + 1);
9670 assert!(
9671 reason.contains("63") && reason.contains("64"),
9672 "diagnostic must name the cap + actual length, got {reason:?}"
9673 );
9674 }
9675
9676 #[test]
9677 fn nome_max_length_validates() {
9678 // The 63-byte cap exactly — the boundary-accepting case pinned
9679 // alongside `validate_nome_rejects_too_long` so a future cap
9680 // shift surfaces both arms simultaneously. Mirrors
9681 // `membro_caixa_max_length_validates`,
9682 // `placement_cluster_max_length_validates`,
9683 // `child_caixa_max_length_validates`.
9684 let at_cap = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
9685 caixa_with_nome(&at_cap).validate_nome().unwrap();
9686 }
9687
9688 #[test]
9689 fn nome_empty_takes_precedence_over_invalid() {
9690 // Order pin: the empty arm fires before the predicate is
9691 // consulted. Empty < invalid in self-locating-ness — the
9692 // narrower `NomeEmpty` diagnostic doesn't carry a useless
9693 // `nome: ""` reference into the parser-shaped reason. Mirrors
9694 // `membro_caixa_empty_takes_precedence_over_invalid` on the
9695 // peer axis (3f9d7a0).
9696 let c = caixa_with_nome("");
9697 assert_eq!(c.validate_nome().unwrap_err(), ManifestError::NomeEmpty);
9698 }
9699
9700 #[test]
9701 fn nome_invalid_diagnostic_carries_offending_nome() {
9702 // Diagnostic-shape pin: the error names the offending `:nome`
9703 // verbatim with a non-empty parser-shaped reason, so a `feira
9704 // lint` run can render the diagnostic without re-parsing.
9705 // Mirrors `membro_caixa_invalid_diagnostic_carries_offending_caixa`.
9706 let c = caixa_with_nome("MyApp");
9707 let err = c.validate_nome().unwrap_err();
9708 let ManifestError::NomeInvalid { nome, reason } = err else {
9709 panic!("expected NomeInvalid variant");
9710 };
9711 assert_eq!(nome, "MyApp");
9712 assert!(
9713 !reason.is_empty(),
9714 "NomeInvalid `reason` must carry the predicate's wording verbatim"
9715 );
9716 }
9717
9718 // ── Caixa::validate_nome_chart_name_budget — joint-length on `:nome` ──
9719 //
9720 // The bare-`:nome` axis [`Caixa::validate_nome`] caps at 63 bytes
9721 // via DNS-1123; this second-axis gate caps the joint
9722 // `lareira-<nome>` chart name at the same 63-byte ceiling. The
9723 // canonical [`crate::lareira_chart_name`] helper's doc comment
9724 // (f7320d7, caixa-core/src/render.rs:3198) explicitly deferred:
9725 // "the M4 admission webhook will pin the joint-length invariant
9726 // when it lands". These tests pin it at the manifest-validate
9727 // layer instead, fail-before-pass-after on the 56-byte boundary.
9728
9729 #[test]
9730 fn validate_nome_chart_name_budget_accepts_canonical_template() {
9731 // Positive control: the bare `feira init`-style template's
9732 // `:nome` ("demo") sits far below the cap; the gate must not
9733 // regress this baseline. Same shape every peer
9734 // value-shape-gate baseline pin uses.
9735 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9736 c.validate_nome_chart_name_budget().unwrap();
9737 }
9738
9739 #[test]
9740 fn validate_nome_chart_name_budget_accepts_canonical_fixtures() {
9741 // Positive-set sweep across the canonical author surface every
9742 // in-tree fixture uses (`hello-rio`, `cart`, `checkout`,
9743 // `worker`, the `checkout-aplicacao` example members, the
9744 // `example-attest` caixa-tatara fixture). Every value sits
9745 // far below the 55-byte per-`:nome` budget. Same shape every
9746 // peer per-axis baseline pin uses.
9747 for nome in [
9748 "hello-rio",
9749 "cart",
9750 "checkout",
9751 "worker",
9752 "example-attest",
9753 "demo",
9754 "a",
9755 ] {
9756 caixa_with_nome(nome)
9757 .validate_nome_chart_name_budget()
9758 .unwrap_or_else(|e| {
9759 panic!("canonical :nome {nome:?} must pass chart-name budget, got {e:?}")
9760 });
9761 }
9762 }
9763
9764 #[test]
9765 fn validate_nome_chart_name_budget_accepts_nome_at_cap() {
9766 // Boundary-accepting case at the 55-byte per-`:nome` budget —
9767 // the joint chart name is exactly 63 bytes, the DNS-1123 label
9768 // cap. Pinned alongside the rejecting-arm test so a future cap
9769 // shift surfaces both arms simultaneously. Mirrors
9770 // `nome_max_length_validates` on the peer bare-`:nome` axis.
9771 let at_cap = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN);
9772 caixa_with_nome(&at_cap)
9773 .validate_nome_chart_name_budget()
9774 .unwrap();
9775 }
9776
9777 #[test]
9778 fn validate_nome_chart_name_budget_rejects_nome_one_over_cap() {
9779 // Fail-before-pass-after pin on the 56-byte boundary: the
9780 // smallest `:nome` length that overflows the joint chart-name
9781 // cap. The inner [`is_dns_1123_label`] gate
9782 // (`Caixa::validate_nome`) accepts it (56 ≤ 63), so prior to
9783 // this gate it silently passed the manifest-validate cascade
9784 // and surfaced as a `helm lint` / apiserver rejection on the
9785 // rendered chart name far from the source `caixa.lisp`, with
9786 // no field naming the overflow. With this gate the diagnostic
9787 // names the offending `:nome` verbatim alongside the rendered
9788 // chart name and the budget, so the author can shorten in one
9789 // edit. Mirrors `validate_nome_rejects_too_long` on the peer
9790 // bare-`:nome` axis.
9791 let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
9792 let c = caixa_with_nome(&over);
9793 let err = c.validate_nome_chart_name_budget().unwrap_err();
9794 let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
9795 panic!("expected NomeChartNameBudgetExceeded for over-budget :nome");
9796 };
9797 assert_eq!(nome.len(), crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
9798 assert_eq!(nome, over);
9799 assert!(
9800 reason.contains("63") && reason.contains("64") && reason.contains("55"),
9801 "diagnostic must name the DNS-1123 cap (63), the actual chart-name length (64), \
9802 and the per-`:nome` budget (55), got {reason:?}"
9803 );
9804 }
9805
9806 #[test]
9807 fn validate_nome_chart_name_budget_rejects_nome_at_bare_dns_cap() {
9808 // The 63-byte `:nome` boundary — passes the bare-`:nome`
9809 // [`is_dns_1123_label`] cap exactly, but produces a 71-byte
9810 // joint chart name that overflows the DNS-1123 label cap
9811 // structurally. The most stringent fail-before-pass-after
9812 // surface: every `:nome` in the 56..=63-byte range passed the
9813 // prior cascade and broke at admission.
9814 let bare_max = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
9815 let c = caixa_with_nome(&bare_max);
9816 // The bare-`:nome` gate accepts the 63-byte length.
9817 c.validate_nome().unwrap();
9818 // The new joint-length gate rejects it.
9819 let err = c.validate_nome_chart_name_budget().unwrap_err();
9820 assert!(
9821 matches!(
9822 err,
9823 ManifestError::NomeChartNameBudgetExceeded { ref nome, .. }
9824 if nome.len() == crate::DNS_1123_LABEL_MAX_LEN
9825 ),
9826 "got {err:?}"
9827 );
9828 }
9829
9830 #[test]
9831 fn validate_nome_chart_name_budget_diagnostic_carries_offending_chart_name() {
9832 // Diagnostic-shape pin: the rendered `lareira-<nome>` chart
9833 // name appears verbatim in the diagnostic so the author sees
9834 // exactly the string the apiserver / `helm lint` would have
9835 // rejected — no re-derivation required to grep the source.
9836 // Peer with `nome_invalid_diagnostic_carries_offending_nome`
9837 // on the bare-`:nome` axis.
9838 let over = "x".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 5);
9839 let c = caixa_with_nome(&over);
9840 let err = c.validate_nome_chart_name_budget().unwrap_err();
9841 let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
9842 panic!("expected NomeChartNameBudgetExceeded variant");
9843 };
9844 assert_eq!(nome, over);
9845 let expected_chart = crate::lareira_chart_name(&over);
9846 assert!(
9847 reason.contains(&expected_chart),
9848 "diagnostic must carry the rendered chart name {expected_chart:?} verbatim, \
9849 got {reason:?}"
9850 );
9851 assert!(
9852 reason.contains("lareira-"),
9853 "diagnostic must name the canonical chart-name prefix verbatim, got {reason:?}"
9854 );
9855 }
9856
9857 #[test]
9858 fn validate_nome_chart_name_budget_runs_after_nome_shape_via_layout_verify() {
9859 // Order pin on the layout cascade: the narrower
9860 // `NomeInvalid` (bare-DNS-1123 shape) fires before the
9861 // joint-length budget. A structurally-malformed `:nome` (here:
9862 // uppercase) surfaces its specific shape error rather than
9863 // the chart-name-budget error, even when the joint length
9864 // would also overflow — the narrower diagnostic is more
9865 // self-locating. Mirrors the cascade-precedence pins peer
9866 // gates already use (e.g. `EntradaParaEmpty` before
9867 // `EntradaParaInvalid`).
9868 let over = "A".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
9869 let c = caixa_with_nome(&over);
9870 // The bare-shape gate fires first.
9871 let err = c.validate_nome().unwrap_err();
9872 assert!(
9873 matches!(err, ManifestError::NomeInvalid { .. }),
9874 "bare-shape gate must fire before chart-name-budget gate; got {err:?}"
9875 );
9876 // And the layout verify cascade surfaces that diagnostic, not
9877 // the budget arm. Inject a path-exists oracle so the cascade
9878 // gets past the manifest-presence check and into the
9879 // value-shape gates.
9880 let layout = crate::StandardLayout::new().with_path_exists(|_| true);
9881 let err = crate::LayoutInvariants::verify(
9882 &layout,
9883 &c,
9884 std::path::Path::new("/tmp/caixa-test-fake-root"),
9885 )
9886 .unwrap_err();
9887 let issue = err.to_string();
9888 assert!(
9889 issue.contains("DNS-1123") || issue.contains("uppercase"),
9890 "layout cascade must surface the bare-DNS-1123 diagnostic on a \
9891 structurally-malformed :nome, not the chart-name-budget diagnostic; got {issue:?}"
9892 );
9893 }
9894
9895 #[test]
9896 fn layout_verify_routes_chart_name_budget_through_nome_violation() {
9897 // Cross-axis envelope pin: the layout cascade wraps both
9898 // bare-`:nome` and joint-length-`:nome` failures through the
9899 // same [`LayoutError::NomeViolation`] envelope, since both
9900 // arms are on the `:nome` axis. The user's diagnostic stays
9901 // self-locating ("which axis"), and a future consumer that
9902 // dispatches on the layout-error variant (e.g. a `feira lint`
9903 // exit-code mapping) sees a single per-axis envelope. The
9904 // wrapped `issue:` carries the full inner diagnostic.
9905 let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
9906 let c = caixa_with_nome(&over);
9907 // The bare-shape gate accepts.
9908 c.validate_nome().unwrap();
9909 let layout = crate::StandardLayout::new().with_path_exists(|_| true);
9910 let err = crate::LayoutInvariants::verify(
9911 &layout,
9912 &c,
9913 std::path::Path::new("/tmp/caixa-test-fake-root"),
9914 )
9915 .unwrap_err();
9916 let crate::LayoutError::NomeViolation { caixa, issue } = err else {
9917 panic!("expected LayoutError::NomeViolation, got {err:?}");
9918 };
9919 assert_eq!(caixa, over);
9920 assert!(
9921 issue.contains("lareira-") && issue.contains("63") && issue.contains("55"),
9922 "wrapped issue must carry the joint-length diagnostic verbatim, got {issue:?}"
9923 );
9924 }
9925
9926 // ── Caixa::validate_versao — top-level :versao value-shape gate ─────
9927
9928 fn caixa_with_versao(versao: &str) -> Caixa {
9929 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9930 c.versao = versao.to_string();
9931 c
9932 }
9933
9934 #[test]
9935 fn validate_versao_accepts_canonical_template() {
9936 // Positive control: the bare `feira init`-style template's
9937 // `:versao` ("0.1.0") is a canonical SemVer-2 literal; the gate
9938 // must not regress this baseline shape. A future tightening of
9939 // the accepted set surfaces here as a test failure first.
9940 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9941 c.validate_versao().unwrap();
9942 }
9943
9944 #[test]
9945 fn validate_versao_accepts_canonical_forms() {
9946 // Positive-set sweep: each realistic SemVer-2 shape the
9947 // substrate's downstream consumers accept must pass — bare
9948 // MAJOR.MINOR.PATCH, pre-release tags (`-rc.1`, `-alpha.0`),
9949 // build metadata (`+build.42`), the combined form, and the
9950 // `0.0.0` boundary case. Mirrors `accepts_canonical_forms` on
9951 // the peer `:nome` axis (6c992f8).
9952 for versao in [
9953 "0.1.0",
9954 "0.0.0",
9955 "1.0.0",
9956 "0.2.0-rc.1",
9957 "1.0.0-alpha.0",
9958 "1.0.0+build.42",
9959 "1.0.0-rc.1+build.42",
9960 "10.20.30",
9961 ] {
9962 caixa_with_versao(versao)
9963 .validate_versao()
9964 .unwrap_or_else(|e| {
9965 panic!("canonical :versao {versao:?} must validate, got {e:?}")
9966 });
9967 }
9968 }
9969
9970 #[test]
9971 fn validate_versao_rejects_empty() {
9972 // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
9973 // an empty `:versao` (the derive macro stores the raw String);
9974 // the gate's empty arm names the offending axis with a narrower
9975 // diagnostic than the `VersaoInvalid` parse arm would emit.
9976 // Mirrors `validate_nome_rejects_empty` (6c992f8).
9977 let c = caixa_with_versao("");
9978 let err = c.validate_versao().unwrap_err();
9979 assert_eq!(err, ManifestError::VersaoEmpty);
9980 }
9981
9982 #[test]
9983 fn validate_versao_rejects_git_tag_shape() {
9984 // The canonical "I copied the git tag verbatim" footgun —
9985 // `feira publish` *emits* `v<versao>` git tags, so a leaked
9986 // `v0.1.0` in `:versao` would render as `vv0.1.0` and silently
9987 // shift every downstream consumer's version axis. `semver`
9988 // rejects the leading `v` at parse time; the gate moves the
9989 // diagnostic to the source `caixa.lisp`.
9990 let c = caixa_with_versao("v0.1.0");
9991 let err = c.validate_versao().unwrap_err();
9992 let ManifestError::VersaoInvalid { versao, reason } = err else {
9993 panic!("expected VersaoInvalid for git-tag-shape :versao");
9994 };
9995 assert_eq!(versao, "v0.1.0");
9996 assert!(
9997 !reason.is_empty(),
9998 "VersaoInvalid `reason` must carry the parser's wording, got {reason:?}"
9999 );
10000 }
10001
10002 #[test]
10003 fn validate_versao_rejects_missing_patch() {
10004 // The canonical "I shortened it" footgun — SemVer-2 requires
10005 // three parts. Cargo's `version =` field accepts the shortened
10006 // form as a requirement, conflating the two leaks across the
10007 // typed `:deps :versao` vs top-level `:versao` axes; the gate
10008 // pins the top-level axis to the strict three-part shape.
10009 let c = caixa_with_versao("0.1");
10010 let err = c.validate_versao().unwrap_err();
10011 assert!(
10012 matches!(
10013 err,
10014 ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1"
10015 ),
10016 "got {err:?}"
10017 );
10018 }
10019
10020 #[test]
10021 fn validate_versao_rejects_requirement_shape() {
10022 // The canonical "I leaked a requirement into a version" footgun —
10023 // the typed `:deps :versao` / `:membros :versao` axes accept
10024 // `^0.1` (a `VersionReq`); the top-level `:versao` requires a
10025 // concrete `Version`. Without this gate the two typed surfaces
10026 // would silently overlap, and a top-level `^0.1` would surface
10027 // at `helm install` time as a Chart.yaml version rejection far
10028 // from the source `caixa.lisp`.
10029 let c = caixa_with_versao("^0.1");
10030 let err = c.validate_versao().unwrap_err();
10031 assert!(
10032 matches!(
10033 err,
10034 ManifestError::VersaoInvalid { ref versao, .. } if versao == "^0.1"
10035 ),
10036 "got {err:?}"
10037 );
10038 }
10039
10040 #[test]
10041 fn validate_versao_rejects_docker_tag_shape() {
10042 // The "I confused it with a docker tag" footgun — `latest`,
10043 // `main`, `stable` parse as identifiers, not SemVer-2 versions.
10044 // SemVer rejects at parse time; the gate moves the diagnostic
10045 // to the source `caixa.lisp`.
10046 for bad in ["latest", "main", "stable"] {
10047 let c = caixa_with_versao(bad);
10048 let err = c.validate_versao().unwrap_err();
10049 assert!(
10050 matches!(
10051 err,
10052 ManifestError::VersaoInvalid { ref versao, .. } if versao == bad
10053 ),
10054 "got {err:?} for {bad:?}"
10055 );
10056 }
10057 }
10058
10059 #[test]
10060 fn validate_versao_rejects_four_part_form() {
10061 // The Java/Microsoft "MAJOR.MINOR.PATCH.BUILD" convention
10062 // SemVer-2 forbids. A leak from a non-SemVer ecosystem; the
10063 // semver crate rejects the extra `.0` at parse time.
10064 let c = caixa_with_versao("0.1.0.0");
10065 let err = c.validate_versao().unwrap_err();
10066 assert!(
10067 matches!(
10068 err,
10069 ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1.0.0"
10070 ),
10071 "got {err:?}"
10072 );
10073 }
10074
10075 #[test]
10076 fn versao_empty_takes_precedence_over_invalid() {
10077 // Order pin: the empty arm fires before the parser is consulted.
10078 // Empty < invalid in self-locating-ness — the narrower
10079 // `VersaoEmpty` diagnostic doesn't carry a useless `versao: ""`
10080 // reference into the parser-shaped reason. Mirrors
10081 // `nome_empty_takes_precedence_over_invalid` (6c992f8) on the
10082 // peer axis.
10083 let c = caixa_with_versao("");
10084 assert_eq!(c.validate_versao().unwrap_err(), ManifestError::VersaoEmpty);
10085 }
10086
10087 #[test]
10088 fn versao_invalid_diagnostic_carries_offending_versao() {
10089 // Diagnostic-shape pin: the error names the offending `:versao`
10090 // verbatim with a non-empty parser-shaped reason, so a `feira
10091 // lint` run can render the diagnostic without re-parsing.
10092 // Mirrors `nome_invalid_diagnostic_carries_offending_nome`.
10093 let c = caixa_with_versao("v0.1.0");
10094 let err = c.validate_versao().unwrap_err();
10095 let ManifestError::VersaoInvalid { versao, reason } = err else {
10096 panic!("expected VersaoInvalid variant");
10097 };
10098 assert_eq!(versao, "v0.1.0");
10099 assert!(
10100 !reason.is_empty(),
10101 "VersaoInvalid `reason` must carry the parser's wording verbatim"
10102 );
10103 }
10104
10105 #[test]
10106 fn validate_versao_accepts_what_upgrade_from_from_accepts() {
10107 // Parity pin: every shape `UpgradeFromEntry::validate` accepts
10108 // for `:upgrade-from :from` must also pass `validate_versao` —
10109 // the two `:versao`-typed surfaces (top-level `:versao`,
10110 // `:upgrade-from :from`) consume the *same* `semver::Version`
10111 // parser, so they must agree on the accepted set. Without this
10112 // pin, a future tightening of one axis could silently diverge
10113 // from the other. Mirrors the `:versao` requirement-axis
10114 // parity (`:deps`/`:deps-dev`/`:membros`/`:children`) the prior
10115 // commits established.
10116 for versao in ["0.1.0", "0.2.0-rc.1", "1.0.0+build.42"] {
10117 // From the canonical UpgradeFromEntry round-trip fixture
10118 // (`upgrade::tests::round_trip_load_module` peers).
10119 let entry = crate::UpgradeFromEntry {
10120 from: versao.to_string(),
10121 instructions: Vec::new(),
10122 };
10123 entry
10124 .validate()
10125 .unwrap_or_else(|e| panic!(":from {versao:?} must validate, got {e:?}"));
10126 caixa_with_versao(versao)
10127 .validate_versao()
10128 .unwrap_or_else(|e| {
10129 panic!(":versao {versao:?} must validate, got {e:?} — peer axis diverges")
10130 });
10131 }
10132 }
10133
10134 // ── Caixa::validate_restart_window — supervisor restart-window
10135 // folds through the shared `supervisor::duration_codec` ────────
10136
10137 fn caixa_with_restart_window(window: Option<&str>) -> Caixa {
10138 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
10139 c.kind = CaixaKind::Supervisor;
10140 c.restart_window = window.map(str::to_string);
10141 c
10142 }
10143
10144 #[test]
10145 fn validate_restart_window_accepts_none() {
10146 // The canonical "omit the slot to express no reset" shape — a
10147 // `None` raw string is the absence of the typed
10148 // `:restart-window` slot, which is exactly the SupervisorSpec
10149 // "never reset" semantics. The gate must be a no-op here; a
10150 // future tightening that rejected `None` would force every
10151 // supervisor caixa to authoring-time pin a window even when
10152 // the OTP semantics call for none.
10153 caixa_with_restart_window(None)
10154 .validate_restart_window()
10155 .unwrap();
10156 }
10157
10158 #[test]
10159 fn validate_restart_window_accepts_canonical_forms() {
10160 // Positive-set sweep across the canonical authoring units the
10161 // shared `supervisor::duration_codec::parse` accepts —
10162 // matches the codec-side `parse_accepts_integer_canonical_units`
10163 // pin in supervisor::tests so a future codec-side tightening
10164 // surfaces simultaneously on both axes.
10165 for window in ["60s", "5m", "1h", "500ms", "30", "0s"] {
10166 caixa_with_restart_window(Some(window))
10167 .validate_restart_window()
10168 .unwrap_or_else(|e| {
10169 panic!("canonical :restart-window {window:?} must validate, got {e:?}")
10170 });
10171 }
10172 }
10173
10174 #[test]
10175 fn validate_restart_window_rejects_fractional_seconds() {
10176 // Fail-before-pass-after pin: the `"1.5s"` drift class (parses
10177 // as f64 to 1.5 → renders back as `"1500ms"` on first
10178 // serialize). Prior to the fold + this gate, the inline
10179 // `parse_window_inline` accepted f64 magnitudes and silently
10180 // produced a `Duration::from_secs_f64(1.5)`, divergent from
10181 // the shared codec's integer-magnitude discipline on the
10182 // serde-routed siblings. The gate now surfaces a self-locating
10183 // diagnostic at the manifest layer.
10184 let err = caixa_with_restart_window(Some("1.5s"))
10185 .validate_restart_window()
10186 .unwrap_err();
10187 let ManifestError::RestartWindowMalformed {
10188 restart_window,
10189 reason,
10190 } = err
10191 else {
10192 panic!("expected RestartWindowMalformed for fractional seconds");
10193 };
10194 assert_eq!(restart_window, "1.5s");
10195 assert!(
10196 reason.contains("\"1.5\"") && reason.contains("not a non-negative integer"),
10197 "diagnostic must carry shared-codec wording, got {reason:?}"
10198 );
10199 }
10200
10201 #[test]
10202 fn validate_restart_window_rejects_decimal_shaped_integer() {
10203 // The `"1.0s"` class — numerically `1s` exactly, but the
10204 // canonical form is `"1s"` not `"1.0s"`. Decimal-shape leak
10205 // gets the same canonical-form diagnostic.
10206 let err = caixa_with_restart_window(Some("1.0s"))
10207 .validate_restart_window()
10208 .unwrap_err();
10209 assert!(
10210 matches!(
10211 err,
10212 ManifestError::RestartWindowMalformed { ref restart_window, .. }
10213 if restart_window == "1.0s"
10214 ),
10215 "got {err:?}"
10216 );
10217 }
10218
10219 #[test]
10220 fn validate_restart_window_rejects_half_unit_minute() {
10221 // `"0.5m"` is the unit-fraction footgun — author writes a
10222 // human-readable half-minute, the prior inline parser silently
10223 // produced `Duration::from_secs_f64(30.0)` and serde
10224 // re-emitted as `"30s"`, rewriting author intent. The gate
10225 // closes the loop at the manifest layer.
10226 let err = caixa_with_restart_window(Some("0.5m"))
10227 .validate_restart_window()
10228 .unwrap_err();
10229 let ManifestError::RestartWindowMalformed {
10230 restart_window,
10231 reason,
10232 } = err
10233 else {
10234 panic!("expected RestartWindowMalformed");
10235 };
10236 assert_eq!(restart_window, "0.5m");
10237 assert!(
10238 reason.contains("\"30s\""),
10239 "diagnostic must point at the canonical-form remediation, got {reason:?}"
10240 );
10241 }
10242
10243 #[test]
10244 fn validate_restart_window_rejects_leading_sign() {
10245 // `"+30s"` and `"-30s"` both round-tripped through f64 cleanly
10246 // on the prior parser (`+30` parses as `30.0`; `-30` parsed
10247 // and was caught by the `num < 0.0` arm which silently
10248 // returned `None`, dropping the author-supplied window). The
10249 // shared codec's digit-only gate rejects both with a unified
10250 // canonical-form diagnostic; the manifest-layer wrapper names
10251 // the offending value.
10252 for bad in ["+30s", "-30s"] {
10253 let err = caixa_with_restart_window(Some(bad))
10254 .validate_restart_window()
10255 .unwrap_err();
10256 assert!(
10257 matches!(
10258 err,
10259 ManifestError::RestartWindowMalformed { ref restart_window, .. }
10260 if restart_window == bad
10261 ),
10262 "got {err:?} for {bad:?}"
10263 );
10264 }
10265 }
10266
10267 #[test]
10268 fn validate_restart_window_rejects_unknown_unit() {
10269 // `"30x"` — the typo / wrong-unit footgun. The shared codec's
10270 // unit dispatch surfaces an `unknown duration unit` reason;
10271 // the manifest-layer wrapper names the offending value.
10272 let err = caixa_with_restart_window(Some("30x"))
10273 .validate_restart_window()
10274 .unwrap_err();
10275 let ManifestError::RestartWindowMalformed {
10276 restart_window,
10277 reason,
10278 } = err
10279 else {
10280 panic!("expected RestartWindowMalformed for unknown unit");
10281 };
10282 assert_eq!(restart_window, "30x");
10283 assert!(
10284 reason.contains("unknown duration unit"),
10285 "diagnostic must carry shared-codec unit-rejection wording, got {reason:?}"
10286 );
10287 }
10288
10289 #[test]
10290 fn validate_restart_window_rejects_garbage() {
10291 // Pure non-numeric magnitude (`"abc"`) falls through to the
10292 // shared codec's narrower `"bad duration magnitude"` arm. Same
10293 // diagnostic shape as the codec-side
10294 // `parse_garbage_still_falls_through_to_bad_magnitude` pin.
10295 let err = caixa_with_restart_window(Some("abc"))
10296 .validate_restart_window()
10297 .unwrap_err();
10298 let ManifestError::RestartWindowMalformed {
10299 restart_window,
10300 reason,
10301 } = err
10302 else {
10303 panic!("expected RestartWindowMalformed for garbage");
10304 };
10305 assert_eq!(restart_window, "abc");
10306 assert!(
10307 reason.contains("bad duration magnitude"),
10308 "diagnostic must carry shared-codec garbage-rejection wording, got {reason:?}"
10309 );
10310 }
10311
10312 #[test]
10313 fn validate_restart_window_rejects_empty_string() {
10314 // The empty-after-trim edge case — distinct from the `None`
10315 // canonical "omit the slot" shape. The shared codec's
10316 // digit-only gate refuses an empty magnitude; the manifest
10317 // layer names the offending `""` so the author can grep for
10318 // the literal empty value in their `caixa.lisp` and either
10319 // remove the slot (the canonical "no reset" shape) or pin a
10320 // positive duration.
10321 let err = caixa_with_restart_window(Some(""))
10322 .validate_restart_window()
10323 .unwrap_err();
10324 assert!(
10325 matches!(
10326 err,
10327 ManifestError::RestartWindowMalformed { ref restart_window, .. }
10328 if restart_window.is_empty()
10329 ),
10330 "got {err:?}"
10331 );
10332 }
10333
10334 #[test]
10335 fn validate_restart_window_diagnostic_carries_offending_value() {
10336 // Diagnostic-shape pin (peer with
10337 // `nome_invalid_diagnostic_carries_offending_nome` /
10338 // `versao_invalid_diagnostic_carries_offending_versao`): the
10339 // error names the offending raw `:restart-window` verbatim
10340 // with a non-empty shared-codec-shaped reason, so a `feira
10341 // lint` run can render the diagnostic without re-parsing.
10342 let err = caixa_with_restart_window(Some("1.5s"))
10343 .validate_restart_window()
10344 .unwrap_err();
10345 let ManifestError::RestartWindowMalformed {
10346 restart_window,
10347 reason,
10348 } = err
10349 else {
10350 panic!("expected RestartWindowMalformed variant");
10351 };
10352 assert_eq!(restart_window, "1.5s");
10353 assert!(
10354 !reason.is_empty(),
10355 "RestartWindowMalformed `reason` must carry the codec's wording verbatim"
10356 );
10357 }
10358
10359 #[test]
10360 fn supervisor_view_folds_through_shared_codec_on_canonical_form() {
10361 // Behavioral parity pin after the fold (`parse_window_inline`
10362 // deletion): the canonical `"60s"` still produces
10363 // `Duration::from_secs(60)` on the typed view — the fold is
10364 // semantically equivalent to the prior inline parser on the
10365 // accepted set. Mirrors the pre-fold `supervisor_view_returns_typed_shape`
10366 // pin, narrowed to the parser-side contract.
10367 let c = caixa_with_restart_window(Some("60s"));
10368 let view = c.supervisor_view().expect("Supervisor kind has a view");
10369 assert_eq!(
10370 view.restart_window,
10371 Some(std::time::Duration::from_secs(60))
10372 );
10373 }
10374
10375 #[test]
10376 fn supervisor_view_soft_swallows_what_validate_rejects() {
10377 // Parity pin between the view-construction path and the
10378 // manifest-level validator: the same `"1.5s"` that surfaces
10379 // `RestartWindowMalformed` at `validate_restart_window` time
10380 // becomes `restart_window: None` on the typed view (the fold
10381 // preserves the existing best-effort shape of `supervisor_view`).
10382 // The contract is: a layout-verifier / `feira lint` flow that
10383 // cares about the malformed-window axis MUST consult
10384 // `validate_restart_window` — relying solely on the view's
10385 // `None` swallows the diagnostic silently. This pin makes the
10386 // expectation a typed invariant.
10387 let c = caixa_with_restart_window(Some("1.5s"));
10388 let view = c.supervisor_view().expect("Supervisor kind has a view");
10389 assert_eq!(
10390 view.restart_window, None,
10391 "view-construction path soft-swallows the parse error to None"
10392 );
10393 // And the manifest-level validator does NOT soft-swallow:
10394 assert!(
10395 matches!(
10396 c.validate_restart_window().unwrap_err(),
10397 ManifestError::RestartWindowMalformed { ref restart_window, .. }
10398 if restart_window == "1.5s"
10399 ),
10400 "validator must surface the offending value",
10401 );
10402 }
10403
10404 // ── validate_code_paths — per-entry shape on :bibliotecas / :exe / :servicos ──
10405
10406 fn caixa_with_code_paths(bibliotecas: Vec<&str>, exe: Vec<&str>, servicos: Vec<&str>) -> Caixa {
10407 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10408 c.bibliotecas = bibliotecas.into_iter().map(String::from).collect();
10409 c.exe = exe.into_iter().map(String::from).collect();
10410 c.servicos = servicos.into_iter().map(String::from).collect();
10411 c
10412 }
10413
10414 #[test]
10415 fn validate_code_paths_accepts_canonical_template() {
10416 // The bare `Caixa::template` shape is the gate's identity element
10417 // on the canonical authoring shape — `:bibliotecas
10418 // ("lib/demo.lisp")` + empty `:exe` + empty `:servicos`. Pins
10419 // that the gate is non-disruptive against every existing caixa.
10420 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10421 c.validate_code_paths().unwrap();
10422 }
10423
10424 #[test]
10425 fn validate_code_paths_accepts_explicit_relative_paths_on_every_slot() {
10426 // Positive control sweep: a canonical-shaped path on every slot
10427 // passes. Mirrors the peer
10428 // `behavior::validate_every_slot_relative_is_ok` pin.
10429 let c = caixa_with_code_paths(
10430 vec!["lib/demo.lisp", "lib/helpers.lisp"],
10431 vec!["exe/demo", "exe/tool"],
10432 vec!["servicos/demo.computeunit.yaml"],
10433 );
10434 c.validate_code_paths().unwrap();
10435 }
10436
10437 #[test]
10438 fn validate_code_paths_accepts_all_empty_lists() {
10439 // The empty-list identity element: every Caixa with no declared
10440 // code paths trivially passes (Supervisor / Aplicacao kinds rely
10441 // on this — the OwnCode gate already rejected them before the
10442 // path-shape gate runs in the layout, but the validator itself
10443 // must accept the empty shape).
10444 let c = caixa_with_code_paths(vec![], vec![], vec![]);
10445 c.validate_code_paths().unwrap();
10446 }
10447
10448 #[test]
10449 fn validate_code_paths_rejects_empty_bibliotecas_entry() {
10450 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
10451 let err = c.validate_code_paths().unwrap_err();
10452 assert!(
10453 matches!(
10454 err,
10455 ManifestError::CodePathEmpty {
10456 slot: ":bibliotecas"
10457 }
10458 ),
10459 "got {err:?}",
10460 );
10461 }
10462
10463 #[test]
10464 fn validate_code_paths_rejects_empty_exe_entry() {
10465 let c = caixa_with_code_paths(vec![], vec![""], vec![]);
10466 let err = c.validate_code_paths().unwrap_err();
10467 assert!(
10468 matches!(err, ManifestError::CodePathEmpty { slot: ":exe" }),
10469 "got {err:?}",
10470 );
10471 }
10472
10473 #[test]
10474 fn validate_code_paths_rejects_empty_servicos_entry() {
10475 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
10476 let err = c.validate_code_paths().unwrap_err();
10477 assert!(
10478 matches!(err, ManifestError::CodePathEmpty { slot: ":servicos" }),
10479 "got {err:?}",
10480 );
10481 }
10482
10483 #[test]
10484 fn validate_code_paths_rejects_absolute_bibliotecas_entry() {
10485 // `:bibliotecas` has no `starts_with(<dir>)` fence downstream,
10486 // so an absolute path that resolves on disk silently passes the
10487 // layout's existence check — the canonical sandbox-escape on
10488 // the biblioteca axis.
10489 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
10490 let err = c.validate_code_paths().unwrap_err();
10491 let ManifestError::CodePathAbsolute { slot, path } = err else {
10492 panic!("expected CodePathAbsolute, got {err:?}");
10493 };
10494 assert_eq!(slot, ":bibliotecas");
10495 assert_eq!(path, PathBuf::from("/etc/passwd"));
10496 }
10497
10498 #[test]
10499 fn validate_code_paths_rejects_absolute_exe_entry() {
10500 let c = caixa_with_code_paths(vec![], vec!["/usr/bin/env"], vec![]);
10501 let err = c.validate_code_paths().unwrap_err();
10502 let ManifestError::CodePathAbsolute { slot, path } = err else {
10503 panic!("expected CodePathAbsolute, got {err:?}");
10504 };
10505 assert_eq!(slot, ":exe");
10506 assert_eq!(path, PathBuf::from("/usr/bin/env"));
10507 }
10508
10509 #[test]
10510 fn validate_code_paths_rejects_absolute_servicos_entry() {
10511 let c = caixa_with_code_paths(vec![], vec![], vec!["/var/servicos/x.yaml"]);
10512 let err = c.validate_code_paths().unwrap_err();
10513 let ManifestError::CodePathAbsolute { slot, path } = err else {
10514 panic!("expected CodePathAbsolute, got {err:?}");
10515 };
10516 assert_eq!(slot, ":servicos");
10517 assert_eq!(path, PathBuf::from("/var/servicos/x.yaml"));
10518 }
10519
10520 #[test]
10521 fn validate_code_paths_rejects_parent_escape_bibliotecas_leading() {
10522 // Canonical "I want a lib from a sibling caixa" footgun on the
10523 // biblioteca axis. `:bibliotecas` has no `starts_with` fence
10524 // downstream, so a leading `..` traverses to the parent of the
10525 // caixa root with no diagnostic at layout time if the resolved
10526 // target exists.
10527 let c = caixa_with_code_paths(vec!["../sibling/x.lisp"], vec![], vec![]);
10528 let err = c.validate_code_paths().unwrap_err();
10529 let ManifestError::CodePathParentEscape { slot, path } = err else {
10530 panic!("expected CodePathParentEscape, got {err:?}");
10531 };
10532 assert_eq!(slot, ":bibliotecas");
10533 assert_eq!(path, PathBuf::from("../sibling/x.lisp"));
10534 }
10535
10536 #[test]
10537 fn validate_code_paths_rejects_parent_escape_exe_mid_path() {
10538 // Mid-path `..` defeats the layout's component-aware
10539 // `starts_with(exe_dir)` fence — `root.join("exe/../../escape")`
10540 // `starts_with(<root>/exe)` is true, but the canonical resolution
10541 // lives outside the caixa root. Caught regardless of where the
10542 // `..` sits — mirrors the peer
10543 // `behavior::validate_rejects_parent_escape_mid_path` pin.
10544 let c = caixa_with_code_paths(vec![], vec!["exe/../../escape"], vec![]);
10545 let err = c.validate_code_paths().unwrap_err();
10546 let ManifestError::CodePathParentEscape { slot, path } = err else {
10547 panic!("expected CodePathParentEscape, got {err:?}");
10548 };
10549 assert_eq!(slot, ":exe");
10550 assert_eq!(path, PathBuf::from("exe/../../escape"));
10551 }
10552
10553 #[test]
10554 fn validate_code_paths_rejects_parent_escape_servicos_trailing() {
10555 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/foo/../../escape.yaml"]);
10556 let err = c.validate_code_paths().unwrap_err();
10557 let ManifestError::CodePathParentEscape { slot, path } = err else {
10558 panic!("expected CodePathParentEscape, got {err:?}");
10559 };
10560 assert_eq!(slot, ":servicos");
10561 assert_eq!(path, PathBuf::from("servicos/foo/../../escape.yaml"));
10562 }
10563
10564 #[test]
10565 fn validate_code_paths_cross_slot_precedence_bibliotecas_before_exe_before_servicos() {
10566 // Cross-slot precedence pin: `:bibliotecas` → `:exe` →
10567 // `:servicos`. A manifest with malformed entries on all three
10568 // surfaces surfaces the `:bibliotecas` defect first, mirroring
10569 // the canonical declaration order
10570 // `Caixa::declared_foreign_code_slots` already establishes for
10571 // the foreign-code-slot diagnostic.
10572 let c = caixa_with_code_paths(vec![""], vec![""], vec![""]);
10573 let err = c.validate_code_paths().unwrap_err();
10574 assert!(
10575 matches!(
10576 err,
10577 ManifestError::CodePathEmpty {
10578 slot: ":bibliotecas"
10579 }
10580 ),
10581 "got {err:?}",
10582 );
10583 }
10584
10585 #[test]
10586 fn validate_code_paths_within_slot_precedence_empty_before_absolute_before_parent_escape() {
10587 // Within-slot precedence pin: empty → absolute → parent-escape,
10588 // matching the [`PathShapeViolation`] arm-ordering every peer
10589 // `is_sandboxed_relative_path` caller follows (b0c8389
10590 // BehaviorSpec, 26da2c7 UpgradeInstruction::StateChange). A
10591 // `:bibliotecas` list whose first entry is empty *and* whose
10592 // later entries are absolute/parent-escape surfaces the empty
10593 // arm first, on the lexicographically-earliest offending entry.
10594 let c = caixa_with_code_paths(vec!["", "/etc/passwd", "../escape.lisp"], vec![], vec![]);
10595 let err = c.validate_code_paths().unwrap_err();
10596 assert!(
10597 matches!(
10598 err,
10599 ManifestError::CodePathEmpty {
10600 slot: ":bibliotecas"
10601 }
10602 ),
10603 "got {err:?}",
10604 );
10605 }
10606
10607 #[test]
10608 fn validate_code_paths_first_offender_per_slot_wins() {
10609 // Within a single slot, the first declaration-order offender
10610 // surfaces — pins that the gate is left-to-right deterministic
10611 // (peer of every `*_first_collision_*` pin on duplicate gates).
10612 let c = caixa_with_code_paths(
10613 vec!["lib/ok.lisp", "/etc/escape", "../also-escape"],
10614 vec![],
10615 vec![],
10616 );
10617 let err = c.validate_code_paths().unwrap_err();
10618 let ManifestError::CodePathAbsolute { slot, path } = err else {
10619 panic!("expected CodePathAbsolute, got {err:?}");
10620 };
10621 assert_eq!(slot, ":bibliotecas");
10622 assert_eq!(path, PathBuf::from("/etc/escape"));
10623 }
10624
10625 #[test]
10626 fn validate_code_paths_diagnostic_carries_offending_slot_and_path() {
10627 // Diagnostic-shape pin (peer with
10628 // `nome_invalid_diagnostic_carries_offending_nome` /
10629 // `versao_invalid_diagnostic_carries_offending_versao`): the
10630 // error's Display surfaces both the offending `:slot` tag and
10631 // the offending path verbatim, so a `feira lint` run can render
10632 // the diagnostic without re-parsing.
10633 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
10634 let rendered = c.validate_code_paths().unwrap_err().to_string();
10635 assert!(
10636 rendered.contains(":bibliotecas"),
10637 "diagnostic must name the offending slot: {rendered}",
10638 );
10639 assert!(
10640 rendered.contains("/etc/passwd"),
10641 "diagnostic must quote the offending path: {rendered}",
10642 );
10643 }
10644
10645 #[test]
10646 fn validate_code_paths_rejects_duplicate_bibliotecas_entry() {
10647 // Canonical copy-paste-the-wrong-file footgun on the biblioteca
10648 // axis. Without the gate `feira build` re-parses the same lib
10649 // twice, wasting work and silently masking the author's intent
10650 // to declare a *second* biblioteca.
10651 let c = caixa_with_code_paths(vec!["lib/demo.lisp", "lib/demo.lisp"], vec![], vec![]);
10652 let err = c.validate_code_paths().unwrap_err();
10653 let ManifestError::CodePathDuplicate { slot, path } = err else {
10654 panic!("expected CodePathDuplicate, got {err:?}");
10655 };
10656 assert_eq!(slot, ":bibliotecas");
10657 assert_eq!(path, PathBuf::from("lib/demo.lisp"));
10658 }
10659
10660 #[test]
10661 fn validate_code_paths_rejects_duplicate_exe_entry() {
10662 // Same footgun on the Binario surface. The future `caixa-flake`
10663 // emitter that materializes each `:exe` entry as a flake
10664 // `packages.<name>` derivation would collide on the duplicate
10665 // package key — surfaced here at the typed-validate layer with a
10666 // self-locating diagnostic instead.
10667 let c = caixa_with_code_paths(vec![], vec!["exe/cli", "exe/cli"], vec![]);
10668 let err = c.validate_code_paths().unwrap_err();
10669 let ManifestError::CodePathDuplicate { slot, path } = err else {
10670 panic!("expected CodePathDuplicate, got {err:?}");
10671 };
10672 assert_eq!(slot, ":exe");
10673 assert_eq!(path, PathBuf::from("exe/cli"));
10674 }
10675
10676 #[test]
10677 fn validate_code_paths_rejects_duplicate_servicos_entry() {
10678 // Same footgun on the Servico surface. The peer caixa-helm /
10679 // caixa-flux renderers refuse `:servicos.len() != 1` with the
10680 // narrower `UnsupportedServicoCount` diagnostic, but that
10681 // diagnostic surfaces "too many servicos" without naming
10682 // "duplicate entry" — the typed self-locating framing only lands
10683 // at this gate.
10684 let c = caixa_with_code_paths(
10685 vec![],
10686 vec![],
10687 vec![
10688 "servicos/demo.computeunit.yaml",
10689 "servicos/demo.computeunit.yaml",
10690 ],
10691 );
10692 let err = c.validate_code_paths().unwrap_err();
10693 let ManifestError::CodePathDuplicate { slot, path } = err else {
10694 panic!("expected CodePathDuplicate, got {err:?}");
10695 };
10696 assert_eq!(slot, ":servicos");
10697 assert_eq!(path, PathBuf::from("servicos/demo.computeunit.yaml"));
10698 }
10699
10700 #[test]
10701 fn validate_code_paths_accepts_same_path_across_slots() {
10702 // Per-list scope pin: a `:bibliotecas` entry that happens to
10703 // collide with an `:exe` or `:servicos` entry as a *string* is
10704 // not a duplicate by this gate (each list gets its own HashSet),
10705 // mirroring the peer `:deps` ↔ `:deps-dev` per-list scope
10706 // (a `:nome` present in both lists is a legitimate dev-vs-runtime
10707 // shape on the dep axis). The structural `starts_with(<exe |
10708 // servicos>_dir)` fence at layout time prevents the realistic
10709 // cross-slot collision case from existing on disk, but the gate's
10710 // per-list scope is correct independent of that downstream fence.
10711 let c = caixa_with_code_paths(
10712 vec!["lib/x.lisp"],
10713 vec!["exe/x"],
10714 vec!["servicos/x.computeunit.yaml"],
10715 );
10716 c.validate_code_paths().unwrap();
10717 }
10718
10719 #[test]
10720 fn validate_code_paths_duplicate_fires_after_structural_checks_on_same_slot() {
10721 // Within-slot ordering pin: structural defects (empty / absolute
10722 // / parent-escape) fire before the duplicate gate on the same
10723 // slot. A `:bibliotecas ("" "lib/x.lisp" "lib/x.lisp")` shape
10724 // surfaces the narrower `CodePathEmpty` for the empty entry
10725 // first, not the duplicate on the later pair — same arm-ordering
10726 // every peer per-list duplicate gate uses (`:etiquetas` 360a499,
10727 // `:autores` 86c769b, `:deps` 359fba5).
10728 let c = caixa_with_code_paths(vec!["", "lib/x.lisp", "lib/x.lisp"], vec![], vec![]);
10729 let err = c.validate_code_paths().unwrap_err();
10730 assert!(
10731 matches!(
10732 err,
10733 ManifestError::CodePathEmpty {
10734 slot: ":bibliotecas"
10735 }
10736 ),
10737 "got {err:?}",
10738 );
10739 }
10740
10741 #[test]
10742 fn validate_code_paths_duplicate_in_bibliotecas_fires_before_duplicate_in_exe() {
10743 // Cross-slot ordering pin on the duplicate arm: `:bibliotecas`
10744 // duplicates surface before `:exe` duplicates, matching the
10745 // canonical `:bibliotecas` → `:exe` → `:servicos` declaration
10746 // order every peer per-slot diagnostic on this surface follows.
10747 let c = caixa_with_code_paths(
10748 vec!["lib/x.lisp", "lib/x.lisp"],
10749 vec!["exe/y", "exe/y"],
10750 vec![],
10751 );
10752 let err = c.validate_code_paths().unwrap_err();
10753 let ManifestError::CodePathDuplicate { slot, path } = err else {
10754 panic!("expected CodePathDuplicate, got {err:?}");
10755 };
10756 assert_eq!(slot, ":bibliotecas");
10757 assert_eq!(path, PathBuf::from("lib/x.lisp"));
10758 }
10759
10760 #[test]
10761 fn validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path() {
10762 // Diagnostic-shape pin (peer with
10763 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
10764 // on the structural arm): the duplicate-arm Display surfaces both
10765 // the offending `:slot` tag and the offending path verbatim, so a
10766 // `feira lint` run can render the diagnostic without re-parsing.
10767 let c = caixa_with_code_paths(
10768 vec![],
10769 vec![],
10770 vec![
10771 "servicos/demo.computeunit.yaml",
10772 "servicos/demo.computeunit.yaml",
10773 ],
10774 );
10775 let rendered = c.validate_code_paths().unwrap_err().to_string();
10776 assert!(
10777 rendered.contains(":servicos"),
10778 "diagnostic must name the offending slot: {rendered}",
10779 );
10780 assert!(
10781 rendered.contains("servicos/demo.computeunit.yaml"),
10782 "diagnostic must quote the offending path: {rendered}",
10783 );
10784 }
10785
10786 // ── validate_code_paths — `.lisp` extension gate on :bibliotecas ──
10787 //
10788 // The lifted [`crate::render::is_lisp_extension`] predicate (33cc830)
10789 // now gates `:bibliotecas` entries on the tatara-lisp-source file-type
10790 // contract. The `feira build` loop (`caixa-feira/src/cmd/build.rs:33`)
10791 // reads every declared `:bibliotecas` entry through `tatara_lisp::read`
10792 // at parse time — the same downstream consumer the peer `:behavior
10793 // :on-*` (c97815a, [`crate::BehaviorError::NonLispExtension`]) and
10794 // `:upgrade-from :state-change :script` (33cc830,
10795 // [`crate::UpgradeError::NonLispExtensionScript`]) axes route through.
10796 // `:exe` and `:servicos` are deliberately excluded — `:exe` is the
10797 // nix-built executable surface (`"exe/<name>"` shape per the canonical
10798 // [`crate::LayoutError::ExeOutsideDir`] error message and every
10799 // in-tree `caixa_with_code_paths` positive control), and `:servicos`
10800 // is the `.computeunit.yaml` ComputeUnit-CR axis.
10801
10802 #[test]
10803 fn validate_code_paths_rejects_no_extension_bibliotecas_entry() {
10804 // Canonical "I dragged the wrong file from the workspace tree"
10805 // footgun on the biblioteca axis. Without the gate `feira build`
10806 // hands the extensionless path to `tatara_lisp::read` and fails
10807 // with a parser-shaped diagnostic far from the source caixa.lisp,
10808 // with no field naming the offending `:bibliotecas` entry.
10809 for relpath in ["lib/demo", "demo", "lib/handlers/inner"] {
10810 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
10811 let err = c.validate_code_paths().unwrap_err();
10812 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
10813 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
10814 };
10815 assert_eq!(slot, ":bibliotecas");
10816 assert_eq!(path, PathBuf::from(relpath));
10817 }
10818 }
10819
10820 #[test]
10821 fn validate_code_paths_rejects_wrong_extension_bibliotecas_entry() {
10822 // Wrong-extension sweep across common authoring footguns. Same
10823 // sweep posture as the peer
10824 // `behavior::validate_rejects_wrong_extension` (c97815a) and
10825 // `upgrade::tests::state_change_rejects_wrong_extension_script`
10826 // (33cc830) cases.
10827 for relpath in [
10828 "lib/demo.rs",
10829 "lib/demo.txt",
10830 "lib/demo.md",
10831 "lib/demo.json",
10832 "lib/demo.yaml",
10833 "lib/demo.toml",
10834 "lib/demo.lisp.bak",
10835 "lib/demo.lispx",
10836 "lib/demo.lis",
10837 ] {
10838 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
10839 let err = c.validate_code_paths().unwrap_err();
10840 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
10841 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
10842 };
10843 assert_eq!(slot, ":bibliotecas");
10844 assert_eq!(path, PathBuf::from(relpath));
10845 }
10846 }
10847
10848 #[test]
10849 fn validate_code_paths_rejects_case_folded_extension_bibliotecas_entry() {
10850 // Case-sensitivity sweep — pins the strict lowercase `.lisp`
10851 // contract. An uppercase `.LISP` shape that the layout's existence
10852 // check would (case-insensitively, on case-insensitive volumes)
10853 // match the on-disk file still mismatches the canonical form the
10854 // codec emits, breaking the THEORY.md §V.2.7 render-determinism
10855 // contract. Mirrors the peer
10856 // `behavior::validate_rejects_case_folded_extension` (c97815a) and
10857 // `upgrade::tests::state_change_rejects_case_folded_extension_script`
10858 // (33cc830) sweeps.
10859 for relpath in [
10860 "lib/demo.LISP",
10861 "lib/demo.Lisp",
10862 "lib/demo.LiSp",
10863 "lib/demo.lISP",
10864 ] {
10865 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
10866 let err = c.validate_code_paths().unwrap_err();
10867 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
10868 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
10869 };
10870 assert_eq!(slot, ":bibliotecas");
10871 assert_eq!(path, PathBuf::from(relpath));
10872 }
10873 }
10874
10875 #[test]
10876 fn validate_code_paths_accepts_canonical_lisp_shapes() {
10877 // Positive-control sweep through every canonical authoring shape
10878 // every in-tree fixture and the `Caixa::template` scaffold use.
10879 // Mirrors the peer `behavior::validate_accepts_canonical_lisp_paths`
10880 // (c97815a) and the lifted predicate's own
10881 // `is_lisp_extension_accepts_canonical_shapes` sweep in render.rs
10882 // (33cc830).
10883 for relpath in [
10884 "lib/demo.lisp",
10885 "lib/handlers.lisp",
10886 "lib/migrations/v01-to-v02.lisp",
10887 "demo.lisp",
10888 "a.lisp",
10889 "./lib/demo.lisp",
10890 "lib/./handlers.lisp",
10891 "lib/migrations/v.0.1.lisp",
10892 ] {
10893 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
10894 c.validate_code_paths()
10895 .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
10896 }
10897 }
10898
10899 #[test]
10900 fn validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos() {
10901 // The file-type gate is per-slot — only `:bibliotecas` carries the
10902 // tatara-lisp-source contract. An extensionless `:exe` entry
10903 // (`exe/demo`) and a `.computeunit.yaml` `:servicos` entry are the
10904 // canonical shapes every in-tree fixture uses, and must continue
10905 // to pass validate. Pins that a future tightening that broadens
10906 // the `.lisp` gate to either axis surfaces as a test failure
10907 // rather than as a silent breaking change to existing valid
10908 // manifests.
10909 let c = caixa_with_code_paths(
10910 vec![],
10911 vec!["exe/demo", "exe/tool"],
10912 vec!["servicos/demo.computeunit.yaml"],
10913 );
10914 c.validate_code_paths().unwrap();
10915 }
10916
10917 #[test]
10918 fn validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension() {
10919 // Cross-arm precedence pin: a `:bibliotecas` entry that is *both*
10920 // sandbox-escaping and non-`.lisp` surfaces the more fundamental
10921 // sandbox-shape diagnostic first (the `.lisp` remediation would
10922 // be misleading when the offending path can never resolve under
10923 // the caixa root anyway). Mirrors the peer
10924 // `EmptyPath` → `AbsolutePath` → `ParentEscape` → `NonLispExtension`
10925 // ordering on `:behavior :on-*` (c97815a) and `EmptyScript` →
10926 // `AbsoluteScript` → `ParentEscapeScript` → `NonLispExtensionScript`
10927 // on `:upgrade-from :state-change :script` (33cc830).
10928 //
10929 // Empty wins (the strictly-smaller-scope structural arm).
10930 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
10931 assert!(
10932 matches!(
10933 c.validate_code_paths().unwrap_err(),
10934 ManifestError::CodePathEmpty {
10935 slot: ":bibliotecas"
10936 }
10937 ),
10938 "empty must win over non-lisp-extension",
10939 );
10940 // Absolute wins (the path can't resolve under the caixa root).
10941 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
10942 let err = c.validate_code_paths().unwrap_err();
10943 let ManifestError::CodePathAbsolute { slot, .. } = err else {
10944 panic!("absolute must win over non-lisp-extension, got {err:?}");
10945 };
10946 assert_eq!(slot, ":bibliotecas");
10947 // ParentEscape wins (the path escapes the caixa root).
10948 let c = caixa_with_code_paths(vec!["../sibling/x.txt"], vec![], vec![]);
10949 let err = c.validate_code_paths().unwrap_err();
10950 let ManifestError::CodePathParentEscape { slot, .. } = err else {
10951 panic!("parent-escape must win over non-lisp-extension, got {err:?}");
10952 };
10953 assert_eq!(slot, ":bibliotecas");
10954 }
10955
10956 #[test]
10957 fn validate_code_paths_non_lisp_extension_precedes_duplicate() {
10958 // Within-slot precedence pin: the per-entry file-type shape gate
10959 // fires before the cross-entry duplicate gate, so the narrower
10960 // structural defect dominates the uniqueness diagnostic. A
10961 // `("lib/x.txt" "lib/x.txt")` shape surfaces
10962 // `CodePathNonLispExtension` on the first entry rather than
10963 // `CodePathDuplicate` on the pair — same posture every per-entry
10964 // shape-gate-precedes-duplicate cascade follows on this surface
10965 // (the empty / absolute / parent-escape arms already precede the
10966 // duplicate arm; the lifted file-type arm joins that set).
10967 let c = caixa_with_code_paths(vec!["lib/x.txt", "lib/x.txt"], vec![], vec![]);
10968 let err = c.validate_code_paths().unwrap_err();
10969 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
10970 panic!("expected CodePathNonLispExtension, got {err:?}");
10971 };
10972 assert_eq!(slot, ":bibliotecas");
10973 assert_eq!(path, PathBuf::from("lib/x.txt"));
10974 }
10975
10976 #[test]
10977 fn validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path() {
10978 // Diagnostic-shape pin (peer with
10979 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
10980 // on the sandbox-shape arms and
10981 // `validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path`
10982 // on the duplicate arm): the file-type-arm Display surfaces both
10983 // the offending `:slot` tag, the offending path verbatim, and the
10984 // expected `.lisp` extension named in the remediation text, so a
10985 // `feira lint` run can render the diagnostic without re-parsing.
10986 let c = caixa_with_code_paths(vec!["lib/demo.rs"], vec![], vec![]);
10987 let rendered = c.validate_code_paths().unwrap_err().to_string();
10988 assert!(
10989 rendered.contains(":bibliotecas"),
10990 "diagnostic must name the offending slot: {rendered}",
10991 );
10992 assert!(
10993 rendered.contains("lib/demo.rs"),
10994 "diagnostic must quote the offending path: {rendered}",
10995 );
10996 assert!(
10997 rendered.contains(".lisp"),
10998 "diagnostic must name the expected extension: {rendered}",
10999 );
11000 }
11001
11002 // ── validate_code_paths — `.computeunit.yaml` compound-suffix gate on :servicos ──
11003 //
11004 // The lifted [`crate::render::is_computeunit_yaml_extension`] predicate
11005 // now gates `:servicos` entries on the ComputeUnit-CR YAML file-type
11006 // contract. The peer caixa-helm / caixa-flux renderers consume each
11007 // `:servicos` entry through `serde_yaml::from_str` as a typed
11008 // `ComputeUnit` CR — same downstream-consumer-shape lift as the peer
11009 // `:bibliotecas` `.lisp` gate (64772a9), here on the compound-suffix
11010 // axis `Path::extension` can't express on its own.
11011
11012 #[test]
11013 fn validate_code_paths_rejects_no_extension_servicos_entry() {
11014 // Canonical "I dragged the wrong file from the workspace tree"
11015 // footgun on the Servico axis. Without the gate the peer
11016 // caixa-helm / caixa-flux renderers hand the extensionless path
11017 // to `serde_yaml::from_str` and fail with a parser-shaped
11018 // diagnostic far from the source caixa.lisp, with no field
11019 // naming the offending `:servicos` entry.
11020 for relpath in ["servicos/demo", "demo", "servicos/sub/nested"] {
11021 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
11022 let err = c.validate_code_paths().unwrap_err();
11023 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
11024 panic!(
11025 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
11026 got {err:?}"
11027 );
11028 };
11029 assert_eq!(slot, ":servicos");
11030 assert_eq!(path, PathBuf::from(relpath));
11031 }
11032 }
11033
11034 #[test]
11035 fn validate_code_paths_rejects_wrong_extension_servicos_entry() {
11036 // Wrong-extension sweep across common authoring footguns on the
11037 // Servico axis. Bare `.yaml` is the canonical "I forgot the
11038 // `.computeunit` segment" typo; the off-by-one-segment shapes
11039 // (`.computeunit-yaml` / `.computeunit_yaml`) silently pass the
11040 // bare `Path::extension` view but mismatch the typed compound
11041 // suffix the renderers' `serde_yaml::from_str` consumer demands.
11042 // Same sweep-posture as the peer
11043 // `validate_code_paths_rejects_wrong_extension_bibliotecas_entry`
11044 // (64772a9) on the sibling tatara-lisp-source axis.
11045 for relpath in [
11046 "servicos/demo.yaml",
11047 "servicos/demo.yml",
11048 "servicos/demo.json",
11049 "servicos/demo.toml",
11050 "servicos/demo.txt",
11051 "servicos/demo.computeunit.yaml.bak",
11052 "servicos/demo.computeunit.yam",
11053 "servicos/demo.computeunit",
11054 "servicos/demo-computeunit.yaml",
11055 "servicos/demo_computeunit.yaml",
11056 ] {
11057 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
11058 let err = c.validate_code_paths().unwrap_err();
11059 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
11060 panic!(
11061 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
11062 got {err:?}"
11063 );
11064 };
11065 assert_eq!(slot, ":servicos");
11066 assert_eq!(path, PathBuf::from(relpath));
11067 }
11068 }
11069
11070 #[test]
11071 fn validate_code_paths_rejects_case_folded_extension_servicos_entry() {
11072 // Case-sensitivity sweep — pins the strict lowercase
11073 // `.computeunit.yaml` contract. A case-folded shape that the
11074 // layout's existence check would (case-insensitively, on
11075 // case-insensitive volumes) match the on-disk file still
11076 // mismatches the canonical form the codec emits, breaking the
11077 // THEORY.md §V.2.7 render-determinism contract. Mirrors the peer
11078 // `validate_code_paths_rejects_case_folded_extension_bibliotecas_entry`
11079 // (64772a9) sweep on the sibling tatara-lisp-source axis.
11080 for relpath in [
11081 "servicos/demo.ComputeUnit.yaml",
11082 "servicos/demo.COMPUTEUNIT.yaml",
11083 "servicos/demo.computeunit.YAML",
11084 "servicos/demo.computeunit.Yaml",
11085 "servicos/demo.COMPUTEUNIT.YAML",
11086 ] {
11087 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
11088 let err = c.validate_code_paths().unwrap_err();
11089 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
11090 panic!(
11091 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
11092 got {err:?}"
11093 );
11094 };
11095 assert_eq!(slot, ":servicos");
11096 assert_eq!(path, PathBuf::from(relpath));
11097 }
11098 }
11099
11100 #[test]
11101 fn validate_code_paths_rejects_empty_stem_servicos_entry() {
11102 // Degenerate hidden-file shape: a file name exactly equal to the
11103 // suffix (`.computeunit.yaml` — no stem preceding the suffix) is
11104 // the structural "Servico declared with no identity" footgun.
11105 // The substrate identifies each ComputeUnit by the file-stem
11106 // segment that precedes `.computeunit.yaml` (the rendered
11107 // `lareira-<stem>` Helm chart, the per-Servico `metadata.name`,
11108 // the M3 `:contratos` membership lookup), so an empty stem
11109 // leaves the Servico unidentifiable. Pinned at the typed-axis
11110 // level so a future regression that drops the `name.len() >
11111 // SUFFIX.len()` bound at the predicate surfaces here, not
11112 // piecemeal as a `lareira-` chart-name collision at render time.
11113 for relpath in ["servicos/.computeunit.yaml"] {
11114 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
11115 let err = c.validate_code_paths().unwrap_err();
11116 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
11117 panic!(
11118 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
11119 got {err:?}"
11120 );
11121 };
11122 assert_eq!(slot, ":servicos");
11123 assert_eq!(path, PathBuf::from(relpath));
11124 }
11125 }
11126
11127 #[test]
11128 fn validate_code_paths_accepts_canonical_computeunit_yaml_shapes() {
11129 // Positive-control sweep through every canonical authoring shape
11130 // every in-tree fixture and the `Caixa::template` scaffold use.
11131 // Mirrors the peer
11132 // `validate_code_paths_accepts_canonical_lisp_shapes` (64772a9)
11133 // and the lifted predicate's own
11134 // `computeunit_yaml_extension_accepts_canonical_shapes` sweep in
11135 // render.rs.
11136 for relpath in [
11137 "servicos/demo.computeunit.yaml",
11138 "servicos/hello-rio.computeunit.yaml",
11139 "servicos/my-service.computeunit.yaml",
11140 "servicos/a.computeunit.yaml",
11141 "./servicos/demo.computeunit.yaml",
11142 "servicos/./demo.computeunit.yaml",
11143 "servicos/sub/nested.computeunit.yaml",
11144 "servicos/v0.1.computeunit.yaml",
11145 ] {
11146 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
11147 c.validate_code_paths()
11148 .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
11149 }
11150 }
11151
11152 #[test]
11153 fn validate_code_paths_non_computeunit_yaml_extension_does_not_fire_on_bibliotecas_or_exe() {
11154 // The file-type gate is per-slot — only `:servicos` carries the
11155 // ComputeUnit-CR YAML contract. A canonical `.lisp` `:bibliotecas`
11156 // entry and an extensionless `:exe` entry are the canonical
11157 // shapes every in-tree fixture uses, and must continue to pass
11158 // validate. Peer of
11159 // `validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos`
11160 // (64772a9) — together pin that the typed
11161 // [`CodePathFileType`] dispatch is exhaustively per-slot, with no
11162 // cross-axis leakage in either direction.
11163 let c = caixa_with_code_paths(
11164 vec!["lib/demo.lisp"],
11165 vec!["exe/demo", "exe/tool"],
11166 vec!["servicos/demo.computeunit.yaml"],
11167 );
11168 c.validate_code_paths().unwrap();
11169 }
11170
11171 #[test]
11172 fn validate_code_paths_sandbox_shape_arms_precede_non_computeunit_yaml_extension() {
11173 // Cross-arm precedence pin: a `:servicos` entry that is *both*
11174 // sandbox-escaping and wrong-extension surfaces the more
11175 // fundamental sandbox-shape diagnostic first (the
11176 // `.computeunit.yaml` remediation would be misleading when the
11177 // offending path can never resolve under the caixa root
11178 // anyway). Mirrors the peer
11179 // `validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension`
11180 // (64772a9) ordering on the sibling `:bibliotecas` axis and the
11181 // peer `EmptyPath` → `AbsolutePath` → `ParentEscape` →
11182 // `NonComputeUnitYamlExtension` arm-ordering the dispatch
11183 // table establishes.
11184 //
11185 // Empty wins (the strictly-smaller-scope structural arm).
11186 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
11187 assert!(
11188 matches!(
11189 c.validate_code_paths().unwrap_err(),
11190 ManifestError::CodePathEmpty { slot: ":servicos" }
11191 ),
11192 "empty must win over non-computeunit-yaml-extension",
11193 );
11194 // Absolute wins (the path can't resolve under the caixa root).
11195 let c = caixa_with_code_paths(vec![], vec![], vec!["/etc/foo.yaml"]);
11196 let err = c.validate_code_paths().unwrap_err();
11197 let ManifestError::CodePathAbsolute { slot, .. } = err else {
11198 panic!("absolute must win over non-computeunit-yaml-extension, got {err:?}");
11199 };
11200 assert_eq!(slot, ":servicos");
11201 // ParentEscape wins (the path escapes the caixa root).
11202 let c = caixa_with_code_paths(vec![], vec![], vec!["../sibling/x.yaml"]);
11203 let err = c.validate_code_paths().unwrap_err();
11204 let ManifestError::CodePathParentEscape { slot, .. } = err else {
11205 panic!("parent-escape must win over non-computeunit-yaml-extension, got {err:?}");
11206 };
11207 assert_eq!(slot, ":servicos");
11208 }
11209
11210 #[test]
11211 fn validate_code_paths_non_computeunit_yaml_extension_precedes_duplicate() {
11212 // Within-slot precedence pin: the per-entry file-type shape gate
11213 // fires before the cross-entry duplicate gate, so the narrower
11214 // structural defect dominates the uniqueness diagnostic. A
11215 // `("servicos/x.yaml" "servicos/x.yaml")` shape surfaces
11216 // `CodePathNonComputeUnitYamlExtension` on the first entry
11217 // rather than `CodePathDuplicate` on the pair — same posture
11218 // every per-entry shape-gate-precedes-duplicate cascade follows
11219 // on this surface, peer of the 64772a9 `:bibliotecas`
11220 // `("lib/x.txt" "lib/x.txt")` ordering.
11221 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/x.yaml", "servicos/x.yaml"]);
11222 let err = c.validate_code_paths().unwrap_err();
11223 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
11224 panic!("expected CodePathNonComputeUnitYamlExtension, got {err:?}");
11225 };
11226 assert_eq!(slot, ":servicos");
11227 assert_eq!(path, PathBuf::from("servicos/x.yaml"));
11228 }
11229
11230 #[test]
11231 fn validate_code_paths_non_computeunit_yaml_extension_diagnostic_carries_offending_slot_and_path()
11232 {
11233 // Diagnostic-shape pin (peer with
11234 // `validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path`
11235 // on the sibling tatara-lisp-source axis): the file-type-arm
11236 // Display surfaces both the offending `:slot` tag, the
11237 // offending path verbatim, and the expected
11238 // `.computeunit.yaml` compound suffix named in the remediation
11239 // text, so a `feira lint` run can render the diagnostic without
11240 // re-parsing.
11241 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.yaml"]);
11242 let rendered = c.validate_code_paths().unwrap_err().to_string();
11243 assert!(
11244 rendered.contains(":servicos"),
11245 "diagnostic must name the offending slot: {rendered}",
11246 );
11247 assert!(
11248 rendered.contains("servicos/demo.yaml"),
11249 "diagnostic must quote the offending path: {rendered}",
11250 );
11251 assert!(
11252 rendered.contains(".computeunit.yaml"),
11253 "diagnostic must name the expected compound suffix: {rendered}",
11254 );
11255 }
11256
11257 // ── validate_etiquetas — universal-axis registry-search-tag shape ──
11258
11259 fn caixa_with_etiquetas(etiquetas: Vec<&str>) -> Caixa {
11260 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11261 c.etiquetas = etiquetas.into_iter().map(String::from).collect();
11262 c
11263 }
11264
11265 #[test]
11266 fn validate_etiquetas_accepts_empty_list() {
11267 // The empty-list identity: every caixa with no declared tags
11268 // trivially passes — `Caixa::template` emits `:etiquetas ()`,
11269 // so the gate is non-disruptive against every existing manifest.
11270 let c = caixa_with_etiquetas(vec![]);
11271 c.validate_etiquetas().unwrap();
11272 }
11273
11274 #[test]
11275 fn validate_etiquetas_accepts_canonical_forms() {
11276 // Positive control sweep: a canonical-shaped non-empty distinct
11277 // tag list passes, mirroring the example checkout-aplicacao
11278 // (`:etiquetas ("example" "aplicacao" "mesh" "ecommerce" "demo")`)
11279 // and the hello-rio fixture (`("hello-world" "wasm" "rust")`).
11280 let c = caixa_with_etiquetas(vec!["example", "aplicacao", "mesh", "ecommerce", "demo"]);
11281 c.validate_etiquetas().unwrap();
11282 }
11283
11284 #[test]
11285 fn validate_etiquetas_rejects_empty_entry() {
11286 // Canonical paste-from-blank-doc footgun. Without the gate the
11287 // empty entry rendered as `keywords: [""]` in `Chart.yaml`, a
11288 // no-op tag indexing nothing in the future caixa-registry.
11289 let c = caixa_with_etiquetas(vec![""]);
11290 let err = c.validate_etiquetas().unwrap_err();
11291 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
11292 }
11293
11294 #[test]
11295 fn validate_etiquetas_rejects_duplicate_entry() {
11296 // Canonical copy-paste-the-wrong-tag footgun. Without the gate
11297 // the duplicate was silently dedup'd by caixa-helm's BTreeSet
11298 // collect at chart render — a "second wins / one silently
11299 // disappears" shape divergent from every peer typed-graph set
11300 // gate. The duplicate-arm names the offending tag verbatim.
11301 let c = caixa_with_etiquetas(vec!["demo", "demo"]);
11302 let err = c.validate_etiquetas().unwrap_err();
11303 let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
11304 panic!("expected EtiquetaDuplicate, got {err:?}");
11305 };
11306 assert_eq!(etiqueta, "demo");
11307 }
11308
11309 #[test]
11310 fn validate_etiquetas_empty_takes_precedence_over_duplicate() {
11311 // Empty-first cascade pin: `("" "demo" "demo")` surfaces
11312 // `EtiquetaEmpty` not `EtiquetaDuplicate` — the narrower
11313 // structural "this entry has no value" defect dominates the
11314 // cross-entry uniqueness diagnostic. Mirrors the peer
11315 // empty-before-duplicate cascades on `:caracteristicas`
11316 // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
11317 // fc3b4d5) and `:membros :caixa` (`MembroCaixaEmpty` before
11318 // `MembroDuplicate`).
11319 let c = caixa_with_etiquetas(vec!["", "demo", "demo"]);
11320 let err = c.validate_etiquetas().unwrap_err();
11321 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
11322 }
11323
11324 #[test]
11325 fn validate_etiquetas_duplicate_reports_first_collision() {
11326 // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
11327 // duplicate (the lexicographically-earliest offending position
11328 // — the second `"a"` at index 2 collides with the first `"a"`
11329 // at index 0), not the later `"b"` collision at index 3,
11330 // peer with every other first-collision diagnostic posture on
11331 // this surface (`validate_load_singularity_reports_first_collision`,
11332 // `validate_cleanup_singularity_reports_first_collision`).
11333 let c = caixa_with_etiquetas(vec!["a", "b", "a", "b"]);
11334 let err = c.validate_etiquetas().unwrap_err();
11335 let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
11336 panic!("expected EtiquetaDuplicate, got {err:?}");
11337 };
11338 assert_eq!(etiqueta, "a");
11339 }
11340
11341 #[test]
11342 fn validate_etiquetas_case_sensitive() {
11343 // Case-sensitivity pin: `("Foo" "foo")` is two distinct entries,
11344 // mirroring the peer `:membros :caixa` / `:children :caixa`
11345 // exact-string-match discipline. The shape gate this routine
11346 // landed (`is_chart_keyword_shape`, Cargo crates.io keyword
11347 // grammar) accepts mixed case — crates.io's keyword rule is
11348 // "case-insensitive" at the index layer but admits mixed case
11349 // at the entry layer (the canonical Helm chart `keywords:`
11350 // shape is lowercase by convention, but the grammar admits
11351 // uppercase). Case-sensitivity at the duplicate-set layer
11352 // remains structural — two distinct strings are two distinct
11353 // entries.
11354 let c = caixa_with_etiquetas(vec!["Foo", "foo"]);
11355 c.validate_etiquetas().unwrap();
11356 }
11357
11358 #[test]
11359 fn validate_etiquetas_diagnostic_carries_offending_tag() {
11360 // Diagnostic-shape pin (peer with
11361 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`):
11362 // the error's Display surfaces the offending tag verbatim, so a
11363 // `feira lint` run can render the diagnostic without re-parsing
11364 // and the author can grep their caixa.lisp for the offending
11365 // value.
11366 let c = caixa_with_etiquetas(vec!["demo", "demo"]);
11367 let rendered = c.validate_etiquetas().unwrap_err().to_string();
11368 assert!(
11369 rendered.contains(":etiquetas"),
11370 "diagnostic must name the offending slot: {rendered}",
11371 );
11372 assert!(
11373 rendered.contains("demo"),
11374 "diagnostic must quote the offending tag: {rendered}",
11375 );
11376 }
11377
11378 #[test]
11379 fn validate_etiquetas_rejects_leading_whitespace_entry() {
11380 // Canonical paste-from-aligned-doc footgun. Without the shape
11381 // gate `" mesh"` silently passed validate and landed as a
11382 // YAML plain-style scalar with leading whitespace in the
11383 // rendered Chart.yaml `keywords:` array — every YAML 1.2
11384 // dumper trims leading whitespace from plain-style scalars,
11385 // so the authored space round-tripped inconsistently back
11386 // through `caixa.lisp`. Mirrors the peer
11387 // `validate_autores_rejects_leading_whitespace_entry`.
11388 let c = caixa_with_etiquetas(vec![" mesh"]);
11389 let err = c.validate_etiquetas().unwrap_err();
11390 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11391 panic!("expected EtiquetaInvalid, got {err:?}");
11392 };
11393 assert_eq!(etiqueta, " mesh");
11394 assert!(reason.contains("whitespace"), "got: {reason}");
11395 }
11396
11397 #[test]
11398 fn validate_etiquetas_rejects_embedded_newline_entry() {
11399 // Canonical paste-from-multiline-doc footgun — the author
11400 // pasted a multi-tag block into one `:etiquetas` entry
11401 // instead of splitting into one entry per tag. Without the
11402 // shape gate `"mesh\nhttp"` silently passed validate and
11403 // landed as a YAML-illegal multi-line scalar in the rendered
11404 // Chart.yaml `keywords:` array.
11405 let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
11406 let err = c.validate_etiquetas().unwrap_err();
11407 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11408 panic!("expected EtiquetaInvalid, got {err:?}");
11409 };
11410 assert_eq!(etiqueta, "mesh\nhttp");
11411 assert!(reason.contains("newline"), "got: {reason}");
11412 }
11413
11414 #[test]
11415 fn validate_etiquetas_rejects_embedded_comma_entry() {
11416 // Canonical CSV-list-separator-confusion footgun: the author
11417 // confused the CSV-style separator convention with the
11418 // `:etiquetas` list grammar. Without the shape gate
11419 // `"mesh,http,grpc"` silently passed validate and landed as a
11420 // single malformed search tag in the rendered Chart.yaml
11421 // `keywords:` array — Artifact Hub's keyword index would
11422 // either silently drop the tag or index it as
11423 // `mesh,http,grpc` instead of three separate tags.
11424 let c = caixa_with_etiquetas(vec!["mesh,http,grpc"]);
11425 let err = c.validate_etiquetas().unwrap_err();
11426 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11427 panic!("expected EtiquetaInvalid, got {err:?}");
11428 };
11429 assert_eq!(etiqueta, "mesh,http,grpc");
11430 assert!(reason.contains('`'), "got: {reason}");
11431 assert!(reason.contains(','), "got: {reason}");
11432 }
11433
11434 #[test]
11435 fn validate_etiquetas_rejects_embedded_slash_entry() {
11436 // Canonical path-separator-confusion footgun: the author
11437 // confused namespace-path notation with the keyword grammar.
11438 let c = caixa_with_etiquetas(vec!["caixa/servico"]);
11439 let err = c.validate_etiquetas().unwrap_err();
11440 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11441 panic!("expected EtiquetaInvalid, got {err:?}");
11442 };
11443 assert_eq!(etiqueta, "caixa/servico");
11444 assert!(reason.contains('/'), "got: {reason}");
11445 }
11446
11447 #[test]
11448 fn validate_etiquetas_rejects_leading_digit_entry() {
11449 // Canonical paste-from-numbered-list footgun: the author
11450 // copied `1. mesh` from a numbered doc and the `1` leaked
11451 // into the tag.
11452 let c = caixa_with_etiquetas(vec!["1mesh"]);
11453 let err = c.validate_etiquetas().unwrap_err();
11454 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11455 panic!("expected EtiquetaInvalid, got {err:?}");
11456 };
11457 assert_eq!(etiqueta, "1mesh");
11458 assert!(reason.contains("digit"), "got: {reason}");
11459 }
11460
11461 #[test]
11462 fn validate_etiquetas_rejects_leading_hyphen_entry() {
11463 // Canonical kebab-leak footgun.
11464 let c = caixa_with_etiquetas(vec!["-foo"]);
11465 let err = c.validate_etiquetas().unwrap_err();
11466 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11467 panic!("expected EtiquetaInvalid, got {err:?}");
11468 };
11469 assert_eq!(etiqueta, "-foo");
11470 assert!(reason.contains('-'), "got: {reason}");
11471 }
11472
11473 #[test]
11474 fn validate_etiquetas_rejects_non_ascii_entry() {
11475 // Canonical paste-from-Unicode-doc footgun. Every legitimate
11476 // search tag is strict ASCII; raw non-ASCII silently
11477 // round-trips inconsistently across NFC/NFD normalization on
11478 // APFS / case-folding filesystems and breaks the Artifact Hub
11479 // keyword search index lookup.
11480 let c = caixa_with_etiquetas(vec!["café"]);
11481 let err = c.validate_etiquetas().unwrap_err();
11482 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11483 panic!("expected EtiquetaInvalid, got {err:?}");
11484 };
11485 assert_eq!(etiqueta, "café");
11486 assert!(reason.contains("non-ASCII"), "got: {reason}");
11487 }
11488
11489 #[test]
11490 fn validate_etiquetas_rejects_period_entry() {
11491 // Canonical namespace-confusion / version-suffix footgun
11492 // (`"http.1"` / `"v1.0"`): Cargo's crates.io keyword grammar
11493 // excludes `.` from the continuation set even though the
11494 // sibling `:caracteristicas` axis (Cargo's feature-name
11495 // grammar) admits it. Tighter than the sibling axis, peer
11496 // with Cargo's own crates.io keyword shape.
11497 let c = caixa_with_etiquetas(vec!["http.1"]);
11498 let err = c.validate_etiquetas().unwrap_err();
11499 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
11500 panic!("expected EtiquetaInvalid, got {err:?}");
11501 };
11502 assert_eq!(etiqueta, "http.1");
11503 assert!(reason.contains('.'), "got: {reason}");
11504 }
11505
11506 #[test]
11507 fn validate_etiquetas_empty_takes_precedence_over_shape() {
11508 // Per-entry empty-first cascade pin: an entry that is both
11509 // empty *and* shape-invalid surfaces `EtiquetaEmpty` (the
11510 // narrower "this entry has no value" structural defect
11511 // dominates the broader shape-predicate diagnostic). The
11512 // empty arm fires before the shape predicate is consulted,
11513 // mirroring the peer `validate_autores_empty_takes_precedence_over_shape`
11514 // cascade established on the sibling universal-axis Vec<String>
11515 // surface.
11516 let c = caixa_with_etiquetas(vec![""]);
11517 let err = c.validate_etiquetas().unwrap_err();
11518 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
11519 }
11520
11521 #[test]
11522 fn validate_etiquetas_shape_takes_precedence_over_duplicate() {
11523 // Per-entry shape-before-cross-entry-duplicate cascade pin: an
11524 // entry that is malformed surfaces `EtiquetaInvalid` even when
11525 // a later entry would have collided on duplicate. The
11526 // per-entry shape arm fires inside the same loop iteration as
11527 // the empty arm, before the seen-set insert at end-of-iteration
11528 // — structural per-entry defects dominate the cross-entry
11529 // uniqueness diagnostic. Mirrors the peer
11530 // `validate_autores_shape_takes_precedence_over_duplicate`.
11531 let c = caixa_with_etiquetas(vec!["mesh\nhttp", "mesh\nhttp"]);
11532 let err = c.validate_etiquetas().unwrap_err();
11533 assert!(
11534 matches!(err, ManifestError::EtiquetaInvalid { .. }),
11535 "got {err:?}",
11536 );
11537 }
11538
11539 #[test]
11540 fn validate_etiquetas_invalid_diagnostic_names_offending_slot_and_value() {
11541 // Diagnostic-shape pin on the new shape arm (peer with
11542 // `validate_autores_invalid_diagnostic_names_offending_slot_and_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 `:etiquetas` entry to fix.
11546 let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
11547 let rendered = c.validate_etiquetas().unwrap_err().to_string();
11548 assert!(
11549 rendered.contains(":etiquetas"),
11550 "diagnostic must name the offending slot: {rendered}",
11551 );
11552 assert!(
11553 rendered.contains("mesh\\nhttp"),
11554 "diagnostic must quote the offending value (debug-escaped): {rendered}",
11555 );
11556 }
11557
11558 #[test]
11559 fn validate_etiquetas_rejects_at_21_byte_boundary() {
11560 // The 20-byte cap pin — boundary-exceeding case rejected,
11561 // boundary-accepting case passes. Mirrors the peer
11562 // `chart_keyword_shape_rejects_at_21_byte_boundary` substrate-
11563 // side pin, surfaced at the per-axis caller so the cap
11564 // propagates through validate end-to-end. Constructed as a
11565 // single all-`a` token so only the cap arm fires.
11566 let max_ok = "a".repeat(20);
11567 let c = caixa_with_etiquetas(vec![max_ok.as_str()]);
11568 c.validate_etiquetas().unwrap();
11569 let too_long = "a".repeat(21);
11570 let c = caixa_with_etiquetas(vec![too_long.as_str()]);
11571 let err = c.validate_etiquetas().unwrap_err();
11572 let ManifestError::EtiquetaInvalid { reason, .. } = err else {
11573 panic!("expected EtiquetaInvalid, got {err:?}");
11574 };
11575 assert!(reason.contains("20"), "got: {reason}");
11576 assert!(reason.contains("21"), "got: {reason}");
11577 }
11578
11579 #[test]
11580 fn validate_etiquetas_accepts_canonical_shaped_forms() {
11581 // Positive control sweep: every canonical-shaped tag from the
11582 // hello-rio / checkout-aplicacao / pangea-tatara-akeyless
11583 // example fixtures plus the substrate-fixed tags caixa-helm
11584 // unions in at chart render. Drift between this list and the
11585 // substrate-side `chart_keyword_shape_accepts_canonical_forms`
11586 // sweep surfaces here — one source of truth for the rule.
11587 let c = caixa_with_etiquetas(vec![
11588 "example",
11589 "aplicacao",
11590 "mesh",
11591 "ecommerce",
11592 "demo",
11593 "infrastructure",
11594 "aws",
11595 "akeyless",
11596 "pangea-native",
11597 "hello-world",
11598 "wasm",
11599 "rust",
11600 "tatara-lisp",
11601 "caixa-servico",
11602 "lareira",
11603 ]);
11604 c.validate_etiquetas().unwrap();
11605 }
11606
11607 // ── validate_autores — universal-axis maintainer shape ────────────
11608
11609 fn caixa_with_autores(autores: Vec<&str>) -> Caixa {
11610 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11611 c.autores = autores.into_iter().map(String::from).collect();
11612 c
11613 }
11614
11615 #[test]
11616 fn validate_autores_accepts_empty_list() {
11617 // The empty-list identity: `Caixa::template` emits `:autores ()`,
11618 // so the gate is non-disruptive against every existing manifest.
11619 let c = caixa_with_autores(vec![]);
11620 c.validate_autores().unwrap();
11621 }
11622
11623 #[test]
11624 fn validate_autores_accepts_canonical_forms() {
11625 // Positive control sweep: every canonical-shaped non-empty
11626 // distinct maintainer list passes — the hello-rio / checkout-
11627 // aplicacao fixtures' `:autores ("pleme-io")` shape, plus the
11628 // multi-author shape downstream packaging surfaces emit.
11629 let c = caixa_with_autores(vec!["pleme-io"]);
11630 c.validate_autores().unwrap();
11631 let c = caixa_with_autores(vec!["alice <alice@example.com>", "bob <bob@example.com>"]);
11632 c.validate_autores().unwrap();
11633 }
11634
11635 #[test]
11636 fn validate_autores_rejects_empty_entry() {
11637 // Canonical paste-from-blank-doc footgun. Without the gate the
11638 // empty entry rendered as `maintainers: [{name: "", email: null}]`
11639 // in `Chart.yaml`, a no-op maintainer the substrate cannot route
11640 // to.
11641 let c = caixa_with_autores(vec![""]);
11642 let err = c.validate_autores().unwrap_err();
11643 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
11644 }
11645
11646 #[test]
11647 fn validate_autores_rejects_duplicate_entry() {
11648 // Canonical copy-paste-the-wrong-author footgun. Unlike the
11649 // `:etiquetas` peer (caixa-helm's `BTreeSet` collect silently
11650 // dedups the rendered `keywords:` array), the `maintainers:`
11651 // rendering has *no* dedup — duplicates stack verbatim. The
11652 // duplicate-arm names the offending author verbatim.
11653 let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
11654 let err = c.validate_autores().unwrap_err();
11655 let ManifestError::AutorDuplicate { autor } = err else {
11656 panic!("expected AutorDuplicate, got {err:?}");
11657 };
11658 assert_eq!(autor, "pleme-io");
11659 }
11660
11661 #[test]
11662 fn validate_autores_empty_takes_precedence_over_duplicate() {
11663 // Empty-first cascade pin: `("" "pleme-io" "pleme-io")` surfaces
11664 // `AutorEmpty` not `AutorDuplicate` — the narrower structural
11665 // "this entry has no value" defect dominates the cross-entry
11666 // uniqueness diagnostic. Mirrors the peer empty-before-duplicate
11667 // cascades on `:etiquetas` (`EtiquetaEmpty` before
11668 // `EtiquetaDuplicate`, 360a499), `:caracteristicas`
11669 // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
11670 // fc3b4d5), and `:membros :caixa` (`MembroCaixaEmpty` before
11671 // `MembroDuplicate`).
11672 let c = caixa_with_autores(vec!["", "pleme-io", "pleme-io"]);
11673 let err = c.validate_autores().unwrap_err();
11674 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
11675 }
11676
11677 #[test]
11678 fn validate_autores_duplicate_reports_first_collision() {
11679 // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
11680 // duplicate (the lexicographically-earliest offending position
11681 // — the second `"a"` at index 2 collides with the first `"a"`
11682 // at index 0), not the later `"b"` collision at index 3,
11683 // peer with every other first-collision diagnostic posture on
11684 // this surface.
11685 let c = caixa_with_autores(vec!["a", "b", "a", "b"]);
11686 let err = c.validate_autores().unwrap_err();
11687 let ManifestError::AutorDuplicate { autor } = err else {
11688 panic!("expected AutorDuplicate, got {err:?}");
11689 };
11690 assert_eq!(autor, "a");
11691 }
11692
11693 #[test]
11694 fn validate_autores_case_sensitive() {
11695 // Case-sensitivity pin: `("Pleme-io" "pleme-io")` is two distinct
11696 // entries, mirroring the peer `:etiquetas` / `:membros :caixa`
11697 // / `:children :caixa` exact-string-match discipline.
11698 let c = caixa_with_autores(vec!["Pleme-io", "pleme-io"]);
11699 c.validate_autores().unwrap();
11700 }
11701
11702 #[test]
11703 fn validate_autores_diagnostic_carries_offending_author() {
11704 // Diagnostic-shape pin (peer with
11705 // `validate_etiquetas_diagnostic_carries_offending_tag`): the
11706 // error's Display surfaces the offending author verbatim, so a
11707 // `feira lint` run can render the diagnostic without re-parsing
11708 // and the author can grep their caixa.lisp for the offending
11709 // value.
11710 let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
11711 let rendered = c.validate_autores().unwrap_err().to_string();
11712 assert!(
11713 rendered.contains(":autores"),
11714 "diagnostic must name the offending slot: {rendered}",
11715 );
11716 assert!(
11717 rendered.contains("pleme-io"),
11718 "diagnostic must quote the offending author: {rendered}",
11719 );
11720 }
11721
11722 #[test]
11723 fn validate_autores_rejects_leading_whitespace_entry() {
11724 // Canonical paste-from-aligned-doc footgun. Without the shape
11725 // gate `" pleme-io"` silently passed validate and landed as a
11726 // YAML plain-style scalar with leading whitespace in the
11727 // rendered Chart.yaml `maintainers:` array — every YAML 1.2
11728 // dumper trims leading whitespace from plain-style scalars, so
11729 // the authored space round-tripped inconsistently back through
11730 // `caixa.lisp`. Mirrors the peer
11731 // `validate_descricao_rejects_leading_whitespace`.
11732 let c = caixa_with_autores(vec![" pleme-io"]);
11733 let err = c.validate_autores().unwrap_err();
11734 let ManifestError::AutorInvalid { autor, reason } = err else {
11735 panic!("expected AutorInvalid, got {err:?}");
11736 };
11737 assert_eq!(autor, " pleme-io");
11738 assert!(reason.contains("whitespace"), "got: {reason}");
11739 }
11740
11741 #[test]
11742 fn validate_autores_rejects_trailing_whitespace_entry() {
11743 // Canonical paste-from-doc footgun.
11744 let c = caixa_with_autores(vec!["pleme-io "]);
11745 let err = c.validate_autores().unwrap_err();
11746 let ManifestError::AutorInvalid { autor, reason } = err else {
11747 panic!("expected AutorInvalid, got {err:?}");
11748 };
11749 assert_eq!(autor, "pleme-io ");
11750 assert!(reason.contains("whitespace"), "got: {reason}");
11751 }
11752
11753 #[test]
11754 fn validate_autores_rejects_embedded_newline_entry() {
11755 // Canonical paste-from-multiline-doc footgun — the author
11756 // pasted a multi-line block of author records into one
11757 // `:autores` entry instead of splitting into one entry per
11758 // author. Without the shape gate `"alice\nbob"` silently
11759 // passed validate and landed as a YAML-illegal multi-line
11760 // scalar in the rendered Chart.yaml `maintainers:` array.
11761 let c = caixa_with_autores(vec!["alice\nbob"]);
11762 let err = c.validate_autores().unwrap_err();
11763 let ManifestError::AutorInvalid { autor, reason } = err else {
11764 panic!("expected AutorInvalid, got {err:?}");
11765 };
11766 assert_eq!(autor, "alice\nbob");
11767 assert!(reason.contains("newline"), "got: {reason}");
11768 }
11769
11770 #[test]
11771 fn validate_autores_rejects_embedded_carriage_return_entry() {
11772 // Canonical paste-from-Windows-CRLF-doc footgun.
11773 let c = caixa_with_autores(vec!["alice\rbob"]);
11774 let err = c.validate_autores().unwrap_err();
11775 let ManifestError::AutorInvalid { autor, reason } = err else {
11776 panic!("expected AutorInvalid, got {err:?}");
11777 };
11778 assert_eq!(autor, "alice\rbob");
11779 assert!(reason.contains("carriage return"), "got: {reason}");
11780 }
11781
11782 #[test]
11783 fn validate_autores_rejects_embedded_tab_entry() {
11784 // Canonical tab-from-aligned-doc footgun.
11785 let c = caixa_with_autores(vec!["Pleme\tContributors"]);
11786 let err = c.validate_autores().unwrap_err();
11787 let ManifestError::AutorInvalid { autor, reason } = err else {
11788 panic!("expected AutorInvalid, got {err:?}");
11789 };
11790 assert_eq!(autor, "Pleme\tContributors");
11791 assert!(reason.contains("tab"), "got: {reason}");
11792 }
11793
11794 #[test]
11795 fn validate_autores_rejects_embedded_control_bytes_entry() {
11796 // Paste-from-binary-blob footguns: NUL, BEL, ESC, DEL all
11797 // surface the same control-byte arm.
11798 for entry in [
11799 "alice\x00bob",
11800 "alice\x07bob",
11801 "alice\x1bbob",
11802 "alice\x7fbob",
11803 ] {
11804 let c = caixa_with_autores(vec![entry]);
11805 let err = c.validate_autores().unwrap_err();
11806 let ManifestError::AutorInvalid { autor, reason } = err else {
11807 panic!("expected AutorInvalid for {entry:?}, got {err:?}");
11808 };
11809 assert_eq!(autor, entry);
11810 assert!(
11811 reason.contains("control character"),
11812 "{entry:?} reason: {reason}",
11813 );
11814 }
11815 }
11816
11817 #[test]
11818 fn validate_autores_accepts_unicode_entry() {
11819 // Unicode positive control: realistic maintainer names carry
11820 // Unicode (`François`, `日本語`, `naïve`). The predicate must
11821 // round-trip Unicode losslessly, peer with the
11822 // `chart_maintainer_name_shape_accepts_unicode` substrate-side
11823 // sweep.
11824 let c = caixa_with_autores(vec![
11825 "François Dupont",
11826 "日本語の名前",
11827 "naïve <naive@example.com>",
11828 ]);
11829 c.validate_autores().unwrap();
11830 }
11831
11832 #[test]
11833 fn validate_autores_empty_takes_precedence_over_shape() {
11834 // Per-entry empty-first cascade pin: an entry that is both
11835 // empty *and* shape-invalid surfaces `AutorEmpty` (the narrower
11836 // "this entry has no value" structural defect dominates the
11837 // broader shape-predicate diagnostic). The empty arm fires
11838 // before the shape predicate is consulted, mirroring the peer
11839 // `validate_repositorio_empty_takes_precedence_over_shape`
11840 // cascade on the universal `Option<String>` siblings — and now
11841 // established on the Vec<String> per-entry surface.
11842 let c = caixa_with_autores(vec![""]);
11843 let err = c.validate_autores().unwrap_err();
11844 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
11845 }
11846
11847 #[test]
11848 fn validate_autores_shape_takes_precedence_over_duplicate() {
11849 // Per-entry shape-before-cross-entry-duplicate cascade pin: an
11850 // entry that is malformed surfaces `AutorInvalid` even when a
11851 // later entry would have collided on duplicate. The per-entry
11852 // shape arm fires inside the same loop iteration as the empty
11853 // arm, before the seen-set insert at end-of-iteration —
11854 // structural per-entry defects dominate the cross-entry
11855 // uniqueness diagnostic.
11856 let c = caixa_with_autores(vec!["alice\nbob", "alice\nbob"]);
11857 let err = c.validate_autores().unwrap_err();
11858 assert!(
11859 matches!(err, ManifestError::AutorInvalid { .. }),
11860 "got {err:?}",
11861 );
11862 }
11863
11864 #[test]
11865 fn validate_autores_invalid_diagnostic_names_offending_slot_and_value() {
11866 // Diagnostic-shape pin on the new shape arm (peer with
11867 // `validate_descricao_invalid_diagnostic_carries_offending_value`):
11868 // the rendered Display surfaces both the offending slot name
11869 // and the offending value verbatim, so a `feira lint` run
11870 // points the author at the exact `:autores` entry to fix.
11871 let c = caixa_with_autores(vec!["alice\nbob"]);
11872 let rendered = c.validate_autores().unwrap_err().to_string();
11873 assert!(
11874 rendered.contains(":autores"),
11875 "diagnostic must name the offending slot: {rendered}",
11876 );
11877 assert!(
11878 rendered.contains("alice\\nbob"),
11879 "diagnostic must quote the offending value (debug-escaped): {rendered}",
11880 );
11881 }
11882
11883 #[test]
11884 fn validate_autores_rejects_at_129_byte_boundary() {
11885 // The 128-byte cap pin — boundary-exceeding case rejected,
11886 // boundary-accepting case passes. Mirrors the peer
11887 // `chart_maintainer_name_shape_rejects_at_129_byte_boundary`
11888 // substrate-side pin, surfaced at the per-axis caller so the
11889 // cap propagates through validate end-to-end. Constructed as
11890 // a single all-`a` token so only the cap arm fires.
11891 let max_ok = "a".repeat(128);
11892 let c = caixa_with_autores(vec![max_ok.as_str()]);
11893 c.validate_autores().unwrap();
11894 let too_long = "a".repeat(129);
11895 let c = caixa_with_autores(vec![too_long.as_str()]);
11896 let err = c.validate_autores().unwrap_err();
11897 let ManifestError::AutorInvalid { reason, .. } = err else {
11898 panic!("expected AutorInvalid, got {err:?}");
11899 };
11900 assert!(reason.contains("128"), "got: {reason}");
11901 assert!(reason.contains("129"), "got: {reason}");
11902 }
11903
11904 // ── validate_repositorio — universal-axis git-repo-URL shape ──────
11905
11906 fn caixa_with_repositorio(repositorio: Option<&str>) -> Caixa {
11907 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11908 c.repositorio = repositorio.map(String::from);
11909 c
11910 }
11911
11912 #[test]
11913 fn validate_repositorio_accepts_none() {
11914 // The omit-the-slot identity: `:repositorio` is optional. The
11915 // gate is a no-op when the author didn't declare a value —
11916 // every caixa without a `:repositorio` line trivially passes,
11917 // and the substrate-side renderers fall back to their
11918 // documented placeholder (`caixa-helm`'s `home: None`,
11919 // `caixa-flux`'s `https://github.com/pleme-io/<nome>` derived
11920 // URL). Mirrors the peer `validate_restart_window_accepts_none`
11921 // posture on the other `Option<String>` Caixa slot.
11922 let c = caixa_with_repositorio(None);
11923 c.validate_repositorio().unwrap();
11924 }
11925
11926 #[test]
11927 fn validate_repositorio_accepts_canonical_forms() {
11928 // Positive control sweep across every documented `:repositorio`
11929 // authoring shape — the same union the shared
11930 // `crate::render::is_git_repo_url` predicate accepts and the
11931 // peer `:deps :fonte :repo` axis already routes through.
11932 // Covers the `github:` shorthand (the canonical pleme-io
11933 // convention used in the `:repositorio` field of every
11934 // manifest fixture across `caixa-helm` / `caixa-mesh` and the
11935 // `examples/`), the `https://…` URL the README quickstart uses,
11936 // the `ssh://`, `git://`, `git@host:path` scp-style SSH, and
11937 // `file://` URL schemes the shared predicate documents.
11938 for repo in [
11939 "github:pleme-io/hello-rio",
11940 "github:pleme-io/checkout",
11941 "https://github.com/pleme-io/hello-rio",
11942 "ssh://git@github.com/pleme-io/hello-rio.git",
11943 "git://github.com/pleme-io/hello-rio.git",
11944 "git@github.com:pleme-io/hello-rio.git",
11945 "file:///srv/pleme/hello-rio",
11946 ] {
11947 let c = caixa_with_repositorio(Some(repo));
11948 c.validate_repositorio()
11949 .unwrap_or_else(|err| panic!("canonical {repo:?} must pass: {err:?}"));
11950 }
11951 }
11952
11953 #[test]
11954 fn validate_repositorio_rejects_empty_some() {
11955 // Canonical paste-from-blank-doc footgun. The narrower
11956 // [`ManifestError::RepositorioEmpty`] arm fires before the
11957 // shape predicate is consulted, mirroring the empty-first
11958 // cascade every peer per-axis identity gate uses
11959 // (`NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
11960 // `FonteRepoEmpty` → `FonteRepoInvalid`). Without this gate
11961 // the empty `Some("")` silently passed the renderer's
11962 // `Option::unwrap_or_else(|| <fallback>)` (which only fires
11963 // on `None`) and landed as `home: ""` in `Chart.yaml` /
11964 // `url: ""` in the FluxCD `GitRepository`.
11965 let c = caixa_with_repositorio(Some(""));
11966 let err = c.validate_repositorio().unwrap_err();
11967 assert!(
11968 matches!(err, ManifestError::RepositorioEmpty),
11969 "got {err:?}",
11970 );
11971 }
11972
11973 #[test]
11974 fn validate_repositorio_rejects_whitespace() {
11975 // Paste-from-doc whitespace footgun. The shared
11976 // `is_git_repo_url` predicate refuses any whitespace byte; a
11977 // trailing space in a `:repositorio` value silently broke
11978 // `git clone '<value> '` at clone time. The diagnostic names
11979 // the offending value verbatim.
11980 let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio "));
11981 let err = c.validate_repositorio().unwrap_err();
11982 let ManifestError::RepositorioInvalid { repositorio, .. } = err else {
11983 panic!("expected RepositorioInvalid, got {err:?}");
11984 };
11985 assert_eq!(repositorio, "github:pleme-io/hello-rio ");
11986 }
11987
11988 #[test]
11989 fn validate_repositorio_rejects_control_char() {
11990 // Paste-from-multiline-doc CRLF footgun — control characters
11991 // at the URL boundary are a class of subprocess-arg injection
11992 // and break git's URL parser at every porcelain entry point.
11993 let c = caixa_with_repositorio(Some("https://example.com/repo\n"));
11994 let err = c.validate_repositorio().unwrap_err();
11995 assert!(
11996 matches!(err, ManifestError::RepositorioInvalid { .. }),
11997 "got {err:?}",
11998 );
11999 }
12000
12001 #[test]
12002 fn validate_repositorio_rejects_leading_dash() {
12003 // Canonical CLI-argument-injection footgun: `git clone <repo>`
12004 // interprets a leading `-` as a CLI flag, so a
12005 // `-upload-pack=…` value escapes the subprocess argument
12006 // boundary. The shared predicate refuses every leading-`-`
12007 // shape at validate time.
12008 let c = caixa_with_repositorio(Some("-upload-pack=evil"));
12009 let err = c.validate_repositorio().unwrap_err();
12010 assert!(
12011 matches!(err, ManifestError::RepositorioInvalid { .. }),
12012 "got {err:?}",
12013 );
12014 }
12015
12016 #[test]
12017 fn validate_repositorio_rejects_missing_colon_separator() {
12018 // The bare `org/repo` ambiguity footgun — `git clone` reads
12019 // a no-`:` form as a relative filesystem path rather than the
12020 // GitHub-shorthand expansion the author probably intended.
12021 // The shared predicate refuses every shape without a `:`
12022 // separator.
12023 let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
12024 let err = c.validate_repositorio().unwrap_err();
12025 assert!(
12026 matches!(err, ManifestError::RepositorioInvalid { .. }),
12027 "got {err:?}",
12028 );
12029 }
12030
12031 #[test]
12032 fn validate_repositorio_rejects_fragment_anchor() {
12033 // Paste-from-browser-address-bar footgun on the
12034 // `:repositorio` axis — an author copies a GitHub permalink
12035 // to a README section / line-permalink and forgets to trim
12036 // the `#fragment` tail. The shared `is_git_repo_url`
12037 // predicate refuses the byte at the URL-grammar layer
12038 // (libcurl strips the fragment before opening the
12039 // transport, so the byte rides verbatim into the rendered
12040 // `Chart.yaml` `home:` and FluxCD `GitRepository` `url:`
12041 // fields but is silently dropped on the wire — two
12042 // manifest variants whose values differ only in their
12043 // fragment anchor lock to two distinct rendered artifacts
12044 // for the byte-identical clone, defeating the THEORY.md
12045 // §V.2 render-determinism contract on the `:repositorio`
12046 // axis the peer `:fonte :repo` axis already closes).
12047 let c = caixa_with_repositorio(Some("https://github.com/pleme-io/hello-rio#readme"));
12048 let err = c.validate_repositorio().unwrap_err();
12049 let ManifestError::RepositorioInvalid {
12050 repositorio,
12051 reason,
12052 } = err
12053 else {
12054 panic!("expected RepositorioInvalid, got {err:?}");
12055 };
12056 assert_eq!(repositorio, "https://github.com/pleme-io/hello-rio#readme");
12057 assert!(
12058 reason.contains("must not contain `#`"),
12059 "reason must surface the fragment-`#` arm, got {reason:?}"
12060 );
12061 }
12062
12063 #[test]
12064 fn validate_repositorio_rejects_query_string() {
12065 // Paste-from-browser-address-bar footgun on the
12066 // `:repositorio` axis (peer with the a68f818 fragment-`#`
12067 // arm on the same axis). An author copies a GitHub tab
12068 // deep-link out of the address bar and forgets to trim
12069 // the `?tab=…` query tail. The shared `is_git_repo_url`
12070 // predicate refuses the byte at the URL-grammar layer
12071 // (GitHub / GitLab / Bitbucket silently ignore the
12072 // `?query` tail and serve the same repo regardless, so
12073 // the byte rides verbatim into the rendered `Chart.yaml`
12074 // `home:` and FluxCD `GitRepository` `url:` fields but
12075 // is silently masked at the wire — two manifest variants
12076 // whose values differ only in their query tail lock to
12077 // two distinct rendered artifacts for the byte-identical
12078 // clone, defeating the THEORY.md §V.2 render-determinism
12079 // contract on the `:repositorio` axis the peer `:fonte
12080 // :repo` axis already closes).
12081 let c = caixa_with_repositorio(Some(
12082 "https://github.com/pleme-io/hello-rio?tab=readme-ov-file",
12083 ));
12084 let err = c.validate_repositorio().unwrap_err();
12085 let ManifestError::RepositorioInvalid {
12086 repositorio,
12087 reason,
12088 } = err
12089 else {
12090 panic!("expected RepositorioInvalid, got {err:?}");
12091 };
12092 assert_eq!(
12093 repositorio,
12094 "https://github.com/pleme-io/hello-rio?tab=readme-ov-file"
12095 );
12096 assert!(
12097 reason.contains("must not contain `?`"),
12098 "reason must surface the query-`?` arm, got {reason:?}"
12099 );
12100 }
12101
12102 #[test]
12103 fn validate_repositorio_rejects_embedded_backslash() {
12104 // Windows-file-path-confusion footgun on the `:repositorio`
12105 // axis (peer with the prior fragment-`#` / query-`?` arms on
12106 // the same axis, and peer with the new dep-level `:fonte :repo`
12107 // backslash arm on the URL-grammar trajectory). An author
12108 // pastes a Windows Explorer address-bar `file:///C:\Users\me\
12109 // hello-rio` into the `:repositorio` slot, expecting the
12110 // `lareira-<nome>` chart's `home:` field and the FluxCD
12111 // `GitRepository` `url:` field to render the canonical local
12112 // file-URI. The shared `is_git_repo_url` predicate refuses
12113 // the byte at the URL-grammar layer (libcurl silently
12114 // translates `\` → `/` on some platforms and refuses it on
12115 // others, so the byte rides verbatim into the rendered
12116 // artifacts but is silently rewritten or rejected at the wire
12117 // — two manifest variants whose values differ only in
12118 // backslash-vs-forward-slash lock to two distinct rendered
12119 // artifacts for the byte-identical clone, defeating the
12120 // THEORY.md §V.2 render-determinism contract on the
12121 // `:repositorio` axis the peer `:fonte :repo` axis already
12122 // closes).
12123 let c = caixa_with_repositorio(Some("file:///C:\\Users\\me\\hello-rio"));
12124 let err = c.validate_repositorio().unwrap_err();
12125 let ManifestError::RepositorioInvalid {
12126 repositorio,
12127 reason,
12128 } = err
12129 else {
12130 panic!("expected RepositorioInvalid, got {err:?}");
12131 };
12132 assert_eq!(repositorio, "file:///C:\\Users\\me\\hello-rio");
12133 assert!(
12134 reason.contains("must not contain `\\`"),
12135 "reason must surface the backslash-`\\` arm, got {reason:?}"
12136 );
12137 }
12138
12139 #[test]
12140 fn validate_repositorio_rejects_uri_template_placeholder() {
12141 // URI Template (RFC 6570) placeholder footgun on the
12142 // `:repositorio` axis (peer with the prior fragment-`#` /
12143 // query-`?` / backslash-`\` arms on the same axis, and peer
12144 // with the new dep-level `:fonte :repo` `{` / `}` arm on the
12145 // URL-grammar trajectory). An author pastes a quick-start
12146 // README snippet / OpenAPI `servers:` URL / Helm chart
12147 // `home:` template carrying unresolved `{org}` / `{repo}`
12148 // placeholders into the `:repositorio` slot, expecting the
12149 // substrate to resolve the placeholder downstream. The
12150 // shared `is_git_repo_url` predicate refuses the byte at the
12151 // URL-grammar layer (libcurl percent-encodes `{` / `}` to
12152 // `%7B` / `%7D` on the wire, so the byte round-trips
12153 // inconsistently between the rendered `Chart.yaml home:` /
12154 // FluxCD `GitRepository url:` and the resolver's `git clone`
12155 // invocation, defeating the THEORY.md §V.2 render-
12156 // determinism contract on the `:repositorio` axis the peer
12157 // `:fonte :repo` axis already closes; every git porcelain
12158 // entry-point additionally fetches a nonexistent literal-
12159 // `{placeholder}`-named path far from the source caixa.lisp).
12160 let c = caixa_with_repositorio(Some("https://github.com/{org}/hello-rio"));
12161 let err = c.validate_repositorio().unwrap_err();
12162 let ManifestError::RepositorioInvalid {
12163 repositorio,
12164 reason,
12165 } = err
12166 else {
12167 panic!("expected RepositorioInvalid, got {err:?}");
12168 };
12169 assert_eq!(repositorio, "https://github.com/{org}/hello-rio");
12170 assert!(
12171 reason.contains("must not contain `{`"),
12172 "reason must surface the open-brace `{{` arm, got {reason:?}"
12173 );
12174 assert!(
12175 reason.contains("URI Template") || reason.contains("RFC 6570"),
12176 "reason must name the RFC 6570 URI Template grammar, got {reason:?}"
12177 );
12178 }
12179
12180 #[test]
12181 fn validate_repositorio_empty_takes_precedence_over_shape() {
12182 // Empty-first cascade pin: the empty `Some("")` surfaces the
12183 // narrower `RepositorioEmpty` not the shape-predicate-wrapped
12184 // `RepositorioInvalid`, mirroring the peer
12185 // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
12186 // `FonteRepoEmpty` → `FonteRepoInvalid` cascades. The shared
12187 // `is_git_repo_url` predicate also rejects the empty input
12188 // (defensively, with its own `"must not be empty"` reason),
12189 // but the manifest-layer empty arm runs first to surface the
12190 // narrower diagnostic verbatim.
12191 let c = caixa_with_repositorio(Some(""));
12192 let err = c.validate_repositorio().unwrap_err();
12193 assert!(
12194 matches!(err, ManifestError::RepositorioEmpty),
12195 "got {err:?}",
12196 );
12197 }
12198
12199 #[test]
12200 fn validate_repositorio_diagnostic_carries_offending_value() {
12201 // Diagnostic-shape pin (peer with
12202 // `validate_autores_diagnostic_carries_offending_author`): the
12203 // error's Display surfaces the offending value + slot name
12204 // verbatim, so a `feira lint` run can render the diagnostic
12205 // without re-parsing and the author can grep their caixa.lisp
12206 // for the offending `:repositorio` value.
12207 let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
12208 let rendered = c.validate_repositorio().unwrap_err().to_string();
12209 assert!(
12210 rendered.contains(":repositorio"),
12211 "diagnostic must name the offending slot: {rendered}",
12212 );
12213 assert!(
12214 rendered.contains("pleme-io/hello-rio"),
12215 "diagnostic must quote the offending value: {rendered}",
12216 );
12217 }
12218
12219 // ── validate_descricao — universal-axis Chart.yaml description shape ──
12220
12221 fn caixa_with_descricao(descricao: Option<&str>) -> Caixa {
12222 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12223 c.descricao = descricao.map(String::from);
12224 c
12225 }
12226
12227 #[test]
12228 fn validate_descricao_accepts_none() {
12229 // The omit-the-slot identity: `:descricao` is optional. The
12230 // gate is a no-op when the author didn't declare a value —
12231 // every caixa without a `:descricao` line trivially passes,
12232 // and the substrate-side renderers fall back to their
12233 // documented `caixa.nome`-derived placeholder. Mirrors the
12234 // peer `validate_repositorio_accepts_none` posture on the
12235 // sibling `Option<String>` Caixa slot.
12236 let c = caixa_with_descricao(None);
12237 c.validate_descricao().unwrap();
12238 }
12239
12240 #[test]
12241 fn validate_descricao_accepts_canonical_summary() {
12242 // Positive control: the canonical pleme-io descricao shape —
12243 // a short free-form prose summary — passes the gate. Covers
12244 // the fixture shapes the `caixa-helm` / `caixa-flux` /
12245 // `caixa-mesh` test fixtures use (`"Canonical Rust→wasm32-
12246 // wasip2 caixa Servico."`, `"Checkout flow."`).
12247 for desc in [
12248 "Canonical Rust→wasm32-wasip2 caixa Servico.",
12249 "Checkout flow.",
12250 "AWS provider caixa for tatara-lisp",
12251 "FIXME — describe this caixa",
12252 "x",
12253 ] {
12254 let c = caixa_with_descricao(Some(desc));
12255 c.validate_descricao()
12256 .unwrap_or_else(|err| panic!("canonical {desc:?} must pass: {err:?}"));
12257 }
12258 }
12259
12260 #[test]
12261 fn validate_descricao_rejects_empty_some() {
12262 // Canonical paste-from-blank-doc footgun. Without this gate
12263 // the empty `Some("")` silently passed the renderer's
12264 // `Option::unwrap_or_else(|| <fallback>)` (which only fires
12265 // on `None`) and landed as `description: ""` in `Chart.yaml`
12266 // and a blank `README.md` header. Mirrors the peer
12267 // [`ManifestError::RepositorioEmpty`] empty-arm on the
12268 // sibling `Option<String>` Caixa slot.
12269 let c = caixa_with_descricao(Some(""));
12270 let err = c.validate_descricao().unwrap_err();
12271 assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
12272 }
12273
12274 #[test]
12275 fn validate_descricao_rejects_leading_whitespace() {
12276 // Paste-from-aligned-doc footgun: a leading ASCII space the
12277 // bare empty-arm gate accepted, the shape predicate now
12278 // refuses. The diagnostic carries the offending value
12279 // verbatim (with the leading space preserved) so the author
12280 // can grep their caixa.lisp for the exact `:descricao` line
12281 // and fix the round-trip-inconsistent leading whitespace.
12282 // Mirrors the peer
12283 // `validate_licenca_rejects_leading_whitespace` arm on the
12284 // sibling `:licenca` axis.
12285 let c = caixa_with_descricao(Some(" Checkout flow."));
12286 let err = c.validate_descricao().unwrap_err();
12287 let ManifestError::DescricaoInvalid { descricao, reason } = err else {
12288 panic!("expected DescricaoInvalid, got {err:?}");
12289 };
12290 assert_eq!(descricao, " Checkout flow.");
12291 assert!(reason.contains("whitespace"), "got: {reason:?}");
12292 }
12293
12294 #[test]
12295 fn validate_descricao_rejects_trailing_whitespace() {
12296 // Paste-from-doc footgun: a trailing ASCII space the bare
12297 // empty-arm gate accepted, the shape predicate now refuses.
12298 let c = caixa_with_descricao(Some("Checkout flow. "));
12299 let err = c.validate_descricao().unwrap_err();
12300 let ManifestError::DescricaoInvalid { descricao, reason } = err else {
12301 panic!("expected DescricaoInvalid, got {err:?}");
12302 };
12303 assert_eq!(descricao, "Checkout flow. ");
12304 assert!(reason.contains("whitespace"), "got: {reason:?}");
12305 }
12306
12307 #[test]
12308 fn validate_descricao_rejects_embedded_newline() {
12309 // Paste-from-multiline-doc footgun: an embedded LF the bare
12310 // empty-arm gate accepted, the shape predicate now refuses.
12311 // Without this gate the embedded newline silently landed in
12312 // the rendered Chart.yaml as a multi-line YAML block scalar,
12313 // and every chart-aware UI (`helm list`, `helm search`,
12314 // Artifact Hub) renders the description in a single-line
12315 // column so the embedded newline is silently dropped at
12316 // every downstream consumer.
12317 let c = caixa_with_descricao(Some("Checkout\nflow."));
12318 let err = c.validate_descricao().unwrap_err();
12319 assert!(
12320 matches!(err, ManifestError::DescricaoInvalid { .. }),
12321 "got {err:?}",
12322 );
12323 assert!(err.to_string().contains("newline"), "got {err}");
12324 }
12325
12326 #[test]
12327 fn validate_descricao_rejects_embedded_carriage_return() {
12328 // Paste-from-Windows-CRLF-doc footgun.
12329 let c = caixa_with_descricao(Some("Checkout\rflow."));
12330 let err = c.validate_descricao().unwrap_err();
12331 assert!(
12332 matches!(err, ManifestError::DescricaoInvalid { .. }),
12333 "got {err:?}",
12334 );
12335 assert!(err.to_string().contains("carriage return"), "got {err}");
12336 }
12337
12338 #[test]
12339 fn validate_descricao_rejects_embedded_tab() {
12340 // Tab-from-aligned-doc footgun.
12341 let c = caixa_with_descricao(Some("Checkout\tflow."));
12342 let err = c.validate_descricao().unwrap_err();
12343 assert!(
12344 matches!(err, ManifestError::DescricaoInvalid { .. }),
12345 "got {err:?}",
12346 );
12347 assert!(err.to_string().contains("tab"), "got {err}");
12348 }
12349
12350 #[test]
12351 fn validate_descricao_rejects_embedded_control_bytes() {
12352 // Paste-from-binary-blob footgun: every other control byte
12353 // (NUL, BEL, ESC, DEL) is refused at validate time. Mirrors
12354 // the peer SPDX-expression control-byte arm.
12355 for s in [
12356 "Checkout\x00flow.",
12357 "Checkout\x07flow.",
12358 "Checkout\x1bflow.",
12359 "Checkout\x7fflow.",
12360 ] {
12361 let c = caixa_with_descricao(Some(s));
12362 let err = c.validate_descricao().unwrap_err();
12363 assert!(
12364 matches!(err, ManifestError::DescricaoInvalid { .. }),
12365 "{s:?} got {err:?}",
12366 );
12367 assert!(
12368 err.to_string().contains("control character"),
12369 "{s:?} got {err}",
12370 );
12371 }
12372 }
12373
12374 #[test]
12375 fn validate_descricao_accepts_unicode_prose() {
12376 // Positive control: Unicode prose is accepted — the
12377 // canonical fixtures carry `→` (U+2192) and `—` (U+2014),
12378 // and `Caixa::template`'s `"FIXME — describe this caixa"`
12379 // scaffold every `feira init` emits must continue to pass.
12380 for s in [
12381 "Canonical Rust→wasm32-wasip2 caixa Servico.",
12382 "FIXME — describe this caixa",
12383 "Caixa pour le projet tâche",
12384 "日本語の説明",
12385 ] {
12386 let c = caixa_with_descricao(Some(s));
12387 c.validate_descricao()
12388 .unwrap_or_else(|err| panic!("Unicode {s:?} must pass: {err:?}"));
12389 }
12390 }
12391
12392 #[test]
12393 fn validate_descricao_empty_takes_precedence_over_shape() {
12394 // Cascade pin: a `Some("")` surfaces the narrower
12395 // `DescricaoEmpty` arm, not the broader `DescricaoInvalid`
12396 // shape-predicate arm. Mirrors the peer
12397 // `validate_licenca_empty_takes_precedence_over_shape` pin
12398 // on the sibling `:licenca` axis.
12399 let c = caixa_with_descricao(Some(""));
12400 let err = c.validate_descricao().unwrap_err();
12401 assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
12402 }
12403
12404 #[test]
12405 fn validate_descricao_invalid_diagnostic_carries_offending_value_and_slot() {
12406 // Diagnostic-shape pin: the error's Display surfaces both
12407 // the `:descricao` slot name and the offending value
12408 // verbatim, so a `feira lint` run can render the diagnostic
12409 // without re-parsing and the author can grep their caixa.lisp
12410 // for the offending `:descricao` line. Mirrors the peer
12411 // `validate_licenca_invalid_diagnostic_carries_offending_value_and_slot`
12412 // pin (ee2e888) on the sibling `:licenca` axis.
12413 // The `{descricao:?}` Debug format escapes embedded control
12414 // bytes; the quoted offending value surfaces as
12415 // `"Checkout\nflow."` (literal backslash-n) in the rendered
12416 // diagnostic. The author can grep their caixa.lisp for the
12417 // literal `Checkout` summary prefix.
12418 let c = caixa_with_descricao(Some("Checkout\nflow."));
12419 let rendered = c.validate_descricao().unwrap_err().to_string();
12420 assert!(
12421 rendered.contains(":descricao"),
12422 "diagnostic must name the offending slot: {rendered}",
12423 );
12424 assert!(
12425 rendered.contains("Checkout\\nflow."),
12426 "diagnostic must quote the offending value (debug-escaped): {rendered}",
12427 );
12428 }
12429
12430 #[test]
12431 fn validate_descricao_template_passes() {
12432 // Round-trip pin: the bare `Caixa::template` shape carries
12433 // `:descricao "FIXME — describe this caixa"` (a non-empty
12434 // sentinel), so the template-derived Caixa passes the gate by
12435 // construction. A future template-shape change that omits or
12436 // empties `:descricao` would surface here as a regression.
12437 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12438 c.validate_descricao().unwrap();
12439 }
12440
12441 #[test]
12442 fn validate_descricao_diagnostic_names_offending_slot() {
12443 // Diagnostic-shape pin (peer with
12444 // `validate_repositorio_diagnostic_carries_offending_value`):
12445 // the error's Display surfaces the `:descricao` slot name
12446 // verbatim, so a `feira lint` run can render the diagnostic
12447 // without re-parsing and the author can grep their caixa.lisp
12448 // for the offending `:descricao` line.
12449 let c = caixa_with_descricao(Some(""));
12450 let rendered = c.validate_descricao().unwrap_err().to_string();
12451 assert!(
12452 rendered.contains(":descricao"),
12453 "diagnostic must name the offending slot: {rendered}",
12454 );
12455 }
12456
12457 // ── validate_licenca — universal-axis chart README license shape ──
12458
12459 fn caixa_with_licenca(licenca: Option<&str>) -> Caixa {
12460 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12461 c.licenca = licenca.map(String::from);
12462 c
12463 }
12464
12465 #[test]
12466 fn validate_licenca_accepts_none() {
12467 // The omit-the-slot identity: `:licenca` is optional. The
12468 // gate is a no-op when the author didn't declare a value —
12469 // every caixa without a `:licenca` line trivially passes,
12470 // and the substrate-side `caixa-helm` renderer falls back to
12471 // the documented `"MIT"` placeholder. Mirrors the peer
12472 // `validate_descricao_accepts_none` posture on the sibling
12473 // `Option<String>` Caixa slot.
12474 let c = caixa_with_licenca(None);
12475 c.validate_licenca().unwrap();
12476 }
12477
12478 #[test]
12479 fn validate_licenca_accepts_canonical_expressions() {
12480 // Positive control: every canonical SPDX expression shape
12481 // pleme-io carries in its existing fixtures + the canonical
12482 // SPDX dual-license / with-exception / `+`-suffix / grouped /
12483 // user-defined-reference shapes all pass the gate. Covers
12484 // the single-license, `OR`-compound, `AND`-compound,
12485 // `WITH`-exception, parenthesis-grouped, `+`-suffix, and
12486 // `LicenseRef-` / `DocumentRef-:LicenseRef-` shapes — every
12487 // production the SPDX 2.1 expression grammar admits that
12488 // sits within the alphabet floor the
12489 // `is_spdx_expression_shape` predicate enforces.
12490 for lic in [
12491 "MIT",
12492 "Apache-2.0",
12493 "Apache-2.0 OR MIT",
12494 "Apache-2.0 AND MIT",
12495 "BSD-3-Clause",
12496 "MPL-2.0",
12497 "GPL-3.0-or-later",
12498 "GPL-2.0+",
12499 "Apache-2.0 WITH LLVM-exception",
12500 "(MIT OR Apache-2.0) AND BSD-3-Clause",
12501 "(MIT OR Apache-2.0) AND BSD-3-Clause AND ISC",
12502 "LicenseRef-MyLicense",
12503 "DocumentRef-spdx-tool:LicenseRef-MIT-Style",
12504 "x",
12505 ] {
12506 let c = caixa_with_licenca(Some(lic));
12507 c.validate_licenca()
12508 .unwrap_or_else(|err| panic!("canonical {lic:?} must pass: {err:?}"));
12509 }
12510 }
12511
12512 #[test]
12513 fn validate_licenca_rejects_trailing_whitespace() {
12514 // Paste-from-doc whitespace footgun. A trailing space in the
12515 // `:licenca` value would silently break a downstream SPDX
12516 // parser that splits on exact `AND` / `OR` / `WITH` keyword
12517 // boundaries. The shape predicate refuses every trailing
12518 // whitespace byte by construction. Peer with
12519 // `validate_repositorio_rejects_whitespace` and
12520 // `validate_edicao_rejects_trailing_whitespace`.
12521 let c = caixa_with_licenca(Some("MIT "));
12522 let err = c.validate_licenca().unwrap_err();
12523 let ManifestError::LicencaInvalid { licenca, .. } = err else {
12524 panic!("expected LicencaInvalid, got {err:?}");
12525 };
12526 assert_eq!(licenca, "MIT ");
12527 }
12528
12529 #[test]
12530 fn validate_licenca_rejects_leading_whitespace() {
12531 // Symmetric paste-from-doc whitespace footgun on the leading
12532 // boundary — the gate refuses every shape that starts with a
12533 // space byte by construction. Peer with
12534 // `validate_edicao_rejects_leading_whitespace`.
12535 let c = caixa_with_licenca(Some(" MIT"));
12536 let err = c.validate_licenca().unwrap_err();
12537 assert!(
12538 matches!(err, ManifestError::LicencaInvalid { .. }),
12539 "got {err:?}",
12540 );
12541 }
12542
12543 #[test]
12544 fn validate_licenca_rejects_control_char() {
12545 // Paste-from-multiline-doc CRLF footgun — control characters
12546 // at the value boundary land as a malformed line in the
12547 // rendered chart `README.md` `## License` section. Peer with
12548 // `validate_repositorio_rejects_control_char` and
12549 // `validate_edicao_rejects_control_char`.
12550 for lic in ["MIT\n", "MIT\r\n", "MIT\rApache-2.0"] {
12551 let c = caixa_with_licenca(Some(lic));
12552 let err = c.validate_licenca().unwrap_err();
12553 assert!(
12554 matches!(err, ManifestError::LicencaInvalid { .. }),
12555 "expected LicencaInvalid on {lic:?}, got {err:?}",
12556 );
12557 }
12558 }
12559
12560 #[test]
12561 fn validate_licenca_rejects_tab() {
12562 // Tab-from-aligned-doc footgun — SPDX expressions use a
12563 // single ASCII space between tokens; a tab breaks every
12564 // downstream SPDX parser that splits on exact `" "`
12565 // boundaries.
12566 let c = caixa_with_licenca(Some("MIT\tOR Apache-2.0"));
12567 let err = c.validate_licenca().unwrap_err();
12568 assert!(
12569 matches!(err, ManifestError::LicencaInvalid { .. }),
12570 "got {err:?}",
12571 );
12572 }
12573
12574 #[test]
12575 fn validate_licenca_rejects_non_ascii() {
12576 // Smart-quote / non-ASCII paste footgun — SPDX identifiers
12577 // are ASCII per the `idstring = 1*(ALPHA / DIGIT / "-" /
12578 // ".")` production. The shape predicate refuses every
12579 // non-ASCII byte by construction; peer with
12580 // `validate_edicao_rejects_non_ascii_lookalike`.
12581 for lic in ["MIT\u{a0}OR Apache-2.0", "MIT\u{2013}1.0", "Café-1.0"] {
12582 let c = caixa_with_licenca(Some(lic));
12583 let err = c.validate_licenca().unwrap_err();
12584 assert!(
12585 matches!(err, ManifestError::LicencaInvalid { .. }),
12586 "expected LicencaInvalid on {lic:?}, got {err:?}",
12587 );
12588 }
12589 }
12590
12591 #[test]
12592 fn validate_licenca_rejects_underscore() {
12593 // Underscore-instead-of-hyphen typo footgun — `Apache_2.0` /
12594 // `MIT_Style` / `BSD_3_Clause` are familiar shapes from
12595 // snake-case identifier conventions that don't apply to the
12596 // SPDX `idstring` grammar (which admits only `ALPHA / DIGIT /
12597 // "-" / "."`). The shape predicate refuses every underscore
12598 // byte by construction.
12599 for lic in ["Apache_2.0", "MIT_Style", "BSD_3_Clause"] {
12600 let c = caixa_with_licenca(Some(lic));
12601 let err = c.validate_licenca().unwrap_err();
12602 assert!(
12603 matches!(err, ManifestError::LicencaInvalid { .. }),
12604 "expected LicencaInvalid on {lic:?}, got {err:?}",
12605 );
12606 }
12607 }
12608
12609 #[test]
12610 fn validate_licenca_rejects_comma_separator() {
12611 // Comma-instead-of-`OR`-keyword colloquial idiom footgun —
12612 // SPDX expressions compose multiple licenses via `AND` / `OR`
12613 // keywords, not the comma separator. The shape predicate
12614 // refuses every comma byte by construction.
12615 for lic in ["MIT, Apache-2.0", "MIT,Apache-2.0"] {
12616 let c = caixa_with_licenca(Some(lic));
12617 let err = c.validate_licenca().unwrap_err();
12618 assert!(
12619 matches!(err, ManifestError::LicencaInvalid { .. }),
12620 "expected LicencaInvalid on {lic:?}, got {err:?}",
12621 );
12622 }
12623 }
12624
12625 #[test]
12626 fn validate_licenca_rejects_slash_dual_license() {
12627 // Slash-dual-license colloquial idiom footgun — the
12628 // `MIT/Apache-2.0` shape is common in Cargo's pre-SPDX
12629 // `package.license` field but non-SPDX; the SPDX equivalent
12630 // is `MIT OR Apache-2.0`. The shape predicate refuses every
12631 // forward-slash byte by construction.
12632 for lic in ["MIT/Apache-2.0", "MIT/BSD-3-Clause"] {
12633 let c = caixa_with_licenca(Some(lic));
12634 let err = c.validate_licenca().unwrap_err();
12635 assert!(
12636 matches!(err, ManifestError::LicencaInvalid { .. }),
12637 "expected LicencaInvalid on {lic:?}, got {err:?}",
12638 );
12639 }
12640 }
12641
12642 #[test]
12643 fn validate_licenca_rejects_semicolon_separator() {
12644 // Semicolon-list-separator confusion footgun — adjacent to
12645 // the comma-separator idiom, every list-separator-belongs-
12646 // to-list-grammar confusion lands here.
12647 let c = caixa_with_licenca(Some("MIT; Apache-2.0"));
12648 let err = c.validate_licenca().unwrap_err();
12649 assert!(
12650 matches!(err, ManifestError::LicencaInvalid { .. }),
12651 "got {err:?}",
12652 );
12653 }
12654
12655 #[test]
12656 fn validate_licenca_empty_takes_precedence_over_shape() {
12657 // Empty-first cascade pin: the empty `Some("")` surfaces the
12658 // narrower `LicencaEmpty` not the shape-predicate-wrapped
12659 // `LicencaInvalid`, mirroring the peer
12660 // `validate_edicao_empty_takes_precedence_over_shape` and
12661 // `validate_repositorio_empty_takes_precedence_over_shape`
12662 // (`RepositorioEmpty` → `RepositorioInvalid`), `NomeEmpty` →
12663 // `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid` cascades.
12664 // The shape predicate also refuses the empty input
12665 // (defensively — `"must not be empty"`), but the manifest-
12666 // layer empty arm runs first to surface the narrower
12667 // diagnostic verbatim.
12668 let c = caixa_with_licenca(Some(""));
12669 let err = c.validate_licenca().unwrap_err();
12670 assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
12671 }
12672
12673 #[test]
12674 fn validate_licenca_invalid_diagnostic_carries_offending_value() {
12675 // Diagnostic-shape pin on the shape-predicate arm (peer with
12676 // `validate_edicao_invalid_diagnostic_carries_offending_value`
12677 // and `validate_repositorio_diagnostic_carries_offending_value`):
12678 // the error's Display surfaces the offending value + slot
12679 // name verbatim, so a `feira lint` run can render the
12680 // diagnostic without re-parsing and the author can grep
12681 // their caixa.lisp for the offending `:licenca` value.
12682 let c = caixa_with_licenca(Some("Apache_2.0"));
12683 let rendered = c.validate_licenca().unwrap_err().to_string();
12684 assert!(
12685 rendered.contains(":licenca"),
12686 "diagnostic must name the offending slot: {rendered}",
12687 );
12688 assert!(
12689 rendered.contains("Apache_2.0"),
12690 "diagnostic must quote the offending value: {rendered}",
12691 );
12692 }
12693
12694 #[test]
12695 fn validate_licenca_rejects_empty_some() {
12696 // Canonical paste-from-blank-doc footgun. Without this gate
12697 // the empty `Some("")` silently passed the renderer's
12698 // `Option::unwrap_or_else(|| "MIT".into())` (which only
12699 // fires on `None`) and landed as a bare trailing period in
12700 // the rendered chart `README.md` `## License` section.
12701 // Mirrors the peer [`ManifestError::DescricaoEmpty`] empty-
12702 // arm on the sibling `Option<String>` Caixa slot.
12703 let c = caixa_with_licenca(Some(""));
12704 let err = c.validate_licenca().unwrap_err();
12705 assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
12706 }
12707
12708 #[test]
12709 fn validate_licenca_template_passes() {
12710 // Round-trip pin: the bare `Caixa::template` shape (whether
12711 // it carries `:licenca` or omits it) passes the gate by
12712 // construction. A future template-shape change that
12713 // introduced `(:licenca "")` would surface here as a
12714 // regression. Mirrors the peer
12715 // `validate_descricao_template_passes` pin.
12716 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12717 c.validate_licenca().unwrap();
12718 }
12719
12720 #[test]
12721 fn validate_licenca_diagnostic_names_offending_slot() {
12722 // Diagnostic-shape pin (peer with
12723 // `validate_descricao_diagnostic_names_offending_slot`):
12724 // the error's Display surfaces the `:licenca` slot name
12725 // verbatim, so a `feira lint` run can render the diagnostic
12726 // without re-parsing and the author can grep their caixa.lisp
12727 // for the offending `:licenca` line.
12728 let c = caixa_with_licenca(Some(""));
12729 let rendered = c.validate_licenca().unwrap_err().to_string();
12730 assert!(
12731 rendered.contains(":licenca"),
12732 "diagnostic must name the offending slot: {rendered}",
12733 );
12734 }
12735
12736 // ── Caixa::licenca — outer top-level Option<&str> scalar accessor ──
12737
12738 #[test]
12739 fn licenca_returns_licenca_byte_string_verbatim_across_permutations() {
12740 // The canonical per-`Caixa` `:licenca` SPDX-expression scalar
12741 // pin: [`Caixa::licenca`] must return the `:licenca` typed
12742 // byte-string verbatim as an `Option<&str>`, byte-equal to the
12743 // raw `self.licenca.as_deref()` access across every
12744 // representative value in the accept-set — `None` (the "omit
12745 // the slot to defer to the caixa-helm renderer's `MIT`
12746 // fallback" arm every existing fixture without a `:licenca`
12747 // line carries), `Some("")` (a past-the-guard sentinel that
12748 // pins the accessor doesn't perform a silent
12749 // `Some("") → None` collapse on the empty arm — validate
12750 // rejects `Some("")` through `LicencaEmpty` but the accessor
12751 // must ship the raw slot verbatim so a validate-time gate
12752 // regression surfaces at the caixa-helm emit boundary rather
12753 // than being silently absorbed into the fallback), `Some("MIT")`
12754 // (the canonical single-license shape every `feira init`
12755 // template scaffolds), `Some("Apache-2.0 OR MIT")` (the
12756 // canonical `OR`-compound shape the peer
12757 // `validate_licenca_accepts_canonical_expressions` positive
12758 // sweep exercises), `Some("(MIT OR Apache-2.0) AND
12759 // BSD-3-Clause")` (the canonical parenthesis-grouped shape),
12760 // `Some("MIT ")` / `Some(" MIT")` / `Some("MIT\n")` /
12761 // `Some("Apache_2.0")` / `Some("MIT,Apache-2.0")` (past-the-
12762 // guard sentinels — validate rejects each through
12763 // `LicencaInvalid` but the accessor must ship the raw slot
12764 // verbatim).
12765 //
12766 // First outer top-level [`Caixa`] `Option<&str>`-return scalar
12767 // accessor pin on the substrate primitive — opens the "outer
12768 // [`Caixa`] `Option<&str>` scalar" projection pattern the
12769 // sibling per-`Caixa` `:descricao` / `:repositorio` / `:edicao`
12770 // future lifts fold on. Sibling in shape to the peer per-`:placement`
12771 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
12772 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
12773 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
12774 // axes, extended onto the outer top-level [`Caixa`] universal-
12775 // axis surface. Pins against a future silent detour that
12776 // returned an owned `Option<String>` (which would type-check
12777 // but silently allocate on every accessor call, breaking the
12778 // zero-cost projection every peer sibling accessor carries), a
12779 // `Some("") → None` collapse (which would silently absorb the
12780 // `LicencaEmpty` refusal case at the accessor boundary and the
12781 // caixa-helm emit path would silently fall back to `"MIT"` on
12782 // a struct-literal `Caixa { licenca: Some(""), .. }`), or a
12783 // `None → Some("MIT")` collapse (which would silently reify
12784 // the caixa-helm renderer's `"MIT"` fallback at the accessor
12785 // boundary and every downstream consumer keying off the
12786 // `Option::is_none()` discriminator would lose the "author
12787 // omitted the slot" signal).
12788 for licenca in [
12789 None,
12790 Some(""),
12791 Some("MIT"),
12792 Some("Apache-2.0 OR MIT"),
12793 Some("(MIT OR Apache-2.0) AND BSD-3-Clause"),
12794 Some("MIT "),
12795 Some(" MIT"),
12796 Some("MIT\n"),
12797 Some("Apache_2.0"),
12798 Some("MIT,Apache-2.0"),
12799 ] {
12800 let c = caixa_with_licenca(licenca);
12801 assert_eq!(
12802 c.licenca(),
12803 licenca,
12804 "Caixa::licenca must return :licenca verbatim (got {:?}, \
12805 expected {licenca:?})",
12806 c.licenca(),
12807 );
12808 assert_eq!(
12809 c.licenca(),
12810 c.licenca.as_deref(),
12811 "Caixa::licenca must byte-equal the raw \
12812 `self.licenca.as_deref()` field access across every \
12813 value in the Option<&str> accept-set",
12814 );
12815 }
12816 }
12817
12818 #[test]
12819 fn validate_licenca_empty_arm_routes_through_accessor() {
12820 // Composition pin: [`Caixa::validate_licenca`]'s empty-arm gate
12821 // must key off [`Caixa::licenca`], not the raw
12822 // `self.licenca.as_deref()` field access. Structurally: a
12823 // `Caixa { licenca: Some(""), .. }` must surface the
12824 // `LicencaEmpty` refusal exactly, and a
12825 // `Caixa { licenca: Some("MIT"), .. }` (the canonical
12826 // single-license form) must pass validate. The pair jointly
12827 // pins the accessor + validate-gate composition: any future
12828 // silent detour that had the accessor return `None` on the
12829 // empty arm (a `.filter(|s| !s.is_empty())` collapse) would
12830 // silently absorb the `LicencaEmpty` refusal at the accessor
12831 // boundary and the validate gate would accept a struct-literal
12832 // `Caixa { licenca: Some(""), .. }` — the composition pin
12833 // catches that at caixa-core build time.
12834 //
12835 // Peer of the per-`:politicas :circuit-breaker`
12836 // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
12837 // accessor-composition pin
12838 // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
12839 // on the sibling per-M3-mesh-slot required-`u32` axis — same
12840 // "the validate / shape-gate predicate must route through the
12841 // substrate-primitive typed dispatch" discipline extended onto
12842 // the outer top-level [`Caixa`] universal-axis
12843 // `Option<&str>`-composition surface.
12844 let c = caixa_with_licenca(Some(""));
12845 assert!(
12846 matches!(c.validate_licenca(), Err(ManifestError::LicencaEmpty)),
12847 "validate_licenca must reject licenca == Some(\"\") with \
12848 LicencaEmpty — the accessor and the validate gate must \
12849 route through the same substrate-primitive typed dispatch \
12850 on the :licenca empty arm",
12851 );
12852 let c = caixa_with_licenca(Some("MIT"));
12853 assert!(
12854 c.validate_licenca().is_ok(),
12855 "validate_licenca must accept licenca == Some(\"MIT\") \
12856 (the canonical single-license SPDX shape)",
12857 );
12858 }
12859
12860 #[test]
12861 fn licenca_projects_option_str_by_borrow() {
12862 // The by-borrow pin: [`Caixa::licenca`] returns
12863 // `Option<&str>` by borrow — the `&str` borrows the underlying
12864 // `String` storage of the `Option<String>` slot and the
12865 // accessor must not allocate a fresh `String` on every call.
12866 // Peer of the per-`:placement`
12867 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
12868 // borrow pin on the peer per-M3-mesh-slot
12869 // `Option<&str>`-return axis, extended onto the outer top-
12870 // level [`Caixa`] universal-axis `Option<&str>` shape — the
12871 // accessor's returned `&str` must borrow from `&self` (the
12872 // returned reference's lifetime is tied to `&self`), and
12873 // calling the accessor twice on the same [`Caixa`] must yield
12874 // the same `Option<&str>` verbatim (idempotent, no side
12875 // effects on `&self`).
12876 //
12877 // Pins against a future silent detour that returned an owned
12878 // `Option<String>` (which would type-check but silently
12879 // allocate on every call, breaking the zero-cost projection
12880 // every peer sibling accessor carries), or a one-arm-only
12881 // accessor that returned a saturating value on some sentinel
12882 // input (breaking the pass-through invariant the sibling
12883 // required-scalar accessors carry).
12884 for licenca in [None, Some(""), Some("MIT"), Some("Apache-2.0 OR MIT")] {
12885 let c = caixa_with_licenca(licenca);
12886 let first = c.licenca();
12887 let second = c.licenca();
12888 assert_eq!(
12889 first, second,
12890 "Caixa::licenca must be idempotent — two successive \
12891 calls on the same &self must return the same \
12892 Option<&str>",
12893 );
12894 assert_eq!(
12895 first, licenca,
12896 "Caixa::licenca must return :licenca verbatim by \
12897 borrow — got {first:?}, expected {licenca:?}",
12898 );
12899 }
12900 }
12901
12902 // ── Caixa::repositorio — outer top-level Option<&str> scalar accessor ──
12903
12904 #[test]
12905 fn repositorio_returns_repositorio_byte_string_verbatim_across_permutations() {
12906 // The canonical per-`Caixa` `:repositorio` git-repo-URL scalar
12907 // pin: [`Caixa::repositorio`] must return the `:repositorio`
12908 // typed byte-string verbatim as an `Option<&str>`, byte-equal
12909 // to the raw `self.repositorio.as_deref()` access across every
12910 // representative value in the accept-set — `None` (the "omit
12911 // the slot to defer to the per-renderer placeholder" arm every
12912 // existing fixture without a `:repositorio` line carries),
12913 // `Some("")` (a past-the-guard sentinel that pins the accessor
12914 // doesn't perform a silent `Some("") → None` collapse on the
12915 // empty arm — validate rejects `Some("")` through
12916 // `RepositorioEmpty` but the accessor must ship the raw slot
12917 // verbatim so a validate-time gate regression surfaces at the
12918 // caixa-helm / caixa-flux emit boundary rather than being
12919 // silently absorbed into the per-renderer fallback),
12920 // `Some("github:pleme-io/hello-rio")` (the canonical `github:`
12921 // shorthand every existing manifest fixture across
12922 // `caixa-helm` / `caixa-mesh` and the `examples/` uses),
12923 // `Some("https://github.com/pleme-io/checkout")` (the canonical
12924 // `https://` URL the README quickstart uses),
12925 // `Some("ssh://git@github.com/pleme-io/checkout.git")` /
12926 // `Some("git://github.com/pleme-io/checkout.git")` /
12927 // `Some("git@github.com:pleme-io/checkout.git")` /
12928 // `Some("file:///opt/mirrors/pleme-io/checkout")` (every non-
12929 // github scheme the shared `is_git_repo_url` predicate
12930 // documents), and five past-the-guard sentinels for the
12931 // `RepositorioInvalid` refusal cases (`Some("pleme-io/checkout")`
12932 // missing-colon, `Some("-upload-pack=evil")` leading-dash, /
12933 // `Some("github:pleme-io/checkout?ref=main")` query-string, /
12934 // `Some("github:pleme-io/checkout#main")` fragment-anchor, /
12935 // `Some("github:pleme-io/{tpl}")` URI-template-placeholder — the
12936 // sentinels pin the accessor doesn't silently absorb the
12937 // refusal cases into a fallback).
12938 //
12939 // Second outer top-level [`Caixa`] `Option<&str>`-return scalar
12940 // accessor pin on the substrate primitive — sibling of the peer
12941 // [`Caixa::licenca`] (6d5bc28) pin
12942 // (`licenca_returns_licenca_byte_string_verbatim_across_permutations`)
12943 // that opened the "outer [`Caixa`] `Option<&str>` scalar"
12944 // projection pin pattern this pin folds on. Sibling in shape to
12945 // the peer per-`:placement`
12946 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
12947 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
12948 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
12949 // axes, extended onto the outer top-level [`Caixa`] universal-
12950 // axis surface. Pins against a future silent detour that
12951 // returned an owned `Option<String>` (which would type-check
12952 // but silently allocate on every accessor call, breaking the
12953 // zero-cost projection every peer sibling accessor carries), a
12954 // `Some("") → None` collapse (which would silently absorb the
12955 // `RepositorioEmpty` refusal case at the accessor boundary and
12956 // the caixa-helm `Chart.yaml` `home:` fold would silently
12957 // render a `home: null` / omitted field on a struct-literal
12958 // `Caixa { repositorio: Some(""), .. }`), or a
12959 // `None → Some(<default>)` collapse (which would silently reify
12960 // the per-renderer fallback at the accessor boundary and every
12961 // downstream consumer keying off the `Option::is_none()`
12962 // discriminator would lose the "author omitted the slot"
12963 // signal).
12964 for repositorio in [
12965 None,
12966 Some(""),
12967 Some("github:pleme-io/hello-rio"),
12968 Some("https://github.com/pleme-io/checkout"),
12969 Some("ssh://git@github.com/pleme-io/checkout.git"),
12970 Some("git://github.com/pleme-io/checkout.git"),
12971 Some("git@github.com:pleme-io/checkout.git"),
12972 Some("file:///opt/mirrors/pleme-io/checkout"),
12973 Some("pleme-io/checkout"),
12974 Some("-upload-pack=evil"),
12975 Some("github:pleme-io/checkout?ref=main"),
12976 Some("github:pleme-io/checkout#main"),
12977 Some("github:pleme-io/{tpl}"),
12978 ] {
12979 let c = caixa_with_repositorio(repositorio);
12980 assert_eq!(
12981 c.repositorio(),
12982 repositorio,
12983 "Caixa::repositorio must return :repositorio verbatim \
12984 (got {:?}, expected {repositorio:?})",
12985 c.repositorio(),
12986 );
12987 assert_eq!(
12988 c.repositorio(),
12989 c.repositorio.as_deref(),
12990 "Caixa::repositorio must byte-equal the raw \
12991 `self.repositorio.as_deref()` field access across every \
12992 value in the Option<&str> accept-set",
12993 );
12994 }
12995 }
12996
12997 #[test]
12998 fn validate_repositorio_empty_arm_routes_through_accessor() {
12999 // Composition pin: [`Caixa::validate_repositorio`]'s empty-arm
13000 // gate must key off [`Caixa::repositorio`], not the raw
13001 // `self.repositorio.as_deref()` field access. Structurally: a
13002 // `Caixa { repositorio: Some(""), .. }` must surface the
13003 // `RepositorioEmpty` refusal exactly, and a
13004 // `Caixa { repositorio: Some("github:pleme-io/hello-rio"), .. }`
13005 // (the canonical `github:` shorthand form) must pass validate.
13006 // The pair jointly pins the accessor + validate-gate
13007 // composition: any future silent detour that had the accessor
13008 // return `None` on the empty arm (a `.filter(|s| !s.is_empty())`
13009 // collapse) would silently absorb the `RepositorioEmpty` refusal
13010 // at the accessor boundary and the validate gate would accept a
13011 // struct-literal `Caixa { repositorio: Some(""), .. }` — the
13012 // composition pin catches that at caixa-core build time.
13013 //
13014 // Peer of the [`Caixa::licenca`] (6d5bc28)
13015 // `validate_licenca_empty_arm_routes_through_accessor`
13016 // composition pin on the sibling outer top-level [`Caixa`]
13017 // `Option<&str>` universal-axis surface — same "the validate /
13018 // shape-gate predicate must route through the substrate-
13019 // primitive typed dispatch" discipline extended onto the second
13020 // outer top-level [`Caixa`] universal-axis `Option<&str>`-
13021 // composition surface.
13022 let c = caixa_with_repositorio(Some(""));
13023 assert!(
13024 matches!(
13025 c.validate_repositorio(),
13026 Err(ManifestError::RepositorioEmpty),
13027 ),
13028 "validate_repositorio must reject repositorio == Some(\"\") \
13029 with RepositorioEmpty — the accessor and the validate gate \
13030 must route through the same substrate-primitive typed \
13031 dispatch on the :repositorio empty arm",
13032 );
13033 let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio"));
13034 assert!(
13035 c.validate_repositorio().is_ok(),
13036 "validate_repositorio must accept repositorio == \
13037 Some(\"github:pleme-io/hello-rio\") (the canonical \
13038 `github:` shorthand git-repo-URL shape)",
13039 );
13040 }
13041
13042 #[test]
13043 fn repositorio_projects_option_str_by_borrow() {
13044 // The by-borrow pin: [`Caixa::repositorio`] returns
13045 // `Option<&str>` by borrow — the `&str` borrows the underlying
13046 // `String` storage of the `Option<String>` slot and the
13047 // accessor must not allocate a fresh `String` on every call.
13048 // Peer of the per-`:placement`
13049 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) and the
13050 // [`Caixa::licenca`] (6d5bc28) by-borrow pins on the peer
13051 // `Option<&str>`-return axes, extended onto the second outer
13052 // top-level [`Caixa`] universal-axis `Option<&str>` shape —
13053 // the accessor's returned `&str` must borrow from `&self` (the
13054 // returned reference's lifetime is tied to `&self`), and
13055 // calling the accessor twice on the same [`Caixa`] must yield
13056 // the same `Option<&str>` verbatim (idempotent, no side effects
13057 // on `&self`).
13058 //
13059 // Pins against a future silent detour that returned an owned
13060 // `Option<String>` (which would type-check but silently
13061 // allocate on every call, breaking the zero-cost projection
13062 // every peer sibling accessor carries), or a one-arm-only
13063 // accessor that returned a saturating value on some sentinel
13064 // input (breaking the pass-through invariant the sibling
13065 // required-scalar accessors carry).
13066 for repositorio in [
13067 None,
13068 Some(""),
13069 Some("github:pleme-io/hello-rio"),
13070 Some("https://github.com/pleme-io/checkout"),
13071 ] {
13072 let c = caixa_with_repositorio(repositorio);
13073 let first = c.repositorio();
13074 let second = c.repositorio();
13075 assert_eq!(
13076 first, second,
13077 "Caixa::repositorio must be idempotent — two successive \
13078 calls on the same &self must return the same \
13079 Option<&str>",
13080 );
13081 assert_eq!(
13082 first, repositorio,
13083 "Caixa::repositorio must return :repositorio verbatim by \
13084 borrow — got {first:?}, expected {repositorio:?}",
13085 );
13086 }
13087 }
13088
13089 // ── Caixa::canonical_git_url — resolved-git-URL composer ──────────
13090
13091 #[test]
13092 fn canonical_git_url_returns_repositorio_verbatim_on_some_arm() {
13093 // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] must
13094 // return the author-declared `:repositorio` byte-string verbatim
13095 // on the `Some` arm — no scheme rewrite, no trailing-slash
13096 // canonicalization, no `github:` → `https://github.com/`
13097 // desugaring. The resolved-URL composer is the projection of
13098 // the raw [`Caixa::repositorio`] `Option<&str>` accessor onto
13099 // the `String`-return arity every substrate-side field-fill
13100 // consumer keys off; on the `Some` arm the projection is
13101 // `str::to_owned` verbatim, so every accept-set value the
13102 // sibling `repositorio_returns_repositorio_byte_string_verbatim_
13103 // across_permutations` pin covers (`https://…`, `github:…`,
13104 // `ssh://…`, `git://…`, `git@…`, `file://…`, and the past-the-
13105 // guard sentinel `pleme-io/…`) must survive the accessor
13106 // byte-equal. Pins against a future silent detour that rewrote
13107 // the `github:` shorthand to the `https://github.com/` full URL
13108 // at the accessor boundary (which would silently split the
13109 // resolved-URL surface from the raw [`Caixa::repositorio`]
13110 // accessor's documented pass-through invariant), or a trailing-
13111 // slash normalization (which would silently break the
13112 // FluxCD `GitRepository` `spec.url` byte-exact match every
13113 // downstream consumer keys the source-controller reconcile off).
13114 for repositorio in [
13115 "github:pleme-io/hello-rio",
13116 "https://github.com/pleme-io/checkout",
13117 "ssh://git@github.com/pleme-io/checkout.git",
13118 "git://github.com/pleme-io/checkout.git",
13119 "git@github.com:pleme-io/checkout.git",
13120 "file:///opt/mirrors/pleme-io/checkout",
13121 ] {
13122 let c = caixa_with_repositorio(Some(repositorio));
13123 assert_eq!(
13124 c.canonical_git_url(),
13125 repositorio,
13126 "Caixa::canonical_git_url on the Some arm must return \
13127 :repositorio verbatim (got {:?}, expected {repositorio:?})",
13128 c.canonical_git_url(),
13129 );
13130 }
13131 }
13132
13133 #[test]
13134 fn canonical_git_url_falls_back_to_pleme_org_url_on_none_arm() {
13135 // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] on the
13136 // `None` arm must emit the substrate's canonical pleme-org github
13137 // URL derived from `caixa.nome()` — `https://github.com/<org>/
13138 // <nome>` with `<org>` bound to [`crate::DEFAULT_PLEME_GIT_ORG`]
13139 // and `<nome>` bound to the typed [`Caixa::nome`] accessor. This
13140 // is the exact byte-image of the prior inline
13141 // [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_url`
13142 // composer at caixa-flux/src/lib.rs:2080 that every prior caller
13143 // re-derived open-coded. Pins against a future silent detour
13144 // that migrated the `<org>` segment to a different constant (a
13145 // fork rebranding that split off a new
13146 // `DEFAULT_PLEME_GIT_ORG_MIRROR` const the accessor would need
13147 // to migrate onto), a scheme change (`https://` → `git://` or
13148 // `ssh://`), or a per-`Caixa` `.canonical_git_url_prefix`
13149 // override (which would break the substrate-wide single-source-
13150 // of-truth guarantee this method encodes).
13151 let c = caixa_with_repositorio(None);
13152 let expected = format!(
13153 "https://github.com/{org}/{nome}",
13154 org = crate::DEFAULT_PLEME_GIT_ORG,
13155 nome = c.nome(),
13156 );
13157 assert_eq!(
13158 c.canonical_git_url(),
13159 expected,
13160 "Caixa::canonical_git_url on the None arm must fold through \
13161 the substrate's canonical pleme-org github URL fallback \
13162 `https://github.com/<DEFAULT_PLEME_GIT_ORG>/<nome>` — got \
13163 {:?}, expected {expected:?}",
13164 c.canonical_git_url(),
13165 );
13166 }
13167
13168 #[test]
13169 fn canonical_git_url_byte_matches_manual_composition() {
13170 // Byte-parity pin: [`Caixa::canonical_git_url`] must render
13171 // byte-identically to the manual open-coded
13172 // `caixa.repositorio().map(str::to_owned).unwrap_or_else(||
13173 // format!("https://github.com/{org}/{nome}", ...))` composition
13174 // every prior substrate-side caller re-derived. Guards the
13175 // paired-site convergence just applied at caixa-flux's
13176 // [`ClusterBundleOpts::for_caixa`] `git_url` composer (which
13177 // now routes through this accessor): a future implementation of
13178 // this method that reordered the format arguments, swapped the
13179 // `<org>` constant for a different one, or interposed a
13180 // canonicalization pass on the `Some` arm surfaces here as a
13181 // caixa-core build-time test failure rather than as a downstream
13182 // FluxCD `GitRepository` reconcile mismatch far from this
13183 // method's source.
13184 for repositorio in [
13185 None,
13186 Some("github:pleme-io/hello-rio"),
13187 Some("https://github.com/pleme-io/checkout"),
13188 Some("ssh://git@github.com/pleme-io/checkout.git"),
13189 ] {
13190 let c = caixa_with_repositorio(repositorio);
13191 let manual = c.repositorio().map_or_else(
13192 || {
13193 format!(
13194 "https://github.com/{org}/{nome}",
13195 org = crate::DEFAULT_PLEME_GIT_ORG,
13196 nome = c.nome(),
13197 )
13198 },
13199 str::to_owned,
13200 );
13201 assert_eq!(
13202 c.canonical_git_url(),
13203 manual,
13204 "Caixa::canonical_git_url must byte-equal the manual \
13205 open-coded `repositorio().map(str::to_owned)\
13206 .unwrap_or_else(|| format!(...))` composition across \
13207 every representative :repositorio input — got {:?}, \
13208 expected {manual:?}",
13209 c.canonical_git_url(),
13210 );
13211 }
13212 }
13213
13214 // ── Caixa::publish_tag — resolved-publish-tag composer ───────────
13215
13216 #[test]
13217 fn publish_tag_composes_prefix_and_versao_on_all_shapes() {
13218 // Fail-before-pass-after pin: [`Caixa::publish_tag`] must compose
13219 // [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] against the caixa's typed
13220 // [`Caixa::versao`] byte-string across every SemVer-2 shape the
13221 // sibling [`validate_versao_accepts_canonical_forms`] positive-set
13222 // sweep documents — bare MAJOR.MINOR.PATCH, pre-release tags
13223 // (`-rc.1`), build metadata (`+build.42`), the combined form, and
13224 // the `0.0.0` boundary case. Every accept-set value the peer
13225 // validate gate lets through must survive the resolved-tag
13226 // projection byte-equal.
13227 for versao in [
13228 "0.1.0",
13229 "0.0.0",
13230 "1.0.0",
13231 "1.2.3-rc.1",
13232 "1.2.3+build.42",
13233 "1.2.3-rc.1+build.42",
13234 ] {
13235 let c = caixa_with_versao(versao);
13236 let expected = format!(
13237 "{prefix}{versao}",
13238 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
13239 );
13240 assert_eq!(
13241 c.publish_tag(),
13242 expected,
13243 "Caixa::publish_tag must compose \
13244 DEFAULT_PUBLISH_TAG_PREFIX ({prefix:?}) against \
13245 :versao ({versao:?}) verbatim — got {got:?}, \
13246 expected {expected:?}",
13247 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
13248 got = c.publish_tag(),
13249 );
13250 }
13251 }
13252
13253 #[test]
13254 fn publish_tag_starts_with_default_publish_tag_prefix() {
13255 // Prefix-shape pin: every [`Caixa::publish_tag`] emission must
13256 // begin with the canonical [`crate::DEFAULT_PUBLISH_TAG_PREFIX`]
13257 // byte-string on every input, guarding a hypothetical future
13258 // implementation that migrated the prefix segment to an inline
13259 // literal (`"v"`) that would silently drift from any rebrand of
13260 // the lifted constant. Peer to the sibling caixa-flux
13261 // `cluster_bundle_default_git_tag_uses_lifted_caixa_core_prefix`
13262 // test which pins the same prefix invariant at the reader-side
13263 // `GitRefSpec::Tag` emit site.
13264 for versao in ["0.0.0", "0.1.0", "1.2.3-rc.1", "9.9.9+build.1"] {
13265 let c = caixa_with_versao(versao);
13266 let tag = c.publish_tag();
13267 assert!(
13268 tag.starts_with(crate::DEFAULT_PUBLISH_TAG_PREFIX),
13269 "Caixa::publish_tag emission {tag:?} must start with \
13270 the lifted crate::DEFAULT_PUBLISH_TAG_PREFIX \
13271 ({prefix:?})",
13272 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
13273 );
13274 }
13275 }
13276
13277 #[test]
13278 fn publish_tag_byte_matches_manual_composition() {
13279 // Byte-parity pin: [`Caixa::publish_tag`] must render byte-
13280 // identically to the manual open-coded
13281 // `format!("{prefix}{versao}", prefix =
13282 // caixa_core::DEFAULT_PUBLISH_TAG_PREFIX, versao =
13283 // caixa.versao())` composition every prior substrate-side
13284 // caller re-derived. Guards the paired-site convergence just
13285 // applied at caixa-flux's [`ClusterBundleOpts::for_caixa`]
13286 // `git_ref` composer (which now routes through this accessor):
13287 // a future implementation of this method that reordered the
13288 // format arguments, swapped the `<prefix>` constant for a
13289 // different one, or interposed a canonicalization pass on the
13290 // `:versao` axis surfaces here as a caixa-core build-time test
13291 // failure rather than as a downstream FluxCD `GitRepository`
13292 // reconcile mismatch far from this method's source.
13293 for versao in [
13294 "0.1.0",
13295 "0.0.0",
13296 "1.2.3-rc.1",
13297 "1.2.3+build.42",
13298 "1.2.3-rc.1+build.42",
13299 ] {
13300 let c = caixa_with_versao(versao);
13301 let manual = format!(
13302 "{prefix}{versao}",
13303 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
13304 versao = c.versao(),
13305 );
13306 assert_eq!(
13307 c.publish_tag(),
13308 manual,
13309 "Caixa::publish_tag must byte-equal the manual \
13310 open-coded `format!(\"{{prefix}}{{versao}}\", ...)` \
13311 composition across every representative :versao input \
13312 — got {got:?}, expected {manual:?}",
13313 got = c.publish_tag(),
13314 );
13315 }
13316 }
13317
13318 // ── Caixa::lareira_chart_name — resolved-chart-name composer ─────
13319
13320 #[test]
13321 fn lareira_chart_name_composes_prefix_and_nome_on_all_shapes() {
13322 // Fail-before-pass-after pin: [`Caixa::lareira_chart_name`] must
13323 // compose [`crate::LAREIRA_CHART_NAME_PREFIX`] against the caixa's
13324 // typed [`Caixa::nome`] byte-string across every DNS-1123 shape
13325 // the sibling [`validate_nome_accepts_canonical_forms`] positive-
13326 // set sweep documents — single-word, hyphen-joined, version-
13327 // suffixed, single-char, two-char, digit-start, retry-suffixed.
13328 // Every accept-set value the peer validate gate lets through must
13329 // survive the resolved-chart-name projection byte-equal.
13330 for nome in [
13331 "checkout",
13332 "cart-v2",
13333 "a",
13334 "db",
13335 "3rd-party-shim",
13336 "payment-retry",
13337 "0",
13338 ] {
13339 let c = caixa_with_nome(nome);
13340 let expected = format!("{prefix}{nome}", prefix = crate::LAREIRA_CHART_NAME_PREFIX);
13341 assert_eq!(
13342 c.lareira_chart_name(),
13343 expected,
13344 "Caixa::lareira_chart_name must compose \
13345 LAREIRA_CHART_NAME_PREFIX ({prefix:?}) against \
13346 :nome ({nome:?}) verbatim — got {got:?}, \
13347 expected {expected:?}",
13348 prefix = crate::LAREIRA_CHART_NAME_PREFIX,
13349 got = c.lareira_chart_name(),
13350 );
13351 }
13352 }
13353
13354 #[test]
13355 fn lareira_chart_name_starts_with_lifted_prefix() {
13356 // Prefix-shape pin: every [`Caixa::lareira_chart_name`] emission
13357 // must begin with the canonical
13358 // [`crate::LAREIRA_CHART_NAME_PREFIX`] byte-string on every
13359 // input, guarding a hypothetical future implementation that
13360 // migrated the prefix segment to an inline literal (`"lareira-"`)
13361 // that would silently drift from any rebrand of the lifted
13362 // constant. Peer to the sibling
13363 // [`publish_tag_starts_with_default_publish_tag_prefix`] pin on
13364 // the co-resident resolved-publish-tag composer's prefix axis.
13365 for nome in ["checkout", "cart", "a", "payment-retry", "0"] {
13366 let c = caixa_with_nome(nome);
13367 let chart = c.lareira_chart_name();
13368 assert!(
13369 chart.starts_with(crate::LAREIRA_CHART_NAME_PREFIX),
13370 "Caixa::lareira_chart_name emission {chart:?} must start \
13371 with the lifted crate::LAREIRA_CHART_NAME_PREFIX \
13372 ({prefix:?})",
13373 prefix = crate::LAREIRA_CHART_NAME_PREFIX,
13374 );
13375 }
13376 }
13377
13378 #[test]
13379 fn lareira_chart_name_byte_matches_canonical_helper_composition() {
13380 // Byte-parity pin: [`Caixa::lareira_chart_name`] must render
13381 // byte-identically to the manual open-coded
13382 // `caixa_core::lareira_chart_name(caixa.nome())` two-step
13383 // composition every prior substrate-side caller re-derived.
13384 // Guards the paired-site convergence just applied at caixa-helm's
13385 // [`render_chart_for_servico_with`] `ChartDir.name` composer,
13386 // caixa-flux's [`cluster_bundle`] per-CR `chart_name` binding,
13387 // and caixa-tatara's [`process_for_aplicacao`] `release_name`
13388 // composer (all of which now route through this accessor): a
13389 // future implementation of this method that reordered the
13390 // composition arguments, swapped the `<prefix>` constant for a
13391 // different one, or interposed a canonicalization pass on the
13392 // `:nome` axis surfaces here as a caixa-core build-time test
13393 // failure rather than as a downstream Helm chart-render / FluxCD
13394 // reconcile / tatara Process-CR mismatch far from this method's
13395 // source.
13396 for nome in [
13397 "checkout",
13398 "cart-v2",
13399 "a",
13400 "db",
13401 "3rd-party-shim",
13402 "payment-retry",
13403 ] {
13404 let c = caixa_with_nome(nome);
13405 let manual = crate::lareira_chart_name(c.nome());
13406 assert_eq!(
13407 c.lareira_chart_name(),
13408 manual,
13409 "Caixa::lareira_chart_name must byte-equal the manual \
13410 open-coded `caixa_core::lareira_chart_name(caixa.nome())` \
13411 composition across every representative :nome input — \
13412 got {got:?}, expected {manual:?}",
13413 got = c.lareira_chart_name(),
13414 );
13415 }
13416 }
13417
13418 // ── Caixa::oci_chart_ref — resolved-OCI-chart-ref composer ────────
13419
13420 #[test]
13421 fn oci_chart_ref_composes_scheme_and_lareira_chart_name_on_all_shapes() {
13422 // Fail-before-pass-after pin: [`Caixa::oci_chart_ref`] must
13423 // compose [`crate::OCI_SCHEME_PREFIX`] + the caller-supplied
13424 // `registry` + [`crate::lareira_chart_name`]-of-[`Caixa::nome`]
13425 // across the full paired `(registry, :nome)` accept-set — every
13426 // representative registry the substrate-side emitters carry
13427 // (`ghcr.io/pleme-io/charts`, the canonical CAIXA-SDLC §II
13428 // ArtifactHub-tier registry; `ghcr.io/pleme-io`, the bare-org
13429 // arm the sibling `oci_chart_ref_pins_byte_shape_against_prior_
13430 // inline_format` render-side pin exercises; `registry.example.
13431 // com`, an off-org shape; `localhost:5000`, the local-dev shape
13432 // every `feira chart` iteration path lands under) × every DNS-
13433 // 1123 `:nome` shape the peer `validate_nome_accepts_canonical_
13434 // forms` positive-set sweep documents (single-word, hyphen-
13435 // joined, single-char, two-char, digit-start, retry-suffixed).
13436 // Every accept-set pair the peer validate gates let through must
13437 // survive the resolved-OCI-ref projection byte-equal.
13438 for registry in [
13439 "ghcr.io/pleme-io/charts",
13440 "ghcr.io/pleme-io",
13441 "registry.example.com",
13442 "localhost:5000",
13443 ] {
13444 for nome in [
13445 "checkout",
13446 "cart-v2",
13447 "a",
13448 "db",
13449 "3rd-party-shim",
13450 "payment-retry",
13451 "0",
13452 ] {
13453 let c = caixa_with_nome(nome);
13454 let expected = format!(
13455 "{scheme}{registry}/{chart}",
13456 scheme = crate::OCI_SCHEME_PREFIX,
13457 chart = crate::lareira_chart_name(nome),
13458 );
13459 assert_eq!(
13460 c.oci_chart_ref(registry),
13461 expected,
13462 "Caixa::oci_chart_ref must compose \
13463 OCI_SCHEME_PREFIX ({scheme:?}) + registry ({registry:?}) + \
13464 lareira_chart_name(:nome ({nome:?})) verbatim — got {got:?}, \
13465 expected {expected:?}",
13466 scheme = crate::OCI_SCHEME_PREFIX,
13467 got = c.oci_chart_ref(registry),
13468 );
13469 }
13470 }
13471 }
13472
13473 #[test]
13474 fn oci_chart_ref_starts_with_lifted_scheme_prefix() {
13475 // Scheme-prefix-shape pin: every [`Caixa::oci_chart_ref`]
13476 // emission must begin with the canonical
13477 // [`crate::OCI_SCHEME_PREFIX`] byte-string on every input, guarding
13478 // a hypothetical future implementation that migrated the scheme
13479 // segment to an inline literal (`"oci://"`) that would silently
13480 // drift from any rebrand of the lifted constant. Peer to the
13481 // sibling [`publish_tag_starts_with_default_publish_tag_prefix`]
13482 // + [`lareira_chart_name_starts_with_lifted_prefix`] pins on the
13483 // co-resident resolved-publish-tag / resolved-chart-name
13484 // composers' prefix axes.
13485 for registry in [
13486 "ghcr.io/pleme-io/charts",
13487 "ghcr.io/pleme-io",
13488 "localhost:5000",
13489 ] {
13490 for nome in ["checkout", "cart", "a", "payment-retry", "0"] {
13491 let c = caixa_with_nome(nome);
13492 let ref_ = c.oci_chart_ref(registry);
13493 assert!(
13494 ref_.starts_with(crate::OCI_SCHEME_PREFIX),
13495 "Caixa::oci_chart_ref emission {ref_:?} must start \
13496 with the lifted crate::OCI_SCHEME_PREFIX ({scheme:?}) \
13497 — registry ({registry:?}), :nome ({nome:?})",
13498 scheme = crate::OCI_SCHEME_PREFIX,
13499 );
13500 }
13501 }
13502 }
13503
13504 #[test]
13505 fn oci_chart_ref_byte_matches_canonical_helper_composition() {
13506 // Byte-parity pin: [`Caixa::oci_chart_ref`] must render byte-
13507 // identically to the manual open-coded
13508 // `caixa_core::oci_chart_ref(registry, caixa.nome())` two-step
13509 // composition every prior substrate-side caller re-derived.
13510 // Guards the paired-site convergence just applied at caixa-
13511 // tatara's [`derive_chart_ref`] helper (which now routes through
13512 // this accessor): a future implementation of this method that
13513 // reordered the composition arguments, swapped the `<scheme>`
13514 // constant for a different one, migrated the `<chart>` segment
13515 // off the paired [`crate::lareira_chart_name`] composer, or
13516 // interposed a canonicalization pass on either input axis
13517 // surfaces here as a caixa-core build-time test failure rather
13518 // than as a downstream `helm install` / FluxCD OCI-source
13519 // reconcile / tatara `Process`-CR mismatch far from this
13520 // method's source. Sibling to the peer
13521 // [`lareira_chart_name_byte_matches_canonical_helper_composition`]
13522 // / [`publish_tag_byte_matches_manual_composition`] /
13523 // [`canonical_git_url_byte_matches_manual_composition`] byte-
13524 // parity pins that carry the same discipline on the co-resident
13525 // resolved-chart-name / resolved-publish-tag / resolved-git-URL
13526 // composers.
13527 for registry in [
13528 "ghcr.io/pleme-io/charts",
13529 "ghcr.io/pleme-io",
13530 "registry.example.com",
13531 "localhost:5000",
13532 ] {
13533 for nome in [
13534 "checkout",
13535 "cart-v2",
13536 "a",
13537 "db",
13538 "3rd-party-shim",
13539 "payment-retry",
13540 ] {
13541 let c = caixa_with_nome(nome);
13542 let manual = crate::oci_chart_ref(registry, c.nome());
13543 assert_eq!(
13544 c.oci_chart_ref(registry),
13545 manual,
13546 "Caixa::oci_chart_ref must byte-equal the manual \
13547 open-coded `caixa_core::oci_chart_ref(registry, \
13548 caixa.nome())` composition across every representative \
13549 (registry, :nome) pair — registry ({registry:?}), \
13550 :nome ({nome:?}), got {got:?}, expected {manual:?}",
13551 got = c.oci_chart_ref(registry),
13552 );
13553 }
13554 }
13555 }
13556
13557 // ── Caixa::descricao — outer top-level Option<&str> scalar accessor ──
13558
13559 #[test]
13560 fn descricao_returns_descricao_byte_string_verbatim_across_permutations() {
13561 // The canonical per-`Caixa` `:descricao` free-form-prose scalar
13562 // pin: [`Caixa::descricao`] must return the `:descricao` typed
13563 // byte-string verbatim as an `Option<&str>`, byte-equal to the
13564 // raw `self.descricao.as_deref()` access across every
13565 // representative value in the accept-set — `None` (the "omit
13566 // the slot to defer to the per-renderer `caixa.nome`-derived
13567 // fallback" arm every existing fixture without a `:descricao`
13568 // line carries), `Some("")` (a past-the-guard sentinel that
13569 // pins the accessor doesn't perform a silent `Some("") → None`
13570 // collapse on the empty arm — validate rejects `Some("")`
13571 // through `DescricaoEmpty` but the accessor must ship the raw
13572 // slot verbatim so a validate-time gate regression surfaces at
13573 // the caixa-helm / caixa-feira emit boundary rather than being
13574 // silently absorbed into the per-renderer `caixa.nome`-derived
13575 // fallback), `Some("Checkout flow.")` (the canonical one-line
13576 // prose descriptor the peer
13577 // `validate_descricao_accepts_canonical_value` positive sweep
13578 // exercises), `Some("Canonical Rust→wasm32-wasip2 caixa
13579 // Servico.")` (the multi-byte Unicode continuation-byte shape
13580 // the `hello-rio` fixture carries), `Some("→ — · ✓")` (a
13581 // multi-glyph Unicode shape the peer
13582 // `is_chart_description_shape` predicate accepts), and five
13583 // past-the-guard sentinels for the `DescricaoInvalid` refusal
13584 // cases (`Some(" Checkout flow.")` leading-whitespace,
13585 // `Some("Checkout flow. ")` trailing-whitespace,
13586 // `Some("Checkout\nflow.")` embedded-LF,
13587 // `Some("Checkout\tflow.")` embedded-TAB, and
13588 // `Some("Checkout\x00flow.")` embedded-NUL — the sentinels pin
13589 // the accessor doesn't silently absorb the refusal cases into
13590 // a fallback).
13591 //
13592 // Third outer top-level [`Caixa`] `Option<&str>`-return scalar
13593 // accessor pin on the substrate primitive — sibling of the peer
13594 // [`Caixa::licenca`] (6d5bc28) and [`Caixa::repositorio`]
13595 // (cc7332d) pins that opened the "outer [`Caixa`]
13596 // `Option<&str>` scalar" projection pin pattern this pin folds
13597 // on. Sibling in shape to the peer per-`:placement`
13598 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
13599 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
13600 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
13601 // axes, extended onto the outer top-level [`Caixa`] universal-
13602 // axis surface. Pins against a future silent detour that
13603 // returned an owned `Option<String>` (which would type-check
13604 // but silently allocate on every accessor call, breaking the
13605 // zero-cost projection every peer sibling accessor carries), a
13606 // `Some("") → None` collapse (which would silently absorb the
13607 // `DescricaoEmpty` refusal case at the accessor boundary and
13608 // the caixa-helm `Chart.yaml` `description:` fold would
13609 // silently render a `caixa.nome`-derived fallback on a
13610 // struct-literal `Caixa { descricao: Some(""), .. }`), or a
13611 // `None → Some(<default>)` collapse (which would silently
13612 // reify the per-renderer `caixa.nome`-derived fallback at the
13613 // accessor boundary and every downstream consumer keying off
13614 // the `Option::is_none()` discriminator would lose the "author
13615 // omitted the slot" signal).
13616 for descricao in [
13617 None,
13618 Some(""),
13619 Some("Checkout flow."),
13620 Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
13621 Some("→ — · ✓"),
13622 Some(" Checkout flow."),
13623 Some("Checkout flow. "),
13624 Some("Checkout\nflow."),
13625 Some("Checkout\tflow."),
13626 Some("Checkout\x00flow."),
13627 ] {
13628 let c = caixa_with_descricao(descricao);
13629 assert_eq!(
13630 c.descricao(),
13631 descricao,
13632 "Caixa::descricao must return :descricao verbatim (got \
13633 {:?}, expected {descricao:?})",
13634 c.descricao(),
13635 );
13636 assert_eq!(
13637 c.descricao(),
13638 c.descricao.as_deref(),
13639 "Caixa::descricao must byte-equal the raw \
13640 `self.descricao.as_deref()` field access across every \
13641 value in the Option<&str> accept-set",
13642 );
13643 }
13644 }
13645
13646 #[test]
13647 fn validate_descricao_empty_arm_routes_through_accessor() {
13648 // Composition pin: [`Caixa::validate_descricao`]'s empty-arm
13649 // gate must key off [`Caixa::descricao`], not the raw
13650 // `self.descricao.as_deref()` field access. Structurally: a
13651 // `Caixa { descricao: Some(""), .. }` must surface the
13652 // `DescricaoEmpty` refusal exactly, and a
13653 // `Caixa { descricao: Some("Checkout flow."), .. }` (the
13654 // canonical one-line-prose form) must pass validate. The pair
13655 // jointly pins the accessor + validate-gate composition: any
13656 // future silent detour that had the accessor return `None` on
13657 // the empty arm (a `.filter(|s| !s.is_empty())` collapse) would
13658 // silently absorb the `DescricaoEmpty` refusal at the accessor
13659 // boundary and the validate gate would accept a struct-literal
13660 // `Caixa { descricao: Some(""), .. }` — the composition pin
13661 // catches that at caixa-core build time.
13662 //
13663 // Peer of the [`Caixa::licenca`] (6d5bc28)
13664 // `validate_licenca_empty_arm_routes_through_accessor` and
13665 // [`Caixa::repositorio`] (cc7332d)
13666 // `validate_repositorio_empty_arm_routes_through_accessor`
13667 // composition pins on the sibling outer top-level [`Caixa`]
13668 // `Option<&str>` universal-axis surface — same "the validate /
13669 // shape-gate predicate must route through the substrate-
13670 // primitive typed dispatch" discipline extended onto the third
13671 // outer top-level [`Caixa`] universal-axis `Option<&str>`-
13672 // composition surface.
13673 let c = caixa_with_descricao(Some(""));
13674 assert!(
13675 matches!(c.validate_descricao(), Err(ManifestError::DescricaoEmpty),),
13676 "validate_descricao must reject descricao == Some(\"\") \
13677 with DescricaoEmpty — the accessor and the validate gate \
13678 must route through the same substrate-primitive typed \
13679 dispatch on the :descricao empty arm",
13680 );
13681 let c = caixa_with_descricao(Some("Checkout flow."));
13682 assert!(
13683 c.validate_descricao().is_ok(),
13684 "validate_descricao must accept descricao == \
13685 Some(\"Checkout flow.\") (the canonical one-line-prose \
13686 chart-description shape)",
13687 );
13688 }
13689
13690 #[test]
13691 fn descricao_projects_option_str_by_borrow() {
13692 // The by-borrow pin: [`Caixa::descricao`] returns
13693 // `Option<&str>` by borrow — the `&str` borrows the underlying
13694 // `String` storage of the `Option<String>` slot and the
13695 // accessor must not allocate a fresh `String` on every call.
13696 // Peer of the [`Caixa::licenca`] (6d5bc28) and
13697 // [`Caixa::repositorio`] (cc7332d) by-borrow pins on the peer
13698 // outer top-level [`Caixa`] `Option<&str>`-return axes, and of
13699 // the per-`:placement`
13700 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
13701 // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
13702 // return axis, extended onto the third outer top-level
13703 // [`Caixa`] universal-axis `Option<&str>` shape — the
13704 // accessor's returned `&str` must borrow from `&self` (the
13705 // returned reference's lifetime is tied to `&self`), and
13706 // calling the accessor twice on the same [`Caixa`] must yield
13707 // the same `Option<&str>` verbatim (idempotent, no side
13708 // effects on `&self`).
13709 //
13710 // Pins against a future silent detour that returned an owned
13711 // `Option<String>` (which would type-check but silently
13712 // allocate on every call, breaking the zero-cost projection
13713 // every peer sibling accessor carries), or a one-arm-only
13714 // accessor that returned a saturating value on some sentinel
13715 // input (breaking the pass-through invariant the sibling
13716 // required-scalar accessors carry).
13717 for descricao in [
13718 None,
13719 Some(""),
13720 Some("Checkout flow."),
13721 Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
13722 ] {
13723 let c = caixa_with_descricao(descricao);
13724 let first = c.descricao();
13725 let second = c.descricao();
13726 assert_eq!(
13727 first, second,
13728 "Caixa::descricao must be idempotent — two successive \
13729 calls on the same &self must return the same \
13730 Option<&str>",
13731 );
13732 assert_eq!(
13733 first, descricao,
13734 "Caixa::descricao must return :descricao verbatim by \
13735 borrow — got {first:?}, expected {descricao:?}",
13736 );
13737 }
13738 }
13739
13740 // ── validate_edicao — universal-axis language-edition shape ──
13741
13742 fn caixa_with_edicao(edicao: Option<&str>) -> Caixa {
13743 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13744 c.edicao = edicao.map(String::from);
13745 c
13746 }
13747
13748 #[test]
13749 fn validate_edicao_accepts_none() {
13750 // The omit-the-slot identity: `:edicao` is optional. The
13751 // gate is a no-op when the author didn't declare a value —
13752 // every caixa without an `:edicao` line trivially passes,
13753 // and the substrate-side build pipeline falls back to the
13754 // documented default edition. Mirrors the peer
13755 // `validate_licenca_accepts_none` posture on the sibling
13756 // `Option<String>` Caixa slot.
13757 let c = caixa_with_edicao(None);
13758 c.validate_edicao().unwrap();
13759 }
13760
13761 #[test]
13762 fn validate_edicao_accepts_canonical_value() {
13763 // Positive control: the canonical `"2026"` edition every
13764 // existing renderer-side fixture (`caixa-helm`, `caixa-flux`,
13765 // `caixa-mesh`) carries by construction passes the gate.
13766 // Future-introduced sibling editions (`"2027"`, `"2030"`,
13767 // `"2049"`) that match the same 4-digit ASCII decimal year
13768 // shape must also trivially pass — the structural shape
13769 // predicate accepts every well-formed year regardless of
13770 // whether the substrate yet understands the specific value
13771 // (a future known-edition allowlist tightens that).
13772 for ed in ["2026", "2027", "2030", "2049"] {
13773 let c = caixa_with_edicao(Some(ed));
13774 c.validate_edicao()
13775 .unwrap_or_else(|err| panic!("canonical {ed:?} must pass: {err:?}"));
13776 }
13777 }
13778
13779 #[test]
13780 fn validate_edicao_rejects_empty_some() {
13781 // Canonical paste-from-blank-doc footgun. Without this gate
13782 // the empty `Some("")` silently lands as `(:edicao "")` in
13783 // the rendered caixa.lisp and a future renderer-side
13784 // consumer's `Option::unwrap_or_else` (which only fires on
13785 // `None`) skips its fallback. Mirrors the peer
13786 // [`ManifestError::LicencaEmpty`] empty-arm on the sibling
13787 // `Option<String>` Caixa slot.
13788 let c = caixa_with_edicao(Some(""));
13789 let err = c.validate_edicao().unwrap_err();
13790 assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
13791 }
13792
13793 #[test]
13794 fn validate_edicao_rejects_free_form_non_year() {
13795 // Free-form non-year footgun: the bare `"x"` / `"latest"` /
13796 // `"nightly"` shapes carry no operational meaning on the
13797 // substrate's build-time edition selector. Until this gate
13798 // landed the bare empty-arm check let every such value
13799 // through and broke far from the source caixa.lisp. Peer
13800 // with the shape-predicate cascade
13801 // `validate_repositorio_rejects_missing_colon_separator`
13802 // establishes past its own empty arm.
13803 for ed in ["x", "latest", "nightly", "stable"] {
13804 let c = caixa_with_edicao(Some(ed));
13805 let err = c.validate_edicao().unwrap_err();
13806 assert!(
13807 matches!(err, ManifestError::EdicaoInvalid { .. }),
13808 "expected EdicaoInvalid on {ed:?}, got {err:?}",
13809 );
13810 }
13811 }
13812
13813 #[test]
13814 fn validate_edicao_rejects_trailing_whitespace() {
13815 // Paste-from-doc whitespace footgun. A trailing space in
13816 // the `:edicao` value would silently break the substrate's
13817 // build-time edition match-table lookup at the rendered
13818 // artifact's edition-selector consumer. The shape predicate
13819 // refuses every whitespace byte by construction (any byte
13820 // outside `0-9` fails `is_ascii_digit`). Peer with
13821 // `validate_repositorio_rejects_whitespace`.
13822 let c = caixa_with_edicao(Some("2026 "));
13823 let err = c.validate_edicao().unwrap_err();
13824 let ManifestError::EdicaoInvalid { edicao, .. } = err else {
13825 panic!("expected EdicaoInvalid, got {err:?}");
13826 };
13827 assert_eq!(edicao, "2026 ");
13828 }
13829
13830 #[test]
13831 fn validate_edicao_rejects_leading_whitespace() {
13832 // Symmetric paste-from-doc whitespace footgun on the leading
13833 // boundary — the gate refuses every shape with a non-digit
13834 // byte by construction.
13835 let c = caixa_with_edicao(Some(" 2026"));
13836 let err = c.validate_edicao().unwrap_err();
13837 assert!(
13838 matches!(err, ManifestError::EdicaoInvalid { .. }),
13839 "got {err:?}",
13840 );
13841 }
13842
13843 #[test]
13844 fn validate_edicao_rejects_control_char() {
13845 // Paste-from-multiline-doc CRLF footgun — control characters
13846 // at the value boundary break the substrate's build-time
13847 // edition-selector parser. Peer with
13848 // `validate_repositorio_rejects_control_char`.
13849 let c = caixa_with_edicao(Some("2026\n"));
13850 let err = c.validate_edicao().unwrap_err();
13851 assert!(
13852 matches!(err, ManifestError::EdicaoInvalid { .. }),
13853 "got {err:?}",
13854 );
13855 }
13856
13857 #[test]
13858 fn validate_edicao_rejects_non_ascii_lookalike() {
13859 // Fullwidth-keyboard look-alike footgun — `"2026"` is
13860 // the U+FF12 U+FF10 U+FF12 U+FF16 sequence (CJK fullwidth
13861 // digits), 4 codepoints but 12 UTF-8 bytes; the substrate's
13862 // edition selector wants an ASCII year, and the gate
13863 // refuses every non-ASCII shape by construction (length in
13864 // bytes is 12 ≠ 4, *and* every byte falls outside
13865 // `is_ascii_digit`'s `0-9` range).
13866 let c = caixa_with_edicao(Some("2026"));
13867 let err = c.validate_edicao().unwrap_err();
13868 assert!(
13869 matches!(err, ManifestError::EdicaoInvalid { .. }),
13870 "got {err:?}",
13871 );
13872 }
13873
13874 #[test]
13875 fn validate_edicao_rejects_version_tag_prefix() {
13876 // Common version-tag idiom footgun — `"v2026"` / `"e2026"`
13877 // / `"r2026"` are familiar shapes from git-tag / Rust
13878 // edition / release-tag conventions that don't apply to
13879 // the year-shaped edition axis. The shape predicate refuses
13880 // every leading non-digit prefix.
13881 for ed in ["v2026", "e2026", "r2026"] {
13882 let c = caixa_with_edicao(Some(ed));
13883 let err = c.validate_edicao().unwrap_err();
13884 assert!(
13885 matches!(err, ManifestError::EdicaoInvalid { .. }),
13886 "expected EdicaoInvalid on {ed:?}, got {err:?}",
13887 );
13888 }
13889 }
13890
13891 #[test]
13892 fn validate_edicao_rejects_decimal_shape() {
13893 // Decimal-shaped pseudo-version footgun — `"2026.1"` /
13894 // `"2026.0"` are familiar shapes from semver / float
13895 // conventions that don't apply to the year-shaped edition
13896 // axis. The shape predicate refuses every non-digit byte
13897 // (`.` falls outside `is_ascii_digit`).
13898 for ed in ["2026.1", "2026.0", "2026.0.1"] {
13899 let c = caixa_with_edicao(Some(ed));
13900 let err = c.validate_edicao().unwrap_err();
13901 assert!(
13902 matches!(err, ManifestError::EdicaoInvalid { .. }),
13903 "expected EdicaoInvalid on {ed:?}, got {err:?}",
13904 );
13905 }
13906 }
13907
13908 #[test]
13909 fn validate_edicao_rejects_wrong_length_numeric() {
13910 // Wrong-length numeric footgun — `"26"` (truncated) /
13911 // `"202"` (truncated) / `"20260"` (extra digit) / `"00026"`
13912 // (zero-padded too wide) all parse as integers but don't
13913 // name a 4-digit year. The shape predicate refuses every
13914 // value whose length isn't exactly 4 bytes.
13915 for ed in ["26", "202", "20260", "00026", "9"] {
13916 let c = caixa_with_edicao(Some(ed));
13917 let err = c.validate_edicao().unwrap_err();
13918 assert!(
13919 matches!(err, ManifestError::EdicaoInvalid { .. }),
13920 "expected EdicaoInvalid on {ed:?}, got {err:?}",
13921 );
13922 }
13923 }
13924
13925 #[test]
13926 fn validate_edicao_empty_takes_precedence_over_shape() {
13927 // Empty-first cascade pin: the empty `Some("")` surfaces
13928 // the narrower `EdicaoEmpty` not the shape-predicate-
13929 // wrapped `EdicaoInvalid`, mirroring the peer
13930 // `validate_repositorio_empty_takes_precedence_over_shape`
13931 // (`RepositorioEmpty` → `RepositorioInvalid`),
13932 // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` →
13933 // `VersaoInvalid`, `FonteRepoEmpty` → `FonteRepoInvalid`
13934 // cascades. The shape predicate also refuses the empty
13935 // input (defensively — `s.len() != 4`), but the
13936 // manifest-layer empty arm runs first to surface the
13937 // narrower diagnostic verbatim.
13938 let c = caixa_with_edicao(Some(""));
13939 let err = c.validate_edicao().unwrap_err();
13940 assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
13941 }
13942
13943 #[test]
13944 fn validate_edicao_template_passes() {
13945 // Round-trip pin: the bare `Caixa::template` shape (which
13946 // carries `:edicao "2026"` verbatim) passes the gate by
13947 // construction. A future template-shape change that
13948 // introduced `(:edicao "")` or a non-year value would
13949 // surface here as a regression. Mirrors the peer
13950 // `validate_licenca_template_passes` pin.
13951 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13952 c.validate_edicao().unwrap();
13953 }
13954
13955 #[test]
13956 fn validate_edicao_diagnostic_names_offending_slot() {
13957 // Diagnostic-shape pin (peer with
13958 // `validate_licenca_diagnostic_names_offending_slot`): the
13959 // error's Display surfaces the `:edicao` slot name verbatim,
13960 // so a `feira lint` run can render the diagnostic without
13961 // re-parsing and the author can grep their caixa.lisp for
13962 // the offending `:edicao` line.
13963 let c = caixa_with_edicao(Some(""));
13964 let rendered = c.validate_edicao().unwrap_err().to_string();
13965 assert!(
13966 rendered.contains(":edicao"),
13967 "diagnostic must name the offending slot: {rendered}",
13968 );
13969 }
13970
13971 #[test]
13972 fn validate_edicao_invalid_diagnostic_carries_offending_value() {
13973 // Diagnostic-shape pin on the shape-predicate arm (peer
13974 // with `validate_repositorio_diagnostic_carries_offending_value`):
13975 // the error's Display surfaces the offending value + slot
13976 // name verbatim, so a `feira lint` run can render the
13977 // diagnostic without re-parsing and the author can grep
13978 // their caixa.lisp for the offending `:edicao` value.
13979 let c = caixa_with_edicao(Some("v2026"));
13980 let rendered = c.validate_edicao().unwrap_err().to_string();
13981 assert!(
13982 rendered.contains(":edicao"),
13983 "diagnostic must name the offending slot: {rendered}",
13984 );
13985 assert!(
13986 rendered.contains("v2026"),
13987 "diagnostic must quote the offending value: {rendered}",
13988 );
13989 }
13990
13991 // ── Caixa::edicao — outer top-level Option<&str> scalar accessor ──
13992
13993 #[test]
13994 fn edicao_returns_edicao_byte_string_verbatim_across_permutations() {
13995 // The canonical per-`Caixa` `:edicao` language-edition scalar
13996 // pin: [`Caixa::edicao`] must return the `:edicao` typed
13997 // byte-string verbatim as an `Option<&str>`, byte-equal to the
13998 // raw `self.edicao.as_deref()` access across every representative
13999 // value in the accept-set — `None` (the "omit the slot to defer
14000 // to the substrate's default edition" arm every existing
14001 // [`caixa-resolver`] fixture without an `:edicao` line carries),
14002 // `Some("")` (a past-the-guard sentinel that pins the accessor
14003 // doesn't perform a silent `Some("") → None` collapse on the
14004 // empty arm — validate rejects `Some("")` through `EdicaoEmpty`
14005 // but the accessor must ship the raw slot verbatim so a
14006 // validate-time gate regression surfaces at any future edition-
14007 // aware consumer's boundary rather than being silently absorbed
14008 // into the substrate's default edition), `Some("2026")` (the
14009 // canonical 4-digit-ASCII-decimal-year shape every `feira init`
14010 // template scaffolds via [`Caixa::template`] and every
14011 // renderer-side fixture at `caixa-helm/src/lib.rs:978` /
14012 // `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208`
14013 // carries by construction), `Some("2018")` / `Some("2021")` /
14014 // `Some("2024")` (canonical 4-digit-ASCII-decimal-year shapes
14015 // peer with Cargo's `[package] edition` grammar every future-
14016 // introduced sibling to `"2026"` will follow), and eight
14017 // past-the-guard sentinels for the `EdicaoInvalid` refusal cases
14018 // (`Some("2026 ")` trailing-whitespace, `Some(" 2026")` leading-
14019 // whitespace, `Some("2026\n")` embedded-LF, `Some("2026")`
14020 // fullwidth-non-ASCII-lookalike, `Some("v2026")` version-tag-
14021 // prefix, `Some("2026.1")` decimal-shape, `Some("26")` wrong-
14022 // length-numeric, `Some("latest")` free-form-non-year — the
14023 // sentinels pin the accessor doesn't silently absorb the
14024 // refusal cases into a substrate-default-edition fallback).
14025 //
14026 // Fourth and final outer top-level [`Caixa`] `Option<&str>`-
14027 // return scalar accessor pin on the substrate primitive —
14028 // sibling of the peer [`Caixa::licenca`] (6d5bc28),
14029 // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
14030 // (3f16e2f) pins that opened the "outer [`Caixa`]
14031 // `Option<&str>` scalar" projection pin pattern this pin folds
14032 // on. Sibling in shape to the peer per-`:placement`
14033 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
14034 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
14035 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
14036 // axes, extended onto the outer top-level [`Caixa`] universal-
14037 // axis surface's last unlifted `Option<String>` slot. Pins
14038 // against a future silent detour that returned an owned
14039 // `Option<String>` (which would type-check but silently
14040 // allocate on every accessor call, breaking the zero-cost
14041 // projection every peer sibling accessor carries), a
14042 // `Some("") → None` collapse (which would silently absorb the
14043 // `EdicaoEmpty` refusal case at the accessor boundary and any
14044 // future edition-aware consumer would silently fall back to
14045 // the substrate's default edition on a struct-literal
14046 // `Caixa { edicao: Some(""), .. }`), or a
14047 // `None → Some("2026")` collapse (which would silently reify
14048 // the substrate's default edition at the accessor boundary
14049 // and every downstream consumer keying off the
14050 // `Option::is_none()` discriminator would lose the "author
14051 // omitted the slot" signal).
14052 for edicao in [
14053 None,
14054 Some(""),
14055 Some("2026"),
14056 Some("2018"),
14057 Some("2021"),
14058 Some("2024"),
14059 Some("2026 "),
14060 Some(" 2026"),
14061 Some("2026\n"),
14062 Some("2026"),
14063 Some("v2026"),
14064 Some("2026.1"),
14065 Some("26"),
14066 Some("latest"),
14067 ] {
14068 let c = caixa_with_edicao(edicao);
14069 assert_eq!(
14070 c.edicao(),
14071 edicao,
14072 "Caixa::edicao must return :edicao verbatim (got {:?}, \
14073 expected {edicao:?})",
14074 c.edicao(),
14075 );
14076 assert_eq!(
14077 c.edicao(),
14078 c.edicao.as_deref(),
14079 "Caixa::edicao must byte-equal the raw \
14080 `self.edicao.as_deref()` field access across every \
14081 value in the Option<&str> accept-set",
14082 );
14083 }
14084 }
14085
14086 #[test]
14087 fn validate_edicao_empty_arm_routes_through_accessor() {
14088 // Composition pin: [`Caixa::validate_edicao`]'s empty-arm gate
14089 // must key off [`Caixa::edicao`], not the raw
14090 // `self.edicao.as_deref()` field access. Structurally: a
14091 // `Caixa { edicao: Some(""), .. }` must surface the
14092 // `EdicaoEmpty` refusal exactly, and a
14093 // `Caixa { edicao: Some("2026"), .. }` (the canonical
14094 // 4-digit-ASCII-decimal-year form) must pass validate. The
14095 // pair jointly pins the accessor + validate-gate composition:
14096 // any future silent detour that had the accessor return `None`
14097 // on the empty arm (a `.filter(|s| !s.is_empty())` collapse)
14098 // would silently absorb the `EdicaoEmpty` refusal at the
14099 // accessor boundary and the validate gate would accept a
14100 // struct-literal `Caixa { edicao: Some(""), .. }` — the
14101 // composition pin catches that at caixa-core build time.
14102 //
14103 // Peer of the [`Caixa::licenca`] (6d5bc28)
14104 // `validate_licenca_empty_arm_routes_through_accessor`,
14105 // [`Caixa::repositorio`] (cc7332d)
14106 // `validate_repositorio_empty_arm_routes_through_accessor`,
14107 // and [`Caixa::descricao`] (3f16e2f)
14108 // `validate_descricao_empty_arm_routes_through_accessor`
14109 // composition pins on the sibling outer top-level [`Caixa`]
14110 // `Option<&str>` universal-axis surface — same "the validate /
14111 // shape-gate predicate must route through the substrate-
14112 // primitive typed dispatch" discipline extended onto the
14113 // fourth and final outer top-level [`Caixa`] universal-axis
14114 // `Option<&str>`-composition surface, closing the accessor-
14115 // composition family.
14116 let c = caixa_with_edicao(Some(""));
14117 assert!(
14118 matches!(c.validate_edicao(), Err(ManifestError::EdicaoEmpty)),
14119 "validate_edicao must reject edicao == Some(\"\") with \
14120 EdicaoEmpty — the accessor and the validate gate must \
14121 route through the same substrate-primitive typed dispatch \
14122 on the :edicao empty arm",
14123 );
14124 let c = caixa_with_edicao(Some("2026"));
14125 assert!(
14126 c.validate_edicao().is_ok(),
14127 "validate_edicao must accept edicao == Some(\"2026\") \
14128 (the canonical 4-digit-ASCII-decimal-year shape)",
14129 );
14130 }
14131
14132 #[test]
14133 fn edicao_projects_option_str_by_borrow() {
14134 // The by-borrow pin: [`Caixa::edicao`] returns
14135 // `Option<&str>` by borrow — the `&str` borrows the underlying
14136 // `String` storage of the `Option<String>` slot and the
14137 // accessor must not allocate a fresh `String` on every call.
14138 // Peer of the [`Caixa::licenca`] (6d5bc28),
14139 // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
14140 // (3f16e2f) by-borrow pins on the peer outer top-level
14141 // [`Caixa`] `Option<&str>`-return axes, and of the
14142 // per-`:placement`
14143 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
14144 // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
14145 // return axis, extended onto the fourth and final outer top-
14146 // level [`Caixa`] universal-axis `Option<&str>` shape — the
14147 // accessor's returned `&str` must borrow from `&self` (the
14148 // returned reference's lifetime is tied to `&self`), and
14149 // calling the accessor twice on the same [`Caixa`] must yield
14150 // the same `Option<&str>` verbatim (idempotent, no side
14151 // effects on `&self`).
14152 //
14153 // Pins against a future silent detour that returned an owned
14154 // `Option<String>` (which would type-check but silently
14155 // allocate on every call, breaking the zero-cost projection
14156 // every peer sibling accessor carries), or a one-arm-only
14157 // accessor that returned a saturating value on some sentinel
14158 // input (breaking the pass-through invariant the sibling
14159 // required-scalar accessors carry).
14160 for edicao in [None, Some(""), Some("2026"), Some("2018")] {
14161 let c = caixa_with_edicao(edicao);
14162 let first = c.edicao();
14163 let second = c.edicao();
14164 assert_eq!(
14165 first, second,
14166 "Caixa::edicao must be idempotent — two successive \
14167 calls on the same &self must return the same \
14168 Option<&str>",
14169 );
14170 assert_eq!(
14171 first, edicao,
14172 "Caixa::edicao must return :edicao verbatim by \
14173 borrow — got {first:?}, expected {edicao:?}",
14174 );
14175 }
14176 }
14177
14178 #[test]
14179 fn nome_returns_nome_byte_string_verbatim_across_permutations() {
14180 // The canonical per-`Caixa` `:nome` universal-axis DNS-1123-
14181 // label caixa-identity scalar pin: [`Caixa::nome`] must return
14182 // the `:nome` typed `String` verbatim as `&str`, byte-equal to
14183 // the raw field access across every representative value in
14184 // the accept-set — the canonical `"demo"` template baseline
14185 // (the same `feira init`-scaffolded default the sibling
14186 // `validate_nome_accepts_canonical_template` positive-control
14187 // gate pins), plus every sibling per-typed-slot atom accessor's
14188 // canonical positive-arm byte-string (`"catalog"` per
14189 // [`crate::aplicacao::Membro::nome`], `"cart"` per the peer
14190 // per-`:contratos` `:de`, `"hello-rio"` per the canonical
14191 // `caixa-helm`/`caixa-flux` cross-crate integration-test
14192 // fixture, `"checkout"` per the M3 mesh-slot Aplicacao
14193 // canonical example), plus every past-the-guard sentinel for
14194 // the `NomeEmpty` / `NomeInvalid` / `NomeChartNameBudgetExceeded`
14195 // refusal cases (`""`, `"Bad_Name"`, `"a"` × 56 — 56 bytes fits
14196 // the bare DNS-1123 63-byte cap but overflows the joint
14197 // `lareira-<nome>` chart-name budget the sibling
14198 // [`Caixa::validate_nome_chart_name_budget`] gate closes on).
14199 //
14200 // The past-the-guard sentinels pin the accessor doesn't
14201 // silently absorb the refusal cases into a template-derived
14202 // fallback (a future `.nome().is_empty().then(|| "demo")`
14203 // collapse would silently absorb the `NomeEmpty` refusal at
14204 // the accessor boundary and the validate gate would accept a
14205 // struct-literal `Caixa { nome: "".into(), .. }` — the pin
14206 // catches that at caixa-core build time).
14207 //
14208 // First outer top-level [`Caixa`] `&str`-return required-
14209 // scalar accessor pin — opens the "outer [`Caixa`] `&str`
14210 // required-scalar" projection pattern the sibling per-`Caixa`
14211 // `:versao` future lift folds on. Sibling in shape to the peer
14212 // per-`:membros` [`crate::aplicacao::Membro::nome`] (4a32abf)
14213 // required-`String`-carry accessor pin on the sibling per-
14214 // sub-struct required-axis, extended onto the outer top-level
14215 // [`Caixa`] universal-axis required-`String`-carry axis.
14216 for nome in [
14217 "demo",
14218 "catalog",
14219 "cart",
14220 "hello-rio",
14221 "checkout",
14222 "",
14223 "Bad_Name",
14224 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
14225 ] {
14226 let c = caixa_with_nome(nome);
14227 assert_eq!(
14228 c.nome(),
14229 nome,
14230 "Caixa::nome must return :nome verbatim (got {}, \
14231 expected {nome})",
14232 c.nome(),
14233 );
14234 assert_eq!(
14235 c.nome(),
14236 c.nome.as_str(),
14237 "Caixa::nome must byte-equal the raw .nome field \
14238 access across every value in the String accept-set",
14239 );
14240 }
14241 }
14242
14243 #[test]
14244 fn validate_nome_empty_arm_routes_through_accessor() {
14245 // Composition pin: [`Caixa::validate_nome`]'s empty-arm must
14246 // key off [`Caixa::nome`], not the raw `.nome` field access.
14247 // Structurally: a `Caixa { nome: "".into(), .. }` must surface
14248 // the `NomeEmpty` refusal exactly, and the canonical `"demo"`
14249 // template baseline (the peer positive-arm the sibling
14250 // `validate_nome_accepts_canonical_template` gate carves out)
14251 // must pass validate. The pair jointly pins the accessor +
14252 // validate-gate composition: any future silent detour that
14253 // had the accessor return a fresh `"demo"` on the empty arm
14254 // (a `.nome().is_empty().then(|| "demo")` fallback collapse)
14255 // would silently absorb the `NomeEmpty` refusal at the
14256 // accessor boundary and the validate gate would accept a
14257 // struct-literal `Caixa { nome: "".into(), .. }` — the
14258 // composition pin catches that at caixa-core build time.
14259 //
14260 // Peer of the sibling per-`Caixa`
14261 // `validate_licenca_empty_arm_routes_through_accessor` (6d5bc28)
14262 // / `validate_repositorio_empty_arm_routes_through_accessor`
14263 // (cc7332d) / `validate_descricao_empty_arm_routes_through_accessor`
14264 // (3f16e2f) / `validate_edicao_empty_arm_routes_through_accessor`
14265 // (2641cbd) composition pins on the sibling outer top-level
14266 // [`Caixa`] `Option<&str>` axes — same "the validate /
14267 // shape-gate predicate must route through the substrate-
14268 // primitive typed dispatch" discipline extended onto the peer
14269 // outer top-level [`Caixa`] required-`&str` composition axis.
14270 let c = caixa_with_nome("");
14271 assert!(
14272 matches!(c.validate_nome(), Err(ManifestError::NomeEmpty)),
14273 "validate_nome must reject nome == \"\" with NomeEmpty — \
14274 the accessor and the validate gate must route through the \
14275 same substrate-primitive typed dispatch on the :nome \
14276 empty-arm",
14277 );
14278 let c = caixa_with_nome("demo");
14279 assert!(
14280 c.validate_nome().is_ok(),
14281 "validate_nome must accept nome == \"demo\" (the canonical \
14282 DNS-1123-label template baseline)",
14283 );
14284 }
14285
14286 #[test]
14287 fn nome_projects_str_by_borrow() {
14288 // The by-borrow pin: [`Caixa::nome`] returns `&str` by borrow
14289 // — the `&str` borrows the underlying `String` storage of the
14290 // required `nome` slot and the accessor must not allocate a
14291 // fresh `String` on every call. Peer of the [`Caixa::licenca`]
14292 // (6d5bc28) / [`Caixa::repositorio`] (cc7332d) /
14293 // [`Caixa::descricao`] (3f16e2f) / [`Caixa::edicao`] (2641cbd)
14294 // by-borrow pins on the peer outer top-level [`Caixa`]
14295 // `Option<&str>`-return axes, extended onto the first outer
14296 // top-level [`Caixa`] required-`&str`-return axis — the
14297 // accessor's returned `&str` must borrow from `&self` (the
14298 // returned reference's lifetime is tied to `&self`), and
14299 // calling the accessor twice on the same [`Caixa`] must yield
14300 // the same `&str` verbatim (idempotent, no side effects on
14301 // `&self`).
14302 //
14303 // Pins against a future silent detour that returned an owned
14304 // `String` (which would type-check but silently allocate on
14305 // every call, breaking the zero-cost projection every peer
14306 // sibling accessor carries), an accidental
14307 // `.nome.to_lowercase()` detour that returned a fresh
14308 // allocation through an already-DNS-1123-lowercase-only
14309 // string (breaking a future `const fn` regression), or a
14310 // one-arm-only accessor that returned a canonicalized value
14311 // on some sentinel input (breaking the pass-through invariant
14312 // the sibling required-scalar accessors carry).
14313 for nome in ["demo", "catalog", "hello-rio", "checkout"] {
14314 let c = caixa_with_nome(nome);
14315 let first = c.nome();
14316 let second = c.nome();
14317 assert_eq!(
14318 first, second,
14319 "Caixa::nome must be idempotent — two successive calls \
14320 on the same &self must return the same &str",
14321 );
14322 assert_eq!(
14323 first, nome,
14324 "Caixa::nome must return :nome verbatim by borrow — \
14325 got {first}, expected {nome}",
14326 );
14327 }
14328 }
14329
14330 #[test]
14331 fn versao_returns_versao_byte_string_verbatim_across_permutations() {
14332 // The canonical per-`Caixa` `:versao` universal-axis SemVer-2
14333 // pinned-version scalar pin: [`Caixa::versao`] must return the
14334 // `:versao` typed `String` verbatim as `&str`, byte-equal to the
14335 // raw `.versao` field access across every representative value
14336 // in the accept-set — the canonical `"0.1.0"` template baseline
14337 // (the same `feira init`-scaffolded default the sibling
14338 // `validate_versao_accepts_canonical_template` positive-control
14339 // gate pins), plus every canonical SemVer-2 shape the sibling
14340 // `validate_versao_accepts_canonical_forms` positive-arm sweep
14341 // covers (`"0.0.0"`, `"1.0.0"`, `"0.2.0-rc.1"`,
14342 // `"1.0.0-alpha.0"`, `"1.0.0+build.42"`, `"1.0.0-rc.1+build.42"`,
14343 // `"10.20.30"`), plus every past-the-guard sentinel for the
14344 // `VersaoEmpty` / `VersaoInvalid` refusal cases (`""` the empty
14345 // arm, `"v0.1.0"` the git-tag-shape-leak footgun, `"0.1"` the
14346 // missing-patch footgun, `"^0.1"` the requirement-shape-leak
14347 // footgun, `"0.1.0.0"` the four-part-Java-convention footgun,
14348 // `"latest"` the docker-tag-shape footgun — the sentinels pin
14349 // the accessor doesn't silently absorb the refusal cases into a
14350 // template-derived fallback like `"0.1.0"`).
14351 //
14352 // The past-the-guard sentinels pin the accessor doesn't silently
14353 // absorb the refusal cases into a template-derived fallback (a
14354 // future `.versao().is_empty().then(|| "0.1.0")` collapse would
14355 // silently absorb the `VersaoEmpty` refusal at the accessor
14356 // boundary and the validate gate would accept a struct-literal
14357 // `Caixa { versao: "".into(), .. }` — the pin catches that at
14358 // caixa-core build time).
14359 //
14360 // Second outer top-level [`Caixa`] `&str`-return required-scalar
14361 // accessor pin — folds on the "outer [`Caixa`] `&str` required-
14362 // scalar" projection pattern the sibling per-`Caixa`
14363 // [`Caixa::nome`] (e6b7d97) opened. Sibling in shape to the peer
14364 // per-`:membros` [`crate::aplicacao::Membro::versao_requirement`]
14365 // (4127bb6) / per-`:children`
14366 // [`crate::supervisor::ChildSpec::versao_requirement`] (2c053c8)
14367 // / per-`:upgrade-from`
14368 // [`crate::UpgradeFromEntry::prior_versao`] (75d27a8) per-sub-
14369 // struct `:versao`-shaped `&str`-return accessor pins on the
14370 // sibling per-typed-slot version-carrier axes, extended onto the
14371 // second outer top-level [`Caixa`] universal-axis required-
14372 // `String`-carry axis so the two universal-axis identity-
14373 // carrying scalars every `defcaixa` form supplies (`:nome` +
14374 // `:versao`) share the same "one typed dispatch per axis" pin
14375 // discipline.
14376 for versao in [
14377 "0.1.0",
14378 "0.0.0",
14379 "1.0.0",
14380 "0.2.0-rc.1",
14381 "1.0.0-alpha.0",
14382 "1.0.0+build.42",
14383 "1.0.0-rc.1+build.42",
14384 "10.20.30",
14385 "",
14386 "v0.1.0",
14387 "0.1",
14388 "^0.1",
14389 "0.1.0.0",
14390 "latest",
14391 ] {
14392 let c = caixa_with_versao(versao);
14393 assert_eq!(
14394 c.versao(),
14395 versao,
14396 "Caixa::versao must return :versao verbatim (got {}, \
14397 expected {versao})",
14398 c.versao(),
14399 );
14400 assert_eq!(
14401 c.versao(),
14402 c.versao.as_str(),
14403 "Caixa::versao must byte-equal the raw .versao field \
14404 access across every value in the String accept-set",
14405 );
14406 }
14407 }
14408
14409 #[test]
14410 fn validate_versao_empty_arm_routes_through_accessor() {
14411 // Composition pin: [`Caixa::validate_versao`]'s empty-arm gate
14412 // must key off [`Caixa::versao`], not the raw `.versao` field
14413 // access. Structurally: a `Caixa { versao: "".into(), .. }` must
14414 // surface the `VersaoEmpty` refusal exactly, and the canonical
14415 // `"0.1.0"` template baseline (the peer positive-arm the sibling
14416 // `validate_versao_accepts_canonical_template` gate carves out)
14417 // must pass validate. The pair jointly pins the accessor +
14418 // validate-gate composition: any future silent detour that had
14419 // the accessor return a fresh `"0.1.0"` on the empty arm
14420 // (a `.versao().is_empty().then(|| "0.1.0")` fallback collapse)
14421 // would silently absorb the `VersaoEmpty` refusal at the
14422 // accessor boundary and the validate gate would accept a
14423 // struct-literal `Caixa { versao: "".into(), .. }` — the
14424 // composition pin catches that at caixa-core build time.
14425 //
14426 // Peer of the sibling per-`Caixa`
14427 // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97)
14428 // composition pin on the sibling outer top-level [`Caixa`]
14429 // required-`&str` universal-axis surface — same "the validate /
14430 // shape-gate predicate must route through the substrate-
14431 // primitive typed dispatch" discipline extended onto the peer
14432 // outer top-level [`Caixa`] required-`&str` universal-axis
14433 // pinned-version composition axis, closing the second
14434 // coordinate of the "one canonical typed dispatch per per-Caixa
14435 // required-`&str` universal-axis" discipline.
14436 let c = caixa_with_versao("");
14437 assert!(
14438 matches!(c.validate_versao(), Err(ManifestError::VersaoEmpty)),
14439 "validate_versao must reject versao == \"\" with VersaoEmpty — \
14440 the accessor and the validate gate must route through the \
14441 same substrate-primitive typed dispatch on the :versao \
14442 empty-arm",
14443 );
14444 let c = caixa_with_versao("0.1.0");
14445 assert!(
14446 c.validate_versao().is_ok(),
14447 "validate_versao must accept versao == \"0.1.0\" (the \
14448 canonical SemVer-2 template baseline)",
14449 );
14450 }
14451
14452 #[test]
14453 fn versao_projects_str_by_borrow() {
14454 // The by-borrow pin: [`Caixa::versao`] returns `&str` by borrow
14455 // — the `&str` borrows the underlying `String` storage of the
14456 // required `versao` slot and the accessor must not allocate a
14457 // fresh `String` on every call. Peer of the [`Caixa::nome`]
14458 // (e6b7d97) by-borrow pin on the sibling outer top-level
14459 // [`Caixa`] required-`&str`-return axis, extended onto the
14460 // second outer top-level [`Caixa`] required-`&str`-return
14461 // universal-axis pinned-version surface — the accessor's
14462 // returned `&str` must borrow from `&self` (the returned
14463 // reference's lifetime is tied to `&self`), and calling the
14464 // accessor twice on the same [`Caixa`] must yield the same
14465 // `&str` verbatim (idempotent, no side effects on `&self`).
14466 //
14467 // Pins against a future silent detour that returned an owned
14468 // `String` (which would type-check but silently allocate on
14469 // every call, breaking the zero-cost projection every peer
14470 // sibling accessor carries), an accidental
14471 // `semver::Version::parse(&self.versao).unwrap().to_string()`
14472 // detour that returned a canonicalized fresh allocation through
14473 // an already-canonical byte-string (breaking a future `const fn`
14474 // regression and silently absorbing the `VersaoInvalid` refusal
14475 // at the accessor boundary), or a one-arm-only accessor that
14476 // returned a canonicalized value on some sentinel input
14477 // (breaking the pass-through invariant the sibling required-
14478 // scalar accessors carry).
14479 for versao in ["0.1.0", "1.0.0", "0.2.0-rc.1", "1.0.0+build.42"] {
14480 let c = caixa_with_versao(versao);
14481 let first = c.versao();
14482 let second = c.versao();
14483 assert_eq!(
14484 first, second,
14485 "Caixa::versao must be idempotent — two successive \
14486 calls on the same &self must return the same &str",
14487 );
14488 assert_eq!(
14489 first, versao,
14490 "Caixa::versao must return :versao verbatim by borrow \
14491 — got {first}, expected {versao}",
14492 );
14493 }
14494 }
14495
14496 fn caixa_with_kind(kind: CaixaKind) -> Caixa {
14497 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14498 c.kind = kind;
14499 c
14500 }
14501
14502 #[test]
14503 fn kind_returns_kind_variant_verbatim_across_permutations() {
14504 // The canonical per-`Caixa` `:kind` universal-axis closed-set-
14505 // enum discriminant pin: [`Caixa::kind`] must return the `:kind`
14506 // typed [`CaixaKind`] variant verbatim by `Copy`, byte-equal to
14507 // the raw `.kind` field access across every variant in the
14508 // closed accept-set (`Biblioteca` — the library kind that
14509 // exports lisp forms; `Binario` — the nix-built executable kind
14510 // under `exe/`; `Servico` — the wasm-component daemon kind
14511 // under `servicos/`; `Supervisor` — the OTP-shaped hierarchical
14512 // reconciliation kind; `Aplicacao` — the M3 typed-mesh
14513 // composition kind).
14514 //
14515 // Pins against a future silent detour that re-derived the kind
14516 // from a peer axis (an accidental fallback to
14517 // `if !servicos.is_empty() { Servico } else if
14518 // !membros.is_empty() { Aplicacao } else { Biblioteca }`
14519 // collapse that read the code-surface / mesh-slot columns into
14520 // the kind discriminator), a variant remap the operator
14521 // authors on one consumer without the other, or a stale-derive
14522 // detour that substituted [`CaixaKind::Biblioteca`] as the
14523 // default when the field held any other variant (which would
14524 // silently collapse the distinction between "author explicitly
14525 // declared `:kind Servico`" and "author declared any other
14526 // kind" every downstream renderer-dispatch site depends on).
14527 //
14528 // First outer top-level [`Caixa`] `Copy`-return required-enum-
14529 // discriminant accessor pin — opens the "outer [`Caixa`]
14530 // `Copy`-return required-discriminant" projection pattern.
14531 // Sibling in shape to the peer per-`:supervisor`
14532 // [`crate::supervisor::SupervisorSpec::estrategia`] (eafb619),
14533 // per-`:placement` [`crate::aplicacao::Placement::estrategia`]
14534 // (921fe1b), and per-`:children`
14535 // [`crate::supervisor::ChildSpec::restart`] (dfb4a81)
14536 // `Copy`-return closed-set-enum discriminant accessor pins on
14537 // the sibling nested-spec typed-slot discriminator axes,
14538 // extended here to the outer top-level [`Caixa`] universal-
14539 // axis surface.
14540 for kind in [
14541 CaixaKind::Biblioteca,
14542 CaixaKind::Binario,
14543 CaixaKind::Servico,
14544 CaixaKind::Supervisor,
14545 CaixaKind::Aplicacao,
14546 ] {
14547 let c = caixa_with_kind(kind);
14548 assert_eq!(
14549 c.kind(),
14550 kind,
14551 "Caixa::kind must return :kind verbatim (got {:?}, \
14552 expected {kind:?})",
14553 c.kind(),
14554 );
14555 assert_eq!(
14556 c.kind(),
14557 c.kind,
14558 "Caixa::kind accessor and .kind field access must \
14559 byte-equal — the accessor is the substrate-primitive \
14560 typed dispatch every downstream kind-gate consumer \
14561 must route through",
14562 );
14563 }
14564 }
14565
14566 #[test]
14567 fn require_kind_reads_through_lifted_kind_accessor() {
14568 // Two-consumer coherence pin: the [`crate::render::require_kind`]
14569 // entry-gate predicate (the canonical two-line
14570 // `require_kind(caixa, Servico)?` prelude every per-Servico /
14571 // per-Aplicacao renderer runs at its entry-point) and the
14572 // sibling [`crate::render::KindMismatch`] error carrier's
14573 // `actual:` field (which names the offending caixa's variant
14574 // in the diagnostic) must both key off the lifted accessor, so
14575 // any future rebrand on the typed slot's reader shape lands at
14576 // exactly one place. Pins the two-site coherence by exercising
14577 // every off-diagonal `(actual, expected)` pair across the
14578 // closed accept-set — the `KindMismatch { actual, expected }`
14579 // surfaced on the mismatch arm must byte-equal the pair the
14580 // accessor returns for each side.
14581 //
14582 // Peer of the sibling per-`:placement`
14583 // `validate_placement_reads_through_lifted_estrategia_accessor`
14584 // (921fe1b) two-arm consumer-coherence pin on the M3 mesh-slot
14585 // `Copy`-return discriminant axis — same "the entry-gate
14586 // predicate and the error carrier's `actual:` field must route
14587 // through the substrate-primitive typed dispatch" discipline
14588 // extended onto the outer top-level [`Caixa`] universal-axis
14589 // discriminant surface.
14590 for expected in [
14591 CaixaKind::Biblioteca,
14592 CaixaKind::Binario,
14593 CaixaKind::Servico,
14594 CaixaKind::Supervisor,
14595 CaixaKind::Aplicacao,
14596 ] {
14597 for actual in [
14598 CaixaKind::Biblioteca,
14599 CaixaKind::Binario,
14600 CaixaKind::Servico,
14601 CaixaKind::Supervisor,
14602 CaixaKind::Aplicacao,
14603 ] {
14604 let c = caixa_with_kind(actual);
14605 let result = crate::render::require_kind(&c, expected);
14606 if expected == actual {
14607 assert!(
14608 result.is_ok(),
14609 "require_kind must accept when actual == expected \
14610 (actual={actual:?}, expected={expected:?})",
14611 );
14612 } else {
14613 let err = result.expect_err("require_kind must reject when actual != expected");
14614 assert_eq!(
14615 err.actual,
14616 c.kind(),
14617 "KindMismatch.actual must byte-equal Caixa::kind() \
14618 — the error carrier's `actual:` field reads \
14619 through the lifted accessor",
14620 );
14621 assert_eq!(
14622 err.expected, expected,
14623 "KindMismatch.expected must byte-equal the \
14624 expected variant passed to require_kind",
14625 );
14626 }
14627 }
14628 }
14629 }
14630
14631 #[test]
14632 fn aplicacao_view_kind_gate_routes_through_accessor() {
14633 // Composition pin: [`Caixa::aplicacao_view`]'s kind-gate arm
14634 // must key off [`Caixa::kind`], not the raw `.kind` field
14635 // access. Structurally: a `Caixa { kind: X, .. }` for any
14636 // non-`Aplicacao` variant must fold to `None` on the
14637 // `aplicacao_view` composer (the "kind mismatch → no typed
14638 // view" contract every downstream Aplicacao consumer keys off
14639 // via `?`), and a `Caixa { kind: Aplicacao, .. }` must fold to
14640 // `Some(_)`. The pair jointly pins the accessor + view-gate
14641 // composition: any future silent detour that had the accessor
14642 // return a fresh [`CaixaKind::Aplicacao`] on some sentinel
14643 // input would silently absorb the kind-mismatch case at the
14644 // accessor boundary and every per-Aplicacao renderer would
14645 // silently render a non-Aplicacao caixa's mesh slots — the
14646 // composition pin catches that at caixa-core build time.
14647 //
14648 // Peer of the sibling per-`Caixa`
14649 // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97) /
14650 // `validate_versao_empty_arm_routes_through_accessor` (20c0539)
14651 // composition pins on the sibling outer top-level [`Caixa`]
14652 // required-`&str` universal-axis surfaces — same "the
14653 // composer / validate gate must route through the substrate-
14654 // primitive typed dispatch" discipline extended onto the
14655 // outer top-level [`Caixa`] `Copy`-return required-
14656 // discriminant composition axis.
14657 for kind in [
14658 CaixaKind::Biblioteca,
14659 CaixaKind::Binario,
14660 CaixaKind::Servico,
14661 CaixaKind::Supervisor,
14662 ] {
14663 let c = caixa_with_kind(kind);
14664 assert!(
14665 c.aplicacao_view().is_none(),
14666 "aplicacao_view must return None on non-Aplicacao \
14667 kind {kind:?} — the composer's kind-gate must route \
14668 through Caixa::kind()",
14669 );
14670 }
14671 let c = caixa_with_kind(CaixaKind::Aplicacao);
14672 assert!(
14673 c.aplicacao_view().is_some(),
14674 "aplicacao_view must return Some on kind Aplicacao — \
14675 the composer's kind-gate must accept the matching arm \
14676 through Caixa::kind()",
14677 );
14678 }
14679
14680 #[test]
14681 fn supervisor_view_kind_gate_routes_through_accessor() {
14682 // Composition pin (mirror of the sibling
14683 // `aplicacao_view_kind_gate_routes_through_accessor` on the
14684 // second `_view` composer): [`Caixa::supervisor_view`]'s kind-
14685 // gate arm must key off [`Caixa::kind`], not the raw `.kind`
14686 // field access. A `Caixa { kind: X, .. }` for any non-
14687 // `Supervisor` variant must fold to `None` on the
14688 // `supervisor_view` composer, and a `Caixa { kind:
14689 // Supervisor, .. }` must fold to `Some(_)`. Same peer
14690 // composition pin discipline on the second `_view` composer
14691 // axis.
14692 for kind in [
14693 CaixaKind::Biblioteca,
14694 CaixaKind::Binario,
14695 CaixaKind::Servico,
14696 CaixaKind::Aplicacao,
14697 ] {
14698 let c = caixa_with_kind(kind);
14699 assert!(
14700 c.supervisor_view().is_none(),
14701 "supervisor_view must return None on non-Supervisor \
14702 kind {kind:?} — the composer's kind-gate must route \
14703 through Caixa::kind()",
14704 );
14705 }
14706 let mut c = caixa_with_kind(CaixaKind::Supervisor);
14707 // A Supervisor caixa needs a strategy + at least one child to
14708 // fold to a Some(_) that also validates; the composer itself
14709 // requires only the kind arm, so bare kind flip is enough to
14710 // pin the `Some(_)` return, but we populate the minimum
14711 // supervisor shape so a future strengthening of the composer
14712 // to reject an empty spec doesn't false-positive this pin.
14713 c.estrategia = Some(crate::supervisor::RestartStrategy::OneForOne);
14714 c.children = vec![crate::supervisor::ChildSpec {
14715 caixa: "child".into(),
14716 versao: "^0.1".into(),
14717 restart: crate::supervisor::RestartPolicy::Permanent,
14718 }];
14719 assert!(
14720 c.supervisor_view().is_some(),
14721 "supervisor_view must return Some on kind Supervisor — \
14722 the composer's kind-gate must accept the matching arm \
14723 through Caixa::kind()",
14724 );
14725 }
14726
14727 #[test]
14728 fn kind_projects_by_copy() {
14729 // The by-`Copy` pin: [`Caixa::kind`] returns a fresh
14730 // [`CaixaKind`] by `Copy` — the accessor must not borrow from
14731 // `&self` (the returned value is owned, `Copy`-projected from
14732 // the underlying [`CaixaKind`] storage; two calls on the same
14733 // [`Caixa`] must yield byte-equal values). Peer of the peer
14734 // per-`:placement` `Placement::estrategia` / per-`:supervisor`
14735 // `SupervisorSpec::estrategia` / per-`:children`
14736 // `ChildSpec::restart` `Copy`-return discriminant accessor
14737 // pins on the sibling nested-spec typed-slot discriminator
14738 // axes, extended onto the first outer top-level [`Caixa`]
14739 // required-`Copy`-return axis — pins against a future silent
14740 // detour that returned `&CaixaKind` (which would type-check
14741 // but silently constrain every consumer's callsite to a
14742 // borrow-shaped dispatch, breaking the zero-cost `Copy`
14743 // projection every peer sibling accessor carries).
14744 for kind in [
14745 CaixaKind::Biblioteca,
14746 CaixaKind::Binario,
14747 CaixaKind::Servico,
14748 CaixaKind::Supervisor,
14749 CaixaKind::Aplicacao,
14750 ] {
14751 let c = caixa_with_kind(kind);
14752 let first: CaixaKind = c.kind();
14753 let second: CaixaKind = c.kind();
14754 assert_eq!(
14755 first, second,
14756 "Caixa::kind must be idempotent — two successive \
14757 calls on the same &self must return the same \
14758 CaixaKind variant",
14759 );
14760 assert_eq!(
14761 first, kind,
14762 "Caixa::kind must return :kind verbatim by Copy — \
14763 got {first:?}, expected {kind:?}",
14764 );
14765 }
14766 }
14767
14768 // ── Caixa::autores — outer top-level &[T] slice accessor ──────────
14769
14770 #[test]
14771 fn autores_returns_autores_slice_verbatim_across_permutations() {
14772 // The canonical per-`Caixa` `:autores` universal-axis maintainer-
14773 // name-list slice pin: [`Caixa::autores`] must return the
14774 // `:autores` typed [`Vec<String>`] list verbatim as a
14775 // `&[String]`, byte-equal to the raw `self.autores.as_slice()`
14776 // access across every representative value in the accept-set —
14777 // `[]` (the "no maintainers declared" arm every existing
14778 // fixture without an `:autores` line carries), `[""]` (a past-
14779 // the-guard sentinel that pins the accessor doesn't perform a
14780 // silent `[""] → []` collapse on the empty-entry arm — validate
14781 // rejects `[""]` through `AutorEmpty` but the accessor must
14782 // ship the raw slot verbatim so a validate-time gate regression
14783 // surfaces at the caixa-helm emit boundary rather than being
14784 // silently absorbed into a maintainer-drop), `["pleme-io"]` (the
14785 // canonical single-maintainer form every `feira init` template
14786 // scaffolds), `["alice", "bob"]` (a canonical multi-maintainer
14787 // form), `["alice <alice@example.com>", "bob <bob@example.com>"]`
14788 // (the canonical RFC-5322 `<name> <email>` form the
14789 // `is_chart_maintainer_name_shape` predicate accepts), and
14790 // `["pleme-io", "pleme-io"]` (a past-the-guard duplicate
14791 // sentinel — validate rejects through `AutorDuplicate` but the
14792 // accessor must ship the raw slot verbatim).
14793 //
14794 // First outer top-level [`Caixa`] `&[T]`-return slice accessor
14795 // pin on the substrate primitive — opens the "outer [`Caixa`]
14796 // `&[T]` slice" projection pattern the sibling per-`Caixa`
14797 // `:etiquetas` / `:deps` / `:deps-dev` / `:exe` / `:bibliotecas`
14798 // / `:servicos` / `:upgrade-from` / `:children` future lifts
14799 // fold on. Sibling in shape to the peer per-`:supervisor`
14800 // [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
14801 // per-`:placement` [`crate::aplicacao::Placement::clusters`]
14802 // (a6e18d7), per-`:membros`
14803 // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
14804 // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
14805 // (0dcc926), and per-`:upgrade-from :instructions`
14806 // [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
14807 // `&[T]`-return slice accessor pins on the sibling per-M2 /
14808 // per-M3 typed-slot list axes, extended onto the outer top-
14809 // level [`Caixa`] universal-axis surface. Pins against a future
14810 // silent detour that returned an owned `Vec<String>` (which
14811 // would type-check but silently clone on every accessor call,
14812 // breaking the zero-cost projection every peer sibling slice
14813 // accessor carries), a `[""] → []` collapse (which would
14814 // silently absorb the `AutorEmpty` refusal case at the accessor
14815 // boundary), or a `["a", "a"] → ["a"]` dedup collapse (which
14816 // would silently absorb the `AutorDuplicate` refusal case at
14817 // the accessor boundary and the caixa-helm `maintainers:` fold
14818 // would silently render a dedupped list on a struct-literal
14819 // `Caixa { autores: vec!["a".into(), "a".into()], .. }`).
14820 for autores in [
14821 vec![],
14822 vec![""],
14823 vec!["pleme-io"],
14824 vec!["alice", "bob"],
14825 vec!["alice <alice@example.com>", "bob <bob@example.com>"],
14826 vec!["pleme-io", "pleme-io"],
14827 ] {
14828 let c = caixa_with_autores(autores.clone());
14829 let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
14830 assert_eq!(
14831 c.autores(),
14832 expected.as_slice(),
14833 "Caixa::autores must return :autores verbatim (got {:?}, \
14834 expected {expected:?})",
14835 c.autores(),
14836 );
14837 assert_eq!(
14838 c.autores(),
14839 c.autores.as_slice(),
14840 "Caixa::autores must byte-equal the raw \
14841 `self.autores.as_slice()` field access across every \
14842 value in the Vec<String> accept-set",
14843 );
14844 }
14845 }
14846
14847 #[test]
14848 fn validate_autores_empty_entry_arm_routes_through_accessor() {
14849 // Composition pin: [`Caixa::validate_autores`]'s per-entry
14850 // empty-arm gate must key off [`Caixa::autores`], not the raw
14851 // `&self.autores` field-borrow walk. Structurally: a
14852 // `Caixa { autores: vec!["".into()], .. }` must surface the
14853 // `AutorEmpty` refusal exactly, and a
14854 // `Caixa { autores: vec!["pleme-io".into()], .. }` (the
14855 // canonical single-maintainer form) must pass validate. The
14856 // pair jointly pins the accessor + validate-gate composition:
14857 // any future silent detour that had the accessor return an
14858 // empty slice on the `[""]` arm (a
14859 // `.iter().filter(|s| !s.is_empty()).collect()` collapse)
14860 // would silently absorb the `AutorEmpty` refusal at the
14861 // accessor boundary and the validate gate would accept a
14862 // struct-literal `Caixa { autores: vec!["".into()], .. }` —
14863 // the composition pin catches that at caixa-core build time.
14864 //
14865 // Peer of the per-`Caixa` [`Caixa::validate_licenca`] (6d5bc28)
14866 // accessor-composition pin
14867 // (`validate_licenca_empty_arm_routes_through_accessor`) on the
14868 // sibling `Option<&str>`-composition axis and the
14869 // per-`:politicas :circuit-breaker`
14870 // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
14871 // accessor-composition pin
14872 // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
14873 // on the sibling required-`u32`-composition axis — same "the
14874 // validate / shape-gate predicate must route through the
14875 // substrate-primitive typed dispatch" discipline extended onto
14876 // the outer top-level [`Caixa`] universal-axis `&[T]`-
14877 // composition surface.
14878 let c = caixa_with_autores(vec![""]);
14879 assert!(
14880 matches!(c.validate_autores(), Err(ManifestError::AutorEmpty)),
14881 "validate_autores must reject autores == vec![\"\"] with \
14882 AutorEmpty — the accessor and the validate gate must \
14883 route through the same substrate-primitive typed dispatch \
14884 on the :autores per-entry empty arm",
14885 );
14886 let c = caixa_with_autores(vec!["pleme-io"]);
14887 assert!(
14888 c.validate_autores().is_ok(),
14889 "validate_autores must accept autores == vec![\"pleme-io\"] \
14890 (the canonical single-maintainer shape every `feira init` \
14891 template scaffolds)",
14892 );
14893 }
14894
14895 #[test]
14896 fn autores_projects_slice_by_borrow() {
14897 // The by-borrow pin: [`Caixa::autores`] returns `&[String]` by
14898 // borrow — the returned slice borrows the underlying
14899 // `Vec<String>` storage of the `:autores` slot and the
14900 // accessor must not clone the backing `Vec` on every call.
14901 // Peer of the per-`:membros`
14902 // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36) /
14903 // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
14904 // (0dcc926) / per-`:placement`
14905 // [`crate::aplicacao::Placement::clusters`] (a6e18d7) /
14906 // per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
14907 // (bc92bce) by-borrow pins on the sibling per-M2 / per-M3
14908 // typed-slot `&[T]`-return axes, extended onto the outer top-
14909 // level [`Caixa`] universal-axis `&[String]` shape — the
14910 // accessor's returned slice must borrow from `&self` (the
14911 // returned reference's lifetime is tied to `&self`), and
14912 // calling the accessor twice on the same [`Caixa`] must yield
14913 // slices that are pointer-equal (the underlying byte-buffer is
14914 // the storage `Vec`'s allocation, not a fresh copy) as well as
14915 // value-equal (idempotent, no side effects on `&self`).
14916 //
14917 // Pins against a future silent detour that returned an owned
14918 // `Vec<String>` (which would type-check but silently clone on
14919 // every call, breaking the zero-cost projection every peer
14920 // sibling slice accessor carries), a `&Vec<String>` return
14921 // (which would leak the backing `Vec`'s grow/push/reserve
14922 // surface no downstream consumer reaches for), or a one-arm-
14923 // only accessor that returned a saturating value on some
14924 // sentinel input (breaking the pass-through invariant the
14925 // sibling slice accessors carry).
14926 for autores in [
14927 vec![],
14928 vec!["pleme-io"],
14929 vec!["alice", "bob"],
14930 vec!["pleme-io", "pleme-io"],
14931 ] {
14932 let c = caixa_with_autores(autores.clone());
14933 let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
14934 let first = c.autores();
14935 let second = c.autores();
14936 assert_eq!(
14937 first, second,
14938 "Caixa::autores must be idempotent — two successive \
14939 calls on the same &self must return the same \
14940 &[String]",
14941 );
14942 assert_eq!(
14943 first.as_ptr(),
14944 second.as_ptr(),
14945 "Caixa::autores must borrow the underlying Vec<String> \
14946 storage — two successive calls must return slices \
14947 with the same backing pointer (a fresh Vec<String> \
14948 clone would change the pointer on every call)",
14949 );
14950 assert_eq!(
14951 first,
14952 expected.as_slice(),
14953 "Caixa::autores must return :autores verbatim by \
14954 borrow — got {first:?}, expected {expected:?}",
14955 );
14956 }
14957 }
14958
14959 // ── Caixa::etiquetas — outer top-level &[T] slice accessor ────────
14960
14961 #[test]
14962 fn etiquetas_returns_etiquetas_slice_verbatim_across_permutations() {
14963 // The canonical per-`Caixa` `:etiquetas` universal-axis
14964 // registry-search-tag-list slice pin: [`Caixa::etiquetas`] must
14965 // return the `:etiquetas` typed [`Vec<String>`] list verbatim
14966 // as a `&[String]`, byte-equal to the raw
14967 // `self.etiquetas.as_slice()` access across every representative
14968 // value in the accept-set — `[]` (the "no tags declared" arm
14969 // every existing fixture without an `:etiquetas` line carries),
14970 // `[""]` (a past-the-guard sentinel that pins the accessor
14971 // doesn't perform a silent `[""] → []` collapse on the empty-
14972 // entry arm — validate rejects `[""]` through `EtiquetaEmpty`
14973 // but the accessor must ship the raw slot verbatim so a
14974 // validate-time gate regression surfaces at the caixa-helm emit
14975 // boundary rather than being silently absorbed into a keyword-
14976 // drop), `["demo"]` (the canonical single-tag form every
14977 // `feira init` template scaffolds), `["example", "aplicacao",
14978 // "mesh", "ecommerce", "demo"]` (the canonical multi-tag form
14979 // the checkout-aplicacao fixture emits), and `["demo", "demo"]`
14980 // (a past-the-guard duplicate sentinel — validate rejects
14981 // through `EtiquetaDuplicate` but the accessor must ship the
14982 // raw slot verbatim so the caixa-helm `BTreeSet::collect` dedup
14983 // at chart-render time isn't silently promoted into the
14984 // accessor boundary and struct-literal
14985 // `Caixa { etiquetas: vec!["demo".into(), "demo".into()], .. }`
14986 // fixtures continue to expose the duplicate at the accessor).
14987 //
14988 // Second outer top-level [`Caixa`] `&[T]`-return slice accessor
14989 // pin on the substrate primitive — folds on the "outer
14990 // [`Caixa`] `&[T]` slice" projection pattern
14991 // `autores_returns_autores_slice_verbatim_across_permutations`
14992 // (b5d813f) opened, sibling in shape and idiom. Pins against a
14993 // future silent detour that returned an owned `Vec<String>`
14994 // (which would type-check but silently clone on every accessor
14995 // call, breaking the zero-cost projection every peer sibling
14996 // slice accessor carries), a `[""] → []` collapse (which would
14997 // silently absorb the `EtiquetaEmpty` refusal case at the
14998 // accessor boundary), or a `["a", "a"] → ["a"]` dedup collapse
14999 // (which would silently absorb the `EtiquetaDuplicate` refusal
15000 // case at the accessor boundary — the caixa-helm chart-render
15001 // `BTreeSet::collect` dedup is downstream of the accessor and
15002 // must not be silently promoted into it).
15003 for etiquetas in [
15004 vec![],
15005 vec![""],
15006 vec!["demo"],
15007 vec!["example", "aplicacao", "mesh", "ecommerce", "demo"],
15008 vec!["demo", "demo"],
15009 ] {
15010 let c = caixa_with_etiquetas(etiquetas.clone());
15011 let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
15012 assert_eq!(
15013 c.etiquetas(),
15014 expected.as_slice(),
15015 "Caixa::etiquetas must return :etiquetas verbatim (got \
15016 {:?}, expected {expected:?})",
15017 c.etiquetas(),
15018 );
15019 assert_eq!(
15020 c.etiquetas(),
15021 c.etiquetas.as_slice(),
15022 "Caixa::etiquetas must byte-equal the raw \
15023 `self.etiquetas.as_slice()` field access across every \
15024 value in the Vec<String> accept-set",
15025 );
15026 }
15027 }
15028
15029 #[test]
15030 fn validate_etiquetas_empty_entry_arm_routes_through_accessor() {
15031 // Composition pin: [`Caixa::validate_etiquetas`]'s per-entry
15032 // empty-arm gate must key off [`Caixa::etiquetas`], not the raw
15033 // `&self.etiquetas` field-borrow walk. Structurally: a
15034 // `Caixa { etiquetas: vec!["".into()], .. }` must surface the
15035 // `EtiquetaEmpty` refusal exactly, and a
15036 // `Caixa { etiquetas: vec!["demo".into()], .. }` (the canonical
15037 // single-tag form) must pass validate. The pair jointly pins
15038 // the accessor + validate-gate composition: any future silent
15039 // detour that had the accessor return an empty slice on the
15040 // `[""]` arm (a
15041 // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
15042 // silently absorb the `EtiquetaEmpty` refusal at the accessor
15043 // boundary and the validate gate would accept a struct-literal
15044 // `Caixa { etiquetas: vec!["".into()], .. }` — the composition
15045 // pin catches that at caixa-core build time.
15046 //
15047 // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
15048 // through_accessor` (b5d813f) accessor-composition pin on the
15049 // sibling `&[T]`-composition axis — same "the validate / shape-
15050 // gate predicate must route through the substrate-primitive
15051 // typed dispatch" discipline extended onto the sibling outer
15052 // top-level [`Caixa`] `&[T]`-composition surface.
15053 let c = caixa_with_etiquetas(vec![""]);
15054 assert!(
15055 matches!(c.validate_etiquetas(), Err(ManifestError::EtiquetaEmpty)),
15056 "validate_etiquetas must reject etiquetas == vec![\"\"] \
15057 with EtiquetaEmpty — the accessor and the validate gate \
15058 must route through the same substrate-primitive typed \
15059 dispatch on the :etiquetas per-entry empty arm",
15060 );
15061 let c = caixa_with_etiquetas(vec!["demo"]);
15062 assert!(
15063 c.validate_etiquetas().is_ok(),
15064 "validate_etiquetas must accept etiquetas == vec![\"demo\"] \
15065 (the canonical single-tag shape every `feira init` \
15066 template scaffolds)",
15067 );
15068 }
15069
15070 #[test]
15071 fn etiquetas_projects_slice_by_borrow() {
15072 // The by-borrow pin: [`Caixa::etiquetas`] returns `&[String]`
15073 // by borrow — the returned slice borrows the underlying
15074 // `Vec<String>` storage of the `:etiquetas` slot and the
15075 // accessor must not clone the backing `Vec` on every call.
15076 // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
15077 // (b5d813f) by-borrow pin on the sibling outer top-level
15078 // [`Caixa`] `&[String]`-return axis — the accessor's returned
15079 // slice must borrow from `&self` (the returned reference's
15080 // lifetime is tied to `&self`), and calling the accessor twice
15081 // on the same [`Caixa`] must yield slices that are pointer-
15082 // equal (the underlying byte-buffer is the storage `Vec`'s
15083 // allocation, not a fresh copy) as well as value-equal
15084 // (idempotent, no side effects on `&self`).
15085 //
15086 // Pins against a future silent detour that returned an owned
15087 // `Vec<String>` (which would type-check but silently clone on
15088 // every call, breaking the zero-cost projection every peer
15089 // sibling slice accessor carries), a `&Vec<String>` return
15090 // (which would leak the backing `Vec`'s grow/push/reserve
15091 // surface no downstream consumer reaches for), or a one-arm-
15092 // only accessor that returned a saturating value on some
15093 // sentinel input (breaking the pass-through invariant the
15094 // sibling slice accessors carry).
15095 for etiquetas in [
15096 vec![],
15097 vec!["demo"],
15098 vec!["example", "aplicacao", "mesh"],
15099 vec!["demo", "demo"],
15100 ] {
15101 let c = caixa_with_etiquetas(etiquetas.clone());
15102 let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
15103 let first = c.etiquetas();
15104 let second = c.etiquetas();
15105 assert_eq!(
15106 first, second,
15107 "Caixa::etiquetas must be idempotent — two successive \
15108 calls on the same &self must return the same \
15109 &[String]",
15110 );
15111 assert_eq!(
15112 first.as_ptr(),
15113 second.as_ptr(),
15114 "Caixa::etiquetas must borrow the underlying \
15115 Vec<String> storage — two successive calls must \
15116 return slices with the same backing pointer (a fresh \
15117 Vec<String> clone would change the pointer on every \
15118 call)",
15119 );
15120 assert_eq!(
15121 first,
15122 expected.as_slice(),
15123 "Caixa::etiquetas must return :etiquetas verbatim by \
15124 borrow — got {first:?}, expected {expected:?}",
15125 );
15126 }
15127 }
15128
15129 // ── Caixa::bibliotecas — outer top-level &[T] slice accessor ──────
15130
15131 #[test]
15132 fn bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations() {
15133 // The canonical per-`Caixa` `:bibliotecas` universal-axis
15134 // library-source-path-list slice pin: [`Caixa::bibliotecas`]
15135 // must return the `:bibliotecas` typed [`Vec<String>`] list
15136 // verbatim as a `&[String]`, byte-equal to the raw
15137 // `self.bibliotecas.as_slice()` access across every
15138 // representative value in the accept-set — `[]` (the "no
15139 // libraries declared" arm every `:kind` other than `Biblioteca`
15140 // + every `Biblioteca` relying on the canonical
15141 // `lib/<nome>.lisp` implicit-default path carries; the
15142 // layout's [`crate::LayoutInvariants`] `MissingLib` arm-gate
15143 // fires exactly on this empty-slot + `Biblioteca`-kind
15144 // combination), `[""]` (a past-the-guard sentinel that pins
15145 // the accessor doesn't perform a silent `[""] → []` collapse
15146 // on the empty-entry arm — validate rejects `[""]` through
15147 // `CodePathEmpty { slot: ":bibliotecas" }` but the accessor
15148 // must ship the raw slot verbatim so a validate-time gate
15149 // regression surfaces at the `feira build` phase-1 parse
15150 // boundary rather than being silently absorbed into a
15151 // library-drop), `["lib/demo.lisp"]` (the canonical single-
15152 // entry form `Caixa::template` scaffolds and every `feira init`
15153 // template emits), `["lib/demo.lisp", "lib/helpers.lisp"]`
15154 // (the canonical multi-library form the
15155 // `validate_code_paths_accepts_explicit_relative_paths_on_
15156 // every_slot` fixture emits), and `["lib/foo.lisp",
15157 // "lib/foo.lisp"]` (a past-the-guard duplicate sentinel —
15158 // validate rejects through `CodePathDuplicate { slot:
15159 // ":bibliotecas" }` per the per-slot set-not-multiset gate,
15160 // but the accessor must ship the raw slot verbatim so the
15161 // `feira build` `for entry in caixa.bibliotecas()` parse walk
15162 // sees the duplicate at the accessor boundary and struct-
15163 // literal `Caixa { bibliotecas: vec!["lib/foo.lisp".into(),
15164 // "lib/foo.lisp".into()], .. }` fixtures continue to expose
15165 // the duplicate at the accessor).
15166 //
15167 // Third outer top-level [`Caixa`] `&[T]`-return slice accessor
15168 // pin on the substrate primitive — folds on the "outer
15169 // [`Caixa`] `&[T]` slice" projection pattern
15170 // `autores_returns_autores_slice_verbatim_across_permutations`
15171 // (b5d813f) opened and
15172 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
15173 // (78c7d3c) folded on, sibling in shape and idiom. Pins
15174 // against a future silent detour that returned an owned
15175 // `Vec<String>` (which would type-check but silently clone on
15176 // every accessor call, breaking the zero-cost projection
15177 // every peer sibling slice accessor carries), a `[""] → []`
15178 // collapse (which would silently absorb the `CodePathEmpty`
15179 // refusal case at the accessor boundary), or a `["lib/foo.lisp",
15180 // "lib/foo.lisp"] → ["lib/foo.lisp"]` dedup collapse (which
15181 // would silently absorb the `CodePathDuplicate` refusal case
15182 // at the accessor boundary — the per-slot set-not-multiset
15183 // gate is downstream of the accessor and must not be silently
15184 // promoted into it).
15185 for bibliotecas in [
15186 vec![],
15187 vec![""],
15188 vec!["lib/demo.lisp"],
15189 vec!["lib/demo.lisp", "lib/helpers.lisp"],
15190 vec!["lib/foo.lisp", "lib/foo.lisp"],
15191 ] {
15192 let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
15193 let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
15194 assert_eq!(
15195 c.bibliotecas(),
15196 expected.as_slice(),
15197 "Caixa::bibliotecas must return :bibliotecas verbatim \
15198 (got {:?}, expected {expected:?})",
15199 c.bibliotecas(),
15200 );
15201 assert_eq!(
15202 c.bibliotecas(),
15203 c.bibliotecas.as_slice(),
15204 "Caixa::bibliotecas must byte-equal the raw \
15205 `self.bibliotecas.as_slice()` field access across \
15206 every value in the Vec<String> accept-set",
15207 );
15208 }
15209 }
15210
15211 #[test]
15212 fn validate_code_paths_bibliotecas_empty_arm_routes_through_accessor() {
15213 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
15214 // empty-arm gate on the `:bibliotecas` slot must key off
15215 // [`Caixa::bibliotecas`], not a divergent raw
15216 // `&self.bibliotecas` field-borrow walk. Structurally: a
15217 // `Caixa { bibliotecas: vec!["".into()], .. }` must surface
15218 // the `CodePathEmpty { slot: ":bibliotecas" }` refusal
15219 // exactly, and a `Caixa { bibliotecas: vec!["lib/demo.lisp".
15220 // into()], .. }` (the canonical single-library form
15221 // `Caixa::template` scaffolds) must pass validate. The pair
15222 // jointly pins the accessor + validate-gate composition: any
15223 // future silent detour that had the accessor return an empty
15224 // slice on the `[""]` arm (a `.iter().filter(|s|
15225 // !s.is_empty()).collect()` collapse) would silently absorb
15226 // the `CodePathEmpty` refusal at the accessor boundary and
15227 // the validate gate would accept a struct-literal
15228 // `Caixa { bibliotecas: vec!["".into()], .. }` — the
15229 // composition pin catches that at caixa-core build time.
15230 //
15231 // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
15232 // through_accessor` (b5d813f) and
15233 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
15234 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
15235 // composition axes — same "the validate / shape-gate
15236 // predicate must route through the substrate-primitive typed
15237 // dispatch" discipline extended onto the sibling outer top-
15238 // level [`Caixa`] `&[T]`-composition surface. Nominally the
15239 // in-tree `validate_code_paths` production body still keys
15240 // off the internal `[(":bibliotecas", &self.bibliotecas,
15241 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
15242 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
15243 // (the tuple's homogeneous slice-typed shape blocks a per-
15244 // element accessor swap in isolation — a future companion
15245 // lift for `:exe` and `:servicos` on the same outer-`Caixa`
15246 // `&[T]` slice-accessor axis closes that tuple onto the
15247 // triple of typed dispatches as a unit); the composition pin
15248 // catches any future accessor-side silent filter drop against
15249 // that eventual tuple-closure regardless of whether the
15250 // `:bibliotecas` slot is threaded through the accessor or the
15251 // raw field access at the tuple's construction site.
15252 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
15253 assert!(
15254 matches!(
15255 c.validate_code_paths(),
15256 Err(ManifestError::CodePathEmpty {
15257 slot: ":bibliotecas"
15258 })
15259 ),
15260 "validate_code_paths must reject bibliotecas == vec![\"\"] \
15261 with CodePathEmpty {{ slot: \":bibliotecas\" }} — the \
15262 accessor and the validate gate must route through the \
15263 same substrate-primitive typed dispatch on the \
15264 :bibliotecas per-entry empty arm",
15265 );
15266 let c = caixa_with_code_paths(vec!["lib/demo.lisp"], vec![], vec![]);
15267 assert!(
15268 c.validate_code_paths().is_ok(),
15269 "validate_code_paths must accept bibliotecas == \
15270 vec![\"lib/demo.lisp\"] (the canonical single-library \
15271 shape every `feira init` template scaffolds)",
15272 );
15273 }
15274
15275 #[test]
15276 fn bibliotecas_projects_slice_by_borrow() {
15277 // The by-borrow pin: [`Caixa::bibliotecas`] returns
15278 // `&[String]` by borrow — the returned slice borrows the
15279 // underlying `Vec<String>` storage of the `:bibliotecas` slot
15280 // and the accessor must not clone the backing `Vec` on every
15281 // call. Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
15282 // (b5d813f) and `etiquetas_projects_slice_by_borrow` (78c7d3c)
15283 // by-borrow pins on the sibling outer top-level [`Caixa`]
15284 // `&[String]`-return axes — the accessor's returned slice
15285 // must borrow from `&self` (the returned reference's lifetime
15286 // is tied to `&self`), and calling the accessor twice on the
15287 // same [`Caixa`] must yield slices that are pointer-equal
15288 // (the underlying byte-buffer is the storage `Vec`'s
15289 // allocation, not a fresh copy) as well as value-equal
15290 // (idempotent, no side effects on `&self`).
15291 //
15292 // Pins against a future silent detour that returned an owned
15293 // `Vec<String>` (which would type-check but silently clone on
15294 // every call, breaking the zero-cost projection every peer
15295 // sibling slice accessor carries), a `&Vec<String>` return
15296 // (which would leak the backing `Vec`'s grow/push/reserve
15297 // surface no downstream consumer reaches for), or a one-arm-
15298 // only accessor that returned a saturating value on some
15299 // sentinel input (breaking the pass-through invariant the
15300 // sibling slice accessors carry).
15301 for bibliotecas in [
15302 vec![],
15303 vec!["lib/demo.lisp"],
15304 vec!["lib/demo.lisp", "lib/helpers.lisp"],
15305 vec!["lib/foo.lisp", "lib/foo.lisp"],
15306 ] {
15307 let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
15308 let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
15309 let first = c.bibliotecas();
15310 let second = c.bibliotecas();
15311 assert_eq!(
15312 first, second,
15313 "Caixa::bibliotecas must be idempotent — two \
15314 successive calls on the same &self must return the \
15315 same &[String]",
15316 );
15317 assert_eq!(
15318 first.as_ptr(),
15319 second.as_ptr(),
15320 "Caixa::bibliotecas must borrow the underlying \
15321 Vec<String> storage — two successive calls must \
15322 return slices with the same backing pointer (a \
15323 fresh Vec<String> clone would change the pointer on \
15324 every call)",
15325 );
15326 assert_eq!(
15327 first,
15328 expected.as_slice(),
15329 "Caixa::bibliotecas must return :bibliotecas verbatim \
15330 by borrow — got {first:?}, expected {expected:?}",
15331 );
15332 }
15333 }
15334
15335 // ── Caixa::exe — outer top-level &[T] slice accessor ──────────────
15336
15337 #[test]
15338 fn exe_returns_exe_slice_verbatim_across_permutations() {
15339 // The canonical per-`Caixa` `:exe` universal-axis
15340 // nix-built-executable-entry-path-list slice pin: [`Caixa::exe`]
15341 // must return the `:exe` typed [`Vec<String>`] list verbatim as
15342 // a `&[String]`, byte-equal to the raw `self.exe.as_slice()`
15343 // access across every representative value in the accept-set —
15344 // `[]` (the "no executable declared" arm every `:kind` other
15345 // than `Binario` carries; the layout's [`crate::LayoutInvariants`]
15346 // `BinarioWithoutExe` arm-gate fires exactly on this empty-slot
15347 // + `Binario`-kind combination), `[""]` (a past-the-guard
15348 // sentinel that pins the accessor doesn't perform a silent
15349 // `[""] → []` collapse on the empty-entry arm — validate rejects
15350 // `[""]` through `CodePathEmpty { slot: ":exe" }` but the
15351 // accessor must ship the raw slot verbatim so a validate-time
15352 // gate regression surfaces at the layout / `feira nix` boundary
15353 // rather than being silently absorbed into an executable-drop),
15354 // `["exe/cli"]` (the canonical single-entry Binario form every
15355 // in-tree `caixa_with_code_paths` positive control uses),
15356 // `["exe/cli", "exe/serve"]` (the canonical multi-executable
15357 // form the `validate_code_paths_accepts_explicit_relative_paths_
15358 // on_every_slot` fixture emits), and `["exe/cli", "exe/cli"]`
15359 // (a past-the-guard duplicate sentinel — validate rejects
15360 // through `CodePathDuplicate { slot: ":exe" }` per the per-slot
15361 // set-not-multiset gate, but the accessor must ship the raw
15362 // slot verbatim so struct-literal `Caixa { exe: vec!["exe/cli".
15363 // into(), "exe/cli".into()], .. }` fixtures continue to expose
15364 // the duplicate at the accessor).
15365 //
15366 // Fourth outer top-level [`Caixa`] `&[T]`-return slice accessor
15367 // pin on the substrate primitive — folds on the "outer
15368 // [`Caixa`] `&[T]` slice" projection pattern
15369 // `autores_returns_autores_slice_verbatim_across_permutations`
15370 // (b5d813f) opened,
15371 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
15372 // (78c7d3c) folded on, and
15373 // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
15374 // (8a36c23) closed the universal-axis text-tag family of.
15375 // Opens the outer-`Caixa` foreign-code-slot `&[T]` sub-family
15376 // the sibling `:servicos` future lift closes onto. Pins against
15377 // a future silent detour that returned an owned `Vec<String>`
15378 // (which would type-check but silently clone on every accessor
15379 // call, breaking the zero-cost projection every peer sibling
15380 // slice accessor carries), a `[""] → []` collapse (which would
15381 // silently absorb the `CodePathEmpty` refusal case at the
15382 // accessor boundary), or an `["exe/cli", "exe/cli"] →
15383 // ["exe/cli"]` dedup collapse (which would silently absorb the
15384 // `CodePathDuplicate` refusal case at the accessor boundary —
15385 // the per-slot set-not-multiset gate is downstream of the
15386 // accessor and must not be silently promoted into it).
15387 for exe in [
15388 vec![],
15389 vec![""],
15390 vec!["exe/cli"],
15391 vec!["exe/cli", "exe/serve"],
15392 vec!["exe/cli", "exe/cli"],
15393 ] {
15394 let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
15395 let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
15396 assert_eq!(
15397 c.exe(),
15398 expected.as_slice(),
15399 "Caixa::exe must return :exe verbatim (got {:?}, \
15400 expected {expected:?})",
15401 c.exe(),
15402 );
15403 assert_eq!(
15404 c.exe(),
15405 c.exe.as_slice(),
15406 "Caixa::exe must byte-equal the raw \
15407 `self.exe.as_slice()` field access across every value \
15408 in the Vec<String> accept-set",
15409 );
15410 }
15411 }
15412
15413 #[test]
15414 fn validate_code_paths_exe_empty_arm_routes_through_accessor() {
15415 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
15416 // empty-arm gate on the `:exe` slot must key off
15417 // [`Caixa::exe`], not a divergent raw `&self.exe` field-borrow
15418 // walk. Structurally: a `Caixa { exe: vec!["".into()], .. }`
15419 // must surface the `CodePathEmpty { slot: ":exe" }` refusal
15420 // exactly, and a `Caixa { exe: vec!["exe/cli".into()], .. }`
15421 // (the canonical single-executable form every in-tree
15422 // `caixa_with_code_paths` positive control uses) must pass
15423 // validate. The pair jointly pins the accessor + validate-gate
15424 // composition: any future silent detour that had the accessor
15425 // return an empty slice on the `[""]` arm (a
15426 // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
15427 // silently absorb the `CodePathEmpty` refusal at the accessor
15428 // boundary and the validate gate would accept a struct-literal
15429 // `Caixa { exe: vec!["".into()], .. }` — the composition pin
15430 // catches that at caixa-core build time.
15431 //
15432 // Peer of the per-`Caixa`
15433 // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
15434 // (8a36c23), `validate_autores_empty_arm_routes_through_accessor`
15435 // (b5d813f), and
15436 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
15437 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
15438 // composition axes — same "the validate / shape-gate predicate
15439 // must route through the substrate-primitive typed dispatch"
15440 // discipline extended onto the sibling outer top-level [`Caixa`]
15441 // `&[T]`-composition surface. Nominally the in-tree
15442 // `validate_code_paths` production body still keys off the
15443 // internal `[(":bibliotecas", &self.bibliotecas,
15444 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
15445 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
15446 // (the tuple's homogeneous slice-typed shape blocks a per-
15447 // element accessor swap in isolation — a future companion lift
15448 // for `:servicos` on the same outer-`Caixa` `&[T]` slice-
15449 // accessor axis closes that tuple onto the triple of typed
15450 // dispatches as a unit); the composition pin catches any future
15451 // accessor-side silent filter drop against that eventual tuple-
15452 // closure regardless of whether the `:exe` slot is threaded
15453 // through the accessor or the raw field access at the tuple's
15454 // construction site.
15455 let c = caixa_with_code_paths(vec![], vec![""], vec![]);
15456 assert!(
15457 matches!(
15458 c.validate_code_paths(),
15459 Err(ManifestError::CodePathEmpty { slot: ":exe" })
15460 ),
15461 "validate_code_paths must reject exe == vec![\"\"] \
15462 with CodePathEmpty {{ slot: \":exe\" }} — the \
15463 accessor and the validate gate must route through the \
15464 same substrate-primitive typed dispatch on the \
15465 :exe per-entry empty arm",
15466 );
15467 let c = caixa_with_code_paths(vec![], vec!["exe/cli"], vec![]);
15468 assert!(
15469 c.validate_code_paths().is_ok(),
15470 "validate_code_paths must accept exe == vec![\"exe/cli\"] \
15471 (the canonical single-executable shape every in-tree \
15472 `caixa_with_code_paths` positive control uses)",
15473 );
15474 }
15475
15476 #[test]
15477 fn exe_projects_slice_by_borrow() {
15478 // The by-borrow pin: [`Caixa::exe`] returns `&[String]` by
15479 // borrow — the returned slice borrows the underlying
15480 // `Vec<String>` storage of the `:exe` slot and the accessor
15481 // must not clone the backing `Vec` on every call. Peer of the
15482 // per-`Caixa` `autores_projects_slice_by_borrow` (b5d813f),
15483 // `etiquetas_projects_slice_by_borrow` (78c7d3c), and
15484 // `bibliotecas_projects_slice_by_borrow` (8a36c23) by-borrow
15485 // pins on the sibling outer top-level [`Caixa`] `&[String]`-
15486 // return axes — the accessor's returned slice must borrow from
15487 // `&self` (the returned reference's lifetime is tied to
15488 // `&self`), and calling the accessor twice on the same
15489 // [`Caixa`] must yield slices that are pointer-equal (the
15490 // underlying byte-buffer is the storage `Vec`'s allocation,
15491 // not a fresh copy) as well as value-equal (idempotent, no
15492 // side effects on `&self`).
15493 //
15494 // Pins against a future silent detour that returned an owned
15495 // `Vec<String>` (which would type-check but silently clone on
15496 // every call, breaking the zero-cost projection every peer
15497 // sibling slice accessor carries), a `&Vec<String>` return
15498 // (which would leak the backing `Vec`'s grow/push/reserve
15499 // surface no downstream consumer reaches for), or a one-arm-
15500 // only accessor that returned a saturating value on some
15501 // sentinel input (breaking the pass-through invariant the
15502 // sibling slice accessors carry).
15503 for exe in [
15504 vec![],
15505 vec!["exe/cli"],
15506 vec!["exe/cli", "exe/serve"],
15507 vec!["exe/cli", "exe/cli"],
15508 ] {
15509 let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
15510 let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
15511 let first = c.exe();
15512 let second = c.exe();
15513 assert_eq!(
15514 first, second,
15515 "Caixa::exe must be idempotent — two successive calls \
15516 on the same &self must return the same &[String]",
15517 );
15518 assert_eq!(
15519 first.as_ptr(),
15520 second.as_ptr(),
15521 "Caixa::exe must borrow the underlying Vec<String> \
15522 storage — two successive calls must return slices \
15523 with the same backing pointer (a fresh Vec<String> \
15524 clone would change the pointer on every call)",
15525 );
15526 assert_eq!(
15527 first,
15528 expected.as_slice(),
15529 "Caixa::exe must return :exe verbatim by borrow — \
15530 got {first:?}, expected {expected:?}",
15531 );
15532 }
15533 }
15534
15535 // ── Caixa::servicos — outer top-level &[T] slice accessor ─────────
15536
15537 #[test]
15538 fn servicos_returns_servicos_slice_verbatim_across_permutations() {
15539 // The canonical per-`Caixa` `:servicos` universal-axis
15540 // ComputeUnit-CR-YAML-entry-path-list slice pin:
15541 // [`Caixa::servicos`] must return the `:servicos` typed
15542 // [`Vec<String>`] list verbatim as a `&[String]`, byte-equal to
15543 // the raw `self.servicos.as_slice()` access across every
15544 // representative value in the accept-set — `[]` (the "no
15545 // ComputeUnit-CR declared" arm every `:kind` other than
15546 // `Servico` carries; the layout's [`crate::LayoutInvariants`]
15547 // `ServicoWithoutServicos` arm-gate fires exactly on this
15548 // empty-slot + `Servico`-kind combination), `[""]` (a past-the-
15549 // guard sentinel that pins the accessor doesn't perform a
15550 // silent `[""] → []` collapse on the empty-entry arm — validate
15551 // rejects `[""]` through `CodePathEmpty { slot: ":servicos" }`
15552 // but the accessor must ship the raw slot verbatim so a
15553 // validate-time gate regression surfaces at the layout /
15554 // per-Servico renderer boundary rather than being silently
15555 // absorbed into a component-drop),
15556 // `["servicos/demo.computeunit.yaml"]` (the canonical
15557 // singleton V0-shape every in-tree `caixa_with_code_paths`
15558 // positive control uses; the same shape
15559 // [`crate::require_single_servico`] admits),
15560 // `["servicos/a.computeunit.yaml", "servicos/b.computeunit.
15561 // yaml"]` (a past-the-guard `len != 1` sentinel — the V0
15562 // singularity gate rejects through `ServicoCountMismatch
15563 // { count: 2 }` but the accessor must ship the raw slot
15564 // verbatim so struct-literal `Caixa { servicos: vec![...,
15565 // ...], .. }` fixtures continue to expose the count at the
15566 // accessor), and `["servicos/a.computeunit.yaml",
15567 // "servicos/a.computeunit.yaml"]` (a past-the-guard duplicate
15568 // sentinel — validate rejects through
15569 // `CodePathDuplicate { slot: ":servicos" }` per the per-slot
15570 // set-not-multiset gate, but the accessor must ship the raw
15571 // slot verbatim so struct-literal fixtures continue to expose
15572 // the duplicate at the accessor).
15573 //
15574 // Fifth and final outer top-level [`Caixa`] `&[T]`-return
15575 // slice accessor pin on the substrate primitive — folds on the
15576 // "outer [`Caixa`] `&[T]` slice" projection pattern
15577 // `autores_returns_autores_slice_verbatim_across_permutations`
15578 // (b5d813f) opened,
15579 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
15580 // (78c7d3c) folded on,
15581 // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
15582 // (8a36c23) closed the universal-axis text-tag family of, and
15583 // `exe_returns_exe_slice_verbatim_across_permutations`
15584 // (65d9527) opened the foreign-code-slot sub-family of. Closes
15585 // the outer-`Caixa` foreign-code-slot `&[T]` sub-family — the
15586 // trio of code-surface list slots (`:bibliotecas` + `:exe` +
15587 // `:servicos`) now each carries a substrate-canonical slice
15588 // accessor. Pins against a future silent detour that returned
15589 // an owned `Vec<String>` (which would type-check but silently
15590 // clone on every accessor call, breaking the zero-cost
15591 // projection every peer sibling slice accessor carries), a
15592 // `[""] → []` collapse (which would silently absorb the
15593 // `CodePathEmpty` refusal case at the accessor boundary), an
15594 // `[a, a] → [a]` dedup collapse (which would silently absorb
15595 // the `CodePathDuplicate` refusal case at the accessor
15596 // boundary — the per-slot set-not-multiset gate is downstream
15597 // of the accessor and must not be silently promoted into it),
15598 // or a `[a, b] → [a]` singleton collapse (which would silently
15599 // absorb the V0 `ServicoCountMismatch` refusal case at the
15600 // accessor boundary — the V0 singularity gate is downstream of
15601 // the accessor and must not be silently promoted into it).
15602 for servicos in [
15603 vec![],
15604 vec![""],
15605 vec!["servicos/demo.computeunit.yaml"],
15606 vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
15607 vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
15608 ] {
15609 let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
15610 let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
15611 assert_eq!(
15612 c.servicos(),
15613 expected.as_slice(),
15614 "Caixa::servicos must return :servicos verbatim (got \
15615 {:?}, expected {expected:?})",
15616 c.servicos(),
15617 );
15618 assert_eq!(
15619 c.servicos(),
15620 c.servicos.as_slice(),
15621 "Caixa::servicos must byte-equal the raw \
15622 `self.servicos.as_slice()` field access across every \
15623 value in the Vec<String> accept-set",
15624 );
15625 }
15626 }
15627
15628 #[test]
15629 fn validate_code_paths_servicos_empty_arm_routes_through_accessor() {
15630 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
15631 // empty-arm gate on the `:servicos` slot must key off
15632 // [`Caixa::servicos`], not a divergent raw `&self.servicos`
15633 // field-borrow walk. Structurally: a `Caixa { servicos:
15634 // vec!["".into()], .. }` must surface the `CodePathEmpty
15635 // { slot: ":servicos" }` refusal exactly, and a `Caixa
15636 // { servicos: vec!["servicos/demo.computeunit.yaml".into()],
15637 // .. }` (the canonical singleton V0-shape every in-tree
15638 // `caixa_with_code_paths` positive control uses) must pass
15639 // validate. The pair jointly pins the accessor + validate-gate
15640 // composition: any future silent detour that had the accessor
15641 // return an empty slice on the `[""]` arm (a `.iter().filter
15642 // (|s| !s.is_empty()).collect()` collapse) would silently
15643 // absorb the `CodePathEmpty` refusal at the accessor boundary
15644 // and the validate gate would accept a struct-literal
15645 // `Caixa { servicos: vec!["".into()], .. }` — the composition
15646 // pin catches that at caixa-core build time.
15647 //
15648 // Peer of the per-`Caixa`
15649 // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
15650 // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
15651 // (65d9527), `validate_autores_empty_arm_routes_through_accessor`
15652 // (b5d813f), and
15653 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
15654 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
15655 // composition axes — same "the validate / shape-gate predicate
15656 // must route through the substrate-primitive typed dispatch"
15657 // discipline extended onto the sibling outer top-level
15658 // [`Caixa`] `&[T]`-composition surface, closing the trio of
15659 // code-surface accessor-composition pins on the same axis.
15660 // Nominally the in-tree `validate_code_paths` production body
15661 // still keys off the internal
15662 // `[(":bibliotecas", &self.bibliotecas,
15663 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
15664 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
15665 // (the tuple's homogeneous `&Vec<String>`-typed shape blocks a
15666 // per-element accessor swap in isolation — a future companion
15667 // lift promotes the tuple's element type to `&[String]` and
15668 // threads the triple of typed dispatches through as a unit);
15669 // the composition pin catches any future accessor-side silent
15670 // filter drop against that eventual tuple-closure regardless
15671 // of whether the `:servicos` slot is threaded through the
15672 // accessor or the raw field access at the tuple's construction
15673 // site.
15674 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
15675 assert!(
15676 matches!(
15677 c.validate_code_paths(),
15678 Err(ManifestError::CodePathEmpty { slot: ":servicos" })
15679 ),
15680 "validate_code_paths must reject servicos == vec![\"\"] \
15681 with CodePathEmpty {{ slot: \":servicos\" }} — the \
15682 accessor and the validate gate must route through the \
15683 same substrate-primitive typed dispatch on the \
15684 :servicos per-entry empty arm",
15685 );
15686 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.computeunit.yaml"]);
15687 assert!(
15688 c.validate_code_paths().is_ok(),
15689 "validate_code_paths must accept servicos == \
15690 vec![\"servicos/demo.computeunit.yaml\"] (the canonical \
15691 singleton V0-shape every in-tree `caixa_with_code_paths` \
15692 positive control uses)",
15693 );
15694 }
15695
15696 #[test]
15697 fn servicos_projects_slice_by_borrow() {
15698 // The by-borrow pin: [`Caixa::servicos`] returns `&[String]` by
15699 // borrow — the returned slice borrows the underlying
15700 // `Vec<String>` storage of the `:servicos` slot and the
15701 // accessor must not clone the backing `Vec` on every call.
15702 // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
15703 // (b5d813f), `etiquetas_projects_slice_by_borrow` (78c7d3c),
15704 // `bibliotecas_projects_slice_by_borrow` (8a36c23), and
15705 // `exe_projects_slice_by_borrow` (65d9527) by-borrow pins on
15706 // the sibling outer top-level [`Caixa`] `&[String]`-return
15707 // axes — the accessor's returned slice must borrow from
15708 // `&self` (the returned reference's lifetime is tied to
15709 // `&self`), and calling the accessor twice on the same
15710 // [`Caixa`] must yield slices that are pointer-equal (the
15711 // underlying byte-buffer is the storage `Vec`'s allocation,
15712 // not a fresh copy) as well as value-equal (idempotent, no
15713 // side effects on `&self`).
15714 //
15715 // Pins against a future silent detour that returned an owned
15716 // `Vec<String>` (which would type-check but silently clone on
15717 // every call, breaking the zero-cost projection every peer
15718 // sibling slice accessor carries), a `&Vec<String>` return
15719 // (which would leak the backing `Vec`'s grow/push/reserve
15720 // surface no downstream consumer reaches for), or a one-arm-
15721 // only accessor that returned a saturating value on some
15722 // sentinel input (breaking the pass-through invariant the
15723 // sibling slice accessors carry).
15724 for servicos in [
15725 vec![],
15726 vec!["servicos/demo.computeunit.yaml"],
15727 vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
15728 vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
15729 ] {
15730 let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
15731 let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
15732 let first = c.servicos();
15733 let second = c.servicos();
15734 assert_eq!(
15735 first, second,
15736 "Caixa::servicos must be idempotent — two successive \
15737 calls on the same &self must return the same &[String]",
15738 );
15739 assert_eq!(
15740 first.as_ptr(),
15741 second.as_ptr(),
15742 "Caixa::servicos must borrow the underlying \
15743 Vec<String> storage — two successive calls must \
15744 return slices with the same backing pointer (a fresh \
15745 Vec<String> clone would change the pointer on every \
15746 call)",
15747 );
15748 assert_eq!(
15749 first,
15750 expected.as_slice(),
15751 "Caixa::servicos must return :servicos verbatim by \
15752 borrow — got {first:?}, expected {expected:?}",
15753 );
15754 }
15755 }
15756
15757 // ── Caixa::deps — outer top-level &[Dep] slice accessor ───────────
15758
15759 fn caixa_with_deps(deps: Vec<Dep>) -> Caixa {
15760 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15761 c.deps = deps;
15762 c
15763 }
15764
15765 #[test]
15766 fn deps_returns_deps_slice_verbatim_across_permutations() {
15767 // The canonical per-`Caixa` `:deps` universal-axis runtime-
15768 // dependency-declaration-list slice pin: [`Caixa::deps`] must
15769 // return the `:deps` typed [`Vec<Dep>`] list verbatim as a
15770 // `&[Dep]`, element-equal to the raw `self.deps.as_slice()`
15771 // access across every representative value in the accept-set —
15772 // `[]` (the "no runtime deps declared" arm every existing
15773 // fixture without a `:deps` line carries; the
15774 // [`Caixa::template`] scaffold emits `:deps ()`), a canonical
15775 // single-entry list (the shape most consumer caixas carry), a
15776 // canonical two-entry list (the multi-dep runtime closure), and
15777 // two past-the-guard sentinels — a `[""]`-`:nome` entry
15778 // ([`Self::validate_deps`] rejects through `NomeEmpty` /
15779 // `NomeInvalid` but the accessor must ship the raw slot
15780 // verbatim) and a `[a, a]` duplicate (validate rejects through
15781 // `DuplicateNome { list: ":deps" }` but the accessor must ship
15782 // the raw slot verbatim so struct-literal fixtures continue to
15783 // expose the duplicate at the accessor).
15784 //
15785 // First outer top-level [`Caixa`] `&[Dep]`-return slice accessor
15786 // pin on the substrate primitive — opens the outer-`Caixa`
15787 // dependency-slot `&[Dep]` sub-family the sibling `:deps-dev`
15788 // future lift closes on. Peer of the closed outer-`Caixa`
15789 // foreign-code-slot `&[String]` sub-family
15790 // (`bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
15791 // 8a36c23, `exe_returns_exe_slice_verbatim_across_permutations`
15792 // 65d9527, `servicos_returns_servicos_slice_verbatim_across_permutations`
15793 // 611f78b) and the outer-`Caixa` universal-axis text-tag family
15794 // (`autores_returns_autores_slice_verbatim_across_permutations`
15795 // b5d813f, `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
15796 // 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice"
15797 // projection pattern onto a novel element-type axis (`Dep`
15798 // composite vs the prior sibling family's `String` scalar).
15799 // Pins against a future silent detour that returned an owned
15800 // `Vec<Dep>` (which would type-check but silently clone on every
15801 // accessor call, breaking the zero-cost projection every peer
15802 // sibling slice accessor carries), a `[""] → []` collapse (which
15803 // would silently absorb the `NomeEmpty` refusal case at the
15804 // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
15805 // would silently absorb the `DuplicateNome` refusal case at the
15806 // accessor boundary).
15807 for deps in [
15808 vec![],
15809 vec![Dep::simple("", "^0.1")],
15810 vec![Dep::simple("caixa-teia", "^0.1")],
15811 vec![
15812 Dep::simple("caixa-teia", "^0.1"),
15813 Dep::simple("caixa-core", "^0.1"),
15814 ],
15815 vec![
15816 Dep::simple("caixa-teia", "^0.1"),
15817 Dep::simple("caixa-teia", "^0.2"),
15818 ],
15819 ] {
15820 let c = caixa_with_deps(deps.clone());
15821 assert_eq!(
15822 c.deps(),
15823 deps.as_slice(),
15824 "Caixa::deps must return :deps verbatim (got {:?}, \
15825 expected {deps:?})",
15826 c.deps(),
15827 );
15828 assert_eq!(
15829 c.deps(),
15830 c.deps.as_slice(),
15831 "Caixa::deps must element-equal the raw \
15832 `self.deps.as_slice()` field access across every \
15833 value in the Vec<Dep> accept-set",
15834 );
15835 }
15836 }
15837
15838 #[test]
15839 fn validate_deps_duplicate_arm_routes_through_accessor() {
15840 // Composition pin: [`Caixa::validate_deps`]'s within-`:deps`
15841 // duplicate-`:nome` gate must key off [`Caixa::deps`], not the
15842 // raw `&self.deps` field-borrow walk. Structurally: a `Caixa
15843 // { deps: vec![Dep::simple("d", "^0.1"), Dep::simple("d",
15844 // "^0.2")], .. }` must surface the `DuplicateNome { list:
15845 // ":deps" }` refusal exactly, and a `Caixa { deps: vec![
15846 // Dep::simple("d", "^0.1")], .. }` (the canonical single-entry
15847 // form) must pass validate. The pair jointly pins the accessor +
15848 // validate-gate composition: any future silent detour that had
15849 // the accessor return a dedupped slice on the `[a, a]` arm (a
15850 // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
15851 // would silently absorb the `DuplicateNome` refusal at the
15852 // accessor boundary and the validate gate would accept a
15853 // struct-literal `Caixa` carrying the drift — the composition
15854 // pin catches that at caixa-core build time.
15855 //
15856 // Peer of the per-`Caixa`
15857 // `validate_autores_empty_entry_arm_routes_through_accessor`
15858 // (b5d813f), `validate_etiquetas_empty_entry_arm_routes_through_accessor`
15859 // (78c7d3c), `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
15860 // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
15861 // (65d9527), and `validate_code_paths_servicos_empty_arm_routes_through_accessor`
15862 // (611f78b) accessor-composition pins on the sibling `&[T]`-
15863 // composition axes — same "the validate gate must route through
15864 // the substrate-primitive typed dispatch" discipline extended
15865 // onto the sibling outer top-level [`Caixa`] `&[Dep]`-
15866 // composition surface, opening the outer-`Caixa` dependency-slot
15867 // arm of the composition-pin family.
15868 let c = caixa_with_deps(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
15869 let err = c.validate_deps().unwrap_err();
15870 assert!(
15871 matches!(
15872 err,
15873 DepError::DuplicateNome { ref nome, list } if nome == "d"
15874 && list == crate::render::DEP_AUTHOR_KEY_DEPS
15875 ),
15876 "validate_deps must reject deps == \
15877 vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
15878 DuplicateNome {{ nome: \"d\", list: \":deps\" }} — the \
15879 accessor and the validate gate must route through the \
15880 same substrate-primitive typed dispatch on the :deps \
15881 within-list duplicate arm (got {err:?})",
15882 );
15883 let c = caixa_with_deps(vec![Dep::simple("d", "^0.1")]);
15884 assert!(
15885 c.validate_deps().is_ok(),
15886 "validate_deps must accept deps == vec![Dep(\"d\",\"^0.1\")] \
15887 (the canonical single-entry form)",
15888 );
15889 }
15890
15891 #[test]
15892 fn deps_projects_slice_by_borrow() {
15893 // The by-borrow pin: [`Caixa::deps`] returns `&[Dep]` by borrow
15894 // — the returned slice borrows the underlying `Vec<Dep>` storage
15895 // of the `:deps` slot and the accessor must not clone the
15896 // backing `Vec` on every call. Peer of the per-`Caixa`
15897 // `autores_projects_slice_by_borrow` (b5d813f),
15898 // `etiquetas_projects_slice_by_borrow` (78c7d3c),
15899 // `bibliotecas_projects_slice_by_borrow` (8a36c23),
15900 // `exe_projects_slice_by_borrow` (65d9527), and
15901 // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
15902 // on the sibling outer top-level [`Caixa`] `&[String]`-return
15903 // axes — the accessor's returned slice must borrow from `&self`
15904 // (the returned reference's lifetime is tied to `&self`), and
15905 // calling the accessor twice on the same [`Caixa`] must yield
15906 // slices that are pointer-equal (the underlying byte-buffer is
15907 // the storage `Vec`'s allocation, not a fresh copy) as well as
15908 // value-equal (idempotent, no side effects on `&self`).
15909 //
15910 // Pins against a future silent detour that returned an owned
15911 // `Vec<Dep>` (which would type-check but silently clone on
15912 // every call), a `&Vec<Dep>` return (which would leak the
15913 // backing `Vec`'s grow/push/reserve surface no downstream
15914 // consumer reaches for), or a one-arm-only accessor that
15915 // returned a saturating value on some sentinel input.
15916 for deps in [
15917 vec![],
15918 vec![Dep::simple("caixa-teia", "^0.1")],
15919 vec![
15920 Dep::simple("caixa-teia", "^0.1"),
15921 Dep::simple("caixa-core", "^0.1"),
15922 ],
15923 ] {
15924 let c = caixa_with_deps(deps.clone());
15925 let first = c.deps();
15926 let second = c.deps();
15927 assert_eq!(
15928 first, second,
15929 "Caixa::deps must be idempotent — two successive calls \
15930 on the same &self must return the same &[Dep]",
15931 );
15932 assert_eq!(
15933 first.as_ptr(),
15934 second.as_ptr(),
15935 "Caixa::deps must borrow the underlying Vec<Dep> \
15936 storage — two successive calls must return slices \
15937 with the same backing pointer (a fresh Vec<Dep> clone \
15938 would change the pointer on every call)",
15939 );
15940 assert_eq!(
15941 first,
15942 deps.as_slice(),
15943 "Caixa::deps must return :deps verbatim by borrow — \
15944 got {first:?}, expected {deps:?}",
15945 );
15946 }
15947 }
15948
15949 // ── Caixa::deps_dev — outer top-level &[Dep] slice accessor ──────
15950
15951 fn caixa_with_deps_dev(deps_dev: Vec<Dep>) -> Caixa {
15952 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15953 c.deps_dev = deps_dev;
15954 c
15955 }
15956
15957 #[test]
15958 fn deps_dev_returns_deps_dev_slice_verbatim_across_permutations() {
15959 // The canonical per-`Caixa` `:deps-dev` universal-axis dev-only-
15960 // dependency-declaration-list slice pin: [`Caixa::deps_dev`]
15961 // must return the `:deps-dev` typed [`Vec<Dep>`] list verbatim as
15962 // a `&[Dep]`, element-equal to the raw `self.deps_dev.as_slice()`
15963 // access across every representative value in the accept-set —
15964 // `[]` (the "no dev deps declared" arm every existing fixture
15965 // without a `:deps-dev` line carries; the [`Caixa::template`]
15966 // scaffold emits `:deps-dev ()`), a canonical single-entry list
15967 // (the shape most consumer caixas carry — a `tatara-check` dev
15968 // pin), a canonical two-entry list (the multi-dev-dep closure),
15969 // and two past-the-guard sentinels — a `[""]`-`:nome` entry
15970 // ([`Self::validate_deps`] rejects through `NomeEmpty` /
15971 // `NomeInvalid` but the accessor must ship the raw slot
15972 // verbatim) and a `[a, a]` duplicate (validate rejects through
15973 // `DuplicateNome { list: ":deps-dev" }` but the accessor must
15974 // ship the raw slot verbatim so struct-literal fixtures continue
15975 // to expose the duplicate at the accessor).
15976 //
15977 // Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor
15978 // pin on the substrate primitive — closes the outer-`Caixa`
15979 // dependency-slot `&[Dep]` sub-family the sibling
15980 // `deps_returns_deps_slice_verbatim_across_permutations`
15981 // (ad34b4e) opened on. Folds the "outer [`Caixa`] `&[Dep]`
15982 // slice" projection pattern onto the sibling dev-dep axis —
15983 // pins against a future silent detour that returned an owned
15984 // `Vec<Dep>` (which would type-check but silently clone on every
15985 // accessor call, breaking the zero-cost projection every peer
15986 // sibling slice accessor carries), a `[""] → []` collapse (which
15987 // would silently absorb the `NomeEmpty` refusal case at the
15988 // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
15989 // would silently absorb the `DuplicateNome` refusal case at the
15990 // accessor boundary).
15991 for deps_dev in [
15992 vec![],
15993 vec![Dep::simple("", "^0.1")],
15994 vec![Dep::simple("tatara-check", "^0.1")],
15995 vec![
15996 Dep::simple("tatara-check", "^0.1"),
15997 Dep::simple("caixa-lint", "^0.1"),
15998 ],
15999 vec![
16000 Dep::simple("tatara-check", "^0.1"),
16001 Dep::simple("tatara-check", "^0.2"),
16002 ],
16003 ] {
16004 let c = caixa_with_deps_dev(deps_dev.clone());
16005 assert_eq!(
16006 c.deps_dev(),
16007 deps_dev.as_slice(),
16008 "Caixa::deps_dev must return :deps-dev verbatim (got \
16009 {:?}, expected {deps_dev:?})",
16010 c.deps_dev(),
16011 );
16012 assert_eq!(
16013 c.deps_dev(),
16014 c.deps_dev.as_slice(),
16015 "Caixa::deps_dev must element-equal the raw \
16016 `self.deps_dev.as_slice()` field access across every \
16017 value in the Vec<Dep> accept-set",
16018 );
16019 }
16020 }
16021
16022 #[test]
16023 fn validate_deps_duplicate_deps_dev_arm_routes_through_accessor() {
16024 // Composition pin: [`Caixa::validate_deps`]'s within-`:deps-dev`
16025 // duplicate-`:nome` gate must key off [`Caixa::deps_dev`], not
16026 // the raw `&self.deps_dev` field-borrow walk. Structurally: a
16027 // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1"),
16028 // Dep::simple("d", "^0.2")], .. }` must surface the
16029 // `DuplicateNome { list: ":deps-dev" }` refusal exactly, and a
16030 // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1")], .. }` (the
16031 // canonical single-entry form) must pass validate. The pair
16032 // jointly pins the accessor + validate-gate composition: any
16033 // future silent detour that had the accessor return a dedupped
16034 // slice on the `[a, a]` arm (a
16035 // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
16036 // would silently absorb the `DuplicateNome` refusal at the
16037 // accessor boundary and the validate gate would accept a
16038 // struct-literal `Caixa` carrying the drift — the composition
16039 // pin catches that at caixa-core build time.
16040 //
16041 // Peer of `validate_deps_duplicate_arm_routes_through_accessor`
16042 // (ad34b4e) on the sibling `:deps` axis — same "the validate
16043 // gate must route through the substrate-primitive typed
16044 // dispatch" discipline folded onto the sibling `:deps-dev`
16045 // axis, closing the two-list dep-graph composition-pin family.
16046 // The `:deps-dev` diagnostic must carry the
16047 // `DEP_AUTHOR_KEY_DEPS_DEV` list-tag (not
16048 // `DEP_AUTHOR_KEY_DEPS`) so the emitted error names the
16049 // offending list unambiguously.
16050 let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
16051 let err = c.validate_deps().unwrap_err();
16052 assert!(
16053 matches!(
16054 err,
16055 DepError::DuplicateNome { ref nome, list } if nome == "d"
16056 && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
16057 ),
16058 "validate_deps must reject deps_dev == \
16059 vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
16060 DuplicateNome {{ nome: \"d\", list: \":deps-dev\" }} — the \
16061 accessor and the validate gate must route through the \
16062 same substrate-primitive typed dispatch on the :deps-dev \
16063 within-list duplicate arm (got {err:?})",
16064 );
16065 let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1")]);
16066 assert!(
16067 c.validate_deps().is_ok(),
16068 "validate_deps must accept deps_dev == \
16069 vec![Dep(\"d\",\"^0.1\")] (the canonical single-entry form)",
16070 );
16071 }
16072
16073 #[test]
16074 fn deps_dev_projects_slice_by_borrow() {
16075 // The by-borrow pin: [`Caixa::deps_dev`] returns `&[Dep]` by
16076 // borrow — the returned slice borrows the underlying `Vec<Dep>`
16077 // storage of the `:deps-dev` slot and the accessor must not
16078 // clone the backing `Vec` on every call. Peer of
16079 // `deps_projects_slice_by_borrow` (ad34b4e) on the sibling
16080 // `:deps` axis, and of the per-`Caixa`
16081 // `autores_projects_slice_by_borrow` (b5d813f),
16082 // `etiquetas_projects_slice_by_borrow` (78c7d3c),
16083 // `bibliotecas_projects_slice_by_borrow` (8a36c23),
16084 // `exe_projects_slice_by_borrow` (65d9527), and
16085 // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
16086 // on the sibling outer top-level [`Caixa`] `&[String]`-return
16087 // axes — the accessor's returned slice must borrow from `&self`
16088 // (the returned reference's lifetime is tied to `&self`), and
16089 // calling the accessor twice on the same [`Caixa`] must yield
16090 // slices that are pointer-equal (the underlying byte-buffer is
16091 // the storage `Vec`'s allocation, not a fresh copy) as well as
16092 // value-equal (idempotent, no side effects on `&self`).
16093 //
16094 // Pins against a future silent detour that returned an owned
16095 // `Vec<Dep>` (which would type-check but silently clone on
16096 // every call), a `&Vec<Dep>` return (which would leak the
16097 // backing `Vec`'s grow/push/reserve surface no downstream
16098 // consumer reaches for), or a one-arm-only accessor that
16099 // returned a saturating value on some sentinel input.
16100 for deps_dev in [
16101 vec![],
16102 vec![Dep::simple("tatara-check", "^0.1")],
16103 vec![
16104 Dep::simple("tatara-check", "^0.1"),
16105 Dep::simple("caixa-lint", "^0.1"),
16106 ],
16107 ] {
16108 let c = caixa_with_deps_dev(deps_dev.clone());
16109 let first = c.deps_dev();
16110 let second = c.deps_dev();
16111 assert_eq!(
16112 first, second,
16113 "Caixa::deps_dev must be idempotent — two successive \
16114 calls on the same &self must return the same &[Dep]",
16115 );
16116 assert_eq!(
16117 first.as_ptr(),
16118 second.as_ptr(),
16119 "Caixa::deps_dev must borrow the underlying Vec<Dep> \
16120 storage — two successive calls must return slices \
16121 with the same backing pointer (a fresh Vec<Dep> clone \
16122 would change the pointer on every call)",
16123 );
16124 assert_eq!(
16125 first,
16126 deps_dev.as_slice(),
16127 "Caixa::deps_dev must return :deps-dev verbatim by \
16128 borrow — got {first:?}, expected {deps_dev:?}",
16129 );
16130 }
16131 }
16132
16133 // ── Caixa::limits — outer top-level Option<&LimitsSpec> composite-reference accessor ──
16134
16135 fn caixa_with_limits(limits: Option<crate::LimitsSpec>) -> Caixa {
16136 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16137 c.limits = limits;
16138 c
16139 }
16140
16141 #[test]
16142 fn limits_returns_limits_option_ref_verbatim_across_permutations() {
16143 // The canonical per-`Caixa` `:limits` M2 typed-slot outer-
16144 // composite optional-composite-reference-shape pin:
16145 // [`Caixa::limits`] must return the `:limits` typed
16146 // `Option<LimitsSpec>` verbatim as an `Option<&LimitsSpec>`
16147 // reference over the same backing storage the raw
16148 // `self.limits.as_ref()` field access borrows from, byte-equal
16149 // across every representative fixture in the accept-set — the
16150 // author-omitted `None` shape (the "engine-default applies"
16151 // partition every downstream Servico M2 overlay emitter treats
16152 // as "emit nothing"), the empty-composite `Some(LimitsSpec {
16153 // .. default })` shape ([`LimitsSpec::is_empty`] holds — every
16154 // per-axis cap is `None`, so the peer M2 overlay emitter's
16155 // `.is_empty()`-gated projection still emits nothing but the
16156 // outer presence-bit is `Some`, so [`Caixa::declared_servico_slots`]
16157 // still pushes the `M2_AUTHOR_KEY_LIMITS` label), a single-axis
16158 // fixture (only `:memory` set — the canonical shape most
16159 // memory-heavy Servicos carry), and a fully-populated composite
16160 // (every per-axis cap set — the canonical shape a
16161 // sandboxed-by-default Servico carries).
16162 //
16163 // Pins against a future silent detour that returned a fresh-
16164 // cloned [`LimitsSpec`] copy (which would type-check via the
16165 // `Clone` impl but silently break every downstream caller that
16166 // relied on the reference sharing the composite's backing
16167 // identity), a reference to an operator-resolved overlay (the
16168 // future per-cluster `:limits-overrides` slot — its resolution
16169 // must land at exactly this accessor body, not silently divert
16170 // the raw slot away from a second consumer), a
16171 // `None` → `Some(LimitsSpec::default)` cluster-default
16172 // projection (which would collapse the load-bearing
16173 // "author-omitted `:limits` ⇒ engine-default applies" partition
16174 // the peer [`crate::render::servico_m2_overlay`] emitter and
16175 // the peer [`Caixa::declared_servico_slots`] enumerator both
16176 // read), or an axis-shuffled projection (a future detour that
16177 // swapped `memory` and `fuel` through the accessor would
16178 // silently split the paired [`crate::StandardLayout::verify`]
16179 // per-`:limits` shape gate's traversal input from the peer
16180 // `servico_m2_overlay` emitter's projection input).
16181 //
16182 // First outer top-level [`Caixa`] `Option<&Composite>`-return
16183 // composite-reference accessor pin on the substrate primitive
16184 // — opens the outer-`Caixa` `Option<&Composite>` composite-
16185 // reference projection pattern the sibling `:behavior`
16186 // [`crate::BehaviorSpec`] / `:politicas`
16187 // [`crate::aplicacao::MeshPolicy`] / `:placement`
16188 // [`crate::aplicacao::Placement`] / `:entrada`
16189 // [`crate::aplicacao::Entrada`] future outer-composite lifts
16190 // fold on. Peer of the closed M3 outer-composite family the
16191 // sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
16192 // [`crate::AplicacaoSpec::placement`] (9abb8f0) /
16193 // [`crate::AplicacaoSpec::entrada`] (d32111c) composite-
16194 // reference accessor pins already carry on the outer
16195 // [`crate::AplicacaoSpec`] altitude — extends the outer-
16196 // accessor byte-equal-projection discipline onto the outer
16197 // top-level [`Caixa`] M2 Servico-runtime slot altitude.
16198 use crate::LimitsSpec;
16199 use std::time::Duration;
16200 let fixtures: Vec<Option<LimitsSpec>> = vec![
16201 None,
16202 Some(LimitsSpec::default()),
16203 Some(LimitsSpec {
16204 memory: Some(64 * 1024 * 1024),
16205 ..Default::default()
16206 }),
16207 Some(LimitsSpec {
16208 memory: Some(64 * 1024 * 1024),
16209 fuel: Some(1_000_000),
16210 wall_clock: Some(Duration::from_secs(30)),
16211 cpu: Some(500),
16212 }),
16213 ];
16214 for limits in fixtures {
16215 let c = caixa_with_limits(limits.clone());
16216 assert_eq!(
16217 c.limits(),
16218 limits.as_ref(),
16219 "Caixa::limits must return :limits verbatim (got {:?}, \
16220 expected {:?})",
16221 c.limits(),
16222 limits.as_ref(),
16223 );
16224 match (c.limits(), c.limits.as_ref()) {
16225 (Some(a), Some(b)) => assert!(
16226 std::ptr::eq(a, b),
16227 "Caixa::limits accessor and self.limits.as_ref() \
16228 field access must borrow the same backing storage \
16229 — the accessor is the substrate-primitive typed \
16230 dispatch every downstream Servico-M2-overlay \
16231 composite consumer must route through, and a \
16232 reference-identity split would silently break \
16233 every consumer that relied on the borrow sharing \
16234 the composite's storage",
16235 ),
16236 (None, None) => {}
16237 _ => panic!(
16238 "Caixa::limits presence bit must byte-equal \
16239 self.limits.is_some() — a presence-bit drift would \
16240 silently split the paired StandardLayout::verify \
16241 per-`:limits` shape gate's traversal head from \
16242 the peer render::servico_m2_overlay M2 overlay \
16243 emitter's traversal head from the peer \
16244 Caixa::declared_servico_slots M2 declared-slot \
16245 enumerator's presence probe",
16246 ),
16247 }
16248 assert_eq!(
16249 c.limits().is_some(),
16250 c.limits.is_some(),
16251 "Caixa::limits().is_some() must byte-equal \
16252 self.limits.is_some() — a presence-bit drift would \
16253 silently split every downstream Option<&LimitsSpec> \
16254 consumer's partition on the engine-default arm",
16255 );
16256 }
16257 }
16258
16259 #[test]
16260 fn declared_servico_slots_limits_arm_routes_through_accessor() {
16261 // Composition pin: [`Caixa::declared_servico_slots`]'s
16262 // `:limits` presence-probe arm must key off [`Caixa::limits`],
16263 // not the raw `self.limits.is_some()` field-probe. Structurally:
16264 // a `Caixa { limits: Some(LimitsSpec::default()), .. }` must
16265 // still push `M2_AUTHOR_KEY_LIMITS` onto the declared-slot list
16266 // (the presence bit is `Some`, so the M2 kind-coherence gate
16267 // must surface the slot as "declared" even when every per-axis
16268 // cap is unset), and a `Caixa { limits: None, .. }` must NOT
16269 // push the label (the "author omitted the slot entirely"
16270 // partition). The pair jointly pins the accessor + declared-
16271 // slot enumerator composition: any future silent detour that
16272 // had the accessor collapse `Some(LimitsSpec::default())` to
16273 // `None` (a `.filter(|l| !l.is_empty())` projection) would
16274 // silently absorb the "declared but empty" arm at the
16275 // accessor boundary and the [`crate::LayoutError::ServicoSlotsOnNonServico`]
16276 // kind-coherence gate would silently accept a
16277 // struct-literal `Caixa` carrying the drift.
16278 //
16279 // Peer of the sibling per-`Caixa`
16280 // `validate_deps_duplicate_arm_routes_through_accessor` (ad34b4e)
16281 // and `validate_deps_duplicate_deps_dev_arm_routes_through_accessor`
16282 // (f7fd81e) accessor-composition pins on the sibling `:deps` /
16283 // `:deps-dev` outer-`&[Dep]`-composition axes — same "the
16284 // enumerator gate must route through the substrate-primitive
16285 // typed dispatch" discipline extended onto the outer top-level
16286 // [`Caixa`] `Option<&LimitsSpec>`-composition surface, opening
16287 // the outer-`Caixa` M2 Servico-runtime-slot arm of the
16288 // composition-pin family.
16289 use crate::LimitsSpec;
16290 let c = caixa_with_limits(Some(LimitsSpec::default()));
16291 let slots = c.declared_servico_slots();
16292 assert!(
16293 slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
16294 "declared_servico_slots must push M2_AUTHOR_KEY_LIMITS \
16295 when `:limits` is Some (even for LimitsSpec::default()) \
16296 — the accessor and the enumerator gate must route through \
16297 the same substrate-primitive typed dispatch on the outer \
16298 :limits presence bit (got slots={slots:?})",
16299 );
16300 let c = caixa_with_limits(None);
16301 let slots = c.declared_servico_slots();
16302 assert!(
16303 !slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
16304 "declared_servico_slots must NOT push M2_AUTHOR_KEY_LIMITS \
16305 when `:limits` is None — the author-omitted arm must \
16306 route through the accessor's None-return unchanged (got \
16307 slots={slots:?})",
16308 );
16309 }
16310
16311 #[test]
16312 fn servico_m2_overlay_limits_arm_routes_through_accessor() {
16313 // Composition pin: [`crate::render::servico_m2_overlay`]'s
16314 // per-`:limits` M2 overlay emit arm must key off
16315 // [`Caixa::limits`], not the raw `&caixa.limits` field-borrow.
16316 // Structurally: a `Caixa { limits: Some(LimitsSpec { memory:
16317 // Some(64 MiB), .. default }), .. }` must surface the
16318 // `M2_KEY_LIMITS` key with the per-axis
16319 // `memory: "64MiB"` sub-mapping in the overlay, a `Caixa {
16320 // limits: Some(LimitsSpec::default()), .. }` must omit the
16321 // key entirely (the `.is_empty()`-gated inner arm elides an
16322 // empty composite even when the outer presence bit is `Some`),
16323 // and a `Caixa { limits: None, .. }` must also omit the key
16324 // (the "author omitted the slot entirely" partition). The
16325 // three-fixture family jointly pins the accessor + M2 overlay
16326 // emitter composition: any future silent detour that had the
16327 // accessor return a fresh-cloned copy on the `Some` arm (a
16328 // `LimitsSpec::clone()` projection) would silently break the
16329 // reference-identity pin the peer per-axis
16330 // `serde_yaml::to_value(limits)` projection reads from.
16331 use crate::LimitsSpec;
16332 use crate::render::{M2_KEY_LIMITS, servico_m2_overlay};
16333 let c = caixa_with_limits(Some(LimitsSpec {
16334 memory: Some(64 * 1024 * 1024),
16335 ..Default::default()
16336 }));
16337 let overlay = servico_m2_overlay(&c).unwrap();
16338 assert!(
16339 overlay.contains_key(M2_KEY_LIMITS),
16340 "servico_m2_overlay must surface M2_KEY_LIMITS when \
16341 `:limits` carries a non-empty composite — the accessor \
16342 and the M2 overlay emitter must route through the same \
16343 substrate-primitive typed dispatch on the outer :limits \
16344 composite (got overlay={overlay:?})",
16345 );
16346 let c = caixa_with_limits(Some(LimitsSpec::default()));
16347 let overlay = servico_m2_overlay(&c).unwrap();
16348 assert!(
16349 !overlay.contains_key(M2_KEY_LIMITS),
16350 "servico_m2_overlay must omit M2_KEY_LIMITS when \
16351 `:limits` is Some(LimitsSpec::default()) — the empty \
16352 composite's `.is_empty()`-gated inner arm must elide \
16353 the key regardless of the outer presence bit (got \
16354 overlay={overlay:?})",
16355 );
16356 let c = caixa_with_limits(None);
16357 let overlay = servico_m2_overlay(&c).unwrap();
16358 assert!(
16359 !overlay.contains_key(M2_KEY_LIMITS),
16360 "servico_m2_overlay must omit M2_KEY_LIMITS when \
16361 `:limits` is None — the author-omitted arm must route \
16362 through the accessor's None-return unchanged (got \
16363 overlay={overlay:?})",
16364 );
16365 }
16366
16367 #[test]
16368 fn limits_projects_option_ref_by_borrow() {
16369 // The by-borrow pin: [`Caixa::limits`] returns
16370 // `Option<&LimitsSpec>` by borrow — the returned reference
16371 // borrows the underlying `Option<LimitsSpec>` storage of the
16372 // `:limits` slot and the accessor must not clone the backing
16373 // composite on every call. Peer of the sibling
16374 // `deps_projects_slice_by_borrow` (ad34b4e) /
16375 // `deps_dev_projects_slice_by_borrow` (f7fd81e) by-borrow pins
16376 // on the outer top-level [`Caixa`] `&[Dep]`-return axes —
16377 // extended here to the outer [`Caixa`] `Option<&Composite>`-
16378 // return axis: the accessor's returned reference must borrow
16379 // from `&self` (the returned reference's lifetime is tied to
16380 // `&self`), and calling the accessor twice on the same
16381 // [`Caixa`] must yield references that are pointer-equal (the
16382 // underlying byte-buffer is the storage `LimitsSpec`'s
16383 // allocation, not a fresh copy) as well as value-equal
16384 // (idempotent, no side effects on `&self`).
16385 //
16386 // Pins against a future silent detour that returned an owned
16387 // `LimitsSpec` (which would type-check via the `Clone` impl
16388 // but silently clone on every call), a `&LimitsSpec` panic-
16389 // return on the `None` arm (which would collapse the load-
16390 // bearing `Option` presence-bit into a runtime panic), or a
16391 // one-arm-only accessor that returned a saturating composite
16392 // on some sentinel input.
16393 use crate::LimitsSpec;
16394 use std::time::Duration;
16395 for limits in [
16396 Some(LimitsSpec::default()),
16397 Some(LimitsSpec {
16398 memory: Some(64 * 1024 * 1024),
16399 fuel: Some(1_000_000),
16400 wall_clock: Some(Duration::from_secs(30)),
16401 cpu: Some(500),
16402 }),
16403 ] {
16404 let c = caixa_with_limits(limits.clone());
16405 let first = c.limits().unwrap();
16406 let second = c.limits().unwrap();
16407 assert_eq!(
16408 first, second,
16409 "Caixa::limits must be idempotent — two successive \
16410 calls on the same &self must return the same \
16411 &LimitsSpec",
16412 );
16413 assert!(
16414 std::ptr::eq(first, second),
16415 "Caixa::limits must borrow the underlying \
16416 Option<LimitsSpec> storage — two successive calls \
16417 must return references with the same backing pointer \
16418 (a fresh LimitsSpec clone would change the pointer \
16419 on every call)",
16420 );
16421 assert_eq!(
16422 Some(first),
16423 limits.as_ref(),
16424 "Caixa::limits must return :limits verbatim by borrow \
16425 — got {first:?}, expected {:?}",
16426 limits.as_ref(),
16427 );
16428 }
16429 let c = caixa_with_limits(None);
16430 assert!(
16431 c.limits().is_none(),
16432 "Caixa::limits must return None when :limits is absent — \
16433 the author-omitted arm must project through the \
16434 accessor's Option::None unchanged",
16435 );
16436 }
16437
16438 // ── Caixa::behavior — outer top-level Option<&BehaviorSpec> composite-reference accessor ──
16439
16440 fn caixa_with_behavior(behavior: Option<crate::BehaviorSpec>) -> Caixa {
16441 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16442 c.behavior = behavior;
16443 c
16444 }
16445
16446 #[test]
16447 fn behavior_returns_behavior_option_ref_verbatim_across_permutations() {
16448 // The canonical per-`Caixa` `:behavior` M2 typed-slot outer-
16449 // composite optional-composite-reference-shape pin:
16450 // [`Caixa::behavior`] must return the `:behavior` typed
16451 // `Option<BehaviorSpec>` verbatim as an `Option<&BehaviorSpec>`
16452 // reference over the same backing storage the raw
16453 // `self.behavior.as_ref()` field access borrows from, byte-equal
16454 // across every representative fixture in the accept-set — the
16455 // author-omitted `None` shape (the "runtime-default applies"
16456 // partition every downstream Servico M2 overlay emitter treats
16457 // as "emit nothing"), the empty-composite `Some(BehaviorSpec {
16458 // .. default })` shape ([`BehaviorSpec::is_empty`] holds —
16459 // every per-callback path is `None`, so the peer M2 overlay
16460 // emitter's `.is_empty()`-gated projection still emits nothing
16461 // but the outer presence-bit is `Some`, so
16462 // [`Caixa::declared_servico_slots`] still pushes the
16463 // `M2_AUTHOR_KEY_BEHAVIOR` label), a single-callback fixture
16464 // (only `:on-state-change` set — the canonical shape a caixa
16465 // that only wires the hot-upgrade migration path carries), and
16466 // a fully-populated composite (every per-callback path set —
16467 // the canonical shape a fully-instrumented gen_server-shaped
16468 // Servico carries).
16469 //
16470 // Peer of the sibling
16471 // `limits_returns_limits_option_ref_verbatim_across_permutations`
16472 // (b2bd9d7) opening fixture-family + reference-identity +
16473 // presence-bit tetrad pin on the outer top-level [`Caixa`]
16474 // `Option<&Composite>`-return sub-family — extended here to the
16475 // second axis of that sub-family so both of the currently-lifted
16476 // M2 Servico-runtime `Option<&Composite>` slots (`:limits` /
16477 // `:behavior`) carry the same "byte-equal, borrow-shared,
16478 // presence-bit-preserved" outer-accessor discipline.
16479 //
16480 // Pins against a future silent detour that returned a fresh-
16481 // cloned [`crate::BehaviorSpec`] copy (which would type-check
16482 // via the `Clone` impl but silently break every downstream
16483 // caller that relied on the reference sharing the composite's
16484 // backing identity), a reference to an operator-resolved
16485 // overlay (a future per-cluster `:behavior-overrides` slot —
16486 // its resolution must land at exactly this accessor body, not
16487 // silently divert the raw slot away from a second consumer), a
16488 // `None` → `Some(BehaviorSpec::default)` cluster-default
16489 // projection (which would collapse the load-bearing
16490 // "author-omitted `:behavior` ⇒ runtime-default applies"
16491 // partition the peer [`crate::render::servico_m2_overlay`]
16492 // emitter, the peer [`Caixa::declared_servico_slots`]
16493 // enumerator, and the cross-slot
16494 // [`crate::upgrade::validate_upgrade_from_against_behavior`]
16495 // gate all read), or a callback-shuffled projection (a future
16496 // detour that swapped `on_init` and `on_terminate` through the
16497 // accessor would silently split the paired
16498 // [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
16499 // traversal input from the peer `servico_m2_overlay` emitter's
16500 // projection input from the cross-slot `:state-change`
16501 // composition gate's traversal input).
16502 use crate::BehaviorSpec;
16503 use std::path::PathBuf;
16504 let fixtures: Vec<Option<BehaviorSpec>> = vec![
16505 None,
16506 Some(BehaviorSpec::default()),
16507 Some(BehaviorSpec {
16508 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
16509 ..Default::default()
16510 }),
16511 Some(BehaviorSpec {
16512 on_init: Some(PathBuf::from("lib/init.lisp")),
16513 on_call: Some(PathBuf::from("lib/handlers.lisp")),
16514 on_cast: Some(PathBuf::from("lib/handlers.lisp")),
16515 on_info: Some(PathBuf::from("lib/handlers.lisp")),
16516 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
16517 on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
16518 }),
16519 ];
16520 for behavior in fixtures {
16521 let c = caixa_with_behavior(behavior.clone());
16522 assert_eq!(
16523 c.behavior(),
16524 behavior.as_ref(),
16525 "Caixa::behavior must return :behavior verbatim (got \
16526 {:?}, expected {:?})",
16527 c.behavior(),
16528 behavior.as_ref(),
16529 );
16530 match (c.behavior(), c.behavior.as_ref()) {
16531 (Some(a), Some(b)) => assert!(
16532 std::ptr::eq(a, b),
16533 "Caixa::behavior accessor and self.behavior.as_ref() \
16534 field access must borrow the same backing storage \
16535 — the accessor is the substrate-primitive typed \
16536 dispatch every downstream Servico-M2-overlay \
16537 composite consumer must route through, and a \
16538 reference-identity split would silently break \
16539 every consumer that relied on the borrow sharing \
16540 the composite's storage",
16541 ),
16542 (None, None) => {}
16543 _ => panic!(
16544 "Caixa::behavior presence bit must byte-equal \
16545 self.behavior.is_some() — a presence-bit drift \
16546 would silently split the paired \
16547 StandardLayout::verify per-`:behavior` shape \
16548 gate's traversal head from the peer \
16549 render::servico_m2_overlay M2 overlay emitter's \
16550 traversal head from the cross-slot \
16551 validate_upgrade_from_against_behavior \
16552 composition gate's traversal head from the peer \
16553 Caixa::declared_servico_slots M2 declared-slot \
16554 enumerator's presence probe",
16555 ),
16556 }
16557 assert_eq!(
16558 c.behavior().is_some(),
16559 c.behavior.is_some(),
16560 "Caixa::behavior().is_some() must byte-equal \
16561 self.behavior.is_some() — a presence-bit drift would \
16562 silently split every downstream Option<&BehaviorSpec> \
16563 consumer's partition on the runtime-default arm",
16564 );
16565 }
16566 }
16567
16568 #[test]
16569 fn declared_servico_slots_behavior_arm_routes_through_accessor() {
16570 // Composition pin: [`Caixa::declared_servico_slots`]'s
16571 // `:behavior` presence-probe arm must key off
16572 // [`Caixa::behavior`], not the raw `self.behavior.is_some()`
16573 // field-probe. Structurally: a `Caixa { behavior:
16574 // Some(BehaviorSpec::default()), .. }` must still push
16575 // `M2_AUTHOR_KEY_BEHAVIOR` onto the declared-slot list (the
16576 // presence bit is `Some`, so the M2 kind-coherence gate must
16577 // surface the slot as "declared" even when every per-callback
16578 // path is unset), and a `Caixa { behavior: None, .. }` must
16579 // NOT push the label (the "author omitted the slot entirely"
16580 // partition). The pair jointly pins the accessor + declared-
16581 // slot enumerator composition: any future silent detour that
16582 // had the accessor collapse `Some(BehaviorSpec::default())`
16583 // to `None` (a `.filter(|b| !b.is_empty())` projection) would
16584 // silently absorb the "declared but empty" arm at the
16585 // accessor boundary and the
16586 // [`crate::LayoutError::ServicoSlotsOnNonServico`]
16587 // kind-coherence gate would silently accept a struct-literal
16588 // `Caixa` carrying the drift.
16589 //
16590 // Peer of the sibling
16591 // `declared_servico_slots_limits_arm_routes_through_accessor`
16592 // (b2bd9d7) composition pin on the sibling `:limits` outer-
16593 // `Option<&LimitsSpec>` arm of the same
16594 // [`Caixa::declared_servico_slots`] M2 declared-slot
16595 // enumerator's traversal — same "the enumerator gate must
16596 // route through the substrate-primitive typed dispatch"
16597 // discipline extended onto the outer top-level [`Caixa`]
16598 // `Option<&BehaviorSpec>`-composition surface.
16599 use crate::BehaviorSpec;
16600 let c = caixa_with_behavior(Some(BehaviorSpec::default()));
16601 let slots = c.declared_servico_slots();
16602 assert!(
16603 slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
16604 "declared_servico_slots must push M2_AUTHOR_KEY_BEHAVIOR \
16605 when `:behavior` is Some (even for BehaviorSpec::default()) \
16606 — the accessor and the enumerator gate must route through \
16607 the same substrate-primitive typed dispatch on the outer \
16608 :behavior presence bit (got slots={slots:?})",
16609 );
16610 let c = caixa_with_behavior(None);
16611 let slots = c.declared_servico_slots();
16612 assert!(
16613 !slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
16614 "declared_servico_slots must NOT push M2_AUTHOR_KEY_BEHAVIOR \
16615 when `:behavior` is None — the author-omitted arm must \
16616 route through the accessor's None-return unchanged (got \
16617 slots={slots:?})",
16618 );
16619 }
16620
16621 #[test]
16622 fn servico_m2_overlay_behavior_arm_routes_through_accessor() {
16623 // Composition pin: [`crate::render::servico_m2_overlay`]'s
16624 // per-`:behavior` M2 overlay emit arm must key off
16625 // [`Caixa::behavior`], not the raw `&caixa.behavior`
16626 // field-borrow. Structurally: a `Caixa { behavior:
16627 // Some(BehaviorSpec { on_state_change: Some(...), .. default
16628 // }), .. }` must surface the `M2_KEY_BEHAVIOR` key with the
16629 // per-callback `onStateChange` sub-mapping in the overlay, a
16630 // `Caixa { behavior: Some(BehaviorSpec::default()), .. }`
16631 // must omit the key entirely (the `.is_empty()`-gated inner
16632 // arm elides an empty composite even when the outer presence
16633 // bit is `Some`), and a `Caixa { behavior: None, .. }` must
16634 // also omit the key (the "author omitted the slot entirely"
16635 // partition). The three-fixture family jointly pins the
16636 // accessor + M2 overlay emitter composition: any future
16637 // silent detour that had the accessor return a fresh-cloned
16638 // copy on the `Some` arm (a `BehaviorSpec::clone()`
16639 // projection) would silently break the reference-identity
16640 // pin the peer per-callback `serde_yaml::to_value(behavior)`
16641 // projection reads from.
16642 //
16643 // Peer of the sibling
16644 // `servico_m2_overlay_limits_arm_routes_through_accessor`
16645 // (b2bd9d7) composition pin on the sibling `:limits` outer-
16646 // `Option<&LimitsSpec>` arm of the same
16647 // [`crate::render::servico_m2_overlay`] M2 overlay emitter's
16648 // traversal — same "the emitter must route through the
16649 // substrate-primitive typed dispatch on the outer composite"
16650 // discipline extended onto the outer top-level [`Caixa`]
16651 // `Option<&BehaviorSpec>`-composition surface.
16652 use crate::BehaviorSpec;
16653 use crate::render::{M2_KEY_BEHAVIOR, servico_m2_overlay};
16654 use std::path::PathBuf;
16655 let c = caixa_with_behavior(Some(BehaviorSpec {
16656 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
16657 ..Default::default()
16658 }));
16659 let overlay = servico_m2_overlay(&c).unwrap();
16660 assert!(
16661 overlay.contains_key(M2_KEY_BEHAVIOR),
16662 "servico_m2_overlay must surface M2_KEY_BEHAVIOR when \
16663 `:behavior` carries a non-empty composite — the accessor \
16664 and the M2 overlay emitter must route through the same \
16665 substrate-primitive typed dispatch on the outer :behavior \
16666 composite (got overlay={overlay:?})",
16667 );
16668 let c = caixa_with_behavior(Some(BehaviorSpec::default()));
16669 let overlay = servico_m2_overlay(&c).unwrap();
16670 assert!(
16671 !overlay.contains_key(M2_KEY_BEHAVIOR),
16672 "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
16673 `:behavior` is Some(BehaviorSpec::default()) — the empty \
16674 composite's `.is_empty()`-gated inner arm must elide the \
16675 key regardless of the outer presence bit (got \
16676 overlay={overlay:?})",
16677 );
16678 let c = caixa_with_behavior(None);
16679 let overlay = servico_m2_overlay(&c).unwrap();
16680 assert!(
16681 !overlay.contains_key(M2_KEY_BEHAVIOR),
16682 "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
16683 `:behavior` is None — the author-omitted arm must route \
16684 through the accessor's None-return unchanged (got \
16685 overlay={overlay:?})",
16686 );
16687 }
16688
16689 #[test]
16690 fn behavior_projects_option_ref_by_borrow() {
16691 // The by-borrow pin: [`Caixa::behavior`] returns
16692 // `Option<&BehaviorSpec>` by borrow — the returned reference
16693 // borrows the underlying `Option<BehaviorSpec>` storage of the
16694 // `:behavior` slot and the accessor must not clone the backing
16695 // composite on every call. Peer of the sibling
16696 // `limits_projects_option_ref_by_borrow` (b2bd9d7) by-borrow
16697 // pin on the outer top-level [`Caixa`] `Option<&Composite>`-
16698 // return sub-family — extended here to the second axis of the
16699 // same sub-family: the accessor's returned reference must
16700 // borrow from `&self` (the returned reference's lifetime is
16701 // tied to `&self`), and calling the accessor twice on the same
16702 // [`Caixa`] must yield references that are pointer-equal (the
16703 // underlying byte-buffer is the storage `BehaviorSpec`'s
16704 // allocation, not a fresh copy) as well as value-equal
16705 // (idempotent, no side effects on `&self`).
16706 //
16707 // Pins against a future silent detour that returned an owned
16708 // `BehaviorSpec` (which would type-check via the `Clone` impl
16709 // but silently clone on every call), a `&BehaviorSpec` panic-
16710 // return on the `None` arm (which would collapse the load-
16711 // bearing `Option` presence-bit into a runtime panic), or a
16712 // one-arm-only accessor that returned a saturating composite
16713 // on some sentinel input.
16714 use crate::BehaviorSpec;
16715 use std::path::PathBuf;
16716 for behavior in [
16717 Some(BehaviorSpec::default()),
16718 Some(BehaviorSpec {
16719 on_init: Some(PathBuf::from("lib/init.lisp")),
16720 on_call: Some(PathBuf::from("lib/handlers.lisp")),
16721 on_cast: Some(PathBuf::from("lib/handlers.lisp")),
16722 on_info: Some(PathBuf::from("lib/handlers.lisp")),
16723 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
16724 on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
16725 }),
16726 ] {
16727 let c = caixa_with_behavior(behavior.clone());
16728 let first = c.behavior().unwrap();
16729 let second = c.behavior().unwrap();
16730 assert_eq!(
16731 first, second,
16732 "Caixa::behavior must be idempotent — two successive \
16733 calls on the same &self must return the same \
16734 &BehaviorSpec",
16735 );
16736 assert!(
16737 std::ptr::eq(first, second),
16738 "Caixa::behavior must borrow the underlying \
16739 Option<BehaviorSpec> storage — two successive calls \
16740 must return references with the same backing pointer \
16741 (a fresh BehaviorSpec clone would change the pointer \
16742 on every call)",
16743 );
16744 assert_eq!(
16745 Some(first),
16746 behavior.as_ref(),
16747 "Caixa::behavior must return :behavior verbatim by \
16748 borrow — got {first:?}, expected {:?}",
16749 behavior.as_ref(),
16750 );
16751 }
16752 let c = caixa_with_behavior(None);
16753 assert!(
16754 c.behavior().is_none(),
16755 "Caixa::behavior must return None when :behavior is absent \
16756 — the author-omitted arm must project through the \
16757 accessor's Option::None unchanged",
16758 );
16759 }
16760
16761 // ── Caixa::politicas — outer top-level Option<&MeshPolicy> composite-reference accessor ──
16762
16763 fn caixa_aplicacao_with_politicas(politicas: Option<crate::aplicacao::MeshPolicy>) -> Caixa {
16764 use crate::aplicacao::{Membro, WitContract};
16765 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16766 c.kind = CaixaKind::Aplicacao;
16767 c.membros = vec![Membro {
16768 caixa: "a".into(),
16769 versao: "^0.1".into(),
16770 }];
16771 c.contratos = vec![WitContract {
16772 de: "a".into(),
16773 para: "a".into(),
16774 wit: "wasi:http/proxy".into(),
16775 endpoint: Some("/x".into()),
16776 subject: None,
16777 slot: None,
16778 }];
16779 c.politicas = politicas;
16780 c
16781 }
16782
16783 #[test]
16784 fn politicas_returns_politicas_option_ref_verbatim_across_permutations() {
16785 // The canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
16786 // composite optional-composite-reference-shape pin:
16787 // [`Caixa::politicas`] must return the `:politicas` typed
16788 // `Option<MeshPolicy>` verbatim as an `Option<&MeshPolicy>`
16789 // reference over the same backing storage the raw
16790 // `self.politicas.as_ref()` field access borrows from,
16791 // byte-equal across every representative fixture in the
16792 // accept-set — the author-omitted `None` shape (the "cluster-
16793 // default applies" partition every downstream mesh-artifact
16794 // emitter treats as "emit no `:politicas` overlay"), the
16795 // empty-composite `Some(MeshPolicy { .. default })` shape
16796 // ([`crate::aplicacao::MeshPolicy::is_empty`] holds — every
16797 // per-axis mesh-policy scalar is `None`, so the peer inner
16798 // [`crate::AplicacaoSpec::politicas`] `.is_empty()`-gated
16799 // caixa-mesh overlay elides every per-axis emit but the outer
16800 // presence-bit is `Some`, so [`Caixa::declared_mesh_slots`]
16801 // still pushes the `M3_AUTHOR_KEY_POLITICAS` label), a
16802 // single-axis fixture (only `:timeout` set — the canonical
16803 // shape a latency-sensitive Aplicacao carries), and a
16804 // fully-populated composite (every per-axis mesh-policy
16805 // scalar set — the canonical shape a fully-governed
16806 // Aplicacao carries).
16807 //
16808 // Pins against a future silent detour that returned a fresh-
16809 // cloned [`crate::aplicacao::MeshPolicy`] copy (which would
16810 // type-check via the `Clone` impl but silently break every
16811 // downstream caller that relied on the reference sharing the
16812 // composite's backing identity), a reference to an operator-
16813 // resolved overlay (the future per-cluster
16814 // `:politicas-overrides` slot — its resolution must land at
16815 // exactly this accessor body, not silently divert the raw
16816 // slot away from the peer [`Caixa::declared_mesh_slots`]
16817 // enumerator's presence probe), a
16818 // `None` → `Some(MeshPolicy::default)` cluster-default
16819 // projection (which would collapse the load-bearing
16820 // "author-omitted `:politicas` ⇒ cluster-default applies"
16821 // partition the peer [`Caixa::declared_mesh_slots`]
16822 // enumerator and the peer [`Caixa::aplicacao_view`]
16823 // Aplicacao-composition seed both read), or an axis-shuffled
16824 // projection (a future detour that swapped `timeout` and
16825 // `retries` through the accessor would silently split the
16826 // paired [`Caixa::aplicacao_view`] seed's fold input from the
16827 // sibling M3 mesh-artifact emitter's projection input).
16828 //
16829 // Third outer top-level [`Caixa`] `Option<&Composite>`-return
16830 // composite-reference accessor pin on the substrate primitive
16831 // — peer of the sibling
16832 // `limits_returns_limits_option_ref_verbatim_across_permutations`
16833 // (b2bd9d7) and
16834 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
16835 // (35d8b52) opening tetrad pins on the outer top-level
16836 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
16837 // here to the first of the three M3 mesh-slot axes so the
16838 // opening third of the outer `Option<&Composite>` sub-family
16839 // carries the same "byte-equal, borrow-shared, presence-bit-
16840 // preserved" outer-accessor discipline.
16841 use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
16842 use std::time::Duration;
16843 let fixtures: Vec<Option<MeshPolicy>> = vec![
16844 None,
16845 Some(MeshPolicy::default()),
16846 Some(MeshPolicy {
16847 timeout: Some(Duration::from_secs(30)),
16848 ..Default::default()
16849 }),
16850 Some(MeshPolicy {
16851 timeout: Some(Duration::from_secs(30)),
16852 retries: Some(3),
16853 circuit_breaker: Some(CircuitBreaker {
16854 max_failures: 5,
16855 window: Duration::from_secs(60),
16856 }),
16857 mtls_required: Some(true),
16858 rate_limit: Some(RateLimit {
16859 rate: 100,
16860 window: Duration::from_secs(1),
16861 }),
16862 }),
16863 ];
16864 for politicas in fixtures {
16865 let c = caixa_aplicacao_with_politicas(politicas.clone());
16866 assert_eq!(
16867 c.politicas(),
16868 politicas.as_ref(),
16869 "Caixa::politicas must return :politicas verbatim (got \
16870 {:?}, expected {:?})",
16871 c.politicas(),
16872 politicas.as_ref(),
16873 );
16874 match (c.politicas(), c.politicas.as_ref()) {
16875 (Some(a), Some(b)) => assert!(
16876 std::ptr::eq(a, b),
16877 "Caixa::politicas accessor and self.politicas.as_ref() \
16878 field access must borrow the same backing storage \
16879 — the accessor is the substrate-primitive typed \
16880 dispatch every downstream Aplicacao-mesh-overlay \
16881 composite consumer must route through, and a \
16882 reference-identity split would silently break \
16883 every consumer that relied on the borrow sharing \
16884 the composite's storage",
16885 ),
16886 (None, None) => {}
16887 _ => panic!(
16888 "Caixa::politicas presence bit must byte-equal \
16889 self.politicas.is_some() — a presence-bit drift \
16890 would silently split the paired \
16891 Caixa::aplicacao_view Aplicacao-composition seed's \
16892 traversal head from the peer \
16893 Caixa::declared_mesh_slots M3 declared-slot \
16894 enumerator's presence probe",
16895 ),
16896 }
16897 assert_eq!(
16898 c.politicas().is_some(),
16899 c.politicas.is_some(),
16900 "Caixa::politicas().is_some() must byte-equal \
16901 self.politicas.is_some() — a presence-bit drift would \
16902 silently split every downstream Option<&MeshPolicy> \
16903 consumer's partition on the cluster-default arm",
16904 );
16905 }
16906 }
16907
16908 #[test]
16909 fn declared_mesh_slots_politicas_arm_routes_through_accessor() {
16910 // Composition pin: [`Caixa::declared_mesh_slots`]'s
16911 // `:politicas` presence-probe arm must key off
16912 // [`Caixa::politicas`], not the raw `self.politicas.is_some()`
16913 // field-probe. Structurally: a `Caixa { politicas:
16914 // Some(MeshPolicy::default()), .. }` must still push
16915 // `M3_AUTHOR_KEY_POLITICAS` onto the declared-slot list (the
16916 // presence bit is `Some`, so the M3 kind-coherence gate must
16917 // surface the slot as "declared" even when every per-axis
16918 // scalar is unset), and a `Caixa { politicas: None, .. }` must
16919 // NOT push the label (the "author omitted the slot entirely"
16920 // partition). The pair jointly pins the accessor + declared-
16921 // slot enumerator composition: any future silent detour that
16922 // had the accessor collapse `Some(MeshPolicy::default())` to
16923 // `None` (a `.filter(|p| !p.is_empty())` projection) would
16924 // silently absorb the "declared but empty" arm at the
16925 // accessor boundary and the
16926 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
16927 // coherence gate would silently accept a struct-literal
16928 // `Caixa` carrying the drift.
16929 //
16930 // Peer of the sibling
16931 // `declared_servico_slots_limits_arm_routes_through_accessor`
16932 // (b2bd9d7) and
16933 // `declared_servico_slots_behavior_arm_routes_through_accessor`
16934 // (35d8b52) composition pins on the sibling `:limits` /
16935 // `:behavior` outer-`Option<&Composite>` arms of the peer
16936 // [`Caixa::declared_servico_slots`] M2 declared-slot
16937 // enumerator's traversal — same "the enumerator gate must
16938 // route through the substrate-primitive typed dispatch"
16939 // discipline extended onto the outer top-level [`Caixa`] M3
16940 // mesh-slot family so the [`Caixa::declared_mesh_slots`]
16941 // enumerator carries the same routing invariant as its M2
16942 // sibling.
16943 use crate::aplicacao::MeshPolicy;
16944 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
16945 let slots = c.declared_mesh_slots();
16946 assert!(
16947 slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
16948 "declared_mesh_slots must push M3_AUTHOR_KEY_POLITICAS \
16949 when `:politicas` is Some (even for MeshPolicy::default()) \
16950 — the accessor and the enumerator gate must route through \
16951 the same substrate-primitive typed dispatch on the outer \
16952 :politicas presence bit (got slots={slots:?})",
16953 );
16954 let c = caixa_aplicacao_with_politicas(None);
16955 let slots = c.declared_mesh_slots();
16956 assert!(
16957 !slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
16958 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_POLITICAS \
16959 when `:politicas` is None — the author-omitted arm must \
16960 route through the accessor's None-return unchanged (got \
16961 slots={slots:?})",
16962 );
16963 }
16964
16965 #[test]
16966 fn aplicacao_view_politicas_arm_folds_through_accessor() {
16967 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:politicas`
16968 // Aplicacao-composition seed must fold through
16969 // [`Caixa::politicas`], not the raw
16970 // `self.politicas.clone().unwrap_or_default()` field-borrow.
16971 // Structurally: a `Caixa { politicas: Some(MeshPolicy {
16972 // timeout: Some(30s), .. default }), kind: Aplicacao, .. }`
16973 // must surface a projected [`crate::AplicacaoSpec`] whose
16974 // `politicas().timeout()` field byte-equals the outer
16975 // composite's `timeout` scalar (the fold must project the
16976 // authored composite verbatim), a `Caixa { politicas:
16977 // Some(MeshPolicy::default()), kind: Aplicacao, .. }` must
16978 // surface an [`crate::AplicacaoSpec`] whose `politicas()`
16979 // byte-equals [`crate::aplicacao::MeshPolicy::default`] (the
16980 // fold's empty-composite arm collapses to the same default the
16981 // author-omitted arm does), and a `Caixa { politicas: None,
16982 // kind: Aplicacao, .. }` must surface an
16983 // [`crate::AplicacaoSpec`] whose `politicas()` byte-equals
16984 // [`crate::aplicacao::MeshPolicy::default`] (the "author
16985 // omitted the slot entirely" arm folds through the
16986 // `unwrap_or_default` onto the cluster-default). The triad
16987 // jointly pins the accessor + Aplicacao-composition seed
16988 // composition: any future silent detour that had the accessor
16989 // divert the raw slot away from the seed's fold (an operator-
16990 // resolved overlay's default-fold arm silently differing from
16991 // the raw slot's default-fold arm) would silently split the
16992 // build-time mesh-artifact emission gate from the caixa-mesh
16993 // renderer's Aplicacao-view input at the composition boundary.
16994 use crate::aplicacao::MeshPolicy;
16995 use std::time::Duration;
16996 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy {
16997 timeout: Some(Duration::from_secs(30)),
16998 ..Default::default()
16999 }));
17000 let view = c.aplicacao_view().unwrap();
17001 assert_eq!(
17002 view.politicas().timeout(),
17003 Some(Duration::from_secs(30)),
17004 "Caixa::aplicacao_view must fold the authored :politicas \
17005 :timeout scalar through the accessor verbatim onto the \
17006 projected AplicacaoSpec — a future silent detour at the \
17007 seed's fold arm would surface here as a projected-scalar \
17008 drift (got {:?})",
17009 view.politicas().timeout(),
17010 );
17011 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
17012 let view = c.aplicacao_view().unwrap();
17013 assert_eq!(
17014 view.politicas(),
17015 &MeshPolicy::default(),
17016 "Caixa::aplicacao_view must fold Some(MeshPolicy::default()) \
17017 through the accessor onto MeshPolicy::default — the empty- \
17018 composite arm collapses to the same default the author- \
17019 omitted arm does (got {:?})",
17020 view.politicas(),
17021 );
17022 let c = caixa_aplicacao_with_politicas(None);
17023 let view = c.aplicacao_view().unwrap();
17024 assert_eq!(
17025 view.politicas(),
17026 &MeshPolicy::default(),
17027 "Caixa::aplicacao_view must fold None through the accessor's \
17028 unwrap_or_default onto MeshPolicy::default — the author- \
17029 omitted arm must route through the accessor's None-return \
17030 unchanged (got {:?})",
17031 view.politicas(),
17032 );
17033 }
17034
17035 #[test]
17036 fn politicas_projects_option_ref_by_borrow() {
17037 // The by-borrow pin: [`Caixa::politicas`] returns
17038 // `Option<&MeshPolicy>` by borrow — the returned reference
17039 // borrows the underlying `Option<MeshPolicy>` storage of the
17040 // `:politicas` slot and the accessor must not clone the
17041 // backing composite on every call. Peer of the sibling
17042 // `limits_projects_option_ref_by_borrow` (b2bd9d7) and
17043 // `behavior_projects_option_ref_by_borrow` (35d8b52) by-borrow
17044 // pins on the outer top-level [`Caixa`]
17045 // `Option<&Composite>`-return sub-family — extended here to
17046 // the third axis of the same sub-family: the accessor's
17047 // returned reference must borrow from `&self` (the returned
17048 // reference's lifetime is tied to `&self`), and calling the
17049 // accessor twice on the same [`Caixa`] must yield references
17050 // that are pointer-equal (the underlying byte-buffer is the
17051 // storage `MeshPolicy`'s allocation, not a fresh copy) as
17052 // well as value-equal (idempotent, no side effects on
17053 // `&self`).
17054 //
17055 // Pins against a future silent detour that returned an owned
17056 // `MeshPolicy` (which would type-check via the `Clone` impl
17057 // but silently clone on every call), a `&MeshPolicy` panic-
17058 // return on the `None` arm (which would collapse the load-
17059 // bearing `Option` presence-bit into a runtime panic), or a
17060 // one-arm-only accessor that returned a saturating composite
17061 // on some sentinel input.
17062 use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
17063 use std::time::Duration;
17064 for politicas in [
17065 Some(MeshPolicy::default()),
17066 Some(MeshPolicy {
17067 timeout: Some(Duration::from_secs(30)),
17068 retries: Some(3),
17069 circuit_breaker: Some(CircuitBreaker {
17070 max_failures: 5,
17071 window: Duration::from_secs(60),
17072 }),
17073 mtls_required: Some(true),
17074 rate_limit: Some(RateLimit {
17075 rate: 100,
17076 window: Duration::from_secs(1),
17077 }),
17078 }),
17079 ] {
17080 let c = caixa_aplicacao_with_politicas(politicas.clone());
17081 let first = c.politicas().unwrap();
17082 let second = c.politicas().unwrap();
17083 assert_eq!(
17084 first, second,
17085 "Caixa::politicas must be idempotent — two successive \
17086 calls on the same &self must return the same \
17087 &MeshPolicy",
17088 );
17089 assert!(
17090 std::ptr::eq(first, second),
17091 "Caixa::politicas must borrow the underlying \
17092 Option<MeshPolicy> storage — two successive calls \
17093 must return references with the same backing pointer \
17094 (a fresh MeshPolicy clone would change the pointer on \
17095 every call)",
17096 );
17097 assert_eq!(
17098 Some(first),
17099 politicas.as_ref(),
17100 "Caixa::politicas must return :politicas verbatim by \
17101 borrow — got {first:?}, expected {:?}",
17102 politicas.as_ref(),
17103 );
17104 }
17105 let c = caixa_aplicacao_with_politicas(None);
17106 assert!(
17107 c.politicas().is_none(),
17108 "Caixa::politicas must return None when :politicas is \
17109 absent — the author-omitted arm must project through the \
17110 accessor's Option::None unchanged",
17111 );
17112 }
17113
17114 // ── Caixa::placement — outer top-level Option<&Placement> composite-reference accessor ──
17115
17116 fn caixa_aplicacao_with_placement(placement: Option<crate::aplicacao::Placement>) -> Caixa {
17117 use crate::aplicacao::{Membro, WitContract};
17118 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17119 c.kind = CaixaKind::Aplicacao;
17120 c.membros = vec![Membro {
17121 caixa: "a".into(),
17122 versao: "^0.1".into(),
17123 }];
17124 c.contratos = vec![WitContract {
17125 de: "a".into(),
17126 para: "a".into(),
17127 wit: "wasi:http/proxy".into(),
17128 endpoint: Some("/x".into()),
17129 subject: None,
17130 slot: None,
17131 }];
17132 c.placement = placement;
17133 c
17134 }
17135
17136 #[test]
17137 fn placement_returns_placement_option_ref_verbatim_across_permutations() {
17138 // The canonical per-`Caixa` `:placement` M3 mesh-slot outer-
17139 // composite optional-composite-reference-shape pin:
17140 // [`Caixa::placement`] must return the `:placement` typed
17141 // `Option<Placement>` verbatim as an `Option<&Placement>`
17142 // reference over the same backing storage the raw
17143 // `self.placement.as_ref()` field access borrows from,
17144 // byte-equal across every representative fixture in the
17145 // accept-set — the author-omitted `None` shape (the
17146 // "cluster-default applies" partition every downstream mesh-
17147 // artifact emitter treats as "emit no `:placement` overlay"),
17148 // the empty-composite `Some(Placement { .. default })` shape
17149 // (`estrategia: SingleNode`, empty clusters, no shard-key /
17150 // affinity — the outer presence-bit is `Some` so
17151 // [`Caixa::declared_mesh_slots`] still pushes the
17152 // `M3_AUTHOR_KEY_PLACEMENT` label), a single-axis
17153 // `Replicated`-on-two-clusters fixture (the canonical shape a
17154 // stateless HTTP Aplicacao carries), and a fully-populated
17155 // `Sharded`-with-shard-key-and-affinity fixture (the canonical
17156 // shape a stateful Akka-style cluster-sharding Aplicacao
17157 // carries).
17158 //
17159 // Pins against a future silent detour that returned a fresh-
17160 // cloned [`crate::aplicacao::Placement`] copy (which would
17161 // type-check via the `Clone` impl but silently break every
17162 // downstream caller that relied on the reference sharing the
17163 // composite's backing identity), a reference to an operator-
17164 // resolved overlay (the future per-cluster
17165 // `:placement-overrides` slot — its resolution must land at
17166 // exactly this accessor body, not silently divert the raw
17167 // slot away from the peer [`Caixa::declared_mesh_slots`]
17168 // enumerator's presence probe), a `None` →
17169 // `Some(Placement::default)` cluster-default projection (which
17170 // would collapse the load-bearing "author-omitted `:placement`
17171 // ⇒ cluster-default applies" partition the peer
17172 // [`Caixa::declared_mesh_slots`] enumerator and the peer
17173 // [`Caixa::aplicacao_view`] Aplicacao-composition seed both
17174 // read), or an axis-shuffled projection (a future detour that
17175 // swapped `clusters` and `affinity` through the accessor would
17176 // silently split the paired [`Caixa::aplicacao_view`] seed's
17177 // fold input from the sibling M3 mesh-artifact emitter's
17178 // projection input).
17179 //
17180 // Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
17181 // composite-reference accessor pin on the substrate primitive
17182 // — peer of the sibling
17183 // `limits_returns_limits_option_ref_verbatim_across_permutations`
17184 // (b2bd9d7),
17185 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
17186 // (35d8b52), and
17187 // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
17188 // (5d23d29) opening triad pins on the outer top-level
17189 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
17190 // here to the second of the three M3 mesh-slot axes so the
17191 // opening four-fifths of the outer `Option<&Composite>` sub-
17192 // family carries the same "byte-equal, borrow-shared,
17193 // presence-bit-preserved" outer-accessor discipline.
17194 use crate::aplicacao::{Placement, PlacementStrategy};
17195 let fixtures: Vec<Option<Placement>> = vec![
17196 None,
17197 Some(Placement::default()),
17198 Some(Placement {
17199 estrategia: PlacementStrategy::Replicated,
17200 clusters: vec!["rio".into(), "sao-paulo".into()],
17201 affinity: None,
17202 shard_key: None,
17203 }),
17204 Some(Placement {
17205 estrategia: PlacementStrategy::Sharded,
17206 clusters: vec!["rio".into(), "sao-paulo".into(), "brasilia".into()],
17207 affinity: Some("data-locality".into()),
17208 shard_key: Some("$tenantId".into()),
17209 }),
17210 ];
17211 for placement in fixtures {
17212 let c = caixa_aplicacao_with_placement(placement.clone());
17213 assert_eq!(
17214 c.placement(),
17215 placement.as_ref(),
17216 "Caixa::placement must return :placement verbatim (got \
17217 {:?}, expected {:?})",
17218 c.placement(),
17219 placement.as_ref(),
17220 );
17221 match (c.placement(), c.placement.as_ref()) {
17222 (Some(a), Some(b)) => assert!(
17223 std::ptr::eq(a, b),
17224 "Caixa::placement accessor and self.placement.as_ref() \
17225 field access must borrow the same backing storage \
17226 — the accessor is the substrate-primitive typed \
17227 dispatch every downstream Aplicacao-distribution- \
17228 overlay composite consumer must route through, and \
17229 a reference-identity split would silently break \
17230 every consumer that relied on the borrow sharing \
17231 the composite's storage",
17232 ),
17233 (None, None) => {}
17234 _ => panic!(
17235 "Caixa::placement presence bit must byte-equal \
17236 self.placement.is_some() — a presence-bit drift \
17237 would silently split the paired \
17238 Caixa::aplicacao_view Aplicacao-composition seed's \
17239 traversal head from the peer \
17240 Caixa::declared_mesh_slots M3 declared-slot \
17241 enumerator's presence probe",
17242 ),
17243 }
17244 assert_eq!(
17245 c.placement().is_some(),
17246 c.placement.is_some(),
17247 "Caixa::placement().is_some() must byte-equal \
17248 self.placement.is_some() — a presence-bit drift would \
17249 silently split every downstream Option<&Placement> \
17250 consumer's partition on the cluster-default arm",
17251 );
17252 }
17253 }
17254
17255 #[test]
17256 fn declared_mesh_slots_placement_arm_routes_through_accessor() {
17257 // Composition pin: [`Caixa::declared_mesh_slots`]'s
17258 // `:placement` presence-probe arm must key off
17259 // [`Caixa::placement`], not the raw `self.placement.is_some()`
17260 // field-probe. Structurally: a `Caixa { placement:
17261 // Some(Placement::default()), .. }` must still push
17262 // `M3_AUTHOR_KEY_PLACEMENT` onto the declared-slot list (the
17263 // presence bit is `Some`, so the M3 kind-coherence gate must
17264 // surface the slot as "declared" even when every per-axis
17265 // scalar defers to the cluster-default arm), and a `Caixa {
17266 // placement: None, .. }` must NOT push the label (the "author
17267 // omitted the slot entirely" partition). The pair jointly pins
17268 // the accessor + declared-slot enumerator composition: any
17269 // future silent detour that had the accessor collapse
17270 // `Some(Placement::default())` to `None` (a `.filter(|p|
17271 // p.clusters().is_empty().not())` projection) would silently
17272 // absorb the "declared but empty" arm at the accessor boundary
17273 // and the [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
17274 // kind-coherence gate would silently accept a struct-literal
17275 // `Caixa` carrying the drift.
17276 //
17277 // Peer of the sibling
17278 // `declared_servico_slots_limits_arm_routes_through_accessor`
17279 // (b2bd9d7),
17280 // `declared_servico_slots_behavior_arm_routes_through_accessor`
17281 // (35d8b52), and
17282 // `declared_mesh_slots_politicas_arm_routes_through_accessor`
17283 // (5d23d29) composition pins on the sibling `:limits` /
17284 // `:behavior` / `:politicas` outer-`Option<&Composite>` arms
17285 // — same "the enumerator gate must route through the
17286 // substrate-primitive typed dispatch" discipline extended onto
17287 // the second of the three M3 mesh-slot axes so the
17288 // [`Caixa::declared_mesh_slots`] enumerator carries the same
17289 // routing invariant on the `:placement` arm as the peer
17290 // `:politicas` arm.
17291 use crate::aplicacao::Placement;
17292 let c = caixa_aplicacao_with_placement(Some(Placement::default()));
17293 let slots = c.declared_mesh_slots();
17294 assert!(
17295 slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
17296 "declared_mesh_slots must push M3_AUTHOR_KEY_PLACEMENT \
17297 when `:placement` is Some (even for Placement::default()) \
17298 — the accessor and the enumerator gate must route through \
17299 the same substrate-primitive typed dispatch on the outer \
17300 :placement presence bit (got slots={slots:?})",
17301 );
17302 let c = caixa_aplicacao_with_placement(None);
17303 let slots = c.declared_mesh_slots();
17304 assert!(
17305 !slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
17306 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_PLACEMENT \
17307 when `:placement` is None — the author-omitted arm must \
17308 route through the accessor's None-return unchanged (got \
17309 slots={slots:?})",
17310 );
17311 }
17312
17313 #[test]
17314 fn aplicacao_view_placement_arm_folds_through_accessor() {
17315 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:placement`
17316 // Aplicacao-composition seed must fold through
17317 // [`Caixa::placement`], not the raw
17318 // `self.placement.clone().unwrap_or_default()` field-borrow.
17319 // Structurally: a `Caixa { placement: Some(Placement {
17320 // estrategia: Replicated, clusters: ["rio"], .. default }),
17321 // kind: Aplicacao, .. }` must surface a projected
17322 // [`crate::AplicacaoSpec`] whose `placement().estrategia()` +
17323 // `placement().clusters()` byte-equal the outer composite's
17324 // authored values (the fold must project the authored
17325 // composite verbatim), a `Caixa { placement:
17326 // Some(Placement::default()), kind: Aplicacao, .. }` must
17327 // surface an [`crate::AplicacaoSpec`] whose `placement()`
17328 // byte-equals [`crate::aplicacao::Placement::default`] (the
17329 // fold's empty-composite arm collapses to the same default
17330 // the author-omitted arm does), and a `Caixa { placement:
17331 // None, kind: Aplicacao, .. }` must surface an
17332 // [`crate::AplicacaoSpec`] whose `placement()` byte-equals
17333 // [`crate::aplicacao::Placement::default`] (the "author
17334 // omitted the slot entirely" arm folds through the
17335 // `unwrap_or_default` onto the cluster-default). The triad
17336 // jointly pins the accessor + Aplicacao-composition seed
17337 // composition: any future silent detour that had the accessor
17338 // divert the raw slot away from the seed's fold (an operator-
17339 // resolved overlay's default-fold arm silently differing from
17340 // the raw slot's default-fold arm) would silently split the
17341 // build-time distribution-artifact emission gate from the
17342 // caixa-mesh renderer's Aplicacao-view input at the
17343 // composition boundary.
17344 use crate::aplicacao::{Placement, PlacementStrategy};
17345 let c = caixa_aplicacao_with_placement(Some(Placement {
17346 estrategia: PlacementStrategy::Replicated,
17347 clusters: vec!["rio".into()],
17348 affinity: None,
17349 shard_key: None,
17350 }));
17351 let view = c.aplicacao_view().unwrap();
17352 assert_eq!(
17353 view.placement().estrategia(),
17354 PlacementStrategy::Replicated,
17355 "Caixa::aplicacao_view must fold the authored :placement \
17356 :estrategia scalar through the accessor verbatim onto the \
17357 projected AplicacaoSpec — a future silent detour at the \
17358 seed's fold arm would surface here as a projected-scalar \
17359 drift (got {:?})",
17360 view.placement().estrategia(),
17361 );
17362 assert_eq!(
17363 view.placement().clusters(),
17364 &["rio"],
17365 "Caixa::aplicacao_view must fold the authored :placement \
17366 :clusters list through the accessor verbatim onto the \
17367 projected AplicacaoSpec — a future silent detour at the \
17368 seed's fold arm would surface here as a projected-list \
17369 drift (got {:?})",
17370 view.placement().clusters(),
17371 );
17372 let c = caixa_aplicacao_with_placement(Some(Placement::default()));
17373 let view = c.aplicacao_view().unwrap();
17374 assert_eq!(
17375 view.placement(),
17376 &Placement::default(),
17377 "Caixa::aplicacao_view must fold Some(Placement::default()) \
17378 through the accessor onto Placement::default — the empty- \
17379 composite arm collapses to the same default the author- \
17380 omitted arm does (got {:?})",
17381 view.placement(),
17382 );
17383 let c = caixa_aplicacao_with_placement(None);
17384 let view = c.aplicacao_view().unwrap();
17385 assert_eq!(
17386 view.placement(),
17387 &Placement::default(),
17388 "Caixa::aplicacao_view must fold None through the accessor's \
17389 unwrap_or_default onto Placement::default — the author- \
17390 omitted arm must route through the accessor's None-return \
17391 unchanged (got {:?})",
17392 view.placement(),
17393 );
17394 }
17395
17396 #[test]
17397 fn placement_projects_option_ref_by_borrow() {
17398 // The by-borrow pin: [`Caixa::placement`] returns
17399 // `Option<&Placement>` by borrow — the returned reference
17400 // borrows the underlying `Option<Placement>` storage of the
17401 // `:placement` slot and the accessor must not clone the
17402 // backing composite on every call. Peer of the sibling
17403 // `limits_projects_option_ref_by_borrow` (b2bd9d7),
17404 // `behavior_projects_option_ref_by_borrow` (35d8b52), and
17405 // `politicas_projects_option_ref_by_borrow` (5d23d29) by-borrow
17406 // pins on the outer top-level [`Caixa`]
17407 // `Option<&Composite>`-return sub-family — extended here to
17408 // the fourth axis of the same sub-family: the accessor's
17409 // returned reference must borrow from `&self` (the returned
17410 // reference's lifetime is tied to `&self`), and calling the
17411 // accessor twice on the same [`Caixa`] must yield references
17412 // that are pointer-equal (the underlying byte-buffer is the
17413 // storage `Placement`'s allocation, not a fresh copy) as well
17414 // as value-equal (idempotent, no side effects on `&self`).
17415 //
17416 // Pins against a future silent detour that returned an owned
17417 // `Placement` (which would type-check via the `Clone` impl
17418 // but silently clone on every call), a `&Placement` panic-
17419 // return on the `None` arm (which would collapse the load-
17420 // bearing `Option` presence-bit into a runtime panic), or a
17421 // one-arm-only accessor that returned a saturating composite
17422 // on some sentinel input.
17423 use crate::aplicacao::{Placement, PlacementStrategy};
17424 for placement in [
17425 Some(Placement::default()),
17426 Some(Placement {
17427 estrategia: PlacementStrategy::Sharded,
17428 clusters: vec!["rio".into(), "sao-paulo".into()],
17429 affinity: Some("data-locality".into()),
17430 shard_key: Some("$tenantId".into()),
17431 }),
17432 ] {
17433 let c = caixa_aplicacao_with_placement(placement.clone());
17434 let first = c.placement().unwrap();
17435 let second = c.placement().unwrap();
17436 assert_eq!(
17437 first, second,
17438 "Caixa::placement must be idempotent — two successive \
17439 calls on the same &self must return the same \
17440 &Placement",
17441 );
17442 assert!(
17443 std::ptr::eq(first, second),
17444 "Caixa::placement must borrow the underlying \
17445 Option<Placement> storage — two successive calls \
17446 must return references with the same backing pointer \
17447 (a fresh Placement clone would change the pointer on \
17448 every call)",
17449 );
17450 assert_eq!(
17451 Some(first),
17452 placement.as_ref(),
17453 "Caixa::placement must return :placement verbatim by \
17454 borrow — got {first:?}, expected {:?}",
17455 placement.as_ref(),
17456 );
17457 }
17458 let c = caixa_aplicacao_with_placement(None);
17459 assert!(
17460 c.placement().is_none(),
17461 "Caixa::placement must return None when :placement is \
17462 absent — the author-omitted arm must project through the \
17463 accessor's Option::None unchanged",
17464 );
17465 }
17466
17467 // ── Caixa::entrada — outer top-level Option<&Entrada> composite-reference accessor ──
17468
17469 fn caixa_aplicacao_with_entrada(entrada: Option<crate::aplicacao::Entrada>) -> Caixa {
17470 use crate::aplicacao::{Membro, WitContract};
17471 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17472 c.kind = CaixaKind::Aplicacao;
17473 c.membros = vec![Membro {
17474 caixa: "a".into(),
17475 versao: "^0.1".into(),
17476 }];
17477 c.contratos = vec![WitContract {
17478 de: "a".into(),
17479 para: "a".into(),
17480 wit: "wasi:http/proxy".into(),
17481 endpoint: Some("/x".into()),
17482 subject: None,
17483 slot: None,
17484 }];
17485 c.entrada = entrada;
17486 c
17487 }
17488
17489 #[test]
17490 fn entrada_returns_entrada_option_ref_verbatim_across_permutations() {
17491 // The canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
17492 // composite optional-composite-reference-shape pin:
17493 // [`Caixa::entrada`] must return the `:entrada` typed
17494 // `Option<Entrada>` verbatim as an `Option<&Entrada>`
17495 // reference over the same backing storage the raw
17496 // `self.entrada.as_ref()` field access borrows from,
17497 // byte-equal across every representative fixture in the
17498 // accept-set — the author-omitted `None` shape (the
17499 // "cluster-internal Aplicacao" partition every downstream
17500 // Gateway-API emitter treats as "emit no listener + no
17501 // HTTPRoute"), a bare-`host`/`para` minimum-composite fixture
17502 // (empty `paths` — the resolved-paths fallback the peer
17503 // [`crate::aplicacao::Entrada::resolved_paths`] cascade folds
17504 // onto the substrate catch-all), and a fully-populated
17505 // multi-path-with-non-default-port fixture (the canonical
17506 // shape a public HTTP Aplicacao carries).
17507 //
17508 // Pins against a future silent detour that returned a fresh-
17509 // cloned [`crate::aplicacao::Entrada`] copy (which would
17510 // type-check via the `Clone` impl but silently break every
17511 // downstream caller that relied on the reference sharing the
17512 // composite's backing identity), a reference to an operator-
17513 // resolved overlay (the future per-cluster
17514 // `:entrada-overrides` slot — its resolution must land at
17515 // exactly this accessor body, not silently divert the raw
17516 // slot away from the peer [`Caixa::declared_mesh_slots`]
17517 // enumerator's presence probe), or an axis-shuffled projection
17518 // (a future detour that swapped `host` and `para` through the
17519 // accessor would silently split the paired
17520 // [`Caixa::aplicacao_view`] seed's forward input from the
17521 // sibling M3 gateway-artifact emitter's projection input).
17522 //
17523 // Fifth and final outer top-level [`Caixa`]
17524 // `Option<&Composite>`-return composite-reference accessor pin
17525 // on the substrate primitive — peer of the sibling
17526 // `limits_returns_limits_option_ref_verbatim_across_permutations`
17527 // (b2bd9d7),
17528 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
17529 // (35d8b52),
17530 // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
17531 // (5d23d29), and
17532 // `placement_returns_placement_option_ref_verbatim_across_permutations`
17533 // (4fb8074) opening tetrad pins on the outer top-level
17534 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
17535 // here to the third and final M3 mesh-slot axis so the closed
17536 // outer `Option<&Composite>` sub-family carries the same
17537 // "byte-equal, borrow-shared, presence-bit-preserved" outer-
17538 // accessor discipline across all five arms.
17539 use crate::aplicacao::Entrada;
17540 let fixtures: Vec<Option<Entrada>> = vec![
17541 None,
17542 Some(Entrada {
17543 host: "checkout.quero.cloud".into(),
17544 para: "gateway".into(),
17545 paths: Vec::new(),
17546 port: crate::DEFAULT_SERVICO_PORT,
17547 }),
17548 Some(Entrada {
17549 host: "api.pleme.io".into(),
17550 para: "public-api".into(),
17551 paths: vec!["/v1".into(), "/v2".into()],
17552 port: 8080,
17553 }),
17554 ];
17555 for entrada in fixtures {
17556 let c = caixa_aplicacao_with_entrada(entrada.clone());
17557 assert_eq!(
17558 c.entrada(),
17559 entrada.as_ref(),
17560 "Caixa::entrada must return :entrada verbatim (got \
17561 {:?}, expected {:?})",
17562 c.entrada(),
17563 entrada.as_ref(),
17564 );
17565 match (c.entrada(), c.entrada.as_ref()) {
17566 (Some(a), Some(b)) => assert!(
17567 std::ptr::eq(a, b),
17568 "Caixa::entrada accessor and self.entrada.as_ref() \
17569 field access must borrow the same backing storage \
17570 — the accessor is the substrate-primitive typed \
17571 dispatch every downstream Aplicacao-external- \
17572 gateway composite consumer must route through, and \
17573 a reference-identity split would silently break \
17574 every consumer that relied on the borrow sharing \
17575 the composite's storage",
17576 ),
17577 (None, None) => {}
17578 _ => panic!(
17579 "Caixa::entrada presence bit must byte-equal \
17580 self.entrada.is_some() — a presence-bit drift \
17581 would silently split the paired \
17582 Caixa::aplicacao_view Aplicacao-composition seed's \
17583 traversal head from the peer \
17584 Caixa::declared_mesh_slots M3 declared-slot \
17585 enumerator's presence probe",
17586 ),
17587 }
17588 assert_eq!(
17589 c.entrada().is_some(),
17590 c.entrada.is_some(),
17591 "Caixa::entrada().is_some() must byte-equal \
17592 self.entrada.is_some() — a presence-bit drift would \
17593 silently split every downstream Option<&Entrada> \
17594 consumer's partition on the cluster-internal arm",
17595 );
17596 }
17597 }
17598
17599 #[test]
17600 fn declared_mesh_slots_entrada_arm_routes_through_accessor() {
17601 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:entrada`
17602 // presence-probe arm must key off [`Caixa::entrada`], not the
17603 // raw `self.entrada.is_some()` field-probe. Structurally: a
17604 // `Caixa { entrada: Some(Entrada { host: "...", para: "...",
17605 // paths: [], port: DEFAULT_SERVICO_PORT }), .. }` must push
17606 // `M3_AUTHOR_KEY_ENTRADA` onto the declared-slot list (the
17607 // presence bit is `Some`, so the M3 kind-coherence gate must
17608 // surface the slot as "declared" even when every per-axis
17609 // scalar defers to the substrate catch-all / default port),
17610 // and a `Caixa { entrada: None, .. }` must NOT push the label
17611 // (the "author omitted the slot entirely" partition). The pair
17612 // jointly pins the accessor + declared-slot enumerator
17613 // composition: any future silent detour that had the accessor
17614 // collapse `Some(Entrada { paths: [], .. })` to `None` (a
17615 // `.filter(|e| !e.paths.is_empty())` projection) would silently
17616 // absorb the "declared but empty-paths" arm at the accessor
17617 // boundary and the
17618 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
17619 // coherence gate would silently accept a struct-literal
17620 // `Caixa` carrying the drift.
17621 //
17622 // Peer of the sibling
17623 // `declared_servico_slots_limits_arm_routes_through_accessor`
17624 // (b2bd9d7),
17625 // `declared_servico_slots_behavior_arm_routes_through_accessor`
17626 // (35d8b52),
17627 // `declared_mesh_slots_politicas_arm_routes_through_accessor`
17628 // (5d23d29), and
17629 // `declared_mesh_slots_placement_arm_routes_through_accessor`
17630 // (4fb8074) composition pins on the sibling `:limits` /
17631 // `:behavior` / `:politicas` / `:placement` outer-
17632 // `Option<&Composite>` arms — same "the enumerator gate must
17633 // route through the substrate-primitive typed dispatch"
17634 // discipline extended onto the third and final M3 mesh-slot
17635 // axis so the [`Caixa::declared_mesh_slots`] enumerator now
17636 // carries the routing invariant on every M3 mesh-slot arm.
17637 use crate::aplicacao::Entrada;
17638 let c = caixa_aplicacao_with_entrada(Some(Entrada {
17639 host: "checkout.quero.cloud".into(),
17640 para: "gateway".into(),
17641 paths: Vec::new(),
17642 port: crate::DEFAULT_SERVICO_PORT,
17643 }));
17644 let slots = c.declared_mesh_slots();
17645 assert!(
17646 slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
17647 "declared_mesh_slots must push M3_AUTHOR_KEY_ENTRADA when \
17648 `:entrada` is Some (even for empty-paths / default-port) \
17649 — the accessor and the enumerator gate must route through \
17650 the same substrate-primitive typed dispatch on the outer \
17651 :entrada presence bit (got slots={slots:?})",
17652 );
17653 let c = caixa_aplicacao_with_entrada(None);
17654 let slots = c.declared_mesh_slots();
17655 assert!(
17656 !slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
17657 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_ENTRADA \
17658 when `:entrada` is None — the author-omitted arm must \
17659 route through the accessor's None-return unchanged (got \
17660 slots={slots:?})",
17661 );
17662 }
17663
17664 #[test]
17665 fn aplicacao_view_entrada_arm_folds_through_accessor() {
17666 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:entrada`
17667 // Aplicacao-composition seed must fold through
17668 // [`Caixa::entrada`], not the raw `self.entrada.clone()` field-
17669 // borrow. Structurally: a `Caixa { entrada: Some(Entrada {
17670 // host: "api.pleme.io", para: "public-api", paths: ["/v1"],
17671 // port: 8080 }), kind: Aplicacao, .. }` must surface a projected
17672 // [`crate::AplicacaoSpec`] whose `entrada().unwrap()` byte-
17673 // equals the outer composite's authored value (the fold must
17674 // project the authored composite verbatim), and a `Caixa {
17675 // entrada: None, kind: Aplicacao, .. }` must surface an
17676 // [`crate::AplicacaoSpec`] whose `entrada()` is `None` (the
17677 // "author omitted the slot entirely" arm folds through the
17678 // accessor's `Option::cloned` onto the same `None` presence
17679 // bit — unlike the peer `:politicas` / `:placement` arms
17680 // `:entrada` has no cluster-default fold, the omitted arm
17681 // stays omitted). The pair jointly pins the accessor +
17682 // Aplicacao-composition seed composition: any future silent
17683 // detour that had the accessor divert the raw slot away from
17684 // the seed's fold (an operator-resolved overlay's forward arm
17685 // silently differing from the raw slot's forward arm) would
17686 // silently split the build-time gateway-artifact emission gate
17687 // from the caixa-mesh renderer's Aplicacao-view input at the
17688 // composition boundary.
17689 use crate::aplicacao::Entrada;
17690 let authored = Entrada {
17691 host: "api.pleme.io".into(),
17692 para: "public-api".into(),
17693 paths: vec!["/v1".into()],
17694 port: 8080,
17695 };
17696 let c = caixa_aplicacao_with_entrada(Some(authored.clone()));
17697 let view = c.aplicacao_view().unwrap();
17698 assert_eq!(
17699 view.entrada(),
17700 Some(&authored),
17701 "Caixa::aplicacao_view must fold the authored :entrada \
17702 composite through the accessor verbatim onto the \
17703 projected AplicacaoSpec — a future silent detour at the \
17704 seed's fold arm would surface here as a projected- \
17705 composite drift (got {:?})",
17706 view.entrada(),
17707 );
17708 let c = caixa_aplicacao_with_entrada(None);
17709 let view = c.aplicacao_view().unwrap();
17710 assert!(
17711 view.entrada().is_none(),
17712 "Caixa::aplicacao_view must fold None through the \
17713 accessor's Option::cloned onto None — the author- \
17714 omitted arm must route through the accessor's None-return \
17715 unchanged (got {:?})",
17716 view.entrada(),
17717 );
17718 }
17719
17720 #[test]
17721 fn entrada_projects_option_ref_by_borrow() {
17722 // The by-borrow pin: [`Caixa::entrada`] returns
17723 // `Option<&Entrada>` by borrow — the returned reference
17724 // borrows the underlying `Option<Entrada>` storage of the
17725 // `:entrada` slot and the accessor must not clone the backing
17726 // composite on every call. Peer of the sibling
17727 // `limits_projects_option_ref_by_borrow` (b2bd9d7),
17728 // `behavior_projects_option_ref_by_borrow` (35d8b52),
17729 // `politicas_projects_option_ref_by_borrow` (5d23d29), and
17730 // `placement_projects_option_ref_by_borrow` (4fb8074) by-
17731 // borrow pins on the outer top-level [`Caixa`]
17732 // `Option<&Composite>`-return sub-family — extended here to
17733 // the fifth and final axis of the same sub-family, closing
17734 // the discipline: the accessor's returned reference must
17735 // borrow from `&self` (the returned reference's lifetime is
17736 // tied to `&self`), and calling the accessor twice on the
17737 // same [`Caixa`] must yield references that are pointer-equal
17738 // (the underlying byte-buffer is the storage `Entrada`'s
17739 // allocation, not a fresh copy) as well as value-equal
17740 // (idempotent, no side effects on `&self`).
17741 //
17742 // Pins against a future silent detour that returned an owned
17743 // `Entrada` (which would type-check via the `Clone` impl but
17744 // silently clone on every call), a `&Entrada` panic-return on
17745 // the `None` arm (which would collapse the load-bearing
17746 // `Option` presence-bit into a runtime panic), or a one-arm-
17747 // only accessor that returned a saturating composite on some
17748 // sentinel input.
17749 use crate::aplicacao::Entrada;
17750 for entrada in [
17751 Some(Entrada {
17752 host: "checkout.quero.cloud".into(),
17753 para: "gateway".into(),
17754 paths: Vec::new(),
17755 port: crate::DEFAULT_SERVICO_PORT,
17756 }),
17757 Some(Entrada {
17758 host: "api.pleme.io".into(),
17759 para: "public-api".into(),
17760 paths: vec!["/v1".into(), "/v2".into()],
17761 port: 8080,
17762 }),
17763 ] {
17764 let c = caixa_aplicacao_with_entrada(entrada.clone());
17765 let first = c.entrada().unwrap();
17766 let second = c.entrada().unwrap();
17767 assert_eq!(
17768 first, second,
17769 "Caixa::entrada must be idempotent — two successive \
17770 calls on the same &self must return the same &Entrada",
17771 );
17772 assert!(
17773 std::ptr::eq(first, second),
17774 "Caixa::entrada must borrow the underlying \
17775 Option<Entrada> storage — two successive calls must \
17776 return references with the same backing pointer (a \
17777 fresh Entrada clone would change the pointer on every \
17778 call)",
17779 );
17780 assert_eq!(
17781 Some(first),
17782 entrada.as_ref(),
17783 "Caixa::entrada must return :entrada verbatim by \
17784 borrow — got {first:?}, expected {:?}",
17785 entrada.as_ref(),
17786 );
17787 }
17788 let c = caixa_aplicacao_with_entrada(None);
17789 assert!(
17790 c.entrada().is_none(),
17791 "Caixa::entrada must return None when :entrada is absent \
17792 — the author-omitted arm must project through the \
17793 accessor's Option::None unchanged",
17794 );
17795 }
17796
17797 // ── Caixa::estrategia — outer top-level Option<RestartStrategy> flat-spread supervisor-tree accessor ──
17798
17799 fn caixa_with_estrategia(estrategia: Option<crate::supervisor::RestartStrategy>) -> Caixa {
17800 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17801 c.estrategia = estrategia;
17802 c
17803 }
17804
17805 #[test]
17806 fn estrategia_returns_estrategia_option_verbatim_across_permutations() {
17807 // The canonical per-`Caixa` `:estrategia` M2 supervisor-tree-slot
17808 // flat-spread `Option<RestartStrategy>`-return `Copy`-composite-
17809 // enum-arm scalar shape pin: [`Caixa::estrategia`] must return
17810 // the `:estrategia` typed `Option<crate::supervisor::RestartStrategy>`
17811 // verbatim as an `Option<RestartStrategy>` `Copy`-projected value
17812 // over the same discriminant the raw `self.estrategia` field
17813 // access carries, byte-equal across every representative fixture
17814 // in the accept-set — the author-omitted `None` shape (the
17815 // "defer to [`RestartStrategy::default`] through the
17816 // [`Self::supervisor_view`] `unwrap_or_default()` fold" partition
17817 // every non-`Supervisor`-kind `defcaixa` carries by
17818 // `#[serde(default)]`), and each of the four closed-set variants
17819 // [`RestartStrategy::OneForOne`] / [`RestartStrategy::OneForAll`]
17820 // / [`RestartStrategy::RestForOne`] /
17821 // [`RestartStrategy::SimpleOneForOne`] the author-declared arm
17822 // partitions on.
17823 //
17824 // Pins against a future silent detour that re-derived the
17825 // strategy from a peer axis (an accidental fallback to
17826 // `if children.is_empty() { SimpleOneForOne } else { OneForOne }`
17827 // collapse that read the outer `:children` list-length axis into
17828 // the strategy discriminator at the accessor boundary), a
17829 // stale-derive detour that substituted [`RestartStrategy::default`]
17830 // when the outer `Option` held `None` (which would silently
17831 // collapse the load-bearing "author explicitly declared
17832 // `:estrategia OneForOne`" vs "author omitted the slot and
17833 // inherited the default" partition the [`Self::declared_supervisor_slots`]
17834 // presence-probe reads — the enumerator gate would still push
17835 // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` on the omitted arm, silently
17836 // splitting the paired [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
17837 // kind-coherence gate's traversal head from the
17838 // [`Self::supervisor_view`] `unwrap_or_default()` fold's
17839 // composition head), a reference to an operator-resolved overlay
17840 // (the future per-cluster `:estrategia-overrides` slot — its
17841 // resolution must land at exactly this accessor body, not
17842 // silently divert the raw slot away from a second consumer), or
17843 // an axis-remap projection (a future detour that mapped
17844 // `OneForAll` through the accessor onto `OneForOne` would
17845 // silently split every downstream sibling-restart-strategy
17846 // consumer's per-arm fan-out).
17847 //
17848 // First outer top-level [`Caixa`] `Option<Copy>`-return
17849 // supervisor-tree-slot flat-spread accessor pin on the substrate
17850 // primitive — opens the outer-`Caixa` `Option<Copy>` flat-spread
17851 // projection pattern the sibling per-`Caixa` `:max-restarts` /
17852 // `:restart-window` future outer-scalar pins fold on. Peer of
17853 // the inner-altitude
17854 // `supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
17855 // (eafb619) pin on the post-composition [`SupervisorSpec`]
17856 // altitude — same "the substrate-primitive accessor must byte-
17857 // equal the raw field access verbatim across every author-
17858 // declared value" discipline extended onto the pre-composition
17859 // outer author-surface [`Caixa`] altitude. Peer of the closed
17860 // outer-`Caixa` `Option<&Composite>` composite-reference family
17861 // the sibling `limits` / `behavior` / `politicas` / `placement` /
17862 // `entrada`
17863 // `..._returns_..._option_ref_verbatim_across_permutations` pins
17864 // already carry on the outer `Option<&Composite>` altitude.
17865 use crate::supervisor::RestartStrategy;
17866 let fixtures: Vec<Option<RestartStrategy>> = vec![
17867 None,
17868 Some(RestartStrategy::OneForOne),
17869 Some(RestartStrategy::OneForAll),
17870 Some(RestartStrategy::RestForOne),
17871 Some(RestartStrategy::SimpleOneForOne),
17872 ];
17873 for estrategia in fixtures {
17874 let c = caixa_with_estrategia(estrategia);
17875 assert_eq!(
17876 c.estrategia(),
17877 estrategia,
17878 "Caixa::estrategia must return :estrategia verbatim (got \
17879 {:?}, expected {:?})",
17880 c.estrategia(),
17881 estrategia,
17882 );
17883 assert_eq!(
17884 c.estrategia(),
17885 c.estrategia,
17886 "Caixa::estrategia accessor and self.estrategia field \
17887 access must byte-equal — the accessor is the substrate-\
17888 primitive typed dispatch every downstream supervisor-\
17889 tree flat-spread consumer must route through, and a \
17890 discriminant split would silently break every consumer \
17891 that relied on the accessor sharing the field's own \
17892 Option<Copy> shape",
17893 );
17894 assert_eq!(
17895 c.estrategia().is_some(),
17896 c.estrategia.is_some(),
17897 "Caixa::estrategia().is_some() must byte-equal \
17898 self.estrategia.is_some() — a presence-bit drift would \
17899 silently split the paired Caixa::declared_supervisor_slots \
17900 presence-probe arm from the Caixa::supervisor_view \
17901 unwrap_or_default() fold's composition input",
17902 );
17903 }
17904 }
17905
17906 #[test]
17907 fn declared_supervisor_slots_estrategia_arm_routes_through_accessor() {
17908 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
17909 // `:estrategia` presence-probe arm must key off
17910 // [`Caixa::estrategia`], not the raw `self.estrategia.is_some()`
17911 // field-probe. Structurally: every `Caixa { estrategia:
17912 // Some(RestartStrategy::_), .. }` variant must push
17913 // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` onto the declared-slot list
17914 // (the presence bit is `Some` for every closed-set variant, so
17915 // the M2 supervisor-tree kind-coherence gate must surface the
17916 // slot as "declared" regardless of which variant the author
17917 // picked), and a `Caixa { estrategia: None, .. }` must NOT push
17918 // the label (the "author omitted the slot entirely, deferring
17919 // to [`RestartStrategy::default`] through the supervisor_view
17920 // fold" partition). The pair jointly pins the accessor +
17921 // declared-slot enumerator composition: any future silent detour
17922 // that had the accessor collapse `Some(RestartStrategy::default())`
17923 // to `None` (a `.filter(|e| *e != RestartStrategy::default())`
17924 // projection) would silently absorb the "declared but default-
17925 // valued" arm at the accessor boundary and the
17926 // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
17927 // coherence gate would silently accept a struct-literal `Caixa`
17928 // carrying the drift.
17929 //
17930 // Peer of the sibling per-`Caixa`
17931 // `declared_servico_slots_limits_arm_routes_through_accessor`
17932 // (b2bd9d7) accessor-composition pin on the sibling outer-`Caixa`
17933 // `Option<&LimitsSpec>` composition axis — same "the enumerator
17934 // gate must route through the substrate-primitive typed
17935 // dispatch" discipline extended onto the flat-spread M2
17936 // supervisor-tree `Option<RestartStrategy>`-composition surface,
17937 // opening the outer-`Caixa` supervisor-tree-slot arm of the
17938 // composition-pin family.
17939 use crate::supervisor::RestartStrategy;
17940 for estrategia in [
17941 RestartStrategy::OneForOne,
17942 RestartStrategy::OneForAll,
17943 RestartStrategy::RestForOne,
17944 RestartStrategy::SimpleOneForOne,
17945 ] {
17946 let c = caixa_with_estrategia(Some(estrategia));
17947 let slots = c.declared_supervisor_slots();
17948 assert!(
17949 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
17950 "declared_supervisor_slots must push \
17951 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is \
17952 Some({estrategia:?}) — the accessor and the enumerator \
17953 gate must route through the same substrate-primitive \
17954 typed dispatch on the outer :estrategia presence bit \
17955 (got slots={slots:?})",
17956 );
17957 }
17958 let c = caixa_with_estrategia(None);
17959 let slots = c.declared_supervisor_slots();
17960 assert!(
17961 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
17962 "declared_supervisor_slots must NOT push \
17963 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is None \
17964 — the author-omitted arm must route through the accessor's \
17965 None-return unchanged (got slots={slots:?})",
17966 );
17967 }
17968
17969 #[test]
17970 fn supervisor_view_estrategia_arm_routes_through_accessor() {
17971 // Composition pin: [`Caixa::supervisor_view`]'s per-`:estrategia`
17972 // [`SupervisorSpec`] construction arm must key off
17973 // [`Caixa::estrategia`]'s `unwrap_or_default()` fold, not the raw
17974 // `self.estrategia.unwrap_or_default()` field-fold. Structurally:
17975 // for every `:kind Supervisor` `Caixa` carrying an author-
17976 // declared `Some(RestartStrategy::_)` variant, the composed
17977 // [`SupervisorSpec`]'s `.estrategia` field must byte-equal the
17978 // outer accessor's declared variant unchanged; and for a
17979 // `:kind Supervisor` `Caixa` carrying `None`, the composed
17980 // [`SupervisorSpec`]'s `.estrategia` field must byte-equal
17981 // [`RestartStrategy::default`] (the [`RestartStrategy::OneForOne`]
17982 // arm the flat-spread `unwrap_or_default()` fold projects to on
17983 // the author-omitted arm — this is the *composition* between the
17984 // outer `Option<RestartStrategy>` accessor's presence-bit
17985 // surface and the inner post-composition non-`Option`
17986 // [`SupervisorSpec::estrategia`] altitude). The pair jointly
17987 // pins the accessor + supervisor_view composition: any future
17988 // silent detour that had the accessor promote `None` to
17989 // `Some(RestartStrategy::default())` (a `.or_else(|| Some(RestartStrategy::default()))`
17990 // projection) would silently collapse the two arms into one at
17991 // the accessor boundary and the [`Self::declared_supervisor_slots`]
17992 // presence probe would silently drift from the composition site.
17993 //
17994 // Peer of the sibling M2 supervisor-slot post-composition
17995 // `validate_reads_through_lifted_estrategia_accessor` (eafb619)
17996 // pin on the [`SupervisorSpec::validate`] altitude — this pin
17997 // extends that inner-altitude accessor-routing discipline onto
17998 // the pre-composition outer author-surface [`Caixa`] altitude,
17999 // pinning the composition edge between the flat-spread outer
18000 // `Option<RestartStrategy>` and the composed [`SupervisorSpec`]
18001 // `RestartStrategy` axes.
18002 use crate::CaixaKind;
18003 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
18004 for estrategia in [
18005 RestartStrategy::OneForOne,
18006 RestartStrategy::OneForAll,
18007 RestartStrategy::RestForOne,
18008 RestartStrategy::SimpleOneForOne,
18009 ] {
18010 let mut c = caixa_with_estrategia(Some(estrategia));
18011 c.kind = CaixaKind::Supervisor;
18012 // Route the `SimpleOneForOne ↔ non-SimpleOneForOne` fixture-
18013 // shape partition through the [`gen_platform::IsVariant`]
18014 // derive-generated
18015 // [`RestartStrategy::is_simple_one_for_one`] predicate rather
18016 // than the raw `matches!(estrategia, RestartStrategy::
18017 // SimpleOneForOne)` open-coded pattern-match — same closed-
18018 // set-typed-enum arm-discriminator dispatch discipline the
18019 // sibling [`crate::upgrade::UpgradeInstruction::is_restart`]
18020 // convergence (915a934) extended onto its two paired positive
18021 // / negated `matches!` sites and the peer
18022 // [`crate::aplicacao::PlacementStrategy`] `IsVariant`-derived
18023 // predicate convergence (766ec63) extended onto the M3 mesh-
18024 // slot per-`:placement` distribution-strategy discriminator
18025 // axis. See the sibling `supervisor::tests::
18026 // round_trip_all_strategies` and
18027 // `supervisor::tests::supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
18028 // fixtures — the three sites (all test-only,
18029 // acknowledged in 915a934's Prior-commits footnote as the
18030 // outstanding follow-up) now consult one typed dispatch on
18031 // the substrate primitive.
18032 c.children = if estrategia.is_simple_one_for_one() {
18033 Vec::new()
18034 } else {
18035 vec![ChildSpec {
18036 caixa: "worker".into(),
18037 versao: "^0.1".into(),
18038 restart: RestartPolicy::Permanent,
18039 }]
18040 };
18041 let view = c.supervisor_view().expect(
18042 "supervisor_view must materialize a SupervisorSpec for a \
18043 :kind Supervisor Caixa carrying a Some(:estrategia) slot",
18044 );
18045 assert_eq!(
18046 view.estrategia(),
18047 c.estrategia().unwrap(),
18048 "supervisor_view must carry the outer Caixa::estrategia() \
18049 declared variant onto the composed SupervisorSpec.estrategia \
18050 field verbatim on the Some arm (got {:?}, expected {:?})",
18051 view.estrategia(),
18052 c.estrategia().unwrap(),
18053 );
18054 }
18055 // The author-omitted arm: outer `None` → composed
18056 // `RestartStrategy::default()` through the flat-spread
18057 // `unwrap_or_default()` fold.
18058 let mut c = caixa_with_estrategia(None);
18059 c.kind = CaixaKind::Supervisor;
18060 // Populate children so the sibling supervisor slots are coherent
18061 // for the [`Self::supervisor_view`] projection; the `:estrategia`
18062 // arm still defers to [`RestartStrategy::default`] on the
18063 // author-omitted arm even when the sibling slots carry values.
18064 c.children = vec![ChildSpec {
18065 caixa: "worker".into(),
18066 versao: "^0.1".into(),
18067 restart: RestartPolicy::Permanent,
18068 }];
18069 let view = c.supervisor_view().expect(
18070 "supervisor_view must materialize a SupervisorSpec for a \
18071 :kind Supervisor Caixa carrying a None `:estrategia` slot",
18072 );
18073 assert_eq!(
18074 view.estrategia(),
18075 RestartStrategy::default(),
18076 "supervisor_view must project the outer Caixa::estrategia() \
18077 None arm onto RestartStrategy::default() through the flat-\
18078 spread unwrap_or_default() fold (got {:?}, expected {:?})",
18079 view.estrategia(),
18080 RestartStrategy::default(),
18081 );
18082 assert!(
18083 c.estrategia().is_none(),
18084 "Caixa::estrategia() must remain None on the author-omitted \
18085 arm — the supervisor_view fold must not mutate the outer \
18086 flat-spread presence bit",
18087 );
18088 }
18089
18090 #[test]
18091 fn estrategia_projects_option_by_copy() {
18092 // The by-`Copy` pin: [`Caixa::estrategia`] returns
18093 // `Option<RestartStrategy>` by value (`RestartStrategy: Copy`) —
18094 // the accessor does not borrow `&self` past the call (no
18095 // lifetime on the return type), and calling the accessor twice
18096 // on the same [`Caixa`] must yield discriminant-equal values
18097 // (idempotent, no side effects on `&self`). Peer of the sibling
18098 // outer-`Caixa` `Option<&Composite>` by-borrow
18099 // `limits_projects_option_ref_by_borrow` (b2bd9d7) /
18100 // `behavior_projects_option_ref_by_borrow` (35d8b52) /
18101 // `politicas_projects_option_ref_by_borrow` (5d23d29) /
18102 // `placement_projects_option_ref_by_borrow` (4fb8074) /
18103 // `entrada_projects_option_ref_by_borrow` (e4128e4) by-borrow
18104 // pins on the outer-`Caixa` `Option<&Composite>`-return axes —
18105 // extended here to the outer-`Caixa` `Option<Copy>`-return
18106 // flat-spread axis. The `Copy` discipline replaces the pointer-
18107 // equality claim the by-borrow siblings pin (a fresh `Copy` of a
18108 // `Copy` discriminant is definitionally the same discriminant, so
18109 // the axis reduces to discriminant equality).
18110 //
18111 // Pins against a future silent detour that returned a fresh
18112 // `Option<&RestartStrategy>` (which would type-check but silently
18113 // introduce a borrow of `&self` past the call, collapsing the
18114 // load-bearing "no lifetime on the return type" `Copy` projection
18115 // the flat-spread axis's `Option<Copy>` shape carries), a stale-
18116 // read side effect that flipped the outer discriminant on
18117 // successive calls, or an axis-remap projection that returned a
18118 // different variant than the field storage.
18119 use crate::supervisor::RestartStrategy;
18120 for estrategia in [
18121 Some(RestartStrategy::OneForOne),
18122 Some(RestartStrategy::OneForAll),
18123 Some(RestartStrategy::RestForOne),
18124 Some(RestartStrategy::SimpleOneForOne),
18125 ] {
18126 let c = caixa_with_estrategia(estrategia);
18127 let first = c.estrategia();
18128 let second = c.estrategia();
18129 assert_eq!(
18130 first, second,
18131 "Caixa::estrategia must be idempotent — two successive \
18132 calls on the same &self must return the same \
18133 Option<RestartStrategy>",
18134 );
18135 assert_eq!(
18136 first, estrategia,
18137 "Caixa::estrategia must return :estrategia verbatim by \
18138 Copy — got {first:?}, expected {estrategia:?}",
18139 );
18140 }
18141 let c = caixa_with_estrategia(None);
18142 assert!(
18143 c.estrategia().is_none(),
18144 "Caixa::estrategia must return None when :estrategia is \
18145 absent — the author-omitted arm must project through the \
18146 accessor's Option::None unchanged",
18147 );
18148 }
18149
18150 // ── Caixa::max_restarts / Caixa::restart_window —
18151 // outer top-level M2 supervisor-tree-slot flat-spread accessors
18152 // (Option<u32> / Option<&str>) folding on the ed04d3c
18153 // Caixa::estrategia Option<Copy> sub-family ─────────────────────
18154
18155 fn caixa_with_max_restarts(max_restarts: Option<u32>) -> Caixa {
18156 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
18157 c.max_restarts = max_restarts;
18158 c
18159 }
18160
18161 fn caixa_supervisor_with_max_restarts_and_window(
18162 max_restarts: Option<u32>,
18163 restart_window: Option<&str>,
18164 ) -> Caixa {
18165 use crate::CaixaKind;
18166 use crate::supervisor::{ChildSpec, RestartPolicy};
18167 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
18168 c.kind = CaixaKind::Supervisor;
18169 c.max_restarts = max_restarts;
18170 c.restart_window = restart_window.map(str::to_string);
18171 c.children = vec![ChildSpec {
18172 caixa: "worker".into(),
18173 versao: "^0.1".into(),
18174 restart: RestartPolicy::Permanent,
18175 }];
18176 c
18177 }
18178
18179 #[test]
18180 fn max_restarts_returns_max_restarts_option_verbatim_across_permutations() {
18181 // Value-shape pin: [`Caixa::max_restarts`] returns the
18182 // `:max-restarts` typed `Option<u32>` verbatim, `Copy`-projected
18183 // from the typed slot's own storage, byte-equal across the
18184 // author-omitted `None` arm (the "defer to the
18185 // [`Self::supervisor_view`] `unwrap_or(5)` OTP-canonical
18186 // `{intensity, 5, 60}` default" partition every
18187 // non-`Supervisor`-kind caixa carries by `#[serde(default)]`)
18188 // and each of the representative fixtures in the accept-set —
18189 // `0` (the zero-floor arm the peer
18190 // [`crate::supervisor::SupervisorSpec::validate`]
18191 // [`crate::SupervisorError::ZeroMaxRestarts`] gate refuses on
18192 // the post-composition altitude — the accessor must ship the
18193 // raw slot verbatim so struct-literal fixtures continue to
18194 // expose the zero at the accessor boundary), the OTP-canonical
18195 // `5` default (`{intensity, 5, 60}` worker-supervisor from
18196 // Learn You Some Erlang), `1000` (the
18197 // [`SUPERVISOR_MAX_RESTARTS_MAX`] cap the peer post-composition
18198 // upper-bound gate accepts on the boundary), `u32::MAX` (a
18199 // past-the-cap sentinel that the substrate-primitive accessor
18200 // must still ship verbatim). Second outer top-level
18201 // [`Caixa`] `Option<Copy>`-return supervisor-tree flat-spread
18202 // pin — folds on the sibling
18203 // `estrategia_returns_estrategia_option_verbatim_across_permutations`
18204 // (ed04d3c) pin's `Option<Copy>` shape, extending the sub-family
18205 // onto the sibling `Option<u32>` restart-budget-count arm.
18206 let fixtures: Vec<Option<u32>> = vec![None, Some(0), Some(5), Some(1000), Some(u32::MAX)];
18207 for max_restarts in fixtures {
18208 let c = caixa_with_max_restarts(max_restarts);
18209 assert_eq!(
18210 c.max_restarts(),
18211 max_restarts,
18212 "Caixa::max_restarts must return :max-restarts verbatim \
18213 (got {:?}, expected {max_restarts:?})",
18214 c.max_restarts(),
18215 );
18216 assert_eq!(
18217 c.max_restarts(),
18218 c.max_restarts,
18219 "Caixa::max_restarts accessor and self.max_restarts \
18220 field access must byte-equal — a presence-bit or count \
18221 drift would silently split the paired \
18222 Caixa::declared_supervisor_slots presence-probe arm \
18223 from the Caixa::supervisor_view unwrap_or(5) fold's \
18224 composition input",
18225 );
18226 }
18227 }
18228
18229 #[test]
18230 fn max_restarts_projects_option_by_copy() {
18231 // The by-`Copy` pin: [`Caixa::max_restarts`] returns
18232 // `Option<u32>` by value (`u32: Copy`) — the accessor does not
18233 // borrow `&self` past the call (no lifetime on the return type),
18234 // and calling the accessor twice on the same [`Caixa`] must
18235 // yield equal values (idempotent, no side effects). Peer of the
18236 // sibling `estrategia_projects_option_by_copy` (ed04d3c) pin on
18237 // the outer-`Caixa` `Option<Copy>`-return flat-spread axis.
18238 for max_restarts in [Some(0u32), Some(5), Some(1000), Some(u32::MAX), None] {
18239 let c = caixa_with_max_restarts(max_restarts);
18240 let first = c.max_restarts();
18241 let second = c.max_restarts();
18242 assert_eq!(
18243 first, second,
18244 "Caixa::max_restarts must be idempotent — two successive \
18245 calls on the same &self must return the same Option<u32>",
18246 );
18247 assert_eq!(
18248 first, max_restarts,
18249 "Caixa::max_restarts must return :max-restarts verbatim \
18250 by Copy — got {first:?}, expected {max_restarts:?}",
18251 );
18252 }
18253 }
18254
18255 #[test]
18256 fn declared_supervisor_slots_max_restarts_arm_routes_through_accessor() {
18257 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
18258 // `:max-restarts` presence-probe arm must key off
18259 // [`Caixa::max_restarts`], not the raw
18260 // `self.max_restarts.is_some()` field-probe. Structurally: every
18261 // `Caixa { max_restarts: Some(_), .. }` variant must push
18262 // `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS` onto the declared-slot
18263 // list (the presence bit is `Some` for every representative
18264 // count, so the M2 kind-coherence gate must surface the slot as
18265 // "declared"), and a `Caixa { max_restarts: None, .. }` must
18266 // NOT push the label. Peer of the sibling
18267 // `declared_supervisor_slots_estrategia_arm_routes_through_accessor`
18268 // (ed04d3c) composition pin — same routing-through-accessor
18269 // discipline extended onto the sibling flat-spread `Option<u32>`
18270 // arm.
18271 for max_restarts in [0u32, 5, 1000, u32::MAX] {
18272 let c = caixa_with_max_restarts(Some(max_restarts));
18273 let slots = c.declared_supervisor_slots();
18274 assert!(
18275 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
18276 "declared_supervisor_slots must push \
18277 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` \
18278 is Some({max_restarts}) — the accessor and the \
18279 enumerator gate must route through the same \
18280 substrate-primitive typed dispatch on the outer \
18281 :max-restarts presence bit (got slots={slots:?})",
18282 );
18283 }
18284 let c = caixa_with_max_restarts(None);
18285 let slots = c.declared_supervisor_slots();
18286 assert!(
18287 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
18288 "declared_supervisor_slots must NOT push \
18289 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` is \
18290 None — the author-omitted arm must route through the \
18291 accessor's None-return unchanged (got slots={slots:?})",
18292 );
18293 }
18294
18295 #[test]
18296 fn supervisor_view_max_restarts_arm_routes_through_accessor() {
18297 // Composition pin: [`Caixa::supervisor_view`]'s per-`:max-restarts`
18298 // [`SupervisorSpec`] construction arm must key off
18299 // [`Caixa::max_restarts`]'s `unwrap_or(5)` fold, not the raw
18300 // `self.max_restarts.unwrap_or(5)` field-fold. Structurally: for
18301 // every `:kind Supervisor` `Caixa` carrying an author-declared
18302 // `Some(n)`, the composed [`SupervisorSpec`]'s `.max_restarts()`
18303 // must byte-equal `n`; and for a `:kind Supervisor` `Caixa`
18304 // carrying `None`, the composed [`SupervisorSpec`]'s
18305 // `.max_restarts()` must byte-equal the OTP-canonical `5`. Peer
18306 // of the sibling
18307 // `supervisor_view_estrategia_arm_routes_through_accessor`
18308 // (ed04d3c) composition pin.
18309 for max_restarts in [1u32, 5, 1000] {
18310 let c = caixa_supervisor_with_max_restarts_and_window(Some(max_restarts), None);
18311 let view = c.supervisor_view().expect(
18312 "supervisor_view must materialize a SupervisorSpec for a \
18313 :kind Supervisor Caixa carrying a Some(:max-restarts)",
18314 );
18315 assert_eq!(
18316 view.max_restarts(),
18317 max_restarts,
18318 "supervisor_view must carry the outer \
18319 Caixa::max_restarts() Some arm onto the composed \
18320 SupervisorSpec.max_restarts field verbatim (got {}, \
18321 expected {max_restarts})",
18322 view.max_restarts(),
18323 );
18324 }
18325 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
18326 let view = c.supervisor_view().expect(
18327 "supervisor_view must materialize a SupervisorSpec for a \
18328 :kind Supervisor Caixa carrying a None :max-restarts",
18329 );
18330 assert_eq!(
18331 view.max_restarts(),
18332 5,
18333 "supervisor_view must project the outer \
18334 Caixa::max_restarts() None arm onto the OTP-canonical \
18335 {{intensity, 5, 60}} default (5) through the flat-spread \
18336 unwrap_or(5) fold (got {})",
18337 view.max_restarts(),
18338 );
18339 assert!(
18340 c.max_restarts().is_none(),
18341 "Caixa::max_restarts() must remain None on the author-\
18342 omitted arm — the supervisor_view fold must not mutate \
18343 the outer flat-spread presence bit",
18344 );
18345 }
18346
18347 #[test]
18348 fn supervisor_view_estrategia_fallback_routes_through_lifted_default() {
18349 // Composition pin: [`Caixa::supervisor_view`]'s author-omitted
18350 // `:estrategia` arm must degrade onto the substrate-canonical
18351 // [`crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT`] typed
18352 // `pub const` — the Erlang/OTP-canonical `one_for_one` strategy
18353 // half of Learn You Some Erlang's `{one_for_one, intensity, 5, 60}`
18354 // worker-supervisor default — rather than the transitively-
18355 // derived [`crate::supervisor::RestartStrategy::default`] route
18356 // the prior `.unwrap_or_default()` fold reached for. Prior to the
18357 // lift the composition site carried `.unwrap_or_default()` with
18358 // no compile-time link back to the shared OTP-canonical strategy
18359 // default that the paired [`crate::supervisor::Default for
18360 // RestartStrategy`] impl and the [`crate::supervisor::Default for
18361 // SupervisorSpec`] impl's struct-literal `estrategia` field both
18362 // (now) route through the same lifted constant — so a future
18363 // rebrand of the OTP-canonical strategy default (an OTP
18364 // `rest_for_one` widening once the substrate discovers startup-
18365 // order-coupled child cohorts as the more common worker-
18366 // supervisor shape, a per-cluster overlay the operator pins
18367 // through the MESH-COMPOSITION §III.2 supervision-canary
18368 // `:estrategia-overrides` roadmap slot) would have had to migrate
18369 // the paired `MaxIntensity` + `Period` halves through the lifted
18370 // constants and the `one_for_one` half through a
18371 // `RestartStrategy::default()` route in lockstep or a
18372 // `:kind Supervisor` caixa carrying an author-omitted
18373 // `:estrategia` slot would silently resolve to a `SupervisorSpec`
18374 // whose `estrategia` disagreed with the paired
18375 // `SupervisorSpec::default()` view. Byte-parity against the
18376 // lifted constant closes the split. Peer of the sibling
18377 // [`supervisor_view_max_restarts_fallback_routes_through_lifted_default`]
18378 // composition pin on the paired `MaxIntensity` half + the
18379 // [`crate::supervisor::restart_strategy_default_routes_through_lifted_default`]
18380 // + [`crate::supervisor::supervisor_spec_default_estrategia_routes_through_lifted_default`]
18381 // pins on the sibling entry points onto the shared substrate
18382 // constant.
18383 use crate::CaixaKind;
18384 use crate::supervisor::{ChildSpec, RestartPolicy};
18385 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
18386 c.kind = CaixaKind::Supervisor;
18387 c.estrategia = None;
18388 c.children = vec![ChildSpec {
18389 caixa: "worker".into(),
18390 versao: "^0.1".into(),
18391 restart: RestartPolicy::Permanent,
18392 }];
18393 let view = c.supervisor_view().expect(
18394 "supervisor_view must materialize a SupervisorSpec for a \
18395 :kind Supervisor Caixa carrying a None :estrategia",
18396 );
18397 assert_eq!(
18398 view.estrategia(),
18399 crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT,
18400 "supervisor_view must degrade the outer \
18401 Caixa::estrategia() None arm onto the lifted \
18402 SUPERVISOR_ESTRATEGIA_DEFAULT typed pub const (got {:?}, \
18403 expected {:?})",
18404 view.estrategia(),
18405 crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT,
18406 );
18407 }
18408
18409 #[test]
18410 fn supervisor_view_max_restarts_fallback_routes_through_lifted_default() {
18411 // Composition pin: [`Caixa::supervisor_view`]'s author-omitted
18412 // `:max-restarts` arm must degrade onto the substrate-canonical
18413 // [`crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT`] typed
18414 // `pub const` — the Erlang/OTP-canonical `{intensity, 5, 60}`
18415 // `MaxIntensity` default — rather than a raw `5` literal. Prior
18416 // to the lift the composition site carried an inline
18417 // `.unwrap_or(5)` with no compile-time link back to the shared
18418 // OTP-canonical default that the serde-side
18419 // `#[serde(default = "default_max_restarts")]` wire-format arm
18420 // and the [`Default for crate::supervisor::SupervisorSpec`]
18421 // struct-literal default arm both key off — so a future rebrand
18422 // of the OTP-canonical default (Elixir's `Supervisor` `3`
18423 // default, a per-cluster overlay the operator pins through the
18424 // MESH-COMPOSITION §III.2 supervision-canary
18425 // `:supervisor :max-restarts-overrides` roadmap slot) would
18426 // have had to be threaded through both the serde-side helper
18427 // and this view-construction arm in lockstep or a `:kind
18428 // Supervisor` caixa carrying `:max-restarts ()` would silently
18429 // resolve to a `SupervisorSpec` whose `max_restarts` disagreed
18430 // with the same fixture's serde-side `SupervisorSpec` view (an
18431 // author-omitted slot round-tripping through
18432 // `SupervisorSpec::default()` to the lifted constant, then
18433 // splitting to a stale literal past `supervisor_view`).
18434 // Byte-parity against the lifted constant closes the split.
18435 // Peer of the sibling
18436 // [`crate::supervisor::default_max_restarts_helper_routes_through_lifted_default`]
18437 // + [`crate::supervisor::supervisor_spec_default_max_restarts_routes_through_lifted_default`]
18438 // composition pins that close the same routing on the two
18439 // sibling entry points onto the shared substrate constant.
18440 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
18441 let view = c.supervisor_view().expect(
18442 "supervisor_view must materialize a SupervisorSpec for a \
18443 :kind Supervisor Caixa carrying a None :max-restarts",
18444 );
18445 assert_eq!(
18446 view.max_restarts(),
18447 crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT,
18448 "supervisor_view must degrade the outer \
18449 Caixa::max_restarts() None arm onto the lifted \
18450 SUPERVISOR_MAX_RESTARTS_DEFAULT typed pub const (got {}, \
18451 expected {})",
18452 view.max_restarts(),
18453 crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT,
18454 );
18455 }
18456
18457 #[test]
18458 fn restart_window_returns_restart_window_option_verbatim_across_permutations() {
18459 // Value-shape pin: [`Caixa::restart_window`] returns the
18460 // `:restart-window` typed `Option<String>` verbatim as an
18461 // `Option<&str>`, borrowed from the typed slot's own storage,
18462 // byte-equal across the author-omitted `None` arm and each of
18463 // the representative fixtures in the accept-set — the canonical
18464 // `"60s"` from `{intensity, 5, 60}`, the sibling
18465 // canonical-magnitude forms (`"5m"` / `"1h"` / `"500ms"` / `"30"`
18466 // / `"0s"`) the shared codec's positive-set sweep pin covers,
18467 // plus a past-the-guard sentinel (`"1.5s"` — the fractional-
18468 // seconds drift the sibling [`Self::validate_restart_window`]
18469 // gate refuses; the accessor must ship the raw slot verbatim
18470 // so struct-literal fixtures continue to expose the drift at
18471 // the accessor boundary). Third outer top-level [`Caixa`]
18472 // supervisor-tree flat-spread pin — extends the sub-family onto
18473 // the sibling `Option<&str>` raw-duration-string arm.
18474 for window in [
18475 None,
18476 Some("60s"),
18477 Some("5m"),
18478 Some("1h"),
18479 Some("500ms"),
18480 Some("1.5s"),
18481 Some(""),
18482 ] {
18483 let c = caixa_with_restart_window(window);
18484 assert_eq!(
18485 c.restart_window(),
18486 window,
18487 "Caixa::restart_window must return :restart-window \
18488 verbatim as Option<&str> (got {:?}, expected {window:?})",
18489 c.restart_window(),
18490 );
18491 assert_eq!(
18492 c.restart_window(),
18493 c.restart_window.as_deref(),
18494 "Caixa::restart_window accessor and \
18495 self.restart_window.as_deref() field access must \
18496 byte-equal — a byte-level drift would silently split \
18497 the paired Caixa::declared_supervisor_slots \
18498 presence-probe arm from the \
18499 Caixa::validate_restart_window shared-codec gate and \
18500 the Caixa::supervisor_view soft-swallowing fold",
18501 );
18502 }
18503 }
18504
18505 #[test]
18506 fn restart_window_projects_slice_by_borrow() {
18507 // The by-borrow pin: [`Caixa::restart_window`] returns
18508 // `Option<&str>` by borrow — the returned string slice borrows
18509 // the underlying `Option<String>` storage of the `:restart-window`
18510 // slot and the accessor must not clone on every call. Peer of
18511 // the sibling outer top-level [`Caixa`] `Option<&str>`-return
18512 // by-borrow pins on the universal-axis scalar family
18513 // (`licenca_projects_option_ref_by_borrow` /
18514 // `descricao_projects_option_ref_by_borrow` and siblings) —
18515 // extended onto the M2 supervisor-tree flat-spread
18516 // `Option<&str>` raw-duration-string axis.
18517 for window in [None, Some("60s"), Some("5m"), Some("")] {
18518 let c = caixa_with_restart_window(window);
18519 let first = c.restart_window();
18520 let second = c.restart_window();
18521 assert_eq!(
18522 first, second,
18523 "Caixa::restart_window must be idempotent — two \
18524 successive calls on the same &self must return the \
18525 same Option<&str>",
18526 );
18527 if let (Some(a), Some(b)) = (first, second) {
18528 assert_eq!(
18529 a.as_ptr(),
18530 b.as_ptr(),
18531 "Caixa::restart_window must borrow the underlying \
18532 String storage — two successive Some-arm calls must \
18533 return slices with the same backing pointer (a fresh \
18534 String clone would change the pointer on every call)",
18535 );
18536 }
18537 assert_eq!(
18538 first, window,
18539 "Caixa::restart_window must return :restart-window \
18540 verbatim by borrow — got {first:?}, expected {window:?}",
18541 );
18542 }
18543 }
18544
18545 #[test]
18546 fn declared_supervisor_slots_restart_window_arm_routes_through_accessor() {
18547 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
18548 // `:restart-window` presence-probe arm must key off
18549 // [`Caixa::restart_window`], not the raw
18550 // `self.restart_window.is_some()` field-probe. Structurally:
18551 // every `Caixa { restart_window: Some(_), .. }` must push
18552 // `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` onto the declared-slot
18553 // list, and a `Caixa { restart_window: None, .. }` must NOT
18554 // push the label. Peer of the sibling
18555 // `declared_supervisor_slots_max_restarts_arm_routes_through_accessor`
18556 // routing pin.
18557 for window in ["60s", "5m", "1h", "500ms", "1.5s", ""] {
18558 let c = caixa_with_restart_window(Some(window));
18559 let slots = c.declared_supervisor_slots();
18560 assert!(
18561 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
18562 "declared_supervisor_slots must push \
18563 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when \
18564 `:restart-window` is Some({window:?}) — the accessor \
18565 and the enumerator gate must route through the same \
18566 substrate-primitive typed dispatch on the outer \
18567 :restart-window presence bit (got slots={slots:?})",
18568 );
18569 }
18570 let c = caixa_with_restart_window(None);
18571 let slots = c.declared_supervisor_slots();
18572 assert!(
18573 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
18574 "declared_supervisor_slots must NOT push \
18575 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when `:restart-window` \
18576 is None — the author-omitted arm must route through the \
18577 accessor's None-return unchanged (got slots={slots:?})",
18578 );
18579 }
18580
18581 #[test]
18582 fn validate_restart_window_arm_routes_through_accessor() {
18583 // Composition pin: [`Caixa::validate_restart_window`]'s
18584 // shared-codec fold arm must key off [`Caixa::restart_window`],
18585 // not the raw `self.restart_window.as_deref()` field-projection.
18586 // Structurally: (1) `None` → `Ok(())` (the "omit the slot to
18587 // express no reset" canonical shape); (2) a canonical `Some`
18588 // arm (`"60s"`) → `Ok(())`; (3) a codec-rejected `Some` arm
18589 // (`"1.5s"`) → `Err(RestartWindowMalformed { restart_window,
18590 // .. })` carrying the offending raw string verbatim. The three
18591 // arms jointly pin that the validator's raw-string binding is
18592 // the accessor's return, not a peer projection — any future
18593 // silent detour that had the accessor collapse `Some("")` to
18594 // `None` would silently absorb the empty-after-trim refusal
18595 // case at the accessor boundary.
18596 caixa_with_restart_window(None)
18597 .validate_restart_window()
18598 .expect("None :restart-window must validate through the accessor");
18599 caixa_with_restart_window(Some("60s"))
18600 .validate_restart_window()
18601 .expect("canonical :restart-window \"60s\" must validate through the accessor");
18602 let err = caixa_with_restart_window(Some("1.5s"))
18603 .validate_restart_window()
18604 .expect_err("fractional-seconds :restart-window must fail through the accessor");
18605 assert!(
18606 matches!(
18607 err,
18608 ManifestError::RestartWindowMalformed { ref restart_window, .. }
18609 if restart_window == "1.5s"
18610 ),
18611 "validator must carry the offending raw string verbatim \
18612 from the accessor's borrowed &str (got {err:?})",
18613 );
18614 }
18615
18616 #[test]
18617 fn supervisor_view_restart_window_arm_routes_through_accessor() {
18618 // Composition pin: [`Caixa::supervisor_view`]'s
18619 // per-`:restart-window` [`SupervisorSpec`] construction arm
18620 // must key off [`Caixa::restart_window`]'s soft-swallowing
18621 // `.and_then(|s| duration_codec::parse(s).ok())` fold, not the
18622 // raw `self.restart_window.as_deref().and_then(…)` field-fold.
18623 // Structurally: (1) `None` → `SupervisorSpec.restart_window ==
18624 // None` (the "never reset" sentinel); (2) canonical `Some("60s")`
18625 // → `SupervisorSpec.restart_window == Some(Duration::from_secs(60))`
18626 // (the shared codec's canonical parse); (3) codec-rejected
18627 // `Some("1.5s")` → `SupervisorSpec.restart_window == None`
18628 // (the soft-swallow preserving the view's best-effort shape).
18629 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
18630 let view = c.supervisor_view().expect("Supervisor kind has a view");
18631 assert_eq!(
18632 view.restart_window(),
18633 None,
18634 "supervisor_view must project outer None :restart-window \
18635 onto None on the composed SupervisorSpec (never-reset \
18636 sentinel) through the accessor's None-return unchanged",
18637 );
18638
18639 let c = caixa_supervisor_with_max_restarts_and_window(None, Some("60s"));
18640 let view = c.supervisor_view().expect("Supervisor kind has a view");
18641 assert_eq!(
18642 view.restart_window(),
18643 Some(std::time::Duration::from_secs(60)),
18644 "supervisor_view must fold outer Some(\"60s\") through the \
18645 shared duration_codec into Duration::from_secs(60) on the \
18646 composed SupervisorSpec (accessor's Some(&str) → codec \
18647 parse → Some(Duration))",
18648 );
18649
18650 let c = caixa_supervisor_with_max_restarts_and_window(None, Some("1.5s"));
18651 let view = c.supervisor_view().expect("Supervisor kind has a view");
18652 assert_eq!(
18653 view.restart_window(),
18654 None,
18655 "supervisor_view must soft-swallow the shared-codec parse \
18656 failure to None (the view's best-effort shape the sibling \
18657 manifest-level validate_restart_window surfaces as \
18658 RestartWindowMalformed); the accessor's raw-string return \
18659 is the single input every downstream consumer keys off",
18660 );
18661 }
18662
18663 // ── Caixa::upgrade_from — outer top-level &[UpgradeFromEntry] composite-slice accessor ──
18664
18665 fn caixa_with_upgrade_from(upgrade_from: Vec<crate::upgrade::UpgradeFromEntry>) -> Caixa {
18666 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
18667 c.upgrade_from = upgrade_from;
18668 c
18669 }
18670
18671 #[test]
18672 fn upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations() {
18673 // The canonical per-`Caixa` `:upgrade-from` M2 typed-slot
18674 // outer-composite `&[UpgradeFromEntry]`-return slice-shape
18675 // pin: [`Caixa::upgrade_from`] must return the `:upgrade-from`
18676 // typed `Vec<UpgradeFromEntry>` verbatim as a
18677 // `&[UpgradeFromEntry]` slice-view over the same backing
18678 // buffer the raw `self.upgrade_from.as_slice()` field access
18679 // borrows from, element-equal across every representative
18680 // fixture in the accept-set — `[]` (the "no hot-upgrade path
18681 // declared" arm every `defcaixa` without an `:upgrade-from`
18682 // block carries; `#[serde(default)]` folds an omitted slot
18683 // onto `Vec::new()`), a canonical single-entry `Restart`
18684 // fixture (the shape most Servicos carry — a single prior
18685 // version with the fallback strategy), a canonical multi-
18686 // entry list carrying every typed instruction variant
18687 // (`LoadModule` / `StateChange` / `SoftPurge` / `Purge` /
18688 // `Restart`), and a past-the-guard sentinel — a duplicate-
18689 // `:from` `[(0.1.0, Restart), (0.1.0, Restart)]` entry pair
18690 // ([`crate::upgrade::validate_upgrade_from`] rejects through
18691 // `DuplicateFrom { from: "0.1.0" }` but the accessor must
18692 // ship the raw slot verbatim so struct-literal fixtures
18693 // continue to expose the duplicate at the accessor boundary).
18694 //
18695 // Pins against a future silent detour that returned an owned
18696 // `Vec<UpgradeFromEntry>` (which would type-check but silently
18697 // clone on every accessor call, breaking the zero-cost
18698 // projection every peer sibling slice accessor carries), a
18699 // `[dup, dup] → [dup]` dedup collapse (which would silently
18700 // absorb the `DuplicateFrom` refusal case at the accessor
18701 // boundary and the [`crate::StandardLayout::verify`] cross-
18702 // entry gate would silently accept a struct-literal `Caixa`
18703 // carrying the drift), a reference to an operator-resolved
18704 // overlay (the future per-cluster `:upgrade-overrides` slot
18705 // — its resolution must land at exactly this accessor body,
18706 // not silently divert the raw slot away from a second
18707 // consumer), or an axis-shuffled projection (a future detour
18708 // that reordered entries through the accessor would silently
18709 // split the paired [`crate::StandardLayout::verify`] per-
18710 // `:upgrade-from` shape gate's traversal input from the peer
18711 // [`crate::render::servico_m2_overlay`] emitter's projection
18712 // input, since the operator's hot-upgrade dispatch matches
18713 // per-`:from` and axis reordering would silently split the
18714 // per-entry script-path existence probe's iteration order
18715 // from the M2 overlay emitter's serialized-entry order).
18716 //
18717 // First outer top-level [`Caixa`] `&[Composite]`-return
18718 // slice accessor pin on the substrate primitive for M2 / M3
18719 // typed-slot vec-carry axes — opens the outer-`Caixa`
18720 // `&[Composite]` composite-slice projection pattern the
18721 // sibling `:children` [`crate::supervisor::ChildSpec`] /
18722 // `:membros` [`crate::aplicacao::Membro`] / `:contratos`
18723 // [`crate::aplicacao::WitContract`] future outer-composite-
18724 // slice pins fold on. Peer of the closed outer-`Caixa`
18725 // scalar `Option<&Composite>` composite-reference family the
18726 // sibling `limits` / `behavior` / `politicas` / `placement`
18727 // / `entrada` `..._returns_..._option_ref_verbatim_across_
18728 // permutations` pins closed (b2bd9d7 → e4128e4) — extends
18729 // the "byte-equal, borrow-shared" outer-accessor discipline
18730 // onto the outer-`Caixa` `&[Composite]` vec-carry altitude.
18731 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
18732 let fixtures: Vec<Vec<UpgradeFromEntry>> = vec![
18733 vec![],
18734 vec![UpgradeFromEntry {
18735 from: "0.0.1".into(),
18736 instructions: vec![UpgradeInstruction::Restart],
18737 }],
18738 vec![
18739 UpgradeFromEntry {
18740 from: "0.0.1".into(),
18741 instructions: vec![
18742 UpgradeInstruction::LoadModule {
18743 module: "demo".into(),
18744 },
18745 UpgradeInstruction::SoftPurge {
18746 module: "demo".into(),
18747 },
18748 ],
18749 },
18750 UpgradeFromEntry {
18751 from: "0.0.2".into(),
18752 instructions: vec![
18753 UpgradeInstruction::StateChange {
18754 script: "servicos/upgrade.lisp".into(),
18755 },
18756 UpgradeInstruction::Purge {
18757 module: "demo".into(),
18758 },
18759 UpgradeInstruction::Restart,
18760 ],
18761 },
18762 ],
18763 vec![
18764 UpgradeFromEntry {
18765 from: "0.1.0".into(),
18766 instructions: vec![UpgradeInstruction::Restart],
18767 },
18768 UpgradeFromEntry {
18769 from: "0.1.0".into(),
18770 instructions: vec![UpgradeInstruction::Restart],
18771 },
18772 ],
18773 ];
18774 for upgrade_from in fixtures {
18775 let c = caixa_with_upgrade_from(upgrade_from.clone());
18776 assert_eq!(
18777 c.upgrade_from(),
18778 upgrade_from.as_slice(),
18779 "Caixa::upgrade_from must return :upgrade-from \
18780 verbatim (got {:?}, expected {upgrade_from:?})",
18781 c.upgrade_from(),
18782 );
18783 assert_eq!(
18784 c.upgrade_from(),
18785 c.upgrade_from.as_slice(),
18786 "Caixa::upgrade_from must element-equal the raw \
18787 `self.upgrade_from.as_slice()` field access across \
18788 every value in the Vec<UpgradeFromEntry> accept-set",
18789 );
18790 assert_eq!(
18791 c.upgrade_from().is_empty(),
18792 c.upgrade_from.is_empty(),
18793 "Caixa::upgrade_from().is_empty() must byte-equal \
18794 self.upgrade_from.is_empty() — a presence-bit drift \
18795 would silently split the paired \
18796 Caixa::declared_servico_slots M2 declared-slot \
18797 enumerator's presence probe from the peer \
18798 crate::render::servico_m2_overlay M2 overlay \
18799 emitter's presence gate",
18800 );
18801 }
18802 }
18803
18804 #[test]
18805 fn declared_servico_slots_upgrade_from_arm_routes_through_accessor() {
18806 // Composition pin: [`Caixa::declared_servico_slots`]'s
18807 // `:upgrade-from` presence-probe arm must key off
18808 // [`Caixa::upgrade_from`], not the raw
18809 // `self.upgrade_from.is_empty()` field-probe. Structurally: a
18810 // `Caixa { upgrade_from: vec![UpgradeFromEntry { from: "0.0.1",
18811 // instructions: vec![Restart] }], .. }` must push
18812 // `M2_AUTHOR_KEY_UPGRADE_FROM` onto the declared-slot list
18813 // (the presence bit is non-empty, so the M2 kind-coherence
18814 // gate must surface the slot as "declared"), and a `Caixa {
18815 // upgrade_from: vec![], .. }` must NOT push the label (the
18816 // "author omitted the slot entirely" arm — the empty-slice
18817 // partition the serde-default folds onto). The pair jointly
18818 // pins the accessor + declared-slot enumerator composition:
18819 // any future silent detour that had the accessor collapse
18820 // `[Restart]` to `[]` (a `.filter(|e| !e.instructions.
18821 // is_empty())` projection) would silently absorb the
18822 // "declared but degenerate" arm at the accessor boundary and
18823 // the [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-
18824 // coherence gate would silently accept a struct-literal
18825 // `Caixa` carrying the drift.
18826 //
18827 // Peer of the sibling
18828 // `declared_servico_slots_limits_arm_routes_through_accessor`
18829 // (b2bd9d7) and
18830 // `declared_servico_slots_behavior_arm_routes_through_accessor`
18831 // (35d8b52) composition pins on the sibling `:limits` /
18832 // `:behavior` outer-`Option<&Composite>` arms — same "the
18833 // enumerator gate must route through the substrate-primitive
18834 // typed dispatch" discipline extended onto the third M2
18835 // Servico-runtime slot axis, closing the enumerator's routing
18836 // invariant on every M2 arm.
18837 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
18838 let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
18839 from: "0.0.1".into(),
18840 instructions: vec![UpgradeInstruction::Restart],
18841 }]);
18842 let slots = c.declared_servico_slots();
18843 assert!(
18844 slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
18845 "declared_servico_slots must push \
18846 M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
18847 non-empty — the accessor and the enumerator gate must \
18848 route through the same substrate-primitive typed \
18849 dispatch on the outer :upgrade-from presence bit (got \
18850 slots={slots:?})",
18851 );
18852 let c = caixa_with_upgrade_from(vec![]);
18853 let slots = c.declared_servico_slots();
18854 assert!(
18855 !slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
18856 "declared_servico_slots must NOT push \
18857 M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
18858 empty — the author-omitted arm must route through the \
18859 accessor's empty-slice return unchanged (got \
18860 slots={slots:?})",
18861 );
18862 }
18863
18864 #[test]
18865 fn servico_m2_overlay_upgrade_from_arm_routes_through_accessor() {
18866 // Composition pin: [`crate::render::servico_m2_overlay`]'s
18867 // per-`:upgrade-from` M2 overlay emit arm must key off
18868 // [`Caixa::upgrade_from`], not the raw
18869 // `!caixa.upgrade_from.is_empty()` presence gate + the
18870 // `serde_yaml::to_value(&caixa.upgrade_from)` projection.
18871 // Structurally: a `Caixa { upgrade_from: vec![UpgradeFromEntry
18872 // { from: "0.0.1", instructions: vec![Restart] }], .. }` must
18873 // surface the `M2_KEY_UPGRADE_FROM` key with a per-entry
18874 // sequence in the overlay (the emitter fans onto the serde
18875 // slice-serialization), and a `Caixa { upgrade_from: vec![],
18876 // .. }` must omit the key entirely (the empty-slice
18877 // partition — the `!.is_empty()` outer gate elides the key
18878 // when the author omitted the slot). The pair jointly pins
18879 // the accessor + M2 overlay emitter composition: any future
18880 // silent detour that had the accessor return a fresh-cloned
18881 // `Vec<UpgradeFromEntry>` copy would silently break the
18882 // reference-identity pin the peer per-entry
18883 // `serde_yaml::to_value(caixa.upgrade_from())` projection
18884 // reads from — the projection would clone once per accessor
18885 // call instead of borrowing the storage buffer verbatim.
18886 //
18887 // Peer of the sibling
18888 // `servico_m2_overlay_limits_arm_routes_through_accessor`
18889 // (b2bd9d7) and
18890 // `servico_m2_overlay_behavior_arm_routes_through_accessor`
18891 // (35d8b52) composition pins on the sibling `:limits` /
18892 // `:behavior` outer-`Option<&Composite>` arms — same "the
18893 // M2 overlay emitter must route through the substrate-
18894 // primitive typed dispatch" discipline extended onto the
18895 // third M2 Servico-runtime slot axis, closing the overlay
18896 // emitter's routing invariant on every M2 arm.
18897 use crate::render::{M2_KEY_UPGRADE_FROM, servico_m2_overlay};
18898 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
18899 let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
18900 from: "0.0.1".into(),
18901 instructions: vec![UpgradeInstruction::Restart],
18902 }]);
18903 let overlay = servico_m2_overlay(&c).unwrap();
18904 assert!(
18905 overlay.contains_key(M2_KEY_UPGRADE_FROM),
18906 "servico_m2_overlay must surface M2_KEY_UPGRADE_FROM when \
18907 `:upgrade-from` is non-empty — the accessor and the M2 \
18908 overlay emitter must route through the same substrate- \
18909 primitive typed dispatch on the outer :upgrade-from \
18910 slice (got overlay={overlay:?})",
18911 );
18912 let c = caixa_with_upgrade_from(vec![]);
18913 let overlay = servico_m2_overlay(&c).unwrap();
18914 assert!(
18915 !overlay.contains_key(M2_KEY_UPGRADE_FROM),
18916 "servico_m2_overlay must omit M2_KEY_UPGRADE_FROM when \
18917 `:upgrade-from` is empty — the empty-slice partition \
18918 must route through the accessor's empty-slice return \
18919 unchanged (got overlay={overlay:?})",
18920 );
18921 }
18922
18923 #[test]
18924 fn upgrade_from_projects_slice_by_borrow() {
18925 // The by-borrow pin: [`Caixa::upgrade_from`] returns
18926 // `&[UpgradeFromEntry]` by borrow — the returned slice
18927 // borrows the underlying `Vec<UpgradeFromEntry>` storage of
18928 // the `:upgrade-from` slot and the accessor must not clone
18929 // the backing `Vec` on every call. Peer of the sibling
18930 // outer top-level [`Caixa`] `&[T]`-return by-borrow pins
18931 // (`autores_projects_slice_by_borrow` b5d813f,
18932 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
18933 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
18934 // `exe_projects_slice_by_borrow` 65d9527,
18935 // `servicos_projects_slice_by_borrow` 611f78b,
18936 // `deps_projects_slice_by_borrow` ad34b4e,
18937 // `deps_dev_projects_slice_by_borrow` f7fd81e) on the
18938 // sibling outer top-level [`Caixa`] scalar-element `&[T]`
18939 // axes — extended here to the first outer-`Caixa`
18940 // composite-element `&[Composite]` axis: the accessor's
18941 // returned slice must borrow from `&self` (the returned
18942 // reference's lifetime is tied to `&self`), and calling the
18943 // accessor twice on the same [`Caixa`] must yield slices
18944 // that are pointer-equal (the underlying byte-buffer is the
18945 // storage `Vec`'s allocation, not a fresh copy) as well as
18946 // value-equal (idempotent, no side effects on `&self`).
18947 //
18948 // Pins against a future silent detour that returned an owned
18949 // `Vec<UpgradeFromEntry>` (which would type-check but
18950 // silently clone on every call), a `&Vec<UpgradeFromEntry>`
18951 // return (which would leak the backing `Vec`'s
18952 // grow/push/reserve surface no downstream consumer reaches
18953 // for), or a one-arm-only accessor that returned a
18954 // saturating value on some sentinel input.
18955 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
18956 for upgrade_from in [
18957 vec![],
18958 vec![UpgradeFromEntry {
18959 from: "0.0.1".into(),
18960 instructions: vec![UpgradeInstruction::Restart],
18961 }],
18962 vec![
18963 UpgradeFromEntry {
18964 from: "0.0.1".into(),
18965 instructions: vec![UpgradeInstruction::Restart],
18966 },
18967 UpgradeFromEntry {
18968 from: "0.0.2".into(),
18969 instructions: vec![UpgradeInstruction::SoftPurge {
18970 module: "demo".into(),
18971 }],
18972 },
18973 ],
18974 ] {
18975 let c = caixa_with_upgrade_from(upgrade_from.clone());
18976 let first = c.upgrade_from();
18977 let second = c.upgrade_from();
18978 assert_eq!(
18979 first, second,
18980 "Caixa::upgrade_from must be idempotent — two \
18981 successive calls on the same &self must return the \
18982 same &[UpgradeFromEntry]",
18983 );
18984 assert_eq!(
18985 first.as_ptr(),
18986 second.as_ptr(),
18987 "Caixa::upgrade_from must borrow the underlying \
18988 Vec<UpgradeFromEntry> storage — two successive calls \
18989 must return slices with the same backing pointer (a \
18990 fresh Vec<UpgradeFromEntry> clone would change the \
18991 pointer on every call)",
18992 );
18993 assert_eq!(
18994 first,
18995 upgrade_from.as_slice(),
18996 "Caixa::upgrade_from must return :upgrade-from \
18997 verbatim by borrow — got {first:?}, expected \
18998 {upgrade_from:?}",
18999 );
19000 }
19001 }
19002
19003 // ── Caixa::children — outer top-level &[ChildSpec] composite-slice accessor ──
19004
19005 fn caixa_with_children(children: Vec<crate::supervisor::ChildSpec>) -> Caixa {
19006 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19007 c.children = children;
19008 c
19009 }
19010
19011 #[test]
19012 fn children_returns_children_slice_verbatim_across_permutations() {
19013 // The canonical per-`Caixa` `:children` M2 supervisor-tree-slot
19014 // outer-composite `&[ChildSpec]`-return slice-shape pin:
19015 // [`Caixa::children`] must return the `:children` typed
19016 // `Vec<ChildSpec>` verbatim as a `&[ChildSpec]` slice-view over
19017 // the same backing buffer the raw `self.children.as_slice()`
19018 // field access borrows from, element-equal across every
19019 // representative fixture in the accept-set — `[]` (the "no
19020 // static children declared" arm every non-`Supervisor`-kind
19021 // `defcaixa` carries by `#[serde(default)]` and every
19022 // `SimpleOneForOne` supervisor carries by cross-slot refusal),
19023 // a canonical single-child `Permanent` fixture (the shape
19024 // most `OneForOne` supervisors carry — a single long-running
19025 // worker child), a canonical multi-child list carrying every
19026 // typed restart-policy variant (`Permanent` / `Transient` /
19027 // `Temporary`), and a past-the-guard sentinel — a duplicate
19028 // `:caixa` `[("w", ...), ("w", ...)]` entry pair
19029 // ([`crate::SupervisorSpec::validate`] rejects through
19030 // `DuplicateChildNome { nome: "w" }` but the accessor must
19031 // ship the raw slot verbatim so struct-literal fixtures
19032 // continue to expose the duplicate at the accessor boundary).
19033 //
19034 // Pins against a future silent detour that returned an owned
19035 // `Vec<ChildSpec>` (which would type-check but silently clone
19036 // on every accessor call, breaking the zero-cost projection
19037 // every peer sibling slice accessor carries), a `[dup, dup] →
19038 // [dup]` dedup collapse (which would silently absorb the
19039 // `DuplicateChildNome` refusal case at the accessor boundary
19040 // and the [`crate::StandardLayout::verify`] cross-child gate
19041 // would silently accept a struct-literal `Caixa` carrying the
19042 // drift), a reference to an operator-resolved overlay (the
19043 // future per-cluster `:children-overrides` slot — its
19044 // resolution must land at exactly this accessor body, not
19045 // silently divert the raw slot away from a second consumer),
19046 // or an axis-shuffled projection (a future detour that
19047 // reordered children through the accessor would silently
19048 // split the paired [`crate::StandardLayout::verify`] per-
19049 // supervisor gate's traversal input from the peer
19050 // [`Self::supervisor_view`] fold-in path's clone-order input,
19051 // since the OTP `RestForOne` restart strategy dispatches on
19052 // declared child order and axis reordering would silently
19053 // split the operator's per-cluster restart-fan-out order
19054 // from the caixa.lisp source-order).
19055 //
19056 // Second outer top-level [`Caixa`] `&[Composite]`-return slice
19057 // accessor pin on the substrate primitive for M2 / M3 typed-
19058 // slot vec-carry axes — folds on the outer-`Caixa`
19059 // `&[Composite]` composite-slice sub-family the sibling
19060 // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
19061 // (2a1f907) pin opened, peer at the outer altitude of the
19062 // closed inner-`SupervisorSpec` `SupervisorSpec::children`
19063 // (bc92bce) accessor on the same OTP-supervisor static-child-
19064 // list axis.
19065 use crate::supervisor::{ChildSpec, RestartPolicy};
19066 let fixtures: Vec<Vec<ChildSpec>> = vec![
19067 vec![],
19068 vec![ChildSpec {
19069 caixa: "worker".into(),
19070 versao: "^0.1".into(),
19071 restart: RestartPolicy::Permanent,
19072 }],
19073 vec![
19074 ChildSpec {
19075 caixa: "worker-a".into(),
19076 versao: "^0.1".into(),
19077 restart: RestartPolicy::Permanent,
19078 },
19079 ChildSpec {
19080 caixa: "worker-b".into(),
19081 versao: "^0.1".into(),
19082 restart: RestartPolicy::Transient,
19083 },
19084 ChildSpec {
19085 caixa: "worker-c".into(),
19086 versao: "^0.1".into(),
19087 restart: RestartPolicy::Temporary,
19088 },
19089 ],
19090 vec![
19091 ChildSpec {
19092 caixa: "w".into(),
19093 versao: "^0.1".into(),
19094 restart: RestartPolicy::Permanent,
19095 },
19096 ChildSpec {
19097 caixa: "w".into(),
19098 versao: "^0.1".into(),
19099 restart: RestartPolicy::Permanent,
19100 },
19101 ],
19102 ];
19103 for children in fixtures {
19104 let c = caixa_with_children(children.clone());
19105 assert_eq!(
19106 c.children(),
19107 children.as_slice(),
19108 "Caixa::children must return :children verbatim \
19109 (got {:?}, expected {children:?})",
19110 c.children(),
19111 );
19112 assert_eq!(
19113 c.children(),
19114 c.children.as_slice(),
19115 "Caixa::children must element-equal the raw \
19116 `self.children.as_slice()` field access across \
19117 every value in the Vec<ChildSpec> accept-set",
19118 );
19119 assert_eq!(
19120 c.children().is_empty(),
19121 c.children.is_empty(),
19122 "Caixa::children().is_empty() must byte-equal \
19123 self.children.is_empty() — a presence-bit drift \
19124 would silently split the paired \
19125 Caixa::declared_supervisor_slots supervisor-tree \
19126 declared-slot enumerator's presence probe from the \
19127 peer Caixa::supervisor_view typed-view composer's \
19128 fold-in path",
19129 );
19130 }
19131 }
19132
19133 #[test]
19134 fn declared_supervisor_slots_children_arm_routes_through_accessor() {
19135 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
19136 // `:children` presence-probe arm must key off
19137 // [`Caixa::children`], not the raw
19138 // `!self.children.is_empty()` field-probe. Structurally: a
19139 // `Caixa { children: vec![ChildSpec { caixa: "w", versao:
19140 // "^0.1", restart: Permanent }], .. }` must push
19141 // `SUPERVISOR_AUTHOR_KEY_CHILDREN` onto the declared-slot list
19142 // (the presence bit is non-empty, so the supervisor-tree
19143 // kind-coherence gate must surface the slot as "declared"),
19144 // and a `Caixa { children: vec![], .. }` must NOT push the
19145 // label (the "author omitted the slot entirely" arm — the
19146 // empty-slice partition the serde-default folds onto). The
19147 // pair jointly pins the accessor + declared-slot enumerator
19148 // composition: any future silent detour that had the accessor
19149 // collapse `[Permanent]` to `[]` (a `.filter(|c| c.nome() !=
19150 // "__reserved__")` projection) would silently absorb the
19151 // "declared but degenerate" arm at the accessor boundary and
19152 // the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
19153 // kind-coherence gate would silently accept a struct-literal
19154 // `Caixa` carrying the drift.
19155 //
19156 // Peer of the sibling
19157 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
19158 // (2a1f907) on the M2 `:upgrade-from` composite-slice arm —
19159 // same "the enumerator gate must route through the substrate-
19160 // primitive typed dispatch" discipline extended onto the
19161 // supervisor-tree `:children` composite-slice arm.
19162 use crate::supervisor::{ChildSpec, RestartPolicy};
19163 let c = caixa_with_children(vec![ChildSpec {
19164 caixa: "w".into(),
19165 versao: "^0.1".into(),
19166 restart: RestartPolicy::Permanent,
19167 }]);
19168 let slots = c.declared_supervisor_slots();
19169 assert!(
19170 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
19171 "declared_supervisor_slots must push \
19172 SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
19173 non-empty — the accessor and the enumerator gate must \
19174 route through the same substrate-primitive typed \
19175 dispatch on the outer :children presence bit (got \
19176 slots={slots:?})",
19177 );
19178 let c = caixa_with_children(vec![]);
19179 let slots = c.declared_supervisor_slots();
19180 assert!(
19181 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
19182 "declared_supervisor_slots must NOT push \
19183 SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
19184 empty — the author-omitted arm must route through the \
19185 accessor's empty-slice return unchanged (got \
19186 slots={slots:?})",
19187 );
19188 }
19189
19190 #[test]
19191 fn supervisor_view_children_arm_routes_through_accessor() {
19192 // Composition pin: [`Caixa::supervisor_view`]'s per-`:children`
19193 // fold-in arm must key off [`Caixa::children`], not the raw
19194 // `self.children.clone()` field-clone. Structurally: a `Caixa {
19195 // kind: Supervisor, estrategia: Some(OneForOne), children:
19196 // vec![ChildSpec { caixa: "w", .. }], .. }` must fold the
19197 // per-child list through the accessor into the typed
19198 // [`SupervisorSpec`] view's `children` field verbatim — every
19199 // entry the accessor surfaces must land in the view's
19200 // `children` slot in the same order. The pair jointly pins the
19201 // accessor + view-composer composition: any future silent
19202 // detour that had the accessor return a fresh-cloned
19203 // `Vec<ChildSpec>` copy would silently break the reference-
19204 // identity pin the peer `supervisor_view` fold-in path reads
19205 // from — the fold would clone once more per accessor call
19206 // instead of borrowing the storage buffer verbatim once.
19207 //
19208 // Peer of the sibling
19209 // `supervisor_view_kind_gate_routes_through_accessor` (35d8b52-
19210 // family) composition pin on the peer kind-gate arm — same
19211 // "the view composer must route through the substrate-
19212 // primitive typed dispatch" discipline extended onto the
19213 // per-`:children` fold-in arm, closing the supervisor-view
19214 // composer's routing invariant on the composite-slice input.
19215 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
19216 let mut c = caixa_with_children(vec![
19217 ChildSpec {
19218 caixa: "worker-a".into(),
19219 versao: "^0.1".into(),
19220 restart: RestartPolicy::Permanent,
19221 },
19222 ChildSpec {
19223 caixa: "worker-b".into(),
19224 versao: "^0.1".into(),
19225 restart: RestartPolicy::Transient,
19226 },
19227 ]);
19228 c.kind = crate::CaixaKind::Supervisor;
19229 c.estrategia = Some(RestartStrategy::OneForOne);
19230 let view = c
19231 .supervisor_view()
19232 .expect("Supervisor kind must produce a supervisor_view");
19233 assert_eq!(
19234 view.children(),
19235 c.children(),
19236 "supervisor_view must fold Caixa::children verbatim into \
19237 SupervisorSpec::children — the accessor and the view \
19238 composer must route through the same substrate-primitive \
19239 typed dispatch on the outer :children slice (got view \
19240 children={:?}, expected {:?})",
19241 view.children(),
19242 c.children(),
19243 );
19244 }
19245
19246 #[test]
19247 fn children_projects_slice_by_borrow() {
19248 // The by-borrow pin: [`Caixa::children`] returns
19249 // `&[ChildSpec]` by borrow — the returned slice borrows the
19250 // underlying `Vec<ChildSpec>` storage of the `:children` slot
19251 // and the accessor must not clone the backing `Vec` on every
19252 // call. Peer of the sibling outer top-level [`Caixa`]
19253 // `&[T]`-return by-borrow pins (`autores_projects_slice_by_borrow`
19254 // b5d813f, `etiquetas_projects_slice_by_borrow` 78c7d3c,
19255 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
19256 // `exe_projects_slice_by_borrow` 65d9527,
19257 // `servicos_projects_slice_by_borrow` 611f78b,
19258 // `deps_projects_slice_by_borrow` ad34b4e,
19259 // `deps_dev_projects_slice_by_borrow` f7fd81e,
19260 // `upgrade_from_projects_slice_by_borrow` 2a1f907) on the
19261 // sibling outer top-level [`Caixa`] scalar-element and
19262 // composite-element `&[T]` axes — folds on the outer-`Caixa`
19263 // composite-element `&[Composite]` axis: the accessor's
19264 // returned slice must borrow from `&self` (the returned
19265 // reference's lifetime is tied to `&self`), and calling the
19266 // accessor twice on the same [`Caixa`] must yield slices
19267 // that are pointer-equal (the underlying byte-buffer is the
19268 // storage `Vec`'s allocation, not a fresh copy) as well as
19269 // value-equal (idempotent, no side effects on `&self`).
19270 //
19271 // Pins against a future silent detour that returned an owned
19272 // `Vec<ChildSpec>` (which would type-check but silently clone
19273 // on every call), a `&Vec<ChildSpec>` return (which would leak
19274 // the backing `Vec`'s grow/push/reserve surface no downstream
19275 // consumer reaches for), or a one-arm-only accessor that
19276 // returned a saturating value on some sentinel input.
19277 use crate::supervisor::{ChildSpec, RestartPolicy};
19278 for children in [
19279 vec![],
19280 vec![ChildSpec {
19281 caixa: "w".into(),
19282 versao: "^0.1".into(),
19283 restart: RestartPolicy::Permanent,
19284 }],
19285 vec![
19286 ChildSpec {
19287 caixa: "worker-a".into(),
19288 versao: "^0.1".into(),
19289 restart: RestartPolicy::Permanent,
19290 },
19291 ChildSpec {
19292 caixa: "worker-b".into(),
19293 versao: "^0.1".into(),
19294 restart: RestartPolicy::Transient,
19295 },
19296 ],
19297 ] {
19298 let c = caixa_with_children(children.clone());
19299 let first = c.children();
19300 let second = c.children();
19301 assert_eq!(
19302 first, second,
19303 "Caixa::children must be idempotent — two successive \
19304 calls on the same &self must return the same \
19305 &[ChildSpec]",
19306 );
19307 assert_eq!(
19308 first.as_ptr(),
19309 second.as_ptr(),
19310 "Caixa::children must borrow the underlying \
19311 Vec<ChildSpec> storage — two successive calls must \
19312 return slices with the same backing pointer (a fresh \
19313 Vec<ChildSpec> clone would change the pointer on \
19314 every call)",
19315 );
19316 assert_eq!(
19317 first,
19318 children.as_slice(),
19319 "Caixa::children must return :children verbatim by \
19320 borrow — got {first:?}, expected {children:?}",
19321 );
19322 }
19323 }
19324
19325 // ── Caixa::membros — outer top-level &[Membro] composite-slice accessor ──
19326
19327 fn caixa_aplicacao_with_membros(membros: Vec<crate::aplicacao::Membro>) -> Caixa {
19328 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19329 c.kind = CaixaKind::Aplicacao;
19330 c.membros = membros;
19331 c
19332 }
19333
19334 #[test]
19335 fn membros_returns_membros_slice_verbatim_across_permutations() {
19336 // The canonical per-`Caixa` `:membros` M3 mesh-slot outer-
19337 // composite `&[Membro]`-return slice-shape pin:
19338 // [`Caixa::membros`] must return the `:membros` typed
19339 // `Vec<Membro>` verbatim as a `&[Membro]` slice-view over the
19340 // same backing buffer the raw `self.membros.as_slice()` field
19341 // access borrows from, element-equal across every
19342 // representative fixture in the accept-set — `[]` (the "no
19343 // members declared" arm every non-`Aplicacao`-kind `defcaixa`
19344 // carries by `#[serde(default)]` and every partially-authored
19345 // Aplicacao carries before the
19346 // [`crate::AplicacaoError::MembrosEmpty`] gate fires), a
19347 // canonical single-member fixture (the shape a minimal
19348 // Aplicacao carries — one Servico wrapping one contained
19349 // computation), a canonical multi-member list carrying three
19350 // distinct entries (the canonical checkout-shape Aplicacao —
19351 // cart / pricing / auth — every canonical example carries), and
19352 // a past-the-guard sentinel — a duplicate `:caixa`
19353 // `[("cart", ...), ("cart", ...)]` entry pair
19354 // ([`crate::AplicacaoSpec::validate`] rejects through
19355 // `DuplicateMembro { nome: "cart" }` but the accessor must ship
19356 // the raw slot verbatim so struct-literal fixtures continue to
19357 // expose the duplicate at the accessor boundary).
19358 //
19359 // Pins against a future silent detour that returned an owned
19360 // `Vec<Membro>` (which would type-check but silently clone on
19361 // every accessor call, breaking the zero-cost projection every
19362 // peer sibling slice accessor carries), a `[dup, dup] → [dup]`
19363 // dedup collapse (which would silently absorb the
19364 // `DuplicateMembro` refusal case at the accessor boundary and
19365 // the [`crate::StandardLayout::verify`] cross-member gate would
19366 // silently accept a struct-literal `Caixa` carrying the drift),
19367 // a reference to an operator-resolved overlay (the future per-
19368 // cluster `:membros-overrides` slot — its resolution must land
19369 // at exactly this accessor body, not silently divert the raw
19370 // slot away from a second consumer), or an axis-shuffled
19371 // projection (a future detour that reordered members through
19372 // the accessor would silently split the paired
19373 // [`crate::StandardLayout::verify`] per-Aplicacao gate's
19374 // traversal input from the peer [`Self::aplicacao_view`] fold-
19375 // in path's clone-order input, since the canonical `:contratos`
19376 // `:de`/`:para` and `:entrada :para` cross-slot refusal probes
19377 // read the member set through the same slice).
19378 //
19379 // Third outer top-level [`Caixa`] `&[Composite]`-return slice
19380 // accessor pin on the substrate primitive for M2 / M3 typed-
19381 // slot vec-carry axes — opens the outer-`Caixa` M3 mesh-slot
19382 // arm of the `&[Composite]` composite-slice sub-family the
19383 // sibling M2 `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
19384 // (2a1f907) and
19385 // `children_returns_children_slice_verbatim_across_permutations`
19386 // (c17b51e) pins opened, peer at the outer altitude of the
19387 // closed inner-[`crate::AplicacaoSpec::membros`] (6c77e36)
19388 // accessor on the same MESH-COMPOSITION per-Aplicacao member-
19389 // list axis.
19390 use crate::aplicacao::Membro;
19391 let fixtures: Vec<Vec<Membro>> = vec![
19392 vec![],
19393 vec![Membro {
19394 caixa: "cart".into(),
19395 versao: "^0.1".into(),
19396 }],
19397 vec![
19398 Membro {
19399 caixa: "cart".into(),
19400 versao: "^0.1".into(),
19401 },
19402 Membro {
19403 caixa: "pricing".into(),
19404 versao: "^0.2".into(),
19405 },
19406 Membro {
19407 caixa: "auth".into(),
19408 versao: "^1.0".into(),
19409 },
19410 ],
19411 vec![
19412 Membro {
19413 caixa: "cart".into(),
19414 versao: "^0.1".into(),
19415 },
19416 Membro {
19417 caixa: "cart".into(),
19418 versao: "^0.1".into(),
19419 },
19420 ],
19421 ];
19422 for membros in fixtures {
19423 let c = caixa_aplicacao_with_membros(membros.clone());
19424 assert_eq!(
19425 c.membros(),
19426 membros.as_slice(),
19427 "Caixa::membros must return :membros verbatim \
19428 (got {:?}, expected {membros:?})",
19429 c.membros(),
19430 );
19431 assert_eq!(
19432 c.membros(),
19433 c.membros.as_slice(),
19434 "Caixa::membros must element-equal the raw \
19435 `self.membros.as_slice()` field access across every \
19436 value in the Vec<Membro> accept-set",
19437 );
19438 assert_eq!(
19439 c.membros().is_empty(),
19440 c.membros.is_empty(),
19441 "Caixa::membros().is_empty() must byte-equal \
19442 self.membros.is_empty() — a presence-bit drift would \
19443 silently split the paired Caixa::declared_mesh_slots \
19444 mesh declared-slot enumerator's presence probe from \
19445 the peer Caixa::aplicacao_view typed-view composer's \
19446 fold-in path",
19447 );
19448 }
19449 }
19450
19451 #[test]
19452 fn declared_mesh_slots_membros_arm_routes_through_accessor() {
19453 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:membros`
19454 // presence-probe arm must key off [`Caixa::membros`], not the
19455 // raw `!self.membros.is_empty()` field-probe. Structurally: a
19456 // `Caixa { membros: vec![Membro { caixa: "cart", versao:
19457 // "^0.1" }], .. }` must push `M3_AUTHOR_KEY_MEMBROS` onto the
19458 // declared-slot list (the presence bit is non-empty, so the
19459 // mesh kind-coherence gate must surface the slot as
19460 // "declared"), and a `Caixa { membros: vec![], .. }` must NOT
19461 // push the label (the "author omitted the slot entirely" arm
19462 // — the empty-slice partition the serde-default folds onto).
19463 // The pair jointly pins the accessor + declared-slot
19464 // enumerator composition: any future silent detour that had
19465 // the accessor collapse `[Membro { .. }]` to `[]` (a
19466 // `.filter(|m| m.nome() != "__reserved__")` projection) would
19467 // silently absorb the "declared but degenerate" arm at the
19468 // accessor boundary and the
19469 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
19470 // coherence gate would silently accept a struct-literal
19471 // `Caixa` carrying the drift.
19472 //
19473 // Peer of the sibling
19474 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
19475 // (2a1f907) and
19476 // `declared_supervisor_slots_children_arm_routes_through_accessor`
19477 // (c17b51e) composition pins on the M2 `:upgrade-from` /
19478 // `:children` composite-slice arms — same "the enumerator gate
19479 // must route through the substrate-primitive typed dispatch"
19480 // discipline extended onto the M3 `:membros` composite-slice
19481 // arm, opening the M3 arm of the declared-slot enumerator's
19482 // routing invariant.
19483 use crate::aplicacao::Membro;
19484 let c = caixa_aplicacao_with_membros(vec![Membro {
19485 caixa: "cart".into(),
19486 versao: "^0.1".into(),
19487 }]);
19488 let slots = c.declared_mesh_slots();
19489 assert!(
19490 slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
19491 "declared_mesh_slots must push M3_AUTHOR_KEY_MEMBROS when \
19492 `:membros` is non-empty — the accessor and the enumerator \
19493 gate must route through the same substrate-primitive \
19494 typed dispatch on the outer :membros presence bit (got \
19495 slots={slots:?})",
19496 );
19497 let c = caixa_aplicacao_with_membros(vec![]);
19498 let slots = c.declared_mesh_slots();
19499 assert!(
19500 !slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
19501 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_MEMBROS \
19502 when `:membros` is empty — the author-omitted arm must \
19503 route through the accessor's empty-slice return unchanged \
19504 (got slots={slots:?})",
19505 );
19506 }
19507
19508 #[test]
19509 fn aplicacao_view_membros_arm_routes_through_accessor() {
19510 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:membros`
19511 // fold-in arm must key off [`Caixa::membros`], not the raw
19512 // `self.membros.clone()` field-clone. Structurally: a `Caixa {
19513 // kind: Aplicacao, membros: vec![Membro { caixa: "cart", .. },
19514 // Membro { caixa: "pricing", .. }], .. }` must fold the per-
19515 // member list through the accessor into the typed
19516 // [`crate::AplicacaoSpec`] view's `membros` slot verbatim —
19517 // every entry the accessor surfaces must land in the view's
19518 // `membros` slot in the same order. The pair jointly pins the
19519 // accessor + view-composer composition: any future silent
19520 // detour that had the accessor return a fresh-cloned
19521 // `Vec<Membro>` copy would silently break the reference-
19522 // identity pin the peer `aplicacao_view` fold-in path reads
19523 // from — the fold would clone once more per accessor call
19524 // instead of borrowing the storage buffer verbatim once.
19525 //
19526 // Peer of the sibling
19527 // `aplicacao_view_politicas_arm_folds_through_accessor`
19528 // (5d23d29) /
19529 // `aplicacao_view_placement_arm_folds_through_accessor`
19530 // (4fb8074) /
19531 // `aplicacao_view_entrada_arm_folds_through_accessor` (e4128e4)
19532 // composition pins on the M3 `:politicas` / `:placement` /
19533 // `:entrada` outer-`Option<&Composite>` arms — extended here to
19534 // the M3 `:membros` outer-`&[Composite]` composite-slice arm,
19535 // closing the aplicacao-view composer's routing invariant on
19536 // the composite-slice input.
19537 use crate::aplicacao::Membro;
19538 let c = caixa_aplicacao_with_membros(vec![
19539 Membro {
19540 caixa: "cart".into(),
19541 versao: "^0.1".into(),
19542 },
19543 Membro {
19544 caixa: "pricing".into(),
19545 versao: "^0.2".into(),
19546 },
19547 ]);
19548 let view = c
19549 .aplicacao_view()
19550 .expect("Aplicacao kind must produce an aplicacao_view");
19551 assert_eq!(
19552 view.membros(),
19553 c.membros(),
19554 "aplicacao_view must fold Caixa::membros verbatim into \
19555 AplicacaoSpec::membros — the accessor and the view \
19556 composer must route through the same substrate-primitive \
19557 typed dispatch on the outer :membros slice (got view \
19558 membros={:?}, expected {:?})",
19559 view.membros(),
19560 c.membros(),
19561 );
19562 }
19563
19564 #[test]
19565 fn membros_projects_slice_by_borrow() {
19566 // The by-borrow pin: [`Caixa::membros`] returns `&[Membro]` by
19567 // borrow — the returned slice borrows the underlying
19568 // `Vec<Membro>` storage of the `:membros` slot and the
19569 // accessor must not clone the backing `Vec` on every call.
19570 // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
19571 // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
19572 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
19573 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
19574 // `exe_projects_slice_by_borrow` 65d9527,
19575 // `servicos_projects_slice_by_borrow` 611f78b,
19576 // `deps_projects_slice_by_borrow` ad34b4e,
19577 // `deps_dev_projects_slice_by_borrow` f7fd81e,
19578 // `upgrade_from_projects_slice_by_borrow` 2a1f907,
19579 // `children_projects_slice_by_borrow` c17b51e) on the sibling
19580 // outer top-level [`Caixa`] scalar-element and composite-
19581 // element `&[T]` axes — folds on the outer-`Caixa` M3 mesh-
19582 // slot composite-element `&[Composite]` axis: the accessor's
19583 // returned slice must borrow from `&self` (the returned
19584 // reference's lifetime is tied to `&self`), and calling the
19585 // accessor twice on the same [`Caixa`] must yield slices that
19586 // are pointer-equal (the underlying byte-buffer is the storage
19587 // `Vec`'s allocation, not a fresh copy) as well as value-equal
19588 // (idempotent, no side effects on `&self`).
19589 //
19590 // Pins against a future silent detour that returned an owned
19591 // `Vec<Membro>` (which would type-check but silently clone on
19592 // every call), a `&Vec<Membro>` return (which would leak the
19593 // backing `Vec`'s grow/push/reserve surface no downstream
19594 // consumer reaches for), or a one-arm-only accessor that
19595 // returned a saturating value on some sentinel input.
19596 use crate::aplicacao::Membro;
19597 for membros in [
19598 vec![],
19599 vec![Membro {
19600 caixa: "cart".into(),
19601 versao: "^0.1".into(),
19602 }],
19603 vec![
19604 Membro {
19605 caixa: "cart".into(),
19606 versao: "^0.1".into(),
19607 },
19608 Membro {
19609 caixa: "pricing".into(),
19610 versao: "^0.2".into(),
19611 },
19612 ],
19613 ] {
19614 let c = caixa_aplicacao_with_membros(membros.clone());
19615 let first = c.membros();
19616 let second = c.membros();
19617 assert_eq!(
19618 first, second,
19619 "Caixa::membros must be idempotent — two successive \
19620 calls on the same &self must return the same &[Membro]",
19621 );
19622 assert_eq!(
19623 first.as_ptr(),
19624 second.as_ptr(),
19625 "Caixa::membros must borrow the underlying Vec<Membro> \
19626 storage — two successive calls must return slices with \
19627 the same backing pointer (a fresh Vec<Membro> clone \
19628 would change the pointer on every call)",
19629 );
19630 assert_eq!(
19631 first,
19632 membros.as_slice(),
19633 "Caixa::membros must return :membros verbatim by borrow \
19634 — got {first:?}, expected {membros:?}",
19635 );
19636 }
19637 }
19638
19639 // ── Caixa::contratos — outer top-level &[WitContract] composite-slice accessor ──
19640
19641 fn caixa_aplicacao_with_contratos(contratos: Vec<crate::aplicacao::WitContract>) -> Caixa {
19642 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19643 c.kind = CaixaKind::Aplicacao;
19644 c.contratos = contratos;
19645 c
19646 }
19647
19648 fn contrato_http_for_test(
19649 de: &str,
19650 para: &str,
19651 endpoint: &str,
19652 ) -> crate::aplicacao::WitContract {
19653 crate::aplicacao::WitContract {
19654 de: de.into(),
19655 para: para.into(),
19656 wit: "wasi:http/proxy".into(),
19657 endpoint: Some(endpoint.into()),
19658 subject: None,
19659 slot: None,
19660 }
19661 }
19662
19663 #[test]
19664 fn contratos_returns_contratos_slice_verbatim_across_permutations() {
19665 // The canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
19666 // composite `&[WitContract]`-return slice-shape pin:
19667 // [`Caixa::contratos`] must return the `:contratos` typed
19668 // `Vec<WitContract>` verbatim as a `&[WitContract]` slice-view
19669 // over the same backing buffer the raw
19670 // `self.contratos.as_slice()` field access borrows from,
19671 // element-equal across every representative fixture in the
19672 // accept-set — `[]` (the "no contracts declared" arm every
19673 // non-`Aplicacao`-kind `defcaixa` carries by
19674 // `#[serde(default)]` and every leaf-Aplicacao with a single
19675 // member carries), a canonical single-edge fixture (the
19676 // minimal directed-graph shape: one HTTP-shape `(cart → catalog)`
19677 // edge), and a canonical multi-edge fixture with three distinct
19678 // edges (the checkout-shape Aplicacao's HTTP-fan pattern:
19679 // `(cart → catalog)`, `(cart → pricing)`, `(cart → auth)`).
19680 //
19681 // Pins against a future silent detour that returned an owned
19682 // `Vec<WitContract>` (which would type-check but silently clone
19683 // on every accessor call, breaking the zero-cost projection
19684 // every peer sibling slice accessor carries), an axis-shuffled
19685 // projection (a future detour that reordered edges through the
19686 // accessor would silently split the paired
19687 // [`crate::StandardLayout::verify`] per-Aplicacao gate's
19688 // traversal input from the peer [`Self::aplicacao_view`] fold-
19689 // in path's clone-order input, since every canonical
19690 // `caixa-mesh` renderer's per-`(:de, :para)` adjacency-list
19691 // seed dispatch reads the edge set through the same slice),
19692 // or a reference to an operator-resolved overlay (the future
19693 // per-cluster `:contratos-overrides` slot — its resolution
19694 // must land at exactly this accessor body, not silently divert
19695 // the raw slot away from a second consumer).
19696 //
19697 // Fourth outer top-level [`Caixa`] `&[Composite]`-return slice
19698 // accessor pin on the substrate primitive for M2 / M3 typed-
19699 // slot vec-carry axes — closes the outer-`Caixa`
19700 // `&[Composite]` composite-slice sub-family the sibling M2
19701 // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
19702 // (2a1f907) and
19703 // `children_returns_children_slice_verbatim_across_permutations`
19704 // (c17b51e) pins opened and the M3
19705 // `membros_returns_membros_slice_verbatim_across_permutations`
19706 // (0f26987) pin folded on, closing the outer-`Caixa` M3 mesh-
19707 // slot arm of the composite-slice sub-family. Peer at the outer
19708 // altitude of the closed inner-
19709 // [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
19710 // same MESH-COMPOSITION per-Aplicacao contract-list axis.
19711 let fixtures: Vec<Vec<crate::aplicacao::WitContract>> = vec![
19712 vec![],
19713 vec![contrato_http_for_test("cart", "catalog", "/items")],
19714 vec![
19715 contrato_http_for_test("cart", "catalog", "/items"),
19716 contrato_http_for_test("cart", "pricing", "/price"),
19717 contrato_http_for_test("cart", "auth", "/whoami"),
19718 ],
19719 ];
19720 for contratos in fixtures {
19721 let c = caixa_aplicacao_with_contratos(contratos.clone());
19722 assert_eq!(
19723 c.contratos(),
19724 contratos.as_slice(),
19725 "Caixa::contratos must return :contratos verbatim \
19726 (got {:?}, expected {contratos:?})",
19727 c.contratos(),
19728 );
19729 assert_eq!(
19730 c.contratos(),
19731 c.contratos.as_slice(),
19732 "Caixa::contratos must element-equal the raw \
19733 `self.contratos.as_slice()` field access across every \
19734 value in the Vec<WitContract> accept-set",
19735 );
19736 assert_eq!(
19737 c.contratos().is_empty(),
19738 c.contratos.is_empty(),
19739 "Caixa::contratos().is_empty() must byte-equal \
19740 self.contratos.is_empty() — a presence-bit drift would \
19741 silently split the paired Caixa::declared_mesh_slots \
19742 mesh declared-slot enumerator's presence probe from \
19743 the peer Caixa::aplicacao_view typed-view composer's \
19744 fold-in path",
19745 );
19746 }
19747 }
19748
19749 #[test]
19750 fn declared_mesh_slots_contratos_arm_routes_through_accessor() {
19751 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:contratos`
19752 // presence-probe arm must key off [`Caixa::contratos`], not the
19753 // raw `!self.contratos.is_empty()` field-probe. Structurally: a
19754 // `Caixa { contratos: vec![WitContract { .. }], .. }` must push
19755 // `M3_AUTHOR_KEY_CONTRATOS` onto the declared-slot list (the
19756 // presence bit is non-empty, so the mesh kind-coherence gate
19757 // must surface the slot as "declared"), and a `Caixa {
19758 // contratos: vec![], .. }` must NOT push the label (the "author
19759 // omitted the slot entirely" arm — the empty-slice partition
19760 // the serde-default folds onto). The pair jointly pins the
19761 // accessor + declared-slot enumerator composition: any future
19762 // silent detour that had the accessor collapse
19763 // `[WitContract { .. }]` to `[]` (a `.filter(|c| c.de() !=
19764 // "__reserved__")` projection) would silently absorb the
19765 // "declared but degenerate" arm at the accessor boundary and
19766 // the [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
19767 // coherence gate would silently accept a struct-literal
19768 // `Caixa` carrying the drift.
19769 //
19770 // Peer of the sibling
19771 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
19772 // (2a1f907),
19773 // `declared_supervisor_slots_children_arm_routes_through_accessor`
19774 // (c17b51e), and
19775 // `declared_mesh_slots_membros_arm_routes_through_accessor`
19776 // (0f26987) composition pins on the M2 `:upgrade-from` /
19777 // `:children` / M3 `:membros` composite-slice arms — same "the
19778 // enumerator gate must route through the substrate-primitive
19779 // typed dispatch" discipline extended onto the M3 `:contratos`
19780 // composite-slice arm, closing the M3 mesh-slot arm of the
19781 // declared-slot enumerator's routing invariant on the
19782 // composite-slice inputs.
19783 let c = caixa_aplicacao_with_contratos(vec![contrato_http_for_test(
19784 "cart", "catalog", "/items",
19785 )]);
19786 let slots = c.declared_mesh_slots();
19787 assert!(
19788 slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
19789 "declared_mesh_slots must push M3_AUTHOR_KEY_CONTRATOS when \
19790 `:contratos` is non-empty — the accessor and the enumerator \
19791 gate must route through the same substrate-primitive \
19792 typed dispatch on the outer :contratos presence bit (got \
19793 slots={slots:?})",
19794 );
19795 let c = caixa_aplicacao_with_contratos(vec![]);
19796 let slots = c.declared_mesh_slots();
19797 assert!(
19798 !slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
19799 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_CONTRATOS \
19800 when `:contratos` is empty — the author-omitted arm must \
19801 route through the accessor's empty-slice return unchanged \
19802 (got slots={slots:?})",
19803 );
19804 }
19805
19806 #[test]
19807 fn aplicacao_view_contratos_arm_routes_through_accessor() {
19808 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:contratos`
19809 // fold-in arm must key off [`Caixa::contratos`], not the raw
19810 // `self.contratos.clone()` field-clone. Structurally: a `Caixa
19811 // { kind: Aplicacao, contratos: vec![WitContract { de: "cart",
19812 // .. }, WitContract { de: "pricing", .. }], .. }` must fold the
19813 // per-edge list through the accessor into the typed
19814 // [`crate::AplicacaoSpec`] view's `contratos` slot verbatim —
19815 // every entry the accessor surfaces must land in the view's
19816 // `contratos` slot in the same order. The pair jointly pins
19817 // the accessor + view-composer composition: a future silent
19818 // detour that had the accessor shuffle or drop an edge would
19819 // silently split the paired declared-slot enumerator's
19820 // presence bit from the typed-view composer's edge-list, a
19821 // two-consumer split at the enumerator and the view composer
19822 // far from the source `caixa.lisp`.
19823 //
19824 // Peer of the sibling
19825 // `aplicacao_view_membros_arm_routes_through_accessor`
19826 // (0f26987) composition pin on the M3 `:membros` outer-
19827 // `&[Composite]` composite-slice arm, closing the aplicacao-
19828 // view composer's routing invariant on the composite-slice
19829 // inputs at the outer altitude.
19830 let c = caixa_aplicacao_with_contratos(vec![
19831 contrato_http_for_test("cart", "catalog", "/items"),
19832 contrato_http_for_test("cart", "pricing", "/price"),
19833 ]);
19834 let view = c
19835 .aplicacao_view()
19836 .expect("Aplicacao kind must produce an aplicacao_view");
19837 assert_eq!(
19838 view.contratos(),
19839 c.contratos(),
19840 "aplicacao_view must fold Caixa::contratos verbatim into \
19841 AplicacaoSpec::contratos — the accessor and the view \
19842 composer must route through the same substrate-primitive \
19843 typed dispatch on the outer :contratos slice (got view \
19844 contratos={:?}, expected {:?})",
19845 view.contratos(),
19846 c.contratos(),
19847 );
19848 }
19849
19850 #[test]
19851 fn contratos_projects_slice_by_borrow() {
19852 // The by-borrow pin: [`Caixa::contratos`] returns `&[WitContract]`
19853 // by borrow — the returned slice borrows the underlying
19854 // `Vec<WitContract>` storage of the `:contratos` slot and the
19855 // accessor must not clone the backing `Vec` on every call.
19856 // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
19857 // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
19858 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
19859 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
19860 // `exe_projects_slice_by_borrow` 65d9527,
19861 // `servicos_projects_slice_by_borrow` 611f78b,
19862 // `deps_projects_slice_by_borrow` ad34b4e,
19863 // `deps_dev_projects_slice_by_borrow` f7fd81e,
19864 // `upgrade_from_projects_slice_by_borrow` 2a1f907,
19865 // `children_projects_slice_by_borrow` c17b51e,
19866 // `membros_projects_slice_by_borrow` 0f26987) on the sibling
19867 // outer top-level [`Caixa`] scalar-element and composite-
19868 // element `&[T]` axes — closes the outer-`Caixa` M3 mesh-slot
19869 // composite-element `&[Composite]` axis on the by-borrow pin:
19870 // the accessor's returned slice must borrow from `&self` (the
19871 // returned reference's lifetime is tied to `&self`), and
19872 // calling the accessor twice on the same [`Caixa`] must yield
19873 // slices that are pointer-equal (the underlying byte-buffer is
19874 // the storage `Vec`'s allocation, not a fresh copy) as well as
19875 // value-equal (idempotent, no side effects on `&self`).
19876 //
19877 // Pins against a future silent detour that returned an owned
19878 // `Vec<WitContract>` (which would type-check but silently clone
19879 // on every call), a `&Vec<WitContract>` return (which would
19880 // leak the backing `Vec`'s grow/push/reserve surface no
19881 // downstream consumer reaches for), or a one-arm-only accessor
19882 // that returned a saturating value on some sentinel input.
19883 for contratos in [
19884 vec![],
19885 vec![contrato_http_for_test("cart", "catalog", "/items")],
19886 vec![
19887 contrato_http_for_test("cart", "catalog", "/items"),
19888 contrato_http_for_test("cart", "pricing", "/price"),
19889 ],
19890 ] {
19891 let c = caixa_aplicacao_with_contratos(contratos.clone());
19892 let first = c.contratos();
19893 let second = c.contratos();
19894 assert_eq!(
19895 first, second,
19896 "Caixa::contratos must be idempotent — two successive \
19897 calls on the same &self must return the same \
19898 &[WitContract]",
19899 );
19900 assert_eq!(
19901 first.as_ptr(),
19902 second.as_ptr(),
19903 "Caixa::contratos must borrow the underlying \
19904 Vec<WitContract> storage — two successive calls must \
19905 return slices with the same backing pointer (a fresh \
19906 Vec<WitContract> clone would change the pointer on \
19907 every call)",
19908 );
19909 assert_eq!(
19910 first,
19911 contratos.as_slice(),
19912 "Caixa::contratos must return :contratos verbatim by \
19913 borrow — got {first:?}, expected {contratos:?}",
19914 );
19915 }
19916 }
19917
19918 // ── drift-detection: Caixa top-level multi-word serde-derive-to-const identity ──
19919
19920 #[test]
19921 fn caixa_multi_word_serde_keys_match_lifted_top_level_key_consts() {
19922 // Load-bearing invariant: every multi-word top-level [`Caixa`]
19923 // serde-derived JSON key routes through a lifted `&'static str`
19924 // const. The Rust field names are `snake_case`
19925 // (`deps_dev` / `upgrade_from` / `max_restarts` /
19926 // `restart_window`); [`Caixa`]'s `#[serde(rename_all =
19927 // "camelCase")]` derive attribute maps each to the camelCase
19928 // byte-string the [`Caixa::to_lisp`] round-trip's
19929 // `serde_json::to_value(self)` step lands under before
19930 // `tatara_lisp::domain::json_to_sexp` re-projects the JSON keys
19931 // to the kebab-case `:deps-dev` / `:upgrade-from` /
19932 // `:max-restarts` / `:restart-window` author surface. Serialize
19933 // a fully-populated [`Caixa`] and pin that each canonical
19934 // byte-sequence appears verbatim in the JSON — a future
19935 // accidental `rename_all = "snake_case"` / `"kebab-case"` /
19936 // verbatim-field-name flip at the derive attribute (any of
19937 // which would silently break every [`Caixa::to_lisp`]
19938 // round-trip and the future M4 operator-side manifest ingest's
19939 // `Value::get(<key>)` navigation) surfaces here as a build-time
19940 // test failure at `manifest.rs`, not as an apply-time
19941 // `.get(<stale-canonical-const>)` returning `None` far from the
19942 // derive-attr drift's commit. Same discipline the sibling
19943 // `supervisor_spec_serde_keys_match_lifted_supervisor_key_consts`
19944 // (40cc4e5), `membro_serde_keys_match_lifted_membro_key_consts`
19945 // (ce80ca0), and `upgrade_from_entry_serde_keys_match_lifted_
19946 // m2_upgrade_from_key_consts` (36ffe65) pins established on the
19947 // sibling M2 supervision-tree, M3 [`Membro`] per-entry, and M2
19948 // [`UpgradeFromEntry`] per-entry axes — extended here to the
19949 // enclosing M0 [`Caixa`] top-level axis so the last of the four
19950 // multi-word top-level [`Caixa`] serde-derived JSON keys
19951 // (`depsDev`) joins the substrate's "one canonical byte-string
19952 // per typed serialized-key axis" discipline.
19953 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
19954 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
19955 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
19956 c.deps_dev = vec![Dep::simple("tatara-check", "^0.1")];
19957 c.upgrade_from = vec![UpgradeFromEntry {
19958 from: "0.0.1".into(),
19959 instructions: vec![UpgradeInstruction::Restart],
19960 }];
19961 c.estrategia = Some(RestartStrategy::OneForOne);
19962 c.max_restarts = Some(3);
19963 c.restart_window = Some("60s".into());
19964 c.children = vec![ChildSpec {
19965 caixa: "child".into(),
19966 versao: "^0.1".into(),
19967 restart: RestartPolicy::Permanent,
19968 }];
19969 let json = serde_json::to_string(&c).unwrap();
19970 for key in [
19971 crate::render::CAIXA_KEY_DEPS_DEV,
19972 crate::render::M2_KEY_UPGRADE_FROM,
19973 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
19974 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
19975 ] {
19976 let quoted = format!("\"{key}\"");
19977 assert!(
19978 json.contains("ed),
19979 "serialized Caixa must carry the lifted top-level \
19980 multi-word byte-sequence {quoted} verbatim in the JSON \
19981 emission (got: {json})",
19982 );
19983 }
19984 }
19985
19986 #[test]
19987 fn caixa_top_level_multi_word_key_consts_are_pairwise_distinct() {
19988 // Cross-axis drift-detection pin: a future collapse of the four
19989 // canonical [`Caixa`] top-level multi-word byte-strings onto the
19990 // same value (e.g. an accidental copy-paste flip of
19991 // [`crate::render::CAIXA_KEY_DEPS_DEV`] to also read
19992 // `"upgradeFrom"`) would silently reroute every downstream
19993 // `Value::get(<key>)` probe on one axis onto the sibling axis's
19994 // top-level entry and pass every propagation-probe test that
19995 // expected only the stale axis's value. Peer of the sibling
19996 // four-way distinct pin on the `SUPERVISOR_KEY_*` tetrad
19997 // (40cc4e5) and the two-way pin on `MEMBRO_KEY_*` (ce80ca0).
19998 let all = [
19999 crate::render::CAIXA_KEY_DEPS_DEV,
20000 crate::render::M2_KEY_UPGRADE_FROM,
20001 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
20002 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
20003 ];
20004 for (i, a) in all.iter().enumerate() {
20005 for b in all.iter().skip(i + 1) {
20006 assert_ne!(
20007 a, b,
20008 "Caixa top-level multi-word key consts must be \
20009 pairwise-distinct canonical byte-sequences — got \
20010 `{a}` == `{b}`",
20011 );
20012 }
20013 }
20014 }
20015
20016 #[test]
20017 fn caixa_top_level_multi_word_key_consts_are_lower_camel_case_shape() {
20018 // Shape-pin: every [`Caixa`] top-level multi-word key const must
20019 // be a lowerCamelCase byte-sequence (no `snake_case`
20020 // underscores, no `kebab-case` hyphens, no leading colon, no
20021 // `PascalCase` leading capital, no whitespace / dots) — the
20022 // canonical shape the `#[serde(rename_all = "camelCase")]`
20023 // derive produces on [`Caixa`]. A future flip to a
20024 // non-camelCase attribute at the derive surfaces both here
20025 // (this test fails on the stale-constant shape) and at
20026 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
20027 // (that test fails on the mismatch between const and derive).
20028 // Peer with `membro_key_consts_are_lower_camel_case_shape`
20029 // (ce80ca0) and `supervisor_key_consts_are_lower_camel_case_shape`
20030 // (40cc4e5) on the sibling per-entry / supervisor-tree axes.
20031 for key in [
20032 crate::render::CAIXA_KEY_DEPS_DEV,
20033 crate::render::M2_KEY_UPGRADE_FROM,
20034 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
20035 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
20036 ] {
20037 assert!(
20038 !key.is_empty(),
20039 "Caixa top-level multi-word key const must be non-empty \
20040 (got {key:?})"
20041 );
20042 let first = key.chars().next().unwrap();
20043 assert!(
20044 first.is_ascii_lowercase(),
20045 "Caixa top-level multi-word key const must lead with an \
20046 ASCII-lowercase byte (got {key:?}, leads with {first:?})",
20047 );
20048 assert!(
20049 key.chars().all(|c| c.is_ascii_alphanumeric()),
20050 "Caixa top-level multi-word key const must be \
20051 ASCII-alphanumeric only — no `_` / `-` / `:` / `.` / \
20052 whitespace (got {key:?})",
20053 );
20054 }
20055 }
20056
20057 #[test]
20058 fn caixa_key_deps_dev_pins_canonical_camel_case_byte_string() {
20059 // Scalar-value pin: the byte-string the
20060 // [`crate::render::CAIXA_KEY_DEPS_DEV`] const resolves to,
20061 // asserted verbatim. A future rebrand (`depsDev` → `devDeps`
20062 // matching Cargo's verbatim `dev-dependencies` axis, `depsDev`
20063 // → `depsTest` matching a hypothetical per-test-target
20064 // vocabulary flip) lands as an edit to exactly one const AND
20065 // one derive attribute — the sibling
20066 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
20067 // pin already ties the const to the derive attribute, so a
20068 // rebrand that touches only one side of the pair fails at
20069 // caixa-core build time. Same "scalar-value pin per const"
20070 // discipline the sibling
20071 // `m2_top_level_author_key_consts_pin_canonical_kebab_case_labels`
20072 // (f49c8b0) and `contrato_key_consts_pin_canonical_camel_case_labels`
20073 // (ca463a4) pins carry on the peer M2 / M3 top-level slot axes.
20074 assert_eq!(crate::render::CAIXA_KEY_DEPS_DEV, "depsDev");
20075 }
20076
20077 #[test]
20078 fn caixa_key_deps_pins_canonical_byte_string() {
20079 // Scalar-value pin: the byte-string the
20080 // [`crate::render::CAIXA_KEY_DEPS`] const resolves to, asserted
20081 // verbatim. Peer of `caixa_key_deps_dev_pins_canonical_camel_case_byte_string`
20082 // on the two-list dep-graph serialized-key axis — the sibling
20083 // pin covers the multi-word `deps_dev → depsDev` camelCase
20084 // arm, this pin covers the single-word `deps → deps` no-op arm
20085 // (the [`crate::Caixa::deps`] field name carries no `_`, so the
20086 // `#[serde(rename_all = "camelCase")]` derive is a no-op on this
20087 // axis and the emitted JSON key equals the source-side field
20088 // name byte-for-byte). A future [`crate::Caixa::deps`] field
20089 // rename (`deps` → `dependencies` matching Cargo's verbatim
20090 // `[dependencies]` axis, `deps` → `runtime_deps` matching a
20091 // hypothetical per-runtime-target vocabulary flip) OR an added
20092 // `#[serde(rename = "…")]` explicit override lands as an edit
20093 // to exactly one const AND one derive-attr / field name — the
20094 // sibling `caixa_deps_serde_key_matches_lifted_caixa_key_deps`
20095 // pin ties the const to the emitted JSON key, so a rebrand
20096 // that touches only one side of the pair fails at caixa-core
20097 // build time.
20098 assert_eq!(crate::render::CAIXA_KEY_DEPS, "deps");
20099 }
20100
20101 #[test]
20102 fn caixa_deps_serde_key_matches_lifted_caixa_key_deps() {
20103 // Load-bearing invariant on the single-word `deps` top-level
20104 // axis: the byte-string [`crate::render::CAIXA_KEY_DEPS`] pins
20105 // must appear verbatim in the JSON [`Caixa::to_lisp`]'s
20106 // `serde_json::to_value(self)` step emits. Serialize a
20107 // populated [`Caixa`] whose `:deps` slot carries at least one
20108 // entry (the `#[serde(default)]` attribute on the field emits
20109 // an empty `[]` even without members, but a non-empty vec
20110 // additionally covers the codec's per-`Dep`-entry emission
20111 // path) and pin that `"deps"` appears verbatim in the JSON
20112 // emission — a future accidental `rename_all = "snake_case"` /
20113 // `"kebab-case"` flip at the derive attribute (or an added
20114 // `#[serde(rename = "…")]` explicit override on the field, or
20115 // a Rust field rename) would break every [`Caixa::to_lisp`]
20116 // round-trip and the future M4 operator-side manifest ingest's
20117 // `Value::get(CAIXA_KEY_DEPS)` navigation — surfaces here as a
20118 // build-time test failure at `manifest.rs`, not as an
20119 // apply-time `.get(<stale-canonical-const>)` returning `None`
20120 // far from the drift's commit. Peer of the sibling
20121 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
20122 // multi-word pin on the same M0 [`Caixa`] top-level
20123 // serialized-key axis, extended here to the single-word arm
20124 // the multi-word test's `rename_all = "camelCase"` sweep can't
20125 // reach (single-word `deps → deps` is a no-op the multi-word
20126 // pin's `\"depsDev\"` / `\"upgradeFrom\"` / `\"maxRestarts\"` /
20127 // `\"restartWindow\"` byte-scan can never observe).
20128 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20129 c.deps = vec![Dep::simple("caixa-core", "^0.1")];
20130 let json = serde_json::to_string(&c).unwrap();
20131 let quoted = format!("\"{}\"", crate::render::CAIXA_KEY_DEPS);
20132 assert!(
20133 json.contains("ed),
20134 "serialized Caixa must carry the lifted top-level `deps` \
20135 byte-sequence {quoted} verbatim in the JSON emission (got: \
20136 {json})",
20137 );
20138 }
20139
20140 #[test]
20141 fn caixa_dep_graph_two_list_key_consts_are_pairwise_distinct() {
20142 // Cross-axis drift-detection pin on the two-list dep-graph
20143 // renderer-side wire-key axis: a future collapse of the
20144 // canonical [`crate::render::CAIXA_KEY_DEPS`] /
20145 // [`crate::render::CAIXA_KEY_DEPS_DEV`] byte-strings onto the
20146 // same value (e.g. an accidental copy-paste flip of
20147 // `CAIXA_KEY_DEPS_DEV` to also read `"deps"`) would silently
20148 // reroute every downstream `Value::get(<key>)` probe on one
20149 // axis onto the sibling axis's dep-list and pass every
20150 // propagation-probe test that expected only the stale axis's
20151 // value — a dev-only dep would land in the runtime closure at
20152 // publish time, or a runtime dep would be excluded from the
20153 // published lacre. Peer of the sibling four-way distinct pin
20154 // on the top-level multi-word tetrad
20155 // (`caixa_top_level_multi_word_key_consts_are_pairwise_distinct`)
20156 // and the two-way pin on the sibling
20157 // [`DEP_AUTHOR_KEY_DEPS`] / [`DEP_AUTHOR_KEY_DEPS_DEV`]
20158 // author-facing arm (4da6fba's test), extended here to the
20159 // renderer-side wire-key arm of the same two-list dep-graph
20160 // axis so both halves of the "one canonical byte-string per
20161 // typed axis per (author, wire)" grid carry the same
20162 // distinct-ness discipline.
20163 assert_ne!(
20164 crate::render::CAIXA_KEY_DEPS,
20165 crate::render::CAIXA_KEY_DEPS_DEV,
20166 "CAIXA_KEY_DEPS and CAIXA_KEY_DEPS_DEV must be distinct \
20167 canonical byte-sequences on the two-list dep-graph \
20168 renderer-side wire-key axis"
20169 );
20170 }
20171
20172 // ── DepList / Caixa::push_dep pin ────────────────────────────────
20173 //
20174 // The compounding pin: the two-arm closed-set typed enum
20175 // [`crate::dep::DepList`] carries the runtime-closure `:deps`
20176 // (`Prod`) vs dev-only-closure `:deps-dev` (`Dev`) dispatch every
20177 // consumer of the top-level manifest's dep-mutation surface reads
20178 // through, and the typed dispatch [`Caixa::push_dep`] on the
20179 // substrate primitive folds the "select list → check within-list
20180 // dup → push" cascade onto one method call. Prior to this landing
20181 // the two axes lived across two `&'static str` constants
20182 // (`DEP_AUTHOR_KEY_DEPS`, `DEP_AUTHOR_KEY_DEPS_DEV`) with no closed-
20183 // set type carrying the pair; the `feira add` mutation site's
20184 // inline `if self.dev { &mut caixa.deps_dev } else { &mut
20185 // caixa.deps }` dispatch expressed no compile-time link back to
20186 // the substrate primitive, and a future third dep-list axis would
20187 // have silently split at every open-coded mutation site.
20188
20189 #[test]
20190 fn dep_list_as_str_routes_through_lifted_author_key_constants() {
20191 // Every arm returns the same `&'static str` the substrate's
20192 // canonical `DEP_AUTHOR_KEY_DEPS` / `DEP_AUTHOR_KEY_DEPS_DEV`
20193 // constants carry. A future rebrand on either constant reaches
20194 // the enum through one edit; a regression to inline literals
20195 // (e.g. `Prod => ":deps"`) would silently split the diagnostic
20196 // quotes from the wire-format constants every consumer routes
20197 // through and this pin flags it at build time.
20198 assert_eq!(
20199 crate::dep::DepList::Prod.as_str(),
20200 crate::render::DEP_AUTHOR_KEY_DEPS
20201 );
20202 assert_eq!(
20203 crate::dep::DepList::Dev.as_str(),
20204 crate::render::DEP_AUTHOR_KEY_DEPS_DEV
20205 );
20206 }
20207
20208 #[test]
20209 fn dep_list_display_routes_through_as_str() {
20210 // Same as-str-through-Display convergence discipline the
20211 // sibling closed-set typed enums carry — a `format!("{list}")`
20212 // call must land byte-for-byte on the accessor's return so a
20213 // future consumer that formats the enum for a diagnostic line
20214 // reaches the same wire-format constant the wire-format
20215 // producers do.
20216 assert_eq!(
20217 format!("{}", crate::dep::DepList::Prod),
20218 crate::dep::DepList::Prod.as_str()
20219 );
20220 assert_eq!(
20221 format!("{}", crate::dep::DepList::Dev),
20222 crate::dep::DepList::Dev.as_str()
20223 );
20224 }
20225
20226 #[test]
20227 fn dep_list_all_enumerates_every_variant_once() {
20228 // Exhaustive-iteration pin — every arm appears exactly once in
20229 // `ALL`, matching the closed set the compiler enforces on the
20230 // sibling `match self` arms. A future variant addition that
20231 // extends only one method's match without extending `ALL`
20232 // would silently drop the new arm from every consumer that
20233 // iterates the slice.
20234 let variants: &[crate::dep::DepList] = crate::dep::DepList::ALL;
20235 assert!(variants.contains(&crate::dep::DepList::Prod));
20236 assert!(variants.contains(&crate::dep::DepList::Dev));
20237 assert_eq!(variants.len(), 2);
20238 }
20239
20240 #[test]
20241 fn dep_list_from_wire_returns_prod_on_deps_wire_scalar() {
20242 // Reverse projection on the two-list dep-graph axis: the
20243 // author-surface wire tag the sibling `as_str` emitter walks
20244 // for `Prod` (`:deps` via `DEP_AUTHOR_KEY_DEPS`) parses back to
20245 // `Some(DepList::Prod)`. A regression that hand-rolled the
20246 // per-arm match without routing through the lifted
20247 // `DEP_AUTHOR_KEY_DEPS` const would silently disagree on any
20248 // future wire-tag rebrand and this pin flags it at build time.
20249 assert_eq!(
20250 crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS),
20251 Some(crate::dep::DepList::Prod)
20252 );
20253 }
20254
20255 #[test]
20256 fn dep_list_from_wire_returns_dev_on_deps_dev_wire_scalar() {
20257 // Peer of the `Prod`-arm pin on the dev-only axis: the
20258 // author-surface wire tag the sibling `as_str` emitter walks
20259 // for `Dev` (`:deps-dev` via `DEP_AUTHOR_KEY_DEPS_DEV`) parses
20260 // back to `Some(DepList::Dev)`. Same drift-detection posture
20261 // as the peer arm — the sibling method `match` arms are
20262 // compiler-checked exhaustive so a future variant addition
20263 // trips at build time.
20264 assert_eq!(
20265 crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS_DEV),
20266 Some(crate::dep::DepList::Dev)
20267 );
20268 }
20269
20270 #[test]
20271 fn dep_list_from_wire_returns_none_on_unknown_wire_scalar() {
20272 // Every input outside the closed-set arm-string set the
20273 // sibling `as_str` emitter walks lands on the terminal `None`
20274 // fallback — no silent-accept surface. Sweeps a set of
20275 // plausibly-adjacent scalars (unprefixed wire form, PascalCase
20276 // rebrand candidates, foreign wire tags, empty string) so a
20277 // future variant addition that widened one wire form without
20278 // extending the emitter's arm-set would trip the sibling
20279 // round-trip pin below rather than silently accepting the new
20280 // form here.
20281 for candidate in [
20282 "",
20283 "deps",
20284 "deps-dev",
20285 ":deps ",
20286 ":Deps",
20287 ":DEPS",
20288 ":build-dep",
20289 ":tool-dep",
20290 "prod",
20291 "dev",
20292 ] {
20293 assert_eq!(
20294 crate::dep::DepList::from_wire(candidate),
20295 None,
20296 "from_wire({candidate:?}) must return None; every input outside \
20297 the {{DEP_AUTHOR_KEY_DEPS, DEP_AUTHOR_KEY_DEPS_DEV}} accept-set \
20298 the sibling as_str emitter walks lands on the terminal fallback",
20299 );
20300 }
20301 }
20302
20303 #[test]
20304 fn dep_list_round_trips_through_as_str_and_from_wire() {
20305 // Load-bearing round-trip pin: every arm the `ALL` iteration
20306 // exposes survives the `as_str` → `from_wire` composition
20307 // byte-for-byte. Same discipline the sibling closed-set enums
20308 // carry — `CaixaKind` /
20309 // `RestartStrategy` / `RestartPolicy` /
20310 // `PlacementStrategy` — extended onto the two-list dep-graph
20311 // axis. A future variant addition that extends `ALL` +
20312 // `as_str` without extending `from_wire` (or vice versa)
20313 // trips at build time on this iteration because the compiler
20314 // enforces exhaustiveness on the sibling `match self` arms.
20315 for &list in crate::dep::DepList::ALL {
20316 assert_eq!(
20317 crate::dep::DepList::from_wire(list.as_str()),
20318 Some(list),
20319 "DepList::from_wire(as_str({list:?})) must round-trip to Some({list:?}) — \
20320 a silent split between the forward emitter and the reverse parser \
20321 would drift the two halves of the two-list dep-graph axis's typed dispatch",
20322 );
20323 }
20324 }
20325
20326 #[test]
20327 fn push_dep_routes_to_deps_slot_on_prod_arm() {
20328 // The `Prod` arm dispatches to the runtime-closure `:deps`
20329 // slot every downstream lacre-pipeline consumer resolves at
20330 // build time. A future arm that regressed to inline `&mut
20331 // self.deps_dev` on the `Prod` path would silently reroute
20332 // every runtime dep into the dev-only closure at publish time
20333 // — this pin refuses that regression.
20334 let src = Caixa::template("host");
20335 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20336 let before_deps = caixa.deps().len();
20337 let before_deps_dev = caixa.deps_dev().len();
20338 let dep = Dep {
20339 nome: "caixa-teia".to_string(),
20340 versao: "^0.1".to_string(),
20341 fonte: None,
20342 opcional: false,
20343 caracteristicas: Vec::new(),
20344 };
20345 caixa
20346 .push_dep(crate::dep::DepList::Prod, dep)
20347 .expect("first push into :deps succeeds");
20348 assert_eq!(caixa.deps().len(), before_deps + 1);
20349 assert_eq!(caixa.deps_dev().len(), before_deps_dev);
20350 assert_eq!(caixa.deps().last().unwrap().nome(), "caixa-teia");
20351 }
20352
20353 #[test]
20354 fn push_dep_routes_to_deps_dev_slot_on_dev_arm() {
20355 // Peer of the sibling `Prod`-arm dispatch pin — the `Dev` arm
20356 // must dispatch to the dev-only-closure `:deps-dev` slot every
20357 // downstream test-facing artifact resolver reads. A future
20358 // regression that inverted the two arms would silently route
20359 // every dev-only dep into the runtime closure at publish time
20360 // and this pin catches it before the drift ships.
20361 let src = Caixa::template("host");
20362 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20363 let dep = Dep {
20364 nome: "tatara-check".to_string(),
20365 versao: "*".to_string(),
20366 fonte: None,
20367 opcional: false,
20368 caracteristicas: Vec::new(),
20369 };
20370 caixa
20371 .push_dep(crate::dep::DepList::Dev, dep)
20372 .expect("first push into :deps-dev succeeds");
20373 assert!(caixa.deps().is_empty());
20374 assert_eq!(caixa.deps_dev().len(), 1);
20375 assert_eq!(caixa.deps_dev().last().unwrap().nome(), "tatara-check");
20376 }
20377
20378 #[test]
20379 fn push_dep_refuses_within_list_duplicate_nome_with_typed_error() {
20380 // Within-list dup check routes through the canonical
20381 // [`DepError::DuplicateNome`] carrier — the substrate's typed
20382 // diagnostic for the same axis [`Caixa::validate_deps`]'s
20383 // parse-time [`crate::render::insert_first_seen`] walk raises
20384 // on. Prior to the lift the mutation site's inline
20385 // `bail!("dep '{}' already declared", …)` string-diagnostic
20386 // path expressed no through-line back to the typed error;
20387 // routing every dep-list refusal through one carrier means an
20388 // author reading a `feira add` refusal and a `feira build`
20389 // refusal reaches for the same corrective surface without
20390 // switching diagnostic idioms.
20391 let src = Caixa::template("host");
20392 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20393 let dep = Dep {
20394 nome: "caixa-teia".to_string(),
20395 versao: "^0.1".to_string(),
20396 fonte: None,
20397 opcional: false,
20398 caracteristicas: Vec::new(),
20399 };
20400 caixa
20401 .push_dep(crate::dep::DepList::Prod, dep.clone())
20402 .expect("first push succeeds");
20403 let dup = Dep {
20404 nome: "caixa-teia".to_string(),
20405 versao: "^0.2".to_string(),
20406 fonte: None,
20407 opcional: false,
20408 caracteristicas: Vec::new(),
20409 };
20410 let err = caixa
20411 .push_dep(crate::dep::DepList::Prod, dup)
20412 .expect_err("second push with same :nome refuses");
20413 assert_eq!(
20414 err,
20415 DepError::DuplicateNome {
20416 nome: "caixa-teia".to_string(),
20417 list: crate::render::DEP_AUTHOR_KEY_DEPS,
20418 }
20419 );
20420 // The refused mutation must not corrupt the target list —
20421 // exactly one entry lives past the refusal, matching the
20422 // canonical single-source-of-truth invariant `Caixa::deps()`
20423 // carries.
20424 assert_eq!(caixa.deps().len(), 1);
20425 }
20426
20427 #[test]
20428 fn push_dep_refuses_dup_on_dev_list_arm_names_deps_dev_key() {
20429 // Peer of the sibling `Prod`-arm dup-refusal pin — the `Dev`
20430 // arm's refusal must carry `DEP_AUTHOR_KEY_DEPS_DEV` in the
20431 // `list` payload so a future author reading the refusal grep's
20432 // for the correct `:deps-dev` block in their `caixa.lisp`,
20433 // not the sibling `:deps` block the runtime closure resolves.
20434 let src = Caixa::template("host");
20435 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20436 let dep = Dep {
20437 nome: "tatara-check".to_string(),
20438 versao: "*".to_string(),
20439 fonte: None,
20440 opcional: false,
20441 caracteristicas: Vec::new(),
20442 };
20443 caixa
20444 .push_dep(crate::dep::DepList::Dev, dep.clone())
20445 .expect("first push succeeds");
20446 let err = caixa
20447 .push_dep(crate::dep::DepList::Dev, dep)
20448 .expect_err("second push with same :nome refuses");
20449 assert!(matches!(
20450 err,
20451 DepError::DuplicateNome {
20452 ref nome,
20453 list,
20454 } if nome == "tatara-check"
20455 && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
20456 ));
20457 }
20458
20459 #[test]
20460 fn push_dep_allows_same_nome_across_prod_and_dev_lists() {
20461 // The within-list dup check is scoped to the target arm — a
20462 // caixa may legitimately carry the same `:nome` under both
20463 // `:deps` and `:deps-dev` (though the substrate's peer
20464 // [`crate::Caixa::validate_deps`] walk still refuses the
20465 // shape at parse time; the mutation-site refusal is scoped to
20466 // the mutation-site's list to match the peer parse-time
20467 // per-list [`crate::render::insert_first_seen`] discipline).
20468 // The two arms hold independent seen-sets.
20469 let src = Caixa::template("host");
20470 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20471 let dep_prod = Dep {
20472 nome: "shared".to_string(),
20473 versao: "^0.1".to_string(),
20474 fonte: None,
20475 opcional: false,
20476 caracteristicas: Vec::new(),
20477 };
20478 let dep_dev = Dep {
20479 nome: "shared".to_string(),
20480 versao: "*".to_string(),
20481 fonte: None,
20482 opcional: false,
20483 caracteristicas: Vec::new(),
20484 };
20485 caixa
20486 .push_dep(crate::dep::DepList::Prod, dep_prod)
20487 .expect("push into :deps succeeds");
20488 caixa
20489 .push_dep(crate::dep::DepList::Dev, dep_dev)
20490 .expect("push same :nome into :deps-dev succeeds");
20491 assert_eq!(caixa.deps().len(), 1);
20492 assert_eq!(caixa.deps_dev().len(), 1);
20493 }
20494
20495 #[test]
20496 fn deps_of_prod_returns_the_deps_slot_verbatim() {
20497 // The `Prod` arm of the typed-dispatch [`Caixa::deps_of`] read
20498 // accessor must project onto the runtime-closure `:deps` slot —
20499 // element-equal and length-equal to the sibling per-slot
20500 // [`Caixa::deps`] accessor's return over every per-caixa fixture.
20501 // A future arm that regressed to `self.deps_dev()` on the `Prod`
20502 // path would silently reroute every downstream typed-dispatch
20503 // walker (the [`Caixa::validate_deps`] per-list
20504 // [`crate::render::insert_first_seen`] dedup walk, any future
20505 // per-axis-parametrised consumer) into the sibling dev-only
20506 // closure and this pin refuses that regression.
20507 let src = Caixa::template("host");
20508 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20509 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
20510 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 0);
20511 let dep = Dep {
20512 nome: "caixa-teia".to_string(),
20513 versao: "^0.1".to_string(),
20514 fonte: None,
20515 opcional: false,
20516 caracteristicas: Vec::new(),
20517 };
20518 caixa
20519 .push_dep(crate::dep::DepList::Prod, dep.clone())
20520 .expect("push into :deps succeeds");
20521 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
20522 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 1);
20523 assert_eq!(
20524 caixa.deps_of(crate::dep::DepList::Prod)[0].nome(),
20525 "caixa-teia"
20526 );
20527 }
20528
20529 #[test]
20530 fn deps_of_dev_returns_the_deps_dev_slot_verbatim() {
20531 // Peer of the sibling `Prod`-arm pin — the `Dev` arm of
20532 // [`Caixa::deps_of`] must project onto the dev-only-closure
20533 // `:deps-dev` slot, element-equal and length-equal to the
20534 // sibling per-slot [`Caixa::deps_dev`] accessor's return. A
20535 // future regression that inverted the two arms would silently
20536 // route every dev-list walker onto the runtime closure and this
20537 // pin catches it before the drift ships.
20538 let src = Caixa::template("host");
20539 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20540 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
20541 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 0);
20542 let dep = Dep {
20543 nome: "tatara-check".to_string(),
20544 versao: "*".to_string(),
20545 fonte: None,
20546 opcional: false,
20547 caracteristicas: Vec::new(),
20548 };
20549 caixa
20550 .push_dep(crate::dep::DepList::Dev, dep)
20551 .expect("push into :deps-dev succeeds");
20552 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
20553 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 1);
20554 assert_eq!(
20555 caixa.deps_of(crate::dep::DepList::Dev)[0].nome(),
20556 "tatara-check"
20557 );
20558 }
20559
20560 #[test]
20561 fn deps_of_exhaustive_over_dep_list_all_covers_the_two_slots() {
20562 // Composition pin: iterating [`crate::dep::DepList::ALL`] through
20563 // [`Caixa::deps_of`] must land on the same two-slot partition the
20564 // per-slot [`Caixa::deps`] / [`Caixa::deps_dev`] accessors
20565 // expose — the canonical dispatch a future per-axis-parametrised
20566 // walker (a future `feira app graph` per-list dep summary, a
20567 // future M4 per-cluster dev-closure-audit overlay the CR
20568 // materializer resolves per-CR) reads through. Prior to the
20569 // lift the two-block iteration lived open-coded at every walker,
20570 // so a future third dep-list axis (`:deps-build`, per CAIXA-SDLC
20571 // §I) would have had to grow a third block at every consumer.
20572 // A regression that dropped the `Dev` arm from `ALL` would flip
20573 // the collected pairs to `[(":deps", &[])]` alone and this pin
20574 // refuses that shape.
20575 let src = Caixa::template("host");
20576 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20577 let prod_dep = Dep {
20578 nome: "caixa-teia".to_string(),
20579 versao: "^0.1".to_string(),
20580 fonte: None,
20581 opcional: false,
20582 caracteristicas: Vec::new(),
20583 };
20584 let dev_dep = Dep {
20585 nome: "tatara-check".to_string(),
20586 versao: "*".to_string(),
20587 fonte: None,
20588 opcional: false,
20589 caracteristicas: Vec::new(),
20590 };
20591 caixa
20592 .push_dep(crate::dep::DepList::Prod, prod_dep)
20593 .expect("push into :deps succeeds");
20594 caixa
20595 .push_dep(crate::dep::DepList::Dev, dev_dep)
20596 .expect("push into :deps-dev succeeds");
20597 let collected: Vec<(&'static str, usize, &str)> = crate::dep::DepList::ALL
20598 .iter()
20599 .map(|&list| {
20600 let slice = caixa.deps_of(list);
20601 (list.as_str(), slice.len(), slice[0].nome())
20602 })
20603 .collect();
20604 assert_eq!(
20605 collected,
20606 vec![
20607 (crate::render::DEP_AUTHOR_KEY_DEPS, 1, "caixa-teia"),
20608 (crate::render::DEP_AUTHOR_KEY_DEPS_DEV, 1, "tatara-check"),
20609 ]
20610 );
20611 }
20612
20613 #[test]
20614 fn caixa_deps_of_is_const_fn() {
20615 // Fail-before-pass-after pin on [`Caixa::deps_of`]'s
20616 // `const`-eval-surface posture. The typed-dispatch read
20617 // accessor forwards through the sibling `pub const fn`
20618 // [`Caixa::deps`] / [`Caixa::deps_dev`] per-slot slice
20619 // accessors on the two [`crate::dep::DepList`] enum arms —
20620 // every operator in the body is already `const`-callable
20621 // (`DepList` is a plain `#[derive(Copy)]` closed-set
20622 // discriminator so the `match` arms are const-evaluable, and
20623 // each arm dispatches through the sibling `pub const fn`
20624 // slice accessor). Any future accidental downgrade to
20625 // non-`const` fails the `deps_of_via_const_fn` wrapper below
20626 // at caixa-core build time with E0015 (`cannot call non-const
20627 // method`), strictly stronger than a runtime `assert!` and
20628 // side-stepping the destructor-in-const restriction the
20629 // `Caixa` fixture's owning `String` / `Vec<Dep>` carriers
20630 // rule out on the direct-`const _: () = assert!(...)`
20631 // residence.
20632 //
20633 // Peer of the sibling outer-`Caixa` accessor family pins
20634 // ([`caixa_outer_string_slice_return_accessor_family_is_const_fn`]
20635 // on the `&[String]` universal-axis surface,
20636 // [`caixa_outer_composite_slice_return_accessor_family_is_const_fn`]
20637 // on the outer `&[T]` composite-slice surface,
20638 // [`caixa_outer_option_composite_reference_return_accessor_family_is_const_fn`]
20639 // on the outer `Option<&Composite>` surface) — this pin
20640 // extends the `const`-eval-surface discipline onto the outer-
20641 // `Caixa` typed-dispatch read surface on the [`DepList`]-keyed
20642 // dep-list axis, closing the outer-`Caixa` accessor family's
20643 // last unlifted `pub fn` on the read side.
20644 const fn deps_of_via_const_fn(c: &Caixa, list: crate::dep::DepList) -> &[Dep] {
20645 c.deps_of(list)
20646 }
20647 let src = Caixa::template("host");
20648 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20649 // Empty-list arm: both `Prod` and `Dev` degenerate to the
20650 // empty slice with no silent `None` collapse — the
20651 // `#[serde(default)]` `Vec::new()` fold every `defcaixa` form
20652 // that omits the slot lands on.
20653 assert!(deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod).is_empty());
20654 assert!(deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev).is_empty());
20655 assert_eq!(
20656 deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod),
20657 caixa.deps()
20658 );
20659 assert_eq!(
20660 deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev),
20661 caixa.deps_dev()
20662 );
20663 // Populated arms: each list carries its own entry, and the
20664 // wrapper / direct dispatches agree byte-for-byte on the
20665 // slice-view under both non-empty arms.
20666 let prod_dep = Dep {
20667 nome: "caixa-teia".to_string(),
20668 versao: "^0.1".to_string(),
20669 fonte: None,
20670 opcional: false,
20671 caracteristicas: Vec::new(),
20672 };
20673 let dev_dep = Dep {
20674 nome: "tatara-check".to_string(),
20675 versao: "*".to_string(),
20676 fonte: None,
20677 opcional: false,
20678 caracteristicas: Vec::new(),
20679 };
20680 caixa
20681 .push_dep(crate::dep::DepList::Prod, prod_dep)
20682 .expect("push into :deps succeeds");
20683 caixa
20684 .push_dep(crate::dep::DepList::Dev, dev_dep)
20685 .expect("push into :deps-dev succeeds");
20686 assert_eq!(
20687 deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod),
20688 caixa.deps()
20689 );
20690 assert_eq!(
20691 deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev),
20692 caixa.deps_dev()
20693 );
20694 assert_eq!(
20695 deps_of_via_const_fn(&caixa, crate::dep::DepList::Prod)[0].nome(),
20696 "caixa-teia"
20697 );
20698 assert_eq!(
20699 deps_of_via_const_fn(&caixa, crate::dep::DepList::Dev)[0].nome(),
20700 "tatara-check"
20701 );
20702 }
20703
20704 #[test]
20705 fn validate_deps_iterates_through_dep_list_all_via_deps_of() {
20706 // Composition pin: the [`Caixa::validate_deps`] parse-time gate
20707 // must route its per-list [`crate::render::insert_first_seen`]
20708 // dedup walk through [`Caixa::deps_of`] + [`crate::dep::DepList::ALL`]
20709 // rather than the pre-lift open-coded two-block iteration over
20710 // `self.deps()` + `self.deps_dev()`. A regression that dropped
20711 // one arm (e.g. hand-inlining `self.deps()` alone) would silently
20712 // stop refusing within-list dups on the sibling arm; a
20713 // regression that flipped the arm-to-list-key mapping
20714 // (`Dev => DEP_AUTHOR_KEY_DEPS`) would silently mislabel the
20715 // diagnostic surface. Both drifts surface here through a paired
20716 // duplicate-name refusal per arm plus an offending-list-key
20717 // check on the emitted [`DepError::DuplicateNome`] carrier.
20718 for &list in crate::dep::DepList::ALL {
20719 let src = Caixa::template("host");
20720 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
20721 let dup = Dep {
20722 nome: "twin".to_string(),
20723 versao: "^0.1".to_string(),
20724 fonte: None,
20725 opcional: false,
20726 caracteristicas: Vec::new(),
20727 };
20728 match list {
20729 crate::dep::DepList::Prod => {
20730 caixa.deps.push(dup.clone());
20731 caixa.deps.push(dup);
20732 }
20733 crate::dep::DepList::Dev => {
20734 caixa.deps_dev.push(dup.clone());
20735 caixa.deps_dev.push(dup);
20736 }
20737 }
20738 let err = caixa
20739 .validate_deps()
20740 .expect_err("within-list duplicate :nome must refuse");
20741 assert_eq!(
20742 err,
20743 DepError::DuplicateNome {
20744 nome: "twin".to_string(),
20745 list: list.as_str(),
20746 },
20747 "validate_deps on {list} arm must emit \
20748 DepError::DuplicateNome carrying the arm's own \
20749 as_str() diagnostic — the arm-to-list-key mapping \
20750 flowed through DepList::ALL + Caixa::deps_of"
20751 );
20752 }
20753 }
20754
20755 #[test]
20756 fn caixa_licenca_default_pins_canonical_mit_byte() {
20757 // Bridge-arm pin: [`CAIXA_LICENCA_DEFAULT`] resolves to the
20758 // canonical SPDX-`"MIT"` byte today, the same license expression
20759 // every peer substrate-side consumer of the author-omitted
20760 // `:licenca` slot ([`caixa-helm`]'s `build_readme` fallback arm at
20761 // `caixa-helm/src/lib.rs`, the future M4
20762 // `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer's per-CR
20763 // `Chart.yaml annotations["artifacthub.io/license"]` emitter this
20764 // crate's [`Caixa::validate_licenca`] docstring roadmap already
20765 // names as the second consumer) fills into its per-consumer
20766 // README/annotation emit site. Pin the literal here (peer with the
20767 // [`crate::version::DEFAULT_PUBLISH_TAG_PREFIX`] /
20768 // [`crate::version::DEFAULT_GIT_REMOTE`] /
20769 // [`crate::version::DEFAULT_PLEME_GIT_ORG`] canonical-literal pins
20770 // on the sibling lifted-constant surfaces) so a future
20771 // substrate-side license-fallback rebrand surfaces here as a
20772 // coordinated edit-point: the sibling caixa-helm
20773 // `build_readme_license_line_routes_through_lifted_caixa_licenca_default`
20774 // pinning test already pins the equality at the renderer-emit
20775 // axis; this pin closes the second coordinate of the pair by
20776 // anchoring the lifted constant's current byte to the canonical
20777 // CAIXA-SDLC §I license scaffold's documented shape.
20778 assert_eq!(CAIXA_LICENCA_DEFAULT, "MIT");
20779 }
20780
20781 // ── Caixa::validate_upgrade_from — compound per-Caixa entry gate on ──
20782 // ── the M2 `:upgrade-from` slot: folds the three top-level ──
20783 // ── `crate::upgrade` validators (per-entry + cross-entry ──
20784 // ── duplicate-`:from`, cross-slot `:from < :versao` precedence, ──
20785 // ── cross-slot `:state-change` ↔ `:on-state-change` composition) ──
20786 // ── onto one substrate primitive. Byte-for-byte equivalent to the ──
20787 // ── pre-fold three-block cascade at ──
20788 // ── `crate::layout::StandardLayout::verify` under the same ──
20789 // ── canonical dispatch order. ──
20790
20791 #[test]
20792 fn validate_upgrade_from_folds_per_entry_arm_matches_gate() {
20793 // Fail-before-pass-after per-arm equivalence pin on the
20794 // per-entry + cross-entry axis: a fixture whose `:upgrade-from`
20795 // carries a per-entry-invalid `:from` (git-tag shape `"v0.1.0"`,
20796 // which `semver::Version::parse` rejects) surfaces the same
20797 // [`crate::UpgradeError`] through the compound gate
20798 // [`Caixa::validate_upgrade_from`] and the standalone per-entry
20799 // gate [`crate::upgrade::validate_upgrade_from`] on the same
20800 // [`Caixa::upgrade_from`] slice. Pins the fold — a silent
20801 // regression that de-folded the per-entry arm would surface here
20802 // as a mismatch between the two dispatches. Sibling in shape to
20803 // the peer per-slot-≡-standalone equivalence pins the
20804 // [`crate::AplicacaoSpec::validate_contratos`] /
20805 // [`crate::MeshPolicy::validate`] /
20806 // [`crate::SupervisorSpec::validate_children`] compound gates
20807 // each carry on their axes.
20808 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20809 c.upgrade_from = vec![crate::UpgradeFromEntry {
20810 from: "v0.1.0".into(),
20811 instructions: vec![crate::UpgradeInstruction::Restart],
20812 }];
20813 let via_method = c.validate_upgrade_from().unwrap_err();
20814 let via_standalone = crate::upgrade::validate_upgrade_from(c.upgrade_from()).unwrap_err();
20815 assert_eq!(
20816 via_method, via_standalone,
20817 "Caixa::validate_upgrade_from must surface the per-entry \
20818 axis's diagnostic byte-equal to the standalone \
20819 `crate::upgrade::validate_upgrade_from` on the same \
20820 upgrade_from() slice"
20821 );
20822 assert!(
20823 matches!(
20824 via_method,
20825 crate::UpgradeError::FromInvalid { ref from, .. } if from == "v0.1.0"
20826 ),
20827 "expected FromInvalid on the git-tag-shape `:from`, got {via_method:?}"
20828 );
20829 }
20830
20831 #[test]
20832 fn validate_upgrade_from_folds_versao_arm_matches_gate() {
20833 // Per-arm equivalence pin on the cross-slot `:from ↔ :versao`
20834 // precedence axis: a fixture with a well-formed `:from` (so the
20835 // per-entry arm passes) whose parsed semver is >= the caixa's
20836 // `:versao` under SemVer-2 precedence surfaces the same
20837 // [`crate::UpgradeError::FromNotBeforeVersao`] through both the
20838 // compound gate and the standalone
20839 // [`crate::upgrade::validate_upgrade_from_against_versao`] gate
20840 // keyed off the same `(upgrade_from, versao)` pair. Pins the
20841 // fold's second arm — reaching this arm through the compound
20842 // gate requires the per-entry arm to pass first, which itself
20843 // pins the per-arm cross-arm ordering.
20844 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20845 c.versao = "0.1.0".into();
20846 c.upgrade_from = vec![crate::UpgradeFromEntry {
20847 from: "0.2.0".into(),
20848 instructions: vec![crate::UpgradeInstruction::Restart],
20849 }];
20850 let via_method = c.validate_upgrade_from().unwrap_err();
20851 let via_standalone =
20852 crate::upgrade::validate_upgrade_from_against_versao(c.upgrade_from(), c.versao())
20853 .unwrap_err();
20854 assert_eq!(
20855 via_method, via_standalone,
20856 "Caixa::validate_upgrade_from must surface the \
20857 `:from >= :versao` diagnostic byte-equal to the standalone \
20858 `crate::upgrade::validate_upgrade_from_against_versao` on \
20859 the same (upgrade_from, versao) pair"
20860 );
20861 assert!(
20862 matches!(
20863 via_method,
20864 crate::UpgradeError::FromNotBeforeVersao { ref from, ref versao }
20865 if from == "0.2.0" && versao == "0.1.0"
20866 ),
20867 "expected FromNotBeforeVersao carrying the offending pair, got {via_method:?}"
20868 );
20869 }
20870
20871 #[test]
20872 fn validate_upgrade_from_folds_behavior_arm_matches_gate() {
20873 // Per-arm equivalence pin on the cross-slot `:state-change ↔
20874 // :on-state-change` composition axis: a fixture with a
20875 // well-formed `:from` strictly less than `:versao` (so the
20876 // per-entry and versao arms both pass) whose `:instructions`
20877 // list carries a `(:state-change …)` instruction with no
20878 // `:behavior :on-state-change` callback declared surfaces the
20879 // same [`crate::UpgradeError::StateChangeWithoutOnStateChangeCallback`]
20880 // through both the compound gate and the standalone
20881 // [`crate::upgrade::validate_upgrade_from_against_behavior`]
20882 // gate keyed off the same `(upgrade_from, behavior)` pair.
20883 // Reaching this arm through the compound gate requires both
20884 // prior arms to pass first — the ordering pin below pins the
20885 // per-arm dispatch order explicitly.
20886 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20887 c.versao = "0.2.0".into();
20888 c.behavior = None;
20889 c.upgrade_from = vec![crate::UpgradeFromEntry {
20890 from: "0.1.0".into(),
20891 instructions: vec![
20892 crate::UpgradeInstruction::LoadModule {
20893 module: "demo".into(),
20894 },
20895 crate::UpgradeInstruction::StateChange {
20896 script: std::path::PathBuf::from("lib/m.lisp"),
20897 },
20898 crate::UpgradeInstruction::SoftPurge {
20899 module: "demo-old".into(),
20900 },
20901 ],
20902 }];
20903 let via_method = c.validate_upgrade_from().unwrap_err();
20904 let via_standalone =
20905 crate::upgrade::validate_upgrade_from_against_behavior(c.upgrade_from(), c.behavior())
20906 .unwrap_err();
20907 assert_eq!(
20908 via_method, via_standalone,
20909 "Caixa::validate_upgrade_from must surface the \
20910 `:state-change` ↔ `:on-state-change` composition \
20911 diagnostic byte-equal to the standalone \
20912 `crate::upgrade::validate_upgrade_from_against_behavior` \
20913 on the same (upgrade_from, behavior) pair"
20914 );
20915 assert!(
20916 matches!(
20917 via_method,
20918 crate::UpgradeError::StateChangeWithoutOnStateChangeCallback {
20919 ref from,
20920 ref script,
20921 } if from == "0.1.0" && script == &std::path::PathBuf::from("lib/m.lisp")
20922 ),
20923 "expected StateChangeWithoutOnStateChangeCallback carrying \
20924 the offending (from, script) pair, got {via_method:?}"
20925 );
20926 }
20927
20928 #[test]
20929 fn validate_upgrade_from_per_entry_arm_fires_before_versao_arm() {
20930 // Cross-arm ordering pin between the first two arms of the
20931 // fold: a fixture carrying BOTH a per-entry-invalid `:from`
20932 // (`"v0.0.5"` — git-tag shape rejected by
20933 // [`crate::upgrade::validate_upgrade_from`]) AND a would-be
20934 // versao-precedence violation on a second entry (`"0.2.0" >=
20935 // :versao "0.1.0"`) surfaces the per-entry diagnostic first
20936 // through the compound gate. Sanity assertion: the second
20937 // entry alone under the same `:versao` trips the versao arm
20938 // on its own via the standalone
20939 // [`crate::upgrade::validate_upgrade_from_against_versao`], so
20940 // the per-entry-first surfacing is a real ordering property,
20941 // not a case where the versao arm silently accepts the
20942 // fixture. Pins the pre-fold layout wire-up's canonical
20943 // dispatch order (per-entry → versao → behavior) as a
20944 // property of the substrate primitive rather than a
20945 // convention of the layout call site.
20946 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
20947 c.versao = "0.1.0".into();
20948 c.upgrade_from = vec![
20949 crate::UpgradeFromEntry {
20950 from: "v0.0.5".into(),
20951 instructions: vec![crate::UpgradeInstruction::Restart],
20952 },
20953 crate::UpgradeFromEntry {
20954 from: "0.2.0".into(),
20955 instructions: vec![crate::UpgradeInstruction::Restart],
20956 },
20957 ];
20958 let err = c.validate_upgrade_from().unwrap_err();
20959 assert!(
20960 matches!(
20961 err,
20962 crate::UpgradeError::FromInvalid { ref from, .. } if from == "v0.0.5"
20963 ),
20964 "per-entry arm must fire before versao arm — expected \
20965 FromInvalid on `v0.0.5`, got {err:?}"
20966 );
20967 // Sanity: the versao-violating second entry alone under the
20968 // same `:versao` trips the versao arm on its own — proves the
20969 // per-entry-first surfacing above is a real ordering property.
20970 let sanity = crate::upgrade::validate_upgrade_from_against_versao(
20971 &[crate::UpgradeFromEntry {
20972 from: "0.2.0".into(),
20973 instructions: vec![crate::UpgradeInstruction::Restart],
20974 }],
20975 "0.1.0",
20976 )
20977 .unwrap_err();
20978 assert!(
20979 matches!(sanity, crate::UpgradeError::FromNotBeforeVersao { .. }),
20980 "sanity: the versao-violating fixture alone must trip the \
20981 versao arm — got {sanity:?}"
20982 );
20983 }
20984
20985 #[test]
20986 fn validate_upgrade_from_versao_arm_fires_before_behavior_arm() {
20987 // Cross-arm ordering pin between the second and third arms of
20988 // the fold: a fixture carrying BOTH a versao-precedence
20989 // violation (`:from "0.2.0" >= :versao "0.1.0"`) AND a
20990 // would-be missing-callback violation (a `(:state-change …)`
20991 // instruction with no `:behavior :on-state-change`) surfaces
20992 // the versao diagnostic first through the compound gate.
20993 // Sanity assertion: the missing-callback fixture alone (with
20994 // the versao-precedence violation removed by bumping
20995 // `:versao` past `:from`) trips the behavior arm on its own
20996 // via the standalone
20997 // [`crate::upgrade::validate_upgrade_from_against_behavior`],
20998 // so the versao-first surfacing is a real ordering property,
20999 // not a case where the behavior arm silently accepts the
21000 // fixture.
21001 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21002 c.versao = "0.1.0".into();
21003 c.behavior = None;
21004 c.upgrade_from = vec![crate::UpgradeFromEntry {
21005 from: "0.2.0".into(),
21006 instructions: vec![
21007 crate::UpgradeInstruction::LoadModule {
21008 module: "demo".into(),
21009 },
21010 crate::UpgradeInstruction::StateChange {
21011 script: std::path::PathBuf::from("lib/m.lisp"),
21012 },
21013 ],
21014 }];
21015 let err = c.validate_upgrade_from().unwrap_err();
21016 assert!(
21017 matches!(
21018 err,
21019 crate::UpgradeError::FromNotBeforeVersao { ref from, .. } if from == "0.2.0"
21020 ),
21021 "versao arm must fire before behavior arm — expected \
21022 FromNotBeforeVersao on `0.2.0`, got {err:?}"
21023 );
21024 // Sanity: the same instructions under a `:versao` that
21025 // accepts the `:from` (so the versao arm passes) trips the
21026 // behavior arm — proves the versao-first surfacing above is a
21027 // real ordering property.
21028 let sanity = crate::upgrade::validate_upgrade_from_against_behavior(
21029 &[crate::UpgradeFromEntry {
21030 from: "0.2.0".into(),
21031 instructions: vec![
21032 crate::UpgradeInstruction::LoadModule {
21033 module: "demo".into(),
21034 },
21035 crate::UpgradeInstruction::StateChange {
21036 script: std::path::PathBuf::from("lib/m.lisp"),
21037 },
21038 ],
21039 }],
21040 None,
21041 )
21042 .unwrap_err();
21043 assert!(
21044 matches!(
21045 sanity,
21046 crate::UpgradeError::StateChangeWithoutOnStateChangeCallback { .. }
21047 ),
21048 "sanity: the missing-callback fixture alone must trip the \
21049 behavior arm — got {sanity:?}"
21050 );
21051 }
21052
21053 #[test]
21054 fn validate_upgrade_from_accepts_clean_fixture() {
21055 // Positive control: a well-formed `:upgrade-from` (single entry
21056 // with `:from` strictly less than `:versao`, no
21057 // `:state-change` instruction so the behavior arm is vacuous)
21058 // passes the compound gate cleanly. A future tightening of any
21059 // one arm's accepted set surfaces here as a test failure
21060 // first. Mirrors the peer `validate_versao_accepts_canonical_forms`
21061 // positive-control posture on the sibling per-Caixa gate.
21062 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21063 c.versao = "0.2.0".into();
21064 c.upgrade_from = vec![crate::UpgradeFromEntry {
21065 from: "0.1.0".into(),
21066 instructions: vec![crate::UpgradeInstruction::Restart],
21067 }];
21068 c.validate_upgrade_from()
21069 .expect("clean fixture must pass the compound `:upgrade-from` gate");
21070 }
21071
21072 #[test]
21073 fn validate_upgrade_from_accepts_empty_upgrade_from() {
21074 // Positive control on the empty-list arm: a caixa without any
21075 // `:upgrade-from` block (the default `Vec::new()`
21076 // `#[serde(default)]` folds an omitted slot onto) passes the
21077 // compound gate cleanly regardless of `:versao` or `:behavior`
21078 // — each of the three standalone validators is vacuous on the
21079 // empty entry list. Pins the identity element of the fold on
21080 // the empty-slot side.
21081 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21082 assert!(
21083 c.upgrade_from().is_empty(),
21084 "template caixa must carry an empty :upgrade-from — got {:?}",
21085 c.upgrade_from()
21086 );
21087 c.validate_upgrade_from()
21088 .expect("empty :upgrade-from must pass the compound gate cleanly");
21089 }
21090
21091 // ── Caixa::validate_limits — compound per-Caixa entry gate on ──
21092 // ── the M2 `:limits` slot: folds the ──
21093 // ── [`crate::LimitsSpec::validate`] four-axis cascade on the ──
21094 // ── present-slot arm and the `Option::None` identity element on ──
21095 // ── the absent-slot arm onto one substrate primitive. ──
21096 // ── Byte-for-byte equivalent to the pre-fold ──
21097 // ── `if let Some(l) = caixa.limits() { l.validate() }` ──
21098 // ── unwrap-and-dispatch pattern at ──
21099 // ── `crate::layout::StandardLayout::verify` (`layout.rs`). ──
21100
21101 #[test]
21102 fn validate_limits_folds_arm_matches_gate() {
21103 // Fail-before-pass-after per-arm equivalence pin on the
21104 // present-slot arm: a fixture whose `:limits` carries a
21105 // zero-floor-violating `:fuel` (`Some(0)`, which
21106 // [`crate::LimitsSpec::validate`] rejects through
21107 // [`crate::LimitsError::FuelZero`]) surfaces the same
21108 // [`crate::LimitsError`] byte-equal through both the compound
21109 // gate [`Caixa::validate_limits`] and the standalone
21110 // [`crate::LimitsSpec::validate`] gate on the same `LimitsSpec`
21111 // value. Pins the fold — a silent regression that de-folded
21112 // the present-slot arm would surface here as a mismatch
21113 // between the two dispatches. Sibling in shape to the peer
21114 // per-arm equivalence pins the
21115 // [`crate::AplicacaoSpec::validate_contratos`] /
21116 // [`crate::MeshPolicy::validate`] /
21117 // [`crate::SupervisorSpec::validate_children`] /
21118 // [`Caixa::validate_upgrade_from`] compound gates each carry
21119 // on their axes.
21120 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21121 let l = crate::LimitsSpec {
21122 memory: None,
21123 fuel: Some(0),
21124 wall_clock: None,
21125 cpu: None,
21126 };
21127 c.limits = Some(l);
21128 let via_method = c.validate_limits().unwrap_err();
21129 let via_standalone = l.validate().unwrap_err();
21130 assert_eq!(
21131 via_method, via_standalone,
21132 "Caixa::validate_limits must surface the present-slot \
21133 arm's diagnostic byte-equal to the standalone \
21134 `LimitsSpec::validate` on the same `LimitsSpec` value"
21135 );
21136 assert!(
21137 matches!(via_method, crate::LimitsError::FuelZero),
21138 "expected FuelZero on the zero-floor-violating `:fuel`, \
21139 got {via_method:?}"
21140 );
21141 }
21142
21143 #[test]
21144 fn validate_limits_accepts_none() {
21145 // Positive control on the absent-slot arm (the fold's identity
21146 // element): a caixa without any `:limits` block (the
21147 // canonical "no bound declared — engine-default applies"
21148 // author shape [`crate::LimitsSpec::is_empty`]'s per-axis
21149 // `None` cascade reads, and the shape the [`Caixa::template`]
21150 // scaffold emits by construction) passes the compound gate
21151 // cleanly, regardless of any per-axis defect a subsequent
21152 // `Some(_)` binding would surface. Pins the identity element
21153 // of the fold on the absent-slot side, matching the peer
21154 // `validate_upgrade_from_accepts_empty_upgrade_from` positive-
21155 // control posture on the sibling M2 slot.
21156 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21157 assert!(
21158 c.limits().is_none(),
21159 "template caixa must carry an absent :limits — got {:?}",
21160 c.limits()
21161 );
21162 c.validate_limits()
21163 .expect("absent :limits must pass the compound gate cleanly");
21164 }
21165
21166 #[test]
21167 fn validate_limits_accepts_clean_fixture() {
21168 // Positive control on the present-slot arm: a caixa whose
21169 // `:limits` is `Some(LimitsSpec::default())` (all four axes
21170 // `None` — every axis absent under the outer `Some(_)`
21171 // binding, so every present-slot arm on
21172 // [`crate::LimitsSpec::validate`] is vacuous) passes the
21173 // compound gate cleanly. A future tightening of any one axis
21174 // that surfaces a diagnostic on the all-`None` `LimitsSpec`
21175 // would land here as a test failure first. Pins the
21176 // present-slot arm's accept-shape on the canonical
21177 // "declared-but-empty" author fixture the
21178 // `limits_round_trip_via_json` peer already round-trips
21179 // (`caixa-core/src/manifest.rs:6971`).
21180 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21181 c.limits = Some(crate::LimitsSpec::default());
21182 c.validate_limits()
21183 .expect("Some(LimitsSpec::default()) must pass the compound gate cleanly");
21184 }
21185
21186 // ── Caixa::validate_behavior — compound per-Caixa entry gate on ──
21187 // ── the M2 `:behavior` slot's pure value-shape surface: folds ──
21188 // ── the [`crate::BehaviorSpec::validate`] six-slot cascade on ──
21189 // ── the present-slot arm and the `Option::None` identity ──
21190 // ── element on the absent-slot arm onto one substrate primitive.──
21191 // ── Byte-for-byte equivalent to the pre-fold ──
21192 // ── `if let Some(b) = caixa.behavior() { b.validate() }` ──
21193 // ── unwrap-and-dispatch pattern at ──
21194 // ── `crate::layout::StandardLayout::verify` (`layout.rs`). The ──
21195 // ── on-disk callback-path existence walk stays open-coded at ──
21196 // ── the layout altitude because it needs the ──
21197 // ── [`crate::layout::LayoutInvariants::exists`] filesystem ──
21198 // ── oracle the pure typed-shape surface has no reference to — ──
21199 // ── mirror of the peer M2 `:upgrade-from` per-instruction ──
21200 // ── script-path existence probe that stayed at the layout ──
21201 // ── altitude after the [`Caixa::validate_upgrade_from`] lift ──
21202 // ── (d6801df) for the same reason. ──
21203
21204 #[test]
21205 fn validate_behavior_folds_arm_matches_gate() {
21206 // Fail-before-pass-after per-arm equivalence pin on the
21207 // present-slot arm: a fixture whose `:behavior` carries an
21208 // absolute-path `:on-init` (`"/etc/passwd"`, which
21209 // [`crate::BehaviorSpec::validate`] rejects through
21210 // [`crate::BehaviorError::AbsolutePath`]) surfaces the same
21211 // [`crate::BehaviorError`] byte-equal through both the
21212 // compound gate [`Caixa::validate_behavior`] and the standalone
21213 // [`crate::BehaviorSpec::validate`] gate on the same
21214 // `BehaviorSpec` value. Pins the fold — a silent regression
21215 // that de-folded the present-slot arm would surface here as a
21216 // mismatch between the two dispatches. Sibling in shape to the
21217 // peer per-arm equivalence pins the
21218 // [`Caixa::validate_limits`] (baa4688),
21219 // [`Caixa::validate_upgrade_from`] (d6801df),
21220 // [`crate::MeshPolicy::validate`],
21221 // [`crate::AplicacaoSpec::validate_contratos`], and
21222 // [`crate::SupervisorSpec::validate_children`] compound gates
21223 // each carry on their axes.
21224 use crate::BehaviorSpec;
21225 use std::path::PathBuf;
21226 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21227 let b = BehaviorSpec {
21228 on_init: Some(PathBuf::from("/etc/passwd")),
21229 ..Default::default()
21230 };
21231 c.behavior = Some(b.clone());
21232 let via_method = c.validate_behavior().unwrap_err();
21233 let via_standalone = b.validate().unwrap_err();
21234 assert_eq!(
21235 via_method, via_standalone,
21236 "Caixa::validate_behavior must surface the present-slot \
21237 arm's diagnostic byte-equal to the standalone \
21238 `BehaviorSpec::validate` on the same `BehaviorSpec` value"
21239 );
21240 assert!(
21241 matches!(via_method, crate::BehaviorError::AbsolutePath { .. }),
21242 "expected AbsolutePath on the absolute `:on-init` path, \
21243 got {via_method:?}"
21244 );
21245 }
21246
21247 #[test]
21248 fn validate_behavior_accepts_none() {
21249 // Positive control on the absent-slot arm (the fold's identity
21250 // element): a caixa without any `:behavior` block (the
21251 // canonical "no callback declared — the runtime falls back to
21252 // the wasm-engine's default per arm" author shape
21253 // [`crate::BehaviorSpec::is_empty`]'s per-slot `None` cascade
21254 // reads, and the shape the [`Caixa::template`] scaffold emits
21255 // by construction) passes the compound gate cleanly,
21256 // regardless of any per-slot defect a subsequent `Some(_)`
21257 // binding would surface. Pins the identity element of the fold
21258 // on the absent-slot side, matching the peer
21259 // `validate_limits_accepts_none` (baa4688) and
21260 // `validate_upgrade_from_accepts_empty_upgrade_from` (d6801df)
21261 // positive-control postures on the sibling M2 slots.
21262 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21263 assert!(
21264 c.behavior().is_none(),
21265 "template caixa must carry an absent :behavior — got {:?}",
21266 c.behavior()
21267 );
21268 c.validate_behavior()
21269 .expect("absent :behavior must pass the compound gate cleanly");
21270 }
21271
21272 #[test]
21273 fn validate_behavior_accepts_clean_fixture() {
21274 // Positive control on the present-slot arm: a caixa whose
21275 // `:behavior` is `Some(BehaviorSpec::default())` (all six
21276 // slots `None` — every slot absent under the outer `Some(_)`
21277 // binding, so every present-slot arm on
21278 // [`crate::BehaviorSpec::validate`] is vacuous) passes the
21279 // compound gate cleanly. A future tightening of any one arm
21280 // that surfaces a diagnostic on the all-`None` `BehaviorSpec`
21281 // would land here as a test failure first. Pins the
21282 // present-slot arm's accept-shape on the canonical
21283 // "declared-but-empty" author fixture the sibling
21284 // `empty_behavior_round_trip` peer already round-trips
21285 // (`caixa-core/src/behavior.rs` tests). Mirror of the peer
21286 // `validate_limits_accepts_clean_fixture` (baa4688)
21287 // positive-control posture on the sibling M2 `:limits` slot.
21288 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21289 c.behavior = Some(crate::BehaviorSpec::default());
21290 c.validate_behavior()
21291 .expect("Some(BehaviorSpec::default()) must pass the compound gate cleanly");
21292 }
21293
21294 // ── Caixa::validate_deps — compound per-Caixa entry gate on the ──
21295 // ── dep-graph axis: folds the two standalone validators ──
21296 // ── (per-entry + within-list duplicate walk that this method ──
21297 // ── opened on, cross-slot self-edge via ──
21298 // ── `crate::dep::validate_no_self_dep`) onto one substrate ──
21299 // ── primitive. Byte-for-byte equivalent to the pre-fold ──
21300 // ── two-block cascade at ──
21301 // ── `crate::layout::StandardLayout::verify` under the same ──
21302 // ── canonical dispatch order (per-entry → self-edge). ──
21303
21304 #[test]
21305 fn validate_deps_folds_per_entry_arm_matches_gate() {
21306 // Fail-before-pass-after per-arm equivalence pin on the
21307 // per-entry + within-list duplicate axis: a fixture whose
21308 // `:deps` carries a per-entry-invalid `:versao` (`"^bad"`,
21309 // which [`crate::parse_requirement`] rejects) surfaces the
21310 // same [`crate::DepError`] through the compound gate
21311 // [`Caixa::validate_deps`] and the standalone per-entry walk
21312 // ([`Dep::validate`]) on the offending entry. Pins the
21313 // fold — a silent regression that de-folded the per-entry arm
21314 // would surface here as a mismatch between the two
21315 // dispatches. Sibling in shape to the peer
21316 // `validate_upgrade_from_folds_per_entry_arm_matches_gate`
21317 // per-arm equivalence pin (d6801df) on the M2
21318 // `:upgrade-from` compound gate's per-entry arm, extended
21319 // here onto the universal-axis `:deps` compound gate's
21320 // per-entry arm.
21321 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21322 c.deps = vec![Dep::simple("d", "^bad")];
21323 let via_method = c.validate_deps().unwrap_err();
21324 let via_standalone = c.deps()[0].validate().unwrap_err();
21325 assert_eq!(
21326 via_method, via_standalone,
21327 "Caixa::validate_deps must surface the per-entry arm's \
21328 diagnostic byte-equal to the standalone \
21329 `Dep::validate` on the same offending entry",
21330 );
21331 assert!(
21332 matches!(
21333 via_method,
21334 DepError::VersaoInvalid { ref nome, .. } if nome == "d"
21335 ),
21336 "expected VersaoInvalid on the malformed :versao, got {via_method:?}",
21337 );
21338 }
21339
21340 #[test]
21341 fn validate_deps_folds_self_edge_arm_matches_gate() {
21342 // Per-arm equivalence pin on the cross-slot self-edge axis:
21343 // a fixture whose `:deps` lists the caixa's own `:nome`
21344 // (a self-dep, which
21345 // [`crate::dep::validate_no_self_dep`] rejects as a
21346 // structurally-invalid one-node cycle in the lacre closure's
21347 // dep-graph) surfaces the same [`crate::DepError::DepIsSelf`]
21348 // through both the compound gate and the standalone
21349 // [`crate::dep::validate_no_self_dep`] gate keyed off the
21350 // same `(deps, deps_dev, nome)` triple. Pins the fold's
21351 // second arm — reaching this arm through the compound gate
21352 // requires the per-entry + within-list duplicate walk to
21353 // pass first, which itself pins one cross-arm ordering step.
21354 // Sibling in shape to the peer
21355 // `validate_upgrade_from_folds_versao_arm_matches_gate` /
21356 // `_folds_behavior_arm_matches_gate` cross-slot equivalence
21357 // pins (d6801df) on the M2 `:upgrade-from` compound gate's
21358 // cross-slot arms.
21359 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21360 c.deps = vec![Dep::simple("demo", "^0.1")];
21361 let via_method = c.validate_deps().unwrap_err();
21362 let via_standalone =
21363 crate::dep::validate_no_self_dep(c.deps(), c.deps_dev(), c.nome()).unwrap_err();
21364 assert_eq!(
21365 via_method, via_standalone,
21366 "Caixa::validate_deps must surface the cross-slot \
21367 self-edge diagnostic byte-equal to the standalone \
21368 `crate::dep::validate_no_self_dep` on the same \
21369 (deps, deps_dev, nome) triple",
21370 );
21371 assert!(
21372 matches!(
21373 via_method,
21374 DepError::DepIsSelf { ref nome, list }
21375 if nome == "demo" && list == crate::render::DEP_AUTHOR_KEY_DEPS
21376 ),
21377 "expected DepIsSelf carrying (nome=\"demo\", list=\":deps\"), got {via_method:?}",
21378 );
21379 }
21380
21381 #[test]
21382 fn validate_deps_per_entry_arm_fires_before_self_edge_arm() {
21383 // Cross-arm ordering pin between the two arms of the fold:
21384 // a fixture carrying BOTH a per-entry-invalid `:versao`
21385 // (`"^bad"` — [`crate::parse_requirement`] rejects the
21386 // requirement grammar) on a non-self-dep entry AND a
21387 // would-be self-edge violation on a second entry (the
21388 // caixa's own `:nome` "demo") surfaces the per-entry
21389 // diagnostic first through the compound gate. Sanity
21390 // assertion: the second entry alone under the same parent
21391 // `:nome` trips the self-edge arm on its own via the
21392 // standalone [`crate::dep::validate_no_self_dep`], so the
21393 // per-entry-first surfacing is a real ordering property,
21394 // not a case where the self-edge arm silently accepts the
21395 // fixture. Pins the pre-fold layout wire-up's canonical
21396 // dispatch order (per-entry + within-list duplicate →
21397 // self-edge) as a property of the substrate primitive
21398 // rather than a convention of the layout call site. Sibling
21399 // in shape to
21400 // `validate_upgrade_from_per_entry_arm_fires_before_versao_arm`
21401 // (d6801df) on the M2 `:upgrade-from` compound gate's
21402 // per-arm ordering property.
21403 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21404 c.deps = vec![
21405 Dep::simple("orquestra", "^bad"),
21406 Dep::simple("demo", "^0.1"),
21407 ];
21408 let err = c.validate_deps().unwrap_err();
21409 assert!(
21410 matches!(
21411 err,
21412 DepError::VersaoInvalid { ref nome, .. } if nome == "orquestra"
21413 ),
21414 "per-entry arm must fire before self-edge arm — expected \
21415 VersaoInvalid on \"orquestra\", got {err:?}",
21416 );
21417 // Sanity: the self-referential entry alone under the same
21418 // parent `:nome` trips the self-edge arm on its own — proves
21419 // the per-entry-first surfacing above is a real ordering
21420 // property, not a case where the self-edge arm silently
21421 // accepts the fixture.
21422 let sanity = crate::dep::validate_no_self_dep(&[Dep::simple("demo", "^0.1")], &[], "demo")
21423 .unwrap_err();
21424 assert!(
21425 matches!(sanity, DepError::DepIsSelf { ref nome, .. } if nome == "demo"),
21426 "sanity: the self-referential entry alone must trip the \
21427 self-edge arm — got {sanity:?}",
21428 );
21429 }
21430
21431 #[test]
21432 fn validate_deps_accepts_clean_fixture() {
21433 // Positive control: a well-formed dep-graph (one `:deps`
21434 // entry naming a non-self DNS-1123 nome + Cargo-shaped
21435 // requirement, one `:deps-dev` entry on a distinct non-self
21436 // nome) passes the compound gate cleanly. A future
21437 // tightening of either arm's accepted set surfaces here as
21438 // a test failure first. Mirrors the peer
21439 // `validate_upgrade_from_accepts_clean_fixture` positive-
21440 // control posture on the sibling per-Caixa compound gate.
21441 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21442 c.deps = vec![Dep::simple("caixa-teia", "^0.1")];
21443 c.deps_dev = vec![Dep::simple("caixa-lint", "^0.2")];
21444 c.validate_deps()
21445 .expect("clean fixture must pass the compound `:deps` gate");
21446 }
21447
21448 #[test]
21449 fn validate_deps_accepts_empty_deps_lists() {
21450 // Positive control on the empty-list arm: a caixa without
21451 // any `:deps` or `:deps-dev` entries (the default
21452 // `Vec::new()` `#[serde(default)]` folds an omitted slot
21453 // onto) passes the compound gate cleanly regardless of
21454 // `:nome` — both the per-entry walk and the self-edge walk
21455 // are vacuous on the empty entry list. Pins the identity
21456 // element of the fold on the empty-slot side, peer with the
21457 // `validate_upgrade_from_accepts_empty_upgrade_from` empty-
21458 // arm positive control (d6801df) on the sibling
21459 // `:upgrade-from` compound gate.
21460 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21461 assert!(
21462 c.deps().is_empty(),
21463 "template caixa must carry an empty :deps — got {:?}",
21464 c.deps(),
21465 );
21466 assert!(
21467 c.deps_dev().is_empty(),
21468 "template caixa must carry an empty :deps-dev — got {:?}",
21469 c.deps_dev(),
21470 );
21471 c.validate_deps()
21472 .expect("empty :deps / :deps-dev must pass the compound gate cleanly");
21473 }
21474
21475 // ── Caixa::validate_aplicacao_shape — compound per-Caixa gate ────────
21476
21477 /// Build a minimal well-formed Aplicacao fixture on top of the
21478 /// canonical template. Every arm of the compound gate then patches
21479 /// exactly one axis away from clean so its per-arm diagnostic
21480 /// surfaces without collateral noise from a peer slot.
21481 fn aplicacao_fixture(nome: &str) -> Caixa {
21482 use crate::aplicacao::{Membro, Placement, PlacementStrategy};
21483 let mut c = Caixa::from_lisp(&Caixa::template(nome)).unwrap();
21484 c.kind = CaixaKind::Aplicacao;
21485 c.bibliotecas = vec![];
21486 c.membros = vec![
21487 Membro {
21488 caixa: "checkout".into(),
21489 versao: "^0.1".into(),
21490 },
21491 Membro {
21492 caixa: "cart".into(),
21493 versao: "^0.1".into(),
21494 },
21495 ];
21496 // `:placement` defaults to `Replicated` with an empty
21497 // `:clusters` list which
21498 // [`crate::AplicacaoSpec::validate_placement`] refuses; every
21499 // per-strategy variant needs at least one named cluster (per
21500 // MESH-COMPOSITION §II.1). Pin a single-cluster `SingleNode`
21501 // placement so the typed-shape cascade passes cleanly and the
21502 // per-arm fixtures below can each patch exactly one axis.
21503 c.placement = Some(Placement {
21504 estrategia: PlacementStrategy::SingleNode,
21505 clusters: vec!["rio".into()],
21506 shard_key: None,
21507 affinity: None,
21508 });
21509 c
21510 }
21511
21512 #[test]
21513 fn validate_aplicacao_shape_folds_view_arm_matches_gate() {
21514 // Fail-before-pass-after per-arm equivalence pin on the
21515 // typed-shape cascade arm: a fixture whose typed
21516 // [`crate::AplicacaoSpec`] view fails
21517 // [`crate::AplicacaoSpec::validate`] (here — empty `:membros`,
21518 // which [`crate::AplicacaoSpec::validate_membros`] rejects as
21519 // [`crate::AplicacaoError::NoMembros`] at the first per-slot
21520 // gate) surfaces the same [`crate::AplicacaoError`] diagnostic
21521 // through both the compound gate
21522 // [`Caixa::validate_aplicacao_shape`] and the standalone
21523 // [`crate::AplicacaoSpec::validate`] on the same folded view.
21524 // Pins the fold — a silent regression that de-folded the
21525 // typed-shape arm would surface here as a mismatch between the
21526 // two dispatches. Sibling in shape to the peer
21527 // `validate_deps_folds_per_entry_arm_matches_gate` (b5dd55e) /
21528 // `validate_upgrade_from_folds_per_entry_arm_matches_gate`
21529 // (d6801df) per-arm equivalence pins on the sibling per-slot
21530 // compound gates.
21531 let mut c = aplicacao_fixture("demo");
21532 c.membros = vec![];
21533 let via_method = c.validate_aplicacao_shape().unwrap_err();
21534 let via_standalone = c.aplicacao_view().unwrap().validate().unwrap_err();
21535 assert_eq!(
21536 via_method, via_standalone,
21537 "Caixa::validate_aplicacao_shape must surface the typed-\
21538 shape arm's diagnostic byte-equal to the standalone \
21539 `AplicacaoSpec::validate` on the same folded view",
21540 );
21541 assert!(
21542 matches!(via_method, crate::AplicacaoError::NoMembros),
21543 "expected NoMembros on the empty :membros, got {via_method:?}",
21544 );
21545 }
21546
21547 #[test]
21548 fn validate_aplicacao_shape_folds_self_membership_arm_matches_gate() {
21549 // Per-arm equivalence pin on the cross-slot self-edge axis: a
21550 // fixture whose `:membros` names the Aplicacao's own `:nome`
21551 // (which [`crate::aplicacao::validate_no_self_membership`]
21552 // rejects as [`crate::AplicacaoError::MembroIsSelfAplicacao`],
21553 // a one-node lacre-closure recursion in the Aplicacao's
21554 // mesh-graph) surfaces the same
21555 // [`crate::AplicacaoError::MembroIsSelfAplicacao`] through both
21556 // the compound gate and the standalone
21557 // [`crate::aplicacao::validate_no_self_membership`] keyed off
21558 // the same `(membros, nome)` pair. Pins the fold's second arm
21559 // — reaching this arm through the compound gate requires the
21560 // typed-shape cascade to pass first, which itself pins one
21561 // cross-arm ordering step. Sibling in shape to the peer
21562 // `validate_deps_folds_self_edge_arm_matches_gate` (b5dd55e)
21563 // cross-slot equivalence pin on the sibling per-slot compound
21564 // gate.
21565 use crate::aplicacao::Membro;
21566 let mut c = aplicacao_fixture("demo");
21567 c.membros = vec![Membro {
21568 caixa: "demo".into(),
21569 versao: "^0.1".into(),
21570 }];
21571 let via_method = c.validate_aplicacao_shape().unwrap_err();
21572 let via_standalone =
21573 crate::aplicacao::validate_no_self_membership(c.membros(), c.nome()).unwrap_err();
21574 assert_eq!(
21575 via_method, via_standalone,
21576 "Caixa::validate_aplicacao_shape must surface the cross-\
21577 slot self-edge diagnostic byte-equal to the standalone \
21578 `aplicacao::validate_no_self_membership` on the same \
21579 (membros, nome) pair",
21580 );
21581 assert!(
21582 matches!(
21583 via_method,
21584 crate::AplicacaoError::MembroIsSelfAplicacao { ref caixa } if caixa == "demo"
21585 ),
21586 "expected MembroIsSelfAplicacao carrying (caixa=\"demo\"), \
21587 got {via_method:?}",
21588 );
21589 }
21590
21591 #[test]
21592 fn validate_aplicacao_shape_view_arm_fires_before_self_membership_arm() {
21593 // Cross-arm ordering pin between the two arms of the fold: a
21594 // fixture carrying BOTH a typed-shape violation (a `:contratos`
21595 // edge whose `:para` is not a declared member — rejected by
21596 // [`crate::AplicacaoSpec::validate_contratos`] as
21597 // [`crate::AplicacaoError::ContratoMemberMissing`]) AND a
21598 // would-be self-edge violation (a `:membros` entry naming the
21599 // caixa's own `:nome`) surfaces the typed-shape diagnostic
21600 // first through the compound gate. Sanity assertion: the
21601 // self-referential `:membros` entry alone under the same
21602 // parent `:nome` trips the self-edge arm on its own via the
21603 // standalone [`crate::aplicacao::validate_no_self_membership`],
21604 // so the typed-shape-first surfacing is a real ordering
21605 // property, not a case where the self-edge arm silently
21606 // accepts the fixture. Pins the pre-fold layout wire-up's
21607 // canonical dispatch order (typed-shape cascade → cross-slot
21608 // self-edge) as a property of the substrate primitive rather
21609 // than a convention of the layout call site. Sibling in shape
21610 // to `validate_deps_per_entry_arm_fires_before_self_edge_arm`
21611 // (b5dd55e) on the sibling per-slot compound gate's per-arm
21612 // ordering property.
21613 use crate::aplicacao::{Membro, WitContract};
21614 let mut c = aplicacao_fixture("demo");
21615 c.membros = vec![Membro {
21616 caixa: "demo".into(),
21617 versao: "^0.1".into(),
21618 }];
21619 c.contratos = vec![WitContract {
21620 de: "demo".into(),
21621 para: "orphan".into(),
21622 wit: "wasi:http/proxy".into(),
21623 endpoint: Some("/x".into()),
21624 subject: None,
21625 slot: None,
21626 }];
21627 let err = c.validate_aplicacao_shape().unwrap_err();
21628 assert!(
21629 matches!(
21630 err,
21631 crate::AplicacaoError::ContratoMemberMissing { ref caixa }
21632 if caixa == "orphan"
21633 ),
21634 "typed-shape arm must fire before self-edge arm — expected \
21635 ContratoMemberMissing on \"orphan\", got {err:?}",
21636 );
21637 // Sanity: the self-referential `:membros` entry alone under
21638 // the same parent `:nome` trips the self-edge arm on its own
21639 // — proves the typed-shape-first surfacing above is a real
21640 // ordering property, not a case where the self-edge arm
21641 // silently accepts the fixture.
21642 let sanity = crate::aplicacao::validate_no_self_membership(
21643 &[Membro {
21644 caixa: "demo".into(),
21645 versao: "^0.1".into(),
21646 }],
21647 "demo",
21648 )
21649 .unwrap_err();
21650 assert!(
21651 matches!(
21652 sanity,
21653 crate::AplicacaoError::MembroIsSelfAplicacao { ref caixa }
21654 if caixa == "demo"
21655 ),
21656 "sanity: the self-referential :membros entry alone must \
21657 trip the self-edge arm — got {sanity:?}",
21658 );
21659 }
21660
21661 #[test]
21662 fn validate_aplicacao_shape_accepts_non_aplicacao_kind() {
21663 // Positive control on the identity-element arm: every non-
21664 // Aplicacao kind passes the compound gate trivially — the
21665 // paired [`Caixa::aplicacao_view`] accessor returns `None`
21666 // off the Aplicacao arm (by construction, keyed on
21667 // `caixa.kind().is_aplicacao()`), so the fold short-circuits
21668 // to `Ok(())` without touching the mesh slots. Pins the
21669 // identity element on every non-Aplicacao kind — a future
21670 // refactor that made the mesh-slot cascade fire on the wrong
21671 // kind (say, on a `Servico` whose mesh slots happen to be
21672 // populated in a mis-authored manifest, which the peer
21673 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
21674 // coherence gate would refuse upstream anyway) surfaces here
21675 // as a test failure first. Peer with the
21676 // `validate_limits_accepts_none` / `validate_behavior_accepts_none`
21677 // identity-element pins on the sibling M2 `Option`-shaped
21678 // per-Caixa compound gates.
21679 for kind in [
21680 CaixaKind::Biblioteca,
21681 CaixaKind::Binario,
21682 CaixaKind::Servico,
21683 CaixaKind::Supervisor,
21684 CaixaKind::Acao,
21685 ] {
21686 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21687 c.kind = kind;
21688 assert!(
21689 c.aplicacao_view().is_none(),
21690 "aplicacao_view must return None off the Aplicacao arm \
21691 for kind {kind:?}",
21692 );
21693 c.validate_aplicacao_shape().expect(
21694 "non-Aplicacao kinds must pass the compound gate as the fold's identity element",
21695 );
21696 }
21697 }
21698
21699 #[test]
21700 fn validate_aplicacao_shape_accepts_clean_fixture() {
21701 // Positive control: a well-formed Aplicacao (two DNS-1123
21702 // members with valid semver constraints, no `:contratos` /
21703 // `:entrada` / `:placement` / `:politicas` set — every
21704 // per-slot gate accepts the vacuous / omitted arm) passes the
21705 // compound gate cleanly. A future tightening of either arm's
21706 // accepted set surfaces here as a test failure first. Mirrors
21707 // the peer `validate_deps_accepts_clean_fixture` (b5dd55e) /
21708 // `validate_upgrade_from_accepts_clean_fixture` (d6801df)
21709 // positive-control postures on the sibling per-Caixa
21710 // compound gates.
21711 let c = aplicacao_fixture("demo");
21712 c.validate_aplicacao_shape()
21713 .expect("clean Aplicacao fixture must pass the compound gate");
21714 }
21715
21716 // ── Caixa::validate_supervisor_shape — compound per-Caixa gate ───────
21717
21718 /// Build a minimal well-formed Supervisor fixture on top of the
21719 /// canonical template. Every arm of the compound gate then patches
21720 /// exactly one axis away from clean so its per-arm diagnostic
21721 /// surfaces without collateral noise from a peer slot. Peer of
21722 /// [`aplicacao_fixture`] on the sibling per-Aplicacao compound
21723 /// gate's pin family.
21724 fn supervisor_fixture(nome: &str) -> Caixa {
21725 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
21726 let mut c = Caixa::from_lisp(&Caixa::template(nome)).unwrap();
21727 c.kind = CaixaKind::Supervisor;
21728 // Supervisors don't run code — clear the biblioteca slot the
21729 // template seeds so the fold's per-arm diagnostics surface
21730 // without the peer `SupervisorOwnsCode` kind-coherence gate
21731 // firing upstream at the layout altitude.
21732 c.bibliotecas = vec![];
21733 // `:estrategia` defaults to `OneForOne` at the typed view level,
21734 // and `OneForOne` requires at least one `:children` entry — pin
21735 // a single-child `Permanent` worker so the typed-shape cascade
21736 // passes cleanly and the per-arm fixtures below can each patch
21737 // exactly one axis.
21738 c.estrategia = Some(RestartStrategy::OneForOne);
21739 c.children = vec![ChildSpec {
21740 caixa: "worker".into(),
21741 versao: "^0.1".into(),
21742 restart: RestartPolicy::Permanent,
21743 }];
21744 c
21745 }
21746
21747 #[test]
21748 fn validate_supervisor_shape_folds_view_arm_matches_gate() {
21749 // Fail-before-pass-after per-arm equivalence pin on the
21750 // typed-shape cascade arm: a fixture whose typed
21751 // [`crate::SupervisorSpec`] view fails
21752 // [`crate::SupervisorSpec::validate`] (here — a duplicate
21753 // `:children` `:caixa` entry, which
21754 // [`crate::SupervisorSpec::validate`]'s set-not-multiset gate
21755 // rejects as [`crate::SupervisorError::DuplicateChildCaixa`])
21756 // surfaces the same [`crate::SupervisorError`] diagnostic
21757 // through both the compound gate
21758 // [`Caixa::validate_supervisor_shape`] and the standalone
21759 // [`crate::SupervisorSpec::validate`] on the same folded view.
21760 // Pins the fold — a silent regression that de-folded the
21761 // typed-shape arm would surface here as a mismatch between the
21762 // two dispatches. Sibling in shape to the peer
21763 // `validate_aplicacao_shape_folds_view_arm_matches_gate`
21764 // (949a7a0) on the sibling per-Aplicacao compound gate.
21765 use crate::supervisor::{ChildSpec, RestartPolicy};
21766 let mut c = supervisor_fixture("demo");
21767 c.children = vec![
21768 ChildSpec {
21769 caixa: "worker".into(),
21770 versao: "^0.1".into(),
21771 restart: RestartPolicy::Permanent,
21772 },
21773 ChildSpec {
21774 caixa: "worker".into(),
21775 versao: "^0.1".into(),
21776 restart: RestartPolicy::Permanent,
21777 },
21778 ];
21779 let via_method = c.validate_supervisor_shape().unwrap_err();
21780 let via_standalone = c.supervisor_view().unwrap().validate().unwrap_err();
21781 assert_eq!(
21782 via_method, via_standalone,
21783 "Caixa::validate_supervisor_shape must surface the typed-\
21784 shape arm's diagnostic byte-equal to the standalone \
21785 `SupervisorSpec::validate` on the same folded view",
21786 );
21787 assert!(
21788 matches!(
21789 via_method,
21790 crate::SupervisorError::DuplicateChildCaixa { ref caixa }
21791 if caixa == "worker"
21792 ),
21793 "expected DuplicateChildCaixa on the duplicate 'worker' \
21794 child, got {via_method:?}",
21795 );
21796 }
21797
21798 #[test]
21799 fn validate_supervisor_shape_folds_self_supervision_arm_matches_gate() {
21800 // Per-arm equivalence pin on the cross-slot self-edge axis: a
21801 // fixture whose `:children :caixa` names the Supervisor's own
21802 // `:nome` (which
21803 // [`crate::supervisor::validate_no_self_supervision`] rejects
21804 // as [`crate::SupervisorError::ChildSupervisesSelf`], a
21805 // one-node reconciliation cycle in the supervisor's
21806 // supervision-tree) surfaces the same
21807 // [`crate::SupervisorError::ChildSupervisesSelf`] through both
21808 // the compound gate and the standalone
21809 // [`crate::supervisor::validate_no_self_supervision`] keyed
21810 // off the same `(children, nome)` pair. Pins the fold's
21811 // second arm — reaching this arm through the compound gate
21812 // requires the typed-shape cascade to pass first, which itself
21813 // pins one cross-arm ordering step. Sibling in shape to the
21814 // peer
21815 // `validate_aplicacao_shape_folds_self_membership_arm_matches_gate`
21816 // (949a7a0) cross-slot equivalence pin on the sibling
21817 // per-Aplicacao compound gate.
21818 use crate::supervisor::{ChildSpec, RestartPolicy};
21819 let mut c = supervisor_fixture("demo");
21820 c.children = vec![ChildSpec {
21821 caixa: "demo".into(),
21822 versao: "^0.1".into(),
21823 restart: RestartPolicy::Permanent,
21824 }];
21825 let via_method = c.validate_supervisor_shape().unwrap_err();
21826 let via_standalone =
21827 crate::supervisor::validate_no_self_supervision(c.children(), c.nome()).unwrap_err();
21828 assert_eq!(
21829 via_method, via_standalone,
21830 "Caixa::validate_supervisor_shape must surface the cross-\
21831 slot self-edge diagnostic byte-equal to the standalone \
21832 `supervisor::validate_no_self_supervision` on the same \
21833 (children, nome) pair",
21834 );
21835 assert!(
21836 matches!(
21837 via_method,
21838 crate::SupervisorError::ChildSupervisesSelf { ref caixa } if caixa == "demo"
21839 ),
21840 "expected ChildSupervisesSelf carrying (caixa=\"demo\"), \
21841 got {via_method:?}",
21842 );
21843 }
21844
21845 #[test]
21846 fn validate_supervisor_shape_view_arm_fires_before_self_supervision_arm() {
21847 // Cross-arm ordering pin between the two arms of the fold: a
21848 // fixture carrying BOTH a typed-shape violation (a per-child
21849 // empty `:caixa` name — rejected by
21850 // [`crate::SupervisorSpec::validate`] as
21851 // [`crate::SupervisorError::EmptyChildName`]) AND a would-be
21852 // self-edge violation (a `:children` entry naming the
21853 // supervisor's own `:nome`) surfaces the typed-shape
21854 // diagnostic first through the compound gate. Sanity
21855 // assertion: the self-referential `:children` entry alone
21856 // under the same parent `:nome` trips the self-edge arm on
21857 // its own via the standalone
21858 // [`crate::supervisor::validate_no_self_supervision`], so the
21859 // typed-shape-first surfacing is a real ordering property, not
21860 // a case where the self-edge arm silently accepts the fixture.
21861 // Pins the pre-fold layout wire-up's canonical dispatch order
21862 // (typed-shape cascade → cross-slot self-edge) as a property
21863 // of the substrate primitive rather than a convention of the
21864 // layout call site. Sibling in shape to
21865 // `validate_aplicacao_shape_view_arm_fires_before_self_membership_arm`
21866 // (949a7a0) on the sibling per-Aplicacao compound gate.
21867 use crate::supervisor::{ChildSpec, RestartPolicy};
21868 let mut c = supervisor_fixture("demo");
21869 c.children = vec![
21870 ChildSpec {
21871 caixa: String::new(),
21872 versao: "^0.1".into(),
21873 restart: RestartPolicy::Permanent,
21874 },
21875 ChildSpec {
21876 caixa: "demo".into(),
21877 versao: "^0.1".into(),
21878 restart: RestartPolicy::Permanent,
21879 },
21880 ];
21881 let err = c.validate_supervisor_shape().unwrap_err();
21882 assert!(
21883 matches!(err, crate::SupervisorError::EmptyChildName),
21884 "typed-shape arm must fire before self-edge arm — expected \
21885 EmptyChildName on the empty :caixa child, got {err:?}",
21886 );
21887 // Sanity: the self-referential `:children` entry alone under
21888 // the same parent `:nome` trips the self-edge arm on its own
21889 // — proves the typed-shape-first surfacing above is a real
21890 // ordering property, not a case where the self-edge arm
21891 // silently accepts the fixture.
21892 let sanity = crate::supervisor::validate_no_self_supervision(
21893 &[ChildSpec {
21894 caixa: "demo".into(),
21895 versao: "^0.1".into(),
21896 restart: RestartPolicy::Permanent,
21897 }],
21898 "demo",
21899 )
21900 .unwrap_err();
21901 assert!(
21902 matches!(
21903 sanity,
21904 crate::SupervisorError::ChildSupervisesSelf { ref caixa } if caixa == "demo"
21905 ),
21906 "sanity: the self-referential :children entry alone must \
21907 trip the self-edge arm — got {sanity:?}",
21908 );
21909 }
21910
21911 #[test]
21912 fn validate_supervisor_shape_accepts_non_supervisor_kind() {
21913 // Positive control on the identity-element arm: every non-
21914 // Supervisor kind passes the compound gate trivially — the
21915 // paired [`Caixa::supervisor_view`] accessor returns `None`
21916 // off the Supervisor arm (by construction, keyed on
21917 // `caixa.kind().is_supervisor()`), so the fold short-circuits
21918 // to `Ok(())` without touching the supervision-tree slots.
21919 // Pins the identity element on every non-Supervisor kind — a
21920 // future refactor that made the supervision-tree cascade fire
21921 // on the wrong kind (say, on a `Servico` whose supervision
21922 // slots happen to be populated in a mis-authored manifest,
21923 // which the peer
21924 // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
21925 // kind-coherence gate would refuse upstream anyway) surfaces
21926 // here as a test failure first. Peer with the
21927 // `validate_aplicacao_shape_accepts_non_aplicacao_kind`
21928 // (949a7a0) / `validate_limits_accepts_none` /
21929 // `validate_behavior_accepts_none` identity-element pins on
21930 // the sibling per-Caixa compound gates.
21931 for kind in [
21932 CaixaKind::Biblioteca,
21933 CaixaKind::Binario,
21934 CaixaKind::Servico,
21935 CaixaKind::Aplicacao,
21936 CaixaKind::Acao,
21937 ] {
21938 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
21939 c.kind = kind;
21940 assert!(
21941 c.supervisor_view().is_none(),
21942 "supervisor_view must return None off the Supervisor \
21943 arm for kind {kind:?}",
21944 );
21945 c.validate_supervisor_shape().expect(
21946 "non-Supervisor kinds must pass the compound gate as the fold's identity element",
21947 );
21948 }
21949 }
21950
21951 #[test]
21952 fn validate_supervisor_shape_accepts_clean_fixture() {
21953 // Positive control: a well-formed Supervisor (single
21954 // DNS-1123-valid `Permanent` worker child under the
21955 // `OneForOne` strategy — the OTP MaxIntensity/Period defaults
21956 // accept the vacuous `:max-restarts` / `:restart-window`
21957 // arms) passes the compound gate cleanly. A future tightening
21958 // of either arm's accepted set surfaces here as a test
21959 // failure first. Mirrors the peer
21960 // `validate_aplicacao_shape_accepts_clean_fixture` (949a7a0)
21961 // positive-control posture on the sibling per-Caixa compound
21962 // gate.
21963 let c = supervisor_fixture("demo");
21964 c.validate_supervisor_shape()
21965 .expect("clean Supervisor fixture must pass the compound gate");
21966 }
21967
21968 // ── Caixa::validate_acao_shape — compound per-Caixa gate ─────────────
21969
21970 /// Build a minimal well-formed `:kind Acao` fixture with a valid
21971 /// two-node acyclic `:ci` slot. Every arm of the compound gate
21972 /// then patches exactly one axis away from clean so its per-arm
21973 /// diagnostic surfaces without collateral noise from a peer slot.
21974 /// Peer of [`supervisor_fixture`] / [`aplicacao_fixture`] on the
21975 /// sibling per-kind compound gates' pin families.
21976 fn acao_fixture(nome: &str) -> Caixa {
21977 let mut c = Caixa::from_lisp(&Caixa::template(nome)).unwrap();
21978 c.kind = CaixaKind::Acao;
21979 // Acaos don't run code — clear the biblioteca slot the template
21980 // seeds so the compound gate's per-arm diagnostics surface
21981 // without the peer `AcaoOwnsCode` kind-coherence gate firing
21982 // upstream at the layout altitude.
21983 c.bibliotecas = vec![];
21984 c.ci = Some(canteiro_types::CiRun {
21985 workspace: "pleme-io".into(),
21986 repo: "caixa".into(),
21987 nodes: vec![
21988 canteiro_types::CiNode::new(
21989 "build",
21990 canteiro_types::EnvClass::None,
21991 canteiro_types::ActionRef {
21992 name: "build".into(),
21993 command: "true".into(),
21994 args: vec![],
21995 },
21996 vec![],
21997 ),
21998 canteiro_types::CiNode::new(
21999 "test",
22000 canteiro_types::EnvClass::None,
22001 canteiro_types::ActionRef {
22002 name: "test".into(),
22003 command: "true".into(),
22004 args: vec![],
22005 },
22006 vec!["build".into()],
22007 ),
22008 ],
22009 });
22010 c
22011 }
22012
22013 #[test]
22014 fn validate_acao_shape_folds_decompose_arm_matches_gate() {
22015 // Fail-before-pass-after per-arm equivalence pin on the
22016 // decompose axis: a fixture whose `:ci` slot fails
22017 // [`canteiro_types::decompose`] (here — a minimal two-node
22018 // cycle `a → b → a`, which the sibling
22019 // [`crate::render::decompose_ci`] wraps as
22020 // [`crate::CiDecomposeFailure`] carrying
22021 // [`canteiro_types::DecomposeError::Cycle`]) surfaces the same
22022 // [`crate::CiDecomposeFailure`] diagnostic through both the
22023 // compound gate [`Caixa::validate_acao_shape`] and the
22024 // standalone [`crate::render::decompose_ci`] on the same
22025 // `(caixa, ci)` fixture. Pins the fold — a silent regression
22026 // that de-folded the decompose arm would surface here as a
22027 // mismatch between the two dispatches. Sibling in shape to the
22028 // peer `validate_supervisor_shape_folds_view_arm_matches_gate`
22029 // / `validate_aplicacao_shape_folds_view_arm_matches_gate` on
22030 // the sibling per-kind compound gates.
22031 //
22032 // [`crate::CiDecomposeFailure`] does not derive `PartialEq`
22033 // (its `#[source]` carrier [`canteiro_types::DecomposeError`]
22034 // does, but the wrapper deliberately does not), so the two
22035 // dispatches are compared through their field pair
22036 // (`nome` + `source`) rather than through `assert_eq!` on the
22037 // wrapper itself — every field on the wrapper is thereby
22038 // pinned byte-equal without depending on an implementation
22039 // detail of `CiDecomposeFailure`'s derive set.
22040 let mut c = acao_fixture("demo");
22041 c.ci = Some(canteiro_types::CiRun {
22042 workspace: "pleme-io".into(),
22043 repo: "caixa".into(),
22044 nodes: vec![
22045 canteiro_types::CiNode::new(
22046 "a",
22047 canteiro_types::EnvClass::None,
22048 canteiro_types::ActionRef {
22049 name: "a".into(),
22050 command: "true".into(),
22051 args: vec![],
22052 },
22053 vec!["b".into()],
22054 ),
22055 canteiro_types::CiNode::new(
22056 "b",
22057 canteiro_types::EnvClass::None,
22058 canteiro_types::ActionRef {
22059 name: "b".into(),
22060 command: "true".into(),
22061 args: vec![],
22062 },
22063 vec!["a".into()],
22064 ),
22065 ],
22066 });
22067 let via_method = c.validate_acao_shape().unwrap_err();
22068 let via_standalone =
22069 crate::render::decompose_ci(&c, c.ci().expect("fixture has a :ci")).unwrap_err();
22070 assert_eq!(
22071 via_method.nome, via_standalone.nome,
22072 "Caixa::validate_acao_shape must surface the decompose \
22073 failure's `nome` byte-equal to the standalone \
22074 `decompose_ci` on the same (caixa, ci) fixture",
22075 );
22076 assert_eq!(
22077 via_method.source, via_standalone.source,
22078 "Caixa::validate_acao_shape must surface the decompose \
22079 failure's `source` byte-equal to the standalone \
22080 `decompose_ci` on the same (caixa, ci) fixture",
22081 );
22082 assert_eq!(
22083 via_method.source,
22084 canteiro_types::DecomposeError::Cycle,
22085 "expected the two-node cycle `a → b → a` to surface as \
22086 DecomposeError::Cycle, got {source:?}",
22087 source = via_method.source,
22088 );
22089 }
22090
22091 #[test]
22092 fn validate_acao_shape_folds_duplicate_node_arm_matches_gate() {
22093 // Per-arm equivalence pin on the `DuplicateNode` decompose
22094 // arm — the sibling of `Cycle` on the substrate's
22095 // `canteiro_types::DecomposeError` enumeration. A fixture
22096 // whose `:ci` slot carries two nodes sharing one name
22097 // surfaces the same [`crate::CiDecomposeFailure`] through
22098 // both dispatches, pinned by field pair. The three
22099 // decompose arms (`DuplicateNode` / `UnknownDep` / `Cycle`)
22100 // together enumerate every failure mode
22101 // [`canteiro_types::decompose`] refuses, so the per-arm
22102 // pins collectively cover the whole decompose axis.
22103 let mut c = acao_fixture("demo");
22104 c.ci = Some(canteiro_types::CiRun {
22105 workspace: "pleme-io".into(),
22106 repo: "caixa".into(),
22107 nodes: vec![
22108 canteiro_types::CiNode::new(
22109 "twin",
22110 canteiro_types::EnvClass::None,
22111 canteiro_types::ActionRef {
22112 name: "twin".into(),
22113 command: "true".into(),
22114 args: vec![],
22115 },
22116 vec![],
22117 ),
22118 canteiro_types::CiNode::new(
22119 "twin",
22120 canteiro_types::EnvClass::None,
22121 canteiro_types::ActionRef {
22122 name: "twin".into(),
22123 command: "true".into(),
22124 args: vec![],
22125 },
22126 vec![],
22127 ),
22128 ],
22129 });
22130 let via_method = c.validate_acao_shape().unwrap_err();
22131 assert_eq!(
22132 via_method.source,
22133 canteiro_types::DecomposeError::DuplicateNode("twin".into()),
22134 "expected DuplicateNode on the two-\"twin\"-name fixture, \
22135 got {source:?}",
22136 source = via_method.source,
22137 );
22138 }
22139
22140 #[test]
22141 fn validate_acao_shape_folds_unknown_dep_arm_matches_gate() {
22142 // Per-arm equivalence pin on the `UnknownDep` decompose arm —
22143 // the third and last arm on `canteiro_types::DecomposeError`
22144 // after `Cycle` and `DuplicateNode`. A fixture whose `:ci`
22145 // slot names a `deps` entry no declared node satisfies
22146 // surfaces the same [`crate::CiDecomposeFailure`] through
22147 // both dispatches. Pins the third decompose arm at the
22148 // compound gate.
22149 let mut c = acao_fixture("demo");
22150 c.ci = Some(canteiro_types::CiRun {
22151 workspace: "pleme-io".into(),
22152 repo: "caixa".into(),
22153 nodes: vec![canteiro_types::CiNode::new(
22154 "orphan",
22155 canteiro_types::EnvClass::None,
22156 canteiro_types::ActionRef {
22157 name: "orphan".into(),
22158 command: "true".into(),
22159 args: vec![],
22160 },
22161 vec!["ghost".into()],
22162 )],
22163 });
22164 let via_method = c.validate_acao_shape().unwrap_err();
22165 assert_eq!(
22166 via_method.source,
22167 canteiro_types::DecomposeError::UnknownDep {
22168 node: "orphan".into(),
22169 dep: "ghost".into(),
22170 },
22171 "expected UnknownDep on the orphan-node-depends-on-ghost \
22172 fixture, got {source:?}",
22173 source = via_method.source,
22174 );
22175 }
22176
22177 #[test]
22178 fn validate_acao_shape_accepts_non_acao_kind() {
22179 // Positive control on the identity-element arm: every non-
22180 // Acao kind passes the compound gate trivially — the paired
22181 // `caixa.kind().is_acao()` guard short-circuits before the
22182 // decompose gate ever fires, so the fold returns `Ok(())`
22183 // without touching the `:ci` slot even when a non-Acao
22184 // fixture happens to declare one (the sibling
22185 // [`crate::LayoutError::CiOnNonAcao`] kind-coherence gate
22186 // catches that at the layout altitude anyway). Pins the
22187 // identity element on every non-Acao kind. Peer with the
22188 // `validate_supervisor_shape_accepts_non_supervisor_kind` /
22189 // `validate_aplicacao_shape_accepts_non_aplicacao_kind`
22190 // identity-element pins on the sibling per-Caixa compound
22191 // gates.
22192 for kind in [
22193 CaixaKind::Biblioteca,
22194 CaixaKind::Binario,
22195 CaixaKind::Servico,
22196 CaixaKind::Supervisor,
22197 CaixaKind::Aplicacao,
22198 ] {
22199 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22200 c.kind = kind;
22201 c.validate_acao_shape().expect(
22202 "non-Acao kinds must pass the compound gate as the fold's identity element",
22203 );
22204 }
22205 }
22206
22207 #[test]
22208 fn validate_acao_shape_accepts_absent_ci_slot() {
22209 // Positive control on the second identity-element arm: a
22210 // `:kind Acao` caixa with `ci = None` passes the compound
22211 // gate trivially — the presence gate is the sibling axis
22212 // owned by [`crate::LayoutError::MissingCi`] /
22213 // [`crate::require_ci`] / [`crate::MissingCiSlot`], not by
22214 // the decompose gate. A caixa that carries no `:ci` slot
22215 // has no run to decompose, so the fold's `let Some(ci) = …
22216 // else { return Ok(()) }` arm short-circuits before the
22217 // decompose gate fires. Pins that the two axes stay
22218 // separately diagnosable at the layout altitude — a future
22219 // regression that collapsed the presence gate onto the
22220 // shape gate here would land a
22221 // [`crate::CiDecomposeFailure`] on the wrong axis and
22222 // surface an off-target diagnostic at `feira build` time.
22223 let mut c = acao_fixture("demo");
22224 c.ci = None;
22225 c.validate_acao_shape().expect(
22226 "an :kind Acao caixa with absent :ci must pass the compound gate — \
22227 the presence gate is layout's MissingCi axis, not the decompose gate",
22228 );
22229 }
22230
22231 #[test]
22232 fn validate_acao_shape_accepts_clean_fixture() {
22233 // Positive control: a well-formed Acao (a two-node acyclic
22234 // `:ci` run with `test` depending on `build`) passes the
22235 // compound gate cleanly. A future tightening of the
22236 // decompose gate's accepted set surfaces here as a test
22237 // failure first. Mirrors the peer
22238 // `validate_supervisor_shape_accepts_clean_fixture` /
22239 // `validate_aplicacao_shape_accepts_clean_fixture`
22240 // positive-control posture on the sibling per-Caixa
22241 // compound gates.
22242 let c = acao_fixture("demo");
22243 c.validate_acao_shape()
22244 .expect("clean Acao fixture must pass the compound gate");
22245 }
22246
22247 fn bare_servico_fixture(nome: &str) -> Caixa {
22248 // A minimal Servico caixa with no code and no typed slots —
22249 // the cross-family fold's identity element on every arm.
22250 // Clears the biblioteca slot the template seeds so the
22251 // per-arm patches below can each add exactly one typed slot
22252 // without a peer `ServicoOwnsCode` / layout-side kind-gate
22253 // firing upstream.
22254 let mut c = Caixa::from_lisp(&Caixa::template(nome)).unwrap();
22255 c.kind = CaixaKind::Servico;
22256 c.bibliotecas = vec![];
22257 c.servicos = vec!["servicos/demo.computeunit.yaml".into()];
22258 c
22259 }
22260
22261 #[test]
22262 fn validate_kind_slot_coherence_folds_mesh_arm_matches_gate() {
22263 // Fail-before-pass-after per-arm equivalence pin on the M3
22264 // mesh-slot arm of the cross-family kind-coherence fold: a
22265 // non-Aplicacao caixa carrying a declared M3 mesh slot (here
22266 // a `:kind Servico` fixture with a single `:membros` entry —
22267 // the smallest possible M3 slot declaration on a foreign
22268 // kind) surfaces the same
22269 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] variant
22270 // through both the compound gate
22271 // [`Caixa::validate_kind_slot_coherence`] and the standalone
22272 // constructor [`crate::LayoutError::mesh_slots_on_non_aplicacao`]
22273 // dispatched on the same `declared_mesh_slots` list. Pins
22274 // the fold — a silent regression that de-folded the mesh
22275 // arm would surface here as a mismatch between the two
22276 // dispatches. Sibling in shape to the peer
22277 // `validate_aplicacao_shape_folds_view_arm_matches_gate` /
22278 // `validate_supervisor_shape_folds_view_arm_matches_gate` /
22279 // `validate_acao_shape_folds_decompose_arm_matches_gate`
22280 // per-arm equivalence pins on the sibling per-kind compound
22281 // gates.
22282 use crate::aplicacao::Membro;
22283 let mut c = bare_servico_fixture("demo");
22284 c.membros = vec![Membro {
22285 caixa: "cart".into(),
22286 versao: "^0.1".into(),
22287 }];
22288 let via_method = c.validate_kind_slot_coherence().unwrap_err();
22289 let via_standalone =
22290 crate::LayoutError::mesh_slots_on_non_aplicacao(&c, c.declared_mesh_slots());
22291 assert_eq!(
22292 via_method, via_standalone,
22293 "Caixa::validate_kind_slot_coherence must surface the M3 \
22294 mesh-slot arm's diagnostic byte-equal to the standalone \
22295 LayoutError::mesh_slots_on_non_aplicacao ctor on the same \
22296 declared_mesh_slots list",
22297 );
22298 }
22299
22300 #[test]
22301 fn validate_kind_slot_coherence_folds_supervisor_arm_matches_gate() {
22302 // Per-arm equivalence pin on the supervisor-tree arm — the
22303 // sibling of the mesh arm on the cross-family fold. A
22304 // non-Supervisor caixa carrying a declared supervisor slot
22305 // (a `:kind Servico` fixture with `:estrategia` set — the
22306 // smallest possible supervisor slot declaration on a
22307 // foreign kind) surfaces the same
22308 // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
22309 // variant through both dispatches, pinned by field pair
22310 // through `PartialEq`.
22311 use crate::supervisor::RestartStrategy;
22312 let mut c = bare_servico_fixture("demo");
22313 c.estrategia = Some(RestartStrategy::OneForOne);
22314 let via_method = c.validate_kind_slot_coherence().unwrap_err();
22315 let via_standalone = crate::LayoutError::supervisor_slots_on_non_supervisor(
22316 &c,
22317 c.declared_supervisor_slots(),
22318 );
22319 assert_eq!(
22320 via_method, via_standalone,
22321 "Caixa::validate_kind_slot_coherence must surface the \
22322 supervisor-tree arm's diagnostic byte-equal to the \
22323 standalone LayoutError::supervisor_slots_on_non_supervisor \
22324 ctor on the same declared_supervisor_slots list",
22325 );
22326 }
22327
22328 #[test]
22329 fn validate_kind_slot_coherence_folds_servico_arm_matches_gate() {
22330 // Per-arm equivalence pin on the M2 Servico-runtime arm —
22331 // the third and last arm on the cross-family fold. A
22332 // non-Servico caixa carrying a declared M2 slot (a `:kind
22333 // Biblioteca` fixture with `:limits` set — the smallest
22334 // possible M2 slot declaration on a foreign kind) surfaces
22335 // the same [`crate::LayoutError::ServicoSlotsOnNonServico`]
22336 // variant through both dispatches. The three arms together
22337 // enumerate every typed-slot family the substrate carries
22338 // whose "declared but ignored" footgun is gated at the
22339 // layout altitude by a `{ caixa, kind, slots }` wrap variant,
22340 // so the per-arm pins collectively cover the whole
22341 // cross-family kind-coherence axis.
22342 use crate::limits::LimitsSpec;
22343 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22344 c.kind = CaixaKind::Biblioteca;
22345 c.limits = Some(LimitsSpec {
22346 memory: Some(64 * 1024 * 1024),
22347 fuel: None,
22348 wall_clock: None,
22349 cpu: None,
22350 });
22351 let via_method = c.validate_kind_slot_coherence().unwrap_err();
22352 let via_standalone =
22353 crate::LayoutError::servico_slots_on_non_servico(&c, c.declared_servico_slots());
22354 assert_eq!(
22355 via_method, via_standalone,
22356 "Caixa::validate_kind_slot_coherence must surface the M2 \
22357 Servico-runtime arm's diagnostic byte-equal to the \
22358 standalone LayoutError::servico_slots_on_non_servico ctor \
22359 on the same declared_servico_slots list",
22360 );
22361 }
22362
22363 #[test]
22364 fn validate_kind_slot_coherence_mesh_arm_fires_before_supervisor_arm() {
22365 // Cross-arm ordering pin between the first two arms of the
22366 // fold: a fixture carrying BOTH a declared M3 mesh slot
22367 // (`:membros`) AND a declared supervisor-tree slot
22368 // (`:estrategia`) on a foreign kind (a `:kind Servico` here —
22369 // foreign to both the Aplicacao arm and the Supervisor arm)
22370 // surfaces the M3 mesh diagnostic first through the compound
22371 // gate. Pins the pre-fold layout wire-up's canonical
22372 // diagnostic sequence (mesh → supervisor → servico) as a
22373 // property of the substrate primitive rather than a
22374 // convention of the layout call site. A silent reordering
22375 // regression at the primitive would surface here as a
22376 // wrong-variant match before landing at a downstream
22377 // consumer's diagnostic-ordering expectation.
22378 use crate::aplicacao::Membro;
22379 use crate::supervisor::RestartStrategy;
22380 let mut c = bare_servico_fixture("demo");
22381 c.membros = vec![Membro {
22382 caixa: "cart".into(),
22383 versao: "^0.1".into(),
22384 }];
22385 c.estrategia = Some(RestartStrategy::OneForOne);
22386 let err = c.validate_kind_slot_coherence().unwrap_err();
22387 assert!(
22388 matches!(err, crate::LayoutError::MeshSlotsOnNonAplicacao { .. }),
22389 "expected MeshSlotsOnNonAplicacao to fire before \
22390 SupervisorSlotsOnNonSupervisor under the canonical \
22391 mesh → supervisor → servico order, got {err:?}",
22392 );
22393 }
22394
22395 #[test]
22396 fn validate_kind_slot_coherence_supervisor_arm_fires_before_servico_arm() {
22397 // Cross-arm ordering pin between the second and third arms
22398 // of the fold: a fixture carrying BOTH a declared
22399 // supervisor-tree slot (`:estrategia`) AND a declared M2 slot
22400 // (`:limits`) on a kind foreign to both (a `:kind Biblioteca`
22401 // here — foreign to both the Supervisor and the Servico
22402 // arms) surfaces the supervisor-tree diagnostic first
22403 // through the compound gate. Together with the peer
22404 // `_mesh_arm_fires_before_supervisor_arm` pin above this
22405 // pins the whole three-arm canonical order (mesh →
22406 // supervisor → servico) at the substrate primitive.
22407 use crate::limits::LimitsSpec;
22408 use crate::supervisor::RestartStrategy;
22409 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22410 c.kind = CaixaKind::Biblioteca;
22411 c.estrategia = Some(RestartStrategy::OneForOne);
22412 c.limits = Some(LimitsSpec {
22413 memory: Some(64 * 1024 * 1024),
22414 fuel: None,
22415 wall_clock: None,
22416 cpu: None,
22417 });
22418 let err = c.validate_kind_slot_coherence().unwrap_err();
22419 assert!(
22420 matches!(
22421 err,
22422 crate::LayoutError::SupervisorSlotsOnNonSupervisor { .. }
22423 ),
22424 "expected SupervisorSlotsOnNonSupervisor to fire before \
22425 ServicoSlotsOnNonServico under the canonical mesh → \
22426 supervisor → servico order, got {err:?}",
22427 );
22428 }
22429
22430 #[test]
22431 fn validate_kind_slot_coherence_accepts_owner_kind_on_every_arm() {
22432 // Positive control on the identity-element arm: the owner
22433 // kind of each typed-slot family passes the compound gate
22434 // even when it declares the full slot set that family owns.
22435 // Aplicacao with `:membros` populated passes the mesh arm;
22436 // Supervisor with `:estrategia` populated passes the
22437 // supervisor arm; Servico with `:limits` populated passes
22438 // the servico arm. Pins the fold's identity element on
22439 // every owner kind — a silent regression that dropped the
22440 // paired `!kind().is_<owner>()` short-circuit guard would
22441 // surface here as a false-positive rejection of every
22442 // native-slot declaration. Peer with the
22443 // `validate_<kind>_shape_accepts_non_<kind>_kind` identity-
22444 // element pins on the sibling per-Caixa compound gates.
22445 use crate::aplicacao::{Membro, Placement, PlacementStrategy};
22446 use crate::limits::LimitsSpec;
22447 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
22448
22449 let mut apli = Caixa::from_lisp(&Caixa::template("app")).unwrap();
22450 apli.kind = CaixaKind::Aplicacao;
22451 apli.bibliotecas = vec![];
22452 apli.membros = vec![Membro {
22453 caixa: "cart".into(),
22454 versao: "^0.1".into(),
22455 }];
22456 apli.placement = Some(Placement {
22457 estrategia: PlacementStrategy::SingleNode,
22458 clusters: vec!["rio".into()],
22459 shard_key: None,
22460 affinity: None,
22461 });
22462 apli.validate_kind_slot_coherence().expect(
22463 "an :kind Aplicacao caixa with declared M3 mesh slots must \
22464 pass the compound gate — Aplicacao is the mesh-slot family's \
22465 owner kind and the fold's identity element on that arm",
22466 );
22467
22468 let mut sup = Caixa::from_lisp(&Caixa::template("sup")).unwrap();
22469 sup.kind = CaixaKind::Supervisor;
22470 sup.bibliotecas = vec![];
22471 sup.estrategia = Some(RestartStrategy::OneForOne);
22472 sup.children = vec![ChildSpec {
22473 caixa: "worker".into(),
22474 versao: "^0.1".into(),
22475 restart: RestartPolicy::Permanent,
22476 }];
22477 sup.validate_kind_slot_coherence().expect(
22478 "an :kind Supervisor caixa with declared supervisor-tree slots \
22479 must pass the compound gate — Supervisor is the \
22480 supervisor-slot family's owner kind and the fold's identity \
22481 element on that arm",
22482 );
22483
22484 let mut svc = bare_servico_fixture("svc");
22485 svc.limits = Some(LimitsSpec {
22486 memory: Some(64 * 1024 * 1024),
22487 fuel: None,
22488 wall_clock: None,
22489 cpu: None,
22490 });
22491 svc.validate_kind_slot_coherence().expect(
22492 "an :kind Servico caixa with declared M2 slots must pass the \
22493 compound gate — Servico is the M2-slot family's owner kind \
22494 and the fold's identity element on that arm",
22495 );
22496 }
22497
22498 #[test]
22499 fn validate_kind_slot_coherence_accepts_bare_caixa_on_every_kind() {
22500 // Positive control on the second identity-element arm: a
22501 // bare caixa (no declared typed slots) passes the compound
22502 // gate on every kind. Pins the fold's identity element on
22503 // the empty-slot axis — the paired `Vec::is_empty` short-
22504 // circuit guard fires before the wrap dispatch on all three
22505 // arms, so a bare caixa of any kind surfaces no diagnostic.
22506 // A silent regression that dropped the emptiness guard
22507 // would surface here as a false-positive rejection of every
22508 // no-slot caixa across the whole kind axis.
22509 for kind in CaixaKind::ALL {
22510 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22511 c.kind = *kind;
22512 c.bibliotecas = vec![];
22513 c.validate_kind_slot_coherence().unwrap_or_else(|err| {
22514 panic!(
22515 "a bare :kind {kind:?} caixa (no declared typed slots) \
22516 must pass the compound gate — the fold's identity \
22517 element on the empty-slot axis is the paired \
22518 Vec::is_empty short-circuit guard, got {err:?}",
22519 )
22520 });
22521 }
22522 }
22523
22524 #[test]
22525 fn run_kind_owned_slot_family_gate_owner_kind_short_circuits_before_accumulator() {
22526 // Fail-before-pass-after identity-element pin on the owner-kind
22527 // arm of the substrate primitive: on a caixa whose kind IS the
22528 // owner of the family named by `is_owner`, the primitive
22529 // short-circuits before dispatching `accumulator` — pinned here
22530 // by a poison-pill accumulator that panics on call. If a
22531 // regression drops the `is_owner` short-circuit and always
22532 // invokes the accumulator, the poison panic surfaces here
22533 // rather than a spurious pass. Byte-equal to the pre-lift
22534 // `if !self.kind().is_<owner>() { … }` outer guard's
22535 // short-circuit at the pre-fold layout call site.
22536 let c = bare_servico_fixture("demo");
22537 c.run_kind_owned_slot_family_gate(
22538 CaixaKind::is_servico,
22539 |_| panic!("run_kind_owned_slot_family_gate must short-circuit before invoking accumulator on the owner kind"),
22540 |_, _| panic!("run_kind_owned_slot_family_gate must short-circuit before invoking wrap on the owner kind"),
22541 )
22542 .expect(
22543 "the owner kind of a slot family must pass the substrate \
22544 primitive as the fold's identity element on the outer \
22545 is_owner guard, without invoking accumulator or wrap",
22546 );
22547 }
22548
22549 #[test]
22550 fn run_kind_owned_slot_family_gate_empty_accumulator_short_circuits_before_wrap() {
22551 // Fail-before-pass-after identity-element pin on the empty-
22552 // accumulator arm: on a non-owner kind whose per-family
22553 // accumulator yields no declared slot, the primitive short-
22554 // circuits before dispatching `wrap` — pinned here by a
22555 // poison-pill wrap that panics on call. Byte-equal to the
22556 // pre-lift `if !<slots>.is_empty() { … }` inner emptiness
22557 // guard's short-circuit at the pre-fold layout call site.
22558 let c = bare_servico_fixture("demo");
22559 c.run_kind_owned_slot_family_gate(
22560 CaixaKind::is_aplicacao,
22561 Caixa::declared_mesh_slots,
22562 |_, _| panic!("run_kind_owned_slot_family_gate must short-circuit before invoking wrap on an empty accumulator"),
22563 )
22564 .expect(
22565 "a non-owner kind carrying no declared slot in the family \
22566 must pass the substrate primitive as the fold's identity \
22567 element on the inner emptiness guard, without invoking \
22568 wrap",
22569 );
22570 }
22571
22572 #[test]
22573 fn run_kind_owned_slot_family_gate_non_owner_non_empty_wraps_verbatim() {
22574 // Equivalence pin on the refusal arm: on a non-owner kind
22575 // whose accumulator yields a non-empty slot list, the primitive
22576 // returns the caller-supplied wrap byte-equal to the direct
22577 // ctor dispatch on the same `(caixa, slots)` pair. Pins the
22578 // three-argument route through — `is_owner` fires false, the
22579 // accumulator produces the slot list, and the wrap ctor
22580 // receives verbatim what a direct dispatch would receive.
22581 // Sibling of the peer per-arm equivalence pins on
22582 // [`Caixa::validate_kind_slot_coherence`].
22583 use crate::aplicacao::Membro;
22584 let mut c = bare_servico_fixture("demo");
22585 c.membros = vec![Membro {
22586 caixa: "cart".into(),
22587 versao: "^0.1".into(),
22588 }];
22589 let via_primitive = c
22590 .run_kind_owned_slot_family_gate(
22591 CaixaKind::is_aplicacao,
22592 Caixa::declared_mesh_slots,
22593 crate::LayoutError::mesh_slots_on_non_aplicacao,
22594 )
22595 .unwrap_err();
22596 let via_direct =
22597 crate::LayoutError::mesh_slots_on_non_aplicacao(&c, c.declared_mesh_slots());
22598 assert_eq!(
22599 via_primitive, via_direct,
22600 "Caixa::run_kind_owned_slot_family_gate must route the \
22601 non-owner-kind + non-empty-accumulator arm through the \
22602 caller-supplied wrap byte-equal to the direct ctor \
22603 dispatch on the same (caixa, slots) pair",
22604 );
22605 }
22606
22607 #[test]
22608 fn validate_kind_slot_coherence_routes_each_arm_through_run_kind_owned_slot_family_gate() {
22609 // Cross-primitive routing pin: every arm of the compound gate
22610 // [`Caixa::validate_kind_slot_coherence`] routes through the
22611 // substrate primitive [`Caixa::run_kind_owned_slot_family_gate`]
22612 // on its `(is_owner, accumulator, wrap)` triple. A silent
22613 // regression that de-folded one arm and re-inlined the four-
22614 // line block would surface here as a mismatch between the
22615 // compound-gate error and the direct-primitive-dispatch error
22616 // on the same fixture. Sibling of the peer
22617 // `probe_declared_entries_routes_miss_arm_through_probe_declared_entry`
22618 // cross-primitive routing pin on the layout-pipeline
22619 // existence-probe axis.
22620 use crate::aplicacao::Membro;
22621 use crate::limits::LimitsSpec;
22622 use crate::supervisor::RestartStrategy;
22623
22624 // Mesh arm — non-Aplicacao carrying a declared M3 slot.
22625 let mut mesh = bare_servico_fixture("demo");
22626 mesh.membros = vec![Membro {
22627 caixa: "cart".into(),
22628 versao: "^0.1".into(),
22629 }];
22630 let via_compound = mesh.validate_kind_slot_coherence().unwrap_err();
22631 let via_primitive = mesh
22632 .run_kind_owned_slot_family_gate(
22633 CaixaKind::is_aplicacao,
22634 Caixa::declared_mesh_slots,
22635 crate::LayoutError::mesh_slots_on_non_aplicacao,
22636 )
22637 .unwrap_err();
22638 assert_eq!(
22639 via_compound, via_primitive,
22640 "validate_kind_slot_coherence's mesh arm must route \
22641 byte-equal through the run_kind_owned_slot_family_gate \
22642 substrate primitive",
22643 );
22644
22645 // Supervisor arm — non-Supervisor carrying a declared
22646 // supervisor-tree slot on a kind foreign to both the Aplicacao
22647 // arm and this one.
22648 let mut sup = bare_servico_fixture("demo");
22649 sup.estrategia = Some(RestartStrategy::OneForOne);
22650 let via_compound = sup.validate_kind_slot_coherence().unwrap_err();
22651 let via_primitive = sup
22652 .run_kind_owned_slot_family_gate(
22653 CaixaKind::is_supervisor,
22654 Caixa::declared_supervisor_slots,
22655 crate::LayoutError::supervisor_slots_on_non_supervisor,
22656 )
22657 .unwrap_err();
22658 assert_eq!(
22659 via_compound, via_primitive,
22660 "validate_kind_slot_coherence's supervisor arm must route \
22661 byte-equal through the run_kind_owned_slot_family_gate \
22662 substrate primitive",
22663 );
22664
22665 // Servico arm — non-Servico carrying a declared M2 slot on a
22666 // kind foreign to every prior arm (Biblioteca — foreign to
22667 // both the Aplicacao mesh arm and the Supervisor supervisor
22668 // arm and the Servico M2 arm).
22669 let mut svc = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22670 svc.kind = CaixaKind::Biblioteca;
22671 svc.limits = Some(LimitsSpec {
22672 memory: Some(64 * 1024 * 1024),
22673 fuel: None,
22674 wall_clock: None,
22675 cpu: None,
22676 });
22677 let via_compound = svc.validate_kind_slot_coherence().unwrap_err();
22678 let via_primitive = svc
22679 .run_kind_owned_slot_family_gate(
22680 CaixaKind::is_servico,
22681 Caixa::declared_servico_slots,
22682 crate::LayoutError::servico_slots_on_non_servico,
22683 )
22684 .unwrap_err();
22685 assert_eq!(
22686 via_compound, via_primitive,
22687 "validate_kind_slot_coherence's servico arm must route \
22688 byte-equal through the run_kind_owned_slot_family_gate \
22689 substrate primitive",
22690 );
22691 }
22692
22693 #[test]
22694 fn validate_no_code_kind_coherence_folds_supervisor_arm_matches_gate() {
22695 // Fail-before-pass-after per-arm equivalence pin on the
22696 // Supervisor no-code arm of the reciprocal code-surface
22697 // fold: a `:kind Supervisor` caixa carrying a declared
22698 // `:bibliotecas` entry (the smallest possible code-surface
22699 // declaration on a no-code kind) surfaces the same
22700 // [`crate::LayoutError::SupervisorOwnsCode`] variant
22701 // through both the compound gate
22702 // [`Caixa::validate_no_code_kind_coherence`] and the
22703 // standalone constructor
22704 // [`crate::LayoutError::supervisor_owns_code`]. Pins the
22705 // fold — a silent regression that de-folded the Supervisor
22706 // arm would surface here as a mismatch between the two
22707 // dispatches. Sibling in shape to the peer
22708 // `validate_kind_slot_coherence_folds_supervisor_arm_matches_gate`
22709 // per-arm equivalence pin on the cross-family
22710 // typed-slot-coherence fold.
22711 let mut c = Caixa::from_lisp(&Caixa::template("sup")).unwrap();
22712 c.kind = CaixaKind::Supervisor;
22713 c.bibliotecas = vec!["lib/sup.lisp".into()];
22714 let via_method = c.validate_no_code_kind_coherence().unwrap_err();
22715 let via_standalone = crate::LayoutError::supervisor_owns_code(&c);
22716 assert_eq!(
22717 via_method, via_standalone,
22718 "Caixa::validate_no_code_kind_coherence must surface the \
22719 Supervisor arm's diagnostic byte-equal to the standalone \
22720 LayoutError::supervisor_owns_code ctor",
22721 );
22722 }
22723
22724 #[test]
22725 fn validate_no_code_kind_coherence_folds_aplicacao_arm_matches_gate() {
22726 // Per-arm equivalence pin on the Aplicacao no-code arm —
22727 // the sibling of the Supervisor arm on the code-surface
22728 // fold. A `:kind Aplicacao` caixa carrying a declared
22729 // `:exe` entry surfaces the same
22730 // [`crate::LayoutError::AplicacaoOwnsCode`] variant through
22731 // both dispatches. Uses the `:exe` code-surface axis (a
22732 // second axis distinct from the Supervisor arm's
22733 // `:bibliotecas` fixture) so the three per-arm pins
22734 // collectively exercise every arm of the `has_code`
22735 // disjunction (`:bibliotecas || :exe || :servicos`).
22736 let mut c = Caixa::from_lisp(&Caixa::template("app")).unwrap();
22737 c.kind = CaixaKind::Aplicacao;
22738 c.bibliotecas = vec![];
22739 c.exe = vec!["exe/app".into()];
22740 let via_method = c.validate_no_code_kind_coherence().unwrap_err();
22741 let via_standalone = crate::LayoutError::aplicacao_owns_code(&c);
22742 assert_eq!(
22743 via_method, via_standalone,
22744 "Caixa::validate_no_code_kind_coherence must surface the \
22745 Aplicacao arm's diagnostic byte-equal to the standalone \
22746 LayoutError::aplicacao_owns_code ctor",
22747 );
22748 }
22749
22750 #[test]
22751 fn validate_no_code_kind_coherence_folds_acao_arm_matches_gate() {
22752 // Per-arm equivalence pin on the Acao no-code arm — the
22753 // third and last arm on the code-surface fold. A `:kind
22754 // Acao` caixa carrying a declared `:servicos` entry
22755 // surfaces the same [`crate::LayoutError::AcaoOwnsCode`]
22756 // variant through both dispatches. Uses the `:servicos`
22757 // code-surface axis (the third distinct axis of the
22758 // `has_code` disjunction) so the three per-arm pins
22759 // collectively cover every arm of the code-surface
22760 // disjunction plus every no-code kind of the arm
22761 // dispatch.
22762 let mut c = Caixa::from_lisp(&Caixa::template("acao")).unwrap();
22763 c.kind = CaixaKind::Acao;
22764 c.bibliotecas = vec![];
22765 c.servicos = vec!["servicos/demo.computeunit.yaml".into()];
22766 let via_method = c.validate_no_code_kind_coherence().unwrap_err();
22767 let via_standalone = crate::LayoutError::acao_owns_code(&c);
22768 assert_eq!(
22769 via_method, via_standalone,
22770 "Caixa::validate_no_code_kind_coherence must surface the \
22771 Acao arm's diagnostic byte-equal to the standalone \
22772 LayoutError::acao_owns_code ctor",
22773 );
22774 }
22775
22776 #[test]
22777 fn validate_no_code_kind_coherence_accepts_owner_kind_on_every_code_axis() {
22778 // Positive control on the code-owning-kind identity
22779 // element: each of the three code-owning kinds
22780 // (`Biblioteca` owning `:bibliotecas`, `Binario` owning
22781 // `:exe`, `Servico` owning `:servicos`) passes the
22782 // compound gate cleanly when it declares its native code
22783 // surface. Pins the fold's second identity element — the
22784 // paired per-arm `is_<no-code-kind>()` short-circuit
22785 // fires on every code-owning kind, so a caixa with any
22786 // native code declaration on its owner kind surfaces no
22787 // diagnostic. A silent regression that dropped the paired
22788 // `is_<no-code-kind>()` short-circuit guard on any arm
22789 // would surface here as a false-positive rejection of the
22790 // corresponding owner kind. Peer with the
22791 // `validate_kind_slot_coherence_accepts_owner_kind_on_every_arm`
22792 // identity-element pin on the sibling cross-family fold.
22793 let mut bib = Caixa::from_lisp(&Caixa::template("bib")).unwrap();
22794 bib.kind = CaixaKind::Biblioteca;
22795 bib.bibliotecas = vec!["lib/bib.lisp".into()];
22796 bib.validate_no_code_kind_coherence().expect(
22797 "a :kind Biblioteca caixa with declared :bibliotecas must pass \
22798 the compound gate — Biblioteca owns the :bibliotecas code surface",
22799 );
22800
22801 let mut bin = Caixa::from_lisp(&Caixa::template("bin")).unwrap();
22802 bin.kind = CaixaKind::Binario;
22803 bin.bibliotecas = vec![];
22804 bin.exe = vec!["exe/bin".into()];
22805 bin.validate_no_code_kind_coherence().expect(
22806 "a :kind Binario caixa with declared :exe must pass the compound \
22807 gate — Binario owns the :exe code surface",
22808 );
22809
22810 let svc = bare_servico_fixture("svc");
22811 svc.validate_no_code_kind_coherence().expect(
22812 "a :kind Servico caixa with declared :servicos must pass the \
22813 compound gate — Servico owns the :servicos code surface",
22814 );
22815 }
22816
22817 #[test]
22818 fn validate_no_code_kind_coherence_accepts_bare_caixa_on_every_kind() {
22819 // Positive control on the has-no-code identity element:
22820 // a bare caixa (no declared code) passes the compound
22821 // gate on every kind — including the three no-code kinds
22822 // that would otherwise fire an OwnsCode diagnostic. Pins
22823 // the fold's first identity element — the paired
22824 // `!has_code` short-circuit fires before every per-arm
22825 // wrap dispatch, so a bare caixa of any kind surfaces no
22826 // diagnostic. A silent regression that dropped the
22827 // has_code guard would surface here as a false-positive
22828 // rejection of every no-code kind that declares no code.
22829 // Peer with the
22830 // `validate_kind_slot_coherence_accepts_bare_caixa_on_every_kind`
22831 // identity-element pin on the sibling cross-family fold.
22832 for kind in CaixaKind::ALL {
22833 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22834 c.kind = *kind;
22835 c.bibliotecas = vec![];
22836 c.exe = vec![];
22837 c.servicos = vec![];
22838 c.validate_no_code_kind_coherence().unwrap_or_else(|err| {
22839 panic!(
22840 "a bare :kind {kind:?} caixa (no declared code) must pass \
22841 the compound gate — the fold's first identity element is \
22842 the paired !has_code short-circuit, got {err:?}",
22843 )
22844 });
22845 }
22846 }
22847
22848 #[test]
22849 fn validate_ci_kind_coherence_folds_arm_matches_gate() {
22850 // Fail-before-pass-after per-arm equivalence pin on the
22851 // `:ci`-on-non-`Acao` arm: a `:kind Biblioteca` caixa
22852 // (the smallest non-`Acao` kind) carrying a declared
22853 // `:ci` slot surfaces the same
22854 // [`crate::LayoutError::CiOnNonAcao`] variant through the
22855 // compound gate [`Caixa::validate_ci_kind_coherence`] and
22856 // an inlined struct-literal wrap carrying `caixa.nome()`
22857 // + `caixa.kind()` verbatim. Pins the fold — a silent
22858 // regression that de-folded the arm would surface here as
22859 // a mismatch between the two dispatches. Sibling in shape
22860 // to the peer
22861 // `validate_no_code_kind_coherence_folds_supervisor_arm_matches_gate`
22862 // per-arm equivalence pin on the reciprocal
22863 // code-surface fold.
22864 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22865 c.kind = CaixaKind::Biblioteca;
22866 c.ci = Some(canteiro_types::CiRun {
22867 workspace: "pleme-io".into(),
22868 repo: "caixa".into(),
22869 nodes: vec![],
22870 });
22871 let via_method = c.validate_ci_kind_coherence().unwrap_err();
22872 let via_standalone = crate::LayoutError::CiOnNonAcao {
22873 caixa: c.nome().to_string(),
22874 kind: c.kind(),
22875 };
22876 assert_eq!(
22877 via_method, via_standalone,
22878 "Caixa::validate_ci_kind_coherence must surface the \
22879 :ci-on-non-Acao arm's diagnostic byte-equal to a \
22880 LayoutError::CiOnNonAcao struct literal carrying the \
22881 caixa's nome + kind",
22882 );
22883 }
22884
22885 #[test]
22886 fn validate_ci_kind_coherence_fold_names_offending_kind_on_every_non_acao_kind() {
22887 // Exhaustive per-kind sweep on the non-`Acao` arm: for each
22888 // of the five non-`Acao` kinds
22889 // (`Biblioteca` / `Binario` / `Servico` / `Supervisor` /
22890 // `Aplicacao`), a caixa carrying a declared `:ci` slot
22891 // surfaces the [`crate::LayoutError::CiOnNonAcao`]
22892 // variant naming the offending kind verbatim. A silent
22893 // regression that mistyped one arm's kind-projection
22894 // (e.g. always threading `CaixaKind::Biblioteca` regardless
22895 // of the caixa's actual kind) would surface here as a
22896 // mismatch on every kind past the first. Peer of the
22897 // `validate_no_code_kind_coherence_accepts_bare_caixa_on_every_kind`
22898 // exhaustive-sweep pin on the sibling code-surface fold.
22899 for kind in CaixaKind::ALL {
22900 if kind.is_acao() {
22901 continue;
22902 }
22903 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22904 c.kind = *kind;
22905 c.ci = Some(canteiro_types::CiRun {
22906 workspace: "pleme-io".into(),
22907 repo: "caixa".into(),
22908 nodes: vec![],
22909 });
22910 let err = c.validate_ci_kind_coherence().unwrap_err();
22911 match err {
22912 crate::LayoutError::CiOnNonAcao {
22913 caixa: got_caixa,
22914 kind: got_kind,
22915 } => {
22916 assert_eq!(
22917 got_caixa,
22918 c.nome(),
22919 "CiOnNonAcao must name the offending caixa's nome verbatim on kind {kind:?}",
22920 );
22921 assert_eq!(
22922 got_kind, *kind,
22923 "CiOnNonAcao must name the offending kind verbatim on kind {kind:?}",
22924 );
22925 }
22926 other => panic!(
22927 "expected CiOnNonAcao on :kind {kind:?} with declared :ci, got {other:?}",
22928 ),
22929 }
22930 }
22931 }
22932
22933 #[test]
22934 fn validate_ci_kind_coherence_accepts_acao_on_every_ci_shape() {
22935 // Positive control on the owner-kind identity element: an
22936 // `:kind Acao` caixa passes the coherence gate cleanly on
22937 // every `:ci` shape — the arm's paired
22938 // `!kind().is_acao()` short-circuit fires before the
22939 // dispatch, so the fold surfaces no diagnostic even on
22940 // fixtures whose `:ci` would fail the peer
22941 // [`Self::validate_acao_shape`] decompose gate (a
22942 // duplicate-node fixture, an unknown-dep fixture, a
22943 // cyclic fixture). Pins the fold's first identity element
22944 // — a silent regression that dropped the paired
22945 // `!kind().is_acao()` short-circuit guard would surface
22946 // here as a false-positive rejection of every `Acao`
22947 // caixa. Peer with the
22948 // `validate_no_code_kind_coherence_accepts_owner_kind_on_every_code_axis`
22949 // identity-element pin on the sibling code-surface fold.
22950 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22951 c.kind = CaixaKind::Acao;
22952 c.bibliotecas = vec![];
22953 c.ci = Some(canteiro_types::CiRun {
22954 workspace: "pleme-io".into(),
22955 repo: "caixa".into(),
22956 nodes: vec![],
22957 });
22958 c.validate_ci_kind_coherence().expect(
22959 "a :kind Acao caixa with declared :ci must pass the compound \
22960 coherence gate — Acao is the :ci-owning kind (a malformed \
22961 :ci on Acao surfaces via validate_acao_shape's decompose gate, \
22962 not via this kind-coherence gate)",
22963 );
22964 }
22965
22966 #[test]
22967 fn validate_ci_kind_coherence_accepts_absent_ci_on_every_kind() {
22968 // Positive control on the absent-`:ci` identity element:
22969 // a caixa with `ci = None` passes the coherence gate on
22970 // every kind — including `Acao`, whose absent `:ci`
22971 // fails a separate presence gate ([`crate::LayoutError::MissingCi`])
22972 // downstream at the layout altitude, not this coherence
22973 // gate. Pins the fold's second identity element — the
22974 // paired `ci().is_some()` short-circuit fires before every
22975 // per-arm dispatch, so a caixa with no declared `:ci`
22976 // surfaces no coherence diagnostic. A silent regression
22977 // that dropped the paired `ci().is_some()` short-circuit
22978 // would surface here as a false-positive rejection on
22979 // every non-`Acao` kind. Peer with the
22980 // `validate_no_code_kind_coherence_accepts_bare_caixa_on_every_kind`
22981 // identity-element pin on the sibling code-surface fold.
22982 for kind in CaixaKind::ALL {
22983 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
22984 c.kind = *kind;
22985 c.ci = None;
22986 c.validate_ci_kind_coherence().unwrap_or_else(|err| {
22987 panic!(
22988 "a :kind {kind:?} caixa with no declared :ci must pass \
22989 the compound coherence gate — the fold's second identity \
22990 element is the paired ci().is_some() short-circuit, got \
22991 {err:?}",
22992 )
22993 });
22994 }
22995 }
22996
22997 #[test]
22998 fn validate_foreign_code_kind_coherence_folds_arm_matches_gate() {
22999 // Fail-before-pass-after equivalence pin on the compound
23000 // foreign-code-slot coherence fold: a `:kind Servico` caixa
23001 // carrying a declared `:exe` entry (the smallest possible
23002 // foreign-code-slot declaration on a code-running kind that
23003 // is not its owner — Servico owns `:servicos`, not `:exe`)
23004 // surfaces the same [`crate::LayoutError::ForeignCodeSlot`]
23005 // variant through both the compound gate
23006 // [`Caixa::validate_foreign_code_kind_coherence`] and the
23007 // standalone constructor
23008 // [`crate::LayoutError::foreign_code_slot`] dispatched on the
23009 // same `declared_foreign_code_slots` list. Pins the fold — a
23010 // silent regression that de-folded the arm would surface here
23011 // as a mismatch between the two dispatches. Sibling in shape
23012 // to the peer
23013 // `validate_kind_slot_coherence_folds_mesh_arm_matches_gate`
23014 // / `validate_ci_kind_coherence_folds_arm_matches_gate` /
23015 // `validate_no_code_kind_coherence_folds_supervisor_arm_matches_gate`
23016 // per-arm equivalence pins on the sibling kind-coherence folds.
23017 let mut c = bare_servico_fixture("demo");
23018 c.exe = vec!["exe/foreign".into()];
23019 let via_method = c.validate_foreign_code_kind_coherence().unwrap_err();
23020 let via_standalone =
23021 crate::LayoutError::foreign_code_slot(&c, c.declared_foreign_code_slots());
23022 assert_eq!(
23023 via_method, via_standalone,
23024 "Caixa::validate_foreign_code_kind_coherence must surface the \
23025 foreign-code-slot diagnostic byte-equal to the standalone \
23026 LayoutError::foreign_code_slot ctor on the same \
23027 declared_foreign_code_slots list",
23028 );
23029 }
23030
23031 #[test]
23032 fn validate_foreign_code_kind_coherence_exe_arm_precedes_servicos_arm() {
23033 // Cross-arm ordering pin on the fold's accumulator: a fixture
23034 // carrying BOTH a declared `:exe` AND a declared `:servicos`
23035 // on a kind foreign to both (a `:kind Biblioteca` here —
23036 // foreign to both the Binario arm and the Servico arm)
23037 // surfaces `:exe` first in the `ForeignCodeSlot`'s slots
23038 // list. Pins the canonical `:exe` → `:servicos` diagnostic
23039 // order [`Caixa::declared_foreign_code_slots`] establishes,
23040 // as a property of the substrate primitive rather than an
23041 // implicit accumulator convention. A silent reordering
23042 // regression at the accumulator would surface here as a
23043 // wrong-first-slot list before landing at a downstream
23044 // consumer's diagnostic-ordering expectation.
23045 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
23046 c.kind = CaixaKind::Biblioteca;
23047 c.exe = vec!["exe/demo".into()];
23048 c.servicos = vec!["servicos/demo.computeunit.yaml".into()];
23049 let err = c.validate_foreign_code_kind_coherence().unwrap_err();
23050 let crate::LayoutError::ForeignCodeSlot { slots, .. } = &err else {
23051 panic!("expected ForeignCodeSlot variant, got {err:?}");
23052 };
23053 assert!(
23054 slots.starts_with(":exe"),
23055 "expected the :exe arm to precede the :servicos arm in the \
23056 ForeignCodeSlot slots list under the canonical :exe → :servicos \
23057 order, got slots = {slots:?}",
23058 );
23059 assert!(
23060 slots.contains(":servicos"),
23061 "expected the :servicos arm to also fire in the ForeignCodeSlot \
23062 slots list on a fixture carrying both foreign code surfaces, \
23063 got slots = {slots:?}",
23064 );
23065 }
23066
23067 #[test]
23068 fn validate_foreign_code_kind_coherence_accepts_native_slot_on_owner_kind() {
23069 // Positive control on the native-slot identity element: each
23070 // code-surface slot's owner kind passes the fold trivially
23071 // when it declares only its native code surface. `:kind
23072 // Binario` with a declared `:exe` and no `:servicos` passes
23073 // (the `!requires_exe()` guard short-circuits the arm inside
23074 // [`Caixa::declared_foreign_code_slots`], so the accumulator
23075 // returns empty); `:kind Servico` with a declared `:servicos`
23076 // and no `:exe` passes for the mirror reason. Pins the fold's
23077 // native-slot identity element on both arms — a silent
23078 // regression that dropped either per-arm `!requires_<slot>()`
23079 // predicate would surface here as a false-positive rejection
23080 // of every native-slot declaration on its owner kind. Peer
23081 // with the
23082 // `validate_kind_slot_coherence_accepts_owner_kind_on_every_arm`
23083 // identity-element pin on the sibling cross-family fold.
23084 let mut bin = Caixa::from_lisp(&Caixa::template("bin")).unwrap();
23085 bin.kind = CaixaKind::Binario;
23086 bin.bibliotecas = vec![];
23087 bin.exe = vec!["exe/bin".into()];
23088 bin.servicos = vec![];
23089 bin.validate_foreign_code_kind_coherence().expect(
23090 "a :kind Binario caixa with a declared native :exe and no \
23091 :servicos must pass the compound coherence gate — Binario is \
23092 the :exe slot's owner kind and the fold's native-slot identity \
23093 element on that arm",
23094 );
23095
23096 let mut svc = bare_servico_fixture("svc");
23097 svc.exe = vec![];
23098 svc.validate_foreign_code_kind_coherence().expect(
23099 "a :kind Servico caixa with a declared native :servicos and no \
23100 :exe must pass the compound coherence gate — Servico is the \
23101 :servicos slot's owner kind and the fold's native-slot identity \
23102 element on that arm",
23103 );
23104 }
23105
23106 #[test]
23107 fn validate_foreign_code_kind_coherence_accepts_bare_caixa_on_every_kind() {
23108 // Positive control on the empty-slot identity element: a
23109 // bare caixa (no declared `:exe` and no declared `:servicos`)
23110 // passes the compound gate on every kind. Pins the fold's
23111 // identity element on the empty-accumulator axis — the outer
23112 // `is_empty` short-circuit fires before the wrap dispatch on
23113 // every kind, so a bare caixa of any kind surfaces no
23114 // foreign-code-slot diagnostic. A silent regression that
23115 // dropped the emptiness guard would surface here as a
23116 // false-positive rejection of every no-code-slot caixa
23117 // across the whole kind axis. Peer with the
23118 // `validate_kind_slot_coherence_accepts_bare_caixa_on_every_kind`
23119 // identity-element pin on the sibling cross-family fold.
23120 for kind in CaixaKind::ALL {
23121 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
23122 c.kind = *kind;
23123 c.bibliotecas = vec![];
23124 c.exe = vec![];
23125 c.servicos = vec![];
23126 c.validate_foreign_code_kind_coherence()
23127 .unwrap_or_else(|err| {
23128 panic!(
23129 "a bare :kind {kind:?} caixa (no declared :exe / \
23130 :servicos) must pass the compound coherence gate — \
23131 the fold's identity element on the empty-accumulator \
23132 axis is the outer Vec::is_empty short-circuit, got \
23133 {err:?}",
23134 )
23135 });
23136 }
23137 }
23138
23139 #[test]
23140 fn validate_required_kind_slot_folds_binario_arm_matches_gate() {
23141 // Fail-before-pass-after per-arm equivalence pin on the
23142 // `Binario` required-`:exe` arm of the required-slot fold:
23143 // a `:kind Binario` caixa carrying no declared `:exe` entry
23144 // surfaces the same
23145 // [`crate::LayoutError::BinarioWithoutExe`] variant through
23146 // both the compound gate
23147 // [`Caixa::validate_required_kind_slot`] and the standalone
23148 // constructor [`crate::LayoutError::binario_without_exe`].
23149 // Pins the fold — a silent regression that de-folded the
23150 // `Binario` arm would surface here as a mismatch between
23151 // the two dispatches. Sibling in shape to the peer
23152 // `validate_no_code_kind_coherence_folds_supervisor_arm_matches_gate`
23153 // per-arm equivalence pin on the reciprocal code-surface
23154 // fold.
23155 let mut c = Caixa::from_lisp(&Caixa::template("bin")).unwrap();
23156 c.kind = CaixaKind::Binario;
23157 c.bibliotecas = vec![];
23158 c.exe = vec![];
23159 let via_method = c.validate_required_kind_slot().unwrap_err();
23160 let via_standalone = crate::LayoutError::binario_without_exe(&c);
23161 assert_eq!(
23162 via_method, via_standalone,
23163 "Caixa::validate_required_kind_slot must surface the \
23164 Binario arm's diagnostic byte-equal to the standalone \
23165 LayoutError::binario_without_exe ctor",
23166 );
23167 }
23168
23169 #[test]
23170 fn validate_required_kind_slot_folds_servico_arm_matches_gate() {
23171 // Per-arm equivalence pin on the `Servico` required-
23172 // `:servicos` arm — the sibling of the Binario arm on the
23173 // required-slot fold. A `:kind Servico` caixa carrying no
23174 // declared `:servicos` entry surfaces the same
23175 // [`crate::LayoutError::ServicoWithoutServicos`] variant
23176 // through both dispatches.
23177 let mut c = Caixa::from_lisp(&Caixa::template("svc")).unwrap();
23178 c.kind = CaixaKind::Servico;
23179 c.bibliotecas = vec![];
23180 c.servicos = vec![];
23181 let via_method = c.validate_required_kind_slot().unwrap_err();
23182 let via_standalone = crate::LayoutError::servico_without_servicos(&c);
23183 assert_eq!(
23184 via_method, via_standalone,
23185 "Caixa::validate_required_kind_slot must surface the \
23186 Servico arm's diagnostic byte-equal to the standalone \
23187 LayoutError::servico_without_servicos ctor",
23188 );
23189 }
23190
23191 #[test]
23192 fn validate_required_kind_slot_folds_acao_arm_matches_gate() {
23193 // Per-arm equivalence pin on the `Acao` required-`:ci` arm
23194 // — the third and last arm on the required-slot fold. A
23195 // `:kind Acao` caixa carrying no declared `:ci` slot
23196 // surfaces the same [`crate::LayoutError::MissingCi`]
23197 // variant through both dispatches. The three per-arm pins
23198 // collectively cover every required-slot axis and every
23199 // owner kind of the arm dispatch.
23200 let mut c = Caixa::from_lisp(&Caixa::template("acao")).unwrap();
23201 c.kind = CaixaKind::Acao;
23202 c.bibliotecas = vec![];
23203 c.ci = None;
23204 let via_method = c.validate_required_kind_slot().unwrap_err();
23205 let via_standalone = crate::LayoutError::missing_ci(&c);
23206 assert_eq!(
23207 via_method, via_standalone,
23208 "Caixa::validate_required_kind_slot must surface the \
23209 Acao arm's diagnostic byte-equal to the standalone \
23210 LayoutError::missing_ci ctor",
23211 );
23212 }
23213
23214 #[test]
23215 fn validate_required_kind_slot_accepts_owner_kind_with_required_slot_present() {
23216 // Positive control on the owner-kind-with-slot-present
23217 // identity element: each of the three owner kinds
23218 // (`Binario` with a non-empty `:exe`, `Servico` with a
23219 // non-empty `:servicos`, `Acao` with `ci = Some(_)`)
23220 // passes the compound gate cleanly when it declares its
23221 // required slot. Pins the fold's second identity element
23222 // — the paired `is_empty` / `is_none` short-circuit fires
23223 // on every owner kind whose required slot is present, so
23224 // a caixa with its native required slot surfaces no
23225 // diagnostic. A silent regression that dropped the paired
23226 // `is_empty` / `is_none` short-circuit guard on any arm
23227 // would surface here as a false-positive rejection of the
23228 // corresponding owner kind. Peer with the
23229 // `validate_no_code_kind_coherence_accepts_owner_kind_on_every_code_axis`
23230 // identity-element pin on the sibling code-surface fold.
23231 let mut bin = Caixa::from_lisp(&Caixa::template("bin")).unwrap();
23232 bin.kind = CaixaKind::Binario;
23233 bin.bibliotecas = vec![];
23234 bin.exe = vec!["exe/bin".into()];
23235 bin.validate_required_kind_slot().expect(
23236 "a :kind Binario caixa with declared :exe must pass the \
23237 required-slot gate — Binario's required slot is present",
23238 );
23239
23240 let svc = bare_servico_fixture("svc");
23241 svc.validate_required_kind_slot().expect(
23242 "a :kind Servico caixa with declared :servicos must pass \
23243 the required-slot gate — Servico's required slot is present",
23244 );
23245
23246 let acao = acao_fixture("acao");
23247 acao.validate_required_kind_slot().expect(
23248 "a :kind Acao caixa with declared :ci must pass the \
23249 required-slot gate — Acao's required slot is present",
23250 );
23251 }
23252
23253 #[test]
23254 fn validate_required_kind_slot_accepts_non_owner_kinds() {
23255 // Positive control on the non-owner-kind identity element:
23256 // every kind that is not one of the three owner kinds
23257 // (`Binario` / `Servico` / `Acao`) passes the compound gate
23258 // trivially — each per-arm predicate is
23259 // `self.kind().requires_<slot>()`, which returns `true`
23260 // only for the owner kind of that arm, so a non-owner kind
23261 // short-circuits every per-arm dispatch. Bibliotheca,
23262 // Supervisor, and Aplicacao are the three non-owner kinds
23263 // this pin exercises — none of them owns a required slot in
23264 // this fold (`Biblioteca`'s `:bibliotecas` default-file
23265 // fallback stays on the layout-side `MissingLib` fs-oracle
23266 // gate outside this fold; `Supervisor`'s `:children` and
23267 // `Aplicacao`'s `:membros` are carried by
23268 // [`CaixaKind::requires_children`] /
23269 // [`CaixaKind::requires_membros`] without a paired
23270 // layout-side wire-up). A silent regression that swapped a
23271 // per-arm predicate for a non-`requires_*` guard would
23272 // surface here as a false-positive rejection of the
23273 // corresponding non-owner kind. Peer with the
23274 // `validate_ci_kind_coherence_accepts_absent_ci_on_every_kind`
23275 // identity-element pin on the sibling `:ci` fold.
23276 for kind in CaixaKind::ALL {
23277 if kind.requires_exe() || kind.requires_servicos() || kind.requires_ci() {
23278 continue;
23279 }
23280 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
23281 c.kind = *kind;
23282 c.bibliotecas = vec![];
23283 c.exe = vec![];
23284 c.servicos = vec![];
23285 c.ci = None;
23286 c.validate_required_kind_slot().unwrap_or_else(|err| {
23287 panic!(
23288 "a :kind {kind:?} caixa (a non-owner kind on every \
23289 required-slot arm) must pass the compound gate — the \
23290 fold's identity element is the paired \
23291 `self.kind().requires_<slot>()` short-circuit, got \
23292 {err:?}",
23293 )
23294 });
23295 }
23296 }
23297
23298 // ── `manifest_code_path_slot_path_ctors!` — the paired `{ slot:
23299 // &'static str, path: PathBuf }` two-slot envelope on
23300 // `ManifestError`, strict sibling of the peer
23301 // [`crate::behavior::behavior_slot_path_ctors!`] (67c31ec) on the
23302 // sibling `BehaviorError` envelope's identical
23303 // `{ slot: &'static str, path: PathBuf }` two-slot shape.
23304 // Five-variant lift closing the five open-coded ctor sites
23305 // remaining on the `:bibliotecas` / `:exe` / `:servicos`
23306 // code-path-list value-shape trajectory this envelope carries.
23307
23308 #[test]
23309 fn code_path_absolute_ctor_matches_struct_literal_wrap() {
23310 let path = Path::new("/abs/lib/x.lisp");
23311 assert_eq!(
23312 ManifestError::code_path_absolute(":bibliotecas", path),
23313 ManifestError::CodePathAbsolute {
23314 slot: ":bibliotecas",
23315 path: path.to_path_buf(),
23316 },
23317 "generated code_path_absolute ctor must produce byte-equal \
23318 `ManifestError::CodePathAbsolute` to the pre-lift \
23319 struct-literal wrap on the same `(&'static str, &Path)` \
23320 fixture",
23321 );
23322 }
23323
23324 #[test]
23325 fn code_path_parent_escape_ctor_matches_struct_literal_wrap() {
23326 let path = Path::new("lib/../../etc/x.lisp");
23327 assert_eq!(
23328 ManifestError::code_path_parent_escape(":bibliotecas", path),
23329 ManifestError::CodePathParentEscape {
23330 slot: ":bibliotecas",
23331 path: path.to_path_buf(),
23332 },
23333 "generated code_path_parent_escape ctor must produce \
23334 byte-equal `ManifestError::CodePathParentEscape` to the \
23335 pre-lift struct-literal wrap on the same `(&'static str, \
23336 &Path)` fixture",
23337 );
23338 }
23339
23340 #[test]
23341 fn code_path_non_lisp_extension_ctor_matches_struct_literal_wrap() {
23342 let path = Path::new("lib/x.txt");
23343 assert_eq!(
23344 ManifestError::code_path_non_lisp_extension(":bibliotecas", path),
23345 ManifestError::CodePathNonLispExtension {
23346 slot: ":bibliotecas",
23347 path: path.to_path_buf(),
23348 },
23349 "generated code_path_non_lisp_extension ctor must produce \
23350 byte-equal `ManifestError::CodePathNonLispExtension` to \
23351 the pre-lift struct-literal wrap on the same \
23352 `(&'static str, &Path)` fixture",
23353 );
23354 }
23355
23356 #[test]
23357 fn code_path_non_computeunit_yaml_extension_ctor_matches_struct_literal_wrap() {
23358 let path = Path::new("servicos/x.yaml");
23359 assert_eq!(
23360 ManifestError::code_path_non_computeunit_yaml_extension(":servicos", path),
23361 ManifestError::CodePathNonComputeUnitYamlExtension {
23362 slot: ":servicos",
23363 path: path.to_path_buf(),
23364 },
23365 "generated code_path_non_computeunit_yaml_extension ctor \
23366 must produce byte-equal \
23367 `ManifestError::CodePathNonComputeUnitYamlExtension` to \
23368 the pre-lift struct-literal wrap on the same \
23369 `(&'static str, &Path)` fixture",
23370 );
23371 }
23372
23373 #[test]
23374 fn code_path_duplicate_ctor_matches_struct_literal_wrap() {
23375 let path = Path::new("lib/x.lisp");
23376 assert_eq!(
23377 ManifestError::code_path_duplicate(":bibliotecas", path),
23378 ManifestError::CodePathDuplicate {
23379 slot: ":bibliotecas",
23380 path: path.to_path_buf(),
23381 },
23382 "generated code_path_duplicate ctor must produce byte-equal \
23383 `ManifestError::CodePathDuplicate` to the pre-lift \
23384 struct-literal wrap on the same `(&'static str, &Path)` \
23385 fixture",
23386 );
23387 }
23388
23389 #[test]
23390 fn manifest_code_path_slot_path_ctors_route_slot_and_path_through_uniformly() {
23391 // Cross-axis routing pin: sweep the two constructor input axes
23392 // (`slot: &'static str`, `path: &Path`) through non-default
23393 // fixtures against every generated arm in the
23394 // [`manifest_code_path_slot_path_ctors!`] macro, so any
23395 // wrapper-side lowercase / trim / truncate / canonicalization at
23396 // codegen time — or a silent field re-name away from the
23397 // canonical `slot` / `path` axes on any one variant, or a `slot`
23398 // axis silently rerouted through `.to_string()` instead of
23399 // passed as `&'static str` verbatim, or a `path` axis silently
23400 // rerouted through `.canonicalize()` / `PathBuf::from(<lossy
23401 // string>)` instead of `.to_path_buf()` — surfaces here rather
23402 // than at a downstream diagnostic-shape mismatch. Peer of the
23403 // sibling
23404 // [`crate::behavior::tests::behavior_slot_path_ctors_route_slot_and_path_through_uniformly`]
23405 // pin (67c31ec) on the sibling `BehaviorError` envelope's
23406 // identical two-slot family.
23407 //
23408 // The `path` fixture carries three distinguishing traits at
23409 // once: a non-`root/`-relative leading segment (`weird/`), a
23410 // `..` component (a canonicalization trap that would collapse
23411 // to `weird/x.lisp` under `.canonicalize()`), and a mixed-case
23412 // extension (a lowercase-normalization trap that would collapse
23413 // `.LISP` to `.lisp` under any `to_ascii_lowercase()` codegen)
23414 // so a routing regression on any one of the three trap axes
23415 // surfaces at assert time. Similarly the `slot` fixture
23416 // sweeps the three canonical code-path author-key literals
23417 // (`:bibliotecas` / `:exe` / `:servicos`) so a silent lookup
23418 // against a per-variant const roster would surface here.
23419 let path = Path::new("weird/../nested/x.LISP");
23420 let cases: [(ManifestError, ManifestError); 5] = [
23421 (
23422 ManifestError::code_path_absolute(":bibliotecas", path),
23423 ManifestError::CodePathAbsolute {
23424 slot: ":bibliotecas",
23425 path: path.to_path_buf(),
23426 },
23427 ),
23428 (
23429 ManifestError::code_path_parent_escape(":exe", path),
23430 ManifestError::CodePathParentEscape {
23431 slot: ":exe",
23432 path: path.to_path_buf(),
23433 },
23434 ),
23435 (
23436 ManifestError::code_path_non_lisp_extension(":servicos", path),
23437 ManifestError::CodePathNonLispExtension {
23438 slot: ":servicos",
23439 path: path.to_path_buf(),
23440 },
23441 ),
23442 (
23443 ManifestError::code_path_non_computeunit_yaml_extension(":bibliotecas", path),
23444 ManifestError::CodePathNonComputeUnitYamlExtension {
23445 slot: ":bibliotecas",
23446 path: path.to_path_buf(),
23447 },
23448 ),
23449 (
23450 ManifestError::code_path_duplicate(":exe", path),
23451 ManifestError::CodePathDuplicate {
23452 slot: ":exe",
23453 path: path.to_path_buf(),
23454 },
23455 ),
23456 ];
23457 for (via_ctor, via_struct_literal) in cases {
23458 assert_eq!(
23459 via_ctor, via_struct_literal,
23460 "manifest_code_path_slot_path_ctors!-generated ctor \
23461 must pass `slot` verbatim onto the canonical \
23462 `&'static str` `slot` field and route `path` through \
23463 `.to_path_buf()` onto the canonical `PathBuf` `path` \
23464 field — a field-rename, silent-conversion, or \
23465 axis-swap regression surfaces here rather than at a \
23466 downstream diagnostic-shape mismatch",
23467 );
23468 }
23469 }
23470
23471 // Per-variant equivalence pin for the [`ManifestError::code_path_empty`]
23472 // one-slot inherent constructor (see the paired doc-block above the impl
23473 // definition) — the constructor folds the uniform
23474 // `Self::CodePathEmpty { slot }` one-field struct-literal onto one
23475 // substrate primitive. The equivalence pin below (fail-before-pass-after
23476 // by construction — a byte-mismatched constructor body would trip this pin
23477 // first) locks the generated constructor to its struct-literal peer under
23478 // `PartialEq`, so the wire-up at
23479 // [`Caixa::validate_code_path_lists`]'s per-slot
23480 // [`PathShapeViolation::Empty`] arm on this variant produces a byte-equal
23481 // `ManifestError` to the pre-lift open-coded struct-literal. The
23482 // cross-axis pin that follows (`slot: &'static str` sweep over every
23483 // canonical `:bibliotecas` / `:exe` / `:servicos` code-path author-key
23484 // label) routes the constructor input axis verbatim (`slot` as
23485 // `&'static str` without conversion), so the fold does not silently
23486 // collapse onto a fixed `slot` value.
23487 //
23488 // Peer of the sibling `code_path_absolute_ctor_matches_struct_literal_wrap`
23489 // / `code_path_parent_escape_ctor_matches_struct_literal_wrap` /
23490 // `code_path_non_lisp_extension_ctor_matches_struct_literal_wrap` /
23491 // `code_path_non_computeunit_yaml_extension_ctor_matches_struct_literal_wrap`
23492 // / `code_path_duplicate_ctor_matches_struct_literal_wrap` /
23493 // `manifest_code_path_slot_path_ctors_route_slot_and_path_through_uniformly`
23494 // equivalence + cross-axis pins the peer
23495 // [`manifest_code_path_slot_path_ctors!`] family (de11917) established on
23496 // the paired `{ slot: &'static str, path: PathBuf }` two-slot envelope of
23497 // the same `ManifestError` — the per-slot [`PathShapeViolation`] cascade
23498 // at [`Caixa::validate_code_path_lists`] now carries a substrate-primitive
23499 // equivalence pin at every arm rather than five pinned arms plus a
23500 // hand-written open-coded sixth. Mirror-symmetric sibling of the peer
23501 // [`crate::behavior::tests::empty_path_ctor_matches_struct_literal_wrap`]
23502 // / `empty_path_ctor_routes_slot_verbatim_across_every_on_star_key` pins
23503 // on the sibling M2 `:behavior` envelope's identical one-slot shape.
23504
23505 #[test]
23506 fn code_path_empty_ctor_matches_struct_literal_wrap() {
23507 let slot = ":bibliotecas";
23508 assert_eq!(
23509 ManifestError::code_path_empty(slot),
23510 ManifestError::CodePathEmpty { slot },
23511 "generated code_path_empty ctor must produce byte-equal \
23512 `ManifestError::CodePathEmpty` to the open-coded struct-literal \
23513 wrap on the same `&'static str` fixture",
23514 );
23515 }
23516
23517 #[test]
23518 fn code_path_empty_ctor_routes_slot_verbatim_across_every_code_path_key() {
23519 // Cross-axis pin: sweep the constructor's single input axis
23520 // (`slot: &'static str`) through every canonical code-path
23521 // author-key label the outer per-slot iterator at
23522 // [`Caixa::validate_code_path_lists`] threads through so any
23523 // wrapper-side lowercase / trim / truncate / fixed-slot substitution
23524 // on the one-field construction surfaces here rather than at a
23525 // downstream diagnostic-shape mismatch. Peer of the sibling
23526 // [`manifest_code_path_slot_path_ctors_route_slot_and_path_through_uniformly`]
23527 // cross-axis pin on the two-slot envelope of the same
23528 // `ManifestError` — extended here onto the one-slot envelope so
23529 // both slot-only and slot+path constructor input axes carry a
23530 // per-code-path-label sweep. Mirror-symmetric sibling of the peer
23531 // [`crate::behavior::tests::empty_path_ctor_routes_slot_verbatim_across_every_on_star_key`]
23532 // sweep on the sibling M2 `:behavior` envelope's identical one-slot
23533 // shape.
23534 for slot in [":bibliotecas", ":exe", ":servicos"] {
23535 assert_eq!(
23536 ManifestError::code_path_empty(slot),
23537 ManifestError::CodePathEmpty { slot },
23538 );
23539 }
23540 }
23541
23542 // ── `manifest_field_reason_ctors!` — the paired `{ <field>: String,
23543 // reason: String }` two-slot envelope on `ManifestError`, direct
23544 // sibling of the peer
23545 // [`crate::aplicacao::aplicacao_field_reason_ctors!`] (981060b)
23546 // on the M3 mesh `AplicacaoError` envelope's identical two-slot
23547 // shape and of the peer [`crate::dep::dep_nome_axis_reason_ctors!`]
23548 // (5621f8a) on the sibling `:deps` envelope's mirror-symmetric
23549 // three-slot shape (the `nome` axis added at the per-dep-owned
23550 // altitude). Ten-variant lift closing the ten open-coded ctor
23551 // sites at the per-axis [`Caixa::validate_*`] cascade — the tenth
23552 // (`restart_window_malformed => RestartWindowMalformed
23553 // { restart_window }`) closes the last open-coded four-line
23554 // `.map_err(|reason| ManifestError::RestartWindowMalformed
23555 // { restart_window: s.to_string(), reason })` block at
23556 // [`Caixa::validate_restart_window`] onto the same substrate
23557 // primitive per typed variant.
23558
23559 #[test]
23560 fn nome_invalid_ctor_matches_struct_literal_wrap() {
23561 let nome = "cart-svc";
23562 let reason = "sample reason text";
23563 assert_eq!(
23564 ManifestError::nome_invalid(nome, reason),
23565 ManifestError::NomeInvalid {
23566 nome: nome.to_string(),
23567 reason: reason.to_string(),
23568 },
23569 "generated nome_invalid ctor must produce byte-equal \
23570 `ManifestError::NomeInvalid` to the pre-lift struct-literal \
23571 wrap on the same `(&str, &str)` fixture",
23572 );
23573 }
23574
23575 #[test]
23576 fn nome_chart_name_budget_exceeded_ctor_matches_struct_literal_wrap() {
23577 let nome = "a-very-long-cart-service-name";
23578 let reason = "sample reason text";
23579 assert_eq!(
23580 ManifestError::nome_chart_name_budget_exceeded(nome, reason),
23581 ManifestError::NomeChartNameBudgetExceeded {
23582 nome: nome.to_string(),
23583 reason: reason.to_string(),
23584 },
23585 "generated nome_chart_name_budget_exceeded ctor must produce \
23586 byte-equal `ManifestError::NomeChartNameBudgetExceeded` to \
23587 the pre-lift struct-literal wrap on the same `(&str, &str)` \
23588 fixture",
23589 );
23590 }
23591
23592 #[test]
23593 fn versao_invalid_ctor_matches_struct_literal_wrap() {
23594 let versao = "0.1";
23595 let reason = "sample reason text";
23596 assert_eq!(
23597 ManifestError::versao_invalid(versao, reason),
23598 ManifestError::VersaoInvalid {
23599 versao: versao.to_string(),
23600 reason: reason.to_string(),
23601 },
23602 "generated versao_invalid ctor must produce byte-equal \
23603 `ManifestError::VersaoInvalid` to the pre-lift \
23604 struct-literal wrap on the same `(&str, &str)` fixture",
23605 );
23606 }
23607
23608 #[test]
23609 fn etiqueta_invalid_ctor_matches_struct_literal_wrap() {
23610 let etiqueta = "MyKeyword";
23611 let reason = "sample reason text";
23612 assert_eq!(
23613 ManifestError::etiqueta_invalid(etiqueta, reason),
23614 ManifestError::EtiquetaInvalid {
23615 etiqueta: etiqueta.to_string(),
23616 reason: reason.to_string(),
23617 },
23618 "generated etiqueta_invalid ctor must produce byte-equal \
23619 `ManifestError::EtiquetaInvalid` to the pre-lift \
23620 struct-literal wrap on the same `(&str, &str)` fixture",
23621 );
23622 }
23623
23624 #[test]
23625 fn autor_invalid_ctor_matches_struct_literal_wrap() {
23626 let autor = "Ada Lovelace";
23627 let reason = "sample reason text";
23628 assert_eq!(
23629 ManifestError::autor_invalid(autor, reason),
23630 ManifestError::AutorInvalid {
23631 autor: autor.to_string(),
23632 reason: reason.to_string(),
23633 },
23634 "generated autor_invalid ctor must produce byte-equal \
23635 `ManifestError::AutorInvalid` to the pre-lift struct-literal \
23636 wrap on the same `(&str, &str)` fixture",
23637 );
23638 }
23639
23640 #[test]
23641 fn repositorio_invalid_ctor_matches_struct_literal_wrap() {
23642 let repositorio = "https://example.com/no-dot-git";
23643 let reason = "sample reason text";
23644 assert_eq!(
23645 ManifestError::repositorio_invalid(repositorio, reason),
23646 ManifestError::RepositorioInvalid {
23647 repositorio: repositorio.to_string(),
23648 reason: reason.to_string(),
23649 },
23650 "generated repositorio_invalid ctor must produce byte-equal \
23651 `ManifestError::RepositorioInvalid` to the pre-lift \
23652 struct-literal wrap on the same `(&str, &str)` fixture",
23653 );
23654 }
23655
23656 #[test]
23657 fn descricao_invalid_ctor_matches_struct_literal_wrap() {
23658 let descricao = "some description";
23659 let reason = "sample reason text";
23660 assert_eq!(
23661 ManifestError::descricao_invalid(descricao, reason),
23662 ManifestError::DescricaoInvalid {
23663 descricao: descricao.to_string(),
23664 reason: reason.to_string(),
23665 },
23666 "generated descricao_invalid ctor must produce byte-equal \
23667 `ManifestError::DescricaoInvalid` to the pre-lift \
23668 struct-literal wrap on the same `(&str, &str)` fixture",
23669 );
23670 }
23671
23672 #[test]
23673 fn licenca_invalid_ctor_matches_struct_literal_wrap() {
23674 let licenca = "not-an-spdx";
23675 let reason = "sample reason text";
23676 assert_eq!(
23677 ManifestError::licenca_invalid(licenca, reason),
23678 ManifestError::LicencaInvalid {
23679 licenca: licenca.to_string(),
23680 reason: reason.to_string(),
23681 },
23682 "generated licenca_invalid ctor must produce byte-equal \
23683 `ManifestError::LicencaInvalid` to the pre-lift \
23684 struct-literal wrap on the same `(&str, &str)` fixture",
23685 );
23686 }
23687
23688 #[test]
23689 fn edicao_invalid_ctor_matches_struct_literal_wrap() {
23690 let edicao = "26";
23691 let reason = "sample reason text";
23692 assert_eq!(
23693 ManifestError::edicao_invalid(edicao, reason),
23694 ManifestError::EdicaoInvalid {
23695 edicao: edicao.to_string(),
23696 reason: reason.to_string(),
23697 },
23698 "generated edicao_invalid ctor must produce byte-equal \
23699 `ManifestError::EdicaoInvalid` to the pre-lift \
23700 struct-literal wrap on the same `(&str, &str)` fixture",
23701 );
23702 }
23703
23704 #[test]
23705 fn restart_window_malformed_ctor_matches_struct_literal_wrap() {
23706 let restart_window = "1.5s";
23707 let reason = "sample reason text";
23708 assert_eq!(
23709 ManifestError::restart_window_malformed(restart_window, reason),
23710 ManifestError::RestartWindowMalformed {
23711 restart_window: restart_window.to_string(),
23712 reason: reason.to_string(),
23713 },
23714 "generated restart_window_malformed ctor must produce byte-equal \
23715 `ManifestError::RestartWindowMalformed` to the pre-lift \
23716 struct-literal wrap on the same `(&str, &str)` fixture",
23717 );
23718 }
23719
23720 // Routing pin against the actual [`Caixa::validate_restart_window`]
23721 // wire-up: the codec surfaces its parse error as `Result<Duration, String>`,
23722 // and the pre-lift `.map_err(|reason| ManifestError::RestartWindowMalformed
23723 // { restart_window: s.to_string(), reason })` closure passed the owned
23724 // `String` verbatim onto the `reason: String` slot. The lifted
23725 // `restart_window_malformed(&str, impl Into<String>)` ctor must produce
23726 // byte-equal output on the same `(offending_value, owned_reason)` pair a
23727 // real parse-failure fixture surfaces, so a silent regression on the
23728 // owned-`String` axis (a future `reason` bound change dropping the
23729 // `Into<String>` route the owned reason threads through) surfaces here
23730 // rather than at a downstream diagnostic-shape drift.
23731 #[test]
23732 fn restart_window_malformed_ctor_matches_wire_up_owned_reason_shape() {
23733 let raw = "1.5s";
23734 let reason: String = crate::supervisor::duration_codec::parse(raw)
23735 .expect_err("fractional-seconds `1.5s` must fail the shared codec");
23736 assert_eq!(
23737 ManifestError::restart_window_malformed(raw, reason.clone()),
23738 ManifestError::RestartWindowMalformed {
23739 restart_window: raw.to_string(),
23740 reason: reason.clone(),
23741 },
23742 "generated restart_window_malformed ctor must accept the owned \
23743 `String` the [`crate::supervisor::duration_codec::parse`] parse-\
23744 error carrier surfaces (the exact shape the \
23745 [`Caixa::validate_restart_window`] `.map_err(|reason| ...)` \
23746 closure passes into it) and produce byte-equal \
23747 `ManifestError::RestartWindowMalformed` to the pre-lift \
23748 struct-literal wrap on the same `(offending_value, owned_reason)` \
23749 pair",
23750 );
23751 }
23752
23753 // Cross-family invariance pin — the ten sibling ctors all route
23754 // `reason: impl Into<String>` + `<field>: &str` verbatim onto their
23755 // respective typed variants through the shared
23756 // [`manifest_field_reason_ctors!`] macro. Sweeps three fixture
23757 // shapes for `reason` (`&str` literal, owned `String`, `format!(…)`
23758 // output — the three shapes every in-crate wire-up threads through:
23759 // the parser-shaped `String` every `Result<(), String>` predicate
23760 // returns, the `e.to_string()` owned `String` the
23761 // `semver::Version::parse` arm passes, and the literal-shape reason
23762 // the `EdicaoInvalid` direct arm passes) against every generated arm
23763 // so any per-arm wrapper transformation drift surfaces here rather
23764 // than at a downstream diagnostic-shape mismatch. Peer of the
23765 // sibling
23766 // [`crate::aplicacao::tests::aplicacao_field_reason_ctors_route_reason_through_into_uniformly`]
23767 // pin (981060b) on the sibling `AplicacaoError` envelope's identical
23768 // two-slot family.
23769 #[test]
23770 fn manifest_field_reason_ctors_route_reason_through_into_uniformly() {
23771 let via_literal = "literal reason text";
23772 let via_owned: String = String::from("literal reason text");
23773 let via_format = format!("{} reason text", "literal");
23774 assert_eq!(
23775 ManifestError::nome_invalid("n", via_literal),
23776 ManifestError::nome_invalid("n", via_owned.clone()),
23777 );
23778 assert_eq!(
23779 ManifestError::nome_invalid("n", via_literal),
23780 ManifestError::nome_invalid("n", via_format.clone()),
23781 );
23782 assert_eq!(
23783 ManifestError::nome_chart_name_budget_exceeded("n", via_literal),
23784 ManifestError::nome_chart_name_budget_exceeded("n", via_owned.clone()),
23785 );
23786 assert_eq!(
23787 ManifestError::versao_invalid("0.1", via_literal),
23788 ManifestError::versao_invalid("0.1", via_owned.clone()),
23789 );
23790 assert_eq!(
23791 ManifestError::etiqueta_invalid("k", via_literal),
23792 ManifestError::etiqueta_invalid("k", via_owned.clone()),
23793 );
23794 assert_eq!(
23795 ManifestError::autor_invalid("a", via_literal),
23796 ManifestError::autor_invalid("a", via_owned.clone()),
23797 );
23798 assert_eq!(
23799 ManifestError::repositorio_invalid("r", via_literal),
23800 ManifestError::repositorio_invalid("r", via_owned.clone()),
23801 );
23802 assert_eq!(
23803 ManifestError::descricao_invalid("d", via_literal),
23804 ManifestError::descricao_invalid("d", via_owned.clone()),
23805 );
23806 assert_eq!(
23807 ManifestError::licenca_invalid("l", via_literal),
23808 ManifestError::licenca_invalid("l", via_owned.clone()),
23809 );
23810 assert_eq!(
23811 ManifestError::edicao_invalid("26", via_literal),
23812 ManifestError::edicao_invalid("26", via_owned.clone()),
23813 );
23814 assert_eq!(
23815 ManifestError::edicao_invalid("26", via_literal),
23816 ManifestError::edicao_invalid("26", via_format.clone()),
23817 );
23818 assert_eq!(
23819 ManifestError::restart_window_malformed("1.5s", via_literal),
23820 ManifestError::restart_window_malformed("1.5s", via_owned),
23821 );
23822 assert_eq!(
23823 ManifestError::restart_window_malformed("1.5s", via_literal),
23824 ManifestError::restart_window_malformed("1.5s", via_format),
23825 );
23826 }
23827
23828 // Cross-arm routing pin — the ten sibling ctors accept both `&str`
23829 // (from the [`Caixa::nome`] / [`Caixa::versao`] / [`Caixa::repositorio`]
23830 // / [`Caixa::descricao`] / [`Caixa::licenca`] / [`Caixa::edicao`]
23831 // accessors that return `&str`) and `&String` (from the
23832 // [`Caixa::etiquetas`] / [`Caixa::autores`] slice iterators that yield
23833 // `&String`) at the `<field>: &str` parameter via Deref coercion. This
23834 // pin sweeps both call shapes against the two accessors' actual
23835 // wire-up postures so a future rebrand of the etiquetas / autores
23836 // slice-iterator type (a lift from `&[String]` to `&[Cow<'_, str>]`,
23837 // a `smol_str::SmolStr` per-entry swap) that silently broke the
23838 // Deref-coercion path surfaces at this pin rather than at a
23839 // recompile-time type-mismatch far from the ctor family.
23840 #[test]
23841 fn manifest_field_reason_ctors_accept_both_str_and_string_slice_iters() {
23842 let owned: String = String::from("MyKeyword");
23843 // `&str` literal — the canonical accessor-return shape
23844 // ([`Caixa::nome`] etc. yield `&str`).
23845 assert_eq!(
23846 ManifestError::etiqueta_invalid("MyKeyword", "r"),
23847 ManifestError::EtiquetaInvalid {
23848 etiqueta: "MyKeyword".to_string(),
23849 reason: "r".to_string(),
23850 },
23851 );
23852 // `&String` — the canonical slice-iterator-yield shape
23853 // ([`Caixa::etiquetas`] / [`Caixa::autores`] yield `&String`).
23854 assert_eq!(
23855 ManifestError::etiqueta_invalid(&owned, "r"),
23856 ManifestError::EtiquetaInvalid {
23857 etiqueta: owned.clone(),
23858 reason: "r".to_string(),
23859 },
23860 );
23861 // Both call shapes must produce byte-equal
23862 // [`ManifestError::EtiquetaInvalid`] values on the same
23863 // underlying `String`, so a wire-up threading `etiqueta: &String`
23864 // through the same ctor as a peer wire-up threading `nome: &str`
23865 // through it collapses onto one canonical shape.
23866 assert_eq!(
23867 ManifestError::etiqueta_invalid("MyKeyword", "r"),
23868 ManifestError::etiqueta_invalid(&owned, "r"),
23869 );
23870 }
23871
23872 // ── `manifest_field_only_ctors!` — the paired `{ <field>: String }`
23873 // single-slot envelope on `ManifestError`, direct sibling of the
23874 // peer [`crate::aplicacao::aplicacao_caixa_only_ctors!`] (d9f6867,
23875 // `{ caixa: String }` on `AplicacaoError`) and
23876 // [`crate::aplicacao::aplicacao_path_only_ctors!`] (3ba8de6,
23877 // `{ path: String }` on `AplicacaoError`) on the M3 mesh envelope,
23878 // of the peer [`crate::supervisor::supervisor_caixa_only_ctors!`]
23879 // (db09650, `{ caixa: String }` on `SupervisorError`), and of the
23880 // peer [`crate::dep::dep_nome_only_ctors!`] (792aa92,
23881 // `{ nome: String }` on `DepError`) folds on their sibling
23882 // envelopes. Two-variant lift closing the last two open-coded
23883 // single-`String`-slot ctor sites at
23884 // [`Caixa::validate_etiquetas`] and [`Caixa::validate_autores`].
23885
23886 #[test]
23887 fn etiqueta_duplicate_ctor_matches_struct_literal_wrap() {
23888 assert_eq!(
23889 ManifestError::etiqueta_duplicate("mesh"),
23890 ManifestError::EtiquetaDuplicate {
23891 etiqueta: "mesh".to_string(),
23892 },
23893 "generated etiqueta_duplicate ctor must produce byte-equal \
23894 `ManifestError::EtiquetaDuplicate` to the pre-lift \
23895 struct-literal wrap on the same `&str` fixture",
23896 );
23897 }
23898
23899 #[test]
23900 fn autor_duplicate_ctor_matches_struct_literal_wrap() {
23901 assert_eq!(
23902 ManifestError::autor_duplicate("pleme-io"),
23903 ManifestError::AutorDuplicate {
23904 autor: "pleme-io".to_string(),
23905 },
23906 "generated autor_duplicate ctor must produce byte-equal \
23907 `ManifestError::AutorDuplicate` to the pre-lift \
23908 struct-literal wrap on the same `&str` fixture",
23909 );
23910 }
23911
23912 #[test]
23913 fn manifest_field_only_ctors_route_field_through_to_string() {
23914 // Cross-axis pin: sweep the sole constructor input axis
23915 // (`<field>: &str`) through a non-default fixture value against
23916 // every generated arm in the [`manifest_field_only_ctors!`]
23917 // macro, so any wrapper-side lowercase / trim / truncate / silent
23918 // constant-substitution on the `<field>.to_string()` sole-field
23919 // construction surfaces here rather than at a downstream
23920 // diagnostic-shape mismatch. Peer of the sibling
23921 // [`crate::aplicacao::tests::aplicacao_caixa_only_ctors_route_caixa_through_to_string`]
23922 // (d9f6867) and
23923 // [`crate::aplicacao::tests::aplicacao_path_only_ctors_route_path_through_to_string`]
23924 // (3ba8de6) cross-axis pins on the peer `AplicacaoError`
23925 // single-`String`-slot envelopes.
23926 let value = "cache-v2";
23927 assert_eq!(
23928 ManifestError::etiqueta_duplicate(value),
23929 ManifestError::EtiquetaDuplicate {
23930 etiqueta: value.to_string(),
23931 },
23932 );
23933 assert_eq!(
23934 ManifestError::autor_duplicate(value),
23935 ManifestError::AutorDuplicate {
23936 autor: value.to_string(),
23937 },
23938 );
23939 }
23940
23941 #[test]
23942 fn manifest_field_only_ctors_accept_both_str_and_string_slice_iters() {
23943 // The two wire-up sites at [`Caixa::validate_etiquetas`] and
23944 // [`Caixa::validate_autores`] each thread a `&String` loop head
23945 // through the ctor via Deref coercion at the `<field>: &str`
23946 // parameter — this pin locks that call shape's byte-equality
23947 // against the direct `&str` shape so a future rebrand of the
23948 // `:etiquetas` / `:autores` slice-iterator type that silently
23949 // broke the Deref-coercion path surfaces here rather than at a
23950 // recompile-time type-mismatch far from the ctor family. Peer of
23951 // the sibling
23952 // [`manifest_field_reason_ctors_accept_both_str_and_string_slice_iters`]
23953 // pin on the peer two-slot `{ <field>: String, reason: String }`
23954 // envelope.
23955 let etiqueta: String = String::from("mesh");
23956 assert_eq!(
23957 ManifestError::etiqueta_duplicate("mesh"),
23958 ManifestError::etiqueta_duplicate(&etiqueta),
23959 );
23960 let autor: String = String::from("pleme-io");
23961 assert_eq!(
23962 ManifestError::autor_duplicate("pleme-io"),
23963 ManifestError::autor_duplicate(&autor),
23964 );
23965 }
23966}