diff --git a/test/integration/adopted-store-cross-node.test.ts b/test/integration/adopted-store-cross-node.test.ts index 4bae27f..25c83aa 100644 --- a/test/integration/adopted-store-cross-node.test.ts +++ b/test/integration/adopted-store-cross-node.test.ts @@ -21,29 +21,25 @@ */ import { test, before, after } from "node:test"; import assert from "node:assert/strict"; -import { existsSync, readFileSync } from "node:fs"; -import { dirname, resolve } from "node:path"; +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, foundationBundle, onTheMachine } from "./harness.ts"; +import { labIsUsable, destroyAll, foundationBundle, catalogueIsPresent, catalogueModule, needsBrokerAccount } from "./harness.ts"; import type { HeldImage } from "../../src/pinning.ts"; const capability = await labIsUsable(); const binary = hostBinaryPath(); const bundle = process.env["MESH_LAB_BUNDLE"] ?? ""; -const modulesEnv = process.env["MESH_LAB_MODULES"] ?? ""; const skip = !capability.usable ? `lab not usable: ${capability.why}` : !binary || !existsSync(binary) ? "MESH_LAB_HOST_BINARY is not set to a built mesh-host" : !bundle || !existsSync(bundle) ? "MESH_LAB_BUNDLE is not set to a foundation bundle" - : false; + : catalogueIsPresent(); const SCENARIO = "adopted-store-cross-node"; const NODE = "node2"; -const catalogDir = process.env["MESH_LAB_CATALOG"] - ?? (modulesEnv ? resolve(dirname(dirname(dirname(modulesEnv))), "mesh-catalog", "modules") : "") - ?? resolve(process.cwd(), "..", "mesh-catalog", "modules"); + let instanceId = ""; let held: HeldImage[] = []; @@ -65,29 +61,12 @@ async function must(machine: string, command: string, timeoutMs?: number): Promi async function mesh(command: string, timeoutMs?: number): Promise { return must("anchor", `docker exec mesh-controller /mesh-controller ${command}`, timeoutMs); } -function pinned(reference: string): string { return onTheMachine(reference, held); } function bundleFor(images: HeldImage[]): string { return foundationBundle(bundle, images); } -/** Load a committed module.json with its container images rewritten to the scenario's pinned digests. */ +/** The catalogue's manifest as the lab runs it (harness), and whether it needs a broker account. */ function loadManifest(name: string): { manifest: string; broker: boolean } { - const path = resolve(catalogDir, name, "module.json"); - const m = JSON.parse(readFileSync(path, "utf8")) as { - resources?: { type: string; image?: string; artifact?: string }[]; - }; - for (const r of m.resources ?? []) { - if (r.type !== "container") continue; - if (typeof r.image === "string") { - // A placeholder image (mesh-runtime-@0…0) resolves to the stocked digest, as redis does. - r.image = pinned(r.image); - } else if (typeof r.artifact === "string") { - // The bundle-model bed does not build, so resolve a module's runtime ARTIFACT to its stocked - // image directly — postgres/lavinmq name their provisioner by artifact, not a placeholder. - r.image = pinned(`mesh-runtime-${name}@sha256:${"0".repeat(64)}`); - delete r.artifact; - } - } - const manifest = JSON.stringify(m); - return { manifest, broker: manifest.includes("MESH_BROKER_FILE") }; + const manifest = catalogueModule(name, held); + return { manifest, broker: needsBrokerAccount(manifest) }; } function tokenFrom(said: string): string { const found = said.split("\n").map((l) => l.trim()).find((l) => l.length > 100 && !l.includes(" ")); @@ -105,7 +84,6 @@ async function install(name: string, node: string): Promise { before(async () => { if (skip) return; - assert.ok(existsSync(catalogDir), `mesh-catalog modules not found at ${catalogDir}`); const raised = await raise(loadScenario(`scenarios/${SCENARIO}.yml`), { onProgress: (m) => console.log(`raise: ${m}`) }); instanceId = raised.instanceId; held = raised.images; diff --git a/test/integration/assigned-two-node-db.test.ts b/test/integration/assigned-two-node-db.test.ts index efe19d4..8708cb3 100644 --- a/test/integration/assigned-two-node-db.test.ts +++ b/test/integration/assigned-two-node-db.test.ts @@ -37,13 +37,12 @@ import { test, before, after } from "node:test"; import assert from "node:assert/strict"; -import { existsSync, readFileSync } from "node:fs"; -import { dirname, resolve } from "node:path"; +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, foundationBundle, onTheMachine } from "./harness.ts"; +import { labIsUsable, destroyAll, foundationBundle, onTheMachine, catalogueIsPresent, catalogueModule, needsBrokerAccount } from "./harness.ts"; import type { HeldImage } from "../../src/pinning.ts"; const capability = await labIsUsable(); @@ -56,16 +55,13 @@ const skip = !capability.usable ? "MESH_LAB_HOST_BINARY is not set to a built mesh-host" : !bundle || !existsSync(bundle) ? "MESH_LAB_BUNDLE is not set to a foundation bundle (mesh-host examples/)" - : false; + : catalogueIsPresent(); const SCENARIO = "two-node-db"; /** The node that carries the whole DB-consumer chain. anchor carries only the foundation. */ const NODE = "laptop"; -const modulesEnv = process.env["MESH_LAB_MODULES"] ?? ""; -const catalogDir = process.env["MESH_LAB_CATALOG"] - ?? (modulesEnv ? resolve(dirname(dirname(dirname(modulesEnv))), "mesh-catalog", "modules") : "") - ?? resolve(process.cwd(), "..", "mesh-catalog", "modules"); + let instanceId = ""; /** The mesh's own images, as the machines hold them. */ @@ -359,22 +355,11 @@ test("consumers on a joined node get their databases from the one foundation sto await mesh(`assign ${NODE} ${name}`); } - // Load a committed catalog module.json with its container images rewritten to this scenario's - // pinned digests, so the foundation store can be ADOPTED in place as the one postgres. + // The catalogue's manifest as the lab runs it (harness), so the foundation store can be ADOPTED + // in place as the one postgres. function loadManifest(name: string): { manifest: string; broker: boolean } { - const m = JSON.parse(readFileSync(resolve(catalogDir, name, "module.json"), "utf8")) as { - resources?: { type: string; image?: string; artifact?: string }[]; - }; - for (const r of m.resources ?? []) { - if (r.type !== "container") continue; - if (typeof r.image === "string") r.image = pinned(r.image); - else if (typeof r.artifact === "string") { - r.image = pinned(`mesh-runtime-${name}@sha256:${"0".repeat(64)}`); - delete r.artifact; - } - } - const manifest = JSON.stringify(m); - return { manifest, broker: manifest.includes("MESH_BROKER_FILE") }; + const manifest = catalogueModule(name, held); + return { manifest, broker: needsBrokerAccount(manifest) }; } async function installCatalog(name: string, node: string): Promise { const { manifest, broker } = loadManifest(name); diff --git a/test/integration/harness.ts b/test/integration/harness.ts index eb87ab9..ac09d5e 100644 --- a/test/integration/harness.ts +++ b/test/integration/harness.ts @@ -283,10 +283,11 @@ export interface ForTheLab { */ artifacts?: Record; /** - * Host-port remaps by container id, where one machine carries modules whose published ports - * collide — `{ server: { "8080": "8090:8080" } }`. The container side never changes. + * Host-port remaps, where one machine carries modules whose published ports collide — + * `{ "8080": "8090:8080" }`, applied to every container of the module. The container side never + * changes. */ - ports?: Record>; + ports?: Record | undefined; /** * Environment a container gets in the lab that it does not get in the mesh — an address the bed * stands up in place of a real upstream, and nothing else. Merged over the manifest's own. @@ -323,8 +324,7 @@ export function catalogueModule(module: string, held: HeldImage[], lab: ForTheLa } else if (typeof r.image === "string") { r.image = onTheMachine(r.image, held); } - const remap = lab.ports?.[r.id]; - if (remap && Array.isArray(r.ports)) r.ports = r.ports.map((p) => remap[p] ?? p); + if (lab.ports && Array.isArray(r.ports)) r.ports = r.ports.map((p) => lab.ports![p] ?? p); const env = lab.env?.[r.id]; if (env) r.env = { ...(r.env ?? {}), ...env }; } diff --git a/test/integration/whole-mesh-ace.test.ts b/test/integration/whole-mesh-ace.test.ts index a0ad542..818c40d 100644 --- a/test/integration/whole-mesh-ace.test.ts +++ b/test/integration/whole-mesh-ace.test.ts @@ -25,19 +25,17 @@ import { test, before, after } from "node:test"; import assert from "node:assert/strict"; -import { existsSync, readFileSync } from "node:fs"; -import { dirname, resolve } from "node:path"; +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, foundationBundle, onTheMachine, deriveTheFilterOn } from "./harness.ts"; +import { labIsUsable, destroyAll, foundationBundle, deriveTheFilterOn, catalogueIsPresent, catalogueModule, needsBrokerAccount } from "./harness.ts"; import type { HeldImage } from "../../src/pinning.ts"; const capability = await labIsUsable(); const binary = hostBinaryPath(); const bundle = process.env["MESH_LAB_BUNDLE"] ?? ""; -const modulesEnv = process.env["MESH_LAB_MODULES"] ?? ""; const skip = !capability.usable ? `lab not usable: ${capability.why}` @@ -45,14 +43,12 @@ const skip = !capability.usable ? "MESH_LAB_HOST_BINARY is not set to a built mesh-host" : !bundle || !existsSync(bundle) ? "MESH_LAB_BUNDLE is not set to a foundation bundle (mesh-host examples/)" - : false; + : catalogueIsPresent(); const SCENARIO = "whole-mesh-ace"; const NODE = "ace"; -const catalogDir = process.env["MESH_LAB_CATALOG"] - ?? (modulesEnv ? resolve(dirname(dirname(dirname(modulesEnv))), "mesh-catalog", "modules") : "") - ?? resolve(process.cwd(), "..", "mesh-catalog", "modules"); + /** The operator-owned media library the ADR-0051 `accesses` point at — pre-created before the push. */ const MEDIA_DIRS = [ @@ -175,27 +171,17 @@ async function mesh(command: string, timeoutMs?: number): Promise { } /** What a manifest's image reference becomes on the machine — ours by ID, everything else as written. */ -function pinned(reference: string): string { - return onTheMachine(reference, held); -} function bundleFor(images: HeldImage[]): string { return foundationBundle(bundle, images); } +/** The catalogue's manifest as the lab runs it (harness). This bed's own loader never resolved a + * runtime ARTIFACT to a stocked image, so every module whose runtime the mesh builds travelled to + * the machine unresolved — the shared loader does (novox/hq 04-ISSUES/073). */ function loadManifest(name: string): { manifest: string; broker: boolean } { - const path = resolve(catalogDir, name, "module.json"); - const m = JSON.parse(readFileSync(path, "utf8")) as { - resources?: { type: string; image?: string; ports?: string[] }[]; - }; - const remap = REMAP[name] ?? {}; - for (const r of m.resources ?? []) { - if (r.type !== "container") continue; - if (typeof r.image === "string") r.image = pinned(r.image); - if (Array.isArray(r.ports)) r.ports = r.ports.map((p) => remap[p] ?? p); - } - const manifest = JSON.stringify(m); - return { manifest, broker: manifest.includes("MESH_BROKER_FILE") }; + const manifest = catalogueModule(name, held, { ports: REMAP[name] }); + return { manifest, broker: needsBrokerAccount(manifest) }; } function tokenFrom(said: string): string { @@ -240,7 +226,6 @@ async function nodeState(node: string): Promise { before(async () => { if (skip) return; - assert.ok(existsSync(catalogDir), `mesh-catalog modules not found at ${catalogDir}`); const raised = await raise(loadScenario(`scenarios/${SCENARIO}.yml`), { onProgress: (m) => console.log(`raise: ${m}`), diff --git a/test/integration/whole-mesh-full.test.ts b/test/integration/whole-mesh-full.test.ts index 82f4009..1eb6de1 100644 --- a/test/integration/whole-mesh-full.test.ts +++ b/test/integration/whole-mesh-full.test.ts @@ -58,21 +58,20 @@ import { test, before, after } from "node:test"; import assert from "node:assert/strict"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; -import { dirname, join, resolve } from "node:path"; +import { join, resolve } from "node:path"; import { loadScenario } from "../../src/declaration/parse.ts"; import { raise } from "../../src/lifecycle/raise.ts"; import { destroy, exec, instanceNameOf, push } from "../../src/lifecycle/operate.ts"; import { bootstrapBinaryPath, hostBinaryPath, placeBootstrap, BOOTSTRAP_PATH, HOST_PATH, } from "../../src/lifecycle/place.ts"; -import { labIsUsable, destroyAll, foundationBundle, onTheMachine } from "./harness.ts"; +import { labIsUsable, destroyAll, foundationBundle, catalogueDir, catalogueIsPresent, catalogueModule, needsBrokerAccount } from "./harness.ts"; import { referenceFor, type HeldImage } from "../../src/pinning.ts"; const capability = await labIsUsable(); 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. @@ -93,7 +92,7 @@ const skip = !capability.usable ? "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; + : catalogueIsPresent(); const SCENARIO = "whole-mesh-full"; /** novox hosts the foundation and the control plane; it is where `mesh` commands run. */ @@ -118,7 +117,7 @@ const CATALOGUE_ON_MACHINE = "/opt/mesh-catalog"; * `internal/bootstrap` RegistryModule and ControlPlaneModule. If it ever opens a third, this list * is where the bed finds out, by the installer saying which manifest it could not read. */ -const CATALOGUE_MODULES = ["registry", "mesh-controller", "builder"]; +const CATALOGUE_MODULES = ["distribution", "mesh-controller", "builder"]; /** * Where this mesh keeps its own images, as the anchor reaches it. @@ -152,9 +151,8 @@ const PUBLIC_DOMAIN: Record = { novox: "novox.incus", ace: "zura const KEEP = !!process.env["MESH_LAB_KEEP"]; const FIXED_ID = process.env["MESH_LAB_INSTANCE_ID"] ?? (KEEP ? "whole-mesh-full-live" : undefined); -const catalogDir = process.env["MESH_LAB_CATALOG"] - ?? (modulesEnv ? resolve(dirname(dirname(dirname(modulesEnv))), "mesh-catalog", "modules") : "") - ?? resolve(process.cwd(), "..", "mesh-catalog", "modules"); +/** The catalogue's modules directory (harness). Absent, the bed skips — see `skip`. */ +const catalogDir = catalogueIsPresent() ? "" : catalogueDir(); const MEDIA_DIRS = [ "/services/media/series", "/services/media/anime", "/services/media/movies", @@ -201,7 +199,7 @@ const NOVOX: Mod[] = [ // what an operator's `module add` + `assign` would do on a mesh that already has it, and a // module the installer put there had better survive being asked for a second time. It also keeps // mesh-registry in the convergence report, where a reader expects to see it. - { name: "registry", containers: ["mesh-registry"] }, + { name: "distribution", containers: ["mesh-registry"] }, { name: "mailu", containers: [ @@ -210,16 +208,16 @@ const NOVOX: Mod[] = [ "mailu-front", "mesh-mailu", ], }, - { name: "firewall", containers: [], node: true }, + { name: "nftables", containers: [], node: true }, { name: "fail2ban", containers: [], node: true }, ]; const CORE_NOVOX = new Set([ "postgres", "redis", "minio", "mongodb", "mssql", "lavinmq", "route-proxy", "keycloak", "gitea", "nextcloud", "invoicing", "photos", "novox.be", - "portainer", "verdaccio", "registry", + "portainer", "verdaccio", "distribution", ]); const GAPS_NOVOX = new Set([ - "umami", "mailu", "firewall", "fail2ban", "only-office", "de-spiegel", "amqp-email-forwarder", + "umami", "mailu", "nftables", "fail2ban", "only-office", "de-spiegel", "amqp-email-forwarder", // step-ca is reported, not gated: the internal-CA ISSUANCE path is still being fixed in // mesh-controller, and this bed is not the place to discover that a fix has not landed yet. What is // gated is the half that is decided and cheap — see the ADR 0066 section at the end. @@ -348,45 +346,16 @@ async function mesh(command: string, timeoutMs?: number): Promise { } /** What a manifest's image reference becomes on the machine — ours by ID, everything else as written. */ -function pinned(reference: string): string { - return onTheMachine(reference, held); -} function bundleFor(images: HeldImage[]): string { return foundationBundle(bundle, images); } +/** The catalogue's manifest as the lab runs it (harness): artifacts resolved to the images the + * scenario stocked — the lab standing in for the builder — images pinned, host ports remapped. */ function loadManifest(name: string): { manifest: string; broker: boolean } { - const path = resolve(catalogDir, name, "module.json"); - const m = JSON.parse(readFileSync(path, "utf8")) as { - resources?: { type: string; image?: string; artifact?: string; ports?: string[] }[]; - }; - const remap = REMAP[name] ?? {}; - for (const r of m.resources ?? []) { - if (r.type !== "container") continue; - // **A container naming an artifact is a module the mesh builds, and this bed does not build.** - // It pre-builds the same images on the workstation and stocks them, which is the lab standing - // in for the builder — so it does here what the builder does: replace the artifact with the - // reference the machine actually holds. Without this the unresolved field travels to the - // machine, whose declaration language has no such field, and the whole declaration is refused. - // - // The repository is `mesh-runtime-`, which is not a guess: it is what this repository's - // own `scripts/build-module-runtime.sh ` produces and what the scenarios stock by name. - if (typeof r.artifact === "string" && typeof r.image !== "string") { - const reference = referenceFor(held, `mesh-runtime-${name}`); - assert.ok(reference, - `${name} declares the "${r.artifact}" artifact and this scenario stocked no ` + - `mesh-runtime-${name}. The mesh would have to build it, and this bed does not build — ` + - `add it to the machine's images: in the scenario, or build it with ` + - `scripts/build-module-runtime.sh ${name}`); - r.image = reference; - delete r.artifact; - } - if (typeof r.image === "string") r.image = pinned(r.image); - if (Array.isArray(r.ports)) r.ports = r.ports.map((p) => remap[p] ?? p); - } - const manifest = JSON.stringify(m); - return { manifest, broker: manifest.includes("MESH_BROKER_FILE") }; + const manifest = catalogueModule(name, held, { ports: REMAP[name] }); + return { manifest, broker: needsBrokerAccount(manifest) }; } function tokenFrom(said: string): string { @@ -745,7 +714,6 @@ async function joinTheMesh(): Promise { before(async () => { if (skip) return; - assert.ok(existsSync(catalogDir), `mesh-catalog modules not found at ${catalogDir}`); const raised = await raise(loadScenario(`scenarios/${SCENARIO}.yml`), { onProgress: (m) => console.log(`raise: ${m}`), diff --git a/test/integration/whole-mesh-novox.test.ts b/test/integration/whole-mesh-novox.test.ts index daa636a..b44ab47 100644 --- a/test/integration/whole-mesh-novox.test.ts +++ b/test/integration/whole-mesh-novox.test.ts @@ -36,19 +36,17 @@ import { test, before, after } from "node:test"; import assert from "node:assert/strict"; -import { existsSync, readFileSync } from "node:fs"; -import { dirname, resolve } from "node:path"; +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, foundationBundle, onTheMachine } from "./harness.ts"; +import { labIsUsable, destroyAll, foundationBundle, onTheMachine, catalogueIsPresent, catalogueModule, needsBrokerAccount } from "./harness.ts"; import type { HeldImage } from "../../src/pinning.ts"; const capability = await labIsUsable(); const binary = hostBinaryPath(); const bundle = process.env["MESH_LAB_BUNDLE"] ?? ""; -const modulesEnv = process.env["MESH_LAB_MODULES"] ?? ""; const skip = !capability.usable ? `lab not usable: ${capability.why}` @@ -56,15 +54,12 @@ const skip = !capability.usable ? "MESH_LAB_HOST_BINARY is not set to a built mesh-host" : !bundle || !existsSync(bundle) ? "MESH_LAB_BUNDLE is not set to a foundation bundle (mesh-host examples/)" - : false; + : catalogueIsPresent(); const SCENARIO = "whole-mesh-novox"; const NODE = "novox"; -/** Where the committed module.json files live: the mesh-catalog beside mesh-controller. */ -const catalogDir = process.env["MESH_LAB_CATALOG"] - ?? (modulesEnv ? resolve(dirname(dirname(dirname(modulesEnv))), "mesh-catalog", "modules") : "") - ?? resolve(process.cwd(), "..", "mesh-catalog", "modules"); + /** * The set, in dependency-reading order (the resolver accepts any order). Each row: the module, and @@ -229,38 +224,14 @@ function bundleFor(images: HeldImage[]): string { } /** - * Load a committed module.json, rewrite every container image to the scenario's pinned digest, and - * apply the host-port remaps. Returns the manifest as a string and whether it needs a broker account - * (a runtime that reads MESH_BROKER_FILE — providers and tooled apps do; plain/node modules do not). + * The catalogue's manifest as the lab runs it (harness): images pinned, artifacts resolved to the + * stocked images, the host-port remaps applied. Returns the manifest and whether it needs a broker + * account (a runtime that reads MESH_BROKER_FILE — providers and tooled apps do; plain/node modules + * do not). */ function loadManifest(name: string): { manifest: string; broker: boolean } { - const path = resolve(catalogDir, name, "module.json"); - const m = JSON.parse(readFileSync(path, "utf8")) as { - resources?: { type: string; image?: string; artifact?: string; ports?: string[] }[]; - build?: unknown; - }; - const remap = REMAP[name] ?? {}; - for (const r of m.resources ?? []) { - if (r.type !== "container") continue; - if (typeof r.image === "string") { - r.image = pinned(r.image); - } else if (typeof r.artifact === "string") { - // Since issue 060 a module's own runtime container names an artifact the mesh's builder - // would fill, not a placeholder image. This bed stocks the image instead of building, so - // map the artifact to the stocked `mesh-runtime-` the machine holds — keyed on the - // MODULE name, not the container's (mailu's runtime container is `mesh-mailu`, its image is - // `mesh-runtime-mailu`). The placeholder digest is what `pinned` already resolves for a - // mesh-built repo, exactly as it did for the old `image` field. - r.image = pinned(`mesh-runtime-${name}@sha256:${"0".repeat(64)}`); - delete r.artifact; - } - if (Array.isArray(r.ports)) r.ports = r.ports.map((p) => remap[p] ?? p); - } - // The build section the mesh's builder would consume: dropped, because this bed stocks the image - // rather than building it. Harmless to leave (the push path never reads it), removed for clarity. - delete m.build; - const manifest = JSON.stringify(m); - return { manifest, broker: manifest.includes("MESH_BROKER_FILE") }; + const manifest = catalogueModule(name, held, { ports: REMAP[name] }); + return { manifest, broker: needsBrokerAccount(manifest) }; } function tokenFrom(said: string): string { @@ -306,7 +277,6 @@ async function nodeState(node: string): Promise { before(async () => { if (skip) return; - assert.ok(existsSync(catalogDir), `mesh-catalog modules not found at ${catalogDir}`); const raised = await raise(loadScenario(`scenarios/${SCENARIO}.yml`), { onProgress: (m) => console.log(`raise: ${m}`),