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///
54/// The [`gen_platform::IsVariant`] derive emits per-arm arm-discriminator
55/// predicates (`is_pacote` / `is_molde` / `is_molde_posicional` /
56/// `is_desconhecido`) as substrate-side typed dispatches on the closed
57/// four-arm dialect-classification discriminator. Peer of the sibling
58/// closed-set fieldless typed enums' [`crate::CaixaKind`] /
59/// [`crate::supervisor::RestartStrategy`] /
60/// [`crate::supervisor::RestartPolicy`] /
61/// [`crate::aplicacao::PlacementStrategy`] /
62/// [`crate::aplicacao::RateLimitUnit`] /
63/// [`crate::dep::DepList`] `IsVariant` derives on the sibling
64/// closed-set typed-enum discriminator axes.
65///
66/// The pre-lift `is_molde_family` predicate hand-rolled its own
67/// `matches!(self, Self::Molde | Self::MoldePosicional)` two-arm literal
68/// with no compile-time link back to the closed set — post-lift it routes
69/// through `self.is_molde() || self.is_molde_posicional()` so a future
70/// arm rename (e.g. `Molde → MoldeKW` under an M4 vocabulary shift) trips
71/// exhaustively at every derive-generated predicate site rather than
72/// leaving the hand-rolled `matches!` silently drifting.
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, gen_platform::IsVariant)]
74pub enum CaixaDialeto {
75 /// This crate's [`crate::Caixa`] — a tatara-lisp package manifest.
76 /// Keyword-argument form headed by `:nome`.
77 Pacote,
78 /// `pleme-doc-gen`'s repo-surface declaration, keyword-argument form
79 /// headed by `:name` (plus `:ecosystem` / `:package`).
80 Molde,
81 /// The same declaration as [`Self::Molde`], written with the package name
82 /// as a bare positional symbol — `(defcaixa todoku-go :kind :Biblioteca
83 /// :ecosystem :go …)`. `pleme-doc-gen`'s parser reads the first token
84 /// after the head as the name, so this is one arity of one declaration,
85 /// not a third schema.
86 MoldePosicional,
87 /// A `(defcaixa …)` form matching neither. Kept as a variant rather than
88 /// an error so [`classify`] is total and a census can COUNT the residue —
89 /// a classifier that threw here would report "0 unknown" by construction.
90 Desconhecido,
91}
92
93impl CaixaDialeto {
94 /// Exhaustive iteration surface for every consumer that walks the
95 /// closed four-arm [`CaixaDialeto`] discriminator set — the
96 /// [`feira dialeto`](../../caixa_feira/cmd/dialeto/index.html)
97 /// census counter's per-arm accept-set, a future
98 /// `feira dialeto --list-dialects` CLI listing of the accepted
99 /// classifications, a future M4 `mesh.pleme.io/v1alpha1/Manifesto`
100 /// CR materializer's admission-webhook rejection body naming the
101 /// accepted-dialect set, any future census-report shape probe that
102 /// sweeps every arm to compute per-arm coverage. A future arm
103 /// addition (a fifth dialect the [`crate::dialeto`] module doc's
104 /// "third dialect" hazard actualises — the module explicitly frames
105 /// its purpose as "what stops a third dialect appearing", and this
106 /// slice is the substrate-side answer: the arm-set is one edit and
107 /// every consumer picks up the new entry by construction) extends
108 /// this slice as one edit and every downstream consumer picks up
109 /// the new entry through the shared iteration; the compiler-checked
110 /// exhaustiveness on the sibling method `match` arms
111 /// ([`Self::palavra_canonica`] / [`Self::consumidor`] /
112 /// [`Self::descricao`] / [`std::fmt::Display`]) is the build-time
113 /// guarantee that no arm forgets to grow.
114 ///
115 /// Peer of the sibling closed-set typed enums'
116 /// [`crate::CaixaKind::ALL`] (6b1f4fb) /
117 /// [`crate::aplicacao::PlacementStrategy::ALL`] (18c7342) /
118 /// [`crate::aplicacao::RateLimitUnit::ALL`] (6bce03d) /
119 /// [`crate::dep::DepList::ALL`] (45ee563) /
120 /// [`crate::supervisor::RestartStrategy::ALL`] (4eec29c) /
121 /// [`crate::supervisor::RestartPolicy::ALL`] (dd32ccf)
122 /// exhaustive-iteration surfaces — the seventh closed-set typed
123 /// enum on the caixa surface to converge onto the same
124 /// one-canonical-arm-list-per-enum discipline, and the first
125 /// dialect-classification axis (as distinct from an OTP-shape M2
126 /// slot or an M3 mesh slot) to reach it. Order matches variant
127 /// declaration order verbatim (`Pacote` → `Molde` →
128 /// `MoldePosicional` → `Desconhecido`) so the slice is the
129 /// canonical ordering every listing / rendering consumer defers to.
130 pub const ALL: &'static [Self] = &[
131 Self::Pacote,
132 Self::Molde,
133 Self::MoldePosicional,
134 Self::Desconhecido,
135 ];
136
137 /// Substrate-canonical `PascalCase` variant-name byte-string every consumer
138 /// that formats the dialect as census-facing text lands on. Returns the
139 /// per-arm `PascalCase` name of the variant (`"Pacote"` / `"Molde"` /
140 /// `"MoldePosicional"` / `"Desconhecido"`) — the one canonical
141 /// byte-string the paired [`std::fmt::Display`] impl routes through so
142 /// every downstream consumer (the `feira dialeto` census counter output
143 /// line, a future `feira dialeto --list-dialects` CLI enumeration, a
144 /// future M4 `mesh.pleme.io/v1alpha1/Manifesto` CR materializer's
145 /// admission-webhook rejection body naming the accepted-dialect set)
146 /// reaches for the same substrate primitive rather than the pre-lift
147 /// hand-rolled four-arm literal-string match every [`std::fmt::Display`]
148 /// call previously routed through in place.
149 ///
150 /// Peer of the sibling closed-set typed enums'
151 /// [`crate::CaixaKind::as_str`] / [`crate::supervisor::RestartStrategy::as_str`]
152 /// / [`crate::supervisor::RestartPolicy::as_str`] /
153 /// [`crate::aplicacao::PlacementStrategy::as_str`] /
154 /// [`crate::dep::DepList::as_str`] projections on the sibling closed-set
155 /// typed-enum discriminator axes — the seventh (and last unlifted)
156 /// closed-set fieldless typed enum on the caixa surface to converge
157 /// onto the same one-canonical-byte-string-per-arm-through-`as_str`
158 /// discipline the six siblings already carry. Unlike [`crate::CaixaKind`]
159 /// (which carries two axes: `as_str` returning lowercase Portuguese
160 /// diagnostic form vs `wire_name` returning `PascalCase` tatara-lisp
161 /// author-surface bytes), [`CaixaDialeto`] is an internal
162 /// classification with no wire surface — the `PascalCase` variant name
163 /// is the census-facing form every consumer reads, so `as_str`
164 /// suffices without a paired `wire_name` axis.
165 #[must_use]
166 pub const fn as_str(self) -> &'static str {
167 match self {
168 Self::Pacote => "Pacote",
169 Self::Molde => "Molde",
170 Self::MoldePosicional => "MoldePosicional",
171 Self::Desconhecido => "Desconhecido",
172 }
173 }
174
175 /// The keyword an author should write for this dialect, once the
176 /// migration named in [`Self::consumidor`] completes.
177 #[must_use]
178 pub const fn palavra_canonica(self) -> &'static str {
179 match self {
180 Self::Pacote => "defcaixa",
181 Self::Molde | Self::MoldePosicional => "defmolde",
182 Self::Desconhecido => "?",
183 }
184 }
185
186 /// Who reads this dialect.
187 #[must_use]
188 pub const fn consumidor(self) -> &'static str {
189 match self {
190 Self::Pacote => "caixa-core / feira",
191 Self::Molde | Self::MoldePosicional => "pleme-doc-gen",
192 Self::Desconhecido => "nobody known",
193 }
194 }
195
196 /// A one-line description for a census row or an error message.
197 #[must_use]
198 pub const fn descricao(self) -> &'static str {
199 match self {
200 Self::Pacote => "tatara-lisp package manifest (:nome :versao :kind :deps …)",
201 Self::Molde => "repo-surface declaration (:name :ecosystem :package {…} …)",
202 Self::MoldePosicional => {
203 "repo-surface declaration, positional name (defcaixa <nome> :kind …)"
204 }
205 Self::Desconhecido => "unrecognised — matches no known defcaixa schema",
206 }
207 }
208
209 /// True when this arm belongs to the `defmolde` declaration family —
210 /// the two-arity closure of [`Self::Molde`] and [`Self::MoldePosicional`]
211 /// under the shared `defmolde` head keyword the sibling
212 /// [`Self::palavra_canonica`] projection already collapses onto
213 /// `"defmolde"` for both arms (and the sibling [`Self::consumidor`]
214 /// projection collapses onto `"pleme-doc-gen"` for the same two arms).
215 /// False on [`Self::Pacote`] (the sibling `defcaixa` tatara-lisp
216 /// package manifest, [`Self::palavra_canonica`] `→ "defcaixa"`) and
217 /// on [`Self::Desconhecido`] (the residue that names no known
218 /// declaration, [`Self::palavra_canonica`] `→ "?"`).
219 ///
220 /// The [`Self::Molde`] / [`Self::MoldePosicional`] split is one
221 /// declaration written two ways ([`Self::MoldePosicional`]'s
222 /// variant-declaration docstring at [`Self::MoldePosicional`] frames
223 /// it exactly: "the same declaration as [`Self::Molde`], written with
224 /// the package name as a bare positional symbol … this is one arity
225 /// of one declaration, not a third schema"). Every downstream gate
226 /// that keys off "does this dialect belong to the `defmolde` family"
227 /// (as distinct from the four-arm-per-arm census-counter axis the
228 /// sibling `feira dialeto` verb already fans on separately at
229 /// `caixa-feira/src/cmd/dialeto.rs:110-127`) previously hand-rolled
230 /// the two-arm collapse inline as `matches!(d, CaixaDialeto::Molde |
231 /// CaixaDialeto::MoldePosicional)` — a compile-time-anonymous
232 /// two-arm literal set with no link back to the [`CaixaDialeto`]
233 /// variant declaration nor to the sibling
234 /// [`Self::palavra_canonica`] / [`Self::consumidor`] projections
235 /// that already carry the same two-arm collapse under the shared
236 /// `defmolde` / `pleme-doc-gen` axis. The `feira dialeto` verb's
237 /// [`caixa-feira/src/cmd/dialeto.rs`] carried the same
238 /// `matches!` twice — once in the `--strict-palavra` gate that
239 /// refuses a repo-surface declaration still written as
240 /// `(defcaixa …)`, once in the wrong-declaration-under-`caixa.lisp`
241 /// gate that refuses a repo-surface declaration under the filename
242 /// `feira` loads as a package manifest — with no compile-time link
243 /// between the two hand-rolled arm sets. A future arm addition (the
244 /// module doc's "third dialect" hazard actualises as a fifth arm
245 /// [`CaixaDialeto`] that belongs to the `defmolde` declaration
246 /// family — a third arity variant, an alias-declaration family
247 /// pleme-doc-gen sharpens as its schema evolves) would silently
248 /// split the two hand-rolled `matches!` arm-sets from each other
249 /// and from the paired [`Self::palavra_canonica`] projection: one
250 /// call site picks up the new arm, one does not, and the disagreement
251 /// surfaces far from the arm-addition commit as a `feira dialeto`
252 /// consumer reporting a repo-surface declaration under one gate but
253 /// not the other. Routing every "belongs to the `defmolde` family"
254 /// predicate through this one substrate primitive closes the axis:
255 /// a future arm addition lands one match arm here (a compile-time
256 /// exhaustiveness error otherwise), not a coordinated per-`matches!`
257 /// rewrite across every caller.
258 ///
259 /// Peer of the sibling [`crate::CaixaKind::requires_lib`] (0421c22)
260 /// per-arm-set predicate on the [`crate::CaixaKind`] closed-set
261 /// discriminator's "kind requires a `lib/` surface" axis — extends
262 /// the same "one canonical typed predicate per per-arm-set gate,
263 /// one dispatch on the substrate primitive" discipline onto the
264 /// [`CaixaDialeto`] closed-set discriminator's "belongs to the
265 /// `defmolde` declaration family" axis. The dialect-classification
266 /// axis's second per-arm-set predicate (first being the implicit
267 /// palavra_canonica-through-consumidor-through-descricao arm-set
268 /// collapse already carried on the sibling projections) — the first
269 /// explicitly-typed per-arm-set predicate on the axis, matching the
270 /// discipline the sibling M2 [`crate::CaixaKind`] closed-set
271 /// discriminator already carries with `requires_lib`.
272 ///
273 /// Three consumers now route through this one typed dispatch: the
274 /// [`caixa-feira`](../../caixa_feira/cmd/dialeto/index.html) verb's
275 /// `--strict-palavra` gate (refusing a repo-surface declaration
276 /// still written as `(defcaixa …)`), the same verb's wrong-
277 /// declaration-under-`caixa.lisp` gate (refusing a repo-surface
278 /// declaration under the filename `feira` loads as a package
279 /// manifest), and [`crate::Caixa::from_lisp`]'s foreign-dialect
280 /// gate (raising [`crate::ManifestError::DialetoEstrangeiro`] before
281 /// the derive's `parse_kwargs_strict` walk on any `defmolde`-family
282 /// classification — the pre-lift hand-rolled three-arm
283 /// `match { Pacote => {}, Desconhecido => {}, foreign => Err(…) }`
284 /// literal whose `foreign =>` wildcard silently absorbed anything
285 /// non-Pacote-non-Desconhecido, now the third external consumer of
286 /// the `defmolde`-family partition).
287 #[must_use]
288 pub const fn is_molde_family(self) -> bool {
289 // Routed through the derive-generated per-arm predicates
290 // [`Self::is_molde`] + [`Self::is_molde_posicional`] so the
291 // two-arm collapse links compile-time back to the closed-set
292 // typed dispatch every peer arm-set predicate on the caixa
293 // surface (e.g. [`crate::CaixaKind::requires_lib`] on the
294 // sibling `:kind` axis) now carries. Byte-equivalent to the
295 // pre-lift `matches!(self, Self::Molde | Self::MoldePosicional)`
296 // form (the derived `is_*` predicates each expand to the same
297 // `matches!(self, Self::X)` shape by construction), but a
298 // future arm rename or IsVariant `#[is_variant(name = "…")]`
299 // override lands at exactly one dispatch on the substrate
300 // primitive rather than a hand-rolled two-arm literal.
301 self.is_molde() || self.is_molde_posicional()
302 }
303}
304
305/// [`std::fmt::Display`] routed through [`CaixaDialeto::as_str`], so the
306/// pretty-printed byte-string every consumer that formats the dialect as
307/// user-facing / census text lands on (the `feira dialeto` per-manifest
308/// `--list` row, the `feira dialeto` census summary line's per-arm
309/// counters, a future M4 admission-webhook's rejection body naming the
310/// accepted-dialect set) reaches for the same `PascalCase` per-arm
311/// byte-string the [`CaixaDialeto::as_str`] helper returns.
312///
313/// Prior to this lift the [`std::fmt::Display`] impl hand-rolled its own
314/// four-arm literal-string match — the one hand-rolled per-arm dispatch
315/// on the closed [`CaixaDialeto`] discriminator that had NO substrate
316/// primitive accessor to defer to (the sibling [`CaixaDialeto::palavra_canonica`] /
317/// [`CaixaDialeto::consumidor`] / [`CaixaDialeto::descricao`] projections
318/// carry distinct byte-shapes per axis, so none of them could serve as
319/// the Display source). A future variant addition (a fifth dialect the
320/// module doc's "third dialect" hazard actualises) would land one arm at
321/// the enum and per-arm returns at the paired accessors, but a hand-rolled
322/// [`std::fmt::Display`] match would silently drop the new arm to compile-
323/// fail-at-the-match-arm-site rather than through the shared substrate
324/// primitive. Routing [`std::fmt::Display`] through [`CaixaDialeto::as_str`]
325/// closes the last unlifted per-arm `PascalCase`-name projection on the
326/// caixa surface — the seventh (and last unlifted) closed-set fieldless
327/// typed enum on the caixa surface to converge onto the same
328/// `Display`-through-`as_str` discipline the six siblings
329/// ([`crate::CaixaKind`] / [`crate::supervisor::RestartStrategy`] /
330/// [`crate::supervisor::RestartPolicy`] /
331/// [`crate::aplicacao::PlacementStrategy`] / [`crate::aplicacao::RateLimitUnit`]
332/// / [`crate::dep::DepList`]) already carry.
333impl std::fmt::Display for CaixaDialeto {
334 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335 f.write_str(self.as_str())
336 }
337}
338
339/// A source that is not a `(defcaixa …)` / `(defmolde …)` form at all.
340#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
341pub enum DialetoError {
342 #[error("source has no top-level form")]
343 Vazio,
344 #[error("top-level form is not a list — a manifest is `(defcaixa …)`")]
345 NaoEhLista,
346 #[error(
347 "top-level form is headed by `{encontrado}`, not `defcaixa` or `defmolde` \
348 (a manifest's first form must be the declaration itself)"
349 )]
350 CabecaErrada { encontrado: String },
351 #[error("manifest does not parse as tatara-lisp: {0}")]
352 Leitura(String),
353}
354
355impl DialetoError {
356 /// Construct a [`DialetoError::CabecaErrada`] naming the offending
357 /// head symbol found at the top-level form.
358 ///
359 /// Substrate primitive every [`classify_form`] wrong-head fallthrough
360 /// wire-up site now routes through, folding the pre-lift uniform
361 /// three-line `Self::CabecaErrada { encontrado: <head>.to_string() }`
362 /// one-field struct-literal onto one substrate primitive matching the
363 /// peer `LimitsError::unknown_byte_unit(unit: &str)` /
364 /// `LimitsError::unknown_duration_unit(unit: &str)`
365 /// (`limits_codec_unit_only_ctors!` — 29fac09) single-slot
366 /// discipline on the sibling one-field `{ <field>: String }` envelope
367 /// axis, and matching the peer `ManifestError::code_path_empty` /
368 /// `BehaviorError::empty_path` / `UpgradeError::duplicate_from` /
369 /// `AplicacaoError::placement_cluster_duplicate` (94dabc8 / 0e33b37 /
370 /// 7e52aec / 92b1c92) single-slot inherent-ctor discipline every
371 /// sibling `{ <field>: <T> }` error-envelope variant on caixa-core's
372 /// error surface now carries.
373 ///
374 /// The one open-coded wire-up site — `classify_form`'s wrong-head
375 /// fallthrough arm on the `head: &str` binding read from the
376 /// top-level form via [`tatara_lisp::Sexp::as_symbol`] — opened the
377 /// identical three-line
378 /// `Self::CabecaErrada { encontrado: <head>.to_string() }` block
379 /// against the codec-scoped `<head>: &str` binding. Now routes
380 /// through `DialetoError::cabeca_errada(head)`, byte-equal to the
381 /// pre-lift struct-literal on the same `&str` fixture, so any future
382 /// widening of the diagnostic shape (e.g. carrying the source-file
383 /// path alongside the head symbol, carrying the head symbol's
384 /// position offset for an authoring-surface caret pointer) lands at
385 /// exactly one dispatch on the substrate primitive rather than re-
386 /// inlining the struct-literal at every wrong-head fallthrough
387 /// consumer.
388 #[must_use]
389 pub fn cabeca_errada(encontrado: &str) -> Self {
390 Self::CabecaErrada {
391 encontrado: encontrado.to_string(),
392 }
393 }
394}
395
396/// Classify a manifest source without committing to either schema.
397///
398/// Deliberately reads only the head symbol and the set of top-level keywords —
399/// enough to route, never enough to half-parse. A classifier that started
400/// validating would grow into a third parser, which is the shape of the problem
401/// it exists to name.
402///
403/// # Errors
404/// [`DialetoError`] when the source is not a manifest declaration at all.
405pub fn classify(src: &str) -> Result<CaixaDialeto, DialetoError> {
406 let forms = tatara_lisp::read(src).map_err(|e| DialetoError::Leitura(e.to_string()))?;
407 let first = forms.first().ok_or(DialetoError::Vazio)?;
408 classify_form(first)
409}
410
411/// [`classify`] over an already-read form.
412///
413/// # Errors
414/// [`DialetoError`] when the form is not a manifest declaration.
415pub fn classify_form(form: &Sexp) -> Result<CaixaDialeto, DialetoError> {
416 let list = form.as_list().ok_or(DialetoError::NaoEhLista)?;
417 let head = list
418 .first()
419 .and_then(Sexp::as_symbol)
420 .ok_or(DialetoError::NaoEhLista)?;
421
422 match head {
423 // `defmolde` is unambiguous by construction — it exists precisely so a
424 // consumer never has to infer which declaration it holds. Both arities
425 // are the same declaration; the positional one keeps its own variant
426 // only so a census can report the split.
427 "defmolde" => {
428 return Ok(if starts_with_positional_name(&list[1..]) {
429 CaixaDialeto::MoldePosicional
430 } else {
431 CaixaDialeto::Molde
432 });
433 }
434 "defcaixa" => {}
435 other => {
436 return Err(DialetoError::cabeca_errada(other));
437 }
438 }
439
440 let args = &list[1..];
441
442 // `(defcaixa <symbol> :kind … :ecosystem …)`. Only the Molde dialect has a
443 // positional arity; `Caixa` is keyword-only, so a leading bare symbol
444 // settles it without looking further.
445 if starts_with_positional_name(args) {
446 return Ok(CaixaDialeto::MoldePosicional);
447 }
448
449 let keys = top_level_keywords(args);
450 let has = |k: &str| keys.iter().any(|s| s == k);
451
452 // Order matters, and it is not arbitrary: `:nome` and `:name` are the two
453 // required head slots and no file in the measured corpus carries both.
454 // Checking them FIRST means the decision rests on the one slot each schema
455 // makes mandatory, rather than on optional evidence like `:ecosystem`.
456 if has("nome") {
457 return Ok(CaixaDialeto::Pacote);
458 }
459 if has("name") || has("ecosystem") || has("package") {
460 return Ok(CaixaDialeto::Molde);
461 }
462 Ok(CaixaDialeto::Desconhecido)
463}
464
465/// True when the first argument is a bare symbol rather than a keyword — the
466/// positional-name arity.
467fn starts_with_positional_name(args: &[Sexp]) -> bool {
468 matches!(args.first(), Some(Sexp::Atom(Atom::Symbol(_))))
469}
470
471/// The top-level keyword names (without the leading `:`) of a kwarg list.
472///
473/// Steps in pairs so a keyword appearing as a VALUE — `:kind :Biblioteca`, or a
474/// nested `(:nome "dep" :versao "^0.1")` inside `:deps` — is never counted as a
475/// top-level slot. A naive scan for `:nome` anywhere in the source classifies
476/// every Molde manifest with a `:deps` list as a Pacote.
477fn top_level_keywords(args: &[Sexp]) -> Vec<String> {
478 let mut out = Vec::new();
479 let mut i = 0;
480 while i < args.len() {
481 if let Sexp::Atom(Atom::Keyword(k)) = &args[i] {
482 out.push(k.clone());
483 i += 2;
484 } else {
485 i += 1;
486 }
487 }
488 out
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 const PACOTE: &str = r#"
496 (defcaixa
497 :nome "checkout"
498 :versao "0.1.0"
499 :kind Servico
500 :deps ((:nome "caixa-teia" :versao "^0.1")))
501 "#;
502
503 const MOLDE: &str = r#"
504 (defcaixa
505 :name "base64"
506 :kind :Biblioteca
507 :ecosystem :rust-single-crate
508 :package {:name "base64" :version "0.22.1"}
509 :workflows [:auto-release])
510 "#;
511
512 const MOLDE_POSICIONAL: &str = r#"
513 (defcaixa todoku-go
514 :kind :Biblioteca
515 :ecosystem :go
516 :package {:name "todoku-go" :version "0.3.0"})
517 "#;
518
519 #[test]
520 fn the_package_dialect_is_recognised() {
521 assert_eq!(classify(PACOTE), Ok(CaixaDialeto::Pacote));
522 }
523
524 #[test]
525 fn the_repo_surface_dialect_is_recognised() {
526 assert_eq!(classify(MOLDE), Ok(CaixaDialeto::Molde));
527 }
528
529 #[test]
530 fn the_positional_arity_is_recognised() {
531 assert_eq!(
532 classify(MOLDE_POSICIONAL),
533 Ok(CaixaDialeto::MoldePosicional)
534 );
535 }
536
537 #[test]
538 fn defmolde_classifies_without_inference() {
539 // The whole point of the new keyword: no schema sniffing required.
540 let src = r#"(defmolde :name "x" :kind :Biblioteca :ecosystem :go)"#;
541 assert_eq!(classify(src), Ok(CaixaDialeto::Molde));
542 let pos = r"(defmolde todoku-go :kind :Biblioteca :ecosystem :go)";
543 assert_eq!(classify(pos), Ok(CaixaDialeto::MoldePosicional));
544 }
545
546 #[test]
547 fn a_nested_nome_does_not_make_a_repo_surface_look_like_a_package() {
548 // The exact failure a substring scan produces: `:deps ((:nome …))`
549 // contains `:nome`, but not as a top-level slot.
550 let src = r#"
551 (defcaixa
552 :name "x"
553 :ecosystem :rust-single-crate
554 :deps ((:nome "inner" :versao "^0.1")))
555 "#;
556 assert_eq!(classify(src), Ok(CaixaDialeto::Molde));
557 }
558
559 #[test]
560 fn a_keyword_in_value_position_is_not_a_slot() {
561 // `:kind :Biblioteca` — the value is itself a keyword. Stepping one at
562 // a time would read `:Biblioteca` as a top-level slot.
563 let src = r#"(defcaixa :kind :Biblioteca :name "x")"#;
564 assert_eq!(classify(src), Ok(CaixaDialeto::Molde));
565 }
566
567 #[test]
568 fn an_unrecognised_defcaixa_is_reported_not_guessed() {
569 let src = r#"(defcaixa :licenca "MIT")"#;
570 assert_eq!(classify(src), Ok(CaixaDialeto::Desconhecido));
571 }
572
573 #[test]
574 fn a_form_that_is_not_a_manifest_is_an_error_not_a_dialect() {
575 assert_eq!(
576 classify("(defflake :nome \"x\")"),
577 Err(DialetoError::cabeca_errada("defflake"))
578 );
579 assert_eq!(classify(""), Err(DialetoError::Vazio));
580 }
581
582 #[test]
583 fn every_dialect_names_its_consumer_and_its_canonical_keyword() {
584 // Guards the routing table itself: a new variant added without an arm
585 // here is a compile error in the match, and a variant that claims
586 // `defcaixa` while being read by pleme-doc-gen would re-open the
587 // collision this module closes. Sweeps [`CaixaDialeto::ALL`] rather
588 // than the pre-lift open-coded four-arm literal list — a future arm
589 // addition extends the slice as one edit and this pin picks it up
590 // by construction.
591 for &d in CaixaDialeto::ALL {
592 assert!(!d.descricao().is_empty(), "{d}");
593 assert!(!d.consumidor().is_empty(), "{d}");
594 }
595 assert_eq!(CaixaDialeto::Pacote.palavra_canonica(), "defcaixa");
596 assert_eq!(CaixaDialeto::Molde.palavra_canonica(), "defmolde");
597 assert_ne!(
598 CaixaDialeto::Pacote.palavra_canonica(),
599 CaixaDialeto::Molde.palavra_canonica(),
600 "the two dialects must not share a canonical keyword — that IS the defect"
601 );
602 }
603
604 #[test]
605 fn caixa_dialeto_all_enumerates_every_variant_exactly_once() {
606 // Three-legged exhaustiveness pin, peer of the sibling
607 // `caixa_kind_all_enumerates_every_variant_exactly_once`
608 // (caixa-core/src/kind.rs) /
609 // `restart_strategy_all_enumerates_every_variant_exactly_once`
610 // (caixa-core/src/supervisor.rs) shape.
611 //
612 // 1. arm-count invariant: `ALL.len()` matches the declared arm
613 // count (four — a fifth arm added without extending `ALL`
614 // fails this pin at caixa-core test time);
615 // 2. pairwise-distinctness invariant: every variant appears at
616 // most once in the slice (a duplicate arm would silently
617 // double-count in the census consumer, so the pin rejects
618 // duplicates outright);
619 // 3. coverage invariant: every literal `CaixaDialeto::X` is in
620 // the slice (the compiler-checked exhaustiveness on the peer
621 // per-arm `match self` in the accessors keeps the enum arm
622 // set and the `ALL` slice mutually aligned).
623 assert_eq!(
624 CaixaDialeto::ALL.len(),
625 4,
626 "ALL must list every arm exactly once; a fifth arm added \
627 without extending ALL fails this pin — extend ALL alongside \
628 the new variant"
629 );
630
631 let mut seen: Vec<CaixaDialeto> = Vec::new();
632 for &d in CaixaDialeto::ALL {
633 assert!(
634 !seen.contains(&d),
635 "ALL contains a duplicate arm: {d}. Every variant appears \
636 exactly once — a duplicate would double-count in every \
637 iteration consumer"
638 );
639 seen.push(d);
640 }
641
642 // Coverage: exhaustively assert every literal variant is somewhere
643 // in the slice. Written as an exhaustive `match` so a future arm
644 // addition fails to compile here (missing match arm) until the
645 // corresponding `assert` is added — the compiler enforces the pin's
646 // completeness rather than a hand-maintained variant list.
647 for variant in [
648 CaixaDialeto::Pacote,
649 CaixaDialeto::Molde,
650 CaixaDialeto::MoldePosicional,
651 CaixaDialeto::Desconhecido,
652 ] {
653 let coverage_probe = match variant {
654 CaixaDialeto::Pacote
655 | CaixaDialeto::Molde
656 | CaixaDialeto::MoldePosicional
657 | CaixaDialeto::Desconhecido => variant,
658 };
659 assert!(
660 CaixaDialeto::ALL.contains(&coverage_probe),
661 "ALL is missing variant {coverage_probe} — extend the slice"
662 );
663 }
664 }
665
666 #[test]
667 fn caixa_dialeto_all_is_const_and_matches_iteration_count() {
668 // Pins the const-ness of the slice at const-fold time. A future
669 // change that promoted `ALL` to a non-const initializer (a lazy-
670 // static, a runtime-computed Vec) would fail to compile here —
671 // the pin locks in the compile-time-known iteration surface
672 // every consumer builds against. Peer of the sibling
673 // `caixa_kind_all_is_const_and_matches_iteration_count` (kind.rs)
674 // / `restart_strategy_all_is_const_and_matches_iteration_count`
675 // (supervisor.rs) shape.
676 const ALL: &[CaixaDialeto] = CaixaDialeto::ALL;
677 assert_eq!(ALL.len(), CaixaDialeto::ALL.len());
678 // Sweep the iterator without collapsing to `.len()` so a future
679 // change to `ALL`'s carrier that decouples `.len()` from the
680 // iteration count (a lazy-computed shape, an alias `impl Iterator`
681 // return, a wrapper newtype) still passes here iff the two agree
682 // arm-for-arm; the `#[allow]` opts this local pin out of the
683 // clippy `iter_count` collapse that would defeat the intent.
684 #[allow(clippy::iter_count)]
685 let iterated = ALL.iter().count();
686 assert_eq!(iterated, CaixaDialeto::ALL.len());
687 }
688
689 #[test]
690 fn caixa_dialeto_all_covers_every_variant_by_display_probe() {
691 // Fanning `Display` over the slice sweeps the paired accessors
692 // ([`CaixaDialeto::palavra_canonica`] / [`CaixaDialeto::consumidor`]
693 // / [`CaixaDialeto::descricao`]) at every arm — every returned
694 // byte-string is non-empty (the accessors' contract). A future
695 // arm added without extending its per-arm `match self` return
696 // would compile-fail at the accessor call inside the loop;
697 // together with the `ALL.len() == 4` pin above, this locks the
698 // accessor arm-set and the `ALL` slice mutually.
699 for &d in CaixaDialeto::ALL {
700 let display_form = d.to_string();
701 assert!(
702 !display_form.is_empty(),
703 "Display must render a non-empty byte-string for every \
704 arm; empty: {d:?}"
705 );
706 // Consumidor / descricao / palavra-canonica must each surface
707 // a non-empty scalar; every downstream diagnostic consumer
708 // reaches through these accessors.
709 assert!(!d.palavra_canonica().is_empty(), "{d}");
710 assert!(!d.consumidor().is_empty(), "{d}");
711 assert!(!d.descricao().is_empty(), "{d}");
712 }
713 }
714
715 #[test]
716 fn caixa_dialeto_as_str_returns_pascal_case_variant_name() {
717 // Fail-before-pass-after per-arm shape pin: the four
718 // [`CaixaDialeto::as_str`] arms must return the canonical
719 // `PascalCase` byte-string that names the variant. Pre-lift this
720 // byte-string existed only inside the hand-rolled Display impl's
721 // four-arm literal-string match — every consumer that wanted the
722 // `PascalCase` name reached through `format!("{d}")`'s allocation
723 // path. Pinning the four arms explicitly here refuses a future
724 // regression that ever reroutes an arm to a distinct spelling
725 // (`"pacote"` lowercase, `"MoldePositional"` English rebrand,
726 // `"Unknown"` for `Desconhecido`) — the census output and the
727 // typed accessor would silently disagree until a downstream
728 // consumer surfaced the drift at census time. Peer of the sibling
729 // [`crate::supervisor::tests::restart_strategy_variants_serialize_to_lifted_scalar_values`]
730 // / `placement_strategy_variants_serialize_to_lifted_scalar_values`
731 // / `caixa_kind_as_str_returns_lifted_peer_const` shape on the
732 // sibling closed-set typed-enum discriminator axes — the seventh
733 // (and last unlifted) closed-set typed enum on the caixa surface
734 // to converge onto the same per-arm-shape-pin discipline.
735 for (variant, expected) in [
736 (CaixaDialeto::Pacote, "Pacote"),
737 (CaixaDialeto::Molde, "Molde"),
738 (CaixaDialeto::MoldePosicional, "MoldePosicional"),
739 (CaixaDialeto::Desconhecido, "Desconhecido"),
740 ] {
741 assert_eq!(
742 variant.as_str(),
743 expected,
744 "CaixaDialeto::{variant:?}.as_str() must return the \
745 canonical `PascalCase` variant-name byte-string; drift here \
746 splits the census-facing text from the substrate \
747 primitive every downstream consumer will read"
748 );
749 }
750 }
751
752 #[test]
753 fn caixa_dialeto_display_routes_through_as_str_helper() {
754 // Fail-before-pass-after convergence pin: for every arm in
755 // [`CaixaDialeto::ALL`], the [`std::fmt::Display`] rendered form
756 // must byte-equal [`CaixaDialeto::as_str`]'s return value. Pre-
757 // lift these two paths were structurally independent — the
758 // Display impl hand-rolled its own four-arm literal-string
759 // match with no compile-time link back to any substrate accessor
760 // — so a future variant rename could land at `Display` without
761 // touching a paired accessor (or vice versa), silently splitting
762 // the two paths on the renamed arm. Pinning the byte-equality
763 // here makes any such split a caixa-core build-time failure at
764 // this test rather than surfacing far from the rename commit as
765 // a downstream census consumer emitting one spelling while the
766 // typed accessor returned another. Peer of the sibling
767 // [`crate::kind::tests::caixa_kind_display_routes_through_as_str_helper`]
768 // (which pins the same convergence on the [`crate::CaixaKind`]
769 // closed-set axis) — extends the discipline onto the seventh
770 // (and last unlifted) closed-set fieldless typed enum on the
771 // caixa surface.
772 for &variant in CaixaDialeto::ALL {
773 assert_eq!(
774 variant.to_string(),
775 variant.as_str(),
776 "CaixaDialeto::{variant:?} Display must route through \
777 CaixaDialeto::as_str (single source of truth: the \
778 lifted per-arm `PascalCase` variant-name byte-string)"
779 );
780 }
781 }
782
783 #[test]
784 fn caixa_dialeto_is_molde_family_returns_true_on_molde_and_positional_arms() {
785 // Fail-before-pass-after per-arm shape pin on the two `defmolde`
786 // declaration-family arms: [`CaixaDialeto::is_molde_family`] must
787 // return `true` for [`CaixaDialeto::Molde`] and
788 // [`CaixaDialeto::MoldePosicional`] — the two-arity closure of
789 // one declaration ([`CaixaDialeto::MoldePosicional`]'s docstring:
790 // "same declaration as [`Self::Molde`], written with the package
791 // name as a bare positional symbol … one arity of one
792 // declaration, not a third schema"). A future accidental flip that
793 // reversed a per-arm arm's return without touching the paired
794 // false-arm pin would silently open the substrate primitive to
795 // false-positive on either arm — the `feira dialeto` verb's
796 // `--strict-palavra` gate would then silently accept
797 // repo-surface declarations under `(defcaixa …)` on one arm and
798 // reject them on the other. Pinning the two true arms explicitly
799 // here refuses that split at caixa-core build time.
800 assert!(
801 CaixaDialeto::Molde.is_molde_family(),
802 "CaixaDialeto::Molde.is_molde_family() must return true — \
803 Molde is the primary `defmolde` arm"
804 );
805 assert!(
806 CaixaDialeto::MoldePosicional.is_molde_family(),
807 "CaixaDialeto::MoldePosicional.is_molde_family() must return \
808 true — MoldePosicional is the positional-arity form of the \
809 same `defmolde` declaration Molde carries"
810 );
811 }
812
813 #[test]
814 fn caixa_dialeto_is_molde_family_returns_false_on_pacote_and_desconhecido_arms() {
815 // Fail-before-pass-after per-arm shape pin on the two non-`defmolde`
816 // arms: [`CaixaDialeto::is_molde_family`] must return `false` for
817 // [`CaixaDialeto::Pacote`] (the sibling `defcaixa` tatara-lisp
818 // package manifest, `palavra_canonica → "defcaixa"`) and for
819 // [`CaixaDialeto::Desconhecido`] (the residue that names no
820 // known declaration, `palavra_canonica → "?"`). Pinning the two
821 // false arms explicitly here refuses a future accidental flip
822 // that let the predicate widen to include either arm — the
823 // `feira dialeto` verb's `--strict-palavra` gate would then
824 // spuriously refuse every `(defcaixa …)` package manifest as if
825 // it were a repo-surface declaration.
826 assert!(
827 !CaixaDialeto::Pacote.is_molde_family(),
828 "CaixaDialeto::Pacote.is_molde_family() must return false — \
829 Pacote is the `defcaixa` tatara-lisp package manifest, not \
830 the `defmolde` repo-surface declaration"
831 );
832 assert!(
833 !CaixaDialeto::Desconhecido.is_molde_family(),
834 "CaixaDialeto::Desconhecido.is_molde_family() must return \
835 false — the residue arm names no known declaration; it is \
836 not silently promoted into the `defmolde` family"
837 );
838 }
839
840 #[test]
841 fn caixa_dialeto_is_molde_family_agrees_with_palavra_canonica_defmolde_projection() {
842 // Load-bearing pin: for every arm in [`CaixaDialeto::ALL`], the
843 // typed [`CaixaDialeto::is_molde_family`] predicate must agree
844 // byte-for-byte with the paired [`CaixaDialeto::palavra_canonica`]
845 // projection's `== "defmolde"` classifier — i.e. the two paths
846 // partition the four-arm discriminator set into the same
847 // `{Molde, MoldePosicional}` and `{Pacote, Desconhecido}` halves.
848 // Pre-lift the sibling [`CaixaDialeto::palavra_canonica`] projection
849 // (which returns `"defmolde"` for `Molde | MoldePosicional`,
850 // `"defcaixa"` for `Pacote`, `"?"` for `Desconhecido`) was the
851 // only substrate-side surface carrying the two-arm collapse; the
852 // hand-rolled `matches!(d, CaixaDialeto::Molde |
853 // CaixaDialeto::MoldePosicional)` sites in the `feira dialeto`
854 // verb expressed no compile-time link back to it. A future arm
855 // addition — the module doc's "third dialect" hazard actualises
856 // as a fifth arm belonging to the `defmolde` family — would land
857 // one match arm at [`Self::palavra_canonica`]'s `defmolde` return
858 // (extending the sibling projection) but silently split the
859 // hand-rolled two-arm `matches!` predicate sites if the new arm's
860 // `is_molde_family` return were forgotten. Pinning byte-equality
861 // between the two paths here makes any such split a caixa-core
862 // build-time failure at this test rather than surfacing far from
863 // the arm-addition commit as a downstream `--strict-palavra` /
864 // `caixa.lisp`-holds-wrong-declaration gate silently ignoring the
865 // new arm.
866 for &d in CaixaDialeto::ALL {
867 let via_palavra_canonica = d.palavra_canonica() == "defmolde";
868 let via_is_molde_family = d.is_molde_family();
869 assert_eq!(
870 via_is_molde_family, via_palavra_canonica,
871 "CaixaDialeto::{d:?}.is_molde_family() ({via_is_molde_family}) \
872 must agree with CaixaDialeto::{d:?}.palavra_canonica() == \
873 \"defmolde\" ({via_palavra_canonica}) — a split between the \
874 typed predicate and the sibling keyword projection would let \
875 a future arm addition land at one path and drift at the other, \
876 which is exactly the drift this pin refuses"
877 );
878 }
879 }
880
881 #[test]
882 fn caixa_dialeto_is_molde_family_is_const_fn() {
883 // Const-context pin: [`CaixaDialeto::is_molde_family`] must remain
884 // `const fn` (its match is a fieldless-arm literal-pattern
885 // discriminator, so no non-const operation exists on the resolution
886 // path). Downstream consumers reaching for the predicate from a
887 // `const` context (a future substrate-wide const-fold-driven audit
888 // table that materializes per-arm gate-membership at build time,
889 // a per-arm CR-admission-webhook gate registration in a `const`
890 // context) rely on the const-ness. A future accidental downgrade
891 // to non-`const` (an added runtime helper reachable only from a
892 // non-`const` context) trips at caixa-core build time rather than
893 // surfacing as a downstream `const`-context regression far from
894 // the predicate declaration. Peer of the sibling
895 // [`caixa_dialeto_as_str_is_const_fn`] pin on the paired
896 // [`CaixaDialeto::as_str`] byte-string axis.
897 const ARMS: [(CaixaDialeto, bool); 4] = [
898 (CaixaDialeto::Pacote, CaixaDialeto::Pacote.is_molde_family()),
899 (CaixaDialeto::Molde, CaixaDialeto::Molde.is_molde_family()),
900 (
901 CaixaDialeto::MoldePosicional,
902 CaixaDialeto::MoldePosicional.is_molde_family(),
903 ),
904 (
905 CaixaDialeto::Desconhecido,
906 CaixaDialeto::Desconhecido.is_molde_family(),
907 ),
908 ];
909 // Materialize the const-fold-evaluated table into a runtime slice
910 // assertion — carries the same `bool = const fn call` shape a raw
911 // `assert!(const_bool)` would, without tripping the
912 // `assertions_on_constants` clippy lint that a per-arm
913 // `assert!(CONST)` on a `const bool` triggers when the arm-count
914 // is enumerated flat rather than compared as a whole-table shape.
915 assert_eq!(
916 ARMS,
917 [
918 (CaixaDialeto::Pacote, false),
919 (CaixaDialeto::Molde, true),
920 (CaixaDialeto::MoldePosicional, true),
921 (CaixaDialeto::Desconhecido, false),
922 ],
923 "CaixaDialeto::is_molde_family() must evaluate in const context \
924 for every arm and land on the {{false, true, true, false}} \
925 partition — a future accidental downgrade to non-`const` \
926 would trip the const-context array-initializer here"
927 );
928 }
929
930 #[test]
931 fn caixa_dialeto_as_str_is_const_fn() {
932 // Const-context pin: [`CaixaDialeto::as_str`] must remain
933 // `const fn` (its match arms return `pub const` byte-strings, so
934 // no non-const operation exists on the resolution path).
935 // Downstream consumers reaching for the accessor from a `const`
936 // context (a future substrate-wide const-fold-driven audit table
937 // that materializes every dialect's census label at build time,
938 // a per-arm CR-admission-webhook message registration in a
939 // `const` gate) rely on the const-ness. A future accidental
940 // downgrade to non-`const` (an added runtime helper reachable
941 // only from a non-`const` context, a manual hand-rolled `impl`
942 // that shadows this method) trips at caixa-core build time
943 // rather than surfacing as a downstream `const`-context
944 // regression far from the accessor declaration. Peer of the
945 // sibling [`crate::kind::tests::caixa_kind_wire_name_is_const_fn`]
946 // pin on the paired [`crate::CaixaKind`] byte-string axis.
947 const PACOTE: &str = CaixaDialeto::Pacote.as_str();
948 const MOLDE: &str = CaixaDialeto::Molde.as_str();
949 const MOLDE_POSICIONAL: &str = CaixaDialeto::MoldePosicional.as_str();
950 const DESCONHECIDO: &str = CaixaDialeto::Desconhecido.as_str();
951 assert_eq!(PACOTE, "Pacote");
952 assert_eq!(MOLDE, "Molde");
953 assert_eq!(MOLDE_POSICIONAL, "MoldePosicional");
954 assert_eq!(DESCONHECIDO, "Desconhecido");
955 }
956
957 #[test]
958 fn caixa_dialeto_is_variant_predicates_partition_the_arm_set() {
959 // Fail-before-pass-after pin on the [`gen_platform::IsVariant`]
960 // derive: for each of the four variants at [`CaixaDialeto::ALL`]`[idx]`
961 // the observed four-slot predicate row must equal a one-hot row
962 // with the `true` at exactly `idx`. Pre-derive the closed four-arm
963 // dialect-classification partition lived only inside the paired
964 // per-arm projections' four-arm match resolvers ([`Self::as_str`] /
965 // [`Self::palavra_canonica`] / [`Self::consumidor`] /
966 // [`Self::descricao`]) plus the two-arm [`Self::is_molde_family`]
967 // hand-rolled `matches!` (now routed through the derived
968 // predicates); a future rebrand (an accidental
969 // `#[is_variant(name = "…")]` drift, a manual hand-rolled `impl`
970 // that shadows the derive-generated method, an arm rename that
971 // reroutes one arm through the wrong predicate lane) trips this
972 // pin at caixa-core build time rather than surfacing far from the
973 // derive declaration as a downstream [`Self::is_molde_family`]
974 // consumer accepting the wrong arm-set. The expected row is
975 // generated live from the [`Self::ALL`] declaration order rather
976 // than transcribed by hand so a copy-paste flip reroutes at the
977 // identity-diagonal assertion.
978 //
979 // Peer of the sibling
980 // [`crate::kind::tests::caixa_kind_is_variant_predicates_partition_the_arm_set`]
981 // / [`crate::supervisor::tests::restart_strategy_is_variant_predicates_partition_the_arm_set`]
982 // / [`crate::aplicacao::tests::placement_strategy_is_variant_predicates_partition_the_arm_set`]
983 // / [`crate::upgrade::tests::upgrade_instruction_is_variant_predicates_partition_the_arm_set`]
984 // pins on the sibling closed-set typed-enum discriminator axes.
985 for (idx, &variant) in CaixaDialeto::ALL.iter().enumerate() {
986 let observed = [
987 variant.is_pacote(),
988 variant.is_molde(),
989 variant.is_molde_posicional(),
990 variant.is_desconhecido(),
991 ];
992 let mut expected = [false; 4];
993 expected[idx] = true;
994 assert_eq!(
995 observed, expected,
996 "CaixaDialeto::{variant:?} at ALL[{idx}] is_* predicates \
997 must fire only on their own arm lane (identity diagonal); \
998 got {observed:?}",
999 );
1000 }
1001 }
1002
1003 #[test]
1004 fn caixa_dialeto_is_variant_predicates_are_const_fn() {
1005 // The [`gen_platform::IsVariant`] derive emits `const fn`
1006 // predicates on the peer [`crate::CaixaKind`] +
1007 // [`crate::upgrade::UpgradeInstruction`] +
1008 // [`crate::supervisor::RestartStrategy`] +
1009 // [`crate::supervisor::RestartPolicy`] +
1010 // [`crate::aplicacao::PlacementStrategy`] +
1011 // [`crate::aplicacao::RateLimitUnit`] +
1012 // [`crate::dep::DepList`] closed-set typed enums — pin the same
1013 // posture on [`CaixaDialeto`] so a future accidental downgrade
1014 // to non-`const` (an added runtime helper reachable only from a
1015 // non-`const` context, a manual hand-rolled `impl` that shadows
1016 // the derive-generated method) trips at caixa-core build time
1017 // rather than surfacing as a downstream `const`-context
1018 // regression far from the derive declaration.
1019 // Use `const { assert!(…) }` (peer of the sibling
1020 // [`crate::render::PathShapeViolation`] +
1021 // [`crate::aplicacao::RateLimitUnit`] +
1022 // [`caixa_theme::style::Semantic`] const-fn pins) so the
1023 // const-context evaluation trips at const-fold time without
1024 // opening a per-`const bool` `assertions_on_constants` clippy
1025 // debt row this crate does not carry today for `dialeto.rs`.
1026 const { assert!(CaixaDialeto::Pacote.is_pacote()) };
1027 const { assert!(CaixaDialeto::Molde.is_molde()) };
1028 const { assert!(CaixaDialeto::MoldePosicional.is_molde_posicional()) };
1029 const { assert!(CaixaDialeto::Desconhecido.is_desconhecido()) };
1030 }
1031
1032 #[test]
1033 fn cabeca_errada_ctor_matches_struct_literal_wrap() {
1034 // Fail-before-pass-after byte-identity pin: the lifted
1035 // [`DialetoError::cabeca_errada`] ctor MUST land on the exact
1036 // same struct-literal shape the pre-lift open-coded wire-up
1037 // block wrote by hand — `DialetoError::CabecaErrada {
1038 // encontrado: <head>.to_string() }`. A future accidental
1039 // divergence (`.into()` swap, per-arm constant substitution, an
1040 // added default field, an `.to_ascii_lowercase()` normalization
1041 // silently injected into the ctor body, a rebrand of the
1042 // `encontrado` field carrying a distinct byte-shape) trips this
1043 // pin at caixa-core build time rather than surfacing far from
1044 // the ctor declaration as a downstream `classify_form`
1045 // wrong-head consumer emitting one diagnostic shape while a
1046 // hand-written test peer opens another. Peer of the sibling
1047 // `unknown_byte_unit_ctor_matches_struct_literal_wrap`
1048 // (limits.rs; 29fac09) / `duplicate_from_ctor_matches_struct_
1049 // literal_wrap` (upgrade.rs; 7e52aec) shape on the sibling
1050 // single-slot `{ <field>: String }` envelope constructors.
1051 assert_eq!(
1052 DialetoError::cabeca_errada("defflake"),
1053 DialetoError::CabecaErrada {
1054 encontrado: "defflake".to_string(),
1055 },
1056 "DialetoError::cabeca_errada must byte-equal the pre-lift \
1057 open-coded struct-literal — a drift here means the ctor \
1058 stopped being a substrate primitive for the wrong-head \
1059 fallthrough site"
1060 );
1061 }
1062
1063 #[test]
1064 fn cabeca_errada_routes_encontrado_verbatim_across_boundary_inputs() {
1065 // Fail-before-pass-after boundary-sweep pin: the lifted
1066 // [`DialetoError::cabeca_errada`] ctor MUST route its
1067 // `encontrado: &str` argument verbatim into the
1068 // [`DialetoError::CabecaErrada`] `encontrado: String` field
1069 // for every boundary-covering `&str` input — empty string, a
1070 // canonical `defcaixa`-adjacent head, a non-ASCII head, a
1071 // whitespace-carrying head, a Unicode-full-width head. Any
1072 // wrapper-side truncation, silent `.trim()`, accidental
1073 // `.to_ascii_lowercase()` normalization, or `.into()` divergence
1074 // on the ctor body surfaces here as a byte-mismatch against the
1075 // input rather than at a downstream
1076 // [`DialetoError::to_string()`] diagnostic-shape drift at a
1077 // wrong-head fallthrough consumer far from the ctor declaration.
1078 // Peer of the sibling `limits_codec_unit_only_ctors_route_unit_
1079 // verbatim_across_every_variant` (limits.rs; 29fac09) shape on
1080 // the sibling single-slot `{ <field>: String }` envelope
1081 // boundary-sweep discipline.
1082 for encontrado in [
1083 "",
1084 "defflake",
1085 "def-molde",
1086 "defcaixa ",
1087 " defcaixa",
1088 "μdefcaixa",
1089 "\u{00A0}defcaixa",
1090 "\u{3000}defcaixa",
1091 "def\u{2028}caixa",
1092 ] {
1093 let via_ctor = DialetoError::cabeca_errada(encontrado);
1094 let via_literal = DialetoError::CabecaErrada {
1095 encontrado: encontrado.to_string(),
1096 };
1097 assert_eq!(
1098 via_ctor, via_literal,
1099 "DialetoError::cabeca_errada({encontrado:?}) must byte- \
1100 equal the open-coded struct-literal on the same input — \
1101 a drift here would let the ctor silently normalize / \
1102 truncate the head symbol before it reached the \
1103 CabecaErrada envelope"
1104 );
1105 let DialetoError::CabecaErrada { encontrado: routed } = via_ctor else {
1106 panic!(
1107 "DialetoError::cabeca_errada must construct the \
1108 CabecaErrada arm — got a different variant on \
1109 input {encontrado:?}"
1110 );
1111 };
1112 assert_eq!(
1113 routed, encontrado,
1114 "DialetoError::cabeca_errada must route the input \
1115 {encontrado:?} verbatim into the encontrado field — \
1116 any wrapper-side truncation / normalization surfaces \
1117 here rather than at a downstream diagnostic shape drift"
1118 );
1119 }
1120 }
1121
1122 #[test]
1123 fn classify_form_wrong_head_routes_through_cabeca_errada_ctor() {
1124 // Fail-before-pass-after routing pin: [`classify`]'s wrong-head
1125 // fallthrough site MUST construct its `Err(DialetoError::…)`
1126 // through the substrate-primitive [`DialetoError::cabeca_errada`]
1127 // ctor rather than through an open-coded struct-literal. Pre-
1128 // lift the wire-up hand-rolled a three-line
1129 // `Self::CabecaErrada { encontrado: other.to_string() }` block
1130 // with no compile-time link back to the substrate primitive; a
1131 // future accidental rebrand of the ctor body (an added
1132 // `.trim()` on `encontrado`, a per-arm constant prefix like
1133 // `"unknown-head:"`, a widening of the field into a
1134 // `(String, usize)` tuple carrying a caret offset) would then
1135 // silently split the two paths — the ctor consumers pick up
1136 // the new shape, the open-coded wire-up does not. Pinning
1137 // byte-equality between the observed `Err` and the ctor-
1138 // constructed `Err` refuses that split at caixa-core build
1139 // time rather than surfacing far from the wire-up commit as a
1140 // downstream diagnostic-consumer split.
1141 for head in ["defflake", "deffoobar", "defcaixaz", "let", "defmoldez"] {
1142 let src = format!("({head} :nome \"x\")");
1143 let observed = classify(&src);
1144 let via_ctor = Err(DialetoError::cabeca_errada(head));
1145 assert_eq!(
1146 observed, via_ctor,
1147 "classify({src:?}) must return the same Err shape as \
1148 DialetoError::cabeca_errada({head:?}) — a drift here \
1149 means the wire-up de-lifted its wrong-head fallthrough \
1150 arm off the substrate primitive"
1151 );
1152 }
1153 }
1154
1155 #[test]
1156 fn caixa_dialeto_is_molde_family_routes_through_is_variant_derived_predicates() {
1157 // Byte-parity pin on the post-lift [`CaixaDialeto::is_molde_family`]
1158 // convergence: for every arm in [`CaixaDialeto::ALL`], the typed
1159 // predicate must byte-equal the direct
1160 // `self.is_molde() || self.is_molde_posicional()` composition of
1161 // the two derived per-arm predicates. Pre-lift the predicate
1162 // hand-rolled `matches!(self, Self::Molde | Self::MoldePosicional)`
1163 // with no compile-time link back to the closed-set typed dispatch;
1164 // post-lift it routes through the derived predicates so a future
1165 // arm rename or `#[is_variant(name = "…")]` override lands at
1166 // exactly one dispatch on the substrate primitive. Pinning the
1167 // byte-equality here refuses a future accidental split between
1168 // the composed predicate and the paired derived predicates
1169 // (a hand-rolled shadow `impl` that overrides one path but not
1170 // the other, an accidental rebrand of `is_molde_family`'s body
1171 // back to the pre-lift `matches!` form) at caixa-core build time.
1172 for &d in CaixaDialeto::ALL {
1173 let via_derived = d.is_molde() || d.is_molde_posicional();
1174 let via_is_molde_family = d.is_molde_family();
1175 assert_eq!(
1176 via_is_molde_family, via_derived,
1177 "CaixaDialeto::{d:?}.is_molde_family() ({via_is_molde_family}) \
1178 must byte-equal the composed derived predicates \
1179 is_molde() || is_molde_posicional() ({via_derived}) — a \
1180 split between the composed predicate and its derived \
1181 building blocks would let a future arm rename land at one \
1182 path and drift at the other, which is exactly the drift \
1183 the IsVariant lift refuses"
1184 );
1185 }
1186 }
1187}