From a1c9fdbc46b9469424c8ddb4889f91d2eb34b96e Mon Sep 17 00:00:00 2001 From: jochens Date: Fri, 2 Oct 2026 18:08:04 +0200 Subject: [PATCH] The two-node bed stocks the packet filter's seat runtime The filter module now serves its verbs from a runtime the mesh builds (novox/hq ADR 0170), and a bed registered its raw manifest, which the mesh refuses as unbuilt. The bed stocks mesh-runtime-nftables and the filter helper registers the module through the stocked image. --- scenarios/two-nodes.yml | 3 +++ test/integration/harness.ts | 6 +++++- test/integration/mesh.test.ts | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/scenarios/two-nodes.yml b/scenarios/two-nodes.yml index 17055ee..cce110a 100644 --- a/scenarios/two-nodes.yml +++ b/scenarios/two-nodes.yml @@ -37,6 +37,9 @@ machines: images: [] images: + # The packet filter's seat runtime (novox/hq ADR 0170): the filter module now serves its verbs from + # a runtime the mesh builds, so a bed that installs the filter stocks it. + - mesh-runtime-nftables:development - mesh-controller:development # And the builder, because it is a module the mesh assigns rather than a program somebody # starts by hand — which is the only way its credential can be one the mesh delivered. diff --git a/test/integration/harness.ts b/test/integration/harness.ts index d26d67e..da8e458 100644 --- a/test/integration/harness.ts +++ b/test/integration/harness.ts @@ -376,11 +376,15 @@ function shellQuote(s: string): string { */ export async function deriveTheFilterOn(o: { machine: string; node: string; hubPort: number; + /** The images the machines hold. Given, the filter module's runtime — the packet-filter seat's, + * which the mesh would build (novox/hq ADR 0170) — is the stocked one, as for any catalogue + * module a bed installs; the scenario must then stock `mesh-runtime-nftables:development`. */ + held?: HeldImage[]; must: (machine: string, command: string, timeoutMs?: number) => Promise; mesh: (command: string, timeoutMs?: number) => Promise; on: (machine: string, command: string, timeoutMs?: number) => Promise<{ out: string; ok: boolean }>; }): Promise { - const manifest = readFileSync(catalogueManifest(FILTER_MODULE), "utf8"); + const manifest = o.held ? catalogueModule(FILTER_MODULE, o.held) : readFileSync(catalogueManifest(FILTER_MODULE), "utf8"); await o.must(o.machine, `printf %s ${shellQuote(manifest)} > /tmp/${FILTER_MODULE}.json && docker cp /tmp/${FILTER_MODULE}.json mesh-controller:/${FILTER_MODULE}.json`); await o.mesh(`module add /${FILTER_MODULE}.json`); diff --git a/test/integration/mesh.test.ts b/test/integration/mesh.test.ts index 351f88f..863a272 100644 --- a/test/integration/mesh.test.ts +++ b/test/integration/mesh.test.ts @@ -346,7 +346,7 @@ test("both machines join it, and the token is all they need", { skip, timeout: 9 await new Promise((r) => setTimeout(r, 3000)); } await new Promise((r) => setTimeout(r, 5000)); - await deriveTheFilterOn({ machine: "anchor", node: "anchor", hubPort: 51820, must, mesh, on }); + await deriveTheFilterOn({ machine: "anchor", node: "anchor", hubPort: 51820, held, must, mesh, on }); // **The laptop makes its tunnel key, and the token is issued for it.** The hub is told the key // before the token is shown, so the tunnel answers the first time the laptop knocks. @@ -409,7 +409,7 @@ test("a credential reaches both ends and the mesh holds neither", { skip, timeou // The anchor's derived filter, admitting the hub's port — what genesis does on the control-node, // and what a bed raised from the bundle must do itself (ADR 0088). Until it is, the base filter // keeps the hub closed and nothing on the laptop reaches anchor over the private network. - await deriveTheFilterOn({ machine: "anchor", node: "anchor", hubPort: 51820, must, mesh, on }); + await deriveTheFilterOn({ machine: "anchor", node: "anchor", hubPort: 51820, held, must, mesh, on }); const onConsumer = (await must("laptop", `cat /etc/meshboard/database.password`)).trim(); // Named after the machine *and* the module, because a consumer is both (novox/hq