Skip to main content

caixa_theme/
style.rs

1//! The small semantic-style enum every caixa tool agrees on.
2
3use serde::{Deserialize, Serialize};
4
5#[derive(
6    Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, gen_platform::IsVariant,
7)]
8pub enum Semantic {
9    /// Language keywords — `defcaixa`, `defteia`, `let`, `lambda`, etc.
10    Keyword,
11    /// Non-keyword symbols — identifiers, function names, variant names.
12    Symbol,
13    /// `:keyword-positioned` atoms.
14    KeywordArg,
15    /// `"string literals"`.
16    String,
17    /// `42`, `3.14`.
18    Number,
19    /// `#t`, `#f`, `nil`.
20    Literal,
21    /// `; comments`.
22    Comment,
23    /// Primary accent — useful for highlights, carets, focused tokens.
24    Accent,
25    /// Dim text — metadata, line numbers, help text.
26    Muted,
27
28    // Diagnostic severities.
29    Error,
30    Warning,
31    Info,
32    Hint,
33
34    // Diff decorations — used by formatter preview and lint output.
35    Added,
36    Removed,
37    Unchanged,
38}
39
40impl Semantic {
41    /// Every variant of [`Semantic`] in declaration order.
42    ///
43    /// The single canonical arm-list every substrate consumer that has
44    /// to walk the closed 15-arm semantic-style partition (the two
45    /// theme overlays' exhaustive-match resolver functions
46    /// `blackmatter_dark_color` / `blackmatter_light_color` in
47    /// [`crate::blackmatter`], the future LSP-side per-Semantic
48    /// `SemanticTokenType` dispatch at
49    /// `caixa-lsp/src/main.rs`, a future
50    /// `feira lint --list-styles` operator-facing enumeration verb)
51    /// reads for. Peer of the sibling closed-set fieldless typed
52    /// enums' `ALL` slices already carried by
53    /// [`caixa_core::CaixaKind`] /
54    /// [`caixa_core::supervisor::RestartStrategy`] /
55    /// [`caixa_core::supervisor::RestartPolicy`] /
56    /// [`caixa_core::aplicacao::PlacementStrategy`] /
57    /// [`caixa_core::upgrade::UpgradeInstruction`] /
58    /// `caixa_lint::diagnostic::Severity` /
59    /// `caixa_lint::diagnostic::FixSafety` /
60    /// `caixa_arch::InvariantKind` / `caixa_arch::ArchVerdict` /
61    /// `caixa_provedor::FerriteRuntime` closed-set typed-enum
62    /// discriminator axes.
63    pub const ALL: &'static [Self] = &[
64        Self::Keyword,
65        Self::Symbol,
66        Self::KeywordArg,
67        Self::String,
68        Self::Number,
69        Self::Literal,
70        Self::Comment,
71        Self::Accent,
72        Self::Muted,
73        Self::Error,
74        Self::Warning,
75        Self::Info,
76        Self::Hint,
77        Self::Added,
78        Self::Removed,
79        Self::Unchanged,
80    ];
81
82    /// Canonical kebab-case discriminator scalar for this variant — the
83    /// single substrate-primitive `&'static str` projection every
84    /// downstream consumer of the closed 16-arm [`Semantic`] partition
85    /// (a future LSP-side per-Semantic `SemanticTokenType` name-mapping
86    /// dispatch at `caixa-lsp/src/main.rs`, a future
87    /// `feira lint --list-styles` operator-facing enumeration verb, a
88    /// future `caixa.nvim` per-Semantic highlight-group name resolver
89    /// that reaches for a stable kebab identifier per arm, a future
90    /// `blackmatter-shell` per-arm classname the terminal emitter
91    /// composes into a `data-semantic="<kebab>"` attribute) reaches
92    /// through. Kebab-case matches the peer
93    /// [`gen_platform::IsVariant`]-derived kebab discriminant convention
94    /// the sibling closed-set typed enums
95    /// ([`caixa_core::CaixaKind::as_str`],
96    /// [`caixa_core::supervisor::RestartStrategy::as_str`],
97    /// [`caixa_core::supervisor::RestartPolicy::as_str`],
98    /// [`caixa_core::aplicacao::PlacementStrategy::as_str`],
99    /// [`caixa_core::upgrade::UpgradeInstruction::as_str`],
100    /// `caixa_lint::diagnostic::Severity::as_str`,
101    /// `caixa_lint::diagnostic::FixSafety::as_str`,
102    /// `caixa_arch::InvariantKind::as_str`,
103    /// `caixa_arch::ArchVerdict::as_str`,
104    /// `caixa_provedor::FerriteRuntime::variant_slug`) already emit
105    /// on their canonical `&'static str` projection axis.
106    ///
107    /// The 16 arms return the kebab-case forms of their `PascalCase`
108    /// variant names (`Keyword` → `"keyword"`, `KeywordArg` →
109    /// `"keyword-arg"`, `Unchanged` → `"unchanged"`, etc.), matching
110    /// the peer closed-set typed-enum canonical byte-string conventions.
111    ///
112    /// Peer of the [`std::fmt::Display`] and [`AsRef<str>`] impls on
113    /// this enum, which both route through this accessor so
114    /// `format!("{s}")`, `s.as_str()`, and
115    /// `<Semantic as AsRef<str>>::as_ref(&s)` resolve to the same
116    /// per-arm byte-string.
117    #[must_use]
118    pub const fn as_str(self) -> &'static str {
119        match self {
120            Self::Keyword => "keyword",
121            Self::Symbol => "symbol",
122            Self::KeywordArg => "keyword-arg",
123            Self::String => "string",
124            Self::Number => "number",
125            Self::Literal => "literal",
126            Self::Comment => "comment",
127            Self::Accent => "accent",
128            Self::Muted => "muted",
129            Self::Error => "error",
130            Self::Warning => "warning",
131            Self::Info => "info",
132            Self::Hint => "hint",
133            Self::Added => "added",
134            Self::Removed => "removed",
135            Self::Unchanged => "unchanged",
136        }
137    }
138
139    /// Reverse projection on the [`Semantic`] closed 16-arm enum's
140    /// canonical kebab-tag axis — parses a `"keyword"` / `"symbol"` /
141    /// `"keyword-arg"` / `"string"` / `"number"` / `"literal"` /
142    /// `"comment"` / `"accent"` / `"muted"` / `"error"` / `"warning"` /
143    /// `"info"` / `"hint"` / `"added"` / `"removed"` / `"unchanged"` wire
144    /// byte-string back to the typed enum, or returns `None` when `s`
145    /// lies outside the 16-arm accept-set [`Self::as_str`] emits. The
146    /// single `&str → Self` projection every future re-entry point on
147    /// the caixa-theme semantic-style axis dispatches through (a future
148    /// `feira lint --list-styles` operator-facing enumeration verb
149    /// hydrating a per-arm row from a stored kebab identifier back to
150    /// the typed enum before rendering, a future `caixa-lsp`-side
151    /// per-`SemanticTokenType` re-parse binding a prior
152    /// [`Self::as_str`] output back to the typed enum for
153    /// per-Semantic-highlight dispatch, a future `caixa.nvim` per-
154    /// Semantic highlight-group resolver re-loading a stored kebab
155    /// identifier back to the typed enum, a future `blackmatter-shell`
156    /// per-arm classname reverse-lookup binding a
157    /// `data-semantic="<kebab>"` DOM attribute back to the typed enum
158    /// for per-arm style dispatch, a `tracing::field::Value::Str`-arm
159    /// structured-log re-loader binding a prior emission's
160    /// [`Self::as_str`] output back to the typed enum for cross-run
161    /// per-Semantic-paint-histogram diff) would have had to re-inline a
162    /// 16-arm `match s` cascade that expressed no compile-time link back
163    /// to the substrate primitive.
164    ///
165    /// Same closed-set-reverse-projection discipline the sibling
166    /// [`caixa_core::CaixaKind::from_wire`] (2aa6d23),
167    /// [`caixa_core::CaixaDialeto::from_wire`] (d0e65ea),
168    /// [`caixa_core::supervisor::RestartStrategy::from_wire`] (4eec29c),
169    /// [`caixa_core::supervisor::RestartPolicy::from_wire`] (dd32ccf),
170    /// [`caixa_core::aplicacao::PlacementStrategy::from_wire`] (18c7342),
171    /// [`caixa_core::dep::DepList::from_wire`] (45ee563),
172    /// [`caixa_core::render::PathShapeViolation::from_wire`] (aebd9c6),
173    /// `caixa_arch::invariants::InvariantKind::from_wire` (b9e4e61),
174    /// `caixa_arch::report::ArchVerdict::from_wire` (6afe564),
175    /// `caixa_lint::diagnostic::Severity::from_wire` (5afff0e), and
176    /// `caixa_lint::diagnostic::FixSafety::from_wire` (bd505a1) typed
177    /// enums carry on the peer wire-side `str → Self` axes — extends
178    /// the substrate-wide `(as_str, from_wire)` round-trip family onto
179    /// the caixa-theme closed-set fieldless typed-enum axis (the first
180    /// closed-set fieldless typed enum on `caixa-theme` to converge on
181    /// the reverse-projection discipline), matching the same two-way
182    /// `str ↔ Self` round-trip every sibling closed-set enum already
183    /// carries. Method-named `from_wire` (not `from_str`) to match the
184    /// peer shapes verbatim and side-step a
185    /// `clippy::should_implement_trait` lint that a plain `from_str`
186    /// name would otherwise trigger without paired
187    /// [`std::str::FromStr`] impl scaffolding this axis does not carry
188    /// today. Returns `Option<Self>` (rather than `Result<Self, _>`) to
189    /// match the peer shapes: the caller picks the diagnostic form
190    /// appropriate for its use site (a `feira lint --list-styles` CLI
191    /// arg-parse renders its own per-verb error message; a future
192    /// admission-webhook rejection body wraps the `None` outcome with
193    /// the accepted-set enumeration `Semantic::ALL.iter().map(…)` for
194    /// operator diagnostics).
195    ///
196    /// Pinned load-bearing at the substrate-primitive level by
197    /// [`tests::semantic_from_wire_accepts_every_as_str_output`]
198    /// (round-trip witness against the peer [`Self::as_str`] axis) and
199    /// [`tests::semantic_from_wire_rejects_unknown_byte_strings`]
200    /// (rejection witness against silent accept-set widening).
201    #[must_use]
202    pub fn from_wire(s: &str) -> Option<Self> {
203        match s {
204            "keyword" => Some(Self::Keyword),
205            "symbol" => Some(Self::Symbol),
206            "keyword-arg" => Some(Self::KeywordArg),
207            "string" => Some(Self::String),
208            "number" => Some(Self::Number),
209            "literal" => Some(Self::Literal),
210            "comment" => Some(Self::Comment),
211            "accent" => Some(Self::Accent),
212            "muted" => Some(Self::Muted),
213            "error" => Some(Self::Error),
214            "warning" => Some(Self::Warning),
215            "info" => Some(Self::Info),
216            "hint" => Some(Self::Hint),
217            "added" => Some(Self::Added),
218            "removed" => Some(Self::Removed),
219            "unchanged" => Some(Self::Unchanged),
220            _ => None,
221        }
222    }
223}
224
225/// [`std::fmt::Display`] routed through [`Semantic::as_str`], so the
226/// pretty-printed byte-string every consumer that formats the semantic
227/// style as user-facing text lands on (a future `feira lint --list-styles`
228/// operator-facing enumeration verb's per-arm line, a future
229/// `caixa-lsp` diagnostic-source line naming the offending semantic,
230/// a future `caixa.nvim` per-Semantic highlight-group name emitter,
231/// a `tracing::field::display(&sem)` structured-log recorder on the
232/// paint-side emit path) reaches for the same lifted kebab-case
233/// per-arm byte-string [`Semantic::as_str`] returns.
234///
235/// Peer of the sibling [`std::fmt::Display`] impls on the closed-set
236/// typed enums the substrate carries — [`caixa_core::CaixaKind`],
237/// [`caixa_core::supervisor::RestartStrategy`],
238/// [`caixa_core::supervisor::RestartPolicy`],
239/// [`caixa_core::aplicacao::PlacementStrategy`],
240/// [`caixa_core::upgrade::UpgradeInstruction`],
241/// `caixa_lint::diagnostic::Severity`,
242/// `caixa_lint::diagnostic::FixSafety`,
243/// `caixa_arch::InvariantKind`, `caixa_arch::ArchVerdict`,
244/// `caixa_provedor::FerriteRuntime` — extended to the second-to-last
245/// un-lifted `caixa-theme` closed-set fieldless typed enum on the
246/// substrate-wide `(as_str, AsRef<str>, Display)` canonical-projection
247/// triple ratchet the prior lifts converged onto.
248impl std::fmt::Display for Semantic {
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        f.write_str(self.as_str())
251    }
252}
253
254/// Substrate-canonical [`AsRef<str>`] projection on the caixa-theme
255/// [`Semantic`] closed-set fieldless typed enum — routes through the
256/// same [`Semantic::as_str`] `pub const fn` scalar accessor the paired
257/// [`std::fmt::Display`] impl already reaches for.
258///
259/// Peer of the sibling [`AsRef<str>`] impls the substrate carries on
260/// [`caixa_core::CaixaKind`], [`caixa_core::CaixaVersion`],
261/// [`caixa_core::CaixaDialeto`], [`caixa_core::dep::DepList`],
262/// [`caixa_core::supervisor::RestartStrategy`],
263/// [`caixa_core::supervisor::RestartPolicy`],
264/// [`caixa_core::aplicacao::PlacementStrategy`],
265/// [`caixa_core::aplicacao::RateLimitUnit`],
266/// `caixa_lint::diagnostic::Severity`,
267/// `caixa_arch::InvariantKind`, `caixa_arch::ArchVerdict`,
268/// `caixa_provedor::FerriteRuntime` — extends the substrate-wide
269/// `(as_str, AsRef<str>, Display)` canonical-projection triple onto
270/// the caixa-theme closed-set typed-enum axis, so a future consumer
271/// bound through the trait-idiomatic `.as_ref()` (a
272/// `HashMap::get::<str>(sem.as_ref())` per-Semantic style-lookup, a
273/// future `caixa-lsp` `SemanticTokenType::new(sem.as_ref())`
274/// registration site, any `impl AsRef<str>`-bound generic function)
275/// reaches the same kebab byte-string [`Semantic::as_str`] returns
276/// rather than an open-coded `.as_str()` projection at every
277/// wire-up.
278impl AsRef<str> for Semantic {
279    fn as_ref(&self) -> &str {
280        self.as_str()
281    }
282}
283
284/// Trait-idiomatic reverse projection on the [`Semantic`] closed 16-arm
285/// caixa-theme semantic-style axis — routes byte-for-byte through the
286/// paired substrate-primitive [`Semantic::from_wire`] `Option<Self>`
287/// accessor so every future consumer that binds a canonical semantic
288/// tag through the standard-library `.try_into()` / [`TryFrom`] axis
289/// (a future `feira lint --semantic=<kebab>` CLI arg-parse that
290/// composes into `let sem: Semantic = s.try_into()?`, a future
291/// `caixa-lsp` per-token re-parse hydrating a prior
292/// [`Semantic::as_str`] output back to the typed enum for
293/// `SemanticTokenType` dispatch, a future `caixa.nvim` per-highlight-
294/// group re-loader binding a stored kebab byte-string back through the
295/// typed enum, a generic `<T: TryFrom<&str>>`-bound theme-overlay
296/// re-loader over any of the substrate's closed-set typed enums)
297/// reaches the same 16-arm accept-set the sibling
298/// [`Semantic::from_wire`] resolver parses through and the sibling
299/// [`Semantic::as_str`] emits, rather than an open-coded per-arm
300/// `match s { "keyword" => …, "symbol" => …, … _ => … }` cascade whose
301/// arm-set has no compile-time link back to the substrate primitive.
302///
303/// Complements the pre-existing forward-projection triple
304/// ([`std::fmt::Display`], [`AsRef<str>`], [`Semantic::as_str`]) with
305/// the paired trait-idiomatic reverse-projection axis: Rust-side
306/// newtype/typed-enum convention pairs [`AsRef<str>`] with either
307/// [`std::str::FromStr`] or [`TryFrom<&str>`] on the same primitive so
308/// a caller who can project *out to* a `&str` can also project *in
309/// from* one. The [`TryFrom<&str>`] axis is deliberately chosen over
310/// [`std::str::FromStr`] to sidestep the `clippy::should_implement_trait`
311/// lint the sibling method-named [`Semantic::from_wire`] would trigger
312/// under a `FromStr` impl (the same design tradeoff the peer
313/// [`caixa_core::CaixaKind`] (3c83606),
314/// [`caixa_core::CaixaDialeto`] (bf33136),
315/// [`caixa_core::aplicacao::PlacementStrategy`] (6fd00cd),
316/// [`caixa_core::supervisor::RestartStrategy`] (5b828ed),
317/// [`caixa_core::supervisor::RestartPolicy`] (6fdd0d9),
318/// [`caixa_core::aplicacao::WitShape`] (5472902),
319/// [`caixa_core::aplicacao::RateLimitUnit`] (bf78400),
320/// [`caixa_core::render::PathShapeViolation`] (e67e48a),
321/// `caixa_arch::invariants::InvariantKind` (e21a857),
322/// `caixa_arch::report::ArchVerdict` (0a4cc45),
323/// `caixa_lint::diagnostic::Severity` (a7bf74c), and
324/// `caixa_lint::diagnostic::FixSafety` (df86c94) blocks note) — this
325/// impl closes the trait-idiomatic reverse axis without disturbing the
326/// method-named `from_wire` shape every peer closed-set typed enum
327/// already carries.
328///
329/// `type Error = ()` matches the sibling [`Semantic::from_wire`]'s
330/// `Option<Self>` return-shape's deliberate deferral of error typing:
331/// the caller picks the diagnostic form appropriate for its use site
332/// (a future `feira lint --semantic` CLI arg-parse composes its own
333/// per-verb "unknown semantic-style tag: <arg> — accepted: {…}"
334/// message enumerating [`Semantic::ALL`], a future admission-webhook
335/// rejection body wraps the `Err(())` outcome with the accepted-set
336/// enumeration for operator diagnostics, a `Result::map_err` at the
337/// call site lifts the axis-error to a per-verb error type). Same
338/// shape the peer sibling reverse-projection axes carry. The return-
339/// shape uses fully-qualified `<Self as TryFrom<&str>>::Error` because
340/// the associated-type name would otherwise collide with the
341/// [`Self::Error`] variant on the same primitive (the identical
342/// ambiguous-associated-item defect the sibling
343/// `impl TryFrom<&str> for Severity` (a7bf74c) already threads around
344/// under `#[deny(future_incompatible)]`).
345///
346/// The paired [`TryFrom<&str>`] impl reaches the same 16-arm accept-
347/// set the [`Semantic::from_wire`] resolver dispatches through, so any
348/// future arm addition (a `Namespace` tier between [`Self::Symbol`]
349/// and [`Self::KeywordArg`] for the M4 tatara-lisp module system's
350/// qualified-name semantic-token dispatch, a `Deleted` tier for a
351/// hard-delete-mark distinct from [`Self::Removed`] the future 3-way
352/// diff surface grows — both trajectory items the sibling
353/// [`Semantic::ALL`] doc block already names) grows the trait-
354/// idiomatic axis by construction: one caixa-theme edit on
355/// [`Semantic::from_wire`] extends both the method-named reverse
356/// projection every existing consumer keys off and the trait-
357/// idiomatic reverse projection this impl exposes, without a
358/// coordinated rewrite across every future `TryFrom<&str>`-bound
359/// consumer's arm-set.
360///
361/// Extends the substrate-wide closed-set-enum trait-idiomatic
362/// reverse-projection family ([`caixa_core::CaixaKind`] via 3c83606,
363/// [`caixa_core::CaixaDialeto`] via bf33136,
364/// [`caixa_core::aplicacao::PlacementStrategy`] via 6fd00cd,
365/// [`caixa_core::supervisor::RestartStrategy`] via 5b828ed,
366/// [`caixa_core::supervisor::RestartPolicy`] via 6fdd0d9,
367/// [`caixa_core::aplicacao::WitShape`] via 5472902,
368/// [`caixa_core::aplicacao::RateLimitUnit`] via bf78400,
369/// [`caixa_core::render::PathShapeViolation`] via e67e48a,
370/// `caixa_arch::invariants::InvariantKind` via e21a857,
371/// `caixa_arch::report::ArchVerdict` via 0a4cc45,
372/// `caixa_lint::diagnostic::Severity` via a7bf74c, and
373/// `caixa_lint::diagnostic::FixSafety` via df86c94) onto the first
374/// closed-set fieldless typed enum on the caixa-theme surface — the
375/// semantic-style 16-arm accept-set every per-Semantic paint dispatch,
376/// every future `caixa-lsp` per-SemanticTokenType wire-up, and every
377/// future `caixa.nvim` per-highlight-group re-loader keys off. The
378/// thirteenth peer on the substrate surface, and the first inside
379/// `caixa-theme`; with `caixa_provedor::FerriteRuntime` remaining as
380/// the only outside-caixa-core closed-set fieldless typed enum whose
381/// trait-idiomatic axis is still open.
382///
383/// Pinned load-bearing by
384/// [`tests::semantic_try_from_str_routes_through_from_wire_accessor`]
385/// (byte-parity pin against [`Semantic::from_wire`] across the 16-arm
386/// accept-set) and
387/// [`tests::semantic_try_from_str_rejects_unknown_byte_strings`]
388/// (rejection witness against silent accept-set widening).
389impl TryFrom<&str> for Semantic {
390    type Error = ();
391
392    fn try_from(s: &str) -> Result<Self, <Self as TryFrom<&str>>::Error> {
393        Self::from_wire(s).ok_or(())
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400
401    #[test]
402    fn semantic_all_enumerates_every_variant_in_declaration_order() {
403        // Fail-before-pass-after pin on the [`Semantic::ALL`] slice:
404        // the slice must list every one of the 15 variants in
405        // declaration order (Keyword → Symbol → KeywordArg → String →
406        // Number → Literal → Comment → Accent → Muted → Error →
407        // Warning → Info → Hint → Added → Removed → Unchanged). Peer
408        // of the sibling ALL slices on the closed-set typed-enum
409        // discriminator axes ([`caixa_core::CaixaKind::ALL`],
410        // [`caixa_core::supervisor::RestartStrategy::ALL`],
411        // [`caixa_core::supervisor::RestartPolicy::ALL`],
412        // [`caixa_core::aplicacao::PlacementStrategy::ALL`],
413        // [`caixa_core::upgrade::UpgradeInstruction::ALL`]). A future
414        // arm addition (a `Namespace` tier between `Symbol` and
415        // `KeywordArg` for the M4 tatara-lisp module system's
416        // qualified-name semantic-token dispatch, a `Deleted` tier
417        // for a hard-delete-mark distinct from `Removed` the future
418        // 3-way diff surface grows) that lands the arm on the enum
419        // but forgets to extend `ALL` must trip this pin rather than
420        // surface as a downstream consumer's silently-partial
421        // iteration.
422        assert_eq!(
423            Semantic::ALL,
424            &[
425                Semantic::Keyword,
426                Semantic::Symbol,
427                Semantic::KeywordArg,
428                Semantic::String,
429                Semantic::Number,
430                Semantic::Literal,
431                Semantic::Comment,
432                Semantic::Accent,
433                Semantic::Muted,
434                Semantic::Error,
435                Semantic::Warning,
436                Semantic::Info,
437                Semantic::Hint,
438                Semantic::Added,
439                Semantic::Removed,
440                Semantic::Unchanged,
441            ],
442        );
443        // Also pin the per-arm `IsVariant`-derived partition: every
444        // arm in `ALL` must satisfy exactly one of the 15 generated
445        // arm-discriminator predicates.
446        for variant in Semantic::ALL {
447            let row = [
448                variant.is_keyword(),
449                variant.is_symbol(),
450                variant.is_keyword_arg(),
451                variant.is_string(),
452                variant.is_number(),
453                variant.is_literal(),
454                variant.is_comment(),
455                variant.is_accent(),
456                variant.is_muted(),
457                variant.is_error(),
458                variant.is_warning(),
459                variant.is_info(),
460                variant.is_hint(),
461                variant.is_added(),
462                variant.is_removed(),
463                variant.is_unchanged(),
464            ];
465            let hits = row.iter().filter(|b| **b).count();
466            assert_eq!(
467                hits, 1,
468                "Semantic::{variant:?} must satisfy exactly one of the \
469                 15 is_* arm-discriminator predicates; got {row:?}",
470            );
471        }
472    }
473
474    #[test]
475    fn semantic_is_variant_predicates_partition_the_arm_set() {
476        // Fail-before-pass-after pin on the [`gen_platform::IsVariant`]
477        // derive: for each of the 15 variants, exactly one of the
478        // generated is_* predicates returns `true` and the other 14
479        // return `false`. Pre-derive the closed 15-arm partition
480        // lived only inside the two theme overlays' 15-arm match
481        // resolvers; a future rebrand (a `#[is_variant(name = "…")]`
482        // drift, a manual hand-rolled `impl` that shadows the
483        // derive-generated method, an arm rename) trips this pin at
484        // caixa-theme build time rather than surfacing far from the
485        // derive declaration. Peer of the sibling
486        // [`caixa_core::CaixaKind`] `IsVariant` partition pin.
487        // A copy-paste flip that reroutes one arm through the wrong
488        // predicate lane trips at the identity-diagonal assertion,
489        // since each variant's row is generated live from `ALL`'s
490        // declaration order rather than transcribed by hand.
491        for (idx, variant) in Semantic::ALL.iter().enumerate() {
492            let observed: [bool; 16] = [
493                variant.is_keyword(),
494                variant.is_symbol(),
495                variant.is_keyword_arg(),
496                variant.is_string(),
497                variant.is_number(),
498                variant.is_literal(),
499                variant.is_comment(),
500                variant.is_accent(),
501                variant.is_muted(),
502                variant.is_error(),
503                variant.is_warning(),
504                variant.is_info(),
505                variant.is_hint(),
506                variant.is_added(),
507                variant.is_removed(),
508                variant.is_unchanged(),
509            ];
510            let mut expected = [false; 16];
511            expected[idx] = true;
512            assert_eq!(
513                observed, expected,
514                "Semantic::{variant:?} at ALL[{idx}] is_* predicates \
515                 must fire only on their own arm lane (identity \
516                 diagonal); got {observed:?}",
517            );
518        }
519    }
520
521    #[test]
522    fn semantic_as_str_returns_canonical_kebab_case_per_arm() {
523        // Fail-before-pass-after per-arm byte-string pin on
524        // [`Semantic::as_str`] — the substrate-canonical `&'static str`
525        // projection every downstream consumer of the closed 16-arm
526        // partition reaches through. A future arm rename (a `Symbol` →
527        // `Identifier` rebrand tracking a hypothetical LSP-side
528        // `SemanticTokenType` reshuffle, an `Accent` → `Highlight`
529        // rebrand tracking a `blackmatter-shell` classname rework) that
530        // touches the enum arm but forgets to update the paired
531        // `as_str` arm — or vice versa — trips this pin at caixa-theme
532        // build time rather than surfacing as a downstream
533        // `feira lint --list-styles` operator-facing enumeration verb's
534        // silently-renamed row far from the two-declaration site.
535        //
536        // Kebab-case matches the peer [`gen_platform::IsVariant`]-
537        // derived kebab discriminant convention the sibling closed-set
538        // typed enums already emit on their canonical byte-string
539        // projection axis.
540        assert_eq!(Semantic::Keyword.as_str(), "keyword");
541        assert_eq!(Semantic::Symbol.as_str(), "symbol");
542        assert_eq!(Semantic::KeywordArg.as_str(), "keyword-arg");
543        assert_eq!(Semantic::String.as_str(), "string");
544        assert_eq!(Semantic::Number.as_str(), "number");
545        assert_eq!(Semantic::Literal.as_str(), "literal");
546        assert_eq!(Semantic::Comment.as_str(), "comment");
547        assert_eq!(Semantic::Accent.as_str(), "accent");
548        assert_eq!(Semantic::Muted.as_str(), "muted");
549        assert_eq!(Semantic::Error.as_str(), "error");
550        assert_eq!(Semantic::Warning.as_str(), "warning");
551        assert_eq!(Semantic::Info.as_str(), "info");
552        assert_eq!(Semantic::Hint.as_str(), "hint");
553        assert_eq!(Semantic::Added.as_str(), "added");
554        assert_eq!(Semantic::Removed.as_str(), "removed");
555        assert_eq!(Semantic::Unchanged.as_str(), "unchanged");
556    }
557
558    #[test]
559    fn semantic_as_str_projections_are_all_distinct_across_arms() {
560        // Fail-before-pass-after pin on the injectivity of
561        // [`Semantic::as_str`]'s projection — no two arms may share
562        // their canonical kebab byte-string, since a future consumer
563        // that keys a per-arm dispatch table off the projection (a
564        // `HashMap::<&str, _>::from_iter(Semantic::ALL.iter().map(|s|
565        // (s.as_str(), …)))` style-lookup, a future `caixa-lsp`
566        // `SemanticTokenType::new(sem.as_ref())` registration table
567        // keyed by kebab identifier, a future `feira lint --list-styles`
568        // one-row-per-arm enumeration table) would silently collapse
569        // the two colliding arms onto one entry, dropping the second
570        // insertion. A future arm addition (a `Namespace` tier between
571        // `Symbol` and `KeywordArg` for the M4 tatara-lisp module
572        // system's qualified-name semantic-token dispatch, a `Deleted`
573        // tier for a hard-delete-mark distinct from `Removed` a future
574        // 3-way diff surface grows) that lands the arm on the enum and
575        // reuses a peer arm's kebab identifier (a copy-paste-derived
576        // `"removed"` on the new `Deleted` arm) trips this pin rather
577        // than surfacing far from the arm addition site.
578        let mut projections: Vec<&'static str> = Semantic::ALL.iter().map(|s| s.as_str()).collect();
579        let before = projections.len();
580        projections.sort_unstable();
581        projections.dedup();
582        assert_eq!(
583            projections.len(),
584            before,
585            "Semantic::as_str must be injective across ALL — collisions: \
586             {projections:?}",
587        );
588    }
589
590    #[test]
591    fn semantic_display_and_as_ref_str_route_through_as_str_accessor() {
592        // Fail-before-pass-after three-path convergence pin on the
593        // substrate-wide `(as_str, AsRef<str>, Display)` canonical-
594        // projection triple for the caixa-theme [`Semantic`] closed-set
595        // fieldless typed enum. For every arm in [`Semantic::ALL`], the
596        // paired [`std::fmt::Display`] impl + [`AsRef<str>`] impl + the
597        // substrate-canonical [`Semantic::as_str`] `pub const fn`
598        // scalar accessor must resolve to the same `&'static str`
599        // per arm. Peer of the sibling three-path-convergence pins the
600        // substrate carries on the closed-set typed enums the prior
601        // lifts converged onto
602        // (`restart_strategy_display_routes_through_as_str_helper` /
603        // `restart_strategy_as_ref_str_routes_through_as_str_accessor`
604        // on [`caixa_core::supervisor::RestartStrategy`],
605        // `ferrite_runtime_display_and_as_ref_str_route_through_variant_slug_accessor`
606        // on `caixa_provedor::FerriteRuntime`, and the analogous pins
607        // on `caixa_lint::Severity` / `caixa_lint::FixSafety` /
608        // `caixa_arch::InvariantKind` / `caixa_arch::ArchVerdict`).
609        //
610        // A future accidental split (a hand-rolled `impl fmt::Display`
611        // that shadows this route through a divergent per-arm match, an
612        // `impl AsRef<str>` that returns the compiler-derived `Debug`
613        // string via `format!("{:?}", self)` — allocating and diverging
614        // on every arm — or a `#[serde(rename_all = "…")]` attribute
615        // drift that quietly forks the projection) trips this pin at
616        // caixa-theme build time rather than surfacing as a downstream
617        // consumer's silently-forked per-Semantic dispatch far from the
618        // trait-impl declaration site.
619        for &sem in Semantic::ALL {
620            let via_as_str: &str = sem.as_str();
621            let via_display: String = format!("{sem}");
622            let via_as_ref: &str = <Semantic as AsRef<str>>::as_ref(&sem);
623            assert_eq!(
624                via_display, via_as_str,
625                "Semantic::{sem:?} — Display routes off `as_str`; got \
626                 Display={via_display:?} vs as_str={via_as_str:?}",
627            );
628            assert_eq!(
629                via_as_ref, via_as_str,
630                "Semantic::{sem:?} — AsRef<str> routes off `as_str`; got \
631                 AsRef={via_as_ref:?} vs as_str={via_as_str:?}",
632            );
633        }
634    }
635
636    #[test]
637    fn semantic_as_str_is_usable_in_const_context() {
638        // The [`Semantic::as_str`] accessor is declared `pub const fn`,
639        // matching the peer closed-set typed enums' canonical
640        // `&'static str` projection accessors
641        // ([`caixa_core::CaixaKind::as_str`],
642        // [`caixa_core::supervisor::RestartStrategy::as_str`],
643        // [`caixa_core::aplicacao::PlacementStrategy::as_str`],
644        // `caixa_provedor::FerriteRuntime::variant_slug`). Pin the
645        // same posture with a `const {}` assertion block so a future
646        // accidental downgrade to non-`const` (an added runtime helper
647        // reachable only from a non-`const` context) trips at
648        // caixa-theme build time rather than surfacing as a downstream
649        // `const`-context regression far from the accessor
650        // declaration.
651        const KEYWORD: &str = Semantic::Keyword.as_str();
652        const ERROR: &str = Semantic::Error.as_str();
653        const UNCHANGED: &str = Semantic::Unchanged.as_str();
654        const { assert!(KEYWORD.as_bytes()[0] == b'k') };
655        const { assert!(ERROR.as_bytes()[0] == b'e') };
656        const { assert!(UNCHANGED.as_bytes()[0] == b'u') };
657    }
658
659    #[test]
660    fn semantic_from_wire_accepts_every_as_str_output() {
661        // Fail-before-pass-after per-arm accept pin on the newly lifted
662        // [`Semantic::from_wire`] reverse projection: every arm in
663        // [`Semantic::ALL`] must parse back through `from_wire` when fed
664        // its own [`Semantic::as_str`] output, landing on
665        // `Some(same_variant)`. A regression that hand-rolled either
666        // side's per-arm match without threading through the shared
667        // 16-string closed set would silently disagree on any future
668        // arm rename (a `Symbol` → `Identifier` rebrand tracking a
669        // hypothetical LSP-side `SemanticTokenType` reshuffle, an
670        // `Accent` → `Highlight` rebrand tracking a `blackmatter-shell`
671        // classname rework) or new arm the theme grows (a `Namespace`
672        // tier between `Symbol` and `KeywordArg` for the M4 tatara-lisp
673        // module system's qualified-name semantic-token dispatch, a
674        // `Deleted` tier for a hard-delete-mark distinct from `Removed`
675        // the future 3-way diff surface grows) and this pin flags it at
676        // caixa-theme build time rather than at a downstream
677        // `feira lint --list-styles` operator-facing enumeration verb's
678        // silent tag misclassification.
679        //
680        // Peer of the sibling
681        // `caixa_lint::diagnostic::tests::severity_from_wire_accepts_every_as_str_output`
682        // (5afff0e) /
683        // `caixa_lint::diagnostic::tests::fix_safety_from_wire_accepts_every_as_str_output`
684        // (bd505a1) /
685        // `caixa_arch::report::tests::arch_verdict_from_wire_accepts_every_as_str_output`
686        // (6afe564) /
687        // `caixa_arch::invariants::tests::invariant_kind_from_wire_accepts_every_as_str_output`
688        // (b9e4e61) round-trip pins on the peer caixa-lint / caixa-arch
689        // closed-set-enum reverse-projection axes, and of the sibling
690        // `caixa_core::kind::tests::caixa_kind_wire_round_trips_through_from_wire`
691        // (2aa6d23) /
692        // `caixa_core::dialeto::tests::caixa_dialeto_from_wire_accepts_every_as_str_output`
693        // (d0e65ea) /
694        // `caixa_core::aplicacao::tests::placement_strategy_from_wire_accepts_every_lifted_constant`
695        // (18c7342) /
696        // `caixa_core::dep::tests::dep_list_round_trips_through_as_str_and_from_wire`
697        // (45ee563) /
698        // `caixa_core::render::tests::path_shape_violation_from_wire_accepts_every_as_str_output`
699        // (aebd9c6) round-trip pins on the sibling caixa-core closed-
700        // set typed-enum reverse-projection axes.
701        for &variant in Semantic::ALL {
702            let wire = variant.as_str();
703            let parsed = Semantic::from_wire(wire).unwrap_or_else(|| {
704                panic!(
705                    "Semantic::from_wire({wire:?}) must accept every \
706                     Semantic::as_str output — got None for the wire \
707                     byte-string of {variant:?}"
708                )
709            });
710            assert_eq!(
711                parsed, variant,
712                "Semantic::from_wire(Semantic::{variant:?}.as_str()) \
713                 must return Semantic::{variant:?} — the (as_str, \
714                 from_wire) pair must form a total round-trip on the \
715                 closed 16-arm Semantic arm-set",
716            );
717        }
718    }
719
720    #[test]
721    fn semantic_from_wire_rejects_unknown_byte_strings() {
722        // Rejection pin on the [`Semantic::from_wire`] parser's accept-
723        // set: any string outside the 16-arm [`Semantic::as_str`] output
724        // set must return `None`. A future accidental widening of the
725        // accept-set (a case-insensitive match that accepts `"KEYWORD"`
726        // / `"Keyword"`, a silent acceptance of the pre-lift PascalCase
727        // Debug-derived shapes `"Keyword"` / `"KeywordArg"` /
728        // `"Unchanged"` on the wire axis, a snake_case drift accepting
729        // `"keyword_arg"` beside the canonical kebab-case
730        // `"keyword-arg"`, a Levenshtein-forgiving arm-lookup that
731        // admits `"kewyord"` typos, a silent absorption of a hypothetical
732        // future `Namespace` / `Deleted` arm before it lands on the enum
733        // and its paired [`Semantic::as_str`] emitter arm) would
734        // silently drift the parser's accept-set from the emitter's — a
735        // downstream style-report re-loader that bound a prior report's
736        // [`Self::as_str`] output back to the typed enum through this
737        // parser would then bind a malformed byte-string to a plausibly-
738        // wrong typed arm the caller does not route through any
739        // fallback, silently misclassifying the reloaded row.
740        //
741        // Peer of the sibling
742        // `caixa_lint::diagnostic::tests::severity_from_wire_rejects_unknown_byte_strings`
743        // (5afff0e) /
744        // `caixa_lint::diagnostic::tests::fix_safety_from_wire_rejects_unknown_byte_strings`
745        // (bd505a1) /
746        // `caixa_arch::report::tests::arch_verdict_from_wire_rejects_unknown_byte_strings`
747        // (6afe564) /
748        // `caixa_arch::invariants::tests::invariant_kind_from_wire_rejects_unknown_byte_strings`
749        // (b9e4e61) rejection pins on the peer caixa-lint / caixa-arch
750        // axes, and of the sibling
751        // `caixa_kind_from_wire_rejects_unknown_byte_strings` (2aa6d23),
752        // `caixa_dialeto_from_wire_rejects_unknown_byte_strings`
753        // (d0e65ea),
754        // `placement_strategy_from_wire_rejects_unknown_byte_strings`
755        // (18c7342),
756        // `dep_list_from_wire_returns_none_on_unknown_wire_scalar`
757        // (45ee563), and
758        // `path_shape_violation_from_wire_rejects_unknown_byte_strings`
759        // (aebd9c6) rejection pins on the sibling caixa-core axes.
760        //
761        // The rejection set also covers overlapping-byte-string tags
762        // from peer axes: caixa-lint `Severity::as_str` outputs
763        // `"error"`/`"warning"`/`"info"`/`"hint"` and caixa-arch
764        // `InvariantKind::as_str` outputs `"safety"`/`"compliance"`
765        // share zero canonical byte-strings with the widened 16-arm
766        // Semantic set here — a widened parser that admitted the peer's
767        // arm on the sibling axis would still not admit an arm foreign
768        // to the caixa-theme semantic-style discriminator's own accept-
769        // set. Yet four peer-axis strings DO overlap with the
770        // caixa-theme set here (`Severity`'s
771        // `"error"`/`"warning"`/`"info"`/`"hint"` map identically onto
772        // the caixa-theme diagnostic-severity sub-region
773        // `Semantic::Error`/`Warning`/`Info`/`Hint`) — a widened parser
774        // that admitted them under a different arm would collapse the
775        // two axes and silently mislabel; the pin excludes those four
776        // from the rejection set precisely because they must accept.
777        for bad in [
778            "",
779            " ",
780            "Keyword",
781            "KEYWORD",
782            "Symbol",
783            "SYMBOL",
784            "KeywordArg",
785            "keyword_arg",
786            "keywordarg",
787            "String",
788            "STRING",
789            "Number",
790            "Literal",
791            "Comment",
792            "Accent",
793            "Muted",
794            "Error",
795            "ERROR",
796            "Warning",
797            "WARNING",
798            "Info",
799            "INFO",
800            "Hint",
801            "HINT",
802            "Added",
803            "ADDED",
804            "Removed",
805            "REMOVED",
806            "Unchanged",
807            "UNCHANGED",
808            "kewyord",
809            "sym",
810            "kw",
811            "str",
812            "num",
813            "lit",
814            "cmt",
815            "safe",
816            "unsafe",
817            "safety",
818            "compliance",
819            "proven",
820            "rejected",
821            "namespace",
822            "deleted",
823            "highlight",
824            "identifier",
825            "keyword ",
826            " keyword",
827            "keyword\n",
828            "keyword\t",
829            "keyword-arg ",
830            " keyword-arg",
831            "added ",
832            " added",
833            "unchanged ",
834            " unchanged",
835        ] {
836            assert!(
837                Semantic::from_wire(bad).is_none(),
838                "Semantic::from_wire({bad:?}) must return None — the \
839                 parser's accept-set is exactly the 16 Semantic::as_str \
840                 outputs; a widening would silently split the parser's \
841                 accept-set from the emitter's arm-set",
842            );
843        }
844    }
845
846    #[test]
847    fn semantic_is_variant_predicates_are_const_fn() {
848        // The [`gen_platform::IsVariant`] derive emits `const fn`
849        // predicates on the peer [`caixa_core::CaixaKind`] /
850        // [`caixa_core::upgrade::UpgradeInstruction`] /
851        // [`caixa_core::supervisor::RestartStrategy`] /
852        // [`caixa_core::supervisor::RestartPolicy`] closed-set typed
853        // enums — pin the same posture on [`Semantic`] so a future
854        // accidental downgrade to non-`const` (an added runtime helper
855        // reachable only from a non-`const` context, a manual hand-
856        // rolled `impl` that shadows the derive-generated method)
857        // trips at caixa-theme build time rather than surfacing as a
858        // downstream `const`-context regression far from the derive
859        // declaration.
860        const { assert!(Semantic::Keyword.is_keyword()) };
861        const { assert!(Semantic::Error.is_error()) };
862        const { assert!(Semantic::Added.is_added()) };
863        const { assert!(Semantic::Unchanged.is_unchanged()) };
864    }
865
866    #[test]
867    fn semantic_try_from_str_routes_through_from_wire_accessor() {
868        // Fail-before-pass-after byte-parity pin on the newly lifted
869        // `impl TryFrom<&str> for Semantic` — asserts the standard-
870        // library trait impl and the substrate-primitive
871        // [`super::Semantic::from_wire`] `Option<Self>` accessor
872        // resolve to the same 16-arm accept-set across every arm the
873        // exhaustive [`super::Semantic::ALL`] slice enumerates. Any
874        // future silent detour that routes the trait impl through a
875        // divergent projection (a per-arm inline `match s { "keyword"
876        // => Ok(Self::Keyword), … }` re-inlining that opens a
877        // compile-time link to the un-lifted arm-literal, a silent
878        // case-fold that admits `"Keyword"` / `"KEYWORD"` and would
879        // collide the canonical-lowercase accept-set the emitter
880        // dispatches on) trips at caixa-theme test time under
881        // `assert_eq!` rather than at a downstream
882        // `impl TryFrom<&str>`-bound consumer's silent split. Sweeps
883        // every one of the 16 arms [`super::Semantic::ALL`] carries so
884        // no arm's projection is covered only by the sibling method-
885        // named `from_wire` path.
886        //
887        // Peer of the sibling
888        // [`caixa_core::kind::tests::caixa_kind_try_from_str_routes_through_from_wire_accessor`]
889        // (3c83606),
890        // [`caixa_core::dialeto::tests::caixa_dialeto_try_from_str_routes_through_from_wire_accessor`]
891        // (bf33136),
892        // `placement_strategy_try_from_str_routes_through_from_wire_accessor`
893        // (6fd00cd),
894        // `rate_limit_unit_try_from_str_routes_through_from_suffix_accessor`
895        // (bf78400),
896        // `path_shape_violation_try_from_str_routes_through_from_wire_accessor`
897        // (e67e48a),
898        // `caixa_arch::invariants::tests::invariant_kind_try_from_str_routes_through_from_wire_accessor`
899        // (e21a857),
900        // `caixa_arch::report::tests::arch_verdict_try_from_str_routes_through_from_wire_accessor`
901        // (0a4cc45),
902        // `caixa_lint::diagnostic::tests::severity_try_from_str_routes_through_from_wire_accessor`
903        // (a7bf74c), and
904        // `caixa_lint::diagnostic::tests::fix_safety_try_from_str_routes_through_from_wire_accessor`
905        // (df86c94) — extends the trait-idiomatic reverse-projection
906        // axis onto the first closed-set fieldless typed enum on the
907        // caixa-theme surface (the semantic-style axis).
908        for &variant in Semantic::ALL {
909            let wire = variant.as_str();
910            assert_eq!(
911                <Semantic as TryFrom<&str>>::try_from(wire),
912                Ok(variant),
913                "TryFrom<&str> impl on Semantic must round-trip \
914                 Semantic::{variant:?}.as_str() = {wire:?} back to \
915                 Ok(Semantic::{variant:?}) — divergence from \
916                 Semantic::from_wire signals a silent detour off the \
917                 substrate-primitive accessor",
918            );
919            assert_eq!(
920                <Semantic as TryFrom<&str>>::try_from(wire).ok(),
921                Semantic::from_wire(wire),
922                "TryFrom<&str> ok()-projection on {wire:?} must \
923                 byte-equal Semantic::from_wire on the same input",
924            );
925        }
926    }
927
928    #[test]
929    fn semantic_try_from_str_rejects_unknown_byte_strings() {
930        // Rejection witness on the `impl TryFrom<&str> for Semantic` —
931        // sweeps a candidate set of byte-strings outside the 16-arm
932        // canonical-lowercase kebab wire accept-set the sibling
933        // [`super::Semantic::as_str`] emits and asserts every one
934        // lands on `Err(())`, so a future accidental widening of the
935        // trait impl's accept-set (a stray additional
936        // `_ if s.eq_ignore_ascii_case("keyword") => Ok(…)` case-fold
937        // path, a silent acceptance of the pre-lift PascalCase Debug-
938        // derived shapes `"Keyword"` / `"Symbol"` / `"KeywordArg"` on
939        // the wire axis, a Levenshtein-forgiving arm-lookup that
940        // admits `"kwd"` / `"sym"` / `"kw"` typos — the exact form a
941        // `format!("{:?}", …).to_lowercase()` round-trip on the paired
942        // [`std::fmt::Debug`] derive would otherwise land on) trips at
943        // caixa-theme test time. The candidate set includes the empty
944        // string, whitespace-only padding, uppercase / PascalCase
945        // rebrand candidates, Levenshtein-neighbor typos, sibling
946        // closed-set-enum canonical tags not shared with this axis
947        // (peer `caixa_lint::diagnostic::FixSafety::as_str` two-arm
948        // `"safe"` / `"unsafe"`, peer `caixa_arch::InvariantKind::as_str`
949        // three-arm `"safety"` / `"compliance"`, peer
950        // `caixa_arch::ArchVerdict::as_str` two-arm `"proven"` /
951        // `"rejected"`), the trajectory-item candidates
952        // (`"namespace"`, `"deleted"`) the sibling [`Semantic::ALL`]
953        // doc block already names, whitespace-padded canonical tags,
954        // and CamelCase spellings of the multi-word `KeywordArg`
955        // variant (`"KeywordArg"`, `"keywordarg"`, `"keyword_arg"`,
956        // `"keyword.arg"`) that would silently admit
957        // if the accept-set widened to a case-fold or separator-
958        // normalization rule.
959        //
960        // Peer of the sibling
961        // `caixa_kind_try_from_str_rejects_unknown_byte_strings`
962        // (3c83606),
963        // `caixa_dialeto_try_from_str_rejects_unknown_byte_strings`
964        // (bf33136),
965        // `rate_limit_unit_try_from_str_rejects_unknown_byte_strings`
966        // (bf78400),
967        // `path_shape_violation_try_from_str_rejects_unknown_byte_strings`
968        // (e67e48a),
969        // `invariant_kind_try_from_str_rejects_unknown_byte_strings`
970        // (e21a857),
971        // `arch_verdict_try_from_str_rejects_unknown_byte_strings`
972        // (0a4cc45),
973        // `severity_try_from_str_rejects_unknown_byte_strings`
974        // (a7bf74c), and
975        // `fix_safety_try_from_str_rejects_unknown_byte_strings`
976        // (df86c94) rejection pins on the sibling closed-set typed-
977        // enum trait-idiomatic reverse-projection axes.
978        for bad in [
979            "",
980            " ",
981            "Keyword",
982            "KEYWORD",
983            "Symbol",
984            "KeywordArg",
985            "keywordarg",
986            "keyword_arg",
987            "keyword.arg",
988            "String",
989            "Number",
990            "Literal",
991            "Comment",
992            "Accent",
993            "Muted",
994            "Error",
995            "ERROR",
996            "Warning",
997            "Info",
998            "Hint",
999            "Added",
1000            "Removed",
1001            "Unchanged",
1002            "kwd",
1003            "sym",
1004            "kw",
1005            "str",
1006            "num",
1007            "lit",
1008            "cmt",
1009            "safe",
1010            "unsafe",
1011            "safety",
1012            "compliance",
1013            "proven",
1014            "rejected",
1015            "namespace",
1016            "deleted",
1017            "highlight",
1018            "identifier",
1019            "keyword ",
1020            " keyword",
1021            "keyword\n",
1022            "keyword\t",
1023            "keyword-arg ",
1024            " keyword-arg",
1025            "added ",
1026            " added",
1027            "unchanged ",
1028            " unchanged",
1029        ] {
1030            assert_eq!(
1031                <Semantic as TryFrom<&str>>::try_from(bad),
1032                Err(()),
1033                "TryFrom<&str> for Semantic({bad:?}) must return \
1034                 Err(()) — the trait impl's accept-set is exactly the \
1035                 16 Semantic::as_str outputs; a widening would \
1036                 silently split the trait impl's accept-set from the \
1037                 emitter's arm-set",
1038            );
1039        }
1040    }
1041
1042    #[test]
1043    fn semantic_try_from_str_and_from_wire_partition_the_accept_set() {
1044        // Cross-axis partition pin: the trait-idiomatic
1045        // [`TryFrom<&str>`] and the method-named
1046        // [`super::Semantic::from_wire`] projections must return
1047        // equivalent decisions on every input — the trait impl's
1048        // `.ok()` project-out from `Result<Self, ()>` and the method's
1049        // `Option<Self>` return must byte-equal each other on both
1050        // accepts and rejects. A future silent bifurcation (the trait
1051        // impl gaining a case-fold path the method does not carry, the
1052        // method gaining a synonym alias the trait impl does not
1053        // honor) trips at caixa-theme test time under a single pin
1054        // rather than at a downstream generic-bound consumer that
1055        // dispatches through one axis while a peer dispatches through
1056        // the other. Sweeps both the 16-arm accept-set (via
1057        // [`super::Semantic::ALL`] threaded through
1058        // [`super::Semantic::as_str`]) and a canonical rejection
1059        // sample so both halves of the partition are covered. Peer of
1060        // the sibling
1061        // `severity_try_from_str_and_from_wire_partition_the_accept_set`
1062        // (a7bf74c) and
1063        // `fix_safety_try_from_str_and_from_wire_partition_the_accept_set`
1064        // (df86c94) partition pins.
1065        for &variant in Semantic::ALL {
1066            let wire = variant.as_str();
1067            assert_eq!(
1068                <Semantic as TryFrom<&str>>::try_from(wire).ok(),
1069                Semantic::from_wire(wire),
1070                "TryFrom<&str>::ok() and from_wire must agree on \
1071                 Semantic::{variant:?}.as_str() = {wire:?}",
1072            );
1073        }
1074        for bad in [
1075            "",
1076            "Keyword",
1077            "unknown",
1078            "safety",
1079            "safe",
1080            "proven",
1081            "namespace",
1082            "keywordarg",
1083        ] {
1084            assert_eq!(
1085                <Semantic as TryFrom<&str>>::try_from(bad).ok(),
1086                Semantic::from_wire(bad),
1087                "TryFrom<&str>::ok() and from_wire must agree on the \
1088                 rejection outcome for {bad:?}",
1089            );
1090        }
1091    }
1092}