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.
155 lines
8.6 KiB
Markdown
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.
|