Files
hq/04-ISSUES/056-an-adopted-module-assigned-to-a-second-node-raises-a-second-server/00-report.md
T
jschoubben 94fee5d849 Fix what the records review found
The glossary's authority page still named the controller's seat the-controller in two
entries, contradicting its own seat section after ADR 0079; issue 058's heading kept the
pre-renumber 059; 055's fixed-by named branches that stop existing after merge (now merge
commits/PRs) and its located-in listed file paths where the convention wants repos; 056's
located-in named mesh-host, which received no fix, instead of mesh-catalog; and the design
layer never said the one-store/one-broker property is enforced — 07-the-foundation and the
installation table now state the seats.

https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
2026-09-17 22:24:57 +02:00

57 lines
3.2 KiB
Markdown

---
status: resolved
opened: 2026-09-17
located-in: [mesh-controller, mesh-catalog]
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
## Symptom
The store and broker are adopted in place ([ADR 0078](../../02-DECISIONS/0078-the-store-and-broker-are-modules.md),
issue 051): genesis raises `mesh-store`/`mesh-broker` on the control-node, and assigning the
`postgres`/`lavinmq` module there makes the module reconcile the already-running container instead
of raising a new one. Adoption works *because the container is already running on that node*.
Nothing stops the same module being assigned to a **second** node. On a node where no
`mesh-store` is running, the applier finds no container by that name and raises one — a second
postgres, a second broker. The whole point of Phase 3 — "one postgres, one lavinmq" — holds only
by the convention that genesis assigns these modules to the control-node alone; it is not enforced.
## Why it matters
"There is one store and one broker" is stated as a property of the mesh, and a property enforced by
nothing is indistinguishable from a wrong one. A single extra `assign` — the ordinary verb an
operator types — silently produces a divergent second server holding none of the first's data, and
consumers resolved to it get an empty store. The failure is silent and far from its cause.
The exclusive foundation modules are the mesh's clearest case of "there is one of me," yet unlike a
mesh-scoped seat, an adopted module carries no claim that the resolver would refuse a second holder
for.
## Open questions
- Should `postgres`/`lavinmq` claim a mesh-scoped exclusive seat (the way `mesh-controller` claims
`the-controller`), so the resolver refuses a second assignment by the same mechanism that keeps
one controller?
- Or should adoption be explicit — a module that adopts a foundation container declares it, and the
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.