Files
mesh-catalog/modules/lab/test/base.test.ts
T
jochen ff9d3ee6a0
mesh/merge-gate head ff9d3ee6a06c merged into trunk ebf7f3773dc7 — pass: builds lab → shanks; no bus step; every machine composes with the change as it did…
mesh/repo-check head ff9d3ee6a06c merged into trunk ebf7f3773dc7 — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery superseded: a newer head of the same pull request
lab: name the base image's age, refuse a stale one, and rebuild it (issue 698)
A bed machine carries the package database its base image was built with,
and the mirrors stop serving it within weeks: genesis-keeps-its-bus failed
on a 404 from an image 44 days old, with nothing saying its age and no mesh
tool to rebuild it. lab_check and lab_run now say the age, lab_run refuses
an image older than 14 days and names the remedy, and lab_base reads the
image and rebuilds it with mesh-lab's base build when asked.
2026-10-14 03:21:23 +02:00

99 lines
5.7 KiB
TypeScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { BASE_IMAGE_ALIAS, BASE_MAX_AGE_DAYS, baseImageFrom, describeBase, runRefusal, staleBase } from "../tools/base.ts";
import { baseScript, buildingBase, listBaseBuilds, listRuns } from "../tools/runs.ts";
// Defends novox/hq issue 698: the lab's base image was 44 days old, a bed machine raised from it carried a
// package database the mirrors no longer served, and genesis-keeps-its-bus stopped on a 404 for
// postgresql-libs. Nothing said how old the image was, nothing refused it, and no mesh tool rebuilt it.
const now = new Date("2026-10-14T01:00:00Z");
const listed = (uploaded: string, alias = BASE_IMAGE_ALIAS) => JSON.stringify([
{ aliases: [{ name: "images:other" }], uploaded_at: "2026-10-13T00:00:00Z" },
{ aliases: [{ name: alias }], uploaded_at: uploaded, created_at: "2026-08-01T00:00:00Z" },
]);
test("the base image's age is read from when this machine made it", () => {
const b = baseImageFrom(listed("2026-08-31T09:00:00Z"), now);
assert.equal(b.exists, true);
assert.equal(b.built, "2026-08-31T09:00:00.000Z");
assert.equal(b.ageDays, 43);
assert.match(describeBase(b), /built 2026-08-31T09:00:00.000Z \(43 day\(s\) ago; the bound is 14\)/);
});
test("an image older than the bound refuses a run, naming the tool that rebuilds it", () => {
const stale = baseImageFrom(listed("2026-08-31T09:00:00Z"), now);
const reason = runRefusal(stale);
assert.ok(reason, "a 43-day-old base image was not refused");
assert.match(reason, /older than the lab's bound of 14 days/);
assert.match(reason, /lab_base \{"rebuild": true\}/);
assert.equal(staleBase(stale), reason);
});
test("an image within the bound runs", () => {
const fresh = baseImageFrom(listed(new Date(now.getTime() - BASE_MAX_AGE_DAYS * 86_400_000).toISOString()), now);
assert.equal(fresh.ageDays, BASE_MAX_AGE_DAYS);
assert.equal(runRefusal(fresh), undefined);
});
test("a missing image, or one whose age cannot be read, is said and not refused", () => {
const none = baseImageFrom(listed("2026-10-01T00:00:00Z", "something-else"), now);
assert.equal(none.exists, false);
assert.equal(runRefusal(none), undefined);
assert.match(describeBase(none), /is not on this machine; rebuild it with lab_base/);
const unread = baseImageFrom(undefined, now);
assert.equal(runRefusal(unread), undefined);
assert.match(describeBase(unread), /its age is not known/);
});
test("no run starts while the base image is rebuilt", () => {
const fresh = baseImageFrom(listed("2026-10-13T00:00:00Z"), now);
const reason = runRefusal(fresh, { id: "base-x", started: "2026-10-14T00:59:00Z" });
assert.match(reason ?? "", /is being rebuilt \(base-x/);
});
test("a rebuild is mesh-lab's base build from the forge's main, with no secret in its script", () => {
const s = baseScript("/work/base-x", "https://forge.invalid", "/work/.base-x.git-credentials");
assert.match(s, /clone --quiet --depth 1 'https:\/\/forge.invalid\/novox\/mesh-lab.git'/);
assert.match(s, /src\/cli.ts base build/);
assert.ok(s.indexOf("rm -f '/work/.base-x.git-credentials'") < s.indexOf("npm ci"), "the credential outlives the clone");
});
test("a rebuild is known by its status, and is not a run", () => {
const work = mkdtempSync(join(tmpdir(), "lab-base-"));
try {
mkdirSync(join(work, "base-1"));
writeFileSync(join(work, "base-1", "status.json"), JSON.stringify({ id: "base-1", state: "built", started: "2026-10-13T00:00:00Z" }));
mkdirSync(join(work, "base-2"));
// A build whose process is gone while it says building was lost, and holds nothing up.
writeFileSync(join(work, "base-2", "status.json"), JSON.stringify({ id: "base-2", state: "building", started: "2026-10-14T00:00:00Z", pid: 2 ** 22 + 7 }));
assert.deepEqual(listBaseBuilds(work).map((b) => `${b.id} ${b.state}`), ["base-2 lost", "base-1 built"]);
assert.equal(buildingBase(work), undefined);
writeFileSync(join(work, "base-2", "status.json"), JSON.stringify({ id: "base-2", state: "building", started: "2026-10-14T00:00:00Z", pid: process.pid }));
assert.equal(buildingBase(work)?.id, "base-2");
assert.deepEqual(listRuns(work), []);
assert.ok(readFileSync(join(work, "base-1", "status.json"), "utf8"));
} finally {
rmSync(work, { recursive: true, force: true });
}
});
// The tools themselves, as written in index.ts (it registers with the SDK when imported, so it is read).
test("lab_run refuses on the base before it starts anything, lab_check names its age, lab_base reads unless asked", () => {
const index = readFileSync(new URL("../tools/index.ts", import.meta.url), "utf8");
const run = index.slice(index.indexOf('name: "lab_run"'), index.indexOf('name: "lab_base"'));
assert.ok(run.indexOf("runRefusal(base, buildingBase(work))") > 0, "lab_run does not ask about the base");
assert.ok(run.indexOf("runRefusal(") < run.indexOf("start(work"), "lab_run starts before it asks about the base");
const check = index.slice(index.indexOf('name: "lab_check"'), index.indexOf('name: "lab_run"'));
assert.match(check, /if \(stale\) problems\.push\(stale\)/);
assert.match(check, /describeBase\(base\)/);
const base = index.slice(index.indexOf('name: "lab_base"'), index.indexOf('name: "lab_status"'));
assert.ok(base.indexOf("if (args.rebuild !== true) return reading;") > 0, "lab_base does not read by default");
assert.ok(base.indexOf("if (args.rebuild !== true) return reading;") < base.indexOf("startBaseBuild("), "lab_base rebuilds unasked");
assert.ok(base.indexOf("running(work)") < base.indexOf("startBaseBuild("), "lab_base rebuilds under a run");
});