Two decisions taken with the author: - Genesis delivers and the vault adopts. The vault cannot run first — it is built on the runtime base the installation makes after the store, broker and controller, and it learns its work over the bus. Genesis generates the foundation's first shared secrets, seals them to the operator key, and delivers them to the vault through the path an operator's value takes; from then on the vault holds and rotates them. This answers ADR 0085's own reason for rejecting vault-only minting, which 0113 now names instead of stepping around. - Rotation re-confirms on every pass. An applier repeats its confirmation until acknowledged, so a lost message costs one pass; an applier that stops after applying locks readers out until its supervised restart, and that window is stated and shown, not claimed away. Fixes: - Scope: a shared secret is made by the vault; a private key (node sealing keys, the operator's key, the certificate authority) is made where it is used. The inventory adds the makers the first version missed: node and builder broker passwords, and enrolment tokens. - Broker accounts are created by the broker's provisioner, not the controller, so the controller never holds their plaintext; mesh-broker delivers amqp again — one broker per mesh — and only mesh-store delivers nothing. - secret is a reserved provision: only the mesh-vault holder may provide it, and no pin routes around it. - A secret's contract says whether a recipient applies it or reads it at start; appliers are never restarted for it, init-only secrets are applied, and confirmation is to-be 13's standard. - Operator secrets are one rule everywhere: a secret requirement answered by the vault (0112 no longer says otherwise). A data provider's adapter may return fields; the data-return check names a lab consumer. - 'Holder' now means a seat's holder only; a secret has recipients.
153 lines
8.5 KiB
Markdown
153 lines
8.5 KiB
Markdown
---
|
|
layer: to-be
|
|
status: in-progress
|
|
code:
|
|
- mesh-controller internal/catalogue/seats.go
|
|
- mesh-controller internal/catalogue/resolve.go
|
|
- mesh-controller cmd/mesh-controller/seats.go
|
|
- mesh-controller cmd/mesh-controller/source.go
|
|
- mesh-controller internal/inventory/migrations/0032-a-source-may-live-on-a-seat.sql
|
|
- mesh-catalog modules/gitea/module.json
|
|
updated: 2026-09-25
|
|
decisions:
|
|
- 02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md
|
|
- 02-DECISIONS/0111-a-build-source-is-on-the-git-seat-or-external.md
|
|
- 02-DECISIONS/0109-a-package-registry-seat-is-one-per-ecosystem.md
|
|
---
|
|
|
|
# 26 — The seats
|
|
|
|
**What a mesh can have one of, and who fills each.** A seat is a named role at a scope, taken by a
|
|
module assignment. The mesh defines which seats exist. Occupying one may deliver a provision, and the
|
|
list of seats with their holders is the quickest answer to "what is in this mesh".
|
|
|
|
## What a seat is
|
|
|
|
A seat has four properties, fixed by the mesh rather than by any module:
|
|
|
|
| property | is |
|
|
|---|---|
|
|
| name | what a manifest claims, and what a person reads in the list |
|
|
| scope | node, site or mesh: where its capacity applies. Every seat in the set has a capacity of one, so one holder per scope. A bench, a seat with several holders, is a word the glossary keeps and no seat uses yet |
|
|
| delivers | the provision its holder answers for, or nothing |
|
|
| decision | the record that made it a seat |
|
|
|
|
**A module assignment holds a seat by claiming it.** The claim is the manifest's `claims`, and it is
|
|
satisfied by assigning the module somewhere. The seat is not a second record beside the assignment.
|
|
It points at the assignment, and everything the mesh knows about the holder is what it knows about
|
|
that assignment: the node, the node's settings for the module, and what the module serves.
|
|
|
|
**The set is closed.** A claim naming a seat the mesh does not define is refused, and so is a claim at
|
|
the wrong scope. Adding a seat is a decision, recorded, for the reason every addition to the host's
|
|
vocabulary is one: the set is what a person reads to learn what a mesh can have, and an entry nobody
|
|
argued for is an entry nobody can explain.
|
|
|
|
## The set
|
|
|
|
| seat | scope | delivers | typically held by |
|
|
|---|---|---|---|
|
|
| `mesh-controller` | mesh | — | the controller |
|
|
| `mesh-store` | mesh | — | the foundation's store |
|
|
| `mesh-broker` | mesh | `amqp` | the broker |
|
|
| `mesh-vault` | mesh | `secret`, reserved | the vault |
|
|
| `the-artifact-store` | mesh | `artifact-store` | the artifact registry |
|
|
| `the-catalogue` | mesh | — | the catalogue |
|
|
| `npm-package-registry` | mesh | `npm-package-registry` | the forge |
|
|
| `git` | mesh | `git` | the forge |
|
|
| `the-build-machine` | node | — | a builder |
|
|
| `the-dns-port` | node | — | the local resolver |
|
|
| `the-intrusion-prevention` | node | — | an intrusion-prevention service |
|
|
| `the-packet-filter` | node | — | the packet filter |
|
|
| `the-private-network` | node | — | the private network the mesh runs over |
|
|
| `the-resolver-configuration` | node | — | whichever of the alternative resolver configurations is chosen |
|
|
| `the-showcase` | node | — | the showcase module |
|
|
|
|
The controller holds this set in code, and a test asserts both its size and that every entry names
|
|
the record that made it a seat. **This table and [ADR 0110](../../02-DECISIONS/0110-a-seat-is-a-module-assignment-from-a-closed-set.md)
|
|
govern, and code that disagrees is what is wrong.** The implementation in progress predates three
|
|
things here: the `mesh-vault` seat and its reservation, and the rule that `mesh-store` delivers
|
|
nothing. It is brought to this table before it merges.
|
|
|
|
## A seat that delivers a provision
|
|
|
|
A seat that delivers a provision may only be held by a module that provides it, at the seat's scope.
|
|
A mesh seat delivers a mesh-scoped provision.
|
|
|
|
**A seat delivers a provision only where the mesh has one answer for everyone.** The broker, the
|
|
artifact store, the npm registry, git and the vault are each one per mesh by decision. The store is
|
|
not: nodes run their own stores and a consumer uses the one on its machine
|
|
([23 — Choosing a provider](23-choosing-a-provider.md)), and the foundation's store is the controller's
|
|
own memory, provider to nobody. So `mesh-store` guards that the foundation's store is singular, and
|
|
routes nobody.
|
|
|
|
**The vault's provision is reserved.** Only the holder of `mesh-vault` may provide `secret` at all: a
|
|
module providing it without the seat is refused, and a pin cannot choose another provider, because
|
|
there is none. A second provider of secrets would be a second place secrets live, which is what the
|
|
vault being one per mesh exists to prevent. Every other delivered provision may have second
|
|
providers, which a pin can choose.
|
|
|
|
**Its holder answers for that provision.** A requirement for it resolves, in order, to:
|
|
|
|
1. the provider the consumer's node was pinned to, because a consumer coupled to one provider's
|
|
contents has said so ([23 — Choosing a provider](23-choosing-a-provider.md));
|
|
2. the holder of the seat, **even when another provider runs on the consumer's own machine**;
|
|
3. otherwise nothing, and the requirement is refused, naming the unheld seat.
|
|
|
|
Co-location, which answers first for every other provision, does not apply here: a seat says which
|
|
one is the mesh's, and co-location answering first would let any second provider on a consumer's
|
|
machine take over for that consumer, silently. So a second provider can run beside the holder and
|
|
harm nothing. The forge holds
|
|
`npm-package-registry`. An npm proxy may provide the same provision on another machine, and a module
|
|
requiring an npm registry is still served by the forge, without anybody pinning it.
|
|
|
|
**Moving the role is changing which module claims the seat, and today that is a definition change.**
|
|
A claim is part of a module's definition, so the proxy's definition must claim the seat and the
|
|
forge's must stop. The forge cannot simply be unassigned, because it holds `git` as well. Every
|
|
consumer follows once the claim moves. Making *which* seats a module holds the assignment's choice,
|
|
with the definition saying only which seats it *can* hold, is the consistent answer, and
|
|
[27 — A module requires, the mesh resolves](27-a-module-requires-the-mesh-resolves.md) lists it as
|
|
not yet settled.
|
|
|
|
**What a consumer receives is a grant**, the same as for any provision: where the provider answers,
|
|
what it serves, and a credential. A consumer never reads the seat directly. The one exception is the
|
|
controller itself, which reaches the store and the broker through a narrow seat placeholder,
|
|
because it made them before any module existed and cannot be their consumer. One foundation module
|
|
also reads it today, to find its own server's port. [27](27-a-module-requires-the-mesh-resolves.md)
|
|
moves that to a host port requirement.
|
|
|
|
## A seat that delivers nothing
|
|
|
|
Most node seats deliver nothing. They say which module is this machine's packet filter, or which of
|
|
two alternative resolver configurations it runs, and a second holder is refused. That is the whole of
|
|
their job, and it is a real one: it is the mesh saying what a machine is, in words a person can read.
|
|
|
|
## The overview
|
|
|
|
The controller lists every seat in the set with its scope, what it delivers, and each holder as a
|
|
node and a module. A seat nobody holds is listed as unheld. That is an answer, "this mesh has no
|
|
forge", and not a fault.
|
|
|
|
Holdings are derived from assignments whenever they are asked for, never stored. The list is always
|
|
what the mesh is running, because it is computed from the same thing that decides what the mesh runs.
|
|
|
|
## The git seat, and where a build comes from
|
|
|
|
A module is built from a repository, a path and a ref. The repository is one of two things, and the
|
|
mesh records which:
|
|
|
|
| form | means | recorded as |
|
|
|---|---|---|
|
|
| on the `git` seat | a repository on the forge that holds the seat | its path on the forge, and the seat |
|
|
| external | a repository anywhere else, a public forge for instance | its URL, exactly as given |
|
|
|
|
For a repository on the seat, the controller composes the clone URL at the moment of building,
|
|
from where the holder runs and the scheme and port it serves for `git`. The recorded source never
|
|
contains an address, so moving the forge changes nothing that was recorded. The build machine is not
|
|
told the difference: it receives a URL either way.
|
|
|
|
With the seat unheld, a build from the seat is refused and says why. External builds carry on.
|
|
|
|
**Not yet designed:** a credential for cloning a private repository. The mesh's own repositories are
|
|
public. The natural place for a clone credential is the `git` provision's grant, and that is a
|
|
decision still to take.
|