Reconcile: adopt initialization's consolidated HQ as canonical, re-home this session's new work #24

Merged
jschoubben merged 177 commits from reconcile-init-into-main into main 2026-09-05 10:27:11 +00:00
3 changed files with 343 additions and 0 deletions
Showing only changes of commit f49d177a31 - Show all commits
@@ -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.