ADR 0135 made a step something the mesh derives for any module that prepares its state, which turned ADR 0052's reach into a fault: a module whose database is briefly unreachable would stop every module declared after it on that machine — the fault issue 011 already removed for every other shape, and the reason the catalogue migrates itself at start rather than in a step. A step now stops the rest of its own module and nothing else; an action still gates the machine, because genesis is a row of them and they belong to no module. What was not attempted is reported as skipped rather than left to be inferred from silence.
6.2 KiB
topic, status, date, deciders, reconstructed, extends
| topic | status | date | deciders | reconstructed | extends |
|---|---|---|---|---|---|
| what runs on it | accepted | 2026-09-28 | jochen | false | 02-DECISIONS/0052-a-step-that-runs-once-before-a-container.md |
136. A step gates its module, not the machine
Context
ADR 0052 made a run-once container a step the host runs to completion, and gave it the same reach a failed action has: it stops everything the declaration places after it. When the only steps on the mesh were a broker's seed and a forge's admin account, that reach was invisible — the thing after the step was the container the step existed for, in the same module.
ADR 0135 made a step something the mesh derives for any module that prepares its state, and that turns the reach into a fault. A module whose database is briefly unreachable now stops every module declared after it on that machine, for as long as it is unreachable.
The host already rejected this for every other shape, and says why in its own loop. From issue 011:
It used to stop at the first one, and that made one broken resource hold the whole machine hostage: a module declaring a package that does not exist meant every module ordered after it was never applied, for ever, and the mesh reported "failed" without saying that the rest had not been tried. A machine with one bad module and nine good ones ran none of the nine.
Everything is attempted and every failure reported — except an action and a run-once step, kept as the deliberate exceptions. So the mesh has two rules about the same question and the wider one is now reachable by any module that declares a schema.
And it deadlocks a case the catalogue already named. The catalogue migrates its own schema when it starts rather than in a step, and says why in its code: a schema step that had to reach the provider over the overlay would block the very apply that brings the overlay up. With a machine-wide gate that is exactly right — the step fails, the apply stops, the overlay module after it is never applied, and the next reconcile is blocked the same way. The module that most obviously wants a step could not have one.
Decision
A step gates its own module. A run-once container that does not complete stops the rest of that module's resources and nothing else. Every other module on the machine is attempted, as every other shape already is.
An action still gates the machine. Genesis is a row of actions, each making the next possible, and they belong to no module — there is nothing narrower for their reach to be.
What was not attempted is reported, not inferred from silence. A skipped resource appears in the machine's account of the apply as skipped, with the reason, because "not attempted" and "nothing to do" are different answers and only one of them is somebody's to fix.
A module is the part of a resource's identity before the first dot, which is how the mesh composes them. What the mesh declares in its own right — a guard, an opening, the adoption's own resources — belongs to no module, and its gate is therefore the machine's.
Options considered
- Leave the reach as it is. Rejected: it reintroduces, through a mechanism now derived for every module, exactly the fault issue 011 removed. A mesh where one module's unreachable database stops a machine converging is worse than one where that module alone is behind.
- Make preparation not a gate at all — run it and carry on. Rejected: then a version serves against a state nobody shaped, which is the whole of what ADR 0135 exists to prevent.
- Order every module's step before everything else on the machine, so a gate stops nothing that matters. Rejected: it inverts the order a module needs — its files and directories are declared before its step because the step reads them — and it would still stop later modules.
- Let a module declare how far its step reaches. Rejected: the answer is the same for every module, and a field would let one be wrong about it.
Consequences
The catalogue can move to a step. The reason it migrates at start — that a step blocks the apply that would make its provider reachable — stops being true: the step fails, that module waits, the overlay comes up, and the next reconcile prepares it. One shape for the whole mesh, which is what ADR 0135 asked for and could not have had.
A module can sit behind while the machine is otherwise current. That is the honest state and it is what the report now says. It also means a preparation that never succeeds is a module that never upgrades, quietly, until somebody reads the report — which is an argument for ADR 0134 rather than against this.
A module's resources must be ordered within the module for the gate to mean anything. They already are: the mesh composes a module's resources in the order its manifest declares them, and its own workload comes after the files it reads.
How this is checked
- A failed step stops its module and nothing else. A test with two modules: the one whose step failed does not start its workload, the other starts, and the error still says the failure gated something. It fails against the previous behaviour, which is how it was written.
- An action still stops the machine. The existing test for a failed action is unchanged, and a step with no module in its identity — which is what genesis carries — takes the same path.
- The report names what was skipped. Asserted in the same test, because a gate nobody can see is indistinguishable from a module that had nothing to do.