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 new file mode 100644 index 0000000..ad7b201 --- /dev/null +++ b/02-DECISIONS/0054-things-that-change-together-share-an-authority.md @@ -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. 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 new file mode 100644 index 0000000..aeb4b04 --- /dev/null +++ b/02-DECISIONS/0055-the-control-plane-is-the-node-coordinating-contexts.md @@ -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. 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 new file mode 100644 index 0000000..67fafd7 --- /dev/null +++ b/02-DECISIONS/0056-the-authority-is-the-control-plane-not-a-database.md @@ -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.