An inherited surface walks the bonds above its provider — a provider is found where it declares itself, the walk follows Governance edges only, and parents that are not each other's ancestors do not get an order
Accepted
Context
MEASURED 2026-09-19, ASKING WHETHER THE OUTREACH TOOLCHAIN IS A DOMAIN. Four projects with a real product behind them have no site yet, and the chain that would give them one — an htmx site, decks, generated images, a docs server — looked like something a project should be able to bond. It is not a domain, and the reason it looked like one is the defect this ADR closes.
THE CHAIN IN PRODUCTION HAS THREE LINKS AND THE PROTOCOL RESOLVES TWO. `rustelo` is a domain with an authority entry under `domains/`. `website-htmx-rustelo` is an implementation of it and, in the same manifest, the provider of a second domain: `domain_origin = rustelo` looking up, `domain_provides = { id = "htmx-site", kind = 'Implicit }` looking down. At least three sites consume that middle link. Its own spine names the mechanism — «rustelo (framework) → website-htmx-rustelo (implementation) → consumer … a file present in a higher layer wins, a file absent from it is inherited from the layer below» — and that cascade runs, for files.
FOR EVERYTHING THE PROTOCOL RESOLVES, THE MIDDLE LINK IS INVISIBLE. `domain-root` looks for `<tool-root>/domains/<id>/domain.ncl` and nothing else, so for `htmx-site` it answers "". adr-109's rung reaches a provider only through `domain-root`, so that link's modes cannot travel. `project-domains` reads `domain_origin` and never `domain_provides`. And `bond validate`, run on ontoref's own carrier, says it in one line: «htmx-site/ontoref-to-htmx-site: cites capabilities but domain 'htmx-site' is not hosted here — catalog unresolvable» — severity `unverifiable`, which is honest and is not red, so the hole reports itself and blocks nothing.
THE COST IS ALREADY ON DISK AS COPIES THAT DIVERGED. `plan-publication.ncl` exists in the framework and in the implementation and the two differ. A second constellation that took the same site implementation shares three build scripts with ontoref's site, and all three differ. What cannot travel by the mechanism travels by copy, and a copy drifts from the day it is made.
THE MODEL ALREADY HOLDS THE ANSWER; THE REFLECTION HALF DOES NOT EXIST. adr-073's bond makes a project «an ontoref-node in a domain's DAG (acyclic)», with uncapped cardinality and one carrier per parent. Adoption after the fact is typed: `'None → 'Node` via `'Onboard`, back via `'Offboard`. `'Merge` was added to the resolution enum to DESCRIBE exactly this cascade, and the schema says of it what this ADR is the answer to: what an update does on a `'Merge` bond «lives in reflection and does not exist yet … a partially-realized synthesis, stated as such». So several parents, natural or adopted later, is the declared shape. Nothing walks it.
BECAUSE «DOMAIN» IS THE ONLY WORD THE PROTOCOL HAS FOR WHAT N PROJECTS SHARE, EVERYTHING SHAREABLE DRESSES AS ONE. The middle link had to be bonded as if it were a domain with an authority entry, and the next two — a docs server, a deck toolchain — would arrive the same way. Registering each under `domains/` would write one machine's toolchain into the protocol, which is the defect adr-109 already refused for one directory layout.
TENSIONS ENGAGED. `plane-habitability`, vertical axis: the intermediate position is populated in the model and claim-only in three mechanisms; the direction is toward exercising what is declared. `formalization-vs-adoption`: `'Implicit` is that provider's honest value and has to stay valid — a link with no schemas is still a link. `enforcement-vs-emergence`: a sibling collision is refused rather than ranked, and the vocabulary for settling one by declaration grows when a real collision needs it, not before.
Decision
THREE RULES AND ONE BOUNDARY.
(1) A PROVIDER IS FOUND WHERE IT DECLARES ITSELF. `domain-root <id>` keeps its signature and gains a second path, tried only when the first finds nothing:
a. `<tool-root>/domains/<id>/domain.ncl` and its `schema_cmd` — unchanged, guard included; b. the registered project whose manifest declares `domain_provides.id` equal to `<id>`, compared verbatim (adr-113).
The material then lives where adr-102 already put it: `<provider>/.ontoref/domains/<id>/`. Two registered projects providing one id resolve to NEITHER, and both are named. Path (b) reads manifests, never a `schema_cmd`, so it cannot re-enter the wrapper.
(2) THE WALK FOLLOWS GOVERNANCE EDGES UPWARD FROM THE PROVIDER. From each provider reached, the resolver reads that provider's own carriers and continues along its `'Governance` bonds, to a root or to a node already visited. The result is the project's inherited providers, each with the path that reached it. Along ONE path the rule is the schema's — «the node wins»: a provider shadows every provider it descends from, per id, and that is not a collision.
(3) PARENTS THAT ARE NOT EACH OTHER'S ANCESTORS DO NOT GET AN ORDER. When two of them provide the same id, the id is CONTESTED. It settles, in this order:
a. by a declaration, where one exists; b. by the project's own artefact with that id — adr-109's override, unchanged; c. otherwise it is listed from NEITHER parent and reported with both claimants named.
There is no priority field. (a) has no dedicated vocabulary today: a `'Compose` bond names capability GROUPS, not ids. It gets one when a collision is met that a group cannot settle.
THE BOUNDARY — THE FIRST HOP IS WHAT ADR-109 SHIPPED. A project reaches every domain it bonds on either axis, as it does today. Only the walk ABOVE a provider is Governance-only. ontoref's own bonds to `rustelo` and `provisioning` are `'Dependency`, and the provisioning workspaces that motivated adr-109 reach their domain that way; filtering hop one by axis would break the ADR this one extends. And the upward restriction is not a preference: `mutual` makes a 2-cycle legal on the Dependency axis, so walking it would loop on data that is correct.
ACYCLICITY IS CHECKED, NOT ASSUMED. The Governance DAG is acyclic by declaration and nothing verifies it across projects. The walk carries a visited set, stops at a repeat, and reports the cycle by the ids on it.
A COLLISION IS CHECKED WHEN IT CAN BE INTRODUCED. An `'Onboard` can contest an id in a project that was green the day before, so the check runs in `bond validate` and at `'Onboard`, not only when modes are listed.
ONE RESOLVER, THREE READERS. The mode rung, `bond validate`'s catalog lookup and `domain validate`'s page lookup read the same walk. Deriving it per reader is how two answers to «where does this resolve» come to disagree inside one tree.
WHICH DECLARATION THE WALK READS. Where a provider states its parent twice — `domain_origin` in the manifest and a Governance bond in its carrier — the walk reads the BOND. `domain_origin` keeps what it already does: membership detection and import-path resolution.
Constraints
- Hard `domain-root` MUST resolve an id with no authority entry to the registered project whose manifest declares it in `domain_provides`, and MUST keep resolving an authority entry exactly as before
- Hard The upward walk MUST follow `'Governance` bonds and MUST NOT follow `'Dependency` ones; the first hop from the project MUST stay axis-agnostic
- Hard An id provided by two parents neither of which descends from the other MUST be listed from neither and reported with both claimants named, unless a declaration or the project's own artefact settles it; an id shadowed along one path MUST NOT be reported
- Hard The walk MUST terminate on a repeated node and MUST name the cycle
- Hard A project with no carrier MUST list exactly what it listed before this ADR
Alternatives considered
- Register each mediating implementation under `domains/` — rejected: Makes `domain-root` work unchanged and puts `htmx-site`, then a docs server, then a deck toolchain, inside the protocol's own tree — a list that grows with one author's machine. It also leaves the walk above the provider unbuilt, so the framework's surface still would not reach a consumer of the implementation.
- A new domain for publication tooling, beside `gov` — rejected: adr-102 defines `gov`'s first referent as the arrangement — «boundaries, members, what is published». What is published and for whom is already `gov`'s; the tooling is a vehicle. A sibling domain would split `gov` on the wrong axis and would not have made any vehicle resolvable.
- Carry the inheritance on `level.parent` (adr-045) — rejected: `level.parent` is single-valued and is the axis mode `'Delegate` and FSM depth resolve against. A consumer constellation needs several parents at once — a site implementation, a docs link, a deck link — and a parent adopted later. The bond is plural and adoptive by construction; the level chain is neither.
- A `priority` field on the bond to order sibling parents — rejected: Settles every collision silently and in advance, including the ones nobody has looked at. Rejected by the maintainer in favour of: the project settles it, unless something declares otherwise.
- Restrict hop one to Governance bonds as well — rejected: Uniform, and it empties the mode listing of every project that reaches its domain over a Dependency bond — ontoref and the provisioning workspaces among them.
Anti-patterns
- A mediating implementation is registered as a domain because no other word resolves — The protocol resolves one kind of shared thing, so every shared thing is declared as that kind. The declaration is accepted, resolves nowhere, and is reported at a severity that blocks nothing. Each new vehicle repeats it, and the question «is this a domain?» gets asked about things that are positions on a path.
Related ADRs
ADR-045 · ADR-073 · ADR-087 · ADR-098 · ADR-102 · ADR-109 · ADR-113
Perspectives