The beds name images the way a machine would find them

Twenty-eight integration tests each carried their own copy of the same two helpers,
which pointed a manifest and the substrate bundle at whatever the lab's registry had
assigned. They now share two in the harness, and the difference is the point: ours is
rewritten to the ID the machine holds it under, and everything else is left exactly as
written so the machine pulls it.

**The substrate bundle is where the fiction was most load-bearing.** mesh-host's
`examples/substrate-first-node.lock` pins all three of its images at
`192.0.2.250:5000/…`, which is the address the lab's registry served from — it was
written for a target, and the target was the lab. Two of those are ordinary third-party
images and become the digests mesh-catalog's own postgres and lavinmq modules pin, so
the substrate's store and broker are literally the images the mesh runs. mesh-control
exists in no registry at all and becomes the ID the machine was handed. **The bundle
itself should be fixed in mesh-host and this substitution deleted with it.**

Beds that wrote a manifest by hand named an image by repository and let the rewrite
supply a digest. There is nothing to supply one now, so `onTheMachine` refuses an
unpinned reference and hands back the digest the catalogue pins — a bed runs the image
the mesh ships, and a bed that drifts from the catalogue is testing a different
postgres.

Three beds took a third-party image out of the raised list, which no longer contains
one: certificates (pebble), objectstore (minio and its client) and provisioner
(postgres) now name theirs and pull it. builds and mesh publish into the MESH's own
artifact store — the `registry` module's image, on the node, on 5000 — rather than into
scenery the lab raised. That is a different claim, and only one of them exists in
production.

New unit tests cover what a full raise would otherwise be the only way to check: the
routes an egress machine gets (that its gateway is still the path to the rest of the
scenario, that a range with no path is unreachable rather than leaked to the uplink,
that each family gets its own next hop), which machine is handed which of our images,
and the `images:` rule that refuses a third-party entry. The "shipped scenarios are
valid" test now loads every scenario rather than two of them.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
2026-09-10 23:16:41 +02:00
parent 5c91c0ecd2
commit 675facdb0d
40 changed files with 898 additions and 705 deletions
+72 -1
View File
@@ -1,7 +1,7 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { parseScenario } from "../src/declaration/parse.ts";
import { planPlacements, PLACEABLE, isPlaceable } from "../src/lifecycle/place.ts";
import { planPlacements, planHeldImages, PLACEABLE, isPlaceable } from "../src/lifecycle/place.ts";
import { assertSupported, UnsupportedError } from "../src/lifecycle/supported.ts";
/**
@@ -59,6 +59,77 @@ test("placing the host is supported", () => {
assert.doesNotThrow(() => assertSupported(scenario("place:\n all: [host]")));
});
/**
* Which machine is handed which of the mesh's own images.
*
* **Not an economy — a fact.** An operator's workstation holds the images its own modules need,
* because somebody put them there, and a home server holds a different set. The lab used to serve
* everything to everyone from a registry it raised, which hid that entirely; handing every machine
* the union instead would put some thirty gigabytes of runtimes onto whole-mesh-full's
* thirty-gigabyte workstations, and the raise would die on disk with the topology looking fine.
*/
test("a machine that says nothing is handed everything the scenario has", () => {
const s = parseScenario(`
scenario: s
segments: { hosting: { kind: public, cidr: [192.0.2.0/24] } }
machines:
anchor: { at: { segment: hosting, address: [192.0.2.10] }, egress: true }
images: [mesh-control:development, mesh-runtime-redis:development]
place: { all: [host, runtime] }
`);
assert.deepEqual(planHeldImages(s), [
{ machine: "anchor", images: ["mesh-control:development", "mesh-runtime-redis:development"] },
]);
});
test("a machine that names some is handed those, and no others", () => {
const s = parseScenario(`
scenario: s
segments: { hosting: { kind: public, cidr: [192.0.2.0/24] } }
machines:
anchor:
at: { segment: hosting, address: [192.0.2.10] }
egress: true
images: [mesh-control:development]
laptop:
at: { segment: hosting, address: [192.0.2.20] }
egress: true
images: [mesh-runtime-redis:development]
images: [mesh-control:development, mesh-runtime-redis:development]
place: { all: [host, runtime] }
`);
assert.deepEqual(planHeldImages(s), [
{ machine: "anchor", images: ["mesh-control:development"] },
{ machine: "laptop", images: ["mesh-runtime-redis:development"] },
]);
});
test("a machine that names none is handed none, and is not a machine to visit", () => {
// Absent and empty are different, and a machine running nothing of ours should be able to say
// so without the lab deciding it must have meant everything.
const s = parseScenario(`
scenario: s
segments: { hosting: { kind: public, cidr: [192.0.2.0/24] } }
machines:
anchor: { at: { segment: hosting, address: [192.0.2.10] }, egress: true }
bare: { at: { segment: hosting, address: [192.0.2.20] }, egress: true, images: [] }
images: [mesh-control:development]
place: { all: [host, runtime] }
`);
assert.deepEqual(planHeldImages(s).map((p) => p.machine), ["anchor"]);
});
test("a scenario with none of our images loads nothing anywhere", () => {
const s = parseScenario(`
scenario: s
segments: { hosting: { kind: public, cidr: [192.0.2.0/24] } }
machines:
anchor: { at: { segment: hosting, address: [192.0.2.10] } }
place: { all: [host] }
`);
assert.deepEqual(planHeldImages(s), []);
});
test("a tier that does not exist is refused BY NAME", () => {
// Named individually rather than refused as a whole, so a scenario placing a host and a
// substrate is told exactly which half the lab cannot do — rather than being told `place:`