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.
This commit is contained in:
2026-08-26 23:02:52 +02:00
parent a3c7e7e1f1
commit fa9889536c
2 changed files with 65 additions and 0 deletions
@@ -85,6 +85,23 @@ Nothing replaces it, because it was never one concept:
| prerequisite-* | an **edge** in the graph | the catalogue | | prerequisite-* | an **edge** in the graph | the catalogue |
| verify, verifiers | the host's **read-back** | the host | | verify, verifiers | the host's **read-back** | the host |
## Tools are a facet, and their consumer is not a module
The most common content in the catalogue: 56 of 126 modules carry a tool surface, more than
carry a service.
It survives the split, and it does not fit either half cleanly. 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**, not another module.
That is a second audience, and the design has only described one. A module `provides` things
other modules require; a module also `provides` things agents call. Same word, different
consumer, different lifecycle — a tool appears when the module is assigned somewhere and
disappears when it is not, and nothing in the graph edges says so.
Whether that is one relation with two audiences or two relations is undecided, and it is the
kind of question that is cheap now and expensive later.
## What is lost, and should not be ## What is lost, and should not be
**Detection.** Features are detected from the directory rather than declared, and the reason is **Detection.** Features are detected from the directory rather than declared, and the reason is
@@ -122,6 +122,54 @@ The pieces that follow, none of them settled here:
- **Moving a binding moves data.** Whatever the mechanism, changing it is a migration and not a - **Moving a binding moves data.** Whatever the mechanism, changing it is a migration and not a
configuration change, and a design that lets it look like the latter will lose something. configuration change, and a design that lets it look like the latter will lose something.
## Provisioning is early, not late
An assumption worth killing: that provisioning is something the control plane does for
consumers once a mesh is running.
**The mesh's own registry is a provisioned database.** So is its virtual host on the broker.
Neither exists until something creates them, and nothing in the mesh works until they do. The
order is:
```
1 the store runs from the bundle the host carries
2 a database is created in it a provisioning step
3 the mesh's own schema is applied a migration, against that database
4 the control plane starts and only now is there a mesh
5 everything else is provisioned the ordinary path
```
Steps 2 and 3 happen **before there is a mesh to do them**. So provisioning is not a
control-plane service that consumers use; it is part of the bootstrap, and part of what the
carried bundle has to be able to express.
**Which strains what a declaration is.** [ADR 0043](../../02-DECISIONS/0043-a-declaration-is-an-ordered-list-of-owned-resources.md)
has the host applying *declared state on this machine*. A database inside a running store is not
a file or a unit — and at bootstrap it is, at least, local: the store is on the same machine as
the host applying the bundle.
Later it is not. A consumer on one node provisioned from a store on another is the ordinary
case, and reaching it is not the host's job. So the same operation is local at bootstrap and
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.
## 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
migration is:
- **owned** by the consumer — it is that module's schema, versioned with that module;
- **hosted** by the provider — it runs inside something the consumer does not control;
- **ordered** after the provisioning edge — there is nothing to migrate until the grant exists;
- **scoped** to the grant — the consumer's migrations touch its database and no other.
Ownership crosses the edge, which nothing in *provides* and *requires* expresses. And it is the
*action* category from [`features.md`](features.md) made concrete: not an artifact, not node
state, and not something the host can apply, because the thing it changes is not the machine.
It also gives a consumer's own install an internal order — **provisioned, then migrated, then
started** — that depends on an edge rather than on the module's contents.
## What still has no answer ## What still has no answer
**How many instances of postgres should exist?** One per mesh is wrong — a node that must work **How many instances of postgres should exist?** One per mesh is wrong — a node that must work