diff --git a/02-DECISIONS/0211-a-machines-power-is-a-node-seat-its-moments-take-contributions-and-its-states-are-events.md b/02-DECISIONS/0211-a-machines-power-is-a-node-seat-its-moments-take-contributions-and-its-states-are-events.md new file mode 100644 index 0000000..d26259d --- /dev/null +++ b/02-DECISIONS/0211-a-machines-power-is-a-node-seat-its-moments-take-contributions-and-its-states-are-events.md @@ -0,0 +1,122 @@ +--- +topic: what runs on it +status: accepted +date: 2026-10-04 +deciders: jochen +reconstructed: false +extends: 02-DECISIONS/0210-a-tools-configuration-is-its-seat-holders-and-every-other-module-extends-it-through-the-seat.md +--- + +# 211. A machine's power is a node seat, its moments take contributions, and its states are events + +## Context + +The laptop's module needs code to run around sleep: +- the GPU driver's own suspend and resume actions; +- a touchpad reset after waking. + +It wrote drop-ins of its own into the service manager's sleep services, so it wrote into files that +belong to another tool's holder. [ADR 0210](0210-a-tools-configuration-is-its-seat-holders-and-every-other-module-extends-it-through-the-seat.md) +forbids exactly that. A second module wanting code after waking would do the same, and nothing would +order the two or say that either needs sleep to be handled at all. + +The mesh also cannot tell a sleeping machine from a lost one. A laptop with its lid closed stops its +heartbeat exactly as a crashed machine does, and is reported "out of touch" either way. +[Research 028](../01-RESEARCH/028-the-meshs-output-channel/00-overview.md) would turn every closed lid +into an alert. + +## Considered Options + +1. **Each module writes its own sleep drop-ins,** as the laptop's did. Rejected by ADR 0210: no + owner, no order, no dependency. +2. **The service manager's holder takes power hooks.** Rejected: sleep and power are logind's and the + firmware's concern, not service management's. On a laptop they also include lid, power source and + battery, which the service manager knows nothing about. +3. **A power seat on every machine.** Its holder: + - owns the machine's power handling; + - places code that modules contribute for named moments; + - publishes the machine's power states as events. + + Chosen. It was the operator's proposal. + +## Decision + +**1. `node-power` is a node seat in the mesh's own set.** One module per node holds it. Every +machine has one, servers included: every machine boots and shuts down. The first holder is a module +named `power`. + +**2. Its holder owns the machine's power handling:** +- logind's power-key and lid settings; +- the hooks around sleep, boot and shutdown; +- the reading of power source and battery where the machine has them. + +A model's specific values, such as what the lid does on that laptop, are the model's module's +contribution or a setting of `power` per node (ADR 0174), never a second writer of logind's +configuration. + +**3. Modules contribute code for named moments.** The moments: +- after boot; +- before sleep; +- after waking; +- before shutdown; +- on mains power; +- on battery. + +A contribution is POSIX shell code, written with ADR 0204's mechanism as ADR 0208 ยง4 did for the +session's start: +- a `shell` contribution whose `for` names the moment, in the `first`, `normal` or `last` slot; +- placed by the holder with `${shell::}` in the scripts its own units run; +- run as root, in module order, each piece bounded in time, so that one module's hang cannot hold a + machine awake. + +Per ADR 0210, a contribution for a moment depends on `node-power`. + +**4. Its states are the holder's events, on the bus:** +- `booted`, `sleeping`, `woke`, `shutting-down`; +- `on-mains`, `on-battery`, `battery-low`, where the machine has a battery. + +They carry the machine's role and a time, and nothing secret, so any node and the controller may +consume them. +- **`sleeping` is published before the machine sleeps.** The holder takes logind's delay lock, + publishes, and releases the lock once the bus has acknowledged, within logind's delay bound. +- **On waking,** the holder queues events until the bus is reachable, then publishes them in order. + +**5. A machine that said `sleeping` is asleep, not out of touch,** until it says `woke` or misses its +expected return. The controller shows the state, and the output channel (research 028) does not +treat a sleeping machine as a fault. + +## Consequences + +- The laptop's module moves its sleep drop-ins into contributions: + - the GPU driver's suspend and resume actions before sleep and after waking; + - its touchpad reset after waking. + + Its own files in the service manager's directories go. +- Assigning `power` to every machine is phase 1 of [to-be 42](../03-DESIGN/01-to-be/42-the-machines-modules-in-order.md). +- The controller gains the moments as contribution targets placed by `node-power`, and a node's + power state in what it shows about the node. +- **What got harder:** + - code that must run at a precise point inside the sleep transaction cannot be a contribution; the + GPU driver's own units are an example. Such code still declares its own units, and only the + request to run them is contributed; + - an event published around sleep depends on the network still being up. The delay lock buys the + time, and if the bus does not answer within it, the machine sleeps anyway and says so on waking. + +## How it is checked + +| Rule | Checked by | +|---|---| +| A moment's contribution derives a dependency on `node-power`, and lands in that moment's placeholder in module order | the controller's contribution tests | +| A contribution naming an unknown moment is refused | the catalogue check | +| One piece of hook code that hangs is ended after its bound, and the next still runs | the power module's tests over real child processes | +| `sleeping` is published before sleep and `woke` after, and a missed acknowledgement does not hold the machine awake | the power module's tests with a fake logind and bus, and a live suspend of the laptop | +| A machine that said `sleeping` is not reported out of touch | the controller's status test | + +## References + +- [ADR 0174](0174-a-node-varies-a-module-through-settings-and-kept-regions-never-an-edit.md), + [ADR 0198](0198-a-modules-long-running-code-is-launched-by-the-node-runtime-and-reaches-the-bus-through-it.md), + [ADR 0204](0204-a-module-contributes-shell-code-to-the-login-shell-in-named-slots.md), + [ADR 0208](0208-the-graphical-session-is-one-module-per-piece-on-the-meshs-seats.md), + [ADR 0210](0210-a-tools-configuration-is-its-seat-holders-and-every-other-module-extends-it-through-the-seat.md) +- [Research 028](../01-RESEARCH/028-the-meshs-output-channel/00-overview.md)