Narrow ADR 0287's claimant rule to any seat naming the path, and correct two facts the review found
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

This commit is contained in:
jochen
2026-10-10 21:57:24 +02:00
parent 6b49ed613c
commit 906d702b8d
3 changed files with 35 additions and 25 deletions
@@ -91,7 +91,7 @@ it; a module that once wrote a line there asks the mesh's resolver instead.
> **The mechanism changed — 2026-10-10, by [ADR 0287](0287-a-seat-names-the-paths-its-holder-owns-and-a-claim-without-them-is-refused.md).**
> Decision 3 and 4 stand: `/etc/hosts` is the hosts seat's holder's, and no other module writes it. What moved
> is where that is stated and checked. The seat's definition now names `/etc/hosts` and `/etc/hostname`, a
> is where that is stated and checked. The seat's definition names `/etc/hosts` and `/etc/hostname` (built under issue 412), a
> claim without them is refused, and a module that does not claim the seat and declares either is refused at
> registration, for every seat that names a path, rather than by this record for one file.
@@ -18,8 +18,7 @@ The operator left the choices to the agent that graduated it ("I trust your judg
([ADR 0129](0129-a-seat-carries-the-protocol-of-its-role.md),
[ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md)). Today the protocol is what the holder
accepts, emits and serves. A seat's definition in the controller carries a name, a scope, what it delivers,
those three, what it receives, whether it is replicated, the seats it needs beside it, and the record that
made it a seat. The store's seat table carries a name, a scope, what it delivers and the record. **Neither
those three, what it receives, whether it is replicated, and the record that made it a seat. The store's seat table carries a name, a scope, what it delivers and the record. **Neither
names a path.**
**Who owns a machine-wide file has been decided one record at a time:**
@@ -47,7 +46,7 @@ on 2026-10-03, *which module owns `/etc/resolv.conf`?*, had no place to be looke
**What the catalogue declares, measured on 2026-10-10** (mesh-catalog main at `61dcdf0`, 125 manifests):
- 29 modules claim a node seat.
- 29 modules claim one of the mesh's own node seats, and two more claim a node seat they declare themselves.
- Three paths that the holders declare are the role's on any machine, whatever program fills the role:
`/etc/hosts` and `/etc/hostname` (declared by `hostname`, holder of `node-hostname`), and
`/etc/resolv.conf` (rendered for `networkmanager` and `systemd-networkd`, holders of `node-uplink`, and for
@@ -56,7 +55,9 @@ on 2026-10-03, *which module owns `/etc/resolv.conf`?*, had no place to be looke
tools), which is access, not ownership ([ADR 0051](0051-shared-data-is-the-operators.md)).
- Every other path a node seat's holder declares belongs to its program, not to the role:
`/etc/fail2ban/jail.local`, `/etc/docker/daemon.json`, `/etc/pacman.conf`, `/etc/lemurs/config.toml`,
`/etc/NetworkManager/conf.d/50-mesh.conf`, or a path under the operator's home.
`/etc/NetworkManager/conf.d/50-mesh.conf`, or a path under the operator's home. Two are neither: the
holder of `node-secret-service` declares `/etc/pam.d/login` and `/etc/pam.d/passwd`, which are shared
files of other programs. They are not the role's on any machine either, so no seat names them.
## Considered Options
@@ -75,11 +76,11 @@ on 2026-10-03, *which module owns `/etc/resolv.conf`?*, had no place to be looke
made and the laptop runs. If `node-uplink` lost `/etc/resolv.conf`, a machine without a resolver of its
own would have no owner of the file (against ADR 0223). If `node-resolver` lost it, ADR 0247 would be
undone.
5. **A seat's paths as a column of the store's seat table.** Rejected. Whether a seat is replicated, what it
receives and which seats it needs are compiled with the set and never stored, because each is the mesh's
definition of the role and changes what every holder must satisfy
([ADR 0220](0220-what-a-machine-asks-needs-its-uplink-held-and-the-retired-resolver-pieces-go.md)). A path
is the same kind of fact. A row edited without a release would make claims that were valid refused, with
5. **A seat's paths as a column of the store's seat table.** Rejected. Whether a seat is replicated and what
it receives are compiled with the set and never stored, as the seats a seat needed were while that field
existed ([ADR 0220](0220-what-a-machine-asks-needs-its-uplink-held-and-the-retired-resolver-pieces-go.md)),
because each is the mesh's definition of the role and changes what every holder must satisfy. A path is
the same kind of fact. A row edited without a release would make claims that were valid refused, with
nothing in review.
6. **Check it only at the merge gate.** Rejected as the only place. The rule is a judgement over the whole
catalogue, which is what registration judges (`CatalogueProblems`). The merge gate already runs
@@ -110,20 +111,25 @@ a seat or a seat's need is.
**3. A module holds the seat only if it declares every path the seat names.** It declares it as a resource
or as a file the mesh renders for it, which is how the uplink and the resolver hold `/etc/resolv.conf`. A
claim without them is refused by `CanHold`, the one judgement of whether a module can hold a seat (ADR 0131),
at registration and at handover, naming the seat and the missing paths. All four modules that hold one of
at registration and at handover, naming the seat and the missing paths. A seat a module declares for itself
is held to the same rule by the check that already judges what such a seat's holder serves
(`CatalogueProblems`). All four modules that hold one of
these seats today declare what the table names, so the rule refuses nothing registered on 2026-10-10.
**4. Only a claimant declares a seat's path.** A module that does not claim the seat and declares a path the
seat names is refused at registration, naming the seat. The same refusal applies at composition to what the
**4. Only a claimant declares a seat's path.** A module that claims no seat naming a path, and declares that
path, is refused at registration, naming the seats that name it. A module claiming any one of them may declare
it, which is how both the uplink and the resolver declare `/etc/resolv.conf`. The same refusal applies at composition to what the
controller generates (ADR 0222 §4), so the controller cannot write a seat's path either. Reading a seat's
path is access, and stays allowed.
**5. No two seats name one path, unless one steps back for the other by definition.** A seat may name a path
*stepping back for* another node seat: where that other seat is held on the machine, its holder owns the path
and this seat's holder does not declare it there. Every path has exactly one seat naming it without stepping
back, and the stepping back does not cycle. This makes the existing rule of ADR 0247 part of the definition,
in the place `checkResources` applies it today. A module declaring a seat that names a path another seat
already names is refused at registration, and the second declaration loses, as for a seat's name.
*stepping back for* another node seat: where that other seat is held on the machine, its holder owns the path,
and this seat's holder still declares it, but its declaration is not composed on that machine. Every path has
exactly one seat naming it without stepping back, and the stepping back does not cycle. This makes the
existing rule of ADR 0247 part of the definition, in the place `checkResources` applies it today. Only the
mesh's own seats step back, each by a record. A module declaring a seat that names a path another seat
already names, stepping back or not, is refused at registration, and the second declaration loses, as for a
seat's name.
**6. The seat listing shows each seat's paths.** *Which module owns `/etc/resolv.conf` on a machine?* is
answered by the controller's `seats` verb: the seat that names the path, and that seat's holder on the
@@ -158,7 +164,7 @@ All in mesh-controller, built under issue 412:
| Only a node seat names paths, absolute and without a placeholder | a unit test over the compiled set; a registration test refusing a module's mesh seat that names a path and a path with `${` |
| Every path has one seat naming it without stepping back, and stepping back does not cycle | a unit test over the compiled set; a registration test refusing a module's seat that names `/etc/hosts` |
| A claim without the seat's paths is refused, naming them | `CanHold` tests at registration and at handover, with a holder declaring the path as a resource, as a rendered file, and not at all |
| Only a claimant declares a seat's path | a registration test refusing a module that declares `/etc/hosts` without claiming `node-hostname`; a composition test refusing a generated resource on a seat's path |
| Only a claimant of a seat naming the path declares it | a registration test refusing a module that declares `/etc/hosts` without claiming `node-hostname`, and one accepting the uplink and the resolver both declaring `/etc/resolv.conf`; a composition test refusing a generated resource on a seat's path |
| The step-back comes from the seat | the existing composition tests for the uplink and the resolver on one machine pass with the special case removed |
| The listing shows the paths | the `seats` command test |
| The catalogue as it is passes | `CatalogueProblems` over the catalogue's main gives no new problem; the merge gate runs it on every catalogue pull request |
@@ -169,7 +175,7 @@ All in mesh-controller, built under issue 412:
effort this graduates.
- [26 — The seats](../03-DESIGN/01-to-be/26-the-seats.md), the design amended.
- mesh-controller main at `c06a2cd`: `internal/catalogue/seats.go` (`Seat`, `CanHold`),
`internal/catalogue/resolve.go` (`checkResources`, `steppedBack`),
`internal/catalogue/resolve.go` (`checkResources`), `internal/catalogue/node_resolver.go` (`steppedBack`),
`internal/catalogue/seats_declared.go` (`CatalogueProblems`), `cmd/mesh-controller/merge_gate.go`.
- mesh-catalog main at `61dcdf0`: the manifests of `hostname`, `networkmanager`, `systemd-networkd` and
`systemd-resolved`.