Files
hq/03-DESIGN/01-to-be/14-model-access.md
T
jschoubben 0bc4b7774f Record what four more pieces of the mesh became
Rotation and the provisioner contract; model access as a provision answered by
a record, with ADR 0024's other two gaps left as gaps; exposure, which closes
the open question about revoking a route; and the delivery loop, which closes
the gap ADR 0010 left when it replaced a pipeline with a comparison.
2026-08-31 02:56:50 +02:00

98 lines
5.1 KiB
Markdown

---
layer: to-be
status: designed
code:
- mesh-control internal/licences
- mesh-control cmd/mesh-control/licence.go
updated: 2026-08-31
decisions:
- 02-DECISIONS/0024-model-access-is-a-provision.md
- 02-DECISIONS/0009-modules-and-the-graph.md
---
# 14 — Model access
*[ADR 0024](../../02-DECISIONS/0024-model-access-is-a-provision.md) decided it and listed four
things the mesh did not have. Written 2026-08-31, when two of them were built. **The other two are
still gaps and are still written as gaps** — the record's own warning is that pretending otherwise
is how a plan becomes a surprise.*
## What was built
**A licence is a record, and the first provision no machine answers.** Everything else the mesh
brokers is answered by something running on a node. A hosted model is on nobody's machine and is
reached over the public internet, so the rule that refuses two ends sharing no private network —
correct everywhere else — must not apply to it. A machine on no private network at all can hold a
licence, and that is not a special case to remember: it falls out of the answer not being a
machine.
**The name is the operator's.** *The personal account*, *the organisation's account*. Those are
names a person uses, and the mesh uses them too, because the whole point is saying **which one** a
consumer uses — and an anonymous credential hanging off a provider cannot be said. Many to many,
so deliberately **not a claim**: two machines sharing an account is the ordinary case rather than
a collision.
**One provision name for all of them.** A module requires `model-access`, never `anthropic`. A
module that named a provider could not be moved onto a model the mesh runs itself without editing
it — and moving it is the point.
**A model in a machine's own set answers it locally**, and no record is consulted. That is what
makes *the mesh's own model* an ordinary answer rather than a parallel arrangement.
### Accept: taking a value the mesh did not make
Every other credential here the mesh generated, sealed to both ends and discarded. An API key
arrives from a person, and the missing verb was *accept*: **take a value, seal it to each holder,
discard the plaintext.** A mesh that kept operator-supplied keys readably is the arrangement this
project measured and rejected.
**It seals to the holders that exist at that moment**, and this has a consequence that must be
said out loud rather than discovered:
> A consumer put on a licence *after* the key was supplied has no key, and **the mesh cannot make
> one** — it discarded the only copy.
So that state is reported at every point a person could meet it: when the consumer is put on the
licence, in `licence list`, and — decisively — **the declaration is refused** rather than written
without the file. A machine that resolves cleanly and receives nothing fails later, somewhere that
names neither the licence nor the mesh.
**A key is read from a file or standard input, never an argument.** A key on a command line is a
key in shell history and in every process listing taken while it ran. It is never echoed back:
what is stored is unreadable by whoever holds it, the control plane included, and printing it
would put the one copy that matters on a terminal.
## Refusing is felt, and that is the design working
ADR 0024 predicted it: *a mesh holding three ways to reach a model refuses every consumer that has
not said which — which is correct and is a great deal of saying-which the first time.*
It is correct, and correct is not the same as usable. So the refusal names **the candidates and
the exact command**. The difference between a mesh that refuses helpfully and one that merely
refuses is whether anybody can act on it without going and reading something else.
## Still gaps
Unchanged from [ADR 0024](../../02-DECISIONS/0024-model-access-is-a-provision.md), and deliberately
not half-built:
**A consumer that is not a machine.** *This worker uses that licence* is a binding to an agent, not
to a node. What is delivered still lands on a machine; what is **chosen** is chosen per agent, and
the provisions model has no consumer identity other than a node. What exists today is per module
per machine, which is a step toward it and is not it.
**Switching is a reaction, not a declaration.** A licence that hits its limit and must be swapped is
a response to something observed. Expressing it as a declaration would make the declaration mean
*whatever is working right now*, which is not a thing anybody declared. It belongs with
observability, changing a binding — and the binding is then declared as usual. **Saying this
plainly is what stops the declaration language growing a conditional**, and nothing built here
grew one.
## How it is checked
In the lab, on real machines, in the order a person would meet it: a consumer is refused with both
candidates named; put on one and still refused because no key exists; the key is given on standard
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.