Files
hq/02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md
T
jschoubben e1f4c7d9e0 Approve 0054-0056, apply them, and fix the two smaller findings
0003 is now superseded by 0056. Nothing is left proposed.

Applied:
- 06 corrected from ten contexts to seven plus the api, each row now stating
  why it passes the more-than-one-node test. work, knowledge and stream are
  named as mesh-hosted rather than dropped; `ai` folds into config; `record`
  is deferred explicitly rather than listed. Its frontmatter now cites 0055.
- how-we-build §4 amended per 0054, and the derived page republished by
  playbook 05.

The sync found the drift the playbook exists to catch: the published §4 and
the source did not say the same thing. The source said "four accidents, not
four boundaries"; the published page said "one intent expressed four times",
and only the published page carried the scope caveat. Same rule, two texts,
already diverging. Verified the republish by reading back -- the new rule is
present and the old section's body returns nothing -- rather than trusting the
success message.

The two smaller findings:
- 0051 separated the transport identity from the declaring authority. It said
  the token carries "an address" and "the identity to expect" without saying
  what the node dials. It dials the broker, so pinning only that would make the
  control plane's authority transitive and let a compromised broker forge
  declarations -- which, since the host applies whatever the link delivers, is
  the whole machine. The token now carries four things, and declarations are
  signed and verified per declaration. Cost recorded: rotating the signing
  identity is fleet-wide.
- 0026 no longer restates 0022's rule about generated views. 0022's own words
  are "prose does not restate status; one place, and two is one too many",
  which is what 0026 was doing to it.
2026-08-27 02:21:34 +02:00

