diff --git a/test/integration/mesh.test.ts b/test/integration/mesh.test.ts index 0a1102e..1d682ab 100644 --- a/test/integration/mesh.test.ts +++ b/test/integration/mesh.test.ts @@ -1017,3 +1017,62 @@ test("model access is answered by a record, and the key the mesh took is one it assert.equal(held.out.trim(), "0", `the key is in the open in ${where}`); } }); + +test("a new commit reaches a machine that is already running the old one", { + skip: skip || (!builder ? "set MESH_LAB_BUILDER to a built mesh-builder" : false), + timeout: 900_000, +}, async () => { + // novox/hq ADR 0010 names the real risk of replacing a pipeline with a comparison: losing the + // question "did my change go out?". This is that question, end to end — a commit, a build, a + // catalogue, and a machine that ends up running what the source says. + const repo = "/var/lib/mesh/builder/repositories/delivered"; + const write = async (what: string) => + await must("anchor", `mkdir -p ${repo} && printf %s '{"module":"delivered","version":"1",` + + `"resources":[{"id":"marker","type":"file","path":"/etc/delivered",` + + `"content":"${what}","mode":"0644"}]}' > ${repo}/module.json`); + + await write("first"); + await must("anchor", `cd ${repo} && git init -q . && git add -A && ` + + `git -c user.email=lab -c user.name=lab commit -qm first`); + await mesh(`build ${repo} --wait 300s`, 420_000); + await mesh("assign laptop delivered"); + await mesh("push laptop"); + await new Promise((r) => setTimeout(r, 15_000)); + assert.equal((await must("laptop", `cat /etc/delivered`)).trim(), "first"); + + // Nothing has moved, so nothing is behind — and it says so rather than doing nothing quietly. + assert.match(await mesh("build --behind"), /every module the mesh holds is what its source last had/); + + // Now the source moves. + await write("second"); + await must("anchor", `cd ${repo} && git add -A && ` + + `git -c user.email=lab -c user.name=lab commit -qm second`); + const moved = (await must("anchor", `cd ${repo} && git rev-parse HEAD`)).trim(); + await mesh(`module moved delivered ${moved}`); + + // The mesh says which module is behind, and by how much, before anything is built. + const behind = await mesh("status"); + assert.match(behind, /delivered/, `status does not name the module that moved:\n${behind}`); + assert.match(behind, /build --behind/, `status does not say how to catch up:\n${behind}`); + + // The loop, in one command: everything behind its source is built and recorded. + const built = await mesh("build --behind --wait 300s", 420_000); + assert.match(built, /delivered/, built); + + // The machine is still running the old one until it is told — the mesh changing its mind is + // not the same as a machine acting on it, and collapsing the two is how a mesh reports success + // for something that has not happened. + assert.equal((await must("laptop", `cat /etc/delivered`)).trim(), "first", + "the machine changed before anything was sent to it"); + + await mesh("push laptop"); + await new Promise((r) => setTimeout(r, 15_000)); + assert.equal((await must("laptop", `cat /etc/delivered`)).trim(), "second", + "the machine is still running what the source no longer says"); + + // And it is no longer out of date, which is the half that makes the answer trustworthy: a + // status that says "behind" for ever is one nobody reads. + const after = await mesh("status"); + assert.doesNotMatch(after, /delivered.*<.*[0-9a-f]{8}/, + `the module is still reported as behind after catching up:\n${after}`); +});