diff --git a/internal/broker/composed_test.go b/internal/broker/composed_test.go index 1c9bac2d..56776e4f 100644 --- a/internal/broker/composed_test.go +++ b/internal/broker/composed_test.go @@ -1,10 +1,12 @@ package broker import ( + "slices" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/delivery" + "github.com/nats-io/nats.go/jetstream" ) // 1945/AC1: a node's composition bucket keeps five revisions of a key and is named for its node; the bucket of digests @@ -147,3 +149,55 @@ func TestTheOperatorWritesTheHandPath(t *testing.T) { t.Errorf("the controller may not say bucket-deleted") } } + +// 1945 (the review's A1): the bus's user list today's controller composes, which is the one the bus runs after release +// 6's switch, already grants every subject the new controller, every new node-engine and the assigner use: the +// controller writes each node's compositions and the digests; a node-engine reads its own compositions and wants, asks +// its own backup holder and hears its word, and nothing of another node's; the assigner reads the digests, announced +// readable. +func TestTheUserListGrantsWhatReleaseSixUses(t *testing.T) { + controller, err := PermissionsFor(Principal{Kind: KindController, PasswordHash: "x", FilesFor: []string{"ace", "g14"}}) + if err != nil { + t.Fatal(err) + } + for _, s := range []string{"$KV.composed-ace.>", "$KV.composed-g14.>", "$KV.mesh-controller_composed.>", + "mesh.seat.mesh-controller.event.bucket-deleted"} { + has(t, controller.Publish, s) + } + node, err := PermissionsFor(Principal{Kind: KindNode, Node: "ace", PasswordHash: "x"}) + if err != nil { + t.Fatal(err) + } + for _, s := range []string{ + "$JS.API.STREAM.INFO.KV_composed-ace", "$JS.API.DIRECT.GET.KV_composed-ace.>", "$JS.API.CONSUMER.CREATE.KV_composed-ace.>", + "$JS.API.DIRECT.GET.KV_mesh-delivery-assigner_wants.$KV.mesh-delivery-assigner_wants.want.ace.>", + "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-assigner_wants.*.$KV.mesh-delivery-assigner_wants.want.ace.>", + "mesh.seat.node-backup.tool.now.ace", "mesh.seat.node-backup.tool.backed-up.ace", + } { + has(t, node.Publish, s) + } + has(t, node.Subscribe, "mesh.mod.restic.event.backed-up") + for _, p := range node.Publish { + if strings.Contains(p, "composed-g14") || strings.Contains(p, "mesh-controller_composed") || strings.Contains(p, "want.g14") || + strings.HasPrefix(p, "$KV.composed-") || strings.HasSuffix(p, "node-backup.tool.now.*") { + t.Errorf("a node-engine of ace may publish %s", p) + } + } + if !slices.Contains(controllerReadableStates(), ControllerComposed) || + !slices.Contains(ControllerBuckets(), delivery.ControllerComposedBucket) || !IsControllerBucket(delivery.ControllerComposedBucket) { + t.Fatalf("the digests are not announced readable among the controller's buckets") + } + if !ControllerAnnouncement("novox").Readable(ControllerComposed) { + t.Fatalf("the controller does not announce %s readable", ControllerComposed) + } + assigner, err := PermissionsFor(Principal{Kind: KindModule, Node: "novox", Module: "mesh-delivery-assigner", PasswordHash: "x", + Reads: []string{"mesh-controller.composed"}}) + if err != nil { + t.Fatal(err) + } + has(t, assigner.Publish, "$JS.API.STREAM.INFO.KV_mesh-controller_composed") + has(t, assigner.Publish, "$JS.API.CONSUMER.CREATE.KV_mesh-controller_composed.>") + if c := ComposedDigestsConfig(); c.Bucket != "mesh-controller_composed" || c.History != 5 || c.Storage != jetstream.FileStorage { + t.Errorf("the digests' bucket is %+v", c) + } +} diff --git a/internal/broker/nats_test.go b/internal/broker/nats_test.go index 523a1eaa..bc2695c2 100644 --- a/internal/broker/nats_test.go +++ b/internal/broker/nats_test.go @@ -237,7 +237,9 @@ func TestAnAdministratorMayAskAnyTool(t *testing.T) { } // **Nothing but tools.** A person who could publish an event would be able to claim a module -// said something; one who could publish control traffic would be a second controller. +// said something; one who could publish control traffic would be a second controller. The one exception is the +// operator's (an administrator's) release 6 hand path, a want and a composition written by hand (novox/hq ADR 0539 +// clauses 2 and 10). func TestAPersonReachesNothingButTools(t *testing.T) { perms, _ := PermissionsFor(Principal{Kind: KindPerson, Module: "jo", Invokes: []string{"*"}, PasswordHash: "x"}) diff --git a/internal/broker/writers.go b/internal/broker/writers.go index 22d6f2b5..31437f66 100644 --- a/internal/broker/writers.go +++ b/internal/broker/writers.go @@ -232,7 +232,8 @@ var WritersTable = []WriterRow{ Others: "read through `reads` as node-engine.have", Subjects: kvOf(delivery.HaveBucket), Writes: ownNodeKeys}, // The contents of a declaration's files over 64 KiB (novox/hq ADR 0486 ยง2): the controller alone writes the bucket, each // object under the digest of its content; a node-engine reads one by its name, with no list and no write. - {State: "a declaration's large files", Writer: "controller, each object under the sha256 digest of its content", + {State: "a declaration's large files", Writer: "controller, each object under the sha256 digest of its content; the " + + "operator's account on release 6's hand path alone (ADR 0539 clause 10)", KeptIn: "the object bucket " + DeclarationFilesBucket + "- of each node, an object removed a day after the " + "node's latest declaration no longer names it", Others: "a node-engine reads an object of its own bucket by its name and may neither list the bucket nor write it",