Serve reset-failed and wanted-by in the systemd module (hq issue 332)
mesh/merge-gate fail: builds systemd → ace, g14, novox, shanks; no bus step; a manifest the change touches fails the module check: systemd claims node-serv…
mesh/repo-check fail: its merge-check.sh failed: long-running resources without health: 70
mesh/delivery superseded: a newer head of the same pull request

So a leftover unit's trigger is found and its failed record cleared
through the seat, not a shell: wanted-by reads the manager's reverse
dependencies and the enable links naming the unit, saying which dangle.
This commit is contained in:
jochen
2026-10-08 17:21:18 +02:00
parent bf542128ee
commit 00f1d04616
6 changed files with 558 additions and 8 deletions
+31 -4
View File
@@ -1,6 +1,6 @@
// systemd's tools: the node-service-manager seat's nine verbs — the units on this machine in both scopes,
// read and acted on by name, their journal, and what has failed (novox/hq ADR 0177). The node
// tools runtime launches this bundle as a process of its own and serves what it serves (ADR 0188, ADR 0193);
// systemd's tools: the node-service-manager seat's eleven verbs — the units on this machine in both scopes,
// read and acted on by name, their journal, what has failed, what wants a unit, and a failed record cleared
// (novox/hq ADR 0177, issue 332). The node tools runtime launches this bundle as a process of its own and serves what it serves (ADR 0188, ADR 0193);
// it runs as the operator account, so acts on the system manager, and reads of its journal, escalate with
// sudo -n, and the user scope is the account's own manager (client.go). The host applies units; this answers
// about them. stdout is the MCP channel; this says nothing else.
@@ -123,6 +123,28 @@ func tools(m *Manager) []stdio.Tool {
}
return m.Journal(scope, unit, q)
}},
// **Clearing a failed record** (novox/hq issue 332): a unit whose file is gone keeps its failed state,
// and its start-limit count, in its manager until it is reset. A unit's name is required, so the
// verb never resets every failed unit at once.
act("reset-failed", "Clear one unit's failed record in its manager — its failed state and its start-limit count — and answer its state after. It removes no file and starts nothing."),
{Name: seat + ".wanted-by",
Description: "What wants, requires or triggers one unit, read-only: 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 it in the scope's configuration directories (*.wants, *.requires, *.upholds) — the " +
"operator account's own ~/.config/systemd/user in user scope — each with where it points, whether it " +
"dangles and whose directory it is in; and what that means, in words.",
Input: map[string]any{"scope": scopeArg, "unit": unitArgS},
Run: func(a map[string]any) (any, error) {
scope, err := scopeOf(a)
if err != nil {
return nil, err
}
unit, err := unitOf(a)
if err != nil {
return nil, err
}
return m.WantedBy(scope, unit)
}},
// Was the module's own systemd_failed; on the seat since the operator's direction of 2026-10-07, so
// whatever holds the role answers it and every machine is asked the same way.
{Name: seat + ".failed",
@@ -157,7 +179,12 @@ func fromEnv() *Manager {
uid = n
}
}
return &Manager{Account: account, UID: uid, User: name, Run: execRunner, Read: readFile, Env: os.Environ()}
home := strings.TrimSpace(os.Getenv("MESH_OPERATOR_HOME"))
if home == "" && account == name {
home, _ = os.UserHomeDir()
}
return &Manager{Account: account, UID: uid, User: name, Run: execRunner, Read: readFile, Env: os.Environ(),
Home: home, ReadDir: readDir}
}
func main() {