A constraint declares what KIND of thing its scope names — `subject` separates unaddressable from undeclared, and the coverage report stops counting them as one defect
Accepted
Context
Asked "what governs this file?", an agent answers from whatever a keyword matched. The constraint set already declares the answer — every constraint carries a `scope` and most carry a `check` whose paths are what actually gets verified — and the routing module (`governs`) derives that mapping on every call rather than storing it.
The derivation exposed a population it could not describe. Measured 2026-08-16 across 382 accepted constraints, 78 could be addressed by no file. Read as one number that said "78 constraints this router cannot answer for", and it was wrong in three different directions at once:
- Some name a CLASS OF PROJECTS or an entity outside every tree. adr-016's `.ontology/core.ncl` is the sharpest case: it is the consumer convention (verified — syntaxis, solera, evol-rustelo and forge-fleet all carry `.ontology/`), while ontoref keeps its own spine at `.ontoref/ontology/`. It resolves nowhere HERE by design. Reported as a defect, it invites the one repair that would destroy it — re-pointing the check at this tree's spine, so it goes green while never looking at the extracted project it governs.
- Some DECLARE a path and none of it resolves. That is a stale pointer with a witness, and folding it into either neighbour hides the most actionable class there is.
- Some say nothing at all about what they govern. That, and only that, is a blind spot.
A router that cannot tell "unaddressable" from "undeclared" reports coverage it does not have, which is the failure the module exists to avoid — adr-072's `coverage-superset-of-extent`, inside the instrument built to measure it.
`subject` was introduced additively during that measurement, with its vocabulary DERIVED from the 74 distinct unresolvable scope expressions rather than invented. It did not get an ADR then, and the evaluation was recorded honestly: criteria 1, 2 and 5 held, criterion 3 did not — one instance, reverting was free.
Criterion 3 now holds, and this is what changed:
- ADOPTION. 58 constraints across 23 ADR files carry a subject. - INHERITANCE. Consumers get the field whether or not they know it: a consumer ADR imports `defaults.ncl` → `adr-defaults.ncl` → `adr-schema.ncl` → `s.Adr`, by relative path into this tree (verified on forge-fleet's adr-001). - LOAD-BEARING IN A MECHANISM, not only in a report. `governs stale` now reads `subject` to tell a project-relative check path from rot; that read is what stopped adr-016 being reported as a stale pointer. Reversing the field now means adr-schema.ncl, 23 ADR files, and two behaviours in routing.nu.
One further conflation was measured the same day and is closed here. Seven of the thirteen constraints then counted as UNTYPED were `gateable = true` with a working check. A `NuCmd` check carries `cmd`, not `paths`, so it derives no route — a fully verified constraint sat under a heading reading "THIS is the blind spot". The count was right; the heading was false for more than half its members.
Decision
A constraint MAY declare `subject`, one of `'Path`, `'Glob`, `'Selection`, `'ProjectClass`, `'Event` or `'External`, stating what KIND of thing its `scope` names. The field is OPTIONAL and absent means unclassified — a reportable state, never a contract breach.
The declaration changes how the router reads the scope, and nothing else:
- `'Path` / `'Glob` LICENSE an extraction that is unsafe by default: pull every path-shaped token out of the prose and keep the ones that RESOLVE. Resolution is the filter, so a gloss like `(qa/describe resolvers)` cannot smuggle in a route. Without the declaration the same extraction would be guessing. - A declared non-path subject SILENCES THE SCOPE SIDE ONLY. Check paths keep routing whatever the subject says, because the check names what is actually verified and a `'Selection` constraint may still grep real files. - `'ProjectClass` and `'External` additionally mark a path as written against the project being CHECKED rather than this tree, so `governs stale` reports it in its own class instead of as rot.
Coverage is published in FOUR SEPARATED BUCKETS that sum to the constraint total — routable, declared-non-path, stale-typed, untyped — never three plus a residue. The untyped bucket is further split by whether a check exists, because a constraint verified by a path-less check is not a blind spot.
UNTYPED IS A MEASUREMENT, NOT A BACKLOG. It is not required to reach zero and must not be driven there by classification: a constraint whose scope genuinely names no addressable thing is left unclassified, and the reason is recorded where the constraint is authored.
`subject` is orthogonal to ADR-076 role-scope. Role-scope says WHOSE rule it is (Base/Domain/Project/Spiral); `subject` says what the rule is ABOUT. They share a word and nothing else, which is why the field is `subject` and not a second `scope_*`.
Constraints
- Hard `subject` MUST remain optional in the ADR schema. A constraint declaring none is unclassified — a reportable state, never a contract breach — and making the field required would convert a reporting layer into an adoption gate.
- Hard `governs coverage` MUST report routable, declared-non-path, stale-typed and untyped as four independently counted numbers whose sum equals the constraint total. No bucket may be derived by subtracting the others.
- Hard `subject` (what the rule is ABOUT) and ADR-076 role-scope (WHOSE rule it is) MUST remain two declarations with two vocabularies. Neither may be merged into, nor derived from, the other.
- Hard A path declared on a 'ProjectClass or 'External constraint is written against the project being CHECKED, not this tree. `governs stale` MUST report it in its own class and never as a stale pointer.
- Soft UNTYPED is a measurement, not a backlog. A constraint whose scope names no addressable thing MUST be left unclassified with its reason recorded, never given an ill-fitting subject to lower the count; and no gate, report or review may state a target for the number.
Alternatives considered
- A second `scope_*` field (e.g. `scope_kind`) instead of a new name — rejected: ADR-076 already owns `scope` for role-scope. A `scope_kind` sibling reads as a refinement of that axis when it is an orthogonal one, and the two would be conflated on first contact by exactly the readers the field exists to help. The rename is the disambiguation.
- Make `subject` required — rejected: It would classify 324 constraints by decree and convert a reporting layer into an adoption gate — the Yang capture of formalization-vs-adoption, on the field whose own purpose is to describe rather than oblige. It would also guarantee the failure this ADR forbids: an author facing a required enum picks the nearest tag rather than leaving the honest blank.
- Infer the kind heuristically from the scope text — rejected: That IS the guessing the router was built to avoid. Applied as a default, the path extraction would mint routes from glosses — `(qa/describe resolvers)` is not a file, and only resolution-under-a-declaration keeps it out. An inferred classification is indistinguishable from a declared one in the report and unfalsifiable in the file.
- Fold 'ProjectClass into 'Selection — rejected: 'Selection is evaluable HERE — a predicate over the substrate this project holds. 'ProjectClass never is. That is not a shade of meaning, it is the mechanical difference `governs stale` needs to decide whether an unresolved path is rot or a correct pointer into someone else's tree; collapsing them would restore the adr-016 misreading.
- Drive UNTYPED to zero by classifying every remaining constraint — rejected: Measured on the last thirteen: five of them genuinely name no addressable thing — a behaviour at a moment that may never have occurred, a negative about a render path that cannot be located, a peer project's tree. Tagging those buys a zero by making the vocabulary lie, and it hides the real finding, which is that the constraint does not say what to check.
- Add a seventh tag for «one named peer project, verified by witness» — rejected: Deferred, not refused. adr-017's two constraints are the only measured instances and they are already governed as a cross-project witness seam by ADR-028. One shape with two instances does not yet justify a vocabulary extension inherited by every consumer; the gap is recorded at the constraints themselves so a second instance can reopen it with evidence.
Anti-patterns
- Tagging to clear the count — A constraint is given the nearest available subject — not the one that fits — because the blind-spot number is what gets read and a tag is cheaper than a sharper scope. The report improves, the vocabulary stops meaning what it says, and the constraint that never stated what to check now looks classified. Refused twice on 2026-08-16: adr-017's two provisioning constraints were left untyped rather than tagged 'External (which means «outside every repository tree», and a peer repo is a tree) or 'ProjectClass (which means a CLASS, and this names one project).
- Gated, and counted as a blind spot anyway — A constraint verified by a check that names a COMMAND rather than paths derives no route, so it falls into the unaddressable bucket while being fully enforced — and the bucket's heading says nothing verifies it. The number is right and the sentence over it is false, which is worse than a wrong number because it is believed. Measured 2026-08-16: 7 of 13 untyped constraints were gateable with a working NuCmd check.