ADR 0162: the three kinds of dependency, where the edges come from, and the one real cycle

This commit is contained in:
2026-10-01 17:53:57 +02:00
parent b0de267301
commit 82496536cd
@@ -38,7 +38,25 @@ kind of dependency on it: `stands-on` (the module's artifact is built on the oth
module's build reads the other's repository), `built-by` (the module is built by the holder of the module's build reads the other's repository), `built-by` (the module is built by the holder of the
build-machine seat), and `declared` (a manifest's `build.on`). The relation is answered by one query build-machine seat), and `declared` (a manifest's `build.on`). The relation is answered by one query
of the catalogue — the controller's inventory today, the catalogue seat's tool when something outside of the catalogue — the controller's inventory today, the catalogue seat's tool when something outside
the controller needs it — and nothing else computes an edge. the controller needs it — and nothing else computes an edge. The edges are derived from facts
recorded at two moments and written by nobody: registration records the manifest (`declared`, and
`built-by` for anything with a source), a build's take-in records what the image was built on and
which repositories it read (`stands-on`, `packages`). A module's first build places it by its
declared edges alone; from its second it is placed by what was true.
**The kinds are three dependencies, not one.** A *code* dependency — B packages A's source — means
B is rebuilt whenever A changes, in the same tier: B's build needs nothing of A's first. A *build*
dependency — B stands on A's artifact, or declares it — means B is rebuilt after A is *built*, the
next tier, and nothing need be deployed in between. A *runtime* dependency — B is built by A — means
B is rebuilt only after A is built *and running*, the next tier with a gate on the machines' reports.
One cycle is real and resolved by the kinds themselves: the runtime image is built by the build
machine, and the build machine stands on the runtime image; the image comes first, built by the
build machine that is running, which is the only one there could be — a `built-by` edge never orders
a module after a build machine that stands on it. A provision is not a dependency of this relation:
a consumer binds to its provider through what the push renders, and a change to the provider's image
changes nothing in the consumer's; a consumer whose build does read a provider's source declares it.
"A was deployed, so restart B" is the push's domain — B is replaced when what it reads changed — and
not the plan's.
**2. A merge produces a plan, and the plan is a record.** The controller takes the modules the merge **2. A merge produces a plan, and the plan is a record.** The controller takes the modules the merge
changed and everything reachable from them along `depends-on` edges, and sorts that set into tiers: changed and everything reachable from them along `depends-on` edges, and sorts that set into tiers:
@@ -50,8 +68,8 @@ outcome in, advances the plan it belongs to; a controller replaced mid-plan resu
**3. A tier is done when it is built, and when what the next tier needs from it is running.** A **3. A tier is done when it is built, and when what the next tier needs from it is running.** A
module whose roll-out policy says *roll out* is sent to its machines when it moves, as today. The next module whose roll-out policy says *roll out* is sent to its machines when it moves, as today. The next
tier is asked only once every module in this tier is built and every rolled-out module of this tier tier is asked only once every module in this tier is built and every rolled-out module of this tier
that a later tier is `built-by` or `packages` has been applied by the machines running it — the that a later tier is `built-by` has been applied by the machines running it — the machines' reports
machines' reports say so. A module whose policy says *record* is built and not waited for. So a merge say so. A module whose policy says *record* is built and not waited for. So a merge
touching the build machine and the controller builds the build machine, waits until it is the build touching the build machine and the controller builds the build machine, waits until it is the build
machine that is running, and only then asks for the controller's build. machine that is running, and only then asks for the controller's build.
@@ -78,7 +96,8 @@ A plan that has waited past a bound is named red there, which is the first fact
| Rule | Checked by | | Rule | Checked by |
|---|---| |---|---|
| Dependencies are one relation, each edge with its kind | an inventory test over a fixture catalogue: a runtime image, a module on it, a module packaging the controller's source, and the build machine; the four kinds come back from one call | | Dependencies are one relation, each edge with its kind | an inventory test over a fixture catalogue: a runtime image, a module on it, a module packaging the controller's source, and the build machine; the four kinds come back from one call |
| A merge's set is sorted into tiers, each depending only on earlier ones | a unit test on the tiering: the runtime, a module on it, a plugin on that, and an unrelated module left out | | A merge's set is sorted into tiers along the three kinds: a code dependency in the same tier, a build dependency after its base is built, a runtime dependency after the build machine; the build machine's own base first | a unit test on the tiering over the mesh's real shape: the runtime image, the build machine on it, modules built by it, a plugin declared on one, the proxy packaging the controller, an unrelated module left out; a cycle is one last tier and said |
| Only a runtime dependency gates on deployment, and only for a module that rolls out | the same test's gate cases |
| The plan is written before any build is asked, and the handler returns | a controller test: a merge announcement produces a plan row with its tiers and one asked build per tier-0 module, and the handler is back before any outcome | | The plan is written before any build is asked, and the handler returns | a controller test: a merge announcement produces a plan row with its tiers and one asked build per tier-0 module, and the handler is back before any outcome |
| An outcome advances its plan; a complete tier asks the next; a tier with a rolled-out base waits for the machines' reports | store-backed tests over a two-tier plan: the first outcome marks built; the tier's roll-out gate holds until the report; the next tier is asked after | | An outcome advances its plan; a complete tier asks the next; a tier with a rolled-out base waits for the machines' reports | store-backed tests over a two-tier plan: the first outcome marks built; the tier's roll-out gate holds until the report; the next tier is asked after |
| A controller restarted mid-plan resumes it | a test that opens a plan, drops the handler, and advances from the store alone | | A controller restarted mid-plan resumes it | a test that opens a plan, drops the handler, and advances from the store alone |