Sketches the options for issue 010: the mesh's identityLimit (63, postgres's) is not the shortest among the backends the derived name reaches — S3's is 20 — so CheckIdentity lets an over-long access key through and minio fails at provision time. Options: bound by the true minimum and refuse at assignment (recommended, with a compact fallback held in reserve), per-interface bounds, or a provider-generated identity (rejected — breaks "the mesh says the identity once"). Links issue 010 to it. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
6.4 KiB
status, date, deciders, reconstructed
| status | date | deciders | reconstructed |
|---|---|---|---|
| proposed | 2026-09-05 | jochen | false |
54. A consumer's identity is bounded by the tightest backend that must accept it
Context
The mesh says who a consumer is, once, and hands the same name to the provider (to create) and the
consumer (to present), so the two ends agree by construction rather than by two conventions (the
principle behind ConsumerIdentity, 04-ISSUES/023). The name is mesh_<node>_<module>, cleaned to
lower-case letters, digits and underscore.
Proving the provider contract per backend (ADR 0053) turned up 04-ISSUES/010: redis and postgres
create that name verbatim, but minio refuses it — an S3 access key is capped at 20 characters,
and mesh_anchor_bucketuser is 22. The provisioner then retries for ever, per consumer, and the
consumer holding that same too-long name could never present it either.
Two things about the existing derivation decide most of this:
- The charset is already right.
[^a-z0-9_]is deliberately conservative, and its own comment says it reaches "a PostgreSQL role, a MinIO access key, an LDAP uid and a Keycloak client without quoting." That much is true. - The length is wrong.
CheckIdentityrefuses names overidentityLimit = 63, commented as "the shortest identifier limit among the systems these names reach: PostgreSQL's". It is not the shortest — S3's 20 is shorter — so the guard that was meant to catch exactly this lets it through, and the failure lands at provision time as a silent retry instead of at assignment as a refusal.
So this is a small wrong constant with a real cost attached: whatever bound we set, mesh_ (5) plus
a node name plus _ plus a module name has to fit inside it.
The options
A — Bound the identity by the true minimum, and refuse early. Lower identityLimit to the real
shortest (20, S3's), so CheckIdentity refuses an over-long name at assignment with a clear
message, the way it already refuses over-63 names. The derivation does not change; long names are
simply rejected before anything is provisioned.
- For: smallest change; keeps "the mesh says the identity once, verbatim" intact; the failure
moves from a per-consumer provision-time retry to an up-front, legible refusal — which is what
CheckIdentityexists to do. - Against: a hard budget.
mesh_+ node +_+ module ≤ 20 means node + module ≤ 14 characters.anchor+bucketuser(16) is already over. It pushes the constraint onto how machines and modules are named, which is a real limitation on legible names.
B — Keep the readable name when it fits, compact it when it does not. Below the bound, the name
is mesh_<node>_<module> as today; over it, the mesh substitutes a deterministic short form (e.g.
mesh_ + a truncated hash of node+module) — still one derivation, so both ends still agree.
- For: no naming constraint; short backends always satisfied; the common case stays legible.
- Against: some identities become opaque, and a provisioner tracing "whose login is this" loses the answer for exactly the consumers that overflowed. The mesh now owns a fallback format and its collision properties (a truncated hash is not free of collisions at 15 characters).
C — Let each interface declare its identifier bounds, and derive within the tightest a consumer
reaches. s3-bucket states identifier: { max: 20 }; postgres-database states 63; the mesh
derives a name that fits the minimum bound across the providers a given consumer is granted.
- For: the most precise — each provision gets exactly the room it has, and a database consumer keeps long legible names while an S3 consumer gets a short one; the constraint lives where the fact does (on the interface).
- Against: the most work, and a consumer of two interfaces with different bounds must satisfy the smaller — so its name shortens for both, reintroducing B's opacity in a narrower case. It also means one consumer can hold different identities per provision, which the "said once" model currently forbids.
D — Let the provider generate a backend-valid identity and hand it back (rejected). minio mints its own access key and returns it to the consumer. This is the data-provision return path this era keeps meeting — but it directly contradicts 023 and ADR 0053: the identity would no longer be the mesh's single derivation the two ends share, it would be a value one side invents and the other must be told. Listed for completeness; not recommended.
Recommendation
A, with B held as the escape hatch. Fixing the constant is what the code already says it meant to do ("the shortest limit among the systems these names reach"), and moving the failure to an honest assignment-time refusal is strictly better than a silent provision loop. Accept the naming budget first; it is a forcing function toward short node/module names, which the mesh benefits from elsewhere. If the ≤14 budget proves too tight in real use, add B's compact fallback for the overflow case only — keeping the legible name for everyone it fits. Reach for C only if per-interface bounds turn out to matter beyond this one 20-vs-63 gap; it is the right shape but more machinery than the problem currently demands.
Consequences (of A)
identityLimitbecomes 20; the comment stops claiming postgres is the shortest and names S3 as the binding one.CheckIdentityrefuses over-long names atmodule add/ assignment, not at provision.- Existing node/module names longer than the budget must be shortened before they can consume a provision — a migration cost paid once, surfaced as a clear refusal.
- minio (04-ISSUES/010) is unblocked without any provider-specific mapping, and the minio per-backend e2e (skipped, pending this) can run.
- How it is checked: the minio grant e2e — a consumer with a name inside the budget reaches its
bucket with the credential the mesh delivered — and a unit test that
CheckIdentityrefuses a name whose derived identity exceeds 20.
References
- 04-ISSUES/010 — the observation.
- ADR 0053 — a provider creates the credential the mesh minted; the identity it creates it under is the one this decision bounds.
mesh-controlinternal/catalogue/identity.go—ConsumerIdentity,identityUnusable,identityLimit,CheckIdentity— where the constant and the check live.