ADR 0133 and 0134: who runs migrations, and the mesh saying what it applied
0133 — a module owns its migrations and the mesh owns when they run. A container declares what must run before it; the mesh derives the gated step from the resource it precedes, so the image, the environment and the credentials come from the one place they are described. The module owns the SQL, the dialect and the lock; the mesh owns the moment and refuses to start a version whose step failed. Per node, with no level: a step that ran once somewhere leaves every other machine ungated, and 'once, mesh-wide' is what holding a seat already means. 0134 — the pipeline is observable from a merge to an artifact and goes dark at the machine. What a node now runs, and what it refused, become facts under the control plane's own seat, emitted when what a machine runs changes rather than on every convergence pass. Design 32's lifecycle carries both; issue 133 points at them as what ends the matter it opened.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
layer: to-be
|
||||
status: proposed
|
||||
code: []
|
||||
updated: 2026-09-27
|
||||
updated: 2026-09-28
|
||||
decisions:
|
||||
- 02-DECISIONS/0126-a-module-declares-its-own-seats.md
|
||||
- 02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md
|
||||
@@ -12,6 +12,8 @@ decisions:
|
||||
- 02-DECISIONS/0112-a-module-definition-names-no-node-mesh-or-path.md
|
||||
- 02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md
|
||||
- 02-DECISIONS/0129-a-seat-carries-the-protocol-of-its-role.md
|
||||
- 02-DECISIONS/0133-a-module-owns-its-migrations-and-the-mesh-owns-when-they-run.md
|
||||
- 02-DECISIONS/0134-the-mesh-says-what-it-applied.md
|
||||
---
|
||||
|
||||
# 32. What a module declares, and what the bus makes of it
|
||||
@@ -258,10 +260,26 @@ queue.
|
||||
and publishes it last-per-subject. A node that was away gets exactly the current one, never a
|
||||
queue of superseded ones, and a replayed older one is refused by sequence.
|
||||
|
||||
**A version that needs its store prepared prepares it first.** A container may declare steps to run
|
||||
before it — the same container, to completion, with different arguments — and the mesh derives them as
|
||||
run-once resources placed ahead of it, so a schema change and the code that needs it arrive together or
|
||||
not at all. The module owns what the step does; the mesh owns when it runs, and refuses to start the
|
||||
container if it failed ([ADR 0133](../../02-DECISIONS/0133-a-module-owns-its-migrations-and-the-mesh-owns-when-they-run.md)).
|
||||
Per node, because a node converges without waiting on its neighbours; a step that must happen once
|
||||
mesh-wide belongs to a module holding a seat, which is what one holder on record already means.
|
||||
|
||||
**Applying is reported to a role.** The host applies and reports to the `mesh-controller` seat —
|
||||
not to an address it was given at genesis. Held and retried while the store restarts
|
||||
([ADR 0083](../../02-DECISIONS/0083-one-push-leaves-the-mesh-consistent.md)).
|
||||
|
||||
**And the mesh says what it applied.** A report is control traffic only the control plane reads, so the
|
||||
chain above went dark at the moment it touched a machine: nothing said which version a machine now runs,
|
||||
or that it refused to. The control plane states those as facts under its own seat's namespace, when what
|
||||
a machine runs changes rather than on every convergence pass, and anything that cares subscribes the way
|
||||
the catalogue subscribes to `built` ([ADR 0134](../../02-DECISIONS/0134-the-mesh-says-what-it-applied.md)).
|
||||
The facts are second-hand by design — one emitter, one ordering — and a machine that cannot reach the bus
|
||||
produces none, so absence is not health.
|
||||
|
||||
What disappears across that chain is every address. No webhook URL, no registered callback, no
|
||||
"which node is the builder on", no controller endpoint baked into a joining node. That is the
|
||||
class of bug
|
||||
@@ -439,6 +457,10 @@ it is the residue of a question the rest of §8 answers and the part a fingerpri
|
||||
**Whether a module may declare a seat it does not itself claim** — the contract as one thing, the
|
||||
implementation as another, which is how two competing implementations would ever exist.
|
||||
|
||||
**Whether a container should have a readiness notion.** Only an action carries `verify`, so a step that
|
||||
must run once a service *answers* — seeding through its own API — cannot be declared at all. Named here
|
||||
because the steps above make the gap obvious, not because they caused it.
|
||||
|
||||
**Whether `consumes` naming another module couples too tightly.** It is kept here deliberately —
|
||||
an event's provenance is its meaning — but a consumer of `billing.order.placed` does depend on
|
||||
billing existing under that name.
|
||||
@@ -447,6 +469,11 @@ billing existing under that name.
|
||||
|
||||
- **A manifest holds no subject.** A catalogue test: no manifest contains a string matching the
|
||||
subject grammar. The rule is worthless if it is followed by convention.
|
||||
- **A derived step is the container it precedes.** A composition test: the step's image, environment,
|
||||
volumes and network equal that container's, so the two cannot drift — which is the failure the
|
||||
hand-written kind has, three times over in the catalogue today.
|
||||
- **A convergence that changed nothing says nothing.** Two identical reports, one emitted fact: what is
|
||||
guarded against is a fact per minute per machine, which is a stream nobody reads.
|
||||
- **Permissions are exactly the three namespaces.** A composition test per module: the derived
|
||||
permission set equals what its declaration implies, and a hand-written addition to it fails.
|
||||
- **A sender cannot read the queue it writes to.** A bed: a module declaring `uses` is refused
|
||||
|
||||
Reference in New Issue
Block a user