Reconcile: adopt initialization's consolidated HQ as canonical, re-home this session's new work #24
@@ -0,0 +1,111 @@
|
||||
---
|
||||
status: proposed
|
||||
date: 2026-08-27
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 0044-a-module-declares-presence-instantiation-and-exclusion.md
|
||||
---
|
||||
|
||||
# 54. Things that change together share an authority, not a package
|
||||
|
||||
## Context
|
||||
|
||||
[`how-we-build.md`](../00-META/how-we-build.md) — the constitution, injected wherever work is
|
||||
decided ([ADR 0009](0009-the-mesh-is-governed-by-a-constitution.md),
|
||||
[ADR 0025](0025-hq-is-the-source-of-the-constitution.md)) — carries this rule:
|
||||
|
||||
> ### Group by domain, not by single function
|
||||
>
|
||||
> 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
|
||||
|
||||
**[ADR 0017](0017-modules-outside-the-core-are-grouped-by-domain.md) is superseded**, and
|
||||
[ADR 0044](0044-a-module-declares-presence-instantiation-and-exclusion.md) says the opposite in
|
||||
as many words:
|
||||
|
||||
> The domain module goes with it: **there is no `networking` thing to install, there are
|
||||
> concrete modules named individually.**
|
||||
|
||||
So the governing document instructs agents to do the thing the decision record forbids. This is
|
||||
not a stale citation in a design note — it is
|
||||
[ADR 0040](0040-the-constitution-absorbs-what-is-enforced.md)'s concern running backwards, in the
|
||||
one document whose entire purpose is to be followed.
|
||||
|
||||
**The example makes it concrete.** *"How a node is reachable"* is exactly the case
|
||||
[`08-connectivity.md`](../03-DESIGN/01-to-be/08-connectivity.md) has now designed — and the
|
||||
design resolves it the other way: five responsibilities under one **context**, delivered by
|
||||
individual modules with edges between them. Anyone following the constitution would build the
|
||||
merged `networking` module that 0044 removed and 08 does not have.
|
||||
|
||||
## What was right about the old rule
|
||||
|
||||
The rule is not simply wrong, and replacing it badly would lose something measured.
|
||||
|
||||
[Research 005](../01-RESEARCH/005-domain-grouping/analysis.md) surveyed the whole catalogue and
|
||||
found that reachability is **the only** place where modules genuinely change together under one
|
||||
intent — the proxy with the resolver, the firewall with the overlay, repeatedly. That is a real
|
||||
observation about coupling, and the smell it identifies is real: four things that always change
|
||||
together and cannot be reasoned about separately are not four boundaries.
|
||||
|
||||
**The observation was right and the conclusion was wrong.** Coupling that tight means they share
|
||||
an *authority* — one place that decides for all of them. It does not mean they should be one
|
||||
installable artifact, and merging them into one is how the observation gets acted on badly:
|
||||
`wireguard` and `traefik` are deployed on different sets of nodes, so a module containing both
|
||||
would be assigned where half of it is unwanted.
|
||||
|
||||
## Decision
|
||||
|
||||
**Things that change together share an authority, not a package.**
|
||||
|
||||
Two units, deliberately separate, and conflating them is what ADR 0017 did:
|
||||
|
||||
| | is | example |
|
||||
|---|---|---|
|
||||
| a **context** | the unit of **coherence** — one authority, one store, one set of decisions | `connectivity` decides the overlay, names, routes, filtering and certificates |
|
||||
| a **module** | the unit of **delivery** — assignable, versionable, replaceable on its own | `wireguard`, the resolver, the proxy, the firewall — four, named individually |
|
||||
|
||||
**When several modules always change together, the answer is to name the context that decides for
|
||||
them** — not to merge them. Connectivity is the worked example and the proof: one authority, five
|
||||
responsibilities, four or more separately assigned modules, and no `networking` module anywhere.
|
||||
|
||||
**Relationships are edges, not folders**
|
||||
([ADR 0044](0044-a-module-declares-presence-instantiation-and-exclusion.md)). What grouping was
|
||||
for — finding things, seeing what belongs together — is a tag and a query, neither of which
|
||||
anybody has to keep true by hand.
|
||||
|
||||
### The constitution is amended
|
||||
|
||||
The section *Group by domain, not by single function* is **replaced**, not repaired, and the
|
||||
derived page republished ([ADR 0025](0025-hq-is-the-source-of-the-constitution.md)). The
|
||||
replacement keeps the observation and changes the instruction:
|
||||
|
||||
> ### Things that change together share an authority, not a package
|
||||
>
|
||||
> When several modules always change together under one intent, name the context that decides
|
||||
> for them. Do not merge them: 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. — ADR 0054
|
||||
|
||||
## Consequences
|
||||
|
||||
- **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.
|
||||
- **`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.
|
||||
- **This is the second time a superseded record was found still steering work.** The first was
|
||||
the to-be README citing 0017 for work still to do. Both were found by a review rather than by
|
||||
anything automatic, and nothing stops the third — **a superseded record has no mechanism that
|
||||
finds its live citations.** Worth an issue in its own right.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0044](0044-a-module-declares-presence-instantiation-and-exclusion.md) — what superseded 0017.
|
||||
- [ADR 0025](0025-hq-is-the-source-of-the-constitution.md) — why amending the source is not enough.
|
||||
- [Research 005](../01-RESEARCH/005-domain-grouping/analysis.md) — the measurement the old rule rested on.
|
||||
- [`08-connectivity.md`](../03-DESIGN/01-to-be/08-connectivity.md) — the worked example.
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
status: proposed
|
||||
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.
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
status: proposed
|
||||
date: 2026-08-27
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
supersedes: 0003-the-mesh-database-is-the-source-of-truth.md
|
||||
extends: 0045-a-context-owns-its-store.md
|
||||
---
|
||||
|
||||
# 56. The authority is the control plane, not a database
|
||||
|
||||
## Context
|
||||
|
||||
[ADR 0003](0003-the-mesh-database-is-the-source-of-truth.md) is still `accepted` and still cited
|
||||
as live by two as-is documents. Its decision says:
|
||||
|
||||
> A **single database** holds every binding... The runtime **loads its configuration from that
|
||||
> database at startup** and **falls back to a local cache** when the database is unreachable.
|
||||
|
||||
Every clause has since been decided against, in four separate records, none of which marked it
|
||||
superseded:
|
||||
|
||||
| 0003 says | contradicted by |
|
||||
|---|---|
|
||||
| a **single** database holds every binding | [ADR 0045](0045-a-context-owns-its-store.md) — a context owns its store exclusively; three contexts must move out of the registry database, taking thirteen tables |
|
||||
| the runtime **loads from that database** | [ADR 0037](0037-the-host-applies-it-does-not-decide.md) — the host does not query the mesh database |
|
||||
| — | [ADR 0039](0039-the-link-is-the-security-boundary.md) — a node holds **no credential** to it |
|
||||
| it **falls back to a local cache** | [ADR 0043](0043-a-declaration-is-an-ordered-list-of-owned-resources.md) — the host has **its own store**, which is not a cache of anything |
|
||||
|
||||
The two modules that still made 0003 literally true — `wireguard` and `traefik`, the only direct
|
||||
database connections left — are removed by
|
||||
[ADR 0049](0049-a-route-is-a-grant.md) and [ADR 0050](0050-reachability-is-a-property-of-the-address.md).
|
||||
When those land, **nothing on any node reads the mesh database at all**, and 0003 will describe a
|
||||
mechanism with no remaining implementation.
|
||||
|
||||
**The error underneath is a category error, and it is the same one
|
||||
[ADR 0054](0054-things-that-change-together-share-an-authority.md) corrects elsewhere:** *source
|
||||
of truth* named a **storage location** when what it meant was an **authority**. Once the store is
|
||||
the answer, "which database" becomes the question, and shared schemas follow — which is precisely
|
||||
the thirteen-table tangle ADR 0045 exists to undo.
|
||||
|
||||
## Decision
|
||||
|
||||
> **The control plane is the authority for what runs where. A database is where one context keeps
|
||||
> its state.**
|
||||
|
||||
Three consequences of that sentence, replacing 0003's three clauses:
|
||||
|
||||
- **There is no single mesh database.** Each context owns its store exclusively
|
||||
([ADR 0045](0045-a-context-owns-its-store.md)). "The mesh database" is not a thing that exists;
|
||||
the registry database is `inventory`'s store, and other contexts have their own.
|
||||
- **No node reads any of them.** A node is told what to own, over the link, in a bounded
|
||||
vocabulary ([ADR 0037](0037-the-host-applies-it-does-not-decide.md),
|
||||
[ADR 0039](0039-the-link-is-the-security-boundary.md)). Reading the authority's storage is not
|
||||
how anything learns anything.
|
||||
- **A node runs from its own store, always — not as a fallback.** The host records what it owns
|
||||
and reconciles against it
|
||||
([ADR 0043](0043-a-declaration-is-an-ordered-list-of-owned-resources.md)). That store is the
|
||||
node's own record of what it applied, not a copy of somebody else's state.
|
||||
|
||||
### What survives from 0003, unchanged
|
||||
|
||||
The half that was right, and it is the half everything else cites:
|
||||
|
||||
> **The repository defines what exists. The mesh defines what runs where.** No node-to-module
|
||||
> mapping is ever committed, and a node is described nowhere in source.
|
||||
|
||||
That is what makes the repositories node-agnostic and it is why anything about the mesh can be
|
||||
published at all ([ADR 0019](0019-hq-is-its-own-repository.md)). Nothing here weakens it — this
|
||||
record changes *where the authority lives and how it is reached*, not whether bindings are
|
||||
committed.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **The cache mode disappears, and with it the fault it created.** The as-is records the sharp
|
||||
edge: *"a node running from cache looks identical to a node running from the database. There is
|
||||
no age on the cache and nothing reports divergence, so a node can be running yesterday's
|
||||
assignment set indefinitely without any signal that it is."* Under this record there is no
|
||||
second mode to be mistaken for the first — **a node always runs from its own store**, and
|
||||
whether it has heard from the mesh recently is a separate, reportable fact rather than an
|
||||
invisible one.
|
||||
- **"The mesh database" should stop being said**, including in conversation. It names a thing that
|
||||
will not exist, and it is the phrase that makes a shared schema sound reasonable.
|
||||
- **This closes the set of records that made nodes hold database credentials.** 0037 decided it,
|
||||
0039 named the exposure, 0049 and 0050 remove the two offenders, and this one removes the
|
||||
*record* that still authorised the arrangement — which was the last thing anybody could have
|
||||
cited in its defence.
|
||||
- **Two as-is documents cite 0003 and describe today's behaviour accurately.** They are not
|
||||
wrong and should not be changed: [ADR 0020](0020-design-is-written-in-two-layers.md) keeps the
|
||||
layers separate, and *as-is* describing a superseded decision is exactly what as-is is for. What
|
||||
changes is the citation's status, not its content.
|
||||
- **It does not say how today's mesh gets there.** Thirteen tables move, two modules are rewritten,
|
||||
and nothing here costs that. Research 006 already lists the migration as unaddressed, and this
|
||||
record adds to what must migrate rather than explaining it.
|
||||
|
||||
## References
|
||||
|
||||
- [ADR 0003](0003-the-mesh-database-is-the-source-of-truth.md) — superseded by this.
|
||||
- [ADR 0045](0045-a-context-owns-its-store.md) — the ownership rule this generalises.
|
||||
- [ADR 0037](0037-the-host-applies-it-does-not-decide.md), [ADR 0039](0039-the-link-is-the-security-boundary.md) — why no node reads it.
|
||||
- [ADR 0054](0054-things-that-change-together-share-an-authority.md) — the same category error, corrected elsewhere.
|
||||
Reference in New Issue
Block a user