Diagnostics & rules reference
English(this file) · 日本語
karasu reports problems with two layers of vocabulary:
- A rule (規則) is a concept — what the language allows and forbids (“an edge originates within its enclosing block”). Rules are how authors and this spec talk about the constraint.
- A diagnostic (診断) is a mechanism — a specific, named check that fires
when one concrete violation of a rule is detected (
edge-source-mismatch).
One rule is often enforced by several diagnostics, and a single diagnostic belongs to exactly one rule. This document is the catalog that maps the two.
How to read this catalog
Section titled “How to read this catalog”- Diagnostic codes are stable API. The
codestring (e.g.edge-source-mismatch) is consumed by the LSP, the app, and downstream tooling. Codes are not renamed to match a rule’s wording; the rule name is conceptual, the code is the contract. When a rule reads more naturally under a different name than its diagnostic, that is expected — they sit at different altitudes. - Every diagnostic code lives under exactly one rule family below, and every
code defined in core (
DiagnosticParamsByCode,WarningKind) appears here. This completeness is enforced by a meta-test (see Catalog completeness), so a new code cannot ship without a catalog entry. - The
fires whencolumn states the concrete trigger. Severities are listed as emitted by core.
Registers and severities
Section titled “Registers and severities”A diagnostic has a severity: error, warning, or info.
error— the model is malformed; the offending construct is rejected.warning— a real defect the author should fix (a dangling reference, a conflicting style).info— a fact, not a defect. karasu surfaces something true about the model that an external school of thought may call a smell (a shared database, a dispersed domain), without asserting it is wrong. This is the fact vs. style register split — see TPL-1386.
karasu also follows warn-don’t-error for unresolved references (spec §S6): an unresolved relation is dropped while the node it points from is preserved, and the drop is reported as a warning rather than failing the whole render.
Source locations
Section titled “Source locations”A diagnostic may carry a source location (loc): a start and an end position,
and the document they index into.
- Positions are 1-based.
lineandcolumncount from 1, as an author sees them in an editor. A surface that needs another base converts once, at its own boundary (the LSP’s 0-based ranges); a tool that prints a location prints the numbers as they are. filenames the document a position indexes into. A project spans files. A diagnostic from an imported file, or a verdict decided on the merged model (the cross-file multiplicity checks under Identifier uniqueness, the reference checks under Cross-reference resolution), anchors on whichever file declared the construct, which is not necessarily the entry. Every diagnostic produced while resolving a project setsfile, as an absolute path, whenever it setsloc. A.krs.stylediagnostic names the sheet.- An absent
filemeans the consumer’s own document. Only a single-document context produces one: the LSP parses each open document by itself,karasu lint-stylereads one sheet, andcompiletakes its model as source text. When such a compile is also handed a style sheet as text, it has a path for neither document, so the sheet’s parse diagnostics carry nolocthere rather than a position that would read as the model’s. - A diagnostic without
locnames no position. One that concerns a missing file carries the path in its message instead (file-not-found,style-file-not-found). Merge-time facts that name ids rather than declarations (infra-redeclared-across-files,system-property-conflict, and the like) carry neither.
Each surface prints a location as follows.
| Surface | Location shown |
|---|---|
CLI (karasu render, and the commands that share its error report) |
<file>:<line>:<column>. <file> is the entry, in the spelling the user typed, when the position has no file or its file is the entry (compared as canonical paths); otherwise the file, relative to the working directory. |
CLI (karasu diff) |
<line>:<column>, with no file: the command compiles two inputs. |
| App preview banner and warning panel | The UI locale’s line label (Line <line> in English, <line> 行目 in Japanese) for a position in the open document or with no file; <path>:<line> for any other file. The path is relative to the project root, or to the entry’s directory in a mode without a project; a snapshot being compared is named by the project path it was taken of. |
| LSP | The document’s own range, 0-based. The LSP is single-document, so no file arises. |
Related TPLs: TPL-2715 (a position is an address only together with the document it indexes, so the parse attaches the file where ranges are built and every surface reads it from there).
Rule families
Section titled “Rule families”Declaration, edge placement & structure
Section titled “Declaration, edge placement & structure”Where a construct may be declared, and what an edge’s origin may be. An edge
declared inside a service / domain block originates from that block’s id;
infra blocks and legend have fixed placement; sync edges must not form a
cycle.
| Code | Severity | Fires when |
|---|---|---|
edge-source-mismatch |
error | An explicit edge source inside a service / domain / entity block does not equal the enclosing block id (the edge origin scope rule; for an entity this enforces the relation direction — origin = the reference-holding entity). |
edge-endpoint-not-at-scope |
warning | An edge endpoint the declaring scope cannot reach (the edge endpoint scope rule). A bare endpoint must be a peer of the block the edge is declared in — e.g. A -> B written at system scope where A and B are domains inside a service; author it inside its source block, or qualify a cross-domain entity target. A qualified endpoint must be anchored at a top-level root — the whole path from a system down to the target — so the message asks instead for the anchored spelling and names where the target is declared. A relation inside an entity block is exempt: the entity view resolves those against its own pool and draws the foreign entity as a ghost. Skipped for ids that resolve nowhere (unresolved-edge-endpoint for a bare id, cross-system-ref-unresolved for a dotted one); a bare domain → domain edge is exempt because it is derived up to an implicit service edge. |
edge-target-ambiguous |
warning | A qualified edge endpoint resolves, within its scope’s reach, to two or more nodes that are not uniform in (kind, depth) — the shared #2088 discriminator, so a uniform multi-match stays silent as intentional broadcast. The message lists each candidate’s full path so the author can qualify further. Bare endpoints draw no ambiguity verdict: they keep the peer binding, where a multi-match is pre-existing broadcast rather than a new question. |
ambiguous-edge-base |
warning | Multiple edges share the same from → to base with no distinguishing author id. |
duplicate-edge-label |
error | An edge writes its label twice: positionally (A -> B "calls") and as a label property inside its property block. Neither form silently wins; keep one. |
service-outside-system |
warning | A service is declared outside any system. |
infra-not-in-context |
error | An infra block (database / queue / storage) is not a direct child of system. |
boundary-not-in-context |
error | A boundary block is declared inside a node kind that draws no canvas of its own (entity, resource, user, client, or an infra leaf), so it would have no peers to frame. |
entity-not-in-domain |
error | An entity is declared somewhere other than as a child of a domain. |
node-not-in-context |
warning | A logical node is nested inside a kind whose May contain column does not list it (e.g. a usecase inside a client). The node is kept and still renders; it simply carries no defined meaning there. Scheduled to become an error in .krs language v2.0 (see roadmap §Syntax 2.0). |
legend-not-top-level |
error | A legend block is declared somewhere other than the top level. |
top-level-declaration |
error | A user or an edge is declared at the top level instead of inside a system block. |
system-property-conflict |
warning | A system label / description conflicts between merged imports. |
cyclic-dependency |
warning | Sync edges (->) form a dependency cycle. |
Related TPLs: TPL-2542 (once one meaning has two spellings, the same PR pins that both land on one AST and that writing it in both forms at once is diagnosed;
duplicate-edge-labelis that diagnostic).
Identifier uniqueness
Section titled “Identifier uniqueness”Ids must be unique within their declaring scope; ownership assigns at most one primary owner.
| Code | Severity | Fires when |
|---|---|---|
duplicate-edge-id |
error | An author-supplied edge id collides with another edge’s id. |
duplicate-node-id-parent |
error | A node id is duplicated within its immediate parent (covers a usecase and entity sharing an id under one domain). |
entity-anchor-collision |
warning | An id is claimed by more than one target in the entity deep-link namespace ({all domain ids} ∪ {all entity ids}) — an entity id duplicated across domains, or an entity id equal to a domain id. Deep-links resolve ambiguously; the model still renders. |
duplicate-node-in-system |
error | A node id is duplicated within a system. |
duplicate-node-in-deploy |
error | A node id is duplicated within a deploy block. |
duplicate-team-id |
error | A team id is duplicated. |
duplicate-team-in-organization |
error | A team id is duplicated within an organization. |
duplicate-resource-operation |
warning | A CRUD verb is listed more than once on one resource. |
duplicate-crud-decoration-target |
warning | A CRUD decoration targets the same operation more than once. |
duplicate-owner-assignment |
info | A node is assigned as owned by more than one team (a fact; see ADR-1566). |
duplicate-boundary-assignment |
info | A node belongs to more than one boundary (a fact; membership is 1:N — see syntax.md for how a view resolves it). |
boundary-membership-not-drawn |
info | Under Group by: boundary, a boundary’s frame could not be widened to enclose one of its members without covering a non-member, so the membership is marked on the card as a ◇ tab instead. States what this drawing did, unlike duplicate-boundary-assignment, which states a fact about the model; it therefore carries no source location and appears only on that axis. |
duplicate-boundary-id |
error | Two boundary blocks in the same enclosing node declare the same id, so the second cannot be addressed. Top-level blocks are unaffected. |
duplicate-facet-id |
error | Two facet blocks declare the same id, so a facets reference cannot say whose metadata it means. Decided on the merged model, so a duplicate split across two files is caught; the first declaration is the one references resolve to. |
positional-label-removed |
error | A boundary, facet, organization, team or member id is followed by a positional label string. ADR-19 made label a property, and the positional form was never in the spec: boundary / facet lost it outright as experimental constructs (#2133), the rest after a deprecation window (#2208). Recovery differs — boundary / facet discard the string, while organization / team / member keep it as the label so the org chart still reads while the file is fixed. |
node-id-multiple-locations |
warning | Two or more logical-layer nodes (service / domain / client) declare the same id at different paths. The warning depends neither on declaration order nor on how the model is split across files: the parse path decides it on the file (#2550) and the project path re-decides it on the merged model (#2596), so moving a block to another file no longer silences it. Being decided on the merged model is also its limit — the verdict covers the declarations that reach the model, so a collision confined to the part of a file a named import leaves behind is not reported for the project (those nodes are not drawn or navigable there; the editor still reports them per file). One declaration is counted once however many merge paths carry it: a file imported both wholesale and by name, or a service mounted into two systems by their edges, is not a collision. Same names across the logical/physical boundary and within the physical layer (database / queue / storage and their sub-resources) are tolerated silently, since physical references are dot-qualified (resource OrderDB.users). Domain-vs-domain multiplicity is excluded (domain-dispersal’s (info) business), and a repeated id under one parent defers to duplicate-node-id-parent (error) — the top-level same-id case has no parent scope to report it, so a parked service X alongside a client X is reported here instead. nodePathIndex keeps a single winner per id: the @migration_target-priority candidate (infra leaves inherit their block’s annotations), ties keeping the first declaration in traversal order (systems, then top-level domains / services / clients, then top-level infra; across files, the merged model’s order, which is the entry file’s own declarations followed by the imported ones). The entry is therefore file-layout independent whenever the candidates differ in priority, which is the migration-coexistence case it exists for; an equal-priority tie follows the import graph, so two unannotated declarations swap winners if they swap files. Each losing logical declaration carries one warning at its own location. The multi-system root view draws every declaration (#2917): each card keeps the bare data-node-id and adds data-node-path (Shop.Api), so a click drills into and describes the card’s own node, while a hand-over that carries only the bare id (highlight, outline, permalink) lands on the index winner or the first matching element. |
Related TPLs: TPL-1583 (1:1 index winner rules stay consistent across indices;
node-id-multiple-locations’ winner rule is one of them), TPL-2221 (a duplicate split across two files is a fact neither file can see, so the verdict and the index it explains are derived once, on the merged model), TPL-2920 (a canvas that draws the same element id twice names each consumer’s landing, and hands a path to the consumers that need one node).
Cross-reference resolution (warn-don’t-error, §S6)
Section titled “Cross-reference resolution (warn-don’t-error, §S6)”A referenced id must resolve to a declared node. When it does not, the source node is preserved and the unresolved relation is reported (it is not a fatal error) — see syntax spec §S6.
| Code | Severity | Fires when |
|---|---|---|
owns-target-not-found |
warning | A team owns an id that names no node in the merged model — any kind, any depth, so a declared user or entity is found here and refused by kind in invalid-owns instead. A capability is a property rather than a node, so it resolves to nothing and lands here. Derived from the merged tree, so the verdict depends neither on the import form nor on where the block was declared. Import-coupled: a document that still has imports to resolve does not decide it at all (the LSP’s single-document context stays silent; the App / CLI decide it on the merged model). |
invalid-owns |
warning | An owns target resolves to a node whose kind cannot be owned; the message names that kind. An id that resolves to nothing is not this diagnostic’s business — owns-target-not-found reports that instead, so one mistake draws exactly one of the two. Import-coupled as a consequence: in a single-document context a cross-file target resolves to nothing, so nothing is reported. The ownable kinds are service / domain / client / infra blocks at any depth (OWNS_TARGET_KINDS, which only this check reads — existence asks nothing about kinds); an infra leaf (table / queue-item / bucket) and a capability exist but are not ownership units, so this is the check that rejects them. Note the system-view card draws a team chip only for the logical kinds; an owned infra block still reads under Group by: team (frames resolve by id) and in the org view. |
contains-target-not-found |
warning | Import-coupled: a document that still has imports to resolve does not decide it at all — the member may be declared in an imported file, and a cross-file system reopen can add the very child a scoped contains names. Otherwise: a boundary contains a node that does not exist — for a top-level block, anywhere in the merged system hierarchy (existence is checked after cross-file merge, not per file); for a scoped block, among the declaring node’s direct children (matched by the suffix rule of the node reference path notation). |
owns-target-ambiguous |
warning | An owns reference (suffix path, bare id included) resolves to two or more nodes that are not uniform in (kind, depth). A uniform multi-match is intentional broadcast (migration coexistence, multi-tenant) and stays silent. The message lists each candidate’s full path so the author can qualify with a longer path. Import-coupled like owns-target-not-found: decided only on the merged model. |
contains-target-ambiguous |
warning | The contains counterpart of owns-target-ambiguous, for top-level boundary blocks: a reference matching nodes of mixed kind or depth is reported with the candidate full paths; a uniform multi-match broadcasts silently. Structurally unreachable in the scoped form — members resolve among sibling-unique direct children. Import-coupled: decided only on the merged model. |
realizes-target-ambiguous |
warning | A realizes reference (suffix path, bare id included) resolves to two or more realizable nodes (service / domain / client / infra blocks) not uniform in (kind, depth); candidates are listed as full paths. Uniform multi-matches stay silent. Existence stays with unresolved-realizes. Import-coupled: decided only on the merged model. |
import-target-ambiguous |
warning | A multi-segment import { … } entry, resolved by the suffix rule, matches two or more nodes not uniform in (kind, depth). Every match is still imported — bare-id imports have always broadcast — and the warning lists the candidate full paths so the author can qualify. |
facet-not-declared |
warning | A facets reference names no declared facet block (existence is checked on the merged model, so a declaration in an imported file counts). Unlike the near-miss annotation hint, the declared set makes this check complete: a typo between two author-defined names is caught too. Reported on the element that carries the reference — the declaring node or edge, not the facets line itself — so the range is the same one every other element-level diagnostic uses. It names a node by its id and an edge by its canonical base form (A-->B), an edge having no id of its own; that is the string edge#<id> addresses the same edge with. |
import-id-not-found |
error | A named import id path fails to resolve. |
import-path-not-found |
error | An import path fails to resolve at some segment. |
unresolved-edge-endpoint |
warning | An edge endpoint id is not found anywhere in the merged model. |
unresolved-handles |
warning | A handles domain is not reachable through the one-hop expose rule. The reference is a node reference path resolved against the domains that are direct children of a system-level child, and the warning anchors on the reference itself, so one entry of handles A, B can fail while the other resolves. handles has no *-target-ambiguous twin, unlike owns / contains / realizes: every candidate in that pool is a domain at the same depth, so a multi-match is uniform by construction (the multi-tenant broadcast pattern) and a longer path is not a remedy the author could apply. A reference naming a domain outside the pool is reported here rather than as an ambiguity. |
unresolved-realizes |
warning | A deploy node realizes a target absent from the logical layer. |
legend-ref-unresolved |
warning | A legend ref matches no style rule and no node. |
cross-system-ref-unresolved |
warning | A cross-system edge (Sys.Svc) target is not found. |
cross-system-ref-implicit-external |
warning | A cross-system edge crosses into a system not tagged [external]. |
delivers-target-not-client |
warning | A delivers target is not a client node. |
unresolved-resource-ref |
warning | A dot-notation resource <Infra>.<Leaf> names an infra block the merged model does not declare, or a leaf that block does not declare. The message says which half is missing, because the repairs differ: a missing block usually means the whole database / queue / storage declaration was lost, a missing leaf that only the sub-resource is absent. [external] resources are exempt, matching unassigned-resource. Import-coupled — shared infra is canonically declared in a file each slice imports (§S4.5), so a single document does not decide it. |
unresolved-table-ref |
warning | An entity’s table <Infra>.<Leaf> mapping names an undeclared block or leaf; same missing split, same [external] exemption, same import-coupling as unresolved-resource-ref. An entity with no table mapping is a legitimate state (forward design, read-model projection, KV-backed aggregate) and is never reported here — karasu coverage counts it instead. |
Infra single-declaration & fan-in
Section titled “Infra single-declaration & fan-in”Infra nodes are declared once; a store referenced by several services is a fact worth surfacing.
| Code | Severity | Fires when |
|---|---|---|
infra-redeclared-across-files |
info | The same database / queue / storage id is declared in more than one merged file. Reported whichever import form brought the second declaration in: a whole-file import, or a named entry whose path roots at the block (import { UserDB.users }). Two entries naming one declaration are one import, not a reopen. |
infra-leaf-redeclared-silently |
info | A table / queue-item / bucket leaf is redeclared within its parent infra. |
shared-infra-fan-in |
info | Two or more services depend on the same store within one system (a fact, not a defect). |
cross-domain-store-access |
info | A usecase reads/writes an infra leaf owned by another domain, in one system (a boundary-crossing fact, not a defect). Ownership is derived from entity mappings; keyed at leaf granularity; [external] and role-tagged ([index] / [cache] / [analytics]) stores excluded. Orthogonal to shared-infra-fan-in. |
CRUD decoration grammar
Section titled “CRUD decoration grammar”The grammar of operation / CRUD decoration on resources.
| Code | Severity | Fires when |
|---|---|---|
invalid-crud-decoration |
error | A CRUD decoration uses an unrecognised verb / letter. |
empty-crud-decoration |
warning | A verb: decoration has an empty right-hand side. |
unknown-resource-operation |
warning | A resource operation verb is not one of create / read / update / delete. |
Assignment & cohesion
Section titled “Assignment & cohesion”Whether structural nodes are assigned to an owner / parent, and cohesion facts about how domains and deploy targets are wired.
| Code | Severity | Fires when |
|---|---|---|
unassigned-service |
warning | A service sits at top level with no team assignment. |
unassigned-domain |
warning | A domain is not assigned to a service — it sits at top level, or directly inside a system. Both placements express the same modelling state, so both fire (#2184); only the top-level form is additionally wrapped in the (Unassigned) pseudo-system. |
unassigned-usecase |
warning | A usecase is a direct child of a service with no domain parent. |
unassigned-client |
warning | A client sits at top level with no team assignment. |
unassigned-database |
warning | A database sits at top level with no team assignment. |
unassigned-queue |
warning | A queue sits at top level with no team assignment. |
unassigned-storage |
warning | A storage sits at top level with no team assignment. |
unassigned-resource |
warning | A bare resource <id> resolves to no store: not dot-notation, not [external], and not a unique entity. Model-wide (resolver, not parser), so it is promoted away — with zero edits — once a matching entity is declared. An ambiguous bare id (>1 matching entity) stays unresolved; the collision is reported by entity-anchor-collision. |
domain-dispersal |
info | One domain id appears under multiple services in scope (a fact). |
missing-realizes |
info | A deploy node lacks a realizes property. |
missing-runtime |
info | A deploy node lacks a runtime property. |
Annotation & lifecycle
Section titled “Annotation & lifecycle”Annotation parameters, removed / deprecated properties, and the v1.x deprecation of non-builtin tag / annotation vocabulary (syntax v2.0 accepts tool vocabulary only — see tags-annotations.md).
| Code | Severity | Fires when |
|---|---|---|
annotation-param-unsupported |
warning | An annotation parameter key is not recognised for that annotation. |
annotation-param-value-unreadable |
warning | A recognised annotation parameter’s value is not one string literal or bare word (until: 2026-12-31, from: system, from: Shop.Legacy). Nothing is recorded. Rendering is unaffected; karasu fmt refuses to rewrite the file, because printing the AST would drop the value. |
annotation-param-conflict |
warning | One element gives the same annotation parameter two different values, across repeated annotations or inside one. The first value is kept, and karasu fmt refuses to rewrite the file rather than print the first over the second. |
duplicate-annotation |
warning | The same annotation is written more than once on one element. The repeat has no effect. |
annotation-possible-typo |
info | An annotation name is a near-match to a builtin (typo hint). |
tag-not-builtin |
warning | A tag name is outside the tool vocabulary (builtin + system-assigned tags). Deprecated in v1.x; no suppression condition. |
tag-not-applicable |
warning | A builtin tag is written on a node kind outside its applicability (e.g. service Api [index] — [index] applies to database). The tag has no effect there. Never fires together with tag-not-builtin: a non-builtin name has no applicability to violate. |
annotation-not-builtin |
warning | An annotation name is outside the builtin set. Deprecated in v1.x; no suppression condition. |
style-tag-selector-not-builtin |
warning | A .krs.style selector targets a tag name outside the tool vocabulary (e.g. [pci] { … }). Deprecated in v1.x — the rule still matches; syntax v2.0 matches tool vocabulary only. Migrate to a facet selector ([facets=<id>]). Fires per selector, independently of the model-side tag-not-builtin: the two name two edits, and warning once would leave the other site unfound. Never fires for the builtin theme or injected system sheets. |
style-annotation-selector-not-builtin |
warning | A .krs.style selector targets an annotation name outside the builtin set (e.g. @canary { … }). Same contract as style-tag-selector-not-builtin. |
team-property-removed |
error | The removed team property is used (see ADR-1564). |
Imports & files
Section titled “Imports & files”Resolving import declarations and style imports against the filesystem.
| Code | Severity | Fires when |
|---|---|---|
circular-import |
warning | A node import forms a cycle. |
circular-style-import |
warning | A style import forms a cycle. |
file-not-found |
error | An imported file does not exist. |
directory-not-found |
error | An imported directory does not exist. |
style-file-not-found |
warning | An imported style file does not exist. |
Style validation
Section titled “Style validation”Validating .krs.style property names and values.
| Code | Severity | Fires when |
|---|---|---|
style-unknown-property |
warning | A style property name is not recognised. |
style-unknown-icon |
warning | A shape: url("<name>") names no registered icon, so the node is drawn as box. Fires on every surface, because the built-in icon set is registered by core itself (TPL-2802). |
style-invalid-enum-value |
error | A style value is not in the allowed enum. |
style-invalid-hex-color |
error | A style hex color is malformed. |
style-invalid-length-unit |
error | A style length uses a disallowed unit. |
style-missing-length-unit |
error | A style length is missing its required unit. |
style-out-of-range |
error | A style numeric value is outside its min / max bounds. |
style-token-type-mismatch |
error | A style token does not match the expected type. |
expected-style-property-name |
error | The style parser expected a property name. |
expected-semicolon-between-properties |
error | The style parser expected a ; between properties. |
unknown-edge-selector-attribute |
error | A selector uses a bracket attribute other than from / to / facets (e.g. edge[source=X]). The code name predates facets, which is accepted on node selectors too, not only on edge. |
style-conflict |
warning | A selector is defined in more than one user style sheet. |
style-column-invalid-value |
warning | A style column value is not left / center / right. |
style-column-ignored-non-system-view |
warning | A column hint is applied to a deploy / org view (ignored). |
style-grid-columns-invalid-value |
warning | A style grid-columns value is not a positive integer (the hint is dropped; layout auto-balances). |
Related TPLs: TPL-2802 (
style-unknown-iconreads the shape registry, whose built-in contents core fills itself, so the verdict is the same on every surface), TPL-1503 (a value the parser accepts has an effect or a diagnostic, never silence).
Client & capability
Section titled “Client & capability”The client sub-language: storage kinds and capabilities.
| Code | Severity | Fires when |
|---|---|---|
client-resource-invalid-kind |
error | A client resource storage kind is not one of the reserved values. |
client-capability-duplicate |
warning | A client declares the same capability name twice. |
Syntactic & parse-level errors
Section titled “Syntactic & parse-level errors”Low-level parser errors raised when tokens do not form a valid construct. These are mechanism-level by nature; the “rule” is the grammar itself.
| Code | Severity | Fires when |
|---|---|---|
token-type-mismatch |
error | A token does not match the type the parser expected. |
unexpected-token-root |
error | An unexpected token appears at the root level. |
unexpected-token-in-block |
error | An unexpected token appears inside a block. |
expected-brace-or-string |
error | The parser expected a { or a string literal. |
expected-identifier |
error | The parser expected an identifier. |
expected-string-after |
error | The parser expected a string after a property keyword. |
expected-id-or-string |
error | The parser expected an id or a string. |
expected-node-id |
error | The parser expected a node id. |
expected-property-value |
error | The parser expected a property value. |
expected-id-after |
error | The parser expected an id after a property keyword. |
invalid-node-kind |
error | A node kind keyword is not recognised. |
property-not-for-node-kind |
error | A property is not valid for the node kind it appears on. |
link-url-scheme-not-allowed |
warning | A link URL scheme is not in the allowed set (http / https / mailto). |
Application-level fallbacks
Section titled “Application-level fallbacks”Synthetic codes the app uses when wrapping a thrown compile / parse error.
| Code | Severity | Fires when |
|---|---|---|
app-project-compile-error |
error | compile() threw and the app reports a generic compile failure. |
app-org-parse-error |
error | Org parsing threw and the app reports a generic parse failure. |
generic-text |
error | A pre-built fallback message string with no structured params. |
Catalog completeness
Section titled “Catalog completeness”Every member of DiagnosticParamsByCode and WarningKind (in
packages/core/src/types) must appear as a code in this document. A meta-test
(packages/core/src/types/diagnostics-catalog.test.ts) asserts this in both
directions, so the catalog cannot silently drift from the emitted codes. The
discipline behind it is recorded as
TPL-1623.
Related TPLs: TPL-1623 (catalog ↔ code completeness), TPL-1386 (fact vs style register), TPL-2171 (spec-promised diagnostics are implemented), TPL-1296 (spec ↔ source-of-truth sync).
© 2026 Hiroki Kondo · Licensed underApache-2.0