From 0333aac13435af05b1daf7ed03a888d4eaa4c28c Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 6 Oct 2026 19:57:21 +0200 Subject: [PATCH] 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. --- ...n-itself-and-so-it-rolls-out-unattended.md | 14 ++++ .../30-the-mesh-updates-itself-on-a-push.md | 11 ++- .../00-report.md | 68 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 04-ISSUES/278-a-module-held-by-no-machine-was-read-as-shared-code/00-report.md diff --git a/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 b/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 index 1e392ce..464e28d 100644 --- a/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 +++ b/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 @@ -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 ` walks them, as before. A person who wants another held says `upgrade record --why …`. diff --git a/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md b/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md index 7c92650..77b3607 100644 --- a/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md +++ b/03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md @@ -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). diff --git a/04-ISSUES/278-a-module-held-by-no-machine-was-read-as-shared-code/00-report.md b/04-ISSUES/278-a-module-held-by-no-machine-was-read-as-shared-code/00-report.md new file mode 100644 index 0000000..78b0096 --- /dev/null +++ b/04-ISSUES/278-a-module-held-by-no-machine-was-read-as-shared-code/00-report.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 |