ADR019 DeclaredDimensions
Title: Declared dimensions
- Status: Accepted
- Decided: D2026-08
- Recorded: D2026-09-07
- Introduces: SP010
DimensionNotFoundError, SP021DimensionAlreadyDeclaredInChainError - Affects:
Concept,Dimension,Parser
Context
The word in front of a concept names its dimension. food Bread says Bread is
a food.
The compiler used to accept any word there. If nothing named food had been
declared, it minted a dimension id from the word and the scope it was written
in and carried on. Nothing failed, so the mistake was invisible — and the
consequence was worse than an error would have been. Two members of what the
author thought was one kind, written in different folders, got two different
minted ids. They looked like they belonged to different kinds, because as far
as the manifest was concerned they did.
That is the failure mode a descriptive language can least afford. The universe compiled, the field guide rendered, and the graph was quietly wrong in a way that only showed up as a kind page with members missing from it.
A second problem sat next to the first. Kind lookup walks outward: a concept's
dimension is found by checking the enclosing folder, then its parent, and so on
to the universe. But a kind word cannot be qualified — there is no way to write
Outer.food Bread to say which food you meant. So if a dimension name were
declared again further down its own scope chain, every member below that point
would silently change kind, and the author would have no syntax available to
say otherwise.
Decision
A kind word must name a dimension that was actually declared, in the concept's
scope or an enclosing one. Nothing is minted. A word that names no dimension is
SP010, and the message says how to fix it in both directions: declare the
dimension, or use concept if the idea has no dimension yet.
concept is the explicit escape hatch, not a special case — resolveConceptDimension
treats the literal word concept as "no dimension" and returns null.
A dimension may not be declared again inside its own scope chain. The inner declaration is SP021, reported at the declaration rather than at any use, because the declaration is the thing that is wrong and the uses all look fine.
Consequences
Authoring. A typo in a kind word is now an error instead of a new kind. The
cost is that a universe sketched with kind words before their dimensions exist
does not compile until the dimensions are declared, or until those concepts are
written as concept.
What this fixed that was not visible. Members of one kind sitting in different folders now share one dimension id. The bug this replaced did not produce a diagnostic; it produced a graph that disagreed with the author. That is the argument for refusing to mint anything: a descriptive language's whole value is that the description is true, and inventing a declaration to keep a compile alive trades that away for convenience.
Why SP021 fires at the declaration. Because there is nothing to report at
the use. Every food Bread below a shadowing redeclaration is well-formed and
resolves; it just resolves to a different kind than the one the author has in
mind. With no way to qualify a kind word, there is also no fix available at the
use site. The only actionable location is the inner declaration.
Cost. Sibling branches may still each declare cleanliness, because they
are not in one another's chain and neither is fallback for the other. That is
deliberate, and it means SP021 is a narrower rule than "dimension names are
unique," which some authors will expect it to be.
What this does not do. It does not change where a concept sits. Where a
thing lives and what kind it is remain two independent trees: food Bread
inside a place is still a universe-level food.
Alternatives considered
Keep minting, and warn. Report the implicit dimension as a warning and carry on. Rejected. A warning that produces a wrong graph is worse than an error that produces none, because the graph is the artifact everything downstream trusts. The manifest would still contain two ids for one kind.
Mint one dimension per word, universe-wide. If food is undeclared, invent
a single food at universe scope rather than one per writing scope. This fixes
the split-kind symptom without requiring declarations. Rejected: it makes every
typo a real kind, and it means the set of dimensions in a universe is whatever
words happen to appear, which is not a vocabulary.
Make concept mean "infer the dimension." Rejected. concept earns its
keep by meaning exactly one thing — this idea has no kind yet — and a reader
needs that to be unambiguous.
Report SP021 at the use rather than the declaration. Rejected for the reason above: there is no qualified form, so the use site has no available fix. This is the same reasoning 008 later applied to relationship edge names, which is why SP025 also reports the inner declaration.
Universe-wide unique dimension names. Rejected. Two unrelated branches each
wanting status is not shadowing, and forbidding it would make large universes
fight over common words.
Evidence
resolveConceptDimension in packages/prose-parser/src/validator.js walks
parents from the innermost scope outward and returns null only for the
literal word concept; if the walk finds nothing, SP010 is raised rather than
an id being constructed. The absence of a minting branch is the change.
validateDimensionChain in the same file compares each scope's dimensions
against its ancestors' and raises SP021 with the inner declaration's span and
the name of the enclosing scope that already holds the word.
Worth noting for a future reader, because it is not obvious and it is not recorded anywhere yet: prose now has three name-resolution axes and they do not behave the same way. A kind word walks outward (this record). A relationship edge name walks outward and then into the target's scopes (008). A concept name does not walk outward at all (002) — that walk was considered and refused. Two of the three walk, one does not, and each of SP021 and SP025 exists to contain a hazard the walking creates. That asymmetry is deliberate in each case but has never been argued as a whole.