A module's containers ran as whatever their image chose and shared folders were made writable by putting accounts in the folder's group (ADR 0312), which ADR 0325 supersedes. The engine now makes each declared module identity as a system account with the controller's numbers, runs a container declaring `identity` as it, and keeps each placed shared folder usable through one POSIX ACL entry per user, set through the kernel's xattrs on descriptors it opened itself. A folder holding data is kept only after the operator's warrant (the take act); a missing folder is made where its data lives and waits where its disk is not; root's data is never kept. A watch and a bounded sweep put back what an app narrows. The group grant is removed, and what it put an account in is taken back. Reuses the descriptor walks, the create act, the missing-path wait and the warrant- backed act message from the paused issue 567 branch.
126 lines
6.7 KiB
Go
126 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
"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"
|
|
)
|
|
|
|
// The machine's failed units in the engine's statement (novox/hq issue 315): a module's is among the
|
|
// resources, unhealthy, as that module's unit, naming it; the machine's own beside them; and an engine
|
|
// not reading them says nothing of them.
|
|
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",
|
|
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)
|
|
if len(h.Resources) != 1 {
|
|
t.Fatalf("one module's unit among the resources: %+v", h.Resources)
|
|
}
|
|
r := h.Resources[0]
|
|
if r.Module != "openrazer" || r.Resource != "openrazer.daemon" || r.Kind != link.KindUnit ||
|
|
r.Target != "openrazer-daemon.service" || r.State != link.StateUnhealthy ||
|
|
!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)
|
|
}
|
|
if h := withUnits(healthAsReported(liveness.Statement{At: at}, nil), nil); h.Units != nil {
|
|
t.Fatal("an engine not reading units said them")
|
|
}
|
|
if h := withUnits(healthAsReported(liveness.Statement{At: at}, nil), &units.Statement{}); h.Units != nil {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// An account declared never to become root (novox/hq ADR 0266) is said with root never, under the contract
|
|
// that tells the controller this engine judges it; an account judged for its groups alone is not.
|
|
func TestTheStatementSaysAnAccountJudgedForRoot(t *testing.T) {
|
|
at := time.Date(2026, 10, 8, 12, 0, 0, 0, time.UTC)
|
|
as := &accounts.Statement{At: at, Accounts: []accounts.Verdict{
|
|
{Account: accounts.Account{Module: "claude-code", ID: "claude-code.agent", Name: "agent", Root: true},
|
|
State: accounts.Unhealthy, Reason: accounts.ReasonRoot + ": in the group docker, which grants root", Since: at},
|
|
{Account: accounts.Account{Module: "openrazer", ID: "openrazer.account", Name: "operator",
|
|
Groups: []string{"openrazer"}}, State: accounts.Healthy, Since: at},
|
|
}}
|
|
h := withAccounts(healthAsReported(liveness.Statement{At: at}, nil), as)
|
|
// At RootContract or later: an engine that also reads identities and keeps shared folders says IdentityContract
|
|
// (novox/hq ADR 0325), one above ADR 0312's GrantContract.
|
|
if h.Contract < link.RootContract || link.RootContract != 3 || h.Contract != link.IdentityContract ||
|
|
link.IdentityContract != link.GrantContract+1 {
|
|
t.Fatalf("contract %d", h.Contract)
|
|
}
|
|
got := map[string]string{}
|
|
for _, r := range h.Resources {
|
|
got[r.Resource] = r.Root
|
|
}
|
|
if got["claude-code.agent"] != "never" || got["openrazer.account"] != "" {
|
|
t.Fatalf("root said: %v", got)
|
|
}
|
|
}
|
|
|
|
// A verdict waiting for the setuid search says how far it got, in the reason the root-free verb repeats and as
|
|
// fields (novox/hq issue 361); a mount point, never a path the walk visited or found.
|
|
func TestTheStatementSaysHowFarTheSetuidSearchGot(t *testing.T) {
|
|
at := time.Date(2026, 10, 10, 1, 40, 0, 0, time.UTC)
|
|
sp := &accounts.SearchProgress{Started: at, Filesystems: 3, Done: 1, Walking: "/home", Visited: 42, Found: 2,
|
|
Kept: at, Finished: []accounts.SearchedFilesystem{{Mount: "/", Took: 90 * time.Second}}}
|
|
as := &accounts.Statement{At: at, Accounts: []accounts.Verdict{{Account: accounts.Account{Module: "claude-code",
|
|
ID: "claude-code.agent", Name: "agent", Root: true}, State: accounts.Unknown,
|
|
Reason: accounts.ReasonPending + ": the search for setuid programs, started 2026-10-10 01:27, has not finished yet",
|
|
Since: at, Search: sp}}}
|
|
h := withAccounts(healthAsReported(liveness.Statement{At: at}, nil), as)
|
|
r := h.Resources[0]
|
|
if !strings.HasPrefix(r.Reason, accounts.ReasonPending) || !strings.Contains(r.Reason, "1 of 3 filesystems walked, walking /home, 42 entries visited") {
|
|
t.Fatalf("the reason: %s", r.Reason)
|
|
}
|
|
s := r.Search
|
|
if s == nil || s.Filesystems != 3 || s.Done != 1 || s.Walking != "/home" || s.Visited != 42 || s.Found != 2 ||
|
|
s.Kept == nil || len(s.Finished) != 1 || s.Finished[0].Mount != "/" || *s.Finished[0].TookSeconds != 90 {
|
|
t.Fatalf("the fields: %+v", s)
|
|
}
|
|
}
|