From 9b8b21ac176feac195041d215a711237f55a0092 Mon Sep 17 00:00:00 2001 From: jochen Date: Mon, 14 Sep 2026 23:36:05 +0200 Subject: [PATCH] E1 moves the module's source for real Reading the branch head and telling the mesh the source had moved there named the commit it had just built, so the mesh correctly answered that everything was current. Naming a different commit would not work either: staleness compares artifacts, not commits, deliberately, so that editing a comment in a shared base does not rebuild everything standing on it to arrive back where it started. So the step makes a real change and pushes it, and asserts the module comes back on a DIFFERENT artifact than it had. A test that writes to a branch is worth knowing about; the alternative is proving the loop by telling the mesh something untrue. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx --- one-node-mesh-report.json | 60 +++++++++++++------------- test/integration/one-node-mesh.test.ts | 52 ++++++++++++++++------ 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/one-node-mesh-report.json b/one-node-mesh-report.json index 9ac706f..a759bdd 100644 --- a/one-node-mesh-report.json +++ b/one-node-mesh-report.json @@ -1,13 +1,13 @@ { "scenario": "one-node-mesh", - "established": 12, + "established": 18, "of": 21, "steps": [ { "code": "R1", "title": "a bare machine becomes a mesh of one, raised by the installer", "status": "pass", - "seconds": 135, + "seconds": 133, "why": "" }, { @@ -56,28 +56,28 @@ "code": "P1", "title": "the mesh builds the shared base from source", "status": "pass", - "seconds": 87, + "seconds": 83, "why": "" }, { "code": "P2", "title": "the mesh builds and runs a store of its own", "status": "pass", - "seconds": 41, + "seconds": 47, "why": "" }, { "code": "P3", "title": "the mesh builds and runs its own catalogue", "status": "pass", - "seconds": 37, + "seconds": 26, "why": "" }, { "code": "P4", "title": "the mesh rebuilds its own control plane from source", "status": "pass", - "seconds": 35, + "seconds": 30, "why": "" }, { @@ -90,58 +90,58 @@ { "code": "N2", "title": "the machine has a packet filter, loaded from what modules declared", - "status": "fail", - "seconds": 0, - "why": "anchor: docker exec mesh-control /mesh-control assign anchor firewall\n\nmesh-control: no module of that name: firewall\n" + "status": "pass", + "seconds": 7, + "why": "" }, { "code": "U1", "title": "the mesh builds a module standing on that base", - "status": "skip", - "seconds": 0, - "why": "not attempted — N2 (the machine has a packet filter, loaded from what modules declared) did not succeed" + "status": "pass", + "seconds": 23, + "why": "" }, { "code": "U2", "title": "the mesh runs a broker for that module to talk to", - "status": "skip", - "seconds": 0, - "why": "not attempted — U1 (the mesh builds a module standing on that base) did not succeed" + "status": "pass", + "seconds": 20, + "why": "" }, { "code": "U3", "title": "the anchor runs the module the mesh built", - "status": "skip", - "seconds": 0, - "why": "not attempted — U2 (the mesh runs a broker for that module to talk to) did not succeed" + "status": "pass", + "seconds": 6, + "why": "" }, { "code": "V1", "title": "the control plane can describe the mesh, and what it says is true", - "status": "skip", - "seconds": 0, - "why": "not attempted — U3 (the anchor runs the module the mesh built) did not succeed" + "status": "pass", + "seconds": 1, + "why": "" }, { "code": "V2", "title": "the catalogue holds every module this mesh built", - "status": "skip", - "seconds": 0, - "why": "not attempted — V1 (the control plane can describe the mesh, and what it says is true) did not succeed" + "status": "fail", + "seconds": 1, + "why": "the catalogue does not hold mesh-tools, postgres — the mesh built them and its own record has no trace of it (novox/hq issue 050):\n{\"modules\":[{\"module\":\"amqp-ping\",\"commit\":\"e0c92195d4240841bfcf4b4a9ef869d5afeca331\",\"repository\":\"https://git.novox.be/novox/mesh-catalog.git\",\"path\":\"modules/amqp-ping\"},{\"module\":\"lavinmq\",\"commit\":\"e0c92195d4240841bfcf4b4a9ef869d5afeca331\",\"repository\":\"https://git.novox.be/novox/mesh-catalog.git\",\"path\":\"modules/lavinmq\"},{\"module\":\"mesh-control\",\"commit\":\"5062c36fc9efe159aa9706c0ca2c873351ef1ce0\",\"repository\":\"https://git.novox.be/novox/mesh-control.git\",\"path\":\"\"}]}\n\n+ actual - expected\n\n+ [\n+ 'mesh-tools',\n+ 'postgres'\n+ ]\n- []\n" }, { "code": "V3", "title": "the machine's networking is what the modules asked for", - "status": "skip", - "seconds": 0, - "why": "not attempted — V1 (the control plane can describe the mesh, and what it says is true) did not succeed" + "status": "pass", + "seconds": 1, + "why": "" }, { "code": "E1", "title": "a change to a module's source reaches the machine on its own", - "status": "skip", - "seconds": 0, - "why": "not attempted — V3 (the machine's networking is what the modules asked for) did not succeed" + "status": "fail", + "seconds": 1, + "why": "the mesh does not report a module behind its source:\n1 machine(s), all doing what they were told, all heard from, running what the mesh would send them, and every module current with its source\n" }, { "code": "E2", diff --git a/test/integration/one-node-mesh.test.ts b/test/integration/one-node-mesh.test.ts index c7c09bd..9f511cf 100644 --- a/test/integration/one-node-mesh.test.ts +++ b/test/integration/one-node-mesh.test.ts @@ -38,7 +38,8 @@ */ import { test, before, after } from "node:test"; import assert from "node:assert/strict"; -import { existsSync, writeFileSync } from "node:fs"; +import { existsSync, writeFileSync, appendFileSync } from "node:fs"; +import { execFileSync } from "node:child_process"; import { resolve } from "node:path"; import { loadScenario } from "../../src/declaration/parse.ts"; import { raise } from "../../src/lifecycle/raise.ts"; @@ -133,7 +134,7 @@ const CATALOGUED = "the catalogue holds every module this mesh built"; const NETWORK = "the machine's networking is what the modules asked for"; const FOLLOWS = "a change to a module's source reaches the machine on its own"; const SURVIVES = "the mesh comes back after the machine reboots"; -const MODULE = { module: "amqp-ping", repo: "mesh-catalog", path: "modules/amqp-ping" }; +const MODULE = { module: "amqp-ping", repo: "mesh-catalog", path: "modules/amqp-ping", container: "amqp-ping" }; const capability = await labIsUsable(); const binary = hostBinaryPath(); @@ -833,26 +834,51 @@ before(async () => { // machinery; this is what the machinery is for. await step("E1", FOLLOWS, NETWORK, async () => { const before = await mesh(`builds ${MODULE.module}`); - const wasPinned = before.match(/sha256:[0-9a-f]{64}/)?.[0] ?? ""; - assert.ok(wasPinned, `nothing is pinned to rebuild from:\n${before}`); + const was = before.match(/sha256:[0-9a-f]{64}/)?.[0] ?? ""; + assert.ok(was, `nothing is pinned to rebuild from:\n${before}`); - // The mesh is told its copy is older than the source. In life a push does this; here it is - // stated, because what is under test is what the mesh does next, not how it hears. - const head = (await must(CONTROL, `git ls-remote ${forgeUrl(MODULE.repo)} ` + - `${refFor(MODULE.repo)} | cut -f1`, 120_000)).trim(); - assert.match(head, /^[0-9a-f]{40}$/, `could not read the source's head: ${head}`); + // **The source has to actually move, and it cannot be faked.** + // + // The first version of this read the branch head and told the mesh the source had moved there + // — the same commit it had just built. The mesh answered, correctly, that everything was + // current. Naming some other commit would not work either: staleness compares ARTIFACTS, not + // commits, which is a deliberate choice so that editing a comment in a shared base does not + // rebuild everything standing on it to arrive back where it started. + // + // So this makes a real change to the module's source and pushes it. It is a test that writes + // to a branch, which is worth knowing about; the alternative is a test that proves the loop by + // telling the mesh something untrue. + const checkout = resolve(catalogDir, ".."); + const marker = `// changed by the one-node test at build ${was.slice(7, 19)}\n`; + const file = resolve(catalogDir, MODULE.module, "index.ts"); + await must(CONTROL, `true`); // keep the shape uniform; the change is made on this workstation + appendFileSync(file, marker); + // Path-scoped: `commit -am` would sweep whatever else is in the working tree into a commit + // this test is about to push. + execFileSync("git", ["-C", checkout, "add", file], { stdio: "pipe" }); + execFileSync("git", ["-C", checkout, "commit", "-q", "-m", + `Move ${MODULE.module}'s source, so the mesh has something to notice`], { stdio: "pipe" }); + execFileSync("git", ["-C", checkout, "push", "-q", "origin", refFor(MODULE.repo)], + { stdio: "pipe" }); + const head = execFileSync("git", ["-C", checkout, "rev-parse", "HEAD"], + { encoding: "utf8" }).trim(); + + // Now the mesh is told. In life a push notices itself; what is under test here is what the + // mesh does NEXT, not how it hears. await mesh(`module moved ${MODULE.module} ${head}`); - const behind = await mesh(`status`); assert.match(behind, /behind|build --behind/, - `the mesh does not report a module behind its source:\n${behind}`); + `the source moved and the mesh does not report the module behind it:\n${behind}`); await mesh(`build --behind --wait 1200s`, 1_500_000); const rolled = await mesh(`upgrade ${MODULE.module} roll-out`, 900_000); - await waitForContainer(CONTROL, MODULE.module); + await waitForContainer(CONTROL, MODULE.container); const after = await mesh(`builds ${MODULE.module}`); - assert.match(after, /sha256:[0-9a-f]{64}/, `nothing was pinned after the rebuild:\n${after}`); + const now = after.match(/sha256:[0-9a-f]{64}/)?.[0] ?? ""; + assert.notEqual(now, was, + `the module was rebuilt and came back on the same artifact, so nothing reached the machine:` + + `\n${after}`); return `${behind}\n${rolled}\n${after}`; });