errors.ts
/ CATEGORIES.md lifecycle, v0.3 session 1): §3 generates from the
value-type enum in /packages/lang/src/schema/valuetypes.ts and §6 from the
keyword registries in /packages/lang/src/parser/keywords.ts — edit the code and
run pnpm gen:taxonomy; CI pins each marked region to its renderer.
Per-category datum inventories stay in the category catalog —
this file owns the axes; that file owns the per-category instances.
(Folding CATEGORIES.md in was considered and kept separate, 2026-08-12:
different readers — reference card while writing loft vs. constitution
while writing the spec — and different ownership. The splice machinery now
exists, so merging CATEGORIES as one more generated section is cheap and
the question stays open as pure taste.)
This file answers one question wherever it arises: what kinds of things
exist in loft, and along which axes is each thing classified? When a
spec section, error message, or design conversation needs a word for one
of these, it uses this file’s word.
1. The identity ladder
Every placeable thing in a model resolves through the same four layers:- component = function · type = partial application · instance = the call.
- Every v0.3 type is a type of the built-in generic component (scaffolding with a designed retirement — SPEC-v0.3 §7 P2).
- Declaration order is category → type → [id] → placement (decision 38); each category requires at least one of the two leading name slots.
- Declared datums (
grid,level, futurerefplane): pure reference geometry — carriers with no recipe. Id required (being referenced is their entire job); type slot absent today, arrivable additively. opening: the type-less, filler-less cutter. Id optional (an element, not a datum); no type, ever — a product-shaped hole is a component’s cutter, which is a recipe role, never an element.- Projected datums (
wall.W1.end,door.D1.jamb): not declared things at all — the datum surface every element exports, per the category’s projection rule. Inventory: the category catalog. - Placement is a how-to, not ontology: Placing elements walks the methods — a plane, a line, a point, a wall host, a loop — with the slots each one takes.
2. Parameter axes
Every parameter is classified along five independent axes. The first two exist in v0.3; the rest are settled direction with reserved arrival points.
Requiredness is a per-parameter fact governed by the requiredness
ratchet (decision 45): authority descends category → component → type →
placement, and each rung may tighten optional → required for what it
governs, never loosen. The category holds the core (every door has
width, height); a type may demand a delegated u- parameter of every
placement (u-hardware instance text required — omission is D0515); the
future param statement (project-profile rung) demands at project scope;
components join with their own vocabulary — including the category’s
optional words (a door’s sill, a part number), which stay spec-typed
even when unenforced — in their era.
3. Value types
The closed set is the type system. Fixed parameters infer their value type from the literal; the unification rule (“one name, one value type, per component” — SPEC-v0.3 §6.3, rendered per category while types apply the built-in generics) makes that inference safe. Both facts require the literal grammar to be a closed, enumerated list: an unenumerated value type is undefined behavior, so this table is normative — and generated from the enum the parser enforces (/packages/lang/src/schema/valuetypes.ts).
v0.3 ships
The lexical law (why length never confuses with text)
- Text is always quoted. No exceptions, no bare words.
- Unquoted values must lex as exactly one non-text literal form
(length, number, boolean).
3'-6"is a length;"3'-6in"is text that mentions one — strings have no escape sequences (the tokenizer ends text at the next"), so a length inside text uses the word-suffix spelling (in/ft), which is already first-class. - Nothing ever falls back to text. An unquoted token matching no
literal form is an error with a did-you-mean — never a guessed
string. (The YAML cautionary tale: unquoted-scalar guessing turns
nointofalse. loft never guesses.) - Delegated declarations state the type by name (
instance text) because there is no literal to infer from; the stated name must be a row in this table.
The growth law
The set grows by versioned addition, and a new value type may only claim literal spellings that were previously errors — never reinterpret a spelling that already means something. (Adding45deg
is legal: it lexes as nothing today. Making bare words mean text is
illegal forever: it would reinterpret every typo.)
Reserved roadmap (each with its motivating case)
Reference-valued parameters store the reference plus the resolved
target (principle 3), and ride the shared resolution chain.
4. The governance ladders
Two ladders, often conflated because both get called “the ladder.” They answer different questions.4a. The declaration ladder — where does a definition live? (3 rungs)
Every nameable definition (profile, type, eventually component) climbs the same three rungs, each migration a mechanical “extract and name”:- Inline — sketched anonymously in the using declaration (a
rectangular profile implied by
width 3' height 7'). Local, zero ceremony. - Project-declared — named at project scope (
doortype 3070 …,profile ARCH-TOP …, the walltype pattern). Reusable, diffable, one source per model. - Registry — imported from a package
(
import { DBL-9070 } from "@registry/…"), version-pinned, vendor-owned.
4b. The vocabulary governance ladder — who owns which words?
For parameters and datums alike (one ladder, two columns), authority descends, and each level may only add, never redefine:- Spec / category core — the format’s own law: every door has
width,height, projectscenterline · head · jamb. Closed, guaranteed, tools may assume it blind. - Project profile — additive-only project law (“every door on this
job needs
u-fire-rating”); the futureparamdeclaration statement. Liskov-governed: a profile may demand more, never less or different. - Component/type — author-added vocabulary (
u-parameters, custom datums), binding level chosen here (fix vs delegate), exported-vs-internal chosen here (component era). - Instance — supplies values for whatever was delegated; declares nothing.
u- prefix marks vocabulary owned below the spec rung, and its
typing scope follows its owner: unification runs at the declaring
component’s scope (“one name, one value type, per category” in v0.3,
where every type applies the built-in category component) — which keeps
rung-3 freedom type-safe without rung-2 ceremony. Rung 2 (param) is
what grants a name one identity across categories: the shared-parameter
tier, where a BIM manager pins the type and requiredness once and the
cross-category schedule column becomes legitimate.
5. Datum kinds and selectors (pointer)
Datums classify by kind (plane, line, level — the decision-20 kind algebra, orientation-worded) and resolve through selectors in two families: geometric (resolved from the referencing statement’s own frame: barejamb) and operational (resolved from the referent’s
declaration: jamb.near, hinge). Per-category inventories, the
required cores, and the projection rule live in
the category catalog and SPEC-v0.2 §6.3 — this file only fixes
the axis names.
6. The keyword registry
Generated from/packages/lang/src/parser/keywords.ts and the category
catalog — the registries the parser enforces. A statement keyword is
claimed (real grammar) or reserved (errors with a teaching message
naming its future, never a generic syntax error). Reserved value words
are contextual: legal only in the slots that define them, the reserved-word
error everywhere else. Reserved parameter words are the catalog’s rows
with an era (schema/categories/): clause words a statement refuses with
a teaching message naming their future (D0274), at the rung the row names.
Claimed statement keywords: project · units · level · grid · walltype · doortype · windowtype · wall · door · window · opening · room · roomline · viewtype · view
Reserved statement keywords
Reserved value words
Reserved parameter words
Change protocol: hand-authored sections ride spec PRs; generated sections are edited only through their source modules and
pnpm gen:taxonomy — same law as the category catalog. Never let this file and the
code disagree silently.