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 af5c939d16 - Show all commits
@@ -70,29 +70,54 @@ keeps meeting — but it directly contradicts 023 and ADR 0053: the identity wou
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.
**E — A module (and a node) may declare a short slug; the identity is built from it.** The identity
becomes `mesh_<node-slug|node-name>_<module-slug|module-name>`: where a slug is declared it is used,
otherwise the cleaned name. A slug is a deliberately short, operator-chosen identifier — `kc` for
keycloak, `wkstn` for a workstation. It is optional: short names (`anchor`, `redis`) need none.
- *For:* this is the escape hatch B wanted to be, without the opacity. The name stays legible — a
provisioner can read `mesh_wkstn_kc` and know who is asking — because a person chose it, not a
hash function. And it makes an early refusal *palatable*: if even the slug-built identity overflows,
the refusal points at the slug, a field made for exactly this, rather than at the machine's name.
Both ends still derive it from one declared thing, so they agree by construction.
- *Against:* a new optional manifest field, and someone must pick the slug — but only for names that
would otherwise overflow, and picking a short legible identifier is a better job than being handed
a hash.
## What implementing A revealed
A was tried first. At `identityLimit = 20`, the readable budget is `mesh_` (5) + node + `_` + module
≤ 20, i.e. **node + module ≤ 14 characters** — far tighter than it looked. The catalogue's own
existing tests use `workstation`+`keycloak` (25), which compacts to `mesh_dbbc02f8dde34d3`; common
mesh names (`home-server`, `the-build-node`, `workstation`) blow the budget with any module. So B's
compact fallback would fire for the *common* case, not the rare overflow — which inverts A+B: most
identities would be opaque hashes. A alone (hard refusal at 20) would refuse most realistic names.
This is what moved the recommendation to E: the problem is not the limit, it is that the *readable
name* is the wrong source when it is long, and a slug is a better source than either a hash or a ban.
## 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.
**E, over a per-consumer bound (start with the global minimum, 20).** Build the identity from an
optional slug, keep it when it fits, and refuse at assignment with "declare or shorten `<module>`'s
slug" when it does not — no hash, no lost legibility, and the fix is a first-class field. Set the
bound to the true minimum (20) now; it needs no per-interface machinery to unblock S3, and a module
that consumes S3 simply declares a short slug. Graduate to **C** (per-interface bounds) later if it
turns out that non-S3 consumers are paying for S3's limit often enough to mind — E and C compose:
slugs are the mechanism, per-interface bounds refine where the ceiling sits. **B is dropped**: a
declared slug is a strictly better escape hatch than an opaque hash. **D stays rejected.**
## Consequences (of A)
## Consequences (of E)
- `identityLimit` becomes 20; the comment stops claiming postgres is the shortest and names S3 as
the binding one. `CheckIdentity` refuses over-long names at `module 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 `CheckIdentity` refuses a
name whose derived identity exceeds 20.
- A module manifest gains an optional `slug`; a node may carry one too. `ConsumerIdentity` prefers
the slug over the cleaned name for each half. `identityLimit` becomes 20 (the true minimum), and
`CheckIdentity` refuses at `module add` / assignment — now with a message naming the slug to set.
- The common case stays legible; only names that overflow the budget need a slug, and what they get
is a name a person chose, not a hash.
- Existing modules/nodes whose names overflow declare a slug once — a migration cost paid as a clear
refusal with an obvious remedy, not a silent hash or a silent truncation.
- minio (04-ISSUES/010) is unblocked: an S3 consumer declares a short slug and its access key fits.
- **How it is checked:** the minio grant e2e — a consumer whose (slugged) identity fits reaches its
bucket with the credential the mesh delivered — plus unit tests that a slug is preferred, that an
un-sluggable over-long identity is refused (naming the slug), and that two consumers never collide.
## References