The 103-module rebuild ADR 0236 put down to the build agent came from a file of the catalogue's reference module, read as shared because its definition was not in the merge. Records the issue, corrects ADR 0236's open question as a progressive insight (the tiering was right; the build agent never widens a plan) and amends to-be 30's rule for what a merge changed.
4.7 KiB
status, opened, located-in, fixed-by, amended-design
| status | opened | located-in | fixed-by | amended-design | ||
|---|---|---|---|---|---|---|
| located | 2026-10-06 |
|
mesh-controller PR | 03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md |
278. A module held by no machine was read as shared code
Symptom
On 2026-10-06 a catalogue merge fixed two modules (the audit logger and the usage store), touched the
event handlers of six others, and changed one file of the catalogue's reference module, showcase.
The controller planned 103 modules in two tiers: the build agent alone in tier 0, everything else
built from the catalogue repository in tier 1. 88 of the builds came out byte-identical to the ones
before (no move, under ADR 0236),
and 15 were different. The bus was rebuilt among them, though nothing touched it.
ADR 0236 put this down to the build agent: "a change to the build agent rebuilds most of the catalogue", left open there. The build agent had not changed. Its directory was not among the merge's files.
Diagnosis
The controller's plans what-if, which saves nothing, reproduces it with one path: a merge changing
only modules/showcase/index.ts plans the same 103 modules, and one changing only the build agent's
manifest plans the build agent alone.
Shared code was inferred from the wrong fact. The controller reads a changed path as shared code, and rebuilds every module built from the repository, unless its directory is a module the mesh holds or the merge also changed that directory's definition (the rule from issue 252). The reference module has a definition, but no machine runs it and the catalogue does not hold it. This merge changed its code and left its definition alone, so its file read as shared. That rule cannot tell a module from a shared library: both are a directory beside the modules whose definition the merge did not touch. Whether a directory is a module is a fact of the repository at the commit, and only something that can read the repository can say it.
Why the build agent looked like the cause. Every module built from source is built by the build agent, so whenever the agent is in a plan it goes first. But a built-by edge orders a plan and never widens it: nothing depends on the agent in any other way, so a change to the agent, whether to its definition or its program, rebuilds the agent alone. It was in this plan only because a shared change rebuilds everything built from the repository, and the agent is built from it too.
The reference module stays. It is used: the controller's test suite parses it as the module that exercises every part of a definition, and to-be 18 and to-be 20 name it as the reference module.
Fix
- The forge's announcer says which directories are modules. For each merge it looks up every directory above a changed file, never the root, for a definition at the merge commit. It announces the ones that have one, and says that it looked. It says nothing when the file list was cut, when there are too many directories to look up, or when the forge cannot be asked.
- The controller believes it. A changed file inside such a directory is that module's business, whether or not the mesh holds the module and whatever the merge did to its definition. Only a file in no such directory is shared. From an announcer that says nothing, the old rule stands: it rebuilds too much rather than too little.
- The what-if takes the same list, so a plan can be read before a merge exactly as the merge will make it.
How it is checked
| Rule | Checked by |
|---|---|
| a module's directory is never shared code | the controller's test: a change inside a directory the announcer says is a module rebuilds nothing else, nested paths included; a directory with no definition is still shared; the root is never a module directory; an announcer that says nothing keeps the old rule |
| the announcer says it at the commit | the forge module's test: the directories above a merge's files, each looked up at the merge commit; a module told from a plain directory; past the bound, nothing said; a refused lookup is an error, not "no module" |
| the build agent never widens a plan | the controller's test over the real dependency relation: every edge to the build agent is built-by; a change to its definition or its program plans the agent alone, and what moved beside it comes after it |
| live | the next catalogue merge that touches a module no machine runs plans that change and nothing else; plans shows it |