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:
@@ -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)
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user