From 1fd2914ae1d1dc5672db6e40881e0145c7872cb6 Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 6 Oct 2026 18:34:34 +0200 Subject: [PATCH] Say which modules wait for a person's push, and announce the files a merge deleted (hq ADR 0236) With a gate on the first machine and a rollback after it, a module's build rolls out by default. The ones kept back say why: the network path a rollback could not cross, the providers every consumer on a machine drops with, and the stores holding the photos. A merge's deleted files are announced, so a module whose manifest went is forgotten rather than asked to build (the public-acme plan failure). --- modules/dnsmasq/module.json | 4 ++++ modules/gitea/client.ts | 15 +++++++++++---- modules/gitea/index.ts | 3 +++ modules/gitea/test/pages.test.ts | 4 +++- modules/keycloak/module.json | 4 ++++ modules/minio/module.json | 4 ++++ modules/mongodb/module.json | 4 ++++ modules/mssql/module.json | 4 ++++ modules/nats/module.json | 4 ++++ modules/networkmanager/module.json | 4 ++++ modules/nftables/module.json | 4 ++++ modules/postgres/module.json | 4 ++++ modules/sshd/module.json | 4 ++++ modules/systemd-networkd/module.json | 4 ++++ 14 files changed, 61 insertions(+), 5 deletions(-) diff --git a/modules/dnsmasq/module.json b/modules/dnsmasq/module.json index f1df031..8fc9825 100644 --- a/modules/dnsmasq/module.json +++ b/modules/dnsmasq/module.json @@ -1,6 +1,10 @@ { "module": "dnsmasq", "version": "1", + "upgrade": { + "policy": "record", + "why": "the mesh's resolver: a build that breaks it can stop a machine resolving the bus, and then neither the gate's rollback nor a push reaches it (hq ADR 0236, issue 260)" + }, "provides": [ { "name": "wildcard-resolution", diff --git a/modules/gitea/client.ts b/modules/gitea/client.ts index e9e10b7..07dc481 100644 --- a/modules/gitea/client.ts +++ b/modules/gitea/client.ts @@ -256,18 +256,25 @@ export class GiteaClient { * whose own files moved was not rebuilt (novox/hq issue 252). Read until a page comes back short; past * `most` files the list is cut and says so, and the mesh then rebuilds everything built from the * repository, the safe direction. */ - async listPullFiles(owner: string, repo: string, index: number, most = 3000): Promise<{ paths: string[]; truncated: boolean }> { + /** Every file a pull request changes, and which of them it deleted: a module whose manifest the merge + * deleted is gone from its source, and the mesh forgets it rather than asking its build (novox/hq ADR + * 0236). The forge says `deleted`; `removed` is read the same. */ + async listPullFiles(owner: string, repo: string, index: number, most = 3000): Promise<{ paths: string[]; removed: string[]; truncated: boolean }> { const paths: string[] = []; + const removed: string[] = []; let pageSize = 0; for (let page = 1; ; page++) { const files = (await this.request(`/repos/${owner}/${repo}/pulls/${index}/files?limit=50&page=${page}`)) ?? []; if (page === 1) pageSize = files.length; for (const f of files) { const name = String(f?.filename ?? ""); - if (name !== "") paths.push(name); + if (name === "") continue; + paths.push(name); + const status = String(f?.status ?? ""); + if (status === "deleted" || status === "removed") removed.push(name); } - if (files.length === 0 || files.length < pageSize) return { paths, truncated: false }; - if (paths.length >= most) return { paths, truncated: true }; + 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/index.ts b/modules/gitea/index.ts index d30214f..99eff32 100644 --- a/modules/gitea/index.ts +++ b/modules/gitea/index.ts @@ -120,6 +120,9 @@ async function pollMerged(client: GiteaClient): Promise { html_url: pull.html_url, paths: changed.paths, paths_truncated: changed.truncated, + // Which of them the merge deleted (novox/hq ADR 0236): a module whose manifest went is forgotten, + // not built. + removed: changed.removed, }); // Said, because a trigger that fires silently is indistinguishable from one that did not // fire (novox/hq 04-ISSUES/131) — this line is how an operator knows the mesh was told. diff --git a/modules/gitea/test/pages.test.ts b/modules/gitea/test/pages.test.ts index 59597a6..f389ab2 100644 --- a/modules/gitea/test/pages.test.ts +++ b/modules/gitea/test/pages.test.ts @@ -9,7 +9,8 @@ test("every page of a pull request's files is read, though the forge caps a page const url = new URL(req.url ?? "", "http://x"); const page = Number(url.searchParams.get("page") ?? "1"); const start = (page - 1) * 50; - const files = Array.from({ length: Math.max(0, Math.min(50, total - start)) }, (_, i) => ({ filename: `modules/m${start + i}/x` })); + const files = Array.from({ length: Math.max(0, Math.min(50, total - start)) }, (_, i) => ({ + filename: `modules/m${start + i}/x`, status: start + i === 3 ? "deleted" : "changed" })); res.setHeader("content-type", "application/json"); res.end(JSON.stringify(files)); }); @@ -21,6 +22,7 @@ test("every page of a pull request's files is read, though the forge caps a page assert.equal(got.paths.length, total); assert.equal(got.truncated, false); assert.equal(new Set(got.paths).size, total); + assert.deepEqual(got.removed, ["modules/m3/x"], "a file the merge deleted is said as deleted"); }); test("a pass asks only the repositories that moved since the last look, every one before the first", async () => { diff --git a/modules/keycloak/module.json b/modules/keycloak/module.json index c82ae5c..a903c64 100644 --- a/modules/keycloak/module.json +++ b/modules/keycloak/module.json @@ -1,6 +1,10 @@ { "module": "keycloak", "version": "1", + "upgrade": { + "policy": "record", + "why": "every person's sign-in to every site goes through it, and its new version migrates its database on start: a person takes each build, after a backup (hq ADR 0236)" + }, "provides": [ { "name": "oidc-client", diff --git a/modules/minio/module.json b/modules/minio/module.json index 0914536..7d6f525 100644 --- a/modules/minio/module.json +++ b/modules/minio/module.json @@ -1,6 +1,10 @@ { "module": "minio", "version": "1", + "upgrade": { + "policy": "record", + "why": "holds the photos themselves (irreplaceable, kept by photos) for its consumers: a person takes each build, after a backup (hq ADR 0236)" + }, "provides": [ { "name": "s3-bucket", diff --git a/modules/mongodb/module.json b/modules/mongodb/module.json index 89bdace..0ee8c53 100644 --- a/modules/mongodb/module.json +++ b/modules/mongodb/module.json @@ -1,6 +1,10 @@ { "module": "mongodb", "version": "1", + "upgrade": { + "policy": "record", + "why": "a provider whose restart drops every consumer on its machine, and which holds the photos' albums (irreplaceable, kept by photos): a person takes each build, after a backup (hq ADR 0236)" + }, "provides": [ { "name": "mongodb-database", diff --git a/modules/mssql/module.json b/modules/mssql/module.json index 3dad4f6..2ccbba5 100644 --- a/modules/mssql/module.json +++ b/modules/mssql/module.json @@ -1,6 +1,10 @@ { "module": "mssql", "version": "1", + "upgrade": { + "policy": "record", + "why": "a provider whose restart drops every consumer on its machine, and whose new version may upgrade its databases in place: a person takes each build, after a backup (hq ADR 0236)" + }, "provides": [ { "name": "mssql-database", diff --git a/modules/nats/module.json b/modules/nats/module.json index 0c42cd1..d59f407 100644 --- a/modules/nats/module.json +++ b/modules/nats/module.json @@ -1,6 +1,10 @@ { "module": "nats", "version": "1", + "upgrade": { + "policy": "record", + "why": "the bus: replaced only as a planned step a person starts (`bus upgrade`), its streams snapshotted first and checked after (hq ADR 0236); the controller holds this whatever is said here" + }, "provides": [ { "name": "mesh-bus", diff --git a/modules/networkmanager/module.json b/modules/networkmanager/module.json index 285c8d4..d039a49 100644 --- a/modules/networkmanager/module.json +++ b/modules/networkmanager/module.json @@ -1,6 +1,10 @@ { "module": "networkmanager", "version": "1", + "upgrade": { + "policy": "record", + "why": "the machine's network: a build that breaks it can cut the machine off from the bus, and then neither the gate's rollback nor a push reaches it (hq ADR 0236)" + }, "slug": "nm", "requires": [ "wildcard-resolution" diff --git a/modules/nftables/module.json b/modules/nftables/module.json index 6c67371..2a80c58 100644 --- a/modules/nftables/module.json +++ b/modules/nftables/module.json @@ -1,6 +1,10 @@ { "module": "nftables", "version": "1", + "upgrade": { + "policy": "record", + "why": "the machine's packet filter: a build that breaks it can cut the machine off from the bus, and then neither the gate's rollback nor a push reaches it (hq ADR 0236)" + }, "capabilities": [ "firewall" ], diff --git a/modules/postgres/module.json b/modules/postgres/module.json index 48ac141..1728406 100644 --- a/modules/postgres/module.json +++ b/modules/postgres/module.json @@ -1,6 +1,10 @@ { "module": "postgres", "version": "1", + "upgrade": { + "policy": "record", + "why": "a provider whose restart drops every consumer on its machine, and whose new major version changes its data's format: a person takes each build, after a backup (hq ADR 0236)" + }, "provides": [ { "name": "postgres-database", diff --git a/modules/sshd/module.json b/modules/sshd/module.json index f2384cf..a675636 100644 --- a/modules/sshd/module.json +++ b/modules/sshd/module.json @@ -1,6 +1,10 @@ { "module": "sshd", "version": "1", + "upgrade": { + "policy": "record", + "why": "the operator's way into the machine when the mesh cannot reach it: a build that breaks it is found only when that way is needed, which no gate sees (hq ADR 0236)" + }, "capabilities": [ "package-manager", "service-manager" diff --git a/modules/systemd-networkd/module.json b/modules/systemd-networkd/module.json index fcedb9e..46d4dd2 100644 --- a/modules/systemd-networkd/module.json +++ b/modules/systemd-networkd/module.json @@ -1,6 +1,10 @@ { "module": "systemd-networkd", "version": "1", + "upgrade": { + "policy": "record", + "why": "the machine's network: a build that breaks it can cut the machine off from the bus, and then neither the gate's rollback nor a push reaches it (hq ADR 0236)" + }, "slug": "networkd", "requires": [ "wildcard-resolution"