diff --git a/00-META/context.md b/00-META/context.md index 95b7cec..768ee8f 100644 --- a/00-META/context.md +++ b/00-META/context.md @@ -30,7 +30,7 @@ named, and nothing should be designed around a particular one existing. - **A hosted model provider** supplies the thinking for non-human agents, drawn from a shared pool of subscriptions — which is why budget pacing is a first-class concern. - **Long-lived user services** rather than an orchestrator. No cluster scheduler, no cloud - control plane. + controller. Defaults, not mandates. A second model provider is anticipated by design; nothing in the domain may assume one vendor's credential lifecycle. diff --git a/00-META/repos.md b/00-META/repos.md index d8e9194..315db01 100644 --- a/00-META/repos.md +++ b/00-META/repos.md @@ -28,8 +28,8 @@ target, not the present. | Repository | Tier | Holds | |---|---|---| | `mesh-host` | 0 | **exists.** The node host — one statically linked binary, requiring nothing present ([ADR 0005](../02-DECISIONS/0005-the-node-host.md)) | -| `mesh-substrate` | 1 | the four pinned services, as declarations | -| `mesh-control` | 2 | **exists.** The control plane and its contexts — one of seven built ([ADR 0006](../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)) | +| `mesh-foundation` | 1 | the four pinned services, as declarations | +| `mesh-control` | 2 | **exists.** The controller and its contexts — one of seven built ([ADR 0006](../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)) | | `mesh-surfaces` | 3 | tools, web, cli | | `mesh-sdk` | — | the stable spine modules build against — the tool-serving harness, the messaging/event framework, the contracts and core primitives. Holds nothing per-module and nothing volatile ([ADR 0039](../02-DECISIONS/0039-what-the-sdk-holds-and-refuses.md)). | | `mesh-lab` | — | **exists.** The lab — scenario lifecycle, networking, placement. Ships to nobody; runs on a workstation. | diff --git a/03-DESIGN/01-to-be/00-work-breakdown.md b/03-DESIGN/01-to-be/00-work-breakdown.md index 6db2583..72fbb87 100644 --- a/03-DESIGN/01-to-be/00-work-breakdown.md +++ b/03-DESIGN/01-to-be/00-work-breakdown.md @@ -92,7 +92,7 @@ What needs something *usable* retries, which is what both provisioners do and is anyway, because a dependency can restart long after everything was applied. **The network was a real gap, and the first thing in Phase 1 that needed a decision.** Adding a -shape widens what a compromised control plane can express, so +shape widens what a compromised controller can express, so [ADR 0029](../../02-DECISIONS/0029-a-network-is-a-shape-because-an-action-cannot-be-undone.md) records why this one is worth it: an `action` could create a network and **nothing could ever remove it**, because an action leaves no footprint the host can undo. The vocabulary is nine. @@ -106,7 +106,7 @@ not after. *Done 2026-08-31. Worth recording because the task was not the one written down.* -**The control plane special-cases nothing.** `provides`, `requires`, `contributes` and `grants` +**The controller special-cases nothing.** `provides`, `requires`, `contributes` and `grants` are name-agnostic — asking for a bucket needed no change to the mesh at all. What was missing was a provider, and the last step where something on the machine turns a delivered secret into a key that works. So "add an object-store provision" was never mesh work. @@ -200,7 +200,7 @@ losing something. *2026-08-31.* **The old system's brain is switched off; its services keep running.** -Not a migration and not a period of dual control. The old control plane — provisioning, the +Not a migration and not a period of dual control. The old controller — provisioning, the coordinator, the pipeline, the things that *decide* and *write* — is stopped. Every workload it was managing goes on running exactly as it is, because nothing is managing it. Then the new mesh takes ownership of them one at a time. @@ -217,7 +217,7 @@ stop having opinions. **Disabled, not merely stopped**, and this is the part that is easy to get wrong: those units are enabled, so stopping them lasts until the machine reboots. A reboot mid-conversion would bring the -old control plane back and it would resume regenerating managed files underneath the new one — +old controller back and it would resume regenerating managed files underneath the new one — which is the one situation where two systems really would be fighting over the same machine. **A service left running with nothing managing it is the safe state.** It has its data, its diff --git a/03-DESIGN/01-to-be/01-end-to-end-testing.md b/03-DESIGN/01-to-be/01-end-to-end-testing.md index 967c23a..2032147 100644 --- a/03-DESIGN/01-to-be/01-end-to-end-testing.md +++ b/03-DESIGN/01-to-be/01-end-to-end-testing.md @@ -40,13 +40,13 @@ not the first one built** ([ADR 0016](../../02-DECISIONS/0016-the-lab.md)). | | **Bootstrap scenario** | **Full scenario** | |---|---|---| -| Contains | virtual machines, the host binary, a pinned substrate bundle | a complete mesh: forge, coordinator, delivery, modules | +| Contains | virtual machines, the host binary, a pinned foundation bundle | a complete mesh: forge, coordinator, delivery, modules | | Verdict from | what the host reports about the state it reconciled | a pipeline result ending in verify | -| Exercises | the node host and the substrate | the control plane and everything above it | +| Exercises | the node host and the foundation | the controller and everything above it | | Exists to | **develop the mesh** | **test what runs on it** | The bootstrap scenario is a **strict subset**: same virtualisation, same networking, same -lifecycle — it simply stops before a control plane exists. Everything from *"Where this sits in +lifecycle — it simply stops before a controller exists. Everything from *"Where this sits in the way work happens"* onward describes the full scenario, and applies once there is a coordinator to describe. @@ -343,7 +343,7 @@ from the existing system has run against any of it yet. --- -## The substrate +## The foundation ### A node is a system container diff --git a/03-DESIGN/01-to-be/02-scenario-declaration.md b/03-DESIGN/01-to-be/02-scenario-declaration.md index ce3b7c5..29c8c5c 100644 --- a/03-DESIGN/01-to-be/02-scenario-declaration.md +++ b/03-DESIGN/01-to-be/02-scenario-declaration.md @@ -205,7 +205,7 @@ machines: place: all: [host] - anchor: [substrate] + anchor: [foundation] snapshot: raised ``` @@ -334,12 +334,12 @@ than a fork. # bootstrap — tiers 0 and 1 place: all: [host] - anchor: [substrate] + anchor: [foundation] -# full — adds a control plane, a forge, and a module under test +# full — adds a controller, a forge, and a module under test place: all: [host] - anchor: [substrate, control, forge] + anchor: [foundation, control, forge] module: a-web-service assert: - the service answers on its published name @@ -524,7 +524,7 @@ machines: place: all: [host] - anchor: [substrate] + anchor: [foundation] snapshot: raised ``` diff --git a/03-DESIGN/01-to-be/05-the-node-host.md b/03-DESIGN/01-to-be/05-the-node-host.md index c3c7f1f..42c7654 100644 --- a/03-DESIGN/01-to-be/05-the-node-host.md +++ b/03-DESIGN/01-to-be/05-the-node-host.md @@ -47,10 +47,10 @@ mesh database, and it has no listening surface. |---|---| | `apply` | reconciling declared state on this machine | | `store` | local state, authoritative while disconnected | -| `link` | the single outbound connection to the control plane | +| `link` | the single outbound connection to the controller | | `profile` | what this machine can be asked to do | | `inventory` | what this machine is and has | -| `substrate.lock` | the pinned tier-1 descriptor, appliable with no mesh present | +| `foundation.lock` | the pinned tier-1 descriptor, appliable with no mesh present | ### apply @@ -74,7 +74,7 @@ the machine in whatever state it reached, and nothing must claim otherwise. ### store -Local, and **authoritative while disconnected**. Not a cache of the control plane — the record +Local, and **authoritative while disconnected**. Not a cache of the controller — the record of what this node has applied and what it currently holds. This is structural rather than convenient: if disconnection is an ordinary situation rather @@ -84,7 +84,7 @@ not come back and ask what it is. ### link -The node's one connection to the control plane, and its security boundary +The node's one connection to the controller, and its security boundary ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). It is the broker connection that already exists @@ -137,11 +137,11 @@ One behaviour, two sources | Situation | Source | |---|---| -| no mesh reachable | `substrate.lock` — the pinned bundle the host carries | -| mesh reachable | the control plane, over the link | +| no mesh reachable | `foundation.lock` — the pinned bundle the host carries | +| mesh reachable | the controller, over the link | **The first node is not a different kind of node.** It is a node whose mesh is not up yet. It -applies the bundle it carries, the control plane comes up on top of it, and from that moment it +applies the bundle it carries, the controller comes up on top of it, and from that moment it takes declarations like every other node. Its specialness is temporary and self-erasing. **A joining node does the minimum to be reachable and nothing else** — an identity, an address, @@ -155,7 +155,7 @@ Settled by [ADR 0005](../../02-DECISIONS/0005-the-node-host.md). **JSON**, because the host has no dependencies to spend and the standard library carries no YAML. **An ordered list of typed resources**, each with a stable identity — the order is stated rather than derived, because deriving it would be the host deciding the thing most likely to -differ from what the control plane intended. +differ from what the controller intended. **Unknown is refused, never skipped.** An unknown version, type or field refuses the whole declaration. A host that skipped what it did not understand would apply most of it and report @@ -173,16 +173,16 @@ without one, applying the bundle it carries, has nothing to check against. Staged so each stage is verifiable in the lab before the next exists. **1 — profile and inventory.** The host runs on a machine, detects what it can do, and reports -what it is. No control plane, no declarations, no network. Verifiable immediately: the lab's +what it is. No controller, no declarations, no network. Verifiable immediately: the lab's `place:` gains its first implementation, and a raised scenario finally contains something. -**2 — apply, from the bundle.** The host applies `substrate.lock` with no mesh present. This is +**2 — apply, from the bundle.** The host applies `foundation.lock` with no mesh present. This is the first node's path, and it is the claim the skeleton's Move 1 rests on and has never proved: -that one host can raise the substrate alone. +that one host can raise the foundation alone. -Raising the substrate uses **four** shapes — `package`, `container`, `service`, `action` — +Raising the foundation uses **four** shapes — `package`, `container`, `service`, `action` — counted from the bundle that exists rather than reasoned about. `file` and `directory` are listed -below because they are the cheapest to be sure of and a substrate that needed them would find them +below because they are the cheapest to be sure of and a foundation that needed them would find them ready; the current bundle simply does not. **All of them are built:** | | | | @@ -225,7 +225,7 @@ container runtime. All three were instead verified against a real machine — a labelled, replaced when its declaration changed, exec'd into and removed; an action that exits zero and satisfies nothing failing the apply. That is lab-installation work ([`04-lab-installation.md`](04-lab-installation.md)) rather than a constraint on the design, but -until it is done the substrate bootstrap has no end-to-end test. +until it is done the foundation bootstrap has no end-to-end test. **3 — link and store.** The node connects, receives declarations, and holds what it applied. @@ -313,7 +313,7 @@ reported to be distinguishable from one that reported an empty list.* ## Open -- **Whether one host can raise the substrate alone.** Move 1 assumes it. Stage 2 tests it, and +- **Whether one host can raise the foundation alone.** Move 1 assumes it. Stage 2 tests it, and if it is false the tier boundary moves. - **What the host carries versus what it finds.** It manages `wg`, `nft`, `pacman`, `docker`; it does not contain them, and how it obtains one it lacks is undecided — @@ -329,15 +329,15 @@ reported to be distinguishable from one that reported an empty list.* ## What was added to the vocabulary, and why each cost was worth paying -*Written 2026-08-30. Every addition widens what a compromised control plane can express, so the +*Written 2026-08-30. Every addition widens what a compromised controller can express, so the count is asserted by a test and a change to it is a decision rather than a convenience.* -Four shapes raise the substrate. Five more exist because most of what a person installs is not a +Four shapes raise the foundation. Five more exist because most of what a person installs is not a service: | | why | |---|---| -| **file**, **directory** | the substrate needs neither, and almost everything else does | +| **file**, **directory** | the foundation needs neither, and almost everything else does | | **user** | a shell, a terminal, a chat client, a desktop are a package plus configuration **in somebody's home**. A mesh with no user owns `/etc` and nothing anybody looks at | | **archive** | a theme is hundreds of files. Inlining them makes every declaration enormous and rewrites all of them when one changes | | **network** | a module of several containers has to let them reach each other by name, and doing it with an action would create something nothing could ever remove ([ADR 0029](../../02-DECISIONS/0029-a-network-is-a-shape-because-an-action-cannot-be-undone.md)) | diff --git a/03-DESIGN/01-to-be/06-the-control-plane.md b/03-DESIGN/01-to-be/06-the-controller.md similarity index 88% rename from 03-DESIGN/01-to-be/06-the-control-plane.md rename to 03-DESIGN/01-to-be/06-the-controller.md index 654acc7..45ab924 100644 --- a/03-DESIGN/01-to-be/06-the-control-plane.md +++ b/03-DESIGN/01-to-be/06-the-controller.md @@ -12,7 +12,7 @@ decisions: - 02-DECISIONS/0019-how-this-repository-works.md --- -# The control plane +# The controller Tier 2. The term appears seventy-nine times across this repository and was defined nowhere, which is `how-we-build` §5 failing on this repository's own vocabulary. @@ -22,7 +22,7 @@ This document defines it. It does **not** design the contexts inside it; those a ## The definition -> **The control plane is everything that needs to know about more than one node.** +> **The controller is everything that needs to know about more than one node.** That is the whole test, and it is not arbitrary — it follows from [ADR 0005](../../02-DECISIONS/0005-the-node-host.md). The host applies and @@ -32,11 +32,11 @@ exactly there: | Question | Whose | |---|---| | write this file, with this content, with this mode | the **host** — one machine | -| which nodes should run the store | the **control plane** — needs every node | +| which nodes should run the store | the **controller** — needs every node | | is this unit running | the **host** — one machine | -| which peers belong in this node's overlay | the **control plane** — needs every node | -| what does this machine have installed | the **host** reports; the control plane **records** | -| has this node been unreachable for a week | the **control plane** — nobody else is watching | +| which peers belong in this node's overlay | the **controller** — needs every node | +| what does this machine have installed | the **host** reports; the controller **records** | +| has this node been unreachable for a week | the **controller** — nobody else is watching | A useful consequence: **anything a single machine could answer alone is not the control plane's.** If it needs no second node, putting it here is a mistake, and the tier rule will not @@ -67,7 +67,7 @@ something infrastructure*. `ai` is folded into `config`: a provider licence is a **`record` is an open question rather than an eighth entry.** Contexts integrate through it ([ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)), which makes it load-bearing, and [research 006](../../01-RESEARCH/006-mesh-from-scratch/skeleton.md) leaves *where it lives* -unresolved — putting it in the substrate risks recreating the circularity the tier design just +unresolved — putting it in the foundation risks recreating the circularity the tier design just removed. Listing it here would settle by naming what has not been settled by arguing. **One of the seven is built.** `inventory` owns a database of that name and holds the node records; @@ -89,7 +89,7 @@ in front of them. The question this answers: **can a node write to the registry database?** No — and not "only through one node", which is the weaker arrangement it might be mistaken for. -> **No node holds a credential to any control-plane store, for writing or for reading.** +> **No node holds a credential to any controller store, for writing or for reading.** That is not a new rule here; it is four already taken, and it is worth seeing them together because each one alone reads like a detail: @@ -118,11 +118,11 @@ Reads work the same way in reverse — a node is *told*, in declarations. It nev ### Who actually consumes, and who writes -**The control plane is the consumer. There is one of it, and the context that owns the data does +**The controller is the consumer. There is one of it, and the context that owns the data does the write.** ``` -node ──► broker ──► the control plane, consuming +node ──► broker ──► the controller, consuming ├─ a node reported what it applied ─► inventory writes the registry ├─ a node reported health ─► observability writes its own store └─ a grant was requested ─► provisioning writes its own store @@ -139,9 +139,9 @@ the store it exclusively owns receiving half of what it expects* — which has happened, between a module's daemon and its capability server. With one consumer that class of fault cannot arise. -**And the broker is the buffer while the control plane is down.** Nodes go on publishing; -messages queue; the control plane drains them when it returns. That is what makes -[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)'s single control plane +**And the broker is the buffer while the controller is down.** Nodes go on publishing; +messages queue; the controller drains them when it returns. That is what makes +[ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)'s single controller tolerable — an outage delays the mesh's *knowledge* rather than losing it. **With one consequence that must be bounded before it is discovered:** a queue with no limit @@ -177,7 +177,7 @@ volume genuinely argues against a relational store. node except through the host. - **Not a surface.** Tier 3 is how people and agents reach it. It has one interface; the surfaces are what speak to that interface. -- **Not the substrate.** It *runs on* tier 1 — PostgreSQL, LavinMQ, an OCI registry +- **Not the foundation.** It *runs on* tier 1 — PostgreSQL, LavinMQ, an OCI registry ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)) — and cannot start without them, which is what makes them a lower tier. - **Not privileged on a node.** It has no more access to a machine than the declaration @@ -185,19 +185,19 @@ volume genuinely argues against a relational store. ## It is also a consumer -The property that makes tier 2 unlike the others: **the control plane has requirements of its +The property that makes tier 2 unlike the others: **the controller has requirements of its own.** It needs a PostgreSQL database, an AMQP virtual host, and a bucket — the same things any module needs, granted the same way. -That is the circularity the tiers exist to resolve rather than hide: the control plane cannot +That is the circularity the tiers exist to resolve rather than hide: the controller cannot provision its own database, because it is not running yet. So its **store** is raised from the -bundle the host carries, before there is a control plane to ask +bundle the host carries, before there is a controller to ask ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [research 011](../../01-RESEARCH/011-the-module-graph/worked-provider.md)). Its virtual host and its bucket are **not** in the bundle — by the time they are wanted there is -a control plane to grant them. Whether the bus must come first is -[open](07-the-substrate.md#open), and it turns on whether these contexts talk to each other over +a controller to grant them. Whether the bus must come first is +[open](07-the-foundation.md#open), and it turns on whether these contexts talk to each other over it. ## Where it runs @@ -209,10 +209,10 @@ hosts, assigned to nodes by the same mechanism as everything else. ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)). The node is assigned, never elected — no promotion, no quorum, no split brain. -That is sound rather than merely cheap, because the design already tolerates the control plane +That is sound rather than merely cheap, because the design already tolerates the controller being absent by construction: a node reconciles from **its own** store ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)) and -never needed to ask anybody to hold the state it was last given. So the control plane being down +never needed to ask anybody to hold the state it was last given. So the controller being down is not a new failure mode — it is [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)'s ordinary disconnected situation, happening to every node at once. **What is lost is change, not operation.** @@ -226,13 +226,13 @@ every public name. - ~~**The contexts themselves.**~~ **Decided** — seven, by [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md). What remains open is narrower and named there: **where the record lives**, which research 006 - leaves unresolved because the substrate is the one place it must not go. + leaves unresolved because the foundation is the one place it must not go. - **How far it may be split.** One deployable today. Splitting a context out costs the single interface a surface depends on ([research 011](../../01-RESEARCH/011-the-module-graph/00-overview.md)). - ~~**How many run, and what a node does without one.**~~ **Resolved** by [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md). What remains is - measurement: nothing reports how long the control plane has been unreachable, or how close a + measurement: nothing reports how long the controller has been unreachable, or how close a certificate is to expiry — both needed for restore-not-failover to be a plan rather than a hope. - **What the interface is.** One interface is stated; its shape, and whether it is request, diff --git a/03-DESIGN/01-to-be/07-the-substrate.md b/03-DESIGN/01-to-be/07-the-foundation.md similarity index 73% rename from 03-DESIGN/01-to-be/07-the-substrate.md rename to 03-DESIGN/01-to-be/07-the-foundation.md index 6be2209..f84d73d 100644 --- a/03-DESIGN/01-to-be/07-the-substrate.md +++ b/03-DESIGN/01-to-be/07-the-foundation.md @@ -2,7 +2,7 @@ layer: to-be status: in-progress code: - - mesh-host examples/substrate-first-node.lock + - mesh-host examples/foundation-first-node.lock - mesh-host internal/apply - mesh-lab test/integration/mesh.test.ts (a bare machine becomes a mesh) updated: 2026-08-31 @@ -15,16 +15,16 @@ decisions: - 02-DECISIONS/0019-how-this-repository-works.md --- -# The substrate +# The foundation -Tier 1. Defined the same way [the control plane](06-the-control-plane.md) is, because the same +Tier 1. Defined the same way [the controller](06-the-controller.md) is, because the same gap applied: the word was load-bearing and unpinned. ## The definition -> **The substrate is what the control plane consumes and cannot grant itself.** +> **The foundation is what the controller consumes and cannot grant itself.** -Every module that needs a database asks the control plane's provisioning for one. The control +Every module that needs a database asks the controller's provisioning for one. The control plane needs a database too — and it cannot ask itself, because it is not running yet. That circularity is not an awkwardness to work around; it *is* the definition. Anything on the wrong side of it must be raised some other way, and the other way is the bundle the host carries @@ -32,19 +32,19 @@ side of it must be raised some other way, and the other way is the bundle the ho The test, applied: -| | control plane needs it | can it grant itself one? | | +| | controller needs it | can it grant itself one? | | |---|---|---|---| -| a relational store — **PostgreSQL** | its own state lives there | no — provisioning needs the store | **substrate** | -| a message bus — **LavinMQ** | it reaches nodes over it ([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md)) | no — it cannot grant itself a virtual host | **substrate** | -| ~~an object store~~ | ~~artifacts and blobs it delivers~~ | — | **not substrate** — [ADR 0028](../../02-DECISIONS/0028-the-substrate-supplies-the-control-plane-and-nothing-else.md) | -| ~~an image registry~~ | ~~images it delivers to nodes~~ | — | **not substrate** — needed to operate, not to start ([ADR 0033](../../02-DECISIONS/0033-the-substrate-is-a-store-and-a-broker.md)) | -| ~~an identity provider~~ | ~~only if it delegates authentication~~ | — | **not substrate** — it delegates to nothing ([ADR 0031](../../02-DECISIONS/0031-the-control-plane-authenticates-nobody.md)) | -| ingress — **Traefik** | not to start; only to be reached by name | — it grants itself one afterwards | **not substrate** ([ADR 0007](../../02-DECISIONS/0007-connectivity.md)) | -| anything else the mesh hosts | no | — | not substrate | +| a relational store — **PostgreSQL** | its own state lives there | no — provisioning needs the store | **foundation** | +| a message bus — **LavinMQ** | it reaches nodes over it ([ADR 0002](../../02-DECISIONS/0002-nodes-communicate-over-a-broker.md)) | no — it cannot grant itself a virtual host | **foundation** | +| ~~an object store~~ | ~~artifacts and blobs it delivers~~ | — | **not foundation** — [ADR 0028](../../02-DECISIONS/0028-the-substrate-supplies-the-control-plane-and-nothing-else.md) | +| ~~an image registry~~ | ~~images it delivers to nodes~~ | — | **not foundation** — needed to operate, not to start ([ADR 0033](../../02-DECISIONS/0033-the-substrate-is-a-store-and-a-broker.md)) | +| ~~an identity provider~~ | ~~only if it delegates authentication~~ | — | **not foundation** — it delegates to nothing ([ADR 0031](../../02-DECISIONS/0031-the-control-plane-authenticates-nobody.md)) | +| ingress — **Traefik** | not to start; only to be reached by name | — it grants itself one afterwards | **not foundation** ([ADR 0007](../../02-DECISIONS/0007-connectivity.md)) | +| anything else the mesh hosts | no | — | not foundation | *The object-store row was wrong, and how it was wrong is worth keeping.* It answered *can it grant itself one* — no, it cannot grant itself a bucket — while assuming the first column. **The -control plane does not need an object store**: it has no S3 client and never has, and artifacts +controller does not need an object store**: it has no S3 client and never has, and artifacts reach nodes as content-addressed blobs in the registry. The row was inherited from the system being replaced, where an object store distributed module tarballs, and was never re-tested against the definition above it. *Both columns must be answered, and the second is true of almost any service.* @@ -60,76 +60,76 @@ does not record that the choice was ever made. The dependency is on the **protocol**, not the product: AMQP for the bus, S3 for the object store, the OCI protocol for the registry. That is what keeps the naming safe rather than a -commitment that cannot be revisited — replacing one is a substrate migration, not a redesign. +commitment that cannot be revisited — replacing one is a foundation migration, not a redesign. The store is the exception, and the exception matters: the provisioning model uses databases, roles and schemas as PostgreSQL means them, so it is the one member that is not a swap. ## What that resolves **Four or five?** [Research 006](../../01-RESEARCH/006-mesh-from-scratch/00-overview.md) asks -whether the identity provider is a substrate service, and the test answers it *conditionally* — +whether the identity provider is a foundation service, and the test answers it *conditionally* — which is the honest answer rather than a number. -- If the control plane **delegates** authentication, it cannot serve anybody before the provider - exists, and it cannot grant itself a client. **Substrate.** +- If the controller **delegates** authentication, it cannot serve anybody before the provider + exists, and it cannot grant itself a client. **Foundation.** - If it **authenticates natively**, the provider is an ordinary hosted service like any other. - **Not substrate.** + **Not foundation.** So the count follows from a design decision that has not been taken, and the record should say that rather than assert four. **Why not "important infrastructure".** An identity provider, a mail server and an analytics -service are all infrastructure by any ordinary reading, and none of them are substrate — the -control plane starts and runs without them. *Important* is not the test; *the control plane +service are all infrastructure by any ordinary reading, and none of them are foundation — the +controller starts and runs without them. *Important* is not the test; *the controller cannot obtain it* is. -## What the substrate is not +## What the foundation is not -- **Not tier 0.** The host raises the substrate; it is not part of it. The host carries the - declaration that brings the substrate up, and depends on nothing. -- **Not the control plane.** These are services with no knowledge of the mesh. A store does not +- **Not tier 0.** The host raises the foundation; it is not part of it. The host carries the + declaration that brings the foundation up, and depends on nothing. +- **Not the controller.** These are services with no knowledge of the mesh. A store does not know what a node is. - **Not a place for logic.** The skeleton is explicit: tier 1 is *declarations only, no logic of - its own.* A substrate service is an upstream image, pinned, with configuration. -- **Not privileged.** The substrate is provisioned *from* by the control plane and grants + its own.* A foundation service is an upstream image, pinned, with configuration. +- **Not privileged.** The foundation is provisioned *from* by the controller and grants nothing on its own initiative. - **Not the mesh's supply of anything** ([ADR 0028](../../02-DECISIONS/0028-the-substrate-supplies-the-control-plane-and-nothing-else.md)). - A substrate service and a module of the same product are **different instances**. The mesh's own + A foundation service and a module of the same product are **different instances**. The mesh's own PostgreSQL and a PostgreSQL a workload was given are two servers, and a node hosting both runs two containers — expected, not duplication to be tidied away. - The substrate is raised from the bundle before any mesh exists, so **it is not in the module + The foundation is raised from the bundle before any mesh exists, so **it is not in the module graph**: a workload depending on it would depend on something the graph cannot see, cannot rotate - a credential for, and cannot move. It would also put workload data in the store the control plane + a credential for, and cannot move. It would also put workload data in the store the controller keeps its own state in, where a workload that fills a disk takes down the one thing needed to fix it. ## The pinned bundle -`substrate.lock` holds **what must exist before the control plane runs** — which is a smaller -set than the substrate, and the difference is easy to miss. It is the only place in the mesh +`foundation.lock` holds **what must exist before the controller runs** — which is a smaller +set than the foundation, and the difference is easy to miss. It is the only place in the mesh where versions are pinned by hand rather than resolved. -Being substrate and being in the bundle are two different questions: +Being foundation and being in the bundle are two different questions: -| | is it substrate? | must it precede the control plane? | +| | is it foundation? | must it precede the controller? | |---|---|---| -| PostgreSQL | yes — the control plane's own state lives in it | **yes** — there is nowhere to put that state otherwise | -| LavinMQ | yes — it cannot grant itself a virtual host | **yes** — the control plane reaches a node only over the link, and the link is the broker ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)) | +| PostgreSQL | yes — the controller's own state lives in it | **yes** — there is nowhere to put that state otherwise | +| LavinMQ | yes — it cannot grant itself a virtual host | **yes** — the controller reaches a node only over the link, and the link is the broker ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)) | | the OCI registry | yes — it cannot grant itself a repository | no — the first node fetches upstream ([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)) | -The registry is **substrate by role and ordinary by delivery**: by the time it is wanted there is -a control plane, and it provisions it the way it provisions anything. That keeps the bundle small +The registry is **foundation by role and ordinary by delivery**: by the time it is wanted there is +a controller, and it provisions it the way it provisions anything. That keeps the bundle small enough for the review [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) requires — one -substrate image until +foundation image until [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) established that the -broker has to precede the control plane, and two since. +broker has to precede the controller, and two since. *Corrected 2026-08-31, from counting what the bundle holds rather than reasoning about it.* **It -carries three images, not two** — PostgreSQL, LavinMQ, and the control plane itself, which the -sentence above had overlooked by counting only substrate services. The control plane is what the -substrate exists to start, and it is in the bundle for the same reason they are: there is nothing +carries three images, not two** — PostgreSQL, LavinMQ, and the controller itself, which the +sentence above had overlooked by counting only foundation services. The controller is what the +foundation exists to start, and it is in the bundle for the same reason they are: there is nothing to fetch it with yet. It also carries seven actions, a package and a service. **Why pinned:** the bundle is applied when no mesh exists, so nothing can resolve a version, ask @@ -154,13 +154,13 @@ The order, from [research 011](../../01-RESEARCH/011-the-module-graph/worked-pro 4 LavinMQ runs pulled by digest, from the bundle 5 a virtual host, a credential, and actions, run locally a self-signed certificate -6 the control plane starts and only now is there a mesh +6 the controller starts and only now is there a mesh 7 the registry, and everything else the ordinary path are provisioned ``` **Steps 4 and 5 are why the bundle is not one image** -([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)). The control plane cannot +([ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md)). The controller cannot provision the broker, because provisioning means telling a host, and telling a host happens over the broker. The first node does not escape this by being local: it enrols the ordinary way, by dialling the broker at the address in its token. @@ -172,15 +172,15 @@ database* names a thing that will not exist database is a boundary a cross-context join cannot casually cross where a separate schema is not. Only PostgreSQL is raised from the bundle, for the reason in *The pinned bundle* above — the -rest of the substrate is wanted only once there is a control plane to provision it. +rest of the foundation is wanted only once there is a controller to provision it. -**Step 0 is easy to leave out and it is where several things meet.** A substrate service is a +**Step 0 is easy to leave out and it is where several things meet.** A foundation service is a container, so a container runtime must be working before anything else happens — and a runtime is a *package*, not a container. **Which runtime is detected, not chosen** ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)): a machine that -already has one keeps it. On a machine with none, the control plane names the package, because +already has one keeps it. On a machine with none, the controller names the package, because what it is called differs per system. It is: - what the host's capability detection already reports, and the first use of that report by @@ -191,7 +191,7 @@ what it is called differs per system. It is: [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md). So the bootstrap uses four shapes: **package**, **container**, **service** and **action** — -*counted from `substrate-first-node.lock`, which is the only bundle there is*. It had said six, +*counted from `foundation-first-node.lock`, which is the only bundle there is*. It had said six, adding `file` and `directory`, which this bootstrap never asks for. All four are built, as are the host's other five @@ -202,7 +202,7 @@ on the host any longer — which is the claim that mattered, and it was true eit the bootstrap rather than a service consumers use later. They are **actions** the bundle declares and the host runs ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)) — so the -host's vocabulary grows by one shape rather than by one resource type per substrate service. +host's vocabulary grows by one shape rather than by one resource type per foundation service. ## Open @@ -210,12 +210,12 @@ host's vocabulary grows by one shape rather than by one resource type per substr [ADR 0031](../../02-DECISIONS/0031-the-control-plane-authenticates-nobody.md): the control plane delegates authentication to nothing, so identity is an ordinary module. With the object store gone ([ADR 0028](../../02-DECISIONS/0028-the-substrate-supplies-the-control-plane-and-nothing-else.md)) - the substrate is three, and no member is conditional. -- ~~**Whether the bus must precede the control plane.**~~ **Resolved** by + the foundation is three, and no member is conditional. +- ~~**Whether the bus must precede the controller.**~~ **Resolved** by [ADR 0006](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) — it must, and the question as - posed here could not have answered it. This asked whether the control plane's contexts talk to + posed here could not have answered it. This asked whether the controller's contexts talk to each other over the bus; they do not, being one process, which under this framing would have - kept LavinMQ out of the bundle. What decides it is how the control plane reaches a *node*, which + kept LavinMQ out of the bundle. What decides it is how the controller reaches a *node*, which is only ever over the link. - **What issues the broker's certificate at bootstrap.** New, and created by the row above. A token pins the fingerprint a host must expect before it sends anything @@ -223,7 +223,7 @@ host's vocabulary grows by one shape rather than by one resource type per substr moment when there is no mesh to issue one and no public name to obtain one for. Self-signed and pinned is the shape that fits; how it is later replaced by the certificates in [`08-connectivity.md`](08-connectivity.md) is not decided. -- **How a context added later gets its database.** By then there is a control plane — but one +- **How a context added later gets its database.** By then there is a controller — but one holding a credential that can create databases holds more than what it exclusively owns ([ADR 0008](../../02-DECISIONS/0008-a-context-owns-its-store.md)). - ~~**Whether the host can do step 2.**~~ **Resolved** by @@ -234,8 +234,8 @@ host's vocabulary grows by one shape rather than by one resource type per substr the module that provides one. - **Whether one host can raise all three.** The claim under stage 2 of [the node host](05-the-node-host.md), never proved. If it is false, the tier boundary moves. -- **How the substrate is updated once a mesh exists.** Pinned by hand at bootstrap; afterwards - the control plane could deliver it like anything else, and nothing says whether it does. +- **How the foundation is updated once a mesh exists.** Pinned by hand at bootstrap; afterwards + the controller could deliver it like anything else, and nothing says whether it does. ## Raised, and observed @@ -243,7 +243,7 @@ host's vocabulary grows by one shape rather than by one resource type per substr **It works, and what that means precisely:** a machine with a container runtime and nothing else applied the bundle its host carries and ended with a store, a database per context, those -contexts' schemas, a broker holding a certificate it generated itself, and the control plane +contexts' schemas, a broker holding a certificate it generated itself, and the controller serving on top of them. Eleven resources, one command, no mesh to ask anything of. **Then it joined itself.** The same machine took a token, checked the broker against the @@ -259,7 +259,7 @@ of a database and pushed to over the broker. What arrived and what did not is th |---|---| | the password, in plain text | **on the machine only**, one file, mode 0600 | | in the declaration that crossed the broker | absent | -| in the control plane's database | absent | +| in the controller's database | absent | | in what the node reported back | absent | **One fault, and it was in the joining.** The token did not say what the mesh calls the machine, diff --git a/03-DESIGN/01-to-be/08-connectivity.md b/03-DESIGN/01-to-be/08-connectivity.md index 5e2ae8e..cc04256 100644 --- a/03-DESIGN/01-to-be/08-connectivity.md +++ b/03-DESIGN/01-to-be/08-connectivity.md @@ -21,25 +21,25 @@ decisions: # Connectivity -One of [the control plane's](06-the-control-plane.md) ten contexts, and the one with the most +One of [the controller's](06-the-controller.md) ten contexts, and the one with the most moving parts: **overlay, resolution, exposure, filtering, certificates.** It is written as a whole because the five are one design. They share inputs, they must agree, and every one of them today is computed in a different place by a different module from a different copy of the same facts. -## Why it is control-plane work +## Why it is controller work -Apply [the test](06-the-control-plane.md) — *everything that needs to know about more than one +Apply [the test](06-the-controller.md) — *everything that needs to know about more than one node* — to each responsibility: | | needs to know | whose | |---|---|---| -| **overlay** — who peers with whom, at what address | **every node**, and which of them can be dialled | control plane | -| **resolution** — which name is which node | **every node** | control plane | -| **exposure** — which public name reaches which container | **which node is publicly reachable** ([ADR 0007](../../02-DECISIONS/0007-connectivity.md)) | control plane | -| **filtering** — which port is open, to whom | what is assigned here, and the overlay's shape | control plane decides, host applies | -| **certificates** — who may present which name | which name belongs to which node | control plane | +| **overlay** — who peers with whom, at what address | **every node**, and which of them can be dialled | controller | +| **resolution** — which name is which node | **every node** | controller | +| **exposure** — which public name reaches which container | **which node is publicly reachable** ([ADR 0007](../../02-DECISIONS/0007-connectivity.md)) | controller | +| **filtering** — which port is open, to whom | what is assigned here, and the overlay's shape | controller decides, host applies | +| **certificates** — who may present which name | which name belongs to which node | controller | **Not one of the five can be answered by a machine on its own.** That is the whole reason this is a context rather than a set of node-local modules — and it is exactly what the current @@ -57,7 +57,7 @@ exist; WireGuard, the resolver and the proxy are all *a container or a package, It is also what removes the last two upward dependencies. [Research 006](../../01-RESEARCH/006-mesh-from-scratch/host-size.md) counted exactly two modules -opening a direct connection to the control plane's database — `wireguard` and `traefik` — and +opening a direct connection to the controller's database — `wireguard` and `traefik` — and they are the reason every node permanently holds a credential to it ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). Both are connectivity modules. **Closing this context closes that set.** @@ -74,7 +74,7 @@ wanted the exception. **What made it look unavoidable:** a peer list cannot be written in a manifest. It is derived from every other machine, so it differs on each one and changes when any of them changes. So the -manifest says its resources are **computed** — it names something in the control plane that works +manifest says its resources are **computed** — it names something in the controller that works them out per node — and it is a module in every other respect: assigned, resolved, configured by settings, and absent from a machine nobody gave it to. @@ -108,7 +108,7 @@ claim, and the collision is refused by name. **And the proxy's half, which was the other module reaching into the database.** A web application requiring a reverse proxy has to say *which name, which port*, and there was nowhere to put it — `requires` says a thing must exist and never said what to do with it. A module now contributes to -a requirement, the control plane collects every contribution on a node, and the provider is given +a requirement, the controller collects every contribution on a node, and the provider is given them as a file at a path it named. It reloads when that file changes, by the same `restart-on` the private network needed when a peer list changed under a running interface. @@ -177,7 +177,7 @@ which of those it may dial, and which must dial it. **Keys.** Each node generates its own keypair. **The private key never leaves the machine**; the public key is published to the mesh. This is already true and it is already right — it is [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)'s *a node holds its own -identity* applied to the overlay, and it means the control plane computes a graph it cannot +identity* applied to the overlay, and it means the controller computes a graph it cannot itself impersonate. **Shape: a hub, with direct peering between co-located nodes.** @@ -217,7 +217,7 @@ files were right, the services were up, and every node reported success. document's own warning, arriving in its implementation: *a more specific route to a dead endpoint blackholes; it does not fall back to the general one.* - **The container runtime closes the door the overlay needs.** Docker sets the FORWARD policy to - DROP, so a hub with `ip_forward` enabled still carries nothing between its spokes. The substrate + DROP, so a hub with `ip_forward` enabled still carries nothing between its spokes. The foundation at tier 1 silently breaks the network at tier 2, and nothing in either tier's state says so. The hub inserts its own rule above those chains and removes it on the way down. @@ -287,7 +287,7 @@ node. What routes it once it arrives is a proxy's, and stays separate. **The mesh writes the data and runs no daemon.** One wildcard per machine, from the same set that writes the hosts file. A resolver is third-party software and runs *on* the mesh rather than being *of* it: the mesh has no business shipping one, choosing which one, or knowing its configuration -language. Swapping dnsmasq for unbound changes that module and nothing in the control plane. +language. Swapping dnsmasq for unbound changes that module and nothing in the controller. **Two roles, two claims, because they are different things.** systemd-resolved cannot answer a wildcard at all — it routes the mesh's suffix to something that can. Treating serving and asking @@ -377,7 +377,7 @@ vocabulary — the mirror of a database grant, where the consumer supplies a tar name rather than supplying nothing and receiving credentials. **A workload on an unreachable node is proxied by a reachable one, across the overlay.** Which is -the case is a mesh-level fact, which is the fourth reason exposure is control-plane work. +the case is a mesh-level fact, which is the fourth reason exposure is controller work. ### What was built @@ -482,7 +482,7 @@ something: | | why not | |---|---| -| decide what the machine **forwards** | the container runtime writes its own forwarding rules and a second policy is consulted alongside them, so a drop here stops every container on the node — the control plane included. Nothing in a manifest says what a machine routes, so there is nothing to derive it from either | +| decide what the machine **forwards** | the container runtime writes its own forwarding rules and a second policy is consulted alongside them, so a drop here stops every container on the node — the controller included. Nothing in a manifest says what a machine routes, so there is nothing to derive it from either | | **flush the ruleset** when loading | that empties every table on the machine, the runtime's among them. Only the mesh's own table is replaced, and it is declared empty first so the replacement works on a machine loading one for the first time | | carry a **command to load itself** | the link may not carry an action ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)). A service is declared to reflect the file instead, so replacing it restarts what loads it — the shape that rule leaves, used here for the first time for its real purpose | @@ -549,7 +549,7 @@ defaults to the public authority's *production* endpoint. Two consequences, and worse than the lab problem that found it — every certificate experiment on a real node consumes production issuance quota, and a retry loop can exhaust it for a week. -**The mesh CA is not a bootstrap concern.** A joining node verifies the control plane against the +**The mesh CA is not a bootstrap concern.** A joining node verifies the controller against the fingerprint in its token ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)), so nothing needs the CA before membership. It certifies internal names afterwards, and that is all it does. diff --git a/03-DESIGN/01-to-be/09-the-node-lifecycle.md b/03-DESIGN/01-to-be/09-the-node-lifecycle.md index ab703ac..56df007 100644 --- a/03-DESIGN/01-to-be/09-the-node-lifecycle.md +++ b/03-DESIGN/01-to-be/09-the-node-lifecycle.md @@ -142,12 +142,12 @@ nox-mesh-host enrol --token The token carries **four** things and is carried by a person ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)): the broker's -address, the fingerprint to expect, **the control plane's signing identity**, and the right to +address, the fingerprint to expect, **the controller's signing identity**, and the right to join once. **The fourth is the one this document listed three of.** A node connects to the broker and takes -instruction from the control plane behind it, and those are two different identities. Pinning only -the broker would make the control plane's authority *transitive* — a compromised broker could then +instruction from the controller behind it, and those are two different identities. Pinning only +the broker would make the controller's authority *transitive* — a compromised broker could then forge declarations, which, since the host applies whatever the link delivers, is the whole machine. So the transport is verified once at connect, and **each declaration is verified by its signature, every time**. @@ -158,10 +158,10 @@ What happens, in order: 2. it checks the broker's certificate against the pinned fingerprint — *before* sending anything; 3. it presents the one-time secret **and its own public key**, which the mesh records; 4. it reports its `profile` and `inventory` upward; -5. the control plane decides what this machine should be, and sends a declaration; +5. the controller decides what this machine should be, and sends a declaration; 6. the host applies it, reads back, and reports. -**Step 4 is the one that is easy to miss and is what makes step 5 possible.** The control plane +**Step 4 is the one that is easy to miss and is what makes step 5 possible.** The controller cannot decide what a machine should run without knowing what it *can* run — a graphical session, a container runtime, an architecture. The profile is not a diagnostic; it is the input. @@ -209,7 +209,7 @@ channel, not about network reachability.** What it forbids is a listening thing instructions and changes the machine. A node being reachable on the overlay — the whole purpose of the overlay — is untouched by it, and so is a person opening a shell on it. -The distinction is *who can tell this machine what to be*: only the control plane, only over the +The distinction is *who can tell this machine what to be*: only the controller, only over the link the node opened, only in declarations of known shape. --- @@ -219,18 +219,18 @@ link the node opened, only in declarations of known shape. The same path, with the mesh built in the middle of it. ``` -# 1 — raise the substrate and the control plane from the carried bundle +# 1 — raise the foundation and the controller from the carried bundle nox-mesh-host reconcile -# 2 — the control plane now exists, and issues the first token +# 2 — the controller now exists, and issues the first token mesh-control token issue # 3 — the machine joins the mesh it just raised nox-mesh-host enrol --token ``` -Step 1 is the bootstrap from [`07-the-substrate.md`](07-the-substrate.md): a container runtime, -then PostgreSQL, then the database, then the schema, then the control plane. It needs no identity +Step 1 is the bootstrap from [`07-the-foundation.md`](07-the-foundation.md): a container runtime, +then PostgreSQL, then the database, then the schema, then the controller. It needs no identity because nothing is being asked of anyone — the host is applying a declaration it already carries, to the machine it is already on. @@ -238,7 +238,7 @@ carries, to the machine it is already on. the bootstrap script never had. Its specialness lasted two commands. **And enrolment is exercised on node one.** The path every other node depends on is walked -immediately, against a control plane on the same machine, rather than being written and first +immediately, against a controller on the same machine, rather than being written and first used months later on node two. --- @@ -265,7 +265,7 @@ closes — an authoritative local store, reconcile on start, *last heard from* r alarm — is what an episodic host needs, at a shorter period. **It cannot be the first node**, and that is not a limitation to work around. Every step of -raising a substrate is a `package`, a `container` or an `action` against one, and a partial host +raising a foundation is a `package`, a `container` or an `action` against one, and a partial host refuses the first two. So `mesh-host-android bundle` returns a file that says so rather than an empty placeholder waiting to be filled in. @@ -350,7 +350,7 @@ rest. The rule that exists to stop the host lying about what it did also makes i ## Updating what the node holds -An ordinary declaration. Someone assigns a module; the control plane recomputes what that node +An ordinary declaration. Someone assigns a module; the controller recomputes what that node should be and sends it; the host applies the difference and removes what is no longer declared. **Removal is not symmetric, and the asymmetry is the design:** @@ -410,7 +410,7 @@ one binary that has always been the same binary. Two cases, and they are genuinely different. -**Graceful.** The control plane sends a final declaration that names nothing. The host removes +**Graceful.** The controller sends a final declaration that names nothing. The host removes what it owns by the table above, reports, and drops its identity. The machine keeps the host installed and is back to `hosted`. Nothing is left behind that anybody has to remember. @@ -638,7 +638,7 @@ lets a node verify a mesh it has never spoken to ([ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md)). A token emailed, committed, or dropped in shared storage has lost the only property that makes it worth carrying. -**On the first node it comes from the control plane that was raised two commands ago**, which is +**On the first node it comes from the controller that was raised two commands ago**, which is the same command against a mesh that is one machine old. --- diff --git a/03-DESIGN/01-to-be/10-delivery.md b/03-DESIGN/01-to-be/10-delivery.md index f1c97e6..0a33b69 100644 --- a/03-DESIGN/01-to-be/10-delivery.md +++ b/03-DESIGN/01-to-be/10-delivery.md @@ -78,7 +78,7 @@ whenever anybody writes something reusable, which is constantly. ## Delivery is a comparison, not a pipeline -The control plane holds two facts and builds the difference: +The controller holds two facts and builds the difference: ``` what source exists ─┐ @@ -94,7 +94,7 @@ That is the same shape the host uses on a machine, one layer up: | | reconciles | against | |---|---|---| -| the control plane | artifacts | source | +| the controller | artifacts | source | | the host | machine state | declarations | **There is no pipeline as a state machine.** No stage list something can be omitted from, and no @@ -178,9 +178,9 @@ Not aspirations — things without which the above does not work: same digest, a cascade would stop at the first module whose output did not move. Without them, one core-library commit redeploys the fleet with no behavioural change. - **How a module publishes its own types**, which differs per language. -- **How the control plane upgrades itself.** It declares its own new version and the host applies +- **How the controller upgrades itself.** It declares its own new version and the host applies it — but if the new one is broken, the thing that would fix it is the thing that is broken. The - host has a launcher for exactly this; the control plane has nothing. + host has a launcher for exactly this; the controller has nothing. ## What "behind" means, and what it used to mean diff --git a/03-DESIGN/01-to-be/11-a-board.md b/03-DESIGN/01-to-be/11-a-board.md index 8348e5c..fdb0dd2 100644 --- a/03-DESIGN/01-to-be/11-a-board.md +++ b/03-DESIGN/01-to-be/11-a-board.md @@ -41,9 +41,9 @@ it is enough to freeze it.** A board that reads the provisioning tables directly breaks when provisioning changes its tables, and the change then gets weighed against the board. **So a board reads through interfaces and holds nothing.** Everything on the mesh page above is -already answerable by asking the control plane — what nodes exist, what each resolves to, what it +already answerable by asking the controller — what nodes exist, what each resolves to, what it takes from elsewhere, which module came from which commit. A board that asks those questions is a -client. A board that queries `inventory` is a second control plane with a worse contract. +client. A board that queries `inventory` is a second controller with a worse contract. **It stores nothing of its own.** No cache that can disagree, no table of "what the mesh looked like last time". If a question is slow to answer, the answer belongs in the context that owns it, @@ -63,7 +63,7 @@ calls the security boundary, and a login there would guard a room whose door is building. This one faces everybody. **Which makes the identity provider the mesh's outermost gate.** The board is a presentation layer -over the control plane and the control plane's networked surfaces can change the mesh +over the controller and the controller's networked surfaces can change the mesh ([ADR 0035](../../02-DECISIONS/0035-one-implementation-several-surfaces.md)), so **whoever that provider admits can assign modules, from anywhere.** Said flatly because it is easy to arrive at one reasonable step at a time and then be surprised by. diff --git a/03-DESIGN/01-to-be/12-a-module-repository.md b/03-DESIGN/01-to-be/12-a-module-repository.md index 65c5bf4..6f7855e 100644 --- a/03-DESIGN/01-to-be/12-a-module-repository.md +++ b/03-DESIGN/01-to-be/12-a-module-repository.md @@ -85,10 +85,10 @@ the worst possible moment. ## The builder runs on a node -**Not in the control plane, and this is the same boundary as everywhere else.** Building needs a -container runtime and a working tree; what the control plane may send a machine is bounded by the +**Not in the controller, and this is the same boundary as everywhere else.** Building needs a +container runtime and a working tree; what the controller may send a machine is bounded by the declaration language ([ADR 0005](../../02-DECISIONS/0005-the-node-host.md)), and *run this build* -is not in it. The alternative — the control plane holding a container socket — would make it the +is not in it. The alternative — the controller holding a container socket — would make it the one component that can do anything on any machine, which is the property the whole design is arranged to avoid. @@ -96,7 +96,7 @@ So the builder is a program a machine runs, given work over the broker like anyt its own credential and nothing more. **A build is work, not state**, and that is why it does not travel as a declaration. Everything -else the control plane sends a node is *what you should be*, reconciled forever. A build happens +else the controller sends a node is *what you should be*, reconciled forever. A build happens once and is finished; as a declaration it would either rebuild on every reconcile or carry "and I already did this" — state about an event rather than about a machine. @@ -203,7 +203,7 @@ at something. A build that failed before it knew what it was building keeps the is what a person goes and looks at. Recording is idempotent on the correlation, because a result arrives twice — once as the answer to -whoever asked and once on the exchange, where the control plane is also listening. Two rows would +whoever asked and once on the exchange, where the controller is also listening. Two rows would show one build as two, and which is real is not answerable afterwards. That is what a builds view reads, and until it existed there was nothing to read: a result was @@ -381,13 +381,13 @@ have a route and one does not: | What | Why it cannot come through the loop | How it arrives | |---|---|---| -| The control plane | It is what installs modules. Nothing can install it before it runs. | **Carried inside the installer** and published once there is a registry ([ADR 0067](../../02-DECISIONS/0067-genesis-is-a-pivot.md)) | +| The controller | It is what installs modules. Nothing can install it before it runs. | **Carried inside the installer** and published once there is a registry ([ADR 0067](../../02-DECISIONS/0067-genesis-is-a-pivot.md)) | | The registry | It *is* where artifacts are delivered from. A store cannot be delivered through itself. | **Pulled from the public internet** — its image is an ordinary public one, never built ([`04-ISSUES/029`](../../04-ISSUES/029-the-artifact-store-cannot-be-delivered-by-the-artifact-store/00-report.md)) | | The builder | It is what builds. Nothing builds it before it runs. | Built at genesis by the init builder ([ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md)) | | The catalogue | It owns the module graph, and nothing can be resolved or installed without it. | Built at genesis by the init builder ([ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md)) | **How they arrive is settled and not yet built.** The installer carries an init builder, which -clones the source and builds the control plane, the catalogue and the builder before a mesh exists +clones the source and builds the controller, the catalogue and the builder before a mesh exists to install anything. Two things about that are open and named in [ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md): where the init builder clones from, given the forge normally runs on the mesh it would be rebuilding, and what it @@ -400,7 +400,7 @@ three. This section previously said the list was closed at three, which was writ catalogue had an owner and is corrected here rather than left to be reasoned from. **And the answer for all four is now one mechanism, not four special cases.** Genesis carries an -*init builder* and builds the core modules on the machine — control plane, catalogue and builder — +*init builder* and builds the core modules on the machine — controller, catalogue and builder — rather than carrying a finished image of any of them. So the question is no longer "how does this one get here first?" asked once per component; it is answered once, by the thing that is carried being a builder rather than a result. @@ -422,7 +422,7 @@ the mesh's registry assigned, exactly like everything the builder produces. A re from a running mesh which of its images were carried, and that is the point: carrying is how the first copy arrives, not what it permanently is. -*Checked by the thing already checked at genesis: after installing, the running control plane is +*Checked by the thing already checked at genesis: after installing, the running controller is pinned to a digest the mesh's own registry assigned, and not to the id of the image the installer carried. The same check applies to the builder and to the registry, and it is the same check — an image id where a registry digest belongs means the pivot did not finish.* diff --git a/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md b/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md index c61b400..a485adf 100644 --- a/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md +++ b/03-DESIGN/01-to-be/13-credentials-and-their-rotation.md @@ -70,7 +70,7 @@ The mesh generated the password, sealed it to the machine that must accept it, a plaintext** — so it cannot tell a database to start accepting it. Something on that machine reads what the host wrote and makes it true. -That something is part of the module, not part of the control plane. **The control plane decides +That something is part of the module, not part of the controller. **The controller decides and never touches a machine; a provisioner runs on the machine and touches it.** Two files, because the mesh could not compose a document containing a value it does not have: diff --git a/03-DESIGN/01-to-be/14-model-access.md b/03-DESIGN/01-to-be/14-model-access.md index 4009227..bc4fa5e 100644 --- a/03-DESIGN/01-to-be/14-model-access.md +++ b/03-DESIGN/01-to-be/14-model-access.md @@ -59,7 +59,7 @@ names neither the licence nor the mesh. **A key is read from a file or standard input, never an argument.** A key on a command line is a key in shell history and in every process listing taken while it ran. It is never echoed back: -what is stored is unreadable by whoever holds it, the control plane included, and printing it +what is stored is unreadable by whoever holds it, the controller included, and printing it would put the one copy that matters on a terminal. ## Refusing is felt, and that is the design working @@ -83,7 +83,7 @@ per machine, which is a step toward it and is not it. *2026-08-31: this gap now has named consumers rather than hypothetical ones.* [ADR 0026](../../02-DECISIONS/0026-the-mesh-has-a-session-of-its-own.md) puts two sessions on the -control-plane node — the node's own and the mesh's — each bound in its own right. See +controller node — the node's own and the mesh's — each bound in its own right. See [`15-the-agent-session.md`](15-the-agent-session.md). **And for sessions the gap is already closed, which was not obvious.** A binding is per module per @@ -172,12 +172,12 @@ it; the metric is vendor-defined, so no false common unit is forced. Anthropic b In the lab, on real machines, in the order a person would meet it: a consumer is refused with both candidates named; put on one and still refused because no key exists; the key is given on standard input and not echoed; the public half arrives saying it came from a record rather than a machine; -the key arrives readable only by that machine — and it is **nowhere in the control plane's own +the key arrives readable only by that machine — and it is **nowhere in the controller's own database**, nor in anything that crossed the broker. For the generalisation ([ADR 0050](../../02-DECISIONS/0050-model-access-is-vendor-agnostic.md)): a second vendor — `anthropic-api-key`, static-key — is bound to a consumer and exercises the whole path -with the carve-out switched off, its key sealed per node and absent from the control plane's database. +with the carve-out switched off, its key sealed per node and absent from the controller's database. For a `refreshable-grant` licence, the refresh token is asserted to exist (encrypted) **only on the manager node**, to be **absent from every holder's delivery**, and the delivered credential to be access-token-only; a `static-key` licence stores no refresh token anywhere. A scenario with two diff --git a/03-DESIGN/01-to-be/15-the-agent-session.md b/03-DESIGN/01-to-be/15-the-agent-session.md index db1528b..bf42494 100644 --- a/03-DESIGN/01-to-be/15-the-agent-session.md +++ b/03-DESIGN/01-to-be/15-the-agent-session.md @@ -22,7 +22,7 @@ so, and the differences are few enough to list here: | **context root** | the node's | the mesh's | | **engram** | that node's | the mesh's | | **licence** | bound in its own right | bound in its own right | -| **runs on** | that node | the node holding the control plane | +| **runs on** | that node | the node holding the controller | | **how many** | one per node | one | Everything below applies to both unless it says otherwise. @@ -64,9 +64,9 @@ as it reports anything else. **A node's session runs on that node**, and cannot be moved. Moved, one machine is answering as another (ADR 0004). -**The mesh's session runs on the node holding the control plane.** The reasoning is in ADR 0026 +**The mesh's session runs on the node holding the controller.** The reasoning is in ADR 0026 and is worth carrying here because it is easy to get backwards: this is not *the important agent -goes on the important machine*. It is that the control-plane node is already the one place +goes on the important machine*. It is that the controller node is already the one place excepted from *compromise of a node is compromise of that node*, and an agent able to reach everything, placed anywhere else, would create a second such place. @@ -102,7 +102,7 @@ machine*. That is not sufficient here, and the shortfall is concrete rather than theoretical: -- the control-plane node hosts **two** sessions, which must be able to hold **different** +- the controller node hosts **two** sessions, which must be able to hold **different** licences — a per-machine binding cannot express it at all; - *this node's session uses the personal licence, the mesh's uses the company one* is the ordinary case, not an exotic one. @@ -125,7 +125,7 @@ session a different agent from another, and memory is part of what makes it *tha and it is not a view over theirs. What the mesh has been asked, and what it worked out, is held in the mesh's root — not in the root of the node that happens to host it. -**That distinction is the point of putting it there.** The control-plane node runs two sessions +**That distinction is the point of putting it there.** The controller node runs two sessions on one machine. If memory belonged to the machine rather than to the root, they would share it, and the mesh's recollection of a fortnight of questions would be indistinguishable from that node's own — which is the collision ADR 0026 exists to avoid, arriving through the back door. @@ -188,7 +188,7 @@ here so the shape is not rediscovered. ## Consequences -**Two sessions on one node, and no ambiguity.** The control-plane node hosts its own node session +**Two sessions on one node, and no ambiguity.** The controller node hosts its own node session and the mesh session. ADR 0004's *one per node* forbids ambiguity about who answers when a **node** is addressed; these answer to different addresses. @@ -207,7 +207,7 @@ and these run in the lab on real machines: | Check | Defends | |---|---| | a node is asked something and its session answers | ADR 0004 | -| the mesh is asked something and the mesh session answers, on the control-plane node | ADR 0026 | +| the mesh is asked something and the mesh session answers, on the controller node | ADR 0026 | | both sessions on that node answer, to their own addresses, without ambiguity | ADR 0026 | | a session switched off replies saying so, rather than timing out | ADR 0004 | | a session whose engram was changed reports having applied it, like any declared file | ADR 0005 | diff --git a/03-DESIGN/01-to-be/16-module-coverage.md b/03-DESIGN/01-to-be/16-module-coverage.md index 8ac9d0a..43b26d5 100644 --- a/03-DESIGN/01-to-be/16-module-coverage.md +++ b/03-DESIGN/01-to-be/16-module-coverage.md @@ -163,14 +163,14 @@ same module. There is one derivation here, and there should stay one. sealing is worth its inconvenience. **An image store is a module, and was written up here as something the mesh does.** It was -considered for the substrate and removed, because the test is not *can it grant itself one* — -nearly anything passes that — but whether the control plane needs it before it can give its first +considered for the foundation and removed, because the test is not *can it grant itself one* — +nearly anything passes that — but whether the controller needs it before it can give its first instruction. It does not. So a registry somebody runs for their own images is the same module as the one the mesh runs for its own: it offers a place to push, and claims that role once per machine. **A rule was enforced only at the far end.** A module may not declare an action, and the host -refused one correctly — but the control plane accepted it into the catalogue, resolved it and +refused one correctly — but the controller accepted it into the catalogue, resolved it and pushed it, so the refusal arrived on a machine with nothing tying it back to the manifest. The rule held; it was just unusable, which is the same shape as the network shape that cost five failing tests before anyone read the host's log. It is now refused where it is written. diff --git a/03-DESIGN/01-to-be/17-raising-a-mesh.md b/03-DESIGN/01-to-be/17-raising-a-mesh.md index 2570dbd..0e02033 100644 --- a/03-DESIGN/01-to-be/17-raising-a-mesh.md +++ b/03-DESIGN/01-to-be/17-raising-a-mesh.md @@ -30,7 +30,7 @@ rules cannot all hold at once, and it is resolved by a pivot **Joining** happens on every machine after the first, and it is ordinary. A mesh exists, so it can be asked for a token and told what the machine should be. Joining installs the host and nothing -else: no temporary anything, no substrate raised by hand, no registry. +else: no temporary anything, no foundation raised by hand, no registry. Confusing the two is what produced a procedure that only ever worked in a fixture. A bed that raises four machines the same way has not tested genesis at all — it has tested joining, four @@ -38,13 +38,13 @@ times, with the first one hand-fed. ## What changed, and what did not -*2026-09-13.* The installer carries a builder now, and builds the control plane it raises. Three +*2026-09-13.* The installer carries a builder now, and builds the controller it raises. Three records settle it: [ADR 0070](../../02-DECISIONS/0070-the-catalogue-owns-the-module-graph.md) that genesis builds rather than carries, [ADR 0071](../../02-DECISIONS/0071-where-genesis-gets-its-source.md) where it clones from, and [ADR 0073](../../02-DECISIONS/0073-the-installer-carries-a-builder.md) how the builder arrives — which also records an argument that failed. It was put that a produced image must be published before anything can fetch it, so the registry would have to come up before the -control plane. It does not: the machine that builds the image is the machine that runs it, and a +controller. It does not: the machine that builds the image is the machine that runs it, and a local image is named by the digest of its own configuration exactly as a carried one is. **Building changes where the bytes came from, not where they are.** @@ -53,7 +53,7 @@ written. What follows describes the program that exists. ## Genesis -The installer is a single program carrying **the builder** inside it — not the control plane +The installer is a single program carrying **the builder** inside it — not the controller ([ADR 0073](../../02-DECISIONS/0073-the-installer-carries-a-builder.md)). What cannot be fetched is the thing that does the fetching, so that is what is carried; everything else is made here. @@ -62,12 +62,12 @@ It proceeds in one direction, and every step is safe to run again. **First it refuses to start if the machine is not ready.** A container runtime, the ability to write where it must write, the host binary where it expects it — and a repository and a commit to build from, because an installer told nothing would raise a store and a broker and then have -nothing to raise a control plane from. A machine that is not ready is told what is missing rather +nothing to raise a controller from. A machine that is not ready is told what is missing rather than half-changed. -**Then it loads the carried builder and builds the control plane with it**, from a repository on a +**Then it loads the carried builder and builds the controller with it**, from a repository on a mesh that already exists and a named commit ([ADR 0071](../../02-DECISIONS/0071-where-genesis-gets-its-source.md)). -This is the same repository and path every later rebuild of the control plane will use, so what +This is the same repository and path every later rebuild of the controller will use, so what raises the mesh is the same thing that will maintain it. **Then it describes what the machine will become.** The image it just made is named by the digest @@ -75,7 +75,7 @@ of its own configuration — content-addressed and unforgeable, and requiring no it. That is legal precisely where nothing could have served one, and it is why building here needs no registry: the machine that made the image is the machine that will run it. -**Then it raises the substrate and a temporary control plane, and waits for that control plane to +**Then it raises the foundation and a temporary controller, and waits for that controller to answer.** At this point the machine is a mesh of one node with nothing joined to it. **Then the machine joins the mesh it is itself running.** It enrols, and an agent runs on it. Being @@ -84,13 +84,13 @@ enrolling is itself the thing that makes a mesh hear from a machine. **Then it installs a registry**, so the mesh has somewhere to keep its own images. -**Then it publishes the control plane's image to that registry**, which is the moment the image +**Then it publishes the controller's image to that registry**, which is the moment the image first receives a digest assigned by something other than itself. This is the carrying step, and it -is the same step for all three things the build loop cannot produce for itself — the control plane, +is the same step for all three things the build loop cannot produce for itself — the controller, the registry, and the builder. The rule and its closed list are in [`12-a-module-repository`](12-a-module-repository.md#the-three-the-loop-cannot-build-and-there-are-only-three). -**Then it installs the control plane again, as an ordinary module pinned to that digest, and drops +**Then it installs the controller again, as an ordinary module pinned to that digest, and drops the temporary one.** The pivot is complete: what raised the mesh is gone, and what runs is a module like any other. From here the mesh can build and roll out its own upgrades, including to the thing that runs it. @@ -98,7 +98,7 @@ that runs it. ## After the pivot, and still part of installing Genesis ends with a mesh of one that runs, and that is not the same as a mesh that works. What it -has is a control plane, a store, a queue and a registry. What it cannot yet do is **produce +has is a controller, a store, a queue and a registry. What it cannot yet do is **produce anything** — and almost every module in the catalogue is waiting to be produced, because a manifest names what its artifacts are and nothing has made them. @@ -107,9 +107,9 @@ So installing continues: **The builder arrives, and installing is what brings it.** It is a module like any other and is assigned to a machine like any other, but it cannot be built by the thing it is — see [`12-a-module-repository`](12-a-module-repository.md#the-three-the-loop-cannot-build-and-there-are-only-three). -So it is carried, and it is already here: it is what built the control plane. The last step of +So it is carried, and it is already here: it is what built the controller. The last step of installing publishes it into the mesh's own registry, installs it as an ordinary module pinned to -that digest, and issues it a broker account — the same two acts the control plane went through, +that digest, and issues it a broker account — the same two acts the controller went through, plus the one thing only a builder needs. The account is issued before the machine is sent anything, because a builder that arrives without its credential starts, finds nothing it may read, and waits, which looks exactly like a builder with no work. @@ -121,12 +121,12 @@ publishes each artifact into the mesh's own registry, and hands back the module pinned and the commit recorded. The mesh records that, and from then on the module is described by something it made rather than by a placeholder. -**The control plane is built like the rest.** It was carried in and published once, which got the +**The controller is built like the rest.** It was carried in and published once, which got the mesh running; building it from its own repository and path is what makes it upgradeable. The first time that happens is the moment the mesh stops depending on the installer for anything. **And then the catalogue.** Every module with source of its own is built the same way. Until this -has happened a mesh can install only what is public or carried, which is the substrate and little +has happened a mesh can install only what is public or carried, which is the foundation and little else. Only after all of that is the ordinary loop available: change a module's source, the mesh notices @@ -138,7 +138,7 @@ What remains after *that* belongs to somebody else: adding machines, and decidin ## Joining -A machine joins with the host binary and a token. It does not raise a substrate, does not install a +A machine joins with the host binary and a token. It does not raise a foundation, does not install a registry, and is never enrolled twice. The mesh already knows how to tell a machine what to be; joining is the point at which a machine starts listening. @@ -169,8 +169,8 @@ paragraphs above describing the catalogue being built are a thing somebody now t thing that cannot happen. **A module's declaration still has to be copied onto the machine by hand.** The installer reads the -registry's and the control plane's manifests from a checkout somebody put there. The control plane's -now lives in the control plane's own repository, which the installer clones anyway, so this is a +registry's and the controller's manifests from a checkout somebody put there. The controller's +now lives in the controller's own repository, which the installer clones anyway, so this is a thing that can be removed rather than a thing that must be designed. **A machine has no account for a registry that asks for one.** The mesh grants a consumer a @@ -198,6 +198,6 @@ the SDK inside `docker build`, which is slow and names a branch head rather than | An image is named exactly | A machine refuses a bundle naming an image by tag. The refusal is exercised, not assumed. | | The installer is what installed this | **Nothing.** See above. | | The builder can arrive on a fresh mesh | The installer carries it and installs it as its last step, and the genesis bed raises a machine by running the installer. A bed that raises one any other way fails its own acceptance check. | -| The control plane a mesh runs is one it built | The genesis bed asserts the running control plane is pinned to a digest this mesh's own registry serves, for an image built from a named repository and commit — not one the installer carried. | -| A core module is built rather than only carried | The control plane is rebuilt from its own repository and path, and the running mesh is upgraded to the result — the same path any module takes. | +| The controller a mesh runs is one it built | The genesis bed asserts the running controller is pinned to a digest this mesh's own registry serves, for an image built from a named repository and commit — not one the installer carried. | +| A core module is built rather than only carried | The controller is rebuilt from its own repository and path, and the running mesh is upgraded to the result — the same path any module takes. | | Installing produced a mesh that can produce | A module with source of its own is asked for and comes back pinned to a digest this mesh's registry assigned, not to a placeholder. **Done by hand on a raised machine, not yet by a bed** — it built the shared base and then a module naming that base. Nothing automated asserts it, which makes this the weakest check on this page. | diff --git a/03-DESIGN/01-to-be/18-building-a-module.md b/03-DESIGN/01-to-be/18-building-a-module.md index 8fe2131..f6fe5dc 100644 --- a/03-DESIGN/01-to-be/18-building-a-module.md +++ b/03-DESIGN/01-to-be/18-building-a-module.md @@ -24,9 +24,9 @@ why the current model does not fit what a module is. Turning a module's source into artifacts the mesh can pin, publish and deliver — and saying truthfully what was produced and what it was produced against. -Everything else is somebody else's: *what* to build is the control plane's, *what a build means* is +Everything else is somebody else's: *what* to build is the controller's, *what a build means* is the catalogue's ([ADR 0072](../../02-DECISIONS/0072-two-graphs-and-the-build-chain.md)), *where an artifact runs* is the -control plane's again. The builder's whole responsibility is the middle. +controller's again. The builder's whole responsibility is the middle. ## The language @@ -130,7 +130,7 @@ change is one edit; with four it is four that must land together, and a mesh who about the envelope fails by ignoring messages rather than by failing to compile. **So the contracts have to stop being expressed twice before they are expressed four times.** They -are already: the manifest, declaration and link shapes exist as Go structs in the control plane and +are already: the manifest, declaration and link shapes exist as Go structs in the controller and as TypeScript types in the SDK, kept in step by hand. Nobody has felt it because both live in one repository. A second *language* makes that drift; a specified envelope and schema that every SDK implements makes a second language an implementation rather than a translation. @@ -176,7 +176,7 @@ disagrees with it. | `certificate` | a certificate for a name it serves | | `grants` | credentials it must create for its consumers | | `filtering` | rules beyond its own ports | -| `computed` | marks a module the control plane generates rather than an author writing | +| `computed` | marks a module the controller generates rather than an author writing | | `build.artifacts` | what it produces | ### What it builds diff --git a/03-DESIGN/01-to-be/19-the-module-protocol.md b/03-DESIGN/01-to-be/19-the-module-protocol.md index c8ad906..f63c2d9 100644 --- a/03-DESIGN/01-to-be/19-the-module-protocol.md +++ b/03-DESIGN/01-to-be/19-the-module-protocol.md @@ -58,7 +58,7 @@ mesh can issue anything. An implementation accepts both and must not treat the s - The connection **pins the fingerprint**. It does not trust a certificate authority, and it does not skip verification. A broker presenting a different certificate is refused, whatever else is true of it. -- A scoped account **does not declare exchanges**. The substrate owns them; an account that may +- A scoped account **does not declare exchanges**. The foundation owns them; an account that may declare one is an account that may create a parallel mesh by typo. - An implementation **declares its own queue** and nothing else. @@ -142,7 +142,7 @@ A module's tools are its operator-facing surface. ### Not yet true The caller's half has no account. Until that is settled, the only thing that can ask a module a -question is the substrate's bootstrap admin, which is not a protocol so much as a way in. +question is the foundation's bootstrap admin, which is not a protocol so much as a way in. --- diff --git a/03-DESIGN/01-to-be/20-writing-a-module.md b/03-DESIGN/01-to-be/20-writing-a-module.md index 5e9bf7d..ccc667f 100644 --- a/03-DESIGN/01-to-be/20-writing-a-module.md +++ b/03-DESIGN/01-to-be/20-writing-a-module.md @@ -124,7 +124,7 @@ ingest/ingest.py emit("module.showcase.ingested", …) → events, emitti ### Step 3 — the mesh does the rest You do not write a Dockerfile, a unit file, or a port number. The builder picks the toolchain from -the language, compiles each artifact alone, and publishes it. The control plane assigns the machine +the language, compiles each artifact alone, and publishes it. The controller assigns the machine and the ports; the host writes the units. ### What it costs you to use four languages diff --git a/03-DESIGN/01-to-be/21-the-installation-in-full.md b/03-DESIGN/01-to-be/21-the-installation-in-full.md index d21b591..d705f29 100644 --- a/03-DESIGN/01-to-be/21-the-installation-in-full.md +++ b/03-DESIGN/01-to-be/21-the-installation-in-full.md @@ -26,9 +26,9 @@ happens*, in order, with each step's name as the installer prints it. | | why | |---|---| -| a container runtime | the substrate is containers, and the installer refuses without one | +| a container runtime | the foundation is containers, and the installer refuses without one | | the host binary, where the installer expects it | it is what the machine becomes | -| a repository and a commit to build from | the installer carries a builder, not a control plane, so it must be told what to make ([ADR 0073](../../02-DECISIONS/0073-the-installer-carries-a-builder.md)) | +| a repository and a commit to build from | the installer carries a builder, not a controller, so it must be told what to make ([ADR 0073](../../02-DECISIONS/0073-the-installer-carries-a-builder.md)) | | a way out to the internet | the store, the broker and the registry are pulled from it | | a reachable git host, and a commit it serves | what is cloned is the trust anchor for everything this mesh will ever run ([ADR 0071](../../02-DECISIONS/0071-where-genesis-gets-its-source.md)) | | the address other machines will reach this one on | a token carries it verbatim; the installer refuses to guess | @@ -41,19 +41,19 @@ Twelve steps, run by one program, each safe to run again. |---|---|---|---| | 1 | `preflight` | everything above is checked | the machine is not half-changed by a missing prerequisite | | 2 | `load` | the carried builder image is loaded | the mesh has the one thing it cannot fetch — the thing that does the fetching | -| 3 | `build` | the builder clones the named repository at the named commit and **builds the control plane** | what will run is something this mesh made and can make again | -| 4 | `bundle` | the substrate template is written out, with the built control plane's id in place of the placeholder | the machine has a description of what it will become | -| 5 | `apply` | store, broker, schemas, and a **temporary** control plane are raised | a mesh of one exists and answers | -| 6 | `verify` | the control plane is asked, rather than assumed | it replies, and says it has no machines | +| 3 | `build` | the builder clones the named repository at the named commit and **builds the controller** | what will run is something this mesh made and can make again | +| 4 | `bundle` | the foundation template is written out, with the built controller's id in place of the placeholder | the machine has a description of what it will become | +| 5 | `apply` | store, broker, schemas, and a **temporary** controller are raised | a mesh of one exists and answers | +| 6 | `verify` | the controller is asked, rather than assumed | it replies, and says it has no machines | | 7 | `enrol` | the machine joins the mesh it is itself running | the mesh has one node, and an agent runs on it | | 8 | `registry` | the mesh's own artifact store is installed | there is somewhere to keep what this mesh makes | -| 9 | `publish` | the control plane's image is pushed into it | the image has a digest something other than itself assigned | -| 10 | `control-plane` | it is installed again, as an ordinary module pinned to that digest | what runs is a module like any other | -| 11 | `retire` | the temporary control plane is dropped | **the pivot is complete** — what raised the mesh is gone | +| 9 | `publish` | the controller's image is pushed into it | the image has a digest something other than itself assigned | +| 10 | `controller` | it is installed again, as an ordinary module pinned to that digest | what runs is a module like any other | +| 11 | `retire` | the temporary controller is dropped | **the pivot is complete** — what raised the mesh is gone | | 12 | `builder` | the carried builder is published, installed as a module, and issued a broker account | the mesh can produce | **Steps 8 to 11 are the pivot** ([ADR 0067](../../02-DECISIONS/0067-genesis-is-a-pivot.md)). Before -them the control plane is something the installer put there; after them it is something the mesh +them the controller is something the installer put there; after them it is something the mesh holds a record of and can upgrade. The account in step 12 is issued *before* the machine is sent anything, because a builder that arrives without its credential starts, finds nothing it may read, and waits — which looks exactly like a builder with no work. @@ -68,10 +68,10 @@ catalogue be missing from a test for weeks without anything complaining. | # | step | what happens | why it is here | |---|---|---|---| | 13 | the shared base is built | the toolchain and runtime every module with code of its own stands on | nothing else with code can be built until it exists | -| 14 | a store module is built and run | a database **provider**, which the substrate's store is not | the substrate's store is the control plane's own memory, and offers nothing to anything | +| 14 | a store module is built and run | a database **provider**, which the foundation's store is not | the foundation's store is the controller's own memory, and offers nothing to anything | | 15 | the catalogue is built and run | the module graph | without it the mesh cannot say what it holds, what a change reaches, or what must be rebuilt | | 16 | the catalogue asks for what it missed | the builds made before it existed are replayed | on a fresh mesh those are always the base, the store and the catalogue itself ([issue 050](../../04-ISSUES/050-the-catalogue-knows-nothing-built-before-it/00-report.md)) | -| 17 | the control plane is rebuilt from its own repository | and rolled out through the module path | the moment the mesh stops depending on the installer for anything | +| 17 | the controller is rebuilt from its own repository | and rolled out through the module path | the moment the mesh stops depending on the installer for anything | | 18 | `networking` is assigned **and the node placed** | a private network, and names | assigning installs the module; placing says where this machine is on it. Both, or the names file is written empty | | 19 | the packet filter is assigned | rules generated from what modules declared | until this, every rule the mesh computes has never been applied to anything | @@ -129,9 +129,9 @@ two. | # | module | provides | note | |---|---|---|---| -| 1 | `postgres` | `postgres-database` | **the control plane's own records and every module's.** One server, not two | +| 1 | `postgres` | `postgres-database` | **the controller's own records and every module's.** One server, not two | | 2 | `lavinmq` | `amqp` | the broker every node dials, and what modules are granted vhosts on | -| 3 | `mesh-control` | *claims* `the-control-plane` | decides what runs where | +| 3 | `mesh-control` | *claims* `the-controller` | decides what runs where | | 4 | `distribution` | `artifact-store` | what the mesh built, pinned by digest — the module is the software (Distribution), the provision is the job | | 5 | `builder` | — | turns source into artifacts | | 6 | `mesh-tools` | build inputs | the base everything with code compiles against. **Runs nowhere** | @@ -144,8 +144,8 @@ two. ### Why it is twelve and not thirteen -**The substrate's store and the `postgres` module are the same module.** They were two rows while the -substrate was a different *kind* of thing: a store raised from a bundle cannot provide +**The foundation's store and the `postgres` module are the same module.** They were two rows while the +foundation was a different *kind* of thing: a store raised from a bundle cannot provide `postgres-database`, so anything wanting a database needed a second server. That is visible on any mesh built today — `mesh-store` and `postgres`, two containers, **the same image**. @@ -156,20 +156,20 @@ The naming rule settles which name survives > interface *is* the protocol. **"database" is not a capability; the protocol is.** Never false > genericity: a name must not promise a swap the contract cannot deliver. -So there is no `store` module. The control plane is coupled to postgres — its own queries use +So there is no `store` module. The controller is coupled to postgres — its own queries use `distinct on` and `on conflict`, which are not portable — and calling it `store` would advertise a swap that would fail the first time somebody tried it. **The same applies to the broker**, with a different outcome: `amqp` *is* a protocol and more than one implementation speaks it, so `amqp` is a legitimate provision and `lavinmq` is one provider of -it. The substrate's broker and the `lavinmq` module collapse the same way. +it. The foundation's broker and the `lavinmq` module collapse the same way. ### What this costs, and it is the last specialty Adopting the store and the broker as modules is [issue 051](../../04-ISSUES/051-the-mesh-cannot-update-what-it-depends-on/00-report.md), and it is the only part of this that has not been designed. The two hard parts: -- **upgrading a store the control plane is reading from** — a rollout where the thing being replaced +- **upgrading a store the controller is reading from** — a rollout where the thing being replaced is the thing holding the record of the rollout - **upgrading a broker over the broker** — the instruction arrives on what it replaces, so the machine must finish without being able to report progress diff --git a/03-DESIGN/01-to-be/22-the-work-ahead.md b/03-DESIGN/01-to-be/22-the-work-ahead.md index 6c1afeb..da1602e 100644 --- a/03-DESIGN/01-to-be/22-the-work-ahead.md +++ b/03-DESIGN/01-to-be/22-the-work-ahead.md @@ -34,7 +34,7 @@ after the phases that change its build path are in — not before. ## Phase 1 — the protocol is one thing, and correct *(mostly done: the drift was dead types)* -**Why here.** The Go control plane and the TypeScript SDK disagree about what a grant carries +**Why here.** The Go controller and the TypeScript SDK disagree about what a grant carries (`consumer` is the module in one, the node in the other). That is exercised by the installer's own provisioning — the catalogue's database — so it belongs before more is built on it. @@ -80,12 +80,12 @@ raises gitea and publishes the SDK before the base build. Those close together i a protocol that agrees, a registry to publish to. Issue 051. - [ ] 3.1 the store is adopted — raised at genesis, then held as the `postgres` module; one server, - the control plane's records and every module's database in it + the controller's records and every module's database in it - [ ] 3.2 the broker is adopted — one `lavinmq`, the `/` vhost for the mesh bus, a vhost per consumer that requires `amqp`; the second server gone -- [ ] 3.3 an upgrade of each, proven: a store the control plane reads from, a broker over the +- [ ] 3.3 an upgrade of each, proven: a store the controller reads from, a broker over the broker, each with a stated window -- [ ] 3.4 `status` can say the substrate is behind its source, which today it cannot form +- [ ] 3.4 `status` can say the foundation is behind its source, which today it cannot form **Done when.** A mesh built from bare metal runs one postgres and one lavinmq, and can upgrade either — so the twelve-module floor has no specialty left in it. diff --git a/03-DESIGN/01-to-be/README.md b/03-DESIGN/01-to-be/README.md index a12de17..1b38359 100644 --- a/03-DESIGN/01-to-be/README.md +++ b/03-DESIGN/01-to-be/README.md @@ -15,8 +15,8 @@ document is written and this one's status becomes `implemented`. | [`03-scenario-lifecycle.md`](03-scenario-lifecycle.md) | What happens to a scenario — raise, snapshot, restore, move, destroy | [ADR 0016](../../02-DECISIONS/0016-the-lab.md) | | [`04-lab-installation.md`](04-lab-installation.md) | Getting the lab onto a clean machine, and why it verifies capability rather than installation | [ADR 0010](../../02-DECISIONS/0010-delivery.md) | | [`05-the-node-host.md`](05-the-node-host.md) | Tier 0 — the one thing installed by hand, and the only thing that changes a machine | [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | -| [`06-the-control-plane.md`](06-the-control-plane.md) | Tier 2 — what the term means, and the test for what belongs in it | [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | -| [`07-the-substrate.md`](07-the-substrate.md) | Tier 1 — what the control plane consumes and cannot grant itself | [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0048](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) | +| [`06-the-controller.md`](06-the-controller.md) | Tier 2 — what the term means, and the test for what belongs in it | [ADR 0005](../../02-DECISIONS/0005-the-node-host.md) | +| [`07-the-foundation.md`](07-the-foundation.md) | Tier 1 — what the controller consumes and cannot grant itself | [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0048](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) | | [`08-connectivity.md`](08-connectivity.md) | One context in full — overlay, resolution, exposure, filtering, certificates | [ADR 0007](../../02-DECISIONS/0007-connectivity.md), [0050](../../02-DECISIONS/0007-connectivity.md), [0051](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0055](../../02-DECISIONS/0006-the-substrate-and-the-control-plane.md) | | [`09-the-node-lifecycle.md`](09-the-node-lifecycle.md) | How a machine becomes a node, stays one, and stops being one | [ADR 0004](../../02-DECISIONS/0004-a-node-and-how-it-joins.md), [0051](../../02-DECISIONS/0004-a-node-and-how-it-joins.md) | | [`10-delivery.md`](10-delivery.md) | Modules, the three edges, and how a change becomes a running thing | [ADR 0010](../../02-DECISIONS/0010-delivery.md), [0064](../../02-DECISIONS/0009-modules-and-the-graph.md), [0065](../../02-DECISIONS/0009-modules-and-the-graph.md) | diff --git a/README.md b/README.md index a647135..720d4e1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Novox HQ The single source of truth for what Novox builds — what it **is**, what it is **becoming**, -and why. Today that is almost entirely **Novox Mesh**, the substrate everything else runs on. Implementation lives in `modules/`; the reasoning behind it lives here. +and why. Today that is almost entirely **Novox Mesh**, the foundation everything else runs on. Implementation lives in `modules/`; the reasoning behind it lives here. ## Structure