The lab's installer carries a builder, and genesis is told what to build

Both beds now pass a repository and a commit, and refuse to run without them
rather than raising a machine the installer cannot finish.
This commit is contained in:
2026-09-13 04:24:18 +02:00
parent 1ba237a634
commit 607ea241c7
5 changed files with 49 additions and 12 deletions
+11 -7
View File
@@ -91,21 +91,25 @@ export function planned(env: NodeJS.ProcessEnv = process.env): Build[] {
builds.push({
what: "installer",
in: where["mesh-host"],
argv: ["make", "bootstrap", `IMAGE=${controlPlaneImage(env)}`, `BOOTSTRAP_OUT=${installer}`],
argv: ["make", "bootstrap", `IMAGE=${carriedImage(env)}`, `BOOTSTRAP_OUT=${installer}`],
});
}
return builds;
}
/**
* The control-plane image the installer carries.
* The image the installer carries.
*
* `mesh-control:development` is what mesh-control's `make image` tags, and what the scenarios name
* — one tag, said in one place. It is overridable because a release installer carries a release
* image, and nothing about that is the lab's business.
* **It is the builder, not the control plane** (novox/hq ADR 0073). The installer used to carry the
* thing it was going to run and now carries the thing that makes it, so a raised mesh holds a
* control plane it built from a repository and a commit rather than one it was handed.
*
* `mesh-builder:development` is what mesh-control's `make builder-image` tags — one tag, said in
* one place. Overridable because a release installer carries a release image, and nothing about
* that is the lab's business.
*/
export function controlPlaneImage(env: NodeJS.ProcessEnv = process.env): string {
return env["MESH_LAB_CONTROL_IMAGE"] ?? "mesh-control:development";
export function carriedImage(env: NodeJS.ProcessEnv = process.env): string {
return env["MESH_LAB_CARRIED_IMAGE"] ?? "mesh-builder:development";
}
/** rebuild runs the plan, and throws on the first failure rather than testing a stale artifact. */
+10 -1
View File
@@ -34,6 +34,11 @@ const binary = hostBinaryPath();
const installer = bootstrapBinaryPath();
const bundle = process.env["MESH_LAB_BUNDLE"] ?? "";
const catalogDir = process.env["MESH_LAB_CATALOG"] ?? "";
// What the installer is told to build. It carries a builder rather than a finished control plane
// (novox/hq ADR 0073), so genesis needs a repository and a commit — and a commit rather than a
// branch, because what is cloned is the trust anchor for everything this mesh will ever run.
const source = process.env["MESH_LAB_SOURCE"] ?? "";
const sourceRef = process.env["MESH_LAB_SOURCE_REF"] ?? "";
const KEEP = !!process.env["MESH_LAB_KEEP"];
const FIXED_ID = process.env["MESH_LAB_INSTANCE_ID"] ?? (KEEP ? "genesis-single-live" : undefined);
@@ -41,7 +46,9 @@ const skip =
!capability.usable ? capability.why :
!binary ? "MESH_LAB_HOST_BINARY is not set to a built mesh-host" :
!installer ? "MESH_LAB_BOOTSTRAP_BINARY is not set to a built mesh-bootstrap (mesh-host `make " +
"bootstrap IMAGE=mesh-control:development`)" :
"bootstrap IMAGE=mesh-builder:development`)" :
!source ? "MESH_LAB_SOURCE is not set to the repository the control plane is built from" :
!sourceRef ? "MESH_LAB_SOURCE_REF is not set to the commit to build" :
!bundle || !existsSync(bundle) ? "MESH_LAB_BUNDLE is not set to a substrate template" :
!catalogDir || !existsSync(catalogDir) ? "MESH_LAB_CATALOG is not set to mesh-catalog/modules" :
false;
@@ -72,6 +79,8 @@ before(async () => {
// naming a registry that does not exist — the installer overwrites it, and leaving it proves
// that it does.
bundleTemplate: substrateBundle(bundle, []),
source,
sourceRef,
log: (m) => console.log(m),
});
} catch (err) {
+12
View File
@@ -44,6 +44,15 @@ export interface GenesisOptions {
catalogueOnMachine?: string;
/** Where this mesh's own registry will answer. */
registry?: string;
/**
* Where the control plane is built from, and the commit.
*
* The installer carries a builder rather than a finished control plane (novox/hq ADR 0073), so
* it has to be told what to make. A commit rather than a branch, because what genesis clones is
* the trust anchor for everything the mesh will ever run (ADR 0071).
*/
source: string;
sourceRef: string;
log?: (m: string) => void;
}
@@ -84,6 +93,7 @@ export async function genesis(o: GenesisOptions): Promise<GenesisResult> {
const name = await instanceNameOf(o.instanceId, node);
const version = await placeBootstrap(name, node, o.installer, (m) => log(`genesis:${m}`));
report.push(` installer ${version} at ${BOOTSTRAP_PATH}`);
report.push(` builds from ${o.source} at ${o.sourceRef.slice(0, 8)}`);
// The catalogue. `mesh-bootstrap --catalog` reads manifests from a CHECKOUT on the machine,
// because at this moment the mesh has no forge, no build machine and — until the registry step
@@ -102,6 +112,8 @@ export async function genesis(o: GenesisOptions): Promise<GenesisResult> {
const command = [
BOOTSTRAP_PATH,
`--source ${o.source}`,
`--source-ref ${o.sourceRef}`,
`--bundle /tmp/substrate-template.lock`,
`--catalog ${catalogueOnMachine}`,
`--node ${node}`,
+13 -1
View File
@@ -73,6 +73,11 @@ const binary = hostBinaryPath();
const installer = bootstrapBinaryPath();
const bundle = process.env["MESH_LAB_BUNDLE"] ?? "";
const modulesEnv = process.env["MESH_LAB_MODULES"] ?? "";
// What the installer is told to build. It carries a builder rather than a finished control plane
// (novox/hq ADR 0073), so genesis is given a repository and a commit — and a commit rather than a
// branch, because what is cloned is the trust anchor for everything this mesh will ever run.
const source = process.env["MESH_LAB_SOURCE"] ?? "";
const sourceRef = process.env["MESH_LAB_SOURCE_REF"] ?? "";
const skip = !capability.usable
? `lab not usable: ${capability.why}`
@@ -82,8 +87,12 @@ const skip = !capability.usable
? "MESH_LAB_BUNDLE is not set to a substrate bundle (mesh-host examples/)"
: !installer || !existsSync(installer)
? "MESH_LAB_BOOTSTRAP_BINARY is not set to a built mesh-bootstrap (mesh-host `make " +
"bootstrap IMAGE=mesh-control:development`). The anchor is raised BY the installer now, " +
"bootstrap IMAGE=mesh-builder:development`). The anchor is raised BY the installer now, " +
"so a run without one would be testing the procedure this bed exists to stop testing"
: !source
? "MESH_LAB_SOURCE is not set to the repository the control plane is built from"
: !sourceRef
? "MESH_LAB_SOURCE_REF is not set to the commit to build"
: false;
const SCENARIO = "whole-mesh-full";
@@ -526,6 +535,7 @@ async function genesis(images: HeldImage[]): Promise<GenesisResult> {
const version = await placeBootstrap(name, CONTROL, installer as string,
(m) => console.log(`genesis:${m}`));
report.push(` installer ${version} at ${BOOTSTRAP_PATH}`);
report.push(` builds from ${source} at ${sourceRef.slice(0, 8)}`);
// The catalogue. `mesh-bootstrap --catalog` reads manifests from a CHECKOUT on the machine,
// because at this moment the mesh has no forge, no build machine and — until step 7 finishes —
@@ -560,6 +570,8 @@ async function genesis(images: HeldImage[]): Promise<GenesisResult> {
const command = [
BOOTSTRAP_PATH,
`--source ${source}`,
`--source-ref ${sourceRef}`,
`--bundle /tmp/substrate-template.lock`,
`--catalog ${CATALOGUE_ON_MACHINE}`,
`--node ${CONTROL}`,
+2 -2
View File
@@ -1,6 +1,6 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { planned, controlPlaneImage } from "../src/rebuild.ts";
import { planned, carriedImage } from "../src/rebuild.ts";
import { repositories } from "../src/repos.ts";
import { loadScenario } from "../src/declaration/parse.ts";
@@ -61,7 +61,7 @@ test("the installer is built, carrying the image built in the same run", () => {
const installer = builds.find((b) => b.what === "installer")!;
assert.equal(installer.in, "/repo/host");
assert.ok(installer.argv.includes(`IMAGE=${controlPlaneImage({})}`), installer.argv.join(" "));
assert.ok(installer.argv.includes(`IMAGE=${carriedImage({})}`), installer.argv.join(" "));
assert.ok(installer.argv.includes("BOOTSTRAP_OUT=/repo/host/mesh-bootstrap"), installer.argv.join(" "));
});