Skip to main content

check and validate

koyu splits "correct" in two.

Structural diagnosticsArchitectural judgement
Commandkoyu checkkoyu validate
Return typeDiagnostic { code, severity }Finding { rule, level }
IdentifierBND04, SUF01 — three letters plus two digitsenvelope.gap, access.unreachable — chapter.rule
Weighterror / warningviolation / caution
Count65 codes15 rules
Versionfreezesdoes not freeze; grows
What it sayswhat is written does not contradict itself as datathis is (probably) architecturally sound

They are spelled differently on purpose. No reader will confuse ENV01 with envelope.gap.

Even the types are separate

Add --json and the fact that these two are not the same shape is visible directly.

npx tsx src/cli.ts check b1.muro --json
[
{
"code": "BND04",
"severity": "error",
"message": "The spaces do not touch, so no boundary can be derived: /L1/a | /L1/b",
"line": 6,
"file": "b1.muro",
"path": [
"/L1/a",
"/L1/b"
]
}
]
npx tsx src/cli.ts validate gap.muro --json
[
{
"rule": "envelope.gap",
"level": "caution",
"message": "Perimeter not faced by any envelope: /L1/b — S 3600mm / E 4000mm / N 3600mm (11200mm over 3 run(s)). Write a boundary to the exterior",
"line": 6,
"file": "gap.muro",
"path": [
"/L1/b"
]
}
]

Being unmixable is the job of these types. For a downstream tool to pour both into one array, it must first flatten the types — and that flattening is visible at the moment it happens.

Why split — the quality demanded is different

Judgement is a domain where what is correct is not settled yet. How to treat daylight correction factors, where to cut off a cramped stair, how to count egress routes — every one of these varies by jurisdiction, by era and by building use. Freeze before they settle and you freeze the mistake.

Structural diagnostics, by contrast, are settled. Is the path unique? Does the referent exist? Do two regions overlap? These are part of reading, and admit no opinion.

So they are treated differently.

Structural diagnosticsArchitectural judgement
Cost of addinghigh — parsing, composition, machine format, spec and docs all at oncelow — one ledger line and one page section
Cost of being wrongvery high — the error sits on a frozen surfacelow — rewrite it
Precisionmust be completemay be coarse
Can it be thrown away?noyes

There is one condition on being allowed to be messy: the result of a judgement must never be mistaken for a guarantee about the source. Spelling, type and output wording all defend that condition.

Validation — 2 violations / 0 cautions
✔ Nothing caught by validation (this is a judgement, not a guarantee about the composition)

Green judgement means only that judgement is green.

Coarseness in practice

envelope.gap reports holes in the envelope. Its population is only those levels where at least one boundary to the exterior has been written.

It will not call a storey whose envelope has not been modelled yet "full of holes". It demands "if you started, finish"; it does not demand completeness.

This is not a precise rule. A scheme-stage model with the envelope written for one storey hears nothing about the others. That is fine — the rule can afford to be this coarse because fixing the coarseness is cheap. Were this rule in core, it could not have shipped this coarse.

core returns numbers; judgement draws lines

Aggregate and graph queries live in core. They never say pass or fail.

QuestionWhat core returnsWhat judgement says
Daylightfloor area and effective window areadoes it meet 1/7 (daylight.ratio)
Sitesite area, frontage, footprint, gross floor area and their ratios2 m of frontage (site.frontage), escaping the site (site.escape)
Vertical circulationriser count, rise, tread, slopecrampedness (stair.proportion), slope (run.slope)
Envelopeperimeter segments faced by nothingis that a hole (envelope.gap)
Circulationfewest-door routes and passabilitycan you get out (access.unreachable and the rest)
Columns and openingscolumns standing on grid crossings, openings on segmentsdo they overlap (column.blocksdoor)

The thresholds belong to architecture. 1/7, 2 m and 240 mm are not invariants the composition must satisfy. core goes as far as returning the number; judgement draws the line on it.

Because of that split, adding a judgement for another jurisdiction touches not one line of core. Add a rule to the ledger, write a page, and you are done; the language version does not move.

Judgement is a surface that grows

Fifteen rules is not a finished set. Fire compartmentation, shadow studies, setback envelopes, travel distances, accessibility, whether services can be made to work — there is a great deal left that could be written as judgement.

All of it goes onto this surface. Adding it does not change what .muro means, existing files still read, and canonical JSON still emits the same bytes.

Adding to the language is expensive; adding to judgement is cheap. Creating that asymmetry is precisely why the two are kept apart.

Next