Skip to main content

koyu validate

Runs the judgements that belong to architecture — daylight, reachability, envelope continuity, stair proportions, the site numbers. It says, under a different name and a different type, what koyu check does not guarantee.

Arguments​

koyu validate <entry.muro> --profile <id> --as-of <YYYY-MM-DD> [--json]

Takes one entry path. The profile and the date are required. Neither is inferred from the filename, the locale, the environment or the clock: a judgement whose grounds were guessed cannot be reproduced, and one that quietly picked a default is worse than one that refused to start.

koyu ships one profile, koyu.profile.schematic-screen. It is design lint, not code compliance — see the validation reference.

Flags​

FlagEffect
--profile <id>Required. Which rule profile to run. An unknown id is a usage error
--as-of <YYYY-MM-DD>Required. The effective date the judgement is made against
--jsonWrites the whole AssessmentReport to stdout as JSON

Output​

When nothing is caught, one line comes out.

npx tsx src/cli.ts validate examples/house/main.muro --profile koyu.profile.schematic-screen --as-of 2026-08-03
✔ Nothing caught by validation (this is a judgement, not a guarantee about the composition)

The second line is the accounting. A rule that never applied and a rule that could not run are not passes, so they are counted separately rather than folded into the silence.

Otherwise each finding gets a line and a count follows. ✖ is a violation, ⚠ is a caution.

npx tsx src/cli.ts validate sealed.muro --profile koyu.profile.schematic-screen --as-of 2026-08-03
✖ [koyu.schematic.daylight.ratio] <absolute path>/sealed.muro:line 6: Insufficient daylight: /L1/a — effective window 0.00 m2 < required 2.31 m2 (1/7 of the 16.20 m2 floor)
✖ [koyu.schematic.access.unreachable] <absolute path>/sealed.muro:line 6: Cannot reach the exterior: /L1/a (no passable boundary leads out — write a door)
✖ [koyu.schematic.access.unreachable] <absolute path>/sealed.muro:line 7: Cannot reach the exterior: /L1/b (no passable boundary leads out — write a door)
Validation — 3 violations / 0 cautions

That sealed.muro is a file koyu check returns green for. The two commands look at different things.

Inside the square brackets is the rule name; what follows, <resolved absolute path>:line <n>:, is the provenance. Findings without a position carry no prefix.

The shape of --json​

--json writes the whole AssessmentReport, not a bare list. The report names the profile and every rule set it applied, keeps one entry per rule with its state, and projects the failures into findings.

{
"schema": "koyu-assessment",
"profile": { "id": "koyu.profile.schematic-screen", "revision": "1" },
"ruleSets": [ { "id": "koyu.ruleset.schematic-screen", "revision": "1" } ],
"model": { "languageVersion": "koyu 1.1", "state": "consistent", "diagnostics": [] },
"context": { "schema": "koyu-context/1", "asOf": "2026-08-03", "values": {} },
"analyses": [ "…" ],
"rules": [ "…" ],
"findings": [
{
"rule": { "id": "koyu.schematic.daylight.unknown", "revision": "1" },
"ruleSet": { "id": "koyu.ruleset.schematic-screen", "revision": "1" },
"level": "caution",
"outcome": {
"id": "/L1/a",
"status": "fail",
"subjects": [ { "kind": "space", "ref": "/L1/a" } ],
"message": "Window area is not fully counted: /L1/a has a window without h: (write h: on it)",
"evidence": [ "…" ]
}
}
],
"summary": {
"state": "complete",
"rules": { "evaluated": 5, "notApplicable": 10, "indeterminate": 0, "error": 0 },
"outcomes": { "pass": 3, "fail": 1, "indeterminate": 0 }
}
}

Every outcome carries the evidence it rests on, and that evidence names the analysis that produced it and the source line it came from — so a verdict can always be walked back to the declaration that caused it.

level is an invariant property of the rule. The same rule is never a violation one time and a caution another.

The rules​

