Settles the design repository now that the self-upgrade build is on main: - Records the two decisions that shipped without a record — ADR 0077 (the controller/foundation/node vocabulary) and ADR 0078 (the store and broker are ordinary modules); accepts ADR 0075 and 0076, which shipped work rests on. - Fills issue 051's amended-design and wires ADR 0078 into 07-the-foundation. - Sweeps the repo rename (mesh-control -> mesh-controller) into the mutable docs now that the forge repo is renamed; updates the glossary note and repos.md. - Fixes the six broken links from the design-doc renames, indexes the glossary, regenerates the decisions reading order. Both checks (records.py, index.py) are green. Statuses stay honest: the build is on main and lab-proven but not deployed as the production mesh, so the to-be docs remain in-progress and the as-is layer (the hal mesh) is unchanged — graduation to implemented + as-is belongs to deployment, not merge. https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
110 lines
5.5 KiB
Markdown
110 lines
5.5 KiB
Markdown
---
|
|
topic: what runs on it
|
|
status: accepted
|
|
date: 2026-08-31
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 02-DECISIONS/0009-modules-and-the-graph.md
|
|
---
|
|
|
|
# 27. A provision names what the consumer is coupled to, not the role it plays
|
|
|
|
## Context
|
|
|
|
Provisions are named after roles. The catalogue and every test fixture built so far say:
|
|
|
|
```
|
|
provides: database
|
|
requires: database
|
|
```
|
|
|
|
**Nothing distinguishes one engine from another.** A module requiring `database` is satisfied by
|
|
any module providing `database`, so a module written against PostgreSQL can be matched to a
|
|
provider of Microsoft SQL Server, resolve as satisfied, deploy, and fail on its first query.
|
|
|
|
**The mesh runs several engines** — PostgreSQL, Microsoft SQL Server, MariaDB, and others behind
|
|
products that expose their own. This is not a hypothetical collision.
|
|
|
|
**The failure is in the direction that hides.** Resolution *succeeds*. Nothing is refused, nothing
|
|
is logged, and the breakage surfaces later as an error inside an application, on a machine, with
|
|
nothing connecting it back to a match made elsewhere by something that thought it had done its
|
|
job. **A wrong answer delivered confidently costs more than a refusal**, and the whole point of
|
|
refusing on ambiguity ([ADR 0009](0009-modules-and-the-graph.md)) was to not do this.
|
|
|
|
**How it got in:** every test written for the resolver had exactly one provider of each name, so no
|
|
mismatch was expressible and none was caught. The fixtures agreed with the design. That is the same
|
|
fault as [`04-ISSUES/005`](../04-ISSUES/005-pipeline-test-harness-unbuildable/00-report.md)'s
|
|
imagined output and [`019`](../04-ISSUES/019-a-comment-asserting-a-fact-about-a-machine/00-report.md)'s
|
|
unchecked comment, at the level of a name rather than a line.
|
|
|
|
## Considered Options
|
|
|
|
1. **Keep role names; let the operator assign correctly.** The mesh would refuse ambiguity when two
|
|
providers exist, so a person picks. **Rejected.** It makes correctness depend on somebody
|
|
knowing that the module they are assigning speaks a particular dialect — which is exactly the
|
|
knowledge the provisioning model exists to remove. And with one provider of each name, nothing
|
|
is ambiguous and nothing is asked.
|
|
|
|
2. **A role name plus a `flavour:` or `engine:` qualifier**, matched as a second field.
|
|
**Rejected.** Two fields that must agree is a constraint the resolver has to enforce and a
|
|
manifest author has to remember, to express something one field already can. The name is the
|
|
contract; splitting it invites a requirement that names a role and forgets the qualifier, which
|
|
then matches everything again.
|
|
|
|
3. **The name says what the consumer is coupled to.** **Adopted.**
|
|
|
|
## Decision
|
|
|
|
**A provision is named for the thing a consumer's code is written against.**
|
|
|
|
```
|
|
provides: postgres-database
|
|
requires: postgres-database
|
|
```
|
|
|
|
**The test is whether the consumer can tell the difference.** If swapping the provider would break
|
|
the consumer, the name must say which provider — because a match that breaks the consumer is not a
|
|
match. If the consumer genuinely cannot tell, a role name is correct and better.
|
|
|
|
| provision | | why |
|
|
|---|---|---|
|
|
| `postgres-database`, `mssql-database` | **specific** | applications are written against a dialect; a swap breaks them |
|
|
| `route` | **role** | the consumer wants its name reachable and does not care what proxies it |
|
|
| `resolver` | **role** | the consumer wants names to resolve |
|
|
| `artifact-store` | **role** | the consumer fetches by digest over a protocol, and nothing else |
|
|
|
|
**`database` is not a provision and may not be provided.** There is no context in which an
|
|
application talks to a generic database: it talks to PostgreSQL or it talks to SQL Server. A name
|
|
that cannot be true of any real consumer should not be expressible.
|
|
|
|
**This is about coupling, not about products.** Two providers of `postgres-database` — a container
|
|
on this node and a managed instance elsewhere — are interchangeable and *should* both match. What
|
|
may not be interchangeable is what the consumer's queries are written in.
|
|
|
|
## Consequences
|
|
|
|
**Every manifest that names a database changes.** Doing this now costs a rename across a handful of
|
|
examples. Doing it after modules are migrated costs it across all of them, plus every deployment
|
|
that resolved against the old name.
|
|
|
|
**Wrong requirements now fail loudly, and at the right moment.** A module requiring
|
|
`postgres-database` where only `mssql-database` is provided is unsatisfiable, so it is **refused at
|
|
resolution** with both names visible — rather than deployed and broken later. This is the property
|
|
that was lost, restored.
|
|
|
|
**Generic role names are still right, and the rule says when.** This does not push specificity
|
|
everywhere; it puts it exactly where a consumer is coupled. Naming `route` after a particular proxy
|
|
would be the same error in the other direction, and would prevent a swap that genuinely changes
|
|
nothing.
|
|
|
|
**It is checked, not merely stated** ([`00-META/how-we-build.md`](../00-META/how-we-build.md) §5).
|
|
A manifest providing a name known to be engine-generic is refused, naming what to say instead.
|
|
Without that, this record is a convention, and a convention is what the previous naming was.
|
|
|
|
## References
|
|
|
|
- [ADR 0009](0009-modules-and-the-graph.md) — provisions, and refusing on ambiguity
|
|
- [`03-DESIGN/01-to-be/07-the-foundation.md`](../03-DESIGN/01-to-be/07-the-foundation.md) — *the
|
|
provisioning model uses databases, roles and schemas as PostgreSQL means them*, which is this
|
|
record's point made about the substrate before it was made about modules
|