Name the account whose own manager runs a unit, so a wait for a new login is not read as a fault (hq ADR 0254, issue 318)
mesh/merge-gate pass: builds mesh-host → 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 delivered
mesh/delivery-group group fix/318-a-wait-for-a-person-is-not-a-failure delivered: every member is delivered

This commit is contained in:
jochen
2026-10-08 13:41:46 +02:00
parent 9815796891
commit 9d8398206e
3 changed files with 52 additions and 4 deletions
+12 -3
View File
@@ -1565,7 +1565,7 @@ func healthAsReported(st liveness.Statement, ns *network.Statement) *link.Health
for _, r := range st.Resources {
h.Resources = append(h.Resources, link.ResourceHealth{Module: r.Module, Resource: r.ID, Kind: r.Kind,
Target: r.Target, State: r.State, Reason: r.Reason, Since: r.Since.UTC(), Streak: r.Streak,
Restarts: r.Restarts, Check: r.CheckOf(), Needs: r.NeedsOf()})
Restarts: r.Restarts, Check: r.CheckOf(), Needs: r.NeedsOf(), Account: userOf(r.Scope, r.User)})
}
if ns != nil && ns.State != "" {
h.Network = &link.NetworkHealth{State: ns.State, Since: ns.Since.UTC(), Parts: []link.NetworkPart{}}
@@ -1600,7 +1600,7 @@ func withUnits(h *link.Health, us *units.Statement) *link.Health {
}
h.Resources = append(h.Resources, link.ResourceHealth{Module: f.Module, Resource: f.Resource,
Kind: link.KindUnit, Target: f.Unit, State: link.StateUnhealthy, Reason: reason, Since: f.Since.UTC(),
Streak: f.Streak})
Streak: f.Streak, Account: userOf(f.Scope, f.User)})
}
return h
}
@@ -1615,11 +1615,20 @@ func withAccounts(h *link.Health, as *accounts.Statement) *link.Health {
for _, v := range as.Accounts {
h.Resources = append(h.Resources, link.ResourceHealth{Module: v.Module, Resource: v.ID,
Kind: link.KindAccount, Target: v.Name, State: v.State, Reason: v.Reason, Since: v.Since.UTC(),
Streak: v.Streak})
Streak: v.Streak, Account: v.Name})
}
return h
}
// userOf is the account whose own service manager a unit is in (novox/hq ADR 0254), empty for the
// machine's own manager.
func userOf(scope, user string) string {
if scope == declaration.ScopeUser {
return user
}
return ""
}
// failedUnitWords is a failed unit as the console says it.
func failedUnitWords(f units.Failed) string {
whose := "no module places it"
+35 -1
View File
@@ -5,6 +5,8 @@ import (
"testing"
"time"
"github.com/novox/mesh-host/internal/accounts"
"github.com/novox/mesh-host/internal/declaration"
"github.com/novox/mesh-host/internal/link"
"github.com/novox/mesh-host/internal/liveness"
"github.com/novox/mesh-host/internal/units"
@@ -17,7 +19,7 @@ func TestTheStatementCarriesTheFailedUnits(t *testing.T) {
at := time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC)
us := &units.Statement{At: at, State: units.Degraded, Failed: []units.Failed{
{Unit: "openrazer-daemon.service", Scope: units.ScopeUser, Load: "loaded", Result: "exit-code",
Module: "openrazer", Resource: "openrazer.daemon", Via: units.ViaPackage, Since: at, Streak: 2},
User: "operator", Module: "openrazer", Resource: "openrazer.daemon", Via: units.ViaPackage, Since: at, Streak: 2},
{Unit: "storage-media.mount", Scope: units.ScopeSystem, Load: "loaded", Result: "timeout", Since: at, Streak: 2},
}}
h := withUnits(healthAsReported(liveness.Statement{At: at}, nil), us)
@@ -30,6 +32,10 @@ func TestTheStatementCarriesTheFailedUnits(t *testing.T) {
!strings.Contains(r.Reason, "account's own service manager") || !strings.Contains(r.Reason, "exit-code") {
t.Fatalf("the module's failed unit: %+v", r)
}
// Whose manager it fails in, so the controller can tell it from a fault (novox/hq ADR 0254).
if r.Account != "operator" {
t.Fatalf("the unit in the account's own manager does not name the account: %q", r.Account)
}
if h.Units == nil || h.Units.State != units.Degraded || len(h.Units.Failed) != 1 ||
h.Units.Failed[0].Unit != "storage-media.mount" || h.Units.Failed[0].Result != "timeout" {
t.Fatalf("the machine's own: %+v", h.Units)
@@ -41,3 +47,31 @@ func TestTheStatementCarriesTheFailedUnits(t *testing.T) {
t.Fatal("a judge not yet given a declaration said units")
}
}
// Every resource that runs in an account's own service manager names the account, and so does the account
// a module put in a group (novox/hq ADR 0254): the controller reads a unit that cannot run before a new login
// by the two together. What the machine's own manager runs names none.
func TestTheStatementNamesTheAccountsManager(t *testing.T) {
at := time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC)
st := liveness.Statement{At: at, Resources: []liveness.State{
{Resource: liveness.Resource{Module: "openrazer", ID: "openrazer.daemon", Kind: "service",
Target: "openrazer-daemon.service", Scope: declaration.ScopeUser, User: "operator"}, State: liveness.Unhealthy,
Reason: "down", Since: at},
{Resource: liveness.Resource{Module: "docker", ID: "docker.engine", Kind: "service", Target: "docker.service",
Scope: declaration.ScopeSystem}, State: liveness.Healthy, Since: at},
}}
as := &accounts.Statement{At: at, Accounts: []accounts.Verdict{{Account: accounts.Account{Module: "openrazer",
ID: "openrazer.operator", Name: "operator", Groups: []string{"openrazer"}}, State: accounts.Unhealthy,
Reason: accounts.ReasonRelogin + ": operator is in the group openrazer", Since: at}}}
h := withAccounts(healthAsReported(st, nil), as)
got := map[string]string{}
for _, r := range h.Resources {
got[r.Resource] = r.Account
}
want := map[string]string{"openrazer.daemon": "operator", "docker.engine": "", "openrazer.operator": "operator"}
for id, account := range want {
if a, said := got[id]; !said || a != account {
t.Errorf("%s names the account %q (said %v); want %q", id, a, said, account)
}
}
}