|
|
|
@@ -0,0 +1,148 @@
|
|
|
|
|
---
|
|
|
|
|
topic: what runs on it
|
|
|
|
|
status: accepted
|
|
|
|
|
date: 2026-10-06
|
|
|
|
|
deciders: jochen
|
|
|
|
|
reconstructed: false
|
|
|
|
|
extends: 02-DECISIONS/0049-a-consumers-identity-fits-the-tightest-backend.md
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
# 225. A consumer's identity is bounded by the provision it requires, judged before merge, and never refuses its provider
|
|
|
|
|
|
|
|
|
|
## Context
|
|
|
|
|
|
|
|
|
|
[ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md) bounds every consumer's identity,
|
|
|
|
|
`mesh_<machine>_<slug-or-module>`, by the tightest backend anywhere in the mesh: an S3 access key's 20
|
|
|
|
|
characters. It chose that over per-interface bounds (its option C) because one constant unblocked the
|
|
|
|
|
object store, and named C as the refinement "if non-S3 consumers are paying for S3's limit often
|
|
|
|
|
enough to mind". [Issue 263](../04-ISSUES/263-every-consumer-pays-for-the-tightest-backends-name-limit/00-report.md)
|
|
|
|
|
is that point. The operator: "the 20 character limit has bitten us multiple times".
|
|
|
|
|
|
|
|
|
|
What happened on 2026-10-06, and what it shows:
|
|
|
|
|
|
|
|
|
|
- **The bound applied where it meant nothing.** A change made the network-manager modules require the
|
|
|
|
|
mesh's resolver provision. The resolver mints no credential and keeps no name: its provider is not
|
|
|
|
|
even told who its consumers are (it receives nothing). `networkmanager`'s identity, 23 to 26
|
|
|
|
|
characters on real machine names, was refused all the same.
|
|
|
|
|
- **It was found late and far from its cause.** The catalogue's module check and the controller's
|
|
|
|
|
tests passed. ADR 0049 says the refusal comes at assignment; this was a new requirement on modules
|
|
|
|
|
already assigned, so no assignment saw it. It surfaced when the provider composed its grants.
|
|
|
|
|
- **It refused the wrong machine, wholly.** The controller judged the identity while composing the
|
|
|
|
|
*provider's* declaration, and one refusal there fails the whole composition. The anchor holds the
|
|
|
|
|
resolver, so the anchor — every module on it — could not be pushed until a slug was changed
|
|
|
|
|
elsewhere.
|
|
|
|
|
|
|
|
|
|
The catalogue's providers keep very different names. Read from each provider's code: the object store
|
|
|
|
|
keeps the identity as an access key (20) and a bucket name; PostgreSQL as a role and a database (63);
|
|
|
|
|
MongoDB as a database (63); SQL Server as a login and a database (128); the identity provider as a
|
|
|
|
|
client id (255); the forge as a user name (40); the mail server as a mailbox's local part (64); the
|
|
|
|
|
public DNS provider as a label (63); the cache, the vault, the message broker and the time-series
|
|
|
|
|
store as names with no limit worth stating. The resolver and both route providers keep no name of
|
|
|
|
|
their consumers at all.
|
|
|
|
|
|
|
|
|
|
## Considered Options
|
|
|
|
|
|
|
|
|
|
1. **Keep one bound, raise or lower it.** Any single number is wrong for most provisions: 20 refuses
|
|
|
|
|
a database consumer for an object store's key, 63 lets the object store fail at provision time
|
|
|
|
|
again ([issue 034](../04-ISSUES/034-mesh-login-exceeds-s3-access-key-limit/00-report.md)). Rejected —
|
|
|
|
|
it is the cause.
|
|
|
|
|
2. **Per-interface bounds in a mesh-wide table of provisions.** Puts the fact in the controller, which
|
|
|
|
|
would then know what an S3 key is. The mesh is name-agnostic about what a provider does with an
|
|
|
|
|
identity (`ConsumerIdentity`); a table would make it learn every backend. Rejected.
|
|
|
|
|
3. **Each offer states its own bound; the bound a consumer meets is that of the provision it
|
|
|
|
|
requires** (ADR 0049's option C, placed where [ADR 0202](0202-a-provider-declares-what-it-derives-for-each-consumer.md)
|
|
|
|
|
already places what a provider derives: in its own definition). Chosen.
|
|
|
|
|
4. **Derive a different identity per provision** (C's own "against": one consumer, several names).
|
|
|
|
|
Not needed: the identity stays one name, said once, and must fit every provision the module
|
|
|
|
|
requires. Only the *bound* is per provision. Rejected as unnecessary.
|
|
|
|
|
|
|
|
|
|
And for where the refusal lands:
|
|
|
|
|
|
|
|
|
|
5. **Keep refusing the provider's composition.** Rejected — it makes one consumer's name a reason
|
|
|
|
|
no push reaches a machine that did nothing wrong.
|
|
|
|
|
6. **Refuse the consumer's whole machine.** Right machine, still too wide, and still late. Not chosen;
|
|
|
|
|
the consumer is named instead, and refused in its own pull request (below).
|
|
|
|
|
|
|
|
|
|
## Decision
|
|
|
|
|
|
|
|
|
|
**1. An offer states the longest consumer identity its backend keeps.** In the provider's
|
|
|
|
|
definition, beside the provision: `identity` with a `max` and the words for what keeps it (`in`), so a
|
|
|
|
|
refusal can say "an S3 access key keeps 20"; `in` alone for a backend with no limit worth stating; or
|
|
|
|
|
`false` for a provision that keeps no name derived from its consumer. The bound applied to a consumer
|
|
|
|
|
is that of the provision it requires, from the offer of the module answering it — not the tightest
|
|
|
|
|
backend in the mesh.
|
|
|
|
|
|
|
|
|
|
**Unsaid, the bound follows from what the provider is told.** A provider that receives the provision,
|
|
|
|
|
or serves its consumers a value built from their identity, is told who each consumer is and may make
|
|
|
|
|
a name of it in a backend nobody measured: it keeps ADR 0049's 20. A provider told neither keeps
|
|
|
|
|
nothing of its consumers: no bound. A provider whose definition is not at hand is held to 20.
|
|
|
|
|
|
|
|
|
|
**2. An overflow is refused before merge.** The catalogue check (`module check`, and the controller's
|
|
|
|
|
test over the real catalogue) judges every module's identity, built on the longest machine name of
|
|
|
|
|
the mesh, against the bound of every provision it wants that a module in the catalogue offers — the
|
|
|
|
|
tightest where several offer it. The pull request that introduces an overflow — a new requirement, a
|
|
|
|
|
lowered bound, a longer module name — is the one that fails, naming the module and the longest slug
|
|
|
|
|
that would fit. The longest machine name is a parameter of the check; its default is the mesh's own
|
|
|
|
|
longest, raised in the same change that names a longer machine.
|
|
|
|
|
|
|
|
|
|
**3. One consumer's identity never refuses its provider's machine.** When the provider's declaration
|
|
|
|
|
is composed, a consumer whose identity overflows the bound is left out of the grants and returned
|
|
|
|
|
beside them; every other consumer is granted and the declaration composes. The consumer is named
|
|
|
|
|
where an operator looks: on the push and plan of the provider, on the plan of the consumer's own
|
|
|
|
|
machine, and in `status` (and its document), which does not call the mesh well while one stands.
|
|
|
|
|
|
|
|
|
|
**4. An identity served as a DNS label stays inside one.** The mesh writes an identity into a label
|
|
|
|
|
without truncating it ([ADR 0202](0202-a-provider-declares-what-it-derives-for-each-consumer.md)),
|
|
|
|
|
so an offer that serves `${consumer:as:dns}` must bound its consumers at 63 or less.
|
|
|
|
|
|
|
|
|
|
What does not change: the identity is still derived once and said to both ends (ADR 0049, issue 023);
|
|
|
|
|
the slug is still the remedy; truncation and hashing are still refused.
|
|
|
|
|
|
|
|
|
|
## Consequences
|
|
|
|
|
|
|
|
|
|
- A consumer of a database or the identity provider keeps a legible name on a long machine name; only
|
|
|
|
|
a consumer of the object store still needs a slug of a few characters. Requiring a keyless
|
|
|
|
|
provision costs nothing in name length.
|
|
|
|
|
- The catalogue's providers each state a bound in their offer, and the check reads it there. A new
|
|
|
|
|
provider that is told its consumers and says nothing keeps the old 20: safe, and visible in review.
|
|
|
|
|
- A consumer left out of the grants holds a binding and credential its provider never created. It
|
|
|
|
|
fails to authenticate; that is reported by name in `status` until a slug fixes it, rather than
|
|
|
|
|
hidden behind a machine nobody could push.
|
|
|
|
|
- The check's default machine-name length is a fact about one mesh carried in code. A mesh that
|
|
|
|
|
names a longer machine must raise it, or pass its own to `module check`; nothing reminds it to.
|
|
|
|
|
- Old controllers refuse the new field (manifests are parsed strictly), so the controller ships before
|
|
|
|
|
the catalogue that states bounds.
|
|
|
|
|
- ADR 0049's consequence "`identityLimit` becomes 20 … `CheckIdentity` refuses at `module add` /
|
|
|
|
|
assignment" now holds only for a provision that keeps 20, and is checked before merge and reported
|
|
|
|
|
at composition rather than refused at assignment. ADR 0049 carries a note saying so.
|
|
|
|
|
|
|
|
|
|
**How each rule is checked.**
|
|
|
|
|
|
|
|
|
|
- *Rule 1:* catalogue unit tests — an offer's stated bound is the one applied; an unstated one is 20
|
|
|
|
|
for a provider told its consumers and none for one told nothing; `false` is none; the field parses
|
|
|
|
|
strictly and a bound without `in`, or shorter than any identity, is refused when the definition is
|
|
|
|
|
parsed. Over the real catalogue: the resolver provision bounds nothing and the object store 20.
|
|
|
|
|
- *Rule 2:* `module check` refuses a module whose identity overflows what it requires, naming the
|
|
|
|
|
slug length, and passes a long name requiring a keyless provision; a test runs the same judgement
|
|
|
|
|
over every manifest in the real catalogue on the default machine-name length, so the catalogue's
|
|
|
|
|
own pull request fails on an overflow.
|
|
|
|
|
- *Rule 3:* a controller test against a real store reproduces the night it was found — the network
|
|
|
|
|
manager, no slug, on a six-character machine, requiring the resolver provision, beside a consumer
|
|
|
|
|
that overflows an object store: the provider's declaration composes, the network manager is granted,
|
|
|
|
|
the overflowing consumer is left out, named by the push, carried by `status` and its document, and
|
|
|
|
|
the mesh is not called well.
|
|
|
|
|
- *Rule 4:* a test that a DNS-label identity under a bound over 63 is refused when the definition is
|
|
|
|
|
parsed.
|
|
|
|
|
|
|
|
|
|
## References
|
|
|
|
|
|
|
|
|
|
- [Issue 263](../04-ISSUES/263-every-consumer-pays-for-the-tightest-backends-name-limit/00-report.md) —
|
|
|
|
|
the observation.
|
|
|
|
|
- [ADR 0049](0049-a-consumers-identity-fits-the-tightest-backend.md) — the bound this refines; its
|
|
|
|
|
option C.
|
|
|
|
|
- [ADR 0202](0202-a-provider-declares-what-it-derives-for-each-consumer.md) —
|
|
|
|
|
a provider declares what it derives; the bound is declared beside it.
|
|
|
|
|
- `mesh-controller` `internal/catalogue/identity.go` (`IdentityBound`, `CheckIdentityWithin`,
|
|
|
|
|
`IdentityProblems`, `Resolution.Overflowing`), `manifest.go` (`Offer.Identity`,
|
|
|
|
|
`IdentityBoundOf`), `cmd/mesh-controller/plan.go` (`grantsFor`), `check.go`, `status.go`.
|
|
|
|
|
- `mesh-catalog` — each provider's offer states its bound.
|