mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestTheInstallersFirstUserListIsWhatTheControllerWouldCompose (0.66s)
mesh/delivery rejected: the gate failed or could not run, or the repository's own check failed
mesh/delivery-group group feat/the-controller-asks-the-operator rejected: a member's own check failed
99 lines
3.3 KiB
Go
99 lines
3.3 KiB
Go
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 + ".>"}
|
|
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 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)
|
|
}
|
|
}
|