Skip to main content

brink_ir/hir/
diagnostics.rs

1//! Diagnostic codes, severities, and the [`Diagnostic`] record.
2//!
3//! Split out of [`super::types`] (issue #652). The stable [`DiagnosticCode`]
4//! catalogue and its lookup tables are touched by every diagnostic-adding
5//! change, while the HIR node definitions next door are touched by every
6//! language-feature change; keeping the two in separate files keeps those
7//! streams of work from colliding.
8//!
9//! Everything here is re-exported through `hir::*`, so consumers keep
10//! importing these names from `brink_ir::hir` exactly as before.
11
12use rowan::TextRange;
13
14use super::types::FileId;
15
16/// A diagnostic produced during HIR lowering or cross-file analysis.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Diagnostic {
19    /// Which file this diagnostic belongs to.
20    pub file: FileId,
21    /// The source span this diagnostic points at.
22    pub range: TextRange,
23    /// Human-readable message describing the problem.
24    pub message: String,
25    /// Structured error code for documentation and tooling.
26    pub code: DiagnosticCode,
27}
28
29/// How seriously a diagnostic should be treated by a consumer (CLI renderer,
30/// LSP client, editor diagnostics panel).
31///
32/// Until issue #1674, no `DiagnosticCode`'s *default* severity
33/// ([`DiagnosticCode::severity`]) was ever `Info` or `Hint` — the two
34/// advisory tiers existed only so a project's `[lints]` table
35/// (`brink-project-config`'s `LintLevel::Info`/`LintLevel::Hint`, resolved
36/// through `brink_analyzer::effective_severity`) could opt a `Warning`-default
37/// code down to one when a squiggle is too loud (issue #1162). Moving any
38/// *existing* code's default into one of these tiers is a separate decision,
39/// deliberately not made by the issue that introduced the tiers.
40/// [`DiagnosticCode::E157`] (issue #1674) is the first code to default here
41/// directly — RULED "off or info by default" for a narrow, precision-tuned
42/// lint that must not nag a single-shot project.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44pub enum Severity {
45    /// Blocks compilation / is surfaced as a hard failure.
46    Error,
47    /// Non-fatal; the default tier for advisory diagnostics until a
48    /// `[lints]` override says otherwise.
49    Warning,
50    /// Advisory, LSP `DiagnosticSeverity::INFORMATION` — worth telling the
51    /// author about, but not something they need to act on.
52    Info,
53    /// Advisory and quiet, LSP `DiagnosticSeverity::HINT` — the tier IDEs use
54    /// for things like unused-symbol dimming, where even an info-level
55    /// squiggle is too loud.
56    Hint,
57}
58
59/// Stable error codes for brink diagnostics.
60///
61/// Codes are never reused once assigned. Each code has a corresponding
62/// explanation file at `docs/diagnostics/Exxx.md`.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64pub enum DiagnosticCode {
65    // ── Containers ──────────────────────────────────────────────
66    /// Knot definition is missing a name.
67    E001,
68    /// Stitch definition is missing a name.
69    E002,
70    /// Knot or stitch parameter is missing a name.
71    E003,
72
73    // ── Declarations ────────────────────────────────────────────
74    /// `VAR` declaration is missing a name.
75    E004,
76    /// `VAR` declaration is missing an initializer.
77    E005,
78    /// `CONST` declaration is missing a name.
79    E006,
80    /// `CONST` declaration is missing an initializer.
81    E007,
82    /// `LIST` declaration is missing a name.
83    E008,
84    /// `LIST` member is missing a name.
85    E009,
86    /// `EXTERNAL` declaration is missing a name.
87    E010,
88    /// RETIRED (lane-A audit, #709) — the parser always materializes a
89    /// `FILE_PATH` node inside `INCLUDE_STMT` (possibly empty) and reports
90    /// missing path as E037 (`parser/declaration.rs::include_statement`).
91    /// Code kept reserved, not reused.
92    E011,
93
94    // ── Control flow ────────────────────────────────────────────
95    /// Divert is missing a target.
96    E012,
97    /// RETIRED (lane-A audit, #709) — `parser/divert.rs::path` always creates
98    /// a `PATH` node (empty on error + E037), so `ThreadStart::target()` is
99    /// never `None`. Code kept reserved, not reused.
100    E013,
101    /// Logic line has no effect (bare `~`).
102    E014,
103
104    // ── Expressions ─────────────────────────────────────────────
105    /// Expression is missing an operand.
106    E015,
107    /// Unknown or unsupported operator.
108    E016,
109    /// Function call is missing a name.
110    E017,
111    /// RETIRED (lane-A audit, #709) — `parser/divert.rs::path` always creates
112    /// a `PATH` node (empty on error + E037), so `DivertTargetExpr::target()`
113    /// is never `None`. Code kept reserved, not reused.
114    E018,
115
116    // ── Choices ─────────────────────────────────────────────────
117    /// RETIRED (lane-A audit, #709) — the parser only builds a `CHOICE` node
118    /// after seeing a bullet token, so a bullet-less choice CST cannot exist.
119    /// Code kept reserved, not reused.
120    E019,
121
122    // ── Inline logic ────────────────────────────────────────────
123    /// Inline conditional is missing a condition.
124    E020,
125    /// Inline sequence has no branches.
126    E021,
127
128    // ── Cross-file analysis ──────────────────────────────────────
129    /// Duplicate knot definition.
130    E022,
131    /// Duplicate variable/constant definition.
132    E023,
133    /// Unresolved divert target.
134    E024,
135    /// Unresolved variable reference.
136    E025,
137    /// Duplicate list item.
138    E026,
139    /// Ambiguous bare list item reference.
140    E027,
141    /// RETIRED (lane-A audit, #709) — circular INCLUDE is detected at the
142    /// discovery phase and surfaces as `CompileError::CircularInclude`, not as
143    /// a per-construct diagnostic. Code kept reserved, not reused.
144    E028,
145
146    // ── Compile errors ────────────────────────────────────────────
147    /// Choice nested in conditional without explicit divert.
148    E029,
149
150    // ── Warnings ─────────────────────────────────────────────────
151    /// String interpolation in constant initializer is ignored.
152    E030,
153    /// Function call argument count mismatch.
154    E031,
155
156    // ── Structural validation ───────────────────────────────────
157    /// Return statement outside function.
158    E032,
159    /// Unreachable code after divert.
160    E033,
161    /// Choice set has only fallback choices.
162    E034,
163    /// Name shadows a built-in function.
164    E035,
165    /// Expected diagnostic not produced (`// brink-expect`).
166    E036,
167    /// Syntax error reported by the parser (malformed source).
168    E037,
169    /// Malformed `///` doc-comment tag on a declaration.
170    E038,
171
172    // ── Host manifest (external-function vocabulary) ─────────────
173    /// Registered host manifest disagrees with the ink `EXTERNAL` arity.
174    E039,
175    /// Doc-comment / manifest references an unknown semantic type.
176    E040,
177    /// External call argument type mismatches the manifest signature.
178    E041,
179    /// External call argument violates a closed-domain constraint.
180    E042,
181    /// Well-formed `///` doc-comment tag that doesn't apply to this
182    /// declaration kind (e.g. `@kind` on a knot, `@param` on a VAR).
183    E043,
184
185    // ── Directives (`#@…` — docs/directive-annotations-spec.md) ──
186    /// Unknown directive name (e.g. `#@locale`).
187    E044,
188    /// Directive has no valid target in this position.
189    E045,
190    /// Directive contains dynamic inline logic — directives are static text.
191    E046,
192    /// Directive must be the only tag on its line.
193    E047,
194    /// Duplicate directive on one target.
195    E048,
196    /// Directive not supported on this target (e.g. `@local` on CONST).
197    E049,
198    /// Directive does not take arguments or trailing text.
199    E050,
200
201    // ── T1b dialect gate (docs/t1b-surface-spec.md §1) ────────────
202    /// A brink-extension construct (block, sigil literal, indexing) was
203    /// used under the `strict-ink` dialect.
204    E051,
205    /// A brink-extension construct parses and analyzes cleanly under the
206    /// `brink` dialect, but its LIR lowering hasn't landed yet. Originally
207    /// minted for T1b-1 (every T1b construct lowers since T1b-2, #570), then
208    /// revived by T1c-1 (#699) as the `#fn(…)` lowering fence, retired again by
209    /// T1c-2 (#700). **Now the `await` fence** (FS-2,
210    /// docs/flow-suspension-spec.md §3, issue #928): `await <cond>` /
211    /// `while await <cond>` parse to HIR and pass the effect-free purity gate
212    /// (E105), but their runtime spill/restore semantics are FS-3 — every
213    /// `await` construct is fenced here at LIR lowering until that lands. The
214    /// code stays a general "parses/analyzes before its lowering lands" fence,
215    /// reused as each new extension needs it.
216    E052,
217    /// RETIRED (T1b-2, #570) — previously a non-suppressible backstop
218    /// rejecting T1b brink-extension HIR nodes (`LogicBlock`, `ArrayLiteral`,
219    /// `MapLiteral`, `Index`) at LIR lowering. T1b-2 completed real lowering
220    /// for all such constructs, making the backstop obsolete. Code kept
221    /// reserved, not reused, for diagnostic-code stability.
222    E053,
223    /// A block-scoped `temp` (`~ { … }`, docs/t1b-surface-spec.md §2) or
224    /// `for` loop variable shadows an already-visible temp/param — either an
225    /// enclosing `~ { … }` block scope or an outer classic `~ temp`.
226    E054,
227
228    // ── T1b stdlib slice 1 (docs/t1b-surface-spec.md §5) ──────────────
229    /// `push`/`insert`/`remove`'s first argument is not an lvalue (a
230    /// variable, temp, or indexed path) — mutators require a place to
231    /// write the mutated container back into.
232    E055,
233    /// `push`/`insert`/`remove` was used in expression position — they
234    /// return nothing and are only valid as a statement.
235    E056,
236
237    // ── T1b logic blocks (docs/t1b-surface-spec.md §2) ────────────────
238    /// `break`/`continue` used outside any enclosing `while`/`for` loop.
239    E057,
240    /// Collection mutator (`push`/`insert`/`remove`) called with the wrong
241    /// number of arguments — a targeted compile error naming the expected
242    /// signature (replaces the generic `E031` warning + silently-dropped
243    /// RMW lowering, RULED 2026-07-12, see `docs/decision-log.md`).
244    E058,
245
246    // ── Weave-in-inline-content backstop (sibling of #578, #585) ──────
247    /// A choice set, labeled gather block, multi-line conditional, or
248    /// sequence was found nested inside inline content (e.g. a choice's own
249    /// display/bracket/inner text) where it would need a child container
250    /// that position structurally cannot hold.
251    E059,
252
253    // ── Codegen defense-in-depth backstop (#586) ──────────────────────
254    /// `brink-codegen-inkb` refused to emit bytecode for a `Program` that
255    /// violates an invariant an earlier, non-suppressible compiler stage is
256    /// supposed to guarantee (currently: an out-of-loop `LogicBreak`/
257    /// `LogicContinue`, normally rejected at `E057`). Reaching this from a
258    /// normal compile is a compiler bug, not an authoring mistake — this
259    /// code exists so that bug fails loudly instead of silently corrupting
260    /// bytecode.
261    E060,
262
263    // ── TM-2 inline type annotations (docs/typed-mode-spec.md §3) ────
264    /// A type annotation names something that isn't a recognized nominal
265    /// type (`int`/`float`/`bool`/`string`/`divert`/`void`), a `List<L>`
266    /// naming a declared `LIST`, `Array<T>`, or `Map<K, V>` — declared
267    /// struct names arrive in TM-4.
268    E061,
269    /// RETIRED (T1c-1, #699): previously "`fn(T…): R` function-type
270    /// annotation used — parses, but types as reserved until T1c". T1c
271    /// unfroze the form (docs/t1c-spec.md §4: "boundary annotations gain
272    /// the `fn(T…): R` form"), so it now resolves to a real checker type.
273    /// Code kept reserved, not reused, for diagnostic-code stability — no
274    /// longer emitted by any pass.
275    E062,
276    /// A param/return/`VAR` type annotation disagrees with the type
277    /// TM-1's body inference would otherwise derive. Advisory only in this
278    /// slice (gradual policy) — strict-mode severity is TM-3's call.
279    E063,
280
281    // ── TM-3 strict typed-mode policy (docs/typed-mode-spec.md §1/§9-3) ──
282    /// `types = strict` was requested but the project's dialect isn't
283    /// `brink` — strict typing is a brink-dialect extension (its annotation
284    /// syntax is extension syntax), so `types = strict` + `dialect =
285    /// strict-ink` is a config error, not a per-construct diagnostic.
286    E064,
287    /// Under `types = strict`, a def's inferred signature or body slot
288    /// (param, return, or temp) resolved to `Unknown` after the SCC
289    /// fixpoint with no annotation to supply a concrete type — "annotate or
290    /// restructure" (spec §1). Legal under `types = gradual`.
291    E065,
292    /// Under `types = strict`, a def's inferred signature or body slot
293    /// resolved to `Ty::Conflicted` (#627) — the body's own uses disagree
294    /// on the slot's type. Legal (advisory-only, unreported) under `types =
295    /// gradual`.
296    E066,
297    /// Under `types = strict`, a `~ x = f()` / `~ temp x = f()` assigns the
298    /// result of a call whose resolved def is a `void`-returning function
299    /// (docs/typed-mode-spec.md §3: "assigning a `void` call is an error in
300    /// strict mode"). Only the assignment/temp-decl's RHS *root* call is
301    /// checked — a statement-position call (`~ f()`) or a call nested inside
302    /// interpolation is never flagged. Never emitted under `types = gradual`.
303    E067,
304
305    // ── TM-4b structs (docs/typed-mode-spec.md §6) ────────────────────
306    /// A struct construction literal's leading shape name (`Name#{…}`)
307    /// doesn't name any declared `STRUCT`.
308    E068,
309    /// Under `types = strict`, a struct construction literal omits a
310    /// declared field — names the missing field.
311    E069,
312    /// A struct construction literal supplies a field the shape doesn't
313    /// declare — names the extra field.
314    E070,
315    /// Under `types = strict`, a struct construction literal's field
316    /// initializer disagrees with the field's declared type — names the
317    /// field.
318    E071,
319    /// RETIRED (TM-4c, #666): previously a non-suppressible backstop
320    /// rejecting *every* struct construct/field access reaching LIR
321    /// lowering, back when codegen for structs didn't exist yet. Structs
322    /// now lower for real (`E073` is TM-4c's narrower replacement
323    /// backstop). Code kept reserved, not reused, for diagnostic-code
324    /// stability — no longer emitted by any pass.
325    E072,
326    /// Non-suppressible defense-in-depth backstop, mirroring `E053`/`E060`/
327    /// (former) `E072`: a struct construction literal referencing a shape
328    /// name that doesn't resolve to any declared `STRUCT` reached LIR
329    /// lowering. Reaching this from a normal compile means
330    /// `brink-analyzer`'s `resolve::resolve_struct_ref` diagnostic (`E068`)
331    /// was suppressed (`// brink-disable-all`), not a compiler bug on its
332    /// own — `RecordNew` needs a real `ShapeId` at compile time; there is no
333    /// dynamic "construct with unknown shape" concept in this design.
334    E073,
335    /// A field-write target (`p.field = expr`) is a *chained* projection —
336    /// `p.a.b = v` or a mixed `p.a[i].b = v` — never a bare `ident.field`
337    /// on a resolvable root. TM-4c ships single-level field writes only
338    /// (mirrors `lower_indexed_assignment`'s `n == 1` fast path); chained
339    /// writes are an explicit, permanent T1e boundary (`docs/
340    /// typed-mode-spec.md` §6), not a "not implemented yet" gap — this is a
341    /// real, reachable, non-suppressible diagnostic authors can hit by
342    /// writing ordinary (if currently unsupported) ink, not a defensive
343    /// backstop for a suppressed analysis diagnostic.
344    ///
345    /// Also covers a write ending in an *index* rather than a field, whose
346    /// index chain's root is itself a struct-field projection or a mixed
347    /// index/field access — `p.field[i] = v`, `p.a[i].b[j] = v`, or the
348    /// mutator spelling (`push(p.field[i], v)`) — same T1e boundary,
349    /// reached via `reject_field_projection_index_root` (issue #2121) from
350    /// `lower_indexed_assignment`/`lower_lvalue_container_chain` rather than
351    /// `try_lower_field_assignment`.
352    E074,
353
354    // ── decls constant-folding backstops (#673) ───────────────────────
355    /// A struct construction literal used as a `VAR`/`CONST` declaration
356    /// default doesn't match its declared shape: it omits a declared field,
357    /// or supplies one the shape doesn't declare.
358    ///
359    /// A *well-formed* construction literal is a legal declaration default
360    /// (issue #1530): `eval_const_struct_literal` folds it into
361    /// `lir::ConstValue::Record`, which is what makes a struct-typed durable
362    /// global — and therefore the T1e projection-receiver path
363    /// (`docs/t1e-spec.md` §2, which requires a durable root) — spellable at
364    /// all. Before #1530 this code was the blanket refusal of *every* struct
365    /// literal in that position, because `ConstValue` had no record-carrying
366    /// variant.
367    ///
368    /// Mid-story `p = Point#{…}` construction with a mismatched shape is a
369    /// runtime construction fault (`RecordNew` against an invalid shape id,
370    /// value-model-spec §11c's gradual path); a declaration default is baked
371    /// into `StoryData` with no runtime construction step to fault at, so
372    /// this is the compile-time equivalent — a real, non-suppressible error,
373    /// never a half-built record. Under `types = strict` `brink-analyzer`'s
374    /// `structs::check` reports the more precise [`Self::E069`]/
375    /// [`Self::E070`] for the same literal; this backstop is
376    /// policy-independent.
377    E075,
378    /// A map literal used as a `VAR`/`CONST` declaration default has a key
379    /// that isn't a compile-time-constant scalar in the ratified map-key
380    /// domain (int/string/bool — value-model-spec §4). Mid-story map
381    /// construction (`MapNew`) faults on this at runtime
382    /// (`InvalidMapKeyType`); a declaration default has no runtime
383    /// construction step to fault at, so this is the compile-time
384    /// equivalent — a real error, never a silent `Null`.
385    E076,
386    /// An array element, map value, struct field, or `#fn` bound `val` arg
387    /// nested inside a `VAR`/`CONST` declaration default has a source
388    /// expression kind that can never constant-fold — a function call,
389    /// postfix indexing, field access, `++`/`--`, or (#743) a bare
390    /// reference to another `VAR`. A declaration default is baked into
391    /// `StoryData` at compile time, so there is no runtime construction
392    /// step left to evaluate the element at; without this diagnostic the
393    /// element recursed into `eval_const_expr`'s `Path`
394    /// (`SymbolKind::Variable`) arm or catch-all and silently became `Null`
395    /// — #673's silent-`Null` bug one level down, inside the literal (#679
396    /// review; the `Path`-to-`Variable` case one level in was left
397    /// deliberately unchanged there and closed by #743). Keyed off the
398    /// source expression *kind*, never the folded result: an `Expr::Null`
399    /// produced by HIR error recovery must not double-report, and a `Path`
400    /// resolving to a `CONST`/list item/knot/stitch/function still folds
401    /// for real and is not flagged — only a resolved `SymbolKind::Variable`
402    /// (or an unresolved path, left to the analyzer's own diagnostic) is
403    /// exempt from the fold-for-real behavior, matching
404    /// `is_const_foldable_decl_default`'s top-level twin (`E083`). (Since
405    /// #1530 a struct literal at this position folds for real, so a
406    /// never-foldable *field* of a nested construction literal reaches this
407    /// arm exactly as an array element or map value does; before #1530 the
408    /// whole literal was unconditionally `E075` regardless of field
409    /// content.)
410    E077,
411    // ── TM-3 completion: conversion intrinsics (docs/typed-mode-spec.md
412    // §4, maintainer ruling 2026-07-13, issue #659) ──────────────────────
413    /// Under `types = strict`, an unresolved (builtin, not author-shadowed)
414    /// call to `int(x)`/`float(x)` where `x` is statically a divert-target,
415    /// LIST, array, map, or struct construction literal — outside the
416    /// permissive numeric+bool domain (ruling 2: "compile error under
417    /// `types = strict`, runtime fault under gradual"). `string(x)` accepts
418    /// every type and is never checked here.
419    E078,
420
421    // ── T1c function values (docs/t1c-spec.md §2/§8, issue #699) ─────
422    /// `#fn(name, …)`'s target does not resolve to a statically-named
423    /// function definition (`=== function name ===`) — it resolved to a
424    /// variable/list/external/label/non-function knot or stitch, or it
425    /// names a builtin/stdlib intrinsic (which has no definition to take a
426    /// token of). Only fires under `dialect = brink` — under `strict-ink`
427    /// the whole literal is already rejected as extension syntax (E051),
428    /// and content diagnostics on rejected syntax are noise (the TM-2
429    /// suppression precedent, maintainer ruling 2026-07-13).
430    E079,
431    /// A `ref` param of a `#fn` target is not bound in the creation-site
432    /// prefix, or is bound to a non-durable lvalue. All `ref` params must
433    /// be bound at creation, and each must capture a durable cell — a
434    /// global `VAR` (flow-local `#@local` VARs included); a `temp`/param
435    /// is a compile error (temps die with the frame, value-model §11), a
436    /// `CONST` is not a mutable cell, and a bare (unmarked) rvalue/field
437    /// reference is not a cell at all.
438    ///
439    /// T1e (docs/t1e-spec.md §2/§6, issue #831) extends this same code —
440    /// "reuse the E080-family message shape" — to the explicit `ref
441    /// lvalue-path` projection form (`heal(ref npc.hp, 5)`,
442    /// `#fn(heal, ref party[leader].hp)`, `bind(f, ref inventory[idx])`):
443    /// the *root* of the path (the innermost variable the segments walk
444    /// from) must still be a durable global `VAR`, by the same rule —
445    /// `temp`/param roots remain a compile error, a `CONST` root is not a
446    /// mutable cell. A projection's own *segments* (dotted fields, `[…]`
447    /// indices) are a separate check (`E098`, strict-mode statically-known
448    /// shapes only) — this code is the root-durability obligation alone.
449    E080,
450    /// `#fn(name, args…)` binds more arguments than the target declares —
451    /// the bound-arg row is a *prefix* of the declared param row
452    /// (docs/t1c-spec.md §2: "binding more args than the target declares
453    /// is a compile error").
454    E081,
455
456    // ── T1b block-temp scoping (docs/t1b-surface-spec.md §2, issue #680) ──
457    /// A T1b block-scoped `temp` (`~ { … }`) — or a `for`-loop variable,
458    /// which desugars the same way — was referenced (by value or by `ref`
459    /// argument) after its own `~ { … }`/`while`/`for`/`if` block already
460    /// closed. Root-caused for #680: LIR lowering's fallback for "temp not
461    /// currently visible" (used for inklecate-compat forward-reference
462    /// emulation of *classic* temps) previously also caught this case,
463    /// silently emitting a phantom hashed `GetGlobal`/`RefGlobal` id that
464    /// was never registered as a real global — a runtime-only
465    /// `UnresolvedGlobal` fault with no compile diagnostic.
466    E082,
467
468    // ── Declaration-default constness, top level (issue #692, sibling to
469    // #673/#679's collection-element E075/E076/E077) ─────────────────────
470    /// A scalar `VAR`/`CONST` declaration default whose *source expression
471    /// kind* can never be a compile-time constant — a bare reference to
472    /// another `VAR` (`VAR x = someOtherVar`) or a function call
473    /// (`VAR x = f()`), including either wrapped in a prefix/infix
474    /// operation. `eval_const_expr`'s `Path` arm (`SymbolKind::Variable`)
475    /// and its catch-all previously folded both silently to `Null` with no
476    /// diagnostic — the same silent-fold bug #673/#679 fixed one level
477    /// down, inside array/map/struct literals, left unfixed at this top
478    /// level. Keyed off the source expression kind, never the folded
479    /// result, same as `E077`. Does not fire for a `Path` nested inside a
480    /// collection/struct/fn literal (array element, map value, struct
481    /// field, `#fn` argument) — those recurse through their own existing
482    /// `E075`/`E076`/`E077` per-element checks one level in, which
483    /// deliberately still leave a `VAR`-reference gap unchanged (#679 scope
484    /// notes) pending its own follow-up.
485    E083,
486
487    // ── TM-5 struct construction literals (docs/typed-mode-spec.md §6,
488    // decision-log "Struct construction literals: source-order evaluation,
489    // duplicate field is a compile error" 2026-07-14, issues #675/#676) ──
490    /// A struct construction literal (`Name#{…}`) supplies the same field
491    /// name more than once. Previously a silent last-wins: only the final
492    /// initializer's value was placed, and — because the well-formed
493    /// `RecordNew` lowering path discarded every non-placed lowered
494    /// expression tree wholesale — an earlier duplicate's initializer
495    /// (including any observable side effect, e.g. a function call) never
496    /// actually ran at all, with no diagnostic (#675's RCA). Now a real
497    /// compile error naming the repeated field, under both
498    /// `types = gradual` and `types = strict` — unlike `E069`/`E070`/
499    /// `E071` (which need a resolved shape to check missing/extra/mistyped
500    /// fields against, and are strict-mode-only), a duplicate field is a
501    /// structural authoring mistake detectable from the literal alone,
502    /// independent of type-checking policy or whether the shape name even
503    /// resolves.
504    E084,
505
506    // ── M-1 modules (docs/modules-spec.md §1/§5) ──────────────────
507    /// An *undeclared* file whose module (its file stem) collides with a
508    /// *declared* module's name (`#@module(name)` elsewhere). Accidental
509    /// membership with mixed visibility defaults is the one footgun the
510    /// module model forbids (modules-spec §1). Fix: declare the file with
511    /// the same `#@module(name)`, or rename it.
512    E085,
513    /// A malformed `#@module(…)` directive: a missing or empty name
514    /// argument, or a second `#@module` in the same file. `#@module`
515    /// takes exactly one non-empty module name and appears at most once
516    /// per file (modules-spec §1).
517    E086,
518
519    // ── M-2 imports + visibility (docs/modules-spec.md §2/§4/§7) ───
520    /// A reference resolves to a `#@private` definition in another module.
521    /// Private names are module-internal; the referrer is outside that
522    /// module. Fix: make the definition `#@public` and `IMPORT` it, or move
523    /// the reference into the module (modules-spec §4/§7).
524    E087,
525    /// A bare-form `IMPORT { name } FROM mod` / native `use mod::name;`
526    /// whose trailing segment `name` names neither a definition `mod`
527    /// publicly exports **nor a declared submodule of `mod`** (dual-reading,
528    /// issue #1592 — a trailing segment that resolves to a module licenses
529    /// it instead, matching Rust's `use`; §13.2). Only enforced against
530    /// *declared* modules — an import naming an unknown/undeclared module is
531    /// not itself flagged by this code, since that module's export/submodule
532    /// set isn't visible to the check (modules-spec §2/§7).
533    E088,
534    /// An `IMPORT` brings the same local name into scope twice (a repeated
535    /// bare import, or two imports whose names/aliases collide) — the
536    /// reference would be ambiguous (modules-spec §2/§7).
537    E089,
538    /// An `IMPORT` names the importing file's own module — a module cannot
539    /// import itself; its own names are already bare (modules-spec §2/§7).
540    E090,
541    /// A qualified access `a.b` is ambiguous: `a` is both a module imported
542    /// in this file and a visible definition. Fix with an `AS` alias — no
543    /// silent precedence (modules-spec §2/§7).
544    E091,
545    /// A `#@public`/`#@private` override that restates the module's default
546    /// (e.g. `#@public` in an undeclared module, `#@private` in a declared
547    /// one) — redundant, no effect (warning, modules-spec §4/§7).
548    E092,
549    /// Conflicting or repeated visibility directives on one declaration
550    /// (both `#@private` and `#@public`, or the same one twice). A
551    /// declaration takes at most one visibility directive (modules-spec §4).
552    E093,
553
554    // ── M-3 renames (docs/modules-spec.md §5/§7) ────────────────────
555    /// A malformed `#@was(…)` directive: a missing or empty old-name
556    /// argument (`#@was`, `#@was()`). `#@was` takes exactly one non-empty
557    /// name (modules-spec §5).
558    E094,
559    /// `#@was(name)` names the thing's own *current* name — a self-alias
560    /// that would be a no-op entry in the compiled alias table. Nothing to
561    /// migrate; likely a stale directive left over from a previous rename
562    /// (warning, modules-spec §5/§7).
563    E095,
564
565    // ── M-2c cross-module collisions (issue #784, decision-log
566    // "Cross-module name collisions" 2026-07-14) ────────────────────────
567    /// Two *declared* modules (`#@module(name)`, different names) each
568    /// define a same-name, same-kind symbol. Escalated from the
569    /// `E022`/`E023`/`E026` inklecate-compat duplicate warning to a hard
570    /// error under `dialect = brink` only: flat resolution (unchanged by
571    /// this stopgap — true import-scoped resolution is #790's job) binds a
572    /// bare name to whichever declared-module definition merge happens to
573    /// see first, so two declared modules sharing a name make that binding
574    /// silently order-dependent for one of them. A duplicate *within* one
575    /// module (same declared module name across its files, or any
576    /// undeclared/legacy file) keeps the existing warning — this code
577    /// fires only when both colliding definitions' owning files declared
578    /// *different* modules. Reported once per colliding definition (both
579    /// spans), under `strict-ink` this code never fires (compat corpus
580    /// untouched).
581    E096,
582
583    // ── T1e-1 path projections (docs/t1e-spec.md §2/§6, issue #831,
584    // tracking #828) ──────────────────────────────────────────────────
585    /// A `ref lvalue-path` projection expression (`ref npc.hp`,
586    /// `ref inventory[idx]`) appears somewhere other than ref-argument
587    /// position (a direct argument of a call, `#fn(…)`, or `bind(…)`) — a
588    /// standalone projection value (`temp r = ref a[0]`), one nested inside
589    /// another expression, or any other position. Deliberate v1 posture
590    /// (t1e-spec §2: "projections exist only where `ref` already exists:
591    /// argument binding"); first-class standalone projection values are a
592    /// future round, tracked as icebox #825 — not a permanent rejection.
593    E097,
594    /// A `ref lvalue-path` projection's segment (a dotted field, or a
595    /// `[…]` index) disagrees with the root's statically-known shape, under
596    /// `types = strict` only — a dotted field the declared `STRUCT` shape
597    /// doesn't have, or a `[…]` index against a declared shape that isn't a
598    /// collection (mirrors `structs::check`'s missing/extra-field
599    /// machinery, `E069`–`E071`, applied to path segments instead of
600    /// construction-literal fields; "Unknown never disagrees" for any
601    /// segment whose base type isn't statically known this way — silently
602    /// unchecked, same spirit as `E071`).
603    E098,
604    /// A `ref lvalue-path` projection with at least one path segment
605    /// (dotted field or `[…]` index — a *real* projection, not a bare
606    /// single-name `ref`) reached LIR lowering. T1e-1 (docs/t1e-spec.md §8
607    /// sequencing item 1) ships grammar + HIR + analyzer only — the
608    /// `MakeProjection`/`ProjRead`/`ProjWrite` opcodes a projection needs to
609    /// actually run land in T1e-2 (tracking #828). The E052-fence pattern:
610    /// every other check (`E080` durable root, `E097` position, `E098`
611    /// strict segment shape) already ran and passed, so this is a clean,
612    /// deliberate "not yet lowerable" stop, not a silent drop or a
613    /// miscompile — see `brink-ir::lir::lower::mod`'s backstop doctrine. A
614    /// bare single-name `ref x` (zero segments) never hits this — it lowers
615    /// exactly like today's unmarked ref-argument binding.
616    E099,
617
618    // ── T2-2 `#@effects(…)` assertion surface (docs/effects-spec.md §10,
619    // issue #861) ──────────────────────────────────────────────────
620    /// `#@effects` with no argument at all (`#@effects`, `#@effects()`, or
621    /// an argument that parses to nothing) — the directive always requires
622    /// either `pure` or at least one `reads:`/`writes:`/`calls:` clause.
623    E100,
624    /// A malformed `#@effects(…)` argument: an unrecognized clause keyword
625    /// (only `reads`/`writes`/`calls` are valid), a value that isn't a bare
626    /// identifier, or a bare value with no preceding clause to attach to.
627    E101,
628    /// A `#@effects(…)` clause names an identifier that isn't a declared
629    /// global `VAR`/`CONST` (for `reads`/`writes`) or a declared `EXTERNAL`
630    /// (for `calls`) anywhere in the project.
631    E102,
632    /// **The exceedance error** (docs/effects-spec.md §10, sitting 2,
633    /// 2026-07-14 ruling): the definition's inferred effect row is not
634    /// covered by (`⊄`) its `#@effects(…)` assertion's declared upper
635    /// bound. Per the ruling, this is the *only* diagnostic the assertion
636    /// surface ever produces — an inferred row that is narrower than the
637    /// bound is silent; there is no drift policy.
638    E103,
639
640    // ── Computed-callee call attempt (docs/t1c-spec.md §3/§10, issue #869) ──
641    /// A call `expr(args…)` whose callee isn't a bare variable/temp/param
642    /// name (an `INDEX_EXPR`, `FIELD_ACCESS_EXPR`, chained call result,
643    /// parenthesized expr, …). Direct-call syntax is RULED (t1c-spec §3) to
644    /// a bare-name callee only; "method-call syntax" through a computed
645    /// callee is explicitly out of T1c (§10). Always rejected — every
646    /// dialect, every mode — pointing at the ratified `call(f, args…)`
647    /// form, which already dispatches through exactly this class of
648    /// expression correctly. Replaces the pre-existing silent drop (the
649    /// parser used to leave `(args…)` unconsumed, so it resurfaced as
650    /// trailing prose text on the content line and the call itself
651    /// vanished) with a loud, unconditional compile error.
652    E104,
653
654    // ── `await` condition purity gate (docs/flow-suspension-spec.md §3/§5, ──
655    // ── issue #928, FS-2) ─────────────────────────────────────────────────
656    /// An `await <cond>` / `while await <cond>` condition is not effect-free.
657    /// The condition is captured as a compiler-synthesized *pure* function
658    /// (docs/flow-suspension-spec.md §5): its effect row must be read-only —
659    /// reads are the wake map's dependency set, but a transitive **write** to a
660    /// global cell, or an effectful host **call**, makes the condition
661    /// re-evaluation itself observable, which the wake contract forbids. Built
662    /// on the effects machinery (#859): the condition's transitive effect row
663    /// (via the whole-project [`crate`]-level effect table) must have empty
664    /// `writes`/`calls` and not be opaque. Brink-only (under strict-ink the
665    /// whole `await` is already `E051`); a bare fn-value reference used as a
666    /// dynamic condition (`await some_fn_value`, no call syntax) is read-only
667    /// by construction and never flagged.
668    E105,
669
670    // ── T1b map-literal key-domain warning (docs/t1b-surface-spec.md §3,
671    // issue #598) ──────────────────────────────────────────────────────
672    /// A `#{key: expr, …}` map-literal key is a statically-classifiable
673    /// literal outside the ratified int/string/bool key domain — a float,
674    /// array (`#[...]`), nested map (`#{...}`), struct (`Name#{...}`),
675    /// function-value (`#fn(...)`), ink `LIST`, or divert-target literal
676    /// used directly as a key. §3 rules the key domain to
677    /// int/string/bool at runtime (`RuntimeError::InvalidMapKeyType`) and
678    /// says the analyzer warns on statically-visible non-key types; this was
679    /// the missing half (`MapLiteral` lowering did zero key-domain checking).
680    /// A dynamic key (a variable, call, index, or any other non-literal
681    /// expression) is not statically visible and is never flagged here —
682    /// the runtime fault remains the sole backstop for those.
683    E106,
684
685    // ── NS-A1 Option[T] (docs/stdlib-spec.md §1.4, issue #1107) ────────
686    /// A fresh, un-annotated declaration (`VAR x = none`, `CONST x = none`,
687    /// `~ temp x = none`) whose initializer is the bare `none` Option
688    /// literal. §1.4's ruled rule: "a bare `none` needs a type from
689    /// context (concrete sites fine; a fresh un-annotated `var x = none`
690    /// errors — the empty-collection posture)." A declaration site IS the
691    /// slot's type origin, so there is no context to take the element type
692    /// from — the fix is to initialize from a real Option-producing
693    /// expression (`some(x)`, or an Option-returning verb like
694    /// `find`/`get`/`pop`). Error in both dialects and both `types`
695    /// policies: the rule is part of the Option package itself, not a
696    /// strict-mode refinement.
697    E107,
698
699    // ── NS-A2 effect-row extension (issue #1108; docs/stdlib-spec.md
700    // §1.2/§9.2, issues #1087/#1097) ───────────────────────────────────
701    /// `@[effects(silent)]` exceedance: the definition's inferred row can
702    /// produce content (`emits`, incl. transitively through callees, or an
703    /// opaque/unbounded row). Exceedance-only, like `E103` — asserting less
704    /// than reality is legal, asserting more is not.
705    E108,
706    /// `@[effects(total)]` exceedance: the definition's inferred row can
707    /// raise a turn-terminating fault (`faults`, incl. transitively, or an
708    /// opaque/unbounded row). Exceedance-only, like `E103`.
709    E109,
710    /// The deprecated `#@effects(…)` tag-channel spelling — superseded by
711    /// the `@[effects(…)]` annotation final form (stdlib-spec §9.2, ruled
712    /// 2026-07-18). Warning: the alias keeps parsing (it shipped in
713    /// released surface, `@brink-lang/web@0.11.1`).
714    E110,
715    /// An `@[…]` annotation line naming something outside the channel's
716    /// closed name set: `effects` on the ink surface, `effects` or the
717    /// file-level `was` on the native `.brink` surface. Tag-channel
718    /// directive names do not alias into it.
719    E111,
720    /// An `@[…]` annotation line outside a recognized placement — ink's
721    /// leading run at the top of a knot/stitch body, or native's Rust-shaped
722    /// position directly above a `flow`/`fn` declaration (issue #1563; the
723    /// file-level `@[was]` record for native modules). Never a silent drop,
724    /// never content — the `E045` posture, on the annotation channel.
725    E112,
726
727    // ── NS-A3 protocol registry (issue #1109; docs/stdlib-spec.md §9.6)
728    /// A declaration named after a registry protocol method — `display`,
729    /// `compare`, or `next` (F6, ruled 2026-07-19): the names are RESERVED
730    /// under the brink dialect, and an author declaration of any callable
731    /// or value-bindable kind (knot/stitch/function, param, temp, VAR,
732    /// CONST, EXTERNAL, for-loop variable) is a **hard error**, not an
733    /// E035-lineage shadowing warning — a shadowed `display` would make
734    /// interpolation untrustworthy.
735    E113,
736    /// A registered protocol impl's inferred effect row exceeds its
737    /// protocol's effect contract (`display`/`compare`: pure·silent·total;
738    /// `iterate`'s `next`: writes-receiver·silent·total — the receiver is
739    /// a `ref` param, invisible to the global row, so every v1 contract
740    /// bounds the *global* row at empty). Exceedance-only, the
741    /// `E103`/`E108`/`E109` posture; an opaque row exceeds every contract.
742    E114,
743    /// An ill-formed protocol impl registration: the named type isn't a
744    /// declared `STRUCT`, the impl target isn't a declared function, the
745    /// signature shape is wrong (arity, `ref`-ness, or a contradicting
746    /// type annotation), or the (protocol, type) pair is already
747    /// registered.
748    E115,
749
750    // ── F27: Option has no truthiness (docs/stdlib-spec.md §1.6, ruled
751    // 2026-07-19, issue #1120) ─────────────────────────────────────────
752    /// A condition-position expression (an `if`/`while` condition, a
753    /// `{cond: …}` conditional branch, a choice guard, an `await`
754    /// condition) whose statically-known type is `Option[T]`. Option has
755    /// **no** truthiness — truthiness is a quiet coercion of exactly the
756    /// kind `Option[T] ≠ T` exists to ban — so a strict-mode author writes
757    /// `== none` / `== some(x)`, or the `as`-binding (B1b, issue #1475,
758    /// `brink-analyzer::option_conditions::check_binding_condition`); a
759    /// bound condition never fires this check. Strict-mode-only,
760    /// best-effort static (the "Unknown never disagrees"
761    /// posture: an unclassifiable condition stays silently unchecked);
762    /// under `types = gradual` the same condition is the
763    /// `RuntimeError::OptionTruthiness` turn-terminating fault — the
764    /// runtime backstop that catches every case either way. Supersedes
765    /// NS-A1's shipped falsy-none truthiness.
766    E116,
767    // ── NS-A5 the inhabited-range refinement (issue #1111;
768    // docs/stdlib-spec.md §7, F7/F8 ruled 2026-07-19) ──────────────────
769    /// A range-refinement violation under `types = strict` (the E078
770    /// precedent — strict-only; gradual mode is inert and leaves the
771    /// runtime fault residual, F8's general rule): `int(r)` demands
772    /// `NonEmptyRange` evidence, and either (a) the range literal in
773    /// argument position is **provably empty** (`0..0`, `5..=2` — bounds
774    /// fold statically, CONST refs included), or (b) the argument's type
775    /// carries no inhabitedness evidence (a possibly-empty range — route
776    /// computed bounds through `non_empty(r)`, parse-don't-validate).
777    E117,
778
779    // ── NS-A8: the numeric tower (docs/tower-mini-spec.md, issue #1114) ──
780    /// A protocol impl registration named a numeric-tower kind
781    /// (`vec2`/`vec3`/`vec4`/`quat`/`mat2`/`mat3`/`mat4`) as its type.
782    /// Tower kinds are compiler-known value kinds, not user structs: their
783    /// `display` is the fixed structural form, their equality is
784    /// componentwise IEEE (T4), and they are NOT orderable — a `compare`
785    /// impl for a tower kind would contradict the ruled §4b doctrine, and
786    /// `display`/`iterate` impls would shadow compiler-owned behavior. The
787    /// rejection is unconditional — it wins even over a user STRUCT
788    /// declared with the same name (tower type names are global like
789    /// `int`).
790    E118,
791
792    // ── NS-A4: the ordering doctrine (docs/stdlib-spec.md §4b, issue
793    // #1110) ─────────────────────────────────────────────────────────────
794    /// A `sort_by`/`sorted_by` comparator provably breaks the pure·silent
795    /// contract (§4b: "the comparator falls under the trio's pure·silent
796    /// rule plus the consistent-total-order LAW"). Exceedance-only, the
797    /// E114 posture: flagged when the comparator is a statically-named
798    /// `#fn(target)` whose inferred row shows a global read/write, an
799    /// external call, a content emission, or a tag touch — an opaque or
800    /// unresolvable comparator is not *proven* in violation and passes
801    /// (the gradual posture; the VM's isolation and
802    /// `ComparatorEscaped` fault are the runtime residual).
803    E119,
804    /// NS-A7 `Weighted[T]` construction refusal (`docs/stdlib-spec.md` §8,
805    /// issue #1113): the compile-classifiable half of the E078-style
806    /// evidence-by-construction split. Fired by the `weighted(…)` lowering
807    /// for a statically-malformed table — an empty pair row, an odd
808    /// (dangling-weight) argument count, or a **literal** weight that is
809    /// not a positive int (zero, negative, float/string/bool). Computed
810    /// weights are not classifiable here; they carry the construction
811    /// *fault* residual instead (`RuntimeError::WeightedBadWeight`), so a
812    /// table that exists is always rollable.
813    E120,
814
815    // ── B0.3 HIR admission validator (docs/hir-admission-contract.md §4.2) ──
816    //
817    // Reserved range for the loud, non-suppressible `validate_admission`
818    // pass wired at the AST→HIR seam (issue #1172, docs/b0-sequencing.md
819    // §B0.3). Each check is a hard error — a malformed `(HirFile,
820    // SymbolManifest)` triple is a frontend bug, not a story-author mistake,
821    // so these never carry the warning-severity carve-out other codes do.
822    /// Contract §4.2 check 1a (manifest ⇄ HIR agreement): an
823    /// `UnresolvedRef.range` in the manifest has no matching
824    /// referencing-expression range anywhere in the file's HIR body — the
825    /// range-equality resolution join (Q2(a)) would silently fail to find
826    /// this reference at all.
827    E121,
828    /// Contract §4.2 check 1b (manifest ⇄ HIR agreement): a manifest-declared
829    /// symbol has no corresponding HIR declaration node of the same name —
830    /// the manifest and the HIR body have drifted apart.
831    E122,
832    /// Contract §4.2 check 1c (manifest ⇄ HIR agreement, F-I#4): a `Knot`'s
833    /// `is_function` flag disagrees with whether its declared symbol carries
834    /// the `"function"` detail sentinel.
835    E123,
836    /// Contract §4.2 check 2a (range well-formedness): a HIR node's source
837    /// range is empty or extends past the end of the source file — ranges
838    /// are resolution join keys and IDE geometry, so a garbage range would
839    /// otherwise corrupt resolution silently instead of erroring loudly.
840    /// Exempts the `Option<Provenance>`-carrying synthesized nodes
841    /// (`Content.ptr`/`Divert.ptr`/`Return.ptr`) when `None` (B0.1 finding
842    /// F-B2) — this fires only on a range that is present but malformed.
843    E124,
844    /// Contract §4.2 check 2b (join-key uniqueness, Q2(a)): two distinct
845    /// `UnresolvedRef` entries in the manifest share an identical source
846    /// range — the range-equality join can no longer distinguish them.
847    E125,
848    /// Contract §4.2 check 3 (name-convention conformance, F-I#3): a
849    /// declared symbol's qualified name does not match the dot-qualification
850    /// shape its `SymbolKind` requires (bare for knots/globals, `knot.stitch`
851    /// for stitches, `List.item` for list items, `knot[.stitch].label` for
852    /// labels).
853    E126,
854    /// Contract §4.2 check 4 (control-flow classification, F-I#7): a
855    /// terminal statement (`Divert`/`Return`) is not the last statement in
856    /// an inline conditional or sequence branch.
857    E127,
858    /// Contract §4.2 check 5 (provenance-kind ⇄ `SymbolKind` consistency,
859    /// F-I#5, the #626 floating-stitch trap): a `Knot`/`Stitch` HIR node's
860    /// provenance class disagrees with the `SymbolKind` bucket its declared
861    /// symbol was indexed under in the manifest.
862    E128,
863
864    // ── B0.6 native frontend (docs/b0-sequencing.md §B0.6) ──
865    //
866    // The native `.brink` declaration lowering (`hir::lower_native`) is
867    // deliberately partial — bodies are B0.7/B0.8, and a handful of
868    // declaration-layer constructs (nested modules, `fn` nested below top
869    // level, the `@[…]` annotation channel, lambda expressions in value
870    // position) have no HIR representation yet. Per the contract's §4.4
871    // additive-open/closed-to-silent-extension posture, every such
872    // construct is a loud diagnostic, never a silent drop.
873    /// A native construct parses cleanly but has no HIR lowering yet in
874    /// this slice (a nested `module { … }` block, a `fn` declared below top
875    /// level, an `@[…]` annotation line, a lambda expression in value
876    /// position, or any other CST shape `hir::lower_native` does not yet
877    /// recognize). The construct is skipped — not silently: this diagnostic
878    /// names exactly what was skipped and why.
879    ///
880    /// Also raised by `brink_analyzer::modules::check` (issue #1592,
881    /// #1686 review) for the whole-project-only instance of the same gap:
882    /// a bare `use`/`IMPORT` item's trailing segment that is both aliased
883    /// and — only knowable once whole-project module data resolves the
884    /// dual-reading — a declared **submodule**. Aliasing an entire
885    /// imported module's export set has no `Import`/`ImportItem`
886    /// representation, same as the single-segment `use a as m;` form
887    /// `lower_native::import::lower_use_decl` already rejects with this
888    /// code; this later firing exists only because that verdict isn't
889    /// decidable until the analyzer's whole-project pass.
890    E129,
891    /// A native `flow` is declared more than two levels deep (a `flow`
892    /// nested inside another nested `flow`'s body) — the contract's Q4(b)
893    /// fence (`docs/hir-admission-contract.md` §5 Q4): exactly two
894    /// container levels for v1, addressing model written to generalize.
895    /// Depth-3+ nesting parses and is rejected here, never silently
896    /// flattened into a 2-level shape.
897    E130,
898    /// `<-` (splice) used outside a choice point (issue #1263, ruled
899    /// #1260 on #1256): charter §11 narrows threads to scoped splices
900    /// inside `{? … }` choice points, so this has no structural meaning —
901    /// but `<-` can also be literal dialogue punctuation, so this is
902    /// **warning severity, never blocking** (see `DiagnosticCode::severity`
903    /// below). The construct still parses as ordinary text; nothing is
904    /// dropped or rejected. `brink-syntax-native`'s
905    /// `parser::choice::splice_outside_choice_point` raises the
906    /// `ParseSeverity::Warning` diagnostic this code carries once it
907    /// reaches `brink-db`'s `lower_native_file`.
908    E131,
909    /// A native file-level `@[was(…)]` rename record (issue #1286) carries no
910    /// quoted old module path — a missing argument, or one that is not a
911    /// string literal. Native module paths are `::`-separated and travel as a
912    /// string (`::` is not annotation-argument grammar), so the migration
913    /// target must be spelled `@[was("story::old::path")]`. **Warning
914    /// severity, never blocking** (see `DiagnosticCode::severity`): the
915    /// malformed directive is skipped — no alias is produced — but the file
916    /// still compiles. `brink-ir::hir::lower_native::module::lower_file_module`
917    /// raises it rather than silently dropping the authored record.
918    E132,
919
920    // ── B0.9 native accept-list admission gate (docs/hir-admission-contract.md
921    // §4.4/§5 Q6, docs/b0-sequencing.md §B0.9, issue #1179) ──
922    //
923    // The inverse of the ink `dialect_gate` reject-list: `brink_analyzer::
924    // validate_native_accept_list` enumerates the HIR shapes a well-formed
925    // native lowering is allowed to produce and refuses everything else,
926    // loudly, at the same non-suppressible seam B0.3's `validate_admission`
927    // runs at. Native-only — never raised against ink-produced HIR.
928    /// A native file's `root_content` carries something other than the one
929    /// documented shape a native lowering may leave there: empty, or the
930    /// single synthesized `flow main()` entry divert (maintainer-ruled
931    /// 2026-07-21, `docs/decision-log.md` "Native story-entry convention").
932    /// Anything else — real weave content, more than one statement, a
933    /// source-backed divert — is ink-only baggage: ink's pre-first-knot root
934    /// weave has no native equivalent.
935    E133,
936    /// A native file's HIR carries an `IncludeSite` — native has no textual
937    /// `INCLUDE` graph (charter §13.2, "the tree is the compilation
938    /// universe"); `hir::lower_native::lower` always leaves `includes`
939    /// empty, so any entry here is ink-only baggage that reached native HIR
940    /// some other way.
941    E134,
942    /// A `ThreadStart` (`<- target`) appears somewhere other than the two
943    /// legal native splice positions B0.7's choice-point lowering produces:
944    /// immediately preceding the `ChoiceSet` it preambles, or as the
945    /// trailing statement(s) of a `Choice`'s own body
946    /// (`hir::lower_native::choice::lower_choice_point`). An "ambient"
947    /// thread start anywhere else has no structural meaning on the native
948    /// surface (charter §11 narrows threads to scoped splices inside `{?
949    /// … }` choice points).
950    E135,
951    /// A native `ChoiceSet` carries a `depth`/`context` other than the
952    /// B0.7-documented neutral values (`depth = 0`, `context = Inline`,
953    /// `docs/hir-admission-contract.md` §3 D4) every native choice set
954    /// stamps uniformly — native has no weave fold to report a real value
955    /// from, so any other value means a weave-fold concept leaked in from
956    /// somewhere it shouldn't have.
957    E136,
958    /// The B0.9 native strict-only enforcement point (docs/b0-sequencing.md
959    /// §B0.9, decision-log 2026-07-19 "Typing posture ruled"): a native
960    /// `.brink` file was compiled with an explicit `types = gradual` knob.
961    /// Gradual typing does not exist on the native surface — `types` is not
962    /// a project knob there the way it is for the transitional brink
963    /// dialect, so an explicit `gradual` setting reaching a `.brink` compile
964    /// is refused, loudly, rather than silently accepted.
965    E137,
966
967    // ── B5: the construction initializer (issue #1464, #1103 RULED
968    //    2026-07-23, `docs/stdlib-spec.md` §9.6) ────────────────────────
969    /// A map literal supplies the same key twice (`Map { k: 1, k: 2 }`).
970    /// The E076-lineage cascade ruling (A) of #1103: a duplicate key is a
971    /// **compile error**, consistent with a struct literal's duplicate
972    /// field ([`Self::E084`]) — last-wins would silently swallow the typo.
973    /// Only *statically comparable* literal keys can collide here
974    /// (int/string/bool, the `E106` key domain); a dynamic key is left to
975    /// the runtime, exactly as the key-domain check leaves it.
976    E138,
977    /// A construction literal's entries are not in the form its target type
978    /// constructs from — `Map { a }` (element form for a key/value target)
979    /// or `Flags { A: 1 }` (key/value form for an element target). The
980    /// brace *tokens* are one fixed grammar; the entry form each type
981    /// consumes is the `construct` protocol's business
982    /// ([`crate::hir::construct::ConstructTarget::form`]), so a mismatch is
983    /// caught at dispatch rather than by the parser.
984    E139,
985
986    // ── B3a: UFCS resolution (issue #1482, D1–D5 RULED 2026-07-26,
987    //    `docs/decision-log.md` "UFCS resolution pass designed") ────────
988    /// **D1**: `recv.name(args)`'s receiver type declares a field `name`,
989    /// but that field is not function-typed. Field access *wins outright* —
990    /// a matching-but-non-callable field is a hard error, never a silent
991    /// fall-through to a free function of the same name, so that a call's
992    /// meaning can never hinge on a field's type.
993    E140,
994    /// `recv.name(args)` resolved as neither: the receiver's type declares
995    /// no field `name`, **and** no free function `name` is visible in
996    /// ordinary lexical scope (D4 — the candidate set is lexical scope only;
997    /// there are no method sets or inherent impls). One diagnostic naming
998    /// both attempts, so the author sees the whole search that failed.
999    E141,
1000    /// **D3**: `recv.name(args)`'s receiver type is not known at the
1001    /// resolution point, so field-access-wins is unanswerable. An annotation
1002    /// is demanded rather than the resolution being deferred (E107-family
1003    /// posture). Explicitly a *for now* trade — smarter inference ordering
1004    /// is planned and additive when it lands.
1005    E142,
1006    /// **D5**: `recv.name(args)` resolved to a free function whose first
1007    /// parameter is declared `ref`, so the receiver is auto-ref'd
1008    /// (`party.leader.heal(5)` → `heal(ref party.leader, 5)`, issue
1009    /// #1462) — but *this* receiver cannot be written through: a `CONST`, or
1010    /// a projection whose root is a frame-local (T1e's durable-root rule,
1011    /// `docs/t1e-spec.md` §2), or — once the grammar can spell them — an
1012    /// rvalue such as `[1,2].push(3)`. Refused rather than silently
1013    /// desugared by value, which would drop the mutation. A non-`ref` first
1014    /// parameter never reaches this code: the by-value desugar puts no
1015    /// lvalue requirement on its receiver.
1016    E143,
1017    /// A UFCS call site that `brink-analyzer::ufcs` **resolved** cleanly has
1018    /// reached LIR lowering, which does not consume the verdict side table
1019    /// yet. Refused loudly rather than lowered: the callee path's resolution
1020    /// record names the *receiver* (the D2 side table is what names the real
1021    /// target), so lowering it as an ordinary call would emit a call against
1022    /// a local's id and silently produce a wrong program. Same "parses/
1023    /// resolves but has no lowering yet" posture as [`Self::E129`], one
1024    /// layer further down.
1025    E144,
1026
1027    // ── B1b: the `as` binding (issue #1475, RULED `docs/decision-log.md`
1028    //    2026-07-26 "The `as` binding") ─────────────────────────────────
1029    /// The v1 whole-condition restriction: an `as` binding was written over
1030    /// a `&&`/`||` composition (`if a && find(x) as s { … }`). The ruling
1031    /// fixes the binding as the **entire** condition for v1 — let-chains
1032    /// can land later, additively — so a boolean composition under the
1033    /// binding is refused rather than silently binding the composite (which
1034    /// is never an `Option[T]` anyway). The mirror spelling, an operator
1035    /// *after* the binding (`if find(x) as s && …`), is caught one layer
1036    /// earlier as a parse error (`brink-syntax-native::parser::binding`).
1037    E145,
1038    /// RETIRED (issue #1508) — previously "an `as` binding in a choice
1039    /// guard (`* {if EXPR as name} [text]`) is ruled but not yet
1040    /// implemented". `hir::lower_native::choice::lower_choice` now lowers
1041    /// it for real: capture-at-presentation, by-value COW
1042    /// (`docs/decision-log.md` 2026-07-26, "Choice-guard `as`
1043    /// un-deferred"), reusing the same `OptionBind`/frame-slot machinery
1044    /// `IfStmt::binding` already used — the guard's `OptionBind` writes
1045    /// into the same frame `BeginChoice`'s `fork_thread` snapshots into
1046    /// the pending choice, so the captured value rides along with no
1047    /// separate wire-level capture needed. Code kept reserved, not reused,
1048    /// for diagnostic-code stability — no longer emitted by any pass.
1049    E146,
1050    /// An `as` binding whose condition is a statically-known **non-Option**
1051    /// type (`if 5 as n { … }`). The binding unwraps `Option[T]` to `T`;
1052    /// there is nothing to unwrap here. Strict-mode-only and
1053    /// classification-gated, exactly like its F27 twin [`Self::E116`]: an
1054    /// `Unknown`/`Conflicted` condition stays unjudged rather than
1055    /// guessing.
1056    E147,
1057    /// A write to an `as` binding — `if find(s) as i { i = 0; }`, `pop(i)`,
1058    /// `i[0] = x`, `bump(ref i)`, `b.field = v`, `push(b.field, v)`, … The
1059    /// binding is **immutable** by ruling (`docs/decision-log.md`
1060    /// 2026-07-26): it names the unwrapped payload the condition proved
1061    /// present, and rebinding it would make the narrowing guarantee a lie.
1062    /// Raised via the shared `lir::lower::stmts::reject_as_binding_write`
1063    /// check (issue #2122) for: plain/compound assignment, an
1064    /// indexed-assignment root, and a bare in-place mutator, all via
1065    /// `lir::lower::stmts::lower_assign_target` itself; a single-level
1066    /// struct-field write (`lir::lower::blocks::lower_single_level_field_write`)
1067    /// and a struct-field mutator (`lir::lower::blocks::lower_field_mutator`),
1068    /// which resolve a `Param`/`Temp` root's slot independently of
1069    /// `lower_assign_target` (their root is the *head* of a two-segment
1070    /// path, not the whole target) and so call the shared check directly
1071    /// instead; and separately at the `ref`-argument choke points
1072    /// (`lir::lower::expr::lower_ref_path_call_arg`,
1073    /// `lower_ref_projection_arg`), since passing the binding by `ref`
1074    /// hands the callee a raw pointer to the slot without ever routing
1075    /// through ordinary assignment lowering.
1076    E148,
1077    /// A `remove(a, i)` call whose first argument is statically known to be
1078    /// an array (issue #1532, the #1501 review's migration-tail finding):
1079    /// `remove` went map-only in #1484 (identity-based, idempotent-total
1080    /// key removal; `docs/t1b-surface-spec.md` §5), and the array-index leg
1081    /// it used to also serve moved to its own verb, `remove_at(a, i)`. With
1082    /// no compatibility shim, an un-migrated `remove(array, i)` call site
1083    /// still parses and type-checks as a call to the (now map-only)
1084    /// builtin — `infer::body`'s `remove` arm already has `Ty::Array` in
1085    /// hand at the call site — and previously reached codegen clean, only
1086    /// faulting at runtime against `MapRemove`'s domain check. Strict-mode-
1087    /// only (`infer::body::InferPass::array_remove_calls`,
1088    /// `strict::check_array_remove_calls`), matching every other TM-3
1089    /// typed-mismatch check in this range — the brink dialect's own
1090    /// implicit default is `types = strict` (issue #1127), so this fires
1091    /// for the common case; under `types = gradual` the `MapRemove`
1092    /// runtime fault stays the backstop, same posture as the rest of TM-3.
1093    E149,
1094    /// A def (function or value-returning flow/stitch) declares a non-`void`
1095    /// return type but its body may fall through without ever executing a
1096    /// value-carrying `return <expr>` (issue #1551, `docs/decision-log.md`
1097    /// 2026-07-22 implicit-end ruling item 3: "a flow that declares a
1098    /// return type must produce a value... falling through without a value
1099    /// is a checker error", ratified for a return-typed flow/stitch and now
1100    /// extended to the identical `fn` shape). Strict-mode-only
1101    /// (`strict::check_def`'s escape check, extended by #1551 to run for
1102    /// any def carrying a declared return type, not just `is_function`);
1103    /// deliberately distinct from [`Self::E065`] Unknown-escape — the
1104    /// annotation-fallback in `infer::body::infer_def_body` backfills a
1105    /// no-return body's inferred return type from the annotation itself,
1106    /// so the type comes out concrete (`Clean`, not `Unknown`) and E065's
1107    /// classification can never see this mistake; only a direct
1108    /// `has_value_return` check catches it. An implicit `-> DONE` is never
1109    /// treated as satisfying this — DONE ends the *turn*, not the value
1110    /// contract.
1111    E150,
1112
1113    // ── Native lint: asymmetric choice-branch dead-end (issue #1219,
1114    //    decision-log 2026-07-22 "Flows end implicitly (native)" item 4) ──
1115    /// A native `{? … }` choice's own body falls through (no divert/return)
1116    /// while a sibling choice in the same set diverts onward, at a genuine
1117    /// dead end (nothing follows the choice point to reconverge into) — the
1118    /// residual value of ink's retired "ran out of content" error,
1119    /// relocated to a narrow, **opt-in, warning-severity** lint
1120    /// (`brink_analyzer::native_choice_dead_end`) rather than a blocking
1121    /// runtime fault. Fires only for the *mixed* case — some siblings
1122    /// divert, at least one doesn't — never for a choice set where every
1123    /// branch falls through (an ordinary menu that ends) or where the
1124    /// choice set's `continuation` is non-empty (native has no gather,
1125    /// `docs/native-surface-charter.md` §5 — a non-empty continuation is
1126    /// the dissolved gather, and every falling-through branch reconverging
1127    /// there is ordinary weave structure, not a mistake).
1128    E151,
1129
1130    /// A `contains(m, needle)` call whose `needle` argument is statically
1131    /// visible as outside the map key domain (int/string/bool) while `m`
1132    /// is statically visible as a map — companion to the #580 ruling
1133    /// (`docs/decision-log.md` 2026-07-12 "contains(map, non-key-domain
1134    /// needle) returns false"): the call can never do anything but return
1135    /// `false` at runtime, so the always-false result is a compile-time
1136    /// warning rather than a silent footgun. Strict-mode-only
1137    /// (`brink_analyzer::contains_domain`, wired into `strict::check`
1138    /// alongside `conversions`/`range_refinement` — the same
1139    /// inference-substrate-backed domain-check family): needs the
1140    /// project's whole-program `InferenceResult`
1141    /// (`structs::classify_expr_ty`) to classify a variable/call/
1142    /// index-valued needle, which is only ever computed under `types =
1143    /// strict`. Under `types = gradual` this stays silent and the
1144    /// runtime's total `false` return is the sole (correct, non-faulting)
1145    /// backstop. `Warning`-severity like `E106`'s map-literal-key sibling
1146    /// check, so it flows through the ordinary suppressible `diagnostics`
1147    /// channel and is re-levelable via the project's `[lints]` table.
1148    E152,
1149
1150    // ── `@[allow(…)]` source-level suppression (issue #1161) ───────
1151    /// An `@[allow(…)]` argument is not a diagnostic code this compiler
1152    /// knows (`DiagnosticCode::from_str_code` says no) — a typo like
1153    /// `@[allow(E1511)]` or a name like `@[allow(dead_code)]`.
1154    ///
1155    /// A hard error by construction, and deliberately so: the whole point
1156    /// of a suppression directive is that the author believes a diagnostic
1157    /// is being silenced, so a misspelled code that silently does nothing
1158    /// is the worst possible outcome (the #1374 reserved-keys lesson, and
1159    /// the `@`-namespace rule in `docs/directive-annotations-spec.md` §1.1
1160    /// — every `@`-mark is a valid directive in a valid placement or a hard
1161    /// error).
1162    E153,
1163
1164    /// An `@[allow(…)]` names a real diagnostic code that is **not
1165    /// suppressible**: one whose default severity
1166    /// ([`DiagnosticCode::severity`]) is `Error`.
1167    ///
1168    /// Source-level suppression only ever reaches the warning/lint tier. An
1169    /// error means the compiler cannot produce a correct artifact, so
1170    /// letting an annotation silence one would be a way to ship broken
1171    /// code; the B0.3 admission-validator family (`E121`–`E128`) is covered
1172    /// by the same rule (all `Error`-severity) *and* structurally, since
1173    /// admission diagnostics never route through
1174    /// [`crate::suppressions::apply_suppressions`] at all. This mirrors the
1175    /// `[lints]` table's own hard-error exemption (issue #1160, step 2 of
1176    /// `brink_analyzer::effective_severity`): rather than curating which
1177    /// `Error` codes are "safe" to relax, none of them are reachable.
1178    E154,
1179
1180    /// An `@[allow(…)]` whose argument list is missing, empty, or not a
1181    /// flat list of bare code identifiers (`@[allow]`, `@[allow()]`,
1182    /// `@[allow("E151")]`, `@[allow(reads(x))]`).
1183    ///
1184    /// The grammar counterpart of `E100` on the `@[effects(…)]` channel:
1185    /// the annotation parses as an annotation but declares nothing this
1186    /// channel can act on.
1187    E155,
1188
1189    // ── Lambdas (native surface, issue #1685) ──────────────────────
1190    /// A lambda body assigns to a **captured binding** — a `let`/param
1191    /// binding declared outside the lambda and read inside it.
1192    ///
1193    /// A hard error by the 2026-07-19 ruling ("assignment to a captured
1194    /// binding is a compile error"): brink lambdas capture BY VALUE always
1195    /// (Rust's `move` as the only mode, no keyword, no ref captures in v1),
1196    /// so the binding a lambda body writes to is its own *snapshot* — the
1197    /// write can never be observed by the enclosing scope. A snapshot write
1198    /// is always a lost write, and this kills the closure-mutation
1199    /// confusion structurally rather than letting authors discover it as a
1200    /// silent no-op at runtime.
1201    ///
1202    /// Writes to a *global* (a module-level `var` cell) are not captures
1203    /// and are not flagged: a global is a durable cell reached by name, not
1204    /// a snapshotted binding.
1205    E156,
1206
1207    // ── Anonymous-container state lint (issue #1674, gap 4 of the identity
1208    //    cluster; ruled 2026-07-27 in PR #1670) ─────────────────────────
1209    /// An unnamed once-only choice, or an unnamed sequence (`{cycle: …}` /
1210    /// `{stopping: …}` / `{once: …}` / `{shuffle: …}` and combinations), that
1211    /// genuinely carries durable visit/turn-count state with no author name
1212    /// to anchor it — the choice/sequence's compiled scope id is purely
1213    /// structural (a positional hash, `brink_ir::hir::stamp`), so a content
1214    /// edit anywhere earlier in the same scope can shift it, orphaning the
1215    /// saved count under the old id. The observable fallout is bounded (only
1216    /// visit/turn counts key on a scope id — see
1217    /// `brink_format::LoadReport::anonymous_states_dropped`): a once-only
1218    /// choice may reappear, or a sequence may restart from its first branch.
1219    ///
1220    /// Naming is the opt-in fix — a labeled choice (`* (label) …`) resolves
1221    /// its identity by name instead of position (`stamp::stamp_stmt`'s
1222    /// `lookup_label_id` branch), immune to this drift. Sequences have no
1223    /// label syntax of their own; the mitigation is structural (isolate the
1224    /// sequence in its own small, stably-named stitch so nothing can be
1225    /// inserted ahead of it).
1226    ///
1227    /// **Off/info by default, tier-able through `[lints]`** like any other
1228    /// diagnostic (`brink_analyzer::strict::effective_severity` — this is
1229    /// the one code whose *default* severity is `Info`, not `Warning`; see
1230    /// that function's doc for how `[lints]` still reaches it). A
1231    /// single-shot project that never patches its content is never nagged;
1232    /// a live-ops/UGC project can raise it to `warn`/`deny`.
1233    ///
1234    /// Precision over recall (`brink_analyzer::anonymous_stateful`): a `+`
1235    /// sticky/repeatable choice never triggers this (no once-only gating,
1236    /// no state) and a single-branch, non-`once` sequence never triggers
1237    /// this either (its computed index is `0` regardless of visit count —
1238    /// genuinely stateless despite the syntax).
1239    E157,
1240
1241    // ── Lambda lifting, LIR (issue #1709 review) ───────────────────
1242    /// A lambda body reads a name that the analyzer resolved as a
1243    /// `Temp`/`Param` of the enclosing frame, but that lifting's free-name
1244    /// scan cannot see as a capturable local at the point it runs — in
1245    /// practice, the lambda's own not-yet-bound `let` name, read
1246    /// recursively (`let f = |x| f(x - 1);`): the initializer is scanned
1247    /// for captures *before* the `let` finishes binding `f`, so `f` has no
1248    /// temp slot yet in the enclosing frame.
1249    ///
1250    /// A hard error rather than a silent fall-through: an unresolved free
1251    /// name that is not a real local (a global `var`, a knot/function
1252    /// name) is left alone and resolved by name from inside the lifted
1253    /// function, which is correct. But a name the analyzer says *is* a
1254    /// local must not take that same silent path — falling through would
1255    /// let call lowering target the `let`'s own `DefinitionId` as though
1256    /// it were a callable container, a miscompile that only surfaces as a
1257    /// runtime fault. Recursive lambdas are not supported in this slice;
1258    /// this refuses them at compile time instead of shipping a broken
1259    /// call.
1260    E158,
1261
1262    // ── `@[element]` / `@[style]` declaration surface (issue #1719,
1263    //    `docs/prose-dialect-spec.md` §3.5b sitting 4 addenda 2–4) ───────
1264    /// An `@[element(…)]` annotation whose `args` clause is missing, or
1265    /// whose value is not a quoted string, or whose value does not compile
1266    /// as a portable-regex pattern (`regex::Regex::new`).
1267    ///
1268    /// The grammar counterpart of `E100`/`E155` on the `@[effects]`/
1269    /// `@[allow]` channels: the annotation parses as an annotation but
1270    /// declares a pattern this channel can't act on.
1271    E159,
1272    /// An `@[element(args = "…")]` pattern's named capture group does not
1273    /// match the name of any parameter on the annotated declaration.
1274    ///
1275    /// The capture contract (§3.5b: "named captures bind params by name
1276    /// (compile-checked)") is enforced here, at the declaration, rather
1277    /// than deferred to the `!name` dispatch site — a capture that can
1278    /// never bind anything is a static defect in the pattern itself, not
1279    /// a per-call-site concern.
1280    E160,
1281    /// An `@[style(…)]` clause is not the `key = "value"` shape (a bare
1282    /// identifier, a nested paren-clause, or a non-string value), or the
1283    /// argument list is missing or empty.
1284    E161,
1285    /// An `@[style(…)]` clause's key is neither `line`, `dispatch`, nor
1286    /// the name of a named capture group in the paired `@[element(…)]`
1287    /// pattern on the same declaration.
1288    ///
1289    /// Validated against the real capture set rather than accepted
1290    /// blind — a typo'd key would otherwise silently style nothing
1291    /// (`CLAUDE.md` "flag silent data drops").
1292    E162,
1293    /// An `@[style(…)]` annotation with no paired `@[element(…)]` on the
1294    /// same declaration.
1295    ///
1296    /// `@[style]` is a *companion* annotation (§3.5b addendum 4): its keys
1297    /// name `@[element]`'s captures (plus the two special keys, `line` and
1298    /// `dispatch`), so a style declaration with nothing to style against
1299    /// is malformed rather than silently inert.
1300    E163,
1301
1302    // ── Host manifest (inline-markup vocabulary, issue #1733,
1303    //    `docs/prose-dialect-spec.md` §4.2) ──────────────────────────────
1304    /// An inline markup span (`<name>…</name>`) whose tag name is not
1305    /// declared in the host manifest's markup vocabulary.
1306    ///
1307    /// Only ever reachable once a host *declares* a vocabulary: markup is
1308    /// freeform by default (§4.2), so with no declared span kinds this code
1309    /// cannot fire at all. `Warning` by default and therefore
1310    /// `[lints]`-configurable and `@[allow(E164)]`-suppressible — the
1311    /// "configurable severity" half of §4.2's ruling.
1312    E164,
1313    /// An inline markup span carries an attribute the host manifest does not
1314    /// declare for that span kind.
1315    ///
1316    /// The per-kind counterpart of `E164`, and gated the same way: it fires
1317    /// only for a span whose *name* the manifest does declare (an undeclared
1318    /// name reports `E164` alone rather than cascading one report per
1319    /// attribute).
1320    E165,
1321
1322    // ── `@[element(…, block)]` declaration surface (issue #1839,
1323    //    `docs/decision-log.md` 2026-07-31 "Conventions are annotated
1324    //    handlers") ──────────────────────────────────────────────────────
1325    /// A `block`-flagged `@[element(…)]` annotation whose declaration has
1326    /// no trailing `content`-typed parameter to receive the captured run,
1327    /// or whose would-be receiver is also one of the pattern's own named
1328    /// captures.
1329    ///
1330    /// `block` widens the same capture contract [`Self::E160`] enforces
1331    /// for `args`' named captures — the ruling's `content` param ("the
1332    /// following run … the same first-class fragment-capture path `!radio`
1333    /// uses for the rest of its line") is a *structural* requirement on
1334    /// the declaration, checked here rather than deferred to dispatch: a
1335    /// `block` annotation with nothing to bind the captured run to is a
1336    /// static defect in the declaration, not a per-call-site concern. The
1337    /// dispatch and capture rewrite itself — matching the terminator,
1338    /// building the `FragmentRef`, calling the handler — is issue #1838's
1339    /// natural-notation dispatch, not yet implemented; see
1340    /// [`crate::ElementAnnotation::block`]'s own doc.
1341    E166,
1342
1343    // ── Natural-notation element dispatch (issue #1838,
1344    //    `docs/decision-log.md` 2026-07-31 "Conventions are annotated
1345    //    handlers") ───────────────────────────────────────────────────────
1346    /// A natural-notation `@[convention(claims = "…", order = N)]` handler declares a
1347    /// parameter that its pattern never captures, so a claimed line has
1348    /// nothing to bind it to.
1349    ///
1350    /// The other half of `E160`'s contract, and the half only *claiming*
1351    /// handlers need: a `!name`-dispatched handler can be called by hand
1352    /// with ordinary arguments, but a claimed line is rewritten to exactly
1353    /// one call whose every argument comes from a named capture — so the
1354    /// pattern's capture set and the handler's parameter list must match
1355    /// exactly, not merely one-way.
1356    ///
1357    /// Renumbered to `E167` (from a since-vacated `E166`) when this landed
1358    /// alongside issue #1839's `block` declaration surface, which claimed
1359    /// `E166` first (merged into `main` first) — see that code's own doc.
1360    E167,
1361    /// Two `@[convention(claims = "…", order = N)]` handlers declare byte-identical
1362    /// patterns, and the later-declared one never actually won a claim in
1363    /// this file — so it is dead code.
1364    ///
1365    /// Issue #1848: dispatch order is first-match-wins over the module's
1366    /// claiming handlers, ordered by `@[convention]`'s required `order`
1367    /// property (issue #2164 — declaration order, the interim pre-#2164
1368    /// rule, no longer applies; `hir::lower_native::element::try_claim`'s
1369    /// own doc) — an undocumented rule until this issue, and one with no
1370    /// diagnostic when two patterns can both claim the same line. This is
1371    /// the *sound*, narrow slice of that check: identical patterns provably match
1372    /// identical inputs, so the overlap is certain, not merely possible.
1373    ///
1374    /// A byte-identical twin is not *unconditionally* dead, though:
1375    /// `try_claim` excludes a handler from claiming lines inside its own
1376    /// declaration (the staging rule), and that exclusion does not extend
1377    /// to a later twin — the later twin is exactly the handler that *can*
1378    /// claim a line living inside the earlier one's own body. So this
1379    /// diagnosis runs after the whole file is lowered and only fires when
1380    /// the later twin produced zero actual claims
1381    /// (`hir::lower_native::element::diagnose_duplicate_patterns`'s own
1382    /// doc) — a later twin that is live for even one line is not flagged.
1383    ///
1384    /// General overlap between two *different* patterns (e.g. one whose
1385    /// matches are a strict subset of the other's) is real and valuable —
1386    /// the issue's own framing calls it "the genuinely valuable half" —
1387    /// but is **not** detected here: proving it soundly needs either a
1388    /// witness string both patterns can be shown to accept or a full
1389    /// regex-intersection analysis, neither of which this slice builds.
1390    /// Tracked as a follow-up, not silently out of scope — see the
1391    /// issue thread. `Warning` by default (`@[allow(E168)]`-suppressible,
1392    /// like every other `Warning`-tier code) since a duplicate claim is
1393    /// dead code, not a hard error the way an unregistered claim (`E112`)
1394    /// is.
1395    E168,
1396    /// A top-level `fn` carries an `@[convention(claims = "…", order = N)]` pattern-
1397    /// claiming annotation, but this file is not the project's configured
1398    /// conventions module — the module half of the 2026-07-31 §9.1 ruling's
1399    /// item (4) asymmetry (issue #1844; #1838 landed the *placement* half,
1400    /// `E112`, and #1847 the module-nesting corner of it): "pattern-
1401    /// claiming is confined to ONE module — the conventions module named in
1402    /// `brink.toml`. `!name`-dispatched handlers stay legal anywhere
1403    /// precisely because they self-announce." A sigil-dispatched line
1404    /// announces itself at the call site; a claiming pattern can silently
1405    /// reinterpret ordinary prose, so the auditability the ruling protects
1406    /// depends on every claim living in the one file an author (or
1407    /// reviewer) knows to open.
1408    ///
1409    /// Only fires when `brink.toml`'s `[project] conventions` key (renamed
1410    /// from `elements` by issue #2180) names a project-relative `.brink`
1411    /// path (`conventions = "conventions.brink"`) — a bare built-in preset
1412    /// name (`conventions = "screenplay"`) points at a
1413    /// `std::conventions::*` module with no project file to compare
1414    /// against, and an unset `conventions` key means no conventions module
1415    /// is configured at all, so there is nothing to confine against yet
1416    /// (`brink_db::queries::analysis::conventions_confinement_diagnostics_query`'s
1417    /// own doc). `Error` by default, the same posture as `E112`: a
1418    /// misplaced claim is not a style nit, it is a claim that violates the
1419    /// one property the whole mechanism depends on.
1420    E169,
1421    /// Two claiming handlers' patterns are textually different but can both
1422    /// match the same line of prose — they silently race, with the earlier
1423    /// one winning (issue #1859, follow-up to #1848).
1424    ///
1425    /// `E168` catches the narrow case: byte-identical patterns provably
1426    /// match identical inputs. This code catches the more common and more
1427    /// valuable instance: two *different* patterns whose matched-line sets
1428    /// overlap (one a strict subset of the other, an alternation branch
1429    /// shared between them, two competing prefixes, etc.).
1430    ///
1431    /// Detection uses a sound-but-incomplete heuristic: finding a concrete
1432    /// witness string demonstrably accepted by both compiled patterns. The
1433    /// heuristic checks:
1434    /// - Whether the patterns share a literal prefix (both start with the
1435    ///   same fixed text, before any regex metacharacters)
1436    /// - Whether one pattern's literal parts are a subset of the other's
1437    ///   (e.g. `^A$` is subsumed by `^AB?$` if the second can match `A`)
1438    /// - Whether generated test strings match both patterns
1439    ///
1440    /// A witness found proves overlap; none found does not prove they
1441    /// never overlap. This is the safer alternative to a textual heuristic
1442    /// that could produce false positives (e.g. "shares a literal prefix"
1443    /// without confirming both patterns actually match anything starting
1444    /// with that prefix — `^A$` and `^AB$` share the prefix `A` but never
1445    /// both match any input). Only reports what it can prove.
1446    ///
1447    /// Reported **at most once** per later-handler, against the first
1448    /// earlier handler it provably overlaps with. A handler that overlaps
1449    /// multiple earlier ones is not re-reported. `Warning` severity, like
1450    /// `E168`, since a silent race is a real problem but not a hard error.
1451    ///
1452    /// See also: `E168` (byte-identical patterns), `docs/prose-dialect-
1453    /// spec.md` §3.5b ("pattern power proportional to auditability").
1454    E170,
1455    /// A natural-notation `@[convention(claims = "…", order = N)]` handler declares a
1456    /// parameter, bound by a named capture, whose declared type is neither
1457    /// `string` nor absent nor `content` — `int`, `float`, `bool`, a
1458    /// struct name, a generic, or a `fn` type.
1459    ///
1460    /// Filed from adversarial review of PR #1845 (issue #1849, itself
1461    /// closing part of #1838): `hir::lower_native::element::try_claim`
1462    /// binds **every** matched capture as a plain `Expr::String` literal,
1463    /// unconditionally, regardless of the receiving parameter's declared
1464    /// type — so `@[convention(claims = "^Take (?<n>\\d+)$", order = N)] fn
1465    /// take(n: int)` could never actually receive an `int`. Numeric capture
1466    /// coercion is `docs/prose-dialect-spec.md` §3.5b's own Deferred list
1467    /// — the underlying gap is ruled-deferred, not itself a bug — but
1468    /// leaving the mismatch silent is: without this check it was, and
1469    /// remains, silent — nothing checks a direct call's arguments against
1470    /// the callee's declared parameter types yet. That generic check
1471    /// (`E063` for this shape) is exactly what open issue #1864 asks to
1472    /// build.
1473    ///
1474    /// `content` is deliberately **not** in this code's target set even
1475    /// though a capture can no more produce a `FragmentRef` than an `int`
1476    /// — it already has an established, ruled, and tested story of its
1477    /// own (the spec's own `fn radio(chan: string, text: content)`
1478    /// example, and the `tier1-native/annotations-element` golden
1479    /// fixture, both compile clean today); see `hir::lower_native::
1480    /// annotation::is_satisfiable_by_a_string_capture`'s own doc for why.
1481    ///
1482    /// Reported at the declaration — the same static-defect-in-the-
1483    /// declaration posture `E160`/`E166`/`E167` already take — pointing
1484    /// at the offending param's own type annotation range (an untyped or
1485    /// `content`-typed param never triggers this code, so the annotation
1486    /// is always present and non-`content` when it fires). `Error` by
1487    /// default: unlike `E168`/`E170`'s "silent race" posture, this is a
1488    /// param that can never receive a value of its declared type, not a
1489    /// stylistic ambiguity.
1490    E171,
1491    /// A native tag (`#…`) whose text begins with `@` — the shape of an
1492    /// ink-dialect compiler directive (`#@private`, `#@was(…)`,
1493    /// `#@local`, `#@module(…)`, `#@effects(…)`) — is lowered by a `.brink`
1494    /// file (issue #1835).
1495    ///
1496    /// `#@…` is not its own grammar production in either dialect: it is an
1497    /// ordinary tag (`HASH` + free text), and only ink's HIR lowerer
1498    /// (`hir::lower::directive::parse_directive_tag`) gives a leading `@`
1499    /// special, compile-time-consumed meaning. `#` is already the runtime-
1500    /// tag sigil in native content position (that is exactly *why* `#@…`
1501    /// parses as a tag rather than a directive there too), and
1502    /// `hir::lower_native` has no matching check — so before this code, an
1503    /// author porting a file from ink, or splitting time between the two
1504    /// dialects, got no error and no warning: the directive text became
1505    /// ordinary tag content on the compiled story, silently, which is worse
1506    /// than a plain no-op because it surfaces as mysterious runtime output
1507    /// rather than a compile-time failure.
1508    ///
1509    /// `Warning` by default, not `Error`: a literal `@`-led tag can be a
1510    /// deliberate runtime convention for a host that wants one (the issue's
1511    /// own caution), so the diagnostic is `[lints]`-configurable and
1512    /// `@[allow(E172)]`-suppressible rather than blocking, the same posture
1513    /// as `E132`/`E168`/`E170`. `hir::lower_native::body::lower_tag` raises
1514    /// it, naming the native spelling to use instead when the tag names a
1515    /// real ink directive that has one (`@[was(…)]`, `@[effects(…)]`) and
1516    /// saying so plainly when it does not (`module`, `public`, `private`,
1517    /// `local` have no native annotation counterpart yet). `#@allow` is
1518    /// its own case — ink's directive recognizer does not know `allow`
1519    /// either, so the message never calls it an ink-dialect spelling; it
1520    /// only notes that native's own `@[allow(…)]` annotation (an unrelated
1521    /// diagnostic-suppression channel) happens to share the name. Any other
1522    /// unrecognized name gets a shape-only wording that never asserts ink
1523    /// membership.
1524    E172,
1525
1526    // ── Required markup attributes (issue #1780 gap 1, ruled by #1997)
1527    //    ────────────────────────────────────────────────────────────────
1528    /// An inline markup span of a declared kind is missing an attribute the
1529    /// host manifest marks `required` for that kind.
1530    ///
1531    /// The counterpart `E164`/`E165` never caught: `attrs` was an
1532    /// *allow*-list only until #1997, so a declared attribute simply absent
1533    /// from a span went undiagnosed. Gated the same way as `E165`: it only
1534    /// ever fires for a span whose *name* the manifest does declare (an
1535    /// undeclared name reports `E164` alone), and only for attributes the
1536    /// declaring kind actually marks `required` — a kind with none required
1537    /// never raises this for any span of that kind. One report per missing
1538    /// attribute, not one combined message, mirroring `E165`'s
1539    /// one-per-attribute posture rather than `E164`'s one-per-span.
1540    ///
1541    /// `Warning` by default, the same posture as `E164`/`E165`, for the same
1542    /// reason: only a `Warning`-base code is `[lints]`-configurable and
1543    /// `@[allow(…)]`-suppressible, and a host that wants a required
1544    /// attribute to be binding raises it with `[lints] E173 = "deny"`.
1545    E173,
1546    /// A lambda's own **written annotation** (a param's `: T` or the
1547    /// lambda's `: R` return annotation) disagrees with its body-derived
1548    /// type (issue #1994, RULED 2026-08-01, closing #1932: "the written
1549    /// annotation takes priority... an incompatible body is an eager error
1550    /// at the lambda, not a deferred surprise at the call site").
1551    ///
1552    /// `#1910`/PR #1928 made `infer::body::InferPass::infer_lambda` read a
1553    /// lambda's body-derived param/return types back — the same overlay
1554    /// `infer_def_body` already applies for a top-level `fn`/`flow` — which
1555    /// silently let a *wrong* body derivation override a *correct* written
1556    /// annotation with no diagnostic anywhere (a standalone `let f = |k:
1557    /// int|: int { "wrong" };` with no call site produced nothing at all).
1558    /// This code closes that gap for the annotated case specifically: a
1559    /// lambda's own written per-param/return annotation now always governs
1560    /// that slot's resulting type, and this diagnostic fires the moment the
1561    /// body-derived type (when it resolves to anything concrete) disagrees
1562    /// with it — deliberately **not** gradual/advisory like `E063`, since
1563    /// the annotation is the ruled source of truth for a lambda's own
1564    /// signature, not a hint to double-check later.
1565    ///
1566    /// `#1910`'s own fix is unchanged for the *unannotated* case — a
1567    /// lambda param/return with no written annotation still exports
1568    /// whatever its body derives, exactly as before.
1569    ///
1570    /// Native-only (`LAMBDA_EXPR` has no `brink-syntax` counterpart, same
1571    /// posture as `E156`/`E158`): raised only from
1572    /// `infer::body::InferPass::infer_lambda`, reported by
1573    /// `strict::check_lambda_annotation_mismatches` under `types = strict`.
1574    E174,
1575    /// RETIRED (issue #2165) — was `register`'s comptime-only-intrinsic
1576    /// confinement check (issue #1840 Q5): `register` was legal only inside
1577    /// the project's configured conventions module's `fn conventions()`,
1578    /// enforced here. The 2026-08-03 ruling (`docs/decision-log.md`, "`fn
1579    /// conventions()` is DISSOLVED") removed `fn conventions()` and
1580    /// `register` from the design entirely — precedence is now a static
1581    /// `order` property on `@[convention]` (issue #2164), needing no
1582    /// comptime evaluator and so no confinement diagnostic to raise. Code
1583    /// kept reserved, not reused.
1584    E175,
1585    /// A divert-with-args site (`-> knot(args)`, `->-> tunnel(args)`, or
1586    /// `<- thread(args)`) supplies a number of arguments that does not
1587    /// match its resolved target's declared parameter count (issue #2156).
1588    ///
1589    /// PR #2150 (issue #2136) wired native's `-> knot(args)` call-args
1590    /// syntax into `DivertTarget::args` for the first time — before that,
1591    /// the shape hard-failed `E129` on native and never reached this check
1592    /// at all. Investigating that newly-reachable path found the arity gap
1593    /// was real on **both** dialects: `brink_ir::symbols::project`'s
1594    /// `walk_divert_target`/`Expr::DivertTarget` ref-pushing sites always
1595    /// recorded `arg_count: None` for a `RefKind::Divert` reference,
1596    /// unconditionally discarding `DivertTarget::args.len()` — so
1597    /// `brink_analyzer::resolve::check_arity` (`E031`, gated on
1598    /// `arg_count.is_some()`) could never fire for a divert, on either
1599    /// dialect, regardless of how many arguments were supplied. `E176` is
1600    /// `E031`'s sibling for the divert call shape, kept as its own code
1601    /// (rather than widening `E031`'s own message, which names *function*
1602    /// calls) so the two diagnostics can be told apart and suppressed
1603    /// independently.
1604    ///
1605    /// Scoped to a resolution that names a `Knot`/`Stitch`/`Label` (the
1606    /// only symbol kinds with their own declared parameter row) —
1607    /// deliberately **not** checked when the divert resolves through a
1608    /// `Variable` or a divert-typed local `Param` (a stored/forwarded
1609    /// divert-target value, e.g. the ink docs' `-> generic_sleep (->
1610    /// waking_in_the_hut)` — see "Advanced: sending divert targets as
1611    /// parameters"), whose underlying target's arity is not known
1612    /// statically at the indirection site. `resolve_function`'s own
1613    /// `check_arity` call sites already draw this same line (only
1614    /// `External`/`Knot` resolutions are checked; `Variable`/local
1615    /// resolutions are not).
1616    ///
1617    /// `Warning`-tier by default, matching `E031`'s own severity precedent
1618    /// for the identical arity-mismatch shape at an ordinary call site —
1619    /// `lir::lower::stmts::lower_divert_target` still lowers a mismatched
1620    /// site (`lower_call_args` pushes exactly as many `CallArg`s as the
1621    /// divert supplies, not the target's declared count), so this stays
1622    /// advisory rather than blocking, and is `[lints]`-configurable /
1623    /// `@[allow(E176)]`-suppressible like every other `Warning`-base code.
1624    E176,
1625
1626    // ── `@[convention]` / `@[element]` split (issue #2164,
1627    //    `docs/decision-log.md` 2026-08-03) — E177 was reserved for #2156
1628    //    at the time this range was assigned; #2156 landed as E176 only
1629    //    (see above), leaving E177 itself unclaimed and unused ────────
1630    /// A `@[convention(claims = "…")]` annotation with no `order` clause.
1631    ///
1632    /// `order` is **required**, not optional (`docs/decision-log.md`
1633    /// 2026-08-03 "`order` is REQUIRED on `@[convention]`…"): a claiming
1634    /// handler competes for lines it did not announce, so its precedence
1635    /// against every other claiming handler in the same module must be
1636    /// total, explicit, and authored — there is no default to fall back
1637    /// to, and the compiler never infers one from declaration position.
1638    /// Reported at the annotation line, the same posture `E159` already
1639    /// takes for a missing/malformed `claims` value; yields no
1640    /// `ConventionAnnotation` at all (never a partial one with a made-up
1641    /// order).
1642    E178,
1643    /// Two `@[convention]` declarations in the same module carry the same
1644    /// `order` value.
1645    ///
1646    /// Ties are **rejected**, not resolved (the same ruling as `E178`):
1647    /// "there is no tie-breaking rule, because ties are rejected rather
1648    /// than resolved." Reported against **every** conflicting declaration —
1649    /// the duplicate-definition posture, not a single "first one wins,
1650    /// second one is the problem" report — so an author sees the whole
1651    /// conflicting group regardless of which one they open first.
1652    E179,
1653    /// A `@[convention(…, attach = StructName)]` clause names a struct the
1654    /// declaration's own return type does not agree with (issue #2178,
1655    /// split from #2164's 2026-08-03 design-backport comment "item 2":
1656    /// "The attachment schema is a STRUCT — do not invent a DSL").
1657    ///
1658    /// `docs/decision-log.md` 2026-08-03 states the governing split
1659    /// plainly: *"keys are declared, values are computed"* — `attach`
1660    /// declares which keys a handler attaches and their types by naming an
1661    /// ordinary `struct`; the handler body computes the values. That only
1662    /// holds if the handler's own declared return type actually **is**
1663    /// the named struct — a mismatch (a different type, a generic, a
1664    /// `fn` type, or no declared return type at all) means the projection
1665    /// and the handler's real output could never agree, so this is
1666    /// reported at the `attach` clause's own value rather than silently
1667    /// trusting the name. Reported the same "never a partial one" way
1668    /// `E159`/`E178` are: no `ConventionAnnotation` at all results, rather
1669    /// than one carrying a schema its own declaration cannot honor.
1670    ///
1671    /// Declaration-surface-only, like every other check in this module:
1672    /// this compares `attach`'s name against the return type's own bare
1673    /// name, and never checks whether a struct of that name is actually
1674    /// *declared* anywhere — that is real name resolution's job (out of
1675    /// scope for this code, same posture `E171`'s own doc explains for
1676    /// captured-parameter types).
1677    E180,
1678
1679    // ── TM-4c struct-shape resolution backstop (docs/typed-mode-spec.md
1680    //    §6, issue #2240) ─────────────────────────────────────────────
1681    /// `lir::lower::structs::build_shape_table`'s own
1682    /// `decls::lookup_global(index, file_id, name, SymbolKind::Struct)`
1683    /// call — resolving a declared `STRUCT`'s **own** `DefinitionId`,
1684    /// using its own declaring file as referrer — came back `None`.
1685    ///
1686    /// This is a non-suppressible defense-in-depth backstop, the
1687    /// `E060`/`E073` posture: it should never fire from a normal compile.
1688    /// The exact-file arm always matches a struct against itself *unless*
1689    /// `brink-analyzer` already dropped this HIR decl's own symbol entry
1690    /// as a true intra-module duplicate (`E023`, same declared module as
1691    /// an earlier same-name declaration) — and even then,
1692    /// `lookup_global`'s unscoped fallback normally rescues the
1693    /// surviving sibling's id (which is exactly what lets
1694    /// `build_shape_table`'s own `by_def.contains_key` dedup recognize
1695    /// "true intra-module duplicate" and skip it a second time, rather
1696    /// than minting a fresh, wrong shape). This code fires only in the
1697    /// narrower case the fallback itself cannot rescue: **every**
1698    /// surviving same-name candidate is std-declared, so the fallback's
1699    /// own std-exclusion (issue #2197) empties the search too. Before
1700    /// this code existed, that combination silently dropped the struct
1701    /// from both `ShapeTable` and `NameTable` seeding with no diagnostic
1702    /// at all — shifting every subsequent `ShapeId`/`NameId` and the
1703    /// bytecode built from them (CLAUDE.md: "silent drops are always
1704    /// bugs until proven otherwise").
1705    ///
1706    /// Reachable **today**, not merely in principle (review finding on
1707    /// #2240): any project whose own declaring file is not all-native
1708    /// (`project_is_all_native`) and whose own `STRUCT`/`struct` shares a
1709    /// name with one the std-mounted screenplay preset declares (`Cue`,
1710    /// `Parenthetical`) collides with it — neither side needs a `#@module`
1711    /// for this to happen. `symbol_index_query` builds the shared index
1712    /// from every `set_file`-registered file regardless of the
1713    /// compilation closure, so the mounted std declaration sits in the
1714    /// index even for an ink entry whose LIR closure never reaches it.
1715    /// With neither declaration module-qualified (or the project simply
1716    /// not `Dialect::Brink`), M-2d cross-declared-module coexistence
1717    /// (`is_cross_declared_module_collision`) never applies, so the pair
1718    /// collapses to an ordinary same-module duplicate — and it is the
1719    /// *project's* declaration that gets dropped whenever its own file
1720    /// sorts after the std key in `FileId`-mint order (any project file
1721    /// named e.g. `story.ink`, `world.ink`, `types.ink` does, since
1722    /// `"std/…"` sorts first). See `brink-environment`'s
1723    /// `e181_is_reachable_from_an_ordinary_ink_project_colliding_with_a_std_preset_name`
1724    /// for this compiled end to end through the real analyzer drop, not a
1725    /// hand-built `SymbolIndex`.
1726    ///
1727    /// `build_struct_shape_data` (the `NameId`-free, cutoff-friendly
1728    /// twin `struct_shape_data_query` memoizes for the per-knot chunk
1729    /// lowering path) performs the textually identical lookup and has no
1730    /// diagnostic sink of its own to push into — it is a pure,
1731    /// `Eq`-cutoff salsa data query, not a lowering pass threading a
1732    /// `Vec<Diagnostic>` accumulator. It is deliberately left silent
1733    /// there rather than given a redundant sink: every real compile
1734    /// (`brink-db`'s `lir_query`) always computes `build_shape_table`
1735    /// (via `lir_prelude_decls_query`) and `build_struct_shape_data` (via
1736    /// `struct_shape_data_query` → `chunk_lowering_ctx_query` →
1737    /// `lir_knot_chunk_query`) in the same salsa revision, over the same
1738    /// `resolutions_index_query` index and the same files' `structs`
1739    /// HIR — so the exact same drop condition always fires this
1740    /// diagnostic from the prelude side in the same compile. See that
1741    /// function's own doc comment for the full argument.
1742    E181,
1743
1744    // ── #2179: `@[convention]` no-world-reads fence ────────────────────
1745    /// A `@[convention]` handler's transitive call closure reaches an
1746    /// `EXTERNAL` classified [`crate::ExternalKind::Query`] (a world read)
1747    /// or left [`crate::ExternalKind::Plain`] (unclassified) —
1748    /// `docs/decision-log.md` 2026-08-06 "No-world-reads fence: analyzer
1749    /// effect-row check; unclassified externals are diagnosed".
1750    ///
1751    /// A claiming handler competes for lines it never announced, which
1752    /// only holds together if classification is a pure function of the
1753    /// text: if it depended on game state, the editor could never display
1754    /// it, the projection could never be cached, and explain-match would
1755    /// depend on a save file. So a handler may call pure functions and
1756    /// [`crate::ExternalKind::Effect`]/[`crate::ExternalKind::Presentation`]
1757    /// externals ("commands"), but never one that reads world state — and
1758    /// an unclassified (`Plain`, the default) external is treated the same
1759    /// as a proven read, not the same as a proven pure call: "unprovable
1760    /// is not passable." The fix is classifying the external, via an
1761    /// inline `@kind` doc tag or the registered host manifest.
1762    ///
1763    /// Computed by `brink_analyzer::no_world_reads` over the **transitive**
1764    /// call closure — a handler calling a helper `fn` that itself calls a
1765    /// `Query`/`Plain` external is diagnosed exactly like a direct call —
1766    /// reusing the same call-graph substrate
1767    /// (`brink_analyzer::infer::{collect_defs,call_edges,def_body}`) T2-1's
1768    /// effect rows are built from, per the #2179 decline comment's finding
1769    /// that the aggregated row/`compute_container_access` route has no
1770    /// span to diagnose with: this walks for the real call-site span the
1771    /// aggregated row structurally cannot carry. Reported at the offending
1772    /// call's own site, which may be inside a different definition (and a
1773    /// different file) than the handler's own declaration.
1774    E182,
1775    /// `brink_ir::lir::lower::expr::lower_call`'s resolved-target match
1776    /// found a symbol kind that is not callable — a `ListItem`, `Label`,
1777    /// `Stitch`, `Param`, `Temp`, or `Struct` sitting at a call position
1778    /// (issue #2837, filed from the #2836/w187 review). `SymbolKind::Knot`
1779    /// is the one non-`External`/`List`/`Variable`/`Constant` kind that
1780    /// *is* callable — ink allows any knot as a function via tunnels, per
1781    /// `brink_analyzer::resolve::resolve_function`'s own comment — so it
1782    /// keeps its own `lir::ExprKind::Call` arm.
1783    ///
1784    /// This *is* reachable from ordinary author source, not only from a
1785    /// hypothetical future resolution regression: `Temp`/`Param` reach this
1786    /// arm whenever `ctx.temp_slot` does not have the name open at the call
1787    /// site — which is the normal, expected shape of two ordinary author
1788    /// mistakes, not a `temp_slot` bug. Calling a T1b block-scoped temp
1789    /// (`~ { … }`) after its own block has closed is diverted to
1790    /// [`Self::E082`] instead (mirroring `lower_path`'s own guard for the
1791    /// same case), but a genuine forward reference — calling a name before
1792    /// its declaring `temp`/param binding — falls through to this arm and
1793    /// reports `E183` today; that reproduces on the plain `.ink` surface
1794    /// with no `--dialect brink` needed. `Stitch`, `ListItem`, `Label`, and
1795    /// `Struct` remain analyzer-unreachable for a real call site as far as
1796    /// this code can tell (`resolve_function` never hands back `Stitch`/
1797    /// `Label`/`Struct` there, and only hands back a bare `ListItem` for a
1798    /// `#fn(target)` literal, which never reaches `lower_call` at all) —
1799    /// those four are the defensive-backstop part of this diagnostic.
1800    ///
1801    /// Refused loudly rather than silently emitting `lir::ExprKind::Call`
1802    /// against the resolved id: that catch-all is exactly the mechanism
1803    /// that let PR #2836's first attempt compile a program clean — 7,941
1804    /// tests, the oracle ratchet, and clippy all green — while it then
1805    /// faulted at runtime with `UnresolvedDefinition(ListItem(..))`. Same
1806    /// "compile error over runtime fault" posture as [`Self::E144`]'s UFCS
1807    /// refusal in the same module.
1808    E183,
1809
1810    // ── issue #2262: E181's own drop class, for every OTHER declaration
1811    //    kind `lir::lower::decls::lookup_global` self-resolves ─────────
1812    /// `lir::lower::decls`'s own `lookup_global(index, file_id, name,
1813    /// kind)` self-declaration lookup — for a `CONST`
1814    /// (`collect_globals`'s constants pass), a `VAR` (`collect_globals`'s
1815    /// variables pass), or an `EXTERNAL` (`collect_externals`) — came back
1816    /// `None`.
1817    ///
1818    /// The exact same non-suppressible defense-in-depth posture as
1819    /// [`Self::E181`], for the exact same reason: this is [`Self::E181`]'s
1820    /// own struct-shape drop class recurring at three more call sites in
1821    /// the same file, all sharing `lookup_global`'s doc comment and none
1822    /// fixed by #2240/#2258 (issue #2262, filed from that PR's own review —
1823    /// "#2240 under-captured the class"). The exact-file arm always
1824    /// matches a declaration against itself *unless* `brink-analyzer`
1825    /// already dropped this HIR decl's own symbol entry as a true
1826    /// intra-module duplicate (`E023`) — and even then, `lookup_global`'s
1827    /// unscoped fallback normally rescues the surviving sibling's id. This
1828    /// code fires only when that fallback also misses: **every** surviving
1829    /// same-name/same-kind candidate is std-declared, so the fallback's
1830    /// own std-visibility carve-out (issue #2197) excludes it too. Before
1831    /// this code existed, that combination silently dropped the
1832    /// `CONST`/`VAR`/`EXTERNAL` from `PreludeDecls` (no `lir::GlobalDef`
1833    /// or `lir::ExternalDef` at all) with no diagnostic whatsoever
1834    /// (CLAUDE.md: "silent drops are always bugs until proven otherwise").
1835    ///
1836    /// **Reachable today**, exactly as [`Self::E181`]'s own doc found for
1837    /// `STRUCT` (review finding on #2240): an ordinary project — no
1838    /// `#@module`, no `dialect` override even needed for `EXTERNAL` (core
1839    /// ink syntax, unlike `STRUCT`) — that declares its own `EXTERNAL
1840    /// scene_entered(…)` collides with the std-mounted screenplay
1841    /// preset's own `extern scene_entered`
1842    /// (`std/conventions/screenplay.brink`). Neither declares a module, so
1843    /// M-2d cross-declared-module coexistence never applies and
1844    /// `insert_symbol` treats the pair as a true intra-module duplicate,
1845    /// dropping whichever one's `FileId` sorts after the other's in mint
1846    /// order. See `brink-environment`'s
1847    /// `external_self_declaration_silently_drops_when_colliding_with_a_std_preset_name`
1848    /// for this compiled end to end through the real analyzer drop. `std`
1849    /// declares no `CONST`/`VAR` today, so the `CONST`/`VAR` call sites
1850    /// stay reachable only in principle (a future std module adding one),
1851    /// same status `E181` itself carried before its own reachable case was
1852    /// found — not a reason to leave them undiagnosed.
1853    E184,
1854
1855    /// Issue #1944: a plain dotted assignment target (`~ p.bogus = 1`)
1856    /// names a field its receiver's *resolved* struct shape doesn't
1857    /// declare — the `E070` mirror for a construction-literal's own
1858    /// unknown-field check (`structs::check`'s "Extra" case,
1859    /// `docs/typed-mode-spec.md` §6), but for `Stmt::Assignment`/
1860    /// `BlockStmt::Assignment` targets instead of a `#{...}` literal.
1861    ///
1862    /// PR #1939's `check_declared_field_assign_target` deliberately stays
1863    /// silent on an unresolvable field — it only compares a *resolved*
1864    /// field's declared type against the RHS ("Unknown never disagrees").
1865    /// `ref_projection::check_strict`'s `E098` covers an unknown segment
1866    /// only in `ref`-argument position (`ref npc.bogus`), not a plain
1867    /// assignment target. Before this code existed, the issue's exact
1868    /// repro —
1869    ///
1870    /// ```text
1871    /// STRUCT Point = #{x: float, y: float}
1872    /// VAR p: Point = Point#{x: 0.0, y: 0.0}
1873    /// ~ p.bogus = 1
1874    /// -> DONE
1875    /// ```
1876    ///
1877    /// — compiled clean under `types = strict` with zero diagnostics.
1878    ///
1879    /// Reported from `structs::check_field_assign_mismatch`, the same
1880    /// function `E063` (field-type mismatch on a *resolved* field) comes
1881    /// from — fired only once the walk has resolved the receiver's shape
1882    /// (`shapes.resolve` succeeded) and the shape itself declares no field
1883    /// by this name. An Unknown/untyped root never reaches this arm at
1884    /// all: the walk's own guard above (`let Ty::Struct(shape_name) =
1885    /// &current else { return; }`) returns silently the moment `current`
1886    /// isn't a resolved struct type, so "Unknown never disagrees" holds
1887    /// for the receiver exactly as it does for `E063`. A chained target
1888    /// (`o.i.a = v`, 3+ segments) never reaches this function at all —
1889    /// `check_declared_field_assign_target`'s own `segments.len() == 2`
1890    /// fence means no `FieldAssignMismatch` fact is ever recorded for one;
1891    /// LIR's `try_lower_field_assignment` already rejects it outright with
1892    /// the non-suppressible `E074`, regardless of whether the field name
1893    /// exists.
1894    E185,
1895
1896    /// Issue #2264: a `@[convention(…)]` handler declares BOTH `block` and
1897    /// `attach = StructName` on the same declaration — `parse_convention`
1898    /// (`annotation.rs`) now rejects the combination outright rather than
1899    /// silently accepting it. Before this code existed, nothing diagnosed
1900    /// the co-occurrence at all: `try_claim`'s dispatch (`element.rs`) is
1901    /// an `if is_block { .. } else if is_attach { .. }` with no exclusivity
1902    /// check anywhere upstream — `block` always won the `if`, `attach` was
1903    /// parsed and stored on `ConventionAnnotation` but never consulted, and
1904    /// the author got zero signal that half of what they wrote did
1905    /// nothing (verified: `red_probe_block_and_attach_together_compile_clean_with_attach_inert_today`
1906    /// in `lower_native::tests`, run BEFORE this code existed, proves the
1907    /// silent-drop shape end to end).
1908    ///
1909    /// This is deliberately a hard rejection, not an attempt to define what
1910    /// "wrap AND attach" would mean together — that is an open design
1911    /// question (issue #2264's own body: "Define what the combination is
1912    /// supposed to mean and implement it — but that's a design question
1913    /// (rule 7), not a good first assumption") with no ruling and no test
1914    /// pinning any combined
1915    /// semantics, so nothing here invents one. `parse_convention` returns
1916    /// `None` (never a partial `ConventionAnnotation`) — the same "never a
1917    /// partial one" posture `E159`/`E166`/`E167`/`E178`/`E180` already take
1918    /// — so a handler declaring both is never registered as a claiming
1919    /// handler at all, not merely warned about.
1920    ///
1921    /// Also reachable through the compact-cue desugar (`@NAME: text`,
1922    /// issue #2079) — it dispatches through the exact same `try_claim`
1923    /// function, so a compact-cue-claiming handler declaring both clauses
1924    /// hits this same check (confirmed on the issue by PR #2341's review).
1925    E186,
1926
1927    /// Issue #2201: a write to a `CONST` — plain/compound assignment, a
1928    /// postfix `++`/`--`, an indexed-assignment root, a bare in-place
1929    /// mutator (`pop`/`heap_pop`), a struct-field write/mutator whose root
1930    /// is a `CONST`, or passing the `CONST` by `ref` (bare or as a
1931    /// projection root). ink semantics (`ink/compiler/ParsedHierarchy/
1932    /// VariableAssignment.cs`, "Can't re-assign to a constant") reject this
1933    /// at compile time; before this code existed, `lir::lower::stmts::
1934    /// lower_assign_target` treated `SymbolKind::Constant` identically to
1935    /// `SymbolKind::Variable` — every one of the write channels above
1936    /// silently mutated the constant's storage cell with zero diagnostics
1937    /// anywhere in the pipeline.
1938    ///
1939    /// Raised via the shared `lir::lower::stmts::reject_const_write` check —
1940    /// the `CONST` analog of [`Self::E148`]'s `reject_as_binding_write` —
1941    /// called from every choke point that resolves a `Global` write root's
1942    /// `SymbolInfo`: `lower_assign_target` itself (plain/compound
1943    /// assignment, postfix's bare-target conversion, the
1944    /// indexed-assignment root via `lower_indexed_assignment`, and a bare
1945    /// mutator's root via `pop`/`heap_pop`, all of which call
1946    /// `lower_assign_target` for their root); `lower_single_level_field_write`/
1947    /// `lower_field_mutator` (their two-segment field-root
1948    /// `SymbolKind::Constant` arm, which resolves the root independently of
1949    /// `lower_assign_target` — the same reason `reject_as_binding_write`
1950    /// needs a direct call there too, per #2122); and the `ref`-argument
1951    /// choke points `lower_ref_path_call_arg`/`lower_ref_projection_arg`
1952    /// (passing a `CONST` by `ref` hands the callee a raw pointer to the
1953    /// cell, bypassing assignment lowering entirely).
1954    ///
1955    /// Deliberately a LIR-lowering refusal (this code's precedent is
1956    /// [`Self::E074`]/[`Self::E148`], not an analyzer diagnostic like
1957    /// [`Self::E185`]): the write-channel enumeration above already lives
1958    /// entirely in `lir::lower` — duplicating it in `brink-analyzer` would
1959    /// re-run the exact same channel-undercounting risk that made this
1960    /// issue's own premise true (#2122 named only two of the seven channels
1961    /// `CONST` reassignment turned out to have). Applies to both surfaces —
1962    /// `.ink` and `.brink` — since this mirrors ink's own compile-time
1963    /// rejection, not a native-only extension; `SymbolKind::Constant` is
1964    /// resolved identically for both frontends by the time LIR lowering
1965    /// sees it.
1966    E187,
1967
1968    // ── TM-2 reserved-type-name shadowing (issue #1865) ───────────────
1969    /// A declared `STRUCT`'s own name collides with one of the fixed names
1970    /// `annotations::resolve`'s `TypeExpr::Named` arm resolves *before* it
1971    /// ever consults `names.structs` — a builtin leaf
1972    /// (`int`/`float`/`bool`/`string`/`content`/`divert`) or an NS-A8 tower
1973    /// kind (`vec2`/`vec3`/`vec4`/`quat`/`mat2`/`mat3`/`mat4`). That
1974    /// ordering is deliberate and unchanged by this code (`resolve`'s own
1975    /// doc: "checked before the struct lookup... the same ordering that
1976    /// keeps int/float unshadowable") — this diagnostic does not re-order
1977    /// resolution, it names the consequence: every bare type annotation
1978    /// spelling the colliding name (`VAR v: content = ...`, a param/return
1979    /// annotation, …) silently resolves to the builtin/tower type, never to
1980    /// the struct, with previously no diagnostic in either direction.
1981    ///
1982    /// Deliberately does **not** cover the generic heads
1983    /// (`List`/`Array`/`Map`/`Option`/`Weighted`/`Handle`): those names are
1984    /// special-cased only inside `TypeExpr::Generic`'s own dispatch (e.g.
1985    /// `Array<T>`) — a *bare* `Named` reference to a struct sharing one of
1986    /// those names (`f: Array`, no `<...>`) still falls through to the
1987    /// ordinary `names.structs` lookup and resolves to the struct
1988    /// correctly; there is no actual collision to diagnose for those names.
1989    /// Also does not cover `void` — unlike the leaves above, `resolve`'s
1990    /// `Named` arm has no explicit `"void"` case at all, so a struct named
1991    /// `void` resolves fine too. Also does not cover a name shared with a
1992    /// declared `LIST` or a registered `Handle<K>` kind: `names.lists`/
1993    /// `names.handles` are only ever consulted inside `List<L>`/`Handle<K>`'s
1994    /// own generic-argument position, never against a bare `Named`
1995    /// annotation — a different namespace, no collision.
1996    ///
1997    /// A construction literal (`Name#{...}`) is unaffected by this
1998    /// shadowing for every name this code covers:
1999    /// `resolve::resolve_struct_ref`/`resolve_type_ref` resolve a `STRUCT`
2000    /// reference by ordinary `SymbolKind::Struct` lookup alone, with no
2001    /// builtin/tower precedence check at all — so `content#{...}` still
2002    /// constructs the user's struct even though `VAR v: content = ...`
2003    /// cannot name it.
2004    ///
2005    /// Warning-tier, not a rejected declaration (matches [`Self::E035`]'s
2006    /// "name shadows a built-in function" precedent, and the "deliberate"
2007    /// framing `resolve`'s own doc already gives this exact ordering) — a
2008    /// `STRUCT` named this way still compiles and constructs normally; only
2009    /// its *annotation* spelling is shadowed.
2010    E188,
2011
2012    /// Renaming an `EXTERNAL` changes the host binding (ruled 2026-08-24,
2013    /// "External renames: allowed behind the always-unsafe Force gate").
2014    ///
2015    /// Synthesized by the IDE's safe-rename gate, never emitted by
2016    /// compilation: an external's name is the story↔engine contract, so the
2017    /// story-side rename is always reported as breakage — the engine must
2018    /// re-register the function under the new name — and applies only
2019    /// through the report's Force path.
2020    E190,
2021
2022    /// An ink `TODO:` author note (issue #3050).
2023    ///
2024    /// Not a defect at all: `AUTHOR_WARNING` lines are the language's own
2025    /// work-remains marker, and until #3050 lowering dropped them silently.
2026    /// Surfacing each as an `Info`-default diagnostic (the [`Self::E157`]
2027    /// tier precedent) puts TODOs in the Problems panel and gives the
2028    /// studio's TODO panel a single source to consume, while never gating a
2029    /// compile and staying `[lints]`-tierable like every other code.
2030    E189,
2031
2032    /// A content line's inline stateful alternatives enumerate to more
2033    /// whole-line variants than the variant-group cap admits (#3274).
2034    ///
2035    /// The stage-2 flip compiles a line of textual alternatives into one
2036    /// enumerated variant group — each variant a real line-table entry, a
2037    /// translation unit, and a VO slot — so the product of the
2038    /// alternatives' branch counts is bounded
2039    /// (`lir::lower::recognize::VARIANT_CAP`). Breaching it is a worded
2040    /// hard error, never a silent fallback: an author whose line quietly
2041    /// stopped being VO-addressable would have no way to notice. The fix
2042    /// is to split the line or move an alternative to its own line.
2043    E191,
2044
2045    /// A `brink-`prefixed comment the suppression parser did not understand
2046    /// (#3259).
2047    ///
2048    /// Directives were matched by exact string equality and anything else
2049    /// was dropped in silence — so `// brink-disable-file E157`, which looks
2050    /// exactly like the line-scoped form that DOES take codes, suppressed
2051    /// nothing and reported nothing. The author got neither the behaviour
2052    /// they asked for nor a reason, which is the silent-drop shape this
2053    /// project treats as a bug by default.
2054    ///
2055    /// `Warning`-tier: the file still compiles. The harm is that a
2056    /// suppression the author believes is in force is not.
2057    E192,
2058
2059    /// A `~ temp` is read on a path its declaration does not dominate
2060    /// (#3354, RULED 2026-09-01 option C).
2061    ///
2062    /// The declaration and the read live in the same call frame, so the
2063    /// read resolves to the temp's own slot — but nothing guarantees the
2064    /// declaring statement ran first. The three shapes the ruling names are
2065    /// a sibling choice branch, a gather reached from a branch that did not
2066    /// declare, and a read written textually ahead of the declaration. (A
2067    /// fourth shape the ruling originally enumerated — a stitch reading a
2068    /// temp declared at its knot's root — is not a dominance question at
2069    /// all: the PR #3369 review found it warns on a knot/stitch divert that
2070    /// runs the declaration and then plays correctly, and the 2026-09-01
2071    /// follow-up ruling on #3373 moved it out of `E193` entirely into its
2072    /// own compat-deny code, [`Self::E194`].)
2073    ///
2074    /// `Warning`-tier, `[lints]`-overridable: the story still runs. The
2075    /// runtime reads an uninitialized slot as ink's missing-variable
2076    /// default (`0`, which is also `false`) and warns — matching the C#
2077    /// reference, so what plays in Inky plays in brink — and this
2078    /// diagnostic is what tells the author before they play.
2079    E193,
2080
2081    /// A knot's `~ temp` (native `~ let`) is read from one of that knot's
2082    /// stitches (#3373, RULED 2026-09-01) — split out of [`Self::E193`]'s
2083    /// former shape 4 during PR #3369's review.
2084    ///
2085    /// Brink's `lir::lower::temps::alloc_temps` treats a knot and every one
2086    /// of its stitches as one shared call frame with one `TempMap`, so a
2087    /// stitch's reference to a name the knot's root declares resolves to
2088    /// that same slot and the story plays correctly. Ink's own compiler
2089    /// does not extend a knot's `~ temp` visibility into its stitches at
2090    /// all — the identical program is a compile-time
2091    /// `Unresolved variable` error in inklecate. This is brink accepting a
2092    /// strict superset of ink, not a defect in either compiler, which makes
2093    /// it the first member of the **compat-deny** tier (`docs/compiler-spec.md`
2094    /// "Compat-deny diagnostics"): `Error` by default (inklecate rejects
2095    /// the program, so brink does too until a project opts in) but, unlike
2096    /// every other `Error`-default code, `[lints]`-overridable — all the
2097    /// way to `allow` — because the admission invariant that tier requires
2098    /// is met: downgraded, brink produces a working program.
2099    E194,
2100
2101    /// A choice with neither display/bracket text nor a divert (#3365),
2102    /// matching inklecate's own "Choice is completely empty" warning
2103    /// (`InkParser/InkParser_Choices.cs:84-86`; line 90 guards a different
2104    /// warning — "Blank choice", on the `* [] some text` shape — which this
2105    /// code deliberately does not cover).
2106    ///
2107    /// Raised from `hir::lower::choice::LowerChoice::lower_choice` (the ink
2108    /// surface only — see this code's doc page for why the native `{? … }`
2109    /// surface is deliberately not wired to it), where the same-line
2110    /// evidence the check needs — whether a `->`/divert token was written at
2111    /// all, even an empty one — is still available. Once lowered into
2112    /// `hir::Choice`, an explicit-but-empty divert (`* ->`) and no divert at
2113    /// all (`* []`) are indistinguishable (both leave no `Stmt::Divert` in
2114    /// the choice's body), so the check cannot be reconstructed later from
2115    /// the HIR alone the way `E034`'s all-fallback check can.
2116    ///
2117    /// Fires only when the choice has none of: a same-line divert (with or
2118    /// without a target), a tag directly on the choice line, or actual text
2119    /// in any of its three content regions (`start`/`bracket`/`inner`). A
2120    /// `(label)` or `{condition}` guard does NOT exempt a choice — matching
2121    /// the reference, which has no such carve-out either. `Warning`,
2122    /// `[lints]`-overridable, matching the sibling markup/shadow-warning
2123    /// family (`E164`/`E188`/…) — the story still compiles.
2124    E195,
2125}
2126
2127impl DiagnosticCode {
2128    /// Every `DiagnosticCode` variant, in declaration order.
2129    ///
2130    /// Kept in sync with the enum by hand (there is no derive-based
2131    /// enumeration here), but exercised by
2132    /// `brink-test-harness/tests/diagnostic_docs_validation.rs`'s
2133    /// `diagnostic_codes_are_unique` test: that test asserts `ALL.len()`
2134    /// matches the number of code strings `from_str_code` recognizes, so a
2135    /// variant added to the enum but missed here fails CI immediately
2136    /// instead of silently under-covering the uniqueness/round-trip checks.
2137    pub const ALL: &'static [Self] = &[
2138        Self::E001,
2139        Self::E002,
2140        Self::E003,
2141        Self::E004,
2142        Self::E005,
2143        Self::E006,
2144        Self::E007,
2145        Self::E008,
2146        Self::E009,
2147        Self::E010,
2148        Self::E011,
2149        Self::E012,
2150        Self::E013,
2151        Self::E014,
2152        Self::E015,
2153        Self::E016,
2154        Self::E017,
2155        Self::E018,
2156        Self::E019,
2157        Self::E020,
2158        Self::E021,
2159        Self::E022,
2160        Self::E023,
2161        Self::E024,
2162        Self::E025,
2163        Self::E026,
2164        Self::E027,
2165        Self::E028,
2166        Self::E029,
2167        Self::E030,
2168        Self::E031,
2169        Self::E032,
2170        Self::E033,
2171        Self::E034,
2172        Self::E035,
2173        Self::E036,
2174        Self::E037,
2175        Self::E038,
2176        Self::E039,
2177        Self::E040,
2178        Self::E041,
2179        Self::E042,
2180        Self::E043,
2181        Self::E044,
2182        Self::E045,
2183        Self::E046,
2184        Self::E047,
2185        Self::E048,
2186        Self::E049,
2187        Self::E050,
2188        Self::E051,
2189        Self::E052,
2190        Self::E053,
2191        Self::E054,
2192        Self::E055,
2193        Self::E056,
2194        Self::E057,
2195        Self::E058,
2196        Self::E059,
2197        Self::E060,
2198        Self::E061,
2199        Self::E062,
2200        Self::E063,
2201        Self::E064,
2202        Self::E065,
2203        Self::E066,
2204        Self::E067,
2205        Self::E068,
2206        Self::E069,
2207        Self::E070,
2208        Self::E071,
2209        Self::E072,
2210        Self::E073,
2211        Self::E074,
2212        Self::E075,
2213        Self::E076,
2214        Self::E077,
2215        Self::E078,
2216        Self::E079,
2217        Self::E080,
2218        Self::E081,
2219        Self::E082,
2220        Self::E083,
2221        Self::E084,
2222        Self::E085,
2223        Self::E086,
2224        Self::E087,
2225        Self::E088,
2226        Self::E089,
2227        Self::E090,
2228        Self::E091,
2229        Self::E092,
2230        Self::E093,
2231        Self::E094,
2232        Self::E095,
2233        Self::E096,
2234        Self::E097,
2235        Self::E098,
2236        Self::E099,
2237        Self::E100,
2238        Self::E101,
2239        Self::E102,
2240        Self::E103,
2241        Self::E104,
2242        Self::E105,
2243        Self::E106,
2244        Self::E107,
2245        Self::E108,
2246        Self::E109,
2247        Self::E110,
2248        Self::E111,
2249        Self::E112,
2250        Self::E113,
2251        Self::E114,
2252        Self::E115,
2253        Self::E116,
2254        Self::E117,
2255        Self::E118,
2256        Self::E119,
2257        Self::E120,
2258        Self::E121,
2259        Self::E122,
2260        Self::E123,
2261        Self::E124,
2262        Self::E125,
2263        Self::E126,
2264        Self::E127,
2265        Self::E128,
2266        Self::E129,
2267        Self::E130,
2268        Self::E131,
2269        Self::E132,
2270        Self::E133,
2271        Self::E134,
2272        Self::E135,
2273        Self::E136,
2274        Self::E137,
2275        Self::E138,
2276        Self::E139,
2277        Self::E140,
2278        Self::E141,
2279        Self::E142,
2280        Self::E143,
2281        Self::E144,
2282        Self::E145,
2283        Self::E146,
2284        Self::E147,
2285        Self::E148,
2286        Self::E149,
2287        Self::E150,
2288        Self::E151,
2289        Self::E152,
2290        Self::E153,
2291        Self::E154,
2292        Self::E155,
2293        Self::E156,
2294        Self::E157,
2295        Self::E158,
2296        Self::E159,
2297        Self::E160,
2298        Self::E161,
2299        Self::E162,
2300        Self::E163,
2301        Self::E164,
2302        Self::E165,
2303        Self::E166,
2304        Self::E167,
2305        Self::E168,
2306        Self::E169,
2307        Self::E170,
2308        Self::E171,
2309        Self::E172,
2310        Self::E173,
2311        Self::E174,
2312        Self::E175,
2313        Self::E176,
2314        Self::E178,
2315        Self::E179,
2316        Self::E180,
2317        Self::E181,
2318        Self::E182,
2319        Self::E183,
2320        Self::E184,
2321        Self::E185,
2322        Self::E186,
2323        Self::E187,
2324        Self::E188,
2325        Self::E189,
2326        Self::E190,
2327        Self::E191,
2328        Self::E192,
2329        Self::E193,
2330        Self::E194,
2331        Self::E195,
2332    ];
2333
2334    /// The stable string representation (e.g., `"E001"`).
2335    #[must_use]
2336    #[expect(
2337        clippy::too_many_lines,
2338        reason = "a flat one-arm-per-code table that necessarily grows with the diagnostic set"
2339    )]
2340    pub fn as_str(self) -> &'static str {
2341        match self {
2342            Self::E001 => "E001",
2343            Self::E002 => "E002",
2344            Self::E003 => "E003",
2345            Self::E004 => "E004",
2346            Self::E005 => "E005",
2347            Self::E006 => "E006",
2348            Self::E007 => "E007",
2349            Self::E008 => "E008",
2350            Self::E009 => "E009",
2351            Self::E010 => "E010",
2352            Self::E011 => "E011",
2353            Self::E012 => "E012",
2354            Self::E013 => "E013",
2355            Self::E014 => "E014",
2356            Self::E015 => "E015",
2357            Self::E016 => "E016",
2358            Self::E017 => "E017",
2359            Self::E018 => "E018",
2360            Self::E019 => "E019",
2361            Self::E020 => "E020",
2362            Self::E021 => "E021",
2363            Self::E022 => "E022",
2364            Self::E023 => "E023",
2365            Self::E024 => "E024",
2366            Self::E025 => "E025",
2367            Self::E026 => "E026",
2368            Self::E027 => "E027",
2369            Self::E028 => "E028",
2370            Self::E029 => "E029",
2371            Self::E030 => "E030",
2372            Self::E031 => "E031",
2373            Self::E032 => "E032",
2374            Self::E033 => "E033",
2375            Self::E034 => "E034",
2376            Self::E035 => "E035",
2377            Self::E036 => "E036",
2378            Self::E037 => "E037",
2379            Self::E038 => "E038",
2380            Self::E039 => "E039",
2381            Self::E040 => "E040",
2382            Self::E041 => "E041",
2383            Self::E042 => "E042",
2384            Self::E043 => "E043",
2385            Self::E044 => "E044",
2386            Self::E045 => "E045",
2387            Self::E046 => "E046",
2388            Self::E047 => "E047",
2389            Self::E048 => "E048",
2390            Self::E049 => "E049",
2391            Self::E050 => "E050",
2392            Self::E051 => "E051",
2393            Self::E052 => "E052",
2394            Self::E053 => "E053",
2395            Self::E054 => "E054",
2396            Self::E055 => "E055",
2397            Self::E056 => "E056",
2398            Self::E057 => "E057",
2399            Self::E058 => "E058",
2400            Self::E059 => "E059",
2401            Self::E060 => "E060",
2402            Self::E061 => "E061",
2403            Self::E062 => "E062",
2404            Self::E063 => "E063",
2405            Self::E064 => "E064",
2406            Self::E065 => "E065",
2407            Self::E066 => "E066",
2408            Self::E067 => "E067",
2409            Self::E068 => "E068",
2410            Self::E069 => "E069",
2411            Self::E070 => "E070",
2412            Self::E071 => "E071",
2413            Self::E072 => "E072",
2414            Self::E073 => "E073",
2415            Self::E074 => "E074",
2416            Self::E075 => "E075",
2417            Self::E076 => "E076",
2418            Self::E077 => "E077",
2419            Self::E078 => "E078",
2420            Self::E079 => "E079",
2421            Self::E080 => "E080",
2422            Self::E081 => "E081",
2423            Self::E082 => "E082",
2424            Self::E083 => "E083",
2425            Self::E084 => "E084",
2426            Self::E085 => "E085",
2427            Self::E086 => "E086",
2428            Self::E087 => "E087",
2429            Self::E088 => "E088",
2430            Self::E089 => "E089",
2431            Self::E090 => "E090",
2432            Self::E091 => "E091",
2433            Self::E092 => "E092",
2434            Self::E093 => "E093",
2435            Self::E094 => "E094",
2436            Self::E095 => "E095",
2437            Self::E096 => "E096",
2438            Self::E097 => "E097",
2439            Self::E098 => "E098",
2440            Self::E099 => "E099",
2441            Self::E100 => "E100",
2442            Self::E101 => "E101",
2443            Self::E102 => "E102",
2444            Self::E103 => "E103",
2445            Self::E104 => "E104",
2446            Self::E105 => "E105",
2447            Self::E106 => "E106",
2448            Self::E107 => "E107",
2449            Self::E108 => "E108",
2450            Self::E109 => "E109",
2451            Self::E110 => "E110",
2452            Self::E111 => "E111",
2453            Self::E112 => "E112",
2454            Self::E113 => "E113",
2455            Self::E114 => "E114",
2456            Self::E115 => "E115",
2457            Self::E116 => "E116",
2458            Self::E117 => "E117",
2459            Self::E118 => "E118",
2460            Self::E119 => "E119",
2461            Self::E120 => "E120",
2462            Self::E121 => "E121",
2463            Self::E122 => "E122",
2464            Self::E123 => "E123",
2465            Self::E124 => "E124",
2466            Self::E125 => "E125",
2467            Self::E126 => "E126",
2468            Self::E127 => "E127",
2469            Self::E128 => "E128",
2470            Self::E129 => "E129",
2471            Self::E130 => "E130",
2472            Self::E131 => "E131",
2473            Self::E132 => "E132",
2474            Self::E133 => "E133",
2475            Self::E134 => "E134",
2476            Self::E135 => "E135",
2477            Self::E136 => "E136",
2478            Self::E137 => "E137",
2479            Self::E138 => "E138",
2480            Self::E139 => "E139",
2481            Self::E140 => "E140",
2482            Self::E141 => "E141",
2483            Self::E142 => "E142",
2484            Self::E143 => "E143",
2485            Self::E144 => "E144",
2486            Self::E145 => "E145",
2487            Self::E146 => "E146",
2488            Self::E147 => "E147",
2489            Self::E148 => "E148",
2490            Self::E149 => "E149",
2491            Self::E150 => "E150",
2492            Self::E151 => "E151",
2493            Self::E152 => "E152",
2494            Self::E153 => "E153",
2495            Self::E154 => "E154",
2496            Self::E155 => "E155",
2497            Self::E156 => "E156",
2498            Self::E157 => "E157",
2499            Self::E158 => "E158",
2500            Self::E159 => "E159",
2501            Self::E160 => "E160",
2502            Self::E161 => "E161",
2503            Self::E162 => "E162",
2504            Self::E163 => "E163",
2505            Self::E164 => "E164",
2506            Self::E165 => "E165",
2507            Self::E166 => "E166",
2508            Self::E167 => "E167",
2509            Self::E168 => "E168",
2510            Self::E169 => "E169",
2511            Self::E170 => "E170",
2512            Self::E171 => "E171",
2513            Self::E172 => "E172",
2514            Self::E173 => "E173",
2515            Self::E174 => "E174",
2516            Self::E175 => "E175",
2517            Self::E176 => "E176",
2518            Self::E178 => "E178",
2519            Self::E179 => "E179",
2520            Self::E180 => "E180",
2521            Self::E181 => "E181",
2522            Self::E182 => "E182",
2523            Self::E183 => "E183",
2524            Self::E184 => "E184",
2525            Self::E185 => "E185",
2526            Self::E186 => "E186",
2527            Self::E187 => "E187",
2528            Self::E188 => "E188",
2529            Self::E189 => "E189",
2530            Self::E190 => "E190",
2531            Self::E191 => "E191",
2532            Self::E192 => "E192",
2533            Self::E193 => "E193",
2534            Self::E194 => "E194",
2535            Self::E195 => "E195",
2536        }
2537    }
2538
2539    /// Short human-readable title for this diagnostic code.
2540    #[must_use]
2541    #[expect(
2542        clippy::too_many_lines,
2543        reason = "a flat one-arm-per-code message table that necessarily grows with the diagnostic set"
2544    )]
2545    pub fn title(self) -> &'static str {
2546        match self {
2547            Self::E001 => "knot is missing a name",
2548            Self::E002 => "stitch is missing a name",
2549            Self::E003 => "parameter is missing a name",
2550            Self::E004 => "VAR declaration is missing a name",
2551            Self::E005 => "VAR declaration is missing an initializer",
2552            Self::E006 => "CONST declaration is missing a name",
2553            Self::E007 => "CONST declaration is missing an initializer",
2554            Self::E008 => "LIST declaration is missing a name",
2555            Self::E009 => "LIST member is missing a name",
2556            Self::E010 => "EXTERNAL declaration is missing a name",
2557            Self::E011 => "retired (lane-A audit) — parser always creates FILE_PATH",
2558            Self::E012 => "divert is missing a target",
2559            Self::E013 | Self::E018 => "retired (lane-A audit) — parser always creates PATH node",
2560            Self::E014 => "logic line has no effect",
2561            Self::E015 => "expression is missing an operand",
2562            Self::E016 => "unknown or unsupported operator",
2563            Self::E017 => "function call is missing a name",
2564            Self::E019 => "retired (lane-A audit) — parser guarantees bullet markers",
2565            Self::E020 => "inline conditional is missing a condition",
2566            Self::E021 => "inline sequence has no branches",
2567            Self::E022 => "duplicate knot definition",
2568            Self::E023 => "duplicate variable/constant definition",
2569            Self::E024 => "unresolved divert target",
2570            Self::E025 => "unresolved variable reference",
2571            Self::E026 => "duplicate list item",
2572            Self::E027 => "ambiguous bare list item reference",
2573            Self::E028 => "retired (lane-A audit) — circular INCLUDE surfaces as CompileError",
2574            Self::E029 => "choice in conditional must explicitly divert",
2575            Self::E030 => "string interpolation in constant initializer is ignored",
2576            Self::E031 => "function call argument count mismatch",
2577            Self::E032 => "return statement outside function",
2578            Self::E033 => "unreachable code after divert",
2579            Self::E034 => "choice set has only fallback choices",
2580            Self::E035 => "name shadows a built-in function",
2581            Self::E036 => "expected diagnostic not produced",
2582            Self::E037 => "syntax error",
2583            Self::E038 => "malformed doc-comment tag",
2584            Self::E039 => "manifest disagrees with EXTERNAL arity",
2585            Self::E040 => "unknown semantic type",
2586            Self::E041 => "external argument type mismatch",
2587            Self::E042 => "external argument out of domain",
2588            Self::E043 => "doc-comment tag not applicable to this declaration",
2589            Self::E044 => "unknown directive",
2590            Self::E045 => "directive has no valid target here",
2591            Self::E046 => "directive must be static text",
2592            Self::E047 => "directive must be the only tag on its line",
2593            Self::E048 => "duplicate directive",
2594            Self::E049 => "directive not supported on this target",
2595            Self::E050 => "directive does not take arguments",
2596            Self::E051 => "brink extension used under strict-ink dialect",
2597            Self::E052 => "brink extension not yet implemented",
2598            Self::E053 => "retired (T1b-2) — T1b extension lowering is complete",
2599            Self::E054 => "block-scoped temp shadows an already-visible temp",
2600            Self::E055 => "collection mutator's first argument is not an lvalue",
2601            Self::E056 => "collection mutator used in expression position",
2602            Self::E057 => "break/continue outside a loop",
2603            Self::E058 => "collection mutator argument count mismatch",
2604            Self::E059 => "choice/gather construct nested inside inline content",
2605            Self::E060 => "internal codegen error",
2606            Self::E061 => "unknown type name in annotation",
2607            Self::E062 => "retired (T1c-1) — fn(T…): R annotations now resolve for real",
2608            Self::E063 => "type annotation disagrees with inferred type",
2609            Self::E064 => "strict types require the brink dialect",
2610            Self::E065 => "type escapes strict inference as Unknown",
2611            Self::E066 => "type is Conflicted under strict inference",
2612            Self::E067 => "assigning the result of a void function",
2613            Self::E068 => "struct construction literal names an undeclared STRUCT",
2614            Self::E069 => "struct construction literal is missing a declared field",
2615            Self::E070 => "struct construction literal supplies an undeclared field",
2616            Self::E071 => "struct construction literal field disagrees with the declared type",
2617            Self::E072 => "retired (TM-4c) — struct constructs now lower for real",
2618            Self::E073 => {
2619                "struct construction literal names an unresolved STRUCT shape at LIR lowering"
2620            }
2621            Self::E074 => "chained field-write projection (p.a.b = v) is not supported",
2622            Self::E075 => {
2623                "struct construction literal in a VAR/CONST declaration default does not match its declared shape"
2624            }
2625            Self::E076 => {
2626                "map literal key in a VAR/CONST declaration default is not a compile-time-constant scalar (int/string/bool)"
2627            }
2628            Self::E077 => {
2629                "array element, map value, or #fn bound value argument in a VAR/CONST declaration default is not a compile-time-constant expression"
2630            }
2631            Self::E078 => "int()/float() argument is outside the permissive numeric+bool domain",
2632            Self::E079 => "#fn target is not a statically-named function definition",
2633            Self::E080 => {
2634                "ref-argument (#fn, call, or bind) does not bind a durable cell at creation"
2635            }
2636            Self::E081 => "#fn binds more arguments than the target declares",
2637            Self::E082 => "block-scoped temp referenced after its block has closed",
2638            Self::E083 => "VAR/CONST declaration default is not a compile-time-constant expression",
2639            Self::E084 => "struct construction literal supplies a duplicate field",
2640            Self::E085 => {
2641                "file's module (its stem) collides with a declared module of the same name"
2642            }
2643            Self::E086 => {
2644                "`#@module` requires exactly one module name and may appear at most once per file"
2645            }
2646            Self::E087 => "reference to a `#@private` definition in another module",
2647            Self::E088 => {
2648                "bare `IMPORT { name } FROM mod` names a definition the declared module does not export"
2649            }
2650            Self::E089 => "`IMPORT` brings the same name into scope more than once",
2651            Self::E090 => "a module cannot `IMPORT` itself",
2652            Self::E091 => {
2653                "qualified access is ambiguous: the name is both an imported module and a definition"
2654            }
2655            Self::E092 => "redundant `#@public`/`#@private` — restates the module default",
2656            Self::E093 => "conflicting or repeated visibility directives on one declaration",
2657            Self::E094 => "`#@was` requires exactly one non-empty old-name argument",
2658            Self::E095 => "`#@was` names the definition's own current name — nothing to migrate",
2659            Self::E096 => "duplicate definition declared in two different modules",
2660            Self::E097 => "`ref` projection expression outside ref-argument position",
2661            Self::E098 => "ref-argument path segment disagrees with the statically-known shape",
2662            Self::E099 => "path-projection ref-argument is not yet lowerable (T1e-2, #828)",
2663            Self::E100 => "`#@effects` requires `pure` or at least one reads/writes/calls clause",
2664            Self::E101 => "malformed `#@effects` clause (unknown keyword or non-identifier value)",
2665            Self::E102 => "`#@effects` clause names an unknown global cell or external",
2666            Self::E103 => "inferred effects exceed the `#@effects` assertion's declared bound",
2667            Self::E104 => {
2668                "direct-call syntax requires a bare variable/temp/param callee — use `call(f, args…)` for a computed callee"
2669            }
2670            Self::E105 => {
2671                "`await` condition must be effect-free (read-only) — it writes a global or performs an effectful call"
2672            }
2673            Self::E106 => "map-literal key is outside the int/string/bool key domain",
2674            Self::E107 => "bare `none` needs a type from context",
2675            Self::E108 => {
2676                "inferred effects exceed the `@[effects(silent)]` assertion (the definition can produce content)"
2677            }
2678            Self::E109 => {
2679                "inferred effects exceed the `@[effects(total)]` assertion (the definition can raise a turn-terminating fault)"
2680            }
2681            Self::E110 => {
2682                "`#@effects(…)` is deprecated; use the `@[effects(…)]` annotation spelling"
2683            }
2684            Self::E111 => {
2685                "unknown annotation name (the `@[…]` channel recognizes `effects`, plus `was` and `allow` on the native surface)"
2686            }
2687            Self::E112 => {
2688                "annotation line outside a recognized placement (ink: top of a knot/stitch body; native: directly above a `flow`/`fn`, or above any declaration or statement for `allow`)"
2689            }
2690            Self::E113 => {
2691                "reserved protocol method name (`display`/`compare`/`next` belong to the protocol registry)"
2692            }
2693            Self::E114 => "protocol impl exceeds its protocol's effect contract",
2694            Self::E115 => "ill-formed protocol impl registration",
2695            Self::E116 => {
2696                "an `Option[T]` has no truthiness — test `== none` / `== some(x)` in the condition"
2697            }
2698            Self::E117 => "`int(r)` requires an inhabited range (NonEmptyRange)",
2699            Self::E118 => {
2700                "numeric-tower kinds are compiler-known and cannot implement registry protocols"
2701            }
2702            // Two verb families share this code because one sitting ruled
2703            // both: NS-A4's `sort_by`/`sorted_by` comparators and the
2704            // fn-value verb layer's pure trio `map`/`filter`/`fold`
2705            // (issue #1679). The title names the shared requirement; the
2706            // per-site message names the verb and its callback's role.
2707            Self::E119 => "callback must be a pure, silent function",
2708            Self::E120 => "`weighted` requires weight/value pairs with positive int weights",
2709            Self::E121 => {
2710                "admission: unresolved reference has no matching referencing expression in the HIR body"
2711            }
2712            Self::E122 => "admission: declared symbol has no corresponding HIR declaration node",
2713            Self::E123 => {
2714                "admission: knot's `is_function` disagrees with its indexed function sentinel"
2715            }
2716            Self::E124 => "admission: node range is empty or extends past the end of the file",
2717            Self::E125 => "admission: two references share an identical source range",
2718            Self::E126 => {
2719                "admission: declared symbol's name does not match its kind's qualification shape"
2720            }
2721            Self::E127 => {
2722                "admission: divert or return is not the last statement in an inline conditional/sequence branch"
2723            }
2724            Self::E128 => {
2725                "admission: container's provenance kind disagrees with its indexed symbol kind"
2726            }
2727            Self::E129 => "native: construct parses but has no HIR lowering yet",
2728            Self::E130 => "native: `flow` nested more than two levels deep is not yet supported",
2729            Self::E131 => "native: `<-` (splice) used outside a choice point has no effect",
2730            Self::E132 => {
2731                "native: `@[was]` needs a quoted old module path, e.g. `@[was(\"story::old::path\")]`"
2732            }
2733            Self::E133 => {
2734                "native accept-list: root_content must be empty or the synthesized `flow main()` entry divert"
2735            }
2736            Self::E134 => {
2737                "native accept-list: INCLUDE sites are ink-only baggage, never legal in native HIR"
2738            }
2739            Self::E135 => "native accept-list: thread-start outside choice-point splice position",
2740            Self::E136 => "native accept-list: choice set carries a non-neutral weave-fold value",
2741            Self::E137 => "native .brink compile requires types = strict",
2742            Self::E138 => "map construction literal supplies a duplicate key",
2743            Self::E139 => "construction literal entries do not match the target type's form",
2744            Self::E140 => "method-call syntax matched a field that is not callable",
2745            Self::E141 => "method-call syntax matched neither a field nor a free function",
2746            Self::E142 => "method-call receiver type is unknown — annotate it",
2747            Self::E143 => "method-call auto-ref needs a receiver that can be written through",
2748            Self::E144 => "native: method call resolves but has no LIR lowering yet",
2749            Self::E145 => {
2750                "the `as` binding must be the entire condition (no `&&`/`||` composition)"
2751            }
2752            Self::E146 => "retired (issue #1508) — choice-guard `as` bindings now lower for real",
2753            Self::E147 => "the `as` binding requires an `Option[T]` condition",
2754            Self::E148 => "an `as` binding is immutable and cannot be assigned to",
2755            Self::E149 => "`remove` is map-only — did you mean `remove_at`?",
2756            Self::E150 => {
2757                "declares a return type but the body may fall through without returning a value"
2758            }
2759            Self::E151 => {
2760                "native: this choice branch falls through while a sibling diverts — did you mean to add `-> …`, or `-> DONE` to end deliberately?"
2761            }
2762            Self::E152 => {
2763                "`contains`'s needle is statically outside the map key domain — this call always returns `false`"
2764            }
2765            Self::E153 => "`@[allow(…)]` names a diagnostic code this compiler does not know",
2766            Self::E154 => {
2767                "`@[allow(…)]` names a non-suppressible diagnostic — only codes whose default severity is not `Error` can be silenced at the source"
2768            }
2769            Self::E155 => {
2770                "`@[allow(…)]` needs at least one bare diagnostic code, e.g. `@[allow(E151)]`"
2771            }
2772            Self::E156 => {
2773                "a lambda cannot assign to a captured binding — captures are by value, so the write would be lost"
2774            }
2775            Self::E157 => {
2776                "this once-only choice or sequence carries durable state but has no name to anchor its identity across edits"
2777            }
2778            Self::E158 => {
2779                "a lambda cannot capture this local here — most likely its own `let` name read recursively, before the `let` finishes binding"
2780            }
2781            Self::E159 => {
2782                "`@[element(…)]` needs exactly one of `args = \"…\"` / `claims = \"…\"`, whose value compiles as a portable-regex pattern"
2783            }
2784            Self::E160 => {
2785                "`@[element(…)]`'s pattern names a capture group that does not match any parameter on the annotated declaration"
2786            }
2787            Self::E161 => {
2788                "`@[style(…)]` clauses must be `key = \"value\"` pairs, e.g. `@[style(line = \"dim\")]`"
2789            }
2790            Self::E162 => {
2791                "`@[style(…)]` names a key that is neither `line`, `dispatch`, nor a capture declared by the paired `@[element(…)]`"
2792            }
2793            Self::E163 => "`@[style(…)]` needs a paired `@[element(…)]` on the same declaration",
2794            Self::E164 => {
2795                "inline markup tag is not declared in the host manifest's markup vocabulary"
2796            }
2797            Self::E165 => {
2798                "inline markup attribute is not declared for this span kind in the host manifest"
2799            }
2800            Self::E166 => {
2801                "a block `@[element(…, block)]` / `@[convention(…, block)]` needs a trailing `content`-typed parameter that is not one of its own named captures"
2802            }
2803            Self::E167 => {
2804                "a `@[convention(claims = \"…\", order = N)]` handler declares a parameter its pattern never captures"
2805            }
2806            Self::E168 => {
2807                "this `@[convention(claims = \"…\", order = N)]` pattern is byte-identical to an earlier-declared handler's, and never won a claim of its own — it is dead code"
2808            }
2809            Self::E169 => {
2810                "a pattern-claiming `@[convention(claims = \"…\", order = N)]` handler is legal only in the project's configured conventions module (`brink.toml`'s `[project] conventions`)"
2811            }
2812            Self::E170 => {
2813                "this `@[convention(claims = \"…\", order = N)]` pattern can overlap with an earlier-declared handler's pattern — they silently race, with the lower-`order` one winning"
2814            }
2815            Self::E171 => {
2816                "a `@[convention(claims = \"…\", order = N)]` handler's captured parameter is declared `string`-incompatible — every capture binds as a plain string literal until numeric coercion lands"
2817            }
2818            Self::E172 => {
2819                "native: a `#…` tag beginning with `@` is the ink-dialect compiler-directive shape (`#@private`/`#@was`/`#@local`/…) — native has no such directive channel, so it lowers as an ordinary runtime tag"
2820            }
2821            Self::E173 => {
2822                "inline markup tag is missing an attribute the host manifest marks required for this span kind"
2823            }
2824            Self::E174 => {
2825                "a lambda's written parameter/return annotation disagrees with the type its body actually infers"
2826            }
2827            Self::E175 => {
2828                "retired (issue #2165) — `fn conventions()`/`register` were dissolved from the design"
2829            }
2830            Self::E176 => {
2831                "a divert-with-args site (`-> knot(args)`, tunnel call, or thread-start) supplies the wrong number of arguments for its resolved target's declared parameters"
2832            }
2833            Self::E178 => "`@[convention(…)]` needs a required `order = N` clause",
2834            Self::E179 => "two `@[convention]` declarations in this module carry the same `order`",
2835            Self::E180 => {
2836                "a `@[convention(…, attach = StructName)]` clause disagrees with the handler's own declared return type"
2837            }
2838            Self::E181 => {
2839                "a declared STRUCT's own definition could not be resolved while building the struct-shape table — every surviving same-name candidate is std-declared"
2840            }
2841            Self::E182 => {
2842                "a `@[convention]` handler's call closure reaches a world-reading (or unclassified) `EXTERNAL` — handlers may call pure functions and commands, but never read world state"
2843            }
2844            Self::E183 => "call target resolved to a symbol kind that is not callable",
2845            Self::E184 => {
2846                "a declared CONST/VAR/EXTERNAL's own definition could not be resolved while lowering — every surviving same-name candidate is std-declared"
2847            }
2848            Self::E185 => "plain assignment target names a field its struct shape does not declare",
2849            Self::E186 => {
2850                "`@[convention(…)]` declares both `block` and `attach = StructName` — mutually exclusive clauses"
2851            }
2852            Self::E187 => {
2853                "write to a CONST — CONST is immutable and can never be reassigned, mutated, or passed by `ref`"
2854            }
2855            Self::E188 => {
2856                "declared STRUCT name collides with a reserved builtin/tower type name and is unreachable in type annotations"
2857            }
2858            Self::E189 => "ink `TODO:` author note — work the author marked as remaining",
2859            Self::E190 => {
2860                "renaming an EXTERNAL changes the host binding — the engine must re-register the new name"
2861            }
2862            Self::E191 => {
2863                "inline alternatives on one line enumerate to more whole-line variants than the cap allows"
2864            }
2865            Self::E192 => {
2866                "unrecognized `brink-` directive comment — it suppresses nothing as written"
2867            }
2868            Self::E193 => "`temp` read on a path its declaration does not dominate",
2869            Self::E194 => "a knot's temp is not visible from its stitches",
2870            Self::E195 => "choice has neither display text nor a divert",
2871        }
2872    }
2873
2874    /// Default severity for this diagnostic code.
2875    #[must_use]
2876    pub fn severity(self) -> Severity {
2877        match self {
2878            Self::E014
2879            | Self::E022
2880            | Self::E023
2881            | Self::E026
2882            | Self::E030
2883            | Self::E031
2884            | Self::E033
2885            | Self::E034
2886            | Self::E035
2887            | Self::E038
2888            | Self::E043
2889            | Self::E054
2890            | Self::E063
2891            | Self::E092
2892            | Self::E095
2893            | Self::E106
2894            | Self::E110
2895            | Self::E131
2896            | Self::E132
2897            | Self::E151
2898            | Self::E152
2899            // Issue #1733 / §4.2: markup vocabulary checks are `Warning` by
2900            // default so they stay `[lints]`-configurable and
2901            // `@[allow(…)]`-suppressible (only `Warning`-base codes are —
2902            // see `crate::suppressions`). A host that wants a declared
2903            // vocabulary to be binding raises them with
2904            // `[lints] E164 = "deny"`. Issue #1780/#1997 adds `E173`
2905            // (missing required attribute) to the same family, same
2906            // rationale.
2907            | Self::E164
2908            | Self::E165
2909            | Self::E173
2910            // Issue #1848: a duplicate claiming pattern is dead code (the
2911            // earlier-declared handler always wins first), not a hard
2912            // error — `Warning`-tier so it stays `[lints]`-configurable and
2913            // `@[allow(E168)]`-suppressible, same posture as E164/E165.
2914            | Self::E168
2915            // Non-identical patterns that can overlap: same rationale as E168.
2916            | Self::E170
2917            // Issue #1835: a project may legitimately want a literal
2918            // `@`-led runtime tag (the issue's own caution) — `Warning`
2919            // plus `@[allow(E172)]` is the escape valve, same posture as
2920            // E132's malformed-directive-tag report.
2921            | Self::E172
2922            // Issue #2156: `E031`'s sibling for a divert-with-args call
2923            // site — same severity precedent as the call-expression arity
2924            // check it extends.
2925            | Self::E176
2926            // Issue #1865: matches E035's "name shadows a built-in
2927            // function" precedent — a declared STRUCT colliding with a
2928            // reserved builtin/tower type name is legal (the declaration
2929            // still compiles and constructs normally), just worth
2930            // surfacing so the author doesn't lose the annotation spelling
2931            // by accident. `resolve`'s own doc already calls this ordering
2932            // "deliberate", the same posture E035's shadow warning takes.
2933            | Self::E188
2934            // E190 (external-rename host-binding breakage, ruled 2026-08-24)
2935            // is Warning-tier: it is the always-unsafe verdict entry behind
2936            // the rename Force gate, synthesized by the IDE, never emitted
2937            // by compilation.
2938            | Self::E190
2939            // E192 (#3259): a directive that suppresses nothing is a
2940            // warning, not an error — the file still compiles, and the harm
2941            // is a suppression the author thinks is in force but is not.
2942            | Self::E192
2943            // E193 (#3354, RULED 2026-09-01 option C): a temp read that its
2944            // declaration does not dominate is a warning, not an error —
2945            // the story still plays (the runtime reads ink's
2946            // missing-variable default and warns), and the ruling asks
2947            // specifically for a `[lints]`-overridable warning so a project
2948            // that leans on the pattern deliberately can turn it down.
2949            | Self::E193
2950            // E195 (#3365): a choice with no text and no divert compiles
2951            // and plays — inklecate's own C# parser only ever *warns* on
2952            // this shape too (`InkParser_Choices.cs`), never rejects it —
2953            // so `Warning`-tier, `[lints]`-overridable like the rest of
2954            // this family.
2955            | Self::E195 => Severity::Warning,
2956            // Issue #1674: the one code whose *default* is the `Info`
2957            // advisory tier rather than `Warning` — RULED "off or info by
2958            // default" (a single-shot project should not be nagged) while
2959            // staying tier-able through `[lints]` like every other code
2960            // (`brink_analyzer::strict::effective_severity` widens its
2961            // overridable set past `Warning`-base codes to cover this one,
2962            // issue #1674).
2963            // E189 (issue #3050): `TODO:` author notes are advisory by
2964            // definition — the same `Info`-default posture, tierable via
2965            // `[lints]` like every other code.
2966            Self::E157 | Self::E189 => Severity::Info,
2967            // E194 (#3373, RULED 2026-09-01) falls through to the `Error`
2968            // default below like every other hard error: inklecate rejects
2969            // the program, so brink does too until a project opts in. What
2970            // makes it different from every other `Error`-default code is
2971            // [`Self::is_overridable`], not `severity` — see that method
2972            // and [`Self::is_compat_deny`].
2973            _ => Severity::Error,
2974        }
2975    }
2976
2977    /// Whether this code is a member of the **compat-deny** tier (#3373,
2978    /// RULED 2026-09-01): "inklecate rejects this; brink can run it; you
2979    /// must opt in." `docs/compiler-spec.md` "Compat-deny diagnostics" owns
2980    /// the tier's admission invariant — a code may join only when brink
2981    /// produces a *working* program with the code downgraded, so every
2982    /// member needs its own fixture proving that.
2983    ///
2984    /// This is the one predicate [`Self::is_overridable`] widens past its
2985    /// old "not `Error`-by-default" rule for: every compat-deny code keeps
2986    /// `severity() == Error` (matching ink's own hard rejection) while
2987    /// still being `[lints]`-overridable, all the way to `allow` — the
2988    /// ruling's explicit ask ("we should allow it to be turned off if the
2989    /// user wants, it's annoying").
2990    #[must_use]
2991    pub fn is_compat_deny(self) -> bool {
2992        matches!(self, Self::E194)
2993    }
2994
2995    /// Whether this code can only ever arise on the NATIVE (`.brink`)
2996    /// surface (#3169).
2997    ///
2998    /// Which surface can produce a diagnostic is a property of the
2999    /// diagnostic, not of any consumer — an ink-only project cannot produce
3000    /// these no matter who is asking, so the answer belongs here rather
3001    /// than in whichever tool happens to want it.
3002    ///
3003    /// **Everything not listed defaults to "both surfaces", deliberately.**
3004    /// No analysis pass declares the surface it can fire on, so this is
3005    /// read from what each diagnostic MEANS — and the two ways of being
3006    /// wrong are not symmetric. Claiming native-only wrongly hides a
3007    /// setting from an author who is actually seeing the diagnostic;
3008    /// claiming both wrongly shows one that cannot fire. The second is
3009    /// clutter, the first is a dead end, so a code earns its place here
3010    /// only when the compiler itself says so, and everything uncertain
3011    /// stays visible.
3012    ///
3013    /// Deliberately a predicate rather than a `Surface` set: nothing is
3014    /// ink-only today, and would be a real surprise if it were — the ink
3015    /// surface is the compatibility floor and native is a superset of it.
3016    /// If an ink-only code ever appears, this wants to become a set rather
3017    /// than gain a second predicate.
3018    #[must_use]
3019    pub fn is_native_only(self) -> bool {
3020        matches!(
3021            self,
3022            // "native is the only frontend that can spell markup"
3023            // — brink-analyzer/src/markup_check.rs
3024            Self::E164 | Self::E165 | Self::E173
3025            // These say it in their own titles.
3026            | Self::E131 // "native: `<-` (splice) used outside a choice point…"
3027            | Self::E132 // "A native file-level `@[was(…)]` rename record…"
3028            | Self::E151 // "A native `{? … }` choice's own body falls through…"
3029            | Self::E172 // "A native (`.brink`) tag whose text begins with `@`…"
3030        )
3031    }
3032
3033    /// The written explanation for this code, or `None` when nobody has
3034    /// written one yet (#3169).
3035    ///
3036    /// The prose lives in `docs/diagnostics/Exxx.md` under `## Explanation`.
3037    /// Every code has a file; only 31 of 189 have that section filled in, so
3038    /// `None` is the common answer and callers must render something else —
3039    /// [`Self::title`] is the intended fallback. Returning `None` rather than
3040    /// an empty string is deliberate: a caller that forgets to check gets a
3041    /// type error instead of a blank panel.
3042    #[must_use]
3043    pub fn explanation(self) -> Option<&'static str> {
3044        super::diagnostic_explanations::EXPLANATIONS
3045            .iter()
3046            .find(|(code, _)| *code == self)
3047            .map(|(_, text)| *text)
3048    }
3049
3050    /// Whether `[lints]` can override this code's severity (#1160).
3051    ///
3052    /// Everything except a hard error: `brink_analyzer::validate_lint_code`
3053    /// accepts any code whose default severity is not `Error`, and refuses
3054    /// the rest with a `ConfigWarning` rather than applying them. You cannot
3055    /// `allow` something that stops the compile.
3056    ///
3057    /// That deliberately INCLUDES the advisory tiers — `E189`, the ink
3058    /// `TODO:` note, is `Info` by default and is exactly the sort of thing
3059    /// an author wants to turn off (ruled 2026-08-27). An earlier version of
3060    /// this predicate said `Warning` only, which silently hid every
3061    /// `Info`-default code from the settings surface; the analyzer would
3062    /// have accepted them all along.
3063    ///
3064    /// It also INCLUDES the **compat-deny** tier (#3373, RULED 2026-09-01):
3065    /// [`Self::is_compat_deny`] members keep `severity() == Error` — brink
3066    /// rejects the program by default, exactly as inklecate does — but stay
3067    /// `[lints]`-overridable specifically because the ruling's admission
3068    /// invariant requires each member to produce a *working* program once
3069    /// downgraded. This is the one deliberate exception to "a hard error
3070    /// can never be downgraded"; every other `Error`-default code stays
3071    /// non-overridable.
3072    ///
3073    /// `agrees_with_the_analyzers_own_gate` in `brink-analyzer` pins this
3074    /// against `apply_lint_overrides` itself rather than against a restated
3075    /// rule — the earlier mistake survived a test that compared this
3076    /// predicate to its own implementation.
3077    #[must_use]
3078    pub fn is_overridable(self) -> bool {
3079        !matches!(self.severity(), Severity::Error) || self.is_compat_deny()
3080    }
3081
3082    /// Parse a diagnostic code from its string representation (e.g., `"E027"`).
3083    #[must_use]
3084    #[expect(
3085        clippy::too_many_lines,
3086        reason = "a flat one-arm-per-code table that necessarily grows with the diagnostic set"
3087    )]
3088    pub fn from_str_code(s: &str) -> Option<Self> {
3089        match s {
3090            "E001" => Some(Self::E001),
3091            "E002" => Some(Self::E002),
3092            "E003" => Some(Self::E003),
3093            "E004" => Some(Self::E004),
3094            "E005" => Some(Self::E005),
3095            "E006" => Some(Self::E006),
3096            "E007" => Some(Self::E007),
3097            "E008" => Some(Self::E008),
3098            "E009" => Some(Self::E009),
3099            "E010" => Some(Self::E010),
3100            "E011" => Some(Self::E011),
3101            "E012" => Some(Self::E012),
3102            "E013" => Some(Self::E013),
3103            "E014" => Some(Self::E014),
3104            "E015" => Some(Self::E015),
3105            "E016" => Some(Self::E016),
3106            "E017" => Some(Self::E017),
3107            "E018" => Some(Self::E018),
3108            "E019" => Some(Self::E019),
3109            "E020" => Some(Self::E020),
3110            "E021" => Some(Self::E021),
3111            "E022" => Some(Self::E022),
3112            "E023" => Some(Self::E023),
3113            "E024" => Some(Self::E024),
3114            "E025" => Some(Self::E025),
3115            "E026" => Some(Self::E026),
3116            "E027" => Some(Self::E027),
3117            "E028" => Some(Self::E028),
3118            "E029" => Some(Self::E029),
3119            "E030" => Some(Self::E030),
3120            "E031" => Some(Self::E031),
3121            "E032" => Some(Self::E032),
3122            "E033" => Some(Self::E033),
3123            "E034" => Some(Self::E034),
3124            "E035" => Some(Self::E035),
3125            "E036" => Some(Self::E036),
3126            "E037" => Some(Self::E037),
3127            "E038" => Some(Self::E038),
3128            "E039" => Some(Self::E039),
3129            "E040" => Some(Self::E040),
3130            "E041" => Some(Self::E041),
3131            "E042" => Some(Self::E042),
3132            "E043" => Some(Self::E043),
3133            "E044" => Some(Self::E044),
3134            "E045" => Some(Self::E045),
3135            "E046" => Some(Self::E046),
3136            "E047" => Some(Self::E047),
3137            "E048" => Some(Self::E048),
3138            "E049" => Some(Self::E049),
3139            "E050" => Some(Self::E050),
3140            "E051" => Some(Self::E051),
3141            "E052" => Some(Self::E052),
3142            "E053" => Some(Self::E053),
3143            "E054" => Some(Self::E054),
3144            "E055" => Some(Self::E055),
3145            "E056" => Some(Self::E056),
3146            "E057" => Some(Self::E057),
3147            "E058" => Some(Self::E058),
3148            "E059" => Some(Self::E059),
3149            "E060" => Some(Self::E060),
3150            "E061" => Some(Self::E061),
3151            "E062" => Some(Self::E062),
3152            "E063" => Some(Self::E063),
3153            "E064" => Some(Self::E064),
3154            "E065" => Some(Self::E065),
3155            "E066" => Some(Self::E066),
3156            "E067" => Some(Self::E067),
3157            "E068" => Some(Self::E068),
3158            "E069" => Some(Self::E069),
3159            "E070" => Some(Self::E070),
3160            "E071" => Some(Self::E071),
3161            "E072" => Some(Self::E072),
3162            "E073" => Some(Self::E073),
3163            "E074" => Some(Self::E074),
3164            "E075" => Some(Self::E075),
3165            "E076" => Some(Self::E076),
3166            "E077" => Some(Self::E077),
3167            "E078" => Some(Self::E078),
3168            "E079" => Some(Self::E079),
3169            "E080" => Some(Self::E080),
3170            "E081" => Some(Self::E081),
3171            "E082" => Some(Self::E082),
3172            "E083" => Some(Self::E083),
3173            "E084" => Some(Self::E084),
3174            "E085" => Some(Self::E085),
3175            "E086" => Some(Self::E086),
3176            "E087" => Some(Self::E087),
3177            "E088" => Some(Self::E088),
3178            "E089" => Some(Self::E089),
3179            "E090" => Some(Self::E090),
3180            "E091" => Some(Self::E091),
3181            "E092" => Some(Self::E092),
3182            "E093" => Some(Self::E093),
3183            "E094" => Some(Self::E094),
3184            "E095" => Some(Self::E095),
3185            "E096" => Some(Self::E096),
3186            "E097" => Some(Self::E097),
3187            "E098" => Some(Self::E098),
3188            "E099" => Some(Self::E099),
3189            "E100" => Some(Self::E100),
3190            "E101" => Some(Self::E101),
3191            "E102" => Some(Self::E102),
3192            "E103" => Some(Self::E103),
3193            "E104" => Some(Self::E104),
3194            "E105" => Some(Self::E105),
3195            "E106" => Some(Self::E106),
3196            "E107" => Some(Self::E107),
3197            "E108" => Some(Self::E108),
3198            "E109" => Some(Self::E109),
3199            "E110" => Some(Self::E110),
3200            "E111" => Some(Self::E111),
3201            "E112" => Some(Self::E112),
3202            "E113" => Some(Self::E113),
3203            "E114" => Some(Self::E114),
3204            "E115" => Some(Self::E115),
3205            "E116" => Some(Self::E116),
3206            "E117" => Some(Self::E117),
3207            "E118" => Some(Self::E118),
3208            "E119" => Some(Self::E119),
3209            "E120" => Some(Self::E120),
3210            "E121" => Some(Self::E121),
3211            "E122" => Some(Self::E122),
3212            "E123" => Some(Self::E123),
3213            "E124" => Some(Self::E124),
3214            "E125" => Some(Self::E125),
3215            "E126" => Some(Self::E126),
3216            "E127" => Some(Self::E127),
3217            "E128" => Some(Self::E128),
3218            "E129" => Some(Self::E129),
3219            "E130" => Some(Self::E130),
3220            "E131" => Some(Self::E131),
3221            "E132" => Some(Self::E132),
3222            "E133" => Some(Self::E133),
3223            "E134" => Some(Self::E134),
3224            "E135" => Some(Self::E135),
3225            "E136" => Some(Self::E136),
3226            "E137" => Some(Self::E137),
3227            "E138" => Some(Self::E138),
3228            "E139" => Some(Self::E139),
3229            "E140" => Some(Self::E140),
3230            "E141" => Some(Self::E141),
3231            "E142" => Some(Self::E142),
3232            "E143" => Some(Self::E143),
3233            "E144" => Some(Self::E144),
3234            "E145" => Some(Self::E145),
3235            "E146" => Some(Self::E146),
3236            "E147" => Some(Self::E147),
3237            "E148" => Some(Self::E148),
3238            "E149" => Some(Self::E149),
3239            "E150" => Some(Self::E150),
3240            "E151" => Some(Self::E151),
3241            "E152" => Some(Self::E152),
3242            "E153" => Some(Self::E153),
3243            "E154" => Some(Self::E154),
3244            "E155" => Some(Self::E155),
3245            "E156" => Some(Self::E156),
3246            "E157" => Some(Self::E157),
3247            "E158" => Some(Self::E158),
3248            "E159" => Some(Self::E159),
3249            "E160" => Some(Self::E160),
3250            "E161" => Some(Self::E161),
3251            "E162" => Some(Self::E162),
3252            "E163" => Some(Self::E163),
3253            "E164" => Some(Self::E164),
3254            "E165" => Some(Self::E165),
3255            "E166" => Some(Self::E166),
3256            "E167" => Some(Self::E167),
3257            "E168" => Some(Self::E168),
3258            "E169" => Some(Self::E169),
3259            "E170" => Some(Self::E170),
3260            "E171" => Some(Self::E171),
3261            "E172" => Some(Self::E172),
3262            "E173" => Some(Self::E173),
3263            "E174" => Some(Self::E174),
3264            "E175" => Some(Self::E175),
3265            "E176" => Some(Self::E176),
3266            "E178" => Some(Self::E178),
3267            "E179" => Some(Self::E179),
3268            "E180" => Some(Self::E180),
3269            "E181" => Some(Self::E181),
3270            "E182" => Some(Self::E182),
3271            "E183" => Some(Self::E183),
3272            "E184" => Some(Self::E184),
3273            "E185" => Some(Self::E185),
3274            "E186" => Some(Self::E186),
3275            "E187" => Some(Self::E187),
3276            "E188" => Some(Self::E188),
3277            "E189" => Some(Self::E189),
3278            "E190" => Some(Self::E190),
3279            "E191" => Some(Self::E191),
3280            "E192" => Some(Self::E192),
3281            "E193" => Some(Self::E193),
3282            "E194" => Some(Self::E194),
3283            "E195" => Some(Self::E195),
3284            _ => None,
3285        }
3286    }
3287}
3288
3289// ── Issue #3169: the registry the settings UI reads ────────────────
3290
3291#[cfg(test)]
3292mod registry_tests {
3293    use super::{DiagnosticCode, Severity};
3294
3295    /// Read the `## Explanation` section out of a doc file, the same way the
3296    /// generator does.
3297    fn doc_explanation(text: &str) -> String {
3298        let Some((_, rest)) = text.split_once("## Explanation\n") else {
3299            return String::new();
3300        };
3301        rest.split("\n## ").next().unwrap_or("").trim().to_owned()
3302    }
3303
3304    fn docs_dir() -> std::path::PathBuf {
3305        std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../docs/diagnostics")
3306    }
3307
3308    #[test]
3309    fn explanations_match_the_docs() {
3310        // The table is embedded (the docs live outside this crate's package
3311        // directory, and the wasm build has no filesystem), so nothing keeps
3312        // it in step with the markdown except this test. It runs in the
3313        // workspace, where `docs/` exists — which is also the only place the
3314        // drift can happen.
3315        let root = docs_dir();
3316        assert!(
3317            root.is_dir(),
3318            "expected diagnostics docs at {}",
3319            root.display()
3320        );
3321
3322        let mut wrong = Vec::new();
3323        for code in DiagnosticCode::ALL {
3324            let text = std::fs::read_to_string(root.join(format!("{}.md", code.as_str())))
3325                .unwrap_or_default();
3326            if doc_explanation(&text) != code.explanation().unwrap_or("") {
3327                wrong.push(code.as_str());
3328            }
3329        }
3330        assert!(
3331            wrong.is_empty(),
3332            "diagnostic_explanations.rs is out of step with docs/diagnostics for {wrong:?} \
3333             — regenerate it so the settings UI does not show stale prose"
3334        );
3335    }
3336
3337    #[test]
3338    fn every_code_has_an_explanation_file() {
3339        let root = docs_dir();
3340        let missing: Vec<_> = DiagnosticCode::ALL
3341            .iter()
3342            .filter(|c| !root.join(format!("{}.md", c.as_str())).is_file())
3343            .map(|c| c.as_str())
3344            .collect();
3345        assert!(
3346            missing.is_empty(),
3347            "codes with no explanation file: {missing:?}"
3348        );
3349    }
3350
3351    #[test]
3352    fn no_explanation_is_a_leftover_placeholder() {
3353        // The generated stubs carried bracketed placeholder prose. Embedding
3354        // one would put "[Detailed explanation of this diagnostic...]" in
3355        // front of an author, which is worse than showing nothing at all.
3356        for code in DiagnosticCode::ALL {
3357            if let Some(text) = code.explanation() {
3358                assert!(
3359                    !text.contains("[Detailed explanation"),
3360                    "{} still carries placeholder text",
3361                    code.as_str()
3362                );
3363                assert!(
3364                    !text.is_empty(),
3365                    "{} has an empty explanation",
3366                    code.as_str()
3367                );
3368            }
3369        }
3370    }
3371
3372    #[test]
3373    fn native_only_codes_say_so_in_their_own_text() {
3374        // The list is judgement, so it is held to its own standard: a code
3375        // is native-only only when the compiler itself says so. Every entry
3376        // must be justified by its title or its explanation naming the
3377        // native surface — if one is not, either the claim is wrong or the
3378        // title needs to state what the code is for.
3379        //
3380        // Markup is the documented exception: `markup_check.rs` says
3381        // "native is the only frontend that can spell markup", which is not
3382        // repeated in each code's own title.
3383        const MARKUP: &[DiagnosticCode] = &[
3384            DiagnosticCode::E164,
3385            DiagnosticCode::E165,
3386            DiagnosticCode::E173,
3387        ];
3388        for code in DiagnosticCode::ALL.iter().filter(|c| c.is_native_only()) {
3389            if MARKUP.contains(code) {
3390                continue;
3391            }
3392            let title = code.title().to_lowercase();
3393            let explanation = code.explanation().unwrap_or("").to_lowercase();
3394            assert!(
3395                title.contains("native")
3396                    || title.contains(".brink")
3397                    || explanation.contains("native")
3398                    || explanation.contains(".brink"),
3399                "{} is marked native-only but nothing in its own text says so",
3400                code.as_str()
3401            );
3402        }
3403    }
3404
3405    #[test]
3406    fn native_only_is_the_exception() {
3407        // The default is "both surfaces", and that is load-bearing: hiding
3408        // a code an author is actually seeing is worse than showing one
3409        // that cannot fire. If most codes became native-only, the default
3410        // has stopped being a default and this design wants revisiting
3411        // rather than the list growing.
3412        let native_only = DiagnosticCode::ALL
3413            .iter()
3414            .filter(|c| c.is_native_only())
3415            .count();
3416        assert!(
3417            native_only * 4 < DiagnosticCode::ALL.len(),
3418            "native-only is no longer an exception: {native_only} of {}",
3419            DiagnosticCode::ALL.len()
3420        );
3421    }
3422
3423    #[test]
3424    fn a_hard_error_is_never_overridable_and_an_advisory_always_is() {
3425        // Stated as the RULE, not as a copy of the implementation. The
3426        // version of this test that said `== matches!(severity, Warning)`
3427        // could not catch the predicate being wrong, because it asserted
3428        // the predicate against itself — and it did not catch it: every
3429        // `Info`-default code (`E189`, the ink TODO note) was hidden from
3430        // the settings surface for exactly that reason.
3431        for code in DiagnosticCode::ALL {
3432            match code.severity() {
3433                // #3373's compat-deny tier is the one deliberate exception:
3434                // `Error`-default AND overridable, exactly the members
3435                // `is_compat_deny` names. Every other `Error`-default code
3436                // must stay non-overridable.
3437                Severity::Error => assert_eq!(
3438                    code.is_overridable(),
3439                    code.is_compat_deny(),
3440                    "{}: an Error-default code is overridable only when it is a \
3441                     compat-deny tier member",
3442                    code.as_str()
3443                ),
3444                Severity::Warning | Severity::Info | Severity::Hint => {
3445                    assert!(code.is_overridable(), "{}", code.as_str());
3446                }
3447            }
3448        }
3449        assert!(
3450            DiagnosticCode::E189.is_overridable(),
3451            "the ink TODO: note must be configurable (ruled 2026-08-27)"
3452        );
3453        assert!(
3454            DiagnosticCode::E194.is_overridable() && DiagnosticCode::E194.is_compat_deny(),
3455            "the compat-deny tier's first member must be Error-default yet overridable \
3456             (ruled 2026-09-01, #3373)"
3457        );
3458        let overridable = DiagnosticCode::ALL
3459            .iter()
3460            .filter(|c| c.is_overridable())
3461            .count();
3462        assert!(
3463            overridable > 0 && overridable < DiagnosticCode::ALL.len(),
3464            "a flag that is true (or false) for every code would be pointless: \
3465             {overridable} of {}",
3466            DiagnosticCode::ALL.len()
3467        );
3468    }
3469
3470    #[test]
3471    fn compat_deny_tier_is_error_default_and_overridable() {
3472        // The tier's own invariant, stated directly against every current
3473        // member rather than folded into the general hard-error test above
3474        // — so a future member that gets `is_compat_deny` right but
3475        // `severity` wrong (or vice versa) fails here with a name attached.
3476        for code in DiagnosticCode::ALL.iter().filter(|c| c.is_compat_deny()) {
3477            assert_eq!(
3478                code.severity(),
3479                Severity::Error,
3480                "{}: a compat-deny member's default must match inklecate's rejection",
3481                code.as_str()
3482            );
3483            assert!(
3484                code.is_overridable(),
3485                "{}: a compat-deny member must be [lints]-overridable",
3486                code.as_str()
3487            );
3488        }
3489    }
3490}