Files
mesh-catalog/modules/gitea/test/check-owed.test.ts
T
jschoubben 5a7076a6a2
mesh/delivery delivered
mesh/check checking 5a7076a6a25e on f836c021e14f
Take the checker's owed without waiting on the forge, so a large owed never times its watch out (issue 1726)
The runtime gives a watch's current values 10 s and waits for each to be taken; a forge write per key made ~200 keys outlast it at every attempt, so after node-tools restarted on novox at 23:25:58 the watch never held and no verdict reached mesh/check. Writes now run in the background, a few at a time, each key's in revision order.
2026-10-23 23:52:52 +02:00

178 lines
11 KiB
TypeScript

// **mesh/check is written from the checker's owed state, read by a watch** (ADR 0521; R4e round 2 finding R2-1; finding K1
// before it). No tool writes mesh/check, since a tool is told no caller and node-tools invokes every tool. Nor does an event:
// nothing in the delivery plan orders this module before the forge bridge, and a durable consumer whose filters change keeps
// its deliver policy, so an event said before this module consumed it was lost. A watch of the checker's state hands over
// the latest value of every key when it starts, whenever that is.
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
const sha = "0123456789abcdef0123456789abcdef01234567";
const key = (s = sha, repo = "novox/mesh-sdk", n = 12) => `${repo}.${n}.${s}.status=mesh/check`;
const owed = (over: Record<string, unknown> = {}) => ({ v: 1, act: "status", context: "mesh/check", state: "success",
text: "pass 0123456789ab on aaaaaaaaaaaa", target: "https://view.example/checks/g.1", ...over });
const put = (k: string, value: unknown, revision: number, current = false) =>
({ state: "mesh-delivery-checker.owed", key: k, op: "put" as const, value, revision, current });
function forge() {
const sent: string[] = [];
let failing = 0;
return {
sent,
fail: (n: number) => { failing = n; },
setCommitStatus: async (o: string, r: string, s: string, st: any) => {
if (failing > 0) { failing--; throw new Error("Gitea API /repos/x/statuses: 502 bad gateway"); }
sent.push(`${o}/${r}@${s.slice(0, 8)} ${st.context} ${st.state} ${st.description}${st.target_url ? " " + st.target_url : ""}`);
},
};
}
const noSleep = async () => {};
test("the checker's owed is what this module reads, and it consumes no check.status", async () => {
const { CHECKER_OWED } = await import("../check.ts");
assert.equal(CHECKER_OWED, "mesh-delivery-checker.owed");
const manifest = JSON.parse(readFileSync(new URL("../module.json", import.meta.url), "utf8"));
assert.ok(manifest.reads.includes(CHECKER_OWED), "gitea's manifest reads the checker's owed");
assert.ok(!manifest.consumes.some((c: string) => c.includes("check.status")), "no check.status is consumed");
});
test("an owed mesh/check writes the status on its head: its state, its line cut and its link", async () => {
const { CheckStatuses } = await import("../check.ts");
const f = forge();
const checks = new CheckStatuses(f, () => {}, noSleep);
assert.equal(await checks.apply(put(key(), owed(), 7, true)), "written");
const other = sha.replace(/0/g, "f");
assert.equal(await checks.apply(put(key(other, "novox/my=2erepo"), owed({ state: "pending", text: "y".repeat(300), target: "javascript:x" }), 8)), "written");
assert.equal(f.sent[0], "novox/mesh-sdk@01234567 mesh/check success pass 0123456789ab on aaaaaaaaaaaa https://view.example/checks/g.1");
assert.ok(f.sent[1].startsWith("novox/my.repo@f1234567 mesh/check pending yyyy"), f.sent[1]);
assert.ok(f.sent[1].length < 200, "the line is cut as every status's is");
assert.ok(!f.sent[1].includes("javascript"), "a link that is no web page is not written");
});
test("a key or value that is no owed mesh/check, and a delete, write nothing", async () => {
const { CheckStatuses } = await import("../check.ts");
const f = forge();
const checks = new CheckStatuses(f, () => {}, noSleep);
const cases = [
put(key(sha.slice(0, 12)), owed(), 1), put(`novox/mesh-sdk.12.${sha}.status=mesh/merge-gate`, owed(), 1),
put(`novox/mesh-sdk.12.${sha}.view`, owed(), 1), put(`nokey`, owed(), 1), put(`../x/y.12.${sha}.status=mesh/check`, owed(), 1),
put(key(), owed({ context: "mesh/delivery" }), 1), put(key(), owed({ act: "view" }), 1), put(key(), owed({ state: "green" }), 1),
put(key(), owed({ v: 2 }), 1), put(key(), null, 1), put(key(), "text", 1), put(key(), owed(), -1),
{ state: "mesh-delivery-checker.owed", key: key(), op: "delete" as const, revision: 2, current: false },
];
for (const c of cases) assert.equal(await checks.apply(c as any), "dropped", JSON.stringify(c));
assert.deepEqual(f.sent, []);
});
test("a revision older than one written on the key is dropped; the same one is written again", async () => {
const { CheckStatuses } = await import("../check.ts");
const f = forge();
const checks = new CheckStatuses(f, () => {}, noSleep);
await checks.apply(put(key(), owed({ state: "success" }), 10));
assert.equal(await checks.apply(put(key(), owed({ state: "pending" }), 9)), "dropped", "a stale pending never hides the verdict");
assert.equal(await checks.apply(put(key(), owed({ state: "success" }), 10)), "written");
assert.equal(await checks.apply(put(key(sha.replace(/1/g, "2")), owed({ state: "pending" }), 3)), "written", "another key is its own");
assert.equal(await checks.apply(put(key(), owed({ state: "pending" }), 11)), "written");
assert.deepEqual(f.sent.map((s) => s.split(" ").slice(0, 3).join(" ")), ["novox/mesh-sdk@01234567 mesh/check success",
"novox/mesh-sdk@01234567 mesh/check success", "novox/mesh-sdk@02234567 mesh/check pending", "novox/mesh-sdk@01234567 mesh/check pending"]);
});
test("a forge that refuses is asked again until it writes, unless a newer revision of the key came meanwhile", async () => {
const { CheckStatuses } = await import("../check.ts");
const f = forge();
const said: string[] = [];
const waits: number[] = [];
const checks = new CheckStatuses(f, (l) => said.push(l), async (ms) => { waits.push(ms); });
f.fail(2);
await checks.take(put(key(), owed({ state: "success" }), 5));
await checks.settled();
assert.deepEqual(f.sent.map((s) => s.split(" ")[2]), ["success"], "written on the third ask");
assert.deepEqual(waits, [2_000, 5_000]);
assert.match(said[0], /502.*asked again/);
// A refusal whose key moved on meanwhile is not asked again: the newer revision is written, the older never after it.
const g = forge();
let release: () => void = () => {};
const gate = new Promise<void>((r) => { release = r; });
const later = new CheckStatuses(g, () => {}, () => gate);
g.fail(1);
await later.take(put(key(), owed({ state: "pending" }), 6));
await later.take(put(key(), owed({ state: "success" }), 7));
release();
await later.settled();
assert.deepEqual(g.sent.map((s) => s.split(" ")[2]), ["success"]);
});
test("a watch refused at first is asked again, said once, and once granted writes the latest revision of each key", async () => {
const { CheckStatuses, watchOwed } = await import("../check.ts");
const f = forge();
const said: string[] = [];
const checks = new CheckStatuses(f, () => {}, noSleep);
// The checker's bucket as the bus keeps it: two revisions of one key written while the read was refused.
const kept = new Map<string, { value: unknown; revision: number }>();
kept.set(key(), { value: owed({ state: "pending" }), revision: 3 });
kept.set(key(), { value: owed({ state: "success" }), revision: 4 });
let asked = 0;
const watch = async (handler: (c: any) => Promise<void>) => {
asked++;
if (asked < 3) throw new Error("gitea keeps and reads no state called \"mesh-delivery-checker.owed\"");
for (const [k, e] of kept) await handler(put(k, e.value, e.revision, true));
};
await watchOwed(watch, checks, noSleep, (l) => said.push(l));
await checks.settled();
assert.equal(asked, 3);
assert.deepEqual(f.sent.map((s) => s.split(" ")[2]), ["success"], "only the latest revision is written");
assert.equal(said.filter((l) => /could not be bound/.test(l)).length, 1, `said once: ${said.join(" | ")}`);
assert.ok(said.some((l) => /bound after 3 attempts/.test(l)), said.join(" | "));
});
test("no tool writes mesh/check: gitea_check_status is gone and gitea_commit_status refuses it", async () => {
const delivery = await import("../delivery.ts");
assert.equal((delivery as Record<string, unknown>).checkStatusTool, undefined, "delivery.ts serves no gitea_check_status");
assert.throws(() => delivery.deliveryStatus("mesh/check", "success", "x"), /mesh-delivery-checker\.owed/);
const index = readFileSync(new URL("../index.ts", import.meta.url), "utf8");
assert.ok(!/gitea_check_status|checkStatusTool/.test(index), "index.ts serves no gitea_check_status");
});
test("the watch's current values are taken without waiting on the forge, so a slow forge never times the watch out (issue 1726)", async () => {
// The tool runtime hands a watch's current values one at a time and waits for each to be taken; it gives the whole set
// 10 s (mesh-tools StateTimeout) and otherwise ends the watch. On 2026-10-23 the owed held ~200 keys, each written to the
// forge before the next was handed over: the set took longer than 10 s, the watch ended at every attempt, and no
// verdict reached the forge's mesh/check again.
const { CheckStatuses, watchOwed } = await import("../check.ts");
const sent: string[] = [];
let release: () => void = () => {};
const slow = new Promise<void>((r) => { release = r; });
const f = { setCommitStatus: async (_o: string, _r: string, s: string, st: any) => { await slow; sent.push(`${s.slice(0, 4)} ${st.state}`); } };
const checks = new CheckStatuses(f, () => {}, noSleep);
const keys = Array.from({ length: 200 }, (_, i) => key(i.toString(16).padStart(4, "0") + sha.slice(4), "novox/mesh-sdk", i + 1));
let handedOver = 0;
let attempts = 0;
const watch = async (handler: (c: any) => Promise<void>) => {
if (++attempts > 1) return; // a second attempt is not what this test waits for: the first must hold
// The runtime's bound on the current values: the forge has answered none of them yet.
const timedOut = new Promise<"timeout">((r) => setTimeout(() => r("timeout"), 200));
const delivered = (async () => { for (const [i, k] of keys.entries()) { await handler(put(k, owed(), i + 1, true)); handedOver++; } return "delivered" as const; })();
if (await Promise.race([delivered, timedOut]) === "timeout") throw new Error("the current values of mesh-delivery-checker.owed did not arrive in 10s");
};
const said: string[] = [];
await watchOwed(watch, checks, noSleep, (l) => said.push(l));
assert.equal(handedOver, 200, "every current value was taken before the forge answered");
assert.ok(!said.some((l) => /could not be bound/.test(l)), said.join(" | "));
assert.equal(sent.length, 0, "nothing is written before the forge answers");
release();
await checks.settled();
assert.equal(sent.length, 200, "every owed status is written once the forge answers");
});
test("the writes a watch's start hands over go to the forge a few at a time, never all at once (issue 1726)", async () => {
const { CheckStatuses, WRITES_AT_ONCE } = await import("../check.ts");
let running = 0, most = 0, done = 0;
const f = { setCommitStatus: async () => { running++; most = Math.max(most, running); await new Promise((r) => setTimeout(r, 1)); running--; done++; } };
const checks = new CheckStatuses(f, () => {}, noSleep);
for (let i = 0; i < 50; i++) await checks.take(put(key(i.toString(16).padStart(4, "0") + sha.slice(4), "novox/mesh-sdk", i + 1), owed(), i + 1, true));
await checks.settled();
assert.equal(done, 50);
assert.equal(most, WRITES_AT_ONCE);
});