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.
55 lines
2.2 KiB
TypeScript
55 lines
2.2 KiB
TypeScript
/**
|
|
* What this lab can actually materialise, and a refusal for everything else.
|
|
*
|
|
* A declaration the runtime silently ignores is the worst thing this project could ship.
|
|
* The mesh already has that fault catalogued: a firewall key declared in five manifests
|
|
* and read by no code, so a manifest appears to restrict a port and restricts nothing
|
|
* (novox/hq 04-ISSUES/003). A scenario that declares a gateway and raises without one
|
|
* would be the same fault, in the tool built to catch it.
|
|
*
|
|
* So unimplemented parts of the model are refused loudly at raise time rather than
|
|
* ignored. The declaration schema deliberately runs ahead of the runtime — it is the
|
|
* design's shape, and validating against it is useful before any of it can be raised —
|
|
* but the gap between the two has to be visible.
|
|
*/
|
|
|
|
import type { Scenario } from "../declaration/types.ts";
|
|
import { PLACEABLE, planPlacements } from "./place.ts";
|
|
|
|
export class UnsupportedError extends Error {
|
|
readonly missing: string[];
|
|
|
|
constructor(missing: string[]) {
|
|
super(
|
|
`this scenario declares things the lab cannot yet materialise:\n - ${missing.join("\n - ")}\n\n` +
|
|
`Raising it would produce a mesh that silently lacks them, which is the exact fault ` +
|
|
`this lab exists to catch. Validation accepts them because the declaration model is ` +
|
|
`complete; the runtime is not.`,
|
|
);
|
|
this.name = "UnsupportedError";
|
|
this.missing = missing;
|
|
}
|
|
}
|
|
|
|
export function assertSupported(scenario: Scenario): void {
|
|
const missing: string[] = [];
|
|
|
|
// `place: [host]` works. Everything else in the vocabulary is named individually rather
|
|
// than refused as a whole, so a scenario that places a host and a substrate is told exactly
|
|
// which half the lab cannot do.
|
|
const unplaceable = new Set<string>();
|
|
for (const { artifacts } of planPlacements(scenario)) {
|
|
for (const artifact of artifacts) {
|
|
if (!(PLACEABLE as readonly string[]).includes(artifact)) unplaceable.add(artifact);
|
|
}
|
|
}
|
|
for (const artifact of [...unplaceable].sort()) {
|
|
missing.push(
|
|
`place: ${artifact} — only ${PLACEABLE.join(", ")} can be placed; the tiers above ` +
|
|
`tier 0 do not exist yet`,
|
|
);
|
|
}
|
|
|
|
if (missing.length > 0) throw new UnsupportedError(missing);
|
|
}
|