diff --git a/02-DECISIONS/0199-a-module-that-answers-names-declares-its-zone-and-a-nodes-hosts-file-is-one-modules.md b/02-DECISIONS/0199-a-module-that-answers-names-declares-its-zone-and-a-nodes-hosts-file-is-one-modules.md index 845f397a..2d1912b6 100644 --- a/02-DECISIONS/0199-a-module-that-answers-names-declares-its-zone-and-a-nodes-hosts-file-is-one-modules.md +++ b/02-DECISIONS/0199-a-module-that-answers-names-declares-its-zone-and-a-nodes-hosts-file-is-one-modules.md @@ -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. diff --git a/02-DECISIONS/0287-a-seat-names-the-paths-its-holder-owns-and-a-claim-without-them-is-refused.md b/02-DECISIONS/0287-a-seat-names-the-paths-its-holder-owns-and-a-claim-without-them-is-refused.md index cb750fc5..02cc2748 100644 --- a/02-DECISIONS/0287-a-seat-names-the-paths-its-holder-owns-and-a-claim-without-them-is-refused.md +++ b/02-DECISIONS/0287-a-seat-names-the-paths-its-holder-owns-and-a-claim-without-them-is-refused.md @@ -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`. diff --git a/03-DESIGN/01-to-be/26-the-seats.md b/03-DESIGN/01-to-be/26-the-seats.md index 06543a12..5dc56c72 100644 --- a/03-DESIGN/01-to-be/26-the-seats.md +++ b/03-DESIGN/01-to-be/26-the-seats.md @@ -264,18 +264,22 @@ is compiled with the set and never stored, like what it needs, and changing it i **Holding the seat means declaring its paths.** A module that claims the seat declares every path it names, as a resource or as a file the mesh renders for it. `CanHold` refuses a claim without them, at registration -and at handover, naming the missing paths, beside the verbs it already requires. +and at handover, naming the missing paths, beside the verbs it already requires. A seat a module declares +for itself is held to the same rule by the registration check that judges what its holder serves. -**Only a claimant declares a seat's path.** Any other module declaring it is refused at registration, and +**Only a claimant declares a seat's path.** A module that claims no seat naming the path, and declares it, is +refused at registration, naming the seats that name it, and what the controller generates is refused at composition by the same rule, which is how the controller is held to writing no file a seat's holder owns. Reading the path is access and stays open to every module. **One seat per path, with a declared step-back.** Every path has exactly one seat naming it outright. A second seat may name it only *stepping back for* that seat: where the other seat is held on the machine, its -holder owns the path, and the stepping-back seat's holder does not declare it there. The uplink steps back +holder owns the path, and the stepping-back seat's holder still declares it, but its declaration is not +composed on that machine. The uplink steps back for a machine's own resolver this way ([ADR 0247](../../02-DECISIONS/0247-a-machine-with-a-vpn-client-routes-names-by-domain-through-a-resolver-of-its-own.md)), and the collision check reads that from the seat rather than from a case of its own. A module declaring a -seat that names a path another seat already names is refused at registration. +seat that names a path another seat already names is refused at registration: only the mesh's own seats +step back, each by a record. **The listing shows each seat's paths**, so the controller's `seats` verb answers who owns a path on a machine: the seat that names it, and that seat's holder there. @@ -339,7 +343,7 @@ checked as their tables say: | A replicated seat has every holder on record and composes on each; a seat held once still refuses a second holder, on record or not; `--add` is refused for it | 0223: resolution and composition tests with two holders on record and a third machine, with two claimants and nothing on record, and on `mesh-store`; a unit test that only `mesh-dns-resolver` is replicated, surviving the store's rows; store tests keeping several holders once each, a handover leaving one, and unassigning one taking only its row. | | A retired seat leaves the set once nothing claims it, from the compiled set and the store's table both | 0220: the closed-set test's count, and the store migration that deletes the row. | | A node seat names only literal absolute paths; every path has one seat naming it outright, and stepping back does not cycle | 0287: a unit test over the compiled set; registration tests refusing a module's seat naming a path at mesh scope, with `${`, or already named by another seat. | -| A holder declares every path its seat names, and only a claimant declares one | 0287: `CanHold` tests at registration and handover (declared as a resource, as a rendered file, not at all); a registration test refusing `/etc/hosts` from a module not claiming `node-hostname`; a composition test refusing a generated resource on a seat's path. | +| A holder declares every path its seat names, and only a claimant declares one | 0287: `CanHold` tests at registration and handover (declared as a resource, as a rendered file, not at all); a registration test refusing `/etc/hosts` from a module not 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 uplink's step-back comes from the seat | 0287: the composition tests for the uplink and the resolver on one machine pass with the case in the collision check removed. | | 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. |