Say where a pending merge check is: queued since when, then building on which machine (hq issue 446)
mesh/merge-gate pass: builds gitea, mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request

The forge's holder set mesh/merge-gate pending as "the mesh is mapping this head onto its module graph" and nothing
changed it until the verdict, so a ten-minute controller check read as a stalled forge (issue 423). The holder now
says the head waits in the build queue since the local time it was announced; mesh-delivery follows the build
seat's started event and says the machine building it and since when, never over a verdict the forge holds.
This commit is contained in:
2026-10-11 03:14:06 +02:00
parent ebd940fcd0
commit 4254d17322
9 changed files with 271 additions and 7 deletions
+5 -4
View File
@@ -27,8 +27,8 @@
import { emit, on } from "@novox/mesh-sdk/events";
import { GiteaClient, movedSince } from "./client.js";
import { CHECK_CONTEXT, REPO_CHECK_CONTEXT, commentFor, headsToAnnounce, statusesFor, type Announced, type Checked,
type RepoCheckFacts, reposToLook } from "./pulls.js";
import { CHECK_CONTEXT, REPO_CHECK_CONTEXT, commentFor, headsToAnnounce, pendingWords, statusesFor, type Announced,
type Checked, type RepoCheckFacts, reposToLook } from "./pulls.js";
// Without a way to a token — configured, or mintable with the admin account (token.ts) — there is
// nothing to watch; log and stay quiet rather than crash the runtime. With one, the first poll mints
@@ -274,10 +274,11 @@ async function pollPulls(client: GiteaClient): Promise<void> {
});
// Said, so an operator knows the mesh was asked to check it.
console.log(`[gitea] announced ${key} at ${String(pull.head_sha).slice(0, 8)} to be checked before it merges`);
// Pending until the verdict comes, so the pull request says a check is running rather than nothing.
// Pending until the verdict comes, so the pull request says a check is running rather than nothing — and
// where it is: in the build queue from now, until mesh-delivery says a build machine took it (issue 446).
await client
.setCommitStatus(repo.owner, repo.name, String(pull.head_sha), {
state: "pending", context: CHECK_CONTEXT, description: "the mesh is mapping this head onto its module graph",
state: "pending", context: CHECK_CONTEXT, description: pendingWords(new Date()),
})
.catch((err) => console.error(`[gitea] ${key}: could not say a check is pending — ${err instanceof Error ? err.message : err}`));
}
+12
View File
@@ -18,6 +18,18 @@ export const CHECK_CONTEXT = "mesh/merge-gate";
/** The status context of the repository's own merge-check.sh, the check's second layer (novox/hq ADR 0237). */
export const REPO_CHECK_CONTEXT = "mesh/repo-check";
/**
* The pending words a head's merge check is announced with (novox/hq issue 446): where the check is, and since when,
* in the time the operator reads. They stood as "the mesh is mapping this head onto its module graph" for the whole
* check — the few seconds the controller maps the head and then the queue wait and the build — and readers took the
* forge for lagging (issue 423). The controller maps and queues the check in seconds, so from the announcement it waits
* in the build queue; mesh-delivery says "building on <machine> since …" when a build machine takes it.
*/
export function pendingWords(announced: Date): string {
const two = (n: number) => String(n).padStart(2, "0");
return `waiting in the build queue since ${two(announced.getHours())}:${two(announced.getMinutes())}`;
}
/** One layer of a check, judged. */
export interface Layer {
verdict: string;
+8
View File
@@ -146,3 +146,11 @@ test("a change plan is the gate's result: said on the status and, when it builds
const nothing = { ...c, plan: { ...plan, moved: [], tiers: [], machines: [], summary: "builds nothing" } };
assert.equal(commentFor(nothing), null);
});
// **A pending check says where it is** (novox/hq issue 446): announced, it waits in the build queue, since the time the
// operator reads — not "mapping this head onto its module graph" for the ten minutes of a controller check.
test("a head announced is pending in the build queue, since the local time it was announced", async () => {
const { pendingWords } = await import("../pulls.ts");
assert.equal(pendingWords(new Date(2026, 9, 11, 3, 8, 50)), "waiting in the build queue since 03:08");
assert.equal(pendingWords(new Date(2026, 9, 11, 14, 0, 0)), "waiting in the build queue since 14:00");
});