Skip to main content

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    /// Construct a [`DialetoError::Leitura`] carrying the offending
396    /// tatara-lisp reader-error message `reason` verbatim in the
397    /// variant's tuple-newtype payload.
398    ///
399    /// Substrate primitive every [`classify`] tatara-lisp-reader
400    /// map-err wire-up site now routes through, folding the pre-lift
401    /// uniform `Self::Leitura(<into-String-expr>)` tuple-newtype
402    /// construction onto one substrate primitive matching the peer
403    /// `LimitsError::empty_byte_size` / `LimitsError::empty_duration`
404    /// (7a4b003 / 319216c) `(String)` single-slot tuple-newtype
405    /// discipline on the sibling
406    /// [`crate::limits::LimitsError`] envelope's empty-shape axis of
407    /// the paired codec-magnitude family. Peer to the sibling
408    /// [`DialetoError::cabeca_errada`] ctor on the same envelope's
409    /// wrong-head axis but on the tatara-lisp-reader axis rather than
410    /// the classifier-fallthrough axis. Closes the last un-lifted
411    /// variant on [`DialetoError`] — every one of the sole wire-up
412    /// sites (the [`classify`] tatara-lisp-reader `.map_err(|e|
413    /// Self::Leitura(e.to_string()))` arm) opened the identical
414    /// `DialetoError::Leitura(<into-String-expr>)` block against the
415    /// codec-scoped `String` (`e.to_string()`) binding, so the fold
416    /// routes the site through one dispatch on a uniform
417    /// `impl Into<String>` param, byte-equal to the pre-lift
418    /// tuple-newtype construction on the same argument.
419    ///
420    /// The `impl Into<String>` bound covers both wire-up shapes on
421    /// [`classify`] — a `String` binding (`e.to_string()` on the
422    /// [`tatara_lisp::Error`]-carrying `e` binding) and a `&str`
423    /// binding (a future admission-webhook consumer probing a
424    /// caller-scoped `&'static str` fixture, a future
425    /// `feira lint --tatara-reader-round-trip` verb sweeping every
426    /// `tatara_lisp::read` return through the same shape gate) —
427    /// without forcing the caller to spell the conversion at the
428    /// wire-up site. Same shape the peer
429    /// [`crate::limits::LimitsError::empty_byte_size`] /
430    /// [`crate::limits::LimitsError::empty_duration`] /
431    /// [`crate::limits::LimitsError::bad_millicores`] /
432    /// [`crate::limits::LimitsError::bad_byte_magnitude`] /
433    /// [`crate::limits::LimitsError::bad_duration_magnitude`] folds
434    /// carry on the peer bad-magnitude and empty-shape axes of the
435    /// same paired `(String)` tuple-newtype codec-magnitude family.
436    /// `#[must_use]` fires a compile warning at any wire-up that
437    /// mistakenly discards the constructed error.
438    ///
439    /// Every future consumer that wants to construct this variant
440    /// outside [`classify`] (a deferred `feira lint --tatara-reader-
441    /// round-trip` per-caixa admission verb probing each authored
442    /// manifest against the tatara-lisp-reader shape gate, an M4
443    /// typed `mesh.pleme.io/v1alpha1/Servico` CR materializer's
444    /// per-manifest admission validator re-checking one edited
445    /// `caixa.lisp` against the reader floor, a per-`caixa.lisp`
446    /// value-shape pre-emitter probing each declared manifest ahead
447    /// of the operator's admit-cycle) now reaches the variant
448    /// through one call rather than re-inlining the tuple-newtype
449    /// block in lockstep with the pre-existing wire-up.
450    #[must_use]
451    pub fn leitura(reason: impl Into<String>) -> Self {
452        Self::Leitura(reason.into())
453    }
454}
455
456/// Classify a manifest source without committing to either schema.
457///
458/// Deliberately reads only the head symbol and the set of top-level keywords —
459/// enough to route, never enough to half-parse. A classifier that started
460/// validating would grow into a third parser, which is the shape of the problem
461/// it exists to name.
462///
463/// # Errors
464/// [`DialetoError`] when the source is not a manifest declaration at all.
465pub fn classify(src: &str) -> Result<CaixaDialeto, DialetoError> {
466    let forms = tatara_lisp::read(src).map_err(|e| DialetoError::leitura(e.to_string()))?;
467    let first = forms.first().ok_or(DialetoError::Vazio)?;
468    classify_form(first)
469}
470
471/// [`classify`] over an already-read form.
472///
473/// # Errors
474/// [`DialetoError`] when the form is not a manifest declaration.
475pub fn classify_form(form: &Sexp) -> Result<CaixaDialeto, DialetoError> {
476    let list = form.as_list().ok_or(DialetoError::NaoEhLista)?;
477    let head = list
478        .first()
479        .and_then(Sexp::as_symbol)
480        .ok_or(DialetoError::NaoEhLista)?;
481
482    match head {
483        // `defmolde` is unambiguous by construction — it exists precisely so a
484        // consumer never has to infer which declaration it holds. Both arities
485        // are the same declaration; the positional one keeps its own variant
486        // only so a census can report the split.
487        "defmolde" => {
488            return Ok(if starts_with_positional_name(&list[1..]) {
489                CaixaDialeto::MoldePosicional
490            } else {
491                CaixaDialeto::Molde
492            });
493        }
494        "defcaixa" => {}
495        other => {
496            return Err(DialetoError::cabeca_errada(other));
497        }
498    }
499
500    let args = &list[1..];
501
502    // `(defcaixa <symbol> :kind … :ecosystem …)`. Only the Molde dialect has a
503    // positional arity; `Caixa` is keyword-only, so a leading bare symbol
504    // settles it without looking further.
505    if starts_with_positional_name(args) {
506        return Ok(CaixaDialeto::MoldePosicional);
507    }
508
509    let keys = top_level_keywords(args);
510    let has = |k: &str| keys.iter().any(|s| s == k);
511
512    // Order matters, and it is not arbitrary: `:nome` and `:name` are the two
513    // required head slots and no file in the measured corpus carries both.
514    // Checking them FIRST means the decision rests on the one slot each schema
515    // makes mandatory, rather than on optional evidence like `:ecosystem`.
516    if has("nome") {
517        return Ok(CaixaDialeto::Pacote);
518    }
519    if has("name") || has("ecosystem") || has("package") {
520        return Ok(CaixaDialeto::Molde);
521    }
522    Ok(CaixaDialeto::Desconhecido)
523}
524
525/// True when the first argument is a bare symbol rather than a keyword — the
526/// positional-name arity.
527fn starts_with_positional_name(args: &[Sexp]) -> bool {
528    matches!(args.first(), Some(Sexp::Atom(Atom::Symbol(_))))
529}
530
531/// The top-level keyword names (without the leading `:`) of a kwarg list.
532///
533/// Steps in pairs so a keyword appearing as a VALUE — `:kind :Biblioteca`, or a
534/// nested `(:nome "dep" :versao "^0.1")` inside `:deps` — is never counted as a
535/// top-level slot. A naive scan for `:nome` anywhere in the source classifies
536/// every Molde manifest with a `:deps` list as a Pacote.
537fn top_level_keywords(args: &[Sexp]) -> Vec<String> {
538    let mut out = Vec::new();
539    let mut i = 0;
540    while i < args.len() {
541        if let Sexp::Atom(Atom::Keyword(k)) = &args[i] {
542            out.push(k.clone());
543            i += 2;
544        } else {
545            i += 1;
546        }
547    }
548    out
549}
550
551#[cfg(test)]
552mod tests {
553    use super::*;
554
555    const PACOTE: &str = r#"
556      (defcaixa
557        :nome   "checkout"
558        :versao "0.1.0"
559        :kind   Servico
560        :deps   ((:nome "caixa-teia" :versao "^0.1")))
561    "#;
562
563    const MOLDE: &str = r#"
564      (defcaixa
565        :name "base64"
566        :kind :Biblioteca
567        :ecosystem :rust-single-crate
568        :package {:name "base64" :version "0.22.1"}
569        :workflows [:auto-release])
570    "#;
571
572    const MOLDE_POSICIONAL: &str = r#"
573      (defcaixa todoku-go
574        :kind :Biblioteca
575        :ecosystem :go
576        :package {:name "todoku-go" :version "0.3.0"})
577    "#;
578
579    #[test]
580    fn the_package_dialect_is_recognised() {
581        assert_eq!(classify(PACOTE), Ok(CaixaDialeto::Pacote));
582    }
583
584    #[test]
585    fn the_repo_surface_dialect_is_recognised() {
586        assert_eq!(classify(MOLDE), Ok(CaixaDialeto::Molde));
587    }
588
589    #[test]
590    fn the_positional_arity_is_recognised() {
591        assert_eq!(
592            classify(MOLDE_POSICIONAL),
593            Ok(CaixaDialeto::MoldePosicional)
594        );
595    }
596
597    #[test]
598    fn defmolde_classifies_without_inference() {
599        // The whole point of the new keyword: no schema sniffing required.
600        let src = r#"(defmolde :name "x" :kind :Biblioteca :ecosystem :go)"#;
601        assert_eq!(classify(src), Ok(CaixaDialeto::Molde));
602        let pos = r"(defmolde todoku-go :kind :Biblioteca :ecosystem :go)";
603        assert_eq!(classify(pos), Ok(CaixaDialeto::MoldePosicional));
604    }
605
606    #[test]
607    fn a_nested_nome_does_not_make_a_repo_surface_look_like_a_package() {
608        // The exact failure a substring scan produces: `:deps ((:nome …))`
609        // contains `:nome`, but not as a top-level slot.
610        let src = r#"
611          (defcaixa
612            :name "x"
613            :ecosystem :rust-single-crate
614            :deps ((:nome "inner" :versao "^0.1")))
615        "#;
616        assert_eq!(classify(src), Ok(CaixaDialeto::Molde));
617    }
618
619    #[test]
620    fn a_keyword_in_value_position_is_not_a_slot() {
621        // `:kind :Biblioteca` — the value is itself a keyword. Stepping one at
622        // a time would read `:Biblioteca` as a top-level slot.
623        let src = r#"(defcaixa :kind :Biblioteca :name "x")"#;
624        assert_eq!(classify(src), Ok(CaixaDialeto::Molde));
625    }
626
627    #[test]
628    fn an_unrecognised_defcaixa_is_reported_not_guessed() {
629        let src = r#"(defcaixa :licenca "MIT")"#;
630        assert_eq!(classify(src), Ok(CaixaDialeto::Desconhecido));
631    }
632
633    #[test]
634    fn a_form_that_is_not_a_manifest_is_an_error_not_a_dialect() {
635        assert_eq!(
636            classify("(defflake :nome \"x\")"),
637            Err(DialetoError::cabeca_errada("defflake"))
638        );
639        assert_eq!(classify(""), Err(DialetoError::Vazio));
640    }
641
642    #[test]
643    fn every_dialect_names_its_consumer_and_its_canonical_keyword() {
644        // Guards the routing table itself: a new variant added without an arm
645        // here is a compile error in the match, and a variant that claims
646        // `defcaixa` while being read by pleme-doc-gen would re-open the
647        // collision this module closes. Sweeps [`CaixaDialeto::ALL`] rather
648        // than the pre-lift open-coded four-arm literal list — a future arm
649        // addition extends the slice as one edit and this pin picks it up
650        // by construction.
651        for &d in CaixaDialeto::ALL {
652            assert!(!d.descricao().is_empty(), "{d}");
653            assert!(!d.consumidor().is_empty(), "{d}");
654        }
655        assert_eq!(CaixaDialeto::Pacote.palavra_canonica(), "defcaixa");
656        assert_eq!(CaixaDialeto::Molde.palavra_canonica(), "defmolde");
657        assert_ne!(
658            CaixaDialeto::Pacote.palavra_canonica(),
659            CaixaDialeto::Molde.palavra_canonica(),
660            "the two dialects must not share a canonical keyword — that IS the defect"
661        );
662    }
663
664    #[test]
665    fn caixa_dialeto_all_enumerates_every_variant_exactly_once() {
666        // Three-legged exhaustiveness pin, peer of the sibling
667        // `caixa_kind_all_enumerates_every_variant_exactly_once`
668        // (caixa-core/src/kind.rs) /
669        // `restart_strategy_all_enumerates_every_variant_exactly_once`
670        // (caixa-core/src/supervisor.rs) shape.
671        //
672        // 1. arm-count invariant: `ALL.len()` matches the declared arm
673        //    count (four — a fifth arm added without extending `ALL`
674        //    fails this pin at caixa-core test time);
675        // 2. pairwise-distinctness invariant: every variant appears at
676        //    most once in the slice (a duplicate arm would silently
677        //    double-count in the census consumer, so the pin rejects
678        //    duplicates outright);
679        // 3. coverage invariant: every literal `CaixaDialeto::X` is in
680        //    the slice (the compiler-checked exhaustiveness on the peer
681        //    per-arm `match self` in the accessors keeps the enum arm
682        //    set and the `ALL` slice mutually aligned).
683        assert_eq!(
684            CaixaDialeto::ALL.len(),
685            4,
686            "ALL must list every arm exactly once; a fifth arm added \
687             without extending ALL fails this pin — extend ALL alongside \
688             the new variant"
689        );
690
691        let mut seen: Vec<CaixaDialeto> = Vec::new();
692        for &d in CaixaDialeto::ALL {
693            assert!(
694                !seen.contains(&d),
695                "ALL contains a duplicate arm: {d}. Every variant appears \
696                 exactly once — a duplicate would double-count in every \
697                 iteration consumer"
698            );
699            seen.push(d);
700        }
701
702        // Coverage: exhaustively assert every literal variant is somewhere
703        // in the slice. Written as an exhaustive `match` so a future arm
704        // addition fails to compile here (missing match arm) until the
705        // corresponding `assert` is added — the compiler enforces the pin's
706        // completeness rather than a hand-maintained variant list.
707        for variant in [
708            CaixaDialeto::Pacote,
709            CaixaDialeto::Molde,
710            CaixaDialeto::MoldePosicional,
711            CaixaDialeto::Desconhecido,
712        ] {
713            let coverage_probe = match variant {
714                CaixaDialeto::Pacote
715                | CaixaDialeto::Molde
716                | CaixaDialeto::MoldePosicional
717                | CaixaDialeto::Desconhecido => variant,
718            };
719            assert!(
720                CaixaDialeto::ALL.contains(&coverage_probe),
721                "ALL is missing variant {coverage_probe} — extend the slice"
722            );
723        }
724    }
725
726    #[test]
727    fn caixa_dialeto_all_is_const_and_matches_iteration_count() {
728        // Pins the const-ness of the slice at const-fold time. A future
729        // change that promoted `ALL` to a non-const initializer (a lazy-
730        // static, a runtime-computed Vec) would fail to compile here —
731        // the pin locks in the compile-time-known iteration surface
732        // every consumer builds against. Peer of the sibling
733        // `caixa_kind_all_is_const_and_matches_iteration_count` (kind.rs)
734        // / `restart_strategy_all_is_const_and_matches_iteration_count`
735        // (supervisor.rs) shape.
736        const ALL: &[CaixaDialeto] = CaixaDialeto::ALL;
737        assert_eq!(ALL.len(), CaixaDialeto::ALL.len());
738        // Sweep the iterator without collapsing to `.len()` so a future
739        // change to `ALL`'s carrier that decouples `.len()` from the
740        // iteration count (a lazy-computed shape, an alias `impl Iterator`
741        // return, a wrapper newtype) still passes here iff the two agree
742        // arm-for-arm; the `#[allow]` opts this local pin out of the
743        // clippy `iter_count` collapse that would defeat the intent.
744        #[allow(clippy::iter_count)]
745        let iterated = ALL.iter().count();
746        assert_eq!(iterated, CaixaDialeto::ALL.len());
747    }
748
749    #[test]
750    fn caixa_dialeto_all_covers_every_variant_by_display_probe() {
751        // Fanning `Display` over the slice sweeps the paired accessors
752        // ([`CaixaDialeto::palavra_canonica`] / [`CaixaDialeto::consumidor`]
753        // / [`CaixaDialeto::descricao`]) at every arm — every returned
754        // byte-string is non-empty (the accessors' contract). A future
755        // arm added without extending its per-arm `match self` return
756        // would compile-fail at the accessor call inside the loop;
757        // together with the `ALL.len() == 4` pin above, this locks the
758        // accessor arm-set and the `ALL` slice mutually.
759        for &d in CaixaDialeto::ALL {
760            let display_form = d.to_string();
761            assert!(
762                !display_form.is_empty(),
763                "Display must render a non-empty byte-string for every \
764                 arm; empty: {d:?}"
765            );
766            // Consumidor / descricao / palavra-canonica must each surface
767            // a non-empty scalar; every downstream diagnostic consumer
768            // reaches through these accessors.
769            assert!(!d.palavra_canonica().is_empty(), "{d}");
770            assert!(!d.consumidor().is_empty(), "{d}");
771            assert!(!d.descricao().is_empty(), "{d}");
772        }
773    }
774
775    #[test]
776    fn caixa_dialeto_as_str_returns_pascal_case_variant_name() {
777        // Fail-before-pass-after per-arm shape pin: the four
778        // [`CaixaDialeto::as_str`] arms must return the canonical
779        // `PascalCase` byte-string that names the variant. Pre-lift this
780        // byte-string existed only inside the hand-rolled Display impl's
781        // four-arm literal-string match — every consumer that wanted the
782        // `PascalCase` name reached through `format!("{d}")`'s allocation
783        // path. Pinning the four arms explicitly here refuses a future
784        // regression that ever reroutes an arm to a distinct spelling
785        // (`"pacote"` lowercase, `"MoldePositional"` English rebrand,
786        // `"Unknown"` for `Desconhecido`) — the census output and the
787        // typed accessor would silently disagree until a downstream
788        // consumer surfaced the drift at census time. Peer of the sibling
789        // [`crate::supervisor::tests::restart_strategy_variants_serialize_to_lifted_scalar_values`]
790        // / `placement_strategy_variants_serialize_to_lifted_scalar_values`
791        // / `caixa_kind_as_str_returns_lifted_peer_const` shape on the
792        // sibling closed-set typed-enum discriminator axes — the seventh
793        // (and last unlifted) closed-set typed enum on the caixa surface
794        // to converge onto the same per-arm-shape-pin discipline.
795        for (variant, expected) in [
796            (CaixaDialeto::Pacote, "Pacote"),
797            (CaixaDialeto::Molde, "Molde"),
798            (CaixaDialeto::MoldePosicional, "MoldePosicional"),
799            (CaixaDialeto::Desconhecido, "Desconhecido"),
800        ] {
801            assert_eq!(
802                variant.as_str(),
803                expected,
804                "CaixaDialeto::{variant:?}.as_str() must return the \
805                 canonical `PascalCase` variant-name byte-string; drift here \
806                 splits the census-facing text from the substrate \
807                 primitive every downstream consumer will read"
808            );
809        }
810    }
811
812    #[test]
813    fn caixa_dialeto_display_routes_through_as_str_helper() {
814        // Fail-before-pass-after convergence pin: for every arm in
815        // [`CaixaDialeto::ALL`], the [`std::fmt::Display`] rendered form
816        // must byte-equal [`CaixaDialeto::as_str`]'s return value. Pre-
817        // lift these two paths were structurally independent — the
818        // Display impl hand-rolled its own four-arm literal-string
819        // match with no compile-time link back to any substrate accessor
820        // — so a future variant rename could land at `Display` without
821        // touching a paired accessor (or vice versa), silently splitting
822        // the two paths on the renamed arm. Pinning the byte-equality
823        // here makes any such split a caixa-core build-time failure at
824        // this test rather than surfacing far from the rename commit as
825        // a downstream census consumer emitting one spelling while the
826        // typed accessor returned another. Peer of the sibling
827        // [`crate::kind::tests::caixa_kind_display_routes_through_as_str_helper`]
828        // (which pins the same convergence on the [`crate::CaixaKind`]
829        // closed-set axis) — extends the discipline onto the seventh
830        // (and last unlifted) closed-set fieldless typed enum on the
831        // caixa surface.
832        for &variant in CaixaDialeto::ALL {
833            assert_eq!(
834                variant.to_string(),
835                variant.as_str(),
836                "CaixaDialeto::{variant:?} Display must route through \
837                 CaixaDialeto::as_str (single source of truth: the \
838                 lifted per-arm `PascalCase` variant-name byte-string)"
839            );
840        }
841    }
842
843    #[test]
844    fn caixa_dialeto_is_molde_family_returns_true_on_molde_and_positional_arms() {
845        // Fail-before-pass-after per-arm shape pin on the two `defmolde`
846        // declaration-family arms: [`CaixaDialeto::is_molde_family`] must
847        // return `true` for [`CaixaDialeto::Molde`] and
848        // [`CaixaDialeto::MoldePosicional`] — the two-arity closure of
849        // one declaration ([`CaixaDialeto::MoldePosicional`]'s docstring:
850        // "same declaration as [`Self::Molde`], written with the package
851        // name as a bare positional symbol … one arity of one
852        // declaration, not a third schema"). A future accidental flip that
853        // reversed a per-arm arm's return without touching the paired
854        // false-arm pin would silently open the substrate primitive to
855        // false-positive on either arm — the `feira dialeto` verb's
856        // `--strict-palavra` gate would then silently accept
857        // repo-surface declarations under `(defcaixa …)` on one arm and
858        // reject them on the other. Pinning the two true arms explicitly
859        // here refuses that split at caixa-core build time.
860        assert!(
861            CaixaDialeto::Molde.is_molde_family(),
862            "CaixaDialeto::Molde.is_molde_family() must return true — \
863             Molde is the primary `defmolde` arm"
864        );
865        assert!(
866            CaixaDialeto::MoldePosicional.is_molde_family(),
867            "CaixaDialeto::MoldePosicional.is_molde_family() must return \
868             true — MoldePosicional is the positional-arity form of the \
869             same `defmolde` declaration Molde carries"
870        );
871    }
872
873    #[test]
874    fn caixa_dialeto_is_molde_family_returns_false_on_pacote_and_desconhecido_arms() {
875        // Fail-before-pass-after per-arm shape pin on the two non-`defmolde`
876        // arms: [`CaixaDialeto::is_molde_family`] must return `false` for
877        // [`CaixaDialeto::Pacote`] (the sibling `defcaixa` tatara-lisp
878        // package manifest, `palavra_canonica → "defcaixa"`) and for
879        // [`CaixaDialeto::Desconhecido`] (the residue that names no
880        // known declaration, `palavra_canonica → "?"`). Pinning the two
881        // false arms explicitly here refuses a future accidental flip
882        // that let the predicate widen to include either arm — the
883        // `feira dialeto` verb's `--strict-palavra` gate would then
884        // spuriously refuse every `(defcaixa …)` package manifest as if
885        // it were a repo-surface declaration.
886        assert!(
887            !CaixaDialeto::Pacote.is_molde_family(),
888            "CaixaDialeto::Pacote.is_molde_family() must return false — \
889             Pacote is the `defcaixa` tatara-lisp package manifest, not \
890             the `defmolde` repo-surface declaration"
891        );
892        assert!(
893            !CaixaDialeto::Desconhecido.is_molde_family(),
894            "CaixaDialeto::Desconhecido.is_molde_family() must return \
895             false — the residue arm names no known declaration; it is \
896             not silently promoted into the `defmolde` family"
897        );
898    }
899
900    #[test]
901    fn caixa_dialeto_is_molde_family_agrees_with_palavra_canonica_defmolde_projection() {
902        // Load-bearing pin: for every arm in [`CaixaDialeto::ALL`], the
903        // typed [`CaixaDialeto::is_molde_family`] predicate must agree
904        // byte-for-byte with the paired [`CaixaDialeto::palavra_canonica`]
905        // projection's `== "defmolde"` classifier — i.e. the two paths
906        // partition the four-arm discriminator set into the same
907        // `{Molde, MoldePosicional}` and `{Pacote, Desconhecido}` halves.
908        // Pre-lift the sibling [`CaixaDialeto::palavra_canonica`] projection
909        // (which returns `"defmolde"` for `Molde | MoldePosicional`,
910        // `"defcaixa"` for `Pacote`, `"?"` for `Desconhecido`) was the
911        // only substrate-side surface carrying the two-arm collapse; the
912        // hand-rolled `matches!(d, CaixaDialeto::Molde |
913        // CaixaDialeto::MoldePosicional)` sites in the `feira dialeto`
914        // verb expressed no compile-time link back to it. A future arm
915        // addition — the module doc's "third dialect" hazard actualises
916        // as a fifth arm belonging to the `defmolde` family — would land
917        // one match arm at [`Self::palavra_canonica`]'s `defmolde` return
918        // (extending the sibling projection) but silently split the
919        // hand-rolled two-arm `matches!` predicate sites if the new arm's
920        // `is_molde_family` return were forgotten. Pinning byte-equality
921        // between the two paths here makes any such split a caixa-core
922        // build-time failure at this test rather than surfacing far from
923        // the arm-addition commit as a downstream `--strict-palavra` /
924        // `caixa.lisp`-holds-wrong-declaration gate silently ignoring the
925        // new arm.
926        for &d in CaixaDialeto::ALL {
927            let via_palavra_canonica = d.palavra_canonica() == "defmolde";
928            let via_is_molde_family = d.is_molde_family();
929            assert_eq!(
930                via_is_molde_family, via_palavra_canonica,
931                "CaixaDialeto::{d:?}.is_molde_family() ({via_is_molde_family}) \
932                 must agree with CaixaDialeto::{d:?}.palavra_canonica() == \
933                 \"defmolde\" ({via_palavra_canonica}) — a split between the \
934                 typed predicate and the sibling keyword projection would let \
935                 a future arm addition land at one path and drift at the other, \
936                 which is exactly the drift this pin refuses"
937            );
938        }
939    }
940
941    #[test]
942    fn caixa_dialeto_is_molde_family_is_const_fn() {
943        // Const-context pin: [`CaixaDialeto::is_molde_family`] must remain
944        // `const fn` (its match is a fieldless-arm literal-pattern
945        // discriminator, so no non-const operation exists on the resolution
946        // path). Downstream consumers reaching for the predicate from a
947        // `const` context (a future substrate-wide const-fold-driven audit
948        // table that materializes per-arm gate-membership at build time,
949        // a per-arm CR-admission-webhook gate registration in a `const`
950        // context) rely on the const-ness. A future accidental downgrade
951        // to non-`const` (an added runtime helper reachable only from a
952        // non-`const` context) trips at caixa-core build time rather than
953        // surfacing as a downstream `const`-context regression far from
954        // the predicate declaration. Peer of the sibling
955        // [`caixa_dialeto_as_str_is_const_fn`] pin on the paired
956        // [`CaixaDialeto::as_str`] byte-string axis.
957        const ARMS: [(CaixaDialeto, bool); 4] = [
958            (CaixaDialeto::Pacote, CaixaDialeto::Pacote.is_molde_family()),
959            (CaixaDialeto::Molde, CaixaDialeto::Molde.is_molde_family()),
960            (
961                CaixaDialeto::MoldePosicional,
962                CaixaDialeto::MoldePosicional.is_molde_family(),
963            ),
964            (
965                CaixaDialeto::Desconhecido,
966                CaixaDialeto::Desconhecido.is_molde_family(),
967            ),
968        ];
969        // Materialize the const-fold-evaluated table into a runtime slice
970        // assertion — carries the same `bool = const fn call` shape a raw
971        // `assert!(const_bool)` would, without tripping the
972        // `assertions_on_constants` clippy lint that a per-arm
973        // `assert!(CONST)` on a `const bool` triggers when the arm-count
974        // is enumerated flat rather than compared as a whole-table shape.
975        assert_eq!(
976            ARMS,
977            [
978                (CaixaDialeto::Pacote, false),
979                (CaixaDialeto::Molde, true),
980                (CaixaDialeto::MoldePosicional, true),
981                (CaixaDialeto::Desconhecido, false),
982            ],
983            "CaixaDialeto::is_molde_family() must evaluate in const context \
984             for every arm and land on the {{false, true, true, false}} \
985             partition — a future accidental downgrade to non-`const` \
986             would trip the const-context array-initializer here"
987        );
988    }
989
990    #[test]
991    fn caixa_dialeto_as_str_is_const_fn() {
992        // Const-context pin: [`CaixaDialeto::as_str`] must remain
993        // `const fn` (its match arms return `pub const` byte-strings, so
994        // no non-const operation exists on the resolution path).
995        // Downstream consumers reaching for the accessor from a `const`
996        // context (a future substrate-wide const-fold-driven audit table
997        // that materializes every dialect's census label at build time,
998        // a per-arm CR-admission-webhook message registration in a
999        // `const` gate) rely on the const-ness. A future accidental
1000        // downgrade to non-`const` (an added runtime helper reachable
1001        // only from a non-`const` context, a manual hand-rolled `impl`
1002        // that shadows this method) trips at caixa-core build time
1003        // rather than surfacing as a downstream `const`-context
1004        // regression far from the accessor declaration. Peer of the
1005        // sibling [`crate::kind::tests::caixa_kind_wire_name_is_const_fn`]
1006        // pin on the paired [`crate::CaixaKind`] byte-string axis.
1007        const PACOTE: &str = CaixaDialeto::Pacote.as_str();
1008        const MOLDE: &str = CaixaDialeto::Molde.as_str();
1009        const MOLDE_POSICIONAL: &str = CaixaDialeto::MoldePosicional.as_str();
1010        const DESCONHECIDO: &str = CaixaDialeto::Desconhecido.as_str();
1011        assert_eq!(PACOTE, "Pacote");
1012        assert_eq!(MOLDE, "Molde");
1013        assert_eq!(MOLDE_POSICIONAL, "MoldePosicional");
1014        assert_eq!(DESCONHECIDO, "Desconhecido");
1015    }
1016
1017    #[test]
1018    fn caixa_dialeto_is_variant_predicates_partition_the_arm_set() {
1019        // Fail-before-pass-after pin on the [`gen_platform::IsVariant`]
1020        // derive: for each of the four variants at [`CaixaDialeto::ALL`]`[idx]`
1021        // the observed four-slot predicate row must equal a one-hot row
1022        // with the `true` at exactly `idx`. Pre-derive the closed four-arm
1023        // dialect-classification partition lived only inside the paired
1024        // per-arm projections' four-arm match resolvers ([`Self::as_str`] /
1025        // [`Self::palavra_canonica`] / [`Self::consumidor`] /
1026        // [`Self::descricao`]) plus the two-arm [`Self::is_molde_family`]
1027        // hand-rolled `matches!` (now routed through the derived
1028        // predicates); a future rebrand (an accidental
1029        // `#[is_variant(name = "…")]` drift, a manual hand-rolled `impl`
1030        // that shadows the derive-generated method, an arm rename that
1031        // reroutes one arm through the wrong predicate lane) trips this
1032        // pin at caixa-core build time rather than surfacing far from the
1033        // derive declaration as a downstream [`Self::is_molde_family`]
1034        // consumer accepting the wrong arm-set. The expected row is
1035        // generated live from the [`Self::ALL`] declaration order rather
1036        // than transcribed by hand so a copy-paste flip reroutes at the
1037        // identity-diagonal assertion.
1038        //
1039        // Peer of the sibling
1040        // [`crate::kind::tests::caixa_kind_is_variant_predicates_partition_the_arm_set`]
1041        // / [`crate::supervisor::tests::restart_strategy_is_variant_predicates_partition_the_arm_set`]
1042        // / [`crate::aplicacao::tests::placement_strategy_is_variant_predicates_partition_the_arm_set`]
1043        // / [`crate::upgrade::tests::upgrade_instruction_is_variant_predicates_partition_the_arm_set`]
1044        // pins on the sibling closed-set typed-enum discriminator axes.
1045        for (idx, &variant) in CaixaDialeto::ALL.iter().enumerate() {
1046            let observed = [
1047                variant.is_pacote(),
1048                variant.is_molde(),
1049                variant.is_molde_posicional(),
1050                variant.is_desconhecido(),
1051            ];
1052            let mut expected = [false; 4];
1053            expected[idx] = true;
1054            assert_eq!(
1055                observed, expected,
1056                "CaixaDialeto::{variant:?} at ALL[{idx}] is_* predicates \
1057                 must fire only on their own arm lane (identity diagonal); \
1058                 got {observed:?}",
1059            );
1060        }
1061    }
1062
1063    #[test]
1064    fn caixa_dialeto_is_variant_predicates_are_const_fn() {
1065        // The [`gen_platform::IsVariant`] derive emits `const fn`
1066        // predicates on the peer [`crate::CaixaKind`] +
1067        // [`crate::upgrade::UpgradeInstruction`] +
1068        // [`crate::supervisor::RestartStrategy`] +
1069        // [`crate::supervisor::RestartPolicy`] +
1070        // [`crate::aplicacao::PlacementStrategy`] +
1071        // [`crate::aplicacao::RateLimitUnit`] +
1072        // [`crate::dep::DepList`] closed-set typed enums — pin the same
1073        // posture on [`CaixaDialeto`] so a future accidental downgrade
1074        // to non-`const` (an added runtime helper reachable only from a
1075        // non-`const` context, a manual hand-rolled `impl` that shadows
1076        // the derive-generated method) trips at caixa-core build time
1077        // rather than surfacing as a downstream `const`-context
1078        // regression far from the derive declaration.
1079        // Use `const { assert!(…) }` (peer of the sibling
1080        // [`crate::render::PathShapeViolation`] +
1081        // [`crate::aplicacao::RateLimitUnit`] +
1082        // [`caixa_theme::style::Semantic`] const-fn pins) so the
1083        // const-context evaluation trips at const-fold time without
1084        // opening a per-`const bool` `assertions_on_constants` clippy
1085        // debt row this crate does not carry today for `dialeto.rs`.
1086        const { assert!(CaixaDialeto::Pacote.is_pacote()) };
1087        const { assert!(CaixaDialeto::Molde.is_molde()) };
1088        const { assert!(CaixaDialeto::MoldePosicional.is_molde_posicional()) };
1089        const { assert!(CaixaDialeto::Desconhecido.is_desconhecido()) };
1090    }
1091
1092    #[test]
1093    fn cabeca_errada_ctor_matches_struct_literal_wrap() {
1094        // Fail-before-pass-after byte-identity pin: the lifted
1095        // [`DialetoError::cabeca_errada`] ctor MUST land on the exact
1096        // same struct-literal shape the pre-lift open-coded wire-up
1097        // block wrote by hand — `DialetoError::CabecaErrada {
1098        // encontrado: <head>.to_string() }`. A future accidental
1099        // divergence (`.into()` swap, per-arm constant substitution, an
1100        // added default field, an `.to_ascii_lowercase()` normalization
1101        // silently injected into the ctor body, a rebrand of the
1102        // `encontrado` field carrying a distinct byte-shape) trips this
1103        // pin at caixa-core build time rather than surfacing far from
1104        // the ctor declaration as a downstream `classify_form`
1105        // wrong-head consumer emitting one diagnostic shape while a
1106        // hand-written test peer opens another. Peer of the sibling
1107        // `unknown_byte_unit_ctor_matches_struct_literal_wrap`
1108        // (limits.rs; 29fac09) / `duplicate_from_ctor_matches_struct_
1109        // literal_wrap` (upgrade.rs; 7e52aec) shape on the sibling
1110        // single-slot `{ <field>: String }` envelope constructors.
1111        assert_eq!(
1112            DialetoError::cabeca_errada("defflake"),
1113            DialetoError::CabecaErrada {
1114                encontrado: "defflake".to_string(),
1115            },
1116            "DialetoError::cabeca_errada must byte-equal the pre-lift \
1117             open-coded struct-literal — a drift here means the ctor \
1118             stopped being a substrate primitive for the wrong-head \
1119             fallthrough site"
1120        );
1121    }
1122
1123    #[test]
1124    fn cabeca_errada_routes_encontrado_verbatim_across_boundary_inputs() {
1125        // Fail-before-pass-after boundary-sweep pin: the lifted
1126        // [`DialetoError::cabeca_errada`] ctor MUST route its
1127        // `encontrado: &str` argument verbatim into the
1128        // [`DialetoError::CabecaErrada`] `encontrado: String` field
1129        // for every boundary-covering `&str` input — empty string, a
1130        // canonical `defcaixa`-adjacent head, a non-ASCII head, a
1131        // whitespace-carrying head, a Unicode-full-width head. Any
1132        // wrapper-side truncation, silent `.trim()`, accidental
1133        // `.to_ascii_lowercase()` normalization, or `.into()` divergence
1134        // on the ctor body surfaces here as a byte-mismatch against the
1135        // input rather than at a downstream
1136        // [`DialetoError::to_string()`] diagnostic-shape drift at a
1137        // wrong-head fallthrough consumer far from the ctor declaration.
1138        // Peer of the sibling `limits_codec_unit_only_ctors_route_unit_
1139        // verbatim_across_every_variant` (limits.rs; 29fac09) shape on
1140        // the sibling single-slot `{ <field>: String }` envelope
1141        // boundary-sweep discipline.
1142        for encontrado in [
1143            "",
1144            "defflake",
1145            "def-molde",
1146            "defcaixa ",
1147            " defcaixa",
1148            "μdefcaixa",
1149            "\u{00A0}defcaixa",
1150            "\u{3000}defcaixa",
1151            "def\u{2028}caixa",
1152        ] {
1153            let via_ctor = DialetoError::cabeca_errada(encontrado);
1154            let via_literal = DialetoError::CabecaErrada {
1155                encontrado: encontrado.to_string(),
1156            };
1157            assert_eq!(
1158                via_ctor, via_literal,
1159                "DialetoError::cabeca_errada({encontrado:?}) must byte- \
1160                 equal the open-coded struct-literal on the same input — \
1161                 a drift here would let the ctor silently normalize / \
1162                 truncate the head symbol before it reached the \
1163                 CabecaErrada envelope"
1164            );
1165            let DialetoError::CabecaErrada { encontrado: routed } = via_ctor else {
1166                panic!(
1167                    "DialetoError::cabeca_errada must construct the \
1168                     CabecaErrada arm — got a different variant on \
1169                     input {encontrado:?}"
1170                );
1171            };
1172            assert_eq!(
1173                routed, encontrado,
1174                "DialetoError::cabeca_errada must route the input \
1175                 {encontrado:?} verbatim into the encontrado field — \
1176                 any wrapper-side truncation / normalization surfaces \
1177                 here rather than at a downstream diagnostic shape drift"
1178            );
1179        }
1180    }
1181
1182    #[test]
1183    fn classify_form_wrong_head_routes_through_cabeca_errada_ctor() {
1184        // Fail-before-pass-after routing pin: [`classify`]'s wrong-head
1185        // fallthrough site MUST construct its `Err(DialetoError::…)`
1186        // through the substrate-primitive [`DialetoError::cabeca_errada`]
1187        // ctor rather than through an open-coded struct-literal. Pre-
1188        // lift the wire-up hand-rolled a three-line
1189        // `Self::CabecaErrada { encontrado: other.to_string() }` block
1190        // with no compile-time link back to the substrate primitive; a
1191        // future accidental rebrand of the ctor body (an added
1192        // `.trim()` on `encontrado`, a per-arm constant prefix like
1193        // `"unknown-head:"`, a widening of the field into a
1194        // `(String, usize)` tuple carrying a caret offset) would then
1195        // silently split the two paths — the ctor consumers pick up
1196        // the new shape, the open-coded wire-up does not. Pinning
1197        // byte-equality between the observed `Err` and the ctor-
1198        // constructed `Err` refuses that split at caixa-core build
1199        // time rather than surfacing far from the wire-up commit as a
1200        // downstream diagnostic-consumer split.
1201        for head in ["defflake", "deffoobar", "defcaixaz", "let", "defmoldez"] {
1202            let src = format!("({head} :nome \"x\")");
1203            let observed = classify(&src);
1204            let via_ctor = Err(DialetoError::cabeca_errada(head));
1205            assert_eq!(
1206                observed, via_ctor,
1207                "classify({src:?}) must return the same Err shape as \
1208                 DialetoError::cabeca_errada({head:?}) — a drift here \
1209                 means the wire-up de-lifted its wrong-head fallthrough \
1210                 arm off the substrate primitive"
1211            );
1212        }
1213    }
1214
1215    #[test]
1216    fn leitura_ctor_matches_tuple_literal_wrap_on_str_binding() {
1217        // Fail-before-pass-after byte-identity pin: the lifted
1218        // [`DialetoError::leitura`] ctor MUST land on the exact same
1219        // tuple-newtype wrap the pre-lift open-coded wire-up block wrote by
1220        // hand — `DialetoError::Leitura(<into-String-expr>)`. A future
1221        // accidental divergence (an added `.trim()` on the reader reason,
1222        // a per-arm constant prefix like `"tatara-lisp:"`, a widening of
1223        // the tuple carrying a caret offset, a rebrand of the payload
1224        // carrying a distinct byte-shape) trips this pin at caixa-core
1225        // build time rather than surfacing far from the ctor declaration
1226        // as a downstream [`classify`] tatara-lisp-reader consumer
1227        // emitting one diagnostic shape while a hand-written test peer
1228        // opens another. Peer of the sibling
1229        // `cabeca_errada_ctor_matches_struct_literal_wrap` pin above on
1230        // the same [`DialetoError`] envelope's wrong-head axis, and of
1231        // the peer `LimitsError::empty_byte_size` /
1232        // `LimitsError::empty_duration` (7a4b003 / 319216c) shape on the
1233        // sibling `(String)` single-slot tuple-newtype envelope
1234        // constructors.
1235        let reason: &str = "unclosed paren at 1:12";
1236        assert_eq!(
1237            DialetoError::leitura(reason),
1238            DialetoError::Leitura(reason.to_string()),
1239            "DialetoError::leitura must byte-equal the pre-lift open-coded \
1240             tuple-newtype wrap — a drift here means the ctor stopped \
1241             being a substrate primitive for the tatara-lisp-reader \
1242             fallthrough site"
1243        );
1244    }
1245
1246    #[test]
1247    fn leitura_ctor_matches_tuple_literal_wrap_on_string_binding() {
1248        // Fail-before-pass-after byte-identity pin on the `String` wire-up
1249        // shape: the lifted [`DialetoError::leitura`] ctor MUST land on
1250        // the same tuple-newtype wrap when the caller passes an owned
1251        // `String` (the actual [`classify`] wire-up shape — `e.to_string()`
1252        // on a [`tatara_lisp::Error`]-carrying binding). Pins that the
1253        // `impl Into<String>` param covers the owned-`String` path with no
1254        // silent double-allocation or intermediate `&str` reslicing. Peer
1255        // of the sibling `_on_str_binding` pin above — together they close
1256        // the `impl Into<String>` bound's two authored wire-up shapes on
1257        // the ctor's substrate primitive.
1258        let reason: String = String::from("read: unexpected EOF at 3:1");
1259        let via_ctor = DialetoError::leitura(reason.clone());
1260        let via_literal = DialetoError::Leitura(reason.clone());
1261        assert_eq!(
1262            via_ctor, via_literal,
1263            "DialetoError::leitura must byte-equal the pre-lift open-coded \
1264             tuple-newtype wrap on the same owned-String fixture — a drift \
1265             here would let the ctor silently reshape the reader reason \
1266             before it reached the Leitura envelope"
1267        );
1268        let DialetoError::Leitura(routed) = via_ctor else {
1269            panic!(
1270                "DialetoError::leitura must construct the Leitura arm — \
1271                 got a different variant on input {reason:?}"
1272            );
1273        };
1274        assert_eq!(
1275            routed, reason,
1276            "DialetoError::leitura must route the input {reason:?} \
1277             verbatim into the tuple-newtype payload — any wrapper-side \
1278             truncation / normalization surfaces here rather than at a \
1279             downstream diagnostic shape drift"
1280        );
1281    }
1282
1283    #[test]
1284    fn leitura_routes_reason_verbatim_across_boundary_inputs() {
1285        // Fail-before-pass-after boundary-sweep pin: the lifted
1286        // [`DialetoError::leitura`] ctor MUST route its
1287        // `reason: impl Into<String>` argument verbatim into the
1288        // [`DialetoError::Leitura`] tuple-newtype `String` payload for
1289        // every boundary-covering input — empty string, a canonical
1290        // tatara-lisp reader error, a non-ASCII reason, a
1291        // whitespace-carrying reason, a Unicode-full-width reason. Any
1292        // wrapper-side truncation, silent `.trim()`, accidental
1293        // `.to_ascii_lowercase()` normalization, or `.into()` divergence
1294        // on the ctor body surfaces here as a byte-mismatch against the
1295        // input rather than at a downstream [`DialetoError::to_string()`]
1296        // diagnostic-shape drift at a tatara-lisp-reader fallthrough
1297        // consumer far from the ctor declaration. Peer of the sibling
1298        // `cabeca_errada_routes_encontrado_verbatim_across_boundary_inputs`
1299        // pin above on the same [`DialetoError`] envelope's wrong-head
1300        // axis.
1301        for reason in [
1302            "",
1303            "unclosed paren at 1:12",
1304            "unexpected token ')'",
1305            "read: eof",
1306            " leading whitespace",
1307            "trailing whitespace ",
1308            "μnicode reason",
1309            "\u{00A0}NBSP-prefixed reason",
1310            "\u{3000}ideographic-space reason",
1311            "reason\u{2028}with-line-separator",
1312        ] {
1313            let via_ctor = DialetoError::leitura(reason);
1314            let via_literal = DialetoError::Leitura(reason.to_string());
1315            assert_eq!(
1316                via_ctor, via_literal,
1317                "DialetoError::leitura({reason:?}) must byte-equal the \
1318                 open-coded tuple-newtype wrap on the same input — a \
1319                 drift here would let the ctor silently normalize / \
1320                 truncate the reader reason before it reached the \
1321                 Leitura envelope"
1322            );
1323            let DialetoError::Leitura(routed) = via_ctor else {
1324                panic!(
1325                    "DialetoError::leitura must construct the Leitura \
1326                     arm — got a different variant on input {reason:?}"
1327                );
1328            };
1329            assert_eq!(
1330                routed, reason,
1331                "DialetoError::leitura must route the input {reason:?} \
1332                 verbatim into the tuple-newtype payload — any \
1333                 wrapper-side truncation / normalization surfaces here \
1334                 rather than at a downstream diagnostic shape drift"
1335            );
1336        }
1337    }
1338
1339    #[test]
1340    fn classify_reader_error_routes_through_leitura_ctor() {
1341        // Fail-before-pass-after routing pin: [`classify`]'s
1342        // tatara-lisp-reader map-err site MUST construct its
1343        // `Err(DialetoError::…)` through the substrate-primitive
1344        // [`DialetoError::leitura`] ctor rather than through an
1345        // open-coded tuple-newtype wrap. Pre-lift the wire-up hand-rolled
1346        // a `Self::Leitura(e.to_string())` block with no compile-time
1347        // link back to the substrate primitive; a future accidental
1348        // rebrand of the ctor body (an added `.trim()` on the reader
1349        // reason, a per-arm constant prefix like `"tatara-lisp:"`, a
1350        // widening of the payload into a `(String, usize)` tuple
1351        // carrying a caret offset) would then silently split the two
1352        // paths — the ctor consumers pick up the new shape, the
1353        // open-coded wire-up does not. Pinning byte-equality between
1354        // the observed `Err` and the ctor-constructed `Err` refuses
1355        // that split at caixa-core build time rather than surfacing far
1356        // from the wire-up commit as a downstream diagnostic-consumer
1357        // split. Peer of the sibling
1358        // `classify_form_wrong_head_routes_through_cabeca_errada_ctor`
1359        // pin above on the same [`DialetoError`] envelope's wrong-head
1360        // fallthrough axis.
1361        //
1362        // The malformed sources below each name a distinct
1363        // tatara-lisp-reader failure shape (unclosed paren, stray close
1364        // paren, unterminated string), so together they sweep the
1365        // reader's rejection surface rather than pinning against one
1366        // specific error message the reader upstream is free to reword.
1367        for src in [
1368            "(defcaixa :nome \"x\"",
1369            "defcaixa :nome \"x\")",
1370            "(defcaixa :nome \"unterminated",
1371        ] {
1372            let observed = classify(src);
1373            let Err(DialetoError::Leitura(reason)) = observed.clone() else {
1374                panic!(
1375                    "classify({src:?}) must return the Leitura arm — got \
1376                     {observed:?}"
1377                );
1378            };
1379            let via_ctor: Result<CaixaDialeto, DialetoError> =
1380                Err(DialetoError::leitura(reason.clone()));
1381            assert_eq!(
1382                observed, via_ctor,
1383                "classify({src:?}) must return the same Err shape as \
1384                 DialetoError::leitura({reason:?}) — a drift here means \
1385                 the wire-up de-lifted its tatara-lisp-reader fallthrough \
1386                 arm off the substrate primitive"
1387            );
1388        }
1389    }
1390
1391    #[test]
1392    fn caixa_dialeto_is_molde_family_routes_through_is_variant_derived_predicates() {
1393        // Byte-parity pin on the post-lift [`CaixaDialeto::is_molde_family`]
1394        // convergence: for every arm in [`CaixaDialeto::ALL`], the typed
1395        // predicate must byte-equal the direct
1396        // `self.is_molde() || self.is_molde_posicional()` composition of
1397        // the two derived per-arm predicates. Pre-lift the predicate
1398        // hand-rolled `matches!(self, Self::Molde | Self::MoldePosicional)`
1399        // with no compile-time link back to the closed-set typed dispatch;
1400        // post-lift it routes through the derived predicates so a future
1401        // arm rename or `#[is_variant(name = "…")]` override lands at
1402        // exactly one dispatch on the substrate primitive. Pinning the
1403        // byte-equality here refuses a future accidental split between
1404        // the composed predicate and the paired derived predicates
1405        // (a hand-rolled shadow `impl` that overrides one path but not
1406        // the other, an accidental rebrand of `is_molde_family`'s body
1407        // back to the pre-lift `matches!` form) at caixa-core build time.
1408        for &d in CaixaDialeto::ALL {
1409            let via_derived = d.is_molde() || d.is_molde_posicional();
1410            let via_is_molde_family = d.is_molde_family();
1411            assert_eq!(
1412                via_is_molde_family, via_derived,
1413                "CaixaDialeto::{d:?}.is_molde_family() ({via_is_molde_family}) \
1414                 must byte-equal the composed derived predicates \
1415                 is_molde() || is_molde_posicional() ({via_derived}) — a \
1416                 split between the composed predicate and its derived \
1417                 building blocks would let a future arm rename land at one \
1418                 path and drift at the other, which is exactly the drift \
1419                 the IsVariant lift refuses"
1420            );
1421        }
1422    }
1423}