Back to 23 records. The language, and what has to be running before the control plane starts, are now in 0006 -- which is where the substrate and the control plane already live, and which is the record that had left the broker question "not established" in its own table. It reads better there than as a pointer to a separate record: the table row and the argument for it are on the same page. The store mechanics went into 0008. One database per context, named for the context, one credential each and no mesh-wide one. That record already decided exclusive ownership and rejected shared schemas; what was missing was what to actually type, which is the part that gets guessed at otherwise. Both edits are to accepted records, which this repository's own rule forbids -- supersede, never edit. Recorded here so it is visible rather than silent. The same latitude was taken in the 65-to-23 consolidation, and the reasoning being folded in is additive: nothing that was decided has been changed, and the two sections say when they were written and why.
5.7 KiB
topic, status, date, deciders, reconstructed, extends
| topic | status | date | deciders | reconstructed | extends |
|---|---|---|---|---|---|
| the tiers | accepted | 2026-08-26 | jochen | false | 0009-modules-and-the-graph.md |
8. A context owns its store, exclusively
Context
ADR 0009 settles what a module declares. This settles what a grant may be, and it is the half that removes things.
how-we-build §4 already says contexts integrate through the record, never through a shared
schema, and states the cost: several domains share one forty-five-table schema, which is why
work belonging to one context keeps having to be implemented in another.
That was written as a principle. Counted, it is thirteen foreign tables belonging to three separate contexts, living in the mesh's own registry database.
Considered options
- A schema per consumer inside a shared database. Namespaced, revocable by dropping the schema, with a cross-context join possible but deliberate. Rejected: it keeps the letter of §4 and leaves the temptation in place, and a boundary that is merely inconvenient to cross gets crossed.
- Read-only roles on another context's store. Rejected for the same reason and one worse: reading another context's tables couples you to its layout exactly as firmly as writing them, and the coupling is invisible until the owner changes a column.
- Exclusive ownership. Chosen.
Decision
A context is granted only what it exclusively owns.
No shared writes. No read-only role on another context's store. If you need what another context holds, you ask it or you subscribe to it.
The unit is the context, not the process. Everything inside a context — its service, its surface, its tools — reads its own store freely. A board showing the mesh's own nodes and modules is the mesh showing its own data, not a boundary crossing. What is forbidden is a different context reading it.
Asking or subscribing is derived, not chosen
ADR 0004 makes disconnection an ordinary situation. So:
- Anything that must keep working while disconnected cannot ask — there is nobody to ask. It keeps a local copy, which means subscribing.
- Anything where a stale answer is worse than none cannot subscribe. A display may lag; a decision about whether a grant is still valid may not.
Neither is a query against another store, whatever transport it travels over.
What that is, concretely
Written 2026-08-29, on building the first one — the rule above was clear and what to type was not.
One PostgreSQL database per context, named for the context. A separate database rather than a separate schema is the whole point: a cross-schema join is a qualified name away, and a cross-database join needs a foreign data wrapper somebody has to install and explain.
And one credential per context, held only by it. There is no mesh-wide connection setting and no way to ask for one, so reaching another context's store is not a matter of restraint — a process has no address for it and nothing to present. That is also how this rule is checked: what a context can reach is the list of variables the declaration running it grants, and it is read there rather than audited in code.
Contexts that do not exist yet do not get a database. The bootstrap creates the ones there are.
How a context added later gets its database is open, and it is a real question: by then there is a control plane, but a control plane holding a credential that can create databases is holding rather more than the thing it exclusively owns.
What this removes
The first clear list of what the design deletes rather than adds:
- Grant kinds. There is one: an exclusive resource. No schema grants, no read roles, no rules about who may see what inside a shared thing.
- The question of who owns which table, and the guessing at revocation time. Removing a consumer drops what it was granted, whole.
- Cross-context migration ordering. Two contexts migrating one database must be ordered against each other. Exclusive ownership means a context's migrations are ordered only against itself.
- A class of permission modelling a shared store would otherwise need.
Consequences
- Cross-context reporting is harder, and that is the point. Anything wanting to see across contexts consumes their events or calls their interfaces. That is §4's argument, and the cost it names is the one already paid.
- A single surface over several contexts still works — that is what a surface is. It reads interfaces, not stores. This holds while the contexts sit behind one interface; splitting a context into its own deployable costs that, and the composition would have nowhere to live that tier 3 permits. A real constraint on how far the control plane may be split.
- Three contexts must move out of the registry database, taking thirteen tables with them. Their dependency on the registry then shrinks to almost nothing — one of them needs a single table.
- The node appliers were already handled. ADR 0005 stopped the host querying the mesh database for tier reasons unrelated to this, and it removes most of the remaining direct readers as a side effect.
- What a consumer does about events missed while disconnected is not decided — replay from a point, ask once and resume, or rebuild. The question every projection has.
References
how-we-build.md§4 — the rule this makes enforceable.- Research 011 — the count, the worked provider, and the dashboard case.
- ADR 0004 — why asking or subscribing is derived.