Files
hq/04-ISSUES/124-a-consumer-cannot-be-told-what-its-provider-derived/00-report.md
T
jschoubben 0b9fc90885 ADR 0188: a provider declares what it derives for each consumer, and the mesh tells both ends
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.
2026-10-02 21:24:31 +02:00

90 lines
5.7 KiB
Markdown

---
status: resolved
opened: 2026-09-26
located-in: [mesh-controller internal/catalogue, mesh-sdk src/provisioner, mesh-catalog modules/minio]
fixed-by: 02-DECISIONS/0188-a-provider-declares-what-it-derives-for-each-consumer.md
amended-design: 03-DESIGN/01-to-be/27-a-module-requires-the-mesh-resolves.md
---
# 124 — A consumer cannot be told a value its provider derived for it, so it transcribes one
## What was observed
The object-store interface gives each consumer a bucket. The provider **derives** which one: it
takes the login the mesh minted for that consumer and normalises it into a bucket name, then creates,
checks and removes exactly that. Its own comment states the reasoning — derived from the login, so
teardown recomputes it with nothing to persist — and it never reads the bucket a consumer's
definition named.
The consumer still has to tell its own software which bucket to use. It has no way to be told, so
all three consumers wrote the answer down by hand. Two transcribed it correctly. The third named the
predecessor's bucket, while the key the mesh scopes for it admits only the derived one: it would have
authenticated successfully and been refused on every object, which reads like a credential fault and
is not one. That was found by reading, not by running, and it was filed as part of the fix.
## Why the value cannot arrive
A definition may interpolate five things: a secret, a seat, a port, a machine fact, and a **bound**
value — what a provider said a consumer must know. Everything about the arrangement is already
delivered that way: the endpoint, the port, the region, the login itself.
The bound values come from what the provider `serves`, and **`serves` is a literal block in the
provider's definition**: the same values for every consumer. A value derived *per consumer* has no
way into it. Nothing else can carry it either — a provisioner's contract takes the provision and
returns nothing, so what it derived stays inside it.
So the mesh has a value it computed, a consumer that needs exactly that value, and no channel
between them. The gap is not that the answer is unknown; it is that the answer cannot be passed.
## Why it matters beyond this instance
Every interface where the provider names the resource has this shape. A database provisioner that
prefixed database names, a queue provider that scoped vhosts, an object store that derives buckets —
each would force the consumer to reproduce the provider's rule in its own definition. That is a copy
of somebody else's logic, kept in agreement by hand, which is the failure
[issue 119](../119-a-module-definition-decides-where-its-files-live/00-report.md) records for paths
and this one records for names.
It also shifts a rule out of the place that can enforce it. The provider knows its own naming rule
and can refuse a bad one; a transcription in a consumer's definition is a string, and no check
compares it to what the provider will actually create. The one wrong instance was well-formed.
## Open questions
- Should a provider be able to return consumer-visible values from provisioning — the natural
channel, since the provider is what derived them, but it means a grant carries data the provider
wrote rather than only data the mesh minted.
- Or should `serves` be able to say a value is derived from the consumer's identity, with the
mesh performing the derivation — which keeps the provider declarative, and requires the mesh to
know normalisation rules belonging to somebody else's protocol. It already knows one: the identity
limit is 20 characters *because* of what an object store's access key accepts.
- Either way: should a consumer that names a resource its provider will not use be **refused** rather
than ignored? The contributed bucket was read by nothing, and looked authoritative for months.
- What would have caught the wrong instance? A test that resolves a consumer's grant and compares the
bucket in its own configuration against the one the provider would create is a check that could
exist today, for any interface, without the mechanism above.
## Answered, 2026-10-02 — [ADR 0188](../../02-DECISIONS/0188-a-provider-declares-what-it-derives-for-each-consumer.md)
The channel is the provider's own `serves` block, which may now name the consumer the mesh is
serving: `${consumer:as}` and `${consumer:as:dns}`. The mesh fills it once, where it knows who the
consumer is, and delivers the one filled value to both ends — the consumer's binding and its
`${bound:…}` substitutions, and the provider's contributions entry, so a provisioner is told the
name rather than deriving it. Each open question above, answered:
- **Should a provider return values from provisioning?** No. It would make a grant carry data the
provider wrote, make a consumer's declaration wait on its provider's reconcile loop, and put the
rule where nothing can refuse it. The reasoning is in the record.
- **Or should `serves` say a value is derived?** Yes, and the mesh performs the derivation — but it
learns no protocol doing it. The only fact is the identity the mesh itself minted, in one of two
alphabets it already knows.
- **Should a consumer that names the resource be refused?** Yes. A consumer's file that already
contains the value the mesh is about to derive for it is refused at resolution, naming the
placeholder to write instead. That is the check this report asked for, and it is exact rather than
heuristic: a derived value carries the identity minted for this consumer on this machine, which
nothing else would spell out.
minio's `bucketFor` is gone; its manifest serves `"bucket": "${consumer:as:dns}"`. The three
consumers' hand-written bucket names are gone with it — each of them also named the machine the
module happens to run on, which is the second thing wrong with a transcription.