--- status: open opened: 2026-09-04 located-in: [mesh-sdk, mesh-catalog] fixed-by: amended-design: --- # A provider's provisioner seals with a key the mesh has no way to deliver — and does not need to ## What was observed Building the vertical slice for the module runtime (the module runs its own code as its own process under its own account), a **provider** module — one that stands up a per-consumer resource and hands back a credential — was assigned to a node and run as a broker-bound runtime. The runtime hosts the module's provisioner (the sdk's `runProvisioner`), and the harness opens by reading a **seal key** from `$MESH_SEAL_KEY`, failing immediately without one. Every credential it produces for a consumer is sealed to that key with the sdk's symmetric `seal()` (AES-256-GCM, `mesh-sdk/src/primitives/index.ts`) before being written. Nothing in the mesh sets `$MESH_SEAL_KEY`. It is read in exactly two places in the sdk and set nowhere — no manifest, no control-plane code, no host code. So a provider runtime, as delivered, aborts at start-up. The slice proved the mechanism only by setting a lab-local key in the manifest by hand. ## What a trace of the credential path turned up The seal key is not a missing delivery. **The whole symmetric-seal provisioner is orphaned, and it duplicates — badly — a job the mesh already does.** - `runProvisioner` reads request files named `*.grant.json`. **Nothing writes those.** - It writes sealed credential files named `..credential`. **Nothing reads those** — not the host, not the control plane. The host reports applied-resource digests upward and never ships credentials; the control plane has no reference to that filename. - No consumer ever calls the symmetric `unseal()`. Consumers receive **plaintext**. Meanwhile the mesh already carries a provider→consumer credential across nodes, with **no shared key anywhere**: - The control plane mints the password once (`secrets.Make`) and seals it **twice, asymmetrically** — `ForConsumer` to the consumer node's X25519 public key, `ForProvider` to the provider node's (`mesh-control/internal/secrets/seal.go`, `mesh-host/internal/identity/ sealing.go`, NaCl box). - Each host opens its own copy with its own private key on the machine; the plaintext exists only for the length of one function call (`mesh-host/internal/apply/apply.go`, the `${secret:name}` substitution — ADR 0024's "the host is the only thing that ever holds both"). - `serves` carries no credential and says so; `receives`/`bound` tell each side *where* its sealed secret is, never the value. The two models also **contradict** each other. The sdk's `seal()` comment says the key is "a per-node passphrase the host holds"; the host holds no such passphrase — it holds an X25519 private key, and the control plane's own code refuses a shared symmetric key on principle: "a key both ends hold is a key the mesh would have to distribute, which is this problem again one level down" (`secrets/seal.go`). A symmetric `MESH_SEAL_KEY` shared between a provider node and a consumer node is exactly the thing the mesh was built not to have. And the provisioner's model is wrong in a second way: its adapter **generates its own password** (`generatePassword()`) and creates the resource with it — a different password from the one the mesh mints and hands the consumer. Even with a seal key delivered, a consumer would authenticate with the mesh's password against a resource created with the provisioner's. ## Why it matters beyond this instance The module-runtime model is what every provider now follows — a cache, an object store, a database. As written, each carries a provisioner that cannot start (no key), and that, if it did, would create resources with the wrong password and seal them for a reader that does not exist. The rule the design states — "a consumer receives a sealed credential and unseals it" — is enforced by nothing, because no consumer unseals and no shared key exists to unseal with. ## The fix this points to Not "deliver the seal key." **Align the provider with the mesh's existing credential path and delete the symmetric seal:** - The provisioner should stop generating a password and stop sealing. It should read the per-consumer password the mesh already mints and delivers to the provider (`ForProvider`, unsealed onto the machine by the host), and *create the resource with that password*. - The consumer already receives the matching password as plaintext its own host wrote — no change needed there. - `runProvisioner`'s `sealKey`, `seal()`, `writeSealedCredential`, and the `*.grant.json` / `*.credential` file dance come out; what replaces them is a reconcile driven by the contributions file the mesh already writes to the provider's `receives` path. ## Open questions - Does the mesh mint and deliver `ForProvider` for a *served interface* today (redis-cache, postgres-database), or only for the own-secret case the trace followed? Confirm the provider actually receives each consumer's password before reworking the adapter around it. - What is the adapter contract after the change — `create(consumer, password)` rather than `create(grant) -> Credential`? That is a breaking change to the four providers (redis, postgres, minio, umami) and wants an ADR, since it changes what a provider module *is*. - Is any of the symmetric `seal()`/`unseal()` primitive still used for anything legitimate, or does it leave with the provisioner?