Once main requires mesh/check, nothing writes mesh/merge-gate, and the merge tool, which judged on it, would refuse every merge. It now judges on the status the base requires: mesh/check's line names the trunk it judged, an out-of-date one is refused and nothing is asked (the checker re-checks on a trunk move, ADR 0447 §4); hq's mesh/repo-check is judged as before. The announcer, the verdict, a trunk's move and a build's start write the controller's two statuses only where a base still requires them, so hq keeps mesh/repo-check and no stale mesh/merge-gate appears anywhere else.
335 lines
19 KiB
TypeScript
335 lines
19 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
|
|
// **A pending check says where it is, and the verdict lands last** (novox/hq issue 446). This module is the one writer
|
|
// of a head's mesh/merge-gate: the announcement's pending words, "building on <machine> since …" when the build seat
|
|
// says a machine took the check, and the verdict. The forge keeps the newest status of a context, so a pending word
|
|
// written after the verdict hides it — a pass reads pending, a failure hides under "building".
|
|
|
|
const SHA = "a6b08de8507c1703528277e5cefc80548dbc9bb3";
|
|
|
|
// The build seat's `started` as a machine said it for novox/hq#309's check on 2026-10-11 (its log, read with the
|
|
// controller's `builds --log build-1791680941048622697`, opens at 03:09:01 on shanks); only the forge's address
|
|
// replaced. The shape is mesh-controller's link.BuildStart.
|
|
const startedOnShanks = { id: "build-1791680941048622697", repository: "http://127.0.0.1:20000/novox/hq.git",
|
|
ref: SHA, on: "shanks", at: "2026-10-11T01:09:01.204319Z" };
|
|
|
|
/** A forge that keeps the newest status per context, as gitea does, and whose writes take as long as a test says. */
|
|
function aForge() {
|
|
const kept: Record<string, { state: string; description: string; updated_at: string }> = {};
|
|
const written: string[] = [];
|
|
/** Every mesh/merge-gate status the forge was given, newest first, as gitea keeps each one. */
|
|
const history: { state: string; description: string; updated_at: string }[] = [];
|
|
let failNext = 0;
|
|
let hold: Promise<void> | null = null;
|
|
let release = () => {};
|
|
let readHold: Promise<void> | null = null;
|
|
let readRelease = () => {};
|
|
return {
|
|
kept, written, history,
|
|
/** The next writes fail: a request the forge did not take. */
|
|
failWrites(n = 1) {
|
|
failNext = n;
|
|
},
|
|
/** The next writes wait until released: a request in flight. */
|
|
holdWrites() {
|
|
hold = new Promise<void>((r) => (release = r));
|
|
},
|
|
release() {
|
|
hold = null;
|
|
release();
|
|
},
|
|
async setCommitStatus(_o: string, _r: string, _sha: string, s: { state: string; context: string; description: string }) {
|
|
if (hold) await hold;
|
|
if (failNext > 0) {
|
|
failNext--;
|
|
throw new Error("the forge answered 502");
|
|
}
|
|
kept[s.context] = { state: s.state, description: s.description, updated_at: new Date().toISOString() };
|
|
if (s.context === "mesh/merge-gate") history.unshift(kept[s.context]);
|
|
written.push(`${s.state} ${s.description}`);
|
|
},
|
|
/** Reads answer what the forge held when asked, as late as the reads are held: the answer of a request in flight. */
|
|
holdReads() {
|
|
readHold = new Promise<void>((r) => (readRelease = r));
|
|
},
|
|
releaseReads() {
|
|
readHold = null;
|
|
readRelease();
|
|
},
|
|
async gateOf(_o: string, _r: string, _sha: string) {
|
|
const held = kept["mesh/merge-gate"] ? { ...kept["mesh/merge-gate"] } : undefined;
|
|
if (readHold) await readHold;
|
|
return held;
|
|
},
|
|
async gateHistory(_o: string, _r: string, _sha: string) {
|
|
return history.map((h) => ({ ...h }));
|
|
},
|
|
};
|
|
}
|
|
|
|
/** The clock of the test: the head announced at 01:08:50Z. */
|
|
const announcedAt = () => new Date("2026-10-11T01:08:50Z");
|
|
|
|
test("a build machine taking the check is said on the head: building on which machine, since the local time", async () => {
|
|
const { GateWriter, buildingWords } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
const gates = new GateWriter(forge, announcedAt);
|
|
await gates.pending("novox", "hq", SHA);
|
|
assert.equal(await gates.started("novox", "hq", SHA, startedOnShanks), true);
|
|
assert.equal(forge.kept["mesh/merge-gate"].state, "pending");
|
|
assert.equal(forge.kept["mesh/merge-gate"].description, buildingWords("shanks", new Date(startedOnShanks.at)));
|
|
assert.equal(buildingWords("shanks", new Date(2026, 9, 11, 3, 9, 1)), "building on shanks since 03:09");
|
|
});
|
|
|
|
test("the verdict heard while a 'building' write is in flight lands last and stays", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
const gates = new GateWriter(forge, announcedAt);
|
|
await gates.pending("novox", "hq", SHA);
|
|
forge.holdWrites(); // the 'building' request is in flight when the verdict comes
|
|
const building = gates.started("novox", "hq", SHA, startedOnShanks);
|
|
await new Promise((r) => setImmediate(r));
|
|
const verdict = gates.verdict("novox", "hq", SHA, () =>
|
|
forge.setCommitStatus("novox", "hq", SHA, { state: "success", context: "mesh/merge-gate", description: "pass" }));
|
|
forge.release();
|
|
await Promise.all([building, verdict]);
|
|
assert.equal(`${forge.kept["mesh/merge-gate"].state} ${forge.kept["mesh/merge-gate"].description}`, "success pass",
|
|
`the verdict is not the newest status: ${forge.written.join(" | ")}`);
|
|
});
|
|
|
|
// The race the review of mesh-catalog#202 reproduced: a writer reads the forge's gate (pending), the verdict is written,
|
|
// then the writer writes "building" — and the pass reads pending until the next push. A head this process did not
|
|
// announce is the one case where the forge is read before writing.
|
|
test("the verdict written while a start's read of the forge is in flight stays the newest status", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
forge.kept["mesh/merge-gate"] = { state: "pending", description: "waiting", updated_at: "2026-10-11T01:08:50Z" };
|
|
const gates = new GateWriter(forge, announcedAt);
|
|
forge.holdReads();
|
|
const building = gates.started("novox", "hq", SHA, startedOnShanks);
|
|
await new Promise((r) => setImmediate(r));
|
|
const verdict = gates.verdict("novox", "hq", SHA, () =>
|
|
forge.setCommitStatus("novox", "hq", SHA, { state: "success", context: "mesh/merge-gate", description: "pass" }));
|
|
await new Promise((r) => setImmediate(r));
|
|
forge.releaseReads();
|
|
await Promise.all([building, verdict]);
|
|
assert.equal(`${forge.kept["mesh/merge-gate"].state} ${forge.kept["mesh/merge-gate"].description}`, "success pass",
|
|
`the verdict is not the newest status: ${forge.written.join(" | ")}`);
|
|
});
|
|
|
|
test("the verdict heard while a 'building' write still waits its turn: the 'building' is never written", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
const gates = new GateWriter(forge, announcedAt);
|
|
forge.holdWrites(); // the pending words are still being written
|
|
const pending = gates.pending("novox", "hq", SHA);
|
|
const building = gates.started("novox", "hq", SHA, startedOnShanks);
|
|
const verdict = gates.verdict("novox", "hq", SHA, () =>
|
|
forge.setCommitStatus("novox", "hq", SHA, { state: "failure", context: "mesh/merge-gate", description: "fail: 0 of 4 compose" }));
|
|
forge.release();
|
|
await Promise.all([pending, verdict]);
|
|
assert.equal(await building, false);
|
|
assert.equal(forge.kept["mesh/merge-gate"].state, "failure");
|
|
assert.ok(!forge.written.some((w) => w.includes("building")), forge.written.join(" | "));
|
|
});
|
|
|
|
test("a start heard after the verdict writes nothing", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
const gates = new GateWriter(forge, announcedAt);
|
|
await gates.pending("novox", "hq", SHA);
|
|
await gates.verdict("novox", "hq", SHA, () =>
|
|
forge.setCommitStatus("novox", "hq", SHA, { state: "success", context: "mesh/merge-gate", description: "pass" }));
|
|
assert.equal(await gates.started("novox", "hq", SHA, startedOnShanks), false);
|
|
assert.equal(forge.kept["mesh/merge-gate"].state, "success");
|
|
});
|
|
|
|
test("a start from before the check now pending (a run before a recheck, the stream's history) writes nothing", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
let now = announcedAt();
|
|
const gates = new GateWriter(forge, () => now);
|
|
await gates.pending("novox", "hq", SHA);
|
|
await gates.verdict("novox", "hq", SHA, () =>
|
|
forge.setCommitStatus("novox", "hq", SHA, { state: "failure", context: "mesh/merge-gate", description: "error" }));
|
|
now = new Date("2026-10-11T01:30:00Z"); // checked again: the recheck's pending, as the tool writes it (issue 456)
|
|
await gates.rechecked("novox", "hq", SHA, { state: "pending", context: "mesh/merge-gate", description: "checking again: flaky" });
|
|
assert.equal(await gates.started("novox", "hq", SHA, startedOnShanks), false, "the first run's start");
|
|
assert.equal(forge.kept["mesh/merge-gate"].description, "checking again: flaky");
|
|
assert.equal(await gates.started("novox", "hq", SHA, { ...startedOnShanks, at: "2026-10-11T01:31:00Z" }), true,
|
|
"the second run's start");
|
|
});
|
|
|
|
// **A recheck's pending goes through the one writer** (novox/hq issue 456). mesh-delivery's recheck puts the gate back
|
|
// to pending through the gitea_commit_status tool; before, that write bypassed the head's chain: the first run's
|
|
// verdict mark stayed set, so the new run never said "building", and an old run's verdict could land over the pending.
|
|
|
|
/** The ask of a check, as the controller's link.NewBuildID names it: when it was asked, in unix nanoseconds. */
|
|
const askOf = (at: string) => `build-${BigInt(Date.parse(at)) * 1_000_000n}`;
|
|
const firstRun = askOf("2026-10-11T01:09:01Z");
|
|
const recheckAt = new Date("2026-10-11T01:30:00Z");
|
|
const secondRun = askOf("2026-10-11T01:30:02Z");
|
|
const checkingAgain = { state: "pending" as const, context: "mesh/merge-gate", description: "checking again: main moved on" };
|
|
const writeOf = (forge: ReturnType<typeof aForge>, state: string, description: string) => () =>
|
|
forge.setCommitStatus("novox", "hq", SHA, { state, context: "mesh/merge-gate", description });
|
|
const gateOf = (forge: ReturnType<typeof aForge>) =>
|
|
`${forge.kept["mesh/merge-gate"].state} ${forge.kept["mesh/merge-gate"].description}`;
|
|
|
|
test("an old run's verdict redelivered after a recheck's pending is not written over it", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
let now = announcedAt();
|
|
const gates = new GateWriter(forge, () => now);
|
|
await gates.pending("novox", "hq", SHA);
|
|
forge.failWrites(); // the first run's verdict is not taken: the event is offered again
|
|
await assert.rejects(gates.verdict("novox", "hq", SHA, writeOf(forge, "success", "pass"), firstRun));
|
|
now = recheckAt;
|
|
await gates.rechecked("novox", "hq", SHA, checkingAgain);
|
|
await gates.verdict("novox", "hq", SHA, writeOf(forge, "success", "pass"), firstRun); // redelivered
|
|
assert.equal(gateOf(forge), "pending checking again: main moved on", forge.written.join(" | "));
|
|
// The recheck's own verdict is written.
|
|
await gates.verdict("novox", "hq", SHA, writeOf(forge, "failure", "fail: 0 of 4 compose"), secondRun);
|
|
assert.equal(gateOf(forge), "failure fail: 0 of 4 compose");
|
|
});
|
|
|
|
test("an old run's verdict in flight when the recheck comes lands before the recheck's pending", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
let now = announcedAt();
|
|
const gates = new GateWriter(forge, () => now);
|
|
await gates.pending("novox", "hq", SHA);
|
|
forge.holdWrites(); // the first run's verdict is being written when the recheck comes
|
|
const verdict = gates.verdict("novox", "hq", SHA, writeOf(forge, "success", "pass"), firstRun);
|
|
await new Promise((r) => setImmediate(r));
|
|
now = recheckAt;
|
|
const pending = gates.rechecked("novox", "hq", SHA, checkingAgain);
|
|
forge.release();
|
|
await Promise.all([verdict, pending]);
|
|
assert.equal(gateOf(forge), "pending checking again: main moved on", forge.written.join(" | "));
|
|
});
|
|
|
|
test("a rechecked head says where its new run is: building on which machine", async () => {
|
|
const { GateWriter, buildingWords } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
let now = announcedAt();
|
|
const gates = new GateWriter(forge, () => now);
|
|
await gates.pending("novox", "hq", SHA);
|
|
await gates.verdict("novox", "hq", SHA, writeOf(forge, "success", "pass"), firstRun);
|
|
now = recheckAt;
|
|
await gates.rechecked("novox", "hq", SHA, checkingAgain);
|
|
const at = "2026-10-11T01:30:03Z";
|
|
assert.equal(await gates.started("novox", "hq", SHA, { ...startedOnShanks, id: secondRun, at }), true);
|
|
assert.equal(forge.kept["mesh/merge-gate"].description, buildingWords("shanks", new Date(at)));
|
|
});
|
|
|
|
test("the recheck's own verdict is written, even asked a little before the pending by another machine's clock", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
const gates = new GateWriter(forge, () => recheckAt);
|
|
await gates.rechecked("novox", "hq", SHA, checkingAgain);
|
|
await gates.verdict("novox", "hq", SHA, writeOf(forge, "success", "pass"), askOf("2026-10-11T01:29:30Z"));
|
|
assert.equal(gateOf(forge), "success pass");
|
|
// A verdict whose ask cannot be read is written: a verdict is never lost to what cannot be weighed.
|
|
await gates.rechecked("novox", "hq", SHA, checkingAgain);
|
|
await gates.verdict("novox", "hq", SHA, writeOf(forge, "failure", "fail"), "an id of another shape");
|
|
assert.equal(gateOf(forge), "failure fail");
|
|
});
|
|
|
|
test("after a restart, an old run's verdict is weighed against the check the forge's history holds open", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
// What the forge kept before this process started: the announcement, the first run's verdict never written,
|
|
// the recheck's pending, and the second run's "building" over it.
|
|
forge.history.unshift({ state: "pending", description: "waiting in the build queue since 03:08", updated_at: "2026-10-11T01:08:50Z" });
|
|
forge.history.unshift({ state: "pending", description: "checking again: main moved on", updated_at: "2026-10-11T01:30:00Z" });
|
|
forge.history.unshift({ state: "pending", description: "building on shanks since 03:30", updated_at: "2026-10-11T01:30:05Z" });
|
|
forge.kept["mesh/merge-gate"] = forge.history[0];
|
|
const gates = new GateWriter(forge, announcedAt);
|
|
await gates.verdict("novox", "hq", SHA, writeOf(forge, "success", "pass"), firstRun);
|
|
assert.equal(forge.kept["mesh/merge-gate"].description, "building on shanks since 03:30", forge.written.join(" | "));
|
|
await gates.verdict("novox", "hq", SHA, writeOf(forge, "failure", "fail"), secondRun);
|
|
assert.equal(gateOf(forge), "failure fail");
|
|
});
|
|
|
|
test("a recheck the forge does not take fails its caller, and the head is as it was", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
let now = announcedAt();
|
|
const gates = new GateWriter(forge, () => now);
|
|
await gates.pending("novox", "hq", SHA);
|
|
forge.failWrites();
|
|
await assert.rejects(gates.verdict("novox", "hq", SHA, writeOf(forge, "success", "pass"), firstRun));
|
|
now = recheckAt;
|
|
forge.failWrites();
|
|
await assert.rejects(gates.rechecked("novox", "hq", SHA, checkingAgain));
|
|
// The recheck was refused, so its check is not asked: the first run's verdict, offered again, is the head's.
|
|
await gates.verdict("novox", "hq", SHA, writeOf(forge, "success", "pass"), firstRun);
|
|
assert.equal(gateOf(forge), "success pass");
|
|
});
|
|
|
|
test("the status tool writes the merge check's pending through the writer, and the mesh's other statuses as they were", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const { commitStatusTool } = await import("../delivery.ts");
|
|
const forge = aForge();
|
|
const direct: string[] = [];
|
|
const gates = new GateWriter(forge, () => recheckAt);
|
|
const tool = commitStatusTool({
|
|
setCommitStatus: async (_o, _r, _sha, s) => void direct.push(`${s.context} ${s.state}`),
|
|
gates,
|
|
});
|
|
await gates.verdict("novox", "hq", SHA, writeOf(forge, "success", "pass"), firstRun);
|
|
await tool.run({ owner: "novox", repo: "hq", sha: SHA, context: "mesh/merge-gate", state: "pending", description: "checking again: x" });
|
|
await tool.run({ owner: "novox", repo: "hq", sha: SHA, context: "mesh/repo-check", state: "pending", description: "checking again: x" });
|
|
await tool.run({ owner: "novox", repo: "hq", sha: SHA, context: "mesh/delivery", state: "success", description: "delivered" });
|
|
assert.equal(gateOf(forge), "pending checking again: x");
|
|
assert.equal(forge.kept["mesh/repo-check"].state, "pending");
|
|
assert.deepEqual(direct, ["mesh/delivery success"]);
|
|
// The redelivered first run's verdict is the writer's to drop.
|
|
await gates.verdict("novox", "hq", SHA, writeOf(forge, "success", "pass"), firstRun);
|
|
assert.equal(gateOf(forge), "pending checking again: x");
|
|
// A head named short is another head to the writer: refused, nothing sent.
|
|
await assert.rejects(tool.run({ owner: "novox", repo: "hq", sha: SHA.slice(0, 12), context: "mesh/merge-gate", state: "pending" }),
|
|
/whole sha/);
|
|
await assert.rejects(tool.run({ owner: "novox", repo: "hq", sha: SHA, context: "mesh/merge-gate", state: "success" }),
|
|
/set by its verdict only/);
|
|
});
|
|
|
|
test("when a check was asked is read from its id; the opening of the check now pending from the gate's history", async () => {
|
|
const { askedAt, openedAt } = await import("../gate.ts");
|
|
assert.equal(askedAt("build-1791680941048622697"), Date.parse("2026-10-11T01:09:01.048Z"));
|
|
assert.equal(askedAt("build-mesh-catalog"), undefined);
|
|
assert.equal(askedAt(undefined), undefined);
|
|
assert.equal(openedAt([{ state: "pending", description: "building on ace since 03:31", updated_at: "2026-10-11T01:31:00Z" },
|
|
{ state: "pending", description: "checking again: x", updated_at: "2026-10-11T01:30:00Z" }]), Date.parse("2026-10-11T01:30:00Z"));
|
|
assert.equal(openedAt([{ state: "success", description: "pass", updated_at: "2026-10-11T01:30:00Z" }]), undefined);
|
|
assert.equal(openedAt([]), undefined);
|
|
});
|
|
|
|
test("a head this process did not announce (it restarted) is said only while the forge holds its gate pending", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
forge.kept["mesh/merge-gate"] = { state: "success", description: "pass", updated_at: "2026-10-11T01:20:00Z" };
|
|
const gates = new GateWriter(forge, announcedAt);
|
|
assert.equal(await gates.started("novox", "hq", SHA, startedOnShanks), false);
|
|
forge.kept["mesh/merge-gate"] = { state: "pending", description: "waiting", updated_at: "2026-10-11T01:08:50Z" };
|
|
assert.equal(await gates.started("novox", "hq", SHA, startedOnShanks), true);
|
|
});
|
|
|
|
test("a repository as the mesh clones it is told from another", async () => {
|
|
const { clonedAs } = await import("../gate.ts");
|
|
assert.equal(clonedAs("http://127.0.0.1:20000/novox/hq.git", "novox", "hq"), true);
|
|
assert.equal(clonedAs("http://127.0.0.1:20000/novox/mesh-hq.git", "novox", "hq"), false);
|
|
assert.equal(clonedAs("http://127.0.0.1:20000/novox/mesh-controller.git", "novox", "hq"), false);
|
|
});
|
|
|
|
// Issue 1301: a build machine's start on a head that holds no mesh/merge-gate (its base requires none, so none was written)
|
|
// says nothing: no "building on" status appears on a main that does not require the gate.
|
|
test("a start on a head with no gate on the forge writes nothing", async () => {
|
|
const { GateWriter } = await import("../gate.ts");
|
|
const forge = aForge();
|
|
const gates = new GateWriter(forge, announcedAt);
|
|
assert.equal(await gates.started("novox", "hq", SHA, startedOnShanks), false);
|
|
assert.equal(forge.kept["mesh/merge-gate"], undefined);
|
|
});
|