132 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: accepted
date: 2026-08-27
deciders: jochen
reconstructed: false
extends: 0015-mesh-brokers-nodes-host-agents-think.md
---
# 55. The control plane is the node-coordinating contexts, and the rest are hosted
## Context
Three different context lists are in circulation and none of them was decided:
| Where | Count | Named |
|---|---|---|
| [ADR 0015](0015-mesh-brokers-nodes-host-agents-think.md), **accepted** | **nine** | mesh, agents, work, stream, delivery, knowledge, ai, observability, config |
| [`06-the-control-plane.md`](../03-DESIGN/01-to-be/06-the-control-plane.md) | **ten** + `api` | record, inventory, config, connectivity, provisioning, delivery, observability, identity, work, knowledge |
| `01-to-be/README.md` (until today) | **eight** | — |
`06` cites ADR 0015 in its own frontmatter while presenting a list that is not 0015's. And
[research 006](../01-RESEARCH/006-mesh-from-scratch/skeleton.md), which produced the ten, said
plainly what should happen next:
> This is an addition to an accepted record, so it is a **decision, not a drafting choice**. It
> belongs in a new record that extends ADR 0015 — **not written here.**
**That record was never written, and the design used the list anyway.** This is exactly what
`01-to-be`'s own rule forbids — *every statement here traces to a record; nothing arrives by
drafting* — and it is why the count could drift three ways without anybody noticing.
### What changed silently
Reconciling the two lists, the differences are not cosmetic:
| | |
|---|---|
| **added, with reasoning** | `connectivity` (research 006 Move 3; now designed in [`08`](../03-DESIGN/01-to-be/08-connectivity.md) and settled by [ADR 0049](0049-a-route-is-a-grant.md)–[0052](0052-a-filter-rule-names-its-source.md)) |
| **added, argued but open** | `record` — research 006 explicitly leaves *where the record lives* unresolved |
| **split** | 0015's `mesh` became `inventory` + `provisioning` |
| **renamed** | 0015's `agents` became `identity` |
| **dropped with no reasoning at all** | **`stream`** — threads, mentions, messages, meetings, notifications; **`ai`** — provider grants and rotation |
The last row is the finding. Two contexts holding real behaviour vanished between an accepted
record and a design document, and nothing anywhere says they were removed or where their content
went.
## Decision
**Apply `06`'s own test honestly, and it sorts the list for us.**
The test is *everything that needs to know about more than one node.* Run it:
| Context | Needs to know about more than one node? | |
|---|---|---|
| **inventory** | which nodes exist, what is assigned where | **control plane** |
| **config** | derives settings and secrets **onto nodes** | **control plane** |
| **connectivity** | who peers with whom, which node is reachable | **control plane** |
| **provisioning** | grants between modules on different nodes | **control plane** |
| **delivery** | source to artifact **to node** | **control plane** |
| **observability** | health of nodes, including *unreachable for a week* | **control plane** |
| **identity** | credentials delivered per agent's node bindings and modality ([ADR 0015](0015-mesh-brokers-nodes-host-agents-think.md)) | **control plane** |
| **work** | a task does not need to know a node exists | **hosted** |
| **knowledge** | a document does not either | **hosted** |
| **stream** | nor does a message | **hosted** |
| **ai** | a provider licence is a **grant** ([ADR 0005](0005-capabilities-are-provisioned-on-declaration.md)) and its delivery is `config`'s | **folded** |
**So: seven contexts and one interface.**
> **inventory, config, connectivity, provisioning, delivery, observability, identity** — plus
> **`api`**, the one interface every surface speaks to.
**`work`, `knowledge` and `stream` are mesh-hosted applications, not control plane.** They are
first-party, they ship with everything else, and they run on the mesh exactly the way anything
else does. Being ours does not make them infrastructure.
**`ai` is not a context.** A provider licence is a grant like a database or a bucket, and
delivering it is `config`'s existing job. 0015 already did the hard part here by removing the node
licence — *a node holds no licence; an agent holds credentials* — and what remains needs no
authority of its own.
**`record` is deferred, deliberately.** [ADR 0045](0045-a-context-owns-its-store.md) makes it
load-bearing — contexts integrate through it — and research 006 leaves *where it lives* open, on
the grounds that putting it in the substrate risks recreating the circularity the tier design just
removed. Naming it a context here would settle by listing what has not been settled by arguing.
**Seven is the decided count; the record is an eighth question, not an eighth entry.**
## The cost, stated plainly
**A surface composing across this boundary now reads more than one interface.** The board shows
nodes *and* tasks *and* documents; under this decision that is the control plane's `api` plus
work's and knowledge's.
This was raised as an objection before — *that only moves the problem up a layer* — and it
deserves an honest answer rather than a reassurance. The answer is that
[ADR 0045](0045-a-context-owns-its-store.md) already requires it: a surface reads **interfaces,
never stores**, so a board was always going to compose rather than join. What this decision
changes is the *number* of interfaces, not the kind of work. And `06`'s constraint — a single
surface can compose contexts only while one interface sits in front of them — was already written
about the control plane's contexts, and still holds for the seven.
**The alternative is available and should be named:** keep all ten under tier 2 and accept that
*control plane* means *everything first-party*, not *everything node-coordinating*. Rejected
because the node-coordinating test is doing real work elsewhere — it is what justified
`connectivity`, and it is the same test that defines the substrate. A definition that sorts
cleanly in one place and is waved through in another is not a definition.
## Consequences
- **Three lists become one, and it is a decision rather than a draft.** `06` and the to-be README
are corrected to seven, and `06`'s frontmatter stops citing a record it contradicts.
- **`stream` is reinstated, as a hosted application.** It was dropped by accident; this puts it
somewhere on purpose. Its content — threads, notifications, meetings — is real and has to live
somewhere nameable.
- **Tier 4 gains its first named residents.** Until now the tier existed with nothing in it, which
is part of why contexts drifted upward into tier 2 unopposed.
- **The eight-nine-ten drift had no mechanism that would have caught it**, and neither does the
next one. A design document cites decisions in its frontmatter and nothing checks that what it
says matches what they say.
- **Splitting `mesh` into `inventory` and `provisioning` is inherited without fresh argument.**
It is right under [ADR 0045](0045-a-context-owns-its-store.md) — they would own separate
stores — but this record adopts it from research 006 rather than re-deriving it, and that is
worth saying rather than implying it was examined.
## References
- [ADR 0015](0015-mesh-brokers-nodes-host-agents-think.md) — the nine this extends.
- [Research 006](../01-RESEARCH/006-mesh-from-scratch/skeleton.md) — the ten, and the instruction
to write this record.
- [ADR 0045](0045-a-context-owns-its-store.md) — why the boundary costs what it costs.
- [`06-the-control-plane.md`](../03-DESIGN/01-to-be/06-the-control-plane.md) — the document this corrects.