systemd: the journal verb reads a window, and failed is the seat's verb
mesh/delivery-group group feat/journal-window-on-the-seat delivering: 0 of 2 delivered
mesh/merge-gate pass: builds systemd → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery held for a person: its group feat/journal-window-on-the-seat's composed check did not pass for the heads that merged; a person decides that…

An incident is read for the minutes it happened in (the operator's direction
2026-10-07): journal takes since, until, priority and a fixed-string match.
Every value is one word of journalctl's argv, held to the forms journalctl
reads, so nothing reaches a shell or is read as an option under sudo. What
the unit printed of a secret is redacted, as docker_logs does, before the
match is applied, so a match cannot find one.

systemd_failed becomes the seat's failed, with an optional scope. Needs the
controller's seat with these verbs (mesh-controller, same branch): an older
controller refuses a claim serving a verb its seat does not promise.
This commit is contained in:
jochen
2026-10-07 19:15:20 +02:00
parent c74f407abd
commit 66106d93ac
7 changed files with 728 additions and 28 deletions
+29 -11
View File
@@ -1,5 +1,5 @@
// systemd's tools: the node-service-manager seat's eight verbs — the units on this machine in both scopes,
// read and acted on by name — and the module's own reading of what has failed (novox/hq ADR 0177). The node
// 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);
// 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
@@ -98,9 +98,16 @@ func tools(m *Manager) []stdio.Tool {
act("enable", "Make one unit start at boot (or at the account's login, in user scope)."),
act("disable", "Stop one unit starting at boot (or at login, in user scope)."),
{Name: seat + ".journal",
Description: "The last lines of one unit's journal (at most 2000) — a system service's included: the read is escalated, so it is the service's own lines and not only the operator account's.",
Description: "The last lines of one unit's journal (at most 2000), in a time window and narrowed to a priority " +
"and to lines holding a text when asked — a system service's included: the read is escalated, so it is the " +
"service's own lines and not only the operator account's. A secret the unit printed is shown as " +
"[redacted: <what it was>].",
Input: map[string]any{"scope": scopeArg, "unit": unitArgS,
"lines": map[string]any{"type": "number", "description": "how many lines from the end (default 100, at most 2000)"}},
"lines": str("how many lines from the end of what matches (default 100, at most 2000)"),
"since": str("the window's start: an RFC 3339 time (2026-10-07T09:30:00Z) or relative to now (-30min, -2h, yesterday) (optional)"),
"until": str("the window's end, in the same forms (optional; now when absent)"),
"match": str("only the lines holding this text, as written — a fixed string, not a pattern (optional)"),
"priority": str("only entries this severe or more: 0-7 or emerg, alert, crit, err, warning, notice, info, debug (optional)")},
Run: func(a map[string]any) (any, error) {
scope, err := scopeOf(a)
if err != nil {
@@ -110,16 +117,27 @@ func tools(m *Manager) []stdio.Tool {
if err != nil {
return nil, err
}
// Bounded so the answer stays well below what the runtime carries back in one reply.
n := 100
if v, ok := a["lines"].(float64); ok && v >= 1 {
n = min(int(v), 2000)
q, err := journalQuery(a)
if err != nil {
return nil, err
}
return m.Journal(scope, unit, n)
return m.Journal(scope, unit, q)
}},
{Name: "systemd_failed",
// 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",
Description: "Every failed unit on this machine, in the system manager and in the operator account's; a manager that does not answer is reported with its error, not as nothing failed.",
Run: func(map[string]any) (any, error) { return m.Failed(), nil }},
Input: map[string]any{"scope": str(`"system" or "user": only that manager (both when absent)`)},
Run: func(a map[string]any) (any, error) {
if s, _ := a["scope"].(string); s == "" {
return m.Failed(), nil
}
scope, err := scopeOf(a)
if err != nil {
return nil, err
}
return m.Failed(scope), nil
}},
}
}