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:
2026-09-16 18:40:40 +02:00
parent b520bd1825
commit 41637befff
9 changed files with 34 additions and 34 deletions
+4 -4
View File
@@ -6,7 +6,7 @@ per module under [`modules/`](modules/).
This is **data, not a control-plane concern**. The manifests describe *what a module is*: what it This is **data, not a control-plane concern**. The manifests describe *what a module is*: what it
provides, what it requires, the seats it claims, the resources the host applies for it. The provides, what it requires, the seats it claims, the resources the host applies for it. The
engine that reads them — parsing, eligibility resolution, sealing, declaration emission — lives engine that reads them — parsing, eligibility resolution, sealing, declaration emission — lives
in the control plane (`novox/mesh-control`, `internal/catalogue`), which consumes this repository in the control plane (`novox/mesh-controller`, `internal/catalogue`), which consumes this repository
as a build source. The host (`novox/mesh-host`) applies the declarations the control plane emits. as a build source. The host (`novox/mesh-host`) applies the declarations the control plane emits.
Neither is here. Neither is here.
@@ -17,9 +17,9 @@ manifest names its image (pinned by digest), the resources the host owns for it
files, the container, the private network it joins), what it `requires` from a provider and what files, the container, the private network it joins), what it `requires` from a provider and what
it `provides` to consumers, and the sealed secrets it needs filled on the machine. it `provides` to consumers, and the sealed secrets it needs filled on the machine.
- **Core mesh components are not modules.** The node host, the substrate, the control-plane - **Core mesh components are not modules.** The node host, the foundation, the control-plane
contexts and the surfaces are the mesh itself; they ship as their own repositories contexts and the surfaces are the mesh itself; they ship as their own repositories
(`mesh-host`, `mesh-substrate`, `mesh-control`, `mesh-surfaces`, `mesh-sdk`), not from here. (`mesh-host`, `mesh-foundation`, `mesh-controller`, `mesh-surfaces`, `mesh-sdk`), not from here.
- **Standalone applications are not here either.** A larger application lives in its own - **Standalone applications are not here either.** A larger application lives in its own
repository with its manifest at the root, registered with the mesh as a build source repository with its manifest at the root, registered with the mesh as a build source
(novox/hq [ADR 0010](https://git.novox.be/novox/hq)). This repository holds the modules the (novox/hq [ADR 0010](https://git.novox.be/novox/hq)). This repository holds the modules the
@@ -46,7 +46,7 @@ provider/consumer edge — is data inside the manifests, not a directory the tre
The shape a manifest must satisfy is owned by the control plane's catalogue engine and is what The shape a manifest must satisfy is owned by the control plane's catalogue engine and is what
validates a manifest before a machine ever sees it — a stray key, a consumer contributing the validates a manifest before a machine ever sees it — a stray key, a consumer contributing the
wrong provision field, an image that nothing builds. That validation belongs with this wrong provision field, an image that nothing builds. That validation belongs with this
repository and is being re-homed here from `mesh-control`; until it is, the pipeline is the repository and is being re-homed here from `mesh-controller`; until it is, the pipeline is the
gate — it builds each module and refuses a manifest it cannot resolve. gate — it builds each module and refuses a manifest it cannot resolve.
## Where the reasoning lives ## Where the reasoning lives
+3 -3
View File
@@ -1,9 +1,9 @@
// Reading the manager node's PUBLIC sealing key out of the bound facts the mesh delivers, and // Reading the manager node's PUBLIC sealing key out of the bound facts the mesh delivers, and
// writing a sealed refresh token in the wire shape mesh-control reads. // writing a sealed refresh token in the wire shape mesh-controller reads.
// //
// **The public key is delivered, not derived.** The manager module holds no node key of its own // **The public key is delivered, not derived.** The manager module holds no node key of its own
// (novox/hq ADR 0050) — it is deliberately never given one. To seal a refresh token to this node it // (novox/hq ADR 0050) — it is deliberately never given one. To seal a refresh token to this node it
// needs the node's PUBLIC sealing key, and mesh-control puts that in the manager holder's bound facts // needs the node's PUBLIC sealing key, and mesh-controller puts that in the manager holder's bound facts
// (`serves.manager_public_key`), safe to disclose because it is public. Both adoption and every // (`serves.manager_public_key`), safe to disclose because it is public. Both adoption and every
// rotation read it from there. // rotation read it from there.
@@ -23,7 +23,7 @@ export function managerPublicKey(boundFile: string): string {
return key; return key;
} }
/** Write a sealed refresh token in the {sealed, manager_key} wire shape mesh-control reads. */ /** Write a sealed refresh token in the {sealed, manager_key} wire shape mesh-controller reads. */
export function writeSealedGrant(path: string, sealed: string, managerKey: string): void { export function writeSealedGrant(path: string, sealed: string, managerKey: string): void {
mkdirSync(dirname(path), { recursive: true }); mkdirSync(dirname(path), { recursive: true });
const tmp = `${path}.tmp`; const tmp = `${path}.tmp`;
+3 -3
View File
@@ -12,13 +12,13 @@
// never the refresh token — which it seals per consumer holder and stores; // never the refresh token — which it seals per consumer holder and stores;
// 5. poll usage with the fresh access token and record the licence-grain reading. // 5. poll usage with the fresh access token and record the licence-grain reading.
// //
// mesh-control receives the products of steps 3–4 through `licence submit-refresh` (access token + // mesh-controller receives the products of steps 3–4 through `licence submit-refresh` (access token +
// sealed box). The refresh token never leaves this process except as ciphertext, and it never had to // sealed box). The refresh token never leaves this process except as ciphertext, and it never had to
// be opened here at all — the host did that. // be opened here at all — the host did that.
// //
// This runs as `mesh-tools run`, which connects no broker, so the outputs are written to files the // This runs as `mesh-tools run`, which connects no broker, so the outputs are written to files the
// host mounts; the submit itself (the transport to mesh-control) is done by the caller invoking // host mounts; the submit itself (the transport to mesh-controller) is done by the caller invoking
// `mesh-control licence submit-refresh`. In the lab that caller is the scenario; in production it is // `mesh-controller licence submit-refresh`. In the lab that caller is the scenario; in production it is
// an authenticated call the manager node makes. The transport is the one part stubbed here — FLAGGED // an authenticated call the manager node makes. The transport is the one part stubbed here — FLAGGED
// — because a cross-node authenticated command surface is out of this module's scope. // — because a cross-node authenticated command surface is out of this module's scope.
+4 -4
View File
@@ -5,14 +5,14 @@
// carve-out delivers the refresh token to the manager module the way the mesh delivers every other // carve-out delivers the refresh token to the manager module the way the mesh delivers every other
// credential: sealed to the node's key, and unsealed by the *host* — never by the module. The host // credential: sealed to the node's key, and unsealed by the *host* — never by the module. The host
// unseals with Go's `golang.org/x/crypto/nacl/box.OpenAnonymous` (mesh-host // unseals with Go's `golang.org/x/crypto/nacl/box.OpenAnonymous` (mesh-host
// internal/identity/sealing.go), and mesh-control seals with `box.SealAnonymous` // internal/identity/sealing.go), and mesh-controller seals with `box.SealAnonymous`
// (mesh-control internal/secrets/seal.go). Both are NaCl `crypto_box_seal`: // (mesh-controller internal/secrets/seal.go). Both are NaCl `crypto_box_seal`:
// //
// sealed = ephemeralPub(32) ‖ crypto_box(msg, nonce, recipientPub, ephemeralSecret) // sealed = ephemeralPub(32) ‖ crypto_box(msg, nonce, recipientPub, ephemeralSecret)
// nonce = blake2b( ephemeralPub ‖ recipientPub , 24 bytes, unkeyed ) // nonce = blake2b( ephemeralPub ‖ recipientPub , 24 bytes, unkeyed )
// //
// When the vendor rotates the refresh token, the manager module must store the new one back the // When the vendor rotates the refresh token, the manager module must store the new one back the
// same way — sealed to the manager node's own sealing key — so mesh-control keeps it without ever // same way — sealed to the manager node's own sealing key — so mesh-controller keeps it without ever
// reading it and the host can later unseal it to deliver the cleartext again. That reseal happens // reading it and the host can later unseal it to deliver the cleartext again. That reseal happens
// here, on the manager node, in TypeScript. It therefore has to produce the *identical* byte format // here, on the manager node, in TypeScript. It therefore has to produce the *identical* byte format
// Go's `Open` accepts, or the host would refuse the delivery. // Go's `Open` accepts, or the host would refuse the delivery.
@@ -27,7 +27,7 @@
// (package.json dependencies; novox/hq ADR 0052). // (package.json dependencies; novox/hq ADR 0052).
// //
// **How it is kept honest.** A cross-language test seals a fixture here and opens it in Go // **How it is kept honest.** A cross-language test seals a fixture here and opens it in Go
// (mesh-control internal/secrets/sealedbox_xcheck_test.go); the fixture is regenerated from this // (mesh-controller internal/secrets/sealedbox_xcheck_test.go); the fixture is regenerated from this
// `seal()`. A drift between this seal and Go's box surfaces there as a seal Go cannot open, not as a // `seal()`. A drift between this seal and Go's box surfaces there as a seal Go cannot open, not as a
// refresh token silently mangled in production. // refresh token silently mangled in production.
// //
@@ -5,7 +5,7 @@ import { generateKeyPairSync } from "node:crypto";
import { seal } from "../sealedbox.ts"; import { seal } from "../sealedbox.ts";
// The definitive proof that this seal interoperates with Go's box.OpenAnonymous (the host's Unseal // The definitive proof that this seal interoperates with Go's box.OpenAnonymous (the host's Unseal
// and mesh-control's secrets.Seal/Open) is a cross-language test in mesh-control // and mesh-controller's secrets.Seal/Open) is a cross-language test in mesh-controller
// (internal/secrets/sealedbox_xcheck_test.go), which opens a fixture this module's seal() produced. // (internal/secrets/sealedbox_xcheck_test.go), which opens a fixture this module's seal() produced.
// These tests hold the TypeScript side: the output has the crypto_box_seal shape, and it is // These tests hold the TypeScript side: the output has the crypto_box_seal shape, and it is
// randomised so a rotation that changed nothing looks nothing like one that changed everything. // randomised so a rotation that changed nothing looks nothing like one that changed everything.
@@ -1,5 +1,5 @@
{ {
"module": "mesh-control", "module": "mesh-controller",
"version": "1", "version": "1",
"slug": "control", "slug": "control",
"capabilities": [ "capabilities": [
@@ -7,43 +7,43 @@
], ],
"claims": [ "claims": [
{ {
"name": "the-control-plane", "name": "the-controller",
"scope": "mesh" "scope": "mesh"
} }
], ],
"own-secrets": { "own-secrets": {
"inventory": "/var/lib/mesh/mesh-control/inventory", "inventory": "/var/lib/mesh/mesh-controller/inventory",
"identity": "/var/lib/mesh/mesh-control/identity", "identity": "/var/lib/mesh/mesh-controller/identity",
"licences": "/var/lib/mesh/mesh-control/licences", "licences": "/var/lib/mesh/mesh-controller/licences",
"broker": "/var/lib/mesh/mesh-control/broker", "broker": "/var/lib/mesh/mesh-controller/broker",
"broker-management": "/var/lib/mesh/mesh-control/broker-management", "broker-management": "/var/lib/mesh/mesh-controller/broker-management",
"broker-address": "/var/lib/mesh/mesh-control/broker-address" "broker-address": "/var/lib/mesh/mesh-controller/broker-address"
}, },
"resources": [ "resources": [
{ {
"id": "mesh-state", "id": "mesh-state",
"type": "directory", "type": "directory",
"path": "/var/lib/mesh/mesh-control", "path": "/var/lib/mesh/mesh-controller",
"mode": "0700" "mode": "0700"
}, },
{ {
"id": "control-env", "id": "control-env",
"type": "file", "type": "file",
"path": "/var/lib/mesh/mesh-control/control.env", "path": "/var/lib/mesh/mesh-controller/control.env",
"mode": "0600", "mode": "0600",
"content": "MESH_STORE_INVENTORY=${secret:inventory}\nMESH_STORE_IDENTITY=${secret:identity}\nMESH_STORE_LICENCES=${secret:licences}\nMESH_BROKER_AMQP=${secret:broker}\nMESH_BROKER_MANAGEMENT=${secret:broker-management}\nMESH_BROKER_ADDRESS=${secret:broker-address}\n" "content": "MESH_STORE_INVENTORY=${secret:inventory}\nMESH_STORE_IDENTITY=${secret:identity}\nMESH_STORE_LICENCES=${secret:licences}\nMESH_BROKER_AMQP=${secret:broker}\nMESH_BROKER_MANAGEMENT=${secret:broker-management}\nMESH_BROKER_ADDRESS=${secret:broker-address}\n"
}, },
{ {
"id": "server", "id": "server",
"type": "container", "type": "container",
"name": "mesh-control", "name": "mesh-controller",
"image": "mesh-control@sha256:0000000000000000000000000000000000000000000000000000000000000000", "image": "mesh-controller@sha256:0000000000000000000000000000000000000000000000000000000000000000",
"network": "host", "network": "host",
"args": [ "args": [
"serve" "serve"
], ],
"env-file": [ "env-file": [
"/var/lib/mesh/mesh-control/control.env" "/var/lib/mesh/mesh-controller/control.env"
], ],
"env": { "env": {
"MESH_BROKER_CERTIFICATE": "/broker-tls/tls.crt" "MESH_BROKER_CERTIFICATE": "/broker-tls/tls.crt"
+1 -1
View File
@@ -1,4 +1,4 @@
// model-usage's entrypoint — the usage context store's consumer (novox/hq ADR 0054). mesh-control is // model-usage's entrypoint — the usage context store's consumer (novox/hq ADR 0054). mesh-controller is
// a CLI and cannot consume events, so the store that keeps the latest usage reading is a MODULE: it // a CLI and cannot consume events, so the store that keeps the latest usage reading is a MODULE: it
// subscribes to `module.*.usage.*` and upserts each row. Like the audit-logger, the on(...) IS the // subscribes to `module.*.usage.*` and upserts each row. Like the audit-logger, the on(...) IS the
// whole handshake — the runtime imports this once the broker is bound, and every usage event any // whole handshake — the runtime imports this once the broker is bound, and every usage event any
+3 -3
View File
@@ -1,12 +1,12 @@
# The route-proxy module's runtime image: the reference reverse proxy compiled into a container. # The route-proxy module's runtime image: the reference reverse proxy compiled into a container.
# #
# **The proxy source is not vendored here.** The canonical proxy — the contract written as something # **The proxy source is not vendored here.** The canonical proxy — the contract written as something
# that runs — lives in the mesh-control repository at examples/route-proxy (novox/hq 08-connectivity # that runs — lives in the mesh-controller repository at examples/route-proxy (novox/hq 08-connectivity
# §3). This module ships the *packaging*, not a second copy of the contract, so the build context is # §3). This module ships the *packaging*, not a second copy of the contract, so the build context is
# the mesh-control repository root, and this Dockerfile compiles ./examples/route-proxy from it. # the mesh-controller repository root, and this Dockerfile compiles ./examples/route-proxy from it.
# #
# docker build -f mesh-catalog/modules/route-proxy/Dockerfile \ # docker build -f mesh-catalog/modules/route-proxy/Dockerfile \
# -t mesh-route-proxy:development <path-to>/mesh-control # -t mesh-route-proxy:development <path-to>/mesh-controller
# #
# The mesh pins the digest of what this produces; the committed module.json carries the placeholder # The mesh pins the digest of what this produces; the committed module.json carries the placeholder
# digest every mesh-built image does, replaced at publish. # digest every mesh-built image does, replaced at publish.
+2 -2
View File
@@ -30,9 +30,9 @@ an event. It only reads the file the mesh writes. (Contrast `redis`, which mints
## How it ships the Go proxy ## How it ships the Go proxy
The proxy is a Go program, unlike the TypeScript tool-runtime modules. The canonical source is The proxy is a Go program, unlike the TypeScript tool-runtime modules. The canonical source is
**not vendored here** — it lives in the mesh-control repository at `examples/route-proxy`, the **not vendored here** — it lives in the mesh-controller repository at `examples/route-proxy`, the
contract written as something that runs. This module ships only the packaging: a multi-stage contract written as something that runs. This module ships only the packaging: a multi-stage
[`Dockerfile`](Dockerfile) whose build context is the mesh-control repository root and which [`Dockerfile`](Dockerfile) whose build context is the mesh-controller repository root and which
compiles `./examples/route-proxy` into `mesh-route-proxy`. The committed `module.json` carries the compiles `./examples/route-proxy` into `mesh-route-proxy`. The committed `module.json` carries the
placeholder digest every mesh-built image does (`@sha256:0000…`); the mesh pins the real digest at placeholder digest every mesh-built image does (`@sha256:0000…`); the mesh pins the real digest at
publish. publish.