From ee7abe0a8e687e1af8267ed31f0a1cc71c723662 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 17 Sep 2026 02:15:08 +0200 Subject: [PATCH] ADR 0079: foundation seats are named after their servers; issue 056 resolved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The store and broker were "one per mesh" by convention only. Each foundation module now claims a mesh-scoped seat named after the server it guards — postgres/mesh-store, lavinmq/mesh-broker — and the controller's seat is renamed the-controller -> mesh-controller so all three follow one rule. The resolver refuses a second holder, closing 056. Glossary, the foundation and installation docs, and the decisions index follow the new name. https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx --- 00-META/glossary.md | 5 +- ...ion-seats-are-named-after-their-servers.md | 63 +++++++++++++++++++ 02-DECISIONS/README.md | 1 + 03-DESIGN/01-to-be/07-the-foundation.md | 3 +- .../01-to-be/21-the-installation-in-full.md | 2 +- .../00-report.md | 20 +++++- 6 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md diff --git a/00-META/glossary.md b/00-META/glossary.md index cba3af2..9fb7ddd 100644 --- a/00-META/glossary.md +++ b/00-META/glossary.md @@ -47,8 +47,9 @@ another — and a mesh you cannot name precisely is a mesh two people describe d - **seat** — a named position at a scope (node / site / mesh) with a **capacity**. A capacity-1 seat is exclusive (one holder); a higher-capacity seat is a **bench** (several holders coexist). - **claim** — a module taking a spot on a seat. `claims: [{name, scope}]` in a manifest. A - mesh-scoped exclusive claim is how the mesh says "there is one of me" — e.g. `mesh-controller` - claims `the-controller`. + mesh-scoped exclusive claim is how the mesh says "there is one of me". A foundation seat is + named after the server it guards: the `mesh-controller`, `postgres` and `lavinmq` modules claim + the `mesh-controller`, `mesh-store` and `mesh-broker` seats ([ADR 0079](../02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md)). - **provision** — a service one module `provides` and others `require`; the mesh resolves a provider and wires the two with an endpoint and a credential. This is separate from seats: a provision is a service you offer, a seat is a slot you occupy. diff --git a/02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md b/02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md new file mode 100644 index 0000000..578c99d --- /dev/null +++ b/02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md @@ -0,0 +1,63 @@ +--- +topic: the tiers +status: accepted +date: 2026-09-17 +deciders: jochen +reconstructed: false +extends: 0078-the-store-and-broker-are-modules.md +--- + +# 79. The foundation seats are named after their servers + +## Context + +[ADR 0078](0078-the-store-and-broker-are-modules.md) settled that the store and broker are the +ordinary `postgres` and `lavinmq` modules, adopted in place on the control-node, and that a mesh +runs **one postgres and one lavinmq**. But that singularity held only by convention: genesis +assigns them to the control-node alone. [Issue 056](../04-ISSUES/056-an-adopted-module-assigned-to-a-second-node-raises-a-second-server/00-report.md) +recorded the gap — a second `assign` to another node finds no container of that name there and +raises a *second* server, holding none of the first's data, and nothing refuses it. + +The mesh already has the mechanism for "there is one of me": a mesh-scoped exclusive **seat**. +[ADR 0077](0077-the-controller-and-the-foundation.md) gave the controller one, which it named +`the-controller`. Nothing gave the store and broker theirs. + +## Decision + +Each foundation module claims a mesh-scoped seat named **after the server it guards**: +`mesh-controller`, `mesh-store`, `mesh-broker`. So the `postgres` module claims `mesh-store` and +the `lavinmq` module claims `mesh-broker`; the resolver refuses a second holder mesh-wide, the same +way it keeps one hub and one controller. A second `assign` is now a refusal at resolution — *one +per mesh* — not a silent second server. + +The controller's seat, which [ADR 0077](0077-the-controller-and-the-foundation.md) named +`the-controller`, is renamed `mesh-controller` under this same rule, so all three foundation seats +follow one convention: the seat is the server. (The module `mesh-controller` and its seat now share +a name, which is the point — there is one of that server, and the seat says so.) + +This does not tie a foundation module to the control-node — a mesh-scoped seat forbids a *second* +holder, not a wrong single one. Adoption still requires the container to already be running where +the module lands, which genesis arranges; the seat closes the "two servers" gap, and the +control-node convention remains what puts the one holder in the right place. + +## How this is checked + +- The resolver's `checkClaims` refuses two holders of a mesh-scoped seat + (`mesh-controller/internal/catalogue/resolve.go`). +- `TestAFoundationModuleCannotBeRaisedOnASecondNode` asserts each foundation module's second + assignment is refused with *one per mesh*. +- The `postgres`, `lavinmq` and `mesh-controller` manifests declare the seat. + +## Consequences + +"One store, one broker, one controller" is now a property the mesh enforces rather than a +convention it hopes for. The silent operational edge that remains — a cross-node consumer +provisioned only when the provider is pushed again — is separate, and tracked as +[issue 057](../04-ISSUES/057-a-cross-node-consumer-is-provisioned-only-when-the-provider-is-pushed-again/00-report.md). + +## References + +- [issue 056](../04-ISSUES/056-an-adopted-module-assigned-to-a-second-node-raises-a-second-server/00-report.md) — the gap this closes. +- [ADR 0077](0077-the-controller-and-the-foundation.md) — named the controller's seat, here renamed. +- [ADR 0078](0078-the-store-and-broker-are-modules.md) — asserted one postgres and one lavinmq, here enforced. +- [`00-META/glossary.md`](../00-META/glossary.md) — seat and claim. diff --git a/02-DECISIONS/README.md b/02-DECISIONS/README.md index 38fb1d4..6db91b6 100644 --- a/02-DECISIONS/README.md +++ b/02-DECISIONS/README.md @@ -108,6 +108,7 @@ python3 00-META/checks/index.py fail if stale - **0074** — [The mesh defines a module protocol; an SDK is an implementation of it](0074-the-wire-is-specified-not-the-types.md) - **0075** — [An artifact store is a provision; a package registry is a different one](0075-two-stores-and-which-provides-what.md) - **0078** — [The store and the broker are ordinary modules](0078-the-store-and-broker-are-modules.md) +- **0079** — [The foundation seats are named after their servers](0079-the-foundation-seats-are-named-after-their-servers.md) ### What runs on them, and how it gets there diff --git a/03-DESIGN/01-to-be/07-the-foundation.md b/03-DESIGN/01-to-be/07-the-foundation.md index 2f15660..f0cb231 100644 --- a/03-DESIGN/01-to-be/07-the-foundation.md +++ b/03-DESIGN/01-to-be/07-the-foundation.md @@ -8,10 +8,11 @@ code: - mesh-catalog modules/postgres - mesh-catalog modules/lavinmq - mesh-lab test/integration/mesh.test.ts (a bare machine becomes a mesh) -updated: 2026-09-16 +updated: 2026-09-17 decisions: - 02-DECISIONS/0004-a-node-and-how-it-joins.md - 02-DECISIONS/0078-the-store-and-broker-are-modules.md + - 02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md - 02-DECISIONS/0077-the-controller-and-the-foundation.md - 02-DECISIONS/0005-the-node-host.md - 02-DECISIONS/0006-the-substrate-and-the-control-plane.md diff --git a/03-DESIGN/01-to-be/21-the-installation-in-full.md b/03-DESIGN/01-to-be/21-the-installation-in-full.md index c4889e2..0e7e170 100644 --- a/03-DESIGN/01-to-be/21-the-installation-in-full.md +++ b/03-DESIGN/01-to-be/21-the-installation-in-full.md @@ -131,7 +131,7 @@ two. |---|---|---|---| | 1 | `postgres` | `postgres-database` | **the controller's own records and every module's.** One server, not two | | 2 | `lavinmq` | `amqp` | the broker every node dials, and what modules are granted vhosts on | -| 3 | `mesh-controller` | *claims* `the-controller` | decides what runs where | +| 3 | `mesh-controller` | *claims* `mesh-controller` | decides what runs where | | 4 | `distribution` | `artifact-store` | what the mesh built, pinned by digest — the module is the software (Distribution), the provision is the job | | 5 | `builder` | — | turns source into artifacts | | 6 | `mesh-tools` | build inputs | the base everything with code compiles against. **Runs nowhere** | diff --git a/04-ISSUES/056-an-adopted-module-assigned-to-a-second-node-raises-a-second-server/00-report.md b/04-ISSUES/056-an-adopted-module-assigned-to-a-second-node-raises-a-second-server/00-report.md index 5088ec3..865175f 100644 --- a/04-ISSUES/056-an-adopted-module-assigned-to-a-second-node-raises-a-second-server/00-report.md +++ b/04-ISSUES/056-an-adopted-module-assigned-to-a-second-node-raises-a-second-server/00-report.md @@ -1,9 +1,9 @@ --- -status: open +status: resolved opened: 2026-09-17 located-in: [mesh-controller, mesh-host] -fixed-by: -amended-design: +fixed-by: mesh-catalog + mesh-controller (the foundation modules claim mesh-scoped seats) +amended-design: 02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md --- # 056 — An adopted module assigned to a second node raises a second server @@ -40,3 +40,17 @@ for. controller refuses to place it on a node whose foundation did not raise that container? - How is "one postgres, one lavinmq" checked, rather than assumed — in the resolver, in `status`, or in a bed that tries the second assignment and asserts the refusal? + +## Resolution (2026-09-17) + +The foundation modules now claim a mesh-scoped **seat** named after the server each guards — +`postgres` claims `mesh-store`, `lavinmq` claims `mesh-broker`, and the controller's seat is +renamed `the-controller` → `mesh-controller` so all three follow one convention +([ADR 0079](../../02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md)). The +resolver's `checkClaims` refuses a second holder mesh-wide — the same mechanism that keeps one +hub and one controller — so a second `assign` is a refusal at resolution (*one per mesh*), not a +silent second server. + +Checked by `TestAFoundationModuleCannotBeRaisedOnASecondNode` (each foundation module's second +assignment is refused) and by the manifests declaring the seat; the two-node adopted-broker bed +stays green with the seats in place, so the claim does not break single-node adoption.