Each issue is resolved with its fix and a replay or the reason none exists, or re-checked against main and the live mesh on 2026-10-08.
76 lines
5.3 KiB
Markdown
76 lines
5.3 KiB
Markdown
---
|
|
status: resolved
|
|
opened: 2026-10-06
|
|
located-in: [mesh-controller cmd/mesh-controller, mesh-catalog modules/gitea]
|
|
fixed-by: novox/mesh-controller PR #93 (0090bf6), novox/mesh-catalog PR #96 (1ba2c05)
|
|
replay-none: opened and fixed before ADR 0237 made a replay a condition of resolving; the fix's own tests, named above, guard it, but none was laid over the commit before the fix and registered, and doing so now would be a new test written after the fact rather than a replay
|
|
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 |
|
|
|
|
## Resolved — 2026-10-08
|
|
|
|
Both pull requests are merged (2026-10-06). The rule went further a day later: issue 280's fix and
|
|
ADR 0238 made a file in no module's directory touch nothing at all, and the controller maps a change
|
|
onto modules in one place, `touchedBy`, for the merge handler, the what-if and the merge gate alike.
|