Prove on a server under the composed grants that a module writes its bound bucket (issue 1966)

The integration case after created proved only that the binding is in the
membership, never that the account can use it (design 25 § 12.7). Under the
list the composer writes, the keeper creates mod-c_kept, mod-c's own account
binds and writes it, and another module reads nothing of it.

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-09 14:01:41 +01:00
parent 67e1c7594f
commit 44422f21db
@@ -16,6 +16,7 @@ import (
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/membership"
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-sdk/go/seal"
"github.com/nats-io/nats-server/v2/server"
@@ -37,7 +38,10 @@ func theBusNodeFollowsTheSealedListUnderTheComposedGrants(t *testing.T) {
"novox": {{Module: buscompose.GrantsModule, State: []buscompose.Bucket{{Module: buscompose.GrantsModule, Name: "user-list"}}}},
"ace": {{Module: "plex", Reads: []string{buscompose.ComposedRead}},
{Module: "mesh-delivery-view", Reads: []string{buscompose.ComposedRead}, ReadsObjects: []string{"mesh-knowledge_sections"}},
{Module: "mesh-knowledge", Objects: []buscompose.ObjectBucket{{Module: "mesh-knowledge", Name: "sections", MaxMiB: 1, Restricted: true}}}},
{Module: "mesh-knowledge", Objects: []buscompose.ObjectBucket{{Module: "mesh-knowledge", Name: "sections", MaxMiB: 1, Restricted: true}}},
// Issue 1966: a module whose state is asked in `requires`, bound as the bus module said it created it.
{Module: "mod-c", Bindings: []membership.BusBinding{{Local: "kept", Storage: catalogue.StorageKeyValue,
Template: catalogue.TemplateState, Bucket: "mod-c_kept"}}}},
},
}
// The bus module and the router as the part composes them from their own manifests (testdata: the catalogue's
@@ -50,6 +54,10 @@ func theBusNodeFollowsTheSealedListUnderTheComposedGrants(t *testing.T) {
}
records.Assigned["novox"] = append(records.Assigned["novox"], fromManifests.Assigned["novox"]...)
records.BusState = fromManifests.BusState
if records.BusState == nil {
records.BusState = map[string][]catalogue.BusStateEntry{}
}
records.BusState["mod-c"] = []catalogue.BusStateEntry{{Name: "kept", Storage: catalogue.StorageKeyValue, Template: catalogue.TemplateState}}
// The node-engine's state as the part's records carry it: every node's compositions (ADR 0593 §6).
records.Engine = &buscompose.Declared{Module: buscompose.EngineModule, State: buscompose.EngineComposedBuckets(records.Nodes)}
users, err := buscompose.Users(records)
@@ -388,6 +396,34 @@ func theBusNodeFollowsTheSealedListUnderTheComposedGrants(t *testing.T) {
if _, err := plex.GetLastMsg(sections.Stream(), "$O."+sections.Bucket()+".M.x", nats.DirectGet()); err == nil {
t.Error("a module the grant does not name read the bucket")
}
// Issue 1966 (ADR 0595 §1, design 25 § 12.7, the binding's usable half): the bus module's keeper creates a key-value
// entry asked in `requires` under its own account, and the module's own account, granted it from its binding, binds the
// bucket by its bus name and writes and reads a key; another module reads nothing of it.
keeperJS, err := jetstream.New(dial("novox.nats.keeper"))
if err != nil {
t.Fatal(err)
}
if _, err := keeperJS.CreateStream(ctx, jetstream.StreamConfig{Name: "KV_mod-c_kept", Subjects: []string{"$KV.mod-c_kept.>"},
MaxMsgsPerSubject: 1, AllowDirect: true, AllowRollup: true, DenyDelete: true, Discard: jetstream.DiscardNew}); err != nil {
t.Fatalf("the keeper creates mod-c's bound bucket: %v", err)
}
modC, err := jetstream.New(dial("ace.mod-c"))
if err != nil {
t.Fatal(err)
}
bound, err := modC.KeyValue(ctx, "mod-c_kept")
if err != nil {
t.Fatalf("mod-c's own account binds its bound bucket: %v", err)
}
if _, err := bound.Put(ctx, "k", []byte("v")); err != nil {
t.Fatalf("mod-c's own account writes its bound bucket: %v", err)
}
if e, err := bound.Get(ctx, "k"); err != nil || string(e.Value()) != "v" {
t.Errorf("mod-c reads back %v, %v", e, err)
}
if _, err := plex.GetLastMsg("KV_mod-c_kept", "$KV.mod-c_kept.k", nats.DirectGet()); err == nil {
t.Error("another module read mod-c's bound bucket")
}
}
// manifestIn is a module's manifest as testdata keeps it.