From 3eb07835f7b977eb26fe76cac00587f9dd98edee Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 05:59:50 +0100 Subject: [PATCH] GR6: a state's -objects store is granted to the state's owner alone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Builds: design 47 G §58, §91 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- .../internal/buscompose/busstate.go | 5 +++ .../internal/buscompose/state_objects_test.go | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 modules/mesh-grants/internal/buscompose/state_objects_test.go diff --git a/modules/mesh-grants/internal/buscompose/busstate.go b/modules/mesh-grants/internal/buscompose/busstate.go index f0716943..7b868fea 100644 --- a/modules/mesh-grants/internal/buscompose/busstate.go +++ b/modules/mesh-grants/internal/buscompose/busstate.go @@ -249,6 +249,11 @@ func boundOf(module string, bindings []membership.BusBinding) boundGrants { case catalogue.StorageKeyValue: if b.Bucket == base { g.States = append(g.States, b.Local) + // Its object store beside it, `-objects`, is its owner's alone (design 47 G §58, §91): the one bucket the + // binding names for this module's own state. + if b.Objects != "" && b.Objects == module+"_"+catalogue.StateObjectsName(b.Local) { + g.Objects = append(g.Objects, b.Objects) + } } case catalogue.StorageObjectStore: if b.ObjectStore == base { diff --git a/modules/mesh-grants/internal/buscompose/state_objects_test.go b/modules/mesh-grants/internal/buscompose/state_objects_test.go new file mode 100644 index 00000000..8a3975a4 --- /dev/null +++ b/modules/mesh-grants/internal/buscompose/state_objects_test.go @@ -0,0 +1,38 @@ +package buscompose + +import ( + "testing" + + "git.novox.be/novox/mesh-sdk/go/catalogue" + "git.novox.be/novox/mesh-sdk/go/membership" +) + +// Design 47 G §58, §59, §91, §124: a state's `-objects` store is its owner's alone, and a sent store is read, never +// written, by the modules it is for. +func TestStateObjectsAndSentStores(t *testing.T) { + owner, err := PermissionsFor(Principal{Kind: KindModule, Node: "one", Module: "shop", Bound: []membership.BusBinding{ + {Local: "orders", Storage: catalogue.StorageKeyValue, Template: catalogue.TemplateState, Bucket: "shop_orders", Objects: "shop_orders-objects"}}}) + if err != nil { + t.Fatal(err) + } + other, err := PermissionsFor(Principal{Kind: KindModule, Node: "one", Module: "bank"}) + if err != nil { + t.Fatal(err) + } + for _, subject := range []string{"$O.shop_orders-objects.C.x", "$O.shop_orders-objects.M.x", "$JS.API.DIRECT.GET.OBJ_shop_orders-objects.x"} { + if !MayPublish(owner, subject) { + t.Errorf("the owner may not publish %s", subject) + } + if MayPublish(other, subject) { + t.Errorf("another module may publish %s", subject) + } + } + reader, err := PermissionsFor(Principal{Kind: KindModule, Node: "one", Module: "bank", ReadsObjects: []string{"shop_sent-objects"}}) + if err != nil { + t.Fatal(err) + } + if !MayPublish(reader, "$JS.API.DIRECT.GET.OBJ_shop_sent-objects.x") || MayPublish(reader, "$O.shop_sent-objects.C.x") || + MayPublish(reader, "$JS.API.DIRECT.GET.OBJ_other_sent-objects.x") { + t.Error("a reader of a sent store reads that one and writes none") + } +}