From 2221be11a6efa6733131cb66f0f61826ab116956 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 6 Oct 2026 22:54:09 +0200 Subject: [PATCH] =?UTF-8?q?ADR=200238:=20a=20commit=20is=20the=20build=20a?= =?UTF-8?q?t=20hand=20=E2=80=94=20one=20commit,=20one=20change=20plan,=20c?= =?UTF-8?q?hecked=20off=20the=20trunk=20and=20published=20only=20on=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turning the merge check on for every repository showed it asked the wrong questions: a repository chose whether it was checked, the gate mapped a change onto modules its own way, the shared-code rule rebuilt 103 modules for a root script, and nothing kept a commit off the trunk from becoming a module's version. Records the operator's decisions, narrows ADR 0237 decision 4, revises to-be 45 §9 and Phase 5 and to-be 30, closes issue 280's left-open, and gives this repository its own merge-check.sh. --- ...runs-before-it-merges-on-the-build-seat.md | 8 + ...-off-the-trunk-and-published-only-on-it.md | 186 ++++++++++++++++++ 02-DECISIONS/README.md | 1 + .../30-the-mesh-updates-itself-on-a-push.md | 10 + .../45-a-core-that-cannot-fail-silently.md | 35 ++++ .../00-report.md | 5 + AGENTS.md | 4 +- merge-check.sh | 11 ++ 8 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md create mode 100644 merge-check.sh diff --git a/02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md b/02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md index 7cb4abe0..47b2a3fe 100644 --- a/02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md +++ b/02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md @@ -9,6 +9,14 @@ extends: 02-DECISIONS/0227-the-core-holds-nine-rules-each-checked-and-is-built-t # 237. A change is judged against the mesh that runs, before it merges, on the build seat +> **Narrowed, not replaced — 2026-10-06, by [ADR 0238](0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md).** +> Decision 4's *which* and *what* moved: a pull request is checked when the mesh's module graph says it +> reaches a module — the planner's own answer, not *a repository it builds a module from into that +> branch* — and every repository of the mesh runs its own `merge-check.sh` as a second status, +> `mesh/repo-check`, a warning when it has none, instead of *the gate alone*. The gate itself is the +> build seat's, no longer each repository's script. The facts, the gate's rules, the replays, where a +> check runs and what it is given stand as decided here. + ## Context **The operator approved starting Phase 5** of [to-be 45](../03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md) diff --git a/02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md b/02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md new file mode 100644 index 00000000..310ac007 --- /dev/null +++ b/02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md @@ -0,0 +1,186 @@ +--- +topic: the mesh +status: accepted +date: 2026-10-06 +deciders: jochen +reconstructed: false +supersedes-in-part: + - 0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md +extends: 02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md +--- + +# 238. A commit is the build at hand: one commit, one change plan, checked off the trunk and published only on it + +## Context + +[ADR 0237](0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md) put a +check before every merge and ran it on the build seat. Turning it on for every repository of the mesh, the +same day, showed four places where it asked the wrong question. + +- **Which pull requests are checked was the repository's choice, not the mesh's.** The controller checked a + pull request when the mesh built a module from that repository into that branch, and ran the + repository's `merge-check.sh`, or the gate alone. A repository nothing is built from (the decision + records, the lab) was not checked at all, and its pull request kept the forge's *pending* forever. A + repository with no script ran only the gate, and nobody was told its own tests never ran. +- **The gate had its own idea of what a change touches.** The merge handler, the release planner's + what-if, the gate's *rebuild width* and the gate's composition each read a change's files in their own + way. Two of them agreed with each other; none of them was guaranteed to agree with the plan a merge + then made. +- **The rule they shared was wrong.** A file in no module's directory was read as *shared code*, and + everything built from the repository was rebuilt for it. The builder never reads such a file: it clones + the repository and builds within the module's own directory (the whole repository for a module built + from its root), plus any other repository an artifact's recipe names. Adding `merge-check.sh` at the + catalogue's root planned 103 rebuilds ([issue 280](../04-ISSUES/280-a-rebuild-of-an-unchanged-source-was-read-as-a-new-bus/00-report.md), + *Left open*). [Issue 278](../04-ISSUES/278-a-module-held-by-no-machine-was-read-as-shared-code/00-report.md) + and [issue 252](../04-ISSUES/252-a-merges-changed-modules-were-read-wrong/00-report.md) had each patched + an exception into the same rule. +- **Nothing stopped a commit off the trunk becoming a module's version.** `build --ref ` of a + feature branch registered what it built, and the next push could send it. A pull request's head was + only kept out of the catalogue by the check never asking to register it — a convention. + +## Considered Options + +**What decides whether a pull request is checked.** + +1. *The repository opts in*, with a script or a registration. Rejected: a repository that has not opted in + is exactly the one nobody is watching, and the forge said *pending* for it forever. +2. **The mesh's module graph** — chosen. The controller holds every module, the repository and directory + it is built from, and the build records' other repositories. Every pull request the forge holds is + announced, and the graph says what it reaches. + +**Where what a change touches is computed.** + +1. *In the gate, beside the planner.* Rejected: two computations of one fact drift, and the drift is found + when a merge does something its check did not say. +2. **In the planner, once** — chosen. One function maps a changed file onto modules, one function answers + what a merge would move and build; the merge handler, the what-if, the gate and the check all ask them. + +**What a file outside every module's directory touches.** + +1. *Everything built from the repository* (the shared-code rule). Rejected: no build reads it — the cost + was 103 rebuilds for a script at the root. +2. **Nothing** — chosen. A build reads its module's directory and the repositories its recipes name; + a repository a recipe names is the build record's, already followed. + +**How a commit off the trunk is kept from being published.** + +1. *By convention*: checks do not ask to register. Rejected: a person building a branch by hand registered + it, and a definition nobody had reviewed reached a machine ([issue 240](../04-ISSUES/240-a-dry-run-build-is-recorded-and-rolled-out/00-report.md)'s class). +2. **By refusal in code**: the build seat says which branches hold the commit it built; the controller + records and never registers a build off its module's trunk — chosen. + +## Decision + +1. **The commit is the build at hand, and where it sits decides what may happen.** A commit **not on the + trunk** — a pull request's head, any branch — is only *checked*: its change plan computed, the gate run + (composed machines validated, replays), its own tests run, the verdict posted. Nothing of it is + registered or sent to any machine; anything built to check it is never registrable. A commit **on the + trunk** is *published* — its builds registered as the module's version — and *pushed*, by the gated + release. **The trunk** is the branch the module follows; for a module new to the catalogue, its + repository's default branch as the forge names it. Enforced in code: the build seat reads, from its own + fresh clone, the default branch and every branch holding the commit, and says them with the outcome; + the controller records a build off its module's trunk and refuses to register it, naming the trunk — + which covers `build --ref`, `rebuild` and `replay --register` alike. A check's or a dry run's outcome + is never registered. A build seat older than this rule says nothing, and its build is registered as + before and said, so the rule can reach the mesh through the build seat it is built into. +2. **One commit, one change plan.** A change plan is computed from a diffset — a repository, the branch it + merges into, the commit at hand — by the planner: the **build plan** (the modules the merge moves + itself, their dependents in tiers; whether each is a move is its build's source fingerprint, issue + 280), the **deploy plan** (per machine, what it is sent in the build plan's order; what waits there + for a person; steps that are not an ordinary send flagged: a planned bus step, a provider whose + consumers are sent again, a module keeping data per ADR 0232/0233), and the **verdict** (the composed + machines, the replays). It is one object: a pull request's check posts it as its result, the release + after the merge follows the same planner and says what differs when another merge landed meanwhile, + and `plans` answers it for any base and head. It is kept — its id, its inputs, its result — so the + pull request, the release and the operator refer to the same thing. +3. **The planner maps a change onto the modules, once.** A changed file touches exactly the modules whose + build reads it: a module's own directory, the whole repository for a module built from its root, and + a repository a recipe names (the build record's `read`). A file read by no build — at the root, in a + directory no module is in — touches nothing and is said as such. A directory the change adds a + `module.json` in, which the graph does not hold, is a new module: checked, never built by the merge. + One function does the mapping and one answers the whole reach; the merge handler, the release + planner's what-if, the merge gate and a pull request's check call them, and nothing else maps a path. +4. **The graph decides what is checked, in two layers** (replacing ADR 0237 decision 4's *for a + repository it builds a module from into that branch* and *or the gate alone*). The forge's holder + announces every pull request it holds, with the directories holding a module at its head, the files + it deletes and whether its head has a `merge-check.sh`. The controller answers every one: + - a change that reaches a module, or adds one: the build seat runs **the gate**, status + `mesh/merge-gate` — the touched manifests through `module check` (a problem the base branch already + had is said and fails nothing), every machine composed with the definitions of the modules the plan + moves or adds (not every definition in the tree), the replays — judged by the controller the mesh + runs, a controller change by itself, a node-engine change by the running controller with its + validator; + - every repository of the mesh — one a module is built from on some branch, one of the core's owner, + one the change adds a module to — gets **its own check**, status `mesh/repo-check`: its + `merge-check.sh`, in the toolchain it declares (`# mesh-check-toolchain: go|typescript`); a + repository with none is a **warning**, never a pass and never silent; + - a change that reaches nothing is a pass that says so — a fact, not a missing check — and a + repository outside the mesh is told nothing more. +5. **Required statuses are the operator's setting, applied by the forge's holder.** `mesh/merge-gate` is + required on the trunk of every repository a module is built from, the list derived from the graph; + `mesh/repo-check` as well on the core repositories. The forge module's + `gitea_branch_protection_get` / `gitea_branch_protection_set` read and set it; a new rule refuses direct + pushes. +6. **A change plan is a state machine, one table.** States and the transitions allowed between them, + each with its guard, compiled once; any other transition is refused. `proposed` (a head) → `checked` + → `rejected` | `ready`; `ready` → `published` (guard: the commit on the trunk, its builds registered) + → `releasing` (per machine: sent → judging → passed | failed → rolled-back) → `done` | `failed` | + `superseded` (a newer plan for the same repository and trunk) | `stopped` (a person, with why) | + `held` (waits for a person: the release backlog, a bus step, a module whose policy records) → + `releasing` (guard: a person's decision, with why). It replaces the release plans' implicit states + (open, building, judging, done, failed, superseded, stopped, held), so there is one machine, not two. + Every transition is kept with the plan and the commit, said on the bus, and appended to the commit's + note; the plan resumes from its recorded state under the lease after a restart; a state held past its + bound raises a condition, and healer H2 works from the table. +7. **The plan is attached to its commit.** The `mesh/merge-gate` status links to the kept plan and + describes it in a line ("builds gitea → the control node; no bus step; 4/4 compose"). A git note under + `refs/notes/mesh-plan` on the pull request's head carries the plan's id and the predicted summary; on + the merge commit on the trunk, the executed plan — what was built, sent where, the gates' verdicts, + rollbacks, timings — written by the mesh's own forge account after the release, never by a person's + or an agent's hand. The note writer appends: running twice adds nothing, and an outcome amends the note, + never history. `git log --notes=mesh-plan` shows each commit's plan. + +## Consequences + +- **A module built from a branch other than its repository's default keeps that branch as its trunk.** + Three applications are built from such a branch; a module new to the catalogue from one is refused until + the branch is the default or merged into it. +- **The build seat must be updated before the trunk rule bites.** Until the build seat built from this + change is held, it says nothing and its builds register as before, said in the controller's log. +- **The repository's own check runs code nobody has approved, and is given nothing to leak**: no container + runtime socket, no package-registry credential. A suite that needs the mesh's own packages from the + registry says those tests did not run; the Go toolchain gains a C compiler (the race detector) and Python + (the decision records' checks), and the TypeScript toolchain git. +- **A rebuild is narrower**: a file at a repository's root rebuilds nothing; the release planner and the + gate say the files no build reads. +- **What is decided here and not yet built** is listed in to-be 45's Phase 5 and stays there until it is: + the kept change plan and its comparison at release, the state machine replacing the release plans' + states, the notes, the status's link. + +## How it is checked + +| Rule | Checked by | +|---|---| +| a commit off its module's trunk is recorded and never registered; a check or a dry run never is | the controller's test of the publish rule (on the trunk, off it, a module following another branch, a feature branch named by hand, a check, a dry run, a build seat that says nothing); the builder's test reading the trunk and the branches holding a commit from a fresh clone | +| one function maps a changed file onto modules, and every reader asks the planner | the controller's tests that a pull request's reach is the merge's (moved, dependents, width, unread) for a root file, a module's directory and both; the merge handler's and the gate's width tests on the same rule; a review of the callers, listed in the pull request | +| a file no build reads touches nothing; a repository a recipe names reaches its packager | the merge-handler tests (a root file, a directory with no manifest, a file among the modules), the gate's width test (a root file and a README rebuild nothing), the check's test of a packaged repository | +| every pull request is answered: a gate when it reaches a module, a repository check for the mesh's repositories (a warning without a script), a pass that says so otherwise | the controller's scope tests; the builder's test of a check with nothing to run; the builder's live tests (a script run beside the mesh's versions, failing, in a toolchain the mesh does not hold, past its bound, the gate with a stand-in judge) | +| a manifest problem the base already had fails nothing | the builder's live gate test with a manifest broken on the base branch | +| the statuses and the plan reach the pull request; an error is never a success | the forge module's tests of both statuses, the plan's line and comment | +| the change plan says what each machine receives and what is not an ordinary send | the controller's change-plan test (a module's own change, the bus with its dependents and a module that waits, a root file) | +| a branch protection rule is set as asked and a new one refuses pushes | the forge module's protection tests | +| the state machine refuses a transition not in its table; notes are append-only | not yet built: a test walking the table, as the signals and healers tables are walked; the note writer's idempotence test | + +## References + +- [ADR 0237](0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md), + [ADR 0236](0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md), + [ADR 0162](0162-a-merge-produces-a-tiered-plan-the-mesh-keeps.md), + [ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md). +- [to-be 45](../03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md) §9 and Phase 5; + [to-be 30](../03-DESIGN/01-to-be/30-the-mesh-updates-itself-on-a-push.md). +- Issues [252](../04-ISSUES/252-a-merges-changed-modules-were-read-wrong/00-report.md), + [278](../04-ISSUES/278-a-module-held-by-no-machine-was-read-as-shared-code/00-report.md), + [280](../04-ISSUES/280-a-rebuild-of-an-unchanged-source-was-read-as-a-new-bus/00-report.md). +- mesh-controller #101, mesh-catalog #98, mesh-host #43, mesh-tools #18, mesh-tools-go #1, mesh-sdk #11, + mesh-lab #53, mesh-media-catalog #12, and the applications' own checks. diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index c6c848a3..e74b7092 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -206,6 +206,7 @@ python3 00-META/checks/index.py fail if stale - **0234** — [The mesh holds a conversation with its operator, over channels that are seats, and an answer that performs an action is authorised by the controller](0234-the-mesh-holds-a-conversation-with-its-operator.md) - **0236** — [A build is judged on its first machine and put back by something other than itself, and so it rolls out unattended](0236-a-build-is-judged-on-its-first-machine-and-put-back-by-something-other-than-itself-and-so-it-rolls-out-unattended.md) - **0237** — [A change is judged against the mesh that runs, before it merges, on the build seat](0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md) +- **0238** — [A commit is the build at hand: one commit, one change plan, checked off the trunk and published only on it](0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md) ### Its tiers, from the bottom up 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 77b36070..4fd7b3bb 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 @@ -4,6 +4,7 @@ status: proposed code: [] updated: 2026-10-06 decisions: + - 02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md - 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 - 02-DECISIONS/0162-a-merge-produces-a-tiered-plan-the-mesh-keeps.md @@ -159,6 +160,15 @@ code. From an announcer that does not list them, the rule above stands. A change rebuilds the build agent alone: what it builds is ordered after it in a plan, never added to one for its sake. +Revision, [ADR 0238](../../02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md) +(2026-10-06), closing [issue 280](../../04-ISSUES/280-a-rebuild-of-an-unchanged-source-was-read-as-a-new-bus/00-report.md)'s +*left open*. **There is no shared code.** A changed file touches exactly the modules whose build reads +it — the module's own directory, the whole repository for a module built from its root, a repository a +recipe names — and a file no build reads, at the root or in a directory holding no module, rebuilds +nothing. One function maps a changed file onto modules and one answers what a merge moves and builds; +the merge handler, the what-if, the merge gate and a pull request's check ask them. Only a commit on the +module's trunk is registered. + ## 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/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md b/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md index f49bb487..6daf0bfb 100644 --- a/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md +++ b/03-DESIGN/01-to-be/45-a-core-that-cannot-fail-silently.md @@ -4,6 +4,7 @@ status: in-progress code: [mesh-controller, mesh-host, mesh-tools, mesh-catalog, mesh-sdk, mesh-lab] updated: 2026-10-06 decisions: + - 02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md - 02-DECISIONS/0237-a-change-is-judged-against-the-mesh-that-runs-before-it-merges-on-the-build-seat.md - 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 - 02-DECISIONS/0234-the-mesh-holds-a-conversation-with-its-operator.md @@ -512,6 +513,22 @@ makes any machine fail to compose or validate fails its check, naming the machin module. The resolver module's tests run under both C libraries the snapshot lists. A test in each core repository asserts that a pinned dependency's version equals the version the snapshot says runs. +Revision, [ADR 0238](../../02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md) +(2026-10-06). **The commit is the build at hand.** A commit off its module's trunk is only checked; a +commit on it is published and pushed, and the controller refuses to register a build of one off it. **The +module graph, not the repository, decides what a pull request's check runs**: every pull request the +forge holds is announced, and the controller asks the planner — the one function that maps a changed file +onto modules, and the one answer of what a merge moves and builds — what a merge of the head would reach. +A changed file touches exactly the modules whose build reads it; a file no build reads touches nothing. A +change reaching a module, or adding one, runs **the gate** on the build seat (`mesh/merge-gate`): the +touched manifests through `module check`, failing only what the change brings; every machine composed with +the definitions of the modules the plan moves or adds; the replays. Every repository of the mesh runs **its +own** `merge-check.sh` (`mesh/repo-check`) in the toolchain it declares, a warning when it has none. The +check's result is the commit's **change plan** — the build plan, the deploy plan machine by machine with +what is not an ordinary send, and the verdict — posted on the pull request. The plan is one object with +a state machine, kept, followed by the release and written to the commit as a note; which parts are built +is Phase 5's. + **The replays.** mesh-lab carries a scripted scenario for each core incident, asserting the rule's outcome, run on every merge to mesh-controller, mesh-host and mesh-tools: @@ -702,6 +719,24 @@ rest of the window's incidents as replays; the merge gate's first runs against t the status required to merge, which is the operator's setting on the forge; mesh-tools' and mesh-sdk's `merge-check.sh`. +**Revised 2026-10-06** ([ADR 0238](../../02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md), +the operator's "enable the checks on every repository of the mesh", "the logic acts on our module graph" +and "the commit is the build at hand"), on branches, not yet merged: + +| Repository | Delivers | +|---|---| +| mesh-controller | the planner's one mapping of a changed file onto modules and its one answer of a merge's reach, asked by the merge handler, the what-if, the gate and the check; a file no build reads touches nothing (issue 280's *left open*); every pull request answered — the gate when it reaches a module, the repository's own check for the mesh's repositories, a pass that says so otherwise; the gate run by the build seat with its judge chosen from the graph, composing the plan's definitions only, failing only a manifest problem the change brings; the change plan computed and carried with the verdict; a build of a commit off its module's trunk recorded and never registered; its own manifest naming every verb of its seat | +| mesh-catalog | the forge's announcer saying, with every pull request, the directories holding a module at its head, the files it deletes and whether it has a `merge-check.sh`; both statuses and the change plan on the pull request; `gitea_branch_protection_get` and `gitea_branch_protection_set`; the catalogue's own check | +| mesh-host, mesh-tools, mesh-sdk, mesh-lab, mesh-media-catalog, and the applications built from their own repositories | each its own `merge-check.sh`; mesh-tools' tests each on a bus of their own | +| mesh-tools-go, mesh-tools | a C compiler and Python in the Go toolchain, git in the TypeScript one | +| hq | its own `merge-check.sh`, running `records.py`, `index.py` and `cycle.py` | + +**Not yet:** the change plan kept (id, inputs, result) and the release comparing its plan with it; the +state machine replacing the release plans' states, with its table, its test, its conditions and healer H2; +the status's link to the kept plan; the notes under `refs/notes/mesh-plan`; check builds in a scratch +namespace (the check builds no module today, so there is none to keep apart yet); the statuses made +required — prepared as calls of the forge module's tool, applied by the operator. + ## What is not decided here - The bus as a cluster of three, to upgrade it live. diff --git a/04-ISSUES/280-a-rebuild-of-an-unchanged-source-was-read-as-a-new-bus/00-report.md b/04-ISSUES/280-a-rebuild-of-an-unchanged-source-was-read-as-a-new-bus/00-report.md index a13c9338..ea574816 100644 --- a/04-ISSUES/280-a-rebuild-of-an-unchanged-source-was-read-as-a-new-bus/00-report.md +++ b/04-ISSUES/280-a-rebuild-of-an-unchanged-source-was-read-as-a-new-bus/00-report.md @@ -72,6 +72,11 @@ still treats such a file as shared code and rebuilds everything built from the r fix that costs build time and no longer moves anything. Narrowing it belongs to the controller's planning rule (issue 278's area) and is not done here. +> **Closed — 2026-10-06, by [ADR 0238](../../02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md)** +> (mesh-controller #101): a changed file touches exactly the modules whose build reads it, and a file at +> the root rebuilds nothing — in the merge handler, the release planner, the merge gate and a pull +> request's check, through one function. + ## How it is checked | Rule | Checked by | diff --git a/AGENTS.md b/AGENTS.md index 71762dfd..a7ee1ac7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,7 +47,9 @@ vocabulary — *controller* (not "control plane"), *foundation* (not "substrate" ## Ground rules -- **Markdown only.** No new top-level folders without explicit confirmation. +- **Markdown only** — but for the checks in `00-META/checks/` and `merge-check.sh` at the root, which + runs them on every pull request as its `mesh/repo-check` ([ADR 0238](02-DECISIONS/0238-a-commit-is-the-build-at-hand-one-commit-one-change-plan-checked-off-the-trunk-and-published-only-on-it.md)). + No new top-level folders without explicit confirmation. - **Status lives in YAML frontmatter** — on research overviews (`status`, `became`), design docs (`layer`, `status`, `code`, `updated`), issue reports (`status`, `located-in`, `fixed-by`, `amended-design`) and decision records (`status`, `date`, `deciders`). diff --git a/merge-check.sh b/merge-check.sh new file mode 100644 index 00000000..8028389f --- /dev/null +++ b/merge-check.sh @@ -0,0 +1,11 @@ +#!/bin/sh +# mesh-check-toolchain: go +# +# This repository's own check (ADR 0238): the second layer of a pull request's merge check, +# `mesh/repo-check`, run by the build seat in the mesh's Go toolchain, which carries Python for it. No +# module is built from here, so no gate runs (`mesh/merge-gate` says the change touches none). The three +# checks every merge here must pass (AGENTS.md): the records' structure, the reading order, the cycle. +set -eu +python3 00-META/checks/records.py +python3 00-META/checks/index.py +python3 00-META/checks/cycle.py