The lab raised an underlay and put nothing on it: correct, and useless, because the thing it exists to test did not exist. Tier 0 now does, so `place: [host]` works and a raised scenario finally contains something. The refusal narrows rather than disappearing. A scenario placing a host and a substrate is told which half is missing, by name — not that `place:` is unsupported when half of it now works. Placement reads back rather than assuming. A file arriving is not a host working, so the binary is run before it is trusted to answer questions, and what it reports is read from the machine (ADR 0035). The binary comes from an explicit path, because the declaration design leaves where artifacts come from open and a search would harden into the answer by accident. The integration test that matters is the one asserting the host reports the MACHINE and not the workstation that placed it. A raised VM and this workstation differ in every capability — root versus uid 1000, a clean init versus a degraded one, no docker versus docker, no wireguard versus wg0 — so a host reporting the wrong machine is obvious here and invisible anywhere else. And the placed host independently confirms ADR 0031: overlay absent on a freshly raised machine. The underlay suite already asserted that by looking for wireguard interfaces; this is a second witness rather than the same check twice. Two tests failed the moment placement worked, which is what they were for. They defended "there is nothing to place yet" while that was true; the decision changed, so they change with it rather than being deleted. Gate: 75 unit, 20 integration.
89 lines
4.2 KiB
TypeScript
89 lines
4.2 KiB
TypeScript
/**
|
|
* The lab, placing tier 0 inside a machine it raised.
|
|
*
|
|
* This is the seam that ends the lab being infrastructure with no consumer
|
|
* (novox/hq 03-DESIGN/00-as-is/11-the-lab.md). It needs a built host binary; without one it
|
|
* skips with a reason rather than passing having checked nothing.
|
|
*/
|
|
|
|
import { test, after } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { existsSync } from "node:fs";
|
|
import { loadScenario } from "../../src/declaration/parse.ts";
|
|
import { raise } from "../../src/lifecycle/raise.ts";
|
|
import { destroy, exec } from "../../src/lifecycle/operate.ts";
|
|
import { hostBinaryPath, HOST_PATH } from "../../src/lifecycle/place.ts";
|
|
import { labIsUsable, destroyAll } from "./harness.ts";
|
|
|
|
const capability = await labIsUsable();
|
|
const binary = hostBinaryPath();
|
|
|
|
const skip = !capability.usable
|
|
? `lab not usable: ${capability.why}`
|
|
: !binary
|
|
? "MESH_LAB_HOST_BINARY is not set — build novox/mesh-host and point at it"
|
|
: !existsSync(binary)
|
|
? `MESH_LAB_HOST_BINARY points at ${binary}, which does not exist`
|
|
: false;
|
|
|
|
let instanceId = "";
|
|
|
|
after(async () => {
|
|
if (instanceId) await destroy(instanceId);
|
|
await destroyAll("bootstrap-single-");
|
|
}, { timeout: 400_000 });
|
|
|
|
test("a raised machine contains the host", { skip, timeout: 900_000 }, async () => {
|
|
const raised = await raise(loadScenario("scenarios/bootstrap-single.yml"), {});
|
|
instanceId = raised.instanceId;
|
|
|
|
const { stdout } = await exec(instanceId, "anchor", [HOST_PATH, "version"]);
|
|
assert.ok(stdout.trim().length > 0, "the host is on the machine but does not run there");
|
|
});
|
|
|
|
test("the host reports the MACHINE, not the workstation that placed it", { skip, timeout: 120_000 }, async () => {
|
|
// The check that proves detection detects rather than reporting a constant. A raised VM and
|
|
// the workstation differ in every capability, so a host that reported the workstation's
|
|
// answers would be obvious here and invisible anywhere else.
|
|
const { stdout } = await exec(instanceId, "anchor", [HOST_PATH, "profile", "--json"]);
|
|
const profile = JSON.parse(stdout) as {
|
|
capabilities: { name: string; present: boolean; detail: string }[];
|
|
};
|
|
const by = new Map(profile.capabilities.map((c) => [c.name, c]));
|
|
|
|
// A machine raised by the lab is root and has a clean init. The workstation session is
|
|
// neither, so these are the two that would flip if the wrong machine were being read.
|
|
assert.equal(by.get("privileged")?.present, true, "a raised machine should be root");
|
|
assert.equal(by.get("service-manager")?.present, true, "a raised machine should have an init");
|
|
|
|
for (const [name, verdict] of by) {
|
|
assert.ok(verdict.detail.trim().length > 0, `${name} was reported with no reason`);
|
|
}
|
|
});
|
|
|
|
test("ADR 0031 — the host confirms the machine carries no overlay", { skip, timeout: 120_000 }, async () => {
|
|
// The lab provides the underlay and NOTHING of the overlay. Asserted elsewhere by looking
|
|
// for wireguard interfaces; here the placed host reports it independently, which is a
|
|
// second witness rather than the same check twice.
|
|
const { stdout } = await exec(instanceId, "anchor", [HOST_PATH, "profile", "--json"]);
|
|
const profile = JSON.parse(stdout) as { capabilities: { name: string; present: boolean }[] };
|
|
const overlay = profile.capabilities.find((c) => c.name === "overlay");
|
|
|
|
assert.equal(overlay?.present, false, "a freshly raised machine already had an overlay");
|
|
});
|
|
|
|
test("the host's inventory is of the raised machine", { skip, timeout: 120_000 }, async () => {
|
|
const { stdout } = await exec(instanceId, "anchor", [HOST_PATH, "inventory", "--json"]);
|
|
const inv = JSON.parse(stdout) as {
|
|
machine: string; cpus: number; memory_kb: number; observed_at: string; unreadable?: string[];
|
|
};
|
|
|
|
assert.ok(inv.machine.length > 0, "the machine did not report a name");
|
|
assert.ok(inv.cpus > 0 && inv.memory_kb > 0, "the machine reported no cpus or no memory");
|
|
assert.deepEqual(inv.unreadable ?? [], [], "something could not be read on a machine we raised");
|
|
|
|
// The scenario gives each machine 1GiB and 2 cpus. A host reporting the workstation's
|
|
// 24 cpus would pass every check above.
|
|
assert.ok(inv.cpus <= 4, `reported ${inv.cpus} cpus — that is not the raised machine`);
|
|
});
|