Merge pull request 'ADR 0252: a module puts an account in a group, and the mesh says when a new login is needed' (#201) from decision/0252-a-module-puts-an-account-in-a-group into main
This commit was merged in pull request #201.
This commit is contained in:
+12
-2
@@ -172,7 +172,7 @@ every other domain has something to run on. **Recorded by** the controller and t
|
||||
|
||||
**Purpose.** To decide, send and apply what each node runs, and to say why.
|
||||
**Recorded by** the controller's `inventory` database. **Upstream of** Provisioning, Connectivity, Data
|
||||
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221.
|
||||
and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221, 0252.
|
||||
**Uses:** node, machine, module, seat, setting (Module), manifest (Module).
|
||||
|
||||
- **assignment** — a module put on a node, with the settings that node gives it.
|
||||
@@ -206,6 +206,11 @@ and Health and repair. **Decided by** ADR 0100, 0126, 0176, 0181, 0207, 0221.
|
||||
against this account's home and owned by it
|
||||
([ADR 0181](../02-DECISIONS/0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md)).
|
||||
Not a name a manifest carries.
|
||||
- **account group** — a group of the machine's group database that a module puts an account in, by
|
||||
declaring the account with that group and nothing else of it. Several modules may each add one to the
|
||||
same account; the mesh takes back only an account group it added
|
||||
([ADR 0252](../02-DECISIONS/0252-a-module-puts-an-account-in-a-group-and-the-mesh-says-when-a-new-login-is-needed.md)).
|
||||
Never *membership*, which is the bus's word.
|
||||
|
||||
## Provisioning — one module serving another
|
||||
|
||||
@@ -320,11 +325,14 @@ planner, the walk). **Downstream of** Module, Core, Health and repair and Data.
|
||||
**Purpose.** To notice what is wrong with the mesh, say it once, repair what may be repaired unattended,
|
||||
and record what was done by hand. **Recorded by** the controller's condition store.
|
||||
**Upstream of** Change and delivery (the first-node gate) and Operator and conversation (a condition
|
||||
becomes a message). **Decided by** ADR 0227, 0231, 0240.
|
||||
becomes a message). **Decided by** ADR 0227, 0231, 0240, 0252.
|
||||
**Uses:** node, module, node-engine (Core).
|
||||
|
||||
- **health** — a module's or a core component's statement of how it is alive and ready, which the
|
||||
node-engine judges ([ADR 0240](../02-DECISIONS/0240-a-module-says-how-it-is-healthy-and-the-node-engine-judges-it.md)).
|
||||
- **relogin needed** — an account's health when the group database lists it in an account group and its
|
||||
running session, started before, does not have that group yet. Said by the node-engine as the module's
|
||||
resource of kind `account`; it clears at the first look after a new login (ADR 0252).
|
||||
- **probe** — one look at one thing's health, run by whoever owns the verdict; what a probe returns is a
|
||||
**finding**, before it becomes a condition.
|
||||
- **signal** and **watchdog** — something that must keep happening (a heartbeat, a report after a send),
|
||||
@@ -455,6 +463,8 @@ renaming one sense, the old sense moves to a *Not:* line and becomes mechanical.
|
||||
| | what a delivery would do to the mesh | **delivery plan** | Change and delivery |
|
||||
| | the sending of one commit across nodes | **walk** (verb `plans`) | Change and delivery |
|
||||
| | a step a person starts, like the bus's | **planned step** | Core |
|
||||
| membership | the subjects a bus account is issued | **membership** | Provisioning |
|
||||
| | an account in a group of the machine | **account group** | Placement |
|
||||
| push | the controller giving nodes their declarations | **send** (verb `push`) | Placement |
|
||||
| | a git push | **git push** | The record |
|
||||
| | a phone notification | **push notification** | Operator and conversation |
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
---
|
||||
topic: what runs on it
|
||||
status: accepted
|
||||
date: 2026-10-08
|
||||
deciders: jochen
|
||||
reconstructed: false
|
||||
extends: 02-DECISIONS/0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md
|
||||
---
|
||||
|
||||
# 252. A module puts an account in a group, and the mesh says when a new login is needed
|
||||
|
||||
## Context
|
||||
|
||||
The module of a peripheral-lighting daemon installs the daemon, its kernel driver and its client
|
||||
library. The driver's package makes a group and gives the devices' files to it. The daemon refuses to
|
||||
start for an account outside that group: "User is not a member of the openrazer group". On both
|
||||
workstations the operator's account was not in it, so the daemon failed at every start
|
||||
([issue 247](../04-ISSUES/247-a-module-cannot-put-the-operators-account-in-a-group/00-report.md)). The fix
|
||||
was a hand step with `sudo`, outside the mesh. Nothing recorded why the account was in the group, and
|
||||
nothing would take it out again.
|
||||
|
||||
What the mesh already had, measured on 2026-10-08 on the main branches:
|
||||
|
||||
- **The `user` resource takes `groups`, additively.** The node-engine appends with `usermod --append`
|
||||
and never removes a group. It never gives one back either, even when the resource that named it is
|
||||
undeclared.
|
||||
- **The controller already lets several modules declare one account**, since a change of 2026-10-04: a
|
||||
module may add groups to an account another module declares; two modules that both set its shell or
|
||||
its home are refused, naming both. Issue 247 and the lighting module's documentation, both written the
|
||||
day after, still said a second module would be refused. Neither was true any more, and nobody used the
|
||||
field.
|
||||
- **A module's accounts are composed before everything else of the module** (issue 213), so that a file
|
||||
owned by the account finds it made. An account declared only to be put in a group would then be put in
|
||||
a group whose package is not yet installed. The first apply fails that resource; the second heals it.
|
||||
- **A group takes effect at the next login.** The database changes at once. Every process already
|
||||
running keeps the groups it started with: the account's own service manager, and the daemon that
|
||||
manager starts. Nothing in the mesh said that a new login was the step left.
|
||||
|
||||
## Considered Options
|
||||
|
||||
1. **A new resource kind for one account group** (account, group), held by the module that needs it.
|
||||
It would read well. Every node-engine already running parses its declaration strictly and refuses one
|
||||
that names a kind it does not know, whole. So the kind could be used only once every machine that
|
||||
runs the module had a new node-engine, and a module assigned early would stop its machine applying
|
||||
anything. The `user` resource with `groups` alone says the same thing, and every node-engine already
|
||||
applies it. Rejected.
|
||||
2. **A contribution to the account's holder**, as ADR 0212 lets a module contribute to a seat. The
|
||||
operator's account is not a seat, and a machine with no login shell module would have nowhere to
|
||||
contribute to. Rejected.
|
||||
3. **Keep it a hand step, and have the module's check say it.** That is the state issue 247 reports.
|
||||
Rejected.
|
||||
4. **The `user` resource with `groups` and nothing else of the account**, from any module; the mesh
|
||||
takes back only what it added; and the node-engine says when a new login is needed. Chosen.
|
||||
|
||||
## Decision
|
||||
|
||||
1. **A module that needs an account in a group declares the account as a `user` resource with that
|
||||
group, and nothing else of it.** For the operator's account the name is `${machine:account}`. The
|
||||
word for such a group is **account group**. The shell and the home stay one module's per machine,
|
||||
as before.
|
||||
2. **Such a resource keeps its written place.** The controller composes a user resource that names
|
||||
groups and no shell, home or lingering where the module wrote it, not first. A module writes it after
|
||||
the package that makes the group. An account that says how it logs in or where it lives is still
|
||||
composed first (issue 213).
|
||||
3. **The node-engine takes back only what it gave.** It records on the resource every account group it
|
||||
put the account in that the account was not in before. When the resource no longer asks for one, or
|
||||
is undeclared, the account leaves that group. It does not leave it while another declared resource
|
||||
still asks for the same group. A group the account was in before the mesh named it is never
|
||||
recorded, and never taken back. The account is taken out of one group only (`gpasswd --delete`, or
|
||||
busybox `delgroup`), read back, and a failure is said and is not fatal.
|
||||
4. **A group the machine does not have fails that resource, saying so.** The node-engine asks the group
|
||||
database before `usermod` runs, and the outcome names the package that should have made it.
|
||||
5. **The node-engine says when a new login is needed.** The apply's outcome says that a session that
|
||||
began before has the group only after a new login. On every look after that, the node-engine reads,
|
||||
for each account a module put in account groups:
|
||||
- whether the database lists the account in each group;
|
||||
- whether the account's own service manager runs, read from the machine's own manager, which never
|
||||
starts it;
|
||||
- if it runs, the groups that process holds, from its status file.
|
||||
|
||||
It states the verdict as the module's resource of kind `account`. **Healthy** means the running
|
||||
manager has every group, or nobody is logged in. **Unhealthy** says why: *not in the group*, or
|
||||
*relogin needed: the account is in the group, and its running session began before it was; log out
|
||||
of every session and in again, or reboot*. The controller raises it as the module's condition on two
|
||||
statements in a row and clears it on the first healthy one, as it does every resource's health
|
||||
(ADR 0240). The first-node gate reads it like any other: a module whose account needs a new login is
|
||||
not yet good on that machine.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The lighting module declares the operator's account in its group. On a workstation whose account is
|
||||
not in it, the next send puts it there, and the module's condition says *relogin needed* until the
|
||||
operator logs out and in again. Nobody runs `sudo` for it.
|
||||
- **A new login holds a delivery of that module on that machine.** The first-node gate does not pass a
|
||||
module whose account needs a new login, so a walk that starts on a workstation waits for the person.
|
||||
The daemon cannot run before then, so a pass would be the false success ADR 0240 exists to prevent.
|
||||
- A node-engine older than this one still puts the account in the group, since the field is the one it
|
||||
already applies. It says nothing about the login, and it never takes the group back. A group it added
|
||||
is not recorded, so a later node-engine never takes it back either.
|
||||
- An account group two resources ask for is kept until the last one goes. If the one that added it goes
|
||||
first, the other found it already there and never recorded it, so it stays with the account when that
|
||||
one goes too. The mesh errs towards keeping a group, never towards taking one a module still needs.
|
||||
- The account's other groups, and every group a person added by hand, are never changed.
|
||||
|
||||
## How it is checked
|
||||
|
||||
- **The node-engine** (`mesh-host`), in `internal/apply/groups_test.go`:
|
||||
- an account group is added with `usermod --append` alone, recorded, and said with "new login";
|
||||
- a group the account was in before is never recorded and never taken back;
|
||||
- an undeclared resource gives back the group it added;
|
||||
- a group another declared resource still asks for stays, and the outcome says which;
|
||||
- a group no longer declared is given back while the account stays declared;
|
||||
- a group the machine does not have fails the resource before `usermod` runs;
|
||||
- a group a person removed is put back while it is declared.
|
||||
- **The node-engine's account judge**, in `internal/accounts/accounts_test.go`: *relogin needed* when
|
||||
the running manager lacks the group; healthy when it has it or none runs; *not in the group* when the
|
||||
database does not list it; unknown, never healthy, when the database does not answer. It also checks
|
||||
that every command the judge runs is a read.
|
||||
- **The controller** (`mesh-controller`), in `internal/catalogue/shared_account_test.go`: an account
|
||||
declared with groups alone is composed after the module's package, and one with a shell is still
|
||||
composed first. The test beside it holds that two modules' groups on one account resolve together.
|
||||
- **The catalogue** (`mesh-catalog`): the lighting module's manifest test holds its one account
|
||||
resource, with that group alone and written after its packages.
|
||||
- **Live**, once the operator's send has reached a workstation: openrazer's condition on that machine
|
||||
says *relogin needed*; after a new login it clears, and `openrazer_check` answers `ok`.
|
||||
|
||||
## References
|
||||
|
||||
- [Issue 247](../04-ISSUES/247-a-module-cannot-put-the-operators-account-in-a-group/00-report.md)
|
||||
- [ADR 0176](0176-the-login-shell-is-a-node-seat-and-execute-is-its-contract.md) (the `user` resource gives back
|
||||
the shell it set), [ADR 0177](0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md)
|
||||
(the account's own manager, and why it is never asked directly),
|
||||
[ADR 0181](0181-the-operator-account-is-a-node-fact-and-a-home-is-a-placement-root.md) (the operator
|
||||
account), [ADR 0240](0240-a-module-says-how-it-is-healthy-and-the-node-engine-judges-it.md) (the
|
||||
node-engine judges health and the controller raises it)
|
||||
- [To-be 41](../03-DESIGN/01-to-be/41-the-shell-and-the-accounts-environment.md), WP6
|
||||
@@ -2,8 +2,9 @@
|
||||
layer: to-be
|
||||
status: in-progress
|
||||
code: [mesh-host, mesh-controller, mesh-catalog]
|
||||
updated: 2026-10-04
|
||||
updated: 2026-10-08
|
||||
decisions:
|
||||
- 02-DECISIONS/0252-a-module-puts-an-account-in-a-group-and-the-mesh-says-when-a-new-login-is-needed.md
|
||||
- 02-DECISIONS/0203-the-accounts-environment-is-one-modules-and-every-module-contributes-to-it.md
|
||||
- 02-DECISIONS/0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md
|
||||
- 02-DECISIONS/0205-software-the-distribution-does-not-package-ships-as-a-pinned-archive-of-the-module.md
|
||||
@@ -83,6 +84,7 @@ WP2 the controller composes environment and shell code (mesh-controller)
|
||||
WP3 the modules (mesh-catalog) needs WP2 to resolve
|
||||
WP4 the service manager's module, finished (mesh-catalog) independent
|
||||
WP5 assign and prove (operator-gated) needs WP1–WP3 merged and rolled
|
||||
WP6 a module puts the account in a group (mesh-host, mesh-controller, mesh-catalog) issue 247
|
||||
```
|
||||
|
||||
WP1, WP2 and WP4 are independent, and are built in parallel on one feature branch per repository
|
||||
@@ -236,3 +238,57 @@ The follow-up records to-be 38 names are still owed:
|
||||
|
||||
- what a shell module assigned beside the holder does;
|
||||
- how a person's own environment variable is a setting rather than a line, once issue 168 closes.
|
||||
|
||||
## WP6 — A module puts the account in a group
|
||||
|
||||
*`mesh-host`, `mesh-controller`, `mesh-catalog`. One day.
|
||||
[Issue 247](../../04-ISSUES/247-a-module-cannot-put-the-operators-account-in-a-group/00-report.md),
|
||||
[ADR 0252](../../02-DECISIONS/0252-a-module-puts-an-account-in-a-group-and-the-mesh-says-when-a-new-login-is-needed.md).*
|
||||
|
||||
The shell's module owns the account's shell. Another module may need the same account in an **account
|
||||
group**: the lighting daemon's, the container runtime's, a serial port's. It declares the account as a
|
||||
`user` resource naming that group and nothing else of it.
|
||||
|
||||
```
|
||||
zsh user ${machine:account} shell /usr/bin/zsh composed first (issue 213)
|
||||
openrazer package ×3 … then user ${machine:account} groups [openrazer] composed where written
|
||||
│
|
||||
node-engine usermod --append ── recorded on openrazer.account: "the mesh added openrazer"
|
||||
│
|
||||
account judge database lists it? ── running manager holds it? ── no: relogin needed
|
||||
│ (module openrazer, kind account)
|
||||
controller module.openrazer.<machine>.unhealthy, cleared on the first look after a new login
|
||||
```
|
||||
|
||||
**What changes.**
|
||||
|
||||
- **The controller** composes a user resource that names groups and no shell, home or lingering where
|
||||
its module wrote it, so after the package that makes the group. Any other account is still composed
|
||||
first.
|
||||
- **The node-engine, applying.** It asks the group database before adding, and a group the machine does
|
||||
not have fails the resource with that reason. It adds with `usermod --append` alone. It records each
|
||||
group it added that the account was not in before. It says in the outcome that a session that began
|
||||
before has the group only after a new login.
|
||||
- **The node-engine, removing.** A group this resource added and no longer asks for, or every such group
|
||||
when the resource is undeclared, is given back. It is not given back while another declared resource
|
||||
asks for it. The account is taken out of that one group (`gpasswd --delete`), read back, and a failure
|
||||
is said and is not fatal, as giving back a shell is (WP1).
|
||||
- **The node-engine, looking.** An account judge sits beside liveness and the failed-unit reading
|
||||
(issue 315). On every look it reads, for each account a module puts in groups, the database's groups
|
||||
and the groups of the account's running manager, from that process's status file. It states each as the
|
||||
module's resource of kind `account`: healthy, or unhealthy with *not in the group* or *relogin needed*.
|
||||
It only reads, and never asks the account's own manager, which asking would start (ADR 0177).
|
||||
- **The lighting module** declares `${machine:account}` with the group `openrazer`, after its three
|
||||
packages. Its check's finding for an account outside the group now says the module declares it, and
|
||||
that a new login follows.
|
||||
|
||||
**Proof.** The tests ADR 0252 lists under *how it is checked*, in each of the three repositories. Live,
|
||||
on the first workstation the operator sends the lighting module to:
|
||||
|
||||
1. the apply's outcome for `openrazer.account` says *put in openrazer*;
|
||||
2. the module's condition on that machine says *relogin needed*;
|
||||
3. after the operator logs out and in again, the condition clears and `openrazer_check` answers `ok`.
|
||||
|
||||
**Order.** The three repositories merge in any order. A node-engine older than WP6 already applies the
|
||||
lighting module's resource: it puts the account in the group. It does not say the login, and does not
|
||||
take the group back.
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
status: open
|
||||
status: resolved
|
||||
opened: 2026-10-05
|
||||
located-in: []
|
||||
fixed-by:
|
||||
amended-design:
|
||||
located-in: [mesh-host internal/apply (the user resource's groups), mesh-controller internal/catalogue (accountsFirst), mesh-catalog modules/openrazer]
|
||||
fixed-by: [mesh-host PR 54, mesh-controller PR 139, mesh-catalog PR 125]
|
||||
amended-design: 03-DESIGN/01-to-be/41-the-shell-and-the-accounts-environment.md
|
||||
replay-none: not an incident of something that ran wrongly but a capability the manifest could not use; there is no earlier run to replay. The fix's own tests in each repository hold it (ADR 0252, how it is checked)
|
||||
---
|
||||
|
||||
# 247 — A module cannot put the operator's account in a group
|
||||
@@ -52,3 +53,30 @@ account in the group, says that a new login is needed, and leaves the account's
|
||||
were. Unassigning it removes only a membership the module added.
|
||||
|
||||
Re-checked 2026-10-08: still holds — the node-engine on main has no resource for one group membership, and the lighting module declares none.
|
||||
|
||||
## Resolution — 2026-10-08
|
||||
|
||||
Decided in [ADR 0252](../../02-DECISIONS/0252-a-module-puts-an-account-in-a-group-and-the-mesh-says-when-a-new-login-is-needed.md),
|
||||
built as [to-be 41](../../03-DESIGN/01-to-be/41-the-shell-and-the-accounts-environment.md) WP6.
|
||||
|
||||
**One statement above was already wrong when it was written.** Since 2026-10-04 the controller has let
|
||||
a second module declare the account with groups only. It refuses only two modules that set the
|
||||
account's shell or home. Nobody used that, because this report and the lighting module's documentation
|
||||
both said it was refused. What was really missing:
|
||||
|
||||
- the account was composed before the module's packages, so on a fresh machine its group did not exist
|
||||
yet;
|
||||
- nothing recorded which groups the mesh added, so nothing could be given back;
|
||||
- nothing said that a new login was the step left.
|
||||
|
||||
The answers to the open questions:
|
||||
|
||||
1. **Neither a new resource nor a contribution.** It is the `user` resource with `groups` and nothing
|
||||
else of the account, which every node-engine already applies. A new resource kind would have been
|
||||
refused, whole, by every node-engine older than it.
|
||||
2. **A finding, stated as health.** The apply's outcome says a new login is needed. The node-engine's
|
||||
account judge states the module's resource of kind `account` unhealthy with *relogin needed* until the
|
||||
account's running service manager has the group, and the controller raises it as the module's
|
||||
condition.
|
||||
3. **Kept.** A group the account was in before any module named it is never recorded and never taken
|
||||
back. Only a group the mesh added is given back, and not while another declared resource asks for it.
|
||||
|
||||
Reference in New Issue
Block a user