Rename mesh-control -> mesh-controller, substrate -> foundation
One name per thing, per the HQ glossary: the module/container/image/binary/repo becomes mesh-controller, the seat the-controller, and the store+broker pair the foundation (embedded base bundles, default template and example lock renamed with their go:embed directives). No behaviour change — a pure vocabulary rename. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
/**
|
||||
* The DB-consumer chain a single node cannot host, proved across two machines.
|
||||
*
|
||||
* app-postgres and the mesh's own substrate store both want host port 5432, so they cannot share a
|
||||
* machine. Every earlier catalogue bed put the provider on the same node as the substrate and got
|
||||
* app-postgres and the mesh's own foundation store both want host port 5432, so they cannot share a
|
||||
* machine. Every earlier catalogue bed put the provider on the same node as the foundation and got
|
||||
* away with it only because the provider published no 5432 a consumer ever reached, or because the
|
||||
* substrate's store and the module's postgres were the same container. The moment a real
|
||||
* foundation's store and the module's postgres were the same container. The moment a real
|
||||
* postgres PROVIDER must publish 5432 for real consumers to connect, it collides with the store the
|
||||
* substrate already has there — and the chain is blocked single-node.
|
||||
* foundation already has there — and the chain is blocked single-node.
|
||||
*
|
||||
* This is the split that unblocks it. `anchor` runs the substrate (store, broker, control) and
|
||||
* This is the split that unblocks it. `anchor` runs the foundation (store, broker, control) and
|
||||
* NOTHING else. `laptop` runs the whole chain: the postgres and redis PROVIDERS, and the baserow
|
||||
* and letta CONSUMERS that require them. Provider and consumers are co-located on laptop, so the
|
||||
* grant never crosses a node boundary and no overlay is needed — only enrolment crosses to anchor,
|
||||
* over the underlay both machines share. And because the substrate store is on the OTHER node, the
|
||||
* over the underlay both machines share. And because the foundation store is on the OTHER node, the
|
||||
* provider owns laptop's 5432 uncontested.
|
||||
*
|
||||
* The four manifests are the committed catalogue shapes (novox/hq ADR 0039/0047/0048), verbatim
|
||||
@@ -22,10 +22,10 @@
|
||||
* 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 substrate bundle:
|
||||
* It needs a host binary and the foundation bundle:
|
||||
*
|
||||
* MESH_LAB_HOST_BINARY=.../mesh-host
|
||||
* MESH_LAB_BUNDLE=.../examples/substrate-first-node.lock
|
||||
* MESH_LAB_BUNDLE=.../examples/foundation-first-node.lock
|
||||
*
|
||||
* HELPER — stock the four runtimes into the local daemon before the run (some may already be there):
|
||||
* scripts/build-module-runtime.sh postgres /tmp/postgres.tar
|
||||
@@ -44,7 +44,7 @@ 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, substrateBundle, onTheMachine } from "./harness.ts";
|
||||
import { labIsUsable, destroyAll, foundationBundle, onTheMachine } from "./harness.ts";
|
||||
import type { HeldImage } from "../../src/pinning.ts";
|
||||
|
||||
const capability = await labIsUsable();
|
||||
@@ -56,11 +56,11 @@ const skip = !capability.usable
|
||||
: !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 substrate bundle (mesh-host examples/)"
|
||||
? "MESH_LAB_BUNDLE is not set to a foundation bundle (mesh-host examples/)"
|
||||
: false;
|
||||
|
||||
const SCENARIO = "two-node-db";
|
||||
/** The node that carries the whole DB-consumer chain. anchor carries only the substrate. */
|
||||
/** The node that carries the whole DB-consumer chain. anchor carries only the foundation. */
|
||||
const NODE = "laptop";
|
||||
|
||||
let instanceId = "";
|
||||
@@ -88,7 +88,7 @@ async function must(machine: string, command: string, timeoutMs?: number): Promi
|
||||
|
||||
/** The control plane, a container on the first node. */
|
||||
async function mesh(command: string, timeoutMs?: number): Promise<string> {
|
||||
return must("anchor", `docker exec mesh-control /mesh-control ${command}`, timeoutMs);
|
||||
return must("anchor", `docker exec mesh-controller /mesh-controller ${command}`, timeoutMs);
|
||||
}
|
||||
|
||||
/** The reference a manifest should carry, once this scenario has been raised. */
|
||||
@@ -97,9 +97,9 @@ function pinned(reference: string): string {
|
||||
return onTheMachine(reference, held);
|
||||
}
|
||||
|
||||
/** The substrate bundle: ours by the ID the machine holds, everything else upstream. */
|
||||
/** The foundation bundle: ours by the ID the machine holds, everything else upstream. */
|
||||
function bundleFor(images: HeldImage[]): string {
|
||||
return substrateBundle(bundle, images);
|
||||
return foundationBundle(bundle, images);
|
||||
}
|
||||
|
||||
function tokenFrom(said: string): string {
|
||||
@@ -128,7 +128,7 @@ async function settled(node: string, withinMs = 1_200_000): Promise<void> {
|
||||
} | undefined;
|
||||
let said = "";
|
||||
try {
|
||||
const asked = await on("anchor", `docker exec mesh-control /mesh-control status --json`);
|
||||
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) {
|
||||
@@ -169,13 +169,13 @@ before(async () => {
|
||||
instanceId = raised.instanceId;
|
||||
held = raised.images;
|
||||
|
||||
// The first node raises the substrate — store, broker, control — from the bundle its host carries,
|
||||
// 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/substrate.lock <<'MESHBUNDLE'\n${bundleFor(raised.images)}\nMESHBUNDLE`);
|
||||
await must("anchor", `${HOST_PATH} apply /tmp/substrate.lock`, 600_000);
|
||||
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-control"]) {
|
||||
assert.match(up, new RegExp(c), `the substrate did not raise ${c}:\n${up}`);
|
||||
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
|
||||
@@ -196,7 +196,7 @@ after(async () => {
|
||||
await destroyAll(`${SCENARIO}-`);
|
||||
}, { timeout: 600_000 });
|
||||
|
||||
test("the provider and its consumers ride the second node while the substrate owns 5432 on the first", {
|
||||
test("the provider and its consumers ride the second node while the foundation owns 5432 on the first", {
|
||||
skip, timeout: 1_500_000,
|
||||
}, async () => {
|
||||
// ================================================================================================
|
||||
@@ -392,7 +392,7 @@ test("the provider and its consumers ride the second node while the substrate ow
|
||||
|
||||
// --- add, issue a scoped broker account, assign to laptop, then ONE push -------------------------
|
||||
async function addIssueAssign(name: string, manifest: string): Promise<void> {
|
||||
await must("anchor", `printf %s ${quote(manifest)} > /tmp/${name}.json && docker cp /tmp/${name}.json mesh-control:/${name}.json`);
|
||||
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);
|
||||
@@ -400,7 +400,7 @@ test("the provider and its consumers ride the second node while the substrate ow
|
||||
}
|
||||
|
||||
// The consumer connects to its provider by the provider's PRIVATE-NETWORK address — the binding's
|
||||
// `at`, which mesh-control fills as "where the consuming machine is on the private network, empty
|
||||
// `at`, which mesh-controller fills as "where the consuming machine is on the private network, empty
|
||||
// if it is not on one" (declaration.go). So even though provider and consumer are co-located on
|
||||
// laptop, the address baserow is handed is the mesh OVERLAY address, and it is empty unless the
|
||||
// machine is on the overlay. The overlay networking is therefore assigned first, to both nodes.
|
||||
@@ -411,7 +411,7 @@ test("the provider and its consumers ride the second node while the substrate ow
|
||||
|
||||
// Providers first, then the consumers that require them. The mesh resolves the whole set at push
|
||||
// time regardless of order; this order simply reads like the dependency graph. Provider AND
|
||||
// consumers all go to laptop; the 5432 conflict is gone because the substrate store is on anchor.
|
||||
// consumers all go to laptop; the 5432 conflict is gone because the foundation store is on anchor.
|
||||
await addIssueAssign("postgres", postgresManifest);
|
||||
await addIssueAssign("redis", redisManifest);
|
||||
await addIssueAssign("baserow", baserowManifest);
|
||||
@@ -422,14 +422,14 @@ test("the provider and its consumers ride the second node while the substrate ow
|
||||
await settled(NODE);
|
||||
|
||||
// ================================================================================================
|
||||
// THE two-node split — the substrate owns 5432 on anchor, the provider owns it on laptop.
|
||||
// THE two-node split — the foundation owns 5432 on anchor, the provider owns it 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 substrate store is not on the first node");
|
||||
assert.match(onAnchor, /(^|\n)mesh-store(\n|$)/, "the foundation store is not on the first node");
|
||||
assert.doesNotMatch(onAnchor, /(^|\n)postgres(\n|$)/,
|
||||
"the postgres provider landed on the substrate node — the 5432 collision this bed exists to avoid");
|
||||
assert.doesNotMatch(onLaptop, /(^|\n)mesh-store(\n|$)/, "the substrate store leaked onto the second node");
|
||||
"the postgres provider landed on the foundation node — the 5432 collision this bed exists to avoid");
|
||||
assert.doesNotMatch(onLaptop, /(^|\n)mesh-store(\n|$)/, "the foundation store leaked onto the second node");
|
||||
assert.match(onLaptop, /(^|\n)postgres(\n|$)/, "the postgres provider is not on the second node");
|
||||
|
||||
// ================================================================================================
|
||||
@@ -453,7 +453,7 @@ test("the provider and its consumers ride the second node while the substrate ow
|
||||
// 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-control fix (secrets_into_files.go matched a need by provision name alone, not by consuming
|
||||
// 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.
|
||||
{
|
||||
@@ -496,7 +496,7 @@ test("the provider and its consumers ride the second node while the substrate ow
|
||||
}
|
||||
|
||||
// ================================================================================================
|
||||
// Each module got its own scoped broker account on the substrate's broker (which is on anchor,
|
||||
// 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`);
|
||||
|
||||
Reference in New Issue
Block a user