/** * Database consumers on a joined node, provisioned from the ONE foundation store over the overlay. * * The mesh runs a single postgres — the foundation store, adopted in place as the `postgres` module * on the control-node (ADR 0078/0079). A module anywhere that requires a database gets one FROM that * store, not a second postgres of its own; a second would be refused, because the postgres module * claims the mesh-scoped `mesh-store` seat. * * This bed proves that across two machines. `anchor` is the control-node: it raises the foundation * and adopts `postgres` there, so `mesh-store` is the one store and `mesh-postgres` its provisioner. * `laptop` joins and runs the CONSUMERS — baserow and letta, which require `postgres-database`. Each * consumer's database is minted on the store on anchor and reached over the overlay: their bindings * name `anchor.internal`, and their minted logins authenticate against the store. baserow's cache is * its own, inside its container (novox/hq 081). * * The three manifests are the committed catalogue shapes (novox/hq ADR 0039/0047/0048), verbatim * from the catalogue-broad bed — postgres publishes 5432 so its consumers connect, and baserow/letta * wire their servers to the grant the mesh writes. They are added, each issued a scoped broker account, assigned to laptop, and pushed * ONCE; laptop converges once with every one up, and the two consumers are provisioned against the * database the provider on their own node gave them. * * It needs a host binary and the foundation bundle: * * MESH_LAB_HOST_BINARY=.../mesh-host * MESH_LAB_BUNDLE=.../examples/foundation-first-node.lock * * HELPER — stock the three runtimes into the local daemon before the run (some may already be there): * scripts/build-module-runtime.sh postgres /tmp/postgres.tar * scripts/build-module-runtime.sh baserow /tmp/baserow.tar * scripts/build-module-runtime.sh letta /tmp/letta.tar * The service images (postgres, baserow, letta, each pinned by digest) * must be in the local daemon too; scenarios/two-node-db.yml stocks all of them, and each node pulls * what it runs from the scenario's own registry by digest. */ import { test, before, after } from "node:test"; import assert from "node:assert/strict"; 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, 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 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 (mesh-host examples/)" : catalogueIsPresent(); const SCENARIO = "two-node-db"; /** The node that carries the whole DB-consumer chain. anchor carries only the foundation. */ const NODE = "laptop"; let instanceId = ""; /** The mesh's own images, as the machines hold them. */ let held: HeldImage[] = []; function quote(s: string): string { return `'${s.replaceAll("'", `'\\''`)}'`; } async function on(machine: string, command: string, timeoutMs?: number): Promise<{ out: string; ok: boolean }> { const { stdout } = await exec(instanceId, machine, [ "sh", "-c", `exec 2>&1\n${command}\necho "__exit=$?"`, ], timeoutMs); const marker = stdout.lastIndexOf("__exit="); if (marker < 0) return { out: stdout, ok: false }; return { out: stdout.slice(0, marker), ok: stdout.slice(marker + 7).trim() === "0" }; } async function must(machine: string, command: string, timeoutMs?: number): Promise { const { out, ok } = await on(machine, command, timeoutMs); if (!ok) throw new Error(`${machine}: ${command}\n${out}`); return out; } /** The control plane, a container on the first node. */ async function mesh(command: string, timeoutMs?: number): Promise { return must("anchor", `docker exec mesh-controller /mesh-controller ${command}`, timeoutMs); } /** The reference a manifest should carry, once this scenario has been raised. */ /** 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); } /** The foundation bundle: ours by the ID the machine holds, everything else upstream. */ function bundleFor(images: HeldImage[]): string { return foundationBundle(bundle, images); } function tokenFrom(said: string): string { const found = said.split("\n").map((l) => l.trim()).find((l) => l.length > 100 && !l.includes(" ")); assert.ok(found, `no token in:\n${said}`); return found; } /** * Wait until a node has actually applied what it was last sent. * * `push` sends and returns; the node applies afterwards, so asserting immediately after a push is a * race. The mesh is asked in its own terms — a node is settled when it is neither waiting for what * it was sent nor wrong about what it applied — and a poll that could not ask (a lost fifo into the * control plane's container, a truncated answer) is distinguished from an answer that was bad. */ async function settled(node: string, withinMs = 1_200_000): Promise { const until = Date.now() + withinMs; let last = ""; while (Date.now() < until) { let state: { wrong: { node: string; outcome: string; refused?: string; failed?: { id: string; error: string }[] }[]; waiting: { node: string; never: boolean }[]; reported: { node: string; outcome: string; current: boolean }[]; } | undefined; let said = ""; try { const asked = await on("anchor", `docker exec mesh-controller /mesh-controller status --json`); said = asked.out; if (asked.ok) state = JSON.parse(said); } catch (err) { said = (err as Error).message; } if (!state) { last = said; await new Promise((r) => setTimeout(r, 5000)); continue; } const bad = state.wrong.find((w) => w.node === node); if (bad) { const why = [bad.refused, ...(bad.failed ?? []).map((f) => `${f.id}: ${f.error}`)] .filter(Boolean).join("\n "); throw new Error(`${node} did not apply what it was sent (${bad.outcome}):\n ${why}`); } const word = state.reported.find((r) => r.node === node); const acted = word?.outcome === "applied" && word.current; if (!state.waiting.some((w) => w.node === node) && acted) return; last = said; await new Promise((r) => setTimeout(r, 5000)); } // Timed out — capture what the node that did not answer is doing, so the failure is diagnosable. // Its own machine, not the second node's: the anchor timing out used to print the laptop's log. const ps = (await on(node, `docker ps -a --format '{{.Names}}\t{{.Status}}'`)).out; const hostLog = (await on(node, `tail -80 /var/log/mesh-host.log`)).out; throw new Error( `${node} never caught up within ${Math.round(withinMs / 1000)}s.\nLast status:\n${last}\n` + `--- ${node} docker ps -a ---\n${ps}\n--- ${node} mesh-host.log tail ---\n${hostLog}`); } before(async () => { if (skip) return; const raised = await raise(loadScenario(`scenarios/${SCENARIO}.yml`), { onProgress: (m) => console.log(`raise: ${m}`), }); instanceId = raised.instanceId; held = raised.images; // The first node raises the foundation — store, broker, control — from the bundle its host carries, // its digests rewritten to the ones this scenario's own registry serves. await must("anchor", `cat > /tmp/foundation.lock <<'MESHBUNDLE'\n${bundleFor(raised.images)}\nMESHBUNDLE`); await must("anchor", `${HOST_PATH} apply /tmp/foundation.lock`, 600_000); const up = await must("anchor", `docker ps --format '{{.Names}}'`); for (const c of ["mesh-store", "mesh-broker", "mesh-controller"]) { assert.match(up, new RegExp(c), `the foundation did not raise ${c}:\n${up}`); } // Both machines join the one mesh, each with a token that says what the mesh calls it, and each // starts a host so it applies what it is pushed. anchor is enrolled and runs a host too, though // nothing is assigned to it — enrolment is the only thing that crosses between the nodes, and it // crosses over the underlay segment both machines share. for (const [machine, node] of [["anchor", "anchor"], ["laptop", "laptop"]] as const) { await mesh(`node add ${node}`); const token = tokenFrom(await mesh(`token issue --node ${node}`)); const said = await must(machine, `${HOST_PATH} enrol --token ${quote(token)}`); assert.match(said, new RegExp(`enrolled as ${node}`), said); await must(machine, `nohup ${HOST_PATH} run > /var/log/mesh-host.log 2>&1 & sleep 3`); } }, { timeout: 1_800_000 }); after(async () => { if (process.env["MESH_LAB_KEEP"]) { console.log(`MESH_LAB_KEEP set — leaving ${instanceId} standing`); return; } if (instanceId) await destroy(instanceId); await destroyAll(`${SCENARIO}-`); }, { timeout: 600_000 }); test("consumers on a joined node get their databases from the one foundation store over the overlay", { skip, timeout: 1_500_000, }, async () => { // ================================================================================================ // THE MANIFESTS — the committed catalogue shapes, verbatim from catalogue-broad. Only the node // they land on changes. // ================================================================================================ // --- baserow: a consumer that requires postgres-database, its server wired to the grant the mesh // writes, plus a runtime that serves baserow's tools. Its cache is its own — the image runs one when // no REDIS_HOST is given — because baserow keeps keys and channels under fixed names a shared // cache's per-consumer grant cannot confine (novox/hq 081). -------------------------------------- const baserowManifest = JSON.stringify({ module: "baserow", version: "1", requires: ["postgres-database"], contributes: { "postgres-database": { name: "baserow" } }, binds: { "postgres-database": "/var/lib/baserow/database.json" }, secrets: { "postgres-database": "/var/lib/baserow/database.secret" }, "own-secrets": { "secret-key": "/var/lib/baserow/secret-key.secret", broker: "/var/lib/mesh/baserow/broker" }, resources: [ { id: "mesh-state", type: "directory", path: "/var/lib/mesh/baserow", mode: "0700" }, { id: "state", type: "directory", path: "/var/lib/baserow", mode: "0700" }, { id: "data", type: "directory", path: "/services/baserow/data", mode: "0700", owner: "9999:9999" }, { id: "server-env", type: "file", path: "/var/lib/baserow/server.env", mode: "0600", content: "DATABASE_HOST=${bound:postgres-database:at}\nDATABASE_PORT=${bound:postgres-database:port}\n" + "DATABASE_NAME=${bound:postgres-database:as}\nDATABASE_USER=${bound:postgres-database:as}\n" + "DATABASE_PASSWORD=${secret:postgres-database}\n" + "SECRET_KEY=${secret:secret-key}\nBASEROW_PUBLIC_URL=http://localhost\n", }, { id: "net", type: "network", name: "baserow" }, { id: "server", type: "container", name: "baserow", image: pinned("baserow/baserow"), network: "baserow", "env-file": ["/var/lib/baserow/server.env"], volumes: ["/services/baserow/data:/baserow/data"], }, { id: "runtime-config", type: "file", path: "/var/lib/mesh/baserow/config.json", mode: "0600", content: "{}\n", merge: "json" }, { id: "runtime", type: "container", name: "mesh-baserow", image: pinned("mesh-runtime-baserow"), network: "baserow", volumes: [ "/var/lib/mesh/baserow/broker:/run/secrets/broker:ro", "/var/lib/mesh/baserow/config.json:/run/config/config.json:ro", ], env: { MESH_BROKER_FILE: "/run/secrets/broker", MESH_BASEROW_URL: "http://baserow:80", MESH_BASEROW_CONFIG_FILE: "/run/config/config.json", }, "restart-on": ["runtime-config"], }, ], }); // --- letta: a consumer that requires postgres-database; server wired to it, runtime given the // server password. ------------------------------------------------------------------------------- const lettaManifest = JSON.stringify({ module: "letta", version: "1", requires: ["postgres-database"], contributes: { "postgres-database": { name: "letta" } }, binds: { "postgres-database": "/var/lib/letta/database.json" }, secrets: { "postgres-database": "/var/lib/letta/database.secret" }, "own-secrets": { "server-password": "/var/lib/letta/server-password.secret", broker: "/var/lib/mesh/letta/broker" }, resources: [ { id: "mesh-state", type: "directory", path: "/var/lib/mesh/letta", mode: "0700" }, { id: "state", type: "directory", path: "/var/lib/letta", mode: "0700" }, { id: "server-env", type: "file", path: "/var/lib/letta/server.env", mode: "0600", content: "LETTA_PG_URI=postgresql://${bound:postgres-database:as}:${secret:postgres-database}@" + "${bound:postgres-database:at}:${bound:postgres-database:port}/${bound:postgres-database:as}\n" + "LETTA_SERVER_PASSWORD=${secret:server-password}\nSECURE=true\nTZ=Europe/Brussels\n", }, { id: "net", type: "network", name: "letta" }, { id: "server", type: "container", name: "letta", image: pinned("letta/letta"), network: "letta", "env-file": ["/var/lib/letta/server.env"], }, { id: "runtime-config", type: "file", path: "/var/lib/mesh/letta/config.json", mode: "0600", content: "{}\n", merge: "json" }, { id: "runtime-env", type: "file", path: "/var/lib/letta/runtime.env", mode: "0600", content: "MESH_LETTA_PASSWORD=${secret:server-password}\n" }, { id: "runtime", type: "container", name: "mesh-letta", image: pinned("mesh-runtime-letta"), network: "letta", volumes: [ "/var/lib/mesh/letta/broker:/run/secrets/broker:ro", "/var/lib/mesh/letta/config.json:/run/config/config.json:ro", ], env: { MESH_BROKER_FILE: "/run/secrets/broker", MESH_LETTA_URL: "http://letta:8283", MESH_LETTA_CONFIG_FILE: "/run/config/config.json", }, "env-file": ["/var/lib/letta/runtime.env"], "restart-on": ["runtime-config"], }, ], }); // --- add, issue a scoped broker account, assign to laptop, then ONE push ------------------------- async function addIssueAssign(name: string, manifest: string): Promise { await must("anchor", `printf %s ${quote(manifest)} > /tmp/${name}.json && docker cp /tmp/${name}.json mesh-controller:/${name}.json`); await mesh(`module add /${name}.json`); const issued = await mesh(`module issue ${name} --node ${NODE}`); assert.match(issued, /scoped to what it emits and consumes/, issued); await mesh(`assign ${NODE} ${name}`); } // 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 manifest = catalogueModule(name, held); return { manifest, broker: needsBrokerAccount(manifest) }; } async function installCatalog(name: string, node: string): Promise { const { manifest, broker } = loadManifest(name); await must("anchor", `printf %s ${quote(manifest)} > /tmp/${name}.json && docker cp /tmp/${name}.json mesh-controller:/${name}.json`); await mesh(`module add /${name}.json`); if (broker) await mesh(`module issue ${name} --node ${node}`); await mesh(`assign ${node} ${name}`); } // The consumer connects to its provider by the provider's PRIVATE-NETWORK address — the binding's // `at`, which mesh-controller fills as "where the providing machine is on the private network, // empty if it is not on one" (declaration.go). The store is on anchor and the consumers on laptop, // so that address is anchor's OVERLAY name — empty unless both are on the overlay. The overlay // networking is therefore assigned first, to both nodes. await mesh("overlay place anchor --hub --endpoint 192.0.2.10:51820 --site lab"); await mesh(`overlay place ${NODE} --site lab`); await mesh("assign anchor networking"); await mesh(`assign ${NODE} networking`); // The packet filter on the control-node, so the from:mesh rule admits laptop's consumers to the // store over the overlay — the firewall half of cross-node provisioning (issue 055). await installCatalog("nftables", "anchor"); // Adopt the foundation store AND broker as the ONE postgres and lavinmq, on the control-node: // each module reconciles the container the foundation raised and brings up its provisioner // (mesh-postgres mints a database per consumer; mesh-lavinmq a vhost per consumer). There is no // second store or broker (ADR 0079). Adopting lavinmq also declares the broker's `listens` — so // its amqps port opens in the firewall's forward chain, which is what lets a consumer on the // joined node reach the bus cross-node at all. await installCatalog("postgres", "anchor"); // The store's superuser is the foundation's, made at genesis (mesh-store's POSTGRES_PASSWORD) — // carried into the module via `secret accept`, exactly as the genesis bootstrap does. Without it // the module mints a random superuser that does not match the running store, and the provisioner // cannot log in to create anyone's database (hq phase3 deliverSuperuser; ADR 0078). const superPw = (await must("anchor", `docker inspect mesh-store --format '{{range .Config.Env}}{{println .}}{{end}}' | sed -n 's/^POSTGRES_PASSWORD=//p'`)).trim(); await must("anchor", `printf %s ${quote(superPw)} > /tmp/superuser && docker cp /tmp/superuser mesh-controller:/superuser`); await mesh(`secret accept anchor postgres superuser --from /superuser`); await installCatalog("lavinmq", "anchor"); await mesh(`push anchor`, 600_000); await settled("anchor"); // The consumers ride laptop. await addIssueAssign("baserow", baserowManifest); await addIssueAssign("letta", lettaManifest); await mesh(`push ${NODE}`); await settled(NODE); // The store's provisioner learns of the cross-node consumers only when anchor is composed again // — a provision secret is minted composing the CONSUMER, and the provider reads it (issue 057). await mesh(`push anchor`, 600_000); await settled("anchor"); // ================================================================================================ // THE two-node split — the ONE store (adopted) on anchor, its cross-node consumers on laptop. // ================================================================================================ const onAnchor = await must("anchor", `docker ps --format '{{.Names}}'`); const onLaptop = await must(NODE, `docker ps --format '{{.Names}}'`); assert.match(onAnchor, /(^|\n)mesh-store(\n|$)/, "the foundation store is not on the control-node"); assert.match(onAnchor, /(^|\n)mesh-postgres(\n|$)/, "the store's provisioner did not come up on the control-node"); assert.doesNotMatch(onLaptop, /(^|\n)mesh-store(\n|$)/, "the foundation store leaked onto the second node"); assert.doesNotMatch(onLaptop, /(^|\n)postgres(\n|$)/, "a second postgres was raised on the second node — the one-store rule (ADR 0079) was violated"); // ================================================================================================ // THE co-residence proof — every module's containers up and stable on the second node. // ================================================================================================ const expected = [ "baserow", "mesh-baserow", "letta", "mesh-letta", ]; for (const name of expected) { assert.match(onLaptop, new RegExp(`(^|\\n)${name}(\\n|$)`), `${name} is not running on the second node after the push:\n${onLaptop}\n---host log---\n${(await on(NODE, `tail -60 /var/log/mesh-host.log`)).out}`); } // Everything up and STABLE (RestartCount 0) after a moment — the consumer services (baserow, letta) // must reach the provider the mesh addressed them to and stay up. await new Promise((r) => setTimeout(r, 8000)); // REGRESSION (provider-seal-key): baserow's server.env DATABASE_PASSWORD is filled from the // ${secret:postgres-database} placeholder; its database.secret file carries the same credential via // the secrets: map. Both are baserow's one postgres password and MUST be equal. Before the // mesh-controller fix (secrets_into_files.go matched a need by provision name alone, not by consuming // module) the placeholder path took whichever co-located consumer came last — letta's — so the two // diverged and baserow authenticated with the wrong password. novox/hq 04-ISSUES/022. { const envPass = (await must(NODE, `sed -n 's/^DATABASE_PASSWORD=//p' /var/lib/baserow/server.env`)).trim(); const secretFile = (await must(NODE, `cat /var/lib/baserow/database.secret`)).replace(/\n$/, ""); assert.ok(envPass.length > 20, `baserow's server.env carries no DATABASE_PASSWORD:\n${envPass}`); assert.equal(envPass, secretFile, "baserow's placeholder-filled password differs from its secret file — a co-located consumer's " + `credential leaked into the placeholder path (env=${envPass} secret=${secretFile})`); } // Stable = currently running and NOT restarting in a loop. The heavy all-in-one app images // (baserow, letta) legitimately restart once on first boot — their supervisor runs the initial DB // migration and bounces — so "exactly zero restarts" is wrong; a crash-loop is what we must catch, // and it shows as a restart count that keeps climbing. Sample, wait, and require it did not rise. // // The `letta` APP container is excluded from this crash-loop check: letta stores vector embeddings // and its migration needs the postgres `vector` (pgvector) extension, which the standard postgres // image does not carry and a non-superuser consumer role cannot CREATE — a separate feature // (per-consumer postgres extension provisioning), tracked as a follow-up. What THIS bed proves — // that letta is a second co-located postgres consumer that gets its OWN credential and reaches its // OWN database (the provider-seal-key gate) — is asserted above (the credential match) and below // (the live provisioning connect); its runtime `mesh-letta` and every other container stay strict. // // A provisioner runtime whose provider is cross-node legitimately restarts a few times at // startup: it exits when the broker is not yet reachable (the overlay tunnel comes up a moment // after the container does) and docker restarts it until it connects. That is startup churn, not // a crash-loop — the difference is that churn STOPS. So wait for each container's restart count // to settle (unchanged over a window) rather than forbid any rise; one that never settles inside // the deadline is the real crash-loop, and it fails with the trajectory and its logs. const stable = expected.filter((n) => n !== "letta"); for (const name of stable) { const deadline = Date.now() + 300_000; let prev = -1, stableSince = 0, last = "?", settled = false; while (Date.now() < deadline) { const [running, count] = (await must(NODE, `docker inspect -f '{{.State.Running}} {{.RestartCount}}' ${name}`)).trim().split(" "); last = `running=${running} restarts=${count}`; const n = Number(count); if (running === "true" && n === prev) { if (stableSince === 0) stableSince = Date.now(); if (Date.now() - stableSince >= 30_000) { settled = true; break; } // up and unchanged 30s } else { prev = n; stableSince = 0; } await new Promise((r) => setTimeout(r, 5_000)); } const [running, count] = (await must(NODE, `docker inspect -f '{{.State.Running}} {{.RestartCount}}' ${name}`)).trim().split(" "); assert.equal(running, "true", `${name} is not running after the push (${last}):\n${(await on(NODE, `docker logs ${name} 2>&1 | tail -40`)).out}`); if (!settled) assert.fail(`${name} never stopped restarting within 300s (last ${last}) — a crash-loop, not startup churn:\n${(await on(NODE, `docker logs ${name} 2>&1 | tail -40`)).out}`); void count; } // ================================================================================================ // Each module got its own scoped broker account on the foundation's broker (which is on anchor, // reached from laptop over the shared segment) — named for the node that runs it and the module. // ================================================================================================ const users = await must("anchor", `docker exec mesh-broker lavinmqctl list_users 2>&1`); for (const acct of ["anchor-postgres", "laptop-baserow", "laptop-letta"]) { assert.match(users, new RegExp(acct), `the scoped account ${acct} is not on the broker:\n${users}`); } // ================================================================================================ // THE consumers were actually provisioned — the migration question that matters most. The postgres // provisioner (running in mesh-postgres on the SECOND node) created each consumer's database and // role; the proof is that the login the mesh derived authenticates with the password it minted. // ================================================================================================ for (const [mod, bindPath, secretPath] of [ ["baserow", "/var/lib/baserow/database.json", "/var/lib/baserow/database.secret"], ["letta", "/var/lib/letta/database.json", "/var/lib/letta/database.secret"], ] as const) { const bound = await waitForBinding(bindPath); assert.equal(bound.provision, "postgres-database", `${mod} was bound the wrong provision: ${bound.provision}`); const pw = (await must(NODE, `cat ${secretPath}`)).trim(); assert.ok(bound.as && pw, `${mod}'s login or password was empty (as=${bound.as})`); const conn = `postgresql://${bound.as}:${encodeURIComponent(pw)}@127.0.0.1:5432/${bound.as}?sslmode=disable`; let pg = { out: "", ok: false }; const untilConn = Date.now() + 90_000; while (Date.now() < untilConn) { // The provisioner (mesh-postgres) is host-networked on anchor beside the store, so it reaches // it on loopback; the consumer's minted login authenticating there is the cross-node grant working. pg = await on("anchor", `docker exec mesh-postgres psql ${quote(conn)} -tAc 'select 1' 2>&1`); if (pg.ok && /^1$/m.test(pg.out)) break; if (/authentication failed/i.test(pg.out)) break; await new Promise((r) => setTimeout(r, 3000)); } assert.doesNotMatch(pg.out, /authentication failed/i, `postgres delivered ${mod} a password that does not authenticate:\n${pg.out}`); assert.match(pg.out, /^1$/m, `${mod} could not connect to its granted postgres database as ${bound.as}:\n${pg.out}`); } // baserow's cache is its own: with no REDIS_HOST the image runs one inside the container, under a // password it makes itself, and the mesh grants nothing (novox/hq 081). Three things say so: // baserow said it chose its own; the cache answers on loopback with the challenge for that password // (PONG would be a cache anyone can use, and fails); and nothing was refused a login. const baserowLog = async () => (await on(NODE, `docker logs baserow 2>&1`)).out; let chose = ""; let cache = { out: "", ok: false }; const untilCache = Date.now() + 240_000; while (Date.now() < untilCache) { chose = await baserowLog(); cache = await on(NODE, `docker exec baserow redis-cli -h 127.0.0.1 ping 2>&1`); if (/Using embedded baserow redis/.test(chose) && /NOAUTH/.test(cache.out)) break; await new Promise((r) => setTimeout(r, 5000)); } assert.match(chose, /Using embedded baserow redis/, `baserow did not start its own cache:\n${chose.split("\n").filter((l) => /redis/i.test(l)).slice(-15).join("\n")}`); assert.match(cache.out, /NOAUTH/, `baserow's own cache does not answer with its password challenge inside the container:\n${cache.out}`); assert.doesNotMatch(chose, /WRONGPASS|NOPERM/, `baserow was refused by its cache:\n${chose.split("\n").filter((l) => /WRONGPASS|NOPERM/.test(l)).slice(-15).join("\n")}`); // Helper: wait for the mesh to write a consumer's bound file with an `as`, and parse it. async function waitForBinding(path: string): Promise<{ as: string; provision: string }> { let raw = ""; const until = Date.now() + 90_000; while (Date.now() < until) { const got = await on(NODE, `cat ${path} 2>/dev/null`); if (got.ok && /"as"/.test(got.out)) { raw = got.out; break; } await new Promise((r) => setTimeout(r, 3000)); } assert.match(raw, /"as"/, `the mesh never wrote a binding with a login to ${path}:\n${raw}`); return JSON.parse(raw) as { as: string; provision: string }; } });