The seats half of to-be 27's review is settled, so the two records it rests on are accepted and the vocabulary catches up: the glossary's *seat* becomes a named role from a closed set, held by an assignment and possibly delivering a provision, and 23 — Choosing a provider gains the seat step in resolution, with ambiguity still refused rather than guessed. Both were held back when 0110 was proposed, because a document may not rest on a record that is not accepted. 26 — The seats moves to in-progress rather than designed: it names the files that implement it, and naming a file claims implementation, which is only defensible once those files are on the owning repositories' main branches. It becomes implemented when mesh-controller #63 and mesh-catalog #69 land. 0112, 0113 and 0114 stay proposed; to-be 27 stays proposed with them.
96 lines
5.8 KiB
Markdown
96 lines
5.8 KiB
Markdown
---
|
|
layer: to-be
|
|
status: designed
|
|
code: []
|
|
updated: 2026-09-25
|
|
decisions:
|
|
- 02-DECISIONS/0084-which-provider-serves-a-consumer.md
|
|
- 02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md
|
|
- 02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md
|
|
---
|
|
|
|
# 23 — Choosing a provider
|
|
|
|
A provision is named for what the consumer's code is coupled to
|
|
([ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md)):
|
|
`postgres-database`, not `database`. That decides *what kind* of provider satisfies a requirement.
|
|
It does not decide *which* provider, and the mesh runs more than one of most kinds.
|
|
|
|
## Why there is a choice at all
|
|
|
|
Node-specific services delivered to the mesh is the design, not an exception. Every control-capable
|
|
node runs its own relational store, its own cache, its own object store; a single node may run
|
|
several relational stores, each raised by the module that needed a particular engine or version.
|
|
The one provision that is single today — the identity provider — already serves applications whose
|
|
home is another node. So for a given provision name there are usually several providers, one per
|
|
node, and they are **not** interchangeable: each holds different data and lives in a different
|
|
place. A consumer bound to the wrong one reads the wrong database or takes a network hop it did not
|
|
need.
|
|
|
|
Naming the kind is therefore only half of "how a consumer gets what it needs". The other half is
|
|
which provider, and it has two shapes: **consume a provider**, or **carry your own**.
|
|
|
|
## Consuming a provider
|
|
|
|
A provider is not a mesh-wide singleton. It is identified by the node it runs on together with the
|
|
module that provides it — a (node, module) pair. A consumer's requirement resolves to one such
|
|
provider, and which one is part of the **assignment**, not the manifest
|
|
([ADR 0046](../../02-DECISIONS/0046-a-module-configuration-is-its-assignments-not-its-manifest.md)):
|
|
the same module, assigned twice, may be served by two different providers.
|
|
|
|
**The default is co-location.** A consumer that names no provider is served by the provider of that
|
|
provision on its own node. This is the ordinary case and is meant to need nothing said — a module
|
|
that wants a database wants, almost always, the database on the machine it runs on. A mesh that
|
|
happens to run exactly one provider of a kind is simply the case where co-location and "the only
|
|
one there is" name the same thing; that is *there happens to be one*, not a mesh-wide scope written
|
|
into the provision.
|
|
|
|
**Coupling to data is named.** The exception to co-location is a consumer coupled to a *particular
|
|
provider's contents*: two modules that must share one database, or a consumer that must reach a
|
|
provider on a different node. That coupling is exactly what may not be guessed, so the assignment
|
|
names the provider. Naming it is also what makes a later move safe — the mesh knows the binding is
|
|
to that provider and not to whichever one is nearest.
|
|
|
|
**A seat names the mesh's one provider of a kind.** Where a seat delivers the provision, its holder
|
|
answers for it when several providers exist and the consumer named none. That is not picking: the
|
|
choice was made once, mesh-wide, by assigning the holder, rather than once per consumer by naming it
|
|
([ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md),
|
|
[26 — The seats](26-the-seats.md)). A named provider still wins over the seat, because a consumer
|
|
coupled to particular contents has said so.
|
|
|
|
**Ambiguity is refused, never resolved by picking.** If several providers of a kind exist, none is
|
|
named, none is co-located, and no seat delivers it, the requirement is unsatisfiable and is refused
|
|
with the candidates shown — the same stance
|
|
[ADR 0027](../../02-DECISIONS/0027-a-provision-names-what-the-consumer-is-coupled-to.md) took
|
|
against a confidently-wrong match, applied to the instance rather than the dialect. A wrong answer
|
|
delivered quietly costs more than a refusal.
|
|
|
|
## Carrying your own
|
|
|
|
A module need not consume a provider at all. It may carry its **own** instance of an engine inside
|
|
its own composition — reachable only on the module's own network, publishing no host port, and
|
|
**not** declared as a provision. Nothing else in the mesh can see it or bind to it, and it cannot
|
|
collide with anything on a well-known port. To resolution it does not exist; it is an internal part
|
|
of the module, like any other container the module runs.
|
|
|
|
This is legitimate but it is the exception, and the design says when: **only when a genuine engine
|
|
fork or a pinned server version makes the shared provider unusable.** A module written against a
|
|
customised engine, or one that needs an extension the node's provider does not carry, has no choice
|
|
but to carry its own. A module that merely pins an old image of an ordinary engine does not — the
|
|
version on a compose file is the *server's*, and the application talks to a newer shared server
|
|
perfectly well once its data is migrated in. The rule is *share by default; embed only when a fork
|
|
or a version forces it*. Most of the per-module stores that exist in the mesh being migrated onto
|
|
are the first kind wearing the second's clothes, and consolidate onto the node's provider.
|
|
|
|
The distinction is worth stating because the two cases look identical from outside — a module with
|
|
a database either way — and the mesh must be able to tell them apart to reason about either. A
|
|
consumed provider is a binding the mesh records, rotates and can move. An embedded instance is a
|
|
private detail the mesh does not manage and must not mistake for a provider.
|
|
|
|
## What is not settled here
|
|
|
|
A provider that moves between nodes must keep its identity, so that a consumer's recorded choice
|
|
does not silently rebind to a different provider that inherited its place. That is a property the
|
|
provider lifecycle must supply, and this document names it as a requirement rather than describing
|
|
its mechanism.
|