0113 — the plaintext claim was false under its own mechanism: handing a provider's answer to the controller puts every secret on the broker and in the controller in the clear. The provider now seals each secret field itself, to the consumer node's public key the mesh hands it, and the controller carries sealed fields it cannot open. That is stricter than today, where the controller holds every minted credential in the clear. Option 3 (plaintext to the controller) is recorded and rejected. The foundation exception now covers root-secret rotation (0085) and forms like the broker admin's hash, so no phase claims to remove the broker's bootstrap step. To-be 24 and 13 are named among what it amends. 27 — resolution is consistent with 0110: co-location and the only provider apply only where no seat delivers the provision, so an unheld seat is refused even with one provider. The secret-field rule now matches 0086 exactly (a declared env-file, never a container environment value). The seat placeholder is the controller's, and the one module reading it moves to a host port. Contracts are held by the controller and written down in phase 1, so they can be checked; every rule has a check. An operator's secret is still the operator's, with the vault as custodian. Which seats a module holds is listed as not settled. 0110 — the unheld-seat-with-one-provider case and the one-answer-for-everyone rule have checks; the claim about moved manifests is corrected. 26 — the table governs and the code catches up, not the reverse; scope and capacity agree with the glossary; moving a seat is described as it really is today. 0112 — aligned with 27, and lists 0049 and 26 among what it changes. Issue 118 is renumbered 119: another branch took 118 first. 'Control-plane' is gone from 0110 and 0111.
9.6 KiB
topic, status, date, deciders, reconstructed
| topic | status | date | deciders | reconstructed |
|---|---|---|---|---|
| what runs on it | proposed | 2026-09-25 | jochen | false |
113. A provider makes what it provides, and the mesh carries it back to the consumer
Context
ADR 0048 decided that a provider is handed the credential and makes none: the controller mints one per consumer and provider pair, seals it to both nodes, and the provider creates the login under it. It fixed a real fault. The provider harness of the time generated its own password and sealed it with a symmetric key nobody held, so a consumer could never receive what the provider made. Controller-minting worked because it needed no way back from provider to consumer.
It left that way back undecided, deliberately. 0048 says so: "delivering provider-generated data back to a consumer is a return path the mesh does not have and this decision does not build — a separate shape, left to a separate decision." Since then, the missing return path has come up repeatedly:
- Data provisions have nothing to answer with. The analytics provider assigns a site id the consumer needs. The DNS provider registers a name the consumer should be told. Both have no path back, and say so in their code.
- Some contracts need a value the controller cannot make. The broker needs its admin password in a hashed form the mesh's plain secret delivery cannot produce, so a module-specific bootstrap step was written to derive it. That admin is a foundation credential, which genesis makes, so this record does not remove that step. It is the clearest instance of the general problem, though: a contract can require a form only the party that understands the software can produce.
- The vault is a ledger. A module's own secret is "a
secretprovision the controller mints and the vault records" (ADR 0085, as amended). The one module whose job is secrets generates none, and cannot apply a policy (length, form, lifetime) because it never makes one. Every other provider creates what it provides. The vault is the exception.
ADR 0112 proposes that everything a module needs is a requirement answered by a provider against a contract. Under that, "the controller mints this one kind of answer on the provider's behalf" is a special case the model would carry forever.
Considered Options
1. Keep 0048: the controller mints credentials, and data provisions stay without a way back. Rejected. The vault stays a ledger, contracts needing a derived form keep needing bespoke steps, and a data provision stays unable to answer at all.
2. The provider makes the value and hands it to the consumer itself. Rejected. The two may be on different machines with no path between them the mesh has agreed to, and a provider reaching consumers directly is a second delivery system beside the mesh's. 0048's objection, a symmetric key both ends hold, is not what rules this out: node keys are asymmetric, and a provider can seal to a node's public key without sharing anything.
3. The provider makes the value and gives it to the controller in plaintext, which seals and delivers it. Rejected. It works, and it puts every secret in the controller's memory and on the broker in the clear, a surface today's design does not have for provider-side values.
4. The provider makes the value, seals each secret field to the consumer's node itself, and the mesh carries the sealed answer. Chosen. The mesh already tells a provider who each consumer is and where; it also hands it the consumer node's public key. The provider seals to it, and answers over its own scoped account. The controller delivers the sealed fields as it delivers everything else, and can open none of them.
Decision
A provider makes what it provides. Given a consumer, it creates the resource and answers with the fields its contract names: a password, an access key, a site id, a registered name, a hashed admin secret. The vault generates the secrets it provides, to their contract, and rotates them.
The provider seals, and the mesh carries. With each consumer, the mesh hands the provider that consumer node's public key. The provider seals every field its contract marks secret to it, and hands the answer to the controller over its own scoped broker account. The controller delivers the answer as the consumer's resolved values, and can open none of the sealed fields. A provider never reaches a consumer directly. A secret's plaintext exists where it is made, on the provider's machine, and where it is used, on the consumer's, and nowhere between. That is stricter than today, where the controller holds every minted credential in the clear when it makes it.
Who a consumer is stays the mesh's. The login a consumer presents is the mesh's derivation, which both ends agree on by construction (ADR 0049, issue 023). A provider makes what a consumer is given, never what it is called.
Rotation is the provider's act. Asked to rotate, a provider makes a new value and answers again, and the mesh redelivers it. An operator-delivered value (ADR 0092) is still never replaced by the mesh: its provider is the operator.
The foundation is the one exception. The foundation's own credentials, the vault's own access and the mesh's root secrets exist before any provider can answer. The controller mints those: at genesis, and when an operator rotates a root secret (ADR 0085). It seals them to the operator key as today, including any form the foundation's software needs, such as the broker admin's hash. Nothing else is minted by the controller.
What this changes in earlier records
On acceptance, each of these is superseded or amended by this record, not edited:
- ADR 0048 is superseded: a provider no longer receives a minted credential. Its refusal of provider-held symmetric keys stands, and is why option 2 is rejected here.
- ADR 0085 is amended: the vault generates a module's own secret rather than recording one the controller minted. "The vault stores no plaintext, ever" stands. It makes a value, seals it, hands it to the mesh and keeps only what it keeps today.
- To-be 24 and to-be 13 are amended: both describe the controller minting a module's credentials and rotating them. After this, the controller mints only the foundation's, and a provider rotates by answering again.
Consequences
- A consumer waits for its provider. Its requirement is not resolved until the provider has answered, so resolution gains a state, waiting on a provider, that is shown rather than silent. Today a consumer can receive a credential before the resource behind it exists. After this it cannot.
- The provider harness in the SDK changes: an adapter's create answers with its contract's fields instead of returning nothing, and every provider in the catalogue moves to it. This is one migration per provider, the cost 0048 named for changing the contract, and it is paid once.
- The controller gains the return path: receiving a sealed answer on a provider's account and delivering it. Each contribution gains the consumer node's public key. Data provisions gain the same path, so the analytics and DNS providers can finally answer.
- A contract needing a derived form is met by its provider. The broker admin's hash stays with the controller, because that admin is a foundation credential.
- What got harder: a provider that is down cannot hand out credentials, where today the controller could mint one in its absence. That is honest, because a credential for a resource that does not exist yet was never usable, but it moves a failure from later and silent to earlier and visible.
How it is checked
| Rule | Checked by |
|---|---|
| A provider's secret fields are sealed before they leave it | An SDK test: an answer whose contract marks a field secret cannot be handed over unsealed. A controller test: an unsealed secret field in an answer is refused, not delivered. |
| A provider's answer reaches the consumer sealed to its node | A controller test delivering a provider's answer: each secret field opens with the consumer node's key and with no other, the controller's included. |
| A consumer's identity is still the mesh's | A resolution test: the login a consumer presents is the mesh's derivation, whatever the provider answers. |
| A consumer waits for its provider | A resolution test with no answer yet: the requirement shows as waiting on a provider, and nothing is delivered. |
| The controller mints only the foundation's credentials | A controller test: the minting function is reachable only from genesis and from root-secret rotation, and never for a module's provision. |
| The vault generates and rotates | A vault test: a requested secret is generated to its contract, and a rotation answers with a new value. |