Keep libraries under a group of their own, and give each joining module a user in it (hq ADR 0328, issue 567) #95

Merged
mesh-admin merged 4 commits from feat/328-libraries-and-module-users into main 2026-10-12 20:28:17 +00:00
Contributor

Why: hq ADR 0328 (accepted with the operator's Approve, 2026-10-12) supersedes ADR 0325: a module asks for a library by name and use; the mesh keeps each library under a group mesh-<library> and gives each joining module a mesh-<module> user whose primary group is its library's. Issue 567.

What (node-engine half, contract 6 link.LibraryContract; nothing new happens until the controller sends contract-6 fields):

  • groups, identities (accounts with home /nonexistent, so a primary-group change never regroups files; a / home is moved off without -m first), libraries per machine; refused: 0/root groups, clashes, nested roots, the machine's own trees. Alpine via adduser/addgroup/deluser/delgroup.
  • Keep without a warrant only an empty root or one in shape, root and a bounded sample beneath (fails closed); sub-paths holding old data wait; new/empty sub-paths made with the root's owner, the group, g+rwx, setgid.
  • Take act on the operator's warrant (library:<name>): chgrp + g+rw / g+rwx+s recursively, owner/owner bits/others never changed, no link, same filesystem, bounded and resumable; hardlinked files regrouped when an account owns them, root-owned ones left (counts said); setuid/setgid cleared on regrouped files and never restored.
  • Disk not there / unnamed filesystem: containers held, waits reason disk/unknown; tool runner joins the library group.
  • ADR 0325's ACL code, mask repair and watch removed. No ADR 0325 take or identity ever ran anywhere (its controller half was never merged), so no ACL cleanup is needed.
  • Old-shape declarations applied exactly as before.

Known limits (from review): a ZFS pool not imported on first sight is not known to be missing until it mounts (then the move check makes it wait); an apply arriving in the instant before a take walk registers waits for that walk; the walk's own registration has no direct test.

Checked: merge-check.sh green (whole suite under -race); as root in a user namespace only issue 358's namespace-only failure. Independent adversarial review over four rounds, every finding fixed with a failing-first test (mutation-checked); final verdict APPROVE ("would run it as root against the 12k-file library").

Delivery effects: node-engine restarts on ace, g14, novox, shanks; nothing changes on any folder until the controller half is delivered.

**Why**: hq ADR 0328 (accepted with the operator's Approve, 2026-10-12) supersedes ADR 0325: a module asks for a library by name and use; the mesh keeps each library under a group `mesh-<library>` and gives each joining module a `mesh-<module>` user whose primary group is its library's. Issue 567. **What** (node-engine half, contract 6 `link.LibraryContract`; nothing new happens until the controller sends contract-6 fields): - `groups`, `identities` (accounts with home `/nonexistent`, so a primary-group change never regroups files; a `/` home is moved off without `-m` first), `libraries` per machine; refused: 0/root groups, clashes, nested roots, the machine's own trees. Alpine via adduser/addgroup/deluser/delgroup. - Keep without a warrant only an empty root or one in shape, root and a bounded sample beneath (fails closed); sub-paths holding old data wait; new/empty sub-paths made with the root's owner, the group, g+rwx, setgid. - Take act on the operator's warrant (`library:<name>`): chgrp + g+rw / g+rwx+s recursively, owner/owner bits/others never changed, no link, same filesystem, bounded and resumable; hardlinked files regrouped when an account owns them, root-owned ones left (counts said); setuid/setgid cleared on regrouped files and never restored. - Disk not there / unnamed filesystem: containers held, waits reason `disk`/`unknown`; tool runner joins the library group. - ADR 0325's ACL code, mask repair and watch removed. No ADR 0325 take or identity ever ran anywhere (its controller half was never merged), so no ACL cleanup is needed. - Old-shape declarations applied exactly as before. **Known limits** (from review): a ZFS pool not imported on first sight is not known to be missing until it mounts (then the move check makes it wait); an apply arriving in the instant before a take walk registers waits for that walk; the walk's own registration has no direct test. **Checked**: `merge-check.sh` green (whole suite under `-race`); as root in a user namespace only issue 358's namespace-only failure. Independent adversarial review over four rounds, every finding fixed with a failing-first test (mutation-checked); final verdict APPROVE ("would run it as root against the 12k-file library"). **Delivery effects**: node-engine restarts on ace, g14, novox, shanks; nothing changes on any folder until the controller half is delivered.
mesh-admin added 4 commits 2026-10-12 20:23:06 +00:00
ADR 0328 supersedes ADR 0325: shared data is a library with one home, kept usable by
its group rather than by ACL entries. The engine now makes each library's group and
each module's user in its libraries' groups; keeps a library's root and requested
sub-paths in its group with group write and setgid on its home, making missing
sub-paths as the root is; waits for the operator's word before touching a root that
holds data, and on the take act walks it by descriptor (no link, same filesystem,
bounded and resumed, files with other names left); holds the containers of a library
whose disk or mount is not there; and puts the tool runner's account in the group.
The ACL keeping, mask repair and watch are gone; an access is again as before ADR 0325.
A root in the group with setgid was kept without a warrant even when the data beneath
it was not in the group, which §7 says the operator is asked about. A root is now
kept without asking only when it is empty, or it and a bounded sample beneath it are
all in the group with group write (and setgid on folders). On busybox, an account the
mesh made is given its added groups with addgroup and delgroup; a change of its
primary group is refused, saying why.
usermod --gid regroups every file under an account's home, and accounts were made
with / as home: they now have /nonexistent, and an older account's home is moved
there (without its files) before its group changes. A sub-path asked for later that
holds data not in the group waits for the operator as the root would. The take act
gives the group to hardlinked files an account owns (the *arr apps' downloads) and
leaves root's; a file's setuid or setgid bit the regroup takes is never put back, so
no setgid file hands out the library's group; each is counted. An apply no longer
waits for a running take walk.
Put busybox's account removal back, test the sample, and read a held lock as kept only for a running walk (hq ADR 0328 review)
mesh/merge-gate head 281837847e03 merged into trunk 303d4037e84e — pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes wi…
mesh/repo-check head 281837847e03 merged into trunk 303d4037e84e — pass: its merge-check.sh passed
mesh/delivery delivered
281837847e
An earlier change dropped alpine's RemoveAccount and RemoveGroup, so alpine made no
module identity at all. The sample beneath a root in shape had no test of its own.
And an apply that found the library's lock held read the library as kept even when
what held it was a take act about to be refused for a missing disk; it now does so
only while a take walk is running, and otherwise waits and judges as always.
Author
Contributor

Delivery novox/mesh-host@281837847e03 — delivered since 2026-10-12T20:32:10Z

Delivery plan — builds mesh-host → ace, g14, novox, shanks; no bus step

  • tier 0: mesh-host
  • ace: receives mesh-host
  • g14: receives mesh-host
  • novox: receives mesh-host
  • shanks: receives mesh-host
  • grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before mesh-host is judged on its first machine

Machines

  • mesh-host on ace: passed — healthy 3 times over 2m39s
  • mesh-host on the rest running it: sent

Transitions

  • 2026-10-12 20:23 (new) → proposed (announced): novox/mesh-host#95's head announced
  • 2026-10-12 20:26 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit
  • 2026-10-12 20:26 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail
  • 2026-10-12 20:28 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move
  • 2026-10-12 20:28 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit
  • 2026-10-12 20:32 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says

The commit's note under refs/notes/mesh-plan keeps every transition: git log --notes=mesh-plan.

<!-- mesh-delivery:view --> **Delivery** `novox/mesh-host@281837847e03` — **delivered** since 2026-10-12T20:32:10Z **Delivery plan** — builds mesh-host → ace, g14, novox, shanks; no bus step - tier 0: mesh-host - ace: receives mesh-host - g14: receives mesh-host - novox: receives mesh-host - shanks: receives mesh-host - grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before mesh-host is judged on its first machine **Machines** - mesh-host on ace: passed — healthy 3 times over 2m39s - mesh-host on the rest running it: sent **Transitions** - 2026-10-12 20:23 (new) → proposed (announced): novox/mesh-host#95's head announced - 2026-10-12 20:26 proposed → checked (checked): a verdict names this commit and the trunk's head as its trunk commit - 2026-10-12 20:26 checked → ready (accepted): the gate passed or warned, and the repository's own check did not fail - 2026-10-12 20:28 ready → published (merged): on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move - 2026-10-12 20:28 published → delivering (go): its walk started: let go by this owner in its group's order, by a person, or on the controller's own path — not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit - 2026-10-12 20:32 delivering → delivered (done): its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says The commit's note under `refs/notes/mesh-plan` keeps every transition: `git log --notes=mesh-plan`.
Author
Contributor

Merge check at 28183784
Judged: head 281837847e03 merged into trunk 303d4037e84e; read beside it: mesh-catalog 9c9a76d220f8, mesh-controller 13711fb5b91b, mesh-controller-main 13be75eb65c0, mesh-tools 954e1a53eef9

  • mesh/merge-gate: PASS — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-host)
  • mesh/repo-check: PASS — its merge-check.sh passed

Delivery plan — builds mesh-host → ace, g14, novox, shanks; no bus step

  • tier 0: mesh-host
  • ace: receives mesh-host
  • g14: receives mesh-host
  • novox: receives mesh-host
  • shanks: receives mesh-host
  • grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before mesh-host is judged on its first machine
  • read by no module's build: cmd/mesh-host/kept_test.go, cmd/mesh-host/libraries_test.go, cmd/mesh-host/units_test.go, internal/apply/contract_test.go, internal/apply/identities_test.go, internal/apply/kept_test.go, internal/apply/library_test.go, internal/apply/oldshape_test.go, internal/system/identities_test.go

Run by the build seat on g14 as build-1791836608800982746 (builds --log build-1791836608800982746).

--- the gate: mesh-host
mesh-host: ok
long-running resources without health: 0
files asking for a setting without saying whether it is trusted: 0
1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here
judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T20:19:09Z says they run
composing every machine as the mesh is
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
composing every machine with the change
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued
merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose)
judged by controller development build against the facts of 2026-10-12T20:19:09Z

machines:
  - a machine (f35): composes
  - holds mesh-dns-resolver (idt): composes
  - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes
  - a machine (yvbpsm): composes

a merge would rebuild 1 module(s) in 1 tier(s): mesh-host

notes:
  - cmd/mesh-host/kept_test.go, cmd/mesh-host/libraries_test.go, cmd/mesh-host/units_test.go, internal/apply/contract_test.go, internal/apply/identities_test.go, internal/apply/kept_test.go, internal/apply/library_test.go, internal/apply/oldshape_test.go and internal/system/identities_test.go read by no module's build, and rebuild nothing (issue 280)
--- its merge-check.sh (go toolchain)
ok  	github.com/novox/mesh-host/cmd/mesh-bootstrap	1.015s
ok  	github.com/novox/mesh-host/cmd/mesh-host	5.422s
ok  	github.com/novox/mesh-host/examples	1.016s
ok  	github.com/novox/mesh-host/internal/accounts	1.379s
ok  	github.com/novox/mesh-host/internal/apply	2.619s
ok  	github.com/novox/mesh-host/internal/bootstrap	1.368s
ok  	github.com/novox/mesh-host/internal/bundle	1.016s
ok  	github.com/novox/mesh-host/internal/declaration	1.056s
ok  	github.com/novox/mesh-host/internal/firewall	1.731s
ok  	github.com/novox/mesh-host/internal/identity	1.063s
ok  	github.com/novox/mesh-host/internal/image	1.015s
ok  	github.com/novox/mesh-host/internal/inventory	1.047s
ok  	github.com/novox/mesh-host/internal/link	10.510s
ok  	github.com/novox/mesh-host/internal/liveness	2.079s
ok  	github.com/novox/mesh-host/internal/meshcli	1.568s
ok  	github.com/novox/mesh-host/internal/network	1.048s
ok  	github.com/novox/mesh-host/internal/outward	1.036s
ok  	github.com/novox/mesh-host/internal/profile	1.520s
ok  	github.com/novox/mesh-host/internal/reachable	1.024s
ok  	github.com/novox/mesh-host/internal/store	1.081s
ok  	github.com/novox/mesh-host/internal/system	1.015s
ok  	github.com/novox/mesh-host/internal/tunnel	1.017s
ok  	github.com/novox/mesh-host/internal/units	1.011s
ok  	github.com/novox/mesh-host/internal/upgrade	1.039s
ok  	github.com/novox/mesh-host/internal/witness	1.412s
ok  	github.com/novox/mesh-host/packaging	1.011s
?   	github.com/novox/mesh-host/rootsearch	[no test files]
ok  	github.com/novox/mesh-host/validate	1.011s
**Merge check** at `28183784` Judged: head `281837847e03` merged into trunk `303d4037e84e`; read beside it: mesh-catalog `9c9a76d220f8`, mesh-controller `13711fb5b91b`, mesh-controller-main `13be75eb65c0`, mesh-tools `954e1a53eef9` - `mesh/merge-gate`: **PASS** — every machine composes with the change as it did without (4 of 4 compose) (modules: mesh-host) - `mesh/repo-check`: **PASS** — its merge-check.sh passed **Delivery plan** — builds mesh-host → ace, g14, novox, shanks; no bus step - tier 0: mesh-host - ace: receives mesh-host - g14: receives mesh-host - novox: receives mesh-host - shanks: receives mesh-host - grants step: if this changes what the bus's user list says (a new tool, subject or user), novox is sent that list alone, every build there kept, before mesh-host is judged on its first machine - read by no module's build: cmd/mesh-host/kept_test.go, cmd/mesh-host/libraries_test.go, cmd/mesh-host/units_test.go, internal/apply/contract_test.go, internal/apply/identities_test.go, internal/apply/kept_test.go, internal/apply/library_test.go, internal/apply/oldshape_test.go, internal/system/identities_test.go Run by the build seat on g14 as `build-1791836608800982746` (`builds --log build-1791836608800982746`). ``` --- the gate: mesh-host mesh-host: ok long-running resources without health: 0 files asking for a setting without saying whether it is trusted: 0 1 manifest(s) checked. Judged against the seats this binary carries; a claim on one of the mesh's own seats is judged fully at registration, and a seat declared by a module not given here reads as unknown. Identities judged on a 6-character machine name, against the bounds of the providers given here judging 0 module(s) changed against 4 machine(s), as the snapshot of 2026-10-12T20:19:09Z says they run composing every machine as the mesh is the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued composing every machine with the change the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued the bus's user list leaves out 5 user(s) the mesh has minted no credential for: controller, node.f35, node.idt, node.ygcyr, node.yvbpsm. Each is a user that cannot connect until one is issued merge gate: PASS — every machine composes with the change as it did without (4 of 4 compose) judged by controller development build against the facts of 2026-10-12T20:19:09Z machines: - a machine (f35): composes - holds mesh-dns-resolver (idt): composes - holds git, holds mesh-artifact-store, holds mesh-broker, holds mesh-catalog, holds mesh-delivery, holds mesh-dns-resolver, holds mesh-store, holds mesh-vault, holds npm-package-registry, the control node, the hub (ygcyr): composes - a machine (yvbpsm): composes a merge would rebuild 1 module(s) in 1 tier(s): mesh-host notes: - cmd/mesh-host/kept_test.go, cmd/mesh-host/libraries_test.go, cmd/mesh-host/units_test.go, internal/apply/contract_test.go, internal/apply/identities_test.go, internal/apply/kept_test.go, internal/apply/library_test.go, internal/apply/oldshape_test.go and internal/system/identities_test.go read by no module's build, and rebuild nothing (issue 280) --- its merge-check.sh (go toolchain) ok github.com/novox/mesh-host/cmd/mesh-bootstrap 1.015s ok github.com/novox/mesh-host/cmd/mesh-host 5.422s ok github.com/novox/mesh-host/examples 1.016s ok github.com/novox/mesh-host/internal/accounts 1.379s ok github.com/novox/mesh-host/internal/apply 2.619s ok github.com/novox/mesh-host/internal/bootstrap 1.368s ok github.com/novox/mesh-host/internal/bundle 1.016s ok github.com/novox/mesh-host/internal/declaration 1.056s ok github.com/novox/mesh-host/internal/firewall 1.731s ok github.com/novox/mesh-host/internal/identity 1.063s ok github.com/novox/mesh-host/internal/image 1.015s ok github.com/novox/mesh-host/internal/inventory 1.047s ok github.com/novox/mesh-host/internal/link 10.510s ok github.com/novox/mesh-host/internal/liveness 2.079s ok github.com/novox/mesh-host/internal/meshcli 1.568s ok github.com/novox/mesh-host/internal/network 1.048s ok github.com/novox/mesh-host/internal/outward 1.036s ok github.com/novox/mesh-host/internal/profile 1.520s ok github.com/novox/mesh-host/internal/reachable 1.024s ok github.com/novox/mesh-host/internal/store 1.081s ok github.com/novox/mesh-host/internal/system 1.015s ok github.com/novox/mesh-host/internal/tunnel 1.017s ok github.com/novox/mesh-host/internal/units 1.011s ok github.com/novox/mesh-host/internal/upgrade 1.039s ok github.com/novox/mesh-host/internal/witness 1.412s ok github.com/novox/mesh-host/packaging 1.011s ? github.com/novox/mesh-host/rootsearch [no test files] ok github.com/novox/mesh-host/validate 1.011s ```
mesh-admin merged commit d8e785bb4b into main 2026-10-12 20:28:17 +00:00
mesh-admin deleted branch feat/328-libraries-and-module-users 2026-10-12 20:28:17 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: novox/mesh-host#95