Skip to main content

tatara_lisp/
error.rs

1use thiserror::Error;
2
3use crate::span::Span;
4
5pub type Result<T> = std::result::Result<T, LispError>;
6
7// Compile-time pairwise-distinctness witnesses — one `const _: () =
8// crate::ast::assert_str_array_pairwise_distinct(&…)` per family-wide
9// `[&'static str; N]` array on this module's closed-set diagnostic /
10// classification algebras. Each invocation is const-evaluated at
11// `cargo check` time; a regression that silently collides two entries
12// on any of these arrays fails the build rather than the test suite.
13//
14// Sibling to the runtime `_pairwise_distinct` tests (`compiler_spec_
15// io_stage_labels_pairwise_distinct`, `macro_def_head_keywords_
16// pairwise_distinct`, `unquote_form_markers_pairwise_distinct`,
17// `unquote_form_iac_forge_tags_pairwise_distinct`,
18// `unquote_form_labels_pairwise_distinct`, `kwarg_path_kind_labels_
19// pairwise_distinct`, `expected_kwarg_shape_labels_pairwise_distinct`,
20// `sexp_shape_labels_pairwise_distinct`,
21// `structural_kind_labels_pairwise_distinct`) plus the runtime cross-
22// check `assert_str_array_pairwise_distinct_accepts_every_family_
23// wide_error_module_array` on this module's tests submodule — the
24// three surfaces enforce the SAME theorem at THREE stages of the
25// toolchain (compile-time `const _` sweep, per-array runtime pin,
26// runtime cross-check), so a build that skips tests still catches
27// the regression here, and a build that runs tests catches it a
28// second and third time as safety nets if the const-eval sweep is
29// ever silently dropped.
30//
31// Column-dual peer to the FIVE `assert_str_array_pairwise_distinct`
32// witnesses on `ast.rs`'s closed-set outer algebras
33// (`Atom::BOOL_LITERALS`, `AtomKind::LABELS`, `QuoteForm::PREFIXES`,
34// `QuoteForm::IAC_FORGE_TAGS`, `QuoteForm::LABELS`) on the
35// (declaration-file) axis: `ast.rs` covers the reader / atomic-
36// payload / quote-family vocabulary; `error.rs` covers the
37// diagnostic / classification / typed-entry-rejection vocabulary
38// across NINE closed-set algebras (`CompilerSpecIoStage`,
39// `TemplateInvariantKind` (STATIC + DYNAMIC subsets),
40// `MacroDefHead`, `OptionalParamMalformedReason` (STATIC + DYNAMIC
41// subsets), `UnquoteForm` (three per-role vocabularies —
42// MARKERS / IAC_FORGE_TAGS / LABELS), `KwargPathKind`,
43// `ExpectedKwargShape`, `SexpShape`, `StructuralKind`) — the two
44// files together lift ALL EIGHTEEN family-wide `[&'static str; N]`
45// arrays declared on `tatara-lisp`'s closed-set outer algebras into
46// a COMPILE-TIME pairwise-distinctness theorem.
47//
48// `CompilerSpecIoStage::OPERATIONS` (`[&'static str; 4]` at
49// line 1744) is DELIBERATELY excluded from this block: it is a
50// variant-aligned projection whose four entries are
51// `[REALIZE_TO_DISK, REALIZE_TO_DISK, LOAD_FROM_DISK, LOAD_FROM_DISK]`
52// (the first two arms and the last two arms collapse onto the
53// per-operation labels), so the array is INTENTIONALLY non-distinct.
54// Enrolling it in this block would fail const-eval; the intended
55// invariant on `OPERATIONS` is "variant-index alignment with
56// `CompilerSpecIoStage::ALL`", not injectivity — different theorem,
57// different helper. The peer helper the substrate now carries is
58// `crate::ast::assert_str_array_is_concatenation_of_two_scalar_
59// replicas` on the (contract-shape ∈ {MANY-TO-ONE-BLOCK-CONSTANCY})
60// corner of the (`&'static str`) row; its ONE `const _` witness
61// binding `CompilerSpecIoStage::OPERATIONS` lives further down this
62// module in the STR-BLOCK-CONSTANCY witness block (search for
63// `assert_str_array_is_concatenation_of_two_scalar_replicas`).
64//
65// Adding a new family-wide `[&'static str; N]` array to this module:
66// add ONE co-located `const _: () = crate::ast::assert_str_array_
67// pairwise_distinct(&<TYPE>::<ARRAY>);` line in this block AND ONE
68// runtime call in the cross-check test on the tests submodule. A
69// regression that silently collided two entries on the new array
70// would fail `cargo check` at the module-level `const _` line
71// before the test suite runs. Rustc's forced-arity `[&'static str;
72// N]` composes with this const-eval sweep so BOTH cardinality AND
73// injectivity are compile-time theorems on the SAME array
74// declaration.
75const _: () = crate::ast::assert_str_array_pairwise_distinct(&CompilerSpecIoStage::LABELS);
76const _: () =
77    crate::ast::assert_str_array_pairwise_distinct(&TemplateInvariantKind::STATIC_MESSAGES);
78const _: () =
79    crate::ast::assert_str_array_pairwise_distinct(&TemplateInvariantKind::DYNAMIC_DESCRIPTORS);
80const _: () = crate::ast::assert_str_array_pairwise_distinct(&MacroDefHead::KEYWORDS);
81const _: () =
82    crate::ast::assert_str_array_pairwise_distinct(&OptionalParamMalformedReason::STATIC_LABELS);
83const _: () = crate::ast::assert_str_array_pairwise_distinct(
84    &OptionalParamMalformedReason::DYNAMIC_DESCRIPTORS,
85);
86const _: () = crate::ast::assert_str_array_pairwise_distinct(&UnquoteForm::MARKERS);
87const _: () = crate::ast::assert_str_array_pairwise_distinct(&UnquoteForm::IAC_FORGE_TAGS);
88const _: () = crate::ast::assert_str_array_pairwise_distinct(&UnquoteForm::LABELS);
89const _: () = crate::ast::assert_str_array_pairwise_distinct(&KwargPathKind::LABELS);
90const _: () = crate::ast::assert_str_array_pairwise_distinct(&ExpectedKwargShape::LABELS);
91const _: () = crate::ast::assert_str_array_pairwise_distinct(&SexpShape::LABELS);
92const _: () = crate::ast::assert_str_array_pairwise_distinct(&StructuralKind::LABELS);
93
94// Compile-time NONEMPTY-CARDINALITY-LOWER-BOUND witnesses — one
95// `const _: () = crate::ast::assert_str_array_all_nonempty(&…)` per
96// family-wide `[&'static str; N]` array declared in this module.
97// Sibling to the `_pairwise_distinct` witnesses above — those pin
98// INJECTIVITY on each array (`∀ i ≠ j : arr[i] ≠ arr[j]`), these pin
99// the strictly-weaker per-entry cardinality gate (`∀ i : arr[i].len()
100// > 0`). The two contracts compose orthogonally: an array carrying
101// `["", ""]` fails INJECTIVITY at the zero-length-pair corner, but an
102// array carrying `["", "a"]` passes INJECTIVITY while failing NONEMPTY
103// — so the NONEMPTY witnesses close the remaining `""`-carrying corner
104// that INJECTIVITY alone cannot pin. Every consumer that spells a
105// closed-set variant through its `&'static str` label
106// (`AtomKind::label` / `QuoteForm::label` / `UnquoteForm::label` /
107// `StructuralKind::label` / `SexpShape::label` label projections;
108// `KwargPathKind::label` / `ExpectedKwargShape::label` for the kwarg-
109// diagnostic vocabulary; `MacroDefHead::KEYWORDS` /
110// `UnquoteForm::MARKERS` / `UnquoteForm::IAC_FORGE_TAGS` for the
111// reader-boundary sub-vocabularies; `CompilerSpecIoStage::LABELS` for
112// the compiler-spec I/O stage diagnostic) treats each entry as a
113// NONEMPTY identifier and would silently mis-behave on a `""` entry.
114// The two `TemplateInvariantKind` message / descriptor arrays and the
115// two `OptionalParamMalformedReason` message / descriptor arrays are
116// nonempty for the same reason — an empty diagnostic message erases
117// the failure signal at the display boundary.
118const _: () = crate::ast::assert_str_array_all_nonempty(&CompilerSpecIoStage::LABELS);
119const _: () = crate::ast::assert_str_array_all_nonempty(&TemplateInvariantKind::STATIC_MESSAGES);
120const _: () =
121    crate::ast::assert_str_array_all_nonempty(&TemplateInvariantKind::DYNAMIC_DESCRIPTORS);
122const _: () = crate::ast::assert_str_array_all_nonempty(&MacroDefHead::KEYWORDS);
123const _: () =
124    crate::ast::assert_str_array_all_nonempty(&OptionalParamMalformedReason::STATIC_LABELS);
125const _: () =
126    crate::ast::assert_str_array_all_nonempty(&OptionalParamMalformedReason::DYNAMIC_DESCRIPTORS);
127const _: () = crate::ast::assert_str_array_all_nonempty(&UnquoteForm::MARKERS);
128const _: () = crate::ast::assert_str_array_all_nonempty(&UnquoteForm::IAC_FORGE_TAGS);
129const _: () = crate::ast::assert_str_array_all_nonempty(&UnquoteForm::LABELS);
130const _: () = crate::ast::assert_str_array_all_nonempty(&KwargPathKind::LABELS);
131const _: () = crate::ast::assert_str_array_all_nonempty(&ExpectedKwargShape::LABELS);
132const _: () = crate::ast::assert_str_array_all_nonempty(&SexpShape::LABELS);
133const _: () = crate::ast::assert_str_array_all_nonempty(&StructuralKind::LABELS);
134
135// Compile-time ASCII-BYTE-RANGE witnesses — one `const _: () =
136// crate::ast::assert_str_array_all_ascii(&…)` per family-wide
137// `[&'static str; N]` array declared in this module. Sibling to the
138// `_all_nonempty` witnesses above on the (per-entry × contract-shape)
139// axis: those pin the length-lower-bound gate (`∀ i : arr[i].len() >
140// 0`), these pin the strictly-orthogonal byte-range gate (`∀ i, ∀ b ∈
141// arr[i].as_bytes() : b <= 0x7F`). The two contracts compose
142// orthogonally — an array carrying `["café"]` passes NONEMPTY while
143// failing ASCII; `["", "a"]` passes ASCII while failing NONEMPTY.
144// Every consumer that ships an entry through a seven-bit-clean
145// downstream surface (K8s annotation keys + label values; YAML
146// flow-scalar map keys; BLAKE3 hash inputs; Rust byte-pattern
147// `matches!` arms) treats each entry as ASCII — a non-ASCII byte
148// silently invites Unicode-normalization drift on the wire and
149// lookalike-label collisions that byte-equality parsing cannot
150// detect. Post-lift a regression that silently re-inlined one label
151// constant to a lookalike non-ASCII spelling fails at `cargo check`
152// BEFORE any test scheduler runs. The thirteen arrays covered here
153// mirror the thirteen arrays already pinned by the `_all_nonempty`
154// witnesses above — the (per-entry × contract-shape) coverage matrix
155// on the (`&'static str`) row of this module now holds at BOTH
156// corners {NONEMPTY, ASCII} for every family-wide array declared
157// here.
158const _: () = crate::ast::assert_str_array_all_ascii(&CompilerSpecIoStage::LABELS);
159const _: () = crate::ast::assert_str_array_all_ascii(&TemplateInvariantKind::STATIC_MESSAGES);
160const _: () = crate::ast::assert_str_array_all_ascii(&TemplateInvariantKind::DYNAMIC_DESCRIPTORS);
161const _: () = crate::ast::assert_str_array_all_ascii(&MacroDefHead::KEYWORDS);
162const _: () = crate::ast::assert_str_array_all_ascii(&OptionalParamMalformedReason::STATIC_LABELS);
163const _: () =
164    crate::ast::assert_str_array_all_ascii(&OptionalParamMalformedReason::DYNAMIC_DESCRIPTORS);
165const _: () = crate::ast::assert_str_array_all_ascii(&UnquoteForm::MARKERS);
166const _: () = crate::ast::assert_str_array_all_ascii(&UnquoteForm::IAC_FORGE_TAGS);
167const _: () = crate::ast::assert_str_array_all_ascii(&UnquoteForm::LABELS);
168const _: () = crate::ast::assert_str_array_all_ascii(&KwargPathKind::LABELS);
169const _: () = crate::ast::assert_str_array_all_ascii(&ExpectedKwargShape::LABELS);
170const _: () = crate::ast::assert_str_array_all_ascii(&SexpShape::LABELS);
171const _: () = crate::ast::assert_str_array_all_ascii(&StructuralKind::LABELS);
172
173// Compile-time SUBSET-embedding witnesses — the THREE family-wide
174// `[&'static str; N]` sub-vocabularies of the substrate's twelve-arm
175// outer-shape label vocabulary at `SexpShape::LABELS` whose distinct-
176// value set is an intentionally-closed PROPER SUBSET of the outer
177// twelve-arm `[&'static str; 12]` superset. Pre-lift the three subset
178// relations lived only as prose in the outer array's twelve-arm
179// partition-rule docstring plus runtime per-role alias-chain checks
180// (each per-role `<TYPE>::<ROLE>_LABEL` on the three sub-vocabulary
181// algebras (`AtomKind`, `QuoteForm`, `StructuralKind`) aliases the
182// namesake `SexpShape::<ROLE>_LABEL` on the outer algebra); post-lift
183// the ARRAY-LEVEL subset embedding of the three pinned pairs binds at
184// rustc time via ONE `const _` line per pair. A regression that
185// silently re-inlined either the SUBSET side (e.g. dropping
186// `AtomKind::SYMBOL_LABEL`'s `SexpShape::SYMBOL_LABEL` alias to a
187// fresh distinct byte spelling `"sym"` — the array would still be
188// pairwise-distinct AND still disjoint from `QuoteForm::LABELS`, but
189// the subset embedding into `SexpShape::LABELS` would break) or the
190// SUPERSET side (e.g. dropping `SexpShape::SYMBOL_LABEL` and leaving
191// `AtomKind::SYMBOL_LABEL` as a stale copy of `"symbol"` outside the
192// outer vocabulary) fails at `cargo check` BEFORE any test scheduler
193// runs. Sibling to the FIVE `assert_str_array_pairwise_distinct`
194// witnesses on `ast.rs` and the THIRTEEN `assert_str_array_pairwise_
195// distinct` witnesses above on the (contract-shape ∈ {pairwise-
196// distinctness}) column: those pin INJECTIVITY on each individual
197// array; these pin SUBSET containment ORIENTED across PAIRS of arrays
198// on the (contract-shape ∈ {subset-embedding}) column.
199//
200// The three pinned pairs are (all under the shared `&'static str`
201// element-type):
202//   1. `AtomKind::LABELS`       ⊂ `SexpShape::LABELS` (6-in-12 arms —
203//      atomic-payload kind labels; symbol / keyword / string / int /
204//      float / bool).
205//   2. `QuoteForm::LABELS`      ⊂ `SexpShape::LABELS` (4-in-12 arms —
206//      quote-family labels; quote / quasiquote / unquote / unquote-
207//      splice).
208//   3. `StructuralKind::LABELS` ⊂ `SexpShape::LABELS` (2-in-12 arms —
209//      structural-shape labels; nil / list).
210//
211// Cardinality composition: 6 + 4 + 2 = 12 = `SexpShape::LABELS.len()`
212// closes a NON-CONTIGUOUS PARTITION of the outer twelve-arm
213// vocabulary at compile time when composed with the THREE pairwise-
214// disjointness witnesses on the sub-vocabulary triple: one lives on
215// `ast.rs` at the (`AtomKind::LABELS`, `QuoteForm::LABELS`) pair (the
216// two sub-vocabularies whose HOST TYPES both live in `ast.rs`), the
217// two peers below at the two (`_, StructuralKind::LABELS`) pairs
218// (each with ONE sub-vocabulary hosted here in `error.rs`). Together
219// with the pre-existing pairwise-distinctness witnesses on each of
220// the four arrays, the three subset-embedding witnesses on the three
221// sub-vocabulary → superset pairs, AND the three pairwise-disjointness
222// witnesses on the three sub-vocabulary × sub-vocabulary pairs, the
223// substrate now carries a COMPLETE COMPILE-TIME PROOF that
224// `SexpShape::LABELS` is a DISJOINT UNION of the three sub-vocabulary
225// arrays — every rearrangement of the twelve outer-shape labels
226// across the four algebras is either (a) a legal partition rebalance
227// (which passes every witness) or (b) drift that breaks at least one
228// of INJECTIVITY / SUBSET-EMBEDDING / PAIRWISE-DISJOINTNESS on at
229// least one array or pair, failing `cargo check` before any test
230// scheduler runs.
231const _: () = crate::ast::assert_str_array_within_str_finite_set::<6, 12>(
232    &crate::ast::AtomKind::LABELS,
233    &SexpShape::LABELS,
234);
235const _: () = crate::ast::assert_str_array_within_str_finite_set::<4, 12>(
236    &crate::ast::QuoteForm::LABELS,
237    &SexpShape::LABELS,
238);
239const _: () = crate::ast::assert_str_array_within_str_finite_set::<2, 12>(
240    &StructuralKind::LABELS,
241    &SexpShape::LABELS,
242);
243
244// Compile-time DISJOINTNESS witnesses closing the twelve-arm
245// `SexpShape::LABELS` partition proof — the TWO remaining sub-
246// vocabulary × sub-vocabulary pairs on the partition triple
247// (`AtomKind::LABELS`, `QuoteForm::LABELS`, `StructuralKind::LABELS`)
248// whose two hosts split across `ast.rs` and `error.rs` (the third
249// pair `(AtomKind::LABELS, QuoteForm::LABELS)`, whose two hosts both
250// live in `ast.rs`, is pinned there). Pre-lift the two disjointness
251// relations lived only as prose in the outer partition-rule
252// docstring plus the runtime `sexp_shape_labels_are_disjoint_union`
253// cross-check; post-lift the ARRAY-LEVEL disjointness of the two
254// pinned pairs binds at rustc time via ONE `const _` line per pair.
255// A regression that silently renamed `StructuralKind::NIL_LABEL`
256// (`"nil"`) to `"symbol"` (aliasing `AtomKind::SYMBOL_LABEL`),
257// renamed `StructuralKind::LIST_LABEL` (`"list"`) to `"quote"`
258// (aliasing `QuoteForm::QUOTE_LABEL`), or drifted either entry of
259// `StructuralKind::LABELS` to bytes shared with an entry of the
260// atomic-kind or quote-family peer sub-vocabulary fails at
261// `cargo check` BEFORE any test scheduler runs. Sibling to the FOUR
262// `assert_str_arrays_disjoint` witnesses in `ast.rs`.
263//
264// The two pinned pairs are (all under the shared `&'static str`
265// element-type):
266//   1. `AtomKind::LABELS`  ∩ `StructuralKind::LABELS` = ∅
267//   2. `QuoteForm::LABELS` ∩ `StructuralKind::LABELS` = ∅
268//
269// Composed with the ast.rs-pinned
270// (`AtomKind::LABELS`, `QuoteForm::LABELS`) disjointness witness,
271// the THREE pairwise-disjointness witnesses cover every pair on the
272// three-element sub-vocabulary triple (C(3, 2) = 3 pairs). Together
273// with the three pairwise-distinctness INTRA-array witnesses AND the
274// three SUBSET-embedding witnesses above, the substrate now carries
275// the full DISJOINT-UNION theorem
276//   `SexpShape::LABELS ≡ AtomKind::LABELS ⊕ QuoteForm::LABELS ⊕
277//    StructuralKind::LABELS`
278// as a rustc-time proof obligation, closing the twelve-arm outer
279// vocabulary partition at compile time.
280const _: () = crate::ast::assert_str_arrays_disjoint::<6, 2>(
281    &crate::ast::AtomKind::LABELS,
282    &StructuralKind::LABELS,
283);
284const _: () = crate::ast::assert_str_arrays_disjoint::<4, 2>(
285    &crate::ast::QuoteForm::LABELS,
286    &StructuralKind::LABELS,
287);
288
289// Compile-time (⊆) SET-COVERAGE witness closing the twelve-arm
290// `SexpShape::LABELS` disjoint-union theorem — every entry of the
291// parent outer-shape vocabulary is reached by at least one of the
292// three sub-vocabularies (`AtomKind::LABELS`, `QuoteForm::LABELS`,
293// `StructuralKind::LABELS`). Composes with the pre-existing (⊇)
294// SUBSET-embedding witnesses (three `_within_str_finite_set` lines
295// above), the pairwise-DISJOINTNESS witnesses (two above plus one
296// (`AtomKind::LABELS` ∩ `QuoteForm::LABELS`) in `ast.rs`), and the
297// parent INJECTIVITY witness (`assert_str_array_pairwise_distinct(
298// &SexpShape::LABELS)` above) to close the FULL disjoint-union
299// theorem
300//   `SexpShape::LABELS ≡ AtomKind::LABELS ⊕ QuoteForm::LABELS ⊕
301//    StructuralKind::LABELS`
302// as a rustc-time proof obligation. Pre-lift the (⊆) direction lived
303// only as the runtime cross-check
304// `sexp_shape_labels_is_disjoint_union_of_three_sub_vocabularies` at
305// the tests submodule; post-lift the (⊆) direction binds at `cargo
306// check` time via ONE `const _` line on the partition quadruple. A
307// regression that silently drops a variant from ONE sub-vocabulary
308// (e.g. removing `AtomKind::Bool` and its `AtomKind::BOOL_LABEL`
309// alias while leaving `SexpShape::Bool` and `SexpShape::BOOL_LABEL`
310// in the parent vocabulary) fires the SET-STR-MISSING panic at
311// `cargo check` BEFORE any test scheduler runs.
312//
313// Composition law with the sibling (⊇) SUBSET + pairwise-
314// DISJOINTNESS + parent INJECTIVITY witnesses: given the four
315// corners, the disjoint-union theorem follows CONSTRUCTIVELY —
316//   (⊇) every sub-vocabulary label is in the parent (three SUBSET
317//        witnesses);
318//   (⊆) every parent label is in some sub-vocabulary (this witness);
319//   pairwise-disjoint sub-vocabularies (three DISJOINTNESS witnesses);
320//   parent has no duplicate entries (INJECTIVITY witness).
321// The cardinality composition `6 + 4 + 2 = 12` is a CONSEQUENCE of
322// the four corners rather than a separate pre-check; the rustc-
323// enforced arities `[&'static str; N]` for N ∈ {6, 4, 2, 12} pin the
324// cardinality quadruple at the declaration site.
325const _: () = crate::ast::assert_str_finite_set_covered_by_three_str_arrays::<6, 4, 2, 12>(
326    &crate::ast::AtomKind::LABELS,
327    &crate::ast::QuoteForm::LABELS,
328    &StructuralKind::LABELS,
329    &SexpShape::LABELS,
330);
331
332// Compile-time SLICE-EQUALS-ARRAY witnesses closing the twelve-arm
333// `SexpShape::LABELS` POSITIONAL decomposition — the FOUR canonical
334// sub-slices of the twelve-slot outer container each byte-equal a
335// sub-carving's canonical `[&'static str; M]` listing at rustc time.
336// Strictly STRONGER on the (contract-strength) axis than the seven
337// sibling SET-level witnesses above (three `_within_str_finite_set`
338// SUBSET-embedding + three `_arrays_disjoint` pairwise-disjointness +
339// one `_finite_set_covered_by_three_str_arrays` coverage): the SET-
340// level disjoint-union theorem `SexpShape::LABELS ≡ AtomKind::LABELS ⊕
341// QuoteForm::LABELS ⊕ StructuralKind::LABELS` those seven witnesses
342// close is SILENT on which SLOTS each sub-vocabulary's arms occupy —
343// a regression that permuted `SexpShape::LABELS` from the CANONICAL
344// declaration order `[NIL, SYMBOL, KEYWORD, STRING, INT, FLOAT, BOOL,
345// LIST, QUOTE, QUASIQUOTE, UNQUOTE, UNQUOTE_SPLICE]` (`StructuralKind`
346// at slots `{0, 7}`, `AtomKind` at slots `[1..7)`, `QuoteForm` at
347// slots `[8..12)`) to `[SYMBOL, NIL, KEYWORD, STRING, INT, FLOAT,
348// BOOL, LIST, QUOTE, QUASIQUOTE, UNQUOTE, UNQUOTE_SPLICE]` (swapping
349// slots `0` and `1`, interleaving `AtomKind` into a slot the
350// structural-residual carving previously owned) preserves the SET-
351// level disjoint-union theorem (both sub-vocabularies still embed
352// into the parent, still cover, still disjoint, parent still
353// injective) but silently misaligns every consumer indexing
354// `SexpShape::LABELS[0]` for the NIL diagnostic literal. Post-lift
355// the ARRAY-LEVEL positional decomposition binds at rustc time via
356// FOUR `const _` lines below, one invocation stage earlier than the
357// runtime pin.
358//
359// Sibling posture to the (u8)-row's FOUR positional witnesses on
360// `SexpShape::HASH_DISCRIMINATORS` at `ast.rs` (two singleton
361// `assert_u8_array_slice_equals_u8_array::<12, 1, {0, 7}>` witnesses
362// on `[0..1)` + `[7..8)`, one six-slot `assert_u8_array_slice_is_
363// scalar_replica::<12, 1, 7>` witness on `[1..7)`, one four-slot
364// `assert_u8_array_slice_equals_u8_array::<12, 4, 8>` witness on
365// `[8..12)`). The (u8) row's `[1..7)` slice collapses to a SCALAR
366// replica (all six atomic-payload shapes share the outer-`Sexp`
367// `Atom` marker byte `1u8`); the (str) row's `[1..7)` slice preserves
368// all six SLOTS individually (each atomic variant carries a DISTINCT
369// diagnostic label byte-string). The (str) row's stronger information
370// content at the `[1..7)` slice is precisely why the (str) row picks
371// up the FULL slice-equals-array shape at ALL FOUR slices (not the
372// SCALAR-REPLICA sibling at the interior slice).
373//
374// The FOUR pinned pairs — all on the shared `&'static str` element-
375// type and on the shared parent `[&'static str; 12]` outer container
376// `SexpShape::LABELS`:
377//   1. `SexpShape::LABELS[0..1)   == [StructuralKind::NIL_LABEL]`
378//      (singleton left-endpoint slot, structural-residual NIL role)
379//   2. `SexpShape::LABELS[1..7)   == AtomKind::LABELS`
380//      (six-slot atomic-payload middle slice — the six atomic-kind
381//      labels in CANONICAL variant-declaration order)
382//   3. `SexpShape::LABELS[7..8)   == [StructuralKind::LIST_LABEL]`
383//      (singleton mirror-endpoint slot at the atomic-collapse right
384//      endpoint, structural-residual LIST role)
385//   4. `SexpShape::LABELS[8..12)  == QuoteForm::LABELS`
386//      (four-slot quote-family tail — the four quote-family labels in
387//      CANONICAL variant-declaration order)
388//
389// The four disjoint slice ranges `[0..1) ∪ [1..7) ∪ [7..8) ∪ [8..12)`
390// exhaust the twelve-slot outer container's position space —
391// `1 + 6 + 1 + 4 = 12`. Composed with the pre-existing pairwise-
392// distinctness witness on the parent array, the FOUR positional
393// witnesses jointly close the POSITIONAL DECOMPOSITION theorem
394//   `SexpShape::LABELS == [NIL] ++ AtomKind::LABELS ++ [LIST] ++
395//    QuoteForm::LABELS`
396// as a rustc-time proof obligation, strictly stronger than the SET-
397// level disjoint-union theorem the seven sibling witnesses above
398// close. A regression that reordered ANY of the twelve slots in the
399// outer array's initializer fails HERE at `cargo check` BEFORE any
400// test scheduler runs.
401//
402// The two singleton pairs use INLINE `[&'static str; 1]` singleton
403// arrays rather than `&StructuralKind::LABELS[0..1]` slice syntax
404// because the helper's signature takes `&[&'static str; M]` (a const-
405// generic array reference, arity-known at rustc time) rather than
406// `&[&'static str]` (a slice type with runtime-length). The inline
407// arrays project the two per-role `pub const *_LABEL` bytes directly
408// — a regression that renamed one of the two aliases fails at the
409// alias's declaration site FIRST (a missing symbol referent), which
410// routes to a distinct diagnostic axis rather than to the witness's
411// STR-SLICE-EQUALS-ARRAY-VIOLATION panic.
412const _: () = crate::ast::assert_str_array_slice_equals_str_array::<12, 1, 0>(
413    &SexpShape::LABELS,
414    &[StructuralKind::NIL_LABEL],
415);
416const _: () = crate::ast::assert_str_array_slice_equals_str_array::<12, 6, 1>(
417    &SexpShape::LABELS,
418    &crate::ast::AtomKind::LABELS,
419);
420const _: () = crate::ast::assert_str_array_slice_equals_str_array::<12, 1, 7>(
421    &SexpShape::LABELS,
422    &[StructuralKind::LIST_LABEL],
423);
424const _: () = crate::ast::assert_str_array_slice_equals_str_array::<12, 4, 8>(
425    &SexpShape::LABELS,
426    &crate::ast::QuoteForm::LABELS,
427);
428
429// Compile-time SUBSET-embedding witnesses closing the `[&'static str;
430// N]` STR row of the (UnquoteForm ⊂ QuoteForm) 2-of-4 subset-carve
431// axis on the substrate's `str`-side vocabulary triple. Pre-lift the
432// three subset relations lived per-role at the SCALAR alias sites on
433// this module (`UnquoteForm::UNQUOTE_MARKER = crate::ast::QuoteForm::
434// UNQUOTE_PREFIX`, `UnquoteForm::SPLICE_MARKER = crate::ast::QuoteForm::
435// UNQUOTE_SPLICE_PREFIX`, `UnquoteForm::UNQUOTE_LABEL = crate::ast::
436// QuoteForm::UNQUOTE_LABEL`, `UnquoteForm::SPLICE_LABEL = crate::ast::
437// QuoteForm::UNQUOTE_SPLICE_LABEL`, `UnquoteForm::UNQUOTE_IAC_FORGE_
438// TAG = crate::ast::QuoteForm::UNQUOTE_IAC_FORGE_TAG`, `UnquoteForm::
439// SPLICE_IAC_FORGE_TAG = crate::ast::QuoteForm::UNQUOTE_SPLICE_IAC_
440// FORGE_TAG`) plus a runtime cross-check that iterated each subset
441// array and matched against the parent superset's array. The ARRAY-
442// LEVEL subset embedding of the three sibling vocabularies onto their
443// respective four-arm supersets was NOT bound at rustc const-eval time
444// — a regression that silently re-inlined the subset array with a
445// fresh literal drifted OUT of the alias-composition (e.g.
446// `UnquoteForm::LABELS: [&'static str; 2] = ["unquote", "splice"]`
447// with the second entry spelling `"splice"` instead of the aliased
448// `"unquote-splice"`) would still pass INJECTIVITY (the array remains
449// pairwise-distinct) AND still pass every DISJOINTNESS witness above
450// (the array's shared vocabulary with `AtomKind::LABELS` /
451// `StructuralKind::LABELS` doesn't gain a member) BUT would silently
452// break the `UnquoteForm::to_quote_form()` composition law consumers
453// route through — every `UnquoteForm::LABELS[i]` would fail its
454// composition round-trip through the parent superset's label
455// projection at runtime rather than at rustc time.
456//
457// The three pinned pairs are (all under the shared `&'static str`
458// element-type):
459//   1. `UnquoteForm::LABELS`         ⊂ `crate::ast::QuoteForm::LABELS`
460//      (2-in-4 arms — the two substitution-subset LABELS embed into
461//      the four-arm quote-family LABELS' vocabulary).
462//   2. `UnquoteForm::MARKERS`        ⊂ `crate::ast::QuoteForm::PREFIXES`
463//      (2-in-4 arms — the two substitution-subset reader-marker
464//      prefixes embed into the four-arm quote-family reader-prefix
465//      vocabulary).
466//   3. `UnquoteForm::IAC_FORGE_TAGS` ⊂ `crate::ast::QuoteForm::IAC_FORGE_TAGS`
467//      (2-in-4 arms — the two substitution-subset iac-forge canonical-
468//      form tag bytes embed into the four-arm quote-family iac-forge
469//      canonical-form tag vocabulary).
470//
471// Sibling to the pre-existing u8-row `assert_u8_array_within_u8_
472// finite_set::<2, 4>(&UnquoteForm::HASH_DISCRIMINATORS,
473// &QuoteForm::HASH_DISCRIMINATORS)` witness at `ast.rs`: together
474// the u8 + str rows close the (element-type × vocabulary-axis) 2×3 =
475// 6-corner face on the (UnquoteForm ⊂ QuoteForm) 2-of-4 subset-carve
476// column at compile time. A future fourth vocabulary axis (e.g. a
477// hypothetical `UnquoteForm::LEADS ⊂ QuoteForm::LEADS` on the
478// `[char; N]` element-type row) picks up ONE new `const _:` witness
479// through `assert_char_array_within_char_finite_set` with the same
480// posture — the 3-vocabulary str row extends mechanically to a fourth
481// (element-type × vocabulary-axis) column.
482//
483// Composition law with the sibling u8-row witness: for every `i ∈
484// [0, 2)`, all four projections stay in lockstep — `UnquoteForm::
485// LABELS[i] == QuoteForm::UNQUOTE_LABEL` or `QuoteForm::UNQUOTE_
486// SPLICE_LABEL` (at some `j ∈ [0, 4)`), and similarly for MARKERS
487// through PREFIXES, IAC_FORGE_TAGS through IAC_FORGE_TAGS, and (via
488// the pre-existing u8 witness) HASH_DISCRIMINATORS through HASH_
489// DISCRIMINATORS. The FOUR per-axis subset-embedding witnesses
490// jointly enforce the (UnquoteForm ⊂ QuoteForm) 2-of-4 subset-carve
491// on every vocabulary axis simultaneously; any single-axis regression
492// (a drifted marker byte, a drifted label spelling, a drifted iac-
493// forge tag, a drifted cache-key byte) fails cargo check on ONE of
494// the four witnesses BEFORE it reaches the runtime cross-check on
495// `unquote_form_v_marker_aliases_quote_form_v_prefix_bytes` /
496// `unquote_form_v_label_aliases_quote_form_v_label_bytes` /
497// `unquote_form_v_iac_forge_tag_aliases_quote_form_v_iac_forge_tag_
498// bytes`.
499const _: () = crate::ast::assert_str_array_within_str_finite_set::<2, 4>(
500    &UnquoteForm::LABELS,
501    &crate::ast::QuoteForm::LABELS,
502);
503const _: () = crate::ast::assert_str_array_within_str_finite_set::<2, 4>(
504    &UnquoteForm::MARKERS,
505    &crate::ast::QuoteForm::PREFIXES,
506);
507const _: () = crate::ast::assert_str_array_within_str_finite_set::<2, 4>(
508    &UnquoteForm::IAC_FORGE_TAGS,
509    &crate::ast::QuoteForm::IAC_FORGE_TAGS,
510);
511
512// Compile-time SLICE-EQUALS-ARRAY witnesses tightening the (UnquoteForm ⊂
513// QuoteForm) 2-of-4 subset-carve from set-level SUBSET to positionwise
514// SLICE-EQUALS at a specific contiguous START offset on the SAME three
515// `[&'static str; N]` sub-vocabulary → superset pairs the SUBSET witnesses
516// above cover. Pre-lift, the sub-carving's positional relationship to the
517// superset was pinned indirectly: (a) each sub-array's INJECTIVITY via the
518// three `assert_str_array_pairwise_distinct` witnesses at the top of this
519// module, (b) each sub-array's SET-EMBEDDING via the three SUBSET witnesses
520// immediately above, (c) each per-role scalar (`UnquoteForm::UNQUOTE_MARKER =
521// QuoteForm::UNQUOTE_PREFIX`, etc.) via alias equality at rustc time. What
522// NEITHER (a) + (b) NOR (c) alone pinned was the ARRAY-LEVEL POSITIONWISE
523// composition — that `UnquoteForm::MARKERS == QuoteForm::PREFIXES[2..4]`
524// element-for-element. A regression that silently swapped the two per-role
525// slot bindings in the sub-array initializer (e.g. `UnquoteForm::MARKERS:
526// [&'static str; 2] = [Self::SPLICE_MARKER, Self::UNQUOTE_MARKER]`) would
527// still pass INJECTIVITY (both aliases distinct), still pass SET-EMBEDDING
528// (both aliases still appear in `QuoteForm::PREFIXES`), still pass per-role
529// scalar equality (each alias still binds to the same `QuoteForm::*_PREFIX`
530// source constant), BUT would silently mis-align every consumer that reads
531// the sub-array by slot ordinal (`UnquoteForm::MARKERS[0]` semantically
532// naming the two-quote-family-arms-in-declaration-order for e.g. a per-slot
533// error diagnostic that mirrors `QuoteForm`'s slot ordinal 2 vs 3). The
534// SLICE-EQUALS witness pins the sub-carving's slot-ordering to a SPECIFIC
535// contiguous slice of the superset (`[2..4)` — the two UnquoteForm-family
536// arms sit at the tail of QuoteForm's four-arm declaration listing,
537// after the two non-substitution `Quote` + `Quasiquote` arms). Both drifts
538// the SUBSET witness stayed silent on (sub-carving reorder AND SUPERSET
539// reorder that leaves the sub-array's two aliases still present but at
540// different positions) fail HERE at rustc time with the
541// `STR-SLICE-EQUALS-ARRAY-VIOLATION` axis panic naming the drifted position.
542//
543// Sibling posture to the four-witness `SexpShape::LABELS` (12) SLICE-EQUALS
544// cluster at lines 331..=346 above (the outer twelve-arm SexpShape
545// vocabulary bound as the SEGMENTED CONCATENATION `[NIL_LABEL] ++
546// AtomKind::LABELS ++ [LIST_LABEL] ++ QuoteForm::LABELS` at rustc time via
547// four `assert_str_array_slice_equals_str_array` witnesses on the four
548// slot segments). Together the seven witnesses close the SLICE-EQUALS
549// column on BOTH sub-carving directions: those four pin sub-vocabularies
550// AS SEGMENTS OF the twelve-arm SexpShape outer array (the outer superset
551// pinned by-segment against its sub-carvings); these three pin
552// UnquoteForm sub-arrays AS SLICES OF the QuoteForm superset (the outer
553// superset pinned as the position-source for the sub-carvings). The
554// (SexpShape ⊃ {AtomKind, QuoteForm, StructuralKind}) SUPERSET-AS-SEGMENTS
555// posture and the (UnquoteForm ⊂ QuoteForm) SUB-AS-SLICE posture are the
556// two orientations of the same positionwise-composition theorem the
557// SLICE-EQUALS helper carries.
558const _: () = crate::ast::assert_str_array_slice_equals_str_array::<4, 2, 2>(
559    &crate::ast::QuoteForm::LABELS,
560    &UnquoteForm::LABELS,
561);
562const _: () = crate::ast::assert_str_array_slice_equals_str_array::<4, 2, 2>(
563    &crate::ast::QuoteForm::PREFIXES,
564    &UnquoteForm::MARKERS,
565);
566const _: () = crate::ast::assert_str_array_slice_equals_str_array::<4, 2, 2>(
567    &crate::ast::QuoteForm::IAC_FORGE_TAGS,
568    &UnquoteForm::IAC_FORGE_TAGS,
569);
570
571// Compile-time STR-DISJOINTNESS witnesses closing the ARRAY-LEVEL
572// vocabulary-disjointness corner of the STATIC ⊕ DYNAMIC partition
573// idiom on the substrate's TWO peer diagnostic algebras
574// (`TemplateInvariantKind` — the bytecode-runtime invariant surface;
575// `OptionalParamMalformedReason` — the `&optional` parser rejection
576// surface). Each algebra carries the SAME closed-set carving idiom
577// documented on `Self::DYNAMIC_DESCRIPTORS`'s docstring: "every closed-
578// set outer algebra the substrate carries now pins BOTH its STATIC-
579// subset canonical-bytes array AND its DYNAMIC-subset per-role-slot
580// array at ONE `pub const` per axis" — TemplateInvariantKind at
581// 2 ⊕ 2 asymmetric widths, OptionalParamMalformedReason at 3 ⊕ 1
582// asymmetric widths. Pre-lift, EACH array's INTRA-array pairwise-
583// distinctness bound at compile time via the four `assert_str_array_
584// pairwise_distinct` witnesses at the top of this module — but the
585// CROSS-array (STATIC ∩ DYNAMIC = ∅) vocabulary-disjointness clause
586// lived ONLY as an emergent property of the two carvings' semantic
587// contract (STATIC arms bind FULL diagnostic bytes; DYNAMIC arms bind
588// only the noun-slot bytes that a shared `format!("… {descriptor} …")`
589// template interpolates). Post-lift the ARRAY-LEVEL disjointness of
590// the STATIC ⊕ DYNAMIC 2-of-2 face binds at rustc time via ONE `const _`
591// line per algebra. A regression that silently drifts a DYNAMIC
592// descriptor into a STATIC message/label byte (e.g. renames
593// `TemplateInvariantKind::SUBST_BAD_INDEX_DESCRIPTOR` from `"param"`
594// into `"compiled template produced no value"`, colliding with
595// `TemplateInvariantKind::FINAL_NO_VALUE_MESSAGE`; renames
596// `OptionalParamMalformedReason::EXTRA_ELEMENTS_DESCRIPTOR` from
597// `"elements"` into `"empty list"`, colliding with
598// `OptionalParamMalformedReason::EMPTY_LIST_LABEL`) fails at
599// `cargo check` BEFORE any test scheduler runs. The invariant is
600// load-bearing at three consumer layers documented on the two
601// algebras' DYNAMIC_DESCRIPTORS docstrings: (i) `tatara-check`
602// coverage assertions grep the STATIC arms' full label bytes and the
603// DYNAMIC arms' descriptor noun bytes as DISJOINT vocabularies, so a
604// collision would silently mis-classify one carving's diagnostic as
605// the other's; (ii) Sekiban audit-trail metrics keyed on the STATIC-
606// subset FULL message OR the DYNAMIC-subset noun-descriptor treat
607// the two byte-spaces as disjoint metric-label sub-vocabularies; (iii)
608// LSP quick-fix vocabularies rendered from the DYNAMIC-arm descriptor
609// slot MUST NOT collide with the STATIC-arm full-label vocabulary
610// (else a quick-fix keyed on the descriptor would fire on a STATIC
611// arm whose full label happens to string-equal the descriptor). The
612// two witnesses close the STR-DISJOINTNESS column on the (algebra ×
613// carving-pair) face at ONE `const _` line per algebra; adding a
614// hypothetical fifth per-algebra rejection reason on either the
615// STATIC or DYNAMIC arm extends only the respective array (with
616// rustc's forced-arity `[&'static str; N]` gate re-firing the
617// disjointness sweep against the extended array in lockstep).
618// Sibling to the FIVE substrate-pinned `assert_str_arrays_disjoint`
619// witnesses on the (SexpShape sub-vocabulary × sub-vocabulary) triple
620// (three at the (`AtomKind`, `QuoteForm`, `StructuralKind`) triple
621// hosted across `ast.rs` and `error.rs`) plus the (UnquoteForm ⊂
622// QuoteForm) subset-embedding face — those witnesses close the DISJOINT
623// UNION theorem on the substrate's twelve-arm outer-shape LABELS
624// vocabulary; these witnesses close the STATIC ⊕ DYNAMIC carving
625// theorem on the substrate's TWO peer diagnostic algebras.
626//
627// The two pinned pairs are (all under the shared `&'static str`
628// element-type):
629//   1. `TemplateInvariantKind::STATIC_MESSAGES` ∩
630//      `TemplateInvariantKind::DYNAMIC_DESCRIPTORS` = ∅
631//      (2-arm STATIC subset carrying the full `"compiled template:
632//      EndList with empty stack"` / `"compiled template produced no
633//      value"` messages vs. 2-arm DYNAMIC subset carrying the `"param"`
634//      / `"splice"` noun slots — no byte shared).
635//   2. `OptionalParamMalformedReason::STATIC_LABELS` ∩
636//      `OptionalParamMalformedReason::DYNAMIC_DESCRIPTORS` = ∅
637//      (3-arm STATIC subset carrying the full `"empty list"` /
638//      `"missing default"` / `"name not a symbol"` labels vs. 1-arm
639//      DYNAMIC subset carrying the `"elements"` noun slot — no byte
640//      shared).
641const _: () = crate::ast::assert_str_arrays_disjoint::<2, 2>(
642    &TemplateInvariantKind::STATIC_MESSAGES,
643    &TemplateInvariantKind::DYNAMIC_DESCRIPTORS,
644);
645const _: () = crate::ast::assert_str_arrays_disjoint::<3, 1>(
646    &OptionalParamMalformedReason::STATIC_LABELS,
647    &OptionalParamMalformedReason::DYNAMIC_DESCRIPTORS,
648);
649
650// Compile-time STR-BLOCK-CONSTANCY witness closing the ARRAY-LEVEL
651// MANY-TO-ONE variant → canonical-projection corner on the substrate's
652// TYPED-VARIANT × CANONICAL-PROJECTION MANY-TO-ONE surface at
653// `CompilerSpecIoStage::OPERATIONS`. Where every OTHER family-wide
654// `[&'static str; N]` array on the substrate carries a BIJECTIVE
655// per-index projection (`AtomKind::LABELS[i]` is bijective with
656// `AtomKind::ALL[i].label()`; `QuoteForm::LABELS[i]` is bijective with
657// `QuoteForm::ALL[i].label()`; `CompilerSpecIoStage::LABELS[i]` is
658// bijective with `CompilerSpecIoStage::ALL[i].label()`) and binds
659// INJECTIVITY through `assert_str_array_pairwise_distinct` at the
660// module top, `CompilerSpecIoStage::OPERATIONS` is the ONE substrate
661// array whose per-index projection is INTENTIONALLY MANY-TO-ONE —
662// the four `CompilerSpecIoStage::ALL[i].operation()` per-variant
663// projections collapse onto EXACTLY TWO canonical operation-label
664// bytes (both `Realize…` variants share `"realize_to_disk"`; both
665// `Load…` variants share `"load_from_disk"`) yielding the
666// `[REALIZE_TO_DISK, REALIZE_TO_DISK, LOAD_FROM_DISK, LOAD_FROM_DISK]`
667// 2-of-2-to-2 block-constant array literal at the declaration site.
668// The pre-existing module-top comment (adjacent to the family-wide
669// `assert_str_array_pairwise_distinct` witnesses) documents the
670// EXCLUSION from injectivity as intentional AND names the intended
671// axis: "variant-index alignment with `CompilerSpecIoStage::ALL`, not
672// injectivity — different theorem, different helper". This witness
673// IS the different helper the comment named — the ARRAY-LEVEL block-
674// constant structure `arr = [head; K] ++ [tail; N - K]` at
675// `(N, K) = (4, 2)`, `(head, tail) = (REALIZE_TO_DISK_OPERATION,
676// LOAD_FROM_DISK_OPERATION)`. Pre-lift the block-constant structure
677// bound only at runtime through `compiler_spec_io_stage_operations_
678// align_with_all_by_index` (positional projection through
679// `stage.operation()`) plus `compiler_spec_io_stage_operations_
680// partition_all_two_ways` (multiplicity counts of the two operation
681// labels); post-lift the ARRAY-LEVEL block-constancy binds at rustc
682// time via ONE `const _` line. A regression that silently flip-
683// flopped the projection (reorder `OPERATIONS` to `[REALIZE, LOAD,
684// REALIZE, LOAD]`; drift a slot to a distinct third operation label;
685// collapse to a single-block `[REALIZE, REALIZE, REALIZE, REALIZE]`)
686// fails at `cargo check` BEFORE any test scheduler runs. The
687// invariant is load-bearing at THREE consumer layers documented on
688// `Self::OPERATIONS`'s docstring: (i) an LSP / REPL completion
689// provider surfacing every legal operation label in an `operation=`
690// metrics query bar (the deduped block-labels IS the ONE typed
691// coverage sweep); (ii) `tatara-check` coverage assertions verifying
692// every workspace `.lisp` file's `CompilerSpec` rejection classifies
693// to some entry of `Self::OPERATIONS`; (iii) Sekiban audit-trail
694// metrics jointly labeled by `Self::operation` × `Self::label` whose
695// `operation=` label set IS the deduped `Self::OPERATIONS` (e.g.
696// `tatara_lisp_compiler_spec_io_total{operation="realize_to_disk",
697// stage="serialize"}`) — all three layers rely on the block-constant
698// partition holding at ONE cargo-check-time site. Adding a hypothetical
699// fifth variant (`LoadFromStrDeserialize` once an in-memory
700// `load_from_str` lands, `RealizeToDiskAtomicReplace` if the realize
701// path grows a crash-safe-rename stage) extends `Self::ALL` ONCE +
702// `Self::operation` ONCE + `Self::OPERATIONS` ONCE — the block-
703// constant witness re-fires against the extended arity through the
704// rustc-forced `[&'static str; N]` gate (with the appropriate new
705// `(N, K)` const-generic reflecting the new 2-of-2-to-3 or 2-of-3-to-2
706// partition) so the extension picks up the block-constancy contract
707// mechanically. Sibling to the FOURTEEN family-wide
708// `assert_str_array_pairwise_distinct` witnesses at the module top on
709// the (contract-shape ∈ {INJECTIVITY}) column of the (`&'static str`)
710// row — this witness closes the (contract-shape ∈ {MANY-TO-ONE-BLOCK-
711// CONSTANCY}) corner peer to the (INJECTIVITY) column at ONE `const
712// _` line, closing the (INJECTIVITY, MANY-TO-ONE-BLOCK-CONSTANCY)
713// 2-corner face on the (`&'static str`) row on the substrate's
714// per-index projection surface.
715const _: () = crate::ast::assert_str_array_is_concatenation_of_two_scalar_replicas::<4, 2>(
716    &CompilerSpecIoStage::OPERATIONS,
717    CompilerSpecIoStage::REALIZE_TO_DISK_OPERATION,
718    CompilerSpecIoStage::LOAD_FROM_DISK_OPERATION,
719);
720
721// Compile-time per-position ORDER witnesses closing the (`&'static
722// str`)-row FULL-ARRAY LITERAL column across the FIVE remaining scalar-
723// composed `[&'static str; N]` label vocabularies in this module —
724// `ExpectedKwargShape::LABELS` (7), `KwargPathKind::LABELS` (3),
725// `MacroDefHead::KEYWORDS` (3), `CompilerSpecIoStage::LABELS` (4),
726// `StructuralKind::LABELS` (2). Peer to ast.rs's FIVE-witness
727// (str)-row cluster on `Atom::BOOL_LITERALS` (2), `AtomKind::LABELS`
728// (6), and `QuoteForm::PREFIXES` / `LABELS` / `IAC_FORGE_TAGS`
729// (4 each) at lines 1252..=1269 in that file, and to the pre-existing
730// FOUR-witness `SexpShape::LABELS` (12) per-position cluster at lines
731// 331..=346 above in this file — together the ten witnesses bind the
732// (str)-row FULL-ARRAY per-position ORDER column across TEN family-
733// wide `[&'static str; N]` label vocabularies on the substrate at
734// rustc time.
735//
736// Each per-role scalar `pub const *_LABEL` / `*_KEYWORD` alias on the
737// closed-set outer algebra binds AT rustc TIME to its CANONICAL
738// literal-str value through the outer array's `[_; N]` initializer
739// positional composition. Two failure axes survive the pre-lift
740// `_pairwise_distinct` cluster at the module top that the post-lift
741// LITERAL witnesses reject at rustc time:
742//   1. Outer-array SLOT REORDER: a regression that swaps
743//      `KwargPathKind::NAMED_LABEL` (`"named"`) and
744//      `KwargPathKind::ITEM_LABEL` (`"item"`) in the outer
745//      `KwargPathKind::LABELS` initializer preserves pairwise-
746//      distinctness (both strs still distinct) but silently
747//      misaligns every consumer indexing `LABELS[0]` for the canonical
748//      NAMED-arm diagnostic dispatch. Analogous reorder-drifts on
749//      `ExpectedKwargShape::LABELS` / `MacroDefHead::KEYWORDS` /
750//      `CompilerSpecIoStage::LABELS` / `StructuralKind::LABELS`
751//      survive the pairwise-distinctness witness the SAME way.
752//   2. Per-role scalar VALUE DRIFT: a silent value-drift of a per-
753//      role scalar (e.g. an operator-facing polish
754//      `ExpectedKwargShape::LIST_OF_STRINGS_LABEL` from `"list of
755//      strings"` to `"list<string>"`; a Racket-compat rename
756//      `MacroDefHead::DEFMACRO_KEYWORD` from `"defmacro"` to
757//      `"define-syntax"`; a shorter idiom migration
758//      `CompilerSpecIoStage::REALIZE_TO_DISK_SERIALIZE_LABEL` from
759//      `"serialize"` to `"ser"`) that flows through the composed
760//      array without updating the outer array's literal image fails
761//      HERE with the `STR-SLICE-EQUALS-ARRAY-VIOLATION` panic naming
762//      the drifted position, where the pairwise-distinctness witness
763//      stays silent because the drifted str remains distinct from
764//      its peers.
765//
766// Future extensions that benefit: adding a hypothetical eighth
767// expected-shape variant (a distinct `Float` once `extract_float`
768// stops accepting integers, a `Symbol`, or a parameterized
769// `ListOfInts`) extends `ExpectedKwargShape::ALL` AND
770// `ExpectedKwargShape::LABELS` AND requires widening this witness's
771// `<7, 7, 0>` const-generic turbofish AND appending the new label
772// literal to the RHS listing in lockstep — rustc's forced-arity
773// check on `[&'static str; N]` fails compilation if either side
774// drifts out of lockstep. Analogous single-variant extensions on the
775// four sibling algebras (`KwargPathKind`, `MacroDefHead`,
776// `CompilerSpecIoStage`, `StructuralKind`) propagate through their
777// respective `<N, N, 0>` witnesses under the same forced-arity
778// gate.
779const _: () = crate::ast::assert_str_array_slice_equals_str_array::<7, 7, 0>(
780    &ExpectedKwargShape::LABELS,
781    &[
782        "keyword",
783        "string",
784        "int",
785        "number",
786        "bool",
787        "list",
788        "list of strings",
789    ],
790);
791const _: () = crate::ast::assert_str_array_slice_equals_str_array::<3, 3, 0>(
792    &KwargPathKind::LABELS,
793    &["named", "item", "slot"],
794);
795const _: () = crate::ast::assert_str_array_slice_equals_str_array::<3, 3, 0>(
796    &MacroDefHead::KEYWORDS,
797    &["defmacro", "defpoint-template", "defcheck"],
798);
799const _: () = crate::ast::assert_str_array_slice_equals_str_array::<4, 4, 0>(
800    &CompilerSpecIoStage::LABELS,
801    &["serialize", "write", "read", "deserialize"],
802);
803const _: () = crate::ast::assert_str_array_slice_equals_str_array::<2, 2, 0>(
804    &StructuralKind::LABELS,
805    &["nil", "list"],
806);
807
808#[derive(Debug, Error)]
809pub enum LispError {
810    #[error("unexpected character {0:?} at position {1}")]
811    UnexpectedChar(char, usize),
812    /// Unterminated string literal. The span runs from the opening `"`
813    /// to end-of-input — the whole run the tokenizer consumed looking
814    /// for a closer — so the rendered diagnostic underlines the
815    /// unterminated literal rather than pointing at its opening quote.
816    #[error("unterminated string literal at position {}", .0.start)]
817    UnterminatedString(Span),
818    /// A `)` with no matching `(`. The span is the offending token — one
819    /// byte — so this renders as a single caret, same as pre-lift.
820    #[error("unmatched closing paren at position {}", .span.start)]
821    UnmatchedParen { span: Span },
822    /// A `(` the reader never found a closer for. The span runs from the
823    /// unclosed `(` to the END OF THE LAST TOKEN read, i.e. the extent of
824    /// the partial form — NOT to `src.len()`, which would drag trailing
825    /// whitespace and comments under the underline. This is the variant
826    /// the `Span` lift buys the most: pre-lift it named the `(` byte and
827    /// rendered one caret, post-lift it underlines the form that was left
828    /// open.
829    #[error("unmatched opening paren at position {}", .span.start)]
830    UnmatchedOpenParen { span: Span },
831    /// Input ran out where a datum was required. A zero-width span AT the
832    /// end offset — there is no text to underline, so `caret_run`'s
833    /// clamp-up-to-one renders the single caret one column past the last
834    /// visible char, exactly as pre-lift.
835    #[error("unexpected end of input at position {}", .span.start)]
836    Eof { span: Span },
837    #[error("invalid number literal {0:?}")]
838    InvalidNumber(String),
839    #[error("unknown symbol: {0}")]
840    UnknownSymbol(String),
841    #[error("type error: expected {expected}, got {got}")]
842    Type { expected: &'static str, got: String },
843    #[error("compile error in {form}: {message}")]
844    Compile { form: String, message: String },
845    /// Structured type mismatch — both sides are first-class fields, not
846    /// embedded substrings of `message`. Rendered as `"compile error in
847    /// {form}: expected {expected}, got {got}"` so the user-facing string
848    /// matches the legacy `Compile`-shaped diagnostic byte-for-byte; the
849    /// gain is structural — authoring tools (REPL, LSP, `tatara-check`)
850    /// pattern-match on the variant and bind directly to `expected` /
851    /// `got` instead of substring-parsing the rendered message.
852    ///
853    /// `form` is the typed closed-set `KwargPath` enum so consumers
854    /// pattern-match on path-shape identity (`KwargPath::Item { .. }`,
855    /// `KwargPath::Slot(_)`, `KwargPath::Named(_)`) directly rather than
856    /// substring-matching the rendered prefix. The Display projection
857    /// flows through `KwargPath::Display`, so the user-facing
858    /// `"compile error in {form}: …"` rendering matches the legacy
859    /// `String`-shaped diagnostic byte-for-byte.
860    ///
861    /// `expected` is the typed closed-set `ExpectedKwargShape` enum —
862    /// the seven reachable expected-shape labels the typed-entry kwarg
863    /// gate emits (`Keyword` ⊎ `String` ⊎ `Int` ⊎ `Number` ⊎ `Bool` ⊎
864    /// `List` ⊎ `ListOfStrings`) encoded as a TYPE so a typo in any
865    /// label literal can never drift into the diagnostic at runtime;
866    /// consumers (REPL, LSP, `tatara-check`) pattern-match on
867    /// `ExpectedKwargShape::Number` etc. directly instead of
868    /// substring-matching `expected == "number"`. Same posture as
869    /// `LispError::Defmacro*.head: MacroDefHead`,
870    /// `LispError::UnboundTemplateVar.prefix: UnquoteForm`,
871    /// `LispError::CompilerSpecIo.stage: CompilerSpecIoStage`,
872    /// `LispError::TemplateInvariant.kind: TemplateInvariantKind`, and
873    /// `LispError::TypeMismatch.form: KwargPath`: the closed set
874    /// becomes a TYPE rather than a `&'static str` projection at the
875    /// helper boundary. The Display projection flows through
876    /// `ExpectedKwargShape::Display`, so the user-facing
877    /// `"... expected {expected}, ..."` rendering matches the legacy
878    /// `&'static str`-shaped diagnostic byte-for-byte.
879    ///
880    /// `got` is the typed closed-set `SexpShape` enum — the twelve
881    /// reachable Sexp outermost shapes (`Nil` ⊎ `Symbol` ⊎ `Keyword` ⊎
882    /// `String` ⊎ `Int` ⊎ `Float` ⊎ `Bool` ⊎ `List` ⊎ `Quote` ⊎
883    /// `Quasiquote` ⊎ `Unquote` ⊎ `UnquoteSplice`) encoded as variant
884    /// identities so the SexpShape that the typed-entry gate observed
885    /// is load-bearing data in the type system. Consumers (REPL, LSP,
886    /// `tatara-check`) pattern-match on `SexpShape::Int` etc. directly
887    /// instead of substring-matching `got == "int"`. Same posture as
888    /// `form: KwargPath` and `expected: ExpectedKwargShape`: after this
889    /// lift the TypeMismatch variant's identity is fully closed-set
890    /// typed in ALL THREE of its slots — no `&'static str` projection
891    /// at any helper boundary, every reachable identity encoded as a
892    /// variant of a typed enum. The Display projection flows through
893    /// `SexpShape::Display` (which delegates to `SexpShape::label()`),
894    /// so the user-facing `"... got {got}"` rendering matches the
895    /// legacy `&'static str`-shaped diagnostic byte-for-byte. When a
896    /// future run gives `Sexp` source spans, `pos: Option<usize>`
897    /// lands here in ONE place and every type-mismatch site picks up
898    /// positional rendering via `crate::diagnostic::format_diagnostic`
899    /// mechanically.
900    #[error("compile error in {form}: expected {expected}, got {got}")]
901    TypeMismatch {
902        form: KwargPath,
903        expected: ExpectedKwargShape,
904        got: SexpShape,
905    },
906    /// Structural head-mismatch — the `(head ...)` of a top-level form
907    /// didn't match `T::KEYWORD`. Both sides are first-class fields, not
908    /// embedded substrings of `message`. Rendered as `"compile error in
909    /// {keyword}: expected ({keyword} ...), got ({got} ...)"` so the
910    /// user-facing string matches the legacy `Compile`-shaped diagnostic
911    /// byte-for-byte; the gain is structural — authoring tools (REPL,
912    /// LSP, `tatara-check`) pattern-match on the variant and bind
913    /// directly to `keyword` / `got` instead of substring-parsing the
914    /// rendered message.
915    ///
916    /// `keyword` is `&'static str` because it always comes from
917    /// `T::KEYWORD`, a compile-time literal; `got` is `String` because
918    /// it is an arbitrary symbol from the source. When a future run
919    /// gives `Sexp` source spans, `pos: Option<usize>` lands here in
920    /// ONE place and every head-mismatch site picks up positional
921    /// rendering via `crate::diagnostic::format_diagnostic`
922    /// mechanically.
923    #[error("compile error in {keyword}: expected ({keyword} ...), got ({got} ...)")]
924    HeadMismatch { keyword: &'static str, got: String },
925    #[error("unknown {category}: {value}")]
926    Unknown {
927        category: &'static str,
928        value: String,
929    },
930    #[error("missing required field: {0}")]
931    Missing(&'static str),
932    /// A kwargs list of odd length: the last element has no partner. The
933    /// `dangling` field holds the offending element's `Sexp::Display`
934    /// projection — `:query` for a keyword whose value got lost, or the
935    /// literal form of a stray non-keyword. Naming both halves of the
936    /// failure (the failure mode AND the offending element) is the
937    /// typed-entry gate's structural-completeness floor (THEORY.md §V.1):
938    /// without it the operator must re-read the source to find what
939    /// actually misfired.
940    #[error("odd keyword arguments: dangling element `{dangling}`")]
941    OddKwargs { dangling: String },
942    /// An unquote (`,name`) or unquote-splice (`,@name`) in a macro template
943    /// body referenced a name that wasn't bound by the macro's params (during
944    /// `compile_template`) or wasn't available in the substitution scope
945    /// (during `substitute`). `prefix` is the syntactic marker — `","` for
946    /// unquote, `",@"` for splice — so the rendered diagnostic preserves the
947    /// form exactly as the author wrote it. `hint` is `Some(name)` when the
948    /// substrate found a near-miss against the available bound names within
949    /// the bounded edit distance (see `crate::domain::suggest`); `None`
950    /// otherwise — a wrong hint is worse than no hint, so the slot stays
951    /// empty unless the substrate is confident.
952    ///
953    /// `prefix` is `UnquoteForm` — the closed-set typed enum whose two
954    /// variants are EXACTLY the two reachable syntactic markers
955    /// (`Unquote` ⊎ `Splice`). Encoding the closed set as a TYPE makes the
956    /// constraint that ONLY 2 marker identities are reachable load-bearing
957    /// in the type system — a third pseudo-marker can never drift into the
958    /// diagnostic at runtime; consumers (REPL, LSP, `tatara-check`)
959    /// pattern-match on `UnquoteForm::Splice` etc. directly instead of
960    /// substring-matching `prefix == ",@"`. Same posture as
961    /// `LispError::Defmacro*.head: MacroDefHead`,
962    /// `LispError::CompilerSpecIo.stage: CompilerSpecIoStage`, and
963    /// `LispError::TemplateInvariant.kind: TemplateInvariantKind`: the
964    /// closed set becomes a TYPE rather than a `&'static str` projection
965    /// at the helper boundary. `name` and `hint` are `String` because
966    /// they come from arbitrary source / the live bindings set. When a
967    /// future run gives `Sexp` source spans, `pos: Option<usize>` lands
968    /// here in ONE place and every unbound-template-var site picks up
969    /// positional rendering via `crate::diagnostic::format_diagnostic`
970    /// mechanically.
971    ///
972    /// Display matches the legacy `Compile`-shaped diagnostic byte-for-byte
973    /// when `hint` is `None` — `"compile error in {prefix}{name}: unbound"`
974    /// — so existing consumer assertions that pattern-match on the message
975    /// substring keep passing. With a hint, the suffix `"; did you mean
976    /// {prefix}{hint}?"` is appended; the prefix is preserved in the hint so
977    /// the operator can copy-paste the suggestion verbatim.
978    #[error(
979        "compile error in {prefix}{name}: unbound{}",
980        unbound_hint_suffix(*prefix, hint.as_deref())
981    )]
982    UnboundTemplateVar {
983        prefix: UnquoteForm,
984        name: String,
985        hint: Option<String>,
986    },
987    /// A kwargs slice contained the same `:key` twice. The offending key is
988    /// carried as a structural field — not embedded in a free-form message —
989    /// so authoring surfaces (REPL, LSP, `tatara-check`) pattern-match on
990    /// the variant and bind to `key` directly instead of substring-parsing
991    /// the rendered diagnostic. Same posture as the `OddKwargs { dangling }`
992    /// sibling: every distinct `parse_kwargs` failure mode (odd length,
993    /// not-a-keyword-at-position, duplicate key) is now a structural variant
994    /// of `LispError`, not a `Compile`-shaped substring.
995    ///
996    /// `key` is `String` because it comes from arbitrary source. Display
997    /// renders `"compile error in :{key}: duplicate keyword"` byte-for-byte
998    /// equivalent to the legacy `Compile { form: kwarg_form(key), message:
999    /// "duplicate keyword" }` shape, so existing consumer assertions
1000    /// (`msg.contains(":name")`, `msg.contains("duplicate keyword")`) pass
1001    /// unchanged. When a future run gives `Sexp` source spans, `pos:
1002    /// Option<usize>` lands here in ONE place and every duplicate-kwarg
1003    /// site picks up positional rendering via
1004    /// `crate::diagnostic::format_diagnostic` mechanically.
1005    #[error("compile error in :{key}: duplicate keyword")]
1006    DuplicateKwarg { key: String },
1007    /// A required kwarg was absent from the kwargs slice. The offending key
1008    /// is carried as a structural field — not embedded in a free-form
1009    /// message — so authoring surfaces (REPL, LSP, `tatara-check`)
1010    /// pattern-match on the variant and bind to `key` directly instead of
1011    /// substring-parsing the rendered diagnostic. Same posture as
1012    /// `DuplicateKwarg { key }` and `OddKwargs { dangling }`: every
1013    /// distinct typed-entry kwarg failure mode is now a structural variant
1014    /// of `LispError`, not a `Compile`-shaped substring. Sibling of the
1015    /// pre-existing `Missing(&'static str)` variant — `MissingKwarg`
1016    /// covers the runtime-key path the kwargs extractors share, while
1017    /// `Missing` stays for compile-time-known names.
1018    ///
1019    /// `key` is `String` because it comes from the runtime kwargs lookup
1020    /// (each derive-generated extractor and every hand-written
1021    /// `TataraDomain` impl can pass an arbitrary key). Display renders
1022    /// `"compile error in :{key}: required but not provided"`
1023    /// byte-for-byte equivalent to the legacy `Compile { form:
1024    /// kwarg_form(key), message: "required but not provided" }` shape, so
1025    /// existing consumer assertions (`msg.contains(":threshold")`,
1026    /// `msg.contains("required")`) pass unchanged. When a future run gives
1027    /// `Sexp` source spans, `pos: Option<usize>` lands here in ONE place
1028    /// and every missing-kwarg site picks up positional rendering via
1029    /// `crate::diagnostic::format_diagnostic` mechanically.
1030    #[error("compile error in :{key}: required but not provided")]
1031    MissingKwarg { key: String },
1032    /// A kwargs slice contained a `:key` that isn't in the allowed-kwarg
1033    /// set for the surrounding `TataraDomain`. The offending key, the
1034    /// near-miss hint (if any), and the full allowed set are all carried
1035    /// as first-class fields — not embedded in a free-form message — so
1036    /// authoring surfaces (REPL, LSP, `tatara-check`) pattern-match on
1037    /// the variant and bind to `key` / `hint` / `allowed` directly
1038    /// instead of substring-parsing the rendered message. Same posture
1039    /// as the `OddKwargs { dangling }`, `DuplicateKwarg { key }`, and
1040    /// `MissingKwarg { key }` siblings: every distinct typed-entry
1041    /// kwarg-gate failure mode is now a structural variant of
1042    /// `LispError`, not a `Compile`-shaped substring.
1043    ///
1044    /// `key` is `String` because it comes from arbitrary source. `hint`
1045    /// is `Some(allowed_keyword)` when `crate::domain::suggest` ranks an
1046    /// allowed kwarg within the bounded edit distance; `None`
1047    /// otherwise — a wrong hint is worse than no hint, so the slot
1048    /// stays empty unless the substrate is confident. `allowed` is
1049    /// `Vec<String>` (sorted lexicographically by `unknown_kwarg`)
1050    /// because the variant owns its data — the derive-emitted
1051    /// `&'static [&'static str]` allowed-set crosses the structural
1052    /// boundary as owned `String`s so the diagnostic crosses thread
1053    /// boundaries cleanly and lives independent of the call frame.
1054    /// Display matches the legacy `Compile { form: kwarg_form(key),
1055    /// message: "unknown keyword (...)" }` rendering byte-for-byte
1056    /// (`"compile error in :{key}: unknown keyword (did you mean
1057    /// :{hint}?; allowed: :a, :b, :c)"` with a hint, `"compile error
1058    /// in :{key}: unknown keyword (allowed: :a, :b, :c)"` without),
1059    /// so existing consumer assertions
1060    /// (`msg.contains("unknown keyword")`,
1061    /// `msg.contains("did you mean :threshold?")`,
1062    /// `msg.contains("allowed: ")`) pass unchanged. When a future
1063    /// run gives `Sexp` source spans, `pos: Option<usize>` lands
1064    /// here in ONE place and every unknown-kwarg site picks up
1065    /// positional rendering via
1066    /// `crate::diagnostic::format_diagnostic` mechanically.
1067    #[error(
1068        "compile error in :{key}: unknown keyword{}",
1069        unknown_kwarg_suffix(hint.as_deref(), allowed)
1070    )]
1071    UnknownKwarg {
1072        key: String,
1073        hint: Option<String>,
1074        allowed: Vec<String>,
1075    },
1076    /// A registry-dispatched form `(<keyword> ...)` whose head symbol isn't in
1077    /// the global `TataraDomain` registry. The offending keyword, the
1078    /// near-miss hint (if any), and the full registered keyword set are all
1079    /// carried as first-class fields — not embedded in a free-form message —
1080    /// so authoring surfaces (REPL, LSP, `tatara-check`) pattern-match on the
1081    /// variant and bind to `keyword` / `hint` / `registered` directly instead
1082    /// of substring-parsing the rendered message. Same posture as the
1083    /// `UnknownKwarg { key, hint, allowed }` sibling: the kwarg-gate's
1084    /// unknown-allowed-set rejection and the registry-gate's
1085    /// unknown-registered-set rejection share ONE structural shape.
1086    ///
1087    /// `keyword` is `String` because it comes from arbitrary source (a
1088    /// top-level form's head symbol). `hint` is
1089    /// `Some(registered_keyword)` when `crate::domain::suggest_keyword`
1090    /// ranks a registered keyword within the bounded edit distance;
1091    /// `None` otherwise — a wrong hint is worse than no hint, so the slot
1092    /// stays empty unless the substrate is confident. `registered` is
1093    /// `Vec<String>` (sorted lexicographically by
1094    /// `unknown_domain_keyword`) because the variant owns its data — the
1095    /// registry's `&'static [&'static str]` keyword-set crosses the
1096    /// structural boundary as owned `String`s so the diagnostic crosses
1097    /// thread boundaries cleanly and lives independent of the call frame.
1098    /// Empty `registered` (no domains seeded) renders `(no domains
1099    /// registered)` so the operator sees the structural reason — the
1100    /// registry has no candidates at all — instead of a misleading empty
1101    /// "registered: " suffix. When a future run gives `Sexp` source
1102    /// spans, `pos: Option<usize>` lands here in ONE place and every
1103    /// unknown-domain-keyword site picks up positional rendering via
1104    /// `crate::diagnostic::format_diagnostic` mechanically.
1105    #[error(
1106        "unknown domain keyword: ({keyword} ...){}",
1107        unknown_domain_keyword_suffix(hint.as_deref(), registered)
1108    )]
1109    UnknownDomainKeyword {
1110        keyword: String,
1111        hint: Option<String>,
1112        registered: Vec<String>,
1113    },
1114    /// The slot inside a `Sexp::Unquote(_)` (`,X`) or
1115    /// `Sexp::UnquoteSplice(_)` (`,@X`) was not a symbol. The `prefix`
1116    /// field is the syntactic marker — `","` for unquote, `",@"` for
1117    /// splice — so the rendered diagnostic preserves the form exactly
1118    /// as the author wrote it. The `got` field is the offending inner's
1119    /// `Sexp::Display` projection so the operator sees both what was
1120    /// expected (a symbol — the only form a no-evaluator template can
1121    /// substitute) and what was actually written (the literal value —
1122    /// `(list 1 2)`, `5`, `:foo`, etc.). Naming both halves of the
1123    /// failure is the typed-entry gate's structural-completeness floor
1124    /// (THEORY.md §V.1).
1125    ///
1126    /// Sibling of `UnboundTemplateVar { prefix, name, hint }` for the
1127    /// same template-side typed-entry surface — that variant fires when
1128    /// the slot IS a symbol but the symbol isn't bound; this variant
1129    /// fires when the slot isn't a symbol at all. After this lift every
1130    /// distinct typed-entry template-gate failure mode binds to ONE
1131    /// structural variant of `LispError`, not a `Compile`-shaped
1132    /// substring.
1133    ///
1134    /// `prefix` is `UnquoteForm` — the closed-set typed enum whose two
1135    /// variants are EXACTLY the two reachable syntactic markers
1136    /// (`Unquote` ⊎ `Splice`). Encoding the closed set as a TYPE makes
1137    /// the constraint load-bearing in the type system — a third
1138    /// pseudo-marker can never drift into the diagnostic at runtime;
1139    /// consumers (REPL, LSP, `tatara-check`) pattern-match on
1140    /// `UnquoteForm::Splice` etc. directly instead of substring-matching
1141    /// `prefix == ",@"`. Same typed-slot posture as `UnboundTemplateVar`'s
1142    /// `prefix` slot, parallel to `LispError::Defmacro*.head:
1143    /// MacroDefHead`. `got` is `SexpWitness` — the closed-set typed
1144    /// joint identity pairing the offending inner's `SexpShape` (the
1145    /// twelve reachable outermost shapes the reader can produce) with
1146    /// its `Sexp::Display` projection (the literal value the author
1147    /// wrote — `(list 1 2)`, `5`, `:foo`, etc.). Same typed-witness
1148    /// posture as `SpliceOutsideList.got: SexpWitness`: authoring
1149    /// tools (REPL, LSP, `tatara-check`) bind to BOTH `got.shape`
1150    /// (structurally pattern-matchable on `SexpShape::List` etc.) AND
1151    /// `got.display` (the literal value, renderable verbatim) without
1152    /// losing either side. The two template-gate `,X/,@X` rejection
1153    /// variants (`NonSymbolUnquoteTarget` AND `SpliceOutsideList`)
1154    /// now share ONE typed witness identity at their `got` slot —
1155    /// every Sexp-display-source `got` slot on the template-gate's
1156    /// distinct rejection variants carries the SAME typed primitive.
1157    /// When a future run gives `Sexp` source spans, `pos:
1158    /// Option<usize>` lands here in ONE place and every
1159    /// non-symbol-unquote-target site picks up positional rendering
1160    /// via `crate::diagnostic::format_diagnostic` mechanically.
1161    #[error("compile error in {prefix}: expected symbol, got {got}")]
1162    NonSymbolUnquoteTarget {
1163        prefix: UnquoteForm,
1164        got: SexpWitness,
1165    },
1166    /// A `,@X` (unquote-splice) appeared at a syntactic position where there
1167    /// is no containing list to splice into — i.e. the splice is the entire
1168    /// macro-template body, not nested inside a `(... ,@xs ...)` list. Splice
1169    /// is always list-flattening: `,@(a b c)` inside `(outer ,@xs)` becomes
1170    /// `(outer a b c)`. At a non-list position there is no list to flatten
1171    /// into; the form is ill-positioned regardless of whether the inner slot
1172    /// is a symbol, a literal, or a bound list.
1173    ///
1174    /// Sibling of `NonSymbolUnquoteTarget { prefix, got }` and
1175    /// `UnboundTemplateVar { prefix, name, hint }` for the template-gate's
1176    /// other distinct failure modes — together the three close every
1177    /// distinct typed-entry template-gate failure mode for the no-evaluator
1178    /// template language: each is a structural variant of `LispError`, not
1179    /// a `Compile`-shaped substring. `prefix` is implicit (always `,@`) and
1180    /// elided from the variant: this failure mode names ONE syntactic
1181    /// marker, parallel to how `OddKwargs` names ONE failure mode (odd-length
1182    /// kwargs slice) without a syntactic-marker slot.
1183    ///
1184    /// `got` is `SexpWitness` — the closed-set typed joint identity
1185    /// pairing the offending inner's `SexpShape` (the twelve reachable
1186    /// outermost shapes the reader can produce) with its
1187    /// `Sexp::Display` projection (the literal value the author wrote
1188    /// — `xs`, `(list 1 2)`, `5`, `:foo`, etc.). Promotes the legacy
1189    /// `got: String` shape to a typed witness so authoring tools (REPL,
1190    /// LSP, `tatara-check`) bind to BOTH `got.shape` (structurally
1191    /// pattern-matchable on `SexpShape::List` etc.) AND `got.display`
1192    /// (the literal value, renderable verbatim) without losing either
1193    /// side. Naming both the failure mode AND the offending element
1194    /// is the typed-entry gate's structural-completeness floor
1195    /// (THEORY.md §V.1) — without it the operator must re-read the
1196    /// source to find what actually misfired. After this lift the
1197    /// structural identity is part of the variant's typed data shape;
1198    /// a regression that re-collapses `got` to a free-form `String`
1199    /// loses the rustc-enforced closed-set guarantee on shape
1200    /// identity.
1201    ///
1202    /// First consumer of the `SexpWitness` primitive. Sibling lifts
1203    /// landed for `NonSymbolUnquoteTarget.got`, `NonSymbolParam.got`,
1204    /// `DefmacroNonSymbolName.got`, and `DefmacroNonListParams.got`;
1205    /// the remaining trajectory —
1206    /// `RestParamMissingName.got: Option<String>` and
1207    /// `MissingHeadSymbol.got: Option<String>` — is the next set of
1208    /// moves: every `got: String` (or `Option<String>`) slot whose
1209    /// source is `Sexp::Display` picks up the typed witness
1210    /// mechanically once the variant's data shape is bumped. The
1211    /// remaining two are both `Option<String>` — the typed witness
1212    /// lands on the `Some` arm directly, the `None` arm encodes the
1213    /// "missing entirely" sub-mode that's structurally distinct from
1214    /// "present but malformed".
1215    ///
1216    /// When a future run gives `Sexp` source spans, `pos: Option<usize>`
1217    /// lands on `SexpWitness` ONCE and every splice-outside-list site
1218    /// picks up positional rendering via
1219    /// `crate::diagnostic::format_diagnostic` mechanically.
1220    ///
1221    /// Display renders `"compile error in ,@: \`,@\` may only appear inside
1222    /// a list (got ,@{got})"` — the legacy substring `"\`,@\` may only
1223    /// appear inside a list"` is preserved verbatim so authoring tools that
1224    /// substring-match on the rendered diagnostic see no drift; the
1225    /// parenthetical `(got ,@{got})` names the offending form so an LSP
1226    /// quick-fix that surfaces "the splice has no containing list; you
1227    /// wrote `,@xs`" gains the literal value as data, no message re-parsing
1228    /// required. The `{got}` slot flows through `SexpWitness::Display`,
1229    /// which writes only the `display` field, so the rendering is
1230    /// byte-for-byte identical to the legacy `got: String` shape.
1231    #[error("compile error in ,@: `,@` may only appear inside a list (got ,@{got})")]
1232    SpliceOutsideList { got: SexpWitness },
1233    /// A macro was called with fewer arguments than its required-param arity:
1234    /// `(defmacro f (a b) `(,a ,b)) (f 1)` — `b` has no arg. Both the failing
1235    /// macro's name AND the un-bound param are first-class structural fields,
1236    /// not embedded substrings of `message`, so authoring surfaces (REPL,
1237    /// LSP, `tatara-check`) pattern-match on the variant and bind to
1238    /// `macro_name` / `param` directly instead of substring-parsing the
1239    /// rendered message. Sibling of `MissingKwarg { key }` for the
1240    /// macro-call-gate's positional-arity surface — that variant fires when
1241    /// a `(<head> :key value …)` kwargs form omits a required keyword;
1242    /// this variant fires when a `(<macroname> a b …)` call omits a required
1243    /// positional param. The two close every distinct typed-entry
1244    /// missing-required surface in the substrate.
1245    ///
1246    /// Same single emission shape across both expansion strategies — the
1247    /// substitute path's `bind_args` and the bytecode path's
1248    /// `apply_compiled` share ONE structural variant, parallel to how the
1249    /// template-gate's `SpliceOutsideList` is shared across both paths
1250    /// (THEORY.md §II.1 invariant 2 — free middle: which strategy you
1251    /// picked must not change which inputs you reject). Before this lift
1252    /// the same failure mode emitted ONE `LispError::Compile { form:
1253    /// format!("call to {macro_name}"), message: format!("missing
1254    /// required arg: {param}") }` triple at TWO call sites — the
1255    /// three-times rule had two sites with byte-identical shape and one
1256    /// failure mode.
1257    ///
1258    /// `macro_name` and `param` are `String` because they come from
1259    /// arbitrary source (the call-site head symbol AND the
1260    /// macro-definition's param symbol). Display matches the legacy
1261    /// `Compile`-shaped diagnostic byte-for-byte — `"compile error in call
1262    /// to {macro_name}: missing required arg: {param}"` — so existing
1263    /// consumer assertions (`msg.contains("missing required arg")`) pass
1264    /// unchanged. When a future run gives `Sexp` source spans, `pos:
1265    /// Option<usize>` lands here in ONE place and every missing-macro-arg
1266    /// site picks up positional rendering via
1267    /// `crate::diagnostic::format_diagnostic` mechanically.
1268    #[error("compile error in call to {macro_name}: missing required arg: {param}")]
1269    MissingMacroArg { macro_name: String, param: String },
1270    /// A macro was called with MORE arguments than its declared
1271    /// required+optional arity, on a param list with NO `&rest` slot to
1272    /// collect the surplus: `(defmacro f (a b) `(,a ,b)) (f 1 2 3)` — `3`
1273    /// has nowhere to bind. The mirror at the call-site of
1274    /// `RestParamTrailingTokens` (the definition-site rejection that
1275    /// surfaces tokens trailing a `&rest <name>` clause, lifted in the
1276    /// prior-run typed-promotion lineage): that variant rejects malformed
1277    /// DEFINITIONS that the typed `MacroParams` shape cannot hold (a
1278    /// `&rest` clause is structurally LAST); this variant rejects
1279    /// malformed CALLS that the typed `bind` cannot honor (a rest-less
1280    /// param list has a FIXED maximum arity equal to
1281    /// `required.len() + optional.len()`). Together with
1282    /// `MissingMacroArg`, the macro-call-gate's positional-arity surface
1283    /// is now structurally complete in both directions — too-few AND
1284    /// too-many — closing the asymmetry where the typed-entry gate
1285    /// rejected too-few-args loudly but silently truncated too-many to
1286    /// the slice `bind` could consume.
1287    ///
1288    /// A rest-PRESENT param list has no maximum arity (the `&rest` slot
1289    /// collects every trailing arg into a `Sexp::List`), so this
1290    /// rejection fires ONLY when `MacroParams.rest` is `None`. The
1291    /// `expected` slot is `required.len() + optional.len()` — the maximum
1292    /// number of args the rest-less binder can consume; `got` is
1293    /// `args.len()` — the actual number supplied. Surfacing both lets
1294    /// authoring tools (REPL, LSP, `tatara-check`) name the
1295    /// "you supplied {got} args but the macro takes at most {expected}"
1296    /// quick-fix without re-deriving either count from the source.
1297    ///
1298    /// The leftmost-priority discipline is preserved: `MissingMacroArg`
1299    /// for a missing REQUIRED arg fires BEFORE this too-many gate
1300    /// (`bind` iterates the required walk first and bails on the first
1301    /// missing slot), so `(defmacro f (a b c) …) (f 1)` is
1302    /// `MissingMacroArg { param: "b" }`, NOT `TooManyMacroArgs`. The two
1303    /// failure modes are structurally disjoint: too-few-required vs.
1304    /// too-many-with-no-rest.
1305    ///
1306    /// `macro_name` is `String` because it comes from arbitrary source
1307    /// (the call-site head symbol); `expected` and `got` are `usize`
1308    /// arities. Display matches the legacy `Compile`-shaped diagnostic
1309    /// style — `"compile error in call to {macro_name}: too many args:
1310    /// expected at most {expected}, got {got}"` — so the same
1311    /// `"compile error in call to {macro_name}:"` substring authoring
1312    /// tools' assertions key on stays unchanged across the new
1313    /// rejection mode. When a future run gives `Sexp` source spans,
1314    /// `pos: Option<usize>` lands here in ONE place and every
1315    /// too-many-macro-args site picks up positional rendering via
1316    /// `crate::diagnostic::format_diagnostic` mechanically — same
1317    /// posture as `MissingMacroArg`.
1318    #[error(
1319        "compile error in call to {macro_name}: too many args: \
1320         expected at most {expected}, got {got}"
1321    )]
1322    TooManyMacroArgs {
1323        macro_name: String,
1324        expected: usize,
1325        got: usize,
1326    },
1327    /// A non-symbol element appeared in a `defmacro` / `defpoint-template`
1328    /// / `defcheck` param list at the named position. The legacy
1329    /// `LispError::Compile { form: "defmacro params", message: "expected
1330    /// symbol" }` shape named only the failure mode — it didn't say WHICH
1331    /// element of the param list misfired NOR what was found in its slot.
1332    /// The structural variant names both: `position` is the 0-based index
1333    /// of the offending element within the param list, `got` is its
1334    /// `Sexp::Display` projection so the operator sees the literal value
1335    /// they wrote (`5`, `"x"`, `:foo`, `(nested)`) instead of the bare
1336    /// "expected symbol" verdict. Naming both the position AND the
1337    /// offending element is the typed-entry gate's
1338    /// structural-completeness floor (THEORY.md §V.1) — without both an
1339    /// LSP that wants to surface "the third element of your param list
1340    /// isn't a symbol; you wrote `5`" must re-parse the source.
1341    ///
1342    /// Sibling of `MissingMacroArg { macro_name, param }` for the
1343    /// macro-call-gate's positional-arity surface — that variant fires
1344    /// when a CALL `(<macroname> a b …)` omits a required positional
1345    /// param; this variant fires when the DEFMACRO `(defmacro <name> (a
1346    /// b …) …)` declaration's param list contains a non-symbol where a
1347    /// param name was expected. The two are the macro-call-gate and the
1348    /// defmacro-syntax-gate's first-named structural failure modes
1349    /// respectively — call-site malformed vs. definition-site malformed.
1350    ///
1351    /// `position` is `usize` because it is always the loop index inside
1352    /// `parse_params`; `got` is `SexpWitness` — the closed-set typed
1353    /// joint identity (structural `SexpShape` + renderable
1354    /// `Sexp::Display` projection) the offending-value side of the
1355    /// typed-entry rejection owes the operator. Third consumer of the
1356    /// `SexpWitness` primitive (after `SpliceOutsideList.got` and
1357    /// `NonSymbolUnquoteTarget.got`); same posture — authoring tools
1358    /// (REPL, LSP, `tatara-check`) bind to BOTH `got.shape`
1359    /// (`SexpShape::Int`, `SexpShape::Keyword`, `SexpShape::List`, etc.)
1360    /// AND `got.display` (the literal value, renderable verbatim)
1361    /// jointly across the variant slot rather than substring-grepping
1362    /// a free-form `String`. Display projects the witness's `display`
1363    /// field verbatim into the `#[error(... got {got})]` annotation's
1364    /// `{got}` slot, so the rendered `"compile error in defmacro params:
1365    /// expected symbol at position {position}, got <display>"` shape is
1366    /// byte-for-byte identical to the pre-lift `got: String` rendering;
1367    /// authoring tools that substring-grep on the rendered diagnostic
1368    /// see no drift. When a future run gives `Sexp` source spans, `pos:
1369    /// Option<usize>` lands inside `SexpWitness` in ONE place and every
1370    /// non-symbol-param site picks up positional rendering via
1371    /// `crate::diagnostic::format_diagnostic` mechanically.
1372    #[error(
1373        "compile error in defmacro params: expected symbol at position \
1374         {position}, got {got}"
1375    )]
1376    NonSymbolParam { position: usize, got: SexpWitness },
1377    /// A `&rest` marker in a `defmacro` / `defpoint-template` / `defcheck`
1378    /// param list was followed by no element at all (`(&rest)`,
1379    /// `(a &rest)`) OR by a non-symbol element (`(&rest 5)`,
1380    /// `(&rest :foo)`). The legacy `LispError::Compile { form: "defmacro
1381    /// params", message: "&rest needs a name" }` shape named only the
1382    /// failure mode — it didn't say WHICH `&rest` (i.e. its position
1383    /// within the param list) misfired NOR what was found in the slot
1384    /// where the rest-name should have been. The structural variant
1385    /// names both: `rest_position` is the 0-based index of the `&rest`
1386    /// marker within the param list, `got` is the offending follower's
1387    /// typed witness (`Some(SexpWitness::new(SexpShape::Int, "5"))`,
1388    /// `Some(SexpWitness::new(SexpShape::Keyword, ":foo"))`,
1389    /// `Some(SexpWitness::new(SexpShape::List, "(nested)"))`) or
1390    /// `None` when the marker was the last element in the list and
1391    /// nothing followed at all. Naming both the marker position AND
1392    /// the offending follower (or its absence) is the typed-entry
1393    /// gate's structural-completeness floor (THEORY.md §V.1) —
1394    /// without both, an LSP that wants to surface "your `&rest` at
1395    /// param-list position 1 has no name; you wrote `5` instead of
1396    /// a symbol" must re-parse the source.
1397    ///
1398    /// Sibling of `NonSymbolParam { position, got }` for the
1399    /// defmacro-syntax-gate's other definition-site failure mode —
1400    /// that variant fires when a NON-`&rest` element at a param
1401    /// position isn't a symbol; this variant fires specifically on the
1402    /// post-`&rest` follower slot, where the failure mode bifurcates
1403    /// into "missing entirely" vs. "present but not a symbol". Both
1404    /// modes share ONE structural variant via `got: Option<SexpWitness>`
1405    /// (parallel to how `UnboundTemplateVar` and `UnknownKwarg` carry
1406    /// `hint: Option<String>` for a present-or-absent secondary slot)
1407    /// rather than splitting into two near-identical variants — the
1408    /// failure mode IS one ("rest name missing"); the bifurcation is
1409    /// in the renderable detail, not in what the gate rejects.
1410    ///
1411    /// Together, `NonSymbolParam` and `RestParamMissingName` close the
1412    /// `parse_params` pair — every distinct failure mode the
1413    /// `parse_params` walker can emit is now a structural variant of
1414    /// `LispError`, not a `Compile`-shaped substring.
1415    ///
1416    /// `rest_position` is `usize` because it is always the loop index
1417    /// inside `parse_params` at which the `&rest` marker was matched;
1418    /// `got` is `Option<SexpWitness>` — the SIXTH consumer of the typed
1419    /// `SexpWitness` primitive (after `SpliceOutsideList.got`,
1420    /// `NonSymbolUnquoteTarget.got`, `NonSymbolParam.got`,
1421    /// `DefmacroNonSymbolName.got`, and `DefmacroNonListParams.got`).
1422    /// The `Option`-shape bifurcates structurally into "missing
1423    /// entirely" (`None`, when the marker was the param list's last
1424    /// element) and "present but malformed" (`Some(SexpWitness)`, when
1425    /// a non-symbol follower came from arbitrary source via
1426    /// `Sexp::Display`); the typed witness lands on the `Some` arm
1427    /// only. Display preserves the legacy `"compile error in defmacro
1428    /// params: &rest needs a name"` prefix byte-for-byte so authoring
1429    /// tools that substring-grep on the rendered diagnostic see no
1430    /// drift; the structural detail (`(rest marker at position
1431    /// {rest_position}, got {got})` when present, `(rest marker at
1432    /// position {rest_position}, none provided)` when absent) is
1433    /// appended. When a future run gives `Sexp` source spans, `pos:
1434    /// Option<usize>` lands inside `SexpWitness` in ONE place and
1435    /// every rest-param-missing-name site picks up positional
1436    /// rendering via `crate::diagnostic::format_diagnostic`
1437    /// mechanically.
1438    #[error(
1439        "compile error in defmacro params: &rest needs a name{}",
1440        rest_param_missing_name_suffix(*rest_position, got.as_ref().map(|w| w.display.as_str()))
1441    )]
1442    RestParamMissingName {
1443        rest_position: usize,
1444        got: Option<SexpWitness>,
1445    },
1446    /// A `&rest <name>` param was followed by one or more further tokens —
1447    /// `(defmacro f (a &rest xs extra) …)`. The `&rest` name binds every
1448    /// remaining call arg into a list, so it is structurally the LAST thing
1449    /// a param list can name: nothing can follow it. Before this variant
1450    /// `parse_params` returned the moment it bound the rest name, SILENTLY
1451    /// DROPPING any trailing tokens — `extra` above vanished with no error,
1452    /// so an author who fat-fingered a stray param (or wrote `&rest xs
1453    /// &optional y` expecting a feature that doesn't exist yet) got no
1454    /// signal that their text was ignored. This variant turns that silent
1455    /// drop into a loud rejection at the typed-entry gate.
1456    ///
1457    /// Sibling of `NonSymbolParam` and `RestParamMissingName` — the third
1458    /// and final `parse_params` definition-site failure mode. The earlier
1459    /// two fire on a param SLOT (a non-symbol where a name was expected) and
1460    /// on the post-`&rest` follower (missing-or-malformed name); this one
1461    /// fires once the rest name is bound and the walker discovers the param
1462    /// list does not end there. Together the three now genuinely close the
1463    /// `parse_params` walker — every shape it accepts is a well-formed
1464    /// `MacroParams`, and every shape it rejects is a structural variant of
1465    /// `LispError`, not a silently-truncated `Vec` nor a `Compile`-shaped
1466    /// substring.
1467    ///
1468    /// `rest_position` is the loop index at which the `&rest` marker was
1469    /// matched (parallel to `RestParamMissingName.rest_position`), so an LSP
1470    /// quick-fix can point at the `&rest` form whose name must be last.
1471    /// `extra` is the count of trailing tokens (always ≥ 1) and `first` is
1472    /// the typed witness of the first of them — the SEVENTH consumer of the
1473    /// `SexpWitness` primitive. `first` is a non-`Option` witness (unlike
1474    /// `RestParamMissingName.got`) because the trailing run is non-empty by
1475    /// construction: this variant is only built when `list[rest_position +
1476    /// 2..]` has a first element. Display appends `(rest marker at position
1477    /// {rest_position}, {extra} trailing after name, first: {first})` via
1478    /// `rest_param_trailing_tokens_suffix`, which delegates the bare
1479    /// parenthetical to the shared `paren_suffix`. When a future run gives
1480    /// `Sexp` source spans, `pos: Option<usize>` lands inside `SexpWitness`
1481    /// in ONE place and this site picks up positional rendering
1482    /// mechanically — exactly as its two siblings do.
1483    #[error(
1484        "compile error in defmacro params: &rest name must be last{}",
1485        rest_param_trailing_tokens_suffix(*rest_position, *extra, &first.display)
1486    )]
1487    RestParamTrailingTokens {
1488        rest_position: usize,
1489        extra: usize,
1490        first: SexpWitness,
1491    },
1492    /// A `defmacro` / `defpoint-template` / `defcheck` param list carried a
1493    /// SECOND `&optional` marker — `(defmacro f (a &optional b &optional c)
1494    /// …)`. The canonical Lisp lambda-list (`(req* &optional opt* &rest r)`,
1495    /// the shape [`MacroParams`](crate::macro_expand::MacroParams) makes a
1496    /// type) has exactly ONE optional section, between the required run and
1497    /// the rest. A second `&optional` is unrepresentable: `MacroParams.optional`
1498    /// is one flat `Vec`, not a sequence of sections. Without this gate the
1499    /// walker would treat the second `&optional` as an optional param literally
1500    /// NAMED `&optional`, binding call args to a marker symbol — the precise
1501    /// silent misalignment the typed param-list shape exists to forbid (a
1502    /// sibling of the index-misalignment `MacroParams` ruled out when it
1503    /// replaced `Vec<Param>`).
1504    ///
1505    /// Sibling of `RestParamTrailingTokens` — both fire INSIDE `parse_params`
1506    /// once a marker is matched and the walker finds the surrounding param
1507    /// list's marker structure is one the canonical lambda-list ordering
1508    /// cannot represent (tokens after `&rest <name>`; a repeated `&optional`).
1509    /// `first_position` is the loop index of the first `&optional` marker,
1510    /// `second_position` the index of the offending second one — naming both
1511    /// lets an LSP quick-fix point at the redundant marker to delete. Neither
1512    /// is a `SexpWitness`: both elements ARE the `&optional` symbol by
1513    /// construction (the variant is only built when `s == "&optional"` twice),
1514    /// so there is nothing to witness — only the two positions carry
1515    /// information. When a future run gives `Sexp` source spans, the marker
1516    /// positions gain editor-ready rendering by threading spans here.
1517    #[error(
1518        "compile error in defmacro params: &optional may appear at most once{}",
1519        optional_marker_repeated_suffix(*first_position, *second_position)
1520    )]
1521    OptionalMarkerRepeated {
1522        first_position: usize,
1523        second_position: usize,
1524    },
1525    /// A `defmacro` / `defpoint-template` / `defcheck` `&optional` section
1526    /// carried a list-form entry that did not match the only admissible
1527    /// shape `(NAME DEFAULT)` — exactly two elements with a symbol head.
1528    /// Per-param default forms are the typed promotion of `optional:
1529    /// Vec<String>` to `optional: Vec<OptionalParam>` that the prior
1530    /// `&optional` run signposted, and this variant is the gate that
1531    /// admits only the canonical `(NAME DEFAULT)` shape into the typed
1532    /// `OptionalParam.default` slot. Four distinct list shapes are
1533    /// rejected, named via the closed-set typed `reason`
1534    /// ([`OptionalParamMalformedReason`]):
1535    ///
1536    ///   * `()`              — empty list spec.
1537    ///   * `(name)`          — one element, no default form supplied.
1538    ///   * `(name d e …)`    — three or more elements (CL's
1539    ///     `(name default supplied-p)` shape is not yet supported — no
1540    ///     `supplied-p` variable binding without an evaluator).
1541    ///   * `(5 default)`     — first element isn't a symbol.
1542    ///
1543    /// Sibling of `OptionalMarkerRepeated` (the `&optional`-section
1544    /// marker gate) and `NonSymbolParam` (the bare-symbol gate): together
1545    /// the three close every distinct typed-entry rejection the optional
1546    /// section can emit. The bare-symbol form `&optional x` is still
1547    /// admitted through the bare-symbol path; the list form `&optional
1548    /// (x default)` is admitted iff this gate accepts the spec.
1549    ///
1550    /// `position` is the loop index of the offending list inside
1551    /// `parse_params`, parallel to `OptionalMarkerRepeated.first_position`
1552    /// / `RestParamTrailingTokens.rest_position` — naming the position
1553    /// lets an LSP quick-fix point at the spec to repair. `got` is
1554    /// `SexpWitness` — the closed-set typed joint identity pairing the
1555    /// offending list's `SexpShape::List` with its `Sexp::Display`
1556    /// projection, so consumers (REPL, LSP, `tatara-check`) bind to
1557    /// BOTH the structural shape AND the renderable literal jointly,
1558    /// same posture as `NonSymbolParam.got` / `OptionalMarkerRepeated`'s
1559    /// SexpWitness siblings. `reason` is `OptionalParamMalformedReason`
1560    /// — the closed-set typed enum whose four variants are EXACTLY the
1561    /// four reachable list-spec rejection modes, encoded as a TYPE so a
1562    /// future fifth reason (e.g. supplied-p once an evaluator lands)
1563    /// becomes a type-level extension rather than a substring drift.
1564    /// Mirror at the `parse_params` optional-section boundary of the
1565    /// prior-run `MacroDefHead` / `UnquoteForm` / `TemplateInvariantKind`
1566    /// / `CompilerSpecIoStage` closed-set lifts.
1567    ///
1568    /// Theory anchor: THEORY.md §V.1 — knowable platform / "make invalid
1569    /// states unrepresentable"; the four malformed list-spec shapes are
1570    /// nonsense `MacroParams` cannot hold, so the gate must REJECT
1571    /// rather than bind args to a marker symbol or drop the extras
1572    /// silently. THEORY.md §II.1 invariant 1 — typed entry; a malformed
1573    /// default-form spec is exactly the failure mode the typed-entry
1574    /// gate exists to reject — and the gate must reject DEFINITIONS as
1575    /// readily as it rejects CALLS. THEORY.md §II.1 invariant 2 — free
1576    /// middle; the gate fires inside `parse_params` BEFORE either
1577    /// expansion strategy runs, so both `Expander::new()` (bytecode) and
1578    /// `Expander::new_substitute_only()` (substitute) reject the SAME
1579    /// malformed spec at the SAME gate.
1580    #[error(
1581        "compile error in defmacro params: malformed &optional spec, got {got}{}",
1582        optional_param_malformed_suffix(*position, *reason)
1583    )]
1584    OptionalParamMalformed {
1585        position: usize,
1586        got: SexpWitness,
1587        reason: OptionalParamMalformedReason,
1588    },
1589    /// A `defmacro` / `defpoint-template` / `defcheck` form had fewer
1590    /// than 4 list elements: the head keyword must be followed by a
1591    /// name symbol, a param list, and a body — three required slots
1592    /// after the head, total length 4. The legacy `LispError::Compile
1593    /// { form: head.to_string(), message: "(defmacro name (params)
1594    /// body) required" }` shape named only the failure mode — it
1595    /// didn't say HOW MANY elements the operator actually wrote, so
1596    /// an authoring surface that wants to surface "you wrote 2
1597    /// elements; need 4" had to re-parse the source. The structural
1598    /// variant carries both: `head` is the head keyword (one of
1599    /// `"defmacro"` / `"defpoint-template"` / `"defcheck"`); `arity`
1600    /// is the actual length of the form, including the head element.
1601    /// Naming the actual arity is the typed-entry gate's structural-
1602    /// completeness floor (THEORY.md §V.1).
1603    ///
1604    /// Sibling of `NonSymbolParam` and `RestParamMissingName` for
1605    /// the defmacro-syntax-gate's other definition-site failure
1606    /// modes — those variants fire INSIDE `parse_params`, AFTER the
1607    /// arity gate has passed; this variant fires AT the arity gate
1608    /// itself, BEFORE name / params / body validation can run.
1609    /// Together, the three close `macro_def_from`'s outermost
1610    /// rejection chain — every distinct failure mode the gate can
1611    /// emit at the top level becomes a structural variant of
1612    /// `LispError`, not a `Compile`-shaped substring.
1613    ///
1614    /// `head` is `MacroDefHead` — the closed-set typed enum whose
1615    /// three variants are EXACTLY the three reachable head keywords
1616    /// (`Defmacro` ⊎ `DefpointTemplate` ⊎ `Defcheck`). Encoding the
1617    /// closed set as a TYPE makes the constraint that ONLY 3 head
1618    /// identities are reachable load-bearing in the type system — a
1619    /// fourth pseudo-head can never drift into the diagnostic at
1620    /// runtime; consumers (REPL, LSP, `tatara-check`) pattern-match
1621    /// on `MacroDefHead::Defcheck` etc. directly instead of
1622    /// substring-matching `head == "defcheck"`. Same posture as
1623    /// `LispError::CompilerSpecIo.stage: CompilerSpecIoStage` and
1624    /// `LispError::TemplateInvariant.kind: TemplateInvariantKind`:
1625    /// the closed set becomes a TYPE rather than a `&'static str`
1626    /// projection at the helper boundary. `arity` is `usize` because
1627    /// it is always `list.len()` at the call site (the length of
1628    /// the form including the head element). Display renders the
1629    /// head via `MacroDefHead`'s Display impl (which projects
1630    /// through `MacroDefHead::keyword()` to the canonical `&'static
1631    /// str` literal), so the legacy `head: &'static str`-shaped
1632    /// diagnostic rides through byte-for-byte.
1633    ///
1634    /// Display preserves the legacy `"(defmacro name (params) body)
1635    /// required"` substring byte-for-byte: the head is parameterized
1636    /// in the prefix `compile error in {head}:`, but the example
1637    /// template literal stays `(defmacro name (params) body)` —
1638    /// matching the legacy form's small infidelity for non-defmacro
1639    /// heads (the legacy shape rendered `compile error in
1640    /// defpoint-template: (defmacro name (params) body) required`)
1641    /// so authoring tools that substring-grep on the legacy
1642    /// rendering see no drift; the structural detail (`got {arity}
1643    /// elements, need 4`) is appended. When a future run gives
1644    /// `Sexp` source spans, `pos: Option<usize>` lands here in ONE
1645    /// place and every defmacro-arity site picks up positional
1646    /// rendering via `crate::diagnostic::format_diagnostic`
1647    /// mechanically.
1648    #[error(
1649        "compile error in {head}: (defmacro name (params) body) required \
1650         (got {arity} elements, need 4)"
1651    )]
1652    DefmacroArity { head: MacroDefHead, arity: usize },
1653    /// A `defmacro` / `defpoint-template` / `defcheck` form passed the
1654    /// arity gate (≥4 elements) but its name slot — `list[1]`, the
1655    /// element directly after the head — wasn't a symbol. The legacy
1656    /// `LispError::Compile { form: head.to_string(), message: "expected
1657    /// name symbol" }` shape named only the failure mode — it didn't
1658    /// say WHAT was found in the name slot, so an authoring surface
1659    /// that wants to surface "you wrote `5` where a name symbol was
1660    /// expected" had to re-parse the source. The structural variant
1661    /// carries both: `head` is the head keyword (one of `"defmacro"` /
1662    /// `"defpoint-template"` / `"defcheck"`); `got` is the offending
1663    /// `Sexp::Display` projection of the non-symbol element. Naming
1664    /// both the head AND the offending element is the typed-entry
1665    /// gate's structural-completeness floor (THEORY.md §V.1).
1666    ///
1667    /// Sibling of `DefmacroArity` and the `parse_params` pair
1668    /// (`NonSymbolParam`, `RestParamMissingName`) for the
1669    /// defmacro-syntax-gate's other definition-site failure modes.
1670    /// Walking a malformed `(defmacro …)` from the outside in, the
1671    /// gate fires:
1672    ///   1. `DefmacroArity { head, arity }` if the form has fewer
1673    ///      than 4 elements (`(defmacro)`, `(defmacro f)`).
1674    ///   2. `DefmacroNonSymbolName { head, got }` if list[1] isn't a
1675    ///      symbol (`(defmacro 5 () body)`, `(defmacro :foo () body)`).
1676    ///   3. Inside `parse_params`: `NonSymbolParam { position, got }`
1677    ///      and `RestParamMissingName { rest_position, got }`.
1678    ///
1679    /// This run lifts step 2; the only remaining `Compile`-shaped
1680    /// site in `macro_def_from` is the `expected param list` gate
1681    /// (list[2] is not a list), which is the next move in the same
1682    /// rejection chain.
1683    ///
1684    /// `head` is `MacroDefHead` — same typed closed-set posture as
1685    /// `DefmacroArity.head`: the three reachable head identities
1686    /// (`Defmacro` ⊎ `DefpointTemplate` ⊎ `Defcheck`) are encoded as
1687    /// a TYPE so consumers pattern-match on variant identity rather
1688    /// than substring-comparing the rendered `head` literal.
1689    /// `got` is `SexpWitness` — the closed-set typed joint identity
1690    /// pairing the offending name-slot element's `SexpShape` (the
1691    /// twelve reachable outermost shapes the reader can produce) with
1692    /// its `Sexp::Display` projection (`5`, `:foo`, `"name"`,
1693    /// `(nested)`, etc.). Fourth consumer of the `SexpWitness`
1694    /// primitive (after `SpliceOutsideList.got`,
1695    /// `NonSymbolUnquoteTarget.got`, and `NonSymbolParam.got`):
1696    /// authoring tools (REPL, LSP, `tatara-check`) bind to BOTH
1697    /// `got.shape` (structurally pattern-matchable on `SexpShape::Int`,
1698    /// `SexpShape::Keyword`, `SexpShape::List`, etc.) AND `got.display`
1699    /// (the literal value, renderable verbatim) jointly across the
1700    /// variant slot.
1701    ///
1702    /// Display preserves the legacy `"expected name symbol"` substring
1703    /// byte-for-byte: the prefix `compile error in {head}:` matches
1704    /// the legacy `Compile { form: head.to_string(), message:
1705    /// "expected name symbol" }` shape; the structural detail (`,
1706    /// got {got}`) is appended. `{got}` flows through
1707    /// `SexpWitness::Display`, which writes only the `display` field,
1708    /// so the rendering is byte-for-byte identical to the legacy
1709    /// `got: String` shape. When a future run gives `Sexp` source
1710    /// spans, `pos: Option<usize>` lands inside `SexpWitness` in ONE
1711    /// place and every non-symbol-name site picks up positional
1712    /// rendering via `crate::diagnostic::format_diagnostic`
1713    /// mechanically.
1714    #[error("compile error in {head}: expected name symbol, got {got}")]
1715    DefmacroNonSymbolName {
1716        head: MacroDefHead,
1717        got: SexpWitness,
1718    },
1719    /// A `defmacro` / `defpoint-template` / `defcheck` form passed both
1720    /// the arity gate (≥4 elements) AND the name-symbol gate (list[1]
1721    /// is a symbol) but its param-list slot — `list[2]`, the third
1722    /// element after the head — wasn't a list. The legacy
1723    /// `LispError::Compile { form: head.to_string(), message: "expected
1724    /// param list" }` shape named only the failure mode — it didn't
1725    /// say WHAT was found in the param-list slot, so an authoring
1726    /// surface that wants to surface "you wrote `x` where a param list
1727    /// was expected" had to re-parse the source. The structural variant
1728    /// carries both: `head` is the head keyword (one of `"defmacro"` /
1729    /// `"defpoint-template"` / `"defcheck"`); `got` is the offending
1730    /// `Sexp::Display` projection of the non-list element. Naming both
1731    /// the head AND the offending element is the typed-entry gate's
1732    /// structural-completeness floor (THEORY.md §V.1).
1733    ///
1734    /// Sibling of `DefmacroArity`, `DefmacroNonSymbolName`, and the
1735    /// `parse_params` pair (`NonSymbolParam`, `RestParamMissingName`)
1736    /// for the defmacro-syntax-gate's other definition-site failure
1737    /// modes. Walking a malformed `(defmacro …)` from the outside in,
1738    /// the gate fires:
1739    ///   1. `DefmacroArity { head, arity }` if the form has fewer
1740    ///      than 4 elements (`(defmacro)`, `(defmacro f)`).
1741    ///   2. `DefmacroNonSymbolName { head, got }` if list[1] isn't a
1742    ///      symbol (`(defmacro 5 () body)`).
1743    ///   3. `DefmacroNonListParams { head, got }` if list[2] isn't a
1744    ///      list (`(defmacro f x body)`).
1745    ///   4. Inside `parse_params`: `NonSymbolParam { position, got }`
1746    ///      and `RestParamMissingName { rest_position, got }`.
1747    ///
1748    /// This run lifts step 3; after it, every inline `LispError::Compile
1749    /// { … }` triple in `macro_def_from` has been lifted to a structural
1750    /// variant — the entire `macro_def_from` rejection chain (arity →
1751    /// name-symbol → param-list → parse_params) is structurally typed
1752    /// for failure modes, with each variant naming WHICH failure mode
1753    /// AND WHAT was offending.
1754    ///
1755    /// `head` is `MacroDefHead` — same typed closed-set posture as
1756    /// `DefmacroArity.head` and `DefmacroNonSymbolName.head`. After
1757    /// this lift all three `Defmacro*` variants share ONE typed
1758    /// head identity, parallel to how `LispError::CompilerSpecIo`
1759    /// carries `stage: CompilerSpecIoStage` for the four
1760    /// disk-persistence (operation, stage) pairs.
1761    /// `got` is `SexpWitness` — the closed-set typed joint identity
1762    /// pairing the offending param-list-slot element's `SexpShape`
1763    /// (the twelve reachable outermost shapes the reader can produce)
1764    /// with its `Sexp::Display` projection (`x`, `5`, `:foo`,
1765    /// `"params"`, etc.). Fifth consumer of the `SexpWitness`
1766    /// primitive (after `SpliceOutsideList.got`,
1767    /// `NonSymbolUnquoteTarget.got`, `NonSymbolParam.got`, and
1768    /// `DefmacroNonSymbolName.got`): authoring tools (REPL, LSP,
1769    /// `tatara-check`) bind to BOTH `got.shape` (structurally
1770    /// pattern-matchable on `SexpShape::Symbol`, `SexpShape::Int`,
1771    /// `SexpShape::Keyword`, `SexpShape::String`, etc.) AND
1772    /// `got.display` (the literal value, renderable verbatim) jointly
1773    /// across the variant slot. After this lift the entire
1774    /// `macro_def_from` rejection chain — arity → name-symbol →
1775    /// param-list — shares ONE typed witness identity at every
1776    /// `Sexp::Display`-source slot; the only remaining unlifted
1777    /// rejection points in `macro_def_from`'s typed-entry chain are
1778    /// `RestParamMissingName.got: Option<String>` (inside
1779    /// `parse_params`) and `MissingHeadSymbol.got: Option<String>`
1780    /// (at the outer typed-entry gate).
1781    ///
1782    /// Display preserves the legacy `"expected param list"` substring
1783    /// byte-for-byte: the prefix `compile error in {head}:` matches
1784    /// the legacy `Compile { form: head.to_string(), message:
1785    /// "expected param list" }` shape; the structural detail (`, got
1786    /// {got}`) is appended. `{got}` flows through
1787    /// `SexpWitness::Display`, which writes only the `display` field,
1788    /// so the rendering is byte-for-byte identical to the legacy
1789    /// `got: String` shape. When a future run gives `Sexp` source
1790    /// spans, `pos: Option<usize>` lands inside `SexpWitness` in ONE
1791    /// place and every non-list-params site picks up positional
1792    /// rendering via `crate::diagnostic::format_diagnostic`
1793    /// mechanically.
1794    #[error("compile error in {head}: expected param list, got {got}")]
1795    DefmacroNonListParams {
1796        head: MacroDefHead,
1797        got: SexpWitness,
1798    },
1799    /// `T::compile_from_sexp` (the `TataraDomain` trait default) was
1800    /// passed something that isn't a list — a bare atom (`5`, `:foo`,
1801    /// `"x"`, `name`) where a top-level `(KEYWORD …)` form was
1802    /// expected. The legacy `LispError::Compile { form:
1803    /// keyword.to_string(), message: "expected list form" }` shape
1804    /// named only the failure mode and the keyword, and required
1805    /// authoring tools (REPL, LSP, `tatara-check`) to substring-grep
1806    /// the rendered message to recognize this specific gate. The
1807    /// structural variant carries `keyword` as a first-class field so
1808    /// consumers pattern-match on the variant and bind directly to
1809    /// the keyword instead of substring-parsing.
1810    ///
1811    /// Sibling of `HeadMismatch` — both are typed-entry rejection
1812    /// gates inside the trait default `compile_from_sexp` walking a
1813    /// malformed form from the outside in:
1814    ///   1. `NotAListForm { keyword }` if the form isn't a list at
1815    ///      all (`5`, `:foo`, `"x"`, `name` — bare atoms).
1816    ///   2. `LispError::Compile { form, message: "missing head
1817    ///      symbol" }` (NOT YET LIFTED) if the list is empty or
1818    ///      list[0] isn't a symbol (`()`, `(5 …)`, `(:foo …)`).
1819    ///   3. `HeadMismatch { keyword, got }` if list[0] is a symbol
1820    ///      but doesn't match `T::KEYWORD` (`(other-name …)`).
1821    ///
1822    /// After this lift step 1 is structural; the `missing head
1823    /// symbol` gate is the next move in the same rejection chain
1824    /// (its own structural-variant lift, parallel to how the
1825    /// `defmacro_*` family was lifted gate-by-gate).
1826    ///
1827    /// `keyword` is `&'static str` because every call site passes
1828    /// `Self::KEYWORD` from the trait default — a compile-time
1829    /// literal sourced from the `#[tatara(keyword = "...")]` derive
1830    /// attribute (or hand-written const). Using a static slot makes
1831    /// that compile-time guarantee load-bearing in the type system
1832    /// (a typo in the keyword can never drift into the diagnostic at
1833    /// runtime — the type system is the floor, same posture as
1834    /// `HeadMismatch.keyword`, `TypeMismatch.expected`, and the
1835    /// `Defmacro*.head` family).
1836    ///
1837    /// Display preserves the legacy `"expected list form"` substring
1838    /// AND the `"compile error in {keyword}:"` prefix byte-for-byte
1839    /// — `"compile error in {keyword}: expected list form"` — so
1840    /// existing consumer assertions (e.g., the
1841    /// `compile_from_sexp_emits_compile_for_non_list_form` test
1842    /// against `MonitorSpec`, `tatara-check`'s diagnostic capture)
1843    /// pass unchanged. The variant carries no `got` slot because the
1844    /// offending value's type is itself the diagnostic — `5` /
1845    /// `:foo` / `"x"` / `name` all reduce to the same failure mode
1846    /// (not a list); naming the type would be redundant with what
1847    /// the source already shows. When a future run gives `Sexp`
1848    /// source spans, `pos: Option<usize>` lands here in ONE place
1849    /// and every not-a-list-form site picks up positional rendering
1850    /// via `crate::diagnostic::format_diagnostic` mechanically.
1851    #[error("compile error in {keyword}: expected list form")]
1852    NotAListForm { keyword: &'static str },
1853    /// `T::compile_from_sexp` was passed a list whose head can't be
1854    /// projected to a symbol — either the list is empty (`()` — there
1855    /// is no first element) or its first element exists but isn't a
1856    /// symbol (`(5 …)`, `(:foo …)`, `("x" …)`, `((nested) …)`). The
1857    /// legacy `LispError::Compile { form: keyword.to_string(),
1858    /// message: "missing head symbol" }` shape collapsed both
1859    /// sub-modes into one diagnostic — a `()` form and a `(5 …)` form
1860    /// produced byte-identical messages, so an authoring surface that
1861    /// wants to surface "your form is empty" vs. "your form's head is
1862    /// `5`, not a symbol" had to re-parse the source. The structural
1863    /// variant carries `got: Option<String>` so the two sub-modes are
1864    /// distinguishable structurally — `None` for the empty-list case,
1865    /// `Some(g)` for the present-but-not-symbol case where `g` is
1866    /// the offending head's `Sexp::Display` projection. Naming both
1867    /// the failure mode AND the structural detail (empty vs. offending
1868    /// head) is the typed-entry gate's structural-completeness floor
1869    /// (THEORY.md §V.1).
1870    ///
1871    /// Sibling of `NotAListForm { keyword }` and `HeadMismatch
1872    /// { keyword, got }` — together the three close every distinct
1873    /// failure mode the trait-default `compile_from_sexp` rejection
1874    /// chain can emit. Walking a malformed `(KEYWORD …)` form from
1875    /// the outside in:
1876    ///   1. `NotAListForm { keyword }` — the form isn't a list at all
1877    ///      (`5`, `:foo`, `"x"`, `name` — bare atoms).
1878    ///   2. `MissingHeadSymbol { keyword, got }` — the form is a
1879    ///      list but list[0] doesn't exist (`()`) or isn't a symbol
1880    ///      (`(5 …)`, `(:foo …)`).
1881    ///   3. `HeadMismatch { keyword, got }` — list[0] is a symbol
1882    ///      but doesn't match `T::KEYWORD` (`(other-name …)`).
1883    ///
1884    /// After this lift the entire `compile_from_sexp` rejection chain
1885    /// is structurally typed for failure modes — every distinct
1886    /// rejection binds to ONE structural variant of `LispError`, not
1887    /// a `Compile`-shaped substring. The `got: Option<String>`
1888    /// posture parallels `RestParamMissingName.got: Option<String>`:
1889    /// the failure mode IS one ("head can't be projected to a
1890    /// symbol"); the bifurcation is in the renderable detail (empty
1891    /// vs. present-but-wrong-type), not in what the gate rejects, so
1892    /// the two sub-modes share ONE variant rather than splitting into
1893    /// near-identical siblings.
1894    ///
1895    /// `keyword` is `&'static str` because every call site passes
1896    /// `Self::KEYWORD` from the trait default — a compile-time literal
1897    /// sourced from the `#[tatara(keyword = "...")]` derive attribute
1898    /// (or hand-written const). Using a static slot makes that
1899    /// compile-time guarantee load-bearing in the type system (a typo
1900    /// in the keyword can never drift into the diagnostic at runtime —
1901    /// the type system is the floor, same posture as
1902    /// `NotAListForm.keyword`, `HeadMismatch.keyword`, and the
1903    /// `Defmacro*.head` family). `got` is `Option<SexpWitness>` — the
1904    /// SEVENTH consumer of the typed `SexpWitness` primitive (after
1905    /// `SpliceOutsideList.got`, `NonSymbolUnquoteTarget.got`,
1906    /// `NonSymbolParam.got`, `DefmacroNonSymbolName.got`,
1907    /// `DefmacroNonListParams.got`, and `RestParamMissingName.got`).
1908    /// The `Option`-wrap bifurcates structurally between "missing
1909    /// entirely" (`None`, when the list is empty) and "present but
1910    /// malformed" (`Some(SexpWitness)`, when the head exists but
1911    /// isn't a symbol); the typed witness lands on the `Some` arm
1912    /// only — same posture as `RestParamMissingName.got:
1913    /// Option<SexpWitness>`. With this lift EVERY Sexp-display-source
1914    /// `got` slot in the substrate carries ONE typed identity:
1915    /// the typed-entry / template-gate / defmacro-syntax-gate
1916    /// rejection surface is structurally unified end-to-end across
1917    /// ALL `got: <T>` slots where `<T>` projects from `Sexp::Display`.
1918    ///
1919    /// Display preserves the legacy `"missing head symbol"` substring
1920    /// AND the `"compile error in {keyword}:"` prefix byte-for-byte —
1921    /// `"compile error in {keyword}: missing head symbol"` is the
1922    /// stable prefix; the structural detail (`(empty list)` for
1923    /// `None`, `(got {g})` for `Some(g)`) is appended in a
1924    /// parenthetical, parallel to how `RestParamMissingName` appends
1925    /// `(rest marker at position {n}, got {g})` /
1926    /// `(rest marker at position {n}, none provided)` and how
1927    /// `SpliceOutsideList` appends `(got ,@{got})`. The `{g}` slot
1928    /// flows through `SexpWitness::Display`, which writes only the
1929    /// `display` field, so the rendering is byte-for-byte identical
1930    /// to the pre-lift `Option<String>` shape. When a future run
1931    /// gives `Sexp` source spans, `pos: Option<usize>` lands inside
1932    /// `SexpWitness` in ONE place and every missing-head-symbol site
1933    /// picks up positional rendering via
1934    /// `crate::diagnostic::format_diagnostic` mechanically.
1935    #[error(
1936        "compile error in {keyword}: missing head symbol{}",
1937        missing_head_symbol_suffix(got.as_ref().map(|w| w.display.as_str()))
1938    )]
1939    MissingHeadSymbol {
1940        keyword: &'static str,
1941        got: Option<SexpWitness>,
1942    },
1943    /// `compile_named_from_forms::<T>` — driving every `(KEYWORD NAME …)`
1944    /// positional-name surface (`(defpoint NAME …)`, `(defalertpolicy
1945    /// NAME …)`) — was passed a list whose head matched `T::KEYWORD` but
1946    /// whose tail had no NAME slot at all. `(defpoint)` — list.len() == 1
1947    /// (just the keyword); the gate fires before NAME extraction. The
1948    /// legacy `LispError::Compile { form: T::KEYWORD.to_string(),
1949    /// message: format!("expected ({} NAME …)", T::KEYWORD) }` shape
1950    /// named the failure mode AND the keyword by embedding both into a
1951    /// formatted message — required authoring tools (REPL, LSP,
1952    /// `tatara-check`) to substring-grep the rendered diagnostic to
1953    /// recognize this specific gate. The structural variant carries
1954    /// `keyword` as a first-class field so consumers pattern-match on
1955    /// the variant and bind to the keyword directly instead of
1956    /// substring-parsing.
1957    ///
1958    /// Sibling of `NotAListForm { keyword }`, `MissingHeadSymbol
1959    /// { keyword, got }`, and `HeadMismatch { keyword, got }` — those
1960    /// close the trait-default `compile_from_sexp` rejection chain
1961    /// (the keyword-only entry point, `(KEYWORD :k v …)`); this
1962    /// variant opens the parallel `compile_named_from_forms`
1963    /// rejection chain (the positional-name entry point, `(KEYWORD
1964    /// NAME :k v …)`). Walking a malformed `(KEYWORD NAME …)` form
1965    /// from the outside in:
1966    ///   1. `NamedFormMissingName { keyword }` — the form passes the
1967    ///      keyword-head match but has no NAME slot (`(defpoint)`).
1968    ///   2. `LispError::Compile { form, message: "positional NAME
1969    ///      must be a symbol or string" }` (NOT YET LIFTED) — the
1970    ///      form has a NAME slot but it's not a symbol or string
1971    ///      (`(defpoint 5 …)`, `(defpoint :foo …)`, `(defpoint
1972    ///      (nested) …)`).
1973    ///   3. Inside `T::compile_from_args(&list[2..])` — derive-
1974    ///      generated kwargs handling with its own structural
1975    ///      variants (`UnknownKwarg`, `MissingKwarg`, etc.).
1976    ///
1977    /// This run lifts step 1; step 2 is the next move in the same
1978    /// rejection chain (its own structural-variant lift, parallel to
1979    /// how the `compile_from_sexp` chain was lifted gate-by-gate
1980    /// across `092a2b2` (`NotAListForm`) and `b3e941e`
1981    /// (`MissingHeadSymbol`)).
1982    ///
1983    /// `keyword` is `&'static str` because every call site passes
1984    /// `T::KEYWORD` from `compile_named_from_forms` — a compile-time
1985    /// literal sourced from the `#[tatara(keyword = "...")]` derive
1986    /// attribute (or hand-written const). Using a static slot makes
1987    /// that compile-time guarantee load-bearing in the type system —
1988    /// a typo in the keyword can never drift into the diagnostic at
1989    /// runtime, the type system is the floor, same posture as
1990    /// `NotAListForm.keyword`, `MissingHeadSymbol.keyword`,
1991    /// `HeadMismatch.keyword`, `TypeMismatch.expected`, and the
1992    /// `Defmacro*.head` family. The variant carries no `arity` slot
1993    /// because the offending form's structure is invariant — every
1994    /// trigger has list.len() == 1 exactly (list[0] is the keyword,
1995    /// no list[1] for NAME); naming a fixed value would be
1996    /// misleading data, parallel to how `NotAListForm` carries no
1997    /// `got` slot (the form's not-a-list type is itself the
1998    /// diagnostic).
1999    ///
2000    /// Display matches the legacy `Compile`-shaped diagnostic
2001    /// byte-for-byte — `"compile error in {keyword}: expected
2002    /// ({keyword} NAME …)"` (note: `…` is the Unicode horizontal
2003    /// ellipsis U+2026, preserved verbatim from the legacy
2004    /// `format!("expected ({} NAME …)", T::KEYWORD)` shape) — so
2005    /// existing consumer assertions (`tatara-check`'s diagnostic
2006    /// capture, REPL substring-greps) pass unchanged. When a future
2007    /// run gives `Sexp` source spans, `pos: Option<usize>` lands
2008    /// here in ONE place and every named-form-missing-name site
2009    /// picks up positional rendering via
2010    /// `crate::diagnostic::format_diagnostic` mechanically.
2011    #[error("compile error in {keyword}: expected ({keyword} NAME …)")]
2012    NamedFormMissingName { keyword: &'static str },
2013    /// `compile_named_from_forms::<T>` was passed a `(KEYWORD NAME …)` form
2014    /// whose NAME slot exists but isn't projectable to a symbol or string —
2015    /// `(defpoint 5 …)`, `(defpoint :foo …)`, `(defpoint (nested) …)`. Gate
2016    /// 2 of the same rejection chain `NamedFormMissingName` opens: that
2017    /// variant fires when there is no NAME slot at all (`(defpoint)` —
2018    /// list.len() == 1); this variant fires when the NAME slot exists but
2019    /// is wrong-typed. Together the two close `compile_named_from_forms`'s
2020    /// outer rejection chain — every typed-entry rejection mode in the
2021    /// positional-name authoring surface is now a structural variant of
2022    /// `LispError`, not a `Compile`-shaped substring.
2023    ///
2024    /// `keyword` is `&'static str` because every call site passes
2025    /// `T::KEYWORD` — a compile-time literal sourced from the
2026    /// `#[tatara(keyword = "...")]` derive attribute (or hand-written
2027    /// const); a typo in the keyword can never drift into the diagnostic
2028    /// at runtime. `got` is the typed closed-set `SexpShape` enum —
2029    /// the twelve reachable Sexp outermost shapes encoded as variant
2030    /// identities so the SexpShape that the typed-entry gate observed
2031    /// is load-bearing data in the type system. Same posture as
2032    /// `TypeMismatch.got: SexpShape`: consumers pattern-match on
2033    /// `SexpShape::Int` etc. directly instead of substring-matching
2034    /// `got == "int"`. Encoding the closed set as a TYPE makes the
2035    /// compile-time guarantee load-bearing, parallel to
2036    /// `NotAListForm.keyword`, `MissingHeadSymbol.keyword`,
2037    /// `HeadMismatch.keyword`, and the `Defmacro*.head` family.
2038    ///
2039    /// Display preserves the legacy `"positional NAME must be a symbol
2040    /// or string"` substring AND the `"compile error in {keyword}:"`
2041    /// prefix byte-for-byte; the structural detail (`(got {got})`) is
2042    /// appended in a parenthetical, parallel to how `MissingHeadSymbol`
2043    /// appends `(got {g})` / `(empty list)` and how `RestParamMissingName`
2044    /// appends `(rest marker at position {n}, got {g})`. When a future
2045    /// run gives `Sexp` source spans, `pos: Option<usize>` lands here in
2046    /// ONE place and every named-form-non-symbol-name site picks up
2047    /// positional rendering via `crate::diagnostic::format_diagnostic`
2048    /// mechanically.
2049    #[error("compile error in {keyword}: positional NAME must be a symbol or string (got {got})")]
2050    NamedFormNonSymbolName {
2051        keyword: &'static str,
2052        got: SexpShape,
2053    },
2054    /// `rewrite_typed::<T>` — the typed-exit gate of the self-optimization
2055    /// primitive (THEORY.md §II.1 invariant 3) — was handed a rewriter
2056    /// closure whose output, after typed round-trip through canonical JSON,
2057    /// did not project to `Sexp::List`. The round-trip contract is:
2058    /// serialize `T` → `Sexp::List` (alternating kwargs), hand the list
2059    /// to the rewriter `F`, re-enter `T::compile_from_args` over the
2060    /// returned list's items. A non-list result violates that contract —
2061    /// the gate fires before `compile_from_args` runs, so a wrong-shaped
2062    /// rewriter output is rejected at the typed-exit boundary rather than
2063    /// confusingly later inside the kwargs decoder.
2064    ///
2065    /// Mirror at the typed-exit boundary of the typed-entry-side
2066    /// `NamedFormNonSymbolName` lift: the latter rejects a wrong-typed
2067    /// NAME slot at `compile_named_from_forms::<T>`'s entry; this variant
2068    /// rejects a wrong-typed rewriter output at `rewrite_typed::<T>`'s
2069    /// exit. Both round-trip the same compile-time `T::KEYWORD` projection
2070    /// into the variant's `keyword` slot, so authoring tools (REPL, LSP,
2071    /// `tatara-check`) bind on variant identity at both boundaries of the
2072    /// self-optimization primitive rather than substring-grepping the
2073    /// rendered diagnostic.
2074    ///
2075    /// `keyword` is `&'static str` because every call site passes
2076    /// `T::KEYWORD` from `rewrite_typed::<T>` — a compile-time literal
2077    /// sourced from the `#[tatara(keyword = "...")]` derive attribute (or
2078    /// hand-written const). Using a static slot makes that compile-time
2079    /// guarantee load-bearing in the type system — a typo can never drift
2080    /// into the diagnostic at runtime, the type system is the floor, same
2081    /// posture as `NotAListForm.keyword`, `MissingHeadSymbol.keyword`,
2082    /// `HeadMismatch.keyword`, `NamedFormMissingName.keyword`, and
2083    /// `NamedFormNonSymbolName.keyword`.
2084    ///
2085    /// `got` is `SexpWitness` — the closed-set typed joint identity
2086    /// pairing the offending rewriter output's `SexpShape` (the twelve
2087    /// reachable Sexp outermost shapes the rewriter closure can produce)
2088    /// with its `Sexp::Display` projection (the literal value the rewriter
2089    /// actually returned — `42`, `:foo`, `"bad"`, `notify-ref`, `()`,
2090    /// etc.). EIGHTH consumer of the typed `SexpWitness` primitive
2091    /// introduced in `error.rs`'s `SpliceOutsideList.got` lift, and the
2092    /// FIRST consumer on the typed-EXIT boundary — sibling lifts of
2093    /// `SpliceOutsideList.got: SexpWitness`, `NonSymbolUnquoteTarget.got:
2094    /// SexpWitness`, `NonSymbolParam.got: SexpWitness`,
2095    /// `DefmacroNonSymbolName.got: SexpWitness`,
2096    /// `DefmacroNonListParams.got: SexpWitness`,
2097    /// `RestParamMissingName.got: Option<SexpWitness>`, and
2098    /// `MissingHeadSymbol.got: Option<SexpWitness>` close the typed-ENTRY
2099    /// rejection surface across the substrate's seven entry-side gates.
2100    /// This eighth lift extends the typed-identity unification contract
2101    /// across BOTH boundaries of the typed-IR algebra
2102    /// (THEORY.md §II.1 invariant 1 + invariant 3) — every
2103    /// `Sexp::Display`-source `got` slot in the substrate, regardless of
2104    /// whether the rejection fires at typed-ENTRY (compile_from_sexp
2105    /// chain, template-gate, defmacro-syntax-gate, parse_params walker)
2106    /// or typed-EXIT (rewrite_typed's `Sexp::List`-contract gate), now
2107    /// shares ONE typed witness identity at the variant slot. Authoring
2108    /// tools (REPL, LSP, `tatara-check`) bind to BOTH `got.shape`
2109    /// (structurally pattern-matchable on `SexpShape::Int` etc.) AND
2110    /// `got.display` (the literal value, renderable verbatim) jointly
2111    /// for a typed-exit-side rejection too — no projection-to-`String`
2112    /// at the helper boundary loses the structural identity. Promotes
2113    /// the legacy `got: String` shape parallel to how the seven entry-
2114    /// side lifts promoted theirs.
2115    ///
2116    /// Display matches the legacy `Compile`-shaped diagnostic byte-for-
2117    /// byte — `"compile error in {keyword}: rewriter must return a list;
2118    /// got {got}"` — so existing consumer assertions (`tatara-check`'s
2119    /// diagnostic capture, REPL substring-greps that match on `"rewriter
2120    /// must return a list; got "`) pass unchanged across the lift. The
2121    /// `{got}` slot flows through `SexpWitness::Display`, which writes
2122    /// only the `display` field, so the rendering is byte-for-byte
2123    /// identical to the pre-lift `got: String` shape. When a future run
2124    /// gives `Sexp` source spans, `pos: Option<usize>` lands inside
2125    /// `SexpWitness` in ONE place and every rewriter-non-list site
2126    /// picks up positional rendering via
2127    /// `crate::diagnostic::format_diagnostic` mechanically.
2128    #[error("compile error in {keyword}: rewriter must return a list; got {got}")]
2129    RewriterNonList {
2130        keyword: &'static str,
2131        got: SexpWitness,
2132    },
2133    /// `serde_json::to_value` of a typed `T` value (any registered
2134    /// `TataraDomain`) errored. Two sites share this failure mode:
2135    /// `register::<T>`'s registry-dispatch closure (the registered
2136    /// handler serializes the just-typed value to JSON for the
2137    /// dispatcher) and `rewrite_typed::<T>`'s round-trip prelude (the
2138    /// self-optimization primitive serializes its input to JSON before
2139    /// projecting it to a `Sexp::List` for the rewriter closure). Both
2140    /// funnel through `serialize_to_json_err::<T>` so the type-level
2141    /// `T::KEYWORD` projection is mechanically threaded into the
2142    /// `keyword` slot, parallel to how `rewriter_non_list_err::<T>`
2143    /// threads `T::KEYWORD` into `RewriterNonList.keyword`.
2144    ///
2145    /// Mirror at the typed-exit boundary of the typed-entry-side
2146    /// `from_value` failure path: `extract_via_serde` /
2147    /// `extract_optional_via_serde` / `extract_vec_via_serde` route
2148    /// through `deserialize_err` / `deserialize_item_err`, which now
2149    /// produce the structural `LispError::KwargDeserialize { key, idx,
2150    /// message }` variant — the typed-entry-side sibling of this lift.
2151    /// After both lifts BOTH directions of the JSON-projection round-
2152    /// trip — `to_value` (typed-exit, keyword-keyed) AND `from_value`
2153    /// (typed-entry, key-keyed) — are structurally typed; there are
2154    /// zero `LispError::Compile { ... }` construction sites left in
2155    /// `tatara-lisp/src/domain.rs`.
2156    ///
2157    /// Sibling of `RewriterNonList { keyword, got }` for the
2158    /// `rewrite_typed::<T>` rejection chain — that variant fires when
2159    /// the rewriter's OUTPUT is not a list; this variant fires when
2160    /// the round-trip's INPUT (the typed value) fails to project to
2161    /// JSON at all. Together with `RewriterNonList`, every distinct
2162    /// `to_value`-side rejection mode in the self-optimization
2163    /// primitive and the registry-dispatch closure binds to ONE
2164    /// structural variant of `LispError`, not a `Compile`-shaped
2165    /// substring.
2166    ///
2167    /// `keyword` is `&'static str` because every call site projects
2168    /// `T::KEYWORD` via `serialize_to_json_err::<T>` — a compile-time
2169    /// literal sourced from the `#[tatara(keyword = "...")]` derive
2170    /// attribute (or hand-written const). Using a static slot makes
2171    /// that compile-time guarantee load-bearing in the type system —
2172    /// a typo can never drift into the diagnostic at runtime, the
2173    /// type system is the floor, same posture as
2174    /// `RewriterNonList.keyword`, `NamedFormMissingName.keyword`,
2175    /// `NamedFormNonSymbolName.keyword`, `NotAListForm.keyword`,
2176    /// `MissingHeadSymbol.keyword`, `HeadMismatch.keyword`, and the
2177    /// `Defmacro*.head` family. `message` is `String` because it
2178    /// carries the `serde_json::Error::Display` projection (errors
2179    /// render `expected … at line L column C` shapes — arbitrary text
2180    /// from the underlying `serde_json::Error`). Carrying the rendered
2181    /// message rather than a `#[source] serde_json::Error` keeps the
2182    /// variant's structural shape parallel to every other String-
2183    /// carrying variant in this enum — every consumer renders via
2184    /// Display, none consumes the underlying error chain.
2185    ///
2186    /// Display matches the legacy `Compile`-shaped diagnostic byte-
2187    /// for-byte — `"compile error in {keyword}: serialize: {message}"`
2188    /// — so existing consumer assertions (`tatara-check`'s diagnostic
2189    /// capture, REPL substring-greps that match on `"serialize: "`)
2190    /// pass unchanged across the lift. When a future run gives `Sexp`
2191    /// source spans, `pos: Option<usize>` lands here in ONE place and
2192    /// every domain-serialize site picks up positional rendering via
2193    /// `crate::diagnostic::format_diagnostic` mechanically.
2194    #[error("compile error in {keyword}: serialize: {message}")]
2195    DomainSerialize {
2196        keyword: &'static str,
2197        message: String,
2198    },
2199    /// `serde_json::from_value::<T>` of a kwarg's canonical-JSON projection
2200    /// errored. Two distinct sites share this failure mode through ONE
2201    /// structural variant whose data carries the typed closed-set
2202    /// `KwargPath` enum directly — `KwargPath::Named(key)` for the scalar
2203    /// / `Option<T>` path, `KwargPath::Item { key, idx }` for the per-item
2204    /// path inside a `Vec<T>` kwarg. The bifurcation lives inside the
2205    /// typed enum's variant identity, not in a sibling `idx: Option<usize>`
2206    /// slot:
2207    ///
2208    ///   1. `extract_via_serde` (required) and `extract_optional_via_serde`
2209    ///      (optional) — kwarg-keyed `from_value` failures at the scalar /
2210    ///      `Option<T>` path. `path: KwargPath::Named(key)`; the failure
2211    ///      binds to the kwarg slot identity ONLY (`:{key}`).
2212    ///   2. `extract_vec_via_serde` (per-item) — kwarg-AND-index-keyed
2213    ///      `from_value` failures inside a `Vec<T>` kwarg's items.
2214    ///      `path: KwargPath::Item { key, idx }`; the failure binds to the
2215    ///      kwarg slot AND the failing item index (`:{key}[{i}]`).
2216    ///
2217    /// Mirror at the typed-entry JSON boundary of the typed-exit-side
2218    /// `DomainSerialize { keyword, message }` lift: the latter rejects a
2219    /// `to_value::<T>` failure (typed-exit, keyword-keyed, sourced from
2220    /// `T::KEYWORD` and so `&'static str`); this variant rejects a
2221    /// `from_value::<T>` failure (typed-entry, kwargs-path-keyed, sourced
2222    /// from the runtime kwarg lookup and carried as a typed `KwargPath`).
2223    /// Together the two close the JSON-projection boundary of the
2224    /// typed-domain surface — every distinct `serde_json` failure mode at
2225    /// the typed-domain boundary binds to ONE structural variant of
2226    /// `LispError`, not a `Compile`-shaped substring.
2227    ///
2228    /// Sibling of `TypeMismatch.form: KwargPath`: both kwargs-path-keyed
2229    /// typed-entry rejection modes now carry the SAME typed kwargs-path
2230    /// identity inside their variant's data shape. The `(key, idx:
2231    /// Option<usize>)` bifurcation collapses into `KwargPath`'s variant
2232    /// identity — `Named` vs. `Item` — so the invalid combination
2233    /// `(key: "", idx: Some(0))` for a scalar path (or any combination that
2234    /// invented a fourth sub-mode) becomes structurally unrepresentable
2235    /// rather than re-asserted at the helper boundary via runtime
2236    /// `Option::is_some` comparison. Same closed-set posture as
2237    /// `LispError::TypeMismatch.form: KwargPath`,
2238    /// `LispError::Defmacro*.head: MacroDefHead`,
2239    /// `LispError::UnboundTemplateVar.prefix: UnquoteForm`,
2240    /// `LispError::CompilerSpecIo.stage: CompilerSpecIoStage`, and
2241    /// `LispError::TemplateInvariant.kind: TemplateInvariantKind`.
2242    ///
2243    /// `path` is `KwargPath` — the closed-set typed enum whose variants
2244    /// are EXACTLY the reachable kwargs-path shapes (`Named(String)` /
2245    /// `Item { key: String, idx: usize }` / `Slot(usize)`). The runtime
2246    /// `kwarg lookup` source-of-key is carried inside the typed enum's
2247    /// `String` payload; the per-item-index bifurcation is the enum's
2248    /// `Named` vs. `Item` variant identity, not a sibling Option slot.
2249    /// `message` is `String` because it carries the
2250    /// `serde_json::Error::Display` projection (errors render `expected …
2251    /// at line L column C` shapes — arbitrary text from the underlying
2252    /// `serde_json::Error`); carrying the rendered message rather than a
2253    /// `#[source] serde_json::Error` keeps the variant's structural shape
2254    /// parallel to every other String-carrying variant in this enum
2255    /// (`DomainSerialize.message`, `Compile.message`).
2256    ///
2257    /// `message` carries the raw `serde_json::Error::Display` projection
2258    /// — NO `"deserialize: "` prefix in the field, the prefix is in the
2259    /// `Display` rendering — so consumers that pattern-match on
2260    /// `message` get the underlying diagnostic unchanged, parallel to how
2261    /// `DomainSerialize.message` carries the raw `serde_json` projection
2262    /// (the `"serialize: "` prefix lives in Display, not in the slot).
2263    ///
2264    /// Display matches the legacy `Compile`-shaped diagnostic byte-for-
2265    /// byte across both sub-modes via `KwargPath`'s Display projection:
2266    /// `"compile error in :{key}: deserialize: {message}"` for
2267    /// `KwargPath::Named`, `"compile error in :{key}[{idx}]: deserialize:
2268    /// {message}"` for `KwargPath::Item` — so existing substring-grep
2269    /// consumers (`tatara-check`'s diagnostic capture, REPL substring-greps
2270    /// that match on `"deserialize: "`, `":steps[1]"`, `":level"`) pass
2271    /// unchanged across the lift. When a future run gives `Sexp` source
2272    /// spans, `pos: Option<usize>` lands here in ONE place and every
2273    /// kwarg-deserialize site picks up positional rendering via
2274    /// `crate::diagnostic::format_diagnostic` mechanically.
2275    #[error("compile error in {path}: deserialize: {message}")]
2276    KwargDeserialize { path: KwargPath, message: String },
2277    /// `compiler_spec.rs`'s disk-persistence surface emitted an
2278    /// I/O or serde failure. Four call sites in `compiler_spec.rs`
2279    /// share this failure mode through ONE structural variant keyed
2280    /// on the closed-set `CompilerSpecIoStage` enum (`realize_to_disk`
2281    /// × {serialize, write} ⊎ `load_from_disk` × {read, deserialize}).
2282    ///
2283    /// Encoding the (operation, stage) pair as ONE typed enum (rather
2284    /// than two `&'static str` slots `operation` × `stage`) makes the
2285    /// constraint that ONLY 4 of the 2×4 = 8 hypothetical pairs are
2286    /// reachable load-bearing in the type system — a typo like
2287    /// `(operation: "load_from_disk", stage: "write")` becomes
2288    /// structurally unrepresentable rather than re-asserted at the
2289    /// helper boundary via runtime string comparison. Same posture as
2290    /// `MacroDefHead` in `macro_expand.rs`: the closed set becomes a
2291    /// TYPE, and rustc's exhaustiveness check is the future invariant-
2292    /// keeper. Adding a new disk-persistence operation (e.g.,
2293    /// `load_from_str`) requires extending `CompilerSpecIoStage`,
2294    /// which rustc-enforces matching at every projection site
2295    /// (`operation()` / `label()`).
2296    ///
2297    /// Mirror at the disk boundary of the typed-domain JSON-projection
2298    /// round-trip's `DomainSerialize` / `KwargDeserialize` sibling pair
2299    /// at the in-memory kwarg boundary: those variants reject
2300    /// `to_value::<T>` / `from_value::<T>` failures at the typed-domain
2301    /// boundary; this variant rejects file I/O + top-level JSON
2302    /// failures at the disk boundary. After this lift, every distinct
2303    /// failure mode in `tatara-lisp/src/compiler_spec.rs`'s persistence
2304    /// surface is structurally typed; there are zero
2305    /// `LispError::Compile { ... }` construction sites left in
2306    /// `tatara-lisp/src/compiler_spec.rs`.
2307    ///
2308    /// `stage` is `CompilerSpecIoStage` — a closed-set typed enum
2309    /// whose `operation()` and `label()` projections feed the Display
2310    /// rendering — so the compile-time guarantee on BOTH slots is
2311    /// load-bearing in the type system. `message` is `String` because
2312    /// it carries the underlying error's `Display` projection
2313    /// (`serde_json::Error` for serialize / deserialize, `std::io::Error`
2314    /// for read / write — arbitrary text); carrying the rendered
2315    /// message rather than a `#[source]` chain keeps the variant's
2316    /// structural shape parallel to every other String-carrying variant
2317    /// in this enum (`DomainSerialize.message`, `KwargDeserialize.message`,
2318    /// `Compile.message`).
2319    ///
2320    /// `message` carries the raw underlying-error `Display` projection
2321    /// — NO `"{stage}: "` prefix in the field, the prefix is in the
2322    /// `Display` rendering — so consumers that pattern-match on
2323    /// `message` get the underlying diagnostic unchanged, parallel to
2324    /// how `DomainSerialize.message` carries the raw `serde_json`
2325    /// projection (the `"serialize: "` prefix lives in Display, not in
2326    /// the slot) and `KwargDeserialize.message` carries the raw
2327    /// `serde_json` projection (the `"deserialize: "` prefix lives in
2328    /// Display, not in the slot).
2329    ///
2330    /// Display matches the legacy `Compile`-shaped diagnostic byte-for-
2331    /// byte across all four stages — `"compile error in {operation}:
2332    /// {stage}: {message}"` where `{operation}` is `stage.operation()`
2333    /// and `{stage}` is `stage.label()` — so existing consumer
2334    /// assertions (`tatara-check`'s diagnostic capture, REPL substring-
2335    /// greps that match on `"realize_to_disk"`, `"load_from_disk"`,
2336    /// `"serialize: "`, `"write: "`, `"read: "`, `"deserialize: "`)
2337    /// pass unchanged across the lift. When a future run gives `Sexp`
2338    /// source spans, `pos: Option<usize>` lands here in ONE place
2339    /// (though the disk surface is non-positional — failures originate
2340    /// from file I/O / serde, not from a Sexp slot — so the field
2341    /// would stay `None` at every call site, the variant joining the
2342    /// `position_is_none_for_non_positional_variants` cohort).
2343    #[error("compile error in {}: {}: {message}", stage.operation(), stage.label())]
2344    CompilerSpecIo {
2345        stage: CompilerSpecIoStage,
2346        message: String,
2347    },
2348    /// `apply_compiled`'s bytecode-runtime invariant violation. Four call
2349    /// sites in `macro_expand.rs::apply_compiled` share this failure mode
2350    /// through ONE structural variant keyed on the closed-set
2351    /// `TemplateInvariantKind` enum. Every violation here is a
2352    /// COMPILER-INTERNAL bug — the bytecode that drives `apply_compiled`
2353    /// is produced by `compile_template` / `compile_node` in this same
2354    /// module, and a well-formed bytecode never references an
2355    /// out-of-bounds param index (Subst / Splice gates) nor leaves the
2356    /// runtime stack unbalanced at the final pop (EndList / no-value
2357    /// gates).
2358    ///
2359    /// Encoding the four failure modes as ONE typed enum (rather than a
2360    /// free-form `message: String` slot) makes the constraint that ONLY
2361    /// 4 distinct violations are reachable load-bearing in the type
2362    /// system — a regression that drifts the failure mode (e.g. a fifth
2363    /// "wrong opcode" gate added without a `TemplateInvariantKind`
2364    /// extension) becomes a `match` compile error at the projection site,
2365    /// not a substring-grep regression that ships. Same posture as
2366    /// `CompilerSpecIoStage` for `CompilerSpecIo`: the closed set becomes
2367    /// a TYPE, not a `matches!` literal in the helper. The index slot of
2368    /// the Subst / Splice gates lives INSIDE the variant
2369    /// (`SubstBadIndex(usize)` / `SpliceBadIndex(usize)`) rather than on
2370    /// the outer variant as `op_index: Option<usize>`, so the invalid
2371    /// combination `EndListEmptyStack { op_index: Some(_) }` is
2372    /// structurally unrepresentable — the type system encodes "this gate
2373    /// has an index, that gate does not."
2374    ///
2375    /// Display matches the legacy `Compile`-shaped diagnostic
2376    /// byte-for-byte across all four kinds — `"compile error in
2377    /// {macro_name}: {kind.message()}"` — so existing consumer assertions
2378    /// (`tatara-check`'s diagnostic capture, REPL substring-greps that
2379    /// match on `"compiled template referenced bad param index"`,
2380    /// `"compiled template referenced bad splice index"`, `"compiled
2381    /// template: EndList with empty stack"`, `"compiled template produced
2382    /// no value"`) pass unchanged across the lift.
2383    ///
2384    /// `macro_name` is `String` because it comes from arbitrary source
2385    /// (the call-site head symbol). `kind` is `TemplateInvariantKind` —
2386    /// a closed-set typed enum whose `message()` projection feeds the
2387    /// Display rendering.
2388    ///
2389    /// Theory anchor: THEORY.md §V.1 — knowable platform; the closed set
2390    /// of bytecode-invariant failure modes becomes a TYPE rather than a
2391    /// runtime string-comparison-and-format dance. THEORY.md §VI.1 —
2392    /// generation over composition; the typed enum lands the structural-
2393    /// completeness floor for the bytecode-runtime surface, parallel to
2394    /// how `CompilerSpecIoStage` lands the structural-completeness floor
2395    /// for the disk-persistence surface and `MacroDefHead` lands it for
2396    /// the macro-definition-head closed set. THEORY.md §II.1 invariant 5
2397    /// (composition preserves proofs): a well-formed bytecode invariant
2398    /// is the proof that drives the interpreter; the structural variant
2399    /// makes the proof's REJECTION shape first-class.
2400    #[error("compile error in {macro_name}: {}", kind.message())]
2401    TemplateInvariant {
2402        macro_name: String,
2403        kind: TemplateInvariantKind,
2404    },
2405    /// `Expander::expand` recursed past its configured ceiling
2406    /// (`Expander::max_expansion_depth`, default
2407    /// `crate::macro_expand::DEFAULT_MAX_EXPANSION_DEPTH = 256`). Fires
2408    /// on the runaway `(defmacro loop (x) `(loop ,x))` — a macro
2409    /// whose expansion contains another call to itself — which pre-
2410    /// lift stack-overflowed the process (a below-floor failure with
2411    /// no `Result` witness). The typed-entry gate turns "we abort" into
2412    /// "we reject at the expansion boundary" so authoring surfaces
2413    /// (REPL, LSP, `tatara-check`) see a `Result::Err` at the same
2414    /// call boundary as every other macro-expansion failure and can
2415    /// pattern-match on the variant identity to route diagnostics
2416    /// through the same channel as `TooManyMacroArgs` /
2417    /// `MissingMacroArg` / `UnboundTemplateVar`.
2418    ///
2419    /// `macro_name` is `String` because it comes from arbitrary
2420    /// source — the head symbol of the offending call at the moment
2421    /// the ceiling is hit; `limit` is `usize` because it is the
2422    /// configured ceiling the expander enforced at that call. The
2423    /// pair names BOTH the offending macro AND the ceiling it
2424    /// breached, so an LSP that wants to surface "your macro `loop`
2425    /// re-expanded past 256 levels; is it recursive without a base
2426    /// case?" has both halves at the variant boundary without
2427    /// substring-parsing the rendered diagnostic.
2428    ///
2429    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
2430    /// runaway-macro-expansion failure mode becomes a first-class
2431    /// typed rejection at the expander boundary instead of an
2432    /// unhandled stack overflow that aborts the process without
2433    /// producing a `Result` at all. THEORY.md §II.1 invariant 1 —
2434    /// typed entry; every macro-expansion failure mode the expander
2435    /// admits is a variant of `LispError`, so the below-floor abort
2436    /// is the last un-typed rejection in the expander's dispatch
2437    /// chain — closing it moves the expansion surface fully into the
2438    /// `Result` algebra.
2439    #[error(
2440        "compile error in call to {macro_name}: macro expansion depth exceeded (limit: {limit})"
2441    )]
2442    ExpansionDepthExceeded { macro_name: String, limit: usize },
2443    /// `Expander::expand`'s freshly-applied macro output crossed the
2444    /// configured node-count ceiling (`Expander::max_expansion_size`,
2445    /// default `crate::macro_expand::DEFAULT_MAX_EXPANSION_SIZE`).
2446    /// Peer to [`Self::ExpansionDepthExceeded`] one RESOURCE axis over:
2447    /// where the depth ceiling bounds RECURSION LENGTH (how many times
2448    /// a macro re-expands into itself before the process aborts) and
2449    /// [`crate::macro_expand::DEFAULT_MAX_CACHE_ENTRIES`] bounds
2450    /// MEMOIZATION WIDTH (how many distinct `(name, args)` pairs the
2451    /// cache retains), this variant bounds OUTPUT SIZE (how many
2452    /// [`crate::ast::Sexp`] nodes a single macro `apply` may produce
2453    /// before the expander rejects rather than descending into a
2454    /// runaway tree). Fires on the canonical "expansion bomb"
2455    /// `(defmacro bomb (x) `(list ,x ,x ,x ,x ,x ,x))` — a well-defined
2456    /// but exponentially-productive macro whose depth stays small
2457    /// while the produced tree grows past any reasonable working-set
2458    /// budget. Pre-lift the runaway ran until the process exhausted
2459    /// its heap; post-lift the expander returns this typed rejection
2460    /// at the first `apply` output whose `node_count` exceeds the
2461    /// ceiling, with `macro_name` populated from the offending call,
2462    /// `size` populated from the observed node count, and `limit`
2463    /// populated from the enforced ceiling — so authoring surfaces
2464    /// (REPL, LSP, `tatara-check`) see BOTH halves at the variant
2465    /// boundary rather than substring-parsing free-form text.
2466    ///
2467    /// The three-field shape names BOTH the offending macro AND the
2468    /// observed size AND the configured ceiling. An LSP that wants to
2469    /// surface "your macro `bomb` produced a 512-node result past the
2470    /// 256-node ceiling; is it exponentially productive?" reads all
2471    /// three from the typed variant without substring parsing. Joins
2472    /// the [`Self::ExpansionDepthExceeded`] cohort so the
2473    /// [`Self::position`] projection routes both resource-ceiling
2474    /// rejections through the SAME `None` arm — the offending macro's
2475    /// byte offset is not what a runaway diagnostic anchors to; the
2476    /// (macro name, observed resource, ceiling) triple is.
2477    ///
2478    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
2479    /// expansion-bomb failure mode becomes a first-class typed
2480    /// rejection at the expander boundary rather than a heap-exhausted
2481    /// abort that never reaches a `Result`. THEORY.md §II.1 invariant
2482    /// 1 — typed entry; every resource-ceiling failure mode the
2483    /// expander admits (recursion, memory, output size) is now a
2484    /// variant of `LispError`, closing the "runaway macro" surface at
2485    /// three RESOURCE-DIMENSION axes.
2486    #[error(
2487        "compile error in call to {macro_name}: macro expansion output size exceeded (size: {size}, limit: {limit})"
2488    )]
2489    ExpansionSizeExceeded {
2490        macro_name: String,
2491        size: usize,
2492        limit: usize,
2493    },
2494    /// `Expander::register_macro_def` rejected a macro definition whose
2495    /// body's [`crate::ast::Sexp::node_count`] exceeded the configured
2496    /// ceiling (`Expander::max_macro_body_size`, default
2497    /// `crate::macro_expand::DEFAULT_MAX_MACRO_BODY_SIZE`). Peer to
2498    /// [`Self::ExpansionSizeExceeded`] one PIPELINE-STAGE axis over:
2499    /// where `ExpansionSizeExceeded` bounds a macro `apply`'s OUTPUT
2500    /// size at EXPAND time (the runtime bomb — `(defmacro bomb (x) `(list ,x ,x ,x ,x))`
2501    /// producing a large tree from a small input), this variant bounds
2502    /// a macro's BODY size at REGISTER time (the authoring bomb —
2503    /// `(defmacro huge (x) `<template-of-N-million-nodes>)` whose
2504    /// storage in the `Expander::macros` table alone would exhaust
2505    /// memory before a single `apply` ran). Fires at
2506    /// [`crate::macro_expand::Expander::register_macro_def`] BEFORE
2507    /// `compile_template` walks the body or the entry lands in the
2508    /// macros table — a failed registration leaves both tables exactly
2509    /// as they were, matching the ordering `TemplateInvariant` /
2510    /// `UnboundTemplateVar` already enjoy on the compile-time reject
2511    /// path.
2512    ///
2513    /// The three-field shape names BOTH the offending macro AND the
2514    /// observed body size AND the configured ceiling — same shape as
2515    /// `ExpansionSizeExceeded` (its EXPAND-time peer) so authoring
2516    /// surfaces that route resource-ceiling rejections through one
2517    /// diagnostic channel bind to ONE structural pattern. `macro_name`
2518    /// is `String` because it comes from arbitrary source — the
2519    /// `(defmacro NAME …)` head at the moment the ceiling is hit;
2520    /// `size` is the observed `body.node_count()`; `limit` is the
2521    /// configured ceiling.
2522    ///
2523    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
2524    /// authoring-bomb failure mode (a macro body large enough to hurt
2525    /// storage or template-compile time) becomes a first-class typed
2526    /// rejection at the REGISTRATION boundary. THEORY.md §II.1
2527    /// invariant 1 — typed entry; every resource-ceiling failure mode
2528    /// the expander admits (expand-time recursion, expand-time
2529    /// memoization width, expand-time output size, register-time body
2530    /// size) is now a variant of `LispError`, closing the "runaway
2531    /// macro" surface at FOUR RESOURCE-DIMENSION axes across TWO
2532    /// pipeline stages (register + expand).
2533    #[error(
2534        "compile error in defmacro {macro_name}: macro body size exceeded (size: {size}, limit: {limit})"
2535    )]
2536    MacroBodySizeExceeded {
2537        macro_name: String,
2538        size: usize,
2539        limit: usize,
2540    },
2541    /// `Expander::register_macro_def` rejected a fresh (non-overwrite)
2542    /// registration because the [`Expander::macros`] table already held
2543    /// `Expander::max_registered_macros` entries (default
2544    /// `crate::macro_expand::DEFAULT_MAX_REGISTERED_MACROS`). Peer to
2545    /// [`Self::MacroBodySizeExceeded`] one RESOURCE-DIMENSION axis over
2546    /// on the REGISTER-time surface — where `MacroBodySizeExceeded`
2547    /// bounds a single macro's BODY SIZE (the per-registration
2548    /// authoring bomb — one huge template landing in
2549    /// `Expander::macros`), this variant bounds the TABLE ENTRY COUNT
2550    /// (the cumulative registration bomb — a code-generator emitting
2551    /// unbounded `(defmacro fresh-N (x) `(list ,x))` heads at REGISTER
2552    /// time whose bodies each sit comfortably under
2553    /// `Expander::max_macro_body_size` yet collectively saturate
2554    /// process memory). Also peer to
2555    /// [`crate::macro_expand::DEFAULT_MAX_CACHE_ENTRIES`] one
2556    /// PIPELINE-STAGE axis over: where the cache-entries ceiling
2557    /// bounds EXPAND-time memoization width, this ceiling bounds
2558    /// REGISTER-time macro-table width — the two entry-count guards
2559    /// close the two pipeline stages symmetrically.
2560    ///
2561    /// A re-registration of an already-registered key (`self.macros`
2562    /// contains `macro_name` before the call) is an OVERWRITE, which
2563    /// does not grow the table and therefore never fires this variant
2564    /// — operators can redefine a macro at capacity without hitting
2565    /// the ceiling, and only FRESH keys are gated. The gate fires
2566    /// BEFORE [`compile_template`] walks the body or the entry lands
2567    /// in the [`crate::macro_expand::Expander::macros`] /
2568    /// `templates` tables — a failed registration leaves both tables
2569    /// exactly as they were, matching the pristine-tables invariant
2570    /// [`Self::MacroBodySizeExceeded`] holds on the body-size axis.
2571    ///
2572    /// The three-field shape names BOTH the offending macro AND the
2573    /// observed table count AND the configured ceiling — same shape
2574    /// as `MacroBodySizeExceeded` / `ExpansionSizeExceeded` (its
2575    /// pipeline-stage peers) so authoring surfaces that route
2576    /// resource-ceiling rejections through one diagnostic channel
2577    /// bind to ONE structural pattern. `macro_name` is `String`
2578    /// because it comes from arbitrary source — the `(defmacro NAME
2579    /// …)` head at the moment the ceiling is hit; `count` is the
2580    /// observed `self.macros.len()` (which equals `limit` at
2581    /// rejection in the common case, or exceeds it if `limit` was
2582    /// lowered mid-run past the current table size); `limit` is the
2583    /// configured ceiling.
2584    ///
2585    /// Joins the [`Self::ExpansionDepthExceeded`] cohort in
2586    /// [`Self::position`] so all four resource-ceiling rejections
2587    /// route through the SAME `None` arm — the offending macro's
2588    /// byte offset is not what a registration-bomb diagnostic
2589    /// anchors to; the (macro name, observed table count, ceiling)
2590    /// triple is.
2591    ///
2592    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
2593    /// registration-bomb failure mode (unbounded macro-table growth)
2594    /// becomes a first-class typed rejection at the REGISTRATION
2595    /// boundary rather than an unhandled resource-drift that no
2596    /// `Result` witnesses. THEORY.md §II.1 invariant 1 — typed entry;
2597    /// every resource-ceiling failure mode the expander admits
2598    /// (expand-time recursion, expand-time memoization width,
2599    /// expand-time output size, register-time body size, register-
2600    /// time table width) is now a variant of `LispError`, closing the
2601    /// "runaway macro" surface at FIVE RESOURCE-DIMENSION axes across
2602    /// TWO pipeline stages — the register-time surface is now a
2603    /// two-dimensional (size × count) closure symmetric with the
2604    /// expand-time (depth + count + size) triple.
2605    #[error(
2606        "compile error in defmacro {macro_name}: registered macros count exceeded (count: {count}, limit: {limit})"
2607    )]
2608    RegisteredMacrosExceeded {
2609        macro_name: String,
2610        count: usize,
2611        limit: usize,
2612    },
2613    /// `Expander::register_macro_def` rejected a macro definition whose
2614    /// lambda-list arity — the [`crate::macro_expand::MacroParams::total_arity`]
2615    /// projection: `required.len() + optional.len() + rest.is_some() as
2616    /// usize` — exceeded the configured ceiling
2617    /// (`Expander::max_macro_arity`, default
2618    /// `crate::macro_expand::DEFAULT_MAX_MACRO_ARITY`). Peer to
2619    /// [`Self::MacroBodySizeExceeded`] one RESOURCE-DIMENSION axis over
2620    /// on the REGISTER-time surface — where `MacroBodySizeExceeded`
2621    /// bounds a per-registration BODY node count (the AST-nodes of the
2622    /// template a macro rewrites INTO), this variant bounds a
2623    /// per-registration PARAM slot count (the fresh symbols
2624    /// `compile_template`'s `,name`-index resolution AND
2625    /// `MacroParams::bind`'s per-index binder walk have to thread
2626    /// through on every call). Also peer to
2627    /// [`Self::RegisteredMacrosExceeded`] one RESOURCE-DIMENSION axis
2628    /// over on the REGISTER-time surface — where `RegisteredMacrosExceeded`
2629    /// bounds cumulative registration COUNT (the table-scoped
2630    /// resource), this variant bounds a per-registration PARAM-LIST
2631    /// WIDTH (the per-entry resource). Together the three
2632    /// REGISTER-time variants (this one +
2633    /// [`Self::MacroBodySizeExceeded`] + [`Self::RegisteredMacrosExceeded`])
2634    /// close the (per-body SIZE, per-body ARITY, per-table COUNT)
2635    /// three-corner surface. Fires at
2636    /// [`crate::macro_expand::Expander::register_macro_def`] BETWEEN the
2637    /// table-count gate (cheapest — O(1) `HashMap::len`) and the
2638    /// body-size gate (O(N) on `def.body.node_count()`) — the arity
2639    /// projection is a three-arm addition on `def.params` fields, no
2640    /// AST walk, so it sits BEFORE the body-size gate on the
2641    /// O(1)-first ordering of the REGISTER-time chain. A failed
2642    /// registration leaves BOTH `self.macros` AND `self.templates`
2643    /// tables exactly as they were, matching the pristine-tables
2644    /// invariant [`Self::MacroBodySizeExceeded`] and
2645    /// [`Self::RegisteredMacrosExceeded`] hold on their axes.
2646    ///
2647    /// Fires on the canonical arity-bomb `(defmacro huge (a-1 a-2 …
2648    /// a-N-million) `,a-1)` — a code-generator whose macro body sits
2649    /// at ONE node (`Sexp::Quasiquote` wrapping a single `,a-1`
2650    /// unquote is 3 nodes total, well under
2651    /// `Expander::max_macro_body_size`) yet whose param list carries
2652    /// millions of fresh names each subsequent call would have to
2653    /// walk in the per-index binder — a below-floor resource-drift
2654    /// the two prior REGISTER-time guards admit through because
2655    /// neither of them fires against the PER-REGISTRATION PARAM-LIST
2656    /// WIDTH.
2657    ///
2658    /// The three-field shape names BOTH the offending macro AND the
2659    /// observed arity AND the configured ceiling — same shape as
2660    /// `MacroBodySizeExceeded` / `ExpansionSizeExceeded` (its
2661    /// register-time peer and expand-time peer). `macro_name` is
2662    /// `String` because it comes from arbitrary source — the
2663    /// `(defmacro NAME …)` head at the moment the ceiling is hit;
2664    /// `arity` is the observed `def.params.total_arity()`; `limit` is
2665    /// the configured ceiling. An LSP that wants to surface "your
2666    /// macro `huge` declares 512 params past the 128-param ceiling;
2667    /// is it a `Vec<TypedEntity>`-unroller?" reads all three from the
2668    /// typed variant without substring parsing.
2669    ///
2670    /// Joins the [`Self::ExpansionDepthExceeded`] cohort in
2671    /// [`Self::position`] so all six resource-ceiling rejections
2672    /// route through the SAME `None` arm — the offending macro's byte
2673    /// offset is not what an arity-bomb diagnostic anchors to; the
2674    /// (macro name, observed arity, ceiling) triple is.
2675    ///
2676    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
2677    /// arity-bomb failure mode (an unbounded per-registration param
2678    /// list) becomes a first-class typed rejection at the REGISTRATION
2679    /// boundary rather than an unhandled per-call binder-walk cost
2680    /// that no `Result` witnesses. THEORY.md §II.1 invariant 1 —
2681    /// typed entry; every resource-ceiling failure mode the expander
2682    /// admits (expand-time recursion, expand-time memoization width,
2683    /// expand-time output size, register-time body size, register-
2684    /// time table width, register-time param-list width) is now a
2685    /// variant of `LispError`, closing the "runaway macro" surface at
2686    /// SIX RESOURCE-DIMENSION axes across TWO pipeline stages — the
2687    /// register-time surface is now a three-dimensional (size × arity
2688    /// × count) closure symmetric with the expand-time (depth + count
2689    /// + size) triple.
2690    ///
2691    /// Frontier inspiration: Common Lisp's `LAMBDA-PARAMETERS-LIMIT`
2692    /// standard variable (CLHS §3.4.1) — the runtime-reflectable
2693    /// "upper exclusive bound on the number of parameters that may
2694    /// appear in a lambda list" every conforming implementation
2695    /// carries; this variant is the substrate's typed-Rust peer at
2696    /// compile time, translated through pleme-io primitives: a typed
2697    /// `LispError` variant rather than an implementation-defined
2698    /// condition, wired into the substrate's `Result` algebra at
2699    /// [`crate::macro_expand::Expander::register_macro_def`] rather
2700    /// than raised at eval time.
2701    #[error(
2702        "compile error in defmacro {macro_name}: macro arity exceeded (arity: {arity}, limit: {limit})"
2703    )]
2704    MacroArityExceeded {
2705        macro_name: String,
2706        arity: usize,
2707        limit: usize,
2708    },
2709}
2710
2711/// Closed-set identifier for the (operation, stage) pair of a
2712/// `LispError::CompilerSpecIo` failure. Encodes the four reachable
2713/// pairs in `tatara-lisp/src/compiler_spec.rs`'s disk-persistence
2714/// surface — `realize_to_disk` × {serialize, write} ⊎ `load_from_disk`
2715/// × {read, deserialize} — as a typed enum, so invalid combinations
2716/// like `(load_from_disk, write)` or `(realize_to_disk, deserialize)`
2717/// are structurally unrepresentable rather than re-asserted at the
2718/// helper boundary via runtime string comparison.
2719///
2720/// Same posture as `MacroDefHead` in `macro_expand.rs`: the closed set
2721/// becomes a TYPE, not a `matches!` literal AND a triplicate
2722/// `match operation { ... }` projection inside each error helper. The
2723/// `operation()` / `label()` projections feed the
2724/// `LispError::CompilerSpecIo` Display rendering directly via the
2725/// `#[error(...)]` annotation; adding a new disk-persistence operation
2726/// (e.g., `load_from_str` for in-memory loads) requires extending this
2727/// enum, which rustc-enforces matching at every projection site.
2728///
2729/// Theory anchor: THEORY.md §V.1 — knowable platform; the closed set
2730/// of (operation, stage) pairs becomes a TYPE rather than a runtime
2731/// string-comparison-and-format dance. THEORY.md §VI.1 — generation
2732/// over composition; the typed enum lands the structural-completeness
2733/// floor for the disk-persistence surface, parallel to how
2734/// `MacroDefHead` lands the structural-completeness floor for the
2735/// macro-definition-head closed set.
2736///
2737/// `#[derive(tatara_closed_set::DeriveClosedSet)]` emits the
2738/// substrate-wide `impl tatara_closed_set::ClosedSet for CompilerSpecIoStage` +
2739/// the `pub struct UnknownCompilerSpecIoStage(pub String)`
2740/// parse-rejection carrier alongside the enum declaration. The
2741/// `#[closed_set(no_from_str)]` axis suppresses the auto-emitted
2742/// `FromStr` delegation — this enum's parse surface is the
2743/// compound `"{operation}: {label}"` key (a projection PAIR
2744/// rather than a single label), so the FromStr body below stays
2745/// hand-rolled. The `#[closed_set(generate_unknown)]` axis emits
2746/// the `UnknownCompilerSpecIoStage` carrier with the
2747/// auto-projected `"unknown compiler spec io stage: {0}"`
2748/// `#[error(...)]` annotation (matching the pre-lift wording
2749/// byte-for-byte via `pascal_to_spaced_lowercase`). `via` defaults
2750/// to `"label"` so the trait's `ClosedSet::label` projection
2751/// delegates to the inherent `label()` method — generic
2752/// consumers walking `ALL` and stringifying through the trait see
2753/// the singular labels (`"serialize"` / `"write"` / `"read"` /
2754/// `"deserialize"`) while the operator-facing `Display` rendering
2755/// stays at the compound `"{operation}: {label}"` shape via the
2756/// hand-rolled block.
2757#[derive(Debug, Clone, Copy, PartialEq, Eq, tatara_closed_set::DeriveClosedSet)]
2758#[closed_set(no_from_str, generate_unknown)]
2759pub enum CompilerSpecIoStage {
2760    /// `serde_json::to_string_pretty` of a `CompilerSpec` errored
2761    /// inside `realize_to_disk`.
2762    RealizeToDiskSerialize,
2763    /// `std::fs::write` of the serialized `CompilerSpec` JSON errored
2764    /// inside `realize_to_disk`.
2765    RealizeToDiskWrite,
2766    /// `std::fs::read_to_string` of the on-disk `CompilerSpec` JSON
2767    /// errored inside `load_from_disk`.
2768    LoadFromDiskRead,
2769    /// `serde_json::from_str` of the on-disk `CompilerSpec` JSON
2770    /// errored inside `load_from_disk`.
2771    LoadFromDiskDeserialize,
2772}
2773
2774impl CompilerSpecIoStage {
2775    /// The canonical `&'static str` bytes returned by
2776    /// [`Self::operation`] for the [`Self::RealizeToDiskSerialize`]
2777    /// and [`Self::RealizeToDiskWrite`] variants — the
2778    /// `"realize_to_disk"` `{operation}` slot of the legacy
2779    /// `Compile`-shaped triple emitted by the public
2780    /// `CompilerSpec::realize_to_disk` entry point.
2781    ///
2782    /// Sibling posture to [`Self::LOAD_FROM_DISK_OPERATION`]
2783    /// (`"load_from_disk"`) on the same disk-persistence
2784    /// operation-label algebra, and to
2785    /// [`Self::REALIZE_TO_DISK_SERIALIZE_LABEL`] /
2786    /// [`Self::REALIZE_TO_DISK_WRITE_LABEL`] /
2787    /// [`Self::LOAD_FROM_DISK_READ_LABEL`] /
2788    /// [`Self::LOAD_FROM_DISK_DESERIALIZE_LABEL`] on the paired
2789    /// stage-label algebra — together the two axes span the
2790    /// (operation × stage) compound-key surface every
2791    /// [`CompilerSpecIoStage`] variant projects through.
2792    ///
2793    /// Pre-lift the same `"realize_to_disk"` bytes lived inline at
2794    /// the [`Self::operation`] match arm plus at the paired
2795    /// `compiler_spec_io_stage_operation_projects_realize_for_serialize_and_write`
2796    /// truth-table test plus at every `operation="realize_to_disk"`
2797    /// metric-label pin. Post-lift the (`RealizeToDiskSerialize |
2798    /// RealizeToDiskWrite`, canonical `&'static str`) pairing binds
2799    /// at ONE `pub const` on the typed [`CompilerSpecIoStage`]
2800    /// algebra — the pre-lift ≥2 PRIME-DIRECTIVE trigger becomes ONE
2801    /// typed source of truth. Consumers that need the
2802    /// `"realize_to_disk"` bytes at compile time (`disallowed_names`
2803    /// clippy config; static-dispatch metric-label tables in
2804    /// Prometheus recorders; `tatara-check` grep budgets) bind
2805    /// against `CompilerSpecIoStage::REALIZE_TO_DISK_OPERATION`
2806    /// rather than re-typing the literal.
2807    pub const REALIZE_TO_DISK_OPERATION: &'static str = "realize_to_disk";
2808
2809    /// The canonical `&'static str` bytes returned by
2810    /// [`Self::operation`] for the [`Self::LoadFromDiskRead`] and
2811    /// [`Self::LoadFromDiskDeserialize`] variants — the
2812    /// `"load_from_disk"` `{operation}` slot of the legacy
2813    /// `Compile`-shaped triple emitted by the public
2814    /// `CompilerSpec::load_from_disk` entry point.
2815    ///
2816    /// Sibling posture to [`Self::REALIZE_TO_DISK_OPERATION`]
2817    /// (`"realize_to_disk"`) on the same disk-persistence
2818    /// operation-label algebra.
2819    pub const LOAD_FROM_DISK_OPERATION: &'static str = "load_from_disk";
2820
2821    /// The closed set of four canonical `&'static str` operation
2822    /// labels — one per variant of [`Self::ALL`] in the SAME
2823    /// declaration order, so `Self::OPERATIONS[i] ==
2824    /// Self::ALL[i].operation()` element-wise (pinned by
2825    /// `compiler_spec_io_stage_operations_align_with_all_by_index`).
2826    ///
2827    /// Unlike [`Self::LABELS`] — whose four elements are pairwise
2828    /// distinct because each `label()` projection is bijective with
2829    /// its variant — [`Self::OPERATIONS`] contains DUPLICATES by
2830    /// construction: the operation projection is 2-of-2-to-2 (both
2831    /// `Realize…` variants share `"realize_to_disk"`, both `Load…`
2832    /// variants share `"load_from_disk"`), pinned by
2833    /// `compiler_spec_io_stage_operations_partition_all_two_ways`.
2834    /// This asymmetry between the two projection axes IS the load-
2835    /// bearing structural property of the (operation × label)
2836    /// compound-key surface — a naive lift that omits it would
2837    /// silently collapse the two-way partition on the operation
2838    /// axis by pinning distinctness where it doesn't hold.
2839    ///
2840    /// Sibling posture to [`Self::LABELS`] (`[&'static str; 4]`) on
2841    /// the same disk-persistence stage-label algebra —
2842    /// [`Self::OPERATIONS`] and [`Self::LABELS`] together span the
2843    /// (operation, label) compound-key rendering surface every
2844    /// [`Display`] / [`FromStr`] round-trip walks, with rustc's
2845    /// forced-arity `[&'static str; 4]` check on BOTH arrays
2846    /// enforcing that any future fifth pair (`LoadFromStrDeserialize`
2847    /// once an in-memory `load_from_str` lands, `RealizeToDiskAtomicReplace`
2848    /// if the realize path grows a crash-safe-rename stage) extends
2849    /// [`Self::ALL`] ONCE + [`Self::operation`] ONCE +
2850    /// [`Self::OPERATIONS`] ONCE + [`Self::label`] ONCE +
2851    /// [`Self::LABELS`] ONCE + adds ONE per-role label `pub const`
2852    /// — the (variant → (operation, label)) product surface picks
2853    /// up the extension mechanically at every downstream consumer.
2854    ///
2855    /// Future consumers that compose against [`Self::OPERATIONS`]:
2856    /// an LSP / REPL completion provider surfacing every legal
2857    /// operation label in an `operation=` metrics query bar
2858    /// ([`Self::OPERATIONS`] deduped is the ONE typed sweep over
2859    /// every legal disk-persistence entry-point name); a
2860    /// `tatara-check` coverage assertion (every workspace `.lisp`
2861    /// file's `CompilerSpec` rejection must classify to some
2862    /// entry of [`Self::OPERATIONS`]); a Sekiban audit-trail metric
2863    /// jointly labeled by [`Self::operation`] × [`Self::label`]
2864    /// whose `operation=` label set IS the deduped
2865    /// [`Self::OPERATIONS`] (e.g.
2866    /// `tatara_lisp_compiler_spec_io_total{operation="realize_to_disk",
2867    /// stage="serialize"}`).
2868    pub const OPERATIONS: [&'static str; 4] = [
2869        Self::REALIZE_TO_DISK_OPERATION,
2870        Self::REALIZE_TO_DISK_OPERATION,
2871        Self::LOAD_FROM_DISK_OPERATION,
2872        Self::LOAD_FROM_DISK_OPERATION,
2873    ];
2874
2875    /// The public entry point's name — the `{form}` slot of the legacy
2876    /// `Compile`-shaped diagnostic. `realize_to_disk` for the
2877    /// serialize / write variants; `load_from_disk` for the read /
2878    /// deserialize variants.
2879    ///
2880    /// Body routes each arm through the per-role
2881    /// [`Self::REALIZE_TO_DISK_OPERATION`] /
2882    /// [`Self::LOAD_FROM_DISK_OPERATION`] `pub const` so the two
2883    /// canonical `&'static str` operation labels live at ONE
2884    /// `pub const` per role rather than at TWO sites (the per-role
2885    /// `pub const` AND an inline arm literal). Sibling posture to
2886    /// [`Self::label`]'s arms routing through the per-role
2887    /// [`Self::REALIZE_TO_DISK_SERIALIZE_LABEL`] /
2888    /// [`Self::REALIZE_TO_DISK_WRITE_LABEL`] /
2889    /// [`Self::LOAD_FROM_DISK_READ_LABEL`] /
2890    /// [`Self::LOAD_FROM_DISK_DESERIALIZE_LABEL`] constants on the
2891    /// paired stage-label algebra.
2892    #[must_use]
2893    pub fn operation(self) -> &'static str {
2894        match self {
2895            Self::RealizeToDiskSerialize | Self::RealizeToDiskWrite => {
2896                Self::REALIZE_TO_DISK_OPERATION
2897            }
2898            Self::LoadFromDiskRead | Self::LoadFromDiskDeserialize => {
2899                Self::LOAD_FROM_DISK_OPERATION
2900            }
2901        }
2902    }
2903
2904    /// The canonical `&'static str` bytes returned by
2905    /// [`Self::label`] for the [`Self::RealizeToDiskSerialize`]
2906    /// variant — the `"serialize"` `{stage}` slot of the legacy
2907    /// `"{stage}: {error}"` message shape emitted when
2908    /// `serde_json::to_string_pretty` errors inside `realize_to_disk`.
2909    /// Sibling posture to [`Self::REALIZE_TO_DISK_WRITE_LABEL`]
2910    /// (`"write"`), [`Self::LOAD_FROM_DISK_READ_LABEL`] (`"read"`),
2911    /// and [`Self::LOAD_FROM_DISK_DESERIALIZE_LABEL`]
2912    /// (`"deserialize"`) on the same disk-persistence stage-label
2913    /// algebra.
2914    ///
2915    /// Pre-lift the same `"serialize"` bytes lived inline at the
2916    /// [`Self::label`] match arm plus at the paired
2917    /// `compiler_spec_io_stage_label_projects_canonical_stage_strings`
2918    /// truth-table test plus at the `stage="serialize"` metric-label
2919    /// pin. Post-lift the (`RealizeToDiskSerialize` variant, canonical
2920    /// `&'static str`) pairing binds at ONE `pub const` on the typed
2921    /// [`CompilerSpecIoStage`] algebra — the pre-lift ≥2 PRIME-
2922    /// DIRECTIVE trigger becomes ONE typed source of truth. Consumers
2923    /// that need the `"serialize"` bytes at compile time
2924    /// (`disallowed_names` clippy config; static-dispatch metric-label
2925    /// tables in Prometheus recorders; `tatara-check` grep budgets)
2926    /// bind against `CompilerSpecIoStage::REALIZE_TO_DISK_SERIALIZE_LABEL`
2927    /// rather than re-typing the literal.
2928    pub const REALIZE_TO_DISK_SERIALIZE_LABEL: &'static str = "serialize";
2929
2930    /// The canonical `&'static str` bytes returned by
2931    /// [`Self::label`] for the [`Self::RealizeToDiskWrite`] variant —
2932    /// the `"write"` `{stage}` slot of the legacy `"{stage}: {error}"`
2933    /// message shape emitted when `std::fs::write` of the serialized
2934    /// `CompilerSpec` JSON errors inside `realize_to_disk`. Sibling
2935    /// posture to [`Self::REALIZE_TO_DISK_SERIALIZE_LABEL`]
2936    /// (`"serialize"`), [`Self::LOAD_FROM_DISK_READ_LABEL`]
2937    /// (`"read"`), and [`Self::LOAD_FROM_DISK_DESERIALIZE_LABEL`]
2938    /// (`"deserialize"`) on the same disk-persistence stage-label
2939    /// algebra.
2940    pub const REALIZE_TO_DISK_WRITE_LABEL: &'static str = "write";
2941
2942    /// The canonical `&'static str` bytes returned by
2943    /// [`Self::label`] for the [`Self::LoadFromDiskRead`] variant —
2944    /// the `"read"` `{stage}` slot of the legacy `"{stage}: {error}"`
2945    /// message shape emitted when `std::fs::read_to_string` of the
2946    /// on-disk `CompilerSpec` JSON errors inside `load_from_disk`.
2947    /// Sibling posture to [`Self::REALIZE_TO_DISK_SERIALIZE_LABEL`]
2948    /// (`"serialize"`), [`Self::REALIZE_TO_DISK_WRITE_LABEL`]
2949    /// (`"write"`), and [`Self::LOAD_FROM_DISK_DESERIALIZE_LABEL`]
2950    /// (`"deserialize"`) on the same disk-persistence stage-label
2951    /// algebra.
2952    pub const LOAD_FROM_DISK_READ_LABEL: &'static str = "read";
2953
2954    /// The canonical `&'static str` bytes returned by
2955    /// [`Self::label`] for the [`Self::LoadFromDiskDeserialize`]
2956    /// variant — the `"deserialize"` `{stage}` slot of the legacy
2957    /// `"{stage}: {error}"` message shape emitted when
2958    /// `serde_json::from_str` of the on-disk `CompilerSpec` JSON
2959    /// errors inside `load_from_disk`. Sibling posture to
2960    /// [`Self::REALIZE_TO_DISK_SERIALIZE_LABEL`] (`"serialize"`),
2961    /// [`Self::REALIZE_TO_DISK_WRITE_LABEL`] (`"write"`), and
2962    /// [`Self::LOAD_FROM_DISK_READ_LABEL`] (`"read"`) on the same
2963    /// disk-persistence stage-label algebra.
2964    pub const LOAD_FROM_DISK_DESERIALIZE_LABEL: &'static str = "deserialize";
2965
2966    /// The closed set of four canonical `&'static str` stage labels —
2967    /// the [`Self::REALIZE_TO_DISK_SERIALIZE_LABEL`] (`"serialize"`)
2968    /// `serde_json::to_string_pretty` failure followed by the
2969    /// [`Self::REALIZE_TO_DISK_WRITE_LABEL`] (`"write"`)
2970    /// `std::fs::write` failure followed by the
2971    /// [`Self::LOAD_FROM_DISK_READ_LABEL`] (`"read"`)
2972    /// `std::fs::read_to_string` failure followed by the
2973    /// [`Self::LOAD_FROM_DISK_DESERIALIZE_LABEL`] (`"deserialize"`)
2974    /// `serde_json::from_str` failure. Canonical declaration order
2975    /// matches [`Self::ALL`] so `Self::LABELS[i] == Self::ALL[i].label()`
2976    /// element-wise — pinned by
2977    /// `compiler_spec_io_stage_labels_align_with_all_by_index`.
2978    ///
2979    /// Sibling posture to [`KwargPathKind::LABELS`]
2980    /// (`[&'static str; 3]`), [`ExpectedKwargShape::LABELS`]
2981    /// (`[&'static str; 7]`), and [`MacroDefHead::KEYWORDS`]
2982    /// (`[&'static str; 3]`) — every closed-set algebra now pins its
2983    /// label-projection ALL array at the declaration site via a
2984    /// forced-arity `[&'static str; N]` array whose length fails
2985    /// compilation if a new variant lands without being added to the
2986    /// set. Adding a hypothetical fifth pair (a
2987    /// `LoadFromStrDeserialize` once an in-memory `load_from_str`
2988    /// lands, a `RealizeToDiskAtomicReplace` if the realize path grows
2989    /// a crash-safe-rename stage) extends [`Self::ALL`] ONCE +
2990    /// [`Self::label`] ONCE + [`Self::LABELS`] ONCE + adds ONE
2991    /// per-role label `pub const` — rustc's forced-arity check on the
2992    /// `[Self; N]` array + `[&'static str; N]` array pair enforces
2993    /// every downstream consumer picks up the extension mechanically.
2994    ///
2995    /// Future consumers that compose against [`Self::LABELS`]: an
2996    /// LSP / REPL completion provider surfacing every legal stage
2997    /// label in a `stage=` metrics query bar (`Self::LABELS.iter()` is
2998    /// the ONE typed sweep over every legal disk-persistence stage
2999    /// label), a `tatara-check` coverage assertion (every workspace
3000    /// `.lisp` file's `CompilerSpec` rejection must classify to some
3001    /// entry of `Self::LABELS`), a Sekiban audit-trail metric jointly
3002    /// labeled by [`Self::label`] whose metric-label set IS
3003    /// `Self::LABELS` (e.g.
3004    /// `tatara_lisp_compiler_spec_io_total{operation="realize_to_disk",
3005    /// stage="serialize"}`).
3006    pub const LABELS: [&'static str; 4] = [
3007        Self::REALIZE_TO_DISK_SERIALIZE_LABEL,
3008        Self::REALIZE_TO_DISK_WRITE_LABEL,
3009        Self::LOAD_FROM_DISK_READ_LABEL,
3010        Self::LOAD_FROM_DISK_DESERIALIZE_LABEL,
3011    ];
3012
3013    /// The step within the operation that failed — the `{stage}` slot
3014    /// of the legacy `"{stage}: {error}"` message shape. One of
3015    /// `"serialize"`, `"write"`, `"read"`, `"deserialize"`.
3016    ///
3017    /// Body routes each arm through the per-role
3018    /// [`Self::REALIZE_TO_DISK_SERIALIZE_LABEL`] /
3019    /// [`Self::REALIZE_TO_DISK_WRITE_LABEL`] /
3020    /// [`Self::LOAD_FROM_DISK_READ_LABEL`] /
3021    /// [`Self::LOAD_FROM_DISK_DESERIALIZE_LABEL`] `pub const` so the
3022    /// four canonical `&'static str` labels live at ONE `pub const`
3023    /// per variant rather than at TWO sites (the per-role `pub const`
3024    /// AND an inline arm literal). Sibling posture to
3025    /// [`KwargPathKind::label`]'s arms routing through the per-role
3026    /// [`KwargPathKind::NAMED_LABEL`] / [`KwargPathKind::ITEM_LABEL`]
3027    /// / [`KwargPathKind::SLOT_LABEL`] constants, and to
3028    /// [`ExpectedKwargShape::label`]'s arms routing through
3029    /// [`ExpectedKwargShape::KEYWORD_LABEL`] etc.
3030    #[must_use]
3031    pub fn label(self) -> &'static str {
3032        match self {
3033            Self::RealizeToDiskSerialize => Self::REALIZE_TO_DISK_SERIALIZE_LABEL,
3034            Self::RealizeToDiskWrite => Self::REALIZE_TO_DISK_WRITE_LABEL,
3035            Self::LoadFromDiskRead => Self::LOAD_FROM_DISK_READ_LABEL,
3036            Self::LoadFromDiskDeserialize => Self::LOAD_FROM_DISK_DESERIALIZE_LABEL,
3037        }
3038    }
3039
3040    /// Closed-set enumeration of every reachable [`CompilerSpecIoStage`]
3041    /// variant — the four (operation, label) pairs the disk-persistence
3042    /// surface emits (`realize_to_disk` × {`serialize`, `write`} ⊎
3043    /// `load_from_disk` × {`read`, `deserialize`}). The `[Self; 4]`
3044    /// array literal forces the arity so a fifth pair — a hypothetical
3045    /// `LoadFromStrDeserialize` once an in-memory `load_from_str` lands,
3046    /// or a `RealizeToDiskAtomicReplace` if the realize path grows a
3047    /// crash-safe-rename stage — cannot be added at the type without
3048    /// extending this constant.
3049    ///
3050    /// Sibling closed-set lift to every other typed-shape enum the
3051    /// substrate carries: this crate's own [`ExpectedKwargShape::ALL`]
3052    /// (the seven reachable expected-kwarg shapes — single-projection
3053    /// closed set), [`SexpShape::ALL`] (the twelve reachable observed-
3054    /// Sexp shapes — single-projection closed set), [`MacroDefHead::ALL`]
3055    /// (the three reachable macro-definition heads), [`UnquoteForm::ALL`]
3056    /// (the two reachable template-marker forms),
3057    /// [`crate::ast::AtomKind::ALL`], [`crate::ast::QuoteForm::ALL`], and
3058    /// across the workspace `ConditionKind::ALL`, `ProcessPhase::ALL`,
3059    /// `RequestorKind::ALL`, `ReceiptKind::ALL`, … . What's distinct here:
3060    /// this is the substrate's first *compound-key* closed set — the
3061    /// reachable identity is the PAIR `(operation, label)`, not either
3062    /// projection alone (`operation` partitions ALL into 2-of-2 halves,
3063    /// `label` is bijective with ALL by accident of the current four
3064    /// variants). [`FromStr`] keys on the compound rendering so the
3065    /// cross-product reachability constraint — only 4 of the 8
3066    /// conceivable `(operation, label)` pairs are reachable, e.g.
3067    /// `(load_from_disk, write)` is structurally absurd — becomes a
3068    /// load-bearing property of the parse boundary, not a runtime
3069    /// re-assertion at the helper.
3070    ///
3071    /// Future consumers that compose against [`Self::ALL`]: LSP / REPL
3072    /// completion for the operator-facing rendered (operation, label)
3073    /// pairs (every `compile error in X: Y: ...` substring in
3074    /// `LispError::CompilerSpecIo` keys on this set's projection through
3075    /// [`Self::operation`] and [`Self::label`]); `tatara-check` coverage
3076    /// assertions over which disk-persistence stages reach a
3077    /// [`LispError::CompilerSpecIo`] site at all — the typed sweep
3078    /// replaces a hand-rolled `match`-over-string vocabulary at consumer
3079    /// boundaries; any future audit-trail metric jointly labeled by
3080    /// [`Self::operation`] × [`Self::label`] (e.g.
3081    /// `tatara_lisp_compiler_spec_io_total{operation="realize_to_disk",
3082    /// stage="serialize"}`) — the metric label set IS [`Self::ALL`]
3083    /// mapped through the projection pair.
3084    pub const ALL: [Self; 4] = [
3085        Self::RealizeToDiskSerialize,
3086        Self::RealizeToDiskWrite,
3087        Self::LoadFromDiskRead,
3088        Self::LoadFromDiskDeserialize,
3089    ];
3090
3091    /// The canonical `&'static str` bytes that separate the
3092    /// [`Self::operation`] slot from the [`Self::label`] slot in the
3093    /// compound-key rendering `"{operation}{SEP}{label}"` — literally
3094    /// `": "` (a colon followed by ONE ASCII space, no more, no less).
3095    /// This is the sole load-bearing separator on the compound-key
3096    /// projection axis: [`Display`] composes through it, [`FromStr`]
3097    /// splits on it, and every doc-comment reference to the compound
3098    /// shape (`"realize_to_disk: read"`, `"load_from_disk: write"`, …)
3099    /// silently threads the same three bytes.
3100    ///
3101    /// Pre-lift the same `": "` bytes lived inline at three sites — the
3102    /// [`Display`] `write!` format string, the [`FromStr`] `split_once`
3103    /// argument, and two doc-comment references — with no typed source
3104    /// of truth binding them together. Post-lift the (`Self`, compound-
3105    /// key separator bytes) pairing binds at ONE `pub const` on the
3106    /// typed [`CompilerSpecIoStage`] algebra — the pre-lift ≥2 PRIME-
3107    /// DIRECTIVE trigger becomes ONE typed source of truth. A future
3108    /// refactor that changes the separator (e.g. to `" — "` on the way
3109    /// to a richer diagnostic surface) touches ONE `pub const` rather
3110    /// than THREE unrelated inline literals; a future refactor that
3111    /// silently drifts one call site (e.g. leaves `Display` at `": "`
3112    /// but drops the space from `split_once` after a `clippy::single_char_pattern`
3113    /// suggestion) fails the round-trip test loudly at the parse
3114    /// boundary rather than silently bifurcating the compound key.
3115    ///
3116    /// Sibling posture to [`Self::REALIZE_TO_DISK_OPERATION`] +
3117    /// [`Self::LOAD_FROM_DISK_OPERATION`] (operation-slot bytes) and
3118    /// [`Self::REALIZE_TO_DISK_SERIALIZE_LABEL`] etc. (label-slot bytes)
3119    /// on the same disk-persistence compound-key algebra — the three
3120    /// canonical slot-byte families together (operation × separator ×
3121    /// label) span the every-byte-of-the-compound-key surface; no
3122    /// character of a rendered `CompilerSpecIoStage` compound key lives
3123    /// at more than ONE typed constant on the algebra.
3124    ///
3125    /// Distinct from the substrate-wide `unknown {SET_LABEL}: {input}`
3126    /// separator in [`crate::closed_set`]'s well-formed-carrier
3127    /// rendering: those bytes happen to be identical (`": "`) but live
3128    /// at a distinct algebraic level (the ClosedSet trait's error-
3129    /// carrier composition, not the per-type compound-key surface).
3130    /// Two distinct separators that happen to be equal bytes; a future
3131    /// diagnostic-surface change that touches one must not touch the
3132    /// other by accident, so keeping them at separate `pub const` /
3133    /// hand-composed sites is load-bearing structure, not duplication.
3134    ///
3135    /// Consumers that need the compound-key separator bytes at compile
3136    /// time bind against `CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR`
3137    /// rather than re-typing the literal — this includes the
3138    /// hand-rolled [`Display`] / [`FromStr`] pair below and any future
3139    /// LSP / REPL diagnostic-substring extractor that needs to
3140    /// `rsplit_once(CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR)` a
3141    /// captured operator-facing `"compile error in {operation}: {label}:
3142    /// {message}"` prefix.
3143    pub const COMPOUND_KEY_SEPARATOR: &'static str = ": ";
3144}
3145
3146/// Standalone rendering of a [`CompilerSpecIoStage`] in the canonical
3147/// compound `"{operation}{SEP}{label}"` form — byte-for-byte the same
3148/// substring that lands inside the
3149/// [`LispError::CompilerSpecIo`] diagnostic between `"compile error in
3150/// "` and `": {message}"`, where `{SEP}` is
3151/// [`CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR`] (`": "`). Pinning
3152/// Display to the compound form means a consumer that extracts the
3153/// (operation, label) prefix from a rendered diagnostic — for example
3154/// by `s.strip_prefix("compile error in ")
3155/// .and_then(|t| t.rsplit_once(CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR))`
3156/// — round-trips the captured substring through [`FromStr`] back into
3157/// the typed variant exactly.
3158///
3159/// The compound form is load-bearing: `label` alone would be bijective
3160/// with the current four variants (`"serialize"` / `"write"` / `"read"`
3161/// / `"deserialize"`) but a future fifth variant like
3162/// `LoadFromStrDeserialize` would collide with `LoadFromDiskDeserialize`
3163/// on `label` alone. Display-ing the compound key means the bijection
3164/// survives the closed-set extension without a label-disambiguation
3165/// dance.
3166///
3167/// The body composes through [`CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR`]
3168/// so the exact separator bytes bind at ONE `pub const` on the typed
3169/// algebra rather than at TWO sites (the [`Display`] `write!` format
3170/// string AND the [`FromStr`] `split_once` argument) — a lift symmetric
3171/// to how [`Self::operation`] and [`Self::label`] route through their
3172/// per-role slot-byte constants.
3173impl std::fmt::Display for CompilerSpecIoStage {
3174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3175        f.write_str(self.operation())?;
3176        f.write_str(Self::COMPOUND_KEY_SEPARATOR)?;
3177        f.write_str(self.label())
3178    }
3179}
3180
3181/// Decode a canonical [`CompilerSpecIoStage`] compound key
3182/// `"{operation}: {label}"` back into the typed variant — `Ok(stage)`
3183/// when the input matches the [`Display`] rendering of one of the four
3184/// variants in [`CompilerSpecIoStage::ALL`] byte-for-byte (case-sensitive
3185/// because the labels are the rendered diagnostic surface and any case
3186/// drift would silently bifurcate the round-trip), and
3187/// [`Err(UnknownCompilerSpecIoStage)`] for every other string.
3188///
3189/// Crucially the decode REJECTS the four conceivable-but-unreachable
3190/// cross-product pairs — `"realize_to_disk: read"`,
3191/// `"realize_to_disk: deserialize"`, `"load_from_disk: serialize"`,
3192/// `"load_from_disk: write"` — because none of those pairs appears in
3193/// [`CompilerSpecIoStage::ALL`]. The cross-product reachability
3194/// constraint, previously a Code-level invariant (only the four call
3195/// sites in `compiler_spec.rs` construct stages, each construction
3196/// site pairs the correct operation with the correct stage), becomes
3197/// a type-level invariant: the parse boundary refuses to deserialize
3198/// an unreachable pair.
3199///
3200/// Partial keys — `"serialize"` alone, `"realize_to_disk"` alone — also
3201/// reject. The [`CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR`] (`": "`)
3202/// bytes must appear at least once for the decode to consider any
3203/// variant; otherwise the diagnostic substring shape isn't a compound
3204/// key at all.
3205///
3206/// Round-trip invariant pinned by
3207/// `compiler_spec_io_stage_compound_key_round_trips_through_from_str`:
3208/// for every variant `s` in [`CompilerSpecIoStage::ALL`],
3209/// `s.to_string().parse() == Ok(s)`. The compound-rendering site is
3210/// singular (the [`Display`] impl projects through [`Self::operation`]
3211/// and [`Self::label`]) so the round-trip is the only way the typed
3212/// surface and the rendered diagnostic literal can drift apart —
3213/// pinning it here means they cannot. Mirror of every sibling
3214/// closed-set round-trip in the workspace ([`SexpShape::from_str`],
3215/// [`ExpectedKwargShape::from_str`], [`MacroDefHead::from_str`],
3216/// [`UnquoteForm::from_str`], `RequestorKind::from_str`,
3217/// `ReceiptKind::from_str`, `ConditionKind::from_str`,
3218/// `ProcessPhase::from_str`, …) — the difference is the compound-key
3219/// shape rather than a single label, which sharpens the closed-set
3220/// constraint from "cardinality matches the variant count" to
3221/// "cardinality matches the variant count AND the projection product
3222/// is partial, not total."
3223impl std::str::FromStr for CompilerSpecIoStage {
3224    type Err = UnknownCompilerSpecIoStage;
3225    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
3226        let Some((op, lbl)) = s.split_once(Self::COMPOUND_KEY_SEPARATOR) else {
3227            return Err(UnknownCompilerSpecIoStage(s.to_owned()));
3228        };
3229        for stage in Self::ALL {
3230            if op == stage.operation() && lbl == stage.label() {
3231                return Ok(stage);
3232            }
3233        }
3234        Err(UnknownCompilerSpecIoStage(s.to_owned()))
3235    }
3236}
3237
3238// `pub struct UnknownCompilerSpecIoStage(pub String)` is generated by
3239// `#[derive(tatara_closed_set::DeriveClosedSet)]` on the `CompilerSpecIoStage`
3240// declaration above through `#[closed_set(generate_unknown)]`. The
3241// auto-projected `#[error("unknown compiler spec io stage: {0}")]`
3242// annotation (via `pascal_to_spaced_lowercase("CompilerSpecIoStage")` →
3243// `"compiler spec io stage"` — pinned by
3244// `pascal_to_spaced_lowercase_tests::contiguous_uppercase_runs_collapse_to_lowercase_without_inner_spaces`)
3245// matches the pre-lift hand-rolled wording byte-for-byte; LSP / REPL
3246// substring-matching `"unknown compiler spec io stage: "` continues to
3247// filter this rejection class without binding to the specific input.
3248// The substrate-wide carrier shape (`Debug + Clone + PartialEq + Eq +
3249// thiserror::Error` derives, `pub struct UnknownX(pub String)` with the
3250// `#[error("unknown <thing>: {0}")]` annotation) — symmetric to every
3251// sibling `Unknown*` error in the workspace
3252// (`UnknownExpectedKwargShape`, `UnknownSexpShape`,
3253// `UnknownMacroDefHead`, `UnknownUnquoteForm`,
3254// `crate::ast::UnknownAtomKind`, `crate::ast::UnknownQuoteForm`,
3255// `tatara_process::allocation::UnknownRequestorKind`,
3256// `tatara_process::receipt::UnknownReceiptKind`,
3257// `tatara_process::phase::UnknownPhase`,
3258// `tatara_process::boundary::UnknownConditionKind`,
3259// `tatara_process::lifetime::UnknownTeardownPolicy`, …) — emits from
3260// the derive rather than from a per-declaration hand-roll.
3261
3262/// Closed-set identifier for a bytecode-runtime invariant violation
3263/// surfaced by `macro_expand.rs::apply_compiled`. Encodes the four
3264/// reachable failure modes — Subst with an out-of-bounds param index,
3265/// Splice with an out-of-bounds param index, EndList against an empty
3266/// stack, and a final pop yielding no value — as a typed enum, so the
3267/// invalid combination of "stack-gate kind with an op-index payload"
3268/// (e.g. `EndListEmptyStack` carrying a `usize`) is structurally
3269/// unrepresentable: the index payload lives INSIDE the variants that
3270/// actually carry one (`SubstBadIndex(usize)` / `SpliceBadIndex(usize)`).
3271///
3272/// Same posture as `CompilerSpecIoStage`: the closed set becomes a
3273/// TYPE, not a free-form `message: String` slot inside the helper. The
3274/// `message()` projection feeds the `LispError::TemplateInvariant`
3275/// Display rendering directly via the `#[error(...)]` annotation;
3276/// adding a new bytecode-runtime invariant (e.g. a future `WrongOpcode`
3277/// gate that names a malformed bytecode header at the type level)
3278/// requires extending this enum, which rustc-enforces matching at the
3279/// projection site.
3280///
3281/// Theory anchor: THEORY.md §V.1 — knowable platform; the closed set
3282/// of bytecode-invariant failure modes becomes a TYPE rather than a
3283/// runtime string-format dance. THEORY.md §VI.1 — generation over
3284/// composition; the typed enum lands the structural-completeness floor
3285/// for the bytecode-runtime surface, parallel to how `CompilerSpecIoStage`
3286/// lands the structural-completeness floor for the disk-persistence
3287/// surface.
3288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3289pub enum TemplateInvariantKind {
3290    /// `TemplateOp::Subst(idx)` referenced a param index that
3291    /// `args_by_index.get(idx)` returned `None` for — the compiled
3292    /// bytecode referenced an out-of-bounds required-param slot.
3293    SubstBadIndex(usize),
3294    /// `TemplateOp::Splice(idx)` referenced a param index that
3295    /// `args_by_index.get(idx)` returned `None` for — the compiled
3296    /// bytecode referenced an out-of-bounds splice-target param slot.
3297    SpliceBadIndex(usize),
3298    /// `TemplateOp::EndList` ran against an empty runtime stack —
3299    /// `stack.pop()` returned `None`, meaning the compiled bytecode
3300    /// emitted an `EndList` without a matching `BeginList`. The stack
3301    /// is the proof artifact; an unbalanced stack is the bytecode
3302    /// compiler's proof obligation having been silently dropped.
3303    EndListEmptyStack,
3304    /// The final `stack.pop()` after the bytecode loop yielded `None`
3305    /// — the compiled bytecode produced no value at all (an empty op
3306    /// list, or a body that consumes its own output). Distinct from
3307    /// `EndListEmptyStack`: that fires mid-loop on an explicit
3308    /// `EndList`; this fires after the loop on the implicit final
3309    /// pop.
3310    FinalNoValue,
3311}
3312
3313impl TemplateInvariantKind {
3314    /// Canonical `&'static str` bytes for [`Self::EndListEmptyStack`] —
3315    /// the mid-loop stack-gate invariant fired inside the bytecode
3316    /// runtime when `TemplateOp::EndList` runs against an empty stack.
3317    /// Per-role peer of `Self::EndListEmptyStack` on the STATIC 2-of-4
3318    /// subset carving of the four-arm closed set.
3319    ///
3320    /// Pre-lift the same `"compiled template: EndList with empty stack"`
3321    /// bytes lived inline at [`Self::message`]'s match arm plus at the
3322    /// truth-table test `template_invariant_kind_message_for_endlist_empty_stack`
3323    /// — the ≥2 PRIME-DIRECTIVE trigger. Post-lift the
3324    /// (`EndListEmptyStack` variant, canonical `&'static str`) pairing
3325    /// binds at ONE `pub const` on the typed [`TemplateInvariantKind`]
3326    /// algebra: the [`Self::message`] arm routes through this constant
3327    /// via [`Self::message_static`], the peer [`Self::STATIC_MESSAGES`]
3328    /// array composes it into a forced-arity `[&'static str; 2]` at ONE
3329    /// site, and every downstream consumer that pins the exact bytes
3330    /// (an LSP quick-fix that surfaces the invariant-name inline, a
3331    /// Sekiban audit-trail metric labeled by the invariant identity)
3332    /// binds through here.
3333    ///
3334    /// Sibling posture to the closed set of per-role canonical
3335    /// `pub const` bytes on the substrate's other closed-set outer
3336    /// algebras: [`MacroDefHead::DEFMACRO_KEYWORD`] (`"defmacro"`),
3337    /// [`CompilerSpecIoStage::REALIZE_TO_DISK_OPERATION`] (`"realize
3338    /// to disk"`), [`UnquoteForm::UNQUOTE_LABEL`] (`","`),
3339    /// [`SexpShape::NIL_LABEL`] (`"nil"`). Each closed-set enum in the
3340    /// substrate now names its canonical per-variant bytes at ONE
3341    /// `pub const` per role rather than at TWO sites (the projection-
3342    /// method arm AND at least one truth-table test).
3343    pub const END_LIST_EMPTY_STACK_MESSAGE: &'static str =
3344        "compiled template: EndList with empty stack";
3345
3346    /// Canonical `&'static str` bytes for [`Self::FinalNoValue`] — the
3347    /// post-loop final-pop invariant fired after the bytecode runtime
3348    /// exhausts its op stream and the final `stack.pop()` yields no
3349    /// value. Per-role peer of `Self::FinalNoValue` on the STATIC
3350    /// 2-of-4 subset carving of the four-arm closed set.
3351    ///
3352    /// Pre-lift the same `"compiled template produced no value"` bytes
3353    /// lived inline at [`Self::message`]'s match arm plus at the truth-
3354    /// table test `template_invariant_kind_message_for_final_no_value`.
3355    /// Post-lift the (`FinalNoValue` variant, canonical `&'static str`)
3356    /// pairing binds at ONE `pub const` on the typed
3357    /// [`TemplateInvariantKind`] algebra: the [`Self::message`] arm
3358    /// routes through this constant via [`Self::message_static`], the
3359    /// peer [`Self::STATIC_MESSAGES`] array composes it into a forced-
3360    /// arity `[&'static str; 2]` at ONE site.
3361    ///
3362    /// Sibling posture to [`Self::END_LIST_EMPTY_STACK_MESSAGE`] on
3363    /// the same STATIC 2-of-4 subset carving — the dynamic arms
3364    /// (`SubstBadIndex(usize)` / `SpliceBadIndex(usize)`) format their
3365    /// `usize` payload into the message and cannot bind to a
3366    /// `&'static str` on this axis. The 2-of-4 STATIC subset is where
3367    /// the `Option<&'static str>` typed subset projection
3368    /// [`Self::message_static`] lives.
3369    pub const FINAL_NO_VALUE_MESSAGE: &'static str = "compiled template produced no value";
3370
3371    /// The closed set of two canonical `&'static str` bytes on the
3372    /// STATIC 2-of-4 subset carving of [`TemplateInvariantKind`]'s
3373    /// four-arm closed set — the [`Self::END_LIST_EMPTY_STACK_MESSAGE`]
3374    /// (`"compiled template: EndList with empty stack"`) mid-loop
3375    /// stack-gate invariant followed by the [`Self::FINAL_NO_VALUE_MESSAGE`]
3376    /// (`"compiled template produced no value"`) post-loop final-pop
3377    /// invariant. Canonical declaration order matches
3378    /// [`Self::message_static`]'s arm order so
3379    /// `Self::STATIC_MESSAGES[0] == Self::message_static(Self::EndListEmptyStack).unwrap()`
3380    /// and `Self::STATIC_MESSAGES[1] == Self::message_static(Self::FinalNoValue).unwrap()`
3381    /// element-wise — pinned by
3382    /// `template_invariant_kind_static_messages_align_with_message_static_by_index`.
3383    ///
3384    /// Sibling posture to every other closed-set STATIC-subset ALL
3385    /// array on the substrate: peer 2-of-4 carving to
3386    /// [`OptionalParamMalformedReason`]'s hypothetical STATIC subset
3387    /// (`EmptyList` / `MissingDefault` / `NonSymbolName` — a 3-of-4
3388    /// static carving on the sibling optional-section-malformed
3389    /// surface), 3-of-3 total to [`KwargPathKind::LABELS`],
3390    /// [`MacroDefHead::KEYWORDS`], and 7-of-7 to
3391    /// [`ExpectedKwargShape::LABELS`]. Adding a hypothetical fifth
3392    /// static invariant (e.g. `WrongOpcode` — a bytecode-header gate
3393    /// naming a malformed bytecode header at the type level with a
3394    /// `&'static str` message payload) extends [`Self::STATIC_MESSAGES`]
3395    /// ONCE + [`Self::message_static`]'s arms ONCE + adds ONE per-role
3396    /// message `pub const` — rustc's forced-arity check on the
3397    /// `[&'static str; N]` array fails compilation if the array grows
3398    /// without the projection method being extended.
3399    ///
3400    /// Future consumers that compose against [`Self::STATIC_MESSAGES`]:
3401    /// an LSP quick-fix that surfaces the static-message vocabulary
3402    /// inline at every `LispError::TemplateInvariant` diagnostic
3403    /// (`Self::STATIC_MESSAGES.iter()` is the ONE typed sweep over
3404    /// every legal static bytecode-invariant message); a
3405    /// `tatara-check` coverage assertion (every workspace `.lisp`
3406    /// file's macro-body must expand without producing a
3407    /// `TemplateInvariantKind` rejection whose message lies OUTSIDE
3408    /// [`Self::STATIC_MESSAGES`] ∪ formatted-dynamic-arm messages —
3409    /// the typed sweep replaces the per-call-site vocabulary of two
3410    /// `&'static str` literals); a Sekiban audit-trail metric jointly
3411    /// labeled by the STATIC-subset invariant message (e.g.
3412    /// `tatara_lisp_template_invariant_total{message="compiled
3413    /// template produced no value"}` — the metric label set on the
3414    /// STATIC subset IS [`Self::STATIC_MESSAGES`]).
3415    pub const STATIC_MESSAGES: [&'static str; 2] = [
3416        Self::END_LIST_EMPTY_STACK_MESSAGE,
3417        Self::FINAL_NO_VALUE_MESSAGE,
3418    ];
3419
3420    /// Canonical `&'static str` descriptor bytes for the
3421    /// [`Self::SubstBadIndex`] rejection — the noun-slot bytes `"param"`
3422    /// naming the required-param slot the compiled bytecode indexed
3423    /// out-of-bounds. Per-role peer of [`Self::SubstBadIndex`] on the
3424    /// DYNAMIC 2-of-4 subset carving of the four-arm closed set — the
3425    /// two arms whose diagnostic bytes format their `usize` payload
3426    /// into the shared message template rather than binding to a fixed
3427    /// `&'static str` on the STATIC axis.
3428    ///
3429    /// Pre-lift the same `"param"` bytes lived inline at
3430    /// [`Self::message`]'s `SubstBadIndex(idx)` match arm as the
3431    /// literal noun slot of `format!("compiled template referenced bad
3432    /// param index {idx}")` plus at the truth-table tests
3433    /// `template_invariant_kind_message_for_subst_bad_idx` and
3434    /// `template_invariant_display_renders_legacy_compile_shape_for_subst_bad_idx`
3435    /// — the ≥2 PRIME-DIRECTIVE trigger. Post-lift the (`SubstBadIndex`
3436    /// variant, canonical `&'static str` descriptor) pairing binds at
3437    /// ONE `pub const` on the typed [`TemplateInvariantKind`] algebra:
3438    /// [`Self::message_dynamic`] routes through this constant plus the
3439    /// [`Self::SPLICE_BAD_INDEX_DESCRIPTOR`] peer via
3440    /// [`Self::descriptor_dynamic`], the peer [`Self::DYNAMIC_DESCRIPTORS`]
3441    /// array composes it into a forced-arity `[&'static str; 2]` at ONE
3442    /// site, and the shared `format!("compiled template referenced bad
3443    /// {descriptor} index {idx}")` template binds at ONE site inside
3444    /// [`Self::message_dynamic`] rather than at TWO byte-for-byte
3445    /// duplicated `format!(...)` arms (the pre-lift `SubstBadIndex(idx)`
3446    /// arm's `"…bad param index {idx}"` AND the `SpliceBadIndex(idx)`
3447    /// arm's `"…bad splice index {idx}"`).
3448    ///
3449    /// Sibling posture to the closed-set per-role canonical `pub const`
3450    /// bytes on peer STATIC-subset carvings: this axis is the DYNAMIC
3451    /// 2-of-4 peer to [`Self::END_LIST_EMPTY_STACK_MESSAGE`] +
3452    /// [`Self::FINAL_NO_VALUE_MESSAGE`]'s STATIC 2-of-4 carving —
3453    /// together the four per-role `pub const`s partition the four-arm
3454    /// closed set into its STATIC ⊕ DYNAMIC halves, each with its own
3455    /// typed subset projection ([`Self::message_static`],
3456    /// [`Self::descriptor_dynamic`]). Cross-axis peer to
3457    /// [`OptionalParamMalformedReason::OPTIONAL_PARAM_SPEC_ARITY`] on
3458    /// the sibling algebra's `ExtraElements` dynamic arm — both name
3459    /// the load-bearing typed slot that the pre-lift `format!(...)`
3460    /// arm embedded as a bare literal.
3461    ///
3462    /// Future consumers keyed on the descriptor: LSP quick-fix for the
3463    /// out-of-bounds Subst rejection (surface `"the compiled template
3464    /// referenced a bad {descriptor} index"` where `{descriptor}`
3465    /// binds to this const); a Sekiban audit-trail metric jointly
3466    /// labeled by the DYNAMIC-subset descriptor
3467    /// (`tatara_lisp_template_invariant_total{descriptor="param"}` —
3468    /// the metric label set on the DYNAMIC subset IS
3469    /// [`Self::DYNAMIC_DESCRIPTORS`]).
3470    pub const SUBST_BAD_INDEX_DESCRIPTOR: &'static str = "param";
3471
3472    /// Canonical `&'static str` descriptor bytes for the
3473    /// [`Self::SpliceBadIndex`] rejection — the noun-slot bytes
3474    /// `"splice"` naming the splice-target param slot the compiled
3475    /// bytecode indexed out-of-bounds. Per-role peer of
3476    /// [`Self::SpliceBadIndex`] on the DYNAMIC 2-of-4 subset carving of
3477    /// the four-arm closed set.
3478    ///
3479    /// Sibling of [`Self::SUBST_BAD_INDEX_DESCRIPTOR`] on the same
3480    /// DYNAMIC 2-of-4 carving — see that constant's doc for the
3481    /// algebra-level pre-lift/post-lift contract every DYNAMIC-subset
3482    /// per-role descriptor shares. Post-lift the shared
3483    /// `format!("compiled template referenced bad {descriptor} index
3484    /// {idx}")` template inside [`Self::message_dynamic`] binds
3485    /// `{descriptor}` to whichever per-role const the DYNAMIC arm
3486    /// projects through [`Self::descriptor_dynamic`].
3487    pub const SPLICE_BAD_INDEX_DESCRIPTOR: &'static str = "splice";
3488
3489    /// The closed set of two canonical `&'static str` descriptor bytes
3490    /// on the DYNAMIC 2-of-4 subset carving of
3491    /// [`TemplateInvariantKind`]'s four-arm closed set — the
3492    /// [`Self::SUBST_BAD_INDEX_DESCRIPTOR`] (`"param"`) noun for the
3493    /// required-param slot followed by the
3494    /// [`Self::SPLICE_BAD_INDEX_DESCRIPTOR`] (`"splice"`) noun for the
3495    /// splice-target param slot. Canonical declaration order matches
3496    /// [`Self::descriptor_dynamic`]'s arm order so
3497    /// `Self::DYNAMIC_DESCRIPTORS[0] == Self::descriptor_dynamic(Self::SubstBadIndex(_)).unwrap()`
3498    /// and `Self::DYNAMIC_DESCRIPTORS[1] == Self::descriptor_dynamic(Self::SpliceBadIndex(_)).unwrap()`
3499    /// element-wise — pinned by
3500    /// `template_invariant_kind_dynamic_descriptors_align_with_descriptor_dynamic_by_index`.
3501    ///
3502    /// Peer 2-of-4 carving to [`Self::STATIC_MESSAGES`] — together the
3503    /// STATIC + DYNAMIC 2-of-4 arrays partition the four-arm closed
3504    /// set into its `&'static str`-projectable halves: the STATIC array
3505    /// carries the two arms whose FULL message is `&'static str`, the
3506    /// DYNAMIC array carries the two arms whose NOUN slot is
3507    /// `&'static str` and whose full message is a
3508    /// `format!("compiled template referenced bad {descriptor} index
3509    /// {idx}")` composition. Adding a hypothetical fifth static
3510    /// invariant lands only on the STATIC array; adding a hypothetical
3511    /// fifth dynamic invariant (e.g. a `KwargBadIndex(usize)` gate
3512    /// naming a keyword-param slot the compiled bytecode indexed
3513    /// out-of-bounds) lands only on this array — rustc's forced-arity
3514    /// check on the `[&'static str; N]` shape fails compilation if the
3515    /// array grows without the corresponding per-role descriptor +
3516    /// [`Self::descriptor_dynamic`] arm being extended in lockstep.
3517    ///
3518    /// Sibling posture to [`OptionalParamMalformedReason::STATIC_LABELS`]
3519    /// (peer algebra's STATIC 3-of-4 carving) — every closed-set outer
3520    /// algebra the substrate carries now pins BOTH its STATIC-subset
3521    /// canonical-bytes array AND its DYNAMIC-subset per-role-slot array
3522    /// at ONE `pub const` per axis rather than at N inline literals
3523    /// scattered across projections and tests. The DYNAMIC axis is
3524    /// what this array lands for `TemplateInvariantKind`.
3525    ///
3526    /// Future consumers that compose against
3527    /// [`Self::DYNAMIC_DESCRIPTORS`]: a `tatara-check` coverage
3528    /// assertion over the workspace corpus (every rendered
3529    /// `TemplateInvariant` diagnostic with a DYNAMIC-arm shape must
3530    /// substring-match one of the two descriptor bytes —
3531    /// `Self::DYNAMIC_DESCRIPTORS.iter()` is the ONE typed sweep over
3532    /// every legal dynamic-arm noun); a Sekiban audit-trail metric
3533    /// keyed on the descriptor slot; an LSP quick-fix that pluralizes
3534    /// the descriptor into a suggestion.
3535    #[allow(dead_code)]
3536    pub const DYNAMIC_DESCRIPTORS: [&'static str; 2] = [
3537        Self::SUBST_BAD_INDEX_DESCRIPTOR,
3538        Self::SPLICE_BAD_INDEX_DESCRIPTOR,
3539    ];
3540
3541    /// Typed `Option<&'static str>` zero-allocation projection over the
3542    /// STATIC 2-of-4 subset carving of [`TemplateInvariantKind`]'s
3543    /// four-arm closed set. Returns `Some(bytes)` for the two arms
3544    /// whose diagnostic bytes bind at a per-role `pub const &'static
3545    /// str` ([`Self::EndListEmptyStack`] →
3546    /// [`Self::END_LIST_EMPTY_STACK_MESSAGE`], [`Self::FinalNoValue`] →
3547    /// [`Self::FINAL_NO_VALUE_MESSAGE`]) and `None` for the two arms
3548    /// whose bytes format their `usize` payload dynamically
3549    /// ([`Self::SubstBadIndex`] / [`Self::SpliceBadIndex`]) and
3550    /// cannot bind to a `&'static str` on this axis.
3551    ///
3552    /// The `const fn` posture matches [`MacroDefHead::keyword`],
3553    /// [`UnquoteForm::label`], [`SexpShape::label`], and every peer
3554    /// closed-set projection method on the substrate — the projection
3555    /// evaluates at rustc const-eval time when the variant is a
3556    /// const, so a future const-context consumer (an
3557    /// `#[allow(dead_code)] const _: Option<&'static str> = ...`
3558    /// static assertion, a `tatara-check` predicate binding at
3559    /// module-init time) picks up the STATIC-subset selector without
3560    /// runtime dispatch.
3561    ///
3562    /// Sibling posture to a hypothetical
3563    /// `OptionalParamMalformedReason::label_static` on the peer
3564    /// 3-of-4 static carving of the sibling optional-section-malformed
3565    /// four-arm closed set: both project the STATIC subset of a
3566    /// mixed-payload closed set (payload-carrying dynamic arms
3567    /// project to `None`) into a typed `Option<&'static str>` — the
3568    /// same idiom applied at two peer boundaries of the substrate's
3569    /// bytecode-runtime and macro-authoring surfaces.
3570    ///
3571    /// Post-lift the substrate's STATIC-subset selector over the
3572    /// four-arm closed set binds at ONE typed function on the algebra
3573    /// rather than at zero-primitive-plus-runtime-dispatch through
3574    /// [`Self::message`]'s match body (which pre-lift owned the
3575    /// selector by matching two `&'static str` literals inline). A
3576    /// hypothetical fifth arm on the STATIC subset (e.g.
3577    /// `WrongOpcode` — a bytecode-header gate) extends
3578    /// [`Self::STATIC_MESSAGES`] AND [`Self::message_static`]'s arms
3579    /// AND adds ONE per-role message `pub const` in lockstep —
3580    /// rustc's exhaustive-match check on the four-arm closed set
3581    /// fails compilation without the new arm's inclusion.
3582    #[must_use]
3583    pub const fn message_static(self) -> Option<&'static str> {
3584        match self {
3585            Self::EndListEmptyStack => Some(Self::END_LIST_EMPTY_STACK_MESSAGE),
3586            Self::FinalNoValue => Some(Self::FINAL_NO_VALUE_MESSAGE),
3587            Self::SubstBadIndex(_) | Self::SpliceBadIndex(_) => None,
3588        }
3589    }
3590
3591    /// Typed `Option<&'static str>` zero-allocation projection over the
3592    /// DYNAMIC 2-of-4 subset carving of [`TemplateInvariantKind`]'s
3593    /// four-arm closed set — returns `Some(descriptor)` for the two
3594    /// arms whose diagnostic bytes carry a `usize` payload and format
3595    /// through the shared `"compiled template referenced bad
3596    /// {descriptor} index {idx}"` template ([`Self::SubstBadIndex`] →
3597    /// [`Self::SUBST_BAD_INDEX_DESCRIPTOR`] (`"param"`),
3598    /// [`Self::SpliceBadIndex`] → [`Self::SPLICE_BAD_INDEX_DESCRIPTOR`]
3599    /// (`"splice"`)), and `None` for the two arms whose full diagnostic
3600    /// bytes bind at a `&'static str` on the STATIC axis
3601    /// ([`Self::EndListEmptyStack`], [`Self::FinalNoValue`]) and carry
3602    /// no per-role descriptor slot.
3603    ///
3604    /// Peer projection to [`Self::message_static`] on the sibling
3605    /// STATIC 2-of-4 subset carving — the two projections together
3606    /// partition the four-arm closed set into its STATIC ⊕ DYNAMIC
3607    /// halves at zero allocation cost. Callers holding a variant of
3608    /// unknown identity thread it through BOTH projections; exactly ONE
3609    /// of `message_static(self)` and `descriptor_dynamic(self)` returns
3610    /// `Some` for every legal variant (`message_static` on the
3611    /// two-arm STATIC subset, `descriptor_dynamic` on the two-arm
3612    /// DYNAMIC subset). The partition is pinned by the truth-table
3613    /// test `template_invariant_kind_static_dynamic_projections_partition_closed_set`.
3614    ///
3615    /// The `const fn` posture matches [`Self::message_static`],
3616    /// [`OptionalParamMalformedReason::label_static`], and every peer
3617    /// closed-set projection method on the substrate — evaluates at
3618    /// rustc const-eval time when the variant is a const.
3619    ///
3620    /// Sibling posture to a hypothetical
3621    /// `OptionalParamMalformedReason::descriptor_dynamic` projecting
3622    /// `Self::ExtraElements` into its "elements" descriptor bytes on
3623    /// the peer algebra's 1-of-4 dynamic carving — the same idiom
3624    /// applied at two peer boundaries of the substrate's bytecode-
3625    /// runtime and macro-authoring surfaces.
3626    ///
3627    /// Future consumers that compose against this projection: an LSP
3628    /// quick-fix keyed on the DYNAMIC-subset descriptor slot (surfaces
3629    /// `"the compiled template referenced a bad {descriptor} index"`
3630    /// with `{descriptor}` bound to whatever this projection returns);
3631    /// a `tatara-check` predicate that filters a corpus's
3632    /// `TemplateInvariant` rejections into static-arm vs dynamic-arm
3633    /// buckets in ONE typed subset selector rather than three inline
3634    /// `matches!(kind, Self::SubstBadIndex(_) | Self::SpliceBadIndex(_))`
3635    /// gates threaded through per-consumer allocation.
3636    #[must_use]
3637    pub const fn descriptor_dynamic(self) -> Option<&'static str> {
3638        match self {
3639            Self::SubstBadIndex(_) => Some(Self::SUBST_BAD_INDEX_DESCRIPTOR),
3640            Self::SpliceBadIndex(_) => Some(Self::SPLICE_BAD_INDEX_DESCRIPTOR),
3641            Self::EndListEmptyStack | Self::FinalNoValue => None,
3642        }
3643    }
3644
3645    /// Typed `Option<String>` projection over the DYNAMIC 2-of-4 subset
3646    /// carving of [`TemplateInvariantKind`]'s four-arm closed set —
3647    /// returns `Some(message)` for the two arms whose diagnostic bytes
3648    /// format their `usize` payload through the SHARED
3649    /// `"compiled template referenced bad {descriptor} index {idx}"`
3650    /// template, and `None` for the two STATIC arms whose full bytes
3651    /// bind on the [`Self::message_static`] axis.
3652    ///
3653    /// Composes [`Self::descriptor_dynamic`] into the shared format
3654    /// template — the `{descriptor}` slot binds to whichever per-role
3655    /// [`Self::SUBST_BAD_INDEX_DESCRIPTOR`] /
3656    /// [`Self::SPLICE_BAD_INDEX_DESCRIPTOR`] constant the DYNAMIC arm
3657    /// projects through, and the `{idx}` slot binds the `usize`
3658    /// payload the enum's dynamic variant carries. Pre-lift
3659    /// [`Self::message`]'s body carried TWO byte-for-byte duplicated
3660    /// `format!(...)` arms whose only variance was the noun slot
3661    /// (`"param"` vs `"splice"`) — post-lift the shared template binds
3662    /// at ONE `format!(...)` site inside this method, the noun slot
3663    /// binds at the per-role descriptor const, and
3664    /// [`Self::descriptor_dynamic`] carries the arm→descriptor
3665    /// mapping.
3666    ///
3667    /// Peer projection to [`Self::message_static`] on the sibling
3668    /// STATIC 2-of-4 subset carving — the two projections together
3669    /// partition the four-arm closed set into its STATIC ⊕ DYNAMIC
3670    /// message-projection halves. [`Self::message`]'s body reduces to
3671    /// `message_static().map(str::to_string).or_else(||
3672    /// message_dynamic()).expect(...)` — ONE compositional flow over
3673    /// TWO typed subset projections rather than FOUR duplicated match
3674    /// arms (two `.into()` sites + two `format!(...)` sites in the
3675    /// pre-lift body).
3676    ///
3677    /// Sibling posture to a hypothetical
3678    /// `OptionalParamMalformedReason::label_dynamic` composing
3679    /// `Self::ExtraElements`'s `{length}` payload through
3680    /// [`OptionalParamMalformedReason::OPTIONAL_PARAM_SPEC_ARITY`] on
3681    /// the peer algebra's 1-of-4 dynamic carving.
3682    ///
3683    /// Future consumers that compose against this projection: a
3684    /// `tatara-check` predicate that filters a corpus's
3685    /// `TemplateInvariant` rejections into static-arm vs dynamic-arm
3686    /// message buckets; a Sekiban audit-trail metric that samples
3687    /// dynamic-arm messages without paying the allocation cost on the
3688    /// STATIC subset (the `None` branch short-circuits).
3689    #[must_use]
3690    pub fn message_dynamic(self) -> Option<String> {
3691        let descriptor = self.descriptor_dynamic()?;
3692        let idx = match self {
3693            Self::SubstBadIndex(idx) | Self::SpliceBadIndex(idx) => idx,
3694            Self::EndListEmptyStack | Self::FinalNoValue => {
3695                // Unreachable: descriptor_dynamic() returned Some only
3696                // for SubstBadIndex / SpliceBadIndex on the `?` above.
3697                // Kept exhaustive rather than `_ =>` so a future refactor
3698                // that adds a fifth dynamic arm without extending
3699                // descriptor_dynamic fails compilation at THIS callsite
3700                // rather than at runtime.
3701                unreachable!(
3702                    "descriptor_dynamic returned Some only for dynamic-arm variants; \
3703                     the STATIC-arm match here is unreachable via the ? short-circuit"
3704                )
3705            }
3706        };
3707        Some(format!(
3708            "compiled template referenced bad {descriptor} index {idx}"
3709        ))
3710    }
3711
3712    /// The `{message}` slot of the legacy `LispError::Compile { form:
3713    /// macro_name, message: <invariant> }` shape. Each variant projects
3714    /// to the canonical message string the pre-lift inline triples
3715    /// emitted — byte-for-byte equivalent so authoring-tool substring
3716    /// greps (`tatara-check`, REPL) see no drift across the lift.
3717    ///
3718    /// Body composes through BOTH typed subset projections — the
3719    /// STATIC 2-of-4 [`Self::message_static`] and the DYNAMIC 2-of-4
3720    /// [`Self::message_dynamic`]. The partition contract (exactly ONE
3721    /// projection returns `Some` for every legal variant) collapses
3722    /// the pre-lift's FOUR duplicated match arms (two `.into()` sites
3723    /// on the STATIC subset AND two byte-for-byte `format!(...)` sites
3724    /// on the DYNAMIC subset) into ONE `.or_else(...)` composition:
3725    /// `message_static().map(str::to_string).or_else(||
3726    /// message_dynamic()).expect(...)`. The `.expect(...)` is
3727    /// structurally unreachable — the partition test
3728    /// `template_invariant_kind_static_dynamic_projections_partition_closed_set`
3729    /// pins that at least ONE of the two projections returns `Some` for
3730    /// every variant. Sibling posture to
3731    /// `OptionalParamMalformedReason::label` composing through
3732    /// `label_static` on the peer 3-of-4 static carving.
3733    #[must_use]
3734    pub fn message(self) -> String {
3735        self.message_static()
3736            .map(str::to_string)
3737            .or_else(|| self.message_dynamic())
3738            .expect(
3739                "message_static/message_dynamic partition covers the four-arm closed set — \
3740                 exactly one projection returns Some for every legal variant, so the None \
3741                 branch here is unreachable",
3742            )
3743    }
3744}
3745
3746/// Closed-set identifier for the head keyword of a `defmacro`-shape
3747/// rejection — the three canonical macro-definition heads
3748/// `defmacro` / `defpoint-template` / `defcheck`. Carried as a typed
3749/// slot on `LispError::DefmacroArity`, `LispError::DefmacroNonSymbolName`,
3750/// and `LispError::DefmacroNonListParams` so authoring tools (REPL, LSP,
3751/// `tatara-check`) bind to variant identity rather than substring-matching
3752/// the rendered `head` string.
3753///
3754/// Mirror at the macro-definition-head boundary of the prior-run
3755/// `CompilerSpecIoStage` (disk-persistence surface) and
3756/// `TemplateInvariantKind` (bytecode-runtime surface) closed-set lifts:
3757/// those variants key on a typed enum for the (operation, stage) pair
3758/// and the invariant kind respectively; this enum keys the three
3759/// `Defmacro*` variants on a typed head identity. Adding a new
3760/// macro-definition head requires extending this enum, which rustc-
3761/// enforces matching at every projection site (`keyword()`) — the
3762/// closed set becomes a TYPE rather than a `matches!` literal at the
3763/// `macro_def_from` gate plus three `match head` projections inside
3764/// each variant's helper.
3765///
3766/// `from_keyword(&str) -> Option<Self>` projects an arbitrary source
3767/// symbol into the typed enum; `keyword(self) -> &'static str` projects
3768/// back to the canonical literal for `LispError::Display` rendering.
3769/// The bidirection is the identity on the closed set —
3770/// `from_keyword(k).unwrap().keyword() == k` for every canonical `k`.
3771///
3772/// Theory anchor: THEORY.md §V.1 — knowable platform; the closed set of
3773/// macro-definition heads becomes a TYPE rather than a runtime
3774/// string-comparison-and-format dance. THEORY.md §VI.1 — generation
3775/// over composition; the typed enum lands the structural-completeness
3776/// floor for the macro-definition-head surface, parallel to how
3777/// `CompilerSpecIoStage` lands it for the disk-persistence surface and
3778/// `TemplateInvariantKind` for the bytecode-runtime surface.
3779#[derive(Debug, Clone, Copy, PartialEq, Eq, tatara_closed_set::DeriveClosedSet)]
3780#[closed_set(via = "keyword", display, generate_unknown = "macro definition head")]
3781pub enum MacroDefHead {
3782    /// `(defmacro NAME (PARAMS) BODY)` — the canonical Lisp-style macro
3783    /// definition.
3784    Defmacro,
3785    /// `(defpoint-template NAME (PARAMS) BODY)` — the K8s-as-processes
3786    /// authoring surface's macro form (see `tatara-process`).
3787    DefpointTemplate,
3788    /// `(defcheck NAME (PARAMS) BODY)` — the workspace-coherence
3789    /// authoring surface's macro form (see
3790    /// `tatara-reconciler/checks.lisp`).
3791    Defcheck,
3792}
3793
3794impl MacroDefHead {
3795    /// The closed set of three macro-definition heads — single source
3796    /// of truth that drives the [`Self::keyword`] / [`fmt::Display`]
3797    /// projection AND the [`Self::from_keyword`] / [`FromStr`] decode
3798    /// sweeps keyed on [`Self::keyword`]. Adding a hypothetical fourth
3799    /// head (e.g. a `defpoint-fragment` partial-template surface, a
3800    /// `defrewrite` typed-rewriter authoring keyword) lands at one
3801    /// [`Self::ALL`] entry + one [`Self::keyword`] arm — exhaustively
3802    /// checked by the compiler (the `[Self; 3]` array literal forces
3803    /// the arity) AND by the per-variant truth-table tests below.
3804    ///
3805    /// Sibling closed-set lift to every other typed-shape enum in the
3806    /// crate ([`crate::ast::AtomKind::ALL`],
3807    /// [`crate::ast::QuoteForm::ALL`], [`SexpShape::ALL`],
3808    /// [`UnquoteForm::ALL`]) and across the workspace
3809    /// (`ConditionKind::ALL`, `ProcessPhase::ALL`,
3810    /// `ProcessSignal::ALL`, `ChannelKind::ALL`, `IntentKind::ALL`,
3811    /// `LifetimeKind::ALL`, `RequestorKind::ALL`, `ReceiptKind::ALL`,
3812    /// …) every one of which paired its typed projection with `ALL`
3813    /// before this lift.
3814    ///
3815    /// Future consumers that compose against `ALL`: LSP / REPL
3816    /// completion for the macro-definition head at point (every
3817    /// `(defma…` partial input expands through `Self::ALL.iter().
3818    /// map(MacroDefHead::keyword)`), `tatara-check` coverage assertions
3819    /// over which macro-definition heads reach a `DefmacroArity` /
3820    /// `DefmacroNonSymbolName` / `DefmacroNonListParams` arm at all
3821    /// (the typed sweep replaces the per-call-site vocabulary of three
3822    /// `&'static str` literals), any future audit-trail metric jointly
3823    /// labeled by [`Self::keyword`] (e.g.
3824    /// `tatara_lisp_defmacro_arity_total{head="defmacro"}` — the
3825    /// metric label set IS [`Self::ALL`] mapped through
3826    /// [`Self::keyword`]).
3827    pub const ALL: [Self; 3] = [Self::Defmacro, Self::DefpointTemplate, Self::Defcheck];
3828
3829    /// Canonical `&'static str` head keyword for [`Self::Defmacro`] —
3830    /// the `(defmacro NAME (PARAMS) BODY)` Lisp-style macro-definition
3831    /// form. Sibling posture to [`Self::DEFPOINT_TEMPLATE_KEYWORD`]
3832    /// (`"defpoint-template"`) and [`Self::DEFCHECK_KEYWORD`]
3833    /// (`"defcheck"`) on the same head-keyword algebra layer.
3834    ///
3835    /// Pre-lift the same `"defmacro"` bytes lived inline at the
3836    /// [`Self::keyword`] match arm plus at the Display truth-table
3837    /// test (`macro_def_head_display_renders_canonical_keyword_for_each_variant`)
3838    /// — the ≥2 PRIME-DIRECTIVE trigger. Post-lift the (`Defmacro`
3839    /// variant, canonical `&'static str`) pairing binds at ONE `pub
3840    /// const` on the typed [`MacroDefHead`] algebra: the [`Self::keyword`]
3841    /// arm and every consumer that pins the exact bytes route through
3842    /// this constant, so a rename to `defmacro-typed` (say, if a
3843    /// future extension lands a peer `defmacro-hygienic` variant and
3844    /// wants to disambiguate the legacy form) is ONE edit HERE with
3845    /// the derived Display / FromStr surfaces mechanically picking it
3846    /// up. Sibling posture to the closed set of per-role canonical
3847    /// `pub const` bytes on the substrate's other closed-set outer
3848    /// algebras: [`crate::ast::Atom::STR_DELIMITER`] (`'"'`),
3849    /// [`crate::ast::Atom::KEYWORD_MARKER`] (`":"`),
3850    /// [`crate::ast::Sexp::LIST_OPEN`] (`'('`),
3851    /// [`crate::macro_expand::MacroParams::REST_MARKER`] (`"&rest"`),
3852    /// [`crate::macro_expand::MacroParams::OPTIONAL_MARKER`] (`"&optional"`).
3853    pub const DEFMACRO_KEYWORD: &'static str = "defmacro";
3854
3855    /// Canonical `&'static str` head keyword for [`Self::DefpointTemplate`]
3856    /// — the `(defpoint-template NAME (PARAMS) BODY)` K8s-as-processes
3857    /// authoring-surface macro form (see the `tatara-process` crate).
3858    /// Sibling posture to [`Self::DEFMACRO_KEYWORD`] (`"defmacro"`)
3859    /// and [`Self::DEFCHECK_KEYWORD`] (`"defcheck"`) on the same
3860    /// head-keyword algebra layer.
3861    ///
3862    /// Pre-lift the same `"defpoint-template"` bytes lived inline at
3863    /// the [`Self::keyword`] match arm plus at the Display truth-table
3864    /// test. Post-lift the (`DefpointTemplate` variant, canonical
3865    /// `&'static str`) pairing binds at ONE `pub const` on the typed
3866    /// [`MacroDefHead`] algebra.
3867    pub const DEFPOINT_TEMPLATE_KEYWORD: &'static str = "defpoint-template";
3868
3869    /// Canonical `&'static str` head keyword for [`Self::Defcheck`] —
3870    /// the `(defcheck NAME (PARAMS) BODY)` workspace-coherence
3871    /// authoring-surface macro form (see `tatara-reconciler/checks.lisp`).
3872    /// Sibling posture to [`Self::DEFMACRO_KEYWORD`] (`"defmacro"`)
3873    /// and [`Self::DEFPOINT_TEMPLATE_KEYWORD`] (`"defpoint-template"`)
3874    /// on the same head-keyword algebra layer.
3875    ///
3876    /// Pre-lift the same `"defcheck"` bytes lived inline at the
3877    /// [`Self::keyword`] match arm plus at the Display truth-table
3878    /// test AND at seven `iter_named_calls_to(&forms, "defcheck")` /
3879    /// `["defmonitor", "defalertpolicy", "defcheck"]` test-site
3880    /// literals across [`crate::ast`] and [`crate::compile`]. Post-lift
3881    /// the (`Defcheck` variant, canonical `&'static str`) pairing
3882    /// binds at ONE `pub const` on the typed [`MacroDefHead`] algebra.
3883    pub const DEFCHECK_KEYWORD: &'static str = "defcheck";
3884
3885    /// The closed set of three canonical `&'static str` head keywords
3886    /// — the [`Self::DEFMACRO_KEYWORD`] (`"defmacro"`) canonical
3887    /// Lisp-style macro-definition keyword followed by the
3888    /// [`Self::DEFPOINT_TEMPLATE_KEYWORD`] (`"defpoint-template"`)
3889    /// K8s-as-processes authoring-surface keyword followed by the
3890    /// [`Self::DEFCHECK_KEYWORD`] (`"defcheck"`) workspace-coherence
3891    /// authoring-surface keyword. Canonical declaration order matches
3892    /// [`Self::ALL`] so `Self::KEYWORDS[i] == Self::ALL[i].keyword()`
3893    /// element-wise — pinned by
3894    /// `macro_def_head_keywords_align_with_all_by_index`.
3895    ///
3896    /// Sibling posture to
3897    /// [`crate::macro_expand::MacroParams::LAMBDA_LIST_KEYWORDS`]
3898    /// (`[Self; 2]`) — every closed-set algebra now pins its
3899    /// keyword-projection ALL array at the declaration site via a
3900    /// forced-arity `[&'static str; N]` array whose length fails
3901    /// compilation if a new variant lands without being added to the
3902    /// set. Adding a hypothetical fourth head (a `defpoint-fragment`
3903    /// partial-template surface, a `defrewrite` typed-rewriter
3904    /// authoring keyword) extends [`Self::ALL`] ONCE +
3905    /// [`Self::keyword`] ONCE + [`Self::KEYWORDS`] ONCE + adds ONE
3906    /// per-role keyword `pub const` — rustc's forced-arity check on
3907    /// the `[Self; N]` array + `[&'static str; N]` array pair
3908    /// enforces every downstream consumer picks up the extension
3909    /// mechanically.
3910    ///
3911    /// Future consumers that compose against [`Self::KEYWORDS`]: an
3912    /// LSP / REPL completion provider surfacing every `(defma…`
3913    /// partial input against the closed set (`Self::KEYWORDS.iter()`
3914    /// is the ONE typed sweep over every legal macro-definition
3915    /// head), a `tatara-check` coverage assertion (every workspace
3916    /// `.lisp` file's `(def…)` form-head must classify to some entry
3917    /// of `Self::KEYWORDS` OR be a registered `TataraDomain` keyword),
3918    /// a Sekiban audit-trail metric jointly labeled by
3919    /// [`Self::keyword`] whose metric-label set IS `Self::KEYWORDS`.
3920    pub const KEYWORDS: [&'static str; 3] = [
3921        Self::DEFMACRO_KEYWORD,
3922        Self::DEFPOINT_TEMPLATE_KEYWORD,
3923        Self::DEFCHECK_KEYWORD,
3924    ];
3925
3926    /// Project a `head: &str` borrow (a `Sexp` symbol slice) into the
3927    /// typed `MacroDefHead`. Returns `None` if `head` is not one of the
3928    /// three canonical macro-definition head keywords; the caller
3929    /// (`macro_def_from`) then returns `Ok(None)` to mean "this form is
3930    /// not a defmacro form."
3931    ///
3932    /// Implemented as a linear sweep over [`Self::ALL`] keyed on
3933    /// [`Self::keyword`] so the three canonical keyword literals
3934    /// (`"defmacro"` / `"defpoint-template"` / `"defcheck"`) live at
3935    /// ONE site (the `keyword` arms) rather than at TWO sites
3936    /// (`keyword` + a per-variant `from_keyword` match arm). Adding a
3937    /// fourth variant extends only [`Self::ALL`] + [`Self::keyword`],
3938    /// NOT a third per-variant literal site. The `Option<Self>` face
3939    /// is the open-by-design projection [`crate::ast::Sexp::as_call_to_any`]
3940    /// composes against; [`FromStr`] is the typed-error face callers
3941    /// reaching for a parse-rejection diagnostic compose against.
3942    /// Cross-face contract pinned by
3943    /// `macro_def_head_from_keyword_matches_from_str_for_every_input`.
3944    #[must_use]
3945    pub fn from_keyword(head: &str) -> Option<Self> {
3946        head.parse().ok()
3947    }
3948
3949    /// Project the typed `MacroDefHead` back to the canonical
3950    /// `&'static str` literal — feeds the `LispError::Defmacro*` Display
3951    /// rendering via the `#[error(...)]` annotation. The `&'static str`
3952    /// lifetime is load-bearing: it's what lets the variants project
3953    /// through this method into their `compile error in {head}:` prefix
3954    /// without an allocation, parallel to how
3955    /// `CompilerSpecIoStage::operation()` / `label()` feed
3956    /// `LispError::CompilerSpecIo`'s Display.
3957    #[must_use]
3958    pub fn keyword(self) -> &'static str {
3959        match self {
3960            Self::Defmacro => Self::DEFMACRO_KEYWORD,
3961            Self::DefpointTemplate => Self::DEFPOINT_TEMPLATE_KEYWORD,
3962            Self::Defcheck => Self::DEFCHECK_KEYWORD,
3963        }
3964    }
3965}
3966
3967// `impl std::fmt::Display for MacroDefHead` + `impl std::str::FromStr
3968// for MacroDefHead` + `impl tatara_closed_set::ClosedSet for MacroDefHead` +
3969// `pub struct UnknownMacroDefHead(pub String)` are generated by
3970// `#[derive(tatara_closed_set::DeriveClosedSet)]` on the enum declaration
3971// above. `label` delegates to the inherent `MacroDefHead::keyword` via
3972// `#[closed_set(via = "keyword")]` so the domain-canonical
3973// reserved-word projection (`"defmacro"` / `"defpoint-template"` /
3974// `"defcheck"`) stays load-bearing at the inherent surface while the
3975// trait surface unifies every closed-set implementor's projection name
3976// onto `label`. The `display` flag emits the substrate-wide
3977// `f.write_str(Self::keyword(*self))` block.
3978// `#[closed_set(generate_unknown = "macro definition head")]` emits the
3979// typed parse-rejection carrier with the substrate-wide `Debug + Clone
3980// + PartialEq + Eq + thiserror::Error` derives and the `#[error("unknown
3981// macro definition head: {0}")]` annotation byte-for-byte; the explicit
3982// label overrides the auto-derived
3983// `pascal_to_spaced_lowercase("MacroDefHead")` (`"macro def head"`)
3984// which abbreviates `Def` rather than expanding it to `definition`,
3985// pinning the pre-lift operator-facing wording. The FromStr decode is
3986// a linear sweep over `MacroDefHead::ALL` keyed on `keyword`; round-trip
3987// + cross-axis rejection (`"defpoint"` / `"symbol"`) pinned by
3988// `macro_def_head_keyword_round_trips_through_from_str` +
3989// `macro_def_head_from_str_rejects_cross_axis_vocabularies`.
3990
3991/// Closed-set identifier for the way a `Sexp::List` entry in a macro's
3992/// `&optional` section failed to match the canonical `(NAME DEFAULT)`
3993/// shape. Carried as a typed slot on `LispError::OptionalParamMalformed`
3994/// so authoring tools (REPL, LSP, `tatara-check`) bind to variant identity
3995/// rather than substring-matching the rendered suffix.
3996///
3997/// Mirror at the `parse_params` optional-section boundary of the prior-run
3998/// `MacroDefHead` (macro-definition-head closed set), `UnquoteForm`
3999/// (template-marker closed set), `CompilerSpecIoStage` (disk-persistence
4000/// surface), and `TemplateInvariantKind` (bytecode-runtime surface)
4001/// closed-set lifts: those enums key their respective rejection variants
4002/// on a typed identity; this enum keys the four reachable list-spec
4003/// rejection modes the optional-section gate can emit on a typed identity.
4004/// Adding a new mode (e.g., `SuppliedPNotYetSupported` once an evaluator
4005/// lands and the three-element `(name default supplied-p)` shape is
4006/// admitted) requires extending this enum, which rustc-enforces matching
4007/// at every projection site (`label()`).
4008///
4009/// `label(self) -> String` projects the typed reason to a short
4010/// human-readable clause (`"empty list"` / `"missing default"` / `"3
4011/// elements (need 2)"` / `"name not a symbol"`) that the
4012/// `LispError::OptionalParamMalformed` Display rendering threads through
4013/// `optional_param_malformed_suffix` into the parenthetical suffix —
4014/// parallel to how `TemplateInvariantKind::message()` feeds
4015/// `LispError::TemplateInvariant`'s Display.
4016///
4017/// Theory anchor: THEORY.md §V.1 — knowable platform; the closed set of
4018/// optional-spec malformed shapes becomes a TYPE rather than a runtime
4019/// string-comparison-and-format dance. A typo like `reason: "empty list
4020/// "` (trailing space) is structurally unrepresentable; the four shapes
4021/// are an exhaustive `match`. THEORY.md §VI.1 — generation over
4022/// composition; the typed enum lands the structural-completeness floor
4023/// for the optional-section-malformed surface, parallel to how
4024/// `TemplateInvariantKind` lands it for the bytecode-runtime surface and
4025/// `MacroDefHead` for the macro-definition-head surface.
4026#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4027pub enum OptionalParamMalformedReason {
4028    /// `&optional ()` — the spec is a list of length zero, with no name
4029    /// and no default form.
4030    EmptyList,
4031    /// `&optional (x)` — the spec is a list of length one, naming an
4032    /// optional but supplying no default form. This is REJECTED rather
4033    /// than reinterpreted as `&optional x` because a bare-symbol spec
4034    /// IS the canonical "no default" shape; a parenthesized
4035    /// single-element spec is ambiguous and would silently DROP the
4036    /// extra parens at the surface.
4037    MissingDefault,
4038    /// `&optional (x default extra …)` — the spec is a list of length
4039    /// three or more. CL's `(name default supplied-p)` shape is NOT
4040    /// supported in v0 (no evaluator → no `supplied-p` variable
4041    /// binding), so any third element is structurally surplus. `length`
4042    /// is the actual element count (≥3).
4043    ExtraElements { length: usize },
4044    /// `&optional (5 default)` — the spec is a list of length two but
4045    /// the first element isn't a symbol. The name slot must be a symbol
4046    /// (the same gate the bare-symbol path enforces); a non-symbol head
4047    /// is rejected here so the `OptionalParam.name: String` slot cannot
4048    /// be populated from a `5` / `:foo` / `(nested)` value.
4049    NonSymbolName,
4050}
4051
4052impl OptionalParamMalformedReason {
4053    /// Canonical `&'static str` diagnostic-label bytes for the
4054    /// [`Self::EmptyList`] rejection — `"empty list"`. Per-role peer of
4055    /// [`Self::EmptyList`] on the closed-set optional-spec-rejection
4056    /// algebra; consumers reach for
4057    /// `OptionalParamMalformedReason::EMPTY_LIST_LABEL` when the caller
4058    /// has a variant in hand at compile time and wants the canonical
4059    /// diagnostic bytes without an intermediate `.to_string()`
4060    /// allocation through [`Self::label`].
4061    ///
4062    /// Sibling posture to [`crate::error::MacroDefHead::DEFMACRO_KEYWORD`]
4063    /// et al (per-role reserved-word algebra on the macro-definition-head
4064    /// closed set) and [`crate::error::StructuralKind::NIL_LABEL`] et al
4065    /// (per-role structural-residual algebra on the SexpShape 2-of-12
4066    /// carving): every closed-set outer algebra the substrate carries
4067    /// now pins its per-variant canonical bytes at ONE `pub const` per
4068    /// role.
4069    ///
4070    /// The three per-role static labels here (this constant,
4071    /// [`Self::MISSING_DEFAULT_LABEL`], [`Self::NON_SYMBOL_NAME_LABEL`])
4072    /// close the STATIC subset of the four-arm closed set — the fourth
4073    /// arm [`Self::ExtraElements`] carries a `usize` payload and formats
4074    /// its label dynamically, so it stays inline at [`Self::label`] and
4075    /// does NOT admit a per-role `&'static str` constant on this axis.
4076    /// The 3-of-4 carving is pinned by
4077    /// [`Self::STATIC_LABELS`]'s `[&'static str; 3]` forced-arity array.
4078    ///
4079    /// Cross-axis overlap: this label's `"empty list"` bytes are
4080    /// byte-for-byte identical to the fallback body emitted by
4081    /// [`missing_head_symbol_suffix`]'s `None` arm (the "no head symbol,
4082    /// list is empty" rejection at [`LispError::MissingHeadSymbol`]).
4083    /// The two sites SHARE the surface bytes but carry DISTINCT
4084    /// semantic scopes (this arm scopes to the `&optional` section
4085    /// gate; the sibling site scopes to a bare call-head list-empty
4086    /// gate), so the alias is intentionally NOT lifted to a
4087    /// substrate-wide constant — a future disambiguation drift on
4088    /// EITHER side must land at the semantic-scope site rather than at
4089    /// a shared canonical byte.
4090    ///
4091    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
4092    /// preserves proofs; the (variant, canonical-bytes) pairing binds
4093    /// at ONE `pub const` on the typed algebra rather than at TWO
4094    /// sites (a match-arm literal AND a Display-test literal).
4095    /// THEORY.md §III — the typescape; the canonical diagnostic bytes
4096    /// bind at ONE `pub const` on the closed-set algebra rather than
4097    /// at inline `.to_string()` literals in the [`Self::label`] match
4098    /// arms.
4099    pub const EMPTY_LIST_LABEL: &'static str = "empty list";
4100
4101    /// Canonical `&'static str` diagnostic-label bytes for the
4102    /// [`Self::MissingDefault`] rejection — `"missing default"`.
4103    /// Sibling of [`Self::EMPTY_LIST_LABEL`] on the closed-set per-role
4104    /// static-label axis; see [`Self::EMPTY_LIST_LABEL`] for the
4105    /// algebra-level round-trip contract every sibling shares.
4106    pub const MISSING_DEFAULT_LABEL: &'static str = "missing default";
4107
4108    /// Canonical `&'static str` diagnostic-label bytes for the
4109    /// [`Self::NonSymbolName`] rejection — `"name not a symbol"`.
4110    /// Sibling of [`Self::EMPTY_LIST_LABEL`] on the closed-set per-role
4111    /// static-label axis; see [`Self::EMPTY_LIST_LABEL`] for the
4112    /// algebra-level round-trip contract every sibling shares.
4113    pub const NON_SYMBOL_NAME_LABEL: &'static str = "name not a symbol";
4114
4115    /// Closed-set forced-arity ALL array over the canonical STATIC
4116    /// diagnostic-label `&'static str` bytes — the three arms that
4117    /// project to a `&'static str` label under [`Self::label`], in
4118    /// canonical declaration order matching the enum's static-label
4119    /// subset ([`Self::EmptyList`], [`Self::MissingDefault`],
4120    /// [`Self::NonSymbolName`] — the fourth variant
4121    /// [`Self::ExtraElements`] formats a `usize` payload dynamically
4122    /// and is excluded from this array).
4123    ///
4124    /// Sibling posture to [`crate::error::StructuralKind::LABELS`]
4125    /// (`[&'static str; 2]` — the 2-of-12 structural-residual carving),
4126    /// [`crate::error::MacroDefHead::KEYWORDS`] (`[&'static str; 3]` —
4127    /// the 3-arm macro-definition-head algebra), and
4128    /// [`crate::ast::AtomKind::LABELS`] (`[&'static str; 6]` — the
4129    /// 6-of-12 atomic-payload carving) — every closed-set outer
4130    /// algebra that carries an `&'static str`-per-variant label now
4131    /// pins its per-role canonical bytes at ONE `pub const` per role
4132    /// PLUS an ALL array for family-wide consumers.
4133    ///
4134    /// Pre-lift the three static rejection-label bytes had NO per-role
4135    /// primitive on this closed-set algebra — a consumer with a
4136    /// [`OptionalParamMalformedReason`] variant in hand at compile time
4137    /// reaching for the canonical bytes had to spell
4138    /// `OptionalParamMalformedReason::EmptyList.label()` (runtime
4139    /// dispatch through the match arm PLUS a `.to_string()` heap
4140    /// allocation) OR inline the `"empty list"` literal at the call
4141    /// site AND at every Display-round-trip test's assertion body.
4142    /// Post-lift the THREE canonical bytes bind at ONE `pub const` per
4143    /// role on the typed algebra AND at [`Self::STATIC_LABELS`] as a
4144    /// family-wide forced-arity array — a future LSP / REPL completion
4145    /// bar keyed on this rejection surface, a `tatara-check` coverage
4146    /// sweep over the `&optional`-spec-malformed arm-set of a
4147    /// `LispError` corpus, or a Sekiban audit-trail metric jointly
4148    /// labeled by the rejection reason
4149    /// (`tatara_lisp_optional_param_malformed_total{reason="empty
4150    /// list"}`) reads through the typed constants without re-deriving
4151    /// the 3-of-4 static-subset carving inline.
4152    ///
4153    /// Adding a hypothetical fifth STATIC rejection reason (e.g. a
4154    /// `KeywordName` rejection for `&optional (:foo default)` if
4155    /// keywords ever became legal in the `&optional` name slot) extends
4156    /// this array AND adds ONE per-role `pub const` in lockstep —
4157    /// rustc's forced-arity check on `[&'static str; N]` fails
4158    /// compilation if the array grows without the const.
4159    ///
4160    /// The `#[allow(dead_code)]` posture matches
4161    /// [`crate::error::StructuralKind::HASH_DISCRIMINATORS`] and other
4162    /// prior-run family-wide ALL arrays: the substrate's current
4163    /// [`Self::label`] body composes through the per-variant match arm
4164    /// rather than sweeping the family-wide array, so no non-test
4165    /// caller currently reaches this array directly. The lift lands
4166    /// the substrate primitive so future consumers keyed on the whole
4167    /// static-subset (a Sekiban metric, a `tatara-check` predicate,
4168    /// a `TypedRewriter<OptionalParamMalformedReasonOp>` sweep) bind
4169    /// to ONE `[&'static str; 3]` primitive rather than re-deriving
4170    /// the array inline per callsite.
4171    ///
4172    /// Theory anchor: THEORY.md §III — the typescape; the three
4173    /// canonical rejection-label bytes bind at ONE typed
4174    /// `[&'static str; 3]` array on the closed-set algebra rather
4175    /// than at zero-primitive-plus-three-inline-`.to_string()`-literals
4176    /// scattered across [`Self::label`]. THEORY.md §V.1 — knowable
4177    /// platform; the static-subset's cardinality becomes a TYPE-level
4178    /// constant on the substrate algebra rather than a per-consumer
4179    /// runtime dispatch through the label match. THEORY.md §VI.1 —
4180    /// generation over composition; the family-wide contract
4181    /// (alignment with the enum's static-label subset, membership
4182    /// through [`Self::label`]) emerges from the composition of FOUR
4183    /// substrate primitives (this `pub const` array + the three
4184    /// per-role `pub const *_LABEL` aliases) rather than as per-
4185    /// variant inline assertions duplicated at each call site.
4186    #[allow(dead_code)]
4187    pub const STATIC_LABELS: [&'static str; 3] = [
4188        Self::EMPTY_LIST_LABEL,
4189        Self::MISSING_DEFAULT_LABEL,
4190        Self::NON_SYMBOL_NAME_LABEL,
4191    ];
4192
4193    /// Canonical target arity of the `&optional (NAME DEFAULT)` list-spec
4194    /// — exactly TWO elements (a symbol head naming the optional and a
4195    /// default form). The accept-arity for `parse_optional_list_spec`'s
4196    /// dispatch; every other arity is a rejection reason on this closed
4197    /// set ([`Self::EmptyList`] at arity 0, [`Self::MissingDefault`] at
4198    /// arity 1, [`Self::ExtraElements`] at arity ≥3).
4199    ///
4200    /// Pre-lift the target arity appeared as the bare literal `2` at
4201    /// FOUR sites: the `match items.len()` accept arm in
4202    /// `parse_optional_list_spec` (macro_expand.rs), the `"need 2"`
4203    /// suffix bytes embedded in [`Self::label`]'s `ExtraElements` arm,
4204    /// the doc-comment enumeration on this enum's Display projection,
4205    /// and every truth-table test that asserts against the rendered
4206    /// diagnostic bytes. A drift on any ONE site (e.g. an evaluator lands
4207    /// and the accept-arity becomes 3 to admit CL's `(name default
4208    /// supplied-p)` shape) had to ripple across all four in lockstep or
4209    /// silently fracture the parser gate from its operator-facing
4210    /// vocabulary. Post-lift the arity binds at ONE `pub const usize` on
4211    /// the typed algebra: the parser reaches through
4212    /// [`Self::classify_arity`] (which threads this constant into the
4213    /// match arm via a local rebind), [`Self::label`] renders `"need
4214    /// {}"` interpolated on this constant, and the doc + tests reach for
4215    /// the const symbol rather than the literal `2`.
4216    ///
4217    /// Sibling posture to the prior-run per-role `&'static str` / `u8` /
4218    /// `char` per-variant algebras ([`Self::EMPTY_LIST_LABEL`],
4219    /// [`crate::error::StructuralKind::HASH_DISCRIMINATORS`],
4220    /// [`crate::ast::QuoteForm::UNQUOTE_PREFIX`], et al) — every prior
4221    /// lift pinned a per-role marker at ONE `pub const` on the typed
4222    /// algebra rather than at N inline literals across parsers,
4223    /// projections, and tests. This lift extends the axis-set with the
4224    /// first per-family-canonical `usize` on the closed-set optional-
4225    /// spec-rejection algebra.
4226    ///
4227    /// Theory anchor: THEORY.md §III — the typescape; the canonical
4228    /// accept-arity of the `&optional (NAME DEFAULT)` spec binds at ONE
4229    /// typed `pub const usize` on the closed-set algebra rather than at
4230    /// four literal-`2` sites scattered across parsers, projections, and
4231    /// tests. THEORY.md §II.1 invariant 5 — composition preserves proofs;
4232    /// a hypothetical bump to arity-3 (once an evaluator admits `(name
4233    /// default supplied-p)`) lands at ONE constant AND rustc-enforces
4234    /// that every downstream projection ([`Self::label`],
4235    /// [`Self::classify_arity`], every truth-table test) recomputes
4236    /// against the new arity. THEORY.md §V.1 — knowable platform; the
4237    /// parser's accept-arity becomes a TYPE-level constant on the
4238    /// substrate algebra rather than a per-callsite magic number.
4239    pub const OPTIONAL_PARAM_SPEC_ARITY: usize = 2;
4240
4241    /// Classify a list-spec's element count against the canonical
4242    /// accept-arity [`Self::OPTIONAL_PARAM_SPEC_ARITY`] — returns `None`
4243    /// iff the arity is exactly the accept target (the parser continues
4244    /// to the head-symbol gate); returns `Some(reason)` otherwise with
4245    /// the arity-derivable rejection reason on the closed set.
4246    ///
4247    /// The three arity-derivable rejection arms are:
4248    ///
4249    ///   * arity 0 → [`Self::EmptyList`]
4250    ///   * arity 1 → [`Self::MissingDefault`]
4251    ///   * arity ≥3 → [`Self::ExtraElements { length }`]
4252    ///
4253    /// The fourth variant [`Self::NonSymbolName`] is NOT arity-derivable
4254    /// — accept-arity with a non-symbol head still rejects — and stays
4255    /// at the caller's head-symbol gate. The caller composes this
4256    /// classifier with the head-symbol check to close the full four-way
4257    /// dispatch. Pre-lift `parse_optional_list_spec` embedded the arity
4258    /// match inline (`match items.len() { 0 => ..., 1 => ..., 2 => ...,
4259    /// length => ... }`) with the accept-arity as a bare literal `2` in
4260    /// the accept arm — a duplicate of the target arity that
4261    /// [`Self::label`]'s `"need 2"` suffix ALSO embedded literally. Post-
4262    /// lift the arity-to-reason projection binds at ONE typed function on
4263    /// the algebra threading [`Self::OPTIONAL_PARAM_SPEC_ARITY`] through
4264    /// a local `const ARITY` rebind (rustc's match-pattern rules require
4265    /// a name binding rather than a `Self::` path pattern).
4266    ///
4267    /// Sibling posture to [`crate::ast::QuoteForm::as_unquote_form`]
4268    /// (typed 2-of-4 subset projection between quote-family enums) and
4269    /// [`crate::ast::AtomKind::from_atom`] (typed constructor from a
4270    /// parent's payload) — every prior-run typed classifier factored an
4271    /// inline dispatch into a `#[must_use]` typed function on the closed
4272    /// set. This method extends the pattern to the arity-to-reason
4273    /// projection.
4274    ///
4275    /// Future consumers that compose against this classifier: a
4276    /// `tatara-check` static analyzer that predicts optional-spec
4277    /// rejections without threading through the whole parser; an LSP
4278    /// quick-fix that suggests wrapping a bare symbol as `(symbol
4279    /// default)` iff `classify_arity(1) == Some(MissingDefault)`; a
4280    /// `TypedRewriter<OptionalParamMalformedReasonOp>` sweep that keys
4281    /// on this reason projection.
4282    ///
4283    /// Theory anchor: THEORY.md §III — the typescape; the arity→reason
4284    /// projection binds at ONE typed function on the algebra rather than
4285    /// at inline `match` bodies duplicated across parsers. THEORY.md
4286    /// §II.1 invariant 1 (typed entry): every parser that accepts an
4287    /// `&optional`-spec list threads its arity through this classifier
4288    /// before touching the head-symbol gate, closing the arity
4289    /// half of the rejection surface at a single typed site.
4290    #[must_use]
4291    pub fn classify_arity(length: usize) -> Option<Self> {
4292        const ARITY: usize = OptionalParamMalformedReason::OPTIONAL_PARAM_SPEC_ARITY;
4293        match length {
4294            0 => Some(Self::EmptyList),
4295            1 => Some(Self::MissingDefault),
4296            ARITY => None,
4297            length => Some(Self::ExtraElements { length }),
4298        }
4299    }
4300
4301    /// Zero-allocation projection over the STATIC subset of the closed
4302    /// set — `Some(&'static str)` for the three arms whose diagnostic
4303    /// bytes bind at a per-role [`Self::EMPTY_LIST_LABEL`] /
4304    /// [`Self::MISSING_DEFAULT_LABEL`] / [`Self::NON_SYMBOL_NAME_LABEL`]
4305    /// constant, and `None` for the fourth arm [`Self::ExtraElements`]
4306    /// whose diagnostic bytes format the `length` payload dynamically
4307    /// through `format!("{length} elements (need {})",
4308    /// [`Self::OPTIONAL_PARAM_SPEC_ARITY`])` and cannot bind to a
4309    /// `&'static str` constant on this axis.
4310    ///
4311    /// This is the STATIC-subset typed projection over the 3-of-4
4312    /// carving [`Self::STATIC_LABELS`] enumerates — the same 3-of-4
4313    /// shape pinned as a `[&'static str; 3]` forced-arity array on
4314    /// that constant. Callers that KNOW they've matched a static arm
4315    /// at compile time reach for the per-role `pub const *_LABEL`
4316    /// directly; callers that hold a variant of unknown identity
4317    /// reach for this projection to filter the STATIC subset without
4318    /// paying an allocation for the failing branch. The composition
4319    /// gives every downstream site — Sekiban audit-trail metrics
4320    /// (`tatara_lisp_optional_param_malformed_total{reason="empty
4321    /// list"}` where the label is a `&'static str` label slot with
4322    /// no heap indirection), an LSP quick-fix keyed on the STATIC
4323    /// subset, a `tatara-check` predicate that filters a corpus's
4324    /// `OptionalParamMalformed` rejections into static-arm vs
4325    /// dynamic-arm buckets — one typed subset selector rather than
4326    /// three inline `matches!(reason, Self::EmptyList |
4327    /// Self::MissingDefault | Self::NonSymbolName)` gates threaded
4328    /// through per-consumer allocation.
4329    ///
4330    /// Sibling posture to [`Self::classify_arity`] (typed `usize` →
4331    /// `Option<Self>` projection over the arity-derivable 3-of-4
4332    /// carving) — both methods carve the same closed set along the
4333    /// same 3-of-4 axis (the fourth variant [`Self::NonSymbolName`]
4334    /// on the classify side, [`Self::ExtraElements`] on this side)
4335    /// and pin the carving at ONE typed function on the algebra.
4336    /// The two carvings compose: a caller with an arity `n` in hand
4337    /// runs `classify_arity(n)` to get `Option<Self>`, then runs
4338    /// `label_static(reason)` on the `Some` result to get
4339    /// `Option<&'static str>` — the double-`Option` collapses the
4340    /// arity → reason → static-label pipeline into TWO typed
4341    /// projections composable at zero allocation cost for the STATIC
4342    /// subset.
4343    ///
4344    /// [`Self::label`] composes this projection with the dynamic
4345    /// [`Self::ExtraElements`] format arm — an `Option::unwrap_or_else`
4346    /// over `label_static()` that lands on the `format!` branch iff
4347    /// the variant is [`Self::ExtraElements`]. Pre-lift `label`'s
4348    /// body carried FOUR match arms — three inline `.to_string()`
4349    /// literals routing through the per-role `pub const`s AND one
4350    /// `format!` arm formatting the `length` payload. Post-lift
4351    /// `label`'s body reduces to `label_static().map(str::to_string)
4352    /// .unwrap_or_else(|| format!(...))`, and the three per-role
4353    /// static arms bind at ONE typed subset projection rather than
4354    /// at three duplicated `.to_string()` sites.
4355    ///
4356    /// Const-fn on `Self` because the four arms match on unit
4357    /// / payload-carrying variants alike without allocation — the
4358    /// `Self::ExtraElements { .. }` arm ignores the payload via
4359    /// `{ .. }` because the projection routes to `None` regardless
4360    /// of the `length` value.
4361    ///
4362    /// Theory anchor: THEORY.md §III — the typescape; the STATIC-
4363    /// subset projection binds at ONE typed function on the
4364    /// algebra rather than at three inline `matches!` gates
4365    /// scattered across consumers. THEORY.md §II.1 invariant 5 —
4366    /// composition preserves proofs; a hypothetical fifth static
4367    /// rejection reason (e.g. `KeywordName` for `&optional (:foo
4368    /// default)`) extends this projection AND [`Self::STATIC_LABELS`]
4369    /// in lockstep — rustc's exhaustive-match check on this method
4370    /// fails compilation without the new arm. THEORY.md §VI.1 —
4371    /// generation over composition; [`Self::label`]'s dynamic body
4372    /// emerges from the composition of TWO substrate primitives (this
4373    /// static-subset projection + the [`Self::ExtraElements`] format
4374    /// arm) rather than as four duplicated match arms.
4375    #[must_use]
4376    pub const fn label_static(self) -> Option<&'static str> {
4377        match self {
4378            Self::EmptyList => Some(Self::EMPTY_LIST_LABEL),
4379            Self::MissingDefault => Some(Self::MISSING_DEFAULT_LABEL),
4380            Self::NonSymbolName => Some(Self::NON_SYMBOL_NAME_LABEL),
4381            Self::ExtraElements { .. } => None,
4382        }
4383    }
4384
4385    /// Canonical `&'static str` noun-descriptor for the
4386    /// [`Self::ExtraElements`] rejection's dynamic format template —
4387    /// `"elements"`. Per-role peer of `Self::ExtraElements` on the
4388    /// DYNAMIC 1-of-4 subset carving of the four-arm closed set.
4389    ///
4390    /// Pre-lift the `"elements"` noun lived inline inside
4391    /// [`Self::label`]'s `format!("{length} elements (need {})", …)`
4392    /// arm — a single site, but one that any downstream sweep over
4393    /// dynamic-arm nouns had to substring-match rather than bind to a
4394    /// typed constant. Post-lift the (`ExtraElements` variant, canonical
4395    /// noun bytes) pairing binds at ONE `pub const` on the typed
4396    /// [`OptionalParamMalformedReason`] algebra: [`Self::label_dynamic`]
4397    /// composes this constant into the shared `"{length} {descriptor}
4398    /// (need {})"` format template, the peer [`Self::DYNAMIC_DESCRIPTORS`]
4399    /// array closes the DYNAMIC carving as a forced-arity
4400    /// `[&'static str; 1]` at ONE site, and [`Self::descriptor_dynamic`]
4401    /// carries the variant→noun projection.
4402    ///
4403    /// Sibling posture to
4404    /// [`TemplateInvariantKind::SUBST_BAD_INDEX_DESCRIPTOR`] /
4405    /// [`TemplateInvariantKind::SPLICE_BAD_INDEX_DESCRIPTOR`] on the peer
4406    /// bytecode-runtime algebra's DYNAMIC 2-of-4 subset carving — both
4407    /// pin per-role noun-descriptor bytes at ONE `pub const` on their
4408    /// closed-set algebra rather than at inline literals inside a
4409    /// `format!(...)` arm. The peer's DYNAMIC subset is TWO arms wide
4410    /// (`SubstBadIndex`, `SpliceBadIndex`); this algebra's DYNAMIC subset
4411    /// is ONE arm wide (`ExtraElements`) — the same carving idiom applied
4412    /// at asymmetric widths.
4413    ///
4414    /// Theory anchor: THEORY.md §III — the typescape; the canonical
4415    /// noun-descriptor of the ExtraElements arm's dynamic format
4416    /// template binds at ONE typed `pub const &'static str` on the
4417    /// closed-set algebra rather than at an inline literal inside
4418    /// [`Self::label`]'s `format!` body. THEORY.md §II.1 invariant 5 —
4419    /// composition preserves proofs; a rename of the noun (e.g. from
4420    /// `"elements"` to `"items"` if an evaluator surface ever admits a
4421    /// more general list-shape) lands at ONE `pub const` edit — every
4422    /// downstream projection ([`Self::label_dynamic`],
4423    /// [`Self::DYNAMIC_DESCRIPTORS`], the [`Self::label`] composition,
4424    /// every truth-table test) recomputes against the new bytes.
4425    /// THEORY.md §VI.1 — generation over composition; the shared format
4426    /// template that emitted the `"elements"` literal inline at ONE site
4427    /// pre-lift now composes ONE typed const AND ONE typed projection
4428    /// AND ONE typed arity, so future dynamic-arm siblings extend at
4429    /// exactly these three axes rather than at inline duplication.
4430    pub const EXTRA_ELEMENTS_DESCRIPTOR: &'static str = "elements";
4431
4432    /// Closed-set forced-arity ALL array over the canonical DYNAMIC
4433    /// noun-descriptor `&'static str` bytes — the ONE arm that projects
4434    /// to a `&'static str` descriptor under [`Self::descriptor_dynamic`],
4435    /// in canonical declaration order matching the enum's dynamic-label
4436    /// subset ([`Self::ExtraElements`] — the other three variants
4437    /// [`Self::EmptyList`], [`Self::MissingDefault`],
4438    /// [`Self::NonSymbolName`] carry their FULL bytes on the STATIC axis
4439    /// via [`Self::STATIC_LABELS`] and are excluded from this array).
4440    ///
4441    /// Peer 1-of-4 carving to [`Self::STATIC_LABELS`] — together the
4442    /// two arrays partition the four-arm closed set into its STATIC ⊕
4443    /// DYNAMIC halves. The STATIC array carries the three arms whose
4444    /// FULL diagnostic is `&'static str`; this DYNAMIC array carries the
4445    /// one arm whose NOUN slot is `&'static str` and whose full label is
4446    /// a `format!("{length} {descriptor} (need {})")` composition over
4447    /// [`Self::OPTIONAL_PARAM_SPEC_ARITY`]. Adding a hypothetical fifth
4448    /// static rejection reason (e.g. `KeywordName` for `&optional (:foo
4449    /// default)`) lands only on the STATIC array; adding a hypothetical
4450    /// fifth dynamic rejection reason (e.g. `NestedList { depth: usize
4451    /// }` naming a spec whose `NAME` slot is itself a list rather than a
4452    /// symbol) lands only on this array — rustc's forced-arity check on
4453    /// the `[&'static str; N]` shape fails compilation if the array
4454    /// grows without the corresponding per-role descriptor +
4455    /// [`Self::descriptor_dynamic`] arm being extended in lockstep.
4456    ///
4457    /// Sibling posture to
4458    /// [`TemplateInvariantKind::DYNAMIC_DESCRIPTORS`] (`[&'static str;
4459    /// 2]` — the peer bytecode-runtime algebra's DYNAMIC 2-of-4 carving)
4460    /// — every closed-set outer algebra the substrate carries now pins
4461    /// BOTH its STATIC-subset canonical-bytes array AND its DYNAMIC-
4462    /// subset per-role-slot array at ONE `pub const` per axis rather
4463    /// than at N inline literals scattered across projections and tests.
4464    /// The two peer algebras carry the same STATIC ⊕ DYNAMIC partition
4465    /// idiom at asymmetric widths (`TemplateInvariantKind` is 2 ⊕ 2;
4466    /// `OptionalParamMalformedReason` is 3 ⊕ 1).
4467    ///
4468    /// The `#[allow(dead_code)]` posture matches
4469    /// [`TemplateInvariantKind::DYNAMIC_DESCRIPTORS`] and
4470    /// [`Self::STATIC_LABELS`]: the substrate's current [`Self::label`]
4471    /// body composes through [`Self::descriptor_dynamic`] rather than
4472    /// sweeping the family-wide array, so no non-test caller currently
4473    /// reaches this array directly. The lift lands the substrate
4474    /// primitive so future consumers keyed on the whole dynamic subset
4475    /// (a Sekiban metric, a `tatara-check` predicate, a
4476    /// `TypedRewriter<OptionalParamMalformedReasonOp>` sweep) bind to
4477    /// ONE `[&'static str; 1]` primitive rather than re-deriving the
4478    /// array inline per callsite.
4479    ///
4480    /// Theory anchor: THEORY.md §III — the typescape; the ONE canonical
4481    /// dynamic-arm noun-descriptor byte binds at ONE typed
4482    /// `[&'static str; 1]` array on the closed-set algebra rather than
4483    /// at zero-primitive-plus-one-inline-literal inside [`Self::label`]'s
4484    /// `format!` body. THEORY.md §V.1 — knowable platform; the DYNAMIC-
4485    /// subset's cardinality becomes a TYPE-level constant on the
4486    /// substrate algebra rather than a per-consumer runtime dispatch
4487    /// through the label match. THEORY.md §VI.1 — generation over
4488    /// composition; the family-wide contract (alignment with the enum's
4489    /// dynamic-label subset, membership through
4490    /// [`Self::descriptor_dynamic`]) emerges from the composition of TWO
4491    /// substrate primitives (this `pub const` array + the one per-role
4492    /// `pub const EXTRA_ELEMENTS_DESCRIPTOR` alias) rather than as
4493    /// per-variant inline assertions duplicated at each call site.
4494    #[allow(dead_code)]
4495    pub const DYNAMIC_DESCRIPTORS: [&'static str; 1] = [Self::EXTRA_ELEMENTS_DESCRIPTOR];
4496
4497    /// Zero-allocation projection over the DYNAMIC subset of the closed
4498    /// set — `Some(&'static str)` for the one arm whose diagnostic bytes
4499    /// carry a `usize` payload and format through the shared
4500    /// `"{length} {descriptor} (need {})"` template
4501    /// ([`Self::ExtraElements`] → [`Self::EXTRA_ELEMENTS_DESCRIPTOR`]
4502    /// (`"elements"`)), and `None` for the three arms whose full
4503    /// diagnostic bytes bind at a `&'static str` on the STATIC axis
4504    /// ([`Self::EmptyList`], [`Self::MissingDefault`],
4505    /// [`Self::NonSymbolName`]) and carry no per-role descriptor slot.
4506    ///
4507    /// Peer projection to [`Self::label_static`] on the sibling STATIC
4508    /// 3-of-4 subset carving — the two projections together partition
4509    /// the four-arm closed set into its STATIC ⊕ DYNAMIC halves at zero
4510    /// allocation cost. Callers holding a variant of unknown identity
4511    /// thread it through BOTH projections; exactly ONE of
4512    /// `label_static(self)` and `descriptor_dynamic(self)` returns
4513    /// `Some` for every legal variant (`label_static` on the three-arm
4514    /// STATIC subset, `descriptor_dynamic` on the one-arm DYNAMIC
4515    /// subset). The partition is pinned by the truth-table test
4516    /// `optional_param_malformed_reason_static_dynamic_projections_partition_closed_set`.
4517    ///
4518    /// The `const fn` posture matches [`Self::label_static`],
4519    /// [`TemplateInvariantKind::descriptor_dynamic`], and every peer
4520    /// closed-set projection method on the substrate — the projection
4521    /// evaluates at rustc const-eval time when the variant is a const,
4522    /// so a future const-context consumer (an `#[allow(dead_code)] const
4523    /// _: Option<&'static str> = ...` static assertion, a `tatara-check`
4524    /// predicate binding at module-init time) picks up the DYNAMIC-
4525    /// subset selector without runtime dispatch.
4526    ///
4527    /// Sibling posture to
4528    /// [`TemplateInvariantKind::descriptor_dynamic`] on the peer
4529    /// bytecode-runtime algebra's DYNAMIC 2-of-4 subset carving — both
4530    /// project the DYNAMIC subset of a mixed-payload closed set (STATIC-
4531    /// only arms project to `None`) into a typed `Option<&'static str>`
4532    /// — the same idiom applied at two peer boundaries of the
4533    /// substrate's bytecode-runtime and macro-authoring surfaces at
4534    /// asymmetric widths (peer is 2-arm DYNAMIC; this is 1-arm DYNAMIC).
4535    ///
4536    /// Future consumers that compose against this projection: an LSP
4537    /// quick-fix keyed on the DYNAMIC-subset descriptor slot (surfaces
4538    /// `"drop the {descriptor} after the default form"` with
4539    /// `{descriptor}` bound to whatever this projection returns); a
4540    /// `tatara-check` predicate that filters a corpus's
4541    /// `OptionalParamMalformed` rejections into static-arm vs dynamic-
4542    /// arm buckets in ONE typed subset selector rather than
4543    /// `matches!(reason, Self::ExtraElements { .. })` gates threaded
4544    /// through per-consumer allocation.
4545    #[must_use]
4546    pub const fn descriptor_dynamic(self) -> Option<&'static str> {
4547        match self {
4548            Self::ExtraElements { .. } => Some(Self::EXTRA_ELEMENTS_DESCRIPTOR),
4549            Self::EmptyList | Self::MissingDefault | Self::NonSymbolName => None,
4550        }
4551    }
4552
4553    /// Typed `Option<String>` projection over the DYNAMIC 1-of-4 subset
4554    /// carving of [`OptionalParamMalformedReason`]'s four-arm closed set
4555    /// — returns `Some(label)` for the one arm whose diagnostic bytes
4556    /// format their `usize` payload through the SHARED `"{length}
4557    /// {descriptor} (need {})"` template, and `None` for the three
4558    /// STATIC arms whose full bytes bind on the [`Self::label_static`]
4559    /// axis.
4560    ///
4561    /// Composes [`Self::descriptor_dynamic`] into the shared format
4562    /// template — the `{descriptor}` slot binds to the per-role
4563    /// [`Self::EXTRA_ELEMENTS_DESCRIPTOR`] constant the DYNAMIC arm
4564    /// projects through, the `{length}` slot binds the `usize` payload
4565    /// the enum's dynamic variant carries, and the `(need {})` suffix
4566    /// binds [`Self::OPTIONAL_PARAM_SPEC_ARITY`]. Pre-lift
4567    /// [`Self::label`]'s body carried the inline `format!("{length}
4568    /// elements (need {})", …)` arm hardcoding the `"elements"` noun —
4569    /// post-lift the shared template binds at ONE `format!(...)` site
4570    /// inside this method, the noun slot binds at the per-role
4571    /// descriptor const, and [`Self::descriptor_dynamic`] carries the
4572    /// arm→descriptor mapping.
4573    ///
4574    /// Peer projection to [`Self::label_static`] on the sibling STATIC
4575    /// 3-of-4 subset carving — the two projections together partition
4576    /// the four-arm closed set into its STATIC ⊕ DYNAMIC label-
4577    /// projection halves. [`Self::label`]'s body reduces to
4578    /// `label_static().map(str::to_string).or_else(||
4579    /// label_dynamic()).expect(...)` — ONE compositional flow over TWO
4580    /// typed subset projections rather than FOUR duplicated match arms
4581    /// (three `.to_string()` sites on the STATIC subset + one
4582    /// `format!(...)` site on the DYNAMIC subset in the pre-lift body).
4583    ///
4584    /// Sibling posture to [`TemplateInvariantKind::message_dynamic`] on
4585    /// the peer bytecode-runtime algebra's DYNAMIC 2-of-4 subset carving
4586    /// — both compose a per-role descriptor projection with a shared
4587    /// `format!(...)` template + a `usize` payload into a typed
4588    /// `Option<String>`. The peer's DYNAMIC subset is 2-arm wide (two
4589    /// noun-slot values, one shared template); this algebra's DYNAMIC
4590    /// subset is 1-arm wide (one noun-slot value, one shared template)
4591    /// — the same STATIC ⊕ DYNAMIC composition idiom at asymmetric
4592    /// widths.
4593    ///
4594    /// Future consumers that compose against this projection: a
4595    /// `tatara-check` predicate that filters a corpus's
4596    /// `OptionalParamMalformed` rejections into static-arm vs dynamic-
4597    /// arm label buckets; a Sekiban audit-trail metric that samples
4598    /// dynamic-arm labels without paying the allocation cost on the
4599    /// STATIC subset (the `None` branch short-circuits); an LSP quick-
4600    /// fix that suggests trimming the surplus elements based on the
4601    /// `{length}` payload the caller extracts from the enum before
4602    /// invoking this projection.
4603    #[must_use]
4604    pub fn label_dynamic(self) -> Option<String> {
4605        let descriptor = self.descriptor_dynamic()?;
4606        let length = match self {
4607            Self::ExtraElements { length } => length,
4608            Self::EmptyList | Self::MissingDefault | Self::NonSymbolName => {
4609                // Unreachable: descriptor_dynamic() returned Some only
4610                // for ExtraElements on the `?` above. Kept exhaustive
4611                // rather than `_ =>` so a future refactor that adds a
4612                // fifth dynamic arm without extending descriptor_dynamic
4613                // fails compilation at THIS callsite rather than at
4614                // runtime.
4615                unreachable!(
4616                    "descriptor_dynamic returned Some only for dynamic-arm variants; \
4617                     the STATIC-arm match here is unreachable via the ? short-circuit"
4618                )
4619            }
4620        };
4621        Some(format!(
4622            "{length} {descriptor} (need {})",
4623            Self::OPTIONAL_PARAM_SPEC_ARITY
4624        ))
4625    }
4626
4627    /// Short human-readable clause for the parenthetical suffix of
4628    /// `LispError::OptionalParamMalformed`'s Display. The variants
4629    /// project to:
4630    ///
4631    ///   * `EmptyList`          → [`Self::EMPTY_LIST_LABEL`] (`"empty list"`)
4632    ///   * `MissingDefault`     → [`Self::MISSING_DEFAULT_LABEL`] (`"missing default"`)
4633    ///   * `ExtraElements{N}`   → `"N elements (need 2)"` (dynamic; the `2`
4634    ///     is [`Self::OPTIONAL_PARAM_SPEC_ARITY`])
4635    ///   * `NonSymbolName`      → [`Self::NON_SYMBOL_NAME_LABEL`] (`"name not a symbol"`)
4636    ///
4637    /// `label` returns `String` (rather than `&'static str`) because the
4638    /// `ExtraElements` arm formats its `length` payload — the other three
4639    /// arms route through per-role [`Self::EMPTY_LIST_LABEL`] /
4640    /// [`Self::MISSING_DEFAULT_LABEL`] / [`Self::NON_SYMBOL_NAME_LABEL`]
4641    /// constants which `.to_string()` projects through. Mirror of
4642    /// `TemplateInvariantKind::message()`: both return `String`, both
4643    /// project the closed-set typed reason into the `LispError::Display`
4644    /// rendering via the variant's `#[error(...)]` annotation.
4645    ///
4646    /// Composes [`Self::label_static`] (the STATIC-subset zero-allocation
4647    /// projection over the 3-of-4 carving) with [`Self::label_dynamic`]
4648    /// (the DYNAMIC-subset composition over the 1-of-4 carving). Pre-
4649    /// lift the three static arms carried inline `.to_string()` literals
4650    /// routing through the per-role `pub const`s AND the dynamic arm
4651    /// carried an inline `format!(...)` — post-lift the STATIC subset
4652    /// binds at ONE typed projection ([`Self::label_static`]) and the
4653    /// DYNAMIC subset binds at ONE typed projection
4654    /// ([`Self::label_dynamic`]), and this body reduces to
4655    /// `label_static().map(str::to_string).or_else(|| label_dynamic())`
4656    /// composing the two — exactly the shape
4657    /// [`TemplateInvariantKind::message`] carries on the peer bytecode-
4658    /// runtime algebra.
4659    ///
4660    /// The STATIC ⊕ DYNAMIC partition guarantees exactly ONE of the two
4661    /// projections returns `Some` for every legal variant; the `expect`
4662    /// arm is structurally unreachable by construction.
4663    #[must_use]
4664    pub fn label(self) -> String {
4665        self.label_static()
4666            .map(str::to_string)
4667            .or_else(|| self.label_dynamic())
4668            .expect(
4669                "label_static ⊕ label_dynamic partition the four-arm closed set; \
4670                 exactly one projection returns Some for every legal variant",
4671            )
4672    }
4673}
4674
4675/// Closed-set identifier for the syntactic marker of a macro-template
4676/// unquote (`,`) or unquote-splice (`,@`). Carried as a typed slot on
4677/// `LispError::UnboundTemplateVar` and `LispError::NonSymbolUnquoteTarget`
4678/// so authoring tools (REPL, LSP, `tatara-check`) bind to variant identity
4679/// via `UnquoteForm::Splice` rather than substring-matching the rendered
4680/// `prefix` literal.
4681///
4682/// Mirror at the template-marker boundary of the prior-run `MacroDefHead`
4683/// (macro-definition-head closed set), `CompilerSpecIoStage`
4684/// (disk-persistence surface), and `TemplateInvariantKind` (bytecode-runtime
4685/// surface) closed-set lifts: those enums key their respective rejection
4686/// variants on a typed identity; this enum keys the two unquote-template
4687/// rejection variants (`UnboundTemplateVar`, `NonSymbolUnquoteTarget`) on a
4688/// typed marker identity. Adding a new unquote variant (e.g., a hypothetical
4689/// `,~` form) requires extending this enum, which rustc-enforces matching at
4690/// every projection site (`marker()`) — the closed set becomes a TYPE rather
4691/// than two `&'static str`-keyed slots that could drift independently.
4692///
4693/// `marker(self) -> &'static str` projects the typed `UnquoteForm` back to
4694/// the canonical literal for `LispError::Display` rendering. The `&'static
4695/// str` lifetime is load-bearing: it lets both variants project through this
4696/// method into their `compile error in {prefix}…:` prefix without an
4697/// allocation, parallel to how `MacroDefHead::keyword()` and
4698/// `CompilerSpecIoStage::operation()` / `label()` feed their respective
4699/// `LispError::*` Display impls.
4700///
4701/// Theory anchor: THEORY.md §V.1 — knowable platform; the closed set of
4702/// unquote markers becomes a TYPE rather than a runtime
4703/// string-comparison-and-format dance. A typo like `prefix: ",,"` is
4704/// structurally unrepresentable rather than re-asserted at the helper
4705/// boundary. THEORY.md §VI.1 — generation over composition; the typed enum
4706/// lands the structural-completeness floor for the template-marker surface,
4707/// parallel to how `MacroDefHead` lands it for the macro-definition-head
4708/// surface and `CompilerSpecIoStage` for the disk-persistence surface.
4709/// THEORY.md §II.1 invariant 1 (typed entry): a non-symbol unquote target /
4710/// an unbound template var is exactly the failure mode the typed-entry gate
4711/// exists to reject, and the marker identity is part of the proof.
4712#[derive(Debug, Clone, Copy, PartialEq, Eq, tatara_closed_set::DeriveClosedSet)]
4713#[closed_set(via = "marker", display, generate_unknown = "unquote form")]
4714pub enum UnquoteForm {
4715    /// `,x` — single-value substitution. The `,` marker; the inner symbol
4716    /// is substituted with its bound value at template expansion.
4717    Unquote,
4718    /// `,@x` — list-splice substitution. The `,@` marker; the inner symbol
4719    /// must be bound to a list, whose elements are flattened into the
4720    /// containing list at template expansion.
4721    Splice,
4722}
4723
4724impl UnquoteForm {
4725    /// The closed set of two template-marker syntactic forms — single
4726    /// source of truth that drives the [`Self::marker`] / [`fmt::Display`]
4727    /// projection AND the [`FromStr`] decode sweep keyed on
4728    /// [`Self::marker`]. Adding a hypothetical third variant (e.g. a
4729    /// `,~` reverse-unquote, a `,?` conditional-unquote) lands at one
4730    /// [`Self::ALL`] entry + one [`Self::marker`] arm — exhaustively
4731    /// checked by the compiler (the `[Self; 2]` array literal forces
4732    /// the arity) AND by the per-variant truth-table tests below.
4733    ///
4734    /// Sibling closed-set lift to every other typed-shape enum the
4735    /// substrate carries: this crate's own [`SexpShape::ALL`] (the
4736    /// twelve reachable outer shapes — superset on the structural axis
4737    /// of the `Sexp` algebra), [`crate::ast::AtomKind::ALL`] (the six
4738    /// atomic-payload kinds — peer axis on the same algebra),
4739    /// [`crate::ast::QuoteForm`] (the four homoiconic prefix-wrappers
4740    /// — superset of THIS enum's two template markers via the 2-of-4
4741    /// projection [`crate::ast::QuoteForm::as_unquote_form`]), and the
4742    /// cross-crate `tatara-process` family (`ConditionKind::ALL`,
4743    /// `ProcessPhase::ALL`, `ProcessSignal::ALL`, `ChannelKind::ALL`,
4744    /// `IntentKind::ALL`, `LifetimeKind::ALL`, `RequestorKind::ALL`,
4745    /// `ReceiptKind::ALL`, …) every one of which paired its typed
4746    /// projection with `ALL` before this lift.
4747    ///
4748    /// Future consumers that compose against `ALL`: LSP / REPL
4749    /// completion for the operator-facing rendered template-marker
4750    /// (every `compile error in {prefix}…:` substring in `LispError`'s
4751    /// rendered diagnostics for a template-substitution rejection keys
4752    /// on this set's projection through [`Self::marker`]);
4753    /// `tatara-check` coverage assertions over which template markers
4754    /// reach a `NonSymbolUnquoteTarget` / `UnboundTemplateVar` arm at
4755    /// all — the typed sweep replaces a per-callsite vocabulary of two
4756    /// `&'static str` literals; any future audit-trail metric jointly
4757    /// labeled by [`Self::marker`] (e.g.
4758    /// `tatara_lisp_unbound_template_var_total{prefix=","}`) — the
4759    /// metric label set IS [`Self::ALL`] mapped through
4760    /// [`Self::marker`]; any future structural rewriter (typed
4761    /// analogue of MLIR's `op.walk<UnquoteFormOp>()`) that wants to
4762    /// sweep over every template marker in a typed sequence.
4763    pub const ALL: [Self; 2] = [Self::Unquote, Self::Splice];
4764
4765    /// Canonical `&'static str` template-marker bytes for the
4766    /// [`Self::Unquote`] substitution — aliases
4767    /// [`crate::ast::QuoteForm::UNQUOTE_PREFIX`] on the UnquoteForm ⊂
4768    /// QuoteForm 2-of-4 subset carving so the marker-level per-role
4769    /// bytes bind at ONE `pub const` on the parent superset's Unquote
4770    /// arm rather than at TWO sites (the per-role `pub const` AND a
4771    /// parallel inline literal). Per-role peer of `Self::Unquote` on
4772    /// the closed-set template-substitution algebra; consumers reach
4773    /// for `UnquoteForm::UNQUOTE_MARKER` when the caller has a variant
4774    /// in hand at compile time and wants the canonical diagnostic
4775    /// bytes without runtime dispatch through [`Self::marker`].
4776    ///
4777    /// Sibling posture to [`crate::ast::AtomKind::SYMBOL_LABEL`] et al
4778    /// (commit fc126b8) — both pin the invariant that a typed-subset
4779    /// enum's per-role bytes are structurally derived from the parent
4780    /// superset's per-role `pub const` via a `pub const = Parent::CONST`
4781    /// alias rather than through parallel literal-discipline sites.
4782    pub const UNQUOTE_MARKER: &'static str = crate::ast::QuoteForm::UNQUOTE_PREFIX;
4783
4784    /// Canonical `&'static str` template-marker bytes for the
4785    /// [`Self::Splice`] list-substitution — aliases
4786    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_PREFIX`] on the
4787    /// UnquoteForm ⊂ QuoteForm 2-of-4 subset carving. Per-role peer of
4788    /// `Self::Splice`; the `,@` two-byte prefix is the ONLY multi-char
4789    /// bytes payload on the UnquoteForm algebra — every other marker
4790    /// is a single-byte comma rendered as `&'static str`.
4791    pub const SPLICE_MARKER: &'static str = crate::ast::QuoteForm::UNQUOTE_SPLICE_PREFIX;
4792
4793    /// Closed-set forced-arity ALL array over the canonical template-
4794    /// marker `&'static str` bytes, in declaration order matching
4795    /// [`Self::ALL`] element-wise (pinned by
4796    /// `unquote_form_markers_align_with_all_by_index`). Sibling posture
4797    /// to [`crate::ast::QuoteForm::PREFIXES`] (`[&'static str; 4]` —
4798    /// the superset carving this UnquoteForm subset embeds into),
4799    /// [`crate::ast::AtomKind::LABELS`] (`[&'static str; 6]` — sibling
4800    /// subset-through-parent alias on the AtomKind ⊂ SexpShape
4801    /// carving), [`SexpShape::LABELS`] (`[&'static str; 12]`),
4802    /// [`ExpectedKwargShape::LABELS`] (`[&'static str; 7]`),
4803    /// [`KwargPathKind::LABELS`] (`[&'static str; 3]`),
4804    /// [`MacroDefHead::KEYWORDS`] (`[&'static str; 3]`),
4805    /// [`crate::ast::Atom::BOOL_LITERALS`] (`[&'static str; 2]`), and
4806    /// [`crate::macro_expand::MacroParams::LAMBDA_LIST_KEYWORDS`]
4807    /// (`[&'static str; 2]`) — every closed-set outer projection on
4808    /// the substrate that carries an `&'static str`-per-variant marker
4809    /// now pins its per-role canonical bytes at ONE `pub const` per
4810    /// role PLUS an ALL array for family-wide consumers.
4811    ///
4812    /// Pre-lift the two template-marker bytes had NO per-role primitive
4813    /// on this closed-set subset algebra — a consumer with an
4814    /// `UnquoteForm` variant in hand at compile time reaching for the
4815    /// canonical marker bytes had to spell
4816    /// `UnquoteForm::Splice.marker()` (runtime dispatch through the
4817    /// composition [`Self::to_quote_form`] +
4818    /// [`crate::ast::QuoteForm::prefix`]) OR reach across the algebra
4819    /// boundary into [`crate::ast::QuoteForm::UNQUOTE_SPLICE_PREFIX`]
4820    /// and re-derive the UnquoteForm ⊂ QuoteForm variant pairing at
4821    /// the call site. Post-lift the TWO canonical bytes bind at ONE
4822    /// `pub const` per role on the typed [`UnquoteForm`] algebra AND
4823    /// at [`Self::MARKERS`] as a family-wide forced-arity array — a
4824    /// future LSP / REPL completion bar keyed on
4825    /// `UnquoteForm::MARKERS`, a `tatara-check` coverage sweep over
4826    /// the template-marker arms of a `NonSymbolUnquoteTarget` /
4827    /// `UnboundTemplateVar` corpus, or a Sekiban audit-trail metric
4828    /// jointly labeled by the template marker
4829    /// (`tatara_lisp_unbound_template_var_total{prefix=","}`) reads
4830    /// through the typed constants on this subset algebra without
4831    /// re-deriving the 2-of-4 carving inline.
4832    ///
4833    /// Each entry is byte-for-byte identical to the corresponding
4834    /// [`crate::ast::QuoteForm`] Unquote / UnquoteSplice arm — an
4835    /// intentional cross-axis overlap pinned by
4836    /// `unquote_form_per_role_markers_alias_quote_form_per_role_prefixes_byte_for_byte`
4837    /// so a future marker rename on EITHER side (a
4838    /// [`crate::ast::QuoteForm`] `","` → `",."` drift, or an
4839    /// [`UnquoteForm`] rename that skips the alias) fails-loudly at
4840    /// the alias test rather than as a silent operator-facing
4841    /// vocabulary fracture. Adding a hypothetical third template-
4842    /// marker (e.g. a `,~` reverse-unquote, a `,?` conditional-unquote)
4843    /// extends [`Self::ALL`] AND [`Self::MARKERS`] AND adds ONE per-
4844    /// role `pub const` alias in lockstep — rustc's forced-arity check
4845    /// on the two `[_; N]` arrays fails compilation if EITHER ALL
4846    /// array grows without the other.
4847    ///
4848    /// Theory anchor: THEORY.md §III — the typescape; the two
4849    /// canonical template-marker bytes bind at ONE typed
4850    /// `[&'static str; 2]` array on the closed-set UnquoteForm algebra
4851    /// rather than at zero-primitive-on-this-subset-plus-two-inline-
4852    /// lookups scattered across the substrate. THEORY.md §V.1 —
4853    /// knowable platform; the family's cardinality becomes a TYPE-
4854    /// level constant on the substrate algebra rather than a per-
4855    /// consumer runtime dispatch through the composition. THEORY.md
4856    /// §VI.1 — generation over composition; the family-wide contract
4857    /// sweeps (alignment with `ALL`, pairwise disjointness, membership
4858    /// through [`Self::marker`]) emerge from the composition of TWO
4859    /// substrate primitives (this `pub const` array + the two per-role
4860    /// `pub const *_MARKER` aliases) rather than as per-variant inline
4861    /// assertions duplicated at each call site.
4862    pub const MARKERS: [&'static str; 2] = [Self::UNQUOTE_MARKER, Self::SPLICE_MARKER];
4863
4864    /// Canonical reader-lead `char` byte for BOTH [`Self::Unquote`] AND
4865    /// [`Self::Splice`] — aliases [`crate::ast::QuoteForm::UNQUOTE_LEAD`]
4866    /// (which is `','`) on the UnquoteForm ⊂ QuoteForm 2-of-4 subset
4867    /// carving so the reader-lead-char per-role bytes bind at ONE
4868    /// `pub const` on the parent superset's Unquote arm rather than at
4869    /// TWO sites (the per-role `pub const` AND a parallel inline `','`
4870    /// literal). Per-role peer of [`Self::Unquote`] on the closed-set
4871    /// template-substitution algebra; consumers reach for
4872    /// `UnquoteForm::UNQUOTE_LEAD` when the caller has a variant in
4873    /// hand at compile time and wants the canonical reader-entry lead
4874    /// char without runtime dispatch through
4875    /// `self.to_quote_form().lead_char()`.
4876    ///
4877    /// The (2-of-2 collapse) shape-asymmetry is load-bearing: BOTH
4878    /// UnquoteForm arms share the SAME lead char at
4879    /// [`crate::ast::QuoteForm::lead_char`]'s `Self::Unquote |
4880    /// Self::UnquoteSplice => Self::UNQUOTE_LEAD` merged arm — the
4881    /// reader's outer tokenizer dispatch selects between quote-family
4882    /// entry and every non-quote-family arm on lead char alone, with
4883    /// the `,`-vs-`,@` disambiguation falling out of the reader's
4884    /// second-char peek (`@` promotes to Splice) rather than at
4885    /// [`crate::ast::QuoteForm::from_lead_char`]'s decode. This
4886    /// UnquoteForm ⊂ QuoteForm subset's LEAD alias is
4887    /// SHAPE-ASYMMETRIC-COLLAPSE to `[Self::UNQUOTE_LEAD]` (a
4888    /// single-entry ALL — see [`Self::LEADS`]) because both subset
4889    /// arms project to the same superset lead byte, mirroring the
4890    /// parent's `[QUOTE_LEAD, QUASIQUOTE_LEAD, UNQUOTE_LEAD]` shape-
4891    /// asymmetric collapse (3 distinct leads for 4 arms).
4892    ///
4893    /// Sibling posture to [`Self::UNQUOTE_MARKER`] (commit 3640f76 —
4894    /// aliases the reader-punctuation prefix vocabulary `","`),
4895    /// [`Self::UNQUOTE_LABEL`] (commit da68af5 — aliases the
4896    /// substrate diagnostic-label vocabulary `"unquote"`),
4897    /// [`Self::UNQUOTE_IAC_FORGE_TAG`] (commit 6acab84 — aliases the
4898    /// iac-forge canonical-form vocabulary `"unquote"`), and
4899    /// [`Self::UNQUOTE_HASH_DISCRIMINATOR`] (commit eca4730 —
4900    /// aliases the outer-`Sexp` cache-key `u8` byte `5u8`) — this
4901    /// FIFTH per-role `pub const` axis closes the (reader-lead char,
4902    /// reader-prefix bytes, diagnostic label, iac-forge canonical-form
4903    /// tag, outer-`Sexp` cache-key byte) QUINTUPLE on the UnquoteForm
4904    /// subset algebra in lockstep with the same quintuple on the
4905    /// parent [`crate::ast::QuoteForm`] superset.
4906    pub const UNQUOTE_LEAD: char = crate::ast::QuoteForm::UNQUOTE_LEAD;
4907
4908    /// Closed-set forced-arity ALL array over the canonical
4909    /// reader-lead `char` bytes on the UnquoteForm ⊂ QuoteForm 2-of-4
4910    /// subset carving — SHAPE-ASYMMETRIC-COLLAPSE `[char; 1]` on this
4911    /// subset carving (both arms share the same `','` lead byte), peer
4912    /// to [`crate::ast::QuoteForm::LEADS`] (`[char; 3]` on the 4-arm
4913    /// superset carving, itself shape-asymmetric-collapse from 4 arms
4914    /// to 3 distinct leads because [`crate::ast::QuoteForm::Unquote`]
4915    /// and [`crate::ast::QuoteForm::UnquoteSplice`] share the same
4916    /// lead byte).
4917    ///
4918    /// Peer axis to [`Self::MARKERS`] on the SAME reader-vocabulary
4919    /// production surface: [`Self::MARKERS`] holds the arm-per-arm
4920    /// prefix bytes (`","`, `",@"` — cardinality matches [`Self::ALL`]
4921    /// at `[_; 2]`), and [`Self::LEADS`] holds the DISTINCT lead-byte
4922    /// sub-vocabulary the reader's outer tokenizer dispatch selects on
4923    /// (`","` alone — cardinality collapses from 2 arms to 1 distinct
4924    /// lead byte). The shape-asymmetric arities `[_; 2]` for MARKERS
4925    /// vs `[_; 1]` for LEADS ARE the shared-lead-char structural
4926    /// collapse invariant carried at the type-system level — a future
4927    /// third UnquoteForm arm (e.g. a hypothetical `,~` reverse-unquote
4928    /// binding a NEW lead char `~`) would extend BOTH [`Self::MARKERS`]
4929    /// to `[_; 3]` AND [`Self::LEADS`] to `[_; 2]` in lockstep, adding
4930    /// ONE per-role LEAD `pub const` alias.
4931    ///
4932    /// Sibling posture to [`crate::ast::QuoteForm::LEADS`] (the
4933    /// superset carving's own DISTINCT-lead-byte ALL array on the
4934    /// SAME reader-lead-vocabulary axis), [`Self::MARKERS`],
4935    /// [`Self::LABELS`], [`Self::IAC_FORGE_TAGS`], and
4936    /// [`Self::HASH_DISCRIMINATORS`] — every one a forced-arity ALL
4937    /// array on the SAME UnquoteForm closed-set algebra spanning ONE
4938    /// of the production byte-vocabularies the substrate carries per
4939    /// role.
4940    ///
4941    /// Pre-lift the sole distinct lead byte had NO per-role primitive
4942    /// on this closed-set subset algebra — a consumer wanting the
4943    /// UnquoteForm-only reader-lead-byte set had to spell the two-
4944    /// step composition `UnquoteForm::ALL.iter().map(|uf|
4945    /// uf.to_quote_form().lead_char()).collect()` (with a manual dedup
4946    /// downstream to project the 2-arm output onto its 1 distinct
4947    /// byte) OR reach across into
4948    /// [`crate::ast::QuoteForm::UNQUOTE_LEAD`] and re-derive the
4949    /// UnquoteForm ⊂ QuoteForm variant-share pairing at the call site.
4950    /// Post-lift the sole canonical byte binds at ONE `pub const` on
4951    /// the typed [`UnquoteForm`] algebra AND at [`Self::LEADS`] as a
4952    /// family-wide `[char; 1]` forced-arity array — a future
4953    /// substrate-facing reader-classifier keyed on the substitution-
4954    /// subset's lead-vocabulary specifically (a future LSP completion
4955    /// bar filtering to template-substitution-only entries, a
4956    /// syntax-highlighter's per-char classifier for the reader-entry
4957    /// template-marker subset, a `tatara-check` predicate
4958    /// `(check-substitution-subset-lead-partition …)` that verifies
4959    /// the subset's sole lead byte sits inside the parent
4960    /// [`crate::ast::QuoteForm::LEADS`] set) reads through the typed
4961    /// constants without re-deriving the 2-of-4 → 1-of-3 collapse
4962    /// inline.
4963    ///
4964    /// Theory anchor: THEORY.md §III — the typescape; the sole
4965    /// canonical distinct-lead byte on the substitution-subset
4966    /// carving binds at ONE typed `[char; 1]` array on the closed-set
4967    /// UnquoteForm algebra rather than as a zero-primitive-on-this-
4968    /// subset-plus-inline-lookup at each callsite. Closes the FIFTH
4969    /// per-role `pub const` axis on the UnquoteForm subset carving in
4970    /// lockstep with the same quintuple on [`crate::ast::QuoteForm`].
4971    /// THEORY.md §V.1 — knowable platform; the SHAPE-ASYMMETRIC-
4972    /// COLLAPSE distinct-lead-byte sub-vocabulary becomes load-bearing
4973    /// typed data on the substrate's substitution-subset closed set —
4974    /// the type-system carries the "both subset arms share ONE lead"
4975    /// invariant at the `[char; 1]` arity rather than as a comment on
4976    /// a lookup site. THEORY.md §VI.1 — generation over composition;
4977    /// the family-wide contract sweeps (alignment with the parent
4978    /// [`crate::ast::QuoteForm::LEADS`]'s subset image, cardinality
4979    /// containment `Self::LEADS.len() <= Self::ALL.len()` — the
4980    /// collapse witness) emerge from ONE substrate primitive plus the
4981    /// per-role LEAD alias rather than from per-consumer runtime
4982    /// dispatch through the two-step composition.
4983    pub const LEADS: [char; 1] = [Self::UNQUOTE_LEAD];
4984
4985    /// Canonical `&'static str` iac-forge canonical-form tag bytes for
4986    /// the [`Self::Unquote`] substitution — aliases
4987    /// [`crate::ast::QuoteForm::UNQUOTE_IAC_FORGE_TAG`] on the
4988    /// UnquoteForm ⊂ QuoteForm 2-of-4 subset carving so the
4989    /// iac-forge-tag-level per-role bytes bind at ONE `pub const` on
4990    /// the parent superset's Unquote arm rather than at TWO sites (the
4991    /// per-role `pub const` AND a parallel inline literal). Per-role
4992    /// peer of [`Self::Unquote`] on the closed-set template-
4993    /// substitution algebra; consumers reach for
4994    /// `UnquoteForm::UNQUOTE_IAC_FORGE_TAG` when the caller has a
4995    /// variant in hand at compile time and wants the canonical iac-
4996    /// forge interop-tag bytes without runtime dispatch through
4997    /// [`Self::iac_forge_tag`].
4998    ///
4999    /// Sibling posture to [`Self::UNQUOTE_MARKER`] (commit 3640f76) —
5000    /// both pin the invariant that a typed-subset enum's per-role
5001    /// bytes are structurally derived from the parent superset's
5002    /// per-role `pub const` via a `pub const = Parent::CONST` alias
5003    /// rather than through parallel literal-discipline sites. Where
5004    /// [`Self::UNQUOTE_MARKER`] aliases the reader-punctuation
5005    /// vocabulary (`","`) shared with the Lisp source-code surface,
5006    /// this constant aliases the iac-forge canonical-form vocabulary
5007    /// (`"unquote"`) shared with the cross-crate attestation surface
5008    /// — the two peer axes span the two production byte-vocabularies
5009    /// the parent [`crate::ast::QuoteForm`] closed set carries, and
5010    /// both now bind through the same aliased typed source of truth on
5011    /// this UnquoteForm subset.
5012    pub const UNQUOTE_IAC_FORGE_TAG: &'static str = crate::ast::QuoteForm::UNQUOTE_IAC_FORGE_TAG;
5013
5014    /// Canonical `&'static str` iac-forge canonical-form tag bytes for
5015    /// the [`Self::Splice`] list-substitution — aliases
5016    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_IAC_FORGE_TAG`] on the
5017    /// UnquoteForm ⊂ QuoteForm 2-of-4 subset carving. Per-role peer of
5018    /// [`Self::Splice`]; the `"unquote-splicing"` bytes are the ONLY
5019    /// hyphenated multi-syllable canonical-form tag on the UnquoteForm
5020    /// algebra — the other iac-forge tag is a single word.
5021    ///
5022    /// INTENTIONALLY diverges from [`crate::error::SexpShape::label`]'s
5023    /// splice arm (which renders `"unquote-splice"` for the diagnostic
5024    /// surface) — the iac-forge canonical-form REQUIRES the
5025    /// `unquote-splicing` spelling, while the substrate's operator-
5026    /// facing diagnostic surface renders the shorter idiom. The typed
5027    /// per-role `pub const` documents the divergence structurally: a
5028    /// future "consolidation" PR that homogenizes the two axes would
5029    /// have to touch this constant explicitly, surfacing the boundary-
5030    /// distinct invariant at code-review time rather than silently.
5031    pub const SPLICE_IAC_FORGE_TAG: &'static str =
5032        crate::ast::QuoteForm::UNQUOTE_SPLICE_IAC_FORGE_TAG;
5033
5034    /// Closed-set forced-arity ALL array over the canonical iac-forge
5035    /// canonical-form tag `&'static str` bytes, in declaration order
5036    /// matching [`Self::ALL`] element-wise (pinned by
5037    /// `unquote_form_iac_forge_tags_align_with_all_by_index`). Sibling
5038    /// posture to [`Self::MARKERS`] (`[&'static str; 2]` on the reader-
5039    /// punctuation axis of the SAME [`UnquoteForm`] closed set — commit
5040    /// 3640f76) and to [`crate::ast::QuoteForm::IAC_FORGE_TAGS`]
5041    /// (`[&'static str; 4]` — the superset carving this UnquoteForm
5042    /// subset embeds into on the SAME iac-forge canonical-form axis).
5043    /// The (canonical iac-forge tag) axis + the (canonical reader
5044    /// marker) axis together span the two production byte-vocabularies
5045    /// the [`UnquoteForm`] closed set carries — [`Self::MARKERS`] holds
5046    /// the Lisp source-code prefixes (`","`, `",@"`) the reader
5047    /// tokenizes on, [`Self::IAC_FORGE_TAGS`] holds the cross-crate
5048    /// canonical-form tag strings (`"unquote"`, `"unquote-splicing"`)
5049    /// the iac-forge interop layer round-trips through.
5050    ///
5051    /// Pre-lift the two iac-forge tag bytes had NO per-role primitive
5052    /// on this closed-set subset algebra — a consumer with an
5053    /// [`UnquoteForm`] variant in hand at compile time reaching for
5054    /// the canonical tag bytes had to spell
5055    /// `UnquoteForm::Splice.iac_forge_tag()` (runtime dispatch through
5056    /// the composition [`Self::to_quote_form`] +
5057    /// [`crate::ast::QuoteForm::iac_forge_tag`]) OR reach across the
5058    /// algebra boundary into
5059    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_IAC_FORGE_TAG`] and
5060    /// re-derive the UnquoteForm ⊂ QuoteForm variant pairing at the
5061    /// call site. Post-lift the TWO canonical bytes bind at ONE
5062    /// `pub const` per role on the typed [`UnquoteForm`] algebra AND
5063    /// at [`Self::IAC_FORGE_TAGS`] as a family-wide forced-arity array
5064    /// — a future LSP / REPL completion bar keyed on
5065    /// `UnquoteForm::IAC_FORGE_TAGS`, a `tatara-check` coverage sweep
5066    /// over the template-marker arms of an attestation-payload corpus,
5067    /// or a Sekiban audit-trail metric jointly labeled by the iac-
5068    /// forge tag (`tatara_lisp_iac_forge_tag_total{tag="unquote"}`)
5069    /// reads through the typed constants on this subset algebra
5070    /// without re-deriving the 2-of-4 carving inline.
5071    ///
5072    /// Each entry is byte-for-byte identical to the corresponding
5073    /// [`crate::ast::QuoteForm`] Unquote / UnquoteSplice arm — an
5074    /// intentional cross-axis overlap pinned by
5075    /// `unquote_form_per_role_iac_forge_tags_alias_quote_form_per_role_iac_forge_tags_byte_for_byte`
5076    /// so a future canonical-form tag rename on EITHER side (a
5077    /// [`crate::ast::QuoteForm`] `"unquote"` → `"unquote-value"` drift,
5078    /// or an [`UnquoteForm`] rename that skips the alias) fails-loudly
5079    /// at the alias test rather than as a silent cross-crate
5080    /// canonical-form vocabulary fracture that mis-hashes BLAKE3
5081    /// attestation keys. Adding a hypothetical third template-marker
5082    /// (e.g. a `,~` reverse-unquote with its own canonical `"reverse-
5083    /// unquote"` tag, a `,?` conditional-unquote with its own tag)
5084    /// extends [`Self::ALL`] AND [`Self::IAC_FORGE_TAGS`] AND adds ONE
5085    /// per-role `pub const` alias in lockstep — rustc's forced-arity
5086    /// check on the two `[_; N]` arrays fails compilation if EITHER
5087    /// ALL array grows without the other.
5088    ///
5089    /// Theory anchor: THEORY.md §III — the typescape; the two
5090    /// canonical iac-forge tag bytes bind at ONE typed
5091    /// `[&'static str; 2]` array on the closed-set UnquoteForm algebra
5092    /// rather than at zero-primitive-on-this-subset-plus-two-inline-
5093    /// lookups scattered across the substrate. THEORY.md §V.1 —
5094    /// knowable platform; the family's cardinality becomes a TYPE-
5095    /// level constant on the substrate algebra rather than a per-
5096    /// consumer runtime dispatch through the composition. THEORY.md
5097    /// §V.3 — three-pillar attestation; the canonical iac-forge tag
5098    /// bytes are the surface the substrate's cross-crate `intent_hash`
5099    /// pillar composes over for every substitution-subset arm of a
5100    /// homoiconic template AST — binding the two bytes on the typed
5101    /// algebra makes attestation-key drift a compile error rather than
5102    /// a silent BLAKE3 mis-hash. THEORY.md §VI.1 — generation over
5103    /// composition; the family-wide contract sweeps (alignment with
5104    /// `ALL`, pairwise disjointness, membership through
5105    /// [`Self::iac_forge_tag`]) emerge from the composition of TWO
5106    /// substrate primitives (this `pub const` array + the two per-role
5107    /// `pub const *_IAC_FORGE_TAG` aliases) rather than as per-variant
5108    /// inline assertions duplicated at each call site.
5109    pub const IAC_FORGE_TAGS: [&'static str; 2] =
5110        [Self::UNQUOTE_IAC_FORGE_TAG, Self::SPLICE_IAC_FORGE_TAG];
5111
5112    /// Canonical `&'static str` diagnostic label bytes for the
5113    /// [`Self::Unquote`] substitution — aliases
5114    /// [`crate::ast::QuoteForm::UNQUOTE_LABEL`] on the UnquoteForm ⊂
5115    /// QuoteForm 2-of-4 subset carving so the label-level per-role
5116    /// bytes bind at ONE `pub const` on the parent superset's Unquote
5117    /// arm (which itself aliases [`SexpShape::UNQUOTE_LABEL`] on the
5118    /// QuoteForm ⊂ SexpShape 4-of-12 subset carving) rather than at
5119    /// multiple sites (the per-role `pub const` PLUS a parallel inline
5120    /// literal). Per-role peer of [`Self::Unquote`] on the closed-set
5121    /// template-substitution algebra; consumers reach for
5122    /// `UnquoteForm::UNQUOTE_LABEL` when the caller has a variant in
5123    /// hand at compile time and wants the canonical diagnostic bytes
5124    /// without runtime dispatch through [`Self::label`].
5125    ///
5126    /// Sibling posture to [`Self::UNQUOTE_MARKER`] (commit 3640f76 —
5127    /// aliases the reader-punctuation vocabulary `","`) and to
5128    /// [`Self::UNQUOTE_IAC_FORGE_TAG`] (commit 6acab84 — aliases the
5129    /// iac-forge canonical-form vocabulary `"unquote"`) — this THIRD
5130    /// per-role `pub const` axis closes the (reader punctuation,
5131    /// diagnostic label, iac-forge canonical-form) triple on the
5132    /// UnquoteForm subset algebra in lockstep with the same triple
5133    /// on the parent [`crate::ast::QuoteForm`] superset. At the
5134    /// Unquote arm the diagnostic label AND the iac-forge tag AND the
5135    /// substrate-facing `SexpShape::UNQUOTE_LABEL` all agree
5136    /// byte-for-byte on `"unquote"`; the divergence axis lives at
5137    /// [`Self::SPLICE_LABEL`] (`"unquote-splice"`) vs
5138    /// [`Self::SPLICE_IAC_FORGE_TAG`] (`"unquote-splicing"`).
5139    pub const UNQUOTE_LABEL: &'static str = crate::ast::QuoteForm::UNQUOTE_LABEL;
5140
5141    /// Canonical `&'static str` diagnostic label bytes for the
5142    /// [`Self::Splice`] list-substitution — aliases
5143    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_LABEL`] on the
5144    /// UnquoteForm ⊂ QuoteForm 2-of-4 subset carving. Per-role peer of
5145    /// [`Self::Splice`]. The `"unquote-splice"` short label matches
5146    /// [`SexpShape::UNQUOTE_SPLICE_LABEL`] byte-for-byte through the
5147    /// alias chain and diverges INTENTIONALLY from
5148    /// [`Self::SPLICE_IAC_FORGE_TAG`] (`"unquote-splicing"`) — the two
5149    /// projections key the SAME closed set on TWO distinct boundaries
5150    /// (substrate diagnostic surface vs cross-crate Common-Lisp
5151    /// canonical form). See [`Self::UNQUOTE_LABEL`] for the alias-
5152    /// chain shape both siblings share.
5153    pub const SPLICE_LABEL: &'static str = crate::ast::QuoteForm::UNQUOTE_SPLICE_LABEL;
5154
5155    /// Closed-set forced-arity ALL array over the canonical diagnostic
5156    /// label `&'static str` bytes, in declaration order matching
5157    /// [`Self::ALL`] element-wise (pinned by
5158    /// `unquote_form_labels_align_with_all_by_index`). Sibling posture
5159    /// to [`Self::MARKERS`] (`[&'static str; 2]` — reader-punctuation
5160    /// axis) and [`Self::IAC_FORGE_TAGS`] (`[&'static str; 2]` — iac-
5161    /// forge canonical-form axis) on the SAME UnquoteForm closed set,
5162    /// and to [`crate::ast::QuoteForm::LABELS`] (`[&'static str; 4]` —
5163    /// the superset carving this UnquoteForm subset embeds into on the
5164    /// SAME diagnostic-label axis). The (diagnostic label) axis + the
5165    /// (reader punctuation) axis + the (iac-forge canonical-form)
5166    /// axis together span the THREE production byte-vocabularies the
5167    /// [`UnquoteForm`] closed set carries — [`Self::MARKERS`] holds
5168    /// the Lisp source-code prefixes (`","`, `",@"`), [`Self::LABELS`]
5169    /// holds the substrate diagnostic labels (`"unquote"`,
5170    /// `"unquote-splice"`), [`Self::IAC_FORGE_TAGS`] holds the cross-
5171    /// crate canonical-form tag strings (`"unquote"`, `"unquote-
5172    /// splicing"`).
5173    ///
5174    /// Pre-lift the two diagnostic-label bytes had NO per-role
5175    /// primitive on this closed-set subset algebra — a consumer with
5176    /// an [`UnquoteForm`] variant in hand at compile time reaching for
5177    /// the canonical diagnostic bytes had to spell the two-step
5178    /// composition `uf.to_quote_form().label()` OR reach across the
5179    /// algebra boundary into
5180    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_LABEL`] and re-derive
5181    /// the UnquoteForm ⊂ QuoteForm variant pairing at the call site.
5182    /// Post-lift the TWO canonical bytes bind at ONE `pub const` per
5183    /// role on the typed [`UnquoteForm`] algebra AND at
5184    /// [`Self::LABELS`] as a family-wide forced-arity array — a
5185    /// future LSP / REPL completion bar keyed on
5186    /// `UnquoteForm::LABELS`, a `tatara-check` coverage sweep over
5187    /// the template-marker arms of a diagnostic corpus, or a Sekiban
5188    /// audit-trail metric jointly labeled by the diagnostic label
5189    /// (`tatara_lisp_unbound_template_var_total{label="unquote"}`)
5190    /// reads through the typed constants on this subset algebra
5191    /// without re-deriving the 2-of-4 carving inline.
5192    ///
5193    /// Each entry is byte-for-byte identical to the corresponding
5194    /// [`crate::ast::QuoteForm`] Unquote / UnquoteSplice arm — an
5195    /// intentional cross-axis overlap pinned by
5196    /// `unquote_form_per_role_labels_alias_quote_form_per_role_labels_byte_for_byte`
5197    /// so a future label rename on EITHER side (a
5198    /// [`crate::ast::QuoteForm`] `"unquote"` → `"substitute"` drift,
5199    /// or an [`UnquoteForm`] rename that skips the alias) fails-
5200    /// loudly at the alias test rather than as a silent operator-
5201    /// facing vocabulary fracture. Adding a hypothetical third
5202    /// template-marker (e.g. a `,~` reverse-unquote) extends
5203    /// [`Self::ALL`] AND [`Self::LABELS`] AND adds ONE per-role
5204    /// `pub const` alias in lockstep — rustc's forced-arity check on
5205    /// the two `[_; N]` arrays fails compilation if EITHER array
5206    /// grows without the other.
5207    ///
5208    /// Theory anchor: THEORY.md §III — the typescape; the two
5209    /// canonical diagnostic label bytes bind at ONE typed
5210    /// `[&'static str; 2]` array on the closed-set UnquoteForm algebra
5211    /// rather than at zero-primitive-on-this-subset-plus-two-inline-
5212    /// lookups scattered across the substrate. Closes the THIRD
5213    /// per-role `pub const` axis on the UnquoteForm subset carving in
5214    /// lockstep with the same triple on [`crate::ast::QuoteForm`].
5215    /// THEORY.md §V.1 — knowable platform; the family's cardinality
5216    /// becomes a TYPE-level constant on the substrate algebra rather
5217    /// than a per-consumer runtime dispatch through the composition.
5218    /// THEORY.md §VI.1 — generation over composition; the family-wide
5219    /// contract sweeps (alignment with `ALL`, pairwise disjointness,
5220    /// membership through [`Self::label`]) emerge from the composition
5221    /// of TWO substrate primitives (this `pub const` array + the two
5222    /// per-role `pub const *_LABEL` aliases) rather than as per-
5223    /// variant inline assertions duplicated at each call site.
5224    /// THEORY.md §II.1 invariant 5 — composition preserves proofs;
5225    /// the alias-chain composition law `UnquoteForm::LABELS[i] ==
5226    /// UnquoteForm::ALL[i].to_quote_form().label()` binds the family-
5227    /// wide array to the composition through [`Self::to_quote_form`] +
5228    /// [`crate::ast::QuoteForm::label`] at rustc time.
5229    pub const LABELS: [&'static str; 2] = [Self::UNQUOTE_LABEL, Self::SPLICE_LABEL];
5230
5231    /// Canonical `u8` outer-`Sexp` cache-key byte for the
5232    /// [`Self::Unquote`] substitution — `5`. Aliases
5233    /// [`crate::ast::QuoteForm::UNQUOTE_HASH_DISCRIMINATOR`] on the
5234    /// UnquoteForm ⊂ QuoteForm 2-of-4 subset carving so the byte-
5235    /// discriminator-level per-role bytes bind at ONE
5236    /// `pub(crate) const` on the parent superset's Unquote arm rather
5237    /// than at TWO sites (the per-role `pub(crate) const` AND a
5238    /// parallel inline literal). Per-role peer of [`Self::Unquote`] on
5239    /// the closed-set template-substitution algebra; consumers reach
5240    /// for `UnquoteForm::UNQUOTE_HASH_DISCRIMINATOR` when the caller
5241    /// has a variant in hand at compile time and wants the canonical
5242    /// cache-key byte without runtime dispatch through
5243    /// [`Self::hash_discriminator`] (which itself composes through
5244    /// [`Self::to_quote_form`] +
5245    /// [`crate::ast::QuoteForm::hash_discriminator`]).
5246    ///
5247    /// Sibling posture to [`Self::UNQUOTE_MARKER`] (commit 3640f76 —
5248    /// aliases the reader-punctuation vocabulary `","`),
5249    /// [`Self::UNQUOTE_IAC_FORGE_TAG`] (commit 6acab84 — aliases the
5250    /// iac-forge canonical-form vocabulary `"unquote"`), and
5251    /// [`Self::UNQUOTE_LABEL`] (commit da68af5 — aliases the substrate
5252    /// diagnostic-label vocabulary `"unquote"`) — this FOURTH per-role
5253    /// `pub const` axis closes the (reader punctuation, iac-forge
5254    /// canonical-form, diagnostic label, outer-`Sexp` cache-key byte)
5255    /// quadruple on the UnquoteForm subset algebra in lockstep with
5256    /// the same quadruple on the parent [`crate::ast::QuoteForm`]
5257    /// superset. The three prior axes span the three `&'static str`
5258    /// production vocabularies the substrate carries at every quote-
5259    /// family arm; this fourth axis spans the ONE `u8` outer-`Sexp`
5260    /// cache-key vocabulary the substrate's
5261    /// [`Hash for Sexp`](crate::ast::Sexp) body composes over — so
5262    /// every substrate-surface byte the parent [`crate::ast::QuoteForm`]
5263    /// superset carries per role now has a matching per-role
5264    /// `pub const` on this substitution subset.
5265    ///
5266    /// `pub(crate)` because the byte-discriminator surface is an
5267    /// implementation detail of the substrate's
5268    /// [`Hash for Sexp`](crate::ast::Sexp) cache-key contract; exposing
5269    /// it publicly would leak the cache-key shape through the API
5270    /// without enabling any external consumer the three public
5271    /// `pub const` axes ([`Self::UNQUOTE_MARKER`],
5272    /// [`Self::UNQUOTE_IAC_FORGE_TAG`], [`Self::UNQUOTE_LABEL`]) don't
5273    /// already serve. Same posture as
5274    /// [`crate::ast::QuoteForm::UNQUOTE_HASH_DISCRIMINATOR`],
5275    /// [`crate::ast::AtomKind::SYMBOL_HASH_DISCRIMINATOR`],
5276    /// [`StructuralKind::NIL_HASH_DISCRIMINATOR`], and every other
5277    /// per-role `*_HASH_DISCRIMINATOR` `pub const` on the substrate's
5278    /// typed-shape family — every one is crate-private.
5279    pub(crate) const UNQUOTE_HASH_DISCRIMINATOR: u8 =
5280        crate::ast::QuoteForm::UNQUOTE_HASH_DISCRIMINATOR;
5281
5282    /// Canonical `u8` outer-`Sexp` cache-key byte for the
5283    /// [`Self::Splice`] list-substitution — `6`. Aliases
5284    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_HASH_DISCRIMINATOR`] on
5285    /// the UnquoteForm ⊂ QuoteForm 2-of-4 subset carving. Per-role
5286    /// peer of [`Self::Splice`]. The gap-free `{5, 6}` partition on
5287    /// the substitution-subset carving is load-bearing: the substrate's
5288    /// [`Hash for Sexp`](crate::ast::Sexp) body reserves `{0, 2}` for
5289    /// [`StructuralKind`], `{1}` for the outer [`crate::ast::Sexp::Atom`]
5290    /// carve, `{3, 4}` for the non-substitution quote-family arms
5291    /// ([`crate::ast::QuoteForm::Quote`],
5292    /// [`crate::ast::QuoteForm::Quasiquote`]), and `{5, 6}` for this
5293    /// substitution subset — a cache-key collision would leak macro-
5294    /// expansion cache hits across carvings. See
5295    /// [`Self::UNQUOTE_HASH_DISCRIMINATOR`] for the alias-chain shape
5296    /// both siblings share.
5297    pub(crate) const SPLICE_HASH_DISCRIMINATOR: u8 =
5298        crate::ast::QuoteForm::UNQUOTE_SPLICE_HASH_DISCRIMINATOR;
5299
5300    /// Closed-set forced-arity ALL array over the canonical outer-
5301    /// `Sexp` cache-key `u8` bytes, in declaration order matching
5302    /// [`Self::ALL`] element-wise (pinned by
5303    /// `unquote_form_hash_discriminators_align_with_all_by_index`).
5304    /// Sibling posture to [`Self::MARKERS`] (`[&'static str; 2]` —
5305    /// reader-punctuation axis), [`Self::IAC_FORGE_TAGS`]
5306    /// (`[&'static str; 2]` — iac-forge canonical-form axis), and
5307    /// [`Self::LABELS`] (`[&'static str; 2]` — diagnostic-label axis)
5308    /// on the SAME UnquoteForm closed set, and to
5309    /// [`crate::ast::QuoteForm::HASH_DISCRIMINATORS`] (`[u8; 4]` —
5310    /// the superset carving this UnquoteForm subset embeds into on
5311    /// the SAME outer-`Sexp` cache-key axis). Every closed-set
5312    /// carving of the substrate's outer-`Sexp` cache-key partition
5313    /// now pins its per-role canonical cache-key bytes at ONE
5314    /// `pub(crate) const` per role PLUS an ALL array for family-wide
5315    /// consumers: [`Self::HASH_DISCRIMINATORS`] (`[u8; 2]` —
5316    /// substitution-subset), [`StructuralKind::HASH_DISCRIMINATORS`]
5317    /// (`[u8; 2]` — structural-residual),
5318    /// [`crate::ast::AtomKind::HASH_DISCRIMINATORS`] (`[u8; 6]` —
5319    /// atomic-payload nested inner carving),
5320    /// [`crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR`] (scalar
5321    /// `1u8` — atomic-payload outer marker),
5322    /// [`crate::ast::QuoteForm::HASH_DISCRIMINATORS`] (`[u8; 4]` —
5323    /// quote-family), and
5324    /// [`SexpShape::HASH_DISCRIMINATORS`] (`[u8; 12]` — full outer
5325    /// twelve-arm shape).
5326    ///
5327    /// Pre-lift the two cache-key bytes had NO per-role primitive on
5328    /// this closed-set subset algebra — a consumer with an
5329    /// [`UnquoteForm`] variant in hand at compile time reaching for
5330    /// the canonical cache-key byte had to spell the two-step
5331    /// composition `uf.to_quote_form().hash_discriminator()` OR reach
5332    /// across the algebra boundary into
5333    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_HASH_DISCRIMINATOR`]
5334    /// and re-derive the UnquoteForm ⊂ QuoteForm variant pairing at
5335    /// the call site. Post-lift the TWO canonical bytes bind at ONE
5336    /// `pub(crate) const` per role on the typed [`UnquoteForm`]
5337    /// algebra AND at [`Self::HASH_DISCRIMINATORS`] as a family-wide
5338    /// forced-arity array — a future
5339    /// [`crate::macro_expand::Expander`] cache-invalidation predicate
5340    /// that decides on the 2-of-4 substitution-subset only, a future
5341    /// `tatara-check` predicate `(check-substitution-subset-cache-
5342    /// key-partition-injective …)` that verifies the substitution-
5343    /// subset carving's cache-key partition sits inside `{5, 6}`, a
5344    /// future `TypedRewriter<UnquoteFormOp>` sweep zipping ALL /
5345    /// LABELS / MARKERS / IAC_FORGE_TAGS / HASH_DISCRIMINATORS in
5346    /// lockstep for a family-wide (variant, label, marker, tag, byte)
5347    /// quintuple render, or a Sekiban audit-trail metric jointly
5348    /// labeled by the substitution-subset cache-key partition read
5349    /// through the typed constants on this subset algebra without
5350    /// re-deriving the 2-of-4 carving inline.
5351    ///
5352    /// Each entry is byte-for-byte identical to the corresponding
5353    /// [`crate::ast::QuoteForm`] Unquote / UnquoteSplice arm — an
5354    /// intentional cross-axis overlap pinned by
5355    /// `unquote_form_per_role_hash_discriminators_alias_quote_form_per_role_hash_discriminators_byte_for_byte`
5356    /// so a future cache-key byte reshuffling on EITHER side (a
5357    /// [`crate::ast::QuoteForm`] `5u8` → `7u8` drift, or an
5358    /// [`UnquoteForm`] rename that skips the alias) fails-loudly at
5359    /// the alias test rather than as a silent macro-expansion cache
5360    /// mis-hash. Adding a hypothetical third template-marker (e.g. a
5361    /// `,~` reverse-unquote) extends [`Self::ALL`] AND
5362    /// [`Self::HASH_DISCRIMINATORS`] AND adds ONE per-role
5363    /// `pub(crate) const` alias in lockstep — rustc's forced-arity
5364    /// check on the two `[_; N]` arrays fails compilation if EITHER
5365    /// array grows without the other.
5366    ///
5367    /// Theory anchor: THEORY.md §III — the typescape; the two
5368    /// canonical cache-key bytes bind at ONE typed `[u8; 2]` array
5369    /// on the closed-set UnquoteForm algebra rather than at zero-
5370    /// primitive-on-this-subset-plus-two-inline-lookups scattered
5371    /// across the substrate. Closes the FOURTH per-role `pub const`
5372    /// axis on the UnquoteForm subset carving in lockstep with the
5373    /// same quadruple on [`crate::ast::QuoteForm`]. THEORY.md §V.1 —
5374    /// knowable platform; the family's cardinality becomes a TYPE-
5375    /// level constant on the substrate algebra rather than a per-
5376    /// consumer runtime dispatch through the composition. THEORY.md
5377    /// §V.3 — three-pillar attestation; the cache-key partition is
5378    /// the substrate's outer [`Sexp`](crate::ast::Sexp) `intent_hash`
5379    /// composition axis for every substitution-subset arm — binding
5380    /// the two bytes on the typed algebra makes attestation-key
5381    /// drift a compile error rather than a silent BLAKE3 mis-hash.
5382    /// THEORY.md §VI.1 — generation over composition; the family-
5383    /// wide contract sweeps (alignment with `ALL`, pairwise
5384    /// disjointness, membership through
5385    /// [`Self::hash_discriminator`]) emerge from the composition of
5386    /// TWO substrate primitives (this `pub(crate) const` array + the
5387    /// two per-role `pub(crate) const *_HASH_DISCRIMINATOR` aliases)
5388    /// rather than as per-variant inline assertions duplicated at
5389    /// each call site. THEORY.md §II.1 invariant 5 — composition
5390    /// preserves proofs; the alias-chain composition law
5391    /// `UnquoteForm::HASH_DISCRIMINATORS[i] ==
5392    /// UnquoteForm::ALL[i].to_quote_form().hash_discriminator()`
5393    /// binds the family-wide array to the composition through
5394    /// [`Self::to_quote_form`] +
5395    /// [`crate::ast::QuoteForm::hash_discriminator`] at rustc time.
5396    ///
5397    /// The `#[allow(dead_code)]` posture matches every sibling
5398    /// `HASH_DISCRIMINATORS` array on the substrate's typed-shape
5399    /// family: the substrate's current
5400    /// [`Hash for Sexp`](crate::ast::Sexp) body composes through the
5401    /// four-arm superset's [`crate::ast::QuoteForm::hash_discriminator`]
5402    /// projection rather than splitting the dispatch into
5403    /// (2 non-substitution + 2 substitution) arms — so no non-test
5404    /// caller currently reaches THIS subset-algebra array. The lift
5405    /// lands the substrate primitive so future consumers keyed on
5406    /// the substitution-subset carving specifically bind to ONE
5407    /// `[u8; 2]` primitive rather than re-deriving the array inline
5408    /// per callsite.
5409    #[allow(dead_code)]
5410    pub(crate) const HASH_DISCRIMINATORS: [u8; 2] = [
5411        Self::UNQUOTE_HASH_DISCRIMINATOR,
5412        Self::SPLICE_HASH_DISCRIMINATOR,
5413    ];
5414
5415    /// Closed-set forced-arity ALL array over the reader's promotion
5416    /// triples on the two-variant substitution-subset lattice —
5417    /// `(Self::Unquote, '@', Self::Splice)` for the ONE `,` → `,@`
5418    /// promotion the substrate's reader `promote_via_next_char` arm
5419    /// depends on at the parent superset level. Its forced-arity `1`
5420    /// is INTENTIONAL and load-bearing on the subset carving: only
5421    /// [`Self::Splice`] carries a two-char rendered marker on the
5422    /// substitution subset (its `,@` prefix decomposes into `,` +
5423    /// [`crate::ast::QuoteForm::SPLICE_DISCRIMINATOR`]), so the
5424    /// promotion table's Some-arm is the singleton `{(Unquote, '@') →
5425    /// Splice}` on the `Self × char → Option<Self>` product — the
5426    /// two-of-four substitution-subset peer of the closed set
5427    /// [`crate::ast::QuoteForm::PROMOTIONS`] `[(head, char, promoted);
5428    /// 1]` carries on the four-arm quote-family superset.
5429    ///
5430    /// Cross-algebra composition law (pinned by
5431    /// `unquote_form_promotions_align_with_quote_form_promotions_by_projection`):
5432    /// for the ONE entry `(head, disc, promoted)` in
5433    /// [`Self::PROMOTIONS`],
5434    /// `(head.to_quote_form(), disc, promoted.to_quote_form())` equals
5435    /// [`crate::ast::QuoteForm::PROMOTIONS`]`[0]` byte-for-byte. The
5436    /// subset's promotion triple embeds into the parent superset's
5437    /// promotion triple through the pre-existing 2-of-4 subset →
5438    /// superset projection [`Self::to_quote_form`] composed with the
5439    /// identity on the middle discriminator `char` (which is a
5440    /// [`crate::ast::QuoteForm::SPLICE_DISCRIMINATOR`] alias direct at
5441    /// the constant site). A regression that drifts the subset's
5442    /// triple silently promotes a phantom pairing (e.g. re-inlines
5443    /// `Self::Splice` at the head column) AND fails the composition
5444    /// pin at rustc / test time rather than at silent tokenizer drift
5445    /// where the reader's `,@xs` sequence tokenizes to a phantom
5446    /// substitution-subset marker.
5447    ///
5448    /// Sibling posture to the closed-set forced-arity ALL arrays
5449    /// across the substrate's [`UnquoteForm`] subset algebra:
5450    /// [`Self::ALL`] (`[Self; 2]` — the closed set of variants),
5451    /// [`Self::MARKERS`] (`[&'static str; 2]` — the reader-marker
5452    /// axis, aliased through
5453    /// [`crate::ast::QuoteForm::UNQUOTE_PREFIX`] /
5454    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_PREFIX`]),
5455    /// [`Self::IAC_FORGE_TAGS`] (`[&'static str; 2]` — the iac-forge
5456    /// canonical-form tag axis, aliased through
5457    /// [`crate::ast::QuoteForm::UNQUOTE_IAC_FORGE_TAG`] /
5458    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_IAC_FORGE_TAG`]),
5459    /// [`Self::LABELS`] (`[&'static str; 2]` — the diagnostic-label
5460    /// axis, aliased through
5461    /// [`crate::ast::QuoteForm::UNQUOTE_LABEL`] /
5462    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_LABEL`]), and
5463    /// [`Self::HASH_DISCRIMINATORS`] (`[u8; 2]` — the outer-`Sexp`
5464    /// cache-key byte axis, aliased through
5465    /// [`crate::ast::QuoteForm::UNQUOTE_HASH_DISCRIMINATOR`] /
5466    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_HASH_DISCRIMINATOR`]).
5467    /// This lift adds the FIFTH per-family axis on the substitution-
5468    /// subset algebra — the (head, disc, promoted) triple axis on
5469    /// the closed-set promotion product. Each of the five axes now
5470    /// pins its per-role canonical data at ONE `pub const` per role
5471    /// (or ONE `pub const` triple on the promotion-asymmetric single-
5472    /// arm collapse) PLUS an ALL array for family-wide consumers.
5473    ///
5474    /// The middle discriminator column aliases
5475    /// [`crate::ast::QuoteForm::SPLICE_DISCRIMINATOR`] direct at the
5476    /// constant site — the substitution subset does NOT introduce a
5477    /// separate `SPLICE_DISCRIMINATOR` const because the discriminator
5478    /// byte is shape-uniform across the parent superset (it is the
5479    /// SAME `'@'` byte that promotes at every quote-family arm on the
5480    /// parent) and lives at ONE canonical site in `ast.rs`. Alias
5481    /// direction runs from the [`UnquoteForm`] subset INTO the parent
5482    /// [`crate::ast::QuoteForm`] superset — the SAME direction the
5483    /// four prior per-family axes take on this subset (MARKERS,
5484    /// IAC_FORGE_TAGS, LABELS, HASH_DISCRIMINATORS).
5485    ///
5486    /// `pub(crate)` because the promotion algebra is an implementation
5487    /// detail of the substrate's reader — exposing it publicly would
5488    /// leak the promotion-table shape through the API without
5489    /// enabling any external consumer the parent superset's public
5490    /// [`crate::ast::QuoteForm::promote_via_next_char`] projection
5491    /// doesn't already serve on the four-arm surface. Same visibility
5492    /// rationale as [`Self::HASH_DISCRIMINATORS`] on the sibling
5493    /// cache-key axis.
5494    ///
5495    /// The `#[allow(dead_code)]` posture matches
5496    /// [`Self::HASH_DISCRIMINATORS`] AND
5497    /// [`crate::ast::QuoteForm::PROMOTIONS`]: the substrate's current
5498    /// reader dispatches through the parent superset's
5499    /// [`crate::ast::QuoteForm::promote_via_next_char`] arm rather
5500    /// than a UnquoteForm-subset promotion body (which does not
5501    /// exist on this subset — the two-of-four carving does not carry
5502    /// a `promote_via_next_char` method because the reader boundary
5503    /// is the four-arm parent), so no non-test caller currently
5504    /// reaches this substitution-subset promotion array directly.
5505    /// The lift lands the substrate primitive so future consumers
5506    /// keyed on the substitution-subset carving specifically (a
5507    /// future `TypedRewriter<UnquoteFormOp>` sweep zipping ALL /
5508    /// MARKERS / IAC_FORGE_TAGS / LABELS / HASH_DISCRIMINATORS /
5509    /// PROMOTIONS in lockstep for a family-wide render, a future
5510    /// `tatara-check` predicate
5511    /// `(check-substitution-subset-promotion-algebra-injective …)`
5512    /// that verifies the substitution subset's promotion triples
5513    /// embed injectively into the parent superset's, a Sekiban
5514    /// audit-trail metric jointly labeled by the substitution-subset
5515    /// promotion partition) bind to ONE `[(Self, char, Self); N]`
5516    /// primitive rather than re-deriving the promotion triples
5517    /// inline per callsite. Matches the preemptive-primitive posture
5518    /// the prior-run [`Self::HASH_DISCRIMINATORS`] +
5519    /// [`crate::ast::QuoteForm::PROMOTIONS`] +
5520    /// [`crate::ast::QuoteForm::HASH_DISCRIMINATORS`] lifts carried
5521    /// before their downstream consumers materialized.
5522    ///
5523    /// Theory anchor: THEORY.md §II.1 invariant 1 — typed entry; the
5524    /// reader's two-char quote-family classification IS the typed-
5525    /// entry gate on the `,@` boundary, and lifting the substitution-
5526    /// subset's promotion triple to ONE typed `[(Self, char, Self);
5527    /// 1]` primitive on the closed-set subset algebra closes the
5528    /// gate's two-char entry surface onto the subset algebra rather
5529    /// than at zero-primitive-on-this-subset dispatch through the
5530    /// parent superset. THEORY.md §II.1 invariant 5 — composition
5531    /// preserves proofs; the (subset promotion triple, superset
5532    /// promotion triple) pairing binds at rustc time by identity
5533    /// composition through [`Self::to_quote_form`] on the head +
5534    /// promoted columns AND by direct alias on the middle
5535    /// discriminator column. THEORY.md §III — the typescape; the
5536    /// singleton promotion triple binds at ONE typed `pub const` on
5537    /// the closed-set [`UnquoteForm`] algebra rather than at zero-
5538    /// primitive-on-this-subset. Closes the FIFTH per-role
5539    /// `pub const` axis on the UnquoteForm subset carving in lockstep
5540    /// with the same-axis lift on [`crate::ast::QuoteForm`].
5541    /// THEORY.md §V.1 — knowable platform; the family's cardinality
5542    /// becomes a TYPE-level constant on the substrate algebra rather
5543    /// than a per-consumer runtime dispatch through the parent
5544    /// superset's method surface. THEORY.md §VI.1 — generation over
5545    /// composition; the family-wide contract sweeps (alignment with
5546    /// [`Self::ALL`], cardinality, cross-algebra composition against
5547    /// [`crate::ast::QuoteForm::PROMOTIONS`]) emerge from the
5548    /// composition of ONE substrate primitive (this `pub(crate) const`
5549    /// array) rather than as per-arm inline assertions duplicated at
5550    /// each call site.
5551    ///
5552    /// Frontier inspiration: MLIR's typed rewriter registry
5553    /// (`mlir::PatternApplicator`) carries per-op-family static
5554    /// rewrite tables at the closed-set boundary; a sub-op-family
5555    /// (a distinguished carving of the parent op-family) inherits
5556    /// the rewrite table through direct alias from the parent's
5557    /// canonical site, so the subset's rewrite table binds at ONE
5558    /// typed constant on the subset algebra with the alignment
5559    /// composition through the subset → superset projection at
5560    /// const-fn evaluation time. [`Self::PROMOTIONS`] is the Rust-
5561    /// typed peer of MLIR's per-subset rewrite-table projection at
5562    /// the two-variant substitution-subset level — its single entry
5563    /// composes from [`crate::ast::QuoteForm::PROMOTIONS`]`[0]`
5564    /// through subset → superset projection on the endpoints and
5565    /// direct alias on the middle discriminator.
5566    #[allow(dead_code)]
5567    pub(crate) const PROMOTIONS: [(Self, char, Self); 1] = [(
5568        Self::Unquote,
5569        crate::ast::QuoteForm::SPLICE_DISCRIMINATOR,
5570        Self::Splice,
5571    )];
5572
5573    /// Project the typed `UnquoteForm` to the canonical `&'static str`
5574    /// literal — feeds the `LispError::UnboundTemplateVar` /
5575    /// `LispError::NonSymbolUnquoteTarget` Display rendering via the
5576    /// `#[error(...)]` annotation. The `&'static str` lifetime is
5577    /// load-bearing: it lets the variants project through this method into
5578    /// their `compile error in {prefix}…:` prefix without an allocation,
5579    /// parallel to how `MacroDefHead::keyword()` and
5580    /// `CompilerSpecIoStage::operation()` / `label()` feed their respective
5581    /// `LispError::*` Display impls.
5582    ///
5583    /// Composition law: `self.marker() == self.to_quote_form().prefix()`
5584    /// for every `self: UnquoteForm`. Pre-lift the body inlined the two
5585    /// literals (`","` / `",@"`) as a parallel match-table; the same two
5586    /// literals also lived at the matching Unquote/UnquoteSplice arms of
5587    /// [`crate::ast::QuoteForm::prefix`]. Post-lift the body composes
5588    /// `Self::to_quote_form()` (the typed 2-of-4 subset → superset
5589    /// projection) with `crate::ast::QuoteForm::prefix()` (the canonical
5590    /// 4-of-4 prefix-string projection), so the two `&'static str`
5591    /// literals live at ONE canonical site (`QuoteForm::prefix`'s
5592    /// Unquote/UnquoteSplice arms in `ast.rs`) and every consumer of
5593    /// `UnquoteForm::marker` (the `ClosedSet`-trait Display/FromStr
5594    /// surface via `#[closed_set(via = "marker")]`, the unbound-template
5595    /// `did you mean ,@args?` hint suffix in this module's
5596    /// `unbound_hint_suffix` helper, the `#[error("... {prefix}")]`
5597    /// annotation on `LispError::UnboundTemplateVar` /
5598    /// `LispError::NonSymbolUnquoteTarget`, the future LSP / REPL /
5599    /// `tatara-check` completion-bar projections that key on the typed
5600    /// marker) inherits the canonical vocabulary through the composition
5601    /// rather than through a parallel inline table that drifts at the
5602    /// next vocabulary rename.
5603    ///
5604    /// Sibling-shape lift to the prior-run `AtomKind::label` ⊂
5605    /// `SexpShape::label` composition (commit 1db697f): both pin the
5606    /// invariant that a typed-subset enum's projection on a closed-set
5607    /// vocabulary is structurally derived from its parent superset's
5608    /// projection, not a parallel literal table the type system happens
5609    /// to not catch when the vocabularies drift.
5610    ///
5611    /// The bidirectional contract is anchored by tests:
5612    /// `unquote_form_marker_projects_canonical_literal_for_each_variant`
5613    /// pins each variant's canonical literal so a typo in any arm
5614    /// fails-loudly,
5615    /// `unquote_form_display_renders_canonical_marker_for_each_variant`
5616    /// pins Display-equals-marker so any `#[error("... {prefix}")]`
5617    /// annotation that threads through this projection projects
5618    /// byte-for-byte,
5619    /// `unquote_form_marker_round_trips_through_from_str` pins the
5620    /// `marker` ↔ [`Self::FromStr`] round-trip for every variant in
5621    /// [`Self::ALL`] so the typed surface and the rendered diagnostic
5622    /// literal cannot drift, and the post-lift composition-routing
5623    /// pin
5624    /// `unquote_form_marker_routes_through_to_quote_form_prefix_via_composition`
5625    /// asserts pointer-equality between `self.marker()` and
5626    /// `self.to_quote_form().prefix()` for every variant — so a
5627    /// regression that re-inlines the literals as a parallel match-table
5628    /// fails the pointer pin even when the rendered bytes still agree
5629    /// (the inline-literal-table copy lives at a different `&'static str`
5630    /// address).
5631    #[must_use]
5632    pub fn marker(self) -> &'static str {
5633        self.to_quote_form().prefix()
5634    }
5635
5636    /// Project the 2-of-4 template-substitution subset back to its
5637    /// parent [`crate::ast::QuoteForm`] superset — the structural
5638    /// inverse of [`crate::ast::QuoteForm::as_unquote_form`]. Returns
5639    /// the [`crate::ast::QuoteForm`] variant whose canonical prefix
5640    /// string matches this `UnquoteForm`'s marker:
5641    /// [`Self::Unquote`] → [`crate::ast::QuoteForm::Unquote`],
5642    /// [`Self::Splice`] → [`crate::ast::QuoteForm::UnquoteSplice`].
5643    ///
5644    /// Total (not `Option`) because every `UnquoteForm` IS a
5645    /// `QuoteForm` — the 2-of-4 subset is a subset by inclusion. The
5646    /// dual projection [`crate::ast::QuoteForm::as_unquote_form`]
5647    /// returns `Option<UnquoteForm>` because the two non-substitution
5648    /// variants ([`crate::ast::QuoteForm::Quote`] and
5649    /// [`crate::ast::QuoteForm::Quasiquote`]) lie outside the
5650    /// substitution subset.
5651    ///
5652    /// Round-trip identity: `self.to_quote_form().as_unquote_form() ==
5653    /// Some(self)` for every `self: UnquoteForm`, pinned by
5654    /// `unquote_form_to_quote_form_round_trips_through_as_unquote_form`.
5655    /// The dual identity `qf.as_unquote_form().map(|uf|
5656    /// uf.to_quote_form()) == qf.as_unquote_form().map(|_| qf)` holds
5657    /// by construction (the projection is a section of
5658    /// [`crate::ast::QuoteForm::as_unquote_form`] restricted to its
5659    /// image — the Unquote/UnquoteSplice variants).
5660    ///
5661    /// Lifts the (UnquoteForm variant, QuoteForm variant) pairing onto
5662    /// the typed algebra so consumers that need the parent superset
5663    /// from a substitution-subset value (the `marker` lift's
5664    /// composition root, the future hint-suffix pretty-printer's
5665    /// `prefix.to_quote_form().prefix()` routing, the future
5666    /// canonical-form interop tag that wants to route an `UnquoteForm`
5667    /// through `iac_forge_tag` via `to_quote_form().iac_forge_tag()`
5668    /// without re-deriving the pairing — now closed at
5669    /// [`Self::iac_forge_tag`]) bind at ONE typed projection
5670    /// rather than at parallel match arms each site re-derives.
5671    ///
5672    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
5673    /// subset-to-superset projection becomes a TYPE projection on the
5674    /// substrate algebra. THEORY.md §VI.1 — generation over
5675    /// composition; the (UnquoteForm variant, QuoteForm variant)
5676    /// pairing emerges from this ONE primitive rather than from
5677    /// per-callsite per-variant literals. THEORY.md §II.1 invariant 1 —
5678    /// typed entry; the typed subset-to-superset projection IS a
5679    /// substrate-owned theorem rather than a hand-inlined match-table
5680    /// duplication discipline N sites had to keep in lockstep.
5681    #[must_use]
5682    pub fn to_quote_form(self) -> crate::ast::QuoteForm {
5683        match self {
5684            Self::Unquote => crate::ast::QuoteForm::Unquote,
5685            Self::Splice => crate::ast::QuoteForm::UnquoteSplice,
5686        }
5687    }
5688
5689    /// Project the 2-of-4 template-substitution subset marker back into
5690    /// its matching [`crate::ast::Sexp`] wrapper variant applied to
5691    /// `inner` — the typed-CONSTRUCT face on the closed-set `UnquoteForm`
5692    /// algebra, sibling section-for-retraction of the existing typed-
5693    /// PROJECT face [`crate::ast::Sexp::as_unquote`] on the outer
5694    /// [`crate::ast::Sexp`] algebra. [`Self::Unquote`] yields
5695    /// [`crate::ast::Sexp::Unquote`], [`Self::Splice`] yields
5696    /// [`crate::ast::Sexp::UnquoteSplice`], each boxing `inner` into the
5697    /// corresponding tuple-variant constructor
5698    /// (`fn(Box<Sexp>) -> Sexp`).
5699    ///
5700    /// Closes the (construct, project) algebra dual on the closed-set
5701    /// `UnquoteForm` algebra — the substitution-subset peer of the
5702    /// closed-set superset algebra's already-closed dual pair
5703    /// ([`crate::ast::QuoteForm::wrap`] +
5704    /// [`crate::ast::Sexp::as_quote_form`]). Where the superset's
5705    /// `wrap` reaches all four homoiconic prefix-wrappers and its
5706    /// projection returns `Option<(QuoteForm, &Sexp)>` on the outer
5707    /// [`crate::ast::Sexp`] algebra, this subset's `wrap` reaches only
5708    /// the two template-substitution wrappers
5709    /// ([`crate::ast::Sexp::Unquote`] and
5710    /// [`crate::ast::Sexp::UnquoteSplice`]) and its projection sibling
5711    /// [`crate::ast::Sexp::as_unquote`] returns
5712    /// `Option<(UnquoteForm, &Sexp)>` — the (construct, project) pair on
5713    /// the subset algebra is symmetric with the (construct, project)
5714    /// pair on the superset algebra, each closed at one method per
5715    /// direction on the respective closed set.
5716    ///
5717    /// Composition law (forward): `self.wrap(inner) ==
5718    /// self.to_quote_form().wrap(inner)` for every `self: UnquoteForm`
5719    /// and every `inner: Sexp` — routes through the superset's
5720    /// [`crate::ast::QuoteForm::wrap`] at the single tuple-variant
5721    /// emission site so the (marker, `Sexp::*` tuple-variant
5722    /// constructor) pairing binds at ONE closed-set match on the
5723    /// superset algebra rather than at a parallel two-arm match table
5724    /// on this subset. Round-trip law (section-for-retraction with the
5725    /// soft-projection sibling): `self.wrap(inner).as_unquote() ==
5726    /// Some((self, &inner))` for every `self: UnquoteForm` and every
5727    /// `inner: Sexp` — the subset's typed constructor pairs
5728    /// section-for-retraction with the outer algebra's soft projection,
5729    /// and the marker + inner body cross-projection preserves identity
5730    /// on the substitution-subset closed set. Sibling-shape lift to the
5731    /// prior-run `UnquoteForm::marker` ⊂ `QuoteForm::prefix` composition
5732    /// (commit 250c001): both pin the invariant that a typed-subset
5733    /// enum's projection on a closed-set operation is structurally
5734    /// derived from its parent superset's projection through the
5735    /// canonical `to_quote_form` composition, not a parallel inline
5736    /// match-table the type system happens to not catch when the
5737    /// vocabularies drift.
5738    ///
5739    /// Pre-lift consumers that had an `UnquoteForm` marker in hand and
5740    /// wanted to build a fresh [`crate::ast::Sexp`] wrapper had to
5741    /// spell the two-step composition
5742    /// `uf.to_quote_form().wrap(inner)` — a future template rewriter
5743    /// consuming [`crate::ast::Sexp::as_unquote`]'s
5744    /// `Option<(UnquoteForm, &Sexp)>` projection and threading a
5745    /// rewritten inner body back into the matching wrapper on the
5746    /// substitution-subset closed set (a future
5747    /// `TypedRewriter<UnquoteFormOp>` sweep, a future macro-template
5748    /// canonicalizer that normalizes `,x` / `,@x` shapes, a future REPL
5749    /// pretty-printer that emits a fresh substitution wrapper from a
5750    /// borrowed marker + inner pair) would have re-derived the
5751    /// `.to_quote_form().wrap(_)` composition at every callsite. Post-
5752    /// lift the composition binds at ONE typed-algebra method on the
5753    /// closed-set `UnquoteForm` algebra, matching the section-for-
5754    /// retraction posture the superset's [`crate::ast::QuoteForm::wrap`]
5755    /// already carries on the outer [`crate::ast::Sexp`] algebra. The
5756    /// (marker, `Sexp::*` tuple-variant constructor) pairing continues
5757    /// to live at ONE site on the superset's `wrap` closed-set match
5758    /// (the single tuple-variant emission point the substrate owns),
5759    /// which is what this subset method routes through — no parallel
5760    /// match table, no per-arm drift risk on the two-variant closed set.
5761    ///
5762    /// The [`crate::ast::Sexp`] (owned) return type complements
5763    /// [`crate::ast::Sexp::as_unquote`]'s `&Sexp` (borrowed) —
5764    /// symmetric with the ([`crate::ast::QuoteForm::wrap`],
5765    /// [`crate::ast::Sexp::as_quote_form`]) asymmetry on the superset
5766    /// algebra: `wrap` consumes the inner body to build the new
5767    /// wrapper, the projection borrows the inner body from the existing
5768    /// wrapper. The typed `Box::new(inner)` allocation lives at ONE
5769    /// site on the superset's [`crate::ast::QuoteForm::wrap`] (the
5770    /// closed-set tuple-variant emission point), so a future
5771    /// allocation-policy change (e.g. arena-allocated wrappers for
5772    /// span-aware [`crate::ast::Sexp`]) lands as ONE edit at the
5773    /// superset's `wrap` and propagates through this subset method
5774    /// byte-for-byte.
5775    ///
5776    /// Theory anchor: THEORY.md §II.1 invariant 1 — typed entry; the
5777    /// (UnquoteForm variant, `Sexp::*` tuple-variant constructor)
5778    /// pairing binds at ONE typed-algebra method on the subset algebra,
5779    /// routed through the ONE closed-set match on the superset algebra
5780    /// the substrate already owns. THEORY.md §II.1 invariant 2 — free
5781    /// middle; every consumer that has an `UnquoteForm` marker and
5782    /// wants to build a wrapper `Sexp` routes through the SAME typed
5783    /// method, so a regression that drifts one consumer's pairing from
5784    /// the others cannot reach the substrate's runtime. THEORY.md §V.1
5785    /// — knowable platform; the typed-construct face becomes a TYPE
5786    /// projection on the subset algebra sitting next to the typed-
5787    /// project face [`crate::ast::Sexp::as_unquote`] on the outer
5788    /// [`crate::ast::Sexp`] algebra rather than a bare
5789    /// `to_quote_form().wrap(_)` two-step composition consumers had to
5790    /// re-derive per site. THEORY.md §VI.1 — generation over
5791    /// composition; the (subset marker, wrapper `Sexp`) pairing emerges
5792    /// from ONE typed-algebra composition on the subset algebra rather
5793    /// than from parallel per-consumer per-variant literals.
5794    ///
5795    /// Frontier inspiration: Racket's `syntax/parse` `~or* (~unquote
5796    /// stx) (~unquote-splice stx)` pattern paired one-for-one with a
5797    /// typed constructor face on the same subset shape — the (project,
5798    /// construct) algebra dual is closed at one method per direction
5799    /// on Racket's substitution-subset surface, and `UnquoteForm::wrap`
5800    /// / [`crate::ast::Sexp::as_unquote`] is the Rust-typed peer on
5801    /// the closed-set `UnquoteForm` algebra with
5802    /// [`crate::ast::QuoteForm::wrap`] standing in for Racket's typed
5803    /// dispatch through the superset. MLIR's typed factory
5804    /// `mlir::OpBuilder::create<UnquoteFamilyOp>(loc, inner)` paired
5805    /// with the projection sibling `mlir::dyn_cast<UnquoteFamilyOp>(op)`
5806    /// — the typed factory + typed downcast pair the IR algebra closes
5807    /// over on every wrapper op subset; `UnquoteForm::wrap` /
5808    /// [`crate::ast::Sexp::as_unquote`] is the unstructured-Rust peer
5809    /// on the outer [`crate::ast::Sexp`] algebra with the closed-set
5810    /// `UnquoteForm` standing in for MLIR's subset-of-`OperationName`
5811    /// taxonomy over the substitution-subset op family.
5812    #[must_use]
5813    pub fn wrap(self, inner: crate::ast::Sexp) -> crate::ast::Sexp {
5814        self.to_quote_form().wrap(inner)
5815    }
5816
5817    /// Canonical [`SexpShape`] embed target for the [`Self::Unquote`]
5818    /// template-substitution arm on the UnquoteForm ⊂ QuoteForm ⊂
5819    /// SexpShape composed carving — aliases
5820    /// [`crate::ast::QuoteForm::UNQUOTE_SHAPE`] on the parent's 4-of-12
5821    /// quote-family carving byte-for-byte (which in turn aliases
5822    /// [`SexpShape::Unquote`]). Per-role peer of `Self::Unquote` on the
5823    /// closed-set UnquoteForm ⊂ SexpShape 2-of-12 composed embed axis;
5824    /// consumers with an `UnquoteForm` variant in hand at compile time
5825    /// bind the canonical embed target through ONE typed `pub const` on
5826    /// the subset algebra rather than through runtime dispatch via
5827    /// `Self::sexp_shape` OR by reaching across the two-hop
5828    /// `Self::to_quote_form().sexp_shape()` composition OR by re-deriving
5829    /// the UnquoteForm ⊂ QuoteForm ⊂ SexpShape variant pairing inline.
5830    /// Sibling posture to [`Self::UNQUOTE_MARKER`] on the reader-prefix
5831    /// axis, [`Self::UNQUOTE_LEAD`] on the reader-lead axis,
5832    /// [`Self::UNQUOTE_IAC_FORGE_TAG`] on the iac-forge canonical-form tag
5833    /// axis, [`Self::UNQUOTE_LABEL`] on the diagnostic-label axis, and
5834    /// [`Self::UNQUOTE_HASH_DISCRIMINATOR`] on the outer-Sexp cache-key
5835    /// byte axis — every one of the pre-existing per-role sub-vocabulary
5836    /// aliases on this subset algebra now has a peer per-role
5837    /// `SexpShape` embed-target alias on the SAME closed-set
5838    /// UnquoteForm ⊂ QuoteForm 2-of-4 subset carving.
5839    pub const UNQUOTE_SHAPE: SexpShape = crate::ast::QuoteForm::UNQUOTE_SHAPE;
5840
5841    /// Canonical [`SexpShape`] embed target for the [`Self::Splice`]
5842    /// template-substitution arm on the UnquoteForm ⊂ QuoteForm ⊂
5843    /// SexpShape composed carving — aliases
5844    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_SHAPE`] on the parent's
5845    /// 4-of-12 quote-family carving byte-for-byte (which in turn aliases
5846    /// [`SexpShape::UnquoteSplice`]). Per-role peer of `Self::Splice` on
5847    /// the closed-set UnquoteForm ⊂ SexpShape 2-of-12 composed embed
5848    /// axis. See [`Self::UNQUOTE_SHAPE`] for the alias-chain shape every
5849    /// sibling shares.
5850    pub const SPLICE_SHAPE: SexpShape = crate::ast::QuoteForm::UNQUOTE_SPLICE_SHAPE;
5851
5852    /// Closed-set forced-arity ALL array over the canonical
5853    /// [`SexpShape`] embed targets on the UnquoteForm ⊂ SexpShape
5854    /// 2-of-12 composed carving, in declaration order matching
5855    /// [`Self::ALL`] element-wise (pinned by
5856    /// `unquote_form_shapes_align_with_all_by_index`). Sibling posture
5857    /// to [`Self::MARKERS`] (`[&'static str; 2]` — reader-prefix bytes),
5858    /// [`Self::LEADS`] (`[char; 1]` — SHAPE-ASYMMETRIC-COLLAPSE distinct-
5859    /// lead byte), [`Self::IAC_FORGE_TAGS`] (`[&'static str; 2]` — iac-
5860    /// forge canonical-form tags), [`Self::LABELS`] (`[&'static str; 2]`
5861    /// — diagnostic labels), [`Self::HASH_DISCRIMINATORS`] (`[u8; 2]` —
5862    /// outer-Sexp cache-key bytes), and [`Self::PROMOTIONS`]
5863    /// (`[(Self, char, Self); 1]` — SHAPE-ASYMMETRIC-COLLAPSE promotion
5864    /// triples) on the SAME closed-set UnquoteForm algebra; where those
5865    /// six arrays lift per-role sub-vocabularies on the substitution
5866    /// subset algebra, this array lifts the per-role [`SexpShape`]
5867    /// embed-target sub-vocabulary at the same `[_; 2]` forced arity.
5868    ///
5869    /// Aliased through [`crate::ast::QuoteForm::UNQUOTE_SHAPE`] +
5870    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_SHAPE`] via the per-role
5871    /// [`Self::UNQUOTE_SHAPE`] + [`Self::SPLICE_SHAPE`] constants — the
5872    /// UnquoteForm ⊂ QuoteForm 2-of-4 subset carving of the parent's
5873    /// 4-of-12 quote-family SHAPES. Sibling posture to
5874    /// [`crate::ast::QuoteForm::SHAPES`] (`[SexpShape; 4]`) —
5875    /// the parent superset's peer array on the QuoteForm ⊂ SexpShape
5876    /// 4-of-12 carving; together with the three closed-set carvings'
5877    /// arrays ([`crate::ast::AtomKind::SHAPES`] 6-of-12,
5878    /// [`crate::ast::QuoteForm::SHAPES`] 4-of-12,
5879    /// [`StructuralKind::SHAPES`] 2-of-12) the four `SHAPES` arrays now
5880    /// close the FULL twelve-arm outer-shape algebra uniformly PLUS the
5881    /// UnquoteForm 2-of-4 subset carving of the quote-family sub-
5882    /// algebra.
5883    ///
5884    /// Pre-lift the two [`SexpShape`] embed targets had NO per-role
5885    /// primitive on this subset algebra — a consumer with an
5886    /// `UnquoteForm` variant in hand at compile time reaching for the
5887    /// canonical embed target had to spell
5888    /// `UnquoteForm::Unquote.sexp_shape()` (runtime dispatch through
5889    /// [`Self::sexp_shape`]'s two-hop `.to_quote_form().sexp_shape()`
5890    /// composition) OR reach across into
5891    /// [`crate::ast::QuoteForm::UNQUOTE_SHAPE`] and re-derive the
5892    /// UnquoteForm ⊂ QuoteForm 2-of-4 subset pairing at the call site.
5893    /// Post-lift the TWO canonical embed targets bind at ONE `pub const`
5894    /// per role on the typed [`UnquoteForm`] subset algebra AND at
5895    /// [`Self::SHAPES`] as a family-wide forced-arity array — a future
5896    /// LSP / REPL completion bar keyed on `UnquoteForm::SHAPES` for the
5897    /// "which SexpShape does this UnquoteForm embed into?" outer-shape
5898    /// column, a `tatara-check` coverage sweep zipping
5899    /// `UnquoteForm::ALL` / `LABELS` / `MARKERS` / `LEADS` /
5900    /// `IAC_FORGE_TAGS` / `HASH_DISCRIMINATORS` / `PROMOTIONS` /
5901    /// `SHAPES` in lockstep for a family-wide (variant, label, marker,
5902    /// lead, iac-forge tag, cache-key byte, promotion triple, embed-
5903    /// target) octuple render, or a Sekiban audit-trail metric jointly
5904    /// labeled by the embed target's SexpShape identity reads through
5905    /// the typed constants on this subset algebra without re-deriving
5906    /// the 2-of-12 composed carving inline.
5907    ///
5908    /// Round-trip identity with the inverse projection
5909    /// [`SexpShape::as_unquote_form`]: for every index `i`,
5910    /// `Self::SHAPES[i].as_unquote_form() == Some(Self::ALL[i])` (pinned
5911    /// by `unquote_form_shapes_align_with_all_by_index_through_as_unquote_form`)
5912    /// — the embed / project section closes as a family-wide array-
5913    /// indexed law on the composed 2-of-12 carving rather than as a
5914    /// per-variant assertion sweep. Adding a hypothetical third
5915    /// template-substitution marker (e.g. `,~` reverse-unquote for
5916    /// destructuring-bind hygiene, `,?` conditional-unquote) extends
5917    /// [`Self::ALL`] AND [`Self::SHAPES`] AND
5918    /// [`crate::ast::QuoteForm::ALL`] AND
5919    /// [`crate::ast::QuoteForm::SHAPES`] AND [`SexpShape::ALL`] AND adds
5920    /// ONE per-role `pub const *_SHAPE` in lockstep — rustc's forced-
5921    /// arity check on the two `[_; N]` arrays fails compilation if
5922    /// EITHER ALL array grows without the other, AND the peer
5923    /// [`SexpShape::as_unquote_form`] arm must grow in lockstep to
5924    /// preserve the round-trip identity.
5925    ///
5926    /// Theory anchor: THEORY.md §III — the typescape; the two canonical
5927    /// [`SexpShape`] embed targets bind at ONE typed `[SexpShape; 2]`
5928    /// array on the closed-set UnquoteForm subset algebra rather than at
5929    /// zero-primitive-on-this-subset-plus-two-inline-lookups-through-
5930    /// two-hop-composition scattered across the substrate. THEORY.md
5931    /// §II.1 invariant 2 — free middle; the (embed, project) pair binds
5932    /// at THREE typed sites now — the projection method
5933    /// [`Self::sexp_shape`], this family-wide array, AND the peer inverse
5934    /// [`SexpShape::as_unquote_form`] — with rustc-enforced consistency
5935    /// across all three via the array-indexed round-trip law. THEORY.md
5936    /// §V.1 — knowable platform; the family's cardinality becomes a
5937    /// TYPE-level constant on the substrate algebra rather than a per-
5938    /// consumer runtime dispatch through the two-hop composition.
5939    /// THEORY.md §VI.1 — generation over composition; the family-wide
5940    /// contract sweeps (alignment with `ALL`, round-trip through
5941    /// `as_unquote_form`, membership through `sexp_shape`, pairwise
5942    /// injectivity across the two embed targets, alias byte-equality
5943    /// with QuoteForm's per-role SHAPE constants) emerge from the
5944    /// composition of TWO substrate primitives (this `pub const` array +
5945    /// the two per-role `pub const *_SHAPE` aliases) rather than as
5946    /// per-variant inline assertions duplicated at each call site.
5947    pub const SHAPES: [SexpShape; 2] = [Self::UNQUOTE_SHAPE, Self::SPLICE_SHAPE];
5948
5949    /// Project the 2-of-4 template-substitution subset marker into its
5950    /// matching [`SexpShape`] variant on the outer [`crate::ast::Sexp`]
5951    /// algebra's twelve-variant shape lattice — [`Self::Unquote`] →
5952    /// [`SexpShape::Unquote`], [`Self::Splice`] → [`SexpShape::UnquoteSplice`].
5953    /// Section peer of [`SexpShape::as_unquote_form`] on the 2-of-12
5954    /// carving that names the template-substitution wrappers; closes the
5955    /// (embed, project) algebra dual on the `UnquoteForm ⊂ SexpShape`
5956    /// typed-shape lattice, sibling of the already-closed
5957    /// ([`crate::ast::AtomKind::sexp_shape`], [`SexpShape::as_atom_kind`])
5958    /// dual on the 6-of-12 atomic-payload carving AND
5959    /// ([`crate::ast::QuoteForm::sexp_shape`], [`SexpShape::as_quote_form`])
5960    /// dual on the 4-of-12 quote-family carving.
5961    ///
5962    /// Composition law: `self.sexp_shape() ==
5963    /// self.to_quote_form().sexp_shape()` for every `self: UnquoteForm`.
5964    /// The (subset marker, outer [`SexpShape`]) pairing binds at ONE
5965    /// closed-set match on the superset's
5966    /// [`crate::ast::QuoteForm::sexp_shape`] rather than at a parallel
5967    /// two-arm inline match table on this subset — matching the posture
5968    /// [`Self::marker`] takes through [`crate::ast::QuoteForm::prefix`]
5969    /// and [`Self::wrap`] takes through [`crate::ast::QuoteForm::wrap`].
5970    /// Round-trip law (section-for-retraction with the outer projection
5971    /// sibling): `self.sexp_shape().as_unquote_form() == Some(self)` for
5972    /// every `self: UnquoteForm`.
5973    ///
5974    /// Pre-lift the (UnquoteForm variant, SexpShape variant) pairing had
5975    /// NO typed projection on this subset algebra — a template rewriter
5976    /// with an `UnquoteForm` marker in hand wanting the outer
5977    /// [`SexpShape`] identity (e.g., a future `tatara-check` predicate
5978    /// that filters diagnostics to "this rejection was on a template-
5979    /// substitution wrapper", a future LSP hover that renders a typed
5980    /// marker's outer-shape witness alongside its punctuation, a future
5981    /// macro-hygiene analyzer that keys on the outer shape of a captured
5982    /// substitution point) had to spell the two-step composition
5983    /// `uf.to_quote_form().sexp_shape()` at every callsite. Post-lift
5984    /// the composition binds at ONE typed-algebra method on the closed-
5985    /// set `UnquoteForm` algebra, and the (embed, project) pair with
5986    /// [`SexpShape::as_unquote_form`] forms an `Iso(UnquoteForm,
5987    /// UnquoteShape ⊂ SexpShape)` on the substitution-subset carving.
5988    ///
5989    /// Theory anchor: THEORY.md §V.1 — knowable platform; the (subset
5990    /// marker, outer shape) pairing becomes a TYPE projection on the
5991    /// substrate algebra rather than a per-callsite `.to_quote_form()
5992    /// .sexp_shape()` two-step. THEORY.md §II.1 invariant 2 — free
5993    /// middle; every consumer that has an `UnquoteForm` marker and
5994    /// wants the outer [`SexpShape`] routes through the SAME typed
5995    /// method. THEORY.md §VI.1 — generation over composition; the
5996    /// pairing emerges from ONE typed-algebra composition on the subset
5997    /// algebra rather than from parallel per-consumer per-variant
5998    /// literals.
5999    ///
6000    /// Frontier inspiration: MLIR's `mlir::OperationName` typed
6001    /// projection from a subset-op-family value into the parent
6002    /// `OperationName` taxonomy — the (subset op, taxonomic identity)
6003    /// pairing lives at ONE typed projection on the subset algebra,
6004    /// composed through the parent op-family's typed identity face.
6005    /// `UnquoteForm::sexp_shape` is the Rust-typed peer on the closed-
6006    /// set `UnquoteForm` algebra with [`SexpShape`] standing in for
6007    /// MLIR's parent `OperationName` taxonomy.
6008    #[must_use]
6009    pub fn sexp_shape(self) -> SexpShape {
6010        self.to_quote_form().sexp_shape()
6011    }
6012
6013    /// Project the 2-of-4 template-substitution subset marker into its
6014    /// canonical iac-forge interop tag — [`Self::Unquote`] →
6015    /// `"unquote"`, [`Self::Splice`] → `"unquote-splicing"`. The
6016    /// Common-Lisp-canonical tag string [`crate::ast::QuoteForm::iac_forge_tag`]
6017    /// projects on the superset carving, restricted through
6018    /// [`Self::to_quote_form`] to the substitution-subset carving.
6019    ///
6020    /// Composition law: `self.iac_forge_tag() ==
6021    /// self.to_quote_form().iac_forge_tag()` for every `self:
6022    /// UnquoteForm`. The body composes [`Self::to_quote_form`] (the
6023    /// typed 2-of-4 subset → superset projection) with
6024    /// [`crate::ast::QuoteForm::iac_forge_tag`] (the canonical 4-of-4
6025    /// interop-tag projection), so the two `&'static str` literals live
6026    /// at ONE canonical site (`QuoteForm::iac_forge_tag`'s
6027    /// Unquote/UnquoteSplice arms in `ast.rs`) — the (subset marker,
6028    /// canonical iac-forge tag string) pairing binds at ONE closed-set
6029    /// match on the superset algebra rather than at a parallel two-arm
6030    /// inline match table on this subset. Matches the posture
6031    /// [`Self::marker`] takes through [`crate::ast::QuoteForm::prefix`],
6032    /// [`Self::wrap`] takes through [`crate::ast::QuoteForm::wrap`],
6033    /// and [`Self::sexp_shape`] takes through
6034    /// [`crate::ast::QuoteForm::sexp_shape`] — the FOURTH
6035    /// composition-through-`to_quote_form` axis the closed-set subset
6036    /// algebra closes.
6037    ///
6038    /// The `&'static str` lifetime is load-bearing: every future iac-
6039    /// forge consumer with an `UnquoteForm` marker in hand projects
6040    /// through this method into the canonical 2-element-list head
6041    /// without an allocation, parallel to how [`Self::marker`] projects
6042    /// the reader-punctuation vocabulary. The tag stays intentionally
6043    /// disjoint from [`Self::marker`] (reader punctuation: `","` /
6044    /// `",@"`) AND from [`Self::sexp_shape`]'s
6045    /// [`crate::error::SexpShape::label`] rendering (diagnostic label:
6046    /// `"unquote"` / `"unquote-splice"`) at the substitution-subset's
6047    /// `Splice` arm — the CL canonical form REQUIRES the
6048    /// `unquote-splicing` spelling, while the substrate's diagnostic
6049    /// surface renders `unquote-splice`. The two projections key the
6050    /// SAME closed-set on TWO distinct boundaries; consolidating them
6051    /// would silently break either the iac-forge canonical-form round-
6052    /// trip OR the operator-facing diagnostic surface. That distinction
6053    /// is pinned bit-for-bit through the composition law: this method
6054    /// AGREES with [`crate::ast::QuoteForm::iac_forge_tag`] at every
6055    /// variant AND disagrees with [`crate::error::SexpShape::label`]
6056    /// at the `Splice` arm through the same closed-set typed algebra.
6057    ///
6058    /// Pre-lift the (UnquoteForm variant, iac-forge tag) pairing had
6059    /// NO typed projection on this subset algebra — a future consumer
6060    /// with an `UnquoteForm` marker wanting the canonical iac-forge
6061    /// tag (e.g. a future template rewriter emitting canonical-form
6062    /// diagnostics keyed on the substitution-subset marker, a future
6063    /// `tatara-check` predicate that renders `(check-iac-forge-tag
6064    /// ,x)` for a substitution-subset value, a future audit-trail
6065    /// metric jointly labeled by the interop tag on a template-
6066    /// substitution rejection) had to spell the two-step composition
6067    /// `uf.to_quote_form().iac_forge_tag()` at every callsite —
6068    /// exactly the composition the docstring for [`Self::wrap`]
6069    /// explicitly names as the NEXT deferred sweep this lift picks up.
6070    /// Post-lift the composition binds at ONE typed-algebra method on
6071    /// the closed-set `UnquoteForm` algebra sitting next to the three
6072    /// prior sibling projections ([`Self::marker`], [`Self::wrap`],
6073    /// [`Self::sexp_shape`]), closing the FOURTH
6074    /// composition-through-`to_quote_form` axis on the subset — every
6075    /// substrate-surface axis the superset algebra carries
6076    /// ([`crate::ast::QuoteForm::prefix`],
6077    /// [`crate::ast::QuoteForm::wrap`],
6078    /// [`crate::ast::QuoteForm::sexp_shape`],
6079    /// [`crate::ast::QuoteForm::iac_forge_tag`]) now has a matching
6080    /// composition on the substitution subset.
6081    ///
6082    /// Theory anchor: THEORY.md §V.1 — knowable platform; the (subset
6083    /// marker, canonical iac-forge tag) pairing becomes a TYPE
6084    /// projection on the substrate algebra rather than a per-callsite
6085    /// `.to_quote_form().iac_forge_tag()` two-step. THEORY.md §II.1
6086    /// invariant 2 — free middle; every consumer that has an
6087    /// `UnquoteForm` marker and wants the canonical iac-forge tag
6088    /// routes through the SAME typed method. THEORY.md §VI.1 —
6089    /// generation over composition; the pairing emerges from ONE
6090    /// typed-algebra composition on the subset algebra rather than
6091    /// from parallel per-consumer per-variant literals. A future
6092    /// third template-substitution marker (e.g. `,~` reverse-unquote)
6093    /// extends [`Self::ALL`] + [`Self::to_quote_form`]'s dispatch
6094    /// table in lockstep — rustc-enforced through the closed-set
6095    /// exhaustiveness — with THIS method inheriting the extension
6096    /// through the [`crate::ast::QuoteForm::iac_forge_tag`]
6097    /// composition site without a per-site edit.
6098    ///
6099    /// Frontier inspiration: Racket's `syntax-source-module` +
6100    /// `syntax->datum` interop chain paired with a substitution-subset
6101    /// pattern-class projection — the (subset pattern, canonical
6102    /// interop identity) pairing lives at ONE typed projection on the
6103    /// subset algebra, composed through the parent syntactic-form
6104    /// interop identity face. `UnquoteForm::iac_forge_tag` is the
6105    /// Rust-typed peer on the closed-set `UnquoteForm` algebra with
6106    /// [`crate::ast::QuoteForm::iac_forge_tag`] standing in for
6107    /// Racket's parent syntactic-form interop identity taxonomy.
6108    /// MLIR's `mlir::OperationName::getStringRef()` typed projection
6109    /// from a subset-op-family value into the canonical cross-crate
6110    /// op-identity string — the (subset op, canonical string) pairing
6111    /// lives at ONE typed projection on the subset algebra, composed
6112    /// through the parent op-family's typed identity face.
6113    /// `UnquoteForm::iac_forge_tag` is the Rust-typed peer on the
6114    /// closed-set `UnquoteForm` algebra with `iac_forge_tag` standing
6115    /// in for MLIR's `getStringRef` cross-boundary identity face.
6116    #[must_use]
6117    pub fn iac_forge_tag(self) -> &'static str {
6118        self.to_quote_form().iac_forge_tag()
6119    }
6120
6121    /// Project the 2-of-4 template-substitution subset marker into its
6122    /// canonical substrate diagnostic label — [`Self::Unquote`] →
6123    /// `"unquote"`, [`Self::Splice`] → `"unquote-splice"`. Feeds the
6124    /// same `SexpShape::label`-backed diagnostic surface consumers
6125    /// reach for on the parent [`crate::ast::QuoteForm::label`]
6126    /// projection, restricted through [`Self::to_quote_form`] to the
6127    /// substitution-subset carving.
6128    ///
6129    /// Composition law: `self.label() ==
6130    /// self.to_quote_form().label()` for every `self: UnquoteForm`.
6131    /// The body composes [`Self::to_quote_form`] (the typed 2-of-4
6132    /// subset → superset projection) with
6133    /// [`crate::ast::QuoteForm::label`] (the canonical 4-of-4
6134    /// diagnostic-label projection through
6135    /// [`crate::ast::QuoteForm::sexp_shape`] +
6136    /// [`SexpShape::label`]), so the two `&'static str` literals live
6137    /// at ONE canonical site ([`SexpShape::UNQUOTE_LABEL`] /
6138    /// [`SexpShape::UNQUOTE_SPLICE_LABEL`] in `error.rs`) — the
6139    /// (subset marker, canonical diagnostic label) pairing binds at
6140    /// ONE closed-set match on the superset algebra rather than at a
6141    /// parallel two-arm inline match table on this subset. Matches
6142    /// the posture [`Self::marker`] takes through
6143    /// [`crate::ast::QuoteForm::prefix`], [`Self::wrap`] takes through
6144    /// [`crate::ast::QuoteForm::wrap`], [`Self::sexp_shape`] takes
6145    /// through [`crate::ast::QuoteForm::sexp_shape`], and
6146    /// [`Self::iac_forge_tag`] takes through
6147    /// [`crate::ast::QuoteForm::iac_forge_tag`] — the FIFTH
6148    /// composition-through-`to_quote_form` axis the closed-set subset
6149    /// algebra closes.
6150    ///
6151    /// The `&'static str` lifetime is load-bearing: every future
6152    /// diagnostic consumer with an `UnquoteForm` marker in hand
6153    /// projects through this method into the canonical
6154    /// SexpShape-label vocabulary without an allocation, parallel to
6155    /// how [`Self::marker`] projects the reader-punctuation vocabulary
6156    /// and [`Self::iac_forge_tag`] projects the cross-crate iac-forge
6157    /// vocabulary. The diagnostic label stays intentionally distinct
6158    /// from [`Self::marker`] (reader punctuation: `","` / `",@"`) AND
6159    /// diverges INTENTIONALLY from [`Self::iac_forge_tag`] at the
6160    /// `Splice` arm — `label` renders `"unquote-splice"` (substrate
6161    /// diagnostic surface) while `iac_forge_tag` renders `"unquote-
6162    /// splicing"` (Common-Lisp canonical form). The typed method
6163    /// documents the boundary-distinct invariant structurally: a
6164    /// future "consolidation" that homogenizes the two axes would
6165    /// have to touch this method explicitly.
6166    ///
6167    /// Note: `#[closed_set(via = "marker")]` on the enum's derive
6168    /// wires the trait-side [`tatara_closed_set::ClosedSet::label`] projection to
6169    /// [`Self::marker`] (the reader-punctuation byte), NOT to this
6170    /// inherent `label` (which is the SexpShape-diagnostic label).
6171    /// The inherent surface takes precedence for direct `uf.label()`
6172    /// calls; generic consumers reaching through the trait
6173    /// (`<UnquoteForm as ClosedSet>::label(uf)`) still see the marker.
6174    /// This mirrors [`crate::ast::QuoteForm::label`]'s posture on the
6175    /// parent superset ([`crate::ast::QuoteForm`]'s trait `label` is
6176    /// wired via `#[closed_set(via = "prefix")]` to `prefix`, while
6177    /// its inherent `label` returns `sexp_shape().label()`) — the
6178    /// UnquoteForm subset now closes the SAME (inherent-diagnostic,
6179    /// trait-punctuation) split on its subset algebra.
6180    ///
6181    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
6182    /// (subset marker, canonical diagnostic label) pairing becomes a
6183    /// TYPE projection on the substrate algebra rather than a per-
6184    /// callsite `.to_quote_form().label()` two-step. THEORY.md §II.1
6185    /// invariant 2 — free middle; every consumer that has an
6186    /// `UnquoteForm` marker and wants the canonical diagnostic label
6187    /// routes through the SAME typed method. THEORY.md §VI.1 —
6188    /// generation over composition; the pairing emerges from ONE
6189    /// typed-algebra composition on the subset algebra rather than
6190    /// from parallel per-consumer per-variant literals. A future
6191    /// third template-substitution marker (e.g. `,~` reverse-unquote)
6192    /// extends [`Self::ALL`] + [`Self::to_quote_form`]'s dispatch
6193    /// table in lockstep — rustc-enforced through the closed-set
6194    /// exhaustiveness — with THIS method inheriting the extension
6195    /// through the [`crate::ast::QuoteForm::label`] composition site
6196    /// without a per-site edit.
6197    #[must_use]
6198    pub fn label(self) -> &'static str {
6199        self.to_quote_form().label()
6200    }
6201
6202    /// Stable, per-variant byte discriminator that paired with the
6203    /// substitution-subset outer-shape hash body builds the substrate's
6204    /// [`Hash for Sexp`](crate::ast::Sexp) projection at the two
6205    /// template-substitution arms — `5u8` for [`Self::Unquote`], `6u8`
6206    /// for [`Self::Splice`]. The byte values are load-bearing because
6207    /// the macro-expansion cache ([`crate::macro_expand::Expander`]'s
6208    /// cache) keys on the hash of `(macro_name, args)`, and every
6209    /// [`crate::ast::Sexp::Unquote`] / [`crate::ast::Sexp::UnquoteSplice`]
6210    /// wrapper participates in that hash through the superset's
6211    /// [`crate::ast::QuoteForm::hash_discriminator`] arms — changing a
6212    /// discriminator here silently invalidates every cached expansion
6213    /// across the substrate AND risks collision with the reserved bytes
6214    /// the other outer-Sexp carvings' arms use (`1u8` for the
6215    /// [`crate::ast::Sexp::Atom`] outer-carve marker, `{0, 2}` for the
6216    /// [`StructuralKind::hash_discriminator`] structural-residual
6217    /// carving, `{3, 4}` for the non-substitution quote-family arms
6218    /// [`crate::ast::QuoteForm::Quote`] and
6219    /// [`crate::ast::QuoteForm::Quasiquote`]).
6220    ///
6221    /// Composition law: `self.hash_discriminator() ==
6222    /// self.to_quote_form().hash_discriminator()` for every `self:
6223    /// UnquoteForm`. The body composes [`Self::to_quote_form`] (the
6224    /// typed 2-of-4 subset → superset projection) with
6225    /// [`crate::ast::QuoteForm::hash_discriminator`] (the canonical
6226    /// 4-of-4 cache-key byte projection), so the two `5u8`/`6u8`
6227    /// literals live at ONE canonical site
6228    /// ([`crate::ast::QuoteForm::hash_discriminator`]'s Unquote /
6229    /// UnquoteSplice arms in `ast.rs`) rather than at a parallel
6230    /// two-arm inline table on this subset. Matches the posture
6231    /// [`Self::marker`] takes through [`crate::ast::QuoteForm::prefix`],
6232    /// [`Self::wrap`] takes through [`crate::ast::QuoteForm::wrap`],
6233    /// [`Self::sexp_shape`] takes through
6234    /// [`crate::ast::QuoteForm::sexp_shape`], and
6235    /// [`Self::iac_forge_tag`] takes through
6236    /// [`crate::ast::QuoteForm::iac_forge_tag`] — the FIFTH
6237    /// composition-through-`to_quote_form` axis the closed-set subset
6238    /// algebra closes.
6239    ///
6240    /// Pre-lift the (UnquoteForm variant, cache-key byte) pairing had
6241    /// NO typed projection on this subset algebra — a future consumer
6242    /// with an [`UnquoteForm`] marker in hand wanting the outer-Sexp
6243    /// cache-key byte (e.g. a future template rewriter that keys a
6244    /// substitution-subset audit-trail metric on the
6245    /// [`Hash for Sexp`](crate::ast::Sexp) cache-key partition, a
6246    /// future `tatara-check` predicate that verifies the substitution-
6247    /// subset carving's cache-key partition sits inside `{5, 6}` at a
6248    /// substrate coherence gate, a future `TypedRewriter<UnquoteFormOp>`
6249    /// that decides cache invalidation on the substitution-subset
6250    /// discriminator) had to spell the two-step composition
6251    /// `uf.to_quote_form().hash_discriminator()` at every callsite.
6252    /// Post-lift the composition binds at ONE typed-algebra method on
6253    /// the closed-set [`UnquoteForm`] algebra sitting next to the four
6254    /// prior sibling projections ([`Self::marker`], [`Self::wrap`],
6255    /// [`Self::sexp_shape`], [`Self::iac_forge_tag`]), closing the
6256    /// FIFTH composition-through-`to_quote_form` axis on the subset
6257    /// — every substrate-surface axis the superset algebra carries
6258    /// ([`crate::ast::QuoteForm::prefix`],
6259    /// [`crate::ast::QuoteForm::wrap`],
6260    /// [`crate::ast::QuoteForm::sexp_shape`],
6261    /// [`crate::ast::QuoteForm::iac_forge_tag`],
6262    /// [`crate::ast::QuoteForm::hash_discriminator`]) now has a
6263    /// matching composition on the substitution subset.
6264    ///
6265    /// `pub(crate)` because the byte-discriminator surface is an
6266    /// implementation detail of the substrate's
6267    /// [`Hash for Sexp`](crate::ast::Sexp) cache-key contract; exposing
6268    /// it publicly would leak the cache-key shape through the API
6269    /// without enabling any external consumer the public projections
6270    /// ([`Self::marker`], [`Self::wrap`], [`Self::sexp_shape`],
6271    /// [`Self::iac_forge_tag`]) don't already serve. Same posture as
6272    /// [`crate::ast::QuoteForm::hash_discriminator`],
6273    /// [`StructuralKind::hash_discriminator`],
6274    /// [`crate::ast::AtomKind::hash_discriminator`], and
6275    /// [`SexpShape::hash_discriminator`] — every `hash_discriminator`
6276    /// projection on the substrate's typed-shape family is
6277    /// crate-private.
6278    ///
6279    /// Theory anchor: THEORY.md §V.1 — knowable platform; the (subset
6280    /// marker, cache-key byte) pairing becomes a TYPE projection on the
6281    /// substrate algebra rather than a per-callsite
6282    /// `.to_quote_form().hash_discriminator()` two-step. THEORY.md
6283    /// §II.1 invariant 2 — free middle; every consumer that has an
6284    /// [`UnquoteForm`] marker and wants the outer-Sexp cache-key byte
6285    /// routes through the SAME typed method, so a regression that
6286    /// drifts ONE consumer's byte from the others cannot reach the
6287    /// substrate's runtime cache. THEORY.md §VI.1 — generation over
6288    /// composition; the pairing emerges from ONE typed-algebra
6289    /// composition on the subset algebra rather than from parallel
6290    /// per-consumer per-variant literals. A future third template-
6291    /// substitution marker (e.g. `,~` reverse-unquote) extends
6292    /// [`Self::ALL`] + [`Self::to_quote_form`]'s dispatch table in
6293    /// lockstep — rustc-enforced through the closed-set exhaustiveness
6294    /// — with THIS method inheriting the extension through the
6295    /// [`crate::ast::QuoteForm::hash_discriminator`] composition site
6296    /// without a per-site edit.
6297    ///
6298    /// Frontier inspiration: MLIR's `mlir::TypeID::get<T>()` typed
6299    /// projection from a subset-op-family value into the canonical
6300    /// per-type identity byte the IR framework hashes on — the (subset
6301    /// op, canonical hash identity) pairing lives at ONE typed
6302    /// projection on the subset algebra, composed through the parent
6303    /// op-family's typed identity face. `UnquoteForm::hash_discriminator`
6304    /// is the Rust-typed peer on the closed-set [`UnquoteForm`]
6305    /// algebra with [`crate::ast::QuoteForm::hash_discriminator`]
6306    /// standing in for MLIR's parent op-family `TypeID` face.
6307    ///
6308    /// The `#[allow(dead_code)]` posture is deliberate: the substrate's
6309    /// current [`Hash for Sexp`](crate::ast::Sexp) body composes
6310    /// through [`crate::ast::QuoteForm::hash_discriminator`] at the
6311    /// four quote-family arms uniformly rather than splitting the
6312    /// dispatch into (2 non-substitution + 2 substitution) arms — so
6313    /// no non-test caller currently reaches THIS subset-algebra
6314    /// method's discriminator projection. The lift lands the
6315    /// substrate primitive so future consumers keyed on the
6316    /// substitution-subset carving specifically (a future
6317    /// [`crate::macro_expand::Expander`] cache-invalidation predicate
6318    /// that decides on the 2-of-4 substitution-subset only, a future
6319    /// `tatara-check` predicate `(check-substitution-subset-cache-key
6320    /// …)` that verifies the substitution-subset carving's cache-key
6321    /// partition sits inside `{5, 6}`, a future
6322    /// `TypedRewriter<UnquoteFormOp>` sweep that keys on the subset
6323    /// discriminator directly) bind to ONE typed algebra method
6324    /// rather than re-deriving the `.to_quote_form()
6325    /// .hash_discriminator()` composition per callsite. Matches the
6326    /// preemptive-primitive posture the prior-run [`Self::wrap`],
6327    /// [`Self::sexp_shape`], and [`Self::iac_forge_tag`] lifts
6328    /// carried before their downstream consumers materialized.
6329    #[must_use]
6330    #[allow(dead_code)]
6331    pub(crate) fn hash_discriminator(self) -> u8 {
6332        self.to_quote_form().hash_discriminator()
6333    }
6334}
6335
6336// `impl std::fmt::Display for UnquoteForm` + `impl std::str::FromStr
6337// for UnquoteForm` + `impl tatara_closed_set::ClosedSet for UnquoteForm` +
6338// `pub struct UnknownUnquoteForm(pub String)` are generated by
6339// `#[derive(tatara_closed_set::DeriveClosedSet)]` on the enum declaration
6340// above. `label` delegates to the inherent `UnquoteForm::marker` via
6341// `#[closed_set(via = "marker")]` so the domain-canonical
6342// punctuation-marker projection (`","` / `",@"`) stays load-bearing at
6343// the inherent surface while the trait surface unifies every
6344// closed-set implementor's projection name onto `label`. The marker
6345// axis stays intentionally disjoint from the structural-axis
6346// `SexpShape` vocabulary (`"unquote"` / `"unquote-splice"`); the
6347// disjointness contract holds at the trait surface exactly because
6348// each implementor's `label` projects its own inherent
6349// axis-vocabulary. The `display` flag emits the substrate-wide
6350// `f.write_str(Self::marker(*self))` block.
6351// `#[closed_set(generate_unknown = "unquote form")]` emits the typed
6352// parse-rejection carrier with the substrate-wide `Debug + Clone +
6353// PartialEq + Eq + thiserror::Error` derives and the `#[error("unknown
6354// unquote form: {0}")]` annotation byte-for-byte; the explicit label
6355// matches the auto-derived `pascal_to_spaced_lowercase("UnquoteForm")`
6356// projection byte-for-byte but pins the pre-lift wording against any
6357// future change to the projection helper's behavior on this name. The
6358// FromStr decode is a linear sweep over `UnquoteForm::ALL` keyed on
6359// `marker`; round-trip + cross-axis rejection (`"unquote"` /
6360// `"unquote-splice"`) pinned by
6361// `unquote_form_marker_round_trips_through_from_str` +
6362// `unquote_form_from_str_rejects_sexp_shape_labels_on_template_marker_axis`.
6363
6364/// Closed-set identifier for a kwargs-path projection — the `form:` label
6365/// shape that a typed-entry kwarg failure renders into the `compile error
6366/// in {form}:` prefix of a `LispError::TypeMismatch` diagnostic. Encodes
6367/// the three reachable path shapes the kwargs gate emits — `:<key>` for a
6368/// named kwarg (`extract_string` / `extract_int` / etc. failure),
6369/// `:<key>[<idx>]` for the Nth item of a list-typed kwarg
6370/// (`extract_string_list` per-item failure), and `kwargs[<idx>]` for an
6371/// even-position slot that failed the "this-position-must-be-a-keyword"
6372/// gate before a key was known (`parse_kwargs` direct call) — as a typed
6373/// borrowed enum, so authoring tools (REPL, LSP, `tatara-check`) bind to
6374/// path-shape identity (`KwargPath::Item { .. }` etc.) rather than
6375/// substring-matching the rendered prefix.
6376///
6377/// Mirror at the kwargs-path-shape boundary of the prior-run
6378/// `MacroDefHead` (macro-definition-head closed set),
6379/// `CompilerSpecIoStage` (disk-persistence surface),
6380/// `TemplateInvariantKind` (bytecode-runtime surface), and `UnquoteForm`
6381/// (template-marker syntactic forms) closed-set lifts: those enums key
6382/// their respective rejection variants on a typed identity carried inside
6383/// the variant's data shape; this enum keys the THREE distinct `form:`
6384/// label shapes emitted by the kwarg-gate's typed-entry chain on a typed
6385/// path identity. The three `format!` literals that used to live inline
6386/// in `domain.rs::kwarg_form` / `kwarg_item_form` / `kwargs_pos_form`
6387/// (three byte-identical `format!` shapes, one per helper) collapse into
6388/// ONE `Display` impl on this enum — the canonical literals (`":<key>"`
6389/// / `":<key>[<idx>]"` / `"kwargs[<idx>]"`) live in ONE place, so a typo
6390/// in any one of the three shapes can never drift independent of the
6391/// others (THEORY.md §VI.1 three-times rule). Adding a fourth path shape
6392/// (e.g., `:<key>.<field>` for nested-struct kwarg failures or
6393/// `:<key>::<variant>` for sum-typed kwarg failures) requires extending
6394/// this enum, which rustc-enforces matching at the `Display` projection
6395/// site.
6396///
6397/// `KwargPath` owns its `key` payload as `String` so it can inhabit
6398/// `LispError::TypeMismatch.form` (and any future error variant) without
6399/// a borrow constraint. The owned shape is the typed-slot promotion the
6400/// prior-run `KwargPath` landing pre-staged: every projection site that
6401/// used to produce a `String` via `KwargPath::Named(key).to_string()` (the
6402/// three sibling helpers `kwarg_form` / `kwarg_item_form` /
6403/// `kwargs_pos_form` and the fourth `kwarg_deserialize_form` helper) now
6404/// produces a typed `KwargPath` value directly; `type_mismatch` and every
6405/// `TypeMismatch.form` consumer pattern-match on the variant identity
6406/// (`KwargPath::Item { key, idx }`, `KwargPath::Slot(idx)`, etc.) instead
6407/// of substring-parsing the rendered prefix.
6408///
6409/// `Copy` is dropped because `String` is not `Copy`; `Clone + Debug +
6410/// PartialEq + Eq` are retained (same posture as every other owned-data
6411/// `LispError` field). The closed-set structural-completeness floor is
6412/// unchanged — only the data ownership changed.
6413///
6414/// Theory anchor: THEORY.md §V.1 — knowable platform; the closed set of
6415/// kwargs-path shapes becomes a TYPE rather than three byte-identical
6416/// `format!` literals scattered across helper definitions. THEORY.md
6417/// §VI.1 — generation over composition; the typed enum lands the
6418/// structural-completeness floor for the kwargs-path surface, parallel
6419/// to how `CompilerSpecIoStage` lands it for the disk-persistence
6420/// surface, `MacroDefHead` for the macro-definition-head surface,
6421/// `TemplateInvariantKind` for the bytecode-runtime surface, and
6422/// `UnquoteForm` for the template-marker surface. THEORY.md §II.1
6423/// invariant 1 — typed entry; the kwargs-path's renderable identity is
6424/// part of the proof of WHICH kwarg-gate fired, and the typed enum makes
6425/// that identity first-class — now as load-bearing data on the
6426/// `TypeMismatch` variant rather than as a projection-to-String.
6427#[derive(Debug, Clone, PartialEq, Eq)]
6428pub enum KwargPath {
6429    /// `:<key>` — failure at a named kwarg (`extract_string`,
6430    /// `extract_int`, `extract_float`, `extract_bool`, etc.). The `key`
6431    /// is the offending kwarg's identifier, owned so the variant lives
6432    /// independent of the call frame.
6433    Named(String),
6434    /// `:<key>[<idx>]` — failure at the Nth item of a list-typed kwarg
6435    /// (`extract_string_list` per-item failure). The `key` is the
6436    /// containing kwarg's identifier (owned); `idx` is the 0-based item
6437    /// index inside that kwarg's list value.
6438    Item { key: String, idx: usize },
6439    /// `kwargs[<idx>]` — failure at the Nth slot of the kwargs slice
6440    /// before a key was known (`parse_kwargs`'s
6441    /// "this-position-must-be-a-keyword" gate firing on an even-position
6442    /// slot). `idx` is the 0-based position into the raw kwargs slice
6443    /// (not into a particular kwarg's value).
6444    Slot(usize),
6445}
6446
6447impl KwargPath {
6448    /// Owned constructor for the `:<key>` shape — used by every call site
6449    /// that has a `&str` borrow of the kwarg identifier and wants to lift
6450    /// it into the typed enum without an inline `.to_string()` projection.
6451    #[must_use]
6452    pub fn named(key: &str) -> Self {
6453        Self::Named(key.to_string())
6454    }
6455
6456    /// Owned constructor for the `:<key>[<idx>]` shape — sibling of
6457    /// `named`, threading the per-item index alongside the kwarg key.
6458    #[must_use]
6459    pub fn item(key: &str, idx: usize) -> Self {
6460        Self::Item {
6461            key: key.to_string(),
6462            idx,
6463        }
6464    }
6465
6466    /// Discriminator projection — strips the payload and returns the
6467    /// closed-set [`KwargPathKind`]. The same shape every sibling
6468    /// payload-carrying closed-set enum in the workspace projects through
6469    /// (e.g. `tatara_process::lifetime_clock::AutoTerminate::kind` →
6470    /// [`crate::error::KwargPath`]'s tatara-process cousin
6471    /// `AutoTerminateKind`, `TerminateReason::kind` →
6472    /// `TerminateReasonKind`, `crate::matrix::SelectStrategy::kind` →
6473    /// `SelectStrategyKind`).
6474    ///
6475    /// Consumers that group [`LispError::TypeMismatch`] /
6476    /// [`LispError::KwargDeserialize`] failures by path-shape
6477    /// CATEGORY rather than full path identity (failure-cluster metrics
6478    /// labelled `path_kind=named` / `path_kind=item` / `path_kind=slot`,
6479    /// an LSP that surfaces "this is a per-item failure" before drilling
6480    /// into the bracket-suffix, a future `tatara-check` diagnostic
6481    /// histogram that buckets by kind) project through this method
6482    /// instead of destructuring the variant and discarding the payload at
6483    /// every site. Adding a fourth path shape (e.g., `:<key>.<field>`
6484    /// for nested-struct kwarg failures or `:<key>::<variant>` for
6485    /// sum-typed kwarg failures) requires extending [`KwargPathKind`],
6486    /// which rustc-enforces matching at this projection.
6487    #[must_use]
6488    pub const fn kind(&self) -> KwargPathKind {
6489        match self {
6490            Self::Named(_) => KwargPathKind::Named,
6491            Self::Item { .. } => KwargPathKind::Item,
6492            Self::Slot(_) => KwargPathKind::Slot,
6493        }
6494    }
6495}
6496
6497impl std::fmt::Display for KwargPath {
6498    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6499        match self {
6500            Self::Named(key) => write!(f, ":{key}"),
6501            Self::Item { key, idx } => write!(f, ":{key}[{idx}]"),
6502            Self::Slot(idx) => write!(f, "kwargs[{idx}]"),
6503        }
6504    }
6505}
6506
6507/// The closed set of [`KwargPath`] kinds — the discriminator view,
6508/// payload-stripped, that sibling closed-set lifts in this crate carry
6509/// (see [`SexpShape`], [`ExpectedKwargShape`], [`MacroDefHead`],
6510/// [`UnquoteForm`], [`CompilerSpecIoStage`], [`TemplateInvariantKind`]).
6511///
6512/// Mirrors the workspace-wide [`payload-carrier, payload-stripped kind]
6513/// pairing — `AutoTerminate` / `AutoTerminateKind`, `TerminateReason` /
6514/// `TerminateReasonKind`, `SelectStrategy` / `SelectStrategyKind`,
6515/// `ChannelVariant` / `ChannelKind`, `ArtifactVariant` / `ArtifactKind`,
6516/// `EncapsulationTarget` / `EncapsulationKind`. [`KwargPath`] owns the
6517/// per-variant payload (`key: String`, `idx: usize`); [`KwargPathKind`]
6518/// is the `Copy`-able discriminator view callers reach when they want
6519/// the CATEGORY without the payload.
6520///
6521/// Drives the `label` / [`Display`] / [`FromStr`] triad over [`Self::ALL`]
6522/// so a new variant added with an `ALL` entry automatically extends the
6523/// parser, the canonical wire-format projection, and any future
6524/// metrics-label / failure-cluster bucket that needs to enumerate the
6525/// kwargs-path categories. The `[Self; 3]` array literal forces the arity
6526/// so a fourth variant — a hypothetical `Field` for nested-struct kwarg
6527/// failures (`:<key>.<field>`) or `Variant` for sum-typed kwarg failures
6528/// (`:<key>::<variant>`) — cannot land without bumping the constant.
6529///
6530/// Theory anchor: THEORY.md §V.1 — knowable platform; the
6531/// payload-stripped kind becomes a TYPE rather than three byte-identical
6532/// `matches!` discriminator literals scattered across consumers.
6533/// THEORY.md §VI.1 — generation over composition; the typed kind enum
6534/// lands the structural-completeness floor for the path-shape category
6535/// surface, parallel to how [`KwargPath`] lands it for the path-identity
6536/// surface, [`ExpectedKwargShape`] for the expected-shape surface, etc.
6537#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, tatara_closed_set::DeriveClosedSet)]
6538#[closed_set(via = "label", display, generate_unknown = "kwarg path kind")]
6539pub enum KwargPathKind {
6540    /// The kind-view of [`KwargPath::Named`] — `:<key>` failures at a
6541    /// named kwarg (typed-atom extractors, `Option<T>` paths).
6542    Named,
6543    /// The kind-view of [`KwargPath::Item`] — `:<key>[<idx>]` failures
6544    /// at the Nth item of a list-typed kwarg
6545    /// (`extract_string_list` per-item).
6546    Item,
6547    /// The kind-view of [`KwargPath::Slot`] — `kwargs[<idx>]` failures
6548    /// at a kwargs slice slot before a key was known
6549    /// (`parse_kwargs`'s slot-must-be-a-keyword gate).
6550    Slot,
6551}
6552
6553impl KwargPathKind {
6554    /// The closed set — single source of truth for [`Self::label`] /
6555    /// [`Display`] / [`FromStr`]. The `[Self; 3]` arity is forced at the
6556    /// declaration so a fourth variant cannot land without bumping the
6557    /// constant.
6558    pub const ALL: [Self; 3] = [Self::Named, Self::Item, Self::Slot];
6559
6560    /// Canonical `&'static str` category label for [`Self::Named`] —
6561    /// the `:<key>` failure category emitted by every typed-atom
6562    /// extractor (`extract_string`, `extract_int`, `extract_float`,
6563    /// `extract_bool`, and their `Option<T>` siblings). Sibling
6564    /// posture to [`Self::ITEM_LABEL`] (`"item"`) and
6565    /// [`Self::SLOT_LABEL`] (`"slot"`) on the same category-label
6566    /// algebra layer.
6567    ///
6568    /// Pre-lift the same `"named"` bytes lived inline at the
6569    /// [`Self::label`] match arm plus at the sibling truth-table tests
6570    /// (`kwarg_path_kind_all_is_unique_and_complete` sorted-labels
6571    /// pin, `kwarg_path_kind_label_does_not_overlap_kwarg_path_display_renderings`
6572    /// disjointness sweep) — the ≥2 PRIME-DIRECTIVE trigger. Post-lift
6573    /// the (`Named` variant, canonical `&'static str`) pairing binds
6574    /// at ONE `pub const` on the typed [`KwargPathKind`] algebra: the
6575    /// [`Self::label`] arm and every consumer that pins the exact
6576    /// bytes route through this constant. Sibling posture to the
6577    /// closed set of per-role canonical `&'static str` labels on the
6578    /// substrate's other closed-set outer algebras:
6579    /// [`MacroDefHead::DEFMACRO_KEYWORD`] (`"defmacro"`),
6580    /// [`crate::ast::Atom::TRUE_LITERAL`] (`"#t"`),
6581    /// [`crate::ast::QuoteForm::QUOTE_PREFIX`] (`"'"`),
6582    /// [`crate::macro_expand::MacroParams::REST_MARKER`] (`"&rest"`).
6583    pub const NAMED_LABEL: &'static str = "named";
6584
6585    /// Canonical `&'static str` category label for [`Self::Item`] —
6586    /// the `:<key>[<idx>]` failure category emitted by
6587    /// `extract_string_list`'s per-item gate. Sibling posture to
6588    /// [`Self::NAMED_LABEL`] (`"named"`) and [`Self::SLOT_LABEL`]
6589    /// (`"slot"`) on the same category-label algebra layer.
6590    ///
6591    /// Pre-lift the same `"item"` bytes lived inline at the
6592    /// [`Self::label`] match arm plus at the sibling truth-table
6593    /// tests. Post-lift the (`Item` variant, canonical `&'static str`)
6594    /// pairing binds at ONE `pub const` on the typed [`KwargPathKind`]
6595    /// algebra.
6596    pub const ITEM_LABEL: &'static str = "item";
6597
6598    /// Canonical `&'static str` category label for [`Self::Slot`] —
6599    /// the `kwargs[<idx>]` failure category emitted by
6600    /// `parse_kwargs`'s slot-must-be-a-keyword gate. Sibling posture
6601    /// to [`Self::NAMED_LABEL`] (`"named"`) and [`Self::ITEM_LABEL`]
6602    /// (`"item"`) on the same category-label algebra layer.
6603    ///
6604    /// Pre-lift the same `"slot"` bytes lived inline at the
6605    /// [`Self::label`] match arm plus at the sibling truth-table
6606    /// tests. Post-lift the (`Slot` variant, canonical `&'static str`)
6607    /// pairing binds at ONE `pub const` on the typed [`KwargPathKind`]
6608    /// algebra.
6609    pub const SLOT_LABEL: &'static str = "slot";
6610
6611    /// The closed set of three canonical `&'static str` category
6612    /// labels — the [`Self::NAMED_LABEL`] (`"named"`) typed-atom-
6613    /// extractor failure category followed by the [`Self::ITEM_LABEL`]
6614    /// (`"item"`) list-item failure category followed by the
6615    /// [`Self::SLOT_LABEL`] (`"slot"`) kwargs-slice-slot failure
6616    /// category. Canonical declaration order matches [`Self::ALL`] so
6617    /// `Self::LABELS[i] == Self::ALL[i].label()` element-wise —
6618    /// pinned by `kwarg_path_kind_labels_align_with_all_by_index`.
6619    ///
6620    /// Sibling posture to [`MacroDefHead::KEYWORDS`]
6621    /// (`[&'static str; 3]`) — every closed-set algebra now pins its
6622    /// label-projection ALL array at the declaration site via a
6623    /// forced-arity `[&'static str; N]` array whose length fails
6624    /// compilation if a new variant lands without being added to the
6625    /// set. Adding a hypothetical fourth category (a `Root` failure
6626    /// category for a future kwargs-root-level rejection, a `Tail`
6627    /// failure category for a future nested-list-tail extractor)
6628    /// extends [`Self::ALL`] ONCE + [`Self::label`] ONCE +
6629    /// [`Self::LABELS`] ONCE + adds ONE per-role label `pub const`
6630    /// — rustc's forced-arity check on the `[Self; N]` array +
6631    /// `[&'static str; N]` array pair enforces every downstream
6632    /// consumer picks up the extension mechanically.
6633    ///
6634    /// Future consumers that compose against [`Self::LABELS`]: an
6635    /// LSP / REPL completion provider surfacing every legal category
6636    /// name in a `path_kind=` metrics query bar
6637    /// (`Self::LABELS.iter()` is the ONE typed sweep over every legal
6638    /// kwarg-path category), a `tatara-check` coverage assertion
6639    /// (every workspace `.lisp` file's typed-atom rejection must
6640    /// classify to some entry of `Self::LABELS`), a Sekiban
6641    /// audit-trail metric jointly labeled by [`Self::label`] whose
6642    /// metric-label set IS `Self::LABELS` (e.g.
6643    /// `tatara_lisp_kwarg_type_mismatch_total{path_kind="named"}`).
6644    pub const LABELS: [&'static str; 3] = [Self::NAMED_LABEL, Self::ITEM_LABEL, Self::SLOT_LABEL];
6645
6646    /// Project the typed [`KwargPathKind`] to the canonical `&'static str`
6647    /// literal — lowercase byte-equal to the variant name (`"named"` /
6648    /// `"item"` / `"slot"`). The labels are kept distinct from the
6649    /// [`KwargPath::Display`] renderings (`":<key>"` / `":<key>[<idx>]"`
6650    /// / `"kwargs[<idx>]"`) because this projection names the CATEGORY,
6651    /// not the rendered identity — a metrics label `path_kind="named"`
6652    /// makes more sense at the kind boundary than a path-prefix template
6653    /// would.
6654    ///
6655    /// Body routes each arm through the per-role [`Self::NAMED_LABEL`] /
6656    /// [`Self::ITEM_LABEL`] / [`Self::SLOT_LABEL`] `pub const` so the
6657    /// three canonical `&'static str` labels live at ONE `pub const`
6658    /// per variant rather than at TWO sites (the per-role `pub const`
6659    /// AND an inline arm literal). Sibling posture to
6660    /// [`MacroDefHead::keyword`]'s arms routing through the per-role
6661    /// [`MacroDefHead::DEFMACRO_KEYWORD`] / [`MacroDefHead::DEFPOINT_TEMPLATE_KEYWORD`]
6662    /// / [`MacroDefHead::DEFCHECK_KEYWORD`] constants.
6663    ///
6664    /// Same shape every sibling kind-projection in the workspace uses
6665    /// (`AutoTerminateKind::as_str`, `TerminateReasonKind::as_str`,
6666    /// [`SexpShape::label`], [`ExpectedKwargShape::label`]).
6667    #[must_use]
6668    pub const fn label(self) -> &'static str {
6669        match self {
6670            Self::Named => Self::NAMED_LABEL,
6671            Self::Item => Self::ITEM_LABEL,
6672            Self::Slot => Self::SLOT_LABEL,
6673        }
6674    }
6675}
6676
6677// `impl std::fmt::Display for KwargPathKind` + `impl std::str::FromStr
6678// for KwargPathKind` + `impl tatara_closed_set::ClosedSet for KwargPathKind` +
6679// `pub struct UnknownKwargPathKind(pub String)` are generated by
6680// `#[derive(tatara_closed_set::DeriveClosedSet)]` on the enum declaration
6681// above. `label` delegates to the inherent `KwargPathKind::label` via
6682// `#[closed_set(via = "label")]` — the inherent name coincides with
6683// the trait method name here, but the delegation stays explicit so the
6684// SAME wiring shape applies whether the inherent projection is named
6685// `label` / `prefix` / `marker` / `keyword` / `as_str`. The `display`
6686// flag emits the substrate-wide `f.write_str(Self::label(*self))` block.
6687// `#[closed_set(generate_unknown = "kwarg path kind")]` emits the typed
6688// parse-rejection carrier with the substrate-wide `Debug + Clone +
6689// PartialEq + Eq + thiserror::Error` derives and the `#[error("unknown
6690// kwarg path kind: {0}")]` annotation byte-for-byte; the explicit label
6691// matches the auto-derived `pascal_to_spaced_lowercase("KwargPathKind")`
6692// projection byte-for-byte but pins the pre-lift wording. Round-trip +
6693// cross-axis rejection (path-rendering literals `":foo"` / `":foo[0]"` /
6694// `"kwargs[0]"`) pinned by
6695// `kwarg_path_kind_label_round_trips_through_from_str` +
6696// `unknown_kwarg_path_kind_carries_offending_input_verbatim`.
6697
6698/// Closed-set identifier for the `expected:` slot of a
6699/// `LispError::TypeMismatch` diagnostic — the seven reachable
6700/// expected-shape labels the typed-entry kwarg gate emits:
6701/// `Keyword` (the `parse_kwargs` slot-must-be-a-keyword gate),
6702/// `String` / `Int` / `Number` / `Bool` (the typed-atom extractors —
6703/// `extract_string`, `extract_int`, `extract_float`, `extract_bool`,
6704/// and their `Option` siblings, plus `extract_string_list`'s per-item
6705/// `string` gate), `List` (the `extract_vec_via_serde` outer-shape
6706/// gate), and `ListOfStrings` (the `extract_string_list` outer-shape
6707/// gate). Encoded as a typed enum so the closed set becomes
6708/// load-bearing data on `LispError::TypeMismatch.expected` rather than
6709/// a `&'static str` literal scattered across eleven call sites in
6710/// `domain.rs`.
6711///
6712/// Mirror at the expected-shape boundary of the prior-run `KwargPath`
6713/// (kwargs-path-shape closed set), `MacroDefHead` (macro-definition-
6714/// head closed set), `CompilerSpecIoStage` (disk-persistence surface),
6715/// `TemplateInvariantKind` (bytecode-runtime surface), and
6716/// `UnquoteForm` (template-marker syntactic forms) closed-set lifts:
6717/// those enums key their respective rejection variants on a typed
6718/// identity carried inside the variant's data shape; this enum keys
6719/// the SECOND slot (`expected`) of every `LispError::TypeMismatch`
6720/// site on a typed expected-shape identity, alongside the
6721/// already-typed `form: KwargPath`. After this lift the type-mismatch
6722/// variant's identity is fully closed-set typed in TWO of its three
6723/// slots — only `got: &'static str` remains as a `&'static str`
6724/// projection, and that slot's compile-time guarantee is sourced from
6725/// `crate::domain::sexp_type_name`'s exhaustive `Sexp` match.
6726///
6727/// Adding a future expected-shape (e.g. `Float` once `extract_float`
6728/// stops accepting integers, `Symbol` if a future extractor accepts
6729/// only `Sexp::Atom(Symbol)`, or a parameterized `ListOf(Box<Self>)`
6730/// for nested-typed-vec extractors) requires extending this enum,
6731/// which rustc-enforces matching at every projection site
6732/// (`label()`).
6733///
6734/// `label(self) -> &'static str` projects the typed `ExpectedKwargShape`
6735/// back to the canonical literal for `LispError::Display` rendering.
6736/// The `&'static str` lifetime is load-bearing: it lets the variant
6737/// project through this method into the `expected {expected}` slot of
6738/// the `#[error(...)]` annotation without an allocation, parallel to
6739/// how `MacroDefHead::keyword()`, `UnquoteForm::marker()`, and
6740/// `CompilerSpecIoStage::operation()` / `label()` feed their
6741/// respective `LispError::*` Display impls.
6742///
6743/// Theory anchor: THEORY.md §V.1 — knowable platform; the closed set
6744/// of expected-shape labels becomes a TYPE rather than eleven
6745/// `&'static str` literal call sites scattered across the kwarg
6746/// extractors. A typo in any literal can never drift into the
6747/// diagnostic at runtime; a regression that drifts the expected-shape
6748/// label (e.g. a typo `"strin"` for `"string"`) becomes a type error
6749/// at the call site, not a runtime substring drift. THEORY.md §VI.1 —
6750/// generation over composition; the typed enum lands the structural-
6751/// completeness floor for the expected-shape surface, parallel to how
6752/// `KwargPath` lands it for the kwargs-path surface, `MacroDefHead`
6753/// for the macro-definition-head surface, `UnquoteForm` for the
6754/// template-marker surface, `CompilerSpecIoStage` for the disk-
6755/// persistence surface, and `TemplateInvariantKind` for the bytecode-
6756/// runtime surface. THEORY.md §II.1 invariant 1 — typed entry; the
6757/// expected-shape identity is part of the proof of WHICH typed-entry
6758/// kwarg gate fired, and the typed enum makes that identity first-
6759/// class as load-bearing data on the variant rather than as a
6760/// projection-to-String at the helper boundary.
6761#[derive(Debug, Clone, Copy, PartialEq, Eq, tatara_closed_set::DeriveClosedSet)]
6762#[closed_set(via = "label", display, generate_unknown = "expected kwarg shape")]
6763pub enum ExpectedKwargShape {
6764    /// `"keyword"` — emitted by `parse_kwargs`'s
6765    /// "this-position-must-be-a-keyword" gate when an even-position
6766    /// slot in the kwargs slice isn't a `Sexp::Atom(Keyword(_))`.
6767    Keyword,
6768    /// `"string"` — emitted by `extract_string` /
6769    /// `extract_optional_string` (the kwarg's value isn't a
6770    /// `Sexp::Atom(Str(_))`) AND by `extract_string_list`'s per-item
6771    /// gate (an item inside a list-typed kwarg isn't a string).
6772    String,
6773    /// `"int"` — emitted by `extract_int` / `extract_optional_int`
6774    /// when the kwarg's value isn't a `Sexp::Atom(Int(_))`.
6775    Int,
6776    /// `"number"` — emitted by `extract_float` /
6777    /// `extract_optional_float` when the kwarg's value isn't a
6778    /// numeric atom. Wider than `Int`: `extract_float` accepts both
6779    /// `Sexp::Atom(Float(_))` and `Sexp::Atom(Int(_))` via
6780    /// `Sexp::as_float`, so the expected-shape label is the union
6781    /// "number" rather than the narrower "float".
6782    Number,
6783    /// `"bool"` — emitted by `extract_bool` / `extract_optional_bool`
6784    /// when the kwarg's value isn't a `Sexp::Atom(Bool(_))`.
6785    Bool,
6786    /// `"list"` — emitted by `extract_vec_via_serde`'s outer-shape
6787    /// gate when the kwarg's value isn't a `Sexp::List(_)`. Used by
6788    /// the universal-Deserialize fallthrough for any `Vec<T>` field.
6789    List,
6790    /// `"list of strings"` — emitted by `extract_string_list`'s
6791    /// outer-shape gate when the kwarg's value isn't a
6792    /// `Sexp::List(_)`. Wider than `List`: names the expected
6793    /// element-type so the diagnostic reads `expected list of
6794    /// strings, got string` instead of the bare `expected list, got
6795    /// string`. The per-item gate fires `String` (the narrower
6796    /// expected-shape for the element-type failure).
6797    ListOfStrings,
6798}
6799
6800impl ExpectedKwargShape {
6801    /// The closed set of seven reachable expected-kwarg shapes — single
6802    /// source of truth that drives the [`Self::label`] / [`fmt::Display`]
6803    /// projection AND the [`FromStr`] decode sweep keyed on
6804    /// [`Self::label`]. Adding a hypothetical eighth variant (e.g.
6805    /// `Float` once `extract_float` stops accepting integers, `Symbol`
6806    /// if a future extractor accepts only `Sexp::Atom(Symbol)`, or a
6807    /// parameterized `ListOf(Box<Self>)` for nested-typed-vec
6808    /// extractors) lands at one [`Self::ALL`] entry + one [`Self::label`]
6809    /// arm — exhaustively checked by the compiler (the `[Self; 7]`
6810    /// array literal forces the arity) AND by the per-variant
6811    /// truth-table tests below.
6812    ///
6813    /// Sibling closed-set lift to every other typed-shape enum the
6814    /// substrate carries: this crate's own [`SexpShape::ALL`] (the
6815    /// twelve reachable outer shapes the reader can produce — peer
6816    /// axis on the same `Sexp` algebra, whose vocabulary overlaps
6817    /// with this set on five of seven entries — `"keyword"`,
6818    /// `"string"`, `"int"`, `"bool"`, `"list"` — and does NOT overlap
6819    /// on two — `"number"` ⊎ `"list of strings"`; the overlap is
6820    /// intentional and pinned by the cross-axis tests), and across
6821    /// the workspace ([`MacroDefHead::ALL`], [`UnquoteForm::ALL`],
6822    /// [`crate::ast::AtomKind::ALL`], [`crate::ast::QuoteForm::ALL`],
6823    /// `ConditionKind::ALL`, `ProcessPhase::ALL`,
6824    /// `ProcessSignal::ALL`, `ChannelKind::ALL`, `IntentKind::ALL`,
6825    /// `LifetimeKind::ALL`, `RequestorKind::ALL`, `ReceiptKind::ALL`,
6826    /// …) every one of which paired its typed projection with `ALL`
6827    /// before this lift.
6828    ///
6829    /// Future consumers that compose against `ALL`: LSP / REPL
6830    /// completion for the operator-facing rendered expected-shape
6831    /// label (every `expected X, ...` substring in `LispError`'s
6832    /// rendered diagnostics keys on this set's projection through
6833    /// [`Self::label`]); `tatara-check` coverage assertions over
6834    /// which expected-shape variants reach a `TypeMismatch.expected`
6835    /// arm at all — the typed sweep replaces the per-call-site
6836    /// vocabulary of seven `&'static str` literals; any future
6837    /// audit-trail metric jointly labeled by [`Self::label`] (e.g.
6838    /// `tatara_lisp_type_mismatch_total{expected="number"}`) — the
6839    /// metric label set IS [`Self::ALL`] mapped through
6840    /// [`Self::label`].
6841    pub const ALL: [Self; 7] = [
6842        Self::Keyword,
6843        Self::String,
6844        Self::Int,
6845        Self::Number,
6846        Self::Bool,
6847        Self::List,
6848        Self::ListOfStrings,
6849    ];
6850
6851    /// Canonical `&'static str` bytes for the `Keyword` expected-shape —
6852    /// aliases [`SexpShape::KEYWORD_LABEL`] on the ExpectedKwargShape ⊂
6853    /// SexpShape 5-of-7 subset carving so the shared `"keyword"`
6854    /// vocabulary binds at ONE `pub const` on the parent-superset's
6855    /// `SexpShape::KEYWORD_LABEL` arm rather than at TWO independent
6856    /// literal-discipline sites (the per-role `pub const` here AND the
6857    /// per-role `pub const` on `SexpShape`). Per-role peer of
6858    /// `Self::Keyword` on the closed-set outer algebra; every consumer
6859    /// of the `"keyword"` bytes (Display, FromStr, the derive-
6860    /// generated `UnknownExpectedKwargShape` carrier, LSP completion,
6861    /// audit-trail metric labels) routes through THIS constant, so a
6862    /// spelling migration (a hypothetical port to `":keyword"`,
6863    /// `"kwarg-key"`, or a case-fold to `"Keyword"`) at
6864    /// [`SexpShape::KEYWORD_LABEL`] propagates HERE structurally at
6865    /// rustc time rather than through a parallel literal edit.
6866    ///
6867    /// Sibling posture to [`Self::STRING_LABEL`], [`Self::INT_LABEL`],
6868    /// [`Self::BOOL_LABEL`], [`Self::LIST_LABEL`] — the FIVE
6869    /// ExpectedKwargShape variants that share bytes with a matching
6870    /// [`SexpShape`] carving arm all bind through this same alias
6871    /// chain. The TWO non-overlapping siblings [`Self::NUMBER_LABEL`]
6872    /// (`"number"` — the wider numeric-union label distinct from
6873    /// [`SexpShape::FLOAT_LABEL`]'s `"float"`) and
6874    /// [`Self::LIST_OF_STRINGS_LABEL`] (`"list of strings"` — an
6875    /// element-typed refinement with no [`SexpShape`] peer) stay as
6876    /// direct literals since there is no superset arm to alias
6877    /// through. The 5-of-7 carving IS the intersection
6878    /// `ExpectedKwargShape::ALL ∩ SexpShape::ALL` on the
6879    /// `&'static str` label vocabulary.
6880    pub const KEYWORD_LABEL: &'static str = SexpShape::KEYWORD_LABEL;
6881
6882    /// Canonical `&'static str` bytes for the `String` expected-shape —
6883    /// aliases [`SexpShape::STRING_LABEL`] on the ExpectedKwargShape ⊂
6884    /// SexpShape 5-of-7 subset carving. Per-role peer of `Self::String`
6885    /// on the closed-set outer algebra. Emitted by `extract_string` /
6886    /// `extract_optional_string` AND by `extract_string_list`'s per-
6887    /// item gate. See [`Self::KEYWORD_LABEL`] for the alias-chain shape
6888    /// every sibling in the 5-of-7 carving shares.
6889    pub const STRING_LABEL: &'static str = SexpShape::STRING_LABEL;
6890
6891    /// Canonical `&'static str` bytes for the `Int` expected-shape —
6892    /// aliases [`SexpShape::INT_LABEL`] on the ExpectedKwargShape ⊂
6893    /// SexpShape 5-of-7 subset carving. Per-role peer of `Self::Int`
6894    /// on the closed-set outer algebra; the sibling
6895    /// [`Self::NUMBER_LABEL`] distinguishes the wider numeric-union
6896    /// case `extract_float` emits (which has NO [`SexpShape`] peer —
6897    /// [`SexpShape::FLOAT_LABEL`] is the narrower `"float"`). See
6898    /// [`Self::KEYWORD_LABEL`] for the alias-chain shape.
6899    pub const INT_LABEL: &'static str = SexpShape::INT_LABEL;
6900
6901    /// Canonical `&'static str` bytes for the `Number` expected-shape —
6902    /// emitted by `extract_float` / `extract_optional_float` when the
6903    /// kwarg's value isn't a numeric atom. The "number" naming (not
6904    /// "float") is load-bearing: `extract_float` accepts BOTH
6905    /// `Sexp::Atom(Float(_))` and `Sexp::Atom(Int(_))` via
6906    /// `Sexp::as_float`, so the expected-shape label names the union
6907    /// rather than the narrower Float element-type. INTENTIONALLY NOT
6908    /// aliased through [`SexpShape`]: the `SexpShape::Float` variant's
6909    /// label is `"float"` (the NARROWER element-type), not `"number"`,
6910    /// so this constant has NO peer on the parent-superset algebra to
6911    /// alias through. Divergence pinned by
6912    /// `sexp_shape_atom_carving_labels_align_with_expected_kwarg_shape_labels`'s
6913    /// `assert_ne!(SexpShape::FLOAT_LABEL, ExpectedKwargShape::NUMBER_LABEL)`
6914    /// disjointness pin — the TWO non-overlapping arms of the 7-arm
6915    /// closed set are the natural residual once the 5-of-7 subset
6916    /// carving aliases through [`SexpShape`].
6917    pub const NUMBER_LABEL: &'static str = "number";
6918
6919    /// Canonical `&'static str` bytes for the `Bool` expected-shape —
6920    /// aliases [`SexpShape::BOOL_LABEL`] on the ExpectedKwargShape ⊂
6921    /// SexpShape 5-of-7 subset carving. Per-role peer of `Self::Bool`
6922    /// on the closed-set outer algebra. Emitted by `extract_bool` /
6923    /// `extract_optional_bool` when the kwarg's value isn't a
6924    /// `Sexp::Atom(Bool(_))`. See [`Self::KEYWORD_LABEL`] for the
6925    /// alias-chain shape.
6926    pub const BOOL_LABEL: &'static str = SexpShape::BOOL_LABEL;
6927
6928    /// Canonical `&'static str` bytes for the `List` expected-shape —
6929    /// aliases [`SexpShape::LIST_LABEL`] on the ExpectedKwargShape ⊂
6930    /// SexpShape 5-of-7 subset carving. Per-role peer of `Self::List`
6931    /// on the closed-set outer algebra; the wider
6932    /// [`Self::LIST_OF_STRINGS_LABEL`] names the element-typed variant
6933    /// (which has NO [`SexpShape`] peer — the SexpShape algebra
6934    /// doesn't type-index list-of-strings as a distinct shape). See
6935    /// [`Self::KEYWORD_LABEL`] for the alias-chain shape.
6936    pub const LIST_LABEL: &'static str = SexpShape::LIST_LABEL;
6937
6938    /// Canonical `&'static str` bytes for the `ListOfStrings`
6939    /// expected-shape — emitted by `extract_string_list`'s outer-shape
6940    /// gate when the kwarg's value isn't a `Sexp::List(_)`. The
6941    /// `"list of strings"` naming is load-bearing: it names the
6942    /// element-type (String) so the diagnostic reads `expected list of
6943    /// strings, got string` instead of the ambiguous `expected list,
6944    /// got string`. The per-item gate fires `STRING_LABEL` (the
6945    /// narrower element-type failure).
6946    pub const LIST_OF_STRINGS_LABEL: &'static str = "list of strings";
6947
6948    /// Closed-set forced-arity ALL array over the canonical expected-
6949    /// shape `&'static str` bytes, in declaration order matching
6950    /// [`Self::ALL`] element-wise (pinned by
6951    /// `expected_kwarg_shape_labels_align_with_all_by_index`). Sibling
6952    /// posture to `KwargPathKind::LABELS: [&'static str; 3]`,
6953    /// `MacroDefHead::KEYWORDS: [&'static str; 3]`,
6954    /// `MacroParams::LAMBDA_LIST_KEYWORDS: [&'static str; 2]`,
6955    /// `Atom::BOOL_LITERALS: [&'static str; 2]`, and
6956    /// `QuoteForm::PREFIXES: [&'static str; 4]` — every closed-set
6957    /// outer projection on the substrate that carries an `&'static
6958    /// str`-per-variant label now pins its per-role canonical bytes
6959    /// at ONE `pub const` per role PLUS an ALL array for family-wide
6960    /// consumers.
6961    ///
6962    /// Future consumers that compose against `LABELS`: LSP completion
6963    /// surfacing the seven expected-shape labels in a
6964    /// `expected=` metrics query bar, a `tatara-check` coverage
6965    /// assertion sweeping `LABELS` over workspace `.lisp` files'
6966    /// kwarg-gate rejection sites, a Sekiban audit-trail metric
6967    /// labeled by the canonical expected-shape (e.g.
6968    /// `tatara_lisp_type_mismatch_total{expected="number"}`) whose
6969    /// metric-label set IS `LABELS`. Adding an eighth variant (e.g.
6970    /// `Float` once `extract_float` stops accepting integers,
6971    /// `Symbol`, or a parameterized `ListOfInts`) extends
6972    /// [`Self::ALL`] AND [`Self::LABELS`] AND [`Self::label`]'s arm
6973    /// AND one new per-role `pub const` in lockstep — rustc's forced-
6974    /// arity check on the two `[_; N]` arrays fails compilation if
6975    /// EITHER ALL array grows without the other.
6976    pub const LABELS: [&'static str; 7] = [
6977        Self::KEYWORD_LABEL,
6978        Self::STRING_LABEL,
6979        Self::INT_LABEL,
6980        Self::NUMBER_LABEL,
6981        Self::BOOL_LABEL,
6982        Self::LIST_LABEL,
6983        Self::LIST_OF_STRINGS_LABEL,
6984    ];
6985
6986    /// Project the typed `ExpectedKwargShape` to the canonical
6987    /// `&'static str` literal — feeds the `LispError::TypeMismatch`
6988    /// Display rendering via the `#[error(...)]` annotation. The
6989    /// `&'static str` lifetime is load-bearing: it lets the variant
6990    /// project through this method into the `expected {expected}` slot
6991    /// of the `#[error(...)]` annotation without an allocation,
6992    /// parallel to how `MacroDefHead::keyword()`,
6993    /// `UnquoteForm::marker()`, [`SexpShape::label`], and
6994    /// `CompilerSpecIoStage::operation()` / `label()` feed their
6995    /// respective `LispError::*` Display impls.
6996    ///
6997    /// Each arm routes through the per-role `pub const` on
6998    /// `impl Self` ([`Self::KEYWORD_LABEL`], [`Self::STRING_LABEL`],
6999    /// [`Self::INT_LABEL`], [`Self::NUMBER_LABEL`],
7000    /// [`Self::BOOL_LABEL`], [`Self::LIST_LABEL`],
7001    /// [`Self::LIST_OF_STRINGS_LABEL`]) so the seven canonical
7002    /// expected-shape byte-strings bind at ONE typed source of truth
7003    /// per role rather than as inline literals scattered across the
7004    /// `match` body. Sibling posture to
7005    /// `KwargPathKind::label`'s post-lift arms.
7006    ///
7007    /// The bidirectional contract is anchored by tests:
7008    /// `label_renders_canonical_string_for_every_variant` pins each
7009    /// variant's canonical literal so a typo in any arm fails-loudly,
7010    /// `display_matches_label_for_every_variant` pins
7011    /// Display-equals-label so the `#[error(...)]` annotation's
7012    /// `{expected}` slot projects byte-for-byte through this method,
7013    /// and `expected_kwarg_shape_label_round_trips_through_from_str`
7014    /// pins the `label` ↔ [`FromStr`] round-trip for every variant in
7015    /// [`Self::ALL`] so the typed surface and the rendered diagnostic
7016    /// literal cannot drift.
7017    #[must_use]
7018    pub fn label(self) -> &'static str {
7019        match self {
7020            Self::Keyword => Self::KEYWORD_LABEL,
7021            Self::String => Self::STRING_LABEL,
7022            Self::Int => Self::INT_LABEL,
7023            Self::Number => Self::NUMBER_LABEL,
7024            Self::Bool => Self::BOOL_LABEL,
7025            Self::List => Self::LIST_LABEL,
7026            Self::ListOfStrings => Self::LIST_OF_STRINGS_LABEL,
7027        }
7028    }
7029}
7030
7031// `impl std::fmt::Display for ExpectedKwargShape` +
7032// `impl std::str::FromStr for ExpectedKwargShape` +
7033// `impl tatara_closed_set::ClosedSet for ExpectedKwargShape` +
7034// `pub struct UnknownExpectedKwargShape(pub String)` are generated by
7035// `#[derive(tatara_closed_set::DeriveClosedSet)]` on the enum declaration
7036// above. `label` delegates to the inherent `ExpectedKwargShape::label`
7037// via `#[closed_set(via = "label")]` — the inherent name coincides
7038// with the trait method name here; the delegation stays explicit so
7039// the SAME wiring shape applies whether the inherent projection is
7040// `label` / `prefix` / `marker` / `keyword` / `as_str`. The `display`
7041// flag emits the substrate-wide `f.write_str(Self::label(*self))`
7042// block. `#[closed_set(generate_unknown = "expected kwarg shape")]`
7043// emits the typed parse-rejection carrier with the substrate-wide
7044// `Debug + Clone + PartialEq + Eq + thiserror::Error` derives and the
7045// `#[error("unknown expected kwarg shape: {0}")]` annotation
7046// byte-for-byte; the explicit label matches the auto-derived
7047// `pascal_to_spaced_lowercase("ExpectedKwargShape")` projection
7048// byte-for-byte but pins the pre-lift wording. Round-trip + cross-axis
7049// rejection (`SexpShape` structural labels `"nil"` / `"symbol"` /
7050// `"float"` / `"quote"` / `"quasiquote"` / `"unquote"` /
7051// `"unquote-splice"`) pinned by
7052// `expected_kwarg_shape_label_round_trips_through_from_str` +
7053// `expected_kwarg_shape_from_str_accepts_only_canonical_labels`.
7054
7055/// Closed-set identifier for the outermost shape of a `Sexp` — the twelve
7056/// reachable shapes the reader can produce (`Nil` ⊎ `Symbol` ⊎ `Keyword` ⊎
7057/// `String` ⊎ `Int` ⊎ `Float` ⊎ `Bool` ⊎ `List` ⊎ `Quote` ⊎ `Quasiquote` ⊎
7058/// `Unquote` ⊎ `UnquoteSplice`). Carried as a typed slot on
7059/// `LispError::TypeMismatch.got` and `LispError::NamedFormNonSymbolName.got`
7060/// so authoring tools (REPL, LSP, `tatara-check`) bind to variant identity
7061/// (`SexpShape::Int` etc.) directly rather than substring-matching the
7062/// rendered `got` literal.
7063///
7064/// Mirror at the observed-shape boundary of the prior-run `KwargPath`
7065/// (kwargs-path-shape closed set), `ExpectedKwargShape` (kwarg-gate's
7066/// expected-shape closed set), `MacroDefHead` (macro-definition-head
7067/// closed set), `CompilerSpecIoStage` (disk-persistence surface),
7068/// `TemplateInvariantKind` (bytecode-runtime surface), and `UnquoteForm`
7069/// (template-marker syntactic forms) closed-set lifts: those enums key
7070/// their respective rejection variants on a typed identity carried inside
7071/// the variant's data shape; this enum keys the THIRD slot (`got`) of
7072/// every `LispError::TypeMismatch` site on a typed observed-shape identity
7073/// — alongside the already-typed `form: KwargPath` and
7074/// `expected: ExpectedKwargShape`. After this lift the type-mismatch
7075/// variant's identity is fully closed-set typed in ALL THREE of its slots
7076/// — no `&'static str` projection at any helper boundary, every reachable
7077/// identity encoded as a variant of a typed enum.
7078///
7079/// Adding a future `Sexp` variant (e.g. a hypothetical `Sexp::Vector` for
7080/// `#(...)` reader syntax, or `Sexp::Map` for `{...}`) requires extending
7081/// this enum, which rustc-enforces matching at every projection site
7082/// (`label()`, `crate::domain::sexp_shape`).
7083///
7084/// `label(self) -> &'static str` projects the typed `SexpShape` back to
7085/// the canonical literal for `LispError::Display` rendering. The
7086/// `&'static str` lifetime is load-bearing: it lets the variant project
7087/// through this method into the `got {got}` slot of the `#[error(...)]`
7088/// annotation without an allocation, parallel to how
7089/// `ExpectedKwargShape::label()`, `MacroDefHead::keyword()`,
7090/// `UnquoteForm::marker()`, and `CompilerSpecIoStage::operation()` /
7091/// `label()` feed their respective `LispError::*` Display impls.
7092///
7093/// Theory anchor: THEORY.md §V.1 — knowable platform; the closed set of
7094/// observed-Sexp shapes becomes a TYPE rather than a `&'static str`
7095/// projection through a string-keyed helper. A regression that drifts the
7096/// observed-shape label (e.g. a typo `"strin"` for `"string"`) becomes a
7097/// type error at the call site, not a runtime substring drift. THEORY.md
7098/// §VI.1 — generation over composition; the typed enum lands the
7099/// structural-completeness floor for the observed-shape surface, parallel
7100/// to how `ExpectedKwargShape` lands it for the expected-shape surface,
7101/// `KwargPath` for the kwargs-path surface, `MacroDefHead` for the
7102/// macro-definition-head surface, `UnquoteForm` for the template-marker
7103/// surface, `CompilerSpecIoStage` for the disk-persistence surface, and
7104/// `TemplateInvariantKind` for the bytecode-runtime surface. THEORY.md
7105/// §II.1 invariant 1 — typed entry; the observed-shape identity is part
7106/// of the proof of WHAT the typed-entry gate observed, and the typed enum
7107/// makes that identity first-class as load-bearing data on the variant
7108/// rather than as a projection-to-`&'static str` at the helper boundary.
7109#[derive(Debug, Clone, Copy, PartialEq, Eq, tatara_closed_set::DeriveClosedSet)]
7110#[closed_set(via = "label", display, generate_unknown = "sexp shape")]
7111pub enum SexpShape {
7112    /// `"nil"` — `Sexp::Nil`.
7113    Nil,
7114    /// `"symbol"` — `Sexp::Atom(Symbol(_))`.
7115    Symbol,
7116    /// `"keyword"` — `Sexp::Atom(Keyword(_))`.
7117    Keyword,
7118    /// `"string"` — `Sexp::Atom(Str(_))`.
7119    String,
7120    /// `"int"` — `Sexp::Atom(Int(_))`.
7121    Int,
7122    /// `"float"` — `Sexp::Atom(Float(_))`.
7123    Float,
7124    /// `"bool"` — `Sexp::Atom(Bool(_))`.
7125    Bool,
7126    /// `"list"` — `Sexp::List(_)`.
7127    List,
7128    /// `"quote"` — `Sexp::Quote(_)`.
7129    Quote,
7130    /// `"quasiquote"` — `Sexp::Quasiquote(_)`.
7131    Quasiquote,
7132    /// `"unquote"` — `Sexp::Unquote(_)`.
7133    Unquote,
7134    /// `"unquote-splice"` — `Sexp::UnquoteSplice(_)`.
7135    UnquoteSplice,
7136}
7137
7138impl SexpShape {
7139    /// The closed set of reachable `Sexp` outermost shapes — single
7140    /// source of truth that drives the [`Self::label`] / [`fmt::Display`]
7141    /// projection AND the [`FromStr`] decode sweep keyed on
7142    /// [`Self::label`]. Adding a hypothetical thirteenth variant (e.g.
7143    /// `Vector` for `#(...)` reader syntax, `Map` for `{...}`, or
7144    /// `Char` for `#\x`) lands at one `ALL` entry + one `label` arm —
7145    /// exhaustively checked by the compiler (the `[Self; 12]` array
7146    /// literal forces the arity) AND by the per-variant truth-table
7147    /// tests below. Sibling closed-set lift to every other typed-shape
7148    /// enum the substrate carries: this crate's own [`UnquoteForm`]
7149    /// (the four template markers — the only other closed set on the
7150    /// `Sexp` algebra with `Sexp variant ↔ enum variant` parity), and
7151    /// the cross-crate `tatara-process` family
7152    /// (`ConditionKind::ALL`, `ProcessPhase::ALL`,
7153    /// `ProcessSignal::ALL`, `ChannelKind::ALL`, `IntentKind::ALL`,
7154    /// …) every one of which paired its typed projection with `ALL`
7155    /// before this lift.
7156    ///
7157    /// Future consumers that compose against `ALL`:
7158    /// - LSP / REPL completion for the operator-facing rendered
7159    ///   shape label (every `expected X, got Y` substring in
7160    ///   `LispError`'s rendered diagnostics keys on this set);
7161    /// - `tatara-check` coverage assertions over which `SexpShape`
7162    ///   variants reach a `TypeMismatch.got` arm at all;
7163    /// - any future audit-trail metric jointly labeled by
7164    ///   `SexpShape::label` (e.g.
7165    ///   `tatara_lisp_type_mismatch_total{got="symbol"}`) — the
7166    ///   metric label set IS [`Self::ALL`] mapped through
7167    ///   [`Self::label`].
7168    pub const ALL: [Self; 12] = [
7169        Self::Nil,
7170        Self::Symbol,
7171        Self::Keyword,
7172        Self::String,
7173        Self::Int,
7174        Self::Float,
7175        Self::Bool,
7176        Self::List,
7177        Self::Quote,
7178        Self::Quasiquote,
7179        Self::Unquote,
7180        Self::UnquoteSplice,
7181    ];
7182
7183    /// Canonical `&'static str` bytes for the [`Self::Nil`] outer-shape —
7184    /// projects [`crate::ast::Sexp::Nil`] into the `got {got}` slot of every
7185    /// `LispError::*` Display rendering. Per-role peer of `Self::Nil` on
7186    /// the closed-set outer algebra.
7187    pub const NIL_LABEL: &'static str = "nil";
7188
7189    /// Canonical `&'static str` bytes for the [`Self::Symbol`] outer-shape —
7190    /// projects [`crate::ast::Sexp::Atom`] `(Symbol(_))` into diagnostic
7191    /// rendering. Per-role peer of `Self::Symbol`.
7192    pub const SYMBOL_LABEL: &'static str = "symbol";
7193
7194    /// Canonical `&'static str` bytes for the [`Self::Keyword`] outer-shape —
7195    /// projects [`crate::ast::Sexp::Atom`] `(Keyword(_))` into diagnostic
7196    /// rendering. Per-role peer of `Self::Keyword`; matches
7197    /// [`ExpectedKwargShape::KEYWORD_LABEL`] byte-for-byte — the cross-axis
7198    /// overlap on the same `Sexp` algebra is intentional and load-bearing,
7199    /// pinned by `sexp_shape_atom_carving_labels_align_with_expected_kwarg_shape_labels`.
7200    pub const KEYWORD_LABEL: &'static str = "keyword";
7201
7202    /// Canonical `&'static str` bytes for the [`Self::String`] outer-shape —
7203    /// projects [`crate::ast::Sexp::Atom`] `(Str(_))` into diagnostic
7204    /// rendering. Per-role peer of `Self::String`; matches
7205    /// [`ExpectedKwargShape::STRING_LABEL`] byte-for-byte.
7206    pub const STRING_LABEL: &'static str = "string";
7207
7208    /// Canonical `&'static str` bytes for the [`Self::Int`] outer-shape —
7209    /// projects [`crate::ast::Sexp::Atom`] `(Int(_))` into diagnostic
7210    /// rendering. Per-role peer of `Self::Int`; matches
7211    /// [`ExpectedKwargShape::INT_LABEL`] byte-for-byte.
7212    pub const INT_LABEL: &'static str = "int";
7213
7214    /// Canonical `&'static str` bytes for the [`Self::Float`] outer-shape —
7215    /// projects [`crate::ast::Sexp::Atom`] `(Float(_))` into diagnostic
7216    /// rendering. Per-role peer of `Self::Float`; distinct from
7217    /// [`ExpectedKwargShape::NUMBER_LABEL`] (which is the wider numeric-
7218    /// union label emitted at `extract_float` gates).
7219    pub const FLOAT_LABEL: &'static str = "float";
7220
7221    /// Canonical `&'static str` bytes for the [`Self::Bool`] outer-shape —
7222    /// projects [`crate::ast::Sexp::Atom`] `(Bool(_))` into diagnostic
7223    /// rendering. Per-role peer of `Self::Bool`; matches
7224    /// [`ExpectedKwargShape::BOOL_LABEL`] byte-for-byte.
7225    pub const BOOL_LABEL: &'static str = "bool";
7226
7227    /// Canonical `&'static str` bytes for the [`Self::List`] outer-shape —
7228    /// projects [`crate::ast::Sexp::List`] into diagnostic rendering.
7229    /// Per-role peer of `Self::List`; matches
7230    /// [`ExpectedKwargShape::LIST_LABEL`] byte-for-byte.
7231    pub const LIST_LABEL: &'static str = "list";
7232
7233    /// Canonical `&'static str` bytes for the [`Self::Quote`] outer-shape —
7234    /// projects [`crate::ast::Sexp::Quote`] into diagnostic rendering.
7235    /// Per-role peer of `Self::Quote`; matches
7236    /// [`crate::ast::QuoteForm::QUOTE_IAC_FORGE_TAG`] byte-for-byte — the
7237    /// cross-axis overlap on the shared quote-family sub-vocabulary is
7238    /// intentional and load-bearing, pinned by
7239    /// `sexp_shape_quote_carving_labels_align_with_quote_form_iac_forge_tags`.
7240    pub const QUOTE_LABEL: &'static str = "quote";
7241
7242    /// Canonical `&'static str` bytes for the [`Self::Quasiquote`] outer-
7243    /// shape — projects [`crate::ast::Sexp::Quasiquote`] into diagnostic
7244    /// rendering. Per-role peer of `Self::Quasiquote`; matches
7245    /// [`crate::ast::QuoteForm::QUASIQUOTE_IAC_FORGE_TAG`] byte-for-byte.
7246    pub const QUASIQUOTE_LABEL: &'static str = "quasiquote";
7247
7248    /// Canonical `&'static str` bytes for the [`Self::Unquote`] outer-shape
7249    /// — projects [`crate::ast::Sexp::Unquote`] into diagnostic rendering.
7250    /// Per-role peer of `Self::Unquote`; matches
7251    /// [`crate::ast::QuoteForm::UNQUOTE_IAC_FORGE_TAG`] byte-for-byte.
7252    pub const UNQUOTE_LABEL: &'static str = "unquote";
7253
7254    /// Canonical `&'static str` bytes for the [`Self::UnquoteSplice`]
7255    /// outer-shape — projects [`crate::ast::Sexp::UnquoteSplice`] into
7256    /// diagnostic rendering. Per-role peer of `Self::UnquoteSplice`;
7257    /// DIFFERS from [`crate::ast::QuoteForm::UNQUOTE_SPLICE_IAC_FORGE_TAG`]
7258    /// (`"unquote-splicing"`) by design — the SexpShape diagnostic layer
7259    /// uses the SHORTER `"unquote-splice"` bytes across every `LispError::*`
7260    /// Display rendering while the iac-forge canonical-form tag uses the
7261    /// LONGER `"unquote-splicing"` for cross-crate byte-identical
7262    /// interoperability. The two-byte spelling asymmetry is the ONE
7263    /// intentional exception to the quote-family cross-axis label
7264    /// alignment; pinned by
7265    /// `sexp_shape_unquote_splice_label_differs_from_quote_form_iac_forge_tag`.
7266    pub const UNQUOTE_SPLICE_LABEL: &'static str = "unquote-splice";
7267
7268    /// Closed-set forced-arity ALL array over the canonical outer-shape
7269    /// `&'static str` bytes, in declaration order matching [`Self::ALL`]
7270    /// element-wise (pinned by
7271    /// `sexp_shape_labels_align_with_all_by_index`). Sibling posture to
7272    /// [`ExpectedKwargShape::LABELS`] (`[&'static str; 7]`),
7273    /// [`KwargPathKind::LABELS`] (`[&'static str; 3]`),
7274    /// [`MacroDefHead::KEYWORDS`] (`[&'static str; 3]`),
7275    /// [`crate::ast::Atom::BOOL_LITERALS`] (`[&'static str; 2]`), and
7276    /// [`crate::ast::QuoteForm::PREFIXES`] (`[&'static str; 4]`) — every
7277    /// closed-set outer projection on the substrate that carries an
7278    /// `&'static str`-per-variant label now pins its per-role canonical
7279    /// bytes at ONE `pub const` per role PLUS an ALL array for
7280    /// family-wide consumers.
7281    ///
7282    /// Pre-lift the twelve canonical outer-shape label bytes lived inline
7283    /// at four sites: [`Self::label`]'s twelve match arms, plus three
7284    /// truth-table tests
7285    /// (`sexp_shape_label_renders_canonical_string_for_every_variant`,
7286    /// `sexp_shape_display_matches_label_for_every_variant`,
7287    /// `sexp_shape_label_round_trips_through_from_str`) enumerating the
7288    /// twelve `&'static str` literals byte-for-byte. Post-lift the
7289    /// per-role bytes bind at ONE `pub const` per role on the typed
7290    /// [`SexpShape`] algebra so every consumer routes through the typed
7291    /// constants — the label match arms, the truth-table tests, and every
7292    /// future consumer (LSP completion, `tatara-check` coverage
7293    /// assertion, Sekiban audit-trail metric labeled by the shape) picks
7294    /// up the same canonical bytes from ONE source of truth.
7295    ///
7296    /// Future consumers that compose against [`Self::LABELS`]: LSP / REPL
7297    /// completion surfacing every `got X` diagnostic label in a
7298    /// `got=` metrics query bar, a `tatara-check` coverage assertion
7299    /// sweeping [`Self::LABELS`] over workspace `.lisp` files' shape-
7300    /// rejection sites, a Sekiban audit-trail metric jointly labeled by
7301    /// the canonical shape (e.g.
7302    /// `tatara_lisp_type_mismatch_total{got="symbol"}`) whose metric-
7303    /// label set IS [`Self::LABELS`]. Adding a hypothetical thirteenth
7304    /// variant (`Vector` for `#(...)` reader syntax, `Map` for `{...}`,
7305    /// `Char` for `#\x`) extends [`Self::ALL`] AND [`Self::LABELS`] AND
7306    /// [`Self::label`]'s arm AND one new per-role `pub const` in
7307    /// lockstep — rustc's forced-arity check on the two `[_; N]` arrays
7308    /// fails compilation if EITHER ALL array grows without the other.
7309    ///
7310    /// Theory anchor: THEORY.md §III — the typescape; the twelve
7311    /// canonical outer-shape label bytes bind at ONE typed `[&'static
7312    /// str; 12]` array on the closed-set outer [`SexpShape`] algebra
7313    /// rather than as twelve inline literals scattered across four
7314    /// substrate sites. THEORY.md §V.1 — knowable platform; the family's
7315    /// cardinality becomes a TYPE-level constant on the substrate
7316    /// algebra rather than a per-consumer hand-rolled enumeration of the
7317    /// twelve labels. THEORY.md §VI.1 — generation over composition; the
7318    /// family-wide contract sweeps (alignment with `ALL`, pairwise
7319    /// disjointness, membership through `label()`) emerge from the
7320    /// composition of TWO substrate primitives (this `pub const` array +
7321    /// the twelve per-role `pub const *_LABEL`s) rather than as per-
7322    /// variant inline assertions the current pins duplicate structurally.
7323    pub const LABELS: [&'static str; 12] = [
7324        Self::NIL_LABEL,
7325        Self::SYMBOL_LABEL,
7326        Self::KEYWORD_LABEL,
7327        Self::STRING_LABEL,
7328        Self::INT_LABEL,
7329        Self::FLOAT_LABEL,
7330        Self::BOOL_LABEL,
7331        Self::LIST_LABEL,
7332        Self::QUOTE_LABEL,
7333        Self::QUASIQUOTE_LABEL,
7334        Self::UNQUOTE_LABEL,
7335        Self::UNQUOTE_SPLICE_LABEL,
7336    ];
7337
7338    /// Project the typed `SexpShape` to the canonical `&'static str`
7339    /// literal — feeds the `LispError::TypeMismatch` /
7340    /// `LispError::NamedFormNonSymbolName` Display rendering via the
7341    /// `#[error(...)]` annotation. The `&'static str` lifetime is
7342    /// load-bearing: it lets the variant project through this method into
7343    /// the `got {got}` slot without an allocation, parallel to how
7344    /// `ExpectedKwargShape::label()`, `MacroDefHead::keyword()`,
7345    /// `UnquoteForm::marker()`, and `CompilerSpecIoStage::operation()` /
7346    /// `label()` feed their respective `LispError::*` Display impls.
7347    ///
7348    /// Each arm routes through the per-role `pub const` on `impl Self`
7349    /// ([`Self::NIL_LABEL`], [`Self::SYMBOL_LABEL`],
7350    /// [`Self::KEYWORD_LABEL`], [`Self::STRING_LABEL`],
7351    /// [`Self::INT_LABEL`], [`Self::FLOAT_LABEL`], [`Self::BOOL_LABEL`],
7352    /// [`Self::LIST_LABEL`], [`Self::QUOTE_LABEL`],
7353    /// [`Self::QUASIQUOTE_LABEL`], [`Self::UNQUOTE_LABEL`],
7354    /// [`Self::UNQUOTE_SPLICE_LABEL`]) so the twelve canonical
7355    /// outer-shape byte-strings bind at ONE typed source of truth per
7356    /// role rather than as inline literals scattered across the `match`
7357    /// body. Sibling posture to
7358    /// [`ExpectedKwargShape::label`]'s post-lift arms.
7359    ///
7360    /// The bidirectional contract is anchored by tests:
7361    /// `sexp_shape_label_renders_canonical_string_for_every_variant` pins
7362    /// each variant's canonical literal so a typo in any arm fails-loudly,
7363    /// `sexp_shape_display_matches_label_for_every_variant` pins
7364    /// Display-equals-label so the `#[error(...)]` annotation's `{got}`
7365    /// slot projects byte-for-byte through this method, and
7366    /// `sexp_shape_label_round_trips_through_from_str` pins the
7367    /// `label` ↔ `FromStr` round-trip for every variant in
7368    /// [`Self::ALL`] so the typed surface and the rendered diagnostic
7369    /// literal cannot drift.
7370    #[must_use]
7371    pub fn label(self) -> &'static str {
7372        match self {
7373            Self::Nil => Self::NIL_LABEL,
7374            Self::Symbol => Self::SYMBOL_LABEL,
7375            Self::Keyword => Self::KEYWORD_LABEL,
7376            Self::String => Self::STRING_LABEL,
7377            Self::Int => Self::INT_LABEL,
7378            Self::Float => Self::FLOAT_LABEL,
7379            Self::Bool => Self::BOOL_LABEL,
7380            Self::List => Self::LIST_LABEL,
7381            Self::Quote => Self::QUOTE_LABEL,
7382            Self::Quasiquote => Self::QUASIQUOTE_LABEL,
7383            Self::Unquote => Self::UNQUOTE_LABEL,
7384            Self::UnquoteSplice => Self::UNQUOTE_SPLICE_LABEL,
7385        }
7386    }
7387
7388    /// Project the twelve-variant [`SexpShape`] back to its
7389    /// corresponding [`crate::ast::AtomKind`] iff the shape names an
7390    /// atomic-payload variant — `Symbol → Some(AtomKind::Symbol)`,
7391    /// `Keyword → Some(AtomKind::Keyword)`, `String → Some(AtomKind::Str)`,
7392    /// `Int → Some(AtomKind::Int)`, `Float → Some(AtomKind::Float)`,
7393    /// `Bool → Some(AtomKind::Bool)`, every other shape (`Nil`,
7394    /// `List`, every quote-family wrapper) `None`. The 6-of-12 carving
7395    /// of [`SexpShape`] that the inverse [`crate::ast::AtomKind::sexp_shape`]
7396    /// embed projection covers — naming the inverse closes the
7397    /// embed/project section on the (atomic-payload, outer-shape)
7398    /// algebra.
7399    ///
7400    /// Composition law (round-trip on the atom carving):
7401    /// `AtomKind::sexp_shape(k).as_atom_kind() == Some(k)` for every
7402    /// `k: AtomKind`. The non-atom shapes form the kernel of the
7403    /// projection — `SexpShape::List.as_atom_kind() == None`,
7404    /// `SexpShape::Nil.as_atom_kind() == None`, every quote-family
7405    /// variant also `None`. The two projections together form an
7406    /// `Iso(AtomKind, AtomShape ⊂ SexpShape)`: `as_atom_kind` is the
7407    /// section (every `AtomKind` round-trips through the embed),
7408    /// `sexp_shape` is the retraction (every atom-shape pre-image
7409    /// recovers the typed marker). Disjoint with [`Self::as_quote_form`]:
7410    /// for every variant in [`Self::ALL`], at most ONE of the two
7411    /// projections returns `Some` — the typed-shape lattice's two
7412    /// closed-set carvings partition the carve-able SexpShape variants
7413    /// (every variant is in exactly one of AtomShape, QuoteShape, or
7414    /// neither — the latter being `Nil` and `List`).
7415    ///
7416    /// Pre-lift the docstring on [`crate::ast::AtomKind::sexp_shape`]
7417    /// explicitly anticipated this dual:
7418    ///
7419    /// > "the dual projection `SexpShape::as_atom_kind(self) ->
7420    /// > Option<AtomKind>` is NOT currently provided because no
7421    /// > consumer needs it ... If a future authoring tool (LSP /
7422    /// > REPL / `tatara-check` typed-pattern matcher) wants to lift
7423    /// > the typed marker out of a diagnostic-side shape identity,
7424    /// > the dual lands as ONE new `match` over the closed set,
7425    /// > parallel to the structure here."
7426    ///
7427    /// Post-lift the dual lives at this method; the `AtomKind::sexp_shape`
7428    /// docstring's anticipation is satisfied. Two plausible future
7429    /// consumers the closed-set projection admits with no new
7430    /// boilerplate:
7431    ///   * **LSP / REPL typed-shape filter** — given a
7432    ///     `LispError::TypeMismatch { got: SexpShape, .. }`, project
7433    ///     the offending shape through `as_atom_kind()`; iff `Some(k)`,
7434    ///     the offending value was an atomic payload and the LSP can
7435    ///     route through `AtomKind`'s diagnostic / completion surface
7436    ///     (`AtomKind::label`, `AtomKind::ALL`-driven completion list)
7437    ///     without re-deriving the 6-of-12 carving inline. Iff `None`,
7438    ///     the surface dispatches to the structural (list / quote-family /
7439    ///     nil) branch instead.
7440    ///   * **`tatara-check` typed-pattern matcher** — a future
7441    ///     `(check-shape-projects-to-atom-kind …)` substrate primitive
7442    ///     binds to this projection rather than substring-matching the
7443    ///     rendered label or duplicating the 6-of-12 carving on its
7444    ///     own.
7445    ///   * **Diagnostic-side audit-trail metric** — a future
7446    ///     `tatara_lisp_type_mismatch_atom_kind_total{kind="symbol"}`
7447    ///     metric reads the typed `AtomKind` directly off the rejected
7448    ///     `SexpShape` via this projection, with the carving's
7449    ///     correctness pinned at ONE site (the typed match here)
7450    ///     rather than across N per-metric inline `match` arms.
7451    ///
7452    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
7453    /// (SexpShape variant, AtomKind variant) inverse pairing becomes a
7454    /// TYPE projection on the substrate algebra rather than an inline
7455    /// 6-of-12 `match` re-derived per call site. THEORY.md §II.1
7456    /// invariant 2 — free middle; the (atomic-payload, outer-shape)
7457    /// algebra now binds at TWO typed sites (`AtomKind::sexp_shape`
7458    /// for the embed, `SexpShape::as_atom_kind` for the project), each
7459    /// rebuilding the same closed-set pairing — a regression that
7460    /// drifts ONE direction from the other fails the
7461    /// `atom_kind_sexp_shape_round_trips_through_as_atom_kind` round-
7462    /// trip pin below. THEORY.md §VI.1 — generation over composition;
7463    /// the inverse pairing's ONE typed `match` displaces the speculative
7464    /// per-consumer 6-of-12 carving (LSP, REPL, `tatara-check`,
7465    /// metrics) the substrate's authoring surface roadmap anticipates.
7466    ///
7467    /// Frontier inspiration: MLIR's `mlir::dyn_cast<AtomicAttr>(attr)`
7468    /// — the typed soft-downcast from a generic `Attribute` to a
7469    /// narrower typed `AtomicAttr` interface IS the closed-set
7470    /// project direction; `SexpShape::as_atom_kind` is the
7471    /// unstructured-Rust peer on the substrate's typed-shape algebra,
7472    /// with [`crate::ast::AtomKind`] standing in for MLIR's
7473    /// `AtomicAttr` interface. Racket's `(syntax->datum stx)` paired
7474    /// with a closed-set predicate (`number?`, `symbol?`, `string?`,
7475    /// `boolean?`) — the typed predicate face IS the project direction
7476    /// on Racket's syntax-datum taxonomy; the substrate's
7477    /// `as_atom_kind` is the Rust-typed peer where the predicate
7478    /// surfaces the typed witness alongside the predicate verdict in
7479    /// ONE `Option<AtomKind>` projection.
7480    #[must_use]
7481    pub fn as_atom_kind(self) -> Option<crate::ast::AtomKind> {
7482        use crate::ast::AtomKind;
7483        match self {
7484            Self::Symbol => Some(AtomKind::Symbol),
7485            Self::Keyword => Some(AtomKind::Keyword),
7486            Self::String => Some(AtomKind::Str),
7487            Self::Int => Some(AtomKind::Int),
7488            Self::Float => Some(AtomKind::Float),
7489            Self::Bool => Some(AtomKind::Bool),
7490            Self::Nil
7491            | Self::List
7492            | Self::Quote
7493            | Self::Quasiquote
7494            | Self::Unquote
7495            | Self::UnquoteSplice => None,
7496        }
7497    }
7498
7499    /// Project the twelve-variant [`SexpShape`] back to its
7500    /// corresponding [`crate::ast::QuoteForm`] iff the shape names a
7501    /// homoiconic quote-family wrapper — `Quote → Some(QuoteForm::Quote)`,
7502    /// `Quasiquote → Some(QuoteForm::Quasiquote)`, `Unquote →
7503    /// Some(QuoteForm::Unquote)`, `UnquoteSplice →
7504    /// Some(QuoteForm::UnquoteSplice)`, every other shape (`Nil`,
7505    /// `List`, every atomic-payload variant) `None`. The 4-of-12
7506    /// carving of [`SexpShape`] that the inverse
7507    /// [`crate::ast::QuoteForm::sexp_shape`] embed projection covers —
7508    /// naming the inverse closes the embed/project section on the
7509    /// (quote-family, outer-shape) algebra, sibling to
7510    /// [`Self::as_atom_kind`] on the atomic-payload axis.
7511    ///
7512    /// Composition law (round-trip on the quote-family carving):
7513    /// `QuoteForm::sexp_shape(qf).as_quote_form() == Some(qf)` for
7514    /// every `qf: QuoteForm`. The non-quote-family shapes form the
7515    /// kernel of the projection — `SexpShape::List.as_quote_form() ==
7516    /// None`, `SexpShape::Nil.as_quote_form() == None`, every atomic-
7517    /// payload variant also `None`. The two projections together form
7518    /// an `Iso(QuoteForm, QuoteShape ⊂ SexpShape)`: `as_quote_form`
7519    /// is the section (every `QuoteForm` round-trips through the
7520    /// embed), `sexp_shape` is the retraction (every quote-shape
7521    /// pre-image recovers the typed marker). Disjoint with
7522    /// [`Self::as_atom_kind`]: for every variant in [`Self::ALL`], at
7523    /// most ONE of the two projections returns `Some` — the typed-
7524    /// shape lattice's two closed-set carvings partition the carve-
7525    /// able SexpShape variants (every variant is in exactly one of
7526    /// AtomShape, QuoteShape, or neither — the latter being `Nil`
7527    /// and `List`).
7528    ///
7529    /// Pre-lift the docstring on [`crate::ast::QuoteForm::sexp_shape`]
7530    /// explicitly anticipated this dual:
7531    ///
7532    /// > "the dual projection `SexpShape::as_quote_form(self) ->
7533    /// > Option<QuoteForm>` is NOT currently provided because no
7534    /// > consumer needs it ... If a future authoring tool (LSP /
7535    /// > REPL / `tatara-check` typed-pattern matcher) wants to lift
7536    /// > the typed marker out of a diagnostic-side shape identity,
7537    /// > the dual lands as ONE new match on the closed set, parallel
7538    /// > to the structure here."
7539    ///
7540    /// Post-lift the dual lives at this method; the
7541    /// `QuoteForm::sexp_shape` docstring's anticipation is satisfied.
7542    /// The same plausible future consumers [`Self::as_atom_kind`]
7543    /// documents apply on the quote-family axis — a typed-shape
7544    /// filter that narrows a diagnostic to "this rejection was on a
7545    /// homoiconic prefix-wrapper" iff `as_quote_form().is_some()`
7546    /// binds to ONE projection rather than re-deriving the 4-of-12
7547    /// carving inline. A future `tatara-check` predicate
7548    /// `(check-shape-projects-to-quote-form …)` reads the typed
7549    /// `QuoteForm` directly off a rejected `SexpShape` via this
7550    /// projection.
7551    ///
7552    /// Theory anchor: same as [`Self::as_atom_kind`]. THEORY.md §V.1
7553    /// (knowable platform; the inverse pairing is a TYPE projection
7554    /// rather than an inline 4-of-12 `match`), THEORY.md §II.1
7555    /// invariant 2 (free middle; the embed/project pair binds at TWO
7556    /// typed sites — `QuoteForm::sexp_shape` for the embed,
7557    /// `SexpShape::as_quote_form` for the project — both rebuilding
7558    /// the same closed-set pairing), THEORY.md §VI.1 (generation
7559    /// over composition; the inverse 4-of-12 carving lifts to ONE
7560    /// typed `match`).
7561    ///
7562    /// Frontier inspiration: same as [`Self::as_atom_kind`] — MLIR's
7563    /// `mlir::dyn_cast<QuoteAttr>(attr)` typed soft-downcast on the
7564    /// quote-family carving of a closed-set attribute union, with
7565    /// Racket's `(or (quote-syntax? stx) (quasiquote-syntax? stx)
7566    /// (unquote-syntax? stx) (unquote-splicing-syntax? stx))` as the
7567    /// closed-form predicate-family sibling whose Rust-typed peer
7568    /// surfaces the typed witness alongside the predicate verdict in
7569    /// ONE `Option<QuoteForm>` projection.
7570    #[must_use]
7571    pub fn as_quote_form(self) -> Option<crate::ast::QuoteForm> {
7572        use crate::ast::QuoteForm;
7573        match self {
7574            Self::Quote => Some(QuoteForm::Quote),
7575            Self::Quasiquote => Some(QuoteForm::Quasiquote),
7576            Self::Unquote => Some(QuoteForm::Unquote),
7577            Self::UnquoteSplice => Some(QuoteForm::UnquoteSplice),
7578            Self::Nil
7579            | Self::List
7580            | Self::Symbol
7581            | Self::Keyword
7582            | Self::String
7583            | Self::Int
7584            | Self::Float
7585            | Self::Bool => None,
7586        }
7587    }
7588
7589    /// Project the twelve-variant [`SexpShape`] back to its
7590    /// corresponding [`UnquoteForm`] iff the shape names a template-
7591    /// substitution wrapper — `Unquote → Some(UnquoteForm::Unquote)`,
7592    /// `UnquoteSplice → Some(UnquoteForm::Splice)`, every other shape
7593    /// (`Quote`, `Quasiquote`, `Nil`, `List`, every atomic-payload
7594    /// variant) `None`. The 2-of-12 carving of [`SexpShape`] that the
7595    /// inverse [`UnquoteForm::sexp_shape`] embed projection covers —
7596    /// naming the inverse closes the embed/project section on the
7597    /// (template-substitution subset, outer-shape) algebra, sibling of
7598    /// the already-closed ([`crate::ast::AtomKind::sexp_shape`],
7599    /// [`Self::as_atom_kind`]) dual on the 6-of-12 atomic-payload
7600    /// carving AND ([`crate::ast::QuoteForm::sexp_shape`],
7601    /// [`Self::as_quote_form`]) dual on the 4-of-12 quote-family
7602    /// carving.
7603    ///
7604    /// Composition law (routes through the quote-family superset):
7605    /// `self.as_unquote_form() == self.as_quote_form().and_then(
7606    /// crate::ast::QuoteForm::as_unquote_form)` for every `self:
7607    /// SexpShape`. The (outer shape, subset marker) pairing binds at
7608    /// TWO existing closed-set matches ([`Self::as_quote_form`] +
7609    /// [`crate::ast::QuoteForm::as_unquote_form`]) rather than at a
7610    /// parallel twelve-arm inline match table here — matching the
7611    /// posture [`UnquoteForm::sexp_shape`] takes through
7612    /// [`crate::ast::QuoteForm::sexp_shape`] on the embed direction.
7613    /// Round-trip law (retraction on the substitution-subset carving):
7614    /// `UnquoteForm::sexp_shape(uf).as_unquote_form() == Some(uf)` for
7615    /// every `uf: UnquoteForm`. The two projections together form an
7616    /// `Iso(UnquoteForm, UnquoteShape ⊂ SexpShape)`:
7617    /// [`Self::as_unquote_form`] is the section (every `UnquoteForm`
7618    /// round-trips through the embed), [`UnquoteForm::sexp_shape`] is
7619    /// the retraction (every substitution-subset shape pre-image
7620    /// recovers the typed marker).
7621    ///
7622    /// Disjoint with [`Self::as_atom_kind`] AND properly subsumed by
7623    /// [`Self::as_quote_form`]: for every variant in [`Self::ALL`], at
7624    /// most ONE of ([`Self::as_atom_kind`], [`Self::as_quote_form`])
7625    /// returns `Some` (the substrate's typed-shape lattice's two closed-
7626    /// set carvings partition the carve-able SexpShape variants); and
7627    /// [`Self::as_unquote_form`] returns `Some` iff [`Self::as_quote_form`]
7628    /// returns `Some(qf)` AND `qf.as_unquote_form()` returns `Some(_)`
7629    /// (the 2-of-4 template-substitution subset of the 4-of-12 quote-
7630    /// family carving). The two quote-family shapes that lie OUTSIDE
7631    /// the substitution subset (`Quote`, `Quasiquote`) form the kernel
7632    /// of this projection but NOT of [`Self::as_quote_form`] — the
7633    /// nested subset relationship makes the projections composable.
7634    ///
7635    /// Pre-lift the (SexpShape variant, UnquoteForm variant) pairing
7636    /// had NO typed projection at this closed-set boundary — a
7637    /// consumer with a `SexpShape` in hand (a `LispError::TypeMismatch
7638    /// .got` slot's outer-shape identity, a rejected shape from a
7639    /// typed-entry gate) wanting to narrow to "was this rejection on a
7640    /// template-substitution wrapper?" had to spell the two-step
7641    /// composition `shape.as_quote_form().and_then(QuoteForm::as_unquote_form)`
7642    /// inline. Post-lift the composition binds at ONE typed-algebra
7643    /// method on the outer [`SexpShape`] algebra, and the pair with
7644    /// [`UnquoteForm::sexp_shape`] closes the embed/project algebra
7645    /// dual on the substitution-subset carving.
7646    ///
7647    /// Theory anchor: THEORY.md §V.1 — knowable platform; the inverse
7648    /// 2-of-12 carving is a TYPE projection on the substrate algebra
7649    /// rather than an inline `.as_quote_form().and_then(...)` two-step.
7650    /// THEORY.md §II.1 invariant 2 — free middle; the embed/project
7651    /// pair binds at TWO typed sites — [`UnquoteForm::sexp_shape`] for
7652    /// the embed, [`Self::as_unquote_form`] for the project — both
7653    /// composing through the parent quote-family carving's closed
7654    /// pair. THEORY.md §VI.1 — generation over composition; the
7655    /// inverse 2-of-12 carving lifts to ONE typed projection.
7656    ///
7657    /// Frontier inspiration: MLIR's `mlir::dyn_cast<UnquoteFamilyOp>(op)`
7658    /// typed soft-downcast on the substitution-subset carving of a
7659    /// closed-set operation union — the (op, typed identity) pairing
7660    /// lives at ONE typed projection composed through the parent
7661    /// op-family's typed downcast. [`Self::as_unquote_form`] is the
7662    /// Rust-typed peer on the [`SexpShape`] closed set, with
7663    /// [`crate::ast::QuoteForm::as_unquote_form`] standing in for the
7664    /// parent-family-to-subset composition and this projection lifting
7665    /// the parent-to-parent [`Self::as_quote_form`] composition into a
7666    /// single typed method the consumer surface binds against directly.
7667    #[must_use]
7668    pub fn as_unquote_form(self) -> Option<UnquoteForm> {
7669        self.as_quote_form()
7670            .and_then(crate::ast::QuoteForm::as_unquote_form)
7671    }
7672
7673    /// Project the twelve-variant [`SexpShape`] back to its corresponding
7674    /// [`StructuralKind`] iff the shape names a structural-residual
7675    /// wrapper — `Nil → Some(StructuralKind::Nil)`, `List →
7676    /// Some(StructuralKind::List)`, every other shape (`Symbol`,
7677    /// `Keyword`, `String`, `Int`, `Float`, `Bool`, `Quote`,
7678    /// `Quasiquote`, `Unquote`, `UnquoteSplice`) `None`. The 2-of-12
7679    /// carving of [`SexpShape`] that the inverse
7680    /// [`StructuralKind::sexp_shape`] embed projection covers — naming
7681    /// the inverse closes the (embed, project) algebra dual on the
7682    /// (structural-residual subset, outer-shape) algebra, THIRD sibling
7683    /// of the already-closed ([`crate::ast::AtomKind::sexp_shape`],
7684    /// [`Self::as_atom_kind`]) dual on the 6-of-12 atomic-payload
7685    /// carving AND ([`crate::ast::QuoteForm::sexp_shape`],
7686    /// [`Self::as_quote_form`]) dual on the 4-of-12 quote-family
7687    /// carving.
7688    ///
7689    /// Closes the typed-shape lattice's partition: every `SexpShape`
7690    /// projects through EXACTLY ONE of [`Self::as_atom_kind`],
7691    /// [`Self::as_quote_form`], [`Self::as_structural_kind`] as
7692    /// `Some(_)`, and the three carvings cover `SexpShape::ALL`
7693    /// disjointly (6 + 4 + 2 = 12). The partition is total AND
7694    /// disjoint on the outer-shape closed set — pre-lift the
7695    /// "residual" side of the partition lived at ONE inline
7696    /// `!matches!(shape, SexpShape::Nil | SexpShape::List)` assertion
7697    /// inside the test-module carving-disjointness pin (the runtime
7698    /// witness the substrate maintained by test discipline); post-lift
7699    /// the residual is a TYPED CARVING binding at ONE typed-algebra
7700    /// method, symmetric with the two already-closed carvings that own
7701    /// the atomic-payload and quote-family sides of the same partition.
7702    ///
7703    /// Composition law (partition-total): for every `self: SexpShape`,
7704    /// EXACTLY ONE of `self.as_atom_kind().is_some()`,
7705    /// `self.as_quote_form().is_some()`,
7706    /// `self.as_structural_kind().is_some()` is `true`. Round-trip law
7707    /// (retraction on the structural-residual subset carving):
7708    /// `StructuralKind::sexp_shape(sk).as_structural_kind() ==
7709    /// Some(sk)` for every `sk: StructuralKind`. The two projections
7710    /// together form an `Iso(StructuralKind, StructuralShape ⊂
7711    /// SexpShape)`: [`Self::as_structural_kind`] is the section (every
7712    /// `StructuralKind` round-trips through the embed),
7713    /// [`StructuralKind::sexp_shape`] is the retraction (every
7714    /// structural-residual shape pre-image recovers the typed marker).
7715    ///
7716    /// Disjoint with [`Self::as_atom_kind`] AND [`Self::as_quote_form`]:
7717    /// for every variant in [`Self::ALL`], at most ONE of the THREE
7718    /// projections returns `Some`. The structural-residual subset
7719    /// (`Nil` and `List`) is the KERNEL of both the atomic-payload and
7720    /// quote-family projections and is EXACTLY the IMAGE of this one —
7721    /// the three projections partition [`Self::ALL`] injectively.
7722    ///
7723    /// Pre-lift the (SexpShape variant, StructuralKind variant) pairing
7724    /// had NO typed projection at this closed-set boundary — a consumer
7725    /// with a `SexpShape` in hand (a `LispError::TypeMismatch.got`
7726    /// slot's outer-shape identity, a rejected shape from a typed-entry
7727    /// gate) wanting to narrow to "was this rejection on the structural
7728    /// residual (neither atomic nor quote-family)?" had to spell the
7729    /// two-step negation `shape.as_atom_kind().is_none() &&
7730    /// shape.as_quote_form().is_none()` OR the inline `matches!(shape,
7731    /// SexpShape::Nil | SexpShape::List)` at the callsite. Post-lift
7732    /// the composition binds at ONE typed-algebra method on the outer
7733    /// [`SexpShape`] algebra, and the pair with
7734    /// [`StructuralKind::sexp_shape`] closes the embed/project algebra
7735    /// dual on the structural-residual carving, completing the typed-
7736    /// shape lattice's third and final closed-set carving.
7737    ///
7738    /// Two plausible future consumers the closed-set projection admits
7739    /// with no new boilerplate:
7740    ///   * **`tatara-check` typed-shape filter** — a future
7741    ///     `(check-shape-projects-to-structural …)` substrate primitive
7742    ///     binds to this projection rather than re-deriving the 2-of-12
7743    ///     carving inline or negating the two sibling projections. A
7744    ///     `LispError::TypeMismatch { got: SexpShape, .. }` whose
7745    ///     offending shape projects through
7746    ///     `as_structural_kind() == Some(_)` is a rejection on the
7747    ///     structural residual (an empty [`crate::ast::Sexp::Nil`]
7748    ///     marker or a bare [`crate::ast::Sexp::List`] shape) rather
7749    ///     than on an atomic-payload OR quote-family wrapper.
7750    ///   * **LSP / REPL typed-shape completion** — a future authoring
7751    ///     surface offering completions filtered by carving axis binds
7752    ///     to `StructuralKind::ALL` for the "structural" completion
7753    ///     column rather than re-deriving `Nil | List` inline.
7754    ///
7755    /// Theory anchor: THEORY.md §V.1 — knowable platform; the inverse
7756    /// 2-of-12 residual carving is a TYPE projection on the substrate
7757    /// algebra rather than an inline `matches!(shape, SexpShape::Nil |
7758    /// SexpShape::List)` two-arm predicate. THEORY.md §II.1 invariant
7759    /// 2 — free middle; the embed/project pair binds at TWO typed
7760    /// sites — [`StructuralKind::sexp_shape`] for the embed,
7761    /// [`Self::as_structural_kind`] for the project — completing the
7762    /// third and final closed-set carving of the outer-shape algebra.
7763    /// THEORY.md §VI.1 — generation over composition; the residual
7764    /// 2-of-12 carving lifts to ONE typed projection AND makes the
7765    /// partition-total invariant (every `SexpShape` is in EXACTLY ONE
7766    /// of the three carvings) a TYPED THEOREM rather than a runtime
7767    /// `matches!` assertion.
7768    ///
7769    /// Frontier inspiration: MLIR's `mlir::dyn_cast<StructuralOp>(op)`
7770    /// typed soft-downcast on the residual carving of a closed-set
7771    /// operation union — the (op, typed identity) pairing lives at ONE
7772    /// typed projection on the outer op-family algebra, sibling of the
7773    /// atomic-payload and quote-family carvings' typed downcasts.
7774    /// [`Self::as_structural_kind`] is the Rust-typed peer on the
7775    /// [`SexpShape`] closed set, completing the three-carving partition
7776    /// the substrate's typed-shape lattice carries. Racket's
7777    /// `(or (null? datum) (pair? datum))` closed-form predicate on the
7778    /// (nil-or-list) residual whose Rust-typed peer surfaces the typed
7779    /// witness alongside the predicate verdict in ONE
7780    /// `Option<StructuralKind>` projection.
7781    #[must_use]
7782    pub fn as_structural_kind(self) -> Option<StructuralKind> {
7783        match self {
7784            Self::Nil => Some(StructuralKind::Nil),
7785            Self::List => Some(StructuralKind::List),
7786            Self::Symbol
7787            | Self::Keyword
7788            | Self::String
7789            | Self::Int
7790            | Self::Float
7791            | Self::Bool
7792            | Self::Quote
7793            | Self::Quasiquote
7794            | Self::Unquote
7795            | Self::UnquoteSplice => None,
7796        }
7797    }
7798
7799    /// Project the twelve-variant [`SexpShape`] into the canonical
7800    /// iac-forge interop tag string iff the shape names a homoiconic
7801    /// quote-family wrapper — `Quote → Some("quote")`, `Quasiquote →
7802    /// Some("quasiquote")`, `Unquote → Some("unquote")`, `UnquoteSplice
7803    /// → Some("unquote-splicing")`, every other shape (`Nil`, `List`,
7804    /// every atomic-payload variant) `None`. The 4-of-12 partial
7805    /// projection on the closed-set [`SexpShape`] algebra that surfaces
7806    /// [`crate::ast::QuoteForm::iac_forge_tag`]'s cross-crate canonical-
7807    /// form tag surface at the outer-shape algebra level.
7808    ///
7809    /// Composition law: `self.iac_forge_tag() ==
7810    /// self.as_quote_form().map(crate::ast::QuoteForm::iac_forge_tag)`
7811    /// for every `self: SexpShape` — the (outer shape, canonical iac-
7812    /// forge tag) pairing binds through the pre-existing quote-family
7813    /// carving ([`Self::as_quote_form`]) composed with the closed-set
7814    /// tag projection ([`crate::ast::QuoteForm::iac_forge_tag`]) rather
7815    /// than at a parallel four-arm inline match table here — matching
7816    /// the posture [`Self::as_unquote_form`] takes through
7817    /// [`crate::ast::QuoteForm::as_unquote_form`] on the 2-of-4
7818    /// template-substitution subset carving. `None` on every non-quote-
7819    /// family shape (six atomic-payload variants + `Nil` + `List`) — the
7820    /// eight-shape kernel of the projection matches the kernel of
7821    /// [`Self::as_quote_form`] exactly.
7822    ///
7823    /// The SIXTH consumer of the outer-shape algebra's shape-level
7824    /// projection surface, sibling of [`Self::label`] (operator-facing
7825    /// diagnostic-label surface, all twelve arms), [`Self::hash_discriminator`]
7826    /// (outer-`Sexp` cache-key discriminator byte, all twelve arms
7827    /// → `{0..=6}`), [`Self::as_atom_kind`] (6-of-12 atomic-payload
7828    /// carving), [`Self::as_quote_form`] (4-of-12 quote-family carving),
7829    /// [`Self::as_structural_kind`] (2-of-12 structural-residual
7830    /// carving), and [`Self::as_unquote_form`] (2-of-12 template-
7831    /// substitution subset carving composed through the parent quote-
7832    /// family carving). Post-lift the outer-shape algebra closes over
7833    /// seven typed projection surfaces, each rebuilt from the SAME
7834    /// closed twelve-arm set — a regression that drifts ONE projection
7835    /// against the algebra's shape-identity fails-loudly at rustc's
7836    /// exhaustiveness rather than silently at runtime.
7837    ///
7838    /// Pre-lift the (SexpShape variant, canonical iac-forge tag) pairing
7839    /// had no typed projection at the outer-shape algebra level — a
7840    /// consumer with a `SexpShape` in hand (a `LispError::TypeMismatch
7841    /// .got` slot's outer-shape identity, a rejected shape from a
7842    /// typed-entry gate, an LSP / REPL / audit-trail metric keyed on the
7843    /// observed outer shape) wanting the cross-crate iac-forge canonical
7844    /// tag had to re-embed the shape into a quote-family typed marker
7845    /// via [`Self::as_quote_form`] then compose through
7846    /// [`crate::ast::QuoteForm::iac_forge_tag`] inline. Post-lift the
7847    /// composition binds at ONE method on the outer [`SexpShape`]
7848    /// algebra — the (outer shape, canonical iac-forge tag) pairing lives
7849    /// at ONE typed projection matching the shape-level algebra sweep
7850    /// the six sibling projections established.
7851    ///
7852    /// The `Option<&'static str>` return shape is the natural typed peer
7853    /// of [`crate::ast::QuoteForm::iac_forge_tag`]'s total `&'static str`
7854    /// return: the quote-family superset TOTALS on the four-arm closed
7855    /// set, and the outer twelve-arm closed set's projection PARTIALIZES
7856    /// on the eight non-quote-family shapes. Same shape as
7857    /// [`Self::as_atom_kind`] / [`Self::as_quote_form`] /
7858    /// [`Self::as_structural_kind`] / [`Self::as_unquote_form`] — each
7859    /// carving lifts a total sub-algebra projection to a partial outer-
7860    /// shape projection with `None` on the residual arms, so a consumer
7861    /// keying on "iff this outer shape has a canonical iac-forge tag"
7862    /// binds against the closed set's typed image directly rather than
7863    /// substring-matching the shape label.
7864    ///
7865    /// The `&'static str` lifetime is load-bearing: every iac-forge
7866    /// consumer projects through this method into the canonical
7867    /// 2-element-list head without an allocation, parallel to how
7868    /// [`crate::ast::QuoteForm::iac_forge_tag`] on the sub-carving,
7869    /// [`Self::label`] on the outer-shape diagnostic surface,
7870    /// [`crate::ast::QuoteForm::prefix`] on the Display / reader
7871    /// prefix surface, and [`UnquoteForm::marker`] on the template
7872    /// marker axis project their respective closed sets. A future
7873    /// homoiconic prefix-wrapper (e.g. hypothetical `,~` reverse-
7874    /// unquote) extends [`crate::ast::QuoteForm`] AND [`Self`]'s
7875    /// closed set together — rustc binds the iac-forge canonical-form
7876    /// surface to the extended algebra at ONE arm each through
7877    /// exhaustiveness, with the composition through [`Self::as_quote_form`]
7878    /// pulling the new variant's tag into this projection mechanically.
7879    ///
7880    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
7881    /// (SexpShape variant, canonical iac-forge tag) pairing becomes a
7882    /// TYPE projection on the outer-shape algebra rather than an inline
7883    /// `.as_quote_form().map(...)` two-step at every consumer. A typo
7884    /// or swap at the projection site is no longer a runtime tag drift
7885    /// but a compile error against the typed composition — the
7886    /// `SexpShape` ↔ `QuoteForm` ↔ tag string chain is rustc-enforced
7887    /// end-to-end. THEORY.md §II.1 invariant 2 — free middle; the
7888    /// (outer shape, canonical iac-forge tag) pairing now binds at ONE
7889    /// site on the outer-shape algebra, composing through the pre-
7890    /// existing four-arm sub-carving's tag projection rather than
7891    /// duplicating the four-arm match here. THEORY.md §VI.1 —
7892    /// generation over composition; the outer-shape algebra's typed
7893    /// projection surface closes over SEVEN methods (label,
7894    /// hash_discriminator, four carvings, iac_forge_tag) each keyed on
7895    /// the SAME closed twelve-arm set.
7896    ///
7897    /// Frontier inspiration: MLIR's `mlir::TypeSwitch<T>` typed
7898    /// pattern-match combinator on a closed union — narrowing to a
7899    /// specific typed case yields the typed downcast + operations on
7900    /// that case in ONE typed step. [`Self::iac_forge_tag`] is the
7901    /// Rust-typed peer where the "narrow to quote-family" step
7902    /// ([`Self::as_quote_form`]) composes with the "read the case's
7903    /// canonical tag" step ([`crate::ast::QuoteForm::iac_forge_tag`])
7904    /// into ONE outer-shape projection. Racket's `(match shape
7905    /// [(? quote-shape? qs) (quote-shape->iac-forge-tag qs)] [_ #f])`
7906    /// closed-form match on the shape-taxonomy union — the outer
7907    /// pattern's partial projection to the canonical form IS the shape
7908    /// this method lifts to a typed Rust closed-set match.
7909    #[must_use]
7910    pub fn iac_forge_tag(self) -> Option<&'static str> {
7911        self.as_quote_form()
7912            .map(crate::ast::QuoteForm::iac_forge_tag)
7913    }
7914
7915    /// Project the twelve-variant [`SexpShape`] into the canonical
7916    /// reader-punctuation prefix string iff the shape names a homoiconic
7917    /// quote-family wrapper — `Quote → Some("'")`, `Quasiquote →
7918    /// Some("`")`, `Unquote → Some(",")`, `UnquoteSplice → Some(",@")`,
7919    /// every other shape (`Nil`, `List`, every atomic-payload variant)
7920    /// `None`. The 4-of-12 partial projection on the closed-set
7921    /// [`SexpShape`] algebra that surfaces
7922    /// [`crate::ast::QuoteForm::prefix`]'s reader-punctuation vocabulary
7923    /// at the outer-shape algebra level.
7924    ///
7925    /// Composition law: `self.prefix() ==
7926    /// self.as_quote_form().map(crate::ast::QuoteForm::prefix)` for every
7927    /// `self: SexpShape` — the (outer shape, reader-punctuation prefix)
7928    /// pairing binds through the pre-existing quote-family carving
7929    /// ([`Self::as_quote_form`]) composed with the closed-set prefix
7930    /// projection ([`crate::ast::QuoteForm::prefix`]) rather than at a
7931    /// parallel four-arm inline match table here — matching the posture
7932    /// [`Self::iac_forge_tag`] takes through
7933    /// [`crate::ast::QuoteForm::iac_forge_tag`] on the cross-crate
7934    /// canonical-form tag axis and [`Self::as_unquote_form`] takes
7935    /// through [`crate::ast::QuoteForm::as_unquote_form`] on the 2-of-4
7936    /// template-substitution subset carving. `None` on every non-quote-
7937    /// family shape (six atomic-payload variants + `Nil` + `List`) — the
7938    /// eight-shape kernel of the projection matches the kernel of
7939    /// [`Self::as_quote_form`] AND [`Self::iac_forge_tag`] byte-for-byte.
7940    ///
7941    /// The EIGHTH consumer of the outer-shape algebra's shape-level
7942    /// projection surface, sibling of [`Self::label`] (operator-facing
7943    /// diagnostic-label surface, all twelve arms),
7944    /// [`Self::hash_discriminator`] (outer-`Sexp` cache-key discriminator
7945    /// byte, all twelve arms → `{0..=6}`), [`Self::as_atom_kind`]
7946    /// (6-of-12 atomic-payload carving), [`Self::as_quote_form`]
7947    /// (4-of-12 quote-family carving), [`Self::as_structural_kind`]
7948    /// (2-of-12 structural-residual carving), [`Self::as_unquote_form`]
7949    /// (2-of-12 template-substitution subset carving composed through
7950    /// the parent quote-family carving), and [`Self::iac_forge_tag`]
7951    /// (4-of-12 cross-crate canonical-form tag projection). Post-lift
7952    /// the outer-shape algebra closes over EIGHT typed projection
7953    /// surfaces, each rebuilt from the SAME closed twelve-arm set — a
7954    /// regression that drifts ONE projection against the algebra's
7955    /// shape-identity fails-loudly at rustc's exhaustiveness rather than
7956    /// silently at runtime.
7957    ///
7958    /// Pre-lift the (SexpShape variant, reader-punctuation prefix)
7959    /// pairing had no typed projection at the outer-shape algebra level
7960    /// — a consumer with a [`SexpShape`] in hand (a
7961    /// [`LispError::TypeMismatch`]'s `got` slot's outer-shape identity,
7962    /// a rejected shape from a typed-entry gate, an LSP hover / REPL
7963    /// completion / audit-trail metric keyed on the observed outer
7964    /// shape) wanting the reader's canonical homoiconic prefix character
7965    /// had to re-embed the shape into a quote-family typed marker via
7966    /// [`Self::as_quote_form`] then compose through
7967    /// [`crate::ast::QuoteForm::prefix`] inline. Post-lift the
7968    /// composition binds at ONE method on the outer [`SexpShape`]
7969    /// algebra — the (outer shape, reader-punctuation prefix) pairing
7970    /// lives at ONE typed projection matching the shape-level algebra
7971    /// sweep the seven sibling projections established.
7972    ///
7973    /// The `Option<&'static str>` return shape is the natural typed peer
7974    /// of [`crate::ast::QuoteForm::prefix`]'s total `&'static str`
7975    /// return: the quote-family superset TOTALS on the four-arm closed
7976    /// set, and the outer twelve-arm closed set's projection PARTIALIZES
7977    /// on the eight non-quote-family shapes. Same shape as
7978    /// [`Self::as_atom_kind`] / [`Self::as_quote_form`] /
7979    /// [`Self::as_structural_kind`] / [`Self::as_unquote_form`] /
7980    /// [`Self::iac_forge_tag`] — each carving lifts a total sub-algebra
7981    /// projection to a partial outer-shape projection with `None` on
7982    /// the residual arms, so a consumer keying on "iff this outer shape
7983    /// has a canonical reader-punctuation prefix" binds against the
7984    /// closed set's typed image directly rather than substring-matching
7985    /// the shape label.
7986    ///
7987    /// The reader-punctuation vocabulary this method projects (`"'"` /
7988    /// `` "`" `` / `","` / `",@"`) is INTENTIONALLY DISJOINT from the
7989    /// two sibling `&'static str` projection axes:
7990    ///
7991    /// * [`Self::iac_forge_tag`] — cross-crate canonical form
7992    ///   (`"quote"` / `"quasiquote"` / `"unquote"` /
7993    ///   `"unquote-splicing"`), BLAKE3 attestation keys, render-cache
7994    ///   shape (load-bearing for byte-identical inter-crate compatibility
7995    ///   with the iac-forge ecosystem).
7996    /// * [`Self::label`] — operator-facing diagnostic label (`"quote"` /
7997    ///   `"quasiquote"` / `"unquote"` / `"unquote-splice"`),
7998    ///   [`LispError::TypeMismatch`]'s `got` rendering, REPL / LSP
7999    ///   shape-of-witness surface.
8000    ///
8001    /// This method projects the reader's SOURCE-TEXT vocabulary — the
8002    /// four punctuation characters that appear literally in Lisp source
8003    /// at each variant's homoiconic prefix. The three closed-set
8004    /// projections key the SAME twelve-arm shape algebra on THREE
8005    /// distinct `&'static str` vocabularies (source-punctuation,
8006    /// diagnostic-label, cross-crate canonical-form); consolidating any
8007    /// two would silently break either the reader round-trip, the
8008    /// operator-facing diagnostic surface, OR the iac-forge attestation
8009    /// pipeline. The three vocabularies' distinctness is pinned bit-for-
8010    /// bit through the composition law across the closed-set typed
8011    /// algebra.
8012    ///
8013    /// The `&'static str` lifetime is load-bearing: every reader / LSP
8014    /// / REPL / diagnostic consumer projects through this method into
8015    /// the canonical prefix character without an allocation, parallel to
8016    /// how [`crate::ast::QuoteForm::prefix`] on the sub-carving,
8017    /// [`Self::iac_forge_tag`] on the cross-crate canonical-form axis,
8018    /// [`Self::label`] on the outer-shape diagnostic surface, and
8019    /// [`UnquoteForm::marker`] on the template marker axis project their
8020    /// respective closed sets. A future homoiconic prefix-wrapper (e.g.
8021    /// hypothetical `,~` reverse-unquote) extends
8022    /// [`crate::ast::QuoteForm`] AND [`Self`]'s closed set together —
8023    /// rustc binds the reader-punctuation surface to the extended
8024    /// algebra at ONE arm each through exhaustiveness, with the
8025    /// composition through [`Self::as_quote_form`] pulling the new
8026    /// variant's prefix into this projection mechanically.
8027    ///
8028    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
8029    /// (SexpShape variant, reader-punctuation prefix) pairing becomes a
8030    /// TYPE projection on the outer-shape algebra rather than an inline
8031    /// `.as_quote_form().map(...)` two-step at every consumer. A typo
8032    /// or swap at the projection site is no longer a runtime prefix
8033    /// drift but a compile error against the typed composition — the
8034    /// `SexpShape` ↔ `QuoteForm` ↔ prefix character chain is
8035    /// rustc-enforced end-to-end. THEORY.md §II.1 invariant 2 — free
8036    /// middle; the (outer shape, reader-punctuation prefix) pairing now
8037    /// binds at ONE site on the outer-shape algebra, composing through
8038    /// the pre-existing four-arm sub-carving's prefix projection rather
8039    /// than duplicating the four-arm match here. THEORY.md §VI.1 —
8040    /// generation over composition; the outer-shape algebra's typed
8041    /// projection surface closes over EIGHT methods (label,
8042    /// hash_discriminator, four carvings, iac_forge_tag, prefix) each
8043    /// keyed on the SAME closed twelve-arm set.
8044    ///
8045    /// Frontier inspiration: MLIR's `mlir::TypeSwitch<T>` typed
8046    /// pattern-match combinator on a closed union — narrowing to a
8047    /// specific typed case yields the typed downcast + operations on
8048    /// that case in ONE typed step. [`Self::prefix`] is the Rust-typed
8049    /// peer where the "narrow to quote-family" step
8050    /// ([`Self::as_quote_form`]) composes with the "read the case's
8051    /// reader-punctuation prefix" step
8052    /// ([`crate::ast::QuoteForm::prefix`]) into ONE outer-shape
8053    /// projection. Racket's `(match shape [(? quote-shape? qs)
8054    /// (quote-shape->reader-prefix qs)] [_ #f])` closed-form match on
8055    /// the shape-taxonomy union — the outer pattern's partial projection
8056    /// to the source-punctuation form IS the shape this method lifts to
8057    /// a typed Rust closed-set match.
8058    #[must_use]
8059    pub fn prefix(self) -> Option<&'static str> {
8060        self.as_quote_form().map(crate::ast::QuoteForm::prefix)
8061    }
8062
8063    /// Project the typed [`SexpShape`] into the outer-`Sexp`
8064    /// [`Hash for Sexp`](crate::ast::Sexp) cache-key discriminator byte
8065    /// `{0..=6}` — the SHAPE-LEVEL peer of
8066    /// [`crate::ast::Sexp::hash_discriminator`] one algebra level up.
8067    /// The twelve outer-shape arms collapse to seven outer bytes: the six
8068    /// atomic-payload arms (`Symbol`, `Keyword`, `String`, `Int`, `Float`,
8069    /// `Bool`) all project to the outer Atom marker byte `1u8` (their
8070    /// per-atom-kind inner byte lives nested inside
8071    /// [`Hash for Atom`](crate::ast::Atom) via
8072    /// [`crate::ast::AtomKind::hash_discriminator`]'s `{0..=5}` payload
8073    /// carving, NOT surfaced here); the two structural-residual arms
8074    /// (`Nil`, `List`) delegate through
8075    /// [`StructuralKind::hash_discriminator`] for `{0, 2}`; the four
8076    /// quote-family arms (`Quote`, `Quasiquote`, `Unquote`,
8077    /// `UnquoteSplice`) delegate through
8078    /// [`crate::ast::QuoteForm::hash_discriminator`] for `{3..=6}`. The
8079    /// byte partition is byte-identical to
8080    /// [`crate::ast::Sexp::hash_discriminator`]'s outer sweep, so the
8081    /// substrate's macro-expansion cache
8082    /// ([`crate::macro_expand::Expander`]'s cache) key stays bit-for-bit
8083    /// stable across the shape-level lift.
8084    ///
8085    /// Composition law: `sexp.hash_discriminator() ==
8086    /// sexp.shape().hash_discriminator()` for every `sexp: &Sexp` — the
8087    /// outer-`Sexp` cache-key discriminator method routes through
8088    /// [`crate::ast::Sexp::shape`] into THIS method, which in turn
8089    /// routes into the three closed-set carvings' discriminator
8090    /// methods. Post-lift the outer-`Sexp` cache-key algebra closes at
8091    /// FIVE typed layers:
8092    ///   1. [`crate::ast::Sexp::hash_discriminator`] — 7-arm outer
8093    ///      dispatch on the outer `Sexp` algebra;
8094    ///   2. [`Self::hash_discriminator`] — 12-arm shape-level dispatch on
8095    ///      the [`SexpShape`] algebra (this method);
8096    ///   3. [`StructuralKind::hash_discriminator`] — 2-arm
8097    ///      structural-residual sub-carving for `{0, 2}`;
8098    ///   4. [`crate::ast::QuoteForm::hash_discriminator`] — 4-arm
8099    ///      quote-family sub-carving for `{3..=6}`;
8100    ///   5. [`crate::ast::AtomKind::hash_discriminator`] — 6-arm
8101    ///      atomic-payload sub-carving for `{0..=5}` (nested inside
8102    ///      `Hash for Atom` under outer byte `1u8`, NOT surfaced through
8103    ///      this shape-level method).
8104    ///
8105    /// Pre-lift the outer-`Sexp` cache-key algebra bottomed out at the
8106    /// three sub-carvings' discriminator methods directly via
8107    /// [`crate::ast::Sexp::hash_discriminator`]'s seven arms — no
8108    /// intermediate shape-level projection existed. Consumers that had a
8109    /// typed [`SexpShape`] identity in hand (a
8110    /// `LispError::TypeMismatch.got` slot, a rejected shape from a
8111    /// typed-entry gate, a future authoring-tool audit-trail metric keyed
8112    /// on the observed shape) wanting the outer cache-key byte had to
8113    /// re-embed the typed shape into a [`crate::ast::Sexp`] value and
8114    /// route through the outer method — an unnecessary allocation. Post-
8115    /// lift the shape-level projection is a NAMED PRIMITIVE on the closed-
8116    /// set [`SexpShape`] algebra sitting next to the three sibling shape-
8117    /// level projections ([`Self::label`], [`Self::as_atom_kind`],
8118    /// [`Self::as_quote_form`], [`Self::as_structural_kind`],
8119    /// [`Self::as_unquote_form`]) — the outer cache-key partition
8120    /// becomes a typed identity on the shape algebra rather than a
8121    /// projection through the outer-`Sexp` value carrier.
8122    ///
8123    /// `pub(crate)` because the byte-discriminator surface is an
8124    /// implementation detail of the substrate's `Hash for Sexp` cache-
8125    /// key contract; exposing it publicly would leak the cache-key shape
8126    /// through the API without enabling any external consumer the public
8127    /// projections ([`Self::label`], [`Self::as_atom_kind`],
8128    /// [`Self::as_quote_form`], [`Self::as_structural_kind`]) don't
8129    /// already serve. Same posture as
8130    /// [`crate::ast::Sexp::hash_discriminator`],
8131    /// [`crate::ast::AtomKind::hash_discriminator`],
8132    /// [`crate::ast::QuoteForm::hash_discriminator`], and
8133    /// [`StructuralKind::hash_discriminator`].
8134    ///
8135    /// A future thirteenth [`SexpShape`] variant (e.g. `Vector` for
8136    /// `#(...)` reader syntax, `Map` for `{...}`, `Char` for `#\x`)
8137    /// picks a fresh cache-key byte outside `{0..=6}` (e.g. `7u8`),
8138    /// extends either [`StructuralKind`] / [`crate::ast::AtomKind`] /
8139    /// [`crate::ast::QuoteForm`] (if it names an existing sub-carving)
8140    /// or a fresh sub-algebra, and lands at ONE new arm here — rustc
8141    /// binds the consistency through exhaustiveness over the closed
8142    /// [`SexpShape`] enum.
8143    ///
8144    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
8145    /// (SexpShape variant, outer cache-key byte) pairing becomes a TYPE
8146    /// projection on the substrate's shape algebra rather than requiring
8147    /// a re-embed into [`crate::ast::Sexp`] followed by an outer
8148    /// dispatch. A typo or swap at the shape-level projection site is
8149    /// a compile error against the typed projection rather than a
8150    /// silent runtime cache-key drift. THEORY.md §II.1 invariant 2 —
8151    /// free middle; the outer-`Sexp` cache-key algebra now closes over
8152    /// FIVE typed layers (outer, shape, three sub-carvings) with rustc-
8153    /// enforced consistency across each — a regression that drifts ONE
8154    /// layer's discriminator bytes from the others cannot reach the
8155    /// substrate's runtime cache. THEORY.md §VI.1 — generation over
8156    /// composition; the shape-level projection is the missing algebra
8157    /// layer between the outer-`Sexp` and the three sub-carvings — the
8158    /// four pre-existing typed layers become a full FIVE-layer typed
8159    /// composition through ONE new named projection.
8160    #[must_use]
8161    pub(crate) fn hash_discriminator(self) -> u8 {
8162        match self {
8163            Self::Nil => StructuralKind::Nil.hash_discriminator(),
8164            Self::List => StructuralKind::List.hash_discriminator(),
8165            Self::Symbol | Self::Keyword | Self::String | Self::Int | Self::Float | Self::Bool => {
8166                crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR
8167            }
8168            Self::Quote => crate::ast::QuoteForm::Quote.hash_discriminator(),
8169            Self::Quasiquote => crate::ast::QuoteForm::Quasiquote.hash_discriminator(),
8170            Self::Unquote => crate::ast::QuoteForm::Unquote.hash_discriminator(),
8171            Self::UnquoteSplice => crate::ast::QuoteForm::UnquoteSplice.hash_discriminator(),
8172        }
8173    }
8174
8175    /// Canonical `u8` outer-`Sexp` cache-key byte for the [`Self::Nil`]
8176    /// outer shape — aliases [`StructuralKind::NIL_HASH_DISCRIMINATOR`]
8177    /// on the StructuralKind ⊂ SexpShape carving so the marker-level
8178    /// byte binds at ONE `pub(crate) const` on the SUB-CARVING's
8179    /// per-role primitive rather than at TWO sites (the sub-carving's
8180    /// per-role byte AND a parallel outer-shape inline literal). The
8181    /// canonical source of the byte lives on the sub-carving; this
8182    /// outer-shape peer alias projects it into the twelve-arm
8183    /// [`SexpShape`] algebra at zero-drift cost.
8184    ///
8185    /// Sibling posture to the twelve peer per-role `*_LABEL` aliases
8186    /// on [`Self`] — the `Nil` outer-shape per-role LABEL is canonical
8187    /// on [`Self`] and is aliased INTO [`StructuralKind::NIL_LABEL`];
8188    /// this per-role HASH_DISCRIMINATOR alias runs in the OPPOSITE
8189    /// direction because the outer-`Sexp` cache-key byte's canonical
8190    /// source is the sub-carving (which the outer `Sexp` dispatch
8191    /// composes through). The load-bearing invariant across the two
8192    /// axes is byte-identity: the outer-shape peer alias equals the
8193    /// sub-carving's canonical byte at rustc-time through the
8194    /// `const` initializer.
8195    pub(crate) const NIL_HASH_DISCRIMINATOR: u8 = StructuralKind::NIL_HASH_DISCRIMINATOR;
8196
8197    /// Canonical `u8` outer-`Sexp` cache-key byte for the [`Self::Symbol`]
8198    /// outer shape — aliases [`crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR`]
8199    /// on the AtomKind ⊂ SexpShape carving. Every atomic-payload outer
8200    /// shape shares this outer marker byte because the per-atom-kind
8201    /// specialisation lives at the NESTED INNER
8202    /// [`crate::ast::AtomKind::HASH_DISCRIMINATORS`] `{0..=5}` carve
8203    /// inside `Hash for Atom`, NOT surfaced through this outer shape-
8204    /// level projection.
8205    pub(crate) const SYMBOL_HASH_DISCRIMINATOR: u8 = crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR;
8206
8207    /// Canonical `u8` outer-`Sexp` cache-key byte for the [`Self::Keyword`]
8208    /// outer shape — aliases [`crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR`]
8209    /// on the AtomKind ⊂ SexpShape carving. Six-way collapse peer of
8210    /// [`Self::SYMBOL_HASH_DISCRIMINATOR`].
8211    pub(crate) const KEYWORD_HASH_DISCRIMINATOR: u8 =
8212        crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR;
8213
8214    /// Canonical `u8` outer-`Sexp` cache-key byte for the [`Self::String`]
8215    /// outer shape — aliases [`crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR`].
8216    /// Six-way collapse peer.
8217    pub(crate) const STRING_HASH_DISCRIMINATOR: u8 = crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR;
8218
8219    /// Canonical `u8` outer-`Sexp` cache-key byte for the [`Self::Int`]
8220    /// outer shape — aliases [`crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR`].
8221    /// Six-way collapse peer.
8222    pub(crate) const INT_HASH_DISCRIMINATOR: u8 = crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR;
8223
8224    /// Canonical `u8` outer-`Sexp` cache-key byte for the [`Self::Float`]
8225    /// outer shape — aliases [`crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR`].
8226    /// Six-way collapse peer.
8227    pub(crate) const FLOAT_HASH_DISCRIMINATOR: u8 = crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR;
8228
8229    /// Canonical `u8` outer-`Sexp` cache-key byte for the [`Self::Bool`]
8230    /// outer shape — aliases [`crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR`].
8231    /// Six-way collapse peer of the atomic-outer sextet.
8232    pub(crate) const BOOL_HASH_DISCRIMINATOR: u8 = crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR;
8233
8234    /// Canonical `u8` outer-`Sexp` cache-key byte for the [`Self::List`]
8235    /// outer shape — aliases [`StructuralKind::LIST_HASH_DISCRIMINATOR`]
8236    /// on the StructuralKind ⊂ SexpShape carving. Structural-residual
8237    /// peer of [`Self::NIL_HASH_DISCRIMINATOR`]; the gap at `1u8`
8238    /// between the two structural-residual bytes reserves the outer
8239    /// atomic-carve byte the six-way atomic-outer sextet collapses to.
8240    pub(crate) const LIST_HASH_DISCRIMINATOR: u8 = StructuralKind::LIST_HASH_DISCRIMINATOR;
8241
8242    /// Canonical `u8` outer-`Sexp` cache-key byte for the [`Self::Quote`]
8243    /// outer shape — aliases [`crate::ast::QuoteForm::QUOTE_HASH_DISCRIMINATOR`]
8244    /// on the QuoteForm ⊂ SexpShape carving.
8245    pub(crate) const QUOTE_HASH_DISCRIMINATOR: u8 = crate::ast::QuoteForm::QUOTE_HASH_DISCRIMINATOR;
8246
8247    /// Canonical `u8` outer-`Sexp` cache-key byte for the
8248    /// [`Self::Quasiquote`] outer shape — aliases
8249    /// [`crate::ast::QuoteForm::QUASIQUOTE_HASH_DISCRIMINATOR`].
8250    pub(crate) const QUASIQUOTE_HASH_DISCRIMINATOR: u8 =
8251        crate::ast::QuoteForm::QUASIQUOTE_HASH_DISCRIMINATOR;
8252
8253    /// Canonical `u8` outer-`Sexp` cache-key byte for the [`Self::Unquote`]
8254    /// outer shape — aliases [`crate::ast::QuoteForm::UNQUOTE_HASH_DISCRIMINATOR`].
8255    pub(crate) const UNQUOTE_HASH_DISCRIMINATOR: u8 =
8256        crate::ast::QuoteForm::UNQUOTE_HASH_DISCRIMINATOR;
8257
8258    /// Canonical `u8` outer-`Sexp` cache-key byte for the
8259    /// [`Self::UnquoteSplice`] outer shape — aliases
8260    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_HASH_DISCRIMINATOR`].
8261    pub(crate) const UNQUOTE_SPLICE_HASH_DISCRIMINATOR: u8 =
8262        crate::ast::QuoteForm::UNQUOTE_SPLICE_HASH_DISCRIMINATOR;
8263
8264    /// Closed-set forced-arity ALL array over the canonical outer-`Sexp`
8265    /// cache-key `u8` bytes, in declaration order matching [`Self::ALL`]
8266    /// element-wise (pinned by
8267    /// `sexp_shape_hash_discriminators_align_with_all_by_index`).
8268    /// Sibling posture to [`Self::LABELS`] (`[&'static str; 12]` — the
8269    /// diagnostic-label axis on the same twelve-variant closed set) AND
8270    /// to the sub-carvings' HASH_DISCRIMINATORS peers:
8271    /// [`StructuralKind::HASH_DISCRIMINATORS`] (`[u8; 2]` — `{0, 2}`),
8272    /// [`crate::ast::QuoteForm::HASH_DISCRIMINATORS`] (`[u8; 4]` — `{3..=6}`),
8273    /// [`crate::ast::AtomKind::HASH_DISCRIMINATORS`] (`[u8; 6]` — the
8274    /// NESTED INNER `{0..=5}` bytes inside `Hash for Atom` under the
8275    /// outer `1u8` marker), and [`crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR`]
8276    /// (scalar `1u8` — the six-way outer-collapse byte). This lift joins
8277    /// the outer-shape closed set to the family-wide ALL-array posture
8278    /// every sibling closed set already carries, closing the SIXTH
8279    /// per-family axis on the [`SexpShape`] algebra (ALL, LABELS,
8280    /// as_atom_kind, as_quote_form, as_structural_kind, plus this).
8281    ///
8282    /// Pre-lift the twelve outer-shape cache-key bytes had NO family-wide
8283    /// forced-arity primitive on the outer-shape algebra — a consumer
8284    /// wanting to sweep the outer partition through a `zip(SexpShape::ALL,
8285    /// bytes)` had to compose the twelve bytes inline by dispatching
8286    /// through [`Self::hash_discriminator`] at runtime OR by re-deriving
8287    /// the twelve-arm collapse against the three sub-carvings'
8288    /// HASH_DISCRIMINATORS arrays + the atomic outer-marker byte at every
8289    /// call site. Post-lift the twelve bytes bind at ONE typed `[u8; 12]`
8290    /// primitive on the outer-shape algebra composed from the sub-
8291    /// carvings' canonical bytes — a future LSP / REPL completion bar
8292    /// keyed on the shape's cache-key byte, a `tatara-check` coverage
8293    /// sweep over the outer partition, or a Sekiban audit-trail metric
8294    /// jointly labeled by the outer-shape byte picks up the same
8295    /// canonical bytes from ONE source of truth.
8296    ///
8297    /// Composition law (pinned by
8298    /// `sexp_shape_hash_discriminators_align_with_hash_discriminator_by_index`):
8299    /// for every `i in 0..12`, `Self::HASH_DISCRIMINATORS[i] ==
8300    /// Self::ALL[i].hash_discriminator()`. Sibling to the peer alignment
8301    /// pins on the three sub-carvings' HASH_DISCRIMINATORS arrays — this
8302    /// pin closes the family-wide alignment at the OUTER twelve-arm
8303    /// algebra so a regression that reorders ONE array without reordering
8304    /// the other silently misaligns every `zip(ALL, HASH_DISCRIMINATORS)`
8305    /// consumer.
8306    ///
8307    /// Partition closure (pinned by
8308    /// `sexp_shape_hash_discriminators_cover_outer_partition_zero_through_six`):
8309    /// the twelve bytes collect to exactly `{0..=6}` as a set — the
8310    /// same outer-partition space [`Self::hash_discriminator`] surjects
8311    /// onto. Sibling to the pre-existing
8312    /// `sexp_shape_hash_discriminator_covers_outer_partition_zero_through_six`
8313    /// projection-driven pin — that pin closes the partition through
8314    /// the projection method; this pin closes it at the array level so
8315    /// a regression that drifts the array's contents without changing
8316    /// the method surfaces here.
8317    ///
8318    /// The `#[allow(dead_code)]` posture matches
8319    /// [`StructuralKind::HASH_DISCRIMINATORS`] /
8320    /// [`crate::ast::QuoteForm::HASH_DISCRIMINATORS`] /
8321    /// [`crate::ast::AtomKind::HASH_DISCRIMINATORS`]: the substrate's
8322    /// current [`Self::hash_discriminator`] body dispatches through the
8323    /// twelve-arm per-role per-variant match rather than through an
8324    /// [`Self::HASH_DISCRIMINATORS`] index lookup (patterns cannot be
8325    /// array-indexing expressions in the current const-fn grammar); the
8326    /// lift lands the substrate primitive so future consumers keyed on
8327    /// the whole outer-shape cache-key algebra (a future `tatara-check`
8328    /// predicate `(check-outer-shape-partition-injective …)`; a future
8329    /// `TypedRewriter<ShapeOp>` sweep zipping ALL / LABELS /
8330    /// HASH_DISCRIMINATORS in lockstep) pick it up through ONE typed
8331    /// primitive.
8332    ///
8333    /// A hypothetical thirteenth [`SexpShape`] variant (`Vector` for
8334    /// `#(...)`, `Map` for `{...}`, `Char` for `#\x`) extends [`Self::ALL`]
8335    /// AND [`Self::LABELS`] AND [`Self::HASH_DISCRIMINATORS`] AND adds
8336    /// ONE new per-role `pub(crate) const *_HASH_DISCRIMINATOR` alias in
8337    /// lockstep — rustc's forced-arity check on the three `[_; N]`
8338    /// arrays fails compilation if ONE array grows without the others,
8339    /// closing the extensibility gap the pre-lift outer-shape algebra
8340    /// silently allowed at the [`Self::hash_discriminator`] match body.
8341    ///
8342    /// Theory anchor: THEORY.md §III — the typescape; the twelve
8343    /// canonical outer-shape cache-key bytes bind at ONE typed
8344    /// `[u8; 12]` array on the closed-set outer [`SexpShape`] algebra
8345    /// rather than at zero-primitive-plus-runtime-dispatch through the
8346    /// three sub-carvings' twelve-arm collapse. THEORY.md §V.1 —
8347    /// knowable platform; the family's cardinality becomes a
8348    /// TYPE-level constant on the substrate algebra rather than a
8349    /// per-consumer runtime dispatch through the match table. THEORY.md
8350    /// §V.3 — three-pillar attestation; the outer-shape cache-key
8351    /// partition is the substrate's outer `Sexp` `intent_hash`
8352    /// composition axis — binding the twelve-arm ALL array on the typed
8353    /// algebra makes attestation-key drift a compile error rather than
8354    /// a silent BLAKE3 mis-hash. THEORY.md §VI.1 — generation over
8355    /// composition; the family-wide contract sweeps (alignment with
8356    /// `ALL`, membership through [`Self::hash_discriminator`], partition
8357    /// closure onto `{0..=6}`) emerge from the composition of ONE
8358    /// substrate primitive (this `pub(crate) const` array) rather than
8359    /// as per-variant inline assertions at each call site.
8360    #[allow(dead_code)]
8361    pub(crate) const HASH_DISCRIMINATORS: [u8; 12] = [
8362        Self::NIL_HASH_DISCRIMINATOR,
8363        Self::SYMBOL_HASH_DISCRIMINATOR,
8364        Self::KEYWORD_HASH_DISCRIMINATOR,
8365        Self::STRING_HASH_DISCRIMINATOR,
8366        Self::INT_HASH_DISCRIMINATOR,
8367        Self::FLOAT_HASH_DISCRIMINATOR,
8368        Self::BOOL_HASH_DISCRIMINATOR,
8369        Self::LIST_HASH_DISCRIMINATOR,
8370        Self::QUOTE_HASH_DISCRIMINATOR,
8371        Self::QUASIQUOTE_HASH_DISCRIMINATOR,
8372        Self::UNQUOTE_HASH_DISCRIMINATOR,
8373        Self::UNQUOTE_SPLICE_HASH_DISCRIMINATOR,
8374    ];
8375}
8376
8377// `impl std::fmt::Display for SexpShape` + `impl std::str::FromStr for
8378// SexpShape` + `impl tatara_closed_set::ClosedSet for SexpShape` +
8379// `pub struct UnknownSexpShape(pub String)` are generated by
8380// `#[derive(tatara_closed_set::DeriveClosedSet)]` on the enum declaration
8381// above. `label` delegates to the inherent `SexpShape::label` via
8382// `#[closed_set(via = "label")]` — the inherent name coincides with
8383// the trait method name here; the delegation stays explicit so the
8384// SAME wiring shape applies whether the inherent projection is `label`
8385// / `prefix` / `marker` / `keyword` / `as_str`. The `display` flag
8386// emits the substrate-wide `f.write_str(Self::label(*self))` block.
8387// `#[closed_set(generate_unknown = "sexp shape")]` emits the typed
8388// parse-rejection carrier with the substrate-wide `Debug + Clone +
8389// PartialEq + Eq + thiserror::Error` derives and the `#[error("unknown
8390// sexp shape: {0}")]` annotation byte-for-byte; the explicit label
8391// matches the auto-derived `pascal_to_spaced_lowercase("SexpShape")`
8392// projection byte-for-byte but pins the pre-lift wording. Round-trip
8393// + cross-axis rejection (`ExpectedKwargShape` labels `"number"` /
8394// `"list of strings"` whose vocabulary partially overlaps SexpShape on
8395// five of seven entries) pinned by
8396// `sexp_shape_label_round_trips_through_from_str` +
8397// `sexp_shape_from_str_accepts_only_canonical_labels`.
8398
8399/// Closed-set identifier for the STRUCTURAL-RESIDUAL carving of the
8400/// twelve-variant [`SexpShape`] outer-shape lattice — the 2-of-12
8401/// subset that names the two outermost shapes lying outside BOTH
8402/// the atomic-payload carving ([`crate::ast::AtomKind`], 6-of-12) and
8403/// the quote-family carving ([`crate::ast::QuoteForm`], 4-of-12):
8404/// [`crate::ast::Sexp::Nil`] (the empty-result marker projecting to
8405/// [`SexpShape::Nil`]) and [`crate::ast::Sexp::List`] (the
8406/// payload-bearing container projecting to [`SexpShape::List`]).
8407///
8408/// Third-and-final closed-set carving of [`SexpShape`], sibling of
8409/// [`crate::ast::AtomKind`] (the 6-of-12 atomic-payload carving) and
8410/// [`crate::ast::QuoteForm`] (the 4-of-12 quote-family carving). The
8411/// three carvings partition [`SexpShape::ALL`] disjointly (6 + 4 + 2 =
8412/// 12) — every outer shape is in EXACTLY ONE carving, and the
8413/// partition-total invariant becomes a TYPED THEOREM through the
8414/// three sibling projections
8415/// ([`SexpShape::as_atom_kind`], [`SexpShape::as_quote_form`],
8416/// [`SexpShape::as_structural_kind`]) rather than through a runtime
8417/// `matches!(shape, SexpShape::Nil | SexpShape::List)` predicate that
8418/// the substrate maintained by test discipline at the carving-
8419/// disjointness pin pre-lift.
8420///
8421/// Mirror at the residual-carving boundary of the prior-run
8422/// [`crate::ast::AtomKind`] (atomic-payload closed set),
8423/// [`crate::ast::QuoteForm`] (quote-family closed set), and
8424/// [`UnquoteForm`] (template-substitution subset closed set) lifts:
8425/// those enums key their respective carvings on a typed identity
8426/// carried inside their variant listings; this enum keys the RESIDUAL
8427/// carving that lies outside all three of them on the same
8428/// [`SexpShape`] outer-shape lattice, closing the three-carving
8429/// partition.
8430///
8431/// Theory anchor: THEORY.md §V.1 — knowable platform; the residual
8432/// carving becomes a TYPE rather than an inline
8433/// `matches!(shape, SexpShape::Nil | SexpShape::List)` predicate the
8434/// substrate maintained by callsite discipline. THEORY.md §VI.1 —
8435/// generation over composition; the typed enum lands the structural-
8436/// completeness floor for the residual outer-shape surface, parallel
8437/// to how [`crate::ast::AtomKind`] lands it for the atomic-payload
8438/// carving and [`crate::ast::QuoteForm`] lands it for the quote-family
8439/// carving. THEORY.md §II.1 invariant 1 — typed entry; the
8440/// (structural-residual identity, outer shape) pairing is first-class
8441/// load-bearing data on the substrate algebra rather than a runtime
8442/// negation of the two sibling carvings.
8443#[derive(Debug, Clone, Copy, PartialEq, Eq, tatara_closed_set::DeriveClosedSet)]
8444#[closed_set(via = "label", display, generate_unknown = "structural kind")]
8445pub enum StructuralKind {
8446    /// `[`crate::ast::Sexp::Nil`]` — the empty-result marker projecting
8447    /// to [`SexpShape::Nil`]. `"nil"` diagnostic label (byte-for-byte
8448    /// identical to [`SexpShape::Nil`]'s label under the
8449    /// [`Self::sexp_shape`] composition).
8450    Nil,
8451    /// `[`crate::ast::Sexp::List(_)`]` — the payload-bearing container
8452    /// projecting to [`SexpShape::List`]. `"list"` diagnostic label
8453    /// (byte-for-byte identical to [`SexpShape::List`]'s label under
8454    /// the [`Self::sexp_shape`] composition).
8455    List,
8456}
8457
8458impl StructuralKind {
8459    /// The closed set of two structural-residual outer shapes — single
8460    /// source of truth that drives the [`Self::label`] / [`fmt::Display`]
8461    /// projection AND the [`FromStr`] decode sweep keyed on
8462    /// [`Self::label`]. Adding a hypothetical third structural-residual
8463    /// variant (e.g. `Vector` for `#(...)` reader syntax, `Map` for
8464    /// `{...}`, or `Char` for `#\x` — each of which extends
8465    /// [`SexpShape::ALL`] AND joins THIS enum's residual carving iff
8466    /// it lies outside BOTH the atomic-payload carving and the
8467    /// quote-family carving) lands at one [`Self::ALL`] entry + one
8468    /// [`Self::sexp_shape`] arm + one [`SexpShape::as_structural_kind`]
8469    /// arm — exhaustively checked by the compiler (the `[Self; 2]`
8470    /// array literal forces the arity) AND by the per-variant truth-
8471    /// table tests below.
8472    ///
8473    /// Sibling closed-set lift to every other typed-shape enum the
8474    /// substrate carries: this crate's own [`SexpShape::ALL`] (the
8475    /// twelve reachable outer shapes — superset of this residual's
8476    /// two via [`Self::sexp_shape`]), [`crate::ast::AtomKind::ALL`]
8477    /// (the six atomic-payload kinds — peer 6-of-12 carving of
8478    /// [`SexpShape`]), [`crate::ast::QuoteForm::ALL`] (the four
8479    /// homoiconic prefix-wrappers — peer 4-of-12 carving of
8480    /// [`SexpShape`]), [`UnquoteForm::ALL`] (the two template-
8481    /// substitution markers — proper 2-of-4 subset of [`crate::ast::QuoteForm`]),
8482    /// and the cross-crate `tatara-process` family (`ConditionKind::ALL`,
8483    /// `ProcessPhase::ALL`, `ProcessSignal::ALL`, `ChannelKind::ALL`,
8484    /// `IntentKind::ALL`, `LifetimeKind::ALL`, `RequestorKind::ALL`,
8485    /// `ReceiptKind::ALL`, …) every one of which paired its typed
8486    /// projection with `ALL` before this lift.
8487    ///
8488    /// Future consumers that compose against `ALL`: LSP / REPL
8489    /// completion for the operator-facing rendered residual-shape
8490    /// label under the "structural" carving-axis column; a future
8491    /// `tatara-check` predicate `(check-shape-projects-to-structural
8492    /// …)` that filters diagnostics to "was this rejection on the
8493    /// structural residual (neither atomic nor quote-family)?"; any
8494    /// future audit-trail metric jointly labeled by [`Self::label`]
8495    /// (e.g. `tatara_lisp_structural_shape_total{shape="list"}`) —
8496    /// the metric label set IS [`Self::ALL`] mapped through
8497    /// [`Self::label`]; any future structural rewriter (typed analogue
8498    /// of MLIR's `op.walk<StructuralOp>()`) that wants to sweep over
8499    /// every residual shape in a typed sequence.
8500    pub const ALL: [Self; 2] = [Self::Nil, Self::List];
8501
8502    /// Canonical `&'static str` bytes for the [`Self::Nil`] structural-
8503    /// residual marker — aliases [`SexpShape::NIL_LABEL`] on the
8504    /// StructuralKind ⊂ SexpShape carving so the marker-level per-role
8505    /// bytes bind at ONE `pub const` on the parent superset's residual
8506    /// arm rather than at TWO sites (the per-role `pub const` AND a
8507    /// parallel inline literal). Per-role peer of `Self::Nil` on the
8508    /// closed-set structural-residual algebra; consumers reach for
8509    /// `StructuralKind::NIL_LABEL` when the caller has a variant in
8510    /// hand at compile time and wants the canonical diagnostic bytes
8511    /// without runtime dispatch through [`Self::label`].
8512    ///
8513    /// Sibling posture to the peer 6-of-12 atomic-payload carving's
8514    /// per-role aliases ([`crate::ast::AtomKind::SYMBOL_LABEL`] …
8515    /// [`crate::ast::AtomKind::BOOL_LABEL`] — every one an alias of
8516    /// its [`SexpShape`] peer) and the peer 4-of-12 quote-family
8517    /// carving's per-role prefixes on [`crate::ast::QuoteForm`] — the
8518    /// third and final closed-set carving of [`SexpShape`] now
8519    /// surfaces its per-role marker bytes through the same alias-chain
8520    /// shape rather than through the composition
8521    /// [`Self::sexp_shape`] + [`SexpShape::label`] alone.
8522    pub const NIL_LABEL: &'static str = SexpShape::NIL_LABEL;
8523
8524    /// Canonical `&'static str` bytes for the [`Self::List`]
8525    /// structural-residual marker — aliases [`SexpShape::LIST_LABEL`]
8526    /// on the StructuralKind ⊂ SexpShape carving. Per-role peer of
8527    /// `Self::List`; matches [`ExpectedKwargShape::LIST_LABEL`] byte-
8528    /// for-byte through the SexpShape canonical site (the same
8529    /// cross-axis overlap [`SexpShape::LIST_LABEL`]'s docstring pins).
8530    pub const LIST_LABEL: &'static str = SexpShape::LIST_LABEL;
8531
8532    /// Closed-set forced-arity ALL array over the canonical structural-
8533    /// residual marker `&'static str` bytes, in declaration order
8534    /// matching [`Self::ALL`] element-wise (pinned by
8535    /// `structural_kind_labels_align_with_all_by_index`). Sibling
8536    /// posture to [`SexpShape::LABELS`] (`[&'static str; 12]` — the
8537    /// superset carving this StructuralKind subset embeds into),
8538    /// [`crate::ast::AtomKind::LABELS`] (`[&'static str; 6]` — the
8539    /// peer 6-of-12 atomic-payload carving's ALL array),
8540    /// [`ExpectedKwargShape::LABELS`] (`[&'static str; 7]`),
8541    /// [`KwargPathKind::LABELS`] (`[&'static str; 3]`),
8542    /// [`MacroDefHead::KEYWORDS`] (`[&'static str; 3]`),
8543    /// [`crate::ast::Atom::BOOL_LITERALS`] (`[&'static str; 2]`), and
8544    /// [`crate::ast::QuoteForm::PREFIXES`] (`[&'static str; 4]`) —
8545    /// every closed-set outer projection on the substrate that carries
8546    /// an `&'static str`-per-variant label now pins its per-role
8547    /// canonical bytes at ONE `pub const` per role PLUS an ALL array
8548    /// for family-wide consumers.
8549    ///
8550    /// Pre-lift the two structural-residual marker bytes had NO per-
8551    /// role primitive on this closed-set algebra — a consumer with a
8552    /// `StructuralKind` variant in hand at compile time reaching for
8553    /// the canonical diagnostic bytes had to spell
8554    /// `StructuralKind::Nil.label()` (runtime dispatch through the
8555    /// composition [`Self::sexp_shape`] + [`SexpShape::label`]) OR
8556    /// reach across the algebra boundary into
8557    /// [`SexpShape::NIL_LABEL`] and re-derive the StructuralKind ⊂
8558    /// SexpShape variant pairing at the call site. Post-lift the TWO
8559    /// canonical bytes bind at ONE `pub const` per role on the typed
8560    /// [`StructuralKind`] algebra AND at [`Self::LABELS`] as a family-
8561    /// wide forced-arity array — a future LSP / REPL completion bar
8562    /// keyed on `StructuralKind::LABELS` for the "structural" carving-
8563    /// axis column, a `tatara-check` coverage sweep over the residual
8564    /// arms of a `TypeMismatch.got` corpus, or a Sekiban audit-trail
8565    /// metric jointly labeled by the structural marker
8566    /// (`tatara_lisp_structural_shape_total{kind="list"}`) reads
8567    /// through the typed constants on this subset algebra without
8568    /// re-deriving the 2-of-12 carving inline.
8569    ///
8570    /// Each entry is byte-for-byte identical to the corresponding
8571    /// [`SexpShape`] residual arm — an intentional cross-axis overlap
8572    /// pinned by
8573    /// `structural_kind_per_role_labels_alias_sexp_shape_per_role_labels_byte_for_byte`
8574    /// so a future label rename on EITHER side (a SexpShape `"nil"` →
8575    /// `"empty"` drift, or a StructuralKind rename that skips the
8576    /// alias) fails-loudly at the alias test rather than as a silent
8577    /// operator-facing vocabulary fracture. Adding a hypothetical
8578    /// third structural-residual variant (e.g. `Vector` for `#(...)`
8579    /// reader syntax, `Map` for `{...}`) extends [`Self::ALL`] AND
8580    /// [`Self::LABELS`] AND adds ONE per-role `pub const` alias in
8581    /// lockstep — rustc's forced-arity check on the two `[_; N]`
8582    /// arrays fails compilation if EITHER ALL array grows without the
8583    /// other.
8584    ///
8585    /// Theory anchor: THEORY.md §III — the typescape; the two
8586    /// canonical structural-residual marker bytes bind at ONE typed
8587    /// `[&'static str; 2]` array on the closed-set StructuralKind
8588    /// algebra rather than at zero-primitive-on-this-subset-plus-two-
8589    /// inline-lookups scattered across the substrate. THEORY.md §V.1
8590    /// — knowable platform; the family's cardinality becomes a TYPE-
8591    /// level constant on the substrate algebra rather than a per-
8592    /// consumer runtime dispatch through the composition. THEORY.md
8593    /// §VI.1 — generation over composition; the family-wide contract
8594    /// sweeps (alignment with `ALL`, pairwise disjointness, membership
8595    /// through [`Self::label`]) emerge from the composition of TWO
8596    /// substrate primitives (this `pub const` array + the two per-
8597    /// role `pub const *_LABEL` aliases) rather than as per-variant
8598    /// inline assertions duplicated at each call site.
8599    pub const LABELS: [&'static str; 2] = [Self::NIL_LABEL, Self::LIST_LABEL];
8600
8601    /// Project the typed `StructuralKind` to the canonical `&'static
8602    /// str` diagnostic label — `"nil"` for [`Self::Nil`], `"list"` for
8603    /// [`Self::List`]. Each label is byte-for-byte identical to the
8604    /// corresponding [`SexpShape`] variant's label — and post-lift
8605    /// this agreement is STRUCTURAL rather than a two-literal-
8606    /// discipline site pinned by a cross-projection test.
8607    ///
8608    /// Composition law: `StructuralKind::label(sk) ==
8609    /// StructuralKind::sexp_shape(sk).label()` for every `sk:
8610    /// StructuralKind`. Body composes [`Self::sexp_shape`] (the typed
8611    /// projection lifting each StructuralKind variant into its peer
8612    /// [`SexpShape`] variant) with [`SexpShape::label`] (the canonical
8613    /// `&'static str` projection on the superset's twelve-variant
8614    /// closed set), so the two residual-arm labels live at ONE
8615    /// canonical site ([`SexpShape::label`]) rather than at TWO
8616    /// ([`SexpShape::label`] AND a parallel two-arm match here).
8617    ///
8618    /// Same lift posture as the sibling
8619    /// [`crate::ast::AtomKind::label`] ⊂ [`SexpShape::label`]
8620    /// composition (via [`crate::ast::AtomKind::sexp_shape`]) and
8621    /// [`UnquoteForm::marker`] ⊂ [`crate::ast::QuoteForm::prefix`]
8622    /// composition (via [`UnquoteForm::to_quote_form`]): the typed
8623    /// projection sits on the value, and the consumer composes
8624    /// through the existing structural pairing rather than re-
8625    /// deriving the per-variant literal. The `&'static str` lifetime
8626    /// is load-bearing: the composition allocates nothing at runtime
8627    /// ([`Self::sexp_shape`] returns a `Copy` value and
8628    /// [`SexpShape::label`] yields `&'static str`).
8629    ///
8630    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
8631    /// StructuralKind ⊂ SexpShape label-vocabulary containment
8632    /// becomes a TYPED CONSEQUENCE of the [`Self::sexp_shape`] +
8633    /// [`SexpShape::label`] composition rather than literal discipline
8634    /// at two sites. THEORY.md §VI.1 — generation over composition;
8635    /// the two residual-arm labels live at ONE canonical site
8636    /// ([`SexpShape::label`]) and this method generates its identity
8637    /// through the typed-projection composition. THEORY.md §II.1
8638    /// invariant 2 — free middle; consumers of the [`StructuralKind`]
8639    /// algebra route through ONE typed closed-set projection family
8640    /// with no per-consumer literal duplication.
8641    #[must_use]
8642    pub fn label(self) -> &'static str {
8643        self.sexp_shape().label()
8644    }
8645
8646    /// Project the typed marker into its matching [`SexpShape`]
8647    /// variant — `Nil → SexpShape::Nil`, `List → SexpShape::List`.
8648    /// ONE projection on the closed-set structural-residual algebra
8649    /// that binds the (StructuralKind variant, SexpShape variant)
8650    /// pairing at ONE site on the typed algebra rather than at two
8651    /// byte-identical inline arms scattered across consumers. Direct
8652    /// sibling to [`crate::ast::AtomKind::sexp_shape`] (the 6-of-12
8653    /// atomic-payload carving's embed) and
8654    /// [`crate::ast::QuoteForm::sexp_shape`] (the 4-of-12 quote-family
8655    /// carving's embed) — all three embeds land at ONE typed method
8656    /// per closed-set carving, symmetric across the partition.
8657    ///
8658    /// Bidirectional dual: the inverse projection
8659    /// [`SexpShape::as_structural_kind`] (12→2, partial) covers the
8660    /// 2-of-12 carving of [`SexpShape`] this embed reaches. The pair
8661    /// `(StructuralKind::sexp_shape, SexpShape::as_structural_kind)`
8662    /// forms an `Iso(StructuralKind, StructuralShape ⊂ SexpShape)`:
8663    /// every typed marker round-trips through the embed
8664    /// (`StructuralKind::sexp_shape(sk).as_structural_kind() ==
8665    /// Some(sk)` for every `sk: StructuralKind`), every structural-
8666    /// residual shape pre-image recovers the typed marker. The ten
8667    /// non-residual shapes (`Symbol`, `Keyword`, `String`, `Int`,
8668    /// `Float`, `Bool`, `Quote`, `Quasiquote`, `Unquote`,
8669    /// `UnquoteSplice`) form the kernel of the inverse —
8670    /// `as_structural_kind` returns `None` for them.
8671    ///
8672    /// Composition law (partition-total, jointly with the two sibling
8673    /// carvings): for every `shape: SexpShape`, EXACTLY ONE of
8674    /// `shape.as_atom_kind()`, `shape.as_quote_form()`,
8675    /// `shape.as_structural_kind()` is `Some(_)`. Pinned by the joint
8676    /// partition test in this module, so a regression that drifts any
8677    /// of the three carvings' partition-membership (an
8678    /// [`crate::ast::AtomKind::sexp_shape`] arm, a
8679    /// [`crate::ast::QuoteForm::sexp_shape`] arm, or this
8680    /// [`Self::sexp_shape`] arm) surfaces immediately.
8681    ///
8682    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
8683    /// (StructuralKind variant, SexpShape variant) pairing becomes a
8684    /// TYPE projection on the substrate algebra rather than a runtime
8685    /// `matches!(shape, SexpShape::Nil | SexpShape::List)` predicate.
8686    /// A typo or swap at the shape-projection site is no longer a
8687    /// runtime drift but a compile error against the typed projection.
8688    /// THEORY.md §II.1 invariant 2 — free middle; the (embed, project)
8689    /// pair binds at TWO typed sites — [`Self::sexp_shape`] for the
8690    /// embed, [`SexpShape::as_structural_kind`] for the project —
8691    /// completing the third and final closed-set carving of the
8692    /// outer-shape algebra. THEORY.md §VI.1 — generation over
8693    /// composition; the two residual arms lift to ONE typed method,
8694    /// and the partition-total invariant across the three carvings
8695    /// becomes a TYPED THEOREM rather than a test assertion.
8696    #[must_use]
8697    pub fn sexp_shape(self) -> SexpShape {
8698        match self {
8699            Self::Nil => Self::NIL_SHAPE,
8700            Self::List => Self::LIST_SHAPE,
8701        }
8702    }
8703
8704    /// Canonical [`SexpShape`] embed target for the [`Self::Nil`]
8705    /// structural-residual arm on the StructuralKind ⊂ SexpShape
8706    /// 2-of-12 carving — [`SexpShape::Nil`]. Per-role peer of
8707    /// `Self::Nil` on the closed-set structural-residual → outer-shape
8708    /// embed axis; consumers with a `StructuralKind` variant in hand
8709    /// at compile time bind the canonical embed target through ONE
8710    /// typed `pub const` per role rather than through runtime dispatch
8711    /// via [`Self::sexp_shape`] or by re-deriving the StructuralKind ⊂
8712    /// SexpShape variant pairing inline.
8713    ///
8714    /// Sibling posture to [`Self::NIL_LABEL`] (the per-role diagnostic
8715    /// label alias) and [`Self::NIL_HASH_DISCRIMINATOR`] (the per-role
8716    /// outer-Sexp cache-key byte) on the closed-set StructuralKind
8717    /// algebra — each closes a distinct per-role sub-vocabulary axis
8718    /// on the StructuralKind carving. This constant closes the THIRD
8719    /// per-role axis on [`StructuralKind`] (the `SexpShape`-embed
8720    /// axis, paired with the pre-existing `&'static str`
8721    /// diagnostic-label axis + the `u8` cache-key axis) at ONE typed
8722    /// alias through the peer superset variant on the [`SexpShape`]
8723    /// closed set.
8724    ///
8725    /// Sibling posture to the peer 6-of-12 atomic-payload carving's
8726    /// per-role SHAPE aliases
8727    /// ([`crate::ast::AtomKind::SYMBOL_SHAPE`] …
8728    /// [`crate::ast::AtomKind::BOOL_SHAPE`]) and the peer 4-of-12
8729    /// quote-family carving's per-role SHAPE aliases
8730    /// ([`crate::ast::QuoteForm::QUOTE_SHAPE`] …
8731    /// [`crate::ast::QuoteForm::UNQUOTE_SPLICE_SHAPE`]). Post-lift the
8732    /// SexpShape's per-carving embed-target axis is uniformly surfaced
8733    /// through per-role `pub const *_SHAPE` aliases on every
8734    /// sub-carving that carries a bidirectional (embed, project)
8735    /// `Iso(_, _ ⊂ SexpShape)` — `AtomKind` (6), `QuoteForm` (4), and
8736    /// now `StructuralKind` (2) — closing the third and final peer of
8737    /// the twelve-arm superset.
8738    pub const NIL_SHAPE: SexpShape = SexpShape::Nil;
8739
8740    /// Canonical [`SexpShape`] embed target for the [`Self::List`]
8741    /// structural-residual arm on the StructuralKind ⊂ SexpShape
8742    /// 2-of-12 carving — [`SexpShape::List`]. Per-role peer of
8743    /// `Self::List`. See [`Self::NIL_SHAPE`] for the alias-chain shape
8744    /// every sibling shares.
8745    pub const LIST_SHAPE: SexpShape = SexpShape::List;
8746
8747    /// Closed-set forced-arity ALL array over the canonical
8748    /// [`SexpShape`] embed targets on the StructuralKind ⊂ SexpShape
8749    /// 2-of-12 carving, in declaration order matching [`Self::ALL`]
8750    /// element-wise (pinned by
8751    /// `structural_kind_shapes_align_with_all_by_index`). Sibling
8752    /// posture to [`Self::LABELS`] (`[&'static str; 2]` — per-role
8753    /// diagnostic bytes) and [`Self::HASH_DISCRIMINATORS`] (`[u8; 2]`
8754    /// — per-role outer-Sexp cache-key bytes) on the SAME closed-set
8755    /// StructuralKind algebra; where those two arrays lift per-role
8756    /// `&'static str` and `u8` sub-vocabularies onto the substrate,
8757    /// this array lifts the per-role [`SexpShape`] embed-target
8758    /// sub-vocabulary at the same `[_; 2]` forced arity.
8759    ///
8760    /// Sibling posture to [`crate::ast::AtomKind::SHAPES`]
8761    /// (`[SexpShape; 6]`) and [`crate::ast::QuoteForm::SHAPES`]
8762    /// (`[SexpShape; 4]`) — the two peer carvings' family-wide
8763    /// embed-target arrays on the AtomKind ⊂ SexpShape 6-of-12 and
8764    /// QuoteForm ⊂ SexpShape 4-of-12 carvings. Together the THREE
8765    /// `SHAPES` arrays (6 + 4 + 2 = 12) cover EVERY sub-carving of
8766    /// [`SexpShape`] that carries a bidirectional `Iso(_, _ ⊂
8767    /// SexpShape)` at the same `[SexpShape; N]` forced-arity shape —
8768    /// a family-wide sweep zipping every carving's `ALL` + `SHAPES` in
8769    /// lockstep now closes over the FULL 12-arm outer-shape algebra at
8770    /// ONE typed pair-of-arrays per carving. This lift closes the
8771    /// THIRD and FINAL peer array — post-lift the substrate's every
8772    /// closed-set carving of [`SexpShape`] has its embed-target
8773    /// sub-vocabulary surfaced through a per-role `pub const` + ALL
8774    /// array uniformly.
8775    ///
8776    /// Pre-lift the two [`SexpShape`] embed targets had NO per-role
8777    /// primitive on this closed-set algebra — a consumer with a
8778    /// [`StructuralKind`] variant in hand at compile time reaching for
8779    /// the canonical embed target had to spell
8780    /// `StructuralKind::Nil.sexp_shape()` (runtime dispatch through
8781    /// the two-arm match body) OR re-derive the StructuralKind ⊂
8782    /// SexpShape variant pairing at the call site by importing both
8783    /// enums and spelling `SexpShape::Nil` inline. Post-lift the TWO
8784    /// canonical embed targets bind at ONE `pub const` per role on the
8785    /// typed [`StructuralKind`] algebra AND at [`Self::SHAPES`] as a
8786    /// family-wide forced-arity array — a future LSP / REPL completion
8787    /// bar keyed on `StructuralKind::SHAPES` for the "which SexpShape
8788    /// does this StructuralKind embed into?" outer-shape column, a
8789    /// `tatara-check` coverage sweep zipping `StructuralKind::ALL` /
8790    /// `LABELS` / `HASH_DISCRIMINATORS` / `SHAPES` in lockstep for a
8791    /// family-wide (variant, label, byte, embed-target) quadruple
8792    /// render, or a Sekiban audit-trail metric jointly labeled by the
8793    /// embed-target's SexpShape identity reads through the typed
8794    /// constants on this subset algebra without re-deriving the 2-of-12
8795    /// carving inline.
8796    ///
8797    /// Round-trip identity with the inverse projection
8798    /// [`SexpShape::as_structural_kind`]: for every index `i`,
8799    /// `Self::SHAPES[i].as_structural_kind() == Some(Self::ALL[i])`
8800    /// (pinned by
8801    /// `structural_kind_shapes_align_with_all_by_index_through_as_structural_kind`)
8802    /// — the embed / project section closes as a family-wide array-
8803    /// indexed law rather than as a per-variant assertion sweep.
8804    /// Adding a hypothetical third structural-residual variant (e.g.
8805    /// `Vector` for `#(...)` reader syntax, `Map` for `{...}`, or
8806    /// `Char` for `#\x` — each of which extends [`SexpShape::ALL`]
8807    /// AND joins THIS carving iff it lies outside BOTH the
8808    /// atomic-payload and quote-family carvings) extends [`Self::ALL`]
8809    /// AND [`Self::SHAPES`] AND [`SexpShape::ALL`] AND adds ONE
8810    /// per-role `pub const *_SHAPE` in lockstep — rustc's forced-arity
8811    /// check on the two `[_; N]` arrays fails compilation if EITHER
8812    /// ALL array grows without the other, AND the peer
8813    /// [`SexpShape::as_structural_kind`] arm must grow in lockstep to
8814    /// preserve the round-trip identity.
8815    ///
8816    /// Theory anchor: THEORY.md §III — the typescape; the two
8817    /// canonical [`SexpShape`] embed targets bind at ONE typed
8818    /// `[SexpShape; 2]` array on the closed-set StructuralKind algebra
8819    /// rather than at zero-primitive-on-this-subset-plus-two-inline-
8820    /// lookups scattered across the substrate. Closes the THIRD
8821    /// per-role `pub const` axis on the StructuralKind carving
8822    /// alongside the pre-existing LABELS + HASH_DISCRIMINATORS axes.
8823    /// THEORY.md §V.1 — knowable platform; the family's cardinality
8824    /// becomes a TYPE-level constant on the substrate algebra rather
8825    /// than a per-consumer runtime dispatch through the composition. A
8826    /// regression that drifts the (StructuralKind variant, SexpShape
8827    /// variant) pairing at ONE of the three typed sites (per-role
8828    /// alias, family-wide array, projection method) fails-loudly at
8829    /// the array-indexed sweep rather than as a silent operator-facing
8830    /// diagnostic drift. THEORY.md §II.1 invariant 2 — free middle;
8831    /// the (embed, project) pair binds at THREE typed sites now — the
8832    /// projection method [`Self::sexp_shape`], this family-wide array,
8833    /// AND the peer inverse [`SexpShape::as_structural_kind`] — with
8834    /// rustc-enforced consistency across all three. THEORY.md §VI.1
8835    /// — generation over composition; the family-wide contract sweeps
8836    /// (alignment with `ALL`, round-trip through
8837    /// `as_structural_kind`, membership through `sexp_shape`, pairwise
8838    /// injectivity across the two embed targets) emerge from the
8839    /// composition of TWO substrate primitives (this `pub const`
8840    /// array + the two per-role `pub const *_SHAPE` aliases) rather
8841    /// than as per-variant inline assertions duplicated at each call
8842    /// site.
8843    pub const SHAPES: [SexpShape; 2] = [Self::NIL_SHAPE, Self::LIST_SHAPE];
8844
8845    /// Stable, per-variant byte discriminator that paired with the
8846    /// residual outer-shape hash body builds the substrate's
8847    /// [`Hash for Sexp`](crate::ast::Sexp) projection at the two
8848    /// structural-residual arms — `0u8` for [`Self::Nil`], `2u8` for
8849    /// [`Self::List`]. The byte values are load-bearing because the
8850    /// macro-expansion cache ([`crate::macro_expand::Expander`]'s cache)
8851    /// keys on the hash of `(macro_name, args)`, and every `Sexp`
8852    /// participates in that hash — changing a discriminator silently
8853    /// invalidates every cached expansion across the substrate AND
8854    /// risks collision with the reserved bytes the other two
8855    /// closed-set carvings' arms use (`1u8` for the [`crate::ast::Sexp::Atom`]
8856    /// outer-carve marker, `3..=6` for the quote-family via
8857    /// [`crate::ast::QuoteForm::hash_discriminator`]).
8858    ///
8859    /// The closed set ensures the two arms partition `{0, 2}`
8860    /// injectively. Disjointness across the outer-Sexp discriminator
8861    /// space is structural rather than overlap-induced hash
8862    /// collision: [`crate::ast::Sexp::Atom(_)`]'s outer-carve byte
8863    /// (`1u8`, single-arm literal inside [`Hash for Sexp`](crate::ast::Sexp))
8864    /// AND [`crate::ast::QuoteForm::hash_discriminator`]'s
8865    /// `{3, 4, 5, 6}` partition sit around this carving's `{0, 2}`
8866    /// with a compile-time-checked prefix-uniqueness contract
8867    /// [`Hash for Sexp`](crate::ast::Sexp)'s outer match maintains
8868    /// through the exhaustive closed-set arm coverage. A future
8869    /// structural-residual, quote-family, or atomic-kind extension
8870    /// must extend both this method's arms AND the sibling
8871    /// discriminator methods in lockstep, with rustc binding the
8872    /// consistency through exhaustiveness over each closed enum.
8873    ///
8874    /// The SIBLING of the two prior-run discriminator lifts on the
8875    /// same outer-Sexp Hash body: [`crate::ast::AtomKind::hash_discriminator`]
8876    /// carries the six-variant atomic-payload carve's inner cache-key
8877    /// bytes at the nested `Atom::hash` level, and
8878    /// [`crate::ast::QuoteForm::hash_discriminator`] carries the four-
8879    /// variant quote-family carve's outer-Sexp cache-key bytes at the
8880    /// same `Hash for Sexp` body. Post-lift the SAME shape lands at
8881    /// the structural-residual axis — every carving now surfaces its
8882    /// hash-projection through ONE typed method rather than through
8883    /// per-arm inline byte literals at the [`Hash for Sexp`](crate::ast::Sexp)
8884    /// body. The three carvings' `hash_discriminator` methods form
8885    /// the joint bit-map onto the outer-Sexp cache-key space, and
8886    /// their partition-disjointness is the substrate's cache-key
8887    /// prefix-uniqueness contract.
8888    ///
8889    /// `pub(crate)` because the byte-discriminator surface is an
8890    /// implementation detail of the substrate's [`Hash for Sexp`](crate::ast::Sexp)
8891    /// cache-key contract; exposing it publicly would leak the
8892    /// cache-key shape through the API without enabling any external
8893    /// consumer the public projections ([`Self::label`],
8894    /// [`Self::sexp_shape`]) don't already serve. Same posture as
8895    /// [`crate::ast::AtomKind::hash_discriminator`] and
8896    /// [`crate::ast::QuoteForm::hash_discriminator`].
8897    ///
8898    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
8899    /// (StructuralKind variant, cache-key byte) pairing becomes a TYPE
8900    /// projection on the substrate algebra rather than two `0u8`/`2u8`
8901    /// inline literals in [`Hash for Sexp`](crate::ast::Sexp). A typo
8902    /// or swap at the hash-projection site is no longer a runtime
8903    /// cache-key drift but a compile error against the typed
8904    /// projection. THEORY.md §II.1 invariant 2 — free middle; ALL
8905    /// THREE closed-set carvings (atomic-payload via
8906    /// [`crate::ast::AtomKind::hash_discriminator`], quote-family via
8907    /// [`crate::ast::QuoteForm::hash_discriminator`], structural-
8908    /// residual via this method) now route through a typed
8909    /// closed-set match family, so a regression that drifts ONE
8910    /// carving's discriminator bytes from the others cannot reach
8911    /// the substrate's runtime cache. THEORY.md §VI.1 — generation
8912    /// over composition; the two `0u8`/`2u8` residual-arm literals
8913    /// appeared inline at [`Hash for Sexp`](crate::ast::Sexp) — past
8914    /// the ≥2 PRIME-DIRECTIVE trigger once the structural shape is
8915    /// named, and post-lift this method's two arms are the ONE
8916    /// canonical site the outer-Sexp Hash body composes through.
8917    /// Canonical `u8` cache-key byte for [`Self::Nil`]'s
8918    /// [`Self::hash_discriminator`] arm — `0`. Per-role peer of
8919    /// [`Self::Nil`] on the closed-set structural-residual cache-key-
8920    /// byte axis; consumers reach for
8921    /// `StructuralKind::NIL_HASH_DISCRIMINATOR` when the caller has a
8922    /// variant in hand at compile time and wants the canonical byte
8923    /// without runtime dispatch through [`Self::hash_discriminator`].
8924    /// The byte is load-bearing because the macro-expansion cache
8925    /// ([`crate::macro_expand::Expander`]'s cache) keys on
8926    /// [`Hash for Sexp`](crate::ast::Sexp), and any renumbering
8927    /// silently invalidates every cached expansion AND risks collision
8928    /// with the reserved bytes the other two closed-set carvings' arms
8929    /// use (`1u8` for the [`crate::ast::Sexp::Atom`] outer-carve
8930    /// marker, `3..=6` for the quote-family via
8931    /// [`crate::ast::QuoteForm::hash_discriminator`]).
8932    ///
8933    /// Sibling posture to
8934    /// [`crate::ast::AtomKind::SYMBOL_HASH_DISCRIMINATOR`] on the
8935    /// atomic-payload sub-carving of the SAME outer-`Sexp` Hash body
8936    /// AND to [`crate::ast::QuoteForm::QUOTE_HASH_DISCRIMINATOR`] on
8937    /// the quote-family sub-carving — the third and final closed-set
8938    /// carving of [`SexpShape`] now surfaces its per-role cache-key
8939    /// bytes at ONE `pub(crate) const` per variant PLUS a family-wide
8940    /// [`Self::HASH_DISCRIMINATORS`] array.
8941    ///
8942    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
8943    /// preserves proofs; the alias-chain composition law
8944    /// `StructuralKind::HASH_DISCRIMINATORS[i] ==
8945    /// StructuralKind::ALL[i].hash_discriminator()` binds the family-
8946    /// wide array to the projection method at rustc time, pinned by
8947    /// byte equality. THEORY.md §III — the typescape; the two
8948    /// canonical cache-key bytes bind at ONE `pub(crate) const` per
8949    /// role on the typed algebra rather than as inline `u8` literals
8950    /// in the [`Self::hash_discriminator`] match arms.
8951    pub(crate) const NIL_HASH_DISCRIMINATOR: u8 = 0;
8952
8953    /// Canonical `u8` cache-key byte for [`Self::List`]'s
8954    /// [`Self::hash_discriminator`] arm — `2`. Sibling of
8955    /// [`Self::NIL_HASH_DISCRIMINATOR`] on the closed-set per-role
8956    /// structural-residual cache-key-byte axis; see
8957    /// [`Self::NIL_HASH_DISCRIMINATOR`] for the algebra-level round-
8958    /// trip + disjointness contracts every sibling shares.
8959    ///
8960    /// The gap at `1u8` between [`Self::NIL_HASH_DISCRIMINATOR`] and
8961    /// this constant is intentional and load-bearing: it reserves the
8962    /// outer-carve byte [`crate::ast::Sexp::Atom(_)`] uses at the
8963    /// outer [`Hash for Sexp`](crate::ast::Sexp) body — the joint
8964    /// partition `{0, 1, 2, 3, 4, 5, 6}` binds bit-for-bit across the
8965    /// three closed-set carvings AND the atomic-carve outer marker.
8966    pub(crate) const LIST_HASH_DISCRIMINATOR: u8 = 2;
8967
8968    /// Closed-set forced-arity ALL array over the canonical
8969    /// structural-residual cache-key `u8` bytes, in declaration order
8970    /// matching [`Self::ALL`] element-wise (pinned by
8971    /// `structural_kind_hash_discriminators_align_with_all_by_index`).
8972    /// Sibling posture to [`Self::LABELS`] (`[&'static str; 2]` — the
8973    /// diagnostic-label `&'static str` axis on the SAME closed set),
8974    /// [`crate::ast::AtomKind::HASH_DISCRIMINATORS`] (`[u8; 6]` — the
8975    /// atomic-payload sub-carving's cache-key-byte peer at the nested
8976    /// [`crate::ast::Atom`] Hash level), and
8977    /// [`crate::ast::QuoteForm::HASH_DISCRIMINATORS`] (`[u8; 4]` —
8978    /// the quote-family sub-carving's cache-key-byte peer at the same
8979    /// outer [`Hash for Sexp`](crate::ast::Sexp) body). Every closed-
8980    /// set carving of [`SexpShape`] now pins its per-role canonical
8981    /// cache-key bytes at ONE `pub(crate) const` per role PLUS an ALL
8982    /// array for family-wide consumers.
8983    ///
8984    /// Pre-lift the two cache-key bytes had NO per-role primitive on
8985    /// this closed-set algebra — a consumer with a [`StructuralKind`]
8986    /// variant in hand at compile time reaching for the canonical byte
8987    /// had to spell `StructuralKind::Nil.hash_discriminator()`
8988    /// (runtime dispatch through the match arm) OR reach across into
8989    /// the inline `0u8` at the pre-lift match arm's [`Self::Nil`]
8990    /// branch and re-derive the (variant, byte) pairing at the call
8991    /// site. Post-lift the TWO canonical bytes bind at ONE
8992    /// `pub(crate) const` per role on the typed [`StructuralKind`]
8993    /// algebra AND at [`Self::HASH_DISCRIMINATORS`] as a family-wide
8994    /// forced-arity array — a future substrate-facing cache-key
8995    /// introspection tool (a `tatara-check` predicate `(check-outer-
8996    /// sexp-cache-key-partition-injective …)` that asserts the joint
8997    /// `{0, 1, 2, 3, 4, 5, 6}` partition structurally by sweeping the
8998    /// three carvings' `HASH_DISCRIMINATORS` arrays, a Sekiban audit-
8999    /// trail metric jointly labeled by the structural-residual cache-
9000    /// key partition, a future `TypedRewriter<StructuralKindOp>` sweep
9001    /// zipping ALL / LABELS / HASH_DISCRIMINATORS in lockstep for a
9002    /// family-wide (variant, label, byte) triple render) reads
9003    /// through the typed constants without re-deriving the two-arm
9004    /// carving inline.
9005    ///
9006    /// Each entry is byte-for-byte identical to the pre-lift inline
9007    /// `u8` literal at the corresponding [`Self::hash_discriminator`]
9008    /// arm — pinned by
9009    /// `structural_kind_hash_discriminators_pin_legacy_cache_key_bytes`
9010    /// so a regression that drifts ONE `pub(crate) const` from its
9011    /// pre-lift byte silently invalidates every cached expansion of
9012    /// a [`crate::ast::Sexp::Nil`] / [`crate::ast::Sexp::List`]
9013    /// participating in [`crate::macro_expand::Expander::cache`],
9014    /// fails-loudly at the alias test rather than at a silent cache
9015    /// mis-hash. Adding a hypothetical third structural-residual
9016    /// variant (e.g. `Vector` for `#(...)` reader syntax, `Map` for
9017    /// `{...}`) extends [`Self::ALL`] AND [`Self::HASH_DISCRIMINATORS`]
9018    /// AND adds ONE per-role `pub(crate) const` in lockstep — rustc's
9019    /// forced-arity check on the two `[_; N]` arrays fails
9020    /// compilation if EITHER array grows without the other, closing
9021    /// the extensibility gap that pre-lift silently allowed a
9022    /// discriminator collision on `7u8` (the next free byte on the
9023    /// outer [`Sexp`](crate::ast::Sexp) cache-key space) or an
9024    /// accidental re-use of the `1u8` outer-carve byte reserved for
9025    /// [`crate::ast::Sexp::Atom(_)`].
9026    ///
9027    /// Theory anchor: THEORY.md §III — the typescape; the two
9028    /// canonical cache-key bytes bind at ONE typed `[u8; 2]` array on
9029    /// the closed-set [`StructuralKind`] algebra rather than at zero-
9030    /// primitive-plus-two-inline-`u8`-literals scattered across the
9031    /// [`Self::hash_discriminator`] match arms. THEORY.md §V.1 —
9032    /// knowable platform; the family's cardinality becomes a TYPE-
9033    /// level constant on the substrate algebra rather than a per-
9034    /// consumer runtime dispatch through the match table. THEORY.md
9035    /// §V.3 — three-pillar attestation; the cache-key partition is
9036    /// the substrate's outer [`Sexp`](crate::ast::Sexp) `intent_hash`
9037    /// composition axis for every structural-residual arm — binding
9038    /// the two bytes on the typed algebra makes attestation-key drift
9039    /// a compile error rather than a silent BLAKE3 mis-hash.
9040    /// THEORY.md §VI.1 — generation over composition; the family-
9041    /// wide contract sweeps (alignment with `ALL`, pairwise
9042    /// disjointness, membership through [`Self::hash_discriminator`])
9043    /// emerge from the composition of TWO substrate primitives (this
9044    /// `pub(crate) const` array + the two per-role
9045    /// `pub(crate) const *_HASH_DISCRIMINATOR` aliases) rather than
9046    /// as per-variant inline assertions duplicated at each call site.
9047    ///
9048    /// The `#[allow(dead_code)]` posture matches
9049    /// [`crate::ast::AtomKind::HASH_DISCRIMINATORS`] and
9050    /// [`crate::ast::QuoteForm::HASH_DISCRIMINATORS`]: the substrate's
9051    /// current [`Hash for Sexp`](crate::ast::Sexp) body composes
9052    /// through the per-variant [`Self::hash_discriminator`] projection
9053    /// arm-by-arm rather than sweeping the family-wide array, so no
9054    /// non-test caller currently reaches this ALL array directly. The
9055    /// lift lands the substrate primitive so future consumers keyed
9056    /// on the whole family (a future
9057    /// [`crate::macro_expand::Expander`] cache-warmup pass that
9058    /// hashes the structural-residual byte-set upfront, a future
9059    /// `tatara-check` predicate `(check-outer-sexp-cache-key-
9060    /// partition-injective …)` that verifies the joint
9061    /// `{0, 1, 2, 3, 4, 5, 6}` partition structurally, a future
9062    /// `TypedRewriter<StructuralKindOp>` sweep zipping ALL / LABELS /
9063    /// HASH_DISCRIMINATORS in lockstep for a family-wide (variant,
9064    /// label, byte) triple render) bind to ONE `[u8; 2]` primitive
9065    /// rather than re-deriving the array inline per callsite.
9066    #[allow(dead_code)]
9067    pub(crate) const HASH_DISCRIMINATORS: [u8; 2] =
9068        [Self::NIL_HASH_DISCRIMINATOR, Self::LIST_HASH_DISCRIMINATOR];
9069
9070    #[must_use]
9071    pub(crate) fn hash_discriminator(self) -> u8 {
9072        match self {
9073            Self::Nil => Self::NIL_HASH_DISCRIMINATOR,
9074            Self::List => Self::LIST_HASH_DISCRIMINATOR,
9075        }
9076    }
9077}
9078
9079// `impl std::fmt::Display for StructuralKind` + `impl std::str::FromStr
9080// for StructuralKind` + `impl tatara_closed_set::ClosedSet for StructuralKind` +
9081// `pub struct UnknownStructuralKind(pub String)` are generated by
9082// `#[derive(tatara_closed_set::DeriveClosedSet)]` on the enum declaration
9083// above. `label` delegates to the inherent `StructuralKind::label` via
9084// `#[closed_set(via = "label")]` — the inherent name coincides with
9085// the trait method name here; the delegation stays explicit so the
9086// SAME wiring shape applies whether the inherent projection is `label`
9087// / `prefix` / `marker` / `keyword` / `as_str`. The `display` flag
9088// emits the substrate-wide `f.write_str(Self::label(*self))` block.
9089// `#[closed_set(generate_unknown = "structural kind")]` emits the typed
9090// parse-rejection carrier with the substrate-wide `Debug + Clone +
9091// PartialEq + Eq + thiserror::Error` derives and the `#[error("unknown
9092// structural kind: {0}")]` annotation byte-for-byte; the explicit label
9093// matches the auto-derived `pascal_to_spaced_lowercase("StructuralKind")`
9094// projection byte-for-byte but pins the pre-lift wording. Round-trip +
9095// cross-axis rejection (the label vocabulary partially overlaps
9096// [`SexpShape`]'s labels on the two residual entries `"nil"` and
9097// `"list"`; the disjointness contract is that a `FromStr` on THIS
9098// closed set MUST reject the ten non-residual `SexpShape` labels since
9099// they lie outside the carving) pinned by
9100// `structural_kind_label_round_trips_through_from_str` +
9101// `structural_kind_from_str_rejects_non_structural_sexp_shape_labels`.
9102
9103/// Typed witness of an offending `Sexp` at a typed-entry rejection
9104/// boundary — the joint identity (shape + literal) the substrate's
9105/// diagnostic surface owes the operator. Pairs the closed-set
9106/// `SexpShape` projection (the twelve reachable Sexp outermost shapes
9107/// the reader can produce) with the `Sexp::Display` projection (the
9108/// literal value the operator wrote: `5`, `:foo`, `(list 1 2)`,
9109/// `notify-ref`, etc.).
9110///
9111/// Mirror at the offending-value boundary of the prior-run
9112/// `SexpShape` (typed-shape closed set), `ExpectedKwargShape`
9113/// (expected-shape closed set), `KwargPath` (kwargs-path shapes),
9114/// `MacroDefHead` (macro-definition-head closed set), `UnquoteForm`
9115/// (template-marker syntactic forms), `CompilerSpecIoStage`
9116/// (disk-persistence surface), and `TemplateInvariantKind`
9117/// (bytecode-runtime surface) closed-set lifts: those enums key
9118/// rejection variants on a typed identity carried inside the
9119/// variant's data shape; `SexpWitness` keys the OFFENDING-VALUE side
9120/// (the `got: String` Sexp::Display slots on `NonSymbolUnquoteTarget`,
9121/// `SpliceOutsideList`, `NonSymbolParam`, `RestParamMissingName`,
9122/// `DefmacroNonSymbolName`, `DefmacroNonListParams`,
9123/// `MissingHeadSymbol`, and any future variant taking a `&Sexp` at
9124/// the helper boundary) on a typed joint identity so authoring tools
9125/// (REPL, LSP, `tatara-check`) bind to BOTH `witness.shape` (the
9126/// structural identity — pattern-matchable on `SexpShape::List` etc.)
9127/// AND `witness.display` (the literal value — renderable verbatim)
9128/// without losing either side.
9129///
9130/// Before this struct landed, the six error-builder helpers in
9131/// `macro_expand.rs` (`non_symbol_unquote_target`, `splice_outside_list`,
9132/// `non_symbol_param`, `rest_param_missing_name`,
9133/// `defmacro_non_symbol_name`, `defmacro_non_list_params`) and one
9134/// in `domain.rs` (`missing_head_err`'s caller) each projected `&Sexp
9135/// → String` via `Sexp::to_string()` at the boundary — the structural
9136/// `SexpShape` was lost. After this primitive lands, every offending-
9137/// value variant slot that takes a `SexpWitness` carries the typed
9138/// shape AND the literal jointly in ONE owned value the variant lives
9139/// independent of the call frame on.
9140///
9141/// The byte-for-byte rendering contract is preserved: `Display` for
9142/// `SexpWitness` writes only the `display` field, so a variant whose
9143/// `#[error(...)]` annotation projects through `{got}` renders
9144/// byte-identically to the legacy `got: String` shape — every
9145/// downstream substring-grep consumer (`tatara-check`, REPL) passes
9146/// unchanged. The gain is structural: tools that pattern-match on
9147/// `witness.shape == SexpShape::List` now bind to the typed identity
9148/// directly instead of substring-parsing the rendered literal.
9149///
9150/// `Clone + Debug + PartialEq + Eq` are retained (same posture as
9151/// every other owned-data `LispError` field); `Copy` is dropped
9152/// because the `display: String` is not `Copy`. When a future run
9153/// gives `Sexp` source spans, `pos: Option<usize>` lands here in ONE
9154/// place and every offending-value site picks up positional rendering
9155/// with no per-variant edit — the same future-proofing posture
9156/// `KwargPath`, `SexpShape`, and `ExpectedKwargShape` already carry.
9157///
9158/// Theory anchor: THEORY.md §V.1 — knowable platform; the offending-
9159/// value's joint identity (structural shape + renderable literal)
9160/// becomes a TYPE rather than a `String` projection at the helper
9161/// boundary that discards the shape. After this primitive lands the
9162/// substrate's understanding of "the offending Sexp at a typed-entry
9163/// rejection" lives in ONE typed struct the diagnostic promotions
9164/// hang off of. THEORY.md §VI.1 — generation over composition; seven
9165/// inline `got.to_string()` projections at error-builder boundaries
9166/// (six in `macro_expand.rs`, one in `domain.rs::missing_head_err`'s
9167/// caller) is past the three-times-rule trigger. THEORY.md §II.1
9168/// invariant 1 — typed entry; the offending Sexp's identity is part
9169/// of the proof of WHAT the typed-entry gate rejected, and the typed
9170/// witness makes both halves of that identity (shape + literal)
9171/// load-bearing data on the variant rather than the literal-only
9172/// `String` projection the legacy shape carried.
9173#[derive(Debug, Clone, PartialEq, Eq)]
9174pub struct SexpWitness {
9175    /// Structural identity — the typed-shape projection (`SexpShape::Int`,
9176    /// `SexpShape::List`, etc.). Pattern-matchable; future `pos: Option<usize>`
9177    /// promotions land alongside this field once `Sexp` carries spans.
9178    pub shape: SexpShape,
9179    /// Renderable identity — the `Sexp::Display` projection (`"5"`,
9180    /// `"(list 1 2)"`, `":foo"`, etc.). Owned so the witness lives
9181    /// independent of the call frame and crosses thread boundaries
9182    /// cleanly. Feeds the `#[error(...)]` annotation's `{got}` slot
9183    /// via `SexpWitness`'s `Display` impl.
9184    pub display: String,
9185}
9186
9187impl SexpWitness {
9188    /// Owned constructor — pairs a typed `SexpShape` with an owned
9189    /// `String` projection of the offending `Sexp::Display`. Used by
9190    /// the `sexp_witness(&Sexp)` projection helper in `domain.rs`;
9191    /// hand-written `TataraDomain` impls that need to construct a
9192    /// witness at their own call boundary route through this
9193    /// constructor.
9194    #[must_use]
9195    pub fn new(shape: SexpShape, display: impl Into<String>) -> Self {
9196        Self {
9197            shape,
9198            display: display.into(),
9199        }
9200    }
9201}
9202
9203impl std::fmt::Display for SexpWitness {
9204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
9205        // Render the literal only — the byte-for-byte legacy rendering
9206        // of every `got: String` variant slot that projected through
9207        // `Sexp::Display`. Authoring tools that substring-grep on the
9208        // rendered diagnostic see no drift; tools that pattern-match
9209        // on the variant's `SexpWitness`-shaped `got` slot bind to
9210        // `witness.shape` directly.
9211        f.write_str(&self.display)
9212    }
9213}
9214
9215fn unbound_hint_suffix(prefix: UnquoteForm, hint: Option<&str>) -> String {
9216    match hint {
9217        Some(h) => format!("; did you mean {}{h}?", prefix.marker()),
9218        None => String::new(),
9219    }
9220}
9221
9222/// Renders the bare parenthetical suffix shared by EVERY `*_suffix`
9223/// diagnostic helper: a single leading space, an open paren, the
9224/// already-formatted `body`, and a close paren — ` ({body})`.
9225///
9226/// This is the lowest layer of the suffix-wrapping algebra. Four helpers
9227/// append a parenthetical structural-detail clause to a `#[error(...)]`
9228/// prefix and ALL FOUR share this exact skeleton — the leading space and the
9229/// parens:
9230///   * `unknown_among_suffix` wraps a `did you mean …?; …` / bare candidate
9231///     clause (the kwarg + registry gates);
9232///   * `rest_param_missing_name_suffix` wraps the `rest marker at position …`
9233///     clause;
9234///   * `missing_head_symbol_suffix` wraps the `got …` / `empty list` clause.
9235///
9236/// Owning the leading-space-and-parens HERE means it cannot drift across the
9237/// four renderers: a regression that drops the leading space at one site,
9238/// moves a paren, or doubles a space is structurally impossible because there
9239/// is exactly ONE wrapping implementation. Each helper keeps ONLY its own
9240/// body-construction and binds it to this primitive.
9241fn paren_suffix(body: &str) -> String {
9242    format!(" ({body})")
9243}
9244
9245/// Renders the parenthetical "unknown X among a known set" suffix shared by
9246/// the kwarg gate (`UnknownKwarg`) and the registry gate
9247/// (`UnknownDomainKeyword`). `hint` is the already-formatted near-miss
9248/// suggestion (`:foo` for kwargs, `(foo ...)` for registry keywords); `body`
9249/// is the already-formatted candidate clause (`allowed: :a, :b` /
9250/// `registered: x, y` / `no domains registered`).
9251///
9252/// This layer owns ONLY the `did you mean {hint}?; ` join when a hint is
9253/// present, so the two gates whose docs declare they "share ONE structural
9254/// shape" cannot drift apart in that join. The bare-parenthetical wrapping —
9255/// the leading space and the parens — is delegated to `paren_suffix`, the one
9256/// skeleton every `*_suffix` helper binds to.
9257fn unknown_among_suffix(hint: Option<&str>, body: &str) -> String {
9258    match hint {
9259        Some(h) => paren_suffix(&format!("did you mean {h}?; {body}")),
9260        None => paren_suffix(body),
9261    }
9262}
9263
9264fn unknown_kwarg_suffix(hint: Option<&str>, allowed: &[String]) -> String {
9265    let allowed_list = allowed
9266        .iter()
9267        .map(|s| format!(":{s}"))
9268        .collect::<Vec<_>>()
9269        .join(", ");
9270    unknown_among_suffix(
9271        hint.map(|h| format!(":{h}")).as_deref(),
9272        &format!("allowed: {allowed_list}"),
9273    )
9274}
9275
9276fn rest_param_missing_name_suffix(rest_position: usize, got: Option<&str>) -> String {
9277    let body = match got {
9278        Some(g) => format!("rest marker at position {rest_position}, got {g}"),
9279        None => format!("rest marker at position {rest_position}, none provided"),
9280    };
9281    paren_suffix(&body)
9282}
9283
9284fn rest_param_trailing_tokens_suffix(rest_position: usize, extra: usize, first: &str) -> String {
9285    paren_suffix(&format!(
9286        "rest marker at position {rest_position}, {extra} trailing after name, first: {first}"
9287    ))
9288}
9289
9290fn optional_marker_repeated_suffix(first_position: usize, second_position: usize) -> String {
9291    paren_suffix(&format!(
9292        "first &optional at position {first_position}, second at position {second_position}"
9293    ))
9294}
9295
9296fn optional_param_malformed_suffix(
9297    position: usize,
9298    reason: OptionalParamMalformedReason,
9299) -> String {
9300    paren_suffix(&format!("position {position}, {}", reason.label()))
9301}
9302
9303fn missing_head_symbol_suffix(got: Option<&str>) -> String {
9304    let body = match got {
9305        Some(g) => format!("got {g}"),
9306        None => "empty list".to_string(),
9307    };
9308    paren_suffix(&body)
9309}
9310
9311fn unknown_domain_keyword_suffix(hint: Option<&str>, registered: &[String]) -> String {
9312    let body = if registered.is_empty() {
9313        "no domains registered".to_string()
9314    } else {
9315        format!("registered: {}", registered.join(", "))
9316    };
9317    unknown_among_suffix(hint.map(|h| format!("({h} ...)")).as_deref(), &body)
9318}
9319
9320impl LispError {
9321    /// Byte offset of the failure into the source, when locatable.
9322    ///
9323    /// A PROJECTION of [`LispError::span`] — `span().map(|s| s.start)` and
9324    /// nothing else. Pre-lift this carried its own copy of the exhaustive
9325    /// positional/non-positional classification, so a new variant had to
9326    /// be classified TWICE and the two matches were free to disagree about
9327    /// whether an error is locatable. Post-lift `span` is the single
9328    /// classification site; this is the point-shaped view of it, kept
9329    /// because `format_diagnostic`'s `--> label:line:col` header names one
9330    /// byte, not a range.
9331    #[must_use]
9332    pub fn position(&self) -> Option<usize> {
9333        self.span().map(|span| span.start)
9334    }
9335
9336    /// Source `Span` of the failure, when locatable.
9337    ///
9338    /// THE classification site: a variant is locatable iff it appears in
9339    /// the `Some(..)` arms below. Variants without a location (`Type`,
9340    /// `Compile`, …) return `None`, so callers render a snippet only when
9341    /// the substrate has the information to do so; adding a variant
9342    /// without classifying it here is a non-exhaustive-match build
9343    /// failure, not a silently-positionless error.
9344    ///
9345    /// The range — not just the start — is what lets
9346    /// `crate::diagnostic::format_diagnostic` underline the offending
9347    /// FORM rather than its first byte. A zero-width span is legal and
9348    /// means "point here" (see `Eof`); `caret_run` clamps it up to one
9349    /// caret.
9350    #[must_use]
9351    pub fn span(&self) -> Option<Span> {
9352        match self {
9353            // `UnexpectedChar` predates the span lift and is not
9354            // constructed anywhere in the workspace today (measured: the
9355            // declaration, this arm, and one unit pin). Its span is
9356            // nonetheless exact rather than invented — a `char` occupies
9357            // `len_utf8()` bytes from the offset it was found at.
9358            Self::UnexpectedChar(c, pos) => Some(Span::new(*pos, pos + c.len_utf8())),
9359            Self::UnterminatedString(span) => Some(*span),
9360            Self::UnmatchedParen { span }
9361            | Self::UnmatchedOpenParen { span }
9362            | Self::Eof { span } => Some(*span),
9363            Self::InvalidNumber(_)
9364            | Self::UnknownSymbol(_)
9365            | Self::Type { .. }
9366            | Self::Compile { .. }
9367            | Self::TypeMismatch { .. }
9368            | Self::HeadMismatch { .. }
9369            | Self::Unknown { .. }
9370            | Self::Missing(_)
9371            | Self::OddKwargs { .. }
9372            | Self::UnboundTemplateVar { .. }
9373            | Self::DuplicateKwarg { .. }
9374            | Self::MissingKwarg { .. }
9375            | Self::UnknownKwarg { .. }
9376            | Self::UnknownDomainKeyword { .. }
9377            | Self::NonSymbolUnquoteTarget { .. }
9378            | Self::SpliceOutsideList { .. }
9379            | Self::MissingMacroArg { .. }
9380            | Self::TooManyMacroArgs { .. }
9381            | Self::NonSymbolParam { .. }
9382            | Self::RestParamMissingName { .. }
9383            | Self::RestParamTrailingTokens { .. }
9384            | Self::OptionalMarkerRepeated { .. }
9385            | Self::OptionalParamMalformed { .. }
9386            | Self::DefmacroArity { .. }
9387            | Self::DefmacroNonSymbolName { .. }
9388            | Self::DefmacroNonListParams { .. }
9389            | Self::NotAListForm { .. }
9390            | Self::MissingHeadSymbol { .. }
9391            | Self::NamedFormMissingName { .. }
9392            | Self::NamedFormNonSymbolName { .. }
9393            | Self::RewriterNonList { .. }
9394            | Self::DomainSerialize { .. }
9395            | Self::KwargDeserialize { .. }
9396            | Self::CompilerSpecIo { .. }
9397            | Self::TemplateInvariant { .. }
9398            | Self::ExpansionDepthExceeded { .. }
9399            | Self::ExpansionSizeExceeded { .. }
9400            | Self::MacroBodySizeExceeded { .. }
9401            | Self::RegisteredMacrosExceeded { .. }
9402            | Self::MacroArityExceeded { .. } => None,
9403        }
9404    }
9405}
9406
9407#[cfg(test)]
9408mod tests {
9409    use super::{
9410        missing_head_symbol_suffix, optional_marker_repeated_suffix,
9411        optional_param_malformed_suffix, paren_suffix, rest_param_missing_name_suffix,
9412        rest_param_trailing_tokens_suffix, unknown_among_suffix, unknown_domain_keyword_suffix,
9413        unknown_kwarg_suffix, CompilerSpecIoStage, ExpectedKwargShape, KwargPath, KwargPathKind,
9414        LispError, MacroDefHead, OptionalParamMalformedReason, SexpShape, SexpWitness,
9415        StructuralKind, TemplateInvariantKind, UnknownExpectedKwargShape, UnknownKwargPathKind,
9416        UnknownMacroDefHead, UnknownSexpShape, UnknownUnquoteForm, UnquoteForm,
9417    };
9418    use crate::span::Span;
9419
9420    #[test]
9421    fn position_extracts_offset_from_positional_variants() {
9422        assert_eq!(LispError::UnexpectedChar('?', 7).position(), Some(7));
9423        assert_eq!(
9424            LispError::UnterminatedString(Span::new(11, 15)).position(),
9425            Some(11)
9426        );
9427        assert_eq!(
9428            LispError::UnmatchedParen {
9429                span: Span::new(3, 4)
9430            }
9431            .position(),
9432            Some(3)
9433        );
9434        assert_eq!(
9435            LispError::UnmatchedOpenParen {
9436                span: Span::new(0, 6)
9437            }
9438            .position(),
9439            Some(0)
9440        );
9441        assert_eq!(
9442            LispError::Eof {
9443                span: Span::new(42, 42)
9444            }
9445            .position(),
9446            Some(42)
9447        );
9448    }
9449
9450    #[test]
9451    fn span_carries_the_full_range_position_projects_the_start_of() {
9452        // `position()` is now `span().map(|s| s.start)` — one
9453        // classification, two views. Pin BOTH views on the same values so
9454        // a regression that re-splits them (a variant locatable through
9455        // one accessor but not the other) fails here rather than showing
9456        // up as a diagnostic that renders a header without a caret.
9457        let cases = [
9458            (
9459                LispError::UnterminatedString(Span::new(11, 15)),
9460                Span::new(11, 15),
9461            ),
9462            (
9463                LispError::UnmatchedParen {
9464                    span: Span::new(3, 4),
9465                },
9466                Span::new(3, 4),
9467            ),
9468            (
9469                LispError::UnmatchedOpenParen {
9470                    span: Span::new(0, 6),
9471                },
9472                Span::new(0, 6),
9473            ),
9474            (
9475                LispError::Eof {
9476                    span: Span::new(42, 42),
9477                },
9478                Span::new(42, 42),
9479            ),
9480            // `UnexpectedChar` still stores a bare offset; its span is
9481            // derived from the char's own UTF-8 width, so a multi-byte
9482            // char reports the bytes it actually occupies.
9483            (LispError::UnexpectedChar('?', 7), Span::new(7, 8)),
9484            (LispError::UnexpectedChar('é', 7), Span::new(7, 9)),
9485        ];
9486        for (err, expected) in cases {
9487            assert_eq!(err.span(), Some(expected), "span mismatch for {err:?}");
9488            assert_eq!(
9489                err.position(),
9490                Some(expected.start),
9491                "position must project span.start for {err:?}"
9492            );
9493        }
9494    }
9495
9496    #[test]
9497    fn span_is_none_exactly_where_position_is_none() {
9498        // The other half of the one-classification invariant: a
9499        // non-locatable error is non-locatable through both views.
9500        let err = LispError::Compile {
9501            form: ":threshold".into(),
9502            message: "expected number".into(),
9503        };
9504        assert_eq!(err.span(), None);
9505        assert_eq!(err.position(), None);
9506    }
9507
9508    #[test]
9509    fn position_is_none_for_non_positional_variants() {
9510        assert_eq!(
9511            LispError::OddKwargs {
9512                dangling: ":query".into()
9513            }
9514            .position(),
9515            None
9516        );
9517        assert_eq!(LispError::Missing("name").position(), None);
9518        assert_eq!(LispError::InvalidNumber("nan".into()).position(), None);
9519        assert_eq!(LispError::UnknownSymbol("foo".into()).position(), None);
9520        assert_eq!(
9521            LispError::Type {
9522                expected: "int",
9523                got: "string".into()
9524            }
9525            .position(),
9526            None
9527        );
9528        assert_eq!(
9529            LispError::Compile {
9530                form: ":x".into(),
9531                message: "bad".into()
9532            }
9533            .position(),
9534            None
9535        );
9536        assert_eq!(
9537            LispError::TypeMismatch {
9538                form: KwargPath::named("x"),
9539                expected: ExpectedKwargShape::String,
9540                got: SexpShape::Int,
9541            }
9542            .position(),
9543            None
9544        );
9545        assert_eq!(
9546            LispError::HeadMismatch {
9547                keyword: "defmonitor",
9548                got: "not-a-monitor".into(),
9549            }
9550            .position(),
9551            None
9552        );
9553        assert_eq!(
9554            LispError::Unknown {
9555                category: "domain",
9556                value: "defx".into()
9557            }
9558            .position(),
9559            None
9560        );
9561        assert_eq!(
9562            LispError::UnboundTemplateVar {
9563                prefix: UnquoteForm::Unquote,
9564                name: "xx".into(),
9565                hint: Some("x".into()),
9566            }
9567            .position(),
9568            None
9569        );
9570        assert_eq!(
9571            LispError::UnboundTemplateVar {
9572                prefix: UnquoteForm::Splice,
9573                name: "ys".into(),
9574                hint: None,
9575            }
9576            .position(),
9577            None
9578        );
9579        assert_eq!(
9580            LispError::DuplicateKwarg { key: "name".into() }.position(),
9581            None
9582        );
9583        assert_eq!(
9584            LispError::MissingKwarg { key: "name".into() }.position(),
9585            None
9586        );
9587        assert_eq!(
9588            LispError::UnknownKwarg {
9589                key: "tthreshold".into(),
9590                hint: Some("threshold".into()),
9591                allowed: vec!["name".into(), "threshold".into()],
9592            }
9593            .position(),
9594            None
9595        );
9596        assert_eq!(
9597            LispError::UnknownDomainKeyword {
9598                keyword: "defmoniter".into(),
9599                hint: Some("defmonitor".into()),
9600                registered: vec!["defalertpolicy".into(), "defmonitor".into()],
9601            }
9602            .position(),
9603            None
9604        );
9605        assert_eq!(
9606            LispError::NonSymbolUnquoteTarget {
9607                prefix: UnquoteForm::Unquote,
9608                got: SexpWitness::new(SexpShape::List, "(list 1 2)"),
9609            }
9610            .position(),
9611            None
9612        );
9613        assert_eq!(
9614            LispError::NonSymbolUnquoteTarget {
9615                prefix: UnquoteForm::Splice,
9616                got: SexpWitness::new(SexpShape::Int, "5"),
9617            }
9618            .position(),
9619            None
9620        );
9621        assert_eq!(
9622            LispError::SpliceOutsideList {
9623                got: SexpWitness::new(SexpShape::Symbol, "xs"),
9624            }
9625            .position(),
9626            None
9627        );
9628        assert_eq!(
9629            LispError::SpliceOutsideList {
9630                got: SexpWitness::new(SexpShape::List, "(list 1 2)"),
9631            }
9632            .position(),
9633            None
9634        );
9635        assert_eq!(
9636            LispError::MissingMacroArg {
9637                macro_name: "wrap".into(),
9638                param: "b".into(),
9639            }
9640            .position(),
9641            None
9642        );
9643        assert_eq!(
9644            LispError::MissingMacroArg {
9645                macro_name: "call".into(),
9646                param: "f".into(),
9647            }
9648            .position(),
9649            None
9650        );
9651        assert_eq!(
9652            LispError::TooManyMacroArgs {
9653                macro_name: "pair".into(),
9654                expected: 2,
9655                got: 3,
9656            }
9657            .position(),
9658            None
9659        );
9660        assert_eq!(
9661            LispError::TooManyMacroArgs {
9662                macro_name: "wrap".into(),
9663                expected: 1,
9664                got: 5,
9665            }
9666            .position(),
9667            None
9668        );
9669        assert_eq!(
9670            LispError::NonSymbolParam {
9671                position: 0,
9672                got: SexpWitness::new(SexpShape::Int, "5"),
9673            }
9674            .position(),
9675            None
9676        );
9677        assert_eq!(
9678            LispError::NonSymbolParam {
9679                position: 2,
9680                got: SexpWitness::new(SexpShape::List, "(nested)"),
9681            }
9682            .position(),
9683            None
9684        );
9685        assert_eq!(
9686            LispError::RestParamMissingName {
9687                rest_position: 1,
9688                got: None,
9689            }
9690            .position(),
9691            None
9692        );
9693        assert_eq!(
9694            LispError::RestParamMissingName {
9695                rest_position: 0,
9696                got: Some(SexpWitness::new(SexpShape::Int, "5")),
9697            }
9698            .position(),
9699            None
9700        );
9701        assert_eq!(
9702            LispError::OptionalParamMalformed {
9703                position: 1,
9704                got: SexpWitness::new(SexpShape::List, "()"),
9705                reason: OptionalParamMalformedReason::EmptyList,
9706            }
9707            .position(),
9708            None
9709        );
9710        assert_eq!(
9711            LispError::OptionalParamMalformed {
9712                position: 3,
9713                got: SexpWitness::new(SexpShape::List, "(x 1 2)"),
9714                reason: OptionalParamMalformedReason::ExtraElements { length: 3 },
9715            }
9716            .position(),
9717            None
9718        );
9719        assert_eq!(
9720            LispError::DefmacroArity {
9721                head: MacroDefHead::Defmacro,
9722                arity: 1,
9723            }
9724            .position(),
9725            None
9726        );
9727        assert_eq!(
9728            LispError::DefmacroArity {
9729                head: MacroDefHead::Defcheck,
9730                arity: 3,
9731            }
9732            .position(),
9733            None
9734        );
9735        assert_eq!(
9736            LispError::DefmacroNonSymbolName {
9737                head: MacroDefHead::Defmacro,
9738                got: SexpWitness::new(SexpShape::Int, "5"),
9739            }
9740            .position(),
9741            None
9742        );
9743        assert_eq!(
9744            LispError::DefmacroNonListParams {
9745                head: MacroDefHead::Defmacro,
9746                got: SexpWitness::new(SexpShape::Symbol, "x"),
9747            }
9748            .position(),
9749            None
9750        );
9751        assert_eq!(
9752            LispError::DefmacroNonListParams {
9753                head: MacroDefHead::Defcheck,
9754                got: SexpWitness::new(SexpShape::Keyword, ":foo"),
9755            }
9756            .position(),
9757            None
9758        );
9759        assert_eq!(
9760            LispError::DefmacroNonSymbolName {
9761                head: MacroDefHead::DefpointTemplate,
9762                got: SexpWitness::new(SexpShape::Keyword, ":foo"),
9763            }
9764            .position(),
9765            None
9766        );
9767        assert_eq!(
9768            LispError::NotAListForm {
9769                keyword: "defmonitor",
9770            }
9771            .position(),
9772            None
9773        );
9774        assert_eq!(
9775            LispError::NotAListForm {
9776                keyword: "defpoint",
9777            }
9778            .position(),
9779            None
9780        );
9781        assert_eq!(
9782            LispError::MissingHeadSymbol {
9783                keyword: "defmonitor",
9784                got: None,
9785            }
9786            .position(),
9787            None
9788        );
9789        assert_eq!(
9790            LispError::MissingHeadSymbol {
9791                keyword: "defpoint",
9792                got: Some(SexpWitness::new(SexpShape::Int, "5")),
9793            }
9794            .position(),
9795            None
9796        );
9797        assert_eq!(
9798            LispError::NamedFormMissingName {
9799                keyword: "defpoint",
9800            }
9801            .position(),
9802            None
9803        );
9804        assert_eq!(
9805            LispError::NamedFormMissingName {
9806                keyword: "defalertpolicy",
9807            }
9808            .position(),
9809            None
9810        );
9811        assert_eq!(
9812            LispError::NamedFormNonSymbolName {
9813                keyword: "defpoint",
9814                got: SexpShape::Int,
9815            }
9816            .position(),
9817            None
9818        );
9819        assert_eq!(
9820            LispError::NamedFormNonSymbolName {
9821                keyword: "defalertpolicy",
9822                got: SexpShape::List,
9823            }
9824            .position(),
9825            None
9826        );
9827        assert_eq!(
9828            LispError::RewriterNonList {
9829                keyword: "defmonitor",
9830                got: SexpWitness::new(SexpShape::Int, "42"),
9831            }
9832            .position(),
9833            None
9834        );
9835        assert_eq!(
9836            LispError::DomainSerialize {
9837                keyword: "defmonitor",
9838                message: "expected value at line 1 column 1".into(),
9839            }
9840            .position(),
9841            None
9842        );
9843        assert_eq!(
9844            LispError::DomainSerialize {
9845                keyword: "defalertpolicy",
9846                message: "key must be a string".into(),
9847            }
9848            .position(),
9849            None
9850        );
9851        assert_eq!(
9852            LispError::KwargDeserialize {
9853                path: KwargPath::named("level"),
9854                message: "unknown variant `NotASeverity`".into(),
9855            }
9856            .position(),
9857            None
9858        );
9859        assert_eq!(
9860            LispError::KwargDeserialize {
9861                path: KwargPath::item("steps", 1),
9862                message: "invalid type: integer `7`, expected a string".into(),
9863            }
9864            .position(),
9865            None
9866        );
9867        assert_eq!(
9868            LispError::CompilerSpecIo {
9869                stage: super::CompilerSpecIoStage::RealizeToDiskSerialize,
9870                message: "expected struct CompilerSpec".into(),
9871            }
9872            .position(),
9873            None
9874        );
9875        assert_eq!(
9876            LispError::CompilerSpecIo {
9877                stage: super::CompilerSpecIoStage::LoadFromDiskDeserialize,
9878                message: "expected value at line 1 column 1".into(),
9879            }
9880            .position(),
9881            None
9882        );
9883    }
9884
9885    #[test]
9886    fn missing_head_symbol_display_with_empty_list_renders_legacy_prefix_and_empty_marker() {
9887        // `()` — list[0] doesn't exist. The variant renders the
9888        // legacy prefix `compile error in {keyword}: missing head
9889        // symbol` byte-for-byte AND names the structural reason
9890        // `(empty list)` parenthetically — same posture as how
9891        // `RestParamMissingName` renders `(rest marker at position
9892        // N, none provided)` and how `UnknownDomainKeyword` renders
9893        // `(no domains registered)` for the empty-side case. A
9894        // regression that drops either fragment fails-loudly here.
9895        let err = LispError::MissingHeadSymbol {
9896            keyword: "defmonitor",
9897            got: None,
9898        };
9899        assert_eq!(
9900            format!("{err}"),
9901            "compile error in defmonitor: missing head symbol (empty list)"
9902        );
9903    }
9904
9905    #[test]
9906    fn missing_head_symbol_display_with_int_got_renders_legacy_prefix_and_got() {
9907        // `(5 …)` — list[0] is the int `5`, not a symbol. The variant
9908        // renders both the keyword AND the offending head's
9909        // `Sexp::Display` projection — both fields are first-class
9910        // structural data, not embedded substrings of `message`. The
9911        // prefix `compile error in {keyword}: missing head symbol`
9912        // matches the legacy `Compile { form: keyword.to_string(),
9913        // message: "missing head symbol" }` byte-for-byte; the
9914        // structural detail (`(got 5)`) is appended.
9915        let err = LispError::MissingHeadSymbol {
9916            keyword: "defmonitor",
9917            got: Some(SexpWitness::new(SexpShape::Int, "5")),
9918        };
9919        assert_eq!(
9920            format!("{err}"),
9921            "compile error in defmonitor: missing head symbol (got 5)"
9922        );
9923    }
9924
9925    #[test]
9926    fn missing_head_symbol_display_carries_keyword_atom_got_unchanged() {
9927        // `(:foo …)` — list[0] is a keyword atom. `Sexp::Display` for
9928        // `Atom::Keyword(s)` writes `:s`; pin that the variant's
9929        // Display passes the keyword form through unchanged so an
9930        // LSP that surfaces "you wrote `:foo` where a head symbol
9931        // was expected" gains the literal keyword value as data, no
9932        // re-parsing required.
9933        let err = LispError::MissingHeadSymbol {
9934            keyword: "defalertpolicy",
9935            got: Some(SexpWitness::new(SexpShape::Keyword, ":foo")),
9936        };
9937        assert_eq!(
9938            format!("{err}"),
9939            "compile error in defalertpolicy: missing head symbol (got :foo)"
9940        );
9941    }
9942
9943    #[test]
9944    fn missing_head_symbol_display_carries_string_got_unchanged() {
9945        // `Sexp::Display` for `Atom::Str(s)` writes `"s"` (with quotes);
9946        // pin that the variant's Display passes the string form through
9947        // unchanged so an LSP that surfaces "you wrote `\"name\"` where
9948        // a head symbol was expected" gains the literal value as data,
9949        // no re-parsing required.
9950        let err = LispError::MissingHeadSymbol {
9951            keyword: "defmonitor",
9952            got: Some(SexpWitness::new(SexpShape::String, "\"name\"")),
9953        };
9954        assert_eq!(
9955            format!("{err}"),
9956            "compile error in defmonitor: missing head symbol (got \"name\")"
9957        );
9958    }
9959
9960    #[test]
9961    fn missing_head_symbol_display_carries_nested_list_got_unchanged() {
9962        // `((nested) …)` — list[0] is itself a list. The nested form
9963        // round-trips through `Sexp::Display` into the variant's `got`
9964        // slot unchanged so the operator sees what they wrote.
9965        let err = LispError::MissingHeadSymbol {
9966            keyword: "defmonitor",
9967            got: Some(SexpWitness::new(SexpShape::List, "(nested)")),
9968        };
9969        assert_eq!(
9970            format!("{err}"),
9971            "compile error in defmonitor: missing head symbol (got (nested))"
9972        );
9973    }
9974
9975    #[test]
9976    fn missing_head_symbol_display_preserves_legacy_substring_for_message_grep() {
9977        // Pin the legacy substring — `"missing head symbol"` — as a
9978        // separate assertion so a regression that drifts the wording
9979        // (e.g., to "no head symbol" or "head must be a symbol")
9980        // fails-loudly here even if the appended parenthetical changes
9981        // shape. The substring is what consumers downstream
9982        // (`tatara-check`, the REPL) substring-match on today; the
9983        // prefix matches the legacy `Compile { form:
9984        // keyword.to_string(), message: "missing head symbol" }`
9985        // byte-for-byte.
9986        let err = LispError::MissingHeadSymbol {
9987            keyword: "defmonitor",
9988            got: None,
9989        };
9990        let msg = format!("{err}");
9991        assert!(
9992            msg.contains("missing head symbol"),
9993            "expected legacy substring in message, got: {msg}"
9994        );
9995        assert!(
9996            msg.contains("compile error in defmonitor:"),
9997            "expected legacy form-label prefix in message, got: {msg}"
9998        );
9999    }
10000
10001    #[test]
10002    fn missing_head_symbol_got_carries_typed_witness_through_variant_slot() {
10003        // Pin the structural binding AND the Display projection on
10004        // `LispError::MissingHeadSymbol.got`. After this lift the
10005        // variant's typed slot is `Option<SexpWitness>` — the joint
10006        // `SexpWitness` identity (the same primitive `SpliceOutsideList.got`,
10007        // `NonSymbolUnquoteTarget.got`, `NonSymbolParam.got`,
10008        // `DefmacroNonSymbolName.got`, `DefmacroNonListParams.got`,
10009        // and `RestParamMissingName.got` already carry) wrapped in
10010        // `Option` because the head slot bifurcates structurally
10011        // between "missing entirely" (`None`, empty list) and
10012        // "present but malformed" (`Some(SexpWitness)`, non-symbol
10013        // head). The typed witness lands on the `Some` arm only. A
10014        // regression that re-collapses to a free-form `Option<String>`
10015        // got slot (losing the rustc-enforced closed-set guarantee
10016        // on shape identity at the outer typed-entry gate's
10017        // non-symbol-head rejection variant) fails-loudly here.
10018        // Display via `SexpWitness::Display` writes only the `display`
10019        // field so the rendered `(got <display>)` clause is
10020        // byte-for-byte identical to the pre-lift `Option<String>`
10021        // shape; downstream substring-grep consumers (`tatara-check`,
10022        // REPL) see no drift.
10023        let err = LispError::MissingHeadSymbol {
10024            keyword: "defmonitor",
10025            got: Some(SexpWitness::new(SexpShape::Int, "5")),
10026        };
10027        match &err {
10028            LispError::MissingHeadSymbol { keyword, got } => {
10029                assert_eq!(*keyword, "defmonitor");
10030                let witness = got.as_ref().expect("got must be Some");
10031                assert_eq!(witness.shape, SexpShape::Int);
10032                assert_eq!(witness.display, "5");
10033            }
10034            other => panic!("expected MissingHeadSymbol, got {other:?}"),
10035        }
10036        assert_eq!(
10037            format!("{err}"),
10038            "compile error in defmonitor: missing head symbol (got 5)"
10039        );
10040    }
10041
10042    #[test]
10043    fn missing_head_symbol_got_distinguishes_int_from_keyword_at_variant_slot() {
10044        // Pin the typed-shape bifurcation at the variant slot — `5`
10045        // (int atom at the head slot) and `:foo` (keyword atom at
10046        // the head slot) BOTH route to `MissingHeadSymbol` on the
10047        // `Some` arm, but the typed `got.shape` slot distinguishes
10048        // them structurally — `SexpShape::Int` vs.
10049        // `SexpShape::Keyword`. Sibling pin for the same structural-
10050        // shape-bifurcation property
10051        // `rest_param_missing_name_got_distinguishes_int_from_keyword_at_variant_slot`
10052        // pins on `RestParamMissingName`. A regression that erases
10053        // the typed shape (e.g., reverts to `got: Option<String>`)
10054        // would lose this distinction — tooling that wants to surface
10055        // "you wrote an int `5` where a head symbol was expected" vs.
10056        // "you wrote a keyword `:foo` where a head symbol was
10057        // expected (did you mean `foo`?)" would have to substring-
10058        // grep the `display` field, brittle.
10059        let err_int = LispError::MissingHeadSymbol {
10060            keyword: "defmonitor",
10061            got: Some(SexpWitness::new(SexpShape::Int, "5")),
10062        };
10063        let err_kw = LispError::MissingHeadSymbol {
10064            keyword: "defalertpolicy",
10065            got: Some(SexpWitness::new(SexpShape::Keyword, ":foo")),
10066        };
10067        let (int_shape, kw_shape) = (
10068            match &err_int {
10069                LispError::MissingHeadSymbol { got: Some(w), .. } => w.shape,
10070                _ => unreachable!(),
10071            },
10072            match &err_kw {
10073                LispError::MissingHeadSymbol { got: Some(w), .. } => w.shape,
10074                _ => unreachable!(),
10075            },
10076        );
10077        assert_ne!(
10078            int_shape, kw_shape,
10079            "Int and Keyword witnesses must remain structurally distinct at the variant slot",
10080        );
10081        assert_eq!(int_shape, SexpShape::Int);
10082        assert_eq!(kw_shape, SexpShape::Keyword);
10083    }
10084
10085    #[test]
10086    fn missing_head_symbol_and_rest_param_gate_share_one_witness_primitive() {
10087        // Pin that ALL SEVEN Sexp-display-source `got` slots in the
10088        // substrate (`SpliceOutsideList`, `NonSymbolUnquoteTarget`,
10089        // `NonSymbolParam`, `DefmacroNonSymbolName`,
10090        // `DefmacroNonListParams`, `RestParamMissingName`,
10091        // `MissingHeadSymbol`) carry the SAME typed `SexpWitness`
10092        // primitive — the closed set of "offending inner Sexp"
10093        // identities is bound by ONE typed primitive across SEVEN
10094        // rejection surfaces: the template-gate's `,X/,@X` pair, the
10095        // defmacro-syntax-gate's `parse_params` walker (BOTH
10096        // non-symbol-param AND post-`&rest`-non-symbol-follower
10097        // rejection points), BOTH of the defmacro-syntax-gate's outer
10098        // `macro_def_from` rejection points (name-symbol AND
10099        // param-list), AND the outer `compile_from_sexp` typed-entry
10100        // gate's non-symbol-head rejection point. With this lift
10101        // EVERY `Sexp::Display`-source `got` slot in the substrate is
10102        // structurally unified end-to-end. The `Option`-wrap on
10103        // `MissingHeadSymbol.got` and `RestParamMissingName.got` is
10104        // the bifurcation between "missing entirely" and "present but
10105        // malformed"; the typed witness rides on the `Some` arm and
10106        // is structurally identical to the other five variants' got
10107        // slots. A regression that diverges the slot type on any one
10108        // variant (e.g., re-collapses `MissingHeadSymbol.got` to
10109        // `Option<String>` while leaving the others typed) fails-
10110        // loudly here because the assignment round-trips the witness
10111        // across all seven slot types. Sibling pin to
10112        // `rest_param_missing_name_and_macro_def_gate_share_one_witness_primitive`
10113        // — extending the typed-identity unification contract from
10114        // six slots to seven, closing the contract.
10115        let same_witness = SexpWitness::new(SexpShape::List, "(nested)");
10116        let missing_head = LispError::MissingHeadSymbol {
10117            keyword: "defmonitor",
10118            got: Some(same_witness.clone()),
10119        };
10120        let rest_param_missing_name = LispError::RestParamMissingName {
10121            rest_position: 0,
10122            got: Some(same_witness.clone()),
10123        };
10124        let defmacro_non_list_params = LispError::DefmacroNonListParams {
10125            head: MacroDefHead::Defmacro,
10126            got: same_witness.clone(),
10127        };
10128        let defmacro_non_symbol_name = LispError::DefmacroNonSymbolName {
10129            head: MacroDefHead::Defmacro,
10130            got: same_witness.clone(),
10131        };
10132        let non_symbol_param = LispError::NonSymbolParam {
10133            position: 0,
10134            got: same_witness.clone(),
10135        };
10136        let non_symbol_target = LispError::NonSymbolUnquoteTarget {
10137            prefix: UnquoteForm::Unquote,
10138            got: same_witness.clone(),
10139        };
10140        let splice_outside = LispError::SpliceOutsideList {
10141            got: same_witness.clone(),
10142        };
10143        match (
10144            &missing_head,
10145            &rest_param_missing_name,
10146            &defmacro_non_list_params,
10147            &defmacro_non_symbol_name,
10148            &non_symbol_param,
10149            &non_symbol_target,
10150            &splice_outside,
10151        ) {
10152            (
10153                LispError::MissingHeadSymbol { got: Some(a), .. },
10154                LispError::RestParamMissingName { got: Some(b), .. },
10155                LispError::DefmacroNonListParams { got: c, .. },
10156                LispError::DefmacroNonSymbolName { got: d, .. },
10157                LispError::NonSymbolParam { got: e, .. },
10158                LispError::NonSymbolUnquoteTarget { got: f, .. },
10159                LispError::SpliceOutsideList { got: g },
10160            ) => {
10161                assert_eq!(a.shape, b.shape);
10162                assert_eq!(b.shape, c.shape);
10163                assert_eq!(c.shape, d.shape);
10164                assert_eq!(d.shape, e.shape);
10165                assert_eq!(e.shape, f.shape);
10166                assert_eq!(f.shape, g.shape);
10167                assert_eq!(a.display, b.display);
10168                assert_eq!(b.display, c.display);
10169                assert_eq!(c.display, d.display);
10170                assert_eq!(d.display, e.display);
10171                assert_eq!(e.display, f.display);
10172                assert_eq!(f.display, g.display);
10173                assert_eq!(*a, same_witness);
10174                assert_eq!(*b, same_witness);
10175                assert_eq!(*c, same_witness);
10176                assert_eq!(*d, same_witness);
10177                assert_eq!(*e, same_witness);
10178                assert_eq!(*f, same_witness);
10179                assert_eq!(*g, same_witness);
10180            }
10181            _ => unreachable!(),
10182        }
10183    }
10184
10185    #[test]
10186    fn not_a_list_form_display_renders_legacy_compile_shape() {
10187        // Pin the rendered diagnostic byte-for-byte against the
10188        // legacy `Compile { form: keyword.to_string(), message:
10189        // "expected list form" }` shape. Authoring tools that
10190        // substring-grep on the rendered message see no drift; tools
10191        // that pattern-match on the variant gain structural binding.
10192        let err = LispError::NotAListForm {
10193            keyword: "defmonitor",
10194        };
10195        assert_eq!(
10196            format!("{err}"),
10197            "compile error in defmonitor: expected list form"
10198        );
10199    }
10200
10201    #[test]
10202    fn not_a_list_form_display_carries_keyword_unchanged() {
10203        // Pin path-uniformity across distinct keywords — every
10204        // `TataraDomain` impl funnels through `not_a_list_form_err`
10205        // with its own `Self::KEYWORD`, so the variant's `keyword`
10206        // slot must round-trip every literal the derive macro
10207        // accepts. A regression that drops or rewrites the keyword
10208        // (e.g., lowercasing, stripping the `def` prefix) fails-
10209        // loudly here.
10210        let err = LispError::NotAListForm {
10211            keyword: "defpoint",
10212        };
10213        assert_eq!(
10214            format!("{err}"),
10215            "compile error in defpoint: expected list form"
10216        );
10217    }
10218
10219    #[test]
10220    fn not_a_list_form_display_preserves_legacy_substring_for_message_grep() {
10221        // Pin the legacy substring — `"expected list form"` — as a
10222        // separate assertion so a regression that drifts the
10223        // wording (e.g., to "expected list" or "must be a list")
10224        // fails-loudly here even if the prefix changes shape.
10225        // The substring is what consumers downstream
10226        // (`tatara-check`, the REPL) substring-match on today; the
10227        // prefix matches the legacy `Compile { form:
10228        // keyword.to_string(), message: "expected list form" }`
10229        // byte-for-byte.
10230        let err = LispError::NotAListForm {
10231            keyword: "defalertpolicy",
10232        };
10233        let msg = format!("{err}");
10234        assert!(
10235            msg.contains("expected list form"),
10236            "expected legacy substring in message, got: {msg}"
10237        );
10238        assert!(
10239            msg.contains("compile error in defalertpolicy:"),
10240            "expected legacy form-label prefix in message, got: {msg}"
10241        );
10242    }
10243
10244    #[test]
10245    fn unknown_kwarg_display_with_hint_renders_did_you_mean_then_allowed_list() {
10246        // The variant renders byte-for-byte the same string the legacy
10247        // `Compile { form: ":tthreshold", message: "unknown keyword (did
10248        // you mean :threshold?; allowed: :a, :b, :c)" }` shape produced,
10249        // so authoring tools (REPL, LSP, `tatara-check`) that
10250        // substring-match on the rendered diagnostic see no drift; tools
10251        // that pattern-match on the variant gain structural binding.
10252        let err = LispError::UnknownKwarg {
10253            key: "tthreshold".into(),
10254            hint: Some("threshold".into()),
10255            allowed: vec!["name".into(), "query".into(), "threshold".into()],
10256        };
10257        assert_eq!(
10258            format!("{err}"),
10259            "compile error in :tthreshold: unknown keyword \
10260             (did you mean :threshold?; allowed: :name, :query, :threshold)"
10261        );
10262    }
10263
10264    #[test]
10265    fn unknown_kwarg_display_without_hint_renders_allowed_list_only() {
10266        // No hint: the rendered message has the allowed list but no `did
10267        // you mean` clause. A wrong hint is worse than no hint — the
10268        // slot stays empty unless `suggest` ranks a candidate within
10269        // the bounded edit distance.
10270        let err = LispError::UnknownKwarg {
10271            key: "totally-unrelated".into(),
10272            hint: None,
10273            allowed: vec!["name".into(), "query".into(), "threshold".into()],
10274        };
10275        assert_eq!(
10276            format!("{err}"),
10277            "compile error in :totally-unrelated: unknown keyword \
10278             (allowed: :name, :query, :threshold)"
10279        );
10280    }
10281
10282    #[test]
10283    fn unknown_kwarg_display_carries_kebab_case_keys_unchanged() {
10284        // `:notify-ref`, `:window-seconds`, every kebab-cased kwarg name
10285        // round-trips through both the offending-key slot AND the
10286        // allowed-list slot unchanged. Pinning this contract means a
10287        // regression that camelCases or lowercases either side fails-
10288        // loudly here.
10289        let err = LispError::UnknownKwarg {
10290            key: "windou-seconds".into(),
10291            hint: Some("window-seconds".into()),
10292            allowed: vec!["notify-ref".into(), "window-seconds".into()],
10293        };
10294        assert_eq!(
10295            format!("{err}"),
10296            "compile error in :windou-seconds: unknown keyword \
10297             (did you mean :window-seconds?; allowed: :notify-ref, :window-seconds)"
10298        );
10299    }
10300
10301    #[test]
10302    fn duplicate_kwarg_display_matches_legacy_compile_shape() {
10303        // The variant renders byte-for-byte the same string the legacy
10304        // `Compile { form: ":name", message: "duplicate keyword" }` shape
10305        // produced, so authoring tools (REPL, LSP, `tatara-check`) that
10306        // substring-match on the rendered diagnostic see no drift; tools
10307        // that pattern-match on the variant gain structural binding.
10308        let err = LispError::DuplicateKwarg { key: "name".into() };
10309        assert_eq!(
10310            format!("{err}"),
10311            "compile error in :name: duplicate keyword"
10312        );
10313    }
10314
10315    #[test]
10316    fn duplicate_kwarg_display_carries_kebab_case_keys_unchanged() {
10317        // `:notify-ref`, `:window-seconds`, every kebab-cased kwarg name
10318        // round-trips through the variant's Display unchanged. Pinning
10319        // this contract means a regression that camelCases or lowercases
10320        // the key in the rendered message fails-loudly.
10321        let err = LispError::DuplicateKwarg {
10322            key: "notify-ref".into(),
10323        };
10324        assert_eq!(
10325            format!("{err}"),
10326            "compile error in :notify-ref: duplicate keyword"
10327        );
10328    }
10329
10330    #[test]
10331    fn missing_kwarg_display_matches_legacy_compile_shape() {
10332        // The variant renders byte-for-byte the same string the legacy
10333        // `Compile { form: ":threshold", message: "required but not provided" }`
10334        // shape produced, so authoring tools (REPL, LSP, `tatara-check`) that
10335        // substring-match on the rendered diagnostic see no drift; tools
10336        // that pattern-match on the variant gain structural binding.
10337        let err = LispError::MissingKwarg {
10338            key: "threshold".into(),
10339        };
10340        assert_eq!(
10341            format!("{err}"),
10342            "compile error in :threshold: required but not provided"
10343        );
10344    }
10345
10346    #[test]
10347    fn missing_kwarg_display_carries_kebab_case_keys_unchanged() {
10348        // `:notify-ref`, `:window-seconds`, every kebab-cased kwarg name
10349        // round-trips through the variant's Display unchanged. Pinning
10350        // this contract means a regression that camelCases or lowercases
10351        // the key in the rendered message fails-loudly.
10352        let err = LispError::MissingKwarg {
10353            key: "notify-ref".into(),
10354        };
10355        assert_eq!(
10356            format!("{err}"),
10357            "compile error in :notify-ref: required but not provided"
10358        );
10359    }
10360
10361    #[test]
10362    fn unbound_template_var_display_without_hint_matches_legacy_compile_shape() {
10363        // Without a hint the variant renders byte-for-byte the same string
10364        // the legacy `Compile { form: ",x", message: "unbound" }` shape
10365        // produced, so authoring tools that substring-match on the rendered
10366        // diagnostic see no drift.
10367        let err = LispError::UnboundTemplateVar {
10368            prefix: UnquoteForm::Unquote,
10369            name: "y".into(),
10370            hint: None,
10371        };
10372        assert_eq!(format!("{err}"), "compile error in ,y: unbound");
10373    }
10374
10375    #[test]
10376    fn unbound_template_var_display_appends_hint_suffix_when_present() {
10377        // With a hint the message gains a `"; did you mean ,X?"` suffix —
10378        // the prefix is preserved in the hint so the operator can copy-paste
10379        // the suggestion verbatim.
10380        let err = LispError::UnboundTemplateVar {
10381            prefix: UnquoteForm::Unquote,
10382            name: "xs".into(),
10383            hint: Some("x".into()),
10384        };
10385        assert_eq!(
10386            format!("{err}"),
10387            "compile error in ,xs: unbound; did you mean ,x?"
10388        );
10389    }
10390
10391    #[test]
10392    fn unknown_domain_keyword_display_with_hint_renders_did_you_mean_then_registered_list() {
10393        // The variant names the offending keyword's full call shape
10394        // (`(defmoniter ...)`), the structural near-miss in the same call
10395        // shape (`(defmonitor ...)`), and the sorted registered set.
10396        // Authoring tools that pattern-match on the variant gain
10397        // structural binding to `keyword` / `hint` / `registered` —
10398        // tools that substring-match on the rendered diagnostic see a
10399        // stable shape.
10400        let err = LispError::UnknownDomainKeyword {
10401            keyword: "defmoniter".into(),
10402            hint: Some("defmonitor".into()),
10403            registered: vec![
10404                "defalertpolicy".into(),
10405                "defmonitor".into(),
10406                "defnotify".into(),
10407            ],
10408        };
10409        assert_eq!(
10410            format!("{err}"),
10411            "unknown domain keyword: (defmoniter ...) \
10412             (did you mean (defmonitor ...)?; \
10413             registered: defalertpolicy, defmonitor, defnotify)"
10414        );
10415    }
10416
10417    #[test]
10418    fn unknown_domain_keyword_display_without_hint_renders_registered_list_only() {
10419        // No near-miss within the bounded edit distance: the rendered
10420        // message has the registered list but no `did you mean` clause.
10421        // A wrong hint is worse than no hint — the slot stays empty
10422        // unless `suggest_keyword` ranks a candidate within the bound.
10423        let err = LispError::UnknownDomainKeyword {
10424            keyword: "totally-unrelated".into(),
10425            hint: None,
10426            registered: vec!["defalertpolicy".into(), "defmonitor".into()],
10427        };
10428        assert_eq!(
10429            format!("{err}"),
10430            "unknown domain keyword: (totally-unrelated ...) \
10431             (registered: defalertpolicy, defmonitor)"
10432        );
10433    }
10434
10435    #[test]
10436    fn unknown_domain_keyword_display_with_empty_registry_renders_no_domains_registered() {
10437        // The substrate names the structural reason — the registry has
10438        // no candidates at all — instead of a misleading empty
10439        // `registered: ` suffix. A typo against an empty registry is
10440        // unambiguously a registry-seeding bug, not a near-miss.
10441        let err = LispError::UnknownDomainKeyword {
10442            keyword: "defmonitor".into(),
10443            hint: None,
10444            registered: vec![],
10445        };
10446        assert_eq!(
10447            format!("{err}"),
10448            "unknown domain keyword: (defmonitor ...) (no domains registered)"
10449        );
10450    }
10451
10452    #[test]
10453    fn unknown_domain_keyword_display_carries_kebab_case_keywords_unchanged() {
10454        // Kebab-cased domain keywords (`defalert-policy`,
10455        // `defprocess-spec`) round-trip through the offending-keyword
10456        // slot AND the registered-list slot unchanged. Pinning this
10457        // contract means a regression that camelCases or lowercases
10458        // either side fails-loudly here.
10459        let err = LispError::UnknownDomainKeyword {
10460            keyword: "defalert-policiy".into(),
10461            hint: Some("defalert-policy".into()),
10462            registered: vec!["defalert-policy".into(), "defprocess-spec".into()],
10463        };
10464        assert_eq!(
10465            format!("{err}"),
10466            "unknown domain keyword: (defalert-policiy ...) \
10467             (did you mean (defalert-policy ...)?; \
10468             registered: defalert-policy, defprocess-spec)"
10469        );
10470    }
10471
10472    #[test]
10473    fn unknown_among_suffix_owns_the_parenthetical_wrapping_skeleton() {
10474        // The Some-arm owns the `did you mean {hint}?; {body}` join; both arms
10475        // delegate the bare leading-space-and-parens to `paren_suffix`. Both
10476        // gates whose docs declare they "share ONE structural shape" wrap
10477        // through this skeleton, so a regression that drifts the join fails
10478        // here; one that drifts the bare wrapping fails in
10479        // `every_suffix_helper_wraps_through_one_paren_primitive`.
10480        assert_eq!(
10481            unknown_among_suffix(Some(":threshold"), "allowed: :name, :threshold"),
10482            " (did you mean :threshold?; allowed: :name, :threshold)"
10483        );
10484        assert_eq!(
10485            unknown_among_suffix(None, "allowed: :name, :threshold"),
10486            " (allowed: :name, :threshold)"
10487        );
10488    }
10489
10490    #[test]
10491    fn optional_param_malformed_display_renders_typed_reason_in_suffix() {
10492        // The variant's Display threads BOTH the offending list literal
10493        // (`got` via SexpWitness's Display projection) AND the typed
10494        // `reason`'s label into the rendered message — the prefix names
10495        // the malformed-spec failure mode, the parenthetical suffix names
10496        // position + reason. Authoring tools (REPL, LSP, `tatara-check`)
10497        // pattern-match on the variant for structural binding; tools that
10498        // substring-match see a stable shape parallel to the existing
10499        // `OptionalMarkerRepeated` Display.
10500        let empty = LispError::OptionalParamMalformed {
10501            position: 1,
10502            got: SexpWitness::new(SexpShape::List, "()"),
10503            reason: OptionalParamMalformedReason::EmptyList,
10504        };
10505        assert_eq!(
10506            format!("{empty}"),
10507            "compile error in defmacro params: malformed &optional spec, got () (position 1, empty list)"
10508        );
10509        let missing = LispError::OptionalParamMalformed {
10510            position: 2,
10511            got: SexpWitness::new(SexpShape::List, "(x)"),
10512            reason: OptionalParamMalformedReason::MissingDefault,
10513        };
10514        assert_eq!(
10515            format!("{missing}"),
10516            "compile error in defmacro params: malformed &optional spec, got (x) (position 2, missing default)"
10517        );
10518        let extra = LispError::OptionalParamMalformed {
10519            position: 3,
10520            got: SexpWitness::new(SexpShape::List, "(x 1 2)"),
10521            reason: OptionalParamMalformedReason::ExtraElements { length: 3 },
10522        };
10523        assert_eq!(
10524            format!("{extra}"),
10525            "compile error in defmacro params: malformed &optional spec, got (x 1 2) (position 3, 3 elements (need 2))"
10526        );
10527        let nonsym = LispError::OptionalParamMalformed {
10528            position: 0,
10529            got: SexpWitness::new(SexpShape::List, "(5 default)"),
10530            reason: OptionalParamMalformedReason::NonSymbolName,
10531        };
10532        assert_eq!(
10533            format!("{nonsym}"),
10534            "compile error in defmacro params: malformed &optional spec, got (5 default) (position 0, name not a symbol)"
10535        );
10536    }
10537
10538    #[test]
10539    fn optional_param_malformed_reason_static_labels_pin_legacy_label_bytes() {
10540        // Each per-role `pub const *_LABEL: &'static str` on the closed-set
10541        // `OptionalParamMalformedReason` algebra binds byte-for-byte to the
10542        // pre-lift inline literal at `label()`'s matching arm — so a
10543        // regression that renames ONE constant silently fractures the
10544        // operator-facing rejection wording. The three static-subset arms
10545        // route through the typed constants; the fourth arm
10546        // (`ExtraElements`) formats a `usize` payload dynamically and is
10547        // excluded from this pin.
10548        assert_eq!(OptionalParamMalformedReason::EMPTY_LIST_LABEL, "empty list");
10549        assert_eq!(
10550            OptionalParamMalformedReason::MISSING_DEFAULT_LABEL,
10551            "missing default"
10552        );
10553        assert_eq!(
10554            OptionalParamMalformedReason::NON_SYMBOL_NAME_LABEL,
10555            "name not a symbol"
10556        );
10557
10558        // The `label()` projection routes through the typed per-role
10559        // constants — pinned by string equality against each constant.
10560        assert_eq!(
10561            OptionalParamMalformedReason::EmptyList.label(),
10562            OptionalParamMalformedReason::EMPTY_LIST_LABEL
10563        );
10564        assert_eq!(
10565            OptionalParamMalformedReason::MissingDefault.label(),
10566            OptionalParamMalformedReason::MISSING_DEFAULT_LABEL
10567        );
10568        assert_eq!(
10569            OptionalParamMalformedReason::NonSymbolName.label(),
10570            OptionalParamMalformedReason::NON_SYMBOL_NAME_LABEL
10571        );
10572
10573        // The dynamic `ExtraElements` arm is EXCLUDED from the static
10574        // subset — its label is `format!("{length} elements (need 2)")`
10575        // and cannot bind to a `&'static str` constant. The three
10576        // per-role constants close the STATIC subset; the fourth arm
10577        // stays inline at `label()`.
10578        assert_eq!(
10579            OptionalParamMalformedReason::ExtraElements { length: 4 }.label(),
10580            "4 elements (need 2)"
10581        );
10582
10583        // Family-wide `STATIC_LABELS` array closes the 3-of-4 carving —
10584        // `[&'static str; 3]` with rustc-enforced arity.
10585        assert_eq!(OptionalParamMalformedReason::STATIC_LABELS.len(), 3);
10586        assert_eq!(
10587            OptionalParamMalformedReason::STATIC_LABELS,
10588            [
10589                OptionalParamMalformedReason::EMPTY_LIST_LABEL,
10590                OptionalParamMalformedReason::MISSING_DEFAULT_LABEL,
10591                OptionalParamMalformedReason::NON_SYMBOL_NAME_LABEL,
10592            ]
10593        );
10594
10595        // Pairwise disjointness across the static subset — a future
10596        // rename that collides `MISSING_DEFAULT_LABEL` onto the
10597        // `EMPTY_LIST_LABEL` bytes silently merges two distinct
10598        // rejection wordings into one; the disjointness pin fails-
10599        // loudly at rustc-test time rather than at silent operator-
10600        // facing vocabulary fracture.
10601        let labels = OptionalParamMalformedReason::STATIC_LABELS;
10602        for i in 0..labels.len() {
10603            for j in (i + 1)..labels.len() {
10604                assert_ne!(
10605                    labels[i], labels[j],
10606                    "OptionalParamMalformedReason::STATIC_LABELS[{i}] and [{j}] collide on `{}`",
10607                    labels[i]
10608                );
10609            }
10610        }
10611    }
10612
10613    #[test]
10614    fn optional_param_spec_arity_pins_canonical_accept_target() {
10615        // The canonical accept-arity of the `&optional (NAME DEFAULT)`
10616        // list-spec is exactly TWO elements — a symbol head naming the
10617        // optional plus a default form. Pinning the const here means a
10618        // regression that bumps the accept-arity (e.g. an evaluator
10619        // lands and CL's `(name default supplied-p)` shape becomes
10620        // admissible) MUST also update this pin — which forces the
10621        // `ExtraElements` label's `"need N"` suffix + every truth-table
10622        // test's rendered-diagnostic assertion to be re-derived in
10623        // lockstep rather than silently drifting the accept target from
10624        // its operator-facing wording.
10625        assert_eq!(OptionalParamMalformedReason::OPTIONAL_PARAM_SPEC_ARITY, 2);
10626    }
10627
10628    #[test]
10629    fn classify_arity_projects_arity_to_typed_rejection_reason() {
10630        // The typed classifier closes the arity-to-reason projection at
10631        // ONE typed function on the algebra — arity 0 → EmptyList,
10632        // arity 1 → MissingDefault, arity == accept-target → None,
10633        // arity ≥3 → ExtraElements{length}. The fourth variant
10634        // NonSymbolName is NOT arity-derivable and stays at the
10635        // caller's head-symbol gate.
10636        use OptionalParamMalformedReason as R;
10637        assert_eq!(R::classify_arity(0), Some(R::EmptyList));
10638        assert_eq!(R::classify_arity(1), Some(R::MissingDefault));
10639        // Accept-arity: the classifier returns None because arity alone
10640        // does not decide the head-symbol gate.
10641        assert_eq!(R::classify_arity(R::OPTIONAL_PARAM_SPEC_ARITY), None);
10642        assert_eq!(R::classify_arity(2), None);
10643        // ExtraElements payload carries the offending arity verbatim so
10644        // the operator-facing diagnostic can name "N elements (need 2)"
10645        // exactly. Pin arity-3, arity-4, and arity-17 to close the
10646        // range of the ≥3 arm.
10647        assert_eq!(R::classify_arity(3), Some(R::ExtraElements { length: 3 }));
10648        assert_eq!(R::classify_arity(4), Some(R::ExtraElements { length: 4 }));
10649        assert_eq!(R::classify_arity(17), Some(R::ExtraElements { length: 17 }));
10650    }
10651
10652    #[test]
10653    fn extra_elements_label_derives_target_arity_from_typed_constant() {
10654        // The `ExtraElements` arm's dynamic label suffix embeds
10655        // [`OptionalParamMalformedReason::OPTIONAL_PARAM_SPEC_ARITY`]
10656        // via `format!("{length} elements (need {})", …)` — pin that a
10657        // future bump to the accept-arity propagates through the label
10658        // in lockstep rather than leaving the suffix stuck at the pre-
10659        // lift literal `2`. The bytes `"3 elements (need 2)"` /
10660        // `"7 elements (need 2)"` are the current cross-product of
10661        // (offending arity, accept-arity) values.
10662        assert_eq!(
10663            OptionalParamMalformedReason::ExtraElements { length: 3 }.label(),
10664            "3 elements (need 2)"
10665        );
10666        assert_eq!(
10667            OptionalParamMalformedReason::ExtraElements { length: 7 }.label(),
10668            "7 elements (need 2)"
10669        );
10670        // Cross-check: the `2` in `"need 2"` IS the typed constant, not
10671        // a bare literal — a rename of the const to a different value
10672        // fails this assertion with a clear diff.
10673        assert!(OptionalParamMalformedReason::ExtraElements { length: 3 }
10674            .label()
10675            .ends_with(&format!(
10676                "(need {})",
10677                OptionalParamMalformedReason::OPTIONAL_PARAM_SPEC_ARITY
10678            )));
10679    }
10680
10681    #[test]
10682    fn label_static_projects_static_subset_to_typed_option() {
10683        // The typed STATIC-subset projection carves the 4-arm closed set
10684        // along the same 3-of-4 axis STATIC_LABELS enumerates — three
10685        // arms whose diagnostic bytes bind at a `pub const &'static str`
10686        // (`Some(bytes)`) and one dynamic arm (`ExtraElements`) whose
10687        // bytes format a `usize` payload (`None`).
10688        //
10689        // Pre-lift the STATIC-subset selector was three inline
10690        // `matches!(reason, EmptyList | MissingDefault | NonSymbolName)`
10691        // gates duplicated across consumers (`label`'s three static match
10692        // arms, plus any downstream site that wanted a zero-allocation
10693        // path over the static subset). Post-lift the carving binds at
10694        // ONE typed function on the algebra with rustc-enforced exhaust-
10695        // iveness — a hypothetical fifth static arm extends the match
10696        // body AND `STATIC_LABELS` in lockstep.
10697        use OptionalParamMalformedReason as R;
10698        assert_eq!(R::EmptyList.label_static(), Some(R::EMPTY_LIST_LABEL));
10699        assert_eq!(
10700            R::MissingDefault.label_static(),
10701            Some(R::MISSING_DEFAULT_LABEL)
10702        );
10703        assert_eq!(
10704            R::NonSymbolName.label_static(),
10705            Some(R::NON_SYMBOL_NAME_LABEL)
10706        );
10707        // ExtraElements returns None regardless of the `length` payload —
10708        // the projection is payload-invariant on the dynamic arm.
10709        assert_eq!(R::ExtraElements { length: 3 }.label_static(), None);
10710        assert_eq!(R::ExtraElements { length: 0 }.label_static(), None);
10711        assert_eq!(R::ExtraElements { length: 42 }.label_static(), None);
10712
10713        // Composition with `label()`: for every static arm,
10714        // `label_static().unwrap().to_string() == label()` byte-for-byte
10715        // — the `label()` body threads through `label_static` for the
10716        // static subset rather than re-deriving the per-role match arms
10717        // inline.
10718        for reason in [R::EmptyList, R::MissingDefault, R::NonSymbolName] {
10719            assert_eq!(reason.label_static().unwrap().to_string(), reason.label());
10720        }
10721    }
10722
10723    #[test]
10724    fn label_static_matches_static_labels_array_in_declaration_order() {
10725        // The three `Some(&'static str)` results, taken in the closed
10726        // set's declaration order (EmptyList, MissingDefault,
10727        // NonSymbolName — same order as STATIC_LABELS), equal
10728        // STATIC_LABELS byte-for-byte. This pins the carving axis: the
10729        // `label_static` projection and the `STATIC_LABELS` array carve
10730        // the same 3-of-4 subset in the same order. A future rename that
10731        // reorders one against the other (e.g. swaps two variants in
10732        // `label_static`'s match without updating `STATIC_LABELS`) fails
10733        // here.
10734        use OptionalParamMalformedReason as R;
10735        let projected = [
10736            R::EmptyList.label_static().unwrap(),
10737            R::MissingDefault.label_static().unwrap(),
10738            R::NonSymbolName.label_static().unwrap(),
10739        ];
10740        assert_eq!(projected, R::STATIC_LABELS);
10741    }
10742
10743    #[test]
10744    fn label_static_composes_with_classify_arity_over_the_3_of_4_carving() {
10745        // The two typed subset projections carve the same closed set
10746        // along overlapping 3-of-4 axes:
10747        //
10748        //   * `classify_arity(n)` returns `Option<Self>` — `None` at
10749        //     accept-arity, `Some(reason)` for the 3-of-4 arity-derivable
10750        //     rejections (EmptyList, MissingDefault, ExtraElements).
10751        //   * `label_static(reason)` returns `Option<&'static str>` —
10752        //     `Some(bytes)` for the 3-of-4 static arms (EmptyList,
10753        //     MissingDefault, NonSymbolName), `None` for ExtraElements.
10754        //
10755        // The 2-of-4 intersection (EmptyList, MissingDefault) is the
10756        // arms both carvings agree on: classify_arity emits them AND
10757        // label_static gives them a `&'static str`. Composition pins:
10758        // for arity 0 or 1 the pipeline yields a `Some(&'static str)`.
10759        use OptionalParamMalformedReason as R;
10760        assert_eq!(
10761            R::classify_arity(0).and_then(R::label_static),
10762            Some(R::EMPTY_LIST_LABEL)
10763        );
10764        assert_eq!(
10765            R::classify_arity(1).and_then(R::label_static),
10766            Some(R::MISSING_DEFAULT_LABEL)
10767        );
10768        // At accept-arity, `classify_arity` returns None — the
10769        // composition short-circuits.
10770        assert_eq!(
10771            R::classify_arity(R::OPTIONAL_PARAM_SPEC_ARITY).and_then(R::label_static),
10772            None
10773        );
10774        // At arity ≥3, `classify_arity` returns
10775        // `Some(ExtraElements{length})` and `label_static` returns None
10776        // — the composition also short-circuits, because the two
10777        // carvings' `None` arms disagree (arity-derivable but NOT
10778        // static-labeled).
10779        assert_eq!(R::classify_arity(3).and_then(R::label_static), None);
10780        assert_eq!(R::classify_arity(17).and_then(R::label_static), None);
10781    }
10782
10783    #[test]
10784    fn label_delegates_static_arms_through_label_static() {
10785        // The `label()` body composes `label_static()` (the STATIC-subset
10786        // projection) with an `unwrap_or_else` fallthrough onto the
10787        // dynamic `ExtraElements` format arm. Pin that the three static
10788        // arms' `label()` bytes ARE the `label_static()` bytes with a
10789        // `.to_string()` allocation, and the fourth arm's `label()`
10790        // bytes ARE the format arm's rendering. A regression that
10791        // re-inlines the per-role `.to_string()` at `label()` (drifting
10792        // from the `label_static` composition) still passes the
10793        // per-variant byte-equality assertions above but fails this
10794        // composition pin.
10795        use OptionalParamMalformedReason as R;
10796        // Static arms — `label()` bytes route through `label_static`.
10797        for (variant, static_bytes) in [
10798            (R::EmptyList, R::EMPTY_LIST_LABEL),
10799            (R::MissingDefault, R::MISSING_DEFAULT_LABEL),
10800            (R::NonSymbolName, R::NON_SYMBOL_NAME_LABEL),
10801        ] {
10802            assert_eq!(variant.label_static(), Some(static_bytes));
10803            assert_eq!(variant.label(), static_bytes.to_string());
10804        }
10805        // Dynamic arm — `label_static()` is None; `label()` falls
10806        // through to the `ExtraElements` format arm.
10807        let extra = R::ExtraElements { length: 5 };
10808        assert_eq!(extra.label_static(), None);
10809        assert_eq!(
10810            extra.label(),
10811            format!("5 elements (need {})", R::OPTIONAL_PARAM_SPEC_ARITY)
10812        );
10813    }
10814
10815    #[test]
10816    fn optional_param_malformed_reason_extra_elements_descriptor_pins_canonical_noun_bytes() {
10817        // Pin the per-role `pub const EXTRA_ELEMENTS_DESCRIPTOR` bytes
10818        // byte-for-byte. The `"elements"` noun IS the shared format
10819        // template's `{descriptor}` slot binding for the ExtraElements
10820        // arm — a regression that renames the noun (e.g. to `"items"`
10821        // for a hypothetical general-list-shape evaluator) surfaces
10822        // here rather than at silent operator-facing diagnostic drift.
10823        assert_eq!(
10824            OptionalParamMalformedReason::EXTRA_ELEMENTS_DESCRIPTOR,
10825            "elements"
10826        );
10827    }
10828
10829    #[test]
10830    fn optional_param_malformed_reason_dynamic_descriptors_forced_arity_pins_1_of_4_carving() {
10831        // Pin `DYNAMIC_DESCRIPTORS.len() == 1` — the DYNAMIC subset of
10832        // the four-arm closed set carves exactly ONE arm
10833        // (`ExtraElements`). The peer STATIC subset carves THREE arms
10834        // (`EmptyList`, `MissingDefault`, `NonSymbolName`). The
10835        // partition sums to four: 3 + 1 == 4 == R::ALL analogue count.
10836        assert_eq!(OptionalParamMalformedReason::DYNAMIC_DESCRIPTORS.len(), 1);
10837        // Element-wise: the single dynamic descriptor equals the per-
10838        // role const byte-for-byte. Reordering the array against the
10839        // per-role const fails here.
10840        assert_eq!(
10841            OptionalParamMalformedReason::DYNAMIC_DESCRIPTORS,
10842            [OptionalParamMalformedReason::EXTRA_ELEMENTS_DESCRIPTOR]
10843        );
10844    }
10845
10846    #[test]
10847    fn descriptor_dynamic_projects_dynamic_subset_to_typed_option() {
10848        // The typed DYNAMIC-subset projection carves the 4-arm closed
10849        // set along the same 1-of-4 axis DYNAMIC_DESCRIPTORS
10850        // enumerates — one arm whose diagnostic bytes format through
10851        // the shared `"{length} {descriptor} (need {})"` template
10852        // (`Some(descriptor)`) and three arms whose full bytes bind at
10853        // a `&'static str` on the STATIC axis (`None`).
10854        use OptionalParamMalformedReason as R;
10855        assert_eq!(
10856            R::ExtraElements { length: 3 }.descriptor_dynamic(),
10857            Some(R::EXTRA_ELEMENTS_DESCRIPTOR)
10858        );
10859        // ExtraElements descriptor is payload-invariant — same
10860        // descriptor regardless of the `length` payload.
10861        assert_eq!(
10862            R::ExtraElements { length: 0 }.descriptor_dynamic(),
10863            Some(R::EXTRA_ELEMENTS_DESCRIPTOR)
10864        );
10865        assert_eq!(
10866            R::ExtraElements { length: 42 }.descriptor_dynamic(),
10867            Some(R::EXTRA_ELEMENTS_DESCRIPTOR)
10868        );
10869        // STATIC arms return None on the descriptor axis.
10870        assert_eq!(R::EmptyList.descriptor_dynamic(), None);
10871        assert_eq!(R::MissingDefault.descriptor_dynamic(), None);
10872        assert_eq!(R::NonSymbolName.descriptor_dynamic(), None);
10873    }
10874
10875    #[test]
10876    fn descriptor_dynamic_matches_dynamic_descriptors_array_in_declaration_order() {
10877        // The single `Some(&'static str)` result, taken in the closed
10878        // set's DYNAMIC declaration order (ExtraElements — same order
10879        // as DYNAMIC_DESCRIPTORS), equals DYNAMIC_DESCRIPTORS byte-for-
10880        // byte. Pins the carving axis: the `descriptor_dynamic`
10881        // projection and the `DYNAMIC_DESCRIPTORS` array carve the
10882        // same 1-of-4 subset in the same order.
10883        use OptionalParamMalformedReason as R;
10884        let projected = [R::ExtraElements { length: 5 }.descriptor_dynamic().unwrap()];
10885        assert_eq!(projected, R::DYNAMIC_DESCRIPTORS);
10886    }
10887
10888    #[test]
10889    fn label_static_and_descriptor_dynamic_partition_the_closed_set() {
10890        // Truth-table pin of the STATIC ⊕ DYNAMIC partition contract:
10891        // for every legal variant, EXACTLY ONE of `label_static(self)`
10892        // and `descriptor_dynamic(self)` returns `Some`. The two
10893        // projections carve the four-arm closed set into disjoint
10894        // covering halves without overlap.
10895        use OptionalParamMalformedReason as R;
10896        for reason in [
10897            R::EmptyList,
10898            R::MissingDefault,
10899            R::NonSymbolName,
10900            R::ExtraElements { length: 3 },
10901            R::ExtraElements { length: 0 },
10902            R::ExtraElements { length: 99 },
10903        ] {
10904            let static_hit = reason.label_static().is_some();
10905            let dynamic_hit = reason.descriptor_dynamic().is_some();
10906            assert!(
10907                static_hit ^ dynamic_hit,
10908                "STATIC ⊕ DYNAMIC partition violated for {reason:?}: \
10909                 label_static={static_hit}, descriptor_dynamic={dynamic_hit}",
10910            );
10911        }
10912    }
10913
10914    #[test]
10915    fn label_dynamic_composes_descriptor_arity_and_payload_through_shared_template() {
10916        // `label_dynamic()` returns `Some(String)` for the DYNAMIC arm
10917        // formatted through the shared `"{length} {descriptor} (need
10918        // {})"` template — the descriptor binds to
10919        // EXTRA_ELEMENTS_DESCRIPTOR, the arity suffix binds to
10920        // OPTIONAL_PARAM_SPEC_ARITY, and the length binds to the
10921        // enum's dynamic payload. The three STATIC arms return None.
10922        use OptionalParamMalformedReason as R;
10923        assert_eq!(R::EmptyList.label_dynamic(), None);
10924        assert_eq!(R::MissingDefault.label_dynamic(), None);
10925        assert_eq!(R::NonSymbolName.label_dynamic(), None);
10926        // Payload-varying: the `length` slot binds each variant's
10927        // payload verbatim.
10928        assert_eq!(
10929            R::ExtraElements { length: 3 }.label_dynamic(),
10930            Some("3 elements (need 2)".to_string())
10931        );
10932        assert_eq!(
10933            R::ExtraElements { length: 7 }.label_dynamic(),
10934            Some("7 elements (need 2)".to_string())
10935        );
10936        assert_eq!(
10937            R::ExtraElements { length: 0 }.label_dynamic(),
10938            Some("0 elements (need 2)".to_string())
10939        );
10940        // Cross-check: the composition threads OPTIONAL_PARAM_SPEC_ARITY
10941        // rather than a bare `2` literal — a rename of the arity const
10942        // propagates through this projection in lockstep.
10943        assert_eq!(
10944            R::ExtraElements { length: 4 }.label_dynamic().unwrap(),
10945            format!(
10946                "4 {} (need {})",
10947                R::EXTRA_ELEMENTS_DESCRIPTOR,
10948                R::OPTIONAL_PARAM_SPEC_ARITY
10949            )
10950        );
10951    }
10952
10953    #[test]
10954    fn label_composes_static_and_dynamic_projections_over_the_full_partition() {
10955        // Post-lift `label()` composes `label_static().map(str::to_string)
10956        // .or_else(|| label_dynamic()).expect(...)` — the STATIC ⊕
10957        // DYNAMIC partition contract guarantees exactly ONE projection
10958        // returns `Some` per variant, so the expect arm is
10959        // structurally unreachable. Pin that every variant's `label()`
10960        // bytes equal exactly what the composed projection yields.
10961        use OptionalParamMalformedReason as R;
10962        for (reason, expected) in [
10963            (R::EmptyList, R::EMPTY_LIST_LABEL.to_string()),
10964            (R::MissingDefault, R::MISSING_DEFAULT_LABEL.to_string()),
10965            (R::NonSymbolName, R::NON_SYMBOL_NAME_LABEL.to_string()),
10966            (
10967                R::ExtraElements { length: 3 },
10968                "3 elements (need 2)".to_string(),
10969            ),
10970            (
10971                R::ExtraElements { length: 42 },
10972                "42 elements (need 2)".to_string(),
10973            ),
10974        ] {
10975            assert_eq!(reason.label(), expected);
10976            // Composition pin: `label()` bytes equal whichever
10977            // projection returned Some.
10978            let composed = reason
10979                .label_static()
10980                .map(str::to_string)
10981                .or_else(|| reason.label_dynamic())
10982                .unwrap();
10983            assert_eq!(reason.label(), composed);
10984        }
10985    }
10986
10987    #[test]
10988    fn paren_suffix_owns_the_bare_parenthetical_skeleton() {
10989        // The lowest layer of the suffix-wrapping algebra: one leading space,
10990        // open paren, body, close paren. A regression that drops the leading
10991        // space, doubles it, or moves a paren fails here.
10992        assert_eq!(paren_suffix("got 5"), " (got 5)");
10993        assert_eq!(paren_suffix(""), " ()");
10994    }
10995
10996    #[test]
10997    fn every_suffix_helper_wraps_through_one_paren_primitive() {
10998        // All seven `*_suffix` helpers delegate their bare ` (…)` wrapping to
10999        // `paren_suffix`; only their body-construction stays helper-specific.
11000        // Pinning that each helper's output EQUALS `paren_suffix` applied with
11001        // that helper's body means a re-inlined divergent skeleton in ANY of
11002        // them (e.g. a dropped leading space, a moved paren) fails-loudly.
11003        // Covers both arms of the multi-arm helpers.
11004
11005        // unknown_among_suffix — the `did you mean …?; …` join layer.
11006        assert_eq!(
11007            unknown_among_suffix(Some(":t"), "allowed: :name"),
11008            paren_suffix("did you mean :t?; allowed: :name")
11009        );
11010        assert_eq!(
11011            unknown_among_suffix(None, "allowed: :name"),
11012            paren_suffix("allowed: :name")
11013        );
11014
11015        // rest_param_missing_name_suffix — the `rest marker at position …` body.
11016        assert_eq!(
11017            rest_param_missing_name_suffix(1, Some("5")),
11018            paren_suffix("rest marker at position 1, got 5")
11019        );
11020        assert_eq!(
11021            rest_param_missing_name_suffix(1, None),
11022            paren_suffix("rest marker at position 1, none provided")
11023        );
11024
11025        // rest_param_trailing_tokens_suffix — the `… trailing after name` body.
11026        assert_eq!(
11027            rest_param_trailing_tokens_suffix(1, 2, "extra"),
11028            paren_suffix("rest marker at position 1, 2 trailing after name, first: extra")
11029        );
11030
11031        // missing_head_symbol_suffix — the `got …` / `empty list` body.
11032        assert_eq!(missing_head_symbol_suffix(Some("5")), paren_suffix("got 5"));
11033        assert_eq!(missing_head_symbol_suffix(None), paren_suffix("empty list"));
11034
11035        // optional_marker_repeated_suffix — the two-marker-position body.
11036        assert_eq!(
11037            optional_marker_repeated_suffix(1, 3),
11038            paren_suffix("first &optional at position 1, second at position 3")
11039        );
11040
11041        // optional_param_malformed_suffix — the position+reason-label body.
11042        // Both `&'static` (the three string-only arms) and formatted
11043        // (the `ExtraElements{length}` arm) cases route through the same
11044        // `paren_suffix` wrapping.
11045        assert_eq!(
11046            optional_param_malformed_suffix(1, OptionalParamMalformedReason::EmptyList),
11047            paren_suffix("position 1, empty list")
11048        );
11049        assert_eq!(
11050            optional_param_malformed_suffix(
11051                2,
11052                OptionalParamMalformedReason::ExtraElements { length: 3 }
11053            ),
11054            paren_suffix("position 2, 3 elements (need 2)")
11055        );
11056    }
11057
11058    #[test]
11059    fn unknown_kwarg_and_domain_suffixes_share_one_wrapping_primitive() {
11060        // Both gates delegate their parenthetical wrapping to
11061        // `unknown_among_suffix`; only the hint-formatting (`:foo` vs
11062        // `(foo ...)`) and the body-construction (`allowed:` vs `registered:`
11063        // / `no domains registered`) stay gate-specific. Pinning that each
11064        // gate's output EQUALS the primitive applied with that gate's
11065        // formatted hint + body means a re-inlined divergent skeleton in
11066        // either gate fails-loudly. Covers all four arms: kwarg-with-hint,
11067        // kwarg-without-hint, domain-with-hint, domain-empty-registry.
11068        let allowed = vec!["name".to_string(), "threshold".to_string()];
11069        assert_eq!(
11070            unknown_kwarg_suffix(Some("threshold"), &allowed),
11071            unknown_among_suffix(Some(":threshold"), "allowed: :name, :threshold")
11072        );
11073        assert_eq!(
11074            unknown_kwarg_suffix(None, &allowed),
11075            unknown_among_suffix(None, "allowed: :name, :threshold")
11076        );
11077
11078        let registered = vec!["defmonitor".to_string(), "defnotify".to_string()];
11079        assert_eq!(
11080            unknown_domain_keyword_suffix(Some("defmonitor"), &registered),
11081            unknown_among_suffix(
11082                Some("(defmonitor ...)"),
11083                "registered: defmonitor, defnotify"
11084            )
11085        );
11086        assert_eq!(
11087            unknown_domain_keyword_suffix(None, &[]),
11088            unknown_among_suffix(None, "no domains registered")
11089        );
11090    }
11091
11092    #[test]
11093    fn non_symbol_unquote_target_display_renders_canonical_type_mismatch_shape() {
11094        // `,(list 1 2)` — the inner is a list, not a symbol. The variant
11095        // names the syntactic marker (`,`), the expected shape (`symbol` —
11096        // the only form a no-evaluator template can substitute), and the
11097        // offending literal (`(list 1 2)`) as first-class fields. Authoring
11098        // tools that pattern-match on the variant gain structural binding;
11099        // tools that substring-match on the rendered diagnostic see a
11100        // stable shape parallel to the existing `TypeMismatch` variant.
11101        let err = LispError::NonSymbolUnquoteTarget {
11102            prefix: UnquoteForm::Unquote,
11103            got: SexpWitness::new(SexpShape::List, "(list 1 2)"),
11104        };
11105        assert_eq!(
11106            format!("{err}"),
11107            "compile error in ,: expected symbol, got (list 1 2)"
11108        );
11109    }
11110
11111    #[test]
11112    fn non_symbol_unquote_target_display_preserves_splice_prefix() {
11113        // Splice marker rides through the `prefix` field; the rendered
11114        // diagnostic is `,@`, not `,`. The operator never has to translate
11115        // `,` ↔ `,@` mentally — same posture as `UnboundTemplateVar`.
11116        let err = LispError::NonSymbolUnquoteTarget {
11117            prefix: UnquoteForm::Splice,
11118            got: SexpWitness::new(SexpShape::Int, "5"),
11119        };
11120        assert_eq!(
11121            format!("{err}"),
11122            "compile error in ,@: expected symbol, got 5"
11123        );
11124    }
11125
11126    #[test]
11127    fn non_symbol_unquote_target_display_carries_keyword_atom_unchanged() {
11128        // `,:foo` — the inner is a keyword atom. The `:foo` form
11129        // round-trips through `SexpWitness::Display` (writing the
11130        // `display` field verbatim) into the variant's `got` slot
11131        // unchanged, so the operator sees what they wrote. The typed
11132        // `got.shape` slot independently carries `SexpShape::Keyword`
11133        // so tooling that wants the structural identity binds without
11134        // re-parsing.
11135        let err = LispError::NonSymbolUnquoteTarget {
11136            prefix: UnquoteForm::Unquote,
11137            got: SexpWitness::new(SexpShape::Keyword, ":foo"),
11138        };
11139        assert_eq!(
11140            format!("{err}"),
11141            "compile error in ,: expected symbol, got :foo"
11142        );
11143    }
11144
11145    #[test]
11146    fn splice_outside_list_display_renders_legacy_substring_with_offending_form() {
11147        // `,@xs` at the body's top level — there is no containing list to
11148        // splice into. The variant names the offending inner (`xs`) as a
11149        // first-class typed witness so authoring tools (REPL, LSP,
11150        // `tatara-check`) gain structural binding to BOTH `got.shape`
11151        // (typed `SexpShape::Symbol` here) AND `got.display` (the literal
11152        // `"xs"`); tools that substring-match on the rendered diagnostic
11153        // still see the legacy `"\`,@\` may only appear inside a list"`
11154        // substring verbatim because `SexpWitness::Display` writes only
11155        // the `display` field.
11156        let err = LispError::SpliceOutsideList {
11157            got: SexpWitness::new(SexpShape::Symbol, "xs"),
11158        };
11159        assert_eq!(
11160            format!("{err}"),
11161            "compile error in ,@: `,@` may only appear inside a list (got ,@xs)"
11162        );
11163    }
11164
11165    #[test]
11166    fn splice_outside_list_display_carries_list_literal_unchanged() {
11167        // The offending inner is a list literal — `,@(list 1 2)` — so the
11168        // operator sees the literal value they wrote in the parenthetical,
11169        // not just a type-name. The typed `SexpShape::List` rides the
11170        // variant slot alongside the `Sexp::Display` projection; tools
11171        // can now pattern-match on `got.shape == SexpShape::List`
11172        // without re-parsing the rendered diagnostic.
11173        let err = LispError::SpliceOutsideList {
11174            got: SexpWitness::new(SexpShape::List, "(list 1 2)"),
11175        };
11176        assert_eq!(
11177            format!("{err}"),
11178            "compile error in ,@: `,@` may only appear inside a list (got ,@(list 1 2))"
11179        );
11180    }
11181
11182    #[test]
11183    fn splice_outside_list_display_carries_kebab_case_symbol_unchanged() {
11184        // `,@notify-ref` — kebab-cased symbol round-trips through the
11185        // variant's `got.display` slot unchanged. Pinning this contract
11186        // means a regression that camelCases or lowercases the offending
11187        // form fails-loudly here.
11188        let err = LispError::SpliceOutsideList {
11189            got: SexpWitness::new(SexpShape::Symbol, "notify-ref"),
11190        };
11191        assert_eq!(
11192            format!("{err}"),
11193            "compile error in ,@: `,@` may only appear inside a list (got ,@notify-ref)"
11194        );
11195    }
11196
11197    #[test]
11198    fn splice_outside_list_display_preserves_legacy_substring_for_message_grep() {
11199        // Pin the legacy substring as a separate assertion so a regression
11200        // that drifts the wording (e.g., to "outside a list" or "without a
11201        // containing list") fails-loudly here even if the parenthetical
11202        // changes shape. The substring is what consumers downstream
11203        // (tatara-check, the REPL) substring-match on today.
11204        let err = LispError::SpliceOutsideList {
11205            got: SexpWitness::new(SexpShape::Symbol, "xs"),
11206        };
11207        let msg = format!("{err}");
11208        assert!(
11209            msg.contains("`,@` may only appear inside a list"),
11210            "expected legacy substring in message, got: {msg}"
11211        );
11212        assert!(
11213            msg.contains("(got ,@xs)"),
11214            "expected offending-form parenthetical in message, got: {msg}"
11215        );
11216    }
11217
11218    // ── SexpWitness: typed joint-identity lift for offending-Sexp slots ──
11219    //
11220    // The `SexpWitness { shape: SexpShape, display: String }` typed
11221    // primitive lands as the first promotion of a `got: String`
11222    // Sexp::Display-projection slot to a typed joint identity. Pins:
11223    // (a) `SexpWitness::Display` writes only the `display` field
11224    // (byte-for-byte legacy rendering preserved); (b) the struct's
11225    // `shape` field is pattern-matchable for tooling that wants
11226    // structural binding without re-parsing the literal; (c) the
11227    // `SpliceOutsideList.got` slot's typed shape is now load-bearing
11228    // — a regression that re-collapses `got` to a free-form `String`
11229    // loses the rustc-enforced typed-shape guarantee.
11230
11231    #[test]
11232    fn sexp_witness_display_writes_only_the_display_field() {
11233        // Pin the byte-for-byte rendering contract: `SexpWitness`'s
11234        // `Display` impl writes ONLY the `display` field, NOT the
11235        // shape label. This is the load-bearing posture for the
11236        // `#[error("...(got ,@{got})")]` annotation on
11237        // `SpliceOutsideList` — the parenthetical reads `(got ,@xs)`
11238        // verbatim, not `(got ,@symbol xs)`. A regression that adds
11239        // a shape prefix to Display fails-loudly here AND at every
11240        // legacy-rendering test downstream.
11241        let w = SexpWitness::new(SexpShape::Symbol, "xs");
11242        assert_eq!(format!("{w}"), "xs");
11243        let w = SexpWitness::new(SexpShape::List, "(list 1 2)");
11244        assert_eq!(format!("{w}"), "(list 1 2)");
11245        let w = SexpWitness::new(SexpShape::Int, "5");
11246        assert_eq!(format!("{w}"), "5");
11247        let w = SexpWitness::new(SexpShape::Keyword, ":foo");
11248        assert_eq!(format!("{w}"), ":foo");
11249    }
11250
11251    #[test]
11252    fn sexp_witness_carries_both_shape_and_display_jointly() {
11253        // Pin the joint-identity contract: `SexpWitness` carries BOTH
11254        // halves of the offending-value identity (typed `SexpShape`
11255        // AND literal `Sexp::Display` projection) in ONE owned value.
11256        // Tools that pattern-match on `witness.shape` bind to the
11257        // structural identity; tools that render via `{witness}` get
11258        // the literal value. Neither half is recoverable from the
11259        // other (a `Sexp::Display` projection of `"5"` could be Int
11260        // or Symbol — substring parsing can't recover the structural
11261        // identity reliably), so the typed witness is the canonical
11262        // source for both.
11263        let w = SexpWitness::new(SexpShape::Int, "5");
11264        assert_eq!(w.shape, SexpShape::Int);
11265        assert_eq!(w.display, "5");
11266        // The literal `"5"` would substring-grep the same as a hand-
11267        // written symbol named `5`, but the typed shape distinguishes
11268        // them structurally — a regression that drops the shape slot
11269        // would collapse this distinction.
11270        let w_sym = SexpWitness::new(SexpShape::Symbol, "5");
11271        assert_eq!(w_sym.shape, SexpShape::Symbol);
11272        assert_eq!(w_sym.display, "5");
11273        assert_ne!(
11274            w, w_sym,
11275            "Witnesses with same display but different shape must NOT be equal — typed shape is load-bearing data",
11276        );
11277    }
11278
11279    #[test]
11280    fn splice_outside_list_got_carries_typed_witness_through_variant_slot() {
11281        // Pin the structural binding on `LispError::SpliceOutsideList.got`
11282        // — a regression that re-introduces a `String`-shaped got slot
11283        // (collapsing the typed witness back into a free-form literal)
11284        // fails-loudly here. After this lift the variant's typed slot
11285        // is the joint `SexpWitness` identity; the Display projection
11286        // through `SexpWitness::Display` writes only the `display`
11287        // field so the rendered `(got ,@<display>)` parenthetical is
11288        // byte-for-byte identical to the legacy `got: String` shape.
11289        let err = LispError::SpliceOutsideList {
11290            got: SexpWitness::new(SexpShape::List, "(list 1 2)"),
11291        };
11292        match &err {
11293            LispError::SpliceOutsideList { got } => {
11294                assert_eq!(got.shape, SexpShape::List);
11295                assert_eq!(got.display, "(list 1 2)");
11296            }
11297            other => panic!("expected SpliceOutsideList, got {other:?}"),
11298        }
11299        assert_eq!(
11300            format!("{err}"),
11301            "compile error in ,@: `,@` may only appear inside a list (got ,@(list 1 2))"
11302        );
11303    }
11304
11305    #[test]
11306    fn splice_outside_list_got_distinguishes_symbol_from_list_at_variant_slot() {
11307        // Pin the typed-shape bifurcation at the variant slot: a
11308        // `,@xs` (symbol unquote-splice outside list) and a `,@(list 1 2)`
11309        // (list-literal unquote-splice outside list) BOTH route to
11310        // `SpliceOutsideList`, but the typed `got.shape` slot
11311        // distinguishes them structurally — `SexpShape::Symbol` vs.
11312        // `SexpShape::List`. A regression that erases the typed
11313        // shape (e.g., reverts to `got: String`) would lose this
11314        // distinction — tooling that wants to surface "you wrote a
11315        // symbol `,@xs` outside a list; bind `xs` to a list first"
11316        // vs. "you wrote a list literal `,@(list 1 2)` outside a
11317        // list; nest it inside `(outer ,@(...))`" would have to
11318        // substring-grep the `display` field, brittle.
11319        let err_sym = LispError::SpliceOutsideList {
11320            got: SexpWitness::new(SexpShape::Symbol, "xs"),
11321        };
11322        let err_list = LispError::SpliceOutsideList {
11323            got: SexpWitness::new(SexpShape::List, "(list 1 2)"),
11324        };
11325        let (sym_shape, list_shape) = (
11326            match &err_sym {
11327                LispError::SpliceOutsideList { got } => got.shape,
11328                _ => unreachable!(),
11329            },
11330            match &err_list {
11331                LispError::SpliceOutsideList { got } => got.shape,
11332                _ => unreachable!(),
11333            },
11334        );
11335        assert_ne!(
11336            sym_shape, list_shape,
11337            "Symbol and List witnesses must remain structurally distinct at the variant slot",
11338        );
11339        assert_eq!(sym_shape, SexpShape::Symbol);
11340        assert_eq!(list_shape, SexpShape::List);
11341    }
11342
11343    #[test]
11344    fn non_symbol_unquote_target_got_carries_typed_witness_through_variant_slot() {
11345        // Sibling pin to
11346        // `splice_outside_list_got_carries_typed_witness_through_variant_slot`
11347        // for the template-gate's OTHER `,X/,@X` rejection variant.
11348        // After this lift `LispError::NonSymbolUnquoteTarget.got` is
11349        // the typed joint `SexpWitness` identity — the same
11350        // primitive `SpliceOutsideList.got` already carries. The two
11351        // template-gate `,X/,@X` rejection variants now share ONE
11352        // typed witness identity at their `got` slot; authoring tools
11353        // bind on `got.shape` AND `got.display` jointly across both
11354        // sites rather than substring-grepping a free-form String on
11355        // each. A regression that re-collapses `got` to `String` loses
11356        // the rustc-enforced closed-set guarantee on shape identity
11357        // here.
11358        let err = LispError::NonSymbolUnquoteTarget {
11359            prefix: UnquoteForm::Unquote,
11360            got: SexpWitness::new(SexpShape::List, "(list 1 2)"),
11361        };
11362        match &err {
11363            LispError::NonSymbolUnquoteTarget { prefix, got } => {
11364                assert_eq!(*prefix, UnquoteForm::Unquote);
11365                assert_eq!(got.shape, SexpShape::List);
11366                assert_eq!(got.display, "(list 1 2)");
11367            }
11368            other => panic!("expected NonSymbolUnquoteTarget, got {other:?}"),
11369        }
11370        assert_eq!(
11371            format!("{err}"),
11372            "compile error in ,: expected symbol, got (list 1 2)"
11373        );
11374    }
11375
11376    #[test]
11377    fn non_symbol_unquote_target_got_distinguishes_int_from_keyword_at_variant_slot() {
11378        // Pin the typed-shape bifurcation at the variant slot — `,5`
11379        // (int atom in unquote slot) and `,:foo` (keyword atom in
11380        // unquote slot) BOTH route to `NonSymbolUnquoteTarget`, but
11381        // the typed `got.shape` slot distinguishes them structurally
11382        // — `SexpShape::Int` vs. `SexpShape::Keyword`. Sibling pin
11383        // for the same structural-shape-bifurcation property
11384        // `splice_outside_list_got_distinguishes_symbol_from_list_at_variant_slot`
11385        // pins on `SpliceOutsideList`. A regression that erases the
11386        // typed shape (e.g., reverts to `got: String`) would lose
11387        // this distinction — tooling that wants to surface "you wrote
11388        // an int `,5` where a symbol was expected; only symbols are
11389        // substitutable in templates" vs. "you wrote a keyword `,:foo`
11390        // where a symbol was expected; keywords aren't substitutable
11391        // (did you mean `,foo`?)" would have to substring-grep the
11392        // `display` field, brittle.
11393        let err_int = LispError::NonSymbolUnquoteTarget {
11394            prefix: UnquoteForm::Unquote,
11395            got: SexpWitness::new(SexpShape::Int, "5"),
11396        };
11397        let err_kw = LispError::NonSymbolUnquoteTarget {
11398            prefix: UnquoteForm::Unquote,
11399            got: SexpWitness::new(SexpShape::Keyword, ":foo"),
11400        };
11401        let (int_shape, kw_shape) = (
11402            match &err_int {
11403                LispError::NonSymbolUnquoteTarget { got, .. } => got.shape,
11404                _ => unreachable!(),
11405            },
11406            match &err_kw {
11407                LispError::NonSymbolUnquoteTarget { got, .. } => got.shape,
11408                _ => unreachable!(),
11409            },
11410        );
11411        assert_ne!(
11412            int_shape, kw_shape,
11413            "Int and Keyword witnesses must remain structurally distinct at the variant slot",
11414        );
11415        assert_eq!(int_shape, SexpShape::Int);
11416        assert_eq!(kw_shape, SexpShape::Keyword);
11417    }
11418
11419    #[test]
11420    fn non_symbol_unquote_target_and_splice_outside_list_share_one_witness_primitive() {
11421        // Pin that BOTH template-gate `,X/,@X` rejection variants
11422        // (`NonSymbolUnquoteTarget` AND `SpliceOutsideList`) carry
11423        // the SAME typed `SexpWitness` primitive at their `got` slot
11424        // — the closed set of "offending inner Sexp" identities is
11425        // bound by ONE typed primitive across both rejection
11426        // surfaces. A regression that diverges the slot type on one
11427        // variant (e.g., re-collapses NonSymbolUnquoteTarget.got to
11428        // String while leaving SpliceOutsideList.got as
11429        // SexpWitness) fails-loudly here because the assignment
11430        // round-trips the witness across both slot types.
11431        let same_witness = SexpWitness::new(SexpShape::List, "(list 1 2)");
11432        let non_symbol_target = LispError::NonSymbolUnquoteTarget {
11433            prefix: UnquoteForm::Splice,
11434            got: same_witness.clone(),
11435        };
11436        let splice_outside = LispError::SpliceOutsideList {
11437            got: same_witness.clone(),
11438        };
11439        match (&non_symbol_target, &splice_outside) {
11440            (
11441                LispError::NonSymbolUnquoteTarget { got: lhs_got, .. },
11442                LispError::SpliceOutsideList { got: rhs_got },
11443            ) => {
11444                assert_eq!(lhs_got.shape, rhs_got.shape);
11445                assert_eq!(lhs_got.display, rhs_got.display);
11446                assert_eq!(*lhs_got, same_witness);
11447                assert_eq!(*rhs_got, same_witness);
11448            }
11449            _ => unreachable!(),
11450        }
11451    }
11452
11453    #[test]
11454    fn non_symbol_param_got_carries_typed_witness_through_variant_slot() {
11455        // Pin the structural binding AND the Display projection on
11456        // `LispError::NonSymbolParam.got`. After this lift the variant's
11457        // typed slot is the joint `SexpWitness` identity — the same
11458        // primitive `SpliceOutsideList.got` and
11459        // `NonSymbolUnquoteTarget.got` already carry. A regression that
11460        // re-collapses `got` to `String` (losing the rustc-enforced
11461        // closed-set guarantee on shape identity) fails-loudly here.
11462        // The Display projection through `SexpWitness::Display` writes
11463        // only the `display` field so the rendered `at position X, got
11464        // <display>` clause is byte-for-byte identical to the legacy
11465        // `got: String` shape; downstream substring-grep consumers
11466        // (`tatara-check`, REPL) see no drift.
11467        let err = LispError::NonSymbolParam {
11468            position: 1,
11469            got: SexpWitness::new(SexpShape::Int, "5"),
11470        };
11471        match &err {
11472            LispError::NonSymbolParam { position, got } => {
11473                assert_eq!(*position, 1);
11474                assert_eq!(got.shape, SexpShape::Int);
11475                assert_eq!(got.display, "5");
11476            }
11477            other => panic!("expected NonSymbolParam, got {other:?}"),
11478        }
11479        assert_eq!(
11480            format!("{err}"),
11481            "compile error in defmacro params: expected symbol at \
11482             position 1, got 5"
11483        );
11484    }
11485
11486    #[test]
11487    fn non_symbol_param_got_distinguishes_int_from_keyword_at_variant_slot() {
11488        // Pin the typed-shape bifurcation at the variant slot — `5`
11489        // (int atom at a param-list position) and `:foo` (keyword atom
11490        // at a param-list position) BOTH route to `NonSymbolParam`, but
11491        // the typed `got.shape` slot distinguishes them structurally —
11492        // `SexpShape::Int` vs. `SexpShape::Keyword`. Sibling pin for
11493        // the same structural-shape-bifurcation property
11494        // `non_symbol_unquote_target_got_distinguishes_int_from_keyword_at_variant_slot`
11495        // pins on `NonSymbolUnquoteTarget`. A regression that erases
11496        // the typed shape (e.g., reverts to `got: String`) would lose
11497        // this distinction — tooling that wants to surface "you wrote
11498        // an int `5` where a symbol was expected at param-list position
11499        // 0" vs. "you wrote a keyword `:foo` where a symbol was expected
11500        // at param-list position 0 (did you mean `foo`?)" would have to
11501        // substring-grep the `display` field, brittle.
11502        let err_int = LispError::NonSymbolParam {
11503            position: 0,
11504            got: SexpWitness::new(SexpShape::Int, "5"),
11505        };
11506        let err_kw = LispError::NonSymbolParam {
11507            position: 0,
11508            got: SexpWitness::new(SexpShape::Keyword, ":foo"),
11509        };
11510        let (int_shape, kw_shape) = (
11511            match &err_int {
11512                LispError::NonSymbolParam { got, .. } => got.shape,
11513                _ => unreachable!(),
11514            },
11515            match &err_kw {
11516                LispError::NonSymbolParam { got, .. } => got.shape,
11517                _ => unreachable!(),
11518            },
11519        );
11520        assert_ne!(
11521            int_shape, kw_shape,
11522            "Int and Keyword witnesses must remain structurally distinct at the variant slot",
11523        );
11524        assert_eq!(int_shape, SexpShape::Int);
11525        assert_eq!(kw_shape, SexpShape::Keyword);
11526    }
11527
11528    #[test]
11529    fn non_symbol_param_and_template_gate_share_one_witness_primitive() {
11530        // Pin that ALL THREE Sexp-display-source `got` slots in the
11531        // substrate (`NonSymbolParam`, `NonSymbolUnquoteTarget`,
11532        // `SpliceOutsideList`) carry the SAME typed `SexpWitness`
11533        // primitive — the closed set of "offending inner Sexp"
11534        // identities is bound by ONE typed primitive across the three
11535        // rejection surfaces (the defmacro-syntax-gate's `parse_params`
11536        // walker AND the template-gate's `,X/,@X` pair). A regression
11537        // that diverges the slot type on any one variant (e.g.,
11538        // re-collapses `NonSymbolParam.got` to `String` while leaving
11539        // the template-gate variants typed) fails-loudly here because
11540        // the assignment round-trips the witness across all three slot
11541        // types. Sibling pin to
11542        // `non_symbol_unquote_target_and_splice_outside_list_share_one_witness_primitive`
11543        // — extending the typed-identity unification contract from
11544        // two slots to three.
11545        let same_witness = SexpWitness::new(SexpShape::List, "(nested)");
11546        let non_symbol_param = LispError::NonSymbolParam {
11547            position: 0,
11548            got: same_witness.clone(),
11549        };
11550        let non_symbol_target = LispError::NonSymbolUnquoteTarget {
11551            prefix: UnquoteForm::Unquote,
11552            got: same_witness.clone(),
11553        };
11554        let splice_outside = LispError::SpliceOutsideList {
11555            got: same_witness.clone(),
11556        };
11557        match (&non_symbol_param, &non_symbol_target, &splice_outside) {
11558            (
11559                LispError::NonSymbolParam { got: a, .. },
11560                LispError::NonSymbolUnquoteTarget { got: b, .. },
11561                LispError::SpliceOutsideList { got: c },
11562            ) => {
11563                assert_eq!(a.shape, b.shape);
11564                assert_eq!(b.shape, c.shape);
11565                assert_eq!(a.display, b.display);
11566                assert_eq!(b.display, c.display);
11567                assert_eq!(*a, same_witness);
11568                assert_eq!(*b, same_witness);
11569                assert_eq!(*c, same_witness);
11570            }
11571            _ => unreachable!(),
11572        }
11573    }
11574
11575    #[test]
11576    fn defmacro_non_symbol_name_got_carries_typed_witness_through_variant_slot() {
11577        // Pin the structural binding AND the Display projection on
11578        // `LispError::DefmacroNonSymbolName.got`. After this lift the
11579        // variant's typed slot is the joint `SexpWitness` identity —
11580        // the same primitive `SpliceOutsideList.got`,
11581        // `NonSymbolUnquoteTarget.got`, and `NonSymbolParam.got`
11582        // already carry. A regression that re-collapses `got` to
11583        // `String` (losing the rustc-enforced closed-set guarantee on
11584        // shape identity at the defmacro-syntax-gate's name-slot
11585        // rejection variant) fails-loudly here. The Display projection
11586        // through `SexpWitness::Display` writes only the `display`
11587        // field so the rendered `compile error in {head}: expected
11588        // name symbol, got <display>` clause is byte-for-byte
11589        // identical to the legacy `got: String` shape; downstream
11590        // substring-grep consumers (`tatara-check`, REPL) see no
11591        // drift.
11592        let err = LispError::DefmacroNonSymbolName {
11593            head: MacroDefHead::Defmacro,
11594            got: SexpWitness::new(SexpShape::Int, "5"),
11595        };
11596        match &err {
11597            LispError::DefmacroNonSymbolName { head, got } => {
11598                assert_eq!(*head, MacroDefHead::Defmacro);
11599                assert_eq!(got.shape, SexpShape::Int);
11600                assert_eq!(got.display, "5");
11601            }
11602            other => panic!("expected DefmacroNonSymbolName, got {other:?}"),
11603        }
11604        assert_eq!(
11605            format!("{err}"),
11606            "compile error in defmacro: expected name symbol, got 5"
11607        );
11608    }
11609
11610    #[test]
11611    fn defmacro_non_symbol_name_got_distinguishes_int_from_keyword_at_variant_slot() {
11612        // Pin the typed-shape bifurcation at the variant slot — `5`
11613        // (int atom at the defmacro name slot) and `:foo` (keyword
11614        // atom at the defmacro name slot) BOTH route to
11615        // `DefmacroNonSymbolName`, but the typed `got.shape` slot
11616        // distinguishes them structurally — `SexpShape::Int`
11617        // vs. `SexpShape::Keyword`. Sibling pin for the same
11618        // structural-shape-bifurcation property
11619        // `non_symbol_param_got_distinguishes_int_from_keyword_at_variant_slot`
11620        // pins on `NonSymbolParam` and
11621        // `non_symbol_unquote_target_got_distinguishes_int_from_keyword_at_variant_slot`
11622        // pins on `NonSymbolUnquoteTarget`. A regression that erases
11623        // the typed shape (e.g., reverts to `got: String`) would lose
11624        // this distinction — tooling that wants to surface "you wrote
11625        // an int `5` where a name symbol was expected" vs. "you wrote
11626        // a keyword `:foo` where a name symbol was expected (did you
11627        // mean `foo`?)" would have to substring-grep the `display`
11628        // field, brittle.
11629        let err_int = LispError::DefmacroNonSymbolName {
11630            head: MacroDefHead::Defmacro,
11631            got: SexpWitness::new(SexpShape::Int, "5"),
11632        };
11633        let err_kw = LispError::DefmacroNonSymbolName {
11634            head: MacroDefHead::Defmacro,
11635            got: SexpWitness::new(SexpShape::Keyword, ":foo"),
11636        };
11637        let (int_shape, kw_shape) = (
11638            match &err_int {
11639                LispError::DefmacroNonSymbolName { got, .. } => got.shape,
11640                _ => unreachable!(),
11641            },
11642            match &err_kw {
11643                LispError::DefmacroNonSymbolName { got, .. } => got.shape,
11644                _ => unreachable!(),
11645            },
11646        );
11647        assert_ne!(
11648            int_shape, kw_shape,
11649            "Int and Keyword witnesses must remain structurally distinct at the variant slot",
11650        );
11651        assert_eq!(int_shape, SexpShape::Int);
11652        assert_eq!(kw_shape, SexpShape::Keyword);
11653    }
11654
11655    #[test]
11656    fn defmacro_non_symbol_name_and_param_gate_share_one_witness_primitive() {
11657        // Pin that ALL FOUR Sexp-display-source `got` slots in the
11658        // substrate (`SpliceOutsideList`, `NonSymbolUnquoteTarget`,
11659        // `NonSymbolParam`, `DefmacroNonSymbolName`) carry the SAME
11660        // typed `SexpWitness` primitive — the closed set of
11661        // "offending inner Sexp" identities is bound by ONE typed
11662        // primitive across FOUR rejection surfaces: the
11663        // template-gate's `,X/,@X` pair, the defmacro-syntax-gate's
11664        // `parse_params` walker, AND the defmacro-syntax-gate's
11665        // outer name-slot rejection. A regression that diverges the
11666        // slot type on any one variant (e.g., re-collapses
11667        // `DefmacroNonSymbolName.got` to `String` while leaving the
11668        // others typed) fails-loudly here because the assignment
11669        // round-trips the witness across all four slot types. Sibling
11670        // pin to `non_symbol_param_and_template_gate_share_one_witness_primitive`
11671        // — extending the typed-identity unification contract from
11672        // three slots to four.
11673        let same_witness = SexpWitness::new(SexpShape::List, "(nested)");
11674        let defmacro_non_symbol_name = LispError::DefmacroNonSymbolName {
11675            head: MacroDefHead::Defmacro,
11676            got: same_witness.clone(),
11677        };
11678        let non_symbol_param = LispError::NonSymbolParam {
11679            position: 0,
11680            got: same_witness.clone(),
11681        };
11682        let non_symbol_target = LispError::NonSymbolUnquoteTarget {
11683            prefix: UnquoteForm::Unquote,
11684            got: same_witness.clone(),
11685        };
11686        let splice_outside = LispError::SpliceOutsideList {
11687            got: same_witness.clone(),
11688        };
11689        match (
11690            &defmacro_non_symbol_name,
11691            &non_symbol_param,
11692            &non_symbol_target,
11693            &splice_outside,
11694        ) {
11695            (
11696                LispError::DefmacroNonSymbolName { got: a, .. },
11697                LispError::NonSymbolParam { got: b, .. },
11698                LispError::NonSymbolUnquoteTarget { got: c, .. },
11699                LispError::SpliceOutsideList { got: d },
11700            ) => {
11701                assert_eq!(a.shape, b.shape);
11702                assert_eq!(b.shape, c.shape);
11703                assert_eq!(c.shape, d.shape);
11704                assert_eq!(a.display, b.display);
11705                assert_eq!(b.display, c.display);
11706                assert_eq!(c.display, d.display);
11707                assert_eq!(*a, same_witness);
11708                assert_eq!(*b, same_witness);
11709                assert_eq!(*c, same_witness);
11710                assert_eq!(*d, same_witness);
11711            }
11712            _ => unreachable!(),
11713        }
11714    }
11715
11716    #[test]
11717    fn defmacro_non_list_params_got_carries_typed_witness_through_variant_slot() {
11718        // Pin the structural binding AND the Display projection on
11719        // `LispError::DefmacroNonListParams.got`. After this lift the
11720        // variant's typed slot is the joint `SexpWitness` identity —
11721        // the same primitive `SpliceOutsideList.got`,
11722        // `NonSymbolUnquoteTarget.got`, `NonSymbolParam.got`, and
11723        // `DefmacroNonSymbolName.got` already carry. A regression that
11724        // re-collapses `got` to `String` (losing the rustc-enforced
11725        // closed-set guarantee on shape identity at the defmacro-
11726        // syntax-gate's param-list-slot rejection variant) fails-loudly
11727        // here. The Display projection through `SexpWitness::Display`
11728        // writes only the `display` field so the rendered `compile
11729        // error in {head}: expected param list, got <display>` clause
11730        // is byte-for-byte identical to the legacy `got: String`
11731        // shape; downstream substring-grep consumers (`tatara-check`,
11732        // REPL) see no drift.
11733        let err = LispError::DefmacroNonListParams {
11734            head: MacroDefHead::Defmacro,
11735            got: SexpWitness::new(SexpShape::Symbol, "x"),
11736        };
11737        match &err {
11738            LispError::DefmacroNonListParams { head, got } => {
11739                assert_eq!(*head, MacroDefHead::Defmacro);
11740                assert_eq!(got.shape, SexpShape::Symbol);
11741                assert_eq!(got.display, "x");
11742            }
11743            other => panic!("expected DefmacroNonListParams, got {other:?}"),
11744        }
11745        assert_eq!(
11746            format!("{err}"),
11747            "compile error in defmacro: expected param list, got x"
11748        );
11749    }
11750
11751    #[test]
11752    fn defmacro_non_list_params_got_distinguishes_symbol_from_int_at_variant_slot() {
11753        // Pin the typed-shape bifurcation at the variant slot — `x`
11754        // (symbol atom at the defmacro param-list slot) and `5`
11755        // (int atom at the defmacro param-list slot) BOTH route to
11756        // `DefmacroNonListParams`, but the typed `got.shape` slot
11757        // distinguishes them structurally — `SexpShape::Symbol`
11758        // vs. `SexpShape::Int`. Sibling pin for the same
11759        // structural-shape-bifurcation property
11760        // `defmacro_non_symbol_name_got_distinguishes_int_from_keyword_at_variant_slot`
11761        // pins on `DefmacroNonSymbolName` and
11762        // `non_symbol_param_got_distinguishes_int_from_keyword_at_variant_slot`
11763        // pins on `NonSymbolParam`. A regression that erases the typed
11764        // shape (e.g., reverts to `got: String`) would lose this
11765        // distinction — tooling that wants to surface "you wrote a
11766        // symbol `x` where a param list was expected (did you mean
11767        // `(x)`?)" vs. "you wrote an int `5` where a param list was
11768        // expected" would have to substring-grep the `display` field,
11769        // brittle. The symbol-vs-int bifurcation matters at THIS slot
11770        // (not the int-vs-keyword bifurcation pinned at the
11771        // name-slot variant) because the most common authoring
11772        // mistake at the param-list slot is to forget the wrapping
11773        // parens — `(defmacro f x body)` instead of `(defmacro f (x)
11774        // body)` — so the symbol shape is the natural sibling to
11775        // distinguish from numeric typos.
11776        let err_sym = LispError::DefmacroNonListParams {
11777            head: MacroDefHead::Defmacro,
11778            got: SexpWitness::new(SexpShape::Symbol, "x"),
11779        };
11780        let err_int = LispError::DefmacroNonListParams {
11781            head: MacroDefHead::Defmacro,
11782            got: SexpWitness::new(SexpShape::Int, "5"),
11783        };
11784        let (sym_shape, int_shape) = (
11785            match &err_sym {
11786                LispError::DefmacroNonListParams { got, .. } => got.shape,
11787                _ => unreachable!(),
11788            },
11789            match &err_int {
11790                LispError::DefmacroNonListParams { got, .. } => got.shape,
11791                _ => unreachable!(),
11792            },
11793        );
11794        assert_ne!(
11795            sym_shape, int_shape,
11796            "Symbol and Int witnesses must remain structurally distinct at the variant slot",
11797        );
11798        assert_eq!(sym_shape, SexpShape::Symbol);
11799        assert_eq!(int_shape, SexpShape::Int);
11800    }
11801
11802    #[test]
11803    fn defmacro_non_list_params_and_name_gate_share_one_witness_primitive() {
11804        // Pin that ALL FIVE Sexp-display-source `got` slots in the
11805        // substrate (`SpliceOutsideList`, `NonSymbolUnquoteTarget`,
11806        // `NonSymbolParam`, `DefmacroNonSymbolName`,
11807        // `DefmacroNonListParams`) carry the SAME typed `SexpWitness`
11808        // primitive — the closed set of "offending inner Sexp"
11809        // identities is bound by ONE typed primitive across FIVE
11810        // rejection surfaces: the template-gate's `,X/,@X` pair, the
11811        // defmacro-syntax-gate's `parse_params` walker, AND BOTH of
11812        // the defmacro-syntax-gate's outer `macro_def_from` rejection
11813        // points (name-symbol AND param-list — the second and third of
11814        // the three `macro_def_from` gates). A regression that
11815        // diverges the slot type on any one variant (e.g., re-collapses
11816        // `DefmacroNonListParams.got` to `String` while leaving the
11817        // others typed) fails-loudly here because the assignment
11818        // round-trips the witness across all five slot types. Sibling
11819        // pin to `defmacro_non_symbol_name_and_param_gate_share_one_witness_primitive`
11820        // — extending the typed-identity unification contract from
11821        // four slots to five, completing structural unification of the
11822        // entire `macro_def_from` rejection chain at the
11823        // `Sexp::Display`-source `got` slot (every offending inner
11824        // Sexp value that `macro_def_from` rejects now carries the
11825        // SAME typed witness, regardless of which of the three gates
11826        // — arity, name-symbol, param-list — fired).
11827        let same_witness = SexpWitness::new(SexpShape::List, "(nested)");
11828        let defmacro_non_list_params = LispError::DefmacroNonListParams {
11829            head: MacroDefHead::Defmacro,
11830            got: same_witness.clone(),
11831        };
11832        let defmacro_non_symbol_name = LispError::DefmacroNonSymbolName {
11833            head: MacroDefHead::Defmacro,
11834            got: same_witness.clone(),
11835        };
11836        let non_symbol_param = LispError::NonSymbolParam {
11837            position: 0,
11838            got: same_witness.clone(),
11839        };
11840        let non_symbol_target = LispError::NonSymbolUnquoteTarget {
11841            prefix: UnquoteForm::Unquote,
11842            got: same_witness.clone(),
11843        };
11844        let splice_outside = LispError::SpliceOutsideList {
11845            got: same_witness.clone(),
11846        };
11847        match (
11848            &defmacro_non_list_params,
11849            &defmacro_non_symbol_name,
11850            &non_symbol_param,
11851            &non_symbol_target,
11852            &splice_outside,
11853        ) {
11854            (
11855                LispError::DefmacroNonListParams { got: a, .. },
11856                LispError::DefmacroNonSymbolName { got: b, .. },
11857                LispError::NonSymbolParam { got: c, .. },
11858                LispError::NonSymbolUnquoteTarget { got: d, .. },
11859                LispError::SpliceOutsideList { got: e },
11860            ) => {
11861                assert_eq!(a.shape, b.shape);
11862                assert_eq!(b.shape, c.shape);
11863                assert_eq!(c.shape, d.shape);
11864                assert_eq!(d.shape, e.shape);
11865                assert_eq!(a.display, b.display);
11866                assert_eq!(b.display, c.display);
11867                assert_eq!(c.display, d.display);
11868                assert_eq!(d.display, e.display);
11869                assert_eq!(*a, same_witness);
11870                assert_eq!(*b, same_witness);
11871                assert_eq!(*c, same_witness);
11872                assert_eq!(*d, same_witness);
11873                assert_eq!(*e, same_witness);
11874            }
11875            _ => unreachable!(),
11876        }
11877    }
11878
11879    #[test]
11880    fn rest_param_missing_name_got_carries_typed_witness_through_variant_slot() {
11881        // Pin the structural binding AND the Display projection on
11882        // `LispError::RestParamMissingName.got`. After this lift the
11883        // variant's typed slot is `Option<SexpWitness>` — the joint
11884        // `SexpWitness` identity (the same primitive `SpliceOutsideList.got`,
11885        // `NonSymbolUnquoteTarget.got`, `NonSymbolParam.got`,
11886        // `DefmacroNonSymbolName.got`, and `DefmacroNonListParams.got`
11887        // already carry) wrapped in `Option` because the post-`&rest`
11888        // follower slot bifurcates structurally between "missing
11889        // entirely" (`None`) and "present but malformed"
11890        // (`Some(SexpWitness)`). The typed witness lands on the `Some`
11891        // arm only. A regression that re-collapses to a free-form
11892        // `Option<String>` got slot (losing the rustc-enforced
11893        // closed-set guarantee on shape identity at the
11894        // `parse_params` walker's `&rest`-follower rejection variant)
11895        // fails-loudly here. The Display projection through
11896        // `SexpWitness::Display` writes only the `display` field so
11897        // the rendered `(rest marker at position {rest_position}, got
11898        // <display>)` clause is byte-for-byte identical to the
11899        // pre-lift `Option<String>` shape; downstream substring-grep
11900        // consumers (`tatara-check`, REPL) see no drift.
11901        let err = LispError::RestParamMissingName {
11902            rest_position: 1,
11903            got: Some(SexpWitness::new(SexpShape::Int, "5")),
11904        };
11905        match &err {
11906            LispError::RestParamMissingName { rest_position, got } => {
11907                assert_eq!(*rest_position, 1);
11908                let witness = got.as_ref().expect("got must be Some");
11909                assert_eq!(witness.shape, SexpShape::Int);
11910                assert_eq!(witness.display, "5");
11911            }
11912            other => panic!("expected RestParamMissingName, got {other:?}"),
11913        }
11914        assert_eq!(
11915            format!("{err}"),
11916            "compile error in defmacro params: &rest needs a name \
11917             (rest marker at position 1, got 5)"
11918        );
11919    }
11920
11921    #[test]
11922    fn rest_param_missing_name_got_distinguishes_int_from_keyword_at_variant_slot() {
11923        // Pin the typed-shape bifurcation at the variant slot — `5`
11924        // (int atom at the post-`&rest` follower slot) and `:foo`
11925        // (keyword atom at the post-`&rest` follower slot) BOTH route
11926        // to `RestParamMissingName` on the `Some` arm, but the typed
11927        // `got.shape` slot distinguishes them structurally —
11928        // `SexpShape::Int` vs. `SexpShape::Keyword`. Sibling pin for
11929        // the same structural-shape-bifurcation property
11930        // `non_symbol_param_got_distinguishes_int_from_keyword_at_variant_slot`
11931        // pins on `NonSymbolParam` and
11932        // `defmacro_non_symbol_name_got_distinguishes_int_from_keyword_at_variant_slot`
11933        // pins on `DefmacroNonSymbolName`. A regression that erases
11934        // the typed shape (e.g., reverts to `got: Option<String>`)
11935        // would lose this distinction — tooling that wants to surface
11936        // "you wrote an int `5` where a rest-name was expected" vs.
11937        // "you wrote a keyword `:foo` where a rest-name was expected
11938        // (did you mean `foo`?)" would have to substring-grep the
11939        // `display` field, brittle.
11940        let err_int = LispError::RestParamMissingName {
11941            rest_position: 0,
11942            got: Some(SexpWitness::new(SexpShape::Int, "5")),
11943        };
11944        let err_kw = LispError::RestParamMissingName {
11945            rest_position: 0,
11946            got: Some(SexpWitness::new(SexpShape::Keyword, ":foo")),
11947        };
11948        let (int_shape, kw_shape) = (
11949            match &err_int {
11950                LispError::RestParamMissingName { got: Some(w), .. } => w.shape,
11951                _ => unreachable!(),
11952            },
11953            match &err_kw {
11954                LispError::RestParamMissingName { got: Some(w), .. } => w.shape,
11955                _ => unreachable!(),
11956            },
11957        );
11958        assert_ne!(
11959            int_shape, kw_shape,
11960            "Int and Keyword witnesses must remain structurally distinct at the variant slot",
11961        );
11962        assert_eq!(int_shape, SexpShape::Int);
11963        assert_eq!(kw_shape, SexpShape::Keyword);
11964    }
11965
11966    #[test]
11967    fn rest_param_missing_name_and_macro_def_gate_share_one_witness_primitive() {
11968        // Pin that ALL SIX Sexp-display-source `got` slots in the
11969        // substrate (`SpliceOutsideList`, `NonSymbolUnquoteTarget`,
11970        // `NonSymbolParam`, `DefmacroNonSymbolName`,
11971        // `DefmacroNonListParams`, `RestParamMissingName`) carry the
11972        // SAME typed `SexpWitness` primitive — the closed set of
11973        // "offending inner Sexp" identities is bound by ONE typed
11974        // primitive across SIX rejection surfaces: the template-gate's
11975        // `,X/,@X` pair, the defmacro-syntax-gate's `parse_params`
11976        // walker (BOTH non-symbol-param AND post-`&rest`-non-symbol-
11977        // follower rejection points), AND BOTH of the
11978        // defmacro-syntax-gate's outer `macro_def_from` rejection
11979        // points (name-symbol AND param-list). The `Option`-wrap on
11980        // `RestParamMissingName.got` is the bifurcation between
11981        // "missing entirely" and "present but malformed"; the typed
11982        // witness rides on the `Some` arm and is structurally identical
11983        // to the other five variants' got slots. A regression that
11984        // diverges the slot type on any one variant (e.g., re-collapses
11985        // `RestParamMissingName.got` to `Option<String>` while leaving
11986        // the others typed) fails-loudly here because the assignment
11987        // round-trips the witness across all six slot types. Sibling
11988        // pin to
11989        // `defmacro_non_list_params_and_name_gate_share_one_witness_primitive`
11990        // — extending the typed-identity unification contract from
11991        // five slots to six.
11992        let same_witness = SexpWitness::new(SexpShape::List, "(nested)");
11993        let rest_param_missing_name = LispError::RestParamMissingName {
11994            rest_position: 0,
11995            got: Some(same_witness.clone()),
11996        };
11997        let defmacro_non_list_params = LispError::DefmacroNonListParams {
11998            head: MacroDefHead::Defmacro,
11999            got: same_witness.clone(),
12000        };
12001        let defmacro_non_symbol_name = LispError::DefmacroNonSymbolName {
12002            head: MacroDefHead::Defmacro,
12003            got: same_witness.clone(),
12004        };
12005        let non_symbol_param = LispError::NonSymbolParam {
12006            position: 0,
12007            got: same_witness.clone(),
12008        };
12009        let non_symbol_target = LispError::NonSymbolUnquoteTarget {
12010            prefix: UnquoteForm::Unquote,
12011            got: same_witness.clone(),
12012        };
12013        let splice_outside = LispError::SpliceOutsideList {
12014            got: same_witness.clone(),
12015        };
12016        match (
12017            &rest_param_missing_name,
12018            &defmacro_non_list_params,
12019            &defmacro_non_symbol_name,
12020            &non_symbol_param,
12021            &non_symbol_target,
12022            &splice_outside,
12023        ) {
12024            (
12025                LispError::RestParamMissingName { got: Some(a), .. },
12026                LispError::DefmacroNonListParams { got: b, .. },
12027                LispError::DefmacroNonSymbolName { got: c, .. },
12028                LispError::NonSymbolParam { got: d, .. },
12029                LispError::NonSymbolUnquoteTarget { got: e, .. },
12030                LispError::SpliceOutsideList { got: f },
12031            ) => {
12032                assert_eq!(a.shape, b.shape);
12033                assert_eq!(b.shape, c.shape);
12034                assert_eq!(c.shape, d.shape);
12035                assert_eq!(d.shape, e.shape);
12036                assert_eq!(e.shape, f.shape);
12037                assert_eq!(a.display, b.display);
12038                assert_eq!(b.display, c.display);
12039                assert_eq!(c.display, d.display);
12040                assert_eq!(d.display, e.display);
12041                assert_eq!(e.display, f.display);
12042                assert_eq!(*a, same_witness);
12043                assert_eq!(*b, same_witness);
12044                assert_eq!(*c, same_witness);
12045                assert_eq!(*d, same_witness);
12046                assert_eq!(*e, same_witness);
12047                assert_eq!(*f, same_witness);
12048            }
12049            _ => unreachable!(),
12050        }
12051    }
12052
12053    #[test]
12054    fn missing_macro_arg_display_matches_legacy_compile_shape() {
12055        // The variant renders byte-for-byte the same string the legacy
12056        // `Compile { form: format!("call to {macro_name}"), message:
12057        // format!("missing required arg: {param}") }` shape produced, so
12058        // authoring tools (REPL, LSP, `tatara-check`) that substring-match
12059        // on the rendered diagnostic see no drift; tools that pattern-match
12060        // on the variant gain structural binding to `macro_name` and
12061        // `param`.
12062        let err = LispError::MissingMacroArg {
12063            macro_name: "wrap".into(),
12064            param: "b".into(),
12065        };
12066        assert_eq!(
12067            format!("{err}"),
12068            "compile error in call to wrap: missing required arg: b"
12069        );
12070    }
12071
12072    #[test]
12073    fn missing_macro_arg_display_carries_kebab_case_names_unchanged() {
12074        // Both `macro_name` and `param` round-trip through the variant's
12075        // Display unchanged. Pinning this contract means a regression that
12076        // camelCases or lowercases either side fails-loudly here.
12077        let err = LispError::MissingMacroArg {
12078            macro_name: "wrap-twice".into(),
12079            param: "notify-ref".into(),
12080        };
12081        assert_eq!(
12082            format!("{err}"),
12083            "compile error in call to wrap-twice: \
12084             missing required arg: notify-ref"
12085        );
12086    }
12087
12088    #[test]
12089    fn missing_macro_arg_display_preserves_legacy_substring_for_message_grep() {
12090        // Pin the legacy substring as a separate assertion so a regression
12091        // that drifts the wording (e.g., to "missing arg" or "no arg
12092        // provided") fails-loudly here even if the head clause changes
12093        // shape. The substring is what consumers downstream
12094        // (tatara-check, the REPL) substring-match on today.
12095        let err = LispError::MissingMacroArg {
12096            macro_name: "f".into(),
12097            param: "x".into(),
12098        };
12099        let msg = format!("{err}");
12100        assert!(
12101            msg.contains("missing required arg: x"),
12102            "expected legacy substring in message, got: {msg}"
12103        );
12104        assert!(
12105            msg.contains("call to f"),
12106            "expected call-to clause in message, got: {msg}"
12107        );
12108    }
12109
12110    #[test]
12111    fn non_symbol_param_display_carries_position_and_got() {
12112        // The variant renders both the failing position (0-based index
12113        // within the param list) AND the offending element via
12114        // `Sexp::Display` — both fields are first-class structural data,
12115        // not embedded substrings of `message`. A regression that drops
12116        // either field from the rendered diagnostic fails-loudly here.
12117        let err = LispError::NonSymbolParam {
12118            position: 1,
12119            got: SexpWitness::new(SexpShape::Int, "5"),
12120        };
12121        assert_eq!(
12122            format!("{err}"),
12123            "compile error in defmacro params: \
12124             expected symbol at position 1, got 5"
12125        );
12126    }
12127
12128    #[test]
12129    fn non_symbol_param_display_preserves_legacy_substring_for_message_grep() {
12130        // Pin the legacy substrings — `"defmacro params"` and `"expected
12131        // symbol"` — as separate assertions so a regression that drifts
12132        // either fragment fails-loudly here even if the appended position
12133        // / got clause changes shape. The substrings are what consumers
12134        // downstream substring-match on today; the prefix matches the
12135        // legacy `Compile { form: "defmacro params", message: "expected
12136        // symbol" }` byte-for-byte.
12137        let err = LispError::NonSymbolParam {
12138            position: 0,
12139            got: SexpWitness::new(SexpShape::List, "(nested)"),
12140        };
12141        let msg = format!("{err}");
12142        assert!(
12143            msg.contains("defmacro params"),
12144            "expected legacy form label in message, got: {msg}"
12145        );
12146        assert!(
12147            msg.contains("expected symbol"),
12148            "expected legacy substring in message, got: {msg}"
12149        );
12150    }
12151
12152    #[test]
12153    fn non_symbol_param_display_carries_keyword_got_unchanged() {
12154        // `Sexp::Display` for `Atom::Keyword(s)` writes `:s`; pin that
12155        // the variant's Display passes the keyword form through
12156        // unchanged so an LSP that surfaces "you wrote `:k` where a
12157        // symbol was expected" gains the literal value as data, no
12158        // re-parsing required.
12159        let err = LispError::NonSymbolParam {
12160            position: 2,
12161            got: SexpWitness::new(SexpShape::Keyword, ":k"),
12162        };
12163        assert_eq!(
12164            format!("{err}"),
12165            "compile error in defmacro params: \
12166             expected symbol at position 2, got :k"
12167        );
12168    }
12169
12170    #[test]
12171    fn rest_param_missing_name_display_with_got_renders_marker_position_and_got() {
12172        // `(defmacro f (a &rest 5) …)` — `&rest` at param-list position 1,
12173        // followed by `5` at position 2. The variant renders both the
12174        // marker's position AND the offending follower via `Sexp::Display`
12175        // — both are first-class structural data, not embedded substrings
12176        // of `message`. A regression that drops either field from the
12177        // rendered diagnostic fails-loudly here.
12178        let err = LispError::RestParamMissingName {
12179            rest_position: 1,
12180            got: Some(SexpWitness::new(SexpShape::Int, "5")),
12181        };
12182        assert_eq!(
12183            format!("{err}"),
12184            "compile error in defmacro params: &rest needs a name \
12185             (rest marker at position 1, got 5)"
12186        );
12187    }
12188
12189    #[test]
12190    fn rest_param_missing_name_display_without_got_renders_marker_position_only() {
12191        // `(defmacro f (a &rest))` — `&rest` at param-list position 1, no
12192        // follower at all. The variant renders the marker position and
12193        // names the absence structurally (`none provided`) instead of a
12194        // misleading empty / partial parenthetical. Sibling of how
12195        // `UnknownDomainKeyword` renders `(no domains registered)` for
12196        // the empty-registry case — the structural reason is named.
12197        let err = LispError::RestParamMissingName {
12198            rest_position: 1,
12199            got: None,
12200        };
12201        assert_eq!(
12202            format!("{err}"),
12203            "compile error in defmacro params: &rest needs a name \
12204             (rest marker at position 1, none provided)"
12205        );
12206    }
12207
12208    #[test]
12209    fn rest_param_missing_name_display_preserves_legacy_substring_for_message_grep() {
12210        // Pin the legacy substrings — `"defmacro params"` and `"&rest
12211        // needs a name"` — as separate assertions so a regression that
12212        // drifts either fragment fails-loudly here even if the appended
12213        // marker / got clause changes shape. The substrings are what
12214        // consumers downstream substring-match on today; the prefix
12215        // matches the legacy `Compile { form: "defmacro params",
12216        // message: "&rest needs a name" }` byte-for-byte.
12217        let err = LispError::RestParamMissingName {
12218            rest_position: 0,
12219            got: None,
12220        };
12221        let msg = format!("{err}");
12222        assert!(
12223            msg.contains("defmacro params"),
12224            "expected legacy form label in message, got: {msg}"
12225        );
12226        assert!(
12227            msg.contains("&rest needs a name"),
12228            "expected legacy substring in message, got: {msg}"
12229        );
12230    }
12231
12232    #[test]
12233    fn rest_param_missing_name_display_carries_keyword_got_unchanged() {
12234        // `Sexp::Display` for `Atom::Keyword(s)` writes `:s`; pin that the
12235        // variant's Display passes the keyword form through unchanged so
12236        // an LSP that surfaces "you wrote `:foo` where a rest-name was
12237        // expected" gains the literal keyword value as data, no
12238        // re-parsing required.
12239        let err = LispError::RestParamMissingName {
12240            rest_position: 2,
12241            got: Some(SexpWitness::new(SexpShape::Keyword, ":foo")),
12242        };
12243        assert_eq!(
12244            format!("{err}"),
12245            "compile error in defmacro params: &rest needs a name \
12246             (rest marker at position 2, got :foo)"
12247        );
12248    }
12249
12250    #[test]
12251    fn defmacro_arity_display_with_defmacro_head_renders_arity_and_legacy_template() {
12252        // The variant renders both the head keyword AND the actual
12253        // arity — both fields are first-class structural data, not
12254        // embedded substrings of `message`. The example template
12255        // `(defmacro name (params) body)` stays the literal `defmacro`
12256        // (not the head) — matching the legacy form's behavior so
12257        // authoring tools that substring-grep on the rendered
12258        // diagnostic see no drift. A regression that drops either
12259        // field from the rendered diagnostic fails-loudly here.
12260        let err = LispError::DefmacroArity {
12261            head: MacroDefHead::Defmacro,
12262            arity: 1,
12263        };
12264        assert_eq!(
12265            format!("{err}"),
12266            "compile error in defmacro: (defmacro name (params) body) required \
12267             (got 1 elements, need 4)"
12268        );
12269    }
12270
12271    #[test]
12272    fn defmacro_arity_display_carries_defpoint_template_head_unchanged() {
12273        // Pin that the head slot accepts every literal the call-site
12274        // matches! gate admits — `defpoint-template` is the second
12275        // head keyword `macro_def_from` recognizes. The example
12276        // template literal stays `(defmacro name (params) body)` even
12277        // for non-defmacro heads (matching the legacy behavior); the
12278        // prefix `compile error in defpoint-template:` carries the
12279        // actual head so an LSP that wants to point at "your
12280        // defpoint-template form is missing elements" gains the head
12281        // as data.
12282        let err = LispError::DefmacroArity {
12283            head: MacroDefHead::DefpointTemplate,
12284            arity: 2,
12285        };
12286        assert_eq!(
12287            format!("{err}"),
12288            "compile error in defpoint-template: \
12289             (defmacro name (params) body) required \
12290             (got 2 elements, need 4)"
12291        );
12292    }
12293
12294    #[test]
12295    fn defmacro_arity_display_carries_defcheck_head_unchanged() {
12296        // Sibling for the `defcheck` head; rounds out the three-head-
12297        // keyword coverage so the variant renders identically across
12298        // `defmacro` / `defpoint-template` / `defcheck` (modulo the
12299        // head literal in the prefix).
12300        let err = LispError::DefmacroArity {
12301            head: MacroDefHead::Defcheck,
12302            arity: 3,
12303        };
12304        assert_eq!(
12305            format!("{err}"),
12306            "compile error in defcheck: (defmacro name (params) body) required \
12307             (got 3 elements, need 4)"
12308        );
12309    }
12310
12311    #[test]
12312    fn defmacro_arity_display_preserves_legacy_substring_for_message_grep() {
12313        // Pin the legacy substring — `"(defmacro name (params) body)
12314        // required"` — as a separate assertion so a regression that
12315        // drifts the example template fails-loudly here even if the
12316        // appended `got X elements, need 4` clause changes shape. The
12317        // substring is what consumers downstream substring-match on
12318        // today; the prefix matches the legacy `Compile { form:
12319        // head.to_string(), message: "(defmacro name (params) body)
12320        // required" }` byte-for-byte.
12321        let err = LispError::DefmacroArity {
12322            head: MacroDefHead::Defmacro,
12323            arity: 0,
12324        };
12325        let msg = format!("{err}");
12326        assert!(
12327            msg.contains("(defmacro name (params) body) required"),
12328            "expected legacy template substring in message, got: {msg}"
12329        );
12330        assert!(
12331            msg.contains("compile error in defmacro:"),
12332            "expected legacy form-label prefix in message, got: {msg}"
12333        );
12334    }
12335
12336    #[test]
12337    fn defmacro_non_symbol_name_display_with_int_got_renders_legacy_prefix_and_got() {
12338        // `(defmacro 5 () body)` — list[1] is `5`, not a symbol. The
12339        // variant renders both the head keyword AND the offending
12340        // `Sexp::Display` projection — both fields are first-class
12341        // structural data, not embedded substrings of `message`. The
12342        // prefix `compile error in defmacro: expected name symbol`
12343        // matches the legacy `Compile { form: "defmacro", message:
12344        // "expected name symbol" }` byte-for-byte; the structural
12345        // detail (`, got 5`) is appended. A regression that drops
12346        // either field from the rendered diagnostic fails-loudly here.
12347        let err = LispError::DefmacroNonSymbolName {
12348            head: MacroDefHead::Defmacro,
12349            got: SexpWitness::new(SexpShape::Int, "5"),
12350        };
12351        assert_eq!(
12352            format!("{err}"),
12353            "compile error in defmacro: expected name symbol, got 5"
12354        );
12355    }
12356
12357    #[test]
12358    fn defmacro_non_symbol_name_display_carries_defpoint_template_head_unchanged() {
12359        // Pin that the head slot accepts every literal the call-site
12360        // matches! gate admits — `defpoint-template` is the second
12361        // head keyword `macro_def_from` recognizes. The prefix
12362        // `compile error in defpoint-template:` carries the actual
12363        // head so an LSP that wants to point at "your defpoint-
12364        // template form's name slot isn't a symbol" gains the head
12365        // as data.
12366        let err = LispError::DefmacroNonSymbolName {
12367            head: MacroDefHead::DefpointTemplate,
12368            got: SexpWitness::new(SexpShape::Keyword, ":foo"),
12369        };
12370        assert_eq!(
12371            format!("{err}"),
12372            "compile error in defpoint-template: expected name symbol, got :foo"
12373        );
12374    }
12375
12376    #[test]
12377    fn defmacro_non_symbol_name_display_carries_defcheck_head_unchanged() {
12378        // Sibling for the `defcheck` head; rounds out the three-head-
12379        // keyword coverage so the variant renders identically across
12380        // `defmacro` / `defpoint-template` / `defcheck` (modulo the
12381        // head literal in the prefix).
12382        let err = LispError::DefmacroNonSymbolName {
12383            head: MacroDefHead::Defcheck,
12384            got: SexpWitness::new(SexpShape::List, "(nested)"),
12385        };
12386        assert_eq!(
12387            format!("{err}"),
12388            "compile error in defcheck: expected name symbol, got (nested)"
12389        );
12390    }
12391
12392    #[test]
12393    fn defmacro_non_symbol_name_display_carries_string_got_unchanged() {
12394        // `Sexp::Display` for `Atom::String(s)` writes `"s"` (with
12395        // quotes); pin that the variant's Display passes the string
12396        // form through unchanged so an LSP that surfaces "you wrote
12397        // `\"name\"` where a name symbol was expected" gains the
12398        // literal value as data, no re-parsing required.
12399        let err = LispError::DefmacroNonSymbolName {
12400            head: MacroDefHead::Defmacro,
12401            got: SexpWitness::new(SexpShape::String, "\"name\""),
12402        };
12403        assert_eq!(
12404            format!("{err}"),
12405            "compile error in defmacro: expected name symbol, got \"name\""
12406        );
12407    }
12408
12409    #[test]
12410    fn defmacro_non_symbol_name_display_preserves_legacy_substring_for_message_grep() {
12411        // Pin the legacy substring — `"expected name symbol"` — as a
12412        // separate assertion so a regression that drifts the wording
12413        // (e.g., to "expected symbol" or "name must be a symbol")
12414        // fails-loudly here even if the appended `, got X` clause
12415        // changes shape. The substring is what consumers downstream
12416        // (tatara-check, the REPL) substring-match on today; the
12417        // prefix matches the legacy `Compile { form: head.to_string(),
12418        // message: "expected name symbol" }` byte-for-byte.
12419        let err = LispError::DefmacroNonSymbolName {
12420            head: MacroDefHead::Defmacro,
12421            got: SexpWitness::new(SexpShape::Int, "5"),
12422        };
12423        let msg = format!("{err}");
12424        assert!(
12425            msg.contains("expected name symbol"),
12426            "expected legacy substring in message, got: {msg}"
12427        );
12428        assert!(
12429            msg.contains("compile error in defmacro:"),
12430            "expected legacy form-label prefix in message, got: {msg}"
12431        );
12432    }
12433
12434    #[test]
12435    fn defmacro_non_list_params_display_with_symbol_got_renders_legacy_prefix_and_got() {
12436        // `(defmacro f x body)` — list[2] is the symbol `x`, not a
12437        // list. The variant renders both the head keyword AND the
12438        // offending `Sexp::Display` projection — both fields are
12439        // first-class structural data, not embedded substrings of
12440        // `message`. The prefix `compile error in defmacro: expected
12441        // param list` matches the legacy `Compile { form: "defmacro",
12442        // message: "expected param list" }` byte-for-byte; the
12443        // structural detail (`, got x`) is appended. A regression that
12444        // drops either field from the rendered diagnostic fails-loudly
12445        // here.
12446        let err = LispError::DefmacroNonListParams {
12447            head: MacroDefHead::Defmacro,
12448            got: SexpWitness::new(SexpShape::Symbol, "x"),
12449        };
12450        assert_eq!(
12451            format!("{err}"),
12452            "compile error in defmacro: expected param list, got x"
12453        );
12454    }
12455
12456    #[test]
12457    fn defmacro_non_list_params_display_carries_defpoint_template_head_unchanged() {
12458        // Pin that the head slot accepts every literal the call-site
12459        // matches! gate admits — `defpoint-template` is the second
12460        // head keyword `macro_def_from` recognizes. The prefix
12461        // `compile error in defpoint-template:` carries the actual
12462        // head so an LSP that wants to point at "your defpoint-
12463        // template form's param-list slot isn't a list" gains the
12464        // head as data.
12465        let err = LispError::DefmacroNonListParams {
12466            head: MacroDefHead::DefpointTemplate,
12467            got: SexpWitness::new(SexpShape::Int, "5"),
12468        };
12469        assert_eq!(
12470            format!("{err}"),
12471            "compile error in defpoint-template: expected param list, got 5"
12472        );
12473    }
12474
12475    #[test]
12476    fn defmacro_non_list_params_display_carries_defcheck_head_unchanged() {
12477        // Sibling for the `defcheck` head; rounds out the three-head-
12478        // keyword coverage so the variant renders identically across
12479        // `defmacro` / `defpoint-template` / `defcheck` (modulo the
12480        // head literal in the prefix).
12481        let err = LispError::DefmacroNonListParams {
12482            head: MacroDefHead::Defcheck,
12483            got: SexpWitness::new(SexpShape::Keyword, ":k"),
12484        };
12485        assert_eq!(
12486            format!("{err}"),
12487            "compile error in defcheck: expected param list, got :k"
12488        );
12489    }
12490
12491    #[test]
12492    fn defmacro_non_list_params_display_carries_string_got_unchanged() {
12493        // `Sexp::Display` for `Atom::String(s)` writes `"s"` (with
12494        // quotes); pin that the variant's Display passes the string
12495        // form through unchanged so an LSP that surfaces "you wrote
12496        // `\"params\"` where a param list was expected" gains the
12497        // literal value as data, no re-parsing required.
12498        let err = LispError::DefmacroNonListParams {
12499            head: MacroDefHead::Defmacro,
12500            got: SexpWitness::new(SexpShape::String, "\"params\""),
12501        };
12502        assert_eq!(
12503            format!("{err}"),
12504            "compile error in defmacro: expected param list, got \"params\""
12505        );
12506    }
12507
12508    #[test]
12509    fn defmacro_non_list_params_display_preserves_legacy_substring_for_message_grep() {
12510        // Pin the legacy substring — `"expected param list"` — as a
12511        // separate assertion so a regression that drifts the wording
12512        // (e.g., to "expected list" or "params must be a list")
12513        // fails-loudly here even if the appended `, got X` clause
12514        // changes shape. The substring is what consumers downstream
12515        // (tatara-check, the REPL) substring-match on today; the
12516        // prefix matches the legacy `Compile { form: head.to_string(),
12517        // message: "expected param list" }` byte-for-byte.
12518        let err = LispError::DefmacroNonListParams {
12519            head: MacroDefHead::Defmacro,
12520            got: SexpWitness::new(SexpShape::Symbol, "x"),
12521        };
12522        let msg = format!("{err}");
12523        assert!(
12524            msg.contains("expected param list"),
12525            "expected legacy substring in message, got: {msg}"
12526        );
12527        assert!(
12528            msg.contains("compile error in defmacro:"),
12529            "expected legacy form-label prefix in message, got: {msg}"
12530        );
12531    }
12532
12533    #[test]
12534    fn unbound_template_var_display_preserves_splice_prefix_in_hint() {
12535        // Splice marker rides through both the form and the suggestion; the
12536        // operator never has to translate `,` ↔ `,@` mentally.
12537        let err = LispError::UnboundTemplateVar {
12538            prefix: UnquoteForm::Splice,
12539            name: "rsts".into(),
12540            hint: Some("rest".into()),
12541        };
12542        assert_eq!(
12543            format!("{err}"),
12544            "compile error in ,@rsts: unbound; did you mean ,@rest?"
12545        );
12546    }
12547
12548    #[test]
12549    fn named_form_missing_name_display_renders_legacy_compile_shape() {
12550        // `(defpoint)` — list.len() == 1 (just the keyword, no NAME). The
12551        // variant renders byte-for-byte the same string the legacy
12552        // `Compile { form: "defpoint", message: "expected (defpoint NAME …)"
12553        // }` shape produced, so authoring tools (REPL, LSP, `tatara-check`)
12554        // that substring-match on the rendered diagnostic see no drift;
12555        // tools that pattern-match on the variant gain structural binding
12556        // to `keyword`.
12557        let err = LispError::NamedFormMissingName {
12558            keyword: "defpoint",
12559        };
12560        assert_eq!(
12561            format!("{err}"),
12562            "compile error in defpoint: expected (defpoint NAME …)"
12563        );
12564    }
12565
12566    #[test]
12567    fn named_form_missing_name_display_carries_defalertpolicy_keyword_unchanged() {
12568        // Pin path-uniformity across distinct keywords — every
12569        // `compile_named` caller funnels through `NamedFormMissingName`
12570        // with its own `T::KEYWORD`, so the variant's `keyword` slot
12571        // must round-trip every literal the derive macro accepts. A
12572        // regression that drops or rewrites the keyword (e.g.,
12573        // lowercasing, stripping the `def` prefix) fails-loudly here.
12574        let err = LispError::NamedFormMissingName {
12575            keyword: "defalertpolicy",
12576        };
12577        assert_eq!(
12578            format!("{err}"),
12579            "compile error in defalertpolicy: expected (defalertpolicy NAME …)"
12580        );
12581    }
12582
12583    #[test]
12584    fn named_form_missing_name_display_carries_kebab_case_keyword_unchanged() {
12585        // Kebab-cased domain keywords (`defprocess-spec`, `defalert-policy`)
12586        // round-trip through both occurrences of the keyword in the rendered
12587        // diagnostic — the prefix `compile error in {keyword}:` AND the
12588        // example template `(... NAME …)`. Pinning this contract means a
12589        // regression that camelCases either occurrence fails-loudly here.
12590        let err = LispError::NamedFormMissingName {
12591            keyword: "defprocess-spec",
12592        };
12593        assert_eq!(
12594            format!("{err}"),
12595            "compile error in defprocess-spec: expected (defprocess-spec NAME …)"
12596        );
12597    }
12598
12599    #[test]
12600    fn named_form_missing_name_display_preserves_unicode_ellipsis_byte_for_byte() {
12601        // The legacy `format!("expected ({} NAME …)", T::KEYWORD)` shape used
12602        // the Unicode horizontal-ellipsis character (U+2026), not the ASCII
12603        // three-dot sequence `...`. Pin the codepoint exactly so a regression
12604        // that replaces `…` with `...` fails-loudly here — consumers
12605        // downstream that substring-match on `"…"` would silently miss every
12606        // future occurrence otherwise.
12607        let err = LispError::NamedFormMissingName {
12608            keyword: "defmonitor",
12609        };
12610        let msg = format!("{err}");
12611        assert!(
12612            msg.contains('\u{2026}'),
12613            "expected Unicode horizontal-ellipsis (U+2026) in message, got: {msg}"
12614        );
12615        assert!(
12616            !msg.contains("..."),
12617            "expected no ASCII three-dot sequence in message, got: {msg}"
12618        );
12619    }
12620
12621    #[test]
12622    fn named_form_missing_name_display_preserves_legacy_substring_for_message_grep() {
12623        // Pin the legacy substring — `"expected ({keyword} NAME …)"` — as a
12624        // separate assertion so a regression that drifts the wording (e.g.,
12625        // to "expected NAME after keyword" or "missing positional name")
12626        // fails-loudly here. The substring is what consumers downstream
12627        // (`tatara-check`, the REPL) substring-match on today; the prefix
12628        // matches the legacy `Compile { form: T::KEYWORD.to_string(),
12629        // message: format!("expected ({} NAME …)", T::KEYWORD) }`
12630        // byte-for-byte.
12631        let err = LispError::NamedFormMissingName {
12632            keyword: "defmonitor",
12633        };
12634        let msg = format!("{err}");
12635        assert!(
12636            msg.contains("expected (defmonitor NAME"),
12637            "expected legacy form-label prefix in message, got: {msg}"
12638        );
12639        assert!(
12640            msg.contains("compile error in defmonitor:"),
12641            "expected legacy form-label prefix in message, got: {msg}"
12642        );
12643    }
12644
12645    #[test]
12646    fn named_form_non_symbol_name_display_renders_legacy_prefix_with_int_got() {
12647        // `(defpoint 5 …)` — list[1] is the int `5`. The variant renders
12648        // the legacy prefix `compile error in {keyword}: positional NAME
12649        // must be a symbol or string` byte-for-byte AND appends the
12650        // structural detail `(got int)` parenthetically — same posture as
12651        // how `MissingHeadSymbol` appends `(got 5)` and how
12652        // `RestParamMissingName` appends `(rest marker at position N,
12653        // got X)`. The `got` slot is the typed `SexpShape` enum sourced
12654        // from `sexp_shape`; pin the Int-arm rendering (via
12655        // `SexpShape::Display` to the canonical `"int"` literal) as the
12656        // canonical example.
12657        let err = LispError::NamedFormNonSymbolName {
12658            keyword: "defpoint",
12659            got: SexpShape::Int,
12660        };
12661        assert_eq!(
12662            format!("{err}"),
12663            "compile error in defpoint: positional NAME must be a symbol or string (got int)"
12664        );
12665    }
12666
12667    #[test]
12668    fn named_form_non_symbol_name_display_carries_keyword_got_unchanged() {
12669        // `(defpoint :foo …)` — list[1] is a `:foo` keyword. Pin
12670        // path-uniformity across distinct `SexpShape` variants: the
12671        // `got` slot is `SexpShape::Keyword` (the typed projection from
12672        // `sexp_shape(Sexp::Atom(Atom::Keyword(_)))`), threaded into
12673        // the parenthetical via `SexpShape::Display` -> "keyword".
12674        let err = LispError::NamedFormNonSymbolName {
12675            keyword: "defpoint",
12676            got: SexpShape::Keyword,
12677        };
12678        assert_eq!(
12679            format!("{err}"),
12680            "compile error in defpoint: positional NAME must be a symbol or string (got keyword)"
12681        );
12682    }
12683
12684    #[test]
12685    fn named_form_non_symbol_name_display_carries_list_got_unchanged() {
12686        // `(defpoint (nested) …)` — list[1] is a nested list. Pin the
12687        // `SexpShape::List` variant round-trips into the variant's
12688        // `got` slot unchanged so an LSP that surfaces "you wrote a
12689        // nested list where a NAME symbol was expected" gains the
12690        // structural shape as data, no re-parsing required.
12691        let err = LispError::NamedFormNonSymbolName {
12692            keyword: "defalertpolicy",
12693            got: SexpShape::List,
12694        };
12695        assert_eq!(
12696            format!("{err}"),
12697            "compile error in defalertpolicy: positional NAME must be a symbol or string (got list)"
12698        );
12699    }
12700
12701    #[test]
12702    fn named_form_non_symbol_name_display_carries_kebab_case_keyword_unchanged() {
12703        // Kebab-cased domain keywords (`defprocess-spec`, `defalert-policy`)
12704        // round-trip through the rendered diagnostic's `compile error in
12705        // {keyword}:` prefix unchanged. A regression that camelCases the
12706        // keyword fails-loudly here.
12707        let err = LispError::NamedFormNonSymbolName {
12708            keyword: "defprocess-spec",
12709            got: SexpShape::Int,
12710        };
12711        assert_eq!(
12712            format!("{err}"),
12713            "compile error in defprocess-spec: positional NAME must be a symbol or string (got int)"
12714        );
12715    }
12716
12717    #[test]
12718    fn named_form_non_symbol_name_display_preserves_legacy_substring_for_message_grep() {
12719        // Pin the legacy substring — `"positional NAME must be a symbol
12720        // or string"` — as a separate assertion so a regression that
12721        // drifts the wording (e.g., to "NAME must be a symbol", "NAME
12722        // slot wrong-typed") fails-loudly here even if the appended
12723        // parenthetical changes shape. The substring is what consumers
12724        // downstream (`tatara-check`, the REPL) substring-match on
12725        // today; the prefix matches the legacy `Compile { form:
12726        // T::KEYWORD.to_string(), message: "positional NAME must be a
12727        // symbol or string" }` byte-for-byte.
12728        let err = LispError::NamedFormNonSymbolName {
12729            keyword: "defmonitor",
12730            got: SexpShape::Int,
12731        };
12732        let msg = format!("{err}");
12733        assert!(
12734            msg.contains("positional NAME must be a symbol or string"),
12735            "expected legacy substring in message, got: {msg}"
12736        );
12737        assert!(
12738            msg.contains("compile error in defmonitor:"),
12739            "expected legacy form-label prefix in message, got: {msg}"
12740        );
12741    }
12742
12743    // ── RewriterNonList: typed-exit structural-variant lift ─────────
12744    //
12745    // `rewriter_non_list_err::<T>` (the typed-exit gate of
12746    // `rewrite_typed::<T>`'s round-trip) was promoted from the
12747    // `LispError::Compile`-shaped triple to the structural
12748    // `LispError::RewriterNonList { keyword, got }` variant. The
12749    // tests below pin: (a) Display matches the legacy `"compile error
12750    // in {keyword}: rewriter must return a list; got {got}"` shape
12751    // byte-for-byte across representative `got` renderings (int,
12752    // symbol, nil rendered as "()", quoted form); (b) the legacy
12753    // substring `"rewriter must return a list; got "` and the legacy
12754    // prefix `"compile error in {keyword}:"` both survive the lift
12755    // unchanged for substring-grep consumers; (c) kebab-case keywords
12756    // thread unchanged; (d) `position()` is `None` today (lands as
12757    // one branch when source spans arrive).
12758
12759    #[test]
12760    fn rewriter_non_list_display_renders_legacy_shape_with_int_got() {
12761        // `Sexp::int(42)` projects to `Sexp::Display = "42"`. The variant
12762        // renders the legacy `"compile error in {keyword}: rewriter must
12763        // return a list; got {got}"` shape byte-for-byte — same wording
12764        // as the pre-lift `Compile`-shaped triple.
12765        let err = LispError::RewriterNonList {
12766            keyword: "defmonitor",
12767            got: SexpWitness::new(SexpShape::Int, "42"),
12768        };
12769        assert_eq!(
12770            format!("{err}"),
12771            "compile error in defmonitor: rewriter must return a list; got 42"
12772        );
12773    }
12774
12775    #[test]
12776    fn rewriter_non_list_display_carries_symbol_got_unchanged() {
12777        // `Sexp::symbol("not-a-list")` projects to `"not-a-list"`. Pin
12778        // path-uniformity across distinct `Sexp::Display` outputs: the
12779        // typed `got` slot threads the value-rendering into the
12780        // diagnostic unchanged via `SexpWitness::Display` (which writes
12781        // only the `display` field).
12782        let err = LispError::RewriterNonList {
12783            keyword: "defmonitor",
12784            got: SexpWitness::new(SexpShape::Symbol, "not-a-list"),
12785        };
12786        assert_eq!(
12787            format!("{err}"),
12788            "compile error in defmonitor: rewriter must return a list; got not-a-list"
12789        );
12790    }
12791
12792    #[test]
12793    fn rewriter_non_list_display_carries_nil_got_as_paren_paren() {
12794        // `Sexp::Nil` projects to `"()"` per the `Sexp::Display`
12795        // contract — NOT `"nil"`. Pin the contract so a regression
12796        // that drifts `Sexp::Nil`'s Display fails-loudly here even
12797        // before reaching the rewriter end-to-end test.
12798        let err = LispError::RewriterNonList {
12799            keyword: "defmonitor",
12800            got: SexpWitness::new(SexpShape::Nil, "()"),
12801        };
12802        assert_eq!(
12803            format!("{err}"),
12804            "compile error in defmonitor: rewriter must return a list; got ()"
12805        );
12806    }
12807
12808    #[test]
12809    fn rewriter_non_list_display_carries_kebab_case_keyword_unchanged() {
12810        // Kebab-cased domain keywords (`defprocess-spec`,
12811        // `defalert-policy`) round-trip through the rendered
12812        // diagnostic's `compile error in {keyword}:` prefix unchanged.
12813        // A regression that camelCases the keyword fails-loudly here.
12814        let err = LispError::RewriterNonList {
12815            keyword: "defprocess-spec",
12816            got: SexpWitness::new(SexpShape::Int, "7"),
12817        };
12818        assert_eq!(
12819            format!("{err}"),
12820            "compile error in defprocess-spec: rewriter must return a list; got 7"
12821        );
12822    }
12823
12824    #[test]
12825    fn rewriter_non_list_display_preserves_legacy_substring_for_message_grep() {
12826        // Pin the legacy substring — `"rewriter must return a list;
12827        // got "` — as a separate assertion so a regression that drifts
12828        // the wording (e.g., to "rewriter returned non-list", "expected
12829        // list output") fails-loudly here. The substring is what
12830        // consumers downstream (`tatara-check`, the REPL) substring-
12831        // match on; the prefix matches the legacy `Compile { form:
12832        // T::KEYWORD.to_string(), message: format!("rewriter must
12833        // return a list; got {other}") }` byte-for-byte.
12834        let err = LispError::RewriterNonList {
12835            keyword: "defmonitor",
12836            got: SexpWitness::new(SexpShape::Int, "42"),
12837        };
12838        let msg = format!("{err}");
12839        assert!(
12840            msg.contains("rewriter must return a list; got "),
12841            "expected legacy substring in message, got: {msg}"
12842        );
12843        assert!(
12844            msg.contains("compile error in defmonitor:"),
12845            "expected legacy form-label prefix in message, got: {msg}"
12846        );
12847    }
12848
12849    #[test]
12850    fn rewriter_non_list_position_is_none_today() {
12851        // Until `Sexp` carries source positions, the variant's
12852        // `position()` returns `None`. Pin the contract: a future run
12853        // that adds `pos: Option<usize>` lands inside `SexpWitness` in
12854        // ONE place and `rewrite_typed`'s rewriter-output rejection
12855        // picks up the span automatically because it routes through
12856        // one helper (`rewriter_non_list_err`).
12857        let err = LispError::RewriterNonList {
12858            keyword: "defmonitor",
12859            got: SexpWitness::new(SexpShape::Int, "42"),
12860        };
12861        assert_eq!(err.position(), None);
12862    }
12863
12864    #[test]
12865    fn rewriter_non_list_got_carries_typed_witness_through_variant_slot() {
12866        // Pin the structural binding on `LispError::RewriterNonList.got`
12867        // — a regression that re-introduces a `String`-shaped got slot
12868        // (collapsing the typed witness back into a free-form literal at
12869        // the typed-EXIT boundary) fails-loudly here. After this lift the
12870        // variant's typed slot is the joint `SexpWitness` identity — the
12871        // SAME primitive the SEVEN typed-ENTRY-side `got` slots already
12872        // carry (`SpliceOutsideList`, `NonSymbolUnquoteTarget`,
12873        // `NonSymbolParam`, `DefmacroNonSymbolName`,
12874        // `DefmacroNonListParams`, `RestParamMissingName`,
12875        // `MissingHeadSymbol`). This is the EIGHTH consumer and the FIRST
12876        // on the typed-EXIT boundary; the typed-identity unification
12877        // contract is now closed across BOTH boundaries of the typed-IR
12878        // algebra. The Display projection through `SexpWitness::Display`
12879        // writes only the `display` field so the rendered `got {display}`
12880        // suffix is byte-for-byte identical to the legacy `got: String`
12881        // shape.
12882        let err = LispError::RewriterNonList {
12883            keyword: "defmonitor",
12884            got: SexpWitness::new(SexpShape::Int, "42"),
12885        };
12886        match &err {
12887            LispError::RewriterNonList { keyword, got } => {
12888                assert_eq!(*keyword, "defmonitor");
12889                assert_eq!(got.shape, SexpShape::Int);
12890                assert_eq!(got.display, "42");
12891            }
12892            other => panic!("expected RewriterNonList, got {other:?}"),
12893        }
12894        assert_eq!(
12895            format!("{err}"),
12896            "compile error in defmonitor: rewriter must return a list; got 42"
12897        );
12898    }
12899
12900    #[test]
12901    fn rewriter_non_list_got_distinguishes_int_from_keyword_at_variant_slot() {
12902        // Pin the typed-shape bifurcation at the variant slot's `got`
12903        // slot — `42` (int) and `:foo` (keyword) BOTH route to
12904        // `RewriterNonList` (the rewriter returned a non-list typed-exit
12905        // rejection), but the typed `got.shape` slot distinguishes them
12906        // structurally as `SexpShape::Int` vs. `SexpShape::Keyword`.
12907        // Sibling pin for the same structural-shape-bifurcation property
12908        // `splice_outside_list_got_distinguishes_symbol_from_list_at_variant_slot`
12909        // pins on the typed-ENTRY-side `SpliceOutsideList` variant — the
12910        // same posture applied to the typed-EXIT-side rejection variant.
12911        // A regression that erases the typed shape (e.g., reverts to
12912        // `got: String`) would lose this distinction — tooling that
12913        // wants to surface "your rewriter returned the int `42` where a
12914        // kwargs list was expected" vs. "your rewriter returned the
12915        // keyword `:foo` where a kwargs list was expected" would have to
12916        // substring-grep the `display` field, brittle.
12917        let err_int = LispError::RewriterNonList {
12918            keyword: "defmonitor",
12919            got: SexpWitness::new(SexpShape::Int, "42"),
12920        };
12921        let err_kw = LispError::RewriterNonList {
12922            keyword: "defmonitor",
12923            got: SexpWitness::new(SexpShape::Keyword, ":foo"),
12924        };
12925        let (int_shape, kw_shape) = (
12926            match &err_int {
12927                LispError::RewriterNonList { got, .. } => got.shape,
12928                _ => unreachable!(),
12929            },
12930            match &err_kw {
12931                LispError::RewriterNonList { got, .. } => got.shape,
12932                _ => unreachable!(),
12933            },
12934        );
12935        assert_ne!(
12936            int_shape, kw_shape,
12937            "Int and Keyword witnesses must remain structurally distinct at the variant slot",
12938        );
12939        assert_eq!(int_shape, SexpShape::Int);
12940        assert_eq!(kw_shape, SexpShape::Keyword);
12941    }
12942
12943    #[test]
12944    fn rewriter_non_list_and_typed_entry_gates_share_one_witness_primitive() {
12945        // Pin that ALL EIGHT Sexp-display-source `got` slots in the
12946        // substrate carry the SAME typed `SexpWitness` primitive — the
12947        // closed set of "offending inner Sexp" identities is bound by
12948        // ONE typed primitive across EIGHT rejection surfaces spanning
12949        // BOTH boundaries of the typed-IR algebra: the typed-ENTRY side
12950        // (seven slots — the template-gate's `,X/,@X` pair, the
12951        // defmacro-syntax-gate's `parse_params` walker (BOTH
12952        // non-symbol-param AND post-`&rest`-non-symbol-follower rejection
12953        // points), BOTH of the defmacro-syntax-gate's outer
12954        // `macro_def_from` rejection points (name-symbol AND
12955        // param-list), AND the outer `compile_from_sexp` typed-entry
12956        // gate's non-symbol-head rejection point) AND the typed-EXIT
12957        // side (ONE slot — `rewrite_typed`'s `Sexp::List`-contract gate
12958        // for the rewriter's output). With this lift EVERY
12959        // `Sexp::Display`-source `got` slot in the substrate is
12960        // structurally unified end-to-end across BOTH typed boundaries.
12961        // The `Option`-wrap on `MissingHeadSymbol.got` and
12962        // `RestParamMissingName.got` is the bifurcation between "missing
12963        // entirely" and "present but malformed"; the typed witness
12964        // rides on the `Some` arm and is structurally identical to the
12965        // other six variants' got slots. A regression that diverges the
12966        // slot type on any one variant (e.g., re-collapses
12967        // `RewriterNonList.got` to `String` while leaving the others
12968        // typed) fails-loudly here because the assignment round-trips
12969        // the witness across all eight slot types. Sibling pin to
12970        // `missing_head_symbol_and_rest_param_gate_share_one_witness_primitive`
12971        // — extending the typed-identity unification contract from
12972        // seven slots (typed-ENTRY only) to eight slots (typed-ENTRY +
12973        // typed-EXIT), CLOSING the contract across BOTH boundaries of
12974        // the typed-IR algebra (THEORY.md §II.1 invariant 1 +
12975        // invariant 3).
12976        let same_witness = SexpWitness::new(SexpShape::Int, "42");
12977        let rewriter_non_list = LispError::RewriterNonList {
12978            keyword: "defmonitor",
12979            got: same_witness.clone(),
12980        };
12981        let missing_head = LispError::MissingHeadSymbol {
12982            keyword: "defmonitor",
12983            got: Some(same_witness.clone()),
12984        };
12985        let rest_param_missing_name = LispError::RestParamMissingName {
12986            rest_position: 0,
12987            got: Some(same_witness.clone()),
12988        };
12989        let defmacro_non_list_params = LispError::DefmacroNonListParams {
12990            head: MacroDefHead::Defmacro,
12991            got: same_witness.clone(),
12992        };
12993        let defmacro_non_symbol_name = LispError::DefmacroNonSymbolName {
12994            head: MacroDefHead::Defmacro,
12995            got: same_witness.clone(),
12996        };
12997        let non_symbol_param = LispError::NonSymbolParam {
12998            position: 0,
12999            got: same_witness.clone(),
13000        };
13001        let non_symbol_target = LispError::NonSymbolUnquoteTarget {
13002            prefix: UnquoteForm::Unquote,
13003            got: same_witness.clone(),
13004        };
13005        let splice_outside = LispError::SpliceOutsideList {
13006            got: same_witness.clone(),
13007        };
13008        match (
13009            &rewriter_non_list,
13010            &missing_head,
13011            &rest_param_missing_name,
13012            &defmacro_non_list_params,
13013            &defmacro_non_symbol_name,
13014            &non_symbol_param,
13015            &non_symbol_target,
13016            &splice_outside,
13017        ) {
13018            (
13019                LispError::RewriterNonList { got: a, .. },
13020                LispError::MissingHeadSymbol { got: Some(b), .. },
13021                LispError::RestParamMissingName { got: Some(c), .. },
13022                LispError::DefmacroNonListParams { got: d, .. },
13023                LispError::DefmacroNonSymbolName { got: e, .. },
13024                LispError::NonSymbolParam { got: f, .. },
13025                LispError::NonSymbolUnquoteTarget { got: g, .. },
13026                LispError::SpliceOutsideList { got: h },
13027            ) => {
13028                assert_eq!(a.shape, b.shape);
13029                assert_eq!(b.shape, c.shape);
13030                assert_eq!(c.shape, d.shape);
13031                assert_eq!(d.shape, e.shape);
13032                assert_eq!(e.shape, f.shape);
13033                assert_eq!(f.shape, g.shape);
13034                assert_eq!(g.shape, h.shape);
13035                assert_eq!(a.display, b.display);
13036                assert_eq!(b.display, c.display);
13037                assert_eq!(c.display, d.display);
13038                assert_eq!(d.display, e.display);
13039                assert_eq!(e.display, f.display);
13040                assert_eq!(f.display, g.display);
13041                assert_eq!(g.display, h.display);
13042                assert_eq!(*a, same_witness);
13043                assert_eq!(*b, same_witness);
13044                assert_eq!(*c, same_witness);
13045                assert_eq!(*d, same_witness);
13046                assert_eq!(*e, same_witness);
13047                assert_eq!(*f, same_witness);
13048                assert_eq!(*g, same_witness);
13049                assert_eq!(*h, same_witness);
13050            }
13051            _ => unreachable!(),
13052        }
13053    }
13054
13055    // ── DomainSerialize: typed-exit `to_value` structural-variant lift ──
13056    //
13057    // `serialize_to_json_err::<T>` (the `to_value`-side gate shared
13058    // between `register::<T>`'s registry-dispatch closure and
13059    // `rewrite_typed::<T>`'s round-trip prelude) was promoted from the
13060    // `LispError::Compile`-shaped triple to the structural
13061    // `LispError::DomainSerialize { keyword, message }` variant. The
13062    // tests below pin: (a) Display matches the legacy `"compile error
13063    // in {keyword}: serialize: {message}"` shape byte-for-byte across
13064    // representative `message` renderings (serde_json's stock
13065    // diagnostic, hand-crafted message); (b) the legacy substring
13066    // `"serialize: "` and the legacy prefix `"compile error in
13067    // {keyword}:"` both survive the lift unchanged for substring-grep
13068    // consumers; (c) kebab-case keywords thread through unchanged.
13069    // The `position()` floor is pinned in the main
13070    // `position_is_none_for_non_positional_variants` block above.
13071
13072    #[test]
13073    fn domain_serialize_display_renders_legacy_shape_with_short_message() {
13074        // Hand-crafted `message` slot — the variant renders the legacy
13075        // `"compile error in {keyword}: serialize: {message}"` shape
13076        // byte-for-byte. Same wording as the pre-lift `Compile`-shaped
13077        // triple in `serialize_to_json_err`.
13078        let err = LispError::DomainSerialize {
13079            keyword: "defmonitor",
13080            message: "key must be a string".into(),
13081        };
13082        assert_eq!(
13083            format!("{err}"),
13084            "compile error in defmonitor: serialize: key must be a string"
13085        );
13086    }
13087
13088    #[test]
13089    fn domain_serialize_display_carries_serde_json_diagnostic_unchanged() {
13090        // Use a real `serde_json::Error` so the test exercises a
13091        // representative `{e}` shape (`"expected value at line L column
13092        // C"`) and pins that the variant's Display rendering threads
13093        // the underlying diagnostic through unchanged.
13094        let raw = serde_json::from_str::<i32>("not-a-number")
13095            .expect_err("parse must fail")
13096            .to_string();
13097        let err = LispError::DomainSerialize {
13098            keyword: "defmonitor",
13099            message: raw.clone(),
13100        };
13101        assert_eq!(
13102            format!("{err}"),
13103            format!("compile error in defmonitor: serialize: {raw}")
13104        );
13105    }
13106
13107    #[test]
13108    fn domain_serialize_display_carries_kebab_case_keyword_unchanged() {
13109        // Kebab-cased domain keywords (`defprocess-spec`,
13110        // `defalert-policy`) round-trip through the rendered
13111        // diagnostic's `compile error in {keyword}:` prefix unchanged.
13112        // A regression that camelCases the keyword fails-loudly here.
13113        let err = LispError::DomainSerialize {
13114            keyword: "defalert-policy",
13115            message: "expected struct".into(),
13116        };
13117        assert_eq!(
13118            format!("{err}"),
13119            "compile error in defalert-policy: serialize: expected struct"
13120        );
13121    }
13122
13123    #[test]
13124    fn domain_serialize_display_preserves_legacy_substring_for_message_grep() {
13125        // Pin the legacy substring — `"serialize: "` — as a separate
13126        // assertion so a regression that drifts the wording (e.g., to
13127        // "to_json failed", "json encode error") fails-loudly here.
13128        // The substring is what consumers downstream (`tatara-check`,
13129        // the REPL) substring-match on; the prefix matches the legacy
13130        // `Compile { form: T::KEYWORD.to_string(), message:
13131        // format!("serialize: {e}") }` byte-for-byte.
13132        let err = LispError::DomainSerialize {
13133            keyword: "defmonitor",
13134            message: "boom".into(),
13135        };
13136        let msg = format!("{err}");
13137        assert!(
13138            msg.contains("serialize: "),
13139            "expected legacy substring in message, got: {msg}"
13140        );
13141        assert!(
13142            msg.contains("compile error in defmonitor:"),
13143            "expected legacy form-label prefix in message, got: {msg}"
13144        );
13145    }
13146
13147    #[test]
13148    fn domain_serialize_display_empty_message_renders_bare_prefix() {
13149        // Edge case: an empty `message` slot renders as `"compile
13150        // error in {keyword}: serialize: "` — pin the trailing space
13151        // after the `serialize:` marker stays put. A regression that
13152        // strips trailing whitespace (e.g., via `.trim_end()`) or
13153        // drops the marker entirely fails-loudly here.
13154        let err = LispError::DomainSerialize {
13155            keyword: "defmonitor",
13156            message: String::new(),
13157        };
13158        assert_eq!(format!("{err}"), "compile error in defmonitor: serialize: ");
13159    }
13160
13161    // ── KwargDeserialize: typed-entry `from_value` structural-variant lift ──
13162    //
13163    // `deserialize_err(key, err)` and `deserialize_item_err(key, idx,
13164    // err)` (the `from_value`-side gate shared between
13165    // `extract_via_serde`, `extract_optional_via_serde`, and
13166    // `extract_vec_via_serde`) were promoted from the
13167    // `LispError::Compile`-shaped triple to the structural
13168    // `LispError::KwargDeserialize { path: KwargPath, message }`
13169    // variant — the `(key: String, idx: Option<usize>)` bifurcation
13170    // collapsed into the typed `KwargPath` enum's `Named` vs. `Item`
13171    // variant identity. The tests below pin: (a) Display matches the
13172    // legacy `"compile error in :{key}: deserialize: {message}"` shape
13173    // byte-for-byte for the scalar path (`path: KwargPath::Named`); (b)
13174    // Display matches the indexed `"compile error in :{key}[{idx}]:
13175    // deserialize: {message}"` shape byte-for-byte for the per-item
13176    // path (`path: KwargPath::Item`); (c) the legacy substring
13177    // `"deserialize: "` and the legacy prefix `"compile error in :"`
13178    // both survive the lift unchanged for substring-grep consumers;
13179    // (d) kebab-case keys thread through unchanged; (e) the typed
13180    // `path` slot carries `KwargPath` data DIRECTLY (not as a projection
13181    // through a helper), structurally bound via pattern-match on the
13182    // typed enum's variant identity. The `position()` floor is pinned
13183    // in the main `position_is_none_for_non_positional_variants` block
13184    // above.
13185
13186    #[test]
13187    fn kwarg_deserialize_display_scalar_path_renders_legacy_shape() {
13188        // `path: KwargPath::Named(_)` — scalar / `Option<T>` path. The
13189        // variant renders the legacy `"compile error in :{key}:
13190        // deserialize: {message}"` shape byte-for-byte. Same wording as
13191        // the pre-lift `Compile`-shaped triple in `deserialize_err`.
13192        let err = LispError::KwargDeserialize {
13193            path: KwargPath::named("level"),
13194            message: "unknown variant `NotASeverity`".into(),
13195        };
13196        assert_eq!(
13197            format!("{err}"),
13198            "compile error in :level: deserialize: unknown variant `NotASeverity`"
13199        );
13200    }
13201
13202    #[test]
13203    fn kwarg_deserialize_display_per_item_path_renders_indexed_shape() {
13204        // `path: KwargPath::Item { .. }` — per-item path. The variant
13205        // renders the legacy `"compile error in :{key}[{idx}]:
13206        // deserialize: {message}"` shape byte-for-byte. Same wording as
13207        // the pre-lift `Compile`-shaped triple in `deserialize_item_err`.
13208        let err = LispError::KwargDeserialize {
13209            path: KwargPath::item("steps", 1),
13210            message: "invalid type: integer `7`, expected a string".into(),
13211        };
13212        assert_eq!(
13213            format!("{err}"),
13214            "compile error in :steps[1]: deserialize: invalid type: integer `7`, expected a string"
13215        );
13216    }
13217
13218    #[test]
13219    fn kwarg_deserialize_display_carries_serde_json_diagnostic_unchanged() {
13220        // Use a real `serde_json::Error` so the test exercises a
13221        // representative `{e}` shape (`"expected value at line L
13222        // column C"`) and pins that the variant's Display rendering
13223        // threads the underlying diagnostic through unchanged.
13224        let raw = serde_json::from_str::<i32>("not-a-number")
13225            .expect_err("parse must fail")
13226            .to_string();
13227        let err = LispError::KwargDeserialize {
13228            path: KwargPath::named("count"),
13229            message: raw.clone(),
13230        };
13231        assert_eq!(
13232            format!("{err}"),
13233            format!("compile error in :count: deserialize: {raw}")
13234        );
13235    }
13236
13237    #[test]
13238    fn kwarg_deserialize_display_carries_kebab_case_key_unchanged() {
13239        // Kebab-cased kwarg names (`notify-ref`, `wait-minutes`,
13240        // `window-seconds`) round-trip through the rendered diagnostic's
13241        // `compile error in :{key}:` prefix unchanged. A regression that
13242        // camelCases the key fails-loudly here.
13243        let err = LispError::KwargDeserialize {
13244            path: KwargPath::named("notify-ref"),
13245            message: "missing field `notify-ref`".into(),
13246        };
13247        assert_eq!(
13248            format!("{err}"),
13249            "compile error in :notify-ref: deserialize: missing field `notify-ref`"
13250        );
13251    }
13252
13253    #[test]
13254    fn kwarg_deserialize_display_carries_kebab_case_key_with_index_unchanged() {
13255        // Kebab-cased keys round-trip through the indexed path too —
13256        // `:notify-refs[2]` not `:notifyRefs[2]`. Pinning both paths
13257        // means a regression in either site (scalar or per-item) fails-
13258        // loudly here.
13259        let err = LispError::KwargDeserialize {
13260            path: KwargPath::item("wait-minutes", 2),
13261            message: "expected u64".into(),
13262        };
13263        assert_eq!(
13264            format!("{err}"),
13265            "compile error in :wait-minutes[2]: deserialize: expected u64"
13266        );
13267    }
13268
13269    #[test]
13270    fn kwarg_deserialize_display_preserves_legacy_substring_for_message_grep() {
13271        // Pin the legacy substring — `"deserialize: "` — as a separate
13272        // assertion so a regression that drifts the wording (e.g., to
13273        // "from_json failed", "json decode error") fails-loudly here.
13274        // The substring is what consumers downstream (`tatara-check`,
13275        // the REPL) substring-match on; the prefix matches the legacy
13276        // `Compile { form: kwarg_form(key), message: format!("deserialize:
13277        // {e}") }` byte-for-byte. Both sub-modes (`KwargPath::Named`
13278        // AND `KwargPath::Item`) preserve the substring.
13279        let scalar = LispError::KwargDeserialize {
13280            path: KwargPath::named("level"),
13281            message: "boom".into(),
13282        };
13283        let scalar_msg = format!("{scalar}");
13284        assert!(
13285            scalar_msg.contains("deserialize: "),
13286            "expected legacy substring in scalar message, got: {scalar_msg}"
13287        );
13288        assert!(
13289            scalar_msg.contains("compile error in :level:"),
13290            "expected legacy form-label prefix in scalar message, got: {scalar_msg}"
13291        );
13292
13293        let item = LispError::KwargDeserialize {
13294            path: KwargPath::item("steps", 3),
13295            message: "boom".into(),
13296        };
13297        let item_msg = format!("{item}");
13298        assert!(
13299            item_msg.contains("deserialize: "),
13300            "expected legacy substring in item message, got: {item_msg}"
13301        );
13302        assert!(
13303            item_msg.contains("compile error in :steps[3]:"),
13304            "expected indexed form-label prefix in item message, got: {item_msg}"
13305        );
13306    }
13307
13308    #[test]
13309    fn kwarg_deserialize_display_zero_index_is_first_class() {
13310        // Edge case: `path: KwargPath::Item { idx: 0, .. }` must render
13311        // as `:steps[0]`, not `:steps` (which would collide with the
13312        // scalar path's `KwargPath::Named` rendering). Pin that the
13313        // bifurcation is by `KwargPath` variant identity, not by
13314        // `idx > 0`.
13315        let err = LispError::KwargDeserialize {
13316            path: KwargPath::item("steps", 0),
13317            message: "bad".into(),
13318        };
13319        assert_eq!(
13320            format!("{err}"),
13321            "compile error in :steps[0]: deserialize: bad"
13322        );
13323    }
13324
13325    #[test]
13326    fn kwarg_deserialize_path_named_threads_typed_kwarg_path_through_variant_slot() {
13327        // Structural pin: the scalar / `Option<T>` path's
13328        // `LispError::KwargDeserialize` carries the typed
13329        // `KwargPath::Named(key)` value DIRECTLY in its `path` slot,
13330        // not a `(key: String, idx: None)` pair. Authoring tools (REPL,
13331        // LSP, `tatara-check`) bind on the typed enum's variant
13332        // identity (`KwargPath::Named(_)`) rather than substring-
13333        // matching the rendered `form:` prefix, parallel to how
13334        // `TypeMismatch.form` is bound. A regression that re-bifurcates
13335        // the variant into a `(key, idx: Option<usize>)` pair fails the
13336        // structural assertion here (the slot would no longer be a
13337        // typed `KwargPath`).
13338        let err = LispError::KwargDeserialize {
13339            path: KwargPath::named("level"),
13340            message: "boom".into(),
13341        };
13342        let LispError::KwargDeserialize {
13343            ref path,
13344            ref message,
13345        } = err
13346        else {
13347            panic!("expected KwargDeserialize, got {err:?}");
13348        };
13349        assert_eq!(*path, KwargPath::Named("level".into()));
13350        assert_eq!(message, "boom");
13351        assert_eq!(
13352            format!("{err}"),
13353            "compile error in :level: deserialize: boom"
13354        );
13355    }
13356
13357    #[test]
13358    fn kwarg_deserialize_path_item_threads_typed_kwarg_path_through_variant_slot() {
13359        // Sibling structural pin to `…_path_named_…`: the per-item path
13360        // carries `KwargPath::Item { key, idx }` directly in its `path`
13361        // slot. The `(key, idx)` bifurcation lives inside the typed
13362        // enum's variant identity (`KwargPath::Named` vs.
13363        // `KwargPath::Item`), so the invalid sibling slot combination
13364        // `(key: "", idx: Some(_))` for a scalar / Optional path is
13365        // structurally unrepresentable in the variant's data shape.
13366        let err = LispError::KwargDeserialize {
13367            path: KwargPath::item("steps", 1),
13368            message: "bad".into(),
13369        };
13370        let LispError::KwargDeserialize {
13371            ref path,
13372            ref message,
13373        } = err
13374        else {
13375            panic!("expected KwargDeserialize, got {err:?}");
13376        };
13377        assert_eq!(
13378            *path,
13379            KwargPath::Item {
13380                key: "steps".into(),
13381                idx: 1
13382            }
13383        );
13384        assert_eq!(message, "bad");
13385        assert_eq!(
13386            format!("{err}"),
13387            "compile error in :steps[1]: deserialize: bad"
13388        );
13389    }
13390
13391    #[test]
13392    fn kwarg_deserialize_display_prefix_matches_kwarg_path_display() {
13393        // End-to-end pin: the `LispError::KwargDeserialize` variant's
13394        // Display rendering threads its typed `path: KwargPath` slot
13395        // through `KwargPath`'s `Display` impl directly (via the
13396        // `#[error("compile error in {path}: ...")]` annotation, no
13397        // intermediate helper). The full rendered diagnostic MUST be
13398        // anchored on the canonical `KwargPath`-projected prefix
13399        // across BOTH variants of `KwargPath` — a regression that
13400        // drifts either projection (e.g., re-introducing an inline
13401        // `format!` literal in a `#[error(..., fmt_fn(path))]` annotation
13402        // that diverges from `KwargPath`'s Display arm) fails-loudly
13403        // here.
13404        let scalar = LispError::KwargDeserialize {
13405            path: KwargPath::named("level"),
13406            message: "boom".into(),
13407        };
13408        assert_eq!(
13409            format!("{scalar}"),
13410            format!(
13411                "compile error in {}: deserialize: boom",
13412                KwargPath::named("level")
13413            )
13414        );
13415
13416        let item = LispError::KwargDeserialize {
13417            path: KwargPath::item("steps", 3),
13418            message: "boom".into(),
13419        };
13420        assert_eq!(
13421            format!("{item}"),
13422            format!(
13423                "compile error in {}: deserialize: boom",
13424                KwargPath::item("steps", 3)
13425            )
13426        );
13427    }
13428
13429    // ── CompilerSpecIo: disk-persistence structural-variant lift ────
13430    //
13431    // `compiler_spec_io_err` (the helper shared by all four
13432    // `realize_to_disk` / `load_from_disk` call sites in
13433    // `compiler_spec.rs`) was promoted from the `LispError::Compile`-
13434    // shaped triple to the structural `LispError::CompilerSpecIo {
13435    // stage, message }` variant — closing the LAST
13436    // `LispError::Compile { ... }` construction site in
13437    // `tatara-lisp/src/compiler_spec.rs`. The `stage` slot is the
13438    // typed closed-set `CompilerSpecIoStage` enum, so the
13439    // (operation, stage) pair is structurally constrained — only the
13440    // four reachable pairs (`realize_to_disk` × {serialize, write}
13441    // ⊎ `load_from_disk` × {read, deserialize}) are representable
13442    // in the variant.
13443    //
13444    // The tests below pin: (a) Display matches the legacy `"compile
13445    // error in {operation}: {stage}: {message}"` shape byte-for-byte
13446    // across all four stages; (b) the closed-set `operation()` /
13447    // `label()` projections; (c) the `CompilerSpecIoStage` enum is
13448    // Copy + Eq + Debug (matches the `MacroDefHead` posture); (d) the
13449    // legacy substring `"realize_to_disk"`, `"load_from_disk"`,
13450    // `"serialize: "`, `"write: "`, `"read: "`, `"deserialize: "`
13451    // survive the lift unchanged for substring-grep consumers. The
13452    // `position()` floor for both representative stages is pinned in
13453    // the main `position_is_none_for_non_positional_variants` block
13454    // above.
13455
13456    #[test]
13457    fn compiler_spec_io_stage_operation_projects_realize_for_serialize_and_write() {
13458        // Both `realize_to_disk` stages share the same `operation()`
13459        // projection. Pin the closed-set posture: the operation slot
13460        // of the legacy `Compile`-shaped triple is now a TYPED
13461        // projection from `CompilerSpecIoStage`, not an
13462        // independently-passed `&'static str` that could drift.
13463        assert_eq!(
13464            super::CompilerSpecIoStage::RealizeToDiskSerialize.operation(),
13465            "realize_to_disk"
13466        );
13467        assert_eq!(
13468            super::CompilerSpecIoStage::RealizeToDiskWrite.operation(),
13469            "realize_to_disk"
13470        );
13471    }
13472
13473    #[test]
13474    fn compiler_spec_io_stage_operation_projects_load_for_read_and_deserialize() {
13475        // Both `load_from_disk` stages share the same `operation()`
13476        // projection. Sibling to the realize-side assertion: pins the
13477        // bifurcation of the closed set by `operation()` is exhaustive
13478        // and exactly 2-way (`realize_to_disk` ⊎ `load_from_disk`).
13479        assert_eq!(
13480            super::CompilerSpecIoStage::LoadFromDiskRead.operation(),
13481            "load_from_disk"
13482        );
13483        assert_eq!(
13484            super::CompilerSpecIoStage::LoadFromDiskDeserialize.operation(),
13485            "load_from_disk"
13486        );
13487    }
13488
13489    #[test]
13490    fn compiler_spec_io_stage_label_projects_canonical_stage_strings() {
13491        // Each `CompilerSpecIoStage` projects to its canonical
13492        // `label()` — the `{stage}` slot of the legacy `"{stage}:
13493        // {error}"` message. Pin all four projections so a regression
13494        // that drifts ANY label (e.g., to "ser", "load", "decode",
13495        // "json-out") fails-loudly here. The four labels are the
13496        // surface that `tatara-check`'s diagnostic capture and the
13497        // REPL substring-grep on today.
13498        assert_eq!(
13499            super::CompilerSpecIoStage::RealizeToDiskSerialize.label(),
13500            "serialize"
13501        );
13502        assert_eq!(
13503            super::CompilerSpecIoStage::RealizeToDiskWrite.label(),
13504            "write"
13505        );
13506        assert_eq!(super::CompilerSpecIoStage::LoadFromDiskRead.label(), "read");
13507        assert_eq!(
13508            super::CompilerSpecIoStage::LoadFromDiskDeserialize.label(),
13509            "deserialize"
13510        );
13511    }
13512
13513    #[test]
13514    fn compiler_spec_io_stage_label_method_routes_through_typed_constants() {
13515        // PATH-UNIFORMITY: the inherent `Self::label(self)` method
13516        // MUST return the per-role `pub const` byte-for-byte for
13517        // each reachable variant. A regression that reverts ONE arm
13518        // to an inline `"serialize"` string literal (e.g. a merge-
13519        // conflict resolution that picked the pre-lift form)
13520        // silently reintroduces the ≥2 PRIME-DIRECTIVE trigger the
13521        // lift resolved — this test catches that by pinning the
13522        // arm's return value to the constant, so the two paths
13523        // (inline vs. typed constant) cannot both hold. Sibling
13524        // posture to
13525        // `kwarg_path_kind_label_method_routes_through_typed_constants`
13526        // on the `KwargPathKind` category-label algebra.
13527        assert_eq!(
13528            super::CompilerSpecIoStage::RealizeToDiskSerialize.label(),
13529            super::CompilerSpecIoStage::REALIZE_TO_DISK_SERIALIZE_LABEL,
13530            "CompilerSpecIoStage::RealizeToDiskSerialize.label() \
13531             drifted from CompilerSpecIoStage::\
13532             REALIZE_TO_DISK_SERIALIZE_LABEL — the match arm reverted \
13533             to an inline literal"
13534        );
13535        assert_eq!(
13536            super::CompilerSpecIoStage::RealizeToDiskWrite.label(),
13537            super::CompilerSpecIoStage::REALIZE_TO_DISK_WRITE_LABEL,
13538            "CompilerSpecIoStage::RealizeToDiskWrite.label() drifted \
13539             from CompilerSpecIoStage::REALIZE_TO_DISK_WRITE_LABEL — \
13540             the match arm reverted to an inline literal"
13541        );
13542        assert_eq!(
13543            super::CompilerSpecIoStage::LoadFromDiskRead.label(),
13544            super::CompilerSpecIoStage::LOAD_FROM_DISK_READ_LABEL,
13545            "CompilerSpecIoStage::LoadFromDiskRead.label() drifted \
13546             from CompilerSpecIoStage::LOAD_FROM_DISK_READ_LABEL — \
13547             the match arm reverted to an inline literal"
13548        );
13549        assert_eq!(
13550            super::CompilerSpecIoStage::LoadFromDiskDeserialize.label(),
13551            super::CompilerSpecIoStage::LOAD_FROM_DISK_DESERIALIZE_LABEL,
13552            "CompilerSpecIoStage::LoadFromDiskDeserialize.label() \
13553             drifted from CompilerSpecIoStage::\
13554             LOAD_FROM_DISK_DESERIALIZE_LABEL — the match arm reverted \
13555             to an inline literal"
13556        );
13557    }
13558
13559    #[test]
13560    fn compiler_spec_io_stage_labels_has_expected_cardinality() {
13561        // Cardinality contract: `Self::LABELS.len() == 4` — pinned
13562        // at the declaration site by rustc's forced-arity check on
13563        // `[&'static str; 4]`. This test surfaces the arity as a
13564        // fail-loud runtime pin so a future refactor that switches
13565        // the array type to `&[&'static str]` (dropping the
13566        // compile-time arity forcing) doesn't silently loosen the
13567        // closed-set discipline the family relies on. Sibling
13568        // posture to `kwarg_path_kind_labels_has_expected_cardinality`
13569        // on the `KwargPathKind::LABELS` family.
13570        assert_eq!(
13571            super::CompilerSpecIoStage::LABELS.len(),
13572            4,
13573            "CompilerSpecIoStage::LABELS cardinality drifted from 4 \
13574             — the disk-persistence stage-label closed set gained or \
13575             lost a variant without the ALL / LABELS pair being \
13576             updated in tandem"
13577        );
13578    }
13579
13580    #[test]
13581    fn compiler_spec_io_stage_labels_align_with_all_by_index() {
13582        // ALIGNMENT CONTRACT: `Self::LABELS[i] ==
13583        // Self::ALL[i].label()` element-wise. The two ALL arrays
13584        // (typed variants, canonical `&'static str` labels) share
13585        // ONE canonical declaration order — a regression that
13586        // reorders ONE array without reordering the other silently
13587        // misaligns every `zip(Self::ALL, Self::LABELS)` consumer
13588        // (LSP completion providers, metric-label emitters, coverage
13589        // reporters). Pinning by-index alignment here catches the
13590        // reorder at test time. Sibling posture to
13591        // `kwarg_path_kind_labels_align_with_all_by_index` on the
13592        // `KwargPathKind::LABELS` family.
13593        assert_eq!(
13594            super::CompilerSpecIoStage::LABELS.len(),
13595            super::CompilerSpecIoStage::ALL.len(),
13596            "CompilerSpecIoStage::LABELS and CompilerSpecIoStage::ALL \
13597             diverged in cardinality — the per-role constants and \
13598             enum variants must stay in lockstep"
13599        );
13600        for (i, stage) in super::CompilerSpecIoStage::ALL.iter().enumerate() {
13601            assert_eq!(
13602                super::CompilerSpecIoStage::LABELS[i],
13603                stage.label(),
13604                "CompilerSpecIoStage::LABELS[{i}] `{lb}` drifted from \
13605                 CompilerSpecIoStage::ALL[{i}].label() `{via_variant}` \
13606                 — the canonical declaration order of the two ALL \
13607                 arrays must match element-wise",
13608                lb = super::CompilerSpecIoStage::LABELS[i],
13609                via_variant = stage.label(),
13610            );
13611        }
13612    }
13613
13614    #[test]
13615    fn compiler_spec_io_stage_labels_pairwise_distinct() {
13616        // PAIRWISE DISJOINTNESS: the four `&'static str` labels on
13617        // the disk-persistence stage-label algebra MUST differ so
13618        // any consumer that keys a hashmap / dispatch table on the
13619        // label bytes (`match label { "serialize" => ...,
13620        // "deserialize" => ..., ... }`) cannot route two variants
13621        // through the same arm. Family-wide sweep over LABELS ×
13622        // LABELS — supersedes any single per-pair assertion and
13623        // picks up new variants mechanically when the array grows.
13624        // Sibling posture to `kwarg_path_kind_labels_pairwise_distinct`
13625        // on the `KwargPathKind::LABELS` family.
13626        //
13627        // Note: unlike `KwargPathKind` / `ExpectedKwargShape`, the
13628        // parent enum keys `FromStr` on the compound
13629        // `"{operation}: {label}"` key rather than on `label` alone
13630        // (see `CompilerSpecIoStage::from_str`), so pairwise-distinct
13631        // labels are not a hard load-bearing invariant for the
13632        // FromStr sweep. But they ARE the load-bearing invariant for
13633        // every downstream consumer that keys on the label alone
13634        // (metric-label tables, LSP completion, `tatara-check` grep
13635        // budgets on the `{stage}:` prefix), and pinning the
13636        // property here catches a regression that collides two
13637        // variants on their singular label projection before those
13638        // consumer sites drift.
13639        for (i, a) in super::CompilerSpecIoStage::LABELS.iter().enumerate() {
13640            for (j, b) in super::CompilerSpecIoStage::LABELS.iter().enumerate() {
13641                if i == j {
13642                    continue;
13643                }
13644                assert_ne!(
13645                    a, b,
13646                    "CompilerSpecIoStage::LABELS[{i}] `{a}` collides \
13647                     with CompilerSpecIoStage::LABELS[{j}] `{b}` — \
13648                     the singular label projection is no longer a \
13649                     bijection with ALL, breaking every consumer that \
13650                     keys on the label alone"
13651                );
13652            }
13653        }
13654    }
13655
13656    #[test]
13657    fn compiler_spec_io_stage_operation_method_routes_through_typed_constants() {
13658        // PATH-UNIFORMITY: the inherent `Self::operation(self)` method
13659        // MUST return the per-role `pub const` byte-for-byte for
13660        // each reachable variant. A regression that reverts ONE arm
13661        // to an inline `"realize_to_disk"` string literal (e.g. a
13662        // merge-conflict resolution that picked the pre-lift form)
13663        // silently reintroduces the ≥2 PRIME-DIRECTIVE trigger the
13664        // lift resolved — this test catches that by pinning each
13665        // arm's return value to the constant, so the two paths
13666        // (inline vs. typed constant) cannot both hold. Sibling
13667        // posture to
13668        // `compiler_spec_io_stage_label_method_routes_through_typed_constants`
13669        // on the paired stage-label algebra.
13670        assert_eq!(
13671            super::CompilerSpecIoStage::RealizeToDiskSerialize.operation(),
13672            super::CompilerSpecIoStage::REALIZE_TO_DISK_OPERATION,
13673            "CompilerSpecIoStage::RealizeToDiskSerialize.operation() \
13674             drifted from CompilerSpecIoStage::\
13675             REALIZE_TO_DISK_OPERATION — the match arm reverted to an \
13676             inline literal"
13677        );
13678        assert_eq!(
13679            super::CompilerSpecIoStage::RealizeToDiskWrite.operation(),
13680            super::CompilerSpecIoStage::REALIZE_TO_DISK_OPERATION,
13681            "CompilerSpecIoStage::RealizeToDiskWrite.operation() \
13682             drifted from CompilerSpecIoStage::\
13683             REALIZE_TO_DISK_OPERATION — the match arm reverted to an \
13684             inline literal"
13685        );
13686        assert_eq!(
13687            super::CompilerSpecIoStage::LoadFromDiskRead.operation(),
13688            super::CompilerSpecIoStage::LOAD_FROM_DISK_OPERATION,
13689            "CompilerSpecIoStage::LoadFromDiskRead.operation() drifted \
13690             from CompilerSpecIoStage::LOAD_FROM_DISK_OPERATION — the \
13691             match arm reverted to an inline literal"
13692        );
13693        assert_eq!(
13694            super::CompilerSpecIoStage::LoadFromDiskDeserialize.operation(),
13695            super::CompilerSpecIoStage::LOAD_FROM_DISK_OPERATION,
13696            "CompilerSpecIoStage::LoadFromDiskDeserialize.operation() \
13697             drifted from CompilerSpecIoStage::LOAD_FROM_DISK_OPERATION \
13698             — the match arm reverted to an inline literal"
13699        );
13700    }
13701
13702    #[test]
13703    fn compiler_spec_io_stage_operations_has_expected_cardinality() {
13704        // Cardinality contract: `Self::OPERATIONS.len() == 4` —
13705        // pinned at the declaration site by rustc's forced-arity
13706        // check on `[&'static str; 4]`. This test surfaces the arity
13707        // as a fail-loud runtime pin so a future refactor that
13708        // switches the array type to `&[&'static str]` (dropping the
13709        // compile-time arity forcing) doesn't silently loosen the
13710        // closed-set discipline the family relies on. Sibling posture
13711        // to `compiler_spec_io_stage_labels_has_expected_cardinality`
13712        // on the paired stage-label algebra.
13713        assert_eq!(
13714            super::CompilerSpecIoStage::OPERATIONS.len(),
13715            4,
13716            "CompilerSpecIoStage::OPERATIONS cardinality drifted from \
13717             4 — the disk-persistence operation-label closed set \
13718             gained or lost a variant without the ALL / OPERATIONS \
13719             pair being updated in tandem"
13720        );
13721    }
13722
13723    #[test]
13724    fn compiler_spec_io_stage_operations_align_with_all_by_index() {
13725        // ALIGNMENT CONTRACT: `Self::OPERATIONS[i] ==
13726        // Self::ALL[i].operation()` element-wise. The two ALL arrays
13727        // (typed variants, canonical `&'static str` operation labels)
13728        // share ONE canonical declaration order — a regression that
13729        // reorders ONE array without reordering the other silently
13730        // misaligns every `zip(Self::ALL, Self::OPERATIONS)` consumer
13731        // (LSP completion providers keyed on the operation slot,
13732        // metric-label emitters, coverage reporters that walk the
13733        // operation × stage compound-key surface). Pinning by-index
13734        // alignment here catches the reorder at test time. Sibling
13735        // posture to `compiler_spec_io_stage_labels_align_with_all_by_index`
13736        // on the paired stage-label algebra.
13737        assert_eq!(
13738            super::CompilerSpecIoStage::OPERATIONS.len(),
13739            super::CompilerSpecIoStage::ALL.len(),
13740            "CompilerSpecIoStage::OPERATIONS and \
13741             CompilerSpecIoStage::ALL diverged in cardinality — the \
13742             per-role constants and enum variants must stay in \
13743             lockstep"
13744        );
13745        for (i, stage) in super::CompilerSpecIoStage::ALL.iter().enumerate() {
13746            assert_eq!(
13747                super::CompilerSpecIoStage::OPERATIONS[i],
13748                stage.operation(),
13749                "CompilerSpecIoStage::OPERATIONS[{i}] `{op}` drifted \
13750                 from CompilerSpecIoStage::ALL[{i}].operation() \
13751                 `{via_variant}` — the canonical declaration order \
13752                 of the two ALL arrays must match element-wise",
13753                op = super::CompilerSpecIoStage::OPERATIONS[i],
13754                via_variant = stage.operation(),
13755            );
13756        }
13757    }
13758
13759    #[test]
13760    fn compiler_spec_io_stage_operations_partition_all_two_ways() {
13761        // TWO-WAY PARTITION CONTRACT: unlike `Self::LABELS` — whose
13762        // four elements are pairwise distinct because each `label()`
13763        // projection is bijective with its variant — `Self::OPERATIONS`
13764        // contains DUPLICATES by construction. The operation
13765        // projection bifurcates the closed set into exactly two
13766        // disjoint groups of two: `{RealizeToDiskSerialize,
13767        // RealizeToDiskWrite}` share `"realize_to_disk"` and
13768        // `{LoadFromDiskRead, LoadFromDiskDeserialize}` share
13769        // `"load_from_disk"`. The 2-of-2-to-2 shape is load-bearing
13770        // for the compound-key `"{operation}: {label}"` surface —
13771        // if the operation projection ever grew bijective with
13772        // `ALL` the compound key would collapse to `label`-only and
13773        // the parse boundary would silently accept unreachable
13774        // cross-product pairs. Pin the exact multiplicities here so
13775        // a future refactor that (a) adds a distinct per-variant
13776        // operation, or (b) collapses the two operations into one
13777        // shared string, fails-loudly. Sibling posture to
13778        // `compiler_spec_io_stage_labels_pairwise_distinct` on the
13779        // paired stage-label algebra — where LABELS pins
13780        // distinctness, OPERATIONS pins the exact two-way partition
13781        // multiplicity.
13782        let realize_count = super::CompilerSpecIoStage::OPERATIONS
13783            .iter()
13784            .filter(|&&op| op == super::CompilerSpecIoStage::REALIZE_TO_DISK_OPERATION)
13785            .count();
13786        let load_count = super::CompilerSpecIoStage::OPERATIONS
13787            .iter()
13788            .filter(|&&op| op == super::CompilerSpecIoStage::LOAD_FROM_DISK_OPERATION)
13789            .count();
13790        assert_eq!(
13791            realize_count, 2,
13792            "CompilerSpecIoStage::OPERATIONS multiplicity of \
13793             REALIZE_TO_DISK_OPERATION drifted from 2 — the \
13794             realize-side bifurcation over {{serialize, write}} is \
13795             load-bearing for the compound-key surface"
13796        );
13797        assert_eq!(
13798            load_count, 2,
13799            "CompilerSpecIoStage::OPERATIONS multiplicity of \
13800             LOAD_FROM_DISK_OPERATION drifted from 2 — the load-side \
13801             bifurcation over {{read, deserialize}} is load-bearing \
13802             for the compound-key surface"
13803        );
13804        assert_eq!(
13805            realize_count + load_count,
13806            super::CompilerSpecIoStage::OPERATIONS.len(),
13807            "CompilerSpecIoStage::OPERATIONS gained an operation label \
13808             outside the two-way partition — the closed-set operation \
13809             axis is no longer a clean {{realize_to_disk ⊎ load_from_disk}} \
13810             bifurcation"
13811        );
13812        assert_ne!(
13813            super::CompilerSpecIoStage::REALIZE_TO_DISK_OPERATION,
13814            super::CompilerSpecIoStage::LOAD_FROM_DISK_OPERATION,
13815            "CompilerSpecIoStage::REALIZE_TO_DISK_OPERATION collided \
13816             with CompilerSpecIoStage::LOAD_FROM_DISK_OPERATION — the \
13817             two-way partition collapsed to one group, breaking every \
13818             compound-key `{{operation}}: {{label}}` round-trip"
13819        );
13820    }
13821
13822    #[test]
13823    fn compiler_spec_io_display_renders_legacy_shape_for_realize_serialize() {
13824        // `RealizeToDiskSerialize` — the `serde_json::to_string_pretty`
13825        // failure inside `realize_to_disk`. The variant renders the
13826        // legacy `"compile error in realize_to_disk: serialize:
13827        // {message}"` shape byte-for-byte — same wording as the
13828        // pre-lift `Compile { form: "realize_to_disk", message:
13829        // "serialize: {e}" }` triple.
13830        let err = LispError::CompilerSpecIo {
13831            stage: super::CompilerSpecIoStage::RealizeToDiskSerialize,
13832            message: "expected struct CompilerSpec".into(),
13833        };
13834        assert_eq!(
13835            format!("{err}"),
13836            "compile error in realize_to_disk: serialize: expected struct CompilerSpec"
13837        );
13838    }
13839
13840    #[test]
13841    fn compiler_spec_io_display_renders_legacy_shape_for_realize_write() {
13842        // `RealizeToDiskWrite` — the `std::fs::write` failure inside
13843        // `realize_to_disk`. Pin path-uniformity across the second
13844        // stage of the realize-side operation: same operation prefix,
13845        // distinct stage label (`write` vs `serialize`).
13846        let err = LispError::CompilerSpecIo {
13847            stage: super::CompilerSpecIoStage::RealizeToDiskWrite,
13848            message: "No such file or directory (os error 2)".into(),
13849        };
13850        assert_eq!(
13851            format!("{err}"),
13852            "compile error in realize_to_disk: write: No such file or directory (os error 2)"
13853        );
13854    }
13855
13856    #[test]
13857    fn compiler_spec_io_display_renders_legacy_shape_for_load_read() {
13858        // `LoadFromDiskRead` — the `std::fs::read_to_string` failure
13859        // inside `load_from_disk`. Pin path-uniformity across the
13860        // operation slot: `load_from_disk` vs `realize_to_disk` are
13861        // structurally distinct via the typed enum, both round-trip
13862        // through Display unchanged.
13863        let err = LispError::CompilerSpecIo {
13864            stage: super::CompilerSpecIoStage::LoadFromDiskRead,
13865            message: "No such file or directory (os error 2)".into(),
13866        };
13867        assert_eq!(
13868            format!("{err}"),
13869            "compile error in load_from_disk: read: No such file or directory (os error 2)"
13870        );
13871    }
13872
13873    #[test]
13874    fn compiler_spec_io_display_renders_legacy_shape_for_load_deserialize() {
13875        // `LoadFromDiskDeserialize` — the `serde_json::from_str`
13876        // failure inside `load_from_disk`. Pin path-uniformity across
13877        // the fourth and final reachable stage. Together with the
13878        // three sibling tests, this closes the structural-completeness
13879        // floor of the closed-set `CompilerSpecIoStage` × Display
13880        // matrix — all four reachable pairs are pinned.
13881        let err = LispError::CompilerSpecIo {
13882            stage: super::CompilerSpecIoStage::LoadFromDiskDeserialize,
13883            message: "expected value at line 1 column 1".into(),
13884        };
13885        assert_eq!(
13886            format!("{err}"),
13887            "compile error in load_from_disk: deserialize: expected value at line 1 column 1"
13888        );
13889    }
13890
13891    #[test]
13892    fn compiler_spec_io_display_carries_serde_json_diagnostic_unchanged() {
13893        // Use a real `serde_json::Error` so the test exercises a
13894        // representative `{e}` shape and pins that the variant's
13895        // Display rendering threads the underlying diagnostic through
13896        // unchanged. Same posture as `domain_serialize_display_carries_
13897        // serde_json_diagnostic_unchanged` and
13898        // `kwarg_deserialize_display_carries_serde_json_diagnostic_
13899        // unchanged`.
13900        let raw = serde_json::from_str::<i32>("not-a-number")
13901            .expect_err("parse must fail")
13902            .to_string();
13903        let err = LispError::CompilerSpecIo {
13904            stage: super::CompilerSpecIoStage::LoadFromDiskDeserialize,
13905            message: raw.clone(),
13906        };
13907        assert_eq!(
13908            format!("{err}"),
13909            format!("compile error in load_from_disk: deserialize: {raw}")
13910        );
13911    }
13912
13913    #[test]
13914    fn compiler_spec_io_display_preserves_legacy_substring_for_message_grep() {
13915        // Pin the legacy substring set — `"realize_to_disk"`,
13916        // `"load_from_disk"`, `"serialize: "`, `"write: "`,
13917        // `"read: "`, `"deserialize: "` — as a separate assertion so a
13918        // regression that drifts ANY of the six surface words (e.g.,
13919        // to "save", "load", "json-out", "json-in") fails-loudly here.
13920        // The substrings are what consumers downstream (`tatara-check`,
13921        // the REPL) substring-match on today; the prefix matches the
13922        // legacy `Compile { form: "{operation}", message: "{stage}:
13923        // {e}" }` byte-for-byte across all four reachable pairs.
13924        let realize_serialize = LispError::CompilerSpecIo {
13925            stage: super::CompilerSpecIoStage::RealizeToDiskSerialize,
13926            message: "boom".into(),
13927        };
13928        let msg = format!("{realize_serialize}");
13929        assert!(
13930            msg.contains("realize_to_disk"),
13931            "expected realize-side operation in message, got: {msg}"
13932        );
13933        assert!(
13934            msg.contains("serialize: "),
13935            "expected serialize-stage substring in message, got: {msg}"
13936        );
13937
13938        let realize_write = LispError::CompilerSpecIo {
13939            stage: super::CompilerSpecIoStage::RealizeToDiskWrite,
13940            message: "boom".into(),
13941        };
13942        let msg = format!("{realize_write}");
13943        assert!(
13944            msg.contains("write: "),
13945            "expected write-stage substring in message, got: {msg}"
13946        );
13947
13948        let load_read = LispError::CompilerSpecIo {
13949            stage: super::CompilerSpecIoStage::LoadFromDiskRead,
13950            message: "boom".into(),
13951        };
13952        let msg = format!("{load_read}");
13953        assert!(
13954            msg.contains("load_from_disk"),
13955            "expected load-side operation in message, got: {msg}"
13956        );
13957        assert!(
13958            msg.contains("read: "),
13959            "expected read-stage substring in message, got: {msg}"
13960        );
13961
13962        let load_deserialize = LispError::CompilerSpecIo {
13963            stage: super::CompilerSpecIoStage::LoadFromDiskDeserialize,
13964            message: "boom".into(),
13965        };
13966        let msg = format!("{load_deserialize}");
13967        assert!(
13968            msg.contains("deserialize: "),
13969            "expected deserialize-stage substring in message, got: {msg}"
13970        );
13971    }
13972
13973    #[test]
13974    fn compiler_spec_io_display_empty_message_renders_bare_stage_marker() {
13975        // Edge case: an empty `message` slot renders as `"compile
13976        // error in {operation}: {stage}: "` — pin the trailing space
13977        // after the stage marker stays put across all four pairs. A
13978        // regression that strips trailing whitespace (e.g., via
13979        // `.trim_end()`) or drops the marker entirely fails-loudly here.
13980        // Sibling of `domain_serialize_display_empty_message_renders_
13981        // bare_prefix`.
13982        let err = LispError::CompilerSpecIo {
13983            stage: super::CompilerSpecIoStage::RealizeToDiskWrite,
13984            message: String::new(),
13985        };
13986        assert_eq!(
13987            format!("{err}"),
13988            "compile error in realize_to_disk: write: "
13989        );
13990    }
13991
13992    #[test]
13993    fn compiler_spec_io_stage_is_copy_and_partial_eq() {
13994        // Pin the closed-set posture: `CompilerSpecIoStage` derives
13995        // Copy + PartialEq + Eq + Debug so it composes ergonomically
13996        // in tests and in consumer pattern-matches (no clone-and-
13997        // own dance). Same posture as `MacroDefHead`. A regression
13998        // that drops Copy fails-loudly here (the let-binding would
13999        // move out instead of copy).
14000        let stage = super::CompilerSpecIoStage::LoadFromDiskRead;
14001        let copied = stage;
14002        assert_eq!(stage, copied);
14003        assert_eq!(stage, super::CompilerSpecIoStage::LoadFromDiskRead);
14004        assert_ne!(stage, super::CompilerSpecIoStage::RealizeToDiskWrite);
14005    }
14006
14007    #[test]
14008    fn compiler_spec_io_stage_all_is_unique_and_complete() {
14009        // Closed-set posture: `ALL` enumerates every reachable variant
14010        // EXACTLY ONCE — no duplicates, no omissions. The `[Self; 4]`
14011        // array literal in the declaration forces the arity at compile
14012        // time; this test catches the orthogonal failure modes — a
14013        // future variant added at the type without being added to ALL
14014        // (silently dropped from every consumer's sweep), or a typo
14015        // that duplicates an entry (silently double-counted). Same
14016        // truth-table pinning every sibling closed-set lift in the
14017        // workspace uses (ExpectedKwargShape::ALL, SexpShape::ALL,
14018        // MacroDefHead::ALL, UnquoteForm::ALL, …).
14019        //
14020        // The asserted compound keys are the canonical (operation,
14021        // label) pairs the disk-persistence surface emits — the four
14022        // entries are the reachable cells of the `operation × label`
14023        // cross-product, and the four-out-of-eight partiality is the
14024        // load-bearing thing: `realize_to_disk × {read, deserialize}`
14025        // and `load_from_disk × {serialize, write}` are conceivable
14026        // but unreachable, and `FromStr` enforces that asymmetry at
14027        // the parse boundary.
14028        assert_eq!(super::CompilerSpecIoStage::ALL.len(), 4);
14029        let mut sorted: Vec<String> = super::CompilerSpecIoStage::ALL
14030            .iter()
14031            .map(std::string::ToString::to_string)
14032            .collect();
14033        sorted.sort_unstable();
14034        let mut deduped = sorted.clone();
14035        deduped.dedup();
14036        assert_eq!(
14037            sorted, deduped,
14038            "CompilerSpecIoStage::ALL must not contain duplicates"
14039        );
14040        assert_eq!(
14041            sorted,
14042            vec![
14043                "load_from_disk: deserialize".to_string(),
14044                "load_from_disk: read".to_string(),
14045                "realize_to_disk: serialize".to_string(),
14046                "realize_to_disk: write".to_string(),
14047            ],
14048            "CompilerSpecIoStage::ALL must cover every reachable (operation, label) pair"
14049        );
14050    }
14051
14052    #[test]
14053    fn compiler_spec_io_stage_display_matches_diagnostic_prefix() {
14054        // Pin standalone Display to the canonical compound key — the
14055        // exact substring that lands between `"compile error in "` and
14056        // `": {message}"` inside the LispError::CompilerSpecIo
14057        // rendering. Consumers that extract the (operation, label)
14058        // prefix from a rendered diagnostic (LSP code-actions, REPL
14059        // replay) round-trip the captured substring through `FromStr`
14060        // back into the typed variant exactly. A regression that
14061        // drifts either side (Display's separator, the LispError's
14062        // `#[error(...)]` annotation, the projection methods) fails
14063        // loudly here because both renderings must agree.
14064        for stage in super::CompilerSpecIoStage::ALL {
14065            let standalone = stage.to_string();
14066            let full = format!(
14067                "{}",
14068                LispError::CompilerSpecIo {
14069                    stage,
14070                    message: "MSG".into()
14071                }
14072            );
14073            let prefix = full
14074                .strip_prefix("compile error in ")
14075                .expect("legacy rendering prefix must hold");
14076            let extracted = prefix
14077                .strip_suffix(": MSG")
14078                .expect("legacy rendering suffix must hold");
14079            assert_eq!(
14080                extracted, standalone,
14081                "extracted prefix `{extracted}` must equal standalone Display `{standalone}`"
14082            );
14083        }
14084    }
14085
14086    #[test]
14087    fn compiler_spec_io_stage_compound_key_round_trips_through_from_str() {
14088        // Bidirectional `Display` ↔ `FromStr` contract: for every
14089        // variant in ALL, `stage.to_string().parse() == Ok(stage)`. A
14090        // regression that drifts the (variant, compound-key) pairing
14091        // at the `Display` impl (typo, missing separator, swapped
14092        // projection order) OR at the `FromStr` decode body (off-by-
14093        // one, missing variant in the sweep) fails-loudly here. The
14094        // canonical-key site is singular (`Display` projects through
14095        // `operation()` and `label()`) so the round-trip is the only
14096        // way the typed surface and the rendered diagnostic literal
14097        // can drift apart — pinning it here means they cannot. Mirror
14098        // of `expected_kwarg_shape_label_round_trips_through_from_str`
14099        // and every sibling closed-set round-trip in the workspace —
14100        // the difference is the compound-key shape rather than a
14101        // single label.
14102        for stage in super::CompilerSpecIoStage::ALL {
14103            let key = stage.to_string();
14104            let parsed: super::CompilerSpecIoStage = key
14105                .parse()
14106                .expect("every ALL variant's compound key must round-trip through FromStr");
14107            assert_eq!(
14108                parsed, stage,
14109                "FromStr({key}) must round-trip to the same variant"
14110            );
14111        }
14112    }
14113
14114    #[test]
14115    fn compiler_spec_io_stage_from_str_rejects_partial_and_unreachable_keys() {
14116        // The compound-key shape is load-bearing: `FromStr` rejects
14117        // partial keys (one of the two projection slots alone),
14118        // separator-less inputs, AND the four conceivable-but-
14119        // unreachable cross-product pairs that the disk-persistence
14120        // surface does NOT emit. The partial-rejection turns the
14121        // call-site invariant ("only the four call sites in
14122        // `compiler_spec.rs` construct stages, each pairs the correct
14123        // operation with the correct stage") into a parse-boundary
14124        // invariant ("the four reachable pairs are structurally
14125        // distinct from the four unreachable ones").
14126        //
14127        // Without this guard a future LSP that captures the prefix
14128        // `"load_from_disk: write"` from a hand-crafted (corrupted)
14129        // log and replays it through `FromStr` would silently round-
14130        // trip an unreachable identity into the typed enum.
14131        for partial in [
14132            "serialize",
14133            "write",
14134            "read",
14135            "deserialize",
14136            "realize_to_disk",
14137            "load_from_disk",
14138            "",
14139        ] {
14140            partial.parse::<super::CompilerSpecIoStage>().expect_err(
14141                "partial key (one projection slot alone, or empty) must NOT decode to a variant",
14142            );
14143        }
14144        for unreachable in [
14145            "realize_to_disk: read",
14146            "realize_to_disk: deserialize",
14147            "load_from_disk: serialize",
14148            "load_from_disk: write",
14149        ] {
14150            unreachable
14151                .parse::<super::CompilerSpecIoStage>()
14152                .expect_err(
14153                "conceivable-but-unreachable (operation, label) pair must NOT decode to a variant",
14154            );
14155        }
14156        // Sanity: each reachable pair still decodes.
14157        assert_eq!(
14158            "realize_to_disk: serialize"
14159                .parse::<super::CompilerSpecIoStage>()
14160                .unwrap(),
14161            super::CompilerSpecIoStage::RealizeToDiskSerialize
14162        );
14163        assert_eq!(
14164            "load_from_disk: deserialize"
14165                .parse::<super::CompilerSpecIoStage>()
14166                .unwrap(),
14167            super::CompilerSpecIoStage::LoadFromDiskDeserialize
14168        );
14169    }
14170
14171    #[test]
14172    fn unknown_compiler_spec_io_stage_carries_offending_input_verbatim() {
14173        // Operator-facing diagnostic contract: the offending input
14174        // lands in the typed error verbatim — no normalization, no
14175        // case-folding, no truncation. Pin the exact `#[error(...)]`
14176        // rendering AND the typed `.0` field projection so a future
14177        // refactor that normalizes (e.g. `.to_lowercase()`) before
14178        // building the error or that drops the input fails-loudly
14179        // here. Symmetric to every sibling `Unknown*` carrier in the
14180        // workspace (`UnknownExpectedKwargShape`, `UnknownSexpShape`,
14181        // `UnknownMacroDefHead`, …).
14182        let err: super::UnknownCompilerSpecIoStage = "Realize_to_disk: serialize"
14183            .parse::<super::CompilerSpecIoStage>()
14184            .expect_err("capitalized operation must NOT decode — keys are byte-equal");
14185        assert_eq!(err.0, "Realize_to_disk: serialize");
14186        assert_eq!(
14187            format!("{err}"),
14188            "unknown compiler spec io stage: Realize_to_disk: serialize"
14189        );
14190
14191        let err: super::UnknownCompilerSpecIoStage = "load_from_disk: write"
14192            .parse::<super::CompilerSpecIoStage>()
14193            .expect_err("unreachable cross-product pair must NOT decode");
14194        assert_eq!(err.0, "load_from_disk: write");
14195        assert_eq!(
14196            format!("{err}"),
14197            "unknown compiler spec io stage: load_from_disk: write"
14198        );
14199
14200        let err: super::UnknownCompilerSpecIoStage = ""
14201            .parse::<super::CompilerSpecIoStage>()
14202            .expect_err("empty input must NOT decode to a CompilerSpecIoStage");
14203        assert_eq!(err.0, "");
14204        assert_eq!(format!("{err}"), "unknown compiler spec io stage: ");
14205    }
14206
14207    #[test]
14208    fn compiler_spec_io_stage_compound_key_separator_is_colon_space() {
14209        // Pin the canonical `": "` compound-key separator bytes
14210        // byte-for-byte on the typed algebra. A regression that
14211        // silently drops the ASCII space (`":"`), doubles it (`":  "`),
14212        // or swaps to a different punctuation family (`" - "`, `" | "`)
14213        // fails-loudly here — every consumer that binds against the
14214        // constant picks up the drift at compile time, not at a
14215        // customer's rendered diagnostic. The exact byte sequence
14216        // matters: LSP substring extractors that key on `rsplit_once(":
14217        // ")` (matching the pre-lift inline literal) and the pre-lift
14218        // `Display` format string `"{}: {}"` both threaded the SAME
14219        // three bytes; post-lift they thread the SAME `pub const`.
14220        assert_eq!(
14221            super::CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR,
14222            ": ",
14223            "compound-key separator must be ASCII colon-space byte-for-byte",
14224        );
14225        assert_eq!(
14226            super::CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR.len(),
14227            2,
14228            "compound-key separator is exactly two bytes — no invisible whitespace drift",
14229        );
14230        assert_eq!(
14231            super::CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR.as_bytes(),
14232            b": ",
14233            "compound-key separator bytes are `:` then ` ` — pinned byte-array shape",
14234        );
14235    }
14236
14237    #[test]
14238    fn compiler_spec_io_stage_display_composes_operation_separator_label_verbatim() {
14239        // For every variant, the `Display` rendering equals the
14240        // triple-concatenation `operation() ++ COMPOUND_KEY_SEPARATOR
14241        // ++ label()` byte-for-byte. A regression that reintroduces
14242        // the pre-lift inline `": "` literal at the `write!` format
14243        // string, or swaps the projection order, or inserts extra
14244        // padding, fails loudly here — the composed reference walks
14245        // the SAME three typed source-of-truth axes the impl body
14246        // does. Sibling posture to
14247        // `compiler_spec_io_stage_operation_method_routes_through_typed_constants`
14248        // and `compiler_spec_io_stage_label_method_routes_through_typed_constants`
14249        // on the operation and label projection axes: the SAME
14250        // through-the-typed-constants routing contract now covers the
14251        // COMPOSITION axis too.
14252        for stage in super::CompilerSpecIoStage::ALL {
14253            let rendered = stage.to_string();
14254            let expected = {
14255                let mut out = String::with_capacity(
14256                    stage.operation().len()
14257                        + super::CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR.len()
14258                        + stage.label().len(),
14259                );
14260                out.push_str(stage.operation());
14261                out.push_str(super::CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR);
14262                out.push_str(stage.label());
14263                out
14264            };
14265            assert_eq!(
14266                rendered, expected,
14267                "Display for {stage:?} must compose `operation` ++ COMPOUND_KEY_SEPARATOR ++ `label` verbatim",
14268            );
14269        }
14270    }
14271
14272    #[test]
14273    fn compiler_spec_io_stage_from_str_uses_typed_separator_bytes() {
14274        // Pin the parse boundary to the typed separator bytes: an
14275        // input that swaps the separator (colon-alone `":"`, space-
14276        // alone `" "`, wrong punctuation `" | "`) fails-loudly even
14277        // if the operation + label slot bytes are otherwise correct.
14278        // A regression that drops the ASCII space from the
14279        // `split_once` argument after a clippy `single_char_pattern`
14280        // suggestion would silently accept `"realize_to_disk:serialize"`
14281        // as a variant — this test rejects it at the parse boundary.
14282        for wrong_separator in [":", "  ", " : ", " ", "|", " - ", "\t"] {
14283            let key = {
14284                let mut out = String::new();
14285                out.push_str(super::CompilerSpecIoStage::RealizeToDiskSerialize.operation());
14286                out.push_str(wrong_separator);
14287                out.push_str(super::CompilerSpecIoStage::RealizeToDiskSerialize.label());
14288                out
14289            };
14290            key.parse::<super::CompilerSpecIoStage>().expect_err(
14291                "compound key with wrong-separator bytes must NOT decode — the parse boundary keys on COMPOUND_KEY_SEPARATOR verbatim",
14292            );
14293        }
14294        // Sanity: constructing the input through the typed constant
14295        // still succeeds — the constant is the source of truth on
14296        // both the Display and the FromStr sides.
14297        let key = {
14298            let mut out = String::new();
14299            out.push_str(super::CompilerSpecIoStage::LoadFromDiskRead.operation());
14300            out.push_str(super::CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR);
14301            out.push_str(super::CompilerSpecIoStage::LoadFromDiskRead.label());
14302            out
14303        };
14304        assert_eq!(
14305            key.parse::<super::CompilerSpecIoStage>()
14306                .expect("compound key composed through COMPOUND_KEY_SEPARATOR must decode"),
14307            super::CompilerSpecIoStage::LoadFromDiskRead,
14308        );
14309    }
14310
14311    #[test]
14312    fn compiler_spec_io_stage_display_middle_bytes_equal_typed_separator() {
14313        // Extract the middle bytes of every variant's `Display`
14314        // rendering — the substring between the operation prefix and
14315        // the label suffix — and pin them to
14316        // `COMPOUND_KEY_SEPARATOR`. This closes the loop from the
14317        // OTHER direction: the two projection-axis tests
14318        // (`operation_method_routes_through_typed_constants`,
14319        // `label_method_routes_through_typed_constants`) pin what
14320        // the operation and label slots ARE at rendering time; this
14321        // test pins what everything BETWEEN them is. Together the
14322        // three tests span every byte of every variant's rendered
14323        // compound key by threading each byte through some typed
14324        // source-of-truth constant on `CompilerSpecIoStage` —
14325        // matching the same span posture `Self::OPERATIONS +
14326        // Self::LABELS` establishes at the (variant → slot bytes)
14327        // aggregate-array level.
14328        for stage in super::CompilerSpecIoStage::ALL {
14329            let rendered = stage.to_string();
14330            let after_op = rendered
14331                .strip_prefix(stage.operation())
14332                .expect("Display must start with operation bytes");
14333            let middle = after_op
14334                .strip_suffix(stage.label())
14335                .expect("Display must end with label bytes");
14336            assert_eq!(
14337                middle,
14338                super::CompilerSpecIoStage::COMPOUND_KEY_SEPARATOR,
14339                "the substring between operation and label in Display for {stage:?} must be COMPOUND_KEY_SEPARATOR verbatim",
14340            );
14341        }
14342    }
14343
14344    // ── TemplateInvariantKind + TemplateInvariant variant ───────────
14345    //
14346    // Closed-set posture for the bytecode-runtime invariant surface in
14347    // `macro_expand.rs::apply_compiled`. The index payload of the Subst /
14348    // Splice gates lives INSIDE the variants (`SubstBadIndex(usize)` /
14349    // `SpliceBadIndex(usize)`), so the invalid combination "stack-gate
14350    // kind with an op-index" (e.g. `EndListEmptyStack` carrying a
14351    // `usize`) is structurally unrepresentable. Display matches the
14352    // legacy `Compile`-shaped diagnostic byte-for-byte through the
14353    // `TemplateInvariantKind::message()` projection so authoring-tool
14354    // substring greps see no drift across the lift.
14355
14356    #[test]
14357    fn template_invariant_kind_message_for_subst_bad_idx() {
14358        // `SubstBadIndex(idx)` projects to the canonical
14359        // `"compiled template referenced bad param index {idx}"`
14360        // shape — byte-for-byte equivalent to the pre-lift inline
14361        // `format!()` at the Subst gate.
14362        assert_eq!(
14363            super::TemplateInvariantKind::SubstBadIndex(99).message(),
14364            "compiled template referenced bad param index 99"
14365        );
14366        assert_eq!(
14367            super::TemplateInvariantKind::SubstBadIndex(0).message(),
14368            "compiled template referenced bad param index 0"
14369        );
14370    }
14371
14372    #[test]
14373    fn template_invariant_kind_message_for_splice_bad_idx() {
14374        // `SpliceBadIndex(idx)` projects to the canonical
14375        // `"compiled template referenced bad splice index {idx}"`
14376        // shape — byte-for-byte equivalent to the pre-lift inline
14377        // `format!()` at the Splice gate. Distinct word (`splice` vs
14378        // `param`) keeps the two gates legible in diagnostic output.
14379        assert_eq!(
14380            super::TemplateInvariantKind::SpliceBadIndex(42).message(),
14381            "compiled template referenced bad splice index 42"
14382        );
14383    }
14384
14385    #[test]
14386    fn template_invariant_kind_message_for_endlist_empty_stack() {
14387        // `EndListEmptyStack` projects to the canonical static-string
14388        // shape — no dynamic payload, no `format!()` overhead. The
14389        // pre-lift inline `&'static str` literal at the EndList gate
14390        // is preserved verbatim.
14391        assert_eq!(
14392            super::TemplateInvariantKind::EndListEmptyStack.message(),
14393            "compiled template: EndList with empty stack"
14394        );
14395    }
14396
14397    #[test]
14398    fn template_invariant_kind_message_for_final_no_value() {
14399        // `FinalNoValue` projects to the canonical static-string
14400        // shape for the post-loop final-pop gate. Preserves the
14401        // pre-lift inline `&'static str` literal verbatim.
14402        assert_eq!(
14403            super::TemplateInvariantKind::FinalNoValue.message(),
14404            "compiled template produced no value"
14405        );
14406    }
14407
14408    #[test]
14409    fn template_invariant_kind_static_message_constants_pin_canonical_bytes() {
14410        // Pin each per-role `pub const *_MESSAGE: &'static str` at its
14411        // exact canonical byte string — the source-of-truth constants
14412        // the `message` projection routes the STATIC 2-of-4 subset
14413        // arms through. A rename here (say, if a future extension
14414        // ports the byte prefix from `"compiled template"` to
14415        // `"compiled macro template"`) must land at ONE `pub const`
14416        // per role rather than at TWO sites (the per-role const AND
14417        // an inline arm literal). Sibling posture to
14418        // `macro_def_head_keyword_constants_pin_canonical_bytes` /
14419        // `unquote_form_label_constants_pin_canonical_bytes` /
14420        // `expected_kwarg_shape_label_constants_pin_canonical_bytes`
14421        // on the peer closed-set surfaces.
14422        assert_eq!(
14423            super::TemplateInvariantKind::END_LIST_EMPTY_STACK_MESSAGE,
14424            "compiled template: EndList with empty stack",
14425            "END_LIST_EMPTY_STACK_MESSAGE must pin the exact mid-loop stack-gate diagnostic bytes",
14426        );
14427        assert_eq!(
14428            super::TemplateInvariantKind::FINAL_NO_VALUE_MESSAGE,
14429            "compiled template produced no value",
14430            "FINAL_NO_VALUE_MESSAGE must pin the exact post-loop final-pop diagnostic bytes",
14431        );
14432    }
14433
14434    #[test]
14435    fn template_invariant_kind_static_messages_pin_two_arm_forced_arity_array() {
14436        // Pin `STATIC_MESSAGES: [&'static str; 2]` as the family-wide
14437        // forced-arity ALL array over the STATIC 2-of-4 subset
14438        // carving. The array literal's arity IS the closed-set-size
14439        // proof — rustc's `[&'static str; 2]` check on the const's
14440        // type fails compilation if a future refactor adds a third
14441        // static arm without extending the array's length in
14442        // lockstep. Cardinality pin at the runtime layer is a
14443        // fail-loud belt-and-braces peer to the compile-time
14444        // `[_; 2]` check: a hypothetical port to `&[&'static str]`
14445        // (a slice, losing the arity discipline) fails here rather
14446        // than silently loosening the closed-set discipline.
14447        assert_eq!(
14448            super::TemplateInvariantKind::STATIC_MESSAGES.len(),
14449            2,
14450            "STATIC_MESSAGES must have exactly two entries — the 2-of-4 static subset carving",
14451        );
14452        assert_eq!(
14453            super::TemplateInvariantKind::STATIC_MESSAGES,
14454            [
14455                super::TemplateInvariantKind::END_LIST_EMPTY_STACK_MESSAGE,
14456                super::TemplateInvariantKind::FINAL_NO_VALUE_MESSAGE,
14457            ],
14458            "STATIC_MESSAGES must compose from the two per-role message constants in declaration order",
14459        );
14460    }
14461
14462    #[test]
14463    fn template_invariant_kind_message_static_projects_static_subset_to_typed_option() {
14464        // Pin `message_static(self) -> Option<&'static str>`'s per-arm
14465        // truth table: the two STATIC arms project to `Some(bytes)`
14466        // routed through the per-role `pub const`s; the two dynamic
14467        // arms (regardless of payload) project to `None`. Payload
14468        // invariance on the dynamic arms is load-bearing: a
14469        // regression that surfaces the payload's `usize` bytes on
14470        // the `Option<&'static str>` axis (say, by lifting the
14471        // format prefix to a per-role static-str) would silently
14472        // widen the static subset — this test pins the current
14473        // 2-of-4 carving.
14474        assert_eq!(
14475            super::TemplateInvariantKind::EndListEmptyStack.message_static(),
14476            Some(super::TemplateInvariantKind::END_LIST_EMPTY_STACK_MESSAGE),
14477        );
14478        assert_eq!(
14479            super::TemplateInvariantKind::FinalNoValue.message_static(),
14480            Some(super::TemplateInvariantKind::FINAL_NO_VALUE_MESSAGE),
14481        );
14482        // Dynamic arms project to None regardless of payload value —
14483        // the `usize` bytes format into `message()`'s dynamic
14484        // fallthrough closure and cannot bind to a `&'static str`
14485        // on this axis.
14486        for idx in [0_usize, 1, 42, 99, usize::MAX] {
14487            assert_eq!(
14488                super::TemplateInvariantKind::SubstBadIndex(idx).message_static(),
14489                None,
14490                "SubstBadIndex({idx}) must project to None — the `usize` payload cannot bind to a &'static str",
14491            );
14492            assert_eq!(
14493                super::TemplateInvariantKind::SpliceBadIndex(idx).message_static(),
14494                None,
14495                "SpliceBadIndex({idx}) must project to None — the `usize` payload cannot bind to a &'static str",
14496            );
14497        }
14498    }
14499
14500    #[test]
14501    fn template_invariant_kind_static_messages_align_with_message_static_by_index() {
14502        // Pin the ALIGNMENT contract:
14503        // `Self::STATIC_MESSAGES[0] == Self::EndListEmptyStack.message_static().unwrap()`
14504        // and
14505        // `Self::STATIC_MESSAGES[1] == Self::FinalNoValue.message_static().unwrap()`
14506        // element-wise. A regression that swaps the array's ordering
14507        // against the projection method's arm order (say, a future
14508        // refactor that reorders the enum variants without
14509        // reordering the array) fails loudly here. Sibling posture
14510        // to `kwarg_path_kind_labels_align_with_all_by_index`,
14511        // `expected_kwarg_shape_labels_align_with_all_by_index`,
14512        // `macro_def_head_keywords_align_with_all_by_index` on the
14513        // peer closed-set surfaces.
14514        assert_eq!(
14515            super::TemplateInvariantKind::STATIC_MESSAGES[0],
14516            super::TemplateInvariantKind::EndListEmptyStack
14517                .message_static()
14518                .unwrap(),
14519        );
14520        assert_eq!(
14521            super::TemplateInvariantKind::STATIC_MESSAGES[1],
14522            super::TemplateInvariantKind::FinalNoValue
14523                .message_static()
14524                .unwrap(),
14525        );
14526    }
14527
14528    #[test]
14529    fn template_invariant_kind_message_delegates_static_arms_through_message_static() {
14530        // Pin the `message()` composition contract: for every arm on
14531        // the STATIC 2-of-4 subset,
14532        // `variant.message() == variant.message_static().unwrap().to_string()`
14533        // — the projection method routes the STATIC arms through
14534        // `message_static()`, and any regression that re-inlines the
14535        // per-role `.into()` at `message()` (undoing the composition)
14536        // still passes the per-variant byte pins but fails THIS
14537        // composition assertion. Sibling posture to a hypothetical
14538        // `OptionalParamMalformedReason::label_delegates_static_arms_through_label_static`
14539        // on the peer 3-of-4 static carving of the sibling optional-
14540        // section-malformed four-arm closed set.
14541        for (variant, static_bytes) in [
14542            (
14543                super::TemplateInvariantKind::EndListEmptyStack,
14544                super::TemplateInvariantKind::END_LIST_EMPTY_STACK_MESSAGE,
14545            ),
14546            (
14547                super::TemplateInvariantKind::FinalNoValue,
14548                super::TemplateInvariantKind::FINAL_NO_VALUE_MESSAGE,
14549            ),
14550        ] {
14551            assert_eq!(
14552                variant.message(),
14553                static_bytes,
14554                "message() for {variant:?} must equal its message_static() bytes — routed through the typed subset projection",
14555            );
14556        }
14557        // Dynamic arms retain the format!() behavior — the composition
14558        // routes them through the fallthrough closure. Pin that the
14559        // outer `map_or_else` did not accidentally swallow the
14560        // payload's `usize` bytes.
14561        assert_eq!(
14562            super::TemplateInvariantKind::SubstBadIndex(7).message(),
14563            "compiled template referenced bad param index 7",
14564        );
14565        assert_eq!(
14566            super::TemplateInvariantKind::SpliceBadIndex(11).message(),
14567            "compiled template referenced bad splice index 11",
14568        );
14569    }
14570
14571    #[test]
14572    fn template_invariant_kind_dynamic_descriptor_constants_pin_canonical_bytes() {
14573        // Per-role `pub const &'static str` truth pins on the DYNAMIC
14574        // 2-of-4 subset carving of the four-arm closed set. Pins the
14575        // canonical descriptor bytes byte-for-byte so a regression on
14576        // any per-role const (a typo like `"parms"` for
14577        // `SUBST_BAD_INDEX_DESCRIPTOR`, a widening drift like
14578        // `"splice-target"` for `SPLICE_BAD_INDEX_DESCRIPTOR`) fails
14579        // loudly here rather than silently drifting the rendered
14580        // diagnostic bytes downstream (through `message()` /
14581        // `LispError::Display`). Peer to the STATIC 2-of-4 subset's
14582        // `template_invariant_kind_static_message_constants_pin_canonical_bytes`.
14583        assert_eq!(
14584            super::TemplateInvariantKind::SUBST_BAD_INDEX_DESCRIPTOR,
14585            "param",
14586        );
14587        assert_eq!(
14588            super::TemplateInvariantKind::SPLICE_BAD_INDEX_DESCRIPTOR,
14589            "splice",
14590        );
14591    }
14592
14593    #[test]
14594    fn template_invariant_kind_dynamic_descriptors_pin_two_arm_forced_arity_array() {
14595        // Pin the ARITY + COMPOSITION contract of the DYNAMIC 2-of-4
14596        // subset ALL array. A regression that grows the array
14597        // without adding a per-role const + `descriptor_dynamic` arm
14598        // fails compilation (forced-arity `[&'static str; 2]`); a
14599        // regression that reorders the array against the projection
14600        // method's arm order fails
14601        // `template_invariant_kind_dynamic_descriptors_align_with_descriptor_dynamic_by_index`
14602        // in lockstep. Cardinality pin here is a fail-loud belt-and-
14603        // braces peer to the compile-time `[_; 2]` check.
14604        assert_eq!(
14605            super::TemplateInvariantKind::DYNAMIC_DESCRIPTORS.len(),
14606            2,
14607            "DYNAMIC_DESCRIPTORS must have exactly two entries — the 2-of-4 dynamic subset carving",
14608        );
14609        assert_eq!(
14610            super::TemplateInvariantKind::DYNAMIC_DESCRIPTORS,
14611            [
14612                super::TemplateInvariantKind::SUBST_BAD_INDEX_DESCRIPTOR,
14613                super::TemplateInvariantKind::SPLICE_BAD_INDEX_DESCRIPTOR,
14614            ],
14615            "DYNAMIC_DESCRIPTORS must compose from the two per-role descriptor constants in declaration order",
14616        );
14617    }
14618
14619    #[test]
14620    fn template_invariant_kind_descriptor_dynamic_projects_dynamic_subset_to_typed_option() {
14621        // Pin `descriptor_dynamic(self) -> Option<&'static str>`'s
14622        // per-arm truth table: the two DYNAMIC arms project to
14623        // `Some(descriptor)` routed through the per-role `pub const`s
14624        // regardless of `usize` payload value; the two STATIC arms
14625        // project to `None`. Payload invariance on the DYNAMIC arms is
14626        // load-bearing: a regression that surfaces the payload's
14627        // `usize` bytes on the descriptor axis (say, by folding the
14628        // idx into a Format-typed descriptor slot) would silently
14629        // widen the DYNAMIC descriptor from a per-arm noun to a
14630        // per-callsite string — this test pins the current per-arm
14631        // per-noun carving. Sibling posture to
14632        // `template_invariant_kind_message_static_projects_static_subset_to_typed_option`.
14633        for idx in [0_usize, 1, 42, 99, usize::MAX] {
14634            assert_eq!(
14635                super::TemplateInvariantKind::SubstBadIndex(idx).descriptor_dynamic(),
14636                Some(super::TemplateInvariantKind::SUBST_BAD_INDEX_DESCRIPTOR),
14637                "SubstBadIndex({idx}) must project to Some(SUBST_BAD_INDEX_DESCRIPTOR) — descriptor is per-arm, not per-idx",
14638            );
14639            assert_eq!(
14640                super::TemplateInvariantKind::SpliceBadIndex(idx).descriptor_dynamic(),
14641                Some(super::TemplateInvariantKind::SPLICE_BAD_INDEX_DESCRIPTOR),
14642                "SpliceBadIndex({idx}) must project to Some(SPLICE_BAD_INDEX_DESCRIPTOR) — descriptor is per-arm, not per-idx",
14643            );
14644        }
14645        // STATIC arms project to None on the descriptor axis — their
14646        // full bytes bind on the message_static() axis, and they carry
14647        // no per-role descriptor slot to project.
14648        assert_eq!(
14649            super::TemplateInvariantKind::EndListEmptyStack.descriptor_dynamic(),
14650            None,
14651        );
14652        assert_eq!(
14653            super::TemplateInvariantKind::FinalNoValue.descriptor_dynamic(),
14654            None,
14655        );
14656    }
14657
14658    #[test]
14659    fn template_invariant_kind_dynamic_descriptors_align_with_descriptor_dynamic_by_index() {
14660        // Pin the ALIGNMENT contract:
14661        // `Self::DYNAMIC_DESCRIPTORS[0] == Self::SubstBadIndex(_).descriptor_dynamic().unwrap()`
14662        // and
14663        // `Self::DYNAMIC_DESCRIPTORS[1] == Self::SpliceBadIndex(_).descriptor_dynamic().unwrap()`
14664        // element-wise. A regression that swaps the array's ordering
14665        // against the projection method's arm order fails loudly
14666        // here. Sibling posture to
14667        // `template_invariant_kind_static_messages_align_with_message_static_by_index`.
14668        assert_eq!(
14669            super::TemplateInvariantKind::DYNAMIC_DESCRIPTORS[0],
14670            super::TemplateInvariantKind::SubstBadIndex(0)
14671                .descriptor_dynamic()
14672                .unwrap(),
14673        );
14674        assert_eq!(
14675            super::TemplateInvariantKind::DYNAMIC_DESCRIPTORS[1],
14676            super::TemplateInvariantKind::SpliceBadIndex(0)
14677                .descriptor_dynamic()
14678                .unwrap(),
14679        );
14680    }
14681
14682    #[test]
14683    fn template_invariant_kind_message_dynamic_projects_dynamic_subset_to_typed_option() {
14684        // Pin `message_dynamic(self) -> Option<String>`'s per-arm
14685        // truth table: the two DYNAMIC arms project to
14686        // `Some(rendered)` composing the shared
14687        // `"compiled template referenced bad {descriptor} index {idx}"`
14688        // template with their per-arm descriptor const + payload; the
14689        // two STATIC arms project to `None`. The DYNAMIC-subset bytes
14690        // are byte-for-byte equivalent to the pre-lift inline
14691        // `format!(...)` arms — authoring-tool substring greps see no
14692        // drift across the lift.
14693        for idx in [0_usize, 1, 42, 99] {
14694            assert_eq!(
14695                super::TemplateInvariantKind::SubstBadIndex(idx).message_dynamic(),
14696                Some(format!("compiled template referenced bad param index {idx}")),
14697                "SubstBadIndex({idx}).message_dynamic() must render the shared template with the `param` descriptor and idx payload",
14698            );
14699            assert_eq!(
14700                super::TemplateInvariantKind::SpliceBadIndex(idx).message_dynamic(),
14701                Some(format!("compiled template referenced bad splice index {idx}")),
14702                "SpliceBadIndex({idx}).message_dynamic() must render the shared template with the `splice` descriptor and idx payload",
14703            );
14704        }
14705        // STATIC arms project to None on the message_dynamic axis —
14706        // their full bytes bind on the message_static() axis and carry
14707        // no `usize` payload to format into the shared template.
14708        assert_eq!(
14709            super::TemplateInvariantKind::EndListEmptyStack.message_dynamic(),
14710            None,
14711        );
14712        assert_eq!(
14713            super::TemplateInvariantKind::FinalNoValue.message_dynamic(),
14714            None,
14715        );
14716    }
14717
14718    #[test]
14719    fn template_invariant_kind_static_dynamic_projections_partition_closed_set() {
14720        // Pin the PARTITION contract: for every legal variant, EXACTLY
14721        // ONE of `message_static(self)` and `descriptor_dynamic(self)`
14722        // returns `Some`. Together the two typed subset projections
14723        // partition the four-arm closed set into its STATIC ⊕ DYNAMIC
14724        // halves at zero allocation cost. The partition is what makes
14725        // `message()`'s `.expect(...)` structurally unreachable —
14726        // rustc's exhaustive-match checks on both projections keep the
14727        // partition sound; this test pins the runtime witness. A
14728        // regression that widens EITHER subset (say, a lift that
14729        // projects `SubstBadIndex` into a per-role STATIC message)
14730        // fails loudly here because both projections would return
14731        // `Some` for the same variant.
14732        for variant in [
14733            super::TemplateInvariantKind::EndListEmptyStack,
14734            super::TemplateInvariantKind::FinalNoValue,
14735            super::TemplateInvariantKind::SubstBadIndex(0),
14736            super::TemplateInvariantKind::SubstBadIndex(usize::MAX),
14737            super::TemplateInvariantKind::SpliceBadIndex(0),
14738            super::TemplateInvariantKind::SpliceBadIndex(usize::MAX),
14739        ] {
14740            let static_hit = variant.message_static().is_some();
14741            let dynamic_hit = variant.descriptor_dynamic().is_some();
14742            assert!(
14743                static_hit ^ dynamic_hit,
14744                "message_static/descriptor_dynamic must partition the closed set — \
14745                 exactly one projection returns Some for {variant:?}, got static_hit={static_hit} dynamic_hit={dynamic_hit}",
14746            );
14747        }
14748    }
14749
14750    #[test]
14751    fn template_invariant_kind_message_delegates_dynamic_arms_through_message_dynamic() {
14752        // Pin the `message()` composition contract on the DYNAMIC side:
14753        // for every arm on the DYNAMIC 2-of-4 subset,
14754        // `variant.message() == variant.message_dynamic().unwrap()`
14755        // — the projection method routes the DYNAMIC arms through
14756        // `message_dynamic()` (which itself routes through
14757        // `descriptor_dynamic()`), and any regression that re-inlines
14758        // the per-arm `format!(...)` at `message()` (undoing the
14759        // composition) still passes the per-variant byte pins but
14760        // fails THIS composition assertion. Peer to
14761        // `template_invariant_kind_message_delegates_static_arms_through_message_static`.
14762        for variant in [
14763            super::TemplateInvariantKind::SubstBadIndex(0),
14764            super::TemplateInvariantKind::SubstBadIndex(99),
14765            super::TemplateInvariantKind::SubstBadIndex(usize::MAX),
14766            super::TemplateInvariantKind::SpliceBadIndex(0),
14767            super::TemplateInvariantKind::SpliceBadIndex(42),
14768            super::TemplateInvariantKind::SpliceBadIndex(usize::MAX),
14769        ] {
14770            assert_eq!(
14771                variant.message(),
14772                variant.message_dynamic().unwrap(),
14773                "message() for {variant:?} must equal its message_dynamic() bytes — routed through the typed subset projection",
14774            );
14775        }
14776    }
14777
14778    #[test]
14779    fn template_invariant_display_renders_legacy_compile_shape_for_subst_bad_idx() {
14780        // End-to-end through the `LispError` Display impl — pins the
14781        // rendered diagnostic byte-for-byte: `"compile error in
14782        // {macro_name}: compiled template referenced bad param index
14783        // {idx}"`. Authoring tools that substring-grep the rendered
14784        // diagnostic (`tatara-check`, REPL substring-greps) see no
14785        // drift across the lift from the pre-lift `Compile { form:
14786        // macro_name, message: format!(...) }` shape.
14787        let err = LispError::TemplateInvariant {
14788            macro_name: "test-macro".into(),
14789            kind: super::TemplateInvariantKind::SubstBadIndex(99),
14790        };
14791        assert_eq!(
14792            format!("{err}"),
14793            "compile error in test-macro: compiled template referenced bad param index 99"
14794        );
14795    }
14796
14797    #[test]
14798    fn template_invariant_display_renders_legacy_compile_shape_for_splice_bad_idx() {
14799        // Sibling Display test for the Splice gate. Pins the message
14800        // byte-for-byte: `"compile error in call-macro: compiled
14801        // template referenced bad splice index 42"`.
14802        let err = LispError::TemplateInvariant {
14803            macro_name: "call-macro".into(),
14804            kind: super::TemplateInvariantKind::SpliceBadIndex(42),
14805        };
14806        assert_eq!(
14807            format!("{err}"),
14808            "compile error in call-macro: compiled template referenced bad splice index 42"
14809        );
14810    }
14811
14812    #[test]
14813    fn template_invariant_display_renders_legacy_compile_shape_for_endlist() {
14814        // Sibling Display test for the EndList gate. Pins the static-
14815        // message byte-for-byte: `"compile error in wrap: compiled
14816        // template: EndList with empty stack"`. Even though this gate
14817        // is currently guarded by `last_mut().unwrap()` and not
14818        // reachable through any single CompiledTemplate, the structural
14819        // variant carries the canonical message verbatim — defensive
14820        // against future changes to the stack discipline.
14821        let err = LispError::TemplateInvariant {
14822            macro_name: "wrap".into(),
14823            kind: super::TemplateInvariantKind::EndListEmptyStack,
14824        };
14825        assert_eq!(
14826            format!("{err}"),
14827            "compile error in wrap: compiled template: EndList with empty stack"
14828        );
14829    }
14830
14831    #[test]
14832    fn template_invariant_display_renders_legacy_compile_shape_for_final_no_value() {
14833        // Sibling Display test for the final-no-value gate. Pins the
14834        // static-message byte-for-byte: `"compile error in id: compiled
14835        // template produced no value"`. Closes the structural-
14836        // completeness floor of the closed-set `TemplateInvariantKind`
14837        // × Display matrix — all four reachable kinds are pinned.
14838        let err = LispError::TemplateInvariant {
14839            macro_name: "id".into(),
14840            kind: super::TemplateInvariantKind::FinalNoValue,
14841        };
14842        assert_eq!(
14843            format!("{err}"),
14844            "compile error in id: compiled template produced no value"
14845        );
14846    }
14847
14848    #[test]
14849    fn template_invariant_display_preserves_legacy_substring_for_message_grep() {
14850        // Pin the legacy substring set — `"compiled template"`,
14851        // `"bad param index"`, `"bad splice index"`, `"EndList with
14852        // empty stack"`, `"produced no value"` — as a separate
14853        // assertion so a regression that drifts ANY of the four
14854        // surface words (e.g., to "invalid", "missing", "no result")
14855        // fails-loudly here. The substrings are what consumers
14856        // downstream (`tatara-check`, REPL) substring-match on today.
14857        let subst = LispError::TemplateInvariant {
14858            macro_name: "m".into(),
14859            kind: super::TemplateInvariantKind::SubstBadIndex(0),
14860        };
14861        let msg = format!("{subst}");
14862        assert!(
14863            msg.contains("compiled template"),
14864            "expected `compiled template` prefix, got: {msg}"
14865        );
14866        assert!(
14867            msg.contains("bad param index"),
14868            "expected `bad param index` substring, got: {msg}"
14869        );
14870
14871        let splice = LispError::TemplateInvariant {
14872            macro_name: "m".into(),
14873            kind: super::TemplateInvariantKind::SpliceBadIndex(0),
14874        };
14875        let msg = format!("{splice}");
14876        assert!(
14877            msg.contains("bad splice index"),
14878            "expected `bad splice index` substring, got: {msg}"
14879        );
14880
14881        let endlist = LispError::TemplateInvariant {
14882            macro_name: "m".into(),
14883            kind: super::TemplateInvariantKind::EndListEmptyStack,
14884        };
14885        let msg = format!("{endlist}");
14886        assert!(
14887            msg.contains("EndList with empty stack"),
14888            "expected `EndList with empty stack` substring, got: {msg}"
14889        );
14890
14891        let final_nv = LispError::TemplateInvariant {
14892            macro_name: "m".into(),
14893            kind: super::TemplateInvariantKind::FinalNoValue,
14894        };
14895        let msg = format!("{final_nv}");
14896        assert!(
14897            msg.contains("produced no value"),
14898            "expected `produced no value` substring, got: {msg}"
14899        );
14900    }
14901
14902    #[test]
14903    fn template_invariant_kind_is_copy_and_partial_eq() {
14904        // Pin the closed-set posture: `TemplateInvariantKind` derives
14905        // Copy + PartialEq + Eq + Debug so it composes ergonomically
14906        // in tests and in consumer pattern-matches (no clone-and-own
14907        // dance). Same posture as `CompilerSpecIoStage` and
14908        // `MacroDefHead`. A regression that drops Copy fails-loudly
14909        // here (the let-binding would move out instead of copy).
14910        let kind = super::TemplateInvariantKind::SubstBadIndex(7);
14911        let copied = kind;
14912        assert_eq!(kind, copied);
14913        assert_eq!(kind, super::TemplateInvariantKind::SubstBadIndex(7));
14914        assert_ne!(kind, super::TemplateInvariantKind::SubstBadIndex(8));
14915        assert_ne!(kind, super::TemplateInvariantKind::SpliceBadIndex(7));
14916        assert_ne!(kind, super::TemplateInvariantKind::EndListEmptyStack);
14917    }
14918
14919    #[test]
14920    fn template_invariant_kind_index_payload_is_structurally_scoped_to_index_carrying_variants() {
14921        // The closed-set invariant: only `SubstBadIndex` and
14922        // `SpliceBadIndex` carry a `usize` payload; `EndListEmptyStack`
14923        // and `FinalNoValue` are bare. This is enforced by the variant
14924        // shape itself — there is no way to construct
14925        // `EndListEmptyStack(7)` because the variant has no fields.
14926        // This test pins the structural shape: the four reachable
14927        // failure modes split 2+2 into "index-carrying" and "bare".
14928        // A regression that adds a payload to the bare variants (or
14929        // strips it from the index-carrying ones) fails to compile,
14930        // making this test redundant — but the test documents the
14931        // shape for readers walking the closed set.
14932        match super::TemplateInvariantKind::SubstBadIndex(5) {
14933            super::TemplateInvariantKind::SubstBadIndex(idx) => assert_eq!(idx, 5),
14934            other => panic!("expected SubstBadIndex, got {other:?}"),
14935        }
14936        match super::TemplateInvariantKind::EndListEmptyStack {
14937            super::TemplateInvariantKind::EndListEmptyStack => {}
14938            other => panic!("expected EndListEmptyStack, got {other:?}"),
14939        }
14940    }
14941
14942    // --- MacroDefHead typed-slot lift (the closed-set promotion) ---
14943    //
14944    // The next eight tests pin the typed-slot promotion that closes
14945    // the three-times rule across the `LispError::Defmacro*` family.
14946    // Before this lift the three variants' `head` slot was
14947    // `&'static str`, projected from a `MacroDefHead` via
14948    // `head.keyword()` at the helper boundary; consumers had to
14949    // substring-compare against three string literals to recognize
14950    // a head identity. After the lift the slot IS the typed enum,
14951    // so authoring tools (REPL, LSP, `tatara-check`) pattern-match
14952    // on `MacroDefHead::Defmacro` etc. directly — same posture as
14953    // `CompilerSpecIoStage` for `LispError::CompilerSpecIo` and
14954    // `TemplateInvariantKind` for `LispError::TemplateInvariant`.
14955
14956    #[test]
14957    fn defmacro_arity_head_slot_is_macro_def_head_not_static_str() {
14958        // Pin that the `head` slot of `LispError::DefmacroArity` is
14959        // `MacroDefHead` (the typed closed-set enum), not `&'static
14960        // str`. A regression that reverts the slot to `&'static str`
14961        // breaks the typed binding here at compile time; a
14962        // construction with a stringly-typed head would fail to
14963        // construct. This test is the structural-completeness pin
14964        // for the typed-slot promotion, parallel to how
14965        // `compiler_spec_io_carries_typed_stage_field` (if it
14966        // existed) would pin `LispError::CompilerSpecIo.stage`.
14967        let err = LispError::DefmacroArity {
14968            head: MacroDefHead::Defmacro,
14969            arity: 1,
14970        };
14971        match err {
14972            LispError::DefmacroArity { head, arity } => {
14973                assert_eq!(head, MacroDefHead::Defmacro);
14974                assert_eq!(arity, 1);
14975            }
14976            other => panic!("expected DefmacroArity, got {other:?}"),
14977        }
14978    }
14979
14980    #[test]
14981    fn defmacro_non_symbol_name_head_slot_is_macro_def_head_not_static_str() {
14982        // Sibling pin of `defmacro_arity_head_slot_is_macro_def_head_not_static_str`
14983        // for the `LispError::DefmacroNonSymbolName` variant. The
14984        // `head` slot carries `MacroDefHead` directly so consumers
14985        // bind on variant identity (`MacroDefHead::DefpointTemplate`)
14986        // instead of substring-matching the rendered diagnostic.
14987        let err = LispError::DefmacroNonSymbolName {
14988            head: MacroDefHead::DefpointTemplate,
14989            got: SexpWitness::new(SexpShape::Int, "5"),
14990        };
14991        match err {
14992            LispError::DefmacroNonSymbolName { head, got } => {
14993                assert_eq!(head, MacroDefHead::DefpointTemplate);
14994                assert_eq!(got.shape, SexpShape::Int);
14995                assert_eq!(got.display, "5");
14996            }
14997            other => panic!("expected DefmacroNonSymbolName, got {other:?}"),
14998        }
14999    }
15000
15001    #[test]
15002    fn defmacro_non_list_params_head_slot_is_macro_def_head_not_static_str() {
15003        // Sibling pin of `defmacro_arity_head_slot_is_macro_def_head_not_static_str`
15004        // for the `LispError::DefmacroNonListParams` variant. The
15005        // `head` slot carries `MacroDefHead` directly so consumers
15006        // pattern-match on `MacroDefHead::Defcheck` etc. for the
15007        // workspace-coherence authoring surface's third head
15008        // keyword.
15009        let err = LispError::DefmacroNonListParams {
15010            head: MacroDefHead::Defcheck,
15011            got: SexpWitness::new(SexpShape::Int, "7"),
15012        };
15013        match err {
15014            LispError::DefmacroNonListParams { head, got } => {
15015                assert_eq!(head, MacroDefHead::Defcheck);
15016                assert_eq!(got.shape, SexpShape::Int);
15017                assert_eq!(got.display, "7");
15018            }
15019            other => panic!("expected DefmacroNonListParams, got {other:?}"),
15020        }
15021    }
15022
15023    #[test]
15024    fn macro_def_head_display_renders_canonical_keyword_for_each_variant() {
15025        // Pin `MacroDefHead`'s Display impl — it must project through
15026        // `keyword()` so the `#[error(...)]` annotation on each
15027        // `LispError::Defmacro*` variant renders the canonical
15028        // `&'static str` literal byte-for-byte. The Display
15029        // bidirection is `MacroDefHead → &'static str`; the inverse
15030        // (`&str → Option<MacroDefHead>`) lives in `from_keyword`.
15031        // Together the two close the bidirectional projection on the
15032        // closed set.
15033        assert_eq!(format!("{}", MacroDefHead::Defmacro), "defmacro");
15034        assert_eq!(
15035            format!("{}", MacroDefHead::DefpointTemplate),
15036            "defpoint-template"
15037        );
15038        assert_eq!(format!("{}", MacroDefHead::Defcheck), "defcheck");
15039    }
15040
15041    // ── `MacroDefHead::{DEFMACRO_KEYWORD, DEFPOINT_TEMPLATE_KEYWORD,
15042    // DEFCHECK_KEYWORD, KEYWORDS}` — the typed per-role head-keyword
15043    // algebra ─────────────────────────────────────────────────────
15044    //
15045    // Sibling posture to
15046    // `crate::macro_expand::MacroParams::{REST_MARKER,
15047    // OPTIONAL_MARKER, LAMBDA_LIST_KEYWORDS}` — the per-role canonical
15048    // `&'static str` marker + closed-set `[&'static str; N]` array
15049    // pattern applied to the `MacroDefHead` closed set. Pre-lift the
15050    // three canonical keyword literals lived at TWO structural sites
15051    // — the `Self::keyword` match arm AND
15052    // `macro_def_head_display_renders_canonical_keyword_for_each_variant`'s
15053    // per-variant `assert_eq!` truth-table — plus at consumer
15054    // test-site literals in `crate::ast` / `crate::compile`. Post-lift
15055    // each (variant, canonical `&'static str`) pairing binds at ONE
15056    // `pub const` on the typed [`MacroDefHead`] algebra the
15057    // [`Self::keyword`] arm routes through.
15058
15059    #[test]
15060    fn macro_def_head_defmacro_keyword_projects_canonical_defmacro_bytes() {
15061        // Pins the exact `"defmacro"` bytes at the typed constant.
15062        // A regression that drifts the constant (e.g. typo
15063        // `"def-macro"` with a hyphen, `"defMacro"` in camelCase, or
15064        // an accidental trailing whitespace) fails-loudly here.
15065        // This is the single site the substrate's canonical-Lisp
15066        // macro-definition head keyword resolves to; every downstream
15067        // consumer (Display, FromStr, tests, LSP completion, metric
15068        // labels) routes through this constant.
15069        assert_eq!(
15070            MacroDefHead::DEFMACRO_KEYWORD,
15071            "defmacro",
15072            "MacroDefHead::DEFMACRO_KEYWORD drifted from the substrate- \
15073             canonical CL-style macro-definition head keyword `\"defmacro\"`"
15074        );
15075    }
15076
15077    #[test]
15078    fn macro_def_head_defpoint_template_keyword_projects_canonical_defpoint_template_bytes() {
15079        // Pins the exact `"defpoint-template"` bytes at the typed
15080        // constant. The hyphen (NOT an underscore, NOT a camel-case
15081        // capital) is load-bearing: it matches the caixa Aplicacao
15082        // authoring surface's convention where multi-word Lisp-style
15083        // identifiers use kebab-case rather than snake_case.
15084        assert_eq!(
15085            MacroDefHead::DEFPOINT_TEMPLATE_KEYWORD,
15086            "defpoint-template",
15087            "MacroDefHead::DEFPOINT_TEMPLATE_KEYWORD drifted from the substrate- \
15088             canonical K8s-as-processes authoring-surface head keyword \
15089             `\"defpoint-template\"`"
15090        );
15091    }
15092
15093    #[test]
15094    fn macro_def_head_defcheck_keyword_projects_canonical_defcheck_bytes() {
15095        // Pins the exact `"defcheck"` bytes at the typed constant.
15096        // The `tatara-reconciler/checks.lisp` workspace-coherence
15097        // driver reads this exact keyword to classify `(defcheck …)`
15098        // forms; any drift here silently breaks the check runner.
15099        assert_eq!(
15100            MacroDefHead::DEFCHECK_KEYWORD,
15101            "defcheck",
15102            "MacroDefHead::DEFCHECK_KEYWORD drifted from the substrate- \
15103             canonical workspace-coherence authoring-surface head keyword \
15104             `\"defcheck\"`"
15105        );
15106    }
15107
15108    #[test]
15109    fn macro_def_head_keyword_method_routes_through_typed_constants() {
15110        // PATH-UNIFORMITY: the inherent `Self::keyword(self)` method
15111        // MUST return the per-role `pub const` byte-for-byte for each
15112        // reachable variant. A regression that reverts ONE arm to an
15113        // inline `"defmacro"` string literal (e.g. a merge-conflict
15114        // resolution that picked the pre-lift form) silently
15115        // reintroduces the ≥2 PRIME-DIRECTIVE trigger the lift
15116        // resolved — this test catches that by pinning the arm's
15117        // return value to the constant, so the two paths (inline vs.
15118        // typed constant) cannot both hold.
15119        assert_eq!(
15120            MacroDefHead::Defmacro.keyword(),
15121            MacroDefHead::DEFMACRO_KEYWORD,
15122            "MacroDefHead::Defmacro.keyword() drifted from \
15123             MacroDefHead::DEFMACRO_KEYWORD — the match arm reverted to \
15124             an inline literal"
15125        );
15126        assert_eq!(
15127            MacroDefHead::DefpointTemplate.keyword(),
15128            MacroDefHead::DEFPOINT_TEMPLATE_KEYWORD,
15129            "MacroDefHead::DefpointTemplate.keyword() drifted from \
15130             MacroDefHead::DEFPOINT_TEMPLATE_KEYWORD — the match arm \
15131             reverted to an inline literal"
15132        );
15133        assert_eq!(
15134            MacroDefHead::Defcheck.keyword(),
15135            MacroDefHead::DEFCHECK_KEYWORD,
15136            "MacroDefHead::Defcheck.keyword() drifted from \
15137             MacroDefHead::DEFCHECK_KEYWORD — the match arm reverted to \
15138             an inline literal"
15139        );
15140    }
15141
15142    #[test]
15143    fn macro_def_head_keywords_has_expected_cardinality() {
15144        // Cardinality contract: `Self::KEYWORDS.len() == 3` — pinned
15145        // at the declaration site by rustc's forced-arity check on
15146        // `[&'static str; 3]`. This test surfaces the arity as a
15147        // fail-loud runtime pin so a future refactor that switches
15148        // the array type to `&[&'static str]` (dropping the compile-
15149        // time arity forcing) doesn't silently loosen the closed-set
15150        // discipline the family relies on. Sibling posture to
15151        // `macro_params_lambda_list_keywords_has_expected_cardinality`
15152        // on the `MacroParams::LAMBDA_LIST_KEYWORDS` family.
15153        assert_eq!(
15154            MacroDefHead::KEYWORDS.len(),
15155            3,
15156            "MacroDefHead::KEYWORDS cardinality drifted from 3 — the \
15157             head-keyword closed set gained or lost a variant without \
15158             the ALL / KEYWORDS pair being updated in tandem"
15159        );
15160    }
15161
15162    #[test]
15163    fn macro_def_head_keywords_align_with_all_by_index() {
15164        // ALIGNMENT CONTRACT: `Self::KEYWORDS[i] ==
15165        // Self::ALL[i].keyword()` element-wise. The two ALL arrays
15166        // (typed variants, canonical `&'static str` keywords) share
15167        // ONE canonical declaration order — a regression that
15168        // reorders ONE array without reordering the other silently
15169        // misaligns every `zip(Self::ALL, Self::KEYWORDS)` consumer
15170        // (LSP completion providers, metric-label emitters, coverage
15171        // reporters). Pinning by-index alignment here catches the
15172        // reorder at test time.
15173        assert_eq!(
15174            MacroDefHead::KEYWORDS.len(),
15175            MacroDefHead::ALL.len(),
15176            "MacroDefHead::KEYWORDS and MacroDefHead::ALL diverged in \
15177             cardinality — the per-role constants and enum variants must \
15178             stay in lockstep"
15179        );
15180        for (i, head) in MacroDefHead::ALL.iter().enumerate() {
15181            assert_eq!(
15182                MacroDefHead::KEYWORDS[i],
15183                head.keyword(),
15184                "MacroDefHead::KEYWORDS[{i}] `{kw}` drifted from \
15185                 MacroDefHead::ALL[{i}].keyword() `{via_variant}` — the \
15186                 canonical declaration order of the two ALL arrays must \
15187                 match element-wise",
15188                kw = MacroDefHead::KEYWORDS[i],
15189                via_variant = head.keyword(),
15190            );
15191        }
15192    }
15193
15194    #[test]
15195    fn macro_def_head_keywords_pairwise_distinct() {
15196        // PAIRWISE DISJOINTNESS: the three `&'static str` keywords on
15197        // the head-keyword algebra MUST differ so the derive-generated
15198        // FromStr's linear sweep cannot route two variants through
15199        // the same arm. Family-wide sweep over KEYWORDS × KEYWORDS —
15200        // supersedes any single per-pair assertion and picks up new
15201        // variants mechanically when the array grows. Sibling posture
15202        // to `macro_params_lambda_list_keywords_pairwise_distinct` on
15203        // the CL lambda-list-keyword family.
15204        for (i, a) in MacroDefHead::KEYWORDS.iter().enumerate() {
15205            for (j, b) in MacroDefHead::KEYWORDS.iter().enumerate() {
15206                if i == j {
15207                    continue;
15208                }
15209                assert_ne!(
15210                    a, b,
15211                    "MacroDefHead::KEYWORDS[{i}] `{a}` collides with \
15212                     MacroDefHead::KEYWORDS[{j}] `{b}` — the derive- \
15213                     generated FromStr sweep would route two variants \
15214                     through the same arm"
15215                );
15216            }
15217        }
15218    }
15219
15220    #[test]
15221    fn macro_def_head_keywords_all_round_trip_through_from_str() {
15222        // Family-wide bidirection: every entry of `Self::KEYWORDS`
15223        // MUST parse back to some `MacroDefHead` variant AND that
15224        // variant's `keyword()` MUST equal the original entry.
15225        // Sibling posture to `macro_def_head_keyword_round_trips_through_from_str`
15226        // (which sweeps over `Self::ALL`); this test sweeps over the
15227        // per-role `&'static str` constants directly, catching a
15228        // regression where one of the three constants drifts from
15229        // the corresponding `Self::keyword` arm (e.g. `DEFCHECK_KEYWORD`
15230        // set to `"defvalidation"` while `Self::Defcheck.keyword()`
15231        // still returns `Self::DEFCHECK_KEYWORD`, which would parse
15232        // to `MacroDefHead::Defcheck` correctly but break every
15233        // downstream consumer that pinned the literal `"defcheck"`
15234        // bytes).
15235        for kw in MacroDefHead::KEYWORDS {
15236            let parsed: MacroDefHead = kw.parse().unwrap_or_else(|_| {
15237                panic!(
15238                    "MacroDefHead::KEYWORDS entry `{kw}` failed to parse — \
15239                     the entry drifted from Self::keyword"
15240                )
15241            });
15242            assert_eq!(
15243                parsed.keyword(),
15244                kw,
15245                "MacroDefHead::KEYWORDS entry `{kw}` parses to a variant \
15246                 whose keyword() `{}` does not match — the constant and \
15247                 the match arm drifted apart",
15248                parsed.keyword(),
15249            );
15250        }
15251    }
15252
15253    #[test]
15254    fn defmacro_arity_display_renders_legacy_prefix_via_macro_def_head_display() {
15255        // End-to-end through `LispError`'s Display: the typed `head:
15256        // MacroDefHead` slot projects to the canonical `&'static
15257        // str` literal at render time via `MacroDefHead`'s Display
15258        // impl, so the rendered diagnostic is byte-for-byte
15259        // identical to the pre-lift `head: &'static str` shape.
15260        // Authoring tools that substring-match on `"compile error
15261        // in defmacro:"` see no drift across the typed-slot
15262        // promotion.
15263        let err = LispError::DefmacroArity {
15264            head: MacroDefHead::Defmacro,
15265            arity: 2,
15266        };
15267        assert_eq!(
15268            format!("{err}"),
15269            "compile error in defmacro: (defmacro name (params) body) required \
15270             (got 2 elements, need 4)"
15271        );
15272    }
15273
15274    #[test]
15275    fn defmacro_non_symbol_name_display_renders_via_macro_def_head_display_for_defpoint_template() {
15276        // Sibling end-to-end test for the `defpoint-template` head:
15277        // pins that the typed-slot promotion preserves the
15278        // K8s-as-processes authoring surface's diagnostic shape
15279        // byte-for-byte. A regression that drifts `MacroDefHead`'s
15280        // Display impl (e.g. returns `"DefpointTemplate"` instead of
15281        // `"defpoint-template"`) fails-loudly here.
15282        let err = LispError::DefmacroNonSymbolName {
15283            head: MacroDefHead::DefpointTemplate,
15284            got: SexpWitness::new(SexpShape::Keyword, ":foo"),
15285        };
15286        assert_eq!(
15287            format!("{err}"),
15288            "compile error in defpoint-template: expected name symbol, got :foo"
15289        );
15290    }
15291
15292    #[test]
15293    fn defmacro_non_list_params_display_renders_via_macro_def_head_display_for_defcheck() {
15294        // Sibling end-to-end test for the `defcheck` head: pins that
15295        // the typed-slot promotion preserves the workspace-coherence
15296        // authoring surface's diagnostic shape byte-for-byte.
15297        let err = LispError::DefmacroNonListParams {
15298            head: MacroDefHead::Defcheck,
15299            got: SexpWitness::new(SexpShape::Symbol, "x"),
15300        };
15301        assert_eq!(
15302            format!("{err}"),
15303            "compile error in defcheck: expected param list, got x"
15304        );
15305    }
15306
15307    #[test]
15308    fn macro_def_head_is_copy_and_partial_eq_for_pattern_match_ergonomics() {
15309        // Pin that `MacroDefHead` derives `Copy + PartialEq + Eq +
15310        // Debug + Clone` — the posture every closed-set typed enum
15311        // in this module shares (`CompilerSpecIoStage`,
15312        // `TemplateInvariantKind`). Copy lets consumers pattern-match
15313        // on the variant without explicit cloning; `PartialEq + Eq`
15314        // makes `assert_eq!` and `matches!` ergonomic; `Debug` makes
15315        // the `other => panic!("got {other:?}")` shape ergonomic at
15316        // assertion sites. A regression that drops any of these
15317        // derives breaks compilation here.
15318        let h = MacroDefHead::Defmacro;
15319        let h_copy: MacroDefHead = h; // Copy
15320        assert_eq!(h, h_copy); // PartialEq
15321        assert!(matches!(h, MacroDefHead::Defmacro)); // exhaustive match
15322        let _: String = format!("{h:?}"); // Debug
15323    }
15324
15325    // --- UnquoteForm typed-slot lift (the closed-set promotion) ---
15326    //
15327    // The next six tests pin the typed-slot promotion that closes the
15328    // three-times rule across `LispError::UnboundTemplateVar` /
15329    // `LispError::NonSymbolUnquoteTarget` — the two template-marker
15330    // rejection variants for the no-evaluator template language.
15331    // Before this lift each variant's `prefix` slot was `&'static
15332    // str`, set from the literal `","` / `",@"` at four (Unbound) +
15333    // four (NonSymbol) call sites; consumers had to substring-compare
15334    // against two string literals to recognize the marker identity.
15335    // After the lift the slot IS the typed `UnquoteForm` enum, so
15336    // authoring tools (REPL, LSP, `tatara-check`) pattern-match on
15337    // `UnquoteForm::Splice` etc. directly — same posture as
15338    // `MacroDefHead` for `LispError::Defmacro*`, `CompilerSpecIoStage`
15339    // for `LispError::CompilerSpecIo`, `TemplateInvariantKind` for
15340    // `LispError::TemplateInvariant`.
15341
15342    #[test]
15343    fn unbound_template_var_prefix_slot_is_unquote_form_not_static_str() {
15344        // Pin that the `prefix` slot of `LispError::UnboundTemplateVar`
15345        // is `UnquoteForm` (the typed closed-set enum), not `&'static
15346        // str`. A regression that reverts the slot to `&'static str`
15347        // breaks the typed binding here at compile time; a
15348        // construction with a stringly-typed prefix would fail to
15349        // construct. This test is the structural-completeness pin
15350        // for the typed-slot promotion, parallel to how
15351        // `defmacro_arity_head_slot_is_macro_def_head_not_static_str`
15352        // pins `LispError::DefmacroArity.head`.
15353        let err = LispError::UnboundTemplateVar {
15354            prefix: UnquoteForm::Unquote,
15355            name: "xs".into(),
15356            hint: None,
15357        };
15358        match err {
15359            LispError::UnboundTemplateVar { prefix, name, hint } => {
15360                assert_eq!(prefix, UnquoteForm::Unquote);
15361                assert_eq!(name, "xs");
15362                assert_eq!(hint, None);
15363            }
15364            other => panic!("expected UnboundTemplateVar, got {other:?}"),
15365        }
15366    }
15367
15368    #[test]
15369    fn non_symbol_unquote_target_prefix_slot_is_unquote_form_not_static_str() {
15370        // Sibling pin for `LispError::NonSymbolUnquoteTarget`. The
15371        // `prefix` slot carries `UnquoteForm` directly so consumers
15372        // bind on variant identity (`UnquoteForm::Splice`) instead
15373        // of substring-matching the rendered diagnostic. Together
15374        // with `unbound_template_var_prefix_slot_is_unquote_form_not_static_str`,
15375        // the two pin the typed-slot promotion across ALL template-
15376        // marker rejection variants — the two reachable rejection
15377        // identities (`UnboundTemplateVar`, `NonSymbolUnquoteTarget`)
15378        // share ONE typed marker identity.
15379        let err = LispError::NonSymbolUnquoteTarget {
15380            prefix: UnquoteForm::Splice,
15381            got: SexpWitness::new(SexpShape::List, "(list 1 2)"),
15382        };
15383        match err {
15384            LispError::NonSymbolUnquoteTarget { prefix, got } => {
15385                assert_eq!(prefix, UnquoteForm::Splice);
15386                assert_eq!(got.shape, SexpShape::List);
15387                assert_eq!(got.display, "(list 1 2)");
15388            }
15389            other => panic!("expected NonSymbolUnquoteTarget, got {other:?}"),
15390        }
15391    }
15392
15393    #[test]
15394    fn unquote_form_marker_projects_canonical_literal_for_each_variant() {
15395        // Pin `UnquoteForm::marker()` — it must project each variant
15396        // to the canonical `&'static str` literal byte-for-byte. The
15397        // projection feeds the `#[error(...)]` annotation on
15398        // `LispError::UnboundTemplateVar` /
15399        // `LispError::NonSymbolUnquoteTarget` via the Display impl,
15400        // and the `unbound_hint_suffix` helper's `prefix.marker()`
15401        // call site. A regression that drifts the literal (e.g.,
15402        // returns `"un"` instead of `","`) fails-loudly here.
15403        assert_eq!(UnquoteForm::Unquote.marker(), ",");
15404        assert_eq!(UnquoteForm::Splice.marker(), ",@");
15405    }
15406
15407    #[test]
15408    fn unquote_form_display_renders_canonical_marker_for_each_variant() {
15409        // Pin `UnquoteForm`'s Display impl — it must project through
15410        // `marker()` so the `#[error(...)]` annotation on each
15411        // affected `LispError::*` variant renders the canonical
15412        // `&'static str` literal byte-for-byte. Same posture as
15413        // `MacroDefHead`'s Display impl (which projects through
15414        // `keyword()`).
15415        assert_eq!(format!("{}", UnquoteForm::Unquote), ",");
15416        assert_eq!(format!("{}", UnquoteForm::Splice), ",@");
15417    }
15418
15419    #[test]
15420    fn unbound_template_var_display_renders_canonical_marker_for_each_variant() {
15421        // End-to-end through `LispError`'s Display: the typed `prefix:
15422        // UnquoteForm` slot projects to the canonical `&'static str`
15423        // literal at render time via `UnquoteForm`'s Display impl,
15424        // so the rendered diagnostic is byte-for-byte identical to
15425        // the pre-lift `prefix: &'static str` shape. Authoring tools
15426        // that substring-match on `,xs` / `,@xs` see no drift across
15427        // the typed-slot promotion. Paired with
15428        // `non_symbol_unquote_target_display_renders_canonical_marker_for_each_variant`
15429        // to pin the lift end-to-end on BOTH affected variants.
15430        let unquote = LispError::UnboundTemplateVar {
15431            prefix: UnquoteForm::Unquote,
15432            name: "xs".into(),
15433            hint: None,
15434        };
15435        assert_eq!(format!("{unquote}"), "compile error in ,xs: unbound");
15436
15437        let splice = LispError::UnboundTemplateVar {
15438            prefix: UnquoteForm::Splice,
15439            name: "argz".into(),
15440            hint: Some("args".into()),
15441        };
15442        assert_eq!(
15443            format!("{splice}"),
15444            "compile error in ,@argz: unbound; did you mean ,@args?"
15445        );
15446    }
15447
15448    #[test]
15449    fn non_symbol_unquote_target_display_renders_canonical_marker_for_each_variant() {
15450        // Sibling end-to-end pin for `LispError::NonSymbolUnquoteTarget`.
15451        // Pins that the typed-slot promotion preserves the
15452        // template-marker diagnostic shape byte-for-byte for BOTH
15453        // variants of `UnquoteForm`.
15454        let unquote = LispError::NonSymbolUnquoteTarget {
15455            prefix: UnquoteForm::Unquote,
15456            got: SexpWitness::new(SexpShape::List, "(list 1 2)"),
15457        };
15458        assert_eq!(
15459            format!("{unquote}"),
15460            "compile error in ,: expected symbol, got (list 1 2)"
15461        );
15462
15463        let splice = LispError::NonSymbolUnquoteTarget {
15464            prefix: UnquoteForm::Splice,
15465            got: SexpWitness::new(SexpShape::Int, "5"),
15466        };
15467        assert_eq!(
15468            format!("{splice}"),
15469            "compile error in ,@: expected symbol, got 5"
15470        );
15471    }
15472
15473    #[test]
15474    fn unquote_form_is_copy_and_partial_eq_for_pattern_match_ergonomics() {
15475        // Pin that `UnquoteForm` derives `Copy + PartialEq + Eq +
15476        // Debug + Clone` — the posture every closed-set typed enum
15477        // in this module shares (`MacroDefHead`, `CompilerSpecIoStage`,
15478        // `TemplateInvariantKind`). Copy lets consumers pattern-match
15479        // on the variant without explicit cloning; `PartialEq + Eq`
15480        // makes `assert_eq!` and `matches!` ergonomic; `Debug` makes
15481        // the `other => panic!("got {other:?}")` shape ergonomic at
15482        // assertion sites. A regression that drops any of these
15483        // derives breaks compilation here.
15484        let f = UnquoteForm::Splice;
15485        let f_copy: UnquoteForm = f; // Copy
15486        assert_eq!(f, f_copy); // PartialEq
15487        assert!(matches!(f, UnquoteForm::Splice)); // exhaustive match
15488        assert_ne!(f, UnquoteForm::Unquote); // Eq/Ne
15489        let _: String = format!("{f:?}"); // Debug
15490    }
15491
15492    #[test]
15493    fn kwarg_path_named_display_renders_legacy_colon_key_literal() {
15494        // `KwargPath::Named(":<key>")` Display must render the literal
15495        // `:<key>` byte-for-byte equivalent to the pre-lift
15496        // `format!(":{key}")` inline literal in `kwarg_form`. The
15497        // canonical literal lives in ONE place (the Display impl), so
15498        // a regression that drifts the prefix (e.g., to `key:` or
15499        // `<key>:`) fails-loudly here AND breaks every
15500        // `LispError::TypeMismatch.form` consumer that depends on the
15501        // `:<key>` shape (the substrate's hot path).
15502        assert_eq!(format!("{}", KwargPath::named("threshold")), ":threshold");
15503    }
15504
15505    #[test]
15506    fn kwarg_path_item_display_renders_legacy_colon_key_bracket_idx_literal() {
15507        // `KwargPath::Item { key, idx }` Display must render
15508        // `:<key>[<idx>]` byte-for-byte equivalent to the pre-lift
15509        // `format!(":{key}[{idx}]")` inline literal in `kwarg_item_form`.
15510        // The bracketed-index suffix is what `extract_string_list`'s
15511        // per-item failure path emits; a regression that drifts the
15512        // bracket-shape (e.g., to `:steps.1` or `:steps#1`) breaks every
15513        // LSP underline that depends on the bracket shape.
15514        assert_eq!(format!("{}", KwargPath::item("steps", 1)), ":steps[1]");
15515    }
15516
15517    #[test]
15518    fn kwarg_path_slot_display_renders_legacy_kwargs_bracket_idx_literal() {
15519        // `KwargPath::Slot(idx)` Display must render `kwargs[<idx>]`
15520        // byte-for-byte equivalent to the pre-lift
15521        // `format!("kwargs[{idx}]")` inline literal in `kwargs_pos_form`.
15522        // The `kwargs` prefix (no leading colon) is what `parse_kwargs`'s
15523        // "this-position-must-be-a-keyword" gate emits when the slot
15524        // failed BEFORE a key was known; the slot shape is structurally
15525        // distinct from the named-kwarg shape (`:<key>` vs `kwargs[i]`)
15526        // so consumers can bifurcate on path identity.
15527        assert_eq!(format!("{}", KwargPath::Slot(0)), "kwargs[0]");
15528    }
15529
15530    #[test]
15531    fn kwarg_path_named_carries_kebab_case_keys_unchanged() {
15532        // Kebab-cased kwarg names (`:notify-ref`, `:window-seconds`)
15533        // round-trip through the Display unchanged — the path shape
15534        // doesn't transform the key, it just wraps it in the `:<…>`
15535        // prefix. Pinning this contract means a regression that
15536        // camelCases or lowercases the key in the rendered prefix
15537        // fails-loudly here.
15538        assert_eq!(format!("{}", KwargPath::named("notify-ref")), ":notify-ref");
15539        assert_eq!(
15540            format!("{}", KwargPath::item("window-seconds", 3)),
15541            ":window-seconds[3]"
15542        );
15543    }
15544
15545    #[test]
15546    fn kwarg_path_is_clone_and_partial_eq_for_pattern_match_ergonomics() {
15547        // `KwargPath` derives Clone + Debug + PartialEq + Eq so that
15548        // pattern-matching call sites (REPL diagnostic capture,
15549        // `tatara-check`'s failure clustering, an LSP that surfaces
15550        // "your `:steps[3]` failed" with structural binding) compare
15551        // by reference cheaply AND inhabit the same kind of test
15552        // assertion shape as `MacroDefHead`, `UnquoteForm`,
15553        // `CompilerSpecIoStage`, and `TemplateInvariantKind`. `Copy`
15554        // is intentionally NOT derived because `String` is not `Copy`
15555        // — the owned key payload is the load-bearing property of the
15556        // typed-slot promotion onto `LispError::TypeMismatch.form`. A
15557        // regression that drops any of the retained derives or that
15558        // re-adds `Copy` breaks compilation here.
15559        let p = KwargPath::item("steps", 2);
15560        let p_clone = p.clone(); // Clone
15561        assert_eq!(p, p_clone); // PartialEq
15562        assert!(matches!(p, KwargPath::Item { idx: 2, .. })); // exhaustive match
15563        assert_ne!(p, KwargPath::Slot(2)); // Eq/Ne — Item and Slot are distinct path identities
15564        let _: String = format!("{p:?}"); // Debug
15565    }
15566
15567    #[test]
15568    fn kwarg_path_named_and_slot_have_distinct_display_shapes() {
15569        // The bifurcation between named-kwarg failures (`:<key>`) and
15570        // pre-key slot failures (`kwargs[<idx>]`) is structural — same
15571        // failure surface (kwargs gate), different path identity. Pin
15572        // the structural-distinctness: even at the rendered-string
15573        // level the two shapes don't collide. Two consumers depend on
15574        // this:
15575        //   1. `tatara-check`'s diagnostic capture, which groups by
15576        //      path prefix — a slot failure must NOT be confused with
15577        //      a `:kwargs`-keyed named-kwarg failure.
15578        //   2. An LSP's structural binding — the `KwargPath::Slot`
15579        //      identity says "we don't know which kwarg yet"; the
15580        //      `KwargPath::Named` identity says "we know the kwarg
15581        //      identifier and it's this".
15582        let named = format!("{}", KwargPath::named("kwargs"));
15583        let slot = format!("{}", KwargPath::Slot(0));
15584        assert_eq!(named, ":kwargs");
15585        assert_eq!(slot, "kwargs[0]");
15586        assert_ne!(named, slot);
15587    }
15588
15589    #[test]
15590    fn type_mismatch_form_carries_typed_kwarg_path_named_through_variant_slot() {
15591        // After the typed-slot promotion, `LispError::TypeMismatch.form`
15592        // is `KwargPath` — owned, structurally bound to the closed-set
15593        // typed enum. Consumers (REPL, LSP, `tatara-check`) pattern-match
15594        // on the variant identity `KwargPath::Named(_)` directly rather
15595        // than substring-matching a rendered prefix. Pin the structural
15596        // binding AND the Display projection so the byte-for-byte
15597        // rendering contract is anchored from both angles. A regression
15598        // that re-introduces a String-shaped form (collapsing the typed
15599        // enum back into a free-form label) fails-loudly here.
15600        let err = LispError::TypeMismatch {
15601            form: KwargPath::named("threshold"),
15602            expected: ExpectedKwargShape::Number,
15603            got: SexpShape::String,
15604        };
15605        match &err {
15606            LispError::TypeMismatch { form, .. } => {
15607                assert_eq!(*form, KwargPath::Named("threshold".into()));
15608            }
15609            other => panic!("expected TypeMismatch, got {other:?}"),
15610        }
15611        assert_eq!(
15612            format!("{err}"),
15613            "compile error in :threshold: expected number, got string"
15614        );
15615    }
15616
15617    #[test]
15618    fn type_mismatch_form_carries_typed_kwarg_path_item_through_variant_slot() {
15619        // Sibling pin to `…_named_…` for the per-item path. The
15620        // `KwargPath::Item { key, idx }` shape names the offending kwarg
15621        // AND the failing item index in one structural variant; the
15622        // bracketed `:<key>[<idx>]` rendering is unchanged.
15623        let err = LispError::TypeMismatch {
15624            form: KwargPath::item("steps", 3),
15625            expected: ExpectedKwargShape::String,
15626            got: SexpShape::Int,
15627        };
15628        match &err {
15629            LispError::TypeMismatch { form, .. } => {
15630                assert_eq!(
15631                    *form,
15632                    KwargPath::Item {
15633                        key: "steps".into(),
15634                        idx: 3
15635                    }
15636                );
15637            }
15638            other => panic!("expected TypeMismatch, got {other:?}"),
15639        }
15640        assert_eq!(
15641            format!("{err}"),
15642            "compile error in :steps[3]: expected string, got int"
15643        );
15644    }
15645
15646    #[test]
15647    fn type_mismatch_form_carries_typed_kwarg_path_slot_through_variant_slot() {
15648        // Sibling pin to `…_named_…` for the pre-key slot path. The
15649        // `KwargPath::Slot(idx)` shape names the offending position
15650        // without binding a key — it's the
15651        // "this-position-must-be-a-keyword" gate firing before any
15652        // identifier is known. The rendered `kwargs[<idx>]` shape
15653        // (no leading colon) bifurcates structurally from
15654        // `KwargPath::Named`'s `:<key>` shape.
15655        let err = LispError::TypeMismatch {
15656            form: KwargPath::Slot(2),
15657            expected: ExpectedKwargShape::Keyword,
15658            got: SexpShape::String,
15659        };
15660        match &err {
15661            LispError::TypeMismatch { form, .. } => {
15662                assert_eq!(*form, KwargPath::Slot(2));
15663            }
15664            other => panic!("expected TypeMismatch, got {other:?}"),
15665        }
15666        assert_eq!(
15667            format!("{err}"),
15668            "compile error in kwargs[2]: expected keyword, got string"
15669        );
15670    }
15671
15672    // ── KwargPathKind closed-set lift ───────────────────────────────────
15673    //
15674    // Sibling discriminator view of `KwargPath` — the payload-stripped
15675    // closed set of path-shape CATEGORIES (`Named` / `Item` / `Slot`).
15676    // Same shape every sibling payload-carrying closed-set enum in the
15677    // workspace pairs with (`AutoTerminate` / `AutoTerminateKind`,
15678    // `TerminateReason` / `TerminateReasonKind`, `SelectStrategy` /
15679    // `SelectStrategyKind`, `ChannelVariant` / `ChannelKind`). Consumers
15680    // that bucket type-mismatch failures by path-shape category (metrics
15681    // labels `path_kind=named` etc., a future tatara-check failure
15682    // histogram, an LSP that switches UI before drilling into the
15683    // bracket suffix) project through `KwargPath::kind` instead of
15684    // pattern-matching the full payload and discarding it at every site.
15685
15686    #[test]
15687    fn kwarg_path_kind_all_is_unique_and_complete() {
15688        // Closed-set posture: `ALL` enumerates every reachable variant
15689        // EXACTLY ONCE — no duplicates, no omissions. The `[Self; 3]`
15690        // array literal in the declaration forces the arity at compile
15691        // time; this test catches the orthogonal failure modes — a
15692        // future variant added at the type without being added to ALL
15693        // (silently dropped from every consumer's sweep), or a typo
15694        // that duplicates an entry (silently double-counted). Same
15695        // truth-table pinning every sibling closed-set lift in the
15696        // workspace uses (ExpectedKwargShape::ALL, SexpShape::ALL,
15697        // MacroDefHead::ALL, CompilerSpecIoStage::ALL,
15698        // AutoTerminateKind::ALL, SelectStrategyKind::ALL, …).
15699        //
15700        // The `iter+map+collect+sort_unstable` quadruple this test
15701        // inlined pre-lift now binds at `<KwargPathKind as
15702        // ClosedSet>::sorted_labels()` — the canonical-ordered
15703        // candidate-list projection on the trait. Distinctness of the
15704        // sorted result is covered by
15705        // `assert_closed_set_well_formed::<KwargPathKind>()`.
15706        assert_eq!(KwargPathKind::ALL.len(), 3);
15707        assert_eq!(
15708            <KwargPathKind as tatara_closed_set::ClosedSet>::sorted_labels(),
15709            vec!["item", "named", "slot"],
15710            "KwargPathKind::ALL must cover every reachable path-shape category"
15711        );
15712    }
15713
15714    #[test]
15715    fn kwarg_path_kind_label_round_trips_through_from_str() {
15716        // Bidirectional `label` ↔ `FromStr` contract: for every variant
15717        // in ALL, `kind.label().parse() == Ok(kind)`. A regression that
15718        // drifts the (variant, literal) pairing at ONE arm of `label`
15719        // (typo, capitalization drift) OR at the `FromStr` decode body
15720        // (off-by-one, missing variant in the sweep) fails-loudly here.
15721        // The canonical-literal site is singular (`label`) so the
15722        // round-trip is the only way the typed surface and the rendered
15723        // category literal can drift apart — pinning it here means they
15724        // cannot. Mirror of `expected_kwarg_shape_label_round_trips_…`
15725        // and every sibling closed-set round-trip in the workspace.
15726        for kind in KwargPathKind::ALL {
15727            let parsed: KwargPathKind = kind
15728                .label()
15729                .parse()
15730                .expect("every ALL variant's label must round-trip through FromStr");
15731            assert_eq!(
15732                parsed,
15733                kind,
15734                "FromStr({}) must round-trip to the same variant",
15735                kind.label()
15736            );
15737        }
15738    }
15739
15740    #[test]
15741    fn kwarg_path_kind_display_matches_label_for_every_variant() {
15742        // Display delegates to `label` — pin the byte-for-byte equality
15743        // for every variant so a future Display impl that diverges from
15744        // the canonical projection (e.g., re-adds a prefix like
15745        // `"kind=named"`) fails-loudly here. Same posture as
15746        // `expected_kwarg_shape_display_matches_label_for_every_variant`.
15747        for kind in KwargPathKind::ALL {
15748            assert_eq!(format!("{kind}"), kind.label());
15749        }
15750    }
15751
15752    #[test]
15753    fn unknown_kwarg_path_kind_carries_offending_input_verbatim() {
15754        // Operator-facing diagnostic contract: the offending input lands
15755        // in the typed error verbatim — no normalization, no case-folding,
15756        // no truncation. Pin the exact `#[error(...)]` rendering AND the
15757        // typed `.0` field projection so a future refactor that
15758        // normalizes (e.g. `.to_lowercase()`) before building the error
15759        // or that drops the input fails-loudly here. Symmetric to every
15760        // sibling `Unknown*` carrier in the workspace.
15761        let err: UnknownKwargPathKind = "Named".parse::<KwargPathKind>().expect_err(
15762            "capitalized `Named` must NOT decode — labels are byte-equal case-sensitive",
15763        );
15764        assert_eq!(err.0, "Named");
15765        assert_eq!(format!("{err}"), "unknown kwarg path kind: Named");
15766
15767        // A kwargs-path RENDERING (`:foo`) is NOT a kind label — the
15768        // CATEGORY axis is orthogonal to the IDENTITY axis; FromStr must
15769        // reject rendered identities, not silently coerce them.
15770        let err: UnknownKwargPathKind = ":foo"
15771            .parse::<KwargPathKind>()
15772            .expect_err("`:foo` is a KwargPath rendering, not a KwargPathKind label");
15773        assert_eq!(err.0, ":foo");
15774        assert_eq!(format!("{err}"), "unknown kwarg path kind: :foo");
15775
15776        let err: UnknownKwargPathKind = ""
15777            .parse::<KwargPathKind>()
15778            .expect_err("empty input must NOT decode to a KwargPathKind");
15779        assert_eq!(err.0, "");
15780        assert_eq!(format!("{err}"), "unknown kwarg path kind: ");
15781    }
15782
15783    #[test]
15784    fn kwarg_path_kind_projects_each_variant_to_canonical_kind() {
15785        // Load-bearing discriminator contract: `KwargPath::kind()` strips
15786        // the payload and projects to the canonical `KwargPathKind`
15787        // variant for each `KwargPath` shape. A regression that swaps
15788        // arms (e.g., `Named` → `KwargPathKind::Slot`) fails-loudly here
15789        // AND breaks every consumer that buckets by category. Symmetric
15790        // to `AutoTerminate::kind` and `TerminateReason::kind` in
15791        // tatara-process.
15792        assert_eq!(KwargPath::named("threshold").kind(), KwargPathKind::Named);
15793        assert_eq!(KwargPath::item("steps", 3).kind(), KwargPathKind::Item);
15794        assert_eq!(KwargPath::Slot(2).kind(), KwargPathKind::Slot);
15795    }
15796
15797    #[test]
15798    fn kwarg_path_kind_is_copy_and_hash_for_metrics_label_ergonomics() {
15799        // `KwargPathKind` derives Clone + Copy + Debug + PartialEq + Eq
15800        // + Hash so consumers that use the kind as a metrics-label key
15801        // (failure-cluster histogram keyed by `path_kind`), a HashMap
15802        // key (per-category counter), or a Copy-able discriminator in a
15803        // hot loop (kind projection in a kwarg-gate batching loop) reach
15804        // for the type without `.clone()` overhead. `String`-carrying
15805        // `KwargPath` is intentionally NOT `Copy`; `KwargPathKind` IS —
15806        // the split is the whole point of the discriminator view. A
15807        // regression that drops Copy or Hash breaks compilation here.
15808        let k = KwargPathKind::Named;
15809        let k_copy = k; // Copy
15810        assert_eq!(k, k_copy);
15811        let _: String = format!("{k:?}"); // Debug
15812        let mut s: std::collections::HashSet<KwargPathKind> = std::collections::HashSet::new();
15813        s.insert(KwargPathKind::Named);
15814        s.insert(KwargPathKind::Item);
15815        s.insert(KwargPathKind::Slot);
15816        s.insert(KwargPathKind::Named); // duplicate insert is a no-op (Hash + Eq)
15817        assert_eq!(s.len(), 3);
15818    }
15819
15820    #[test]
15821    fn kwarg_path_kind_label_does_not_overlap_kwarg_path_display_renderings() {
15822        // Cross-axis guard: the CATEGORY labels (`"named"` / `"item"` /
15823        // `"slot"`) are intentionally disjoint from the IDENTITY
15824        // renderings (`":<key>"` / `":<key>[<idx>]"` / `"kwargs[<idx>]"`)
15825        // so a consumer that confuses the two surfaces (e.g., parses
15826        // `kind.label()` as a `KwargPath` rendering, or parses a rendered
15827        // path as a kind label) fails-loudly through `FromStr` rejection
15828        // in both directions. Pin the non-overlap so a future label
15829        // rename that drifts into the rendering vocabulary (e.g.,
15830        // renaming `Slot`'s label to `"kwargs"`) fails-loudly here.
15831        for kind in KwargPathKind::ALL {
15832            let label = kind.label();
15833            assert!(
15834                !label.starts_with(':'),
15835                "kind label {label:?} must not start with `:` (would collide with KwargPath::Named/Item rendering)"
15836            );
15837            assert!(
15838                !label.contains('['),
15839                "kind label {label:?} must not contain `[` (would collide with KwargPath::Item/Slot rendering)"
15840            );
15841        }
15842    }
15843
15844    // ── `KwargPathKind::{NAMED_LABEL, ITEM_LABEL, SLOT_LABEL, LABELS}`
15845    // — the typed per-role category-label algebra ─────────────────────
15846    //
15847    // Sibling posture to `MacroDefHead::{DEFMACRO_KEYWORD,
15848    // DEFPOINT_TEMPLATE_KEYWORD, DEFCHECK_KEYWORD, KEYWORDS}` — the
15849    // per-role canonical `&'static str` marker + closed-set
15850    // `[&'static str; N]` array pattern applied to the
15851    // `KwargPathKind` closed set. Pre-lift the three canonical
15852    // category labels lived at TWO structural sites — the
15853    // `Self::label` match arm AND the sibling truth-table tests
15854    // (`kwarg_path_kind_all_is_unique_and_complete` sorted-labels
15855    // pin, `kwarg_path_kind_label_does_not_overlap_kwarg_path_display_renderings`
15856    // disjointness sweep). Post-lift each (variant, canonical
15857    // `&'static str`) pairing binds at ONE `pub const` on the typed
15858    // `KwargPathKind` algebra the `Self::label` arm routes through.
15859
15860    #[test]
15861    fn kwarg_path_kind_named_label_projects_canonical_named_bytes() {
15862        // Pins the exact `"named"` bytes at the typed constant. A
15863        // regression that drifts the constant (e.g. typo `"name"`,
15864        // `"Named"` in PascalCase, or an accidental trailing
15865        // whitespace) fails-loudly here. This is the single site
15866        // the substrate's canonical typed-atom-extractor failure
15867        // category resolves to; every downstream consumer (Display,
15868        // FromStr, tests, LSP completion, metric labels) routes
15869        // through this constant.
15870        assert_eq!(
15871            KwargPathKind::NAMED_LABEL,
15872            "named",
15873            "KwargPathKind::NAMED_LABEL drifted from the substrate-\
15874             canonical typed-atom-extractor failure category `\"named\"`"
15875        );
15876    }
15877
15878    #[test]
15879    fn kwarg_path_kind_item_label_projects_canonical_item_bytes() {
15880        // Pins the exact `"item"` bytes at the typed constant. The
15881        // `extract_string_list` per-item gate emits this exact
15882        // category; any drift here silently breaks every metric or
15883        // diagnostic consumer that partitions failures by category.
15884        assert_eq!(
15885            KwargPathKind::ITEM_LABEL,
15886            "item",
15887            "KwargPathKind::ITEM_LABEL drifted from the substrate-\
15888             canonical list-item failure category `\"item\"`"
15889        );
15890    }
15891
15892    #[test]
15893    fn kwarg_path_kind_slot_label_projects_canonical_slot_bytes() {
15894        // Pins the exact `"slot"` bytes at the typed constant. The
15895        // `parse_kwargs` slot-must-be-a-keyword gate emits this
15896        // exact category; any drift here silently breaks every
15897        // metric or diagnostic consumer that partitions failures by
15898        // category.
15899        assert_eq!(
15900            KwargPathKind::SLOT_LABEL,
15901            "slot",
15902            "KwargPathKind::SLOT_LABEL drifted from the substrate-\
15903             canonical kwargs-slice-slot failure category `\"slot\"`"
15904        );
15905    }
15906
15907    #[test]
15908    fn kwarg_path_kind_label_method_routes_through_typed_constants() {
15909        // PATH-UNIFORMITY: the inherent `Self::label(self)` method
15910        // MUST return the per-role `pub const` byte-for-byte for
15911        // each reachable variant. A regression that reverts ONE arm
15912        // to an inline `"named"` string literal (e.g. a merge-
15913        // conflict resolution that picked the pre-lift form)
15914        // silently reintroduces the ≥2 PRIME-DIRECTIVE trigger the
15915        // lift resolved — this test catches that by pinning the
15916        // arm's return value to the constant, so the two paths
15917        // (inline vs. typed constant) cannot both hold. Sibling
15918        // posture to `macro_def_head_keyword_method_routes_through_typed_constants`
15919        // on the `MacroDefHead` head-keyword algebra.
15920        assert_eq!(
15921            KwargPathKind::Named.label(),
15922            KwargPathKind::NAMED_LABEL,
15923            "KwargPathKind::Named.label() drifted from \
15924             KwargPathKind::NAMED_LABEL — the match arm reverted to \
15925             an inline literal"
15926        );
15927        assert_eq!(
15928            KwargPathKind::Item.label(),
15929            KwargPathKind::ITEM_LABEL,
15930            "KwargPathKind::Item.label() drifted from \
15931             KwargPathKind::ITEM_LABEL — the match arm reverted to \
15932             an inline literal"
15933        );
15934        assert_eq!(
15935            KwargPathKind::Slot.label(),
15936            KwargPathKind::SLOT_LABEL,
15937            "KwargPathKind::Slot.label() drifted from \
15938             KwargPathKind::SLOT_LABEL — the match arm reverted to \
15939             an inline literal"
15940        );
15941    }
15942
15943    #[test]
15944    fn kwarg_path_kind_labels_has_expected_cardinality() {
15945        // Cardinality contract: `Self::LABELS.len() == 3` — pinned
15946        // at the declaration site by rustc's forced-arity check on
15947        // `[&'static str; 3]`. This test surfaces the arity as a
15948        // fail-loud runtime pin so a future refactor that switches
15949        // the array type to `&[&'static str]` (dropping the
15950        // compile-time arity forcing) doesn't silently loosen the
15951        // closed-set discipline the family relies on. Sibling
15952        // posture to `macro_def_head_keywords_has_expected_cardinality`
15953        // on the `MacroDefHead::KEYWORDS` family.
15954        assert_eq!(
15955            KwargPathKind::LABELS.len(),
15956            3,
15957            "KwargPathKind::LABELS cardinality drifted from 3 — the \
15958             category-label closed set gained or lost a variant \
15959             without the ALL / LABELS pair being updated in tandem"
15960        );
15961    }
15962
15963    #[test]
15964    fn kwarg_path_kind_labels_align_with_all_by_index() {
15965        // ALIGNMENT CONTRACT: `Self::LABELS[i] ==
15966        // Self::ALL[i].label()` element-wise. The two ALL arrays
15967        // (typed variants, canonical `&'static str` labels) share
15968        // ONE canonical declaration order — a regression that
15969        // reorders ONE array without reordering the other silently
15970        // misaligns every `zip(Self::ALL, Self::LABELS)` consumer
15971        // (LSP completion providers, metric-label emitters, coverage
15972        // reporters). Pinning by-index alignment here catches the
15973        // reorder at test time. Sibling posture to
15974        // `macro_def_head_keywords_align_with_all_by_index` on the
15975        // `MacroDefHead::KEYWORDS` family.
15976        assert_eq!(
15977            KwargPathKind::LABELS.len(),
15978            KwargPathKind::ALL.len(),
15979            "KwargPathKind::LABELS and KwargPathKind::ALL diverged in \
15980             cardinality — the per-role constants and enum variants \
15981             must stay in lockstep"
15982        );
15983        for (i, kind) in KwargPathKind::ALL.iter().enumerate() {
15984            assert_eq!(
15985                KwargPathKind::LABELS[i],
15986                kind.label(),
15987                "KwargPathKind::LABELS[{i}] `{lb}` drifted from \
15988                 KwargPathKind::ALL[{i}].label() `{via_variant}` — \
15989                 the canonical declaration order of the two ALL \
15990                 arrays must match element-wise",
15991                lb = KwargPathKind::LABELS[i],
15992                via_variant = kind.label(),
15993            );
15994        }
15995    }
15996
15997    #[test]
15998    fn kwarg_path_kind_labels_pairwise_distinct() {
15999        // PAIRWISE DISJOINTNESS: the three `&'static str` labels on
16000        // the category-label algebra MUST differ so the derive-
16001        // generated FromStr's linear sweep cannot route two variants
16002        // through the same arm. Family-wide sweep over LABELS ×
16003        // LABELS — supersedes any single per-pair assertion and
16004        // picks up new variants mechanically when the array grows.
16005        // Sibling posture to `macro_def_head_keywords_pairwise_distinct`
16006        // on the `MacroDefHead::KEYWORDS` family.
16007        for (i, a) in KwargPathKind::LABELS.iter().enumerate() {
16008            for (j, b) in KwargPathKind::LABELS.iter().enumerate() {
16009                if i == j {
16010                    continue;
16011                }
16012                assert_ne!(
16013                    a, b,
16014                    "KwargPathKind::LABELS[{i}] `{a}` collides with \
16015                     KwargPathKind::LABELS[{j}] `{b}` — the derive-\
16016                     generated FromStr sweep would route two variants \
16017                     through the same arm"
16018                );
16019            }
16020        }
16021    }
16022
16023    #[test]
16024    fn kwarg_path_kind_labels_all_round_trip_through_from_str() {
16025        // Family-wide bidirection: every entry of `Self::LABELS`
16026        // MUST parse back to some `KwargPathKind` variant AND that
16027        // variant's `label()` MUST equal the original entry.
16028        // Sibling posture to `macro_def_head_keywords_all_round_trip_through_from_str`
16029        // (which sweeps over `Self::ALL`); this test sweeps over the
16030        // per-role `&'static str` constants directly, catching a
16031        // regression where one of the three constants drifts from
16032        // the corresponding `Self::label` arm (e.g. `SLOT_LABEL` set
16033        // to `"slice"` while `Self::Slot.label()` still returns
16034        // `Self::SLOT_LABEL`, which would parse to
16035        // `KwargPathKind::Slot` correctly but break every downstream
16036        // consumer that pinned the literal `"slot"` bytes).
16037        for lb in KwargPathKind::LABELS {
16038            let parsed: KwargPathKind = lb.parse().unwrap_or_else(|_| {
16039                panic!(
16040                    "KwargPathKind::LABELS entry `{lb}` failed to parse \
16041                     — the entry drifted from Self::label"
16042                )
16043            });
16044            assert_eq!(
16045                parsed.label(),
16046                lb,
16047                "KwargPathKind::LABELS entry `{lb}` parses to a variant \
16048                 whose label() `{}` does not match — the constant and \
16049                 the match arm drifted apart",
16050                parsed.label(),
16051            );
16052        }
16053    }
16054
16055    // ── ExpectedKwargShape closed-set lift ──────────────────────────────
16056    //
16057    // The `LispError::TypeMismatch.expected` slot was promoted from
16058    // `&'static str` to the typed closed-set `ExpectedKwargShape` enum.
16059    // The seven reachable expected-shape labels — `Keyword` /
16060    // `String` / `Int` / `Number` / `Bool` / `List` / `ListOfStrings`
16061    // — are now encoded as variant identities so authoring tools (REPL,
16062    // LSP, `tatara-check`) bind on `ExpectedKwargShape::Number` etc.
16063    // directly rather than substring-matching `expected == "number"`.
16064    // Same posture as `KwargPath`, `MacroDefHead`, `UnquoteForm`,
16065    // `CompilerSpecIoStage`, and `TemplateInvariantKind`.
16066
16067    #[test]
16068    fn label_renders_canonical_string_for_every_variant() {
16069        // Pin every variant's canonical `&'static str` projection — a
16070        // regression that drifts any label (typo in `"strin"` for
16071        // `"string"`, swap of `"int"` ↔ `"number"`) fails-loudly here.
16072        // The seven labels are byte-for-byte identical to the pre-lift
16073        // `&'static str` literals scattered across `domain.rs` so
16074        // existing `format!("{err}").contains("expected string")`
16075        // / `expected int` / `expected number` / etc. assertions in
16076        // consumer crates pass unchanged across the lift.
16077        assert_eq!(ExpectedKwargShape::Keyword.label(), "keyword");
16078        assert_eq!(ExpectedKwargShape::String.label(), "string");
16079        assert_eq!(ExpectedKwargShape::Int.label(), "int");
16080        assert_eq!(ExpectedKwargShape::Number.label(), "number");
16081        assert_eq!(ExpectedKwargShape::Bool.label(), "bool");
16082        assert_eq!(ExpectedKwargShape::List.label(), "list");
16083        assert_eq!(ExpectedKwargShape::ListOfStrings.label(), "list of strings");
16084    }
16085
16086    #[test]
16087    fn display_matches_label_for_every_variant() {
16088        // Pin Display-equals-label: the `#[error("... expected
16089        // {expected}, ...")]` annotation on `LispError::TypeMismatch`
16090        // projects through Display, and Display delegates to `label()`.
16091        // A regression that introduces a Display impl that deviates from
16092        // `label()` (e.g. capitalizing one variant) would drift the
16093        // diagnostic surface; this test pins the contract.
16094        assert_eq!(format!("{}", ExpectedKwargShape::Keyword), "keyword");
16095        assert_eq!(format!("{}", ExpectedKwargShape::String), "string");
16096        assert_eq!(format!("{}", ExpectedKwargShape::Int), "int");
16097        assert_eq!(format!("{}", ExpectedKwargShape::Number), "number");
16098        assert_eq!(format!("{}", ExpectedKwargShape::Bool), "bool");
16099        assert_eq!(format!("{}", ExpectedKwargShape::List), "list");
16100        assert_eq!(
16101            format!("{}", ExpectedKwargShape::ListOfStrings),
16102            "list of strings"
16103        );
16104    }
16105
16106    #[test]
16107    fn type_mismatch_expected_carries_typed_shape_through_variant_slot() {
16108        // After the typed-slot promotion, `LispError::TypeMismatch.expected`
16109        // is `ExpectedKwargShape` — the closed-set typed enum.
16110        // Consumers (REPL, LSP, `tatara-check`) pattern-match on the
16111        // variant identity `ExpectedKwargShape::Number` directly rather
16112        // than substring-matching a rendered `"expected number"` prefix.
16113        // Pin the structural binding AND the Display projection so the
16114        // byte-for-byte rendering contract is anchored from both
16115        // angles. A regression that re-introduces a `&'static str`-
16116        // shaped expected slot (collapsing the typed enum back into a
16117        // free-form label) fails-loudly here.
16118        let err = LispError::TypeMismatch {
16119            form: KwargPath::named("threshold"),
16120            expected: ExpectedKwargShape::Number,
16121            got: SexpShape::String,
16122        };
16123        match &err {
16124            LispError::TypeMismatch { expected, .. } => {
16125                assert_eq!(*expected, ExpectedKwargShape::Number);
16126            }
16127            other => panic!("expected TypeMismatch, got {other:?}"),
16128        }
16129        assert_eq!(
16130            format!("{err}"),
16131            "compile error in :threshold: expected number, got string"
16132        );
16133    }
16134
16135    #[test]
16136    fn type_mismatch_expected_list_of_strings_bifurcates_from_list() {
16137        // The `extract_string_list` outer-shape gate emits
16138        // `ExpectedKwargShape::ListOfStrings` (`"list of strings"`),
16139        // bifurcating structurally from `extract_vec_via_serde`'s
16140        // outer-shape gate which emits `ExpectedKwargShape::List`
16141        // (`"list"`). Two related-but-distinct gates, two distinct
16142        // variant identities; the typed enum makes that bifurcation
16143        // load-bearing. A regression that collapses them into one
16144        // variant (e.g. `ExpectedKwargShape::AnyList`) would drift the
16145        // diagnostic message; this test pins both shapes.
16146        let list_of_strings = LispError::TypeMismatch {
16147            form: KwargPath::named("tags"),
16148            expected: ExpectedKwargShape::ListOfStrings,
16149            got: SexpShape::String,
16150        };
16151        let list = LispError::TypeMismatch {
16152            form: KwargPath::named("steps"),
16153            expected: ExpectedKwargShape::List,
16154            got: SexpShape::String,
16155        };
16156        assert_eq!(
16157            format!("{list_of_strings}"),
16158            "compile error in :tags: expected list of strings, got string"
16159        );
16160        assert_eq!(
16161            format!("{list}"),
16162            "compile error in :steps: expected list, got string"
16163        );
16164        match (&list_of_strings, &list) {
16165            (
16166                LispError::TypeMismatch { expected: a, .. },
16167                LispError::TypeMismatch { expected: b, .. },
16168            ) => {
16169                assert_ne!(a, b);
16170                assert_eq!(*a, ExpectedKwargShape::ListOfStrings);
16171                assert_eq!(*b, ExpectedKwargShape::List);
16172            }
16173            _ => panic!("both must be TypeMismatch"),
16174        }
16175    }
16176
16177    #[test]
16178    fn expected_kwarg_shape_all_is_unique_and_complete() {
16179        // Closed-set posture: `ALL` enumerates every reachable variant
16180        // EXACTLY ONCE — no duplicates, no omissions. The `[Self; 7]`
16181        // array literal in the declaration forces the arity at compile
16182        // time; this test catches the orthogonal failure modes — a
16183        // future variant added at the type without being added to ALL
16184        // (silently dropped from every consumer's sweep), or a typo
16185        // that duplicates an entry (silently double-counted). Same
16186        // truth-table pinning every sibling closed-set lift in the
16187        // workspace uses (SexpShape::ALL, MacroDefHead::ALL,
16188        // UnquoteForm::ALL, RequestorKind::ALL, ReceiptKind::ALL,
16189        // ConditionKind::ALL, ProcessPhase::ALL, ChannelKind::ALL, …).
16190        //
16191        // The `iter+map+collect+sort_unstable` quadruple this test
16192        // inlined pre-lift now binds at `<ExpectedKwargShape as
16193        // ClosedSet>::sorted_labels()` — the canonical-ordered
16194        // candidate-list projection on the trait. Distinctness of the
16195        // sorted result is covered by
16196        // `assert_closed_set_well_formed::<ExpectedKwargShape>()`.
16197        assert_eq!(ExpectedKwargShape::ALL.len(), 7);
16198        assert_eq!(
16199            <ExpectedKwargShape as tatara_closed_set::ClosedSet>::sorted_labels(),
16200            vec![
16201                "bool",
16202                "int",
16203                "keyword",
16204                "list",
16205                "list of strings",
16206                "number",
16207                "string",
16208            ],
16209            "ExpectedKwargShape::ALL must cover every reachable expected-shape label"
16210        );
16211    }
16212
16213    #[test]
16214    fn expected_kwarg_shape_label_round_trips_through_from_str() {
16215        // Bidirectional `label` ↔ `FromStr` contract: for every
16216        // variant in ALL, `shape.label().parse() == Ok(shape)`. A
16217        // regression that drifts the (variant, literal) pairing at
16218        // ONE arm of `label` (typo, capitalization drift) OR at the
16219        // `FromStr` decode body (off-by-one, missing variant in the
16220        // sweep) fails-loudly here. The canonical-literal site is
16221        // singular (`label`) so the round-trip is the only way the
16222        // typed surface and the rendered diagnostic literal can drift
16223        // apart — pinning it here means they cannot. Mirror of
16224        // `sexp_shape_label_round_trips_through_from_str` and every
16225        // sibling closed-set round-trip in the workspace.
16226        for shape in ExpectedKwargShape::ALL {
16227            let parsed: ExpectedKwargShape = shape
16228                .label()
16229                .parse()
16230                .expect("every ALL variant's label must round-trip through FromStr");
16231            assert_eq!(
16232                parsed,
16233                shape,
16234                "FromStr({}) must round-trip to the same variant",
16235                shape.label()
16236            );
16237        }
16238    }
16239
16240    #[test]
16241    fn unknown_expected_kwarg_shape_carries_offending_input_verbatim() {
16242        // Operator-facing diagnostic contract: the offending input
16243        // lands in the typed error verbatim — no normalization, no
16244        // case-folding, no truncation. Pin the exact `#[error(...)]`
16245        // rendering AND the typed `.0` field projection so a future
16246        // refactor that normalizes (e.g. `.to_lowercase()`) before
16247        // building the error or that drops the input fails-loudly
16248        // here. Symmetric to every sibling `Unknown*` carrier in the
16249        // workspace.
16250        let err: UnknownExpectedKwargShape = "Number".parse::<ExpectedKwargShape>().expect_err(
16251            "capitalized `Number` must NOT decode — labels are byte-equal case-sensitive",
16252        );
16253        assert_eq!(err.0, "Number");
16254        assert_eq!(format!("{err}"), "unknown expected kwarg shape: Number");
16255
16256        let err: UnknownExpectedKwargShape = "float"
16257            .parse::<ExpectedKwargShape>()
16258            .expect_err("`float` is SexpShape's vocabulary, not ExpectedKwargShape's");
16259        assert_eq!(err.0, "float");
16260        assert_eq!(format!("{err}"), "unknown expected kwarg shape: float");
16261
16262        let err: UnknownExpectedKwargShape = ""
16263            .parse::<ExpectedKwargShape>()
16264            .expect_err("empty input must NOT decode to an ExpectedKwargShape");
16265        assert_eq!(err.0, "");
16266        assert_eq!(format!("{err}"), "unknown expected kwarg shape: ");
16267    }
16268
16269    #[test]
16270    fn expected_kwarg_shape_from_str_accepts_only_canonical_labels() {
16271        // Cross-axis guard: `SexpShape::label()`'s vocabulary overlaps
16272        // with `ExpectedKwargShape::label()` on five of seven entries
16273        // (`keyword` / `string` / `int` / `bool` / `list`) and DOES
16274        // NOT overlap on the structural-only `nil` / `symbol` /
16275        // `float` / `quote` / `quasiquote` / `unquote` / `unquote-splice`
16276        // entries — those name Sexp identities the typed-entry kwarg
16277        // gate cannot `expect`. The overlap is intentional — both
16278        // axes are projections of the same `Sexp` algebra at typed-
16279        // entry gates — but the non-overlap is the load-bearing part:
16280        // a `FromStr` that silently accepts `"float"` as an
16281        // `ExpectedKwargShape` would corrupt the typed identity. Pin
16282        // BOTH directions: the overlap decodes successfully (and to
16283        // the matching `ExpectedKwargShape` variant), the non-overlap
16284        // rejects. Symmetric to `sexp_shape_from_str_accepts_only_
16285        // canonical_labels` from the other axis.
16286        assert_eq!(
16287            "keyword".parse::<ExpectedKwargShape>().unwrap(),
16288            ExpectedKwargShape::Keyword
16289        );
16290        assert_eq!(
16291            "string".parse::<ExpectedKwargShape>().unwrap(),
16292            ExpectedKwargShape::String
16293        );
16294        assert_eq!(
16295            "int".parse::<ExpectedKwargShape>().unwrap(),
16296            ExpectedKwargShape::Int
16297        );
16298        assert_eq!(
16299            "bool".parse::<ExpectedKwargShape>().unwrap(),
16300            ExpectedKwargShape::Bool
16301        );
16302        assert_eq!(
16303            "list".parse::<ExpectedKwargShape>().unwrap(),
16304            ExpectedKwargShape::List
16305        );
16306        // Non-overlap: SexpShape-only labels reject through FromStr.
16307        for sexp_only in ["nil", "symbol", "float", "quote", "quasiquote", "unquote"] {
16308            sexp_only.parse::<ExpectedKwargShape>().unwrap_err();
16309        }
16310        // ExpectedKwargShape-only labels: `number` and `list of strings`
16311        // decode here but reject through SexpShape::FromStr — the
16312        // non-overlap axis is symmetric.
16313        assert_eq!(
16314            "number".parse::<ExpectedKwargShape>().unwrap(),
16315            ExpectedKwargShape::Number
16316        );
16317        assert_eq!(
16318            "list of strings".parse::<ExpectedKwargShape>().unwrap(),
16319            ExpectedKwargShape::ListOfStrings
16320        );
16321        "number".parse::<SexpShape>().unwrap_err();
16322        "list of strings".parse::<SexpShape>().unwrap_err();
16323    }
16324
16325    // ── ExpectedKwargShape per-role `pub const` + LABELS ALL array lift ─
16326    //
16327    // Sibling posture to `KwargPathKind::{NAMED_LABEL, ITEM_LABEL,
16328    // SLOT_LABEL, LABELS}` on the kwarg-path category algebra —
16329    // per-role canonical `&'static str` marker + closed-set
16330    // `[&'static str; N]` array pattern applied to the
16331    // `ExpectedKwargShape` closed set. Pre-lift the seven canonical
16332    // expected-shape labels lived at TWO structural sites — the
16333    // `Self::label` match arms AND the sibling truth-table tests
16334    // (`label_renders_canonical_string_for_every_variant`,
16335    // `expected_kwarg_shape_all_is_unique_and_complete` sorted-labels
16336    // pin). Post-lift each (variant, canonical `&'static str`)
16337    // pairing binds at ONE `pub const` on the typed algebra the
16338    // `Self::label` arm routes through.
16339
16340    #[test]
16341    fn expected_kwarg_shape_keyword_label_projects_canonical_keyword_bytes() {
16342        // Pins the exact `"keyword"` bytes at the typed constant. The
16343        // `parse_kwargs` slot-must-be-a-keyword gate emits this exact
16344        // expected-shape; any drift here silently breaks every metric
16345        // or diagnostic consumer that partitions failures by expected-
16346        // shape. Sibling posture to
16347        // `kwarg_path_kind_named_label_projects_canonical_named_bytes`.
16348        assert_eq!(
16349            ExpectedKwargShape::KEYWORD_LABEL,
16350            "keyword",
16351            "ExpectedKwargShape::KEYWORD_LABEL drifted from the \
16352             substrate-canonical slot-must-be-a-keyword gate label \
16353             `\"keyword\"`"
16354        );
16355    }
16356
16357    #[test]
16358    fn expected_kwarg_shape_string_label_projects_canonical_string_bytes() {
16359        // Pins the exact `"string"` bytes at the typed constant.
16360        // `extract_string` / `extract_optional_string` / the
16361        // `extract_string_list` per-item gate all emit this exact
16362        // expected-shape; any drift here silently breaks every
16363        // consumer. Byte-equal to `SexpShape::STRING_LABEL` (future
16364        // lift) — the cross-axis overlap on the same `Sexp` algebra
16365        // is load-bearing.
16366        assert_eq!(
16367            ExpectedKwargShape::STRING_LABEL,
16368            "string",
16369            "ExpectedKwargShape::STRING_LABEL drifted from the \
16370             substrate-canonical extract-string gate label \
16371             `\"string\"`"
16372        );
16373    }
16374
16375    #[test]
16376    fn expected_kwarg_shape_int_label_projects_canonical_int_bytes() {
16377        // Pins the exact `"int"` bytes at the typed constant.
16378        // `extract_int` / `extract_optional_int` emit this exact
16379        // expected-shape; the sibling `NUMBER_LABEL` names the wider
16380        // numeric-union the `extract_float` extractor emits.
16381        assert_eq!(
16382            ExpectedKwargShape::INT_LABEL,
16383            "int",
16384            "ExpectedKwargShape::INT_LABEL drifted from the substrate-\
16385             canonical extract-int gate label `\"int\"`"
16386        );
16387    }
16388
16389    #[test]
16390    fn expected_kwarg_shape_number_label_projects_canonical_number_bytes() {
16391        // Pins the exact `"number"` bytes at the typed constant.
16392        // `extract_float` / `extract_optional_float` emit this exact
16393        // expected-shape; the naming (not `"float"`) is load-bearing
16394        // — `extract_float` accepts BOTH `Sexp::Atom(Float(_))` and
16395        // `Sexp::Atom(Int(_))` via `Sexp::as_float`, so the label
16396        // names the union rather than the narrower Float element-
16397        // type. A regression to `"float"` here would silently narrow
16398        // the diagnostic's semantics without changing the extractor's
16399        // acceptance set.
16400        assert_eq!(
16401            ExpectedKwargShape::NUMBER_LABEL,
16402            "number",
16403            "ExpectedKwargShape::NUMBER_LABEL drifted from the \
16404             substrate-canonical extract-float gate label \
16405             `\"number\"`"
16406        );
16407    }
16408
16409    #[test]
16410    fn expected_kwarg_shape_bool_label_projects_canonical_bool_bytes() {
16411        // Pins the exact `"bool"` bytes at the typed constant.
16412        // `extract_bool` / `extract_optional_bool` emit this exact
16413        // expected-shape.
16414        assert_eq!(
16415            ExpectedKwargShape::BOOL_LABEL,
16416            "bool",
16417            "ExpectedKwargShape::BOOL_LABEL drifted from the substrate-\
16418             canonical extract-bool gate label `\"bool\"`"
16419        );
16420    }
16421
16422    #[test]
16423    fn expected_kwarg_shape_list_label_projects_canonical_list_bytes() {
16424        // Pins the exact `"list"` bytes at the typed constant.
16425        // `extract_vec_via_serde`'s outer-shape gate emits this exact
16426        // expected-shape; the sibling `LIST_OF_STRINGS_LABEL` names
16427        // the element-typed variant `extract_string_list`'s outer
16428        // gate emits.
16429        assert_eq!(
16430            ExpectedKwargShape::LIST_LABEL,
16431            "list",
16432            "ExpectedKwargShape::LIST_LABEL drifted from the substrate-\
16433             canonical extract-vec-via-serde gate label `\"list\"`"
16434        );
16435    }
16436
16437    #[test]
16438    fn expected_kwarg_shape_list_of_strings_label_projects_canonical_bytes() {
16439        // Pins the exact `"list of strings"` bytes at the typed
16440        // constant. `extract_string_list`'s outer-shape gate emits
16441        // this exact expected-shape; the naming (not the narrower
16442        // `"list"`) is load-bearing — it names the element-type so
16443        // the diagnostic reads `expected list of strings, got
16444        // string` instead of the ambiguous `expected list, got
16445        // string`. A regression to `"list"` here would silently
16446        // collapse the two structurally distinct extractor gates
16447        // into one indistinguishable diagnostic.
16448        assert_eq!(
16449            ExpectedKwargShape::LIST_OF_STRINGS_LABEL,
16450            "list of strings",
16451            "ExpectedKwargShape::LIST_OF_STRINGS_LABEL drifted from \
16452             the substrate-canonical extract-string-list outer-shape \
16453             gate label `\"list of strings\"`"
16454        );
16455    }
16456
16457    #[test]
16458    fn expected_kwarg_shape_label_method_routes_through_typed_constants() {
16459        // PATH-UNIFORMITY: the inherent `Self::label(self)` method
16460        // MUST return the per-role `pub const` byte-for-byte for
16461        // each reachable variant. A regression that reverts ONE arm
16462        // to an inline `"number"` / `"list of strings"` string
16463        // literal (e.g. a merge-conflict resolution that picked the
16464        // pre-lift form) silently reintroduces the ≥2 PRIME-DIRECTIVE
16465        // trigger the lift resolved — this test catches that by
16466        // pinning the arm's return value to the constant, so the two
16467        // paths (inline vs. typed constant) cannot both hold.
16468        // Sibling posture to
16469        // `kwarg_path_kind_label_method_routes_through_typed_constants`.
16470        assert_eq!(
16471            ExpectedKwargShape::Keyword.label(),
16472            ExpectedKwargShape::KEYWORD_LABEL,
16473            "ExpectedKwargShape::Keyword.label() drifted from \
16474             ExpectedKwargShape::KEYWORD_LABEL — the match arm \
16475             reverted to an inline literal"
16476        );
16477        assert_eq!(
16478            ExpectedKwargShape::String.label(),
16479            ExpectedKwargShape::STRING_LABEL,
16480            "ExpectedKwargShape::String.label() drifted from \
16481             ExpectedKwargShape::STRING_LABEL — the match arm \
16482             reverted to an inline literal"
16483        );
16484        assert_eq!(
16485            ExpectedKwargShape::Int.label(),
16486            ExpectedKwargShape::INT_LABEL,
16487            "ExpectedKwargShape::Int.label() drifted from \
16488             ExpectedKwargShape::INT_LABEL — the match arm reverted \
16489             to an inline literal"
16490        );
16491        assert_eq!(
16492            ExpectedKwargShape::Number.label(),
16493            ExpectedKwargShape::NUMBER_LABEL,
16494            "ExpectedKwargShape::Number.label() drifted from \
16495             ExpectedKwargShape::NUMBER_LABEL — the match arm \
16496             reverted to an inline literal"
16497        );
16498        assert_eq!(
16499            ExpectedKwargShape::Bool.label(),
16500            ExpectedKwargShape::BOOL_LABEL,
16501            "ExpectedKwargShape::Bool.label() drifted from \
16502             ExpectedKwargShape::BOOL_LABEL — the match arm reverted \
16503             to an inline literal"
16504        );
16505        assert_eq!(
16506            ExpectedKwargShape::List.label(),
16507            ExpectedKwargShape::LIST_LABEL,
16508            "ExpectedKwargShape::List.label() drifted from \
16509             ExpectedKwargShape::LIST_LABEL — the match arm reverted \
16510             to an inline literal"
16511        );
16512        assert_eq!(
16513            ExpectedKwargShape::ListOfStrings.label(),
16514            ExpectedKwargShape::LIST_OF_STRINGS_LABEL,
16515            "ExpectedKwargShape::ListOfStrings.label() drifted from \
16516             ExpectedKwargShape::LIST_OF_STRINGS_LABEL — the match \
16517             arm reverted to an inline literal"
16518        );
16519    }
16520
16521    #[test]
16522    fn expected_kwarg_shape_labels_has_expected_cardinality() {
16523        // Cardinality contract: `Self::LABELS.len() == 7` — pinned at
16524        // the declaration site by rustc's forced-arity check on
16525        // `[&'static str; 7]`. This test surfaces the arity as a
16526        // fail-loud runtime pin so a future refactor that switches
16527        // the array type to `&[&'static str]` (dropping the compile-
16528        // time arity forcing) doesn't silently loosen the closed-set
16529        // discipline the family relies on. Sibling posture to
16530        // `kwarg_path_kind_labels_has_expected_cardinality` on the
16531        // `KwargPathKind::LABELS` family.
16532        assert_eq!(
16533            ExpectedKwargShape::LABELS.len(),
16534            7,
16535            "ExpectedKwargShape::LABELS cardinality drifted from 7 \
16536             — the expected-shape closed set gained or lost a variant \
16537             without the ALL / LABELS pair being updated in tandem"
16538        );
16539    }
16540
16541    #[test]
16542    fn expected_kwarg_shape_labels_align_with_all_by_index() {
16543        // ALIGNMENT CONTRACT: `Self::LABELS[i] ==
16544        // Self::ALL[i].label()` element-wise. The two ALL arrays
16545        // (typed variants, canonical `&'static str` labels) share
16546        // ONE canonical declaration order — a regression that
16547        // reorders ONE array without reordering the other silently
16548        // misaligns every `zip(Self::ALL, Self::LABELS)` consumer
16549        // (LSP completion providers, metric-label emitters, coverage
16550        // reporters). Pinning by-index alignment here catches the
16551        // reorder at test time. Sibling posture to
16552        // `kwarg_path_kind_labels_align_with_all_by_index`.
16553        assert_eq!(
16554            ExpectedKwargShape::LABELS.len(),
16555            ExpectedKwargShape::ALL.len(),
16556            "ExpectedKwargShape::LABELS and ExpectedKwargShape::ALL \
16557             diverged in cardinality — the per-role constants and \
16558             enum variants must stay in lockstep"
16559        );
16560        for (i, shape) in ExpectedKwargShape::ALL.iter().enumerate() {
16561            assert_eq!(
16562                ExpectedKwargShape::LABELS[i],
16563                shape.label(),
16564                "ExpectedKwargShape::LABELS[{i}] `{lb}` drifted from \
16565                 ExpectedKwargShape::ALL[{i}].label() `{via_variant}` \
16566                 — the canonical declaration order of the two ALL \
16567                 arrays must match element-wise",
16568                lb = ExpectedKwargShape::LABELS[i],
16569                via_variant = shape.label(),
16570            );
16571        }
16572    }
16573
16574    #[test]
16575    fn expected_kwarg_shape_labels_pairwise_distinct() {
16576        // PAIRWISE DISJOINTNESS: the seven `&'static str` labels on
16577        // the expected-shape algebra MUST differ so the derive-
16578        // generated FromStr's linear sweep cannot route two variants
16579        // through the same arm. Family-wide sweep over LABELS ×
16580        // LABELS — supersedes any single per-pair assertion and picks
16581        // up new variants mechanically when the array grows. Sibling
16582        // posture to `kwarg_path_kind_labels_pairwise_distinct`.
16583        for (i, a) in ExpectedKwargShape::LABELS.iter().enumerate() {
16584            for (j, b) in ExpectedKwargShape::LABELS.iter().enumerate() {
16585                if i == j {
16586                    continue;
16587                }
16588                assert_ne!(
16589                    a, b,
16590                    "ExpectedKwargShape::LABELS[{i}] `{a}` collides \
16591                     with ExpectedKwargShape::LABELS[{j}] `{b}` — the \
16592                     derive-generated FromStr sweep would route two \
16593                     variants through the same arm"
16594                );
16595            }
16596        }
16597    }
16598
16599    #[test]
16600    fn expected_kwarg_shape_labels_all_round_trip_through_from_str() {
16601        // Family-wide bidirection: every entry of `Self::LABELS` MUST
16602        // parse back to some `ExpectedKwargShape` variant AND that
16603        // variant's `label()` MUST equal the original entry. Sibling
16604        // posture to
16605        // `kwarg_path_kind_labels_all_round_trip_through_from_str`
16606        // (sweeps over `Self::ALL`); this test sweeps over the per-
16607        // role `&'static str` constants directly, catching a
16608        // regression where one of the seven constants drifts from
16609        // the corresponding `Self::label` arm (e.g.
16610        // `LIST_OF_STRINGS_LABEL` set to `"list-of-strings"` while
16611        // `Self::ListOfStrings.label()` still returns
16612        // `Self::LIST_OF_STRINGS_LABEL`, which would parse to
16613        // `ExpectedKwargShape::ListOfStrings` correctly but break
16614        // every downstream consumer that pinned the literal `"list of
16615        // strings"` bytes).
16616        for lb in ExpectedKwargShape::LABELS {
16617            let parsed: ExpectedKwargShape = lb.parse().unwrap_or_else(|_| {
16618                panic!(
16619                    "ExpectedKwargShape::LABELS entry `{lb}` failed to \
16620                     parse — the entry drifted from Self::label"
16621                )
16622            });
16623            assert_eq!(
16624                parsed.label(),
16625                lb,
16626                "ExpectedKwargShape::LABELS entry `{lb}` parses to a \
16627                 variant whose label() `{}` does not match — the \
16628                 constant and the match arm drifted apart",
16629                parsed.label(),
16630            );
16631        }
16632    }
16633
16634    #[test]
16635    fn expected_kwarg_shape_labels_cover_sorted_closed_set() {
16636        // Family-wide sweep: `Self::LABELS`, projected through sort,
16637        // MUST equal the derive-generated `sorted_labels()` on the
16638        // `ClosedSet` trait. Catches a regression where a new
16639        // variant is added to `Self::ALL` + `Self::label` (so
16640        // `sorted_labels()` grows) but the maintainer forgets to
16641        // extend the parallel per-role `pub const` + `Self::LABELS`
16642        // pair. Distinct from `_align_with_all_by_index` (which pins
16643        // ORDER preservation) and `_pairwise_distinct` (which pins
16644        // WITHIN-array uniqueness): this pin cross-checks the LABELS
16645        // array against the derive-generated candidate-list on the
16646        // trait, so a partial extension where LABELS grows but ALL
16647        // doesn't (or vice-versa) surfaces at both this pin AND
16648        // `_align_with_all_by_index`.
16649        let mut sorted_labels: Vec<&'static str> = ExpectedKwargShape::LABELS.to_vec();
16650        sorted_labels.sort_unstable();
16651        assert_eq!(
16652            sorted_labels,
16653            <ExpectedKwargShape as tatara_closed_set::ClosedSet>::sorted_labels(),
16654            "ExpectedKwargShape::LABELS diverged from the derive-\
16655             generated `sorted_labels()` — a variant was added or \
16656             removed from one array without the other tracking"
16657        );
16658    }
16659
16660    // ── SexpShape closed-set lift ───────────────────────────────────────
16661    //
16662    // The `LispError::TypeMismatch.got` and
16663    // `LispError::NamedFormNonSymbolName.got` slots were promoted from
16664    // `&'static str` to the typed closed-set `SexpShape` enum. The
16665    // twelve reachable Sexp outermost shapes — `Nil` / `Symbol` /
16666    // `Keyword` / `String` / `Int` / `Float` / `Bool` / `List` /
16667    // `Quote` / `Quasiquote` / `Unquote` / `UnquoteSplice` — are now
16668    // encoded as variant identities so authoring tools (REPL, LSP,
16669    // `tatara-check`) bind on `SexpShape::Int` etc. directly rather
16670    // than substring-matching `got == "int"`. Same posture as
16671    // `KwargPath`, `ExpectedKwargShape`, `MacroDefHead`, `UnquoteForm`,
16672    // `CompilerSpecIoStage`, and `TemplateInvariantKind`. After this
16673    // lift the `TypeMismatch` variant is fully closed-set typed in
16674    // ALL THREE of its slots — no `&'static str` projection remains
16675    // at any helper boundary.
16676
16677    #[test]
16678    fn sexp_shape_label_renders_canonical_string_for_every_variant() {
16679        // Pin every variant's canonical `&'static str` projection — a
16680        // regression that drifts any label (typo in `"strin"` for
16681        // `"string"`, swap of `"int"` ↔ `"float"`, capitalization
16682        // drift `"Quote"` for `"quote"`) fails-loudly here. The twelve
16683        // labels are byte-for-byte identical to the pre-lift
16684        // `sexp_type_name` projection so existing
16685        // `format!("{err}").contains("got int")` /
16686        // `got string` / `got list` / etc. assertions in consumer
16687        // crates pass unchanged across the lift.
16688        assert_eq!(SexpShape::Nil.label(), "nil");
16689        assert_eq!(SexpShape::Symbol.label(), "symbol");
16690        assert_eq!(SexpShape::Keyword.label(), "keyword");
16691        assert_eq!(SexpShape::String.label(), "string");
16692        assert_eq!(SexpShape::Int.label(), "int");
16693        assert_eq!(SexpShape::Float.label(), "float");
16694        assert_eq!(SexpShape::Bool.label(), "bool");
16695        assert_eq!(SexpShape::List.label(), "list");
16696        assert_eq!(SexpShape::Quote.label(), "quote");
16697        assert_eq!(SexpShape::Quasiquote.label(), "quasiquote");
16698        assert_eq!(SexpShape::Unquote.label(), "unquote");
16699        assert_eq!(SexpShape::UnquoteSplice.label(), "unquote-splice");
16700    }
16701
16702    #[test]
16703    fn sexp_shape_display_matches_label_for_every_variant() {
16704        // Pin Display-equals-label: the `#[error("... got {got}")]`
16705        // annotations on `LispError::TypeMismatch` and
16706        // `LispError::NamedFormNonSymbolName` project through Display,
16707        // and Display delegates to `label()`. A regression that
16708        // introduces a Display impl that deviates from `label()`
16709        // (e.g. capitalizing one variant) would drift the diagnostic
16710        // surface; this test pins the contract.
16711        assert_eq!(format!("{}", SexpShape::Nil), "nil");
16712        assert_eq!(format!("{}", SexpShape::Symbol), "symbol");
16713        assert_eq!(format!("{}", SexpShape::Keyword), "keyword");
16714        assert_eq!(format!("{}", SexpShape::String), "string");
16715        assert_eq!(format!("{}", SexpShape::Int), "int");
16716        assert_eq!(format!("{}", SexpShape::Float), "float");
16717        assert_eq!(format!("{}", SexpShape::Bool), "bool");
16718        assert_eq!(format!("{}", SexpShape::List), "list");
16719        assert_eq!(format!("{}", SexpShape::Quote), "quote");
16720        assert_eq!(format!("{}", SexpShape::Quasiquote), "quasiquote");
16721        assert_eq!(format!("{}", SexpShape::Unquote), "unquote");
16722        assert_eq!(format!("{}", SexpShape::UnquoteSplice), "unquote-splice");
16723    }
16724
16725    #[test]
16726    fn sexp_shape_label_routes_through_typed_per_variant_constants() {
16727        // PATH-UNIFORMITY: `SexpShape::label(v)` MUST equal the per-role
16728        // `pub const V_LABEL` for every `v`. Pre-lift the twelve arms
16729        // held inline `&'static str` literals; post-lift each arm binds
16730        // to its per-role constant. A regression that reverts ANY arm
16731        // to an inline literal (e.g. `Self::Symbol => "symbol"`) still
16732        // renders identically at runtime but silently disagrees with
16733        // the typed source of truth — this pin closes that drift.
16734        assert_eq!(SexpShape::Nil.label(), SexpShape::NIL_LABEL);
16735        assert_eq!(SexpShape::Symbol.label(), SexpShape::SYMBOL_LABEL);
16736        assert_eq!(SexpShape::Keyword.label(), SexpShape::KEYWORD_LABEL);
16737        assert_eq!(SexpShape::String.label(), SexpShape::STRING_LABEL);
16738        assert_eq!(SexpShape::Int.label(), SexpShape::INT_LABEL);
16739        assert_eq!(SexpShape::Float.label(), SexpShape::FLOAT_LABEL);
16740        assert_eq!(SexpShape::Bool.label(), SexpShape::BOOL_LABEL);
16741        assert_eq!(SexpShape::List.label(), SexpShape::LIST_LABEL);
16742        assert_eq!(SexpShape::Quote.label(), SexpShape::QUOTE_LABEL);
16743        assert_eq!(SexpShape::Quasiquote.label(), SexpShape::QUASIQUOTE_LABEL);
16744        assert_eq!(SexpShape::Unquote.label(), SexpShape::UNQUOTE_LABEL);
16745        assert_eq!(
16746            SexpShape::UnquoteSplice.label(),
16747            SexpShape::UNQUOTE_SPLICE_LABEL
16748        );
16749    }
16750
16751    #[test]
16752    fn sexp_shape_labels_has_expected_cardinality() {
16753        // Cardinality contract: `Self::LABELS.len() == 12` — the closed
16754        // set of Sexp outermost shapes has EXACTLY twelve members. A
16755        // regression that loosens the type to `&[&'static str]` fails
16756        // HERE, as does a refactor that adds a thirteenth variant to
16757        // ALL without adding its label to LABELS (rustc's forced-arity
16758        // check on `[&'static str; 12]` fails compilation).
16759        assert_eq!(
16760            SexpShape::LABELS.len(),
16761            12,
16762            "SexpShape::LABELS cardinality drifted from 12 — the twelve \
16763             reachable Sexp outermost shapes were extended without \
16764             extending the LABELS array in lockstep"
16765        );
16766        assert_eq!(SexpShape::LABELS.len(), SexpShape::ALL.len(),);
16767    }
16768
16769    #[test]
16770    fn sexp_shape_labels_align_with_all_by_index() {
16771        // ALIGNMENT CONTRACT: `Self::LABELS[i] == Self::ALL[i].label()`
16772        // element-wise. Binds the closed-set outer algebra's ordering
16773        // to the per-role LABEL constant ordering so any
16774        // `zip(SexpShape::ALL, SexpShape::LABELS)` consumer (LSP
16775        // completion, `tatara-check` coverage sweep, Sekiban metric-
16776        // label emitter) reads a coherent (variant, label) pair off
16777        // ONE forced-arity array pair rather than through a per-
16778        // consumer paired-iteration convention.
16779        for (i, v) in SexpShape::ALL.iter().enumerate() {
16780            assert_eq!(
16781                SexpShape::LABELS[i],
16782                v.label(),
16783                "SexpShape::LABELS[{i}] `{kw}` drifted from \
16784                 SexpShape::ALL[{i}].label() `{via_variant}` — the \
16785                 canonical ALL ordering and the LABELS ordering must \
16786                 match element-wise",
16787                kw = SexpShape::LABELS[i],
16788                via_variant = v.label(),
16789            );
16790        }
16791    }
16792
16793    #[test]
16794    fn sexp_shape_labels_pairwise_distinct() {
16795        // Pairwise disjointness contract: every distinct pair of rows
16796        // in `Self::LABELS` MUST be byte-distinct. A collision would
16797        // silently degrade a `TypeMismatch { got: SexpShape::Int, ... }`
16798        // and a `TypeMismatch { got: SexpShape::Float, ... }` to
16799        // identically-rendered diagnostics, defeating the closed-set
16800        // typed-slot promotion. Twelve labels × twelve rows sweeps
16801        // `LABELS × LABELS` — supersedes any single collision pin.
16802        for (i, a) in SexpShape::LABELS.iter().enumerate() {
16803            for (j, b) in SexpShape::LABELS.iter().enumerate() {
16804                if i == j {
16805                    continue;
16806                }
16807                assert_ne!(
16808                    a, b,
16809                    "SexpShape::LABELS[{i}] `{a}` collides with \
16810                     SexpShape::LABELS[{j}] `{b}` — the diagnostic \
16811                     label surface must be pairwise byte-distinct across \
16812                     the twelve reachable Sexp outermost shapes"
16813                );
16814            }
16815        }
16816    }
16817
16818    #[test]
16819    fn sexp_shape_atom_carving_labels_align_with_expected_kwarg_shape_labels() {
16820        // CROSS-AXIS IDENTITY: the SexpShape's atomic-payload carving
16821        // ({Symbol, Keyword, String, Int, Bool, List}) shares FIVE
16822        // label-bytes byte-for-byte with ExpectedKwargShape's
16823        // corresponding per-role constants — the ExpectedKwargShape
16824        // docstrings on `KEYWORD_LABEL`, `STRING_LABEL`, `INT_LABEL`,
16825        // `BOOL_LABEL`, `LIST_LABEL` already anticipate this identity
16826        // ("matches `SexpShape::X_LABEL` byte-for-byte"). Post-lift the
16827        // identity is a compile-time cross-const composition pin
16828        // rather than a per-consumer byte-string convention.
16829        //
16830        // The overlap is intentional and load-bearing: consumer
16831        // diagnostic strings like `"expected keyword, got keyword"`
16832        // reuse the SAME byte-string on both sides of the got/expected
16833        // gate, so a spelling migration on ONE axis without the OTHER
16834        // would introduce a silent diagnostic asymmetry. Pinned here.
16835        assert_eq!(SexpShape::KEYWORD_LABEL, ExpectedKwargShape::KEYWORD_LABEL);
16836        assert_eq!(SexpShape::STRING_LABEL, ExpectedKwargShape::STRING_LABEL);
16837        assert_eq!(SexpShape::INT_LABEL, ExpectedKwargShape::INT_LABEL);
16838        assert_eq!(SexpShape::BOOL_LABEL, ExpectedKwargShape::BOOL_LABEL);
16839        assert_eq!(SexpShape::LIST_LABEL, ExpectedKwargShape::LIST_LABEL);
16840        // Non-overlapping bytes on the sibling axes: SexpShape::Float
16841        // ("float") is the narrower element-type; ExpectedKwargShape's
16842        // NUMBER_LABEL ("number") is the wider numeric-union label the
16843        // `extract_float` gate emits. The pair is intentionally
16844        // distinct — pin it so a naive rename doesn't collapse them.
16845        assert_ne!(SexpShape::FLOAT_LABEL, ExpectedKwargShape::NUMBER_LABEL);
16846    }
16847
16848    #[test]
16849    fn expected_kwarg_shape_per_role_labels_alias_sexp_shape_per_role_labels_via_static_ptr() {
16850        // ALIAS-CHAIN ROUTING CONTRACT: the FIVE per-role
16851        // `pub const ExpectedKwargShape::X_LABEL` constants that share
16852        // vocabulary with a matching `SexpShape` carving arm MUST route
16853        // through the parent-superset's `pub const SexpShape::X_LABEL`
16854        // as a compile-time typed alias
16855        // (`pub const ExpectedKwargShape::X_LABEL: &'static str =
16856        // SexpShape::X_LABEL`) rather than as two independent inline
16857        // string literals whose bytes happen to agree — the byte-
16858        // equality contract is anchored one test up at
16859        // `sexp_shape_atom_carving_labels_align_with_expected_kwarg_shape_labels`;
16860        // THIS pin catches a regression that re-inlines ONE per-role
16861        // ExpectedKwargShape constant to a fresh literal even when the
16862        // rendered bytes still agree (because a distinct inline
16863        // literal lives at a different `&'static str` address inside
16864        // the rodata segment, while a `pub const` aliased through
16865        // `SexpShape::X_LABEL` MUST point at the SAME rodata address
16866        // as the parent superset's constant).
16867        //
16868        // Sibling-shape pin to the four prior-run alias-chain routing
16869        // pins on the UnquoteForm ⊂ QuoteForm 2-of-4 subset carving
16870        // (`unquote_form_marker_routes_through_to_quote_form_prefix_via_composition`,
16871        // `unquote_form_label_routes_through_to_quote_form_label_via_composition`,
16872        // `unquote_form_iac_forge_tag_routes_through_to_quote_form_iac_forge_tag_via_composition`,
16873        // `unquote_form_hash_discriminator_routes_through_to_quote_form_hash_discriminator_via_composition`)
16874        // — those pin the substitution-subset composition through the
16875        // superset via runtime pointer-equality on a value-carrying
16876        // method; THIS pin pins the ExpectedKwargShape ⊂ SexpShape
16877        // 5-of-7 subset carving's alias-chain through the parent-
16878        // superset via compile-time `pub const` identity on a
16879        // vocabulary-carrying constant.
16880        //
16881        // The TWO non-overlapping ExpectedKwargShape arms
16882        // (`NUMBER_LABEL` and `LIST_OF_STRINGS_LABEL`) stay OUTSIDE
16883        // this pin's sweep — those are direct-literal constants with
16884        // no [`SexpShape`] peer to alias through.
16885        //
16886        // Theory anchor: THEORY.md §II.1 invariant 5 (composition
16887        // preserves proofs) — the alias-chain composition law
16888        // `ExpectedKwargShape::X_LABEL == SexpShape::X_LABEL` on the
16889        // 5-of-7 subset carving is a rustc-time typed identity, not a
16890        // runtime byte-equality convention.
16891        assert!(
16892            std::ptr::eq(
16893                ExpectedKwargShape::KEYWORD_LABEL.as_ptr(),
16894                SexpShape::KEYWORD_LABEL.as_ptr(),
16895            ),
16896            "ExpectedKwargShape::KEYWORD_LABEL and SexpShape::KEYWORD_LABEL disagree on `&'static str` rodata address — the alias chain has been broken by an inline-literal re-derivation on the ExpectedKwargShape side",
16897        );
16898        assert!(
16899            std::ptr::eq(
16900                ExpectedKwargShape::STRING_LABEL.as_ptr(),
16901                SexpShape::STRING_LABEL.as_ptr(),
16902            ),
16903            "ExpectedKwargShape::STRING_LABEL and SexpShape::STRING_LABEL disagree on `&'static str` rodata address — the alias chain has been broken by an inline-literal re-derivation on the ExpectedKwargShape side",
16904        );
16905        assert!(
16906            std::ptr::eq(
16907                ExpectedKwargShape::INT_LABEL.as_ptr(),
16908                SexpShape::INT_LABEL.as_ptr(),
16909            ),
16910            "ExpectedKwargShape::INT_LABEL and SexpShape::INT_LABEL disagree on `&'static str` rodata address — the alias chain has been broken by an inline-literal re-derivation on the ExpectedKwargShape side",
16911        );
16912        assert!(
16913            std::ptr::eq(
16914                ExpectedKwargShape::BOOL_LABEL.as_ptr(),
16915                SexpShape::BOOL_LABEL.as_ptr(),
16916            ),
16917            "ExpectedKwargShape::BOOL_LABEL and SexpShape::BOOL_LABEL disagree on `&'static str` rodata address — the alias chain has been broken by an inline-literal re-derivation on the ExpectedKwargShape side",
16918        );
16919        assert!(
16920            std::ptr::eq(
16921                ExpectedKwargShape::LIST_LABEL.as_ptr(),
16922                SexpShape::LIST_LABEL.as_ptr(),
16923            ),
16924            "ExpectedKwargShape::LIST_LABEL and SexpShape::LIST_LABEL disagree on `&'static str` rodata address — the alias chain has been broken by an inline-literal re-derivation on the ExpectedKwargShape side",
16925        );
16926    }
16927
16928    #[test]
16929    fn expected_kwarg_shape_residual_labels_lie_outside_sexp_shape_label_vocabulary() {
16930        // RESIDUAL-CARVING DISJOINTNESS CONTRACT: the TWO
16931        // ExpectedKwargShape arms that lie OUTSIDE the 5-of-7 aliased
16932        // subset carving (`NUMBER_LABEL`, `LIST_OF_STRINGS_LABEL`)
16933        // MUST carry vocabulary distinct from EVERY per-role
16934        // `SexpShape::X_LABEL` constant — otherwise the 5-of-7
16935        // carving would fail its subset-and-residual invariant (the
16936        // subset would carry MORE than 5 aliasable arms, or the
16937        // residual would carry FEWER than 2 direct-literal arms). The
16938        // pre-existing pin
16939        // `sexp_shape_atom_carving_labels_align_with_expected_kwarg_shape_labels`
16940        // anchors ONE of the two disjointness edges (SexpShape::FLOAT
16941        // vs ExpectedKwargShape::NUMBER); THIS pin anchors the
16942        // whole-family disjointness of both residual arms against
16943        // every SexpShape label at once.
16944        //
16945        // A regression that adds `SexpShape::Number` as a new variant
16946        // (renaming Float to Number) would fail HERE by making
16947        // `ExpectedKwargShape::NUMBER_LABEL` collide with the newly
16948        // added `SexpShape::NUMBER_LABEL`; the 5-of-7 carving MUST be
16949        // re-derived to 6-of-7 in that case (aliasing NUMBER_LABEL
16950        // through the new SexpShape arm). The pin surfaces the
16951        // structural-partition-change requirement at the disjointness
16952        // gate rather than through a silent literal-duplication.
16953        for &shape in SexpShape::ALL.iter() {
16954            let via_shape = SexpShape::label(shape);
16955            assert_ne!(
16956                ExpectedKwargShape::NUMBER_LABEL,
16957                via_shape,
16958                "ExpectedKwargShape::NUMBER_LABEL `\"number\"` collides with SexpShape::{shape:?}.label() `{via_shape}` — the 5-of-7 aliased subset carving's residual arm {{NUMBER}} MUST lie outside every SexpShape label; a collision means the carving needs to be re-derived to a 6-of-7 subset aliased through the newly-overlapping SexpShape arm",
16959            );
16960            assert_ne!(
16961                ExpectedKwargShape::LIST_OF_STRINGS_LABEL,
16962                via_shape,
16963                "ExpectedKwargShape::LIST_OF_STRINGS_LABEL `\"list of strings\"` collides with SexpShape::{shape:?}.label() `{via_shape}` — the 5-of-7 aliased subset carving's residual arm {{LIST_OF_STRINGS}} MUST lie outside every SexpShape label",
16964            );
16965        }
16966    }
16967
16968    #[test]
16969    fn sexp_shape_quote_carving_labels_align_with_quote_form_iac_forge_tags() {
16970        // CROSS-AXIS IDENTITY: SexpShape's quote-family carving
16971        // ({Quote, Quasiquote, Unquote}) shares THREE label-bytes
16972        // byte-for-byte with QuoteForm's corresponding iac-forge tag
16973        // constants. Post-lift the identity is a compile-time cross-
16974        // const composition pin rather than a per-consumer convention.
16975        //
16976        // The UnquoteSplice variant is the ONE intentional exception:
16977        // SexpShape::UNQUOTE_SPLICE_LABEL is `"unquote-splice"` (short
16978        // form used in `LispError::TypeMismatch` diagnostic Display)
16979        // while QuoteForm::UNQUOTE_SPLICE_IAC_FORGE_TAG is
16980        // `"unquote-splicing"` (long form used for cross-crate byte-
16981        // identical interoperability with the iac-forge canonical
16982        // form). The asymmetry is documented in the per-role constant's
16983        // docstring and pinned in
16984        // `sexp_shape_unquote_splice_label_differs_from_quote_form_iac_forge_tag`.
16985        assert_eq!(
16986            SexpShape::QUOTE_LABEL,
16987            crate::ast::QuoteForm::QUOTE_IAC_FORGE_TAG
16988        );
16989        assert_eq!(
16990            SexpShape::QUASIQUOTE_LABEL,
16991            crate::ast::QuoteForm::QUASIQUOTE_IAC_FORGE_TAG
16992        );
16993        assert_eq!(
16994            SexpShape::UNQUOTE_LABEL,
16995            crate::ast::QuoteForm::UNQUOTE_IAC_FORGE_TAG
16996        );
16997    }
16998
16999    #[test]
17000    fn sexp_shape_unquote_splice_label_differs_from_quote_form_iac_forge_tag() {
17001        // ONE intentional cross-axis asymmetry: SexpShape's diagnostic
17002        // label surface uses the SHORTER `"unquote-splice"` bytes while
17003        // QuoteForm's iac-forge canonical-form tag surface uses the
17004        // LONGER `"unquote-splicing"` bytes. Pin the asymmetry so a
17005        // naive rename on either surface doesn't collapse the two
17006        // sibling label vocabularies onto ONE spelling.
17007        assert_ne!(
17008            SexpShape::UNQUOTE_SPLICE_LABEL,
17009            crate::ast::QuoteForm::UNQUOTE_SPLICE_IAC_FORGE_TAG,
17010            "SexpShape::UNQUOTE_SPLICE_LABEL and \
17011             QuoteForm::UNQUOTE_SPLICE_IAC_FORGE_TAG collapsed to ONE \
17012             spelling — the two sibling label surfaces must render \
17013             distinct bytes so the UnquoteSplice diagnostic renders \
17014             the short `\"unquote-splice\"` bytes while iac-forge \
17015             canonical-form emits the long `\"unquote-splicing\"` bytes"
17016        );
17017        assert_eq!(SexpShape::UNQUOTE_SPLICE_LABEL, "unquote-splice");
17018        assert_eq!(
17019            crate::ast::QuoteForm::UNQUOTE_SPLICE_IAC_FORGE_TAG,
17020            "unquote-splicing"
17021        );
17022    }
17023
17024    #[test]
17025    fn type_mismatch_got_carries_typed_shape_through_variant_slot() {
17026        // After the typed-slot promotion, `LispError::TypeMismatch.got`
17027        // is `SexpShape` — the closed-set typed enum. Consumers
17028        // (REPL, LSP, `tatara-check`) pattern-match on the variant
17029        // identity `SexpShape::Int` directly rather than
17030        // substring-matching a rendered `"got int"` prefix. Pin the
17031        // structural binding AND the Display projection so the
17032        // byte-for-byte rendering contract is anchored from both
17033        // angles. A regression that re-introduces a `&'static str`-
17034        // shaped `got` slot (collapsing the typed enum back into a
17035        // free-form label) fails-loudly here.
17036        let err = LispError::TypeMismatch {
17037            form: KwargPath::named("threshold"),
17038            expected: ExpectedKwargShape::Number,
17039            got: SexpShape::String,
17040        };
17041        match &err {
17042            LispError::TypeMismatch { got, .. } => {
17043                assert_eq!(*got, SexpShape::String);
17044            }
17045            other => panic!("expected TypeMismatch, got {other:?}"),
17046        }
17047        assert_eq!(
17048            format!("{err}"),
17049            "compile error in :threshold: expected number, got string"
17050        );
17051    }
17052
17053    #[test]
17054    fn named_form_non_symbol_name_got_carries_typed_shape_through_variant_slot() {
17055        // Sibling pin to `type_mismatch_got_…` on the second `got`
17056        // slot that flows from `sexp_shape`. Both
17057        // `LispError::TypeMismatch.got` and
17058        // `LispError::NamedFormNonSymbolName.got` are typed
17059        // `SexpShape` now — one helper (`crate::domain::sexp_shape`)
17060        // is the single projection source, and rustc-enforces
17061        // matching at every projection site. A regression that
17062        // bifurcates the two slots (e.g. typed `SexpShape` on one,
17063        // `&'static str` on the other) fails-loudly here.
17064        let err = LispError::NamedFormNonSymbolName {
17065            keyword: "defpoint",
17066            got: SexpShape::List,
17067        };
17068        match &err {
17069            LispError::NamedFormNonSymbolName { got, .. } => {
17070                assert_eq!(*got, SexpShape::List);
17071            }
17072            other => panic!("expected NamedFormNonSymbolName, got {other:?}"),
17073        }
17074        assert_eq!(
17075            format!("{err}"),
17076            "compile error in defpoint: positional NAME must be a symbol or string (got list)"
17077        );
17078    }
17079
17080    #[test]
17081    fn sexp_shape_all_is_unique_and_complete() {
17082        // Closed-set posture: `ALL` enumerates every reachable variant
17083        // EXACTLY ONCE — no duplicates, no omissions. The `[Self; 12]`
17084        // array literal in the declaration forces the arity at compile
17085        // time; this test catches the orthogonal failure modes — a
17086        // future variant added at the type without being added to ALL
17087        // (silently dropped from every consumer's sweep), or a typo
17088        // that duplicates an entry (silently double-counted). Same
17089        // truth-table pinning every sibling closed-set lift in the
17090        // workspace uses (RequestorKind::ALL, ReceiptKind::ALL,
17091        // ConditionKind::ALL, ProcessPhase::ALL, ChannelKind::ALL, …).
17092        //
17093        // The `iter+map+collect+sort_unstable` quadruple this test
17094        // inlined pre-lift now binds at `<SexpShape as
17095        // ClosedSet>::sorted_labels()` — the canonical-ordered
17096        // candidate-list projection on the trait. Distinctness of the
17097        // sorted result is covered by
17098        // `assert_closed_set_well_formed::<SexpShape>()`.
17099        assert_eq!(SexpShape::ALL.len(), 12);
17100        assert_eq!(
17101            <SexpShape as tatara_closed_set::ClosedSet>::sorted_labels(),
17102            vec![
17103                "bool",
17104                "float",
17105                "int",
17106                "keyword",
17107                "list",
17108                "nil",
17109                "quasiquote",
17110                "quote",
17111                "string",
17112                "symbol",
17113                "unquote",
17114                "unquote-splice",
17115            ],
17116            "SexpShape::ALL must cover every reachable Sexp outermost shape"
17117        );
17118    }
17119
17120    #[test]
17121    fn sexp_shape_label_round_trips_through_from_str() {
17122        // Bidirectional `label` ↔ `FromStr` contract: for every
17123        // variant in ALL, `shape.label().parse() == Ok(shape)`. A
17124        // regression that drifts the (variant, literal) pairing at
17125        // ONE arm of `label` (typo, capitalization drift) OR at the
17126        // `FromStr` decode body (off-by-one, missing variant in the
17127        // sweep) fails-loudly here. The canonical-literal site is
17128        // singular (`label`) so the round-trip is the only way the
17129        // typed surface and the rendered diagnostic literal can
17130        // drift apart — pinning it here means they cannot.
17131        for shape in SexpShape::ALL {
17132            let parsed: SexpShape = shape
17133                .label()
17134                .parse()
17135                .expect("every ALL variant's label must round-trip through FromStr");
17136            assert_eq!(
17137                parsed,
17138                shape,
17139                "FromStr({}) must round-trip to the same variant",
17140                shape.label()
17141            );
17142        }
17143    }
17144
17145    #[test]
17146    fn unknown_sexp_shape_carries_offending_input_verbatim() {
17147        // Operator-facing diagnostic contract: the offending input
17148        // lands in the typed error verbatim — no normalization, no
17149        // case-folding, no truncation. Pin the exact `#[error(...)]`
17150        // rendering AND the typed `.0` field projection so a future
17151        // refactor that normalizes (e.g. `.to_lowercase()`) before
17152        // building the error or that drops the input fails-loudly
17153        // here. Symmetric to every sibling `Unknown*` carrier in the
17154        // workspace.
17155        let err: UnknownSexpShape = "Symbol".parse::<SexpShape>().expect_err(
17156            "capitalized `Symbol` must NOT decode — labels are byte-equal case-sensitive",
17157        );
17158        assert_eq!(err.0, "Symbol");
17159        assert_eq!(format!("{err}"), "unknown sexp shape: Symbol");
17160
17161        let err: UnknownSexpShape = "number"
17162            .parse::<SexpShape>()
17163            .expect_err("`number` is ExpectedKwargShape's vocabulary, not SexpShape's");
17164        assert_eq!(err.0, "number");
17165        assert_eq!(format!("{err}"), "unknown sexp shape: number");
17166
17167        let err: UnknownSexpShape = ""
17168            .parse::<SexpShape>()
17169            .expect_err("empty input must NOT decode to a SexpShape");
17170        assert_eq!(err.0, "");
17171        assert_eq!(format!("{err}"), "unknown sexp shape: ");
17172    }
17173
17174    #[test]
17175    fn sexp_shape_from_str_accepts_only_canonical_labels() {
17176        // Cross-axis guard: `ExpectedKwargShape::label()`'s vocabulary
17177        // overlaps with `SexpShape::label()` on five of seven entries
17178        // (`keyword` / `string` / `int` / `bool` / `list`) and DOES
17179        // NOT overlap on two (`number` / `list of strings`). The
17180        // overlap is intentional — both axes are projections of the
17181        // same `Sexp` algebra at typed-entry gates — but the
17182        // non-overlap is the load-bearing part: a `FromStr` that
17183        // silently accepts `"number"` as a `SexpShape` would corrupt
17184        // the typed identity. Pin BOTH directions: the overlap
17185        // decodes successfully (and to the matching `SexpShape`
17186        // variant), the non-overlap rejects.
17187        assert_eq!("keyword".parse::<SexpShape>().unwrap(), SexpShape::Keyword);
17188        assert_eq!("string".parse::<SexpShape>().unwrap(), SexpShape::String);
17189        assert_eq!("int".parse::<SexpShape>().unwrap(), SexpShape::Int);
17190        assert_eq!("bool".parse::<SexpShape>().unwrap(), SexpShape::Bool);
17191        assert_eq!("list".parse::<SexpShape>().unwrap(), SexpShape::List);
17192
17193        assert!("number".parse::<SexpShape>().is_err());
17194        assert!("list of strings".parse::<SexpShape>().is_err());
17195
17196        // Sanity: every UnquoteForm marker literal (`,` / `,@` / etc.)
17197        // is also NOT a SexpShape label — the marker projection lives
17198        // on a different axis (the rendered punctuation) than the
17199        // shape label (the structural identity).
17200        assert!(",".parse::<SexpShape>().is_err());
17201        assert!(",@".parse::<SexpShape>().is_err());
17202    }
17203
17204    #[test]
17205    fn sexp_shape_int_bifurcates_from_float_through_variant_slot() {
17206        // `Int` and `Float` are distinct typed variants — a regression
17207        // that collapses them into a single `Number` variant (which
17208        // would drop the bifurcation that `Sexp::Atom(Int(_))` and
17209        // `Sexp::Atom(Float(_))` already carry at the AST layer) is
17210        // caught here. The two render distinct rendered labels and
17211        // hold distinct variant identities.
17212        let int_err = LispError::TypeMismatch {
17213            form: KwargPath::named("count"),
17214            expected: ExpectedKwargShape::String,
17215            got: SexpShape::Int,
17216        };
17217        let float_err = LispError::TypeMismatch {
17218            form: KwargPath::named("ratio"),
17219            expected: ExpectedKwargShape::String,
17220            got: SexpShape::Float,
17221        };
17222        assert_eq!(
17223            format!("{int_err}"),
17224            "compile error in :count: expected string, got int"
17225        );
17226        assert_eq!(
17227            format!("{float_err}"),
17228            "compile error in :ratio: expected string, got float"
17229        );
17230        match (&int_err, &float_err) {
17231            (LispError::TypeMismatch { got: a, .. }, LispError::TypeMismatch { got: b, .. }) => {
17232                assert_ne!(a, b);
17233                assert_eq!(*a, SexpShape::Int);
17234                assert_eq!(*b, SexpShape::Float);
17235            }
17236            _ => panic!("both must be TypeMismatch"),
17237        }
17238    }
17239
17240    // ── SexpShape ↔ AtomKind / QuoteForm: typed-shape lattice inverses ──
17241    //
17242    // The forward embed projections [`crate::ast::AtomKind::sexp_shape`]
17243    // (6→12) and [`crate::ast::QuoteForm::sexp_shape`] (4→12) have
17244    // existed for prior runs (commits 121bb60 + b15-ish); their dual
17245    // projections [`SexpShape::as_atom_kind`] (12→6, partial) and
17246    // [`SexpShape::as_quote_form`] (12→4, partial) close the
17247    // embed/project section on the typed-shape lattice. The
17248    // composition laws below pin the (embed, project) pair is an
17249    // `Iso(AtomKind, AtomShape ⊂ SexpShape)` AND
17250    // `Iso(QuoteForm, QuoteShape ⊂ SexpShape)` — every typed marker
17251    // round-trips through the embed, every shape pre-image recovers
17252    // the typed marker. Pre-lift the typed-shape lattice's two
17253    // forward embeds had no dual projection naming the inverse 6-of-
17254    // 12 + 4-of-12 carvings; post-lift the carvings live at ONE site
17255    // each on the [`SexpShape`] algebra so a regression that drifts
17256    // the inverse from the embed surfaces at the round-trip pin
17257    // instead of at every speculative LSP / REPL / `tatara-check` /
17258    // metrics consumer's per-carving inline `match`.
17259
17260    #[test]
17261    fn as_atom_kind_projects_each_atom_shape_to_canonical_atom_kind_and_rejects_non_atom_shapes() {
17262        // Per-variant truth-table sweep across every `SexpShape::ALL`
17263        // entry — pins each variant's canonical mapping (atomic-payload
17264        // arms project to the matching `AtomKind`; `Nil` / `List` /
17265        // every quote-family arm project to `None`) byte-for-byte so a
17266        // regression that drifts ONE arm (e.g. swaps `Symbol →
17267        // AtomKind::Keyword`, drops `Bool`'s arm to `None`, accepts
17268        // `List` as `Some(AtomKind::Str)`) fails loudly. The full
17269        // `SexpShape::ALL` sweep doubles as exhaustiveness — adding a
17270        // hypothetical thirteenth variant (e.g. `Vector` for `#(...)`)
17271        // forces the test author to extend BOTH this sweep AND the
17272        // typed `match` body, with rustc enforcing the match arm's
17273        // presence and this sweep enforcing the (variant, projected
17274        // mapping) pairing's canonical-form.
17275        use crate::ast::AtomKind;
17276        for shape in SexpShape::ALL {
17277            let projected = shape.as_atom_kind();
17278            let expected = match shape {
17279                SexpShape::Symbol => Some(AtomKind::Symbol),
17280                SexpShape::Keyword => Some(AtomKind::Keyword),
17281                SexpShape::String => Some(AtomKind::Str),
17282                SexpShape::Int => Some(AtomKind::Int),
17283                SexpShape::Float => Some(AtomKind::Float),
17284                SexpShape::Bool => Some(AtomKind::Bool),
17285                SexpShape::Nil
17286                | SexpShape::List
17287                | SexpShape::Quote
17288                | SexpShape::Quasiquote
17289                | SexpShape::Unquote
17290                | SexpShape::UnquoteSplice => None,
17291            };
17292            assert_eq!(
17293                projected, expected,
17294                "SexpShape::{shape:?}.as_atom_kind() drifted from canonical mapping"
17295            );
17296        }
17297    }
17298
17299    #[test]
17300    fn atom_kind_sexp_shape_round_trips_through_as_atom_kind() {
17301        // The embed/project section law on the atomic carving:
17302        // `AtomKind::sexp_shape(k).as_atom_kind() == Some(k)` for every
17303        // `k: AtomKind::ALL`. Pinning the round-trip for every variant
17304        // in the closed set proves the (embed, project) pair is an
17305        // `Iso(AtomKind, AtomShape ⊂ SexpShape)` — the section is total
17306        // on `AtomKind`'s carving. A regression that drifts EITHER
17307        // direction (an `AtomKind::sexp_shape` arm that mis-maps OR a
17308        // `SexpShape::as_atom_kind` arm that mis-inverts) fails here
17309        // without depending on any per-consumer call site. Same posture
17310        // as `unquote_form_marker_routes_through_to_quote_form_prefix_
17311        // via_composition`'s round-trip on the 2-of-4 quote-family
17312        // subset.
17313        use crate::ast::AtomKind;
17314        for kind in AtomKind::ALL {
17315            let shape = kind.sexp_shape();
17316            let recovered = shape.as_atom_kind();
17317            assert_eq!(
17318                recovered,
17319                Some(kind),
17320                "AtomKind::{kind:?} did NOT round-trip — sexp_shape().as_atom_kind() must recover the typed marker"
17321            );
17322        }
17323    }
17324
17325    #[test]
17326    fn as_quote_form_projects_each_quote_shape_to_canonical_quote_form_and_rejects_non_quote_shapes(
17327    ) {
17328        // Per-variant truth-table sweep across every `SexpShape::ALL`
17329        // entry — pins each variant's canonical mapping (quote-family
17330        // arms project to the matching `QuoteForm`; `Nil` / `List` /
17331        // every atomic-payload arm project to `None`) byte-for-byte.
17332        // Sibling sweep to
17333        // `as_atom_kind_projects_each_atom_shape_to_canonical_atom_kind_and_rejects_non_atom_shapes`
17334        // on the quote-family axis.
17335        use crate::ast::QuoteForm;
17336        for shape in SexpShape::ALL {
17337            let projected = shape.as_quote_form();
17338            let expected = match shape {
17339                SexpShape::Quote => Some(QuoteForm::Quote),
17340                SexpShape::Quasiquote => Some(QuoteForm::Quasiquote),
17341                SexpShape::Unquote => Some(QuoteForm::Unquote),
17342                SexpShape::UnquoteSplice => Some(QuoteForm::UnquoteSplice),
17343                SexpShape::Nil
17344                | SexpShape::List
17345                | SexpShape::Symbol
17346                | SexpShape::Keyword
17347                | SexpShape::String
17348                | SexpShape::Int
17349                | SexpShape::Float
17350                | SexpShape::Bool => None,
17351            };
17352            assert_eq!(
17353                projected, expected,
17354                "SexpShape::{shape:?}.as_quote_form() drifted from canonical mapping"
17355            );
17356        }
17357    }
17358
17359    #[test]
17360    fn quote_form_sexp_shape_round_trips_through_as_quote_form() {
17361        // The embed/project section law on the quote-family carving:
17362        // `QuoteForm::sexp_shape(qf).as_quote_form() == Some(qf)` for
17363        // every `qf: QuoteForm::ALL`. Proves the (embed, project) pair
17364        // is an `Iso(QuoteForm, QuoteShape ⊂ SexpShape)` — the section
17365        // is total on `QuoteForm`'s carving. Sibling round-trip to
17366        // `atom_kind_sexp_shape_round_trips_through_as_atom_kind` on
17367        // the quote-family axis.
17368        use crate::ast::QuoteForm;
17369        for qf in QuoteForm::ALL {
17370            let shape = qf.sexp_shape();
17371            let recovered = shape.as_quote_form();
17372            assert_eq!(
17373                recovered,
17374                Some(qf),
17375                "QuoteForm::{qf:?} did NOT round-trip — sexp_shape().as_quote_form() must recover the typed marker"
17376            );
17377        }
17378    }
17379
17380    #[test]
17381    fn as_atom_kind_and_as_quote_form_partition_carvable_sexp_shape_variants() {
17382        // Disjointness invariant: for every variant in
17383        // `SexpShape::ALL`, AT MOST ONE of `as_atom_kind()` and
17384        // `as_quote_form()` returns `Some` — the typed-shape lattice's
17385        // two named carvings partition the carve-able SexpShape
17386        // variants. The two non-carved variants (`Nil`, `List`) project
17387        // to `None` through BOTH projections — the kernel of both
17388        // partial inverses. A regression that drifts the partition
17389        // (e.g. accepts `SexpShape::List` as an atom kind, or as a
17390        // quote form) fails here. Sibling to
17391        // `as_atom_kind_projects_each_atom_shape_to_canonical_atom_kind_and_rejects_non_atom_shapes`
17392        // and
17393        // `as_quote_form_projects_each_quote_shape_to_canonical_quote_form_and_rejects_non_quote_shapes`
17394        // — those pin the per-axis canonical mapping; this pins the
17395        // joint disjointness across both axes. The residual-side of the
17396        // partition (the two `Nil`/`List` variants) is now itself a
17397        // typed carving `StructuralKind` — pinned by
17398        // `sexp_shape_partition_is_total_across_atom_quote_structural_carvings`.
17399        for shape in SexpShape::ALL {
17400            let atom = shape.as_atom_kind().is_some();
17401            let quote = shape.as_quote_form().is_some();
17402            assert!(
17403                !(atom && quote),
17404                "SexpShape::{shape:?} projects as BOTH an atom kind AND a quote form — typed-shape carvings must be disjoint"
17405            );
17406            // Cross-axis closure: the only variants that project as
17407            // NEITHER are the structural-residual shapes (`Nil` and
17408            // `List`). Every other variant must be in exactly ONE of
17409            // the two named carvings — the substrate's typed-shape
17410            // lattice's structural completeness pin. Post-lift the
17411            // residual-side identity binds to the typed
17412            // `as_structural_kind()` projection rather than a runtime
17413            // `matches!(shape, SexpShape::Nil | SexpShape::List)`
17414            // predicate; the invariant lives at ONE closed-set match
17415            // on the outer algebra.
17416            let carved = atom || quote;
17417            let expected_carved = shape.as_structural_kind().is_none();
17418            assert_eq!(
17419                carved, expected_carved,
17420                "SexpShape::{shape:?} must be carved iff it does not project through as_structural_kind"
17421            );
17422        }
17423    }
17424
17425    #[test]
17426    fn sexp_shape_partition_is_total_across_atom_quote_structural_carvings() {
17427        // Partition-total invariant across ALL THREE typed-shape
17428        // carvings — the (typed theorem, previously test assertion)
17429        // this lift makes structural. For every variant in
17430        // `SexpShape::ALL`, EXACTLY ONE of `as_atom_kind()`,
17431        // `as_quote_form()`, `as_structural_kind()` returns `Some(_)`.
17432        // Pre-lift the residual side lived at ONE inline
17433        // `!matches!(shape, SexpShape::Nil | SexpShape::List)`
17434        // assertion inside the carving-disjointness pin above (the
17435        // runtime witness the substrate maintained by test
17436        // discipline); post-lift the residual is a TYPED CARVING
17437        // binding at ONE typed-algebra method, and the partition-
17438        // total invariant across the three carvings becomes a TYPED
17439        // THEOREM the joint sweep here surfaces byte-for-byte.
17440        //
17441        // Cardinalities: 6 + 4 + 2 = 12 (atomic-payload + quote-family
17442        // + structural-residual = every SexpShape). A regression that
17443        // drifts the partition-membership of any variant across any
17444        // of the three carvings surfaces here.
17445        let mut atom_count = 0;
17446        let mut quote_count = 0;
17447        let mut structural_count = 0;
17448        for shape in SexpShape::ALL {
17449            let atom = shape.as_atom_kind().is_some();
17450            let quote = shape.as_quote_form().is_some();
17451            let structural = shape.as_structural_kind().is_some();
17452            let membership = usize::from(atom) + usize::from(quote) + usize::from(structural);
17453            assert_eq!(
17454                membership, 1,
17455                "SexpShape::{shape:?} lands in {membership} carvings — partition must land it in EXACTLY ONE of (as_atom_kind, as_quote_form, as_structural_kind)",
17456            );
17457            atom_count += usize::from(atom);
17458            quote_count += usize::from(quote);
17459            structural_count += usize::from(structural);
17460        }
17461        assert_eq!(
17462            atom_count, 6,
17463            "atomic-payload carving must cover EXACTLY 6 of SexpShape::ALL",
17464        );
17465        assert_eq!(
17466            quote_count, 4,
17467            "quote-family carving must cover EXACTLY 4 of SexpShape::ALL",
17468        );
17469        assert_eq!(
17470            structural_count, 2,
17471            "structural-residual carving must cover EXACTLY 2 of SexpShape::ALL",
17472        );
17473        assert_eq!(
17474            atom_count + quote_count + structural_count,
17475            SexpShape::ALL.len(),
17476            "the three carvings must partition SexpShape::ALL totally (6 + 4 + 2 = 12)",
17477        );
17478    }
17479
17480    #[test]
17481    fn as_atom_kind_composes_with_sexp_shape_via_atom_kind_label_round_trip() {
17482        // Cross-projection composition law: for every atom kind, the
17483        // diagnostic label round-trips through both directions of the
17484        // embed/project pair AND `AtomKind::label`:
17485        // `AtomKind::ALL[i].label() == AtomKind::ALL[i].sexp_shape()
17486        // .as_atom_kind().expect("...").label()`. This pin proves the
17487        // typed-shape lattice's two carvings are not only structural
17488        // inverses but ALSO label-coherent — a regression that drifts
17489        // the (AtomKind variant, SexpShape variant) pairing while
17490        // preserving the projection's structural inverseness (e.g. a
17491        // future refactor that renames the variants in lockstep but
17492        // leaves the per-variant labels stale) surfaces here. The
17493        // label-coherence binds the diagnostic surface to the typed
17494        // algebra at BOTH layers.
17495        use crate::ast::AtomKind;
17496        for kind in AtomKind::ALL {
17497            let via_round_trip = kind
17498                .sexp_shape()
17499                .as_atom_kind()
17500                .expect("every AtomKind round-trips through the embed/project pair")
17501                .label();
17502            assert_eq!(
17503                via_round_trip,
17504                kind.label(),
17505                "AtomKind::{kind:?}.label() drifted from sexp_shape().as_atom_kind().label() — embed/project must preserve label coherence"
17506            );
17507        }
17508    }
17509
17510    #[test]
17511    fn as_quote_form_composes_with_sexp_shape_via_quote_form_prefix_round_trip() {
17512        // Cross-projection composition law (quote-family sibling of
17513        // `as_atom_kind_composes_with_sexp_shape_via_atom_kind_label_round_trip`):
17514        // for every `qf: QuoteForm::ALL`,
17515        // `qf.prefix() == qf.sexp_shape().as_quote_form().expect("...").prefix()`.
17516        // Pins the (QuoteForm variant, SexpShape variant) pairing
17517        // round-trips through the embed/project pair AND preserves
17518        // each variant's canonical homoiconic-prefix punctuation
17519        // (`"'"` / `` "`" `` / `","` / `",@"`) — a regression that
17520        // drifts the round-trip OR drifts the prefix surfaces here.
17521        use crate::ast::QuoteForm;
17522        for qf in QuoteForm::ALL {
17523            let via_round_trip = qf
17524                .sexp_shape()
17525                .as_quote_form()
17526                .expect("every QuoteForm round-trips through the embed/project pair")
17527                .prefix();
17528            assert_eq!(
17529                via_round_trip,
17530                qf.prefix(),
17531                "QuoteForm::{qf:?}.prefix() drifted from sexp_shape().as_quote_form().prefix() — embed/project must preserve prefix coherence"
17532            );
17533        }
17534    }
17535
17536    // ── UnquoteForm: ALL closure + FromStr round-trip ──────────────────
17537    //
17538    // `UnquoteForm` (the two template-marker syntactic forms `,` and
17539    // `,@`) joins the substrate's closed-set algebra family —
17540    // `SexpShape::ALL` + `FromStr`, `AtomKind::ALL` + `FromStr`,
17541    // `RequestorKind::ALL` + `FromStr`, etc. — by lifting the canonical
17542    // `&'static str` marker literal vocabulary onto ONE site
17543    // (`Self::marker` keyed by `Self::ALL`) the operator-facing decode
17544    // path inverts. Pre-lift the punctuation vocabulary lived ONLY
17545    // in `marker()`'s match arms; post-lift the SAME vocabulary
17546    // round-trips through `FromStr` keyed on the closed set, so the
17547    // typed surface and the rendered diagnostic literal cannot drift.
17548    // Same posture as `sexp_shape_label_round_trips_through_from_str`
17549    // / `atom_kind_label_round_trips_through_from_str`.
17550
17551    #[test]
17552    fn unquote_form_all_is_unique_and_complete() {
17553        // Closed-set posture: `ALL` enumerates every reachable variant
17554        // EXACTLY ONCE — no duplicates, no omissions. The `[Self; 2]`
17555        // array literal in the declaration forces the arity at compile
17556        // time; this test catches the orthogonal failure modes — a
17557        // future variant added at the type without being added to ALL
17558        // (silently dropped from every consumer's sweep), or a typo
17559        // that duplicates an entry (silently double-counted). Same
17560        // truth-table pinning every sibling closed-set lift in the
17561        // workspace uses (SexpShape::ALL, AtomKind::ALL,
17562        // RequestorKind::ALL, ReceiptKind::ALL, ConditionKind::ALL, …).
17563        //
17564        // The `iter+map+collect+sort_unstable` quadruple this test
17565        // inlined pre-lift now binds at `<UnquoteForm as
17566        // ClosedSet>::sorted_labels()` — the canonical-ordered
17567        // candidate-list projection on the trait. Distinctness of the
17568        // sorted result is covered by
17569        // `assert_closed_set_well_formed::<UnquoteForm>()`.
17570        assert_eq!(UnquoteForm::ALL.len(), 2);
17571        assert_eq!(
17572            <UnquoteForm as tatara_closed_set::ClosedSet>::sorted_labels(),
17573            vec![",", ",@"],
17574            "UnquoteForm::ALL must cover both template-marker syntactic forms"
17575        );
17576    }
17577
17578    #[test]
17579    fn unquote_form_marker_round_trips_through_from_str() {
17580        // Bidirectional `marker` ↔ `FromStr` contract: for every
17581        // variant in ALL, `form.marker().parse() == Ok(form)`. A
17582        // regression that drifts the (variant, literal) pairing at
17583        // ONE arm of `marker` (typo, `,,` instead of `,`, `, @` with
17584        // a stray space) OR at the `FromStr` decode body (off-by-one,
17585        // missing variant in the sweep) fails-loudly here. The
17586        // canonical-literal site is singular (`marker`) so the
17587        // round-trip is the only way the typed surface and the
17588        // rendered diagnostic literal can drift apart — pinning it
17589        // here means they cannot.
17590        for form in UnquoteForm::ALL {
17591            let parsed: UnquoteForm = form
17592                .marker()
17593                .parse()
17594                .expect("every ALL variant's marker must round-trip through FromStr");
17595            assert_eq!(
17596                parsed,
17597                form,
17598                "FromStr({}) must round-trip to the same variant",
17599                form.marker()
17600            );
17601        }
17602    }
17603
17604    #[test]
17605    fn unknown_unquote_form_carries_offending_input_verbatim() {
17606        // Operator-facing diagnostic contract: the offending input
17607        // lands in the typed error verbatim — no normalization, no
17608        // truncation, no whitespace coercion. Pin the exact
17609        // `#[error(...)]` rendering AND the typed `.0` field
17610        // projection so a future refactor that normalizes (e.g.
17611        // `.trim()`) before building the error or that drops the
17612        // input fails-loudly here. Symmetric to every sibling
17613        // `Unknown*` carrier in the workspace
17614        // ([`UnknownSexpShape`], [`crate::ast::UnknownAtomKind`],
17615        // `tatara_process::allocation::UnknownRequestorKind`, …).
17616        let err: UnknownUnquoteForm = ",,"
17617            .parse::<UnquoteForm>()
17618            .expect_err("doubled comma `,,` is not a canonical template marker");
17619        assert_eq!(err.0, ",,");
17620        assert_eq!(format!("{err}"), "unknown unquote form: ,,");
17621
17622        let err: UnknownUnquoteForm = ",@@"
17623            .parse::<UnquoteForm>()
17624            .expect_err("doubled-at `,@@` is not a canonical template marker");
17625        assert_eq!(err.0, ",@@");
17626        assert_eq!(format!("{err}"), "unknown unquote form: ,@@");
17627
17628        let err: UnknownUnquoteForm = ""
17629            .parse::<UnquoteForm>()
17630            .expect_err("empty input must NOT decode to an UnquoteForm");
17631        assert_eq!(err.0, "");
17632        assert_eq!(format!("{err}"), "unknown unquote form: ");
17633    }
17634
17635    #[test]
17636    fn unquote_form_from_str_rejects_sexp_shape_labels_on_template_marker_axis() {
17637        // Cross-axis guard: [`SexpShape`] projects the SAME two
17638        // `Sexp::Unquote` / `Sexp::UnquoteSplice` constructors as
17639        // [`UnquoteForm`] does, but onto a DIFFERENT vocabulary —
17640        // `"unquote"` / `"unquote-splice"` (structural-identity labels)
17641        // vs `","` / `",@"` (punctuation markers). The two axes share
17642        // the same closed-set cardinality (2) but their vocabularies
17643        // are intentionally disjoint. A `FromStr` that silently
17644        // accepted `"unquote"` as an `UnquoteForm` would corrupt the
17645        // typed identity at the diagnostic boundary. Pin BOTH
17646        // directions: the SAME punctuation labels (`,` / `,@`) decode
17647        // through [`UnquoteForm`] but NOT through [`SexpShape`]; the
17648        // SAME structural labels (`"unquote"` / `"unquote-splice"`)
17649        // decode through [`SexpShape`] but NOT through [`UnquoteForm`].
17650        // Anchors the cross-axis disjointness from BOTH sides so a
17651        // regression that conflates the two axes' vocabularies fails
17652        // here.
17653        assert_eq!(",".parse::<UnquoteForm>().unwrap(), UnquoteForm::Unquote);
17654        assert_eq!(",@".parse::<UnquoteForm>().unwrap(), UnquoteForm::Splice);
17655
17656        // The structural-identity labels project the SAME variants on
17657        // the SexpShape axis but are NOT canonical UnquoteForm markers.
17658        assert!("unquote".parse::<UnquoteForm>().is_err());
17659        assert!("unquote-splice".parse::<UnquoteForm>().is_err());
17660
17661        // Sibling homoiconic-prefix-wrapper markers (`'` for quote,
17662        // `` ` `` for quasiquote) belong to the WIDER QuoteForm
17663        // superset on the SAME punctuation axis — they MUST reject
17664        // here because UnquoteForm carves the 2-of-4 template-
17665        // substitution subset of QuoteForm's 4-prefix closed set.
17666        assert!("'".parse::<UnquoteForm>().is_err());
17667        assert!("`".parse::<UnquoteForm>().is_err());
17668
17669        // Whitespace-padded markers are NOT canonical — the
17670        // round-trip must be exact byte-for-byte against `marker()`.
17671        assert!(" ,".parse::<UnquoteForm>().is_err());
17672        assert!(", ".parse::<UnquoteForm>().is_err());
17673        assert!(", @".parse::<UnquoteForm>().is_err());
17674    }
17675
17676    #[test]
17677    fn unquote_form_to_quote_form_round_trips_through_as_unquote_form() {
17678        // Pin the 2-of-4 subset → superset projection as a typed
17679        // section of [`crate::ast::QuoteForm::as_unquote_form`]: for
17680        // every `uf: UnquoteForm`, `uf.to_quote_form().as_unquote_form()
17681        // == Some(uf)`. Closes the (UnquoteForm, QuoteForm) pairing as
17682        // a round-trip identity on the typed algebra — pre-lift the
17683        // pairing only lived in the existing
17684        // `unquote_form_marker_subset_decodes_through_quote_form_from_str`
17685        // cross-axis test (which round-tripped through the rendered
17686        // marker string + FromStr); post-lift the pairing rides the
17687        // typed projection directly so a future regression that drifts
17688        // the (UnquoteForm variant, QuoteForm variant) pairing
17689        // (e.g., a future arm that maps `UnquoteForm::Splice →
17690        // QuoteForm::Unquote`) fails this assertion without depending
17691        // on the FromStr decoder sitting between the two.
17692        use crate::ast::QuoteForm;
17693        for uf in UnquoteForm::ALL {
17694            assert_eq!(
17695                uf.to_quote_form().as_unquote_form(),
17696                Some(uf),
17697                "UnquoteForm::{uf:?} → QuoteForm via to_quote_form does not invert through QuoteForm::as_unquote_form — the 2-of-4 subset projection is no longer a section",
17698            );
17699        }
17700
17701        // Per-arm pinning of the canonical mapping (the byte-for-byte
17702        // pairing the composition `marker == to_quote_form().prefix()`
17703        // depends on).
17704        assert_eq!(UnquoteForm::Unquote.to_quote_form(), QuoteForm::Unquote);
17705        assert_eq!(
17706            UnquoteForm::Splice.to_quote_form(),
17707            QuoteForm::UnquoteSplice
17708        );
17709    }
17710
17711    #[test]
17712    fn unquote_form_marker_routes_through_to_quote_form_prefix_via_composition() {
17713        // Post-lift composition pin: for every `uf: UnquoteForm`,
17714        // `uf.marker()` and `uf.to_quote_form().prefix()` agree on
17715        // BOTH axes — (a) byte equality (the rendered diagnostic
17716        // literal cannot drift between the two projections); (b)
17717        // pointer equality (the canonical `&'static str` literal lives
17718        // at ONE site — `QuoteForm::prefix`'s Unquote/UnquoteSplice
17719        // arms in `ast.rs` — and `UnquoteForm::marker` routes through
17720        // that ONE address via the typed composition).
17721        //
17722        // The pointer-equality axis is load-bearing: a regression that
17723        // re-inlines the literals at `UnquoteForm::marker` as a
17724        // parallel match-table fails the pointer pin even when the
17725        // rendered bytes still agree (the inline-literal-table copy
17726        // lives at a different `&'static str` address — rustc may
17727        // de-duplicate identical literals within a single Rust
17728        // compilation unit, but the contract this test pins is
17729        // routing-through-the-canonical-site, not deduplication-by-the-
17730        // optimizer; a future build flag that disables literal dedup
17731        // would unmask the regression that the bytes-only assertion
17732        // misses). Sibling-shape pin to commit 1db697f's
17733        // `atom_kind_label_routes_through_sexp_shape_label_via_sexp_shape_projection`
17734        // — both pin the subset's projection through the superset's
17735        // canonical site via pointer-equality, the structural invariant
17736        // the subset-to-superset composition was lifted to make
17737        // load-bearing on the type system rather than on per-callsite
17738        // discipline.
17739        for uf in UnquoteForm::ALL {
17740            let from_marker = uf.marker();
17741            let from_composition = uf.to_quote_form().prefix();
17742            assert_eq!(
17743                from_marker, from_composition,
17744                "UnquoteForm::{uf:?}.marker() bytes drifted from .to_quote_form().prefix() bytes — the subset's diagnostic vocabulary is no longer derived from the superset's canonical site",
17745            );
17746            assert!(
17747                std::ptr::eq(from_marker.as_ptr(), from_composition.as_ptr()),
17748                "UnquoteForm::{uf:?}.marker() and .to_quote_form().prefix() disagree on `&'static str` address — pointer drift means the lift composes through a parallel literal table rather than routing into the canonical QuoteForm::prefix site",
17749            );
17750        }
17751    }
17752
17753    #[test]
17754    fn unquote_form_per_role_markers_alias_quote_form_per_role_prefixes_byte_for_byte() {
17755        // ALIAS CONTRACT: pin both per-role `pub const UnquoteForm::*_MARKER`
17756        // aliases equal the corresponding `pub const QuoteForm::*_PREFIX`
17757        // byte-for-byte — so the UnquoteForm ⊂ QuoteForm marker-vocabulary
17758        // containment routes through the typed
17759        // `pub const UnquoteForm::V_MARKER: &'static str = QuoteForm::V_PREFIX`
17760        // alias chain rather than through two independent literal-discipline
17761        // sites. A regression that renames the QuoteForm side without
17762        // updating the UnquoteForm alias pointing at it fails-loudly here
17763        // with the exact axis identified (UNQUOTE / SPLICE); a regression
17764        // that re-inlines the UnquoteForm constant to a fresh literal still
17765        // passes this pin but loses the alias-chain typing (which is what
17766        // `unquote_form_marker_arms_route_through_per_role_markers_for_every_variant`
17767        // + `unquote_form_markers_align_with_all_by_index` catch in
17768        // combination).
17769        //
17770        // Sibling posture to
17771        // `atom_kind_per_role_labels_alias_sexp_shape_per_role_labels_byte_for_byte`
17772        // (commit fc126b8): both pin the invariant that a typed-subset
17773        // enum's per-role bytes are structurally derived from the parent
17774        // superset's per-role bytes via a `pub const = Parent::CONST`
17775        // alias rather than through parallel literal tables. The subset
17776        // there is AtomKind ⊂ SexpShape (6-of-12 on the atomic-payload
17777        // axis); the subset here is UnquoteForm ⊂ QuoteForm (2-of-4 on
17778        // the template-substitution axis) — both are load-bearing
17779        // structural carvings whose per-role vocabularies MUST agree
17780        // byte-for-byte with their parent's.
17781        use crate::ast::QuoteForm;
17782        assert_eq!(UnquoteForm::UNQUOTE_MARKER, QuoteForm::UNQUOTE_PREFIX);
17783        assert_eq!(UnquoteForm::SPLICE_MARKER, QuoteForm::UNQUOTE_SPLICE_PREFIX);
17784    }
17785
17786    #[test]
17787    fn unquote_form_marker_arms_route_through_per_role_markers_for_every_variant() {
17788        // PATH-UNIFORMITY: `UnquoteForm::V.marker()` MUST equal the
17789        // per-role `pub const UnquoteForm::V_MARKER` for every
17790        // `v: UnquoteForm`. Pre-lift the two template-marker bytes
17791        // were reachable through `UnquoteForm::marker` (the composition
17792        // `self.to_quote_form().prefix()` — routing into
17793        // `QuoteForm::UNQUOTE_PREFIX` / `UNQUOTE_SPLICE_PREFIX`) OR
17794        // through direct `QuoteForm::UNQUOTE_PREFIX` reach-across;
17795        // post-lift each variant's canonical bytes are reachable
17796        // through the per-role `UnquoteForm::*_MARKER` alias too. Pin
17797        // the byte-equality between the runtime projection and the
17798        // compile-time alias so a regression that renames the alias
17799        // without updating the arm (or vice versa) fails-loudly at
17800        // the exact axis.
17801        //
17802        // Sibling-shape pin to
17803        // `atom_kind_label_arms_route_through_per_role_labels_for_every_variant`
17804        // one algebra layer over — the AtomKind subset algebra's per-
17805        // role aliases are pinned against `AtomKind::label`'s
17806        // composition-routed arms there; this pin binds the
17807        // UnquoteForm subset algebra's per-role aliases against
17808        // `UnquoteForm::marker`'s composition-routed arms so the two
17809        // template-marker bytes project through ONE aliased typed
17810        // source of truth per role rather than through per-consumer
17811        // inline literals.
17812        assert_eq!(UnquoteForm::Unquote.marker(), UnquoteForm::UNQUOTE_MARKER);
17813        assert_eq!(UnquoteForm::Splice.marker(), UnquoteForm::SPLICE_MARKER);
17814    }
17815
17816    #[test]
17817    fn unquote_form_markers_has_expected_cardinality() {
17818        // Cardinality pin: `MARKERS.len() == 2` matches `ALL.len()` so
17819        // a refactor that loosens the type to `&'static [&'static str]`
17820        // fails HERE (the `[_; 2]` slot cannot be sliced silently),
17821        // and a variant added to `ALL` without a matching `MARKERS`
17822        // row fails the pair-arity gate at the array literal itself
17823        // before this test even runs. The pin doubles as an operator-
17824        // visible mark of the family's cardinality across the
17825        // substrate — two template markers, matching the 2-of-4
17826        // carving of the parent `QuoteForm::PREFIXES` (the
17827        // substitution-subset of the four canonical homoiconic prefix
17828        // bytes).
17829        assert_eq!(UnquoteForm::MARKERS.len(), 2);
17830        assert_eq!(UnquoteForm::MARKERS.len(), UnquoteForm::ALL.len());
17831    }
17832
17833    #[test]
17834    fn unquote_form_markers_align_with_all_by_index() {
17835        // ALIGNMENT PIN: sweep `MARKERS[i] == ALL[i].marker()` so any
17836        // `zip(ALL, MARKERS)` consumer reads a coherent (variant,
17837        // marker) pair off ONE forced-arity array pair. The
17838        // declaration-order pin makes a family-wide consumer that
17839        // walks the ALL / MARKERS pair in lockstep (an LSP completion
17840        // bar keyed on `UnquoteForm::MARKERS`, a Sekiban metric
17841        // emitter labeling
17842        // `tatara_lisp_unbound_template_var_total{prefix}` by the
17843        // per-index marker) read one canonical (variant, bytes) pair
17844        // per slot rather than routing through per-consumer paired-
17845        // iteration. A regression that reorders MARKERS without also
17846        // reordering ALL (or vice versa) fails-loudly at the exact
17847        // index that drifted.
17848        assert_eq!(UnquoteForm::MARKERS.len(), UnquoteForm::ALL.len());
17849        for (i, form) in UnquoteForm::ALL.iter().enumerate() {
17850            assert_eq!(
17851                UnquoteForm::MARKERS[i],
17852                form.marker(),
17853                "UnquoteForm::MARKERS[{i}] `{mkr}` drifted from \
17854                 UnquoteForm::ALL[{i}].marker() `{via_variant}` — the \
17855                 canonical ALL ordering and the MARKERS ordering must \
17856                 match element-wise",
17857                mkr = UnquoteForm::MARKERS[i],
17858                via_variant = form.marker(),
17859            );
17860        }
17861    }
17862
17863    #[test]
17864    fn unquote_form_markers_pairwise_distinct() {
17865        // 2x2 pairwise sweep so a collision between the two markers
17866        // (which would silently degrade two distinct template-
17867        // substitution markers to the SAME diagnostic bytes and
17868        // violate the closed-set FromStr round-trip) fails-loudly at
17869        // the exact pair. Distinctness is already enforced
17870        // structurally by `assert_closed_set_well_formed::<UnquoteForm>()`
17871        // (clause 3), so this pin is a secondary guard focused on the
17872        // per-role `pub const` surface directly rather than the
17873        // runtime projection through the trait's default `labels()`.
17874        for (i, a) in UnquoteForm::MARKERS.iter().enumerate() {
17875            for (j, b) in UnquoteForm::MARKERS.iter().enumerate() {
17876                if i == j {
17877                    continue;
17878                }
17879                assert_ne!(
17880                    a, b,
17881                    "UnquoteForm::MARKERS[{i}] ({a:?}) collides with \
17882                     UnquoteForm::MARKERS[{j}] ({b:?}) — two distinct \
17883                     template markers cannot share diagnostic bytes",
17884                );
17885            }
17886        }
17887    }
17888
17889    #[test]
17890    fn unquote_form_unquote_lead_aliases_quote_form_unquote_lead_byte_for_byte() {
17891        // ALIAS CONTRACT: pin the sole per-role
17892        // `pub const UnquoteForm::UNQUOTE_LEAD` alias equals
17893        // `pub const QuoteForm::UNQUOTE_LEAD` byte-for-byte — so the
17894        // UnquoteForm ⊂ QuoteForm reader-lead-char vocabulary
17895        // containment routes through the typed
17896        // `pub const UnquoteForm::UNQUOTE_LEAD: char =
17897        // QuoteForm::UNQUOTE_LEAD` alias chain rather than through an
17898        // independent literal-discipline site. A regression that
17899        // renames the QuoteForm side (a hypothetical repartitioning
17900        // that swaps the substitution-family lead byte from `,` to a
17901        // different char) without updating the UnquoteForm alias
17902        // pointing at it fails-loudly here; a regression that re-
17903        // inlines the UnquoteForm constant to a fresh `','` literal
17904        // still passes this pin but loses the alias-chain typing
17905        // (which is what the routing pin below catches in
17906        // combination).
17907        //
17908        // Sibling posture to
17909        // `unquote_form_per_role_markers_alias_quote_form_per_role_prefixes_byte_for_byte`
17910        // (commit 3640f76 — reader-punctuation-prefix axis),
17911        // `unquote_form_per_role_iac_forge_tags_alias_quote_form_per_role_iac_forge_tags_byte_for_byte`
17912        // (commit 6acab84 — iac-forge canonical-form axis),
17913        // `unquote_form_per_role_labels_alias_quote_form_per_role_labels_byte_for_byte`
17914        // (commit da68af5 — diagnostic-label axis), and
17915        // `unquote_form_per_role_hash_discriminators_alias_quote_form_per_role_hash_discriminators_byte_for_byte`
17916        // (commit eca4730 — outer-`Sexp` cache-key axis) — this
17917        // FIFTH alias-contract pin closes the QUINTUPLE of per-role
17918        // `pub const` axes on the UnquoteForm subset carving.
17919        assert_eq!(
17920            UnquoteForm::UNQUOTE_LEAD,
17921            crate::ast::QuoteForm::UNQUOTE_LEAD,
17922            "UnquoteForm::UNQUOTE_LEAD drifted from QuoteForm::UNQUOTE_LEAD — the alias chain broke",
17923        );
17924    }
17925
17926    #[test]
17927    fn unquote_form_unquote_lead_pins_canonical_reader_lead_char() {
17928        // Byte-for-byte pin of the canonical
17929        // (UnquoteForm, reader-lead char) mapping at the per-role
17930        // `pub const` axis: `UnquoteForm::UNQUOTE_LEAD == ','` — the
17931        // exact char the reader's outer tokenizer dispatch selects
17932        // template-substitution-family entry on. This char is
17933        // load-bearing: the reader's outer tokenizer pre-check
17934        // (`QuoteForm::from_lead_char(ch)`) routes into the
17935        // substitution-family branch iff `ch == UNQUOTE_LEAD`, and
17936        // the `,`-vs-`,@` disambiguation lives at the reader's
17937        // peek-then-consume `@` second-char step (promoting the
17938        // default `Unquote` return into a `Splice` on `@`). A
17939        // regression that drifts this const from `,` silently
17940        // fractures the reader's outer dispatch AND the
17941        // `PROMOTIONS[0].1 == UNQUOTE_LEAD.next_char_promoter_source`
17942        // structural pairing that the promotion table binds to.
17943        // Sibling of
17944        // `unquote_form_per_role_hash_discriminators_pin_legacy_cache_key_bytes`
17945        // (which pins the outer-`Sexp` cache-key bytes at the
17946        // per-role `pub const` axis) — this test pins the reader-
17947        // lead char on the same axis one production vocabulary over.
17948        assert_eq!(
17949            UnquoteForm::UNQUOTE_LEAD,
17950            ',',
17951            "UnquoteForm::UNQUOTE_LEAD drifted from legacy reader-lead char ','",
17952        );
17953    }
17954
17955    #[test]
17956    fn unquote_form_unquote_lead_routes_through_to_quote_form_lead_char_via_composition() {
17957        // Post-lift composition pin: for every `uf: UnquoteForm`,
17958        // `UnquoteForm::UNQUOTE_LEAD` and
17959        // `uf.to_quote_form().lead_char()` agree byte-for-byte — the
17960        // (subset marker, reader-lead char) pairing rides through the
17961        // superset's canonical `QuoteForm::lead_char` closed-set
17962        // match rather than through a parallel two-arm inline table
17963        // on the subset. A regression that re-inlines the two arms
17964        // as a parallel match-table (e.g. a future edit that spells
17965        // `Self::Unquote => ',' / Self::Splice => ','` directly at
17966        // `UnquoteForm::lead_char` instead of routing through
17967        // `self.to_quote_form().lead_char()`) is caught here — both
17968        // subset variants project to the SAME parent-superset lead
17969        // byte at
17970        // [`crate::ast::QuoteForm::lead_char`]'s
17971        // `Self::Unquote | Self::UnquoteSplice => Self::UNQUOTE_LEAD`
17972        // MERGED arm, and this composition pin binds that merge on
17973        // the subset carving. Sibling-shape pin to the four prior-
17974        // lift composition pins on `UnquoteForm`:
17975        // `unquote_form_marker_routes_through_to_quote_form_prefix_via_composition`,
17976        // `unquote_form_iac_forge_tag_routes_through_to_quote_form_iac_forge_tag_via_composition`,
17977        // `unquote_form_label_routes_through_to_quote_form_label_via_composition`,
17978        // and
17979        // `unquote_form_hash_discriminator_routes_through_to_quote_form_hash_discriminator_via_composition`
17980        // — all five pin the subset's projection through the
17981        // superset's canonical site via the same typed composition
17982        // posture. The FIFTH composition-through-`to_quote_form`
17983        // axis on the substitution-subset closed set is now load-
17984        // bearing on the type system.
17985        for uf in UnquoteForm::ALL {
17986            let via_composition = uf.to_quote_form().lead_char();
17987            assert_eq!(
17988                UnquoteForm::UNQUOTE_LEAD, via_composition,
17989                "UnquoteForm::{uf:?} projects through .to_quote_form().lead_char() = `{via_composition}` — but UnquoteForm::UNQUOTE_LEAD = `{}` — the SHAPE-ASYMMETRIC-COLLAPSE identity (both subset arms → one lead byte) broke",
17990                UnquoteForm::UNQUOTE_LEAD,
17991            );
17992        }
17993    }
17994
17995    #[test]
17996    fn unquote_form_leads_has_expected_shape_asymmetric_cardinality() {
17997        // Cardinality pin: `LEADS.len() == 1` — the SHAPE-ASYMMETRIC-
17998        // COLLAPSE cardinality that IS the collapse witness on the
17999        // 2-of-4 subset carving. UnquoteForm has 2 arms
18000        // (`Self::ALL.len() == 2`), but both arms share the same
18001        // reader-lead byte `,`, so the distinct-lead-byte closed-set
18002        // ALL array collapses to `[char; 1]`. The cardinality gap
18003        // `Self::ALL.len() (== 2) - Self::LEADS.len() (== 1) == 1`
18004        // IS the shared-lead-char invariant carried at the type-
18005        // system level.
18006        //
18007        // Peer to `unquote_form_markers_has_expected_cardinality`
18008        // (which pins `MARKERS.len() == 2 == ALL.len()` — the
18009        // arm-per-arm axis has NO collapse), this test pins the
18010        // OPPOSITE `LEADS.len() == 1 < ALL.len()` invariant — the
18011        // distinct-lead-set axis DOES collapse. The two axes together
18012        // fully characterize the reader-vocabulary shape on the
18013        // UnquoteForm closed set.
18014        //
18015        // Peer to `quote_form_leads_has_expected_shape_asymmetric_cardinality`
18016        // (parent 4-of-4 carving pins `QuoteForm::LEADS.len() == 3`
18017        // for 4 arms — the same shared-lead-byte collapse identity
18018        // one algebra level up).
18019        assert_eq!(
18020            UnquoteForm::LEADS.len(),
18021            1,
18022            "UnquoteForm::LEADS cardinality drifted from 1 — the shared-lead-char collapse invariant on the 2-of-4 substitution-subset carving is broken",
18023        );
18024        assert!(
18025            UnquoteForm::LEADS.len() < UnquoteForm::ALL.len(),
18026            "UnquoteForm::LEADS.len() ({leads}) MUST be strictly less than UnquoteForm::ALL.len() ({all}) — the SHAPE-ASYMMETRIC-COLLAPSE identity requires a strict cardinality gap",
18027            leads = UnquoteForm::LEADS.len(),
18028            all = UnquoteForm::ALL.len(),
18029        );
18030    }
18031
18032    #[test]
18033    fn unquote_form_leads_covers_all_variants_via_to_quote_form_lead_char() {
18034        // COVERAGE pin: every UnquoteForm variant's reader-lead byte
18035        // (projected through the superset composition
18036        // `self.to_quote_form().lead_char()`) MUST appear in
18037        // `UnquoteForm::LEADS`. Combined with the cardinality pin
18038        // above (`LEADS.len() == 1`), this pins that `LEADS` is
18039        // EXACTLY the image of `ALL` under the composition — no
18040        // superfluous entries, no missing coverage. A regression
18041        // that adds a spurious char to `LEADS` (e.g. a future edit
18042        // that includes both `,` and `@` in the array under the
18043        // mistaken belief that `,@` is a separate lead byte) fails
18044        // the cardinality pin above; a regression that DROPS the
18045        // sole entry from `LEADS` fails this coverage pin at every
18046        // variant.
18047        for uf in UnquoteForm::ALL {
18048            let lead = uf.to_quote_form().lead_char();
18049            assert!(
18050                UnquoteForm::LEADS.contains(&lead),
18051                "UnquoteForm::{uf:?} projects through .to_quote_form().lead_char() = `{lead}` — but UnquoteForm::LEADS ({leads:?}) does NOT contain `{lead}`; the distinct-lead-byte ALL array must cover every variant's lead byte",
18052                leads = UnquoteForm::LEADS,
18053            );
18054        }
18055    }
18056
18057    #[test]
18058    fn unquote_form_leads_is_subset_of_quote_form_leads() {
18059        // CONTAINMENT pin: `UnquoteForm::LEADS` is a subset of
18060        // `QuoteForm::LEADS` — the substitution-subset carving's
18061        // reader-lead-byte set sits inside the parent QuoteForm
18062        // superset's reader-lead-byte set by the UnquoteForm ⊂
18063        // QuoteForm inclusion. The sole entry `UnquoteForm::LEADS[0]
18064        // == ','` must equal `QuoteForm::UNQUOTE_LEAD`, which IS the
18065        // third entry `QuoteForm::LEADS[2]` (Quote/Quasiquote/Unquote
18066        // in declaration order per `QuoteForm::LEADS`). A regression
18067        // that drifts `UnquoteForm::UNQUOTE_LEAD` out of alignment
18068        // with the parent (e.g. a future edit that inlines a fresh
18069        // `';'` literal into the UnquoteForm alias but leaves
18070        // QuoteForm's canonical byte at `','`) fails-loudly here.
18071        // Sibling of
18072        // `unquote_form_hash_discriminator_partitions_disjointly_from_non_substitution_carvings`
18073        // (which pins the cache-key axis's partition-membership on
18074        // the same subset carving) — this test pins the reader-lead-
18075        // byte axis's subset-membership one production vocabulary
18076        // over.
18077        use crate::ast::QuoteForm;
18078        for lead in UnquoteForm::LEADS {
18079            assert!(
18080                QuoteForm::LEADS.contains(&lead),
18081                "UnquoteForm::LEADS entry `{lead}` MUST appear in QuoteForm::LEADS ({parent:?}) — the UnquoteForm ⊂ QuoteForm subset-inclusion breaks otherwise",
18082                parent = QuoteForm::LEADS,
18083            );
18084        }
18085    }
18086
18087    #[test]
18088    fn unquote_form_leads_matches_to_quote_form_lead_char_image_exactly() {
18089        // EXACT-IMAGE pin: `UnquoteForm::LEADS` as an unordered set
18090        // equals the image of `UnquoteForm::ALL` under
18091        // `.to_quote_form().lead_char()`. Combined with the
18092        // cardinality pin (`LEADS.len() == 1`) and the coverage pin
18093        // (every variant's lead appears in LEADS), this ties LEADS
18094        // to the composition's image at the SET level — a regression
18095        // that reorders entries, adds duplicates, or drops entries
18096        // fails at exactly one of the three pins.
18097        use std::collections::HashSet;
18098        let leads_set: HashSet<char> = UnquoteForm::LEADS.iter().copied().collect();
18099        let image_set: HashSet<char> = UnquoteForm::ALL
18100            .iter()
18101            .map(|uf| uf.to_quote_form().lead_char())
18102            .collect();
18103        assert_eq!(
18104            leads_set, image_set,
18105            "UnquoteForm::LEADS ({leads_set:?}) as a set MUST equal the image of UnquoteForm::ALL under .to_quote_form().lead_char() ({image_set:?}) — the distinct-lead-byte closed-set ALL array drifted from its composition source",
18106        );
18107    }
18108
18109    #[test]
18110    fn unquote_form_per_role_iac_forge_tags_alias_quote_form_per_role_iac_forge_tags_byte_for_byte()
18111    {
18112        // ALIAS CONTRACT: pin both per-role
18113        // `pub const UnquoteForm::*_IAC_FORGE_TAG` aliases equal the
18114        // corresponding `pub const QuoteForm::*_IAC_FORGE_TAG`
18115        // byte-for-byte — so the UnquoteForm ⊂ QuoteForm iac-forge-
18116        // canonical-form vocabulary containment routes through the
18117        // typed
18118        // `pub const UnquoteForm::V_IAC_FORGE_TAG: &'static str = QuoteForm::V_IAC_FORGE_TAG`
18119        // alias chain rather than through two independent literal-
18120        // discipline sites. A regression that renames the QuoteForm
18121        // side (e.g. a Common-Lisp-standard rename of `"unquote-
18122        // splicing"` on the iac-forge canonical-form surface) without
18123        // updating the UnquoteForm alias pointing at it fails-loudly
18124        // here with the exact axis identified (UNQUOTE / SPLICE); a
18125        // regression that re-inlines the UnquoteForm constant to a
18126        // fresh literal still passes this pin but loses the alias-
18127        // chain typing (which is what
18128        // `unquote_form_iac_forge_tag_arms_route_through_per_role_iac_forge_tags_for_every_variant`
18129        // + `unquote_form_iac_forge_tags_align_with_all_by_index`
18130        // catch in combination).
18131        //
18132        // Sibling posture to
18133        // `unquote_form_per_role_markers_alias_quote_form_per_role_prefixes_byte_for_byte`
18134        // (commit 3640f76): both pin the invariant that the
18135        // UnquoteForm subset algebra's per-role bytes are structurally
18136        // derived from the parent QuoteForm superset's per-role bytes
18137        // via a `pub const = Parent::CONST` alias rather than through
18138        // parallel literal tables — the axis there is the reader-
18139        // punctuation vocabulary; the axis here is the iac-forge
18140        // canonical-form vocabulary. Both axes span the two production
18141        // byte-vocabularies the parent [`QuoteForm`] closed set
18142        // carries, and both bind through the same aliased typed source
18143        // of truth on this UnquoteForm subset.
18144        use crate::ast::QuoteForm;
18145        assert_eq!(
18146            UnquoteForm::UNQUOTE_IAC_FORGE_TAG,
18147            QuoteForm::UNQUOTE_IAC_FORGE_TAG,
18148        );
18149        assert_eq!(
18150            UnquoteForm::SPLICE_IAC_FORGE_TAG,
18151            QuoteForm::UNQUOTE_SPLICE_IAC_FORGE_TAG,
18152        );
18153    }
18154
18155    #[test]
18156    fn unquote_form_iac_forge_tag_arms_route_through_per_role_iac_forge_tags_for_every_variant() {
18157        // PATH-UNIFORMITY: `UnquoteForm::V.iac_forge_tag()` MUST equal
18158        // the per-role `pub const UnquoteForm::V_IAC_FORGE_TAG` for
18159        // every `v: UnquoteForm`. Pre-lift the two iac-forge canonical-
18160        // form tag bytes were reachable through
18161        // `UnquoteForm::iac_forge_tag` (the composition
18162        // `self.to_quote_form().iac_forge_tag()` — routing into
18163        // `QuoteForm::UNQUOTE_IAC_FORGE_TAG` /
18164        // `UNQUOTE_SPLICE_IAC_FORGE_TAG`) OR through direct
18165        // `QuoteForm::UNQUOTE_IAC_FORGE_TAG` reach-across; post-lift
18166        // each variant's canonical bytes are reachable through the
18167        // per-role `UnquoteForm::*_IAC_FORGE_TAG` alias too. Pin the
18168        // byte-equality between the runtime projection and the
18169        // compile-time alias so a regression that renames the alias
18170        // without updating the arm (or vice versa) fails-loudly at the
18171        // exact axis.
18172        //
18173        // Sibling-shape pin to
18174        // `unquote_form_marker_arms_route_through_per_role_markers_for_every_variant`
18175        // one vocabulary over on the SAME subset algebra — the reader-
18176        // punctuation axis is pinned there; the iac-forge canonical-
18177        // form axis is pinned here. Together the two pins close the
18178        // subset algebra's `pub const *_MARKER` + `pub const
18179        // *_IAC_FORGE_TAG` surfaces against the runtime projections
18180        // that feed the two production byte-vocabularies.
18181        assert_eq!(
18182            UnquoteForm::Unquote.iac_forge_tag(),
18183            UnquoteForm::UNQUOTE_IAC_FORGE_TAG,
18184        );
18185        assert_eq!(
18186            UnquoteForm::Splice.iac_forge_tag(),
18187            UnquoteForm::SPLICE_IAC_FORGE_TAG,
18188        );
18189    }
18190
18191    #[test]
18192    fn unquote_form_iac_forge_tags_has_expected_cardinality() {
18193        // Cardinality pin: `IAC_FORGE_TAGS.len() == 2` matches
18194        // `ALL.len()` so a refactor that loosens the type to
18195        // `&'static [&'static str]` fails HERE (the `[_; 2]` slot
18196        // cannot be sliced silently), and a variant added to `ALL`
18197        // without a matching `IAC_FORGE_TAGS` row fails the pair-arity
18198        // gate at the array literal itself before this test even runs.
18199        // The pin doubles as an operator-visible mark of the family's
18200        // cardinality across the substrate — two iac-forge tags,
18201        // matching the 2-of-4 carving of the parent
18202        // `QuoteForm::IAC_FORGE_TAGS` (the substitution-subset of the
18203        // four canonical homoiconic canonical-form tag strings).
18204        assert_eq!(UnquoteForm::IAC_FORGE_TAGS.len(), 2);
18205        assert_eq!(UnquoteForm::IAC_FORGE_TAGS.len(), UnquoteForm::ALL.len(),);
18206    }
18207
18208    #[test]
18209    fn unquote_form_iac_forge_tags_align_with_all_by_index() {
18210        // ALIGNMENT PIN: sweep `IAC_FORGE_TAGS[i] ==
18211        // ALL[i].iac_forge_tag()` so any `zip(ALL, IAC_FORGE_TAGS)`
18212        // consumer reads a coherent (variant, iac-forge tag) pair off
18213        // ONE forced-arity array pair. The declaration-order pin
18214        // makes a family-wide consumer that walks the
18215        // ALL / IAC_FORGE_TAGS pair in lockstep (an LSP completion
18216        // bar keyed on `UnquoteForm::IAC_FORGE_TAGS`, a Sekiban
18217        // audit-trail metric labeling
18218        // `tatara_lisp_iac_forge_tag_total{tag}` by the per-index
18219        // iac-forge tag) read one canonical (variant, bytes) pair per
18220        // slot rather than routing through per-consumer paired-
18221        // iteration. A regression that reorders IAC_FORGE_TAGS without
18222        // also reordering ALL (or vice versa) fails-loudly at the
18223        // exact index that drifted.
18224        assert_eq!(UnquoteForm::IAC_FORGE_TAGS.len(), UnquoteForm::ALL.len(),);
18225        for (i, form) in UnquoteForm::ALL.iter().enumerate() {
18226            assert_eq!(
18227                UnquoteForm::IAC_FORGE_TAGS[i],
18228                form.iac_forge_tag(),
18229                "UnquoteForm::IAC_FORGE_TAGS[{i}] `{tag}` drifted \
18230                 from UnquoteForm::ALL[{i}].iac_forge_tag() \
18231                 `{via_variant}` — the canonical ALL ordering and \
18232                 the IAC_FORGE_TAGS ordering must match element-wise",
18233                tag = UnquoteForm::IAC_FORGE_TAGS[i],
18234                via_variant = form.iac_forge_tag(),
18235            );
18236        }
18237    }
18238
18239    #[test]
18240    fn unquote_form_iac_forge_tags_pairwise_distinct() {
18241        // 2x2 pairwise sweep so a collision between the two iac-forge
18242        // canonical-form tags (which would silently degrade two
18243        // distinct template-substitution markers to the SAME cross-
18244        // crate canonical-form bytes and mis-hash the substrate's
18245        // BLAKE3 attestation intent-hash pillar) fails-loudly at the
18246        // exact pair. Sibling-shape pin to
18247        // `unquote_form_markers_pairwise_distinct` one vocabulary over
18248        // on the SAME subset algebra — the reader-punctuation axis is
18249        // pinned there; the iac-forge canonical-form axis is pinned
18250        // here.
18251        for (i, a) in UnquoteForm::IAC_FORGE_TAGS.iter().enumerate() {
18252            for (j, b) in UnquoteForm::IAC_FORGE_TAGS.iter().enumerate() {
18253                if i == j {
18254                    continue;
18255                }
18256                assert_ne!(
18257                    a, b,
18258                    "UnquoteForm::IAC_FORGE_TAGS[{i}] ({a:?}) \
18259                     collides with UnquoteForm::IAC_FORGE_TAGS[{j}] \
18260                     ({b:?}) — two distinct template markers cannot \
18261                     share cross-crate canonical-form bytes",
18262                );
18263            }
18264        }
18265    }
18266
18267    #[test]
18268    fn unquote_form_label_routes_through_to_quote_form_label_via_composition() {
18269        // Post-lift composition pin: for every `uf: UnquoteForm`,
18270        // `uf.label()` and `uf.to_quote_form().label()` agree on BOTH
18271        // axes — (a) byte equality (the rendered diagnostic literal
18272        // cannot drift between the two projections); (b) pointer
18273        // equality (the canonical `&'static str` literal lives at ONE
18274        // site — `SexpShape::UNQUOTE_LABEL` /
18275        // `SexpShape::UNQUOTE_SPLICE_LABEL` — reached through
18276        // `QuoteForm::label`'s composition through
18277        // `sexp_shape().label()`, and `UnquoteForm::label` routes
18278        // through that ONE address via the two-hop typed composition
18279        // `to_quote_form().label()`).
18280        //
18281        // The pointer-equality axis is load-bearing: a regression that
18282        // re-inlines the literals at `UnquoteForm::label` as a
18283        // parallel match-table fails the pointer pin even when the
18284        // rendered bytes still agree (the inline-literal-table copy
18285        // lives at a different `&'static str` address). Sibling-shape
18286        // pin to
18287        // `unquote_form_marker_routes_through_to_quote_form_prefix_via_composition`
18288        // and
18289        // `unquote_form_iac_forge_tag_routes_through_to_quote_form_iac_forge_tag_via_composition`
18290        // — this closes the THIRD composition-through-`to_quote_form`
18291        // per-role-bytes projection on the subset algebra pinned
18292        // against the superset's canonical site.
18293        for uf in UnquoteForm::ALL {
18294            let from_label = uf.label();
18295            let from_composition = uf.to_quote_form().label();
18296            assert_eq!(
18297                from_label, from_composition,
18298                "UnquoteForm::{uf:?}.label() bytes drifted from .to_quote_form().label() bytes — the subset's diagnostic vocabulary is no longer derived from the superset's canonical site",
18299            );
18300            assert!(
18301                std::ptr::eq(from_label.as_ptr(), from_composition.as_ptr()),
18302                "UnquoteForm::{uf:?}.label() and .to_quote_form().label() disagree on `&'static str` address — pointer drift means the lift composes through a parallel literal table rather than routing into the canonical QuoteForm::label / SexpShape::*_LABEL site",
18303            );
18304        }
18305    }
18306
18307    #[test]
18308    fn unquote_form_per_role_labels_alias_quote_form_per_role_labels_byte_for_byte() {
18309        // ALIAS CONTRACT: pin both per-role
18310        // `pub const UnquoteForm::*_LABEL` aliases equal the
18311        // corresponding `pub const QuoteForm::*_LABEL` byte-for-byte
18312        // — so the UnquoteForm ⊂ QuoteForm diagnostic-label
18313        // vocabulary containment routes through the typed
18314        // `pub const UnquoteForm::V_LABEL: &'static str = QuoteForm::V_LABEL`
18315        // alias chain (which itself aliases the ultimate canonical
18316        // site at `SexpShape::*_LABEL`) rather than through two
18317        // independent literal-discipline sites. A regression that
18318        // renames the QuoteForm side without updating the UnquoteForm
18319        // alias pointing at it fails-loudly here with the exact axis
18320        // identified (UNQUOTE / SPLICE); a regression that re-inlines
18321        // the UnquoteForm constant to a fresh literal still passes
18322        // this pin but loses the alias-chain typing (which is what
18323        // `unquote_form_label_arms_route_through_per_role_labels_for_every_variant`
18324        // + `unquote_form_labels_align_with_all_by_index` catch in
18325        // combination).
18326        //
18327        // Sibling posture to
18328        // `unquote_form_per_role_markers_alias_quote_form_per_role_prefixes_byte_for_byte`
18329        // (commit 3640f76) and
18330        // `unquote_form_per_role_iac_forge_tags_alias_quote_form_per_role_iac_forge_tags_byte_for_byte`
18331        // (commit 6acab84): all three pin the invariant that the
18332        // UnquoteForm subset algebra's per-role bytes are structurally
18333        // derived from the parent QuoteForm superset's per-role bytes
18334        // via a `pub const = Parent::CONST` alias rather than through
18335        // parallel literal tables — the axis there is the reader-
18336        // punctuation vocabulary and the iac-forge canonical-form
18337        // vocabulary; the axis here is the substrate diagnostic-label
18338        // vocabulary. All three axes span the THREE production byte-
18339        // vocabularies the parent [`QuoteForm`] closed set carries,
18340        // and all three bind through the same aliased typed source of
18341        // truth on this UnquoteForm subset.
18342        use crate::ast::QuoteForm;
18343        assert_eq!(UnquoteForm::UNQUOTE_LABEL, QuoteForm::UNQUOTE_LABEL);
18344        assert_eq!(UnquoteForm::SPLICE_LABEL, QuoteForm::UNQUOTE_SPLICE_LABEL);
18345    }
18346
18347    #[test]
18348    fn unquote_form_label_arms_route_through_per_role_labels_for_every_variant() {
18349        // PATH-UNIFORMITY: `UnquoteForm::V.label()` MUST equal the
18350        // per-role `pub const UnquoteForm::V_LABEL` for every
18351        // `v: UnquoteForm`. Pre-lift the two diagnostic-label bytes
18352        // were reachable through the two-step composition
18353        // `uf.to_quote_form().label()` OR through direct
18354        // `QuoteForm::UNQUOTE_LABEL` reach-across; post-lift each
18355        // variant's canonical bytes are reachable through the per-
18356        // role `UnquoteForm::*_LABEL` alias AND through the inherent
18357        // `label()` method too. Pin the byte-equality between the
18358        // runtime projection and the compile-time alias so a
18359        // regression that renames the alias without updating the arm
18360        // (or vice versa) fails-loudly at the exact axis.
18361        //
18362        // Sibling-shape pin to
18363        // `unquote_form_marker_arms_route_through_per_role_markers_for_every_variant`
18364        // and
18365        // `unquote_form_iac_forge_tag_arms_route_through_per_role_iac_forge_tags_for_every_variant`
18366        // one vocabulary over on the SAME subset algebra — together
18367        // the three pins close the subset algebra's
18368        // `pub const *_MARKER` + `pub const *_IAC_FORGE_TAG` +
18369        // `pub const *_LABEL` surfaces against the runtime
18370        // projections that feed the three production byte-
18371        // vocabularies.
18372        assert_eq!(UnquoteForm::Unquote.label(), UnquoteForm::UNQUOTE_LABEL);
18373        assert_eq!(UnquoteForm::Splice.label(), UnquoteForm::SPLICE_LABEL);
18374    }
18375
18376    #[test]
18377    fn unquote_form_labels_has_expected_cardinality() {
18378        // Cardinality pin: `LABELS.len() == 2` matches `ALL.len()` so
18379        // a refactor that loosens the type to `&'static [&'static str]`
18380        // fails HERE (the `[_; 2]` slot cannot be sliced silently),
18381        // and a variant added to `ALL` without a matching `LABELS`
18382        // row fails the pair-arity gate at the array literal itself
18383        // before this test even runs.
18384        assert_eq!(UnquoteForm::LABELS.len(), 2);
18385        assert_eq!(UnquoteForm::LABELS.len(), UnquoteForm::ALL.len());
18386    }
18387
18388    #[test]
18389    fn unquote_form_labels_align_with_all_by_index() {
18390        // ALIGNMENT PIN: sweep `LABELS[i] == ALL[i].label()` so any
18391        // `zip(ALL, LABELS)` consumer reads a coherent (variant,
18392        // label) pair off ONE forced-arity array pair. A regression
18393        // that reorders LABELS without also reordering ALL (or vice
18394        // versa) fails-loudly at the exact index that drifted.
18395        assert_eq!(UnquoteForm::LABELS.len(), UnquoteForm::ALL.len());
18396        for (i, form) in UnquoteForm::ALL.iter().enumerate() {
18397            assert_eq!(
18398                UnquoteForm::LABELS[i],
18399                form.label(),
18400                "UnquoteForm::LABELS[{i}] `{lbl}` drifted from \
18401                 UnquoteForm::ALL[{i}].label() `{via_variant}` — the \
18402                 canonical ALL ordering and the LABELS ordering must \
18403                 match element-wise",
18404                lbl = UnquoteForm::LABELS[i],
18405                via_variant = form.label(),
18406            );
18407        }
18408    }
18409
18410    #[test]
18411    fn unquote_form_labels_pairwise_distinct() {
18412        // 2x2 pairwise sweep so a collision between the two
18413        // diagnostic labels (which would silently degrade two distinct
18414        // template-substitution markers to the SAME operator-facing
18415        // rendered vocabulary) fails-loudly at the exact pair.
18416        // Sibling-shape pin to `unquote_form_markers_pairwise_distinct`
18417        // and `unquote_form_iac_forge_tags_pairwise_distinct` one
18418        // vocabulary over on the SAME subset algebra.
18419        for (i, a) in UnquoteForm::LABELS.iter().enumerate() {
18420            for (j, b) in UnquoteForm::LABELS.iter().enumerate() {
18421                if i == j {
18422                    continue;
18423                }
18424                assert_ne!(
18425                    a, b,
18426                    "UnquoteForm::LABELS[{i}] ({a:?}) collides with \
18427                     UnquoteForm::LABELS[{j}] ({b:?}) — two distinct \
18428                     template markers cannot share diagnostic bytes",
18429                );
18430            }
18431        }
18432    }
18433
18434    #[test]
18435    fn unquote_form_label_emits_canonical_sexp_shape_label_for_every_marker() {
18436        // Per-arm truth-table pin of the canonical (UnquoteForm
18437        // variant, diagnostic label) mapping: `UnquoteForm::Unquote →
18438        // "unquote"` and `UnquoteForm::Splice → "unquote-splice"`
18439        // byte-for-byte. The `-splice` short form on `Splice` is
18440        // load-bearing: the substrate diagnostic surface renders
18441        // `unquote-splice`, distinct from the iac-forge canonical
18442        // form's `unquote-splicing` — a regression that homogenizes
18443        // the two axes surfaces here on the diagnostic-surface side
18444        // (`unquote_form_iac_forge_tag_diverges_from_sexp_shape_label_at_splice_arm`
18445        // surfaces it on the iac-forge-surface side). Sibling of
18446        // `unquote_form_iac_forge_tag_emits_canonical_cl_tag_for_every_marker`
18447        // on the iac-forge axis rather than the diagnostic axis.
18448        assert_eq!(
18449            UnquoteForm::Unquote.label(),
18450            "unquote",
18451            "UnquoteForm::Unquote.label() drifted from canonical substrate 'unquote' diagnostic",
18452        );
18453        assert_eq!(
18454            UnquoteForm::Splice.label(),
18455            "unquote-splice",
18456            "UnquoteForm::Splice.label() drifted from canonical substrate 'unquote-splice' \
18457             diagnostic — the short form is load-bearing (distinct from iac-forge 'unquote-splicing')",
18458        );
18459    }
18460
18461    #[test]
18462    fn unquote_form_label_diverges_from_iac_forge_tag_at_splice_arm() {
18463        // BOUNDARY-DISTINCT CONTRACT (subset peer): the diagnostic
18464        // label for `UnquoteForm::Splice` is `"unquote-splice"` (short
18465        // substrate form), distinct from `UnquoteForm::Splice.iac_forge_tag()`
18466        // which renders the longer `"unquote-splicing"` (Common-Lisp
18467        // canonical form). Pins the divergence on the substitution-
18468        // subset carving. On the non-`Splice` arm the two projections
18469        // MUST agree (both spell `"unquote"` at the substitution-
18470        // subset's `Unquote` arm). Peer of
18471        // `unquote_form_iac_forge_tag_diverges_from_sexp_shape_label_at_splice_arm`
18472        // one composition path over — that pin routes through
18473        // `sexp_shape().label()`; this pin routes through the direct
18474        // subset `label()` inherent (which composes through
18475        // `to_quote_form().label()`).
18476        assert_eq!(
18477            UnquoteForm::Unquote.label(),
18478            UnquoteForm::Unquote.iac_forge_tag(),
18479            "UnquoteForm::Unquote.label() and .iac_forge_tag() must agree on 'unquote' — the two axes only diverge at the Splice arm",
18480        );
18481        assert_ne!(
18482            UnquoteForm::Splice.label(),
18483            UnquoteForm::Splice.iac_forge_tag(),
18484            "UnquoteForm::Splice.label() and .iac_forge_tag() must diverge — substrate renders 'unquote-splice', iac-forge canonical form is 'unquote-splicing'",
18485        );
18486    }
18487
18488    #[test]
18489    fn unquote_form_wrap_routes_through_to_quote_form_wrap_via_composition() {
18490        // Post-lift composition pin: for every `uf: UnquoteForm` and
18491        // every representative `inner: Sexp`, `uf.wrap(inner.clone())
18492        // == uf.to_quote_form().wrap(inner)` byte-for-byte. The
18493        // (UnquoteForm marker, `Sexp::*` tuple-variant constructor)
18494        // pairing on the substitution-subset closed set is derived
18495        // structurally from the superset's canonical
18496        // [`crate::ast::QuoteForm::wrap`] closed-set match rather than
18497        // from a parallel two-arm inline table on this subset. A
18498        // regression that re-inlines the two arms as a parallel
18499        // match-table (a future edit that spells `Self::Unquote =>
18500        // Sexp::Unquote(Box::new(inner))` / `Self::Splice =>
18501        // Sexp::UnquoteSplice(Box::new(inner))` directly at
18502        // `UnquoteForm::wrap` instead of routing through
18503        // `self.to_quote_form().wrap(inner)`) still passes the round-
18504        // trip and canonical-tuple-variant sweeps below but fails
18505        // THIS composition pin — the subset's construct-family
18506        // vocabulary is no longer derived from the superset's
18507        // canonical site. Sibling-shape pin to commit 250c001's
18508        // `unquote_form_marker_routes_through_to_quote_form_prefix_via_composition`:
18509        // both pin the subset's projection through the superset's
18510        // canonical site via structural equality, the invariant the
18511        // subset-to-superset composition was lifted to make load-
18512        // bearing on the type system rather than on per-callsite
18513        // discipline.
18514        use crate::ast::Sexp;
18515        let inners = [
18516            Sexp::Nil,
18517            Sexp::symbol("x"),
18518            Sexp::keyword("k"),
18519            Sexp::string("s"),
18520            Sexp::int(42),
18521            Sexp::float(1.5),
18522            Sexp::boolean(true),
18523            Sexp::List(vec![Sexp::symbol("f"), Sexp::int(1), Sexp::int(2)]),
18524        ];
18525        for uf in UnquoteForm::ALL {
18526            for inner in &inners {
18527                let via_wrap = uf.wrap(inner.clone());
18528                let via_composition = uf.to_quote_form().wrap(inner.clone());
18529                assert_eq!(
18530                    via_wrap, via_composition,
18531                    "UnquoteForm::{uf:?}.wrap(inner) drifted from .to_quote_form().wrap(inner) — the subset's construct vocabulary is no longer derived from the superset's canonical site",
18532                );
18533            }
18534        }
18535    }
18536
18537    #[test]
18538    fn unquote_form_wrap_emits_canonical_tuple_variant_for_every_marker() {
18539        // Byte-for-byte tuple-variant emission pin: for every
18540        // `uf: UnquoteForm` and every representative `inner: Sexp`,
18541        // `uf.wrap(inner)` produces the canonical `Sexp::Unquote(
18542        // Box::new(inner))` / `Sexp::UnquoteSplice(Box::new(inner))`
18543        // shape byte-for-byte. Pins the (subset marker, `Sexp::*`
18544        // tuple-variant constructor) pairing at the observable
18545        // wrapper-shape boundary — a regression that maps
18546        // `UnquoteForm::Unquote → Sexp::UnquoteSplice` (a marker/
18547        // constructor swap that still routes through the superset's
18548        // `wrap`) surfaces here because the composition through
18549        // `to_quote_form()` picks up the swap at the subset-to-
18550        // superset projection. Sibling of the outer `Sexp` construct
18551        // family's
18552        // `sexp_quote_family_constructors_emit_canonical_tuple_variant_for_every_marker`
18553        // (commit 38f076b) — same posture on the subset closed set.
18554        use crate::ast::Sexp;
18555        let inners = [
18556            Sexp::Nil,
18557            Sexp::symbol("x"),
18558            Sexp::keyword("k"),
18559            Sexp::string("s"),
18560            Sexp::int(-7),
18561            Sexp::float(2.0),
18562            Sexp::boolean(false),
18563            Sexp::List(vec![Sexp::symbol("f"), Sexp::int(1)]),
18564        ];
18565        for inner in &inners {
18566            assert_eq!(
18567                UnquoteForm::Unquote.wrap(inner.clone()),
18568                Sexp::Unquote(Box::new(inner.clone())),
18569                "UnquoteForm::Unquote.wrap({inner:?}) drifted from Sexp::Unquote(Box::new({inner:?})) canonical tuple-variant shape",
18570            );
18571            assert_eq!(
18572                UnquoteForm::Splice.wrap(inner.clone()),
18573                Sexp::UnquoteSplice(Box::new(inner.clone())),
18574                "UnquoteForm::Splice.wrap({inner:?}) drifted from Sexp::UnquoteSplice(Box::new({inner:?})) canonical tuple-variant shape",
18575            );
18576        }
18577    }
18578
18579    #[test]
18580    fn unquote_form_wrap_round_trips_through_sexp_as_unquote() {
18581        // Section-for-retraction pin: `uf.wrap(inner).as_unquote() ==
18582        // Some((uf, &inner))` for every `uf: UnquoteForm` and every
18583        // representative `inner: Sexp`. Closes the (construct,
18584        // project) algebra dual on the closed-set `UnquoteForm`
18585        // algebra — the substitution-subset peer of the closed-set
18586        // superset algebra's already-closed
18587        // ([`crate::ast::QuoteForm::wrap`],
18588        // [`crate::ast::Sexp::as_quote_form`]) dual. The typed
18589        // constructor + typed projection pair form an `Iso(inner,
18590        // Sexp::X_variant(inner))` on the subset closed set. A future
18591        // arm added to `UnquoteForm` extends [`UnquoteForm::ALL`] +
18592        // [`UnquoteForm::to_quote_form`] + this sweep in lockstep —
18593        // rustc-enforced through the closed-set exhaustiveness across
18594        // the `UnquoteForm` match. Sibling of the outer `Sexp`
18595        // construct family's
18596        // `sexp_quote_family_constructors_round_trip_through_as_quote_form`
18597        // (commit 38f076b) — same posture on the subset closed set,
18598        // routed through `Sexp::as_unquote` rather than
18599        // `Sexp::as_quote_form`.
18600        use crate::ast::Sexp;
18601        let inners = [
18602            Sexp::Nil,
18603            Sexp::symbol("x"),
18604            Sexp::keyword("k"),
18605            Sexp::string("s"),
18606            Sexp::int(1),
18607            Sexp::List(vec![Sexp::symbol("f")]),
18608        ];
18609        for uf in UnquoteForm::ALL {
18610            for inner in &inners {
18611                let wrapped = uf.wrap(inner.clone());
18612                assert_eq!(
18613                    wrapped.as_unquote(),
18614                    Some((uf, inner)),
18615                    "UnquoteForm::{uf:?}.wrap({inner:?}).as_unquote() failed to round-trip — the (construct, project) pair on the subset algebra is not a section-for-retraction of Sexp::as_unquote",
18616                );
18617            }
18618        }
18619    }
18620
18621    #[test]
18622    fn unquote_form_wrap_composes_with_shape_via_to_quote_form_sexp_shape() {
18623        // Outer-shape composition pin: for every `uf: UnquoteForm`
18624        // and every representative `inner: Sexp`, `uf.wrap(inner)
18625        // .shape() == uf.to_quote_form().sexp_shape()` — the
18626        // (subset marker, outer [`crate::error::SexpShape`]) pairing
18627        // binds through the SAME closed-set composition
18628        // (`to_quote_form` → `QuoteForm::sexp_shape`) that
18629        // [`crate::ast::QuoteForm::wrap`]'s outer-shape composition
18630        // rides on. A regression that drifts ONE construct arm's
18631        // outer-shape from the superset's `sexp_shape` while
18632        // preserving the tuple-variant emission surfaces here
18633        // alongside the round-trip pin. Sibling of the outer `Sexp`
18634        // construct family's
18635        // `sexp_quote_family_constructors_compose_with_shape_via_quote_form_sexp_shape`
18636        // (commit 38f076b) — same posture on the subset closed set.
18637        use crate::ast::Sexp;
18638        let inners = [
18639            Sexp::Nil,
18640            Sexp::symbol("x"),
18641            Sexp::int(42),
18642            Sexp::List(vec![Sexp::symbol("f")]),
18643        ];
18644        for uf in UnquoteForm::ALL {
18645            for inner in &inners {
18646                assert_eq!(
18647                    uf.wrap(inner.clone()).shape(),
18648                    uf.to_quote_form().sexp_shape(),
18649                    "UnquoteForm::{uf:?}.wrap({inner:?}).shape() drifted from .to_quote_form().sexp_shape() — the (subset marker, outer SexpShape) pairing is no longer derived from the superset's canonical composition",
18650                );
18651            }
18652        }
18653    }
18654
18655    #[test]
18656    fn unquote_form_sexp_shape_routes_through_to_quote_form_sexp_shape_via_composition() {
18657        // Post-lift composition pin: for every `uf: UnquoteForm`,
18658        // `uf.sexp_shape()` and `uf.to_quote_form().sexp_shape()`
18659        // agree byte-for-byte — the (subset marker, outer SexpShape)
18660        // pairing rides through the superset's canonical
18661        // `QuoteForm::sexp_shape` closed-set match rather than through
18662        // a parallel two-arm inline table on the subset. A regression
18663        // that re-inlines the two arms as a parallel match-table (e.g.
18664        // a future edit that spells `Self::Unquote => SexpShape::Unquote`
18665        // / `Self::Splice => SexpShape::UnquoteSplice` directly at
18666        // `UnquoteForm::sexp_shape` instead of routing through
18667        // `self.to_quote_form().sexp_shape()`) still passes the round-
18668        // trip sweep below but fails THIS composition pin — the
18669        // subset's outer-shape vocabulary is no longer derived from
18670        // the superset's canonical site. Sibling-shape pin to commit
18671        // 250c001's `unquote_form_marker_routes_through_to_quote_form_prefix_via_composition`
18672        // and commit 92daace's `unquote_form_wrap_routes_through_to_quote_form_wrap_via_composition`:
18673        // all three pin the subset's projection through the superset's
18674        // canonical site via the same typed composition posture, the
18675        // invariant the subset-to-superset composition was lifted to
18676        // make load-bearing on the type system rather than on
18677        // per-callsite discipline.
18678        for uf in UnquoteForm::ALL {
18679            let from_shape = uf.sexp_shape();
18680            let from_composition = uf.to_quote_form().sexp_shape();
18681            assert_eq!(
18682                from_shape, from_composition,
18683                "UnquoteForm::{uf:?}.sexp_shape() drifted from .to_quote_form().sexp_shape() — the subset's outer-shape vocabulary is no longer derived from the superset's canonical site",
18684            );
18685        }
18686    }
18687
18688    #[test]
18689    fn unquote_form_sexp_shape_emits_canonical_shape_for_every_marker() {
18690        // Per-arm truth-table pin of the canonical (UnquoteForm variant,
18691        // SexpShape variant) mapping: `UnquoteForm::Unquote →
18692        // SexpShape::Unquote` and `UnquoteForm::Splice →
18693        // SexpShape::UnquoteSplice` byte-for-byte. A regression that
18694        // swaps the two arms (a marker/shape swap that still routes
18695        // through the superset's `sexp_shape`) surfaces here because
18696        // the composition through `to_quote_form()` picks up the swap
18697        // at the subset-to-superset projection. Sibling of the outer
18698        // `Sexp` construct family's
18699        // `sexp_quote_family_constructors_emit_canonical_tuple_variant_for_every_marker`
18700        // (commit 38f076b) on the outer-shape axis rather than the
18701        // tuple-variant axis.
18702        assert_eq!(
18703            UnquoteForm::Unquote.sexp_shape(),
18704            SexpShape::Unquote,
18705            "UnquoteForm::Unquote.sexp_shape() drifted from SexpShape::Unquote",
18706        );
18707        assert_eq!(
18708            UnquoteForm::Splice.sexp_shape(),
18709            SexpShape::UnquoteSplice,
18710            "UnquoteForm::Splice.sexp_shape() drifted from SexpShape::UnquoteSplice",
18711        );
18712    }
18713
18714    #[test]
18715    fn unquote_form_iac_forge_tag_routes_through_to_quote_form_iac_forge_tag_via_composition() {
18716        // Post-lift composition pin: for every `uf: UnquoteForm`,
18717        // `uf.iac_forge_tag()` and `uf.to_quote_form().iac_forge_tag()`
18718        // agree byte-for-byte — the (subset marker, canonical
18719        // iac-forge tag) pairing rides through the superset's
18720        // canonical `QuoteForm::iac_forge_tag` closed-set match rather
18721        // than through a parallel two-arm inline table on the subset.
18722        // A regression that re-inlines the two arms as a parallel
18723        // match-table (e.g. a future edit that spells `Self::Unquote
18724        // => "unquote"` / `Self::Splice => "unquote-splicing"` directly
18725        // at `UnquoteForm::iac_forge_tag` instead of routing through
18726        // `self.to_quote_form().iac_forge_tag()`) still passes the
18727        // per-arm truth-table pin below but fails THIS composition
18728        // pin — the subset's canonical interop vocabulary is no
18729        // longer derived from the superset's canonical site. Sibling-
18730        // shape pin to commit 250c001's
18731        // `unquote_form_marker_routes_through_to_quote_form_prefix_via_composition`,
18732        // commit 92daace's
18733        // `unquote_form_wrap_routes_through_to_quote_form_wrap_via_composition`,
18734        // and this run's sibling
18735        // `unquote_form_sexp_shape_routes_through_to_quote_form_sexp_shape_via_composition`
18736        // — all four pin the subset's projection through the
18737        // superset's canonical site via the same typed composition
18738        // posture, the invariant the subset-to-superset composition
18739        // was lifted to make load-bearing on the type system rather
18740        // than on per-callsite discipline.
18741        for uf in UnquoteForm::ALL {
18742            let from_tag = uf.iac_forge_tag();
18743            let from_composition = uf.to_quote_form().iac_forge_tag();
18744            assert_eq!(
18745                from_tag, from_composition,
18746                "UnquoteForm::{uf:?}.iac_forge_tag() drifted from .to_quote_form().iac_forge_tag() — the subset's canonical interop vocabulary is no longer derived from the superset's canonical site",
18747            );
18748        }
18749    }
18750
18751    #[test]
18752    fn unquote_form_iac_forge_tag_emits_canonical_cl_tag_for_every_marker() {
18753        // Per-arm truth-table pin of the canonical (UnquoteForm variant,
18754        // iac-forge tag) mapping: `UnquoteForm::Unquote → "unquote"`
18755        // and `UnquoteForm::Splice → "unquote-splicing"` byte-for-byte.
18756        // The `-splicing` suffix on `Splice` is load-bearing: the
18757        // Common-Lisp-canonical form REQUIRES `(unquote-splicing x)`
18758        // rather than `(unquote-splice x)`, and every downstream
18759        // BLAKE3 attestation key + render-cache shape hashes on the
18760        // exact tag spelling. A regression that swaps the two arms
18761        // (a marker/tag swap that still routes through the superset's
18762        // `iac_forge_tag`) surfaces here because the composition
18763        // through `to_quote_form()` picks up the swap at the subset-
18764        // to-superset projection. A regression that drops the
18765        // `-splicing` suffix and consolidates the tag with the
18766        // substrate's diagnostic label (`unquote-splice`) also
18767        // surfaces here. Sibling of the outer `Sexp` construct
18768        // family's canonical tuple-variant pins on the interop-tag
18769        // axis rather than the tuple-variant axis.
18770        assert_eq!(
18771            UnquoteForm::Unquote.iac_forge_tag(),
18772            "unquote",
18773            "UnquoteForm::Unquote.iac_forge_tag() drifted from canonical CL 'unquote' tag",
18774        );
18775        assert_eq!(
18776            UnquoteForm::Splice.iac_forge_tag(),
18777            "unquote-splicing",
18778            "UnquoteForm::Splice.iac_forge_tag() drifted from canonical CL 'unquote-splicing' tag \
18779             — the '-splicing' suffix is load-bearing for iac-forge canonical-form round-trip",
18780        );
18781    }
18782
18783    #[test]
18784    fn unquote_form_iac_forge_tag_diverges_from_sexp_shape_label_at_splice_arm() {
18785        // BOUNDARY-DISTINCT CONTRACT (subset peer): the iac-forge
18786        // canonical tag for `UnquoteForm::Splice` is
18787        // `"unquote-splicing"` (CL canonical form), distinct from
18788        // `SexpShape::label` for the corresponding subset shape
18789        // `SexpShape::UnquoteSplice`, which renders the shorter
18790        // `"unquote-splice"` (substrate diagnostic surface). Pins the
18791        // divergence on the substitution-subset carving in lockstep
18792        // with the superset-side pin
18793        // `quote_form_iac_forge_tag_diverges_from_sexp_shape_label_for_unquote_splice`
18794        // in `ast.rs`. On the non-`Splice` arm the two projections
18795        // MUST agree (both spell `"unquote"` at the substitution-
18796        // subset's `Unquote` arm) — pin that path-uniformity too so
18797        // a regression that homogenizes both arms surfaces here.
18798        // A future "consolidation" PR that renames one side would
18799        // silently break either the iac-forge canonical-form round-
18800        // trip OR the operator-facing diagnostic surface at the
18801        // template-substitution rejection sites.
18802        assert_eq!(
18803            UnquoteForm::Unquote.iac_forge_tag(),
18804            SexpShape::Unquote.label(),
18805            "the Unquote arm's iac-forge tag AND diagnostic label MUST agree — \
18806             both spell 'unquote' by CL + substrate convention alike",
18807        );
18808        assert_eq!(
18809            UnquoteForm::Splice.iac_forge_tag(),
18810            "unquote-splicing",
18811            "UnquoteForm::Splice.iac_forge_tag() drifted from canonical CL 'unquote-splicing' tag",
18812        );
18813        assert_eq!(
18814            SexpShape::UnquoteSplice.label(),
18815            "unquote-splice",
18816            "SexpShape::UnquoteSplice.label() drifted from substrate diagnostic 'unquote-splice' label",
18817        );
18818        assert_ne!(
18819            UnquoteForm::Splice.iac_forge_tag(),
18820            SexpShape::UnquoteSplice.label(),
18821            "the two projections MUST disagree at Splice — the CL canonical form \
18822             requires '-splicing' while the substrate's diagnostic label uses the \
18823             shorter '-splice'; consolidating them would break either side",
18824        );
18825    }
18826
18827    #[test]
18828    fn unquote_form_iac_forge_tag_agrees_with_outer_sexp_iac_forge_arm_when_iac_forge_feature_gate_is_on(
18829    ) {
18830        // Cross-boundary intent pin: for every `uf: UnquoteForm` and a
18831        // representative `inner: Sexp`, the tag the outer `From<&Sexp>
18832        // for iac_forge::SExpr` interop arm emits when it decomposes
18833        // the wrapper via `expect_quote_form()` and projects through
18834        // `QuoteForm::iac_forge_tag()` agrees byte-for-byte with the
18835        // direct `UnquoteForm::iac_forge_tag()` projection on the
18836        // marker the outer projection would have recovered. Pinned
18837        // through the composition `qf.iac_forge_tag() ==
18838        // qf.as_unquote_form().map(UnquoteForm::iac_forge_tag)
18839        // .unwrap_or_else(|| qf.iac_forge_tag())` — the (QuoteForm,
18840        // UnquoteForm) tag agreement on the substitution-subset
18841        // carving is exactly the invariant a future refactor that
18842        // routes the outer interop arm through THIS subset method
18843        // (on Unquote/UnquoteSplice) instead of the superset method
18844        // (on all four) can rely on to stay byte-identical.
18845        //
18846        // Feature-gated because the iac-forge feature is off by
18847        // default in the isolated tatara-lisp build (the actual
18848        // `From<&Sexp> for iac_forge::SExpr` impl and its tests live
18849        // behind the same gate); this test pins the invariant that
18850        // WOULD bind the cross-boundary agreement WITHOUT depending
18851        // on the feature-gated impl compiling — the composition law
18852        // holds on the typed algebra regardless.
18853        for uf in UnquoteForm::ALL {
18854            let via_subset = uf.iac_forge_tag();
18855            let via_superset = uf.to_quote_form().iac_forge_tag();
18856            assert_eq!(
18857                via_subset, via_superset,
18858                "UnquoteForm::{uf:?}.iac_forge_tag() drifted from .to_quote_form().iac_forge_tag() — \
18859                 the outer interop arm's byte-identity on the substitution-subset carving is broken",
18860            );
18861        }
18862    }
18863
18864    #[test]
18865    fn unquote_form_iac_forge_tag_specializes_to_matching_arm_of_quote_form_iac_forge_tag() {
18866        // PER-VARIANT RESTRICTION LAW pin: the subset's tag projection
18867        // agrees with the superset's tag projection on the
18868        // substitution-subset carving arm-for-arm. `UnquoteForm::
18869        // Unquote.iac_forge_tag() == crate::ast::QuoteForm::Unquote
18870        // .iac_forge_tag()`, and `UnquoteForm::Splice.iac_forge_tag()
18871        // == crate::ast::QuoteForm::UnquoteSplice.iac_forge_tag()`.
18872        // The composition through `to_quote_form()` binds this
18873        // agreement to the SAME closed-set match arm on the
18874        // superset's `iac_forge_tag` per subset variant — a regression
18875        // that drifts `to_quote_form`'s arm mapping (e.g. a future
18876        // edit that pairs `UnquoteForm::Splice` with
18877        // `QuoteForm::Unquote` on the subset → superset projection)
18878        // fails BOTH the arm-check here AND the composition pin above,
18879        // with distinct arm-anchored failure signatures. Sibling of
18880        // commit 3e92457's `sexp_shape` per-variant restriction pins
18881        // and commit 92daace's `wrap` per-variant restriction pins
18882        // on the interop-tag axis.
18883        use crate::ast::QuoteForm;
18884        assert_eq!(
18885            UnquoteForm::Unquote.iac_forge_tag(),
18886            QuoteForm::Unquote.iac_forge_tag(),
18887            "UnquoteForm::Unquote.iac_forge_tag() drifted from QuoteForm::Unquote.iac_forge_tag()",
18888        );
18889        assert_eq!(
18890            UnquoteForm::Splice.iac_forge_tag(),
18891            QuoteForm::UnquoteSplice.iac_forge_tag(),
18892            "UnquoteForm::Splice.iac_forge_tag() drifted from QuoteForm::UnquoteSplice.iac_forge_tag()",
18893        );
18894    }
18895
18896    #[test]
18897    fn unquote_form_hash_discriminator_routes_through_to_quote_form_hash_discriminator_via_composition(
18898    ) {
18899        // Post-lift composition pin: for every `uf: UnquoteForm`,
18900        // `uf.hash_discriminator()` and
18901        // `uf.to_quote_form().hash_discriminator()` agree byte-for-byte
18902        // — the (subset marker, outer-Sexp cache-key byte) pairing
18903        // rides through the superset's canonical
18904        // `QuoteForm::hash_discriminator` closed-set match rather than
18905        // through a parallel two-arm inline table on the subset. A
18906        // regression that re-inlines the two arms as a parallel
18907        // match-table (e.g. a future edit that spells `Self::Unquote
18908        // => 5` / `Self::Splice => 6` directly at
18909        // `UnquoteForm::hash_discriminator` instead of routing through
18910        // `self.to_quote_form().hash_discriminator()`) still passes
18911        // the per-arm truth-table pin below but fails THIS composition
18912        // pin — the subset's cache-key vocabulary is no longer derived
18913        // from the superset's canonical site. Sibling-shape pin to the
18914        // four prior-lift composition pins on `UnquoteForm`:
18915        // `unquote_form_marker_routes_through_to_quote_form_prefix_via_composition`,
18916        // `unquote_form_wrap_routes_through_to_quote_form_wrap_via_composition`,
18917        // `unquote_form_sexp_shape_routes_through_to_quote_form_sexp_shape_via_composition`,
18918        // and
18919        // `unquote_form_iac_forge_tag_routes_through_to_quote_form_iac_forge_tag_via_composition`
18920        // — all five pin the subset's projection through the
18921        // superset's canonical site via the same typed composition
18922        // posture. The FIFTH composition-through-`to_quote_form` axis
18923        // on the substitution-subset closed set is now load-bearing
18924        // on the type system rather than on per-callsite discipline.
18925        for uf in UnquoteForm::ALL {
18926            let from_disc = uf.hash_discriminator();
18927            let from_composition = uf.to_quote_form().hash_discriminator();
18928            assert_eq!(
18929                from_disc, from_composition,
18930                "UnquoteForm::{uf:?}.hash_discriminator() drifted from .to_quote_form().hash_discriminator() — the subset's cache-key vocabulary is no longer derived from the superset's canonical site",
18931            );
18932        }
18933    }
18934
18935    #[test]
18936    fn unquote_form_hash_discriminator_pins_legacy_cache_key_bytes() {
18937        // Per-arm truth-table pin of the canonical (UnquoteForm variant,
18938        // outer-Sexp cache-key byte) mapping: `UnquoteForm::Unquote → 5`
18939        // and `UnquoteForm::Splice → 6` byte-for-byte. These bytes are
18940        // load-bearing: the substrate's `Hash for Sexp` body composes
18941        // `QuoteForm::hash_discriminator` at the Unquote/UnquoteSplice
18942        // arms and the macro-expansion cache keys on those bytes;
18943        // changing either arm silently invalidates every cached
18944        // expansion across the substrate AND risks collision with the
18945        // reserved bytes the non-substitution carvings' arms use
18946        // (`{0, 2}` for `StructuralKind`, `1` for the outer `Atom`
18947        // marker, `{3, 4}` for the non-substitution quote-family arms
18948        // `Quote`/`Quasiquote`). Sibling of
18949        // `quote_form_hash_discriminator_pins_legacy_cache_key_bytes`
18950        // (4-arm superset pin),
18951        // `structural_kind_hash_discriminator_pins_legacy_cache_key_bytes`
18952        // (2-arm structural-residual pin),
18953        // `atom_kind_hash_discriminator_pins_legacy_atom_cache_key_bytes`
18954        // (6-arm atomic-payload pin), and
18955        // `sexp_shape_hash_discriminator_pins_legacy_outer_cache_key_bytes`
18956        // (12-arm shape-level pin) — this 2-arm subset pin joins the
18957        // family the closed-set typed algebra composes on for outer-
18958        // Sexp cache-key partition coherence.
18959        assert_eq!(
18960            UnquoteForm::Unquote.hash_discriminator(),
18961            5,
18962            "UnquoteForm::Unquote.hash_discriminator() drifted from legacy cache-key byte 5",
18963        );
18964        assert_eq!(
18965            UnquoteForm::Splice.hash_discriminator(),
18966            6,
18967            "UnquoteForm::Splice.hash_discriminator() drifted from legacy cache-key byte 6",
18968        );
18969    }
18970
18971    #[test]
18972    fn unquote_form_hash_discriminator_specializes_to_matching_arm_of_quote_form_hash_discriminator(
18973    ) {
18974        // PER-VARIANT RESTRICTION LAW pin: the subset's discriminator
18975        // projection agrees with the superset's discriminator
18976        // projection on the substitution-subset carving arm-for-arm.
18977        // `UnquoteForm::Unquote.hash_discriminator() ==
18978        // QuoteForm::Unquote.hash_discriminator()`, and
18979        // `UnquoteForm::Splice.hash_discriminator() ==
18980        // QuoteForm::UnquoteSplice.hash_discriminator()`. The
18981        // composition through `to_quote_form()` binds this agreement
18982        // to the SAME closed-set match arm on the superset's
18983        // `hash_discriminator` per subset variant — a regression that
18984        // drifts `to_quote_form`'s arm mapping (e.g. a future edit
18985        // that pairs `UnquoteForm::Splice` with `QuoteForm::Unquote`
18986        // on the subset → superset projection) fails BOTH the
18987        // arm-check here AND the composition pin above, with distinct
18988        // arm-anchored failure signatures. Sibling of the four prior
18989        // per-variant restriction pins on `UnquoteForm`: `marker`
18990        // (through `QuoteForm::prefix`), `wrap` (through
18991        // `QuoteForm::wrap`), `sexp_shape` (through
18992        // `QuoteForm::sexp_shape`), and `iac_forge_tag` (through
18993        // `QuoteForm::iac_forge_tag`) — the cache-key axis closes the
18994        // per-variant restriction family on the substitution subset.
18995        use crate::ast::QuoteForm;
18996        assert_eq!(
18997            UnquoteForm::Unquote.hash_discriminator(),
18998            QuoteForm::Unquote.hash_discriminator(),
18999            "UnquoteForm::Unquote.hash_discriminator() drifted from QuoteForm::Unquote.hash_discriminator()",
19000        );
19001        assert_eq!(
19002            UnquoteForm::Splice.hash_discriminator(),
19003            QuoteForm::UnquoteSplice.hash_discriminator(),
19004            "UnquoteForm::Splice.hash_discriminator() drifted from QuoteForm::UnquoteSplice.hash_discriminator()",
19005        );
19006    }
19007
19008    #[test]
19009    fn unquote_form_hash_discriminator_partitions_disjointly_from_non_substitution_carvings() {
19010        // DISJOINTNESS CONTRACT pin: the substitution-subset
19011        // discriminator image `{5, 6}` MUST be disjoint from every
19012        // other outer-Sexp carving's discriminator image — otherwise
19013        // the substrate's `Hash for Sexp` cache-key partition
19014        // silently collides and macro-expansion cache hits leak
19015        // across carvings. The reserved bytes are:
19016        //   * `StructuralKind::hash_discriminator` image: `{0, 2}`
19017        //     (Nil=0, List=2)
19018        //   * outer `Atom` carve marker: `{1}` (single-arm literal
19019        //     inside `Hash for Sexp`)
19020        //   * non-substitution `QuoteForm` arms
19021        //     (Quote/Quasiquote): `{3, 4}` (subset image of
19022        //     `QuoteForm::hash_discriminator` restricted to the
19023        //     non-`UnquoteForm` carving)
19024        // Sibling of the joint outer-Sexp discriminator-partition
19025        // pin at the shape-level algebra
19026        // (`sexp_shape_hash_discriminator_partitions_by_three_way_carving_disjointly`)
19027        // — this test pins the FOURTH carving's partition membership
19028        // (the 2-of-4 substitution-subset carving of `QuoteForm`)
19029        // rather than the three top-level carvings of `SexpShape`.
19030        use crate::ast::QuoteForm;
19031        use std::collections::HashSet;
19032
19033        let subset_image: HashSet<u8> = UnquoteForm::ALL
19034            .iter()
19035            .map(|uf| uf.hash_discriminator())
19036            .collect();
19037        assert_eq!(
19038            subset_image,
19039            HashSet::from([5, 6]),
19040            "UnquoteForm::ALL swept through hash_discriminator MUST cover exactly {{5, 6}} — the substitution-subset cache-key partition",
19041        );
19042
19043        let structural_image: HashSet<u8> = StructuralKind::ALL
19044            .iter()
19045            .map(|sk| sk.hash_discriminator())
19046            .collect();
19047        assert!(
19048            subset_image.is_disjoint(&structural_image),
19049            "UnquoteForm hash_discriminator image {subset_image:?} MUST be disjoint from StructuralKind image {structural_image:?} — a cache-key collision would leak macro-expansion hits across the substitution-subset and structural-residual carvings",
19050        );
19051
19052        let atom_outer_byte: HashSet<u8> = HashSet::from([1]);
19053        assert!(
19054            subset_image.is_disjoint(&atom_outer_byte),
19055            "UnquoteForm hash_discriminator image {subset_image:?} MUST be disjoint from the outer Atom carve marker {{1}} — a cache-key collision would leak macro-expansion hits across the substitution-subset and atomic-payload carvings",
19056        );
19057
19058        let non_substitution_quote_image: HashSet<u8> = [QuoteForm::Quote, QuoteForm::Quasiquote]
19059            .iter()
19060            .map(|qf| qf.hash_discriminator())
19061            .collect();
19062        assert!(
19063            subset_image.is_disjoint(&non_substitution_quote_image),
19064            "UnquoteForm hash_discriminator image {subset_image:?} MUST be disjoint from the non-substitution QuoteForm arms' image {non_substitution_quote_image:?} — a cache-key collision would leak macro-expansion hits across the substitution-subset and non-substitution quote-family carvings",
19065        );
19066    }
19067
19068    #[test]
19069    fn unquote_form_per_role_hash_discriminators_alias_quote_form_per_role_hash_discriminators_byte_for_byte(
19070    ) {
19071        // ALIAS CONTRACT: pin both per-role
19072        // `pub(crate) const UnquoteForm::*_HASH_DISCRIMINATOR`
19073        // aliases equal the corresponding
19074        // `pub(crate) const QuoteForm::*_HASH_DISCRIMINATOR`
19075        // byte-for-byte — so the UnquoteForm ⊂ QuoteForm outer-`Sexp`
19076        // cache-key-byte vocabulary containment routes through the
19077        // typed `pub(crate) const UnquoteForm::V_HASH_DISCRIMINATOR:
19078        // u8 = QuoteForm::V_HASH_DISCRIMINATOR` alias chain rather
19079        // than through two independent literal-discipline sites. A
19080        // regression that renames the QuoteForm side without updating
19081        // the UnquoteForm alias pointing at it fails-loudly here with
19082        // the exact axis identified (UNQUOTE / SPLICE); a regression
19083        // that re-inlines the UnquoteForm constant to a fresh literal
19084        // still passes this pin but loses the alias-chain typing
19085        // (which is what
19086        // `unquote_form_hash_discriminators_align_with_all_by_index`
19087        // catches in combination).
19088        //
19089        // Sibling posture to
19090        // `unquote_form_per_role_markers_alias_quote_form_per_role_prefixes_byte_for_byte`
19091        // (commit 3640f76),
19092        // `unquote_form_per_role_iac_forge_tags_alias_quote_form_per_role_iac_forge_tags_byte_for_byte`
19093        // (commit 6acab84), and
19094        // `unquote_form_per_role_labels_alias_quote_form_per_role_labels_byte_for_byte`
19095        // (commit da68af5): all four pin the invariant that the
19096        // UnquoteForm subset algebra's per-role bytes are structurally
19097        // derived from the parent QuoteForm superset's per-role bytes
19098        // via a `pub const = Parent::CONST` alias rather than through
19099        // parallel literal tables — the axis there is the three
19100        // `&'static str` production vocabularies (reader punctuation,
19101        // iac-forge canonical-form, diagnostic label); the axis here
19102        // is the `u8` outer-`Sexp` cache-key vocabulary. All four axes
19103        // span the FOUR production byte-vocabularies the parent
19104        // [`QuoteForm`] closed set carries per role, and all four
19105        // bind through the same aliased typed source of truth on
19106        // this UnquoteForm subset.
19107        use crate::ast::QuoteForm;
19108        assert_eq!(
19109            UnquoteForm::UNQUOTE_HASH_DISCRIMINATOR,
19110            QuoteForm::UNQUOTE_HASH_DISCRIMINATOR
19111        );
19112        assert_eq!(
19113            UnquoteForm::SPLICE_HASH_DISCRIMINATOR,
19114            QuoteForm::UNQUOTE_SPLICE_HASH_DISCRIMINATOR
19115        );
19116    }
19117
19118    #[test]
19119    fn unquote_form_hash_discriminator_arms_route_through_per_role_hash_discriminators_for_every_variant(
19120    ) {
19121        // PATH-UNIFORMITY: `UnquoteForm::V.hash_discriminator()` MUST
19122        // equal the per-role `pub(crate) const
19123        // UnquoteForm::V_HASH_DISCRIMINATOR` for every
19124        // `v: UnquoteForm`. Pre-lift the two cache-key bytes were
19125        // reachable through the two-step composition
19126        // `uf.to_quote_form().hash_discriminator()` OR through direct
19127        // `QuoteForm::UNQUOTE_HASH_DISCRIMINATOR` reach-across; post-
19128        // lift each variant's canonical bytes are reachable through
19129        // the per-role `UnquoteForm::*_HASH_DISCRIMINATOR` alias AND
19130        // through the inherent `hash_discriminator()` method too. Pin
19131        // the byte-equality between the runtime projection and the
19132        // compile-time alias so a regression that renames the alias
19133        // without updating the arm (or vice versa) fails-loudly at
19134        // the exact axis.
19135        //
19136        // Sibling-shape pin to
19137        // `unquote_form_marker_arms_route_through_per_role_markers_for_every_variant`,
19138        // `unquote_form_iac_forge_tag_arms_route_through_per_role_iac_forge_tags_for_every_variant`,
19139        // and
19140        // `unquote_form_label_arms_route_through_per_role_labels_for_every_variant`
19141        // one vocabulary over on the SAME subset algebra — together
19142        // the four pins close the subset algebra's
19143        // `pub const *_MARKER` + `pub const *_IAC_FORGE_TAG` +
19144        // `pub const *_LABEL` + `pub(crate) const *_HASH_DISCRIMINATOR`
19145        // surfaces against the runtime projections that feed the four
19146        // production byte-vocabularies.
19147        assert_eq!(
19148            UnquoteForm::Unquote.hash_discriminator(),
19149            UnquoteForm::UNQUOTE_HASH_DISCRIMINATOR
19150        );
19151        assert_eq!(
19152            UnquoteForm::Splice.hash_discriminator(),
19153            UnquoteForm::SPLICE_HASH_DISCRIMINATOR
19154        );
19155    }
19156
19157    #[test]
19158    fn unquote_form_hash_discriminators_has_expected_cardinality() {
19159        // Cardinality pin: `HASH_DISCRIMINATORS.len() == 2` matches
19160        // `ALL.len()` so a refactor that loosens the type to `&[u8]`
19161        // fails HERE (the `[_; 2]` slot cannot be sliced silently),
19162        // and a variant added to `ALL` without a matching
19163        // `HASH_DISCRIMINATORS` row fails the pair-arity gate at the
19164        // array literal itself before this test even runs. Sibling
19165        // posture to `unquote_form_markers_has_expected_cardinality`,
19166        // `unquote_form_iac_forge_tags_has_expected_cardinality`, and
19167        // `unquote_form_labels_has_expected_cardinality` one
19168        // vocabulary over on the SAME subset algebra.
19169        assert_eq!(UnquoteForm::HASH_DISCRIMINATORS.len(), 2);
19170        assert_eq!(
19171            UnquoteForm::HASH_DISCRIMINATORS.len(),
19172            UnquoteForm::ALL.len()
19173        );
19174    }
19175
19176    #[test]
19177    fn unquote_form_hash_discriminators_align_with_all_by_index() {
19178        // ALIGNMENT PIN: sweep `HASH_DISCRIMINATORS[i] ==
19179        // ALL[i].hash_discriminator()` so any `zip(ALL,
19180        // HASH_DISCRIMINATORS)` consumer reads a coherent (variant,
19181        // cache-key byte) pair off ONE forced-arity array pair. A
19182        // regression that reorders HASH_DISCRIMINATORS without also
19183        // reordering ALL (or vice versa) fails-loudly at the exact
19184        // index that drifted. Sibling of
19185        // `unquote_form_markers_align_with_all_by_index`,
19186        // `unquote_form_iac_forge_tags_align_with_all_by_index`, and
19187        // `unquote_form_labels_align_with_all_by_index` — same
19188        // alignment contract on the fourth byte-vocabulary axis.
19189        assert_eq!(
19190            UnquoteForm::HASH_DISCRIMINATORS.len(),
19191            UnquoteForm::ALL.len()
19192        );
19193        for (i, form) in UnquoteForm::ALL.iter().enumerate() {
19194            assert_eq!(
19195                UnquoteForm::HASH_DISCRIMINATORS[i],
19196                form.hash_discriminator(),
19197                "UnquoteForm::HASH_DISCRIMINATORS[{i}] `{byte}` drifted \
19198                 from UnquoteForm::ALL[{i}].hash_discriminator() \
19199                 `{via_variant}` — the canonical ALL ordering and the \
19200                 HASH_DISCRIMINATORS ordering must match element-wise",
19201                byte = UnquoteForm::HASH_DISCRIMINATORS[i],
19202                via_variant = form.hash_discriminator(),
19203            );
19204        }
19205    }
19206
19207    #[test]
19208    fn unquote_form_hash_discriminators_pairwise_distinct() {
19209        // 2x2 pairwise sweep so a collision between the two cache-
19210        // key bytes (which would silently degrade two distinct
19211        // template-substitution markers to the SAME macro-expansion
19212        // cache key) fails-loudly at the exact pair. Sibling-shape
19213        // pin to `unquote_form_markers_pairwise_distinct`,
19214        // `unquote_form_iac_forge_tags_pairwise_distinct`, and
19215        // `unquote_form_labels_pairwise_distinct` one vocabulary
19216        // over on the SAME subset algebra.
19217        for (i, a) in UnquoteForm::HASH_DISCRIMINATORS.iter().enumerate() {
19218            for (j, b) in UnquoteForm::HASH_DISCRIMINATORS.iter().enumerate() {
19219                if i == j {
19220                    continue;
19221                }
19222                assert_ne!(
19223                    a, b,
19224                    "UnquoteForm::HASH_DISCRIMINATORS[{i}] ({a}) collides \
19225                     with UnquoteForm::HASH_DISCRIMINATORS[{j}] ({b}) — \
19226                     two distinct template markers cannot share cache-key \
19227                     bytes",
19228                );
19229            }
19230        }
19231    }
19232
19233    #[test]
19234    fn unquote_form_hash_discriminators_pin_legacy_cache_key_bytes() {
19235        // Per-arm truth-table pin of each per-role
19236        // `pub(crate) const UnquoteForm::*_HASH_DISCRIMINATOR` at
19237        // its exact canonical `u8` byte: `UNQUOTE_HASH_DISCRIMINATOR
19238        // == 5` and `SPLICE_HASH_DISCRIMINATOR == 6`. Pins each
19239        // per-role `pub(crate) const` value itself (as opposed to
19240        // pinning the `hash_discriminator()` projection method's
19241        // returns, which is
19242        // `unquote_form_hash_discriminator_pins_legacy_cache_key_bytes`'s
19243        // job) so a regression that drifts the alias source on the
19244        // parent [`QuoteForm`] side without updating this subset's
19245        // aliases (which would require ALSO fixing this test) fails
19246        // here BEFORE the composition-through-`to_quote_form()`
19247        // routing masks the drift. Together the two pins close the
19248        // (per-role const, projection method) pairing on the
19249        // substitution-subset cache-key axis. Sibling posture to
19250        // `structural_kind_hash_discriminators_pin_legacy_cache_key_bytes`
19251        // (2-arm structural-residual),
19252        // `quote_form_hash_discriminators_pin_legacy_cache_key_bytes`
19253        // (4-arm quote-family),
19254        // `atom_kind_hash_discriminators_pin_legacy_atom_cache_key_bytes`
19255        // (6-arm atomic-payload nested inner), and
19256        // `sexp_shape_hash_discriminators_pin_legacy_outer_cache_key_bytes`
19257        // (12-arm outer shape) — this 2-arm subset pin joins the
19258        // family the closed-set typed algebra composes on for outer-
19259        // Sexp cache-key partition coherence.
19260        assert_eq!(
19261            UnquoteForm::UNQUOTE_HASH_DISCRIMINATOR,
19262            5,
19263            "UnquoteForm::UNQUOTE_HASH_DISCRIMINATOR drifted from legacy cache-key byte 5",
19264        );
19265        assert_eq!(
19266            UnquoteForm::SPLICE_HASH_DISCRIMINATOR,
19267            6,
19268            "UnquoteForm::SPLICE_HASH_DISCRIMINATOR drifted from legacy cache-key byte 6",
19269        );
19270    }
19271
19272    #[test]
19273    fn unquote_form_hash_discriminators_align_with_superset_by_projection() {
19274        // CROSS-ALGEBRA COMPOSITION contract: for every `i in 0..2`,
19275        // `UnquoteForm::HASH_DISCRIMINATORS[i]` equals the byte the
19276        // parent superset's projection returns for
19277        // `UnquoteForm::ALL[i].to_quote_form()` — the outer-subset
19278        // peer alias equals the superset's canonical byte at rustc-
19279        // time through the `const` initializer AND at test time
19280        // through the projection composition. Sibling posture to
19281        // `sexp_shape_hash_discriminators_align_with_sub_carvings_by_projection`
19282        // (12-arm SexpShape × 3 sub-carvings composition) — this
19283        // pin closes the same composition contract at the 2-of-4
19284        // subset carving level.
19285        use crate::ast::QuoteForm;
19286        for (i, uf) in UnquoteForm::ALL.iter().enumerate() {
19287            let subset_byte = UnquoteForm::HASH_DISCRIMINATORS[i];
19288            let superset_byte = uf.to_quote_form().hash_discriminator();
19289            assert_eq!(
19290                subset_byte, superset_byte,
19291                "UnquoteForm::HASH_DISCRIMINATORS[{i}] ({subset_byte}) \
19292                 drifted from ALL[{i}].to_quote_form().hash_discriminator() \
19293                 ({superset_byte}) — the subset-through-superset composition \
19294                 no longer agrees with the aliased `pub(crate) const` array",
19295            );
19296        }
19297        // Additionally pin that the subset array is exactly the
19298        // Unquote/UnquoteSplice slice of the superset's
19299        // HASH_DISCRIMINATORS. QuoteForm::ALL declaration order is
19300        // `[Quote, Quasiquote, Unquote, UnquoteSplice]` (indices 2/3
19301        // are the substitution subset) — the two subset bytes are
19302        // exactly the `[2..4]` slice of QuoteForm::HASH_DISCRIMINATORS.
19303        assert_eq!(
19304            UnquoteForm::HASH_DISCRIMINATORS[0],
19305            QuoteForm::HASH_DISCRIMINATORS[2]
19306        );
19307        assert_eq!(
19308            UnquoteForm::HASH_DISCRIMINATORS[1],
19309            QuoteForm::HASH_DISCRIMINATORS[3]
19310        );
19311    }
19312
19313    #[test]
19314    fn unquote_form_promotions_has_expected_cardinality() {
19315        // CARDINALITY CONTRACT: `UnquoteForm::PROMOTIONS.len() == 1` —
19316        // pin the single-arm collapse structurally so a future
19317        // refactor that switches the array type to `&[(Self, char,
19318        // Self)]` or extends the triple set doesn't silently loosen
19319        // the closed-set discipline. Sibling posture to
19320        // `quote_form_promotions_has_expected_cardinality` on the
19321        // parent four-arm superset — that pin binds the parent's
19322        // singleton collapse; this pin binds the same collapse at the
19323        // substitution-subset level.
19324        assert_eq!(UnquoteForm::PROMOTIONS.len(), 1);
19325    }
19326
19327    #[test]
19328    fn unquote_form_promotions_pin_legacy_substitution_subset_splice_promotion_triple() {
19329        // TRIPLE-IDENTITY CONTRACT: pin the singleton entry of
19330        // `UnquoteForm::PROMOTIONS` at its exact canonical triple
19331        // `(Self::Unquote, '@', Self::Splice)` — the reader's ONE
19332        // `,` + `@` → `,@` promotion at the substitution-subset
19333        // level. A regression that drifts the triple (e.g. flips the
19334        // head column to `Self::Splice`, drifts the discriminator to
19335        // a different byte, or promotes to a phantom variant) fails
19336        // loudly here at rustc-time equality rather than silently at
19337        // reader tokenizer drift where `,@xs` sequences degrade to
19338        // the wrong substitution-subset marker. Sibling posture to
19339        // `quote_form_promotions_pin_legacy_splice_promotion_triple`
19340        // on the parent four-arm superset — the triple's shape is
19341        // byte-identical up to the substitution-subset variant
19342        // renaming (`QuoteForm::Unquote` ↔ `UnquoteForm::Unquote`,
19343        // `QuoteForm::UnquoteSplice` ↔ `UnquoteForm::Splice`, with
19344        // the middle discriminator column aliased directly at the
19345        // constant site).
19346        let (head, disc, promoted) = UnquoteForm::PROMOTIONS[0];
19347        assert_eq!(head, UnquoteForm::Unquote);
19348        assert_eq!(disc, crate::ast::QuoteForm::SPLICE_DISCRIMINATOR);
19349        assert_eq!(disc, '@');
19350        assert_eq!(promoted, UnquoteForm::Splice);
19351    }
19352
19353    #[test]
19354    fn unquote_form_promotions_align_with_quote_form_promotions_by_projection() {
19355        // CROSS-ALGEBRA COMPOSITION contract: for every `i in 0..1`,
19356        // `(UnquoteForm::PROMOTIONS[i].0.to_quote_form(),
19357        //   UnquoteForm::PROMOTIONS[i].1,
19358        //   UnquoteForm::PROMOTIONS[i].2.to_quote_form())`
19359        // equals `QuoteForm::PROMOTIONS[0]` byte-for-byte — the
19360        // substitution-subset promotion triple embeds into the parent
19361        // superset's promotion triple through the pre-existing 2-of-4
19362        // subset → superset projection `Self::to_quote_form` on the
19363        // head + promoted endpoints AND identity on the middle
19364        // discriminator `char` column. Sibling posture to
19365        // `unquote_form_hash_discriminators_align_with_superset_by_projection`
19366        // (2-arm subset × 4-arm superset composition on the outer-
19367        // `Sexp` cache-key axis) — this pin closes the same
19368        // composition contract on the (head, disc, promoted) triple
19369        // axis at the substitution-subset carving level.
19370        use crate::ast::QuoteForm;
19371        for i in 0..UnquoteForm::PROMOTIONS.len() {
19372            let (subset_head, subset_disc, subset_promoted) = UnquoteForm::PROMOTIONS[i];
19373            let projected_head = subset_head.to_quote_form();
19374            let projected_promoted = subset_promoted.to_quote_form();
19375            let superset_triple = QuoteForm::PROMOTIONS[i];
19376            assert_eq!(
19377                (projected_head, subset_disc, projected_promoted),
19378                superset_triple,
19379                "UnquoteForm::PROMOTIONS[{i}] projection through \
19380                 `to_quote_form()` drifted from \
19381                 QuoteForm::PROMOTIONS[{i}]",
19382            );
19383        }
19384    }
19385
19386    #[test]
19387    fn unquote_form_promotions_head_column_is_unquote_and_promoted_column_is_splice() {
19388        // CARVING PARTITION CONTRACT (substitution-subset level): on
19389        // the two-variant substitution-subset lattice, the promotion
19390        // triple's head column is the ONLY variant with a single-
19391        // char rendered marker (`Self::Unquote`, marker `,`) and the
19392        // promoted column is the ONLY variant with a two-char
19393        // rendered marker (`Self::Splice`, marker `,@`). Pin the
19394        // (head, promoted) endpoint identity across the closed
19395        // `UnquoteForm::ALL` set so a regression that flips the
19396        // endpoints or promotes to a phantom variant fails-loudly
19397        // here. The two variants of `UnquoteForm` split cleanly along
19398        // the promotion axis: single-char-marker `Unquote` is the
19399        // head, two-char-marker `Splice` is the promoted target.
19400        for (head, _, promoted) in UnquoteForm::PROMOTIONS {
19401            assert_eq!(
19402                head,
19403                UnquoteForm::Unquote,
19404                "the substitution-subset promotion head must be the \
19405                 single-char-marker variant `UnquoteForm::Unquote`",
19406            );
19407            assert_eq!(
19408                promoted,
19409                UnquoteForm::Splice,
19410                "the substitution-subset promotion target must be \
19411                 the two-char-marker variant `UnquoteForm::Splice`",
19412            );
19413            assert_eq!(
19414                head.marker(),
19415                crate::ast::QuoteForm::UNQUOTE_PREFIX,
19416                "the substitution-subset promotion head's marker \
19417                 must alias `QuoteForm::UNQUOTE_PREFIX`",
19418            );
19419            assert_eq!(
19420                promoted.marker(),
19421                crate::ast::QuoteForm::UNQUOTE_SPLICE_PREFIX,
19422                "the substitution-subset promotion target's marker \
19423                 must alias `QuoteForm::UNQUOTE_SPLICE_PREFIX`",
19424            );
19425        }
19426    }
19427
19428    #[test]
19429    fn unquote_form_promotions_compose_marker_from_head_marker_and_discriminator() {
19430        // RENDERED-MARKER COMPOSITION LAW (subset-level): for every
19431        // `(head, disc, promoted)` in `UnquoteForm::PROMOTIONS`,
19432        // `format!("{}{}", head.marker(), disc) == promoted.marker()`.
19433        // The subset's rendered-marker composition (head-marker byte
19434        // + discriminator byte = promoted-marker two-byte string)
19435        // agrees byte-for-byte with the promoted variant's rendered
19436        // marker — the reader's peek-then-consume arm's rendered
19437        // marker identity closes the read↔write duality across the
19438        // substitution-subset promotion algebra. Sibling posture to
19439        // `quote_form_promotions_compose_prefix_from_source_prefix_and_discriminator_for_every_entry`
19440        // on the parent four-arm superset — that pin binds the
19441        // parent superset's rendered-prefix composition; this pin
19442        // binds the substitution-subset's rendered-marker
19443        // composition on the SAME single promotion triple.
19444        for (head, disc, promoted) in UnquoteForm::PROMOTIONS {
19445            let composed = format!("{}{}", head.marker(), disc);
19446            assert_eq!(
19447                composed,
19448                promoted.marker(),
19449                "UnquoteForm::PROMOTIONS `({head:?}, {disc:?}, {promoted:?})` \
19450                 marker composition drifted — expected \
19451                 `head.marker() + disc` to equal `promoted.marker()`",
19452            );
19453        }
19454    }
19455
19456    #[test]
19457    fn unquote_form_sexp_shape_round_trips_through_as_unquote_form() {
19458        // The embed/project section law on the substitution-subset
19459        // carving: `UnquoteForm::sexp_shape(uf).as_unquote_form() ==
19460        // Some(uf)` for every `uf: UnquoteForm::ALL`. Proves the
19461        // (embed, project) pair is an `Iso(UnquoteForm, UnquoteShape ⊂
19462        // SexpShape)` — the section is total on `UnquoteForm`'s carving.
19463        // Sibling round-trip to
19464        // `atom_kind_sexp_shape_round_trips_through_as_atom_kind`
19465        // (on the 6-of-12 atomic-payload carving) and
19466        // `quote_form_sexp_shape_round_trips_through_as_quote_form`
19467        // (on the 4-of-12 quote-family carving). Closes the third and
19468        // final closed-set carving of `SexpShape` (the 2-of-12
19469        // substitution-subset carving) as a round-trip identity on
19470        // the typed algebra — pre-lift the pairing only lived in
19471        // the two-step composition `uf.to_quote_form().sexp_shape()
19472        // .as_quote_form().and_then(QuoteForm::as_unquote_form)` at
19473        // callsite; post-lift the pairing rides the typed projection
19474        // directly so a future regression that drifts the
19475        // (UnquoteForm variant, SexpShape variant) pairing fails
19476        // this assertion without depending on the composition
19477        // chain sitting between the two.
19478        for uf in UnquoteForm::ALL {
19479            let shape = uf.sexp_shape();
19480            let recovered = shape.as_unquote_form();
19481            assert_eq!(
19482                recovered,
19483                Some(uf),
19484                "UnquoteForm::{uf:?} did NOT round-trip — sexp_shape().as_unquote_form() must recover the typed marker",
19485            );
19486        }
19487    }
19488
19489    #[test]
19490    fn unquote_form_per_role_shapes_alias_quote_form_per_role_shapes_byte_for_byte() {
19491        // ALIAS-CHAIN CONTRACT: each per-role `pub const *_SHAPE` on
19492        // `UnquoteForm` MUST bind byte-for-byte to the corresponding
19493        // per-role SHAPE on the parent [`crate::ast::QuoteForm`]
19494        // superset — the substitution-subset per-role embed targets
19495        // live at ONE substrate primitive per arm (aliased through
19496        // QuoteForm's canonical per-role SHAPE constants) rather than
19497        // at inline `SexpShape::X` literals OR a parallel two-arm
19498        // match table on this subset. Sibling posture to
19499        // `unquote_form_per_role_labels_alias_quote_form_per_role_labels_byte_for_byte`
19500        // on the diagnostic-label axis of the SAME closed set and to
19501        // `unquote_form_per_role_hash_discriminators_alias_quote_form_per_role_hash_discriminators_byte_for_byte`
19502        // on the outer-Sexp cache-key byte axis — every per-role
19503        // sub-vocabulary axis on this substitution subset now closes
19504        // through the parent's per-role canonical site.
19505        assert_eq!(
19506            UnquoteForm::UNQUOTE_SHAPE,
19507            crate::ast::QuoteForm::UNQUOTE_SHAPE,
19508        );
19509        assert_eq!(
19510            UnquoteForm::SPLICE_SHAPE,
19511            crate::ast::QuoteForm::UNQUOTE_SPLICE_SHAPE,
19512        );
19513    }
19514
19515    #[test]
19516    fn unquote_form_per_role_shapes_pin_canonical_sexp_shape_variants() {
19517        // CANONICAL-SEXP-SHAPE CONTRACT: each per-role `pub const
19518        // *_SHAPE` on `UnquoteForm` MUST project to the canonical
19519        // [`SexpShape`] variant — `UNQUOTE_SHAPE = SexpShape::Unquote`,
19520        // `SPLICE_SHAPE = SexpShape::UnquoteSplice`. A regression that
19521        // silently renumbers QuoteForm's per-role SHAPE at the parent
19522        // superset (which this subset aliases) fails-loudly here at
19523        // the byte-equality level — sibling posture to
19524        // `quote_form_per_role_shapes_pin_canonical_sexp_shape_variants`
19525        // on the parent's 4-of-4 quote-family sweep.
19526        assert_eq!(UnquoteForm::UNQUOTE_SHAPE, SexpShape::Unquote);
19527        assert_eq!(UnquoteForm::SPLICE_SHAPE, SexpShape::UnquoteSplice);
19528    }
19529
19530    #[test]
19531    fn unquote_form_shapes_has_expected_cardinality() {
19532        // CARDINALITY CONTRACT: `UnquoteForm::SHAPES.len() == 2 ==
19533        // UnquoteForm::ALL.len()`. Arity is forced at the declaration
19534        // site by rustc's `[SexpShape; 2]` check; this runtime pin
19535        // fails-loud so a future refactor that switches the array
19536        // type to `&[SexpShape]` doesn't silently loosen the closed-
19537        // set discipline. Sibling posture to
19538        // `unquote_form_hash_discriminators_has_expected_cardinality`
19539        // on the cache-key byte axis of the SAME closed set.
19540        assert_eq!(
19541            UnquoteForm::SHAPES.len(),
19542            2,
19543            "UnquoteForm::SHAPES cardinality drifted from the two-arm substitution-subset algebra",
19544        );
19545        assert_eq!(
19546            UnquoteForm::SHAPES.len(),
19547            UnquoteForm::ALL.len(),
19548            "UnquoteForm::SHAPES cardinality drifted from UnquoteForm::ALL — every family-wide array on this algebra must stay ALL-length in lockstep",
19549        );
19550    }
19551
19552    #[test]
19553    fn unquote_form_shapes_align_with_all_by_index() {
19554        // ALIGNMENT CONTRACT: for every `i` in `0..UnquoteForm::ALL.len()`,
19555        // `UnquoteForm::SHAPES[i] == UnquoteForm::ALL[i].sexp_shape()`
19556        // — the family-wide array is a projection of the two-arm
19557        // `sexp_shape` method sweep in declaration order. A regression
19558        // that reorders ONE array without reordering the other fails-
19559        // loudly here. Sibling posture to
19560        // `unquote_form_hash_discriminators_align_with_all_by_index`
19561        // on the cache-key byte axis of the SAME closed set.
19562        for (i, uf) in UnquoteForm::ALL.iter().enumerate() {
19563            let via_variant = uf.sexp_shape();
19564            let via_array = UnquoteForm::SHAPES[i];
19565            assert_eq!(
19566                via_variant, via_array,
19567                "UnquoteForm::SHAPES[{i}] `{via_array:?}` diverged from UnquoteForm::ALL[{i}].sexp_shape() `{via_variant:?}`",
19568            );
19569        }
19570    }
19571
19572    #[test]
19573    fn unquote_form_shapes_align_with_all_by_index_through_as_unquote_form() {
19574        // ROUND-TRIP CONTRACT: for every `i` in `0..UnquoteForm::ALL.len()`,
19575        // `UnquoteForm::SHAPES[i].as_unquote_form() ==
19576        // Some(UnquoteForm::ALL[i])` — closes the (embed, project)
19577        // section of the `Iso(UnquoteForm, UnquoteShape ⊂ SexpShape)`
19578        // as a family-wide array-indexed law rather than as a per-
19579        // variant assertion sweep (which the pre-existing
19580        // `unquote_form_sexp_shape_round_trips_through_as_unquote_form`
19581        // pins per-variant). Post-lift the round-trip identity closes
19582        // at BOTH the per-variant projection and the family-wide array
19583        // indexing. Sibling posture to
19584        // `quote_form_shapes_align_with_all_by_index_through_as_quote_form`
19585        // on the parent's 4-of-4 sweep.
19586        for (i, uf) in UnquoteForm::ALL.iter().enumerate() {
19587            let shape = UnquoteForm::SHAPES[i];
19588            assert_eq!(
19589                shape.as_unquote_form(),
19590                Some(*uf),
19591                "UnquoteForm::SHAPES[{i}] `{shape:?}` did NOT round-trip through SexpShape::as_unquote_form — the (embed, project) family-wide law broke",
19592            );
19593        }
19594    }
19595
19596    #[test]
19597    fn unquote_form_shapes_pairwise_distinct() {
19598        // INJECTIVITY CONTRACT: the two entries of `UnquoteForm::SHAPES`
19599        // MUST be pairwise distinct — the UnquoteForm ⊂ SexpShape
19600        // 2-of-12 composed carving is injective (each subset variant
19601        // embeds into a UNIQUE outer SexpShape variant). A regression
19602        // that drifts one of the two aliases into the other (e.g. a
19603        // typo that spells `SPLICE_SHAPE = QuoteForm::UNQUOTE_SHAPE`)
19604        // fails-loudly here at the array level rather than as a silent
19605        // round-trip collision at consumer sites. Sibling posture to
19606        // `quote_form_shapes_pairwise_distinct` on the parent's 4-of-4
19607        // sweep and to `unquote_form_hash_discriminators_pairwise_distinct`
19608        // on the cache-key byte axis of the SAME closed set.
19609        assert_ne!(
19610            UnquoteForm::SHAPES[0],
19611            UnquoteForm::SHAPES[1],
19612            "UnquoteForm::SHAPES[0] `{a:?}` collides with UnquoteForm::SHAPES[1] `{b:?}` — the two-arm carving must be injective",
19613            a = UnquoteForm::SHAPES[0],
19614            b = UnquoteForm::SHAPES[1],
19615        );
19616    }
19617
19618    #[test]
19619    fn unquote_form_shapes_align_with_superset_by_projection() {
19620        // CROSS-ALGEBRA COMPOSITION CONTRACT: for every `i`,
19621        // `UnquoteForm::SHAPES[i]` equals the byte the SUPERSET
19622        // projection returns for the corresponding QuoteForm variant
19623        // — the substitution-subset per-role embed target equals the
19624        // parent quote-family superset's canonical SHAPE at rustc-time
19625        // through the `const` alias AND at test time through the
19626        // projection composition. Pin the composition-identity across
19627        // the closed subset so a regression that drifts EITHER the
19628        // subset alias OR the superset canonical site fails-loudly
19629        // here. Sibling posture to
19630        // `unquote_form_hash_discriminators_align_with_superset_by_projection`
19631        // on the cache-key byte axis of the SAME closed set — that
19632        // pin binds the subset cache-key byte to the parent's
19633        // canonical byte; this pin binds the subset embed target to
19634        // the parent's canonical SHAPE.
19635        for (i, uf) in UnquoteForm::ALL.iter().enumerate() {
19636            let via_subset = UnquoteForm::SHAPES[i];
19637            let via_superset = uf.to_quote_form().sexp_shape();
19638            assert_eq!(
19639                via_subset, via_superset,
19640                "UnquoteForm::SHAPES[{i}] `{via_subset:?}` diverged from UnquoteForm::ALL[{i}].to_quote_form().sexp_shape() `{via_superset:?}` — the composition law across the subset carving broke",
19641            );
19642        }
19643    }
19644
19645    #[test]
19646    fn as_unquote_form_projects_each_unquote_shape_to_canonical_unquote_form_and_rejects_non_unquote_shapes(
19647    ) {
19648        // Per-variant truth-table sweep across every `SexpShape::ALL`
19649        // entry — pins each variant's canonical mapping (the two
19650        // substitution-subset shapes project to the matching
19651        // `UnquoteForm`; every other shape — `Quote`, `Quasiquote`,
19652        // `Nil`, `List`, every atomic-payload arm — projects to
19653        // `None`) byte-for-byte. Sibling sweep to
19654        // `as_quote_form_projects_each_quote_shape_to_canonical_quote_form_and_rejects_non_quote_shapes`
19655        // and
19656        // `as_atom_kind_projects_each_atom_shape_to_canonical_atom_kind_and_rejects_non_atom_shapes`
19657        // on the substitution-subset carving axis.
19658        for shape in SexpShape::ALL {
19659            let projected = shape.as_unquote_form();
19660            let expected = match shape {
19661                SexpShape::Unquote => Some(UnquoteForm::Unquote),
19662                SexpShape::UnquoteSplice => Some(UnquoteForm::Splice),
19663                SexpShape::Nil
19664                | SexpShape::List
19665                | SexpShape::Quote
19666                | SexpShape::Quasiquote
19667                | SexpShape::Symbol
19668                | SexpShape::Keyword
19669                | SexpShape::String
19670                | SexpShape::Int
19671                | SexpShape::Float
19672                | SexpShape::Bool => None,
19673            };
19674            assert_eq!(
19675                projected, expected,
19676                "SexpShape::{shape:?}.as_unquote_form() drifted from canonical mapping",
19677            );
19678        }
19679    }
19680
19681    #[test]
19682    fn as_unquote_form_routes_through_as_quote_form_and_quote_form_as_unquote_form_via_composition()
19683    {
19684        // Post-lift composition pin: for every `shape: SexpShape`,
19685        // `shape.as_unquote_form()` and `shape.as_quote_form()
19686        // .and_then(QuoteForm::as_unquote_form)` agree byte-for-byte
19687        // — the 2-of-12 substitution-subset carving rides through
19688        // the composition of the 4-of-12 quote-family carving
19689        // (`SexpShape::as_quote_form`) and the 2-of-4 template-
19690        // substitution subset gate on the quote-family superset
19691        // (`QuoteForm::as_unquote_form`), rather than through a
19692        // parallel twelve-arm inline match table here. A regression
19693        // that re-inlines the twelve arms as a parallel match-table
19694        // still passes the round-trip and truth-table sweeps but
19695        // fails THIS composition pin — the (outer shape, subset
19696        // marker) pairing is no longer derived from the two existing
19697        // closed-set matches the substrate already owns. Sibling
19698        // posture to `unquote_form_sexp_shape_routes_through_to_quote_form_sexp_shape_via_composition`
19699        // on the retraction direction of the (embed, project) dual.
19700        for shape in SexpShape::ALL {
19701            let from_projection = shape.as_unquote_form();
19702            let from_composition = shape
19703                .as_quote_form()
19704                .and_then(crate::ast::QuoteForm::as_unquote_form);
19705            assert_eq!(
19706                from_projection, from_composition,
19707                "SexpShape::{shape:?}.as_unquote_form() drifted from .as_quote_form().and_then(QuoteForm::as_unquote_form) — the retraction is no longer derived from the composition through the quote-family carving",
19708            );
19709        }
19710    }
19711
19712    #[test]
19713    fn as_unquote_form_composes_with_marker_via_unquote_form_marker_round_trip() {
19714        // Cross-projection composition law (substitution-subset sibling
19715        // of `as_quote_form_composes_with_sexp_shape_via_quote_form_prefix_round_trip`
19716        // and `as_atom_kind_composes_with_sexp_shape_via_atom_kind_label_round_trip`):
19717        // for every `uf: UnquoteForm::ALL`, `uf.marker() ==
19718        // uf.sexp_shape().as_unquote_form().expect("...").marker()`.
19719        // Pins the (UnquoteForm variant, SexpShape variant) pairing
19720        // round-trips through the embed/project pair AND preserves
19721        // each variant's canonical template-substitution punctuation
19722        // (`","` / `",@"`) — a regression that drifts the round-trip
19723        // OR drifts the marker surfaces here. The label-coherence
19724        // binds the diagnostic surface to the typed algebra at BOTH
19725        // layers, matching the posture the atomic-payload and quote-
19726        // family cross-projections already carry.
19727        for uf in UnquoteForm::ALL {
19728            let via_round_trip = uf
19729                .sexp_shape()
19730                .as_unquote_form()
19731                .expect("every UnquoteForm round-trips through the embed/project pair")
19732                .marker();
19733            assert_eq!(
19734                via_round_trip,
19735                uf.marker(),
19736                "UnquoteForm::{uf:?}.marker() drifted from sexp_shape().as_unquote_form().marker() — embed/project must preserve marker coherence",
19737            );
19738        }
19739    }
19740
19741    // --- MacroDefHead closed-set FromStr + UnknownMacroDefHead lift ---
19742    //
19743    // Same posture as `unquote_form_*` / `sexp_shape_*` /
19744    // `atom_kind_*` / `quote_form_*`: pin the four contract laws of the
19745    // closed-set (ALL, projection, FromStr, Unknown*) quadruple so the
19746    // typed surface and the rendered diagnostic literal cannot drift,
19747    // and the open-by-design `from_keyword` face matches the
19748    // typed-error `FromStr` face byte-for-byte (the same closed-set
19749    // sweep, different rejection polarities).
19750
19751    #[test]
19752    fn macro_def_head_all_is_unique_and_complete() {
19753        // Closed-set posture: `ALL` enumerates every reachable variant
19754        // EXACTLY ONCE — no duplicates, no omissions. The `[Self; 3]`
19755        // array literal in the declaration forces the arity at compile
19756        // time; this test catches the orthogonal failure modes — a
19757        // future variant added at the type without being added to ALL
19758        // (silently dropped from every consumer's sweep), or a typo
19759        // that duplicates an entry (silently double-counted). Same
19760        // truth-table pinning every sibling closed-set lift in the
19761        // workspace uses (SexpShape::ALL, AtomKind::ALL,
19762        // QuoteForm::ALL, UnquoteForm::ALL, RequestorKind::ALL,
19763        // ReceiptKind::ALL, ConditionKind::ALL, …).
19764        //
19765        // The `iter+map+collect+sort_unstable` quadruple this test
19766        // inlined pre-lift now binds at `<MacroDefHead as
19767        // ClosedSet>::sorted_labels()` — the canonical-ordered
19768        // candidate-list projection on the trait. Distinctness of the
19769        // sorted result is covered by
19770        // `assert_closed_set_well_formed::<MacroDefHead>()`.
19771        assert_eq!(MacroDefHead::ALL.len(), 3);
19772        assert_eq!(
19773            <MacroDefHead as tatara_closed_set::ClosedSet>::sorted_labels(),
19774            vec!["defcheck", "defmacro", "defpoint-template"],
19775            "MacroDefHead::ALL must cover all three macro-definition heads"
19776        );
19777    }
19778
19779    #[test]
19780    fn macro_def_head_keyword_round_trips_through_from_str() {
19781        // Bidirectional `keyword` ↔ `FromStr` contract: for every
19782        // variant in ALL, `head.keyword().parse() == Ok(head)`. A
19783        // regression that drifts the (variant, literal) pairing at
19784        // ONE arm of `keyword` (e.g. typo `defpoint_template` with an
19785        // underscore instead of `defpoint-template` with a hyphen) OR
19786        // at the `FromStr` decode body (off-by-one, missing variant in
19787        // the sweep) fails-loudly here. The canonical-literal site is
19788        // singular (`keyword`) so the round-trip is the only way the
19789        // typed surface and the rendered diagnostic literal can drift
19790        // apart — pinning it here means they cannot.
19791        for head in MacroDefHead::ALL {
19792            let parsed: MacroDefHead = head
19793                .keyword()
19794                .parse()
19795                .expect("every ALL variant's keyword must round-trip through FromStr");
19796            assert_eq!(
19797                parsed,
19798                head,
19799                "FromStr({}) must round-trip to the same variant",
19800                head.keyword()
19801            );
19802        }
19803    }
19804
19805    #[test]
19806    fn macro_def_head_from_keyword_matches_from_str_for_every_input() {
19807        // Cross-face contract: the `Option`-faced `from_keyword`
19808        // projection (`tatara_lisp::ast::Sexp::as_call_to_any`'s
19809        // decoder slot, signature `Fn(&str) -> Option<T>`) and the
19810        // typed-error-faced `FromStr` projection are the SAME closed-
19811        // set sweep with different rejection polarities. After the
19812        // lift `from_keyword` delegates to `parse().ok()`, so the
19813        // closed-set sweep lives at ONE site (the `FromStr` impl) and
19814        // both faces project the same accept/reject decision at every
19815        // input. Pinning this law means a future refactor that drifts
19816        // ONE face from the other (e.g., adding a fourth variant to
19817        // `keyword` but forgetting to bump `Self::ALL`, or branching
19818        // `from_keyword` against a hand-rolled match arm instead of
19819        // routing through the typed sweep) fails here.
19820        let inputs: &[&str] = &[
19821            // The three canonical heads — both faces accept.
19822            "defmacro",
19823            "defpoint-template",
19824            "defcheck",
19825            // Non-canonical capitalizations — both faces reject.
19826            "Defmacro",
19827            "DEFCHECK",
19828            "DefpointTemplate",
19829            // Near misses — both faces reject.
19830            "defmacroo",
19831            "defcheckk",
19832            "defpoint_template",
19833            "defpoint",
19834            "defpoint-templates",
19835            // Sibling authoring surfaces from other closed sets —
19836            // both faces reject (cross-set disjointness).
19837            "defmonitor",
19838            "defnotify",
19839            "defpoint",
19840            "defalertpolicy",
19841            // SexpShape labels (the structural-identity vocabulary on
19842            // a DIFFERENT axis) — both faces reject.
19843            "symbol",
19844            "list",
19845            "nil",
19846            // Punctuation from QuoteForm / UnquoteForm vocabularies —
19847            // both faces reject (closed sets live on disjoint axes).
19848            ",",
19849            ",@",
19850            "'",
19851            "`",
19852            // Edge cases — both faces reject.
19853            "",
19854            " ",
19855            " defmacro",
19856            "defmacro ",
19857        ];
19858        for s in inputs {
19859            let from_kw = MacroDefHead::from_keyword(s);
19860            let from_str = s.parse::<MacroDefHead>().ok();
19861            assert_eq!(
19862                from_kw,
19863                from_str,
19864                "`from_keyword` and `FromStr` must agree on {s:?}: from_keyword={from_kw:?}, FromStr={from_str:?}",
19865            );
19866        }
19867    }
19868
19869    #[test]
19870    fn unknown_macro_def_head_carries_offending_input_verbatim() {
19871        // Operator-facing diagnostic contract: the offending input
19872        // lands in the typed error verbatim — no normalization, no
19873        // truncation, no whitespace coercion. Pin the exact
19874        // `#[error(...)]` rendering AND the typed `.0` field
19875        // projection so a future refactor that normalizes (e.g.
19876        // `.trim()` / `.to_ascii_lowercase()`) before building the
19877        // error or that drops the input fails-loudly here. Symmetric
19878        // to every sibling `Unknown*` carrier in the workspace
19879        // ([`UnknownSexpShape`], [`crate::ast::UnknownAtomKind`],
19880        // [`UnknownUnquoteForm`], [`crate::ast::UnknownQuoteForm`],
19881        // `tatara_process::allocation::UnknownRequestorKind`, …).
19882        let err: UnknownMacroDefHead = "Defmacro"
19883            .parse::<MacroDefHead>()
19884            .expect_err("capitalized `Defmacro` is not a canonical macro-definition head");
19885        assert_eq!(err.0, "Defmacro");
19886        assert_eq!(format!("{err}"), "unknown macro definition head: Defmacro");
19887
19888        let err: UnknownMacroDefHead = "defmacroo"
19889            .parse::<MacroDefHead>()
19890            .expect_err("`defmacroo` is not a canonical macro-definition head");
19891        assert_eq!(err.0, "defmacroo");
19892        assert_eq!(format!("{err}"), "unknown macro definition head: defmacroo");
19893
19894        let err: UnknownMacroDefHead = ""
19895            .parse::<MacroDefHead>()
19896            .expect_err("empty input must NOT decode to a MacroDefHead");
19897        assert_eq!(err.0, "");
19898        assert_eq!(format!("{err}"), "unknown macro definition head: ");
19899
19900        // Whitespace-padded canonical keyword MUST reject — the
19901        // typed identity is byte-exact, the offending input is
19902        // returned verbatim with its padding intact (not trimmed).
19903        // The rendered diagnostic preserves the leading space, so
19904        // the bad value reaches the operator unmolested.
19905        let err: UnknownMacroDefHead = " defmacro"
19906            .parse::<MacroDefHead>()
19907            .expect_err("leading-space `defmacro` must reject — typed identity is byte-exact");
19908        assert_eq!(err.0, " defmacro");
19909        assert_eq!(
19910            format!("{err}"),
19911            "unknown macro definition head:  defmacro",
19912            "leading whitespace is preserved verbatim in the rendered diagnostic",
19913        );
19914    }
19915
19916    #[test]
19917    fn macro_def_head_from_str_rejects_cross_axis_vocabularies() {
19918        // Cross-axis guard: a MacroDefHead is the head keyword of a
19919        // `(defmacro …)`-shaped form — distinct from every other
19920        // closed-set vocabulary in this crate. A `FromStr` that
19921        // silently accepted a SexpShape label, an UnquoteForm marker,
19922        // a QuoteForm prefix, or an AtomKind label would corrupt the
19923        // typed identity at the macro-definition-head boundary. Pin
19924        // BOTH directions: the three canonical keywords decode
19925        // through MacroDefHead, the sibling closed-set vocabularies
19926        // do NOT.
19927        assert_eq!(
19928            "defmacro".parse::<MacroDefHead>().unwrap(),
19929            MacroDefHead::Defmacro
19930        );
19931        assert_eq!(
19932            "defpoint-template".parse::<MacroDefHead>().unwrap(),
19933            MacroDefHead::DefpointTemplate
19934        );
19935        assert_eq!(
19936            "defcheck".parse::<MacroDefHead>().unwrap(),
19937            MacroDefHead::Defcheck
19938        );
19939
19940        // SexpShape labels — the structural-identity vocabulary
19941        // (twelve outer Sexp shapes) — share NO labels with the
19942        // macro-definition-head vocabulary. All must reject.
19943        for shape in SexpShape::ALL {
19944            assert!(
19945                shape.label().parse::<MacroDefHead>().is_err(),
19946                "SexpShape::{shape:?} label `{}` must NOT decode as a MacroDefHead",
19947                shape.label(),
19948            );
19949        }
19950
19951        // UnquoteForm punctuation markers (`,` / `,@`) belong to the
19952        // template-marker axis — they MUST reject here because
19953        // MacroDefHead is on the symbol-keyword axis.
19954        for form in UnquoteForm::ALL {
19955            assert!(
19956                form.marker().parse::<MacroDefHead>().is_err(),
19957                "UnquoteForm marker `{}` must NOT decode as a MacroDefHead",
19958                form.marker(),
19959            );
19960        }
19961
19962        // The `defpoint` authoring-surface keyword names a
19963        // `(defpoint …)` definition form — NOT a definition-template
19964        // form. The two are intentionally disjoint: `defpoint` is a
19965        // tatara-process domain authoring head, `defpoint-template`
19966        // is a parameterized-template head on the same surface.
19967        // Pinning the rejection here keeps the two from drifting.
19968        assert!("defpoint".parse::<MacroDefHead>().is_err());
19969        assert!("defmonitor".parse::<MacroDefHead>().is_err());
19970        assert!("defnotify".parse::<MacroDefHead>().is_err());
19971        assert!("defalertpolicy".parse::<MacroDefHead>().is_err());
19972
19973        // Common-Lisp-style aliases the substrate does NOT admit.
19974        // Pinning these rejects keeps a future refactor from
19975        // silently extending the vocabulary without bumping
19976        // `Self::ALL` first.
19977        assert!("def-macro".parse::<MacroDefHead>().is_err());
19978        assert!("defun".parse::<MacroDefHead>().is_err());
19979        assert!("define-syntax".parse::<MacroDefHead>().is_err());
19980    }
19981
19982    #[test]
19983    fn macro_def_head_is_well_formed_closed_set() {
19984        // Structural contract: MacroDefHead's three variants are
19985        // pairwise distinct, round-trip through the trait's `label` ↔
19986        // `parse_label`, and reject the empty string — the
19987        // workspace-wide `assert_closed_set_well_formed::<T>()` testkit
19988        // pinned across every `tatara-process` closed-set implementor
19989        // AND every prior tatara-lisp retrofit (`AtomKind`, `QuoteForm`).
19990        // The substrate-level assertion runs on the auto-derived
19991        // `impl ClosedSet for MacroDefHead` emitted by
19992        // `#[derive(tatara_closed_set::DeriveClosedSet)]` — a regression
19993        // that drifts the derive's `make_unknown` delegation, the
19994        // `via = "keyword"` projection (`"defmacro"` /
19995        // `"defpoint-template"` / `"defcheck"`), or the variant
19996        // listing forced through `Self::ALL` fails-loudly here in
19997        // isolation from the per-variant truth tables above.
19998        tatara_closed_set::assert_closed_set_well_formed::<MacroDefHead>();
19999    }
20000
20001    #[test]
20002    fn unquote_form_is_well_formed_closed_set() {
20003        // Structural contract: UnquoteForm's two variants are pairwise
20004        // distinct, round-trip through the trait's `label` ↔
20005        // `parse_label`, and reject the empty string. The substrate-
20006        // level assertion runs on the auto-derived `impl ClosedSet
20007        // for UnquoteForm` emitted by
20008        // `#[derive(tatara_closed_set::DeriveClosedSet)]` — a regression
20009        // that drifts the `via = "marker"` projection (`","` /
20010        // `",@"`) or conflates the punctuation-axis vocabulary with
20011        // the structural-axis SexpShape vocabulary
20012        // (`"unquote"` / `"unquote-splice"`) fails-loudly here. The
20013        // cross-axis disjointness check stays at
20014        // `unquote_form_from_str_rejects_sexp_shape_labels_on_template_marker_axis`;
20015        // THIS test pins the in-axis well-formedness floor.
20016        tatara_closed_set::assert_closed_set_well_formed::<UnquoteForm>();
20017    }
20018
20019    #[test]
20020    fn kwarg_path_kind_is_well_formed_closed_set() {
20021        // Structural contract: KwargPathKind's three variants are
20022        // pairwise distinct, round-trip through the trait's `label` ↔
20023        // `parse_label`, and reject the empty string. The substrate-
20024        // level assertion runs on the auto-derived `impl ClosedSet
20025        // for KwargPathKind` emitted by
20026        // `#[derive(tatara_closed_set::DeriveClosedSet)]` — a regression
20027        // that drifts the `via = "label"` projection (`"named"` /
20028        // `"item"` / `"slot"`) or the variant listing forced through
20029        // `Self::ALL` fails-loudly here.
20030        tatara_closed_set::assert_closed_set_well_formed::<KwargPathKind>();
20031    }
20032
20033    #[test]
20034    fn expected_kwarg_shape_is_well_formed_closed_set() {
20035        // Structural contract: ExpectedKwargShape's seven variants are
20036        // pairwise distinct, round-trip through the trait's `label` ↔
20037        // `parse_label`, and reject the empty string. The substrate-
20038        // level assertion runs on the auto-derived `impl ClosedSet
20039        // for ExpectedKwargShape` emitted by
20040        // `#[derive(tatara_closed_set::DeriveClosedSet)]` — a regression
20041        // that drifts the `via = "label"` projection (`"keyword"` /
20042        // `"string"` / `"int"` / `"number"` / `"bool"` / `"list"` /
20043        // `"list of strings"`) fails-loudly here.
20044        tatara_closed_set::assert_closed_set_well_formed::<ExpectedKwargShape>();
20045    }
20046
20047    #[test]
20048    fn sexp_shape_is_well_formed_closed_set() {
20049        // Structural contract: SexpShape's twelve variants are
20050        // pairwise distinct, round-trip through the trait's `label` ↔
20051        // `parse_label`, and reject the empty string. The substrate-
20052        // level assertion runs on the auto-derived `impl ClosedSet
20053        // for SexpShape` emitted by
20054        // `#[derive(tatara_closed_set::DeriveClosedSet)]` — a regression
20055        // that drifts the `via = "label"` projection (`"nil"` /
20056        // `"symbol"` / `"keyword"` / `"string"` / `"int"` / `"float"`
20057        // / `"bool"` / `"list"` / `"quote"` / `"quasiquote"` /
20058        // `"unquote"` / `"unquote-splice"`) or the variant listing
20059        // forced through `Self::ALL` (cardinality 12 = every reachable
20060        // outer `Sexp` shape) fails-loudly here.
20061        tatara_closed_set::assert_closed_set_well_formed::<SexpShape>();
20062    }
20063
20064    #[test]
20065    fn compiler_spec_io_stage_is_well_formed_closed_set() {
20066        // Structural contract: CompilerSpecIoStage's four variants are
20067        // pairwise distinct, round-trip through the trait's `label` ↔
20068        // `parse_label`, and reject the empty string. The substrate-
20069        // level assertion runs on the auto-derived `impl ClosedSet
20070        // for CompilerSpecIoStage` emitted by
20071        // `#[derive(tatara_closed_set::DeriveClosedSet)]` — a regression
20072        // that drifts the `via = "label"` projection (`"serialize"` /
20073        // `"write"` / `"read"` / `"deserialize"`) or the variant
20074        // listing forced through `Self::ALL` (cardinality 4 = every
20075        // reachable disk-persistence (operation, stage) pair) fails-
20076        // loudly here.
20077        //
20078        // Distinct from the sibling closed-set well-formedness assertions
20079        // above: this enum carries `#[closed_set(no_from_str)]`, so the
20080        // trait surface's `parse_label` keys on the SINGULAR label
20081        // (`"read"` → `LoadFromDiskRead`) while the inherent
20082        // `std::str::FromStr` impl below keys on the COMPOUND key
20083        // (`"load_from_disk: read"` → `LoadFromDiskRead`). The trait
20084        // surface's well-formedness floor — non-empty `ALL`, round-trip
20085        // through `label`, pairwise-distinct labels, empty-string
20086        // outside the set — STILL holds on the singular projection
20087        // because the four labels (`"serialize"` / `"write"` /
20088        // `"read"` / `"deserialize"`) ARE bijective with the four
20089        // variants by accident of the current closed set; the
20090        // compound-key shape is what `FromStr` enforces and is what
20091        // `compiler_spec_io_stage_compound_key_round_trips_through_from_str`
20092        // pins. The two surfaces are deliberately disjoint — the
20093        // `no_from_str` axis exists for exactly this case.
20094        tatara_closed_set::assert_closed_set_well_formed::<CompilerSpecIoStage>();
20095    }
20096
20097    #[test]
20098    fn structural_kind_all_is_unique_and_complete() {
20099        // Cardinality + uniqueness floor for the structural-residual
20100        // carving's typed algebra: `StructuralKind::ALL` has exactly
20101        // TWO entries (`Nil`, `List`) and both are pairwise distinct.
20102        // Sibling of the substrate-wide `*_all_is_unique_and_complete`
20103        // truth-table shape carried by every closed-set implementor.
20104        assert_eq!(
20105            StructuralKind::ALL.len(),
20106            2,
20107            "StructuralKind::ALL must have exactly 2 entries (Nil + List)",
20108        );
20109        let mut seen = Vec::new();
20110        for sk in StructuralKind::ALL {
20111            assert!(
20112                !seen.contains(&sk),
20113                "StructuralKind::{sk:?} repeated in ALL — variants must be pairwise distinct",
20114            );
20115            seen.push(sk);
20116        }
20117    }
20118
20119    #[test]
20120    fn structural_kind_sexp_shape_projects_canonical_variant_for_every_marker() {
20121        // Per-arm truth-table pin of the canonical (StructuralKind
20122        // variant, SexpShape variant) mapping: `StructuralKind::Nil →
20123        // SexpShape::Nil` and `StructuralKind::List → SexpShape::List`
20124        // byte-for-byte. Sibling of
20125        // `unquote_form_sexp_shape_emits_canonical_shape_for_every_marker`
20126        // on the residual-carving axis rather than the substitution-
20127        // subset axis.
20128        assert_eq!(
20129            StructuralKind::Nil.sexp_shape(),
20130            SexpShape::Nil,
20131            "StructuralKind::Nil.sexp_shape() drifted from SexpShape::Nil",
20132        );
20133        assert_eq!(
20134            StructuralKind::List.sexp_shape(),
20135            SexpShape::List,
20136            "StructuralKind::List.sexp_shape() drifted from SexpShape::List",
20137        );
20138    }
20139
20140    #[test]
20141    fn structural_kind_sexp_shape_round_trips_through_as_structural_kind() {
20142        // The embed/project section law on the structural-residual
20143        // carving: `StructuralKind::sexp_shape(sk).as_structural_kind()
20144        // == Some(sk)` for every `sk: StructuralKind::ALL`. Proves the
20145        // (embed, project) pair is an `Iso(StructuralKind,
20146        // StructuralShape ⊂ SexpShape)` — the section is total on
20147        // `StructuralKind`'s carving. Sibling round-trip to
20148        // `atom_kind_sexp_shape_round_trips_through_as_atom_kind`
20149        // (on the 6-of-12 atomic-payload carving),
20150        // `quote_form_sexp_shape_round_trips_through_as_quote_form`
20151        // (on the 4-of-12 quote-family carving), AND
20152        // `unquote_form_sexp_shape_round_trips_through_as_unquote_form`
20153        // (on the 2-of-12 substitution-subset carving). Closes the
20154        // third and final closed-set CARVING of `SexpShape` — the
20155        // structural-residual — as a round-trip identity on the typed
20156        // algebra.
20157        for sk in StructuralKind::ALL {
20158            let shape = sk.sexp_shape();
20159            let recovered = shape.as_structural_kind();
20160            assert_eq!(
20161                recovered,
20162                Some(sk),
20163                "StructuralKind::{sk:?} did NOT round-trip — sexp_shape().as_structural_kind() must recover the typed marker",
20164            );
20165        }
20166    }
20167
20168    #[test]
20169    fn as_structural_kind_projects_each_structural_shape_to_canonical_structural_kind_and_rejects_non_structural_shapes(
20170    ) {
20171        // Per-variant truth-table sweep across every `SexpShape::ALL`
20172        // entry — pins each variant's canonical mapping (the two
20173        // structural-residual shapes project to the matching
20174        // `StructuralKind`; every other shape — every atomic-payload
20175        // arm AND every quote-family wrapper — projects to `None`)
20176        // byte-for-byte. Sibling sweep to
20177        // `as_atom_kind_projects_each_atom_shape_to_canonical_atom_kind_and_rejects_non_atom_shapes`,
20178        // `as_quote_form_projects_each_quote_shape_to_canonical_quote_form_and_rejects_non_quote_shapes`,
20179        // and
20180        // `as_unquote_form_projects_each_unquote_shape_to_canonical_unquote_form_and_rejects_non_unquote_shapes`
20181        // on the structural-residual carving axis.
20182        for shape in SexpShape::ALL {
20183            let projected = shape.as_structural_kind();
20184            let expected = match shape {
20185                SexpShape::Nil => Some(StructuralKind::Nil),
20186                SexpShape::List => Some(StructuralKind::List),
20187                SexpShape::Symbol
20188                | SexpShape::Keyword
20189                | SexpShape::String
20190                | SexpShape::Int
20191                | SexpShape::Float
20192                | SexpShape::Bool
20193                | SexpShape::Quote
20194                | SexpShape::Quasiquote
20195                | SexpShape::Unquote
20196                | SexpShape::UnquoteSplice => None,
20197            };
20198            assert_eq!(
20199                projected, expected,
20200                "SexpShape::{shape:?}.as_structural_kind() drifted from canonical mapping",
20201            );
20202        }
20203    }
20204
20205    #[test]
20206    fn structural_kind_hash_discriminator_pins_legacy_cache_key_bytes() {
20207        // CACHE-KEY CONTRACT: pre-lift `Hash for Sexp` used the literal
20208        // byte values 0 for `Sexp::Nil` and 2 for `Sexp::List(_)` as
20209        // the two structural-residual arm discriminators. The
20210        // macro-expansion cache (`Expander::cache`) keys on Hash;
20211        // ANY change to a discriminator byte silently invalidates
20212        // every cached expansion across the substrate AND risks
20213        // collision with the reserved bytes the two sibling closed-
20214        // set carvings use (`1u8` for the `Sexp::Atom(_)` outer-carve
20215        // marker, `3..=6` for the quote-family via
20216        // `crate::ast::QuoteForm::hash_discriminator`). Pin the two
20217        // legacy values explicitly so a regression that re-numbers
20218        // them surfaces immediately — the `StructuralKind` algebra
20219        // MUST preserve the prior byte mapping bit-for-bit. Sibling
20220        // posture to
20221        // `atom_kind_hash_discriminator_pins_legacy_atom_cache_key_bytes`
20222        // on the atomic-payload axis (nested inner) AND
20223        // `quote_form_hash_discriminator_pins_legacy_cache_key_bytes`
20224        // on the quote-family axis (outer `Sexp` level).
20225        assert_eq!(StructuralKind::Nil.hash_discriminator(), 0);
20226        assert_eq!(StructuralKind::List.hash_discriminator(), 2);
20227    }
20228
20229    #[test]
20230    fn structural_kind_hash_discriminator_bytes_are_pairwise_disjoint() {
20231        // Closed-set injectivity: the two discriminator bytes must
20232        // partition their subset of the outer-`Sexp` discriminator
20233        // space injectively so `Sexp::Nil` and `Sexp::List(_)` never
20234        // produce the SAME hash discriminator — a violation here
20235        // means the cache could conflate the empty-result marker
20236        // with the empty-list container. Sibling pin to
20237        // `atom_kind_hash_discriminator_bytes_are_pairwise_disjoint`
20238        // on the atomic-payload axis.
20239        let bytes: Vec<u8> = StructuralKind::ALL
20240            .iter()
20241            .map(|sk| sk.hash_discriminator())
20242            .collect();
20243        let mut sorted = bytes.clone();
20244        sorted.sort_unstable();
20245        let mut deduped = sorted.clone();
20246        deduped.dedup();
20247        assert_eq!(
20248            sorted, deduped,
20249            "StructuralKind hash discriminator bytes must be pairwise disjoint",
20250        );
20251        assert_eq!(sorted, vec![0, 2]);
20252    }
20253
20254    #[test]
20255    fn structural_kind_hash_discriminator_disjoint_from_atom_outer_carve_byte_and_quote_form_hash_discriminator_partition(
20256    ) {
20257        // JOINT PARTITION CONTRACT: the outer-`Sexp` discriminator
20258        // space `{0, 1, 2, 3, 4, 5, 6}` MUST partition across the
20259        // three closed-set carvings AND the atomic-carve outer
20260        // marker byte with NO overlap — the substrate's cache-key
20261        // prefix-uniqueness contract binds bit-for-bit against this
20262        // partition. This test pins the disjointness of
20263        // `StructuralKind::hash_discriminator`'s `{0, 2}` against the
20264        // atomic-carve outer marker byte `1u8` (single-arm literal
20265        // inside `Hash for Sexp`) AND against
20266        // `crate::ast::QuoteForm::hash_discriminator`'s `{3, 4, 5,
20267        // 6}`. A regression that drifts ANY of the three carvings'
20268        // discriminator arms into another's byte space (e.g. a
20269        // future refactor that re-numbers `StructuralKind::List` to
20270        // `1u8`) surfaces here — the prefix-uniqueness contract
20271        // becomes a compile-time-verified theorem across the joint
20272        // partition, not a per-carving isolated invariant. Companion
20273        // to the on-axis `structural_kind_hash_discriminator_bytes_are_pairwise_disjoint`
20274        // pin (which enforces intra-carving injectivity); together
20275        // they close the three-carving-plus-atom-marker joint
20276        // disjointness contract.
20277        // Route the atomic-carve outer marker byte through
20278        // `crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR` (the typed
20279        // substrate primitive on the closed-set atomic-payload
20280        // algebra) rather than through a duplicated local `const
20281        // ATOM_OUTER_CARVE_BYTE: u8 = 1` fixture — the pre-lift local
20282        // const named the byte at ONE test site with no algebra
20283        // provenance, so a rename or renumbering at the algebra
20284        // required an inline byte drift at every joint-partition
20285        // fixture. Post-lift the byte lives at ONE
20286        // `pub(crate) const` on the [`crate::ast::AtomKind`] algebra;
20287        // this test's disjointness contract binds against the typed
20288        // constant so a regression that renumbers the primitive
20289        // fails-loudly at THIS test alongside the shape-level
20290        // collapse test.
20291        let structural: Vec<u8> = StructuralKind::ALL
20292            .iter()
20293            .map(|sk| sk.hash_discriminator())
20294            .collect();
20295        let quote_family: Vec<u8> = crate::ast::QuoteForm::ALL
20296            .iter()
20297            .map(|qf| qf.hash_discriminator())
20298            .collect();
20299        for byte in &structural {
20300            assert_ne!(
20301                *byte,
20302                crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR,
20303                "StructuralKind::hash_discriminator collided with the atomic-carve outer marker byte {marker}",
20304                marker = crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR,
20305            );
20306            assert!(
20307                !quote_family.contains(byte),
20308                "StructuralKind::hash_discriminator byte {byte} collided with QuoteForm::hash_discriminator partition {quote_family:?}",
20309            );
20310        }
20311        let mut union: Vec<u8> = structural
20312            .iter()
20313            .copied()
20314            .chain(std::iter::once(
20315                crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR,
20316            ))
20317            .chain(quote_family.iter().copied())
20318            .collect();
20319        union.sort_unstable();
20320        let mut deduped = union.clone();
20321        deduped.dedup();
20322        assert_eq!(
20323            union, deduped,
20324            "the three-carving-plus-atom-marker joint discriminator space {union:?} must partition without overlap",
20325        );
20326        assert_eq!(union, vec![0, 1, 2, 3, 4, 5, 6]);
20327    }
20328
20329    #[test]
20330    fn structural_kind_hash_discriminators_pin_legacy_cache_key_bytes() {
20331        // Pin each per-role `pub(crate) const` at its exact canonical
20332        // `u8` byte. Sibling of
20333        // `structural_kind_hash_discriminator_pins_legacy_cache_key_bytes`
20334        // (which pins the method's projection) — this pin asserts the
20335        // `pub(crate) const` value itself, so a regression that drifts
20336        // the constant but leaves the method's arm literal in place
20337        // (unlikely post-lift but structurally distinct) surfaces
20338        // here. The cache-key partition `{0, 2}` is load-bearing for
20339        // the outer [`Hash for Sexp`](crate::ast::Sexp) prefix-
20340        // uniqueness contract — a `2u8` drift to `1u8` would silently
20341        // collide `Sexp::List(_)` with the reserved atomic-carve
20342        // outer marker and mis-hash every List through the Atom
20343        // arm's cache slot.
20344        assert_eq!(StructuralKind::NIL_HASH_DISCRIMINATOR, 0);
20345        assert_eq!(StructuralKind::LIST_HASH_DISCRIMINATOR, 2);
20346    }
20347
20348    #[test]
20349    fn structural_kind_hash_discriminator_routes_through_typed_per_role_constants() {
20350        // PATH-UNIFORMITY: `Self::hash_discriminator(self)` returns
20351        // the per-role `pub(crate) const` byte-for-byte per variant,
20352        // catching a regression that reverts ONE arm to an inline
20353        // `0` / `2` `u8` literal (or drifts one arm's byte silently).
20354        // Sibling posture to
20355        // `atom_kind_hash_discriminator_routes_through_typed_per_role_constants`
20356        // on the atomic-payload sub-carving AND to
20357        // `quote_form_hash_discriminator_routes_through_typed_per_role_constants`
20358        // on the quote-family sub-carving of the SAME outer-`Sexp`
20359        // Hash body.
20360        for (kind, expected) in [
20361            (StructuralKind::Nil, StructuralKind::NIL_HASH_DISCRIMINATOR),
20362            (
20363                StructuralKind::List,
20364                StructuralKind::LIST_HASH_DISCRIMINATOR,
20365            ),
20366        ] {
20367            let actual = kind.hash_discriminator();
20368            assert_eq!(
20369                actual, expected,
20370                "StructuralKind::{kind:?}.hash_discriminator() `{actual}` \
20371                 drifted from per-role constant `{expected}` — the arm \
20372                 must route through the typed `pub(crate) const` rather \
20373                 than an inline `u8` literal",
20374            );
20375        }
20376    }
20377
20378    #[test]
20379    fn structural_kind_hash_discriminators_has_expected_cardinality() {
20380        // Cardinality contract: `Self::HASH_DISCRIMINATORS.len() == 2`
20381        // — pinned at the declaration site by rustc's forced-arity
20382        // check on `[u8; 2]`. This test surfaces the arity as a
20383        // fail-loud runtime pin so a future refactor that switches
20384        // the array type to `&[u8]` (dropping the compile-time arity
20385        // forcing) doesn't silently loosen the closed-set discipline
20386        // the family relies on. Sibling posture to
20387        // `structural_kind_labels_has_expected_cardinality` on the
20388        // diagnostic label axis of the SAME closed set, and to
20389        // `atom_kind_hash_discriminators_has_expected_cardinality` +
20390        // `quote_form_hash_discriminators_has_expected_cardinality`
20391        // on the two sibling carvings' cache-key-byte peers.
20392        assert_eq!(
20393            StructuralKind::HASH_DISCRIMINATORS.len(),
20394            2,
20395            "StructuralKind::HASH_DISCRIMINATORS cardinality drifted \
20396             from 2 — the closed structural-residual domain admits \
20397             exactly two kinds by construction; a third extension \
20398             surfaces here"
20399        );
20400    }
20401
20402    #[test]
20403    fn structural_kind_hash_discriminators_align_with_all_by_index() {
20404        // ALIGNMENT CONTRACT: `Self::HASH_DISCRIMINATORS[i] ==
20405        // Self::ALL[i].hash_discriminator()` element-wise. Pins that
20406        // the typed variant ALL and the `u8` HASH_DISCRIMINATORS ALL
20407        // stay in lockstep under any reorder — a regression that
20408        // reorders ONE array without reordering the other silently
20409        // misaligns every `zip(ALL, HASH_DISCRIMINATORS)` consumer.
20410        // Sibling posture to
20411        // `structural_kind_labels_align_with_all_by_index` on the
20412        // diagnostic label axis of the SAME closed set.
20413        for (i, kind) in StructuralKind::ALL.iter().enumerate() {
20414            assert_eq!(
20415                StructuralKind::HASH_DISCRIMINATORS[i],
20416                kind.hash_discriminator(),
20417                "StructuralKind::HASH_DISCRIMINATORS[{i}] `{disc}` \
20418                 drifted from StructuralKind::ALL[{i}] ({kind:?}).hash_discriminator() \
20419                 `{via_variant}` — the canonical declaration order of \
20420                 the ALL array and the hash_discriminator projection \
20421                 must match element-wise",
20422                disc = StructuralKind::HASH_DISCRIMINATORS[i],
20423                via_variant = kind.hash_discriminator(),
20424            );
20425        }
20426    }
20427
20428    #[test]
20429    fn structural_kind_hash_discriminators_pairwise_distinct() {
20430        // PAIRWISE DISJOINTNESS: every entry of the
20431        // `HASH_DISCRIMINATORS` array must differ so the outer-`Sexp`
20432        // Hash body cannot route two structural-residual variants
20433        // through the same cache-key byte — a collision would
20434        // silently mis-hash two structurally-distinct residual shapes
20435        // (`Sexp::Nil` and `Sexp::List(_)`) to the same
20436        // `Expander::cache` slot. Family-wide sweep over
20437        // `HASH_DISCRIMINATORS × HASH_DISCRIMINATORS` — supersedes any
20438        // per-pair pin and picks up new discriminators mechanically.
20439        // Sibling posture to
20440        // `structural_kind_hash_discriminator_bytes_are_pairwise_disjoint`
20441        // (which sweeps via the projection) — this sweep pins the
20442        // array itself so a regression that lifts a fresh entry with
20443        // a colliding byte surfaces at the ALL sweep.
20444        for (i, a) in StructuralKind::HASH_DISCRIMINATORS.iter().enumerate() {
20445            for (j, b) in StructuralKind::HASH_DISCRIMINATORS.iter().enumerate() {
20446                if i == j {
20447                    continue;
20448                }
20449                assert_ne!(
20450                    a, b,
20451                    "StructuralKind::HASH_DISCRIMINATORS[{i}] `{a}` \
20452                     collides with StructuralKind::HASH_DISCRIMINATORS[{j}] \
20453                     `{b}` — the outer-Sexp Hash body's cache-key \
20454                     partition would route two structural-residual \
20455                     variants through the same slot"
20456                );
20457            }
20458        }
20459    }
20460
20461    #[test]
20462    fn structural_kind_label_matches_sexp_shape_label_via_composition() {
20463        // Composition-routing pin: for every `sk: StructuralKind`,
20464        // `sk.label()` and `sk.sexp_shape().label()` agree byte-for-
20465        // byte AND at the SAME `&'static str` pointer (the
20466        // composition returns the SAME literal `SexpShape::label`
20467        // emits, without a parallel arm-table on the subset). A
20468        // regression that re-inlines the two arms as a parallel
20469        // match-table (`Self::Nil => "nil"`, `Self::List => "list"`)
20470        // still passes the label-equality sweep but fails the pointer
20471        // pin — the residual-carving's label vocabulary is no longer
20472        // derived from the superset's canonical site. Sibling-shape
20473        // pin to `atom_kind_label_routes_through_sexp_shape_label_via_sexp_shape_projection`
20474        // (the 6-of-12 atomic-payload carving) and
20475        // `unquote_form_marker_routes_through_to_quote_form_prefix_via_composition`
20476        // (the 2-of-4 substitution-subset carving).
20477        for sk in StructuralKind::ALL {
20478            let from_direct = sk.label();
20479            let from_composition = sk.sexp_shape().label();
20480            assert_eq!(
20481                from_direct, from_composition,
20482                "StructuralKind::{sk:?}.label() drifted from .sexp_shape().label() — the residual carving's label vocabulary is no longer derived from the superset's canonical site",
20483            );
20484            assert!(
20485                std::ptr::eq(from_direct.as_ptr(), from_composition.as_ptr()),
20486                "StructuralKind::{sk:?}.label() and .sexp_shape().label() must point at the SAME `&'static str` — a parallel inline table has a different pointer",
20487            );
20488        }
20489    }
20490
20491    #[test]
20492    fn structural_kind_label_round_trips_through_from_str() {
20493        // `label` ↔ `FromStr` round-trip pin: for every `sk:
20494        // StructuralKind::ALL`, `sk.label().parse::<StructuralKind>()
20495        // == Ok(sk)`. The auto-derived `impl FromStr` (via
20496        // `#[derive(ClosedSet)]` + the default `parse_label`) sweeps
20497        // over `Self::ALL` keyed on `self.label()`, so a drift in
20498        // either direction (a label typo or a variant-set drift)
20499        // surfaces here. Sibling round-trip to
20500        // `sexp_shape_label_round_trips_through_from_str` and
20501        // `atom_kind_label_round_trips_through_from_str`.
20502        for sk in StructuralKind::ALL {
20503            let parsed = sk.label().parse::<StructuralKind>();
20504            assert_eq!(
20505                parsed,
20506                Ok(sk),
20507                "StructuralKind::{sk:?}.label() must round-trip through FromStr",
20508            );
20509        }
20510    }
20511
20512    #[test]
20513    fn structural_kind_from_str_rejects_non_structural_sexp_shape_labels() {
20514        // Cross-axis rejection pin: the `FromStr` decode on
20515        // StructuralKind MUST reject every `SexpShape` label that lies
20516        // OUTSIDE the residual carving (the ten `symbol` / `keyword` /
20517        // `string` / `int` / `float` / `bool` / `quote` / `quasiquote`
20518        // / `unquote` / `unquote-splice` labels — every atomic-payload
20519        // AND quote-family label). The two residual labels (`nil`,
20520        // `list`) accept because they lie INSIDE the carving; every
20521        // other `SexpShape` label rejects with `Err(UnknownStructuralKind)`.
20522        // Sibling of
20523        // `unquote_form_from_str_rejects_sexp_shape_labels_on_template_marker_axis`
20524        // and
20525        // `sexp_shape_from_str_accepts_only_canonical_labels` — the
20526        // pattern the substrate carries at every closed-set-carving
20527        // boundary.
20528        let residual_labels: Vec<&'static str> =
20529            StructuralKind::ALL.iter().map(|sk| sk.label()).collect();
20530        for shape in SexpShape::ALL {
20531            let label = shape.label();
20532            let parsed = label.parse::<StructuralKind>();
20533            if residual_labels.contains(&label) {
20534                assert!(
20535                    parsed.is_ok(),
20536                    "SexpShape::{shape:?}.label() = {label:?} is a residual label — StructuralKind::from_str must accept",
20537                );
20538            } else {
20539                assert!(
20540                    parsed.is_err(),
20541                    "SexpShape::{shape:?}.label() = {label:?} lies outside the residual carving — StructuralKind::from_str must reject",
20542                );
20543            }
20544        }
20545    }
20546
20547    #[test]
20548    fn structural_kind_is_well_formed_closed_set() {
20549        // Structural contract: StructuralKind's two variants are
20550        // pairwise distinct, round-trip through the trait's `label` ↔
20551        // `parse_label`, and reject the empty string — the
20552        // workspace-wide `assert_closed_set_well_formed::<T>()` testkit
20553        // pinned across every prior closed-set implementor
20554        // (`AtomKind`, `QuoteForm`, `UnquoteForm`, `SexpShape`,
20555        // `MacroDefHead`, `ExpectedKwargShape`, `KwargPathKind`,
20556        // `CompilerSpecIoStage`). The substrate-level assertion runs
20557        // on the auto-derived `impl ClosedSet for StructuralKind`
20558        // emitted by `#[derive(tatara_closed_set::DeriveClosedSet)]` — a
20559        // regression that drifts the `via = "label"` projection
20560        // (`"nil"` / `"list"`) or the variant listing forced through
20561        // `Self::ALL` (cardinality 2 = every structural-residual
20562        // outer shape) fails-loudly here.
20563        tatara_closed_set::assert_closed_set_well_formed::<StructuralKind>();
20564    }
20565
20566    #[test]
20567    fn structural_kind_per_role_labels_alias_sexp_shape_per_role_labels_byte_for_byte() {
20568        // ALIAS CONTRACT: pin every one of the two per-role `pub const
20569        // StructuralKind::*_LABEL` aliases equals the corresponding
20570        // `pub const SexpShape::*_LABEL` byte-for-byte — so the
20571        // StructuralKind ⊂ SexpShape marker-vocabulary containment
20572        // routes through the typed `pub const StructuralKind::V_LABEL:
20573        // &'static str = SexpShape::V_LABEL` alias chain rather than
20574        // through two independent literal-discipline sites. Sibling-
20575        // shape pin to
20576        // `atom_kind_per_role_labels_alias_sexp_shape_per_role_labels_byte_for_byte`
20577        // on the peer 6-of-12 atomic-payload carving — the two pins
20578        // together bind the two SexpShape sub-carvings' per-role
20579        // alias chains at the SAME shape (parent's canonical bytes on
20580        // the RHS, subset's alias on the LHS). A regression that
20581        // renames the SexpShape side without updating the
20582        // StructuralKind alias pointing at it fails-loudly here with
20583        // the exact axis identified (NIL / LIST); a regression that
20584        // re-inlines the StructuralKind constant to a fresh literal
20585        // still passes this pin but loses the alias-chain typing
20586        // (which is what
20587        // `structural_kind_label_arms_route_through_per_role_labels_for_every_variant`
20588        // + `structural_kind_labels_align_with_all_by_index` catch in
20589        // combination).
20590        assert_eq!(StructuralKind::NIL_LABEL, SexpShape::NIL_LABEL);
20591        assert_eq!(StructuralKind::LIST_LABEL, SexpShape::LIST_LABEL);
20592    }
20593
20594    #[test]
20595    fn structural_kind_label_arms_route_through_per_role_labels_for_every_variant() {
20596        // PATH-UNIFORMITY: `StructuralKind::V.label()` MUST equal the
20597        // per-role `pub const StructuralKind::V_LABEL` for every `v:
20598        // StructuralKind`. Pre-lift the two structural-residual marker
20599        // bytes were reachable through `StructuralKind::label` (the
20600        // composition `self.sexp_shape().label()` — routing into
20601        // `SexpShape::*_LABEL`) OR through direct
20602        // `SexpShape::*_LABEL` reach-across; post-lift each variant's
20603        // canonical bytes are reachable through the per-role
20604        // `StructuralKind::*_LABEL` alias too. Pin the byte-equality
20605        // between the runtime projection and the compile-time alias
20606        // so a regression that renames the alias without updating the
20607        // arm (or vice versa) fails-loudly at the exact axis.
20608        //
20609        // Sibling-shape pin to
20610        // `atom_kind_label_arms_route_through_per_role_labels_for_every_variant`
20611        // on the peer 6-of-12 atomic-payload carving — the two pins
20612        // together bind BOTH SexpShape sub-carvings' per-role alias
20613        // chains against their respective label projections.
20614        assert_eq!(StructuralKind::Nil.label(), StructuralKind::NIL_LABEL);
20615        assert_eq!(StructuralKind::List.label(), StructuralKind::LIST_LABEL);
20616    }
20617
20618    #[test]
20619    fn structural_kind_labels_has_expected_cardinality() {
20620        // Cardinality pin: `LABELS.len() == 2` matches `ALL.len()` so
20621        // a refactor that loosens the type to `&'static [&'static
20622        // str]` fails HERE (the `[_; 2]` slot cannot be sliced
20623        // silently), and a variant added to `ALL` without a matching
20624        // `LABELS` row fails the pair-arity gate at the array literal
20625        // itself before this test even runs. The pin doubles as an
20626        // operator-visible mark of the family's cardinality across
20627        // the substrate — two structural-residual markers, matching
20628        // the two-arm carving of the parent `SexpShape::LABELS` (the
20629        // residual subset of the twelve canonical outer-shape labels).
20630        assert_eq!(StructuralKind::LABELS.len(), 2);
20631        assert_eq!(StructuralKind::LABELS.len(), StructuralKind::ALL.len());
20632    }
20633
20634    #[test]
20635    fn structural_kind_labels_align_with_all_by_index() {
20636        // ALIGNMENT PIN: sweep `LABELS[i] == ALL[i].label()` so any
20637        // `zip(ALL, LABELS)` consumer reads a coherent (variant,
20638        // label) pair off ONE forced-arity array pair. The
20639        // declaration-order pin makes a family-wide consumer that
20640        // walks the ALL / LABELS pair in lockstep (an LSP completion
20641        // bar keyed on `StructuralKind::LABELS`, a Sekiban metric
20642        // emitter labeling `tatara_lisp_structural_shape_total{kind}`
20643        // by the per-index label) read one canonical (variant, bytes)
20644        // pair per slot rather than routing through per-consumer
20645        // paired-iteration. A regression that reorders LABELS without
20646        // also reordering ALL (or vice versa) fails-loudly at the
20647        // exact index that drifted.
20648        assert_eq!(StructuralKind::LABELS.len(), StructuralKind::ALL.len());
20649        for (i, kind) in StructuralKind::ALL.iter().enumerate() {
20650            assert_eq!(
20651                StructuralKind::LABELS[i],
20652                kind.label(),
20653                "StructuralKind::LABELS[{i}] `{lbl}` drifted from \
20654                 StructuralKind::ALL[{i}].label() `{via_variant}` — \
20655                 the canonical ALL ordering and the LABELS ordering \
20656                 must match element-wise",
20657                lbl = StructuralKind::LABELS[i],
20658                via_variant = kind.label(),
20659            );
20660        }
20661    }
20662
20663    #[test]
20664    fn structural_kind_labels_pairwise_distinct() {
20665        // 2x2 pairwise sweep so a collision between the two labels
20666        // (which would silently degrade two distinct structural-
20667        // residual markers to the SAME diagnostic bytes and violate
20668        // the closed-set FromStr round-trip) fails-loudly at the
20669        // exact pair. Distinctness is already enforced structurally
20670        // by `assert_closed_set_well_formed::<StructuralKind>()`
20671        // (clause 3), so this pin is a secondary guard focused on
20672        // the per-role `pub const` surface directly rather than the
20673        // runtime projection through the trait's default `labels()`.
20674        for (i, a) in StructuralKind::LABELS.iter().enumerate() {
20675            for (j, b) in StructuralKind::LABELS.iter().enumerate() {
20676                if i == j {
20677                    continue;
20678                }
20679                assert_ne!(
20680                    a, b,
20681                    "StructuralKind::LABELS[{i}] ({a:?}) collides with \
20682                     StructuralKind::LABELS[{j}] ({b:?}) — two distinct \
20683                     structural-residual markers cannot share \
20684                     diagnostic bytes",
20685                );
20686            }
20687        }
20688    }
20689
20690    #[test]
20691    fn structural_kind_labels_match_sexp_shape_labels_element_wise_via_alias_chain() {
20692        // CROSS-AXIS PIN: sweep `StructuralKind::LABELS[i] ==
20693        // StructuralKind::ALL[i].sexp_shape().label()` so the ALL
20694        // array projected through the [`Self::sexp_shape`] embed +
20695        // [`SexpShape::label`] composition agrees with LABELS byte-
20696        // for-byte at every index. The pin closes the alias-chain
20697        // identity at the family-wide array level: the LABELS array
20698        // is NOT a fresh literal table but the projection of ALL
20699        // through the composition, materialized once at declaration
20700        // time through the two per-role `pub const *_LABEL` aliases.
20701        // A regression that re-inlines LABELS to fresh literals still
20702        // passes the pairwise-distinct + cardinality + alignment
20703        // pins but drifts the alias chain — this pin catches that
20704        // drift by comparing against the composition on the SAME
20705        // ALL ordering.
20706        //
20707        // Sibling-shape pin to the peer 6-of-12 atomic-payload
20708        // carving's family-wide alias-chain pin (implicit in
20709        // `atom_kind_labels_align_with_all_by_index` +
20710        // `atom_kind_per_role_labels_alias_sexp_shape_per_role_labels_byte_for_byte`
20711        // together); this pin binds the 2-of-12 residual carving's
20712        // family-wide alias chain in ONE test at the family-wide
20713        // array level.
20714        for (i, kind) in StructuralKind::ALL.iter().enumerate() {
20715            assert_eq!(
20716                StructuralKind::LABELS[i],
20717                kind.sexp_shape().label(),
20718                "StructuralKind::LABELS[{i}] drifted from \
20719                 StructuralKind::ALL[{i}].sexp_shape().label() — the \
20720                 LABELS array must project through the sexp_shape + \
20721                 SexpShape::label composition byte-for-byte",
20722            );
20723        }
20724    }
20725
20726    #[test]
20727    fn structural_kind_per_role_shapes_pin_canonical_sexp_shape_variants() {
20728        // PER-ROLE ALIAS CONTRACT: each `StructuralKind::*_SHAPE` per-
20729        // role `pub const` binds byte-for-byte to its canonical
20730        // `SexpShape` variant on the StructuralKind ⊂ SexpShape 2-of-12
20731        // carving. Pin so a regression that swaps ONE alias (e.g.
20732        // re-aims `LIST_SHAPE` at `SexpShape::Nil`) surfaces at rustc
20733        // / test time rather than as a silent operator-facing
20734        // diagnostic drift at every consumer keyed on the typed embed
20735        // target. Sibling posture to
20736        // `structural_kind_per_role_labels_alias_sexp_shape_per_role_labels_byte_for_byte`
20737        // on the diagnostic label axis of the SAME closed set — this
20738        // pin is the peer on the `SexpShape` embed-target axis.
20739        assert_eq!(StructuralKind::NIL_SHAPE, SexpShape::Nil);
20740        assert_eq!(StructuralKind::LIST_SHAPE, SexpShape::List);
20741    }
20742
20743    #[test]
20744    fn structural_kind_shapes_has_expected_cardinality() {
20745        // CLOSED-SET CARDINALITY CONTRACT: `StructuralKind::SHAPES`
20746        // carries exactly TWO entries — one per variant in the
20747        // closed-set structural-residual carving. Runtime companion to
20748        // the `[SexpShape; 2]` type annotation's compile-time forced
20749        // arity. A regression that widens the array without adding a
20750        // matching per-role `*_SHAPE` alias would fail the
20751        // `[SexpShape; 2]` type check at rustc time; this runtime pin
20752        // catches a silent shrink or duplicate-arm coalesce. Sibling
20753        // posture to `structural_kind_labels_has_expected_cardinality`
20754        // and `structural_kind_hash_discriminators_has_expected_cardinality`
20755        // on the other two per-role axes of the SAME closed set.
20756        assert_eq!(StructuralKind::SHAPES.len(), 2);
20757        assert_eq!(StructuralKind::SHAPES.len(), StructuralKind::ALL.len());
20758    }
20759
20760    #[test]
20761    fn structural_kind_shapes_align_with_all_by_index() {
20762        // ALIGNMENT CONTRACT: `Self::SHAPES[i] ==
20763        // Self::ALL[i].sexp_shape()` element-wise. Pins that the typed
20764        // variant ALL and the `SexpShape` SHAPES ALL stay in lockstep
20765        // under any reorder — a regression that reorders ONE array
20766        // without reordering the other silently misaligns every
20767        // `zip(ALL, SHAPES)` consumer that wants to project each
20768        // structural-residual variant to its canonical outer-shape
20769        // identity (LSP completion, `tatara-check` predicate over the
20770        // structural ⊂ outer partition, Sekiban audit-trail metric
20771        // jointly labeled by the embed target). Sibling posture to
20772        // `structural_kind_labels_align_with_all_by_index` and
20773        // `structural_kind_hash_discriminators_align_with_all_by_index`
20774        // on the other two per-role axes of the SAME closed set.
20775        for (i, kind) in StructuralKind::ALL.iter().enumerate() {
20776            assert_eq!(
20777                StructuralKind::SHAPES[i],
20778                kind.sexp_shape(),
20779                "StructuralKind::SHAPES[{i}] `{shape:?}` drifted from \
20780                 StructuralKind::ALL[{i}] ({kind:?}).sexp_shape() \
20781                 `{via_variant:?}` — the canonical declaration order \
20782                 of the ALL array and the sexp_shape projection must \
20783                 match element-wise",
20784                shape = StructuralKind::SHAPES[i],
20785                via_variant = kind.sexp_shape(),
20786            );
20787        }
20788    }
20789
20790    #[test]
20791    fn structural_kind_shapes_align_with_all_by_index_through_as_structural_kind() {
20792        // ROUND-TRIP CONTRACT: `Self::SHAPES[i].as_structural_kind() ==
20793        // Some(Self::ALL[i])` element-wise. Pins the embed / project
20794        // section of the (`StructuralKind::sexp_shape`,
20795        // `SexpShape::as_structural_kind`) `Iso(StructuralKind,
20796        // StructuralShape ⊂ SexpShape)` as a family-wide array-indexed
20797        // law rather than as a per-variant assertion sweep. A
20798        // regression that drifts EITHER the `SHAPES` array entries OR
20799        // the peer inverse `as_structural_kind` arms silently breaks
20800        // the (embed, project) section — this pin catches both
20801        // directions at ONCE. Sibling posture to
20802        // `atom_kind_shapes_align_with_all_by_index_through_as_atom_kind`
20803        // on the peer 6-of-12 atomic-payload carving and
20804        // `quote_form_shapes_align_with_all_by_index_through_as_quote_form`
20805        // on the peer 4-of-12 quote-family carving — this pin closes
20806        // the third and final peer round-trip.
20807        for (i, kind) in StructuralKind::ALL.iter().enumerate() {
20808            assert_eq!(
20809                StructuralKind::SHAPES[i].as_structural_kind(),
20810                Some(*kind),
20811                "StructuralKind::SHAPES[{i}] `{shape:?}`.as_structural_kind() = \
20812                 {actual:?} drifted from Some(StructuralKind::ALL[{i}]) = \
20813                 Some({expected:?}) — the (SHAPES entry, ALL variant) \
20814                 round-trip through the peer inverse \
20815                 SexpShape::as_structural_kind must hold element-wise",
20816                shape = StructuralKind::SHAPES[i],
20817                actual = StructuralKind::SHAPES[i].as_structural_kind(),
20818                expected = kind,
20819            );
20820        }
20821    }
20822
20823    #[test]
20824    fn structural_kind_sexp_shape_routes_through_typed_per_role_constants() {
20825        // ROUTING CONTRACT: `StructuralKind::sexp_shape()`'s two arms
20826        // bind through the per-role `pub const *_SHAPE` aliases rather
20827        // than through inline `SexpShape::X` literals. Pin so a
20828        // regression that re-inlines the two literals here (and gains
20829        // its own drift surface separate from the canonical per-role
20830        // alias site) surfaces immediately at variant equality.
20831        // Sibling posture to
20832        // `structural_kind_label_arms_route_through_per_role_labels_for_every_variant`
20833        // (which pins label routing through the per-role LABEL aliases)
20834        // and `atom_kind_sexp_shape_routes_through_typed_per_role_constants`
20835        // / `quote_form_sexp_shape_routes_through_typed_per_role_constants`
20836        // on the peer 6-of-12 atomic-payload and 4-of-12 quote-family
20837        // carvings.
20838        assert_eq!(StructuralKind::Nil.sexp_shape(), StructuralKind::NIL_SHAPE);
20839        assert_eq!(
20840            StructuralKind::List.sexp_shape(),
20841            StructuralKind::LIST_SHAPE
20842        );
20843    }
20844
20845    #[test]
20846    fn structural_kind_shapes_pairwise_distinct() {
20847        // PAIRWISE DISJOINTNESS: every entry of the `SHAPES` array
20848        // must differ so the (StructuralKind variant, SexpShape embed
20849        // target) mapping stays injective — a collision would silently
20850        // route two distinct StructuralKind variants through the SAME
20851        // outer-shape identity, breaking the StructuralKind ⊂
20852        // SexpShape 2-of-12 carving. Family-wide sweep over `SHAPES ×
20853        // SHAPES` — supersedes any per-pair pin and picks up new embed
20854        // targets mechanically. Sibling posture to
20855        // `atom_kind_shapes_pairwise_distinct` and
20856        // `quote_form_shapes_pairwise_distinct` on the peer 6-of-12
20857        // atomic-payload and 4-of-12 quote-family carvings — this pin
20858        // closes the third and final peer pairwise-distinct sweep.
20859        for (i, a) in StructuralKind::SHAPES.iter().enumerate() {
20860            for (j, b) in StructuralKind::SHAPES.iter().enumerate() {
20861                if i == j {
20862                    continue;
20863                }
20864                assert_ne!(
20865                    a, b,
20866                    "StructuralKind::SHAPES[{i}] `{a:?}` collides with \
20867                     StructuralKind::SHAPES[{j}] `{b:?}` — the \
20868                     StructuralKind ⊂ SexpShape 2-of-12 carving would \
20869                     route two structural-residual variants through the \
20870                     same outer-shape identity"
20871                );
20872            }
20873        }
20874    }
20875
20876    #[test]
20877    fn sexp_shape_hash_discriminator_pins_legacy_outer_cache_key_bytes() {
20878        // CACHE-KEY CONTRACT (shape-level peer): the shape-level
20879        // `SexpShape::hash_discriminator` MUST project each of the twelve
20880        // outer-shape arms to the SAME outer cache-key byte the
20881        // corresponding `Sexp` variant surfaces through
20882        // `crate::ast::Sexp::hash_discriminator`. Pin the twelve-arm →
20883        // seven-byte collapse: Nil→0, {Symbol/Keyword/String/Int/Float/
20884        // Bool}→1 (the outer Atom marker byte; the per-atom-kind inner
20885        // byte lives INSIDE `Hash for Atom` via
20886        // `AtomKind::hash_discriminator`'s `{0..=5}`, NOT surfaced
20887        // through this shape-level method), List→2, Quote→3,
20888        // Quasiquote→4, Unquote→5, UnquoteSplice→6. Any regression that
20889        // renumbers a byte here silently drifts the outer-`Sexp` cache-
20890        // key partition since `Sexp::hash_discriminator` composes
20891        // through `self.shape().hash_discriminator()` post-lift.
20892        // Sibling posture to
20893        // `structural_kind_hash_discriminator_pins_legacy_cache_key_bytes`
20894        // on the 2-arm sub-carving, `crate::ast::AtomKind`'s
20895        // `atom_kind_hash_discriminator_pins_legacy_atom_cache_key_bytes`
20896        // on the 6-arm sub-carving, and
20897        // `crate::ast::QuoteForm`'s
20898        // `quote_form_hash_discriminator_pins_legacy_cache_key_bytes`
20899        // on the 4-arm sub-carving.
20900        assert_eq!(SexpShape::Nil.hash_discriminator(), 0);
20901        assert_eq!(SexpShape::Symbol.hash_discriminator(), 1);
20902        assert_eq!(SexpShape::Keyword.hash_discriminator(), 1);
20903        assert_eq!(SexpShape::String.hash_discriminator(), 1);
20904        assert_eq!(SexpShape::Int.hash_discriminator(), 1);
20905        assert_eq!(SexpShape::Float.hash_discriminator(), 1);
20906        assert_eq!(SexpShape::Bool.hash_discriminator(), 1);
20907        assert_eq!(SexpShape::List.hash_discriminator(), 2);
20908        assert_eq!(SexpShape::Quote.hash_discriminator(), 3);
20909        assert_eq!(SexpShape::Quasiquote.hash_discriminator(), 4);
20910        assert_eq!(SexpShape::Unquote.hash_discriminator(), 5);
20911        assert_eq!(SexpShape::UnquoteSplice.hash_discriminator(), 6);
20912    }
20913
20914    #[test]
20915    fn sexp_shape_hash_discriminator_covers_outer_partition_zero_through_six() {
20916        // PARTITION-COMPLETENESS CONTRACT (shape-level peer): the
20917        // 12-arm sweep of `SexpShape::ALL` through
20918        // `SexpShape::hash_discriminator` MUST cover exactly the outer
20919        // cache-key partition `{0..=6}` — no byte outside the outer
20920        // partition, no byte inside missing. The projection is
20921        // surjective onto `{0..=6}` (every outer byte reached by at
20922        // least one shape arm) with the atomic-shape collapse
20923        // ({Symbol/Keyword/String/Int/Float/Bool} → 1) covering the
20924        // outer Atom marker byte. Sibling posture to
20925        // `sexp_hash_discriminator_partitions_the_full_outer_discriminator_space_zero_through_six`
20926        // on the outer-`Sexp` algebra — that pin binds the outer-Sexp
20927        // partition; this pin binds the shape-level partition. Post-
20928        // lift the two partitions coincide byte-for-byte through
20929        // `Sexp::hash_discriminator` = `self.shape().hash_discriminator()`.
20930        let outer_bytes: std::collections::BTreeSet<u8> = SexpShape::ALL
20931            .iter()
20932            .map(|shape| shape.hash_discriminator())
20933            .collect();
20934        let expected: std::collections::BTreeSet<u8> = (0u8..=6u8).collect();
20935        assert_eq!(
20936            outer_bytes, expected,
20937            "SexpShape::hash_discriminator must cover exactly the outer discriminator space {{0..=6}}",
20938        );
20939    }
20940
20941    #[test]
20942    fn sexp_shape_hash_discriminator_atomic_arms_collapse_to_outer_atom_marker() {
20943        // OUTER-CARVING CONTRACT (shape-level atomic arm): every
20944        // `SexpShape` variant whose `as_atom_kind()` projection returns
20945        // `Some(_)` MUST project through `SexpShape::hash_discriminator`
20946        // to the SAME outer discriminator byte `1u8` — the atomic outer
20947        // arm is a single-byte marker on the outer partition, with the
20948        // per-atom-kind inner byte
20949        // (`crate::ast::AtomKind::hash_discriminator`'s `{0..=5}`)
20950        // nested INSIDE `Hash for Atom` — NOT surfaced through this
20951        // shape-level method. Pin the six-way collapse so a regression
20952        // that drifts ONE atomic-shape arm's outer routing (e.g. routes
20953        // `SexpShape::Int` to `7u8` inline) surfaces here immediately.
20954        // Sibling posture to
20955        // `sexp_hash_discriminator_atom_arm_collapses_over_every_atom_kind`
20956        // on the outer-`Sexp` algebra — that pin binds the outer-Sexp
20957        // atom-arm collapse; this pin binds the shape-level atomic-arm
20958        // collapse. Post-lift the two collapses coincide byte-for-byte
20959        // through `Sexp::hash_discriminator` =
20960        // `self.shape().hash_discriminator()`.
20961        for shape in SexpShape::ALL {
20962            if shape.as_atom_kind().is_some() {
20963                assert_eq!(
20964                    shape.hash_discriminator(),
20965                    crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR,
20966                    "SexpShape::{shape:?} is an atomic-payload shape but did not collapse to outer byte {marker}",
20967                    marker = crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR,
20968                );
20969            }
20970        }
20971    }
20972
20973    #[test]
20974    fn sexp_shape_hash_discriminator_atomic_arms_route_through_atom_kind_outer_hash_discriminator()
20975    {
20976        // PATH-UNIFORMITY (shape-level atomic-carve outer marker):
20977        // for every `SexpShape` variant whose `as_atom_kind()`
20978        // projection returns `Some(_)`, `SexpShape::hash_discriminator`
20979        // MUST bind byte-for-byte to
20980        // `crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR` — the
20981        // atomic-carve outer marker binds through the typed
20982        // substrate primitive rather than through an inline `1u8`
20983        // literal (which is what pre-lift wrote at the six-arm
20984        // atomic collapse arm). Pin the routing-identity across the
20985        // closed set so a regression that reverts the atomic
20986        // collapse to `Self::Symbol | ... | Self::Bool => 1` (or
20987        // drifts the routing byte silently while leaving the const
20988        // in place) fails-loudly here. Sibling posture to
20989        // `sexp_shape_hash_discriminator_structural_arms_route_through_structural_kind`
20990        // on the structural-residual sub-carving AND
20991        // `sexp_shape_hash_discriminator_quote_family_arms_route_through_quote_form`
20992        // on the quote-family sub-carving — every one of the three
20993        // shape-level carvings' six-plus-two-plus-four arms routes
20994        // through the typed substrate primitive on its owning
20995        // closed-set algebra rather than an inline `u8` literal
20996        // scattered across `SexpShape::hash_discriminator`. Together
20997        // with the two pre-existing companion pins this pin closes
20998        // the shape-level projection's TWELVE-ARM routing across the
20999        // FOUR typed byte primitives (three sub-algebra methods +
21000        // the outer-Atom scalar) at ONE byte-equality contract per
21001        // arm.
21002        //
21003        // The atomic-carve outer marker is a SCALAR (single byte),
21004        // NOT an ALL-array sweep like the other two carvings —
21005        // reflected in the assertion body: all six atomic-payload
21006        // shapes assert against the SAME
21007        // `AtomKind::OUTER_HASH_DISCRIMINATOR` constant rather than
21008        // zip against a `HASH_DISCRIMINATORS` array. This scalar-vs-
21009        // array asymmetry is intrinsic to the carve semantics: all
21010        // six atomic-payload arms of `SexpShape` COLLAPSE to the
21011        // SAME outer byte (the outer-Sexp distinguisher is
21012        // variant-level: `Sexp::Atom(_)` vs the six sibling `Sexp`
21013        // variants); per-atom-kind specialisation lives at the
21014        // NESTED INNER `AtomKind::HASH_DISCRIMINATORS` carve inside
21015        // `Hash for Atom`.
21016        for shape in SexpShape::ALL {
21017            if shape.as_atom_kind().is_some() {
21018                assert_eq!(
21019                    shape.hash_discriminator(),
21020                    crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR,
21021                    "SexpShape::{shape:?} atomic arm must route \
21022                     through the typed `AtomKind::OUTER_HASH_DISCRIMINATOR` \
21023                     rather than an inline `1u8` literal — the arm's \
21024                     projection `{proj}` diverged from the typed \
21025                     primitive `{expected}`",
21026                    proj = shape.hash_discriminator(),
21027                    expected = crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR,
21028                );
21029            }
21030        }
21031    }
21032
21033    #[test]
21034    fn atom_kind_outer_hash_discriminator_closes_outer_sexp_partition_with_structural_and_quote_form(
21035    ) {
21036        // JOINT PARTITION CONTRACT (four-way typed closure): the
21037        // outer-`Sexp` cache-key partition `{0..=6}` MUST close
21038        // exactly under the FOUR typed byte primitives — scalar
21039        // `AtomKind::OUTER_HASH_DISCRIMINATOR` (`{1}`), array
21040        // `StructuralKind::HASH_DISCRIMINATORS` (`{0, 2}`), array
21041        // `QuoteForm::HASH_DISCRIMINATORS` (`{3..=6}`). The union
21042        // covers exactly `{0..=6}` with NO overlap. Pre-lift the
21043        // partition closure was pinned INDIRECTLY through the
21044        // `structural_kind_hash_discriminator_disjoint_from_atom_outer_carve_byte_and_quote_form_hash_discriminator_partition`
21045        // sweep which used a duplicated local `const
21046        // ATOM_OUTER_CARVE_BYTE: u8 = 1` fixture for the atom side;
21047        // post-lift THIS pin binds the four-way closure DIRECTLY
21048        // through the typed substrate primitives. A regression that
21049        // renumbers `AtomKind::OUTER_HASH_DISCRIMINATOR` into
21050        // `{0, 2}` (StructuralKind's space) or `{3..=6}`
21051        // (QuoteForm's space) fails-loudly at THIS test as a
21052        // partition-collision rather than at a downstream cache
21053        // mis-hash. Sibling posture to
21054        // `structural_kind_hash_discriminator_disjoint_from_atom_outer_carve_byte_and_quote_form_hash_discriminator_partition`
21055        // — that pin sweeps the three-carving-plus-atom-marker
21056        // union bottom-up from `StructuralKind`; this pin closes
21057        // the same partition top-down from `AtomKind`'s outer scalar
21058        // as the sole SCALAR entry alongside the two ARRAY entries.
21059        // Together the two pins bind the four-way closure at BOTH
21060        // directions.
21061        let atomic: std::collections::BTreeSet<u8> =
21062            std::iter::once(crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR).collect();
21063        let structural: std::collections::BTreeSet<u8> = StructuralKind::HASH_DISCRIMINATORS
21064            .iter()
21065            .copied()
21066            .collect();
21067        let quote_family: std::collections::BTreeSet<u8> =
21068            crate::ast::QuoteForm::HASH_DISCRIMINATORS
21069                .iter()
21070                .copied()
21071                .collect();
21072        assert!(
21073            atomic.is_disjoint(&structural),
21074            "atomic-carve outer marker `{atomic:?}` and structural-carve \
21075             partition `{structural:?}` MUST be disjoint on the outer-Sexp \
21076             cache-key space `{{0..=6}}`",
21077        );
21078        assert!(
21079            atomic.is_disjoint(&quote_family),
21080            "atomic-carve outer marker `{atomic:?}` and quote-family carve \
21081             partition `{quote_family:?}` MUST be disjoint on the outer-Sexp \
21082             cache-key space `{{0..=6}}`",
21083        );
21084        assert!(
21085            structural.is_disjoint(&quote_family),
21086            "structural-carve partition `{structural:?}` and quote-family \
21087             carve partition `{quote_family:?}` MUST be disjoint on the \
21088             outer-Sexp cache-key space `{{0..=6}}`",
21089        );
21090        let union: std::collections::BTreeSet<u8> = atomic
21091            .iter()
21092            .chain(structural.iter())
21093            .chain(quote_family.iter())
21094            .copied()
21095            .collect();
21096        let expected: std::collections::BTreeSet<u8> = (0u8..=6u8).collect();
21097        assert_eq!(
21098            union, expected,
21099            "the four-way typed closure `atomic ∪ structural ∪ quote_family` \
21100             = `{union:?}` MUST equal exactly the outer-Sexp cache-key \
21101             partition `{{0..=6}}` — a regression that drifts ANY of the four \
21102             primitives into an outer byte outside `{{0..=6}}` OR leaves \
21103             ANY outer byte unclaimed fails-loudly here",
21104        );
21105    }
21106
21107    #[test]
21108    fn sexp_shape_hash_discriminator_structural_arms_route_through_structural_kind() {
21109        // COMPOSITION-ROUTING CONTRACT (shape-level structural-residual
21110        // arm): for every `SexpShape` variant whose `as_structural_kind()`
21111        // projection returns `Some(sk)`, `SexpShape::hash_discriminator`
21112        // MUST agree byte-for-byte with `sk.hash_discriminator()` — the
21113        // two structural-residual arms (`Nil`, `List`) DELEGATE to the
21114        // typed sub-carving rather than inline two byte literals. Pin
21115        // the delegation-identity across the closed set so a regression
21116        // that inlines a byte at ONE arm (e.g. routes `SexpShape::Nil`
21117        // to `7u8` inline instead of through
21118        // `StructuralKind::Nil.hash_discriminator()`) fails-loudly here
21119        // — it would type-check but silently drift if the sub-algebra's
21120        // byte is renumbered. Sibling posture to the outer-`Sexp` axis
21121        // where `Sexp::Nil` / `Sexp::List(_)` also compose through
21122        // `StructuralKind::hash_discriminator` (pre-lift on that axis;
21123        // post-lift the same composition emerges through `self.shape()`
21124        // routing).
21125        for shape in SexpShape::ALL {
21126            if let Some(sk) = shape.as_structural_kind() {
21127                assert_eq!(
21128                    shape.hash_discriminator(),
21129                    sk.hash_discriminator(),
21130                    "SexpShape::{shape:?} structural arm must delegate to StructuralKind::{sk:?}.hash_discriminator()",
21131                );
21132            }
21133        }
21134    }
21135
21136    #[test]
21137    fn sexp_shape_hash_discriminator_quote_family_arms_route_through_quote_form() {
21138        // COMPOSITION-ROUTING CONTRACT (shape-level quote-family arm):
21139        // for every `SexpShape` variant whose `as_quote_form()`
21140        // projection returns `Some(qf)`, `SexpShape::hash_discriminator`
21141        // MUST agree byte-for-byte with `qf.hash_discriminator()` — the
21142        // four quote-family arms DELEGATE to the sub-algebra's
21143        // discriminator method rather than inline four literals. Pin the
21144        // delegation-identity across the closed set so a regression that
21145        // inlines a byte at ONE arm (e.g. routes
21146        // `SexpShape::UnquoteSplice` to `6u8` inline instead of through
21147        // `QuoteForm::UnquoteSplice.hash_discriminator()`) fails-loudly
21148        // here — it would type-check but silently drift if the sub-
21149        // algebra's byte is renumbered. Sibling posture to
21150        // `sexp_hash_discriminator_quote_arm_delegates_to_quote_form_hash_discriminator`
21151        // on the outer-`Sexp` algebra — that pin binds the outer-Sexp
21152        // quote-arm delegation; this pin binds the shape-level quote-
21153        // family delegation.
21154        for shape in SexpShape::ALL {
21155            if let Some(qf) = shape.as_quote_form() {
21156                assert_eq!(
21157                    shape.hash_discriminator(),
21158                    qf.hash_discriminator(),
21159                    "SexpShape::{shape:?} quote-family arm must delegate to QuoteForm::{qf:?}.hash_discriminator()",
21160                );
21161            }
21162        }
21163    }
21164
21165    #[test]
21166    fn sexp_shape_hash_discriminator_partitions_by_three_way_carving_disjointly() {
21167        // THREE-WAY CARVING PARTITION CONTRACT: for every `SexpShape`
21168        // variant, EXACTLY ONE of the three closed-set carvings
21169        // (`as_atom_kind`, `as_quote_form`, `as_structural_kind`)
21170        // projects to `Some(_)`, AND the outer discriminator byte the
21171        // shape-level `hash_discriminator` returns partitions across
21172        // the three carvings' image spaces: atomic → `{1}` (outer Atom
21173        // marker), quote-family → `{3, 4, 5, 6}`, structural → `{0, 2}`.
21174        // A regression that drifts a carving assignment (e.g. routes
21175        // `SexpShape::Nil` through `as_atom_kind()` due to a bogus
21176        // typed-shape lattice edit) fails-loudly here — the partition
21177        // completeness is a compile-time-verified theorem across the
21178        // three carvings via rustc's exhaustiveness on each carving's
21179        // `match`. Sibling posture to
21180        // `structural_kind_hash_discriminator_disjoint_from_atom_outer_carve_byte_and_quote_form_hash_discriminator_partition`
21181        // — that pin binds the sub-carving byte-space disjointness;
21182        // this pin binds the shape-level three-way carving assignment
21183        // AND its image-space partition.
21184        let atomic_image: std::collections::BTreeSet<u8> = SexpShape::ALL
21185            .iter()
21186            .filter(|s| s.as_atom_kind().is_some())
21187            .map(|s| s.hash_discriminator())
21188            .collect();
21189        let quote_image: std::collections::BTreeSet<u8> = SexpShape::ALL
21190            .iter()
21191            .filter(|s| s.as_quote_form().is_some())
21192            .map(|s| s.hash_discriminator())
21193            .collect();
21194        let structural_image: std::collections::BTreeSet<u8> = SexpShape::ALL
21195            .iter()
21196            .filter(|s| s.as_structural_kind().is_some())
21197            .map(|s| s.hash_discriminator())
21198            .collect();
21199        let expected_atomic: std::collections::BTreeSet<u8> =
21200            std::iter::once(crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR).collect();
21201        let expected_quote: std::collections::BTreeSet<u8> = (3u8..=6u8).collect();
21202        let expected_structural: std::collections::BTreeSet<u8> = [0u8, 2u8].into_iter().collect();
21203        assert_eq!(
21204            atomic_image, expected_atomic,
21205            "the atomic-shape carving's `hash_discriminator` image must be {{1}} (the outer Atom marker byte)",
21206        );
21207        assert_eq!(
21208            quote_image, expected_quote,
21209            "the quote-family carving's `hash_discriminator` image must be {{3, 4, 5, 6}}",
21210        );
21211        assert_eq!(
21212            structural_image, expected_structural,
21213            "the structural-residual carving's `hash_discriminator` image must be {{0, 2}}",
21214        );
21215        assert!(
21216            atomic_image.is_disjoint(&quote_image),
21217            "atomic and quote-family carvings' `hash_discriminator` images must be disjoint",
21218        );
21219        assert!(
21220            atomic_image.is_disjoint(&structural_image),
21221            "atomic and structural-residual carvings' `hash_discriminator` images must be disjoint",
21222        );
21223        assert!(
21224            quote_image.is_disjoint(&structural_image),
21225            "quote-family and structural-residual carvings' `hash_discriminator` images must be disjoint",
21226        );
21227        let mut union: std::collections::BTreeSet<u8> = atomic_image;
21228        union.extend(&quote_image);
21229        union.extend(&structural_image);
21230        let full_outer: std::collections::BTreeSet<u8> = (0u8..=6u8).collect();
21231        assert_eq!(
21232            union, full_outer,
21233            "the three-carving `hash_discriminator` image union must exactly cover {{0..=6}}",
21234        );
21235    }
21236
21237    #[test]
21238    fn sexp_shape_hash_discriminators_pin_legacy_outer_cache_key_bytes() {
21239        // Pin each per-role `pub(crate) const *_HASH_DISCRIMINATOR` on
21240        // `SexpShape` at its exact canonical outer-`Sexp` cache-key byte
21241        // — sibling of the pre-existing
21242        // `sexp_shape_hash_discriminator_pins_legacy_outer_cache_key_bytes`
21243        // (which pins the projection method's returns). Together the two
21244        // pins close the (per-role const, projection method) pairing on
21245        // the outer-shape algebra. Post-lift the twelve per-role bytes
21246        // live at ONE `pub(crate) const` per role on the `SexpShape`
21247        // algebra rather than at twelve inline `u8` literals scattered
21248        // across the projection method's match arms.
21249        assert_eq!(SexpShape::NIL_HASH_DISCRIMINATOR, 0);
21250        assert_eq!(SexpShape::SYMBOL_HASH_DISCRIMINATOR, 1);
21251        assert_eq!(SexpShape::KEYWORD_HASH_DISCRIMINATOR, 1);
21252        assert_eq!(SexpShape::STRING_HASH_DISCRIMINATOR, 1);
21253        assert_eq!(SexpShape::INT_HASH_DISCRIMINATOR, 1);
21254        assert_eq!(SexpShape::FLOAT_HASH_DISCRIMINATOR, 1);
21255        assert_eq!(SexpShape::BOOL_HASH_DISCRIMINATOR, 1);
21256        assert_eq!(SexpShape::LIST_HASH_DISCRIMINATOR, 2);
21257        assert_eq!(SexpShape::QUOTE_HASH_DISCRIMINATOR, 3);
21258        assert_eq!(SexpShape::QUASIQUOTE_HASH_DISCRIMINATOR, 4);
21259        assert_eq!(SexpShape::UNQUOTE_HASH_DISCRIMINATOR, 5);
21260        assert_eq!(SexpShape::UNQUOTE_SPLICE_HASH_DISCRIMINATOR, 6);
21261    }
21262
21263    #[test]
21264    fn sexp_shape_hash_discriminator_routes_through_typed_per_role_constants() {
21265        // PATH-UNIFORMITY (shape-level per-role): each of the twelve arms
21266        // of `SexpShape::hash_discriminator` MUST return the corresponding
21267        // `SexpShape::*_HASH_DISCRIMINATOR` byte-for-byte — catches a
21268        // regression that reverts ONE arm to an inline `u8` literal OR
21269        // to a `.hash_discriminator()` runtime dispatch through a sub-
21270        // carving variant (both of which type-check but bypass the typed
21271        // per-role primitive). Sibling posture to
21272        // `atom_kind_hash_discriminator_routes_through_typed_per_role_constants`,
21273        // `quote_form_hash_discriminator_routes_through_typed_per_role_constants`,
21274        // and `structural_kind_hash_discriminator_routes_through_typed_per_role_constants`
21275        // on the three sub-carvings — this pin closes the same routing
21276        // contract at the outer twelve-arm shape level.
21277        let pairs = [
21278            (SexpShape::Nil, SexpShape::NIL_HASH_DISCRIMINATOR),
21279            (SexpShape::Symbol, SexpShape::SYMBOL_HASH_DISCRIMINATOR),
21280            (SexpShape::Keyword, SexpShape::KEYWORD_HASH_DISCRIMINATOR),
21281            (SexpShape::String, SexpShape::STRING_HASH_DISCRIMINATOR),
21282            (SexpShape::Int, SexpShape::INT_HASH_DISCRIMINATOR),
21283            (SexpShape::Float, SexpShape::FLOAT_HASH_DISCRIMINATOR),
21284            (SexpShape::Bool, SexpShape::BOOL_HASH_DISCRIMINATOR),
21285            (SexpShape::List, SexpShape::LIST_HASH_DISCRIMINATOR),
21286            (SexpShape::Quote, SexpShape::QUOTE_HASH_DISCRIMINATOR),
21287            (
21288                SexpShape::Quasiquote,
21289                SexpShape::QUASIQUOTE_HASH_DISCRIMINATOR,
21290            ),
21291            (SexpShape::Unquote, SexpShape::UNQUOTE_HASH_DISCRIMINATOR),
21292            (
21293                SexpShape::UnquoteSplice,
21294                SexpShape::UNQUOTE_SPLICE_HASH_DISCRIMINATOR,
21295            ),
21296        ];
21297        for (shape, expected) in pairs {
21298            assert_eq!(
21299                shape.hash_discriminator(),
21300                expected,
21301                "SexpShape::hash_discriminator arm for {shape:?} must route through the typed \
21302                 `pub(crate) const` per-role byte",
21303            );
21304        }
21305    }
21306
21307    #[test]
21308    fn sexp_shape_hash_discriminators_has_expected_cardinality() {
21309        // Cardinality contract: `Self::HASH_DISCRIMINATORS.len() == 12`
21310        // matching `Self::ALL.len() == 12`. Arity is forced at the
21311        // declaration site by rustc's `[u8; 12]` check; this runtime pin
21312        // fails-loud so a future refactor that switches the array type
21313        // to `&[u8]` doesn't silently loosen the closed-set discipline.
21314        // Sibling posture to `sexp_shape_labels_has_expected_cardinality`
21315        // on the diagnostic-label axis of the same twelve-variant closed
21316        // set.
21317        assert_eq!(SexpShape::HASH_DISCRIMINATORS.len(), 12);
21318        assert_eq!(
21319            SexpShape::HASH_DISCRIMINATORS.len(),
21320            SexpShape::ALL.len(),
21321            "SexpShape::HASH_DISCRIMINATORS cardinality drifted from SexpShape::ALL",
21322        );
21323    }
21324
21325    #[test]
21326    fn sexp_shape_hash_discriminators_align_with_all_by_index() {
21327        // ALIGNMENT CONTRACT: `Self::HASH_DISCRIMINATORS[i] ==
21328        // Self::ALL[i].hash_discriminator()` element-wise — the ALL
21329        // array's variant order MUST align with the HASH_DISCRIMINATORS
21330        // array's byte order. A regression that reorders ONE array
21331        // without reordering the other silently misaligns every
21332        // `zip(ALL, HASH_DISCRIMINATORS)` consumer. Sibling posture to
21333        // `structural_kind_hash_discriminators_align_with_all_by_index`,
21334        // `quote_form_hash_discriminators_align_with_all_by_index`, and
21335        // `atom_kind_hash_discriminators_align_with_all_by_index` on the
21336        // three sub-carvings — this pin closes the same alignment
21337        // contract at the outer twelve-arm shape level.
21338        for (i, shape) in SexpShape::ALL.iter().copied().enumerate() {
21339            let disc = shape.hash_discriminator();
21340            assert_eq!(
21341                SexpShape::HASH_DISCRIMINATORS[i],
21342                disc,
21343                "SexpShape::HASH_DISCRIMINATORS[{i}] `{disc}` != \
21344                 SexpShape::ALL[{i}].hash_discriminator() `{disc}` — array-vs-projection drift",
21345            );
21346        }
21347    }
21348
21349    #[test]
21350    fn sexp_shape_hash_discriminators_cover_outer_partition_zero_through_six() {
21351        // PARTITION-COMPLETENESS CONTRACT (array level): the twelve-byte
21352        // set of `Self::HASH_DISCRIMINATORS` MUST cover exactly the
21353        // outer cache-key partition `{0..=6}` — no byte outside, no byte
21354        // inside missing. Sibling to
21355        // `sexp_shape_hash_discriminator_covers_outer_partition_zero_through_six`
21356        // (which closes the same partition through the projection method)
21357        // — this pin closes it at the ARRAY level so a regression that
21358        // drifts the array's contents without changing the method
21359        // surfaces here rather than only through the projection sweep.
21360        let bytes: std::collections::BTreeSet<u8> =
21361            SexpShape::HASH_DISCRIMINATORS.iter().copied().collect();
21362        let expected: std::collections::BTreeSet<u8> = (0u8..=6u8).collect();
21363        assert_eq!(
21364            bytes, expected,
21365            "SexpShape::HASH_DISCRIMINATORS must cover exactly the outer discriminator space {{0..=6}}",
21366        );
21367    }
21368
21369    #[test]
21370    fn sexp_shape_hash_discriminators_align_with_typed_per_role_constants_by_index() {
21371        // PER-ROLE ROUTING CONTRACT (family-wide array level): for every
21372        // `i in 0..12`, `SexpShape::HASH_DISCRIMINATORS[i]` MUST equal the
21373        // corresponding twelve-arm `SexpShape::*_HASH_DISCRIMINATOR`
21374        // per-role `pub(crate) const` at the SAME position in
21375        // `SexpShape::ALL`. Catches a regression that inlines byte
21376        // LITERALS in the `HASH_DISCRIMINATORS` array definition (e.g.
21377        // rewrites `[Self::NIL_HASH_DISCRIMINATOR, Self::SYMBOL_HASH_DISCRIMINATOR,
21378        // ...]` to `[0u8, 1u8, ...]`) — that regression would still
21379        // byte-equal the outer partition `{0..=6}`, still byte-equal the
21380        // sub-carving projections, and still byte-equal
21381        // `SexpShape::ALL[i].hash_discriminator()`, so every existing pin
21382        // (`_pin_legacy_outer_cache_key_bytes`, `_align_with_all_by_index`,
21383        // `_cover_outer_partition_zero_through_six`,
21384        // `_align_with_sub_carvings_by_projection`) would still PASS while
21385        // the FAMILY-WIDE array silently DRIFTED from the per-role
21386        // primitives it aliases through. Pin the alias-identity across
21387        // the twelve positions so the outer-shape ALL-array binds to the
21388        // per-role sources of truth at rustc-time PLUS runtime by ONE
21389        // typed pairing per position.
21390        //
21391        // Sibling posture to `sexp_shape_hash_discriminator_routes_through_typed_per_role_constants`
21392        // on the METHOD-side per-role routing axis — that pin binds the
21393        // twelve-arm projection method's per-role routing; this pin binds
21394        // the twelve-position family-wide array's per-role routing. Together
21395        // the two pins close the per-role routing contract at BOTH the
21396        // METHOD-side and the ARRAY-side of the shape algebra, in the SAME
21397        // shape as the sibling `SexpShape::LABELS` axis's
21398        // `sexp_shape_labels_align_with_all_by_index` pin (via
21399        // `SexpShape::ALL[i].label()`) — both axes now pin BOTH sides of
21400        // the (method, array) pair to the SAME per-role source of truth.
21401        let per_role = [
21402            SexpShape::NIL_HASH_DISCRIMINATOR,
21403            SexpShape::SYMBOL_HASH_DISCRIMINATOR,
21404            SexpShape::KEYWORD_HASH_DISCRIMINATOR,
21405            SexpShape::STRING_HASH_DISCRIMINATOR,
21406            SexpShape::INT_HASH_DISCRIMINATOR,
21407            SexpShape::FLOAT_HASH_DISCRIMINATOR,
21408            SexpShape::BOOL_HASH_DISCRIMINATOR,
21409            SexpShape::LIST_HASH_DISCRIMINATOR,
21410            SexpShape::QUOTE_HASH_DISCRIMINATOR,
21411            SexpShape::QUASIQUOTE_HASH_DISCRIMINATOR,
21412            SexpShape::UNQUOTE_HASH_DISCRIMINATOR,
21413            SexpShape::UNQUOTE_SPLICE_HASH_DISCRIMINATOR,
21414        ];
21415        assert_eq!(
21416            per_role.len(),
21417            SexpShape::HASH_DISCRIMINATORS.len(),
21418            "the twelve per-role SexpShape::*_HASH_DISCRIMINATOR constants \
21419             must cover the same cardinality as SexpShape::HASH_DISCRIMINATORS",
21420        );
21421        for (i, expected) in per_role.iter().copied().enumerate() {
21422            assert_eq!(
21423                SexpShape::HASH_DISCRIMINATORS[i],
21424                expected,
21425                "SexpShape::HASH_DISCRIMINATORS[{i}] `{actual}` drifted from \
21426                 the per-role SexpShape::*_HASH_DISCRIMINATOR constant `{expected}` \
21427                 for {shape:?} — the family-wide array MUST alias through the \
21428                 per-role source of truth rather than inline a byte literal",
21429                actual = SexpShape::HASH_DISCRIMINATORS[i],
21430                shape = SexpShape::ALL[i],
21431            );
21432        }
21433    }
21434
21435    #[test]
21436    fn sexp_shape_hash_discriminators_align_with_sub_carvings_by_projection() {
21437        // CROSS-ALGEBRA COMPOSITION CONTRACT: for every `i in 0..12`,
21438        // `Self::HASH_DISCRIMINATORS[i]` MUST equal the byte the SUB-
21439        // CARVING projection returns for `Self::ALL[i]` — i.e. the
21440        // outer-shape ALL-array byte at every position is byte-identical
21441        // to (a) `AtomKind::OUTER_HASH_DISCRIMINATOR` on the atomic-
21442        // payload variants, (b) `StructuralKind::HASH_DISCRIMINATORS`'s
21443        // byte on structural-residual variants (routed via
21444        // `as_structural_kind`), and (c)
21445        // `QuoteForm::HASH_DISCRIMINATORS`'s byte on quote-family
21446        // variants (routed via `as_quote_form`). Closes the load-
21447        // bearing invariant that the outer-shape peer alias equals the
21448        // sub-carving's canonical byte at rustc-time through the `const`
21449        // initializer. A regression that drifts ONE sub-carving's byte
21450        // without updating the outer-shape peer alias fails to compile
21451        // (the `const` initializer references the same primitive); a
21452        // regression that drifts the OUTER alias byte-order in the
21453        // array fails-loudly here.
21454        for (i, shape) in SexpShape::ALL.iter().copied().enumerate() {
21455            let expected = if shape.as_atom_kind().is_some() {
21456                crate::ast::AtomKind::OUTER_HASH_DISCRIMINATOR
21457            } else if let Some(sk) = shape.as_structural_kind() {
21458                sk.hash_discriminator()
21459            } else if let Some(qf) = shape.as_quote_form() {
21460                qf.hash_discriminator()
21461            } else {
21462                unreachable!(
21463                    "SexpShape::{shape:?} lies outside all three sub-carvings — \
21464                     the three-carving partition (Atom + Structural + Quote) is not exhaustive",
21465                );
21466            };
21467            assert_eq!(
21468                SexpShape::HASH_DISCRIMINATORS[i], expected,
21469                "SexpShape::HASH_DISCRIMINATORS[{i}] must equal the sub-carving projection for {shape:?}",
21470            );
21471        }
21472    }
21473
21474    #[test]
21475    fn sexp_shape_iac_forge_tag_pins_canonical_cl_tags_for_every_quote_family_arm() {
21476        // CANONICAL-TAG CONTRACT (shape-level peer): the shape-level
21477        // `SexpShape::iac_forge_tag` MUST project each of the four
21478        // quote-family arms to the SAME canonical Common-Lisp tag
21479        // string `crate::ast::QuoteForm::iac_forge_tag` projects at the
21480        // sub-carving level — `Quote → "quote"`, `Quasiquote →
21481        // "quasiquote"`, `Unquote → "unquote"`, `UnquoteSplice →
21482        // "unquote-splicing"`. A regression that inlines a byte-drifted
21483        // spelling here (e.g. `SexpShape::UnquoteSplice → Some(
21484        // "unquote-splice")` conflating the substrate's shorter
21485        // diagnostic label with the CL canonical form) silently breaks
21486        // every cross-crate iac-forge consumer keyed on `(unquote-
21487        // splicing ...)`. Sibling posture to
21488        // `quote_form_iac_forge_tag_pins_canonical_lisp_tag_strings_for_every_variant`
21489        // (in `crate::ast`) on the 4-arm sub-carving — that pin binds
21490        // the sub-carving's canonical tag surface; this pin binds the
21491        // shape-level projection's canonical tag surface AND its
21492        // Option-partial shape.
21493        assert_eq!(SexpShape::Quote.iac_forge_tag(), Some("quote"));
21494        assert_eq!(SexpShape::Quasiquote.iac_forge_tag(), Some("quasiquote"));
21495        assert_eq!(SexpShape::Unquote.iac_forge_tag(), Some("unquote"));
21496        assert_eq!(
21497            SexpShape::UnquoteSplice.iac_forge_tag(),
21498            Some("unquote-splicing"),
21499        );
21500    }
21501
21502    #[test]
21503    fn sexp_shape_iac_forge_tag_returns_none_on_every_non_quote_family_shape() {
21504        // PARTIAL-PROJECTION KERNEL CONTRACT (shape-level peer): every
21505        // `SexpShape` variant OUTSIDE the four-arm quote-family carving
21506        // MUST project through `SexpShape::iac_forge_tag` to `None` —
21507        // the eight-shape kernel coincides byte-for-byte with the
21508        // kernel of `SexpShape::as_quote_form`. Pin the eight-shape
21509        // sweep (six atomic-payload variants + `Nil` + `List`) so a
21510        // regression that surfaces a bogus tag for a non-quote-family
21511        // arm (e.g. `SexpShape::List → Some("list")` conflating the
21512        // outer-shape diagnostic label with the quote-family canonical
21513        // form) fails-loudly here. Sibling posture to the projection's
21514        // parent-carving `sexp_shape_as_quote_form` returning `None`
21515        // on the eight-shape kernel.
21516        assert_eq!(SexpShape::Nil.iac_forge_tag(), None);
21517        assert_eq!(SexpShape::Symbol.iac_forge_tag(), None);
21518        assert_eq!(SexpShape::Keyword.iac_forge_tag(), None);
21519        assert_eq!(SexpShape::String.iac_forge_tag(), None);
21520        assert_eq!(SexpShape::Int.iac_forge_tag(), None);
21521        assert_eq!(SexpShape::Float.iac_forge_tag(), None);
21522        assert_eq!(SexpShape::Bool.iac_forge_tag(), None);
21523        assert_eq!(SexpShape::List.iac_forge_tag(), None);
21524    }
21525
21526    #[test]
21527    fn sexp_shape_iac_forge_tag_composes_through_as_quote_form_for_every_variant() {
21528        // COMPOSITION-LAW CONTRACT (shape-level peer):
21529        // `SexpShape::iac_forge_tag() ==
21530        // SexpShape::as_quote_form().map(QuoteForm::iac_forge_tag)` for
21531        // every variant in `SexpShape::ALL`. The (outer shape, canonical
21532        // iac-forge tag) pairing binds through the pre-existing quote-
21533        // family carving composed with the closed-set tag projection
21534        // rather than at a parallel four-arm inline match — this pin
21535        // enforces the composition-identity across the closed twelve-arm
21536        // sweep so a regression that drifts ONE arm's inline projection
21537        // from the composition (e.g. re-inlining `SexpShape::Quote →
21538        // Some("quote-old")` without updating `QuoteForm::iac_forge_tag`)
21539        // fails-loudly here. Sibling posture to
21540        // `sexp_shape_hash_discriminator_quote_family_arms_route_through_quote_form`
21541        // (delegation-identity on the byte-discriminator surface) — that
21542        // pin binds the sub-carving byte routing; this pin binds the
21543        // sub-carving tag routing.
21544        for shape in SexpShape::ALL {
21545            let via_method = shape.iac_forge_tag();
21546            let via_composition = shape
21547                .as_quote_form()
21548                .map(crate::ast::QuoteForm::iac_forge_tag);
21549            assert_eq!(
21550                via_method, via_composition,
21551                "SexpShape::{shape:?}.iac_forge_tag() drifted from \
21552                 .as_quote_form().map(QuoteForm::iac_forge_tag) — the shape-level tag projection is no longer derived from the sub-carving's canonical site",
21553            );
21554        }
21555    }
21556
21557    #[test]
21558    fn sexp_shape_iac_forge_tag_partitions_quote_family_and_kernel_disjointly() {
21559        // IMAGE-PARTITION CONTRACT (shape-level peer): sweeping
21560        // `SexpShape::ALL` through `SexpShape::iac_forge_tag` MUST
21561        // partition into EXACTLY the four-arm quote-family image (four
21562        // distinct canonical CL tag strings — the pre-image of `Some(_)`)
21563        // AND the eight-shape kernel (all `None` — six atomic-payload
21564        // variants + `Nil` + `List`). The image's `is_some()` count
21565        // MUST be four (surjective onto the four-tag closed set), the
21566        // kernel's `is_none()` count MUST be eight, and the sum MUST
21567        // hit `SexpShape::ALL.len()` (twelve) — a regression that leaks
21568        // a bogus `Some(_)` from a non-quote-family arm or drops a
21569        // `Some(_)` from a quote-family arm fails-loudly here on the
21570        // partition-cardinality axis before any downstream iac-forge
21571        // consumer would surface the drift. Sibling posture to the
21572        // three-way carving `sexp_shape_hash_discriminator_partitions_by_three_way_carving_disjointly`
21573        // pinning the byte-image partition; this pin binds the shape-
21574        // level tag-image partition on the four-arm quote-family
21575        // carving.
21576        let tag_image: std::collections::BTreeSet<&'static str> = SexpShape::ALL
21577            .iter()
21578            .filter_map(|shape| shape.iac_forge_tag())
21579            .collect();
21580        let expected_tag_image: std::collections::BTreeSet<&'static str> =
21581            ["quote", "quasiquote", "unquote", "unquote-splicing"]
21582                .into_iter()
21583                .collect();
21584        assert_eq!(
21585            tag_image, expected_tag_image,
21586            "SexpShape::iac_forge_tag image must exactly cover the four canonical CL quote-family tags",
21587        );
21588        let some_count = SexpShape::ALL
21589            .iter()
21590            .filter(|shape| shape.iac_forge_tag().is_some())
21591            .count();
21592        assert_eq!(
21593            some_count, 4,
21594            "SexpShape::iac_forge_tag must return `Some(_)` on exactly the four-arm quote-family carving",
21595        );
21596        let none_count = SexpShape::ALL
21597            .iter()
21598            .filter(|shape| shape.iac_forge_tag().is_none())
21599            .count();
21600        assert_eq!(
21601            none_count, 8,
21602            "SexpShape::iac_forge_tag must return `None` on exactly the eight-shape non-quote-family kernel",
21603        );
21604        assert_eq!(
21605            some_count + none_count,
21606            SexpShape::ALL.len(),
21607            "SexpShape::iac_forge_tag's image + kernel must partition SexpShape::ALL exactly",
21608        );
21609    }
21610
21611    #[test]
21612    fn sexp_shape_prefix_pins_canonical_reader_prefixes_for_every_quote_family_arm() {
21613        // CANONICAL-PREFIX CONTRACT (shape-level peer): the shape-level
21614        // `SexpShape::prefix` MUST project each of the four quote-family
21615        // arms to the SAME canonical reader-punctuation string
21616        // `crate::ast::QuoteForm::prefix` projects at the sub-carving
21617        // level — `Quote → "'"`, `Quasiquote → "`"`, `Unquote → ","`,
21618        // `UnquoteSplice → ",@"`. A regression that inlines a byte-
21619        // drifted spelling here (e.g. `SexpShape::Quote → Some("`")`
21620        // swapping the single-quote and backtick prefixes, or
21621        // `SexpShape::UnquoteSplice → Some("@,")` swapping the two-byte
21622        // splice prefix's byte order) silently breaks every reader /
21623        // LSP / REPL consumer keyed on the source-punctuation
21624        // vocabulary. Sibling posture to
21625        // `sexp_shape_iac_forge_tag_pins_canonical_cl_tags_for_every_quote_family_arm`
21626        // on the cross-crate canonical-form axis — that pin binds the
21627        // iac-forge tag surface; this pin binds the reader-punctuation
21628        // prefix surface AND its Option-partial shape.
21629        assert_eq!(SexpShape::Quote.prefix(), Some("'"));
21630        assert_eq!(SexpShape::Quasiquote.prefix(), Some("`"));
21631        assert_eq!(SexpShape::Unquote.prefix(), Some(","));
21632        assert_eq!(SexpShape::UnquoteSplice.prefix(), Some(",@"));
21633    }
21634
21635    #[test]
21636    fn sexp_shape_prefix_returns_none_on_every_non_quote_family_shape() {
21637        // PARTIAL-PROJECTION KERNEL CONTRACT (shape-level peer): every
21638        // `SexpShape` variant OUTSIDE the four-arm quote-family carving
21639        // MUST project through `SexpShape::prefix` to `None` — the
21640        // eight-shape kernel coincides byte-for-byte with the kernel of
21641        // `SexpShape::as_quote_form` AND `SexpShape::iac_forge_tag`
21642        // (there is NO homoiconic reader-punctuation prefix for the
21643        // atomic-payload variants `Symbol`/`Keyword`/`String`/`Int`/
21644        // `Float`/`Bool` — those render via their own atomic syntax —
21645        // nor for the structural residuals `Nil`/`List` which render as
21646        // `nil`/`(...)` respectively, neither via a prefix character).
21647        // Pin the eight-shape sweep so a regression that surfaces a
21648        // bogus prefix for a non-quote-family arm (e.g. `SexpShape::Nil
21649        // → Some("()")` conflating the structural-residual Display with
21650        // the reader-punctuation vocabulary) fails-loudly here. Sibling
21651        // posture to
21652        // `sexp_shape_iac_forge_tag_returns_none_on_every_non_quote_family_shape`
21653        // one vocabulary axis over.
21654        assert_eq!(SexpShape::Nil.prefix(), None);
21655        assert_eq!(SexpShape::Symbol.prefix(), None);
21656        assert_eq!(SexpShape::Keyword.prefix(), None);
21657        assert_eq!(SexpShape::String.prefix(), None);
21658        assert_eq!(SexpShape::Int.prefix(), None);
21659        assert_eq!(SexpShape::Float.prefix(), None);
21660        assert_eq!(SexpShape::Bool.prefix(), None);
21661        assert_eq!(SexpShape::List.prefix(), None);
21662    }
21663
21664    #[test]
21665    fn sexp_shape_prefix_composes_through_as_quote_form_for_every_variant() {
21666        // COMPOSITION-LAW CONTRACT (shape-level peer): `SexpShape::prefix()
21667        // == SexpShape::as_quote_form().map(QuoteForm::prefix)` for every
21668        // variant in `SexpShape::ALL`. The (outer shape, reader-
21669        // punctuation prefix) pairing binds through the pre-existing
21670        // quote-family carving composed with the closed-set prefix
21671        // projection rather than at a parallel four-arm inline match —
21672        // this pin enforces the composition-identity across the closed
21673        // twelve-arm sweep so a regression that drifts ONE arm's inline
21674        // projection from the composition (e.g. re-inlining
21675        // `SexpShape::Quote → Some("’")` — a fancy-quote look-alike —
21676        // without updating `QuoteForm::prefix`) fails-loudly here.
21677        // Sibling posture to
21678        // `sexp_shape_iac_forge_tag_composes_through_as_quote_form_for_every_variant`
21679        // one vocabulary axis over.
21680        for shape in SexpShape::ALL {
21681            let via_method = shape.prefix();
21682            let via_composition = shape.as_quote_form().map(crate::ast::QuoteForm::prefix);
21683            assert_eq!(
21684                via_method, via_composition,
21685                "SexpShape::{shape:?}.prefix() drifted from \
21686                 .as_quote_form().map(QuoteForm::prefix) — the shape-level prefix projection is no longer derived from the sub-carving's canonical site",
21687            );
21688        }
21689    }
21690
21691    #[test]
21692    fn sexp_shape_prefix_partitions_quote_family_and_kernel_disjointly() {
21693        // IMAGE-PARTITION CONTRACT (shape-level peer): sweeping
21694        // `SexpShape::ALL` through `SexpShape::prefix` MUST partition
21695        // into EXACTLY the four-arm quote-family image (four distinct
21696        // canonical reader-punctuation strings — the pre-image of
21697        // `Some(_)`) AND the eight-shape kernel (all `None` — six
21698        // atomic-payload variants + `Nil` + `List`). The image's
21699        // `is_some()` count MUST be four (surjective onto the four-
21700        // prefix closed set), the kernel's `is_none()` count MUST be
21701        // eight, and the sum MUST hit `SexpShape::ALL.len()` (twelve)
21702        // — a regression that leaks a bogus `Some(_)` from a non-
21703        // quote-family arm or drops a `Some(_)` from a quote-family
21704        // arm fails-loudly here on the partition-cardinality axis
21705        // before any downstream reader / LSP / REPL consumer would
21706        // surface the drift. Sibling posture to
21707        // `sexp_shape_iac_forge_tag_partitions_quote_family_and_kernel_disjointly`
21708        // pinning the tag-image partition; this pin binds the shape-
21709        // level prefix-image partition on the four-arm quote-family
21710        // carving. Disjoint vocabulary from the iac-forge partition
21711        // (`"'"` / `"`"` / `","` / `",@"` vs. `"quote"` / `"quasiquote"`
21712        // / `"unquote"` / `"unquote-splicing"`) — the same closed twelve-
21713        // arm shape algebra carries THREE distinct `&'static str`
21714        // vocabularies keyed on the same four-arm quote-family carving,
21715        // and a regression that conflates any two lands here.
21716        let prefix_image: std::collections::BTreeSet<&'static str> = SexpShape::ALL
21717            .iter()
21718            .filter_map(|shape| shape.prefix())
21719            .collect();
21720        let expected_prefix_image: std::collections::BTreeSet<&'static str> =
21721            ["'", "`", ",", ",@"].into_iter().collect();
21722        assert_eq!(
21723            prefix_image, expected_prefix_image,
21724            "SexpShape::prefix image must exactly cover the four canonical reader-punctuation quote-family prefixes",
21725        );
21726        let some_count = SexpShape::ALL
21727            .iter()
21728            .filter(|shape| shape.prefix().is_some())
21729            .count();
21730        assert_eq!(
21731            some_count, 4,
21732            "SexpShape::prefix must return `Some(_)` on exactly the four-arm quote-family carving",
21733        );
21734        let none_count = SexpShape::ALL
21735            .iter()
21736            .filter(|shape| shape.prefix().is_none())
21737            .count();
21738        assert_eq!(
21739            none_count, 8,
21740            "SexpShape::prefix must return `None` on exactly the eight-shape non-quote-family kernel",
21741        );
21742        assert_eq!(
21743            some_count + none_count,
21744            SexpShape::ALL.len(),
21745            "SexpShape::prefix's image + kernel must partition SexpShape::ALL exactly",
21746        );
21747    }
21748
21749    /// Runtime cross-check for the 13 module-level `const _: () =
21750    /// crate::ast::assert_str_array_pairwise_distinct(&…)` witnesses
21751    /// declared at the top of `error.rs`. Runs the same const-fn
21752    /// helper on every family-wide `[&'static str; N]` array on this
21753    /// module's closed-set diagnostic / classification algebras at
21754    /// `cargo test` time — the runtime path pairs the compile-time
21755    /// path so a build that runs tests catches the regression a
21756    /// second time as a safety net if the const-eval sweep is ever
21757    /// silently dropped.
21758    ///
21759    /// Sibling to
21760    /// `assert_str_array_pairwise_distinct_accepts_every_family_
21761    /// wide_substrate_array` on `ast.rs`'s tests module — that
21762    /// pair covers the FIVE family-wide `[&'static str; N]` arrays
21763    /// on `ast.rs`'s closed-set outer algebras (`Atom::BOOL_LITERALS`,
21764    /// `AtomKind::LABELS`, `QuoteForm::PREFIXES`,
21765    /// `QuoteForm::IAC_FORGE_TAGS`, `QuoteForm::LABELS`); this test
21766    /// covers the THIRTEEN peer arrays on `error.rs`'s closed-set
21767    /// diagnostic / classification algebras. Together the two tests
21768    /// runtime-verify every ONE of the workspace-wide EIGHTEEN
21769    /// pairwise-distinct family-wide `[&'static str; N]` arrays
21770    /// through the SAME `assert_str_array_pairwise_distinct` const-
21771    /// fn primitive.
21772    ///
21773    /// `CompilerSpecIoStage::OPERATIONS` (`[&'static str; 4]`) is
21774    /// intentionally omitted — it is a variant-aligned projection
21775    /// with two-fold duplicated entries (`[REALIZE_TO_DISK,
21776    /// REALIZE_TO_DISK, LOAD_FROM_DISK, LOAD_FROM_DISK]`), so
21777    /// enrolling it here would panic; the intended invariant on
21778    /// `OPERATIONS` is "variant-index alignment with
21779    /// `CompilerSpecIoStage::ALL`", a distinct theorem covered by
21780    /// its own tests.
21781    #[test]
21782    fn assert_str_array_pairwise_distinct_accepts_every_family_wide_error_module_array() {
21783        use crate::ast::assert_str_array_pairwise_distinct;
21784        assert_str_array_pairwise_distinct(&CompilerSpecIoStage::LABELS);
21785        assert_str_array_pairwise_distinct(&TemplateInvariantKind::STATIC_MESSAGES);
21786        assert_str_array_pairwise_distinct(&TemplateInvariantKind::DYNAMIC_DESCRIPTORS);
21787        assert_str_array_pairwise_distinct(&MacroDefHead::KEYWORDS);
21788        assert_str_array_pairwise_distinct(&OptionalParamMalformedReason::STATIC_LABELS);
21789        assert_str_array_pairwise_distinct(&OptionalParamMalformedReason::DYNAMIC_DESCRIPTORS);
21790        assert_str_array_pairwise_distinct(&UnquoteForm::MARKERS);
21791        assert_str_array_pairwise_distinct(&UnquoteForm::IAC_FORGE_TAGS);
21792        assert_str_array_pairwise_distinct(&UnquoteForm::LABELS);
21793        assert_str_array_pairwise_distinct(&KwargPathKind::LABELS);
21794        assert_str_array_pairwise_distinct(&ExpectedKwargShape::LABELS);
21795        assert_str_array_pairwise_distinct(&SexpShape::LABELS);
21796        assert_str_array_pairwise_distinct(&StructuralKind::LABELS);
21797    }
21798
21799    /// Runtime cross-check that the SAME thirteen family-wide
21800    /// `[&'static str; N]` arrays declared under this module that
21801    /// the compile-time `const _: () = crate::ast::
21802    /// assert_str_array_all_ascii(&…)` witness block above pins at
21803    /// `cargo check` time are all-ASCII at runtime too. Sibling to
21804    /// `assert_str_array_pairwise_distinct_accepts_every_family_
21805    /// wide_error_module_array` on the (contract-shape) column —
21806    /// the two together pin BOTH the INJECTIVITY axis AND the
21807    /// ASCII-BYTE-RANGE axis on the SAME thirteen arrays, at TWO
21808    /// stages of the toolchain (compile-time `const _` line + this
21809    /// runtime safety-net). Includes `CompilerSpecIoStage::
21810    /// OPERATIONS` — which is DELIBERATELY excluded from the
21811    /// pairwise-distinct sibling because its four entries are
21812    /// intentionally-collapsed onto two per-operation labels but
21813    /// is enrolled here because its two distinct entries
21814    /// (`"realize_to_disk"`, `"load_from_disk"`) are ASCII by
21815    /// construction.
21816    #[test]
21817    fn assert_str_array_all_ascii_accepts_every_family_wide_error_module_array() {
21818        use crate::ast::assert_str_array_all_ascii;
21819        assert_str_array_all_ascii(&CompilerSpecIoStage::LABELS);
21820        assert_str_array_all_ascii(&CompilerSpecIoStage::OPERATIONS);
21821        assert_str_array_all_ascii(&TemplateInvariantKind::STATIC_MESSAGES);
21822        assert_str_array_all_ascii(&TemplateInvariantKind::DYNAMIC_DESCRIPTORS);
21823        assert_str_array_all_ascii(&MacroDefHead::KEYWORDS);
21824        assert_str_array_all_ascii(&OptionalParamMalformedReason::STATIC_LABELS);
21825        assert_str_array_all_ascii(&OptionalParamMalformedReason::DYNAMIC_DESCRIPTORS);
21826        assert_str_array_all_ascii(&UnquoteForm::MARKERS);
21827        assert_str_array_all_ascii(&UnquoteForm::IAC_FORGE_TAGS);
21828        assert_str_array_all_ascii(&UnquoteForm::LABELS);
21829        assert_str_array_all_ascii(&KwargPathKind::LABELS);
21830        assert_str_array_all_ascii(&ExpectedKwargShape::LABELS);
21831        assert_str_array_all_ascii(&SexpShape::LABELS);
21832        assert_str_array_all_ascii(&StructuralKind::LABELS);
21833    }
21834
21835    /// Confirm `CompilerSpecIoStage::OPERATIONS` — the ONE family-
21836    /// wide `[&'static str; N]` array on this module that is
21837    /// DELIBERATELY excluded from the compile-time pairwise-
21838    /// distinctness witness block above — panics on the same helper
21839    /// (`crate::ast::assert_str_array_pairwise_distinct`) at
21840    /// runtime. Two positional pairs collide byte-for-byte on this
21841    /// array by design: positions 0+1 (both
21842    /// `Self::REALIZE_TO_DISK_OPERATION` = `"realize_to_disk"`) AND
21843    /// positions 2+3 (both `Self::LOAD_FROM_DISK_OPERATION` =
21844    /// `"load_from_disk"`), and the array's intended invariant is
21845    /// variant-index alignment with `Self::ALL`, not injectivity.
21846    ///
21847    /// This test doubles as a documented boundary marker — if a
21848    /// future refactor DID sharpen `OPERATIONS` to a distinct set
21849    /// (e.g. by carving the two-per-operation collapse into a
21850    /// separate `[&'static str; 2]` array), enrolling the sharpened
21851    /// array in the `const _` block above would then be legal, and
21852    /// this test would need updating to match. Pinning the "is
21853    /// intentionally non-distinct" property here surfaces that
21854    /// refactor at test time rather than leaving it as a silent
21855    /// module-level tribal-knowledge assumption.
21856    #[test]
21857    #[should_panic(expected = "assert_str_array_pairwise_distinct")]
21858    fn assert_str_array_pairwise_distinct_panics_on_compiler_spec_io_stage_operations() {
21859        use crate::ast::assert_str_array_pairwise_distinct;
21860        assert_str_array_pairwise_distinct(&CompilerSpecIoStage::OPERATIONS);
21861    }
21862
21863    /// Runtime cross-check that the TWO
21864    /// (`_`, `StructuralKind::LABELS`) disjointness pairs the
21865    /// module-level `const _: () = crate::ast::assert_str_arrays_
21866    /// disjoint::<N, 2>(...)` witnesses pin at COMPILE time are
21867    /// proper DISJOINTNESS embeddings at runtime too. Peer to
21868    /// `assert_str_arrays_disjoint_accepts_each_family_wide_
21869    /// substrate_pair` in `ast.rs` (which sweeps the FOUR pairs
21870    /// whose two hosts both live in `ast.rs`): together the two
21871    /// tests sweep all SIX substrate-pinned `&'static str`
21872    /// disjointness pairs (`ast.rs` × 4 + `error.rs` × 2 = 6) at
21873    /// runtime as a safety net. The compile-time `const _` sweep
21874    /// fires FIRST at `cargo check`; this test catches drift again
21875    /// at `cargo test` if the const-eval sweep is ever silently
21876    /// dropped.
21877    ///
21878    /// Composed with the ast.rs-pinned
21879    /// (`AtomKind::LABELS`, `QuoteForm::LABELS`) pair — the third
21880    /// pair on the three-element sub-vocabulary triple — these two
21881    /// checks close the DISJOINT-UNION theorem
21882    /// `SexpShape::LABELS ≡ AtomKind::LABELS ⊕ QuoteForm::LABELS ⊕
21883    /// StructuralKind::LABELS` at runtime as the sibling proof to
21884    /// the three subset-embedding witnesses on the same triple.
21885    #[test]
21886    fn assert_str_arrays_disjoint_accepts_each_structural_kind_partition_pair() {
21887        use crate::ast::assert_str_arrays_disjoint;
21888        assert_str_arrays_disjoint::<6, 2>(&crate::ast::AtomKind::LABELS, &StructuralKind::LABELS);
21889        assert_str_arrays_disjoint::<4, 2>(&crate::ast::QuoteForm::LABELS, &StructuralKind::LABELS);
21890    }
21891
21892    /// Runtime safety-net sibling of the module-level compile-time
21893    /// STR-DISJOINTNESS witnesses on the STATIC ⊕ DYNAMIC 2-of-2 face
21894    /// of the substrate's TWO peer diagnostic algebras
21895    /// (`TemplateInvariantKind` at 2 ⊕ 2 asymmetric widths;
21896    /// `OptionalParamMalformedReason` at 3 ⊕ 1 asymmetric widths).
21897    /// Re-runs each `const _: () = crate::ast::assert_str_arrays_
21898    /// disjoint::<N, M>(&<algebra>::STATIC_*, &<algebra>::DYNAMIC_
21899    /// DESCRIPTORS)` witness pinned above the `LispError` enum at
21900    /// runtime as a safety net so a build that skips `cargo check`'s
21901    /// const-eval sweep still catches drift here. Peer to
21902    /// `assert_str_arrays_disjoint_accepts_each_structural_kind_partition_pair`
21903    /// above (which sweeps the SexpShape 3-way sub-vocabulary triple's
21904    /// two `error.rs`-hosted disjointness pairs); together the two
21905    /// tests sweep all FOUR `error.rs`-hosted `assert_str_arrays_
21906    /// disjoint` witnesses at runtime. The compile-time `const _`
21907    /// sweep at the top of this module fires FIRST at `cargo check`;
21908    /// this test catches drift again at `cargo test` if the const-
21909    /// eval sweep is ever silently dropped.
21910    #[test]
21911    fn assert_str_arrays_disjoint_accepts_each_static_dynamic_carving_pair() {
21912        use crate::ast::assert_str_arrays_disjoint;
21913        assert_str_arrays_disjoint::<2, 2>(
21914            &TemplateInvariantKind::STATIC_MESSAGES,
21915            &TemplateInvariantKind::DYNAMIC_DESCRIPTORS,
21916        );
21917        assert_str_arrays_disjoint::<3, 1>(
21918            &OptionalParamMalformedReason::STATIC_LABELS,
21919            &OptionalParamMalformedReason::DYNAMIC_DESCRIPTORS,
21920        );
21921    }
21922
21923    /// Runtime safety-net sibling of the module-level compile-time
21924    /// STR-BLOCK-CONSTANCY witness on the MANY-TO-ONE variant →
21925    /// canonical-projection surface at
21926    /// `CompilerSpecIoStage::OPERATIONS`. Re-runs the `const _: () =
21927    /// crate::ast::assert_str_array_is_concatenation_of_two_scalar_
21928    /// replicas::<4, 2>(&OPERATIONS, REALIZE_TO_DISK_OPERATION,
21929    /// LOAD_FROM_DISK_OPERATION)` witness pinned above the
21930    /// `LispError` enum at runtime as a safety net so a build that
21931    /// skips `cargo check`'s const-eval sweep still catches the
21932    /// block-constant drift here. Peer to
21933    /// `compiler_spec_io_stage_operations_align_with_all_by_index`
21934    /// (which pins per-position projection equality through
21935    /// `stage.operation()`) and to
21936    /// `compiler_spec_io_stage_operations_partition_all_two_ways`
21937    /// (which pins the two operation-label multiplicities) — the
21938    /// three tests jointly sweep the block-constant partition at
21939    /// THREE distinct runtime angles (per-position equality with
21940    /// ALL, multiplicity count of each block scalar, ARRAY-LEVEL
21941    /// block-constancy structural pattern) while the compile-time
21942    /// witness above binds the ARRAY-LEVEL structural pattern at
21943    /// `cargo check` time as the FIRST-firing arm.
21944    #[test]
21945    fn assert_str_array_is_concatenation_of_two_scalar_replicas_accepts_compiler_spec_io_stage_operations(
21946    ) {
21947        use crate::ast::assert_str_array_is_concatenation_of_two_scalar_replicas;
21948        assert_str_array_is_concatenation_of_two_scalar_replicas::<4, 2>(
21949            &CompilerSpecIoStage::OPERATIONS,
21950            CompilerSpecIoStage::REALIZE_TO_DISK_OPERATION,
21951            CompilerSpecIoStage::LOAD_FROM_DISK_OPERATION,
21952        );
21953    }
21954
21955    /// Runtime cross-check that the SAME five (str)-row FULL-ARRAY
21956    /// LITERAL witnesses covered at COMPILE time by the module-level
21957    /// `const _: () = crate::ast::assert_str_array_slice_equals_str_
21958    /// array::<N, N, 0>(&…, &[literal strs; N])` cluster immediately
21959    /// below the `assert_str_array_is_concatenation_of_two_scalar_
21960    /// replicas::<4, 2>(&CompilerSpecIoStage::OPERATIONS, …)` block
21961    /// witness — pinning `ExpectedKwargShape::LABELS`,
21962    /// `KwargPathKind::LABELS`, `MacroDefHead::KEYWORDS`,
21963    /// `CompilerSpecIoStage::LABELS`, and `StructuralKind::LABELS`
21964    /// against their canonical literal-str listings — also hold when
21965    /// exercised at runtime. A regression that removes ONE of the
21966    /// const witnesses would still leave THIS runtime pin as a
21967    /// safety net; the const witness fires FIRST at `cargo check`,
21968    /// this runtime pin catches the positionwise drift at `cargo
21969    /// test`. The pair enforces the theorem at TWO stages of the
21970    /// toolchain.
21971    ///
21972    /// Sibling posture to
21973    /// `assert_str_array_slice_equals_str_array_accepts_every_family_wide_substrate_array_full_array_literal_listing`
21974    /// in `ast.rs` (the ast-side (str)-row cluster's runtime peer
21975    /// on `Atom::BOOL_LITERALS` / `AtomKind::LABELS` /
21976    /// `QuoteForm::PREFIXES` / `QuoteForm::LABELS` /
21977    /// `QuoteForm::IAC_FORGE_TAGS`) — together the two runtime
21978    /// tests sweep TEN family-wide `[&'static str; N]` label
21979    /// vocabularies on the substrate at `cargo test` time as
21980    /// safety-net peers to the ten compile-time `const _` witnesses.
21981    ///
21982    /// A regression that (a) reorders one of the outer arrays' slots
21983    /// away from canonical declaration order, or (b) drifts one of
21984    /// the per-role scalar `pub const *_LABEL` / `*_KEYWORD` aliases
21985    /// the outer array's slots re-export, fails HERE with the
21986    /// `STR-SLICE-EQUALS-ARRAY-VIOLATION` axis panic naming the
21987    /// drifted position.
21988    #[test]
21989    fn assert_str_array_slice_equals_str_array_accepts_every_family_wide_error_module_array_full_array_literal_listing(
21990    ) {
21991        use crate::ast::assert_str_array_slice_equals_str_array;
21992        assert_str_array_slice_equals_str_array::<7, 7, 0>(
21993            &ExpectedKwargShape::LABELS,
21994            &[
21995                "keyword",
21996                "string",
21997                "int",
21998                "number",
21999                "bool",
22000                "list",
22001                "list of strings",
22002            ],
22003        );
22004        assert_str_array_slice_equals_str_array::<3, 3, 0>(
22005            &KwargPathKind::LABELS,
22006            &["named", "item", "slot"],
22007        );
22008        assert_str_array_slice_equals_str_array::<3, 3, 0>(
22009            &MacroDefHead::KEYWORDS,
22010            &["defmacro", "defpoint-template", "defcheck"],
22011        );
22012        assert_str_array_slice_equals_str_array::<4, 4, 0>(
22013            &CompilerSpecIoStage::LABELS,
22014            &["serialize", "write", "read", "deserialize"],
22015        );
22016        assert_str_array_slice_equals_str_array::<2, 2, 0>(
22017            &StructuralKind::LABELS,
22018            &["nil", "list"],
22019        );
22020    }
22021
22022    /// Runtime DISJOINT-UNION theorem — the twelve-arm outer
22023    /// `SexpShape::LABELS` array is byte-for-byte the union of the
22024    /// three sub-vocabulary arrays (`AtomKind::LABELS` ∪
22025    /// `QuoteForm::LABELS` ∪ `StructuralKind::LABELS`) up to
22026    /// permutation, sweeping both directions of the SET-equality
22027    /// obligation:
22028    ///
22029    ///   (⊆) every outer label appears in EXACTLY ONE sub-
22030    ///        vocabulary (existence + uniqueness);
22031    ///   (⊇) every sub-vocabulary label appears in the outer
22032    ///        vocabulary (SUBSET-embedding, already pinned above at
22033    ///        rustc time by the three `assert_str_array_within_str_
22034    ///        finite_set` witnesses; swept here again as a runtime
22035    ///        safety net).
22036    ///
22037    /// Cardinality composition (6 + 4 + 2 = 12) is enforced
22038    /// STRUCTURALLY by rustc through the `[&'static str; N]` array
22039    /// arities; this test pins the byte-level partition INDEPENDENT
22040    /// of the sub-vocabulary array declarations, so a regression
22041    /// that silently split a two-arm sub-vocabulary into
22042    /// (`[&; 1]`, `[&; 1]`) while keeping the total at 12 would
22043    /// still catch here even if the arity sum remained legal.
22044    #[test]
22045    fn sexp_shape_labels_is_disjoint_union_of_three_sub_vocabularies() {
22046        for outer in SexpShape::LABELS.iter() {
22047            let hits_atom = crate::ast::AtomKind::LABELS
22048                .iter()
22049                .filter(|s| s == &outer)
22050                .count();
22051            let hits_quote = crate::ast::QuoteForm::LABELS
22052                .iter()
22053                .filter(|s| s == &outer)
22054                .count();
22055            let hits_struct = StructuralKind::LABELS
22056                .iter()
22057                .filter(|s| s == &outer)
22058                .count();
22059            let total = hits_atom + hits_quote + hits_struct;
22060            assert_eq!(
22061                total, 1,
22062                "outer SexpShape label {outer:?} must appear in EXACTLY ONE \
22063                 sub-vocabulary (atom={hits_atom}, quote={hits_quote}, \
22064                 struct={hits_struct}); the twelve-arm partition triple \
22065                 (AtomKind::LABELS, QuoteForm::LABELS, StructuralKind::LABELS) \
22066                 must be a DISJOINT UNION of SexpShape::LABELS"
22067            );
22068        }
22069        for inner in crate::ast::AtomKind::LABELS
22070            .iter()
22071            .chain(crate::ast::QuoteForm::LABELS.iter())
22072            .chain(StructuralKind::LABELS.iter())
22073        {
22074            assert!(
22075                SexpShape::LABELS.iter().any(|s| s == inner),
22076                "sub-vocabulary label {inner:?} must be embedded in the \
22077                 outer SexpShape::LABELS vocabulary — the (⊇) direction \
22078                 of the disjoint-union theorem"
22079            );
22080        }
22081    }
22082
22083    /// Runtime SLICE-EQUALS-ARRAY safety net for the (UnquoteForm ⊂
22084    /// QuoteForm) 2-of-4 sub-carve on the three sibling `[&'static str;
22085    /// N]` vocabulary axes — sweeps the same three
22086    /// `assert_str_array_slice_equals_str_array::<4, 2, 2>` invocations
22087    /// pinned at compile time in the module-level `const _` block
22088    /// immediately below the peer SUBSET-embedding witness cluster.
22089    /// The `const _` witnesses fire FIRST at `cargo check`; this
22090    /// runtime pin catches the ARRAY-LEVEL positionwise drift at
22091    /// `cargo test` as a safety net enforcing the theorem at BOTH
22092    /// stages of the toolchain.
22093    ///
22094    /// A regression that (a) swaps the two per-role slot bindings in
22095    /// one of the `UnquoteForm::MARKERS` / `LABELS` / `IAC_FORGE_TAGS`
22096    /// initializers, or (b) reorders the four-arm `QuoteForm::PREFIXES`
22097    /// / `LABELS` / `IAC_FORGE_TAGS` superset such that the two
22098    /// UnquoteForm-family arms no longer sit contiguously at slots
22099    /// `[2..4)`, fails HERE with the `STR-SLICE-EQUALS-ARRAY-VIOLATION`
22100    /// axis panic naming the drifted position — where the sibling
22101    /// SUBSET-embedding safety-net pins stay silent (both aliases still
22102    /// appear in the superset, just at different positions).
22103    #[test]
22104    fn assert_str_array_slice_equals_str_array_accepts_unquote_form_sub_carve_of_quote_form() {
22105        use crate::ast::assert_str_array_slice_equals_str_array;
22106        assert_str_array_slice_equals_str_array::<4, 2, 2>(
22107            &crate::ast::QuoteForm::LABELS,
22108            &UnquoteForm::LABELS,
22109        );
22110        assert_str_array_slice_equals_str_array::<4, 2, 2>(
22111            &crate::ast::QuoteForm::PREFIXES,
22112            &UnquoteForm::MARKERS,
22113        );
22114        assert_str_array_slice_equals_str_array::<4, 2, 2>(
22115            &crate::ast::QuoteForm::IAC_FORGE_TAGS,
22116            &UnquoteForm::IAC_FORGE_TAGS,
22117        );
22118    }
22119}