Issue 008's trace confirmed the premise in control-plane code: the mesh already mints one password per consumer/provider pair and delivers the provider its copy (SecretFor/SecretsFrom/grantsFor -> Grant.Sealed; the receives contribution carries As + Secret). The provisioner's symmetric seal is an orphaned, contradictory second model. ADR 0053 corrects the provider contract in one place (the sdk harness): providers create the resource with the mesh-supplied login and password and drop seal/key/return entirely. Reframe 008 as contract-first (every provider, not four). Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
7.3 KiB
status, date, deciders, reconstructed
| status | date | deciders | reconstructed |
|---|---|---|---|
| proposed | 2026-09-05 | jochen | false |
53. A provider creates the credential the mesh minted, and seals nothing
Context
A provider module stands up a per-consumer resource — a database, a cache bucket, an object store user — and the consumer must end up holding a credential that authenticates against it. Building the module runtime (ADR 0052: a module runs its own code as its own process under its own account), the provider's provisioner was run for the first time as a delivered thing, and it did not work. It reads a seal key from the environment that nothing sets, and it seals every credential it produces to that key with a symmetric passphrase.
Tracing the credential's path turned up something larger than a missing key. The provisioner harness the whole catalogue is built on describes a credential flow the mesh does not have, and duplicates — incorrectly — one it does.
What the sdk's runProvisioner does today:
- reads request files named
*.grant.json— which nothing in the mesh writes; - calls an adapter whose
creategenerates its own password and returns it; - seals that password with a symmetric key (
$MESH_SEAL_KEY) and writes a*.credentialfile — which nothing in the mesh reads, and no consumer ever unseals.
What the mesh already does, and has wired end to end:
- The control plane mints one password per (consumer, provider) pair (
Inventory.SecretFor→secrets.Make) and seals it to both node keys asymmetrically — a copy the consumer's host can open and a copy the provider's host can open. No shared symmetric key exists anywhere, on purpose: a key both ends hold is a key the mesh would have to distribute, which is the same problem one level down, and the control plane's own code refuses it. - The provider is handed, at the path its
receivesnames, one contribution per consumer: the login to create (As, derived by the mesh so the two ends agree by construction), the consumer's address and requested values, and aSecretfile holding that consumer's password sealed to the provider and unsealed onto the machine by its host. - The consumer is handed the same password, as plaintext its own host wrote by unsealing its copy and substituting it into a config file. The consumer never unseals anything itself and holds no key.
So the password a provider's provisioner invents is not even the password the consumer was given: a consumer authenticating with the mesh's password against a resource the provisioner created with its own would simply fail. The symmetric seal is not an incomplete feature to finish delivering a key for. It is a second, contradictory credential model bolted beside the real one, and it cannot be made to work without building the very thing the mesh was designed not to have.
This is a decision and not a patch because the harness is the provider contract. Every
provider — the four that exist and the many a real mesh grows — is built on
runProvisioner(resource, adapter). Whatever it says a provider is, they all inherit; and
changing it later is one migration per provider. It is cheaper and more honest to settle what
a provider is now.
Decision
A provider is handed the credential; it does not make one, does not seal one, and does not hand one back. The provisioner's only job is to make the mesh's grants true in its own software.
Concretely, for the sdk harness and the adapter contract:
- The harness reconciles the contributions the mesh delivers to the provider's
receivespath — the list of consumers, each with its login name (As), address, requested values, and the path to its unsealed password (Secret). It does not read*.grant.jsonand it does not write*.credential. - For each consumer present, the harness reads the password from that consumer's
Secretfile and calls the adapter to bring the resource into being under the given login. For each consumer no longer present — the mesh drops it from the contributions file when its consumer goes away — the harness calls the adapter to withdraw it. - The adapter shrinks to the per-software half and nothing else. It is given the login, the
password, and the values, and it makes the resource exist or removes it. It generates no
password, derives no name, seals nothing, and returns no credential:
roughly
create({ as, password, values })andremove({ as }), both returning nothing. $MESH_SEAL_KEY, the symmetricseal()/writeSealedCredentialpath, and the*.grant.json/*.credentialfiles are removed from the provisioning path entirely. The credential reaches the consumer through the mesh's own asymmetric channel, which already crosses node boundaries and holds no shared secret.
Identity stays the mesh's to say. The login the provider creates is the name the mesh derived and gave the consumer to present; the provider never invents a name, because a name the consumer cannot learn is a name it cannot authenticate with.
Consequences
- A provider module becomes smaller and unable to be wrong in this way: with no password to generate and no key to seal to, the class of bug where the two ends hold different secrets cannot be written. A provider added after this inherits the corrected contract and has no seal to reintroduce.
- The four current providers (redis, postgres, minio, umami) each lose their
generatePassword- seal code and gain a
createthat takes the password it is given. Their teardown becomes "withdraw the login namedAs".
- seal code and gain a
- The symmetric
seal()/unseal()primitive loses its only caller. If nothing else uses it, it leaves with the provisioner; that is checked, not assumed. - How this is verified: a provider is assigned in the lab, a consumer is granted its
resource, and the consumer authenticates against the provider using only what the mesh
delivered — no
$MESH_SEAL_KEYset anywhere, no.credentialfile written. The trail is the consumer connecting; a provider that invented its own password fails it. This replaces the audit-logger/redis lab beds' current use of a hand-set lab seal key, which existed only to get the orphaned harness to start. - Teardown beyond "remove the login" — data an object store leaves behind when a consumer leaves — is named by each provider's adapter, not by the harness, and is out of scope here except to say the contract must leave room for it.
References
- 04-ISSUES/008 — the observation and the cross-repo trace this decision rests on.
- ADR 0052 (the module runtime) — what first ran a provider's provisioner as a delivered process and exposed this; link to be filled when 0052 lands on the trunk.
- Control-plane mechanisms this relies on already existing:
mesh-control—internal/inventory/secrets.go(SecretFor,SecretsFrom),internal/secrets/seal.go(Make, the two-blob asymmetric sealing),cmd/mesh-control/plan.go(grantsFor, theGrant.Sealed = ForProviderdelivery),internal/catalogue/declaration.go(thereceivescontribution:As,At,Values,Secret). - The path being removed:
mesh-sdk—src/provisioner/index.ts(runProvisioner,sealKey,writeSealedCredential) and the symmetricsrc/primitives/index.tsseal()/unseal().