Issue 009 (fixed) + Issue 008 (resolved via ADR 0053) — module-runtime config & provider contract #21

Merged
jschoubben merged 21 commits from worktree-issue-provider-seal-key into main 2026-09-05 01:02:34 +00:00
Showing only changes of commit 75887d0bc5 - Show all commits
@@ -0,0 +1,63 @@
---
status: open
opened: 2026-09-05
located-in: [mesh-control, mesh-catalog]
fixed-by:
amended-design:
---
# The mesh's derived login does not fit every backend's identity rules — S3 rejects it
## What was observed
Proving the provider/consumer contract per backend (ADR 0053), redis and postgres passed: a
consumer authenticated against the provider with the login the mesh derived and the password
the mesh minted. **minio failed**, and not on the credential — on the *name*:
```
mc: <ERROR> Unable to add a new service account. The access key is invalid.
(access key length should be between 3 and 20).
```
The mesh derives a consumer's login as `mesh_<node>_<module>` — here `mesh_anchor_bucketuser`,
22 characters. That is a valid postgres role and a valid redis ACL user, so those providers
create it verbatim. S3 access keys are capped at **20 characters**, so minio refuses to create
the service account under it, and the provisioner retries forever while the consumer, holding
that same too-long access key, could never present it either.
## Why it matters beyond this instance
ADR 0053 says a provider creates *exactly* the login the mesh derived, so that the two ends
agree by construction — the mesh hands the same name to the provider (to create) and the
consumer (to present). That only holds if the derived name is one every provider can accept.
It is not: the mesh's `as` is a single format with no knowledge of a backend's identity rules,
and S3's are stricter than a database's. Any provider whose backend constrains identifiers more
tightly than postgres — a length cap, a charset, a required prefix — inherits this, and the
failure lands at provision time, per consumer, as an infinite retry rather than a refusal at
assignment.
This also shows the seam is real, not cosmetic: `as` is doing two jobs — a stable per-consumer
identity the two ends must agree on, and a literal identifier a specific backend must accept —
and those are not always the same string.
## The shape of a fix (open, not decided)
- **Constrain the derivation** so `as` is broadly acceptable — short (≤ 20), a conservative
charset, deterministic. This keeps "the provider creates exactly what the mesh derived" true
everywhere, at the cost of a less legible name, and it is a mesh-wide identity change (every
provider that already created the longer name would see it change).
- **Let a provider map `as` to a backend-valid identifier** it derives the same way on create
and on the consumer's behalf — but the consumer is generic and cannot run minio's mapping, so
this only works if the mapped identifier is *delivered back* to the consumer. That is the
data-provision return path this era keeps meeting (umami's siteId, cloudflare's record) and
does not yet have.
- **Declare the constraint on the interface** (`s3-bucket` states its identifier bounds) and
have the mesh derive within them — the most honest, the most work.
## Open questions
- Is `as` meant to be human-legible, or is a short opaque token acceptable — i.e., can the
derivation simply be shortened without anyone minding?
- Do redis/postgres actually want the long name, or did it only survive because they are
permissive? If nothing needs it long, the cheap fix is to cap it.
- Does this fold into the same decision as the data-provision return path, or is it separate?