RuleLevelWhat it says
koyu.schematic.daylight.ratioviolationEffective window area is below one seventh of the floor
koyu.schematic.daylight.unknowncautionA window has no h, so the window area was not fully counted
koyu.schematic.stair.proportioncautionThe derived steps are cramped (tread under 240mm, or 2R+T outside 550–700)
koyu.schematic.ramp.declared-slope / koyu.schematic.escalator.usual-slopecautionThe derived slope is steeper than declared, or outside normal use
koyu.schematic.run.disconnectedcautionA vertical run exists in form, but no vertical boundary joins the two levels
koyu.schematic.access.unreachableviolationA room with a region cannot reach the exterior
koyu.schematic.access.voidonlyviolationThe only doors open onto a void, where there is no floor
koyu.schematic.access.throughtenantcautionEscape from a stair core passes through a leased tenancy
koyu.schematic.access.parkingviolationA car cannot get out of the parking
koyu.schematic.access.backofhousecautionA vertical run is unreachable from the public corridor without crossing back-of-house
koyu.schematic.column.blocksdoorviolationA derived column overlaps a derived door
koyu.schematic.site.escapeviolationThe building leaves the site polygon
koyu.schematic.site.areacautionThe declared and derived site areas disagree
koyu.schematic.site.frontageviolationRoad frontage is under 2m

A rule id is koyu.schematic. plus a chapter (envelope / daylight / stair / ramp / escalator / run / access / column / site) and a name inside it. The chapter is a subject, not a jurisdiction, and the namespace is there so nobody reads a design-lint result as a statement about the law.

Findings come out in rule-set declaration order, and within a rule in scan order.

Exit codes​

Exit codeMeaning
0The whole set ran and nothing was violated — cautions alone still give 0
1A violation, an inconsistent model, a rule left indeterminate, or a rule that errored
2A missing or unknown profile, a missing date, no file path, or malformed context

Being unable to judge is not passing. A rule whose input was missing, or that failed to run, exits 1 rather than disappearing into the silence — that is the whole reason the summary distinguishes indeterminate and error from not-applicable.

Configuration problems exit 2, off the 0/1 axis, so no script can read "could not run" as "nothing was violated".

npx tsx src/cli.ts validate main.muro
validate needs an explicit profile: --profile <id>
  koyu ships one: koyu.profile.schematic-screen

A file that could not be read exits 1, with a single ✖ on stderr. --json does not produce valid JSON on that path — the SYN01 trick that check --json uses does not exist in validate.

Here is the cautions-only exit code for real.

muro 1.5
grid X 0 4500
grid Y 0 3600
level L1 0 h:2400 slab:150
space /L1/a room X1..X2 Y1..Y2 daylight:1
space /out outside:1
boundary /L1/a /out t:120
  door w:900 edge:S
  window w:2400 h:1500 edge:N
  window w:1200 edge:E
npx tsx src/cli.ts validate caution.muro --profile koyu.profile.schematic-screen --as-of 2026-08-03
⚠ [koyu.schematic.daylight.unknown] <absolute path>/caution.muro:line 5: Window area is not fully counted: /L1/a has a window without h: (write h: on it)
Validation — 0 violations / 1 caution

The exit code is 0. To fail CI on cautions too, read --json and count them yourself. There is no validate equivalent of check --strict.

How it differs from check​

koyu checkkoyu validate
Returned typeDiagnosticAssessmentReport
Namecode (BND04 — three letters and two digits)rule (koyu.schematic.access.unreachable — a namespaced id)
Weightseverity: error / warninglevel: violation / caution
PopulationThe diagnostic ledgerThe validation rule ledger
Groundsnone neededa profile and a date, always explicit
What it guaranteesThat what is written holds together as dataNothing — it is a judgement
Nature of the surfaceFrozen. Adding or removing moves the language versionNot frozen. May grow, may be discarded

The field names differ, so the two arrays cannot be confused; try to concatenate them and the types fail. Making it impossible to describe "check's green" and "validate's green" with the same words is the point of the split.

Adding a judgement does not move the language version. The validate surface is allowed to be coarse, to cover one jurisdiction only, and to be imprecise — it is cheap because it is not frozen.

See also​