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 fn licenca(&self) -> Option<&str> {
513 self.licenca.as_deref()
514 }
515
516 /// Substrate-canonical per-`Caixa` `:repositorio` git-repo-URL scalar
517 /// accessor every consumer of the top-level manifest's homepage /
518 /// source-of-truth axis keys off — returns the author-declared
519 /// `:repositorio` byte-string verbatim as an `Option<&str>`, borrowed
520 /// from the typed slot's own `Option<String>` storage. `None` when
521 /// the slot is absent (the canonical "omit to defer to the renderer's
522 /// per-target placeholder" shape — [`caixa-helm`]'s `ChartYaml.home`
523 /// carries the `Option<String>` through verbatim so an author-omitted
524 /// `:repositorio` renders a `Chart.yaml` without a `home:` field
525 /// (`skip_serializing_if = "Option::is_none"`), while [`caixa-flux`]'s
526 /// `ClusterBundleOpts::for_caixa` folds the omitted slot through a
527 /// `format!("https://github.com/{DEFAULT_PLEME_GIT_ORG}/{nome}")`
528 /// fallback derived from `caixa.nome`).
529 ///
530 /// The `:repositorio` slot carries the universal-axis git-repo-URL
531 /// homepage identifier every kind of caixa emits under (CAIXA-SDLC
532 /// §I — the author-facing surface every `defcaixa` form supplies) —
533 /// the typed slot's `Option<String>` accept-set (empty-string
534 /// rejected through [`ManifestError::RepositorioEmpty`], git-repo-URL-
535 /// shape-invalid rejected through [`ManifestError::RepositorioInvalid`]
536 /// past the shared [`crate::render::is_git_repo_url`] predicate the
537 /// peer per-`:deps :fonte :repo` axis also routes through) maps onto
538 /// four load-bearing downstream consumers:
539 ///
540 /// - [`Self::validate_repositorio`]'s empty-arm + shape-predicate
541 /// gate binding at caixa-core/src/manifest.rs:1456 — the
542 /// universal-axis identity gate wired at caixa-build time.
543 /// - [`caixa-helm`]'s `build_chart_yaml` `ChartYaml.home` fold at
544 /// caixa-helm/src/lib.rs:840 — the rendered `lareira-<nome>`
545 /// Helm chart's `Chart.yaml` `home:` field, which every registry
546 /// that ingests the chart (ArtifactHub, chartmuseum,
547 /// `helm search repo`) surfaces as the chart's canonical source-
548 /// of-truth link.
549 /// - [`caixa-helm`]'s `build_readme` `## Source` fold at
550 /// caixa-helm/src/lib.rs:957 — the rendered `lareira-<nome>`
551 /// chart's `README.md` header link back to the source repo,
552 /// which every author who inspects the rendered chart bundle
553 /// lands at.
554 /// - [`caixa-flux`]'s `ClusterBundleOpts::for_caixa`
555 /// `GitRepository.spec.url` fold at caixa-flux/src/lib.rs:2006 —
556 /// the rendered `GitRepository` CR's `spec.url` field, which
557 /// FluxCD's `source-controller` polls to reconcile the caixa's
558 /// manifest bundle from git.
559 ///
560 /// Prior to this lift the `.repositorio` field was accessed inline
561 /// at four production sites — [`Self::validate_repositorio`]'s
562 /// `self.repositorio.as_deref()` empty-and-shape gate binding, the
563 /// caixa-helm `build_chart_yaml` `caixa.repositorio.clone()`
564 /// `Chart.yaml` `home:` field fold, the caixa-helm `build_readme`
565 /// `caixa.repositorio.clone().unwrap_or_else(|| caixa.nome.clone())`
566 /// `README.md` `## Source` fold, and the caixa-flux
567 /// `ClusterBundleOpts::for_caixa`
568 /// `caixa.repositorio.clone().unwrap_or_else(|| format!(...))`
569 /// `GitRepository.spec.url` fold — four open-coded field-accesses
570 /// that expressed no compile-time link back to the typed slot. A
571 /// future extension of the `:repositorio` axis to a richer author
572 /// surface — a per-`:repositorio` structured
573 /// [`crate::render::GitRepoUrl`]-shaped scheme+host+path parse
574 /// (the future tightening [`Self::validate_repositorio`]'s
575 /// docstring anticipates alongside the peer per-`:deps :fonte
576 /// :repo` axis), a per-cluster repo-mirror overlay the M4 CR
577 /// materializer resolves per-CR (the "cluster policy rewrites
578 /// `github:pleme-io/...` to `git.internal/mirror/pleme-io/...`"
579 /// arm the private-registry story acknowledges), a promotion of
580 /// the plain `Option<String>` byte-string to a richer
581 /// `RepoUrl` enum discriminated on scheme — would have had to be
582 /// threaded through all four open-coded copies in lockstep or the
583 /// validate gate and the three emit paths would silently disagree
584 /// on which URL a given [`Caixa`] resolves to (an author's
585 /// `:repositorio "github:pleme-io/checkout"` would satisfy validate
586 /// while one of the emit paths silently rendered a stale URL, or
587 /// vice versa). Lifting the resolution to a typed method on the
588 /// substrate primitive means every downstream consumer of the
589 /// caixa's per-`Caixa` repo-URL surface reaches for exactly one
590 /// typed dispatch — the resolver's accept-set migrates as a unit on
591 /// any future axis addition.
592 ///
593 /// Second outer top-level [`Caixa`] `Option<&str>`-return scalar
594 /// accessor — sibling of [`Self::licenca`] (6d5bc28), the accessor
595 /// that opened the "outer [`Caixa`] `Option<&str>` scalar"
596 /// projection pattern this lift folds on. Same "one typed dispatch
597 /// on the substrate primitive, thin projections at each consumer"
598 /// discipline the peer per-`:placement`
599 /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
600 /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
601 /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
602 /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
603 /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
604 /// `Option<&str>`-return accessors carry on the sibling M2 / M3
605 /// typed-slot atom axes, extended here to the second outer top-level
606 /// `Caixa` universal-axis surface. Named `repositorio()` to match
607 /// the storage field's name; the accessor's identity maps onto the
608 /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
609 /// carries.
610 #[must_use]
611 pub fn repositorio(&self) -> Option<&str> {
612 self.repositorio.as_deref()
613 }
614
615 /// Substrate-canonical per-`Caixa` **resolved-git-repo-URL** composer —
616 /// returns the caixa's canonical git-source-of-truth URL as an owned
617 /// [`String`], author-declared `:repositorio` byte-string verbatim on
618 /// the `Some` arm and the substrate's canonical pleme-org github URL
619 /// fallback ([`crate::DEFAULT_PLEME_GIT_ORG`] and [`Self::nome`]
620 /// interpolated into `https://github.com/<org>/<nome>`) on the
621 /// `None` arm. Every substrate-side consumer that resolves
622 /// "which git URL does this caixa's source live at?" reaches for
623 /// exactly one typed dispatch on the substrate primitive — the raw
624 /// `caixa.repositorio().map(str::to_owned).unwrap_or_else(|| format!(
625 /// "https://github.com/{org}/{nome}", org = DEFAULT_PLEME_GIT_ORG,
626 /// nome = caixa.nome()))` open-coded composition every prior caller
627 /// re-derived collapses onto one canonical arm.
628 ///
629 /// Distinct from [`Self::repositorio`] (`Option<&str>`, exposes the
630 /// author-omitted / author-declared partition to the caller) — this
631 /// accessor is the **resolved** URL surface, folding the fallback in
632 /// at the substrate-primitive boundary. Every consumer that keys off
633 /// the `Option::is_none()` discriminator (a [`Chart.yaml`] `home:`
634 /// field emit that must omit the field entirely on an author-omitted
635 /// `:repositorio`, per the [`Self::repositorio`] docstring's
636 /// documented four-consumer list) reaches through the raw
637 /// [`Self::repositorio`] `Option<&str>` accessor by construction — the
638 /// resolved-URL composer sits alongside it as the second projection
639 /// on the same underlying `:repositorio` slot rather than replacing
640 /// the raw accessor.
641 ///
642 /// The fallback branch is the exact byte-image of the prior inline
643 /// [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_url` composer at
644 /// caixa-flux/src/lib.rs:2080 — pinned by the sibling caixa-flux
645 /// byte-parity test
646 /// `cluster_bundle_opts_for_caixa_git_url_routes_through_canonical_git_url_accessor`
647 /// against a future implementation of this method that reordered the
648 /// `format!` template arguments, migrated the `<org>` segment to a
649 /// different constant (the [`crate::DEFAULT_PLEME_GIT_ORG`] axis a
650 /// future substrate-side git-org migration may split off), or
651 /// silently absorbed the empty-string arm (a hypothetical
652 /// `Some("") → fallback` collapse the raw [`Self::repositorio`]
653 /// accessor's docstring explicitly rejects on the sibling raw
654 /// accessor).
655 ///
656 /// Peer of the sibling per-`&Caixa`-axis composed helpers
657 /// [`caixa-flux::cluster_bundle_for_caixa`] (06d52d7) on the sibling
658 /// substrate-side renderer surface — same "close the composed
659 /// substrate-primitive at one canonical arm on the single-`&Caixa`
660 /// dispatch, converge every prior open-coded caller onto the arm"
661 /// discipline extended onto the resolved-git-URL projection of the
662 /// per-`Caixa` `:repositorio` axis. Owns per-call [`String`]
663 /// allocation on both arms (the `Some` arm's `str::to_owned` and the
664 /// `None` arm's `format!`) — the by-value return matches every
665 /// downstream consumer's field-fill shape (the caixa-flux
666 /// `ClusterBundleOpts::git_url: String` field, every future
667 /// `Chart.yaml` `home:` fold's `Option<String>` field-fill on the
668 /// `Some` arm).
669 #[must_use]
670 pub fn canonical_git_url(&self) -> String {
671 self.repositorio().map_or_else(
672 || {
673 format!(
674 "https://github.com/{org}/{nome}",
675 org = crate::DEFAULT_PLEME_GIT_ORG,
676 nome = self.nome(),
677 )
678 },
679 str::to_owned,
680 )
681 }
682
683 /// Substrate-canonical per-`Caixa` **resolved-publish-tag** composer —
684 /// returns the caixa's canonical Zig-style git-publish-tag as an owned
685 /// [`String`], derived by concatenating
686 /// [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] with the typed
687 /// [`Self::versao`] byte-string on a single `format!` template.
688 /// Every substrate-side consumer that resolves "which git tag does this
689 /// caixa publish under?" reaches for exactly one typed dispatch on the
690 /// substrate primitive — the raw `format!("{prefix}{versao}", prefix =
691 /// caixa_core::DEFAULT_PUBLISH_TAG_PREFIX, versao = caixa.versao())`
692 /// open-coded composition every prior caller re-derived collapses onto
693 /// one canonical arm.
694 ///
695 /// Peer of the sibling [`Self::canonical_git_url`] (124f864) resolved-
696 /// git-URL composer on the paired per-`Caixa` git-remote axis — same
697 /// "close the composed substrate-primitive at one canonical arm on the
698 /// single-`&Caixa` dispatch, converge every prior open-coded caller
699 /// onto the arm" discipline extended from the resolved-URL projection
700 /// of the per-`Caixa` `:repositorio` axis onto the resolved-tag
701 /// projection of the per-`Caixa` `:versao` axis. The two accessors
702 /// jointly close the pair of scalars every `FluxCD` `GitRepository` CR
703 /// keys off (`spec.url` via [`Self::canonical_git_url`],
704 /// `spec.ref.tag` via [`Self::publish_tag`]) at the substrate primitive
705 /// — a downstream consumer that reaches through both accessors reads
706 /// the complete published-git-identity of a caixa through two typed
707 /// dispatches, not four open-coded field accesses.
708 ///
709 /// The reader-side (`caixa-flux::cluster_bundle` /
710 /// `ClusterBundleOpts::for_caixa`'s `git_ref` field, every future
711 /// per-cluster snapshot bundle emitter, the future M4
712 /// `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer's tag-carrier
713 /// slot on the tatara `Process` intent) always resolves the tag under
714 /// the canonical [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] prefix — this
715 /// method encodes that reader-side convention. The writer-side
716 /// (`caixa-feira`'s `feira publish` `--prefix` clap flag) allows the
717 /// operator to override the prefix at publish time; the two surfaces
718 /// intentionally sit on the "canonical default + operator override"
719 /// pair the sibling [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] constant's
720 /// own docstring documents — a `feira publish --prefix release/`
721 /// override is the operator's explicit opt-out from the substrate
722 /// default, not a supported drift axis.
723 ///
724 /// The composition body is the exact byte-image of the prior inline
725 /// [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_ref` composer at
726 /// caixa-flux/src/lib.rs:2105 — pinned by the sibling caixa-flux
727 /// byte-parity test
728 /// `cluster_bundle_opts_for_caixa_git_ref_routes_through_publish_tag_accessor`
729 /// against a future implementation of this method that reordered the
730 /// `format!` template arguments, migrated the `<prefix>` segment to a
731 /// different constant (the [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] axis
732 /// a future Zig-style-tag rebrand may split off — the constant's own
733 /// docstring anticipates a substrate-side move to `release/<versao>`
734 /// or bare `<versao>` shapes once a sibling forge convention adopts a
735 /// slash-namespaced or bare-scalar form), interposed a canonicalization
736 /// pass on the `:versao` axis (a SemVer-2 build-metadata strip an OCI-
737 /// tag normalizer might apply once the M4 registry-alignment slot
738 /// lands), or silently absorbed an empty `:versao` arm (which cannot
739 /// occur past the [`Self::validate_versao`] gate but which a
740 /// hypothetical bypass on the accessor path must not silently paper
741 /// over).
742 ///
743 /// Owns per-call [`String`] allocation via the single `format!`
744 /// invocation — the by-value return matches every downstream
745 /// consumer's field-fill shape (the caixa-flux `GitRefSpec::Tag(String)`
746 /// variant's owned payload, every future `intent.aplicacao.tag: String`
747 /// field-fill on the M4 CR materializer's tag-carrier slot).
748 #[must_use]
749 pub fn publish_tag(&self) -> String {
750 format!(
751 "{prefix}{versao}",
752 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
753 versao = self.versao(),
754 )
755 }
756
757 /// Substrate-canonical per-`Caixa` **resolved-Helm-chart-name** composer
758 /// — returns the caixa's canonical `lareira-<nome>` per-Servico Helm
759 /// chart identity as an owned [`String`], derived by dispatching through
760 /// the substrate-canonical [`crate::lareira_chart_name`] helper against
761 /// the typed [`Self::nome`] byte-string. Every substrate-side consumer
762 /// that resolves "which Helm chart identity does this caixa render
763 /// under?" reaches for exactly one typed dispatch on the substrate
764 /// primitive — the raw `caixa_core::lareira_chart_name(caixa.nome())`
765 /// two-step compose every prior caller re-derived collapses onto one
766 /// canonical arm on the single-`&Caixa` dispatch.
767 ///
768 /// Peer of the sibling [`Self::canonical_git_url`] (124f864) resolved-
769 /// git-URL composer + [`Self::publish_tag`] (07e05b8) resolved-publish-
770 /// tag composer on the paired per-`Caixa` published-artifact-identity
771 /// axis — same "close the composed substrate-primitive at one canonical
772 /// arm on the single-`&Caixa` dispatch, converge every prior open-coded
773 /// caller onto the arm" discipline extended from the resolved-URL /
774 /// resolved-tag projections of the `:repositorio` / `:versao` axes onto
775 /// the resolved-chart-name projection of the `:nome` axis. The three
776 /// accessors jointly close the triple of scalars every per-Servico
777 /// deploy artifact keys off (git source URL via
778 /// [`Self::canonical_git_url`], git source tag via
779 /// [`Self::publish_tag`], per-Servico Helm chart identity via
780 /// [`Self::lareira_chart_name`]) at the substrate primitive — a
781 /// downstream consumer that reaches through all three reads the
782 /// complete deploy-artifact identity of a caixa through three typed
783 /// dispatches, not six open-coded compositions across three renderer
784 /// crates.
785 ///
786 /// The reader-side (three production sites at the time of the lift —
787 /// [`caixa-helm::render_chart_for_servico_with`]'s `ChartDir.name`
788 /// composer at caixa-helm/src/lib.rs:778, the peer
789 /// [`caixa-flux::cluster_bundle`]'s per-CR `chart_name` binding at
790 /// caixa-flux/src/lib.rs:2219, and
791 /// [`caixa-tatara::process_for_aplicacao`]'s `release_name`
792 /// composer at caixa-tatara/src/lib.rs:227, plus every future
793 /// per-Servico OCI publish emitter the CAIXA-SDLC §II
794 /// `caixa-publish.yml` reusable workflow's `skopeo push` step keys
795 /// off, the future per-cluster snapshot bundle emitter, the future
796 /// M4 `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer's
797 /// per-member chart-carrier slot on the tatara `Process` intent) —
798 /// always resolves the chart name under the canonical
799 /// [`crate::LAREIRA_CHART_NAME_PREFIX`] prefix; this method encodes
800 /// that reader-side convention. The joint-length invariant the peer
801 /// [`Self::validate_nome_chart_name_budget`] gate enforces at
802 /// caixa-build time (author-declared `:nome` + fixed prefix ≤
803 /// [`crate::DNS_1123_LABEL_MAX_LEN`]) is verified on the input to
804 /// this composer by construction, so the produced `lareira-<nome>`
805 /// string is a valid Helm chart-name segment on every accept-set
806 /// input.
807 ///
808 /// The composition body is the exact byte-image of the prior inline
809 /// `caixa_core::lareira_chart_name(caixa.nome())` two-step form every
810 /// prior caller re-derived — pinned by the sibling caixa-helm /
811 /// caixa-flux / caixa-tatara byte-parity tests
812 /// `<crate>_lareira_chart_name_routes_through_caixa_accessor` against
813 /// a future implementation of this method that reordered the
814 /// composition arguments, migrated the `<prefix>` segment to a
815 /// different constant (the [`crate::LAREIRA_CHART_NAME_PREFIX`] axis a
816 /// future substrate-side chart-family rebrand may split off — the
817 /// constant's own docstring anticipates a substrate-side move once
818 /// the `lareira-` scoping intent outlives the family it names),
819 /// interposed a canonicalization pass on the `:nome` axis (a per-
820 /// registry namespace-qualification an M4 CR materializer might apply
821 /// per-CR — the "`pleme-io/checkout` vs `partner-org/checkout`
822 /// collision" arm the multi-tenant-registry story acknowledges), or
823 /// silently absorbed an empty `:nome` arm (which cannot occur past
824 /// the [`Self::validate_nome`] gate but which a hypothetical bypass
825 /// on the accessor path must not silently paper over).
826 ///
827 /// Owns per-call [`String`] allocation via the single
828 /// [`crate::lareira_chart_name`] `format!` invocation — the by-value
829 /// return matches every downstream consumer's field-fill shape (the
830 /// caixa-helm `ChartDir.name: String` field, the caixa-flux per-CR
831 /// `chart_name: String` binding, the caixa-tatara
832 /// `AplicacaoIntent.release_name: Option<String>` field-fill on the
833 /// `Some` arm).
834 #[must_use]
835 pub fn lareira_chart_name(&self) -> String {
836 crate::lareira_chart_name(self.nome())
837 }
838
839 /// Substrate-canonical per-`Caixa` **resolved-OCI-chart-ref** composer
840 /// — returns the caixa's canonical `oci://<registry>/lareira-<nome>`
841 /// per-Servico Helm chart OCI artifact reference as an owned
842 /// [`String`], derived by dispatching through the substrate-canonical
843 /// [`crate::oci_chart_ref`] helper (which itself composes
844 /// [`crate::OCI_SCHEME_PREFIX`] + the caller-supplied `registry` +
845 /// [`crate::lareira_chart_name`]-of-[`Self::nome`]) against the
846 /// caller-supplied `registry` and the typed [`Self::nome`] byte-string.
847 /// Every substrate-side consumer that resolves "which OCI chart
848 /// artifact does this caixa publish under, in this registry?" reaches
849 /// for exactly one typed dispatch on the substrate primitive — the raw
850 /// `caixa_core::oci_chart_ref(registry, caixa.nome())` two-step compose
851 /// every prior caller re-derived collapses onto one canonical arm on
852 /// the single-`(&Caixa, &str)` dispatch.
853 ///
854 /// Fourth member of the paired per-`Caixa` published-artifact-identity
855 /// axis alongside [`Self::canonical_git_url`] (124f864) /
856 /// [`Self::publish_tag`] (07e05b8) / [`Self::lareira_chart_name`]
857 /// (a8f0bee) — same "close the composed substrate-primitive at one
858 /// canonical arm on the single-`&Caixa` dispatch, converge every
859 /// prior open-coded caller onto the arm" discipline extended from the
860 /// resolved-URL / resolved-tag / resolved-chart-name projections of
861 /// the `:repositorio` / `:versao` / `:nome` axes onto the resolved-
862 /// OCI-ref projection over the paired `(registry, :nome)` inputs. The
863 /// four accessors jointly close the per-`Caixa` published-artifact-
864 /// identity surface every downstream consumer of a caixa's published
865 /// deploy artifacts keys off (git source URL via
866 /// [`Self::canonical_git_url`], git source tag via
867 /// [`Self::publish_tag`], per-Servico Helm chart identity via
868 /// [`Self::lareira_chart_name`], per-registry OCI chart artifact
869 /// reference via [`Self::oci_chart_ref`]) at the substrate primitive
870 /// — a downstream consumer that reaches through all four reads the
871 /// complete deploy-artifact identity of a caixa through four typed
872 /// dispatches, not eight open-coded compositions across four renderer
873 /// crates. The unique-signature dispatch (`(&Caixa, &str)` on this
874 /// method vs. `&Caixa` on the sibling three) reflects the extra input
875 /// axis this composer folds in: unlike the git-URL / git-tag / chart-
876 /// name axes (each derived purely from a `&Caixa`), the OCI-ref axis
877 /// pairs the caixa's per-`:nome` chart identity with the caller-
878 /// supplied per-registry authority segment, so the accessor threads
879 /// the registry byte-string through as a positional `&str`.
880 ///
881 /// The reader-side (one production site at the time of the lift —
882 /// [`caixa-tatara::process_for_aplicacao`]'s `derive_chart_ref` helper
883 /// at caixa-tatara/src/lib.rs:333 that composes the emitted
884 /// `AplicacaoIntent.chart_ref` scalar the tatara-reconciler feeds into
885 /// `helm install`, plus every future per-Servico OCI publish emitter
886 /// the CAIXA-SDLC §II `caixa-publish.yml` reusable workflow's
887 /// `skopeo push` step keys off, the future per-cluster snapshot bundle
888 /// emitter's per-CR `oci://…` field-fill on the M4 registry-alignment
889 /// slot, the future M4 `mesh.pleme.io/v1alpha1/Aplicacao` CR
890 /// materializer's per-member `chart_ref` slot on the tatara `Process`
891 /// intent, the `FluxCD` `HelmRelease` `spec.chart.spec.chart` field-fill
892 /// on the OCI-source path an M4 per-cluster registry-rewrite overlay
893 /// applies per-CR) — always resolves the OCI ref under the canonical
894 /// [`crate::OCI_SCHEME_PREFIX`] scheme prefix + the canonical
895 /// [`Self::lareira_chart_name`] chart-name segment; this method
896 /// encodes that reader-side convention.
897 ///
898 /// The composition body is the exact byte-image of the prior inline
899 /// `caixa_core::oci_chart_ref(registry, caixa.nome())` two-step form
900 /// every prior caller re-derived — pinned by the sibling caixa-tatara
901 /// byte-parity test
902 /// `derive_chart_ref_routes_through_caixa_oci_chart_ref_accessor`
903 /// against a future implementation of this method that reordered the
904 /// composition arguments, migrated the `<scheme>` segment to a
905 /// different constant (the [`crate::OCI_SCHEME_PREFIX`] axis a future
906 /// substrate-side registry-protocol rebrand may split off — the
907 /// constant's own docstring anticipates a substrate-side move once
908 /// Helm 3 / `FluxCD` introduce a successor scheme past `oci://`),
909 /// migrated the `<chart>` segment off the paired
910 /// [`crate::lareira_chart_name`] composer (a per-registry
911 /// namespace-qualification an M4 CR materializer might apply per-CR),
912 /// interposed a canonicalization pass on the `registry` axis (an OCI-
913 /// authority normalization once the M4 registry-alignment slot lands),
914 /// or silently absorbed an empty `:nome` arm (which cannot occur past
915 /// the [`Self::validate_nome`] gate but which a hypothetical bypass
916 /// on the accessor path must not silently paper over).
917 ///
918 /// Owns per-call [`String`] allocation via the single
919 /// [`crate::oci_chart_ref`] `format!` invocation — the by-value return
920 /// matches every downstream consumer's field-fill shape (the caixa-
921 /// tatara `AplicacaoIntent.chart_ref: String` field-fill, every
922 /// future `intent.aplicacao.chart_ref: String` field-fill on the M4
923 /// CR materializer's chart-ref-carrier slot, every future
924 /// `HelmRelease.spec.chart.spec.chart: String` field-fill on the OCI-
925 /// source path).
926 #[must_use]
927 pub fn oci_chart_ref(&self, registry: &str) -> String {
928 crate::oci_chart_ref(registry, self.nome())
929 }
930
931 /// Substrate-canonical per-`Caixa` `:descricao` free-form-prose
932 /// chart-description scalar accessor every consumer of the top-level
933 /// manifest's Chart.yaml `description:` axis keys off — returns the
934 /// author-declared `:descricao` byte-string verbatim as an
935 /// `Option<&str>`, borrowed from the typed slot's own
936 /// `Option<String>` storage. `None` when the slot is absent (the
937 /// canonical "omit to defer to the per-renderer `caixa.nome`-derived
938 /// fallback" shape — [`caixa-helm`]'s `build_chart_yaml` folds the
939 /// omitted slot through a `format!("Generated chart for caixa Servico
940 /// {}", caixa.nome)` fallback, [`caixa-helm`]'s `build_readme` folds
941 /// it through a `format!("caixa Servico {}", caixa.nome)` fallback,
942 /// and [`caixa-feira`]'s `render_flake` folds it through a
943 /// `format!("caixa {}", c.nome)` `flake.nix` `description = ""`
944 /// fallback — each derived from `caixa.nome` on the null-carrier arm).
945 ///
946 /// The `:descricao` slot carries the universal-axis free-form-prose
947 /// chart-description identifier every kind of caixa emits under
948 /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa` form
949 /// supplies) — the typed slot's `Option<String>` accept-set
950 /// (empty-string rejected through [`ManifestError::DescricaoEmpty`],
951 /// chart-description-shape-invalid rejected through
952 /// [`ManifestError::DescricaoInvalid`] past the shared
953 /// [`crate::render::is_chart_description_shape`] predicate the peer
954 /// per-`Caixa` `:descricao` axis also routes through) maps onto four
955 /// load-bearing downstream consumers:
956 ///
957 /// - [`Self::validate_descricao`]'s empty-arm + shape-predicate
958 /// gate binding — the universal-axis identity gate wired at
959 /// caixa-build time.
960 /// - [`caixa-helm`]'s `build_chart_yaml` `ChartYaml.description`
961 /// `Chart.yaml` field fold — the rendered `lareira-<nome>` Helm
962 /// chart's `Chart.yaml` `description:` field, which
963 /// `apiVersion: v2` charts require non-empty (`helm lint` fires
964 /// `WARNING [chart.metadata.description]: description is required`
965 /// when absent) and which every registry that ingests the chart
966 /// (ArtifactHub, chartmuseum, `helm search repo`) surfaces as the
967 /// chart's canonical one-line prose descriptor.
968 /// - [`caixa-helm`]'s `build_readme` chart-`README.md` header fold
969 /// — the rendered `lareira-<nome>` chart's `README.md` prose
970 /// header directly beneath the `# <chart-name>` title, which
971 /// every author who inspects the rendered chart bundle lands at.
972 /// - [`caixa-feira`]'s `render_flake` `flake.nix` `description = ""`
973 /// top-level fold — the emitted `flake.nix`'s `description`
974 /// field, which every Nix consumer (`nix flake show`,
975 /// `nix flake metadata`, downstream flake-registry ingestors)
976 /// surfaces as the flake's canonical descriptor.
977 ///
978 /// Prior to this lift the `.descricao` field was accessed inline at
979 /// four production sites — [`Self::validate_descricao`]'s
980 /// `self.descricao.as_deref()` empty-and-shape gate binding, the
981 /// caixa-helm `build_chart_yaml`
982 /// `caixa.descricao.clone().unwrap_or_else(|| format!(...))`
983 /// `Chart.yaml` `description:` fold, the caixa-helm `build_readme`
984 /// `caixa.descricao.clone().unwrap_or_else(|| format!(...))`
985 /// `README.md` header fold, and the caixa-feira `render_flake`
986 /// `c.descricao.clone().unwrap_or_else(|| format!(...))` `flake.nix`
987 /// `description = ""` fold — four open-coded field-accesses that
988 /// expressed no compile-time link back to the typed slot. A future
989 /// extension of the `:descricao` axis to a richer author surface —
990 /// a per-`:descricao` locale-tagged multi-language descriptor map
991 /// (the "one caixa, N language-tagged prose descriptions" arm
992 /// author-tooling internationalization anticipates), a
993 /// per-registry-target length-and-shape overlay the M4 CR
994 /// materializer resolves per-CR (the "ArtifactHub caps description
995 /// at 512 bytes but the internal registry caps at 256" arm), a
996 /// promotion of the plain `Option<String>` byte-string to a richer
997 /// `ChartDescription` newtype guaranteeing the
998 /// `is_chart_description_shape` predicate at the type level — would
999 /// have had to be threaded through all four open-coded copies in
1000 /// lockstep or the validate gate and the three emit paths would
1001 /// silently disagree on which prose string a given [`Caixa`]
1002 /// resolves to (an author's
1003 /// `:descricao "Checkout flow orchestration."` would satisfy
1004 /// validate while one of the emit paths silently rendered a stale
1005 /// `caixa.nome`-derived fallback, or vice versa). Lifting the
1006 /// resolution to a typed method on the substrate primitive means
1007 /// every downstream consumer of the caixa's per-`Caixa`
1008 /// chart-description surface reaches for exactly one typed dispatch
1009 /// — the resolver's accept-set migrates as a unit on any future
1010 /// axis addition.
1011 ///
1012 /// Third outer top-level [`Caixa`] `Option<&str>`-return scalar
1013 /// accessor — sibling of [`Self::licenca`] (6d5bc28) and
1014 /// [`Self::repositorio`] (cc7332d), the accessors that opened the
1015 /// "outer [`Caixa`] `Option<&str>` scalar" projection pattern this
1016 /// lift folds on. Same "one typed dispatch on the substrate
1017 /// primitive, thin projections at each consumer" discipline the
1018 /// peer per-`:placement`
1019 /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
1020 /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
1021 /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
1022 /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
1023 /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
1024 /// `Option<&str>`-return accessors carry on the sibling M2 / M3
1025 /// typed-slot atom axes, extended here to the third outer top-level
1026 /// `Caixa` universal-axis surface. Named `descricao()` to match the
1027 /// storage field's name; the accessor's identity maps onto the
1028 /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
1029 /// carries. The one remaining universal `Option<String>` slot
1030 /// (`:edicao`) folds on this pattern next.
1031 #[must_use]
1032 pub fn descricao(&self) -> Option<&str> {
1033 self.descricao.as_deref()
1034 }
1035
1036 /// Substrate-canonical per-`Caixa` `:edicao` language-edition scalar
1037 /// accessor every consumer of the top-level manifest's tatara-lisp
1038 /// edition-selector axis keys off — returns the author-declared
1039 /// `:edicao` byte-string verbatim as an `Option<&str>`, borrowed from
1040 /// the typed slot's own `Option<String>` storage. `None` when the
1041 /// slot is absent (the canonical "omit the slot to defer to the
1042 /// substrate's default edition" shape every existing
1043 /// [`caixa-resolver`] integration test fixture carries via
1044 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`;
1045 /// the peer [`Self::validate_edicao`] gate is a no-op on the omitted
1046 /// arm by construction, so an author-omitted `:edicao` round-trips
1047 /// to a build without triggering the year-shape predicate).
1048 ///
1049 /// The `:edicao` slot carries the universal-axis 4-digit-ASCII-
1050 /// decimal-year language-edition identifier every kind of caixa
1051 /// emits under (CAIXA-SDLC §I — the author-facing surface every
1052 /// `defcaixa` form supplies) — the typed slot's `Option<String>`
1053 /// accept-set (empty-string rejected through
1054 /// [`ManifestError::EdicaoEmpty`], year-shape-invalid rejected
1055 /// through [`ManifestError::EdicaoInvalid`] past the 4-digit-ASCII-
1056 /// decimal-year predicate [`Self::validate_edicao`] enforces) maps
1057 /// onto one load-bearing downstream consumer today
1058 /// ([`Self::validate_edicao`]'s empty-arm + year-shape-predicate
1059 /// gate binding at caixa-core/src/manifest.rs:1959) plus every
1060 /// future edition-aware substrate consumer the CAIXA-SDLC §I
1061 /// roadmap anticipates (the tatara-lisp compiler's macro-surface
1062 /// selector every edition-aware build step keys off, the future
1063 /// per-edition compatibility-flag overlay the M4 CR materializer
1064 /// resolves per-CR, the peer [`Caixa::template`] canonical
1065 /// `:edicao "2026"` scaffold every `feira init` emits verbatim,
1066 /// and the renderer-side fixtures at `caixa-helm/src/lib.rs:978` /
1067 /// `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208` that
1068 /// carry `edicao: Some("2026".into())` by construction).
1069 ///
1070 /// Prior to this lift the `.edicao` field was accessed inline at
1071 /// one production site — [`Self::validate_edicao`]'s
1072 /// `self.edicao.as_deref()` empty-and-shape gate binding — one
1073 /// open-coded field-access that expressed no compile-time link
1074 /// back to the typed slot. A future extension of the `:edicao`
1075 /// axis to a richer author surface — a per-`:edicao` known-
1076 /// edition allowlist (the future tightening
1077 /// [`Self::validate_edicao`]'s docstring acknowledges past the
1078 /// structural year-shape floor, rejecting year-shaped values that
1079 /// don't name a tatara-lisp edition the substrate actually
1080 /// understands — `"1999"` is year-shaped but no `1999` edition
1081 /// exists), a per-edition compatibility-flag overlay the M4 CR
1082 /// materializer resolves per-CR (the "edition `"2026"` enables
1083 /// macro-surface features the sibling `"2018"` gates behind a
1084 /// feature flag" arm the edition-selector story anticipates), a
1085 /// promotion of the plain `Option<String>` byte-string to a
1086 /// richer `CaixaEdition` enum discriminated on year once a sibling
1087 /// edition to `"2026"` lands — would have had to be threaded
1088 /// through the open-coded copy in lockstep with every future
1089 /// edition-aware consumer, or the validate gate and the future
1090 /// edition-aware consumer path would silently disagree on which
1091 /// edition a given [`Caixa`] resolves to (an author's
1092 /// `:edicao "2026"` would satisfy validate while a future
1093 /// edition-aware consumer silently defaulted to a stale edition,
1094 /// or vice versa). Lifting the resolution to a typed method on
1095 /// the substrate primitive means every downstream consumer of the
1096 /// caixa's per-`Caixa` edition surface reaches for exactly one
1097 /// typed dispatch — the resolver's accept-set migrates as a unit
1098 /// on any future axis addition.
1099 ///
1100 /// Fourth and final outer top-level [`Caixa`] `Option<&str>`-return
1101 /// scalar accessor — sibling of [`Self::licenca`] (6d5bc28),
1102 /// [`Self::repositorio`] (cc7332d), and [`Self::descricao`]
1103 /// (3f16e2f), the accessors that opened the "outer [`Caixa`]
1104 /// `Option<&str>` scalar" projection pattern this lift folds on.
1105 /// Same "one typed dispatch on the substrate primitive, thin
1106 /// projections at each consumer" discipline the peer per-`:placement`
1107 /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
1108 /// [`crate::aplicacao::Placement::affinity`] (74ec2d3) /
1109 /// per-`:contratos` [`crate::aplicacao::WitContract::endpoint`]
1110 /// (7020470) / [`crate::aplicacao::WitContract::subject`] (90de675)
1111 /// / [`crate::aplicacao::WitContract::slot`] (ed22b66)
1112 /// `Option<&str>`-return accessors carry on the sibling M2 / M3
1113 /// typed-slot atom axes, extended here to close the outer top-level
1114 /// `Caixa` universal-axis surface's last unlifted `Option<String>`
1115 /// slot. Named `edicao()` to match the storage field's name; the
1116 /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1117 /// vocabulary the slot's docstring already carries.
1118 #[must_use]
1119 pub fn edicao(&self) -> Option<&str> {
1120 self.edicao.as_deref()
1121 }
1122
1123 /// Substrate-canonical per-`Caixa` `:nome` universal-axis DNS-1123-
1124 /// label caixa-identity scalar accessor every consumer of the top-
1125 /// level manifest's identity axis keys off — returns the author-
1126 /// declared `:nome` byte-string verbatim as an `&str`, borrowed from
1127 /// the typed slot's own `String` storage. Non-optional (`:nome` is
1128 /// a required-axis scalar every `defcaixa` form must supply; the
1129 /// [`Self::from_lisp`] derive rejects an omitted / non-string
1130 /// `:nome` at parse time, so a `Caixa` past parse definitionally
1131 /// carries a non-`None` `:nome`).
1132 ///
1133 /// The `:nome` slot carries the universal-axis DNS-1123-label
1134 /// caixa-identity every kind of caixa emits under (CAIXA-SDLC §I —
1135 /// the primary identity axis every `defcaixa` form supplies
1136 /// alongside `:versao` / `:kind`; the substrate-wide identity every
1137 /// other typed surface that names a caixa reaches through — `:deps`
1138 /// entries, `:membros` entries, `:children` entries, the
1139 /// `lareira-<nome>` Helm chart name every per-Servico renderer
1140 /// derives, the `pleme-program-<nome>` label every per-Aplicacao
1141 /// renderer emits) — the typed slot's `String` accept-set (empty
1142 /// rejected through [`ManifestError::NomeEmpty`], DNS-1123-shape-
1143 /// invalid rejected through [`ManifestError::NomeInvalid`] past
1144 /// the shared [`crate::render::require_valid_dns_1123_label`] gate
1145 /// the peer name axes each land on, joint-length-with-`lareira-`-
1146 /// prefix rejected through
1147 /// [`ManifestError::NomeChartNameBudgetExceeded`] past
1148 /// [`crate::render::is_lareira_chart_name_shape`]) maps onto every
1149 /// load-bearing downstream consumer the substrate carries — the
1150 /// two universal-axis validate gates at caixa-build time
1151 /// ([`Self::validate_nome`] + [`Self::validate_nome_chart_name_budget`]),
1152 /// [`crate::lareira_chart_name`]'s `lareira-<nome>` Helm chart-name
1153 /// derivation every per-Servico renderer keys off, the caixa-helm
1154 /// `Chart.yaml`'s `name:` axis, caixa-flux's `programs.yaml` entry
1155 /// `name:` axis, caixa-mesh's Cilium `CiliumNetworkPolicy` /
1156 /// `HTTPRoute` per-Aplicacao name axes at
1157 /// caixa-mesh/src/lib.rs:{2650, 2797, 2919, 2925},
1158 /// [`crate::pleme_program_selector`] /
1159 /// [`crate::pleme_program_in_aplicacao_selector`] label-selector
1160 /// derivations, and every future substrate renderer that emits an
1161 /// artifact keyed by the caixa's identity.
1162 ///
1163 /// Prior to this lift the `.nome` field was accessed inline at a
1164 /// dozen production sites across `caixa-core` (the two universal-
1165 /// axis validate gates + [`Dep::validate`]-adjacent duplicate
1166 /// tracking), `caixa-helm` (the `lareira_chart_name` fold, the
1167 /// `ChartYaml.name` / `ChartYaml.description` / `Chart.yaml`
1168 /// `keywords` fallback), `caixa-flux` (the `programs.yaml`
1169 /// entry `name:` fold, the `flux_kustomization_source_subtree`
1170 /// per-cluster subpath derivation), and `caixa-mesh` (the
1171 /// `pleme_program_in_aplicacao_selector` label-selector fold, the
1172 /// `cilium_network_policy_name` / `gateway_api_http_route_name`
1173 /// per-CR name derivations, the `LABEL_APLICACAO` labels-map
1174 /// insert) — a dozen open-coded field-accesses that expressed no
1175 /// compile-time link back to the typed slot. A future extension of
1176 /// the `:nome` axis to a richer author surface — a per-`:nome`
1177 /// structured `CaixaIdentity` newtype that carries the joint-
1178 /// length-with-prefix invariant [`Self::validate_nome_chart_name_budget`]
1179 /// enforces at the type level (rather than as a validate-time
1180 /// gate), a per-registry `:nome` namespacing overlay the M4 CR
1181 /// materializer resolves per-CR (the "`pleme-io/checkout` vs
1182 /// `partner-org/checkout` collision" arm the multi-tenant-registry
1183 /// story acknowledges), a promotion of the plain `String` byte-
1184 /// string to a richer `CaixaNome` newtype discriminated on
1185 /// namespace prefix — would have had to be threaded through every
1186 /// open-coded copy in lockstep or the two validate gates and the
1187 /// dozen emit paths would silently disagree on which identity a
1188 /// given [`Caixa`] resolves to (an author's `:nome "checkout"`
1189 /// would satisfy validate while one of the emit paths silently
1190 /// rendered a drifted other identity, or vice versa). Lifting the
1191 /// resolution to a typed method on the substrate primitive means
1192 /// every downstream consumer of the caixa's per-`Caixa` identity
1193 /// surface reaches for exactly one typed dispatch — the resolver's
1194 /// accept-set migrates as a unit on any future axis addition.
1195 ///
1196 /// First outer top-level [`Caixa`] `&str`-return required-scalar
1197 /// accessor — opens the "outer [`Caixa`] `&str` required-scalar"
1198 /// projection pattern the sibling per-`Caixa` `:versao` future lift
1199 /// folds on. Sibling in shape to the peer per-`:membros`
1200 /// [`crate::aplicacao::Membro::nome`] (4a32abf) / per-`:contratos`
1201 /// [`crate::aplicacao::WitContract::source`] /
1202 /// [`crate::aplicacao::WitContract::destination`] (7f0fd43),
1203 /// [`crate::aplicacao::WitContract::world_ref`] (0804823),
1204 /// [`crate::aplicacao::Membro::versao_requirement`] (a40b0e3),
1205 /// [`crate::aplicacao::Entrada::destination`] (6db982c),
1206 /// [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062),
1207 /// per-sub-struct required-axis accessors carry on the sibling M3
1208 /// mesh-slot-atom scalar-value axes, extended here to open the
1209 /// outer top-level [`Caixa`] `&str`-return required-scalar surface.
1210 /// Named `nome()` to match the storage field's name; the accessor's
1211 /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
1212 /// slot's docstring already carries.
1213 #[must_use]
1214 pub const fn nome(&self) -> &str {
1215 self.nome.as_str()
1216 }
1217
1218 /// Substrate-canonical per-`Caixa` `:versao` universal-axis SemVer-2
1219 /// pinned-version scalar accessor every consumer of the top-level
1220 /// manifest's version axis keys off — returns the author-declared
1221 /// `:versao` byte-string verbatim as an `&str`, borrowed from the
1222 /// typed slot's own `String` storage. Non-optional (`:versao` is a
1223 /// required-axis scalar every `defcaixa` form must supply alongside
1224 /// `:nome` / `:kind`; the [`Self::from_lisp`] derive rejects an
1225 /// omitted / non-string `:versao` at parse time, so a `Caixa` past
1226 /// parse definitionally carries a non-`None` `:versao`).
1227 ///
1228 /// The `:versao` slot carries the universal-axis SemVer-2
1229 /// concrete-version body every kind of caixa emits under
1230 /// (CAIXA-SDLC §I — the required-scalar every `defcaixa` form
1231 /// supplies alongside `:nome` / `:kind`; the substrate-wide
1232 /// pinned-version every downstream artifact-emitting consumer
1233 /// composes under — the `lareira-<nome>` Helm chart's `Chart.yaml`
1234 /// `version:` + `appVersion:` axes, the `feira publish` Zig-style
1235 /// `v<versao>` git tag the [`crate::DEFAULT_PUBLISH_TAG_PREFIX`]
1236 /// prefix composes on top of, the programs.yaml entry's `versao:`
1237 /// value the `lareira-fleet-programs` aggregator carries onto each
1238 /// rendered `ComputeUnit`, the OCI image's `:v<versao>` / `:latest`
1239 /// tags every substrate-side `skopeo push` writes, the lacre
1240 /// closure's pinned `concrete_versao`, and the `:upgrade-from :from`
1241 /// prior-version references peers in the exact same SemVer-2 shape).
1242 /// The typed slot's `String` accept-set (empty rejected through
1243 /// [`ManifestError::VersaoEmpty`], SemVer-2-shape-invalid rejected
1244 /// through [`ManifestError::VersaoInvalid`] past
1245 /// [`semver::Version::parse`]) maps onto every load-bearing
1246 /// downstream consumer the substrate carries — the [`Self::validate_versao`]
1247 /// universal-axis validate gate at caixa-build time, the
1248 /// [`crate::CaixaVersion::parse`] typed-wrapper resolver,
1249 /// [`caixa-helm`]'s `Chart.yaml` `version:` / `appVersion:` fold,
1250 /// [`caixa-flux`]'s `programs.yaml` entry `versao:` fold + the
1251 /// `cluster_bundle` `GitRepository` `ref: { tag: v<versao> }`
1252 /// derivation, [`caixa-mesh`]'s per-Aplicacao `programs.yaml` fan-
1253 /// out entry `versao:` fold, [`caixa-feira`]'s `feira publish` git-
1254 /// tag derivation (`format!("{prefix}{versao}")`), and every future
1255 /// substrate renderer that emits an artifact keyed by the caixa's
1256 /// pinned version.
1257 ///
1258 /// Prior to this lift the `.versao` field was accessed inline at a
1259 /// dozen production sites across `caixa-core` (the universal-axis
1260 /// [`Self::validate_versao`] gate + [`Dep::validate`]-adjacent
1261 /// version-shape gates), `caixa-helm` (the `ChartYaml.version` /
1262 /// `ChartYaml.app_version` folds), `caixa-flux` (the `programs.yaml`
1263 /// entry `versao:` fold, the `cluster_bundle` `GitRepository` `ref:
1264 /// { tag: v<versao> }` derivation), `caixa-mesh` (the per-Aplicacao
1265 /// `programs.yaml` fan-out entry `versao:` fold), and `caixa-feira`
1266 /// (the `feira publish` git-tag derivation + the `feira app graph` /
1267 /// `feira app deploy` diagnostic renderers) — a dozen open-coded
1268 /// field-accesses that expressed no compile-time link back to the
1269 /// typed slot. A future extension of the `:versao` axis to a richer
1270 /// author surface — a per-`:versao` structured `CaixaVersion` at the
1271 /// storage layer (the substrate already carries a `CaixaVersion`
1272 /// newtype at [`crate::version::CaixaVersion`], deferred until the
1273 /// serde-transparent-newtype-through-DeriveTataraDomain path lands),
1274 /// a per-registry `:versao` immutability overlay the M4 CR
1275 /// materializer enforces per-CR, a promotion of the plain `String`
1276 /// byte-string to a richer `PinnedVersao` newtype discriminated on
1277 /// SemVer-2 pre-release / build-metadata presence — would have had
1278 /// to be threaded through every open-coded copy in lockstep or the
1279 /// validate gate and the dozen emit paths would silently disagree
1280 /// on which version a given [`Caixa`] resolves to (an author's
1281 /// `:versao "0.1.0"` would satisfy validate while one of the emit
1282 /// paths silently rendered a drifted other version, or vice versa).
1283 /// Lifting the resolution to a typed method on the substrate
1284 /// primitive means every downstream consumer of the caixa's
1285 /// per-`Caixa` pinned-version surface reaches for exactly one typed
1286 /// dispatch — the resolver's accept-set migrates as a unit on any
1287 /// future axis addition.
1288 ///
1289 /// Second outer top-level [`Caixa`] `&str`-return required-scalar
1290 /// accessor — folds on the "outer [`Caixa`] `&str` required-scalar"
1291 /// projection pattern the sibling per-`Caixa` [`Self::nome`]
1292 /// (e6b7d97) opened. Sibling in shape to the peer per-`:membros`
1293 /// [`crate::aplicacao::Membro::versao_requirement`] (4127bb6) /
1294 /// per-`:children` [`crate::supervisor::ChildSpec::versao_requirement`]
1295 /// (2c053c8) / per-`:upgrade-from` [`crate::UpgradeFromEntry::prior_versao`]
1296 /// (75d27a8) per-sub-struct `:versao`-shaped `&str`-return accessors
1297 /// on the sibling per-typed-slot version-carrier axes, extended here
1298 /// to close the second outer top-level [`Caixa`] required-`&str`-
1299 /// carrying axis so the two universal-axis identity-carrying
1300 /// scalars every `defcaixa` form supplies (`:nome` + `:versao`)
1301 /// share the same "one typed dispatch per axis" discipline. Named
1302 /// `versao()` to match the storage field's name; the accessor's
1303 /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
1304 /// slot's docstring already carries.
1305 #[must_use]
1306 pub const fn versao(&self) -> &str {
1307 self.versao.as_str()
1308 }
1309
1310 /// Substrate-canonical per-`Caixa` `:kind` universal-axis
1311 /// closed-set-enum discriminant accessor every consumer of the top-
1312 /// level manifest's kind axis keys off — returns the author-declared
1313 /// `:kind` variant verbatim as a [`CaixaKind`], `Copy`-projected
1314 /// from the typed slot's own [`CaixaKind`] storage. Non-optional
1315 /// (`:kind` is a required-axis discriminant every `defcaixa` form
1316 /// must supply alongside `:nome` / `:versao`; the [`Self::from_lisp`]
1317 /// derive rejects an omitted / non-symbol `:kind` at parse time, so
1318 /// a `Caixa` past parse definitionally carries a valid [`CaixaKind`]
1319 /// variant).
1320 ///
1321 /// The `:kind` slot carries the universal-axis closed-set typed-
1322 /// discriminant every substrate-side dispatch keys off (CAIXA-SDLC
1323 /// §I — the primary shape gate every renderer / verifier /
1324 /// operator branches on; the five variants `Biblioteca` /
1325 /// `Binario` / `Servico` / `Supervisor` / `Aplicacao` partition
1326 /// the caixa surface into disjoint runtime contracts) — the typed
1327 /// slot's [`CaixaKind`] accept-set (parse-time-rejected non-symbol
1328 /// values through the derive-macro's symbol-arm gate, exhaustively
1329 /// matched at every downstream dispatch site) maps onto every
1330 /// load-bearing downstream consumer the substrate carries:
1331 ///
1332 /// - [`crate::render::require_kind`]'s per-renderer entry-gate
1333 /// predicate — the canonical two-line
1334 /// `require_kind(caixa, Servico)?` prelude every per-Servico
1335 /// renderer (`caixa-helm`, `caixa-flux`, the future `caixa-otel`
1336 /// / per-Servico OCI packager / M4 `wasm.pleme.io/v1alpha1/
1337 /// ComputeUnit` CR materializer) runs at its entry-point,
1338 /// alongside the [`crate::render::KindMismatch`] error carrier's
1339 /// `actual:` field the diagnostic surfaces to name the offending
1340 /// caixa's variant.
1341 /// - [`Self::aplicacao_view`]'s + [`Self::supervisor_view`]'s
1342 /// per-view kind-gate binding — the two `Option<TypedSpec>`
1343 /// `_view` composers that fold the flat mesh-slot / supervisor-
1344 /// slot columns into their typed sub-spec only when the kind
1345 /// matches (returns `None` otherwise); the future per-Servico
1346 /// M2-view composer (`servico_view`) will follow the same shape.
1347 /// - [`Self::declared_foreign_code_slots`]'s per-slot kind-
1348 /// coherence gate — the `!self.kind.requires_exe()` /
1349 /// `!self.kind.requires_servicos()` predicates that fence
1350 /// each code-surface slot from the wrong owning kind.
1351 /// - [`crate::LayoutInvariants::verify`]'s kind ↔ code-surface
1352 /// coherence gates — the six `caixa.kind == CaixaKind::X` /
1353 /// `caixa.kind != CaixaKind::X` predicates and the four kind-
1354 /// coherence error carriers (`SupervisorOwnsCode` /
1355 /// `AplicacaoOwnsCode` / `MeshSlotsOnNonAplicacao` /
1356 /// `SupervisorSlotsOnNonSupervisor` / `ServicoSlotsOnNonServico`
1357 /// / `ForeignCodeSlot`) which each name the offending caixa's
1358 /// variant in their `kind:` field.
1359 ///
1360 /// Prior to this lift the `.kind` field was accessed inline at
1361 /// twenty-plus production sites across `caixa-core` (the
1362 /// [`crate::render::require_kind`] entry-gate predicate + the
1363 /// [`crate::render::KindMismatch`] `actual:` field, the two `_view`
1364 /// composers, the `declared_foreign_code_slots` per-slot kind-
1365 /// coherence gate, and the six [`crate::LayoutInvariants::verify`]
1366 /// kind ↔ code-surface predicates + four error carriers) — a score
1367 /// of open-coded field-accesses that expressed no compile-time link
1368 /// back to the typed slot. A future extension of the `:kind` axis
1369 /// to a richer author surface — a per-`:kind` sub-variant discriminant
1370 /// (e.g. `Servico(ServicoRuntime)` splitting the current single
1371 /// variant across the wasm-component / legacy-container / native-
1372 /// binary runtime axes the M5 roadmap acknowledges), a per-cluster
1373 /// kind-overlay the M4 CR materializer resolves per-CR (the
1374 /// "cluster policy demotes `Aplicacao` to `Servico` on a single-
1375 /// tenant cluster" arm), a promotion of the plain [`CaixaKind`]
1376 /// enum to a richer `KindWithRuntime` discriminated on the
1377 /// component-model world axis — would have had to be threaded
1378 /// through every open-coded copy in lockstep or the entry gate,
1379 /// the view composers, and the layout invariants would silently
1380 /// disagree on which kind a given [`Caixa`] resolves to. Lifting
1381 /// the resolution to a typed method on the substrate primitive
1382 /// means every downstream consumer of the caixa's per-`Caixa`
1383 /// kind surface reaches for exactly one typed dispatch — the
1384 /// resolver's accept-set migrates as a unit on any future axis
1385 /// addition.
1386 ///
1387 /// First outer top-level [`Caixa`] `Copy`-return required-enum-
1388 /// discriminant accessor — opens the "outer [`Caixa`] `Copy`-return
1389 /// required-discriminant" projection pattern. Sibling in shape to
1390 /// the peer per-`:supervisor` [`crate::supervisor::SupervisorSpec::estrategia`]
1391 /// (eafb619), per-`:placement` [`crate::aplicacao::Placement::estrategia`]
1392 /// (921fe1b), and per-`:children` [`crate::supervisor::ChildSpec::restart`]
1393 /// (dfb4a81) `Copy`-return closed-set-enum discriminant accessors
1394 /// on the sibling nested-spec typed-slot discriminator axes,
1395 /// extended here to the outer top-level [`Caixa`] universal-axis
1396 /// surface. Named `kind()` to match the storage field's name;
1397 /// the accessor's identity maps onto the canonical CAIXA-SDLC §I
1398 /// vocabulary the slot's docstring already carries.
1399 #[must_use]
1400 pub fn kind(&self) -> CaixaKind {
1401 self.kind
1402 }
1403
1404 /// Substrate-canonical per-`Caixa` `:autores` universal-axis
1405 /// maintainer-name-list slice-accessor every consumer of the top-
1406 /// level manifest's maintainer axis keys off — returns the author-
1407 /// declared `:autores` list verbatim as a `&[String]` slice-view over
1408 /// the same backing buffer the raw `self.autores.as_slice()` field
1409 /// access borrows from. Empty-list-carrying (`:autores` is a default-
1410 /// empty axis every `defcaixa` form supplies with an empty `()` when
1411 /// unset; the [`Self::from_lisp`] derive folds an omitted `:autores`
1412 /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
1413 /// parse definitionally carries a `Vec<String>` slot — possibly
1414 /// empty — and the returned `&[String]` degenerates to an empty
1415 /// slice on that arm without any silent `None` collapse).
1416 ///
1417 /// The `:autores` slot carries the universal-axis maintainer-name
1418 /// list every kind of caixa emits under (CAIXA-SDLC §I — the author-
1419 /// facing surface every `defcaixa` form supplies alongside `:nome` /
1420 /// `:versao` / `:kind`; the substrate-wide contact-carrying axis
1421 /// every downstream registry-facing artifact emits under) — the
1422 /// typed slot's `Vec<String>` accept-set (empty-per-entry rejected
1423 /// through [`ManifestError::AutorEmpty`], non-chart-maintainer-shape
1424 /// rejected through [`ManifestError::AutorInvalid`], cross-entry
1425 /// duplicate rejected through [`ManifestError::AutorDuplicate`]) maps
1426 /// onto every load-bearing downstream consumer the substrate carries
1427 /// — the [`Self::validate_autores`] universal-axis empty-per-entry +
1428 /// shape + duplicate gate at caixa-core/src/manifest.rs, the
1429 /// caixa-helm `build_chart_yaml` `maintainers:` fold at
1430 /// caixa-helm/src/lib.rs that walks each entry into a `Maintainer {
1431 /// name, email: None }` record, every future per-`Caixa` registry-
1432 /// facing renderer the CAIXA-SDLC §I roadmap acknowledges (the
1433 /// future `artifacthub.io/maintainers` `Chart.yaml` annotation the
1434 /// caixa-helm docstring alludes to at [`Self::validate_licenca`],
1435 /// the future per-cluster author-notification overlay the M4 CR
1436 /// materializer resolves per-CR).
1437 ///
1438 /// Prior to this lift the `.autores` field was accessed inline at
1439 /// two production sites — [`Self::validate_autores`]'s `for autor
1440 /// in &self.autores` walk that gates every entry through
1441 /// [`ManifestError::AutorEmpty`] / `AutorInvalid` / `AutorDuplicate`,
1442 /// and the caixa-helm `build_chart_yaml` `caixa.autores.iter().map(|a|
1443 /// Maintainer { name: a.clone(), email: None }).collect()` fold that
1444 /// materializes every entry into a `Chart.yaml` `maintainers:` row —
1445 /// two open-coded field-accesses that expressed no compile-time link
1446 /// back to the typed slot. A future extension of the `:autores` axis
1447 /// to a richer author surface — a per-`:autores` structured
1448 /// `Maintainer { name, email, url }` at the storage layer once the
1449 /// substrate absorbs `artifacthub.io/maintainers`' name+email+url
1450 /// tuple, a per-registry `:autores` allowlist the M4 CR materializer
1451 /// enforces per-CR (the "cluster policy demands every author declare
1452 /// an on-file `mailto:` contact" arm), a promotion of the plain
1453 /// `Vec<String>` byte-string list to a richer
1454 /// `Vec<ChartMaintainer>` newtype discriminated on the RFC-5322
1455 /// `<name> [<email>]` grammar the `is_chart_maintainer_name_shape`
1456 /// predicate already resolves through — would have had to be
1457 /// threaded through both open-coded copies in lockstep or the
1458 /// validate gate and the caixa-helm emit path would silently
1459 /// disagree on which authors a given [`Caixa`] resolves to (an
1460 /// author's `:autores ("alice" "bob")` would satisfy validate while
1461 /// the caixa-helm emit path silently rendered a drifted other
1462 /// maintainer list, or vice versa). Lifting the resolution to a
1463 /// typed method on the substrate primitive means every downstream
1464 /// consumer of the caixa's per-`Caixa` maintainer surface reaches
1465 /// for exactly one typed dispatch — the resolver's accept-set
1466 /// migrates as a unit on any future axis addition.
1467 ///
1468 /// First outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1469 /// opens the "outer [`Caixa`] `&[T]` slice" projection pattern the
1470 /// sibling per-`Caixa` `:etiquetas` / `:deps` / `:deps-dev` / `:exe`
1471 /// / `:bibliotecas` / `:servicos` / `:upgrade-from` / `:children`
1472 /// future lifts fold on. Sibling in shape to the peer per-`:supervisor`
1473 /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce), per-`:placement`
1474 /// [`crate::aplicacao::Placement::clusters`] (a6e18d7), per-`:membros`
1475 /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36), per-`:contratos`
1476 /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1477 /// per-`:upgrade-from :instructions` [`crate::upgrade::UpgradeFromEntry::instructions`]
1478 /// (0137e5a) `&[T]`-return slice accessors on the sibling per-M2 /
1479 /// per-M3 typed-slot list axes, extended here to the outer top-level
1480 /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1481 /// `&Vec<String>`) because every downstream consumer of the author
1482 /// list treats it as a read-only sequence — the slice-view is the
1483 /// narrowest borrow that supports every present + roadmapped consumer
1484 /// (`.iter()`, `.len()`, `.is_empty()`) without leaking the backing
1485 /// `Vec`'s grow/push/reserve surface no consumer of the typed view
1486 /// reaches for (the storage-side `Vec` remains reachable through the
1487 /// `pub autores` field for the mutation-carrying serde round-trip and
1488 /// per-test fixture-mutation paths). Named `autores()` to match the
1489 /// storage field's name; the accessor's identity maps onto the
1490 /// canonical CAIXA-SDLC §I vocabulary the slot's docstring already
1491 /// carries.
1492 #[must_use]
1493 pub fn autores(&self) -> &[String] {
1494 self.autores.as_slice()
1495 }
1496
1497 /// Substrate-canonical per-`Caixa` `:etiquetas` universal-axis
1498 /// registry-search-tag-list slice-accessor every consumer of the
1499 /// top-level manifest's topical-tag axis keys off — returns the
1500 /// author-declared `:etiquetas` list verbatim as a `&[String]`
1501 /// slice-view over the same backing buffer the raw
1502 /// `self.etiquetas.as_slice()` field access borrows from. Empty-
1503 /// list-carrying (`:etiquetas` is a default-empty axis every
1504 /// `defcaixa` form supplies with an empty `()` when unset; the
1505 /// [`Self::from_lisp`] derive folds an omitted `:etiquetas` through
1506 /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
1507 /// definitionally carries a `Vec<String>` slot — possibly empty —
1508 /// and the returned `&[String]` degenerates to an empty slice on
1509 /// that arm without any silent `None` collapse).
1510 ///
1511 /// The `:etiquetas` slot carries the universal-axis topical-tag
1512 /// list every kind of caixa emits under (CAIXA-SDLC §I — the
1513 /// author-facing surface every `defcaixa` form supplies alongside
1514 /// `:nome` / `:versao` / `:kind`; the substrate-wide registry-
1515 /// search-facing axis every downstream registry-facing artifact
1516 /// emits under) — the typed slot's `Vec<String>` accept-set
1517 /// (empty-per-entry rejected through [`ManifestError::EtiquetaEmpty`],
1518 /// non-chart-keyword-shape rejected through
1519 /// [`ManifestError::EtiquetaInvalid`], cross-entry duplicate
1520 /// rejected through [`ManifestError::EtiquetaDuplicate`]) maps onto
1521 /// every load-bearing downstream consumer the substrate carries —
1522 /// the [`Self::validate_etiquetas`] universal-axis empty-per-entry
1523 /// + shape + duplicate gate at caixa-core/src/manifest.rs, the
1524 /// caixa-helm `build_chart_yaml` `keywords:` fold at
1525 /// caixa-helm/src/lib.rs that walks each entry into the rendered
1526 /// `Chart.yaml` `keywords:` array (chained with the
1527 /// [`crate::LAREIRA_CHART_KEYWORDS`] substrate-wide floor set and
1528 /// dedup'd through a `BTreeSet` at emit time), every future per-
1529 /// `Caixa` registry-facing renderer the CAIXA-SDLC §I roadmap
1530 /// acknowledges (the future `artifacthub.io/keywords` `Chart.yaml`
1531 /// annotation, the future per-cluster tag-notification overlay the
1532 /// M4 CR materializer resolves per-CR).
1533 ///
1534 /// Prior to this lift the `.etiquetas` field was accessed inline at
1535 /// two production sites — [`Self::validate_etiquetas`]'s `for
1536 /// etiqueta in &self.etiquetas` walk that gates every entry through
1537 /// [`ManifestError::EtiquetaEmpty`] / `EtiquetaInvalid` /
1538 /// `EtiquetaDuplicate`, and the caixa-helm `build_chart_yaml`
1539 /// `caixa.etiquetas.iter().cloned().chain(...)` fold that
1540 /// materializes every entry into a `Chart.yaml` `keywords:` row —
1541 /// two open-coded field-accesses that expressed no compile-time
1542 /// link back to the typed slot. A future extension of the
1543 /// `:etiquetas` axis to a richer tag surface — a per-`:etiquetas`
1544 /// structured `ChartKeyword { name, uri, category }` at the storage
1545 /// layer once the substrate absorbs `artifacthub.io/keywords`
1546 /// richer tag tuple, a per-registry `:etiquetas` allowlist the M4
1547 /// CR materializer enforces per-CR (the "cluster policy demands
1548 /// every tag come from a substrate-approved taxonomy" arm), a
1549 /// promotion of the plain `Vec<String>` byte-string list to a
1550 /// richer `Vec<ChartKeyword>` newtype discriminated on the DNS-
1551 /// 1123-label-shaped grammar the `is_chart_keyword_shape` predicate
1552 /// already resolves through — would have had to be threaded through
1553 /// both open-coded copies in lockstep or the validate gate and the
1554 /// caixa-helm emit path would silently disagree on which tags a
1555 /// given [`Caixa`] resolves to (an author's `:etiquetas ("demo"
1556 /// "aplicacao")` would satisfy validate while the caixa-helm emit
1557 /// path silently rendered a drifted other keyword list, or vice
1558 /// versa). Lifting the resolution to a typed method on the
1559 /// substrate primitive means every downstream consumer of the
1560 /// caixa's per-`Caixa` topical-tag surface reaches for exactly one
1561 /// typed dispatch — the resolver's accept-set migrates as a unit
1562 /// on any future axis addition.
1563 ///
1564 /// Second outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1565 /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1566 /// [`Self::autores`] (b5d813f) opened, sibling in shape and
1567 /// idiom. The remaining unlifted outer-`Caixa` slice-carrying axes
1568 /// (`:deps` / `:deps-dev` / `:exe` / `:bibliotecas` / `:servicos`
1569 /// / `:upgrade-from` / `:children` / `:membros` / `:contratos`)
1570 /// fold onto the same pattern in future lifts. Sibling in shape to
1571 /// the peer per-`:supervisor`
1572 /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1573 /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1574 /// (a6e18d7), per-`:membros`
1575 /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1576 /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
1577 /// (0dcc926), and per-`:upgrade-from :instructions`
1578 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1579 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1580 /// typed-slot list axes, extended here to the outer top-level
1581 /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1582 /// `&Vec<String>`) because every downstream consumer of the tag
1583 /// list treats it as a read-only sequence — the slice-view is the
1584 /// narrowest borrow that supports every present + roadmapped
1585 /// consumer (`.iter()`, `.len()`, `.is_empty()`) without leaking
1586 /// the backing `Vec`'s grow/push/reserve surface no consumer of
1587 /// the typed view reaches for (the storage-side `Vec` remains
1588 /// reachable through the `pub etiquetas` field for the mutation-
1589 /// carrying serde round-trip and per-test fixture-mutation paths).
1590 /// Named `etiquetas()` to match the storage field's name; the
1591 /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1592 /// vocabulary the slot's docstring already carries.
1593 #[must_use]
1594 pub fn etiquetas(&self) -> &[String] {
1595 self.etiquetas.as_slice()
1596 }
1597
1598 /// Substrate-canonical per-`Caixa` `:bibliotecas` universal-axis
1599 /// library-source-path-list slice-accessor every consumer of the
1600 /// top-level manifest's Biblioteca-source axis keys off — returns
1601 /// the author-declared `:bibliotecas` list verbatim as a
1602 /// `&[String]` slice-view over the same backing buffer the raw
1603 /// `self.bibliotecas.as_slice()` field access borrows from. Empty-
1604 /// list-carrying (`:bibliotecas` is a default-empty axis every
1605 /// `defcaixa` form supplies with an empty `()` when unset; the
1606 /// [`Self::from_lisp`] derive folds an omitted `:bibliotecas`
1607 /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
1608 /// parse definitionally carries a `Vec<String>` slot — possibly
1609 /// empty — and the returned `&[String]` degenerates to an empty
1610 /// slice on that arm without any silent `None` collapse).
1611 ///
1612 /// The `:bibliotecas` slot carries the universal-axis lisp-library
1613 /// entry-path list every `:kind Biblioteca` caixa emits under
1614 /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa`
1615 /// form supplies alongside `:nome` / `:versao` / `:kind`; the
1616 /// substrate-wide library-carrier axis every downstream
1617 /// authoring-facing consumer keys off) — the typed slot's
1618 /// `Vec<String>` accept-set (empty-per-entry rejected through
1619 /// [`ManifestError::CodePathEmpty { slot: ":bibliotecas" }`],
1620 /// non-sandboxed-relative-shape rejected through
1621 /// [`ManifestError::CodePathShape`], non-`.lisp`-extension rejected
1622 /// through [`ManifestError::CodePathNonLispExtension`], cross-entry
1623 /// duplicate rejected through [`ManifestError::CodePathDuplicate`])
1624 /// maps onto every load-bearing downstream consumer the substrate
1625 /// carries — the [`crate::LayoutInvariants`] Biblioteca-arm
1626 /// empty-check + per-entry file-exists loop at
1627 /// caixa-core/src/layout.rs that gates each entry through
1628 /// [`crate::LayoutError::MissingLib`] / `MissingEntry`, the
1629 /// [`Self::validate_code_paths`] per-slot shape gate at
1630 /// caixa-core/src/manifest.rs that walks each entry through the
1631 /// sandbox-relative / `.lisp`-extension / cross-entry duplicate
1632 /// gates, the `feira build` per-entry `tatara_lisp::read` parse
1633 /// walk at caixa-feira/src/cmd/build.rs that phase-1-checks each
1634 /// declared library file for lexical / structural errors before
1635 /// downstream `importar` resolution, every future per-`Caixa`
1636 /// library-facing renderer the CAIXA-SDLC §I roadmap acknowledges
1637 /// (the future `tatara-lispc` compilation entry the docstring at
1638 /// caixa-feira/src/cmd/build.rs alludes to, the future per-cluster
1639 /// bytecode-caching overlay the M4 CR materializer resolves per-CR,
1640 /// the future `caixa-lsp` per-library semantic-token stream the
1641 /// caixa-lsp docstring roadmaps).
1642 ///
1643 /// Prior to this lift the `.bibliotecas` field was accessed inline
1644 /// at three production sites — [`crate::LayoutInvariants`]'s
1645 /// `caixa.bibliotecas.is_empty()` `MissingLib`-arm gate + `for p
1646 /// in &caixa.bibliotecas` `MissingEntry` walk that gates each
1647 /// declared library path through the on-disk-existence check,
1648 /// the compound-code-path `has_code = !caixa.bibliotecas.is_empty()
1649 /// || !caixa.exe.is_empty() || !caixa.servicos.is_empty()` OR-fold
1650 /// on the [`crate::LayoutError::SupervisorOwnsCode`] /
1651 /// `AplicacaoOwnsCode` kind-coherence gate, and the `feira build`
1652 /// per-entry `for entry in &caixa.bibliotecas` + `caixa.bibliotecas.
1653 /// len()` phase-1 tatara-lispc-precursor parse walk — three open-
1654 /// coded field-accesses that expressed no compile-time link back
1655 /// to the typed slot. A future extension of the `:bibliotecas`
1656 /// axis to a richer library surface — a per-`:bibliotecas`
1657 /// structured `BibliotecaEntry { path, edition, exports }` at the
1658 /// storage layer once the substrate absorbs the per-library
1659 /// language-edition + explicit-exports tuple the tatara-lisp
1660 /// module-system roadmap acknowledges, a per-registry
1661 /// `:bibliotecas` allowlist the M4 CR materializer enforces
1662 /// per-CR (the "cluster policy demands every biblioteca declare
1663 /// its own :edicao" arm), a promotion of the plain `Vec<String>`
1664 /// byte-string list to a richer `Vec<LibraryPath>` newtype
1665 /// discriminated on the `lib/<nome>.lisp`-shape grammar the
1666 /// [`crate::render::is_sandboxed_relative_path`] +
1667 /// [`crate::render::is_lisp_extension`] predicates already resolve
1668 /// through — would have had to be threaded through all three
1669 /// open-coded copies in lockstep or the layout gate, the shape
1670 /// validator, and the `feira build` phase-1 parse walk would
1671 /// silently disagree on which library paths a given [`Caixa`]
1672 /// resolves to (an author's `:bibliotecas ("lib/foo.lisp"
1673 /// "lib/bar.lisp")` would satisfy layout while `feira build`
1674 /// silently parsed a drifted other list, or vice versa). Lifting
1675 /// the resolution to a typed method on the substrate primitive
1676 /// means every downstream consumer of the caixa's per-`Caixa`
1677 /// library-source surface reaches for exactly one typed dispatch
1678 /// — the resolver's accept-set migrates as a unit on any future
1679 /// axis addition.
1680 ///
1681 /// Third outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1682 /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1683 /// [`Self::autores`] (b5d813f) opened and [`Self::etiquetas`]
1684 /// (78c7d3c) folded on, sibling in shape and idiom. The remaining
1685 /// unlifted outer-`Caixa` slice-carrying axes (`:deps` /
1686 /// `:deps-dev` / `:exe` / `:servicos` / `:upgrade-from` /
1687 /// `:children` / `:membros` / `:contratos`) fold onto the same
1688 /// pattern in future lifts. Sibling in shape to the peer
1689 /// per-`:supervisor`
1690 /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1691 /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1692 /// (a6e18d7), per-`:membros`
1693 /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1694 /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
1695 /// (0dcc926), and per-`:upgrade-from :instructions`
1696 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1697 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1698 /// typed-slot list axes, extended here to the outer top-level
1699 /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1700 /// `&Vec<String>`) because every downstream consumer of the
1701 /// library-source list treats it as a read-only sequence — the
1702 /// slice-view is the narrowest borrow that supports every
1703 /// present + roadmapped consumer (`.iter()`, `.len()`,
1704 /// `.is_empty()`) without leaking the backing `Vec`'s
1705 /// grow/push/reserve surface no consumer of the typed view
1706 /// reaches for (the storage-side `Vec` remains reachable through
1707 /// the `pub bibliotecas` field for the mutation-carrying serde
1708 /// round-trip and per-test fixture-mutation paths). Named
1709 /// `bibliotecas()` to match the storage field's name; the
1710 /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1711 /// vocabulary the slot's docstring already carries.
1712 #[must_use]
1713 pub fn bibliotecas(&self) -> &[String] {
1714 self.bibliotecas.as_slice()
1715 }
1716
1717 /// Substrate-canonical per-`Caixa` `:exe` universal-axis
1718 /// nix-built-executable-entry-path-list slice-accessor every consumer
1719 /// of the top-level manifest's Binario-executable axis keys off —
1720 /// returns the author-declared `:exe` list verbatim as a `&[String]`
1721 /// slice-view over the same backing buffer the raw
1722 /// `self.exe.as_slice()` field access borrows from. Empty-list-
1723 /// carrying (`:exe` is a default-empty axis every `defcaixa` form
1724 /// supplies with an empty `()` when unset; the [`Self::from_lisp`]
1725 /// derive folds an omitted `:exe` through `#[serde(default)]` to
1726 /// `Vec::new()`, so a `Caixa` past parse definitionally carries a
1727 /// `Vec<String>` slot — possibly empty — and the returned `&[String]`
1728 /// degenerates to an empty slice on that arm without any silent
1729 /// `None` collapse).
1730 ///
1731 /// The `:exe` slot carries the universal-axis nix-built executable
1732 /// entry-path list every `:kind Binario` caixa emits under
1733 /// (CAIXA-SDLC §I — the author-facing surface every `defcaixa`
1734 /// form supplies alongside `:nome` / `:versao` / `:kind`; the
1735 /// substrate-wide `exe/`-directory-fenced entry-carrier axis every
1736 /// downstream flake-build-facing consumer keys off) — the typed
1737 /// slot's `Vec<String>` accept-set (empty-per-entry rejected
1738 /// through [`ManifestError::CodePathEmpty { slot: ":exe" }`],
1739 /// non-sandboxed-relative-shape rejected through
1740 /// [`ManifestError::CodePathShape`], cross-entry duplicate rejected
1741 /// through [`ManifestError::CodePathDuplicate`], out-of-`exe/`-
1742 /// directory paths rejected past the layout's
1743 /// [`crate::LayoutError::ExeOutsideDir`] `starts_with` fence) maps
1744 /// onto every load-bearing downstream consumer the substrate carries
1745 /// — the [`crate::LayoutInvariants`] Binario-arm empty-check +
1746 /// per-entry file-exists + `exe/`-directory-fence loop at
1747 /// caixa-core/src/layout.rs that gates each entry through
1748 /// [`crate::LayoutError::BinarioWithoutExe`] / `MissingEntry` /
1749 /// `ExeOutsideDir`, the compound `has_code` OR-fold on the
1750 /// [`crate::LayoutError::SupervisorOwnsCode`] /
1751 /// [`crate::LayoutError::AplicacaoOwnsCode`] kind-coherence gate
1752 /// that fences code-surface slots off from the two no-code kinds,
1753 /// [`Self::declared_foreign_code_slots`]'s `!self.exe.is_empty()`
1754 /// arm on the [`crate::LayoutError::ForeignCodeSlot`] gate that
1755 /// fences the `:exe` code surface off from every non-Binario code-
1756 /// running kind, [`Self::validate_code_paths`]'s per-slot shape gate
1757 /// that walks each entry through the sandbox-relative / cross-entry
1758 /// duplicate gates, every future per-`Caixa` executable-facing
1759 /// renderer the CAIXA-SDLC §I roadmap acknowledges (the future
1760 /// `caixa-flake` per-Binario `packages.<system>.<nome>` derivation
1761 /// entry the caixa-flake docstring roadmaps, the future per-cluster
1762 /// `nix-store` overlay the M4 CR materializer resolves per-CR, the
1763 /// future `feira nix` per-executable Binario-target emit path).
1764 ///
1765 /// Prior to this lift the `.exe` field was accessed inline at three
1766 /// production sites — the compound-code-path `has_code =
1767 /// !caixa.bibliotecas().is_empty() || !caixa.exe.is_empty() ||
1768 /// !caixa.servicos.is_empty()` OR-fold on the
1769 /// [`crate::LayoutError::SupervisorOwnsCode`] /
1770 /// `AplicacaoOwnsCode` kind-coherence gate, the Binario-arm
1771 /// `caixa.exe.is_empty()` [`crate::LayoutError::BinarioWithoutExe`]
1772 /// gate, the per-entry `for p in &caixa.exe`
1773 /// `MissingEntry`/`ExeOutsideDir` walk, and the
1774 /// [`Self::declared_foreign_code_slots`]'s
1775 /// `!self.exe.is_empty()` arm on the `ForeignCodeSlot` gate — four
1776 /// open-coded field-accesses that expressed no compile-time link
1777 /// back to the typed slot. A future extension of the `:exe` axis
1778 /// to a richer executable surface — a per-`:exe` structured
1779 /// `BinarioEntry { path, wrapper, capabilities }` at the storage
1780 /// layer once the substrate absorbs the per-executable
1781 /// nix-wrapper + linux-capabilities tuple the CAIXA-SDLC §I
1782 /// executable roadmap acknowledges, a per-registry `:exe` allowlist
1783 /// the M4 CR materializer enforces per-CR (the "cluster policy
1784 /// demands every Binario declare an explicit `:wrapper`" arm), a
1785 /// promotion of the plain `Vec<String>` byte-string list to a
1786 /// richer `Vec<ExecutablePath>` newtype discriminated on the
1787 /// `exe/<nome>`-shape grammar the layout's `starts_with(exe_dir)`
1788 /// fence already resolves through — would have had to be threaded
1789 /// through all four open-coded copies in lockstep or the layout
1790 /// gate, the shape validator, and the `feira nix` emit path would
1791 /// silently disagree on which executable paths a given [`Caixa`]
1792 /// resolves to (an author's `:exe ("exe/cli" "exe/serve")` would
1793 /// satisfy layout while `feira nix` silently packaged a drifted
1794 /// other list, or vice versa). Lifting the resolution to a typed
1795 /// method on the substrate primitive means every downstream
1796 /// consumer of the caixa's per-`Caixa` executable-source surface
1797 /// reaches for exactly one typed dispatch — the resolver's accept-
1798 /// set migrates as a unit on any future axis addition.
1799 ///
1800 /// Fourth outer top-level [`Caixa`] `&[T]`-return slice-accessor —
1801 /// folds on the "outer [`Caixa`] `&[T]` slice" projection pattern
1802 /// [`Self::autores`] (b5d813f) opened, [`Self::etiquetas`]
1803 /// (78c7d3c) folded on, and [`Self::bibliotecas`] (8a36c23) closed
1804 /// the universal-axis text-tag family of. Opens the outer-`Caixa`
1805 /// foreign-code-slot `&[T]` sub-family the sibling `:servicos`
1806 /// future lift closes onto (per the trio of code-surface list slots
1807 /// the [`Self::validate_code_paths`] per-slot dispatch tuple
1808 /// already carries — `:bibliotecas` + `:exe` + `:servicos`, of which
1809 /// `:bibliotecas` landed at 8a36c23 and `:servicos` remains as the
1810 /// last unlifted code-surface slot). Sibling in shape to the peer
1811 /// per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
1812 /// (bc92bce), per-`:placement`
1813 /// [`crate::aplicacao::Placement::clusters`] (a6e18d7),
1814 /// per-`:membros` [`crate::aplicacao::AplicacaoSpec::membros`]
1815 /// (6c77e36), per-`:contratos`
1816 /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1817 /// per-`:upgrade-from :instructions`
1818 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1819 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1820 /// typed-slot list axes, extended here to the outer top-level
1821 /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1822 /// `&Vec<String>`) because every downstream consumer of the
1823 /// executable-source list treats it as a read-only sequence — the
1824 /// slice-view is the narrowest borrow that supports every
1825 /// present + roadmapped consumer (`.iter()`, `.len()`,
1826 /// `.is_empty()`) without leaking the backing `Vec`'s
1827 /// grow/push/reserve surface no consumer of the typed view
1828 /// reaches for (the storage-side `Vec` remains reachable through
1829 /// the `pub exe` field for the mutation-carrying serde
1830 /// round-trip and per-test fixture-mutation paths). Named `exe()`
1831 /// to match the storage field's name; the accessor's identity
1832 /// maps onto the canonical CAIXA-SDLC §I vocabulary the slot's
1833 /// docstring already carries.
1834 #[must_use]
1835 pub fn exe(&self) -> &[String] {
1836 self.exe.as_slice()
1837 }
1838
1839 /// Substrate-canonical per-`Caixa` `:servicos` universal-axis
1840 /// ComputeUnit-CR-YAML-entry-path-list slice-accessor every consumer
1841 /// of the top-level manifest's Servico-component axis keys off —
1842 /// returns the author-declared `:servicos` list verbatim as a
1843 /// `&[String]` slice-view over the same backing buffer the raw
1844 /// `self.servicos.as_slice()` field access borrows from. Empty-list-
1845 /// carrying (`:servicos` is a default-empty axis every `defcaixa`
1846 /// form supplies with an empty `()` when unset; the
1847 /// [`Self::from_lisp`] derive folds an omitted `:servicos` through
1848 /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
1849 /// definitionally carries a `Vec<String>` slot — possibly empty —
1850 /// and the returned `&[String]` degenerates to an empty slice on
1851 /// that arm without any silent `None` collapse).
1852 ///
1853 /// The `:servicos` slot carries the universal-axis
1854 /// `.computeunit.yaml` ComputeUnit-CR entry-path list every
1855 /// `:kind Servico` caixa emits under (CAIXA-SDLC §I — the
1856 /// author-facing surface every `defcaixa` form supplies alongside
1857 /// `:nome` / `:versao` / `:kind`; the substrate-wide
1858 /// `servicos/`-directory-fenced entry-carrier axis every downstream
1859 /// Servico-facing renderer keys off) — the typed slot's
1860 /// `Vec<String>` accept-set (empty-per-entry rejected through
1861 /// [`ManifestError::CodePathEmpty { slot: ":servicos" }`],
1862 /// non-sandboxed-relative-shape rejected through
1863 /// [`ManifestError::CodePathShape`], non-`.computeunit.yaml`
1864 /// extension rejected through
1865 /// [`ManifestError::CodePathNonComputeUnitYamlExtension`], cross-
1866 /// entry duplicate rejected through
1867 /// [`ManifestError::CodePathDuplicate`], `len != 1` rejected by the
1868 /// V0 [`crate::ServicoCountMismatch`] gate on the per-Servico
1869 /// renderer entry-points, out-of-`servicos/`-directory paths
1870 /// rejected past the layout's [`crate::LayoutError::ServicoOutsideDir`]
1871 /// `starts_with` fence) maps onto every load-bearing downstream
1872 /// consumer the substrate carries — the [`crate::LayoutInvariants`]
1873 /// Servico-arm empty-check + per-entry file-exists + `servicos/`-
1874 /// directory-fence loop at caixa-core/src/layout.rs that gates each
1875 /// entry through [`crate::LayoutError::ServicoWithoutServicos`] /
1876 /// `MissingEntry` / `ServicoOutsideDir`, the compound `has_code`
1877 /// OR-fold on the [`crate::LayoutError::SupervisorOwnsCode`] /
1878 /// [`crate::LayoutError::AplicacaoOwnsCode`] kind-coherence gate
1879 /// that fences code-surface slots off from the two no-code kinds,
1880 /// [`Self::declared_foreign_code_slots`]'s
1881 /// `!self.servicos.is_empty()` arm on the
1882 /// [`crate::LayoutError::ForeignCodeSlot`] gate that fences the
1883 /// `:servicos` code surface off from every non-Servico code-running
1884 /// kind, [`Self::validate_code_paths`]'s per-slot shape gate that
1885 /// walks each entry through the sandbox-relative / `.computeunit.
1886 /// yaml`-extension / cross-entry duplicate gates, the
1887 /// [`crate::require_single_servico`] V0 singularity gate every
1888 /// per-Servico renderer entry-point runs through
1889 /// [`crate::require_v0_servico_shape`], the `feira chart` /
1890 /// `feira deploy` per-verb `first_servico_path` walk at
1891 /// caixa-feira/src/cmd/chart.rs that resolves the singleton
1892 /// ComputeUnit-CR file, every future per-`Caixa` Servico-facing
1893 /// renderer the CAIXA-SDLC §I roadmap acknowledges (the future
1894 /// per-Servico OCI packager, the future M4
1895 /// `wasm.pleme.io/v1alpha1/ComputeUnit` CR materializer, the future
1896 /// per-Servico OTel collector-config emit).
1897 ///
1898 /// Prior to this lift the `.servicos` field was accessed inline at
1899 /// five production sites — the compound-code-path `has_code =
1900 /// !caixa.bibliotecas().is_empty() || !caixa.exe().is_empty() ||
1901 /// !caixa.servicos.is_empty()` OR-fold on the
1902 /// [`crate::LayoutError::SupervisorOwnsCode`] /
1903 /// `AplicacaoOwnsCode` kind-coherence gate, the Servico-arm
1904 /// `caixa.servicos.is_empty()`
1905 /// [`crate::LayoutError::ServicoWithoutServicos`] gate, the
1906 /// per-entry `for p in &caixa.servicos`
1907 /// `MissingEntry`/`ServicoOutsideDir` walk, the
1908 /// [`Self::declared_foreign_code_slots`]'s
1909 /// `!self.servicos.is_empty()` arm on the `ForeignCodeSlot` gate,
1910 /// and the [`crate::require_single_servico`] V0 count gate's
1911 /// `caixa.servicos.len() == 1` / `caixa.servicos.len()` count
1912 /// projection (both the accept-arm predicate and the
1913 /// diagnostic-carrying `ServicoCountMismatch { count }`
1914 /// projection) — five open-coded field-accesses across three
1915 /// crates that expressed no compile-time link back to the typed
1916 /// slot. A future extension of the `:servicos` axis to a richer
1917 /// component surface — a per-`:servicos` structured
1918 /// `ServicoEntry { path, world, capabilities }` at the storage
1919 /// layer once the substrate absorbs the per-component WIT-world +
1920 /// capability-set tuple the CAIXA-SDLC §I Servico roadmap
1921 /// acknowledges, a per-registry `:servicos` allowlist the M4 CR
1922 /// materializer enforces per-CR (the "cluster policy demands every
1923 /// Servico declare an explicit `:world`" arm), a promotion of the
1924 /// plain `Vec<String>` byte-string list to a richer
1925 /// `Vec<ComputeUnitPath>` newtype discriminated on the
1926 /// `servicos/<nome>.computeunit.yaml`-shape grammar the layout's
1927 /// `starts_with(servicos_dir)` fence and the
1928 /// [`crate::render::is_computeunit_yaml_extension`] predicate
1929 /// already resolve through, a promotion of the V0 singleton
1930 /// contract to a multi-component `Vec<ComputeUnitPath>` past the M5
1931 /// component-model multi-world boundary — would have had to be
1932 /// threaded through all five open-coded copies in lockstep or the
1933 /// layout gate, the shape validator, the V0 count gate, and the
1934 /// `feira chart` / `feira deploy` entry-point walks would silently
1935 /// disagree on which ComputeUnit-CR paths a given [`Caixa`]
1936 /// resolves to (an author's `:servicos ("servicos/foo.computeunit.
1937 /// yaml")` would satisfy layout while `feira chart` silently
1938 /// packaged a drifted other list, or vice versa). Lifting the
1939 /// resolution to a typed method on the substrate primitive means
1940 /// every downstream consumer of the caixa's per-`Caixa`
1941 /// ComputeUnit-CR-source surface reaches for exactly one typed
1942 /// dispatch — the resolver's accept-set migrates as a unit on any
1943 /// future axis addition.
1944 ///
1945 /// Fifth and final outer top-level [`Caixa`] `&[T]`-return slice-
1946 /// accessor — folds on the "outer [`Caixa`] `&[T]` slice"
1947 /// projection pattern [`Self::autores`] (b5d813f) opened,
1948 /// [`Self::etiquetas`] (78c7d3c) folded on, [`Self::bibliotecas`]
1949 /// (8a36c23) closed the universal-axis text-tag family of, and
1950 /// [`Self::exe`] (65d9527) opened the foreign-code-slot sub-family
1951 /// of. Closes the outer-`Caixa` foreign-code-slot `&[T]` sub-family
1952 /// — with `:bibliotecas`, `:exe`, and `:servicos` now each carrying
1953 /// a substrate-canonical slice accessor, the trio of code-surface
1954 /// list slots the [`Self::validate_code_paths`] per-slot dispatch
1955 /// tuple carries is complete on the typed dispatch surface (the
1956 /// internal `[(":bibliotecas", &self.bibliotecas, ..), (":exe",
1957 /// &self.exe, ..), (":servicos", &self.servicos, ..)]` per-slot
1958 /// dispatch tuple's homogeneous `&Vec<String>`-typed shape blocks a
1959 /// per-element accessor swap in isolation — a future companion lift
1960 /// promotes the tuple's element type to `&[String]` and threads the
1961 /// triple of typed dispatches through as a unit). Sibling in shape
1962 /// to the peer per-`:supervisor`
1963 /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
1964 /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
1965 /// (a6e18d7), per-`:membros`
1966 /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
1967 /// per-`:contratos`
1968 /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
1969 /// per-`:upgrade-from :instructions`
1970 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
1971 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
1972 /// typed-slot list axes, extended here to the outer top-level
1973 /// [`Caixa`] universal-axis surface. Returns `&[String]` (not
1974 /// `&Vec<String>`) because every downstream consumer of the
1975 /// ComputeUnit-CR-source list treats it as a read-only sequence —
1976 /// the slice-view is the narrowest borrow that supports every
1977 /// present + roadmapped consumer (`.iter()`, `.len()`,
1978 /// `.is_empty()`, `.first()`) without leaking the backing `Vec`'s
1979 /// grow/push/reserve surface no consumer of the typed view reaches
1980 /// for (the storage-side `Vec` remains reachable through the
1981 /// `pub servicos` field for the mutation-carrying serde round-trip
1982 /// and per-test fixture-mutation paths, and for the
1983 /// [`Self::validate_code_paths`] per-slot dispatch tuple whose
1984 /// homogeneous-element-type shape carries the raw field access
1985 /// until the trio-closure lift promotes the tuple as a unit).
1986 /// Named `servicos()` to match the storage field's name; the
1987 /// accessor's identity maps onto the canonical CAIXA-SDLC §I
1988 /// vocabulary the slot's docstring already carries.
1989 #[must_use]
1990 pub fn servicos(&self) -> &[String] {
1991 self.servicos.as_slice()
1992 }
1993
1994 /// Substrate-canonical per-`Caixa` `:deps` universal-axis
1995 /// runtime-dependency-declaration-list slice-accessor every consumer
1996 /// of the top-level manifest's runtime-dep-graph axis keys off —
1997 /// returns the author-declared `:deps` list verbatim as a `&[Dep]`
1998 /// slice-view over the same backing buffer the raw
1999 /// `self.deps.as_slice()` field access borrows from. Empty-list-
2000 /// carrying (`:deps` is a default-empty axis every `defcaixa` form
2001 /// supplies with an empty `()` when unset; the [`Self::from_lisp`]
2002 /// derive folds an omitted `:deps` through `#[serde(default)]` to
2003 /// `Vec::new()`, so a `Caixa` past parse definitionally carries a
2004 /// `Vec<Dep>` slot — possibly empty — and the returned `&[Dep]`
2005 /// degenerates to an empty slice on that arm without any silent
2006 /// `None` collapse).
2007 ///
2008 /// The `:deps` slot carries the universal-axis runtime dependency
2009 /// list every kind of caixa emits under (CAIXA-SDLC §I — the author-
2010 /// facing surface every `defcaixa` form supplies alongside `:nome` /
2011 /// `:versao` / `:kind`; the substrate-wide runtime-closure-input axis
2012 /// every downstream resolver-facing artifact emits under) — the
2013 /// typed slot's `Vec<Dep>` accept-set (empty-`:nome` rejected through
2014 /// [`DepError::NomeEmpty`], non-DNS-1123-label `:nome` rejected
2015 /// through [`DepError::NomeInvalid`], malformed `:versao` rejected
2016 /// through [`DepError::VersaoInvalid`], empty `:fonte.repo` rejected
2017 /// through [`DepError::FonteRepoEmpty`], within-list duplicate `:nome`
2018 /// rejected through [`DepError::DuplicateNome { list: ":deps" }`])
2019 /// maps onto every load-bearing downstream consumer the substrate
2020 /// carries — the [`Self::validate_deps`] per-entry
2021 /// [`Dep::validate`] + within-list dedup walk at
2022 /// caixa-core/src/manifest.rs, the [`crate::dep::validate_no_self_dep`]
2023 /// cross-list self-reference gate at caixa-core/src/layout.rs that
2024 /// checks each entry against the caixa's own `:nome`, the
2025 /// caixa-resolver `for dep in &root.deps` closure walk at
2026 /// caixa-resolver/src/resolve.rs that seeds every git-clone target
2027 /// through the resolver's [`crate::Dep`]-keyed pipeline, the
2028 /// caixa-crd `caixa.deps.iter().map(dep_into_ref).collect()` fold at
2029 /// caixa-crd/src/conversion.rs that materializes each entry into the
2030 /// K8s `Caixa` CR's `spec.deps` field, every future per-`Caixa`
2031 /// resolver-facing renderer the CAIXA-SDLC §I roadmap acknowledges
2032 /// (the future per-cluster runtime-closure-audit overlay the M4 CR
2033 /// materializer resolves per-CR, the future `lacre.lisp` BLAKE3-
2034 /// closure emit walk the caixa-resolver docstring roadmaps).
2035 ///
2036 /// First outer top-level [`Caixa`] `&[Dep]`-return slice-accessor —
2037 /// opens the outer-`Caixa` dependency-slot `&[Dep]` sub-family the
2038 /// sibling `:deps-dev` future lift closes on. Peer of the closed
2039 /// outer-`Caixa` foreign-code-slot `&[String]` sub-family
2040 /// ([`Self::bibliotecas`] 8a36c23, [`Self::exe`] 65d9527,
2041 /// [`Self::servicos`] 611f78b) and the outer-`Caixa` universal-axis
2042 /// text-tag family ([`Self::autores`] b5d813f, [`Self::etiquetas`]
2043 /// 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice" projection
2044 /// pattern onto a novel element-type axis (`Dep` composite vs the
2045 /// prior sibling family's `String` scalar). Sibling in shape to the
2046 /// peer per-`:supervisor`
2047 /// [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
2048 /// per-`:placement` [`crate::aplicacao::Placement::clusters`]
2049 /// (a6e18d7), per-`:membros`
2050 /// [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
2051 /// per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
2052 /// (0dcc926), and per-`:upgrade-from :instructions`
2053 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
2054 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
2055 /// typed-slot list axes, extended here to the outer top-level
2056 /// [`Caixa`] universal-axis dep-graph surface. Returns `&[Dep]`
2057 /// (not `&Vec<Dep>`) because every downstream consumer of the
2058 /// runtime-dep list treats it as a read-only sequence — the slice-
2059 /// view is the narrowest borrow that supports every present +
2060 /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`) without
2061 /// leaking the backing `Vec`'s grow/push/reserve surface no consumer
2062 /// of the typed view reaches for (the storage-side `Vec` remains
2063 /// reachable through the `pub deps` field for the mutation-carrying
2064 /// serde round-trip and per-test fixture-mutation paths). Named
2065 /// `deps()` to match the storage field's name; the accessor's
2066 /// identity maps onto the canonical CAIXA-SDLC §I vocabulary the
2067 /// slot's docstring already carries.
2068 #[must_use]
2069 pub fn deps(&self) -> &[Dep] {
2070 self.deps.as_slice()
2071 }
2072
2073 /// Substrate-canonical per-`Caixa` `:deps-dev` universal-axis
2074 /// development-only-dependency-declaration-list slice-accessor every
2075 /// consumer of the top-level manifest's dev-dep-graph axis keys off —
2076 /// returns the author-declared `:deps-dev` list verbatim as a `&[Dep]`
2077 /// slice-view over the same backing buffer the raw
2078 /// `self.deps_dev.as_slice()` field access borrows from. Empty-list-
2079 /// carrying (`:deps-dev` is a default-empty axis every `defcaixa`
2080 /// form supplies with an empty `()` when unset; the
2081 /// [`Self::from_lisp`] derive folds an omitted `:deps-dev` through
2082 /// `#[serde(default)]` to `Vec::new()`, so a `Caixa` past parse
2083 /// definitionally carries a `Vec<Dep>` slot — possibly empty — and
2084 /// the returned `&[Dep]` degenerates to an empty slice on that arm
2085 /// without any silent `None` collapse).
2086 ///
2087 /// The `:deps-dev` slot carries the universal-axis dev-only
2088 /// dependency list every kind of caixa emits under (CAIXA-SDLC §I —
2089 /// the author-facing sibling of `:deps` that every `defcaixa` form
2090 /// supplies to declare tests / lint / bench closures the runtime
2091 /// `:deps` axis does not carry; the substrate-wide dev-closure-input
2092 /// axis every downstream test-facing artifact emits under, matching
2093 /// Cargo's `[dev-dependencies]` table's dev-time-only visibility
2094 /// contract) — the typed slot's `Vec<Dep>` accept-set (empty-`:nome`
2095 /// rejected through [`DepError::NomeEmpty`], non-DNS-1123-label
2096 /// `:nome` rejected through [`DepError::NomeInvalid`], malformed
2097 /// `:versao` rejected through [`DepError::VersaoInvalid`], empty
2098 /// `:fonte.repo` rejected through [`DepError::FonteRepoEmpty`],
2099 /// within-list duplicate `:nome` rejected through
2100 /// [`DepError::DuplicateNome { list: ":deps-dev" }`]) maps onto every
2101 /// load-bearing downstream consumer the substrate carries — the
2102 /// [`Self::validate_deps`] per-entry [`Dep::validate`] + within-list
2103 /// dedup walk at caixa-core/src/manifest.rs, the
2104 /// [`crate::dep::validate_no_self_dep`] cross-list self-reference
2105 /// gate at caixa-core/src/layout.rs that checks each entry against
2106 /// the caixa's own `:nome`, the caixa-resolver
2107 /// `for dep in &root.deps_dev` closure walk at
2108 /// caixa-resolver/src/resolve.rs that seeds every dev-only git-clone
2109 /// target through the resolver's [`crate::Dep`]-keyed pipeline, and
2110 /// every future per-`Caixa` resolver-facing renderer the CAIXA-SDLC
2111 /// §I roadmap acknowledges (the future per-cluster dev-closure-audit
2112 /// overlay the M4 CR materializer resolves per-CR, the future
2113 /// `lacre.lisp` BLAKE3-closure emit walk the caixa-resolver docstring
2114 /// roadmaps).
2115 ///
2116 /// Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor —
2117 /// closes the outer-`Caixa` dependency-slot `&[Dep]` sub-family the
2118 /// sibling [`Self::deps`] (ad34b4e) opened on. The two accessors
2119 /// jointly close the two-list dep-graph surface every downstream
2120 /// resolver-facing consumer keys off (runtime `:deps` +
2121 /// dev-only `:deps-dev`, the canonical Cargo-shaped dependency-table
2122 /// pair the [`Self::validate_deps`] gate already walks in canonical
2123 /// order). Peer of the closed outer-`Caixa` foreign-code-slot
2124 /// `&[String]` sub-family ([`Self::bibliotecas`] 8a36c23,
2125 /// [`Self::exe`] 65d9527, [`Self::servicos`] 611f78b) and the outer-
2126 /// `Caixa` universal-axis text-tag family ([`Self::autores`]
2127 /// b5d813f, [`Self::etiquetas`] 78c7d3c) — folds the "outer
2128 /// [`Caixa`] `&[T]` slice" projection pattern onto the sibling
2129 /// dev-dep composite-element axis (`Dep` composite, matching the
2130 /// [`Self::deps`] element type). Sibling in shape to the peer
2131 /// per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
2132 /// (bc92bce), per-`:placement`
2133 /// [`crate::aplicacao::Placement::clusters`] (a6e18d7),
2134 /// per-`:membros` [`crate::aplicacao::AplicacaoSpec::membros`]
2135 /// (6c77e36), per-`:contratos`
2136 /// [`crate::aplicacao::AplicacaoSpec::contratos`] (0dcc926), and
2137 /// per-`:upgrade-from :instructions`
2138 /// [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
2139 /// `&[T]`-return slice accessors on the sibling per-M2 / per-M3
2140 /// typed-slot list axes, folded here to the outer top-level
2141 /// [`Caixa`] universal-axis dev-dep-graph surface. Returns `&[Dep]`
2142 /// (not `&Vec<Dep>`) because every downstream consumer of the
2143 /// dev-dep list treats it as a read-only sequence — the slice-view
2144 /// is the narrowest borrow that supports every present +
2145 /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`) without
2146 /// leaking the backing `Vec`'s grow/push/reserve surface no consumer
2147 /// of the typed view reaches for (the storage-side `Vec` remains
2148 /// reachable through the `pub deps_dev` field for the mutation-
2149 /// carrying serde round-trip and per-test fixture-mutation paths).
2150 /// Named `deps_dev()` to match the storage field's `snake_case` name;
2151 /// the kebab-case author-surface tag `:deps-dev` is the same axis
2152 /// after tatara-lisp's kebab↔snake fold and the accessor's identity
2153 /// maps onto the canonical CAIXA-SDLC §I vocabulary the slot's
2154 /// docstring already carries.
2155 #[must_use]
2156 pub fn deps_dev(&self) -> &[Dep] {
2157 self.deps_dev.as_slice()
2158 }
2159
2160 /// Substrate-canonical per-[`Caixa`] typed-dispatch read accessor
2161 /// every consumer that walks one of the two dep-list axes keyed on a
2162 /// [`crate::dep::DepList`] discriminant reaches for — routes the
2163 /// `(list: DepList) -> &[Dep]` projection through one typed method on
2164 /// the substrate primitive rather than the prior open-coded
2165 /// `match list { Prod => caixa.deps(), Dev => caixa.deps_dev() }`
2166 /// inline dispatch every per-axis walker would otherwise carry.
2167 /// Returns the author-declared per-list `Vec<Dep>` verbatim as a
2168 /// `&[Dep]` slice-view over the same backing buffer the sibling
2169 /// [`Self::deps`] (`Prod`) / [`Self::deps_dev`] (`Dev`) per-slot
2170 /// accessors borrow from, preserving the empty-list-carrying invariant
2171 /// each per-slot accessor already establishes (`:deps` / `:deps-dev`
2172 /// are default-empty axes every `defcaixa` form supplies with an empty
2173 /// `()` when unset; the [`Self::from_lisp`] derive folds an omitted
2174 /// list through `#[serde(default)]` to `Vec::new()`, so both arms
2175 /// definitionally carry a `Vec<Dep>` slot — possibly empty — and the
2176 /// returned `&[Dep]` degenerates to an empty slice on either arm
2177 /// without any silent `None` collapse).
2178 ///
2179 /// The [`crate::dep::DepList`] closed-set typed enum is the
2180 /// substrate's canonical discriminator for the "runtime-closure
2181 /// `:deps` vs dev-only-closure `:deps-dev`" axis every dep-list
2182 /// consumer dispatches on — the compiler-checked exhaustiveness on
2183 /// the enum's `match` arms is the build-time guarantee that no future
2184 /// per-list read-site regresses to a bare-`bool`-flag inline dispatch
2185 /// that a future third dep-list axis (a `:deps-build` build-only
2186 /// closure once the substrate grows cross-artifact heterogeneous
2187 /// dep-graphs, per CAIXA-SDLC §I) would silently split at every
2188 /// consumer. Prior to this the read side carried two per-slot
2189 /// accessors ([`Self::deps`] ad34b4e, [`Self::deps_dev`]) and no
2190 /// typed dispatch that a per-axis walker could parametrise on, so
2191 /// every per-list walker (the [`Self::validate_deps`] per-list
2192 /// [`crate::render::insert_first_seen`] dedup walk, a future
2193 /// `feira app graph` per-list dep summary, a future M4 per-cluster
2194 /// dev-closure-audit overlay the CR materializer resolves per-CR)
2195 /// open-coded the same two-block "run over `:deps`, then run over
2196 /// `:deps-dev`" pattern — a silent duplication that a future third
2197 /// dep-list axis would have had to grow a third block at every site.
2198 ///
2199 /// Peer of the sibling [`Self::push_dep`] typed-mutation dispatch
2200 /// (359fba5) — closes the two-side dispatch symmetry on the outer
2201 /// [`Caixa`] two-list dep-graph surface: `push_dep` on the mutation
2202 /// side, `deps_of` on the read side, both keyed on the same
2203 /// [`crate::dep::DepList`] discriminator. Same "one typed dispatch on
2204 /// the substrate primitive, thin projections at each consumer"
2205 /// discipline the sibling per-slot read accessors ([`Self::nome`]
2206 /// e6b7d97, [`Self::versao`], [`Self::kind`]) carry — extended onto
2207 /// the outer-[`Caixa`] typed-dispatch read surface.
2208 #[must_use]
2209 pub fn deps_of(&self, list: crate::dep::DepList) -> &[Dep] {
2210 match list {
2211 crate::dep::DepList::Prod => self.deps(),
2212 crate::dep::DepList::Dev => self.deps_dev(),
2213 }
2214 }
2215
2216 /// Substrate-canonical per-[`Caixa`] typed-mutation dispatch every
2217 /// consumer that appends to one of the two dep-list axes keys off
2218 /// — routes the `(list: DepList, dep: Dep)` tuple through one typed
2219 /// method on the substrate primitive rather than the prior
2220 /// `feira add`-side open-coded `if self.dev { &mut caixa.deps_dev }
2221 /// else { &mut caixa.deps }` inline dispatch + open-coded
2222 /// `.iter().any(|d| d.nome == …)` dup-check cascade. Refuses the
2223 /// mutation with the canonical typed [`DepError::DuplicateNome`] on
2224 /// a within-list name collision — the same `list: &'static str`
2225 /// diagnostic shape [`Self::validate_deps`]'s per-list
2226 /// [`crate::render::insert_first_seen`] walk raises on the peer
2227 /// parse-time within-list dedup axis, so a future author reading a
2228 /// `feira add` refusal and a `feira build` refusal reaches for the
2229 /// same corrective surface without switching diagnostic idioms.
2230 ///
2231 /// The two-arm [`crate::dep::DepList`] enum is the substrate's
2232 /// closed-set typed carrier for the "runtime-closure `:deps` vs
2233 /// dev-only-closure `:deps-dev`" axis every dep-list consumer
2234 /// dispatches on — the compiler-checked exhaustiveness on the
2235 /// enum's `match` arms is the build-time guarantee that no future
2236 /// per-list mutation-site regresses to a bare-`bool`-flag
2237 /// (`is_dev: bool`) inline dispatch that a future third
2238 /// dep-list axis (a `:deps-build` build-only closure once the
2239 /// substrate grows cross-artifact heterogeneous dep-graphs, per
2240 /// CAIXA-SDLC §I) would silently split at every consumer.
2241 ///
2242 /// Same "one typed dispatch on the substrate primitive, thin
2243 /// projections at each consumer" discipline the sibling per-slot
2244 /// read accessors ([`Self::deps`] ad34b4e, [`Self::deps_dev`],
2245 /// [`Self::nome`] e6b7d97, [`Self::versao`], [`Self::kind`])
2246 /// carry — extended onto the outer-[`Caixa`] typed-mutation surface,
2247 /// the substrate's first typed-mutation dispatch on the top-level
2248 /// manifest. The prior `feira add` open-coded `&mut caixa.deps` /
2249 /// `&mut caixa.deps_dev` inline field-access + `bail!` string-
2250 /// diagnostic path routed no through-line back to the typed slot,
2251 /// so a future extension of either dep-list axis to a richer author
2252 /// surface (a per-cluster override the operator pins through a
2253 /// future `:placement`-scoped dep-list slot the CAIXA-SDLC §I
2254 /// roadmap acknowledges, an M4
2255 /// `mesh.pleme.io/v1alpha1/Caixa` CR materializer's per-CR
2256 /// admission-webhook that normalized the list at admission time)
2257 /// would have had to be threaded through the `feira add` mutation
2258 /// site in lockstep with every read consumer or one path would
2259 /// silently disagree with the other on which list a given dep lands
2260 /// in. Lifting the resolution rule to a typed method on the
2261 /// substrate primitive means every downstream dep-list-mutating
2262 /// consumer of the top-level manifest reaches for exactly one typed
2263 /// dispatch — the resolver's accept-set migrates as a unit on any
2264 /// future axis addition.
2265 ///
2266 /// # Errors
2267 ///
2268 /// Returns [`DepError::DuplicateNome`] with `list = list.as_str()`
2269 /// when another entry in the same list already carries the same
2270 /// `:nome` — the mutation is refused and the caller can surface the
2271 /// typed diagnostic to the author (the `feira add` verb routes the
2272 /// error through `anyhow::Error::from`, which preserves the
2273 /// canonical `#[error(...)]`-templated diagnostic body).
2274 pub fn push_dep(&mut self, list: crate::dep::DepList, dep: Dep) -> Result<(), DepError> {
2275 let target = match list {
2276 crate::dep::DepList::Prod => &mut self.deps,
2277 crate::dep::DepList::Dev => &mut self.deps_dev,
2278 };
2279 if target.iter().any(|d| d.nome() == dep.nome()) {
2280 return Err(DepError::DuplicateNome {
2281 nome: dep.nome().to_string(),
2282 list: list.as_str(),
2283 });
2284 }
2285 target.push(dep);
2286 Ok(())
2287 }
2288
2289 /// Substrate-canonical per-`Caixa` `:limits` M2 typed-slot outer-
2290 /// composite Lunatic-per-process wasm32-sandboxing-composite optional-
2291 /// composite-reference accessor every consumer of the top-level
2292 /// manifest's per-Servico [`LimitsSpec`] outer-composite reader keys
2293 /// off — returns the author-declared `:limits` typed composite
2294 /// verbatim as an `Option<&LimitsSpec>` reference over the same
2295 /// backing storage the raw `self.limits.as_ref()` field access
2296 /// borrows from, with `None` naming the "no `:limits` block
2297 /// authored — every per-axis Lunatic-sandbox cap defers to the
2298 /// wasm-engine-default arm named on the per-axis
2299 /// [`LimitsSpec::memory`] / [`LimitsSpec::fuel`] /
2300 /// [`LimitsSpec::wall_clock`] / [`LimitsSpec::cpu`] scalar-accessor
2301 /// docstrings" partition every downstream Servico-M2-overlay
2302 /// emitter treats as "emit nothing" and the sibling
2303 /// [`crate::StandardLayout::verify`] per-`:limits` shape gate
2304 /// treats as "skip the per-axis
2305 /// [`crate::LimitsError::MemoryZero`] / `MemoryBelowWasm32Page` /
2306 /// `FuelZero` / `WallClockZero` / `CpuZero` refusal cascade".
2307 ///
2308 /// The outer `:limits` slot carries the M2 Servico-runtime typed
2309 /// composite — the load-bearing container of every Lunatic-shaped
2310 /// per-process wasm32-sandbox cap axis every long-running wasm
2311 /// component's runtime dispatches on (INSPIRATIONS §III.1 —
2312 /// Lunatic per-process linear-memory / fuel / wall-clock /
2313 /// millicore cap primitives translated onto pleme-io's typed
2314 /// `:limits :memory` / `:limits :fuel` / `:limits :wall-clock` /
2315 /// `:limits :cpu` sub-slot axes; CAIXA-SDLC §II — the typed-M2
2316 /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
2317 /// chart both fan on). Every per-`:limits` axis threads through a
2318 /// lifted per-slot accessor on the [`LimitsSpec`] type: the
2319 /// [`LimitsSpec::memory`] wasm32 linear-memory byte-cap scalar
2320 /// accessor, the [`LimitsSpec::fuel`] wasmtime fuel-cap scalar
2321 /// accessor, the [`LimitsSpec::wall_clock`] per-call wall-clock
2322 /// deadline scalar accessor, and the [`LimitsSpec::cpu`]
2323 /// K8s-millicore soft-CPU-share scalar accessor. Every downstream
2324 /// consumer that reaches for a limits axis first passes through
2325 /// this outer accessor onto the composite and then dispatches
2326 /// onto the per-axis accessor — the two-level dispatch means
2327 /// every per-`:limits` reader now routes through a typed dispatch
2328 /// on the substrate primitive at both altitudes.
2329 ///
2330 /// Prior to this lift the `.limits` `Option<LimitsSpec>` composite
2331 /// was accessed inline at three production sites — the
2332 /// [`crate::StandardLayout::verify`] per-`:limits` shape gate's
2333 /// `if let Some(l) = &caixa.limits { … }` traversal head
2334 /// (caixa-core/src/layout.rs:882, which drives the per-axis
2335 /// refusal cascade on the composite: the `LimitsError::MemoryZero`
2336 /// / `MemoryBelowWasm32Page` / `MemoryExceedsWasm32Max` /
2337 /// `FuelZero` / `FuelExceedsMax` / `WallClockZero` /
2338 /// `WallClockExceedsMax` / `CpuZero` / `CpuExceedsMax` refusals
2339 /// [`LimitsSpec::validate`] fans onto), the
2340 /// [`crate::render::servico_m2_overlay`] per-Servico M2 overlay
2341 /// emitter's `if let Some(limits) = &caixa.limits { … }` traversal
2342 /// head (caixa-core/src/render.rs:18504, which drives the
2343 /// `M2_KEY_LIMITS`-keyed `limits.is_empty()`-gated `serde_yaml`
2344 /// projection every `caixa-helm` / `caixa-flux` Servico values-
2345 /// block emitter fans on), and the
2346 /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
2347 /// set enumerator's `self.limits.is_some()` presence probe
2348 /// (caixa-core/src/manifest.rs:1788, which drives the
2349 /// `M2_AUTHOR_KEY_LIMITS` kebab-case author-label push every
2350 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2351 /// gate reads) — three open-coded outer-field accesses that
2352 /// expressed no compile-time link back to the typed slot at the
2353 /// [`Caixa`] altitude. A future extension of the `:limits` outer
2354 /// axis to a richer author surface (a multi-`:limits` list the M4
2355 /// CR materializer resolves per-CR at admission time so a Servico
2356 /// can expose a compute-heavy + IO-heavy limits pair, a per-
2357 /// cluster `:limits-overrides` slot the operator pins so a
2358 /// cluster-specific policy can tighten a caixa-declared cap
2359 /// without re-authoring the `caixa.lisp`, a promotion of the
2360 /// plain `Option<LimitsSpec>` to a richer
2361 /// `{static, dynamic}` partition once the wasm-engine's runtime-
2362 /// resolved dynamic-cap surface lands) would have had to be
2363 /// threaded through all three open-coded copies in lockstep or
2364 /// one consumer would silently disagree with the peers on which
2365 /// limits composite a given Caixa resolves to — the layout gate's
2366 /// per-axis bracket-dispatch seed reading the raw slot while the
2367 /// peer `servico_m2_overlay` emitter read an operator-resolved
2368 /// slot would silently split the build-time sandbox-shape gate
2369 /// from the runtime `ComputeUnit` CR emission gate, a three-
2370 /// consumer split at the layout gate, the M2 overlay emitter, and
2371 /// the declared-slot enumerator far from the source `caixa.lisp`
2372 /// with no field naming the limits-drift root cause. Lifting the
2373 /// resolution rule to a typed method on the substrate primitive
2374 /// means every downstream consumer of the caixa's per-`Caixa`
2375 /// Lunatic-sandboxing outer-composite surface reaches for exactly
2376 /// one typed dispatch — the resolver's accept-set migrates as a
2377 /// unit on any future axis addition.
2378 ///
2379 /// First outer top-level [`Caixa`] `Option<&Composite>`-return
2380 /// composite-reference accessor — opens the outer-`Caixa`
2381 /// `Option<&Composite>` composite-reference projection pattern the
2382 /// sibling per-`Caixa` `:behavior` [`crate::BehaviorSpec`] /
2383 /// `:politicas` [`crate::aplicacao::MeshPolicy`] / `:placement`
2384 /// [`crate::aplicacao::Placement`] / `:entrada`
2385 /// [`crate::aplicacao::Entrada`] future outer-composite lifts
2386 /// fold on. Peer of the M3 mesh-slot outer-composite family the
2387 /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2388 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2389 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2390 /// accessors already close on the outer [`crate::AplicacaoSpec`]
2391 /// altitude — extends that "one typed dispatch on the substrate
2392 /// primitive, thin projections at each consumer" discipline onto
2393 /// the outer top-level [`Caixa`] altitude, opening the M2 Servico-
2394 /// runtime slot family's outer-composite axis. Returns
2395 /// `Option<&LimitsSpec>` (not the owning composite by copy or
2396 /// clone) because every downstream consumer of the limits
2397 /// composite treats it as a read-only per-axis dispatch source —
2398 /// the reference-view is the narrowest borrow that supports every
2399 /// present + roadmapped consumer (per-axis accessor dispatch,
2400 /// `.is_empty()`-gated overlay projection, presence-probe early
2401 /// return on the "author-omitted `:limits` ⇒ engine-default
2402 /// applies" partition) without cloning the composite through
2403 /// every consumer's fast path. The `Option` half of the return-
2404 /// type preserves the load-bearing "author-omitted `:limits` ⇒
2405 /// engine-default applies" partition (not a default composite the
2406 /// downstream must reject on emptiness) — the accessor projects
2407 /// the raw `Option<LimitsSpec>` slot's presence bit through the
2408 /// reference-return unchanged. Named `limits()` to match the
2409 /// storage field's name verbatim and the tatara-lisp author-
2410 /// surface term (`:limits`) the field's own docstring already
2411 /// carries.
2412 #[must_use]
2413 pub fn limits(&self) -> Option<&LimitsSpec> {
2414 self.limits.as_ref()
2415 }
2416
2417 /// Substrate-canonical per-`Caixa` `:behavior` M2 typed-slot outer-
2418 /// composite OTP-`gen_server`-shaped callback-table optional-
2419 /// composite-reference accessor every consumer of the top-level
2420 /// manifest's per-Servico [`BehaviorSpec`] outer-composite reader
2421 /// keys off — returns the author-declared `:behavior` typed
2422 /// composite verbatim as an `Option<&BehaviorSpec>` reference over
2423 /// the same backing storage the raw `self.behavior.as_ref()` field
2424 /// access borrows from, with `None` naming the "no `:behavior`
2425 /// block authored — every per-callback OTP-shaped hook defers to
2426 /// the wasm-engine's runtime default arm named on the per-axis
2427 /// [`BehaviorSpec::on_init`] / [`BehaviorSpec::on_call`] /
2428 /// [`BehaviorSpec::on_cast`] / [`BehaviorSpec::on_info`] /
2429 /// [`BehaviorSpec::on_state_change`] /
2430 /// [`BehaviorSpec::on_terminate`] scalar-accessor docstrings"
2431 /// partition every downstream Servico-M2-overlay emitter treats as
2432 /// "emit nothing" and the sibling [`crate::StandardLayout::verify`]
2433 /// per-`:behavior` shape gate treats as "skip the per-arm
2434 /// [`crate::behavior::BehaviorError`] refusal cascade + the
2435 /// per-callback on-disk `MissingEntry` existence check".
2436 ///
2437 /// The outer `:behavior` slot carries the M2 Servico-runtime typed
2438 /// composite — the load-bearing container of every OTP-shaped
2439 /// per-Servico lifecycle-callback path axis every long-running wasm
2440 /// component's runtime dispatches on (INSPIRATIONS §II.3 — Erlang/
2441 /// OTP `gen_server:init/1` / `handle_call/3` / `handle_cast/2` /
2442 /// `handle_info/2` / `code_change/3` / `terminate/2` primitives
2443 /// translated onto pleme-io's typed `:behavior :on-init` /
2444 /// `:on-call` / `:on-cast` / `:on-info` / `:on-state-change` /
2445 /// `:on-terminate` sub-slot axes; CAIXA-SDLC §II — the typed-M2
2446 /// slot algebra the wasm-engine + `pleme-computeunit` Helm-library
2447 /// chart both fan on). Every per-`:behavior` axis threads through a
2448 /// lifted per-callback accessor on the [`BehaviorSpec`] type
2449 /// (9b4ecde / d66c702 / 156ddbe / 99616ac / 4846cef / 701add7).
2450 /// Every downstream consumer that reaches for a behavior axis
2451 /// first passes through this outer accessor onto the composite
2452 /// and then dispatches onto the per-callback accessor — the
2453 /// two-level dispatch means every per-`:behavior` reader now
2454 /// routes through a typed dispatch on the substrate primitive at
2455 /// both altitudes.
2456 ///
2457 /// Composes cross-slot with the M2 `:upgrade-from` gate: the
2458 /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
2459 /// cross-slot composition gate at [`crate::StandardLayout::verify`]
2460 /// keys the "per-version `:state-change` instruction must have a
2461 /// `:on-state-change` callback" precondition off this accessor's
2462 /// composite (the callback-side counterpart to the
2463 /// `:upgrade-from :instructions :state-change :script` refusal at
2464 /// the appup-side). Threading that gate's traversal input through
2465 /// this accessor closes the cross-slot invariant on the substrate
2466 /// primitive, not on the raw field.
2467 ///
2468 /// Prior to this lift the `.behavior` `Option<BehaviorSpec>`
2469 /// composite was accessed inline at four production sites — the
2470 /// [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
2471 /// `if let Some(b) = &caixa.behavior { … }` traversal head
2472 /// (caixa-core/src/layout.rs:896, which drives the per-arm
2473 /// `BehaviorError` refusal cascade + the per-callback on-disk
2474 /// [`crate::LayoutError::MissingEntry`] existence check under
2475 /// [`crate::render::LAYOUT_MISSING_ENTRY_KIND_BEHAVIOR_CALLBACK`]),
2476 /// the [`crate::upgrade::validate_upgrade_from_against_behavior`]
2477 /// cross-slot composition gate's `caixa.behavior.as_ref()`
2478 /// traversal-input feed (caixa-core/src/layout.rs:1008, which
2479 /// drives the `:state-change` ↔ `:on-state-change` precondition
2480 /// refusal), the [`crate::render::servico_m2_overlay`] per-Servico
2481 /// M2 overlay emitter's `if let Some(behavior) = &caixa.behavior
2482 /// { … }` traversal head (caixa-core/src/render.rs:18513, which
2483 /// drives the `M2_KEY_BEHAVIOR`-keyed `behavior.is_empty()`-gated
2484 /// `serde_yaml` projection every `caixa-helm` / `caixa-flux`
2485 /// Servico values-block emitter fans on), and the
2486 /// [`Self::declared_servico_slots`] per-Servico M2 declared-slot-
2487 /// set enumerator's `self.behavior.is_some()` presence probe
2488 /// (caixa-core/src/manifest.rs:1919, which drives the
2489 /// `M2_AUTHOR_KEY_BEHAVIOR` kebab-case author-label push every
2490 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
2491 /// gate reads) — four open-coded outer-field accesses that
2492 /// expressed no compile-time link back to the typed slot at the
2493 /// [`Caixa`] altitude. A future extension of the `:behavior`
2494 /// outer axis to a richer author surface (a per-callback overlay
2495 /// resolver the operator materializes at admission time so a
2496 /// cluster-specific policy can inject a per-callback tracing
2497 /// interceptor without re-authoring the `caixa.lisp`, a promotion
2498 /// of the plain `Option<BehaviorSpec>` to a richer `{static,
2499 /// dynamic}` partition once a runtime-resolved behavior-swap
2500 /// surface lands, the M4 per-callback middleware chain the
2501 /// caixa-operator's per-Servico admission webhook keys off) would
2502 /// have had to be threaded through all four open-coded copies in
2503 /// lockstep or one consumer would silently disagree with the
2504 /// peers on which behavior composite a given Caixa resolves to —
2505 /// the layout gate's per-callback existence-check seed reading
2506 /// the raw slot while the peer `servico_m2_overlay` emitter read
2507 /// an operator-resolved slot would silently split the build-time
2508 /// callback-shape gate from the runtime `ComputeUnit` CR emission
2509 /// gate from the cross-slot `:state-change` composition gate from
2510 /// the M2 declared-slot enumerator, a four-consumer split far
2511 /// from the source `caixa.lisp` with no field naming the
2512 /// behavior-drift root cause. Lifting the resolution rule to a
2513 /// typed method on the substrate primitive means every downstream
2514 /// consumer of the caixa's per-`Caixa` OTP-callback-table outer-
2515 /// composite surface reaches for exactly one typed dispatch — the
2516 /// resolver's accept-set migrates as a unit on any future axis
2517 /// addition.
2518 ///
2519 /// Second outer top-level [`Caixa`] `Option<&Composite>`-return
2520 /// composite-reference accessor — sibling to the opening
2521 /// [`Self::limits`] (b2bd9d7) accessor on the outer-`Caixa`
2522 /// `Option<&Composite>` composite-reference sub-family, extends
2523 /// the "one typed dispatch on the substrate primitive, thin
2524 /// projections at each consumer" discipline onto the second of
2525 /// the three M2 Servico-runtime slots. The remaining
2526 /// `Option<&Composite>` axes at the outer top-level [`Caixa`]
2527 /// altitude — the M3 mesh-slot family (`:politicas`,
2528 /// `:placement`, `:entrada` — already closed on the inner
2529 /// [`crate::AplicacaoSpec`] altitude via 534dc21 / 9abb8f0 /
2530 /// d32111c) — remain the future sibling lifts on the outer
2531 /// top-level projection. Returns `Option<&BehaviorSpec>` (not
2532 /// the owning composite by copy or clone) because every
2533 /// downstream consumer of the behavior composite treats it as a
2534 /// read-only per-callback dispatch source — the reference-view is
2535 /// the narrowest borrow that supports every present + roadmapped
2536 /// consumer (per-callback accessor dispatch, `.is_empty()`-gated
2537 /// overlay projection, presence-probe early return on the
2538 /// "author-omitted `:behavior` ⇒ runtime-default applies"
2539 /// partition, cross-slot `:state-change` composition input)
2540 /// without cloning the composite through every consumer's fast
2541 /// path. The `Option` half of the return-type preserves the
2542 /// load-bearing "author-omitted `:behavior` ⇒ runtime-default
2543 /// applies" partition (not a default composite the downstream
2544 /// must reject on emptiness) — the accessor projects the raw
2545 /// `Option<BehaviorSpec>` slot's presence bit through the
2546 /// reference-return unchanged. Named `behavior()` to match the
2547 /// storage field's name verbatim and the tatara-lisp author-
2548 /// surface term (`:behavior`) the field's own docstring already
2549 /// carries.
2550 #[must_use]
2551 pub fn behavior(&self) -> Option<&crate::BehaviorSpec> {
2552 self.behavior.as_ref()
2553 }
2554
2555 /// Substrate-canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
2556 /// composite MESH-COMPOSITION-shaped mesh-policy optional-composite-
2557 /// reference accessor every consumer of the top-level manifest's
2558 /// per-Aplicacao [`crate::aplicacao::MeshPolicy`] outer-composite
2559 /// reader keys off — returns the author-declared `:politicas` typed
2560 /// composite verbatim as an `Option<&MeshPolicy>` reference over the
2561 /// same backing storage the raw `self.politicas.as_ref()` field
2562 /// access borrows from, with `None` naming the "no `:politicas`
2563 /// block authored — every per-axis mesh-policy scalar defers to the
2564 /// cluster-default arm named on the per-axis
2565 /// [`crate::aplicacao::MeshPolicy::timeout`] /
2566 /// [`crate::aplicacao::MeshPolicy::retries`] /
2567 /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] /
2568 /// [`crate::aplicacao::MeshPolicy::mtls_required`] /
2569 /// [`crate::aplicacao::MeshPolicy::rate_limit`] scalar-accessor
2570 /// docstrings" partition every downstream caixa-mesh /
2571 /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2572 /// "emit no per-`:politicas` overlay" and the sibling
2573 /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2574 /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2575 /// arm.
2576 ///
2577 /// The outer `:politicas` slot carries the M3 mesh-slot per-
2578 /// Aplicacao typed composite — the load-bearing container of every
2579 /// mesh-level policy axis every Cilium NetworkPolicy / Gateway API
2580 /// v1.x HTTPRoute / future M4 per-edge policy overlay emitter fans
2581 /// on (MESH-COMPOSITION §III.2 — the Aplicacao's typed mesh-policy
2582 /// composite; §V — the "no infinite blocking" per-call deadline +
2583 /// "sandboxing-by-default" mTLS-enforcement CSE invariants; §III.3
2584 /// — the typed inter-Servico contrato-edge overlay the per-`(:de,
2585 /// :para)` mesh renderer keys off). Every per-`:politicas` axis
2586 /// threads through a lifted per-slot accessor on the
2587 /// [`crate::aplicacao::MeshPolicy`] type: the
2588 /// [`crate::aplicacao::MeshPolicy::mtls_required`] (c0110f1) Cilium
2589 /// mTLS-enforcement toggle, the
2590 /// [`crate::aplicacao::MeshPolicy::retries`] (bdfb399) transient-
2591 /// failure retry budget, the [`crate::aplicacao::MeshPolicy::timeout`]
2592 /// (7073d0f) Gateway-API per-call deadline, the
2593 /// [`crate::aplicacao::MeshPolicy::circuit_breaker`] (b0e741a)
2594 /// Envoy-outlier-detection composite. Every downstream consumer
2595 /// that reaches for a mesh-policy axis first passes through this
2596 /// outer accessor onto the composite and then dispatches onto the
2597 /// per-axis accessor — the two-level dispatch means every per-
2598 /// `:politicas` reader now routes through a typed dispatch on the
2599 /// substrate primitive at both altitudes.
2600 ///
2601 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2602 /// seed: the Aplicacao-view builder folds the outer `Option`'s
2603 /// author-omitted arm onto the [`crate::aplicacao::MeshPolicy::default`]
2604 /// cluster-default, so the peer inner [`crate::AplicacaoSpec::politicas`]
2605 /// (534dc21) `&MeshPolicy`-return accessor observes a typed
2606 /// composite whether or not the author declared the outer slot.
2607 /// The outer accessor preserves the "author-omitted vs authored-
2608 /// empty" partition the inner accessor's `is_empty()`-gated
2609 /// renderer overlay collapses — routing the presence bit through
2610 /// this accessor keeps the [`Self::declared_mesh_slots`] M3 kind-
2611 /// coherence enumerator's `M3_AUTHOR_KEY_POLITICAS` push separate
2612 /// from the inner `MeshPolicy::is_empty()`-gated overlay elision.
2613 ///
2614 /// Prior to this lift the `.politicas` `Option<MeshPolicy>`
2615 /// composite was accessed inline at two production sites — the
2616 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2617 /// `self.politicas.clone().unwrap_or_default()` traversal head
2618 /// (caixa-core/src/manifest.rs:1899, which drives the fold onto
2619 /// the [`crate::aplicacao::MeshPolicy::default`] cluster-default
2620 /// arm the inner [`crate::AplicacaoSpec::politicas`] accessor
2621 /// then observes), and the [`Self::declared_mesh_slots`] M3
2622 /// declared-slot-set enumerator's `self.politicas.is_some()`
2623 /// presence probe (caixa-core/src/manifest.rs:1961, which drives
2624 /// the `M3_AUTHOR_KEY_POLITICAS` kebab-case author-label push
2625 /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2626 /// coherence gate reads) — two open-coded outer-field accesses
2627 /// that expressed no compile-time link back to the typed slot at
2628 /// the [`Caixa`] altitude. A future extension of the `:politicas`
2629 /// outer axis to a richer author surface (a per-cluster
2630 /// `:politicas-overrides` slot the operator materializes at
2631 /// admission time so a cluster-specific policy can tighten the
2632 /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2633 /// promotion of the plain `Option<MeshPolicy>` to a richer
2634 /// `{static, dynamic}` partition once the M4 per-edge
2635 /// contrato-scoped policy-override surface lands, the M5 traffic-
2636 /// shaping composition the caixa-operator's per-Aplicacao mesh
2637 /// admission webhook keys off) would have had to be threaded
2638 /// through both open-coded copies in lockstep or the Aplicacao-
2639 /// composition seed's default-fold arm would silently disagree
2640 /// with the M3 declared-slot enumerator on which policy composite
2641 /// a given Caixa resolves to — the seed reading an operator-
2642 /// resolved slot while the enumerator's presence probe read the
2643 /// raw slot would silently split the build-time mesh-artifact
2644 /// emission gate from the M3 declared-slot enumerator's kind-
2645 /// coherence gate, a two-consumer split far from the source
2646 /// `caixa.lisp` with no field naming the policy-drift root cause.
2647 /// Lifting the resolution rule to a typed method on the substrate
2648 /// primitive means every downstream consumer of the caixa's per-
2649 /// `Caixa` MESH-COMPOSITION mesh-policy outer-composite surface
2650 /// reaches for exactly one typed dispatch — the resolver's
2651 /// accept-set migrates as a unit on any future axis addition.
2652 ///
2653 /// Third outer top-level [`Caixa`] `Option<&Composite>`-return
2654 /// composite-reference accessor — sibling to the opening
2655 /// [`Self::limits`] (b2bd9d7) and [`Self::behavior`] (35d8b52)
2656 /// accessors on the outer-`Caixa` `Option<&Composite>` composite-
2657 /// reference sub-family, extends the "one typed dispatch on the
2658 /// substrate primitive, thin projections at each consumer"
2659 /// discipline onto the first of the three M3 mesh-slot axes.
2660 /// Peer of the closed inner mesh-slot outer-composite family the
2661 /// sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
2662 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2663 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2664 /// accessor pins already close on the inner [`crate::AplicacaoSpec`]
2665 /// altitude — opens the outer top-level [`Caixa`] altitude's M3
2666 /// mesh-slot arm of the composite-reference family the remaining
2667 /// two axes (`:placement`, `:entrada`) fold onto in future
2668 /// sibling lifts. Returns `Option<&MeshPolicy>` (not the owning
2669 /// composite by copy or clone) because every downstream consumer
2670 /// of the mesh-policy composite treats it as a read-only per-axis
2671 /// dispatch source — the reference-view is the narrowest borrow
2672 /// that supports every present + roadmapped consumer (per-axis
2673 /// accessor dispatch, `.is_empty()`-gated overlay projection,
2674 /// presence-probe early return on the "author-omitted `:politicas`
2675 /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2676 /// seed's default-fold arm) without cloning the composite through
2677 /// every consumer's fast path. The `Option` half of the return-
2678 /// type preserves the load-bearing "author-omitted `:politicas` ⇒
2679 /// cluster-default applies" partition (not a default composite
2680 /// the downstream must reject on emptiness) — the accessor
2681 /// projects the raw `Option<MeshPolicy>` slot's presence bit
2682 /// through the reference-return unchanged. Named `politicas()` to
2683 /// match the storage field's name verbatim and the tatara-lisp
2684 /// author-surface term (`:politicas`) the field's own docstring
2685 /// already carries.
2686 #[must_use]
2687 pub fn politicas(&self) -> Option<&crate::aplicacao::MeshPolicy> {
2688 self.politicas.as_ref()
2689 }
2690
2691 /// Substrate-canonical per-`Caixa` `:placement` M3 mesh-slot outer-
2692 /// composite MESH-COMPOSITION-shaped distribution optional-composite-
2693 /// reference accessor every consumer of the top-level manifest's
2694 /// per-Aplicacao [`crate::aplicacao::Placement`] outer-composite
2695 /// reader keys off — returns the author-declared `:placement` typed
2696 /// composite verbatim as an `Option<&Placement>` reference over the
2697 /// same backing storage the raw `self.placement.as_ref()` field
2698 /// access borrows from, with `None` naming the "no `:placement`
2699 /// block authored — every per-axis placement scalar defers to the
2700 /// cluster-default arm named on the per-axis
2701 /// [`crate::aplicacao::Placement::estrategia`] /
2702 /// [`crate::aplicacao::Placement::clusters`] /
2703 /// [`crate::aplicacao::Placement::affinity`] /
2704 /// [`crate::aplicacao::Placement::shard_key`] scalar-accessor
2705 /// docstrings" partition every downstream caixa-mesh /
2706 /// caixa-flux / caixa-helm Aplicacao-artifact emitter treats as
2707 /// "emit no per-`:placement` overlay" and the sibling
2708 /// [`Self::aplicacao_view`] Aplicacao-composition seed folds through
2709 /// the [`crate::aplicacao::Placement::default`] cluster-default arm.
2710 ///
2711 /// The outer `:placement` slot carries the M3 mesh-slot per-
2712 /// Aplicacao typed distribution composite — the load-bearing
2713 /// container of every where-does-this-Aplicacao-run axis every
2714 /// caixa-mesh programs.yaml per-cluster distribution overlay /
2715 /// caixa-flux per-Aplicacao GitRepository/HelmRelease fan-out /
2716 /// future M4 per-Aplicacao Akka-style cluster-sharding entity-id
2717 /// resolver emitter fans on (MESH-COMPOSITION §II.4 — the
2718 /// Aplicacao's typed distribution composite; §V CSE invariants —
2719 /// "distribution is a first-class typed composite, not a runtime
2720 /// scheduler hint" the per-axis scalars enforce; §III.3 — the
2721 /// typed inter-Servico contrato-edge overlay the per-cluster
2722 /// mesh renderer keys off). Every per-`:placement` axis threads
2723 /// through a lifted per-slot accessor on the
2724 /// [`crate::aplicacao::Placement`] type: the
2725 /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
2726 /// MESH-COMPOSITION distribution-strategy scalar, the
2727 /// [`crate::aplicacao::Placement::clusters`] (a6e18d7) per-cluster
2728 /// distribution-target slice, the [`crate::aplicacao::Placement::affinity`]
2729 /// M3-Adaptive-compression-hint optional-scalar, and the
2730 /// [`crate::aplicacao::Placement::shard_key`] (7cd2a28) Akka-cluster-
2731 /// sharding extractor-expression optional-scalar. Every downstream
2732 /// consumer that reaches for a placement axis first passes through
2733 /// this outer accessor onto the composite and then dispatches onto
2734 /// the per-axis accessor — the two-level dispatch means every per-
2735 /// `:placement` reader now routes through a typed dispatch on the
2736 /// substrate primitive at both altitudes.
2737 ///
2738 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2739 /// seed: the Aplicacao-view builder folds the outer `Option`'s
2740 /// author-omitted arm onto the [`crate::aplicacao::Placement::default`]
2741 /// cluster-default, so the peer inner [`crate::AplicacaoSpec::placement`]
2742 /// (9abb8f0) `&Placement`-return accessor observes a typed composite
2743 /// whether or not the author declared the outer slot. The outer
2744 /// accessor preserves the "author-omitted vs authored-empty" partition
2745 /// the inner accessor collapses at the cluster-default fold —
2746 /// routing the presence bit through this accessor keeps the
2747 /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2748 /// `M3_AUTHOR_KEY_PLACEMENT` push separate from the inner
2749 /// [`crate::AplicacaoSpec::validate_placement`]-gated overlay
2750 /// dispatch.
2751 ///
2752 /// Prior to this lift the `.placement` `Option<Placement>`
2753 /// composite was accessed inline at two production sites — the
2754 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2755 /// `self.placement.clone().unwrap_or_default()` traversal head
2756 /// (caixa-core/src/manifest.rs:2036, which drives the fold onto
2757 /// the [`crate::aplicacao::Placement::default`] cluster-default
2758 /// arm the inner [`crate::AplicacaoSpec::placement`] accessor
2759 /// then observes), and the [`Self::declared_mesh_slots`] M3
2760 /// declared-slot-set enumerator's `self.placement.is_some()`
2761 /// presence probe (caixa-core/src/manifest.rs:2100, which drives
2762 /// the `M3_AUTHOR_KEY_PLACEMENT` kebab-case author-label push
2763 /// every [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
2764 /// coherence gate reads) — two open-coded outer-field accesses
2765 /// that expressed no compile-time link back to the typed slot at
2766 /// the [`Caixa`] altitude. A future extension of the `:placement`
2767 /// outer axis to a richer author surface (a per-cluster
2768 /// `:placement-overrides` slot the operator materializes at
2769 /// admission time so a cluster-specific placement can tighten the
2770 /// caixa-declared bound without re-authoring the `caixa.lisp`, a
2771 /// per-tenant placement-alias table the M4
2772 /// `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer resolves
2773 /// per-CR at admission time, a promotion of the plain
2774 /// `Option<Placement>` to a richer `{static, dynamic}` partition
2775 /// once Orleans-style virtual-actor dynamic placement comes into
2776 /// typed scope) would have had to be threaded through both open-
2777 /// coded copies in lockstep or the Aplicacao-composition seed's
2778 /// default-fold arm would silently disagree with the M3 declared-
2779 /// slot enumerator on which distribution composite a given Caixa
2780 /// resolves to — the seed reading an operator-resolved slot while
2781 /// the enumerator's presence probe read the raw slot would
2782 /// silently split the build-time distribution-artifact emission
2783 /// gate from the M3 declared-slot enumerator's kind-coherence
2784 /// gate, a two-consumer split far from the source `caixa.lisp`
2785 /// with no field naming the distribution-drift root cause.
2786 /// Lifting the resolution rule to a typed method on the substrate
2787 /// primitive means every downstream consumer of the caixa's per-
2788 /// `Caixa` MESH-COMPOSITION distribution outer-composite surface
2789 /// reaches for exactly one typed dispatch — the resolver's
2790 /// accept-set migrates as a unit on any future axis addition.
2791 ///
2792 /// Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
2793 /// composite-reference accessor — sibling to the opening
2794 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) M2-
2795 /// Servico-runtime pair and the peer [`Self::politicas`] (5d23d29)
2796 /// M3-mesh-slot arm on the outer-`Caixa` `Option<&Composite>`
2797 /// composite-reference sub-family, folds on the "one typed
2798 /// dispatch on the substrate primitive, thin projections at each
2799 /// consumer" discipline extended onto the second of the three M3
2800 /// mesh-slot axes. Peer of the closed inner mesh-slot outer-
2801 /// composite family the sibling
2802 /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2803 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2804 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2805 /// accessor pins already close on the inner
2806 /// [`crate::AplicacaoSpec`] altitude — folds on the outer top-
2807 /// level [`Caixa`] altitude's M3 mesh-slot arm the sibling
2808 /// [`Self::politicas`] opened, extending the discipline onto the
2809 /// second of the three M3 mesh-slot axes. The remaining M3
2810 /// mesh-slot axis (`:entrada`) folds onto this accessor's
2811 /// discipline in the final sibling lift, closing the outer top-
2812 /// level [`Caixa`] `Option<&Composite>` M3 mesh-slot sub-family.
2813 /// Returns `Option<&Placement>` (not the owning composite by copy
2814 /// or clone) because every downstream consumer of the placement
2815 /// composite treats it as a read-only per-axis dispatch source —
2816 /// the reference-view is the narrowest borrow that supports every
2817 /// present + roadmapped consumer (per-axis accessor dispatch,
2818 /// serde composite-serialization on the programs.yaml overlay,
2819 /// presence-probe early return on the "author-omitted `:placement`
2820 /// ⇒ cluster-default applies" partition, `Aplicacao`-composition
2821 /// seed's default-fold arm) without cloning the composite through
2822 /// every consumer's fast path. The `Option` half of the return-
2823 /// type preserves the load-bearing "author-omitted `:placement` ⇒
2824 /// cluster-default applies" partition (not a default composite
2825 /// the downstream must reject on emptiness) — the accessor
2826 /// projects the raw `Option<Placement>` slot's presence bit
2827 /// through the reference-return unchanged. Named `placement()` to
2828 /// match the storage field's name verbatim and the tatara-lisp
2829 /// author-surface term (`:placement`) the field's own docstring
2830 /// already carries.
2831 #[must_use]
2832 pub fn placement(&self) -> Option<&crate::aplicacao::Placement> {
2833 self.placement.as_ref()
2834 }
2835
2836 /// Substrate-canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
2837 /// composite MESH-COMPOSITION-shaped external-gateway optional-
2838 /// composite-reference accessor every consumer of the top-level
2839 /// manifest's per-Aplicacao [`crate::aplicacao::Entrada`] outer-
2840 /// composite reader keys off — returns the author-declared
2841 /// `:entrada` typed composite verbatim as an `Option<&Entrada>`
2842 /// reference over the same backing storage the raw
2843 /// `self.entrada.as_ref()` field access borrows from, with `None`
2844 /// naming the "no `:entrada` block authored — this Aplicacao is
2845 /// cluster-internal, no `Gateway`/`HTTPRoute` fan-out emitted"
2846 /// partition every downstream caixa-mesh Gateway-API artifact
2847 /// emitter treats as "emit no gateway-listener + no `HTTPRoute`
2848 /// backend for this Aplicacao" and the sibling
2849 /// [`Self::aplicacao_view`] Aplicacao-composition seed forwards
2850 /// verbatim (unlike the peer `:politicas` / `:placement` arms,
2851 /// `:entrada` has no cluster-default fold — an omitted `:entrada`
2852 /// stays `None` on the projected [`crate::AplicacaoSpec`] and the
2853 /// peer inner [`crate::AplicacaoSpec::entrada`] accessor observes
2854 /// the same `Option<&Entrada>` presence bit unchanged).
2855 ///
2856 /// The outer `:entrada` slot carries the M3 mesh-slot per-
2857 /// Aplicacao typed external-gateway composite — the load-bearing
2858 /// container of every how-does-the-outside-world-reach-this-
2859 /// Aplicacao axis every caixa-mesh `Gateway`/`HTTPRoute` fan-out
2860 /// emitter fans on (MESH-COMPOSITION §II.5 — the Aplicacao's typed
2861 /// external-entry composite; §V CSE invariants — "the external
2862 /// gateway is a first-class typed composite, not a per-Servico
2863 /// ingress annotation" the per-axis scalars enforce; §III.4 — the
2864 /// typed hostname + backend-Servico pair the per-cluster Gateway-
2865 /// API renderer keys off). Every per-`:entrada` axis threads
2866 /// through a lifted per-slot accessor on the
2867 /// [`crate::aplicacao::Entrada`] type: the
2868 /// [`crate::aplicacao::Entrada::host`] Gateway-API `Listener.hostname`
2869 /// scalar, the [`crate::aplicacao::Entrada::para`] backend-Servico
2870 /// caixa-name scalar, the [`crate::aplicacao::Entrada::paths`]
2871 /// per-rule `HTTPPathMatch` list, the [`crate::aplicacao::Entrada::port`]
2872 /// backend `trigger.service.port` scalar, and the
2873 /// [`crate::aplicacao::Entrada::resolved_paths`] URL-path fallback
2874 /// resolver every HTTPRoute-aware renderer consumes. Every
2875 /// downstream consumer that reaches for an entry axis first passes
2876 /// through this outer accessor onto the composite and then
2877 /// dispatches onto the per-axis accessor — the two-level dispatch
2878 /// means every per-`:entrada` reader now routes through a typed
2879 /// dispatch on the substrate primitive at both altitudes.
2880 ///
2881 /// Composes through [`Self::aplicacao_view`]'s Aplicacao-composition
2882 /// seed: the Aplicacao-view builder forwards the outer `Option`
2883 /// arm verbatim (no default fold — `:entrada` is inherently
2884 /// optional; a cluster-internal Aplicacao has no external gateway
2885 /// at all, not "an external gateway that defaults to nothing"), so
2886 /// the peer inner [`crate::AplicacaoSpec::entrada`] (d32111c)
2887 /// `Option<&Entrada>`-return accessor observes the same presence
2888 /// bit whether or not the author declared the outer slot. Routing
2889 /// the presence bit through this accessor keeps the
2890 /// [`Self::declared_mesh_slots`] M3 kind-coherence enumerator's
2891 /// `M3_AUTHOR_KEY_ENTRADA` push separate from the inner
2892 /// [`crate::AplicacaoSpec::validate_entrada`]-gated
2893 /// hostname/backend/path emission dispatch.
2894 ///
2895 /// Prior to this lift the `.entrada` `Option<Entrada>` composite
2896 /// was accessed inline at two production sites — the
2897 /// [`Self::aplicacao_view`] Aplicacao-composition seed's
2898 /// `self.entrada.clone()` traversal head (caixa-core/src/manifest.rs:2182,
2899 /// which drives the forward onto the peer inner
2900 /// [`crate::AplicacaoSpec::entrada`] accessor the caixa-mesh
2901 /// Gateway-API fan-out then observes), and the
2902 /// [`Self::declared_mesh_slots`] M3 declared-slot-set enumerator's
2903 /// `self.entrada.is_some()` presence probe (caixa-core/src/manifest.rs:2248,
2904 /// which drives the `M3_AUTHOR_KEY_ENTRADA` kebab-case author-
2905 /// label push every [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
2906 /// kind-coherence gate reads) — two open-coded outer-field
2907 /// accesses that expressed no compile-time link back to the typed
2908 /// slot at the [`Caixa`] altitude. A future extension of the
2909 /// `:entrada` outer axis to a richer author surface (a per-cluster
2910 /// `:entrada-overrides` slot the operator materializes at admission
2911 /// time so a cluster-specific hostname can pin the caixa-declared
2912 /// bound without re-authoring the `caixa.lisp`, a per-tenant
2913 /// gateway-alias table the M4 `mesh.pleme.io/v1alpha1/Aplicacao`
2914 /// CR materializer resolves per-CR at admission time, a promotion
2915 /// of the plain `Option<Entrada>` to a richer
2916 /// `{public, private, internal}` partition once Cilium-identity-
2917 /// scoped internal gateways come into typed scope) would have had
2918 /// to be threaded through both open-coded copies in lockstep or the
2919 /// Aplicacao-composition seed's forward arm would silently
2920 /// disagree with the M3 declared-slot enumerator on which external-
2921 /// gateway composite a given Caixa resolves to — the seed reading
2922 /// an operator-resolved slot while the enumerator's presence probe
2923 /// read the raw slot would silently split the build-time gateway-
2924 /// artifact emission gate from the M3 declared-slot enumerator's
2925 /// kind-coherence gate, a two-consumer split far from the source
2926 /// `caixa.lisp` with no field naming the entry-drift root cause.
2927 /// Lifting the resolution rule to a typed method on the substrate
2928 /// primitive means every downstream consumer of the caixa's per-
2929 /// `Caixa` MESH-COMPOSITION external-gateway outer-composite
2930 /// surface reaches for exactly one typed dispatch — the resolver's
2931 /// accept-set migrates as a unit on any future axis addition.
2932 ///
2933 /// Fifth and final outer top-level [`Caixa`] `Option<&Composite>`-
2934 /// return composite-reference accessor — closes the outer-`Caixa`
2935 /// `Option<&Composite>` composite-reference sub-family opened by
2936 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) on the
2937 /// M2 Servico-runtime arm and extended onto the M3 mesh-slot arm
2938 /// by [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074),
2939 /// folds on the "one typed dispatch on the substrate primitive,
2940 /// thin projections at each consumer" discipline extended onto the
2941 /// third and final M3 mesh-slot axis. Peer of the closed inner
2942 /// mesh-slot outer-composite family the sibling
2943 /// [`crate::AplicacaoSpec::politicas`] (534dc21) /
2944 /// [`crate::AplicacaoSpec::placement`] (9abb8f0) /
2945 /// [`crate::AplicacaoSpec::entrada`] (d32111c) composite-reference
2946 /// accessor pins already close on the inner
2947 /// [`crate::AplicacaoSpec`] altitude — this lift closes the mirror
2948 /// sub-family on the outer top-level [`Caixa`] altitude, so both
2949 /// altitudes of the outer-composite reference-return discipline
2950 /// (per-`Caixa` outer-slot presence + per-`AplicacaoSpec` inner-
2951 /// slot presence) now carry the full five-arm accept-set behind a
2952 /// typed dispatch on the substrate primitive. Returns
2953 /// `Option<&Entrada>` (not the owning composite by copy or clone)
2954 /// because every downstream consumer of the entrada composite
2955 /// treats it as a read-only per-axis dispatch source — the
2956 /// reference-view is the narrowest borrow that supports every
2957 /// present + roadmapped consumer (per-axis accessor dispatch,
2958 /// serde composite-serialization on the programs.yaml overlay,
2959 /// presence-probe early return on the "author-omitted `:entrada`
2960 /// ⇒ cluster-internal Aplicacao" partition, `Aplicacao`-composition
2961 /// seed's forward arm) without cloning the composite through every
2962 /// consumer's fast path. The `Option` half of the return-type
2963 /// preserves the load-bearing "author-omitted `:entrada` ⇒
2964 /// cluster-internal Aplicacao" partition (not a default composite
2965 /// the downstream must reject on emptiness — a cluster-internal
2966 /// Aplicacao has no external gateway at all, not "a default gateway
2967 /// that emits nothing"); the accessor projects the raw
2968 /// `Option<Entrada>` slot's presence bit through the reference-
2969 /// return unchanged. Named `entrada()` to match the storage field's
2970 /// name verbatim and the tatara-lisp author-surface term
2971 /// (`:entrada`) the field's own docstring already carries.
2972 #[must_use]
2973 pub fn entrada(&self) -> Option<&crate::aplicacao::Entrada> {
2974 self.entrada.as_ref()
2975 }
2976
2977 /// Substrate-canonical per-`Caixa` `:ci` slot accessor — returns the
2978 /// author-declared typed CI run (`canteiro_types::CiRun`) verbatim as
2979 /// an `Option<&CiRun>`, borrowed from the typed slot's own
2980 /// `Option<CiRun>` storage. `None` when the slot is absent (every
2981 /// non-`Acao` kind, and an `Acao` caixa that hasn't declared `:ci`
2982 /// yet — the latter is caught by [`crate::LayoutError::MissingCi`],
2983 /// not silently accepted).
2984 ///
2985 /// Named `ci()` to match the storage field's name and the
2986 /// tatara-lisp author surface (`:ci`); mirrors the sibling
2987 /// `Option<&Composite>` accessors on this same `Caixa` altitude
2988 /// ([`Self::limits`], [`Self::behavior`], [`Self::politicas`],
2989 /// [`Self::placement`], [`Self::entrada`]) — one typed dispatch on
2990 /// the substrate primitive rather than an open-coded `self.ci.as_ref()`
2991 /// at every consumer.
2992 #[must_use]
2993 pub fn ci(&self) -> Option<&canteiro_types::CiRun> {
2994 self.ci.as_ref()
2995 }
2996
2997 /// Substrate-canonical per-`Caixa` `:estrategia` M2 supervisor-tree-
2998 /// slot flat-spread OTP-shaped sibling-restart-strategy discriminant
2999 /// accessor every consumer of the top-level manifest's per-Supervisor
3000 /// restart-strategy axis keys off — returns the author-declared
3001 /// `:estrategia` variant verbatim as an `Option<RestartStrategy>`,
3002 /// `Copy`-projected from the typed slot's own
3003 /// `Option<crate::supervisor::RestartStrategy>` storage. Optional
3004 /// (`:estrategia` is a flat-spread supervisor-only slot every
3005 /// non-`Supervisor`-kind `defcaixa` carries as `None` by
3006 /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
3007 /// still omit to defer to [`RestartStrategy::default`] —
3008 /// [`RestartStrategy::OneForOne`] — through the [`Self::supervisor_view`]
3009 /// `unwrap_or_default()` fold; a returned `None` degenerates to the
3010 /// [`SupervisorSpec::default`]-inherited strategy without any silent
3011 /// promotion to a fresh explicit variant at the accessor boundary).
3012 ///
3013 /// The `:estrategia` slot carries the M2 typed OTP-shaped sibling-
3014 /// restart-strategy discriminant every substrate-side per-Supervisor
3015 /// dispatch fans on (INSPIRATIONS §II.2 — OTP `supervisor:strategy`
3016 /// closed-set `one_for_one | one_for_all | rest_for_one |
3017 /// simple_one_for_one` algebra translated onto pleme-io's typed
3018 /// [`RestartStrategy`] enum; CAIXA-SDLC §II — the M2 supervisor-tree
3019 /// slot algebra the operator's hierarchical reconciliation scheduler
3020 /// fans on). The slot is *flat-spread* on the outer top-level `Caixa`
3021 /// (per the field-shape docstring at caixa-core/src/manifest.rs — "The
3022 /// supervisor slots are flat on Caixa (vs nested under a
3023 /// `SupervisorSpec` sub-form) to keep tatara-lisp authoring at one
3024 /// level of nesting"), so the accessor's altitude is the outer
3025 /// [`Caixa`] surface rather than the composed [`SupervisorSpec`]
3026 /// altitude the sibling [`crate::supervisor::SupervisorSpec::estrategia`]
3027 /// (eafb619) accessor keys off. The two typed axes — the outer
3028 /// author-surface `Option<RestartStrategy>` on the [`Caixa`] altitude
3029 /// (author-omitted arm carried as `None`) and the inner post-
3030 /// composition `RestartStrategy` on the [`SupervisorSpec`] altitude
3031 /// (`Option` collapsed through the [`Self::supervisor_view`]
3032 /// `unwrap_or_default()` fold) — now share one accessor discipline for
3033 /// the shared substrate concept "the author-declared OTP-shaped
3034 /// sibling-restart-strategy variant that partitions the downstream
3035 /// per-Supervisor renderer's per-arm fan-out"; the outer-altitude
3036 /// `None` arm is the pre-composition presence bit every declared-slot
3037 /// enumerator ([`Self::declared_supervisor_slots`]) reads, and the
3038 /// inner-altitude non-`Option` `RestartStrategy` is the post-
3039 /// composition partition-dispatch input every strategy-arm consumer
3040 /// ([`SupervisorSpec::validate`], the future wasm-operator's per-
3041 /// Supervisor sibling-restart branch, the future M4
3042 /// `mesh.pleme.io/v1alpha1/Supervisor` CR materializer's admission
3043 /// webhook) fans on.
3044 ///
3045 /// Prior to this lift the `.estrategia` field was accessed inline at
3046 /// two production sites in `caixa-core/src/manifest.rs` — the
3047 /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`
3048 /// presence-probe arm at `if self.estrategia.is_some()` (which drives
3049 /// the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
3050 /// coherence gate's per-slot label push) and the [`Self::supervisor_view`]
3051 /// `SupervisorSpec` construction site at `estrategia:
3052 /// self.estrategia.unwrap_or_default()` (which composes the flat-
3053 /// spread outer author-surface `Option<RestartStrategy>` onto the
3054 /// inner post-composition [`SupervisorSpec`] `RestartStrategy` field
3055 /// the [`SupervisorSpec::estrategia`] accessor keys off) — two open-
3056 /// coded field-accesses that expressed no compile-time link back to
3057 /// the typed slot. A future extension of the outer `:estrategia` axis
3058 /// to a richer author surface (a per-cluster strategy override the
3059 /// operator pins through a future `:estrategia-overrides` overlay the
3060 /// MESH-COMPOSITION §III.2 supervision-canary roadmap acknowledges,
3061 /// a per-tenant strategy-alias table the M4 CR materializer resolves
3062 /// per-CR, a per-Supervisor dynamic strategy derivation the future
3063 /// adaptive-supervision engine computes from child-failure-history
3064 /// topology, a per-child-cohort strategy split the future
3065 /// `RestForCohort` extension the INSPIRATIONS.md §II.2 Erlang/OTP
3066 /// absorption roadmap acknowledges, a promotion of the plain
3067 /// `Option<RestartStrategy>` to a richer
3068 /// `AuthorDeclaredStrategy { declared, overlay }` newtype once the
3069 /// operator-resolved overlay lands) would have had to be threaded
3070 /// through both open-coded copies in lockstep or the enumerator's
3071 /// presence probe and the composition site's `unwrap_or_default()`
3072 /// fold would silently disagree on which strategy a given [`Caixa`]
3073 /// resolves to (an author's `:estrategia OneForAll` would satisfy
3074 /// the enumerator's presence probe while the composition site
3075 /// silently rendered a stale `OneForOne`, or vice versa). Lifting
3076 /// the resolution rule to a typed method on the substrate primitive
3077 /// means every downstream consumer of the caixa's per-`Caixa` outer-
3078 /// altitude sibling-restart-strategy surface reaches for exactly one
3079 /// typed dispatch — the resolver's accept-set migrates as a unit on
3080 /// any future axis addition.
3081 ///
3082 /// First outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
3083 /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
3084 /// axes — opens the outer-`Caixa` `Option<Copy>` flat-spread
3085 /// projection pattern the sibling per-`Caixa` `:max-restarts`
3086 /// `Option<u32>` and (through the future duration-newtype landing)
3087 /// `:restart-window` `Option<Duration>` future outer-scalar lifts
3088 /// fold on. Peer of the inner-altitude [`crate::supervisor::SupervisorSpec::estrategia`]
3089 /// (eafb619) `Copy`-return sibling-restart-strategy scalar accessor on
3090 /// the post-composition [`SupervisorSpec`] altitude — same "one
3091 /// typed dispatch on the substrate primitive, thin projections at
3092 /// each consumer" discipline extended onto the pre-composition outer
3093 /// author-surface [`Caixa`] altitude for the same OTP-shaped
3094 /// sibling-restart-strategy axis. Peer of the closed outer-`Caixa`
3095 /// `Option<&Composite>` composite-reference family the sibling
3096 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
3097 /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
3098 /// [`Self::entrada`] (e4128e4) accessor pins already carry on the
3099 /// outer `Option<&Composite>` altitude — extends the outer-`Caixa`
3100 /// typed-slot accessor discipline onto the flat-spread M2 supervisor-
3101 /// tree `Option<Copy>`-discriminant sub-family the sibling M3
3102 /// [`crate::aplicacao::Placement::estrategia`] (921fe1b)
3103 /// `PlacementStrategy` `Copy`-composite-enum scalar accessor already
3104 /// pins on the inner-altitude per-`:placement` composite. Named
3105 /// `estrategia()` to match the storage field's name and the
3106 /// per-[`SupervisorSpec`] peer [`crate::supervisor::SupervisorSpec::estrategia`]
3107 /// / per-[`crate::aplicacao::Placement`] peer
3108 /// [`crate::aplicacao::Placement::estrategia`] method-name discipline
3109 /// verbatim; the accessor's identity name maps onto the canonical
3110 /// OTP-shape supervision vocabulary the [`RestartStrategy`] enum's
3111 /// docstring already carries.
3112 #[must_use]
3113 pub fn estrategia(&self) -> Option<crate::supervisor::RestartStrategy> {
3114 self.estrategia
3115 }
3116
3117 /// Substrate-canonical per-`Caixa` `:max-restarts` M2 supervisor-tree-
3118 /// slot flat-spread OTP-`MaxIntensity`-shaped restart-budget-count
3119 /// scalar accessor every consumer of the top-level manifest's per-
3120 /// Supervisor `:max-restarts` restart-budget-count axis keys off —
3121 /// returns the author-declared `:max-restarts` typed `Option<u32>`
3122 /// verbatim, `Copy`-projected from the typed slot's own `Option<u32>`
3123 /// storage (`u32` is `Copy`, so `Option<u32>` is `Copy` and the
3124 /// accessor returns by value; no borrow of `&self` past the call).
3125 /// Optional (`:max-restarts` is a flat-spread supervisor-only slot
3126 /// every non-`Supervisor`-kind `defcaixa` carries as `None` by
3127 /// `#[serde(default)]`, and every `Supervisor`-kind `defcaixa` may
3128 /// still omit to defer to the [`Self::supervisor_view`]
3129 /// `unwrap_or(5)` fold's OTP-canonical `{intensity, 5, 60}` default).
3130 ///
3131 /// The `:max-restarts` slot carries the M2 typed Erlang/OTP-shaped
3132 /// `MaxIntensity` restart-budget count that pairs with the sibling
3133 /// `:restart-window` `Period` to form the `MaxIntensity / Period`
3134 /// restart-intensity ratio the supervisor trips its own escalation on
3135 /// (INSPIRATIONS §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}`
3136 /// worker-supervisor default; RUNTIME-PATTERNS §II.2; CAIXA-SDLC §II
3137 /// — the M2 supervisor-tree slot algebra the operator's hierarchical
3138 /// reconciliation scheduler fans on). The slot is *flat-spread* on
3139 /// the outer top-level `Caixa` (per the field-shape docstring at
3140 /// caixa-core/src/manifest.rs — "The supervisor slots are flat on
3141 /// Caixa (vs nested under a `SupervisorSpec` sub-form)"), so the
3142 /// accessor's altitude is the outer [`Caixa`] surface rather than the
3143 /// composed [`SupervisorSpec`] altitude the sibling
3144 /// [`crate::supervisor::SupervisorSpec::max_restarts`] accessor keys
3145 /// off. The two typed axes — the outer author-surface `Option<u32>`
3146 /// on the [`Caixa`] altitude (author-omitted arm carried as `None`)
3147 /// and the inner post-composition `u32` on the [`SupervisorSpec`]
3148 /// altitude (`Option` collapsed through the [`Self::supervisor_view`]
3149 /// `unwrap_or(5)` fold) — now share one accessor discipline for the
3150 /// shared substrate concept "the author-declared OTP-shaped
3151 /// restart-budget count every downstream per-Supervisor consumer's
3152 /// restart-intensity budget-vs-count comparator fans on".
3153 ///
3154 /// Prior to this lift the `.max_restarts` field was accessed inline
3155 /// at two production sites in `caixa-core/src/manifest.rs` — the
3156 /// [`Self::declared_supervisor_slots`] `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`
3157 /// presence-probe arm at `if self.max_restarts.is_some()` (which
3158 /// drives the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
3159 /// kind-coherence gate's per-slot label push) and the
3160 /// [`Self::supervisor_view`] `SupervisorSpec` construction site at
3161 /// `max_restarts: self.max_restarts.unwrap_or(5)` (which composes the
3162 /// flat-spread outer author-surface `Option<u32>` onto the inner
3163 /// post-composition [`SupervisorSpec`] `u32` field the
3164 /// [`SupervisorSpec::max_restarts`] accessor keys off) — two open-
3165 /// coded field-accesses that expressed no compile-time link back to
3166 /// the typed slot. A future extension of the outer `:max-restarts`
3167 /// axis to a richer author surface (a per-cluster restart-budget
3168 /// override the operator pins through a future `:max-restarts-overrides`
3169 /// overlay the MESH-COMPOSITION §III.2 supervision-canary roadmap
3170 /// acknowledges, a per-tenant restart-budget-alias table the M4 CR
3171 /// materializer resolves per-CR, a per-Supervisor dynamic restart-
3172 /// budget derivation the future adaptive-supervision engine computes
3173 /// from child-failure-history topology, a promotion of the plain
3174 /// `Option<u32>` count to a richer `{MaxR, MaxT}` per-child-cohort
3175 /// restart-budget-partition once the INSPIRATIONS §II.2 Erlang/OTP
3176 /// per-child-cohort roadmap lands) would have had to be threaded
3177 /// through both open-coded copies in lockstep or the enumerator's
3178 /// presence probe and the composition site's `unwrap_or(5)` fold
3179 /// would silently disagree on which restart-budget a given [`Caixa`]
3180 /// resolves to (an author's `:max-restarts 10` would satisfy the
3181 /// enumerator's presence probe while the composition site silently
3182 /// composed the OTP-canonical `5`, or vice versa). Lifting the
3183 /// resolution rule to a typed method on the substrate primitive means
3184 /// every downstream consumer of the caixa's per-`Caixa` outer-altitude
3185 /// restart-budget-count surface reaches for exactly one typed dispatch
3186 /// — the resolver's accept-set migrates as a unit on any future axis
3187 /// addition.
3188 ///
3189 /// Second outer top-level [`Caixa`] `Option<Copy>`-return supervisor-
3190 /// tree-slot flat-spread accessor for M2 supervisor-slot Copy-carry
3191 /// axes — folds on the outer-`Caixa` `Option<Copy>` flat-spread
3192 /// projection pattern the sibling per-`Caixa`
3193 /// [`Self::estrategia`] (ed04d3c) accessor opened, extends the
3194 /// sub-family onto the sibling `Option<u32>` restart-budget-count arm.
3195 /// Peer of the inner-altitude
3196 /// [`crate::supervisor::SupervisorSpec::max_restarts`] `u32` accessor
3197 /// on the post-composition [`SupervisorSpec`] altitude — same "one
3198 /// typed dispatch on the substrate primitive, thin projections at
3199 /// each consumer" discipline extended onto the pre-composition outer
3200 /// author-surface [`Caixa`] altitude for the same OTP-`MaxIntensity`-
3201 /// shaped restart-budget-count axis. Named `max_restarts()` to match
3202 /// the storage field's name and the per-[`SupervisorSpec`] peer
3203 /// [`crate::supervisor::SupervisorSpec::max_restarts`] method-name
3204 /// discipline verbatim; the accessor's identity maps onto the
3205 /// canonical OTP-shape supervision vocabulary the `:max-restarts`
3206 /// field's docstring already carries.
3207 #[must_use]
3208 pub const fn max_restarts(&self) -> Option<u32> {
3209 self.max_restarts
3210 }
3211
3212 /// Substrate-canonical per-`Caixa` `:restart-window` M2 supervisor-
3213 /// tree-slot flat-spread OTP-`Period`-shaped restart-intensity-
3214 /// denominator raw-duration-string scalar accessor every consumer of
3215 /// the top-level manifest's per-Supervisor `:restart-window` sliding-
3216 /// window axis keys off — returns the author-declared `:restart-window`
3217 /// typed `Option<String>` verbatim as an `Option<&str>`, borrowed
3218 /// from the typed slot's own `Option<String>` storage. `None` when
3219 /// the slot is absent (the canonical "never reset — every restart
3220 /// across the supervisor's lifetime counts against the sibling
3221 /// `:max-restarts` budget" sentinel every non-`Supervisor`-kind
3222 /// `defcaixa` carries by `#[serde(default)]` and every
3223 /// `Supervisor`-kind `defcaixa` may still omit to defer to the
3224 /// [`Self::supervisor_view`] `restart_window: None` composition
3225 /// through the [`crate::supervisor::duration_codec::parse`] soft-
3226 /// swallow `.and_then(|s| … .ok())` fold).
3227 ///
3228 /// The `:restart-window` slot carries the raw M2 typed Erlang/OTP-
3229 /// shaped `Period` sliding-observation-interval duration string that
3230 /// pairs with the sibling `:max-restarts` `MaxIntensity` restart-
3231 /// budget count to form the `MaxIntensity / Period` restart-intensity
3232 /// ratio the supervisor trips its own escalation on (INSPIRATIONS
3233 /// §II.2 — Erlang/OTP `supervisor` `{intensity, 5, 60}` worker-
3234 /// supervisor default; RUNTIME-PATTERNS §II.2). The outer-`Caixa`
3235 /// slot stores the raw duration string (`"60s"`, `"5m"`, `"500ms"`)
3236 /// authored under `:restart-window` — the typed [`SupervisorSpec`]
3237 /// holds an `Option<Duration>` routed through the shared
3238 /// [`crate::supervisor::duration_codec`] via `with = "duration_codec"`
3239 /// — so the outer altitude's accessor returns `Option<&str>` (raw
3240 /// authoring surface) while the inner altitude's
3241 /// [`crate::supervisor::SupervisorSpec::restart_window`] returns
3242 /// `Option<Duration>` (parsed typed surface). The parse-refusal arm
3243 /// is closed by the sibling [`Self::validate_restart_window`] gate
3244 /// that surfaces [`ManifestError::RestartWindowMalformed`] naming
3245 /// the offending value; the view-construction path
3246 /// [`Self::supervisor_view`] soft-swallows the same parse error to
3247 /// `None` to keep the view best-effort.
3248 ///
3249 /// Prior to this lift the `.restart_window` field was accessed inline
3250 /// at three production sites in `caixa-core/src/manifest.rs` — the
3251 /// [`Self::declared_supervisor_slots`]
3252 /// `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` presence-probe arm at
3253 /// `if self.restart_window.is_some()` (which drives the
3254 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
3255 /// coherence gate's per-slot label push), the
3256 /// [`Self::validate_restart_window`] `let Some(s) =
3257 /// self.restart_window.as_deref()` empty-and-shape gate binding
3258 /// (which folds the raw string through the shared
3259 /// [`crate::supervisor::duration_codec::parse`] to surface
3260 /// [`ManifestError::RestartWindowMalformed`] naming the offending
3261 /// value), and the [`Self::supervisor_view`] `self.restart_window
3262 /// .as_deref().and_then(…)` view-construction fold (which composes
3263 /// the flat-spread outer author-surface `Option<String>` onto the
3264 /// inner post-composition [`SupervisorSpec`] `Option<Duration>`
3265 /// field the [`SupervisorSpec::restart_window`] accessor keys off) —
3266 /// three open-coded field-accesses that expressed no compile-time
3267 /// link back to the typed slot. A future extension of the outer
3268 /// `:restart-window` axis to a richer author surface (a per-cluster
3269 /// window override, a per-tenant window-alias table, a per-Supervisor
3270 /// dynamic window derivation the future adaptive-supervision engine
3271 /// computes from child-failure-history topology, a promotion of the
3272 /// plain `Option<String>` raw duration to a typed `Option<Duration>`
3273 /// once the future author-surface parser lands at the [`Caixa`]
3274 /// altitude and the raw-string form is retired) would have had to be
3275 /// threaded through every open-coded copy in lockstep or the three
3276 /// consumers would silently disagree on which raw string a given
3277 /// [`Caixa`] resolves to. Lifting the resolution rule to a typed
3278 /// method on the substrate primitive means every downstream consumer
3279 /// of the caixa's per-`Caixa` outer-altitude restart-window raw-
3280 /// string surface reaches for exactly one typed dispatch — the
3281 /// resolver's accept-set migrates as a unit on any future axis
3282 /// addition.
3283 ///
3284 /// Third outer top-level [`Caixa`] supervisor-tree-slot flat-spread
3285 /// accessor — folds on the outer-`Caixa` M2 supervisor-tree flat-
3286 /// spread projection pattern the sibling per-`Caixa`
3287 /// [`Self::estrategia`] (ed04d3c) `Option<Copy>` and
3288 /// [`Self::max_restarts`] `Option<Copy>` accessors opened, extends
3289 /// the sub-family onto the sibling `Option<&str>` raw-duration-
3290 /// string arm (the outer altitude's raw-string form; the inner
3291 /// altitude's parsed [`Duration`] form is the peer
3292 /// [`crate::supervisor::SupervisorSpec::restart_window`] accessor).
3293 /// Peer of the sibling per-`Caixa` `Option<&str>`-return scalar
3294 /// accessors ([`Self::licenca`] / [`Self::repositorio`] /
3295 /// [`Self::descricao`] / [`Self::edicao`]) on the universal-axis
3296 /// outer scalar-projection family the outer-`Caixa` `Option<&str>`
3297 /// sub-family already carries — same "one typed dispatch on the
3298 /// substrate primitive, thin projections at each consumer"
3299 /// discipline extended onto the M2 supervisor-tree flat-spread
3300 /// `Option<&str>` raw-duration-string arm. Named `restart_window()`
3301 /// to match the storage field's name and the per-[`SupervisorSpec`]
3302 /// peer [`crate::supervisor::SupervisorSpec::restart_window`]
3303 /// method-name discipline verbatim; the accessor's identity maps
3304 /// onto the canonical OTP-shape supervision vocabulary the
3305 /// `:restart-window` field's docstring already carries.
3306 #[must_use]
3307 pub fn restart_window(&self) -> Option<&str> {
3308 self.restart_window.as_deref()
3309 }
3310
3311 /// Substrate-canonical per-`Caixa` `:upgrade-from` M2 typed-slot
3312 /// outer-composite OTP-appup-shaped per-prior-version migration-
3313 /// entry-list slice accessor every consumer of the top-level
3314 /// manifest's per-Servico hot-upgrade-block `&[UpgradeFromEntry]`
3315 /// slice-view keys off — returns the author-declared `:upgrade-from`
3316 /// typed `Vec<UpgradeFromEntry>` verbatim as a
3317 /// `&[UpgradeFromEntry]` slice-view over the same backing buffer
3318 /// the raw `self.upgrade_from.as_slice()` field access borrows
3319 /// from. Empty-slice-carrying (the "no hot-upgrade path declared"
3320 /// arm every `defcaixa` without an `:upgrade-from` block carries;
3321 /// the [`Self::from_lisp`] derive folds an omitted `:upgrade-from`
3322 /// through `#[serde(default)]` to `Vec::new()`, so a `Caixa` past
3323 /// parse definitionally carries a `Vec<UpgradeFromEntry>` slot —
3324 /// possibly empty — and the returned `&[UpgradeFromEntry]`
3325 /// degenerates to an empty slice on that arm without any silent
3326 /// `None` collapse).
3327 ///
3328 /// The outer `:upgrade-from` slot carries the M2 typed OTP-appup
3329 /// migration block — the load-bearing container of every per-
3330 /// prior-`:versao` migration-instruction list the wasm-operator
3331 /// dispatches on at hot-upgrade time (INSPIRATIONS §II.4 — OTP
3332 /// `.appup` per-prior-version `LoadModule | StateChange |
3333 /// SoftPurge | Purge | Restart` instruction algebra translated
3334 /// onto pleme-io's typed `:upgrade-from :from` + `:instructions`
3335 /// entry list; CAIXA-SDLC §II — the typed-M2 slot algebra the
3336 /// operator's hot-upgrade dispatch fans on). Every per-entry axis
3337 /// threads through a lifted per-entry accessor on the
3338 /// [`UpgradeFromEntry`] type: the
3339 /// [`UpgradeFromEntry::prior_versao`] SemVer-shaped previous-
3340 /// version scalar accessor and the
3341 /// [`UpgradeFromEntry::instructions`] `&[UpgradeInstruction]`-
3342 /// return per-entry instruction-list accessor (0137e5a). Every
3343 /// downstream consumer of the hot-upgrade path first passes
3344 /// through this outer accessor onto the slice and then dispatches
3345 /// per-entry through the inner accessors — the two-level dispatch
3346 /// means every per-`:upgrade-from` reader now routes through a
3347 /// typed dispatch on the substrate primitive at both altitudes.
3348 ///
3349 /// Prior to this lift the `.upgrade_from` `Vec<UpgradeFromEntry>`
3350 /// slot was accessed inline at production sites across three
3351 /// files — the [`Self::declared_servico_slots`] M2 declared-slot
3352 /// enumerator's `self.upgrade_from.is_empty()` presence probe
3353 /// (caixa-core/src/manifest.rs, which drives the
3354 /// `M2_AUTHOR_KEY_UPGRADE_FROM` kebab-case author-label push every
3355 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-coherence
3356 /// gate reads), the [`crate::StandardLayout::verify`] per-
3357 /// `:upgrade-from` three-stage validation pass (caixa-core/src/
3358 /// layout.rs, which fans onto the
3359 /// [`crate::upgrade::validate_upgrade_from`] per-entry shape +
3360 /// cross-entry duplicate gate, the
3361 /// [`crate::upgrade::validate_upgrade_from_against_versao`]
3362 /// SemVer-precedence cross-slot gate, the
3363 /// [`crate::upgrade::validate_upgrade_from_against_behavior`]
3364 /// `:state-change` ↔ `:on-state-change` cross-slot composition
3365 /// gate, and the per-instruction script-path existence-probe walk
3366 /// that reads each entry's [`UpgradeFromEntry::instructions`] to
3367 /// resolve every declared migration script against the layout
3368 /// root), and the [`crate::render::servico_m2_overlay`] per-
3369 /// Servico M2 overlay emitter's `!caixa.upgrade_from.is_empty()`
3370 /// presence gate + `serde_yaml::to_value(&caixa.upgrade_from)`
3371 /// projection (caixa-core/src/render.rs, which drives the
3372 /// `M2_KEY_UPGRADE_FROM`-keyed `serde_yaml` projection every
3373 /// `caixa-helm` / `caixa-flux` Servico values-block emitter fans
3374 /// on and lands as the ComputeUnit CR's `spec.upgradeFrom` field).
3375 /// A future extension of the outer `:upgrade-from` axis (a per-
3376 /// cluster `:upgrade-overrides` overlay the wasm-engine operator
3377 /// resolves at admission time so a cluster-specific migration
3378 /// policy can tighten a caixa-declared step without re-authoring
3379 /// the `caixa.lisp`, promotion of the plain
3380 /// `Vec<UpgradeFromEntry>` to a richer `{static, dynamic}`
3381 /// partition once runtime-resolved hot-upgrade instructions land,
3382 /// per-entry priority annotation once multi-strategy fan-out
3383 /// lands) would have had to be threaded through all six open-
3384 /// coded copies in lockstep or one consumer would silently
3385 /// disagree with the peers on which upgrade slice a given Caixa
3386 /// resolves to — a six-consumer split at the enumerator, the
3387 /// three-stage validate pass, the script-path probe walk, and the
3388 /// M2 overlay emitter, far from the source `caixa.lisp` with no
3389 /// field naming the upgrade-drift root cause. Lifting the
3390 /// resolution rule to a typed method on the substrate primitive
3391 /// means every downstream consumer of the caixa's per-`Caixa`
3392 /// OTP-appup outer-slice surface reaches for exactly one typed
3393 /// dispatch — the resolver's accept-set migrates as a unit on any
3394 /// future axis addition.
3395 ///
3396 /// First outer top-level [`Caixa`] `&[Composite]`-return slice
3397 /// accessor for M2 / M3 typed-slot vec-carry axes — opens the
3398 /// outer-`Caixa` `&[Composite]` composite-slice projection
3399 /// pattern the sibling `:children`
3400 /// [`crate::supervisor::ChildSpec`] / `:membros`
3401 /// [`crate::aplicacao::Membro`] / `:contratos`
3402 /// [`crate::aplicacao::WitContract`] future outer-composite-slice
3403 /// lifts fold on. Peer of the closed outer-`Caixa` scalar
3404 /// `Option<&Composite>` composite-reference family the sibling
3405 /// [`Self::limits`] (b2bd9d7) / [`Self::behavior`] (35d8b52) /
3406 /// [`Self::politicas`] (5d23d29) / [`Self::placement`] (4fb8074) /
3407 /// [`Self::entrada`] (e4128e4) accessors closed on the outer
3408 /// `Option<&Composite>` altitude, extended here to the outer-
3409 /// `Caixa` `&[Composite]` vec-carry altitude. Peer at the inner
3410 /// altitude of [`crate::upgrade::UpgradeFromEntry::instructions`]
3411 /// (0137e5a) — same "one typed dispatch on the substrate
3412 /// primitive, thin projections at each consumer" discipline
3413 /// folded onto the outer top-level [`Caixa`] altitude, opening the
3414 /// M2 vec-carry slot family's outer-composite-slice axis. Sibling
3415 /// in shape to the peer outer-`Caixa` `&[Dep]`-return
3416 /// [`Self::deps`] (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and
3417 /// `&[String]`-return [`Self::autores`] (b5d813f) /
3418 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`]
3419 /// (8a36c23) / [`Self::exe`] (65d9527) / [`Self::servicos`]
3420 /// (611f78b) slice-accessors on the sibling outer-`Caixa` scalar-
3421 /// element vec-carry axes — folds the "outer [`Caixa`] `&[T]`
3422 /// slice" projection pattern onto the sibling M2 typed-composite-
3423 /// element axis (`UpgradeFromEntry` composite, matching the
3424 /// per-inner [`UpgradeFromEntry::instructions`] element type at a
3425 /// different altitude).
3426 ///
3427 /// Returns `&[UpgradeFromEntry]` (not `&Vec<UpgradeFromEntry>`)
3428 /// because every downstream consumer of the hot-upgrade list
3429 /// treats it as a read-only sequence — the slice-view is the
3430 /// narrowest borrow that supports every present + roadmapped
3431 /// consumer (`.iter()`, `.len()`, `.is_empty()`, `serde` slice-
3432 /// serialization through
3433 /// `serde_yaml::to_value(&[UpgradeFromEntry])`) without leaking
3434 /// the backing `Vec`'s grow/push/reserve surface no consumer of
3435 /// the typed view reaches for (the storage-side `Vec` remains
3436 /// reachable through the `pub upgrade_from` field for the
3437 /// mutation-carrying serde round-trip and per-test fixture-
3438 /// mutation paths). Named `upgrade_from()` to match the storage
3439 /// field's `snake_case` name; the kebab-case author-surface tag
3440 /// `:upgrade-from` is the same axis after tatara-lisp's
3441 /// kebab↔snake fold and the accessor's identity maps onto the
3442 /// canonical CAIXA-SDLC §II vocabulary the slot's docstring
3443 /// already carries.
3444 #[must_use]
3445 pub fn upgrade_from(&self) -> &[UpgradeFromEntry] {
3446 self.upgrade_from.as_slice()
3447 }
3448
3449 /// Substrate-canonical per-`Caixa` `:children` M2 supervisor-tree-
3450 /// slot outer-composite OTP-shaped per-supervisor static-child-list
3451 /// slice accessor every consumer of the top-level manifest's per-
3452 /// Supervisor `&[ChildSpec]` slice-view keys off — returns the
3453 /// author-declared `:children` typed `Vec<crate::supervisor::ChildSpec>`
3454 /// verbatim as a `&[crate::supervisor::ChildSpec]` slice-view over
3455 /// the same backing buffer the raw `self.children.as_slice()` field
3456 /// access borrows from. Empty-slice-carrying (the "no static children
3457 /// declared" arm every non-`Supervisor`-kind `defcaixa` carries by
3458 /// #[serde(default)] and every `SimpleOneForOne` supervisor carries
3459 /// by [`crate::supervisor::SupervisorError::SimpleOneForOneWithStaticChildren`]
3460 /// gate; the returned `&[ChildSpec]` degenerates to an empty slice
3461 /// on those arms without any silent `None` collapse).
3462 ///
3463 /// The outer `:children` slot carries the M2 typed OTP-supervisor
3464 /// static-child list — the load-bearing container of every per-
3465 /// child `{caixa, versao, restart}` triple the wasm-operator's
3466 /// hierarchical reconciler dispatches on at supervisor-tree
3467 /// materialization time (INSPIRATIONS §II.2 — OTP `supervisor:init/1`
3468 /// static-child list translated onto pleme-io's typed
3469 /// [`crate::supervisor::ChildSpec`] entry list; CAIXA-SDLC §II —
3470 /// the typed-M2 slot algebra the operator's per-supervisor fan-out
3471 /// dispatch fans on). Every per-child axis threads through a lifted
3472 /// per-entry accessor on the [`crate::supervisor::ChildSpec`] type:
3473 /// the [`crate::supervisor::ChildSpec::nome`] DNS-1123-label
3474 /// child-caixa-identity scalar accessor, the peer versao SemVer-2
3475 /// version-requirement scalar accessor, and the
3476 /// [`crate::supervisor::ChildSpec::restart`] `Copy`-composite-enum
3477 /// per-child post-exit restart-decision-policy discriminant
3478 /// accessor (dfb4a81). Every downstream consumer of the supervisor-
3479 /// tree path first passes through this outer accessor onto the
3480 /// slice and then dispatches per-child through the inner accessors
3481 /// — the two-level dispatch means every per-`:children` reader now
3482 /// routes through a typed dispatch on the substrate primitive at
3483 /// both altitudes.
3484 ///
3485 /// Prior to this lift the `.children` `Vec<ChildSpec>` slot was
3486 /// accessed inline at three production sites across two files —
3487 /// the [`Self::declared_supervisor_slots`] supervisor-tree
3488 /// declared-slot enumerator's `!self.children.is_empty()` presence
3489 /// probe (caixa-core/src/manifest.rs, which drives the
3490 /// `SUPERVISOR_AUTHOR_KEY_CHILDREN` kebab-case author-label push
3491 /// every [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
3492 /// kind-coherence gate reads), the [`Self::supervisor_view`]
3493 /// per-supervisor typed-view composer's `self.children.clone()`
3494 /// per-child fold-in path (caixa-core/src/manifest.rs, which
3495 /// materializes the typed [`crate::supervisor::SupervisorSpec`]
3496 /// view every [`crate::StandardLayout::verify`] Supervisor-arm gate
3497 /// dispatches on), and the [`crate::StandardLayout::verify`] per-
3498 /// `:children :caixa` self-parent refusal probe's
3499 /// `&caixa.children`-borrowed
3500 /// [`crate::supervisor::validate_no_self_supervision`] input
3501 /// (caixa-core/src/layout.rs, which pins the "no child names the
3502 /// supervisor's own `:nome`" cross-slot coherence gate). A future
3503 /// extension of the outer `:children` axis (a per-cluster
3504 /// `:children-overrides` overlay the wasm-engine operator resolves
3505 /// at admission time so a cluster-specific child-set can tighten
3506 /// a caixa-declared list without re-authoring the `caixa.lisp`,
3507 /// promotion of the plain `Vec<ChildSpec>` to a richer
3508 /// `{static, dynamic}` partition once Erlang/OTP's
3509 /// `simple_one_for_one`-shaped dynamic-child slot lands as a typed
3510 /// axis, per-child priority annotation once multi-strategy fan-out
3511 /// lands) would have had to be threaded through all three open-
3512 /// coded copies in lockstep or one consumer would silently
3513 /// disagree with the peers on which child slice a given Caixa
3514 /// resolves to — the enumerator's presence probe reading the raw
3515 /// slot while the peer view-composer's fold-in path read an
3516 /// operator-resolved slot would silently split the paired
3517 /// declared-slot enumerator and typed-view composition, and the
3518 /// [`crate::supervisor::validate_no_self_supervision`] self-parent
3519 /// refusal probe reading a third borrow would silently drift the
3520 /// cross-slot coherence gate's traversal input from the two peers,
3521 /// a three-consumer split at the enumerator, the view composer,
3522 /// and the self-parent gate far from the source `caixa.lisp` with
3523 /// no field naming the child-set-drift root cause. Lifting the
3524 /// resolution rule to a typed method on the substrate primitive
3525 /// means every downstream consumer of the caixa's per-`Caixa`
3526 /// OTP-supervisor outer-slice surface reaches for exactly one
3527 /// typed dispatch — the resolver's accept-set migrates as a unit
3528 /// on any future axis addition.
3529 ///
3530 /// Second outer top-level [`Caixa`] `&[Composite]`-return slice
3531 /// accessor for M2 / M3 typed-slot vec-carry axes — folds on the
3532 /// outer-`Caixa` `&[Composite]` composite-slice sub-family the
3533 /// sibling [`Self::upgrade_from`] (2a1f907) accessor opened, peer
3534 /// at the outer altitude of the closed inner-`SupervisorSpec`
3535 /// [`crate::SupervisorSpec::children`] (bc92bce) accessor on the
3536 /// same OTP-supervisor static-child-list axis — same "byte-equal,
3537 /// borrow-shared" outer-accessor discipline extended onto the
3538 /// second outer-`Caixa` `&[Composite]` vec-carry axis. Sibling in
3539 /// shape to the peer outer-`Caixa` `&[Dep]`-return [`Self::deps`]
3540 /// (ad34b4e) / [`Self::deps_dev`] (f7fd81e) and `&[String]`-return
3541 /// [`Self::autores`] (b5d813f) / [`Self::etiquetas`] (78c7d3c) /
3542 /// [`Self::bibliotecas`] (8a36c23) / [`Self::exe`] (65d9527) /
3543 /// [`Self::servicos`] (611f78b) slice-accessors on the sibling
3544 /// outer-`Caixa` scalar-element vec-carry axes — folds the "outer
3545 /// [`Caixa`] `&[T]` slice" projection pattern onto the sibling
3546 /// M2 typed-composite-element axis
3547 /// ([`crate::supervisor::ChildSpec`] composite, matching the
3548 /// per-inner [`crate::SupervisorSpec::children`] element type at a
3549 /// different altitude).
3550 ///
3551 /// Returns `&[crate::supervisor::ChildSpec]` (not
3552 /// `&Vec<ChildSpec>`) because every downstream consumer of the
3553 /// child list treats it as a read-only sequence — the slice-view
3554 /// is the narrowest borrow that supports every present +
3555 /// roadmapped consumer (`.iter()`, `.len()`, `.is_empty()`, the
3556 /// [`crate::supervisor::validate_no_self_supervision`] `&[ChildSpec]`
3557 /// input, `serde` slice-serialization) without leaking the backing
3558 /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3559 /// reaches for (the storage-side `Vec` remains reachable through
3560 /// the `pub children` field for the mutation-carrying serde round-
3561 /// trip and per-test fixture-mutation paths, including the
3562 /// [`Self::supervisor_view`] fold-in path that clones the slot
3563 /// into the typed view). Named `children()` to match the storage
3564 /// field's name verbatim and the tatara-lisp author-surface term
3565 /// (`:children`) the field's own docstring already carries; the
3566 /// accessor's identity maps onto the canonical OTP supervision
3567 /// vocabulary the [`Caixa::children`] field's docstring already
3568 /// reaches for ("Static children of a supervisor").
3569 #[must_use]
3570 pub fn children(&self) -> &[crate::supervisor::ChildSpec] {
3571 self.children.as_slice()
3572 }
3573
3574 /// Substrate-canonical per-`Caixa` `:membros` M3 mesh-slot outer-
3575 /// composite MESH-COMPOSITION-shaped per-Aplicacao member-list slice
3576 /// accessor every consumer of the top-level manifest's per-Aplicacao
3577 /// `&[crate::aplicacao::Membro]` slice-view keys off — returns the
3578 /// author-declared `:membros` typed `Vec<crate::aplicacao::Membro>`
3579 /// verbatim as a `&[crate::aplicacao::Membro]` slice-view over the
3580 /// same backing buffer the raw `self.membros.as_slice()` field access
3581 /// borrows from. Empty-slice-carrying (the "no members declared" arm
3582 /// every non-`Aplicacao`-kind `defcaixa` carries by `#[serde(default)]`
3583 /// and every partially-authored Aplicacao carries before the
3584 /// [`crate::AplicacaoError::MembrosEmpty`] gate fires; the returned
3585 /// `&[Membro]` degenerates to an empty slice on those arms without any
3586 /// silent `None` collapse).
3587 ///
3588 /// The outer `:membros` slot carries the M3 typed MESH-COMPOSITION
3589 /// per-Aplicacao member list — the load-bearing container of every
3590 /// per-member `{caixa, versao}` pair the caixa-mesh renderer's
3591 /// per-Aplicacao program-emission dispatch fans on at mesh-artifact
3592 /// materialization time (MESH-COMPOSITION §III.1 — the typed graph's
3593 /// vertex set the `:contratos` `:de`/`:para` edges resolve against and
3594 /// the `:entrada :para` external-gateway destination validates
3595 /// against; CAIXA-SDLC §II — the typed-M3 slot algebra the operator's
3596 /// per-Aplicacao fan-out dispatch fans on). Every per-member axis
3597 /// threads through a lifted per-entry accessor on the
3598 /// [`crate::aplicacao::Membro`] type: the
3599 /// [`crate::aplicacao::Membro::nome`] DNS-1123-label member-caixa-
3600 /// identity scalar accessor (4a32abf) and the peer
3601 /// [`crate::aplicacao::Membro::versao_requirement`] SemVer-2
3602 /// version-requirement scalar accessor (a40b0e3). Every downstream
3603 /// consumer of the mesh-graph path first passes through this outer
3604 /// accessor onto the slice and then dispatches per-member through
3605 /// the inner accessors — the two-level dispatch means every per-
3606 /// `:membros` reader now routes through a typed dispatch on the
3607 /// substrate primitive at both altitudes.
3608 ///
3609 /// Prior to this lift the `.membros` `Vec<Membro>` slot was accessed
3610 /// inline at three production sites across two files — the
3611 /// [`Self::declared_mesh_slots`] mesh-slot declared-slot
3612 /// enumerator's `!self.membros.is_empty()` presence probe
3613 /// (caixa-core/src/manifest.rs, which drives the
3614 /// `M3_AUTHOR_KEY_MEMBROS` kebab-case author-label push every
3615 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3616 /// gate reads), the [`Self::aplicacao_view`] per-Aplicacao typed-view
3617 /// composer's `self.membros.clone()` per-member fold-in path
3618 /// (caixa-core/src/manifest.rs, which materializes the typed
3619 /// [`crate::aplicacao::AplicacaoSpec`] view every
3620 /// [`crate::StandardLayout::verify`] Aplicacao-arm gate dispatches
3621 /// on), and the [`crate::StandardLayout::verify`] per-`:membros
3622 /// :caixa` self-membership refusal probe's `&caixa.membros`-borrowed
3623 /// [`crate::aplicacao::validate_no_self_membership`] input
3624 /// (caixa-core/src/layout.rs, which pins the "no member names the
3625 /// Aplicacao's own `:nome`" cross-slot coherence gate). A future
3626 /// extension of the outer `:membros` axis (a per-cluster
3627 /// `:membros-overrides` overlay the wasm-engine operator resolves at
3628 /// admission time so a cluster-specific member-set can tighten a
3629 /// caixa-declared list without re-authoring the `caixa.lisp`,
3630 /// promotion of the plain `Vec<Membro>` to a richer
3631 /// `{static, dynamic}` partition once runtime-resolved Aplicacao
3632 /// members land as a typed axis, per-member priority annotation once
3633 /// multi-strategy fan-out lands) would have had to be threaded
3634 /// through all three open-coded copies in lockstep or one consumer
3635 /// would silently disagree with the peers on which member slice a
3636 /// given Caixa resolves to — the enumerator's presence probe reading
3637 /// the raw slot while the peer view-composer's fold-in path read an
3638 /// operator-resolved slot would silently split the paired
3639 /// declared-slot enumerator and typed-view composition, and the
3640 /// [`crate::aplicacao::validate_no_self_membership`] self-membership
3641 /// refusal probe reading a third borrow would silently drift the
3642 /// cross-slot coherence gate's traversal input from the two peers, a
3643 /// three-consumer split at the enumerator, the view composer, and
3644 /// the self-membership gate far from the source `caixa.lisp` with no
3645 /// field naming the member-set-drift root cause. Lifting the
3646 /// resolution rule to a typed method on the substrate primitive
3647 /// means every downstream consumer of the caixa's per-`Caixa`
3648 /// MESH-COMPOSITION outer-slice surface reaches for exactly one
3649 /// typed dispatch — the resolver's accept-set migrates as a unit on
3650 /// any future axis addition.
3651 ///
3652 /// Third outer top-level [`Caixa`] `&[Composite]`-return slice
3653 /// accessor for M2 / M3 typed-slot vec-carry axes — opens the outer-
3654 /// `Caixa` M3 mesh-slot arm of the `&[Composite]` composite-slice
3655 /// sub-family the sibling M2 [`Self::upgrade_from`] (2a1f907) /
3656 /// [`Self::children`] (c17b51e) accessors opened for the M2 vec-carry
3657 /// altitude. Peer at the outer altitude of the closed inner-
3658 /// [`crate::AplicacaoSpec::membros`] (6c77e36) accessor on the same
3659 /// MESH-COMPOSITION per-Aplicacao member-list axis — the two
3660 /// altitudes now share the same "byte-equal, borrow-shared" outer-
3661 /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3662 /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3663 /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3664 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3665 /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3666 /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3667 /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3668 /// pattern onto the sibling M3 typed-composite-element axis
3669 /// ([`crate::aplicacao::Membro`] composite, matching the per-inner
3670 /// [`crate::AplicacaoSpec::membros`] element type at a different
3671 /// altitude).
3672 ///
3673 /// Returns `&[crate::aplicacao::Membro]` (not `&Vec<Membro>`)
3674 /// because every downstream consumer of the member list treats it
3675 /// as a read-only sequence — the slice-view is the narrowest borrow
3676 /// that supports every present + roadmapped consumer (`.iter()`,
3677 /// `.len()`, `.is_empty()`, the
3678 /// [`crate::aplicacao::validate_no_self_membership`] `&[Membro]`
3679 /// input, `serde` slice-serialization) without leaking the backing
3680 /// `Vec`'s grow/push/reserve surface no consumer of the typed view
3681 /// reaches for (the storage-side `Vec` remains reachable through the
3682 /// `pub membros` field for the mutation-carrying serde round-trip
3683 /// and per-test fixture-mutation paths, including the
3684 /// [`Self::aplicacao_view`] fold-in path that clones the slot into
3685 /// the typed view). Named `membros()` to match the storage field's
3686 /// name verbatim and the tatara-lisp author-surface term
3687 /// (`:membros`) the field's own docstring already carries; the
3688 /// accessor's identity maps onto the canonical MESH-COMPOSITION
3689 /// vocabulary the [`Caixa::membros`] field's docstring already
3690 /// reaches for ("Member Servicos that make up this Aplicacao").
3691 #[must_use]
3692 pub fn membros(&self) -> &[crate::aplicacao::Membro] {
3693 self.membros.as_slice()
3694 }
3695
3696 /// Substrate-canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
3697 /// composite MESH-COMPOSITION-shaped per-Aplicacao WIT-typed
3698 /// inter-Servico contract-list slice accessor every consumer of the
3699 /// top-level manifest's per-Aplicacao `&[crate::aplicacao::WitContract]`
3700 /// slice-view keys off — returns the author-declared `:contratos`
3701 /// typed `Vec<crate::aplicacao::WitContract>` verbatim as a
3702 /// `&[crate::aplicacao::WitContract]` slice-view over the same
3703 /// backing buffer the raw `self.contratos.as_slice()` field access
3704 /// borrows from. Empty-slice-carrying (the "no contracts declared"
3705 /// arm every non-`Aplicacao`-kind `defcaixa` carries by
3706 /// `#[serde(default)]` and every leaf Aplicacao carrying only a
3707 /// single member with no inter-Servico edge carries; the returned
3708 /// `&[WitContract]` degenerates to an empty slice on those arms
3709 /// without any silent `None` collapse).
3710 ///
3711 /// The outer `:contratos` slot carries the M3 typed MESH-COMPOSITION
3712 /// per-Aplicacao WIT-typed inter-Servico edge list — the load-bearing
3713 /// container of every per-edge `{de, para, wit, endpoint | subject |
3714 /// slot}` quadruple the caixa-mesh renderer's per-Aplicacao
3715 /// `CiliumNetworkPolicy` fan-out (one L7 policy per edge —
3716 /// MESH-COMPOSITION §III.2 point 2) and per-`(:de, :para)`
3717 /// adjacency-list seed dispatch on at mesh-artifact materialization
3718 /// time (MESH-COMPOSITION §III.1 — the typed graph's edge set the
3719 /// `:membros` vertex set resolves against, closed by the
3720 /// [`crate::AplicacaoError::ContractoUnknownMember`] / cycle-refusal
3721 /// gates in §III.3; CAIXA-SDLC §II — the typed-M3 slot algebra the
3722 /// operator's per-Aplicacao fan-out dispatch fans on). Every
3723 /// per-edge axis threads through a lifted per-entry accessor on the
3724 /// [`crate::aplicacao::WitContract`] type: the peer `de` / `para`
3725 /// DNS-1123-label member-caixa-name endpoint scalar accessors, the
3726 /// [`crate::aplicacao::WitContract::endpoint`] (7020470) HTTP-shape
3727 /// / [`crate::aplicacao::WitContract::subject`] (90de675)
3728 /// NATS-pub-sub-shape / [`crate::aplicacao::WitContract::slot`]
3729 /// (ed22b66) `wasi:keyvalue/store`-shape payload-carrier accessors,
3730 /// and the WIT-world discriminant. Every downstream consumer of the
3731 /// mesh-graph edge path first passes through this outer accessor
3732 /// onto the slice and then dispatches per-contract through the
3733 /// inner accessors — the two-level dispatch means every
3734 /// per-`:contratos` reader now routes through a typed dispatch on
3735 /// the substrate primitive at both altitudes.
3736 ///
3737 /// Prior to this lift the `.contratos` `Vec<WitContract>` slot was
3738 /// accessed inline at two production sites in
3739 /// caixa-core/src/manifest.rs — the [`Self::declared_mesh_slots`]
3740 /// mesh-slot declared-slot enumerator's
3741 /// `!self.contratos.is_empty()` presence probe (which drives the
3742 /// `M3_AUTHOR_KEY_CONTRATOS` kebab-case author-label push every
3743 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-coherence
3744 /// gate reads) and the [`Self::aplicacao_view`] per-Aplicacao
3745 /// typed-view composer's `self.contratos.clone()` per-contract
3746 /// fold-in path (which materializes the typed
3747 /// [`crate::aplicacao::AplicacaoSpec`] view every
3748 /// [`crate::StandardLayout::verify`] Aplicacao-arm gate and every
3749 /// downstream `caixa-mesh` renderer dispatches on). A future
3750 /// extension of the outer `:contratos` axis (a per-cluster
3751 /// `:contratos-overrides` overlay the wasm-engine operator resolves
3752 /// at admission time so a cluster-specific edge-set can tighten a
3753 /// caixa-declared list without re-authoring the `caixa.lisp`,
3754 /// promotion of the plain `Vec<WitContract>` to a richer
3755 /// `{static, dynamic}` partition once runtime-resolved contract
3756 /// edges land, per-edge policy annotation once the M4 per-edge
3757 /// policy overlay axis lands) would have had to be threaded through
3758 /// both open-coded copies in lockstep or one consumer would
3759 /// silently disagree with the peer on which edge slice a given
3760 /// Caixa resolves to — the enumerator's presence probe reading the
3761 /// raw slot while the peer view-composer's fold-in path read an
3762 /// operator-resolved slot would silently split the paired
3763 /// declared-slot enumerator and typed-view composition, a
3764 /// two-consumer split at the enumerator and the view composer far
3765 /// from the source `caixa.lisp` with no field naming the edge-set-
3766 /// drift root cause. Lifting the resolution rule to a typed method
3767 /// on the substrate primitive means every downstream consumer of
3768 /// the caixa's per-`Caixa` MESH-COMPOSITION outer-slice surface
3769 /// reaches for exactly one typed dispatch — the resolver's
3770 /// accept-set migrates as a unit on any future axis addition.
3771 ///
3772 /// Fourth and final outer top-level [`Caixa`] `&[Composite]`-return
3773 /// slice accessor for M2 / M3 typed-slot vec-carry axes — closes
3774 /// the outer-`Caixa` `&[Composite]` composite-slice sub-family the
3775 /// sibling M2 [`Self::upgrade_from`] (2a1f907) / [`Self::children`]
3776 /// (c17b51e) accessors opened and the M3 [`Self::membros`]
3777 /// (0f26987) accessor folded on, and closes the outer-`Caixa` M3
3778 /// mesh-slot arm of the composite-slice sub-family the sibling
3779 /// [`Self::membros`] accessor opened for the M3 vec-carry altitude.
3780 /// Peer at the outer altitude of the closed inner-
3781 /// [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
3782 /// same MESH-COMPOSITION per-Aplicacao contract-list axis — the two
3783 /// altitudes now share the same "byte-equal, borrow-shared" outer-
3784 /// accessor discipline. Sibling in shape to the peer outer-`Caixa`
3785 /// `&[Dep]`-return [`Self::deps`] (ad34b4e) / [`Self::deps_dev`]
3786 /// (f7fd81e) and `&[String]`-return [`Self::autores`] (b5d813f) /
3787 /// [`Self::etiquetas`] (78c7d3c) / [`Self::bibliotecas`] (8a36c23) /
3788 /// [`Self::exe`] (65d9527) / [`Self::servicos`] (611f78b) slice-
3789 /// accessors on the sibling outer-`Caixa` scalar-element vec-carry
3790 /// axes — folds the "outer [`Caixa`] `&[T]` slice" projection
3791 /// pattern onto the sibling M3 typed-composite-element axis
3792 /// ([`crate::aplicacao::WitContract`] composite, matching the
3793 /// per-inner [`crate::AplicacaoSpec::contratos`] element type at a
3794 /// different altitude).
3795 ///
3796 /// Returns `&[crate::aplicacao::WitContract]` (not
3797 /// `&Vec<WitContract>`) because every downstream consumer of the
3798 /// contract list treats it as a read-only sequence — the slice-view
3799 /// is the narrowest borrow that supports every present + roadmapped
3800 /// consumer (`.iter()`, `.len()`, `.is_empty()`, per-edge WIT-world
3801 /// discriminant dispatch, `serde` slice-serialization) without
3802 /// leaking the backing `Vec`'s grow/push/reserve surface no
3803 /// consumer of the typed view reaches for (the storage-side `Vec`
3804 /// remains reachable through the `pub contratos` field for the
3805 /// mutation-carrying serde round-trip and per-test fixture-mutation
3806 /// paths, including the [`Self::aplicacao_view`] fold-in path that
3807 /// clones the slot into the typed view). Named `contratos()` to
3808 /// match the storage field's name verbatim and the tatara-lisp
3809 /// author-surface term (`:contratos`) the field's own docstring
3810 /// already carries; the accessor's identity maps onto the canonical
3811 /// MESH-COMPOSITION vocabulary the [`Caixa::contratos`] field's
3812 /// docstring already reaches for ("WIT-typed inter-Servico
3813 /// contracts").
3814 #[must_use]
3815 pub fn contratos(&self) -> &[crate::aplicacao::WitContract] {
3816 self.contratos.as_slice()
3817 }
3818
3819 /// Compose the Aplicacao-related flat slots into a single typed
3820 /// [`crate::aplicacao::AplicacaoSpec`] for validation +
3821 /// downstream renderer consumption. Returns `None` when the
3822 /// caixa isn't a `:kind Aplicacao`.
3823 #[must_use]
3824 pub fn aplicacao_view(&self) -> Option<crate::aplicacao::AplicacaoSpec> {
3825 if !self.kind().is_aplicacao() {
3826 return None;
3827 }
3828 Some(crate::aplicacao::AplicacaoSpec {
3829 membros: self.membros().to_vec(),
3830 contratos: self.contratos().to_vec(),
3831 politicas: self.politicas().cloned().unwrap_or_default(),
3832 placement: self.placement().cloned().unwrap_or_default(),
3833 entrada: self.entrada().cloned(),
3834 })
3835 }
3836
3837 /// The kebab-case `:slot` tags of every M3 mesh slot this caixa
3838 /// *declares* a value on, in canonical declaration order
3839 /// (`:membros` → `:contratos` → `:politicas` → `:placement` →
3840 /// `:entrada`). A slot counts as declared when its backing field
3841 /// carries a value — a non-empty `Vec`, or a `Some(...)`.
3842 ///
3843 /// The M3 mesh slots compose the typed graph of a `:kind Aplicacao`
3844 /// (MESH-COMPOSITION §III.1). [`Self::aplicacao_view`] only folds
3845 /// them into a validatable [`crate::aplicacao::AplicacaoSpec`] when
3846 /// the kind matches (returns `None` otherwise), and the caixa-mesh /
3847 /// caixa-flux / caixa-helm renderers only emit them for an
3848 /// Aplicacao. On any *other* kind a declared mesh slot is the
3849 /// manifest field's documented "ignored otherwise" (see the
3850 /// `:membros` … `:entrada` field docs): it silently passes
3851 /// [`Caixa::from_lisp`] and then vanishes — never validated, never
3852 /// rendered — far from the source caixa.lisp.
3853 /// [`crate::StandardLayout::verify`] consults this to reject that
3854 /// silent-drop at caixa-build time
3855 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]), mirroring the
3856 /// `SupervisorOwnsCode` / `AplicacaoOwnsCode` kind-coherence gates:
3857 /// a slot foreign to the kind is a build error, not a silent drop.
3858 ///
3859 /// Lifted as a typed method (rather than an inline disjunction at
3860 /// the verify call site) so the mesh-slot set lives in one place —
3861 /// a future M4 axis added to the Aplicacao surface (per-edge policy
3862 /// overlay, distributed-app takeover config) is one push here, and
3863 /// every consumer reaching for "which mesh slots are set" (the
3864 /// verify gate, a future `feira lint` kind-coherence advisory)
3865 /// inherits the canonical order without rolling its own.
3866 ///
3867 /// Each per-arm kebab-case label is routed through the peer
3868 /// [`crate::M3_AUTHOR_KEY_MEMBROS`] /
3869 /// [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
3870 /// [`crate::M3_AUTHOR_KEY_POLITICAS`] /
3871 /// [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
3872 /// [`crate::M3_AUTHOR_KEY_ENTRADA`] consts declared next to the
3873 /// [`crate::M3_KEY_PLACEMENT`] renderer-side wire-key peer, so both
3874 /// halves of every M3 top-level mesh slot's dual axis (author-facing
3875 /// kebab-case label + renderer-side artifact key) route through one
3876 /// canonical declaration per arm — same discipline the peer
3877 /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`]
3878 /// / [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot consts
3879 /// (f49c8b0) establish on the sibling per-Servico M2 top-level slot
3880 /// axis, extended here to close the M3 mesh-slot author-facing-label
3881 /// axis so both altitudes of the typed-slot algebra
3882 /// (per-Servico M2 + per-Aplicacao M3) share the same
3883 /// "one canonical byte-string per arm, next to the axis" discipline.
3884 #[must_use]
3885 pub fn declared_mesh_slots(&self) -> Vec<&'static str> {
3886 let mut slots = Vec::new();
3887 if !self.membros().is_empty() {
3888 slots.push(crate::render::M3_AUTHOR_KEY_MEMBROS);
3889 }
3890 if !self.contratos().is_empty() {
3891 slots.push(crate::render::M3_AUTHOR_KEY_CONTRATOS);
3892 }
3893 if self.politicas().is_some() {
3894 slots.push(crate::render::M3_AUTHOR_KEY_POLITICAS);
3895 }
3896 if self.placement().is_some() {
3897 slots.push(crate::render::M3_AUTHOR_KEY_PLACEMENT);
3898 }
3899 if self.entrada().is_some() {
3900 slots.push(crate::render::M3_AUTHOR_KEY_ENTRADA);
3901 }
3902 slots
3903 }
3904
3905 /// The kebab-case `:slot` tags of every supervisor-tree slot this
3906 /// caixa *declares* a value on, in canonical declaration order
3907 /// (`:estrategia` → `:max-restarts` → `:restart-window` →
3908 /// `:children`). A slot counts as declared when its backing field
3909 /// carries a value — a `Some(...)`, or a non-empty `Vec`.
3910 ///
3911 /// The supervisor-tree slots compose the typed OTP supervisor of a
3912 /// `:kind Supervisor` (INSPIRATIONS §II.2; the `:estrategia` +
3913 /// `:children` field docs above). [`Self::supervisor_view`] only
3914 /// folds them into a validatable [`SupervisorSpec`] when the kind
3915 /// matches (returns `None` otherwise), and the wasm-operator's
3916 /// hierarchical reconciler only consumes them for a Supervisor. On
3917 /// any *other* kind a declared supervisor slot is the manifest
3918 /// field's documented "ignored otherwise" (see the `:estrategia` …
3919 /// `:children` field docs): it silently passes [`Caixa::from_lisp`]
3920 /// and then vanishes — never validated, never reconciled — far from
3921 /// the source caixa.lisp. [`crate::StandardLayout::verify`] consults
3922 /// this to reject that silent-drop at caixa-build time
3923 /// ([`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]), the
3924 /// exact mirror of the [`Self::declared_mesh_slots`] /
3925 /// [`crate::LayoutError::MeshSlotsOnNonAplicacao`] gate on the
3926 /// Aplicacao-only slot set: a slot foreign to the kind is a build
3927 /// error, not a silent drop.
3928 #[must_use]
3929 pub fn declared_supervisor_slots(&self) -> Vec<&'static str> {
3930 let mut slots = Vec::new();
3931 if self.estrategia().is_some() {
3932 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA);
3933 }
3934 if self.max_restarts().is_some() {
3935 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS);
3936 }
3937 if self.restart_window().is_some() {
3938 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW);
3939 }
3940 if !self.children().is_empty() {
3941 slots.push(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN);
3942 }
3943 slots
3944 }
3945
3946 /// The kebab-case `:slot` tags of every M2 Servico-runtime slot this
3947 /// caixa *declares* a value on, in canonical declaration order
3948 /// (`:limits` → `:behavior` → `:upgrade-from`). A slot counts as
3949 /// declared when its backing field carries a value — a `Some(...)`,
3950 /// or a non-empty `Vec`.
3951 ///
3952 /// The M2 slots configure the runtime of a long-running wasm
3953 /// component, i.e. a `:kind Servico`: `:limits` is Lunatic
3954 /// per-process sandboxing (INSPIRATIONS §III.1), `:behavior` is the
3955 /// OTP `gen_server` callback set (§II.3), `:upgrade-from` is the OTP
3956 /// appup hot-code-reload table (§II.4). The caixa-helm / caixa-flux
3957 /// renderers gate on [`crate::require_kind`]`(_, Servico)` and only
3958 /// emit these slots for a Servico; on any *other* kind a declared M2
3959 /// slot is the manifest field's documented "ignored otherwise": its
3960 /// well-formedness is checked by [`crate::StandardLayout::verify`]
3961 /// but the value is never rendered into a chart / programs.yaml entry
3962 /// — it silently passes [`Caixa::from_lisp`] + `feira build` and then
3963 /// vanishes, far from the source caixa.lisp.
3964 /// [`crate::StandardLayout::verify`] consults this to reject that
3965 /// silent-drop at caixa-build time
3966 /// ([`crate::LayoutError::ServicoSlotsOnNonServico`]), the exact
3967 /// mirror of the [`Self::declared_mesh_slots`] /
3968 /// [`Self::declared_supervisor_slots`] gates on the peer
3969 /// kind-exclusive slot sets: a slot foreign to the kind is a build
3970 /// error, not a silent drop.
3971 ///
3972 /// Each per-arm kebab-case label is routed through the peer
3973 /// [`crate::M2_AUTHOR_KEY_LIMITS`] / [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
3974 /// [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts declared next to the
3975 /// [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
3976 /// [`crate::M2_KEY_UPGRADE_FROM`] renderer-side wire-key peers, so
3977 /// both halves of the M2 top-level slot's dual axis (author-facing
3978 /// kebab-case label + renderer-side camelCase overlay-container wire
3979 /// key) route through one canonical declaration per arm — same
3980 /// discipline the peer [`crate::M2_BEHAVIOR_AUTHOR_KEY_ON_*`] sub-slot
3981 /// author-label consts (889dc18) establish on the sibling
3982 /// per-callback axis inside the `:behavior` overlay block.
3983 #[must_use]
3984 pub fn declared_servico_slots(&self) -> Vec<&'static str> {
3985 let mut slots = Vec::new();
3986 if self.limits().is_some() {
3987 slots.push(crate::render::M2_AUTHOR_KEY_LIMITS);
3988 }
3989 if self.behavior().is_some() {
3990 slots.push(crate::render::M2_AUTHOR_KEY_BEHAVIOR);
3991 }
3992 if !self.upgrade_from().is_empty() {
3993 slots.push(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM);
3994 }
3995 slots
3996 }
3997
3998 /// The kebab-case `:slot` tags of every code-surface slot this caixa
3999 /// declares a value on that its [`CaixaKind`] doesn't natively own,
4000 /// in canonical declaration order (`:exe` → `:servicos`). A
4001 /// code-surface slot is owned by exactly one kind: `:exe` by
4002 /// [`CaixaKind::Binario`] (the nix-built executable surface), and
4003 /// `:servicos` by [`CaixaKind::Servico`] (the wasm component +
4004 /// `ComputeUnit` daemon surface).
4005 ///
4006 /// Each is silently ignored when declared on the wrong kind: the
4007 /// caixa-helm / caixa-flux / caixa-flake renderers gate on
4008 /// [`crate::require_kind`]`(_, <owning-kind>)`, so on any *other*
4009 /// code-running kind a declared `:exe` / `:servicos` is the manifest
4010 /// field's documented "ignored otherwise" — its path is checked for
4011 /// existence by the layout's `bibliotecas`/`exe`/`servicos` loops
4012 /// (which run after [`Caixa::from_lisp`]), but the value is never
4013 /// rendered into a build target or programs.yaml entry. It silently
4014 /// passes [`Caixa::from_lisp`] + `feira build`, far from the source
4015 /// caixa.lisp, with no field naming which slot is foreign.
4016 ///
4017 /// [`crate::StandardLayout::verify`] consults this to reject that
4018 /// silent-drop at caixa-build time
4019 /// ([`crate::LayoutError::ForeignCodeSlot`]), beside the M2
4020 /// servico-runtime, supervisor-tree, and M3 mesh kind-coherence
4021 /// gates ([`Self::declared_servico_slots`] /
4022 /// [`Self::declared_supervisor_slots`] /
4023 /// [`Self::declared_mesh_slots`]): the fourth kind ↔ slot algebra
4024 /// axis to be closed on the typed surface. The Supervisor /
4025 /// Aplicacao "no code at all" cases ([`crate::LayoutError::SupervisorOwnsCode`]
4026 /// / [`crate::LayoutError::AplicacaoOwnsCode`]) keep their dedicated
4027 /// diagnostics — they fire ahead of this gate on the same `verify`
4028 /// pass, so for Supervisor / Aplicacao the `OwnCode` arm always wins
4029 /// and this method is moot. For Biblioteca / Binario / Servico, this
4030 /// gate fires when a code-running kind declares another code-running
4031 /// kind's exclusive code surface.
4032 ///
4033 /// `:bibliotecas` is deliberately excluded — a Binario or Servico
4034 /// may legitimately ship a `lib/` helper that the underlying
4035 /// substrate (the nix flake for Binario, the wasm component build
4036 /// for Servico) bundles into its build, so the slot's
4037 /// declared-on-wrong-kind cardinality isn't a structural error on
4038 /// either code-running kind. A Biblioteca declaring `:bibliotecas`
4039 /// is the native case (the slot's owning kind). Supervisor /
4040 /// Aplicacao declaring `:bibliotecas` is gated upstream by
4041 /// [`crate::LayoutError::SupervisorOwnsCode`] /
4042 /// [`crate::LayoutError::AplicacaoOwnsCode`].
4043 ///
4044 /// Lifted as a typed method (rather than an inline disjunction at
4045 /// the verify call site) so the foreign-code-slot set lives in one
4046 /// place — a future kind that gains its own code-surface slot is
4047 /// one push here, and every consumer reaching for "which code
4048 /// surfaces are foreign to this kind" (the verify gate, a future
4049 /// `feira lint` kind-coherence advisory, the future `app-operator`'s
4050 /// per-caixa build-target classifier) inherits the canonical order
4051 /// without rolling its own.
4052 #[must_use]
4053 pub fn declared_foreign_code_slots(&self) -> Vec<&'static str> {
4054 let mut slots = Vec::new();
4055 if !self.exe().is_empty() && !self.kind().requires_exe() {
4056 slots.push(":exe");
4057 }
4058 if !self.servicos().is_empty() && !self.kind().requires_servicos() {
4059 slots.push(":servicos");
4060 }
4061 slots
4062 }
4063
4064 /// Validate every entry of `:deps` and `:deps-dev` through
4065 /// [`Dep::validate`] — closing the parity loop with the per-axis
4066 /// `:versao` gates already wired into the typed-graph
4067 /// ([`crate::AplicacaoSpec::validate_membros`] for `:membros`,
4068 /// 9888b13) and typed supervisor tree
4069 /// ([`crate::SupervisorSpec::validate`] for `:children`, b38ff3a).
4070 ///
4071 /// Until this gate landed `:deps :versao` and `:deps-dev :versao`
4072 /// were the only `:versao` axes still untyped past
4073 /// [`Caixa::from_lisp`]: the derive macro stored the requirement
4074 /// as a String without parsing it, so a malformed-but-non-empty
4075 /// requirement (`"^bad-version"`, `"^^0.1"`, `"v0.1"`, `"not-a-req"`)
4076 /// silently passed parse and the `semver::Error` surfaced at
4077 /// lacre-resolve time, far from the source caixa.lisp, with no
4078 /// field naming which `:deps` entry carried the typo. Lifting the
4079 /// gate here makes the four `:versao` typed surfaces (`:deps`,
4080 /// `:deps-dev`, `:membros`, `:children`) structurally equivalent —
4081 /// every requirement string past `validate_deps` is round-trippable
4082 /// through [`crate::parse_requirement`] without re-checking at the
4083 /// resolver layer.
4084 ///
4085 /// Both lists run through the same per-entry validator so a typo
4086 /// in `:deps-dev` surfaces with the same diagnostic as one in
4087 /// `:deps` — neither axis is a second-class citizen of the typed
4088 /// surface.
4089 ///
4090 /// Within each list, [`DepError::DuplicateNome`] closes the
4091 /// set-not-multiset discipline on the `:nome` axis: two entries
4092 /// naming the same caixa carry two `:versao` / `:fonte` / feature
4093 /// triples that the caixa-resolver's lacre pipeline collapses to one
4094 /// via its `HashMap`-keyed-by-`:nome` consumption — the second entry
4095 /// silently overwrites the first at `concrete_versao`-resolve time
4096 /// (the same "second wins / one silently overwrites the other"
4097 /// shape the peer typed-graph duplicate gates already close on every
4098 /// other Vec-shaped authoring surface that keys by name). The
4099 /// duplicate check fires per-list and runs *after* each per-entry
4100 /// [`Dep::validate`] call so a malformed-and-duplicated entry
4101 /// surfaces its narrower per-entry diagnostic
4102 /// ([`DepError::NomeInvalid`], [`DepError::VersaoInvalid`],
4103 /// [`DepError::FonteRepoEmpty`], …) before the cross-entry duplicate
4104 /// diagnostic — the canonical "per-entry shape before cross-entry
4105 /// uniqueness" precedence the peer `:children :caixa`
4106 /// ([`crate::SupervisorSpec::validate`]), `:membros :caixa`
4107 /// ([`crate::AplicacaoSpec::validate_membros`]), `:contratos`
4108 /// ([`crate::AplicacaoSpec::validate`]), `:placement :clusters`
4109 /// ([`crate::AplicacaoSpec::validate_placement`]),
4110 /// `:entrada :paths` ([`crate::AplicacaoSpec::validate`]),
4111 /// `:upgrade-from :from` ([`crate::upgrade::validate_upgrade_from`]),
4112 /// and the within-`:upgrade-from`-entry per-instruction-class
4113 /// singularity gates ([`crate::UpgradeError::DuplicateLoadModule`],
4114 /// [`crate::UpgradeError::DuplicateStateChange`],
4115 /// [`crate::UpgradeError::DuplicateCleanup`]) all establish.
4116 ///
4117 /// Cross-list (`:deps` ↔ `:deps-dev`) coincidence is *not* gated
4118 /// here: Cargo's `[dependencies]` + `[dev-dependencies]` accept the
4119 /// same name in both tables (the dev table's pin overrides the
4120 /// runtime table's pin in test/dev contexts), and caixa's surface
4121 /// mirrors that convention until a deliberate choice retires the
4122 /// override pattern. Only within-list duplicates are structurally
4123 /// incoherent — those are what this gate closes.
4124 pub fn validate_deps(&self) -> Result<(), DepError> {
4125 for &list in crate::dep::DepList::ALL {
4126 let mut seen = std::collections::HashSet::new();
4127 for dep in self.deps_of(list) {
4128 dep.validate()?;
4129 crate::render::insert_first_seen(&mut seen, dep.nome(), || {
4130 DepError::DuplicateNome {
4131 nome: dep.nome().to_string(),
4132 list: list.as_str(),
4133 }
4134 })?;
4135 }
4136 }
4137 Ok(())
4138 }
4139
4140 /// Reject `:nome` values the K8s apiserver would refuse at admission
4141 /// time. The top-level Caixa identity flows directly into every
4142 /// substrate-side artifact's `metadata.name` axis: the
4143 /// `lareira-<nome>` Helm chart name ([`caixa-helm::lib::chart_name`]),
4144 /// the programs.yaml `name:` entry the `lareira-fleet-programs`
4145 /// aggregator keys ComputeUnit derivation off
4146 /// ([`caixa-flux::lib::programs_yaml_entry`]), the
4147 /// `LABEL_APLICACAO` label value carried on every Aplicacao-owned
4148 /// pod and the per-`:contratos` CiliumNetworkPolicy `metadata.name`
4149 /// (`<aplicacao>-<de>-to-<para>`) and the per-`:entrada`
4150 /// `<aplicacao>-<para>` HTTPRoute `metadata.name`
4151 /// ([`caixa-mesh::lib::cilium_network_policies`],
4152 /// [`caixa-mesh::lib::gateway_routes`]), and the default
4153 /// `lib/<nome>.lisp` / `exe/<nome>` layout paths
4154 /// ([`crate::StandardLayout::verify`]). Each K8s apiserver-side
4155 /// schema enforces the DNS-1123 label rule on admission; a
4156 /// structurally invalid `:nome` (`"MyApp"` — the canonical
4157 /// "I copied the display name verbatim" footgun, `"my_app"` — the
4158 /// Python-/Postgres-leak, `"team.app"` — `:nome` is a single label
4159 /// not a subdomain, `"-app"` / `"app-"` — DNS-1123 boundary
4160 /// violations, `"my app"` — the paste-from-doc footgun, `"café"` —
4161 /// IDN must be pre-encoded as Punycode, the 64-byte UUID-shaped
4162 /// over-cap slug) silently passed [`Caixa::from_lisp`] and the
4163 /// failure surfaced at `kubectl apply` time as a `metadata.name:
4164 /// Invalid value` rejection on whichever derived artifact admitted
4165 /// first, far from the source `caixa.lisp` and without any field
4166 /// naming the offending `:nome`.
4167 ///
4168 /// Thin wrapper around [`crate::render::is_dns_1123_label`] (the
4169 /// substrate-side predicate the per-axis name gates already share:
4170 /// `:membros :caixa` 3f9d7a0, `:placement :clusters` 6cbb900,
4171 /// `:children :caixa` 31bfa43) that maps the shared parser-shaped
4172 /// reason into the [`ManifestError::NomeInvalid`] variant, so the
4173 /// diagnostic is self-locating (the offending `:nome` is named
4174 /// verbatim) and the author can grep their `caixa.lisp` for
4175 /// `:nome "<value>"` and fix it in one edit. Same diagnostic shape
4176 /// every per-axis sibling gate already exposes
4177 /// ([`crate::AplicacaoError::MembroCaixaInvalid`],
4178 /// [`crate::AplicacaoError::PlacementClusterInvalid`],
4179 /// [`crate::SupervisorError::ChildCaixaInvalid`]).
4180 ///
4181 /// Empty `:nome` (which [`Caixa::from_lisp`] does not reject — the
4182 /// derive macro stores the raw String) is gated by the narrower
4183 /// [`ManifestError::NomeEmpty`] arm before the predicate is
4184 /// consulted, mirroring the empty-first cascade every per-axis
4185 /// name gate already uses (e.g. `MembroCaixaEmpty` before
4186 /// `MembroCaixaInvalid`, `EmptyChildName` before `ChildCaixaInvalid`).
4187 pub fn validate_nome(&self) -> Result<(), ManifestError> {
4188 // Routes through the shared
4189 // [`crate::render::require_valid_dns_1123_label`] gate the peer
4190 // name axes each land on so drift between the eight axes'
4191 // accepted DNS-1123-label sets is structurally impossible.
4192 let nome = self.nome();
4193 crate::render::require_valid_dns_1123_label(
4194 nome,
4195 || ManifestError::NomeEmpty,
4196 |reason| ManifestError::NomeInvalid {
4197 nome: nome.to_string(),
4198 reason,
4199 },
4200 )
4201 }
4202
4203 /// Reject `:nome` values whose joint length with the canonical
4204 /// [`crate::LAREIRA_CHART_NAME_PREFIX`] (`"lareira-"`) overflows
4205 /// the K8s DNS-1123 label cap [`crate::DNS_1123_LABEL_MAX_LEN`]
4206 /// (63 bytes). Every per-Servico / per-Aplicacao renderer the
4207 /// substrate carries materializes the caixa's `:nome` through the
4208 /// canonical [`crate::lareira_chart_name`] helper (f7320d7) into a
4209 /// `lareira-<nome>` artifact that lands as a K8s `metadata.name` /
4210 /// Helm chart name / `HelmRelease` `release_name`: `caixa-helm`'s
4211 /// `ChartDir.name` + `Chart.yaml::name`
4212 /// (caixa-helm/src/lib.rs:207), `caixa-flux`'s `cluster_bundle`
4213 /// `HelmRelease` `chart:` slot (caixa-flux/src/lib.rs:329),
4214 /// `caixa-tatara`'s `process_for_aplicacao` `release_name` +
4215 /// `oci://<registry>/lareira-<nome>` chart ref
4216 /// (caixa-tatara/src/lib.rs:124,178). Helm's own `Chart.yaml::name`
4217 /// admission rule strict-parses against DNS-1123-label, the Helm
4218 /// operator's tracking-secret name is derived from `release_name`
4219 /// and is itself DNS-1123-label-bounded, and the rendered chart's
4220 /// K8s object `metadata.name` axes embed the chart name as a
4221 /// prefix — every one fails admission on a > 63-byte chart name.
4222 ///
4223 /// The per-axis [`Self::validate_nome`] gate (6c992f8) already
4224 /// caps `:nome` itself at 63 bytes via [`is_dns_1123_label`], so a
4225 /// `:nome` of 56–63 bytes silently passed validate (the inner
4226 /// DNS-1123 check accepts the bare `:nome`) but produced a
4227 /// `lareira-<nome>` of 64–71 bytes that the apiserver / `helm lint`
4228 /// rejected at admission — far from the source `caixa.lisp`, with
4229 /// no field naming the overflow root cause. The
4230 /// [`lareira_chart_name`] helper's own doc comment
4231 /// (caixa-core/src/render.rs:3198) explicitly deferred the fix:
4232 /// "the M4 admission webhook will pin the joint-length invariant
4233 /// when it lands". This gate lands the invariant at the
4234 /// manifest-validate layer rather than waiting for the apiserver
4235 /// — the same fail-at-the-source posture every peer per-axis
4236 /// value-shape gate (DNS-1123 on `:nome`, SemVer-2 on `:versao`,
4237 /// SPDX-expression-shape on `:licenca`, 4-digit decimal year on
4238 /// `:edicao`, etc.) takes.
4239 ///
4240 /// Thin wrapper around
4241 /// [`crate::render::is_lareira_chart_name_shape`] (the
4242 /// substrate-side predicate that composes [`lareira_chart_name`] +
4243 /// [`is_dns_1123_label`] via the lifted
4244 /// [`crate::LAREIRA_CHART_NAME_NOME_MAX_LEN`] budget); maps the
4245 /// shared parser-shaped reason into the
4246 /// [`ManifestError::NomeChartNameBudgetExceeded`] variant so the
4247 /// diagnostic is self-locating (the offending `:nome` is named
4248 /// verbatim alongside the rendered chart name and the budget) and
4249 /// the author can shorten in one edit. The gate runs across every
4250 /// `:kind` — `:nome` is the substrate-wide identity axis any
4251 /// future renderer the substrate adds can derive a
4252 /// `lareira-<nome>` artifact from, and uniform enforcement closes
4253 /// the drift footgun where a future kind grows a chart-emitting
4254 /// render path while the validate cascade doesn't catch it.
4255 ///
4256 /// Runs *after* [`Self::validate_nome`] so the narrower
4257 /// `NomeEmpty` / `NomeInvalid` shape diagnostics fire first — a
4258 /// structurally-malformed `:nome` (empty, uppercase, underscore,
4259 /// dot, leading/trailing hyphen, Unicode, > 63 bytes) surfaces its
4260 /// specific shape error rather than the chart-name-budget error,
4261 /// preserving the legitimate "well-shaped `:nome` that happens to
4262 /// overflow the joint cap" arm for this gate.
4263 pub fn validate_nome_chart_name_budget(&self) -> Result<(), ManifestError> {
4264 let nome = self.nome();
4265 crate::render::is_lareira_chart_name_shape(nome).map_err(|reason| {
4266 ManifestError::NomeChartNameBudgetExceeded {
4267 nome: nome.to_string(),
4268 reason,
4269 }
4270 })
4271 }
4272
4273 /// Reject `:versao` values that don't parse as [`semver::Version`].
4274 /// The top-level Caixa version flows directly into every
4275 /// substrate-side artifact that carries a "this is which version of
4276 /// the caixa" axis: the `lareira-<nome>` Helm chart's `Chart.yaml`
4277 /// `version:` + `appVersion:` axes ([`caixa-helm::lib`] —
4278 /// SemVer-2-strict at `helm template` / `helm install` time per
4279 /// https://helm.sh/docs/topics/charts/#charts-and-versioning), the
4280 /// `feira publish` Zig-style `v<versao>` git tag
4281 /// ([`caixa-flux::lib::programs_yaml_entry`] / the
4282 /// `caixa-publish.yml` reusable workflow), the programs.yaml entry's
4283 /// `versao:` value the `lareira-fleet-programs` aggregator carries
4284 /// onto each rendered ComputeUnit, the OCI image's `:v<versao>` /
4285 /// `:latest` tags the substrate's `wasi-service-flake` builds with
4286 /// `skopeo push`, the lacre closure's pinned versions
4287 /// ([`caixa-resolver`] keys `concrete_versao`), and the
4288 /// `:upgrade-from :from` references peers in this exact `versao`
4289 /// shape (`semver::Version`, not `VersionReq`). Each consumer
4290 /// expects a strict three-part `MAJOR.MINOR.PATCH` (optionally
4291 /// `-prerelease` and/or `+build`); a structurally invalid `:versao`
4292 /// (`"0.1"` — missing patch, the canonical "I shortened it" footgun;
4293 /// `"v0.1.0"` — the git-tag-shape-leaking-into-versao typo;
4294 /// `"latest"` / `"main"` — the "I confused it with a docker tag"
4295 /// footgun; `"^0.1"` / `"~0.1.2"` — the requirement-shape leaking
4296 /// into the version field a peer `:deps :versao` accepts;
4297 /// `"0.1.0.0"` — the four-part Java/Microsoft convention DNS
4298 /// SemVer-2 forbids) silently passed [`Caixa::from_lisp`] (the
4299 /// derive macro stores the raw String) and the failure surfaced at
4300 /// the *first* downstream consumer that strict-parses it: at
4301 /// `helm install` time as a chart-version rejection, at
4302 /// `feira publish` time as a malformed git tag, at lacre-resolve
4303 /// time as a `semver::Error` not naming the offending caixa, at
4304 /// `feira upgrade --to <versao>` time as an unresolvable
4305 /// `:upgrade-from :from` match — far from the source `caixa.lisp`
4306 /// and without any field naming the offending `:versao`.
4307 ///
4308 /// Thin wrapper around [`semver::Version::parse`] — the same parser
4309 /// [`crate::CaixaVersion::parse`] (the typed `:versao` accessor)
4310 /// and [`crate::UpgradeFromEntry::validate`] (the peer
4311 /// `:upgrade-from :from` axis, 26da2c7) consume. Maps the
4312 /// `semver::Error` reason into the [`ManifestError::VersaoInvalid`]
4313 /// variant, carrying the offending `:versao` verbatim + a
4314 /// parser-shaped reason naming the specific violation, so the
4315 /// diagnostic is self-locating (the author can grep their
4316 /// `caixa.lisp` for `:versao "<value>"` and fix it in one edit).
4317 /// Same diagnostic shape as [`ManifestError::NomeInvalid`]
4318 /// (6c992f8) and [`crate::UpgradeError::FromInvalid`]
4319 /// (b0c8389) on the peer axes. With this gate, the typed `:versao`
4320 /// surfaces — top-level `:versao`, `:upgrade-from :from` — are
4321 /// now structurally equivalent (every value past validate is
4322 /// round-trippable through [`semver::Version::parse`] without
4323 /// re-checking at the renderer, resolver, or operator hot-upgrade
4324 /// layer), peer with the four `:versao` requirement axes (`:deps`,
4325 /// `:deps-dev`, `:membros`, `:children`) the prior commits
4326 /// (2420c44, 9888b13, b38ff3a) wired through `parse_requirement`.
4327 ///
4328 /// Empty `:versao` (which [`Caixa::from_lisp`] does not reject —
4329 /// the derive macro stores the raw String) is gated by the
4330 /// narrower [`ManifestError::VersaoEmpty`] arm before the parser is
4331 /// consulted, mirroring the empty-first cascade every per-axis
4332 /// version gate already uses (e.g. `MembroVersaoEmpty` before
4333 /// `MembroVersaoInvalid`, `EmptyChildVersion` before
4334 /// `ChildVersaoInvalid`, `NomeEmpty` before `NomeInvalid`).
4335 pub fn validate_versao(&self) -> Result<(), ManifestError> {
4336 let versao = self.versao();
4337 if versao.is_empty() {
4338 return Err(ManifestError::VersaoEmpty);
4339 }
4340 semver::Version::parse(versao).map_err(|e| ManifestError::VersaoInvalid {
4341 versao: versao.to_string(),
4342 reason: e.to_string(),
4343 })?;
4344 Ok(())
4345 }
4346
4347 /// Reject `:restart-window` values the shared
4348 /// [`crate::supervisor::duration_codec::parse`] refuses. The flat
4349 /// `restart_window: Option<String>` slot on [`Caixa`] is stored
4350 /// raw by the derive macro (the typed [`SupervisorSpec`] holds an
4351 /// `Option<Duration>` routed through the shared codec via `with =
4352 /// "duration_codec"`); the inline `Caixa → SupervisorSpec`
4353 /// view-construction path ([`Self::supervisor_view`]) folds the
4354 /// raw string through the same shared codec and soft-swallows the
4355 /// parse error as `None` to keep the view best-effort. Without
4356 /// this gate a malformed `:restart-window` (`"1.5s"` — the
4357 /// fractional-seconds drift class; `"1.0s"` — the decimal-shaped
4358 /// integer drift; `"0.5m"` — the unit-fraction drift; `"+30s"` /
4359 /// `"-30s"` — the leading-sign drift; `"30x"` — the unknown-unit
4360 /// footgun; `"abc"` — pure garbage; `""` — the empty-after-trim
4361 /// edge case) silently produced a `SupervisorSpec` with
4362 /// `restart_window: None`, indistinguishable from the canonical
4363 /// "omit the slot to express no reset" authoring shape — Erlang/OTP's
4364 /// `MaxIntensity / Period` invariant turns into a never-reset
4365 /// supervisor far from the source `caixa.lisp`, with no field
4366 /// naming the offending `:restart-window`. Lifting the gate to a
4367 /// Caixa-level validator mirrors the trajectory of the peer
4368 /// per-axis identity gates ([`Self::validate_nome`] 6c992f8,
4369 /// [`Self::validate_versao`] 1fdaa02, [`Self::validate_deps`]
4370 /// a7f0d8c) and the ABSORPTION-ROADMAP.md M2.2 test pin
4371 /// (line 196: "reject invalid `:restart-window` (non-duration)").
4372 ///
4373 /// Thin wrapper around [`crate::supervisor::duration_codec::parse`]
4374 /// (the shared codec backing `:supervisor :restart-window` as
4375 /// serde-routed on [`SupervisorSpec`], `:politicas :timeout`, and
4376 /// `:politicas :circuit-breaker :window` — all three covered by
4377 /// the integer-magnitude gate 1c55a2a). Maps the codec's parse
4378 /// error verbatim into the [`ManifestError::RestartWindowMalformed`]
4379 /// variant, carrying the offending raw string + a parser-shaped
4380 /// reason naming the canonical authoring form, so the diagnostic
4381 /// is self-locating (the author can grep their `caixa.lisp` for
4382 /// `:restart-window "<value>"` and fix it in one edit) and
4383 /// uniform with every other manifest-level validate diagnostic.
4384 /// With this gate the four `:restart-window`-shaped surfaces (the
4385 /// flat raw string on [`Caixa`], the typed `Option<Duration>` on
4386 /// [`SupervisorSpec`], the two `MeshPolicy` peer durations) are
4387 /// now structurally equivalent — every value past the codec is in
4388 /// one accepted set, by construction.
4389 ///
4390 /// `None` (the canonical "omit the slot to express no reset"
4391 /// shape) is accepted trivially — the gate is a no-op when the
4392 /// author didn't author a window. The empty string is rejected by
4393 /// the shared codec (its digit-only gate refuses an empty
4394 /// magnitude), surfacing the same `RestartWindowMalformed`
4395 /// diagnostic as every other rejected non-canonical shape.
4396 pub fn validate_restart_window(&self) -> Result<(), ManifestError> {
4397 let Some(s) = self.restart_window() else {
4398 return Ok(());
4399 };
4400 crate::supervisor::duration_codec::parse(s)
4401 .map(|_| ())
4402 .map_err(|reason| ManifestError::RestartWindowMalformed {
4403 restart_window: s.to_string(),
4404 reason,
4405 })
4406 }
4407
4408 /// Reject per-entry values on the three Caixa-level code-surface
4409 /// path lists (`:bibliotecas`, `:exe`, `:servicos`) that the
4410 /// layout checker's `root.join(p)` sandbox would silently subvert.
4411 /// Same three structural footguns the peer
4412 /// [`BehaviorSpec::validate`] (b0c8389) and
4413 /// [`crate::UpgradeInstruction::validate`] `StateChange` arm
4414 /// (26da2c7) already close on the M2 `:behavior :on-*` and
4415 /// `:upgrade-from :state-change :script` axes, here lifted onto
4416 /// the three top-level code-path axes through the shared
4417 /// [`is_sandboxed_relative_path`] predicate:
4418 ///
4419 /// - empty entry (`(:bibliotecas (""))` / `(:exe (""))` /
4420 /// `(:servicos (""))`): `PathBuf::new()` round-trips through
4421 /// [`Path::join`] as the base itself — `root.join("")` ==
4422 /// `root`, so the existence check (`self.exists(&root)`)
4423 /// trivially passes (the project root exists), and the layout
4424 /// silently treats the project root as a biblioteca / exe /
4425 /// servico entry. The `:bibliotecas` loop then hands the root
4426 /// to `tatara_lisp::read` at `feira build` time as if the root
4427 /// directory itself were a Lisp source file — a parse error
4428 /// far from the source `caixa.lisp` with no field naming the
4429 /// offending entry.
4430 /// - absolute path (`(:bibliotecas ("/etc/passwd"))`):
4431 /// [`Path::join`] *replaces* the base when the right-hand side
4432 /// is absolute, so `root.join("/etc/passwd")` resolves to
4433 /// `"/etc/passwd"` and escapes the project sandbox entirely.
4434 /// The existence check then silently consults whatever the
4435 /// escaped path resolves to — for `:bibliotecas`, the layout
4436 /// has no `starts_with`-fence (only `:exe` is fenced under
4437 /// `exe/` and `:servicos` under `servicos/`), so an absolute
4438 /// `:bibliotecas` entry that happens to resolve on disk
4439 /// silently passes. For `:exe` / `:servicos` the fence catches
4440 /// the absolute case downstream as `ExeOutsideDir` /
4441 /// `ServicoOutsideDir` (or `MissingEntry` if the absolute path
4442 /// doesn't exist), but with a downstream-shaped diagnostic
4443 /// that names the resolved escape path rather than the
4444 /// authoring footgun at the source.
4445 /// - parent-escape (`(:bibliotecas ("../sibling/x.lisp"))` /
4446 /// `(:exe ("exe/../../escape.lisp"))`): a [`PathBuf`] with any
4447 /// [`std::path::Component::ParentDir`] anywhere round-trips
4448 /// through [`Path::join`] as a traversal above the caixa root.
4449 /// The `:exe` / `:servicos` `starts_with(<dir>)` fence is
4450 /// *component-aware* (not canonical-path-aware), so
4451 /// `root.join("exe/../../escape.lisp")` `starts_with(exe_dir)`
4452 /// is **true** even though the canonical resolution
4453 /// `{parent of root}/escape.lisp` lives outside the caixa root
4454 /// — the fence silently lets the parent-escape through, and
4455 /// the existence check passes if that escape-target happens
4456 /// to exist. Caught regardless of where the `..` sits
4457 /// (leading, mid-path, trailing) so the gate matches the peer
4458 /// predicate's full coverage.
4459 ///
4460 /// Same `Empty` → `Absolute` → `ParentEscape` arm-ordering every peer
4461 /// `is_sandboxed_relative_path` consumer follows (b0c8389 / 26da2c7);
4462 /// same per-slot diagnostic shape every peer per-axis path-gate
4463 /// exposes (`*Empty { slot }` / `*Absolute { slot, path }` /
4464 /// `*ParentEscape { slot, path }`). Cross-slot precedence is
4465 /// `:bibliotecas` → `:exe` → `:servicos` — the same declaration
4466 /// order [`Caixa::declared_foreign_code_slots`] uses for its
4467 /// canonical foreign-code-slot diagnostic, so a manifest with
4468 /// multiple malformed slots surfaces the lexicographically-earliest
4469 /// slot's diagnostic deterministically.
4470 ///
4471 /// Lifted to the typed surface as a Caixa-level validator (peer
4472 /// of [`Self::validate_nome`] / [`Self::validate_versao`] /
4473 /// [`Self::validate_deps`] / [`Self::validate_restart_window`])
4474 /// and wired into [`crate::StandardLayout::verify`] before the
4475 /// existence-check loops so the diagnostic names the offending
4476 /// slot at the source caixa.lisp rather than reporting a
4477 /// downstream `MissingEntry` / `ExeOutsideDir` /
4478 /// `ServicoOutsideDir` against the resolved sandbox-escape path.
4479 /// The fourth typed code-path surface — every author-supplied
4480 /// path on the manifest — is now structurally accept-shaped
4481 /// past validate, peer with `:behavior :on-*` and
4482 /// `:upgrade-from :state-change :script`.
4483 pub fn validate_code_paths(&self) -> Result<(), ManifestError> {
4484 /// Per-slot file-type contract for the three Caixa-level
4485 /// code-path surfaces (`:bibliotecas`, `:exe`, `:servicos`).
4486 /// Each variant names the predicate the per-entry file-type
4487 /// gate consults; [`Self::None`] opts the slot out of any
4488 /// file-type contract. Lifted as a typed local enum so the
4489 /// per-slot dispatch is exhaustive at the `match` — adding a
4490 /// future axis to the typed-substrate `:` slot set (the
4491 /// future `:assets` resource axis the M5 roadmap names, the
4492 /// future `:nix-flake` derivation axis the caixa-flake
4493 /// emitter consults) lands as one variant + one `match` arm,
4494 /// not a coordinated rewrite of every per-slot bool flag.
4495 ///
4496 /// Peer of the typed-substrate per-slot variant disciplines
4497 /// already established on this surface
4498 /// ([`crate::supervisor::RestartStrategy`] +
4499 /// [`crate::supervisor::RestartPolicy`] on the OTP-shape
4500 /// supervision-tree axis,
4501 /// [`crate::aplicacao::PlacementStrategy`] on the §III.1
4502 /// placement axis, [`crate::aplicacao::WitTarget`] on the
4503 /// `:contratos` payload-target axis): the typed `enum` is
4504 /// the substrate's single source of truth for the per-axis
4505 /// dispatch, and every consumer (the per-arm body here, the
4506 /// future feira-lint per-slot diagnostic renderer, the M4
4507 /// per-axis admission webhook) reaches for the same typed
4508 /// surface rather than re-deriving the partition from inline
4509 /// flag combinations.
4510 enum CodePathFileType {
4511 /// `:exe` — nix-build derivation output, no terminating-
4512 /// extension contract (the canonical `"exe/<name>"`
4513 /// fixtures the layout's `ExeOutsideDir` error message
4514 /// documents carry no extension by convention).
4515 None,
4516 /// `:bibliotecas` — tatara-lisp source files the
4517 /// `feira build` loop reads through `tatara_lisp::read`
4518 /// at parse time. Routes to [`is_lisp_extension`].
4519 LispSource,
4520 /// `:servicos` — ComputeUnit-CR YAML files the
4521 /// caixa-helm / caixa-flux renderers consume through
4522 /// `serde_yaml::from_str`. Routes to
4523 /// [`is_computeunit_yaml_extension`].
4524 ComputeUnitYaml,
4525 }
4526
4527 // The per-slot [`CodePathFileType`] selects which axes carry the
4528 // lifted file-type predicate. `:bibliotecas` is the tatara-lisp
4529 // source axis (the `feira build` loop at
4530 // `caixa-feira/src/cmd/build.rs:33` reads each entry through
4531 // `tatara_lisp::read` at parse time) — the lifted
4532 // [`is_lisp_extension`] predicate gates the `.lisp` extension.
4533 // `:exe` is the nix-built executable surface (per the canonical
4534 // `"exe/<name>"`-shaped fixtures the layout's `ExeOutsideDir`
4535 // error message documents and every in-tree
4536 // `caixa_with_code_paths` positive control uses) — its file-type
4537 // contract is "nix-build derivation output", not a typed source
4538 // file, so [`CodePathFileType::None`] opts the slot out of any
4539 // file-type gate. `:servicos` is the `.computeunit.yaml`
4540 // ComputeUnit-CR axis (the peer caixa-helm / caixa-flux
4541 // renderers consume each entry through `serde_yaml::from_str` as
4542 // a typed `ComputeUnit` CR) — the lifted
4543 // [`is_computeunit_yaml_extension`] predicate gates the compound
4544 // `.computeunit.yaml` suffix. All three axes are surfaced through
4545 // the same iteration so the sandbox-shape + duplicate gates
4546 // apply uniformly; the typed file-type dispatch fires per-slot
4547 // exactly where the downstream consumer's accepted set demands
4548 // it. The third file-type variant ([`ComputeUnitYaml`]) is the
4549 // compounding lift on the peer 64772a9 `:bibliotecas`
4550 // `.lisp`-gate trajectory — the second of the three code-path
4551 // axes to land on a typed compound-suffix gate, with the same
4552 // self-locating per-slot diagnostic shape every peer per-axis
4553 // file-type lift uses (`*NonLispExtension { slot, path }` /
4554 // `*NonComputeUnitYamlExtension { slot, path }`).
4555 for (slot, list, file_type) in [
4556 (
4557 ":bibliotecas",
4558 &self.bibliotecas,
4559 CodePathFileType::LispSource,
4560 ),
4561 (":exe", &self.exe, CodePathFileType::None),
4562 (
4563 ":servicos",
4564 &self.servicos,
4565 CodePathFileType::ComputeUnitYaml,
4566 ),
4567 ] {
4568 // Per-slot set-not-multiset gate on the typed code-path axis.
4569 // Every peer Vec-shaped author-supplied list past validate is
4570 // a set, not a multiset: `:membros :caixa`
4571 // ([`crate::AplicacaoError::MembroDuplicate`]), `:placement
4572 // :clusters` ([`crate::AplicacaoError::PlacementClusterDuplicate`]),
4573 // `:entrada :paths` ([`crate::AplicacaoError::EntradaPathDuplicate`]),
4574 // `:contratos` ([`crate::AplicacaoError::ContratoDuplicate`]),
4575 // `:children :caixa` ([`crate::SupervisorError::DuplicateChild`]),
4576 // `:deps` / `:deps-dev` `:nome` ([`crate::DepError::DuplicateNome`]
4577 // per 359fba5), `:upgrade-from :from` ([`crate::UpgradeError::DuplicateFrom`]),
4578 // `:etiquetas` ([`ManifestError::EtiquetaDuplicate`] per 360a499),
4579 // `:autores` ([`ManifestError::AutorDuplicate`] per 86c769b) —
4580 // the three code-path lists are the last Vec-shaped author-
4581 // supplied slots on the typed Caixa surface still admitting a
4582 // duplicate entry silently. Scope is per-list (`:bibliotecas`
4583 // duplicates are flagged within `:bibliotecas`, not across
4584 // `:bibliotecas` ↔ `:exe`) — the same per-list scope `:deps`
4585 // ↔ `:deps-dev` use (a `:nome` present in both lists is a
4586 // legitimate dev-vs-runtime shape on the dep axis, fenced
4587 // separately by [`crate::dep::validate_no_self_dep`]). On the
4588 // code-path axis a cross-slot collision is structurally
4589 // impossible by the layout's `starts_with(<exe|servicos>_dir)`
4590 // fence — `:exe` and `:servicos` entries are confined to their
4591 // own directory trees, so the only way a string could appear
4592 // on two code-path lists is the (rare, structurally invalid)
4593 // case where `:bibliotecas` carries an `"exe/<x>"` or
4594 // `"servicos/<x>.yaml"`-shaped path.
4595 //
4596 // Without the gate three authoring footguns silently passed:
4597 //
4598 // - `:bibliotecas ("lib/foo.lisp" "lib/foo.lisp")` — the
4599 // canonical copy-paste-the-wrong-file footgun. `feira
4600 // build` (`caixa-feira/src/cmd/build.rs:33`) walks the
4601 // list and re-parses the same file twice, wasting work
4602 // and silently masking the author's intent to declare a
4603 // *second* biblioteca.
4604 // - `:exe ("exe/cli" "exe/cli")` — the same footgun on the
4605 // Binario surface. The future `caixa-flake` `nix flake`
4606 // emitter that materializes each `:exe` entry as a flake
4607 // `packages.<exe-name>` derivation would collide on the
4608 // duplicate package name and surface a flake-eval error
4609 // far from the source `caixa.lisp`.
4610 // - `:servicos ("servicos/x.computeunit.yaml"
4611 // "servicos/x.computeunit.yaml")` — the same footgun on
4612 // the Servico surface. The peer `caixa-helm` / `caixa-flux`
4613 // renderers already refuse `:servicos.len() != 1` with
4614 // the narrower [`UnsupportedServicoCount`] diagnostic, but
4615 // that diagnostic surfaces "too many servicos" without
4616 // naming "duplicate entry" — the typed self-locating
4617 // "which entry is the duplicate" framing only lands at
4618 // this gate.
4619 //
4620 // Same `seen.insert(entry.as_str())` shape every peer per-list
4621 // duplicate gate uses (`:etiquetas` 360a499, `:autores`
4622 // 86c769b, `:deps` 359fba5) and the same "structural shape
4623 // checks fire before the duplicate check on the same entry"
4624 // ordering (a `(:bibliotecas ("" "lib/x.lisp" "lib/x.lisp"))`
4625 // shape surfaces the narrower [`Self::CodePathEmpty`] for the
4626 // empty entry first, not the duplicate on the later pair).
4627 let mut seen = std::collections::HashSet::new();
4628 for entry in list {
4629 let path = Path::new(entry);
4630 match is_sandboxed_relative_path(path) {
4631 Ok(()) => {}
4632 Err(PathShapeViolation::Empty) => {
4633 return Err(ManifestError::CodePathEmpty { slot });
4634 }
4635 Err(PathShapeViolation::Absolute) => {
4636 return Err(ManifestError::CodePathAbsolute {
4637 slot,
4638 path: path.to_path_buf(),
4639 });
4640 }
4641 Err(PathShapeViolation::ParentEscape) => {
4642 return Err(ManifestError::CodePathParentEscape {
4643 slot,
4644 path: path.to_path_buf(),
4645 });
4646 }
4647 }
4648 // The per-slot file-type gate dispatched through the
4649 // typed [`CodePathFileType`] selector above. Each variant
4650 // routes to the lifted predicate the downstream consumer
4651 // demands:
4652 //
4653 // - [`LispSource`] → [`is_lisp_extension`] for
4654 // `:bibliotecas` (the `feira build` loop's
4655 // `tatara_lisp::read` consumer);
4656 // - [`ComputeUnitYaml`] → [`is_computeunit_yaml_extension`]
4657 // for `:servicos` (the caixa-helm / caixa-flux
4658 // `serde_yaml::from_str` consumer's `ComputeUnit` CR
4659 // accepted set);
4660 // - [`None`] for `:exe` — the nix-build derivation-
4661 // output axis has no terminating-extension contract.
4662 //
4663 // Fires after the sandbox-shape arms so a path that is
4664 // *both* sandbox-escaping and wrong-extension surfaces
4665 // the more fundamental sandbox-shape diagnostic first
4666 // (mirrors the peer `EmptyPath` → `AbsolutePath` →
4667 // `ParentEscape` → `NonLispExtension` arm-ordering on
4668 // `:behavior :on-*` c97815a, and `EmptyScript` →
4669 // `AbsoluteScript` → `ParentEscapeScript` →
4670 // `NonLispExtensionScript` on
4671 // `:upgrade-from :state-change :script` 33cc830), and
4672 // before the duplicate gate so the narrower per-entry
4673 // file-type shape dominates the cross-entry uniqueness
4674 // diagnostic (a
4675 // `("servicos/x.yaml" "servicos/x.yaml")` shape on
4676 // `:servicos` surfaces
4677 // `CodePathNonComputeUnitYamlExtension` on the first
4678 // entry rather than `CodePathDuplicate` on the pair —
4679 // peer with the 64772a9 `:bibliotecas`
4680 // `("lib/x.txt" "lib/x.txt")` ordering).
4681 match file_type {
4682 CodePathFileType::None => {}
4683 CodePathFileType::LispSource => {
4684 if !is_lisp_extension(path) {
4685 return Err(ManifestError::CodePathNonLispExtension {
4686 slot,
4687 path: path.to_path_buf(),
4688 });
4689 }
4690 }
4691 CodePathFileType::ComputeUnitYaml => {
4692 if !is_computeunit_yaml_extension(path) {
4693 return Err(ManifestError::CodePathNonComputeUnitYamlExtension {
4694 slot,
4695 path: path.to_path_buf(),
4696 });
4697 }
4698 }
4699 }
4700 crate::render::insert_first_seen(&mut seen, entry.as_str(), || {
4701 ManifestError::CodePathDuplicate {
4702 slot,
4703 path: path.to_path_buf(),
4704 }
4705 })?;
4706 }
4707 }
4708 Ok(())
4709 }
4710
4711 /// Reject `:etiquetas` lists with an empty entry or with two entries
4712 /// agreeing on the same string. `:etiquetas` is the universal
4713 /// registry-search-tag axis on [`Caixa`] (every kind carries the
4714 /// `Vec<String>` slot) and lands verbatim as the Helm chart
4715 /// `Chart.yaml` `keywords:` array on every Servico (caixa-helm's
4716 /// `build_chart_yaml` at `caixa-helm/src/lib.rs:236` folds it through
4717 /// a [`std::collections::BTreeSet`] alongside the four substrate-
4718 /// fixed tags `lareira` / `wasm` / `tatara-lisp` / `caixa-servico`).
4719 /// Two authoring footguns silently passed validate without this gate:
4720 ///
4721 /// - Empty entry (`(:etiquetas (""))` — the canonical paste-from-
4722 /// blank-doc footgun) rendered as `keywords: ["", "caixa-servico",
4723 /// "lareira", "tatara-lisp", "wasm"]` in `Chart.yaml`. Helm's
4724 /// `chart.metadata.keywords` admits the value without a strict
4725 /// parser-side gate, but the empty keyword has no operational
4726 /// meaning — it indexes nothing in the future caixa-registry
4727 /// search axis and clutters the rendered chart with a no-op tag.
4728 /// - Duplicate entries (`(:etiquetas ("demo" "demo"))` — the
4729 /// copy-paste-the-wrong-tag footgun) silently passed validate
4730 /// and were silently dedup'd by caixa-helm's `BTreeSet` collect
4731 /// at chart render — a "second wins / one silently disappears"
4732 /// shape divergent from every peer typed-graph set gate
4733 /// ([`crate::AplicacaoError::MembroDuplicate`] on `:membros`,
4734 /// [`crate::AplicacaoError::PlacementClusterDuplicate`] on
4735 /// `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
4736 /// on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
4737 /// on `:contratos`, [`crate::DepError::DuplicateNome`] on
4738 /// `:deps` / `:deps-dev` per 359fba5, [`crate::UpgradeError::DuplicateFrom`]
4739 /// on `:upgrade-from`, the per-instruction-class singularity
4740 /// gates [`crate::UpgradeError::DuplicateLoadModule`] /
4741 /// [`crate::UpgradeError::DuplicateStateChange`] /
4742 /// [`crate::UpgradeError::DuplicateCleanup`]). The typed-graph
4743 /// discipline is uniform: every Vec-shaped author-supplied list
4744 /// past validate is set-not-multiset, by construction.
4745 ///
4746 /// Past the empty arm the gate enforces the chart-keyword shape
4747 /// predicate via [`crate::render::is_chart_keyword_shape`]: Cargo's
4748 /// crates.io `[package] keywords` grammar — 1..=20 bytes, starts
4749 /// with an ASCII letter, ASCII alphanumeric / `_` / `-`
4750 /// continuation. Closes the canonical paste-from-doc footguns the
4751 /// bare empty + duplicate arms left open: paste-from-aligned-doc
4752 /// whitespace (`" mesh"`, `"mesh "`), paste-from-multiline-doc
4753 /// newline (`"mesh\nhttp"` — the author pasted a multi-tag block
4754 /// into one entry instead of splitting), paste-from-Windows-CRLF-doc
4755 /// carriage return, CSV-list-separator confusion (`"mesh,http,grpc"`
4756 /// — the author meant three separate list entries), path-separator
4757 /// confusion (`"caixa/servico"`), namespace-suffix (`"http.1"`),
4758 /// leading-digit (`"1foo"`), kebab-leak (`"-foo"`), snake-leak
4759 /// (`"_foo"`), non-ASCII (`"café"`), and paste-from-binary-blob
4760 /// control bytes that would silently land as malformed search tags
4761 /// in the rendered Chart.yaml `keywords:` array and break the
4762 /// Artifact Hub keyword index lookup far from the source caixa.lisp.
4763 /// Mirrors the [`Self::validate_autores`] shape-predicate cascade
4764 /// established on the sibling universal-axis `Vec<String>` surface
4765 /// — the second universal-axis Vec<String> surface to land the
4766 /// empty-first-then-shape-then-duplicate per-entry cascade.
4767 ///
4768 /// Same empty-first cascade discipline every peer per-axis gate
4769 /// uses: the per-entry empty arm fires before the per-entry shape
4770 /// arm fires before the cross-entry duplicate arm, so an
4771 /// `("" "mesh" "mesh")` authoring shape surfaces the narrower
4772 /// [`ManifestError::EtiquetaEmpty`] (the structural "this entry
4773 /// has no value" defect) before either the shape or the duplicate
4774 /// diagnostic. Walks the list in declaration order so the
4775 /// first-collision diagnostic surfaces the lexicographically-
4776 /// earliest offending position, peer with every other duplicate
4777 /// gate on this surface.
4778 ///
4779 /// Universal-axis (every kind carries `:etiquetas`), so wired at the
4780 /// caixa-build gate alongside the peer universal gates
4781 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4782 /// [`Self::validate_deps`] / [`Self::validate_code_paths`] — before
4783 /// the kind-coherence gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`]
4784 /// / [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4785 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4786 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
4787 /// slot sets. The future caixa-registry search axis can reach for
4788 /// `caixa.etiquetas` knowing every entry is a non-empty distinct
4789 /// chart-keyword-shaped string without re-deriving the precondition.
4790 pub fn validate_etiquetas(&self) -> Result<(), ManifestError> {
4791 let mut seen = std::collections::HashSet::new();
4792 for etiqueta in self.etiquetas() {
4793 if etiqueta.is_empty() {
4794 return Err(ManifestError::EtiquetaEmpty);
4795 }
4796 crate::render::is_chart_keyword_shape(etiqueta).map_err(|reason| {
4797 ManifestError::EtiquetaInvalid {
4798 etiqueta: etiqueta.clone(),
4799 reason,
4800 }
4801 })?;
4802 crate::render::insert_first_seen(&mut seen, etiqueta.as_str(), || {
4803 ManifestError::EtiquetaDuplicate {
4804 etiqueta: etiqueta.clone(),
4805 }
4806 })?;
4807 }
4808 Ok(())
4809 }
4810
4811 /// Reject `:autores` lists with an empty entry or with two entries
4812 /// agreeing on the same string. `:autores` is the universal
4813 /// maintainer-axis on [`Caixa`] (every kind carries the
4814 /// `Vec<String>` slot) and lands verbatim as the Helm chart
4815 /// `Chart.yaml` `maintainers:` array on every Servico (caixa-helm's
4816 /// `build_chart_yaml` at `caixa-helm/src/lib.rs:251` maps each entry
4817 /// to a `Maintainer { name, email: None }` without dedup). Two
4818 /// authoring footguns silently passed validate without this gate:
4819 ///
4820 /// - Empty entry (`(:autores (""))` — the canonical paste-from-
4821 /// blank-doc footgun) rendered as
4822 /// `maintainers: [{name: "", email: null}]` in `Chart.yaml`. The
4823 /// empty maintainer name has no operational meaning — it
4824 /// identifies no one in the substrate's authorship index and
4825 /// clutters the rendered chart with a no-op maintainer.
4826 /// - Duplicate entries (`(:autores ("pleme-io" "pleme-io"))` —
4827 /// the copy-paste-the-wrong-author footgun) silently passed
4828 /// validate and rendered as two identical maintainer entries.
4829 /// Unlike the [`Self::validate_etiquetas`] peer (caixa-helm's
4830 /// `BTreeSet`-collect on `:etiquetas` silently dedups the
4831 /// rendered `keywords:` array at chart-render time), the
4832 /// `maintainers:` rendering has *no* dedup — duplicate `:autores`
4833 /// entries stack verbatim in the chart, divergent from every
4834 /// peer typed-graph set gate ([`crate::AplicacaoError::MembroDuplicate`]
4835 /// on `:membros`, [`crate::AplicacaoError::PlacementClusterDuplicate`]
4836 /// on `:placement :clusters`, [`crate::AplicacaoError::EntradaPathDuplicate`]
4837 /// on `:entrada :paths`, [`crate::AplicacaoError::ContratoDuplicate`]
4838 /// on `:contratos`, [`crate::DepError::DuplicateNome`] on
4839 /// `:deps` / `:deps-dev`, [`crate::UpgradeError::DuplicateFrom`]
4840 /// on `:upgrade-from`, [`ManifestError::EtiquetaDuplicate`] on
4841 /// `:etiquetas`).
4842 ///
4843 /// Past the empty arm the gate enforces the chart-maintainer-name
4844 /// shape predicate via [`crate::render::is_chart_maintainer_name_shape`]:
4845 /// the structural single-line printable-UTF-8 floor every realistic
4846 /// Helm chart maintainer name carries — 1..=128 bytes, no leading
4847 /// or trailing whitespace, no ASCII control characters anywhere,
4848 /// Unicode bytes accepted. Closes the canonical paste-from-doc
4849 /// footguns the bare empty + duplicate arms left open:
4850 /// paste-from-aligned-doc whitespace (`" pleme-io"`, `"pleme-io "`),
4851 /// paste-from-multiline-doc newline (`"alice\nbob"` — the author
4852 /// pasted a multi-line block of author records into one `:autores`
4853 /// entry instead of splitting into one entry per author),
4854 /// paste-from-Windows-CRLF-doc carriage return, tab-from-aligned-doc,
4855 /// and the paste-from-binary-blob control bytes that would silently
4856 /// land as YAML-illegal byte sequences in the rendered Chart.yaml
4857 /// `maintainers:` array. Mirrors the shape-predicate cascade
4858 /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
4859 /// [`Self::validate_edicao`] / [`Self::validate_repositorio`]
4860 /// establish past their own empty arms on the sibling universal-axis
4861 /// `Option<String>` surfaces — the first universal-axis Vec<String>
4862 /// surface to land the empty-first-then-shape-then-duplicate per-entry
4863 /// cascade.
4864 ///
4865 /// Same empty-first cascade discipline every peer per-axis gate
4866 /// uses: the per-entry empty arm fires before the per-entry shape
4867 /// arm before the cross-entry duplicate arm. Walks the list in
4868 /// declaration order so the first-collision diagnostic surfaces the
4869 /// lexicographically-earliest offending position, peer with every
4870 /// other duplicate gate on this surface.
4871 ///
4872 /// Universal-axis (every kind carries `:autores`), so wired at the
4873 /// caixa-build gate alongside the peer universal gates
4874 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4875 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4876 /// [`Self::validate_code_paths`] — before the kind-coherence gates
4877 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4878 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4879 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4880 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-specific
4881 /// slot sets.
4882 pub fn validate_autores(&self) -> Result<(), ManifestError> {
4883 let mut seen = std::collections::HashSet::new();
4884 for autor in self.autores() {
4885 if autor.is_empty() {
4886 return Err(ManifestError::AutorEmpty);
4887 }
4888 crate::render::is_chart_maintainer_name_shape(autor).map_err(|reason| {
4889 ManifestError::AutorInvalid {
4890 autor: autor.clone(),
4891 reason,
4892 }
4893 })?;
4894 crate::render::insert_first_seen(&mut seen, autor.as_str(), || {
4895 ManifestError::AutorDuplicate {
4896 autor: autor.clone(),
4897 }
4898 })?;
4899 }
4900 Ok(())
4901 }
4902
4903 /// Reject `:repositorio` values whose shape the shared
4904 /// [`crate::render::is_git_repo_url`] predicate refuses. The flat
4905 /// `repositorio: Option<String>` slot on [`Caixa`] is the
4906 /// universal git-shaped homepage axis every kind carries — the
4907 /// substrate routes the same string through two load-bearing
4908 /// consumers:
4909 ///
4910 /// - [`caixa-helm`] folds it verbatim into the rendered
4911 /// `lareira-<nome>` Helm chart's `Chart.yaml` `home:` field
4912 /// (`build_chart_yaml` at `caixa-helm/src/lib.rs:268`) and into
4913 /// the chart `README.md` `repo = …` interpolation
4914 /// (`caixa-helm/src/lib.rs:359`).
4915 /// - [`caixa-flux`] folds it verbatim into the standalone
4916 /// `ClusterBundleOpts::for_caixa` `git_url:` field
4917 /// (`caixa-flux/src/lib.rs:293`), which becomes the `FluxCD`
4918 /// `GitRepository.spec.url` the cluster's source-controller
4919 /// polls — the load-bearing deploy-time axis.
4920 ///
4921 /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
4922 /// substitute a placeholder when the slot is absent (`None` → the
4923 /// fallback fires); a `Some("")` *skips the fallback* and silently
4924 /// passes the empty string through to `Chart.yaml home: ""` /
4925 /// `GitRepository url: ""` — Helm's chart lint and `FluxCD`'s source
4926 /// controller both reject the empty URL far from the source
4927 /// `caixa.lisp`, with no field naming the offending `:repositorio`.
4928 /// Similarly a malformed `:repositorio` (whitespace, control char,
4929 /// missing `:` separator, leading `-`) silently lands in the
4930 /// rendered artifacts and breaks at `git clone` / `helm template`
4931 /// / `flux reconcile` time.
4932 ///
4933 /// Thin wrapper around [`crate::render::is_git_repo_url`] — the
4934 /// same shared predicate the peer [`crate::DepSource::validate`]
4935 /// routes the `:fonte (:tipo git :repo …)` axis through. With this
4936 /// gate the two `git URL`-shaped surfaces on the typed Caixa
4937 /// (`:repositorio` here, `:deps :fonte :repo` peer) are
4938 /// structurally equivalent: every value past validate is
4939 /// guaranteed-acceptable by the predicate's union of constraints
4940 /// (non-empty, length-bounded, no leading `-`, no whitespace, no
4941 /// control chars, ASCII only, no leading `:`, contains a `:`
4942 /// separator). The predicate accepts every documented authoring
4943 /// shape — `github:org/repo` shorthand, `https://host/path`,
4944 /// `ssh://[user@]host/path`, `git://host/path`, `git@host:path`
4945 /// scp-style SSH, `file:///path` — and refuses the canonical
4946 /// paste-from-blank-doc / paste-from-multiline-doc / CLI-arg-
4947 /// injection footguns at validate time. Maps the predicate's
4948 /// `String` reason verbatim into the
4949 /// [`ManifestError::RepositorioInvalid`] variant, carrying the
4950 /// offending value + parser-shaped reason so the diagnostic is
4951 /// self-locating (the author can grep their `caixa.lisp` for
4952 /// `:repositorio "<value>"` and fix it in one edit).
4953 ///
4954 /// `None` (the canonical "omit the slot to express no published
4955 /// homepage" shape) is accepted trivially — the gate is a no-op
4956 /// when the author didn't declare a value. `Some("")` is gated by
4957 /// the narrower [`ManifestError::RepositorioEmpty`] arm before the
4958 /// shape predicate is consulted, mirroring the empty-first cascade
4959 /// every peer per-axis identity gate uses
4960 /// ([`ManifestError::NomeEmpty`] → [`ManifestError::NomeInvalid`],
4961 /// [`ManifestError::VersaoEmpty`] → [`ManifestError::VersaoInvalid`],
4962 /// [`crate::DepError::FonteRepoEmpty`] →
4963 /// [`crate::DepError::FonteRepoInvalid`]).
4964 ///
4965 /// Universal-axis (every kind carries `:repositorio`), so wired at
4966 /// the caixa-build gate alongside the peer universal gates
4967 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
4968 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
4969 /// [`Self::validate_autores`] / [`Self::validate_code_paths`] —
4970 /// before the kind-coherence gates
4971 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
4972 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
4973 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
4974 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
4975 /// specific slot sets.
4976 pub fn validate_repositorio(&self) -> Result<(), ManifestError> {
4977 let Some(s) = self.repositorio() else {
4978 return Ok(());
4979 };
4980 if s.is_empty() {
4981 return Err(ManifestError::RepositorioEmpty);
4982 }
4983 is_git_repo_url(s).map_err(|reason| ManifestError::RepositorioInvalid {
4984 repositorio: s.to_string(),
4985 reason,
4986 })
4987 }
4988
4989 /// Reject `:descricao` values that are the empty string. The flat
4990 /// `descricao: Option<String>` slot on [`Caixa`] is the universal
4991 /// free-form-prose homepage axis every kind carries — the
4992 /// substrate routes the same string through two load-bearing
4993 /// consumers in the [`caixa-helm`] renderer:
4994 ///
4995 /// - `build_chart_yaml` folds it verbatim into the rendered
4996 /// `lareira-<nome>` Helm chart's `Chart.yaml` `description:`
4997 /// field (`caixa-helm/src/lib.rs:232-235`).
4998 /// - `build_readme` folds it verbatim into the rendered chart
4999 /// `README.md` header (`caixa-helm/src/lib.rs:333-336`).
5000 ///
5001 /// Both consumers use `Option::unwrap_or_else(|| <fallback>)` to
5002 /// substitute a `caixa.nome`-derived placeholder when the slot is
5003 /// absent (`None` → the fallback fires); a `Some("")` *skips the
5004 /// fallback* and silently passes the empty string through to
5005 /// `Chart.yaml description: ""` / a blank chart `README.md`
5006 /// header. Helm's chart spec requires a non-empty `description:`
5007 /// field on `apiVersion: v2` charts (`helm lint` surfaces it as
5008 /// `WARNING [chart.metadata.description]: description is required`),
5009 /// so the empty `Some("")` silently lands in the rendered
5010 /// artifacts and breaks at `helm lint` / `helm install` time far
5011 /// from the source `caixa.lisp`, with no field naming the
5012 /// offending `:descricao`.
5013 ///
5014 /// `None` (the canonical "omit the slot to defer to the renderer's
5015 /// `caixa.nome`-derived fallback" shape) is accepted trivially —
5016 /// the gate is a no-op when the author didn't declare a value.
5017 /// `Some("")` is gated by the narrower
5018 /// [`ManifestError::DescricaoEmpty`] arm, mirroring the empty-arm
5019 /// shape every peer per-axis empty gate uses
5020 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
5021 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
5022 /// [`ManifestError::RepositorioEmpty`]).
5023 ///
5024 /// Universal-axis (every kind carries `:descricao`), so wired at
5025 /// the caixa-build gate alongside the peer universal gates
5026 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5027 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
5028 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
5029 /// [`Self::validate_code_paths`] — before the kind-coherence
5030 /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5031 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5032 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5033 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
5034 /// specific slot sets.
5035 ///
5036 /// Past the empty arm the gate enforces the chart-description
5037 /// shape predicate via [`crate::render::is_chart_description_shape`]:
5038 /// the structural single-line UTF-8 floor every realistic chart
5039 /// description in the wild matches — 1..=512 bytes, no leading
5040 /// or trailing whitespace, no ASCII control characters anywhere
5041 /// (`0x00..=0x1F` plus `0x7F` DEL — banning tab, newline,
5042 /// carriage return, and every other control byte), Unicode
5043 /// continuation bytes accepted (the canonical fixtures carry
5044 /// `→` and `—`). Closes the canonical paste-from-doc footguns
5045 /// the bare empty-arm gate left open: paste-from-aligned-doc
5046 /// leading / trailing whitespace (`" Checkout flow."`,
5047 /// `"Checkout flow. "`), paste-from-multiline-doc newline
5048 /// (`"Checkout\nflow."`), paste-from-Windows-CRLF-doc CR
5049 /// (`"Checkout\rflow."`), tab-from-aligned-doc
5050 /// (`"Checkout\tflow."`), and paste-from-binary-blob NUL / BEL /
5051 /// ESC / DEL bytes. Mirrors the shape-predicate cascade
5052 /// [`Self::validate_repositorio`] / [`Self::validate_licenca`] /
5053 /// [`Self::validate_edicao`] establish past their own empty arms
5054 /// on the sibling universal-axis `Option<String>` Caixa-level
5055 /// value-shape surfaces.
5056 ///
5057 /// The empty-first cascade discipline mirrors every peer per-axis
5058 /// identity gate: [`ManifestError::DescricaoEmpty`] runs before
5059 /// [`ManifestError::DescricaoInvalid`], so the narrower empty
5060 /// diagnostic surfaces on `Some("")` rather than the broader
5061 /// shape-predicate diagnostic — peer with how
5062 /// [`ManifestError::LicencaEmpty`] runs before
5063 /// [`ManifestError::LicencaInvalid`],
5064 /// [`ManifestError::EdicaoEmpty`] runs before
5065 /// [`ManifestError::EdicaoInvalid`],
5066 /// [`ManifestError::RepositorioEmpty`] runs before
5067 /// [`ManifestError::RepositorioInvalid`].
5068 pub fn validate_descricao(&self) -> Result<(), ManifestError> {
5069 let Some(s) = self.descricao() else {
5070 return Ok(());
5071 };
5072 if s.is_empty() {
5073 return Err(ManifestError::DescricaoEmpty);
5074 }
5075 crate::render::is_chart_description_shape(s).map_err(|reason| {
5076 ManifestError::DescricaoInvalid {
5077 descricao: s.to_string(),
5078 reason,
5079 }
5080 })?;
5081 Ok(())
5082 }
5083
5084 /// Reject `:licenca` values that are the empty string. The flat
5085 /// `licenca: Option<String>` slot on [`Caixa`] is the universal
5086 /// SPDX-shaped license-expression axis every kind carries — the
5087 /// substrate routes the same string through the [`caixa-helm`]
5088 /// renderer's `build_readme` which folds it verbatim into the
5089 /// rendered `lareira-<nome>` Helm chart's `README.md` `## License`
5090 /// section (`caixa-helm/src/lib.rs:361`) via
5091 /// `caixa.licenca.clone().unwrap_or_else(|| "MIT".into())`. The
5092 /// fallback only fires on `None`; a `Some("")` *skips the
5093 /// fallback* and silently passes the empty string through to a
5094 /// chart `README.md` whose `License` section renders as the bare
5095 /// trailing period (`.\n`) — peer footgun with the
5096 /// `Some("")`-skips-`unwrap_or_else` shape the
5097 /// [`Self::validate_descricao`] and [`Self::validate_repositorio`]
5098 /// gates close on the sibling free-form-prose and git-URL axes.
5099 ///
5100 /// `None` (the canonical "omit the slot to defer to the
5101 /// renderer's `MIT` fallback" shape every existing fixture
5102 /// carries) is accepted trivially — the gate is a no-op when the
5103 /// author didn't declare a value. `Some("")` is gated by the
5104 /// narrower [`ManifestError::LicencaEmpty`] arm, mirroring the
5105 /// empty-arm shape every peer per-axis empty gate uses
5106 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
5107 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
5108 /// [`ManifestError::RepositorioEmpty`],
5109 /// [`ManifestError::DescricaoEmpty`]).
5110 ///
5111 /// Universal-axis (every kind carries `:licenca`), so wired at
5112 /// the caixa-build gate alongside the peer universal gates
5113 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5114 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
5115 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
5116 /// [`Self::validate_descricao`] / [`Self::validate_code_paths`]
5117 /// — before the kind-coherence gates
5118 /// ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5119 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5120 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5121 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
5122 /// specific slot sets.
5123 ///
5124 /// Past the empty arm the gate enforces the SPDX-expression shape
5125 /// predicate via [`crate::render::is_spdx_expression_shape`]: the
5126 /// structural alphabet floor every realistic SPDX expression in
5127 /// the wild uses — ASCII alphanumeric plus `.`, `-`, `+`, `(`,
5128 /// `)`, `:` (the `DocumentRef-…:LicenseRef-…` separator), and a
5129 /// single ASCII space (token separator). Closes the canonical
5130 /// paste-from-doc footguns the bare empty-arm gate left open:
5131 /// paste-from-doc whitespace (`"MIT "`, `" MIT"`), paste-from-
5132 /// multiline-doc CRLF (`"MIT\n"`), tab-from-aligned-doc
5133 /// (`"MIT\tOR Apache-2.0"`), non-ASCII smart-quote paste,
5134 /// underscore-instead-of-hyphen typo (`"Apache_2.0"`),
5135 /// comma-instead-of-`OR`-keyword colloquial idiom (`"MIT,
5136 /// Apache-2.0"`), slash-dual-license colloquial idiom (`"MIT/
5137 /// Apache-2.0"`), and semicolon-list-separator confusion
5138 /// (`"MIT; Apache-2.0"`). Mirrors the shape-predicate cascade
5139 /// [`Self::validate_repositorio`] / [`Self::validate_edicao`]
5140 /// establish past their own empty arms.
5141 ///
5142 /// The empty-first cascade discipline mirrors every peer per-axis
5143 /// identity gate: [`ManifestError::LicencaEmpty`] runs before
5144 /// [`ManifestError::LicencaInvalid`], so the narrower empty
5145 /// diagnostic surfaces on `Some("")` rather than the broader
5146 /// shape-predicate diagnostic — peer with how
5147 /// [`ManifestError::EdicaoEmpty`] runs before
5148 /// [`ManifestError::EdicaoInvalid`],
5149 /// [`ManifestError::RepositorioEmpty`] runs before
5150 /// [`ManifestError::RepositorioInvalid`].
5151 ///
5152 /// A future tightening on this axis can extend the alphabet
5153 /// floor into a full SPDX expression parser + license-id
5154 /// allowlist (rejecting alphabet-valid values that don't name a
5155 /// real SPDX license identifier — e.g., `"NotAReal"` is
5156 /// alphabet-valid but no `NotAReal` license-id exists). That
5157 /// parser only becomes meaningful past a real SPDX-spec
5158 /// dependency; this gate establishes the structural floor by
5159 /// refusing every non-SPDX-alphabet value at validate time.
5160 pub fn validate_licenca(&self) -> Result<(), ManifestError> {
5161 let Some(s) = self.licenca() else {
5162 return Ok(());
5163 };
5164 if s.is_empty() {
5165 return Err(ManifestError::LicencaEmpty);
5166 }
5167 crate::render::is_spdx_expression_shape(s).map_err(|reason| {
5168 ManifestError::LicencaInvalid {
5169 licenca: s.to_string(),
5170 reason,
5171 }
5172 })?;
5173 Ok(())
5174 }
5175
5176 /// Reject `:edicao` values that are the empty string. The flat
5177 /// `edicao: Option<String>` slot on [`Caixa`] is the universal
5178 /// language-edition axis every kind carries — it determines the
5179 /// tatara-lisp macro surface + compatibility flags the substrate
5180 /// applies when building a caixa, and lands verbatim in the
5181 /// `Caixa::template` author-time scaffold (the canonical
5182 /// `:edicao "2026"` line every `feira init` emits via
5183 /// [`Caixa::template`] at `caixa-core/src/manifest.rs:1193`) and
5184 /// in every renderer-side fixture (`caixa-helm/src/lib.rs:375`,
5185 /// `caixa-flux/src/lib.rs:445`, `caixa-mesh/src/lib.rs:629`,
5186 /// `caixa-core/src/render.rs:2510`) via
5187 /// `edicao: Some("2026".into())`.
5188 ///
5189 /// `None` (the canonical "omit the slot to defer to the
5190 /// substrate's default edition" shape every existing
5191 /// [`caixa-resolver`] integration test fixture carries via
5192 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
5193 /// is accepted trivially — the gate is a no-op when the author
5194 /// didn't declare a value. `Some("")` is gated by the narrower
5195 /// [`ManifestError::EdicaoEmpty`] arm, mirroring the empty-arm
5196 /// shape every peer per-axis empty gate uses
5197 /// ([`ManifestError::NomeEmpty`], [`ManifestError::VersaoEmpty`],
5198 /// [`ManifestError::EtiquetaEmpty`], [`ManifestError::AutorEmpty`],
5199 /// [`ManifestError::RepositorioEmpty`],
5200 /// [`ManifestError::DescricaoEmpty`], [`ManifestError::LicencaEmpty`]).
5201 ///
5202 /// Universal-axis (every kind carries `:edicao`), so wired at
5203 /// the caixa-build gate alongside the peer universal gates
5204 /// [`Self::validate_nome`] / [`Self::validate_versao`] /
5205 /// [`Self::validate_deps`] / [`Self::validate_etiquetas`] /
5206 /// [`Self::validate_autores`] / [`Self::validate_repositorio`] /
5207 /// [`Self::validate_descricao`] / [`Self::validate_licenca`] /
5208 /// [`Self::validate_code_paths`] — before the kind-coherence
5209 /// gates ([`crate::LayoutError::MeshSlotsOnNonAplicacao`] /
5210 /// [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] /
5211 /// [`crate::LayoutError::ServicoSlotsOnNonServico`] /
5212 /// [`crate::LayoutError::ForeignCodeSlot`]) which fence kind-
5213 /// specific slot sets.
5214 ///
5215 /// Past the empty arm the gate enforces the canonical year-shape
5216 /// predicate: every documented tatara-lisp edition is a 4-digit
5217 /// ASCII decimal year (`"2026"` is the only edition currently
5218 /// minted; future-introduced siblings will follow the same
5219 /// shape, peer with Cargo's `[package] edition` grammar which
5220 /// every value Cargo has ever accepted matches — `"2015"`,
5221 /// `"2018"`, `"2021"`, `"2024"`). Any value that's not exactly
5222 /// 4 ASCII decimal bytes is rejected with the narrower
5223 /// [`ManifestError::EdicaoInvalid`] arm, mirroring the
5224 /// shape-predicate cascade [`Self::validate_repositorio`]
5225 /// establishes past its own empty arm
5226 /// ([`ManifestError::RepositorioEmpty`] →
5227 /// [`ManifestError::RepositorioInvalid`]). Closes the canonical
5228 /// paste-from-doc footguns the bare empty-arm gate left open:
5229 ///
5230 /// - leading / trailing whitespace from a paste-from-doc
5231 /// (`"2026 "`, `" 2026"`)
5232 /// - control characters / CRLF from a paste-from-multiline-doc
5233 /// (`"2026\n"`)
5234 /// - non-ASCII look-alikes from a fullwidth keyboard
5235 /// (`"2026"`) which would silently land as a non-ASCII
5236 /// string in the rendered caixa.lisp
5237 /// - free-form non-year values (`"x"`, `"latest"`,
5238 /// `"nightly"`) that have no operational meaning on the
5239 /// substrate's build-time edition selector
5240 /// - leading non-digit prefixes (`"v2026"`, `"e2026"`,
5241 /// `"r2026"`) — common version-tag idioms that don't apply
5242 /// to the year-shaped edition axis
5243 /// - decimal-shaped values (`"2026.1"`, `"2026.0"`) — every
5244 /// edition is a year, not a fractional version
5245 /// - wrong-length numeric values (`"26"`, `"202"`, `"20260"`,
5246 /// `"00026"`) that don't name a year
5247 ///
5248 /// `None` (the canonical "omit the slot to defer to the
5249 /// substrate's default edition" shape every existing
5250 /// [`caixa-resolver`] integration test fixture carries via
5251 /// `edicao: None` — see `caixa-resolver/tests/git_integration.rs`)
5252 /// is accepted trivially — the gate is a no-op when the author
5253 /// didn't declare a value. The empty-first cascade discipline
5254 /// mirrors every peer per-axis identity gate:
5255 /// [`ManifestError::EdicaoEmpty`] runs before
5256 /// [`ManifestError::EdicaoInvalid`], so the narrower empty
5257 /// diagnostic surfaces on `Some("")` rather than the broader
5258 /// shape-predicate diagnostic — peer with how
5259 /// [`ManifestError::NomeEmpty`] runs before
5260 /// [`ManifestError::NomeInvalid`],
5261 /// [`ManifestError::VersaoEmpty`] runs before
5262 /// [`ManifestError::VersaoInvalid`],
5263 /// [`ManifestError::RepositorioEmpty`] runs before
5264 /// [`ManifestError::RepositorioInvalid`].
5265 ///
5266 /// A future tightening on this axis can extend the shape
5267 /// predicate into a known-edition allowlist (rejecting
5268 /// year-shaped values that don't name a tatara-lisp edition
5269 /// the substrate actually understands — e.g., `"1999"` is
5270 /// year-shaped but no `1999` edition exists). That allowlist
5271 /// only becomes meaningful past the introduction of a sibling
5272 /// edition to `"2026"`; this gate establishes the structural
5273 /// floor by refusing every non-year-shaped value at validate
5274 /// time.
5275 pub fn validate_edicao(&self) -> Result<(), ManifestError> {
5276 let Some(s) = self.edicao() else {
5277 return Ok(());
5278 };
5279 if s.is_empty() {
5280 return Err(ManifestError::EdicaoEmpty);
5281 }
5282 if s.len() != 4 || !s.bytes().all(|b| b.is_ascii_digit()) {
5283 return Err(ManifestError::EdicaoInvalid {
5284 edicao: s.to_string(),
5285 reason: "must be a 4-digit ASCII decimal year (canonical \"2026\")".to_string(),
5286 });
5287 }
5288 Ok(())
5289 }
5290
5291 /// Compose the supervisor-related flat slots into a single
5292 /// [`SupervisorSpec`] for validation. Returns `None` when the
5293 /// caixa isn't a `:kind Supervisor`.
5294 ///
5295 /// The flat representation in [`Caixa`] keeps tatara-lisp authoring
5296 /// simple (one form, no nested `:supervisor (…)` block); this view
5297 /// is the "typed shape" the operator + supervisor reconciler
5298 /// consume.
5299 #[must_use]
5300 pub fn supervisor_view(&self) -> Option<SupervisorSpec> {
5301 if !self.kind().is_supervisor() {
5302 return None;
5303 }
5304 // Fold through the shared `supervisor::duration_codec::parse`
5305 // — the same parser the serde-routed `with = "duration_codec"`
5306 // on `SupervisorSpec::restart_window`, the `:politicas
5307 // :timeout` codec, and the `:politicas :circuit-breaker
5308 // :window` codec all consume. The prior inline f64-shaped
5309 // duplicate (`parse_window_inline`) admitted every magnitude
5310 // the integer-magnitude gate (1c55a2a) rejects on the three
5311 // serde-routed siblings — `"1.5s"`, `"1.0s"`, `"0.5m"`,
5312 // `"+30s"`, `"-30s"` — and silently dropped malformed input as
5313 // `None` (i.e. "no reset"), divergent from the shared codec's
5314 // integer-magnitude discipline by construction. The fold
5315 // closes the divergence: every value the typed
5316 // `SupervisorSpec` carries past `supervisor_view` is in the
5317 // shared codec's accepted set. The `.ok()` here preserves the
5318 // existing soft-swallow shape on this view-construction path;
5319 // the new [`Caixa::validate_restart_window`] (sibling of
5320 // [`Self::validate_nome`] / [`Self::validate_versao`]) names
5321 // the offending raw string at build time so authoring tools
5322 // (`feira lint`, the future layout-side wire-up) surface a
5323 // self-locating diagnostic instead of a silently dropped
5324 // window.
5325 let restart_window = self
5326 .restart_window()
5327 .and_then(|s| crate::supervisor::duration_codec::parse(s).ok());
5328 Some(SupervisorSpec {
5329 // Route the author-omitted `:estrategia` arm through the
5330 // substrate-canonical
5331 // [`crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT`] typed
5332 // `pub const` rather than the transitively-derived
5333 // [`RestartStrategy::default`] route the prior
5334 // `.unwrap_or_default()` fold reached for — one source of
5335 // truth for the Erlang/OTP `one_for_one` half of Learn You
5336 // Some Erlang's `{one_for_one, intensity, 5, 60}` worker-
5337 // supervisor canonical default that also backs the
5338 // [`crate::supervisor::Default for RestartStrategy`] impl
5339 // and the [`crate::supervisor::Default for SupervisorSpec`]
5340 // impl's struct-literal `estrategia` field, all now routed
5341 // through the same lifted constant. Prior to the lift the
5342 // composition site carried `.unwrap_or_default()` with no
5343 // compile-time link back to the shared OTP-canonical
5344 // default that the peer paired
5345 // `.unwrap_or(SUPERVISOR_MAX_RESTARTS_DEFAULT)` (b698ec0)
5346 // arm on the sibling `:max-restarts` axis routes through —
5347 // so a future rebrand of the OTP-canonical strategy default
5348 // (a widening to `rest_for_one` once the substrate
5349 // discovers startup-order-coupled child cohorts as the more
5350 // common shape, a per-cluster overlay the operator pins
5351 // through the MESH-COMPOSITION §III.2 supervision-canary
5352 // `:estrategia-overrides` roadmap slot) would have had to
5353 // migrate the paired `MaxIntensity` + `Period` halves
5354 // through the lifted constants and the `one_for_one` half
5355 // through a `RestartStrategy::default()` route in lockstep
5356 // or the three halves of the same OTP-canonical default
5357 // would silently drift out of pairing. Byte-parity against
5358 // the lifted constant closes the split. Pinned by
5359 // [`supervisor_view_estrategia_fallback_routes_through_lifted_default`]
5360 // in the tests module.
5361 estrategia: self
5362 .estrategia()
5363 .unwrap_or(crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT),
5364 // Route the author-omitted `:max-restarts` arm through the
5365 // substrate-canonical [`crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT`]
5366 // typed `pub const` rather than the raw `5` literal — one
5367 // source of truth for the Erlang/OTP-canonical
5368 // `{intensity, 5, 60}` `MaxIntensity` default that also
5369 // backs the serde-side wire-format author-omitted arm on
5370 // [`crate::supervisor::SupervisorSpec::max_restarts`] via
5371 // `#[serde(default = "default_max_restarts")]` and the
5372 // [`Default for SupervisorSpec`] impl's struct-literal
5373 // default field. Prior to the lift the composition site
5374 // carried a raw `5` with no compile-time link back to the
5375 // serde-side default, so a future rebrand of the OTP-
5376 // canonical default (a tightening to Elixir's `3`, a
5377 // widening to a per-cluster overlay the operator pins
5378 // through the MESH-COMPOSITION §III.2 supervision-canary
5379 // `:supervisor :max-restarts-overrides` roadmap slot)
5380 // would have had to be threaded through both open-coded
5381 // copies in lockstep or the wire-format author-omitted arm
5382 // and this view-construction author-omitted arm would
5383 // silently disagree on which restart-budget an omitted
5384 // `:max-restarts` resolves to. Pinned by
5385 // [`supervisor_view_max_restarts_fallback_routes_through_lifted_default`]
5386 // in the tests module.
5387 max_restarts: self
5388 .max_restarts()
5389 .unwrap_or(crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT),
5390 restart_window,
5391 children: self.children().to_vec(),
5392 })
5393 }
5394
5395 /// A minimal starter manifest emitted by `feira init`.
5396 #[must_use]
5397 pub fn template(nome: &str) -> String {
5398 format!(
5399 "(defcaixa\n \
5400 :nome {nome:?}\n \
5401 :versao \"0.1.0\"\n \
5402 :kind Biblioteca\n \
5403 :edicao \"2026\"\n \
5404 :descricao \"FIXME — describe this caixa\"\n \
5405 :autores ()\n \
5406 :etiquetas ()\n \
5407 :deps ()\n \
5408 :deps-dev ()\n \
5409 :bibliotecas (\"lib/{nome}.lisp\"))\n"
5410 )
5411 }
5412
5413 /// Serialize to a canonical `caixa.lisp` source — suitable for writing
5414 /// back after mutation (e.g. `feira add`).
5415 ///
5416 /// Goes through serde JSON → canonical Sexp → per-field pretty print.
5417 /// The derive-macro `compile_from_sexp` path is the inverse, so any
5418 /// `Caixa` round-trips through `to_lisp` + `from_lisp`.
5419 #[must_use]
5420 pub fn to_lisp(&self) -> String {
5421 let json = serde_json::to_value(self).expect("Caixa serialize");
5422 let sexp = tatara_lisp::domain::json_to_sexp(&json);
5423 let tatara_lisp::Sexp::List(items) = sexp else {
5424 return format!("(defcaixa {sexp})\n");
5425 };
5426 let mut out = String::from("(defcaixa");
5427 let mut i = 0;
5428 while i + 1 < items.len() {
5429 out.push_str("\n ");
5430 out.push_str(&items[i].to_string());
5431 out.push(' ');
5432 out.push_str(&items[i + 1].to_string());
5433 i += 2;
5434 }
5435 out.push_str(")\n");
5436 out
5437 }
5438}
5439
5440/// Errors raised by top-level [`Caixa`] validators that don't fit
5441/// the per-axis [`DepError`] / [`crate::AplicacaoError`] /
5442/// [`crate::SupervisorError`] / [`crate::LayoutError`] families —
5443/// the Caixa's own identity axes (`:nome`, `:versao`) that flow
5444/// through every substrate-side artifact's `metadata.name` /
5445/// version derivation.
5446///
5447/// A future top-level sum (the M4 `CaixaError` the [`DepError`]
5448/// doc-comment anticipates) can hold one of each per-axis error
5449/// family without reshaping individual diagnostics; this enum is
5450/// the first such per-Caixa-identity family.
5451#[derive(Debug, Error, PartialEq, Eq)]
5452pub enum ManifestError {
5453 #[error(
5454 ":nome is empty (every caixa must name itself; the value flows \
5455 into every K8s artifact's `metadata.name` derivation and into \
5456 the default `lib/<nome>.lisp` / `exe/<nome>` layout paths)"
5457 )]
5458 NomeEmpty,
5459 #[error(
5460 ":nome {nome:?} is not a valid DNS-1123 label: {reason} (the K8s \
5461 apiserver enforces this rule on every `metadata.name` the \
5462 caixa's substrate-side renderers derive from `:nome` — the \
5463 `lareira-<nome>` Helm chart name, the programs.yaml entry \
5464 name, the `LABEL_APLICACAO` label value, the `<aplicacao>-<de>-to-<para>` \
5465 CiliumNetworkPolicy name, the `<aplicacao>-<para>` HTTPRoute \
5466 name; use a lowercase alphanumeric + hyphen identifier like \
5467 `\"checkout\"` or `\"cart-v2\"`)"
5468 )]
5469 NomeInvalid { nome: String, reason: String },
5470 #[error(
5471 ":nome {nome:?} overflows the joint-length budget on the canonical \
5472 `lareira-<nome>` chart-name shape: {reason} (every per-Servico / \
5473 per-Aplicacao renderer the substrate carries — `caixa-helm`'s \
5474 `Chart.yaml::name`, `caixa-flux`'s `cluster_bundle` HelmRelease \
5475 `chart:` slot, `caixa-tatara`'s `release_name` + \
5476 `oci://<registry>/lareira-<nome>` chart ref — derives the same \
5477 joint name through the canonical `lareira_chart_name` helper, and \
5478 Helm's `Chart.yaml::name` admission rule + the K8s apiserver's \
5479 DNS-1123 label cap on every chart-name-derived `metadata.name` \
5480 reject any joint name exceeding 63 bytes; the narrower \
5481 `:nome` shape (`NomeInvalid`) gates the bare-`:nome` budget, this \
5482 arm gates the chart-name budget downstream renderers inherit)"
5483 )]
5484 NomeChartNameBudgetExceeded { nome: String, reason: String },
5485 #[error(
5486 ":versao is empty (every caixa must pin its own version; the value flows \
5487 into the `lareira-<nome>` Helm chart's `Chart.yaml` version + appVersion, \
5488 the `feira publish` `v<versao>` git tag, the OCI image's `:v<versao>` / \
5489 `:latest` tags, the lacre closure's `concrete_versao`, and the \
5490 `:upgrade-from :from` peers — use a SemVer-2 literal like `\"0.1.0\"`)"
5491 )]
5492 VersaoEmpty,
5493 #[error(
5494 ":versao {versao:?} is not a valid SemVer-2 version: {reason} (the substrate \
5495 consumes this string as `semver::Version` — three-part `MAJOR.MINOR.PATCH` \
5496 with optional `-prerelease` and `+build` — across every artifact derived \
5497 from `:versao`: the `lareira-<nome>` Helm chart's `Chart.yaml` version + \
5498 appVersion (Helm SemVer-2-strict), the `feira publish` `v<versao>` git tag, \
5499 the OCI image's `:v<versao>` tag, the lacre closure's `concrete_versao`, \
5500 and the `:upgrade-from :from` peers that match against this exact shape; \
5501 use a literal like `\"0.1.0\"`, `\"0.2.0-rc.1\"`, or `\"1.0.0+build.42\"` — \
5502 not a git-tag-shape like `\"v0.1.0\"`, a docker-tag-shape like `\"latest\"`, \
5503 a requirement-shape like `\"^0.1\"`, or a four-part `\"0.1.0.0\"`)"
5504 )]
5505 VersaoInvalid { versao: String, reason: String },
5506 #[error(
5507 ":restart-window {restart_window:?} is not a valid duration: {reason} (the \
5508 substrate consumes this string through the shared \
5509 `supervisor::duration_codec` — the same parser routed via `with = \
5510 \"duration_codec\"` onto the typed `SupervisorSpec::restart_window`, \
5511 `:politicas :timeout`, and `:politicas :circuit-breaker :window` slots; \
5512 the canonical authoring form is `<integer><unit>` where the unit is one \
5513 of `ms` / `s` / `m` / `h` and the magnitude has no decimal point and no \
5514 leading `+` / `-` sign — e.g. `\"60s\"`, `\"5m\"`, `\"1h\"`, `\"500ms\"`. \
5515 Without this gate a malformed `:restart-window` silently produced a \
5516 supervisor with `restart_window: None` (\"never reset\"), turning OTP's \
5517 `MaxIntensity / Period` invariant into a never-reset supervisor far from \
5518 the source `caixa.lisp`; the gate moves the diagnostic to the manifest \
5519 layer with the offending value named verbatim. Omit the slot entirely to \
5520 express \"no reset\"; carry a positive integer duration to express the \
5521 sliding window)"
5522 )]
5523 RestartWindowMalformed {
5524 restart_window: String,
5525 reason: String,
5526 },
5527 #[error(
5528 "{slot} entry is an empty path string — every {slot} entry must name \
5529 a file relative to the caixa root; omit the entry to omit the file \
5530 (the layout checker's `root.join(\"\")` resolves to the caixa root \
5531 itself, so an empty entry silently aliases the project root as a \
5532 declared {slot} file, then fails downstream at parse / existence \
5533 time with a diagnostic that names the root rather than the offending \
5534 entry)"
5535 )]
5536 CodePathEmpty { slot: &'static str },
5537 #[error(
5538 "{slot} entry {} is an absolute path — entries must be relative to \
5539 the caixa root, since `Path::join` replaces the base with an absolute \
5540 right-hand side and `root.join(\"/abs/...\")` resolves to \"/abs/...\" \
5541 outside the caixa root sandbox; rewrite the entry as a relative path \
5542 under the caixa root (e.g. `\"lib/<name>.lisp\"`, `\"exe/<name>\"`, \
5543 `\"servicos/<name>.computeunit.yaml\"`)",
5544 path.display()
5545 )]
5546 CodePathAbsolute { slot: &'static str, path: PathBuf },
5547 #[error(
5548 "{slot} entry {} contains a `..` component — entries must not traverse \
5549 above the caixa root (the layout's `starts_with(<dir>)` fence on \
5550 `:exe` / `:servicos` is component-aware, not canonical-path-aware, \
5551 so a mid-path `..` silently traverses the sandbox; `:bibliotecas` \
5552 has no such fence, so a leading `..` escapes unconditionally if the \
5553 resolved target happens to exist)",
5554 path.display()
5555 )]
5556 CodePathParentEscape { slot: &'static str, path: PathBuf },
5557 #[error(
5558 "{slot} entry {} does not terminate in the `.lisp` extension — every \
5559 `:bibliotecas` entry is a tatara-lisp source file the `feira build` \
5560 loop reads through `tatara_lisp::read` at parse time, so any other \
5561 extension (`.rs`, `.txt`, `.lisp.bak`) or no-extension shape is \
5562 structurally a parser error far from the source caixa.lisp, with \
5563 no field naming the offending `:bibliotecas` entry. Pin a relative \
5564 path under the caixa root whose terminating extension is \
5565 lowercase-`.lisp` (e.g. `\"lib/<name>.lisp\"`, \
5566 `\"lib/handlers.lisp\"`) — the same file-type contract the peer \
5567 `:behavior :on-*` (c97815a) and `:upgrade-from :state-change :script` \
5568 (33cc830) axes already carry through the same lifted \
5569 `is_lisp_extension` predicate",
5570 path.display()
5571 )]
5572 CodePathNonLispExtension { slot: &'static str, path: PathBuf },
5573 #[error(
5574 "{slot} entry {} does not terminate in the `.computeunit.yaml` \
5575 compound suffix — every `:servicos` entry is a typed `ComputeUnit` \
5576 CR YAML file the peer caixa-helm / caixa-flux renderers consume \
5577 through `serde_yaml::from_str` at chart / FluxCD bundle render \
5578 time, so any other extension (`.yaml`, `.yml`, `.json`, the \
5579 off-by-one-segment `.computeunit-yaml`, the editor-backup \
5580 `.computeunit.yaml.bak`) or no-extension shape is structurally a \
5581 YAML-parser error / `ComputeUnit` schema-mismatch far from the \
5582 source caixa.lisp, with no field naming the offending `:servicos` \
5583 entry. Pin a relative path under the caixa root whose terminating \
5584 compound suffix is lowercase-`.computeunit.yaml` (e.g. \
5585 `\"servicos/<name>.computeunit.yaml\"`, \
5586 `\"servicos/hello-rio.computeunit.yaml\"`) — the same file-type \
5587 contract the sibling `:bibliotecas` axis (64772a9) already carries \
5588 on the tatara-lisp-source axis through the peer lifted \
5589 `is_lisp_extension` predicate, here on the compound-suffix axis \
5590 `Path::extension` can't express on its own through the lifted \
5591 `is_computeunit_yaml_extension` predicate",
5592 path.display()
5593 )]
5594 CodePathNonComputeUnitYamlExtension { slot: &'static str, path: PathBuf },
5595 #[error(
5596 "{slot} entry {} appears more than once (the code-path list is \
5597 a set, not a multiset; every peer Vec-shaped author-supplied \
5598 list past validate is set-not-multiset — `:membros :caixa`, \
5599 `:placement :clusters`, `:entrada :paths`, `:contratos`, \
5600 `:children :caixa`, `:deps` / `:deps-dev` `:nome`, \
5601 `:upgrade-from :from`, `:etiquetas`, `:autores` — and the three \
5602 code-path lists are the last Vec-shaped author-supplied slots on \
5603 the typed Caixa surface still admitting a duplicate entry. \
5604 `:bibliotecas` duplicates re-parse the same file at \
5605 `feira build` time and silently mask the author's intent to \
5606 declare a *second* biblioteca; `:exe` duplicates collide on the \
5607 flake `packages.<name>` derivation key at the future \
5608 `caixa-flake` materializer; `:servicos` duplicates surface as the \
5609 narrower [`caixa-helm`] / [`caixa-flux`] `UnsupportedServicoCount` \
5610 rejection far from the source `caixa.lisp`. Drop the duplicate \
5611 or rename it to the actual second file intended)",
5612 path.display()
5613 )]
5614 CodePathDuplicate { slot: &'static str, path: PathBuf },
5615 #[error(
5616 ":etiquetas entry is empty (every tag must carry a non-empty \
5617 registry-search identifier; the empty entry has no operational \
5618 meaning — it indexes nothing in the future caixa-registry search \
5619 axis and clutters the rendered Helm `Chart.yaml` `keywords:` array \
5620 with a no-op tag; omit the entry to express \"no tag on this \
5621 position\")"
5622 )]
5623 EtiquetaEmpty,
5624 #[error(
5625 ":etiquetas entry {etiqueta:?} appears more than once (the \
5626 registry-search tag set is a set, not a multiset; duplicate \
5627 entries are silently dedup'd by caixa-helm's `BTreeSet` collect \
5628 at chart render — a \"second wins / one silently disappears\" \
5629 shape divergent from every peer typed-graph set gate \
5630 (`:membros :caixa`, `:placement :clusters`, `:entrada :paths`, \
5631 `:contratos`, `:deps :nome`, `:upgrade-from :from`); drop the \
5632 duplicate or rename it to the actual tag intended)"
5633 )]
5634 EtiquetaDuplicate { etiqueta: String },
5635 #[error(
5636 ":etiquetas entry {etiqueta:?} is not a valid chart-keyword shape: \
5637 {reason} (the substrate consumes this string through the shared \
5638 `crate::render::is_chart_keyword_shape` predicate — the same \
5639 Cargo crates.io `[package] keywords` grammar entry shape: 1..=20 \
5640 bytes, starts with an ASCII letter, ASCII alphanumeric / `_` / `-` \
5641 continuation. The canonical authoring shapes are short kebab-case \
5642 identifiers like `\"mesh\"`, `\"wasm\"`, `\"tatara-lisp\"`, \
5643 `\"hello-world\"`, `\"caixa-servico\"`, `\"infrastructure\"`. \
5644 Without this gate a malformed `:etiquetas` entry (paste-from-doc \
5645 leading / trailing whitespace `\" mesh\"` / `\"mesh \"`; \
5646 paste-from-multiline-doc newline `\"mesh\\nhttp\"`; \
5647 paste-from-Windows-CRLF-doc CR; CSV-list-separator confusion \
5648 `\"mesh,http,grpc\"` — the author meant to author three separate \
5649 list entries; path-separator confusion `\"caixa/servico\"`; \
5650 namespace-suffix `\"http.1\"`; leading-digit `\"1foo\"`; \
5651 kebab-leak `\"-foo\"`; snake-leak `\"_foo\"`; non-ASCII \
5652 `\"café\"` — every legitimate search tag is strict ASCII; \
5653 paste-from-binary-blob NUL / BEL / ESC / DEL byte) silently \
5654 passed `from_lisp` + `validate_etiquetas` + \
5655 `StandardLayout::verify` and landed in the rendered \
5656 `lareira-<nome>` Helm chart's `Chart.yaml keywords:` array as a \
5657 malformed search tag — Artifact Hub's keyword index + the future \
5658 caixa-registry's keyword index would either silently drop the \
5659 tag or fail to index it far from the source caixa.lisp; the gate \
5660 moves the diagnostic to the manifest layer with the offending \
5661 value named verbatim)"
5662 )]
5663 EtiquetaInvalid { etiqueta: String, reason: String },
5664 #[error(
5665 ":autores entry is empty (every maintainer must carry a non-empty \
5666 identifier; the empty entry has no operational meaning — it \
5667 identifies no one in the substrate's authorship index and renders \
5668 as `maintainers: [{{name: \"\", email: null}}]` in the Helm chart's \
5669 `Chart.yaml`, a no-op maintainer the substrate cannot route to; \
5670 omit the entry to express \"no maintainer on this position\")"
5671 )]
5672 AutorEmpty,
5673 #[error(
5674 ":autores entry {autor:?} appears more than once (the maintainer \
5675 set is a set, not a multiset; unlike `:etiquetas`, caixa-helm's \
5676 `maintainers:` rendering does *no* dedup — duplicate entries \
5677 stack verbatim in `Chart.yaml` as two identical \
5678 `Maintainer {{ name, email: None }}` records, divergent from every \
5679 peer typed-graph set gate (`:etiquetas`, `:membros :caixa`, \
5680 `:placement :clusters`, `:entrada :paths`, `:contratos`, \
5681 `:deps :nome`, `:upgrade-from :from`); drop the duplicate or \
5682 rename it to the actual author intended)"
5683 )]
5684 AutorDuplicate { autor: String },
5685 #[error(
5686 ":autores entry {autor:?} is not a valid chart-maintainer-name shape: \
5687 {reason} (the substrate consumes this string through the shared \
5688 `crate::render::is_chart_maintainer_name_shape` predicate — the same \
5689 single-line-UTF-8 floor every realistic chart maintainer name carries: \
5690 1..=128 bytes, no leading or trailing whitespace, no ASCII control \
5691 characters anywhere, Unicode bytes accepted. The canonical authoring \
5692 shapes are short single-line identifiers like `\"pleme-io\"`, \
5693 `\"Pleme Contributors\"`, `\"alice <alice@example.com>\"`, \
5694 `\"François Dupont\"`. Without this gate a malformed `:autores` entry \
5695 (paste-from-aligned-doc leading whitespace `\" pleme-io\"` / trailing \
5696 whitespace `\"pleme-io \"`; paste-from-multiline-doc newline \
5697 `\"alice\\nbob\"` — the author pasted a multi-line block of author \
5698 records into one entry instead of splitting into one entry per author; \
5699 paste-from-Windows-CRLF-doc carriage return `\"alice\\rbob\"`; \
5700 tab-from-aligned-doc `\"Pleme\\tContributors\"`; paste-from-binary-blob \
5701 NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
5702 `validate_autores` + `StandardLayout::verify` and landed in the \
5703 rendered `lareira-<nome>` Helm chart's `Chart.yaml maintainers:` array \
5704 as a YAML-illegal multi-line scalar or a silently-trimmed whitespace \
5705 round-trip — every chart-aware UI (`helm list`, `helm search`, \
5706 Artifact Hub maintainer index) would render the maintainer name in a \
5707 single-line column far from the source caixa.lisp; the gate moves the \
5708 diagnostic to the manifest layer with the offending value named \
5709 verbatim)"
5710 )]
5711 AutorInvalid { autor: String, reason: String },
5712 #[error(
5713 ":repositorio is the empty string (every published caixa names its \
5714 git source via a non-empty `:repositorio` locator — the value \
5715 flows verbatim into the rendered `lareira-<nome>` Helm chart's \
5716 `Chart.yaml` `home:` field via `caixa-helm` and into the FluxCD \
5717 `GitRepository.spec.url` via `caixa-flux`'s \
5718 `ClusterBundleOpts::for_caixa`; both consumers' \
5719 `Option::unwrap_or_else` fallbacks only fire when the slot is \
5720 `None`, so an empty `Some(\"\")` silently lands as `home: \"\"` / \
5721 `url: \"\"` in the rendered artifacts and breaks at `helm \
5722 template` / FluxCD source-controller reconcile time far from the \
5723 source caixa.lisp; omit the slot entirely to defer to the \
5724 renderer's `https://github.com/pleme-io/<nome>` / \
5725 `caixa.nome`-derived fallback, or carry a canonical authoring \
5726 shape like `\"github:org/repo\"`, `\"https://host/path\"`, \
5727 `\"ssh://[user@]host/path\"`, `\"git@host:path\"`, or \
5728 `\"file:///path\"`)"
5729 )]
5730 RepositorioEmpty,
5731 #[error(
5732 ":repositorio {repositorio:?} is not a valid git repo URL: {reason} \
5733 (the substrate consumes this string through the shared \
5734 `crate::render::is_git_repo_url` predicate — the same parser the \
5735 peer `:deps :fonte (:tipo git :repo …)` axis routes its `:repo` \
5736 value through via `DepSource::validate`; the canonical authoring \
5737 shapes are `\"github:org/repo\"` shorthand, `\"https://host/path\"` \
5738 / `\"ssh://[user@]host/path\"` / `\"git://host/path\"` / \
5739 `\"file:///path\"` URL schemes, or the `\"git@host:path\"` \
5740 scp-style SSH form. Without this gate a malformed `:repositorio` \
5741 (whitespace from a paste-from-doc; control characters / CRLF \
5742 from a paste-from-multiline-doc; a leading `-` from a \
5743 CLI-argument-injection footgun; a missing `:` separator from a \
5744 bare `org/repo` shape git treats as a relative filesystem path) \
5745 silently landed in the rendered `Chart.yaml home:` and the \
5746 FluxCD `GitRepository.spec.url` and broke at `git clone` / \
5747 FluxCD reconcile time far from the source caixa.lisp; the gate \
5748 moves the diagnostic to the manifest layer with the offending \
5749 value named verbatim)"
5750 )]
5751 RepositorioInvalid { repositorio: String, reason: String },
5752 #[error(
5753 ":descricao is the empty string (every published caixa names \
5754 its purpose via a non-empty `:descricao` summary — the value \
5755 flows verbatim into the rendered `lareira-<nome>` Helm \
5756 chart's `Chart.yaml` `description:` field via `caixa-helm`'s \
5757 `build_chart_yaml` and into the chart `README.md` header via \
5758 `build_readme`; both consumers' `Option::unwrap_or_else` \
5759 `caixa.nome`-derived fallbacks only fire when the slot is \
5760 `None`, so an empty `Some(\"\")` silently lands as \
5761 `description: \"\"` / a blank `README.md` header in the \
5762 rendered artifacts and breaks at `helm lint` time \
5763 (`WARNING [chart.metadata.description]: description is \
5764 required` on `apiVersion: v2` charts) far from the source \
5765 caixa.lisp; omit the slot entirely to defer to the \
5766 renderer's `\"Generated chart for caixa Servico <nome>\"` / \
5767 `\"caixa Servico <nome>\"` fallbacks, or carry a non-empty \
5768 summary like `\"Canonical Rust→wasm32-wasip2 caixa \
5769 Servico.\"`)"
5770 )]
5771 DescricaoEmpty,
5772 #[error(
5773 ":descricao {descricao:?} is not a valid chart-description shape: \
5774 {reason} (the substrate consumes this string through the shared \
5775 `crate::render::is_chart_description_shape` predicate — the same \
5776 single-line-UTF-8 floor every realistic chart description carries: \
5777 1..=512 bytes, no leading or trailing whitespace, no ASCII control \
5778 characters anywhere, Unicode prose bytes accepted. The canonical \
5779 authoring shapes are short single-line summaries like `\"Canonical \
5780 Rust→wasm32-wasip2 caixa Servico.\"`, `\"Checkout flow.\"`, \
5781 `\"AWS provider caixa for tatara-lisp\"`. Without this gate a \
5782 malformed `:descricao` (paste-from-aligned-doc leading whitespace \
5783 `\" Checkout flow.\"` / trailing whitespace `\"Checkout flow. \"`; \
5784 paste-from-multiline-doc newline `\"Checkout\\nflow.\"`; \
5785 paste-from-Windows-CRLF-doc carriage return `\"Checkout\\rflow.\"`; \
5786 tab-from-aligned-doc `\"Checkout\\tflow.\"`; paste-from-binary-blob \
5787 NUL / BEL / ESC / DEL byte) silently passed `from_lisp` + \
5788 `validate_descricao` + `StandardLayout::verify` and landed in the \
5789 rendered `lareira-<nome>` Helm chart's `Chart.yaml description:` \
5790 field + `README.md` header paragraph as a YAML-illegal multi-line \
5791 scalar or a silently-trimmed whitespace round-trip — every \
5792 chart-aware UI (`helm list`, `helm search`, Artifact Hub) would \
5793 render the description in a single-line column far from the source \
5794 caixa.lisp; the gate moves the diagnostic to the manifest layer \
5795 with the offending value named verbatim)"
5796 )]
5797 DescricaoInvalid { descricao: String, reason: String },
5798 #[error(
5799 ":licenca is the empty string (every published caixa names \
5800 its license via a non-empty `:licenca` SPDX expression — the \
5801 value flows verbatim into the rendered `lareira-<nome>` Helm \
5802 chart's `README.md` `## License` section via `caixa-helm`'s \
5803 `build_readme` at `caixa-helm/src/lib.rs:361`; the consumer's \
5804 `Option::unwrap_or_else(|| \"MIT\".into())` `MIT` fallback \
5805 only fires when the slot is `None`, so an empty `Some(\"\")` \
5806 silently lands as a bare trailing period in the rendered \
5807 chart `README.md` `License` section far from the source \
5808 caixa.lisp; omit the slot entirely to defer to the \
5809 renderer's `MIT` fallback, or carry a canonical SPDX \
5810 expression like `\"MIT\"`, `\"Apache-2.0\"`, \
5811 `\"Apache-2.0 OR MIT\"`)"
5812 )]
5813 LicencaEmpty,
5814 #[error(
5815 ":licenca {licenca:?} is not a valid SPDX expression shape: {reason} \
5816 (the substrate consumes this string through the shared \
5817 `crate::render::is_spdx_expression_shape` predicate — the same \
5818 alphabet-floor parser every peer per-axis value-shape gate routes \
5819 its value through; the canonical authoring shapes are single \
5820 license identifiers like `\"MIT\"`, `\"Apache-2.0\"`, `\"BSD-3-Clause\"`, \
5821 compound expressions like `\"Apache-2.0 OR MIT\"`, \
5822 `\"MIT AND BSD-3-Clause\"`, `\"(MIT OR Apache-2.0) AND ISC\"`, \
5823 license-with-exception forms like `\"Apache-2.0 WITH LLVM-exception\"`, \
5824 `+`-suffix variants like `\"GPL-2.0+\"`, and user-defined references \
5825 like `\"LicenseRef-MyLicense\"` / \
5826 `\"DocumentRef-doc:LicenseRef-MyLicense\"`. Without this gate a \
5827 malformed `:licenca` (paste-from-doc whitespace `\"MIT \"` / \
5828 `\" MIT\"`; paste-from-multiline-doc CRLF `\"MIT\\n\"`; \
5829 tab-from-aligned-doc `\"MIT\\tOR Apache-2.0\"`; non-ASCII byte from \
5830 a smart-quote paste; underscore-instead-of-hyphen typo \
5831 `\"Apache_2.0\"`; comma-instead-of-`OR`-keyword colloquial idiom \
5832 `\"MIT, Apache-2.0\"`; slash-dual-license colloquial idiom \
5833 `\"MIT/Apache-2.0\"`; semicolon-list-separator confusion \
5834 `\"MIT; Apache-2.0\"`) silently landed in the rendered chart \
5835 `README.md` `## License` section + a future SPDX-aware \
5836 `Chart.yaml license:` emitter would refuse the value at \
5837 `helm lint` time far from the source caixa.lisp; the gate moves \
5838 the diagnostic to the manifest layer with the offending value \
5839 named verbatim)"
5840 )]
5841 LicencaInvalid { licenca: String, reason: String },
5842 #[error(
5843 ":edicao is the empty string (every published caixa names \
5844 its language edition via a non-empty `:edicao` value — the \
5845 edition determines the tatara-lisp macro surface + \
5846 compatibility flags the substrate applies when building \
5847 the caixa; the canonical `Caixa::template` scaffold every \
5848 `feira init` emits carries `:edicao \"2026\"` verbatim and \
5849 every renderer-side fixture (`caixa-helm`, `caixa-flux`, \
5850 `caixa-mesh`) carries `edicao: Some(\"2026\".into())` by \
5851 construction, so an empty `Some(\"\")` silently lands as a \
5852 bare `(:edicao \"\")` line in the rendered `caixa.lisp` and \
5853 a future renderer-side consumer that folds it through \
5854 `Option::unwrap_or_else` will skip the fallback and pass the \
5855 empty edition through to the substrate's build-time edition \
5856 selector far from the source caixa.lisp; omit the slot \
5857 entirely to defer to the substrate's default edition, or \
5858 carry a canonical edition like `\"2026\"`)"
5859 )]
5860 EdicaoEmpty,
5861 #[error(
5862 ":edicao {edicao:?} is not a valid edition: {reason} (every \
5863 documented tatara-lisp edition is a 4-digit ASCII decimal \
5864 year — `\"2026\"` is the only edition currently minted; \
5865 future-introduced siblings will follow the same shape, peer \
5866 with Cargo's `[package] edition` grammar which every value \
5867 Cargo has ever accepted matches: `\"2015\"`, `\"2018\"`, \
5868 `\"2021\"`, `\"2024\"`. Without this gate the canonical \
5869 paste-from-doc footguns silently passed: a trailing space \
5870 (`\"2026 \"`) from a paste-from-doc, a CRLF (`\"2026\\n\"`) \
5871 from a paste-from-multiline-doc, a fullwidth-keyboard \
5872 look-alike (`\"2026\"`), a free-form non-year value \
5873 (`\"x\"`, `\"latest\"`, `\"nightly\"`), a leading non-digit \
5874 version-tag prefix (`\"v2026\"`, `\"e2026\"`), a \
5875 decimal-shaped pseudo-version (`\"2026.1\"`), or a \
5876 wrong-length numeric value (`\"26\"`, `\"202\"`, \
5877 `\"20260\"`) all landed as `(:edicao \"<garbage>\")` in the \
5878 rendered caixa.lisp and broke at the substrate's \
5879 build-time edition selector far from the source caixa.lisp; \
5880 omit the slot entirely to defer to the substrate's default \
5881 edition, or carry a canonical 4-digit ASCII decimal year \
5882 like `\"2026\"`)"
5883 )]
5884 EdicaoInvalid { edicao: String, reason: String },
5885}
5886
5887#[cfg(test)]
5888mod tests {
5889 use super::*;
5890
5891 #[test]
5892 fn template_round_trips() {
5893 let src = Caixa::template("demo");
5894 let c = Caixa::from_lisp(&src).expect("template must parse");
5895 assert_eq!(c.nome, "demo");
5896 assert_eq!(c.versao, "0.1.0");
5897 assert_eq!(c.kind, CaixaKind::Biblioteca);
5898 assert_eq!(c.bibliotecas, vec!["lib/demo.lisp".to_string()]);
5899 assert!(c.deps.is_empty());
5900 assert!(c.deps_dev.is_empty());
5901 }
5902
5903 #[test]
5904 fn caixa_universal_axis_scalar_accessor_pair_is_const_fn() {
5905 // Fail-before-pass-after pin on [`Caixa::nome`] +
5906 // [`Caixa::versao`]'s `const`-eval-surface posture. Each
5907 // accessor projects the top-level manifest's per-`:nome` /
5908 // per-`:versao` [`String`] storage through the `pub const fn`
5909 // [`String::as_str`] (const-stable since Rust 1.87, well within
5910 // the workspace MSRV) — any future accidental downgrade to
5911 // non-`const` fails the corresponding `<name>_via_const_fn`
5912 // wrapper at caixa-core build time with E0015 (`cannot call
5913 // non-const method`), strictly stronger than a runtime
5914 // `assert!`. Sibling of the peer per-M2/M3-slot `String → &str`
5915 // scalar-accessor family pins on the sibling `const`-eval-
5916 // surface passes ([`crate::CaixaVersion::as_str`] at the
5917 // typed-newtype wrapper, [`crate::aplicacao::Membro::nome`] /
5918 // [`crate::aplicacao::Membro::versao_requirement`] at the M3
5919 // membership axis, [`crate::aplicacao::Entrada::hostname`] /
5920 // [`crate::aplicacao::Entrada::destination`] at the M3 ingress
5921 // axis, [`crate::supervisor::ChildSpec::nome`] /
5922 // [`crate::supervisor::ChildSpec::versao_requirement`] at the
5923 // M2 supervisor-tree axis,
5924 // [`crate::upgrade::UpgradeFromEntry::prior_versao`] at the M2
5925 // upgrade axis, [`crate::dep::Dep::nome`] /
5926 // [`crate::dep::Dep::versao_requirement`] at the dep-graph
5927 // axis, and the per-`:contratos`
5928 // [`crate::aplicacao::WitContract::source`] /
5929 // [`crate::aplicacao::WitContract::destination`] /
5930 // [`crate::aplicacao::WitContract::world_ref`] trio the
5931 // sibling pin at 279823b already anchors).
5932 const fn nome_via_const_fn(c: &Caixa) -> &str {
5933 c.nome()
5934 }
5935 const fn versao_via_const_fn(c: &Caixa) -> &str {
5936 c.versao()
5937 }
5938 let src = Caixa::template("demo");
5939 let c = Caixa::from_lisp(&src).expect("template must parse");
5940 assert_eq!(nome_via_const_fn(&c), c.nome());
5941 assert_eq!(versao_via_const_fn(&c), c.versao());
5942 assert_eq!(c.nome(), "demo");
5943 assert_eq!(c.versao(), "0.1.0");
5944 }
5945
5946 #[test]
5947 fn register_populates_registry() {
5948 Caixa::register().expect("first register call in this test process must succeed");
5949 let kws = tatara_lisp::domain::registered_keywords();
5950 assert!(kws.contains(&"defcaixa"));
5951 }
5952
5953 #[test]
5954 fn to_lisp_round_trips() {
5955 let src = Caixa::template("demo");
5956 let c1 = Caixa::from_lisp(&src).unwrap();
5957 let emitted = c1.to_lisp();
5958 let c2 = Caixa::from_lisp(&emitted).expect("emitted lisp parses back");
5959 assert_eq!(c1, c2);
5960 }
5961
5962 // ── DialetoEstrangeiro carries a single typed axis ────────────────────
5963 //
5964 // The compounding pin: the variant stores only the typed
5965 // [`crate::dialeto::CaixaDialeto`], and every user-facing byte-string
5966 // (canonical keyword, description, consumer) routes through the enum's
5967 // own accessors at Display time. Prior to that closure the variant
5968 // carried each accessor's return value as a stored `&'static str`
5969 // snapshot alongside `dialeto`; a caller could construct the variant
5970 // with a snapshot that drifted from what `dialeto`'s accessors would
5971 // return, and every downstream user-facing projection would silently
5972 // disagree with the classification. Storing only the axis makes the
5973 // drift structurally impossible.
5974
5975 #[test]
5976 fn dialeto_estrangeiro_variant_carries_only_the_typed_dialeto_axis() {
5977 // Single-field construction is the whole compounding shape — a
5978 // future re-introduction of a snapshot field (a `palavra_canonica:
5979 // &'static str`, a stored `descricao:`, a stored `consumidor:`)
5980 // would re-open the drift surface and this construction would fail
5981 // to compile with "missing field" until every snapshot was seeded
5982 // at the call site again. The compile-time guarantee is the
5983 // invariant; the assertion below only witnesses that the
5984 // construction is well-formed after the closure.
5985 let err = LeituraError::DialetoEstrangeiro {
5986 dialeto: crate::dialeto::CaixaDialeto::Molde,
5987 };
5988 assert!(matches!(
5989 err,
5990 LeituraError::DialetoEstrangeiro {
5991 dialeto: crate::dialeto::CaixaDialeto::Molde,
5992 }
5993 ));
5994 }
5995
5996 #[test]
5997 fn dialeto_estrangeiro_display_routes_through_typed_dialeto_accessors() {
5998 // For every foreign-dialect classification the variant surfaces —
5999 // [`crate::dialeto::CaixaDialeto::Molde`] and
6000 // [`crate::dialeto::CaixaDialeto::MoldePosicional`], the two
6001 // variants [`Caixa::from_lisp`] raises this error for — the
6002 // rendered [`std::fmt::Display`] byte-string must interpolate each
6003 // typed accessor's return verbatim. A future re-introduction of a
6004 // stored `&'static str` snapshot alongside `dialeto` that Display
6005 // read instead of the accessor would fail this pin as soon as the
6006 // two disagreed; a future accessor rebrand (a per-dialect
6007 // consumer rename, a canonical-keyword shift once the substrate
6008 // migration named in [`crate::dialeto`] completes) reaches every
6009 // consumer through one typed dispatch and this pin verifies the
6010 // display path is one of them.
6011 for d in [
6012 crate::dialeto::CaixaDialeto::Molde,
6013 crate::dialeto::CaixaDialeto::MoldePosicional,
6014 ] {
6015 let rendered = LeituraError::DialetoEstrangeiro { dialeto: d }.to_string();
6016 assert!(
6017 rendered.contains(d.palavra_canonica()),
6018 "Display must interpolate `dialeto.palavra_canonica()` \
6019 verbatim — a stored snapshot would silently drift from \
6020 the typed accessor. dialect: {d}, rendered: {rendered:?}"
6021 );
6022 assert!(
6023 rendered.contains(d.descricao()),
6024 "Display must interpolate `dialeto.descricao()` verbatim. \
6025 dialect: {d}, rendered: {rendered:?}"
6026 );
6027 assert!(
6028 rendered.contains(d.consumidor()),
6029 "Display must interpolate `dialeto.consumidor()` verbatim. \
6030 dialect: {d}, rendered: {rendered:?}"
6031 );
6032 }
6033 }
6034
6035 #[test]
6036 fn from_lisp_rejects_molde_dialect_via_typed_variant() {
6037 // The end-to-end pin the compounding closure defends: a
6038 // Molde-dialect source lands as [`LeituraError::DialetoEstrangeiro`]
6039 // carrying [`crate::dialeto::CaixaDialeto::Molde`], and the
6040 // rendered Display byte-string names the Molde accessors'
6041 // returns verbatim. Any future path that constructed the variant
6042 // with a mismatched snapshot (a stored `palavra_canonica:
6043 // "defcaixa"` on a `Molde` classification) would land Display
6044 // pointing at `defcaixa` while the typed axis said `Molde` — the
6045 // exact drift the closure removes.
6046 let src = r#"
6047 (defcaixa
6048 :name "x"
6049 :kind :Biblioteca
6050 :ecosystem :rust-single-crate
6051 :package {:name "x" :version "0.1.0"})
6052 "#;
6053 let err = Caixa::from_lisp(src).expect_err("Molde dialect must not parse as Pacote");
6054 match err {
6055 LeituraError::DialetoEstrangeiro { dialeto } => {
6056 assert_eq!(dialeto, crate::dialeto::CaixaDialeto::Molde);
6057 let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
6058 assert!(rendered.contains(dialeto.palavra_canonica()));
6059 assert!(rendered.contains(dialeto.consumidor()));
6060 assert!(rendered.contains(dialeto.descricao()));
6061 }
6062 other => panic!("expected DialetoEstrangeiro, got {other:?}"),
6063 }
6064 }
6065
6066 #[test]
6067 fn from_lisp_rejects_molde_posicional_dialect_via_typed_variant() {
6068 // Coverage pin for the [`crate::dialeto::CaixaDialeto::MoldePosicional`]
6069 // arm of the [`Caixa::from_lisp`] foreign-dialect gate — the
6070 // positional-arity `defmolde` form written under a `(defcaixa …)`
6071 // head (`(defcaixa todoku-go :kind :Biblioteca :ecosystem :go
6072 // …)`). Pre-lift this arm rode the same `foreign =>` wildcard
6073 // the [`crate::dialeto::CaixaDialeto::Molde`] sibling arm rode,
6074 // so no test exercised the positional-arity path through
6075 // `Caixa::from_lisp` specifically; the sibling
6076 // [`from_lisp_rejects_molde_dialect_via_typed_variant`] only
6077 // covered [`crate::dialeto::CaixaDialeto::Molde`]. Post-lift the
6078 // two arms route through the lifted
6079 // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
6080 // typed predicate — the same predicate the pre-lift `foreign =>`
6081 // wildcard resolved to today — and this pin makes the
6082 // positional-arity arm's byte-shape at the gate explicit rather
6083 // than implied by wildcard-absorption. A future regression that
6084 // silently reordered [`crate::dialeto::CaixaDialeto::is_molde_family`]'s
6085 // arm-set (dropped [`crate::dialeto::CaixaDialeto::MoldePosicional`]
6086 // from the two-arity closure) would fail this pin at caixa-core
6087 // test time rather than surfacing far from the change as a
6088 // `caixa.lisp` carrying a `(defcaixa todoku-go :ecosystem :go
6089 // …)` silently parsing past the derive.
6090 let src = r#"
6091 (defcaixa todoku-go
6092 :kind :Biblioteca
6093 :ecosystem :go
6094 :package {:name "todoku-go" :version "0.3.0"})
6095 "#;
6096 let err =
6097 Caixa::from_lisp(src).expect_err("MoldePosicional dialect must not parse as Pacote");
6098 match err {
6099 LeituraError::DialetoEstrangeiro { dialeto } => {
6100 assert_eq!(
6101 dialeto,
6102 crate::dialeto::CaixaDialeto::MoldePosicional,
6103 "DialetoEstrangeiro must carry the MoldePosicional \
6104 variant verbatim — the positional-arity `defmolde` \
6105 form under a `(defcaixa …)` head is the \
6106 `MoldePosicional` arm's canonical byte-shape"
6107 );
6108 let rendered = LeituraError::DialetoEstrangeiro { dialeto }.to_string();
6109 assert!(
6110 rendered.contains(dialeto.palavra_canonica()),
6111 "Display must interpolate `dialeto.palavra_canonica()` \
6112 verbatim on the MoldePosicional arm; rendered: \
6113 {rendered:?}"
6114 );
6115 assert!(
6116 rendered.contains(dialeto.consumidor()),
6117 "Display must interpolate `dialeto.consumidor()` \
6118 verbatim on the MoldePosicional arm; rendered: \
6119 {rendered:?}"
6120 );
6121 assert!(
6122 rendered.contains(dialeto.descricao()),
6123 "Display must interpolate `dialeto.descricao()` \
6124 verbatim on the MoldePosicional arm; rendered: \
6125 {rendered:?}"
6126 );
6127 }
6128 other => panic!("expected DialetoEstrangeiro, got {other:?}"),
6129 }
6130 }
6131
6132 #[test]
6133 fn from_lisp_dialect_gate_dispatches_through_caixa_dialeto_is_molde_family_predicate() {
6134 // Load-bearing byte-parity pin: for every arm in
6135 // [`crate::dialeto::CaixaDialeto::ALL`], the
6136 // [`Caixa::from_lisp`] foreign-dialect gate's DialetoEstrangeiro
6137 // partition must agree with the lifted
6138 // [`crate::dialeto::CaixaDialeto::is_molde_family`] (e9d2315)
6139 // typed predicate — i.e. from_lisp raises
6140 // [`LeituraError::DialetoEstrangeiro`] carrying `d` iff
6141 // `d.is_molde_family()` returns `true`, and does NOT raise
6142 // [`LeituraError::DialetoEstrangeiro`] on any arm where the
6143 // predicate returns `false` (the arm's source falls through to
6144 // the derive — parses cleanly on
6145 // [`crate::dialeto::CaixaDialeto::Pacote`], surfaces a
6146 // [`LeituraError::Leitura`] on
6147 // [`crate::dialeto::CaixaDialeto::Desconhecido`]).
6148 //
6149 // Pre-lift the gate hand-rolled a three-arm match
6150 // (`Pacote => {}`, `Desconhecido => {}`, `foreign => Err(…)`)
6151 // whose `foreign =>` wildcard expressed no compile-time link
6152 // back to the substrate primitive's arm-family; a future fifth
6153 // dialect the [`crate::dialeto`] module doc's "third dialect"
6154 // hazard actualises would fall silently onto the wildcard
6155 // regardless of whether it belonged to the `defmolde` family or
6156 // to a distinct `defcaixa`-family. Post-lift the partition
6157 // resolves through
6158 // [`crate::dialeto::CaixaDialeto::is_molde_family`]'s single
6159 // typed dispatch, and this pin refuses any future regression
6160 // that silently split the from_lisp partition from the typed
6161 // predicate — the two paths now migrate as one on any future
6162 // arm addition.
6163 //
6164 // Sibling in shape to the peer
6165 // [`crate::dialeto::tests::caixa_dialeto_is_molde_family_agrees_with_palavra_canonica_defmolde_projection`]
6166 // (e9d2315) that pins the same byte-parity between
6167 // [`crate::dialeto::CaixaDialeto::is_molde_family`] and the
6168 // sibling [`crate::dialeto::CaixaDialeto::palavra_canonica`]
6169 // `== "defmolde"` classifier — extends the discipline from the
6170 // two paths within the [`crate::dialeto`] primitive onto the
6171 // third external consumer of the `defmolde`-family partition
6172 // (the [`Caixa::from_lisp`] gate that raises
6173 // [`LeituraError::DialetoEstrangeiro`]).
6174 let fixtures: &[(crate::dialeto::CaixaDialeto, &str)] = &[
6175 (
6176 crate::dialeto::CaixaDialeto::Pacote,
6177 r#"
6178 (defcaixa
6179 :nome "checkout"
6180 :versao "0.1.0"
6181 :kind Biblioteca
6182 :edicao "2026"
6183 :descricao "canonical Pacote source"
6184 :autores ()
6185 :etiquetas ()
6186 :deps ()
6187 :deps-dev ()
6188 :bibliotecas ("lib/checkout.lisp"))
6189 "#,
6190 ),
6191 (
6192 crate::dialeto::CaixaDialeto::Molde,
6193 r#"
6194 (defcaixa
6195 :name "base64"
6196 :kind :Biblioteca
6197 :ecosystem :rust-single-crate
6198 :package {:name "base64" :version "0.22.1"}
6199 :workflows [:auto-release])
6200 "#,
6201 ),
6202 (
6203 crate::dialeto::CaixaDialeto::MoldePosicional,
6204 r#"
6205 (defcaixa todoku-go
6206 :kind :Biblioteca
6207 :ecosystem :go
6208 :package {:name "todoku-go" :version "0.3.0"})
6209 "#,
6210 ),
6211 (
6212 crate::dialeto::CaixaDialeto::Desconhecido,
6213 r#"(defcaixa :licenca "MIT")"#,
6214 ),
6215 ];
6216
6217 // Coverage: every arm in [`crate::dialeto::CaixaDialeto::ALL`]
6218 // must appear in the fixture table so the pin's arm-set stays
6219 // synchronised with the enum's arm-set. Fails at test time if a
6220 // future fifth arm added to [`crate::dialeto::CaixaDialeto`]
6221 // (with a corresponding `is_molde_family` return) forgot to
6222 // extend this fixture table with a canonical source for the new
6223 // arm — the pin cannot cover an arm it has no source for.
6224 for &expected in crate::dialeto::CaixaDialeto::ALL {
6225 assert!(
6226 fixtures.iter().any(|(d, _)| *d == expected),
6227 "fixture table must carry a canonical source for every \
6228 CaixaDialeto arm; missing: {expected:?}"
6229 );
6230 }
6231
6232 for &(expected_dialect, src) in fixtures {
6233 let classified = crate::dialeto::classify(src.trim()).unwrap_or_else(|err| {
6234 panic!(
6235 "fixture source for {expected_dialect:?} must classify \
6236 cleanly, got err: {err:?}"
6237 )
6238 });
6239 assert_eq!(
6240 classified, expected_dialect,
6241 "fixture source for {expected_dialect:?} must classify as \
6242 {expected_dialect:?} (drift here defeats the byte-parity \
6243 pin below — a source labelled for one arm but classifying \
6244 as another would silently satisfy or violate the pin for \
6245 the wrong reason)"
6246 );
6247
6248 let outcome = Caixa::from_lisp(src);
6249 match (expected_dialect.is_molde_family(), &outcome) {
6250 (true, Err(LeituraError::DialetoEstrangeiro { dialeto })) => {
6251 assert_eq!(
6252 *dialeto, expected_dialect,
6253 "DialetoEstrangeiro must carry the same typed arm \
6254 the classifier returned — a drift here would let \
6255 from_lisp raise the error while pointing at the \
6256 wrong dialect (e.g. rejecting a \
6257 MoldePosicional source as Molde). arm: \
6258 {expected_dialect:?}"
6259 );
6260 }
6261 (true, other) => panic!(
6262 "arm {expected_dialect:?} has is_molde_family() = true \
6263 so from_lisp must raise DialetoEstrangeiro carrying \
6264 {expected_dialect:?}; got: {other:?}"
6265 ),
6266 (false, Err(LeituraError::DialetoEstrangeiro { dialeto })) => panic!(
6267 "arm {expected_dialect:?} has is_molde_family() = false \
6268 so from_lisp must NOT raise DialetoEstrangeiro; got \
6269 one carrying: {dialeto:?}. This means the typed \
6270 predicate and the from_lisp partition disagree on \
6271 this arm — exactly the drift this pin refuses."
6272 ),
6273 (false, _) => {
6274 // A non-molde arm's source falls through to the
6275 // derive: Pacote sources parse to Ok(_); Desconhecido
6276 // sources surface as LeituraError::Leitura from the
6277 // derive's own unknown-keyword rejection. Either
6278 // shape is acceptable here — the pin's promise is
6279 // narrower: "no DialetoEstrangeiro on
6280 // is_molde_family() == false".
6281 }
6282 }
6283 }
6284 }
6285
6286 // ── M2 typed-substrate slot tests (limits, behavior, upgrade-from, supervisor) ──
6287
6288 #[test]
6289 fn limits_round_trip_via_json() {
6290 use crate::LimitsSpec;
6291 use std::time::Duration;
6292 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6293 c.limits = Some(LimitsSpec {
6294 memory: Some(64 * 1024 * 1024),
6295 fuel: Some(1_000_000),
6296 wall_clock: Some(Duration::from_secs(30)),
6297 cpu: Some(500),
6298 });
6299 let json = serde_json::to_string(&c).unwrap();
6300 assert!(json.contains("\"limits\""));
6301 assert!(json.contains("\"64MiB\""));
6302 assert!(json.contains("\"30s\""));
6303 assert!(json.contains("\"500m\""));
6304 let back: Caixa = serde_json::from_str(&json).unwrap();
6305 assert_eq!(c.limits, back.limits);
6306 }
6307
6308 #[test]
6309 fn behavior_round_trip_via_json() {
6310 use crate::BehaviorSpec;
6311 use std::path::PathBuf;
6312 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6313 c.behavior = Some(BehaviorSpec {
6314 on_init: Some(PathBuf::from("lib/init.lisp")),
6315 on_call: Some(PathBuf::from("lib/handlers.lisp")),
6316 ..Default::default()
6317 });
6318 let json = serde_json::to_string(&c).unwrap();
6319 let back: Caixa = serde_json::from_str(&json).unwrap();
6320 assert_eq!(c.behavior, back.behavior);
6321 }
6322
6323 #[test]
6324 fn upgrade_from_round_trip_via_json() {
6325 use crate::{UpgradeFromEntry, UpgradeInstruction};
6326 use std::path::PathBuf;
6327 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6328 c.upgrade_from = vec![UpgradeFromEntry {
6329 from: "0.1.0".into(),
6330 instructions: vec![
6331 UpgradeInstruction::LoadModule {
6332 module: "demo".into(),
6333 },
6334 UpgradeInstruction::StateChange {
6335 script: PathBuf::from("lib/migrations/v01-to-v02.lisp"),
6336 },
6337 UpgradeInstruction::SoftPurge {
6338 module: "demo-old".into(),
6339 },
6340 ],
6341 }];
6342 let json = serde_json::to_string(&c).unwrap();
6343 let back: Caixa = serde_json::from_str(&json).unwrap();
6344 assert_eq!(c.upgrade_from, back.upgrade_from);
6345 }
6346
6347 #[test]
6348 fn supervisor_view_returns_typed_shape() {
6349 use crate::{ChildSpec, RestartPolicy, RestartStrategy};
6350 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
6351 c.kind = CaixaKind::Supervisor;
6352 c.bibliotecas.clear();
6353 c.estrategia = Some(RestartStrategy::OneForOne);
6354 c.max_restarts = Some(5);
6355 c.restart_window = Some("60s".into());
6356 c.children = vec![ChildSpec {
6357 caixa: "worker".into(),
6358 versao: "^0.1".into(),
6359 restart: RestartPolicy::Permanent,
6360 }];
6361 let view = c.supervisor_view().expect("Supervisor kind has a view");
6362 assert_eq!(view.estrategia, RestartStrategy::OneForOne);
6363 assert_eq!(view.max_restarts, 5);
6364 assert_eq!(
6365 view.restart_window,
6366 Some(std::time::Duration::from_secs(60))
6367 );
6368 assert_eq!(view.children.len(), 1);
6369 view.validate().unwrap();
6370 }
6371
6372 #[test]
6373 fn supervisor_view_none_for_non_supervisor_kinds() {
6374 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6375 assert!(c.supervisor_view().is_none());
6376 }
6377
6378 #[test]
6379 fn declared_mesh_slots_empty_for_bare_caixa() {
6380 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6381 assert!(c.declared_mesh_slots().is_empty());
6382 }
6383
6384 #[test]
6385 fn declared_mesh_slots_reports_only_set_slots_in_canonical_order() {
6386 use crate::{Entrada, Membro};
6387 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6388 // Set a non-adjacent pair (:membros + :entrada) to pin that the
6389 // canonical declaration order is preserved regardless of which
6390 // subset is populated.
6391 c.membros = vec![Membro {
6392 caixa: "a".into(),
6393 versao: "^0.1".into(),
6394 }];
6395 c.entrada = Some(Entrada {
6396 host: "x.example.com".into(),
6397 para: "a".into(),
6398 paths: vec![],
6399 port: 8080,
6400 });
6401 assert_eq!(
6402 c.declared_mesh_slots(),
6403 vec![
6404 crate::render::M3_AUTHOR_KEY_MEMBROS,
6405 crate::render::M3_AUTHOR_KEY_ENTRADA,
6406 ]
6407 );
6408 }
6409
6410 #[test]
6411 fn m3_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
6412 // Scalar-value pin: the five author-facing kebab-case labels the
6413 // `(defcaixa … :<slot> (…))` surface admits on the M3 top-level
6414 // mesh slot axis, one arm per typed slot. Mirrors the peer
6415 // scalar-value pin the sibling
6416 // [`crate::M2_AUTHOR_KEY_LIMITS`] /
6417 // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
6418 // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] M2 top-level slot consts
6419 // carry (f49c8b0), so both altitudes of the typed-slot algebra
6420 // (per-Servico M2 + per-Aplicacao M3) share the same
6421 // "one canonical byte-string per arm" discipline. A future
6422 // rebrand (`:membros` → `:members`, `:contratos` → `:contracts`,
6423 // `:politicas` → `:policies`, `:placement` → `:distribution`,
6424 // `:entrada` → `:ingress`) lands as an edit to exactly one const,
6425 // and every consumer that reaches for the label picks it up at
6426 // build time rather than at runtime as a downstream mismatch.
6427 assert_eq!(crate::render::M3_AUTHOR_KEY_MEMBROS, ":membros");
6428 assert_eq!(crate::render::M3_AUTHOR_KEY_CONTRATOS, ":contratos");
6429 assert_eq!(crate::render::M3_AUTHOR_KEY_POLITICAS, ":politicas");
6430 assert_eq!(crate::render::M3_AUTHOR_KEY_PLACEMENT, ":placement");
6431 assert_eq!(crate::render::M3_AUTHOR_KEY_ENTRADA, ":entrada");
6432 }
6433
6434 #[test]
6435 fn declared_mesh_slots_route_through_lifted_m3_author_key_consts() {
6436 // Production-through-const pin: the five per-arm labels the
6437 // [`Caixa::declared_mesh_slots`] tagger pushes onto its return
6438 // `Vec` route through the lifted
6439 // [`crate::M3_AUTHOR_KEY_MEMBROS`] /
6440 // [`crate::M3_AUTHOR_KEY_CONTRATOS`] /
6441 // [`crate::M3_AUTHOR_KEY_POLITICAS`] /
6442 // [`crate::M3_AUTHOR_KEY_PLACEMENT`] /
6443 // [`crate::M3_AUTHOR_KEY_ENTRADA`] consts, in canonical
6444 // declaration order. A future re-order or drift at the tagger
6445 // (a rename that reaches the tagger but not the const, or vice
6446 // versa) surfaces here at build time rather than at runtime as
6447 // a [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
6448 // `slots: <stale-kebab-case>` diagnostic far from the rename's
6449 // commit. Mirror of the peer
6450 // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
6451 // pin (f49c8b0) on the sibling per-Servico M2 top-level slot
6452 // axis.
6453 use crate::{Entrada, Membro, MeshPolicy, Placement, PlacementStrategy, WitContract};
6454 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6455 c.membros = vec![Membro {
6456 caixa: "a".into(),
6457 versao: "^0.1".into(),
6458 }];
6459 c.contratos = vec![WitContract {
6460 de: "a".into(),
6461 para: "a".into(),
6462 wit: "wasi:http/proxy".into(),
6463 endpoint: Some("/x".into()),
6464 subject: None,
6465 slot: None,
6466 }];
6467 c.politicas = Some(MeshPolicy::default());
6468 c.placement = Some(Placement {
6469 estrategia: PlacementStrategy::Replicated,
6470 clusters: vec!["rio".into()],
6471 affinity: None,
6472 shard_key: None,
6473 });
6474 c.entrada = Some(Entrada {
6475 host: "x.example.com".into(),
6476 para: "a".into(),
6477 paths: vec![],
6478 port: 8080,
6479 });
6480 assert_eq!(
6481 c.declared_mesh_slots(),
6482 vec![
6483 crate::render::M3_AUTHOR_KEY_MEMBROS,
6484 crate::render::M3_AUTHOR_KEY_CONTRATOS,
6485 crate::render::M3_AUTHOR_KEY_POLITICAS,
6486 crate::render::M3_AUTHOR_KEY_PLACEMENT,
6487 crate::render::M3_AUTHOR_KEY_ENTRADA,
6488 ]
6489 );
6490 }
6491
6492 #[test]
6493 fn declared_supervisor_slots_empty_for_bare_caixa() {
6494 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6495 assert!(c.declared_supervisor_slots().is_empty());
6496 }
6497
6498 #[test]
6499 fn declared_supervisor_slots_reports_only_set_slots_in_canonical_order() {
6500 use crate::RestartStrategy;
6501 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6502 // Set a non-adjacent pair (:estrategia + :restart-window) to pin
6503 // that the canonical declaration order is preserved regardless
6504 // of which subset is populated.
6505 c.estrategia = Some(RestartStrategy::OneForOne);
6506 c.restart_window = Some("60s".into());
6507 assert_eq!(
6508 c.declared_supervisor_slots(),
6509 vec![
6510 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
6511 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
6512 ]
6513 );
6514 }
6515
6516 #[test]
6517 fn supervisor_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
6518 // Scalar-value pin: the four author-facing kebab-case labels the
6519 // `(defcaixa … :<slot> (…))` surface admits on the Supervisor
6520 // supervision-tree slot axis, one arm per typed slot. Mirrors the
6521 // peer scalar-value pins the sibling
6522 // [`crate::render::M2_AUTHOR_KEY_LIMITS`] /
6523 // [`crate::render::M2_AUTHOR_KEY_BEHAVIOR`] /
6524 // [`crate::render::M2_AUTHOR_KEY_UPGRADE_FROM`] top-level M2 slot
6525 // consts and [`crate::render::M3_AUTHOR_KEY_MEMBROS`] etc.
6526 // top-level M3 slot consts carry, so all three kind-scoped
6527 // typed-slot-family author-facing-label axes route through one
6528 // canonical per-arm declaration. A future rebrand
6529 // (`:estrategia` → `:strategy` for English uniformity,
6530 // `:max-restarts` → `:max-intensity` matching Erlang/OTP's
6531 // `MaxIntensity` name, `:restart-window` → `:period` matching
6532 // OTP's `Period` name, `:children` → `:workers` matching Elixir
6533 // idiom) lands as an edit to exactly one const, and every
6534 // consumer that reaches for the label picks it up at build time
6535 // rather than at runtime as a downstream mismatch.
6536 assert_eq!(
6537 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
6538 ":estrategia"
6539 );
6540 assert_eq!(
6541 crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
6542 ":max-restarts"
6543 );
6544 assert_eq!(
6545 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
6546 ":restart-window"
6547 );
6548 assert_eq!(crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN, ":children");
6549 }
6550
6551 #[test]
6552 fn declared_supervisor_slots_route_through_lifted_supervisor_author_key_consts() {
6553 // Production-through-const pin: the four per-arm labels the
6554 // [`Caixa::declared_supervisor_slots`] tagger pushes onto its
6555 // return `Vec` route through the lifted
6556 // [`crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA`] /
6557 // [`crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS`] /
6558 // [`crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW`] /
6559 // [`crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN`] consts, in
6560 // canonical declaration order. A future re-order or drift at the
6561 // tagger (a rename that reaches the tagger but not the const, or
6562 // vice versa) surfaces here at build time rather than at runtime
6563 // as a [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
6564 // `slots: <stale-kebab-case>` diagnostic far from the rename's
6565 // commit. Mirror of the peer
6566 // [`declared_servico_slots_route_through_lifted_m2_author_key_consts`]
6567 // (f49c8b0) and
6568 // [`declared_mesh_slots_route_through_lifted_m3_author_key_consts`]
6569 // (882f498) pins on the sibling M2 / M3 top-level slot axes.
6570 use crate::{ChildSpec, RestartPolicy, RestartStrategy};
6571 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6572 c.estrategia = Some(RestartStrategy::OneForOne);
6573 c.max_restarts = Some(5);
6574 c.restart_window = Some("60s".into());
6575 c.children = vec![ChildSpec {
6576 caixa: "worker".into(),
6577 versao: "^0.1".into(),
6578 restart: RestartPolicy::Permanent,
6579 }];
6580 assert_eq!(
6581 c.declared_supervisor_slots(),
6582 vec![
6583 crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA,
6584 crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS,
6585 crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW,
6586 crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN,
6587 ]
6588 );
6589 }
6590
6591 #[test]
6592 fn declared_servico_slots_empty_for_bare_caixa() {
6593 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6594 assert!(c.declared_servico_slots().is_empty());
6595 }
6596
6597 #[test]
6598 fn declared_servico_slots_reports_only_set_slots_in_canonical_order() {
6599 use crate::{UpgradeFromEntry, UpgradeInstruction};
6600 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6601 // Set a non-adjacent pair (:limits + :upgrade-from) to pin that
6602 // the canonical declaration order is preserved regardless of
6603 // which subset is populated.
6604 c.limits = Some(crate::LimitsSpec {
6605 fuel: Some(1_000_000),
6606 ..Default::default()
6607 });
6608 c.upgrade_from = vec![UpgradeFromEntry {
6609 from: "0.1.0".into(),
6610 instructions: vec![UpgradeInstruction::Restart],
6611 }];
6612 assert_eq!(
6613 c.declared_servico_slots(),
6614 vec![
6615 crate::render::M2_AUTHOR_KEY_LIMITS,
6616 crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
6617 ]
6618 );
6619 }
6620
6621 #[test]
6622 fn m2_top_level_author_key_consts_pin_canonical_kebab_case_labels() {
6623 // Scalar-value pin: the three author-facing kebab-case labels
6624 // the `(defcaixa … :<slot> (…))` surface admits on the M2
6625 // top-level slot axis, one arm per typed slot. Mirrors the peer
6626 // scalar-value pin the sibling renderer-side
6627 // [`crate::M2_KEY_LIMITS`] / [`crate::M2_KEY_BEHAVIOR`] /
6628 // [`crate::M2_KEY_UPGRADE_FROM`] camelCase overlay-container
6629 // consts carry, so both halves of the M2 top-level slot dual
6630 // axis (author-facing kebab-case label + renderer-side
6631 // camelCase overlay-container wire key) route through one
6632 // canonical per-arm declaration. A future rebrand
6633 // (`:limits` → `:sandbox` matching Lunatic per-process
6634 // terminology INSPIRATIONS §III.1, `:behavior` → `:gen-server`
6635 // matching Erlang's verbatim name, `:upgrade-from` → `:appup`
6636 // matching Erlang's verbatim appup name) lands as an edit to
6637 // exactly one const, and every consumer that reaches for the
6638 // label picks it up at build time rather than at runtime as a
6639 // downstream mismatch.
6640 assert_eq!(crate::render::M2_AUTHOR_KEY_LIMITS, ":limits");
6641 assert_eq!(crate::render::M2_AUTHOR_KEY_BEHAVIOR, ":behavior");
6642 assert_eq!(crate::render::M2_AUTHOR_KEY_UPGRADE_FROM, ":upgrade-from");
6643 }
6644
6645 #[test]
6646 fn declared_servico_slots_route_through_lifted_m2_author_key_consts() {
6647 // Production-through-const pin: the three per-arm labels the
6648 // [`Caixa::declared_servico_slots`] tagger pushes onto its
6649 // return `Vec` route through the lifted
6650 // [`crate::M2_AUTHOR_KEY_LIMITS`] /
6651 // [`crate::M2_AUTHOR_KEY_BEHAVIOR`] /
6652 // [`crate::M2_AUTHOR_KEY_UPGRADE_FROM`] consts, in canonical
6653 // declaration order. A future re-order or drift at the tagger
6654 // (a rename that reaches the tagger but not the const, or vice
6655 // versa) surfaces here at build time rather than at runtime as
6656 // a [`crate::LayoutError::ServicoSlotsOnNonServico`]
6657 // `slots: <stale-kebab-case>` diagnostic far from the rename's
6658 // commit. Mirror of the peer
6659 // [`crate::behavior::BehaviorSpec::declared_slots`] production
6660 // tagger pin (889dc18) on the sibling per-callback axis.
6661 use crate::{BehaviorSpec, UpgradeFromEntry, UpgradeInstruction};
6662 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6663 c.limits = Some(crate::LimitsSpec {
6664 fuel: Some(1_000_000),
6665 ..Default::default()
6666 });
6667 c.behavior = Some(BehaviorSpec {
6668 on_init: Some(PathBuf::from("lib/init.lisp")),
6669 ..Default::default()
6670 });
6671 c.upgrade_from = vec![UpgradeFromEntry {
6672 from: "0.1.0".into(),
6673 instructions: vec![UpgradeInstruction::Restart],
6674 }];
6675 assert_eq!(
6676 c.declared_servico_slots(),
6677 vec![
6678 crate::render::M2_AUTHOR_KEY_LIMITS,
6679 crate::render::M2_AUTHOR_KEY_BEHAVIOR,
6680 crate::render::M2_AUTHOR_KEY_UPGRADE_FROM,
6681 ]
6682 );
6683 }
6684
6685 #[test]
6686 fn existing_manifests_unaffected_by_new_optional_slots() {
6687 // Regression test: a caixa.lisp authored before M2 typed slots
6688 // should still parse + serialize cleanly. The bare `defcaixa`
6689 // emitted by `Caixa::template` has none of the new fields.
6690 let src = Caixa::template("legacy");
6691 let c = Caixa::from_lisp(&src).unwrap();
6692 assert!(c.limits.is_none());
6693 assert!(c.behavior.is_none());
6694 assert!(c.upgrade_from.is_empty());
6695 assert!(c.estrategia.is_none());
6696 assert!(c.children.is_empty());
6697
6698 // And to_lisp emits a manifest with the new slots in the
6699 // empty/default state — round-trippable.
6700 let emitted = c.to_lisp();
6701 let back = Caixa::from_lisp(&emitted).unwrap();
6702 assert_eq!(c, back);
6703 }
6704
6705 #[test]
6706 fn validate_deps_accepts_canonical_caixa() {
6707 // Positive control: the bare template — zero deps, zero
6708 // deps_dev — passes the gate trivially. A future axis added to
6709 // `Dep::validate` mustn't regress an empty-deps caixa to a
6710 // build error.
6711 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6712 c.validate_deps().unwrap();
6713 }
6714
6715 #[test]
6716 fn validate_deps_rejects_invalid_versao_in_deps() {
6717 // Fail-before-pass-after pin: a malformed `:deps :versao`
6718 // surfaces at validate_deps() time, not at lacre-resolve time.
6719 // Mirrors `rejects_invalid_membro_versao_requirement` and
6720 // `validate_rejects_invalid_child_versao_requirement` on the
6721 // other two `:versao` axes.
6722 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6723 c.deps = vec![Dep::simple("caixa-teia", "^bad-version")];
6724 let err = c.validate_deps().unwrap_err();
6725 assert!(
6726 matches!(
6727 err,
6728 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
6729 if nome == "caixa-teia" && versao == "^bad-version"
6730 ),
6731 "got {err:?}"
6732 );
6733 }
6734
6735 #[test]
6736 fn validate_deps_rejects_invalid_versao_in_deps_dev() {
6737 // Parity pin: `:deps-dev` must run through the same per-entry
6738 // validator as `:deps` — a typo in either axis surfaces the
6739 // same diagnostic. Without this leg, `:deps-dev` would be a
6740 // second-class citizen of the typed surface and an author
6741 // could land a build that passes validate_deps but fails at
6742 // `feira lock`-time when the dev-dep is resolved for a test
6743 // build.
6744 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6745 c.deps_dev = vec![Dep::simple("tatara-check", "^^0.1")];
6746 let err = c.validate_deps().unwrap_err();
6747 assert!(
6748 matches!(
6749 err,
6750 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
6751 if nome == "tatara-check" && versao == "^^0.1"
6752 ),
6753 "got {err:?}"
6754 );
6755 }
6756
6757 #[test]
6758 fn validate_deps_runs_deps_before_deps_dev() {
6759 // Order pin: when both lists carry typos, the `:deps`
6760 // diagnostic surfaces first. The author's mental model is
6761 // "runtime deps are load-bearing; dev deps are scaffolding";
6762 // surfacing the runtime axis first matches that hierarchy.
6763 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6764 c.deps = vec![Dep::simple("runtime-dep", "^bad-runtime")];
6765 c.deps_dev = vec![Dep::simple("dev-dep", "^bad-dev")];
6766 let err = c.validate_deps().unwrap_err();
6767 assert!(
6768 matches!(
6769 err,
6770 crate::dep::DepError::VersaoInvalid { ref nome, .. }
6771 if nome == "runtime-dep"
6772 ),
6773 "expected `:deps` typo to surface first, got {err:?}"
6774 );
6775 }
6776
6777 #[test]
6778 fn validate_deps_accepts_canonical_versao_forms_in_both_lists() {
6779 // Positive control sweep across both lists. Pin every
6780 // canonical Cargo-shaped form so a future tightening of the
6781 // accepted set surfaces here as a test failure (parity with
6782 // `accepts_canonical_membro_versao_forms` and
6783 // `validate_accepts_canonical_child_versao_forms`).
6784 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6785 c.deps = vec![
6786 Dep::simple("caret", "^0.1"),
6787 Dep::simple("tilde", "~0.1.2"),
6788 Dep::simple("exact", "0.1.0"),
6789 Dep::simple("wildcard", "*"),
6790 Dep::simple("multi-range", ">=0.1, <2"),
6791 ];
6792 c.deps_dev = vec![
6793 Dep::simple("dev-caret", "^0.1"),
6794 Dep::simple("dev-wildcard", "*"),
6795 ];
6796 c.validate_deps().unwrap();
6797 }
6798
6799 #[test]
6800 fn validate_deps_diagnostic_carries_offending_dep() {
6801 // Diagnostic-shape pin: the error names the offending entry's
6802 // `:nome` + `:versao` verbatim and carries a non-empty
6803 // `reason` from `semver::VersionReq::parse`, so a `feira lint`
6804 // run can render the diagnostic without re-parsing.
6805 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6806 c.deps = vec![Dep::simple("caixa-teia", "not-a-req")];
6807 let err = c.validate_deps().unwrap_err();
6808 let crate::dep::DepError::VersaoInvalid {
6809 nome,
6810 versao,
6811 reason,
6812 } = err
6813 else {
6814 panic!("expected VersaoInvalid, got other variant");
6815 };
6816 assert_eq!(nome, "caixa-teia");
6817 assert_eq!(versao, "not-a-req");
6818 assert!(
6819 !reason.is_empty(),
6820 "VersaoInvalid `reason` must carry the parser's wording verbatim"
6821 );
6822 }
6823
6824 #[test]
6825 fn validate_deps_rejects_ambiguous_fonte_in_deps_dev() {
6826 // Cross-axis pin: `validate_deps` walks both :deps and
6827 // :deps-dev through `Dep::validate`, and the new fonte gate
6828 // (`:tag` + `:branch` both set — the canonical "pin drift"
6829 // footgun) must surface from the :deps-dev arm with the
6830 // offending entry's :nome named. Pin the :deps-dev arm
6831 // explicitly so a future shortcut that only walks :deps
6832 // surfaces here as a regression.
6833 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6834 c.deps_dev = vec![Dep {
6835 nome: "dev-only".into(),
6836 versao: "^0.1".into(),
6837 fonte: Some(crate::DepSource::Git {
6838 repo: "github:p/x".into(),
6839 tag: Some("v1".into()),
6840 rev: None,
6841 branch: Some("main".into()),
6842 }),
6843 opcional: false,
6844 caracteristicas: vec![],
6845 }];
6846 let err = c.validate_deps().unwrap_err();
6847 let crate::dep::DepError::FontePinAmbiguous { nome, pins } = err else {
6848 panic!("expected FontePinAmbiguous from :deps-dev walk");
6849 };
6850 assert_eq!(nome, "dev-only");
6851 assert!(pins.contains(":tag") && pins.contains(":branch"));
6852 }
6853
6854 #[test]
6855 fn validate_deps_rejects_empty_repo_in_deps() {
6856 // Parity pin on the :deps arm: an empty :repo on the runtime
6857 // deps list surfaces the same FonteRepoEmpty diagnostic the
6858 // dep.rs per-entry tests pin, naming the offending entry.
6859 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6860 c.deps = vec![Dep {
6861 nome: "runtime".into(),
6862 versao: "^0.1".into(),
6863 fonte: Some(crate::DepSource::Git {
6864 repo: String::new(),
6865 tag: Some("v1".into()),
6866 rev: None,
6867 branch: None,
6868 }),
6869 opcional: false,
6870 caracteristicas: vec![],
6871 }];
6872 let err = c.validate_deps().unwrap_err();
6873 assert!(
6874 matches!(
6875 err,
6876 crate::dep::DepError::FonteRepoEmpty { ref nome }
6877 if nome == "runtime"
6878 ),
6879 "got {err:?}"
6880 );
6881 }
6882
6883 // ── validate_deps: within-list :nome set-not-multiset gate ─────────
6884
6885 #[test]
6886 fn validate_deps_rejects_duplicate_nome_in_deps() {
6887 // Fail-before-pass-after pin: two `:deps` entries naming the same
6888 // caixa carry two `:versao` / `:fonte` / feature triples that the
6889 // caixa-resolver's lacre pipeline collapses (the second silently
6890 // overwrites the first at `concrete_versao`-resolve time). The
6891 // gate surfaces the duplicate at validate-time, naming the
6892 // offending caixa + the list, before the resolver-side silent
6893 // drop. Mirrors the peer typed-graph duplicate gates
6894 // (`DuplicateChildCaixa`, `MembroDuplicate`, `DuplicateFrom`, …).
6895 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6896 c.deps = vec![
6897 Dep::simple("caixa-teia", "^0.1"),
6898 Dep::simple("caixa-teia", "^0.2"),
6899 ];
6900 let err = c.validate_deps().unwrap_err();
6901 assert!(
6902 matches!(
6903 err,
6904 crate::dep::DepError::DuplicateNome { ref nome, list }
6905 if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
6906 ),
6907 "got {err:?}"
6908 );
6909 }
6910
6911 #[test]
6912 fn validate_deps_rejects_duplicate_nome_in_deps_dev() {
6913 // Parity pin: `:deps-dev` runs through the same per-list
6914 // duplicate check as `:deps` — neither axis is a second-class
6915 // citizen of the set-not-multiset discipline.
6916 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6917 c.deps_dev = vec![
6918 Dep::simple("tatara-check", "*"),
6919 Dep::simple("tatara-check", "^0.1"),
6920 ];
6921 let err = c.validate_deps().unwrap_err();
6922 assert!(
6923 matches!(
6924 err,
6925 crate::dep::DepError::DuplicateNome { ref nome, list }
6926 if nome == "tatara-check" && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
6927 ),
6928 "got {err:?}"
6929 );
6930 }
6931
6932 #[test]
6933 fn validate_deps_accepts_cross_list_same_nome() {
6934 // The Cargo `[dependencies]` + `[dev-dependencies]` override
6935 // convention is preserved: a name appearing in *both* lists is
6936 // valid (the dev-pin overrides at test/dev time). Only
6937 // within-list duplicates are structurally incoherent — pin the
6938 // permissive cross-list semantics so a future shortcut that
6939 // collapses the two seen-sets into one surfaces here as a test
6940 // failure.
6941 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6942 c.deps = vec![Dep::simple("caixa-teia", "^0.1")];
6943 c.deps_dev = vec![Dep::simple("caixa-teia", "^0.2")];
6944 c.validate_deps().unwrap();
6945 }
6946
6947 #[test]
6948 fn validate_deps_accepts_distinct_nome_in_both_lists() {
6949 // Positive control: distinct names within each list pass — the
6950 // gate's identity element on the canonical authoring shape.
6951 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6952 c.deps = vec![
6953 Dep::simple("caixa-teia", "^0.1"),
6954 Dep::simple("pleme-mesh", "*"),
6955 ];
6956 c.deps_dev = vec![
6957 Dep::simple("tatara-check", "*"),
6958 Dep::simple("dev-shim", "^0.1"),
6959 ];
6960 c.validate_deps().unwrap();
6961 }
6962
6963 #[test]
6964 fn validate_deps_per_entry_validate_fires_before_duplicate_in_deps() {
6965 // Diagnostic-precedence pin: a malformed `:versao` on the
6966 // duplicating entry surfaces its narrower `VersaoInvalid`
6967 // diagnostic first, before the cross-entry duplicate gate fires
6968 // — the canonical "per-entry shape before cross-entry uniqueness"
6969 // precedence every peer set-not-multiset gate establishes
6970 // (`*_invalid_fires_before_duplicate_check` pins on
6971 // `SupervisorSpec::validate`, `AplicacaoSpec::validate_membros`,
6972 // `validate_upgrade_from`).
6973 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
6974 c.deps = vec![
6975 Dep::simple("caixa-teia", "^0.1"),
6976 Dep::simple("caixa-teia", "^bad-version"),
6977 ];
6978 let err = c.validate_deps().unwrap_err();
6979 assert!(
6980 matches!(
6981 err,
6982 crate::dep::DepError::VersaoInvalid { ref nome, ref versao, .. }
6983 if nome == "caixa-teia" && versao == "^bad-version"
6984 ),
6985 "expected VersaoInvalid to surface before DuplicateNome, got {err:?}"
6986 );
6987 }
6988
6989 #[test]
6990 fn validate_deps_duplicate_diagnostic_names_first_collision() {
6991 // First-collision determinism pin: with three entries naming the
6992 // same caixa, the first colliding pair surfaces — not the last.
6993 // Mirrors the peer first-collision posture on every
6994 // duplicate-target gate
6995 // (`validate_upgrade_from_duplicate_diagnostic_names_second_collision`
6996 // — the second entry is the first collision; this gate uses the
6997 // same shape: the second entry's `:nome` lands in the diagnostic
6998 // because `seen.insert(first.nome)` already populated the set).
6999 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7000 c.deps = vec![
7001 Dep::simple("caixa-teia", "^0.1"),
7002 Dep::simple("caixa-teia", "^0.2"),
7003 Dep::simple("caixa-teia", "^0.3"),
7004 ];
7005 let err = c.validate_deps().unwrap_err();
7006 // The diagnostic carries the offending caixa name; the
7007 // implementation surfaces on the *second* entry (the first
7008 // collision), so the test pins the `:nome` value.
7009 assert!(
7010 matches!(
7011 err,
7012 crate::dep::DepError::DuplicateNome { ref nome, list }
7013 if nome == "caixa-teia" && list == crate::render::DEP_AUTHOR_KEY_DEPS
7014 ),
7015 "got {err:?}"
7016 );
7017 }
7018
7019 #[test]
7020 fn validate_deps_duplicate_in_deps_fires_before_duplicate_in_deps_dev() {
7021 // Cross-list precedence pin: when both lists carry duplicates,
7022 // the `:deps` diagnostic surfaces first — same author-mental-
7023 // model ordering the `validate_deps_runs_deps_before_deps_dev`
7024 // pin establishes for malformed `:versao` (runtime axis before
7025 // dev axis).
7026 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7027 c.deps = vec![
7028 Dep::simple("runtime-dep", "^0.1"),
7029 Dep::simple("runtime-dep", "^0.2"),
7030 ];
7031 c.deps_dev = vec![Dep::simple("dev-dep", "*"), Dep::simple("dev-dep", "^0.1")];
7032 let err = c.validate_deps().unwrap_err();
7033 assert!(
7034 matches!(
7035 err,
7036 crate::dep::DepError::DuplicateNome { ref nome, list }
7037 if nome == "runtime-dep" && list == crate::render::DEP_AUTHOR_KEY_DEPS
7038 ),
7039 "expected :deps duplicate to surface before :deps-dev duplicate, got {err:?}"
7040 );
7041 }
7042
7043 #[test]
7044 fn validate_deps_empty_lists_pass_duplicate_gate() {
7045 // Empty-set identity pin: the bare template (zero deps, zero
7046 // deps_dev) passes the duplicate gate as the gate's identity
7047 // element. A future tighten that conflates "empty" with
7048 // "missing" would regress this baseline.
7049 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7050 c.validate_deps().unwrap();
7051 }
7052
7053 #[test]
7054 fn validate_deps_duplicate_diagnostic_carries_list_tag() {
7055 // Diagnostic-shape pin: the `list:` field tags which list the
7056 // duplicate landed in (`:deps` vs `:deps-dev`) verbatim, so a
7057 // `feira lint` run can route the author to the right block in
7058 // their caixa.lisp without re-deriving the list from context.
7059 // Same self-locating shape every peer per-axis diagnostic
7060 // already exposes.
7061 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7062 c.deps_dev = vec![
7063 Dep::simple("dev-thing", "*"),
7064 Dep::simple("dev-thing", "^0.1"),
7065 ];
7066 let err = c.validate_deps().unwrap_err();
7067 let crate::dep::DepError::DuplicateNome { nome, list } = err else {
7068 panic!("expected DuplicateNome from :deps-dev walk");
7069 };
7070 assert_eq!(nome, "dev-thing");
7071 assert_eq!(list, crate::render::DEP_AUTHOR_KEY_DEPS_DEV);
7072 }
7073
7074 // ── validate_deps: per-entry :caracteristicas set-discipline gate ──
7075
7076 #[test]
7077 fn validate_deps_surfaces_caracteristicas_duplicate_in_deps_list() {
7078 // Thread-through pin on `:deps`: the per-entry
7079 // `Dep::validate_caracteristicas` gate fires inside
7080 // `Caixa::validate_deps`'s linear walk, so a malformed feature
7081 // list on any `:deps` entry surfaces as a `DepError` from
7082 // `validate_deps` — the same reachability shape every per-entry
7083 // `Dep::validate` arm threads through. Without this pin a future
7084 // shortcut that skips the per-entry `Dep::validate` call on the
7085 // cross-entry-uniqueness path would mask the within-entry
7086 // `:caracteristicas` gates.
7087 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7088 c.deps = vec![Dep {
7089 nome: "caixa-teia".into(),
7090 versao: "^0.1".into(),
7091 fonte: None,
7092 opcional: false,
7093 caracteristicas: vec!["http".into(), "http".into()],
7094 }];
7095 let err = c.validate_deps().unwrap_err();
7096 let crate::dep::DepError::CaracteristicaDuplicate {
7097 nome,
7098 caracteristica,
7099 } = err
7100 else {
7101 panic!("expected CaracteristicaDuplicate from :deps walk, got {err:?}");
7102 };
7103 assert_eq!(nome, "caixa-teia");
7104 assert_eq!(caracteristica, "http");
7105 }
7106
7107 #[test]
7108 fn validate_deps_surfaces_caracteristicas_empty_in_deps_dev_list() {
7109 // Peer thread-through pin on `:deps-dev`: same reachability as
7110 // the `:deps` arm above, on the dev-only authoring axis. Pins
7111 // that the `validate_deps` walk visits both lists' per-entry
7112 // gates uniformly. The empty-feature arm carries here so both
7113 // new `:caracteristicas` arms are surfaced via at least one
7114 // `validate_deps` thread-through.
7115 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7116 c.deps_dev = vec![Dep {
7117 nome: "caixa-teia".into(),
7118 versao: "^0.1".into(),
7119 fonte: None,
7120 opcional: false,
7121 caracteristicas: vec![String::new()],
7122 }];
7123 let err = c.validate_deps().unwrap_err();
7124 let crate::dep::DepError::CaracteristicaEmpty { nome } = err else {
7125 panic!("expected CaracteristicaEmpty from :deps-dev walk, got {err:?}");
7126 };
7127 assert_eq!(nome, "caixa-teia");
7128 }
7129
7130 #[test]
7131 fn validate_deps_surfaces_caracteristicas_invalid_in_deps_list() {
7132 // Thread-through pin on `:deps`: the per-entry
7133 // `Dep::validate_caracteristicas` value-shape gate (lifted via
7134 // `crate::render::is_cargo_feature_name`) fires inside
7135 // `Caixa::validate_deps`'s linear walk on the `:deps` list, so
7136 // a structurally invalid feature name on any `:deps` entry
7137 // surfaces as `DepError::CaracteristicaInvalid` from
7138 // `validate_deps` — the same reachability shape every per-entry
7139 // `Dep::validate` arm threads through. Without this pin a
7140 // future shortcut that skips the per-entry `Dep::validate` call
7141 // on the cross-entry-uniqueness path would mask the within-
7142 // entry `:caracteristicas` value-shape gate.
7143 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7144 c.deps = vec![Dep {
7145 nome: "caixa-teia".into(),
7146 versao: "^0.1".into(),
7147 fonte: None,
7148 opcional: false,
7149 caracteristicas: vec!["+http".into()],
7150 }];
7151 let err = c.validate_deps().unwrap_err();
7152 let crate::dep::DepError::CaracteristicaInvalid {
7153 nome,
7154 caracteristica,
7155 ..
7156 } = err
7157 else {
7158 panic!("expected CaracteristicaInvalid from :deps walk, got {err:?}");
7159 };
7160 assert_eq!(nome, "caixa-teia");
7161 assert_eq!(caracteristica, "+http");
7162 }
7163
7164 #[test]
7165 fn validate_deps_surfaces_caracteristicas_invalid_in_deps_dev_list() {
7166 // Peer thread-through pin on `:deps-dev`: same reachability as
7167 // the `:deps` arm above, on the dev-only authoring axis. The
7168 // `http/json` shape carries here so the segment-separator
7169 // diagnostic (the canonical Cargo `dep/feat` namespaced-dep
7170 // confusion footgun) is surfaced via the cross-entry walk too —
7171 // pinning that the `:deps-dev` list visits the same per-entry
7172 // value-shape gate as the `:deps` list.
7173 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7174 c.deps_dev = vec![Dep {
7175 nome: "caixa-teia".into(),
7176 versao: "^0.1".into(),
7177 fonte: None,
7178 opcional: false,
7179 caracteristicas: vec!["http/json".into()],
7180 }];
7181 let err = c.validate_deps().unwrap_err();
7182 let crate::dep::DepError::CaracteristicaInvalid {
7183 nome,
7184 caracteristica,
7185 ..
7186 } = err
7187 else {
7188 panic!("expected CaracteristicaInvalid from :deps-dev walk, got {err:?}");
7189 };
7190 assert_eq!(nome, "caixa-teia");
7191 assert_eq!(caracteristica, "http/json");
7192 }
7193
7194 #[test]
7195 fn to_lisp_preserves_deps() {
7196 let src = r#"
7197(defcaixa
7198 :nome "x"
7199 :versao "0.1.0"
7200 :kind Biblioteca
7201 :deps ((:nome "a" :versao "^0.1")
7202 (:nome "b" :versao "*" :fonte (:tipo git :repo "github:o/b" :tag "v1"))))
7203"#;
7204 let c1 = Caixa::from_lisp(src).unwrap();
7205 let emitted = c1.to_lisp();
7206 let c2 = Caixa::from_lisp(&emitted).expect("round trip");
7207 assert_eq!(c1.deps, c2.deps);
7208 }
7209
7210 // ── Caixa::validate_nome — top-level :nome value-shape gate ─────────
7211
7212 fn caixa_with_nome(nome: &str) -> Caixa {
7213 let mut c = Caixa::from_lisp(&Caixa::template("placeholder")).unwrap();
7214 c.nome = nome.to_string();
7215 c
7216 }
7217
7218 #[test]
7219 fn validate_nome_accepts_canonical_template() {
7220 // Positive control: the bare `feira init`-style template's
7221 // `:nome` ("demo") is a canonical DNS-1123 label; the gate must
7222 // not regress this baseline shape. A future tightening of the
7223 // accepted set surfaces here as a test failure first.
7224 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7225 c.validate_nome().unwrap();
7226 }
7227
7228 #[test]
7229 fn validate_nome_accepts_canonical_forms() {
7230 // Positive-set sweep: each realistic caixa-name shape the K8s
7231 // apiserver accepts as a `metadata.name` label must pass —
7232 // single-word, hyphen-joined, version-suffixed, single-char,
7233 // two-char, digit-start (DNS-1123 allows this; the stricter
7234 // DNS-1035 Service-name rule doesn't), version-suffix-bearing.
7235 // Mirrors `accepts_canonical_membro_caixa_forms` (3f9d7a0) on
7236 // the peer member-name axis.
7237 for nome in [
7238 "checkout",
7239 "cart-v2",
7240 "a",
7241 "db",
7242 "3rd-party-shim",
7243 "payment-retry",
7244 "0",
7245 ] {
7246 caixa_with_nome(nome)
7247 .validate_nome()
7248 .unwrap_or_else(|e| panic!("canonical :nome {nome:?} must validate, got {e:?}"));
7249 }
7250 }
7251
7252 #[test]
7253 fn validate_nome_rejects_empty() {
7254 // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
7255 // an empty `:nome` (the derive macro stores the raw String);
7256 // the gate's empty arm names the offending axis with a narrower
7257 // diagnostic than the `NomeInvalid` parse arm would emit.
7258 let c = caixa_with_nome("");
7259 let err = c.validate_nome().unwrap_err();
7260 assert_eq!(err, ManifestError::NomeEmpty);
7261 }
7262
7263 #[test]
7264 fn validate_nome_rejects_uppercase() {
7265 // The canonical "I copied the TitleCase display name verbatim"
7266 // footgun. The K8s apiserver rejects `metadata.name: MyApp` at
7267 // admission on every derived artifact (Helm chart, ComputeUnit,
7268 // CNP, HTTPRoute, label values); the gate moves the diagnostic
7269 // to the source `caixa.lisp` and the reason suggests the
7270 // lowercased fix verbatim.
7271 let c = caixa_with_nome("MyApp");
7272 let err = c.validate_nome().unwrap_err();
7273 let ManifestError::NomeInvalid { nome, reason } = err else {
7274 panic!("expected NomeInvalid for uppercase :nome");
7275 };
7276 assert_eq!(nome, "MyApp");
7277 assert!(
7278 reason.contains("uppercase") && reason.contains("myapp"),
7279 "diagnostic must name the violation + the lowercased fix, got {reason:?}"
7280 );
7281 }
7282
7283 #[test]
7284 fn validate_nome_rejects_underscore() {
7285 // The Python-/Postgres-style `snake_case` leak. DNS-1123 forbids
7286 // `_`; the apiserver rejects on admission across every derived
7287 // artifact. Same fixture pinned for `:membros :caixa` (3f9d7a0)
7288 // and `:children :caixa` (31bfa43).
7289 let c = caixa_with_nome("my_app");
7290 let err = c.validate_nome().unwrap_err();
7291 assert!(
7292 matches!(
7293 err,
7294 ManifestError::NomeInvalid { ref nome, ref reason }
7295 if nome == "my_app" && reason.contains('_')
7296 ),
7297 "got {err:?}"
7298 );
7299 }
7300
7301 #[test]
7302 fn validate_nome_rejects_dot() {
7303 // A `:nome` is a single DNS-1123 label, not a subdomain. The
7304 // "I want to namespace with `.`" footgun the gate redirects to
7305 // `-` via the shared predicate's reason wording.
7306 let c = caixa_with_nome("team.app");
7307 let err = c.validate_nome().unwrap_err();
7308 assert!(
7309 matches!(
7310 err,
7311 ManifestError::NomeInvalid { ref nome, ref reason }
7312 if nome == "team.app" && reason.contains('.')
7313 ),
7314 "got {err:?}"
7315 );
7316 }
7317
7318 #[test]
7319 fn validate_nome_rejects_leading_hyphen() {
7320 // DNS-1123 boundary rule: the label must start with an ASCII
7321 // alphanumeric. Pin the leading-`-` arm explicitly.
7322 let c = caixa_with_nome("-app");
7323 let err = c.validate_nome().unwrap_err();
7324 assert!(
7325 matches!(
7326 err,
7327 ManifestError::NomeInvalid { ref nome, .. } if nome == "-app"
7328 ),
7329 "got {err:?}"
7330 );
7331 }
7332
7333 #[test]
7334 fn validate_nome_rejects_trailing_hyphen() {
7335 // Symmetric arm of the boundary rule, pinned separately so a
7336 // future relaxation that only checks the leading position
7337 // surfaces here. Mirrors `rejects_membro_caixa_with_trailing_hyphen`
7338 // and `_with_trailing_hyphen` on the supervisor / aplicacao
7339 // axes.
7340 let c = caixa_with_nome("app-");
7341 let err = c.validate_nome().unwrap_err();
7342 assert!(
7343 matches!(
7344 err,
7345 ManifestError::NomeInvalid { ref nome, .. } if nome == "app-"
7346 ),
7347 "got {err:?}"
7348 );
7349 }
7350
7351 #[test]
7352 fn validate_nome_rejects_unicode() {
7353 // IDN must be pre-encoded as Punycode (`xn--…`); raw Unicode
7354 // bytes are rejected by the K8s apiserver on every name axis.
7355 let c = caixa_with_nome("café");
7356 let err = c.validate_nome().unwrap_err();
7357 assert!(
7358 matches!(
7359 err,
7360 ManifestError::NomeInvalid { ref nome, .. } if nome == "café"
7361 ),
7362 "got {err:?}"
7363 );
7364 }
7365
7366 #[test]
7367 fn validate_nome_rejects_whitespace() {
7368 // The paste-from-sketch / paste-from-spec footgun. Internal
7369 // whitespace is rejected by every K8s name axis.
7370 let c = caixa_with_nome("my app");
7371 let err = c.validate_nome().unwrap_err();
7372 assert!(
7373 matches!(
7374 err,
7375 ManifestError::NomeInvalid { ref nome, .. } if nome == "my app"
7376 ),
7377 "got {err:?}"
7378 );
7379 }
7380
7381 #[test]
7382 fn validate_nome_rejects_too_long() {
7383 // 64-byte boundary pin: the K8s apiserver rejects any
7384 // `metadata.name` over 63 bytes at admission; the diagnostic
7385 // names both the 63-byte cap and the actual length so the
7386 // author can shorten in one edit. Mirrors `_too_long` on the
7387 // peer member-/cluster-/child-name axes.
7388 let over = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN + 1);
7389 let c = caixa_with_nome(&over);
7390 let err = c.validate_nome().unwrap_err();
7391 let ManifestError::NomeInvalid { nome, reason } = err else {
7392 panic!("expected NomeInvalid for over-cap :nome");
7393 };
7394 assert_eq!(nome.len(), crate::DNS_1123_LABEL_MAX_LEN + 1);
7395 assert!(
7396 reason.contains("63") && reason.contains("64"),
7397 "diagnostic must name the cap + actual length, got {reason:?}"
7398 );
7399 }
7400
7401 #[test]
7402 fn nome_max_length_validates() {
7403 // The 63-byte cap exactly — the boundary-accepting case pinned
7404 // alongside `validate_nome_rejects_too_long` so a future cap
7405 // shift surfaces both arms simultaneously. Mirrors
7406 // `membro_caixa_max_length_validates`,
7407 // `placement_cluster_max_length_validates`,
7408 // `child_caixa_max_length_validates`.
7409 let at_cap = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
7410 caixa_with_nome(&at_cap).validate_nome().unwrap();
7411 }
7412
7413 #[test]
7414 fn nome_empty_takes_precedence_over_invalid() {
7415 // Order pin: the empty arm fires before the predicate is
7416 // consulted. Empty < invalid in self-locating-ness — the
7417 // narrower `NomeEmpty` diagnostic doesn't carry a useless
7418 // `nome: ""` reference into the parser-shaped reason. Mirrors
7419 // `membro_caixa_empty_takes_precedence_over_invalid` on the
7420 // peer axis (3f9d7a0).
7421 let c = caixa_with_nome("");
7422 assert_eq!(c.validate_nome().unwrap_err(), ManifestError::NomeEmpty);
7423 }
7424
7425 #[test]
7426 fn nome_invalid_diagnostic_carries_offending_nome() {
7427 // Diagnostic-shape pin: the error names the offending `:nome`
7428 // verbatim with a non-empty parser-shaped reason, so a `feira
7429 // lint` run can render the diagnostic without re-parsing.
7430 // Mirrors `membro_caixa_invalid_diagnostic_carries_offending_caixa`.
7431 let c = caixa_with_nome("MyApp");
7432 let err = c.validate_nome().unwrap_err();
7433 let ManifestError::NomeInvalid { nome, reason } = err else {
7434 panic!("expected NomeInvalid variant");
7435 };
7436 assert_eq!(nome, "MyApp");
7437 assert!(
7438 !reason.is_empty(),
7439 "NomeInvalid `reason` must carry the predicate's wording verbatim"
7440 );
7441 }
7442
7443 // ── Caixa::validate_nome_chart_name_budget — joint-length on `:nome` ──
7444 //
7445 // The bare-`:nome` axis [`Caixa::validate_nome`] caps at 63 bytes
7446 // via DNS-1123; this second-axis gate caps the joint
7447 // `lareira-<nome>` chart name at the same 63-byte ceiling. The
7448 // canonical [`crate::lareira_chart_name`] helper's doc comment
7449 // (f7320d7, caixa-core/src/render.rs:3198) explicitly deferred:
7450 // "the M4 admission webhook will pin the joint-length invariant
7451 // when it lands". These tests pin it at the manifest-validate
7452 // layer instead, fail-before-pass-after on the 56-byte boundary.
7453
7454 #[test]
7455 fn validate_nome_chart_name_budget_accepts_canonical_template() {
7456 // Positive control: the bare `feira init`-style template's
7457 // `:nome` ("demo") sits far below the cap; the gate must not
7458 // regress this baseline. Same shape every peer
7459 // value-shape-gate baseline pin uses.
7460 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7461 c.validate_nome_chart_name_budget().unwrap();
7462 }
7463
7464 #[test]
7465 fn validate_nome_chart_name_budget_accepts_canonical_fixtures() {
7466 // Positive-set sweep across the canonical author surface every
7467 // in-tree fixture uses (`hello-rio`, `cart`, `checkout`,
7468 // `worker`, the `checkout-aplicacao` example members, the
7469 // `akeyless-attest` caixa-tatara fixture). Every value sits
7470 // far below the 55-byte per-`:nome` budget. Same shape every
7471 // peer per-axis baseline pin uses.
7472 for nome in [
7473 "hello-rio",
7474 "cart",
7475 "checkout",
7476 "worker",
7477 "akeyless-attest",
7478 "demo",
7479 "a",
7480 ] {
7481 caixa_with_nome(nome)
7482 .validate_nome_chart_name_budget()
7483 .unwrap_or_else(|e| {
7484 panic!("canonical :nome {nome:?} must pass chart-name budget, got {e:?}")
7485 });
7486 }
7487 }
7488
7489 #[test]
7490 fn validate_nome_chart_name_budget_accepts_nome_at_cap() {
7491 // Boundary-accepting case at the 55-byte per-`:nome` budget —
7492 // the joint chart name is exactly 63 bytes, the DNS-1123 label
7493 // cap. Pinned alongside the rejecting-arm test so a future cap
7494 // shift surfaces both arms simultaneously. Mirrors
7495 // `nome_max_length_validates` on the peer bare-`:nome` axis.
7496 let at_cap = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN);
7497 caixa_with_nome(&at_cap)
7498 .validate_nome_chart_name_budget()
7499 .unwrap();
7500 }
7501
7502 #[test]
7503 fn validate_nome_chart_name_budget_rejects_nome_one_over_cap() {
7504 // Fail-before-pass-after pin on the 56-byte boundary: the
7505 // smallest `:nome` length that overflows the joint chart-name
7506 // cap. The inner [`is_dns_1123_label`] gate
7507 // (`Caixa::validate_nome`) accepts it (56 ≤ 63), so prior to
7508 // this gate it silently passed the manifest-validate cascade
7509 // and surfaced as a `helm lint` / apiserver rejection on the
7510 // rendered chart name far from the source `caixa.lisp`, with
7511 // no field naming the overflow. With this gate the diagnostic
7512 // names the offending `:nome` verbatim alongside the rendered
7513 // chart name and the budget, so the author can shorten in one
7514 // edit. Mirrors `validate_nome_rejects_too_long` on the peer
7515 // bare-`:nome` axis.
7516 let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
7517 let c = caixa_with_nome(&over);
7518 let err = c.validate_nome_chart_name_budget().unwrap_err();
7519 let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
7520 panic!("expected NomeChartNameBudgetExceeded for over-budget :nome");
7521 };
7522 assert_eq!(nome.len(), crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
7523 assert_eq!(nome, over);
7524 assert!(
7525 reason.contains("63") && reason.contains("64") && reason.contains("55"),
7526 "diagnostic must name the DNS-1123 cap (63), the actual chart-name length (64), \
7527 and the per-`:nome` budget (55), got {reason:?}"
7528 );
7529 }
7530
7531 #[test]
7532 fn validate_nome_chart_name_budget_rejects_nome_at_bare_dns_cap() {
7533 // The 63-byte `:nome` boundary — passes the bare-`:nome`
7534 // [`is_dns_1123_label`] cap exactly, but produces a 71-byte
7535 // joint chart name that overflows the DNS-1123 label cap
7536 // structurally. The most stringent fail-before-pass-after
7537 // surface: every `:nome` in the 56..=63-byte range passed the
7538 // prior cascade and broke at admission.
7539 let bare_max = "a".repeat(crate::DNS_1123_LABEL_MAX_LEN);
7540 let c = caixa_with_nome(&bare_max);
7541 // The bare-`:nome` gate accepts the 63-byte length.
7542 c.validate_nome().unwrap();
7543 // The new joint-length gate rejects it.
7544 let err = c.validate_nome_chart_name_budget().unwrap_err();
7545 assert!(
7546 matches!(
7547 err,
7548 ManifestError::NomeChartNameBudgetExceeded { ref nome, .. }
7549 if nome.len() == crate::DNS_1123_LABEL_MAX_LEN
7550 ),
7551 "got {err:?}"
7552 );
7553 }
7554
7555 #[test]
7556 fn validate_nome_chart_name_budget_diagnostic_carries_offending_chart_name() {
7557 // Diagnostic-shape pin: the rendered `lareira-<nome>` chart
7558 // name appears verbatim in the diagnostic so the author sees
7559 // exactly the string the apiserver / `helm lint` would have
7560 // rejected — no re-derivation required to grep the source.
7561 // Peer with `nome_invalid_diagnostic_carries_offending_nome`
7562 // on the bare-`:nome` axis.
7563 let over = "x".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 5);
7564 let c = caixa_with_nome(&over);
7565 let err = c.validate_nome_chart_name_budget().unwrap_err();
7566 let ManifestError::NomeChartNameBudgetExceeded { nome, reason } = err else {
7567 panic!("expected NomeChartNameBudgetExceeded variant");
7568 };
7569 assert_eq!(nome, over);
7570 let expected_chart = crate::lareira_chart_name(&over);
7571 assert!(
7572 reason.contains(&expected_chart),
7573 "diagnostic must carry the rendered chart name {expected_chart:?} verbatim, \
7574 got {reason:?}"
7575 );
7576 assert!(
7577 reason.contains("lareira-"),
7578 "diagnostic must name the canonical chart-name prefix verbatim, got {reason:?}"
7579 );
7580 }
7581
7582 #[test]
7583 fn validate_nome_chart_name_budget_runs_after_nome_shape_via_layout_verify() {
7584 // Order pin on the layout cascade: the narrower
7585 // `NomeInvalid` (bare-DNS-1123 shape) fires before the
7586 // joint-length budget. A structurally-malformed `:nome` (here:
7587 // uppercase) surfaces its specific shape error rather than
7588 // the chart-name-budget error, even when the joint length
7589 // would also overflow — the narrower diagnostic is more
7590 // self-locating. Mirrors the cascade-precedence pins peer
7591 // gates already use (e.g. `EntradaParaEmpty` before
7592 // `EntradaParaInvalid`).
7593 let over = "A".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
7594 let c = caixa_with_nome(&over);
7595 // The bare-shape gate fires first.
7596 let err = c.validate_nome().unwrap_err();
7597 assert!(
7598 matches!(err, ManifestError::NomeInvalid { .. }),
7599 "bare-shape gate must fire before chart-name-budget gate; got {err:?}"
7600 );
7601 // And the layout verify cascade surfaces that diagnostic, not
7602 // the budget arm. Inject a path-exists oracle so the cascade
7603 // gets past the manifest-presence check and into the
7604 // value-shape gates.
7605 let layout = crate::StandardLayout::new().with_path_exists(|_| true);
7606 let err = crate::LayoutInvariants::verify(
7607 &layout,
7608 &c,
7609 std::path::Path::new("/tmp/caixa-test-fake-root"),
7610 )
7611 .unwrap_err();
7612 let issue = err.to_string();
7613 assert!(
7614 issue.contains("DNS-1123") || issue.contains("uppercase"),
7615 "layout cascade must surface the bare-DNS-1123 diagnostic on a \
7616 structurally-malformed :nome, not the chart-name-budget diagnostic; got {issue:?}"
7617 );
7618 }
7619
7620 #[test]
7621 fn layout_verify_routes_chart_name_budget_through_nome_violation() {
7622 // Cross-axis envelope pin: the layout cascade wraps both
7623 // bare-`:nome` and joint-length-`:nome` failures through the
7624 // same [`LayoutError::NomeViolation`] envelope, since both
7625 // arms are on the `:nome` axis. The user's diagnostic stays
7626 // self-locating ("which axis"), and a future consumer that
7627 // dispatches on the layout-error variant (e.g. a `feira lint`
7628 // exit-code mapping) sees a single per-axis envelope. The
7629 // wrapped `issue:` carries the full inner diagnostic.
7630 let over = "a".repeat(crate::LAREIRA_CHART_NAME_NOME_MAX_LEN + 1);
7631 let c = caixa_with_nome(&over);
7632 // The bare-shape gate accepts.
7633 c.validate_nome().unwrap();
7634 let layout = crate::StandardLayout::new().with_path_exists(|_| true);
7635 let err = crate::LayoutInvariants::verify(
7636 &layout,
7637 &c,
7638 std::path::Path::new("/tmp/caixa-test-fake-root"),
7639 )
7640 .unwrap_err();
7641 let crate::LayoutError::NomeViolation { caixa, issue } = err else {
7642 panic!("expected LayoutError::NomeViolation, got {err:?}");
7643 };
7644 assert_eq!(caixa, over);
7645 assert!(
7646 issue.contains("lareira-") && issue.contains("63") && issue.contains("55"),
7647 "wrapped issue must carry the joint-length diagnostic verbatim, got {issue:?}"
7648 );
7649 }
7650
7651 // ── Caixa::validate_versao — top-level :versao value-shape gate ─────
7652
7653 fn caixa_with_versao(versao: &str) -> Caixa {
7654 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7655 c.versao = versao.to_string();
7656 c
7657 }
7658
7659 #[test]
7660 fn validate_versao_accepts_canonical_template() {
7661 // Positive control: the bare `feira init`-style template's
7662 // `:versao` ("0.1.0") is a canonical SemVer-2 literal; the gate
7663 // must not regress this baseline shape. A future tightening of
7664 // the accepted set surfaces here as a test failure first.
7665 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
7666 c.validate_versao().unwrap();
7667 }
7668
7669 #[test]
7670 fn validate_versao_accepts_canonical_forms() {
7671 // Positive-set sweep: each realistic SemVer-2 shape the
7672 // substrate's downstream consumers accept must pass — bare
7673 // MAJOR.MINOR.PATCH, pre-release tags (`-rc.1`, `-alpha.0`),
7674 // build metadata (`+build.42`), the combined form, and the
7675 // `0.0.0` boundary case. Mirrors `accepts_canonical_forms` on
7676 // the peer `:nome` axis (6c992f8).
7677 for versao in [
7678 "0.1.0",
7679 "0.0.0",
7680 "1.0.0",
7681 "0.2.0-rc.1",
7682 "1.0.0-alpha.0",
7683 "1.0.0+build.42",
7684 "1.0.0-rc.1+build.42",
7685 "10.20.30",
7686 ] {
7687 caixa_with_versao(versao)
7688 .validate_versao()
7689 .unwrap_or_else(|e| {
7690 panic!("canonical :versao {versao:?} must validate, got {e:?}")
7691 });
7692 }
7693 }
7694
7695 #[test]
7696 fn validate_versao_rejects_empty() {
7697 // Fail-before-pass-after pin: `Caixa::from_lisp` does not refuse
7698 // an empty `:versao` (the derive macro stores the raw String);
7699 // the gate's empty arm names the offending axis with a narrower
7700 // diagnostic than the `VersaoInvalid` parse arm would emit.
7701 // Mirrors `validate_nome_rejects_empty` (6c992f8).
7702 let c = caixa_with_versao("");
7703 let err = c.validate_versao().unwrap_err();
7704 assert_eq!(err, ManifestError::VersaoEmpty);
7705 }
7706
7707 #[test]
7708 fn validate_versao_rejects_git_tag_shape() {
7709 // The canonical "I copied the git tag verbatim" footgun —
7710 // `feira publish` *emits* `v<versao>` git tags, so a leaked
7711 // `v0.1.0` in `:versao` would render as `vv0.1.0` and silently
7712 // shift every downstream consumer's version axis. `semver`
7713 // rejects the leading `v` at parse time; the gate moves the
7714 // diagnostic to the source `caixa.lisp`.
7715 let c = caixa_with_versao("v0.1.0");
7716 let err = c.validate_versao().unwrap_err();
7717 let ManifestError::VersaoInvalid { versao, reason } = err else {
7718 panic!("expected VersaoInvalid for git-tag-shape :versao");
7719 };
7720 assert_eq!(versao, "v0.1.0");
7721 assert!(
7722 !reason.is_empty(),
7723 "VersaoInvalid `reason` must carry the parser's wording, got {reason:?}"
7724 );
7725 }
7726
7727 #[test]
7728 fn validate_versao_rejects_missing_patch() {
7729 // The canonical "I shortened it" footgun — SemVer-2 requires
7730 // three parts. Cargo's `version =` field accepts the shortened
7731 // form as a requirement, conflating the two leaks across the
7732 // typed `:deps :versao` vs top-level `:versao` axes; the gate
7733 // pins the top-level axis to the strict three-part shape.
7734 let c = caixa_with_versao("0.1");
7735 let err = c.validate_versao().unwrap_err();
7736 assert!(
7737 matches!(
7738 err,
7739 ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1"
7740 ),
7741 "got {err:?}"
7742 );
7743 }
7744
7745 #[test]
7746 fn validate_versao_rejects_requirement_shape() {
7747 // The canonical "I leaked a requirement into a version" footgun —
7748 // the typed `:deps :versao` / `:membros :versao` axes accept
7749 // `^0.1` (a `VersionReq`); the top-level `:versao` requires a
7750 // concrete `Version`. Without this gate the two typed surfaces
7751 // would silently overlap, and a top-level `^0.1` would surface
7752 // at `helm install` time as a Chart.yaml version rejection far
7753 // from the source `caixa.lisp`.
7754 let c = caixa_with_versao("^0.1");
7755 let err = c.validate_versao().unwrap_err();
7756 assert!(
7757 matches!(
7758 err,
7759 ManifestError::VersaoInvalid { ref versao, .. } if versao == "^0.1"
7760 ),
7761 "got {err:?}"
7762 );
7763 }
7764
7765 #[test]
7766 fn validate_versao_rejects_docker_tag_shape() {
7767 // The "I confused it with a docker tag" footgun — `latest`,
7768 // `main`, `stable` parse as identifiers, not SemVer-2 versions.
7769 // SemVer rejects at parse time; the gate moves the diagnostic
7770 // to the source `caixa.lisp`.
7771 for bad in ["latest", "main", "stable"] {
7772 let c = caixa_with_versao(bad);
7773 let err = c.validate_versao().unwrap_err();
7774 assert!(
7775 matches!(
7776 err,
7777 ManifestError::VersaoInvalid { ref versao, .. } if versao == bad
7778 ),
7779 "got {err:?} for {bad:?}"
7780 );
7781 }
7782 }
7783
7784 #[test]
7785 fn validate_versao_rejects_four_part_form() {
7786 // The Java/Microsoft "MAJOR.MINOR.PATCH.BUILD" convention
7787 // SemVer-2 forbids. A leak from a non-SemVer ecosystem; the
7788 // semver crate rejects the extra `.0` at parse time.
7789 let c = caixa_with_versao("0.1.0.0");
7790 let err = c.validate_versao().unwrap_err();
7791 assert!(
7792 matches!(
7793 err,
7794 ManifestError::VersaoInvalid { ref versao, .. } if versao == "0.1.0.0"
7795 ),
7796 "got {err:?}"
7797 );
7798 }
7799
7800 #[test]
7801 fn versao_empty_takes_precedence_over_invalid() {
7802 // Order pin: the empty arm fires before the parser is consulted.
7803 // Empty < invalid in self-locating-ness — the narrower
7804 // `VersaoEmpty` diagnostic doesn't carry a useless `versao: ""`
7805 // reference into the parser-shaped reason. Mirrors
7806 // `nome_empty_takes_precedence_over_invalid` (6c992f8) on the
7807 // peer axis.
7808 let c = caixa_with_versao("");
7809 assert_eq!(c.validate_versao().unwrap_err(), ManifestError::VersaoEmpty);
7810 }
7811
7812 #[test]
7813 fn versao_invalid_diagnostic_carries_offending_versao() {
7814 // Diagnostic-shape pin: the error names the offending `:versao`
7815 // verbatim with a non-empty parser-shaped reason, so a `feira
7816 // lint` run can render the diagnostic without re-parsing.
7817 // Mirrors `nome_invalid_diagnostic_carries_offending_nome`.
7818 let c = caixa_with_versao("v0.1.0");
7819 let err = c.validate_versao().unwrap_err();
7820 let ManifestError::VersaoInvalid { versao, reason } = err else {
7821 panic!("expected VersaoInvalid variant");
7822 };
7823 assert_eq!(versao, "v0.1.0");
7824 assert!(
7825 !reason.is_empty(),
7826 "VersaoInvalid `reason` must carry the parser's wording verbatim"
7827 );
7828 }
7829
7830 #[test]
7831 fn validate_versao_accepts_what_upgrade_from_from_accepts() {
7832 // Parity pin: every shape `UpgradeFromEntry::validate` accepts
7833 // for `:upgrade-from :from` must also pass `validate_versao` —
7834 // the two `:versao`-typed surfaces (top-level `:versao`,
7835 // `:upgrade-from :from`) consume the *same* `semver::Version`
7836 // parser, so they must agree on the accepted set. Without this
7837 // pin, a future tightening of one axis could silently diverge
7838 // from the other. Mirrors the `:versao` requirement-axis
7839 // parity (`:deps`/`:deps-dev`/`:membros`/`:children`) the prior
7840 // commits established.
7841 for versao in ["0.1.0", "0.2.0-rc.1", "1.0.0+build.42"] {
7842 // From the canonical UpgradeFromEntry round-trip fixture
7843 // (`upgrade::tests::round_trip_load_module` peers).
7844 let entry = crate::UpgradeFromEntry {
7845 from: versao.to_string(),
7846 instructions: Vec::new(),
7847 };
7848 entry
7849 .validate()
7850 .unwrap_or_else(|e| panic!(":from {versao:?} must validate, got {e:?}"));
7851 caixa_with_versao(versao)
7852 .validate_versao()
7853 .unwrap_or_else(|e| {
7854 panic!(":versao {versao:?} must validate, got {e:?} — peer axis diverges")
7855 });
7856 }
7857 }
7858
7859 // ── Caixa::validate_restart_window — supervisor restart-window
7860 // folds through the shared `supervisor::duration_codec` ────────
7861
7862 fn caixa_with_restart_window(window: Option<&str>) -> Caixa {
7863 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
7864 c.kind = CaixaKind::Supervisor;
7865 c.restart_window = window.map(str::to_string);
7866 c
7867 }
7868
7869 #[test]
7870 fn validate_restart_window_accepts_none() {
7871 // The canonical "omit the slot to express no reset" shape — a
7872 // `None` raw string is the absence of the typed
7873 // `:restart-window` slot, which is exactly the SupervisorSpec
7874 // "never reset" semantics. The gate must be a no-op here; a
7875 // future tightening that rejected `None` would force every
7876 // supervisor caixa to authoring-time pin a window even when
7877 // the OTP semantics call for none.
7878 caixa_with_restart_window(None)
7879 .validate_restart_window()
7880 .unwrap();
7881 }
7882
7883 #[test]
7884 fn validate_restart_window_accepts_canonical_forms() {
7885 // Positive-set sweep across the canonical authoring units the
7886 // shared `supervisor::duration_codec::parse` accepts —
7887 // matches the codec-side `parse_accepts_integer_canonical_units`
7888 // pin in supervisor::tests so a future codec-side tightening
7889 // surfaces simultaneously on both axes.
7890 for window in ["60s", "5m", "1h", "500ms", "30", "0s"] {
7891 caixa_with_restart_window(Some(window))
7892 .validate_restart_window()
7893 .unwrap_or_else(|e| {
7894 panic!("canonical :restart-window {window:?} must validate, got {e:?}")
7895 });
7896 }
7897 }
7898
7899 #[test]
7900 fn validate_restart_window_rejects_fractional_seconds() {
7901 // Fail-before-pass-after pin: the `"1.5s"` drift class (parses
7902 // as f64 to 1.5 → renders back as `"1500ms"` on first
7903 // serialize). Prior to the fold + this gate, the inline
7904 // `parse_window_inline` accepted f64 magnitudes and silently
7905 // produced a `Duration::from_secs_f64(1.5)`, divergent from
7906 // the shared codec's integer-magnitude discipline on the
7907 // serde-routed siblings. The gate now surfaces a self-locating
7908 // diagnostic at the manifest layer.
7909 let err = caixa_with_restart_window(Some("1.5s"))
7910 .validate_restart_window()
7911 .unwrap_err();
7912 let ManifestError::RestartWindowMalformed {
7913 restart_window,
7914 reason,
7915 } = err
7916 else {
7917 panic!("expected RestartWindowMalformed for fractional seconds");
7918 };
7919 assert_eq!(restart_window, "1.5s");
7920 assert!(
7921 reason.contains("\"1.5\"") && reason.contains("not a non-negative integer"),
7922 "diagnostic must carry shared-codec wording, got {reason:?}"
7923 );
7924 }
7925
7926 #[test]
7927 fn validate_restart_window_rejects_decimal_shaped_integer() {
7928 // The `"1.0s"` class — numerically `1s` exactly, but the
7929 // canonical form is `"1s"` not `"1.0s"`. Decimal-shape leak
7930 // gets the same canonical-form diagnostic.
7931 let err = caixa_with_restart_window(Some("1.0s"))
7932 .validate_restart_window()
7933 .unwrap_err();
7934 assert!(
7935 matches!(
7936 err,
7937 ManifestError::RestartWindowMalformed { ref restart_window, .. }
7938 if restart_window == "1.0s"
7939 ),
7940 "got {err:?}"
7941 );
7942 }
7943
7944 #[test]
7945 fn validate_restart_window_rejects_half_unit_minute() {
7946 // `"0.5m"` is the unit-fraction footgun — author writes a
7947 // human-readable half-minute, the prior inline parser silently
7948 // produced `Duration::from_secs_f64(30.0)` and serde
7949 // re-emitted as `"30s"`, rewriting author intent. The gate
7950 // closes the loop at the manifest layer.
7951 let err = caixa_with_restart_window(Some("0.5m"))
7952 .validate_restart_window()
7953 .unwrap_err();
7954 let ManifestError::RestartWindowMalformed {
7955 restart_window,
7956 reason,
7957 } = err
7958 else {
7959 panic!("expected RestartWindowMalformed");
7960 };
7961 assert_eq!(restart_window, "0.5m");
7962 assert!(
7963 reason.contains("\"30s\""),
7964 "diagnostic must point at the canonical-form remediation, got {reason:?}"
7965 );
7966 }
7967
7968 #[test]
7969 fn validate_restart_window_rejects_leading_sign() {
7970 // `"+30s"` and `"-30s"` both round-tripped through f64 cleanly
7971 // on the prior parser (`+30` parses as `30.0`; `-30` parsed
7972 // and was caught by the `num < 0.0` arm which silently
7973 // returned `None`, dropping the author-supplied window). The
7974 // shared codec's digit-only gate rejects both with a unified
7975 // canonical-form diagnostic; the manifest-layer wrapper names
7976 // the offending value.
7977 for bad in ["+30s", "-30s"] {
7978 let err = caixa_with_restart_window(Some(bad))
7979 .validate_restart_window()
7980 .unwrap_err();
7981 assert!(
7982 matches!(
7983 err,
7984 ManifestError::RestartWindowMalformed { ref restart_window, .. }
7985 if restart_window == bad
7986 ),
7987 "got {err:?} for {bad:?}"
7988 );
7989 }
7990 }
7991
7992 #[test]
7993 fn validate_restart_window_rejects_unknown_unit() {
7994 // `"30x"` — the typo / wrong-unit footgun. The shared codec's
7995 // unit dispatch surfaces an `unknown duration unit` reason;
7996 // the manifest-layer wrapper names the offending value.
7997 let err = caixa_with_restart_window(Some("30x"))
7998 .validate_restart_window()
7999 .unwrap_err();
8000 let ManifestError::RestartWindowMalformed {
8001 restart_window,
8002 reason,
8003 } = err
8004 else {
8005 panic!("expected RestartWindowMalformed for unknown unit");
8006 };
8007 assert_eq!(restart_window, "30x");
8008 assert!(
8009 reason.contains("unknown duration unit"),
8010 "diagnostic must carry shared-codec unit-rejection wording, got {reason:?}"
8011 );
8012 }
8013
8014 #[test]
8015 fn validate_restart_window_rejects_garbage() {
8016 // Pure non-numeric magnitude (`"abc"`) falls through to the
8017 // shared codec's narrower `"bad duration magnitude"` arm. Same
8018 // diagnostic shape as the codec-side
8019 // `parse_garbage_still_falls_through_to_bad_magnitude` pin.
8020 let err = caixa_with_restart_window(Some("abc"))
8021 .validate_restart_window()
8022 .unwrap_err();
8023 let ManifestError::RestartWindowMalformed {
8024 restart_window,
8025 reason,
8026 } = err
8027 else {
8028 panic!("expected RestartWindowMalformed for garbage");
8029 };
8030 assert_eq!(restart_window, "abc");
8031 assert!(
8032 reason.contains("bad duration magnitude"),
8033 "diagnostic must carry shared-codec garbage-rejection wording, got {reason:?}"
8034 );
8035 }
8036
8037 #[test]
8038 fn validate_restart_window_rejects_empty_string() {
8039 // The empty-after-trim edge case — distinct from the `None`
8040 // canonical "omit the slot" shape. The shared codec's
8041 // digit-only gate refuses an empty magnitude; the manifest
8042 // layer names the offending `""` so the author can grep for
8043 // the literal empty value in their `caixa.lisp` and either
8044 // remove the slot (the canonical "no reset" shape) or pin a
8045 // positive duration.
8046 let err = caixa_with_restart_window(Some(""))
8047 .validate_restart_window()
8048 .unwrap_err();
8049 assert!(
8050 matches!(
8051 err,
8052 ManifestError::RestartWindowMalformed { ref restart_window, .. }
8053 if restart_window.is_empty()
8054 ),
8055 "got {err:?}"
8056 );
8057 }
8058
8059 #[test]
8060 fn validate_restart_window_diagnostic_carries_offending_value() {
8061 // Diagnostic-shape pin (peer with
8062 // `nome_invalid_diagnostic_carries_offending_nome` /
8063 // `versao_invalid_diagnostic_carries_offending_versao`): the
8064 // error names the offending raw `:restart-window` verbatim
8065 // with a non-empty shared-codec-shaped reason, so a `feira
8066 // lint` run can render the diagnostic without re-parsing.
8067 let err = caixa_with_restart_window(Some("1.5s"))
8068 .validate_restart_window()
8069 .unwrap_err();
8070 let ManifestError::RestartWindowMalformed {
8071 restart_window,
8072 reason,
8073 } = err
8074 else {
8075 panic!("expected RestartWindowMalformed variant");
8076 };
8077 assert_eq!(restart_window, "1.5s");
8078 assert!(
8079 !reason.is_empty(),
8080 "RestartWindowMalformed `reason` must carry the codec's wording verbatim"
8081 );
8082 }
8083
8084 #[test]
8085 fn supervisor_view_folds_through_shared_codec_on_canonical_form() {
8086 // Behavioral parity pin after the fold (`parse_window_inline`
8087 // deletion): the canonical `"60s"` still produces
8088 // `Duration::from_secs(60)` on the typed view — the fold is
8089 // semantically equivalent to the prior inline parser on the
8090 // accepted set. Mirrors the pre-fold `supervisor_view_returns_typed_shape`
8091 // pin, narrowed to the parser-side contract.
8092 let c = caixa_with_restart_window(Some("60s"));
8093 let view = c.supervisor_view().expect("Supervisor kind has a view");
8094 assert_eq!(
8095 view.restart_window,
8096 Some(std::time::Duration::from_secs(60))
8097 );
8098 }
8099
8100 #[test]
8101 fn supervisor_view_soft_swallows_what_validate_rejects() {
8102 // Parity pin between the view-construction path and the
8103 // manifest-level validator: the same `"1.5s"` that surfaces
8104 // `RestartWindowMalformed` at `validate_restart_window` time
8105 // becomes `restart_window: None` on the typed view (the fold
8106 // preserves the existing best-effort shape of `supervisor_view`).
8107 // The contract is: a layout-verifier / `feira lint` flow that
8108 // cares about the malformed-window axis MUST consult
8109 // `validate_restart_window` — relying solely on the view's
8110 // `None` swallows the diagnostic silently. This pin makes the
8111 // expectation a typed invariant.
8112 let c = caixa_with_restart_window(Some("1.5s"));
8113 let view = c.supervisor_view().expect("Supervisor kind has a view");
8114 assert_eq!(
8115 view.restart_window, None,
8116 "view-construction path soft-swallows the parse error to None"
8117 );
8118 // And the manifest-level validator does NOT soft-swallow:
8119 assert!(
8120 matches!(
8121 c.validate_restart_window().unwrap_err(),
8122 ManifestError::RestartWindowMalformed { ref restart_window, .. }
8123 if restart_window == "1.5s"
8124 ),
8125 "validator must surface the offending value",
8126 );
8127 }
8128
8129 // ── validate_code_paths — per-entry shape on :bibliotecas / :exe / :servicos ──
8130
8131 fn caixa_with_code_paths(bibliotecas: Vec<&str>, exe: Vec<&str>, servicos: Vec<&str>) -> Caixa {
8132 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8133 c.bibliotecas = bibliotecas.into_iter().map(String::from).collect();
8134 c.exe = exe.into_iter().map(String::from).collect();
8135 c.servicos = servicos.into_iter().map(String::from).collect();
8136 c
8137 }
8138
8139 #[test]
8140 fn validate_code_paths_accepts_canonical_template() {
8141 // The bare `Caixa::template` shape is the gate's identity element
8142 // on the canonical authoring shape — `:bibliotecas
8143 // ("lib/demo.lisp")` + empty `:exe` + empty `:servicos`. Pins
8144 // that the gate is non-disruptive against every existing caixa.
8145 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8146 c.validate_code_paths().unwrap();
8147 }
8148
8149 #[test]
8150 fn validate_code_paths_accepts_explicit_relative_paths_on_every_slot() {
8151 // Positive control sweep: a canonical-shaped path on every slot
8152 // passes. Mirrors the peer
8153 // `behavior::validate_every_slot_relative_is_ok` pin.
8154 let c = caixa_with_code_paths(
8155 vec!["lib/demo.lisp", "lib/helpers.lisp"],
8156 vec!["exe/demo", "exe/tool"],
8157 vec!["servicos/demo.computeunit.yaml"],
8158 );
8159 c.validate_code_paths().unwrap();
8160 }
8161
8162 #[test]
8163 fn validate_code_paths_accepts_all_empty_lists() {
8164 // The empty-list identity element: every Caixa with no declared
8165 // code paths trivially passes (Supervisor / Aplicacao kinds rely
8166 // on this — the OwnCode gate already rejected them before the
8167 // path-shape gate runs in the layout, but the validator itself
8168 // must accept the empty shape).
8169 let c = caixa_with_code_paths(vec![], vec![], vec![]);
8170 c.validate_code_paths().unwrap();
8171 }
8172
8173 #[test]
8174 fn validate_code_paths_rejects_empty_bibliotecas_entry() {
8175 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
8176 let err = c.validate_code_paths().unwrap_err();
8177 assert!(
8178 matches!(
8179 err,
8180 ManifestError::CodePathEmpty {
8181 slot: ":bibliotecas"
8182 }
8183 ),
8184 "got {err:?}",
8185 );
8186 }
8187
8188 #[test]
8189 fn validate_code_paths_rejects_empty_exe_entry() {
8190 let c = caixa_with_code_paths(vec![], vec![""], vec![]);
8191 let err = c.validate_code_paths().unwrap_err();
8192 assert!(
8193 matches!(err, ManifestError::CodePathEmpty { slot: ":exe" }),
8194 "got {err:?}",
8195 );
8196 }
8197
8198 #[test]
8199 fn validate_code_paths_rejects_empty_servicos_entry() {
8200 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
8201 let err = c.validate_code_paths().unwrap_err();
8202 assert!(
8203 matches!(err, ManifestError::CodePathEmpty { slot: ":servicos" }),
8204 "got {err:?}",
8205 );
8206 }
8207
8208 #[test]
8209 fn validate_code_paths_rejects_absolute_bibliotecas_entry() {
8210 // `:bibliotecas` has no `starts_with(<dir>)` fence downstream,
8211 // so an absolute path that resolves on disk silently passes the
8212 // layout's existence check — the canonical sandbox-escape on
8213 // the biblioteca axis.
8214 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
8215 let err = c.validate_code_paths().unwrap_err();
8216 let ManifestError::CodePathAbsolute { slot, path } = err else {
8217 panic!("expected CodePathAbsolute, got {err:?}");
8218 };
8219 assert_eq!(slot, ":bibliotecas");
8220 assert_eq!(path, PathBuf::from("/etc/passwd"));
8221 }
8222
8223 #[test]
8224 fn validate_code_paths_rejects_absolute_exe_entry() {
8225 let c = caixa_with_code_paths(vec![], vec!["/usr/bin/env"], vec![]);
8226 let err = c.validate_code_paths().unwrap_err();
8227 let ManifestError::CodePathAbsolute { slot, path } = err else {
8228 panic!("expected CodePathAbsolute, got {err:?}");
8229 };
8230 assert_eq!(slot, ":exe");
8231 assert_eq!(path, PathBuf::from("/usr/bin/env"));
8232 }
8233
8234 #[test]
8235 fn validate_code_paths_rejects_absolute_servicos_entry() {
8236 let c = caixa_with_code_paths(vec![], vec![], vec!["/var/servicos/x.yaml"]);
8237 let err = c.validate_code_paths().unwrap_err();
8238 let ManifestError::CodePathAbsolute { slot, path } = err else {
8239 panic!("expected CodePathAbsolute, got {err:?}");
8240 };
8241 assert_eq!(slot, ":servicos");
8242 assert_eq!(path, PathBuf::from("/var/servicos/x.yaml"));
8243 }
8244
8245 #[test]
8246 fn validate_code_paths_rejects_parent_escape_bibliotecas_leading() {
8247 // Canonical "I want a lib from a sibling caixa" footgun on the
8248 // biblioteca axis. `:bibliotecas` has no `starts_with` fence
8249 // downstream, so a leading `..` traverses to the parent of the
8250 // caixa root with no diagnostic at layout time if the resolved
8251 // target exists.
8252 let c = caixa_with_code_paths(vec!["../sibling/x.lisp"], vec![], vec![]);
8253 let err = c.validate_code_paths().unwrap_err();
8254 let ManifestError::CodePathParentEscape { slot, path } = err else {
8255 panic!("expected CodePathParentEscape, got {err:?}");
8256 };
8257 assert_eq!(slot, ":bibliotecas");
8258 assert_eq!(path, PathBuf::from("../sibling/x.lisp"));
8259 }
8260
8261 #[test]
8262 fn validate_code_paths_rejects_parent_escape_exe_mid_path() {
8263 // Mid-path `..` defeats the layout's component-aware
8264 // `starts_with(exe_dir)` fence — `root.join("exe/../../escape")`
8265 // `starts_with(<root>/exe)` is true, but the canonical resolution
8266 // lives outside the caixa root. Caught regardless of where the
8267 // `..` sits — mirrors the peer
8268 // `behavior::validate_rejects_parent_escape_mid_path` pin.
8269 let c = caixa_with_code_paths(vec![], vec!["exe/../../escape"], vec![]);
8270 let err = c.validate_code_paths().unwrap_err();
8271 let ManifestError::CodePathParentEscape { slot, path } = err else {
8272 panic!("expected CodePathParentEscape, got {err:?}");
8273 };
8274 assert_eq!(slot, ":exe");
8275 assert_eq!(path, PathBuf::from("exe/../../escape"));
8276 }
8277
8278 #[test]
8279 fn validate_code_paths_rejects_parent_escape_servicos_trailing() {
8280 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/foo/../../escape.yaml"]);
8281 let err = c.validate_code_paths().unwrap_err();
8282 let ManifestError::CodePathParentEscape { slot, path } = err else {
8283 panic!("expected CodePathParentEscape, got {err:?}");
8284 };
8285 assert_eq!(slot, ":servicos");
8286 assert_eq!(path, PathBuf::from("servicos/foo/../../escape.yaml"));
8287 }
8288
8289 #[test]
8290 fn validate_code_paths_cross_slot_precedence_bibliotecas_before_exe_before_servicos() {
8291 // Cross-slot precedence pin: `:bibliotecas` → `:exe` →
8292 // `:servicos`. A manifest with malformed entries on all three
8293 // surfaces surfaces the `:bibliotecas` defect first, mirroring
8294 // the canonical declaration order
8295 // `Caixa::declared_foreign_code_slots` already establishes for
8296 // the foreign-code-slot diagnostic.
8297 let c = caixa_with_code_paths(vec![""], vec![""], vec![""]);
8298 let err = c.validate_code_paths().unwrap_err();
8299 assert!(
8300 matches!(
8301 err,
8302 ManifestError::CodePathEmpty {
8303 slot: ":bibliotecas"
8304 }
8305 ),
8306 "got {err:?}",
8307 );
8308 }
8309
8310 #[test]
8311 fn validate_code_paths_within_slot_precedence_empty_before_absolute_before_parent_escape() {
8312 // Within-slot precedence pin: empty → absolute → parent-escape,
8313 // matching the [`PathShapeViolation`] arm-ordering every peer
8314 // `is_sandboxed_relative_path` caller follows (b0c8389
8315 // BehaviorSpec, 26da2c7 UpgradeInstruction::StateChange). A
8316 // `:bibliotecas` list whose first entry is empty *and* whose
8317 // later entries are absolute/parent-escape surfaces the empty
8318 // arm first, on the lexicographically-earliest offending entry.
8319 let c = caixa_with_code_paths(vec!["", "/etc/passwd", "../escape.lisp"], vec![], vec![]);
8320 let err = c.validate_code_paths().unwrap_err();
8321 assert!(
8322 matches!(
8323 err,
8324 ManifestError::CodePathEmpty {
8325 slot: ":bibliotecas"
8326 }
8327 ),
8328 "got {err:?}",
8329 );
8330 }
8331
8332 #[test]
8333 fn validate_code_paths_first_offender_per_slot_wins() {
8334 // Within a single slot, the first declaration-order offender
8335 // surfaces — pins that the gate is left-to-right deterministic
8336 // (peer of every `*_first_collision_*` pin on duplicate gates).
8337 let c = caixa_with_code_paths(
8338 vec!["lib/ok.lisp", "/etc/escape", "../also-escape"],
8339 vec![],
8340 vec![],
8341 );
8342 let err = c.validate_code_paths().unwrap_err();
8343 let ManifestError::CodePathAbsolute { slot, path } = err else {
8344 panic!("expected CodePathAbsolute, got {err:?}");
8345 };
8346 assert_eq!(slot, ":bibliotecas");
8347 assert_eq!(path, PathBuf::from("/etc/escape"));
8348 }
8349
8350 #[test]
8351 fn validate_code_paths_diagnostic_carries_offending_slot_and_path() {
8352 // Diagnostic-shape pin (peer with
8353 // `nome_invalid_diagnostic_carries_offending_nome` /
8354 // `versao_invalid_diagnostic_carries_offending_versao`): the
8355 // error's Display surfaces both the offending `:slot` tag and
8356 // the offending path verbatim, so a `feira lint` run can render
8357 // the diagnostic without re-parsing.
8358 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
8359 let rendered = c.validate_code_paths().unwrap_err().to_string();
8360 assert!(
8361 rendered.contains(":bibliotecas"),
8362 "diagnostic must name the offending slot: {rendered}",
8363 );
8364 assert!(
8365 rendered.contains("/etc/passwd"),
8366 "diagnostic must quote the offending path: {rendered}",
8367 );
8368 }
8369
8370 #[test]
8371 fn validate_code_paths_rejects_duplicate_bibliotecas_entry() {
8372 // Canonical copy-paste-the-wrong-file footgun on the biblioteca
8373 // axis. Without the gate `feira build` re-parses the same lib
8374 // twice, wasting work and silently masking the author's intent
8375 // to declare a *second* biblioteca.
8376 let c = caixa_with_code_paths(vec!["lib/demo.lisp", "lib/demo.lisp"], vec![], vec![]);
8377 let err = c.validate_code_paths().unwrap_err();
8378 let ManifestError::CodePathDuplicate { slot, path } = err else {
8379 panic!("expected CodePathDuplicate, got {err:?}");
8380 };
8381 assert_eq!(slot, ":bibliotecas");
8382 assert_eq!(path, PathBuf::from("lib/demo.lisp"));
8383 }
8384
8385 #[test]
8386 fn validate_code_paths_rejects_duplicate_exe_entry() {
8387 // Same footgun on the Binario surface. The future `caixa-flake`
8388 // emitter that materializes each `:exe` entry as a flake
8389 // `packages.<name>` derivation would collide on the duplicate
8390 // package key — surfaced here at the typed-validate layer with a
8391 // self-locating diagnostic instead.
8392 let c = caixa_with_code_paths(vec![], vec!["exe/cli", "exe/cli"], vec![]);
8393 let err = c.validate_code_paths().unwrap_err();
8394 let ManifestError::CodePathDuplicate { slot, path } = err else {
8395 panic!("expected CodePathDuplicate, got {err:?}");
8396 };
8397 assert_eq!(slot, ":exe");
8398 assert_eq!(path, PathBuf::from("exe/cli"));
8399 }
8400
8401 #[test]
8402 fn validate_code_paths_rejects_duplicate_servicos_entry() {
8403 // Same footgun on the Servico surface. The peer caixa-helm /
8404 // caixa-flux renderers refuse `:servicos.len() != 1` with the
8405 // narrower `UnsupportedServicoCount` diagnostic, but that
8406 // diagnostic surfaces "too many servicos" without naming
8407 // "duplicate entry" — the typed self-locating framing only lands
8408 // at this gate.
8409 let c = caixa_with_code_paths(
8410 vec![],
8411 vec![],
8412 vec![
8413 "servicos/demo.computeunit.yaml",
8414 "servicos/demo.computeunit.yaml",
8415 ],
8416 );
8417 let err = c.validate_code_paths().unwrap_err();
8418 let ManifestError::CodePathDuplicate { slot, path } = err else {
8419 panic!("expected CodePathDuplicate, got {err:?}");
8420 };
8421 assert_eq!(slot, ":servicos");
8422 assert_eq!(path, PathBuf::from("servicos/demo.computeunit.yaml"));
8423 }
8424
8425 #[test]
8426 fn validate_code_paths_accepts_same_path_across_slots() {
8427 // Per-list scope pin: a `:bibliotecas` entry that happens to
8428 // collide with an `:exe` or `:servicos` entry as a *string* is
8429 // not a duplicate by this gate (each list gets its own HashSet),
8430 // mirroring the peer `:deps` ↔ `:deps-dev` per-list scope
8431 // (a `:nome` present in both lists is a legitimate dev-vs-runtime
8432 // shape on the dep axis). The structural `starts_with(<exe |
8433 // servicos>_dir)` fence at layout time prevents the realistic
8434 // cross-slot collision case from existing on disk, but the gate's
8435 // per-list scope is correct independent of that downstream fence.
8436 let c = caixa_with_code_paths(
8437 vec!["lib/x.lisp"],
8438 vec!["exe/x"],
8439 vec!["servicos/x.computeunit.yaml"],
8440 );
8441 c.validate_code_paths().unwrap();
8442 }
8443
8444 #[test]
8445 fn validate_code_paths_duplicate_fires_after_structural_checks_on_same_slot() {
8446 // Within-slot ordering pin: structural defects (empty / absolute
8447 // / parent-escape) fire before the duplicate gate on the same
8448 // slot. A `:bibliotecas ("" "lib/x.lisp" "lib/x.lisp")` shape
8449 // surfaces the narrower `CodePathEmpty` for the empty entry
8450 // first, not the duplicate on the later pair — same arm-ordering
8451 // every peer per-list duplicate gate uses (`:etiquetas` 360a499,
8452 // `:autores` 86c769b, `:deps` 359fba5).
8453 let c = caixa_with_code_paths(vec!["", "lib/x.lisp", "lib/x.lisp"], vec![], vec![]);
8454 let err = c.validate_code_paths().unwrap_err();
8455 assert!(
8456 matches!(
8457 err,
8458 ManifestError::CodePathEmpty {
8459 slot: ":bibliotecas"
8460 }
8461 ),
8462 "got {err:?}",
8463 );
8464 }
8465
8466 #[test]
8467 fn validate_code_paths_duplicate_in_bibliotecas_fires_before_duplicate_in_exe() {
8468 // Cross-slot ordering pin on the duplicate arm: `:bibliotecas`
8469 // duplicates surface before `:exe` duplicates, matching the
8470 // canonical `:bibliotecas` → `:exe` → `:servicos` declaration
8471 // order every peer per-slot diagnostic on this surface follows.
8472 let c = caixa_with_code_paths(
8473 vec!["lib/x.lisp", "lib/x.lisp"],
8474 vec!["exe/y", "exe/y"],
8475 vec![],
8476 );
8477 let err = c.validate_code_paths().unwrap_err();
8478 let ManifestError::CodePathDuplicate { slot, path } = err else {
8479 panic!("expected CodePathDuplicate, got {err:?}");
8480 };
8481 assert_eq!(slot, ":bibliotecas");
8482 assert_eq!(path, PathBuf::from("lib/x.lisp"));
8483 }
8484
8485 #[test]
8486 fn validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path() {
8487 // Diagnostic-shape pin (peer with
8488 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
8489 // on the structural arm): the duplicate-arm Display surfaces both
8490 // the offending `:slot` tag and the offending path verbatim, so a
8491 // `feira lint` run can render the diagnostic without re-parsing.
8492 let c = caixa_with_code_paths(
8493 vec![],
8494 vec![],
8495 vec![
8496 "servicos/demo.computeunit.yaml",
8497 "servicos/demo.computeunit.yaml",
8498 ],
8499 );
8500 let rendered = c.validate_code_paths().unwrap_err().to_string();
8501 assert!(
8502 rendered.contains(":servicos"),
8503 "diagnostic must name the offending slot: {rendered}",
8504 );
8505 assert!(
8506 rendered.contains("servicos/demo.computeunit.yaml"),
8507 "diagnostic must quote the offending path: {rendered}",
8508 );
8509 }
8510
8511 // ── validate_code_paths — `.lisp` extension gate on :bibliotecas ──
8512 //
8513 // The lifted [`crate::render::is_lisp_extension`] predicate (33cc830)
8514 // now gates `:bibliotecas` entries on the tatara-lisp-source file-type
8515 // contract. The `feira build` loop (`caixa-feira/src/cmd/build.rs:33`)
8516 // reads every declared `:bibliotecas` entry through `tatara_lisp::read`
8517 // at parse time — the same downstream consumer the peer `:behavior
8518 // :on-*` (c97815a, [`crate::BehaviorError::NonLispExtension`]) and
8519 // `:upgrade-from :state-change :script` (33cc830,
8520 // [`crate::UpgradeError::NonLispExtensionScript`]) axes route through.
8521 // `:exe` and `:servicos` are deliberately excluded — `:exe` is the
8522 // nix-built executable surface (`"exe/<name>"` shape per the canonical
8523 // [`crate::LayoutError::ExeOutsideDir`] error message and every
8524 // in-tree `caixa_with_code_paths` positive control), and `:servicos`
8525 // is the `.computeunit.yaml` ComputeUnit-CR axis.
8526
8527 #[test]
8528 fn validate_code_paths_rejects_no_extension_bibliotecas_entry() {
8529 // Canonical "I dragged the wrong file from the workspace tree"
8530 // footgun on the biblioteca axis. Without the gate `feira build`
8531 // hands the extensionless path to `tatara_lisp::read` and fails
8532 // with a parser-shaped diagnostic far from the source caixa.lisp,
8533 // with no field naming the offending `:bibliotecas` entry.
8534 for relpath in ["lib/demo", "demo", "lib/handlers/inner"] {
8535 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
8536 let err = c.validate_code_paths().unwrap_err();
8537 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
8538 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
8539 };
8540 assert_eq!(slot, ":bibliotecas");
8541 assert_eq!(path, PathBuf::from(relpath));
8542 }
8543 }
8544
8545 #[test]
8546 fn validate_code_paths_rejects_wrong_extension_bibliotecas_entry() {
8547 // Wrong-extension sweep across common authoring footguns. Same
8548 // sweep posture as the peer
8549 // `behavior::validate_rejects_wrong_extension` (c97815a) and
8550 // `upgrade::tests::state_change_rejects_wrong_extension_script`
8551 // (33cc830) cases.
8552 for relpath in [
8553 "lib/demo.rs",
8554 "lib/demo.txt",
8555 "lib/demo.md",
8556 "lib/demo.json",
8557 "lib/demo.yaml",
8558 "lib/demo.toml",
8559 "lib/demo.lisp.bak",
8560 "lib/demo.lispx",
8561 "lib/demo.lis",
8562 ] {
8563 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
8564 let err = c.validate_code_paths().unwrap_err();
8565 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
8566 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
8567 };
8568 assert_eq!(slot, ":bibliotecas");
8569 assert_eq!(path, PathBuf::from(relpath));
8570 }
8571 }
8572
8573 #[test]
8574 fn validate_code_paths_rejects_case_folded_extension_bibliotecas_entry() {
8575 // Case-sensitivity sweep — pins the strict lowercase `.lisp`
8576 // contract. An uppercase `.LISP` shape that the layout's existence
8577 // check would (case-insensitively, on case-insensitive volumes)
8578 // match the on-disk file still mismatches the canonical form the
8579 // codec emits, breaking the THEORY.md §V.2.7 render-determinism
8580 // contract. Mirrors the peer
8581 // `behavior::validate_rejects_case_folded_extension` (c97815a) and
8582 // `upgrade::tests::state_change_rejects_case_folded_extension_script`
8583 // (33cc830) sweeps.
8584 for relpath in [
8585 "lib/demo.LISP",
8586 "lib/demo.Lisp",
8587 "lib/demo.LiSp",
8588 "lib/demo.lISP",
8589 ] {
8590 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
8591 let err = c.validate_code_paths().unwrap_err();
8592 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
8593 panic!("expected CodePathNonLispExtension for {relpath:?}, got {err:?}");
8594 };
8595 assert_eq!(slot, ":bibliotecas");
8596 assert_eq!(path, PathBuf::from(relpath));
8597 }
8598 }
8599
8600 #[test]
8601 fn validate_code_paths_accepts_canonical_lisp_shapes() {
8602 // Positive-control sweep through every canonical authoring shape
8603 // every in-tree fixture and the `Caixa::template` scaffold use.
8604 // Mirrors the peer `behavior::validate_accepts_canonical_lisp_paths`
8605 // (c97815a) and the lifted predicate's own
8606 // `is_lisp_extension_accepts_canonical_shapes` sweep in render.rs
8607 // (33cc830).
8608 for relpath in [
8609 "lib/demo.lisp",
8610 "lib/handlers.lisp",
8611 "lib/migrations/v01-to-v02.lisp",
8612 "demo.lisp",
8613 "a.lisp",
8614 "./lib/demo.lisp",
8615 "lib/./handlers.lisp",
8616 "lib/migrations/v.0.1.lisp",
8617 ] {
8618 let c = caixa_with_code_paths(vec![relpath], vec![], vec![]);
8619 c.validate_code_paths()
8620 .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
8621 }
8622 }
8623
8624 #[test]
8625 fn validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos() {
8626 // The file-type gate is per-slot — only `:bibliotecas` carries the
8627 // tatara-lisp-source contract. An extensionless `:exe` entry
8628 // (`exe/demo`) and a `.computeunit.yaml` `:servicos` entry are the
8629 // canonical shapes every in-tree fixture uses, and must continue
8630 // to pass validate. Pins that a future tightening that broadens
8631 // the `.lisp` gate to either axis surfaces as a test failure
8632 // rather than as a silent breaking change to existing valid
8633 // manifests.
8634 let c = caixa_with_code_paths(
8635 vec![],
8636 vec!["exe/demo", "exe/tool"],
8637 vec!["servicos/demo.computeunit.yaml"],
8638 );
8639 c.validate_code_paths().unwrap();
8640 }
8641
8642 #[test]
8643 fn validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension() {
8644 // Cross-arm precedence pin: a `:bibliotecas` entry that is *both*
8645 // sandbox-escaping and non-`.lisp` surfaces the more fundamental
8646 // sandbox-shape diagnostic first (the `.lisp` remediation would
8647 // be misleading when the offending path can never resolve under
8648 // the caixa root anyway). Mirrors the peer
8649 // `EmptyPath` → `AbsolutePath` → `ParentEscape` → `NonLispExtension`
8650 // ordering on `:behavior :on-*` (c97815a) and `EmptyScript` →
8651 // `AbsoluteScript` → `ParentEscapeScript` → `NonLispExtensionScript`
8652 // on `:upgrade-from :state-change :script` (33cc830).
8653 //
8654 // Empty wins (the strictly-smaller-scope structural arm).
8655 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
8656 assert!(
8657 matches!(
8658 c.validate_code_paths().unwrap_err(),
8659 ManifestError::CodePathEmpty {
8660 slot: ":bibliotecas"
8661 }
8662 ),
8663 "empty must win over non-lisp-extension",
8664 );
8665 // Absolute wins (the path can't resolve under the caixa root).
8666 let c = caixa_with_code_paths(vec!["/etc/passwd"], vec![], vec![]);
8667 let err = c.validate_code_paths().unwrap_err();
8668 let ManifestError::CodePathAbsolute { slot, .. } = err else {
8669 panic!("absolute must win over non-lisp-extension, got {err:?}");
8670 };
8671 assert_eq!(slot, ":bibliotecas");
8672 // ParentEscape wins (the path escapes the caixa root).
8673 let c = caixa_with_code_paths(vec!["../sibling/x.txt"], vec![], vec![]);
8674 let err = c.validate_code_paths().unwrap_err();
8675 let ManifestError::CodePathParentEscape { slot, .. } = err else {
8676 panic!("parent-escape must win over non-lisp-extension, got {err:?}");
8677 };
8678 assert_eq!(slot, ":bibliotecas");
8679 }
8680
8681 #[test]
8682 fn validate_code_paths_non_lisp_extension_precedes_duplicate() {
8683 // Within-slot precedence pin: the per-entry file-type shape gate
8684 // fires before the cross-entry duplicate gate, so the narrower
8685 // structural defect dominates the uniqueness diagnostic. A
8686 // `("lib/x.txt" "lib/x.txt")` shape surfaces
8687 // `CodePathNonLispExtension` on the first entry rather than
8688 // `CodePathDuplicate` on the pair — same posture every per-entry
8689 // shape-gate-precedes-duplicate cascade follows on this surface
8690 // (the empty / absolute / parent-escape arms already precede the
8691 // duplicate arm; the lifted file-type arm joins that set).
8692 let c = caixa_with_code_paths(vec!["lib/x.txt", "lib/x.txt"], vec![], vec![]);
8693 let err = c.validate_code_paths().unwrap_err();
8694 let ManifestError::CodePathNonLispExtension { slot, path } = err else {
8695 panic!("expected CodePathNonLispExtension, got {err:?}");
8696 };
8697 assert_eq!(slot, ":bibliotecas");
8698 assert_eq!(path, PathBuf::from("lib/x.txt"));
8699 }
8700
8701 #[test]
8702 fn validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path() {
8703 // Diagnostic-shape pin (peer with
8704 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`
8705 // on the sandbox-shape arms and
8706 // `validate_code_paths_duplicate_diagnostic_carries_offending_slot_and_path`
8707 // on the duplicate arm): the file-type-arm Display surfaces both
8708 // the offending `:slot` tag, the offending path verbatim, and the
8709 // expected `.lisp` extension named in the remediation text, so a
8710 // `feira lint` run can render the diagnostic without re-parsing.
8711 let c = caixa_with_code_paths(vec!["lib/demo.rs"], vec![], vec![]);
8712 let rendered = c.validate_code_paths().unwrap_err().to_string();
8713 assert!(
8714 rendered.contains(":bibliotecas"),
8715 "diagnostic must name the offending slot: {rendered}",
8716 );
8717 assert!(
8718 rendered.contains("lib/demo.rs"),
8719 "diagnostic must quote the offending path: {rendered}",
8720 );
8721 assert!(
8722 rendered.contains(".lisp"),
8723 "diagnostic must name the expected extension: {rendered}",
8724 );
8725 }
8726
8727 // ── validate_code_paths — `.computeunit.yaml` compound-suffix gate on :servicos ──
8728 //
8729 // The lifted [`crate::render::is_computeunit_yaml_extension`] predicate
8730 // now gates `:servicos` entries on the ComputeUnit-CR YAML file-type
8731 // contract. The peer caixa-helm / caixa-flux renderers consume each
8732 // `:servicos` entry through `serde_yaml::from_str` as a typed
8733 // `ComputeUnit` CR — same downstream-consumer-shape lift as the peer
8734 // `:bibliotecas` `.lisp` gate (64772a9), here on the compound-suffix
8735 // axis `Path::extension` can't express on its own.
8736
8737 #[test]
8738 fn validate_code_paths_rejects_no_extension_servicos_entry() {
8739 // Canonical "I dragged the wrong file from the workspace tree"
8740 // footgun on the Servico axis. Without the gate the peer
8741 // caixa-helm / caixa-flux renderers hand the extensionless path
8742 // to `serde_yaml::from_str` and fail with a parser-shaped
8743 // diagnostic far from the source caixa.lisp, with no field
8744 // naming the offending `:servicos` entry.
8745 for relpath in ["servicos/demo", "demo", "servicos/sub/nested"] {
8746 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8747 let err = c.validate_code_paths().unwrap_err();
8748 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8749 panic!(
8750 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
8751 got {err:?}"
8752 );
8753 };
8754 assert_eq!(slot, ":servicos");
8755 assert_eq!(path, PathBuf::from(relpath));
8756 }
8757 }
8758
8759 #[test]
8760 fn validate_code_paths_rejects_wrong_extension_servicos_entry() {
8761 // Wrong-extension sweep across common authoring footguns on the
8762 // Servico axis. Bare `.yaml` is the canonical "I forgot the
8763 // `.computeunit` segment" typo; the off-by-one-segment shapes
8764 // (`.computeunit-yaml` / `.computeunit_yaml`) silently pass the
8765 // bare `Path::extension` view but mismatch the typed compound
8766 // suffix the renderers' `serde_yaml::from_str` consumer demands.
8767 // Same sweep-posture as the peer
8768 // `validate_code_paths_rejects_wrong_extension_bibliotecas_entry`
8769 // (64772a9) on the sibling tatara-lisp-source axis.
8770 for relpath in [
8771 "servicos/demo.yaml",
8772 "servicos/demo.yml",
8773 "servicos/demo.json",
8774 "servicos/demo.toml",
8775 "servicos/demo.txt",
8776 "servicos/demo.computeunit.yaml.bak",
8777 "servicos/demo.computeunit.yam",
8778 "servicos/demo.computeunit",
8779 "servicos/demo-computeunit.yaml",
8780 "servicos/demo_computeunit.yaml",
8781 ] {
8782 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8783 let err = c.validate_code_paths().unwrap_err();
8784 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8785 panic!(
8786 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
8787 got {err:?}"
8788 );
8789 };
8790 assert_eq!(slot, ":servicos");
8791 assert_eq!(path, PathBuf::from(relpath));
8792 }
8793 }
8794
8795 #[test]
8796 fn validate_code_paths_rejects_case_folded_extension_servicos_entry() {
8797 // Case-sensitivity sweep — pins the strict lowercase
8798 // `.computeunit.yaml` contract. A case-folded shape that the
8799 // layout's existence check would (case-insensitively, on
8800 // case-insensitive volumes) match the on-disk file still
8801 // mismatches the canonical form the codec emits, breaking the
8802 // THEORY.md §V.2.7 render-determinism contract. Mirrors the peer
8803 // `validate_code_paths_rejects_case_folded_extension_bibliotecas_entry`
8804 // (64772a9) sweep on the sibling tatara-lisp-source axis.
8805 for relpath in [
8806 "servicos/demo.ComputeUnit.yaml",
8807 "servicos/demo.COMPUTEUNIT.yaml",
8808 "servicos/demo.computeunit.YAML",
8809 "servicos/demo.computeunit.Yaml",
8810 "servicos/demo.COMPUTEUNIT.YAML",
8811 ] {
8812 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8813 let err = c.validate_code_paths().unwrap_err();
8814 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8815 panic!(
8816 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
8817 got {err:?}"
8818 );
8819 };
8820 assert_eq!(slot, ":servicos");
8821 assert_eq!(path, PathBuf::from(relpath));
8822 }
8823 }
8824
8825 #[test]
8826 fn validate_code_paths_rejects_empty_stem_servicos_entry() {
8827 // Degenerate hidden-file shape: a file name exactly equal to the
8828 // suffix (`.computeunit.yaml` — no stem preceding the suffix) is
8829 // the structural "Servico declared with no identity" footgun.
8830 // The substrate identifies each ComputeUnit by the file-stem
8831 // segment that precedes `.computeunit.yaml` (the rendered
8832 // `lareira-<stem>` Helm chart, the per-Servico `metadata.name`,
8833 // the M3 `:contratos` membership lookup), so an empty stem
8834 // leaves the Servico unidentifiable. Pinned at the typed-axis
8835 // level so a future regression that drops the `name.len() >
8836 // SUFFIX.len()` bound at the predicate surfaces here, not
8837 // piecemeal as a `lareira-` chart-name collision at render time.
8838 for relpath in ["servicos/.computeunit.yaml"] {
8839 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8840 let err = c.validate_code_paths().unwrap_err();
8841 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8842 panic!(
8843 "expected CodePathNonComputeUnitYamlExtension for {relpath:?}, \
8844 got {err:?}"
8845 );
8846 };
8847 assert_eq!(slot, ":servicos");
8848 assert_eq!(path, PathBuf::from(relpath));
8849 }
8850 }
8851
8852 #[test]
8853 fn validate_code_paths_accepts_canonical_computeunit_yaml_shapes() {
8854 // Positive-control sweep through every canonical authoring shape
8855 // every in-tree fixture and the `Caixa::template` scaffold use.
8856 // Mirrors the peer
8857 // `validate_code_paths_accepts_canonical_lisp_shapes` (64772a9)
8858 // and the lifted predicate's own
8859 // `computeunit_yaml_extension_accepts_canonical_shapes` sweep in
8860 // render.rs.
8861 for relpath in [
8862 "servicos/demo.computeunit.yaml",
8863 "servicos/hello-rio.computeunit.yaml",
8864 "servicos/my-service.computeunit.yaml",
8865 "servicos/a.computeunit.yaml",
8866 "./servicos/demo.computeunit.yaml",
8867 "servicos/./demo.computeunit.yaml",
8868 "servicos/sub/nested.computeunit.yaml",
8869 "servicos/v0.1.computeunit.yaml",
8870 ] {
8871 let c = caixa_with_code_paths(vec![], vec![], vec![relpath]);
8872 c.validate_code_paths()
8873 .unwrap_or_else(|e| panic!("canonical shape {relpath:?} must pass, got {e:?}"));
8874 }
8875 }
8876
8877 #[test]
8878 fn validate_code_paths_non_computeunit_yaml_extension_does_not_fire_on_bibliotecas_or_exe() {
8879 // The file-type gate is per-slot — only `:servicos` carries the
8880 // ComputeUnit-CR YAML contract. A canonical `.lisp` `:bibliotecas`
8881 // entry and an extensionless `:exe` entry are the canonical
8882 // shapes every in-tree fixture uses, and must continue to pass
8883 // validate. Peer of
8884 // `validate_code_paths_non_lisp_extension_does_not_fire_on_exe_or_servicos`
8885 // (64772a9) — together pin that the typed
8886 // [`CodePathFileType`] dispatch is exhaustively per-slot, with no
8887 // cross-axis leakage in either direction.
8888 let c = caixa_with_code_paths(
8889 vec!["lib/demo.lisp"],
8890 vec!["exe/demo", "exe/tool"],
8891 vec!["servicos/demo.computeunit.yaml"],
8892 );
8893 c.validate_code_paths().unwrap();
8894 }
8895
8896 #[test]
8897 fn validate_code_paths_sandbox_shape_arms_precede_non_computeunit_yaml_extension() {
8898 // Cross-arm precedence pin: a `:servicos` entry that is *both*
8899 // sandbox-escaping and wrong-extension surfaces the more
8900 // fundamental sandbox-shape diagnostic first (the
8901 // `.computeunit.yaml` remediation would be misleading when the
8902 // offending path can never resolve under the caixa root
8903 // anyway). Mirrors the peer
8904 // `validate_code_paths_sandbox_shape_arms_precede_non_lisp_extension`
8905 // (64772a9) ordering on the sibling `:bibliotecas` axis and the
8906 // peer `EmptyPath` → `AbsolutePath` → `ParentEscape` →
8907 // `NonComputeUnitYamlExtension` arm-ordering the dispatch
8908 // table establishes.
8909 //
8910 // Empty wins (the strictly-smaller-scope structural arm).
8911 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
8912 assert!(
8913 matches!(
8914 c.validate_code_paths().unwrap_err(),
8915 ManifestError::CodePathEmpty { slot: ":servicos" }
8916 ),
8917 "empty must win over non-computeunit-yaml-extension",
8918 );
8919 // Absolute wins (the path can't resolve under the caixa root).
8920 let c = caixa_with_code_paths(vec![], vec![], vec!["/etc/foo.yaml"]);
8921 let err = c.validate_code_paths().unwrap_err();
8922 let ManifestError::CodePathAbsolute { slot, .. } = err else {
8923 panic!("absolute must win over non-computeunit-yaml-extension, got {err:?}");
8924 };
8925 assert_eq!(slot, ":servicos");
8926 // ParentEscape wins (the path escapes the caixa root).
8927 let c = caixa_with_code_paths(vec![], vec![], vec!["../sibling/x.yaml"]);
8928 let err = c.validate_code_paths().unwrap_err();
8929 let ManifestError::CodePathParentEscape { slot, .. } = err else {
8930 panic!("parent-escape must win over non-computeunit-yaml-extension, got {err:?}");
8931 };
8932 assert_eq!(slot, ":servicos");
8933 }
8934
8935 #[test]
8936 fn validate_code_paths_non_computeunit_yaml_extension_precedes_duplicate() {
8937 // Within-slot precedence pin: the per-entry file-type shape gate
8938 // fires before the cross-entry duplicate gate, so the narrower
8939 // structural defect dominates the uniqueness diagnostic. A
8940 // `("servicos/x.yaml" "servicos/x.yaml")` shape surfaces
8941 // `CodePathNonComputeUnitYamlExtension` on the first entry
8942 // rather than `CodePathDuplicate` on the pair — same posture
8943 // every per-entry shape-gate-precedes-duplicate cascade follows
8944 // on this surface, peer of the 64772a9 `:bibliotecas`
8945 // `("lib/x.txt" "lib/x.txt")` ordering.
8946 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/x.yaml", "servicos/x.yaml"]);
8947 let err = c.validate_code_paths().unwrap_err();
8948 let ManifestError::CodePathNonComputeUnitYamlExtension { slot, path } = err else {
8949 panic!("expected CodePathNonComputeUnitYamlExtension, got {err:?}");
8950 };
8951 assert_eq!(slot, ":servicos");
8952 assert_eq!(path, PathBuf::from("servicos/x.yaml"));
8953 }
8954
8955 #[test]
8956 fn validate_code_paths_non_computeunit_yaml_extension_diagnostic_carries_offending_slot_and_path()
8957 {
8958 // Diagnostic-shape pin (peer with
8959 // `validate_code_paths_non_lisp_extension_diagnostic_carries_offending_slot_and_path`
8960 // on the sibling tatara-lisp-source axis): the file-type-arm
8961 // Display surfaces both the offending `:slot` tag, the
8962 // offending path verbatim, and the expected
8963 // `.computeunit.yaml` compound suffix named in the remediation
8964 // text, so a `feira lint` run can render the diagnostic without
8965 // re-parsing.
8966 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.yaml"]);
8967 let rendered = c.validate_code_paths().unwrap_err().to_string();
8968 assert!(
8969 rendered.contains(":servicos"),
8970 "diagnostic must name the offending slot: {rendered}",
8971 );
8972 assert!(
8973 rendered.contains("servicos/demo.yaml"),
8974 "diagnostic must quote the offending path: {rendered}",
8975 );
8976 assert!(
8977 rendered.contains(".computeunit.yaml"),
8978 "diagnostic must name the expected compound suffix: {rendered}",
8979 );
8980 }
8981
8982 // ── validate_etiquetas — universal-axis registry-search-tag shape ──
8983
8984 fn caixa_with_etiquetas(etiquetas: Vec<&str>) -> Caixa {
8985 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
8986 c.etiquetas = etiquetas.into_iter().map(String::from).collect();
8987 c
8988 }
8989
8990 #[test]
8991 fn validate_etiquetas_accepts_empty_list() {
8992 // The empty-list identity: every caixa with no declared tags
8993 // trivially passes — `Caixa::template` emits `:etiquetas ()`,
8994 // so the gate is non-disruptive against every existing manifest.
8995 let c = caixa_with_etiquetas(vec![]);
8996 c.validate_etiquetas().unwrap();
8997 }
8998
8999 #[test]
9000 fn validate_etiquetas_accepts_canonical_forms() {
9001 // Positive control sweep: a canonical-shaped non-empty distinct
9002 // tag list passes, mirroring the example checkout-aplicacao
9003 // (`:etiquetas ("example" "aplicacao" "mesh" "ecommerce" "demo")`)
9004 // and the hello-rio fixture (`("hello-world" "wasm" "rust")`).
9005 let c = caixa_with_etiquetas(vec!["example", "aplicacao", "mesh", "ecommerce", "demo"]);
9006 c.validate_etiquetas().unwrap();
9007 }
9008
9009 #[test]
9010 fn validate_etiquetas_rejects_empty_entry() {
9011 // Canonical paste-from-blank-doc footgun. Without the gate the
9012 // empty entry rendered as `keywords: [""]` in `Chart.yaml`, a
9013 // no-op tag indexing nothing in the future caixa-registry.
9014 let c = caixa_with_etiquetas(vec![""]);
9015 let err = c.validate_etiquetas().unwrap_err();
9016 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
9017 }
9018
9019 #[test]
9020 fn validate_etiquetas_rejects_duplicate_entry() {
9021 // Canonical copy-paste-the-wrong-tag footgun. Without the gate
9022 // the duplicate was silently dedup'd by caixa-helm's BTreeSet
9023 // collect at chart render — a "second wins / one silently
9024 // disappears" shape divergent from every peer typed-graph set
9025 // gate. The duplicate-arm names the offending tag verbatim.
9026 let c = caixa_with_etiquetas(vec!["demo", "demo"]);
9027 let err = c.validate_etiquetas().unwrap_err();
9028 let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
9029 panic!("expected EtiquetaDuplicate, got {err:?}");
9030 };
9031 assert_eq!(etiqueta, "demo");
9032 }
9033
9034 #[test]
9035 fn validate_etiquetas_empty_takes_precedence_over_duplicate() {
9036 // Empty-first cascade pin: `("" "demo" "demo")` surfaces
9037 // `EtiquetaEmpty` not `EtiquetaDuplicate` — the narrower
9038 // structural "this entry has no value" defect dominates the
9039 // cross-entry uniqueness diagnostic. Mirrors the peer
9040 // empty-before-duplicate cascades on `:caracteristicas`
9041 // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
9042 // fc3b4d5) and `:membros :caixa` (`MembroCaixaEmpty` before
9043 // `MembroDuplicate`).
9044 let c = caixa_with_etiquetas(vec!["", "demo", "demo"]);
9045 let err = c.validate_etiquetas().unwrap_err();
9046 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
9047 }
9048
9049 #[test]
9050 fn validate_etiquetas_duplicate_reports_first_collision() {
9051 // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
9052 // duplicate (the lexicographically-earliest offending position
9053 // — the second `"a"` at index 2 collides with the first `"a"`
9054 // at index 0), not the later `"b"` collision at index 3,
9055 // peer with every other first-collision diagnostic posture on
9056 // this surface (`validate_load_singularity_reports_first_collision`,
9057 // `validate_cleanup_singularity_reports_first_collision`).
9058 let c = caixa_with_etiquetas(vec!["a", "b", "a", "b"]);
9059 let err = c.validate_etiquetas().unwrap_err();
9060 let ManifestError::EtiquetaDuplicate { etiqueta } = err else {
9061 panic!("expected EtiquetaDuplicate, got {err:?}");
9062 };
9063 assert_eq!(etiqueta, "a");
9064 }
9065
9066 #[test]
9067 fn validate_etiquetas_case_sensitive() {
9068 // Case-sensitivity pin: `("Foo" "foo")` is two distinct entries,
9069 // mirroring the peer `:membros :caixa` / `:children :caixa`
9070 // exact-string-match discipline. The shape gate this routine
9071 // landed (`is_chart_keyword_shape`, Cargo crates.io keyword
9072 // grammar) accepts mixed case — crates.io's keyword rule is
9073 // "case-insensitive" at the index layer but admits mixed case
9074 // at the entry layer (the canonical Helm chart `keywords:`
9075 // shape is lowercase by convention, but the grammar admits
9076 // uppercase). Case-sensitivity at the duplicate-set layer
9077 // remains structural — two distinct strings are two distinct
9078 // entries.
9079 let c = caixa_with_etiquetas(vec!["Foo", "foo"]);
9080 c.validate_etiquetas().unwrap();
9081 }
9082
9083 #[test]
9084 fn validate_etiquetas_diagnostic_carries_offending_tag() {
9085 // Diagnostic-shape pin (peer with
9086 // `validate_code_paths_diagnostic_carries_offending_slot_and_path`):
9087 // the error's Display surfaces the offending tag verbatim, so a
9088 // `feira lint` run can render the diagnostic without re-parsing
9089 // and the author can grep their caixa.lisp for the offending
9090 // value.
9091 let c = caixa_with_etiquetas(vec!["demo", "demo"]);
9092 let rendered = c.validate_etiquetas().unwrap_err().to_string();
9093 assert!(
9094 rendered.contains(":etiquetas"),
9095 "diagnostic must name the offending slot: {rendered}",
9096 );
9097 assert!(
9098 rendered.contains("demo"),
9099 "diagnostic must quote the offending tag: {rendered}",
9100 );
9101 }
9102
9103 #[test]
9104 fn validate_etiquetas_rejects_leading_whitespace_entry() {
9105 // Canonical paste-from-aligned-doc footgun. Without the shape
9106 // gate `" mesh"` silently passed validate and landed as a
9107 // YAML plain-style scalar with leading whitespace in the
9108 // rendered Chart.yaml `keywords:` array — every YAML 1.2
9109 // dumper trims leading whitespace from plain-style scalars,
9110 // so the authored space round-tripped inconsistently back
9111 // through `caixa.lisp`. Mirrors the peer
9112 // `validate_autores_rejects_leading_whitespace_entry`.
9113 let c = caixa_with_etiquetas(vec![" mesh"]);
9114 let err = c.validate_etiquetas().unwrap_err();
9115 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9116 panic!("expected EtiquetaInvalid, got {err:?}");
9117 };
9118 assert_eq!(etiqueta, " mesh");
9119 assert!(reason.contains("whitespace"), "got: {reason}");
9120 }
9121
9122 #[test]
9123 fn validate_etiquetas_rejects_embedded_newline_entry() {
9124 // Canonical paste-from-multiline-doc footgun — the author
9125 // pasted a multi-tag block into one `:etiquetas` entry
9126 // instead of splitting into one entry per tag. Without the
9127 // shape gate `"mesh\nhttp"` silently passed validate and
9128 // landed as a YAML-illegal multi-line scalar in the rendered
9129 // Chart.yaml `keywords:` array.
9130 let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
9131 let err = c.validate_etiquetas().unwrap_err();
9132 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9133 panic!("expected EtiquetaInvalid, got {err:?}");
9134 };
9135 assert_eq!(etiqueta, "mesh\nhttp");
9136 assert!(reason.contains("newline"), "got: {reason}");
9137 }
9138
9139 #[test]
9140 fn validate_etiquetas_rejects_embedded_comma_entry() {
9141 // Canonical CSV-list-separator-confusion footgun: the author
9142 // confused the CSV-style separator convention with the
9143 // `:etiquetas` list grammar. Without the shape gate
9144 // `"mesh,http,grpc"` silently passed validate and landed as a
9145 // single malformed search tag in the rendered Chart.yaml
9146 // `keywords:` array — Artifact Hub's keyword index would
9147 // either silently drop the tag or index it as
9148 // `mesh,http,grpc` instead of three separate tags.
9149 let c = caixa_with_etiquetas(vec!["mesh,http,grpc"]);
9150 let err = c.validate_etiquetas().unwrap_err();
9151 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9152 panic!("expected EtiquetaInvalid, got {err:?}");
9153 };
9154 assert_eq!(etiqueta, "mesh,http,grpc");
9155 assert!(reason.contains('`'), "got: {reason}");
9156 assert!(reason.contains(','), "got: {reason}");
9157 }
9158
9159 #[test]
9160 fn validate_etiquetas_rejects_embedded_slash_entry() {
9161 // Canonical path-separator-confusion footgun: the author
9162 // confused namespace-path notation with the keyword grammar.
9163 let c = caixa_with_etiquetas(vec!["caixa/servico"]);
9164 let err = c.validate_etiquetas().unwrap_err();
9165 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9166 panic!("expected EtiquetaInvalid, got {err:?}");
9167 };
9168 assert_eq!(etiqueta, "caixa/servico");
9169 assert!(reason.contains('/'), "got: {reason}");
9170 }
9171
9172 #[test]
9173 fn validate_etiquetas_rejects_leading_digit_entry() {
9174 // Canonical paste-from-numbered-list footgun: the author
9175 // copied `1. mesh` from a numbered doc and the `1` leaked
9176 // into the tag.
9177 let c = caixa_with_etiquetas(vec!["1mesh"]);
9178 let err = c.validate_etiquetas().unwrap_err();
9179 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9180 panic!("expected EtiquetaInvalid, got {err:?}");
9181 };
9182 assert_eq!(etiqueta, "1mesh");
9183 assert!(reason.contains("digit"), "got: {reason}");
9184 }
9185
9186 #[test]
9187 fn validate_etiquetas_rejects_leading_hyphen_entry() {
9188 // Canonical kebab-leak footgun.
9189 let c = caixa_with_etiquetas(vec!["-foo"]);
9190 let err = c.validate_etiquetas().unwrap_err();
9191 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9192 panic!("expected EtiquetaInvalid, got {err:?}");
9193 };
9194 assert_eq!(etiqueta, "-foo");
9195 assert!(reason.contains('-'), "got: {reason}");
9196 }
9197
9198 #[test]
9199 fn validate_etiquetas_rejects_non_ascii_entry() {
9200 // Canonical paste-from-Unicode-doc footgun. Every legitimate
9201 // search tag is strict ASCII; raw non-ASCII silently
9202 // round-trips inconsistently across NFC/NFD normalization on
9203 // APFS / case-folding filesystems and breaks the Artifact Hub
9204 // keyword search index lookup.
9205 let c = caixa_with_etiquetas(vec!["café"]);
9206 let err = c.validate_etiquetas().unwrap_err();
9207 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9208 panic!("expected EtiquetaInvalid, got {err:?}");
9209 };
9210 assert_eq!(etiqueta, "café");
9211 assert!(reason.contains("non-ASCII"), "got: {reason}");
9212 }
9213
9214 #[test]
9215 fn validate_etiquetas_rejects_period_entry() {
9216 // Canonical namespace-confusion / version-suffix footgun
9217 // (`"http.1"` / `"v1.0"`): Cargo's crates.io keyword grammar
9218 // excludes `.` from the continuation set even though the
9219 // sibling `:caracteristicas` axis (Cargo's feature-name
9220 // grammar) admits it. Tighter than the sibling axis, peer
9221 // with Cargo's own crates.io keyword shape.
9222 let c = caixa_with_etiquetas(vec!["http.1"]);
9223 let err = c.validate_etiquetas().unwrap_err();
9224 let ManifestError::EtiquetaInvalid { etiqueta, reason } = err else {
9225 panic!("expected EtiquetaInvalid, got {err:?}");
9226 };
9227 assert_eq!(etiqueta, "http.1");
9228 assert!(reason.contains('.'), "got: {reason}");
9229 }
9230
9231 #[test]
9232 fn validate_etiquetas_empty_takes_precedence_over_shape() {
9233 // Per-entry empty-first cascade pin: an entry that is both
9234 // empty *and* shape-invalid surfaces `EtiquetaEmpty` (the
9235 // narrower "this entry has no value" structural defect
9236 // dominates the broader shape-predicate diagnostic). The
9237 // empty arm fires before the shape predicate is consulted,
9238 // mirroring the peer `validate_autores_empty_takes_precedence_over_shape`
9239 // cascade established on the sibling universal-axis Vec<String>
9240 // surface.
9241 let c = caixa_with_etiquetas(vec![""]);
9242 let err = c.validate_etiquetas().unwrap_err();
9243 assert!(matches!(err, ManifestError::EtiquetaEmpty), "got {err:?}",);
9244 }
9245
9246 #[test]
9247 fn validate_etiquetas_shape_takes_precedence_over_duplicate() {
9248 // Per-entry shape-before-cross-entry-duplicate cascade pin: an
9249 // entry that is malformed surfaces `EtiquetaInvalid` even when
9250 // a later entry would have collided on duplicate. The
9251 // per-entry shape arm fires inside the same loop iteration as
9252 // the empty arm, before the seen-set insert at end-of-iteration
9253 // — structural per-entry defects dominate the cross-entry
9254 // uniqueness diagnostic. Mirrors the peer
9255 // `validate_autores_shape_takes_precedence_over_duplicate`.
9256 let c = caixa_with_etiquetas(vec!["mesh\nhttp", "mesh\nhttp"]);
9257 let err = c.validate_etiquetas().unwrap_err();
9258 assert!(
9259 matches!(err, ManifestError::EtiquetaInvalid { .. }),
9260 "got {err:?}",
9261 );
9262 }
9263
9264 #[test]
9265 fn validate_etiquetas_invalid_diagnostic_names_offending_slot_and_value() {
9266 // Diagnostic-shape pin on the new shape arm (peer with
9267 // `validate_autores_invalid_diagnostic_names_offending_slot_and_value`):
9268 // the rendered Display surfaces both the offending slot name
9269 // and the offending value verbatim, so a `feira lint` run
9270 // points the author at the exact `:etiquetas` entry to fix.
9271 let c = caixa_with_etiquetas(vec!["mesh\nhttp"]);
9272 let rendered = c.validate_etiquetas().unwrap_err().to_string();
9273 assert!(
9274 rendered.contains(":etiquetas"),
9275 "diagnostic must name the offending slot: {rendered}",
9276 );
9277 assert!(
9278 rendered.contains("mesh\\nhttp"),
9279 "diagnostic must quote the offending value (debug-escaped): {rendered}",
9280 );
9281 }
9282
9283 #[test]
9284 fn validate_etiquetas_rejects_at_21_byte_boundary() {
9285 // The 20-byte cap pin — boundary-exceeding case rejected,
9286 // boundary-accepting case passes. Mirrors the peer
9287 // `chart_keyword_shape_rejects_at_21_byte_boundary` substrate-
9288 // side pin, surfaced at the per-axis caller so the cap
9289 // propagates through validate end-to-end. Constructed as a
9290 // single all-`a` token so only the cap arm fires.
9291 let max_ok = "a".repeat(20);
9292 let c = caixa_with_etiquetas(vec![max_ok.as_str()]);
9293 c.validate_etiquetas().unwrap();
9294 let too_long = "a".repeat(21);
9295 let c = caixa_with_etiquetas(vec![too_long.as_str()]);
9296 let err = c.validate_etiquetas().unwrap_err();
9297 let ManifestError::EtiquetaInvalid { reason, .. } = err else {
9298 panic!("expected EtiquetaInvalid, got {err:?}");
9299 };
9300 assert!(reason.contains("20"), "got: {reason}");
9301 assert!(reason.contains("21"), "got: {reason}");
9302 }
9303
9304 #[test]
9305 fn validate_etiquetas_accepts_canonical_shaped_forms() {
9306 // Positive control sweep: every canonical-shaped tag from the
9307 // hello-rio / checkout-aplicacao / pangea-tatara-akeyless
9308 // example fixtures plus the substrate-fixed tags caixa-helm
9309 // unions in at chart render. Drift between this list and the
9310 // substrate-side `chart_keyword_shape_accepts_canonical_forms`
9311 // sweep surfaces here — one source of truth for the rule.
9312 let c = caixa_with_etiquetas(vec![
9313 "example",
9314 "aplicacao",
9315 "mesh",
9316 "ecommerce",
9317 "demo",
9318 "infrastructure",
9319 "aws",
9320 "akeyless",
9321 "pangea-native",
9322 "hello-world",
9323 "wasm",
9324 "rust",
9325 "tatara-lisp",
9326 "caixa-servico",
9327 "lareira",
9328 ]);
9329 c.validate_etiquetas().unwrap();
9330 }
9331
9332 // ── validate_autores — universal-axis maintainer shape ────────────
9333
9334 fn caixa_with_autores(autores: Vec<&str>) -> Caixa {
9335 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9336 c.autores = autores.into_iter().map(String::from).collect();
9337 c
9338 }
9339
9340 #[test]
9341 fn validate_autores_accepts_empty_list() {
9342 // The empty-list identity: `Caixa::template` emits `:autores ()`,
9343 // so the gate is non-disruptive against every existing manifest.
9344 let c = caixa_with_autores(vec![]);
9345 c.validate_autores().unwrap();
9346 }
9347
9348 #[test]
9349 fn validate_autores_accepts_canonical_forms() {
9350 // Positive control sweep: every canonical-shaped non-empty
9351 // distinct maintainer list passes — the hello-rio / checkout-
9352 // aplicacao fixtures' `:autores ("pleme-io")` shape, plus the
9353 // multi-author shape downstream packaging surfaces emit.
9354 let c = caixa_with_autores(vec!["pleme-io"]);
9355 c.validate_autores().unwrap();
9356 let c = caixa_with_autores(vec!["alice <alice@example.com>", "bob <bob@example.com>"]);
9357 c.validate_autores().unwrap();
9358 }
9359
9360 #[test]
9361 fn validate_autores_rejects_empty_entry() {
9362 // Canonical paste-from-blank-doc footgun. Without the gate the
9363 // empty entry rendered as `maintainers: [{name: "", email: null}]`
9364 // in `Chart.yaml`, a no-op maintainer the substrate cannot route
9365 // to.
9366 let c = caixa_with_autores(vec![""]);
9367 let err = c.validate_autores().unwrap_err();
9368 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
9369 }
9370
9371 #[test]
9372 fn validate_autores_rejects_duplicate_entry() {
9373 // Canonical copy-paste-the-wrong-author footgun. Unlike the
9374 // `:etiquetas` peer (caixa-helm's `BTreeSet` collect silently
9375 // dedups the rendered `keywords:` array), the `maintainers:`
9376 // rendering has *no* dedup — duplicates stack verbatim. The
9377 // duplicate-arm names the offending author verbatim.
9378 let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
9379 let err = c.validate_autores().unwrap_err();
9380 let ManifestError::AutorDuplicate { autor } = err else {
9381 panic!("expected AutorDuplicate, got {err:?}");
9382 };
9383 assert_eq!(autor, "pleme-io");
9384 }
9385
9386 #[test]
9387 fn validate_autores_empty_takes_precedence_over_duplicate() {
9388 // Empty-first cascade pin: `("" "pleme-io" "pleme-io")` surfaces
9389 // `AutorEmpty` not `AutorDuplicate` — the narrower structural
9390 // "this entry has no value" defect dominates the cross-entry
9391 // uniqueness diagnostic. Mirrors the peer empty-before-duplicate
9392 // cascades on `:etiquetas` (`EtiquetaEmpty` before
9393 // `EtiquetaDuplicate`, 360a499), `:caracteristicas`
9394 // (`CaracteristicaEmpty` before `CaracteristicaDuplicate`,
9395 // fc3b4d5), and `:membros :caixa` (`MembroCaixaEmpty` before
9396 // `MembroDuplicate`).
9397 let c = caixa_with_autores(vec!["", "pleme-io", "pleme-io"]);
9398 let err = c.validate_autores().unwrap_err();
9399 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
9400 }
9401
9402 #[test]
9403 fn validate_autores_duplicate_reports_first_collision() {
9404 // First-collision pin: `("a" "b" "a" "b")` surfaces the `"a"`
9405 // duplicate (the lexicographically-earliest offending position
9406 // — the second `"a"` at index 2 collides with the first `"a"`
9407 // at index 0), not the later `"b"` collision at index 3,
9408 // peer with every other first-collision diagnostic posture on
9409 // this surface.
9410 let c = caixa_with_autores(vec!["a", "b", "a", "b"]);
9411 let err = c.validate_autores().unwrap_err();
9412 let ManifestError::AutorDuplicate { autor } = err else {
9413 panic!("expected AutorDuplicate, got {err:?}");
9414 };
9415 assert_eq!(autor, "a");
9416 }
9417
9418 #[test]
9419 fn validate_autores_case_sensitive() {
9420 // Case-sensitivity pin: `("Pleme-io" "pleme-io")` is two distinct
9421 // entries, mirroring the peer `:etiquetas` / `:membros :caixa`
9422 // / `:children :caixa` exact-string-match discipline.
9423 let c = caixa_with_autores(vec!["Pleme-io", "pleme-io"]);
9424 c.validate_autores().unwrap();
9425 }
9426
9427 #[test]
9428 fn validate_autores_diagnostic_carries_offending_author() {
9429 // Diagnostic-shape pin (peer with
9430 // `validate_etiquetas_diagnostic_carries_offending_tag`): the
9431 // error's Display surfaces the offending author verbatim, so a
9432 // `feira lint` run can render the diagnostic without re-parsing
9433 // and the author can grep their caixa.lisp for the offending
9434 // value.
9435 let c = caixa_with_autores(vec!["pleme-io", "pleme-io"]);
9436 let rendered = c.validate_autores().unwrap_err().to_string();
9437 assert!(
9438 rendered.contains(":autores"),
9439 "diagnostic must name the offending slot: {rendered}",
9440 );
9441 assert!(
9442 rendered.contains("pleme-io"),
9443 "diagnostic must quote the offending author: {rendered}",
9444 );
9445 }
9446
9447 #[test]
9448 fn validate_autores_rejects_leading_whitespace_entry() {
9449 // Canonical paste-from-aligned-doc footgun. Without the shape
9450 // gate `" pleme-io"` silently passed validate and landed as a
9451 // YAML plain-style scalar with leading whitespace in the
9452 // rendered Chart.yaml `maintainers:` array — every YAML 1.2
9453 // dumper trims leading whitespace from plain-style scalars, so
9454 // the authored space round-tripped inconsistently back through
9455 // `caixa.lisp`. Mirrors the peer
9456 // `validate_descricao_rejects_leading_whitespace`.
9457 let c = caixa_with_autores(vec![" pleme-io"]);
9458 let err = c.validate_autores().unwrap_err();
9459 let ManifestError::AutorInvalid { autor, reason } = err else {
9460 panic!("expected AutorInvalid, got {err:?}");
9461 };
9462 assert_eq!(autor, " pleme-io");
9463 assert!(reason.contains("whitespace"), "got: {reason}");
9464 }
9465
9466 #[test]
9467 fn validate_autores_rejects_trailing_whitespace_entry() {
9468 // Canonical paste-from-doc footgun.
9469 let c = caixa_with_autores(vec!["pleme-io "]);
9470 let err = c.validate_autores().unwrap_err();
9471 let ManifestError::AutorInvalid { autor, reason } = err else {
9472 panic!("expected AutorInvalid, got {err:?}");
9473 };
9474 assert_eq!(autor, "pleme-io ");
9475 assert!(reason.contains("whitespace"), "got: {reason}");
9476 }
9477
9478 #[test]
9479 fn validate_autores_rejects_embedded_newline_entry() {
9480 // Canonical paste-from-multiline-doc footgun — the author
9481 // pasted a multi-line block of author records into one
9482 // `:autores` entry instead of splitting into one entry per
9483 // author. Without the shape gate `"alice\nbob"` silently
9484 // passed validate and landed as a YAML-illegal multi-line
9485 // scalar in the rendered Chart.yaml `maintainers:` array.
9486 let c = caixa_with_autores(vec!["alice\nbob"]);
9487 let err = c.validate_autores().unwrap_err();
9488 let ManifestError::AutorInvalid { autor, reason } = err else {
9489 panic!("expected AutorInvalid, got {err:?}");
9490 };
9491 assert_eq!(autor, "alice\nbob");
9492 assert!(reason.contains("newline"), "got: {reason}");
9493 }
9494
9495 #[test]
9496 fn validate_autores_rejects_embedded_carriage_return_entry() {
9497 // Canonical paste-from-Windows-CRLF-doc footgun.
9498 let c = caixa_with_autores(vec!["alice\rbob"]);
9499 let err = c.validate_autores().unwrap_err();
9500 let ManifestError::AutorInvalid { autor, reason } = err else {
9501 panic!("expected AutorInvalid, got {err:?}");
9502 };
9503 assert_eq!(autor, "alice\rbob");
9504 assert!(reason.contains("carriage return"), "got: {reason}");
9505 }
9506
9507 #[test]
9508 fn validate_autores_rejects_embedded_tab_entry() {
9509 // Canonical tab-from-aligned-doc footgun.
9510 let c = caixa_with_autores(vec!["Pleme\tContributors"]);
9511 let err = c.validate_autores().unwrap_err();
9512 let ManifestError::AutorInvalid { autor, reason } = err else {
9513 panic!("expected AutorInvalid, got {err:?}");
9514 };
9515 assert_eq!(autor, "Pleme\tContributors");
9516 assert!(reason.contains("tab"), "got: {reason}");
9517 }
9518
9519 #[test]
9520 fn validate_autores_rejects_embedded_control_bytes_entry() {
9521 // Paste-from-binary-blob footguns: NUL, BEL, ESC, DEL all
9522 // surface the same control-byte arm.
9523 for entry in [
9524 "alice\x00bob",
9525 "alice\x07bob",
9526 "alice\x1bbob",
9527 "alice\x7fbob",
9528 ] {
9529 let c = caixa_with_autores(vec![entry]);
9530 let err = c.validate_autores().unwrap_err();
9531 let ManifestError::AutorInvalid { autor, reason } = err else {
9532 panic!("expected AutorInvalid for {entry:?}, got {err:?}");
9533 };
9534 assert_eq!(autor, entry);
9535 assert!(
9536 reason.contains("control character"),
9537 "{entry:?} reason: {reason}",
9538 );
9539 }
9540 }
9541
9542 #[test]
9543 fn validate_autores_accepts_unicode_entry() {
9544 // Unicode positive control: realistic maintainer names carry
9545 // Unicode (`François`, `日本語`, `naïve`). The predicate must
9546 // round-trip Unicode losslessly, peer with the
9547 // `chart_maintainer_name_shape_accepts_unicode` substrate-side
9548 // sweep.
9549 let c = caixa_with_autores(vec![
9550 "François Dupont",
9551 "日本語の名前",
9552 "naïve <naive@example.com>",
9553 ]);
9554 c.validate_autores().unwrap();
9555 }
9556
9557 #[test]
9558 fn validate_autores_empty_takes_precedence_over_shape() {
9559 // Per-entry empty-first cascade pin: an entry that is both
9560 // empty *and* shape-invalid surfaces `AutorEmpty` (the narrower
9561 // "this entry has no value" structural defect dominates the
9562 // broader shape-predicate diagnostic). The empty arm fires
9563 // before the shape predicate is consulted, mirroring the peer
9564 // `validate_repositorio_empty_takes_precedence_over_shape`
9565 // cascade on the universal `Option<String>` siblings — and now
9566 // established on the Vec<String> per-entry surface.
9567 let c = caixa_with_autores(vec![""]);
9568 let err = c.validate_autores().unwrap_err();
9569 assert!(matches!(err, ManifestError::AutorEmpty), "got {err:?}",);
9570 }
9571
9572 #[test]
9573 fn validate_autores_shape_takes_precedence_over_duplicate() {
9574 // Per-entry shape-before-cross-entry-duplicate cascade pin: an
9575 // entry that is malformed surfaces `AutorInvalid` even when a
9576 // later entry would have collided on duplicate. The per-entry
9577 // shape arm fires inside the same loop iteration as the empty
9578 // arm, before the seen-set insert at end-of-iteration —
9579 // structural per-entry defects dominate the cross-entry
9580 // uniqueness diagnostic.
9581 let c = caixa_with_autores(vec!["alice\nbob", "alice\nbob"]);
9582 let err = c.validate_autores().unwrap_err();
9583 assert!(
9584 matches!(err, ManifestError::AutorInvalid { .. }),
9585 "got {err:?}",
9586 );
9587 }
9588
9589 #[test]
9590 fn validate_autores_invalid_diagnostic_names_offending_slot_and_value() {
9591 // Diagnostic-shape pin on the new shape arm (peer with
9592 // `validate_descricao_invalid_diagnostic_carries_offending_value`):
9593 // the rendered Display surfaces both the offending slot name
9594 // and the offending value verbatim, so a `feira lint` run
9595 // points the author at the exact `:autores` entry to fix.
9596 let c = caixa_with_autores(vec!["alice\nbob"]);
9597 let rendered = c.validate_autores().unwrap_err().to_string();
9598 assert!(
9599 rendered.contains(":autores"),
9600 "diagnostic must name the offending slot: {rendered}",
9601 );
9602 assert!(
9603 rendered.contains("alice\\nbob"),
9604 "diagnostic must quote the offending value (debug-escaped): {rendered}",
9605 );
9606 }
9607
9608 #[test]
9609 fn validate_autores_rejects_at_129_byte_boundary() {
9610 // The 128-byte cap pin — boundary-exceeding case rejected,
9611 // boundary-accepting case passes. Mirrors the peer
9612 // `chart_maintainer_name_shape_rejects_at_129_byte_boundary`
9613 // substrate-side pin, surfaced at the per-axis caller so the
9614 // cap propagates through validate end-to-end. Constructed as
9615 // a single all-`a` token so only the cap arm fires.
9616 let max_ok = "a".repeat(128);
9617 let c = caixa_with_autores(vec![max_ok.as_str()]);
9618 c.validate_autores().unwrap();
9619 let too_long = "a".repeat(129);
9620 let c = caixa_with_autores(vec![too_long.as_str()]);
9621 let err = c.validate_autores().unwrap_err();
9622 let ManifestError::AutorInvalid { reason, .. } = err else {
9623 panic!("expected AutorInvalid, got {err:?}");
9624 };
9625 assert!(reason.contains("128"), "got: {reason}");
9626 assert!(reason.contains("129"), "got: {reason}");
9627 }
9628
9629 // ── validate_repositorio — universal-axis git-repo-URL shape ──────
9630
9631 fn caixa_with_repositorio(repositorio: Option<&str>) -> Caixa {
9632 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9633 c.repositorio = repositorio.map(String::from);
9634 c
9635 }
9636
9637 #[test]
9638 fn validate_repositorio_accepts_none() {
9639 // The omit-the-slot identity: `:repositorio` is optional. The
9640 // gate is a no-op when the author didn't declare a value —
9641 // every caixa without a `:repositorio` line trivially passes,
9642 // and the substrate-side renderers fall back to their
9643 // documented placeholder (`caixa-helm`'s `home: None`,
9644 // `caixa-flux`'s `https://github.com/pleme-io/<nome>` derived
9645 // URL). Mirrors the peer `validate_restart_window_accepts_none`
9646 // posture on the other `Option<String>` Caixa slot.
9647 let c = caixa_with_repositorio(None);
9648 c.validate_repositorio().unwrap();
9649 }
9650
9651 #[test]
9652 fn validate_repositorio_accepts_canonical_forms() {
9653 // Positive control sweep across every documented `:repositorio`
9654 // authoring shape — the same union the shared
9655 // `crate::render::is_git_repo_url` predicate accepts and the
9656 // peer `:deps :fonte :repo` axis already routes through.
9657 // Covers the `github:` shorthand (the canonical pleme-io
9658 // convention used in the `:repositorio` field of every
9659 // manifest fixture across `caixa-helm` / `caixa-mesh` and the
9660 // `examples/`), the `https://…` URL the README quickstart uses,
9661 // the `ssh://`, `git://`, `git@host:path` scp-style SSH, and
9662 // `file://` URL schemes the shared predicate documents.
9663 for repo in [
9664 "github:pleme-io/hello-rio",
9665 "github:pleme-io/checkout",
9666 "https://github.com/pleme-io/hello-rio",
9667 "ssh://git@github.com/pleme-io/hello-rio.git",
9668 "git://github.com/pleme-io/hello-rio.git",
9669 "git@github.com:pleme-io/hello-rio.git",
9670 "file:///srv/pleme/hello-rio",
9671 ] {
9672 let c = caixa_with_repositorio(Some(repo));
9673 c.validate_repositorio()
9674 .unwrap_or_else(|err| panic!("canonical {repo:?} must pass: {err:?}"));
9675 }
9676 }
9677
9678 #[test]
9679 fn validate_repositorio_rejects_empty_some() {
9680 // Canonical paste-from-blank-doc footgun. The narrower
9681 // [`ManifestError::RepositorioEmpty`] arm fires before the
9682 // shape predicate is consulted, mirroring the empty-first
9683 // cascade every peer per-axis identity gate uses
9684 // (`NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
9685 // `FonteRepoEmpty` → `FonteRepoInvalid`). Without this gate
9686 // the empty `Some("")` silently passed the renderer's
9687 // `Option::unwrap_or_else(|| <fallback>)` (which only fires
9688 // on `None`) and landed as `home: ""` in `Chart.yaml` /
9689 // `url: ""` in the FluxCD `GitRepository`.
9690 let c = caixa_with_repositorio(Some(""));
9691 let err = c.validate_repositorio().unwrap_err();
9692 assert!(
9693 matches!(err, ManifestError::RepositorioEmpty),
9694 "got {err:?}",
9695 );
9696 }
9697
9698 #[test]
9699 fn validate_repositorio_rejects_whitespace() {
9700 // Paste-from-doc whitespace footgun. The shared
9701 // `is_git_repo_url` predicate refuses any whitespace byte; a
9702 // trailing space in a `:repositorio` value silently broke
9703 // `git clone '<value> '` at clone time. The diagnostic names
9704 // the offending value verbatim.
9705 let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio "));
9706 let err = c.validate_repositorio().unwrap_err();
9707 let ManifestError::RepositorioInvalid { repositorio, .. } = err else {
9708 panic!("expected RepositorioInvalid, got {err:?}");
9709 };
9710 assert_eq!(repositorio, "github:pleme-io/hello-rio ");
9711 }
9712
9713 #[test]
9714 fn validate_repositorio_rejects_control_char() {
9715 // Paste-from-multiline-doc CRLF footgun — control characters
9716 // at the URL boundary are a class of subprocess-arg injection
9717 // and break git's URL parser at every porcelain entry point.
9718 let c = caixa_with_repositorio(Some("https://example.com/repo\n"));
9719 let err = c.validate_repositorio().unwrap_err();
9720 assert!(
9721 matches!(err, ManifestError::RepositorioInvalid { .. }),
9722 "got {err:?}",
9723 );
9724 }
9725
9726 #[test]
9727 fn validate_repositorio_rejects_leading_dash() {
9728 // Canonical CLI-argument-injection footgun: `git clone <repo>`
9729 // interprets a leading `-` as a CLI flag, so a
9730 // `-upload-pack=…` value escapes the subprocess argument
9731 // boundary. The shared predicate refuses every leading-`-`
9732 // shape at validate time.
9733 let c = caixa_with_repositorio(Some("-upload-pack=evil"));
9734 let err = c.validate_repositorio().unwrap_err();
9735 assert!(
9736 matches!(err, ManifestError::RepositorioInvalid { .. }),
9737 "got {err:?}",
9738 );
9739 }
9740
9741 #[test]
9742 fn validate_repositorio_rejects_missing_colon_separator() {
9743 // The bare `org/repo` ambiguity footgun — `git clone` reads
9744 // a no-`:` form as a relative filesystem path rather than the
9745 // GitHub-shorthand expansion the author probably intended.
9746 // The shared predicate refuses every shape without a `:`
9747 // separator.
9748 let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
9749 let err = c.validate_repositorio().unwrap_err();
9750 assert!(
9751 matches!(err, ManifestError::RepositorioInvalid { .. }),
9752 "got {err:?}",
9753 );
9754 }
9755
9756 #[test]
9757 fn validate_repositorio_rejects_fragment_anchor() {
9758 // Paste-from-browser-address-bar footgun on the
9759 // `:repositorio` axis — an author copies a GitHub permalink
9760 // to a README section / line-permalink and forgets to trim
9761 // the `#fragment` tail. The shared `is_git_repo_url`
9762 // predicate refuses the byte at the URL-grammar layer
9763 // (libcurl strips the fragment before opening the
9764 // transport, so the byte rides verbatim into the rendered
9765 // `Chart.yaml` `home:` and FluxCD `GitRepository` `url:`
9766 // fields but is silently dropped on the wire — two
9767 // manifest variants whose values differ only in their
9768 // fragment anchor lock to two distinct rendered artifacts
9769 // for the byte-identical clone, defeating the THEORY.md
9770 // §V.2 render-determinism contract on the `:repositorio`
9771 // axis the peer `:fonte :repo` axis already closes).
9772 let c = caixa_with_repositorio(Some("https://github.com/pleme-io/hello-rio#readme"));
9773 let err = c.validate_repositorio().unwrap_err();
9774 let ManifestError::RepositorioInvalid {
9775 repositorio,
9776 reason,
9777 } = err
9778 else {
9779 panic!("expected RepositorioInvalid, got {err:?}");
9780 };
9781 assert_eq!(repositorio, "https://github.com/pleme-io/hello-rio#readme");
9782 assert!(
9783 reason.contains("must not contain `#`"),
9784 "reason must surface the fragment-`#` arm, got {reason:?}"
9785 );
9786 }
9787
9788 #[test]
9789 fn validate_repositorio_rejects_query_string() {
9790 // Paste-from-browser-address-bar footgun on the
9791 // `:repositorio` axis (peer with the a68f818 fragment-`#`
9792 // arm on the same axis). An author copies a GitHub tab
9793 // deep-link out of the address bar and forgets to trim
9794 // the `?tab=…` query tail. The shared `is_git_repo_url`
9795 // predicate refuses the byte at the URL-grammar layer
9796 // (GitHub / GitLab / Bitbucket silently ignore the
9797 // `?query` tail and serve the same repo regardless, so
9798 // the byte rides verbatim into the rendered `Chart.yaml`
9799 // `home:` and FluxCD `GitRepository` `url:` fields but
9800 // is silently masked at the wire — two manifest variants
9801 // whose values differ only in their query tail lock to
9802 // two distinct rendered artifacts for the byte-identical
9803 // clone, defeating the THEORY.md §V.2 render-determinism
9804 // contract on the `:repositorio` axis the peer `:fonte
9805 // :repo` axis already closes).
9806 let c = caixa_with_repositorio(Some(
9807 "https://github.com/pleme-io/hello-rio?tab=readme-ov-file",
9808 ));
9809 let err = c.validate_repositorio().unwrap_err();
9810 let ManifestError::RepositorioInvalid {
9811 repositorio,
9812 reason,
9813 } = err
9814 else {
9815 panic!("expected RepositorioInvalid, got {err:?}");
9816 };
9817 assert_eq!(
9818 repositorio,
9819 "https://github.com/pleme-io/hello-rio?tab=readme-ov-file"
9820 );
9821 assert!(
9822 reason.contains("must not contain `?`"),
9823 "reason must surface the query-`?` arm, got {reason:?}"
9824 );
9825 }
9826
9827 #[test]
9828 fn validate_repositorio_rejects_embedded_backslash() {
9829 // Windows-file-path-confusion footgun on the `:repositorio`
9830 // axis (peer with the prior fragment-`#` / query-`?` arms on
9831 // the same axis, and peer with the new dep-level `:fonte :repo`
9832 // backslash arm on the URL-grammar trajectory). An author
9833 // pastes a Windows Explorer address-bar `file:///C:\Users\me\
9834 // hello-rio` into the `:repositorio` slot, expecting the
9835 // `lareira-<nome>` chart's `home:` field and the FluxCD
9836 // `GitRepository` `url:` field to render the canonical local
9837 // file-URI. The shared `is_git_repo_url` predicate refuses
9838 // the byte at the URL-grammar layer (libcurl silently
9839 // translates `\` → `/` on some platforms and refuses it on
9840 // others, so the byte rides verbatim into the rendered
9841 // artifacts but is silently rewritten or rejected at the wire
9842 // — two manifest variants whose values differ only in
9843 // backslash-vs-forward-slash lock to two distinct rendered
9844 // artifacts for the byte-identical clone, defeating the
9845 // THEORY.md §V.2 render-determinism contract on the
9846 // `:repositorio` axis the peer `:fonte :repo` axis already
9847 // closes).
9848 let c = caixa_with_repositorio(Some("file:///C:\\Users\\me\\hello-rio"));
9849 let err = c.validate_repositorio().unwrap_err();
9850 let ManifestError::RepositorioInvalid {
9851 repositorio,
9852 reason,
9853 } = err
9854 else {
9855 panic!("expected RepositorioInvalid, got {err:?}");
9856 };
9857 assert_eq!(repositorio, "file:///C:\\Users\\me\\hello-rio");
9858 assert!(
9859 reason.contains("must not contain `\\`"),
9860 "reason must surface the backslash-`\\` arm, got {reason:?}"
9861 );
9862 }
9863
9864 #[test]
9865 fn validate_repositorio_rejects_uri_template_placeholder() {
9866 // URI Template (RFC 6570) placeholder footgun on the
9867 // `:repositorio` axis (peer with the prior fragment-`#` /
9868 // query-`?` / backslash-`\` arms on the same axis, and peer
9869 // with the new dep-level `:fonte :repo` `{` / `}` arm on the
9870 // URL-grammar trajectory). An author pastes a quick-start
9871 // README snippet / OpenAPI `servers:` URL / Helm chart
9872 // `home:` template carrying unresolved `{org}` / `{repo}`
9873 // placeholders into the `:repositorio` slot, expecting the
9874 // substrate to resolve the placeholder downstream. The
9875 // shared `is_git_repo_url` predicate refuses the byte at the
9876 // URL-grammar layer (libcurl percent-encodes `{` / `}` to
9877 // `%7B` / `%7D` on the wire, so the byte round-trips
9878 // inconsistently between the rendered `Chart.yaml home:` /
9879 // FluxCD `GitRepository url:` and the resolver's `git clone`
9880 // invocation, defeating the THEORY.md §V.2 render-
9881 // determinism contract on the `:repositorio` axis the peer
9882 // `:fonte :repo` axis already closes; every git porcelain
9883 // entry-point additionally fetches a nonexistent literal-
9884 // `{placeholder}`-named path far from the source caixa.lisp).
9885 let c = caixa_with_repositorio(Some("https://github.com/{org}/hello-rio"));
9886 let err = c.validate_repositorio().unwrap_err();
9887 let ManifestError::RepositorioInvalid {
9888 repositorio,
9889 reason,
9890 } = err
9891 else {
9892 panic!("expected RepositorioInvalid, got {err:?}");
9893 };
9894 assert_eq!(repositorio, "https://github.com/{org}/hello-rio");
9895 assert!(
9896 reason.contains("must not contain `{`"),
9897 "reason must surface the open-brace `{{` arm, got {reason:?}"
9898 );
9899 assert!(
9900 reason.contains("URI Template") || reason.contains("RFC 6570"),
9901 "reason must name the RFC 6570 URI Template grammar, got {reason:?}"
9902 );
9903 }
9904
9905 #[test]
9906 fn validate_repositorio_empty_takes_precedence_over_shape() {
9907 // Empty-first cascade pin: the empty `Some("")` surfaces the
9908 // narrower `RepositorioEmpty` not the shape-predicate-wrapped
9909 // `RepositorioInvalid`, mirroring the peer
9910 // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid`,
9911 // `FonteRepoEmpty` → `FonteRepoInvalid` cascades. The shared
9912 // `is_git_repo_url` predicate also rejects the empty input
9913 // (defensively, with its own `"must not be empty"` reason),
9914 // but the manifest-layer empty arm runs first to surface the
9915 // narrower diagnostic verbatim.
9916 let c = caixa_with_repositorio(Some(""));
9917 let err = c.validate_repositorio().unwrap_err();
9918 assert!(
9919 matches!(err, ManifestError::RepositorioEmpty),
9920 "got {err:?}",
9921 );
9922 }
9923
9924 #[test]
9925 fn validate_repositorio_diagnostic_carries_offending_value() {
9926 // Diagnostic-shape pin (peer with
9927 // `validate_autores_diagnostic_carries_offending_author`): the
9928 // error's Display surfaces the offending value + slot name
9929 // verbatim, so a `feira lint` run can render the diagnostic
9930 // without re-parsing and the author can grep their caixa.lisp
9931 // for the offending `:repositorio` value.
9932 let c = caixa_with_repositorio(Some("pleme-io/hello-rio"));
9933 let rendered = c.validate_repositorio().unwrap_err().to_string();
9934 assert!(
9935 rendered.contains(":repositorio"),
9936 "diagnostic must name the offending slot: {rendered}",
9937 );
9938 assert!(
9939 rendered.contains("pleme-io/hello-rio"),
9940 "diagnostic must quote the offending value: {rendered}",
9941 );
9942 }
9943
9944 // ── validate_descricao — universal-axis Chart.yaml description shape ──
9945
9946 fn caixa_with_descricao(descricao: Option<&str>) -> Caixa {
9947 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
9948 c.descricao = descricao.map(String::from);
9949 c
9950 }
9951
9952 #[test]
9953 fn validate_descricao_accepts_none() {
9954 // The omit-the-slot identity: `:descricao` is optional. The
9955 // gate is a no-op when the author didn't declare a value —
9956 // every caixa without a `:descricao` line trivially passes,
9957 // and the substrate-side renderers fall back to their
9958 // documented `caixa.nome`-derived placeholder. Mirrors the
9959 // peer `validate_repositorio_accepts_none` posture on the
9960 // sibling `Option<String>` Caixa slot.
9961 let c = caixa_with_descricao(None);
9962 c.validate_descricao().unwrap();
9963 }
9964
9965 #[test]
9966 fn validate_descricao_accepts_canonical_summary() {
9967 // Positive control: the canonical pleme-io descricao shape —
9968 // a short free-form prose summary — passes the gate. Covers
9969 // the fixture shapes the `caixa-helm` / `caixa-flux` /
9970 // `caixa-mesh` test fixtures use (`"Canonical Rust→wasm32-
9971 // wasip2 caixa Servico."`, `"Checkout flow."`).
9972 for desc in [
9973 "Canonical Rust→wasm32-wasip2 caixa Servico.",
9974 "Checkout flow.",
9975 "AWS provider caixa for tatara-lisp",
9976 "FIXME — describe this caixa",
9977 "x",
9978 ] {
9979 let c = caixa_with_descricao(Some(desc));
9980 c.validate_descricao()
9981 .unwrap_or_else(|err| panic!("canonical {desc:?} must pass: {err:?}"));
9982 }
9983 }
9984
9985 #[test]
9986 fn validate_descricao_rejects_empty_some() {
9987 // Canonical paste-from-blank-doc footgun. Without this gate
9988 // the empty `Some("")` silently passed the renderer's
9989 // `Option::unwrap_or_else(|| <fallback>)` (which only fires
9990 // on `None`) and landed as `description: ""` in `Chart.yaml`
9991 // and a blank `README.md` header. Mirrors the peer
9992 // [`ManifestError::RepositorioEmpty`] empty-arm on the
9993 // sibling `Option<String>` Caixa slot.
9994 let c = caixa_with_descricao(Some(""));
9995 let err = c.validate_descricao().unwrap_err();
9996 assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
9997 }
9998
9999 #[test]
10000 fn validate_descricao_rejects_leading_whitespace() {
10001 // Paste-from-aligned-doc footgun: a leading ASCII space the
10002 // bare empty-arm gate accepted, the shape predicate now
10003 // refuses. The diagnostic carries the offending value
10004 // verbatim (with the leading space preserved) so the author
10005 // can grep their caixa.lisp for the exact `:descricao` line
10006 // and fix the round-trip-inconsistent leading whitespace.
10007 // Mirrors the peer
10008 // `validate_licenca_rejects_leading_whitespace` arm on the
10009 // sibling `:licenca` axis.
10010 let c = caixa_with_descricao(Some(" Checkout flow."));
10011 let err = c.validate_descricao().unwrap_err();
10012 let ManifestError::DescricaoInvalid { descricao, reason } = err else {
10013 panic!("expected DescricaoInvalid, got {err:?}");
10014 };
10015 assert_eq!(descricao, " Checkout flow.");
10016 assert!(reason.contains("whitespace"), "got: {reason:?}");
10017 }
10018
10019 #[test]
10020 fn validate_descricao_rejects_trailing_whitespace() {
10021 // Paste-from-doc footgun: a trailing ASCII space the bare
10022 // empty-arm gate accepted, the shape predicate now refuses.
10023 let c = caixa_with_descricao(Some("Checkout flow. "));
10024 let err = c.validate_descricao().unwrap_err();
10025 let ManifestError::DescricaoInvalid { descricao, reason } = err else {
10026 panic!("expected DescricaoInvalid, got {err:?}");
10027 };
10028 assert_eq!(descricao, "Checkout flow. ");
10029 assert!(reason.contains("whitespace"), "got: {reason:?}");
10030 }
10031
10032 #[test]
10033 fn validate_descricao_rejects_embedded_newline() {
10034 // Paste-from-multiline-doc footgun: an embedded LF the bare
10035 // empty-arm gate accepted, the shape predicate now refuses.
10036 // Without this gate the embedded newline silently landed in
10037 // the rendered Chart.yaml as a multi-line YAML block scalar,
10038 // and every chart-aware UI (`helm list`, `helm search`,
10039 // Artifact Hub) renders the description in a single-line
10040 // column so the embedded newline is silently dropped at
10041 // every downstream consumer.
10042 let c = caixa_with_descricao(Some("Checkout\nflow."));
10043 let err = c.validate_descricao().unwrap_err();
10044 assert!(
10045 matches!(err, ManifestError::DescricaoInvalid { .. }),
10046 "got {err:?}",
10047 );
10048 assert!(err.to_string().contains("newline"), "got {err}");
10049 }
10050
10051 #[test]
10052 fn validate_descricao_rejects_embedded_carriage_return() {
10053 // Paste-from-Windows-CRLF-doc footgun.
10054 let c = caixa_with_descricao(Some("Checkout\rflow."));
10055 let err = c.validate_descricao().unwrap_err();
10056 assert!(
10057 matches!(err, ManifestError::DescricaoInvalid { .. }),
10058 "got {err:?}",
10059 );
10060 assert!(err.to_string().contains("carriage return"), "got {err}");
10061 }
10062
10063 #[test]
10064 fn validate_descricao_rejects_embedded_tab() {
10065 // Tab-from-aligned-doc footgun.
10066 let c = caixa_with_descricao(Some("Checkout\tflow."));
10067 let err = c.validate_descricao().unwrap_err();
10068 assert!(
10069 matches!(err, ManifestError::DescricaoInvalid { .. }),
10070 "got {err:?}",
10071 );
10072 assert!(err.to_string().contains("tab"), "got {err}");
10073 }
10074
10075 #[test]
10076 fn validate_descricao_rejects_embedded_control_bytes() {
10077 // Paste-from-binary-blob footgun: every other control byte
10078 // (NUL, BEL, ESC, DEL) is refused at validate time. Mirrors
10079 // the peer SPDX-expression control-byte arm.
10080 for s in [
10081 "Checkout\x00flow.",
10082 "Checkout\x07flow.",
10083 "Checkout\x1bflow.",
10084 "Checkout\x7fflow.",
10085 ] {
10086 let c = caixa_with_descricao(Some(s));
10087 let err = c.validate_descricao().unwrap_err();
10088 assert!(
10089 matches!(err, ManifestError::DescricaoInvalid { .. }),
10090 "{s:?} got {err:?}",
10091 );
10092 assert!(
10093 err.to_string().contains("control character"),
10094 "{s:?} got {err}",
10095 );
10096 }
10097 }
10098
10099 #[test]
10100 fn validate_descricao_accepts_unicode_prose() {
10101 // Positive control: Unicode prose is accepted — the
10102 // canonical fixtures carry `→` (U+2192) and `—` (U+2014),
10103 // and `Caixa::template`'s `"FIXME — describe this caixa"`
10104 // scaffold every `feira init` emits must continue to pass.
10105 for s in [
10106 "Canonical Rust→wasm32-wasip2 caixa Servico.",
10107 "FIXME — describe this caixa",
10108 "Caixa pour le projet tâche",
10109 "日本語の説明",
10110 ] {
10111 let c = caixa_with_descricao(Some(s));
10112 c.validate_descricao()
10113 .unwrap_or_else(|err| panic!("Unicode {s:?} must pass: {err:?}"));
10114 }
10115 }
10116
10117 #[test]
10118 fn validate_descricao_empty_takes_precedence_over_shape() {
10119 // Cascade pin: a `Some("")` surfaces the narrower
10120 // `DescricaoEmpty` arm, not the broader `DescricaoInvalid`
10121 // shape-predicate arm. Mirrors the peer
10122 // `validate_licenca_empty_takes_precedence_over_shape` pin
10123 // on the sibling `:licenca` axis.
10124 let c = caixa_with_descricao(Some(""));
10125 let err = c.validate_descricao().unwrap_err();
10126 assert!(matches!(err, ManifestError::DescricaoEmpty), "got {err:?}",);
10127 }
10128
10129 #[test]
10130 fn validate_descricao_invalid_diagnostic_carries_offending_value_and_slot() {
10131 // Diagnostic-shape pin: the error's Display surfaces both
10132 // the `:descricao` slot name and the offending value
10133 // verbatim, so a `feira lint` run can render the diagnostic
10134 // without re-parsing and the author can grep their caixa.lisp
10135 // for the offending `:descricao` line. Mirrors the peer
10136 // `validate_licenca_invalid_diagnostic_carries_offending_value_and_slot`
10137 // pin (ee2e888) on the sibling `:licenca` axis.
10138 // The `{descricao:?}` Debug format escapes embedded control
10139 // bytes; the quoted offending value surfaces as
10140 // `"Checkout\nflow."` (literal backslash-n) in the rendered
10141 // diagnostic. The author can grep their caixa.lisp for the
10142 // literal `Checkout` summary prefix.
10143 let c = caixa_with_descricao(Some("Checkout\nflow."));
10144 let rendered = c.validate_descricao().unwrap_err().to_string();
10145 assert!(
10146 rendered.contains(":descricao"),
10147 "diagnostic must name the offending slot: {rendered}",
10148 );
10149 assert!(
10150 rendered.contains("Checkout\\nflow."),
10151 "diagnostic must quote the offending value (debug-escaped): {rendered}",
10152 );
10153 }
10154
10155 #[test]
10156 fn validate_descricao_template_passes() {
10157 // Round-trip pin: the bare `Caixa::template` shape carries
10158 // `:descricao "FIXME — describe this caixa"` (a non-empty
10159 // sentinel), so the template-derived Caixa passes the gate by
10160 // construction. A future template-shape change that omits or
10161 // empties `:descricao` would surface here as a regression.
10162 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10163 c.validate_descricao().unwrap();
10164 }
10165
10166 #[test]
10167 fn validate_descricao_diagnostic_names_offending_slot() {
10168 // Diagnostic-shape pin (peer with
10169 // `validate_repositorio_diagnostic_carries_offending_value`):
10170 // the error's Display surfaces the `:descricao` slot name
10171 // verbatim, so a `feira lint` run can render the diagnostic
10172 // without re-parsing and the author can grep their caixa.lisp
10173 // for the offending `:descricao` line.
10174 let c = caixa_with_descricao(Some(""));
10175 let rendered = c.validate_descricao().unwrap_err().to_string();
10176 assert!(
10177 rendered.contains(":descricao"),
10178 "diagnostic must name the offending slot: {rendered}",
10179 );
10180 }
10181
10182 // ── validate_licenca — universal-axis chart README license shape ──
10183
10184 fn caixa_with_licenca(licenca: Option<&str>) -> Caixa {
10185 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10186 c.licenca = licenca.map(String::from);
10187 c
10188 }
10189
10190 #[test]
10191 fn validate_licenca_accepts_none() {
10192 // The omit-the-slot identity: `:licenca` is optional. The
10193 // gate is a no-op when the author didn't declare a value —
10194 // every caixa without a `:licenca` line trivially passes,
10195 // and the substrate-side `caixa-helm` renderer falls back to
10196 // the documented `"MIT"` placeholder. Mirrors the peer
10197 // `validate_descricao_accepts_none` posture on the sibling
10198 // `Option<String>` Caixa slot.
10199 let c = caixa_with_licenca(None);
10200 c.validate_licenca().unwrap();
10201 }
10202
10203 #[test]
10204 fn validate_licenca_accepts_canonical_expressions() {
10205 // Positive control: every canonical SPDX expression shape
10206 // pleme-io carries in its existing fixtures + the canonical
10207 // SPDX dual-license / with-exception / `+`-suffix / grouped /
10208 // user-defined-reference shapes all pass the gate. Covers
10209 // the single-license, `OR`-compound, `AND`-compound,
10210 // `WITH`-exception, parenthesis-grouped, `+`-suffix, and
10211 // `LicenseRef-` / `DocumentRef-:LicenseRef-` shapes — every
10212 // production the SPDX 2.1 expression grammar admits that
10213 // sits within the alphabet floor the
10214 // `is_spdx_expression_shape` predicate enforces.
10215 for lic in [
10216 "MIT",
10217 "Apache-2.0",
10218 "Apache-2.0 OR MIT",
10219 "Apache-2.0 AND MIT",
10220 "BSD-3-Clause",
10221 "MPL-2.0",
10222 "GPL-3.0-or-later",
10223 "GPL-2.0+",
10224 "Apache-2.0 WITH LLVM-exception",
10225 "(MIT OR Apache-2.0) AND BSD-3-Clause",
10226 "(MIT OR Apache-2.0) AND BSD-3-Clause AND ISC",
10227 "LicenseRef-MyLicense",
10228 "DocumentRef-spdx-tool:LicenseRef-MIT-Style",
10229 "x",
10230 ] {
10231 let c = caixa_with_licenca(Some(lic));
10232 c.validate_licenca()
10233 .unwrap_or_else(|err| panic!("canonical {lic:?} must pass: {err:?}"));
10234 }
10235 }
10236
10237 #[test]
10238 fn validate_licenca_rejects_trailing_whitespace() {
10239 // Paste-from-doc whitespace footgun. A trailing space in the
10240 // `:licenca` value would silently break a downstream SPDX
10241 // parser that splits on exact `AND` / `OR` / `WITH` keyword
10242 // boundaries. The shape predicate refuses every trailing
10243 // whitespace byte by construction. Peer with
10244 // `validate_repositorio_rejects_whitespace` and
10245 // `validate_edicao_rejects_trailing_whitespace`.
10246 let c = caixa_with_licenca(Some("MIT "));
10247 let err = c.validate_licenca().unwrap_err();
10248 let ManifestError::LicencaInvalid { licenca, .. } = err else {
10249 panic!("expected LicencaInvalid, got {err:?}");
10250 };
10251 assert_eq!(licenca, "MIT ");
10252 }
10253
10254 #[test]
10255 fn validate_licenca_rejects_leading_whitespace() {
10256 // Symmetric paste-from-doc whitespace footgun on the leading
10257 // boundary — the gate refuses every shape that starts with a
10258 // space byte by construction. Peer with
10259 // `validate_edicao_rejects_leading_whitespace`.
10260 let c = caixa_with_licenca(Some(" MIT"));
10261 let err = c.validate_licenca().unwrap_err();
10262 assert!(
10263 matches!(err, ManifestError::LicencaInvalid { .. }),
10264 "got {err:?}",
10265 );
10266 }
10267
10268 #[test]
10269 fn validate_licenca_rejects_control_char() {
10270 // Paste-from-multiline-doc CRLF footgun — control characters
10271 // at the value boundary land as a malformed line in the
10272 // rendered chart `README.md` `## License` section. Peer with
10273 // `validate_repositorio_rejects_control_char` and
10274 // `validate_edicao_rejects_control_char`.
10275 for lic in ["MIT\n", "MIT\r\n", "MIT\rApache-2.0"] {
10276 let c = caixa_with_licenca(Some(lic));
10277 let err = c.validate_licenca().unwrap_err();
10278 assert!(
10279 matches!(err, ManifestError::LicencaInvalid { .. }),
10280 "expected LicencaInvalid on {lic:?}, got {err:?}",
10281 );
10282 }
10283 }
10284
10285 #[test]
10286 fn validate_licenca_rejects_tab() {
10287 // Tab-from-aligned-doc footgun — SPDX expressions use a
10288 // single ASCII space between tokens; a tab breaks every
10289 // downstream SPDX parser that splits on exact `" "`
10290 // boundaries.
10291 let c = caixa_with_licenca(Some("MIT\tOR Apache-2.0"));
10292 let err = c.validate_licenca().unwrap_err();
10293 assert!(
10294 matches!(err, ManifestError::LicencaInvalid { .. }),
10295 "got {err:?}",
10296 );
10297 }
10298
10299 #[test]
10300 fn validate_licenca_rejects_non_ascii() {
10301 // Smart-quote / non-ASCII paste footgun — SPDX identifiers
10302 // are ASCII per the `idstring = 1*(ALPHA / DIGIT / "-" /
10303 // ".")` production. The shape predicate refuses every
10304 // non-ASCII byte by construction; peer with
10305 // `validate_edicao_rejects_non_ascii_lookalike`.
10306 for lic in ["MIT\u{a0}OR Apache-2.0", "MIT\u{2013}1.0", "Café-1.0"] {
10307 let c = caixa_with_licenca(Some(lic));
10308 let err = c.validate_licenca().unwrap_err();
10309 assert!(
10310 matches!(err, ManifestError::LicencaInvalid { .. }),
10311 "expected LicencaInvalid on {lic:?}, got {err:?}",
10312 );
10313 }
10314 }
10315
10316 #[test]
10317 fn validate_licenca_rejects_underscore() {
10318 // Underscore-instead-of-hyphen typo footgun — `Apache_2.0` /
10319 // `MIT_Style` / `BSD_3_Clause` are familiar shapes from
10320 // snake-case identifier conventions that don't apply to the
10321 // SPDX `idstring` grammar (which admits only `ALPHA / DIGIT /
10322 // "-" / "."`). The shape predicate refuses every underscore
10323 // byte by construction.
10324 for lic in ["Apache_2.0", "MIT_Style", "BSD_3_Clause"] {
10325 let c = caixa_with_licenca(Some(lic));
10326 let err = c.validate_licenca().unwrap_err();
10327 assert!(
10328 matches!(err, ManifestError::LicencaInvalid { .. }),
10329 "expected LicencaInvalid on {lic:?}, got {err:?}",
10330 );
10331 }
10332 }
10333
10334 #[test]
10335 fn validate_licenca_rejects_comma_separator() {
10336 // Comma-instead-of-`OR`-keyword colloquial idiom footgun —
10337 // SPDX expressions compose multiple licenses via `AND` / `OR`
10338 // keywords, not the comma separator. The shape predicate
10339 // refuses every comma byte by construction.
10340 for lic in ["MIT, Apache-2.0", "MIT,Apache-2.0"] {
10341 let c = caixa_with_licenca(Some(lic));
10342 let err = c.validate_licenca().unwrap_err();
10343 assert!(
10344 matches!(err, ManifestError::LicencaInvalid { .. }),
10345 "expected LicencaInvalid on {lic:?}, got {err:?}",
10346 );
10347 }
10348 }
10349
10350 #[test]
10351 fn validate_licenca_rejects_slash_dual_license() {
10352 // Slash-dual-license colloquial idiom footgun — the
10353 // `MIT/Apache-2.0` shape is common in Cargo's pre-SPDX
10354 // `package.license` field but non-SPDX; the SPDX equivalent
10355 // is `MIT OR Apache-2.0`. The shape predicate refuses every
10356 // forward-slash byte by construction.
10357 for lic in ["MIT/Apache-2.0", "MIT/BSD-3-Clause"] {
10358 let c = caixa_with_licenca(Some(lic));
10359 let err = c.validate_licenca().unwrap_err();
10360 assert!(
10361 matches!(err, ManifestError::LicencaInvalid { .. }),
10362 "expected LicencaInvalid on {lic:?}, got {err:?}",
10363 );
10364 }
10365 }
10366
10367 #[test]
10368 fn validate_licenca_rejects_semicolon_separator() {
10369 // Semicolon-list-separator confusion footgun — adjacent to
10370 // the comma-separator idiom, every list-separator-belongs-
10371 // to-list-grammar confusion lands here.
10372 let c = caixa_with_licenca(Some("MIT; Apache-2.0"));
10373 let err = c.validate_licenca().unwrap_err();
10374 assert!(
10375 matches!(err, ManifestError::LicencaInvalid { .. }),
10376 "got {err:?}",
10377 );
10378 }
10379
10380 #[test]
10381 fn validate_licenca_empty_takes_precedence_over_shape() {
10382 // Empty-first cascade pin: the empty `Some("")` surfaces the
10383 // narrower `LicencaEmpty` not the shape-predicate-wrapped
10384 // `LicencaInvalid`, mirroring the peer
10385 // `validate_edicao_empty_takes_precedence_over_shape` and
10386 // `validate_repositorio_empty_takes_precedence_over_shape`
10387 // (`RepositorioEmpty` → `RepositorioInvalid`), `NomeEmpty` →
10388 // `NomeInvalid`, `VersaoEmpty` → `VersaoInvalid` cascades.
10389 // The shape predicate also refuses the empty input
10390 // (defensively — `"must not be empty"`), but the manifest-
10391 // layer empty arm runs first to surface the narrower
10392 // diagnostic verbatim.
10393 let c = caixa_with_licenca(Some(""));
10394 let err = c.validate_licenca().unwrap_err();
10395 assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
10396 }
10397
10398 #[test]
10399 fn validate_licenca_invalid_diagnostic_carries_offending_value() {
10400 // Diagnostic-shape pin on the shape-predicate arm (peer with
10401 // `validate_edicao_invalid_diagnostic_carries_offending_value`
10402 // and `validate_repositorio_diagnostic_carries_offending_value`):
10403 // the error's Display surfaces the offending value + slot
10404 // name verbatim, so a `feira lint` run can render the
10405 // diagnostic without re-parsing and the author can grep
10406 // their caixa.lisp for the offending `:licenca` value.
10407 let c = caixa_with_licenca(Some("Apache_2.0"));
10408 let rendered = c.validate_licenca().unwrap_err().to_string();
10409 assert!(
10410 rendered.contains(":licenca"),
10411 "diagnostic must name the offending slot: {rendered}",
10412 );
10413 assert!(
10414 rendered.contains("Apache_2.0"),
10415 "diagnostic must quote the offending value: {rendered}",
10416 );
10417 }
10418
10419 #[test]
10420 fn validate_licenca_rejects_empty_some() {
10421 // Canonical paste-from-blank-doc footgun. Without this gate
10422 // the empty `Some("")` silently passed the renderer's
10423 // `Option::unwrap_or_else(|| "MIT".into())` (which only
10424 // fires on `None`) and landed as a bare trailing period in
10425 // the rendered chart `README.md` `## License` section.
10426 // Mirrors the peer [`ManifestError::DescricaoEmpty`] empty-
10427 // arm on the sibling `Option<String>` Caixa slot.
10428 let c = caixa_with_licenca(Some(""));
10429 let err = c.validate_licenca().unwrap_err();
10430 assert!(matches!(err, ManifestError::LicencaEmpty), "got {err:?}",);
10431 }
10432
10433 #[test]
10434 fn validate_licenca_template_passes() {
10435 // Round-trip pin: the bare `Caixa::template` shape (whether
10436 // it carries `:licenca` or omits it) passes the gate by
10437 // construction. A future template-shape change that
10438 // introduced `(:licenca "")` would surface here as a
10439 // regression. Mirrors the peer
10440 // `validate_descricao_template_passes` pin.
10441 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
10442 c.validate_licenca().unwrap();
10443 }
10444
10445 #[test]
10446 fn validate_licenca_diagnostic_names_offending_slot() {
10447 // Diagnostic-shape pin (peer with
10448 // `validate_descricao_diagnostic_names_offending_slot`):
10449 // the error's Display surfaces the `:licenca` slot name
10450 // verbatim, so a `feira lint` run can render the diagnostic
10451 // without re-parsing and the author can grep their caixa.lisp
10452 // for the offending `:licenca` line.
10453 let c = caixa_with_licenca(Some(""));
10454 let rendered = c.validate_licenca().unwrap_err().to_string();
10455 assert!(
10456 rendered.contains(":licenca"),
10457 "diagnostic must name the offending slot: {rendered}",
10458 );
10459 }
10460
10461 // ── Caixa::licenca — outer top-level Option<&str> scalar accessor ──
10462
10463 #[test]
10464 fn licenca_returns_licenca_byte_string_verbatim_across_permutations() {
10465 // The canonical per-`Caixa` `:licenca` SPDX-expression scalar
10466 // pin: [`Caixa::licenca`] must return the `:licenca` typed
10467 // byte-string verbatim as an `Option<&str>`, byte-equal to the
10468 // raw `self.licenca.as_deref()` access across every
10469 // representative value in the accept-set — `None` (the "omit
10470 // the slot to defer to the caixa-helm renderer's `MIT`
10471 // fallback" arm every existing fixture without a `:licenca`
10472 // line carries), `Some("")` (a past-the-guard sentinel that
10473 // pins the accessor doesn't perform a silent
10474 // `Some("") → None` collapse on the empty arm — validate
10475 // rejects `Some("")` through `LicencaEmpty` but the accessor
10476 // must ship the raw slot verbatim so a validate-time gate
10477 // regression surfaces at the caixa-helm emit boundary rather
10478 // than being silently absorbed into the fallback), `Some("MIT")`
10479 // (the canonical single-license shape every `feira init`
10480 // template scaffolds), `Some("Apache-2.0 OR MIT")` (the
10481 // canonical `OR`-compound shape the peer
10482 // `validate_licenca_accepts_canonical_expressions` positive
10483 // sweep exercises), `Some("(MIT OR Apache-2.0) AND
10484 // BSD-3-Clause")` (the canonical parenthesis-grouped shape),
10485 // `Some("MIT ")` / `Some(" MIT")` / `Some("MIT\n")` /
10486 // `Some("Apache_2.0")` / `Some("MIT,Apache-2.0")` (past-the-
10487 // guard sentinels — validate rejects each through
10488 // `LicencaInvalid` but the accessor must ship the raw slot
10489 // verbatim).
10490 //
10491 // First outer top-level [`Caixa`] `Option<&str>`-return scalar
10492 // accessor pin on the substrate primitive — opens the "outer
10493 // [`Caixa`] `Option<&str>` scalar" projection pattern the
10494 // sibling per-`Caixa` `:descricao` / `:repositorio` / `:edicao`
10495 // future lifts fold on. Sibling in shape to the peer per-`:placement`
10496 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
10497 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
10498 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
10499 // axes, extended onto the outer top-level [`Caixa`] universal-
10500 // axis surface. Pins against a future silent detour that
10501 // returned an owned `Option<String>` (which would type-check
10502 // but silently allocate on every accessor call, breaking the
10503 // zero-cost projection every peer sibling accessor carries), a
10504 // `Some("") → None` collapse (which would silently absorb the
10505 // `LicencaEmpty` refusal case at the accessor boundary and the
10506 // caixa-helm emit path would silently fall back to `"MIT"` on
10507 // a struct-literal `Caixa { licenca: Some(""), .. }`), or a
10508 // `None → Some("MIT")` collapse (which would silently reify
10509 // the caixa-helm renderer's `"MIT"` fallback at the accessor
10510 // boundary and every downstream consumer keying off the
10511 // `Option::is_none()` discriminator would lose the "author
10512 // omitted the slot" signal).
10513 for licenca in [
10514 None,
10515 Some(""),
10516 Some("MIT"),
10517 Some("Apache-2.0 OR MIT"),
10518 Some("(MIT OR Apache-2.0) AND BSD-3-Clause"),
10519 Some("MIT "),
10520 Some(" MIT"),
10521 Some("MIT\n"),
10522 Some("Apache_2.0"),
10523 Some("MIT,Apache-2.0"),
10524 ] {
10525 let c = caixa_with_licenca(licenca);
10526 assert_eq!(
10527 c.licenca(),
10528 licenca,
10529 "Caixa::licenca must return :licenca verbatim (got {:?}, \
10530 expected {licenca:?})",
10531 c.licenca(),
10532 );
10533 assert_eq!(
10534 c.licenca(),
10535 c.licenca.as_deref(),
10536 "Caixa::licenca must byte-equal the raw \
10537 `self.licenca.as_deref()` field access across every \
10538 value in the Option<&str> accept-set",
10539 );
10540 }
10541 }
10542
10543 #[test]
10544 fn validate_licenca_empty_arm_routes_through_accessor() {
10545 // Composition pin: [`Caixa::validate_licenca`]'s empty-arm gate
10546 // must key off [`Caixa::licenca`], not the raw
10547 // `self.licenca.as_deref()` field access. Structurally: a
10548 // `Caixa { licenca: Some(""), .. }` must surface the
10549 // `LicencaEmpty` refusal exactly, and a
10550 // `Caixa { licenca: Some("MIT"), .. }` (the canonical
10551 // single-license form) must pass validate. The pair jointly
10552 // pins the accessor + validate-gate composition: any future
10553 // silent detour that had the accessor return `None` on the
10554 // empty arm (a `.filter(|s| !s.is_empty())` collapse) would
10555 // silently absorb the `LicencaEmpty` refusal at the accessor
10556 // boundary and the validate gate would accept a struct-literal
10557 // `Caixa { licenca: Some(""), .. }` — the composition pin
10558 // catches that at caixa-core build time.
10559 //
10560 // Peer of the per-`:politicas :circuit-breaker`
10561 // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
10562 // accessor-composition pin
10563 // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
10564 // on the sibling per-M3-mesh-slot required-`u32` axis — same
10565 // "the validate / shape-gate predicate must route through the
10566 // substrate-primitive typed dispatch" discipline extended onto
10567 // the outer top-level [`Caixa`] universal-axis
10568 // `Option<&str>`-composition surface.
10569 let c = caixa_with_licenca(Some(""));
10570 assert!(
10571 matches!(c.validate_licenca(), Err(ManifestError::LicencaEmpty)),
10572 "validate_licenca must reject licenca == Some(\"\") with \
10573 LicencaEmpty — the accessor and the validate gate must \
10574 route through the same substrate-primitive typed dispatch \
10575 on the :licenca empty arm",
10576 );
10577 let c = caixa_with_licenca(Some("MIT"));
10578 assert!(
10579 c.validate_licenca().is_ok(),
10580 "validate_licenca must accept licenca == Some(\"MIT\") \
10581 (the canonical single-license SPDX shape)",
10582 );
10583 }
10584
10585 #[test]
10586 fn licenca_projects_option_str_by_borrow() {
10587 // The by-borrow pin: [`Caixa::licenca`] returns
10588 // `Option<&str>` by borrow — the `&str` borrows the underlying
10589 // `String` storage of the `Option<String>` slot and the
10590 // accessor must not allocate a fresh `String` on every call.
10591 // Peer of the per-`:placement`
10592 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
10593 // borrow pin on the peer per-M3-mesh-slot
10594 // `Option<&str>`-return axis, extended onto the outer top-
10595 // level [`Caixa`] universal-axis `Option<&str>` shape — the
10596 // accessor's returned `&str` must borrow from `&self` (the
10597 // returned reference's lifetime is tied to `&self`), and
10598 // calling the accessor twice on the same [`Caixa`] must yield
10599 // the same `Option<&str>` verbatim (idempotent, no side
10600 // effects on `&self`).
10601 //
10602 // Pins against a future silent detour that returned an owned
10603 // `Option<String>` (which would type-check but silently
10604 // allocate on every call, breaking the zero-cost projection
10605 // every peer sibling accessor carries), or a one-arm-only
10606 // accessor that returned a saturating value on some sentinel
10607 // input (breaking the pass-through invariant the sibling
10608 // required-scalar accessors carry).
10609 for licenca in [None, Some(""), Some("MIT"), Some("Apache-2.0 OR MIT")] {
10610 let c = caixa_with_licenca(licenca);
10611 let first = c.licenca();
10612 let second = c.licenca();
10613 assert_eq!(
10614 first, second,
10615 "Caixa::licenca must be idempotent — two successive \
10616 calls on the same &self must return the same \
10617 Option<&str>",
10618 );
10619 assert_eq!(
10620 first, licenca,
10621 "Caixa::licenca must return :licenca verbatim by \
10622 borrow — got {first:?}, expected {licenca:?}",
10623 );
10624 }
10625 }
10626
10627 // ── Caixa::repositorio — outer top-level Option<&str> scalar accessor ──
10628
10629 #[test]
10630 fn repositorio_returns_repositorio_byte_string_verbatim_across_permutations() {
10631 // The canonical per-`Caixa` `:repositorio` git-repo-URL scalar
10632 // pin: [`Caixa::repositorio`] must return the `:repositorio`
10633 // typed byte-string verbatim as an `Option<&str>`, byte-equal
10634 // to the raw `self.repositorio.as_deref()` access across every
10635 // representative value in the accept-set — `None` (the "omit
10636 // the slot to defer to the per-renderer placeholder" arm every
10637 // existing fixture without a `:repositorio` line carries),
10638 // `Some("")` (a past-the-guard sentinel that pins the accessor
10639 // doesn't perform a silent `Some("") → None` collapse on the
10640 // empty arm — validate rejects `Some("")` through
10641 // `RepositorioEmpty` but the accessor must ship the raw slot
10642 // verbatim so a validate-time gate regression surfaces at the
10643 // caixa-helm / caixa-flux emit boundary rather than being
10644 // silently absorbed into the per-renderer fallback),
10645 // `Some("github:pleme-io/hello-rio")` (the canonical `github:`
10646 // shorthand every existing manifest fixture across
10647 // `caixa-helm` / `caixa-mesh` and the `examples/` uses),
10648 // `Some("https://github.com/pleme-io/checkout")` (the canonical
10649 // `https://` URL the README quickstart uses),
10650 // `Some("ssh://git@github.com/pleme-io/checkout.git")` /
10651 // `Some("git://github.com/pleme-io/checkout.git")` /
10652 // `Some("git@github.com:pleme-io/checkout.git")` /
10653 // `Some("file:///opt/mirrors/pleme-io/checkout")` (every non-
10654 // github scheme the shared `is_git_repo_url` predicate
10655 // documents), and five past-the-guard sentinels for the
10656 // `RepositorioInvalid` refusal cases (`Some("pleme-io/checkout")`
10657 // missing-colon, `Some("-upload-pack=evil")` leading-dash, /
10658 // `Some("github:pleme-io/checkout?ref=main")` query-string, /
10659 // `Some("github:pleme-io/checkout#main")` fragment-anchor, /
10660 // `Some("github:pleme-io/{tpl}")` URI-template-placeholder — the
10661 // sentinels pin the accessor doesn't silently absorb the
10662 // refusal cases into a fallback).
10663 //
10664 // Second outer top-level [`Caixa`] `Option<&str>`-return scalar
10665 // accessor pin on the substrate primitive — sibling of the peer
10666 // [`Caixa::licenca`] (6d5bc28) pin
10667 // (`licenca_returns_licenca_byte_string_verbatim_across_permutations`)
10668 // that opened the "outer [`Caixa`] `Option<&str>` scalar"
10669 // projection pin pattern this pin folds on. Sibling in shape to
10670 // the peer per-`:placement`
10671 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
10672 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
10673 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
10674 // axes, extended onto the outer top-level [`Caixa`] universal-
10675 // axis surface. Pins against a future silent detour that
10676 // returned an owned `Option<String>` (which would type-check
10677 // but silently allocate on every accessor call, breaking the
10678 // zero-cost projection every peer sibling accessor carries), a
10679 // `Some("") → None` collapse (which would silently absorb the
10680 // `RepositorioEmpty` refusal case at the accessor boundary and
10681 // the caixa-helm `Chart.yaml` `home:` fold would silently
10682 // render a `home: null` / omitted field on a struct-literal
10683 // `Caixa { repositorio: Some(""), .. }`), or a
10684 // `None → Some(<default>)` collapse (which would silently reify
10685 // the per-renderer fallback at the accessor boundary and every
10686 // downstream consumer keying off the `Option::is_none()`
10687 // discriminator would lose the "author omitted the slot"
10688 // signal).
10689 for repositorio in [
10690 None,
10691 Some(""),
10692 Some("github:pleme-io/hello-rio"),
10693 Some("https://github.com/pleme-io/checkout"),
10694 Some("ssh://git@github.com/pleme-io/checkout.git"),
10695 Some("git://github.com/pleme-io/checkout.git"),
10696 Some("git@github.com:pleme-io/checkout.git"),
10697 Some("file:///opt/mirrors/pleme-io/checkout"),
10698 Some("pleme-io/checkout"),
10699 Some("-upload-pack=evil"),
10700 Some("github:pleme-io/checkout?ref=main"),
10701 Some("github:pleme-io/checkout#main"),
10702 Some("github:pleme-io/{tpl}"),
10703 ] {
10704 let c = caixa_with_repositorio(repositorio);
10705 assert_eq!(
10706 c.repositorio(),
10707 repositorio,
10708 "Caixa::repositorio must return :repositorio verbatim \
10709 (got {:?}, expected {repositorio:?})",
10710 c.repositorio(),
10711 );
10712 assert_eq!(
10713 c.repositorio(),
10714 c.repositorio.as_deref(),
10715 "Caixa::repositorio must byte-equal the raw \
10716 `self.repositorio.as_deref()` field access across every \
10717 value in the Option<&str> accept-set",
10718 );
10719 }
10720 }
10721
10722 #[test]
10723 fn validate_repositorio_empty_arm_routes_through_accessor() {
10724 // Composition pin: [`Caixa::validate_repositorio`]'s empty-arm
10725 // gate must key off [`Caixa::repositorio`], not the raw
10726 // `self.repositorio.as_deref()` field access. Structurally: a
10727 // `Caixa { repositorio: Some(""), .. }` must surface the
10728 // `RepositorioEmpty` refusal exactly, and a
10729 // `Caixa { repositorio: Some("github:pleme-io/hello-rio"), .. }`
10730 // (the canonical `github:` shorthand form) must pass validate.
10731 // The pair jointly pins the accessor + validate-gate
10732 // composition: any future silent detour that had the accessor
10733 // return `None` on the empty arm (a `.filter(|s| !s.is_empty())`
10734 // collapse) would silently absorb the `RepositorioEmpty` refusal
10735 // at the accessor boundary and the validate gate would accept a
10736 // struct-literal `Caixa { repositorio: Some(""), .. }` — the
10737 // composition pin catches that at caixa-core build time.
10738 //
10739 // Peer of the [`Caixa::licenca`] (6d5bc28)
10740 // `validate_licenca_empty_arm_routes_through_accessor`
10741 // composition pin on the sibling outer top-level [`Caixa`]
10742 // `Option<&str>` universal-axis surface — same "the validate /
10743 // shape-gate predicate must route through the substrate-
10744 // primitive typed dispatch" discipline extended onto the second
10745 // outer top-level [`Caixa`] universal-axis `Option<&str>`-
10746 // composition surface.
10747 let c = caixa_with_repositorio(Some(""));
10748 assert!(
10749 matches!(
10750 c.validate_repositorio(),
10751 Err(ManifestError::RepositorioEmpty),
10752 ),
10753 "validate_repositorio must reject repositorio == Some(\"\") \
10754 with RepositorioEmpty — the accessor and the validate gate \
10755 must route through the same substrate-primitive typed \
10756 dispatch on the :repositorio empty arm",
10757 );
10758 let c = caixa_with_repositorio(Some("github:pleme-io/hello-rio"));
10759 assert!(
10760 c.validate_repositorio().is_ok(),
10761 "validate_repositorio must accept repositorio == \
10762 Some(\"github:pleme-io/hello-rio\") (the canonical \
10763 `github:` shorthand git-repo-URL shape)",
10764 );
10765 }
10766
10767 #[test]
10768 fn repositorio_projects_option_str_by_borrow() {
10769 // The by-borrow pin: [`Caixa::repositorio`] returns
10770 // `Option<&str>` by borrow — the `&str` borrows the underlying
10771 // `String` storage of the `Option<String>` slot and the
10772 // accessor must not allocate a fresh `String` on every call.
10773 // Peer of the per-`:placement`
10774 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) and the
10775 // [`Caixa::licenca`] (6d5bc28) by-borrow pins on the peer
10776 // `Option<&str>`-return axes, extended onto the second outer
10777 // top-level [`Caixa`] universal-axis `Option<&str>` shape —
10778 // the accessor's returned `&str` must borrow from `&self` (the
10779 // returned reference's lifetime is tied to `&self`), and
10780 // calling the accessor twice on the same [`Caixa`] must yield
10781 // the same `Option<&str>` verbatim (idempotent, no side effects
10782 // on `&self`).
10783 //
10784 // Pins against a future silent detour that returned an owned
10785 // `Option<String>` (which would type-check but silently
10786 // allocate on every call, breaking the zero-cost projection
10787 // every peer sibling accessor carries), or a one-arm-only
10788 // accessor that returned a saturating value on some sentinel
10789 // input (breaking the pass-through invariant the sibling
10790 // required-scalar accessors carry).
10791 for repositorio in [
10792 None,
10793 Some(""),
10794 Some("github:pleme-io/hello-rio"),
10795 Some("https://github.com/pleme-io/checkout"),
10796 ] {
10797 let c = caixa_with_repositorio(repositorio);
10798 let first = c.repositorio();
10799 let second = c.repositorio();
10800 assert_eq!(
10801 first, second,
10802 "Caixa::repositorio must be idempotent — two successive \
10803 calls on the same &self must return the same \
10804 Option<&str>",
10805 );
10806 assert_eq!(
10807 first, repositorio,
10808 "Caixa::repositorio must return :repositorio verbatim by \
10809 borrow — got {first:?}, expected {repositorio:?}",
10810 );
10811 }
10812 }
10813
10814 // ── Caixa::canonical_git_url — resolved-git-URL composer ──────────
10815
10816 #[test]
10817 fn canonical_git_url_returns_repositorio_verbatim_on_some_arm() {
10818 // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] must
10819 // return the author-declared `:repositorio` byte-string verbatim
10820 // on the `Some` arm — no scheme rewrite, no trailing-slash
10821 // canonicalization, no `github:` → `https://github.com/`
10822 // desugaring. The resolved-URL composer is the projection of
10823 // the raw [`Caixa::repositorio`] `Option<&str>` accessor onto
10824 // the `String`-return arity every substrate-side field-fill
10825 // consumer keys off; on the `Some` arm the projection is
10826 // `str::to_owned` verbatim, so every accept-set value the
10827 // sibling `repositorio_returns_repositorio_byte_string_verbatim_
10828 // across_permutations` pin covers (`https://…`, `github:…`,
10829 // `ssh://…`, `git://…`, `git@…`, `file://…`, and the past-the-
10830 // guard sentinel `pleme-io/…`) must survive the accessor
10831 // byte-equal. Pins against a future silent detour that rewrote
10832 // the `github:` shorthand to the `https://github.com/` full URL
10833 // at the accessor boundary (which would silently split the
10834 // resolved-URL surface from the raw [`Caixa::repositorio`]
10835 // accessor's documented pass-through invariant), or a trailing-
10836 // slash normalization (which would silently break the
10837 // FluxCD `GitRepository` `spec.url` byte-exact match every
10838 // downstream consumer keys the source-controller reconcile off).
10839 for repositorio in [
10840 "github:pleme-io/hello-rio",
10841 "https://github.com/pleme-io/checkout",
10842 "ssh://git@github.com/pleme-io/checkout.git",
10843 "git://github.com/pleme-io/checkout.git",
10844 "git@github.com:pleme-io/checkout.git",
10845 "file:///opt/mirrors/pleme-io/checkout",
10846 ] {
10847 let c = caixa_with_repositorio(Some(repositorio));
10848 assert_eq!(
10849 c.canonical_git_url(),
10850 repositorio,
10851 "Caixa::canonical_git_url on the Some arm must return \
10852 :repositorio verbatim (got {:?}, expected {repositorio:?})",
10853 c.canonical_git_url(),
10854 );
10855 }
10856 }
10857
10858 #[test]
10859 fn canonical_git_url_falls_back_to_pleme_org_url_on_none_arm() {
10860 // Fail-before-pass-after pin: [`Caixa::canonical_git_url`] on the
10861 // `None` arm must emit the substrate's canonical pleme-org github
10862 // URL derived from `caixa.nome()` — `https://github.com/<org>/
10863 // <nome>` with `<org>` bound to [`crate::DEFAULT_PLEME_GIT_ORG`]
10864 // and `<nome>` bound to the typed [`Caixa::nome`] accessor. This
10865 // is the exact byte-image of the prior inline
10866 // [`caixa-flux::ClusterBundleOpts::for_caixa`] `git_url`
10867 // composer at caixa-flux/src/lib.rs:2080 that every prior caller
10868 // re-derived open-coded. Pins against a future silent detour
10869 // that migrated the `<org>` segment to a different constant (a
10870 // fork rebranding that split off a new
10871 // `DEFAULT_PLEME_GIT_ORG_MIRROR` const the accessor would need
10872 // to migrate onto), a scheme change (`https://` → `git://` or
10873 // `ssh://`), or a per-`Caixa` `.canonical_git_url_prefix`
10874 // override (which would break the substrate-wide single-source-
10875 // of-truth guarantee this method encodes).
10876 let c = caixa_with_repositorio(None);
10877 let expected = format!(
10878 "https://github.com/{org}/{nome}",
10879 org = crate::DEFAULT_PLEME_GIT_ORG,
10880 nome = c.nome(),
10881 );
10882 assert_eq!(
10883 c.canonical_git_url(),
10884 expected,
10885 "Caixa::canonical_git_url on the None arm must fold through \
10886 the substrate's canonical pleme-org github URL fallback \
10887 `https://github.com/<DEFAULT_PLEME_GIT_ORG>/<nome>` — got \
10888 {:?}, expected {expected:?}",
10889 c.canonical_git_url(),
10890 );
10891 }
10892
10893 #[test]
10894 fn canonical_git_url_byte_matches_manual_composition() {
10895 // Byte-parity pin: [`Caixa::canonical_git_url`] must render
10896 // byte-identically to the manual open-coded
10897 // `caixa.repositorio().map(str::to_owned).unwrap_or_else(||
10898 // format!("https://github.com/{org}/{nome}", ...))` composition
10899 // every prior substrate-side caller re-derived. Guards the
10900 // paired-site convergence just applied at caixa-flux's
10901 // [`ClusterBundleOpts::for_caixa`] `git_url` composer (which
10902 // now routes through this accessor): a future implementation of
10903 // this method that reordered the format arguments, swapped the
10904 // `<org>` constant for a different one, or interposed a
10905 // canonicalization pass on the `Some` arm surfaces here as a
10906 // caixa-core build-time test failure rather than as a downstream
10907 // FluxCD `GitRepository` reconcile mismatch far from this
10908 // method's source.
10909 for repositorio in [
10910 None,
10911 Some("github:pleme-io/hello-rio"),
10912 Some("https://github.com/pleme-io/checkout"),
10913 Some("ssh://git@github.com/pleme-io/checkout.git"),
10914 ] {
10915 let c = caixa_with_repositorio(repositorio);
10916 let manual = c.repositorio().map_or_else(
10917 || {
10918 format!(
10919 "https://github.com/{org}/{nome}",
10920 org = crate::DEFAULT_PLEME_GIT_ORG,
10921 nome = c.nome(),
10922 )
10923 },
10924 str::to_owned,
10925 );
10926 assert_eq!(
10927 c.canonical_git_url(),
10928 manual,
10929 "Caixa::canonical_git_url must byte-equal the manual \
10930 open-coded `repositorio().map(str::to_owned)\
10931 .unwrap_or_else(|| format!(...))` composition across \
10932 every representative :repositorio input — got {:?}, \
10933 expected {manual:?}",
10934 c.canonical_git_url(),
10935 );
10936 }
10937 }
10938
10939 // ── Caixa::publish_tag — resolved-publish-tag composer ───────────
10940
10941 #[test]
10942 fn publish_tag_composes_prefix_and_versao_on_all_shapes() {
10943 // Fail-before-pass-after pin: [`Caixa::publish_tag`] must compose
10944 // [`crate::DEFAULT_PUBLISH_TAG_PREFIX`] against the caixa's typed
10945 // [`Caixa::versao`] byte-string across every SemVer-2 shape the
10946 // sibling [`validate_versao_accepts_canonical_forms`] positive-set
10947 // sweep documents — bare MAJOR.MINOR.PATCH, pre-release tags
10948 // (`-rc.1`), build metadata (`+build.42`), the combined form, and
10949 // the `0.0.0` boundary case. Every accept-set value the peer
10950 // validate gate lets through must survive the resolved-tag
10951 // projection byte-equal.
10952 for versao in [
10953 "0.1.0",
10954 "0.0.0",
10955 "1.0.0",
10956 "1.2.3-rc.1",
10957 "1.2.3+build.42",
10958 "1.2.3-rc.1+build.42",
10959 ] {
10960 let c = caixa_with_versao(versao);
10961 let expected = format!(
10962 "{prefix}{versao}",
10963 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
10964 );
10965 assert_eq!(
10966 c.publish_tag(),
10967 expected,
10968 "Caixa::publish_tag must compose \
10969 DEFAULT_PUBLISH_TAG_PREFIX ({prefix:?}) against \
10970 :versao ({versao:?}) verbatim — got {got:?}, \
10971 expected {expected:?}",
10972 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
10973 got = c.publish_tag(),
10974 );
10975 }
10976 }
10977
10978 #[test]
10979 fn publish_tag_starts_with_default_publish_tag_prefix() {
10980 // Prefix-shape pin: every [`Caixa::publish_tag`] emission must
10981 // begin with the canonical [`crate::DEFAULT_PUBLISH_TAG_PREFIX`]
10982 // byte-string on every input, guarding a hypothetical future
10983 // implementation that migrated the prefix segment to an inline
10984 // literal (`"v"`) that would silently drift from any rebrand of
10985 // the lifted constant. Peer to the sibling caixa-flux
10986 // `cluster_bundle_default_git_tag_uses_lifted_caixa_core_prefix`
10987 // test which pins the same prefix invariant at the reader-side
10988 // `GitRefSpec::Tag` emit site.
10989 for versao in ["0.0.0", "0.1.0", "1.2.3-rc.1", "9.9.9+build.1"] {
10990 let c = caixa_with_versao(versao);
10991 let tag = c.publish_tag();
10992 assert!(
10993 tag.starts_with(crate::DEFAULT_PUBLISH_TAG_PREFIX),
10994 "Caixa::publish_tag emission {tag:?} must start with \
10995 the lifted crate::DEFAULT_PUBLISH_TAG_PREFIX \
10996 ({prefix:?})",
10997 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
10998 );
10999 }
11000 }
11001
11002 #[test]
11003 fn publish_tag_byte_matches_manual_composition() {
11004 // Byte-parity pin: [`Caixa::publish_tag`] must render byte-
11005 // identically to the manual open-coded
11006 // `format!("{prefix}{versao}", prefix =
11007 // caixa_core::DEFAULT_PUBLISH_TAG_PREFIX, versao =
11008 // caixa.versao())` composition every prior substrate-side
11009 // caller re-derived. Guards the paired-site convergence just
11010 // applied at caixa-flux's [`ClusterBundleOpts::for_caixa`]
11011 // `git_ref` composer (which now routes through this accessor):
11012 // a future implementation of this method that reordered the
11013 // format arguments, swapped the `<prefix>` constant for a
11014 // different one, or interposed a canonicalization pass on the
11015 // `:versao` axis surfaces here as a caixa-core build-time test
11016 // failure rather than as a downstream FluxCD `GitRepository`
11017 // reconcile mismatch far from this method's source.
11018 for versao in [
11019 "0.1.0",
11020 "0.0.0",
11021 "1.2.3-rc.1",
11022 "1.2.3+build.42",
11023 "1.2.3-rc.1+build.42",
11024 ] {
11025 let c = caixa_with_versao(versao);
11026 let manual = format!(
11027 "{prefix}{versao}",
11028 prefix = crate::DEFAULT_PUBLISH_TAG_PREFIX,
11029 versao = c.versao(),
11030 );
11031 assert_eq!(
11032 c.publish_tag(),
11033 manual,
11034 "Caixa::publish_tag must byte-equal the manual \
11035 open-coded `format!(\"{{prefix}}{{versao}}\", ...)` \
11036 composition across every representative :versao input \
11037 — got {got:?}, expected {manual:?}",
11038 got = c.publish_tag(),
11039 );
11040 }
11041 }
11042
11043 // ── Caixa::lareira_chart_name — resolved-chart-name composer ─────
11044
11045 #[test]
11046 fn lareira_chart_name_composes_prefix_and_nome_on_all_shapes() {
11047 // Fail-before-pass-after pin: [`Caixa::lareira_chart_name`] must
11048 // compose [`crate::LAREIRA_CHART_NAME_PREFIX`] against the caixa's
11049 // typed [`Caixa::nome`] byte-string across every DNS-1123 shape
11050 // the sibling [`validate_nome_accepts_canonical_forms`] positive-
11051 // set sweep documents — single-word, hyphen-joined, version-
11052 // suffixed, single-char, two-char, digit-start, retry-suffixed.
11053 // Every accept-set value the peer validate gate lets through must
11054 // survive the resolved-chart-name projection byte-equal.
11055 for nome in [
11056 "checkout",
11057 "cart-v2",
11058 "a",
11059 "db",
11060 "3rd-party-shim",
11061 "payment-retry",
11062 "0",
11063 ] {
11064 let c = caixa_with_nome(nome);
11065 let expected = format!("{prefix}{nome}", prefix = crate::LAREIRA_CHART_NAME_PREFIX);
11066 assert_eq!(
11067 c.lareira_chart_name(),
11068 expected,
11069 "Caixa::lareira_chart_name must compose \
11070 LAREIRA_CHART_NAME_PREFIX ({prefix:?}) against \
11071 :nome ({nome:?}) verbatim — got {got:?}, \
11072 expected {expected:?}",
11073 prefix = crate::LAREIRA_CHART_NAME_PREFIX,
11074 got = c.lareira_chart_name(),
11075 );
11076 }
11077 }
11078
11079 #[test]
11080 fn lareira_chart_name_starts_with_lifted_prefix() {
11081 // Prefix-shape pin: every [`Caixa::lareira_chart_name`] emission
11082 // must begin with the canonical
11083 // [`crate::LAREIRA_CHART_NAME_PREFIX`] byte-string on every
11084 // input, guarding a hypothetical future implementation that
11085 // migrated the prefix segment to an inline literal (`"lareira-"`)
11086 // that would silently drift from any rebrand of the lifted
11087 // constant. Peer to the sibling
11088 // [`publish_tag_starts_with_default_publish_tag_prefix`] pin on
11089 // the co-resident resolved-publish-tag composer's prefix axis.
11090 for nome in ["checkout", "cart", "a", "payment-retry", "0"] {
11091 let c = caixa_with_nome(nome);
11092 let chart = c.lareira_chart_name();
11093 assert!(
11094 chart.starts_with(crate::LAREIRA_CHART_NAME_PREFIX),
11095 "Caixa::lareira_chart_name emission {chart:?} must start \
11096 with the lifted crate::LAREIRA_CHART_NAME_PREFIX \
11097 ({prefix:?})",
11098 prefix = crate::LAREIRA_CHART_NAME_PREFIX,
11099 );
11100 }
11101 }
11102
11103 #[test]
11104 fn lareira_chart_name_byte_matches_canonical_helper_composition() {
11105 // Byte-parity pin: [`Caixa::lareira_chart_name`] must render
11106 // byte-identically to the manual open-coded
11107 // `caixa_core::lareira_chart_name(caixa.nome())` two-step
11108 // composition every prior substrate-side caller re-derived.
11109 // Guards the paired-site convergence just applied at caixa-helm's
11110 // [`render_chart_for_servico_with`] `ChartDir.name` composer,
11111 // caixa-flux's [`cluster_bundle`] per-CR `chart_name` binding,
11112 // and caixa-tatara's [`process_for_aplicacao`] `release_name`
11113 // composer (all of which now route through this accessor): a
11114 // future implementation of this method that reordered the
11115 // composition arguments, swapped the `<prefix>` constant for a
11116 // different one, or interposed a canonicalization pass on the
11117 // `:nome` axis surfaces here as a caixa-core build-time test
11118 // failure rather than as a downstream Helm chart-render / FluxCD
11119 // reconcile / tatara Process-CR mismatch far from this method's
11120 // source.
11121 for nome in [
11122 "checkout",
11123 "cart-v2",
11124 "a",
11125 "db",
11126 "3rd-party-shim",
11127 "payment-retry",
11128 ] {
11129 let c = caixa_with_nome(nome);
11130 let manual = crate::lareira_chart_name(c.nome());
11131 assert_eq!(
11132 c.lareira_chart_name(),
11133 manual,
11134 "Caixa::lareira_chart_name must byte-equal the manual \
11135 open-coded `caixa_core::lareira_chart_name(caixa.nome())` \
11136 composition across every representative :nome input — \
11137 got {got:?}, expected {manual:?}",
11138 got = c.lareira_chart_name(),
11139 );
11140 }
11141 }
11142
11143 // ── Caixa::oci_chart_ref — resolved-OCI-chart-ref composer ────────
11144
11145 #[test]
11146 fn oci_chart_ref_composes_scheme_and_lareira_chart_name_on_all_shapes() {
11147 // Fail-before-pass-after pin: [`Caixa::oci_chart_ref`] must
11148 // compose [`crate::OCI_SCHEME_PREFIX`] + the caller-supplied
11149 // `registry` + [`crate::lareira_chart_name`]-of-[`Caixa::nome`]
11150 // across the full paired `(registry, :nome)` accept-set — every
11151 // representative registry the substrate-side emitters carry
11152 // (`ghcr.io/pleme-io/charts`, the canonical CAIXA-SDLC §II
11153 // ArtifactHub-tier registry; `ghcr.io/pleme-io`, the bare-org
11154 // arm the sibling `oci_chart_ref_pins_byte_shape_against_prior_
11155 // inline_format` render-side pin exercises; `registry.example.
11156 // com`, an off-org shape; `localhost:5000`, the local-dev shape
11157 // every `feira chart` iteration path lands under) × every DNS-
11158 // 1123 `:nome` shape the peer `validate_nome_accepts_canonical_
11159 // forms` positive-set sweep documents (single-word, hyphen-
11160 // joined, single-char, two-char, digit-start, retry-suffixed).
11161 // Every accept-set pair the peer validate gates let through must
11162 // survive the resolved-OCI-ref projection byte-equal.
11163 for registry in [
11164 "ghcr.io/pleme-io/charts",
11165 "ghcr.io/pleme-io",
11166 "registry.example.com",
11167 "localhost:5000",
11168 ] {
11169 for nome in [
11170 "checkout",
11171 "cart-v2",
11172 "a",
11173 "db",
11174 "3rd-party-shim",
11175 "payment-retry",
11176 "0",
11177 ] {
11178 let c = caixa_with_nome(nome);
11179 let expected = format!(
11180 "{scheme}{registry}/{chart}",
11181 scheme = crate::OCI_SCHEME_PREFIX,
11182 chart = crate::lareira_chart_name(nome),
11183 );
11184 assert_eq!(
11185 c.oci_chart_ref(registry),
11186 expected,
11187 "Caixa::oci_chart_ref must compose \
11188 OCI_SCHEME_PREFIX ({scheme:?}) + registry ({registry:?}) + \
11189 lareira_chart_name(:nome ({nome:?})) verbatim — got {got:?}, \
11190 expected {expected:?}",
11191 scheme = crate::OCI_SCHEME_PREFIX,
11192 got = c.oci_chart_ref(registry),
11193 );
11194 }
11195 }
11196 }
11197
11198 #[test]
11199 fn oci_chart_ref_starts_with_lifted_scheme_prefix() {
11200 // Scheme-prefix-shape pin: every [`Caixa::oci_chart_ref`]
11201 // emission must begin with the canonical
11202 // [`crate::OCI_SCHEME_PREFIX`] byte-string on every input, guarding
11203 // a hypothetical future implementation that migrated the scheme
11204 // segment to an inline literal (`"oci://"`) that would silently
11205 // drift from any rebrand of the lifted constant. Peer to the
11206 // sibling [`publish_tag_starts_with_default_publish_tag_prefix`]
11207 // + [`lareira_chart_name_starts_with_lifted_prefix`] pins on the
11208 // co-resident resolved-publish-tag / resolved-chart-name
11209 // composers' prefix axes.
11210 for registry in [
11211 "ghcr.io/pleme-io/charts",
11212 "ghcr.io/pleme-io",
11213 "localhost:5000",
11214 ] {
11215 for nome in ["checkout", "cart", "a", "payment-retry", "0"] {
11216 let c = caixa_with_nome(nome);
11217 let ref_ = c.oci_chart_ref(registry);
11218 assert!(
11219 ref_.starts_with(crate::OCI_SCHEME_PREFIX),
11220 "Caixa::oci_chart_ref emission {ref_:?} must start \
11221 with the lifted crate::OCI_SCHEME_PREFIX ({scheme:?}) \
11222 — registry ({registry:?}), :nome ({nome:?})",
11223 scheme = crate::OCI_SCHEME_PREFIX,
11224 );
11225 }
11226 }
11227 }
11228
11229 #[test]
11230 fn oci_chart_ref_byte_matches_canonical_helper_composition() {
11231 // Byte-parity pin: [`Caixa::oci_chart_ref`] must render byte-
11232 // identically to the manual open-coded
11233 // `caixa_core::oci_chart_ref(registry, caixa.nome())` two-step
11234 // composition every prior substrate-side caller re-derived.
11235 // Guards the paired-site convergence just applied at caixa-
11236 // tatara's [`derive_chart_ref`] helper (which now routes through
11237 // this accessor): a future implementation of this method that
11238 // reordered the composition arguments, swapped the `<scheme>`
11239 // constant for a different one, migrated the `<chart>` segment
11240 // off the paired [`crate::lareira_chart_name`] composer, or
11241 // interposed a canonicalization pass on either input axis
11242 // surfaces here as a caixa-core build-time test failure rather
11243 // than as a downstream `helm install` / FluxCD OCI-source
11244 // reconcile / tatara `Process`-CR mismatch far from this
11245 // method's source. Sibling to the peer
11246 // [`lareira_chart_name_byte_matches_canonical_helper_composition`]
11247 // / [`publish_tag_byte_matches_manual_composition`] /
11248 // [`canonical_git_url_byte_matches_manual_composition`] byte-
11249 // parity pins that carry the same discipline on the co-resident
11250 // resolved-chart-name / resolved-publish-tag / resolved-git-URL
11251 // composers.
11252 for registry in [
11253 "ghcr.io/pleme-io/charts",
11254 "ghcr.io/pleme-io",
11255 "registry.example.com",
11256 "localhost:5000",
11257 ] {
11258 for nome in [
11259 "checkout",
11260 "cart-v2",
11261 "a",
11262 "db",
11263 "3rd-party-shim",
11264 "payment-retry",
11265 ] {
11266 let c = caixa_with_nome(nome);
11267 let manual = crate::oci_chart_ref(registry, c.nome());
11268 assert_eq!(
11269 c.oci_chart_ref(registry),
11270 manual,
11271 "Caixa::oci_chart_ref must byte-equal the manual \
11272 open-coded `caixa_core::oci_chart_ref(registry, \
11273 caixa.nome())` composition across every representative \
11274 (registry, :nome) pair — registry ({registry:?}), \
11275 :nome ({nome:?}), got {got:?}, expected {manual:?}",
11276 got = c.oci_chart_ref(registry),
11277 );
11278 }
11279 }
11280 }
11281
11282 // ── Caixa::descricao — outer top-level Option<&str> scalar accessor ──
11283
11284 #[test]
11285 fn descricao_returns_descricao_byte_string_verbatim_across_permutations() {
11286 // The canonical per-`Caixa` `:descricao` free-form-prose scalar
11287 // pin: [`Caixa::descricao`] must return the `:descricao` typed
11288 // byte-string verbatim as an `Option<&str>`, byte-equal to the
11289 // raw `self.descricao.as_deref()` access across every
11290 // representative value in the accept-set — `None` (the "omit
11291 // the slot to defer to the per-renderer `caixa.nome`-derived
11292 // fallback" arm every existing fixture without a `:descricao`
11293 // line carries), `Some("")` (a past-the-guard sentinel that
11294 // pins the accessor doesn't perform a silent `Some("") → None`
11295 // collapse on the empty arm — validate rejects `Some("")`
11296 // through `DescricaoEmpty` but the accessor must ship the raw
11297 // slot verbatim so a validate-time gate regression surfaces at
11298 // the caixa-helm / caixa-feira emit boundary rather than being
11299 // silently absorbed into the per-renderer `caixa.nome`-derived
11300 // fallback), `Some("Checkout flow.")` (the canonical one-line
11301 // prose descriptor the peer
11302 // `validate_descricao_accepts_canonical_value` positive sweep
11303 // exercises), `Some("Canonical Rust→wasm32-wasip2 caixa
11304 // Servico.")` (the multi-byte Unicode continuation-byte shape
11305 // the `hello-rio` fixture carries), `Some("→ — · ✓")` (a
11306 // multi-glyph Unicode shape the peer
11307 // `is_chart_description_shape` predicate accepts), and five
11308 // past-the-guard sentinels for the `DescricaoInvalid` refusal
11309 // cases (`Some(" Checkout flow.")` leading-whitespace,
11310 // `Some("Checkout flow. ")` trailing-whitespace,
11311 // `Some("Checkout\nflow.")` embedded-LF,
11312 // `Some("Checkout\tflow.")` embedded-TAB, and
11313 // `Some("Checkout\x00flow.")` embedded-NUL — the sentinels pin
11314 // the accessor doesn't silently absorb the refusal cases into
11315 // a fallback).
11316 //
11317 // Third outer top-level [`Caixa`] `Option<&str>`-return scalar
11318 // accessor pin on the substrate primitive — sibling of the peer
11319 // [`Caixa::licenca`] (6d5bc28) and [`Caixa::repositorio`]
11320 // (cc7332d) pins that opened the "outer [`Caixa`]
11321 // `Option<&str>` scalar" projection pin pattern this pin folds
11322 // on. Sibling in shape to the peer per-`:placement`
11323 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
11324 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
11325 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
11326 // axes, extended onto the outer top-level [`Caixa`] universal-
11327 // axis surface. Pins against a future silent detour that
11328 // returned an owned `Option<String>` (which would type-check
11329 // but silently allocate on every accessor call, breaking the
11330 // zero-cost projection every peer sibling accessor carries), a
11331 // `Some("") → None` collapse (which would silently absorb the
11332 // `DescricaoEmpty` refusal case at the accessor boundary and
11333 // the caixa-helm `Chart.yaml` `description:` fold would
11334 // silently render a `caixa.nome`-derived fallback on a
11335 // struct-literal `Caixa { descricao: Some(""), .. }`), or a
11336 // `None → Some(<default>)` collapse (which would silently
11337 // reify the per-renderer `caixa.nome`-derived fallback at the
11338 // accessor boundary and every downstream consumer keying off
11339 // the `Option::is_none()` discriminator would lose the "author
11340 // omitted the slot" signal).
11341 for descricao in [
11342 None,
11343 Some(""),
11344 Some("Checkout flow."),
11345 Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
11346 Some("→ — · ✓"),
11347 Some(" Checkout flow."),
11348 Some("Checkout flow. "),
11349 Some("Checkout\nflow."),
11350 Some("Checkout\tflow."),
11351 Some("Checkout\x00flow."),
11352 ] {
11353 let c = caixa_with_descricao(descricao);
11354 assert_eq!(
11355 c.descricao(),
11356 descricao,
11357 "Caixa::descricao must return :descricao verbatim (got \
11358 {:?}, expected {descricao:?})",
11359 c.descricao(),
11360 );
11361 assert_eq!(
11362 c.descricao(),
11363 c.descricao.as_deref(),
11364 "Caixa::descricao must byte-equal the raw \
11365 `self.descricao.as_deref()` field access across every \
11366 value in the Option<&str> accept-set",
11367 );
11368 }
11369 }
11370
11371 #[test]
11372 fn validate_descricao_empty_arm_routes_through_accessor() {
11373 // Composition pin: [`Caixa::validate_descricao`]'s empty-arm
11374 // gate must key off [`Caixa::descricao`], not the raw
11375 // `self.descricao.as_deref()` field access. Structurally: a
11376 // `Caixa { descricao: Some(""), .. }` must surface the
11377 // `DescricaoEmpty` refusal exactly, and a
11378 // `Caixa { descricao: Some("Checkout flow."), .. }` (the
11379 // canonical one-line-prose form) must pass validate. The pair
11380 // jointly pins the accessor + validate-gate composition: any
11381 // future silent detour that had the accessor return `None` on
11382 // the empty arm (a `.filter(|s| !s.is_empty())` collapse) would
11383 // silently absorb the `DescricaoEmpty` refusal at the accessor
11384 // boundary and the validate gate would accept a struct-literal
11385 // `Caixa { descricao: Some(""), .. }` — the composition pin
11386 // catches that at caixa-core build time.
11387 //
11388 // Peer of the [`Caixa::licenca`] (6d5bc28)
11389 // `validate_licenca_empty_arm_routes_through_accessor` and
11390 // [`Caixa::repositorio`] (cc7332d)
11391 // `validate_repositorio_empty_arm_routes_through_accessor`
11392 // composition pins on the sibling outer top-level [`Caixa`]
11393 // `Option<&str>` universal-axis surface — same "the validate /
11394 // shape-gate predicate must route through the substrate-
11395 // primitive typed dispatch" discipline extended onto the third
11396 // outer top-level [`Caixa`] universal-axis `Option<&str>`-
11397 // composition surface.
11398 let c = caixa_with_descricao(Some(""));
11399 assert!(
11400 matches!(c.validate_descricao(), Err(ManifestError::DescricaoEmpty),),
11401 "validate_descricao must reject descricao == Some(\"\") \
11402 with DescricaoEmpty — the accessor and the validate gate \
11403 must route through the same substrate-primitive typed \
11404 dispatch on the :descricao empty arm",
11405 );
11406 let c = caixa_with_descricao(Some("Checkout flow."));
11407 assert!(
11408 c.validate_descricao().is_ok(),
11409 "validate_descricao must accept descricao == \
11410 Some(\"Checkout flow.\") (the canonical one-line-prose \
11411 chart-description shape)",
11412 );
11413 }
11414
11415 #[test]
11416 fn descricao_projects_option_str_by_borrow() {
11417 // The by-borrow pin: [`Caixa::descricao`] returns
11418 // `Option<&str>` by borrow — the `&str` borrows the underlying
11419 // `String` storage of the `Option<String>` slot and the
11420 // accessor must not allocate a fresh `String` on every call.
11421 // Peer of the [`Caixa::licenca`] (6d5bc28) and
11422 // [`Caixa::repositorio`] (cc7332d) by-borrow pins on the peer
11423 // outer top-level [`Caixa`] `Option<&str>`-return axes, and of
11424 // the per-`:placement`
11425 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
11426 // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
11427 // return axis, extended onto the third outer top-level
11428 // [`Caixa`] universal-axis `Option<&str>` shape — the
11429 // accessor's returned `&str` must borrow from `&self` (the
11430 // returned reference's lifetime is tied to `&self`), and
11431 // calling the accessor twice on the same [`Caixa`] must yield
11432 // the same `Option<&str>` verbatim (idempotent, no side
11433 // effects on `&self`).
11434 //
11435 // Pins against a future silent detour that returned an owned
11436 // `Option<String>` (which would type-check but silently
11437 // allocate on every call, breaking the zero-cost projection
11438 // every peer sibling accessor carries), or a one-arm-only
11439 // accessor that returned a saturating value on some sentinel
11440 // input (breaking the pass-through invariant the sibling
11441 // required-scalar accessors carry).
11442 for descricao in [
11443 None,
11444 Some(""),
11445 Some("Checkout flow."),
11446 Some("Canonical Rust→wasm32-wasip2 caixa Servico."),
11447 ] {
11448 let c = caixa_with_descricao(descricao);
11449 let first = c.descricao();
11450 let second = c.descricao();
11451 assert_eq!(
11452 first, second,
11453 "Caixa::descricao must be idempotent — two successive \
11454 calls on the same &self must return the same \
11455 Option<&str>",
11456 );
11457 assert_eq!(
11458 first, descricao,
11459 "Caixa::descricao must return :descricao verbatim by \
11460 borrow — got {first:?}, expected {descricao:?}",
11461 );
11462 }
11463 }
11464
11465 // ── validate_edicao — universal-axis language-edition shape ──
11466
11467 fn caixa_with_edicao(edicao: Option<&str>) -> Caixa {
11468 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11469 c.edicao = edicao.map(String::from);
11470 c
11471 }
11472
11473 #[test]
11474 fn validate_edicao_accepts_none() {
11475 // The omit-the-slot identity: `:edicao` is optional. The
11476 // gate is a no-op when the author didn't declare a value —
11477 // every caixa without an `:edicao` line trivially passes,
11478 // and the substrate-side build pipeline falls back to the
11479 // documented default edition. Mirrors the peer
11480 // `validate_licenca_accepts_none` posture on the sibling
11481 // `Option<String>` Caixa slot.
11482 let c = caixa_with_edicao(None);
11483 c.validate_edicao().unwrap();
11484 }
11485
11486 #[test]
11487 fn validate_edicao_accepts_canonical_value() {
11488 // Positive control: the canonical `"2026"` edition every
11489 // existing renderer-side fixture (`caixa-helm`, `caixa-flux`,
11490 // `caixa-mesh`) carries by construction passes the gate.
11491 // Future-introduced sibling editions (`"2027"`, `"2030"`,
11492 // `"2049"`) that match the same 4-digit ASCII decimal year
11493 // shape must also trivially pass — the structural shape
11494 // predicate accepts every well-formed year regardless of
11495 // whether the substrate yet understands the specific value
11496 // (a future known-edition allowlist tightens that).
11497 for ed in ["2026", "2027", "2030", "2049"] {
11498 let c = caixa_with_edicao(Some(ed));
11499 c.validate_edicao()
11500 .unwrap_or_else(|err| panic!("canonical {ed:?} must pass: {err:?}"));
11501 }
11502 }
11503
11504 #[test]
11505 fn validate_edicao_rejects_empty_some() {
11506 // Canonical paste-from-blank-doc footgun. Without this gate
11507 // the empty `Some("")` silently lands as `(:edicao "")` in
11508 // the rendered caixa.lisp and a future renderer-side
11509 // consumer's `Option::unwrap_or_else` (which only fires on
11510 // `None`) skips its fallback. Mirrors the peer
11511 // [`ManifestError::LicencaEmpty`] empty-arm on the sibling
11512 // `Option<String>` Caixa slot.
11513 let c = caixa_with_edicao(Some(""));
11514 let err = c.validate_edicao().unwrap_err();
11515 assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
11516 }
11517
11518 #[test]
11519 fn validate_edicao_rejects_free_form_non_year() {
11520 // Free-form non-year footgun: the bare `"x"` / `"latest"` /
11521 // `"nightly"` shapes carry no operational meaning on the
11522 // substrate's build-time edition selector. Until this gate
11523 // landed the bare empty-arm check let every such value
11524 // through and broke far from the source caixa.lisp. Peer
11525 // with the shape-predicate cascade
11526 // `validate_repositorio_rejects_missing_colon_separator`
11527 // establishes past its own empty arm.
11528 for ed in ["x", "latest", "nightly", "stable"] {
11529 let c = caixa_with_edicao(Some(ed));
11530 let err = c.validate_edicao().unwrap_err();
11531 assert!(
11532 matches!(err, ManifestError::EdicaoInvalid { .. }),
11533 "expected EdicaoInvalid on {ed:?}, got {err:?}",
11534 );
11535 }
11536 }
11537
11538 #[test]
11539 fn validate_edicao_rejects_trailing_whitespace() {
11540 // Paste-from-doc whitespace footgun. A trailing space in
11541 // the `:edicao` value would silently break the substrate's
11542 // build-time edition match-table lookup at the rendered
11543 // artifact's edition-selector consumer. The shape predicate
11544 // refuses every whitespace byte by construction (any byte
11545 // outside `0-9` fails `is_ascii_digit`). Peer with
11546 // `validate_repositorio_rejects_whitespace`.
11547 let c = caixa_with_edicao(Some("2026 "));
11548 let err = c.validate_edicao().unwrap_err();
11549 let ManifestError::EdicaoInvalid { edicao, .. } = err else {
11550 panic!("expected EdicaoInvalid, got {err:?}");
11551 };
11552 assert_eq!(edicao, "2026 ");
11553 }
11554
11555 #[test]
11556 fn validate_edicao_rejects_leading_whitespace() {
11557 // Symmetric paste-from-doc whitespace footgun on the leading
11558 // boundary — the gate refuses every shape with a non-digit
11559 // byte by construction.
11560 let c = caixa_with_edicao(Some(" 2026"));
11561 let err = c.validate_edicao().unwrap_err();
11562 assert!(
11563 matches!(err, ManifestError::EdicaoInvalid { .. }),
11564 "got {err:?}",
11565 );
11566 }
11567
11568 #[test]
11569 fn validate_edicao_rejects_control_char() {
11570 // Paste-from-multiline-doc CRLF footgun — control characters
11571 // at the value boundary break the substrate's build-time
11572 // edition-selector parser. Peer with
11573 // `validate_repositorio_rejects_control_char`.
11574 let c = caixa_with_edicao(Some("2026\n"));
11575 let err = c.validate_edicao().unwrap_err();
11576 assert!(
11577 matches!(err, ManifestError::EdicaoInvalid { .. }),
11578 "got {err:?}",
11579 );
11580 }
11581
11582 #[test]
11583 fn validate_edicao_rejects_non_ascii_lookalike() {
11584 // Fullwidth-keyboard look-alike footgun — `"2026"` is
11585 // the U+FF12 U+FF10 U+FF12 U+FF16 sequence (CJK fullwidth
11586 // digits), 4 codepoints but 12 UTF-8 bytes; the substrate's
11587 // edition selector wants an ASCII year, and the gate
11588 // refuses every non-ASCII shape by construction (length in
11589 // bytes is 12 ≠ 4, *and* every byte falls outside
11590 // `is_ascii_digit`'s `0-9` range).
11591 let c = caixa_with_edicao(Some("2026"));
11592 let err = c.validate_edicao().unwrap_err();
11593 assert!(
11594 matches!(err, ManifestError::EdicaoInvalid { .. }),
11595 "got {err:?}",
11596 );
11597 }
11598
11599 #[test]
11600 fn validate_edicao_rejects_version_tag_prefix() {
11601 // Common version-tag idiom footgun — `"v2026"` / `"e2026"`
11602 // / `"r2026"` are familiar shapes from git-tag / Rust
11603 // edition / release-tag conventions that don't apply to
11604 // the year-shaped edition axis. The shape predicate refuses
11605 // every leading non-digit prefix.
11606 for ed in ["v2026", "e2026", "r2026"] {
11607 let c = caixa_with_edicao(Some(ed));
11608 let err = c.validate_edicao().unwrap_err();
11609 assert!(
11610 matches!(err, ManifestError::EdicaoInvalid { .. }),
11611 "expected EdicaoInvalid on {ed:?}, got {err:?}",
11612 );
11613 }
11614 }
11615
11616 #[test]
11617 fn validate_edicao_rejects_decimal_shape() {
11618 // Decimal-shaped pseudo-version footgun — `"2026.1"` /
11619 // `"2026.0"` are familiar shapes from semver / float
11620 // conventions that don't apply to the year-shaped edition
11621 // axis. The shape predicate refuses every non-digit byte
11622 // (`.` falls outside `is_ascii_digit`).
11623 for ed in ["2026.1", "2026.0", "2026.0.1"] {
11624 let c = caixa_with_edicao(Some(ed));
11625 let err = c.validate_edicao().unwrap_err();
11626 assert!(
11627 matches!(err, ManifestError::EdicaoInvalid { .. }),
11628 "expected EdicaoInvalid on {ed:?}, got {err:?}",
11629 );
11630 }
11631 }
11632
11633 #[test]
11634 fn validate_edicao_rejects_wrong_length_numeric() {
11635 // Wrong-length numeric footgun — `"26"` (truncated) /
11636 // `"202"` (truncated) / `"20260"` (extra digit) / `"00026"`
11637 // (zero-padded too wide) all parse as integers but don't
11638 // name a 4-digit year. The shape predicate refuses every
11639 // value whose length isn't exactly 4 bytes.
11640 for ed in ["26", "202", "20260", "00026", "9"] {
11641 let c = caixa_with_edicao(Some(ed));
11642 let err = c.validate_edicao().unwrap_err();
11643 assert!(
11644 matches!(err, ManifestError::EdicaoInvalid { .. }),
11645 "expected EdicaoInvalid on {ed:?}, got {err:?}",
11646 );
11647 }
11648 }
11649
11650 #[test]
11651 fn validate_edicao_empty_takes_precedence_over_shape() {
11652 // Empty-first cascade pin: the empty `Some("")` surfaces
11653 // the narrower `EdicaoEmpty` not the shape-predicate-
11654 // wrapped `EdicaoInvalid`, mirroring the peer
11655 // `validate_repositorio_empty_takes_precedence_over_shape`
11656 // (`RepositorioEmpty` → `RepositorioInvalid`),
11657 // `NomeEmpty` → `NomeInvalid`, `VersaoEmpty` →
11658 // `VersaoInvalid`, `FonteRepoEmpty` → `FonteRepoInvalid`
11659 // cascades. The shape predicate also refuses the empty
11660 // input (defensively — `s.len() != 4`), but the
11661 // manifest-layer empty arm runs first to surface the
11662 // narrower diagnostic verbatim.
11663 let c = caixa_with_edicao(Some(""));
11664 let err = c.validate_edicao().unwrap_err();
11665 assert!(matches!(err, ManifestError::EdicaoEmpty), "got {err:?}",);
11666 }
11667
11668 #[test]
11669 fn validate_edicao_template_passes() {
11670 // Round-trip pin: the bare `Caixa::template` shape (which
11671 // carries `:edicao "2026"` verbatim) passes the gate by
11672 // construction. A future template-shape change that
11673 // introduced `(:edicao "")` or a non-year value would
11674 // surface here as a regression. Mirrors the peer
11675 // `validate_licenca_template_passes` pin.
11676 let c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
11677 c.validate_edicao().unwrap();
11678 }
11679
11680 #[test]
11681 fn validate_edicao_diagnostic_names_offending_slot() {
11682 // Diagnostic-shape pin (peer with
11683 // `validate_licenca_diagnostic_names_offending_slot`): the
11684 // error's Display surfaces the `:edicao` slot name verbatim,
11685 // so a `feira lint` run can render the diagnostic without
11686 // re-parsing and the author can grep their caixa.lisp for
11687 // the offending `:edicao` line.
11688 let c = caixa_with_edicao(Some(""));
11689 let rendered = c.validate_edicao().unwrap_err().to_string();
11690 assert!(
11691 rendered.contains(":edicao"),
11692 "diagnostic must name the offending slot: {rendered}",
11693 );
11694 }
11695
11696 #[test]
11697 fn validate_edicao_invalid_diagnostic_carries_offending_value() {
11698 // Diagnostic-shape pin on the shape-predicate arm (peer
11699 // with `validate_repositorio_diagnostic_carries_offending_value`):
11700 // the error's Display surfaces the offending value + slot
11701 // name verbatim, so a `feira lint` run can render the
11702 // diagnostic without re-parsing and the author can grep
11703 // their caixa.lisp for the offending `:edicao` value.
11704 let c = caixa_with_edicao(Some("v2026"));
11705 let rendered = c.validate_edicao().unwrap_err().to_string();
11706 assert!(
11707 rendered.contains(":edicao"),
11708 "diagnostic must name the offending slot: {rendered}",
11709 );
11710 assert!(
11711 rendered.contains("v2026"),
11712 "diagnostic must quote the offending value: {rendered}",
11713 );
11714 }
11715
11716 // ── Caixa::edicao — outer top-level Option<&str> scalar accessor ──
11717
11718 #[test]
11719 fn edicao_returns_edicao_byte_string_verbatim_across_permutations() {
11720 // The canonical per-`Caixa` `:edicao` language-edition scalar
11721 // pin: [`Caixa::edicao`] must return the `:edicao` typed
11722 // byte-string verbatim as an `Option<&str>`, byte-equal to the
11723 // raw `self.edicao.as_deref()` access across every representative
11724 // value in the accept-set — `None` (the "omit the slot to defer
11725 // to the substrate's default edition" arm every existing
11726 // [`caixa-resolver`] fixture without an `:edicao` line carries),
11727 // `Some("")` (a past-the-guard sentinel that pins the accessor
11728 // doesn't perform a silent `Some("") → None` collapse on the
11729 // empty arm — validate rejects `Some("")` through `EdicaoEmpty`
11730 // but the accessor must ship the raw slot verbatim so a
11731 // validate-time gate regression surfaces at any future edition-
11732 // aware consumer's boundary rather than being silently absorbed
11733 // into the substrate's default edition), `Some("2026")` (the
11734 // canonical 4-digit-ASCII-decimal-year shape every `feira init`
11735 // template scaffolds via [`Caixa::template`] and every
11736 // renderer-side fixture at `caixa-helm/src/lib.rs:978` /
11737 // `caixa-flux/src/lib.rs:2319` / `caixa-mesh/src/lib.rs:3208`
11738 // carries by construction), `Some("2018")` / `Some("2021")` /
11739 // `Some("2024")` (canonical 4-digit-ASCII-decimal-year shapes
11740 // peer with Cargo's `[package] edition` grammar every future-
11741 // introduced sibling to `"2026"` will follow), and eight
11742 // past-the-guard sentinels for the `EdicaoInvalid` refusal cases
11743 // (`Some("2026 ")` trailing-whitespace, `Some(" 2026")` leading-
11744 // whitespace, `Some("2026\n")` embedded-LF, `Some("2026")`
11745 // fullwidth-non-ASCII-lookalike, `Some("v2026")` version-tag-
11746 // prefix, `Some("2026.1")` decimal-shape, `Some("26")` wrong-
11747 // length-numeric, `Some("latest")` free-form-non-year — the
11748 // sentinels pin the accessor doesn't silently absorb the
11749 // refusal cases into a substrate-default-edition fallback).
11750 //
11751 // Fourth and final outer top-level [`Caixa`] `Option<&str>`-
11752 // return scalar accessor pin on the substrate primitive —
11753 // sibling of the peer [`Caixa::licenca`] (6d5bc28),
11754 // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
11755 // (3f16e2f) pins that opened the "outer [`Caixa`]
11756 // `Option<&str>` scalar" projection pin pattern this pin folds
11757 // on. Sibling in shape to the peer per-`:placement`
11758 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) /
11759 // [`crate::aplicacao::Placement::affinity`] (74ec2d3) accessor
11760 // pins on the sibling per-M3-mesh-slot `Option<&str>`-return
11761 // axes, extended onto the outer top-level [`Caixa`] universal-
11762 // axis surface's last unlifted `Option<String>` slot. Pins
11763 // against a future silent detour that returned an owned
11764 // `Option<String>` (which would type-check but silently
11765 // allocate on every accessor call, breaking the zero-cost
11766 // projection every peer sibling accessor carries), a
11767 // `Some("") → None` collapse (which would silently absorb the
11768 // `EdicaoEmpty` refusal case at the accessor boundary and any
11769 // future edition-aware consumer would silently fall back to
11770 // the substrate's default edition on a struct-literal
11771 // `Caixa { edicao: Some(""), .. }`), or a
11772 // `None → Some("2026")` collapse (which would silently reify
11773 // the substrate's default edition at the accessor boundary
11774 // and every downstream consumer keying off the
11775 // `Option::is_none()` discriminator would lose the "author
11776 // omitted the slot" signal).
11777 for edicao in [
11778 None,
11779 Some(""),
11780 Some("2026"),
11781 Some("2018"),
11782 Some("2021"),
11783 Some("2024"),
11784 Some("2026 "),
11785 Some(" 2026"),
11786 Some("2026\n"),
11787 Some("2026"),
11788 Some("v2026"),
11789 Some("2026.1"),
11790 Some("26"),
11791 Some("latest"),
11792 ] {
11793 let c = caixa_with_edicao(edicao);
11794 assert_eq!(
11795 c.edicao(),
11796 edicao,
11797 "Caixa::edicao must return :edicao verbatim (got {:?}, \
11798 expected {edicao:?})",
11799 c.edicao(),
11800 );
11801 assert_eq!(
11802 c.edicao(),
11803 c.edicao.as_deref(),
11804 "Caixa::edicao must byte-equal the raw \
11805 `self.edicao.as_deref()` field access across every \
11806 value in the Option<&str> accept-set",
11807 );
11808 }
11809 }
11810
11811 #[test]
11812 fn validate_edicao_empty_arm_routes_through_accessor() {
11813 // Composition pin: [`Caixa::validate_edicao`]'s empty-arm gate
11814 // must key off [`Caixa::edicao`], not the raw
11815 // `self.edicao.as_deref()` field access. Structurally: a
11816 // `Caixa { edicao: Some(""), .. }` must surface the
11817 // `EdicaoEmpty` refusal exactly, and a
11818 // `Caixa { edicao: Some("2026"), .. }` (the canonical
11819 // 4-digit-ASCII-decimal-year form) must pass validate. The
11820 // pair jointly pins the accessor + validate-gate composition:
11821 // any future silent detour that had the accessor return `None`
11822 // on the empty arm (a `.filter(|s| !s.is_empty())` collapse)
11823 // would silently absorb the `EdicaoEmpty` refusal at the
11824 // accessor boundary and the validate gate would accept a
11825 // struct-literal `Caixa { edicao: Some(""), .. }` — the
11826 // composition pin catches that at caixa-core build time.
11827 //
11828 // Peer of the [`Caixa::licenca`] (6d5bc28)
11829 // `validate_licenca_empty_arm_routes_through_accessor`,
11830 // [`Caixa::repositorio`] (cc7332d)
11831 // `validate_repositorio_empty_arm_routes_through_accessor`,
11832 // and [`Caixa::descricao`] (3f16e2f)
11833 // `validate_descricao_empty_arm_routes_through_accessor`
11834 // composition pins on the sibling outer top-level [`Caixa`]
11835 // `Option<&str>` universal-axis surface — same "the validate /
11836 // shape-gate predicate must route through the substrate-
11837 // primitive typed dispatch" discipline extended onto the
11838 // fourth and final outer top-level [`Caixa`] universal-axis
11839 // `Option<&str>`-composition surface, closing the accessor-
11840 // composition family.
11841 let c = caixa_with_edicao(Some(""));
11842 assert!(
11843 matches!(c.validate_edicao(), Err(ManifestError::EdicaoEmpty)),
11844 "validate_edicao must reject edicao == Some(\"\") with \
11845 EdicaoEmpty — the accessor and the validate gate must \
11846 route through the same substrate-primitive typed dispatch \
11847 on the :edicao empty arm",
11848 );
11849 let c = caixa_with_edicao(Some("2026"));
11850 assert!(
11851 c.validate_edicao().is_ok(),
11852 "validate_edicao must accept edicao == Some(\"2026\") \
11853 (the canonical 4-digit-ASCII-decimal-year shape)",
11854 );
11855 }
11856
11857 #[test]
11858 fn edicao_projects_option_str_by_borrow() {
11859 // The by-borrow pin: [`Caixa::edicao`] returns
11860 // `Option<&str>` by borrow — the `&str` borrows the underlying
11861 // `String` storage of the `Option<String>` slot and the
11862 // accessor must not allocate a fresh `String` on every call.
11863 // Peer of the [`Caixa::licenca`] (6d5bc28),
11864 // [`Caixa::repositorio`] (cc7332d), and [`Caixa::descricao`]
11865 // (3f16e2f) by-borrow pins on the peer outer top-level
11866 // [`Caixa`] `Option<&str>`-return axes, and of the
11867 // per-`:placement`
11868 // [`crate::aplicacao::Placement::shard_key`] (7cd2a28) by-
11869 // borrow pin on the peer per-M3-mesh-slot `Option<&str>`-
11870 // return axis, extended onto the fourth and final outer top-
11871 // level [`Caixa`] universal-axis `Option<&str>` shape — the
11872 // accessor's returned `&str` must borrow from `&self` (the
11873 // returned reference's lifetime is tied to `&self`), and
11874 // calling the accessor twice on the same [`Caixa`] must yield
11875 // the same `Option<&str>` verbatim (idempotent, no side
11876 // effects on `&self`).
11877 //
11878 // Pins against a future silent detour that returned an owned
11879 // `Option<String>` (which would type-check but silently
11880 // allocate on every call, breaking the zero-cost projection
11881 // every peer sibling accessor carries), or a one-arm-only
11882 // accessor that returned a saturating value on some sentinel
11883 // input (breaking the pass-through invariant the sibling
11884 // required-scalar accessors carry).
11885 for edicao in [None, Some(""), Some("2026"), Some("2018")] {
11886 let c = caixa_with_edicao(edicao);
11887 let first = c.edicao();
11888 let second = c.edicao();
11889 assert_eq!(
11890 first, second,
11891 "Caixa::edicao must be idempotent — two successive \
11892 calls on the same &self must return the same \
11893 Option<&str>",
11894 );
11895 assert_eq!(
11896 first, edicao,
11897 "Caixa::edicao must return :edicao verbatim by \
11898 borrow — got {first:?}, expected {edicao:?}",
11899 );
11900 }
11901 }
11902
11903 #[test]
11904 fn nome_returns_nome_byte_string_verbatim_across_permutations() {
11905 // The canonical per-`Caixa` `:nome` universal-axis DNS-1123-
11906 // label caixa-identity scalar pin: [`Caixa::nome`] must return
11907 // the `:nome` typed `String` verbatim as `&str`, byte-equal to
11908 // the raw field access across every representative value in
11909 // the accept-set — the canonical `"demo"` template baseline
11910 // (the same `feira init`-scaffolded default the sibling
11911 // `validate_nome_accepts_canonical_template` positive-control
11912 // gate pins), plus every sibling per-typed-slot atom accessor's
11913 // canonical positive-arm byte-string (`"catalog"` per
11914 // [`crate::aplicacao::Membro::nome`], `"cart"` per the peer
11915 // per-`:contratos` `:de`, `"hello-rio"` per the canonical
11916 // `caixa-helm`/`caixa-flux` cross-crate integration-test
11917 // fixture, `"checkout"` per the M3 mesh-slot Aplicacao
11918 // canonical example), plus every past-the-guard sentinel for
11919 // the `NomeEmpty` / `NomeInvalid` / `NomeChartNameBudgetExceeded`
11920 // refusal cases (`""`, `"Bad_Name"`, `"a"` × 56 — 56 bytes fits
11921 // the bare DNS-1123 63-byte cap but overflows the joint
11922 // `lareira-<nome>` chart-name budget the sibling
11923 // [`Caixa::validate_nome_chart_name_budget`] gate closes on).
11924 //
11925 // The past-the-guard sentinels pin the accessor doesn't
11926 // silently absorb the refusal cases into a template-derived
11927 // fallback (a future `.nome().is_empty().then(|| "demo")`
11928 // collapse would silently absorb the `NomeEmpty` refusal at
11929 // the accessor boundary and the validate gate would accept a
11930 // struct-literal `Caixa { nome: "".into(), .. }` — the pin
11931 // catches that at caixa-core build time).
11932 //
11933 // First outer top-level [`Caixa`] `&str`-return required-
11934 // scalar accessor pin — opens the "outer [`Caixa`] `&str`
11935 // required-scalar" projection pattern the sibling per-`Caixa`
11936 // `:versao` future lift folds on. Sibling in shape to the peer
11937 // per-`:membros` [`crate::aplicacao::Membro::nome`] (4a32abf)
11938 // required-`String`-carry accessor pin on the sibling per-
11939 // sub-struct required-axis, extended onto the outer top-level
11940 // [`Caixa`] universal-axis required-`String`-carry axis.
11941 for nome in [
11942 "demo",
11943 "catalog",
11944 "cart",
11945 "hello-rio",
11946 "checkout",
11947 "",
11948 "Bad_Name",
11949 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
11950 ] {
11951 let c = caixa_with_nome(nome);
11952 assert_eq!(
11953 c.nome(),
11954 nome,
11955 "Caixa::nome must return :nome verbatim (got {}, \
11956 expected {nome})",
11957 c.nome(),
11958 );
11959 assert_eq!(
11960 c.nome(),
11961 c.nome.as_str(),
11962 "Caixa::nome must byte-equal the raw .nome field \
11963 access across every value in the String accept-set",
11964 );
11965 }
11966 }
11967
11968 #[test]
11969 fn validate_nome_empty_arm_routes_through_accessor() {
11970 // Composition pin: [`Caixa::validate_nome`]'s empty-arm must
11971 // key off [`Caixa::nome`], not the raw `.nome` field access.
11972 // Structurally: a `Caixa { nome: "".into(), .. }` must surface
11973 // the `NomeEmpty` refusal exactly, and the canonical `"demo"`
11974 // template baseline (the peer positive-arm the sibling
11975 // `validate_nome_accepts_canonical_template` gate carves out)
11976 // must pass validate. The pair jointly pins the accessor +
11977 // validate-gate composition: any future silent detour that
11978 // had the accessor return a fresh `"demo"` on the empty arm
11979 // (a `.nome().is_empty().then(|| "demo")` fallback collapse)
11980 // would silently absorb the `NomeEmpty` refusal at the
11981 // accessor boundary and the validate gate would accept a
11982 // struct-literal `Caixa { nome: "".into(), .. }` — the
11983 // composition pin catches that at caixa-core build time.
11984 //
11985 // Peer of the sibling per-`Caixa`
11986 // `validate_licenca_empty_arm_routes_through_accessor` (6d5bc28)
11987 // / `validate_repositorio_empty_arm_routes_through_accessor`
11988 // (cc7332d) / `validate_descricao_empty_arm_routes_through_accessor`
11989 // (3f16e2f) / `validate_edicao_empty_arm_routes_through_accessor`
11990 // (2641cbd) composition pins on the sibling outer top-level
11991 // [`Caixa`] `Option<&str>` axes — same "the validate /
11992 // shape-gate predicate must route through the substrate-
11993 // primitive typed dispatch" discipline extended onto the peer
11994 // outer top-level [`Caixa`] required-`&str` composition axis.
11995 let c = caixa_with_nome("");
11996 assert!(
11997 matches!(c.validate_nome(), Err(ManifestError::NomeEmpty)),
11998 "validate_nome must reject nome == \"\" with NomeEmpty — \
11999 the accessor and the validate gate must route through the \
12000 same substrate-primitive typed dispatch on the :nome \
12001 empty-arm",
12002 );
12003 let c = caixa_with_nome("demo");
12004 assert!(
12005 c.validate_nome().is_ok(),
12006 "validate_nome must accept nome == \"demo\" (the canonical \
12007 DNS-1123-label template baseline)",
12008 );
12009 }
12010
12011 #[test]
12012 fn nome_projects_str_by_borrow() {
12013 // The by-borrow pin: [`Caixa::nome`] returns `&str` by borrow
12014 // — the `&str` borrows the underlying `String` storage of the
12015 // required `nome` slot and the accessor must not allocate a
12016 // fresh `String` on every call. Peer of the [`Caixa::licenca`]
12017 // (6d5bc28) / [`Caixa::repositorio`] (cc7332d) /
12018 // [`Caixa::descricao`] (3f16e2f) / [`Caixa::edicao`] (2641cbd)
12019 // by-borrow pins on the peer outer top-level [`Caixa`]
12020 // `Option<&str>`-return axes, extended onto the first outer
12021 // top-level [`Caixa`] required-`&str`-return axis — the
12022 // accessor's returned `&str` must borrow from `&self` (the
12023 // returned reference's lifetime is tied to `&self`), and
12024 // calling the accessor twice on the same [`Caixa`] must yield
12025 // the same `&str` verbatim (idempotent, no side effects on
12026 // `&self`).
12027 //
12028 // Pins against a future silent detour that returned an owned
12029 // `String` (which would type-check but silently allocate on
12030 // every call, breaking the zero-cost projection every peer
12031 // sibling accessor carries), an accidental
12032 // `.nome.to_lowercase()` detour that returned a fresh
12033 // allocation through an already-DNS-1123-lowercase-only
12034 // string (breaking a future `const fn` regression), or a
12035 // one-arm-only accessor that returned a canonicalized value
12036 // on some sentinel input (breaking the pass-through invariant
12037 // the sibling required-scalar accessors carry).
12038 for nome in ["demo", "catalog", "hello-rio", "checkout"] {
12039 let c = caixa_with_nome(nome);
12040 let first = c.nome();
12041 let second = c.nome();
12042 assert_eq!(
12043 first, second,
12044 "Caixa::nome must be idempotent — two successive calls \
12045 on the same &self must return the same &str",
12046 );
12047 assert_eq!(
12048 first, nome,
12049 "Caixa::nome must return :nome verbatim by borrow — \
12050 got {first}, expected {nome}",
12051 );
12052 }
12053 }
12054
12055 #[test]
12056 fn versao_returns_versao_byte_string_verbatim_across_permutations() {
12057 // The canonical per-`Caixa` `:versao` universal-axis SemVer-2
12058 // pinned-version scalar pin: [`Caixa::versao`] must return the
12059 // `:versao` typed `String` verbatim as `&str`, byte-equal to the
12060 // raw `.versao` field access across every representative value
12061 // in the accept-set — the canonical `"0.1.0"` template baseline
12062 // (the same `feira init`-scaffolded default the sibling
12063 // `validate_versao_accepts_canonical_template` positive-control
12064 // gate pins), plus every canonical SemVer-2 shape the sibling
12065 // `validate_versao_accepts_canonical_forms` positive-arm sweep
12066 // covers (`"0.0.0"`, `"1.0.0"`, `"0.2.0-rc.1"`,
12067 // `"1.0.0-alpha.0"`, `"1.0.0+build.42"`, `"1.0.0-rc.1+build.42"`,
12068 // `"10.20.30"`), plus every past-the-guard sentinel for the
12069 // `VersaoEmpty` / `VersaoInvalid` refusal cases (`""` the empty
12070 // arm, `"v0.1.0"` the git-tag-shape-leak footgun, `"0.1"` the
12071 // missing-patch footgun, `"^0.1"` the requirement-shape-leak
12072 // footgun, `"0.1.0.0"` the four-part-Java-convention footgun,
12073 // `"latest"` the docker-tag-shape footgun — the sentinels pin
12074 // the accessor doesn't silently absorb the refusal cases into a
12075 // template-derived fallback like `"0.1.0"`).
12076 //
12077 // The past-the-guard sentinels pin the accessor doesn't silently
12078 // absorb the refusal cases into a template-derived fallback (a
12079 // future `.versao().is_empty().then(|| "0.1.0")` collapse would
12080 // silently absorb the `VersaoEmpty` refusal at the accessor
12081 // boundary and the validate gate would accept a struct-literal
12082 // `Caixa { versao: "".into(), .. }` — the pin catches that at
12083 // caixa-core build time).
12084 //
12085 // Second outer top-level [`Caixa`] `&str`-return required-scalar
12086 // accessor pin — folds on the "outer [`Caixa`] `&str` required-
12087 // scalar" projection pattern the sibling per-`Caixa`
12088 // [`Caixa::nome`] (e6b7d97) opened. Sibling in shape to the peer
12089 // per-`:membros` [`crate::aplicacao::Membro::versao_requirement`]
12090 // (4127bb6) / per-`:children`
12091 // [`crate::supervisor::ChildSpec::versao_requirement`] (2c053c8)
12092 // / per-`:upgrade-from`
12093 // [`crate::UpgradeFromEntry::prior_versao`] (75d27a8) per-sub-
12094 // struct `:versao`-shaped `&str`-return accessor pins on the
12095 // sibling per-typed-slot version-carrier axes, extended onto the
12096 // second outer top-level [`Caixa`] universal-axis required-
12097 // `String`-carry axis so the two universal-axis identity-
12098 // carrying scalars every `defcaixa` form supplies (`:nome` +
12099 // `:versao`) share the same "one typed dispatch per axis" pin
12100 // discipline.
12101 for versao in [
12102 "0.1.0",
12103 "0.0.0",
12104 "1.0.0",
12105 "0.2.0-rc.1",
12106 "1.0.0-alpha.0",
12107 "1.0.0+build.42",
12108 "1.0.0-rc.1+build.42",
12109 "10.20.30",
12110 "",
12111 "v0.1.0",
12112 "0.1",
12113 "^0.1",
12114 "0.1.0.0",
12115 "latest",
12116 ] {
12117 let c = caixa_with_versao(versao);
12118 assert_eq!(
12119 c.versao(),
12120 versao,
12121 "Caixa::versao must return :versao verbatim (got {}, \
12122 expected {versao})",
12123 c.versao(),
12124 );
12125 assert_eq!(
12126 c.versao(),
12127 c.versao.as_str(),
12128 "Caixa::versao must byte-equal the raw .versao field \
12129 access across every value in the String accept-set",
12130 );
12131 }
12132 }
12133
12134 #[test]
12135 fn validate_versao_empty_arm_routes_through_accessor() {
12136 // Composition pin: [`Caixa::validate_versao`]'s empty-arm gate
12137 // must key off [`Caixa::versao`], not the raw `.versao` field
12138 // access. Structurally: a `Caixa { versao: "".into(), .. }` must
12139 // surface the `VersaoEmpty` refusal exactly, and the canonical
12140 // `"0.1.0"` template baseline (the peer positive-arm the sibling
12141 // `validate_versao_accepts_canonical_template` gate carves out)
12142 // must pass validate. The pair jointly pins the accessor +
12143 // validate-gate composition: any future silent detour that had
12144 // the accessor return a fresh `"0.1.0"` on the empty arm
12145 // (a `.versao().is_empty().then(|| "0.1.0")` fallback collapse)
12146 // would silently absorb the `VersaoEmpty` refusal at the
12147 // accessor boundary and the validate gate would accept a
12148 // struct-literal `Caixa { versao: "".into(), .. }` — the
12149 // composition pin catches that at caixa-core build time.
12150 //
12151 // Peer of the sibling per-`Caixa`
12152 // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97)
12153 // composition pin on the sibling outer top-level [`Caixa`]
12154 // required-`&str` universal-axis surface — same "the validate /
12155 // shape-gate predicate must route through the substrate-
12156 // primitive typed dispatch" discipline extended onto the peer
12157 // outer top-level [`Caixa`] required-`&str` universal-axis
12158 // pinned-version composition axis, closing the second
12159 // coordinate of the "one canonical typed dispatch per per-Caixa
12160 // required-`&str` universal-axis" discipline.
12161 let c = caixa_with_versao("");
12162 assert!(
12163 matches!(c.validate_versao(), Err(ManifestError::VersaoEmpty)),
12164 "validate_versao must reject versao == \"\" with VersaoEmpty — \
12165 the accessor and the validate gate must route through the \
12166 same substrate-primitive typed dispatch on the :versao \
12167 empty-arm",
12168 );
12169 let c = caixa_with_versao("0.1.0");
12170 assert!(
12171 c.validate_versao().is_ok(),
12172 "validate_versao must accept versao == \"0.1.0\" (the \
12173 canonical SemVer-2 template baseline)",
12174 );
12175 }
12176
12177 #[test]
12178 fn versao_projects_str_by_borrow() {
12179 // The by-borrow pin: [`Caixa::versao`] returns `&str` by borrow
12180 // — the `&str` borrows the underlying `String` storage of the
12181 // required `versao` slot and the accessor must not allocate a
12182 // fresh `String` on every call. Peer of the [`Caixa::nome`]
12183 // (e6b7d97) by-borrow pin on the sibling outer top-level
12184 // [`Caixa`] required-`&str`-return axis, extended onto the
12185 // second outer top-level [`Caixa`] required-`&str`-return
12186 // universal-axis pinned-version surface — the accessor's
12187 // returned `&str` must borrow from `&self` (the returned
12188 // reference's lifetime is tied to `&self`), and calling the
12189 // accessor twice on the same [`Caixa`] must yield the same
12190 // `&str` verbatim (idempotent, no side effects on `&self`).
12191 //
12192 // Pins against a future silent detour that returned an owned
12193 // `String` (which would type-check but silently allocate on
12194 // every call, breaking the zero-cost projection every peer
12195 // sibling accessor carries), an accidental
12196 // `semver::Version::parse(&self.versao).unwrap().to_string()`
12197 // detour that returned a canonicalized fresh allocation through
12198 // an already-canonical byte-string (breaking a future `const fn`
12199 // regression and silently absorbing the `VersaoInvalid` refusal
12200 // at the accessor boundary), or a one-arm-only accessor that
12201 // returned a canonicalized value on some sentinel input
12202 // (breaking the pass-through invariant the sibling required-
12203 // scalar accessors carry).
12204 for versao in ["0.1.0", "1.0.0", "0.2.0-rc.1", "1.0.0+build.42"] {
12205 let c = caixa_with_versao(versao);
12206 let first = c.versao();
12207 let second = c.versao();
12208 assert_eq!(
12209 first, second,
12210 "Caixa::versao must be idempotent — two successive \
12211 calls on the same &self must return the same &str",
12212 );
12213 assert_eq!(
12214 first, versao,
12215 "Caixa::versao must return :versao verbatim by borrow \
12216 — got {first}, expected {versao}",
12217 );
12218 }
12219 }
12220
12221 fn caixa_with_kind(kind: CaixaKind) -> Caixa {
12222 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
12223 c.kind = kind;
12224 c
12225 }
12226
12227 #[test]
12228 fn kind_returns_kind_variant_verbatim_across_permutations() {
12229 // The canonical per-`Caixa` `:kind` universal-axis closed-set-
12230 // enum discriminant pin: [`Caixa::kind`] must return the `:kind`
12231 // typed [`CaixaKind`] variant verbatim by `Copy`, byte-equal to
12232 // the raw `.kind` field access across every variant in the
12233 // closed accept-set (`Biblioteca` — the library kind that
12234 // exports lisp forms; `Binario` — the nix-built executable kind
12235 // under `exe/`; `Servico` — the wasm-component daemon kind
12236 // under `servicos/`; `Supervisor` — the OTP-shaped hierarchical
12237 // reconciliation kind; `Aplicacao` — the M3 typed-mesh
12238 // composition kind).
12239 //
12240 // Pins against a future silent detour that re-derived the kind
12241 // from a peer axis (an accidental fallback to
12242 // `if !servicos.is_empty() { Servico } else if
12243 // !membros.is_empty() { Aplicacao } else { Biblioteca }`
12244 // collapse that read the code-surface / mesh-slot columns into
12245 // the kind discriminator), a variant remap the operator
12246 // authors on one consumer without the other, or a stale-derive
12247 // detour that substituted [`CaixaKind::Biblioteca`] as the
12248 // default when the field held any other variant (which would
12249 // silently collapse the distinction between "author explicitly
12250 // declared `:kind Servico`" and "author declared any other
12251 // kind" every downstream renderer-dispatch site depends on).
12252 //
12253 // First outer top-level [`Caixa`] `Copy`-return required-enum-
12254 // discriminant accessor pin — opens the "outer [`Caixa`]
12255 // `Copy`-return required-discriminant" projection pattern.
12256 // Sibling in shape to the peer per-`:supervisor`
12257 // [`crate::supervisor::SupervisorSpec::estrategia`] (eafb619),
12258 // per-`:placement` [`crate::aplicacao::Placement::estrategia`]
12259 // (921fe1b), and per-`:children`
12260 // [`crate::supervisor::ChildSpec::restart`] (dfb4a81)
12261 // `Copy`-return closed-set-enum discriminant accessor pins on
12262 // the sibling nested-spec typed-slot discriminator axes,
12263 // extended here to the outer top-level [`Caixa`] universal-
12264 // axis surface.
12265 for kind in [
12266 CaixaKind::Biblioteca,
12267 CaixaKind::Binario,
12268 CaixaKind::Servico,
12269 CaixaKind::Supervisor,
12270 CaixaKind::Aplicacao,
12271 ] {
12272 let c = caixa_with_kind(kind);
12273 assert_eq!(
12274 c.kind(),
12275 kind,
12276 "Caixa::kind must return :kind verbatim (got {:?}, \
12277 expected {kind:?})",
12278 c.kind(),
12279 );
12280 assert_eq!(
12281 c.kind(),
12282 c.kind,
12283 "Caixa::kind accessor and .kind field access must \
12284 byte-equal — the accessor is the substrate-primitive \
12285 typed dispatch every downstream kind-gate consumer \
12286 must route through",
12287 );
12288 }
12289 }
12290
12291 #[test]
12292 fn require_kind_reads_through_lifted_kind_accessor() {
12293 // Two-consumer coherence pin: the [`crate::render::require_kind`]
12294 // entry-gate predicate (the canonical two-line
12295 // `require_kind(caixa, Servico)?` prelude every per-Servico /
12296 // per-Aplicacao renderer runs at its entry-point) and the
12297 // sibling [`crate::render::KindMismatch`] error carrier's
12298 // `actual:` field (which names the offending caixa's variant
12299 // in the diagnostic) must both key off the lifted accessor, so
12300 // any future rebrand on the typed slot's reader shape lands at
12301 // exactly one place. Pins the two-site coherence by exercising
12302 // every off-diagonal `(actual, expected)` pair across the
12303 // closed accept-set — the `KindMismatch { actual, expected }`
12304 // surfaced on the mismatch arm must byte-equal the pair the
12305 // accessor returns for each side.
12306 //
12307 // Peer of the sibling per-`:placement`
12308 // `validate_placement_reads_through_lifted_estrategia_accessor`
12309 // (921fe1b) two-arm consumer-coherence pin on the M3 mesh-slot
12310 // `Copy`-return discriminant axis — same "the entry-gate
12311 // predicate and the error carrier's `actual:` field must route
12312 // through the substrate-primitive typed dispatch" discipline
12313 // extended onto the outer top-level [`Caixa`] universal-axis
12314 // discriminant surface.
12315 for expected in [
12316 CaixaKind::Biblioteca,
12317 CaixaKind::Binario,
12318 CaixaKind::Servico,
12319 CaixaKind::Supervisor,
12320 CaixaKind::Aplicacao,
12321 ] {
12322 for actual in [
12323 CaixaKind::Biblioteca,
12324 CaixaKind::Binario,
12325 CaixaKind::Servico,
12326 CaixaKind::Supervisor,
12327 CaixaKind::Aplicacao,
12328 ] {
12329 let c = caixa_with_kind(actual);
12330 let result = crate::render::require_kind(&c, expected);
12331 if expected == actual {
12332 assert!(
12333 result.is_ok(),
12334 "require_kind must accept when actual == expected \
12335 (actual={actual:?}, expected={expected:?})",
12336 );
12337 } else {
12338 let err = result.expect_err("require_kind must reject when actual != expected");
12339 assert_eq!(
12340 err.actual,
12341 c.kind(),
12342 "KindMismatch.actual must byte-equal Caixa::kind() \
12343 — the error carrier's `actual:` field reads \
12344 through the lifted accessor",
12345 );
12346 assert_eq!(
12347 err.expected, expected,
12348 "KindMismatch.expected must byte-equal the \
12349 expected variant passed to require_kind",
12350 );
12351 }
12352 }
12353 }
12354 }
12355
12356 #[test]
12357 fn aplicacao_view_kind_gate_routes_through_accessor() {
12358 // Composition pin: [`Caixa::aplicacao_view`]'s kind-gate arm
12359 // must key off [`Caixa::kind`], not the raw `.kind` field
12360 // access. Structurally: a `Caixa { kind: X, .. }` for any
12361 // non-`Aplicacao` variant must fold to `None` on the
12362 // `aplicacao_view` composer (the "kind mismatch → no typed
12363 // view" contract every downstream Aplicacao consumer keys off
12364 // via `?`), and a `Caixa { kind: Aplicacao, .. }` must fold to
12365 // `Some(_)`. The pair jointly pins the accessor + view-gate
12366 // composition: any future silent detour that had the accessor
12367 // return a fresh [`CaixaKind::Aplicacao`] on some sentinel
12368 // input would silently absorb the kind-mismatch case at the
12369 // accessor boundary and every per-Aplicacao renderer would
12370 // silently render a non-Aplicacao caixa's mesh slots — the
12371 // composition pin catches that at caixa-core build time.
12372 //
12373 // Peer of the sibling per-`Caixa`
12374 // `validate_nome_empty_arm_routes_through_accessor` (e6b7d97) /
12375 // `validate_versao_empty_arm_routes_through_accessor` (20c0539)
12376 // composition pins on the sibling outer top-level [`Caixa`]
12377 // required-`&str` universal-axis surfaces — same "the
12378 // composer / validate gate must route through the substrate-
12379 // primitive typed dispatch" discipline extended onto the
12380 // outer top-level [`Caixa`] `Copy`-return required-
12381 // discriminant composition axis.
12382 for kind in [
12383 CaixaKind::Biblioteca,
12384 CaixaKind::Binario,
12385 CaixaKind::Servico,
12386 CaixaKind::Supervisor,
12387 ] {
12388 let c = caixa_with_kind(kind);
12389 assert!(
12390 c.aplicacao_view().is_none(),
12391 "aplicacao_view must return None on non-Aplicacao \
12392 kind {kind:?} — the composer's kind-gate must route \
12393 through Caixa::kind()",
12394 );
12395 }
12396 let c = caixa_with_kind(CaixaKind::Aplicacao);
12397 assert!(
12398 c.aplicacao_view().is_some(),
12399 "aplicacao_view must return Some on kind Aplicacao — \
12400 the composer's kind-gate must accept the matching arm \
12401 through Caixa::kind()",
12402 );
12403 }
12404
12405 #[test]
12406 fn supervisor_view_kind_gate_routes_through_accessor() {
12407 // Composition pin (mirror of the sibling
12408 // `aplicacao_view_kind_gate_routes_through_accessor` on the
12409 // second `_view` composer): [`Caixa::supervisor_view`]'s kind-
12410 // gate arm must key off [`Caixa::kind`], not the raw `.kind`
12411 // field access. A `Caixa { kind: X, .. }` for any non-
12412 // `Supervisor` variant must fold to `None` on the
12413 // `supervisor_view` composer, and a `Caixa { kind:
12414 // Supervisor, .. }` must fold to `Some(_)`. Same peer
12415 // composition pin discipline on the second `_view` composer
12416 // axis.
12417 for kind in [
12418 CaixaKind::Biblioteca,
12419 CaixaKind::Binario,
12420 CaixaKind::Servico,
12421 CaixaKind::Aplicacao,
12422 ] {
12423 let c = caixa_with_kind(kind);
12424 assert!(
12425 c.supervisor_view().is_none(),
12426 "supervisor_view must return None on non-Supervisor \
12427 kind {kind:?} — the composer's kind-gate must route \
12428 through Caixa::kind()",
12429 );
12430 }
12431 let mut c = caixa_with_kind(CaixaKind::Supervisor);
12432 // A Supervisor caixa needs a strategy + at least one child to
12433 // fold to a Some(_) that also validates; the composer itself
12434 // requires only the kind arm, so bare kind flip is enough to
12435 // pin the `Some(_)` return, but we populate the minimum
12436 // supervisor shape so a future strengthening of the composer
12437 // to reject an empty spec doesn't false-positive this pin.
12438 c.estrategia = Some(crate::supervisor::RestartStrategy::OneForOne);
12439 c.children = vec![crate::supervisor::ChildSpec {
12440 caixa: "child".into(),
12441 versao: "^0.1".into(),
12442 restart: crate::supervisor::RestartPolicy::Permanent,
12443 }];
12444 assert!(
12445 c.supervisor_view().is_some(),
12446 "supervisor_view must return Some on kind Supervisor — \
12447 the composer's kind-gate must accept the matching arm \
12448 through Caixa::kind()",
12449 );
12450 }
12451
12452 #[test]
12453 fn kind_projects_by_copy() {
12454 // The by-`Copy` pin: [`Caixa::kind`] returns a fresh
12455 // [`CaixaKind`] by `Copy` — the accessor must not borrow from
12456 // `&self` (the returned value is owned, `Copy`-projected from
12457 // the underlying [`CaixaKind`] storage; two calls on the same
12458 // [`Caixa`] must yield byte-equal values). Peer of the peer
12459 // per-`:placement` `Placement::estrategia` / per-`:supervisor`
12460 // `SupervisorSpec::estrategia` / per-`:children`
12461 // `ChildSpec::restart` `Copy`-return discriminant accessor
12462 // pins on the sibling nested-spec typed-slot discriminator
12463 // axes, extended onto the first outer top-level [`Caixa`]
12464 // required-`Copy`-return axis — pins against a future silent
12465 // detour that returned `&CaixaKind` (which would type-check
12466 // but silently constrain every consumer's callsite to a
12467 // borrow-shaped dispatch, breaking the zero-cost `Copy`
12468 // projection every peer sibling accessor carries).
12469 for kind in [
12470 CaixaKind::Biblioteca,
12471 CaixaKind::Binario,
12472 CaixaKind::Servico,
12473 CaixaKind::Supervisor,
12474 CaixaKind::Aplicacao,
12475 ] {
12476 let c = caixa_with_kind(kind);
12477 let first: CaixaKind = c.kind();
12478 let second: CaixaKind = c.kind();
12479 assert_eq!(
12480 first, second,
12481 "Caixa::kind must be idempotent — two successive \
12482 calls on the same &self must return the same \
12483 CaixaKind variant",
12484 );
12485 assert_eq!(
12486 first, kind,
12487 "Caixa::kind must return :kind verbatim by Copy — \
12488 got {first:?}, expected {kind:?}",
12489 );
12490 }
12491 }
12492
12493 // ── Caixa::autores — outer top-level &[T] slice accessor ──────────
12494
12495 #[test]
12496 fn autores_returns_autores_slice_verbatim_across_permutations() {
12497 // The canonical per-`Caixa` `:autores` universal-axis maintainer-
12498 // name-list slice pin: [`Caixa::autores`] must return the
12499 // `:autores` typed [`Vec<String>`] list verbatim as a
12500 // `&[String]`, byte-equal to the raw `self.autores.as_slice()`
12501 // access across every representative value in the accept-set —
12502 // `[]` (the "no maintainers declared" arm every existing
12503 // fixture without an `:autores` line carries), `[""]` (a past-
12504 // the-guard sentinel that pins the accessor doesn't perform a
12505 // silent `[""] → []` collapse on the empty-entry arm — validate
12506 // rejects `[""]` through `AutorEmpty` but the accessor must
12507 // ship the raw slot verbatim so a validate-time gate regression
12508 // surfaces at the caixa-helm emit boundary rather than being
12509 // silently absorbed into a maintainer-drop), `["pleme-io"]` (the
12510 // canonical single-maintainer form every `feira init` template
12511 // scaffolds), `["alice", "bob"]` (a canonical multi-maintainer
12512 // form), `["alice <alice@example.com>", "bob <bob@example.com>"]`
12513 // (the canonical RFC-5322 `<name> <email>` form the
12514 // `is_chart_maintainer_name_shape` predicate accepts), and
12515 // `["pleme-io", "pleme-io"]` (a past-the-guard duplicate
12516 // sentinel — validate rejects through `AutorDuplicate` but the
12517 // accessor must ship the raw slot verbatim).
12518 //
12519 // First outer top-level [`Caixa`] `&[T]`-return slice accessor
12520 // pin on the substrate primitive — opens the "outer [`Caixa`]
12521 // `&[T]` slice" projection pattern the sibling per-`Caixa`
12522 // `:etiquetas` / `:deps` / `:deps-dev` / `:exe` / `:bibliotecas`
12523 // / `:servicos` / `:upgrade-from` / `:children` future lifts
12524 // fold on. Sibling in shape to the peer per-`:supervisor`
12525 // [`crate::supervisor::SupervisorSpec::children`] (bc92bce),
12526 // per-`:placement` [`crate::aplicacao::Placement::clusters`]
12527 // (a6e18d7), per-`:membros`
12528 // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36),
12529 // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
12530 // (0dcc926), and per-`:upgrade-from :instructions`
12531 // [`crate::upgrade::UpgradeFromEntry::instructions`] (0137e5a)
12532 // `&[T]`-return slice accessor pins on the sibling per-M2 /
12533 // per-M3 typed-slot list axes, extended onto the outer top-
12534 // level [`Caixa`] universal-axis surface. Pins against a future
12535 // silent detour that returned an owned `Vec<String>` (which
12536 // would type-check but silently clone on every accessor call,
12537 // breaking the zero-cost projection every peer sibling slice
12538 // accessor carries), a `[""] → []` collapse (which would
12539 // silently absorb the `AutorEmpty` refusal case at the accessor
12540 // boundary), or a `["a", "a"] → ["a"]` dedup collapse (which
12541 // would silently absorb the `AutorDuplicate` refusal case at
12542 // the accessor boundary and the caixa-helm `maintainers:` fold
12543 // would silently render a dedupped list on a struct-literal
12544 // `Caixa { autores: vec!["a".into(), "a".into()], .. }`).
12545 for autores in [
12546 vec![],
12547 vec![""],
12548 vec!["pleme-io"],
12549 vec!["alice", "bob"],
12550 vec!["alice <alice@example.com>", "bob <bob@example.com>"],
12551 vec!["pleme-io", "pleme-io"],
12552 ] {
12553 let c = caixa_with_autores(autores.clone());
12554 let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
12555 assert_eq!(
12556 c.autores(),
12557 expected.as_slice(),
12558 "Caixa::autores must return :autores verbatim (got {:?}, \
12559 expected {expected:?})",
12560 c.autores(),
12561 );
12562 assert_eq!(
12563 c.autores(),
12564 c.autores.as_slice(),
12565 "Caixa::autores must byte-equal the raw \
12566 `self.autores.as_slice()` field access across every \
12567 value in the Vec<String> accept-set",
12568 );
12569 }
12570 }
12571
12572 #[test]
12573 fn validate_autores_empty_entry_arm_routes_through_accessor() {
12574 // Composition pin: [`Caixa::validate_autores`]'s per-entry
12575 // empty-arm gate must key off [`Caixa::autores`], not the raw
12576 // `&self.autores` field-borrow walk. Structurally: a
12577 // `Caixa { autores: vec!["".into()], .. }` must surface the
12578 // `AutorEmpty` refusal exactly, and a
12579 // `Caixa { autores: vec!["pleme-io".into()], .. }` (the
12580 // canonical single-maintainer form) must pass validate. The
12581 // pair jointly pins the accessor + validate-gate composition:
12582 // any future silent detour that had the accessor return an
12583 // empty slice on the `[""]` arm (a
12584 // `.iter().filter(|s| !s.is_empty()).collect()` collapse)
12585 // would silently absorb the `AutorEmpty` refusal at the
12586 // accessor boundary and the validate gate would accept a
12587 // struct-literal `Caixa { autores: vec!["".into()], .. }` —
12588 // the composition pin catches that at caixa-core build time.
12589 //
12590 // Peer of the per-`Caixa` [`Caixa::validate_licenca`] (6d5bc28)
12591 // accessor-composition pin
12592 // (`validate_licenca_empty_arm_routes_through_accessor`) on the
12593 // sibling `Option<&str>`-composition axis and the
12594 // per-`:politicas :circuit-breaker`
12595 // [`crate::aplicacao::CircuitBreaker::max_failures`] (3a74062)
12596 // accessor-composition pin
12597 // (`validate_politicas_max_failures_zero_floor_arm_routes_through_accessor`)
12598 // on the sibling required-`u32`-composition axis — same "the
12599 // validate / shape-gate predicate must route through the
12600 // substrate-primitive typed dispatch" discipline extended onto
12601 // the outer top-level [`Caixa`] universal-axis `&[T]`-
12602 // composition surface.
12603 let c = caixa_with_autores(vec![""]);
12604 assert!(
12605 matches!(c.validate_autores(), Err(ManifestError::AutorEmpty)),
12606 "validate_autores must reject autores == vec![\"\"] with \
12607 AutorEmpty — the accessor and the validate gate must \
12608 route through the same substrate-primitive typed dispatch \
12609 on the :autores per-entry empty arm",
12610 );
12611 let c = caixa_with_autores(vec!["pleme-io"]);
12612 assert!(
12613 c.validate_autores().is_ok(),
12614 "validate_autores must accept autores == vec![\"pleme-io\"] \
12615 (the canonical single-maintainer shape every `feira init` \
12616 template scaffolds)",
12617 );
12618 }
12619
12620 #[test]
12621 fn autores_projects_slice_by_borrow() {
12622 // The by-borrow pin: [`Caixa::autores`] returns `&[String]` by
12623 // borrow — the returned slice borrows the underlying
12624 // `Vec<String>` storage of the `:autores` slot and the
12625 // accessor must not clone the backing `Vec` on every call.
12626 // Peer of the per-`:membros`
12627 // [`crate::aplicacao::AplicacaoSpec::membros`] (6c77e36) /
12628 // per-`:contratos` [`crate::aplicacao::AplicacaoSpec::contratos`]
12629 // (0dcc926) / per-`:placement`
12630 // [`crate::aplicacao::Placement::clusters`] (a6e18d7) /
12631 // per-`:supervisor` [`crate::supervisor::SupervisorSpec::children`]
12632 // (bc92bce) by-borrow pins on the sibling per-M2 / per-M3
12633 // typed-slot `&[T]`-return axes, extended onto the outer top-
12634 // level [`Caixa`] universal-axis `&[String]` shape — the
12635 // accessor's returned slice must borrow from `&self` (the
12636 // returned reference's lifetime is tied to `&self`), and
12637 // calling the accessor twice on the same [`Caixa`] must yield
12638 // slices that are pointer-equal (the underlying byte-buffer is
12639 // the storage `Vec`'s allocation, not a fresh copy) as well as
12640 // value-equal (idempotent, no side effects on `&self`).
12641 //
12642 // Pins against a future silent detour that returned an owned
12643 // `Vec<String>` (which would type-check but silently clone on
12644 // every call, breaking the zero-cost projection every peer
12645 // sibling slice accessor carries), a `&Vec<String>` return
12646 // (which would leak the backing `Vec`'s grow/push/reserve
12647 // surface no downstream consumer reaches for), or a one-arm-
12648 // only accessor that returned a saturating value on some
12649 // sentinel input (breaking the pass-through invariant the
12650 // sibling slice accessors carry).
12651 for autores in [
12652 vec![],
12653 vec!["pleme-io"],
12654 vec!["alice", "bob"],
12655 vec!["pleme-io", "pleme-io"],
12656 ] {
12657 let c = caixa_with_autores(autores.clone());
12658 let expected: Vec<String> = autores.iter().map(|s| (*s).to_string()).collect();
12659 let first = c.autores();
12660 let second = c.autores();
12661 assert_eq!(
12662 first, second,
12663 "Caixa::autores must be idempotent — two successive \
12664 calls on the same &self must return the same \
12665 &[String]",
12666 );
12667 assert_eq!(
12668 first.as_ptr(),
12669 second.as_ptr(),
12670 "Caixa::autores must borrow the underlying Vec<String> \
12671 storage — two successive calls must return slices \
12672 with the same backing pointer (a fresh Vec<String> \
12673 clone would change the pointer on every call)",
12674 );
12675 assert_eq!(
12676 first,
12677 expected.as_slice(),
12678 "Caixa::autores must return :autores verbatim by \
12679 borrow — got {first:?}, expected {expected:?}",
12680 );
12681 }
12682 }
12683
12684 // ── Caixa::etiquetas — outer top-level &[T] slice accessor ────────
12685
12686 #[test]
12687 fn etiquetas_returns_etiquetas_slice_verbatim_across_permutations() {
12688 // The canonical per-`Caixa` `:etiquetas` universal-axis
12689 // registry-search-tag-list slice pin: [`Caixa::etiquetas`] must
12690 // return the `:etiquetas` typed [`Vec<String>`] list verbatim
12691 // as a `&[String]`, byte-equal to the raw
12692 // `self.etiquetas.as_slice()` access across every representative
12693 // value in the accept-set — `[]` (the "no tags declared" arm
12694 // every existing fixture without an `:etiquetas` line carries),
12695 // `[""]` (a past-the-guard sentinel that pins the accessor
12696 // doesn't perform a silent `[""] → []` collapse on the empty-
12697 // entry arm — validate rejects `[""]` through `EtiquetaEmpty`
12698 // but the accessor must ship the raw slot verbatim so a
12699 // validate-time gate regression surfaces at the caixa-helm emit
12700 // boundary rather than being silently absorbed into a keyword-
12701 // drop), `["demo"]` (the canonical single-tag form every
12702 // `feira init` template scaffolds), `["example", "aplicacao",
12703 // "mesh", "ecommerce", "demo"]` (the canonical multi-tag form
12704 // the checkout-aplicacao fixture emits), and `["demo", "demo"]`
12705 // (a past-the-guard duplicate sentinel — validate rejects
12706 // through `EtiquetaDuplicate` but the accessor must ship the
12707 // raw slot verbatim so the caixa-helm `BTreeSet::collect` dedup
12708 // at chart-render time isn't silently promoted into the
12709 // accessor boundary and struct-literal
12710 // `Caixa { etiquetas: vec!["demo".into(), "demo".into()], .. }`
12711 // fixtures continue to expose the duplicate at the accessor).
12712 //
12713 // Second outer top-level [`Caixa`] `&[T]`-return slice accessor
12714 // pin on the substrate primitive — folds on the "outer
12715 // [`Caixa`] `&[T]` slice" projection pattern
12716 // `autores_returns_autores_slice_verbatim_across_permutations`
12717 // (b5d813f) opened, sibling in shape and idiom. Pins against a
12718 // future silent detour that returned an owned `Vec<String>`
12719 // (which would type-check but silently clone on every accessor
12720 // call, breaking the zero-cost projection every peer sibling
12721 // slice accessor carries), a `[""] → []` collapse (which would
12722 // silently absorb the `EtiquetaEmpty` refusal case at the
12723 // accessor boundary), or a `["a", "a"] → ["a"]` dedup collapse
12724 // (which would silently absorb the `EtiquetaDuplicate` refusal
12725 // case at the accessor boundary — the caixa-helm chart-render
12726 // `BTreeSet::collect` dedup is downstream of the accessor and
12727 // must not be silently promoted into it).
12728 for etiquetas in [
12729 vec![],
12730 vec![""],
12731 vec!["demo"],
12732 vec!["example", "aplicacao", "mesh", "ecommerce", "demo"],
12733 vec!["demo", "demo"],
12734 ] {
12735 let c = caixa_with_etiquetas(etiquetas.clone());
12736 let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
12737 assert_eq!(
12738 c.etiquetas(),
12739 expected.as_slice(),
12740 "Caixa::etiquetas must return :etiquetas verbatim (got \
12741 {:?}, expected {expected:?})",
12742 c.etiquetas(),
12743 );
12744 assert_eq!(
12745 c.etiquetas(),
12746 c.etiquetas.as_slice(),
12747 "Caixa::etiquetas must byte-equal the raw \
12748 `self.etiquetas.as_slice()` field access across every \
12749 value in the Vec<String> accept-set",
12750 );
12751 }
12752 }
12753
12754 #[test]
12755 fn validate_etiquetas_empty_entry_arm_routes_through_accessor() {
12756 // Composition pin: [`Caixa::validate_etiquetas`]'s per-entry
12757 // empty-arm gate must key off [`Caixa::etiquetas`], not the raw
12758 // `&self.etiquetas` field-borrow walk. Structurally: a
12759 // `Caixa { etiquetas: vec!["".into()], .. }` must surface the
12760 // `EtiquetaEmpty` refusal exactly, and a
12761 // `Caixa { etiquetas: vec!["demo".into()], .. }` (the canonical
12762 // single-tag form) must pass validate. The pair jointly pins
12763 // the accessor + validate-gate composition: any future silent
12764 // detour that had the accessor return an empty slice on the
12765 // `[""]` arm (a
12766 // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
12767 // silently absorb the `EtiquetaEmpty` refusal at the accessor
12768 // boundary and the validate gate would accept a struct-literal
12769 // `Caixa { etiquetas: vec!["".into()], .. }` — the composition
12770 // pin catches that at caixa-core build time.
12771 //
12772 // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
12773 // through_accessor` (b5d813f) accessor-composition pin on the
12774 // sibling `&[T]`-composition axis — same "the validate / shape-
12775 // gate predicate must route through the substrate-primitive
12776 // typed dispatch" discipline extended onto the sibling outer
12777 // top-level [`Caixa`] `&[T]`-composition surface.
12778 let c = caixa_with_etiquetas(vec![""]);
12779 assert!(
12780 matches!(c.validate_etiquetas(), Err(ManifestError::EtiquetaEmpty)),
12781 "validate_etiquetas must reject etiquetas == vec![\"\"] \
12782 with EtiquetaEmpty — the accessor and the validate gate \
12783 must route through the same substrate-primitive typed \
12784 dispatch on the :etiquetas per-entry empty arm",
12785 );
12786 let c = caixa_with_etiquetas(vec!["demo"]);
12787 assert!(
12788 c.validate_etiquetas().is_ok(),
12789 "validate_etiquetas must accept etiquetas == vec![\"demo\"] \
12790 (the canonical single-tag shape every `feira init` \
12791 template scaffolds)",
12792 );
12793 }
12794
12795 #[test]
12796 fn etiquetas_projects_slice_by_borrow() {
12797 // The by-borrow pin: [`Caixa::etiquetas`] returns `&[String]`
12798 // by borrow — the returned slice borrows the underlying
12799 // `Vec<String>` storage of the `:etiquetas` slot and the
12800 // accessor must not clone the backing `Vec` on every call.
12801 // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
12802 // (b5d813f) by-borrow pin on the sibling outer top-level
12803 // [`Caixa`] `&[String]`-return axis — the accessor's returned
12804 // slice must borrow from `&self` (the returned reference's
12805 // lifetime is tied to `&self`), and calling the accessor twice
12806 // on the same [`Caixa`] must yield slices that are pointer-
12807 // equal (the underlying byte-buffer is the storage `Vec`'s
12808 // allocation, not a fresh copy) as well as value-equal
12809 // (idempotent, no side effects on `&self`).
12810 //
12811 // Pins against a future silent detour that returned an owned
12812 // `Vec<String>` (which would type-check but silently clone on
12813 // every call, breaking the zero-cost projection every peer
12814 // sibling slice accessor carries), a `&Vec<String>` return
12815 // (which would leak the backing `Vec`'s grow/push/reserve
12816 // surface no downstream consumer reaches for), or a one-arm-
12817 // only accessor that returned a saturating value on some
12818 // sentinel input (breaking the pass-through invariant the
12819 // sibling slice accessors carry).
12820 for etiquetas in [
12821 vec![],
12822 vec!["demo"],
12823 vec!["example", "aplicacao", "mesh"],
12824 vec!["demo", "demo"],
12825 ] {
12826 let c = caixa_with_etiquetas(etiquetas.clone());
12827 let expected: Vec<String> = etiquetas.iter().map(|s| (*s).to_string()).collect();
12828 let first = c.etiquetas();
12829 let second = c.etiquetas();
12830 assert_eq!(
12831 first, second,
12832 "Caixa::etiquetas must be idempotent — two successive \
12833 calls on the same &self must return the same \
12834 &[String]",
12835 );
12836 assert_eq!(
12837 first.as_ptr(),
12838 second.as_ptr(),
12839 "Caixa::etiquetas must borrow the underlying \
12840 Vec<String> storage — two successive calls must \
12841 return slices with the same backing pointer (a fresh \
12842 Vec<String> clone would change the pointer on every \
12843 call)",
12844 );
12845 assert_eq!(
12846 first,
12847 expected.as_slice(),
12848 "Caixa::etiquetas must return :etiquetas verbatim by \
12849 borrow — got {first:?}, expected {expected:?}",
12850 );
12851 }
12852 }
12853
12854 // ── Caixa::bibliotecas — outer top-level &[T] slice accessor ──────
12855
12856 #[test]
12857 fn bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations() {
12858 // The canonical per-`Caixa` `:bibliotecas` universal-axis
12859 // library-source-path-list slice pin: [`Caixa::bibliotecas`]
12860 // must return the `:bibliotecas` typed [`Vec<String>`] list
12861 // verbatim as a `&[String]`, byte-equal to the raw
12862 // `self.bibliotecas.as_slice()` access across every
12863 // representative value in the accept-set — `[]` (the "no
12864 // libraries declared" arm every `:kind` other than `Biblioteca`
12865 // + every `Biblioteca` relying on the canonical
12866 // `lib/<nome>.lisp` implicit-default path carries; the
12867 // layout's [`crate::LayoutInvariants`] `MissingLib` arm-gate
12868 // fires exactly on this empty-slot + `Biblioteca`-kind
12869 // combination), `[""]` (a past-the-guard sentinel that pins
12870 // the accessor doesn't perform a silent `[""] → []` collapse
12871 // on the empty-entry arm — validate rejects `[""]` through
12872 // `CodePathEmpty { slot: ":bibliotecas" }` but the accessor
12873 // must ship the raw slot verbatim so a validate-time gate
12874 // regression surfaces at the `feira build` phase-1 parse
12875 // boundary rather than being silently absorbed into a
12876 // library-drop), `["lib/demo.lisp"]` (the canonical single-
12877 // entry form `Caixa::template` scaffolds and every `feira init`
12878 // template emits), `["lib/demo.lisp", "lib/helpers.lisp"]`
12879 // (the canonical multi-library form the
12880 // `validate_code_paths_accepts_explicit_relative_paths_on_
12881 // every_slot` fixture emits), and `["lib/foo.lisp",
12882 // "lib/foo.lisp"]` (a past-the-guard duplicate sentinel —
12883 // validate rejects through `CodePathDuplicate { slot:
12884 // ":bibliotecas" }` per the per-slot set-not-multiset gate,
12885 // but the accessor must ship the raw slot verbatim so the
12886 // `feira build` `for entry in caixa.bibliotecas()` parse walk
12887 // sees the duplicate at the accessor boundary and struct-
12888 // literal `Caixa { bibliotecas: vec!["lib/foo.lisp".into(),
12889 // "lib/foo.lisp".into()], .. }` fixtures continue to expose
12890 // the duplicate at the accessor).
12891 //
12892 // Third outer top-level [`Caixa`] `&[T]`-return slice accessor
12893 // pin on the substrate primitive — folds on the "outer
12894 // [`Caixa`] `&[T]` slice" projection pattern
12895 // `autores_returns_autores_slice_verbatim_across_permutations`
12896 // (b5d813f) opened and
12897 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
12898 // (78c7d3c) folded on, sibling in shape and idiom. Pins
12899 // against a future silent detour that returned an owned
12900 // `Vec<String>` (which would type-check but silently clone on
12901 // every accessor call, breaking the zero-cost projection
12902 // every peer sibling slice accessor carries), a `[""] → []`
12903 // collapse (which would silently absorb the `CodePathEmpty`
12904 // refusal case at the accessor boundary), or a `["lib/foo.lisp",
12905 // "lib/foo.lisp"] → ["lib/foo.lisp"]` dedup collapse (which
12906 // would silently absorb the `CodePathDuplicate` refusal case
12907 // at the accessor boundary — the per-slot set-not-multiset
12908 // gate is downstream of the accessor and must not be silently
12909 // promoted into it).
12910 for bibliotecas in [
12911 vec![],
12912 vec![""],
12913 vec!["lib/demo.lisp"],
12914 vec!["lib/demo.lisp", "lib/helpers.lisp"],
12915 vec!["lib/foo.lisp", "lib/foo.lisp"],
12916 ] {
12917 let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
12918 let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
12919 assert_eq!(
12920 c.bibliotecas(),
12921 expected.as_slice(),
12922 "Caixa::bibliotecas must return :bibliotecas verbatim \
12923 (got {:?}, expected {expected:?})",
12924 c.bibliotecas(),
12925 );
12926 assert_eq!(
12927 c.bibliotecas(),
12928 c.bibliotecas.as_slice(),
12929 "Caixa::bibliotecas must byte-equal the raw \
12930 `self.bibliotecas.as_slice()` field access across \
12931 every value in the Vec<String> accept-set",
12932 );
12933 }
12934 }
12935
12936 #[test]
12937 fn validate_code_paths_bibliotecas_empty_arm_routes_through_accessor() {
12938 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
12939 // empty-arm gate on the `:bibliotecas` slot must key off
12940 // [`Caixa::bibliotecas`], not a divergent raw
12941 // `&self.bibliotecas` field-borrow walk. Structurally: a
12942 // `Caixa { bibliotecas: vec!["".into()], .. }` must surface
12943 // the `CodePathEmpty { slot: ":bibliotecas" }` refusal
12944 // exactly, and a `Caixa { bibliotecas: vec!["lib/demo.lisp".
12945 // into()], .. }` (the canonical single-library form
12946 // `Caixa::template` scaffolds) must pass validate. The pair
12947 // jointly pins the accessor + validate-gate composition: any
12948 // future silent detour that had the accessor return an empty
12949 // slice on the `[""]` arm (a `.iter().filter(|s|
12950 // !s.is_empty()).collect()` collapse) would silently absorb
12951 // the `CodePathEmpty` refusal at the accessor boundary and
12952 // the validate gate would accept a struct-literal
12953 // `Caixa { bibliotecas: vec!["".into()], .. }` — the
12954 // composition pin catches that at caixa-core build time.
12955 //
12956 // Peer of the per-`Caixa` `validate_autores_empty_arm_routes_
12957 // through_accessor` (b5d813f) and
12958 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
12959 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
12960 // composition axes — same "the validate / shape-gate
12961 // predicate must route through the substrate-primitive typed
12962 // dispatch" discipline extended onto the sibling outer top-
12963 // level [`Caixa`] `&[T]`-composition surface. Nominally the
12964 // in-tree `validate_code_paths` production body still keys
12965 // off the internal `[(":bibliotecas", &self.bibliotecas,
12966 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
12967 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
12968 // (the tuple's homogeneous slice-typed shape blocks a per-
12969 // element accessor swap in isolation — a future companion
12970 // lift for `:exe` and `:servicos` on the same outer-`Caixa`
12971 // `&[T]` slice-accessor axis closes that tuple onto the
12972 // triple of typed dispatches as a unit); the composition pin
12973 // catches any future accessor-side silent filter drop against
12974 // that eventual tuple-closure regardless of whether the
12975 // `:bibliotecas` slot is threaded through the accessor or the
12976 // raw field access at the tuple's construction site.
12977 let c = caixa_with_code_paths(vec![""], vec![], vec![]);
12978 assert!(
12979 matches!(
12980 c.validate_code_paths(),
12981 Err(ManifestError::CodePathEmpty {
12982 slot: ":bibliotecas"
12983 })
12984 ),
12985 "validate_code_paths must reject bibliotecas == vec![\"\"] \
12986 with CodePathEmpty {{ slot: \":bibliotecas\" }} — the \
12987 accessor and the validate gate must route through the \
12988 same substrate-primitive typed dispatch on the \
12989 :bibliotecas per-entry empty arm",
12990 );
12991 let c = caixa_with_code_paths(vec!["lib/demo.lisp"], vec![], vec![]);
12992 assert!(
12993 c.validate_code_paths().is_ok(),
12994 "validate_code_paths must accept bibliotecas == \
12995 vec![\"lib/demo.lisp\"] (the canonical single-library \
12996 shape every `feira init` template scaffolds)",
12997 );
12998 }
12999
13000 #[test]
13001 fn bibliotecas_projects_slice_by_borrow() {
13002 // The by-borrow pin: [`Caixa::bibliotecas`] returns
13003 // `&[String]` by borrow — the returned slice borrows the
13004 // underlying `Vec<String>` storage of the `:bibliotecas` slot
13005 // and the accessor must not clone the backing `Vec` on every
13006 // call. Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
13007 // (b5d813f) and `etiquetas_projects_slice_by_borrow` (78c7d3c)
13008 // by-borrow pins on the sibling outer top-level [`Caixa`]
13009 // `&[String]`-return axes — the accessor's returned slice
13010 // must borrow from `&self` (the returned reference's lifetime
13011 // is tied to `&self`), and calling the accessor twice on the
13012 // same [`Caixa`] must yield slices that are pointer-equal
13013 // (the underlying byte-buffer is the storage `Vec`'s
13014 // allocation, not a fresh copy) as well as value-equal
13015 // (idempotent, no side effects on `&self`).
13016 //
13017 // Pins against a future silent detour that returned an owned
13018 // `Vec<String>` (which would type-check but silently clone on
13019 // every call, breaking the zero-cost projection every peer
13020 // sibling slice accessor carries), a `&Vec<String>` return
13021 // (which would leak the backing `Vec`'s grow/push/reserve
13022 // surface no downstream consumer reaches for), or a one-arm-
13023 // only accessor that returned a saturating value on some
13024 // sentinel input (breaking the pass-through invariant the
13025 // sibling slice accessors carry).
13026 for bibliotecas in [
13027 vec![],
13028 vec!["lib/demo.lisp"],
13029 vec!["lib/demo.lisp", "lib/helpers.lisp"],
13030 vec!["lib/foo.lisp", "lib/foo.lisp"],
13031 ] {
13032 let c = caixa_with_code_paths(bibliotecas.clone(), vec![], vec![]);
13033 let expected: Vec<String> = bibliotecas.iter().map(|s| (*s).to_string()).collect();
13034 let first = c.bibliotecas();
13035 let second = c.bibliotecas();
13036 assert_eq!(
13037 first, second,
13038 "Caixa::bibliotecas must be idempotent — two \
13039 successive calls on the same &self must return the \
13040 same &[String]",
13041 );
13042 assert_eq!(
13043 first.as_ptr(),
13044 second.as_ptr(),
13045 "Caixa::bibliotecas must borrow the underlying \
13046 Vec<String> storage — two successive calls must \
13047 return slices with the same backing pointer (a \
13048 fresh Vec<String> clone would change the pointer on \
13049 every call)",
13050 );
13051 assert_eq!(
13052 first,
13053 expected.as_slice(),
13054 "Caixa::bibliotecas must return :bibliotecas verbatim \
13055 by borrow — got {first:?}, expected {expected:?}",
13056 );
13057 }
13058 }
13059
13060 // ── Caixa::exe — outer top-level &[T] slice accessor ──────────────
13061
13062 #[test]
13063 fn exe_returns_exe_slice_verbatim_across_permutations() {
13064 // The canonical per-`Caixa` `:exe` universal-axis
13065 // nix-built-executable-entry-path-list slice pin: [`Caixa::exe`]
13066 // must return the `:exe` typed [`Vec<String>`] list verbatim as
13067 // a `&[String]`, byte-equal to the raw `self.exe.as_slice()`
13068 // access across every representative value in the accept-set —
13069 // `[]` (the "no executable declared" arm every `:kind` other
13070 // than `Binario` carries; the layout's [`crate::LayoutInvariants`]
13071 // `BinarioWithoutExe` arm-gate fires exactly on this empty-slot
13072 // + `Binario`-kind combination), `[""]` (a past-the-guard
13073 // sentinel that pins the accessor doesn't perform a silent
13074 // `[""] → []` collapse on the empty-entry arm — validate rejects
13075 // `[""]` through `CodePathEmpty { slot: ":exe" }` but the
13076 // accessor must ship the raw slot verbatim so a validate-time
13077 // gate regression surfaces at the layout / `feira nix` boundary
13078 // rather than being silently absorbed into an executable-drop),
13079 // `["exe/cli"]` (the canonical single-entry Binario form every
13080 // in-tree `caixa_with_code_paths` positive control uses),
13081 // `["exe/cli", "exe/serve"]` (the canonical multi-executable
13082 // form the `validate_code_paths_accepts_explicit_relative_paths_
13083 // on_every_slot` fixture emits), and `["exe/cli", "exe/cli"]`
13084 // (a past-the-guard duplicate sentinel — validate rejects
13085 // through `CodePathDuplicate { slot: ":exe" }` per the per-slot
13086 // set-not-multiset gate, but the accessor must ship the raw
13087 // slot verbatim so struct-literal `Caixa { exe: vec!["exe/cli".
13088 // into(), "exe/cli".into()], .. }` fixtures continue to expose
13089 // the duplicate at the accessor).
13090 //
13091 // Fourth outer top-level [`Caixa`] `&[T]`-return slice accessor
13092 // pin on the substrate primitive — folds on the "outer
13093 // [`Caixa`] `&[T]` slice" projection pattern
13094 // `autores_returns_autores_slice_verbatim_across_permutations`
13095 // (b5d813f) opened,
13096 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
13097 // (78c7d3c) folded on, and
13098 // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
13099 // (8a36c23) closed the universal-axis text-tag family of.
13100 // Opens the outer-`Caixa` foreign-code-slot `&[T]` sub-family
13101 // the sibling `:servicos` future lift closes onto. Pins against
13102 // a future silent detour that returned an owned `Vec<String>`
13103 // (which would type-check but silently clone on every accessor
13104 // call, breaking the zero-cost projection every peer sibling
13105 // slice accessor carries), a `[""] → []` collapse (which would
13106 // silently absorb the `CodePathEmpty` refusal case at the
13107 // accessor boundary), or an `["exe/cli", "exe/cli"] →
13108 // ["exe/cli"]` dedup collapse (which would silently absorb the
13109 // `CodePathDuplicate` refusal case at the accessor boundary —
13110 // the per-slot set-not-multiset gate is downstream of the
13111 // accessor and must not be silently promoted into it).
13112 for exe in [
13113 vec![],
13114 vec![""],
13115 vec!["exe/cli"],
13116 vec!["exe/cli", "exe/serve"],
13117 vec!["exe/cli", "exe/cli"],
13118 ] {
13119 let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
13120 let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
13121 assert_eq!(
13122 c.exe(),
13123 expected.as_slice(),
13124 "Caixa::exe must return :exe verbatim (got {:?}, \
13125 expected {expected:?})",
13126 c.exe(),
13127 );
13128 assert_eq!(
13129 c.exe(),
13130 c.exe.as_slice(),
13131 "Caixa::exe must byte-equal the raw \
13132 `self.exe.as_slice()` field access across every value \
13133 in the Vec<String> accept-set",
13134 );
13135 }
13136 }
13137
13138 #[test]
13139 fn validate_code_paths_exe_empty_arm_routes_through_accessor() {
13140 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
13141 // empty-arm gate on the `:exe` slot must key off
13142 // [`Caixa::exe`], not a divergent raw `&self.exe` field-borrow
13143 // walk. Structurally: a `Caixa { exe: vec!["".into()], .. }`
13144 // must surface the `CodePathEmpty { slot: ":exe" }` refusal
13145 // exactly, and a `Caixa { exe: vec!["exe/cli".into()], .. }`
13146 // (the canonical single-executable form every in-tree
13147 // `caixa_with_code_paths` positive control uses) must pass
13148 // validate. The pair jointly pins the accessor + validate-gate
13149 // composition: any future silent detour that had the accessor
13150 // return an empty slice on the `[""]` arm (a
13151 // `.iter().filter(|s| !s.is_empty()).collect()` collapse) would
13152 // silently absorb the `CodePathEmpty` refusal at the accessor
13153 // boundary and the validate gate would accept a struct-literal
13154 // `Caixa { exe: vec!["".into()], .. }` — the composition pin
13155 // catches that at caixa-core build time.
13156 //
13157 // Peer of the per-`Caixa`
13158 // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
13159 // (8a36c23), `validate_autores_empty_arm_routes_through_accessor`
13160 // (b5d813f), and
13161 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
13162 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
13163 // composition axes — same "the validate / shape-gate predicate
13164 // must route through the substrate-primitive typed dispatch"
13165 // discipline extended onto the sibling outer top-level [`Caixa`]
13166 // `&[T]`-composition surface. Nominally the in-tree
13167 // `validate_code_paths` production body still keys off the
13168 // internal `[(":bibliotecas", &self.bibliotecas,
13169 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
13170 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
13171 // (the tuple's homogeneous slice-typed shape blocks a per-
13172 // element accessor swap in isolation — a future companion lift
13173 // for `:servicos` on the same outer-`Caixa` `&[T]` slice-
13174 // accessor axis closes that tuple onto the triple of typed
13175 // dispatches as a unit); the composition pin catches any future
13176 // accessor-side silent filter drop against that eventual tuple-
13177 // closure regardless of whether the `:exe` slot is threaded
13178 // through the accessor or the raw field access at the tuple's
13179 // construction site.
13180 let c = caixa_with_code_paths(vec![], vec![""], vec![]);
13181 assert!(
13182 matches!(
13183 c.validate_code_paths(),
13184 Err(ManifestError::CodePathEmpty { slot: ":exe" })
13185 ),
13186 "validate_code_paths must reject exe == vec![\"\"] \
13187 with CodePathEmpty {{ slot: \":exe\" }} — the \
13188 accessor and the validate gate must route through the \
13189 same substrate-primitive typed dispatch on the \
13190 :exe per-entry empty arm",
13191 );
13192 let c = caixa_with_code_paths(vec![], vec!["exe/cli"], vec![]);
13193 assert!(
13194 c.validate_code_paths().is_ok(),
13195 "validate_code_paths must accept exe == vec![\"exe/cli\"] \
13196 (the canonical single-executable shape every in-tree \
13197 `caixa_with_code_paths` positive control uses)",
13198 );
13199 }
13200
13201 #[test]
13202 fn exe_projects_slice_by_borrow() {
13203 // The by-borrow pin: [`Caixa::exe`] returns `&[String]` by
13204 // borrow — the returned slice borrows the underlying
13205 // `Vec<String>` storage of the `:exe` slot and the accessor
13206 // must not clone the backing `Vec` on every call. Peer of the
13207 // per-`Caixa` `autores_projects_slice_by_borrow` (b5d813f),
13208 // `etiquetas_projects_slice_by_borrow` (78c7d3c), and
13209 // `bibliotecas_projects_slice_by_borrow` (8a36c23) by-borrow
13210 // pins on the sibling outer top-level [`Caixa`] `&[String]`-
13211 // return axes — the accessor's returned slice must borrow from
13212 // `&self` (the returned reference's lifetime is tied to
13213 // `&self`), and calling the accessor twice on the same
13214 // [`Caixa`] must yield slices that are pointer-equal (the
13215 // underlying byte-buffer is the storage `Vec`'s allocation,
13216 // not a fresh copy) as well as value-equal (idempotent, no
13217 // side effects on `&self`).
13218 //
13219 // Pins against a future silent detour that returned an owned
13220 // `Vec<String>` (which would type-check but silently clone on
13221 // every call, breaking the zero-cost projection every peer
13222 // sibling slice accessor carries), a `&Vec<String>` return
13223 // (which would leak the backing `Vec`'s grow/push/reserve
13224 // surface no downstream consumer reaches for), or a one-arm-
13225 // only accessor that returned a saturating value on some
13226 // sentinel input (breaking the pass-through invariant the
13227 // sibling slice accessors carry).
13228 for exe in [
13229 vec![],
13230 vec!["exe/cli"],
13231 vec!["exe/cli", "exe/serve"],
13232 vec!["exe/cli", "exe/cli"],
13233 ] {
13234 let c = caixa_with_code_paths(vec![], exe.clone(), vec![]);
13235 let expected: Vec<String> = exe.iter().map(|s| (*s).to_string()).collect();
13236 let first = c.exe();
13237 let second = c.exe();
13238 assert_eq!(
13239 first, second,
13240 "Caixa::exe must be idempotent — two successive calls \
13241 on the same &self must return the same &[String]",
13242 );
13243 assert_eq!(
13244 first.as_ptr(),
13245 second.as_ptr(),
13246 "Caixa::exe must borrow the underlying Vec<String> \
13247 storage — two successive calls must return slices \
13248 with the same backing pointer (a fresh Vec<String> \
13249 clone would change the pointer on every call)",
13250 );
13251 assert_eq!(
13252 first,
13253 expected.as_slice(),
13254 "Caixa::exe must return :exe verbatim by borrow — \
13255 got {first:?}, expected {expected:?}",
13256 );
13257 }
13258 }
13259
13260 // ── Caixa::servicos — outer top-level &[T] slice accessor ─────────
13261
13262 #[test]
13263 fn servicos_returns_servicos_slice_verbatim_across_permutations() {
13264 // The canonical per-`Caixa` `:servicos` universal-axis
13265 // ComputeUnit-CR-YAML-entry-path-list slice pin:
13266 // [`Caixa::servicos`] must return the `:servicos` typed
13267 // [`Vec<String>`] list verbatim as a `&[String]`, byte-equal to
13268 // the raw `self.servicos.as_slice()` access across every
13269 // representative value in the accept-set — `[]` (the "no
13270 // ComputeUnit-CR declared" arm every `:kind` other than
13271 // `Servico` carries; the layout's [`crate::LayoutInvariants`]
13272 // `ServicoWithoutServicos` arm-gate fires exactly on this
13273 // empty-slot + `Servico`-kind combination), `[""]` (a past-the-
13274 // guard sentinel that pins the accessor doesn't perform a
13275 // silent `[""] → []` collapse on the empty-entry arm — validate
13276 // rejects `[""]` through `CodePathEmpty { slot: ":servicos" }`
13277 // but the accessor must ship the raw slot verbatim so a
13278 // validate-time gate regression surfaces at the layout /
13279 // per-Servico renderer boundary rather than being silently
13280 // absorbed into a component-drop),
13281 // `["servicos/demo.computeunit.yaml"]` (the canonical
13282 // singleton V0-shape every in-tree `caixa_with_code_paths`
13283 // positive control uses; the same shape
13284 // [`crate::require_single_servico`] admits),
13285 // `["servicos/a.computeunit.yaml", "servicos/b.computeunit.
13286 // yaml"]` (a past-the-guard `len != 1` sentinel — the V0
13287 // singularity gate rejects through `ServicoCountMismatch
13288 // { count: 2 }` but the accessor must ship the raw slot
13289 // verbatim so struct-literal `Caixa { servicos: vec![...,
13290 // ...], .. }` fixtures continue to expose the count at the
13291 // accessor), and `["servicos/a.computeunit.yaml",
13292 // "servicos/a.computeunit.yaml"]` (a past-the-guard duplicate
13293 // sentinel — validate rejects through
13294 // `CodePathDuplicate { slot: ":servicos" }` per the per-slot
13295 // set-not-multiset gate, but the accessor must ship the raw
13296 // slot verbatim so struct-literal fixtures continue to expose
13297 // the duplicate at the accessor).
13298 //
13299 // Fifth and final outer top-level [`Caixa`] `&[T]`-return
13300 // slice accessor pin on the substrate primitive — folds on the
13301 // "outer [`Caixa`] `&[T]` slice" projection pattern
13302 // `autores_returns_autores_slice_verbatim_across_permutations`
13303 // (b5d813f) opened,
13304 // `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
13305 // (78c7d3c) folded on,
13306 // `bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
13307 // (8a36c23) closed the universal-axis text-tag family of, and
13308 // `exe_returns_exe_slice_verbatim_across_permutations`
13309 // (65d9527) opened the foreign-code-slot sub-family of. Closes
13310 // the outer-`Caixa` foreign-code-slot `&[T]` sub-family — the
13311 // trio of code-surface list slots (`:bibliotecas` + `:exe` +
13312 // `:servicos`) now each carries a substrate-canonical slice
13313 // accessor. Pins against a future silent detour that returned
13314 // an owned `Vec<String>` (which would type-check but silently
13315 // clone on every accessor call, breaking the zero-cost
13316 // projection every peer sibling slice accessor carries), a
13317 // `[""] → []` collapse (which would silently absorb the
13318 // `CodePathEmpty` refusal case at the accessor boundary), an
13319 // `[a, a] → [a]` dedup collapse (which would silently absorb
13320 // the `CodePathDuplicate` refusal case at the accessor
13321 // boundary — the per-slot set-not-multiset gate is downstream
13322 // of the accessor and must not be silently promoted into it),
13323 // or a `[a, b] → [a]` singleton collapse (which would silently
13324 // absorb the V0 `ServicoCountMismatch` refusal case at the
13325 // accessor boundary — the V0 singularity gate is downstream of
13326 // the accessor and must not be silently promoted into it).
13327 for servicos in [
13328 vec![],
13329 vec![""],
13330 vec!["servicos/demo.computeunit.yaml"],
13331 vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
13332 vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
13333 ] {
13334 let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
13335 let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
13336 assert_eq!(
13337 c.servicos(),
13338 expected.as_slice(),
13339 "Caixa::servicos must return :servicos verbatim (got \
13340 {:?}, expected {expected:?})",
13341 c.servicos(),
13342 );
13343 assert_eq!(
13344 c.servicos(),
13345 c.servicos.as_slice(),
13346 "Caixa::servicos must byte-equal the raw \
13347 `self.servicos.as_slice()` field access across every \
13348 value in the Vec<String> accept-set",
13349 );
13350 }
13351 }
13352
13353 #[test]
13354 fn validate_code_paths_servicos_empty_arm_routes_through_accessor() {
13355 // Composition pin: [`Caixa::validate_code_paths`]'s per-entry
13356 // empty-arm gate on the `:servicos` slot must key off
13357 // [`Caixa::servicos`], not a divergent raw `&self.servicos`
13358 // field-borrow walk. Structurally: a `Caixa { servicos:
13359 // vec!["".into()], .. }` must surface the `CodePathEmpty
13360 // { slot: ":servicos" }` refusal exactly, and a `Caixa
13361 // { servicos: vec!["servicos/demo.computeunit.yaml".into()],
13362 // .. }` (the canonical singleton V0-shape every in-tree
13363 // `caixa_with_code_paths` positive control uses) must pass
13364 // validate. The pair jointly pins the accessor + validate-gate
13365 // composition: any future silent detour that had the accessor
13366 // return an empty slice on the `[""]` arm (a `.iter().filter
13367 // (|s| !s.is_empty()).collect()` collapse) would silently
13368 // absorb the `CodePathEmpty` refusal at the accessor boundary
13369 // and the validate gate would accept a struct-literal
13370 // `Caixa { servicos: vec!["".into()], .. }` — the composition
13371 // pin catches that at caixa-core build time.
13372 //
13373 // Peer of the per-`Caixa`
13374 // `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
13375 // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
13376 // (65d9527), `validate_autores_empty_arm_routes_through_accessor`
13377 // (b5d813f), and
13378 // `validate_etiquetas_empty_entry_arm_routes_through_accessor`
13379 // (78c7d3c) accessor-composition pins on the sibling `&[T]`-
13380 // composition axes — same "the validate / shape-gate predicate
13381 // must route through the substrate-primitive typed dispatch"
13382 // discipline extended onto the sibling outer top-level
13383 // [`Caixa`] `&[T]`-composition surface, closing the trio of
13384 // code-surface accessor-composition pins on the same axis.
13385 // Nominally the in-tree `validate_code_paths` production body
13386 // still keys off the internal
13387 // `[(":bibliotecas", &self.bibliotecas,
13388 // CodePathFileType::LispSource), (":exe", &self.exe, ..),
13389 // (":servicos", &self.servicos, ..)]` per-slot dispatch tuple
13390 // (the tuple's homogeneous `&Vec<String>`-typed shape blocks a
13391 // per-element accessor swap in isolation — a future companion
13392 // lift promotes the tuple's element type to `&[String]` and
13393 // threads the triple of typed dispatches through as a unit);
13394 // the composition pin catches any future accessor-side silent
13395 // filter drop against that eventual tuple-closure regardless
13396 // of whether the `:servicos` slot is threaded through the
13397 // accessor or the raw field access at the tuple's construction
13398 // site.
13399 let c = caixa_with_code_paths(vec![], vec![], vec![""]);
13400 assert!(
13401 matches!(
13402 c.validate_code_paths(),
13403 Err(ManifestError::CodePathEmpty { slot: ":servicos" })
13404 ),
13405 "validate_code_paths must reject servicos == vec![\"\"] \
13406 with CodePathEmpty {{ slot: \":servicos\" }} — the \
13407 accessor and the validate gate must route through the \
13408 same substrate-primitive typed dispatch on the \
13409 :servicos per-entry empty arm",
13410 );
13411 let c = caixa_with_code_paths(vec![], vec![], vec!["servicos/demo.computeunit.yaml"]);
13412 assert!(
13413 c.validate_code_paths().is_ok(),
13414 "validate_code_paths must accept servicos == \
13415 vec![\"servicos/demo.computeunit.yaml\"] (the canonical \
13416 singleton V0-shape every in-tree `caixa_with_code_paths` \
13417 positive control uses)",
13418 );
13419 }
13420
13421 #[test]
13422 fn servicos_projects_slice_by_borrow() {
13423 // The by-borrow pin: [`Caixa::servicos`] returns `&[String]` by
13424 // borrow — the returned slice borrows the underlying
13425 // `Vec<String>` storage of the `:servicos` slot and the
13426 // accessor must not clone the backing `Vec` on every call.
13427 // Peer of the per-`Caixa` `autores_projects_slice_by_borrow`
13428 // (b5d813f), `etiquetas_projects_slice_by_borrow` (78c7d3c),
13429 // `bibliotecas_projects_slice_by_borrow` (8a36c23), and
13430 // `exe_projects_slice_by_borrow` (65d9527) by-borrow pins on
13431 // the sibling outer top-level [`Caixa`] `&[String]`-return
13432 // axes — the accessor's returned slice must borrow from
13433 // `&self` (the returned reference's lifetime is tied to
13434 // `&self`), and calling the accessor twice on the same
13435 // [`Caixa`] must yield slices that are pointer-equal (the
13436 // underlying byte-buffer is the storage `Vec`'s allocation,
13437 // not a fresh copy) as well as value-equal (idempotent, no
13438 // side effects on `&self`).
13439 //
13440 // Pins against a future silent detour that returned an owned
13441 // `Vec<String>` (which would type-check but silently clone on
13442 // every call, breaking the zero-cost projection every peer
13443 // sibling slice accessor carries), a `&Vec<String>` return
13444 // (which would leak the backing `Vec`'s grow/push/reserve
13445 // surface no downstream consumer reaches for), or a one-arm-
13446 // only accessor that returned a saturating value on some
13447 // sentinel input (breaking the pass-through invariant the
13448 // sibling slice accessors carry).
13449 for servicos in [
13450 vec![],
13451 vec!["servicos/demo.computeunit.yaml"],
13452 vec!["servicos/a.computeunit.yaml", "servicos/b.computeunit.yaml"],
13453 vec!["servicos/a.computeunit.yaml", "servicos/a.computeunit.yaml"],
13454 ] {
13455 let c = caixa_with_code_paths(vec![], vec![], servicos.clone());
13456 let expected: Vec<String> = servicos.iter().map(|s| (*s).to_string()).collect();
13457 let first = c.servicos();
13458 let second = c.servicos();
13459 assert_eq!(
13460 first, second,
13461 "Caixa::servicos must be idempotent — two successive \
13462 calls on the same &self must return the same &[String]",
13463 );
13464 assert_eq!(
13465 first.as_ptr(),
13466 second.as_ptr(),
13467 "Caixa::servicos must borrow the underlying \
13468 Vec<String> storage — two successive calls must \
13469 return slices with the same backing pointer (a fresh \
13470 Vec<String> clone would change the pointer on every \
13471 call)",
13472 );
13473 assert_eq!(
13474 first,
13475 expected.as_slice(),
13476 "Caixa::servicos must return :servicos verbatim by \
13477 borrow — got {first:?}, expected {expected:?}",
13478 );
13479 }
13480 }
13481
13482 // ── Caixa::deps — outer top-level &[Dep] slice accessor ───────────
13483
13484 fn caixa_with_deps(deps: Vec<Dep>) -> Caixa {
13485 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13486 c.deps = deps;
13487 c
13488 }
13489
13490 #[test]
13491 fn deps_returns_deps_slice_verbatim_across_permutations() {
13492 // The canonical per-`Caixa` `:deps` universal-axis runtime-
13493 // dependency-declaration-list slice pin: [`Caixa::deps`] must
13494 // return the `:deps` typed [`Vec<Dep>`] list verbatim as a
13495 // `&[Dep]`, element-equal to the raw `self.deps.as_slice()`
13496 // access across every representative value in the accept-set —
13497 // `[]` (the "no runtime deps declared" arm every existing
13498 // fixture without a `:deps` line carries; the
13499 // [`Caixa::template`] scaffold emits `:deps ()`), a canonical
13500 // single-entry list (the shape most consumer caixas carry), a
13501 // canonical two-entry list (the multi-dep runtime closure), and
13502 // two past-the-guard sentinels — a `[""]`-`:nome` entry
13503 // ([`Self::validate_deps`] rejects through `NomeEmpty` /
13504 // `NomeInvalid` but the accessor must ship the raw slot
13505 // verbatim) and a `[a, a]` duplicate (validate rejects through
13506 // `DuplicateNome { list: ":deps" }` but the accessor must ship
13507 // the raw slot verbatim so struct-literal fixtures continue to
13508 // expose the duplicate at the accessor).
13509 //
13510 // First outer top-level [`Caixa`] `&[Dep]`-return slice accessor
13511 // pin on the substrate primitive — opens the outer-`Caixa`
13512 // dependency-slot `&[Dep]` sub-family the sibling `:deps-dev`
13513 // future lift closes on. Peer of the closed outer-`Caixa`
13514 // foreign-code-slot `&[String]` sub-family
13515 // (`bibliotecas_returns_bibliotecas_slice_verbatim_across_permutations`
13516 // 8a36c23, `exe_returns_exe_slice_verbatim_across_permutations`
13517 // 65d9527, `servicos_returns_servicos_slice_verbatim_across_permutations`
13518 // 611f78b) and the outer-`Caixa` universal-axis text-tag family
13519 // (`autores_returns_autores_slice_verbatim_across_permutations`
13520 // b5d813f, `etiquetas_returns_etiquetas_slice_verbatim_across_permutations`
13521 // 78c7d3c) — extends the "outer [`Caixa`] `&[T]` slice"
13522 // projection pattern onto a novel element-type axis (`Dep`
13523 // composite vs the prior sibling family's `String` scalar).
13524 // Pins against a future silent detour that returned an owned
13525 // `Vec<Dep>` (which would type-check but silently clone on every
13526 // accessor call, breaking the zero-cost projection every peer
13527 // sibling slice accessor carries), a `[""] → []` collapse (which
13528 // would silently absorb the `NomeEmpty` refusal case at the
13529 // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
13530 // would silently absorb the `DuplicateNome` refusal case at the
13531 // accessor boundary).
13532 for deps in [
13533 vec![],
13534 vec![Dep::simple("", "^0.1")],
13535 vec![Dep::simple("caixa-teia", "^0.1")],
13536 vec![
13537 Dep::simple("caixa-teia", "^0.1"),
13538 Dep::simple("caixa-core", "^0.1"),
13539 ],
13540 vec![
13541 Dep::simple("caixa-teia", "^0.1"),
13542 Dep::simple("caixa-teia", "^0.2"),
13543 ],
13544 ] {
13545 let c = caixa_with_deps(deps.clone());
13546 assert_eq!(
13547 c.deps(),
13548 deps.as_slice(),
13549 "Caixa::deps must return :deps verbatim (got {:?}, \
13550 expected {deps:?})",
13551 c.deps(),
13552 );
13553 assert_eq!(
13554 c.deps(),
13555 c.deps.as_slice(),
13556 "Caixa::deps must element-equal the raw \
13557 `self.deps.as_slice()` field access across every \
13558 value in the Vec<Dep> accept-set",
13559 );
13560 }
13561 }
13562
13563 #[test]
13564 fn validate_deps_duplicate_arm_routes_through_accessor() {
13565 // Composition pin: [`Caixa::validate_deps`]'s within-`:deps`
13566 // duplicate-`:nome` gate must key off [`Caixa::deps`], not the
13567 // raw `&self.deps` field-borrow walk. Structurally: a `Caixa
13568 // { deps: vec![Dep::simple("d", "^0.1"), Dep::simple("d",
13569 // "^0.2")], .. }` must surface the `DuplicateNome { list:
13570 // ":deps" }` refusal exactly, and a `Caixa { deps: vec![
13571 // Dep::simple("d", "^0.1")], .. }` (the canonical single-entry
13572 // form) must pass validate. The pair jointly pins the accessor +
13573 // validate-gate composition: any future silent detour that had
13574 // the accessor return a dedupped slice on the `[a, a]` arm (a
13575 // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
13576 // would silently absorb the `DuplicateNome` refusal at the
13577 // accessor boundary and the validate gate would accept a
13578 // struct-literal `Caixa` carrying the drift — the composition
13579 // pin catches that at caixa-core build time.
13580 //
13581 // Peer of the per-`Caixa`
13582 // `validate_autores_empty_entry_arm_routes_through_accessor`
13583 // (b5d813f), `validate_etiquetas_empty_entry_arm_routes_through_accessor`
13584 // (78c7d3c), `validate_code_paths_bibliotecas_empty_arm_routes_through_accessor`
13585 // (8a36c23), `validate_code_paths_exe_empty_arm_routes_through_accessor`
13586 // (65d9527), and `validate_code_paths_servicos_empty_arm_routes_through_accessor`
13587 // (611f78b) accessor-composition pins on the sibling `&[T]`-
13588 // composition axes — same "the validate gate must route through
13589 // the substrate-primitive typed dispatch" discipline extended
13590 // onto the sibling outer top-level [`Caixa`] `&[Dep]`-
13591 // composition surface, opening the outer-`Caixa` dependency-slot
13592 // arm of the composition-pin family.
13593 let c = caixa_with_deps(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
13594 let err = c.validate_deps().unwrap_err();
13595 assert!(
13596 matches!(
13597 err,
13598 DepError::DuplicateNome { ref nome, list } if nome == "d"
13599 && list == crate::render::DEP_AUTHOR_KEY_DEPS
13600 ),
13601 "validate_deps must reject deps == \
13602 vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
13603 DuplicateNome {{ nome: \"d\", list: \":deps\" }} — the \
13604 accessor and the validate gate must route through the \
13605 same substrate-primitive typed dispatch on the :deps \
13606 within-list duplicate arm (got {err:?})",
13607 );
13608 let c = caixa_with_deps(vec![Dep::simple("d", "^0.1")]);
13609 assert!(
13610 c.validate_deps().is_ok(),
13611 "validate_deps must accept deps == vec![Dep(\"d\",\"^0.1\")] \
13612 (the canonical single-entry form)",
13613 );
13614 }
13615
13616 #[test]
13617 fn deps_projects_slice_by_borrow() {
13618 // The by-borrow pin: [`Caixa::deps`] returns `&[Dep]` by borrow
13619 // — the returned slice borrows the underlying `Vec<Dep>` storage
13620 // of the `:deps` slot and the accessor must not clone the
13621 // backing `Vec` on every call. Peer of the per-`Caixa`
13622 // `autores_projects_slice_by_borrow` (b5d813f),
13623 // `etiquetas_projects_slice_by_borrow` (78c7d3c),
13624 // `bibliotecas_projects_slice_by_borrow` (8a36c23),
13625 // `exe_projects_slice_by_borrow` (65d9527), and
13626 // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
13627 // on the sibling outer top-level [`Caixa`] `&[String]`-return
13628 // axes — the accessor's returned slice must borrow from `&self`
13629 // (the returned reference's lifetime is tied to `&self`), and
13630 // calling the accessor twice on the same [`Caixa`] must yield
13631 // slices that are pointer-equal (the underlying byte-buffer is
13632 // the storage `Vec`'s allocation, not a fresh copy) as well as
13633 // value-equal (idempotent, no side effects on `&self`).
13634 //
13635 // Pins against a future silent detour that returned an owned
13636 // `Vec<Dep>` (which would type-check but silently clone on
13637 // every call), a `&Vec<Dep>` return (which would leak the
13638 // backing `Vec`'s grow/push/reserve surface no downstream
13639 // consumer reaches for), or a one-arm-only accessor that
13640 // returned a saturating value on some sentinel input.
13641 for deps in [
13642 vec![],
13643 vec![Dep::simple("caixa-teia", "^0.1")],
13644 vec![
13645 Dep::simple("caixa-teia", "^0.1"),
13646 Dep::simple("caixa-core", "^0.1"),
13647 ],
13648 ] {
13649 let c = caixa_with_deps(deps.clone());
13650 let first = c.deps();
13651 let second = c.deps();
13652 assert_eq!(
13653 first, second,
13654 "Caixa::deps must be idempotent — two successive calls \
13655 on the same &self must return the same &[Dep]",
13656 );
13657 assert_eq!(
13658 first.as_ptr(),
13659 second.as_ptr(),
13660 "Caixa::deps must borrow the underlying Vec<Dep> \
13661 storage — two successive calls must return slices \
13662 with the same backing pointer (a fresh Vec<Dep> clone \
13663 would change the pointer on every call)",
13664 );
13665 assert_eq!(
13666 first,
13667 deps.as_slice(),
13668 "Caixa::deps must return :deps verbatim by borrow — \
13669 got {first:?}, expected {deps:?}",
13670 );
13671 }
13672 }
13673
13674 // ── Caixa::deps_dev — outer top-level &[Dep] slice accessor ──────
13675
13676 fn caixa_with_deps_dev(deps_dev: Vec<Dep>) -> Caixa {
13677 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13678 c.deps_dev = deps_dev;
13679 c
13680 }
13681
13682 #[test]
13683 fn deps_dev_returns_deps_dev_slice_verbatim_across_permutations() {
13684 // The canonical per-`Caixa` `:deps-dev` universal-axis dev-only-
13685 // dependency-declaration-list slice pin: [`Caixa::deps_dev`]
13686 // must return the `:deps-dev` typed [`Vec<Dep>`] list verbatim as
13687 // a `&[Dep]`, element-equal to the raw `self.deps_dev.as_slice()`
13688 // access across every representative value in the accept-set —
13689 // `[]` (the "no dev deps declared" arm every existing fixture
13690 // without a `:deps-dev` line carries; the [`Caixa::template`]
13691 // scaffold emits `:deps-dev ()`), a canonical single-entry list
13692 // (the shape most consumer caixas carry — a `tatara-check` dev
13693 // pin), a canonical two-entry list (the multi-dev-dep closure),
13694 // and two past-the-guard sentinels — a `[""]`-`:nome` entry
13695 // ([`Self::validate_deps`] rejects through `NomeEmpty` /
13696 // `NomeInvalid` but the accessor must ship the raw slot
13697 // verbatim) and a `[a, a]` duplicate (validate rejects through
13698 // `DuplicateNome { list: ":deps-dev" }` but the accessor must
13699 // ship the raw slot verbatim so struct-literal fixtures continue
13700 // to expose the duplicate at the accessor).
13701 //
13702 // Second outer top-level [`Caixa`] `&[Dep]`-return slice-accessor
13703 // pin on the substrate primitive — closes the outer-`Caixa`
13704 // dependency-slot `&[Dep]` sub-family the sibling
13705 // `deps_returns_deps_slice_verbatim_across_permutations`
13706 // (ad34b4e) opened on. Folds the "outer [`Caixa`] `&[Dep]`
13707 // slice" projection pattern onto the sibling dev-dep axis —
13708 // pins against a future silent detour that returned an owned
13709 // `Vec<Dep>` (which would type-check but silently clone on every
13710 // accessor call, breaking the zero-cost projection every peer
13711 // sibling slice accessor carries), a `[""] → []` collapse (which
13712 // would silently absorb the `NomeEmpty` refusal case at the
13713 // accessor boundary), or a `[a, a] → [a]` dedup collapse (which
13714 // would silently absorb the `DuplicateNome` refusal case at the
13715 // accessor boundary).
13716 for deps_dev in [
13717 vec![],
13718 vec![Dep::simple("", "^0.1")],
13719 vec![Dep::simple("tatara-check", "^0.1")],
13720 vec![
13721 Dep::simple("tatara-check", "^0.1"),
13722 Dep::simple("caixa-lint", "^0.1"),
13723 ],
13724 vec![
13725 Dep::simple("tatara-check", "^0.1"),
13726 Dep::simple("tatara-check", "^0.2"),
13727 ],
13728 ] {
13729 let c = caixa_with_deps_dev(deps_dev.clone());
13730 assert_eq!(
13731 c.deps_dev(),
13732 deps_dev.as_slice(),
13733 "Caixa::deps_dev must return :deps-dev verbatim (got \
13734 {:?}, expected {deps_dev:?})",
13735 c.deps_dev(),
13736 );
13737 assert_eq!(
13738 c.deps_dev(),
13739 c.deps_dev.as_slice(),
13740 "Caixa::deps_dev must element-equal the raw \
13741 `self.deps_dev.as_slice()` field access across every \
13742 value in the Vec<Dep> accept-set",
13743 );
13744 }
13745 }
13746
13747 #[test]
13748 fn validate_deps_duplicate_deps_dev_arm_routes_through_accessor() {
13749 // Composition pin: [`Caixa::validate_deps`]'s within-`:deps-dev`
13750 // duplicate-`:nome` gate must key off [`Caixa::deps_dev`], not
13751 // the raw `&self.deps_dev` field-borrow walk. Structurally: a
13752 // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1"),
13753 // Dep::simple("d", "^0.2")], .. }` must surface the
13754 // `DuplicateNome { list: ":deps-dev" }` refusal exactly, and a
13755 // `Caixa { deps_dev: vec![Dep::simple("d", "^0.1")], .. }` (the
13756 // canonical single-entry form) must pass validate. The pair
13757 // jointly pins the accessor + validate-gate composition: any
13758 // future silent detour that had the accessor return a dedupped
13759 // slice on the `[a, a]` arm (a
13760 // `.iter().unique_by(|d| d.nome.as_str()).collect()` collapse)
13761 // would silently absorb the `DuplicateNome` refusal at the
13762 // accessor boundary and the validate gate would accept a
13763 // struct-literal `Caixa` carrying the drift — the composition
13764 // pin catches that at caixa-core build time.
13765 //
13766 // Peer of `validate_deps_duplicate_arm_routes_through_accessor`
13767 // (ad34b4e) on the sibling `:deps` axis — same "the validate
13768 // gate must route through the substrate-primitive typed
13769 // dispatch" discipline folded onto the sibling `:deps-dev`
13770 // axis, closing the two-list dep-graph composition-pin family.
13771 // The `:deps-dev` diagnostic must carry the
13772 // `DEP_AUTHOR_KEY_DEPS_DEV` list-tag (not
13773 // `DEP_AUTHOR_KEY_DEPS`) so the emitted error names the
13774 // offending list unambiguously.
13775 let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1"), Dep::simple("d", "^0.2")]);
13776 let err = c.validate_deps().unwrap_err();
13777 assert!(
13778 matches!(
13779 err,
13780 DepError::DuplicateNome { ref nome, list } if nome == "d"
13781 && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
13782 ),
13783 "validate_deps must reject deps_dev == \
13784 vec![Dep(\"d\",\"^0.1\"), Dep(\"d\",\"^0.2\")] with \
13785 DuplicateNome {{ nome: \"d\", list: \":deps-dev\" }} — the \
13786 accessor and the validate gate must route through the \
13787 same substrate-primitive typed dispatch on the :deps-dev \
13788 within-list duplicate arm (got {err:?})",
13789 );
13790 let c = caixa_with_deps_dev(vec![Dep::simple("d", "^0.1")]);
13791 assert!(
13792 c.validate_deps().is_ok(),
13793 "validate_deps must accept deps_dev == \
13794 vec![Dep(\"d\",\"^0.1\")] (the canonical single-entry form)",
13795 );
13796 }
13797
13798 #[test]
13799 fn deps_dev_projects_slice_by_borrow() {
13800 // The by-borrow pin: [`Caixa::deps_dev`] returns `&[Dep]` by
13801 // borrow — the returned slice borrows the underlying `Vec<Dep>`
13802 // storage of the `:deps-dev` slot and the accessor must not
13803 // clone the backing `Vec` on every call. Peer of
13804 // `deps_projects_slice_by_borrow` (ad34b4e) on the sibling
13805 // `:deps` axis, and of the per-`Caixa`
13806 // `autores_projects_slice_by_borrow` (b5d813f),
13807 // `etiquetas_projects_slice_by_borrow` (78c7d3c),
13808 // `bibliotecas_projects_slice_by_borrow` (8a36c23),
13809 // `exe_projects_slice_by_borrow` (65d9527), and
13810 // `servicos_projects_slice_by_borrow` (611f78b) by-borrow pins
13811 // on the sibling outer top-level [`Caixa`] `&[String]`-return
13812 // axes — the accessor's returned slice must borrow from `&self`
13813 // (the returned reference's lifetime is tied to `&self`), and
13814 // calling the accessor twice on the same [`Caixa`] must yield
13815 // slices that are pointer-equal (the underlying byte-buffer is
13816 // the storage `Vec`'s allocation, not a fresh copy) as well as
13817 // value-equal (idempotent, no side effects on `&self`).
13818 //
13819 // Pins against a future silent detour that returned an owned
13820 // `Vec<Dep>` (which would type-check but silently clone on
13821 // every call), a `&Vec<Dep>` return (which would leak the
13822 // backing `Vec`'s grow/push/reserve surface no downstream
13823 // consumer reaches for), or a one-arm-only accessor that
13824 // returned a saturating value on some sentinel input.
13825 for deps_dev in [
13826 vec![],
13827 vec![Dep::simple("tatara-check", "^0.1")],
13828 vec![
13829 Dep::simple("tatara-check", "^0.1"),
13830 Dep::simple("caixa-lint", "^0.1"),
13831 ],
13832 ] {
13833 let c = caixa_with_deps_dev(deps_dev.clone());
13834 let first = c.deps_dev();
13835 let second = c.deps_dev();
13836 assert_eq!(
13837 first, second,
13838 "Caixa::deps_dev must be idempotent — two successive \
13839 calls on the same &self must return the same &[Dep]",
13840 );
13841 assert_eq!(
13842 first.as_ptr(),
13843 second.as_ptr(),
13844 "Caixa::deps_dev must borrow the underlying Vec<Dep> \
13845 storage — two successive calls must return slices \
13846 with the same backing pointer (a fresh Vec<Dep> clone \
13847 would change the pointer on every call)",
13848 );
13849 assert_eq!(
13850 first,
13851 deps_dev.as_slice(),
13852 "Caixa::deps_dev must return :deps-dev verbatim by \
13853 borrow — got {first:?}, expected {deps_dev:?}",
13854 );
13855 }
13856 }
13857
13858 // ── Caixa::limits — outer top-level Option<&LimitsSpec> composite-reference accessor ──
13859
13860 fn caixa_with_limits(limits: Option<crate::LimitsSpec>) -> Caixa {
13861 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
13862 c.limits = limits;
13863 c
13864 }
13865
13866 #[test]
13867 fn limits_returns_limits_option_ref_verbatim_across_permutations() {
13868 // The canonical per-`Caixa` `:limits` M2 typed-slot outer-
13869 // composite optional-composite-reference-shape pin:
13870 // [`Caixa::limits`] must return the `:limits` typed
13871 // `Option<LimitsSpec>` verbatim as an `Option<&LimitsSpec>`
13872 // reference over the same backing storage the raw
13873 // `self.limits.as_ref()` field access borrows from, byte-equal
13874 // across every representative fixture in the accept-set — the
13875 // author-omitted `None` shape (the "engine-default applies"
13876 // partition every downstream Servico M2 overlay emitter treats
13877 // as "emit nothing"), the empty-composite `Some(LimitsSpec {
13878 // .. default })` shape ([`LimitsSpec::is_empty`] holds — every
13879 // per-axis cap is `None`, so the peer M2 overlay emitter's
13880 // `.is_empty()`-gated projection still emits nothing but the
13881 // outer presence-bit is `Some`, so [`Caixa::declared_servico_slots`]
13882 // still pushes the `M2_AUTHOR_KEY_LIMITS` label), a single-axis
13883 // fixture (only `:memory` set — the canonical shape most
13884 // memory-heavy Servicos carry), and a fully-populated composite
13885 // (every per-axis cap set — the canonical shape a
13886 // sandboxed-by-default Servico carries).
13887 //
13888 // Pins against a future silent detour that returned a fresh-
13889 // cloned [`LimitsSpec`] copy (which would type-check via the
13890 // `Clone` impl but silently break every downstream caller that
13891 // relied on the reference sharing the composite's backing
13892 // identity), a reference to an operator-resolved overlay (the
13893 // future per-cluster `:limits-overrides` slot — its resolution
13894 // must land at exactly this accessor body, not silently divert
13895 // the raw slot away from a second consumer), a
13896 // `None` → `Some(LimitsSpec::default)` cluster-default
13897 // projection (which would collapse the load-bearing
13898 // "author-omitted `:limits` ⇒ engine-default applies" partition
13899 // the peer [`crate::render::servico_m2_overlay`] emitter and
13900 // the peer [`Caixa::declared_servico_slots`] enumerator both
13901 // read), or an axis-shuffled projection (a future detour that
13902 // swapped `memory` and `fuel` through the accessor would
13903 // silently split the paired [`crate::StandardLayout::verify`]
13904 // per-`:limits` shape gate's traversal input from the peer
13905 // `servico_m2_overlay` emitter's projection input).
13906 //
13907 // First outer top-level [`Caixa`] `Option<&Composite>`-return
13908 // composite-reference accessor pin on the substrate primitive
13909 // — opens the outer-`Caixa` `Option<&Composite>` composite-
13910 // reference projection pattern the sibling `:behavior`
13911 // [`crate::BehaviorSpec`] / `:politicas`
13912 // [`crate::aplicacao::MeshPolicy`] / `:placement`
13913 // [`crate::aplicacao::Placement`] / `:entrada`
13914 // [`crate::aplicacao::Entrada`] future outer-composite lifts
13915 // fold on. Peer of the closed M3 outer-composite family the
13916 // sibling [`crate::AplicacaoSpec::politicas`] (534dc21) /
13917 // [`crate::AplicacaoSpec::placement`] (9abb8f0) /
13918 // [`crate::AplicacaoSpec::entrada`] (d32111c) composite-
13919 // reference accessor pins already carry on the outer
13920 // [`crate::AplicacaoSpec`] altitude — extends the outer-
13921 // accessor byte-equal-projection discipline onto the outer
13922 // top-level [`Caixa`] M2 Servico-runtime slot altitude.
13923 use crate::LimitsSpec;
13924 use std::time::Duration;
13925 let fixtures: Vec<Option<LimitsSpec>> = vec![
13926 None,
13927 Some(LimitsSpec::default()),
13928 Some(LimitsSpec {
13929 memory: Some(64 * 1024 * 1024),
13930 ..Default::default()
13931 }),
13932 Some(LimitsSpec {
13933 memory: Some(64 * 1024 * 1024),
13934 fuel: Some(1_000_000),
13935 wall_clock: Some(Duration::from_secs(30)),
13936 cpu: Some(500),
13937 }),
13938 ];
13939 for limits in fixtures {
13940 let c = caixa_with_limits(limits.clone());
13941 assert_eq!(
13942 c.limits(),
13943 limits.as_ref(),
13944 "Caixa::limits must return :limits verbatim (got {:?}, \
13945 expected {:?})",
13946 c.limits(),
13947 limits.as_ref(),
13948 );
13949 match (c.limits(), c.limits.as_ref()) {
13950 (Some(a), Some(b)) => assert!(
13951 std::ptr::eq(a, b),
13952 "Caixa::limits accessor and self.limits.as_ref() \
13953 field access must borrow the same backing storage \
13954 — the accessor is the substrate-primitive typed \
13955 dispatch every downstream Servico-M2-overlay \
13956 composite consumer must route through, and a \
13957 reference-identity split would silently break \
13958 every consumer that relied on the borrow sharing \
13959 the composite's storage",
13960 ),
13961 (None, None) => {}
13962 _ => panic!(
13963 "Caixa::limits presence bit must byte-equal \
13964 self.limits.is_some() — a presence-bit drift would \
13965 silently split the paired StandardLayout::verify \
13966 per-`:limits` shape gate's traversal head from \
13967 the peer render::servico_m2_overlay M2 overlay \
13968 emitter's traversal head from the peer \
13969 Caixa::declared_servico_slots M2 declared-slot \
13970 enumerator's presence probe",
13971 ),
13972 }
13973 assert_eq!(
13974 c.limits().is_some(),
13975 c.limits.is_some(),
13976 "Caixa::limits().is_some() must byte-equal \
13977 self.limits.is_some() — a presence-bit drift would \
13978 silently split every downstream Option<&LimitsSpec> \
13979 consumer's partition on the engine-default arm",
13980 );
13981 }
13982 }
13983
13984 #[test]
13985 fn declared_servico_slots_limits_arm_routes_through_accessor() {
13986 // Composition pin: [`Caixa::declared_servico_slots`]'s
13987 // `:limits` presence-probe arm must key off [`Caixa::limits`],
13988 // not the raw `self.limits.is_some()` field-probe. Structurally:
13989 // a `Caixa { limits: Some(LimitsSpec::default()), .. }` must
13990 // still push `M2_AUTHOR_KEY_LIMITS` onto the declared-slot list
13991 // (the presence bit is `Some`, so the M2 kind-coherence gate
13992 // must surface the slot as "declared" even when every per-axis
13993 // cap is unset), and a `Caixa { limits: None, .. }` must NOT
13994 // push the label (the "author omitted the slot entirely"
13995 // partition). The pair jointly pins the accessor + declared-
13996 // slot enumerator composition: any future silent detour that
13997 // had the accessor collapse `Some(LimitsSpec::default())` to
13998 // `None` (a `.filter(|l| !l.is_empty())` projection) would
13999 // silently absorb the "declared but empty" arm at the
14000 // accessor boundary and the [`crate::LayoutError::ServicoSlotsOnNonServico`]
14001 // kind-coherence gate would silently accept a
14002 // struct-literal `Caixa` carrying the drift.
14003 //
14004 // Peer of the sibling per-`Caixa`
14005 // `validate_deps_duplicate_arm_routes_through_accessor` (ad34b4e)
14006 // and `validate_deps_duplicate_deps_dev_arm_routes_through_accessor`
14007 // (f7fd81e) accessor-composition pins on the sibling `:deps` /
14008 // `:deps-dev` outer-`&[Dep]`-composition axes — same "the
14009 // enumerator gate must route through the substrate-primitive
14010 // typed dispatch" discipline extended onto the outer top-level
14011 // [`Caixa`] `Option<&LimitsSpec>`-composition surface, opening
14012 // the outer-`Caixa` M2 Servico-runtime-slot arm of the
14013 // composition-pin family.
14014 use crate::LimitsSpec;
14015 let c = caixa_with_limits(Some(LimitsSpec::default()));
14016 let slots = c.declared_servico_slots();
14017 assert!(
14018 slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
14019 "declared_servico_slots must push M2_AUTHOR_KEY_LIMITS \
14020 when `:limits` is Some (even for LimitsSpec::default()) \
14021 — the accessor and the enumerator gate must route through \
14022 the same substrate-primitive typed dispatch on the outer \
14023 :limits presence bit (got slots={slots:?})",
14024 );
14025 let c = caixa_with_limits(None);
14026 let slots = c.declared_servico_slots();
14027 assert!(
14028 !slots.contains(&crate::render::M2_AUTHOR_KEY_LIMITS),
14029 "declared_servico_slots must NOT push M2_AUTHOR_KEY_LIMITS \
14030 when `:limits` is None — the author-omitted arm must \
14031 route through the accessor's None-return unchanged (got \
14032 slots={slots:?})",
14033 );
14034 }
14035
14036 #[test]
14037 fn servico_m2_overlay_limits_arm_routes_through_accessor() {
14038 // Composition pin: [`crate::render::servico_m2_overlay`]'s
14039 // per-`:limits` M2 overlay emit arm must key off
14040 // [`Caixa::limits`], not the raw `&caixa.limits` field-borrow.
14041 // Structurally: a `Caixa { limits: Some(LimitsSpec { memory:
14042 // Some(64 MiB), .. default }), .. }` must surface the
14043 // `M2_KEY_LIMITS` key with the per-axis
14044 // `memory: "64MiB"` sub-mapping in the overlay, a `Caixa {
14045 // limits: Some(LimitsSpec::default()), .. }` must omit the
14046 // key entirely (the `.is_empty()`-gated inner arm elides an
14047 // empty composite even when the outer presence bit is `Some`),
14048 // and a `Caixa { limits: None, .. }` must also omit the key
14049 // (the "author omitted the slot entirely" partition). The
14050 // three-fixture family jointly pins the accessor + M2 overlay
14051 // emitter composition: any future silent detour that had the
14052 // accessor return a fresh-cloned copy on the `Some` arm (a
14053 // `LimitsSpec::clone()` projection) would silently break the
14054 // reference-identity pin the peer per-axis
14055 // `serde_yaml::to_value(limits)` projection reads from.
14056 use crate::LimitsSpec;
14057 use crate::render::{M2_KEY_LIMITS, servico_m2_overlay};
14058 let c = caixa_with_limits(Some(LimitsSpec {
14059 memory: Some(64 * 1024 * 1024),
14060 ..Default::default()
14061 }));
14062 let overlay = servico_m2_overlay(&c).unwrap();
14063 assert!(
14064 overlay.contains_key(M2_KEY_LIMITS),
14065 "servico_m2_overlay must surface M2_KEY_LIMITS when \
14066 `:limits` carries a non-empty composite — the accessor \
14067 and the M2 overlay emitter must route through the same \
14068 substrate-primitive typed dispatch on the outer :limits \
14069 composite (got overlay={overlay:?})",
14070 );
14071 let c = caixa_with_limits(Some(LimitsSpec::default()));
14072 let overlay = servico_m2_overlay(&c).unwrap();
14073 assert!(
14074 !overlay.contains_key(M2_KEY_LIMITS),
14075 "servico_m2_overlay must omit M2_KEY_LIMITS when \
14076 `:limits` is Some(LimitsSpec::default()) — the empty \
14077 composite's `.is_empty()`-gated inner arm must elide \
14078 the key regardless of the outer presence bit (got \
14079 overlay={overlay:?})",
14080 );
14081 let c = caixa_with_limits(None);
14082 let overlay = servico_m2_overlay(&c).unwrap();
14083 assert!(
14084 !overlay.contains_key(M2_KEY_LIMITS),
14085 "servico_m2_overlay must omit M2_KEY_LIMITS when \
14086 `:limits` is None — the author-omitted arm must route \
14087 through the accessor's None-return unchanged (got \
14088 overlay={overlay:?})",
14089 );
14090 }
14091
14092 #[test]
14093 fn limits_projects_option_ref_by_borrow() {
14094 // The by-borrow pin: [`Caixa::limits`] returns
14095 // `Option<&LimitsSpec>` by borrow — the returned reference
14096 // borrows the underlying `Option<LimitsSpec>` storage of the
14097 // `:limits` slot and the accessor must not clone the backing
14098 // composite on every call. Peer of the sibling
14099 // `deps_projects_slice_by_borrow` (ad34b4e) /
14100 // `deps_dev_projects_slice_by_borrow` (f7fd81e) by-borrow pins
14101 // on the outer top-level [`Caixa`] `&[Dep]`-return axes —
14102 // extended here to the outer [`Caixa`] `Option<&Composite>`-
14103 // return axis: the accessor's returned reference must borrow
14104 // from `&self` (the returned reference's lifetime is tied to
14105 // `&self`), and calling the accessor twice on the same
14106 // [`Caixa`] must yield references that are pointer-equal (the
14107 // underlying byte-buffer is the storage `LimitsSpec`'s
14108 // allocation, not a fresh copy) as well as value-equal
14109 // (idempotent, no side effects on `&self`).
14110 //
14111 // Pins against a future silent detour that returned an owned
14112 // `LimitsSpec` (which would type-check via the `Clone` impl
14113 // but silently clone on every call), a `&LimitsSpec` panic-
14114 // return on the `None` arm (which would collapse the load-
14115 // bearing `Option` presence-bit into a runtime panic), or a
14116 // one-arm-only accessor that returned a saturating composite
14117 // on some sentinel input.
14118 use crate::LimitsSpec;
14119 use std::time::Duration;
14120 for limits in [
14121 Some(LimitsSpec::default()),
14122 Some(LimitsSpec {
14123 memory: Some(64 * 1024 * 1024),
14124 fuel: Some(1_000_000),
14125 wall_clock: Some(Duration::from_secs(30)),
14126 cpu: Some(500),
14127 }),
14128 ] {
14129 let c = caixa_with_limits(limits.clone());
14130 let first = c.limits().unwrap();
14131 let second = c.limits().unwrap();
14132 assert_eq!(
14133 first, second,
14134 "Caixa::limits must be idempotent — two successive \
14135 calls on the same &self must return the same \
14136 &LimitsSpec",
14137 );
14138 assert!(
14139 std::ptr::eq(first, second),
14140 "Caixa::limits must borrow the underlying \
14141 Option<LimitsSpec> storage — two successive calls \
14142 must return references with the same backing pointer \
14143 (a fresh LimitsSpec clone would change the pointer \
14144 on every call)",
14145 );
14146 assert_eq!(
14147 Some(first),
14148 limits.as_ref(),
14149 "Caixa::limits must return :limits verbatim by borrow \
14150 — got {first:?}, expected {:?}",
14151 limits.as_ref(),
14152 );
14153 }
14154 let c = caixa_with_limits(None);
14155 assert!(
14156 c.limits().is_none(),
14157 "Caixa::limits must return None when :limits is absent — \
14158 the author-omitted arm must project through the \
14159 accessor's Option::None unchanged",
14160 );
14161 }
14162
14163 // ── Caixa::behavior — outer top-level Option<&BehaviorSpec> composite-reference accessor ──
14164
14165 fn caixa_with_behavior(behavior: Option<crate::BehaviorSpec>) -> Caixa {
14166 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14167 c.behavior = behavior;
14168 c
14169 }
14170
14171 #[test]
14172 fn behavior_returns_behavior_option_ref_verbatim_across_permutations() {
14173 // The canonical per-`Caixa` `:behavior` M2 typed-slot outer-
14174 // composite optional-composite-reference-shape pin:
14175 // [`Caixa::behavior`] must return the `:behavior` typed
14176 // `Option<BehaviorSpec>` verbatim as an `Option<&BehaviorSpec>`
14177 // reference over the same backing storage the raw
14178 // `self.behavior.as_ref()` field access borrows from, byte-equal
14179 // across every representative fixture in the accept-set — the
14180 // author-omitted `None` shape (the "runtime-default applies"
14181 // partition every downstream Servico M2 overlay emitter treats
14182 // as "emit nothing"), the empty-composite `Some(BehaviorSpec {
14183 // .. default })` shape ([`BehaviorSpec::is_empty`] holds —
14184 // every per-callback path is `None`, so the peer M2 overlay
14185 // emitter's `.is_empty()`-gated projection still emits nothing
14186 // but the outer presence-bit is `Some`, so
14187 // [`Caixa::declared_servico_slots`] still pushes the
14188 // `M2_AUTHOR_KEY_BEHAVIOR` label), a single-callback fixture
14189 // (only `:on-state-change` set — the canonical shape a caixa
14190 // that only wires the hot-upgrade migration path carries), and
14191 // a fully-populated composite (every per-callback path set —
14192 // the canonical shape a fully-instrumented gen_server-shaped
14193 // Servico carries).
14194 //
14195 // Peer of the sibling
14196 // `limits_returns_limits_option_ref_verbatim_across_permutations`
14197 // (b2bd9d7) opening fixture-family + reference-identity +
14198 // presence-bit tetrad pin on the outer top-level [`Caixa`]
14199 // `Option<&Composite>`-return sub-family — extended here to the
14200 // second axis of that sub-family so both of the currently-lifted
14201 // M2 Servico-runtime `Option<&Composite>` slots (`:limits` /
14202 // `:behavior`) carry the same "byte-equal, borrow-shared,
14203 // presence-bit-preserved" outer-accessor discipline.
14204 //
14205 // Pins against a future silent detour that returned a fresh-
14206 // cloned [`crate::BehaviorSpec`] copy (which would type-check
14207 // via the `Clone` impl but silently break every downstream
14208 // caller that relied on the reference sharing the composite's
14209 // backing identity), a reference to an operator-resolved
14210 // overlay (a future per-cluster `:behavior-overrides` slot —
14211 // its resolution must land at exactly this accessor body, not
14212 // silently divert the raw slot away from a second consumer), a
14213 // `None` → `Some(BehaviorSpec::default)` cluster-default
14214 // projection (which would collapse the load-bearing
14215 // "author-omitted `:behavior` ⇒ runtime-default applies"
14216 // partition the peer [`crate::render::servico_m2_overlay`]
14217 // emitter, the peer [`Caixa::declared_servico_slots`]
14218 // enumerator, and the cross-slot
14219 // [`crate::upgrade::validate_upgrade_from_against_behavior`]
14220 // gate all read), or a callback-shuffled projection (a future
14221 // detour that swapped `on_init` and `on_terminate` through the
14222 // accessor would silently split the paired
14223 // [`crate::StandardLayout::verify`] per-`:behavior` shape gate's
14224 // traversal input from the peer `servico_m2_overlay` emitter's
14225 // projection input from the cross-slot `:state-change`
14226 // composition gate's traversal input).
14227 use crate::BehaviorSpec;
14228 use std::path::PathBuf;
14229 let fixtures: Vec<Option<BehaviorSpec>> = vec![
14230 None,
14231 Some(BehaviorSpec::default()),
14232 Some(BehaviorSpec {
14233 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
14234 ..Default::default()
14235 }),
14236 Some(BehaviorSpec {
14237 on_init: Some(PathBuf::from("lib/init.lisp")),
14238 on_call: Some(PathBuf::from("lib/handlers.lisp")),
14239 on_cast: Some(PathBuf::from("lib/handlers.lisp")),
14240 on_info: Some(PathBuf::from("lib/handlers.lisp")),
14241 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
14242 on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
14243 }),
14244 ];
14245 for behavior in fixtures {
14246 let c = caixa_with_behavior(behavior.clone());
14247 assert_eq!(
14248 c.behavior(),
14249 behavior.as_ref(),
14250 "Caixa::behavior must return :behavior verbatim (got \
14251 {:?}, expected {:?})",
14252 c.behavior(),
14253 behavior.as_ref(),
14254 );
14255 match (c.behavior(), c.behavior.as_ref()) {
14256 (Some(a), Some(b)) => assert!(
14257 std::ptr::eq(a, b),
14258 "Caixa::behavior accessor and self.behavior.as_ref() \
14259 field access must borrow the same backing storage \
14260 — the accessor is the substrate-primitive typed \
14261 dispatch every downstream Servico-M2-overlay \
14262 composite consumer must route through, and a \
14263 reference-identity split would silently break \
14264 every consumer that relied on the borrow sharing \
14265 the composite's storage",
14266 ),
14267 (None, None) => {}
14268 _ => panic!(
14269 "Caixa::behavior presence bit must byte-equal \
14270 self.behavior.is_some() — a presence-bit drift \
14271 would silently split the paired \
14272 StandardLayout::verify per-`:behavior` shape \
14273 gate's traversal head from the peer \
14274 render::servico_m2_overlay M2 overlay emitter's \
14275 traversal head from the cross-slot \
14276 validate_upgrade_from_against_behavior \
14277 composition gate's traversal head from the peer \
14278 Caixa::declared_servico_slots M2 declared-slot \
14279 enumerator's presence probe",
14280 ),
14281 }
14282 assert_eq!(
14283 c.behavior().is_some(),
14284 c.behavior.is_some(),
14285 "Caixa::behavior().is_some() must byte-equal \
14286 self.behavior.is_some() — a presence-bit drift would \
14287 silently split every downstream Option<&BehaviorSpec> \
14288 consumer's partition on the runtime-default arm",
14289 );
14290 }
14291 }
14292
14293 #[test]
14294 fn declared_servico_slots_behavior_arm_routes_through_accessor() {
14295 // Composition pin: [`Caixa::declared_servico_slots`]'s
14296 // `:behavior` presence-probe arm must key off
14297 // [`Caixa::behavior`], not the raw `self.behavior.is_some()`
14298 // field-probe. Structurally: a `Caixa { behavior:
14299 // Some(BehaviorSpec::default()), .. }` must still push
14300 // `M2_AUTHOR_KEY_BEHAVIOR` onto the declared-slot list (the
14301 // presence bit is `Some`, so the M2 kind-coherence gate must
14302 // surface the slot as "declared" even when every per-callback
14303 // path is unset), and a `Caixa { behavior: None, .. }` must
14304 // NOT push the label (the "author omitted the slot entirely"
14305 // partition). The pair jointly pins the accessor + declared-
14306 // slot enumerator composition: any future silent detour that
14307 // had the accessor collapse `Some(BehaviorSpec::default())`
14308 // to `None` (a `.filter(|b| !b.is_empty())` projection) would
14309 // silently absorb the "declared but empty" arm at the
14310 // accessor boundary and the
14311 // [`crate::LayoutError::ServicoSlotsOnNonServico`]
14312 // kind-coherence gate would silently accept a struct-literal
14313 // `Caixa` carrying the drift.
14314 //
14315 // Peer of the sibling
14316 // `declared_servico_slots_limits_arm_routes_through_accessor`
14317 // (b2bd9d7) composition pin on the sibling `:limits` outer-
14318 // `Option<&LimitsSpec>` arm of the same
14319 // [`Caixa::declared_servico_slots`] M2 declared-slot
14320 // enumerator's traversal — same "the enumerator gate must
14321 // route through the substrate-primitive typed dispatch"
14322 // discipline extended onto the outer top-level [`Caixa`]
14323 // `Option<&BehaviorSpec>`-composition surface.
14324 use crate::BehaviorSpec;
14325 let c = caixa_with_behavior(Some(BehaviorSpec::default()));
14326 let slots = c.declared_servico_slots();
14327 assert!(
14328 slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
14329 "declared_servico_slots must push M2_AUTHOR_KEY_BEHAVIOR \
14330 when `:behavior` is Some (even for BehaviorSpec::default()) \
14331 — the accessor and the enumerator gate must route through \
14332 the same substrate-primitive typed dispatch on the outer \
14333 :behavior presence bit (got slots={slots:?})",
14334 );
14335 let c = caixa_with_behavior(None);
14336 let slots = c.declared_servico_slots();
14337 assert!(
14338 !slots.contains(&crate::render::M2_AUTHOR_KEY_BEHAVIOR),
14339 "declared_servico_slots must NOT push M2_AUTHOR_KEY_BEHAVIOR \
14340 when `:behavior` is None — the author-omitted arm must \
14341 route through the accessor's None-return unchanged (got \
14342 slots={slots:?})",
14343 );
14344 }
14345
14346 #[test]
14347 fn servico_m2_overlay_behavior_arm_routes_through_accessor() {
14348 // Composition pin: [`crate::render::servico_m2_overlay`]'s
14349 // per-`:behavior` M2 overlay emit arm must key off
14350 // [`Caixa::behavior`], not the raw `&caixa.behavior`
14351 // field-borrow. Structurally: a `Caixa { behavior:
14352 // Some(BehaviorSpec { on_state_change: Some(...), .. default
14353 // }), .. }` must surface the `M2_KEY_BEHAVIOR` key with the
14354 // per-callback `onStateChange` sub-mapping in the overlay, a
14355 // `Caixa { behavior: Some(BehaviorSpec::default()), .. }`
14356 // must omit the key entirely (the `.is_empty()`-gated inner
14357 // arm elides an empty composite even when the outer presence
14358 // bit is `Some`), and a `Caixa { behavior: None, .. }` must
14359 // also omit the key (the "author omitted the slot entirely"
14360 // partition). The three-fixture family jointly pins the
14361 // accessor + M2 overlay emitter composition: any future
14362 // silent detour that had the accessor return a fresh-cloned
14363 // copy on the `Some` arm (a `BehaviorSpec::clone()`
14364 // projection) would silently break the reference-identity
14365 // pin the peer per-callback `serde_yaml::to_value(behavior)`
14366 // projection reads from.
14367 //
14368 // Peer of the sibling
14369 // `servico_m2_overlay_limits_arm_routes_through_accessor`
14370 // (b2bd9d7) composition pin on the sibling `:limits` outer-
14371 // `Option<&LimitsSpec>` arm of the same
14372 // [`crate::render::servico_m2_overlay`] M2 overlay emitter's
14373 // traversal — same "the emitter must route through the
14374 // substrate-primitive typed dispatch on the outer composite"
14375 // discipline extended onto the outer top-level [`Caixa`]
14376 // `Option<&BehaviorSpec>`-composition surface.
14377 use crate::BehaviorSpec;
14378 use crate::render::{M2_KEY_BEHAVIOR, servico_m2_overlay};
14379 use std::path::PathBuf;
14380 let c = caixa_with_behavior(Some(BehaviorSpec {
14381 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
14382 ..Default::default()
14383 }));
14384 let overlay = servico_m2_overlay(&c).unwrap();
14385 assert!(
14386 overlay.contains_key(M2_KEY_BEHAVIOR),
14387 "servico_m2_overlay must surface M2_KEY_BEHAVIOR when \
14388 `:behavior` carries a non-empty composite — the accessor \
14389 and the M2 overlay emitter must route through the same \
14390 substrate-primitive typed dispatch on the outer :behavior \
14391 composite (got overlay={overlay:?})",
14392 );
14393 let c = caixa_with_behavior(Some(BehaviorSpec::default()));
14394 let overlay = servico_m2_overlay(&c).unwrap();
14395 assert!(
14396 !overlay.contains_key(M2_KEY_BEHAVIOR),
14397 "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
14398 `:behavior` is Some(BehaviorSpec::default()) — the empty \
14399 composite's `.is_empty()`-gated inner arm must elide the \
14400 key regardless of the outer presence bit (got \
14401 overlay={overlay:?})",
14402 );
14403 let c = caixa_with_behavior(None);
14404 let overlay = servico_m2_overlay(&c).unwrap();
14405 assert!(
14406 !overlay.contains_key(M2_KEY_BEHAVIOR),
14407 "servico_m2_overlay must omit M2_KEY_BEHAVIOR when \
14408 `:behavior` is None — the author-omitted arm must route \
14409 through the accessor's None-return unchanged (got \
14410 overlay={overlay:?})",
14411 );
14412 }
14413
14414 #[test]
14415 fn behavior_projects_option_ref_by_borrow() {
14416 // The by-borrow pin: [`Caixa::behavior`] returns
14417 // `Option<&BehaviorSpec>` by borrow — the returned reference
14418 // borrows the underlying `Option<BehaviorSpec>` storage of the
14419 // `:behavior` slot and the accessor must not clone the backing
14420 // composite on every call. Peer of the sibling
14421 // `limits_projects_option_ref_by_borrow` (b2bd9d7) by-borrow
14422 // pin on the outer top-level [`Caixa`] `Option<&Composite>`-
14423 // return sub-family — extended here to the second axis of the
14424 // same sub-family: the accessor's returned reference must
14425 // borrow from `&self` (the returned reference's lifetime is
14426 // tied to `&self`), and calling the accessor twice on the same
14427 // [`Caixa`] must yield references that are pointer-equal (the
14428 // underlying byte-buffer is the storage `BehaviorSpec`'s
14429 // allocation, not a fresh copy) as well as value-equal
14430 // (idempotent, no side effects on `&self`).
14431 //
14432 // Pins against a future silent detour that returned an owned
14433 // `BehaviorSpec` (which would type-check via the `Clone` impl
14434 // but silently clone on every call), a `&BehaviorSpec` panic-
14435 // return on the `None` arm (which would collapse the load-
14436 // bearing `Option` presence-bit into a runtime panic), or a
14437 // one-arm-only accessor that returned a saturating composite
14438 // on some sentinel input.
14439 use crate::BehaviorSpec;
14440 use std::path::PathBuf;
14441 for behavior in [
14442 Some(BehaviorSpec::default()),
14443 Some(BehaviorSpec {
14444 on_init: Some(PathBuf::from("lib/init.lisp")),
14445 on_call: Some(PathBuf::from("lib/handlers.lisp")),
14446 on_cast: Some(PathBuf::from("lib/handlers.lisp")),
14447 on_info: Some(PathBuf::from("lib/handlers.lisp")),
14448 on_state_change: Some(PathBuf::from("lib/migrations.lisp")),
14449 on_terminate: Some(PathBuf::from("lib/cleanup.lisp")),
14450 }),
14451 ] {
14452 let c = caixa_with_behavior(behavior.clone());
14453 let first = c.behavior().unwrap();
14454 let second = c.behavior().unwrap();
14455 assert_eq!(
14456 first, second,
14457 "Caixa::behavior must be idempotent — two successive \
14458 calls on the same &self must return the same \
14459 &BehaviorSpec",
14460 );
14461 assert!(
14462 std::ptr::eq(first, second),
14463 "Caixa::behavior must borrow the underlying \
14464 Option<BehaviorSpec> storage — two successive calls \
14465 must return references with the same backing pointer \
14466 (a fresh BehaviorSpec clone would change the pointer \
14467 on every call)",
14468 );
14469 assert_eq!(
14470 Some(first),
14471 behavior.as_ref(),
14472 "Caixa::behavior must return :behavior verbatim by \
14473 borrow — got {first:?}, expected {:?}",
14474 behavior.as_ref(),
14475 );
14476 }
14477 let c = caixa_with_behavior(None);
14478 assert!(
14479 c.behavior().is_none(),
14480 "Caixa::behavior must return None when :behavior is absent \
14481 — the author-omitted arm must project through the \
14482 accessor's Option::None unchanged",
14483 );
14484 }
14485
14486 // ── Caixa::politicas — outer top-level Option<&MeshPolicy> composite-reference accessor ──
14487
14488 fn caixa_aplicacao_with_politicas(politicas: Option<crate::aplicacao::MeshPolicy>) -> Caixa {
14489 use crate::aplicacao::{Membro, WitContract};
14490 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14491 c.kind = CaixaKind::Aplicacao;
14492 c.membros = vec![Membro {
14493 caixa: "a".into(),
14494 versao: "^0.1".into(),
14495 }];
14496 c.contratos = vec![WitContract {
14497 de: "a".into(),
14498 para: "a".into(),
14499 wit: "wasi:http/proxy".into(),
14500 endpoint: Some("/x".into()),
14501 subject: None,
14502 slot: None,
14503 }];
14504 c.politicas = politicas;
14505 c
14506 }
14507
14508 #[test]
14509 fn politicas_returns_politicas_option_ref_verbatim_across_permutations() {
14510 // The canonical per-`Caixa` `:politicas` M3 mesh-slot outer-
14511 // composite optional-composite-reference-shape pin:
14512 // [`Caixa::politicas`] must return the `:politicas` typed
14513 // `Option<MeshPolicy>` verbatim as an `Option<&MeshPolicy>`
14514 // reference over the same backing storage the raw
14515 // `self.politicas.as_ref()` field access borrows from,
14516 // byte-equal across every representative fixture in the
14517 // accept-set — the author-omitted `None` shape (the "cluster-
14518 // default applies" partition every downstream mesh-artifact
14519 // emitter treats as "emit no `:politicas` overlay"), the
14520 // empty-composite `Some(MeshPolicy { .. default })` shape
14521 // ([`crate::aplicacao::MeshPolicy::is_empty`] holds — every
14522 // per-axis mesh-policy scalar is `None`, so the peer inner
14523 // [`crate::AplicacaoSpec::politicas`] `.is_empty()`-gated
14524 // caixa-mesh overlay elides every per-axis emit but the outer
14525 // presence-bit is `Some`, so [`Caixa::declared_mesh_slots`]
14526 // still pushes the `M3_AUTHOR_KEY_POLITICAS` label), a
14527 // single-axis fixture (only `:timeout` set — the canonical
14528 // shape a latency-sensitive Aplicacao carries), and a
14529 // fully-populated composite (every per-axis mesh-policy
14530 // scalar set — the canonical shape a fully-governed
14531 // Aplicacao carries).
14532 //
14533 // Pins against a future silent detour that returned a fresh-
14534 // cloned [`crate::aplicacao::MeshPolicy`] copy (which would
14535 // type-check via the `Clone` impl but silently break every
14536 // downstream caller that relied on the reference sharing the
14537 // composite's backing identity), a reference to an operator-
14538 // resolved overlay (the future per-cluster
14539 // `:politicas-overrides` slot — its resolution must land at
14540 // exactly this accessor body, not silently divert the raw
14541 // slot away from the peer [`Caixa::declared_mesh_slots`]
14542 // enumerator's presence probe), a
14543 // `None` → `Some(MeshPolicy::default)` cluster-default
14544 // projection (which would collapse the load-bearing
14545 // "author-omitted `:politicas` ⇒ cluster-default applies"
14546 // partition the peer [`Caixa::declared_mesh_slots`]
14547 // enumerator and the peer [`Caixa::aplicacao_view`]
14548 // Aplicacao-composition seed both read), or an axis-shuffled
14549 // projection (a future detour that swapped `timeout` and
14550 // `retries` through the accessor would silently split the
14551 // paired [`Caixa::aplicacao_view`] seed's fold input from the
14552 // sibling M3 mesh-artifact emitter's projection input).
14553 //
14554 // Third outer top-level [`Caixa`] `Option<&Composite>`-return
14555 // composite-reference accessor pin on the substrate primitive
14556 // — peer of the sibling
14557 // `limits_returns_limits_option_ref_verbatim_across_permutations`
14558 // (b2bd9d7) and
14559 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
14560 // (35d8b52) opening tetrad pins on the outer top-level
14561 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
14562 // here to the first of the three M3 mesh-slot axes so the
14563 // opening third of the outer `Option<&Composite>` sub-family
14564 // carries the same "byte-equal, borrow-shared, presence-bit-
14565 // preserved" outer-accessor discipline.
14566 use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
14567 use std::time::Duration;
14568 let fixtures: Vec<Option<MeshPolicy>> = vec![
14569 None,
14570 Some(MeshPolicy::default()),
14571 Some(MeshPolicy {
14572 timeout: Some(Duration::from_secs(30)),
14573 ..Default::default()
14574 }),
14575 Some(MeshPolicy {
14576 timeout: Some(Duration::from_secs(30)),
14577 retries: Some(3),
14578 circuit_breaker: Some(CircuitBreaker {
14579 max_failures: 5,
14580 window: Duration::from_secs(60),
14581 }),
14582 mtls_required: Some(true),
14583 rate_limit: Some(RateLimit {
14584 rate: 100,
14585 window: Duration::from_secs(1),
14586 }),
14587 }),
14588 ];
14589 for politicas in fixtures {
14590 let c = caixa_aplicacao_with_politicas(politicas.clone());
14591 assert_eq!(
14592 c.politicas(),
14593 politicas.as_ref(),
14594 "Caixa::politicas must return :politicas verbatim (got \
14595 {:?}, expected {:?})",
14596 c.politicas(),
14597 politicas.as_ref(),
14598 );
14599 match (c.politicas(), c.politicas.as_ref()) {
14600 (Some(a), Some(b)) => assert!(
14601 std::ptr::eq(a, b),
14602 "Caixa::politicas accessor and self.politicas.as_ref() \
14603 field access must borrow the same backing storage \
14604 — the accessor is the substrate-primitive typed \
14605 dispatch every downstream Aplicacao-mesh-overlay \
14606 composite consumer must route through, and a \
14607 reference-identity split would silently break \
14608 every consumer that relied on the borrow sharing \
14609 the composite's storage",
14610 ),
14611 (None, None) => {}
14612 _ => panic!(
14613 "Caixa::politicas presence bit must byte-equal \
14614 self.politicas.is_some() — a presence-bit drift \
14615 would silently split the paired \
14616 Caixa::aplicacao_view Aplicacao-composition seed's \
14617 traversal head from the peer \
14618 Caixa::declared_mesh_slots M3 declared-slot \
14619 enumerator's presence probe",
14620 ),
14621 }
14622 assert_eq!(
14623 c.politicas().is_some(),
14624 c.politicas.is_some(),
14625 "Caixa::politicas().is_some() must byte-equal \
14626 self.politicas.is_some() — a presence-bit drift would \
14627 silently split every downstream Option<&MeshPolicy> \
14628 consumer's partition on the cluster-default arm",
14629 );
14630 }
14631 }
14632
14633 #[test]
14634 fn declared_mesh_slots_politicas_arm_routes_through_accessor() {
14635 // Composition pin: [`Caixa::declared_mesh_slots`]'s
14636 // `:politicas` presence-probe arm must key off
14637 // [`Caixa::politicas`], not the raw `self.politicas.is_some()`
14638 // field-probe. Structurally: a `Caixa { politicas:
14639 // Some(MeshPolicy::default()), .. }` must still push
14640 // `M3_AUTHOR_KEY_POLITICAS` onto the declared-slot list (the
14641 // presence bit is `Some`, so the M3 kind-coherence gate must
14642 // surface the slot as "declared" even when every per-axis
14643 // scalar is unset), and a `Caixa { politicas: None, .. }` must
14644 // NOT push the label (the "author omitted the slot entirely"
14645 // partition). The pair jointly pins the accessor + declared-
14646 // slot enumerator composition: any future silent detour that
14647 // had the accessor collapse `Some(MeshPolicy::default())` to
14648 // `None` (a `.filter(|p| !p.is_empty())` projection) would
14649 // silently absorb the "declared but empty" arm at the
14650 // accessor boundary and the
14651 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
14652 // coherence gate would silently accept a struct-literal
14653 // `Caixa` carrying the drift.
14654 //
14655 // Peer of the sibling
14656 // `declared_servico_slots_limits_arm_routes_through_accessor`
14657 // (b2bd9d7) and
14658 // `declared_servico_slots_behavior_arm_routes_through_accessor`
14659 // (35d8b52) composition pins on the sibling `:limits` /
14660 // `:behavior` outer-`Option<&Composite>` arms of the peer
14661 // [`Caixa::declared_servico_slots`] M2 declared-slot
14662 // enumerator's traversal — same "the enumerator gate must
14663 // route through the substrate-primitive typed dispatch"
14664 // discipline extended onto the outer top-level [`Caixa`] M3
14665 // mesh-slot family so the [`Caixa::declared_mesh_slots`]
14666 // enumerator carries the same routing invariant as its M2
14667 // sibling.
14668 use crate::aplicacao::MeshPolicy;
14669 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
14670 let slots = c.declared_mesh_slots();
14671 assert!(
14672 slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
14673 "declared_mesh_slots must push M3_AUTHOR_KEY_POLITICAS \
14674 when `:politicas` is Some (even for MeshPolicy::default()) \
14675 — the accessor and the enumerator gate must route through \
14676 the same substrate-primitive typed dispatch on the outer \
14677 :politicas presence bit (got slots={slots:?})",
14678 );
14679 let c = caixa_aplicacao_with_politicas(None);
14680 let slots = c.declared_mesh_slots();
14681 assert!(
14682 !slots.contains(&crate::render::M3_AUTHOR_KEY_POLITICAS),
14683 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_POLITICAS \
14684 when `:politicas` is None — the author-omitted arm must \
14685 route through the accessor's None-return unchanged (got \
14686 slots={slots:?})",
14687 );
14688 }
14689
14690 #[test]
14691 fn aplicacao_view_politicas_arm_folds_through_accessor() {
14692 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:politicas`
14693 // Aplicacao-composition seed must fold through
14694 // [`Caixa::politicas`], not the raw
14695 // `self.politicas.clone().unwrap_or_default()` field-borrow.
14696 // Structurally: a `Caixa { politicas: Some(MeshPolicy {
14697 // timeout: Some(30s), .. default }), kind: Aplicacao, .. }`
14698 // must surface a projected [`crate::AplicacaoSpec`] whose
14699 // `politicas().timeout()` field byte-equals the outer
14700 // composite's `timeout` scalar (the fold must project the
14701 // authored composite verbatim), a `Caixa { politicas:
14702 // Some(MeshPolicy::default()), kind: Aplicacao, .. }` must
14703 // surface an [`crate::AplicacaoSpec`] whose `politicas()`
14704 // byte-equals [`crate::aplicacao::MeshPolicy::default`] (the
14705 // fold's empty-composite arm collapses to the same default the
14706 // author-omitted arm does), and a `Caixa { politicas: None,
14707 // kind: Aplicacao, .. }` must surface an
14708 // [`crate::AplicacaoSpec`] whose `politicas()` byte-equals
14709 // [`crate::aplicacao::MeshPolicy::default`] (the "author
14710 // omitted the slot entirely" arm folds through the
14711 // `unwrap_or_default` onto the cluster-default). The triad
14712 // jointly pins the accessor + Aplicacao-composition seed
14713 // composition: any future silent detour that had the accessor
14714 // divert the raw slot away from the seed's fold (an operator-
14715 // resolved overlay's default-fold arm silently differing from
14716 // the raw slot's default-fold arm) would silently split the
14717 // build-time mesh-artifact emission gate from the caixa-mesh
14718 // renderer's Aplicacao-view input at the composition boundary.
14719 use crate::aplicacao::MeshPolicy;
14720 use std::time::Duration;
14721 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy {
14722 timeout: Some(Duration::from_secs(30)),
14723 ..Default::default()
14724 }));
14725 let view = c.aplicacao_view().unwrap();
14726 assert_eq!(
14727 view.politicas().timeout(),
14728 Some(Duration::from_secs(30)),
14729 "Caixa::aplicacao_view must fold the authored :politicas \
14730 :timeout scalar through the accessor verbatim onto the \
14731 projected AplicacaoSpec — a future silent detour at the \
14732 seed's fold arm would surface here as a projected-scalar \
14733 drift (got {:?})",
14734 view.politicas().timeout(),
14735 );
14736 let c = caixa_aplicacao_with_politicas(Some(MeshPolicy::default()));
14737 let view = c.aplicacao_view().unwrap();
14738 assert_eq!(
14739 view.politicas(),
14740 &MeshPolicy::default(),
14741 "Caixa::aplicacao_view must fold Some(MeshPolicy::default()) \
14742 through the accessor onto MeshPolicy::default — the empty- \
14743 composite arm collapses to the same default the author- \
14744 omitted arm does (got {:?})",
14745 view.politicas(),
14746 );
14747 let c = caixa_aplicacao_with_politicas(None);
14748 let view = c.aplicacao_view().unwrap();
14749 assert_eq!(
14750 view.politicas(),
14751 &MeshPolicy::default(),
14752 "Caixa::aplicacao_view must fold None through the accessor's \
14753 unwrap_or_default onto MeshPolicy::default — the author- \
14754 omitted arm must route through the accessor's None-return \
14755 unchanged (got {:?})",
14756 view.politicas(),
14757 );
14758 }
14759
14760 #[test]
14761 fn politicas_projects_option_ref_by_borrow() {
14762 // The by-borrow pin: [`Caixa::politicas`] returns
14763 // `Option<&MeshPolicy>` by borrow — the returned reference
14764 // borrows the underlying `Option<MeshPolicy>` storage of the
14765 // `:politicas` slot and the accessor must not clone the
14766 // backing composite on every call. Peer of the sibling
14767 // `limits_projects_option_ref_by_borrow` (b2bd9d7) and
14768 // `behavior_projects_option_ref_by_borrow` (35d8b52) by-borrow
14769 // pins on the outer top-level [`Caixa`]
14770 // `Option<&Composite>`-return sub-family — extended here to
14771 // the third axis of the same sub-family: the accessor's
14772 // returned reference must borrow from `&self` (the returned
14773 // reference's lifetime is tied to `&self`), and calling the
14774 // accessor twice on the same [`Caixa`] must yield references
14775 // that are pointer-equal (the underlying byte-buffer is the
14776 // storage `MeshPolicy`'s allocation, not a fresh copy) as
14777 // well as value-equal (idempotent, no side effects on
14778 // `&self`).
14779 //
14780 // Pins against a future silent detour that returned an owned
14781 // `MeshPolicy` (which would type-check via the `Clone` impl
14782 // but silently clone on every call), a `&MeshPolicy` panic-
14783 // return on the `None` arm (which would collapse the load-
14784 // bearing `Option` presence-bit into a runtime panic), or a
14785 // one-arm-only accessor that returned a saturating composite
14786 // on some sentinel input.
14787 use crate::aplicacao::{CircuitBreaker, MeshPolicy, RateLimit};
14788 use std::time::Duration;
14789 for politicas in [
14790 Some(MeshPolicy::default()),
14791 Some(MeshPolicy {
14792 timeout: Some(Duration::from_secs(30)),
14793 retries: Some(3),
14794 circuit_breaker: Some(CircuitBreaker {
14795 max_failures: 5,
14796 window: Duration::from_secs(60),
14797 }),
14798 mtls_required: Some(true),
14799 rate_limit: Some(RateLimit {
14800 rate: 100,
14801 window: Duration::from_secs(1),
14802 }),
14803 }),
14804 ] {
14805 let c = caixa_aplicacao_with_politicas(politicas.clone());
14806 let first = c.politicas().unwrap();
14807 let second = c.politicas().unwrap();
14808 assert_eq!(
14809 first, second,
14810 "Caixa::politicas must be idempotent — two successive \
14811 calls on the same &self must return the same \
14812 &MeshPolicy",
14813 );
14814 assert!(
14815 std::ptr::eq(first, second),
14816 "Caixa::politicas must borrow the underlying \
14817 Option<MeshPolicy> storage — two successive calls \
14818 must return references with the same backing pointer \
14819 (a fresh MeshPolicy clone would change the pointer on \
14820 every call)",
14821 );
14822 assert_eq!(
14823 Some(first),
14824 politicas.as_ref(),
14825 "Caixa::politicas must return :politicas verbatim by \
14826 borrow — got {first:?}, expected {:?}",
14827 politicas.as_ref(),
14828 );
14829 }
14830 let c = caixa_aplicacao_with_politicas(None);
14831 assert!(
14832 c.politicas().is_none(),
14833 "Caixa::politicas must return None when :politicas is \
14834 absent — the author-omitted arm must project through the \
14835 accessor's Option::None unchanged",
14836 );
14837 }
14838
14839 // ── Caixa::placement — outer top-level Option<&Placement> composite-reference accessor ──
14840
14841 fn caixa_aplicacao_with_placement(placement: Option<crate::aplicacao::Placement>) -> Caixa {
14842 use crate::aplicacao::{Membro, WitContract};
14843 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
14844 c.kind = CaixaKind::Aplicacao;
14845 c.membros = vec![Membro {
14846 caixa: "a".into(),
14847 versao: "^0.1".into(),
14848 }];
14849 c.contratos = vec![WitContract {
14850 de: "a".into(),
14851 para: "a".into(),
14852 wit: "wasi:http/proxy".into(),
14853 endpoint: Some("/x".into()),
14854 subject: None,
14855 slot: None,
14856 }];
14857 c.placement = placement;
14858 c
14859 }
14860
14861 #[test]
14862 fn placement_returns_placement_option_ref_verbatim_across_permutations() {
14863 // The canonical per-`Caixa` `:placement` M3 mesh-slot outer-
14864 // composite optional-composite-reference-shape pin:
14865 // [`Caixa::placement`] must return the `:placement` typed
14866 // `Option<Placement>` verbatim as an `Option<&Placement>`
14867 // reference over the same backing storage the raw
14868 // `self.placement.as_ref()` field access borrows from,
14869 // byte-equal across every representative fixture in the
14870 // accept-set — the author-omitted `None` shape (the
14871 // "cluster-default applies" partition every downstream mesh-
14872 // artifact emitter treats as "emit no `:placement` overlay"),
14873 // the empty-composite `Some(Placement { .. default })` shape
14874 // (`estrategia: SingleNode`, empty clusters, no shard-key /
14875 // affinity — the outer presence-bit is `Some` so
14876 // [`Caixa::declared_mesh_slots`] still pushes the
14877 // `M3_AUTHOR_KEY_PLACEMENT` label), a single-axis
14878 // `Replicated`-on-two-clusters fixture (the canonical shape a
14879 // stateless HTTP Aplicacao carries), and a fully-populated
14880 // `Sharded`-with-shard-key-and-affinity fixture (the canonical
14881 // shape a stateful Akka-style cluster-sharding Aplicacao
14882 // carries).
14883 //
14884 // Pins against a future silent detour that returned a fresh-
14885 // cloned [`crate::aplicacao::Placement`] copy (which would
14886 // type-check via the `Clone` impl but silently break every
14887 // downstream caller that relied on the reference sharing the
14888 // composite's backing identity), a reference to an operator-
14889 // resolved overlay (the future per-cluster
14890 // `:placement-overrides` slot — its resolution must land at
14891 // exactly this accessor body, not silently divert the raw
14892 // slot away from the peer [`Caixa::declared_mesh_slots`]
14893 // enumerator's presence probe), a `None` →
14894 // `Some(Placement::default)` cluster-default projection (which
14895 // would collapse the load-bearing "author-omitted `:placement`
14896 // ⇒ cluster-default applies" partition the peer
14897 // [`Caixa::declared_mesh_slots`] enumerator and the peer
14898 // [`Caixa::aplicacao_view`] Aplicacao-composition seed both
14899 // read), or an axis-shuffled projection (a future detour that
14900 // swapped `clusters` and `affinity` through the accessor would
14901 // silently split the paired [`Caixa::aplicacao_view`] seed's
14902 // fold input from the sibling M3 mesh-artifact emitter's
14903 // projection input).
14904 //
14905 // Fourth outer top-level [`Caixa`] `Option<&Composite>`-return
14906 // composite-reference accessor pin on the substrate primitive
14907 // — peer of the sibling
14908 // `limits_returns_limits_option_ref_verbatim_across_permutations`
14909 // (b2bd9d7),
14910 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
14911 // (35d8b52), and
14912 // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
14913 // (5d23d29) opening triad pins on the outer top-level
14914 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
14915 // here to the second of the three M3 mesh-slot axes so the
14916 // opening four-fifths of the outer `Option<&Composite>` sub-
14917 // family carries the same "byte-equal, borrow-shared,
14918 // presence-bit-preserved" outer-accessor discipline.
14919 use crate::aplicacao::{Placement, PlacementStrategy};
14920 let fixtures: Vec<Option<Placement>> = vec![
14921 None,
14922 Some(Placement::default()),
14923 Some(Placement {
14924 estrategia: PlacementStrategy::Replicated,
14925 clusters: vec!["rio".into(), "sao-paulo".into()],
14926 affinity: None,
14927 shard_key: None,
14928 }),
14929 Some(Placement {
14930 estrategia: PlacementStrategy::Sharded,
14931 clusters: vec!["rio".into(), "sao-paulo".into(), "brasilia".into()],
14932 affinity: Some("data-locality".into()),
14933 shard_key: Some("$tenantId".into()),
14934 }),
14935 ];
14936 for placement in fixtures {
14937 let c = caixa_aplicacao_with_placement(placement.clone());
14938 assert_eq!(
14939 c.placement(),
14940 placement.as_ref(),
14941 "Caixa::placement must return :placement verbatim (got \
14942 {:?}, expected {:?})",
14943 c.placement(),
14944 placement.as_ref(),
14945 );
14946 match (c.placement(), c.placement.as_ref()) {
14947 (Some(a), Some(b)) => assert!(
14948 std::ptr::eq(a, b),
14949 "Caixa::placement accessor and self.placement.as_ref() \
14950 field access must borrow the same backing storage \
14951 — the accessor is the substrate-primitive typed \
14952 dispatch every downstream Aplicacao-distribution- \
14953 overlay composite consumer must route through, and \
14954 a reference-identity split would silently break \
14955 every consumer that relied on the borrow sharing \
14956 the composite's storage",
14957 ),
14958 (None, None) => {}
14959 _ => panic!(
14960 "Caixa::placement presence bit must byte-equal \
14961 self.placement.is_some() — a presence-bit drift \
14962 would silently split the paired \
14963 Caixa::aplicacao_view Aplicacao-composition seed's \
14964 traversal head from the peer \
14965 Caixa::declared_mesh_slots M3 declared-slot \
14966 enumerator's presence probe",
14967 ),
14968 }
14969 assert_eq!(
14970 c.placement().is_some(),
14971 c.placement.is_some(),
14972 "Caixa::placement().is_some() must byte-equal \
14973 self.placement.is_some() — a presence-bit drift would \
14974 silently split every downstream Option<&Placement> \
14975 consumer's partition on the cluster-default arm",
14976 );
14977 }
14978 }
14979
14980 #[test]
14981 fn declared_mesh_slots_placement_arm_routes_through_accessor() {
14982 // Composition pin: [`Caixa::declared_mesh_slots`]'s
14983 // `:placement` presence-probe arm must key off
14984 // [`Caixa::placement`], not the raw `self.placement.is_some()`
14985 // field-probe. Structurally: a `Caixa { placement:
14986 // Some(Placement::default()), .. }` must still push
14987 // `M3_AUTHOR_KEY_PLACEMENT` onto the declared-slot list (the
14988 // presence bit is `Some`, so the M3 kind-coherence gate must
14989 // surface the slot as "declared" even when every per-axis
14990 // scalar defers to the cluster-default arm), and a `Caixa {
14991 // placement: None, .. }` must NOT push the label (the "author
14992 // omitted the slot entirely" partition). The pair jointly pins
14993 // the accessor + declared-slot enumerator composition: any
14994 // future silent detour that had the accessor collapse
14995 // `Some(Placement::default())` to `None` (a `.filter(|p|
14996 // p.clusters().is_empty().not())` projection) would silently
14997 // absorb the "declared but empty" arm at the accessor boundary
14998 // and the [`crate::LayoutError::MeshSlotsOnNonAplicacao`]
14999 // kind-coherence gate would silently accept a struct-literal
15000 // `Caixa` carrying the drift.
15001 //
15002 // Peer of the sibling
15003 // `declared_servico_slots_limits_arm_routes_through_accessor`
15004 // (b2bd9d7),
15005 // `declared_servico_slots_behavior_arm_routes_through_accessor`
15006 // (35d8b52), and
15007 // `declared_mesh_slots_politicas_arm_routes_through_accessor`
15008 // (5d23d29) composition pins on the sibling `:limits` /
15009 // `:behavior` / `:politicas` outer-`Option<&Composite>` arms
15010 // — same "the enumerator gate must route through the
15011 // substrate-primitive typed dispatch" discipline extended onto
15012 // the second of the three M3 mesh-slot axes so the
15013 // [`Caixa::declared_mesh_slots`] enumerator carries the same
15014 // routing invariant on the `:placement` arm as the peer
15015 // `:politicas` arm.
15016 use crate::aplicacao::Placement;
15017 let c = caixa_aplicacao_with_placement(Some(Placement::default()));
15018 let slots = c.declared_mesh_slots();
15019 assert!(
15020 slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
15021 "declared_mesh_slots must push M3_AUTHOR_KEY_PLACEMENT \
15022 when `:placement` is Some (even for Placement::default()) \
15023 — the accessor and the enumerator gate must route through \
15024 the same substrate-primitive typed dispatch on the outer \
15025 :placement presence bit (got slots={slots:?})",
15026 );
15027 let c = caixa_aplicacao_with_placement(None);
15028 let slots = c.declared_mesh_slots();
15029 assert!(
15030 !slots.contains(&crate::render::M3_AUTHOR_KEY_PLACEMENT),
15031 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_PLACEMENT \
15032 when `:placement` is None — the author-omitted arm must \
15033 route through the accessor's None-return unchanged (got \
15034 slots={slots:?})",
15035 );
15036 }
15037
15038 #[test]
15039 fn aplicacao_view_placement_arm_folds_through_accessor() {
15040 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:placement`
15041 // Aplicacao-composition seed must fold through
15042 // [`Caixa::placement`], not the raw
15043 // `self.placement.clone().unwrap_or_default()` field-borrow.
15044 // Structurally: a `Caixa { placement: Some(Placement {
15045 // estrategia: Replicated, clusters: ["rio"], .. default }),
15046 // kind: Aplicacao, .. }` must surface a projected
15047 // [`crate::AplicacaoSpec`] whose `placement().estrategia()` +
15048 // `placement().clusters()` byte-equal the outer composite's
15049 // authored values (the fold must project the authored
15050 // composite verbatim), a `Caixa { placement:
15051 // Some(Placement::default()), kind: Aplicacao, .. }` must
15052 // surface an [`crate::AplicacaoSpec`] whose `placement()`
15053 // byte-equals [`crate::aplicacao::Placement::default`] (the
15054 // fold's empty-composite arm collapses to the same default
15055 // the author-omitted arm does), and a `Caixa { placement:
15056 // None, kind: Aplicacao, .. }` must surface an
15057 // [`crate::AplicacaoSpec`] whose `placement()` byte-equals
15058 // [`crate::aplicacao::Placement::default`] (the "author
15059 // omitted the slot entirely" arm folds through the
15060 // `unwrap_or_default` onto the cluster-default). The triad
15061 // jointly pins the accessor + Aplicacao-composition seed
15062 // composition: any future silent detour that had the accessor
15063 // divert the raw slot away from the seed's fold (an operator-
15064 // resolved overlay's default-fold arm silently differing from
15065 // the raw slot's default-fold arm) would silently split the
15066 // build-time distribution-artifact emission gate from the
15067 // caixa-mesh renderer's Aplicacao-view input at the
15068 // composition boundary.
15069 use crate::aplicacao::{Placement, PlacementStrategy};
15070 let c = caixa_aplicacao_with_placement(Some(Placement {
15071 estrategia: PlacementStrategy::Replicated,
15072 clusters: vec!["rio".into()],
15073 affinity: None,
15074 shard_key: None,
15075 }));
15076 let view = c.aplicacao_view().unwrap();
15077 assert_eq!(
15078 view.placement().estrategia(),
15079 PlacementStrategy::Replicated,
15080 "Caixa::aplicacao_view must fold the authored :placement \
15081 :estrategia scalar through the accessor verbatim onto the \
15082 projected AplicacaoSpec — a future silent detour at the \
15083 seed's fold arm would surface here as a projected-scalar \
15084 drift (got {:?})",
15085 view.placement().estrategia(),
15086 );
15087 assert_eq!(
15088 view.placement().clusters(),
15089 &["rio"],
15090 "Caixa::aplicacao_view must fold the authored :placement \
15091 :clusters list through the accessor verbatim onto the \
15092 projected AplicacaoSpec — a future silent detour at the \
15093 seed's fold arm would surface here as a projected-list \
15094 drift (got {:?})",
15095 view.placement().clusters(),
15096 );
15097 let c = caixa_aplicacao_with_placement(Some(Placement::default()));
15098 let view = c.aplicacao_view().unwrap();
15099 assert_eq!(
15100 view.placement(),
15101 &Placement::default(),
15102 "Caixa::aplicacao_view must fold Some(Placement::default()) \
15103 through the accessor onto Placement::default — the empty- \
15104 composite arm collapses to the same default the author- \
15105 omitted arm does (got {:?})",
15106 view.placement(),
15107 );
15108 let c = caixa_aplicacao_with_placement(None);
15109 let view = c.aplicacao_view().unwrap();
15110 assert_eq!(
15111 view.placement(),
15112 &Placement::default(),
15113 "Caixa::aplicacao_view must fold None through the accessor's \
15114 unwrap_or_default onto Placement::default — the author- \
15115 omitted arm must route through the accessor's None-return \
15116 unchanged (got {:?})",
15117 view.placement(),
15118 );
15119 }
15120
15121 #[test]
15122 fn placement_projects_option_ref_by_borrow() {
15123 // The by-borrow pin: [`Caixa::placement`] returns
15124 // `Option<&Placement>` by borrow — the returned reference
15125 // borrows the underlying `Option<Placement>` storage of the
15126 // `:placement` slot and the accessor must not clone the
15127 // backing composite on every call. Peer of the sibling
15128 // `limits_projects_option_ref_by_borrow` (b2bd9d7),
15129 // `behavior_projects_option_ref_by_borrow` (35d8b52), and
15130 // `politicas_projects_option_ref_by_borrow` (5d23d29) by-borrow
15131 // pins on the outer top-level [`Caixa`]
15132 // `Option<&Composite>`-return sub-family — extended here to
15133 // the fourth axis of the same sub-family: the accessor's
15134 // returned reference must borrow from `&self` (the returned
15135 // reference's lifetime is tied to `&self`), and calling the
15136 // accessor twice on the same [`Caixa`] must yield references
15137 // that are pointer-equal (the underlying byte-buffer is the
15138 // storage `Placement`'s allocation, not a fresh copy) as well
15139 // as value-equal (idempotent, no side effects on `&self`).
15140 //
15141 // Pins against a future silent detour that returned an owned
15142 // `Placement` (which would type-check via the `Clone` impl
15143 // but silently clone on every call), a `&Placement` panic-
15144 // return on the `None` arm (which would collapse the load-
15145 // bearing `Option` presence-bit into a runtime panic), or a
15146 // one-arm-only accessor that returned a saturating composite
15147 // on some sentinel input.
15148 use crate::aplicacao::{Placement, PlacementStrategy};
15149 for placement in [
15150 Some(Placement::default()),
15151 Some(Placement {
15152 estrategia: PlacementStrategy::Sharded,
15153 clusters: vec!["rio".into(), "sao-paulo".into()],
15154 affinity: Some("data-locality".into()),
15155 shard_key: Some("$tenantId".into()),
15156 }),
15157 ] {
15158 let c = caixa_aplicacao_with_placement(placement.clone());
15159 let first = c.placement().unwrap();
15160 let second = c.placement().unwrap();
15161 assert_eq!(
15162 first, second,
15163 "Caixa::placement must be idempotent — two successive \
15164 calls on the same &self must return the same \
15165 &Placement",
15166 );
15167 assert!(
15168 std::ptr::eq(first, second),
15169 "Caixa::placement must borrow the underlying \
15170 Option<Placement> storage — two successive calls \
15171 must return references with the same backing pointer \
15172 (a fresh Placement clone would change the pointer on \
15173 every call)",
15174 );
15175 assert_eq!(
15176 Some(first),
15177 placement.as_ref(),
15178 "Caixa::placement must return :placement verbatim by \
15179 borrow — got {first:?}, expected {:?}",
15180 placement.as_ref(),
15181 );
15182 }
15183 let c = caixa_aplicacao_with_placement(None);
15184 assert!(
15185 c.placement().is_none(),
15186 "Caixa::placement must return None when :placement is \
15187 absent — the author-omitted arm must project through the \
15188 accessor's Option::None unchanged",
15189 );
15190 }
15191
15192 // ── Caixa::entrada — outer top-level Option<&Entrada> composite-reference accessor ──
15193
15194 fn caixa_aplicacao_with_entrada(entrada: Option<crate::aplicacao::Entrada>) -> Caixa {
15195 use crate::aplicacao::{Membro, WitContract};
15196 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15197 c.kind = CaixaKind::Aplicacao;
15198 c.membros = vec![Membro {
15199 caixa: "a".into(),
15200 versao: "^0.1".into(),
15201 }];
15202 c.contratos = vec![WitContract {
15203 de: "a".into(),
15204 para: "a".into(),
15205 wit: "wasi:http/proxy".into(),
15206 endpoint: Some("/x".into()),
15207 subject: None,
15208 slot: None,
15209 }];
15210 c.entrada = entrada;
15211 c
15212 }
15213
15214 #[test]
15215 fn entrada_returns_entrada_option_ref_verbatim_across_permutations() {
15216 // The canonical per-`Caixa` `:entrada` M3 mesh-slot outer-
15217 // composite optional-composite-reference-shape pin:
15218 // [`Caixa::entrada`] must return the `:entrada` typed
15219 // `Option<Entrada>` verbatim as an `Option<&Entrada>`
15220 // reference over the same backing storage the raw
15221 // `self.entrada.as_ref()` field access borrows from,
15222 // byte-equal across every representative fixture in the
15223 // accept-set — the author-omitted `None` shape (the
15224 // "cluster-internal Aplicacao" partition every downstream
15225 // Gateway-API emitter treats as "emit no listener + no
15226 // HTTPRoute"), a bare-`host`/`para` minimum-composite fixture
15227 // (empty `paths` — the resolved-paths fallback the peer
15228 // [`crate::aplicacao::Entrada::resolved_paths`] cascade folds
15229 // onto the substrate catch-all), and a fully-populated
15230 // multi-path-with-non-default-port fixture (the canonical
15231 // shape a public HTTP Aplicacao carries).
15232 //
15233 // Pins against a future silent detour that returned a fresh-
15234 // cloned [`crate::aplicacao::Entrada`] copy (which would
15235 // type-check via the `Clone` impl but silently break every
15236 // downstream caller that relied on the reference sharing the
15237 // composite's backing identity), a reference to an operator-
15238 // resolved overlay (the future per-cluster
15239 // `:entrada-overrides` slot — its resolution must land at
15240 // exactly this accessor body, not silently divert the raw
15241 // slot away from the peer [`Caixa::declared_mesh_slots`]
15242 // enumerator's presence probe), or an axis-shuffled projection
15243 // (a future detour that swapped `host` and `para` through the
15244 // accessor would silently split the paired
15245 // [`Caixa::aplicacao_view`] seed's forward input from the
15246 // sibling M3 gateway-artifact emitter's projection input).
15247 //
15248 // Fifth and final outer top-level [`Caixa`]
15249 // `Option<&Composite>`-return composite-reference accessor pin
15250 // on the substrate primitive — peer of the sibling
15251 // `limits_returns_limits_option_ref_verbatim_across_permutations`
15252 // (b2bd9d7),
15253 // `behavior_returns_behavior_option_ref_verbatim_across_permutations`
15254 // (35d8b52),
15255 // `politicas_returns_politicas_option_ref_verbatim_across_permutations`
15256 // (5d23d29), and
15257 // `placement_returns_placement_option_ref_verbatim_across_permutations`
15258 // (4fb8074) opening tetrad pins on the outer top-level
15259 // [`Caixa`] `Option<&Composite>`-return sub-family — extended
15260 // here to the third and final M3 mesh-slot axis so the closed
15261 // outer `Option<&Composite>` sub-family carries the same
15262 // "byte-equal, borrow-shared, presence-bit-preserved" outer-
15263 // accessor discipline across all five arms.
15264 use crate::aplicacao::Entrada;
15265 let fixtures: Vec<Option<Entrada>> = vec![
15266 None,
15267 Some(Entrada {
15268 host: "checkout.quero.cloud".into(),
15269 para: "gateway".into(),
15270 paths: Vec::new(),
15271 port: crate::DEFAULT_SERVICO_PORT,
15272 }),
15273 Some(Entrada {
15274 host: "api.pleme.io".into(),
15275 para: "public-api".into(),
15276 paths: vec!["/v1".into(), "/v2".into()],
15277 port: 8080,
15278 }),
15279 ];
15280 for entrada in fixtures {
15281 let c = caixa_aplicacao_with_entrada(entrada.clone());
15282 assert_eq!(
15283 c.entrada(),
15284 entrada.as_ref(),
15285 "Caixa::entrada must return :entrada verbatim (got \
15286 {:?}, expected {:?})",
15287 c.entrada(),
15288 entrada.as_ref(),
15289 );
15290 match (c.entrada(), c.entrada.as_ref()) {
15291 (Some(a), Some(b)) => assert!(
15292 std::ptr::eq(a, b),
15293 "Caixa::entrada accessor and self.entrada.as_ref() \
15294 field access must borrow the same backing storage \
15295 — the accessor is the substrate-primitive typed \
15296 dispatch every downstream Aplicacao-external- \
15297 gateway composite consumer must route through, and \
15298 a reference-identity split would silently break \
15299 every consumer that relied on the borrow sharing \
15300 the composite's storage",
15301 ),
15302 (None, None) => {}
15303 _ => panic!(
15304 "Caixa::entrada presence bit must byte-equal \
15305 self.entrada.is_some() — a presence-bit drift \
15306 would silently split the paired \
15307 Caixa::aplicacao_view Aplicacao-composition seed's \
15308 traversal head from the peer \
15309 Caixa::declared_mesh_slots M3 declared-slot \
15310 enumerator's presence probe",
15311 ),
15312 }
15313 assert_eq!(
15314 c.entrada().is_some(),
15315 c.entrada.is_some(),
15316 "Caixa::entrada().is_some() must byte-equal \
15317 self.entrada.is_some() — a presence-bit drift would \
15318 silently split every downstream Option<&Entrada> \
15319 consumer's partition on the cluster-internal arm",
15320 );
15321 }
15322 }
15323
15324 #[test]
15325 fn declared_mesh_slots_entrada_arm_routes_through_accessor() {
15326 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:entrada`
15327 // presence-probe arm must key off [`Caixa::entrada`], not the
15328 // raw `self.entrada.is_some()` field-probe. Structurally: a
15329 // `Caixa { entrada: Some(Entrada { host: "...", para: "...",
15330 // paths: [], port: DEFAULT_SERVICO_PORT }), .. }` must push
15331 // `M3_AUTHOR_KEY_ENTRADA` onto the declared-slot list (the
15332 // presence bit is `Some`, so the M3 kind-coherence gate must
15333 // surface the slot as "declared" even when every per-axis
15334 // scalar defers to the substrate catch-all / default port),
15335 // and a `Caixa { entrada: None, .. }` must NOT push the label
15336 // (the "author omitted the slot entirely" partition). The pair
15337 // jointly pins the accessor + declared-slot enumerator
15338 // composition: any future silent detour that had the accessor
15339 // collapse `Some(Entrada { paths: [], .. })` to `None` (a
15340 // `.filter(|e| !e.paths.is_empty())` projection) would silently
15341 // absorb the "declared but empty-paths" arm at the accessor
15342 // boundary and the
15343 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
15344 // coherence gate would silently accept a struct-literal
15345 // `Caixa` carrying the drift.
15346 //
15347 // Peer of the sibling
15348 // `declared_servico_slots_limits_arm_routes_through_accessor`
15349 // (b2bd9d7),
15350 // `declared_servico_slots_behavior_arm_routes_through_accessor`
15351 // (35d8b52),
15352 // `declared_mesh_slots_politicas_arm_routes_through_accessor`
15353 // (5d23d29), and
15354 // `declared_mesh_slots_placement_arm_routes_through_accessor`
15355 // (4fb8074) composition pins on the sibling `:limits` /
15356 // `:behavior` / `:politicas` / `:placement` outer-
15357 // `Option<&Composite>` arms — same "the enumerator gate must
15358 // route through the substrate-primitive typed dispatch"
15359 // discipline extended onto the third and final M3 mesh-slot
15360 // axis so the [`Caixa::declared_mesh_slots`] enumerator now
15361 // carries the routing invariant on every M3 mesh-slot arm.
15362 use crate::aplicacao::Entrada;
15363 let c = caixa_aplicacao_with_entrada(Some(Entrada {
15364 host: "checkout.quero.cloud".into(),
15365 para: "gateway".into(),
15366 paths: Vec::new(),
15367 port: crate::DEFAULT_SERVICO_PORT,
15368 }));
15369 let slots = c.declared_mesh_slots();
15370 assert!(
15371 slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
15372 "declared_mesh_slots must push M3_AUTHOR_KEY_ENTRADA when \
15373 `:entrada` is Some (even for empty-paths / default-port) \
15374 — the accessor and the enumerator gate must route through \
15375 the same substrate-primitive typed dispatch on the outer \
15376 :entrada presence bit (got slots={slots:?})",
15377 );
15378 let c = caixa_aplicacao_with_entrada(None);
15379 let slots = c.declared_mesh_slots();
15380 assert!(
15381 !slots.contains(&crate::render::M3_AUTHOR_KEY_ENTRADA),
15382 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_ENTRADA \
15383 when `:entrada` is None — the author-omitted arm must \
15384 route through the accessor's None-return unchanged (got \
15385 slots={slots:?})",
15386 );
15387 }
15388
15389 #[test]
15390 fn aplicacao_view_entrada_arm_folds_through_accessor() {
15391 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:entrada`
15392 // Aplicacao-composition seed must fold through
15393 // [`Caixa::entrada`], not the raw `self.entrada.clone()` field-
15394 // borrow. Structurally: a `Caixa { entrada: Some(Entrada {
15395 // host: "api.pleme.io", para: "public-api", paths: ["/v1"],
15396 // port: 8080 }), kind: Aplicacao, .. }` must surface a projected
15397 // [`crate::AplicacaoSpec`] whose `entrada().unwrap()` byte-
15398 // equals the outer composite's authored value (the fold must
15399 // project the authored composite verbatim), and a `Caixa {
15400 // entrada: None, kind: Aplicacao, .. }` must surface an
15401 // [`crate::AplicacaoSpec`] whose `entrada()` is `None` (the
15402 // "author omitted the slot entirely" arm folds through the
15403 // accessor's `Option::cloned` onto the same `None` presence
15404 // bit — unlike the peer `:politicas` / `:placement` arms
15405 // `:entrada` has no cluster-default fold, the omitted arm
15406 // stays omitted). The pair jointly pins the accessor +
15407 // Aplicacao-composition seed composition: any future silent
15408 // detour that had the accessor divert the raw slot away from
15409 // the seed's fold (an operator-resolved overlay's forward arm
15410 // silently differing from the raw slot's forward arm) would
15411 // silently split the build-time gateway-artifact emission gate
15412 // from the caixa-mesh renderer's Aplicacao-view input at the
15413 // composition boundary.
15414 use crate::aplicacao::Entrada;
15415 let authored = Entrada {
15416 host: "api.pleme.io".into(),
15417 para: "public-api".into(),
15418 paths: vec!["/v1".into()],
15419 port: 8080,
15420 };
15421 let c = caixa_aplicacao_with_entrada(Some(authored.clone()));
15422 let view = c.aplicacao_view().unwrap();
15423 assert_eq!(
15424 view.entrada(),
15425 Some(&authored),
15426 "Caixa::aplicacao_view must fold the authored :entrada \
15427 composite through the accessor verbatim onto the \
15428 projected AplicacaoSpec — a future silent detour at the \
15429 seed's fold arm would surface here as a projected- \
15430 composite drift (got {:?})",
15431 view.entrada(),
15432 );
15433 let c = caixa_aplicacao_with_entrada(None);
15434 let view = c.aplicacao_view().unwrap();
15435 assert!(
15436 view.entrada().is_none(),
15437 "Caixa::aplicacao_view must fold None through the \
15438 accessor's Option::cloned onto None — the author- \
15439 omitted arm must route through the accessor's None-return \
15440 unchanged (got {:?})",
15441 view.entrada(),
15442 );
15443 }
15444
15445 #[test]
15446 fn entrada_projects_option_ref_by_borrow() {
15447 // The by-borrow pin: [`Caixa::entrada`] returns
15448 // `Option<&Entrada>` by borrow — the returned reference
15449 // borrows the underlying `Option<Entrada>` storage of the
15450 // `:entrada` slot and the accessor must not clone the backing
15451 // composite on every call. Peer of the sibling
15452 // `limits_projects_option_ref_by_borrow` (b2bd9d7),
15453 // `behavior_projects_option_ref_by_borrow` (35d8b52),
15454 // `politicas_projects_option_ref_by_borrow` (5d23d29), and
15455 // `placement_projects_option_ref_by_borrow` (4fb8074) by-
15456 // borrow pins on the outer top-level [`Caixa`]
15457 // `Option<&Composite>`-return sub-family — extended here to
15458 // the fifth and final axis of the same sub-family, closing
15459 // the discipline: the accessor's returned reference must
15460 // borrow from `&self` (the returned reference's lifetime is
15461 // tied to `&self`), and calling the accessor twice on the
15462 // same [`Caixa`] must yield references that are pointer-equal
15463 // (the underlying byte-buffer is the storage `Entrada`'s
15464 // allocation, not a fresh copy) as well as value-equal
15465 // (idempotent, no side effects on `&self`).
15466 //
15467 // Pins against a future silent detour that returned an owned
15468 // `Entrada` (which would type-check via the `Clone` impl but
15469 // silently clone on every call), a `&Entrada` panic-return on
15470 // the `None` arm (which would collapse the load-bearing
15471 // `Option` presence-bit into a runtime panic), or a one-arm-
15472 // only accessor that returned a saturating composite on some
15473 // sentinel input.
15474 use crate::aplicacao::Entrada;
15475 for entrada in [
15476 Some(Entrada {
15477 host: "checkout.quero.cloud".into(),
15478 para: "gateway".into(),
15479 paths: Vec::new(),
15480 port: crate::DEFAULT_SERVICO_PORT,
15481 }),
15482 Some(Entrada {
15483 host: "api.pleme.io".into(),
15484 para: "public-api".into(),
15485 paths: vec!["/v1".into(), "/v2".into()],
15486 port: 8080,
15487 }),
15488 ] {
15489 let c = caixa_aplicacao_with_entrada(entrada.clone());
15490 let first = c.entrada().unwrap();
15491 let second = c.entrada().unwrap();
15492 assert_eq!(
15493 first, second,
15494 "Caixa::entrada must be idempotent — two successive \
15495 calls on the same &self must return the same &Entrada",
15496 );
15497 assert!(
15498 std::ptr::eq(first, second),
15499 "Caixa::entrada must borrow the underlying \
15500 Option<Entrada> storage — two successive calls must \
15501 return references with the same backing pointer (a \
15502 fresh Entrada clone would change the pointer on every \
15503 call)",
15504 );
15505 assert_eq!(
15506 Some(first),
15507 entrada.as_ref(),
15508 "Caixa::entrada must return :entrada verbatim by \
15509 borrow — got {first:?}, expected {:?}",
15510 entrada.as_ref(),
15511 );
15512 }
15513 let c = caixa_aplicacao_with_entrada(None);
15514 assert!(
15515 c.entrada().is_none(),
15516 "Caixa::entrada must return None when :entrada is absent \
15517 — the author-omitted arm must project through the \
15518 accessor's Option::None unchanged",
15519 );
15520 }
15521
15522 // ── Caixa::estrategia — outer top-level Option<RestartStrategy> flat-spread supervisor-tree accessor ──
15523
15524 fn caixa_with_estrategia(estrategia: Option<crate::supervisor::RestartStrategy>) -> Caixa {
15525 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15526 c.estrategia = estrategia;
15527 c
15528 }
15529
15530 #[test]
15531 fn estrategia_returns_estrategia_option_verbatim_across_permutations() {
15532 // The canonical per-`Caixa` `:estrategia` M2 supervisor-tree-slot
15533 // flat-spread `Option<RestartStrategy>`-return `Copy`-composite-
15534 // enum-arm scalar shape pin: [`Caixa::estrategia`] must return
15535 // the `:estrategia` typed `Option<crate::supervisor::RestartStrategy>`
15536 // verbatim as an `Option<RestartStrategy>` `Copy`-projected value
15537 // over the same discriminant the raw `self.estrategia` field
15538 // access carries, byte-equal across every representative fixture
15539 // in the accept-set — the author-omitted `None` shape (the
15540 // "defer to [`RestartStrategy::default`] through the
15541 // [`Self::supervisor_view`] `unwrap_or_default()` fold" partition
15542 // every non-`Supervisor`-kind `defcaixa` carries by
15543 // `#[serde(default)]`), and each of the four closed-set variants
15544 // [`RestartStrategy::OneForOne`] / [`RestartStrategy::OneForAll`]
15545 // / [`RestartStrategy::RestForOne`] /
15546 // [`RestartStrategy::SimpleOneForOne`] the author-declared arm
15547 // partitions on.
15548 //
15549 // Pins against a future silent detour that re-derived the
15550 // strategy from a peer axis (an accidental fallback to
15551 // `if children.is_empty() { SimpleOneForOne } else { OneForOne }`
15552 // collapse that read the outer `:children` list-length axis into
15553 // the strategy discriminator at the accessor boundary), a
15554 // stale-derive detour that substituted [`RestartStrategy::default`]
15555 // when the outer `Option` held `None` (which would silently
15556 // collapse the load-bearing "author explicitly declared
15557 // `:estrategia OneForOne`" vs "author omitted the slot and
15558 // inherited the default" partition the [`Self::declared_supervisor_slots`]
15559 // presence-probe reads — the enumerator gate would still push
15560 // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` on the omitted arm, silently
15561 // splitting the paired [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
15562 // kind-coherence gate's traversal head from the
15563 // [`Self::supervisor_view`] `unwrap_or_default()` fold's
15564 // composition head), a reference to an operator-resolved overlay
15565 // (the future per-cluster `:estrategia-overrides` slot — its
15566 // resolution must land at exactly this accessor body, not
15567 // silently divert the raw slot away from a second consumer), or
15568 // an axis-remap projection (a future detour that mapped
15569 // `OneForAll` through the accessor onto `OneForOne` would
15570 // silently split every downstream sibling-restart-strategy
15571 // consumer's per-arm fan-out).
15572 //
15573 // First outer top-level [`Caixa`] `Option<Copy>`-return
15574 // supervisor-tree-slot flat-spread accessor pin on the substrate
15575 // primitive — opens the outer-`Caixa` `Option<Copy>` flat-spread
15576 // projection pattern the sibling per-`Caixa` `:max-restarts` /
15577 // `:restart-window` future outer-scalar pins fold on. Peer of
15578 // the inner-altitude
15579 // `supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
15580 // (eafb619) pin on the post-composition [`SupervisorSpec`]
15581 // altitude — same "the substrate-primitive accessor must byte-
15582 // equal the raw field access verbatim across every author-
15583 // declared value" discipline extended onto the pre-composition
15584 // outer author-surface [`Caixa`] altitude. Peer of the closed
15585 // outer-`Caixa` `Option<&Composite>` composite-reference family
15586 // the sibling `limits` / `behavior` / `politicas` / `placement` /
15587 // `entrada`
15588 // `..._returns_..._option_ref_verbatim_across_permutations` pins
15589 // already carry on the outer `Option<&Composite>` altitude.
15590 use crate::supervisor::RestartStrategy;
15591 let fixtures: Vec<Option<RestartStrategy>> = vec![
15592 None,
15593 Some(RestartStrategy::OneForOne),
15594 Some(RestartStrategy::OneForAll),
15595 Some(RestartStrategy::RestForOne),
15596 Some(RestartStrategy::SimpleOneForOne),
15597 ];
15598 for estrategia in fixtures {
15599 let c = caixa_with_estrategia(estrategia);
15600 assert_eq!(
15601 c.estrategia(),
15602 estrategia,
15603 "Caixa::estrategia must return :estrategia verbatim (got \
15604 {:?}, expected {:?})",
15605 c.estrategia(),
15606 estrategia,
15607 );
15608 assert_eq!(
15609 c.estrategia(),
15610 c.estrategia,
15611 "Caixa::estrategia accessor and self.estrategia field \
15612 access must byte-equal — the accessor is the substrate-\
15613 primitive typed dispatch every downstream supervisor-\
15614 tree flat-spread consumer must route through, and a \
15615 discriminant split would silently break every consumer \
15616 that relied on the accessor sharing the field's own \
15617 Option<Copy> shape",
15618 );
15619 assert_eq!(
15620 c.estrategia().is_some(),
15621 c.estrategia.is_some(),
15622 "Caixa::estrategia().is_some() must byte-equal \
15623 self.estrategia.is_some() — a presence-bit drift would \
15624 silently split the paired Caixa::declared_supervisor_slots \
15625 presence-probe arm from the Caixa::supervisor_view \
15626 unwrap_or_default() fold's composition input",
15627 );
15628 }
15629 }
15630
15631 #[test]
15632 fn declared_supervisor_slots_estrategia_arm_routes_through_accessor() {
15633 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
15634 // `:estrategia` presence-probe arm must key off
15635 // [`Caixa::estrategia`], not the raw `self.estrategia.is_some()`
15636 // field-probe. Structurally: every `Caixa { estrategia:
15637 // Some(RestartStrategy::_), .. }` variant must push
15638 // `SUPERVISOR_AUTHOR_KEY_ESTRATEGIA` onto the declared-slot list
15639 // (the presence bit is `Some` for every closed-set variant, so
15640 // the M2 supervisor-tree kind-coherence gate must surface the
15641 // slot as "declared" regardless of which variant the author
15642 // picked), and a `Caixa { estrategia: None, .. }` must NOT push
15643 // the label (the "author omitted the slot entirely, deferring
15644 // to [`RestartStrategy::default`] through the supervisor_view
15645 // fold" partition). The pair jointly pins the accessor +
15646 // declared-slot enumerator composition: any future silent detour
15647 // that had the accessor collapse `Some(RestartStrategy::default())`
15648 // to `None` (a `.filter(|e| *e != RestartStrategy::default())`
15649 // projection) would silently absorb the "declared but default-
15650 // valued" arm at the accessor boundary and the
15651 // [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`] kind-
15652 // coherence gate would silently accept a struct-literal `Caixa`
15653 // carrying the drift.
15654 //
15655 // Peer of the sibling per-`Caixa`
15656 // `declared_servico_slots_limits_arm_routes_through_accessor`
15657 // (b2bd9d7) accessor-composition pin on the sibling outer-`Caixa`
15658 // `Option<&LimitsSpec>` composition axis — same "the enumerator
15659 // gate must route through the substrate-primitive typed
15660 // dispatch" discipline extended onto the flat-spread M2
15661 // supervisor-tree `Option<RestartStrategy>`-composition surface,
15662 // opening the outer-`Caixa` supervisor-tree-slot arm of the
15663 // composition-pin family.
15664 use crate::supervisor::RestartStrategy;
15665 for estrategia in [
15666 RestartStrategy::OneForOne,
15667 RestartStrategy::OneForAll,
15668 RestartStrategy::RestForOne,
15669 RestartStrategy::SimpleOneForOne,
15670 ] {
15671 let c = caixa_with_estrategia(Some(estrategia));
15672 let slots = c.declared_supervisor_slots();
15673 assert!(
15674 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
15675 "declared_supervisor_slots must push \
15676 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is \
15677 Some({estrategia:?}) — the accessor and the enumerator \
15678 gate must route through the same substrate-primitive \
15679 typed dispatch on the outer :estrategia presence bit \
15680 (got slots={slots:?})",
15681 );
15682 }
15683 let c = caixa_with_estrategia(None);
15684 let slots = c.declared_supervisor_slots();
15685 assert!(
15686 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_ESTRATEGIA),
15687 "declared_supervisor_slots must NOT push \
15688 SUPERVISOR_AUTHOR_KEY_ESTRATEGIA when `:estrategia` is None \
15689 — the author-omitted arm must route through the accessor's \
15690 None-return unchanged (got slots={slots:?})",
15691 );
15692 }
15693
15694 #[test]
15695 fn supervisor_view_estrategia_arm_routes_through_accessor() {
15696 // Composition pin: [`Caixa::supervisor_view`]'s per-`:estrategia`
15697 // [`SupervisorSpec`] construction arm must key off
15698 // [`Caixa::estrategia`]'s `unwrap_or_default()` fold, not the raw
15699 // `self.estrategia.unwrap_or_default()` field-fold. Structurally:
15700 // for every `:kind Supervisor` `Caixa` carrying an author-
15701 // declared `Some(RestartStrategy::_)` variant, the composed
15702 // [`SupervisorSpec`]'s `.estrategia` field must byte-equal the
15703 // outer accessor's declared variant unchanged; and for a
15704 // `:kind Supervisor` `Caixa` carrying `None`, the composed
15705 // [`SupervisorSpec`]'s `.estrategia` field must byte-equal
15706 // [`RestartStrategy::default`] (the [`RestartStrategy::OneForOne`]
15707 // arm the flat-spread `unwrap_or_default()` fold projects to on
15708 // the author-omitted arm — this is the *composition* between the
15709 // outer `Option<RestartStrategy>` accessor's presence-bit
15710 // surface and the inner post-composition non-`Option`
15711 // [`SupervisorSpec::estrategia`] altitude). The pair jointly
15712 // pins the accessor + supervisor_view composition: any future
15713 // silent detour that had the accessor promote `None` to
15714 // `Some(RestartStrategy::default())` (a `.or_else(|| Some(RestartStrategy::default()))`
15715 // projection) would silently collapse the two arms into one at
15716 // the accessor boundary and the [`Self::declared_supervisor_slots`]
15717 // presence probe would silently drift from the composition site.
15718 //
15719 // Peer of the sibling M2 supervisor-slot post-composition
15720 // `validate_reads_through_lifted_estrategia_accessor` (eafb619)
15721 // pin on the [`SupervisorSpec::validate`] altitude — this pin
15722 // extends that inner-altitude accessor-routing discipline onto
15723 // the pre-composition outer author-surface [`Caixa`] altitude,
15724 // pinning the composition edge between the flat-spread outer
15725 // `Option<RestartStrategy>` and the composed [`SupervisorSpec`]
15726 // `RestartStrategy` axes.
15727 use crate::CaixaKind;
15728 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
15729 for estrategia in [
15730 RestartStrategy::OneForOne,
15731 RestartStrategy::OneForAll,
15732 RestartStrategy::RestForOne,
15733 RestartStrategy::SimpleOneForOne,
15734 ] {
15735 let mut c = caixa_with_estrategia(Some(estrategia));
15736 c.kind = CaixaKind::Supervisor;
15737 // Route the `SimpleOneForOne ↔ non-SimpleOneForOne` fixture-
15738 // shape partition through the [`gen_platform::IsVariant`]
15739 // derive-generated
15740 // [`RestartStrategy::is_simple_one_for_one`] predicate rather
15741 // than the raw `matches!(estrategia, RestartStrategy::
15742 // SimpleOneForOne)` open-coded pattern-match — same closed-
15743 // set-typed-enum arm-discriminator dispatch discipline the
15744 // sibling [`crate::upgrade::UpgradeInstruction::is_restart`]
15745 // convergence (915a934) extended onto its two paired positive
15746 // / negated `matches!` sites and the peer
15747 // [`crate::aplicacao::PlacementStrategy`] `IsVariant`-derived
15748 // predicate convergence (766ec63) extended onto the M3 mesh-
15749 // slot per-`:placement` distribution-strategy discriminator
15750 // axis. See the sibling `supervisor::tests::
15751 // round_trip_all_strategies` and
15752 // `supervisor::tests::supervisor_spec_estrategia_returns_estrategia_verbatim_across_permutations`
15753 // fixtures — the three sites (all test-only,
15754 // acknowledged in 915a934's Prior-commits footnote as the
15755 // outstanding follow-up) now consult one typed dispatch on
15756 // the substrate primitive.
15757 c.children = if estrategia.is_simple_one_for_one() {
15758 Vec::new()
15759 } else {
15760 vec![ChildSpec {
15761 caixa: "worker".into(),
15762 versao: "^0.1".into(),
15763 restart: RestartPolicy::Permanent,
15764 }]
15765 };
15766 let view = c.supervisor_view().expect(
15767 "supervisor_view must materialize a SupervisorSpec for a \
15768 :kind Supervisor Caixa carrying a Some(:estrategia) slot",
15769 );
15770 assert_eq!(
15771 view.estrategia(),
15772 c.estrategia().unwrap(),
15773 "supervisor_view must carry the outer Caixa::estrategia() \
15774 declared variant onto the composed SupervisorSpec.estrategia \
15775 field verbatim on the Some arm (got {:?}, expected {:?})",
15776 view.estrategia(),
15777 c.estrategia().unwrap(),
15778 );
15779 }
15780 // The author-omitted arm: outer `None` → composed
15781 // `RestartStrategy::default()` through the flat-spread
15782 // `unwrap_or_default()` fold.
15783 let mut c = caixa_with_estrategia(None);
15784 c.kind = CaixaKind::Supervisor;
15785 // Populate children so the sibling supervisor slots are coherent
15786 // for the [`Self::supervisor_view`] projection; the `:estrategia`
15787 // arm still defers to [`RestartStrategy::default`] on the
15788 // author-omitted arm even when the sibling slots carry values.
15789 c.children = vec![ChildSpec {
15790 caixa: "worker".into(),
15791 versao: "^0.1".into(),
15792 restart: RestartPolicy::Permanent,
15793 }];
15794 let view = c.supervisor_view().expect(
15795 "supervisor_view must materialize a SupervisorSpec for a \
15796 :kind Supervisor Caixa carrying a None `:estrategia` slot",
15797 );
15798 assert_eq!(
15799 view.estrategia(),
15800 RestartStrategy::default(),
15801 "supervisor_view must project the outer Caixa::estrategia() \
15802 None arm onto RestartStrategy::default() through the flat-\
15803 spread unwrap_or_default() fold (got {:?}, expected {:?})",
15804 view.estrategia(),
15805 RestartStrategy::default(),
15806 );
15807 assert!(
15808 c.estrategia().is_none(),
15809 "Caixa::estrategia() must remain None on the author-omitted \
15810 arm — the supervisor_view fold must not mutate the outer \
15811 flat-spread presence bit",
15812 );
15813 }
15814
15815 #[test]
15816 fn estrategia_projects_option_by_copy() {
15817 // The by-`Copy` pin: [`Caixa::estrategia`] returns
15818 // `Option<RestartStrategy>` by value (`RestartStrategy: Copy`) —
15819 // the accessor does not borrow `&self` past the call (no
15820 // lifetime on the return type), and calling the accessor twice
15821 // on the same [`Caixa`] must yield discriminant-equal values
15822 // (idempotent, no side effects on `&self`). Peer of the sibling
15823 // outer-`Caixa` `Option<&Composite>` by-borrow
15824 // `limits_projects_option_ref_by_borrow` (b2bd9d7) /
15825 // `behavior_projects_option_ref_by_borrow` (35d8b52) /
15826 // `politicas_projects_option_ref_by_borrow` (5d23d29) /
15827 // `placement_projects_option_ref_by_borrow` (4fb8074) /
15828 // `entrada_projects_option_ref_by_borrow` (e4128e4) by-borrow
15829 // pins on the outer-`Caixa` `Option<&Composite>`-return axes —
15830 // extended here to the outer-`Caixa` `Option<Copy>`-return
15831 // flat-spread axis. The `Copy` discipline replaces the pointer-
15832 // equality claim the by-borrow siblings pin (a fresh `Copy` of a
15833 // `Copy` discriminant is definitionally the same discriminant, so
15834 // the axis reduces to discriminant equality).
15835 //
15836 // Pins against a future silent detour that returned a fresh
15837 // `Option<&RestartStrategy>` (which would type-check but silently
15838 // introduce a borrow of `&self` past the call, collapsing the
15839 // load-bearing "no lifetime on the return type" `Copy` projection
15840 // the flat-spread axis's `Option<Copy>` shape carries), a stale-
15841 // read side effect that flipped the outer discriminant on
15842 // successive calls, or an axis-remap projection that returned a
15843 // different variant than the field storage.
15844 use crate::supervisor::RestartStrategy;
15845 for estrategia in [
15846 Some(RestartStrategy::OneForOne),
15847 Some(RestartStrategy::OneForAll),
15848 Some(RestartStrategy::RestForOne),
15849 Some(RestartStrategy::SimpleOneForOne),
15850 ] {
15851 let c = caixa_with_estrategia(estrategia);
15852 let first = c.estrategia();
15853 let second = c.estrategia();
15854 assert_eq!(
15855 first, second,
15856 "Caixa::estrategia must be idempotent — two successive \
15857 calls on the same &self must return the same \
15858 Option<RestartStrategy>",
15859 );
15860 assert_eq!(
15861 first, estrategia,
15862 "Caixa::estrategia must return :estrategia verbatim by \
15863 Copy — got {first:?}, expected {estrategia:?}",
15864 );
15865 }
15866 let c = caixa_with_estrategia(None);
15867 assert!(
15868 c.estrategia().is_none(),
15869 "Caixa::estrategia must return None when :estrategia is \
15870 absent — the author-omitted arm must project through the \
15871 accessor's Option::None unchanged",
15872 );
15873 }
15874
15875 // ── Caixa::max_restarts / Caixa::restart_window —
15876 // outer top-level M2 supervisor-tree-slot flat-spread accessors
15877 // (Option<u32> / Option<&str>) folding on the ed04d3c
15878 // Caixa::estrategia Option<Copy> sub-family ─────────────────────
15879
15880 fn caixa_with_max_restarts(max_restarts: Option<u32>) -> Caixa {
15881 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
15882 c.max_restarts = max_restarts;
15883 c
15884 }
15885
15886 fn caixa_supervisor_with_max_restarts_and_window(
15887 max_restarts: Option<u32>,
15888 restart_window: Option<&str>,
15889 ) -> Caixa {
15890 use crate::CaixaKind;
15891 use crate::supervisor::{ChildSpec, RestartPolicy};
15892 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
15893 c.kind = CaixaKind::Supervisor;
15894 c.max_restarts = max_restarts;
15895 c.restart_window = restart_window.map(str::to_string);
15896 c.children = vec![ChildSpec {
15897 caixa: "worker".into(),
15898 versao: "^0.1".into(),
15899 restart: RestartPolicy::Permanent,
15900 }];
15901 c
15902 }
15903
15904 #[test]
15905 fn max_restarts_returns_max_restarts_option_verbatim_across_permutations() {
15906 // Value-shape pin: [`Caixa::max_restarts`] returns the
15907 // `:max-restarts` typed `Option<u32>` verbatim, `Copy`-projected
15908 // from the typed slot's own storage, byte-equal across the
15909 // author-omitted `None` arm (the "defer to the
15910 // [`Self::supervisor_view`] `unwrap_or(5)` OTP-canonical
15911 // `{intensity, 5, 60}` default" partition every
15912 // non-`Supervisor`-kind caixa carries by `#[serde(default)]`)
15913 // and each of the representative fixtures in the accept-set —
15914 // `0` (the zero-floor arm the peer
15915 // [`crate::supervisor::SupervisorSpec::validate`]
15916 // [`crate::SupervisorError::ZeroMaxRestarts`] gate refuses on
15917 // the post-composition altitude — the accessor must ship the
15918 // raw slot verbatim so struct-literal fixtures continue to
15919 // expose the zero at the accessor boundary), the OTP-canonical
15920 // `5` default (`{intensity, 5, 60}` worker-supervisor from
15921 // Learn You Some Erlang), `1000` (the
15922 // [`SUPERVISOR_MAX_RESTARTS_MAX`] cap the peer post-composition
15923 // upper-bound gate accepts on the boundary), `u32::MAX` (a
15924 // past-the-cap sentinel that the substrate-primitive accessor
15925 // must still ship verbatim). Second outer top-level
15926 // [`Caixa`] `Option<Copy>`-return supervisor-tree flat-spread
15927 // pin — folds on the sibling
15928 // `estrategia_returns_estrategia_option_verbatim_across_permutations`
15929 // (ed04d3c) pin's `Option<Copy>` shape, extending the sub-family
15930 // onto the sibling `Option<u32>` restart-budget-count arm.
15931 let fixtures: Vec<Option<u32>> = vec![None, Some(0), Some(5), Some(1000), Some(u32::MAX)];
15932 for max_restarts in fixtures {
15933 let c = caixa_with_max_restarts(max_restarts);
15934 assert_eq!(
15935 c.max_restarts(),
15936 max_restarts,
15937 "Caixa::max_restarts must return :max-restarts verbatim \
15938 (got {:?}, expected {max_restarts:?})",
15939 c.max_restarts(),
15940 );
15941 assert_eq!(
15942 c.max_restarts(),
15943 c.max_restarts,
15944 "Caixa::max_restarts accessor and self.max_restarts \
15945 field access must byte-equal — a presence-bit or count \
15946 drift would silently split the paired \
15947 Caixa::declared_supervisor_slots presence-probe arm \
15948 from the Caixa::supervisor_view unwrap_or(5) fold's \
15949 composition input",
15950 );
15951 }
15952 }
15953
15954 #[test]
15955 fn max_restarts_projects_option_by_copy() {
15956 // The by-`Copy` pin: [`Caixa::max_restarts`] returns
15957 // `Option<u32>` by value (`u32: Copy`) — the accessor does not
15958 // borrow `&self` past the call (no lifetime on the return type),
15959 // and calling the accessor twice on the same [`Caixa`] must
15960 // yield equal values (idempotent, no side effects). Peer of the
15961 // sibling `estrategia_projects_option_by_copy` (ed04d3c) pin on
15962 // the outer-`Caixa` `Option<Copy>`-return flat-spread axis.
15963 for max_restarts in [Some(0u32), Some(5), Some(1000), Some(u32::MAX), None] {
15964 let c = caixa_with_max_restarts(max_restarts);
15965 let first = c.max_restarts();
15966 let second = c.max_restarts();
15967 assert_eq!(
15968 first, second,
15969 "Caixa::max_restarts must be idempotent — two successive \
15970 calls on the same &self must return the same Option<u32>",
15971 );
15972 assert_eq!(
15973 first, max_restarts,
15974 "Caixa::max_restarts must return :max-restarts verbatim \
15975 by Copy — got {first:?}, expected {max_restarts:?}",
15976 );
15977 }
15978 }
15979
15980 #[test]
15981 fn declared_supervisor_slots_max_restarts_arm_routes_through_accessor() {
15982 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
15983 // `:max-restarts` presence-probe arm must key off
15984 // [`Caixa::max_restarts`], not the raw
15985 // `self.max_restarts.is_some()` field-probe. Structurally: every
15986 // `Caixa { max_restarts: Some(_), .. }` variant must push
15987 // `SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS` onto the declared-slot
15988 // list (the presence bit is `Some` for every representative
15989 // count, so the M2 kind-coherence gate must surface the slot as
15990 // "declared"), and a `Caixa { max_restarts: None, .. }` must
15991 // NOT push the label. Peer of the sibling
15992 // `declared_supervisor_slots_estrategia_arm_routes_through_accessor`
15993 // (ed04d3c) composition pin — same routing-through-accessor
15994 // discipline extended onto the sibling flat-spread `Option<u32>`
15995 // arm.
15996 for max_restarts in [0u32, 5, 1000, u32::MAX] {
15997 let c = caixa_with_max_restarts(Some(max_restarts));
15998 let slots = c.declared_supervisor_slots();
15999 assert!(
16000 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
16001 "declared_supervisor_slots must push \
16002 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` \
16003 is Some({max_restarts}) — the accessor and the \
16004 enumerator gate must route through the same \
16005 substrate-primitive typed dispatch on the outer \
16006 :max-restarts presence bit (got slots={slots:?})",
16007 );
16008 }
16009 let c = caixa_with_max_restarts(None);
16010 let slots = c.declared_supervisor_slots();
16011 assert!(
16012 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS),
16013 "declared_supervisor_slots must NOT push \
16014 SUPERVISOR_AUTHOR_KEY_MAX_RESTARTS when `:max-restarts` is \
16015 None — the author-omitted arm must route through the \
16016 accessor's None-return unchanged (got slots={slots:?})",
16017 );
16018 }
16019
16020 #[test]
16021 fn supervisor_view_max_restarts_arm_routes_through_accessor() {
16022 // Composition pin: [`Caixa::supervisor_view`]'s per-`:max-restarts`
16023 // [`SupervisorSpec`] construction arm must key off
16024 // [`Caixa::max_restarts`]'s `unwrap_or(5)` fold, not the raw
16025 // `self.max_restarts.unwrap_or(5)` field-fold. Structurally: for
16026 // every `:kind Supervisor` `Caixa` carrying an author-declared
16027 // `Some(n)`, the composed [`SupervisorSpec`]'s `.max_restarts()`
16028 // must byte-equal `n`; and for a `:kind Supervisor` `Caixa`
16029 // carrying `None`, the composed [`SupervisorSpec`]'s
16030 // `.max_restarts()` must byte-equal the OTP-canonical `5`. Peer
16031 // of the sibling
16032 // `supervisor_view_estrategia_arm_routes_through_accessor`
16033 // (ed04d3c) composition pin.
16034 for max_restarts in [1u32, 5, 1000] {
16035 let c = caixa_supervisor_with_max_restarts_and_window(Some(max_restarts), None);
16036 let view = c.supervisor_view().expect(
16037 "supervisor_view must materialize a SupervisorSpec for a \
16038 :kind Supervisor Caixa carrying a Some(:max-restarts)",
16039 );
16040 assert_eq!(
16041 view.max_restarts(),
16042 max_restarts,
16043 "supervisor_view must carry the outer \
16044 Caixa::max_restarts() Some arm onto the composed \
16045 SupervisorSpec.max_restarts field verbatim (got {}, \
16046 expected {max_restarts})",
16047 view.max_restarts(),
16048 );
16049 }
16050 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
16051 let view = c.supervisor_view().expect(
16052 "supervisor_view must materialize a SupervisorSpec for a \
16053 :kind Supervisor Caixa carrying a None :max-restarts",
16054 );
16055 assert_eq!(
16056 view.max_restarts(),
16057 5,
16058 "supervisor_view must project the outer \
16059 Caixa::max_restarts() None arm onto the OTP-canonical \
16060 {{intensity, 5, 60}} default (5) through the flat-spread \
16061 unwrap_or(5) fold (got {})",
16062 view.max_restarts(),
16063 );
16064 assert!(
16065 c.max_restarts().is_none(),
16066 "Caixa::max_restarts() must remain None on the author-\
16067 omitted arm — the supervisor_view fold must not mutate \
16068 the outer flat-spread presence bit",
16069 );
16070 }
16071
16072 #[test]
16073 fn supervisor_view_estrategia_fallback_routes_through_lifted_default() {
16074 // Composition pin: [`Caixa::supervisor_view`]'s author-omitted
16075 // `:estrategia` arm must degrade onto the substrate-canonical
16076 // [`crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT`] typed
16077 // `pub const` — the Erlang/OTP-canonical `one_for_one` strategy
16078 // half of Learn You Some Erlang's `{one_for_one, intensity, 5, 60}`
16079 // worker-supervisor default — rather than the transitively-
16080 // derived [`crate::supervisor::RestartStrategy::default`] route
16081 // the prior `.unwrap_or_default()` fold reached for. Prior to the
16082 // lift the composition site carried `.unwrap_or_default()` with
16083 // no compile-time link back to the shared OTP-canonical strategy
16084 // default that the paired [`crate::supervisor::Default for
16085 // RestartStrategy`] impl and the [`crate::supervisor::Default for
16086 // SupervisorSpec`] impl's struct-literal `estrategia` field both
16087 // (now) route through the same lifted constant — so a future
16088 // rebrand of the OTP-canonical strategy default (an OTP
16089 // `rest_for_one` widening once the substrate discovers startup-
16090 // order-coupled child cohorts as the more common worker-
16091 // supervisor shape, a per-cluster overlay the operator pins
16092 // through the MESH-COMPOSITION §III.2 supervision-canary
16093 // `:estrategia-overrides` roadmap slot) would have had to migrate
16094 // the paired `MaxIntensity` + `Period` halves through the lifted
16095 // constants and the `one_for_one` half through a
16096 // `RestartStrategy::default()` route in lockstep or a
16097 // `:kind Supervisor` caixa carrying an author-omitted
16098 // `:estrategia` slot would silently resolve to a `SupervisorSpec`
16099 // whose `estrategia` disagreed with the paired
16100 // `SupervisorSpec::default()` view. Byte-parity against the
16101 // lifted constant closes the split. Peer of the sibling
16102 // [`supervisor_view_max_restarts_fallback_routes_through_lifted_default`]
16103 // composition pin on the paired `MaxIntensity` half + the
16104 // [`crate::supervisor::restart_strategy_default_routes_through_lifted_default`]
16105 // + [`crate::supervisor::supervisor_spec_default_estrategia_routes_through_lifted_default`]
16106 // pins on the sibling entry points onto the shared substrate
16107 // constant.
16108 use crate::CaixaKind;
16109 use crate::supervisor::{ChildSpec, RestartPolicy};
16110 let mut c = Caixa::from_lisp(&Caixa::template("root")).unwrap();
16111 c.kind = CaixaKind::Supervisor;
16112 c.estrategia = None;
16113 c.children = vec![ChildSpec {
16114 caixa: "worker".into(),
16115 versao: "^0.1".into(),
16116 restart: RestartPolicy::Permanent,
16117 }];
16118 let view = c.supervisor_view().expect(
16119 "supervisor_view must materialize a SupervisorSpec for a \
16120 :kind Supervisor Caixa carrying a None :estrategia",
16121 );
16122 assert_eq!(
16123 view.estrategia(),
16124 crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT,
16125 "supervisor_view must degrade the outer \
16126 Caixa::estrategia() None arm onto the lifted \
16127 SUPERVISOR_ESTRATEGIA_DEFAULT typed pub const (got {:?}, \
16128 expected {:?})",
16129 view.estrategia(),
16130 crate::supervisor::SUPERVISOR_ESTRATEGIA_DEFAULT,
16131 );
16132 }
16133
16134 #[test]
16135 fn supervisor_view_max_restarts_fallback_routes_through_lifted_default() {
16136 // Composition pin: [`Caixa::supervisor_view`]'s author-omitted
16137 // `:max-restarts` arm must degrade onto the substrate-canonical
16138 // [`crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT`] typed
16139 // `pub const` — the Erlang/OTP-canonical `{intensity, 5, 60}`
16140 // `MaxIntensity` default — rather than a raw `5` literal. Prior
16141 // to the lift the composition site carried an inline
16142 // `.unwrap_or(5)` with no compile-time link back to the shared
16143 // OTP-canonical default that the serde-side
16144 // `#[serde(default = "default_max_restarts")]` wire-format arm
16145 // and the [`Default for crate::supervisor::SupervisorSpec`]
16146 // struct-literal default arm both key off — so a future rebrand
16147 // of the OTP-canonical default (Elixir's `Supervisor` `3`
16148 // default, a per-cluster overlay the operator pins through the
16149 // MESH-COMPOSITION §III.2 supervision-canary
16150 // `:supervisor :max-restarts-overrides` roadmap slot) would
16151 // have had to be threaded through both the serde-side helper
16152 // and this view-construction arm in lockstep or a `:kind
16153 // Supervisor` caixa carrying `:max-restarts ()` would silently
16154 // resolve to a `SupervisorSpec` whose `max_restarts` disagreed
16155 // with the same fixture's serde-side `SupervisorSpec` view (an
16156 // author-omitted slot round-tripping through
16157 // `SupervisorSpec::default()` to the lifted constant, then
16158 // splitting to a stale literal past `supervisor_view`).
16159 // Byte-parity against the lifted constant closes the split.
16160 // Peer of the sibling
16161 // [`crate::supervisor::default_max_restarts_helper_routes_through_lifted_default`]
16162 // + [`crate::supervisor::supervisor_spec_default_max_restarts_routes_through_lifted_default`]
16163 // composition pins that close the same routing on the two
16164 // sibling entry points onto the shared substrate constant.
16165 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
16166 let view = c.supervisor_view().expect(
16167 "supervisor_view must materialize a SupervisorSpec for a \
16168 :kind Supervisor Caixa carrying a None :max-restarts",
16169 );
16170 assert_eq!(
16171 view.max_restarts(),
16172 crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT,
16173 "supervisor_view must degrade the outer \
16174 Caixa::max_restarts() None arm onto the lifted \
16175 SUPERVISOR_MAX_RESTARTS_DEFAULT typed pub const (got {}, \
16176 expected {})",
16177 view.max_restarts(),
16178 crate::supervisor::SUPERVISOR_MAX_RESTARTS_DEFAULT,
16179 );
16180 }
16181
16182 #[test]
16183 fn restart_window_returns_restart_window_option_verbatim_across_permutations() {
16184 // Value-shape pin: [`Caixa::restart_window`] returns the
16185 // `:restart-window` typed `Option<String>` verbatim as an
16186 // `Option<&str>`, borrowed from the typed slot's own storage,
16187 // byte-equal across the author-omitted `None` arm and each of
16188 // the representative fixtures in the accept-set — the canonical
16189 // `"60s"` from `{intensity, 5, 60}`, the sibling
16190 // canonical-magnitude forms (`"5m"` / `"1h"` / `"500ms"` / `"30"`
16191 // / `"0s"`) the shared codec's positive-set sweep pin covers,
16192 // plus a past-the-guard sentinel (`"1.5s"` — the fractional-
16193 // seconds drift the sibling [`Self::validate_restart_window`]
16194 // gate refuses; the accessor must ship the raw slot verbatim
16195 // so struct-literal fixtures continue to expose the drift at
16196 // the accessor boundary). Third outer top-level [`Caixa`]
16197 // supervisor-tree flat-spread pin — extends the sub-family onto
16198 // the sibling `Option<&str>` raw-duration-string arm.
16199 for window in [
16200 None,
16201 Some("60s"),
16202 Some("5m"),
16203 Some("1h"),
16204 Some("500ms"),
16205 Some("1.5s"),
16206 Some(""),
16207 ] {
16208 let c = caixa_with_restart_window(window);
16209 assert_eq!(
16210 c.restart_window(),
16211 window,
16212 "Caixa::restart_window must return :restart-window \
16213 verbatim as Option<&str> (got {:?}, expected {window:?})",
16214 c.restart_window(),
16215 );
16216 assert_eq!(
16217 c.restart_window(),
16218 c.restart_window.as_deref(),
16219 "Caixa::restart_window accessor and \
16220 self.restart_window.as_deref() field access must \
16221 byte-equal — a byte-level drift would silently split \
16222 the paired Caixa::declared_supervisor_slots \
16223 presence-probe arm from the \
16224 Caixa::validate_restart_window shared-codec gate and \
16225 the Caixa::supervisor_view soft-swallowing fold",
16226 );
16227 }
16228 }
16229
16230 #[test]
16231 fn restart_window_projects_slice_by_borrow() {
16232 // The by-borrow pin: [`Caixa::restart_window`] returns
16233 // `Option<&str>` by borrow — the returned string slice borrows
16234 // the underlying `Option<String>` storage of the `:restart-window`
16235 // slot and the accessor must not clone on every call. Peer of
16236 // the sibling outer top-level [`Caixa`] `Option<&str>`-return
16237 // by-borrow pins on the universal-axis scalar family
16238 // (`licenca_projects_option_ref_by_borrow` /
16239 // `descricao_projects_option_ref_by_borrow` and siblings) —
16240 // extended onto the M2 supervisor-tree flat-spread
16241 // `Option<&str>` raw-duration-string axis.
16242 for window in [None, Some("60s"), Some("5m"), Some("")] {
16243 let c = caixa_with_restart_window(window);
16244 let first = c.restart_window();
16245 let second = c.restart_window();
16246 assert_eq!(
16247 first, second,
16248 "Caixa::restart_window must be idempotent — two \
16249 successive calls on the same &self must return the \
16250 same Option<&str>",
16251 );
16252 if let (Some(a), Some(b)) = (first, second) {
16253 assert_eq!(
16254 a.as_ptr(),
16255 b.as_ptr(),
16256 "Caixa::restart_window must borrow the underlying \
16257 String storage — two successive Some-arm calls must \
16258 return slices with the same backing pointer (a fresh \
16259 String clone would change the pointer on every call)",
16260 );
16261 }
16262 assert_eq!(
16263 first, window,
16264 "Caixa::restart_window must return :restart-window \
16265 verbatim by borrow — got {first:?}, expected {window:?}",
16266 );
16267 }
16268 }
16269
16270 #[test]
16271 fn declared_supervisor_slots_restart_window_arm_routes_through_accessor() {
16272 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
16273 // `:restart-window` presence-probe arm must key off
16274 // [`Caixa::restart_window`], not the raw
16275 // `self.restart_window.is_some()` field-probe. Structurally:
16276 // every `Caixa { restart_window: Some(_), .. }` must push
16277 // `SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW` onto the declared-slot
16278 // list, and a `Caixa { restart_window: None, .. }` must NOT
16279 // push the label. Peer of the sibling
16280 // `declared_supervisor_slots_max_restarts_arm_routes_through_accessor`
16281 // routing pin.
16282 for window in ["60s", "5m", "1h", "500ms", "1.5s", ""] {
16283 let c = caixa_with_restart_window(Some(window));
16284 let slots = c.declared_supervisor_slots();
16285 assert!(
16286 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
16287 "declared_supervisor_slots must push \
16288 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when \
16289 `:restart-window` is Some({window:?}) — the accessor \
16290 and the enumerator gate must route through the same \
16291 substrate-primitive typed dispatch on the outer \
16292 :restart-window presence bit (got slots={slots:?})",
16293 );
16294 }
16295 let c = caixa_with_restart_window(None);
16296 let slots = c.declared_supervisor_slots();
16297 assert!(
16298 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW),
16299 "declared_supervisor_slots must NOT push \
16300 SUPERVISOR_AUTHOR_KEY_RESTART_WINDOW when `:restart-window` \
16301 is None — the author-omitted arm must route through the \
16302 accessor's None-return unchanged (got slots={slots:?})",
16303 );
16304 }
16305
16306 #[test]
16307 fn validate_restart_window_arm_routes_through_accessor() {
16308 // Composition pin: [`Caixa::validate_restart_window`]'s
16309 // shared-codec fold arm must key off [`Caixa::restart_window`],
16310 // not the raw `self.restart_window.as_deref()` field-projection.
16311 // Structurally: (1) `None` → `Ok(())` (the "omit the slot to
16312 // express no reset" canonical shape); (2) a canonical `Some`
16313 // arm (`"60s"`) → `Ok(())`; (3) a codec-rejected `Some` arm
16314 // (`"1.5s"`) → `Err(RestartWindowMalformed { restart_window,
16315 // .. })` carrying the offending raw string verbatim. The three
16316 // arms jointly pin that the validator's raw-string binding is
16317 // the accessor's return, not a peer projection — any future
16318 // silent detour that had the accessor collapse `Some("")` to
16319 // `None` would silently absorb the empty-after-trim refusal
16320 // case at the accessor boundary.
16321 caixa_with_restart_window(None)
16322 .validate_restart_window()
16323 .expect("None :restart-window must validate through the accessor");
16324 caixa_with_restart_window(Some("60s"))
16325 .validate_restart_window()
16326 .expect("canonical :restart-window \"60s\" must validate through the accessor");
16327 let err = caixa_with_restart_window(Some("1.5s"))
16328 .validate_restart_window()
16329 .expect_err("fractional-seconds :restart-window must fail through the accessor");
16330 assert!(
16331 matches!(
16332 err,
16333 ManifestError::RestartWindowMalformed { ref restart_window, .. }
16334 if restart_window == "1.5s"
16335 ),
16336 "validator must carry the offending raw string verbatim \
16337 from the accessor's borrowed &str (got {err:?})",
16338 );
16339 }
16340
16341 #[test]
16342 fn supervisor_view_restart_window_arm_routes_through_accessor() {
16343 // Composition pin: [`Caixa::supervisor_view`]'s
16344 // per-`:restart-window` [`SupervisorSpec`] construction arm
16345 // must key off [`Caixa::restart_window`]'s soft-swallowing
16346 // `.and_then(|s| duration_codec::parse(s).ok())` fold, not the
16347 // raw `self.restart_window.as_deref().and_then(…)` field-fold.
16348 // Structurally: (1) `None` → `SupervisorSpec.restart_window ==
16349 // None` (the "never reset" sentinel); (2) canonical `Some("60s")`
16350 // → `SupervisorSpec.restart_window == Some(Duration::from_secs(60))`
16351 // (the shared codec's canonical parse); (3) codec-rejected
16352 // `Some("1.5s")` → `SupervisorSpec.restart_window == None`
16353 // (the soft-swallow preserving the view's best-effort shape).
16354 let c = caixa_supervisor_with_max_restarts_and_window(None, None);
16355 let view = c.supervisor_view().expect("Supervisor kind has a view");
16356 assert_eq!(
16357 view.restart_window(),
16358 None,
16359 "supervisor_view must project outer None :restart-window \
16360 onto None on the composed SupervisorSpec (never-reset \
16361 sentinel) through the accessor's None-return unchanged",
16362 );
16363
16364 let c = caixa_supervisor_with_max_restarts_and_window(None, Some("60s"));
16365 let view = c.supervisor_view().expect("Supervisor kind has a view");
16366 assert_eq!(
16367 view.restart_window(),
16368 Some(std::time::Duration::from_secs(60)),
16369 "supervisor_view must fold outer Some(\"60s\") through the \
16370 shared duration_codec into Duration::from_secs(60) on the \
16371 composed SupervisorSpec (accessor's Some(&str) → codec \
16372 parse → Some(Duration))",
16373 );
16374
16375 let c = caixa_supervisor_with_max_restarts_and_window(None, Some("1.5s"));
16376 let view = c.supervisor_view().expect("Supervisor kind has a view");
16377 assert_eq!(
16378 view.restart_window(),
16379 None,
16380 "supervisor_view must soft-swallow the shared-codec parse \
16381 failure to None (the view's best-effort shape the sibling \
16382 manifest-level validate_restart_window surfaces as \
16383 RestartWindowMalformed); the accessor's raw-string return \
16384 is the single input every downstream consumer keys off",
16385 );
16386 }
16387
16388 // ── Caixa::upgrade_from — outer top-level &[UpgradeFromEntry] composite-slice accessor ──
16389
16390 fn caixa_with_upgrade_from(upgrade_from: Vec<crate::upgrade::UpgradeFromEntry>) -> Caixa {
16391 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16392 c.upgrade_from = upgrade_from;
16393 c
16394 }
16395
16396 #[test]
16397 fn upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations() {
16398 // The canonical per-`Caixa` `:upgrade-from` M2 typed-slot
16399 // outer-composite `&[UpgradeFromEntry]`-return slice-shape
16400 // pin: [`Caixa::upgrade_from`] must return the `:upgrade-from`
16401 // typed `Vec<UpgradeFromEntry>` verbatim as a
16402 // `&[UpgradeFromEntry]` slice-view over the same backing
16403 // buffer the raw `self.upgrade_from.as_slice()` field access
16404 // borrows from, element-equal across every representative
16405 // fixture in the accept-set — `[]` (the "no hot-upgrade path
16406 // declared" arm every `defcaixa` without an `:upgrade-from`
16407 // block carries; `#[serde(default)]` folds an omitted slot
16408 // onto `Vec::new()`), a canonical single-entry `Restart`
16409 // fixture (the shape most Servicos carry — a single prior
16410 // version with the fallback strategy), a canonical multi-
16411 // entry list carrying every typed instruction variant
16412 // (`LoadModule` / `StateChange` / `SoftPurge` / `Purge` /
16413 // `Restart`), and a past-the-guard sentinel — a duplicate-
16414 // `:from` `[(0.1.0, Restart), (0.1.0, Restart)]` entry pair
16415 // ([`crate::upgrade::validate_upgrade_from`] rejects through
16416 // `DuplicateFrom { from: "0.1.0" }` but the accessor must
16417 // ship the raw slot verbatim so struct-literal fixtures
16418 // continue to expose the duplicate at the accessor boundary).
16419 //
16420 // Pins against a future silent detour that returned an owned
16421 // `Vec<UpgradeFromEntry>` (which would type-check but silently
16422 // clone on every accessor call, breaking the zero-cost
16423 // projection every peer sibling slice accessor carries), a
16424 // `[dup, dup] → [dup]` dedup collapse (which would silently
16425 // absorb the `DuplicateFrom` refusal case at the accessor
16426 // boundary and the [`crate::StandardLayout::verify`] cross-
16427 // entry gate would silently accept a struct-literal `Caixa`
16428 // carrying the drift), a reference to an operator-resolved
16429 // overlay (the future per-cluster `:upgrade-overrides` slot
16430 // — its resolution must land at exactly this accessor body,
16431 // not silently divert the raw slot away from a second
16432 // consumer), or an axis-shuffled projection (a future detour
16433 // that reordered entries through the accessor would silently
16434 // split the paired [`crate::StandardLayout::verify`] per-
16435 // `:upgrade-from` shape gate's traversal input from the peer
16436 // [`crate::render::servico_m2_overlay`] emitter's projection
16437 // input, since the operator's hot-upgrade dispatch matches
16438 // per-`:from` and axis reordering would silently split the
16439 // per-entry script-path existence probe's iteration order
16440 // from the M2 overlay emitter's serialized-entry order).
16441 //
16442 // First outer top-level [`Caixa`] `&[Composite]`-return
16443 // slice accessor pin on the substrate primitive for M2 / M3
16444 // typed-slot vec-carry axes — opens the outer-`Caixa`
16445 // `&[Composite]` composite-slice projection pattern the
16446 // sibling `:children` [`crate::supervisor::ChildSpec`] /
16447 // `:membros` [`crate::aplicacao::Membro`] / `:contratos`
16448 // [`crate::aplicacao::WitContract`] future outer-composite-
16449 // slice pins fold on. Peer of the closed outer-`Caixa`
16450 // scalar `Option<&Composite>` composite-reference family the
16451 // sibling `limits` / `behavior` / `politicas` / `placement`
16452 // / `entrada` `..._returns_..._option_ref_verbatim_across_
16453 // permutations` pins closed (b2bd9d7 → e4128e4) — extends
16454 // the "byte-equal, borrow-shared" outer-accessor discipline
16455 // onto the outer-`Caixa` `&[Composite]` vec-carry altitude.
16456 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
16457 let fixtures: Vec<Vec<UpgradeFromEntry>> = vec![
16458 vec![],
16459 vec![UpgradeFromEntry {
16460 from: "0.0.1".into(),
16461 instructions: vec![UpgradeInstruction::Restart],
16462 }],
16463 vec![
16464 UpgradeFromEntry {
16465 from: "0.0.1".into(),
16466 instructions: vec![
16467 UpgradeInstruction::LoadModule {
16468 module: "demo".into(),
16469 },
16470 UpgradeInstruction::SoftPurge {
16471 module: "demo".into(),
16472 },
16473 ],
16474 },
16475 UpgradeFromEntry {
16476 from: "0.0.2".into(),
16477 instructions: vec![
16478 UpgradeInstruction::StateChange {
16479 script: "servicos/upgrade.lisp".into(),
16480 },
16481 UpgradeInstruction::Purge {
16482 module: "demo".into(),
16483 },
16484 UpgradeInstruction::Restart,
16485 ],
16486 },
16487 ],
16488 vec![
16489 UpgradeFromEntry {
16490 from: "0.1.0".into(),
16491 instructions: vec![UpgradeInstruction::Restart],
16492 },
16493 UpgradeFromEntry {
16494 from: "0.1.0".into(),
16495 instructions: vec![UpgradeInstruction::Restart],
16496 },
16497 ],
16498 ];
16499 for upgrade_from in fixtures {
16500 let c = caixa_with_upgrade_from(upgrade_from.clone());
16501 assert_eq!(
16502 c.upgrade_from(),
16503 upgrade_from.as_slice(),
16504 "Caixa::upgrade_from must return :upgrade-from \
16505 verbatim (got {:?}, expected {upgrade_from:?})",
16506 c.upgrade_from(),
16507 );
16508 assert_eq!(
16509 c.upgrade_from(),
16510 c.upgrade_from.as_slice(),
16511 "Caixa::upgrade_from must element-equal the raw \
16512 `self.upgrade_from.as_slice()` field access across \
16513 every value in the Vec<UpgradeFromEntry> accept-set",
16514 );
16515 assert_eq!(
16516 c.upgrade_from().is_empty(),
16517 c.upgrade_from.is_empty(),
16518 "Caixa::upgrade_from().is_empty() must byte-equal \
16519 self.upgrade_from.is_empty() — a presence-bit drift \
16520 would silently split the paired \
16521 Caixa::declared_servico_slots M2 declared-slot \
16522 enumerator's presence probe from the peer \
16523 crate::render::servico_m2_overlay M2 overlay \
16524 emitter's presence gate",
16525 );
16526 }
16527 }
16528
16529 #[test]
16530 fn declared_servico_slots_upgrade_from_arm_routes_through_accessor() {
16531 // Composition pin: [`Caixa::declared_servico_slots`]'s
16532 // `:upgrade-from` presence-probe arm must key off
16533 // [`Caixa::upgrade_from`], not the raw
16534 // `self.upgrade_from.is_empty()` field-probe. Structurally: a
16535 // `Caixa { upgrade_from: vec![UpgradeFromEntry { from: "0.0.1",
16536 // instructions: vec![Restart] }], .. }` must push
16537 // `M2_AUTHOR_KEY_UPGRADE_FROM` onto the declared-slot list
16538 // (the presence bit is non-empty, so the M2 kind-coherence
16539 // gate must surface the slot as "declared"), and a `Caixa {
16540 // upgrade_from: vec![], .. }` must NOT push the label (the
16541 // "author omitted the slot entirely" arm — the empty-slice
16542 // partition the serde-default folds onto). The pair jointly
16543 // pins the accessor + declared-slot enumerator composition:
16544 // any future silent detour that had the accessor collapse
16545 // `[Restart]` to `[]` (a `.filter(|e| !e.instructions.
16546 // is_empty())` projection) would silently absorb the
16547 // "declared but degenerate" arm at the accessor boundary and
16548 // the [`crate::LayoutError::ServicoSlotsOnNonServico`] kind-
16549 // coherence gate would silently accept a struct-literal
16550 // `Caixa` carrying the drift.
16551 //
16552 // Peer of the sibling
16553 // `declared_servico_slots_limits_arm_routes_through_accessor`
16554 // (b2bd9d7) and
16555 // `declared_servico_slots_behavior_arm_routes_through_accessor`
16556 // (35d8b52) composition pins on the sibling `:limits` /
16557 // `:behavior` outer-`Option<&Composite>` arms — same "the
16558 // enumerator gate must route through the substrate-primitive
16559 // typed dispatch" discipline extended onto the third M2
16560 // Servico-runtime slot axis, closing the enumerator's routing
16561 // invariant on every M2 arm.
16562 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
16563 let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
16564 from: "0.0.1".into(),
16565 instructions: vec![UpgradeInstruction::Restart],
16566 }]);
16567 let slots = c.declared_servico_slots();
16568 assert!(
16569 slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
16570 "declared_servico_slots must push \
16571 M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
16572 non-empty — the accessor and the enumerator gate must \
16573 route through the same substrate-primitive typed \
16574 dispatch on the outer :upgrade-from presence bit (got \
16575 slots={slots:?})",
16576 );
16577 let c = caixa_with_upgrade_from(vec![]);
16578 let slots = c.declared_servico_slots();
16579 assert!(
16580 !slots.contains(&crate::render::M2_AUTHOR_KEY_UPGRADE_FROM),
16581 "declared_servico_slots must NOT push \
16582 M2_AUTHOR_KEY_UPGRADE_FROM when `:upgrade-from` is \
16583 empty — the author-omitted arm must route through the \
16584 accessor's empty-slice return unchanged (got \
16585 slots={slots:?})",
16586 );
16587 }
16588
16589 #[test]
16590 fn servico_m2_overlay_upgrade_from_arm_routes_through_accessor() {
16591 // Composition pin: [`crate::render::servico_m2_overlay`]'s
16592 // per-`:upgrade-from` M2 overlay emit arm must key off
16593 // [`Caixa::upgrade_from`], not the raw
16594 // `!caixa.upgrade_from.is_empty()` presence gate + the
16595 // `serde_yaml::to_value(&caixa.upgrade_from)` projection.
16596 // Structurally: a `Caixa { upgrade_from: vec![UpgradeFromEntry
16597 // { from: "0.0.1", instructions: vec![Restart] }], .. }` must
16598 // surface the `M2_KEY_UPGRADE_FROM` key with a per-entry
16599 // sequence in the overlay (the emitter fans onto the serde
16600 // slice-serialization), and a `Caixa { upgrade_from: vec![],
16601 // .. }` must omit the key entirely (the empty-slice
16602 // partition — the `!.is_empty()` outer gate elides the key
16603 // when the author omitted the slot). The pair jointly pins
16604 // the accessor + M2 overlay emitter composition: any future
16605 // silent detour that had the accessor return a fresh-cloned
16606 // `Vec<UpgradeFromEntry>` copy would silently break the
16607 // reference-identity pin the peer per-entry
16608 // `serde_yaml::to_value(caixa.upgrade_from())` projection
16609 // reads from — the projection would clone once per accessor
16610 // call instead of borrowing the storage buffer verbatim.
16611 //
16612 // Peer of the sibling
16613 // `servico_m2_overlay_limits_arm_routes_through_accessor`
16614 // (b2bd9d7) and
16615 // `servico_m2_overlay_behavior_arm_routes_through_accessor`
16616 // (35d8b52) composition pins on the sibling `:limits` /
16617 // `:behavior` outer-`Option<&Composite>` arms — same "the
16618 // M2 overlay emitter must route through the substrate-
16619 // primitive typed dispatch" discipline extended onto the
16620 // third M2 Servico-runtime slot axis, closing the overlay
16621 // emitter's routing invariant on every M2 arm.
16622 use crate::render::{M2_KEY_UPGRADE_FROM, servico_m2_overlay};
16623 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
16624 let c = caixa_with_upgrade_from(vec![UpgradeFromEntry {
16625 from: "0.0.1".into(),
16626 instructions: vec![UpgradeInstruction::Restart],
16627 }]);
16628 let overlay = servico_m2_overlay(&c).unwrap();
16629 assert!(
16630 overlay.contains_key(M2_KEY_UPGRADE_FROM),
16631 "servico_m2_overlay must surface M2_KEY_UPGRADE_FROM when \
16632 `:upgrade-from` is non-empty — the accessor and the M2 \
16633 overlay emitter must route through the same substrate- \
16634 primitive typed dispatch on the outer :upgrade-from \
16635 slice (got overlay={overlay:?})",
16636 );
16637 let c = caixa_with_upgrade_from(vec![]);
16638 let overlay = servico_m2_overlay(&c).unwrap();
16639 assert!(
16640 !overlay.contains_key(M2_KEY_UPGRADE_FROM),
16641 "servico_m2_overlay must omit M2_KEY_UPGRADE_FROM when \
16642 `:upgrade-from` is empty — the empty-slice partition \
16643 must route through the accessor's empty-slice return \
16644 unchanged (got overlay={overlay:?})",
16645 );
16646 }
16647
16648 #[test]
16649 fn upgrade_from_projects_slice_by_borrow() {
16650 // The by-borrow pin: [`Caixa::upgrade_from`] returns
16651 // `&[UpgradeFromEntry]` by borrow — the returned slice
16652 // borrows the underlying `Vec<UpgradeFromEntry>` storage of
16653 // the `:upgrade-from` slot and the accessor must not clone
16654 // the backing `Vec` on every call. Peer of the sibling
16655 // outer top-level [`Caixa`] `&[T]`-return by-borrow pins
16656 // (`autores_projects_slice_by_borrow` b5d813f,
16657 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
16658 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
16659 // `exe_projects_slice_by_borrow` 65d9527,
16660 // `servicos_projects_slice_by_borrow` 611f78b,
16661 // `deps_projects_slice_by_borrow` ad34b4e,
16662 // `deps_dev_projects_slice_by_borrow` f7fd81e) on the
16663 // sibling outer top-level [`Caixa`] scalar-element `&[T]`
16664 // axes — extended here to the first outer-`Caixa`
16665 // composite-element `&[Composite]` axis: the accessor's
16666 // returned slice must borrow from `&self` (the returned
16667 // reference's lifetime is tied to `&self`), and calling the
16668 // accessor twice on the same [`Caixa`] must yield slices
16669 // that are pointer-equal (the underlying byte-buffer is the
16670 // storage `Vec`'s allocation, not a fresh copy) as well as
16671 // value-equal (idempotent, no side effects on `&self`).
16672 //
16673 // Pins against a future silent detour that returned an owned
16674 // `Vec<UpgradeFromEntry>` (which would type-check but
16675 // silently clone on every call), a `&Vec<UpgradeFromEntry>`
16676 // return (which would leak the backing `Vec`'s
16677 // grow/push/reserve surface no downstream consumer reaches
16678 // for), or a one-arm-only accessor that returned a
16679 // saturating value on some sentinel input.
16680 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
16681 for upgrade_from in [
16682 vec![],
16683 vec![UpgradeFromEntry {
16684 from: "0.0.1".into(),
16685 instructions: vec![UpgradeInstruction::Restart],
16686 }],
16687 vec![
16688 UpgradeFromEntry {
16689 from: "0.0.1".into(),
16690 instructions: vec![UpgradeInstruction::Restart],
16691 },
16692 UpgradeFromEntry {
16693 from: "0.0.2".into(),
16694 instructions: vec![UpgradeInstruction::SoftPurge {
16695 module: "demo".into(),
16696 }],
16697 },
16698 ],
16699 ] {
16700 let c = caixa_with_upgrade_from(upgrade_from.clone());
16701 let first = c.upgrade_from();
16702 let second = c.upgrade_from();
16703 assert_eq!(
16704 first, second,
16705 "Caixa::upgrade_from must be idempotent — two \
16706 successive calls on the same &self must return the \
16707 same &[UpgradeFromEntry]",
16708 );
16709 assert_eq!(
16710 first.as_ptr(),
16711 second.as_ptr(),
16712 "Caixa::upgrade_from must borrow the underlying \
16713 Vec<UpgradeFromEntry> storage — two successive calls \
16714 must return slices with the same backing pointer (a \
16715 fresh Vec<UpgradeFromEntry> clone would change the \
16716 pointer on every call)",
16717 );
16718 assert_eq!(
16719 first,
16720 upgrade_from.as_slice(),
16721 "Caixa::upgrade_from must return :upgrade-from \
16722 verbatim by borrow — got {first:?}, expected \
16723 {upgrade_from:?}",
16724 );
16725 }
16726 }
16727
16728 // ── Caixa::children — outer top-level &[ChildSpec] composite-slice accessor ──
16729
16730 fn caixa_with_children(children: Vec<crate::supervisor::ChildSpec>) -> Caixa {
16731 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
16732 c.children = children;
16733 c
16734 }
16735
16736 #[test]
16737 fn children_returns_children_slice_verbatim_across_permutations() {
16738 // The canonical per-`Caixa` `:children` M2 supervisor-tree-slot
16739 // outer-composite `&[ChildSpec]`-return slice-shape pin:
16740 // [`Caixa::children`] must return the `:children` typed
16741 // `Vec<ChildSpec>` verbatim as a `&[ChildSpec]` slice-view over
16742 // the same backing buffer the raw `self.children.as_slice()`
16743 // field access borrows from, element-equal across every
16744 // representative fixture in the accept-set — `[]` (the "no
16745 // static children declared" arm every non-`Supervisor`-kind
16746 // `defcaixa` carries by `#[serde(default)]` and every
16747 // `SimpleOneForOne` supervisor carries by cross-slot refusal),
16748 // a canonical single-child `Permanent` fixture (the shape
16749 // most `OneForOne` supervisors carry — a single long-running
16750 // worker child), a canonical multi-child list carrying every
16751 // typed restart-policy variant (`Permanent` / `Transient` /
16752 // `Temporary`), and a past-the-guard sentinel — a duplicate
16753 // `:caixa` `[("w", ...), ("w", ...)]` entry pair
16754 // ([`crate::SupervisorSpec::validate`] rejects through
16755 // `DuplicateChildNome { nome: "w" }` but the accessor must
16756 // ship the raw slot verbatim so struct-literal fixtures
16757 // continue to expose the duplicate at the accessor boundary).
16758 //
16759 // Pins against a future silent detour that returned an owned
16760 // `Vec<ChildSpec>` (which would type-check but silently clone
16761 // on every accessor call, breaking the zero-cost projection
16762 // every peer sibling slice accessor carries), a `[dup, dup] →
16763 // [dup]` dedup collapse (which would silently absorb the
16764 // `DuplicateChildNome` refusal case at the accessor boundary
16765 // and the [`crate::StandardLayout::verify`] cross-child gate
16766 // would silently accept a struct-literal `Caixa` carrying the
16767 // drift), a reference to an operator-resolved overlay (the
16768 // future per-cluster `:children-overrides` slot — its
16769 // resolution must land at exactly this accessor body, not
16770 // silently divert the raw slot away from a second consumer),
16771 // or an axis-shuffled projection (a future detour that
16772 // reordered children through the accessor would silently
16773 // split the paired [`crate::StandardLayout::verify`] per-
16774 // supervisor gate's traversal input from the peer
16775 // [`Self::supervisor_view`] fold-in path's clone-order input,
16776 // since the OTP `RestForOne` restart strategy dispatches on
16777 // declared child order and axis reordering would silently
16778 // split the operator's per-cluster restart-fan-out order
16779 // from the caixa.lisp source-order).
16780 //
16781 // Second outer top-level [`Caixa`] `&[Composite]`-return slice
16782 // accessor pin on the substrate primitive for M2 / M3 typed-
16783 // slot vec-carry axes — folds on the outer-`Caixa`
16784 // `&[Composite]` composite-slice sub-family the sibling
16785 // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
16786 // (2a1f907) pin opened, peer at the outer altitude of the
16787 // closed inner-`SupervisorSpec` `SupervisorSpec::children`
16788 // (bc92bce) accessor on the same OTP-supervisor static-child-
16789 // list axis.
16790 use crate::supervisor::{ChildSpec, RestartPolicy};
16791 let fixtures: Vec<Vec<ChildSpec>> = vec![
16792 vec![],
16793 vec![ChildSpec {
16794 caixa: "worker".into(),
16795 versao: "^0.1".into(),
16796 restart: RestartPolicy::Permanent,
16797 }],
16798 vec![
16799 ChildSpec {
16800 caixa: "worker-a".into(),
16801 versao: "^0.1".into(),
16802 restart: RestartPolicy::Permanent,
16803 },
16804 ChildSpec {
16805 caixa: "worker-b".into(),
16806 versao: "^0.1".into(),
16807 restart: RestartPolicy::Transient,
16808 },
16809 ChildSpec {
16810 caixa: "worker-c".into(),
16811 versao: "^0.1".into(),
16812 restart: RestartPolicy::Temporary,
16813 },
16814 ],
16815 vec![
16816 ChildSpec {
16817 caixa: "w".into(),
16818 versao: "^0.1".into(),
16819 restart: RestartPolicy::Permanent,
16820 },
16821 ChildSpec {
16822 caixa: "w".into(),
16823 versao: "^0.1".into(),
16824 restart: RestartPolicy::Permanent,
16825 },
16826 ],
16827 ];
16828 for children in fixtures {
16829 let c = caixa_with_children(children.clone());
16830 assert_eq!(
16831 c.children(),
16832 children.as_slice(),
16833 "Caixa::children must return :children verbatim \
16834 (got {:?}, expected {children:?})",
16835 c.children(),
16836 );
16837 assert_eq!(
16838 c.children(),
16839 c.children.as_slice(),
16840 "Caixa::children must element-equal the raw \
16841 `self.children.as_slice()` field access across \
16842 every value in the Vec<ChildSpec> accept-set",
16843 );
16844 assert_eq!(
16845 c.children().is_empty(),
16846 c.children.is_empty(),
16847 "Caixa::children().is_empty() must byte-equal \
16848 self.children.is_empty() — a presence-bit drift \
16849 would silently split the paired \
16850 Caixa::declared_supervisor_slots supervisor-tree \
16851 declared-slot enumerator's presence probe from the \
16852 peer Caixa::supervisor_view typed-view composer's \
16853 fold-in path",
16854 );
16855 }
16856 }
16857
16858 #[test]
16859 fn declared_supervisor_slots_children_arm_routes_through_accessor() {
16860 // Composition pin: [`Caixa::declared_supervisor_slots`]'s
16861 // `:children` presence-probe arm must key off
16862 // [`Caixa::children`], not the raw
16863 // `!self.children.is_empty()` field-probe. Structurally: a
16864 // `Caixa { children: vec![ChildSpec { caixa: "w", versao:
16865 // "^0.1", restart: Permanent }], .. }` must push
16866 // `SUPERVISOR_AUTHOR_KEY_CHILDREN` onto the declared-slot list
16867 // (the presence bit is non-empty, so the supervisor-tree
16868 // kind-coherence gate must surface the slot as "declared"),
16869 // and a `Caixa { children: vec![], .. }` must NOT push the
16870 // label (the "author omitted the slot entirely" arm — the
16871 // empty-slice partition the serde-default folds onto). The
16872 // pair jointly pins the accessor + declared-slot enumerator
16873 // composition: any future silent detour that had the accessor
16874 // collapse `[Permanent]` to `[]` (a `.filter(|c| c.nome() !=
16875 // "__reserved__")` projection) would silently absorb the
16876 // "declared but degenerate" arm at the accessor boundary and
16877 // the [`crate::LayoutError::SupervisorSlotsOnNonSupervisor`]
16878 // kind-coherence gate would silently accept a struct-literal
16879 // `Caixa` carrying the drift.
16880 //
16881 // Peer of the sibling
16882 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
16883 // (2a1f907) on the M2 `:upgrade-from` composite-slice arm —
16884 // same "the enumerator gate must route through the substrate-
16885 // primitive typed dispatch" discipline extended onto the
16886 // supervisor-tree `:children` composite-slice arm.
16887 use crate::supervisor::{ChildSpec, RestartPolicy};
16888 let c = caixa_with_children(vec![ChildSpec {
16889 caixa: "w".into(),
16890 versao: "^0.1".into(),
16891 restart: RestartPolicy::Permanent,
16892 }]);
16893 let slots = c.declared_supervisor_slots();
16894 assert!(
16895 slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
16896 "declared_supervisor_slots must push \
16897 SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
16898 non-empty — the accessor and the enumerator gate must \
16899 route through the same substrate-primitive typed \
16900 dispatch on the outer :children presence bit (got \
16901 slots={slots:?})",
16902 );
16903 let c = caixa_with_children(vec![]);
16904 let slots = c.declared_supervisor_slots();
16905 assert!(
16906 !slots.contains(&crate::render::SUPERVISOR_AUTHOR_KEY_CHILDREN),
16907 "declared_supervisor_slots must NOT push \
16908 SUPERVISOR_AUTHOR_KEY_CHILDREN when `:children` is \
16909 empty — the author-omitted arm must route through the \
16910 accessor's empty-slice return unchanged (got \
16911 slots={slots:?})",
16912 );
16913 }
16914
16915 #[test]
16916 fn supervisor_view_children_arm_routes_through_accessor() {
16917 // Composition pin: [`Caixa::supervisor_view`]'s per-`:children`
16918 // fold-in arm must key off [`Caixa::children`], not the raw
16919 // `self.children.clone()` field-clone. Structurally: a `Caixa {
16920 // kind: Supervisor, estrategia: Some(OneForOne), children:
16921 // vec![ChildSpec { caixa: "w", .. }], .. }` must fold the
16922 // per-child list through the accessor into the typed
16923 // [`SupervisorSpec`] view's `children` field verbatim — every
16924 // entry the accessor surfaces must land in the view's
16925 // `children` slot in the same order. The pair jointly pins the
16926 // accessor + view-composer composition: any future silent
16927 // detour that had the accessor return a fresh-cloned
16928 // `Vec<ChildSpec>` copy would silently break the reference-
16929 // identity pin the peer `supervisor_view` fold-in path reads
16930 // from — the fold would clone once more per accessor call
16931 // instead of borrowing the storage buffer verbatim once.
16932 //
16933 // Peer of the sibling
16934 // `supervisor_view_kind_gate_routes_through_accessor` (35d8b52-
16935 // family) composition pin on the peer kind-gate arm — same
16936 // "the view composer must route through the substrate-
16937 // primitive typed dispatch" discipline extended onto the
16938 // per-`:children` fold-in arm, closing the supervisor-view
16939 // composer's routing invariant on the composite-slice input.
16940 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
16941 let mut c = caixa_with_children(vec![
16942 ChildSpec {
16943 caixa: "worker-a".into(),
16944 versao: "^0.1".into(),
16945 restart: RestartPolicy::Permanent,
16946 },
16947 ChildSpec {
16948 caixa: "worker-b".into(),
16949 versao: "^0.1".into(),
16950 restart: RestartPolicy::Transient,
16951 },
16952 ]);
16953 c.kind = crate::CaixaKind::Supervisor;
16954 c.estrategia = Some(RestartStrategy::OneForOne);
16955 let view = c
16956 .supervisor_view()
16957 .expect("Supervisor kind must produce a supervisor_view");
16958 assert_eq!(
16959 view.children(),
16960 c.children(),
16961 "supervisor_view must fold Caixa::children verbatim into \
16962 SupervisorSpec::children — the accessor and the view \
16963 composer must route through the same substrate-primitive \
16964 typed dispatch on the outer :children slice (got view \
16965 children={:?}, expected {:?})",
16966 view.children(),
16967 c.children(),
16968 );
16969 }
16970
16971 #[test]
16972 fn children_projects_slice_by_borrow() {
16973 // The by-borrow pin: [`Caixa::children`] returns
16974 // `&[ChildSpec]` by borrow — the returned slice borrows the
16975 // underlying `Vec<ChildSpec>` storage of the `:children` slot
16976 // and the accessor must not clone the backing `Vec` on every
16977 // call. Peer of the sibling outer top-level [`Caixa`]
16978 // `&[T]`-return by-borrow pins (`autores_projects_slice_by_borrow`
16979 // b5d813f, `etiquetas_projects_slice_by_borrow` 78c7d3c,
16980 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
16981 // `exe_projects_slice_by_borrow` 65d9527,
16982 // `servicos_projects_slice_by_borrow` 611f78b,
16983 // `deps_projects_slice_by_borrow` ad34b4e,
16984 // `deps_dev_projects_slice_by_borrow` f7fd81e,
16985 // `upgrade_from_projects_slice_by_borrow` 2a1f907) on the
16986 // sibling outer top-level [`Caixa`] scalar-element and
16987 // composite-element `&[T]` axes — folds on the outer-`Caixa`
16988 // composite-element `&[Composite]` axis: the accessor's
16989 // returned slice must borrow from `&self` (the returned
16990 // reference's lifetime is tied to `&self`), and calling the
16991 // accessor twice on the same [`Caixa`] must yield slices
16992 // that are pointer-equal (the underlying byte-buffer is the
16993 // storage `Vec`'s allocation, not a fresh copy) as well as
16994 // value-equal (idempotent, no side effects on `&self`).
16995 //
16996 // Pins against a future silent detour that returned an owned
16997 // `Vec<ChildSpec>` (which would type-check but silently clone
16998 // on every call), a `&Vec<ChildSpec>` return (which would leak
16999 // the backing `Vec`'s grow/push/reserve surface no downstream
17000 // consumer reaches for), or a one-arm-only accessor that
17001 // returned a saturating value on some sentinel input.
17002 use crate::supervisor::{ChildSpec, RestartPolicy};
17003 for children in [
17004 vec![],
17005 vec![ChildSpec {
17006 caixa: "w".into(),
17007 versao: "^0.1".into(),
17008 restart: RestartPolicy::Permanent,
17009 }],
17010 vec![
17011 ChildSpec {
17012 caixa: "worker-a".into(),
17013 versao: "^0.1".into(),
17014 restart: RestartPolicy::Permanent,
17015 },
17016 ChildSpec {
17017 caixa: "worker-b".into(),
17018 versao: "^0.1".into(),
17019 restart: RestartPolicy::Transient,
17020 },
17021 ],
17022 ] {
17023 let c = caixa_with_children(children.clone());
17024 let first = c.children();
17025 let second = c.children();
17026 assert_eq!(
17027 first, second,
17028 "Caixa::children must be idempotent — two successive \
17029 calls on the same &self must return the same \
17030 &[ChildSpec]",
17031 );
17032 assert_eq!(
17033 first.as_ptr(),
17034 second.as_ptr(),
17035 "Caixa::children must borrow the underlying \
17036 Vec<ChildSpec> storage — two successive calls must \
17037 return slices with the same backing pointer (a fresh \
17038 Vec<ChildSpec> clone would change the pointer on \
17039 every call)",
17040 );
17041 assert_eq!(
17042 first,
17043 children.as_slice(),
17044 "Caixa::children must return :children verbatim by \
17045 borrow — got {first:?}, expected {children:?}",
17046 );
17047 }
17048 }
17049
17050 // ── Caixa::membros — outer top-level &[Membro] composite-slice accessor ──
17051
17052 fn caixa_aplicacao_with_membros(membros: Vec<crate::aplicacao::Membro>) -> Caixa {
17053 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17054 c.kind = CaixaKind::Aplicacao;
17055 c.membros = membros;
17056 c
17057 }
17058
17059 #[test]
17060 fn membros_returns_membros_slice_verbatim_across_permutations() {
17061 // The canonical per-`Caixa` `:membros` M3 mesh-slot outer-
17062 // composite `&[Membro]`-return slice-shape pin:
17063 // [`Caixa::membros`] must return the `:membros` typed
17064 // `Vec<Membro>` verbatim as a `&[Membro]` slice-view over the
17065 // same backing buffer the raw `self.membros.as_slice()` field
17066 // access borrows from, element-equal across every
17067 // representative fixture in the accept-set — `[]` (the "no
17068 // members declared" arm every non-`Aplicacao`-kind `defcaixa`
17069 // carries by `#[serde(default)]` and every partially-authored
17070 // Aplicacao carries before the
17071 // [`crate::AplicacaoError::MembrosEmpty`] gate fires), a
17072 // canonical single-member fixture (the shape a minimal
17073 // Aplicacao carries — one Servico wrapping one contained
17074 // computation), a canonical multi-member list carrying three
17075 // distinct entries (the canonical checkout-shape Aplicacao —
17076 // cart / pricing / auth — every canonical example carries), and
17077 // a past-the-guard sentinel — a duplicate `:caixa`
17078 // `[("cart", ...), ("cart", ...)]` entry pair
17079 // ([`crate::AplicacaoSpec::validate`] rejects through
17080 // `DuplicateMembro { nome: "cart" }` but the accessor must ship
17081 // the raw slot verbatim so struct-literal fixtures continue to
17082 // expose the duplicate at the accessor boundary).
17083 //
17084 // Pins against a future silent detour that returned an owned
17085 // `Vec<Membro>` (which would type-check but silently clone on
17086 // every accessor call, breaking the zero-cost projection every
17087 // peer sibling slice accessor carries), a `[dup, dup] → [dup]`
17088 // dedup collapse (which would silently absorb the
17089 // `DuplicateMembro` refusal case at the accessor boundary and
17090 // the [`crate::StandardLayout::verify`] cross-member gate would
17091 // silently accept a struct-literal `Caixa` carrying the drift),
17092 // a reference to an operator-resolved overlay (the future per-
17093 // cluster `:membros-overrides` slot — its resolution must land
17094 // at exactly this accessor body, not silently divert the raw
17095 // slot away from a second consumer), or an axis-shuffled
17096 // projection (a future detour that reordered members through
17097 // the accessor would silently split the paired
17098 // [`crate::StandardLayout::verify`] per-Aplicacao gate's
17099 // traversal input from the peer [`Self::aplicacao_view`] fold-
17100 // in path's clone-order input, since the canonical `:contratos`
17101 // `:de`/`:para` and `:entrada :para` cross-slot refusal probes
17102 // read the member set through the same slice).
17103 //
17104 // Third outer top-level [`Caixa`] `&[Composite]`-return slice
17105 // accessor pin on the substrate primitive for M2 / M3 typed-
17106 // slot vec-carry axes — opens the outer-`Caixa` M3 mesh-slot
17107 // arm of the `&[Composite]` composite-slice sub-family the
17108 // sibling M2 `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
17109 // (2a1f907) and
17110 // `children_returns_children_slice_verbatim_across_permutations`
17111 // (c17b51e) pins opened, peer at the outer altitude of the
17112 // closed inner-[`crate::AplicacaoSpec::membros`] (6c77e36)
17113 // accessor on the same MESH-COMPOSITION per-Aplicacao member-
17114 // list axis.
17115 use crate::aplicacao::Membro;
17116 let fixtures: Vec<Vec<Membro>> = vec![
17117 vec![],
17118 vec![Membro {
17119 caixa: "cart".into(),
17120 versao: "^0.1".into(),
17121 }],
17122 vec![
17123 Membro {
17124 caixa: "cart".into(),
17125 versao: "^0.1".into(),
17126 },
17127 Membro {
17128 caixa: "pricing".into(),
17129 versao: "^0.2".into(),
17130 },
17131 Membro {
17132 caixa: "auth".into(),
17133 versao: "^1.0".into(),
17134 },
17135 ],
17136 vec![
17137 Membro {
17138 caixa: "cart".into(),
17139 versao: "^0.1".into(),
17140 },
17141 Membro {
17142 caixa: "cart".into(),
17143 versao: "^0.1".into(),
17144 },
17145 ],
17146 ];
17147 for membros in fixtures {
17148 let c = caixa_aplicacao_with_membros(membros.clone());
17149 assert_eq!(
17150 c.membros(),
17151 membros.as_slice(),
17152 "Caixa::membros must return :membros verbatim \
17153 (got {:?}, expected {membros:?})",
17154 c.membros(),
17155 );
17156 assert_eq!(
17157 c.membros(),
17158 c.membros.as_slice(),
17159 "Caixa::membros must element-equal the raw \
17160 `self.membros.as_slice()` field access across every \
17161 value in the Vec<Membro> accept-set",
17162 );
17163 assert_eq!(
17164 c.membros().is_empty(),
17165 c.membros.is_empty(),
17166 "Caixa::membros().is_empty() must byte-equal \
17167 self.membros.is_empty() — a presence-bit drift would \
17168 silently split the paired Caixa::declared_mesh_slots \
17169 mesh declared-slot enumerator's presence probe from \
17170 the peer Caixa::aplicacao_view typed-view composer's \
17171 fold-in path",
17172 );
17173 }
17174 }
17175
17176 #[test]
17177 fn declared_mesh_slots_membros_arm_routes_through_accessor() {
17178 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:membros`
17179 // presence-probe arm must key off [`Caixa::membros`], not the
17180 // raw `!self.membros.is_empty()` field-probe. Structurally: a
17181 // `Caixa { membros: vec![Membro { caixa: "cart", versao:
17182 // "^0.1" }], .. }` must push `M3_AUTHOR_KEY_MEMBROS` onto the
17183 // declared-slot list (the presence bit is non-empty, so the
17184 // mesh kind-coherence gate must surface the slot as
17185 // "declared"), and a `Caixa { membros: vec![], .. }` must NOT
17186 // push the label (the "author omitted the slot entirely" arm
17187 // — the empty-slice partition the serde-default folds onto).
17188 // The pair jointly pins the accessor + declared-slot
17189 // enumerator composition: any future silent detour that had
17190 // the accessor collapse `[Membro { .. }]` to `[]` (a
17191 // `.filter(|m| m.nome() != "__reserved__")` projection) would
17192 // silently absorb the "declared but degenerate" arm at the
17193 // accessor boundary and the
17194 // [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
17195 // coherence gate would silently accept a struct-literal
17196 // `Caixa` carrying the drift.
17197 //
17198 // Peer of the sibling
17199 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
17200 // (2a1f907) and
17201 // `declared_supervisor_slots_children_arm_routes_through_accessor`
17202 // (c17b51e) composition pins on the M2 `:upgrade-from` /
17203 // `:children` composite-slice arms — same "the enumerator gate
17204 // must route through the substrate-primitive typed dispatch"
17205 // discipline extended onto the M3 `:membros` composite-slice
17206 // arm, opening the M3 arm of the declared-slot enumerator's
17207 // routing invariant.
17208 use crate::aplicacao::Membro;
17209 let c = caixa_aplicacao_with_membros(vec![Membro {
17210 caixa: "cart".into(),
17211 versao: "^0.1".into(),
17212 }]);
17213 let slots = c.declared_mesh_slots();
17214 assert!(
17215 slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
17216 "declared_mesh_slots must push M3_AUTHOR_KEY_MEMBROS when \
17217 `:membros` is non-empty — the accessor and the enumerator \
17218 gate must route through the same substrate-primitive \
17219 typed dispatch on the outer :membros presence bit (got \
17220 slots={slots:?})",
17221 );
17222 let c = caixa_aplicacao_with_membros(vec![]);
17223 let slots = c.declared_mesh_slots();
17224 assert!(
17225 !slots.contains(&crate::render::M3_AUTHOR_KEY_MEMBROS),
17226 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_MEMBROS \
17227 when `:membros` is empty — the author-omitted arm must \
17228 route through the accessor's empty-slice return unchanged \
17229 (got slots={slots:?})",
17230 );
17231 }
17232
17233 #[test]
17234 fn aplicacao_view_membros_arm_routes_through_accessor() {
17235 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:membros`
17236 // fold-in arm must key off [`Caixa::membros`], not the raw
17237 // `self.membros.clone()` field-clone. Structurally: a `Caixa {
17238 // kind: Aplicacao, membros: vec![Membro { caixa: "cart", .. },
17239 // Membro { caixa: "pricing", .. }], .. }` must fold the per-
17240 // member list through the accessor into the typed
17241 // [`crate::AplicacaoSpec`] view's `membros` slot verbatim —
17242 // every entry the accessor surfaces must land in the view's
17243 // `membros` slot in the same order. The pair jointly pins the
17244 // accessor + view-composer composition: any future silent
17245 // detour that had the accessor return a fresh-cloned
17246 // `Vec<Membro>` copy would silently break the reference-
17247 // identity pin the peer `aplicacao_view` fold-in path reads
17248 // from — the fold would clone once more per accessor call
17249 // instead of borrowing the storage buffer verbatim once.
17250 //
17251 // Peer of the sibling
17252 // `aplicacao_view_politicas_arm_folds_through_accessor`
17253 // (5d23d29) /
17254 // `aplicacao_view_placement_arm_folds_through_accessor`
17255 // (4fb8074) /
17256 // `aplicacao_view_entrada_arm_folds_through_accessor` (e4128e4)
17257 // composition pins on the M3 `:politicas` / `:placement` /
17258 // `:entrada` outer-`Option<&Composite>` arms — extended here to
17259 // the M3 `:membros` outer-`&[Composite]` composite-slice arm,
17260 // closing the aplicacao-view composer's routing invariant on
17261 // the composite-slice input.
17262 use crate::aplicacao::Membro;
17263 let c = caixa_aplicacao_with_membros(vec![
17264 Membro {
17265 caixa: "cart".into(),
17266 versao: "^0.1".into(),
17267 },
17268 Membro {
17269 caixa: "pricing".into(),
17270 versao: "^0.2".into(),
17271 },
17272 ]);
17273 let view = c
17274 .aplicacao_view()
17275 .expect("Aplicacao kind must produce an aplicacao_view");
17276 assert_eq!(
17277 view.membros(),
17278 c.membros(),
17279 "aplicacao_view must fold Caixa::membros verbatim into \
17280 AplicacaoSpec::membros — the accessor and the view \
17281 composer must route through the same substrate-primitive \
17282 typed dispatch on the outer :membros slice (got view \
17283 membros={:?}, expected {:?})",
17284 view.membros(),
17285 c.membros(),
17286 );
17287 }
17288
17289 #[test]
17290 fn membros_projects_slice_by_borrow() {
17291 // The by-borrow pin: [`Caixa::membros`] returns `&[Membro]` by
17292 // borrow — the returned slice borrows the underlying
17293 // `Vec<Membro>` storage of the `:membros` slot and the
17294 // accessor must not clone the backing `Vec` on every call.
17295 // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
17296 // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
17297 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
17298 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
17299 // `exe_projects_slice_by_borrow` 65d9527,
17300 // `servicos_projects_slice_by_borrow` 611f78b,
17301 // `deps_projects_slice_by_borrow` ad34b4e,
17302 // `deps_dev_projects_slice_by_borrow` f7fd81e,
17303 // `upgrade_from_projects_slice_by_borrow` 2a1f907,
17304 // `children_projects_slice_by_borrow` c17b51e) on the sibling
17305 // outer top-level [`Caixa`] scalar-element and composite-
17306 // element `&[T]` axes — folds on the outer-`Caixa` M3 mesh-
17307 // slot composite-element `&[Composite]` axis: the accessor's
17308 // returned slice must borrow from `&self` (the returned
17309 // reference's lifetime is tied to `&self`), and calling the
17310 // accessor twice on the same [`Caixa`] must yield slices that
17311 // are pointer-equal (the underlying byte-buffer is the storage
17312 // `Vec`'s allocation, not a fresh copy) as well as value-equal
17313 // (idempotent, no side effects on `&self`).
17314 //
17315 // Pins against a future silent detour that returned an owned
17316 // `Vec<Membro>` (which would type-check but silently clone on
17317 // every call), a `&Vec<Membro>` return (which would leak the
17318 // backing `Vec`'s grow/push/reserve surface no downstream
17319 // consumer reaches for), or a one-arm-only accessor that
17320 // returned a saturating value on some sentinel input.
17321 use crate::aplicacao::Membro;
17322 for membros in [
17323 vec![],
17324 vec![Membro {
17325 caixa: "cart".into(),
17326 versao: "^0.1".into(),
17327 }],
17328 vec![
17329 Membro {
17330 caixa: "cart".into(),
17331 versao: "^0.1".into(),
17332 },
17333 Membro {
17334 caixa: "pricing".into(),
17335 versao: "^0.2".into(),
17336 },
17337 ],
17338 ] {
17339 let c = caixa_aplicacao_with_membros(membros.clone());
17340 let first = c.membros();
17341 let second = c.membros();
17342 assert_eq!(
17343 first, second,
17344 "Caixa::membros must be idempotent — two successive \
17345 calls on the same &self must return the same &[Membro]",
17346 );
17347 assert_eq!(
17348 first.as_ptr(),
17349 second.as_ptr(),
17350 "Caixa::membros must borrow the underlying Vec<Membro> \
17351 storage — two successive calls must return slices with \
17352 the same backing pointer (a fresh Vec<Membro> clone \
17353 would change the pointer on every call)",
17354 );
17355 assert_eq!(
17356 first,
17357 membros.as_slice(),
17358 "Caixa::membros must return :membros verbatim by borrow \
17359 — got {first:?}, expected {membros:?}",
17360 );
17361 }
17362 }
17363
17364 // ── Caixa::contratos — outer top-level &[WitContract] composite-slice accessor ──
17365
17366 fn caixa_aplicacao_with_contratos(contratos: Vec<crate::aplicacao::WitContract>) -> Caixa {
17367 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17368 c.kind = CaixaKind::Aplicacao;
17369 c.contratos = contratos;
17370 c
17371 }
17372
17373 fn contrato_http_for_test(
17374 de: &str,
17375 para: &str,
17376 endpoint: &str,
17377 ) -> crate::aplicacao::WitContract {
17378 crate::aplicacao::WitContract {
17379 de: de.into(),
17380 para: para.into(),
17381 wit: "wasi:http/proxy".into(),
17382 endpoint: Some(endpoint.into()),
17383 subject: None,
17384 slot: None,
17385 }
17386 }
17387
17388 #[test]
17389 fn contratos_returns_contratos_slice_verbatim_across_permutations() {
17390 // The canonical per-`Caixa` `:contratos` M3 mesh-slot outer-
17391 // composite `&[WitContract]`-return slice-shape pin:
17392 // [`Caixa::contratos`] must return the `:contratos` typed
17393 // `Vec<WitContract>` verbatim as a `&[WitContract]` slice-view
17394 // over the same backing buffer the raw
17395 // `self.contratos.as_slice()` field access borrows from,
17396 // element-equal across every representative fixture in the
17397 // accept-set — `[]` (the "no contracts declared" arm every
17398 // non-`Aplicacao`-kind `defcaixa` carries by
17399 // `#[serde(default)]` and every leaf-Aplicacao with a single
17400 // member carries), a canonical single-edge fixture (the
17401 // minimal directed-graph shape: one HTTP-shape `(cart → catalog)`
17402 // edge), and a canonical multi-edge fixture with three distinct
17403 // edges (the checkout-shape Aplicacao's HTTP-fan pattern:
17404 // `(cart → catalog)`, `(cart → pricing)`, `(cart → auth)`).
17405 //
17406 // Pins against a future silent detour that returned an owned
17407 // `Vec<WitContract>` (which would type-check but silently clone
17408 // on every accessor call, breaking the zero-cost projection
17409 // every peer sibling slice accessor carries), an axis-shuffled
17410 // projection (a future detour that reordered edges through the
17411 // accessor would silently split the paired
17412 // [`crate::StandardLayout::verify`] per-Aplicacao gate's
17413 // traversal input from the peer [`Self::aplicacao_view`] fold-
17414 // in path's clone-order input, since every canonical
17415 // `caixa-mesh` renderer's per-`(:de, :para)` adjacency-list
17416 // seed dispatch reads the edge set through the same slice),
17417 // or a reference to an operator-resolved overlay (the future
17418 // per-cluster `:contratos-overrides` slot — its resolution
17419 // must land at exactly this accessor body, not silently divert
17420 // the raw slot away from a second consumer).
17421 //
17422 // Fourth outer top-level [`Caixa`] `&[Composite]`-return slice
17423 // accessor pin on the substrate primitive for M2 / M3 typed-
17424 // slot vec-carry axes — closes the outer-`Caixa`
17425 // `&[Composite]` composite-slice sub-family the sibling M2
17426 // `upgrade_from_returns_upgrade_from_slice_verbatim_across_permutations`
17427 // (2a1f907) and
17428 // `children_returns_children_slice_verbatim_across_permutations`
17429 // (c17b51e) pins opened and the M3
17430 // `membros_returns_membros_slice_verbatim_across_permutations`
17431 // (0f26987) pin folded on, closing the outer-`Caixa` M3 mesh-
17432 // slot arm of the composite-slice sub-family. Peer at the outer
17433 // altitude of the closed inner-
17434 // [`crate::AplicacaoSpec::contratos`] (0dcc926) accessor on the
17435 // same MESH-COMPOSITION per-Aplicacao contract-list axis.
17436 let fixtures: Vec<Vec<crate::aplicacao::WitContract>> = vec![
17437 vec![],
17438 vec![contrato_http_for_test("cart", "catalog", "/items")],
17439 vec![
17440 contrato_http_for_test("cart", "catalog", "/items"),
17441 contrato_http_for_test("cart", "pricing", "/price"),
17442 contrato_http_for_test("cart", "auth", "/whoami"),
17443 ],
17444 ];
17445 for contratos in fixtures {
17446 let c = caixa_aplicacao_with_contratos(contratos.clone());
17447 assert_eq!(
17448 c.contratos(),
17449 contratos.as_slice(),
17450 "Caixa::contratos must return :contratos verbatim \
17451 (got {:?}, expected {contratos:?})",
17452 c.contratos(),
17453 );
17454 assert_eq!(
17455 c.contratos(),
17456 c.contratos.as_slice(),
17457 "Caixa::contratos must element-equal the raw \
17458 `self.contratos.as_slice()` field access across every \
17459 value in the Vec<WitContract> accept-set",
17460 );
17461 assert_eq!(
17462 c.contratos().is_empty(),
17463 c.contratos.is_empty(),
17464 "Caixa::contratos().is_empty() must byte-equal \
17465 self.contratos.is_empty() — a presence-bit drift would \
17466 silently split the paired Caixa::declared_mesh_slots \
17467 mesh declared-slot enumerator's presence probe from \
17468 the peer Caixa::aplicacao_view typed-view composer's \
17469 fold-in path",
17470 );
17471 }
17472 }
17473
17474 #[test]
17475 fn declared_mesh_slots_contratos_arm_routes_through_accessor() {
17476 // Composition pin: [`Caixa::declared_mesh_slots`]'s `:contratos`
17477 // presence-probe arm must key off [`Caixa::contratos`], not the
17478 // raw `!self.contratos.is_empty()` field-probe. Structurally: a
17479 // `Caixa { contratos: vec![WitContract { .. }], .. }` must push
17480 // `M3_AUTHOR_KEY_CONTRATOS` onto the declared-slot list (the
17481 // presence bit is non-empty, so the mesh kind-coherence gate
17482 // must surface the slot as "declared"), and a `Caixa {
17483 // contratos: vec![], .. }` must NOT push the label (the "author
17484 // omitted the slot entirely" arm — the empty-slice partition
17485 // the serde-default folds onto). The pair jointly pins the
17486 // accessor + declared-slot enumerator composition: any future
17487 // silent detour that had the accessor collapse
17488 // `[WitContract { .. }]` to `[]` (a `.filter(|c| c.de() !=
17489 // "__reserved__")` projection) would silently absorb the
17490 // "declared but degenerate" arm at the accessor boundary and
17491 // the [`crate::LayoutError::MeshSlotsOnNonAplicacao`] kind-
17492 // coherence gate would silently accept a struct-literal
17493 // `Caixa` carrying the drift.
17494 //
17495 // Peer of the sibling
17496 // `declared_servico_slots_upgrade_from_arm_routes_through_accessor`
17497 // (2a1f907),
17498 // `declared_supervisor_slots_children_arm_routes_through_accessor`
17499 // (c17b51e), and
17500 // `declared_mesh_slots_membros_arm_routes_through_accessor`
17501 // (0f26987) composition pins on the M2 `:upgrade-from` /
17502 // `:children` / M3 `:membros` composite-slice arms — same "the
17503 // enumerator gate must route through the substrate-primitive
17504 // typed dispatch" discipline extended onto the M3 `:contratos`
17505 // composite-slice arm, closing the M3 mesh-slot arm of the
17506 // declared-slot enumerator's routing invariant on the
17507 // composite-slice inputs.
17508 let c = caixa_aplicacao_with_contratos(vec![contrato_http_for_test(
17509 "cart", "catalog", "/items",
17510 )]);
17511 let slots = c.declared_mesh_slots();
17512 assert!(
17513 slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
17514 "declared_mesh_slots must push M3_AUTHOR_KEY_CONTRATOS when \
17515 `:contratos` is non-empty — the accessor and the enumerator \
17516 gate must route through the same substrate-primitive \
17517 typed dispatch on the outer :contratos presence bit (got \
17518 slots={slots:?})",
17519 );
17520 let c = caixa_aplicacao_with_contratos(vec![]);
17521 let slots = c.declared_mesh_slots();
17522 assert!(
17523 !slots.contains(&crate::render::M3_AUTHOR_KEY_CONTRATOS),
17524 "declared_mesh_slots must NOT push M3_AUTHOR_KEY_CONTRATOS \
17525 when `:contratos` is empty — the author-omitted arm must \
17526 route through the accessor's empty-slice return unchanged \
17527 (got slots={slots:?})",
17528 );
17529 }
17530
17531 #[test]
17532 fn aplicacao_view_contratos_arm_routes_through_accessor() {
17533 // Composition pin: [`Caixa::aplicacao_view`]'s per-`:contratos`
17534 // fold-in arm must key off [`Caixa::contratos`], not the raw
17535 // `self.contratos.clone()` field-clone. Structurally: a `Caixa
17536 // { kind: Aplicacao, contratos: vec![WitContract { de: "cart",
17537 // .. }, WitContract { de: "pricing", .. }], .. }` must fold the
17538 // per-edge list through the accessor into the typed
17539 // [`crate::AplicacaoSpec`] view's `contratos` slot verbatim —
17540 // every entry the accessor surfaces must land in the view's
17541 // `contratos` slot in the same order. The pair jointly pins
17542 // the accessor + view-composer composition: a future silent
17543 // detour that had the accessor shuffle or drop an edge would
17544 // silently split the paired declared-slot enumerator's
17545 // presence bit from the typed-view composer's edge-list, a
17546 // two-consumer split at the enumerator and the view composer
17547 // far from the source `caixa.lisp`.
17548 //
17549 // Peer of the sibling
17550 // `aplicacao_view_membros_arm_routes_through_accessor`
17551 // (0f26987) composition pin on the M3 `:membros` outer-
17552 // `&[Composite]` composite-slice arm, closing the aplicacao-
17553 // view composer's routing invariant on the composite-slice
17554 // inputs at the outer altitude.
17555 let c = caixa_aplicacao_with_contratos(vec![
17556 contrato_http_for_test("cart", "catalog", "/items"),
17557 contrato_http_for_test("cart", "pricing", "/price"),
17558 ]);
17559 let view = c
17560 .aplicacao_view()
17561 .expect("Aplicacao kind must produce an aplicacao_view");
17562 assert_eq!(
17563 view.contratos(),
17564 c.contratos(),
17565 "aplicacao_view must fold Caixa::contratos verbatim into \
17566 AplicacaoSpec::contratos — the accessor and the view \
17567 composer must route through the same substrate-primitive \
17568 typed dispatch on the outer :contratos slice (got view \
17569 contratos={:?}, expected {:?})",
17570 view.contratos(),
17571 c.contratos(),
17572 );
17573 }
17574
17575 #[test]
17576 fn contratos_projects_slice_by_borrow() {
17577 // The by-borrow pin: [`Caixa::contratos`] returns `&[WitContract]`
17578 // by borrow — the returned slice borrows the underlying
17579 // `Vec<WitContract>` storage of the `:contratos` slot and the
17580 // accessor must not clone the backing `Vec` on every call.
17581 // Peer of the sibling outer top-level [`Caixa`] `&[T]`-return
17582 // by-borrow pins (`autores_projects_slice_by_borrow` b5d813f,
17583 // `etiquetas_projects_slice_by_borrow` 78c7d3c,
17584 // `bibliotecas_projects_slice_by_borrow` 8a36c23,
17585 // `exe_projects_slice_by_borrow` 65d9527,
17586 // `servicos_projects_slice_by_borrow` 611f78b,
17587 // `deps_projects_slice_by_borrow` ad34b4e,
17588 // `deps_dev_projects_slice_by_borrow` f7fd81e,
17589 // `upgrade_from_projects_slice_by_borrow` 2a1f907,
17590 // `children_projects_slice_by_borrow` c17b51e,
17591 // `membros_projects_slice_by_borrow` 0f26987) on the sibling
17592 // outer top-level [`Caixa`] scalar-element and composite-
17593 // element `&[T]` axes — closes the outer-`Caixa` M3 mesh-slot
17594 // composite-element `&[Composite]` axis on the by-borrow pin:
17595 // the accessor's returned slice must borrow from `&self` (the
17596 // returned reference's lifetime is tied to `&self`), and
17597 // calling the accessor twice on the same [`Caixa`] must yield
17598 // slices that are pointer-equal (the underlying byte-buffer is
17599 // the storage `Vec`'s allocation, not a fresh copy) as well as
17600 // value-equal (idempotent, no side effects on `&self`).
17601 //
17602 // Pins against a future silent detour that returned an owned
17603 // `Vec<WitContract>` (which would type-check but silently clone
17604 // on every call), a `&Vec<WitContract>` return (which would
17605 // leak the backing `Vec`'s grow/push/reserve surface no
17606 // downstream consumer reaches for), or a one-arm-only accessor
17607 // that returned a saturating value on some sentinel input.
17608 for contratos in [
17609 vec![],
17610 vec![contrato_http_for_test("cart", "catalog", "/items")],
17611 vec![
17612 contrato_http_for_test("cart", "catalog", "/items"),
17613 contrato_http_for_test("cart", "pricing", "/price"),
17614 ],
17615 ] {
17616 let c = caixa_aplicacao_with_contratos(contratos.clone());
17617 let first = c.contratos();
17618 let second = c.contratos();
17619 assert_eq!(
17620 first, second,
17621 "Caixa::contratos must be idempotent — two successive \
17622 calls on the same &self must return the same \
17623 &[WitContract]",
17624 );
17625 assert_eq!(
17626 first.as_ptr(),
17627 second.as_ptr(),
17628 "Caixa::contratos must borrow the underlying \
17629 Vec<WitContract> storage — two successive calls must \
17630 return slices with the same backing pointer (a fresh \
17631 Vec<WitContract> clone would change the pointer on \
17632 every call)",
17633 );
17634 assert_eq!(
17635 first,
17636 contratos.as_slice(),
17637 "Caixa::contratos must return :contratos verbatim by \
17638 borrow — got {first:?}, expected {contratos:?}",
17639 );
17640 }
17641 }
17642
17643 // ── drift-detection: Caixa top-level multi-word serde-derive-to-const identity ──
17644
17645 #[test]
17646 fn caixa_multi_word_serde_keys_match_lifted_top_level_key_consts() {
17647 // Load-bearing invariant: every multi-word top-level [`Caixa`]
17648 // serde-derived JSON key routes through a lifted `&'static str`
17649 // const. The Rust field names are `snake_case`
17650 // (`deps_dev` / `upgrade_from` / `max_restarts` /
17651 // `restart_window`); [`Caixa`]'s `#[serde(rename_all =
17652 // "camelCase")]` derive attribute maps each to the camelCase
17653 // byte-string the [`Caixa::to_lisp`] round-trip's
17654 // `serde_json::to_value(self)` step lands under before
17655 // `tatara_lisp::domain::json_to_sexp` re-projects the JSON keys
17656 // to the kebab-case `:deps-dev` / `:upgrade-from` /
17657 // `:max-restarts` / `:restart-window` author surface. Serialize
17658 // a fully-populated [`Caixa`] and pin that each canonical
17659 // byte-sequence appears verbatim in the JSON — a future
17660 // accidental `rename_all = "snake_case"` / `"kebab-case"` /
17661 // verbatim-field-name flip at the derive attribute (any of
17662 // which would silently break every [`Caixa::to_lisp`]
17663 // round-trip and the future M4 operator-side manifest ingest's
17664 // `Value::get(<key>)` navigation) surfaces here as a build-time
17665 // test failure at `manifest.rs`, not as an apply-time
17666 // `.get(<stale-canonical-const>)` returning `None` far from the
17667 // derive-attr drift's commit. Same discipline the sibling
17668 // `supervisor_spec_serde_keys_match_lifted_supervisor_key_consts`
17669 // (40cc4e5), `membro_serde_keys_match_lifted_membro_key_consts`
17670 // (ce80ca0), and `upgrade_from_entry_serde_keys_match_lifted_
17671 // m2_upgrade_from_key_consts` (36ffe65) pins established on the
17672 // sibling M2 supervision-tree, M3 [`Membro`] per-entry, and M2
17673 // [`UpgradeFromEntry`] per-entry axes — extended here to the
17674 // enclosing M0 [`Caixa`] top-level axis so the last of the four
17675 // multi-word top-level [`Caixa`] serde-derived JSON keys
17676 // (`depsDev`) joins the substrate's "one canonical byte-string
17677 // per typed serialized-key axis" discipline.
17678 use crate::supervisor::{ChildSpec, RestartPolicy, RestartStrategy};
17679 use crate::upgrade::{UpgradeFromEntry, UpgradeInstruction};
17680 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17681 c.deps_dev = vec![Dep::simple("tatara-check", "^0.1")];
17682 c.upgrade_from = vec![UpgradeFromEntry {
17683 from: "0.0.1".into(),
17684 instructions: vec![UpgradeInstruction::Restart],
17685 }];
17686 c.estrategia = Some(RestartStrategy::OneForOne);
17687 c.max_restarts = Some(3);
17688 c.restart_window = Some("60s".into());
17689 c.children = vec![ChildSpec {
17690 caixa: "child".into(),
17691 versao: "^0.1".into(),
17692 restart: RestartPolicy::Permanent,
17693 }];
17694 let json = serde_json::to_string(&c).unwrap();
17695 for key in [
17696 crate::render::CAIXA_KEY_DEPS_DEV,
17697 crate::render::M2_KEY_UPGRADE_FROM,
17698 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
17699 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
17700 ] {
17701 let quoted = format!("\"{key}\"");
17702 assert!(
17703 json.contains("ed),
17704 "serialized Caixa must carry the lifted top-level \
17705 multi-word byte-sequence {quoted} verbatim in the JSON \
17706 emission (got: {json})",
17707 );
17708 }
17709 }
17710
17711 #[test]
17712 fn caixa_top_level_multi_word_key_consts_are_pairwise_distinct() {
17713 // Cross-axis drift-detection pin: a future collapse of the four
17714 // canonical [`Caixa`] top-level multi-word byte-strings onto the
17715 // same value (e.g. an accidental copy-paste flip of
17716 // [`crate::render::CAIXA_KEY_DEPS_DEV`] to also read
17717 // `"upgradeFrom"`) would silently reroute every downstream
17718 // `Value::get(<key>)` probe on one axis onto the sibling axis's
17719 // top-level entry and pass every propagation-probe test that
17720 // expected only the stale axis's value. Peer of the sibling
17721 // four-way distinct pin on the `SUPERVISOR_KEY_*` tetrad
17722 // (40cc4e5) and the two-way pin on `MEMBRO_KEY_*` (ce80ca0).
17723 let all = [
17724 crate::render::CAIXA_KEY_DEPS_DEV,
17725 crate::render::M2_KEY_UPGRADE_FROM,
17726 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
17727 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
17728 ];
17729 for (i, a) in all.iter().enumerate() {
17730 for b in all.iter().skip(i + 1) {
17731 assert_ne!(
17732 a, b,
17733 "Caixa top-level multi-word key consts must be \
17734 pairwise-distinct canonical byte-sequences — got \
17735 `{a}` == `{b}`",
17736 );
17737 }
17738 }
17739 }
17740
17741 #[test]
17742 fn caixa_top_level_multi_word_key_consts_are_lower_camel_case_shape() {
17743 // Shape-pin: every [`Caixa`] top-level multi-word key const must
17744 // be a lowerCamelCase byte-sequence (no `snake_case`
17745 // underscores, no `kebab-case` hyphens, no leading colon, no
17746 // `PascalCase` leading capital, no whitespace / dots) — the
17747 // canonical shape the `#[serde(rename_all = "camelCase")]`
17748 // derive produces on [`Caixa`]. A future flip to a
17749 // non-camelCase attribute at the derive surfaces both here
17750 // (this test fails on the stale-constant shape) and at
17751 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
17752 // (that test fails on the mismatch between const and derive).
17753 // Peer with `membro_key_consts_are_lower_camel_case_shape`
17754 // (ce80ca0) and `supervisor_key_consts_are_lower_camel_case_shape`
17755 // (40cc4e5) on the sibling per-entry / supervisor-tree axes.
17756 for key in [
17757 crate::render::CAIXA_KEY_DEPS_DEV,
17758 crate::render::M2_KEY_UPGRADE_FROM,
17759 crate::render::SUPERVISOR_KEY_MAX_RESTARTS,
17760 crate::render::SUPERVISOR_KEY_RESTART_WINDOW,
17761 ] {
17762 assert!(
17763 !key.is_empty(),
17764 "Caixa top-level multi-word key const must be non-empty \
17765 (got {key:?})"
17766 );
17767 let first = key.chars().next().unwrap();
17768 assert!(
17769 first.is_ascii_lowercase(),
17770 "Caixa top-level multi-word key const must lead with an \
17771 ASCII-lowercase byte (got {key:?}, leads with {first:?})",
17772 );
17773 assert!(
17774 key.chars().all(|c| c.is_ascii_alphanumeric()),
17775 "Caixa top-level multi-word key const must be \
17776 ASCII-alphanumeric only — no `_` / `-` / `:` / `.` / \
17777 whitespace (got {key:?})",
17778 );
17779 }
17780 }
17781
17782 #[test]
17783 fn caixa_key_deps_dev_pins_canonical_camel_case_byte_string() {
17784 // Scalar-value pin: the byte-string the
17785 // [`crate::render::CAIXA_KEY_DEPS_DEV`] const resolves to,
17786 // asserted verbatim. A future rebrand (`depsDev` → `devDeps`
17787 // matching Cargo's verbatim `dev-dependencies` axis, `depsDev`
17788 // → `depsTest` matching a hypothetical per-test-target
17789 // vocabulary flip) lands as an edit to exactly one const AND
17790 // one derive attribute — the sibling
17791 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
17792 // pin already ties the const to the derive attribute, so a
17793 // rebrand that touches only one side of the pair fails at
17794 // caixa-core build time. Same "scalar-value pin per const"
17795 // discipline the sibling
17796 // `m2_top_level_author_key_consts_pin_canonical_kebab_case_labels`
17797 // (f49c8b0) and `contrato_key_consts_pin_canonical_camel_case_labels`
17798 // (ca463a4) pins carry on the peer M2 / M3 top-level slot axes.
17799 assert_eq!(crate::render::CAIXA_KEY_DEPS_DEV, "depsDev");
17800 }
17801
17802 #[test]
17803 fn caixa_key_deps_pins_canonical_byte_string() {
17804 // Scalar-value pin: the byte-string the
17805 // [`crate::render::CAIXA_KEY_DEPS`] const resolves to, asserted
17806 // verbatim. Peer of `caixa_key_deps_dev_pins_canonical_camel_case_byte_string`
17807 // on the two-list dep-graph serialized-key axis — the sibling
17808 // pin covers the multi-word `deps_dev → depsDev` camelCase
17809 // arm, this pin covers the single-word `deps → deps` no-op arm
17810 // (the [`crate::Caixa::deps`] field name carries no `_`, so the
17811 // `#[serde(rename_all = "camelCase")]` derive is a no-op on this
17812 // axis and the emitted JSON key equals the source-side field
17813 // name byte-for-byte). A future [`crate::Caixa::deps`] field
17814 // rename (`deps` → `dependencies` matching Cargo's verbatim
17815 // `[dependencies]` axis, `deps` → `runtime_deps` matching a
17816 // hypothetical per-runtime-target vocabulary flip) OR an added
17817 // `#[serde(rename = "…")]` explicit override lands as an edit
17818 // to exactly one const AND one derive-attr / field name — the
17819 // sibling `caixa_deps_serde_key_matches_lifted_caixa_key_deps`
17820 // pin ties the const to the emitted JSON key, so a rebrand
17821 // that touches only one side of the pair fails at caixa-core
17822 // build time.
17823 assert_eq!(crate::render::CAIXA_KEY_DEPS, "deps");
17824 }
17825
17826 #[test]
17827 fn caixa_deps_serde_key_matches_lifted_caixa_key_deps() {
17828 // Load-bearing invariant on the single-word `deps` top-level
17829 // axis: the byte-string [`crate::render::CAIXA_KEY_DEPS`] pins
17830 // must appear verbatim in the JSON [`Caixa::to_lisp`]'s
17831 // `serde_json::to_value(self)` step emits. Serialize a
17832 // populated [`Caixa`] whose `:deps` slot carries at least one
17833 // entry (the `#[serde(default)]` attribute on the field emits
17834 // an empty `[]` even without members, but a non-empty vec
17835 // additionally covers the codec's per-`Dep`-entry emission
17836 // path) and pin that `"deps"` appears verbatim in the JSON
17837 // emission — a future accidental `rename_all = "snake_case"` /
17838 // `"kebab-case"` flip at the derive attribute (or an added
17839 // `#[serde(rename = "…")]` explicit override on the field, or
17840 // a Rust field rename) would break every [`Caixa::to_lisp`]
17841 // round-trip and the future M4 operator-side manifest ingest's
17842 // `Value::get(CAIXA_KEY_DEPS)` navigation — surfaces here as a
17843 // build-time test failure at `manifest.rs`, not as an
17844 // apply-time `.get(<stale-canonical-const>)` returning `None`
17845 // far from the drift's commit. Peer of the sibling
17846 // `caixa_multi_word_serde_keys_match_lifted_top_level_key_consts`
17847 // multi-word pin on the same M0 [`Caixa`] top-level
17848 // serialized-key axis, extended here to the single-word arm
17849 // the multi-word test's `rename_all = "camelCase"` sweep can't
17850 // reach (single-word `deps → deps` is a no-op the multi-word
17851 // pin's `\"depsDev\"` / `\"upgradeFrom\"` / `\"maxRestarts\"` /
17852 // `\"restartWindow\"` byte-scan can never observe).
17853 let mut c = Caixa::from_lisp(&Caixa::template("demo")).unwrap();
17854 c.deps = vec![Dep::simple("caixa-core", "^0.1")];
17855 let json = serde_json::to_string(&c).unwrap();
17856 let quoted = format!("\"{}\"", crate::render::CAIXA_KEY_DEPS);
17857 assert!(
17858 json.contains("ed),
17859 "serialized Caixa must carry the lifted top-level `deps` \
17860 byte-sequence {quoted} verbatim in the JSON emission (got: \
17861 {json})",
17862 );
17863 }
17864
17865 #[test]
17866 fn caixa_dep_graph_two_list_key_consts_are_pairwise_distinct() {
17867 // Cross-axis drift-detection pin on the two-list dep-graph
17868 // renderer-side wire-key axis: a future collapse of the
17869 // canonical [`crate::render::CAIXA_KEY_DEPS`] /
17870 // [`crate::render::CAIXA_KEY_DEPS_DEV`] byte-strings onto the
17871 // same value (e.g. an accidental copy-paste flip of
17872 // `CAIXA_KEY_DEPS_DEV` to also read `"deps"`) would silently
17873 // reroute every downstream `Value::get(<key>)` probe on one
17874 // axis onto the sibling axis's dep-list and pass every
17875 // propagation-probe test that expected only the stale axis's
17876 // value — a dev-only dep would land in the runtime closure at
17877 // publish time, or a runtime dep would be excluded from the
17878 // published lacre. Peer of the sibling four-way distinct pin
17879 // on the top-level multi-word tetrad
17880 // (`caixa_top_level_multi_word_key_consts_are_pairwise_distinct`)
17881 // and the two-way pin on the sibling
17882 // [`DEP_AUTHOR_KEY_DEPS`] / [`DEP_AUTHOR_KEY_DEPS_DEV`]
17883 // author-facing arm (4da6fba's test), extended here to the
17884 // renderer-side wire-key arm of the same two-list dep-graph
17885 // axis so both halves of the "one canonical byte-string per
17886 // typed axis per (author, wire)" grid carry the same
17887 // distinct-ness discipline.
17888 assert_ne!(
17889 crate::render::CAIXA_KEY_DEPS,
17890 crate::render::CAIXA_KEY_DEPS_DEV,
17891 "CAIXA_KEY_DEPS and CAIXA_KEY_DEPS_DEV must be distinct \
17892 canonical byte-sequences on the two-list dep-graph \
17893 renderer-side wire-key axis"
17894 );
17895 }
17896
17897 // ── DepList / Caixa::push_dep pin ────────────────────────────────
17898 //
17899 // The compounding pin: the two-arm closed-set typed enum
17900 // [`crate::dep::DepList`] carries the runtime-closure `:deps`
17901 // (`Prod`) vs dev-only-closure `:deps-dev` (`Dev`) dispatch every
17902 // consumer of the top-level manifest's dep-mutation surface reads
17903 // through, and the typed dispatch [`Caixa::push_dep`] on the
17904 // substrate primitive folds the "select list → check within-list
17905 // dup → push" cascade onto one method call. Prior to this landing
17906 // the two axes lived across two `&'static str` constants
17907 // (`DEP_AUTHOR_KEY_DEPS`, `DEP_AUTHOR_KEY_DEPS_DEV`) with no closed-
17908 // set type carrying the pair; the `feira add` mutation site's
17909 // inline `if self.dev { &mut caixa.deps_dev } else { &mut
17910 // caixa.deps }` dispatch expressed no compile-time link back to
17911 // the substrate primitive, and a future third dep-list axis would
17912 // have silently split at every open-coded mutation site.
17913
17914 #[test]
17915 fn dep_list_as_str_routes_through_lifted_author_key_constants() {
17916 // Every arm returns the same `&'static str` the substrate's
17917 // canonical `DEP_AUTHOR_KEY_DEPS` / `DEP_AUTHOR_KEY_DEPS_DEV`
17918 // constants carry. A future rebrand on either constant reaches
17919 // the enum through one edit; a regression to inline literals
17920 // (e.g. `Prod => ":deps"`) would silently split the diagnostic
17921 // quotes from the wire-format constants every consumer routes
17922 // through and this pin flags it at build time.
17923 assert_eq!(
17924 crate::dep::DepList::Prod.as_str(),
17925 crate::render::DEP_AUTHOR_KEY_DEPS
17926 );
17927 assert_eq!(
17928 crate::dep::DepList::Dev.as_str(),
17929 crate::render::DEP_AUTHOR_KEY_DEPS_DEV
17930 );
17931 }
17932
17933 #[test]
17934 fn dep_list_display_routes_through_as_str() {
17935 // Same as-str-through-Display convergence discipline the
17936 // sibling closed-set typed enums carry — a `format!("{list}")`
17937 // call must land byte-for-byte on the accessor's return so a
17938 // future consumer that formats the enum for a diagnostic line
17939 // reaches the same wire-format constant the wire-format
17940 // producers do.
17941 assert_eq!(
17942 format!("{}", crate::dep::DepList::Prod),
17943 crate::dep::DepList::Prod.as_str()
17944 );
17945 assert_eq!(
17946 format!("{}", crate::dep::DepList::Dev),
17947 crate::dep::DepList::Dev.as_str()
17948 );
17949 }
17950
17951 #[test]
17952 fn dep_list_all_enumerates_every_variant_once() {
17953 // Exhaustive-iteration pin — every arm appears exactly once in
17954 // `ALL`, matching the closed set the compiler enforces on the
17955 // sibling `match self` arms. A future variant addition that
17956 // extends only one method's match without extending `ALL`
17957 // would silently drop the new arm from every consumer that
17958 // iterates the slice.
17959 let variants: &[crate::dep::DepList] = crate::dep::DepList::ALL;
17960 assert!(variants.contains(&crate::dep::DepList::Prod));
17961 assert!(variants.contains(&crate::dep::DepList::Dev));
17962 assert_eq!(variants.len(), 2);
17963 }
17964
17965 #[test]
17966 fn dep_list_from_wire_returns_prod_on_deps_wire_scalar() {
17967 // Reverse projection on the two-list dep-graph axis: the
17968 // author-surface wire tag the sibling `as_str` emitter walks
17969 // for `Prod` (`:deps` via `DEP_AUTHOR_KEY_DEPS`) parses back to
17970 // `Some(DepList::Prod)`. A regression that hand-rolled the
17971 // per-arm match without routing through the lifted
17972 // `DEP_AUTHOR_KEY_DEPS` const would silently disagree on any
17973 // future wire-tag rebrand and this pin flags it at build time.
17974 assert_eq!(
17975 crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS),
17976 Some(crate::dep::DepList::Prod)
17977 );
17978 }
17979
17980 #[test]
17981 fn dep_list_from_wire_returns_dev_on_deps_dev_wire_scalar() {
17982 // Peer of the `Prod`-arm pin on the dev-only axis: the
17983 // author-surface wire tag the sibling `as_str` emitter walks
17984 // for `Dev` (`:deps-dev` via `DEP_AUTHOR_KEY_DEPS_DEV`) parses
17985 // back to `Some(DepList::Dev)`. Same drift-detection posture
17986 // as the peer arm — the sibling method `match` arms are
17987 // compiler-checked exhaustive so a future variant addition
17988 // trips at build time.
17989 assert_eq!(
17990 crate::dep::DepList::from_wire(crate::render::DEP_AUTHOR_KEY_DEPS_DEV),
17991 Some(crate::dep::DepList::Dev)
17992 );
17993 }
17994
17995 #[test]
17996 fn dep_list_from_wire_returns_none_on_unknown_wire_scalar() {
17997 // Every input outside the closed-set arm-string set the
17998 // sibling `as_str` emitter walks lands on the terminal `None`
17999 // fallback — no silent-accept surface. Sweeps a set of
18000 // plausibly-adjacent scalars (unprefixed wire form, PascalCase
18001 // rebrand candidates, foreign wire tags, empty string) so a
18002 // future variant addition that widened one wire form without
18003 // extending the emitter's arm-set would trip the sibling
18004 // round-trip pin below rather than silently accepting the new
18005 // form here.
18006 for candidate in [
18007 "",
18008 "deps",
18009 "deps-dev",
18010 ":deps ",
18011 ":Deps",
18012 ":DEPS",
18013 ":build-dep",
18014 ":tool-dep",
18015 "prod",
18016 "dev",
18017 ] {
18018 assert_eq!(
18019 crate::dep::DepList::from_wire(candidate),
18020 None,
18021 "from_wire({candidate:?}) must return None; every input outside \
18022 the {{DEP_AUTHOR_KEY_DEPS, DEP_AUTHOR_KEY_DEPS_DEV}} accept-set \
18023 the sibling as_str emitter walks lands on the terminal fallback",
18024 );
18025 }
18026 }
18027
18028 #[test]
18029 fn dep_list_round_trips_through_as_str_and_from_wire() {
18030 // Load-bearing round-trip pin: every arm the `ALL` iteration
18031 // exposes survives the `as_str` → `from_wire` composition
18032 // byte-for-byte. Same discipline the sibling closed-set enums
18033 // carry — `CaixaKind` /
18034 // `RestartStrategy` / `RestartPolicy` /
18035 // `PlacementStrategy` — extended onto the two-list dep-graph
18036 // axis. A future variant addition that extends `ALL` +
18037 // `as_str` without extending `from_wire` (or vice versa)
18038 // trips at build time on this iteration because the compiler
18039 // enforces exhaustiveness on the sibling `match self` arms.
18040 for &list in crate::dep::DepList::ALL {
18041 assert_eq!(
18042 crate::dep::DepList::from_wire(list.as_str()),
18043 Some(list),
18044 "DepList::from_wire(as_str({list:?})) must round-trip to Some({list:?}) — \
18045 a silent split between the forward emitter and the reverse parser \
18046 would drift the two halves of the two-list dep-graph axis's typed dispatch",
18047 );
18048 }
18049 }
18050
18051 #[test]
18052 fn push_dep_routes_to_deps_slot_on_prod_arm() {
18053 // The `Prod` arm dispatches to the runtime-closure `:deps`
18054 // slot every downstream lacre-pipeline consumer resolves at
18055 // build time. A future arm that regressed to inline `&mut
18056 // self.deps_dev` on the `Prod` path would silently reroute
18057 // every runtime dep into the dev-only closure at publish time
18058 // — this pin refuses that regression.
18059 let src = Caixa::template("host");
18060 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18061 let before_deps = caixa.deps().len();
18062 let before_deps_dev = caixa.deps_dev().len();
18063 let dep = Dep {
18064 nome: "caixa-teia".to_string(),
18065 versao: "^0.1".to_string(),
18066 fonte: None,
18067 opcional: false,
18068 caracteristicas: Vec::new(),
18069 };
18070 caixa
18071 .push_dep(crate::dep::DepList::Prod, dep)
18072 .expect("first push into :deps succeeds");
18073 assert_eq!(caixa.deps().len(), before_deps + 1);
18074 assert_eq!(caixa.deps_dev().len(), before_deps_dev);
18075 assert_eq!(caixa.deps().last().unwrap().nome(), "caixa-teia");
18076 }
18077
18078 #[test]
18079 fn push_dep_routes_to_deps_dev_slot_on_dev_arm() {
18080 // Peer of the sibling `Prod`-arm dispatch pin — the `Dev` arm
18081 // must dispatch to the dev-only-closure `:deps-dev` slot every
18082 // downstream test-facing artifact resolver reads. A future
18083 // regression that inverted the two arms would silently route
18084 // every dev-only dep into the runtime closure at publish time
18085 // and this pin catches it before the drift ships.
18086 let src = Caixa::template("host");
18087 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18088 let dep = Dep {
18089 nome: "tatara-check".to_string(),
18090 versao: "*".to_string(),
18091 fonte: None,
18092 opcional: false,
18093 caracteristicas: Vec::new(),
18094 };
18095 caixa
18096 .push_dep(crate::dep::DepList::Dev, dep)
18097 .expect("first push into :deps-dev succeeds");
18098 assert!(caixa.deps().is_empty());
18099 assert_eq!(caixa.deps_dev().len(), 1);
18100 assert_eq!(caixa.deps_dev().last().unwrap().nome(), "tatara-check");
18101 }
18102
18103 #[test]
18104 fn push_dep_refuses_within_list_duplicate_nome_with_typed_error() {
18105 // Within-list dup check routes through the canonical
18106 // [`DepError::DuplicateNome`] carrier — the substrate's typed
18107 // diagnostic for the same axis [`Caixa::validate_deps`]'s
18108 // parse-time [`crate::render::insert_first_seen`] walk raises
18109 // on. Prior to the lift the mutation site's inline
18110 // `bail!("dep '{}' already declared", …)` string-diagnostic
18111 // path expressed no through-line back to the typed error;
18112 // routing every dep-list refusal through one carrier means an
18113 // author reading a `feira add` refusal and a `feira build`
18114 // refusal reaches for the same corrective surface without
18115 // switching diagnostic idioms.
18116 let src = Caixa::template("host");
18117 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18118 let dep = Dep {
18119 nome: "caixa-teia".to_string(),
18120 versao: "^0.1".to_string(),
18121 fonte: None,
18122 opcional: false,
18123 caracteristicas: Vec::new(),
18124 };
18125 caixa
18126 .push_dep(crate::dep::DepList::Prod, dep.clone())
18127 .expect("first push succeeds");
18128 let dup = Dep {
18129 nome: "caixa-teia".to_string(),
18130 versao: "^0.2".to_string(),
18131 fonte: None,
18132 opcional: false,
18133 caracteristicas: Vec::new(),
18134 };
18135 let err = caixa
18136 .push_dep(crate::dep::DepList::Prod, dup)
18137 .expect_err("second push with same :nome refuses");
18138 assert_eq!(
18139 err,
18140 DepError::DuplicateNome {
18141 nome: "caixa-teia".to_string(),
18142 list: crate::render::DEP_AUTHOR_KEY_DEPS,
18143 }
18144 );
18145 // The refused mutation must not corrupt the target list —
18146 // exactly one entry lives past the refusal, matching the
18147 // canonical single-source-of-truth invariant `Caixa::deps()`
18148 // carries.
18149 assert_eq!(caixa.deps().len(), 1);
18150 }
18151
18152 #[test]
18153 fn push_dep_refuses_dup_on_dev_list_arm_names_deps_dev_key() {
18154 // Peer of the sibling `Prod`-arm dup-refusal pin — the `Dev`
18155 // arm's refusal must carry `DEP_AUTHOR_KEY_DEPS_DEV` in the
18156 // `list` payload so a future author reading the refusal grep's
18157 // for the correct `:deps-dev` block in their `caixa.lisp`,
18158 // not the sibling `:deps` block the runtime closure resolves.
18159 let src = Caixa::template("host");
18160 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18161 let dep = Dep {
18162 nome: "tatara-check".to_string(),
18163 versao: "*".to_string(),
18164 fonte: None,
18165 opcional: false,
18166 caracteristicas: Vec::new(),
18167 };
18168 caixa
18169 .push_dep(crate::dep::DepList::Dev, dep.clone())
18170 .expect("first push succeeds");
18171 let err = caixa
18172 .push_dep(crate::dep::DepList::Dev, dep)
18173 .expect_err("second push with same :nome refuses");
18174 assert!(matches!(
18175 err,
18176 DepError::DuplicateNome {
18177 ref nome,
18178 list,
18179 } if nome == "tatara-check"
18180 && list == crate::render::DEP_AUTHOR_KEY_DEPS_DEV
18181 ));
18182 }
18183
18184 #[test]
18185 fn push_dep_allows_same_nome_across_prod_and_dev_lists() {
18186 // The within-list dup check is scoped to the target arm — a
18187 // caixa may legitimately carry the same `:nome` under both
18188 // `:deps` and `:deps-dev` (though the substrate's peer
18189 // [`crate::Caixa::validate_deps`] walk still refuses the
18190 // shape at parse time; the mutation-site refusal is scoped to
18191 // the mutation-site's list to match the peer parse-time
18192 // per-list [`crate::render::insert_first_seen`] discipline).
18193 // The two arms hold independent seen-sets.
18194 let src = Caixa::template("host");
18195 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18196 let dep_prod = Dep {
18197 nome: "shared".to_string(),
18198 versao: "^0.1".to_string(),
18199 fonte: None,
18200 opcional: false,
18201 caracteristicas: Vec::new(),
18202 };
18203 let dep_dev = Dep {
18204 nome: "shared".to_string(),
18205 versao: "*".to_string(),
18206 fonte: None,
18207 opcional: false,
18208 caracteristicas: Vec::new(),
18209 };
18210 caixa
18211 .push_dep(crate::dep::DepList::Prod, dep_prod)
18212 .expect("push into :deps succeeds");
18213 caixa
18214 .push_dep(crate::dep::DepList::Dev, dep_dev)
18215 .expect("push same :nome into :deps-dev succeeds");
18216 assert_eq!(caixa.deps().len(), 1);
18217 assert_eq!(caixa.deps_dev().len(), 1);
18218 }
18219
18220 #[test]
18221 fn deps_of_prod_returns_the_deps_slot_verbatim() {
18222 // The `Prod` arm of the typed-dispatch [`Caixa::deps_of`] read
18223 // accessor must project onto the runtime-closure `:deps` slot —
18224 // element-equal and length-equal to the sibling per-slot
18225 // [`Caixa::deps`] accessor's return over every per-caixa fixture.
18226 // A future arm that regressed to `self.deps_dev()` on the `Prod`
18227 // path would silently reroute every downstream typed-dispatch
18228 // walker (the [`Caixa::validate_deps`] per-list
18229 // [`crate::render::insert_first_seen`] dedup walk, any future
18230 // per-axis-parametrised consumer) into the sibling dev-only
18231 // closure and this pin refuses that regression.
18232 let src = Caixa::template("host");
18233 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18234 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
18235 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 0);
18236 let dep = Dep {
18237 nome: "caixa-teia".to_string(),
18238 versao: "^0.1".to_string(),
18239 fonte: None,
18240 opcional: false,
18241 caracteristicas: Vec::new(),
18242 };
18243 caixa
18244 .push_dep(crate::dep::DepList::Prod, dep.clone())
18245 .expect("push into :deps succeeds");
18246 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod), caixa.deps());
18247 assert_eq!(caixa.deps_of(crate::dep::DepList::Prod).len(), 1);
18248 assert_eq!(
18249 caixa.deps_of(crate::dep::DepList::Prod)[0].nome(),
18250 "caixa-teia"
18251 );
18252 }
18253
18254 #[test]
18255 fn deps_of_dev_returns_the_deps_dev_slot_verbatim() {
18256 // Peer of the sibling `Prod`-arm pin — the `Dev` arm of
18257 // [`Caixa::deps_of`] must project onto the dev-only-closure
18258 // `:deps-dev` slot, element-equal and length-equal to the
18259 // sibling per-slot [`Caixa::deps_dev`] accessor's return. A
18260 // future regression that inverted the two arms would silently
18261 // route every dev-list walker onto the runtime closure and this
18262 // pin catches it before the drift ships.
18263 let src = Caixa::template("host");
18264 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18265 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
18266 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 0);
18267 let dep = Dep {
18268 nome: "tatara-check".to_string(),
18269 versao: "*".to_string(),
18270 fonte: None,
18271 opcional: false,
18272 caracteristicas: Vec::new(),
18273 };
18274 caixa
18275 .push_dep(crate::dep::DepList::Dev, dep)
18276 .expect("push into :deps-dev succeeds");
18277 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev), caixa.deps_dev());
18278 assert_eq!(caixa.deps_of(crate::dep::DepList::Dev).len(), 1);
18279 assert_eq!(
18280 caixa.deps_of(crate::dep::DepList::Dev)[0].nome(),
18281 "tatara-check"
18282 );
18283 }
18284
18285 #[test]
18286 fn deps_of_exhaustive_over_dep_list_all_covers_the_two_slots() {
18287 // Composition pin: iterating [`crate::dep::DepList::ALL`] through
18288 // [`Caixa::deps_of`] must land on the same two-slot partition the
18289 // per-slot [`Caixa::deps`] / [`Caixa::deps_dev`] accessors
18290 // expose — the canonical dispatch a future per-axis-parametrised
18291 // walker (a future `feira app graph` per-list dep summary, a
18292 // future M4 per-cluster dev-closure-audit overlay the CR
18293 // materializer resolves per-CR) reads through. Prior to the
18294 // lift the two-block iteration lived open-coded at every walker,
18295 // so a future third dep-list axis (`:deps-build`, per CAIXA-SDLC
18296 // §I) would have had to grow a third block at every consumer.
18297 // A regression that dropped the `Dev` arm from `ALL` would flip
18298 // the collected pairs to `[(":deps", &[])]` alone and this pin
18299 // refuses that shape.
18300 let src = Caixa::template("host");
18301 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18302 let prod_dep = Dep {
18303 nome: "caixa-teia".to_string(),
18304 versao: "^0.1".to_string(),
18305 fonte: None,
18306 opcional: false,
18307 caracteristicas: Vec::new(),
18308 };
18309 let dev_dep = Dep {
18310 nome: "tatara-check".to_string(),
18311 versao: "*".to_string(),
18312 fonte: None,
18313 opcional: false,
18314 caracteristicas: Vec::new(),
18315 };
18316 caixa
18317 .push_dep(crate::dep::DepList::Prod, prod_dep)
18318 .expect("push into :deps succeeds");
18319 caixa
18320 .push_dep(crate::dep::DepList::Dev, dev_dep)
18321 .expect("push into :deps-dev succeeds");
18322 let collected: Vec<(&'static str, usize, &str)> = crate::dep::DepList::ALL
18323 .iter()
18324 .map(|&list| {
18325 let slice = caixa.deps_of(list);
18326 (list.as_str(), slice.len(), slice[0].nome())
18327 })
18328 .collect();
18329 assert_eq!(
18330 collected,
18331 vec![
18332 (crate::render::DEP_AUTHOR_KEY_DEPS, 1, "caixa-teia"),
18333 (crate::render::DEP_AUTHOR_KEY_DEPS_DEV, 1, "tatara-check"),
18334 ]
18335 );
18336 }
18337
18338 #[test]
18339 fn validate_deps_iterates_through_dep_list_all_via_deps_of() {
18340 // Composition pin: the [`Caixa::validate_deps`] parse-time gate
18341 // must route its per-list [`crate::render::insert_first_seen`]
18342 // dedup walk through [`Caixa::deps_of`] + [`crate::dep::DepList::ALL`]
18343 // rather than the pre-lift open-coded two-block iteration over
18344 // `self.deps()` + `self.deps_dev()`. A regression that dropped
18345 // one arm (e.g. hand-inlining `self.deps()` alone) would silently
18346 // stop refusing within-list dups on the sibling arm; a
18347 // regression that flipped the arm-to-list-key mapping
18348 // (`Dev => DEP_AUTHOR_KEY_DEPS`) would silently mislabel the
18349 // diagnostic surface. Both drifts surface here through a paired
18350 // duplicate-name refusal per arm plus an offending-list-key
18351 // check on the emitted [`DepError::DuplicateNome`] carrier.
18352 for &list in crate::dep::DepList::ALL {
18353 let src = Caixa::template("host");
18354 let mut caixa = Caixa::from_lisp(&src).expect("template parses");
18355 let dup = Dep {
18356 nome: "twin".to_string(),
18357 versao: "^0.1".to_string(),
18358 fonte: None,
18359 opcional: false,
18360 caracteristicas: Vec::new(),
18361 };
18362 match list {
18363 crate::dep::DepList::Prod => {
18364 caixa.deps.push(dup.clone());
18365 caixa.deps.push(dup);
18366 }
18367 crate::dep::DepList::Dev => {
18368 caixa.deps_dev.push(dup.clone());
18369 caixa.deps_dev.push(dup);
18370 }
18371 }
18372 let err = caixa
18373 .validate_deps()
18374 .expect_err("within-list duplicate :nome must refuse");
18375 assert_eq!(
18376 err,
18377 DepError::DuplicateNome {
18378 nome: "twin".to_string(),
18379 list: list.as_str(),
18380 },
18381 "validate_deps on {list} arm must emit \
18382 DepError::DuplicateNome carrying the arm's own \
18383 as_str() diagnostic — the arm-to-list-key mapping \
18384 flowed through DepList::ALL + Caixa::deps_of"
18385 );
18386 }
18387 }
18388
18389 #[test]
18390 fn caixa_licenca_default_pins_canonical_mit_byte() {
18391 // Bridge-arm pin: [`CAIXA_LICENCA_DEFAULT`] resolves to the
18392 // canonical SPDX-`"MIT"` byte today, the same license expression
18393 // every peer substrate-side consumer of the author-omitted
18394 // `:licenca` slot ([`caixa-helm`]'s `build_readme` fallback arm at
18395 // `caixa-helm/src/lib.rs`, the future M4
18396 // `mesh.pleme.io/v1alpha1/Aplicacao` CR materializer's per-CR
18397 // `Chart.yaml annotations["artifacthub.io/license"]` emitter this
18398 // crate's [`Caixa::validate_licenca`] docstring roadmap already
18399 // names as the second consumer) fills into its per-consumer
18400 // README/annotation emit site. Pin the literal here (peer with the
18401 // [`crate::version::DEFAULT_PUBLISH_TAG_PREFIX`] /
18402 // [`crate::version::DEFAULT_GIT_REMOTE`] /
18403 // [`crate::version::DEFAULT_PLEME_GIT_ORG`] canonical-literal pins
18404 // on the sibling lifted-constant surfaces) so a future
18405 // substrate-side license-fallback rebrand surfaces here as a
18406 // coordinated edit-point: the sibling caixa-helm
18407 // `build_readme_license_line_routes_through_lifted_caixa_licenca_default`
18408 // pinning test already pins the equality at the renderer-emit
18409 // axis; this pin closes the second coordinate of the pair by
18410 // anchoring the lifted constant's current byte to the canonical
18411 // CAIXA-SDLC §I license scaffold's documented shape.
18412 assert_eq!(CAIXA_LICENCA_DEFAULT, "MIT");
18413 }
18414}