aa767d17a89630f839cf6fe24a2f8451d56ff686
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
aa767d17a8 |
011: a module is granted only what it exclusively owns
Reconsidered by the operator — maybe shared databases should not be allowed at all — and the stricter version is better and goes further than the schemas it replaces. 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. That is how-we-build §4 taken at its word rather than at its letter. The permissive version — a per-consumer schema, revocable, with cross-context joins possible but deliberate — kept the letter and left the temptation. A boundary that is merely inconvenient to cross is a boundary that gets crossed. The cost is cross-module reporting, and it is the point rather than a regrettable side effect: anything wanting to know what several modules hold consumes their events or calls their interface. That is §4's whole argument, and the mesh already has both mechanisms. What gets harder is precisely the thing that was making work belonging to one context keep having to be implemented in another. And it is the first clear instance of what this effort has been hunting — what the design DELETES rather than adds. Grant kinds collapse to one: an exclusive resource. With them go the question of who owns which table, the guessing at revocation time, cross-module migration ordering, and a class of permission modelling a shared store would otherwise need. One thing it does not answer, recorded because it could make the rule unworkable: the mesh's own registry is read directly by many things today, and under this rule they consume events or call tools instead. Achievable in principle. Whether EVERY current consumer can be served that way is unchecked, and should be before this becomes a decision. |
||
|
|
4c8515507a |
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. |
||
|
|
fa9889536c |
011: tools have a different audience, migrations cross the edge, provisioning is early
Three additions, and the third kills an assumption. Tools are the most common content in the catalogue — 56 of 126 modules, more than carry a service — and they survive the split without fitting either half. A tool is not an artifact and not node state; it is a contract the mesh publishes on a module's behalf, and what consumes it is an AGENT rather than another module. That is a second audience the design has not described. Whether it is one relation with two audiences or two relations is cheap to decide now and expensive later. A migration belongs to the CONSUMER and runs on the PROVIDER. A game's migrations run against the database the store granted it: owned by the consumer, hosted inside something it does not control, ordered after the provisioning edge because there is nothing to migrate until the grant exists, and scoped to that grant. Ownership crosses the edge, which nothing in provides and requires expresses — and it gives a consumer's own install an internal order, provisioned then migrated then started, that depends on an edge rather than on its contents. And provisioning is EARLY, not late. The assumption worth killing is that it is something the control plane does for consumers once a mesh is running. The mesh's own registry database is provisioned before there is a mesh, and so is its virtual host on the broker: the store runs from the carried bundle, a database is created in it, the mesh's own schema is applied, and only then does a control plane exist. Steps two and three happen before there is a mesh to do them, so provisioning is part of the bootstrap and part of what the bundle has to express. Which strains ADR 0043. The host applies declared state ON THIS MACHINE, and a database inside a running store is not a file or a unit. At bootstrap it is at least local — the store is on the same machine. Afterwards a consumer on one node provisioned from a store on another is the ordinary case and reaching it is not the host's job. The same operation is local at bootstrap and remote later, which is either two mechanisms or one with a tier boundary crossing inside it. Currently the sharpest unresolved thing in the effort. |
||
|
|
a3c7e7e1f1 |
011: providing is a facet, and the assignment is a third thing
Any hosted service can be a factory — an identity provider grants clients, an analytics service grants a tracking identity, a mail server grants mailboxes, an application platform grants a project that is several of those at once. Providing is a FACET a module may have, not a kind of module it is, which is the same conclusion this effort reached about services and applications arriving from the other direction. So `provider` stops being a category too. Two relational stores from different vendors both grant "a database" and are the sharpest possible test of the substitutability rule. They fail it completely — different protocol, dialect, driver, client library compiled into the consumer — so `database` stays a tag, now with two real providers rather than a thought experiment. The assignment is a third entity, recorded because the operator tried the alternative: modules were once node-agnostic and it did not survive. Several of a provider's properties belong to neither end — where its state lives, how it is reached, tuning derived from the machine's hardware, which instance serves a given consumer. Not the catalogue, because they differ per node; not the node, because they are about this module. A design with only modules and nodes has nowhere to put them, which is what node-agnostic ran out of. The current system already stores environment values per module AND per node, arriving the same way. Which answers the question asked directly: two nodes both run a store, so which serves a consumer? Neither obvious answer. Not the consumer naming a node — that is placement in the consumer's manifest, a game edited because a database moved. Not the consumer not caring — for presence it genuinely does not, for instantiation it cares permanently. What the consumer knows is the SCOPE of its own need: one instance shared across every instance of itself, or one each. That decides, and needs no node named. Then the mesh binds, and the binding is recorded on the assignment and is sticky — a resolver that re-derives which store serves a consumer will one day derive a different answer and relocate a database. |
||
|
|
13c6068874 |
011: the provider shape generalises, and two things differ inside it
The broker has all nine properties the store has. So do the object store and the image registry. A substrate service is a SERVICE PLUS A FACTORY, there are four of them, and the pattern generalises past the substrate: anything granting something per consumer has this shape. Two differences matter more than the similarity. The broker cannot be managed over the broker. ADR 0001 makes it the channel every node takes work from and ADR 0039 makes it the security boundary, so the module providing it is also the way modules are managed — a declaration cannot be delivered to it over itself. Nothing else has that property; the store is consumed by the control plane but is not how the control plane REACHES anything. This is what the carried bundle exists for: the broker is raised from what the host carries because there is no other way to raise it. A constraint on one module, not a general rule, and a schema with no way to say so hides it. And two modules of identical shape want opposite instance counts. The broker is one per mesh by decision. The store cannot be, because a node that must keep working while disconnected cannot depend on a database elsewhere. Which settles what cases.md left open: how many instances is NOT derivable from what a module is. It is a per-module decision, it has to be declared, and nothing in provides, requires or excludes says it. Revocation differs in consequence too. Dropping a database leaves data until something removes it — a leak, recoverable. Dropping a virtual host loses whatever was undelivered — silent, and not. Same relation, different blast radius, which argues for the provider deciding what revocation means rather than the mesh applying one rule. File renamed: it was never really about postgres. |