Issue 300: a new module is not built at its merge

The change plan said a merge adding a module builds it; the merge built
nothing, so the module stayed unregistered until a hand build.
This commit is contained in:
jochen
2026-10-07 22:39:56 +02:00
parent dda850863f
commit 9b573f4ca0
2 changed files with 95 additions and 0 deletions
@@ -0,0 +1,50 @@
---
status: located
opened: 2026-10-07
located-in: [mesh-controller cmd/mesh-controller (upgrades.go, the merge handler)]
fixed-by: mesh-controller PR #122
amended-design:
---
# 300. A new module is not built at its merge
## Symptom
A pull request to the catalogue added one module, `systemd-resolved` (ADR 0247). Its delivery plan,
posted with the pull request's check, said:
> builds new: modules/systemd-resolved, sent nowhere
At the merge, 20:14:37 UTC on 2026-10-07, the controller said:
> novox/mesh-catalog merged into main (…); it changed nothing any module the mesh holds is built from
The module was not built and not registered. `assign` then refused it with "no module of that name".
It took a hand `mesh-controller.build` to register it. So the plan said one thing and the merge did
another, with nothing on the mesh noting the difference.
## Why it is a design issue
ADR 0238 makes the change plan the planner's one answer for a pull request's check, the merge gate and
the merge. The plan counts a new module's directory as something the merge builds. The merge handler
read the same answer (`touchedBy`'s `added`) and dropped it: it only walks modules the catalogue already
holds, and a new one is not held until it is built. The code said so outright: "a new module, which a
check judges before it merges and a merge does not build". The take-in refuses a build off the trunk
with "merge it, and the merge builds it". So one part of the controller promised the build and another
never made it.
Assigning a module is a person's act, and it needs the module registered first. A merge that leaves a
new module unregistered turns each new module into two acts by hand, and the plan says it is one.
## How it is checked
- `TestAMergeBuildsTheNewModuleItAdds` replays this case. A held catalogue module, and a merge whose
changed files are only a new module's directory: the merge asks the build seat for that directory, at
the branch merged into, from the repository as the mesh spells it, and opens no plan. It fails on the
commit before the fix.
- `TestAMergeBuildsItsNewModuleBesideItsPlan`: a merge that changes a held module and adds a new one
plans the held one and asks for the new one beside the plan. A merge that adds no module asks for
nothing.
The trail is in [01-diagnosis.md](01-diagnosis.md). This is a core issue: at resolution it names its
replay, or says in `replay-none:` why none is possible (ADR 0237).
@@ -0,0 +1,45 @@
# 300 — diagnosis
## 2026-10-07: where the merge decides what to build
The text "changed nothing any module the mesh holds is built from" is printed in one place: the
controller's merge handler (`SourceMoved`), when the merge moves no held module. The handler takes the
catalogue modules built from the merged repository and branch (`mergeCandidates`). It narrows them to
the ones whose directory a changed file is in (`touchedBy`). A plan is opened only for those and for
whatever stands on them.
`touchedBy` is the one place the mesh maps a changed file onto a module (ADR 0238). It returns three
answers: the touched modules, the `added` directories (a module that no module of the mesh is known
from) and the `unread` files. The change plan and the pull request's check take `added` as "new" and say
it is built. The merge handler went through `whatTheMergeTouched`, which keeps only the first answer.
Ruled out:
- **The forge's announcement.** The plan for the same pull request found the new directory, from the same
paths and module directories the merge carries (issue 278). `added` was computed correctly; it was just
not used.
- **The take-in.** A hand build of the directory was taken in and registered as usual. Nothing between a
build's outcome and registration needed changing.
## The fix
At a merge into the branch its repository's modules follow, the handler asks the build seat for every
added directory: at that branch, from the repository spelled and found on the seat as the modules
already built from it are, and without waiting (`askNewModules`). The build's take-in registers the
module, as it does for a hand `build`. Nothing is sent: no machine is assigned a new module, and
assigning one stays a person's act.
The new module is outside the merge's plan. A plan walks modules the catalogue holds, tier by tier, and
sends each built one to the machines running it. A new module has no machine and nothing standing on it,
and the plan's ask for a module the catalogue does not hold fails the plan.
A merge read as history, or of a branch that no module follows, builds no new module either. The new
module's build follows the same candidates as everything else.
## What it does not cover
- **A repository the mesh holds nothing from.** There is no module whose source spelling and seat the new
module could use, so nothing is built. That is unchanged: "nothing the mesh holds reads it".
- **The catch-up of merges the bus did not hand over** (issue 266) judges a missed merge by the held
modules it would move. A merge that only adds a module, and that the bus skipped, is still not built
by the catch-up. Counting `added` there would ask again on every pass until the build landed.