ADR 0050 (proposed) — model access is vendor-agnostic; amend 14-model-access
Turn the completed vendor-agnostic analysis into HQ design. The model-access provision stays one vendor-blind interface (extends 0024/0027); the vendor-specific lifecycle moves into a per-vendor adapter keyed by the licence's `vendor` field, mirroring registrar-scoped public-dns providers (0044), named at the consumer's real coupling per 0040. The crux is the sealing-vs-central-rotation carve-out: for refreshable-grant vendors only, the manager node holds the refresh token encrypted at rest (a bounded, declared exception), access tokens sealed per holder, refresh stripped on delivery. Static-key vendors keep full sealing. Amend 03-DESIGN/01-to-be/14-model-access.md with the adapter generalisation as a proposed section (prose + diagram, no code); regenerate the decision index. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
@@ -4,7 +4,7 @@ status: in-progress
|
||||
code:
|
||||
- mesh-control internal/licences
|
||||
- mesh-control cmd/mesh-control/licence.go
|
||||
updated: 2026-08-31
|
||||
updated: 2026-09-05
|
||||
decisions:
|
||||
- 02-DECISIONS/0024-model-access-is-a-provision.md
|
||||
- 02-DECISIONS/0009-modules-and-the-graph.md
|
||||
@@ -107,6 +107,66 @@ observability, changing a binding — and the binding is then declared as usual.
|
||||
plainly is what stops the declaration language growing a conditional**, and nothing built here
|
||||
grew one.
|
||||
|
||||
## The vendor-agnostic generalisation (proposed)
|
||||
|
||||
*[ADR 0050](../../02-DECISIONS/0050-model-access-is-vendor-agnostic.md) is proposed and not yet
|
||||
ratified; this section describes what it decides. The section above stands as what is built.*
|
||||
|
||||
What runs is one vendor — the mesh's Anthropic feature. A read-only trace asked whether the
|
||||
`model-access` provision is Anthropic-shaped or genuinely general, and found that the general layer
|
||||
already exists: a licence is a record with a `vendor` and a non-secret `serves`, a holder's
|
||||
credential is sealed per holder, `accept` takes an operator-supplied value and discards the
|
||||
plaintext, and a locally-run model answers at node scope with no licence. None of that names
|
||||
Anthropic. What is Anthropic's is a thin band: the credential is a subscription OAuth grant — an
|
||||
hourly access token and a refresh token — and that shape alone drags central rotation, a
|
||||
refresh-token-stripping delivery, an identity guard and a `utilization%` usage reading behind it.
|
||||
|
||||
**A vendor is an adapter.** The vendor-specific lifecycle moves into a per-vendor adapter selected by
|
||||
the licence's `vendor` field — the same shape as a registrar-scoped `public-dns` provider behind the
|
||||
neutral `public-dns` interface ([ADR 0044](../../02-DECISIONS/0044-a-public-name-is-provisioned-like-any-capability.md)).
|
||||
A consumer still requires `model-access` and never names a vendor; the interface is drawn at the
|
||||
consumer's real coupling — *reach a model* — per [ADR 0040](../../02-DECISIONS/0040-what-a-module-is.md).
|
||||
The field is `vendor` rather than `provider`, because "provider" already means *which node answers a
|
||||
brokered provision* and the two facts must not share a word.
|
||||
|
||||
The adapter declares a `shape` — `static-key` or `refreshable-grant` — and, optionally, the verbs a
|
||||
vendor happens to need: `accept` a supplied key, `refresh` a grant, read an `identity` off the
|
||||
credential to catch a mis-binding, report `usage`, and `deliver` the value. **A static-key vendor
|
||||
implements almost nothing** — a supplied key, sealed to its holders, delivered. The abstraction is
|
||||
built so the common vendor is small and the rare one carries its own weight.
|
||||
|
||||
```
|
||||
requires: model-access (the consumer, vendor-blind)
|
||||
│
|
||||
┌──────┴───────┐
|
||||
│ a licence │ vendor: … serves: base URL, model
|
||||
└──────┬───────┘
|
||||
selected by │ vendor
|
||||
┌─────────────────┼──────────────────────────┐
|
||||
▼ ▼ ▼
|
||||
anthropic-api-key anthropic (another vendor)
|
||||
shape: static-key shape: refreshable-grant
|
||||
accept, deliver accept, refresh, identity,
|
||||
usage, deliver
|
||||
```
|
||||
|
||||
**The crux is one relaxation, stated plainly.** A refreshable credential cannot be both sealed so the
|
||||
mesh cannot read it *and* rotated centrally — rotation needs a readable refresh token, and the working
|
||||
central rotation is the half [ADR 0024](../../02-DECISIONS/0024-model-access-is-a-provision.md) keeps
|
||||
on purpose. So for `refreshable-grant` vendors only, the **manager node holds the refresh token
|
||||
encrypted at rest** — a bounded, declared exception. Access tokens stay sealed per holder, and the
|
||||
refresh token is stripped on delivery, so *a node never holds a refresh token* remains true for every
|
||||
node but the one manager. **Static-key vendors keep the full guarantee**: there is nothing to rotate,
|
||||
so `accept` discards the plaintext and the carve-out never fires — and static-key is the majority. The
|
||||
exception is written down because a relaxed guarantee that is not stated is indistinguishable from a
|
||||
broken one, and it is narrow on three axes at once: refreshable-grant only, the refresh token only,
|
||||
the manager node only.
|
||||
|
||||
Usage is normalised to `(licence, consumer, period, metric, value)` with the raw response kept beside
|
||||
it; the metric is vendor-defined, so no false common unit is forced. Anthropic becomes the first
|
||||
`refreshable-grant` adapter, and `anthropic-api-key` — the same vendor's plain keys — is the
|
||||
`static-key` case that proves the abstraction is more than one vendor in disguise.
|
||||
|
||||
## How it is checked
|
||||
|
||||
In the lab, on real machines, in the order a person would meet it: a consumer is refused with both
|
||||
@@ -114,3 +174,11 @@ candidates named; put on one and still refused because no key exists; the key is
|
||||
input and not echoed; the public half arrives saying it came from a record rather than a machine;
|
||||
the key arrives readable only by that machine — and it is **nowhere in the control plane's own
|
||||
database**, nor in anything that crossed the broker.
|
||||
|
||||
For the generalisation ([ADR 0050](../../02-DECISIONS/0050-model-access-is-vendor-agnostic.md)): a
|
||||
second vendor — `anthropic-api-key`, static-key — is bound to a consumer and exercises the whole path
|
||||
with the carve-out switched off, its key sealed per node and absent from the control plane's database.
|
||||
For a `refreshable-grant` licence, the refresh token is asserted to exist (encrypted) **only on the
|
||||
manager node**, to be **absent from every holder's delivery**, and the delivered credential to be
|
||||
access-token-only; a `static-key` licence stores no refresh token anywhere. A scenario with two
|
||||
vendors confirms each licence's lifecycle runs its own adapter, selected by `vendor`.
|
||||
|
||||
Reference in New Issue
Block a user