ADR 0053 (proposed) — a provider creates the credential the mesh minted, and seals nothing

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
This commit is contained in:
2026-09-05 00:06:50 +02:00
parent 332c334767
commit e27f8b1b7d
2 changed files with 165 additions and 23 deletions
@@ -62,33 +62,55 @@ would authenticate with the mesh's password against a resource created with the
## 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.
This is not a four-module problem. The provider contract lives in **one place** — the sdk's
`runProvisioner(resource, adapter)` harness — and every provider is built on it. Four exist
today (redis, postgres, minio, umami); a mesh of any size ends up with many. Whatever the
provisioner harness does, every present and future provider inherits, so the orphaned
symmetric seal is a fault stamped into the interface, not into four adapters. That also sets
the cost of getting it wrong: a contract N providers depend on is N migrations to change
later, which is the argument for settling it deliberately now rather than patching around it.
As written, each provider carries a provisioner that cannot start (no key), and that, if it
did, would create resources with a password it invented — a *different* password from the one
the mesh minted and handed the consumer — 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: no consumer unseals, and no shared key exists to unseal with.
## The mesh already does this — confirmed
The premise the fix rests on is not a hope; it is in the control plane today. For a served
interface, `Inventory.SecretFor` mints one password per (consumer, provider) pair via
`secrets.Make`, sealing it to **both** node keys — `ForConsumer` and `ForProvider`.
`SecretsFrom(provider)` is documented as "every credential a provider node was issued, so it
can be told what to create," and `grantsFor` (plan.go) hands the provider node one `Grant` per
consumer carrying `Sealed: ForProvider`. The provider receives, at the path its `receives`
names, one `Contribution` per consumer: the login to create (`As`, derived by the mesh so both
ends agree — 04-ISSUES/023), the consumer's address (`At`) and requested `Values`, and
`Secret`, the file holding that consumer's password sealed to this provider and unsealed by
its host. Everything the provisioner needs is delivered. It reads the wrong files
(`*.grant.json`, which nothing writes) and invents a password instead of reading the one in
`Secret`.
## 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:**
A **one-place contract change in the sdk harness**, plus re-pointing today's adapters at it —
not per-provider surgery, and inherited correctly by every provider after them:
- 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.
- `runProvisioner` reconciles the mesh-delivered `receives` contributions (not `*.grant.json`):
for each consumer, create the resource under the login `As` with the password read from the
delivered `Secret` file, for its `Values`; withdraw the login when a consumer leaves the file.
- The adapter stops generating a password and stops returning a credential — it is handed the
name and the password and only makes the resource exist. Roughly `create({as, password,
values})` / `remove({as})`, no return.
- `sealKey`, `seal()`, `writeSealedCredential`, `MESH_SEAL_KEY`, and the `.credential` file
leave entirely; the consumer already receives its copy through the mesh's own channel.
This is proposed as ADR 0053, which defines the corrected provider contract, for ratification.
## 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?
- Does anything legitimate still use the symmetric `seal()`/`unseal()` primitive, or does it
leave with the provisioner?
- On withdrawal the mesh drops the consumer from the contributions file; is "remove the login"
the whole of teardown for every provider, or does an object store (data left behind) need a
policy the contract should name?