|
|
|
@@ -0,0 +1,106 @@
|
|
|
|
|
---
|
|
|
|
|
topic: what runs on it
|
|
|
|
|
status: accepted
|
|
|
|
|
date: 2026-09-28
|
|
|
|
|
deciders: jochen
|
|
|
|
|
reconstructed: false
|
|
|
|
|
extends: 02-DECISIONS/0052-a-step-that-runs-once-before-a-container.md
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# 136. A step gates its module, not the machine
|
|
|
|
|
|
|
|
|
|
## Context
|
|
|
|
|
|
|
|
|
|
[ADR 0052](0052-a-step-that-runs-once-before-a-container.md) 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](0135-a-module-version-prepares-its-state-before-it-runs.md) 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](../04-ISSUES/011-one-broken-module-blocks-every-other/00-report.md):
|
|
|
|
|
|
|
|
|
|
> 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
|
|
|
|
|
|
|
|
|
|
1. **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.
|
|
|
|
|
2. **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.
|
|
|
|
|
3. **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.
|
|
|
|
|
4. **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](0134-the-mesh-says-what-it-applied.md) 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.
|
|
|
|
|
|
|
|
|
|
## References
|
|
|
|
|
|
|
|
|
|
- [ADR 0052](0052-a-step-that-runs-once-before-a-container.md) — the step this narrows
|
|
|
|
|
- [ADR 0135](0135-a-module-version-prepares-its-state-before-it-runs.md) — what made the reach reachable
|
|
|
|
|
- [issue 011](../04-ISSUES/011-one-broken-module-blocks-every-other/00-report.md) — the same fault, removed once already
|
|
|
|
|
- [ADR 0134](0134-the-mesh-says-what-it-applied.md) — how a module left behind becomes visible
|
|
|
|
|
- mesh-host `internal/apply` — the loop whose own comment argued this case for every other shape
|