Reconcile: adopt initialization's consolidated HQ as canonical, re-home this session's new work #24
@@ -159,40 +159,66 @@ 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
|
||||
## Several modules, one database — and the case for refusing
|
||||
|
||||
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.
|
||||
Asked, then reconsidered by the operator: *maybe we should not allow it.*
|
||||
|
||||
**A grant is not always a whole database.** A provider offers *kinds* of grant, and the kind is
|
||||
what separates these:
|
||||
The permissive version was a per-consumer **schema** inside a shared database — its own
|
||||
namespace, its own migrations, revocable by dropping the schema, with a cross-context join
|
||||
possible but deliberate.
|
||||
|
||||
| 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 stricter version is better, and it goes further than schemas.**
|
||||
|
||||
The third is the interesting one, and the one asked about.
|
||||
> **A module is only ever granted a resource it exclusively owns.**
|
||||
|
||||
**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.
|
||||
No shared writes. And **no read-only role on another module's database either** — reading
|
||||
another context's tables couples you to its layout exactly as firmly as writing them does, and
|
||||
the coupling is harder to see because nothing breaks until the owner changes a column.
|
||||
|
||||
**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.
|
||||
That is what [`how-we-build`](../../00-META/how-we-build.md) §4 already says: *contexts integrate
|
||||
through the record, never through a shared schema.* The permissive version kept the letter of it
|
||||
and left the temptation in place, and the path of least resistance wins eventually. A boundary
|
||||
that is merely inconvenient to cross is a boundary that gets crossed.
|
||||
|
||||
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.
|
||||
### What it costs
|
||||
|
||||
**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.
|
||||
**Cross-module reporting.** Anything wanting to know what several modules hold can no longer
|
||||
join across them. It consumes their events, or calls their interface, and neither is as
|
||||
immediate as a query.
|
||||
|
||||
That cost is the point rather than a regrettable side effect — it is §4's whole argument, and
|
||||
the mesh already has both mechanisms: an event stream every context publishes to, and a tool
|
||||
surface every module exposes. What gets harder is the thing that was making work belonging to
|
||||
one context keep having to be implemented in another.
|
||||
|
||||
**One more connection per consumer.** A dozen modules means a dozen databases rather than a
|
||||
dozen schemas in one. For a relational store this is unremarkable; it is worth stating only so
|
||||
nobody discovers it as a surprise.
|
||||
|
||||
### What it deletes
|
||||
|
||||
The effort has been looking for what the design *removes* rather than adds, and this is the
|
||||
first clear instance:
|
||||
|
||||
- **Grant kinds.** There is one — an exclusive resource. No schema grants, no read roles, no
|
||||
scoping rules for who may see what inside a shared thing.
|
||||
- **The question of who owns which table**, and with it the guessing at revocation time.
|
||||
- **Cross-module migration ordering.** Two modules migrating one database need their migrations
|
||||
ordered against each other. Exclusive ownership means a module's migrations are ordered only
|
||||
against itself.
|
||||
- **A whole class of permission modelling** that a shared store would otherwise need.
|
||||
|
||||
### What it does not answer
|
||||
|
||||
**The mesh's own registry is read by many things.** Under this rule they cannot read its tables,
|
||||
so they consume its events or call its tools. That is achievable and it is a real change from
|
||||
how the mesh works today, where reading the registry directly is ordinary — and it is the same
|
||||
change [ADR 0037](../../02-DECISIONS/0037-the-host-applies-it-does-not-decide.md) already forces
|
||||
on the host for unrelated reasons.
|
||||
|
||||
Whether *every* consumer of the registry can be served by events and an interface is not
|
||||
established here. It is the one thing that could make this rule unworkable, and it should be
|
||||
checked against real consumers before the rule is recorded as a decision.
|
||||
|
||||
## A migration belongs to the consumer and runs on the provider
|
||||
|
||||
|
||||
Reference in New Issue
Block a user