From 5ded8e2a8e8976906bfa99e761a3a1d392ae9bb1 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 17 Sep 2026 22:55:00 +0200 Subject: [PATCH] The bed keeps its genesis warm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MESH_LAB_WARM=1 snapshots the post-genesis, both-nodes-enrolled state and restores it in seconds on later runs — refused, not silently rebuilt, when the commits have moved (src/warm.ts, the mechanism mesh.test.ts already uses and this session had ignored). Fresh stays the default. https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx --- .../built-store-cross-node.test.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/integration/built-store-cross-node.test.ts b/test/integration/built-store-cross-node.test.ts index 72fb0fe..7f95ac1 100644 --- a/test/integration/built-store-cross-node.test.ts +++ b/test/integration/built-store-cross-node.test.ts @@ -34,6 +34,7 @@ import { destroy, exec, push } from "../../src/lifecycle/operate.ts"; import { bootstrapBinaryPath, hostBinaryPath, HOST_PATH } from "../../src/lifecycle/place.ts"; import { labIsUsable, destroyAll, foundationBundle } from "./harness.ts"; import { genesis, type GenesisResult } from "./genesis.ts"; +import { keep, ready, returnTo } from "../../src/warm.ts"; const SCENARIO = "built-store-cross-node"; const CONTROL = "anchor"; @@ -50,6 +51,13 @@ const catalogDir = process.env["MESH_LAB_CATALOG"] ?? ""; const source = process.env["MESH_LAB_SOURCE"] ?? ""; const sourceRef = process.env["MESH_LAB_SOURCE_REF"] ?? ""; const KEEP = !!process.env["MESH_LAB_KEEP"]; +/** + * MESH_LAB_WARM=1: restore the post-genesis, both-nodes-enrolled state from a snapshot instead of + * re-running the installer (minutes -> seconds), refused — not silently rebuilt — when the commits + * it was built from have moved (src/warm.ts). Fresh stays the default: a run that must MEAN + * something raises from nothing. + */ +const warming = process.env["MESH_LAB_WARM"] === "1"; function forgeUrl(repo: string): string { const override = process.env[`MESH_LAB_SOURCE_${repo.toUpperCase().replaceAll("-", "_")}`]; @@ -151,6 +159,21 @@ async function waitForContainer(node: string, container: string, seconds = 300): before(async () => { if (skip) return; + if (warming) { + const said = await ready(SCENARIO); + if (said.use === "restore") { + instanceId = said.instanceId; + const seconds = await returnTo(instanceId); + // A snapshot captures disk, not memory: the restore reboots. anchor's host is a systemd + // service (genesis hostService) and comes back on its own; node2's was hand-started. + await must(NODE, `pgrep -x mesh-host >/dev/null || (nohup ${HOST_PATH} run > /var/log/mesh-host.log 2>&1 & sleep 3)`); + const back = (await on(CONTROL, `docker ps --format '{{.Names}}'`)).out; + assert.match(back, /mesh-controller/, `the control plane did not come back after restore:\n${back}`); + console.log(`warm: returned ${instanceId} to its post-genesis state in ${seconds.toFixed(1)}s`); + return; + } + console.log(`warm: raising fresh — ${said.why}`); + } const bed = await raise(loadScenario(`scenarios/${SCENARIO}.yml`), { onProgress: (m) => console.log(`raise: ${m}`), }); @@ -188,9 +211,17 @@ before(async () => { const said = await must(NODE, `${HOST_PATH} enrol --token ${quote(token)}`); assert.match(said, new RegExp(`enrolled as ${NODE}`), said); await must(NODE, `nohup ${HOST_PATH} run > /var/log/mesh-host.log 2>&1 & sleep 3`); + + // The expensive, deterministic part is done: genesis ran and both machines are enrolled. Keep it. + if (warming) { + const warm = await keep(SCENARIO, instanceId); + console.log(`warm: ${warm.instanceId} kept, against ` + + Object.entries(warm.against).map(([n, c]) => `${n} ${c}`).join(", ")); + } }, { timeout: 7_200_000 }); after(async () => { + if (warming) { console.log(`warm — ${instanceId} stays; \`mesh-lab warm cool\` retires it`); return; } if (KEEP) { console.log(`MESH_LAB_KEEP set — leaving ${instanceId} standing`); return; } if (instanceId) await destroy(instanceId); await destroyAll(`${SCENARIO}-`);