diff --git a/04-ISSUES/161-an-assignment-does-not-record-which-provider-answers-it/00-report.md b/04-ISSUES/161-an-assignment-does-not-record-which-provider-answers-it/00-report.md new file mode 100644 index 0000000..b4c51b0 --- /dev/null +++ b/04-ISSUES/161-an-assignment-does-not-record-which-provider-answers-it/00-report.md @@ -0,0 +1,63 @@ +--- +status: open +opened: 2026-09-30 +located-in: + - mesh-controller cmd/mesh-controller/modules.go (assign takes no provider; pin is a separate, per-machine command) + - mesh-controller internal/inventory (provision_pin keyed by (node, name)) +fixed-by: +amended-design: +--- + +# 161 — An assignment does not record which provider answers it + +## What was observed + +Planning ace's modules that need a database (baserow, letta, n8n, and the apps using ace's +predecessor postgres). The operator's model — and ADR 0110's — is that **an assignment states where +each of its requirements is answered from**: gitea's assignment on novox says its `postgres-database` +comes from novox; an app assigned to ace says whether its database comes from ace or from novox. + +The mesh holds no such statement for any assignment. Read on novox (2026-09-30): + +``` +select … from provision_pin; -- 0 rows +``` + +Every requirement in the mesh resolves implicitly, each time, by ADR 0084's order (a pin, then the +provider on the consumer's own node, then the only provider). + +## What was decided, and what exists + +[ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md): + +> Where several remain and none is local, **a person chooses when the module is assigned**. +> Assignment lists the candidates, with the holder of a seat that delivers the provision suggested +> first, and records the answer on the assignment as its pin. Without an answer the module is not +> assigned. + +What the control plane implements: + +| decided | implemented | +|---|---| +| the answer is recorded **on the assignment** | `provision_pin` is keyed `(node, name)` — one answer per machine per provision, shared by every module on it | +| chosen **at assignment** | `assign ` takes no provider; `pin ` is a separate command | +| an assignment may be answered from its own machine (gitea ← novox) | `pin` refuses a machine pinning to itself ("does not need saying") | +| every assignment has an answer | none recorded; resolution guesses the same answer every time | + +## Consequence + +Nothing is wrong *today* — with one postgres provider, every guess is the intended answer. But the +answer is not a fact anyone stated, so: + +- **it changes silently** the day a second provider appears (e.g. a postgres assigned on ace): every + unpinned consumer re-resolves — a consumer on ace moves from novox's database to an empty one on ace + at the next push, which is data a module stops seeing without anything saying so; +- two modules on one machine cannot take one provision from different providers; +- a person reading an assignment cannot see where its data lives. + +## What would be right + +ADR 0110 as written: `assign` records, per requirement, the node that answers it (its own node +included), offering the candidates and refusing an assignment without an answer where several exist; +the per-machine `provision_pin` becomes a per-assignment record, with existing assignments backfilled +from what they resolve to now so nothing moves.