caixa_core/dialeto.rs
1//! `defcaixa` is spoken by two unrelated declarations. This module makes that
2//! a **typed fact** instead of an anonymous parse failure.
3//!
4//! # The finding
5//!
6//! Measured 2026-07-31 over the pleme-io org checkout (270 `caixa.lisp` /
7//! `*.caixa.lisp` files found with `rg --no-ignore`; a bare `rg` from the org
8//! root returns 0, which is how this stayed invisible), the corpus splits into
9//! two schemas that share zero required slots:
10//!
11//! * [`CaixaDialeto::Pacote`] — this crate's [`crate::Caixa`]. `:nome
12//! :versao :kind :deps :bibliotecas :exe :servicos` + the supervisor/mesh
13//! slots. It declares a **tatara-lisp package**: the thing `feira` resolves,
14//! builds, links and publishes.
15//! * [`CaixaDialeto::Molde`] — `:name :kind :ecosystem :package {…} :workflows
16//! […] :ci-config {…} :files […]`. It declares a **repo's generated
17//! surface**: which foreign ecosystem (rust / go / python / …), that
18//! ecosystem's own package metadata, the CI shims to emit, and byte-captured
19//! file bodies. Read by `pleme-doc-gen`, never by `feira`.
20//!
21//! `:package`, `:ecosystem`, `:supports` and `:profile` have no counterpart in
22//! [`crate::Caixa`] at all — the theory doc's own D4 note records the same
23//! thing: those manifests "are authored against a schema that does not exist in
24//! Rust". They are not two spellings of one declaration. They are two domains
25//! that collided on one word, because *caixa* names a box and both are boxes.
26//!
27//! # Why this is not a bug report about broken files
28//!
29//! The Molde-dialect files are not malformed. They are correct inputs to their
30//! own consumer, and nothing in the shipped `feira` reads them, so nothing is
31//! failing today. The hazard is **latent and certain**: any new declarative
32//! surface written against "a `.caixa.lisp` is a [`crate::Caixa`]" meets a
33//! corpus where that is false for the large majority of files, and gets a flat
34//! unknown-keyword rejection that reads as "this manifest is broken" rather
35//! than "this manifest is not yours".
36//!
37//! # What this module does about it
38//!
39//! [`classify`] is total: every `(defcaixa …)` form lands in exactly one
40//! [`CaixaDialeto`], including [`CaixaDialeto::Desconhecido`] for one that
41//! matches neither. [`crate::Caixa::from_lisp`] runs it first, so a foreign
42//! dialect is [`crate::ManifestError::DialetoEstrangeiro`] — an error that
43//! names the dialect it found and the consumer that speaks it — rather than an
44//! unknown-kwarg error indistinguishable from a typo.
45//!
46//! Tier-honest: this is **parse-time rejection with a named cause**, not
47//! unrepresentability. A caller that ignores the `Err` still gets nothing
48//! useful; what it can no longer do is mistake "wrong dialect" for "bad file".
49
50use tatara_lisp::{Atom, Sexp};
51
52/// Which `(defcaixa …)` declaration a source speaks.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
54pub enum CaixaDialeto {
55 /// This crate's [`crate::Caixa`] — a tatara-lisp package manifest.
56 /// Keyword-argument form headed by `:nome`.
57 Pacote,
58 /// `pleme-doc-gen`'s repo-surface declaration, keyword-argument form
59 /// headed by `:name` (plus `:ecosystem` / `:package`).
60 Molde,
61 /// The same declaration as [`Self::Molde`], written with the package name
62 /// as a bare positional symbol — `(defcaixa todoku-go :kind :Biblioteca
63 /// :ecosystem :go …)`. `pleme-doc-gen`'s parser reads the first token
64 /// after the head as the name, so this is one arity of one declaration,
65 /// not a third schema.
66 MoldePosicional,
67 /// A `(defcaixa …)` form matching neither. Kept as a variant rather than
68 /// an error so [`classify`] is total and a census can COUNT the residue —
69 /// a classifier that threw here would report "0 unknown" by construction.
70 Desconhecido,
71}
72
73impl CaixaDialeto {
74 /// Exhaustive iteration surface for every consumer that walks the
75 /// closed four-arm [`CaixaDialeto`] discriminator set — the
76 /// [`feira dialeto`](../../caixa_feira/cmd/dialeto/index.html)
77 /// census counter's per-arm accept-set, a future
78 /// `feira dialeto --list-dialects` CLI listing of the accepted
79 /// classifications, a future M4 `mesh.pleme.io/v1alpha1/Manifesto`
80 /// CR materializer's admission-webhook rejection body naming the
81 /// accepted-dialect set, any future census-report shape probe that
82 /// sweeps every arm to compute per-arm coverage. A future arm
83 /// addition (a fifth dialect the [`crate::dialeto`] module doc's
84 /// "third dialect" hazard actualises — the module explicitly frames
85 /// its purpose as "what stops a third dialect appearing", and this
86 /// slice is the substrate-side answer: the arm-set is one edit and
87 /// every consumer picks up the new entry by construction) extends
88 /// this slice as one edit and every downstream consumer picks up
89 /// the new entry through the shared iteration; the compiler-checked
90 /// exhaustiveness on the sibling method `match` arms
91 /// ([`Self::palavra_canonica`] / [`Self::consumidor`] /
92 /// [`Self::descricao`] / [`std::fmt::Display`]) is the build-time
93 /// guarantee that no arm forgets to grow.
94 ///
95 /// Peer of the sibling closed-set typed enums'
96 /// [`crate::CaixaKind::ALL`] (6b1f4fb) /
97 /// [`crate::aplicacao::PlacementStrategy::ALL`] (18c7342) /
98 /// [`crate::aplicacao::RateLimitUnit::ALL`] (6bce03d) /
99 /// [`crate::dep::DepList::ALL`] (45ee563) /
100 /// [`crate::supervisor::RestartStrategy::ALL`] (4eec29c) /
101 /// [`crate::supervisor::RestartPolicy::ALL`] (dd32ccf)
102 /// exhaustive-iteration surfaces — the seventh closed-set typed
103 /// enum on the caixa surface to converge onto the same
104 /// one-canonical-arm-list-per-enum discipline, and the first
105 /// dialect-classification axis (as distinct from an OTP-shape M2
106 /// slot or an M3 mesh slot) to reach it. Order matches variant
107 /// declaration order verbatim (`Pacote` → `Molde` →
108 /// `MoldePosicional` → `Desconhecido`) so the slice is the
109 /// canonical ordering every listing / rendering consumer defers to.
110 pub const ALL: &'static [Self] = &[
111 Self::Pacote,
112 Self::Molde,
113 Self::MoldePosicional,
114 Self::Desconhecido,
115 ];
116
117 /// Substrate-canonical `PascalCase` variant-name byte-string every consumer
118 /// that formats the dialect as census-facing text lands on. Returns the
119 /// per-arm `PascalCase` name of the variant (`"Pacote"` / `"Molde"` /
120 /// `"MoldePosicional"` / `"Desconhecido"`) — the one canonical
121 /// byte-string the paired [`std::fmt::Display`] impl routes through so
122 /// every downstream consumer (the `feira dialeto` census counter output
123 /// line, a future `feira dialeto --list-dialects` CLI enumeration, a
124 /// future M4 `mesh.pleme.io/v1alpha1/Manifesto` CR materializer's
125 /// admission-webhook rejection body naming the accepted-dialect set)
126 /// reaches for the same substrate primitive rather than the pre-lift
127 /// hand-rolled four-arm literal-string match every [`std::fmt::Display`]
128 /// call previously routed through in place.
129 ///
130 /// Peer of the sibling closed-set typed enums'
131 /// [`crate::CaixaKind::as_str`] / [`crate::supervisor::RestartStrategy::as_str`]
132 /// / [`crate::supervisor::RestartPolicy::as_str`] /
133 /// [`crate::aplicacao::PlacementStrategy::as_str`] /
134 /// [`crate::dep::DepList::as_str`] projections on the sibling closed-set
135 /// typed-enum discriminator axes — the seventh (and last unlifted)
136 /// closed-set fieldless typed enum on the caixa surface to converge
137 /// onto the same one-canonical-byte-string-per-arm-through-`as_str`
138 /// discipline the six siblings already carry. Unlike [`crate::CaixaKind`]
139 /// (which carries two axes: `as_str` returning lowercase Portuguese
140 /// diagnostic form vs `wire_name` returning `PascalCase` tatara-lisp
141 /// author-surface bytes), [`CaixaDialeto`] is an internal
142 /// classification with no wire surface — the `PascalCase` variant name
143 /// is the census-facing form every consumer reads, so `as_str`
144 /// suffices without a paired `wire_name` axis.
145 #[must_use]
146 pub const fn as_str(self) -> &'static str {
147 match self {
148 Self::Pacote => "Pacote",
149 Self::Molde => "Molde",
150 Self::MoldePosicional => "MoldePosicional",
151 Self::Desconhecido => "Desconhecido",
152 }
153 }
154
155 /// The keyword an author should write for this dialect, once the
156 /// migration named in [`Self::consumidor`] completes.
157 #[must_use]
158 pub const fn palavra_canonica(self) -> &'static str {
159 match self {
160 Self::Pacote => "defcaixa",
161 Self::Molde | Self::MoldePosicional => "defmolde",
162 Self::Desconhecido => "?",
163 }
164 }
165
166 /// Who reads this dialect.
167 #[must_use]
168 pub const fn consumidor(self) -> &'static str {
169 match self {
170 Self::Pacote => "caixa-core / feira",
171 Self::Molde | Self::MoldePosicional => "pleme-doc-gen",
172 Self::Desconhecido => "nobody known",
173 }
174 }
175
176 /// A one-line description for a census row or an error message.
177 #[must_use]
178 pub const fn descricao(self) -> &'static str {
179 match self {
180 Self::Pacote => "tatara-lisp package manifest (:nome :versao :kind :deps …)",
181 Self::Molde => "repo-surface declaration (:name :ecosystem :package {…} …)",
182 Self::MoldePosicional => {
183 "repo-surface declaration, positional name (defcaixa <nome> :kind …)"
184 }
185 Self::Desconhecido => "unrecognised — matches no known defcaixa schema",
186 }
187 }
188}
189
190/// [`std::fmt::Display`] routed through [`CaixaDialeto::as_str`], so the
191/// pretty-printed byte-string every consumer that formats the dialect as
192/// user-facing / census text lands on (the `feira dialeto` per-manifest
193/// `--list` row, the `feira dialeto` census summary line's per-arm
194/// counters, a future M4 admission-webhook's rejection body naming the
195/// accepted-dialect set) reaches for the same `PascalCase` per-arm
196/// byte-string the [`CaixaDialeto::as_str`] helper returns.
197///
198/// Prior to this lift the [`std::fmt::Display`] impl hand-rolled its own
199/// four-arm literal-string match — the one hand-rolled per-arm dispatch
200/// on the closed [`CaixaDialeto`] discriminator that had NO substrate
201/// primitive accessor to defer to (the sibling [`CaixaDialeto::palavra_canonica`] /
202/// [`CaixaDialeto::consumidor`] / [`CaixaDialeto::descricao`] projections
203/// carry distinct byte-shapes per axis, so none of them could serve as
204/// the Display source). A future variant addition (a fifth dialect the
205/// module doc's "third dialect" hazard actualises) would land one arm at
206/// the enum and per-arm returns at the paired accessors, but a hand-rolled
207/// [`std::fmt::Display`] match would silently drop the new arm to compile-
208/// fail-at-the-match-arm-site rather than through the shared substrate
209/// primitive. Routing [`std::fmt::Display`] through [`CaixaDialeto::as_str`]
210/// closes the last unlifted per-arm `PascalCase`-name projection on the
211/// caixa surface — the seventh (and last unlifted) closed-set fieldless
212/// typed enum on the caixa surface to converge onto the same
213/// `Display`-through-`as_str` discipline the six siblings
214/// ([`crate::CaixaKind`] / [`crate::supervisor::RestartStrategy`] /
215/// [`crate::supervisor::RestartPolicy`] /
216/// [`crate::aplicacao::PlacementStrategy`] / [`crate::aplicacao::RateLimitUnit`]
217/// / [`crate::dep::DepList`]) already carry.
218impl std::fmt::Display for CaixaDialeto {
219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220 f.write_str(self.as_str())
221 }
222}
223
224/// A source that is not a `(defcaixa …)` / `(defmolde …)` form at all.
225#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
226pub enum DialetoError {
227 #[error("source has no top-level form")]
228 Vazio,
229 #[error("top-level form is not a list — a manifest is `(defcaixa …)`")]
230 NaoEhLista,
231 #[error(
232 "top-level form is headed by `{encontrado}`, not `defcaixa` or `defmolde` \
233 (a manifest's first form must be the declaration itself)"
234 )]
235 CabecaErrada { encontrado: String },
236 #[error("manifest does not parse as tatara-lisp: {0}")]
237 Leitura(String),
238}
239
240/// Classify a manifest source without committing to either schema.
241///
242/// Deliberately reads only the head symbol and the set of top-level keywords —
243/// enough to route, never enough to half-parse. A classifier that started
244/// validating would grow into a third parser, which is the shape of the problem
245/// it exists to name.
246///
247/// # Errors
248/// [`DialetoError`] when the source is not a manifest declaration at all.
249pub fn classify(src: &str) -> Result<CaixaDialeto, DialetoError> {
250 let forms = tatara_lisp::read(src).map_err(|e| DialetoError::Leitura(e.to_string()))?;
251 let first = forms.first().ok_or(DialetoError::Vazio)?;
252 classify_form(first)
253}
254
255/// [`classify`] over an already-read form.
256///
257/// # Errors
258/// [`DialetoError`] when the form is not a manifest declaration.
259pub fn classify_form(form: &Sexp) -> Result<CaixaDialeto, DialetoError> {
260 let list = form.as_list().ok_or(DialetoError::NaoEhLista)?;
261 let head = list
262 .first()
263 .and_then(Sexp::as_symbol)
264 .ok_or(DialetoError::NaoEhLista)?;
265
266 match head {
267 // `defmolde` is unambiguous by construction — it exists precisely so a
268 // consumer never has to infer which declaration it holds. Both arities
269 // are the same declaration; the positional one keeps its own variant
270 // only so a census can report the split.
271 "defmolde" => {
272 return Ok(if starts_with_positional_name(&list[1..]) {
273 CaixaDialeto::MoldePosicional
274 } else {
275 CaixaDialeto::Molde
276 });
277 }
278 "defcaixa" => {}
279 other => {
280 return Err(DialetoError::CabecaErrada {
281 encontrado: other.to_string(),
282 });
283 }
284 }
285
286 let args = &list[1..];
287
288 // `(defcaixa <symbol> :kind … :ecosystem …)`. Only the Molde dialect has a
289 // positional arity; `Caixa` is keyword-only, so a leading bare symbol
290 // settles it without looking further.
291 if starts_with_positional_name(args) {
292 return Ok(CaixaDialeto::MoldePosicional);
293 }
294
295 let keys = top_level_keywords(args);
296 let has = |k: &str| keys.iter().any(|s| s == k);
297
298 // Order matters, and it is not arbitrary: `:nome` and `:name` are the two
299 // required head slots and no file in the measured corpus carries both.
300 // Checking them FIRST means the decision rests on the one slot each schema
301 // makes mandatory, rather than on optional evidence like `:ecosystem`.
302 if has("nome") {
303 return Ok(CaixaDialeto::Pacote);
304 }
305 if has("name") || has("ecosystem") || has("package") {
306 return Ok(CaixaDialeto::Molde);
307 }
308 Ok(CaixaDialeto::Desconhecido)
309}
310
311/// True when the first argument is a bare symbol rather than a keyword — the
312/// positional-name arity.
313fn starts_with_positional_name(args: &[Sexp]) -> bool {
314 matches!(args.first(), Some(Sexp::Atom(Atom::Symbol(_))))
315}
316
317/// The top-level keyword names (without the leading `:`) of a kwarg list.
318///
319/// Steps in pairs so a keyword appearing as a VALUE — `:kind :Biblioteca`, or a
320/// nested `(:nome "dep" :versao "^0.1")` inside `:deps` — is never counted as a
321/// top-level slot. A naive scan for `:nome` anywhere in the source classifies
322/// every Molde manifest with a `:deps` list as a Pacote.
323fn top_level_keywords(args: &[Sexp]) -> Vec<String> {
324 let mut out = Vec::new();
325 let mut i = 0;
326 while i < args.len() {
327 if let Sexp::Atom(Atom::Keyword(k)) = &args[i] {
328 out.push(k.clone());
329 i += 2;
330 } else {
331 i += 1;
332 }
333 }
334 out
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 const PACOTE: &str = r#"
342 (defcaixa
343 :nome "checkout"
344 :versao "0.1.0"
345 :kind Servico
346 :deps ((:nome "caixa-teia" :versao "^0.1")))
347 "#;
348
349 const MOLDE: &str = r#"
350 (defcaixa
351 :name "base64"
352 :kind :Biblioteca
353 :ecosystem :rust-single-crate
354 :package {:name "base64" :version "0.22.1"}
355 :workflows [:auto-release])
356 "#;
357
358 const MOLDE_POSICIONAL: &str = r#"
359 (defcaixa todoku-go
360 :kind :Biblioteca
361 :ecosystem :go
362 :package {:name "todoku-go" :version "0.3.0"})
363 "#;
364
365 #[test]
366 fn the_package_dialect_is_recognised() {
367 assert_eq!(classify(PACOTE), Ok(CaixaDialeto::Pacote));
368 }
369
370 #[test]
371 fn the_repo_surface_dialect_is_recognised() {
372 assert_eq!(classify(MOLDE), Ok(CaixaDialeto::Molde));
373 }
374
375 #[test]
376 fn the_positional_arity_is_recognised() {
377 assert_eq!(
378 classify(MOLDE_POSICIONAL),
379 Ok(CaixaDialeto::MoldePosicional)
380 );
381 }
382
383 #[test]
384 fn defmolde_classifies_without_inference() {
385 // The whole point of the new keyword: no schema sniffing required.
386 let src = r#"(defmolde :name "x" :kind :Biblioteca :ecosystem :go)"#;
387 assert_eq!(classify(src), Ok(CaixaDialeto::Molde));
388 let pos = r"(defmolde todoku-go :kind :Biblioteca :ecosystem :go)";
389 assert_eq!(classify(pos), Ok(CaixaDialeto::MoldePosicional));
390 }
391
392 #[test]
393 fn a_nested_nome_does_not_make_a_repo_surface_look_like_a_package() {
394 // The exact failure a substring scan produces: `:deps ((:nome …))`
395 // contains `:nome`, but not as a top-level slot.
396 let src = r#"
397 (defcaixa
398 :name "x"
399 :ecosystem :rust-single-crate
400 :deps ((:nome "inner" :versao "^0.1")))
401 "#;
402 assert_eq!(classify(src), Ok(CaixaDialeto::Molde));
403 }
404
405 #[test]
406 fn a_keyword_in_value_position_is_not_a_slot() {
407 // `:kind :Biblioteca` — the value is itself a keyword. Stepping one at
408 // a time would read `:Biblioteca` as a top-level slot.
409 let src = r#"(defcaixa :kind :Biblioteca :name "x")"#;
410 assert_eq!(classify(src), Ok(CaixaDialeto::Molde));
411 }
412
413 #[test]
414 fn an_unrecognised_defcaixa_is_reported_not_guessed() {
415 let src = r#"(defcaixa :licenca "MIT")"#;
416 assert_eq!(classify(src), Ok(CaixaDialeto::Desconhecido));
417 }
418
419 #[test]
420 fn a_form_that_is_not_a_manifest_is_an_error_not_a_dialect() {
421 assert_eq!(
422 classify("(defflake :nome \"x\")"),
423 Err(DialetoError::CabecaErrada {
424 encontrado: "defflake".into()
425 })
426 );
427 assert_eq!(classify(""), Err(DialetoError::Vazio));
428 }
429
430 #[test]
431 fn every_dialect_names_its_consumer_and_its_canonical_keyword() {
432 // Guards the routing table itself: a new variant added without an arm
433 // here is a compile error in the match, and a variant that claims
434 // `defcaixa` while being read by pleme-doc-gen would re-open the
435 // collision this module closes. Sweeps [`CaixaDialeto::ALL`] rather
436 // than the pre-lift open-coded four-arm literal list — a future arm
437 // addition extends the slice as one edit and this pin picks it up
438 // by construction.
439 for &d in CaixaDialeto::ALL {
440 assert!(!d.descricao().is_empty(), "{d}");
441 assert!(!d.consumidor().is_empty(), "{d}");
442 }
443 assert_eq!(CaixaDialeto::Pacote.palavra_canonica(), "defcaixa");
444 assert_eq!(CaixaDialeto::Molde.palavra_canonica(), "defmolde");
445 assert_ne!(
446 CaixaDialeto::Pacote.palavra_canonica(),
447 CaixaDialeto::Molde.palavra_canonica(),
448 "the two dialects must not share a canonical keyword — that IS the defect"
449 );
450 }
451
452 #[test]
453 fn caixa_dialeto_all_enumerates_every_variant_exactly_once() {
454 // Three-legged exhaustiveness pin, peer of the sibling
455 // `caixa_kind_all_enumerates_every_variant_exactly_once`
456 // (caixa-core/src/kind.rs) /
457 // `restart_strategy_all_enumerates_every_variant_exactly_once`
458 // (caixa-core/src/supervisor.rs) shape.
459 //
460 // 1. arm-count invariant: `ALL.len()` matches the declared arm
461 // count (four — a fifth arm added without extending `ALL`
462 // fails this pin at caixa-core test time);
463 // 2. pairwise-distinctness invariant: every variant appears at
464 // most once in the slice (a duplicate arm would silently
465 // double-count in the census consumer, so the pin rejects
466 // duplicates outright);
467 // 3. coverage invariant: every literal `CaixaDialeto::X` is in
468 // the slice (the compiler-checked exhaustiveness on the peer
469 // per-arm `match self` in the accessors keeps the enum arm
470 // set and the `ALL` slice mutually aligned).
471 assert_eq!(
472 CaixaDialeto::ALL.len(),
473 4,
474 "ALL must list every arm exactly once; a fifth arm added \
475 without extending ALL fails this pin — extend ALL alongside \
476 the new variant"
477 );
478
479 let mut seen: Vec<CaixaDialeto> = Vec::new();
480 for &d in CaixaDialeto::ALL {
481 assert!(
482 !seen.contains(&d),
483 "ALL contains a duplicate arm: {d}. Every variant appears \
484 exactly once — a duplicate would double-count in every \
485 iteration consumer"
486 );
487 seen.push(d);
488 }
489
490 // Coverage: exhaustively assert every literal variant is somewhere
491 // in the slice. Written as an exhaustive `match` so a future arm
492 // addition fails to compile here (missing match arm) until the
493 // corresponding `assert` is added — the compiler enforces the pin's
494 // completeness rather than a hand-maintained variant list.
495 for variant in [
496 CaixaDialeto::Pacote,
497 CaixaDialeto::Molde,
498 CaixaDialeto::MoldePosicional,
499 CaixaDialeto::Desconhecido,
500 ] {
501 let coverage_probe = match variant {
502 CaixaDialeto::Pacote
503 | CaixaDialeto::Molde
504 | CaixaDialeto::MoldePosicional
505 | CaixaDialeto::Desconhecido => variant,
506 };
507 assert!(
508 CaixaDialeto::ALL.contains(&coverage_probe),
509 "ALL is missing variant {coverage_probe} — extend the slice"
510 );
511 }
512 }
513
514 #[test]
515 fn caixa_dialeto_all_is_const_and_matches_iteration_count() {
516 // Pins the const-ness of the slice at const-fold time. A future
517 // change that promoted `ALL` to a non-const initializer (a lazy-
518 // static, a runtime-computed Vec) would fail to compile here —
519 // the pin locks in the compile-time-known iteration surface
520 // every consumer builds against. Peer of the sibling
521 // `caixa_kind_all_is_const_and_matches_iteration_count` (kind.rs)
522 // / `restart_strategy_all_is_const_and_matches_iteration_count`
523 // (supervisor.rs) shape.
524 const ALL: &[CaixaDialeto] = CaixaDialeto::ALL;
525 assert_eq!(ALL.len(), CaixaDialeto::ALL.len());
526 // Sweep the iterator without collapsing to `.len()` so a future
527 // change to `ALL`'s carrier that decouples `.len()` from the
528 // iteration count (a lazy-computed shape, an alias `impl Iterator`
529 // return, a wrapper newtype) still passes here iff the two agree
530 // arm-for-arm; the `#[allow]` opts this local pin out of the
531 // clippy `iter_count` collapse that would defeat the intent.
532 #[allow(clippy::iter_count)]
533 let iterated = ALL.iter().count();
534 assert_eq!(iterated, CaixaDialeto::ALL.len());
535 }
536
537 #[test]
538 fn caixa_dialeto_all_covers_every_variant_by_display_probe() {
539 // Fanning `Display` over the slice sweeps the paired accessors
540 // ([`CaixaDialeto::palavra_canonica`] / [`CaixaDialeto::consumidor`]
541 // / [`CaixaDialeto::descricao`]) at every arm — every returned
542 // byte-string is non-empty (the accessors' contract). A future
543 // arm added without extending its per-arm `match self` return
544 // would compile-fail at the accessor call inside the loop;
545 // together with the `ALL.len() == 4` pin above, this locks the
546 // accessor arm-set and the `ALL` slice mutually.
547 for &d in CaixaDialeto::ALL {
548 let display_form = d.to_string();
549 assert!(
550 !display_form.is_empty(),
551 "Display must render a non-empty byte-string for every \
552 arm; empty: {d:?}"
553 );
554 // Consumidor / descricao / palavra-canonica must each surface
555 // a non-empty scalar; every downstream diagnostic consumer
556 // reaches through these accessors.
557 assert!(!d.palavra_canonica().is_empty(), "{d}");
558 assert!(!d.consumidor().is_empty(), "{d}");
559 assert!(!d.descricao().is_empty(), "{d}");
560 }
561 }
562
563 #[test]
564 fn caixa_dialeto_as_str_returns_pascal_case_variant_name() {
565 // Fail-before-pass-after per-arm shape pin: the four
566 // [`CaixaDialeto::as_str`] arms must return the canonical
567 // `PascalCase` byte-string that names the variant. Pre-lift this
568 // byte-string existed only inside the hand-rolled Display impl's
569 // four-arm literal-string match — every consumer that wanted the
570 // `PascalCase` name reached through `format!("{d}")`'s allocation
571 // path. Pinning the four arms explicitly here refuses a future
572 // regression that ever reroutes an arm to a distinct spelling
573 // (`"pacote"` lowercase, `"MoldePositional"` English rebrand,
574 // `"Unknown"` for `Desconhecido`) — the census output and the
575 // typed accessor would silently disagree until a downstream
576 // consumer surfaced the drift at census time. Peer of the sibling
577 // [`crate::supervisor::tests::restart_strategy_variants_serialize_to_lifted_scalar_values`]
578 // / `placement_strategy_variants_serialize_to_lifted_scalar_values`
579 // / `caixa_kind_as_str_returns_lifted_peer_const` shape on the
580 // sibling closed-set typed-enum discriminator axes — the seventh
581 // (and last unlifted) closed-set typed enum on the caixa surface
582 // to converge onto the same per-arm-shape-pin discipline.
583 for (variant, expected) in [
584 (CaixaDialeto::Pacote, "Pacote"),
585 (CaixaDialeto::Molde, "Molde"),
586 (CaixaDialeto::MoldePosicional, "MoldePosicional"),
587 (CaixaDialeto::Desconhecido, "Desconhecido"),
588 ] {
589 assert_eq!(
590 variant.as_str(),
591 expected,
592 "CaixaDialeto::{variant:?}.as_str() must return the \
593 canonical `PascalCase` variant-name byte-string; drift here \
594 splits the census-facing text from the substrate \
595 primitive every downstream consumer will read"
596 );
597 }
598 }
599
600 #[test]
601 fn caixa_dialeto_display_routes_through_as_str_helper() {
602 // Fail-before-pass-after convergence pin: for every arm in
603 // [`CaixaDialeto::ALL`], the [`std::fmt::Display`] rendered form
604 // must byte-equal [`CaixaDialeto::as_str`]'s return value. Pre-
605 // lift these two paths were structurally independent — the
606 // Display impl hand-rolled its own four-arm literal-string
607 // match with no compile-time link back to any substrate accessor
608 // — so a future variant rename could land at `Display` without
609 // touching a paired accessor (or vice versa), silently splitting
610 // the two paths on the renamed arm. Pinning the byte-equality
611 // here makes any such split a caixa-core build-time failure at
612 // this test rather than surfacing far from the rename commit as
613 // a downstream census consumer emitting one spelling while the
614 // typed accessor returned another. Peer of the sibling
615 // [`crate::kind::tests::caixa_kind_display_routes_through_as_str_helper`]
616 // (which pins the same convergence on the [`crate::CaixaKind`]
617 // closed-set axis) — extends the discipline onto the seventh
618 // (and last unlifted) closed-set fieldless typed enum on the
619 // caixa surface.
620 for &variant in CaixaDialeto::ALL {
621 assert_eq!(
622 variant.to_string(),
623 variant.as_str(),
624 "CaixaDialeto::{variant:?} Display must route through \
625 CaixaDialeto::as_str (single source of truth: the \
626 lifted per-arm `PascalCase` variant-name byte-string)"
627 );
628 }
629 }
630
631 #[test]
632 fn caixa_dialeto_as_str_is_const_fn() {
633 // Const-context pin: [`CaixaDialeto::as_str`] must remain
634 // `const fn` (its match arms return `pub const` byte-strings, so
635 // no non-const operation exists on the resolution path).
636 // Downstream consumers reaching for the accessor from a `const`
637 // context (a future substrate-wide const-fold-driven audit table
638 // that materializes every dialect's census label at build time,
639 // a per-arm CR-admission-webhook message registration in a
640 // `const` gate) rely on the const-ness. A future accidental
641 // downgrade to non-`const` (an added runtime helper reachable
642 // only from a non-`const` context, a manual hand-rolled `impl`
643 // that shadows this method) trips at caixa-core build time
644 // rather than surfacing as a downstream `const`-context
645 // regression far from the accessor declaration. Peer of the
646 // sibling [`crate::kind::tests::caixa_kind_wire_name_is_const_fn`]
647 // pin on the paired [`crate::CaixaKind`] byte-string axis.
648 const PACOTE: &str = CaixaDialeto::Pacote.as_str();
649 const MOLDE: &str = CaixaDialeto::Molde.as_str();
650 const MOLDE_POSICIONAL: &str = CaixaDialeto::MoldePosicional.as_str();
651 const DESCONHECIDO: &str = CaixaDialeto::Desconhecido.as_str();
652 assert_eq!(PACOTE, "Pacote");
653 assert_eq!(MOLDE, "Molde");
654 assert_eq!(MOLDE_POSICIONAL, "MoldePosicional");
655 assert_eq!(DESCONHECIDO, "Desconhecido");
656 }
657}