From 4e13280604172ea21ca88e3b860f5680c721c9fa Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 28 Sep 2026 03:59:39 +0200 Subject: [PATCH] Design 28: 5.5 done, the mesh has one bus; issue 131 resolved The AMQP transport is gone from the control plane and the hosts (mesh-controller #112, mesh-host #39). On the way: no build had ever recorded its bases, so every bases-first order walked an empty graph; the builder now reports what it was handed and the graph is read from builds (mesh-controller #113/#114). Issue 131 is resolved by the forge module's merge event, the control plane following it, and those edges. --- 03-DESIGN/01-to-be/28-building-the-bus.md | 25 +++- .../00-report.md | 107 ++++++++++++++++++ 2 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 04-ISSUES/131-nothing-tells-the-mesh-a-source-moved/00-report.md diff --git a/03-DESIGN/01-to-be/28-building-the-bus.md b/03-DESIGN/01-to-be/28-building-the-bus.md index ab5593e..d96cb95 100644 --- a/03-DESIGN/01-to-be/28-building-the-bus.md +++ b/03-DESIGN/01-to-be/28-building-the-bus.md @@ -5,7 +5,7 @@ code: - mesh-catalog modules/nats - mesh-controller internal/catalogue - mesh-lab scenarios -updated: 2026-09-27 +updated: 2026-09-28 decisions: - 02-DECISIONS/0116-the-bus-is-built-in-five-steps.md - 02-DECISIONS/0106-the-bus-is-nats.md @@ -694,8 +694,27 @@ healthy while reacting to nothing. > shutting it down ends the path that reaches this installation's machines from a workstation. > The rollout is driven from the node, or before the broker stops — a sequencing constraint on > 5.2, not an afterthought. -- [ ] 5.5 **the AMQP transport is deleted from the control plane and the hosts**, and the variable - that selected a transport is refused at start as unknown. One bus, nothing to select ([ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md)). +- [x] 5.5 **the AMQP transport is deleted from the control plane and the hosts**. One bus, nothing + to select ([ADR 0131](../../02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md)). + + **Done 2026-09-28.** The control plane's old consume loop, build request, tool call, management + API and account scoping went, and the host's old dialling and enrolment paths with them; a + membership or token naming any other bus is refused before anything is sent. Nothing selects a + transport any more: the variable that once did (`MESH_BUS_NATS`) now only names where the + control plane reads its own bus credential, the way any module reads a secret. **Checked by the + build**: neither repository's module file names the AMQP client library, so a line that still + used it would not compile. The store-window guarantee ([issue 083](../../04-ISSUES/083-other-control-messages-are-lost-while-the-store-restarts/00-report.md)) + is tested against a bus-less fake rather than the old transport's memory, which is what let + that memory go — the one thing it did that the stream does not (superseding a held report) is + the staleness check on the message itself (design 25 §3). + + Found on the way: **no build had ever recorded what it stood on.** A recipe reads its base from + a build argument, so the digest was never in the file the builder derived edges from, and every + order that says *bases first* — `build --on`, `build --behind`, the merge follow-up of + [issue 131](../../04-ISSUES/131-nothing-tells-the-mesh-a-source-moved/00-report.md) — walked a + graph with no edges. The builder now reports the bases it was handed, the control plane records + them by artifact path, and the graph is read from the newest build of each module — a recorded + manifest carries no `build.on`, so the edge is derived from the build or it does not exist. > **The old 5.4 note is history.** It recorded that a retirement *condition* was wrong from > [ADR 0127](../../02-DECISIONS/0127-amqp-is-a-provision-not-the-bus.md) onward, which framed the old broker diff --git a/04-ISSUES/131-nothing-tells-the-mesh-a-source-moved/00-report.md b/04-ISSUES/131-nothing-tells-the-mesh-a-source-moved/00-report.md new file mode 100644 index 0000000..a7caf00 --- /dev/null +++ b/04-ISSUES/131-nothing-tells-the-mesh-a-source-moved/00-report.md @@ -0,0 +1,107 @@ +--- +status: resolved +opened: 2026-09-27 +located-in: [mesh-catalog modules/gitea, mesh-controller cmd/mesh-controller, mesh-controller internal/builder] +fixed-by: mesh-catalog #124 — the forge module watches for merged pull requests and emits `pull.merged` with the merge commit and the clone address; mesh-controller #110/#111 — the control plane follows that event on the bus, marks every module built from that repository and branch as moved, and builds them bases first, stopping when a base fails; mesh-controller #113/#114 — a build records the bases it was handed and the graph is read from builds, without which "bases first" had no edges to order by. +amended-design: 03-DESIGN/01-to-be/28-building-the-bus.md +--- + +# 131 — Nothing tells the mesh a source moved, and it reports itself current anyway + +## What was observed + +Six changes were merged to the trunk of six repositories in one sitting. The build machine built +nothing. Its last build, minutes before the first merge, was still the one it reported; no build was +requested, refused or failed, because none was ever asked for. + +Asked afterwards what was wrong, the mesh said: + +> 4 machine(s), all doing what they were told, all heard from, running what the mesh would send them, +> and every module current with its source + +Every one of the six had moved. The last clause was false for all of them, and it is the clause a +person reads to decide whether there is anything to do. + +## Why it matters beyond this instance + +**The mesh learns a source moved by being told, and there is no longer anything to tell it.** The +command exists — a person names the module and the commit — and so does the question the overview +answers. What is missing is whatever used to connect the two. One repository still carries a forge +webhook aimed at a port; the rest carry none, and the port belongs to a different service than the +one the arrangement implies. So the state is not "the trigger is broken" but "there is no trigger, +and nothing says so". + +**A wrong answer is worse here than no answer.** "Every module current with its source" is +indistinguishable, to a reader, from a mesh that has genuinely caught up. The overview is built to be +the thing you check instead of checking by hand, so a confident false negative removes the habit that +would otherwise have caught it. Nothing in the mesh is at fault for being out of date — it is at +fault for saying it is not. + +**It is also why "current with its source" cannot be a stored fact.** The mesh compares what it built +against what it was last told the source was, and calls that agreement. Two facts agreeing tells you +nothing when both come from the same place. + +## The intended shape, which is decided and not built + +The forge emits what happened to it — a pull request merged — and the build machine reacts by +building what that commit affects. That keeps the forge ignorant of the build system and the build +machine ignorant of the forge's internals, which is the same argument +[ADR 0126](../../02-DECISIONS/0126-a-module-declares-its-own-seats.md) makes for addressing an event +to its emitter: the merge is a fact about the forge, and what should be rebuilt because of it is not +the forge's business to know. + +The forge's module already declares the event. The build machine declares that it consumes nothing. + +## What the trigger cannot be + +**Not one build per changed module.** The modules form a graph: several are built from one +repository, and some are the base another is compiled on — a runtime image, a compiler base, a +repository whose context a second module builds from. Firing a build for each changed module +independently would start work that cannot succeed yet and produce a failure per dependent, for one +cause. + +Observed while catching the mesh up by hand on 2026-09-27: a compiler base had to move before +anything compiled against it could build, and when it failed, the right behaviour was for its +dependents to wait rather than each fail the same way. Fifteen modules shared the cause. A trigger +that reports it fifteen times has buried it. + +So whatever reacts to the forge's event resolves what changed into an order, builds the bases first, +and holds a dependent while its base is unbuilt or failed. That is a larger thing than "rebuild what +the commit touched", and knowing it now is cheaper than discovering it from fifteen identical +failures. + +## Open questions + +- Is "the source moved" still a thing a person can assert by hand once the event path exists, or does + the hand-operated form become the thing that made this failure possible? +- Which commit does the build machine act on — the merge, or each commit it brought — and what does + it do when several arrive for one module at once? +- How does the overview stop being able to lie? Comparing what was built against what was recorded + will always agree. Whether the trunk has moved is a question only the forge can answer, so either + the overview asks it, or it stops claiming to know. +- Does this want to be the same mechanism as the build request on the bus + ([ADR 0129](../../02-DECISIONS/0129-a-seat-carries-the-protocol-of-its-role.md)), or does it sit in + front of it? + +## What was done (2026-09-28) + +The shape above was built as described: the forge's module emits the merge, the control plane +consumes it, and nothing on either side knows the other's internals. The build is asked for the +merge commit, not each commit the merge brought — the trunk moved once, to one place. Several merges +for one module arriving in a row are followed in turn, each moving the recorded source to its own +commit, so the last one to arrive is the one the mesh ends up built from. + +The hand-operated form stays. `module moved` is how a source is recorded without a forge — a module +built from a repository elsewhere, or a mesh whose forge module is down — and it is the same act the +event performs, so the two cannot disagree about what "moved" means. + +**Bases first needed edges, and there were none.** The order this report asked for was written and +walked a graph that no build had ever recorded: a recipe reads its base from a build argument, so the +digest was never in the file the builder read edges from. A build now reports what it was handed, the +control plane records it by artifact path, and the order is read from each module's newest build. + +**What still can lie.** The overview compares what was built against where it was last told the +source is; the forge's event is now what moves that mark, so it is right for as long as the forge +module was listening. A merge made while that module was down is a merge the mesh does not know of +until the module next polls — it announces what merged since it last looked, so the gap closes when +it comes back, and not before. The overview does not say so.