Issue 278: a module held by no machine was read as shared code

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.
This commit is contained in:
jochen
2026-10-06 19:57:21 +02:00
parent 62d4b1e5d4
commit 0333aac134
3 changed files with 92 additions and 1 deletions
@@ -225,6 +225,20 @@ not say which files went, marks the module deleted in its plan, which goes on.
- **A change to the build agent rebuilds most of the catalogue** (its modules are built on what it
builds), which is how the bus came to be rebuilt by a merge that did not touch it. Whether that tiering
is right is left open here; a rebuild that changes nothing is at least no longer a move of the bus.
> **Progressive insight — 2026-10-06.** The open question rested on a wrong cause. This consequence
> said "a change to the build agent rebuilds most of the catalogue", and the measurement in decision 4
> said "the catalogue merge that rebuilt 103 modules for a change to the build agent". That merge did
> not change the build agent. It changed a file of the catalogue's reference module, which no machine
> runs. Its definition was not in the merge, so the controller read the file as shared code and
> rebuilt everything built from the repository
> ([issue 278](../04-ISSUES/278-a-module-held-by-no-machine-was-read-as-shared-code/00-report.md)).
> The build agent stood in tier 0 only because everything else is built by it. A built-by edge
> orders a plan and never widens it, so a change to the build agent rebuilds the build agent alone.
> The controller's test checks this over the real dependency relation. The open question is answered:
> the tiering was right. What was wrong is now fixed: the forge's announcer says which directories
> hold a module at the merge commit, and only a file in none of them is shared. The measurement
> stands as a count; only its cause was wrong. Everything this record decided stands.
- **Thirteen modules still wait for a person**, each saying why in the catalogue or by its data; `status`
lists the machines behind them and `push <machine>` walks them, as before. A person who wants another
held says `upgrade <module> record --why …`.
@@ -2,7 +2,7 @@
layer: to-be
status: proposed
code: []
updated: 2026-10-05
updated: 2026-10-06
decisions:
- 02-DECISIONS/0221-a-push-sends-no-build-a-policy-or-a-plan-holds-back-except-to-the-machine-it-names.md
- 02-DECISIONS/0218-a-plan-sends-grants-before-code-rolls-out-one-machine-first-and-a-newer-merge-takes-over-an-older-plan.md
@@ -150,6 +150,15 @@ What a merge changed is read from the forge whole, page by page
module directory the mesh does not hold yet is that module's own, not shared code, when its definition is
among the changed paths.
Revision, [issue 278](../../04-ISSUES/278-a-module-held-by-no-machine-was-read-as-shared-code/00-report.md)
(2026-10-06). Whether a directory is a module is a fact of the repository at the merge commit, and the
forge's announcer says it. With each merge it lists the directories holding the changed files that hold a
definition at that commit. A changed path in one of them is that module's own, whether or not the mesh
holds the module and whether or not its definition changed. Only a path in no such directory is shared
code. From an announcer that does not list them, the rule above stands. A change to the build agent
rebuilds the build agent alone: what it builds is ordered after it in a plan, never added to one for its
sake.
## What a push does not send (2026-10-05)
Revision, [ADR 0221](../../02-DECISIONS/0221-a-push-sends-no-build-a-policy-or-a-plan-holds-back-except-to-the-machine-it-names.md).
@@ -0,0 +1,68 @@
---
status: located
opened: 2026-10-06
located-in: [mesh-controller cmd/mesh-controller, mesh-catalog modules/gitea]
fixed-by: mesh-controller PR #93, mesh-catalog PR #96
amended-design: 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](../../02-DECISIONS/0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md)),
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](../252-a-merges-changed-modules-were-read-wrong/00-report.md)).
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 |