Files
hq/02-DECISIONS/0079-the-foundation-seats-are-named-after-their-servers.md
jschoubben ee7abe0a8e ADR 0079: foundation seats are named after their servers; issue 056 resolved
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
2026-09-17 02:15:08 +02:00

3.4 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
the tiers accepted 2026-09-17 jochen false 0078-the-store-and-broker-are-modules.md

79. The foundation seats are named after their servers

Context

ADR 0078 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 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 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 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.

References