From 28853a251bdbf91cea3b0533f49e53bb0472f9d6 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 04:04:41 +0200 Subject: [PATCH] The build seat's holder follows the controller that defines its worker (hq issue 206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A plan is ordered by artifacts and says nothing about what must be running before what (ADR 0162); on 2026-10-03 that put the build machine in tier 0 and the controller in tier 1, and the new build machine could not bind the worker the old controller had defined. One running order enters the graph, named as its own edge: a module claiming the build seat follows the control plane, and the built-by edge from the control plane to that holder yields to it — the controller is built by whichever build machine is running, as the runtime image always was. The edge orders a plan and never widens it, like built-by. --- cmd/mesh-controller/release_plan.go | 25 ++++++++++--- cmd/mesh-controller/worker_order_test.go | 45 ++++++++++++++++++++++++ internal/inventory/dependencies.go | 16 +++++++++ 3 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 cmd/mesh-controller/worker_order_test.go diff --git a/cmd/mesh-controller/release_plan.go b/cmd/mesh-controller/release_plan.go index 83976f5..438ea38 100644 --- a/cmd/mesh-controller/release_plan.go +++ b/cmd/mesh-controller/release_plan.go @@ -36,17 +36,29 @@ func tiersOf(set []string, edges []inventory.Edge) [][]string { for _, m := range set { deps[m] = map[string]bool{} } + // The build seat's holders follow the controller that defines their worker (EdgeWorkerOf, + // novox/hq issue 206), so the built-by edge from that controller to such a holder yields: the + // controller is built by whichever build machine is running, as the runtime image always was. + worker := map[string]map[string]bool{} + for _, e := range edges { + if e.Kind == inventory.EdgeWorkerOf && in[e.From] && in[e.To] { + if worker[e.To] == nil { + worker[e.To] = map[string]bool{} + } + worker[e.To][e.From] = true + } + } for _, e := range edges { // A code dependency — B packages A's source — rebuilds B with A, in the same tier: B's // build needs nothing of A's first. The other kinds order: stands-on and declared after // the base is built, built-by after the build machine is built and running — except for - // what the build machine itself stands on. The runtime image is built by the builder and - // the builder is built on the runtime image; the image comes first, built by the builder - // that is running, which is the only one there could be. + // what the build machine itself stands on, and for the controller whose worker the build + // machine binds. The runtime image is built by the builder and the builder is built on the + // runtime image; the image comes first, built by the builder that is running. if !in[e.From] || !in[e.To] || e.From == e.To || e.Kind == inventory.EdgePackages { continue } - if e.Kind == inventory.EdgeBuiltBy && isBaseOf(e.From, e.To, edges, in) { + if e.Kind == inventory.EdgeBuiltBy && (isBaseOf(e.From, e.To, edges, in) || worker[e.From][e.To]) { continue } deps[e.From][e.To] = true @@ -122,7 +134,10 @@ func reachableFrom(moved []string, edges []inventory.Edge) []string { for grew := true; grew; { grew = false for _, e := range edges { - if e.Kind == inventory.EdgeBuiltBy { + // Built-by and worker-of order a plan; neither widens it. A new build machine changes + // nothing it builds, and a new controller changes nothing about the holder it orders — + // what packages the controller's source is already a code edge. + if e.Kind == inventory.EdgeBuiltBy || e.Kind == inventory.EdgeWorkerOf { continue } if in[e.To] && !in[e.From] { diff --git a/cmd/mesh-controller/worker_order_test.go b/cmd/mesh-controller/worker_order_test.go new file mode 100644 index 0000000..f5b43f2 --- /dev/null +++ b/cmd/mesh-controller/worker_order_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "testing" + + "github.com/novox/mesh-controller/internal/inventory" +) + +// The holder of the build seat follows the controller that defines its worker (novox/hq issue 206). +// On 2026-10-03 a plan put the build machine in tier 0 and the controller in tier 1; the new build +// machine could not bind the worker the old controller had defined, and nothing could build the +// controller that would have redefined it. The built-by edge from the controller to its build +// machine yields to that order: the controller is built by whichever build machine is running. +func TestTheBuildSeatsHolderFollowsTheControllerThatDefinesItsWorker(t *testing.T) { + edges := []inventory.Edge{ + {From: "build-agent", To: "mesh-controller", Kind: inventory.EdgePackages}, + {From: "build-agent", To: "mesh-controller", Kind: inventory.EdgeWorkerOf}, + {From: "mesh-controller", To: "build-agent", Kind: inventory.EdgeBuiltBy}, + {From: "route-proxy", To: "mesh-controller", Kind: inventory.EdgePackages}, + {From: "route-proxy", To: "build-agent", Kind: inventory.EdgeBuiltBy}, + } + set := reachableFrom([]string{"mesh-controller"}, edges) + if len(set) != 3 { + t.Fatalf("the controller, what packages it, and nothing more: %v", set) + } + tiers := tiersOf(set, edges) + pos := map[string]int{} + for i, tier := range tiers { + for _, m := range tier { + pos[m] = i + } + } + if pos["mesh-controller"] != 0 { + t.Fatalf("the controller first, built by the build machine that is running: %v", tiers) + } + if pos["build-agent"] <= pos["mesh-controller"] { + t.Fatalf("the build machine after the controller that defines its worker: %v", tiers) + } + if pos["route-proxy"] <= pos["build-agent"] { + t.Fatalf("what the build machine builds comes after it: %v", tiers) + } + if hasCycle(tiers, edges) { + t.Fatalf("no cycle here: %v", tiers) + } +} diff --git a/internal/inventory/dependencies.go b/internal/inventory/dependencies.go index 26274e7..580586c 100644 --- a/internal/inventory/dependencies.go +++ b/internal/inventory/dependencies.go @@ -18,8 +18,17 @@ const ( EdgeBuiltBy = "built-by" // EdgeDeclared: the manifest's own `build.on`. EdgeDeclared = "declared" + // EdgeWorkerOf: the module holds the build seat, whose worker the control plane defines + // (novox/hq issue 206). The one place a *running* order enters the graph: a build machine rolled + // before the controller that redefines its worker cannot bind it, and nothing can then build the + // controller that would end that — so the holder of the build seat follows the controller, and + // the controller is built by whichever build machine is running, as it always was. + EdgeWorkerOf = "worker-of" ) +// TheControlPlane is the module that defines every seat's worker on the bus. +const TheControlPlane = "mesh-controller" + // Edge is one dependency: From depends on To, in the way Kind says. type Edge struct { From string `json:"from"` @@ -106,6 +115,13 @@ func dependenciesOf(entries []Entry, against map[string][]string, read map[strin } } } + if known[TheControlPlane] { + for _, b := range builders { + if b != TheControlPlane { + add(b, TheControlPlane, EdgeWorkerOf) + } + } + } sort.Slice(out, func(a, b int) bool { if out[a].From != out[b].From { return out[a].From < out[b].From