Files
hq/02-DECISIONS/0162-a-merge-produces-a-tiered-plan-the-mesh-keeps.md
T

10 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
the mesh accepted 2026-10-01 jochen false 02-DECISIONS/0157-a-build-says-what-it-does-on-the-bus-as-it-happens.md

162. A merge produces a tiered plan the mesh keeps, and a module's dependencies are one relation in the catalogue

Context

A merge on the forge reaches the controller as an event, and the controller asks the build machine for what that merge changed. Until today that meant the modules whose recorded source is that repository; since this afternoon it also means everything standing on what moved (issue 186). Both are done inside the handler that received the event: it asks one build, waits for it, asks the next, and returns when the last is done. Three things followed from that shape on 2026-10-01:

  • The controller hears nothing else for the length of the work — twenty-five minutes for the runtime image and its forty-three dependents (issue 184).
  • A controller replaced mid-merge loses the rest of the merge: the redelivered announcement reads as history, and the dependents are asked by hand.
  • Nothing is deployed between builds. A merge that changes the build machine and something the build machine builds asks for both in order, but the second is built by whichever build machine is running — the old one, unless somebody pushed in between. The order the dependents are sorted in exists for the artifacts; it says nothing about what must be running.

And the knowledge the order is computed from is scattered: a manifest's build.on, the artifacts a build was made against, the repositories a build read, and the fact that every source-built module is built by the build machine, each read by a different function in the merge handler.

Decision

1. A module's dependencies are one relation in the catalogue. depends-on edges, each with the kind of dependency on it: stands-on (the module's artifact is built on the other's), packages (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 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 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 changed and everything reachable from them along depends-on edges, and sorts that set into tiers: tier 0 depends on nothing else in the set, tier 1 only on tier 0, and so on. The plan — the merge it answers, the tiers, and each module's state — is written to the store before any build is asked. The handler asks tier 0 and returns. Every build's outcome, taken in by the same handler that takes every outcome in, advances the plan it belongs to; a controller replaced mid-plan resumes it from the store.

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 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 has been applied by the machines running it — the machines' reports 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 machine that is running, and only then asks for the controller's build.

4. A plan is read where the mesh is read. status lists every open plan: the merge, the tier it is at of how many, what it is waiting for and since when; builds lists the asked beside the built. A plan that has waited past a bound is named red there, which is the first fact of issue 187's list.

Consequences

  • The merge handler returns in milliseconds; the receive loop is never held by a build again. Issue 184's remaining cause — a handler that waits for its own work — is removed rather than worked around; the bus's heartbeats stop being dropped under a merge.
  • A controller roll in the middle of a plan costs nothing: the plan is in the store and the asks are in the queue (mesh-controller 194).
  • A release across repositories is a plan whose edges cross repositories; the order a person kept in a work-order file is the order the tiers give. Issue 186's third fault is answered by the plan, not by a separate release record.
  • The explicit build --on <base> stays as the way to ask for the same plan by hand.
  • A module's build.on remains the one place a manifest states a dependency the store cannot see.

How this is checked

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
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
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
status lists open plans and names one that waits past the bound the status JSON test with a fixture plan
Live a catalogue merge touching a base and a dependent: the plan's tiers in status, the base rolled before the dependent is asked

Built and proven live, 2026-10-01

Progressive insight — 2026-10-01. The decision stands; these are the facts of its building.

Built in mesh-controller 197 (the relation, the plan record, the driver, status), 198 (plans), 199 (a built-by edge orders and gates but never widens — the first live plan had taken the whole catalogue along for a controller change; plans stop), 200. The first merge handled by the finished machinery, at 18:56Z, was a controller change and produced the plan this record describes: tier 0 the build machine; tier 1 the controller and the proxy that packages its source. The handler returned at once; the build machine was built, rolled, and the plan read tier 0 built; waiting for builder on novox to be applied until the machine reported; then tier 1 was asked, both built, and the plan read done — three minutes, read through the console with plans, the receive loop taking reports throughout. What the day between decision and proof taught is in issues 184, 186 and 188.

References