Files
hq/02-DECISIONS/0177-a-unit-may-be-user-scoped-and-the-service-manager-is-a-node-seat.md
T
jochen bf39baf104 Research 018 graduates: ADRs 0173–0177 and to-be 37, the operator's machine
Every configurable thing on a node is a module, the home included, and a
module is whatever it declares (0173, extending 0040). A node varies a module
only through a setting rendered into the file or a kept region, never an edit
(0174, extending 0011; issue 168 first). One tool runtime per node serves every
module's tools on the host side, never in a container; the console is its
serving mode, renamed node-tools (0175, extending 0150; 0047/0150/0152 carry
dated notes). The login shell is a node seat held by one shell module with
`execute` as its contract (0176). A unit may be user-scoped and the service
manager is a node seat held by systemd (0177).

To-be 37 is handed off in-progress to mesh-host, mesh-controller, mesh-tools
and mesh-catalog, with the build in order: the account on every node, the
runtime, zsh, systemd, then the graphical stack. To-be 29 keeps ~/.ssh and
points at 37; 33 §6 and 34 are amended; the glossary gains node tools, bundle,
kept region, installed/holding, and retires flavor.
2026-10-02 16:34:57 +02:00

81 lines
4.8 KiB
Markdown

---
topic: what runs on it
status: accepted
date: 2026-10-02
deciders: jochen
reconstructed: false
extends: 02-DECISIONS/0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md
---
# 177. A unit may be user-scoped, and the service manager is a node seat whose holder answers for the units
## Context
The host's `service` shape puts a system unit into a state. It has no user scope.
[To-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md) states the gap: *a
workstation's per-user daemons have no form the mesh can send.* Four of the predecessor's
environment modules ship user units — the desktop's reload watcher and bar watchdog, the audio
module's masks, the power module's memory guard, the thermal daemon's profile switcher — and the
predecessor needed a hook to enable them because *shipping a unit file does not run it*; one unit
was deployed for months and ran on one machine only.
[ADR 0040](0040-what-a-module-is.md) says the host hardcodes no supervisor, and a swappable
machine mechanism is a module implementing a capability — which is what the nftables module is for
the packet filter ([ADR 0170](0170-the-firewall-seat-serves-its-verbs.md)). The service manager is
reported today as a capability, `service-manager`, and held by nobody. The operator's proposal: a
systemd module that holds the seat and serves the tools about units, system and user.
## Considered Options
1. **Keep user units as a module concern** — each module runs `systemctl --user` in a hook.
Rejected: that is the hook that silently never ran, and an action over the link is refused.
2. **The service-manager module applies units** on behalf of others, as a provision. Rejected by
the operator: provisioning is for resources a provider creates for a consumer; a unit is
declared state the host applies, as every resource is.
3. **The host's `service` shape gains a user scope; a systemd module holds the service-manager
seat and serves the verbs about units.** Chosen.
## Decision
**1. The `service` shape gains `scope`: `system` (the default) or `user`.** A user-scoped unit
is applied as the operator account through the account's own service manager: enabled, started,
stopped, reloaded on its triggers, exactly as a system unit is, and refused on a node with no
account, naming the fact. The host applies it; no module does.
**2. `node-service-manager` is a seat of the mesh's own, node-scoped**, seeded by the controller
under this record, as ADR 0121 requires of a `node-*` name. The `systemd` module claims it and is
assigned to every machine whose profile reports `service-manager`.
**3. The seat's verbs answer for every unit on the machine**, each taking an optional `scope`:
`units`, `status`, `start`, `stop`, `restart`, `enable`, `disable`, `journal`. The host applies what
is declared; the holder answers questions and operator acts about it, and says, for a mesh-held
unit, that the host will restore what its declaration says.
## Consequences
- The host's vocabulary grows by one field on one shape, asserted by its count test
([to-be 05](../03-DESIGN/01-to-be/05-the-node-host.md)); an older host refuses a declaration
carrying it, so the host rolls before the first module that uses it.
- The predecessor's four user-unit modules become declarable without a hook.
- The seat's holder is the first system seat held by a module that runs nothing of its own: its
verbs are served by the node tools runtime ([ADR 0175](0175-one-tool-runtime-per-node-serves-every-modules-tools-on-the-host-side.md)).
- What got harder: `journal` and `status` on a user unit need the account's manager reachable
from the runtime's process, which runs as the node's account; the holder's tool escalates or
switches user as it needs, which is ADR 0175 §4 applied.
## How it is checked
| Rule | Checked by |
|---|---|
| A `service` with `scope: user` is enabled and started under the account, and refused with no account | the host's tests with a fake service manager |
| The seat declares its verbs; a claim serving fewer is refused by name | the catalogue's seat tests |
| The verbs act on a named unit in the named scope and name the unit's holder when the mesh declares it | the module's tests over a fake runner |
| Live | the desktop's reload watcher declared `scope: user` on a workstation; `node-service-manager.status@<node>` reports it active |
## References
- [Research 018](../01-RESEARCH/018-the-operators-machine-as-modules/04-the-seats-of-the-environment.md)
- [ADR 0121](0121-a-system-seat-is-named-for-its-scope-and-modules-define-their-own.md), [ADR 0132](0132-a-seat-carries-the-tools-its-holder-must-serve.md),
[ADR 0040](0040-what-a-module-is.md), [ADR 0170](0170-the-firewall-seat-serves-its-verbs.md)
- [To-be 05](../03-DESIGN/01-to-be/05-the-node-host.md), [to-be 29](../03-DESIGN/01-to-be/29-a-node-has-operator-accounts.md)