ADR 0161: what deserves a seat — the vault's seat, the hub as a placement of capacity one, the uplink holder as the machine's dialect; design 26; issues 105, 106, 138

This commit is contained in:
2026-10-01 15:55:15 +02:00
parent 6b8fb562ce
commit f35f3757bb
6 changed files with 162 additions and 15 deletions
+102
View File
@@ -0,0 +1,102 @@
---
topic: the mesh
status: accepted
date: 2026-10-01
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0126-a-module-declares-its-own-seats.md
---
# 161. What deserves a seat: a role of a module is a seat, a singular fact about machines is a placement with a capacity of one, and a holder's software is the machine's
## Context
Three issues asked the same question from three sides. The vault provides `secret` to the whole
mesh and claims no seat, so nothing refuses a second vault by name
([issue 106](../04-ISSUES/106-the-vault-claims-no-seat/00-report.md)). The hub of the private
network is a placement, `overlay place <node> --hub`, and the issue asked whether "there is exactly
one hub" is a seat's shape ([issue 105](../04-ISSUES/105-the-hub-of-the-private-network-is-not-a-seat/00-report.md)).
Three modules claim the one uplink seat, one per network manager a machine might run, and nothing
checks that the holder names the manager the machine actually runs
([issue 138](../04-ISSUES/138-two-modules-claim-one-seat-and-are-not-interchangeable/00-report.md)).
Read against the code on the day of deciding:
- The mesh's own seats are five by [design 26](../03-DESIGN/01-to-be/26-the-seats.md)'s table and
four in the controller's seed: `mesh-vault` is in the table and not in the seed, and the vault's
definition claims nothing. The design also says `secret` is reserved; no parser or resolution rule
reserves it. A second provider of `secret` would be a second candidate, settled by a pin.
- The store already keeps one hub: a unique index since the overlay's first migration, and the
placing command refuses a second hub naming the first. What 105 observed as silent is not.
[ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md) decided that
the private network becomes a mesh-scoped seat held by a server module, with client modules —
the overlay is the host's own today, so that seat has nothing to be held by yet.
- A machine's capabilities are its profile, detected by the host at enrolment and never since, and
resolution refuses a module on a machine lacking one it declares, naming the capability. The uplink
holders declare `package-manager` and `service-manager`, which every machine has.
[ADR 0126](0126-a-module-declares-its-own-seats.md) gave the reason the mesh's own seats exist:
**the mesh's own code looks them up by name.** `mesh-store` is an identifier the controller
dereferences, not a convention. That reason decides the first question; the other two are decided
by what a seat is — a role held by a module assignment — and by what the mesh can check.
## Decision
**1. A provision the mesh itself dereferences is delivered by a mesh seat its provider claims.**
The vault's `secret` is one: the controller seals every minted credential with it. `mesh-vault` is
the fifth seat of the mesh's own, mesh-scoped, delivering `secret`, under
[ADR 0079](0079-the-foundation-seats-are-named-after-their-servers.md)'s convention; the vault's
definition claims it; a second provider of `secret` is a second claimant and refused by name. The
word *reserved* leaves design 26: the effect it described is the seat's. Every other mesh-scoped
provision — `smtp`, `oidc-client`, `s3-bucket`, `route`, `acme-ca` and the rest — may have several
providers, and a consumer with several and none local is a person's choice, as the glossary says.
The test for "deserves a seat" is the question 0126 asked: does the mesh's own code find it by name?
**2. A singular fact about machines is a placement with a capacity of one; a singular role of a
module is a seat.** A seat is held by a module assignment and points at it; the hub is a machine,
and the private network is the host's own until 0121's server and client modules exist. So the hub
stays a placement, and what a seat would have given — refusal of a second by name, and the one
named when asked — a placement of capacity one gives: the store keeps one (the unique index), the
placing command refuses a second naming the one that stands, and the overlay listing names it.
0121's seat for the private network stands, deferred with the split it needs. The rule generalises:
a fact of the shape *exactly one machine is X* is a placement checked by the store and said by name,
never a seat with no module to hold it.
**3. A holder of a seat whose role is "speak to what this machine runs" must be the dialect the
machine runs, and the machine says which.** The host's profile gains one capability per network
manager found active — `uplink-networkmanager`, `uplink-systemd-networkd`, `uplink-dhcpcd`, each
`systemctl is-active` of the manager's unit — and each uplink holder declares its own. Assignment
then refuses the wrong holder with the refusal that already exists, naming the capability; nothing
new is judged. The profile is detected again by every apply and travels in the report, and the
controller keeps the latest, so a machine that switches managers is, at its next push, a machine
whose holder lacks a capability: the plan refuses and names it, which is the one thing the machine
is the only one to know. `node-uplink` stays one seat: its three holders are three dialects of one
role, and the capability picks the dialect. One module speaking all three is allowed by this and
built by nobody.
## Consequences
- The controller's seed gains `mesh-vault`; the seat table takes it additively at the next start,
as every seed row does. The vault's definition claims it, one release after the controller.
- The uplink definitions declare their capability one release after the host reports it, or they
are refused on every machine in between; the order is controller (the report carries a profile),
host, then catalogue.
- Design 26 loses the word *reserved* for `secret` and states rules 2 and 3; the uplink row of the
seat table names the capability its holders declare.
- Issue 106 is resolved by rule 1, 105 by rule 2 with nothing to build, 138 by rule 3.
## How this is checked
| Rule | Checked by |
|---|---|
| `mesh-vault` is in the mesh's own set, mesh-scoped, delivering `secret`, and the vault claims it | a catalogue test on the default seats; registration refuses a second claimant by name (`CanHold`'s existing test, with the vault's seat) |
| A second hub is refused naming the first, and the listing names the hub | the overlay command's test; the store's unique index |
| A machine's profile names the network manager it runs, and is renewed by every report | a host detector test per manager; a controller test that a report carrying a profile replaces the stored one |
| An uplink holder on a machine running another manager is refused, naming the capability | the existing capability refusal, exercised by a resolution test with a networkmanager machine and the systemd-networkd holder |
| Live | `mesh-controller.seats` lists `mesh-vault` held by the vault on the control node; `plan` of a machine refuses the wrong uplink holder naming `uplink-<manager>` |
## References
- [ADR 0079](0079-the-foundation-seats-are-named-after-their-servers.md), [ADR 0110](0110-a-seat-is-a-module-assignment-from-a-closed-set.md), [ADR 0117](0117-a-machines-uplink-is-a-seat.md), [ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md), [ADR 0126](0126-a-module-declares-its-own-seats.md)
- [Design 26 — The seats](../03-DESIGN/01-to-be/26-the-seats.md)
- Issues [105](../04-ISSUES/105-the-hub-of-the-private-network-is-not-a-seat/00-report.md), [106](../04-ISSUES/106-the-vault-claims-no-seat/00-report.md), [138](../04-ISSUES/138-two-modules-claim-one-seat-and-are-not-interchangeable/00-report.md)
+1
View File
@@ -174,6 +174,7 @@ python3 00-META/checks/index.py fail if stale
- **0158** — [A provider with one credential shares it with every consumer, and the vault remakes it for all of them at once](0158-a-provider-with-one-credential-shares-it-with-every-consumer.md) - **0158** — [A provider with one credential shares it with every consumer, and the vault remakes it for all of them at once](0158-a-provider-with-one-credential-shares-it-with-every-consumer.md)
- **0159** — [A tool call names the machine it is for, every answer says which machine answered, and a holder's runtime serves its seat's verbs](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md) - **0159** — [A tool call names the machine it is for, every answer says which machine answered, and a holder's runtime serves its seat's verbs](0159-a-tool-call-names-the-machine-and-a-holder-serves-its-seats-verbs.md)
- **0160** — [The mesh issues an assignment's subjects, and a runtime serves what it is issued](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md) - **0160** — [The mesh issues an assignment's subjects, and a runtime serves what it is issued](0160-the-mesh-issues-an-assignments-subjects-and-a-runtime-serves-what-it-is-issued.md)
- **0161** — [What deserves a seat: a role of a module is a seat, a singular fact about machines is a placement with a capacity of one, and a holder's software is the machine's](0161-what-deserves-a-seat.md)
### Its tiers, from the bottom up ### Its tiers, from the bottom up
+17 -3
View File
@@ -10,8 +10,9 @@ code:
- mesh-controller cmd/mesh-controller/source.go - mesh-controller cmd/mesh-controller/source.go
- mesh-controller internal/inventory/migrations/0032-a-source-may-live-on-a-seat.sql - mesh-controller internal/inventory/migrations/0032-a-source-may-live-on-a-seat.sql
- mesh-catalog modules/gitea/module.json - mesh-catalog modules/gitea/module.json
updated: 2026-09-27 updated: 2026-10-01
decisions: decisions:
- 02-DECISIONS/0161-what-deserves-a-seat.md
- 02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md - 02-DECISIONS/0131-everything-on-the-mesh-speaks-to-the-broker-seat.md
- 02-DECISIONS/0126-a-module-declares-its-own-seats.md - 02-DECISIONS/0126-a-module-declares-its-own-seats.md
- 02-DECISIONS/0128-the-mesh-bus-is-required-not-ambient.md - 02-DECISIONS/0128-the-mesh-bus-is-required-not-ambient.md
@@ -75,6 +76,17 @@ named at the wrong scope. Adding a seat is a decision, recorded, for the reason
host's vocabulary is one: the set is what a person reads to learn what a mesh can have, and an entry 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. nobody argued for is an entry nobody can explain.
**What deserves one** ([ADR 0161](../../02-DECISIONS/0161-what-deserves-a-seat.md)). A provision the
mesh's own code dereferences by name is delivered by a mesh seat its provider claims — the store, the
bus, the vault, the artifact store, the catalogue. A provision a module merely offers may have several
providers, and a consumer with several is a person's choice. A fact of the shape *exactly one machine
is X* — the hub — is not a seat, because a seat is held by a module assignment and points at it; it is
a placement with a capacity of one, kept by the store, refused by name when a second is placed, and
named in the listing. And a seat whose role is to speak to what the machine runs — the uplink — is
held only by the dialect the machine runs: the machine says which in its profile, renewed with every
report, and the holder declares the capability, so the wrong one is refused the way any missing
capability is.
## The set ## The set
**The set is derived, and only the mesh's half is written here.** Revision, 2026-09-26 **The set is derived, and only the mesh's half is written here.** Revision, 2026-09-26
@@ -108,7 +120,7 @@ convention, which later seats departed from.
| `mesh-controller` | — | mesh | — | the controller | | `mesh-controller` | — | mesh | — | the controller |
| `mesh-store` | — | mesh | — | the store the mesh's own records live in | | `mesh-store` | — | mesh | — | the store the mesh's own records live in |
| `mesh-broker` | — | mesh | `mesh-bus` | the broker carrying the mesh's own bus | | `mesh-broker` | — | mesh | `mesh-bus` | the broker carrying the mesh's own bus |
| `mesh-vault` | — | mesh | `secret`, reserved | the vault | | `mesh-vault` | — | mesh | `secret` | the vault (in the seed since [ADR 0161](../../02-DECISIONS/0161-what-deserves-a-seat.md); the earlier *reserved* named an effect no rule produced) |
| `mesh-artifact-store` | `the-artifact-store` (renamed 2026-09-30, [ADR 0156](../../02-DECISIONS/0156-an-artifact-is-what-a-build-produces-and-the-store-is-named-for-its-scope.md)) | mesh | `artifact-store` | the artifact registry | | `mesh-artifact-store` | `the-artifact-store` (renamed 2026-09-30, [ADR 0156](../../02-DECISIONS/0156-an-artifact-is-what-a-build-produces-and-the-store-is-named-for-its-scope.md)) | mesh | `artifact-store` | the artifact registry |
| `mesh-catalog` | `the-catalogue` | mesh | — | the catalogue | | `mesh-catalog` | `the-catalogue` | mesh | — | the catalogue |
| `mesh-npm-package-registry` | `npm-package-registry` | mesh | `npm-package-registry` | the forge | | `mesh-npm-package-registry` | `npm-package-registry` | mesh | `npm-package-registry` | the forge |
@@ -240,6 +252,8 @@ checked as their tables say:
| A handover replaces the holder as one write, needs an assignment to point at, and goes with it | 0131: store tests — a second handover leaves one row; a handover to a module not assigned where named is refused; unassigning the holder removes the row. | | A handover replaces the holder as one write, needs an assignment to point at, and goes with it | 0131: store tests — a second handover leaves one row; a handover to a module not assigned where named is refused; unassigning the holder removes the row. |
| A requirement naming a seat is answered by its holder; a foundation seat cannot be named | 0118: resolution tests with a second provider on the consumer's node, with the seat unheld, and naming `mesh-store`. | | A requirement naming a seat is answered by its holder; a foundation seat cannot be named | 0118: resolution tests with a second provider on the consumer's node, with the seat unheld, and naming `mesh-store`. |
| Several providers and none local is a person's choice | 0118: an assignment test listing candidates with the seat's holder first and recording the pin. | | Several providers and none local is a person's choice | 0118: an assignment test listing candidates with the seat's holder first and recording the pin. |
| `secret` is reserved | 0118: the parser and resolution refusals for another provider and a pin. | | `secret` has one provider, the holder of `mesh-vault` | 0161: a second claimant of the seat is refused by name (`CanHold`); *correction of fact, 2026-10-01: no parser rule ever reserved the word, the seat does the work*. |
| A singular fact about machines is a placement of capacity one, refused by name | 0161: the overlay command's test for a second hub; the store's unique index. |
| A holder of `node-uplink` is the dialect the machine runs | 0161: the host reports `uplink-<manager>` in its profile with every report; a resolution test refuses the other holder naming the capability. |
| Holdings are derived, and the overview lists every seat | 0118: the `seats` command test, including an unheld seat. | | Holdings are derived, and the overview lists every seat | 0118: the `seats` command test, including an unheld seat. |
| A build source on the seat records no address; an unheld seat refuses only self-hosted builds | 0111's tests. | | A build source on the seat records no address; an unheld seat refuses only self-hosted builds | 0111's tests. |
@@ -1,9 +1,9 @@
--- ---
status: open status: resolved
opened: 2026-09-23 opened: 2026-09-23
located-in: [] located-in: [mesh-controller cmd/mesh-controller/network.go (the placing command), internal/inventory/migrations/0004-the-overlay.sql (one hub)]
fixed-by: fixed-by: nothing to build — ADR 0161 rule 2; the second hub was already refused by name, and the private network's seat waits for ADR 0121's server and client modules
amended-design: amended-design: [03-DESIGN/01-to-be/26-the-seats.md]
--- ---
# 105 — The hub of the private network is a placement, not a seat # 105 — The hub of the private network is a placement, not a seat
@@ -32,3 +32,16 @@ a node-scoped seat held by every node, which is true and not what was asked.
- Does the per-node seat still say anything once the hub is a seat, or is it the interface's - Does the per-node seat still say anything once the hub is a seat, or is it the interface's
presence restated? presence restated?
- What else in the mesh is "exactly one" and recorded as a placement rather than a seat? - What else in the mesh is "exactly one" and recorded as a placement rather than a seat?
## Resolved, 2026-10-01
Read against the code: the store has kept one hub since the overlay's first migration (a unique
index), and `overlay place <node> --hub` refuses a second naming the first. What this report saw as
silent is not. [ADR 0161](../../02-DECISIONS/0161-what-deserves-a-seat.md), rule 2, answers the
question that remained: a singular fact about machines is a placement with a capacity of one,
refused by name and named in the listing — never a seat, because a seat is held by a module
assignment and the private network is the host's own until
[ADR 0121](../../02-DECISIONS/0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md)'s
server and client modules exist. That seat stands, deferred with the split it needs.
*How it is checked:* the overlay command's test for a second hub, and the index.
@@ -1,9 +1,9 @@
--- ---
status: open status: located
opened: 2026-09-23 opened: 2026-09-23
located-in: [] located-in: [mesh-controller internal/catalogue/seats.go (the seed lacks mesh-vault), mesh-catalog modules/mesh-vault/module.json (claims nothing)]
fixed-by: fixed-by:
amended-design: amended-design: [03-DESIGN/01-to-be/26-the-seats.md]
--- ---
# 106 — The vault claims no seat, so nothing refuses a second one # 106 — The vault claims no seat, so nothing refuses a second one
@@ -31,3 +31,11 @@ others were missed the same way — every provider added after 0079.
- A mesh-scoped seat `mesh-vault`, by the 0079 convention — is there any reason not to? - A mesh-scoped seat `mesh-vault`, by the 0079 convention — is there any reason not to?
- Should a provider of a mesh-scoped provision be required to claim a seat, or say explicitly that - Should a provider of a mesh-scoped provision be required to claim a seat, or say explicitly that
more than one is allowed, so the omission cannot recur? more than one is allowed, so the omission cannot recur?
## Decided, 2026-10-01
[ADR 0161](../../02-DECISIONS/0161-what-deserves-a-seat.md), rule 1: `mesh-vault` joins the mesh's
own set, mesh-scoped, delivering `secret`, and the vault claims it; a second provider is a second
claimant, refused by name. The record also answers the second question: a provision the mesh's own
code dereferences by name gets a seat, every other mesh-scoped provision may have several providers.
Design 26's *reserved* for `secret` named an effect no rule produced; corrected there.
@@ -1,9 +1,9 @@
--- ---
status: open status: located
opened: 2026-09-28 opened: 2026-09-28
located-in: [mesh-controller internal/catalogue, mesh-catalog] located-in: [mesh-host internal/profile/detectors.go (no detector for the network manager), mesh-host cmd/mesh-host (the profile is detected at enrolment only), mesh-controller internal/link (a report carries no profile), mesh-catalog modules/networkmanager, systemd-networkd, dhcpcd (declare no capability of their own)]
fixed-by: fixed-by:
amended-design: amended-design: [03-DESIGN/01-to-be/26-the-seats.md]
--- ---
# 138 — Two modules claim one seat and are not interchangeable, and nothing says so # 138 — Two modules claim one seat and are not interchangeable, and nothing says so
@@ -54,3 +54,12 @@ able to switch the manager, which ADR 0117 refuses for a reason that has not cha
alternative is one module that speaks whichever dialect the machine needs, chosen from the report. alternative is one module that speaks whichever dialect the machine needs, chosen from the report.
- What should happen on a machine that switches manager afterwards? The seat would then be held by the - What should happen on a machine that switches manager afterwards? The seat would then be held by the
wrong module, and the machine is the only place that knows. wrong module, and the machine is the only place that knows.
## Decided, 2026-10-01
[ADR 0161](../../02-DECISIONS/0161-what-deserves-a-seat.md), rule 3: the host's profile gains one
capability per network manager found active, each holder declares its own, and the existing
capability refusal does the rest, naming it. The profile is detected again by every apply and
travels in the report, so a machine that switches managers is refused at its next push. The uplink
stays one seat; the capability picks the dialect. Order of building: controller (a report may carry
a profile), host, then the three definitions.