Issue 332: the service manager could neither clear a failed unit nor say what wants it
mesh/merge-gate pass: the change touches no module of the mesh's graph
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

A leftover user unit stayed raised on the workstation: no seat verb showed
the dangling enable link that keeps asking for it, or reset its failed
record, so the only step left was a shell. Lands per ADR 0246.
This commit is contained in:
jochen
2026-10-08 17:21:16 +02:00
parent 72e0a851cf
commit c3a20baece
@@ -0,0 +1,61 @@
---
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.
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 both verbs as optional (mesh-controller, this issue's first pull request).
2. The `systemd` module serves both (mesh-catalog). Its check passes only against a controller that
already promises them, so it merges after step 1 is delivered.
3. The controller requires both, in a later pull request, once every holder serves them.
## How it is checked
- mesh-controller: `TestResetFailedAndWantedByAreOptionalVerbsOfTheServiceManager` (both promised,
optional, read back optional from the store's row, each naming the command it replaces), 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.
- Step 3 is not checked by anything yet, as ADR 0246 says of every optional verb.