ADR-0122: Vocabulary scopes for the three assurance disciplines¶
Status: Proposed Date: 2026-08-01 Enforcement: decision-of-record Related: ADR-0053 (UL as a living artifact — the term store this ADR scopes), ADR-0054 (term-proposal machinery), ADR-0099 (orchestration as a control system — the canonical control roles), ADR-0100 (conformance ratchet), ADR-0101 (the reference regulator), ADR-0120 (setpoint regulators) Addresses: #10834 (this scoping ADR). Sequenced before #10831 (ADR-corpus coherence dig) so register collisions are not reported as contradictions. Register neighbours: #10833 (formal methods — the kernel proof), #10819–#10832 / #10829 (the control/stillness program).
Precedent: Bounded Context + Ubiquitous Language (Eric Evans, Domain-Driven Design, 2003) — vocabulary is scoped to a context, and the same word legitimately carries different meanings in different contexts, resolved by naming the context rather than renaming the word. Divergence: DDD assumes contexts map to separated subsystems/teams, each with its own model boundary, so a collision term never co-occurs unqualified inside one artifact; here the three assurance disciplines (constitutional/legal, formal methods, control theory) share one codebase and one ADR corpus, so the collision terms co-occur in a single document with no subsystem boundary to keep them apart — forcing an explicit per-term register convention (default owner + qualified forms) rather than a context-per-subsystem split (receipt: #10834; forcing condition: the #10831 coherence dig would otherwise surface these register collisions as apparent contradictions and spend its first pass on false positives).
This is a Proposed ADR — a design ruling for decision, not an accepted commitment. The approach below (three scopes + a per-term register convention) is what the owner signed on #10834 (2026-07-29, "approved, proceed"). The specific resolutions of the seven collision terms are drafted set-point text: the factory drafts, the author signs the wording. Accept, amend, or reject the individual defaults.
Context¶
The factory has acquired three assurance disciplines with overlapping words:
- Constitutional/legal design governs authority — who may change what, by what procedure, reviewed by whom, and why anyone outside should accept it: the ADR corpus, gate immutability, allowlists, escalation, human-signed envelopes.
- Formal methods govern the kernel's correctness (#10833): does the gate/verdict logic do what the rules say, under every interleaving.
- Control theory governs dynamics (#10819–#10832, #10829): will it hold, can it rest, is it drifting.
The three compose cleanly — the rules say it, the proof checks the kernel obeys it, the instruments watch what neither can reach — but they share vocabulary. Seven words already carry a different meaning in each register (verdict, setpoint, invariant, authority, gate, independence, erosion). Left unscoped, three registers in one ADR corpus muddle: a reader cannot tell whether "invariant" means proven, monitored, or asserted, and the #10831 coherence dig will read a register collision as a contradiction.
The CI-enforced UL store already exists (ADR-0053 term files + ADR-0054 proposal machinery, plus ADR-0099's canonical control roles), so this is a scoping addition, not new infrastructure. Doing it now is the cheap version: retrofitting disambiguation across ~90 ADRs after the corpus mixes is the expensive one.
Decision¶
1. Assign each discipline a vocabulary scope (register)¶
Every UL term belongs to a register — the discipline whose meaning it carries by default:
| Register | Discipline | Governs | Canonical anchors |
|---|---|---|---|
| legal | Constitutional/legal design | authority, procedure, entrenchment | the ADR corpus, allowlists, models.py:HitlEscalation |
| formal | Formal methods | proven correctness of the kernel (#10833) | the kernel proof, model-checker verdicts |
| control | Control theory | dynamics: stability, rest, drift | signal_control/controllers.py:PidController, erosion_metrics_loop.py:ErosionMetricsLoop |
The register is recorded in each term's Definition prose (the UL Term schema has no scope field today, so scope lives in the body — see Consequences). A term whose word is unambiguous keeps its single meaning; a collision term names its default owner and the qualified forms the other registers must use.
2. The register convention for collision terms¶
For each collision term: one register owns the bare word; the other registers must qualify (e.g. "monitored invariant", "model-family independence"). This is exactly DDD's "name the context" move, applied per-term because the contexts share one corpus.
3. The seven collision resolutions (drafted — author signs the wording)¶
Each is a new UL term file under docs/wiki/terms/. The default owner is the drafter's proposal; every one is an adjustable set-point.
| Term | Default owner (bare word) | Qualified forms in other registers |
|---|---|---|
| verdict | control/kernel — a gate's pass/fail (convergence_gate.py:JudgeVerdict) |
model-checker verdict / counterexample (formal, #10833); adjudication (legal ruling) |
| setpoint | control — a regulator's reference signal (signal_control/controllers.py:PidController) |
a specification (written requirement); a ruling (ADR decision) — neither is a "setpoint" |
| invariant | (none — always qualify) | proven (formal), monitored (control), asserted (legal); bare "invariant" overclaims |
| authority | legal — jurisdiction, who holds the decision (models.py:HitlEscalation) |
actuation authority (control: what a loop may do, bounded by the Governor) |
| gate | control/kernel — the mechanism (convergence_gate.py:Gate) |
gate policy / entrenched gate rule (legal: gate immutability, the allowlist) |
| independence | evidence/formal — model-family diversity (judge_independence.py:IndependenceDisposition, #10371/#10832) |
institutional independence (legal: a structurally separate reviewer) |
| erosion | control, plant-side — code decay (erosion_metrics_loop.py:ErosionMetricsLoop) |
setpoint erosion (control, reference-side, #10829): the target drifting, not the plant |
Note two terms deliberately break the "one owner" default:
- invariant has no default owner — the word must always be qualified, because the bare form silently claims proven and none of the three registers should be allowed to annex it.
- erosion is owned within one register (control) but split across its two sides (plant vs reference) because #10829 (setpoint erosion) and plant-side code decay have opposite remedies (tighten the plant vs restore the setpoint) and must not share a bare word.
4. Partition / jurisdiction split (same ADR)¶
Partition boundaries are jurisdictional (legal scope: which authority owns a region), while their sizing is measured (control scope, via context-cost). Naming that split now keeps the decomposition work (#10819-program) from straddling registers: "where the boundary is" is a legal decision; "how big it should be" is a control signal.
Consequences¶
- Positive. The #10831 coherence dig can treat a register-qualified word as intentional, not contradictory — its first pass is signal, not false positives. New ADRs inherit a convention instead of improvising per-author. The seven terms are now CI-anchored (ADR-0053 lint): each resolves to a live class, so the register scoping cannot silently drift from code.
- Schema gap (flagged for the author). The
Termmodel (ubiquitous_language.py:Term) has noregister/scopefield today; #10831's note anticipated one. This ADR records register in Definition prose as an interim. If the author wants register machine-queryable (e.g. to lint "unqualified collision word used outside its owning register"), that is a follow-up schema addition (new optional field + renderer column + a lint rule) — deliberately not bundled here to keep this a pure scoping decision. code_anchoris a representative, not a claim of sole ownership. Each collision term anchors to the one class that best embodies its default register (a UL term carries exactly one anchor); the other registers' embodiments are named in the term's prose, not as anchors.- Confidence. The seven term files ship at
confidence: accepted— the codebase invariant (tests/test_seed_terms.py::test_seed_terms_are_accepted, ADR-0054) is that all committed terms areaccepted; there is no soft-launch term lifecycle. The "not yet signed" state of the specific wording is carried by this ADR'sProposedstatus, not by the term confidence. When the author amends a resolution, the term file's Definition changes with it.
Alternatives considered¶
- One term per (word × register) — e.g.
verdict-control,verdict-formal,verdict-legal. Rejected: it triples the glossary, breaks the one-word/one-file intuition, and still needs a convention for which spelling is the bare default. The register-convention approach keeps one term per word and encodes the split in prose. - A
bounded_contextper discipline (addlegal/formal/controlto theBoundedContextenum). Rejected here: bounded context in this codebase already means an architectural boundary (caretaker/builder/shared-kernel), and these disciplines cut across those boundaries. Overloadingbounded_contextwould itself be a register collision. A dedicatedregisterfield (see Consequences) is the honest shape if machine-queryable scope is wanted. - Defer until #10831 surfaces the collisions. Rejected on the issue's own sequencing argument: deferring guarantees the coherence dig's first output is mostly false positives, and the retrofit cost grows with every ADR added in the meantime.