diff --git a/00-META/how-we-build.md b/00-META/how-we-build.md index 4f1c98f..0b56818 100644 --- a/00-META/how-we-build.md +++ b/00-META/how-we-build.md @@ -114,12 +114,14 @@ and it runs on no node at all. Anatomy makes attractive names and poor boundaries. Name the thing the domain calls it. -### Group by domain, not by single function +### Things that change together share an authority, not a package -A module is a purpose, not a piece of software. Four modules that together constitute "how a -node is reachable" and cannot be assigned, versioned or replaced as one thing are four -accidents, not four boundaries. -[ADR 0017](../02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md) +When several modules always change together under one intent, name the **context** that decides +for them. Do not merge them into one module: they are delivered to different nodes, and a module +that must be assigned where half of it is unwanted is not a boundary either. + +Coherence is a context. Delivery is a module. Relationships are edges, not folders. +[ADR 0054](../02-DECISIONS/0054-things-that-change-together-share-an-authority.md) ### Contexts integrate through the record, never through a shared schema diff --git a/02-DECISIONS/0003-the-mesh-database-is-the-source-of-truth.md b/02-DECISIONS/0003-the-mesh-database-is-the-source-of-truth.md index 1b91bcc..34ea3d5 100644 --- a/02-DECISIONS/0003-the-mesh-database-is-the-source-of-truth.md +++ b/02-DECISIONS/0003-the-mesh-database-is-the-source-of-truth.md @@ -1,5 +1,6 @@ --- -status: accepted +status: superseded +superseded-by: 02-DECISIONS/0056-the-authority-is-the-control-plane-not-a-database.md date: 2026-04-02 deciders: jochen reconstructed: true diff --git a/02-DECISIONS/0026-every-decision-is-a-record.md b/02-DECISIONS/0026-every-decision-is-a-record.md index eb64b60..eb89847 100644 --- a/02-DECISIONS/0026-every-decision-is-a-record.md +++ b/02-DECISIONS/0026-every-decision-is-a-record.md @@ -47,9 +47,16 @@ beside `02-DECISIONS/`, holding different things. **If a decision is worth recording, it is worth a record. If it is not worth a record, it is not recorded.** -`02-DECISIONS` holds every decision. There is no ledger, no index file, and no central status -of any kind. The chronological view — decisions in the order they were taken — is *generated* -from record frontmatter, which is what the ledger was actually for. +`02-DECISIONS` holds every decision, and **there is no ledger** — no separate document in which +a decision is also summarised, ranked or tracked. The chronological view — decisions in the order +they were taken — is *generated* from record frontmatter, which is what the ledger was actually +for. + +That generation is not this record's rule. It is +[ADR 0022](0022-status-lives-in-frontmatter.md), which already decides repository-wide that +status lives in frontmatter and every cross-cutting view is generated rather than written. This +record does not restate it — 0022's own words are *prose does not restate status; one place, and +two is one too many*, and an earlier version of this paragraph did exactly that. Content that was only in the ledger was rehomed rather than dropped: diff --git a/02-DECISIONS/0051-the-enrolment-token-carries-the-mesh.md b/02-DECISIONS/0051-the-enrolment-token-carries-the-mesh.md index fd1a465..f685e7c 100644 --- a/02-DECISIONS/0051-the-enrolment-token-carries-the-mesh.md +++ b/02-DECISIONS/0051-the-enrolment-token-carries-the-mesh.md @@ -55,14 +55,35 @@ mesh. ## Decision -**The enrolment token carries three things**, and it is the only thing a joining node needs: +**The enrolment token carries four things**, and it is the only thing a joining node needs: | | | | |---|---|---| -| **where** | an **address**, not a name | there is no resolution yet, and this is why none is needed | -| **who** | the fingerprint of the identity to expect | what makes the mesh provable rather than assumed | +| **where** | the **broker's address**, not a name | a node dials the broker ([ADR 0001](0001-nodes-communicate-over-a-broker.md)); there is no resolution yet, and this is why none is needed | +| **what it is connecting to** | the fingerprint of the **broker's** certificate | so the node reaches the mesh's bus and not something answering in its place | +| **who it will believe** | the **control plane's** signing identity | what makes the mesh provable rather than assumed | | **the right to join** | the one-time secret ADR 0039 already specifies | useless once used, useless after it expires | +### The endpoint and the authority are two identities, not one + +This is worth separating because collapsing it is the easy mistake, and the collapsed version +silently fails to deliver what [ADR 0039](0039-the-link-is-the-security-boundary.md) asks for. + +A node connects to the **broker** and takes instruction from the **control plane**, which sits +behind it. Pinning only the broker would make the control plane's authority *transitive* — the +node would believe a declaration because of where it arrived from. **A compromised broker could +then forge declarations**, and since the host applies whatever the link delivers, that is the +whole machine. + +So the node verifies **the transport** and **each declaration** separately: + +- the broker, by its certificate, at connect time; +- the control plane, by a **signature on the declaration itself**, every time. + +Then 0039's *the control plane proves it is the mesh* holds against a hostile broker rather than +assuming a friendly one — which matters because the broker is the one component every node must +reach and the one most exposed. + The token is issued by the mesh for one enrolment and **carried out of band** — by the person adopting the machine. That is what breaks both circles: its authenticity comes from the channel it travelled, not from anything the node can check afterwards. @@ -104,6 +125,13 @@ being a design statement and being a fact. - **The address in the token can go stale.** If the control plane moves, unissued tokens point somewhere wrong. Tokens are short-lived, which bounds it; moving the control plane is [`06`](../03-DESIGN/01-to-be/06-the-control-plane.md)'s undesigned territory regardless. +- **Declarations must be signed, and that is a real requirement rather than a note.** The host + verifies a signature before applying anything, which adds a key to what it must carry and a + failure mode it must report legibly — *this declaration is not from the mesh I joined* is a + different condition from *this declaration is malformed*, and they must not read alike. +- **Rotating the control plane's signing identity is a fleet-wide operation**, because every node + holds the previous one. That is the cost of not trusting the broker, it is accepted, and it + needs a rollover that overlaps rather than a flag day. - **A rejoining node is an ordinary case, not a special one.** A node that has lost its identity gets a new token. There is no recovery path to design because there is no long-lived secret to recover. diff --git a/02-DECISIONS/0054-things-that-change-together-share-an-authority.md b/02-DECISIONS/0054-things-that-change-together-share-an-authority.md index ad7b201..24b5907 100644 --- a/02-DECISIONS/0054-things-that-change-together-share-an-authority.md +++ b/02-DECISIONS/0054-things-that-change-together-share-an-authority.md @@ -1,5 +1,5 @@ --- -status: proposed +status: accepted date: 2026-08-27 deciders: jochen reconstructed: false @@ -91,10 +91,16 @@ replacement keeps the observation and changes the instruction: - **The instruction now matches the design.** An agent reading the constitution and an agent reading 0044 reach the same answer, which they currently do not. -- **The republication is the point, not a formality.** Until the derived page is regenerated the - mesh is still governed by the old rule, and this record has changed nothing where it matters. - Verified by reading the rule back out of the published page - ([ADR 0035](0035-a-picture-is-read-from-what-runs.md)), not by the publish reporting success. +- **Synced 2026-08-27**, playbook [05](../00-META/process/05-constitution-sync.md). The derived + page carries the new rule and §4 keeps its number. **Verified by reading back**, not by the + publish reporting success: the replacement text is present, and the old section's body — *scope + under measurement: grouping is evidenced for reachability* — returns nothing. +- **The sync found a drift the playbook exists to catch.** The published §4 and the source did not + say the same thing: the source spoke of *four accidents, not four boundaries*, the published + page of *one intent expressed four times*, and only the published page carried the scope + caveat. Same rule, two texts, already diverging — which is the exact failure + [ADR 0025](0025-hq-is-the-source-of-the-constitution.md) predicted and the reason the playbook + re-publishes the whole page rather than patching a section. - **`context` becomes a word the constitution uses**, which raises the obvious next question — *which contexts are there* — and that is [ADR 0055](0055-the-control-plane-is-the-node-coordinating-contexts.md), not this record. diff --git a/02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md b/02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md index aeb4b04..4410f37 100644 --- a/02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md +++ b/02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md @@ -1,5 +1,5 @@ --- -status: proposed +status: accepted date: 2026-08-27 deciders: jochen reconstructed: false diff --git a/02-DECISIONS/0056-the-authority-is-the-control-plane-not-a-database.md b/02-DECISIONS/0056-the-authority-is-the-control-plane-not-a-database.md index 67fafd7..00c4b29 100644 --- a/02-DECISIONS/0056-the-authority-is-the-control-plane-not-a-database.md +++ b/02-DECISIONS/0056-the-authority-is-the-control-plane-not-a-database.md @@ -1,5 +1,5 @@ --- -status: proposed +status: accepted date: 2026-08-27 deciders: jochen reconstructed: false diff --git a/03-DESIGN/01-to-be/06-the-control-plane.md b/03-DESIGN/01-to-be/06-the-control-plane.md index 9cdf334..1947f8f 100644 --- a/03-DESIGN/01-to-be/06-the-control-plane.md +++ b/03-DESIGN/01-to-be/06-the-control-plane.md @@ -8,6 +8,7 @@ decisions: - 02-DECISIONS/0053-one-control-plane-and-no-failover.md - 02-DECISIONS/0030-the-repository-structure.md - 02-DECISIONS/0015-mesh-brokers-nodes-host-agents-think.md + - 02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md --- # The control plane @@ -42,22 +43,31 @@ catch it because the dependency direction is still correct. ## What is inside it -Ten contexts and one interface, from the skeleton -([research 006](../../01-RESEARCH/006-mesh-from-scratch/skeleton.md)): +**Seven contexts and one interface** +([ADR 0055](../../02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md)) — +each one earning its place by the test above rather than by being ours: -| | | -|---|---| -| **record** | the event log every other context integrates through | -| **inventory** | nodes, modules, assignments, versions | -| **config** | settings, secrets, and deriving them onto nodes | -| **connectivity** | overlay, resolution, exposure, filtering, certificates — **specified in full in [`08-connectivity.md`](08-connectivity.md)** | -| **provisioning** | resource grants between modules | -| **delivery** | source to artifact to node | -| **observability** | health, logs, metrics, alerts | -| **identity** | agents, humans, services, authorisation | -| **work** | tasks, workflows, runs | -| **knowledge** | memory, documents, retrieval | -| **api** | the one interface every surface speaks to | +| | | needs to know about more than one node because | +|---|---|---| +| **inventory** | nodes, modules, assignments, versions | that *is* the mesh-wide fact | +| **config** | settings, secrets, and deriving them onto nodes | it derives **onto nodes** | +| **connectivity** | overlay, resolution, exposure, filtering, certificates — **specified in full in [`08-connectivity.md`](08-connectivity.md)** | who peers with whom; which node is reachable | +| **provisioning** | resource grants between modules | consumer and provider may be on different nodes | +| **delivery** | source to artifact to node | it targets nodes | +| **observability** | health, logs, metrics, alerts | *unreachable for a week* is nobody else's to notice | +| **identity** | agents, humans, services, authorisation | credentials follow an agent's node bindings and modality | +| **api** | the one interface every surface speaks to | — it is an interface, not a context | + +**What is deliberately not here.** `work`, `knowledge` and `stream` are **mesh-hosted +applications** — first-party, shipped with everything else, and running on the mesh the way +anything else does. A task does not need to know a node exists, and *being ours does not make +something infrastructure*. `ai` is folded into `config`: a provider licence is an ordinary grant. + +**`record` is an open question rather than an eighth entry.** Contexts integrate through it +([ADR 0045](../../02-DECISIONS/0045-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 +removed. Listing it here would settle by naming what has not been settled by arguing. **These are contexts, not services.** They are separate in the sense that matters — each owns its own store, and they integrate through the record rather than by reading one another @@ -118,9 +128,10 @@ every public name. ## Open -- **The contexts themselves.** Ten is the skeleton's claim, not a settled list. Research 006 - asks whether the record belongs here or in the substrate, and whether identity is a context or - a substrate service. +- ~~**The contexts themselves.**~~ **Decided** — seven, by + [ADR 0055](../../02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.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. - **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)). diff --git a/03-DESIGN/01-to-be/README.md b/03-DESIGN/01-to-be/README.md index 4da6f15..e31bd73 100644 --- a/03-DESIGN/01-to-be/README.md +++ b/03-DESIGN/01-to-be/README.md @@ -17,14 +17,15 @@ document is written and this one's status becomes `implemented`. | [`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 0037](../../02-DECISIONS/0037-the-host-applies-it-does-not-decide.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 0037](../../02-DECISIONS/0037-the-host-applies-it-does-not-decide.md) | | [`07-the-substrate.md`](07-the-substrate.md) | Tier 1 — what the control plane consumes and cannot grant itself | [ADR 0038](../../02-DECISIONS/0038-a-node-joins-by-linking-first.md), [0048](../../02-DECISIONS/0048-the-substrate-is-named.md) | -| [`08-connectivity.md`](08-connectivity.md) | One context in full — overlay, resolution, exposure, filtering, certificates | [ADR 0049](../../02-DECISIONS/0049-a-route-is-a-grant.md), [0050](../../02-DECISIONS/0050-reachability-is-a-property-of-the-address.md), [0051](../../02-DECISIONS/0051-the-enrolment-token-carries-the-mesh.md) | +| [`08-connectivity.md`](08-connectivity.md) | One context in full — overlay, resolution, exposure, filtering, certificates | [ADR 0049](../../02-DECISIONS/0049-a-route-is-a-grant.md), [0050](../../02-DECISIONS/0050-reachability-is-a-property-of-the-address.md), [0051](../../02-DECISIONS/0051-the-enrolment-token-carries-the-mesh.md), [0055](../../02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md) | ## Not yet written -- **The remaining contexts.** [ADR 0015](../../02-DECISIONS/0015-mesh-brokers-nodes-host-agents-think.md) - decides the decomposition; `connectivity` is the first written in full - ([`08`](08-connectivity.md)) and the others do not exist yet. The work breakdown says in what - order they are needed. +- **The remaining six contexts.** + [ADR 0055](../../02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md) + settles the list at seven; `connectivity` is the first written in full + ([`08`](08-connectivity.md)) and the other six do not exist yet. The work breakdown says in + what order they are needed. - ~~**Domain grouping outside the core.**~~ **Not needed.** [ADR 0017](../../02-DECISIONS/0017-modules-outside-the-core-are-grouped-by-domain.md) is superseded by [ADR 0044](../../02-DECISIONS/0044-a-module-declares-presence-instantiation-and-exclusion.md):