Files
mesh-catalog/modules/gitea/test/check-owed.test.ts
T
jschoubben 896bd88380
mesh/repo-check head 896bd88380b3 merged into trunk a21da33d4857 beside mesh-controller#441@d51324df — pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.s…
mesh/merge-gate head 896bd88380b3 merged into trunk a21da33d4857 — pass: group feat/r4e composed: every machine composes with the change as it did without …
mesh/delivery covered after its stop: a later walk that contains it was delivered: plan-1792707174627079192 (novox/mesh-catalog at 89e15c7b into main, do…
mesh/delivery-group group feat/r4e delivered: every member is delivered
mesh/check failed: ace: novox/mesh-catalog#457: modules/gitea/go.mod names mesh-sdk 104533e8c0c9, which is neither on mesh-sdk's trunk nor the group's…
Write mesh/check from the checker's owed state, read by a watch (R2-1)
R4e round 2 finding R2-1, ADR 0521: the delivery plan draws no edge that
orders the forge module before the forge bridge, and the forge module's
durable consumer keeps its deliver policy when its filters change, so a
check.status the bridge emitted before the forge module consumed it was lost.
An in-memory revision guard also forgot itself on a restart.

The forge module now reads mesh-delivery-checker.owed, which the checker
announces readable, by a watch: the watch hands over the latest value of every
key whenever the module starts, so a status written while it was away is
written then, and only the latest. Each mesh/check is written once per
revision, an older one dropped; a forge refusal is asked again on a bound
until written or overtaken; a refused read is said once and asked again until
granted. No tool writes mesh/check, and no check.status is consumed.
2026-10-22 21:45:53 +02:00

136 lines
8.1 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");
});