From 3dadc46ee6ccb47fed038394a483af722ff542a3 Mon Sep 17 00:00:00 2001 From: jochen Date: Sun, 25 Oct 2026 01:58:27 +0200 Subject: [PATCH] Grant each provisioner client a read of its node's own have, so it refills when the node's own composition moves (issue 1945) --- internal/broker/provisioner.go | 5 ++++ internal/broker/provisioner_have_test.go | 29 ++++++++++++++++++++++++ internal/broker/provisioner_test.go | 7 ++++++ 3 files changed, 41 insertions(+) create mode 100644 internal/broker/provisioner_have_test.go diff --git a/internal/broker/provisioner.go b/internal/broker/provisioner.go index a23f749c..75dab599 100644 --- a/internal/broker/provisioner.go +++ b/internal/broker/provisioner.go @@ -3,6 +3,7 @@ package broker import ( "errors" "fmt" + "git.novox.be/novox/mesh-sdk/go/delivery" "strings" "github.com/nats-io/nats.go" @@ -131,6 +132,10 @@ func provisionerGrants(p Principal) (pub, sub []string, err error) { pub = append(pub, "$KV."+bucket+"."+p.Node+".>") } pub = append(pub, wholeBucketRead(ProvisionerRegistryBucket)...) + // Its own node's own have, `.node`, read and never written, and no other key of the bucket: it fills every + // module again when the node-engine moves to a new composition of the node's own (issue 1945), since an apply can + // leave a filled file as the composition had it. + pub = append(pub, readGrants(delivery.HaveBucket, p.Node+".node")...) pub = append(pub, "prov.applied."+p.Node+".>") token := CallerToken(p.Username()) if token == "" { diff --git a/internal/broker/provisioner_have_test.go b/internal/broker/provisioner_have_test.go new file mode 100644 index 00000000..76b464a1 --- /dev/null +++ b/internal/broker/provisioner_have_test.go @@ -0,0 +1,29 @@ +package broker + +import ( + "slices" + "testing" +) + +// The provisioner client reads its own node's own have, `.node` in node-engine_have, and no other key of it: it +// fills every module again when the node-engine moves to a new composition of the node's own (issue 1945, live on +// 2026-10-25: the files a composition had written empty were refilled by nothing). +func TestTheProvisionerReadsItsNodesOwnHaveAlone(t *testing.T) { + p, err := PermissionsFor(Principal{Kind: KindProvisioner, Node: "g14", PasswordHash: "x"}) + if err != nil { + t.Fatal(err) + } + for _, want := range []string{"$JS.API.STREAM.INFO.KV_node-engine_have", + "$JS.API.DIRECT.GET.KV_node-engine_have.$KV.node-engine_have.g14.node", + "$JS.API.CONSUMER.CREATE.KV_node-engine_have.*.$KV.node-engine_have.g14.node"} { + if !slices.Contains(p.Publish, want) { + t.Errorf("the provisioner may not %s", want) + } + } + for _, s := range p.Publish { + if s == "$JS.API.DIRECT.GET.KV_node-engine_have.$KV.node-engine_have.g14.>" || + s == "$JS.API.DIRECT.GET.KV_node-engine_have.>" || s == "$KV.node-engine_have.g14.node" { + t.Errorf("the provisioner is granted %s", s) + } + } +} diff --git a/internal/broker/provisioner_test.go b/internal/broker/provisioner_test.go index c0d47c75..2c8ba1a9 100644 --- a/internal/broker/provisioner_test.go +++ b/internal/broker/provisioner_test.go @@ -93,6 +93,13 @@ func TestTheProvisionerClientIsGrantedItsMachinesKeysAndNothingElse(t *testing.T "$JS.API.CONSUMER.CREATE.KV_mesh-provisioner_registry.>", "$JS.API.CONSUMER.DELETE.KV_mesh-provisioner_registry.>", "$JS.FC.KV_mesh-provisioner_registry.>") + // Its node's own have, one key, read (issue 1945). + wantPub = append(wantPub, + "$JS.API.STREAM.INFO.KV_node-engine_have", + "$JS.API.DIRECT.GET.KV_node-engine_have.$KV.node-engine_have.g14.node", + "$JS.API.CONSUMER.CREATE.KV_node-engine_have.*.$KV.node-engine_have.g14.node", + "$JS.API.CONSUMER.DELETE.KV_node-engine_have.>", + "$JS.FC.KV_node-engine_have.>") wantPub = append(wantPub, "prov.applied.g14.>", "mesh.seat.mesh-vault.call.get.provisioner~g14", "prov.changed.g14.>") slices.Sort(wantPub)