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:
@@ -31,9 +31,9 @@
|
||||
* MESH_LAB_INCUS='sudo -n incus'
|
||||
* MESH_LAB_HOST_BINARY=.../mesh-host/mesh-host
|
||||
* MESH_LAB_BOOTSTRAP_BINARY=.../mesh-host/mesh-bootstrap
|
||||
* MESH_LAB_BUNDLE=.../mesh-host/examples/substrate-first-node.lock
|
||||
* MESH_LAB_BUNDLE=.../mesh-host/examples/foundation-first-node.lock
|
||||
* MESH_LAB_CATALOG=.../mesh-catalog/modules
|
||||
* MESH_LAB_SOURCE=<forge>/mesh-control.git MESH_LAB_SOURCE_REF=<commit>
|
||||
* MESH_LAB_SOURCE=<forge>/mesh-controller.git MESH_LAB_SOURCE_REF=<commit>
|
||||
* MESH_LAB_KEEP=1 to leave it standing afterwards
|
||||
*/
|
||||
import { test, before, after } from "node:test";
|
||||
@@ -45,7 +45,7 @@ import { loadScenario } from "../../src/declaration/parse.ts";
|
||||
import { raise } from "../../src/lifecycle/raise.ts";
|
||||
import { destroy, exec, push } from "../../src/lifecycle/operate.ts";
|
||||
import { bootstrapBinaryPath, hostBinaryPath, HOST_PATH } from "../../src/lifecycle/place.ts";
|
||||
import { labIsUsable, destroyAll, substrateBundle } from "./harness.ts";
|
||||
import { labIsUsable, destroyAll, foundationBundle } from "./harness.ts";
|
||||
import { genesis, type GenesisResult } from "./genesis.ts";
|
||||
import { incus } from "../../src/incus/client.ts";
|
||||
import { instanceNameOf } from "../../src/lifecycle/operate.ts";
|
||||
@@ -69,7 +69,7 @@ const REGISTRY = `${ANCHOR}:5000`;
|
||||
*/
|
||||
const BASE = { module: "mesh-tools", repo: "mesh-tools", path: "" };
|
||||
/**
|
||||
* What `amqp-ping` requires, and what the substrate does not supply.
|
||||
* What `amqp-ping` requires, and what the foundation does not supply.
|
||||
*
|
||||
* The installer raises a broker, but as a bundle resource — plumbing, not a module the mesh has a
|
||||
* record of, so it provides nothing to anything. A module asking for `amqp` is asking for a
|
||||
@@ -98,7 +98,7 @@ const FILTER_MODULE = "nftables";
|
||||
const STORE = { module: "postgres", repo: "mesh-catalog", path: "modules/postgres", container: "mesh-postgres" };
|
||||
const CATALOGUE = { module: "mesh-catalog", repo: "mesh-catalog", path: "modules/mesh-catalog", container: "mesh-catalog" };
|
||||
/** The control plane, rebuilt from its own repository — the step that ends the installer's tenure. */
|
||||
const CONTROL_PLANE = { module: "mesh-control", repo: "mesh-control", path: "", container: "mesh-control" };
|
||||
const CONTROL_PLANE = { module: "mesh-controller", repo: "mesh-controller", path: "", container: "mesh-controller" };
|
||||
|
||||
/**
|
||||
* What this mesh must hold when it is finished, and what must be RUNNING on the machine.
|
||||
@@ -108,14 +108,14 @@ const CONTROL_PLANE = { module: "mesh-control", repo: "mesh-control", path: "",
|
||||
* the registry and the builder — are here too: they are carried in, and a mesh missing any of them
|
||||
* is not one.
|
||||
*/
|
||||
const MUST_HOLD = ["mesh-control", "distribution", "builder", "mesh-tools", "postgres",
|
||||
const MUST_HOLD = ["mesh-controller", "distribution", "builder", "mesh-tools", "postgres",
|
||||
"mesh-catalog", "lavinmq", "amqp-ping"];
|
||||
const MUST_RUN = ["mesh-control", "mesh-registry", "mesh-broker", "mesh-store",
|
||||
const MUST_RUN = ["mesh-controller", "mesh-registry", "mesh-broker", "mesh-store",
|
||||
"mesh-postgres", "mesh-catalog", "mesh-lavinmq", "amqp-ping"];
|
||||
/** Named once, because the step title is also how later steps say what they waited on. */
|
||||
const NEEDS = "the mesh runs a broker for that module to talk to";
|
||||
const GENESIS = "a bare machine becomes a mesh of one, raised by the installer";
|
||||
const SUBSTRATE = "the substrate is up — a store and a broker of the mesh's own";
|
||||
const FOUNDATION = "the foundation is up — a store and a broker of the mesh's own";
|
||||
const BUILT_CP = "the control plane is one this mesh built, not one it was handed";
|
||||
const PIVOTED = "the pivot finished — what raised the mesh is gone";
|
||||
const HAS_REGISTRY = "the registry serves this mesh its own images";
|
||||
@@ -191,7 +191,7 @@ const skip =
|
||||
!installer ? "MESH_LAB_BOOTSTRAP_BINARY is not set to a built mesh-bootstrap" :
|
||||
!source ? "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" :
|
||||
!bundle || !existsSync(bundle) ? "MESH_LAB_BUNDLE is not set to a substrate template" :
|
||||
!bundle || !existsSync(bundle) ? "MESH_LAB_BUNDLE is not set to a foundation template" :
|
||||
!catalogDir || !existsSync(catalogDir) ? "MESH_LAB_CATALOG is not set to mesh-catalog/modules" :
|
||||
false;
|
||||
|
||||
@@ -220,19 +220,19 @@ async function mesh(command: string, timeoutMs?: number): Promise<string> {
|
||||
// The control plane is a live, mesh-MANAGED container: the mesh recreates it whenever its
|
||||
// declaration changes — most visibly when the machine first gets its `.internal` name on the
|
||||
// private network, which becomes the container's `--add-host` (containers are immutable, so a new
|
||||
// spec is a new container). A `docker exec mesh-control` that lands in that brief recreate window
|
||||
// spec is a new container). A `docker exec mesh-controller` that lands in that brief recreate window
|
||||
// fails with "container ... is not running". That is not the mesh being wrong — it is a command
|
||||
// racing a legitimate restart — so it is retried until the control plane answers again. A real
|
||||
// command failure (anything else) still throws at once.
|
||||
const deadline = Date.now() + (timeoutMs ?? 120_000);
|
||||
for (;;) {
|
||||
const { out, ok } = await on(CONTROL, `docker exec mesh-control /mesh-control ${command}`, timeoutMs);
|
||||
const { out, ok } = await on(CONTROL, `docker exec mesh-controller /mesh-controller ${command}`, timeoutMs);
|
||||
if (ok) return out;
|
||||
if (/is not running|No such container/i.test(out) && Date.now() < deadline) {
|
||||
await new Promise((r) => setTimeout(r, 2_000));
|
||||
continue;
|
||||
}
|
||||
throw new Error(`${CONTROL}: docker exec mesh-control /mesh-control ${command}\n${out}`);
|
||||
throw new Error(`${CONTROL}: docker exec mesh-controller /mesh-controller ${command}\n${out}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ async function waitForContainer(node: string, container: string, seconds = 200):
|
||||
*
|
||||
* **The control plane runs in a container, so a file on the machine is not a file it can open.**
|
||||
* Pushing the manifest to the machine and naming that path got `no such file or directory` from
|
||||
* inside mesh-control, which is correct and was briefly mistaken for a missing manifest. It is
|
||||
* inside mesh-controller, which is correct and was briefly mistaken for a missing manifest. It is
|
||||
* copied the last step of the way with `docker cp`.
|
||||
*
|
||||
* **Into the root, not into /tmp.** The control plane's image is a minimal one and has no `/tmp`
|
||||
@@ -315,7 +315,7 @@ async function registerModule(module: string, manifest: string): Promise<string>
|
||||
const onMachine = `/tmp/${module}.json`;
|
||||
const inContainer = `/${module}.json`;
|
||||
await push(instanceId, CONTROL, manifest, onMachine);
|
||||
await must(CONTROL, `docker cp ${onMachine} mesh-control:${inContainer}`);
|
||||
await must(CONTROL, `docker cp ${onMachine} mesh-controller:${inContainer}`);
|
||||
return mesh(`module add ${inContainer}`);
|
||||
}
|
||||
function tokenFrom(said: string): string {
|
||||
@@ -393,7 +393,7 @@ function report(name: string): string {
|
||||
*/
|
||||
const PLAN: { code: string; title: string }[] = [
|
||||
{ code: "R1", title: GENESIS },
|
||||
{ code: "R2", title: SUBSTRATE },
|
||||
{ code: "R2", title: FOUNDATION },
|
||||
{ code: "R3", title: BUILT_CP },
|
||||
{ code: "R4", title: PIVOTED },
|
||||
{ code: "R5", title: HAS_REGISTRY },
|
||||
@@ -479,7 +479,7 @@ before(async () => {
|
||||
// whose anchor is somewhere else every node, including this one, would enrol against an
|
||||
// address nothing answers on. The installer refuses to guess it and says so, which is
|
||||
// right: it does not know what this machine is called from outside.
|
||||
bundleTemplate: substrateBundle(bundle, []).replaceAll("192.0.2.10:5671", `${ANCHOR}:5671`),
|
||||
bundleTemplate: foundationBundle(bundle, []).replaceAll("192.0.2.10:5671", `${ANCHOR}:5671`),
|
||||
registry: REGISTRY,
|
||||
source,
|
||||
sourceRef,
|
||||
@@ -514,7 +514,7 @@ before(async () => {
|
||||
// saying it published an image and the registry serving one are different facts, and it is the
|
||||
// second that matters.
|
||||
|
||||
await step("R2", SUBSTRATE, GENESIS, async () => {
|
||||
await step("R2", FOUNDATION, GENESIS, async () => {
|
||||
await waitForContainer(CONTROL, "mesh-store", 120);
|
||||
await waitForContainer(CONTROL, "mesh-broker", 120);
|
||||
return (await on(CONTROL, `docker ps --format '{{.Names}}\t{{.Status}}'`)).out;
|
||||
@@ -525,7 +525,7 @@ before(async () => {
|
||||
// the image has to be one this mesh's own registry serves.
|
||||
await step("R3", BUILT_CP, GENESIS, async () => {
|
||||
const image = (await on(CONTROL,
|
||||
`docker inspect -f '{{.Config.Image}}' mesh-control 2>&1`)).out.trim();
|
||||
`docker inspect -f '{{.Config.Image}}' mesh-controller 2>&1`)).out.trim();
|
||||
assert.match(image, /@sha256:[0-9a-f]{64}/,
|
||||
`the control plane names its image by tag, not by digest: ${image}`);
|
||||
assert.ok(image.includes(":5000/"),
|
||||
@@ -535,17 +535,17 @@ before(async () => {
|
||||
|
||||
await step("R4", PIVOTED, BUILT_CP, async () => {
|
||||
const ps = (await on(CONTROL, `docker ps -a --format '{{.Names}}'`)).out;
|
||||
assert.doesNotMatch(ps, /^temp-mesh-control$/m,
|
||||
assert.doesNotMatch(ps, /^temp-mesh-controller$/m,
|
||||
`the temporary control plane is still here, so the pivot did not finish:\n${ps}`);
|
||||
return ps;
|
||||
});
|
||||
|
||||
await step("R5", HAS_REGISTRY, SUBSTRATE, async () => {
|
||||
await step("R5", HAS_REGISTRY, FOUNDATION, async () => {
|
||||
await waitForContainer(CONTROL, "mesh-registry", 120);
|
||||
const held = (await on(CONTROL,
|
||||
`curl -sS --max-time 15 http://127.0.0.1:5000/v2/_catalog`)).out;
|
||||
assert.match(held, /mesh-control/,
|
||||
`the registry serves no mesh-control, so nothing was published into it:\n${held}`);
|
||||
assert.match(held, /mesh-controller/,
|
||||
`the registry serves no mesh-controller, so nothing was published into it:\n${held}`);
|
||||
return held.trim();
|
||||
});
|
||||
|
||||
@@ -597,7 +597,7 @@ before(async () => {
|
||||
return built;
|
||||
});
|
||||
|
||||
// A store of its own. **Not the substrate's.** The installer raises a store for the control
|
||||
// A store of its own. **Not the foundation's.** The installer raises a store for the control
|
||||
// plane to keep its own records in, the way it raises a broker — plumbing, not a module the mesh
|
||||
// has any record of, so it provides nothing to anything. A module that wants a database wants a
|
||||
// provider in the graph, and the catalogue below is the first thing to want one.
|
||||
@@ -790,7 +790,7 @@ before(async () => {
|
||||
// broker: a module's account is scoped to what it declares it emits and consumes, and calling
|
||||
// a tool needs a temporary reply queue that scope does not cover. So a module can SERVE tools
|
||||
// and cannot CALL them, and nothing issues an account to anyone who wants to ask (novox/hq
|
||||
// issue 049). Until that is decided the caller is the substrate's bootstrap admin over the
|
||||
// issue 049). Until that is decided the caller is the foundation's bootstrap admin over the
|
||||
// broker's loopback, reached by joining its network namespace.
|
||||
const image = (await on(CONTROL,
|
||||
`docker inspect -f '{{.Config.Image}}' mesh-catalog`)).out.trim();
|
||||
@@ -1061,7 +1061,7 @@ after(async () => {
|
||||
|
||||
for (const name of [
|
||||
GENESIS,
|
||||
SUBSTRATE,
|
||||
FOUNDATION,
|
||||
BUILT_CP,
|
||||
PIVOTED,
|
||||
HAS_REGISTRY,
|
||||
|
||||
Reference in New Issue
Block a user