Seats have been doing two jobs and neither is written down. The mechanism ADR 0009 introduced is enforced — a second holder is refused — but any well-formed name becomes a seat by being claimed, and nothing can say which seats a mesh has or who holds them: holdings are assembled while planning and discarded. The enumeration done while preparing this missed the control plane's own manifest, because core modules' manifests live in its repository rather than the catalogue. 0110 closes the set. Each seat has a name, a scope, what occupying it delivers, and the record that made it one; a claim outside the set is refused. A seat is held by a module assignment, and what the mesh knows about the holder is what it knows about that assignment — nothing is stored beside it. A seat may deliver a provision, and then its holder answers for it among several providers: pin, then the holder, then the only provider, then refused. That keeps 0009's "refused, never guessed": the seat is the choice made once, mesh-wide, instead of a pin per consumer node. The first set is the eleven seats already claimed plus 0109's npm-package-registry, so nothing in use is refused. Two concepts — seats for exclusion, a new word for consumable singulars — was rejected: both mean "this mesh's one X", and the overview a person wants is one list. 0111 gives the mesh a git seat and makes a build source one of two explicit forms: a repository on the seat's holder, recorded by its path and cloned from wherever the holder runs at build time; or an external URL, recorded and cloned exactly as given. Recognising self-hosted sources by matching URLs against the forge's address was rejected — it fails in the one case it exists for, after the forge moves. Credentials for private repositories are left undecided and said so. Design: new to-be 26 (the seats); 23 gains the seat step in resolution; 18's source entry names the two forms; the glossary's seat and provision entries say where they meet. 0109 is carried from its own branch so every link here resolves.
155 lines
10 KiB
Markdown
155 lines
10 KiB
Markdown
---
|
|
topic: what runs on it
|
|
status: accepted
|
|
date: 2026-09-25
|
|
deciders: jochen
|
|
reconstructed: false
|
|
extends: 0009-modules-and-the-graph.md
|
|
---
|
|
|
|
# 110. A seat is a module assignment from a closed set, and it may deliver a provision
|
|
|
|
## Context
|
|
|
|
[ADR 0009](0009-modules-and-the-graph.md) introduced claims: a module declares something
|
|
singular, at a scope, and a second holder is refused. [ADR 0079](0079-the-foundation-seats-are-named-after-their-servers.md)
|
|
named the foundation's three after their servers. That mechanism is enforced and works. What it
|
|
means has drifted, and three things are now true of it that no record says.
|
|
|
|
**Any well-formed name becomes a seat by being claimed.** The control plane's manifest check
|
|
refuses a claim only for a malformed name or an unknown scope. Nothing says which seats a mesh has.
|
|
The names in use were each invented by the module that claims them: `the-showcase`,
|
|
`the-build-machine`, `the-intrusion-prevention`.
|
|
|
|
**Nothing can say what a mesh has, or who fills it.** There is no seat table and no command that
|
|
lists seats. Holdings are assembled while planning, one node at a time, and discarded afterwards.
|
|
The only way to answer "which seats does this mesh have, and which module holds each" is to read
|
|
every manifest in two repositories, because the core modules' manifests moved into the control
|
|
plane's own repository ([ADR 0069](0069-a-module-is-a-repository-and-a-path.md)). While this record
|
|
was being prepared, that enumeration was done once by hand, and it missed the control plane's own
|
|
manifest: eleven claims were reported where there are twelve.
|
|
|
|
**Some seats are the mesh's one of something that others consume, and nothing uses that fact.**
|
|
Of the twelve claims, four are held by a module that provides something consumers require:
|
|
`mesh-store` (`postgres-database`, eleven consumers), `mesh-broker` (`amqp`), `the-artifact-store`
|
|
(`artifact-store`) and `the-dns-port`. The rest deliver nothing to anybody, and are still
|
|
meaningful: they say which module is this mesh's packet filter, or resolver configuration.
|
|
Meanwhile a requirement for a mesh-scoped provision with more than one provider is refused until a
|
|
person pins, **per consumer node**, which provider to use. [ADR 0109](0109-a-package-registry-seat-is-one-per-ecosystem.md)
|
|
anticipates exactly that case — gitea and verdaccio both answering npm — and under today's
|
|
resolution it would mean a pin on every machine that builds anything.
|
|
|
|
## Considered Options
|
|
|
|
**1. Leave seats as free-form exclusion, and keep provisions as the only thing that delivers.**
|
|
Rejected. The overview stays unanswerable, and a second provider of anything costs a pin per
|
|
consumer node — the decision "gitea is our npm registry" made again on every machine.
|
|
|
|
**2. Two concepts: seats for exclusion, and a new word for the mesh's one consumable thing.**
|
|
Rejected. Both mean "this mesh's one X". Every existing claim would first have to be classified
|
|
into one or the other, and the overview a person wants is one list, not two.
|
|
|
|
**3. A seat is a module assignment from a closed set, and occupying it may deliver a provision.**
|
|
Chosen.
|
|
|
|
## Decision
|
|
|
|
**The mesh defines a closed set of seats.** Each entry has a name, a scope, what occupying it
|
|
delivers (if anything), and the decision that made it a seat. A claim naming a seat outside the set
|
|
is refused, and so is a claim at a scope other than the seat's. Adding a seat is a decision, for the
|
|
same reason adding a shape to the host's vocabulary is one: the set is what a person reads to learn
|
|
what a mesh can have, and a name added without an argument is a name nobody can explain later.
|
|
|
|
**A seat is held by a module assignment.** What the mesh knows about a seat's holder is what it knows
|
|
about that assignment: its node, the node's settings for it, and what it serves. Holdings are not
|
|
stored separately. The seat points at an assignment, and a second record of the same fact would be a
|
|
second thing to disagree with the first.
|
|
|
|
**A seat may deliver a provision, and then its holder answers for it.** A seat that delivers a
|
|
provision can only be held by a module that provides it, at the seat's scope, and a claim that does
|
|
not is refused. When several providers answer a requirement for that provision, resolution takes, in
|
|
order:
|
|
|
|
1. a provider the consumer's node was **pinned** to — a consumer coupled to one provider's
|
|
contents, as [to-be 23](../03-DESIGN/01-to-be/23-choosing-a-provider.md) already allows;
|
|
2. **the holder of the seat** that delivers it;
|
|
3. the **only** provider, when there is one;
|
|
4. otherwise, refused with the candidates named, as now.
|
|
|
|
This keeps [ADR 0009](0009-modules-and-the-graph.md)'s rule that a requirement with several answers is
|
|
never guessed. The seat is not a guess. It is the choice made once, mesh-wide, by assigning the holder,
|
|
instead of once per consumer by pinning. A second provider may run beside the holder, and whatever
|
|
requires the provision still resolves to the holder without anybody naming it.
|
|
|
|
**Seats are also informational.** The control plane lists every seat in the set, what it delivers,
|
|
and which assignment holds it, including seats nobody holds. An unheld seat is an answer, "this mesh
|
|
has no X", not an error.
|
|
|
|
**The first set is the eleven seats already claimed, plus one.** Twelve claims are in use, and they
|
|
name eleven seats because two alternative modules claim `the-resolver-configuration`. This record
|
|
admits every seat the catalogue and the control plane claim today, so no module is refused by it:
|
|
|
|
| seat | scope | delivers | held today by | made a seat by |
|
|
|---|---|---|---|---|
|
|
| `mesh-controller` | mesh | — | `mesh-controller` | [0079](0079-the-foundation-seats-are-named-after-their-servers.md) |
|
|
| `mesh-store` | mesh | `postgres-database` | `postgres` | [0079](0079-the-foundation-seats-are-named-after-their-servers.md) |
|
|
| `mesh-broker` | mesh | `amqp` | `lavinmq` | [0079](0079-the-foundation-seats-are-named-after-their-servers.md) |
|
|
| `the-artifact-store` | mesh | `artifact-store` | `distribution` | [0075](0075-two-stores-and-which-provides-what.md) |
|
|
| `the-catalogue` | mesh | — | `mesh-catalog` | this record |
|
|
| `npm-package-registry` | mesh | `npm-package-registry` | `gitea` | [0109](0109-a-package-registry-seat-is-one-per-ecosystem.md) |
|
|
| `the-build-machine` | node | — | `builder` | this record |
|
|
| `the-dns-port` | node | — | `dnsmasq` | this record |
|
|
| `the-intrusion-prevention` | node | — | `fail2ban` | this record |
|
|
| `the-packet-filter` | node | — | `nftables` | this record |
|
|
| `the-resolver-configuration` | node | — | `resolv-conf` or `resolved-split-dns` | this record |
|
|
| `the-showcase` | node | — | `showcase` | this record |
|
|
|
|
`npm-package-registry` is the one addition. It is [ADR 0109](0109-a-package-registry-seat-is-one-per-ecosystem.md)'s
|
|
seat, named after the provision it delivers, as 0079 named the foundation's seats after what they are.
|
|
gitea claims it. verdaccio provides the same provision and claims nothing, so it is the second
|
|
provider this record exists to make harmless.
|
|
|
|
`the-dns-port` is listed as delivering nothing, although `dnsmasq` provides `wildcard-resolution`.
|
|
That provision is node-scoped and answered on the machine, so no preference between providers
|
|
arises. Whether the seat should say it delivers it is left for when a second resolver makes the
|
|
question real.
|
|
|
|
## Consequences
|
|
|
|
- The control plane carries the set in code. A test asserts its size, and that every entry names the
|
|
record that made it a seat, so changing the set means finding the argument rather than a number.
|
|
This is the pattern the host's vocabulary test already follows.
|
|
- Manifest validation refuses an unknown seat, a seat claimed at the wrong scope, and a
|
|
provision-delivering seat claimed by a module that does not provide the provision. The three
|
|
refusals name the seat and the set.
|
|
- Resolution prefers the seat's holder among several providers, after a pin. A provider record
|
|
gains the module it came from, because two modules on one node could otherwise not be told apart
|
|
as holder and non-holder.
|
|
- A `seats` command lists the set with each seat's holders, derived from assignments.
|
|
- gitea claims `npm-package-registry`. The catalogue's `package-registry` becomes
|
|
`npm-package-registry` throughout, per [ADR 0109](0109-a-package-registry-seat-is-one-per-ecosystem.md).
|
|
- **What got harder:** a module wanting a new singular role can no longer invent a name. It needs a
|
|
record. That is the point, and it costs one record per seat.
|
|
- **Not changed:** `${seat:<seat>:<port>}` stays as it is. It exists so the control plane can reach a
|
|
foundation it made before any module existed, and it cannot be a consumer. A module that needs
|
|
something from a seat's holder requires the provision the seat delivers, and receives it the way
|
|
any provision is received: through a grant.
|
|
|
|
## How it is checked
|
|
|
|
| Rule | Checked by |
|
|
|---|---|
|
|
| The set is closed, and every entry names its decision | A control-plane unit test asserts the set's size and a non-empty decision for every entry. |
|
|
| A claim outside the set is refused | Manifest-validation tests for an unknown seat, the wrong scope, and a delivering seat whose claimant does not provide. |
|
|
| Every module in use claims a seat in the set | A control-plane test parses every catalogue manifest and fails on any refused claim. The lab's beds read the same manifests ([ADR 0089](0089-a-bed-reads-the-catalogue-it-proves.md)). |
|
|
| The holder answers among several providers | Resolution tests: two providers with the seat held, two with a pin overriding the seat, two with the seat unheld (refused). |
|
|
|
|
## References
|
|
|
|
- [ADR 0009](0009-modules-and-the-graph.md): claims, scopes, and "refused, never guessed"
|
|
- [ADR 0079](0079-the-foundation-seats-are-named-after-their-servers.md): a seat named after what it is
|
|
- [ADR 0109](0109-a-package-registry-seat-is-one-per-ecosystem.md): the per-ecosystem registry seats
|
|
- [to-be 23](../03-DESIGN/01-to-be/23-choosing-a-provider.md): pins, co-location and refusal
|
|
- `mesh-controller internal/catalogue/resolve.go` (`checkClaims`, the brokered branch of `Resolve`),
|
|
`internal/catalogue/manifest.go` (claim validation), `cmd/mesh-controller/plan.go` (holdings)
|