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.
8.8 KiB
A provider, all the way through
One module, worked out completely, because it is the case that breaks the tidy version. It looks like a container that runs a database and it is at least nine things.
Postgres is the example. The shape is not specific to it — see the end.
What it carries
1 — A supervised container. An image, a version, a data volume, and configuration. Easy, and the only part the phrase "a docker service" describes.
2 — Persistent state, and where it lives matters. The volume is the database. Moving this
module between nodes is not rescheduling; it is a migration. Almost nothing else in the
catalogue has this property, and nothing in provides / requires expresses it.
3 — Configuration that is partly the machine's. Tuning follows the hardware — memory, storage. A declaration generated centrally cannot know those, and research 012 says the machine's own values win on conflict. So some of this module's configuration is derived from the node it lands on.
4 — A tool surface. It exposes capabilities agents can call — query, list, provision. That is not a resource on a machine and not an artifact; it is a contract the mesh publishes on the module's behalf.
5 — A provisioner. The part that matters, and the one below.
6 — Its own bookkeeping. The provisioner must remember what it granted to whom, or it cannot revoke, rotate or clean up. So a module that provides state to others also holds state about its providing — and that state is not the database's data.
7 — An exposure decision, per node it runs on. Reachable from the machine only, from the local network, or publicly. That is a property of this assignment, not of the module: the same module on two nodes may answer differently.
8 — Credentials it generates. Per consumer, and they have to reach the consumer. Which means a provisioning edge carries a payload, and the payload is a secret.
9 — Health that is not "the container is up". A container running and a database accepting connections are different facts, and the second is the one anything cares about. This is the host's read-back rule, at a distance.
The provisioner is a second kind of edge
The tidy version of this effort said: a module provides names, a module requires names, that is the only edge. A small game wanting to store data shows it is not.
my-cool-game requires postgres # I speak its protocol, it must exist
my-cool-game requires a database FROM postgres, called my-cool-game
The first is presence: the thing exists and is reachable. Nothing is created; nothing flows
back. vscode requires terminal is this, and so is requires container-runtime.
The second is instantiation: the provider is asked to make something for this consumer, and hands back what the consumer needs to use it. A database, a user, a password, an address.
They differ in every way that matters:
| presence | instantiation | |
|---|---|---|
| creates something | no | yes, one per consumer |
| carries a payload back | no | credentials, an address |
| can be revoked | — | yes, and must be when the consumer goes |
| provider holds state about it | no | yes — who was granted what |
| satisfied by | anything providing the name | that provider, specifically |
This is the mesh's actual power, in the operator's words: a small game declares it wants a database and the mesh makes one. Nobody creates a user by hand, nobody pastes a connection string. That is worth being precise about rather than folding into a single relation because one relation is prettier.
What that costs the design
The proposal's "one kind of edge" is wrong, and the current system already knew: it has
dependencies for presence and requires: provision: for instantiation, with the resolver
deriving a presence edge from every instantiation edge. analysis.md recorded
that derivation as a convenience. It is not — it is the correct relationship between two
genuinely different relations.
So: two kinds of edge, one graph. Instantiation implies presence. Presence does not imply instantiation.
What still has no answer
Which postgres? A mesh with two of them, and a game that wants a database. Presence would be satisfied by either. Instantiation cannot be — the data will live in exactly one, and choosing wrongly is not a preference, it is the game's data in the wrong place, discovered later.
This is the who chooses between providers question from proposal.md, and the
worked example shows it is far sharper for instantiation than for presence. For terminal the
consumer genuinely does not care. For a database it cares permanently.
How many instances of postgres should exist? One per mesh is wrong — a node that must work
while disconnected cannot depend on a database elsewhere. One per node is wrong — the mesh's own
registry is one thing, not one per node. So the answer is per-module, and nothing in the schema
says it. This is cases.md axis how many instances, and postgres is the case that
proves it cannot be a global rule.
What happens to a grant when the consumer is removed? The game is uninstalled. Its database still exists, holding its data. Dropping it silently is data loss; keeping it forever is a leak. ADR 0043 says the host removes what it applied and no longer declares — but this is not on the host, it is inside another module's state, and the same reasoning does not obviously carry.
Where does node-derived configuration come from? (3) The control plane composes a declaration, and cannot know this machine's memory. Either the host fills in a blank the declaration leaves — which makes the host decide something, against ADR 0037 — or the control plane reads the node's inventory first and composes with it. The second is consistent and means a declaration is composed per node from what the node reported, which is a stronger claim than anything recorded so far.
The same shape, three more times
The message broker has all nine. So does the object store, and so does the image registry. They differ in what a consumer asks for — a database, a virtual host, a bucket, a repository — and in nothing structural.
A substrate service is a service plus a factory. That is the whole pattern, and there are four of them. It generalises past the substrate too: anything that grants something per consumer has this shape, and anything that does not is the simpler case.
But two things differ between them, and both matter more than the similarity.
The broker cannot be managed over the broker
ADR 0001 makes the broker the channel every node takes work from, and ADR 0039 makes it the security boundary — everything a node applies arrives through it.
So the module providing the broker is also the way modules are managed. A declaration cannot be delivered to it over itself, and reconfiguring it is done through the thing being reconfigured. Nothing else in the catalogue has that property; the store is consumed by the control plane but is not how the control plane reaches anything.
This is exactly what the carried bundle exists for (ADR 0038): the broker is raised from what the host carries, before there is a channel, because there is no other way to raise it. Recorded here because it is a constraint on one module, not a general rule, and a schema with no way to say so hides it.
Two modules of identical shape want different instance counts
The broker is one per mesh — a single point of failure and a single point of trust, by decision rather than by accident. The store cannot be: a node that must keep working while disconnected (ADR 0036) cannot depend on a database somewhere else.
Same nine properties, opposite answers. Which settles something the cases file left open: how
many instances is not derivable from what a module is. It is a decision per module, it has to
be declared, and nothing in provides, requires or excludes says it.
And revocation differs in consequence
Revoking a database leaves data behind until something drops it — a leak, and recoverable. Revoking a virtual host drops whatever had not been delivered — not recoverable, and silent.
The relation is the same and the blast radius is not, which is an argument for the provider deciding what revocation means rather than the mesh applying one rule to all of them.