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 new file mode 100644 index 0000000..5088ec3 --- /dev/null +++ b/04-ISSUES/056-an-adopted-module-assigned-to-a-second-node-raises-a-second-server/00-report.md @@ -0,0 +1,42 @@ +--- +status: open +opened: 2026-09-17 +located-in: [mesh-controller, mesh-host] +fixed-by: +amended-design: +--- + +# 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?