Stop hearing the retired build seats' events in gitea and the catalogue module, so no manifest consumes a seat nothing claims once node-build-agent goes (issue 1887 E2 with A9)

This commit is contained in:
2026-10-23 15:37:17 +02:00
parent db937546ae
commit d0ba022d20
6 changed files with 14 additions and 37 deletions
+3 -21
View File
@@ -20,8 +20,8 @@
// writes it (check.ts; ADR 0521, R4e finding R2-1). No tool writes mesh/check (finding K1), and no event: the watch hands
// over the latest value of every key whenever this module starts.
//
// Consumes node-build-agent.started — a build machine took an ask — and says, on the head a pending check is of,
// which machine builds it since when (novox/hq issue 446). This module is the one writer of mesh/merge-gate.
// This module is the one writer of mesh/merge-gate. It hears no build seat's event: since R4g a check's steps are asks
// on the build seats' streams, whose backlog the delivery view shows (novox/hq ADR 0525 §9; issue 1887).
//
// **A trunk's move puts every open verdict into it out of date** (novox/hq ADR 0323 §4, trunk.ts): each pass reads the
// head of every trunk an open pull request merges into, and when one moved, the merge check statuses of every open pull
@@ -53,7 +53,7 @@ import { mergeTool } from "./merge.js";
import { GiteaAdmin, GiteaClient, movedSince } from "./client.js";
import { CHECK_CONTEXT, CHECK_STATUS, REPO_CHECK_CONTEXT, commentFor, composedGate, groupGateStatus, headsToAnnounce, maintained,
requiredOf, statusesFor, writesGate, type Announced, type Checked, repoCheckFacts, reposToLook } from "./pulls.js";
import { GateWriter, clonedAs, type BuildStarted } from "./gate.js";
import { GateWriter } from "./gate.js";
import { HOOK_PATH, HOOK_PORT, Pace, claimHead, ensureSystemHook, eventsTool, keptSecret, receiver, type ForgeEvent } from "./webhook.js";
import { keepSiblings, noteComposed, noteVerdict, readSiblings, siblingsFile, siblingsOf, weighSiblings } from "./siblings.js";
import { bindInBackground } from "./bind.js";
@@ -504,23 +504,6 @@ registerModuleTools("gitea", () => [eventsTool(pace), ...(gitea ? [commitStatusT
gates,
}), mergeTool(gitea, { gates })] : [])]);
// **A build machine took an ask** (the build seat's `started`, novox/hq ADR 0157): said on the head it checks, when
// the head is an open pull request's this module announced. Any other build — a branch, a merge's — says nothing.
async function buildStarted(event: { body: unknown }): Promise<void> {
const b = (event.body ?? {}) as BuildStarted;
const ref = String(b.ref ?? "").toLowerCase();
if (!/^[0-9a-f]{40}$/.test(ref)) return;
for (const [key, sha] of Object.entries(heads)) {
if (sha.toLowerCase() !== ref) continue;
const [owner, rest] = key.split("/");
const repo = rest?.split("#")[0] ?? "";
if (owner && repo && clonedAs(b.repository, owner, repo)) {
await gates.started(owner, repo, sha, b);
return;
}
}
}
/** What the forge's event asks of the announcer at once (issue 729): a new head is announced, and a merge. */
async function actOn(client: GiteaClient, event: ForgeEvent): Promise<void> {
const b = event.body as { owner?: string; repo?: string; number?: number };
@@ -628,7 +611,6 @@ if (gitea) {
}
return setVerdict(client, event);
})],
["node-build-agent.started", () => on("node-build-agent.started", buildStarted)],
], () => console.log("[gitea] watching for new repositories and merged pull requests"));
// The checker's mesh/check, from its owed state (check.ts; ADR 0521): a read not granted yet is said once and asked again,
// apart from the binds above, so neither waits for the other.
+1 -2
View File
@@ -86,8 +86,7 @@
"forge.other"
],
"consumes": [
"mesh-controller.checked",
"node-build-agent.started"
"mesh-controller.checked"
],
"listens": [
{
+2 -6
View File
@@ -47,12 +47,8 @@ export interface Built {
/** The events a build outcome reaches this module on: the build seat's, as it happens, and the control plane's replay. */
export const BUILD_EVENTS = [
// **The build seat that builds now** (novox/hq ADR 0190). Until issue 712 the catalogue heard only the
// retired seat below, so every build after the handover reached the graph only through the replay this
// module asks for when it starts: a merge built and pushed stayed out of the catalogue until its next start.
"node-build-agent.built",
// The retired build role, while a machine still serves it (ADR 0190's handover). Goes with the seat row.
"mesh-build-machine.built",
// The build seat node-build-agent and the retired mesh-build-machine went with build-agent (novox/hq ADR 0524 §5,
// ADR 0525 §10): no build seat says `built` since R4g, so what the mesh holds is asked when this module starts.
// What the mesh already held, when this module asks what it missed (novox/hq ADR 0134).
"mesh-controller.built-before",
] as const;
-2
View File
@@ -26,8 +26,6 @@
"postgres-database": "${dir:state}/database.secret"
},
"consumes": [
"node-build-agent.built",
"mesh-build-machine.built",
"mesh-controller.built-before"
],
"emits": [
+4 -2
View File
@@ -20,8 +20,10 @@ const walkBuild = {
made: [{ name: "code", kind: "bundle", reference: "artifact-store://mailu/code/blobs/sha256:d041" }],
};
test("the catalogue hears the build seat that builds now, not only the retired one (issue 712)", async () => {
assert.ok(BUILD_EVENTS.includes("node-build-agent.built"));
test("the catalogue hears no retired build seat, and what the mesh held (issue 712, issue 1887 E2)", async () => {
for (const retired of ["node-build-agent.built", "mesh-build-machine.built"]) {
assert.ok(!(BUILD_EVENTS as readonly string[]).includes(retired), `${retired} is still heard`);
}
assert.ok(BUILD_EVENTS.includes("mesh-controller.built-before"));
// Subscribing is not enough: the mesh grants and routes only what the manifest consumes.
const manifest = JSON.parse(await readFile(new URL("../module.json", import.meta.url), "utf8"));
+4 -4
View File
@@ -96,9 +96,9 @@ func TestAConsumerWithSeveralFiltersIsHandedEveryMessage(t *testing.T) {
FilterSubjects: []string{
"mesh.mod.mesh-catalog.event.upgraded",
"mesh.mod.mesh-catalog.event.catching-up",
"mesh.seat.node-build-agent.event.built",
"mesh.seat.node-backup.event.done",
"mesh.mod.gitea.event.pull.merged",
"mesh.seat.mesh-build-machine.event.built",
"mesh.seat.node-power.event.slept",
"mesh.mod.*.event.provisioner.failing",
"mesh.mod.*.event.provisioner.recovered",
},
@@ -128,7 +128,7 @@ func TestAConsumerWithSeveralFiltersIsHandedEveryMessage(t *testing.T) {
}
}()
followed := []string{"mesh.mod.gitea.event.pull.merged", "mesh.seat.node-build-agent.event.built",
followed := []string{"mesh.mod.gitea.event.pull.merged", "mesh.seat.node-backup.event.done",
"mesh.mod.postgres.event.provisioner.recovered", "mesh.mod.mesh-catalog.event.catching-up"}
var mu sync.Mutex
var sent []uint64
@@ -153,7 +153,7 @@ func TestAConsumerWithSeveralFiltersIsHandedEveryMessage(t *testing.T) {
mu.Unlock()
}
} else {
_, _ = pjs.Publish(fmt.Sprintf("mesh.seat.node-build-agent.event.log.build-%d-%d", w, i/50), make([]byte, 200))
_, _ = pjs.Publish(fmt.Sprintf("mesh.seat.node-backup.event.log.run-%d-%d", w, i/50), make([]byte, 200))
}
if rand.Intn(100) == 0 {
time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)