From f4fb0a746186912b20d87722c5c4342ed9360be8 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 10 Oct 2026 02:58:31 +0200 Subject: [PATCH] Name the package each controller-repository image compiles, and say a renamed file under both names (hq ADR 0267, issue 363) Every merge to the controller's repository rebuilt the route proxy and the build seat's holder, because their builds read that repository whole. Named, the build seat hands each only its program's build source and the planner moves each only when that changes. A rename was said under its new name alone, so a file moved out of a build source changed nothing the planner could see. --- modules/build-agent/Dockerfile | 5 +++-- modules/build-agent/module.json | 1 + modules/gitea/client.ts | 7 +++++++ modules/gitea/test/pages.test.ts | 18 ++++++++++++++++++ modules/route-proxy/Dockerfile | 5 ++++- modules/route-proxy/module.json | 1 + 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/modules/build-agent/Dockerfile b/modules/build-agent/Dockerfile index 971f3f26..8abeaa19 100644 --- a/modules/build-agent/Dockerfile +++ b/modules/build-agent/Dockerfile @@ -5,9 +5,10 @@ ARG ALPINE_BASE # **The source is not vendored here.** builder's actual code — cmd/mesh-builder, internal/builder, # internal/catalogue — lives in the mesh-controller repository, the same control plane it is one # half of. This module ships the packaging, not a second copy of the source, so the build context -# is the mesh-controller repository root (declared under build.artifacts[].context), and this +# is the mesh-controller repository (declared under build.artifacts[].context), and this # Dockerfile compiles ./cmd/mesh-builder from it — the same shape route-proxy already uses for the -# same reason. +# same reason. The build seat hands it only that program's build source (novox/hq ADR 0267, the +# artifact's `compiles`): a merge elsewhere in that repository does not move this image. FROM ${GO_BASE} AS build WORKDIR /src COPY go.mod go.sum ./ diff --git a/modules/build-agent/module.json b/modules/build-agent/module.json index a7539ad3..87a37dbb 100644 --- a/modules/build-agent/module.json +++ b/modules/build-agent/module.json @@ -79,6 +79,7 @@ "name": "server", "kind": "image", "from": "Dockerfile", + "compiles": "cmd/mesh-builder", "context": { "seat": "git", "repository": "novox/mesh-controller", diff --git a/modules/gitea/client.ts b/modules/gitea/client.ts index f242683f..06a87020 100644 --- a/modules/gitea/client.ts +++ b/modules/gitea/client.ts @@ -362,6 +362,13 @@ export class GiteaClient { paths.push(name); const status = String(f?.status ?? ""); if (status === "deleted" || status === "removed") removed.push(name); + // **A renamed file is said under both names** (novox/hq ADR 0267, issue 363): its old path is gone, and + // a module whose build source held it changed even though the new path is elsewhere. + const previous = String(f?.previous_filename ?? ""); + if (previous !== "" && previous !== name) { + paths.push(previous); + removed.push(previous); + } } if (files.length === 0 || files.length < pageSize) return { paths, removed, truncated: false }; if (paths.length >= most) return { paths, removed, truncated: true }; diff --git a/modules/gitea/test/pages.test.ts b/modules/gitea/test/pages.test.ts index 063cf324..b4bed7e0 100644 --- a/modules/gitea/test/pages.test.ts +++ b/modules/gitea/test/pages.test.ts @@ -71,3 +71,21 @@ test("a lookup the forge refuses is thrown, not read as no module", async () => await assert.rejects(client.moduleDirsAt("novox", "mesh-catalog", "abc", ["modules/x/y.ts"])); server.close(); }); + +test("a renamed file is said under its old name too, as gone (novox/hq ADR 0267)", async () => { + const server = createServer((req, res) => { + const page = Number(new URL(req.url ?? "", "http://x").searchParams.get("page") ?? "1"); + res.setHeader("content-type", "application/json"); + res.end(JSON.stringify(page > 1 ? [] : [ + { filename: "internal/elsewhere/moved.go", previous_filename: "internal/broker/moved.go", status: "renamed" }, + { filename: "README.md", status: "changed" }, + ])); + }); + await new Promise((r) => server.listen(0, r)); + const port = (server.address() as any).port; + const client = new GiteaClient(`http://127.0.0.1:${port}`, "t"); + const got = await client.listPullFiles("novox", "mesh-controller", 7); + server.close(); + assert.deepEqual(got.paths, ["internal/elsewhere/moved.go", "internal/broker/moved.go", "README.md"]); + assert.deepEqual(got.removed, ["internal/broker/moved.go"], "the old name is gone from the tree"); +}); diff --git a/modules/route-proxy/Dockerfile b/modules/route-proxy/Dockerfile index 45593a7e..b556c2f6 100644 --- a/modules/route-proxy/Dockerfile +++ b/modules/route-proxy/Dockerfile @@ -5,7 +5,10 @@ ARG GO_BASE=golang:1.25 # **The proxy source is not vendored here.** The canonical proxy — the contract written as something # that runs — lives in the mesh-controller repository at examples/route-proxy (novox/hq 08-connectivity # §3). This module ships the *packaging*, not a second copy of the contract, so the build context is -# the mesh-controller repository root, and this Dockerfile compiles ./examples/route-proxy from it. +# the mesh-controller repository, and this Dockerfile compiles ./examples/route-proxy from it. The build seat +# hands it only that program's build source — its import closure, go.mod, go.sum and the vendored packages +# it uses (novox/hq ADR 0267, the artifact's `compiles`) — so a merge elsewhere in that repository does not +# move this image, and a COPY of anything outside it fails by name. # # docker build -f mesh-catalog/modules/route-proxy/Dockerfile \ # -t mesh-route-proxy:development /mesh-controller diff --git a/modules/route-proxy/module.json b/modules/route-proxy/module.json index 38444a45..548695a4 100644 --- a/modules/route-proxy/module.json +++ b/modules/route-proxy/module.json @@ -188,6 +188,7 @@ "name": "server", "kind": "image", "from": "Dockerfile", + "compiles": "examples/route-proxy", "context": { "seat": "git", "repository": "novox/mesh-controller",