Issue 124: a value the mesh's own rule produced reached neither end as a statement. The object store's provisioner derived each consumer's bucket in its own code; all three consumers transcribed the rule into their own definitions, one of them wrong, and each of the three also named the machine it happens to run on. A served value may now name the consumer the mesh is serving. Design 27 amended; issue 124 resolved.
8.6 KiB
topic, status, date, deciders, reconstructed, extends
| topic | status | date | deciders | reconstructed | extends |
|---|---|---|---|---|---|
| the mesh | accepted | 2026-10-02 | jochen | false | 02-DECISIONS/0048-a-provider-creates-the-credential-the-mesh-minted.md |
188. A provider declares what it derives for each consumer, and the mesh tells both ends
Context
An arrangement between a consumer and a provider is delivered entirely by the mesh. Where the
provider is, which port it answers on, what name the consumer must present, where its password
is — each arrives as a fact the consumer reads from its binding, or as ${bound:…} filled into a
file before the declaration leaves the control plane. The provider invents none of it and hands
none of it back (ADR 0048).
One kind of value escapes that. Where the provider names the resource — a bucket, a database,
a vhost — the name is derived from the consumer, per consumer, and the mesh has no way to carry
it. serves is a literal block in the provider's definition: the same values for every consumer.
A provisioner's contract takes a provision and returns nothing. So a value the mesh's own rule
produced reaches neither end as a statement; it is recomputed at one end and transcribed at the
other.
The object store is the instance (issue 124). Its provisioner normalises the login the mesh minted into a bucket name and creates, checks and removes exactly that; the rule lives in twenty lines of the module's own TypeScript. Its three consumers each write the answer into their own definition by hand. Two transcribed it correctly; one named a predecessor's bucket, and would have authenticated successfully and been refused on every object, which reads like a credential fault and is not one.
Even corrected, the transcriptions are wrong in a second way. Each is mesh-<node>-<slug>, so
each names the machine the module happens to run on today — a definition stating a fact about
one installation, which ADR 0155
forbids and whose check does not catch because the name is not a domain. Move any of the three to
another machine and its configuration points at a bucket its key cannot open.
The shape is not the object store's. A database provisioner that prefixed names, a queue provider that scoped vhosts, any provider that derives a resource from who is asking: each forces the consumer to reproduce somebody else's rule and keep it in agreement by hand.
Decision
1. A served value may name the consumer the mesh is serving. A serves block, which is
literal today, may interpolate the mesh's own statement of who the consumer is:
${consumer:as}— the identity the mesh minted for this consumer, exactly as the login it is told to present (ADR 0049);${consumer:as:dns}— the same identity written as a DNS label.
Nothing else. The mesh learns no protocol here; it spells its own name in an alphabet it already
knows. The identity is the mesh's, minted by the mesh, already capped at twenty characters
because of what an S3 access key accepts; dns is that same name with its separator written -
instead of _, which is the whole of the difference between the mesh's identifier alphabet and
the one buckets, vhosts and hostnames use. A provider that needs a prefix or a suffix writes it
around the placeholder, because a served value is a string.
The rejected alternative is the provider returning values from provisioning — the natural channel, since the provider is what derived them. It is rejected for three reasons, in order of weight. It inverts the delivery the mesh is built on: a grant would carry data the provider wrote rather than only data the mesh minted, and ADR 0048 removed exactly that second path once already. It makes a consumer's declaration incomplete until its provider's reconcile loop has run, so a consumer could not be composed before a provider answered — a bootstrap order the mesh does not have and does not want. And it puts the rule where nothing can check it: a value that arrives from a running process cannot be refused at resolution, only discovered wrong later, which is the failure this record exists to end.
2. The mesh resolves it once, per consumer, and tells both ends from the one resolution. At the moment a consumer's declaration is composed, the mesh knows exactly who the consumer is. There, and only there, the placeholders are filled. The result reaches:
- the consumer, as the served facts in its binding file and as
${bound:<provision>:<key>}in any file it writes — unchanged mechanisms, carrying one more key; - the provider, as
serveson that consumer's entry in its contributions file, so the provisioner is told the name rather than recomputing it.
The provider stops deriving in code and starts declaring. One statement, filled once, delivered to both ends: the two cannot disagree, because there is no second computation to disagree with.
3. A served value stays settled before it is per-consumer. Settings still compose into serves
(ADR 0174), and the consumer
placeholders are filled after that, so an operator may set a prefix and the mesh still derives the
rest. A ${consumer:…} naming a fact or an alphabet the mesh does not have is refused when the
definition is parsed, with what it may say.
4. A consumer may no longer name the resource its provider derives. With the value delivered, a literal in a consumer's definition is not merely redundant — it is the one thing that can disagree with what the provider will actually create. The three object-store consumers lose their hand-written bucket names in this change.
Consequences
- One more thing a definition may say, and one less thing a module may be wrong about. The vocabulary grows by a placeholder; the catalogue loses three literals that named this installation's control node.
- A provider's naming rule becomes readable in its definition instead of in its source.
minio'sbucketForgoes; the manifest says"bucket": "${consumer:as:dns}"and the provisioner uses what it is given. - A provider that already serves consumers keeps serving them: the derived value equals what the code derived, so no bucket, database or login changes name. This is a change of who says it, not of what is said.
- The mesh now holds a rule in another system's alphabet — one rule,
dns, stated once. A second alphabet is a decision, not an addition: the cost of each is that the mesh must be right about somebody else's naming, and that cost is only worth paying where the mesh already mints the name.
How this is checked
- A served value naming an unknown fact or alphabet is refused at parse, with the list of what it may say — tested on both halves of the message.
- Resolving a consumer whose provider derives a value puts that value in the consumer's binding
file, in its
${bound:…}substitutions, and in the provider's contributions entry for that consumer — one test asserting the three agree, because agreeing is the whole point. - Two consumers of one provider on one machine get two different derived values, and neither gets the other's.
- A catalogue-wide test refuses a consumer definition that writes a literal where its provider
derives: the provider's
servesnames the key, so the catalogue can say which definitions transcribe one. dnsis checked against the identity the mesh actually mints, not against an invented string: the test derives an identity withConsumerIdentityand asserts the label it becomes.
References
- issue 124 — a consumer cannot be told a value its provider derived for it
- ADR 0048 — a provider creates the credential the mesh minted, and seals nothing
- ADR 0049 — a consumer's identity fits the tightest backend
- ADR 0174 — a node varies a module through settings and kept regions, never through an edit
- ADR 0155 — a definition names no installation, and how that is checked
- design 27 — a module requires, the mesh resolves