011: where a binding lives follows the scope, and a grant is not always a whole resource

Two questions asked directly, and the second collides with a rule in force.

One module on two nodes sharing a database corrects something stated flatly: the
binding is not "recorded on the assignment". Where it is written down FOLLOWS
THE SCOPE. A shared grant belongs to the module and every assignment references
the same one — which is the answer for two nodes wanting one database between
them. A per-instance grant belongs to the assignment. Same relation, two homes,
and which home is what makes two instances share something or not.

Several modules adding their own tables to one database is three needs wearing
one sentence, and a provider offers KINDS of grant rather than one: a database
for a consumer whose tables are nobody else's business, a read-only role for one
that needs to see what another holds, and a SCHEMA within a shared database for
the case actually asked about.

Loose tables in a shared database is what how-we-build §4 warns against in as
many words — several domains sharing one forty-five-table schema, which is why
work belonging to one context keeps having to be implemented in another. Not a
style objection; the observed cost, already paid.

A per-consumer schema keeps what the request wants and drops what §4 objects to.
Same database, same connection, same backup, and a cross-schema read remains
physically possible when genuinely needed. What it adds is ownership: migrations
touch one namespace, two modules cannot collide over a table name, and revoking
drops the schema rather than guessing which tables belonged to whom.

So the fault §4 names is still possible and no longer accidental — a
cross-context join becomes something somebody deliberately writes rather than
the path of least resistance. And revocation becomes answerable, which the
whole-database version never was.
This commit is contained in:
2026-08-26 23:08:32 +02:00
parent fa9889536c
commit 4c8515507a
@@ -107,9 +107,15 @@ That is a property of the consumer, expressible without naming anything. And it
that actually decides: a shared need cannot be satisfied by a provider each node runs
separately, and a per-instance need should not be satisfied by a shared one.
**Then the mesh binds, and the binding is recorded on the assignment.** Not recomputed. A
resolver that re-derives which store serves a consumer will one day derive a different answer and
relocate a database — so the binding is made once, written down, and changed only deliberately.
**Then the mesh binds, and the binding is written down.** Not recomputed: a resolver that
re-derives which store serves a consumer will one day derive a different answer and relocate a
database, so the binding is made once and changed only deliberately.
**Where it is written down follows the scope.** A shared grant belongs to the *module* and every
assignment of it references the same one — which is the answer for one module installed on two
nodes wanting one database between them. A per-instance grant belongs to the *assignment*. Same
relation, two homes, and which home is not a detail: it is what makes two instances share
something or not.
The pieces that follow, none of them settled here:
@@ -153,6 +159,41 @@ case, and reaching it is not the host's job. So the same operation is local at b
remote afterwards, which is either two mechanisms or one mechanism with a boundary crossing in
it. Undecided, and it is the sharpest unresolved thing in this file.
## Several modules, one database
Asked directly, and it is three different needs wearing one sentence. They want different
answers and one of them collides with a rule already in force.
**A grant is not always a whole database.** A provider offers *kinds* of grant, and the kind is
what separates these:
| The need | The grant |
|---|---|
| my own tables, nobody else's business | a **database** |
| read what another module holds | a **read-only role** on an existing database |
| my own tables *inside* a database others also use | a **schema** within it — a namespace the consumer owns |
The third is the interesting one, and the one asked about.
**Loose tables in a shared database is what `how-we-build` §4 warns against**, in as many words:
*contexts integrate through the record, never through a shared schema — today several domains
share one forty-five-table schema, which is why work belonging to one context keeps having to be
implemented in another.* That is not a style objection; it is the observed cost, already paid.
**A per-consumer schema inside a shared database keeps what the request wants and drops what §4
objects to.** The consumer gets tables in the same database — same connection, same backup, and
a cross-schema read is physically possible when it is genuinely needed. What it also gets is
ownership: its migrations touch its own namespace, two modules cannot collide over a table name,
and revoking the grant drops the schema rather than guessing which tables belonged to whom.
Which leaves the fault §4 names — *joining across a boundary* — possible but no longer
accidental. That is the honest position: physically co-located, logically owned, and a
cross-context join is now a thing somebody has to deliberately write rather than the path of
least resistance.
**And it makes revocation answerable**, which the whole-database version was not. Uninstalling a
consumer drops its schema. Nothing has to work out which of forty-five tables were whose.
## A migration belongs to the consumer and runs on the provider
A game defines migrations. They run against the database the store granted **it**. So the