Files
hq/03-DESIGN/01-to-be/26-the-seats.md
T
jochen 4a1b218706 Seats held by assignments, one assignment per module per node, and 0113's bottom of the stack
Decided with the author:
- A seat is held by one assignment, not claimed by a definition. A definition says which seats a module
  can hold; an assignment says which it does. The store module can run on every node and one assignment
  holds mesh-store; moving a role changes an assignment, never a definition. The foundation's seats name
  what the mesh itself uses and route no consumer — database and amqp consumers use co-location, the
  holder included. This replaces the wrong rationale that the foundation's store is "provider to nobody",
  which contradicted ADR 0078 and to-be 21. 0079's one-postgres rule becomes one mesh-store holder.
- A module is assigned at most once to a node. The instance identity in 0112 and 27 is withdrawn, and the
  login-length problem with it.

Review fixes to 0113:
- The bottom of the stack: the vault is installed as soon as the shared runtime base exists, and genesis
  generates everything needed until then — including the permanent controller's, the control-node
  agent's, the builder's and the broker provisioner's bus accounts, and the controller's store login.
  Genesis creates those accounts until the broker's provisioner runs and adopts them.
- Genesis's values are delivered recorded as the mesh's own, so 0092's never-replace rule for operator
  values does not make them unrotatable.
- Backend-issued secrets (a forge's once-only API token) enter through the vault. Non-module parties
  (the controller's logins, node agents' accounts) are answered the same way, the controller asking on
  their behalf; an enrolment token reaches the controller only as what verifies it.
- A secret with no provisioner to apply it is marked not rotatable by the mesh and refused, instead of
  a restart reported as done. Unused password generators in six provider clients are removed, and a
  catalogue scan checks no module mints.
- Rotation's lock-out cases (offline reader, bus account owner, restarted provisioner) are recorded as
  open, with overlap and re-confirm-with-safeguards as the two answers, to be chosen before acceptance.

0110, 0111 and 26 are marked proposed: they changed in meaning and are under review, and an accepted
record must not rest on proposed ones. To-be 23 and the glossary are restored to main; they change when
these records are accepted.
2026-09-25 23:47:26 +02:00

155 lines
8.6 KiB
Markdown

---
layer: to-be
status: proposed
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, held by one
module assignment. The mesh defines which seats exist. Holding 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 definition names and an assignment holds, 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 definition says which seats a module can hold. An assignment says which it does hold.** The store
module can hold `mesh-store`, and it may run on every node whose capabilities match. Exactly one of
those assignments holds the seat, because that assignment says so, and a second assignment saying so
is refused. A seat makes a role singular, never a module.
**The seat points at the assignment.** 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 seat the mesh does not define is refused wherever it is named, and so is one
named 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 store the mesh's own records live in |
| `mesh-broker` | mesh | — | the broker carrying the mesh's own bus |
| `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 several
things here: seats held by assignments rather than claimed by definitions, the `mesh-vault` seat and
its reservation, and the foundation's seats delivering nothing. It is brought to this table before it
merges.
## The foundation's seats
`mesh-controller`, `mesh-store` and `mesh-broker` name which assignment the mesh *itself* uses: the
controller, the store holding its records, the broker carrying its bus. **They route no consumer.** The
store and broker modules may run on other nodes too. A database or `amqp` consumer is served by
co-location, from whichever runs on its own node, the seat's holder included
([23 — Choosing a provider](23-choosing-a-provider.md)).
## A seat that delivers a provision
**A seat delivers a provision only where the mesh has one answer for everyone.** The artifact store,
the npm registry, git and the vault are each one per mesh by decision. A seat that delivers a
provision may only be held by an assignment of a module that provides it, at the seat's scope.
**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. A forge assignment 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 assignment holds the seat.** No definition changes and nothing is
unassigned: the forge keeps running, and keeps holding `git`, when its npm role moves. A module can
take the role only if its definition says it can hold the seat.
**The vault's provision is reserved.** Only an assignment holding `mesh-vault` may provide `secret` at
all: a definition providing it that cannot hold the seat is refused, an assignment providing it without
holding 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.
**What a consumer receives is what it required**, 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 its 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 a `secret` from the vault, and that is a decision
still to take.