mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery-group group fix/361-the-setuid-search-runs-to-completion delivering: 0 of 2 delivered
mesh/delivery superseded: a newer head of the same pull request
266 lines
14 KiB
Go
266 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/conditions"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
var rootNow = time.Date(2026, 10, 9, 12, 0, 0, 0, time.UTC)
|
|
|
|
// A machine is root-free only on a positive measure of each thing (the review of 2026-10-09, H2/H3): every
|
|
// read succeeded, an agent account is named and judged confined by the node-engine, execute is not served.
|
|
func TestRootFreeIsAPositiveMeasureAndNothingElse(t *testing.T) {
|
|
pass := rootFacts{Machine: "anchor", AgentNamed: true, Confined: true,
|
|
ConfinedWhy: "the agent account agents cannot become root without a person (judged 2026-10-09 12:00)"}
|
|
if v := judgeRoot(pass, rootNow); !v.Free || v.Machine != "anchor" || !v.Judged.Equal(rootNow) {
|
|
t.Fatalf("the one pass: %+v", v)
|
|
}
|
|
fails := map[string]func(*rootFacts){
|
|
"a read failed": func(f *rootFacts) { f.Unread = []string{"the store did not answer"} },
|
|
"no agent account named": func(f *rootFacts) { f.AgentNamed, f.Confined = false, false },
|
|
"not confined": func(f *rootFacts) { f.Confined = false },
|
|
"nothing said of it": func(f *rootFacts) { f.ConfinedWhy = "" },
|
|
"execute served": func(f *rootFacts) { f.Execute, f.ExecuteWhy = true, "the bus hears execute answered there" },
|
|
"confined, but agent read": func(f *rootFacts) { f.Unread, f.ConfinedWhy = []string{"x"}, "" },
|
|
}
|
|
for name, mutate := range fails {
|
|
f := pass
|
|
mutate(&f)
|
|
if v := judgeRoot(f, rootNow); v.Free || v.Why == "" {
|
|
t.Errorf("%s: judged %+v", name, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The reader fails closed on every case the review named: the agent account read only where the coding-agent
|
|
// module runs (old :225), no account to measure taken for a pass (old :246), and a measure that did not answer
|
|
// taken for "not root" (old :114, :123).
|
|
func TestTheRootReaderFailsClosed(t *testing.T) {
|
|
shell := catalogue.Manifest{Module: "zsh", Claims: []catalogue.Claim{{Name: loginShellSeat, Serves: []string{"execute"}}},
|
|
Settings: map[string]catalogue.SettingDeclaration{"execute": {Default: "withhold"}}}
|
|
reader := func() *rootReader {
|
|
return &rootReader{
|
|
entries: []inventory.Entry{{Manifest: shell, On: []string{"anchor"}}},
|
|
heard: map[string]map[string]map[string]bool{},
|
|
confined: func(context.Context, string, time.Time) (bool, bool, string, error) {
|
|
return true, true, "the agent account agents cannot become root without a person", nil
|
|
},
|
|
settings: func(context.Context, string, string) ([]catalogue.Layer, error) { return nil, nil },
|
|
}
|
|
}
|
|
ctx := context.Background()
|
|
if v := judgeRootFree(ctx, reader(), []string{"anchor"}, rootNow)[0]; !v.Free {
|
|
t.Fatalf("the control: agents confined, execute withheld and unheard, everything read: %+v", v)
|
|
}
|
|
cases := map[string]func(*rootReader){
|
|
"no agent account named, no coding-agent module there": func(r *rootReader) {
|
|
r.confined = func(context.Context, string, time.Time) (bool, bool, string, error) {
|
|
return false, false, "anchor names no agent account: agents run as the operator account (ops)", nil
|
|
}
|
|
},
|
|
"the node-engine's verdict not read": func(r *rootReader) {
|
|
r.confined = func(context.Context, string, time.Time) (bool, bool, string, error) {
|
|
return false, false, "", errors.New("the store did not answer")
|
|
}
|
|
},
|
|
"a stale verdict": func(r *rootReader) {
|
|
r.confined = func(context.Context, string, time.Time) (bool, bool, string, error) {
|
|
return true, false, "the agent account agents is not judged: the machine's newest statement was heard at …", nil
|
|
}
|
|
},
|
|
"the bus not asked": func(r *rootReader) { r.asked = errors.New("no bus") },
|
|
"the placements not read": func(r *rootReader) { r.read = errors.New("no store") },
|
|
"the holder's setting not read": func(r *rootReader) {
|
|
r.settings = func(context.Context, string, string) ([]catalogue.Layer, error) { return nil, errors.New("no store") }
|
|
},
|
|
"execute heard on the bus": func(r *rootReader) {
|
|
r.heard = map[string]map[string]map[string]bool{loginShellSeat: {"execute": {"anchor": true}}}
|
|
},
|
|
"a holder that serves execute": func(r *rootReader) {
|
|
r.entries[0].Manifest.Settings = nil
|
|
},
|
|
}
|
|
for name, mutate := range cases {
|
|
r := reader()
|
|
mutate(r)
|
|
if v := judgeRootFree(ctx, r, []string{"anchor"}, rootNow)[0]; v.Free {
|
|
t.Errorf("%s: judged free: %+v", name, v)
|
|
}
|
|
}
|
|
// A machine with no login shell holder at all: still the bus must hear none.
|
|
r := reader()
|
|
r.entries = nil
|
|
r.heard = map[string]map[string]map[string]bool{loginShellSeat: {"execute": {"anchor": true}}}
|
|
if v := judgeRootFree(ctx, r, []string{"anchor"}, rootNow)[0]; v.Free {
|
|
t.Errorf("execute answered by a module nobody assigned: %+v", v)
|
|
}
|
|
}
|
|
|
|
// The probe says agent-root, by the same judgement, on each machine where the router or a module of its own
|
|
// account runs and that is not root-free; urgent and in plain words; nothing where every one is free.
|
|
func TestTheProbeSaysEachMachineThatIsNotRootFree(t *testing.T) {
|
|
entries := []inventory.Entry{
|
|
{Manifest: catalogue.Manifest{Module: "telegram", RunsAs: "telegram"}, On: []string{"anchor"}},
|
|
{Manifest: catalogue.Manifest{Module: "messenger", RunsAs: "messenger",
|
|
Claims: []catalogue.Claim{{Name: routerSeat}}}, On: []string{"anchor"}},
|
|
{Manifest: catalogue.Manifest{Module: "xorg", Claims: []catalogue.Claim{{Name: catalogue.DisplayServerSeat}}},
|
|
On: []string{"laptop"}},
|
|
}
|
|
trusted := trustedMachines(entries)
|
|
if len(trusted["anchor"]) != 2 || len(trusted["laptop"]) != 0 {
|
|
t.Fatalf("trusted %v", trusted)
|
|
}
|
|
r := &rootReader{entries: entries, heard: map[string]map[string]map[string]bool{},
|
|
confined: func(_ context.Context, node string, _ time.Time) (bool, bool, string, error) {
|
|
return false, false, node + " names no agent account: agents run as the operator account (ops)", nil
|
|
},
|
|
settings: func(context.Context, string, string) ([]catalogue.Layer, error) { return nil, nil }}
|
|
got := rootObservations(context.Background(), r, trusted, rootNow)
|
|
if len(got) != 1 || got[0].Machine != "anchor" || got[0].Kind != kindRootNotFree || got[0].Severity != conditions.Urgent ||
|
|
!strings.Contains(got[0].Summary, "messenger, telegram") || !strings.Contains(got[0].Summary, "names no agent account") {
|
|
t.Fatalf("said %+v", got)
|
|
}
|
|
o := got[0]
|
|
if why, ok := conditions.PlainWords(conditions.Words{Headline: o.Headline, Explanation: o.Explanation,
|
|
Needs: o.Needs, Resolved: o.Resolved}, o.Machine); !ok {
|
|
t.Errorf("not plain: %s", why)
|
|
}
|
|
r.confined = func(context.Context, string, time.Time) (bool, bool, string, error) {
|
|
return true, true, "confined", nil
|
|
}
|
|
if got := rootObservations(context.Background(), r, trusted, rootNow); len(got) != 0 {
|
|
t.Errorf("said of a free machine: %+v", got)
|
|
}
|
|
}
|
|
|
|
// novox/hq ADR 0268: the login shell counts only where its execute is served, and fails closed.
|
|
func TestTheLoginShellCountsOnlyWhereExecuteIsServed(t *testing.T) {
|
|
val := func(v any) *any { return &v }
|
|
cases := []struct {
|
|
name string
|
|
claims bool
|
|
setting *any
|
|
heard, asked bool
|
|
served bool
|
|
saysInTheWhys string
|
|
}{
|
|
{"withheld by the setting and silent on the bus", true, val("withhold"), false, true, false, ""},
|
|
{"withheld by the setting, the machine not yet pushed", true, val("withhold"), true, true, true, "the bus hears"},
|
|
{"the setting serves", true, val("serve"), false, true, true, "setting there is serve"},
|
|
{"a wrong value withholds, as the holder does", true, val("Serve"), false, true, false, ""},
|
|
{"the setting withholds, the bus could not be asked", true, val("withhold"), false, false, true, "could not be asked"},
|
|
{"a holder with no such setting that claims execute", true, nil, false, true, true, "claims execute"},
|
|
{"a holder with no such setting that does not claim it, heard all the same", false, nil, true, true, true, "the bus hears"},
|
|
{"a holder with no such setting that does not claim it, silent", false, nil, false, true, false, ""},
|
|
}
|
|
for _, c := range cases {
|
|
served, why := loginShellServed("zsh", c.claims, c.setting, c.heard, c.asked)
|
|
if served != c.served || (c.saysInTheWhys != "" && !strings.Contains(why, c.saysInTheWhys)) {
|
|
t.Errorf("%s: served %v (%q), want %v saying %q", c.name, served, why, c.served, c.saysInTheWhys)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The root-free verb is the serving controller's alone, answered in its process and never as a command; a
|
|
// controller not serving answers an error, which the router reads as no machine free.
|
|
func TestRootFreeIsAnsweredOnlyByTheServingController(t *testing.T) {
|
|
was := doctorFrom
|
|
doctorFrom = nil
|
|
t.Cleanup(func() { doctorFrom = was })
|
|
if _, err := rootFreeAnswer(context.Background(), "anchor", rootNow); err == nil {
|
|
t.Error("a controller not serving judged a machine")
|
|
}
|
|
if !inProcess["root-free"] {
|
|
t.Error("root-free is not answered in the serving process")
|
|
}
|
|
if _, err := argvFor("root-free", map[string]any{"machines": "anchor"}); err == nil {
|
|
t.Error("root-free ran as a command")
|
|
}
|
|
// The router names its machines as a list (its contract with this verb); one text separated by commas is
|
|
// the same; anything else in the list is refused.
|
|
for _, given := range []any{[]any{"anchor", "relay"}, "anchor, relay"} {
|
|
a, err := readArguments("root-free", map[string]any{"machines": given})
|
|
if err != nil || a.given["machines"] != "anchor,relay" && a.given["machines"] != "anchor, relay" {
|
|
t.Errorf("root-free given %v read %v (%v)", given, a, err)
|
|
}
|
|
}
|
|
if _, err := readArguments("root-free", map[string]any{"machines": []any{"anchor", 7}}); err == nil {
|
|
t.Error("root-free took a number for a machine")
|
|
}
|
|
if _, err := readArguments("status", map[string]any{"machines": []any{"anchor"}}); err == nil {
|
|
t.Error("a verb that takes no list took one")
|
|
}
|
|
}
|
|
|
|
// The confirmation review of 2026-10-09: ADR 0266's quiet window (#175) keeps the self-check from raising
|
|
// agent-can-become-root while the node-engine's setuid search runs and no complete one judges. It must not make root-free answer free:
|
|
// root-free needs a complete, fresh verdict. A verdict still waiting for the search is "not judged" to
|
|
// agentConfined, so the machine is not root-free, whatever the quiet says — and the same statement, complete
|
|
// and healthy, is the control.
|
|
func TestAMachineWaitingForItsFirstSetuidSearchIsNotRootFree(t *testing.T) {
|
|
now := rootNow
|
|
statement := func(state, reason string) inventory.NodeHealth {
|
|
return inventory.NodeHealth{Node: "anchor", Contract: link.RootContract, SaidAt: now, HeardAt: now,
|
|
Resources: []inventory.ResourceHealth{{Module: "claude-code", Resource: "agent", Kind: link.KindAccount,
|
|
Target: "agents", State: state, Reason: reason, Root: link.RootNever}}}
|
|
}
|
|
judged := func(h inventory.NodeHealth) rootVerdict {
|
|
confined, why := judgedConfined("agents", h, true, now)
|
|
return judgeRoot(rootFacts{Machine: "anchor", AgentNamed: true, Confined: confined, ConfinedWhy: why}, now)
|
|
}
|
|
if v := judged(statement(link.StateHealthy, "")); !v.Free {
|
|
t.Fatalf("the control: a complete healthy verdict, fresh: %+v", v)
|
|
}
|
|
pending := statement(link.StateUnknown, link.ReasonRootPending+": the search runs")
|
|
if rootVerdictKind("agents", pending, true, now) != verdictPending {
|
|
t.Fatal("the statement is not one the quiet window counts as waiting for the search")
|
|
}
|
|
if v := judged(pending); v.Free {
|
|
t.Errorf("a machine whose setuid search is pending was judged root-free: %+v", v)
|
|
}
|
|
}
|
|
|
|
// The confirmation review of 2026-10-09, on #154 beside ADR 0266: D-root keeps ADR 0266's quiet window — nothing
|
|
// raised while the one thing unjudged is the setuid search, within searchQuietFor — while the root-free verb
|
|
// still answers the machine not free; and D-root's condition has a key of its own, apart from DA's.
|
|
func TestRootNotFreeIsQuietWhileTheFirstSearchRunsAndKeyedApartFromDA(t *testing.T) {
|
|
entries := []inventory.Entry{{Manifest: catalogue.Manifest{Module: "telegram", RunsAs: "telegram"}, On: []string{"anchor"}}}
|
|
r := &rootReader{entries: entries, heard: map[string]map[string]map[string]bool{},
|
|
confined: func(context.Context, string, time.Time) (bool, bool, string, error) {
|
|
return true, false, "the agent account agents is not judged: the search for setuid programs runs", nil
|
|
},
|
|
settings: func(context.Context, string, string) ([]catalogue.Layer, error) { return nil, nil },
|
|
quiet: func(context.Context, string, time.Time) bool { return true }}
|
|
trusted := trustedMachines(entries)
|
|
if got := rootObservations(context.Background(), r, trusted, rootNow); len(got) != 0 {
|
|
t.Errorf("raised while the search runs: %+v", got)
|
|
}
|
|
if v := judgeRootFree(context.Background(), r, []string{"anchor"}, rootNow)[0]; v.Free || !v.Quiet {
|
|
t.Errorf("root-free while the search runs: %+v", v)
|
|
}
|
|
// Quiet hides nothing else: the login shell served as well is said.
|
|
r.heard = map[string]map[string]map[string]bool{loginShellSeat: {"execute": {"anchor": true}}}
|
|
if got := rootObservations(context.Background(), r, trusted, rootNow); len(got) != 1 {
|
|
t.Errorf("a second failure was kept quiet: %+v", got)
|
|
}
|
|
// Past searchQuietFor, said.
|
|
r.heard, r.quiet = map[string]map[string]map[string]bool{}, func(context.Context, string, time.Time) bool { return false }
|
|
got := rootObservations(context.Background(), r, trusted, rootNow)
|
|
if len(got) != 1 {
|
|
t.Fatalf("a search past searchQuietFor was not said: %+v", got)
|
|
}
|
|
// One key per judgement: DA's is machine.<m>.agent-root, this one its own.
|
|
da := conditions.Observation{Scope: conditions.ScopeMachine, ID: "anchor", Token: "agent-root", Machine: "anchor"}
|
|
if got[0].Key() == da.Key() {
|
|
t.Errorf("D-root and DA share the key %s", da.Key())
|
|
}
|
|
}
|