package broker import ( "context" "slices" "strings" "testing" "time" "github.com/nats-io/nats.go/jetstream" "github.com/novox/mesh-controller/internal/testbus" ) // **The controller may write every bucket it writes** (novox/hq to-be 45 §1, issue 269). Writing a // key is a publish to the bucket's own subject, which the management interface's grant does not // cover: the cancelled sets' writes timed out for want of this, and the controller's own buckets // would have. func TestTheControllerMayWriteEveryBucketItWrites(t *testing.T) { p, err := PermissionsFor(Principal{Kind: KindController, PasswordHash: "x"}) if err != nil { t.Fatal(err) } want := []string{"$KV." + CallsBucket + ".>", "$KV." + HandActsBucket + ".>", "$KV." + AssignmentsBucket + ".>", "$KV." + DeclaredConfigBucket + ".>"} for _, seat := range seatsTheControllerAsks { if hasCancelledSet(seat) { want = append(want, "$KV."+CancelledSetName(seat)+".>") } } for _, subject := range want { if !slices.Contains(p.Publish, subject) { t.Errorf("the controller may not publish %s, so it cannot write that bucket", subject) } } if slices.Contains(p.Publish, "$KV.>") { t.Error("the controller may write any bucket, a module's state included") } } // **A module reads a record mirror through `reads`, and never writes it** (ADR 0201 §1, design 54 §5, // design 57 F3): granted the direct reads and watches of the bucket, and refused at composition a grant to // write it, whose writer is the controller. func TestAModuleReadsTheControllersMirrorsAndWritesNone(t *testing.T) { reader := Principal{Kind: KindModule, Node: "one", Module: "mesh-config", PasswordHash: "x", Reads: []string{"mesh-controller." + MirrorAssignments, "mesh-controller." + MirrorDeclaredConfig}} p, err := PermissionsFor(reader) if err != nil { t.Fatal(err) } for _, bucket := range []string{AssignmentsBucket, DeclaredConfigBucket} { for _, subject := range []string{"$JS.API.STREAM.INFO.KV_" + bucket, "$JS.API.DIRECT.GET.KV_" + bucket + ".>", "$JS.API.CONSUMER.CREATE.KV_" + bucket + ".>"} { if !slices.Contains(p.Publish, subject) { t.Errorf("a module reading %s may not publish %s", bucket, subject) } } for _, subject := range p.Publish { if SubjectsOverlap(subject, "$KV."+bucket+".>") { t.Errorf("a module reading %s may write it through %s", bucket, subject) } } if err := CheckWriters(reader, []string{"$KV." + bucket + ".>"}); err == nil { t.Errorf("a module granted a write of %s would compose", bucket) } } } // **A machine's node tools may say they are there, as that machine and no other** (novox/hq to-be 45 // S11), and the controller may hear the bus's advisories and ask who answers — read-only, named. func TestTheWatchedSignalsMayBeSaidAndHeard(t *testing.T) { tools, err := PermissionsFor(Principal{Kind: KindNodeTools, Node: "anchor", Module: RuntimeModule, PasswordHash: "x"}) if err != nil { t.Fatal(err) } if !slices.Contains(tools.Publish, "mesh.control.anchor.tools-alive") { t.Error("the node tools may not say they are there") } for _, s := range tools.Publish { if strings.Contains(s, "tools-alive") && s != "mesh.control.anchor.tools-alive" { t.Errorf("the node tools may say %s", s) } } controller, err := PermissionsFor(Principal{Kind: KindController, PasswordHash: "x"}) if err != nil { t.Fatal(err) } for _, s := range BusAdvisories { if !slices.Contains(controller.Subscribe, s) { t.Errorf("the controller may not hear %s", s) } } if !slices.Contains(controller.Publish, "$SRV.INFO") || slices.Contains(controller.Subscribe, "$JS.EVENT.>") { t.Error("the controller may not ask who answers, or hears every API call") } } // The controller's record of what it asked the operator is bounded (correctness review of 2026-10-08): one // value a key, a month's age, and a size it cannot outgrow. func TestWhatTheControllerAskedIsBounded(t *testing.T) { js, err := Dial(testbus.URL(t)) if err != nil { t.Fatal(err) } defer js.Close() if err := js.EnsureControllerBuckets(); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() kv, err := jetstream.New(js.Conn()) if err != nil { t.Fatal(err) } bucket, err := kv.KeyValue(ctx, AskedBucket) if err != nil { t.Fatal(err) } status, err := bucket.Status(ctx) if err != nil { t.Fatal(err) } info := status.(*jetstream.KeyValueBucketStatus).StreamInfo() if status.History() != 1 || status.TTL() != AskedKeptFor || info.Config.MaxBytes <= 0 || info.Config.MaxBytes > 64<<20 { t.Errorf("history %d, age %s, bytes %d", status.History(), status.TTL(), info.Config.MaxBytes) } }