package manifest import ( "bytes" "context" "encoding/json" "errors" "fmt" "io/fs" "maps" "os" "path/filepath" "reflect" "slices" "strings" "sync" "testing" "time" "github.com/nats-io/nats.go/jetstream" "git.novox.be/novox/mesh-sdk/go/asker" "git.novox.be/novox/mesh-sdk/go/asks" "git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/health" "git.novox.be/novox/mesh-sdk/go/membership" "git.novox.be/novox/mesh-sdk/go/outward" "git.novox.be/novox/mesh-sdk/go/seal" "mesh-grants/internal/grants" ) // fakeBus is the bus as the part sees it: other parts' states to read (refusing those named), the bus module's read // tools, the part's own states and its events. It records every write, call and event, so a row can prove nothing was // published or asserted. type fakeBus struct { mu sync.Mutex states map[string]map[string][]byte refused map[string]bool loaded []grants.User streams []map[string]any own map[string]map[string][]byte revs map[string]uint64 refuseTo map[string]bool calls []string events []string writes []string // Each membership's subject: the last membership said there, every notice, and whether it is refused. stream map[string][]byte publishes []string refusePub bool // hold, when set, keeps every nats_users call waiting until it is closed: a pass waiting on the bus. hold chan struct{} // creates are the streams the part asked to create, refused when refuseCreate. creates []string createdAge map[string]time.Duration refuseCreate bool // refuseUsers refuses the bus module's nats_users: the loaded list cannot be read. refuseUsers bool // rootFree answers the controller's root-free verb: nil is no answerer at all (no responders). rootFree func(machines []string) (any, error) // consumers are the durable consumers the bus holds, by stream, as nats_streams answers them for one stream; and // consumerCreates each consumer the part asked to create, `.`. consumers map[string][]map[string]any consumerCreates []string // maxValue, when set, refuses an own value larger than it as the bus does (JetStream 10054). maxValue int } // MatchConsumer is the bus's update of a held durable consumer to the configuration given. func (b *fakeBus) MatchConsumer(stream string, cfg jetstream.ConsumerConfig) error { b.mu.Lock() defer b.mu.Unlock() b.consumerCreates = append(b.consumerCreates, "match "+stream+"."+cfg.Durable) for _, c := range b.consumers[stream] { if c["name"] == cfg.Durable { c["max_deliver"] = cfg.MaxDeliver return nil } } return errors.New("consumer not found") } // CreateConsumer is the bus's create of a durable consumer: held when the bus holds one of that name, else held from now. func (b *fakeBus) CreateConsumer(stream string, cfg jetstream.ConsumerConfig) error { b.mu.Lock() defer b.mu.Unlock() b.consumerCreates = append(b.consumerCreates, stream+"."+cfg.Durable) for _, c := range b.consumers[stream] { if c["name"] == cfg.Durable { return grants.ErrHeld } } filters := append([]string{}, cfg.FilterSubjects...) if cfg.FilterSubject != "" { filters = []string{cfg.FilterSubject} } if b.consumers == nil { b.consumers = map[string][]map[string]any{} } b.consumers[stream] = append(b.consumers[stream], map[string]any{"name": cfg.Durable, "filters": filters, "max_deliver": cfg.MaxDeliver}) return nil } func (b *fakeBus) PublishMembership(node, module string, body []byte) error { b.mu.Lock() defer b.mu.Unlock() subject := "mesh.assignment." + node + "." + module if b.refusePub { return errors.New("Permissions Violation for Publish to " + subject) } if b.stream == nil { b.stream = map[string][]byte{} } b.stream[subject] = body b.publishes = append(b.publishes, subject) return nil } // CreateDeclared is the bus's create of a stream: refused when refuseCreate, held when the bus holds it, and otherwise // held from now on, as nats_streams then reads it. func (b *fakeBus) CreateDeclared(cfg jetstream.StreamConfig) error { b.mu.Lock() defer b.mu.Unlock() b.creates = append(b.creates, cfg.Name) if b.createdAge == nil { b.createdAge = map[string]time.Duration{} } b.createdAge[cfg.Name] = cfg.MaxAge if b.refuseCreate { return errors.New("Permissions Violation for Publish to $JS.API.STREAM.CREATE." + cfg.Name) } for _, s := range b.streams { if s["name"] == cfg.Name { return grants.ErrHeld } } b.streams = append(b.streams, map[string]any{"name": cfg.Name, "subjects": cfg.Subjects, "retention": "limits", "max_age_hours": int(cfg.MaxAge.Hours()), "max_per_subject": int(cfg.MaxMsgsPerSubject)}) return nil } func newFakeBus() *fakeBus { return &fakeBus{states: map[string]map[string][]byte{}, refused: map[string]bool{}, own: map[string]map[string][]byte{}, revs: map[string]uint64{}, refuseTo: map[string]bool{}} } func (b *fakeBus) put(state, key string, v any) { raw, err := json.Marshal(v) if err != nil { panic(err) } if b.states[state] == nil { b.states[state] = map[string][]byte{} } b.states[state][key] = raw } func (b *fakeBus) Keys(state string) ([]string, error) { b.mu.Lock() defer b.mu.Unlock() if b.refused[state] { return nil, errors.New("Permissions Violation for Publish to $JS.API.STREAM.INFO.KV_" + state) } var out []string for k := range b.states[state] { out = append(out, k) } slices.Sort(out) return out, nil } func (b *fakeBus) Get(state, key string) ([]byte, bool, error) { b.mu.Lock() defer b.mu.Unlock() if b.refused[state] { return nil, false, errors.New("refused") } v, ok := b.states[state][key] return v, ok, nil } func (b *fakeBus) Call(key string, body any) (json.RawMessage, error) { b.mu.Lock() hold := b.hold b.mu.Unlock() if hold != nil && key == grants.CallUsers { <-hold } b.mu.Lock() defer b.mu.Unlock() b.calls = append(b.calls, key) args, _ := body.(map[string]any) switch key { case grants.CallUsers: if b.refuseUsers { return nil, errors.New("nats: no responders available for request") } part, _ := args["user"].(string) if part == "" { var names []map[string]any for _, u := range b.loaded { names = append(names, map[string]any{"user": u.User, "publish_grants": len(u.Publish)}) } return json.Marshal(map[string]any{"users": names}) } var out []grants.User for _, u := range b.loaded { if strings.Contains(u.User, part) { out = append(out, u) } } return json.Marshal(map[string]any{"users": out}) case grants.CallStreams: if stream, _ := args["stream"].(string); stream != "" { return json.Marshal(map[string]any{"detail": []map[string]any{{"name": stream, "consumer_detail": b.consumers[stream]}}}) } return json.Marshal(map[string]any{"detail": b.streams}) case grants.CallRootFree: if b.rootFree == nil { return nil, errors.New("nats: no responders available for request") } out, err := b.rootFree(args["machines"].([]string)) if err != nil { return nil, err } return json.Marshal(out) } return nil, fmt.Errorf("no tool %s", key) } func (b *fakeBus) ownGet(state, key string) ([]byte, uint64, bool, error) { b.mu.Lock() defer b.mu.Unlock() v, ok := b.own[state][key] return v, b.revs[state+"/"+key], ok, nil } type ownStore struct{ b *fakeBus } func (s ownStore) Get(state, key string) ([]byte, uint64, bool, error) { return s.b.ownGet(state, key) } func (s ownStore) Put(state, key string, value []byte) (uint64, error) { b := s.b b.mu.Lock() defer b.mu.Unlock() if b.refuseTo[state] { return 0, errors.New("Permissions Violation for Publish to $KV.mesh-grants_" + state) } if b.maxValue > 0 && len(value) > b.maxValue { return 0, errors.New("nats: API error: code=400 err_code=10054 description=message size exceeds maximum allowed") } b.writes = append(b.writes, state) if b.own[state] == nil { b.own[state] = map[string][]byte{} } b.own[state][key] = value b.revs[state+"/"+key]++ return b.revs[state+"/"+key], nil } func (s ownStore) Keys(state string) ([]string, error) { b := s.b b.mu.Lock() defer b.mu.Unlock() var out []string for k := range b.own[state] { out = append(out, k) } slices.Sort(out) return out, nil } func (s ownStore) Delete(state, key string) error { b := s.b b.mu.Lock() defer b.mu.Unlock() b.writes = append(b.writes, state+" delete") delete(b.own[state], key) return nil } func (b *fakeBus) Emit(event string, _ any) error { b.mu.Lock() defer b.mu.Unlock() b.events = append(b.events, event) return nil } func (b *fakeBus) count(key string) int { b.mu.Lock() defer b.mu.Unlock() n := 0 for _, c := range b.calls { if c == key { n++ } } return n } // manifestOf is a fixture manifest of one module, in the indexer's two buckets. func (b *fakeBus) manifestOf(module, raw string) { digest := delivery.ManifestDigest([]byte(raw)) b.put(grants.ReadManifests, digest, delivery.Manifest{V: delivery.Version, Module: module, JSON: raw}) b.put(grants.ReadModules, module, delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32), Manifest: digest, Repository: "novox/x", Commit: "c"}) } // refuse marks the indexer's refusal of module's manifest in its value (novox/hq ADR 0578 §1). func (b *fakeBus) refuse(module, raw, problem string) { b.put(grants.ReadModules, module, delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32), Manifest: delivery.ManifestDigest([]byte(raw)), Repository: "novox/x", Commit: "c", Verdict: delivery.VerdictRefused, Problems: []string{problem}}) } func (b *fakeBus) assign(node, module string) { b.put(grants.ReadAssignments, node+"."+module, map[string]any{"node": node, "module": module, "assigned": "2026-10-25T20:00:00Z"}) } const ( natsManifest = `{"module":"nats","version":"1","claims":[{"name":"mesh-broker"}],"own-secrets":{"broker":"/var/lib/nats/broker"}}` xManifest = `{"module":"mod-x","version":"1","own-secrets":{"broker":"/var/lib/x/broker"},"state":[{"name":"kept","history":3}],` + `"streams":[{"name":"things","events":["thing.added"],"max-age-days":2,"max-mib":5}],"logs":[{"name":"history","max-age-days":30}]}` yManifest = `{"module":"mod-y","version":"1","own-secrets":{"broker":"/var/lib/y/broker"},"reads":["mod-x.kept"]}` ) // brokerSeat is one verb of the bus's seat as a membership serves it: nats claims the seat in fixture and no membership // composed there serves it, so a membership or list that drops it leaves the seat with no holder (issue 1945). func brokerSeat() (seat, verb string) { s, _ := catalogue.SeatNamed(catalogue.BrokerSeat) verb = "v" if len(s.Serves) > 0 { verb = s.Serves[0].Name } return s.Name, verb } // fixture is a mesh of two machines: a runs the bus and mod-x, b runs mod-y; the bus node's agent loaded a list. func fixture() *fakeBus { b := newFakeBus() b.manifestOf("nats", natsManifest) b.manifestOf("mod-x", xManifest) b.manifestOf("mod-y", yManifest) b.assign("a", "nats") b.assign("a", "mod-x") b.assign("b", "mod-y") b.put(grants.ReadAssignments, "b.gone", map[string]any{"node": "b", "module": "gone", "assigned": "2026-10-01T00:00:00Z", "ended": "2026-10-02T00:00:00Z"}) b.put(grants.ReadHave, "a.node", map[string]any{"v": 1}) b.put(grants.ReadHave, "b.node", map[string]any{"v": 1}) b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Wanted: "sha256:aa", Digest: "sha256:aa", Users: 6}}) b.put(grants.ReadControllerGrant, "mod-x.0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123", delivery.ControllerGrant{V: delivery.Version, Accounts: []string{"a.mod-x"}}) return b } func partOn(b *fakeBus) *grants.Part { return &grants.Part{Read: b, Call: b, Store: ownStore{b}, Emit: b, Publish: b, Now: func() time.Time { return time.Date(2026, 10, 25, 22, 0, 0, 0, time.UTC) }} } // shadowOf is the list the part composes from b's records now. func shadowOf(t *testing.T, b *fakeBus) []grants.User { t.Helper() recs, _ := grants.Records(grants.ReadInputs(b, &grants.Manifests{})) users, err := grants.ComposeUsers(recs) if err != nil { t.Fatal(err) } return users } func names(us []grants.User) []string { var out []string for _, u := range us { out = append(out, u.User) } return out } func gapNamed(gs []grants.Gap, input string) bool { return slices.ContainsFunc(gs, func(g grants.Gap) bool { return strings.Contains(g.Input, input) }) } // TestGrants is the grants part's one table of checks (ADR 0547 clauses 5 to 8 in shadow, issue 1945): its manifest, // the shadow list composed from bus values, the compare, each tool, that nothing is published or asserted, and that a // refused read or write never stops it. func TestGrants(t *testing.T) { t.Run("manifest", func(t *testing.T) { raw, err := os.ReadFile("module.json") if err != nil { t.Fatal(err) } m, err := catalogue.ParseManifest(raw) if err != nil { t.Fatalf("mesh-sdk's manifest reader refuses the manifest: %v", err) } if m.Module != "mesh-grants" || len(m.Claims) != 0 { t.Errorf("module %q claims %v: the part claims no seat until mesh-sdk's seat list names mesh-grants", m.Module, m.Claims) } for _, row := range []struct { what string got, want []string }{ {"reads", m.Reads, grants.Reads}, {"invokes", m.Invokes, grants.Invokes}, {"consumes", m.Consumes, grants.Consumes}, {"emits", m.Emits, grants.Emits}, {"tools", m.Tools, grants.Tools}, } { if !slices.Equal(slices.Sorted(slices.Values(row.got)), slices.Sorted(slices.Values(row.want))) { t.Errorf("%s = %v, the program's are %v", row.what, row.got, row.want) } } var states []string for _, s := range m.State { states = append(states, s.Name) } if !slices.Equal(slices.Sorted(slices.Values(states)), grants.States) { t.Errorf("state = %v, want %v", states, grants.States) } if _, ok := m.OwnSecrets["broker"]; !ok || len(m.OwnSecrets) != 1 || m.SecretsOwner != "mesh-grants" { t.Errorf("own-secrets %v owner %q: a credential of its own, broker", m.OwnSecrets, m.SecretsOwner) } }) t.Run("no timer, no update or delete, a publish only of memberships, a create only of declared streams", func(t *testing.T) { forbidden := []string{"time.Tick", "time.NewTicker", "time.Sleep", "time.After", "time.NewTimer", "time.AfterFunc", "PublishMsg", "CreateKeyValue", "CreateOrUpdate", "UpdateStream", "UpdateKeyValue", "DeleteStream", "PurgeStream", "DeleteKeyValue", "CreateObjectStore", "EnsureStream", "RaiseBuckets", "RaiseLogs", ".Create("} // The one publish: a membership, on its own subject, in publish.go; the one create: a declared stream the bus lacks, // in create.go (issue 1790). publishOnly := filepath.Join("cmd", "mesh-grants", "publish.go") createOnly := filepath.Join("cmd", "mesh-grants", "create.go") // And the operator's asks and conditions, on the seat operator-channel's subjects, in asking.go (ADR 0547 clause 8). asksOnly := filepath.Join("cmd", "mesh-grants", "asking.go") sealedOnly := filepath.Join("cmd", "mesh-grants", "sealed.go") files := 0 for _, root := range []string{"cmd", "internal"} { err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { return err } files++ src, err := os.ReadFile(path) if path != publishOnly && path != asksOnly && (strings.Contains(string(src), ".Publish(") || strings.Contains(string(src), "mesh.assignment")) { t.Errorf("%s publishes: only publish.go publishes memberships, and asking.go the operator's asks", path) } if path == asksOnly && (strings.Count(string(src), ".Publish(") != 2 || strings.Contains(string(src), "mesh.assignment")) { t.Errorf("asking.go publishes other than an ask, a cancel and a condition, through js.Publish twice") } if path == publishOnly && strings.Count(string(src), ".Publish(") != 1 { t.Errorf("publish.go publishes other than one membership") } if n := strings.Count(string(src), "CreateStream"); (path == createOnly && n != 1) || (path != createOnly && n != 0) { t.Errorf("%s holds CreateStream %d times: only create.go creates, once", path, n) } // The one object bucket: the part's own, of the user list sealed to the bus node, in sealed.go (ADR 0547 clause 7). if path == sealedOnly { if strings.Count(string(src), "CreateObjectStore(") != 1 || !strings.Contains(string(src), "delivery.GrantsUserListObjects") { t.Errorf("sealed.go creates other than the part's one object bucket") } src = []byte(strings.ReplaceAll(string(src), "CreateObjectStore", "")) } for _, f := range forbidden { if strings.Contains(string(src), f) { t.Errorf("%s holds %s: the part holds no timer and creates or asserts nothing", path, f) } } return err }) if err != nil { t.Fatal(err) } } if files < 8 { t.Fatalf("read %d source files, want the program's", files) } }) t.Run("shadow list from bus values", func(t *testing.T) { for _, row := range []struct { name string bend func(*fakeBus) users []string gap string }{ {"every record on the bus", func(*fakeBus) {}, []string{"a.mod-x", "a.nats", "b.mod-y", "controller", "node.a", "node.b"}, "enrolment tokens"}, {"a manifest missing is a gap and its user is left out", func(b *fakeBus) { delete(b.states[grants.ReadModules], "mod-y") }, []string{"a.mod-x", "a.nats", "controller", "node.a", "node.b"}, "manifest of mod-y"}, {"a want's built manifest is composed, not the trunk's; the list's records carry both (issue 774)", func(b *fakeBus) { built := strings.Replace(xManifest, `{"name":"kept","history":3}`, `{"name":"newer"}`, 1) digest := delivery.ManifestDigest([]byte(built)) b.put(grants.ReadManifests, digest, delivery.Manifest{V: delivery.Version, Module: "mod-x", JSON: built}) fp := "src2:" + strings.Repeat("cd", 32) b.put(grants.ReadWants, "want.a.mod-x", delivery.Want{V: delivery.Version, FP: fp, Composed: "sha256:" + strings.Repeat("ef", 32)}) key, _ := delivery.BuilderBuildKey("mod-x", fp) b.put(grants.ReadBuilds, key, map[string]any{"v": delivery.Version, "state": "done", "manifest": digest}) }, []string{"a.mod-x", "a.nats", "b.mod-y", "controller", "node.a", "node.b"}, "enrolment tokens"}, {"placement refused is a gap; the machines come from the haves", func(b *fakeBus) { b.refused[grants.ReadAssignments] = true }, []string{"controller", "node.a", "node.b"}, grants.ReadAssignments}, } { t.Run(row.name, func(t *testing.T) { b := fixture() row.bend(b) in := grants.ReadInputs(b, &grants.Manifests{}) recs, left := grants.Records(in) users, err := grants.ComposeUsers(recs) if err != nil { t.Fatal(err) } if got := names(users); !slices.Equal(got, row.users) { t.Errorf("users = %v, want %v", got, row.users) } if !gapNamed(append(in.Gaps, left...), row.gap) { t.Errorf("gaps %v name no %q", in.Gaps, row.gap) } if strings.HasPrefix(row.name, "a want's") { x := users[slices.IndexFunc(users, func(u grants.User) bool { return u.User == "a.mod-x" })] if !slices.Contains(x.Publish, "$KV.mod-x_newer.>") || slices.Contains(x.Publish, "$KV.mod-x_kept.>") { t.Errorf("a.mod-x is not granted the state its wanted build declares alone: %v", x.Publish) } // The list composes what runs and what the trunk names: a subject only the trunk's manifest names is in // it before the want moves to the trunk's build (issue 774). listRecs, _ := grants.ListRecords(in) listed, err := grants.ComposeUsers(listRecs) if err != nil { t.Fatal(err) } if got := names(listed); !slices.Equal(got, row.users) { t.Errorf("the list's users = %v, want %v", got, row.users) } lx := listed[slices.IndexFunc(listed, func(u grants.User) bool { return u.User == "a.mod-x" })] if !slices.Contains(lx.Publish, "$KV.mod-x_newer.>") || !slices.Contains(lx.Publish, "$KV.mod-x_kept.>") { t.Errorf("the list does not grant a.mod-x both its running and its trunk manifest's state: %v", lx.Publish) } } }) } // A module whose only runtime-loaded part is a helper bundle (route-proxy's route-provider) says `loads` in // the repository manifest's build.artifacts; the indexer's manifest has no derived Bundles (issue 1945). for _, row := range []struct { name, build string carried bool }{ {"a bundle that says loads is carried by node-tools", `,"build":{"artifacts":[{"name":"helper","kind":"bundle","language":"go","system":"arch","from":"cmd/h","binary":"h","loads":["h"]}]}`, true}, {"a module with no loading bundle is left to its own account", ``, false}, } { t.Run(row.name, func(t *testing.T) { b := fixture() b.manifestOf("mod-z", `{"module":"mod-z","version":"1","own-secrets":{"broker":"/var/lib/z/broker"}`+row.build+`}`) b.assign("a", "mod-z") b.manifestOf("node-tools", `{"module":"node-tools","version":"1","own-secrets":{"broker":"/var/lib/nt/broker"}}`) b.assign("a", "node-tools") users := shadowOf(t, b) tools := users[slices.IndexFunc(users, func(u grants.User) bool { return u.User == "a.node-tools" })] if got := slices.Contains(tools.Subscribe, "mesh.mod.mod-z.tool.>"); got != row.carried { t.Errorf("a.node-tools subscribes to mod-z's tools = %v, want %v: %v", got, row.carried, tools.Subscribe) } }) } // ADR 0567 §3, §9: the seat mesh-placement's candidates are read by every node agent (mesh-sdk NodeComposeReads) // and by a module whose manifest reads them (the view), written by their holder, the assigner, and read by no other. cands := fixture() cands.manifestOf("mesh-delivery-assigner", `{"module":"mesh-delivery-assigner","version":"1","own-secrets":{"broker":"/var/lib/as/broker"},"state":[{"name":"candidates","history":5}]}`) cands.manifestOf("mesh-delivery-view", `{"module":"mesh-delivery-view","version":"1","own-secrets":{"broker":"/var/lib/v/broker"},"reads":["mesh-delivery-assigner.candidates"]}`) cands.manifestOf("mesh-delivery-indexer", `{"module":"mesh-delivery-indexer","version":"1","own-secrets":{"broker":"/var/lib/i/broker"}}`) for _, m := range []string{"mesh-delivery-indexer", "mesh-delivery-assigner", "mesh-delivery-view"} { cands.assign("a", m) } var readers []string for _, u := range shadowOf(t, cands) { if slices.Contains(u.Publish, "$JS.API.DIRECT.GET.KV_"+delivery.PlacementCandidatesBucket+".>") { readers = append(readers, u.User) } } if want := []string{"a.mesh-delivery-assigner", "a.mesh-delivery-view", "node.a", "node.b"}; !slices.Equal(readers, want) { t.Errorf("the placement candidates are read by %v, want the node agents, the view and their holder %v", readers, want) } b := fixture() users := shadowOf(t, b) y := users[slices.IndexFunc(users, func(u grants.User) bool { return u.User == "b.mod-y" })] if !slices.Contains(y.Publish, "$JS.API.DIRECT.GET.KV_mod-x_kept.>") { t.Errorf("b.mod-y reads mod-x.kept and is not granted its direct get: %v", y.Publish) } }) t.Run("withheld: a hearing of a restricted stream in the tool runner is written on change only, and deleted once heard (issue 1955 L7)", func(t *testing.T) { const mail = `{"module":"mesh-mail","version":"1","streams":[{"name":"mail","events":["mail.*.message.added"],"max-age-days":7,` + `"grant":{"config":"grants","events":"mail.{key}.**"}}],"resources":[{"id":"state","type":"directory","mode":"0700","place":"."},` + `{"id":"grants","trusted":true,"type":"file","path":"${dir:state}/grants.json","mode":"0600","merge":"json","takes-settings":["grants"],"content":"{\"grants\": {}}"}]}` b := fixture() b.manifestOf("mesh-mail", mail) b.manifestOf("reader", `{"module":"reader","version":"1","consumes":["mesh-mail.mail.box.message.added"]}`) b.assign("a", "mesh-mail") b.assign("a", "reader") b.put(grants.ReadResolved, "a.mesh-mail.grants", map[string]any{"node": "a", "module": "mesh-mail", "key": "grants", "value": map[string]any{"box": []string{"reader"}}}) p := partOn(b) res := p.Compose() const key = "reader.mail.a" var h grants.WithheldHearing if res.Withheld == nil || !slices.Equal(res.Withheld.Written, []string{key}) || json.Unmarshal(b.own[grants.StateWithheld][key], &h) != nil || h.Module != "reader" || h.Node != "a" || h.Stream != "MAIL" || h.Emitter != "mesh-mail" || !strings.Contains(h.Said, "runs-as") { t.Fatalf("withheld = %+v, stored %+v (errors %v), want %s written naming the remedy", res.Withheld, h, res.Errors, key) } if again := p.Compose(); again.Withheld == nil || len(again.Withheld.Written) != 0 || again.Withheld.Withheld != 1 { t.Errorf("a second pass wrote %+v, want nothing (on change only)", again.Withheld) } b.manifestOf("reader", `{"module":"reader","version":"1","runs-as":"reader","resources":[{"id":"account","type":"user","name":"reader","shell":"/usr/bin/nologin","home":"/var/lib/reader"}],`+ `"own-secrets":{"broker":"/var/lib/r/broker"},"secrets-owner":"reader","consumes":["mesh-mail.mail.box.message.added"]}`) if heard := p.Compose(); heard.Withheld == nil || !slices.Equal(heard.Withheld.Deleted, []string{key}) || b.own[grants.StateWithheld][key] != nil { t.Errorf("once reader runs as its own account the pass did %+v (errors %v), want %s deleted", heard.Withheld, heard.Errors, key) } }) t.Run("root-free: the controller's word fills the composition, failing closed (issue 1945)", func(t *testing.T) { const router = `{"module":"messenger","version":"1","runs-as":"messenger","resources":[{"id":"account","type":"user","name":"messenger","shell":"/usr/bin/nologin","home":"/var/lib/messenger"}],"own-secrets":{"broker":"/var/lib/m/broker"},"secrets-owner":"messenger",` + `"seats":[{"name":"operator-channel","scope":"mesh","serves":["open"]},{"name":"channel","scope":"mesh","kinded":true,"accepts":["show"]}],` + `"claims":[{"name":"operator-channel","scope":"mesh","serves":["open"]}]}` const telegram = `{"module":"telegram","version":"1","runs-as":"telegram","resources":[{"id":"account","type":"user","name":"telegram","shell":"/usr/bin/nologin","home":"/var/lib/telegram"}],"own-secrets":{"broker":"/var/lib/t/broker"},"secrets-owner":"telegram",` + `"claims":[{"name":"channel","scope":"mesh","kind":"telegram","capabilities":["deliver","verified-sender"]}]}` answer := func(free ...string) func([]string) (any, error) { return func(machines []string) (any, error) { var vs []map[string]any for _, m := range machines { ok := slices.Contains(free, m) why := "judged free" if !ok { why = "its node-engine judged nothing" } vs = append(vs, map[string]any{"machine": m, "free": ok, "why": why}) } return map[string]any{"machines": vs}, nil } } for _, row := range []struct { name string routerOn []string telegramOn string ask func([]string) (any, error) keeps bool note string }{ {"router and telegram on one free machine", []string{"a"}, "a", answer("a"), true, ""}, {"router and telegram on two free machines", []string{"a"}, "b", answer("a", "b"), true, ""}, {"the holder's machine is not free", []string{"a"}, "b", answer("a"), false, "b: not free, its node-engine judged nothing"}, {"the router's machine is not free", []string{"a"}, "b", answer("b"), false, "a: not free"}, {"an error is not free", []string{"a"}, "a", func([]string) (any, error) { return nil, errors.New("boom") }, false, "boom"}, {"no answerer is not free", []string{"a"}, "a", nil, false, "no responders"}, {"a machine left out of the answer is not free", []string{"a"}, "a", func([]string) (any, error) { return map[string]any{"machines": []any{}}, nil }, false, "named no verdict"}, {"the router on two machines frees nothing", []string{"a", "b"}, "a", answer("a", "b"), false, ""}, } { t.Run(row.name, func(t *testing.T) { b := fixture() b.manifestOf("messenger", router) b.manifestOf("telegram", telegram) for _, node := range row.routerOn { b.assign(node, "messenger") } b.assign(row.telegramOn, "telegram") b.rootFree = row.ask res := partOn(b).Compose() recs, _ := grants.Records(grants.ReadInputs(b, &grants.Manifests{})) grants.WithRootFree(&recs, b) kept := false for _, k := range buscompose.PlacementsOf(recs, recs.Interchangeable).Kinds { kept = kept || slices.Contains(k.Capabilities, "verified-sender") } if kept != row.keeps { t.Errorf("verified-sender kept = %v, want %v (said %v)", kept, row.keeps, res.RootNotFree) } if !strings.Contains(strings.Join(res.RootNotFree, " | "), row.note) || (row.note == "" && row.keeps && len(res.RootNotFree) != 0) { t.Errorf("the pass says %v, want it to name %q", res.RootNotFree, row.note) } }) } // A root-not-free condition raised or cleared wakes a pass; another kind does not (never a timer). wb := fixture() var handle func(string, []byte) wf := &grants.Follower{Part: partOn(wb), Watch: &fakeWatcher{opened: make(chan string, 16)}, Logf: func(string, ...any) {}, Hear: func(h func(string, []byte)) error { handle = h; return nil }} wctx, wcancel := context.WithCancel(context.Background()) wdone := make(chan struct{}) go func() { wf.Run(wctx); close(wdone) }() waitFor(t, func() bool { return wb.count(grants.CallUsers) > 0 && handle != nil }) passes := func() int { return wb.count(grants.CallUsers) } before := passes() handle(grants.EventConditionRaised, []byte(`{"kind":"disk-full","key":"machine.a.disk-full"}`)) time.Sleep(100 * time.Millisecond) if passes() != before { t.Errorf("a condition of another kind woke a pass") } // 1952/G1 (ADR 0564 §9): the keeper's events are not heard while the take-over key follows the controller handle("mesh.seat.mesh-conditions.event.condition-raised", []byte(`{"kind":"root-not-free","key":"machine.a.root-not-free"}`)) time.Sleep(100 * time.Millisecond) if passes() != before || wf.Follows() != "mesh-controller" { t.Errorf("the keeper's event woke a pass while the part follows %s", wf.Follows()) } handle(grants.EventConditionCleared, []byte(`{"kind":"root-not-free","key":"machine.a.root-not-free"}`)) waitFor(t, func() bool { return passes() > before }) // and from the take-over only the keeper's are heard, never both watcher := wf.Watch.(*fakeWatcher) waitFor(t, func() bool { watcher.mu.Lock() defer watcher.mu.Unlock() return watcher.changes[grants.ReadTakeover] != nil }) watcher.mu.Lock() toKeeper := watcher.changes[grants.ReadTakeover] watcher.mu.Unlock() toKeeper(health.TakeoverKey, []byte(`{"v":1,"follow":"keeper","by":"operator","at":"2026-10-29T20:00:00Z"}`)) if wf.Follows() != health.KeeperModule { t.Fatalf("the part follows %s after the take-over", wf.Follows()) } before = passes() handle("mesh.seat.mesh-controller.event.condition-raised", []byte(`{"kind":"root-not-free","key":"machine.a.root-not-free"}`)) time.Sleep(100 * time.Millisecond) if passes() != before { t.Errorf("the controller's event woke a pass after the take-over") } handle(grants.EventKeeperRaised, []byte(`{"kind":"root-not-free","key":"machine.a.root-not-free"}`)) waitFor(t, func() bool { return passes() > before }) toKeeper(health.TakeoverKey, nil) // handed back: the key deleted follows the controller if wf.Follows() != "mesh-controller" { t.Errorf("the part follows %s after the hand-back", wf.Follows()) } wcancel() <-wdone if !grants.IsRootNotFree([]byte(`{"kind":"root-not-free"}`)) || !grants.IsRootNotFree([]byte(`{"key":"machine.novox.root-not-free"}`)) || grants.IsRootNotFree([]byte(`{"kind":"disk-full","key":"machine.novox.disk-full"}`)) { t.Errorf("only a root-not-free condition wakes a pass") } }) t.Run("a predefined-key fallback is read with no list; every refused manifest listed", func(t *testing.T) { // mod-p's value falls back to a predefined key, as claude-code's managed_settings does, and no list is on the bus // (issue 1945); mod-z's manifest the indexer's judge refused (ADR 0578 §3), and mod-z is assigned nowhere. const pManifest = `{"module":"mod-p","version":"1","own-secrets":{"broker":"/var/lib/p/broker"},` + `"config":{"size":{"type":"number","description":"The size text shows in, in points.","predefined":"font-size","default":12,"runtime":true}}}` const zManifest = `{"module":"mod-z","version":"1","no-such-key":true}` b := fixture() b.manifestOf("mod-p", pManifest) b.manifestOf("mod-z", zManifest) b.refuse("mod-z", zManifest, "it carries no-such-key") b.assign("b", "mod-p") // mesh-wireguard is provided by the mesh, not read from a repository: no manifest in the indexer, none refused, // no gap, and its manifest is the one mesh-sdk provides (issue 1945). b.assign("b", "mesh-wireguard") in := grants.ReadInputs(b, &grants.Manifests{}) // An indexer row of the same name never wins: the provided manifest is used and the row is said as a gap. bw := fixture() bw.manifestOf("mesh-wireguard", `{"module":"mesh-wireguard","version":"9","own-secrets":{"broker":"/x"}}`) bw.assign("b", "mesh-wireguard") if iw := grants.ReadInputs(bw, &grants.Manifests{}); !strings.HasPrefix(iw.Digests["mesh-wireguard"], "provided:") || !gapNamed(iw.Gaps, "manifest of mesh-wireguard") || len(iw.Refused) != 0 { t.Errorf("an indexer manifest of mesh-wireguard won or went unsaid: %v %v", iw.Digests["mesh-wireguard"], iw.Gaps) } if _, ok := in.Manifests["mesh-wireguard"]; !ok || gapNamed(in.Gaps, "manifest of mesh-wireguard") || !strings.HasPrefix(in.Digests["mesh-wireguard"], "provided:") { t.Errorf("mesh-wireguard's manifest is not taken from mesh-sdk: gaps %v", in.Gaps) } if len(in.Refused) != 1 || in.Refused[0].Input != "manifest of mod-z" || !gapNamed(in.Gaps, "manifest of mod-z") { t.Errorf("refused = %v: only mod-z's, and named in the gaps", in.Refused) } recs, _ := grants.Records(in) users, err := grants.ComposeUsers(recs) if err != nil { t.Fatal(err) } if !slices.Contains(names(users), "b.mod-p") { t.Errorf("b.mod-p is not composed, so its module counts as placed nowhere: %v", names(users)) } // Issue 1126: a key a module's served value fills from its settings (mailu's smtp domain) is granted to every other // node's agent, that key alone, so a node composing a consumer elsewhere settles what it serves. bs := fixture() bs.manifestOf("mod-s", `{"module":"mod-s","version":"1","own-secrets":{"broker":"/var/lib/s/broker"},`+ `"config":{"domain":{"type":"string","description":"The mail domain.","required":true}},`+ `"provides":[{"name":"smtp","scope":"mesh"}],"serves":{"smtp":{"port":587,"domain":"${config:domain}"}}}`) bs.assign("b", "mod-s") served := "$JS.API.DIRECT.GET.KV_mesh-config_resolved.$KV.mesh-config_resolved.b.mod-s.domain" for _, u := range shadowOf(t, bs) { if (u.User == "node.a") != slices.Contains(u.Publish, served) { t.Errorf("%s is granted b's mod-s domain: %v (node a alone)", u.User, slices.Contains(u.Publish, served)) } } // The provider not yet moved: it runs a build whose manifest serves no setting, and the trunk's adds one, which a // node composing a consumer reads (frombus reads the trunk's manifest of another node's module): granted too. bt := fixture() const oldS = `{"module":"mod-s","version":"1","own-secrets":{"broker":"/var/lib/s/broker"},` + `"provides":[{"name":"smtp","scope":"mesh"}],"serves":{"smtp":{"port":587}}}` bt.manifestOf("mod-s", oldS) oldDigest := delivery.ManifestDigest([]byte(oldS)) bt.manifestOf("mod-s", `{"module":"mod-s","version":"1","own-secrets":{"broker":"/var/lib/s/broker"},`+ `"config":{"domain":{"type":"string","description":"The mail domain.","required":true}},`+ `"provides":[{"name":"smtp","scope":"mesh"}],"serves":{"smtp":{"port":587,"domain":"${config:domain}"}}}`) bt.assign("b", "mod-s") runsFP := "src2:" + strings.Repeat("cd", 32) bt.put(grants.ReadWants, "want.b.mod-s", delivery.Want{V: delivery.Version, FP: runsFP, Composed: "sha256:" + strings.Repeat("ef", 32)}) runsKey, _ := delivery.BuilderBuildKey("mod-s", runsFP) bt.put(grants.ReadBuilds, runsKey, map[string]any{"v": delivery.Version, "state": "done", "manifest": oldDigest}) for _, u := range shadowOf(t, bt) { if u.User == "node.a" && !slices.Contains(u.Publish, served) { t.Errorf("node a is not granted the domain the trunk's mod-s serves, while b runs one that serves none") } } // The pass, the tool listing held memberships, and the log each say the refusal. part := partOn(b) res := part.Pass() if len(res.Refused) != 1 || !strings.HasPrefix(res.Refused[0], "manifest of mod-z: ") { t.Errorf("the pass says refused %v", res.Refused) } held, err := part.Held() if err != nil { t.Fatal(err) } if got, _ := held.(map[string]any)["refused"].([]string); len(got) != 1 { t.Errorf("grants_membership lists refused %v", got) } var logged []string var lmu sync.Mutex f := &grants.Follower{Part: partOn(b), Watch: &fakeWatcher{opened: make(chan string, 16)}, Logf: func(format string, args ...any) { lmu.Lock() logged = append(logged, fmt.Sprintf(format, args...)) lmu.Unlock() }} ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { f.Run(ctx); close(done) }() waitFor(t, func() bool { lmu.Lock() defer lmu.Unlock() return strings.Contains(strings.Join(logged, "\n"), "refused manifest of mod-z: ") }) cancel() <-done }) t.Run("credentials", func(t *testing.T) { shadow := []grants.User{{User: "a.held"}, {User: "a.new"}, {User: "a.bare"}} loaded := []grants.User{{User: "a.held"}, {User: "a.bare"}, {User: "enrol.x"}, {User: "person.op"}} hashes := map[string]grants.HeldHash{"a.held": {Hash: "$2a$10$h1", Generation: 2}, "a.new": {Hash: "$2a$10$h2", Generation: 1}, "person.op": {Hash: "$2a$10$h3", Adopted: true}} c := grants.CredentialsOf(shadow, loaded, hashes) for _, row := range []struct { user, would string held, adopted bool }{ {"a.bare", grants.WouldLeaveOut, false, false}, {"a.held", grants.WouldSame, true, false}, {"a.new", grants.WouldAdd, true, false}, {"enrol.x", grants.WouldLoseLogin, false, false}, {"person.op", grants.WouldNotComposed, true, true}, } { i := slices.IndexFunc(c.Users, func(u grants.Credential) bool { return u.User == row.user }) if i < 0 || c.Users[i].Would != row.would || c.Users[i].HashHeld != row.held || c.Users[i].Adopted != row.adopted { t.Errorf("%s: %+v, want %s held %v", row.user, c.Users, row.would, row.held) } } if c.HashesHeld != 3 || !slices.Equal(c.LeftOut, []string{"a.bare"}) || !slices.Equal(c.LosesLogin, []string{"enrol.x"}) || !slices.Equal(c.Adds, []string{"a.new"}) { t.Errorf("credentials = %+v", c) } // Composed from the vault's state: a hash changes the list's state (written on the event), and no hash is ever // in what the part writes or says. b := fixture() b.loaded = shadowOf(t, b) p := partOn(b) before := p.Compose() b.put(grants.ReadAccounts, "a.mod-x", map[string]any{"user": "a.mod-x", "hash": "$2a$10$secrethashbytes", "generation": 1}) after := p.Compose() cr := after.UserList.Credentials if !after.Written || after.Revision != before.Revision+1 || cr.HashesHeld != 1 || slices.Contains(cr.LeftOut, "a.mod-x") || !slices.Contains(cr.LeftOut, "b.mod-y") { t.Errorf("a hash heard = %+v", after.UserList.Credentials) } why := strings.Join(p.Decide(), "; ") if !strings.Contains(why, "no hash for") || strings.Contains(why, "a.mod-x,") { t.Errorf("take-over is refused for %q, want the users with no hash named", why) } for state, keys := range b.own { for k, v := range keys { if strings.Contains(string(v), "secrethashbytes") { t.Errorf("the hash is written in %s %s", state, k) } } } }) t.Run("compare", func(t *testing.T) { b := fixture() shadow := shadowOf(t, b) in := grants.ReadInputs(b, &grants.Manifests{}) changed := slices.Clone(shadow) x := slices.IndexFunc(changed, func(u grants.User) bool { return u.User == "a.mod-x" }) changed[x].Publish = append(slices.Clone(changed[x].Publish[1:]), "extra.subject") // Only added rights (ADR 0562): the loaded list lacks a publish of a.mod-x the shadow grants. fewer := slices.Clone(shadow) fewer[x].Publish = slices.Clone(fewer[x].Publish[1:]) // A deny the shadow adds where the loaded list says its own (empty): a publish right lost. And the same deny where // the loaded list says none, as nats_users answers today, on a user whose grants differ: it cannot be compared. denied := slices.Clone(shadow) denied[x].PublishDeny = []string{"mesh.seat.node-launcher.tool.secret"} readable := slices.Clone(fewer) for i := range readable { readable[i].PublishDeny = []string{} } // The live comparison the operator's yes was refused on, 2026-10-27 21:19 UTC (issue 1945): 41 users differ, every // difference an added right. Read whole; a row's mutate makes one of the cases that must still refuse. var live struct { Compare grants.Comparison `json:"compare"` Credentials grants.Credentials `json:"credentials"` } if raw, err := os.ReadFile(filepath.Join("testdata", "takeover-compare-2026-10-27.json")); err != nil || json.Unmarshal(raw, &live) != nil { t.Fatalf("the captured comparison cannot be read: %v", err) } if len(live.Compare.Differ) != 41 || len(live.Credentials.LosesLogin)+len(live.Credentials.LeftOut) != 0 { t.Fatalf("the captured comparison is not the one of 21:19: %d differ", len(live.Compare.Differ)) } captured := func(mutate func(*grants.Comparison)) *grants.Comparison { raw, _ := json.Marshal(live.Compare) var c grants.Comparison _ = json.Unmarshal(raw, &c) if mutate != nil { mutate(&c) } return &c } nodeAce := func(c *grants.Comparison) *grants.UserDiff { return &c.Differ[slices.IndexFunc(c.Differ, func(d grants.UserDiff) bool { return d.User == "node.ace" })] } for _, row := range []struct { name string loaded []grants.User added, dropped, gone []string differ []string equal bool // live is a captured comparison used in place of Compare; removing the users take-over is refused for // (ADR 0562 §2), and adds the users whose added rights the ask names. live *grants.Comparison removing []string adds int }{ {"equal", shadow, nil, nil, nil, nil, true, nil, nil, 0}, {"one user the bus lacks", shadow[1:], []string{shadow[0].User}, nil, nil, nil, false, nil, []string{shadow[0].User}, 0}, {"one loaded user still placed", append(slices.Clone(shadow), grants.User{User: "a.mod-x.worker"}), nil, []string{"a.mod-x.worker"}, nil, nil, false, nil, []string{"a.mod-x.worker"}, 0}, {"one loaded user placement no longer names", append(slices.Clone(shadow), grants.User{User: "b.gone"}), nil, []string{"b.gone"}, []string{"b.gone"}, nil, true, nil, nil, 0}, {"grants differ", changed, nil, nil, nil, []string{"a.mod-x"}, false, nil, []string{"a.mod-x"}, 1}, {"only a right added", fewer, nil, nil, nil, []string{"a.mod-x"}, false, nil, nil, 1}, {"a deny added refuses", readable, nil, nil, nil, []string{"a.mod-x"}, false, nil, []string{"a.mod-x"}, 1}, {"a deny the loaded list cannot say refuses a user whose grants differ", fewer, nil, nil, nil, []string{"a.mod-x"}, false, nil, []string{"a.mod-x"}, 1}, {"captured 21:19: only added rights", nil, nil, nil, nil, nil, false, captured(nil), nil, 41}, {"captured, a right removed", nil, nil, nil, nil, nil, false, captured(func(c *grants.Comparison) { nodeAce(c).SubscribeDropped = []string{"mesh.mod.mesh-vault.event.secret-changed"} }), []string{"node.ace"}, 41}, {"captured, responses lost", nil, nil, nil, nil, nil, false, captured(func(c *grants.Comparison) { nodeAce(c).Responses = "shadow false, loaded true" }), []string{"node.ace"}, 41}, {"captured, a user dropped", nil, nil, nil, nil, nil, false, captured(func(c *grants.Comparison) { c.Dropped = []string{"novox.mesh-vault.worker"} }), []string{"novox.mesh-vault.worker"}, 41}, {"captured, a user added", nil, nil, nil, nil, nil, false, captured(func(c *grants.Comparison) { c.Added = []string{"shanks.new-module"} }), []string{"shanks.new-module"}, 41}, {"captured, a login lost", nil, nil, nil, nil, nil, false, captured(nil), []string{"novox.telegram"}, 41}, } { t.Run(row.name, func(t *testing.T) { var c grants.Comparison if row.live != nil { c = *row.live } else { from := shadow if strings.Contains(row.name, "deny") { from = denied } c = grants.Compare(from, row.loaded, grants.Placed(in)) var differ []string for _, d := range c.Differ { differ = append(differ, d.User) } if !slices.Equal(c.Added, nonNil(row.added)) || !slices.Equal(c.Dropped, nonNil(row.dropped)) || !slices.Equal(c.Gone, row.gone) || !slices.Equal(differ, row.differ) || c.Equal() != row.equal { t.Errorf("compare = %+v", c) } } var cr grants.Credentials if row.live != nil { cr = live.Credentials } if strings.Contains(row.name, "login lost") { cr.LosesLogin = []string{"novox.telegram"} } // Removing, and the users with no hash in the vault, are what the yes is refused for. var removing []string for _, r := range c.Removing() { removing = append(removing, strings.Fields(r)[0]) } removing = append(removing, cr.LosesLogin...) if !slices.Equal(removing, row.removing) || len(c.AddedRights()) != row.adds { t.Errorf("take-over refused for %v, want %v; added rights of %d users, want %d", c.Removing(), row.removing, len(c.AddedRights()), row.adds) } // The ask's words pass the operator channel's content rule, read at a glance and fit one message; where only // rights are added they name every user whose rights are added, and where a loss stands they never say none is // taken away and name the users the yes would be refused for. summary, points, list := grants.TakeOverWords(c, cr) if row.removing != nil { if strings.Contains(summary, "takes none away") { t.Errorf("the ask says none is taken away with a loss: %q", summary) } for _, u := range row.removing { if !strings.Contains(list, u) { t.Errorf("the ask does not name %s, refused for: %s", u, list) } } } if row.live != nil && row.removing == nil && c.Equal() { t.Error("the captured comparison is equal: the old guard would not have refused it") } if row.adds > 0 { if r, ok := outward.Check(summary + "\n" + strings.Join(points, "\n") + "\n" + list); !ok { t.Errorf("the ask's words break the content rule: %s", r) } } if row.adds > 0 && row.removing == nil { expl := summary + "\n" + strings.Join(points, "\n") for _, text := range []string{expl, list} { if r, ok := outward.Check(text); !ok { t.Errorf("the ask's words break the content rule: %s", r) } } if err := asks.Glance(expl); err != nil || len([]rune(list)) > grants.AddedWordsBudget { t.Errorf("the ask's words do not fit: %v, a list of %d characters", err, len([]rune(list))) } for _, d := range c.AddedRights() { if !strings.Contains(list, d.User) && !strings.Contains(list, "more users") { t.Errorf("the ask does not name %s", d.User) } } } if row.live != nil && row.removing == nil && (!strings.Contains(summary, "adds 391 rights to 41 users") || !strings.Contains(list, "node.ace, node.novox: 36 reads, 1 call, 1 event each")) { t.Errorf("the captured ask says %q and lists %q", summary, list) } if row.name == "grants differ" && (len(c.Differ[0].PublishAdded) != 1 || c.Differ[0].PublishDropped[0] != "extra.subject") { t.Errorf("the grant difference is not named: %+v", c.Differ[0]) } }) } }) // 1126 (ADR 0549 §1, §3): once the catalogue places the nodes, a seat held once for the mesh is held where placement // says, not on every machine running a module that claims it; what placement adds is composed beside the assignments. t.Run("a mesh seat is held where placement says", func(t *testing.T) { store := catalogue.Manifest{Module: "store", Version: "1", DefinesSeats: []catalogue.SeatDeclaration{{Name: "store-seat", Scope: catalogue.ScopeMesh, Serves: []catalogue.Verb{{Name: "databases"}}}}, Claims: []catalogue.Claim{{Name: "store-seat", Scope: catalogue.ScopeMesh}}} in := grants.Inputs{Nodes: []string{"a", "b"}, Assigned: map[string][]string{"a": {"store"}, "b": {"store"}}, Manifests: map[string]catalogue.Manifest{"store": store}} holds := func(recs buscompose.Records, node string) int { n := 0 for _, d := range recs.Assigned[node] { n += len(d.Holds) } return n } if recs, _ := grants.Records(in); holds(recs, "a") != 1 || holds(recs, "b") != 1 { t.Fatalf("before placement the seat is held on a %d and b %d times, want both", holds(recs, "a"), holds(recs, "b")) } in.Placed = map[string]delivery.Placement{ "a": {Node: "a", Modules: []delivery.PlacedModule{{Module: "store"}}, Holds: []delivery.Holding{{Claim: "store-seat", Scope: "mesh", Module: "store"}}}, "b": {Node: "b", Modules: []delivery.PlacedModule{{Module: "store"}}}} if recs, _ := grants.Records(in); holds(recs, "a") != 1 || holds(recs, "b") != 0 { t.Errorf("placed, the seat is held on a %d and b %d times, want a alone", holds(recs, "a"), holds(recs, "b")) } b := fixture() pl, _ := delivery.EncodePlacement(delivery.Placement{Node: "b", Modules: []delivery.PlacedModule{{Module: "mod-y"}, {Module: "mod-x"}}}) b.states[grants.ReadPlacement] = map[string][]byte{"placement.b": pl} read := grants.ReadInputs(b, &grants.Manifests{}) if !slices.Contains(read.Assigned["b"], "mod-x") || read.Placed["b"].Node != "b" || slices.ContainsFunc(read.Gaps, func(g grants.Gap) bool { return g.Input == "seat holdings" }) { t.Errorf("read: assigned %v, placed %v, gaps %v", read.Assigned, read.Placed, read.Gaps) } // Research 072 row 37: a node key and a key per module, the legacy mirror left out, read the same. perModule, _ := delivery.PlacementValues(delivery.Placement{Node: "b", Modules: []delivery.PlacedModule{{Module: "mod-y"}, {Module: "mod-x"}}}) delete(perModule, "placement.b") nb := fixture() nb.states[grants.ReadPlacement] = perModule if got := grants.ReadInputs(nb, &grants.Manifests{}); !slices.Equal(got.Placed["b"].Assigned(), []string{"mod-y", "mod-x"}) || slices.ContainsFunc(got.Gaps, func(g grants.Gap) bool { return strings.HasPrefix(g.Input, grants.ReadPlacement) }) { t.Errorf("read of the keys per module: placed %v, gaps %v", got.Placed, got.Gaps) } b.refused[grants.ReadPlacement] = true if read := grants.ReadInputs(b, &grants.Manifests{}); slices.ContainsFunc(read.Gaps, func(g grants.Gap) bool { return g.Input == grants.ReadPlacement && g.Failed }) { t.Errorf("placement not on the bus is a failed read: %v", read.Gaps) } // Issue 1945, the user list of 2026-10-27 09:21: a placement's holds are the holders on record, not every holder. // A mesh seat no placement holds keeps the module claiming it as its holder, wherever placement puts it. in.Manifests["other"] = catalogue.Manifest{Module: "other", Version: "1", Claims: []catalogue.Claim{{Name: "other-seat", Scope: catalogue.ScopeMesh}}, DefinesSeats: []catalogue.SeatDeclaration{{Name: "other-seat", Scope: catalogue.ScopeMesh, Serves: []catalogue.Verb{{Name: "v"}}}}} in.Assigned["b"] = append(in.Assigned["b"], "other") in.Placed["b"] = delivery.Placement{Node: "b", Modules: []delivery.PlacedModule{{Module: "store"}, {Module: "other"}}} if recs, _ := grants.Records(in); holds(recs, "a") != 1 || holds(recs, "b") != 1 { t.Errorf("a seat no placement holds is held on b %d times, want once (and store-seat on a alone: a %d)", holds(recs, "b"), holds(recs, "a")) } in.Placed["b"] = delivery.Placement{Node: "b", Modules: []delivery.PlacedModule{{Module: "store"}, {Module: "other"}}, Holds: []delivery.Holding{{Claim: "other-seat", Scope: "mesh", Module: "someone-else"}}} if recs, _ := grants.Records(in); holds(recs, "b") != 0 { t.Errorf("a seat placement holds by another module is still held by other on b") } // The live shapes: the seed's novox placement (mesh-catalog 0c61bad, its twelve holdings, without // anthropic-licence-manager), the licence manager's manifest (mesh-catalog c05aa5e6), and the subjects the bus // grants novox.node-tools for its seat (nats_users, 2026-10-27 19:40Z). file := func(name string, v any) { raw, err := os.ReadFile(filepath.Join("testdata", name)) if err == nil { err = json.Unmarshal(raw, v) } if err != nil { t.Fatal(err) } } var seed delivery.Placement var live struct{ Users []grants.User } file("placement-novox-0c61bad.json", &seed) file("node-tools-novox-licence-subjects.json", &live) licence, _ := os.ReadFile(filepath.Join("testdata", "claude-licence-manager.json")) n := newFakeBus() // 1952/S5 (ADR 0564 Limits 4): the bus module's tools bundle declares its own account and what it may remove. n.manifestOf("nats", strings.Replace(strings.TrimSuffix(natsManifest, "}"), `"broker":"/var/lib/nats/broker"`, `"broker":"/var/lib/nats/broker","broker-nats-tools":"${dir:tools-secret}/broker-nats-tools"`, 1)+ `,"resources":[{"id":"tools-secret","type":"directory","path":"/var/lib/mesh/nats-tools","mode":"0711"}],"build":{"artifacts":[{"name":"tools","kind":"bundle","language":"go",`+ `"system":"arch","from":"cmd/nats-tools","binary":"nats-tools","loads":["nats-tools"],"bus":{"removes":["DEAD_LETTER_NOTICES.controller","mesh-host_acts"]}}]}}`) n.manifestOf("claude-licence-manager", string(licence)) n.assign("novox", "nats") n.assign("novox", "claude-licence-manager") n.manifestOf("node-tools", `{"module":"node-tools","version":"1"}`) // the machine's tool runner, which carries the seat n.assign("novox", "node-tools") // The live placement names mesh-host; with its manifest's state the runner once carried it and the whole list was // refused as a second writer of the engine's health (issue 774, 2026-10-30): the shadow below now composes. n.manifestOf(buscompose.EngineModule, `{"module":"mesh-host","version":"1","consumes":["restic.backed-up"],"state":[{"name":"health"}]}`) n.put(grants.ReadHave, "novox.node", map[string]any{"v": 1}) pl, _ = delivery.EncodePlacement(seed) n.states[grants.ReadPlacement] = map[string][]byte{"placement.novox": pl} recs, _ := grants.Records(grants.ReadInputs(n, &grants.Manifests{})) var tools []buscompose.ProcessPrincipal for _, d := range recs.Assigned["novox"] { if d.Module == "nats" { tools = d.Processes } } if len(tools) != 1 || tools[0].Process != "nats-tools" || !slices.Equal(tools[0].Removes, []string{"DEAD_LETTER_NOTICES.controller", "mesh-host_acts"}) { t.Errorf("the bus module's principals = %+v, want nats-tools carrying both removals", tools) } if m := grants.Memberships(recs)["novox.claude-licence-manager"]; len(m.Membership.Seats) == 0 { t.Errorf("the licence manager's membership under the seeded placement carries no seats: %+v", m.Membership) } var runtime grants.User for _, u := range shadowOf(t, n) { if u.User == "novox.node-tools" { runtime = u } } if slices.ContainsFunc(runtime.Publish, func(s string) bool { return strings.HasPrefix(s, "$KV.mesh-host_") }) { t.Errorf("novox.node-tools is granted a write of the node-engine's state: %v", runtime.Publish) } for _, s := range live.Users[0].Subscribe { if strings.Contains(s, ".call.") && !slices.Contains(runtime.Subscribe, s) { t.Errorf("under the seeded placement novox.node-tools is not granted %s, which the bus grants it today", s) } } }) t.Run("compare excuses a retired verb and a multiply claimed mesh seat's added subscriptions on its machines, nothing else", func(t *testing.T) { const retired, store, storeInfo = "mesh.seat.mesh-delivery.tool.mark-ready", "mesh.seat.mesh-store.tool.databases", "$SRV.INFO.mesh-store.>" two := map[string][]string{"mesh-store": {"a", "n"}} for _, row := range []struct { name string user string subShadow, subBus, pubShadow []string multi map[string][]string differs bool excused int }{ {"a retired verb the shadow dropped", "a.node-tools", []string{"x"}, []string{"x", retired}, nil, nil, false, 1}, {"a verb of a live seat the shadow lost", "a.node-tools", []string{"x"}, []string{"x", store}, nil, two, true, 0}, {"a seat claimed on two machines, added to a user on one", "a.node-tools", []string{"x", store, storeInfo}, []string{"x"}, nil, two, false, 2}, {"the same added to a user on a machine that does not claim it", "q.node-tools", []string{"x", store}, []string{"x"}, nil, two, true, 0}, {"a publish of the seat added is not excused", "a.node-tools", []string{"x"}, []string{"x"}, []string{store}, two, true, 0}, {"the same added for a seat claimed once", "a.node-tools", []string{"x", store}, []string{"x"}, nil, nil, true, 0}, } { c := grants.Compare([]grants.User{{User: row.user, Subscribe: row.subShadow, Publish: row.pubShadow}}, []grants.User{{User: row.user, Subscribe: row.subBus}}, nil). Excusing(grants.ExcusedBy(row.multi)) if c.Equal() == row.differs || len(c.Excused) != row.excused { t.Errorf("%s: equal %v, excused %v", row.name, c.Equal(), c.Excused) } } // The guard of the user list and the memberships (issue 1945, the lead's decision in review round 1 of its fix): a // seat the list in force holds left with no holder while a placed module still claims it, nothing else. sm, _ := catalogue.SeatNamed(catalogue.ServiceManagerSeat) nodeSeat := func(node string) string { return "mesh.seat." + sm.Name + ".tool." + sm.Serves[0].Name + "." + node } h := grants.HoldingsOf(grants.Inputs{Assigned: map[string][]string{"novox": {"postgres", "systemd"}, "ace": {"postgres", "systemd"}}, Manifests: map[string]catalogue.Manifest{ "postgres": {Module: "postgres", Claims: []catalogue.Claim{{Name: "mesh-store", Scope: catalogue.ScopeMesh}}}, "systemd": {Module: "systemd", Claims: []catalogue.Claim{{Name: sm.Name, Scope: catalogue.ScopeNode}}}}}) u := func(user string, sub ...string) grants.User { return grants.User{User: user, Subscribe: sub} } for _, row := range []struct { name string inForce, now []grants.User lost []string }{ {"09:21: the seat's holder lost, its claimant still placed", []grants.User{u("novox.node-tools", "x", store)}, []grants.User{u("novox.node-tools", "x")}, []string{"mesh-store"}}, {"a dropped tool", []grants.User{u("novox.node-tools", "mesh.mod.postgres.tool.gone", store)}, []grants.User{u("novox.node-tools", store)}, nil}, {"a seat moved from novox to ace", []grants.User{u("novox.node-tools", store)}, []grants.User{u("novox.node-tools"), u("ace.node-tools", store)}, nil}, {"a seat no placed module claims any more", []grants.User{u("novox.node-tools", "mesh.seat.unclaimed.tool.v")}, []grants.User{u("novox.node-tools")}, nil}, {"a retired verb dropped", []grants.User{u("novox.node-tools", retired)}, []grants.User{u("novox.node-tools")}, nil}, {"a node seat lost on novox, held on ace", []grants.User{u("novox.node-tools", nodeSeat("novox")), u("ace.node-tools", nodeSeat("ace"))}, []grants.User{u("novox.node-tools"), u("ace.node-tools", nodeSeat("ace"))}, []string{sm.Name + "@novox"}}, } { if lost := h.LastHolders(h.ByUsers(row.inForce), h.ByUsers(row.now)); !slices.Equal(lost, row.lost) { t.Errorf("%s: lost %v, want %v", row.name, lost, row.lost) } } }) t.Run("tools", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) b.streams = []map[string]any{{"name": "KV_mod-x_kept", "subjects": []string{"$KV.mod-x_kept.>"}, "retention": "limits", "max_per_subject": 3}} p := partOn(b) // The bus node says its key in its have of the bus module; the process puts the sealed list (ADR 0547 clause 7). busKey, err := seal.GenerateKeyPair() if err != nil { t.Fatal(err) } withKey := func(w delivery.UserListWritten) { w.Key = busKey.PublicKey b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, UserList: &w}) } loadedBefore := delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Wanted: "sha256:aa", Digest: "sha256:aa", Users: 6} withKey(loadedBefore) objs := &fakeObjects{} p.Objects = objs open := func(g delivery.GrantsUserList) string { t.Helper() list, err := delivery.OpenUserList(objs.objects[g.Object], g.Digest, func(bx seal.Box) ([]byte, error) { out, err := seal.Open(bx, busKey.PrivateKey) return []byte(out), err }) if err != nil { t.Fatalf("the sealed list does not open with the bus node's key: %v", err) } return string(list) } got, _ := p.UserList() if !strings.Contains(fmt.Sprint(got), "nothing composed yet") { t.Errorf("user-list before any composition = %v", got) } first := p.Compose() if !first.Written || first.Revision != 1 || !first.UserList.Compare.Equal() || first.UserList.Users != 6 { t.Fatalf("first composition = %+v", first) } // Written in shadow, the bus node told to follow the controller's list; every user left out while the vault holds // no hash; a tool's part (no object bucket) writes none. if g, _, had := p.Sealed(); !had || g.Follow != delivery.FollowController || g.Revision != 1 || g.Node != "a" || g.Users != 0 || len(g.LeftOut) != 6 || len(objs.objects) != 1 || !strings.Contains(open(g), "accounts {") { t.Errorf("the sealed list in shadow = %+v, %d objects", g, len(objs.objects)) } if _, _, had := partOn(fixture()).Sealed(); had { t.Error("a part read before any pass holds a sealed list") } if tp := partOn(fixture()); tp.Compose().Sealed == nil { t.Error("a tool's pass does not say the sealed list") } else if _, _, had := tp.Sealed(); had { t.Error("a tool's part wrote the sealed list") } if len(first.Published) != 3 || len(b.publishes) != 3 { t.Errorf("the first composition published %v, want the three assignments' memberships", first.Published) } // The second pass says each membership unchanged (published at the first); the third writes nothing at all. if again := p.Compose(); again.Written || again.Revision != 1 || len(again.Published) != 0 || again.AssertionsWritten != 0 { t.Errorf("a composition with no input changed wrote or published: %+v", again) } if third := p.Compose(); third.Written || third.MembershipsWritten != 0 || len(third.Published) != 0 || len(b.publishes) != 3 { t.Errorf("a membership equal to the one last published was published again, or a state written: %+v", third) } // mod-x placed on b too: b.mod-x is added and its first membership published; a.mod-x's loses the module's plain // subject (mod-x now runs on two machines), which leaves no seat without its holder, so it is published too (issue // 1945, the lead's decision in review round 1 of its fix). b.assign("b", "mod-x") moved := p.Compose() if !moved.Written || moved.Revision != 2 || !slices.Equal(moved.Published, []string{"a.mod-x", "b.mod-x"}) || len(moved.NotPublished) != 0 || moved.UserList.Compare.Equal() || !slices.Equal(moved.UserList.Compare.Added, []string{"b.mod-x"}) { t.Errorf("a composition after placement moved = %+v", moved) } got, _ = p.UserList() ul, _ := got.(map[string]any) if st, ok := ul["written"].(grants.UserListState); !ok || st.Loaded == nil || st.Loaded.Digest != "sha256:aa" || st.BusNode != "a" || ul["revision"] != uint64(2) || !gapNamed(st.Gaps, "people") { t.Errorf("user-list = %+v", got) } a, err := p.AssertOne("mod-x") am, _ := a.(map[string]any) as, _ := am["assertion"].(grants.Assertion) if err != nil || as.Asserted || as.Says != grants.ShadowAsserts || len(as.Items) != 3 || am["asked"] == nil { t.Fatalf("assert = %+v, %v", a, err) } for _, it := range as.Items { if it.Stream == "KV_mod-x_kept" && len(it.Differences) != 0 { t.Errorf("a bucket the bus holds as composed differs: %v", it.Differences) } if it.Stream == "THINGS" && !slices.Contains(it.Differences, "the bus holds no stream THINGS") { t.Errorf("a stream the bus lacks is not named: %+v", it) } } ms, _ := p.MembershipOf("a", "mod-x") if mo, _ := ms.(map[string]any); !strings.Contains(fmt.Sprint(mo["published"]), "membership:") || mo["last_pass"] == nil || mo["composed"] == nil { t.Errorf("membership = %v", ms) } gr, _ := p.Grants("mod-x") if g, _ := gr.(map[string]any); g["writes"] != grants.WritesGrants || g["controller_count"] != 1 { t.Errorf("grants = %v", gr) } b.loaded = append(b.loaded, grants.User{User: "b.gone"}) fa := &fakeAsker{wheres: map[string]*asks.Warrant{}} var forgot []string n := 0 p.Nonce, p.Forget = func() string { n++; return fmt.Sprintf("%04d", n) }, func(id string) error { forgot = append(forgot, id); return nil } var said []asks.Condition p.Asker, p.Say = fa, func(c asks.Condition) error { said = append(said, c); return nil } warrant := func(id string) asks.Warrant { return asks.Warrant{Ask: id, Outcome: asks.OutcomeChosen} } last := func() grants.Act { raw, _, _, _ := b.ownGet(grants.StateTakeover, grants.KeySwitch) var s grants.Switch _ = json.Unmarshal(raw, &s) if s.Open != "" && s.Acts[len(s.Acts)-1].Said != "asked" { t.Errorf("an ask is open and its act is not asked: %+v", s) } return s.Acts[len(s.Acts)-1] } asked := func(act string) string { t.Helper() var res struct{ Asked string } var err error var out any if act == "take-over" { out, err = p.TakeOver("shanks.node-tools", "a row") } else { out, err = p.HandBack("shanks.node-tools", "a row") } if err != nil { t.Fatalf("%s: %v", act, err) } raw, _ := json.Marshal(out) _ = json.Unmarshal(raw, &res) return res.Asked } yes, no := asks.Option{ID: "yes"}, asks.Option{ID: "no"} // settled is the placement of the lists agreeing, every hash held, and the loaded list as edit leaves it. settled := func(edit func(modX *grants.User)) func() { return func() { b.states[grants.ReadAssignments] = nil b.assign("a", "nats") b.assign("a", "mod-x") b.assign("b", "mod-y") b.loaded = append(shadowOf(t, b), grants.User{User: "b.gone"}) x := slices.IndexFunc(b.loaded, func(u grants.User) bool { return u.User == "a.mod-x" }) b.loaded[x].Publish = slices.Clone(b.loaded[x].Publish) edit(&b.loaded[x]) for _, u := range shadowOf(t, b) { b.put(grants.ReadAccounts, u.User, map[string]any{"user": u.User, "hash": "$2a$10$x", "generation": 1}) } } } lacks := func(modX *grants.User) { modX.Publish = modX.Publish[1:] } // the shadow adds a right (ADR 0562) for _, row := range []struct { name, act string option asks.Option setup func() said, never []string // the refusal's words and the users it must not name state string outcome string // between changes the lists after the ask and before the yes; asked are words the ask must carry. between func() asked []string }{ {"a yes while a user differs is refused and said, naming it", "take-over", yes, func() {}, []string{"users differ", "b.mod-x"}, []string{"b.gone"}, grants.Shadow, "refused", nil, nil}, {"a no leaves the switch", "take-over", no, func() {}, nil, nil, grants.Shadow, "declined", nil, nil}, {"a yes while a loaded user would lose a right is refused, naming it", "take-over", yes, settled(func(modX *grants.User) { modX.Publish = append(modX.Publish, "extra.subject") }), []string{"other than by an added right", "a.mod-x (would lose 1 publish)"}, []string{"b.gone"}, grants.Shadow, "refused", nil, nil}, {"a yes after the added rights changed since the ask is refused", "take-over", yes, settled(lacks), []string{"changed since you were asked"}, nil, grants.Shadow, "refused", func() { lacks(&b.loaded[slices.IndexFunc(b.loaded, func(u grants.User) bool { return u.User == "a.mod-x" })]) }, []string{"a.mod-x: 1 read"}}, {"a yes with only added rights and every hash held records the switch, the ask naming them", "take-over", yes, settled(lacks), nil, nil, grants.TookOver, "recorded", nil, []string{"adds 1 right to 1 user and takes none away", "a.mod-x: 1 read"}}, {"a yes to hand back records the switch back", "hand-back", yes, func() {}, nil, nil, grants.Shadow, "recorded", nil, nil}, } { t.Run(row.name, func(t *testing.T) { row.setup() before := len(fa.asks) id := asked(row.act) if _, err := p.TakeOver("shanks.node-tools", "again"); err == nil || !strings.Contains(err.Error(), "already asked") { t.Errorf("a second ask while one is open = %v", err) } p.Compose() p.Compose() if len(fa.asks) != before+1 || fa.asks[before].ID != id { t.Fatalf("the operator was asked %d times, want once: %+v", len(fa.asks)-before, fa.asks) } ask := fa.asks[before] if err := ask.Check(time.Date(2026, 10, 25, 22, 0, 0, 0, time.UTC)); err != nil { t.Errorf("the router would refuse the ask: %v", err) } if yesOpt, ok := ask.Option("yes"); !ok || yesOpt.Level != asks.Approve || !strings.HasPrefix(yesOpt.Binds, "sha256:") { t.Errorf("the yes does not approve and bind its act: %+v", yesOpt) } for _, text := range []string{ask.Explanation, ask.Details} { if r, ok := outward.Check(text); !ok { t.Errorf("the ask breaks the content rule: %s", r) } } if err := asks.Glance(ask.Explanation); err != nil { t.Error(err) } for _, w := range row.asked { if !strings.Contains(ask.Explanation+"\n"+ask.Details, w) { t.Errorf("the ask does not say %q: %s\n%s", w, ask.Explanation, ask.Details) } } if row.between != nil { row.between() } said = nil calls := len(b.calls) if err := p.Warranted(row.option, warrant(id)); err != nil { t.Fatal(err) } // 1953/D8 (ADR 0565 §11): a switch made is recorded in the hand-act log's seat; one refused or declined is not. if recorded := slices.Contains(b.calls[calls:], grants.CallRecordWarrant); recorded != (row.outcome == "recorded") { t.Errorf("recorded in the hand-act log %v, the act %s", recorded, row.outcome) } l := last() if l.Said != row.outcome || l.Ask != id || p.SwitchState() != row.state { t.Errorf("the act = %+v, the switch %s; want %s and %s", l, p.SwitchState(), row.outcome, row.state) } for _, w := range row.said { if !strings.Contains(l.Refused, w) || (row.outcome == "refused" && (len(said) != 1 || !strings.Contains(said[0].Explanation, w))) { t.Errorf("the refusal %q (said %v) does not say %q", l.Refused, said, w) } } for _, w := range row.never { if strings.Contains(l.Refused, w) { t.Errorf("the refusal names %q", w) } } // A recorded switch says no refusal and clears the one a refused yes left raised. if row.outcome == "recorded" && row.act == "take-over" && (len(said) != 1 || said[0].State != asks.StateCleared || said[0].Key != "mesh-grants.take-over-refused") { t.Errorf("a recorded switch did not clear the refusal: %v", said) } if row.outcome == "recorded" && !strings.Contains(fmt.Sprint(p.TakeOverState()), "bus_node_loaded_grants_list:not yet") { t.Errorf("the state claims the bus node follows with no loaded list: %v", fmt.Sprint(p.TakeOverState())) } // The key the bus node follows turns with the switch at once, and the state says switched only once the bus // node loaded the grants part's written list. g, _, _ := p.Sealed() if want := map[string]string{grants.TookOver: delivery.FollowGrants, grants.Shadow: delivery.FollowController}[p.SwitchState()]; g.Follow != want { t.Errorf("the key says follow %s with the switch %s", g.Follow, p.SwitchState()) } effect := func() string { st, _ := p.TakeOverState(); return fmt.Sprint(st.(map[string]any)["effect"]) } if row.outcome == "recorded" && row.act == "take-over" { if e := effect(); strings.HasPrefix(e, "switched") || !strings.Contains(e, "not in effect") { t.Errorf("effect before the bus node loaded = %s", e) } if users := strings.Count(open(g), "{ user: "); users != g.Users || g.Users == 0 || len(g.LeftOut) != 0 { t.Errorf("the sealed list holds %d users, the key says %d, left out %v", users, g.Users, g.LeftOut) } withKey(delivery.UserListWritten{Source: delivery.GrantsUserListSource, Revision: g.Revision, Wanted: g.Digest, Digest: g.Digest, Users: g.Users, Follows: delivery.FollowGrants}) if e := effect(); !strings.HasPrefix(e, "switched") { t.Errorf("effect once the bus node loaded the written list = %s", e) } // Steady (issue 1126, 2026-10-28 00:16: the shadow list moved every 3 s, 99270 to 99357, with nothing // differing): the bus node saying again that it loaded the same list, at a later time and the key's // later revision, moves neither the shadow list nor the sealed key. _, sealedRev, _ := p.Sealed() p.Compose() for i := 0; i < 3; i++ { withKey(delivery.UserListWritten{Source: delivery.GrantsUserListSource, Revision: sealedRev + uint64(i+1), Wanted: g.Digest, Digest: g.Digest, Users: g.Users, Follows: delivery.FollowGrants, At: time.Date(2026, 10, 28, 0, 16, i, 0, time.UTC)}) if res := p.Compose(); res.Written { t.Errorf("pass %d: the shadow list is written again for the bus node saying the same list again (revision %d)", i, res.Revision) } if _, rev, _ := p.Sealed(); rev != sealedRev { t.Errorf("pass %d: the sealed key is written again with nothing changed: revision %d, was %d", i, rev, sealedRev) } } withKey(loadedBefore) // After take-over a list composed past a failed read, or one dropping a user of the list in force for // want of a hash, is not sealed: the list in force stays, and the pass says why. b.refused[grants.ReadAccounts] = true if res := p.Compose(); res.Sealed == nil || !strings.Contains(res.Sealed.Why, "a read of this pass failed") { t.Errorf("a pass past a failed read = %+v", res.Sealed) } // While the hold lasts the warning stands, said once however many passes find it; it names why. p.Compose() nHeld := 0 for _, c := range said { if c.Key == grants.HeldCondition && c.State == asks.StateRaised && c.Severity == asks.SeverityWarning && strings.Contains(c.Summary, "a read of this pass failed") { nHeld++ } } if nHeld != 1 { t.Errorf("the held list raised the warning %d times, want once: %+v", nHeld, said) } b.refused[grants.ReadAccounts] = false if now, _, _ := p.Sealed(); now.Digest != g.Digest || now.Object != g.Object { t.Errorf("the list in force moved past a failed read: %s, was %s", now.Digest, g.Digest) } b.mu.Lock() hash := b.states[grants.ReadAccounts]["a.mod-x"] delete(b.states[grants.ReadAccounts], "a.mod-x") b.mu.Unlock() if res := p.Compose(); res.Sealed == nil || !strings.Contains(res.Sealed.Why, "a.mod-x would be left out") { t.Errorf("a pass that drops a.mod-x's hash = %+v", res.Sealed) } if now, _, _ := p.Sealed(); now.Digest != g.Digest { t.Errorf("the list in force moved without a.mod-x: %s", now.Digest) } // A list in force written before the part kept its users (no sealed-users): checked against the users the // bus loaded, so the first list after take-over has the same protection. b.mu.Lock() delete(b.own[grants.StateUserList], grants.KeyWrittenUsers) b.mu.Unlock() if res := p.Compose(); res.Sealed == nil || !strings.Contains(res.Sealed.Why, "a.mod-x would be left out") { t.Errorf("a pass with no kept users that drops a.mod-x's hash = %+v", res.Sealed) } if now, _, _ := p.Sealed(); now.Digest != g.Digest { t.Errorf("the list in force moved without a.mod-x and no kept users: %s", now.Digest) } b.put(grants.ReadAccounts, "a.mod-x", json.RawMessage(hash)) if p.Compose(); func() bool { now, _, _ := p.Sealed(); return now.Digest != g.Digest }() { t.Errorf("the list in force moved with every read and hash back") } if last := said[len(said)-1]; last.Key != grants.HeldCondition || last.State != asks.StateCleared { t.Errorf("the held warning is not cleared once nothing is held: %+v", last) } // A part started while the warning stood (the said state is in memory only) clears it on its first pass. var fresh []asks.Condition fp := partOn(b) fp.Objects = p.Objects fp.Asker, fp.Say = fa, func(c asks.Condition) error { if c.Key != grants.GrantMissingCondition && c.Key != grants.RefusedCondition { // their own rows fresh = append(fresh, c) } return nil } fp.Compose() if len(fresh) == 0 || fresh[0].Key != grants.HeldCondition || fresh[0].State != asks.StateCleared { t.Errorf("a fresh part's first pass does not say the held warning is cleared: %+v", fresh) } // Issue 1945, 2026-10-27 09:21: a list that would leave a seat the list in force holds with no holder, // while a placed module still claims it, is held, as such a membership is: not written, the warning // raised naming the seat. A list that only adds is written; one whose loaded grants cannot be read is held. inForce := slices.Clone(b.loaded) seat, verb := brokerSeat() seatGrant := "mesh.seat." + seat + ".call." + verb + ".*" for i := range b.loaded { if b.loaded[i].User == "a.nats" { b.loaded[i].Subscribe = append(slices.Clone(b.loaded[i].Subscribe), seatGrant) } } b.put(grants.ReadAssignments, "a.mod-y", map[string]any{"node": "a", "module": "mod-y", "assigned": "2026-10-27T08:20:00Z"}) b.put(grants.ReadAccounts, "a.mod-y", map[string]any{"user": "a.mod-y", "hash": "$2a$10$x", "generation": 1}) said = nil res := p.Compose() if res.Sealed == nil || !strings.Contains(res.Sealed.Why, "publishing would lose the last holder of "+seat) { t.Errorf("a list losing a seat grant = %+v", res.Sealed) } if now, _, _ := p.Sealed(); now.Digest != g.Digest { t.Errorf("a list losing a seat grant was written: %s, was %s", now.Digest, g.Digest) } if len(said) != 1 || said[0].Key != grants.HeldCondition || said[0].State != asks.StateRaised || !strings.Contains(said[0].Summary, "the last holder of "+seat) { t.Errorf("the held list did not raise the warning naming the lost grant: %+v", said) } b.loaded = inForce if p.Compose(); func() bool { now, _, _ := p.Sealed(); return now.Digest == g.Digest }() { t.Errorf("a list that only adds a.mod-y was not written") } written, _, _ := p.Sealed() b.mu.Lock() b.refuseUsers = true b.mu.Unlock() b.mu.Lock() delete(b.states[grants.ReadAssignments], "a.mod-y") b.mu.Unlock() if res := p.Compose(); res.Sealed == nil || !strings.Contains(res.Sealed.Why, "the grants of the list in force cannot be read") { t.Errorf("a pass whose loaded list cannot be read = %+v", res.Sealed) } if now, _, _ := p.Sealed(); now.Digest != written.Digest { t.Errorf("a list was written while the loaded list could not be read") } b.mu.Lock() b.refuseUsers = false b.mu.Unlock() } }) } t.Run("hand-back in shadow is refused at once, with nothing asked", func(t *testing.T) { n := len(fa.asks) if _, err := p.HandBack("shanks.node-tools", "a row"); err == nil || !strings.Contains(err.Error(), "nothing to hand back") { t.Errorf("hand-back in shadow = %v", err) } p.Compose() if len(fa.asks) != n { t.Errorf("a refused hand-back asked the operator") } }) t.Run("an ask that can stay open is closed every way and can be asked again", func(t *testing.T) { // A warrant the process missed is caught up from the router's record, whatever its outcome. id := asked("take-over") p.Compose() fa.wheres[id] = &asks.Warrant{Ask: id, Outcome: asks.OutcomeChosen, Option: "no"} p.Compose() if l := last(); l.Ask != id || l.Said != "declined" { t.Errorf("a missed warrant was not caught up: %+v", l) } // The router ended it with no choice, and the process was not told. id = asked("take-over") p.Compose() fa.wheres[id] = &asks.Warrant{Ask: id, Outcome: asks.OutcomeCancelled} p.Compose() if l := last(); l.Ask != id || !strings.Contains(l.Said, "cancelled") { t.Errorf("an ask the router ended was not closed: %+v", l) } // Said closes on any outcome for the open id, and none for another id. id = asked("take-over") p.Said(asker.Refused, asks.Warrant{Ask: "other", Outcome: asks.OutcomeRefused}, nil) if _, err := p.TakeOver("x", "y"); err == nil { t.Errorf("a warrant for another ask closed the open one") } p.Said(asker.Refused, asks.Warrant{Ask: id, Outcome: asks.OutcomeRefused}, nil) // A warrant for an ask that is not the open one switches nothing. if err := p.Warranted(yes, warrant("take-over-other")); err == nil { t.Errorf("a warrant for an ask that is not open was acted on") } // Past its expiry and a margin the part closes it itself; the clock moves, not a timer. id = asked("take-over") now := p.Now p.Now = func() time.Time { return now().Add(25 * time.Hour) } p.Compose() p.Now = now if l := last(); l.Ask != id || l.Said != string(asks.OutcomeExpired) { t.Errorf("an expired ask was not closed: %+v", l) } // An ask that could not be published is closed as refused, its half-made record taken away, and is not retried. fa.fail = true id = asked("take-over") p.Compose() p.Compose() if l := last(); l.Ask != id || l.Said != "refused" || !slices.Contains(forgot, id) || !slices.Contains(fa.cancels, id) { t.Errorf("a failed ask stays open, or is not taken back at the router: %+v, forgot %v, cancelled %v", l, forgot, fa.cancels) } fa.fail = false // An answer the part's own record cannot take yet (its state out of reach) leaves the ask open, closed by // nothing, and is taken at a later pass. id = asked("take-over") p.Compose() fa.wheres[id] = &asks.Warrant{Ask: id, Outcome: asks.OutcomeChosen, Option: "no"} fa.takeErr = errors.New("nats: no responders available for request") p.Compose() if l := last(); l.Ask != id || l.Said != "asked" { t.Errorf("an answer not taken closed the ask: %+v", l) } fa.takeErr = nil p.Compose() if l := last(); l.Ask != id || l.Said != "declined" { t.Errorf("the answer was not taken once its record could be reached: %+v", l) } asked("take-over") }) t.Run("only memberships published, nothing asserted", func(t *testing.T) { for _, s := range b.publishes { if !strings.HasPrefix(s, "mesh.assignment.") { t.Errorf("published on %s", s) } } for _, w := range b.writes { if !slices.Contains(grants.States, strings.TrimSuffix(w, " delete")) { t.Errorf("wrote %s, which is not the part's own state", w) } } for _, c := range b.calls { if c != grants.CallUsers && c != grants.CallStreams && c != grants.CallRecordWarrant { t.Errorf("called %s: the part calls only the bus module's read tools, and the hand-act log for a switch", c) } } for _, e := range b.events { if !slices.Contains(grants.Emits, e) { t.Errorf("said %s", e) } } }) }) t.Run("memberships: published unless a seat would lose its last holder or a holder a capability it asks for", func(t *testing.T) { const key = "a.mod-x" brokerName, brokerVerb := brokerSeat() // The telegram module's claim of its channel, as mesh-catalog's telegram manifest has it (c05aa5e6), on mod-x. const telegramClaim = `[{"name":"channel","scope":"mesh","kind":"telegram","capabilities":["deliver","verified-sender"]}]` verified := func(m map[string]any) map[string]any { m["seat-traffic"] = map[string]any{"kinds": []any{map[string]any{"seat": "channel", "kind": "telegram", "module": "mod-x", "node": "a", "capabilities": []any{"deliver", "verified-sender"}}}} return m } composed := func(b *fakeBus) map[string]any { recs, _ := grants.Records(grants.ReadInputs(b, &grants.Manifests{})) raw, _ := json.Marshal(grants.Memberships(recs)[key].Membership) var m map[string]any _ = json.Unmarshal(raw, &m) return m } for _, row := range []struct { name string last func(map[string]any) map[string]any published bool names string // claims, when set, are mod-x's claims in its manifest for the row. claims string }{ {"a first membership is published", nil, true, "", ""}, {"an additive change is published, the private network carried", func(m map[string]any) map[string]any { delete(m, "state") m["mesh"] = []any{"10.0.0.1"} return m }, true, "", ""}, // The lead's decision in review round 1 of the fix of issue 1945: only a seat left with no holder, or a // capability its holder still asks for (verified-sender, dropped by a root-free word that failed), holds a // membership back; a dropped tool or field is published. {"a change that drops a tool is published", func(m map[string]any) map[string]any { m["serves"] = append(m["serves"].([]any), map[string]any{"subject": "mesh.mod.mod-x.tool.gone"}) m["mesh"] = []any{"10.0.0.1"} return m }, true, "", ""}, {"a change that drops verified-sender its holder still asks for is not published, and named", verified, false, "channel telegram verified-sender of a/mod-x", telegramClaim}, {"a change that drops verified-sender its holder no longer asks for is published", verified, true, "published:published", `[{"name":"channel","scope":"mesh","kind":"telegram"}]`}, {"a change that takes the last holder from a seat a placed module claims is not published, the seat named", func(m map[string]any) map[string]any { m["seats"] = []any{map[string]any{"seat": brokerName, "verb": brokerVerb, "subject": "mesh.seat." + brokerName + ".tool." + brokerVerb}} return m }, false, "the last holder of " + brokerName, ""}, // A verb the seat catalogue retired on purpose (mark-ready, mesh-sdk d506f21, ADR 0421) is dropped and named; a // verb of a seat the catalogue does not define is still a loss (issue 1945). {"a change that drops only a retired seat verb is published, the verb named", func(m map[string]any) map[string]any { m["seats"] = []any{map[string]any{"seat": "mesh-delivery", "verb": "mark-ready", "subject": "mesh.seat.mesh-delivery.tool.mark-ready"}} return m }, true, "mesh-delivery mark-ready", ""}, {"a seat no placed module claims dropped is published", func(m map[string]any) map[string]any { m["seats"] = []any{map[string]any{"seat": "mesh-delivery", "verb": "mark-ready", "subject": "mesh.seat.mesh-delivery.tool.mark-ready"}, map[string]any{"seat": "telegram-sender", "verb": "send", "subject": "mesh.seat.telegram-sender.tool.send"}} return m }, true, "mesh-delivery mark-ready", ""}, } { t.Run(row.name, func(t *testing.T) { b := fixture() if row.claims != "" { b.manifestOf("mod-x", strings.Replace(xManifest, `"own-secrets"`, `"claims":`+row.claims+`,"own-secrets"`, 1)) } b.loaded = shadowOf(t, b) // The last membership is the part's own state's, never the assignments stream's: the stream goes at the lease // write (research 072 row 33), and a part reading its last there would publish nothing after it. if row.last != nil { raw, _ := json.Marshal(row.last(composed(b))) b.own[grants.StateMemberships] = map[string][]byte{key: raw} } res := partOn(b).Pass() if got := slices.Contains(res.Published, key); got != row.published { t.Fatalf("published %v, not published %v", res.Published, res.NotPublished) } // The state holds the bare membership the assignments stream carries, through mesh-sdk's codec, and // only one published (issue 774); what became of it is the kept pass's, which grants_membership reads. state, _, _, _ := b.ownGet(grants.StateMemberships, key) ms, _ := partOn(b).MembershipOf("a", "mod-x") pass := fmt.Sprint(ms.(map[string]any)["last_pass"]) if row.published { if row.names == "" && row.last != nil && !strings.Contains(string(state), "10.0.0.1") { t.Errorf("the private network of the last membership is not carried: %s", state) } got, err := membership.DecodeGrantsMembership(state) var sent membership.Membership _ = json.Unmarshal(b.stream["mesh.assignment."+key], &sent) if err != nil || !reflect.DeepEqual(got, sent) || bytes.Contains(state, []byte(`"membership"`)) { t.Errorf("the state is not the bare membership published (%v): %s", err, state) } if !strings.Contains(pass, row.names) { t.Errorf("the pass does not name %q: %s", row.names, pass) } return } if was, _ := json.Marshal(row.last(composed(b))); !bytes.Equal(state, was) { t.Errorf("a membership held back reached the state: %s", state) } said := strings.Join(res.NotPublished, "\n") if !strings.Contains(said, grants.ShadowDifference) || !strings.Contains(said, row.names) { t.Errorf("the loss is not named: %s", said) } if !strings.Contains(pass, row.names) { t.Errorf("grants_membership does not name what would be lost: %v", ms) } }) } // The assignments stream removed (nats_stream_remove at the lease write) and its subject refused: every membership // is still written to the state, where each runtime reads it, and the refusal is said (issue 774). t.Run("the stream gone and its subject refused: memberships still written, the refusal said", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) b.refusePub = true p := partOn(b) refused := p.Compose() if !slices.Equal(refused.Published, []string{"a.mod-x", "a.nats", "b.mod-y"}) || refused.MembershipsWritten != 3 || !strings.Contains(strings.Join(refused.Errors, "\n"), "Permissions Violation") { t.Errorf("with the stream gone = %+v: every membership written, the refusal said", refused) } b.refusePub = false if next := p.Compose(); len(next.Published) != 0 || next.MembershipsWritten != 0 { t.Errorf("the next event published %v again", next.Published) } }) t.Run("a tool answers while a pass waits on the bus", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) b.hold = make(chan struct{}) p := partOn(b) done := make(chan struct{}) go func() { p.Compose(); close(done) }() answered := make(chan struct{}) go func() { _, _ = p.UserList() _, _ = p.MembershipOf("a", "mod-x") _, _ = p.Grants("") _ = p.Last() close(answered) }() select { case <-answered: case <-time.After(10 * time.Second): t.Error("a tool waited on the pass's round trip") } close(b.hold) <-done }) }) t.Run("assertions: what the bus lacks is created once, what it holds is never touched", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) // The bus holds mod-x's bucket with another history, and neither its log nor its stream. b.streams = []map[string]any{{"name": "KV_mod-x_kept", "subjects": []string{"$KV.mod-x_kept.>"}, "retention": "limits", "max_per_subject": 2}} p := partOn(b) p.Create = b assertion := func() grants.Assertion { raw, _, _, _ := b.ownGet(grants.StateAssertions, "mod-x") var a grants.Assertion _ = json.Unmarshal(raw, &a) return a } first := p.Compose() if !slices.Equal(b.creates, []string{"LOG_mod-x_history", "THINGS"}) || len(first.Asserted) != 1 || !strings.HasPrefix(first.Asserted[0], "mod-x at ") || !slices.Contains(b.events, grants.EventAsserted) { t.Fatalf("the first pass created %v, said %v, events %v", b.creates, first.Asserted, b.events) } if b.createdAge["LOG_mod-x_history"] != 30*24*time.Hour { // a log's max-age-days is its stream's MaxAge (mesh-sdk 2e5cc4f) t.Errorf("the log was created with max age %v, want 30 days", b.createdAge["LOG_mod-x_history"]) } a := assertion() if !a.Asserted || a.Says != grants.CreatesMissing { t.Errorf("the assertion kept = %+v", a) } for _, it := range a.Items { switch it.Stream { case "KV_mod-x_kept": if it.Created != "" || !slices.Contains(it.Differences, "history 2 held, 3 composed") { t.Errorf("a bucket the bus holds was touched, or its difference not said: %+v", it) } default: if it.Created != grants.ItemCreated { t.Errorf("%s: %q, want created", it.Stream, it.Created) } } } if again := p.Compose(); len(again.Asserted) != 0 || len(b.creates) != 2 { t.Errorf("a pass with everything held created again: %v, %v", again.Asserted, b.creates) } // THINGS deleted since: not made again at the same manifest, said; a hand assert (the tool, asking the process) // makes it again. b.mu.Lock() b.streams = slices.DeleteFunc(b.streams, func(s map[string]any) bool { return s["name"] == "THINGS" }) b.mu.Unlock() gone := p.Compose() if len(b.creates) != 2 || !strings.Contains(strings.Join(gone.NotAsserted, " "), "THINGS: created at this manifest and gone since") { t.Errorf("a stream gone since was made again by itself, or not said: %v, %v", b.creates, gone.NotAsserted) } tool := partOn(b) if got, err := tool.AssertOne("mod-x"); err != nil || fmt.Sprint(got) == "" || len(b.creates) != 2 { t.Fatalf("the tool = %v, %v, creates %v: it asks, and creates nothing itself", got, err, b.creates) } if hand := p.Compose(); len(hand.Asserted) != 1 || b.creates[len(b.creates)-1] != "THINGS" { t.Errorf("the hand assert did not create THINGS again: %v, %v", hand.Asserted, b.creates) } if after := p.Compose(); len(after.Asserted) != 0 { t.Errorf("the hand ask was acted on twice: %v", after.Asserted) } for _, c := range b.creates { if c == "KV_mod-x_kept" { t.Error("the bucket the bus holds was asked to be created") } } }) // 774 (ADR 0563 §2, §3): the node-engine's manifest (mesh-host) is placed on every node though no node is assigned it, // so its declared state acts is created as a placed module's, and composed for the user list's create right. t.Run("assertions: the node-engine's declared state is created though no node is assigned it", func(t *testing.T) { b := fixture() b.manifestOf(buscompose.EngineModule, `{"module":"mesh-host","version":"1","state":[{"name":"acts"}]}`) b.loaded = shadowOf(t, b) p := partOn(b) p.Create = b p.Compose() if !slices.Contains(b.creates, "KV_"+buscompose.EngineActsBucket) { t.Fatalf("the node-engine's acts were not created: %v", b.creates) } recs, _ := grants.Records(grants.ReadInputs(b, &grants.Manifests{})) if recs.Engine == nil || len(recs.Engine.State) != 1 || recs.Engine.State[0].Bucket() != buscompose.EngineActsBucket { t.Fatalf("the records carry the node-engine as %+v", recs.Engine) } for node, mods := range recs.Assigned { for _, d := range mods { if d.Module == buscompose.EngineModule { t.Errorf("the node-engine is a module of %s, which would give it a module's account", node) } } } }) t.Run("assertions: a refused create is said, never stops it, and is tried at the next event", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) b.refuseCreate = true p := partOn(b) p.Create = b refused := p.Compose() if len(refused.Asserted) != 0 || !strings.Contains(strings.Join(refused.NotAsserted, " "), "Permissions Violation") || slices.Contains(b.events, grants.EventAsserted) { t.Fatalf("a refused create = %+v", refused) } b.refuseCreate = false if next := p.Compose(); len(next.Asserted) != 1 || !strings.Contains(next.Asserted[0], "KV_mod-x_kept") { t.Errorf("the next event created %v", next.Asserted) } // What the bus holds unread: nothing is created. c := fixture() c.loaded = shadowOf(t, c) q := partOn(c) q.Create = c q.Call = refusingStreams{c} if r := q.Compose(); len(c.creates) != 0 || len(r.Asserted) != 0 { t.Errorf("created %v with what the bus holds unread", c.creates) } }) t.Run("memberships held back: every one is said and listed", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) recs, _ := grants.Records(grants.ReadInputs(b, &grants.Manifests{})) b.own[grants.StateMemberships] = map[string][]byte{} for _, key := range []string{"a.mod-x", "b.mod-y"} { raw, _ := json.Marshal(grants.Memberships(recs)[key].Membership) var m map[string]any _ = json.Unmarshal(raw, &m) seat, verb := brokerSeat() m["seats"] = []any{map[string]any{"seat": seat, "verb": verb, "subject": "mesh.seat." + seat + ".tool." + verb}} raw, _ = json.Marshal(m) b.own[grants.StateMemberships][key] = raw } var logged []string var lmu sync.Mutex w := &fakeWatcher{opened: make(chan string, 16)} f := &grants.Follower{Part: partOn(b), Watch: w, Logf: func(format string, args ...any) { lmu.Lock() logged = append(logged, fmt.Sprintf(format, args...)) lmu.Unlock() }} ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { f.Run(ctx); close(done) }() waitFor(t, func() bool { lmu.Lock() defer lmu.Unlock() all := strings.Join(logged, "\n") return strings.Contains(all, "held a.mod-x: ") && strings.Contains(all, "held b.mod-y: ") }) cancel() <-done held, err := partOn(b).MembershipOf("", "") h, _ := held.(map[string]any) if seat, _ := brokerSeat(); err != nil || h["count"] != 2 || !strings.Contains(fmt.Sprint(h["held"]), "the last holder of "+seat) { t.Errorf("grants_membership with neither node nor module = %v, %v", held, err) } if _, err := partOn(b).MembershipOf("a", ""); err == nil { t.Error("a node without a module was answered") } }) t.Run("a refused read or write never stops it", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) b.refuseTo[grants.StateMemberships] = true res := partOn(b).Compose() if !res.Written || len(res.Errors) == 0 || !strings.Contains(strings.Join(res.Errors, " "), "memberships") { t.Errorf("a refused write of memberships = %+v: the user list is written all the same, the refusal said", res) } w := &fakeWatcher{refuse: map[string]int{grants.ReadModules: 1}, opened: make(chan string, 16)} var logged []string var lmu sync.Mutex f := &grants.Follower{Part: partOn(b), Watch: w, Logf: func(format string, args ...any) { lmu.Lock() logged = append(logged, fmt.Sprintf(format, args...)) lmu.Unlock() }} ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { f.Run(ctx); close(done) }() waitOpened(t, w, grants.ReadAssignments) if r := f.Refused(); r[grants.ReadModules] == "" { waitFor(t, func() bool { return f.Refused()[grants.ReadModules] != "" }) } w.change(grants.ReadAssignments, "a.mod-x") waitOpened(t, w, grants.ReadModules) waitFor(t, func() bool { return len(f.Refused()) == 0 }) select { case <-done: t.Fatal("the follower stopped") default: } cancel() <-done lmu.Lock() defer lmu.Unlock() if !strings.Contains(strings.Join(logged, "\n"), "cannot be watched yet") { t.Errorf("the refusal was not said: %v", logged) } }) // 774/L3a (novox/hq ADR 0547 §5, ADR 0561 §2): the part writes grants.. in shadow, once each and only for a // module whose accounts the vault holds a hash for, says grant-written for each, deletes one only when a module has no // account with a hash and every read was made, and the tool says every difference from the controller's. t.Run("grants: written in shadow once each, only for hashed accounts, deleted only when read whole, compared with the controller's; after take-over once the loaded list carries them", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) p := partOn(b) fp := "src2:" + strings.Repeat("ab", 32) key := func(module string) string { k, _ := delivery.PartGrantKey(module, fp); return k } hash := func(user string) { b.put(grants.ReadAccounts, user, map[string]any{"user": user, "hash": "$2a$10$x", "generation": 1}) } held := func() []string { return slices.Sorted(maps.Keys(b.own[grants.StateGrants])) } // The controller's grant for this fingerprint, beside the stale one the fixture holds for another. delete(b.states[grants.ReadControllerGrant], "mod-x.0123456789ab0123456789ab0123456789ab0123456789ab0123456789ab0123") b.put(grants.ReadControllerGrant, key("mod-x"), delivery.ControllerGrant{V: delivery.Version, Accounts: []string{"a.mod-x"}}) hash("a.mod-x") hash("a.nats") res := p.Compose() if res.Grants == nil || !slices.Equal(held(), []string{key("mod-x"), key("nats")}) || len(res.Grants.Written) != 2 { t.Fatalf("grants of the hashed modules: held %v, %+v", held(), res.Grants) } if g, err := delivery.DecodeControllerGrant(b.own[grants.StateGrants][key("mod-x")]); err != nil || !slices.Equal(g.Accounts, []string{"a.mod-x"}) { t.Errorf("the grant of mod-x is %+v, %v", g, err) } said := func() int { n := 0 for _, e := range b.events { if e == grants.EventGrantWritten { n++ } } return n } if said() != 2 { t.Errorf("grant-written said %d times for two grants: %v", said(), b.events) } if res := p.Compose(); len(res.Grants.Written) != 0 || said() != 2 { t.Errorf("a second pass wrote %v and said %d", res.Grants.Written, said()) } // The tool: equal on mod-x, and nats is the part's alone (the controller's fixture holds none). got, err := p.Grants("") gm, _ := got.(map[string]any) diff, _ := gm["differences"].([]grants.GrantDifference) if err != nil || gm["equal"] != false || len(diff) != 1 || diff[0].Key != key("nats") || diff[0].Kind != "only the part's" { t.Errorf("grants = %+v, %v", got, err) } // A user with a hash later: written once. Without it again: deleted. Unread hashes: nothing deleted. hash("b.mod-y") if res := p.Compose(); !slices.Equal(res.Grants.Written, []string{key("mod-y")}) { t.Errorf("mod-y was granted %v when its account got a hash", res.Grants.Written) } b.refused[grants.ReadAccounts] = true if res := p.Compose(); res.Grants == nil || len(res.Grants.Deleted)+len(res.Grants.Written) != 0 || len(res.Grants.Kept) != 1 || len(held()) != 3 { t.Errorf("a pass that could not read the hashes changed grants: %+v, held %v", res.Grants, held()) } delete(b.refused, grants.ReadAccounts) delete(b.states[grants.ReadAccounts], "b.mod-y") if res := p.Compose(); !slices.Equal(res.Grants.Deleted, []string{key("mod-y")}) || len(held()) != 2 { t.Errorf("a module with no hashed account kept its grant: %+v, held %v", res.Grants, held()) } // Issue 774: after take-over a grant is written at once when the list the bus loaded carries its accounts' permissions, // held list or not, and waits only when it adds one. (1) mod-x moves to a new fingerprint with the same permissions // while the list is held (users of the loaded list have no hash): written. busKey, err := seal.GenerateKeyPair() if err != nil { t.Fatal(err) } loads := func(source, digest string) { b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, UserList: &delivery.UserListWritten{Source: source, Revision: 8, Wanted: digest, Digest: digest, Users: 6, Key: busKey.PublicKey, Follows: delivery.FollowGrants}}) } loads("a.nats.src2", "sha256:aa") p.Objects = &fakeObjects{} sw, _ := json.Marshal(grants.Switch{V: 1, State: grants.TookOver, Acts: []grants.Act{}}) if _, err := (ownStore{b}).Put(grants.StateTakeover, grants.KeySwitch, sw); err != nil { t.Fatal(err) } moves := func(module, manifest, fp string) string { digest := delivery.ManifestDigest([]byte(manifest)) b.put(grants.ReadManifests, digest, delivery.Manifest{V: delivery.Version, Module: module, JSON: manifest}) b.put(grants.ReadModules, module, delivery.Module{V: delivery.Version, FP2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"}) k, _ := delivery.PartGrantKey(module, fp) return k } written := func(k string) bool { _, ok := b.own[grants.StateGrants][k]; return ok } keyX2 := moves("mod-x", xManifest, "src2:"+strings.Repeat("cd", 32)) res = p.Compose() if !written(keyX2) || res.Sealed == nil || !strings.Contains(res.Sealed.Why, "held") || len(res.Grants.Waits) != 0 { t.Fatalf("a new fingerprint with the same permissions under a held list: sealed %+v, grants %+v", res.Sealed, res.Grants) } // Every user hashed: the list is sealed, and the part keeps its permissions. The bus loads it, and its tools go quiet. for _, u := range shadowOf(t, b) { hash(u.User) } p.Compose() sealed, _, _ := p.Sealed() if sealed.Digest == "" { t.Fatal("no list sealed once every user has a hash") } loads(delivery.GrantsUserListSource, sealed.Digest) b.refuseUsers = true // (2) mod-y's trunk adds a state, so its account adds permissions: it waits, judged from the part's own record of the // loaded list; mod-x at another fingerprint with the same permissions is written. fp3 := "src2:" + strings.Repeat("ef", 32) keyY3 := moves("mod-y", strings.Replace(yManifest, `"reads"`, `"state":[{"name":"fresh"}],"reads"`, 1), fp3) keyX3 := moves("mod-x", xManifest, fp3) res = p.Compose() if !written(keyX3) || written(keyY3) || len(res.Grants.Waits) != 1 || !strings.HasPrefix(res.Grants.Waits[0], keyY3+": waits for the bus to load a list carrying what it adds: b.mod-y") { t.Fatalf("an added permission against the loaded list: %+v", res.Grants) } // A loaded list the part keeps no permissions of: every grant naming an account waits, said. loads(delivery.GrantsUserListSource, "sha256:bb") keyX4 := moves("mod-x", xManifest, "src2:"+strings.Repeat("12", 32)) if res := p.Compose(); written(keyX4) || len(res.Grants.Waits) != 2 || !strings.Contains(res.Grants.Waits[0], "unknown") { t.Fatalf("an unknown loaded list let a grant through: %+v", res.Grants) } // The bus loads a list carrying mod-y's state (its tools answer it): both written, nothing waits. listRecs, _ := grants.ListRecords(grants.ReadInputs(b, &grants.Manifests{})) now, err := grants.ComposeUsers(listRecs) if err != nil { t.Fatal(err) } b.refuseUsers, b.loaded = false, now if res := p.Compose(); len(res.Grants.Waits) != 0 || !written(keyY3) || !written(keyX4) { t.Errorf("once the loaded list carries them: %+v", res.Grants) } }) // Issue 1126 (C5): a module whose source commit has not moved since a build of the old form, with a done key of the // new form at that commit (link2pay, snake, f1-circuits, txt-game), is granted at the indexer's fingerprint, never // judged by its commit; and a placed module left without a grant is said, never passed over: in the pass, and as the // condition mesh-grants.grant-missing once the part's grants are the ones read, cleared when it has one. t.Run("grants: at the indexer's fingerprint whatever the commit, and a placed module without one is said", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) p := partOn(b) var said []asks.Condition p.Say = func(c asks.Condition) error { if c.Key == grants.GrantMissingCondition { said = append(said, c) } return nil } b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"handed-to": "the operator's hand-over"}) // The private network the mesh provides is placed with no fingerprint by construction: never a missing grant. b.assign("b", "mesh-wireguard") fp := "src2:d4afe7a1abab5ccdbef6d8629b2245006a2aec60290f430f7b4756e2930ed60c" b.put(grants.ReadModules, "mod-x", delivery.Module{V: delivery.Version, FP2: fp, Manifest: delivery.ManifestDigest([]byte(xManifest)), Repository: "novox/Link2Pay", Commit: "5d20e8bfbc953c51e8ebe3c671fe883df9c36e15"}) b.put(grants.ReadModules, "mod-y", delivery.Module{V: delivery.Version, None2: "its builder's version is not read yet", Manifest: delivery.ManifestDigest([]byte(yManifest)), Repository: "novox/y", Commit: "c"}) for _, u := range []string{"a.mod-x", "a.nats", "b.mod-y"} { b.put(grants.ReadAccounts, u, map[string]any{"user": u, "hash": "$2a$10$x", "generation": 1}) } res := p.Compose() key, _ := delivery.PartGrantKey("mod-x", fp) if _, held := b.own[grants.StateGrants][key]; !held { t.Errorf("mod-x has no grant at the indexer's fingerprint %s: %+v", fp, res.Grants) } if res.Grants == nil || len(res.Grants.Missing) != 1 || !strings.HasPrefix(res.Grants.Missing[0], "mod-y: no fingerprint of the new form: its builder's version is not read yet") { t.Fatalf("missing = %+v", res.Grants) } if len(said) != 1 || said[0].State != asks.StateRaised || !strings.Contains(said[0].Explanation, "mod-y") { t.Fatalf("said %+v", said) } if p.Compose(); len(said) != 1 { t.Errorf("the same missing grant was said again: %+v", said) } b.put(grants.ReadModules, "mod-y", delivery.Module{V: delivery.Version, FP2: fp, Manifest: delivery.ManifestDigest([]byte(yManifest)), Repository: "novox/y", Commit: "c"}) if res := p.Compose(); len(res.Grants.Missing) != 0 || len(said) != 2 || said[1].State != asks.StateCleared { t.Errorf("once mod-y has a grant: missing %v, said %+v", res.Grants.Missing, said) } }) // The guard of ADR 0549 §6 for this repository, as mesh-catalog's checks/placement is for the catalogue: no module.json // here reads the controller's assignments unless ../assignments-readers names it with why, and that list only shrinks. t.Run("assignments readers: only the named modules read the controller's assignments, and the list only shrinks", func(t *testing.T) { raw, err := os.ReadFile("../assignments-readers") if err != nil { t.Fatal(err) } allowed := map[string]bool{} for _, line := range strings.Split(string(raw), "\n") { if f := strings.Fields(line); len(f) > 1 && !strings.HasPrefix(f[0], "#") { allowed[f[0]] = true } else if len(f) == 1 { t.Errorf("../assignments-readers names %s with no why", f[0]) } } paths, _ := filepath.Glob("../*/module.json") reads := map[string]bool{} for _, path := range paths { raw, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var m struct { Module string `json:"module"` Reads []string `json:"reads"` } if err := json.Unmarshal(raw, &m); err != nil { t.Fatalf("%s: %v", path, err) } if slices.Contains(m.Reads, grants.ReadAssignments) { reads[m.Module] = true if !allowed[m.Module] { t.Errorf("%s reads %s: read where modules run from the seat mesh-placement's placement (ADR 0549 §3); the lease write "+ "is refused while a module reads the assignments (ADR 0549 §6)", m.Module, grants.ReadAssignments) } } } for m := range allowed { if !reads[m] { t.Errorf("../assignments-readers names %s, which no longer reads %s: take it off the list", m, grants.ReadAssignments) } } }) // 774 (the L3 review, ADR 0549 §3, §6): once the controller's lease is handed away the assignments are frozen and place // nothing; the seat's placement alone does. A module left only in the stale assignments loses its account and its // grant names no account of it; before the lease write the two are still unioned. t.Run("placement alone once the lease is handed away: a module only in the stale assignments has no account or grant of it", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) p := partOn(b) p.Bus = grants.BusAddress{URL: "nats://a.mesh:4222", Fingerprint: "ab"} pl, _ := delivery.EncodePlacement(delivery.Placement{Node: "a", Modules: []delivery.PlacedModule{{Module: "nats"}, {Module: "mod-x"}}}) b.states[grants.ReadPlacement] = map[string][]byte{"placement.a": pl} for _, u := range []string{"a.mod-x", "a.nats", "b.mod-y"} { b.put(grants.ReadAccounts, u, map[string]any{"user": u, "hash": "$2a$10$x", "generation": 1}) } fp := "src2:" + strings.Repeat("ab", 32) key, _ := delivery.PartGrantKey("mod-y", fp) grantOf := func() []string { g, _ := delivery.DecodeControllerGrant(b.own[grants.StateGrants][key]) return g.Accounts } b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"instance": "novox/1/2026-10-28", "epoch": 60}) p.Compose() if b.own[grants.StateAccounts]["b.mod-y"] == nil || !slices.Equal(grantOf(), []string{"b.mod-y"}) { t.Fatalf("under the controller's lease the assignments no longer count: account %v, grant %v", b.own[grants.StateAccounts]["b.mod-y"] != nil, grantOf()) } b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"handed-to": "the operator's hand-over"}) in := grants.ReadInputs(b, &grants.Manifests{}) if len(in.Assigned["b"]) != 0 || !slices.Equal(in.Assigned["a"], []string{"mod-x", "nats"}) || in.BusNode != "a" { t.Errorf("after the lease write the stale assignments still place: %v, bus %q", in.Assigned, in.BusNode) } res := p.Compose() if res.Accounts == nil || !slices.Equal(res.Accounts.Deleted, []string{"b.mod-y"}) || slices.Contains(grantOf(), "b.mod-y") { t.Fatalf("a module only in the stale assignments kept its account or grant: %+v, grant %v", res.Accounts, grantOf()) } }) // 774/AC4 (novox/hq ADR 0561 §2): every account the vault mints that the records compose is written to the state // accounts once, with the bus's address and a module's claims, read by the vault alone; a key is deleted when placement // no longer names its user, and never in a pass whose reads failed. t.Run("wanted accounts: written once each, read by the vault alone, retired only when placement drops them", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) p := partOn(b) p.Bus = grants.BusAddress{URL: "nats://a.mesh:4222", Fingerprint: "ab"} p.Compose() p.Compose() wrote := 0 for _, w := range b.writes { if w == grants.StateAccounts { wrote++ } } held := slices.Sorted(maps.Keys(b.own[grants.StateAccounts])) if !slices.Equal(held, []string{"a.mod-x", "a.nats", "b.mod-y"}) || wrote != 3 { t.Fatalf("accounts %v written %d times in two passes, want the three modules' once each", held, wrote) } nats, err := buscompose.ReadWantedAccount("a.nats", b.own[grants.StateAccounts]["a.nats"]) if err != nil || nats.URL != "nats://a.mesh:4222" || nats.Fingerprint != "ab" || len(nats.Claims) != 1 || nats.Claims[0].Seat != catalogue.BrokerSeat || nats.At.IsZero() { t.Errorf("a.nats is wanted as %+v (%v)", nats, err) } var readers []string for _, s := range grants.Announced() { if s.Name == grants.StateAccounts { if s.Readable { t.Error("the wanted accounts are announced readable by anyone") } readers = s.Readers } } if !slices.Equal(readers, []string{"mesh-vault"}) || buscompose.ReaderMay("mesh-delivery-view", buscompose.GrantsAccounts) { t.Errorf("the wanted accounts are read by %v", readers) } // A read that fails retires nothing; once read, the user placement dropped is retired. delete(b.states[grants.ReadAssignments], "b.mod-y") b.refused[grants.ReadResolved] = true if res := p.Compose(); res.Accounts == nil || len(res.Accounts.Deleted) != 0 || b.own[grants.StateAccounts]["b.mod-y"] == nil { t.Fatalf("a pass whose read failed deleted %+v", res.Accounts) } delete(b.refused, grants.ReadResolved) if res := p.Compose(); res.Accounts == nil || !slices.Equal(res.Accounts.Deleted, []string{"b.mod-y"}) { t.Fatalf("the user placement dropped is not retired: %+v", res.Accounts) } // The tool reads them, one and all, each with when it was written. one, err := p.Accounts("a.nats") all, err2 := p.Accounts("") if err != nil || err2 != nil || one.(map[string]any)["count"] != 1 || all.(map[string]any)["count"] != 2 || one.(map[string]any)["accounts"].([]buscompose.WantedAccount)[0].At.IsZero() { t.Errorf("the tool reads %v and %v (%v %v)", one, all, err, err2) } if _, err := p.Accounts("b.mod-y"); err == nil { t.Error("the tool reads an account no longer wanted") } p.Pass() if all, _ := p.Accounts(""); all.(map[string]any)["consumers"] == nil { t.Errorf("the tool does not say the account consumers in shadow: %v", all) } // With no bus address no account is written, and the pass says why. bare := partOn(fixture()) if res := bare.Compose(); res.Accounts != nil || !strings.Contains(strings.Join(res.Errors, " "), "no bus address") { t.Errorf("a part with no bus address wrote %+v: %v", res.Accounts, res.Errors) } }) // 774/AC5 (novox/hq ADR 0561 §1): the vault's account-issued wakes a pass, so the next list carries the new user's hash // within one pass; an event of another kind does not. t.Run("the vault's account-issued composes the list again", func(t *testing.T) { b := fixture() var handle func(string, []byte) p := partOn(b) f := &grants.Follower{Part: p, Watch: &fakeWatcher{opened: make(chan string, 16)}, Logf: func(string, ...any) {}, Hear: func(h func(string, []byte)) error { handle = h; return nil }} ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { f.Run(ctx); close(done) }() defer func() { cancel(); <-done }() waitFor(t, func() bool { return b.count(grants.CallUsers) > 0 && handle != nil }) held := func() bool { for _, c := range p.Last().UserList.Credentials.Users { if c.User == "a.mod-x" { return c.HashHeld } } return false } if held() { t.Fatal("a.mod-x has a hash before the vault made one") } b.mu.Lock() b.states[grants.ReadAccounts] = map[string][]byte{"a.mod-x": []byte(`{"user":"a.mod-x","hash":"$2a$10$x","generation":1}`)} b.mu.Unlock() handle(buscompose.AccountIssuedSubject, []byte(`{"user":"a.mod-x","kind":"module","node":"a","module":"mod-x","generation":1}`)) waitFor(t, held) if !slices.Contains(grants.Consumes, grants.EventAccountIssued) || !grants.IsAccountIssued(grants.EventAccountIssued) || grants.IsAccountIssued("mesh.mod.mesh-vault.event.secret-changed") { t.Errorf("the part hears %v", grants.Consumes) } }) // 774/AC6 (novox/hq ADR 0561 §6): the durable consumers an account needs, with the controller's configuration, are // compared in shadow while the controller holds the lease, and created, only where the bus lacks them, from the write // that hands the lease away; one the bus holds is never changed. t.Run("account consumers: compared while the controller holds the lease, created once it is handed away", func(t *testing.T) { const cManifest = `{"module":"mod-c","version":"1","own-secrets":{"broker":"/var/lib/c/broker"},"consumes":["mod-y.done"]}` b := fixture() b.manifestOf("mod-c", cManifest) b.assign("b", "mod-c") b.loaded = shadowOf(t, b) b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"instance": "novox/1/2026-10-27", "epoch": 57}) p := partOn(b) p.Consumers = b res := p.Compose() if res.Consumers == nil || res.Consumers.On || !slices.Contains(res.Consumers.Missing, "EVENTS.b_mod-c") || len(b.consumerCreates) != 0 { t.Fatalf("while the controller holds the lease: %+v, created %v", res.Consumers, b.consumerCreates) } b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"handed-to": "the operator's hand-over"}) b.consumers = map[string][]map[string]any{"EVENTS": {{"name": "b_other", "filters": []string{"x"}, "max_deliver": 5}}} res = p.Compose() if !res.Consumers.On || !slices.Equal(res.Consumers.Created, []string{"EVENTS.b_mod-c"}) { t.Fatalf("once handed away: %+v", res.Consumers) } var made map[string]any for _, c := range b.consumers["EVENTS"] { if c["name"] == "b_mod-c" { made = c } } if made == nil || made["max_deliver"] != 5 || !slices.Contains(made["filters"].([]string), "mesh.mod.mod-y.event.done") { t.Errorf("b_mod-c was made as %v, not as the controller makes it", made) } res = p.Compose() if len(res.Consumers.Created) != 0 || len(res.Consumers.Missing) != 0 || len(b.consumerCreates) != 1 { t.Errorf("a consumer the bus holds was asked again: %+v, %v", res.Consumers, b.consumerCreates) } // A held consumer that differs is never changed by a pass: it is raised as a condition naming it, and brought to // the composed configuration only by hand (grants_consumer_match), never while the controller holds the lease. var said []asks.Condition p.Say = func(c asks.Condition) error { if c.Key != grants.GrantMissingCondition && c.Key != grants.RefusedCondition { // their own rows said = append(said, c) } return nil } made["max_deliver"] = 9 res = p.Compose() if len(res.Consumers.Differ) != 1 || !strings.Contains(res.Consumers.Differ[0], "EVENTS.b_mod-c: max deliver 9") || len(said) != 1 || said[0].Key != grants.ConsumerDiffersCondition || said[0].State != asks.StateRaised || !strings.Contains(said[0].Explanation, "EVENTS.b_mod-c") { t.Fatalf("a differing consumer: %+v, said %+v", res.Consumers, said) } b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"instance": "novox/2/2026-10-27", "epoch": 58}) if _, err := p.MatchConsumer("EVENTS", "b_mod-c"); err == nil || !strings.Contains(err.Error(), "controller holds the lease") { t.Errorf("matched by hand under the controller's lease: %v", err) } b.put(grants.ReadLease, grants.LeaseKey, map[string]any{"handed-to": "the operator's hand-over"}) if _, err := p.MatchConsumer("EVENTS", "b_nothing"); err == nil { t.Error("a consumer the records do not compose was asked to match") } if _, err := p.MatchConsumer("EVENTS", "b_mod-c"); err != nil { t.Fatal(err) } res = p.Compose() if !slices.Contains(b.consumerCreates, "match EVENTS.b_mod-c") || made["max_deliver"] != 5 || len(res.Consumers.Matched) != 1 { t.Errorf("the ask to match: %+v, %v", res.Consumers, b.consumerCreates) } res = p.Compose() if len(res.Consumers.Differ) != 0 || said[len(said)-1].State != asks.StateCleared { t.Errorf("once matched: %+v, said %+v", res.Consumers, said) } }) // 774/AC13 (novox/hq ADR 0561 §7): after take-over a list in which more than one loaded user's hash changes, with no // rotation by the vault naming each, is held: the list in force stays, user-list-held is raised naming those users. One // user's change, or changes the vault's rotation names, are written. t.Run("a list changing several loaded users' hashes is held unless the vault's rotation names each", func(t *testing.T) { b := fixture() b.loaded = shadowOf(t, b) hash := func(user, h string, generation int, rotating bool) { v := map[string]any{"user": user, "hash": h, "generation": generation, "fingerprint": fmt.Sprintf("sha256:%d", generation)} if rotating { v["rotating"] = map[string]any{"login": user + "--b", "generation": generation, "revision": 7} } b.put(grants.ReadAccounts, user, v) } for _, u := range shadowOf(t, b) { hash(u.User, "$2a$10$first", 1, false) } busKey, err := seal.GenerateKeyPair() if err != nil { t.Fatal(err) } b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 6, Key: busKey.PublicKey}}) p := partOn(b) p.Objects = &fakeObjects{} var said []asks.Condition p.Say = func(c asks.Condition) error { said = append(said, c); return nil } sw, _ := json.Marshal(grants.Switch{V: 1, State: grants.TookOver, Acts: []grants.Act{}}) if _, err := (ownStore{b}).Put(grants.StateTakeover, grants.KeySwitch, sw); err != nil { t.Fatal(err) } p.Compose() inForce, _, _ := p.Sealed() if inForce.Digest == "" { t.Fatal("no list written after take-over") } // One user's hash changes: written. hash("a.mod-x", "$2a$10$second", 2, false) p.Compose() if now, _, _ := p.Sealed(); now.Digest == inForce.Digest { t.Fatal("a list changing one loaded user's hash was not written") } inForce, _, _ = p.Sealed() // Two change at once, no rotation names them: held, said naming both. hash("a.mod-x", "$2a$10$third", 3, false) hash("a.nats", "$2a$10$third", 2, false) said = nil res := p.Compose() if now, _, _ := p.Sealed(); now.Digest != inForce.Digest || res.Sealed == nil || !strings.Contains(res.Sealed.Why, "a.mod-x, a.nats") { t.Fatalf("a list changing two loaded users' hashes: %+v", res.Sealed) } if len(said) != 1 || said[0].Key != grants.HeldCondition || said[0].State != asks.StateRaised || !strings.Contains(said[0].Summary, "a.mod-x, a.nats") { t.Errorf("the hold was said as %+v", said) } // The hold is its own guard, beside take-over's refusals (ADR 0562): the switch stays taken over. if p.SwitchState() != grants.TookOver { t.Errorf("the hold moved the switch to %s", p.SwitchState()) } // A person accepts exactly the named users' changes of the held list, with why: refused for another set, with no // why, or once the list changed since; accepted, the next pass writes it once, said as a hand act. if _, err := p.AcceptHashes("person.jochen", []string{"a.mod-x"}, "rotated by hand"); err == nil || !strings.Contains(err.Error(), "a.mod-x, a.nats") { t.Errorf("an acceptance of another set than the held one: %v", err) } if _, err := p.AcceptHashes("person.jochen", []string{"a.mod-x", "a.nats"}, ""); err == nil { t.Error("an acceptance with no why") } if _, err := p.AcceptHashes("person.jochen", []string{"a.nats", "a.mod-x"}, "the vault's store was restored"); err != nil { t.Fatal(err) } // The list changed since the acceptance (a.mod-x's hash again): the acceptance is bound to the held list, refused. hash("a.mod-x", "$2a$10$fourth", 4, false) res = p.Compose() if now, _, _ := p.Sealed(); now.Digest != inForce.Digest || res.Sealed == nil || !strings.Contains(res.Sealed.Why, "held") { t.Fatalf("an acceptance of a list that changed since released it: %+v", res.Sealed) } if _, err := p.AcceptHashes("person.jochen", []string{"a.mod-x", "a.nats"}, "the vault's store was restored"); err != nil { t.Fatal(err) } b.events = nil p.Compose() if now, _, _ := p.Sealed(); now.Digest == inForce.Digest || said[len(said)-1].State != asks.StateCleared || !slices.Contains(b.events, grants.EventHashesAccepted) { t.Fatalf("an accepted hold was not released once and said: %+v, events %v", said, b.events) } inForce, _, _ = p.Sealed() // The vault's rotation names each: written, the warning cleared. hash("a.mod-x", "$2a$10$fifth", 5, true) hash("a.nats", "$2a$10$fifth", 3, true) p.Compose() if now, _, _ := p.Sealed(); now.Digest == inForce.Digest || said[len(said)-1].State != asks.StateCleared { t.Errorf("changes the vault's rotation names were held: %+v", said) } }) // Sweep 2026-10-30 item 5: the permissions of the sealed lists were one value holding every user of two lists, which // outgrew the bucket's largest value, so they were never kept. One key per user: no value grows with the list. t.Run("sealed grants: one key per user, so no value grows with the list, and only the last two lists kept", func(t *testing.T) { b := fixture() for _, u := range shadowOf(t, b) { b.put(grants.ReadAccounts, u.User, map[string]any{"user": u.User, "hash": "$2a$10$first", "generation": 1, "fingerprint": "sha256:1"}) } largest := 0 for _, u := range shadowOf(t, b) { raw, _ := json.Marshal(u) largest = max(largest, len(raw)) } b.maxValue = largest + 256 // one user's permissions fit, every user's of a list do not busKey, err := seal.GenerateKeyPair() if err != nil { t.Fatal(err) } b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 6, Key: busKey.PublicKey}}) p := partOn(b) p.Objects = &fakeObjects{} var digests []string for gen := 1; gen <= 3; gen++ { b.put(grants.ReadAccounts, "a.mod-x", map[string]any{"user": "a.mod-x", "hash": fmt.Sprintf("$2a$10$gen%d", gen), "generation": gen, "fingerprint": fmt.Sprintf("sha256:%d", gen)}) res := p.Compose() for _, e := range res.Errors { if strings.Contains(e, "10054") { t.Fatalf("a sealed-grants write exceeded the bucket's largest value: %s", e) } } g, _, _ := p.Sealed() digests = append(digests, g.Digest) } var idx grants.SealedLists raw, _, found, _ := b.ownGet(grants.StateUserList, grants.KeySealedLists) if !found || json.Unmarshal(raw, &idx) != nil || len(idx.Lists) != 2 || idx.Lists[0].Digest != digests[2] || idx.Lists[1].Digest != digests[1] { t.Fatalf("the index of kept lists = %s, want the last two of %v", raw, digests) } for i, d := range digests { _, _, kept, _ := b.ownGet(grants.StateUserList, grants.SealedUserKey(d, "a.mod-x")) if kept != (i > 0) { t.Errorf("list %d's a.mod-x key kept = %v", i, kept) } } for _, u := range idx.Lists[0].Users { if _, _, ok, _ := b.ownGet(grants.StateUserList, grants.SealedUserKey(digests[2], u)); !ok { t.Errorf("the index names %s, whose key is not there", u) } } // The loaded list's permissions are read back from the keys when the bus module's tools cannot say them. b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, UserList: &delivery.UserListWritten{Source: delivery.GrantsUserListSource, Follows: delivery.FollowGrants, Revision: 8, Digest: digests[2], Users: 6, Key: busKey.PublicKey}}) b.refuseUsers = true if res := p.Compose(); slices.ContainsFunc(res.Grants.Waits, func(w string) bool { return strings.Contains(w, "unknown") }) { t.Errorf("the kept permissions of the loaded list were not read: %v", res.Grants.Waits) } }) // Sweep 2026-10-30 item 5, a kind met twice that day (the engine's acts, then mesh-host_health): one principal the // composer refuses as a second writer froze the whole list. Now it keeps its grants of the list in force, the rest is // written, and the refusal is raised by name; one the list in force does not carry stays out, said too. t.Run("a refused principal keeps its grants of the list in force; the rest of the list is written", func(t *testing.T) { b := fixture() const ctl = `{"module":"mesh-controller","version":"1","own-secrets":{"broker":"/var/lib/c/broker"}}` refusedCtl := strings.Replace(ctl, `}}`, `},"state":[{"name":"declared-config"}]}`, 1) b.manifestOf("mesh-controller", ctl) b.assign("b", "mesh-controller") hash := func(user, h string, gen int) { b.put(grants.ReadAccounts, user, map[string]any{"user": user, "hash": h, "generation": gen, "fingerprint": fmt.Sprintf("sha256:%d", gen)}) } for _, u := range shadowOf(t, b) { hash(u.User, "$2a$10$first", 1) } hash("a.mesh-controller", "$2a$10$first", 1) busKey, err := seal.GenerateKeyPair() if err != nil { t.Fatal(err) } b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 6, Key: busKey.PublicKey}}) p := partOn(b) objs := &fakeObjects{} p.Objects = objs entryOf := func(g delivery.GrantsUserList, user string) string { t.Helper() list, err := delivery.OpenUserList(objs.objects[g.Object], g.Digest, func(bx seal.Box) ([]byte, error) { out, err := seal.Open(bx, busKey.PrivateKey) return []byte(out), err }) if err != nil { t.Fatalf("the sealed list does not open: %v", err) } at := strings.Index(string(list), fmt.Sprintf("{ user: %q,", user)) if at < 0 { return "" } rest := string(list)[at:] return rest[:strings.Index(rest, "} }\n")+4] } var said []asks.Condition p.Say = func(c asks.Condition) error { if c.Key == grants.RefusedCondition { said = append(said, c) } return nil } sw, _ := json.Marshal(grants.Switch{V: 1, State: grants.TookOver, Acts: []grants.Act{}}) if _, err := (ownStore{b}).Put(grants.StateTakeover, grants.KeySwitch, sw); err != nil { t.Fatal(err) } p.Compose() inForce, _, _ := p.Sealed() if inForce.Digest == "" { t.Fatal("no list written after take-over") } entryBefore := entryOf(inForce, "b.mesh-controller") ctlKey := grants.SealedUserKey(inForce.Digest, "b.mesh-controller") before, _, ok, _ := b.ownGet(grants.StateUserList, ctlKey) if !ok { t.Fatal("the list in force keeps no grants of b.mesh-controller") } // b.mesh-controller now declares the controller's mirror as its own state: refused as a second writer. a.mod-x's hash // changes in the same pass, and a.mesh-controller, refused the same way, is placed new. b.manifestOf("mesh-controller", refusedCtl) b.assign("a", "mesh-controller") hash("a.mod-x", "$2a$10$second", 2) res := p.Compose() now, _, _ := p.Sealed() if now.Digest == inForce.Digest || slices.ContainsFunc(res.Errors, func(e string) bool { return strings.Contains(e, "composed whole") }) { t.Fatalf("a refused principal froze the list: sealed %+v, errors %v", res.Sealed, res.Errors) } after, _, ok, _ := b.ownGet(grants.StateUserList, grants.SealedUserKey(now.Digest, "b.mesh-controller")) if !ok || string(after) != string(before) { t.Errorf("b.mesh-controller did not keep its grants of the list in force: %s, was %s", after, before) } // The kept user is written as the composer writes one: the bus reads the same entry it loaded. if got := entryOf(now, "b.mesh-controller"); entryBefore == "" || got != entryBefore { t.Errorf("the kept entry is written otherwise than the list in force holds it:\n%s\nwas\n%s", got, entryBefore) } if entryOf(now, "a.mesh-controller") != "" { t.Error("a refused principal the list in force did not carry is in the sealed list") } if _, _, ok, _ := b.ownGet(grants.StateUserList, grants.SealedUserKey(now.Digest, "a.mesh-controller")); ok { t.Error("a refused principal the list in force did not carry is in the list") } if len(res.PrincipalsRefused) != 2 || len(said) == 0 || said[len(said)-1].State != asks.StateRaised || !strings.Contains(said[len(said)-1].Summary, "b.mesh-controller (keeps its grants of the list in force)") || !strings.Contains(said[len(said)-1].Summary, "a.mesh-controller (left out of the list") { t.Fatalf("the refusals were not said by name: %v, said %+v", res.PrincipalsRefused, said) } // Composing again changes nothing and says nothing new; once the manifest composes, the warning is cleared. n := len(said) if p.Compose(); len(said) != n { t.Errorf("an unchanged refusal was said again: %+v", said[n:]) } b.manifestOf("mesh-controller", ctl) if res := p.Compose(); len(res.PrincipalsRefused) != 0 || said[len(said)-1].State != asks.StateCleared { t.Errorf("the refusal was not cleared once it composes: %v, said %+v", res.PrincipalsRefused, said) } }) } type fakeAsker struct { asks []asks.Ask wheres map[string]*asks.Warrant // the router's warrants, by ask fail bool cancels []string // takeErr makes Take fail as the asker does when the part's own record cannot be reached: no handling, an error. takeErr error } func (f *fakeAsker) Cancel(id string) error { f.cancels = append(f.cancels, id); return nil } // fakeObjects is the part's object bucket of sealed lists. type fakeObjects struct{ objects map[string][]byte } func (o *fakeObjects) PutObject(name string, data []byte) error { if o.objects == nil { o.objects = map[string][]byte{} } o.objects[name] = data return nil } func (o *fakeObjects) DeleteObject(name string) error { delete(o.objects, name); return nil } func (f *fakeAsker) Ask(a asks.Ask) error { if f.fail { return errors.New("the bus refused the publish") } f.asks = append(f.asks, a) return nil } func (f *fakeAsker) Where(id string) (string, *asks.Warrant, error) { return "open", f.wheres[id], nil } // Take hands the warrant to act as the SDK's asker does for one that chose, and records any other. func (f *fakeAsker) Take(w asks.Warrant, act func(asks.Option, asks.Warrant) error) (asker.Handled, error) { if f.takeErr != nil { return "", f.takeErr } if w.Outcome != asks.OutcomeChosen { return asker.Recorded, nil } return asker.Acted, act(asks.Option{ID: w.Option}, w) } func nonNil(s []string) []string { if s == nil { return []string{} } return s } // fakeWatcher refuses a state's first watches as told, and hands changes to the watches it opened. // refusingStreams is the bus with nats_streams refused: what the bus holds cannot be read. type refusingStreams struct{ *fakeBus } func (r refusingStreams) Call(key string, body any) (json.RawMessage, error) { if key == grants.CallStreams { return nil, errors.New("refused") } return r.fakeBus.Call(key, body) } type fakeWatcher struct { mu sync.Mutex refuse map[string]int changes map[string]func(string, []byte) opened chan string } func (w *fakeWatcher) Watch(state string, changed func(string, []byte), _ func(error)) error { w.mu.Lock() defer w.mu.Unlock() if w.refuse[state] > 0 { w.refuse[state]-- return errors.New("Permissions Violation for Publish to $JS.API.STREAM.INFO.KV_" + state) } if w.changes == nil { w.changes = map[string]func(string, []byte){} } w.changes[state] = changed w.opened <- state return nil } func (w *fakeWatcher) change(state, key string) { w.mu.Lock() h := w.changes[state] w.mu.Unlock() h(key, []byte(`{}`)) } func waitOpened(t *testing.T, w *fakeWatcher, state string) { t.Helper() deadline := time.After(10 * time.Second) for { select { case s := <-w.opened: if s == state { return } case <-deadline: t.Fatalf("%s was never watched", state) } } } func waitFor(t *testing.T, ok func() bool) { t.Helper() for end := time.Now().Add(10 * time.Second); time.Now().Before(end); time.Sleep(10 * time.Millisecond) { if ok() { return } } t.Fatal("never came") }