Files
hq/04-ISSUES/056-an-adopted-module-assigned-to-a-second-node-raises-a-second-server/00-report.md
T
jschoubben 418261213c
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the change's judges the pull requests after it merges; it…
mesh/delivery superseded: a newer head of the same pull request
Remove the decision records and research efforts, which the knowledge base now holds whole (issue 426)
ADR 0290 rule 9: once the import is checked record by record, hq keeps no copy.
The records and efforts are removed; every link or path to them reads "ADR NNNN" or
"research NNN"; a design's decisions: lists "ADR NNNN". 02-DECISIONS keeps a pointer
README with the reading order's six topics, which the knowledge base reads through the
records module; 01-RESEARCH keeps a pointer README. The checks drop the rules that
judged records (the knowledge base holds each at propose; checks/README.md says where)
and refuse a record written here again. Playbooks 01 and 02 and the skills write and
read records with the knowledge base's verbs.
2026-10-11 12:56:09 +02:00

2.8 KiB

opened
opened
2026-09-17

056 — An adopted module assigned to a second node raises a second server

Symptom

The store and broker are adopted in place (ADR 0078, 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). 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.