Merge pull request 'Issue 332: the service manager could neither clear a failed unit nor say what wants it' (#222) from issues/332-the-service-manager-cannot-clear-or-explain-a-failed-unit into main

This commit was merged in pull request #222.
This commit is contained in:
2026-10-08 15:52:54 +00:00
@@ -0,0 +1,75 @@
---
status: located
opened: 2026-10-08
located-in: [mesh-controller internal/catalogue (seats.go, the node-service-manager seat's verbs), mesh-catalog modules/systemd (the seat's holder)]
fixed-by:
amended-design:
---
# 332. The service manager cannot clear or explain a failed unit
## Symptom
On 2026-10-08 the workstation's machine finding (`machine.<workstation>.units`, from
[issue 315](../315-a-failed-unit-of-a-module-was-never-raised/00-report.md)) listed a user unit of a
clipboard tool that a module had replaced: load state `not-found`, active state `failed`, no unit file.
The operator approved removing it. Two steps were needed, and the `node-service-manager` seat had a verb
for neither:
1. **Find what still asks for the unit.** The seat's `status` says the file is gone, and its `journal`
says when the unit last failed. Neither says which unit wants it, or whether an enable link in a
`*.wants` directory still names it and dangles. Issue 315's diagnosis found the link from the module's
README, not from a tool.
2. **Clear the failed record.** A manager keeps a unit's failed state and start-limit count until they
are reset. The seat had no verb for `systemctl reset-failed`, so the finding stays raised until the
account's manager restarts.
Without either verb, the step left is a shell on the machine, which
[ADR 0245](../../02-DECISIONS/0245-a-verb-says-what-it-replaces-and-the-agent-is-guarded-from-working-round-the-mesh.md) rules out: the missing
tool is built in the module that owns the area, on its seat.
## Fix
Two verbs on the seat, each saying the command it replaces:
- **`reset-failed {unit, scope}`** clears one unit's failed record. A unit is required, so the verb never
resets every failed unit at once. It replaces `systemctl reset-failed`.
- **`wanted-by {unit, scope}`** is read-only. It answers what wants, requires or triggers one unit: the
manager's reverse dependencies (`WantedBy`, `RequiredBy`, `UpheldBy`, `BoundBy`, `TriggeredBy` and the
rest, and the units that pull those in), and every enable link naming the unit in the scope's
configuration directories. In user scope that includes the account's own. Each link is given with where
it points, whether it dangles and whose directory it is in. It replaces `systemctl list-dependencies
--reverse` and reading the `*.wants` directories by hand.
A third verb followed from the review of the first two. `systemctl disable` does not remove an enable
link once the unit's file is gone, so the link found by `wanted-by` had no verb to remove it either:
- **`unlink-dangling {unit, scope, why}`** removes the dangling enable links named for one unit, only in
the account's or the machine's own configuration, on the person's word. It never removes a package's, a
generator's or the runtime's link, and leaves any link that changed since it was read. Each link is
recorded with its target and the reason before it is removed.
The review also made every verb refuse a unit name holding `*`, `?` or `[`: the service manager expands a
glob to every unit it matches, so `reset-failed` or `stop` of `*` would have acted on all of them.
They land in the three steps of
[ADR 0246](../../02-DECISIONS/0246-a-seats-new-verb-is-promised-before-it-is-required.md):
1. The controller promises the verbs as optional: mesh-controller #158 (`reset-failed`, `wanted-by`) and
#160 (`unlink-dangling`).
2. The `systemd` module serves them: mesh-catalog #137 and #138. A module's check passes only against a
controller that already promises its verbs, so each merges after its step 1 is delivered.
3. The controller requires them, in a later pull request, once every holder serves them.
## How it is checked
- mesh-controller: `TestResetFailedAndWantedByAreOptionalVerbsOfTheServiceManager` (the verbs promised,
optional, read back optional from the store's row, each naming the command it replaces, and `why`
required of `unlink-dangling`), and the seat's verb list in `TestTheServiceManagerSeatServesTheUnitVerbs`.
- mesh-catalog `modules/systemd`: the `wanted_test.go` tests (a dangling enable link in the account's own
configuration found and named, a link matched by its target, an unreadable directory and an unreachable
manager said rather than read as nothing, read-only), and the manifest test that the module serves exactly
the verbs it claims. `TestAGlobIsRefusedAsAUnitByEveryVerb` covers every verb taking a unit. The
`unlink_test.go` tests check what is removed and what is left, that the record is written before each
removal, and that a link outside the account's home goes through `sudo -n`.
- Step 3 is not checked by anything yet, as ADR 0246 says of every optional verb.