Files
mesh-lab/test/beds-read-the-catalogue.test.ts
T
jschoubben 26177d81be Six beds retired: their coverage lives in the catalogue beds and the whole-mesh beds now
The four sidecar beds (grafana, plex, sonarr, redis) proved a sidecar comes up and
serves tools with the module's server cut away; the catalogue beds and the whole-mesh
beds prove the modules whole. The minio and postgres grant beds proved a grant
mechanism with a second store beside the foundation's; the grant bed and the vault bed
prove it against the catalogue. Ten conversions become six deletions (novox/hq
04-ISSUES/074).
2026-09-21 21:53:11 +02:00

103 lines
6.5 KiB
TypeScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { resolve } from "node:path";
import { catalogueIsPresent, catalogueDir } from "./integration/harness.ts";
/**
* A bed installs a catalogue module by reading the catalogue, never by carrying a copy.
*
* The beds used to build the manifests they install inline, as literals taken from the catalogue
* when each bed was written. The copies did not move when the catalogue did: six modules were
* converted to file-delivered secrets and not one bed ran the converted shape, because every bed
* ran its own copy (novox/hq 04-ISSUES/073). "Proven in the lab" then meant "the copy was proven".
*
* So: a manifest literal in a bed that names a catalogue module is refused, unless the bed is
* listed below with the reason it still carries one. The list is the debt, and it only shrinks.
*
* What this reads: `module: "<name>"` and `"module": "<name>"` with a `version` close by, either
* order, in test/integration/*.test.ts, against the catalogue's directory names. Skipped aloud
* where MESH_LAB_CATALOG is unset — a skip is reported, never silent. A bed that hid the name
* behind a computed string would pass — this is a fence, not a proof, and the reviewer of a bed
* that builds a manifest inline is the proof.
*/
/**
* Beds that still carry an inline copy of a catalogue module's manifest, and why. Three reasons
* recur, and each names the work that removes the entry:
*
* BESIDE the catalogue's module CLAIMS the foundation's container (postgres claims mesh-store,
* lavinmq mesh-broker) and adopts it in place; the bed raises a second one beside the
* foundation's instead. Reading the catalogue changes what the bed raises — it would
* adopt — and the bed's assertions with it.
* WEARING the bed proves a mesh mechanism (a grant, a credential, a restart, a route) with a
* module cut down to the shape the mechanism needs — no upstream server, a secret in the
* environment, a requirement edge removed — and gives it a catalogue name. It is a mesh
* test wearing a catalogue module's name. It cannot simply be renamed: a module's name
* is its tool namespace and its broker scope, so a fixture running the module's runtime
* must carry the module's name (novox/hq ADR 0093). It reads the catalogue and installs
* what the module requires — the vault for a secret, the route module for a route — or
* it runs no real runtime and carries a name of its own.
* DIFFERS a module bed whose copy differs from the catalogue in more than the lab may rewrite
* (an image, a port, an address). Reading the catalogue is the fix and needs a run.
*/
const STILL_CARRIED: Record<string, { modules: string[]; why: string }> = {
"assigned-catalogue-apps.test.ts": { modules: ["postgres", "mongodb", "unifi", "marrytts"],
why: "BESIDE (postgres); DIFFERS (unifi takes its credentials from the environment, mongodb and marrytts drop listens)" },
"assigned-catalogue-media.test.ts": { modules: ["sonarr", "radarr"],
why: "DIFFERS: both drop the route requirement the catalogue declares, and take their API keys from the environment" },
"assigned-catalogue-small.test.ts": { modules: ["postgres", "minio", "redis", "plex"],
why: "BESIDE (postgres); DIFFERS (minio's root password by env-file, redis minting its own secret instead of the vault's, plex without its server)" },
"assigned-model-usage.test.ts": { modules: ["postgres"], why: "BESIDE" },
"assigned-two-node-db.test.ts": { modules: ["redis", "baserow", "letta"],
why: "DIFFERS: redis mints its own secret, baserow drops its route requirement, letta drops its ports" },
"lavinmq-bed.test.ts": { modules: ["lavinmq", "amqp-ping"],
why: "BESIDE (lavinmq, with a bootstrap step and a data directory the catalogue has not got); DIFFERS (amqp-ping names its entrypoint)" },
"provider-uses-mesh-credential.test.ts": { modules: ["redis"], why: "WEARING: a credential mechanism test" },
"runtime-restart-on-config.test.ts": { modules: ["grafana"], why: "WEARING: a restart mechanism test" },
"route-forwarding.test.ts": { modules: ["route-proxy", "hello-web"],
why: "WEARING: route-proxy without its certificate authority, hello-web with the route shape ADR 0066 replaced" },
"mesh.test.ts": { modules: ["postgres", "builder", "umami"],
why: "WEARING: a postgres with no resources, a builder that builds itself, an umami that is another module of that name" },
};
const beds = resolve(import.meta.dirname, "integration");
test("a bed that installs a catalogue module reads the catalogue", (t) => {
const absent = catalogueIsPresent();
if (absent) {
// Said, not silent: a check that cannot see the catalogue has checked nothing.
t.skip(`cannot check — ${absent}`);
return;
}
const names = new Set(readdirSync(catalogueDir(), { withFileTypes: true })
.filter((d) => d.isDirectory() && existsSync(resolve(catalogueDir(), d.name, "module.json")))
.map((d) => d.name));
const offences: string[] = [];
for (const file of readdirSync(beds).filter((f) => f.endsWith(".test.ts")).sort()) {
const text = readFileSync(resolve(beds, file), "utf8");
const found = new Set<string>();
// A manifest literal: the module's name with its version close behind it. A `module:` key
// elsewhere (a table of what to register, a grant entry) has no version and is not one.
for (const m of text.matchAll(/(?:^|[\s{,])(?:"module"|module)\s*:\s*"([a-z0-9.-]+)"[^}]{0,160}?(?:"version"|version)\s*:/g)) {
if (names.has(m[1]!)) found.add(m[1]!);
}
// And the other order — a literal that names its version first.
for (const m of text.matchAll(/(?:^|[\s{,])(?:"version"|version)\s*:\s*"[^"]*"[^}]{0,160}?(?:"module"|module)\s*:\s*"([a-z0-9.-]+)"/g)) {
if (names.has(m[1]!)) found.add(m[1]!);
}
const declared = STILL_CARRIED[file];
for (const name of [...found].sort()) {
if (declared?.modules.includes(name)) continue;
offences.push(`${file}: an inline manifest for the catalogue's '${name}'`);
}
for (const name of declared?.modules ?? []) {
if (!found.has(name)) offences.push(`${file}: declared as still carrying '${name}', and it does not — remove the declaration`);
}
}
assert.deepEqual(offences, [],
`a bed carries a copy of a catalogue manifest; read it with catalogueModule() from the harness:\n ${offences.join("\n ")}`);
});