From 5c73853653e06c78fbdd00f60d1dbb5719799cb3 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 16:54:28 +0100 Subject: [PATCH] =?UTF-8?q?mesh-grants:=20no=20go/delivery,=20health=20or?= =?UTF-8?q?=20provisions=20import=20left:=20the=20user=20list=20and=20gran?= =?UTF-8?q?ts=20are=20its=20own=20grantsstate,=20the=20other=20parts'=20st?= =?UTF-8?q?ates=20are=20named=20in=20its=20buscompose=20names.go=20(checke?= =?UTF-8?q?d=20against=20its=20manifest),=20their=20values=20are=20the=20s?= =?UTF-8?q?eats'=20types=20(ADR=200634=20=C2=A71,=20=C2=A74;=20issue=20196?= =?UTF-8?q?6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../mesh-grants/composed_integration_test.go | 22 +- .../cmd/mesh-grants/integration_test.go | 29 +- modules/mesh-grants/cmd/mesh-grants/sealed.go | 8 +- modules/mesh-grants/go.mod | 15 + modules/mesh-grants/grants_test.go | 264 +++++++++--------- .../internal/buscompose/buildbacklog.go | 16 +- .../internal/buscompose/buscompose_test.go | 64 +++-- .../internal/buscompose/composed.go | 18 +- .../mesh-grants/internal/buscompose/engine.go | 15 +- .../internal/buscompose/health_grants.go | 7 +- .../mesh-grants/internal/buscompose/names.go | 75 +++++ .../internal/buscompose/names_test.go | 50 ++++ .../mesh-grants/internal/buscompose/nats.go | 19 +- .../internal/buscompose/provisioner.go | 8 +- .../internal/buscompose/seattraffic.go | 6 +- .../mesh-grants/internal/buscompose/state.go | 21 +- .../internal/buscompose/userlist_switch.go | 20 +- .../mesh-grants/internal/buscompose/users.go | 13 +- .../internal/buscompose/writers.go | 18 +- .../mesh-grants/internal/grants/firstviews.go | 27 +- modules/mesh-grants/internal/grants/follow.go | 4 +- modules/mesh-grants/internal/grants/grants.go | 33 ++- modules/mesh-grants/internal/grants/inputs.go | 97 ++++--- modules/mesh-grants/internal/grants/part.go | 11 +- .../mesh-grants/internal/grants/records.go | 12 +- .../mesh-grants/internal/grants/rootfree.go | 8 +- modules/mesh-grants/internal/grants/sealed.go | 36 +-- modules/mesh-grants/internal/grants/tools.go | 20 +- .../mesh-grants/internal/grants/userlist.go | 4 +- 29 files changed, 557 insertions(+), 383 deletions(-) create mode 100644 modules/mesh-grants/internal/buscompose/names.go create mode 100644 modules/mesh-grants/internal/buscompose/names_test.go diff --git a/modules/mesh-grants/cmd/mesh-grants/composed_integration_test.go b/modules/mesh-grants/cmd/mesh-grants/composed_integration_test.go index 8e78640e..69cffdca 100644 --- a/modules/mesh-grants/cmd/mesh-grants/composed_integration_test.go +++ b/modules/mesh-grants/cmd/mesh-grants/composed_integration_test.go @@ -13,8 +13,8 @@ import ( "testing" "time" + "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/delivery" "git.novox.be/novox/mesh-sdk/go/process" "git.novox.be/novox/mesh-sdk/go/seal" "github.com/nats-io/nats-server/v2/server" @@ -34,8 +34,8 @@ func theBusNodeFollowsTheSealedListUnderTheComposedGrants(t *testing.T) { Nodes: []string{"novox", "ace"}, Assigned: map[string][]buscompose.Declared{ "novox": {{Module: buscompose.GrantsModule, State: []buscompose.Bucket{{Module: buscompose.GrantsModule, Name: "user-list"}}}}, - "ace": {{Module: "plex", Reads: []string{delivery.ComposedRead}}, - {Module: "mesh-delivery-view", Reads: []string{delivery.ComposedRead}, ReadsObjects: []string{"mesh-knowledge_sections"}}, + "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}}}}, }, } @@ -117,19 +117,19 @@ func theBusNodeFollowsTheSealedListUnderTheComposedGrants(t *testing.T) { t.Fatal(err) } list := []byte(accounts) - digest := delivery.UserListDigest(list) - object, err := delivery.SealUserList(list, node.PublicKey) + digest := grantsstate.UserListDigest(list) + object, err := grantsstate.SealUserList(list, node.PublicKey) if err != nil { t.Fatal(err) } - name := delivery.UserListObjectName(digest) + name := grantsstate.UserListObjectName(digest) for range 2 { // put twice: a second put of a name purges its old chunks if _, err := objects.PutBytes(ctx, name, object); err != nil { t.Fatalf("the grants part put its object: %v", err) } } - value, err := delivery.EncodeGrantsUserList(delivery.GrantsUserList{Follow: delivery.FollowGrants, Digest: digest, - Object: name, SealedTo: delivery.SealingKeyID(node.PublicKey), Node: "novox", Revision: 1, Users: len(users)}) + value, err := grantsstate.EncodeGrantsUserList(grantsstate.GrantsUserList{Follow: grantsstate.FollowGrants, Digest: digest, + Object: name, SealedTo: grantsstate.SealingKeyID(node.PublicKey), Node: "novox", Revision: 1, Users: len(users)}) if err != nil { t.Fatal(err) } @@ -155,13 +155,13 @@ func theBusNodeFollowsTheSealedListUnderTheComposedGrants(t *testing.T) { t.Fatalf("the bus node watches the key: %v", err) } defer w.Stop() - var got delivery.GrantsUserList + var got grantsstate.GrantsUserList select { case e := <-w.Updates(): if e == nil { t.Fatal("the watch found no value") } - if got, err = delivery.DecodeGrantsUserList(e.Value()); err != nil { + if got, err = grantsstate.DecodeGrantsUserList(e.Value()); err != nil { t.Fatal(err) } case <-ctx.Done(): @@ -200,7 +200,7 @@ func theBusNodeFollowsTheSealedListUnderTheComposedGrants(t *testing.T) { if err != nil { t.Fatalf("the bus node reads the object by its name: %v", err) } - opened, err := delivery.OpenUserList(body, got.Digest, func(b seal.Box) ([]byte, error) { + opened, err := grantsstate.OpenUserList(body, got.Digest, func(b seal.Box) ([]byte, error) { s, err := seal.Open(b, node.PrivateKey) return []byte(s), err }) diff --git a/modules/mesh-grants/cmd/mesh-grants/integration_test.go b/modules/mesh-grants/cmd/mesh-grants/integration_test.go index 72ae65bf..79ef9c83 100644 --- a/modules/mesh-grants/cmd/mesh-grants/integration_test.go +++ b/modules/mesh-grants/cmd/mesh-grants/integration_test.go @@ -19,7 +19,14 @@ import ( "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate" + "git.novox.be/novox/mesh-sdk/go/keys" "git.novox.be/novox/mesh-sdk/go/membership" "git.novox.be/novox/mesh-sdk/go/process" "git.novox.be/novox/mesh-sdk/go/seal" @@ -91,11 +98,11 @@ func TestTheGrantsPartWhole(t *testing.T) { } } manifest := func(module, raw string) { - digest := delivery.ManifestDigest([]byte(raw)) - put(grants.ReadManifests, digest, delivery.Manifest{V: delivery.Version, Module: module, JSON: raw}) - put(grants.ReadModules, module, delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32), Manifest: digest, + digest := meshcatalogclient.ManifestDigest([]byte(raw)) + put(grants.ReadManifests, digest, meshcatalog.ManifestsByDigestValue{V: keys.Version, Module: module, Manifest: raw}) + put(grants.ReadModules, module, meshcatalog.ModulesByModuleValue{V: keys.Version, Fp2: "src2:" + strings.Repeat("ab", 32), Manifest: digest, Repository: "novox/x", Commit: "c"}) - verdict, _ := delivery.EncodeManifestVerdict(delivery.ManifestVerdict{Module: module, State: delivery.VerdictAccepted, Judge: "j", At: time.Now()}) + verdict, _ := meshcatalogclient.EncodeVerdict(meshcatalog.VerdictsByDigestValue{Module: module, State: meshcatalogclient.VerdictAccepted, Judge: "j", At: new(time.Now())}) put(grants.ReadVerdicts, digest, json.RawMessage(verdict)) } manifest("nats", `{"module":"nats","version":"1","claims":[{"name":"mesh-broker"}],"own-secrets":{"broker":"/var/lib/nats/broker"},`+ @@ -107,16 +114,16 @@ func TestTheGrantsPartWhole(t *testing.T) { t.Fatal(err) } manifest(grants.Module, string(own)) - pl, _ := delivery.EncodePlacement(delivery.Placement{Node: "a", Modules: []delivery.PlacedModule{{Module: "nats"}, {Module: "mod-c"}, + pl, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "a", Modules: []meshmoduleassigner.PlacedModule{{Module: "nats"}, {Module: "mod-c"}, {Module: grants.Module}}}) if _, err := kvs[grants.ReadPlacement].Put(ctx, "placement.a", pl); err != nil { t.Fatal(err) } busKey, _ := seal.GenerateKeyPair() put(grants.ReadHave, "a.node", map[string]any{"v": 1}) - put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, - UserList: &delivery.UserListWritten{Source: delivery.GrantsUserListSource, Revision: 1, Digest: "sha256:aa", Users: 1, - Key: busKey.PublicKey, Follows: delivery.FollowGrants}}) + put(grants.ReadHave, "a.nats", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, + UserList: &nodeengine.UserListWritten{Source: grantsstate.GrantsUserListSource, Revision: 1, Digest: "sha256:aa", Users: 1, + Key: busKey.PublicKey, Follows: grantsstate.FollowGrants}}) // The bus module's read tools, as nats-tools answers them: the streams with their consumers as the bus holds them now. answer := func(m *nats.Msg, result any) { @@ -265,8 +272,8 @@ func TestTheGrantsPartWhole(t *testing.T) { if err != nil { return false } - g, err := delivery.DecodeGrantsUserList(e.Value()) - return err == nil && g.Follow == delivery.FollowGrants + g, err := grantsstate.DecodeGrantsUserList(e.Value()) + return err == nil && g.Follow == grantsstate.FollowGrants }) if n := said.Load(); n != 0 { t.Errorf("%d memberships were said on their subject", n) diff --git a/modules/mesh-grants/cmd/mesh-grants/sealed.go b/modules/mesh-grants/cmd/mesh-grants/sealed.go index 1c8156e6..ab0cf73b 100644 --- a/modules/mesh-grants/cmd/mesh-grants/sealed.go +++ b/modules/mesh-grants/cmd/mesh-grants/sealed.go @@ -7,7 +7,7 @@ import ( "github.com/nats-io/nats.go/jetstream" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate" ) // sealedObjects is the part's object bucket of user lists sealed to the bus node (novox/hq ADR 0547 clause 7), created on @@ -18,14 +18,14 @@ type sealedObjects struct { } func (o sealedObjects) store(ctx context.Context) (jetstream.ObjectStore, error) { - s, err := o.js.ObjectStore(ctx, delivery.GrantsUserListObjects) + s, err := o.js.ObjectStore(ctx, grantsstate.GrantsUserListObjects) if errors.Is(err, jetstream.ErrBucketNotFound) { - s, err = o.js.CreateObjectStore(ctx, jetstream.ObjectStoreConfig{Bucket: delivery.GrantsUserListObjects, + s, err = o.js.CreateObjectStore(ctx, jetstream.ObjectStoreConfig{Bucket: grantsstate.GrantsUserListObjects, Description: "the bus's user list composed by the grants part, each sealed to the bus node's key under the hex of its digest (novox/hq ADR 0547 clause 7): written by mesh-grants alone, read by name by the node-engine of the node holding mesh-broker", Storage: jetstream.FileStorage, MaxBytes: 64 << 20}) } if err != nil { - return nil, fmt.Errorf("the object bucket %s: %w", delivery.GrantsUserListObjects, err) + return nil, fmt.Errorf("the object bucket %s: %w", grantsstate.GrantsUserListObjects, err) } return s, nil } diff --git a/modules/mesh-grants/go.mod b/modules/mesh-grants/go.mod index 26c51537..b1e91f2a 100644 --- a/modules/mesh-grants/go.mod +++ b/modules/mesh-grants/go.mod @@ -3,6 +3,21 @@ module mesh-grants go 1.26.0 require ( + git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhealers v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshselfcheck v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/go/seats/meshbroker v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000 diff --git a/modules/mesh-grants/grants_test.go b/modules/mesh-grants/grants_test.go index 2bace7a8..c9e45eed 100644 --- a/modules/mesh-grants/grants_test.go +++ b/modules/mesh-grants/grants_test.go @@ -22,10 +22,22 @@ import ( "github.com/nats-io/nats.go/jetstream" + "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient" + "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshnodeenroller" + "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault" occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" + "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" + "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/delivery" + "git.novox.be/novox/mesh-sdk/go/keys" "git.novox.be/novox/mesh-sdk/go/membership" "git.novox.be/novox/mesh-sdk/go/seal" "git.novox.be/novox/mesh-sdk/go/views" @@ -160,8 +172,8 @@ func (b *fakeBus) put(state, key string, v any) { b.states[state][key] = raw // A modules value that carries no verdict is accepted by the indexer's verdict on its digest, as the indexer writes it; // a row that wants the window before the judge deletes that key. - if m, ok := v.(delivery.Module); ok && state == grants.ReadModules && m.Verdict == "" && m.Manifest != "" { - verdict, _ := delivery.EncodeManifestVerdict(delivery.ManifestVerdict{Module: key, State: delivery.VerdictAccepted, Judge: "j", At: time.Now()}) + if m, ok := v.(meshcatalog.ModulesByModuleValue); ok && state == grants.ReadModules && m.Verdict == "" && m.Manifest != "" { + verdict, _ := meshcatalogclient.EncodeVerdict(meshcatalog.VerdictsByDigestValue{Module: key, State: meshcatalogclient.VerdictAccepted, Judge: "j", At: new(time.Now())}) b.put(grants.ReadVerdicts, m.Manifest, json.RawMessage(verdict)) } } @@ -315,23 +327,23 @@ func (b *fakeBus) count(key string) int { // manifestOf is a fixture manifest of one module, in the indexer's two buckets. func (b *fakeBus) manifestOf(module, raw string) { - digest := delivery.ManifestDigest([]byte(raw)) - b.put(grants.ReadManifests, digest, delivery.Manifest{V: delivery.Version, Module: module, JSON: raw}) - b.put(grants.ReadModules, module, delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32), Manifest: digest, Repository: "novox/x", Commit: "c"}) + digest := meshcatalogclient.ManifestDigest([]byte(raw)) + b.put(grants.ReadManifests, digest, meshcatalog.ManifestsByDigestValue{V: keys.Version, Module: module, Manifest: raw}) + b.put(grants.ReadModules, module, meshcatalog.ModulesByModuleValue{V: keys.Version, Fp2: "src2:" + strings.Repeat("ab", 32), Manifest: digest, Repository: "novox/x", Commit: "c"}) } // refuse marks the indexer's refusal of module's manifest in its value (novox/hq ADR 0578 §1). func (b *fakeBus) refuse(module, raw, problem string) { - b.put(grants.ReadModules, module, delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32), - Manifest: delivery.ManifestDigest([]byte(raw)), Repository: "novox/x", Commit: "c", Verdict: delivery.VerdictRefused, + b.put(grants.ReadModules, module, meshcatalog.ModulesByModuleValue{V: keys.Version, Fp2: "src2:" + strings.Repeat("ab", 32), + Manifest: meshcatalogclient.ManifestDigest([]byte(raw)), Repository: "novox/x", Commit: "c", Verdict: meshcatalogclient.VerdictRefused, Problems: []string{problem}}) } // assign places module on node in the seat's placement, the one place the part reads it (ADR 0549 §5; issue 774). func (b *fakeBus) assign(node, module string) { - var pl delivery.Placement + var pl meshmoduleassigner.Placement if raw, ok := b.states[grants.ReadPlacement]["placement."+node]; ok { - pl, _ = delivery.DecodePlacement(raw) + pl, _ = meshmoduleassignerclient.DecodePlacement(raw) } pl.Node = node for _, m := range pl.Modules { @@ -339,8 +351,8 @@ func (b *fakeBus) assign(node, module string) { return } } - pl.Modules = append(pl.Modules, delivery.PlacedModule{Module: module}) - raw, err := delivery.EncodePlacement(pl) + pl.Modules = append(pl.Modules, meshmoduleassigner.PlacedModule{Module: module}) + raw, err := meshmoduleassignerclient.EncodePlacement(pl) if err != nil { panic(err) } @@ -352,12 +364,12 @@ func (b *fakeBus) assign(node, module string) { // unassign takes module off node in the seat's placement. func (b *fakeBus) unassign(node, module string) { - pl, err := delivery.DecodePlacement(b.states[grants.ReadPlacement]["placement."+node]) + pl, err := meshmoduleassignerclient.DecodePlacement(b.states[grants.ReadPlacement]["placement."+node]) if err != nil { return } - pl.Modules = slices.DeleteFunc(pl.Modules, func(m delivery.PlacedModule) bool { return m.Module == module }) - raw, _ := delivery.EncodePlacement(pl) + pl.Modules = slices.DeleteFunc(pl.Modules, func(m meshmoduleassigner.PlacedModule) bool { return m.Module == module }) + raw, _ := meshmoduleassignerclient.EncodePlacement(pl) b.states[grants.ReadPlacement]["placement."+node] = raw } @@ -404,8 +416,8 @@ func fixture() *fakeBus { b.assign("b", "mod-y") b.put(grants.ReadHave, "a.node", map[string]any{"v": 1}) b.put(grants.ReadHave, "b.node", map[string]any{"v": 1}) - b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, - UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Wanted: "sha256:aa", Digest: "sha256:aa", Users: 5}}) + b.put(grants.ReadHave, "a.nats", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, + UserList: &nodeengine.UserListWritten{Source: "a.nats.src2", Revision: 7, Wanted: "sha256:aa", Digest: "sha256:aa", Users: 5}}) return b } @@ -519,7 +531,7 @@ func TestGrants(t *testing.T) { } // The one object bucket: the part's own, of the user list sealed to the bus node, in sealed.go (ADR 0547 clause 7). if path == sealedOnly { - if strings.Count(string(src), "CreateObjectStore(") != 1 || !strings.Contains(string(src), "delivery.GrantsUserListObjects") { + if strings.Count(string(src), "CreateObjectStore(") != 1 || !strings.Contains(string(src), "grantsstate.GrantsUserListObjects") { t.Errorf("sealed.go creates other than the part's one object bucket") } src = []byte(strings.ReplaceAll(string(src), "CreateObjectStore", "")) @@ -557,12 +569,12 @@ func TestGrants(t *testing.T) { []string{"a.mod-x", "a.nats", "node.a", "node.b"}, "manifest of mod-y"}, {"a want's built manifest is composed, not the trunk's; the list's records carry both (issue 774)", func(b *fakeBus) { built := strings.Replace(xManifest, `{"name":"kept","history":3}`, `{"name":"newer"}`, 1) - digest := delivery.ManifestDigest([]byte(built)) - b.put(grants.ReadManifests, digest, delivery.Manifest{V: delivery.Version, Module: "mod-x", JSON: built}) + digest := meshcatalogclient.ManifestDigest([]byte(built)) + b.put(grants.ReadManifests, digest, meshcatalog.ManifestsByDigestValue{V: keys.Version, Module: "mod-x", Manifest: built}) fp := "src2:" + strings.Repeat("cd", 32) - b.put(grants.ReadWants, "want.a.mod-x", delivery.Want{V: delivery.Version, FP: fp, Composed: "sha256:" + strings.Repeat("ef", 32)}) - key, _ := delivery.BuilderBuildKey("mod-x", fp) - b.put(grants.ReadBuilds, key, map[string]any{"v": delivery.Version, "state": "done", "manifest": digest}) + b.put(grants.ReadWants, "want.a.mod-x", meshwantsclient.Want{V: keys.Version, Fp: fp, Composed: "sha256:" + strings.Repeat("ef", 32)}) + key, _ := builds.BuilderBuildKey("mod-x", fp) + b.put(grants.ReadBuilds, key, map[string]any{"v": keys.Version, "state": "done", "manifest": digest}) }, []string{"a.mod-x", "a.nats", "b.mod-y", "node.a", "node.b"}, "people"}, {"placement refused is a gap; the machines come from the haves", func(b *fakeBus) { b.refused[grants.ReadPlacement] = true }, []string{"node.a", "node.b"}, grants.ReadPlacement}, @@ -643,7 +655,7 @@ func TestGrants(t *testing.T) { } var readers []string for _, u := range shadowOf(t, cands) { - if slices.Contains(u.Publish, "$JS.API.DIRECT.GET.KV_"+delivery.PlacementCandidatesBucket+".>") { + if slices.Contains(u.Publish, "$JS.API.DIRECT.GET.KV_"+buscompose.PlacementCandidatesBucket+".>") { readers = append(readers, u.User) } } @@ -708,12 +720,12 @@ func TestGrants(t *testing.T) { // ADR 0601 clause 3: the owner's trunk manifest now waits for a field; its running build's manifest still declares the // bucket, so web keeps its object grant. fp := "src2:" + strings.Repeat("cd", 32) - b.put(grants.ReadModules, "mesh-knowledge", delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32), - Manifest: delivery.ManifestDigest([]byte(`{"module":"mesh-knowledge","version":"2"}`)), Repository: "novox/x", Commit: "c", - Verdict: delivery.VerdictRefused, Problems: []string{"it waits"}, Unknown: []string{"core-credentials"}}) - b.put(grants.ReadHave, "a.mesh-knowledge", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, FP: fp}) - heldKey, _ := delivery.BuilderBuildKey("mesh-knowledge", fp) - b.put(grants.ReadBuilds, heldKey, map[string]any{"v": delivery.Version, "state": "done", "manifest": delivery.ManifestDigest([]byte(knowledge))}) + b.put(grants.ReadModules, "mesh-knowledge", meshcatalog.ModulesByModuleValue{V: keys.Version, Fp2: "src2:" + strings.Repeat("ab", 32), + Manifest: meshcatalogclient.ManifestDigest([]byte(`{"module":"mesh-knowledge","version":"2"}`)), Repository: "novox/x", Commit: "c", + Verdict: meshcatalogclient.VerdictRefused, Problems: []string{"it waits"}, Unknown: []string{"core-credentials"}}) + b.put(grants.ReadHave, "a.mesh-knowledge", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, Fp: fp}) + heldKey, _ := builds.BuilderBuildKey("mesh-knowledge", fp) + b.put(grants.ReadBuilds, heldKey, map[string]any{"v": keys.Version, "state": "done", "manifest": meshcatalogclient.ManifestDigest([]byte(knowledge))}) if heldWeb := (func() grants.User { us := shadowOf(t, b) return us[slices.IndexFunc(us, func(u grants.User) bool { return u.User == "a.web" })] @@ -786,16 +798,16 @@ func TestGrants(t *testing.T) { b.assign("b", "telegram") if held { // The trunk's manifest waits for a field; the build that runs was judged on the manifest before. - b.put(grants.ReadModules, "messenger", delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32), - Manifest: delivery.ManifestDigest([]byte(`{"module":"messenger","version":"2"}`)), Repository: "novox/x", Commit: "c", - Verdict: delivery.VerdictRefused, Problems: []string{"it waits"}, Unknown: []string{"core-credentials"}}) + b.put(grants.ReadModules, "messenger", meshcatalog.ModulesByModuleValue{V: keys.Version, Fp2: "src2:" + strings.Repeat("ab", 32), + Manifest: meshcatalogclient.ManifestDigest([]byte(`{"module":"messenger","version":"2"}`)), Repository: "novox/x", Commit: "c", + Verdict: meshcatalogclient.VerdictRefused, Problems: []string{"it waits"}, Unknown: []string{"core-credentials"}}) if runs { fp := "src2:" + strings.Repeat("cd", 32) - digest := delivery.ManifestDigest([]byte(router)) - b.put(grants.ReadManifests, digest, delivery.Manifest{V: delivery.Version, Module: "messenger", JSON: router}) - b.put(grants.ReadHave, "a.messenger", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, FP: fp}) - key, _ := delivery.BuilderBuildKey("messenger", fp) - b.put(grants.ReadBuilds, key, map[string]any{"v": delivery.Version, "state": "done", "manifest": digest}) + digest := meshcatalogclient.ManifestDigest([]byte(router)) + b.put(grants.ReadManifests, digest, meshcatalog.ManifestsByDigestValue{V: keys.Version, Module: "messenger", Manifest: router}) + b.put(grants.ReadHave, "a.messenger", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, Fp: fp}) + key, _ := builds.BuilderBuildKey("messenger", fp) + b.put(grants.ReadBuilds, key, map[string]any{"v": keys.Version, "state": "done", "manifest": digest}) } } return b @@ -972,15 +984,15 @@ func TestGrants(t *testing.T) { const oldS = `{"module":"mod-s","version":"1","own-secrets":{"broker":"/var/lib/s/broker"},` + `"provides":[{"name":"smtp","scope":"mesh"}],"serves":{"smtp":{"port":587}}}` bt.manifestOf("mod-s", oldS) - oldDigest := delivery.ManifestDigest([]byte(oldS)) + oldDigest := meshcatalogclient.ManifestDigest([]byte(oldS)) bt.manifestOf("mod-s", `{"module":"mod-s","version":"1","own-secrets":{"broker":"/var/lib/s/broker"},`+ `"config":{"domain":{"type":"string","description":"The mail domain.","required":true}},`+ `"provides":[{"name":"smtp","scope":"mesh"}],"serves":{"smtp":{"port":587,"domain":"${config:domain}"}}}`) bt.assign("b", "mod-s") runsFP := "src2:" + strings.Repeat("cd", 32) - bt.put(grants.ReadWants, "want.b.mod-s", delivery.Want{V: delivery.Version, FP: runsFP, Composed: "sha256:" + strings.Repeat("ef", 32)}) - runsKey, _ := delivery.BuilderBuildKey("mod-s", runsFP) - bt.put(grants.ReadBuilds, runsKey, map[string]any{"v": delivery.Version, "state": "done", "manifest": oldDigest}) + bt.put(grants.ReadWants, "want.b.mod-s", meshwantsclient.Want{V: keys.Version, Fp: runsFP, Composed: "sha256:" + strings.Repeat("ef", 32)}) + runsKey, _ := builds.BuilderBuildKey("mod-s", runsFP) + bt.put(grants.ReadBuilds, runsKey, map[string]any{"v": keys.Version, "state": "done", "manifest": oldDigest}) for _, u := range shadowOf(t, bt) { if u.User == "node.a" && !slices.Contains(u.Publish, served) { t.Errorf("node a is not granted the domain the trunk's mod-s serves, while b runs one that serves none") @@ -1202,25 +1214,25 @@ func TestGrants(t *testing.T) { if recs, _ := grants.Records(in); holds(recs, "a") != 1 || holds(recs, "b") != 1 { t.Fatalf("before placement the seat is held on a %d and b %d times, want both", holds(recs, "a"), holds(recs, "b")) } - in.Placed = map[string]delivery.Placement{ - "a": {Node: "a", Modules: []delivery.PlacedModule{{Module: "store"}}, Holds: []delivery.Holding{{Claim: "store-seat", Scope: "mesh", Module: "store"}}}, - "b": {Node: "b", Modules: []delivery.PlacedModule{{Module: "store"}}}} + in.Placed = map[string]meshmoduleassigner.Placement{ + "a": {Node: "a", Modules: []meshmoduleassigner.PlacedModule{{Module: "store"}}, Holds: []meshmoduleassigner.Holding{{Claim: "store-seat", Scope: "mesh", Module: "store"}}}, + "b": {Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "store"}}}} if recs, _ := grants.Records(in); holds(recs, "a") != 1 || holds(recs, "b") != 0 { t.Errorf("placed, the seat is held on a %d and b %d times, want a alone", holds(recs, "a"), holds(recs, "b")) } b := fixture() - pl, _ := delivery.EncodePlacement(delivery.Placement{Node: "b", Modules: []delivery.PlacedModule{{Module: "mod-y"}, {Module: "mod-x"}}}) + pl, _ := meshmoduleassignerclient.EncodePlacement(meshmoduleassigner.Placement{Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "mod-y"}, {Module: "mod-x"}}}) b.states[grants.ReadPlacement] = map[string][]byte{"placement.b": pl} read := grants.ReadInputs(b, &grants.Manifests{}) if !slices.Contains(read.Assigned["b"], "mod-x") || read.Placed["b"].Node != "b" || slices.ContainsFunc(read.Gaps, func(g grants.Gap) bool { return g.Input == "seat holdings" }) { t.Errorf("read: assigned %v, placed %v, gaps %v", read.Assigned, read.Placed, read.Gaps) } // Research 072 row 37: a node key and a key per module, the legacy mirror left out, read the same. - perModule, _ := delivery.PlacementValues(delivery.Placement{Node: "b", Modules: []delivery.PlacedModule{{Module: "mod-y"}, {Module: "mod-x"}}}) + perModule, _ := meshmoduleassignerclient.PlacementValues(meshmoduleassigner.Placement{Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "mod-y"}, {Module: "mod-x"}}}) delete(perModule, "placement.b") nb := fixture() nb.states[grants.ReadPlacement] = perModule - if got := grants.ReadInputs(nb, &grants.Manifests{}); !slices.Equal(got.Placed["b"].Assigned(), []string{"mod-y", "mod-x"}) || + if got := grants.ReadInputs(nb, &grants.Manifests{}); !slices.Equal(meshmoduleassignerclient.Assigned(got.Placed["b"]), []string{"mod-y", "mod-x"}) || slices.ContainsFunc(got.Gaps, func(g grants.Gap) bool { return strings.HasPrefix(g.Input, grants.ReadPlacement) }) { t.Errorf("read of the keys per module: placed %v, gaps %v", got.Placed, got.Gaps) } @@ -1235,12 +1247,12 @@ func TestGrants(t *testing.T) { in.Manifests["other"] = catalogue.Manifest{Module: "other", Version: "1", Claims: []catalogue.Claim{{Name: "other-seat", Scope: catalogue.ScopeMesh}}, DefinesSeats: []catalogue.SeatDeclaration{{Name: "other-seat", Scope: catalogue.ScopeMesh, Serves: []catalogue.Verb{{Name: "v"}}}}} in.Assigned["b"] = append(in.Assigned["b"], "other") - in.Placed["b"] = delivery.Placement{Node: "b", Modules: []delivery.PlacedModule{{Module: "store"}, {Module: "other"}}} + in.Placed["b"] = meshmoduleassigner.Placement{Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "store"}, {Module: "other"}}} if recs, _ := grants.Records(in); holds(recs, "a") != 1 || holds(recs, "b") != 1 { t.Errorf("a seat no placement holds is held on b %d times, want once (and store-seat on a alone: a %d)", holds(recs, "b"), holds(recs, "a")) } - in.Placed["b"] = delivery.Placement{Node: "b", Modules: []delivery.PlacedModule{{Module: "store"}, {Module: "other"}}, - Holds: []delivery.Holding{{Claim: "other-seat", Scope: "mesh", Module: "someone-else"}}} + in.Placed["b"] = meshmoduleassigner.Placement{Node: "b", Modules: []meshmoduleassigner.PlacedModule{{Module: "store"}, {Module: "other"}}, + Holds: []meshmoduleassigner.Holding{{Claim: "other-seat", Scope: "mesh", Module: "someone-else"}}} if recs, _ := grants.Records(in); holds(recs, "b") != 0 { t.Errorf("a seat placement holds by another module is still held by other on b") } @@ -1256,7 +1268,7 @@ func TestGrants(t *testing.T) { t.Fatal(err) } } - var seed delivery.Placement + var seed meshmoduleassigner.Placement var live struct{ Users []grants.User } file("placement-novox-0c61bad.json", &seed) file("node-tools-novox-licence-subjects.json", &live) @@ -1279,7 +1291,7 @@ func TestGrants(t *testing.T) { // alone (ADR 0636 §1), does not bring the refusal back, and the runner is granted none of them. n.manifestOf(buscompose.EngineModule, `{"module":"mesh-host","version":"1","consumes":["restic.backed-up"],"state":[{"name":"health"},{"name":"findings"},{"name":"data"},{"name":"packages"}]}`) n.put(grants.ReadHave, "novox.node", map[string]any{"v": 1}) - pl, _ = delivery.EncodePlacement(seed) + pl, _ = meshmoduleassignerclient.EncodePlacement(seed) n.states[grants.ReadPlacement] = map[string][]byte{"placement.novox": pl} recs, _ := grants.Records(grants.ReadInputs(n, &grants.Manifests{})) var tools []buscompose.ProcessPrincipal @@ -1375,17 +1387,17 @@ func TestGrants(t *testing.T) { if err != nil { t.Fatal(err) } - withKey := func(w delivery.UserListWritten) { + withKey := func(w nodeengine.UserListWritten) { w.Key = busKey.PublicKey - b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, UserList: &w}) + b.put(grants.ReadHave, "a.nats", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, UserList: &w}) } - loadedBefore := delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Wanted: "sha256:aa", Digest: "sha256:aa", Users: 5} + loadedBefore := nodeengine.UserListWritten{Source: "a.nats.src2", Revision: 7, Wanted: "sha256:aa", Digest: "sha256:aa", Users: 5} withKey(loadedBefore) objs := &fakeObjects{} p.Objects = objs - open := func(g delivery.GrantsUserList) string { + open := func(g grantsstate.GrantsUserList) string { t.Helper() - list, err := delivery.OpenUserList(objs.objects[g.Object], g.Digest, func(bx seal.Box) ([]byte, error) { + list, err := grantsstate.OpenUserList(objs.objects[g.Object], g.Digest, func(bx seal.Box) ([]byte, error) { out, err := seal.Open(bx, busKey.PrivateKey) return []byte(out), err }) @@ -1406,7 +1418,7 @@ func TestGrants(t *testing.T) { // The bus node follows the part's list alone (issue 1126): a first list that would leave out every loaded user // while the vault holds no hash is held, nothing sealed, the key saying follow grants; a tool's part (no object // bucket) writes none. - if g, _, had := p.Sealed(); !had || g.Follow != delivery.FollowGrants || g.Digest != "" || + if g, _, had := p.Sealed(); !had || g.Follow != grantsstate.FollowGrants || g.Digest != "" || len(objs.objects) != 0 || first.Sealed == nil || !strings.Contains(first.Sealed.Why, "would be left out") { t.Errorf("the first sealed list = %+v, %d objects, %+v", g, len(objs.objects), first.Sealed) } @@ -1493,21 +1505,21 @@ func TestGrants(t *testing.T) { settled(lacks)() p.Compose() g, _, _ := p.Sealed() - if g.Follow != delivery.FollowGrants { + if g.Follow != grantsstate.FollowGrants { t.Errorf("the key says follow %s, want grants (1126/A1/AC2)", g.Follow) } if users := strings.Count(open(g), "{ user: "); users != g.Users || g.Users == 0 || len(g.LeftOut) != 0 { t.Errorf("the sealed list holds %d users, the key says %d, left out %v", users, g.Users, g.LeftOut) } - withKey(delivery.UserListWritten{Source: delivery.GrantsUserListSource, Revision: g.Revision, Wanted: g.Digest, Digest: g.Digest, Users: g.Users, Follows: delivery.FollowGrants}) + withKey(nodeengine.UserListWritten{Source: grantsstate.GrantsUserListSource, Revision: int64(g.Revision), Wanted: g.Digest, Digest: g.Digest, Users: int64(g.Users), Follows: grantsstate.FollowGrants}) // Steady (issue 1126, 2026-10-28 00:16: the shadow list moved every 3 s, 99270 to 99357, with nothing // differing): the bus node saying again that it loaded the same list, at a later time and the key's // later revision, moves neither the shadow list nor the sealed key. _, sealedRev, _ := p.Sealed() p.Compose() for i := 0; i < 3; i++ { - withKey(delivery.UserListWritten{Source: delivery.GrantsUserListSource, Revision: sealedRev + uint64(i+1), Wanted: g.Digest, - Digest: g.Digest, Users: g.Users, Follows: delivery.FollowGrants, At: time.Date(2026, 10, 28, 0, 16, i, 0, time.UTC)}) + withKey(nodeengine.UserListWritten{Source: grantsstate.GrantsUserListSource, Revision: int64(sealedRev) + int64(i+1), Wanted: g.Digest, + Digest: g.Digest, Users: int64(g.Users), Follows: grantsstate.FollowGrants, At: time.Date(2026, 10, 28, 0, 16, i, 0, time.UTC)}) if res := p.Compose(); res.Written { t.Errorf("pass %d: the shadow list is written again for the bus node saying the same list again (revision %d)", i, res.Revision) } @@ -1862,13 +1874,13 @@ func TestGrants(t *testing.T) { t.Fatalf("the records carry the node-engine as %+v", recs.Engine) } for _, node := range recs.Nodes { - bucket, _ := delivery.ComposedBucketFor(node) + bucket, _ := buscompose.ComposedBucketFor(node) if !slices.Contains(b.creates, "KV_"+bucket) { t.Errorf("the compositions of %s were not created: %v", node, b.creates) } engine := catalogue.Manifest{Module: buscompose.EngineModule} - if cfg := grants.Configs(engine, recs.Nodes)["KV_"+bucket]; cfg.MaxMsgsPerSubject != delivery.ComposedHistory { - t.Errorf("the compositions of %s keep %d revisions, want %d", node, cfg.MaxMsgsPerSubject, delivery.ComposedHistory) + if cfg := grants.Configs(engine, recs.Nodes)["KV_"+bucket]; cfg.MaxMsgsPerSubject != buscompose.ComposedHistory { + t.Errorf("the compositions of %s keep %d revisions, want %d", node, cfg.MaxMsgsPerSubject, buscompose.ComposedHistory) } } for node, mods := range recs.Assigned { @@ -1998,7 +2010,7 @@ func TestGrants(t *testing.T) { b.loaded = shadowOf(t, b) p := partOn(b) fp := "src2:" + strings.Repeat("ab", 32) - key := func(module string) string { k, _ := delivery.PartGrantKey(module, fp); return k } + key := func(module string) string { k, _ := grantsstate.PartGrantKey(module, fp); return k } hash := func(user string) { b.put(grants.ReadAccounts, user, map[string]any{"user": user, "hash": "$2a$10$x", "generation": 1}) } @@ -2009,7 +2021,7 @@ func TestGrants(t *testing.T) { if res.Grants == nil || !slices.Equal(held(), []string{key("mod-x"), key("nats")}) || len(res.Grants.Written) != 2 { t.Fatalf("grants of the hashed modules: held %v, %+v", held(), res.Grants) } - if g, err := delivery.DecodeGrant(b.own[grants.StateGrants][key("mod-x")]); err != nil || !slices.Equal(g.Accounts, []string{"a.mod-x"}) { + if g, err := grantsstate.DecodeGrant(b.own[grants.StateGrants][key("mod-x")]); err != nil || !slices.Equal(g.Accounts, []string{"a.mod-x"}) { t.Errorf("the grant of mod-x is %+v, %v", g, err) } said := func() int { @@ -2056,17 +2068,17 @@ func TestGrants(t *testing.T) { t.Fatal(err) } loads := func(source, digest string) { - b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, - UserList: &delivery.UserListWritten{Source: source, Revision: 8, Wanted: digest, Digest: digest, Users: 5, Key: busKey.PublicKey, - Follows: delivery.FollowGrants}}) + b.put(grants.ReadHave, "a.nats", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, + UserList: &nodeengine.UserListWritten{Source: source, Revision: 8, Wanted: digest, Digest: digest, Users: 5, Key: busKey.PublicKey, + Follows: grantsstate.FollowGrants}}) } loads("a.nats.src2", "sha256:aa") p.Objects = &fakeObjects{} moves := func(module, manifest, fp string) string { - digest := delivery.ManifestDigest([]byte(manifest)) - b.put(grants.ReadManifests, digest, delivery.Manifest{V: delivery.Version, Module: module, JSON: manifest}) - b.put(grants.ReadModules, module, delivery.Module{V: delivery.Version, FP2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"}) - k, _ := delivery.PartGrantKey(module, fp) + digest := meshcatalogclient.ManifestDigest([]byte(manifest)) + b.put(grants.ReadManifests, digest, meshcatalog.ManifestsByDigestValue{V: keys.Version, Module: module, Manifest: manifest}) + b.put(grants.ReadModules, module, meshcatalog.ModulesByModuleValue{V: keys.Version, Fp2: fp, Manifest: digest, Repository: "novox/x", Commit: "c"}) + k, _ := grantsstate.PartGrantKey(module, fp) return k } written := func(k string) bool { _, ok := b.own[grants.StateGrants][k]; return ok } @@ -2084,7 +2096,7 @@ func TestGrants(t *testing.T) { if sealed.Digest == "" { t.Fatal("no list sealed once every user has a hash") } - loads(delivery.GrantsUserListSource, sealed.Digest) + loads(grantsstate.GrantsUserListSource, sealed.Digest) b.refuseUsers = true // (2) mod-y's trunk adds a state, so its account adds permissions: it waits, judged from the part's own record of the // loaded list; mod-x at another fingerprint with the same permissions is written. @@ -2097,7 +2109,7 @@ func TestGrants(t *testing.T) { t.Fatalf("an added permission against the loaded list: %+v", res.Grants) } // A loaded list the part keeps no permissions of: every grant naming an account waits, said. - loads(delivery.GrantsUserListSource, "sha256:bb") + loads(grantsstate.GrantsUserListSource, "sha256:bb") keyX4 := moves("mod-x", xManifest, "src2:"+strings.Repeat("12", 32)) if res := p.Compose(); written(keyX4) || len(res.Grants.Waits) != 2 || !strings.Contains(res.Grants.Waits[0], "unknown") { t.Fatalf("an unknown loaded list let a grant through: %+v", res.Grants) @@ -2133,15 +2145,15 @@ func TestGrants(t *testing.T) { // The private network the mesh provides is placed with no fingerprint by construction: never a missing grant. b.assign("b", "mesh-wireguard") fp := "src2:d4afe7a1abab5ccdbef6d8629b2245006a2aec60290f430f7b4756e2930ed60c" - b.put(grants.ReadModules, "mod-x", delivery.Module{V: delivery.Version, FP2: fp, Manifest: delivery.ManifestDigest([]byte(xManifest)), + b.put(grants.ReadModules, "mod-x", meshcatalog.ModulesByModuleValue{V: keys.Version, Fp2: fp, Manifest: meshcatalogclient.ManifestDigest([]byte(xManifest)), Repository: "novox/Link2Pay", Commit: "5d20e8bfbc953c51e8ebe3c671fe883df9c36e15"}) - b.put(grants.ReadModules, "mod-y", delivery.Module{V: delivery.Version, None2: "its builder's version is not read yet", - Manifest: delivery.ManifestDigest([]byte(yManifest)), Repository: "novox/y", Commit: "c"}) + b.put(grants.ReadModules, "mod-y", meshcatalog.ModulesByModuleValue{V: keys.Version, None2: "its builder's version is not read yet", + Manifest: meshcatalogclient.ManifestDigest([]byte(yManifest)), Repository: "novox/y", Commit: "c"}) for _, u := range []string{"a.mod-x", "a.nats", "b.mod-y"} { b.put(grants.ReadAccounts, u, map[string]any{"user": u, "hash": "$2a$10$x", "generation": 1}) } res := p.Compose() - key, _ := delivery.PartGrantKey("mod-x", fp) + key, _ := grantsstate.PartGrantKey("mod-x", fp) if _, held := b.own[grants.StateGrants][key]; !held { t.Errorf("mod-x has no grant at the indexer's fingerprint %s: %+v", fp, res.Grants) } @@ -2155,7 +2167,7 @@ func TestGrants(t *testing.T) { if p.Compose(); len(said) != 1 { t.Errorf("the same missing grant was said again: %+v", said) } - b.put(grants.ReadModules, "mod-y", delivery.Module{V: delivery.Version, FP2: fp, Manifest: delivery.ManifestDigest([]byte(yManifest)), + b.put(grants.ReadModules, "mod-y", meshcatalog.ModulesByModuleValue{V: keys.Version, Fp2: fp, Manifest: meshcatalogclient.ManifestDigest([]byte(yManifest)), Repository: "novox/y", Commit: "c"}) if res := p.Compose(); len(res.Grants.Missing) != 0 || len(said) != 2 || said[1].State != occ.StateCleared { t.Errorf("once mod-y has a grant: missing %v, said %+v", res.Grants.Missing, said) @@ -2517,35 +2529,35 @@ func TestGrants(t *testing.T) { t.Fatalf("%s: %v", event, err) } } - verdictOf := func() delivery.ManifestVerdict { + verdictOf := func() meshcatalog.VerdictsByDigestValue { raw, _, _ := fv.Get(grants.ReadVerdicts, digest) - v, _ := delivery.DecodeManifestVerdict(raw) + v, _ := meshcatalogclient.DecodeVerdict(raw) return v } asked := verdicts.Status().Asks - heard(delivery.ManifestAcceptedEvent, delivery.ManifestAccepted{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Revision: 8}) - if v := verdictOf(); v.State != delivery.VerdictAccepted || v.Module != "mod-z" || verdicts.Status().Revision != 8 { + heard(meshcatalog.EventManifestAccepted, meshcatalog.ManifestAcceptedEvent{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Revision: 8}) + if v := verdictOf(); v.State != meshcatalogclient.VerdictAccepted || v.Module != "mod-z" || verdicts.Status().Revision != 8 { t.Errorf("manifest-accepted at the next revision: %+v at %d", v, verdicts.Status().Revision) } - heard(delivery.ManifestRefusedEvent, delivery.ManifestRefused{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Problems: []string{"p"}, Revision: 9}) - heard(delivery.ManifestAcceptedEvent, delivery.ManifestAccepted{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Revision: 9}) - if v := verdictOf(); !v.Refused() || !slices.Equal(v.Problems, []string{"p"}) || verdicts.Status().Asks != asked { + heard(meshcatalog.EventManifestRefused, meshcatalog.ManifestRefusedEvent{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Problems: []string{"p"}, Revision: 9}) + heard(meshcatalog.EventManifestAccepted, meshcatalog.ManifestAcceptedEvent{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Revision: 9}) + if v := verdictOf(); !meshcatalogclient.VerdictRefuses(v) || !slices.Equal(v.Problems, []string{"p"}) || verdicts.Status().Asks != asked { t.Errorf("manifest-refused, then a revision already held: %+v, asks %d (was %d)", v, verdicts.Status().Asks, asked) } // A revision below the picture's (ADR 0615 §5; the verdicts made again restart low): the same verdict again is a // redelivery, passed over; another verdict asks the first view once more. - heard(delivery.ManifestRefusedEvent, delivery.ManifestRefused{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Problems: []string{"p"}, Revision: 3}) + heard(meshcatalog.EventManifestRefused, meshcatalog.ManifestRefusedEvent{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Problems: []string{"p"}, Revision: 3}) if verdicts.Status().Asks != asked { t.Errorf("a held verdict redelivered at a lower revision asked: asks %d (was %d)", verdicts.Status().Asks, asked) } - heard(delivery.ManifestAcceptedEvent, delivery.ManifestAccepted{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Revision: 2}) + heard(meshcatalog.EventManifestAccepted, meshcatalog.ManifestAcceptedEvent{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Revision: 2}) waitFor(t, func() bool { return verdicts.Status().Asks > asked }) waitFor(t, func() bool { s := verdicts.Status(); return !s.Answered.Before(s.Asked) }) asked = verdicts.Status().Asks - heard(delivery.ManifestAcceptedEvent, delivery.ManifestAccepted{V: 1, Module: "mod-z", Digest: digest, Judge: "j"}) + heard(meshcatalog.EventManifestAccepted, meshcatalog.ManifestAcceptedEvent{V: 1, Module: "mod-z", Digest: digest, Judge: "j"}) waitFor(t, func() bool { return verdicts.Status().Asks > asked }) asked = verdicts.Status().Asks - heard(delivery.ManifestAcceptedEvent, delivery.ManifestAccepted{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Revision: 12}) + heard(meshcatalog.EventManifestAccepted, meshcatalog.ManifestAcceptedEvent{V: 1, Module: "mod-z", Digest: digest, Judge: "j", Revision: 12}) waitFor(t, func() bool { return verdicts.Status().Asks > asked }) if err := fv.HearVerdict(ctx, "manifest-judged", []byte("{}"), t.Logf); err == nil { t.Error("an event that is no verdict was taken") @@ -2664,14 +2676,14 @@ func TestGrants(t *testing.T) { b.manifestOf("mesh-enrolment", enrolmentManifest) b.assign("a", "mesh-enrolment") issued := time.Date(2026, 11, 2, 1, 0, 0, 0, time.UTC) - for node, stage := range map[string]string{"lab": delivery.EnrolmentLive, "pi": delivery.EnrolmentAsked} { - raw, err := delivery.EncodeEnrolment(delivery.Enrolment{Node: node, Address: "10.0.0.7", Why: "joins", - Caller: "a.mesh-enrolment", Stage: stage, Issued: issued, Expires: issued.Add(time.Hour), + for node, stage := range map[string]string{"lab": buscompose.EnrolmentLive, "pi": "asked"} { + raw, err := json.Marshal(meshnodeenroller.EnrolmentsByNodeNodeValue{Node: node, Address: "10.0.0.7", Why: "joins", + Caller: "a.mesh-enrolment", Stage: stage, Issued: &issued, Expires: new(issued.Add(time.Hour)), OverlayKey: base64.StdEncoding.EncodeToString(make([]byte, 32))}) if err != nil { t.Fatal(err) } - b.put(grants.ReadEnrolments, delivery.EnrolmentNodeKey(node), json.RawMessage(raw)) + b.put(grants.ReadEnrolments, meshnodeenroller.EnrolmentsByNodeNodeKey(node), json.RawMessage(raw)) } b.put(grants.ReadAccounts, "enrol.lab", map[string]any{"user": "enrol.lab", "hash": "$2a$10$x", "generation": 1}) in := grants.ReadInputs(b, &grants.Manifests{}) @@ -2696,7 +2708,7 @@ func TestGrants(t *testing.T) { t.Errorf("enrol.lab's hash is not carried: %+v", c) } b.mu.Lock() - delete(b.states[grants.ReadEnrolments], delivery.EnrolmentNodeKey("lab")) + delete(b.states[grants.ReadEnrolments], meshnodeenroller.EnrolmentsByNodeNodeKey("lab")) b.mu.Unlock() if slices.Contains(names(shadowOf(t, b)), "enrol.lab") { t.Error("enrol.lab stays after its key ended") @@ -2713,14 +2725,14 @@ func TestGrants(t *testing.T) { b.manifestOf("mesh-vault", vaultManifest) b.assign("a", "mesh-vault") since := time.Date(2026, 11, 2, 1, 0, 0, 0, time.UTC) - login := func(l delivery.NodeLogin) { - raw, err := delivery.EncodeNodeLogin(l) + login := func(l meshvault.NodeLoginsByNodeValue) { + raw, err := meshvaultclient.EncodeNodeLogin(l) if err != nil { t.Fatal(err) } b.put(grants.ReadNodeLogins, l.Node, json.RawMessage(raw)) } - login(delivery.NodeLogin{Node: "b", Login: "node.b", Rotating: "node.b--b", Stage: delivery.RotationBegun, Since: since}) + login(meshvault.NodeLoginsByNodeValue{Node: "b", Login: "node.b", Rotating: "node.b--b", Stage: meshvaultclient.RotationBegun, Since: since}) b.put(grants.ReadAccounts, "node.b--b", map[string]any{"user": "node.b--b", "hash": "$2a$10$x", "generation": 1}) users := shadowOf(t, b) in := grants.ReadInputs(b, &grants.Manifests{}) @@ -2734,20 +2746,20 @@ func TestGrants(t *testing.T) { t.Error("the second login is not judged its node's, or not rotating") } get := func(key string) string { - return "$JS.API.DIRECT.GET.KV_" + delivery.NodeLoginsBucket + ".$KV." + delivery.NodeLoginsBucket + "." + key + return "$JS.API.DIRECT.GET.KV_" + buscompose.NodeLoginsBucket + ".$KV." + buscompose.NodeLoginsBucket + "." + key } for _, u := range users { if !strings.HasPrefix(u.User, "node.") { continue } - node, _ := delivery.LoginNode(u.User) + node, _ := meshvaultclient.LoginNode(u.User) for _, of := range []string{"a", "b"} { if slices.Contains(u.Publish, get(of)) != (of == node) { t.Errorf("%s reads %s's node-logins key: %v", u.User, of, of != node) } } } - login(delivery.NodeLogin{Node: "b", Login: "node.b--b", Since: since.Add(time.Hour)}) + login(meshvault.NodeLoginsByNodeValue{Node: "b", Login: "node.b--b", Since: since.Add(time.Hour)}) if got := names(shadowOf(t, b)); slices.Contains(got, "node.b") || !slices.Contains(got, "node.b--b") { t.Errorf("after rotation-done the node users are %v", got) } @@ -2892,8 +2904,8 @@ func TestGrants(t *testing.T) { if err != nil { t.Fatal(err) } - b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, - UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}}) + b.put(grants.ReadHave, "a.nats", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, + UserList: &nodeengine.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}}) p := partOn(b) p.Objects = &fakeObjects{} var said []oc.Condition @@ -2974,8 +2986,8 @@ func TestGrants(t *testing.T) { if err != nil { t.Fatal(err) } - b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, - UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}}) + b.put(grants.ReadHave, "a.nats", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, + UserList: &nodeengine.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}}) p := partOn(b) p.Objects = &fakeObjects{} var digests []string @@ -3007,8 +3019,8 @@ func TestGrants(t *testing.T) { } } // The loaded list's permissions are read back from the keys when the bus module's tools cannot say them. - b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, - UserList: &delivery.UserListWritten{Source: delivery.GrantsUserListSource, Follows: delivery.FollowGrants, Revision: 8, + b.put(grants.ReadHave, "a.nats", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, + UserList: &nodeengine.UserListWritten{Source: grantsstate.GrantsUserListSource, Follows: grantsstate.FollowGrants, Revision: 8, Digest: digests[2], Users: 5, Key: busKey.PublicKey}}) b.refuseUsers = true if res := p.Compose(); slices.ContainsFunc(res.Grants.Waits, func(w string) bool { return strings.Contains(w, "unknown") }) { @@ -3040,14 +3052,14 @@ func TestGrants(t *testing.T) { if err != nil { t.Fatal(err) } - b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, - UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}}) + b.put(grants.ReadHave, "a.nats", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, + UserList: &nodeengine.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}}) p := partOn(b) objs := &fakeObjects{} p.Objects = objs - entryOf := func(g delivery.GrantsUserList, user string) string { + entryOf := func(g grantsstate.GrantsUserList, user string) string { t.Helper() - list, err := delivery.OpenUserList(objs.objects[g.Object], g.Digest, func(bx seal.Box) ([]byte, error) { + list, err := grantsstate.OpenUserList(objs.objects[g.Object], g.Digest, func(bx seal.Box) ([]byte, error) { out, err := seal.Open(bx, busKey.PrivateKey) return []byte(out), err }) @@ -3137,13 +3149,13 @@ func TestGrants(t *testing.T) { if err != nil { t.Fatal(err) } - b.put(grants.ReadHave, "a.nats", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, - UserList: &delivery.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}}) + b.put(grants.ReadHave, "a.nats", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, + UserList: &nodeengine.UserListWritten{Source: "a.nats.src2", Revision: 7, Digest: "sha256:aa", Users: 5, Key: busKey.PublicKey}}) // mod-r runs a build whose manifest is the accepted one: the held module's seats and streams are composed from it. runFP := "src2:" + strings.Repeat("cd", 32) - b.put(grants.ReadHave, "b.mod-r", delivery.Have{V: delivery.Version, Phase: delivery.PhaseRunning, Health: delivery.HealthOK, FP: runFP}) - runKey, _ := delivery.BuilderBuildKey("mod-r", runFP) - b.put(grants.ReadBuilds, runKey, map[string]any{"v": delivery.Version, "state": "done", "manifest": delivery.ManifestDigest([]byte(ctl))}) + b.put(grants.ReadHave, "b.mod-r", nodeengine.HaveByNodeModuleValue{V: keys.Version, Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK, Fp: runFP}) + runKey, _ := builds.BuilderBuildKey("mod-r", runFP) + b.put(grants.ReadBuilds, runKey, map[string]any{"v": keys.Version, "state": "done", "manifest": meshcatalogclient.ManifestDigest([]byte(ctl))}) p := partOn(b) p.Objects = &fakeObjects{} var said []oc.Condition @@ -3167,11 +3179,11 @@ func TestGrants(t *testing.T) { if held.unknown != "" { unknown = []string{held.unknown} } - b.put(grants.ReadModules, "mod-r", delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32), - Manifest: delivery.ManifestDigest([]byte(ctl)), Repository: "novox/x", Commit: "c", Verdict: delivery.VerdictRefused, + b.put(grants.ReadModules, "mod-r", meshcatalog.ModulesByModuleValue{V: keys.Version, Fp2: "src2:" + strings.Repeat("ab", 32), + Manifest: meshcatalogclient.ManifestDigest([]byte(ctl)), Repository: "novox/x", Commit: "c", Verdict: meshcatalogclient.VerdictRefused, Problems: []string{"it is held"}, Unknown: unknown}) - b.put(grants.ReadModules, "mod-q", delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32), - Manifest: delivery.ManifestDigest([]byte(ctl)), Repository: "novox/x", Commit: "c"}) + b.put(grants.ReadModules, "mod-q", meshcatalog.ModulesByModuleValue{V: keys.Version, Fp2: "src2:" + strings.Repeat("ab", 32), + Manifest: meshcatalogclient.ManifestDigest([]byte(ctl)), Repository: "novox/x", Commit: "c"}) res := p.Compose() now, _, _ := p.Sealed() after, _, ok, _ := b.ownGet(grants.StateUserList, grants.SealedUserKey(now.Digest, "b.mod-r.mod-r-tools")) @@ -3188,8 +3200,8 @@ func TestGrants(t *testing.T) { // A held module that runs no build the part can read declares nothing to anyone: only its own users stay as the list in // force holds them, said unreadable; the others move (ADR 0614 §8). delete(b.states[grants.ReadHave], "b.mod-r") - b.put(grants.ReadModules, "mod-r", delivery.Module{V: delivery.Version, FP2: "src2:" + strings.Repeat("ab", 32), - Manifest: delivery.ManifestDigest([]byte(ctl)), Repository: "novox/x", Commit: "c", Verdict: delivery.VerdictRefused, + b.put(grants.ReadModules, "mod-r", meshcatalog.ModulesByModuleValue{V: keys.Version, Fp2: "src2:" + strings.Repeat("ab", 32), + Manifest: meshcatalogclient.ManifestDigest([]byte(ctl)), Repository: "novox/x", Commit: "c", Verdict: meshcatalogclient.VerdictRefused, Problems: []string{"it is held"}, Unknown: []string{"core-credentials"}}) b.put(grants.ReadAccounts, "a.mod-x", map[string]any{"user": "a.mod-x", "hash": "$2a$10$gen9", "generation": 9, "fingerprint": "sha256:9"}) wasList, _, _ := p.Sealed() @@ -3207,7 +3219,7 @@ func TestGrants(t *testing.T) { // The window before the judge: the indexer wrote a changed manifest's value and no verdict on its digest yet. Only that // module's users stay (named unreadable); the others move. b.manifestOf("mod-r", ctl) - delete(b.states[grants.ReadVerdicts], delivery.ManifestDigest([]byte(ctl))) + delete(b.states[grants.ReadVerdicts], meshcatalogclient.ManifestDigest([]byte(ctl))) b.put(grants.ReadAccounts, "a.mod-x", map[string]any{"user": "a.mod-x", "hash": "$2a$10$gen11", "generation": 11, "fingerprint": "sha256:11"}) wasList, _, _ = p.Sealed() res = p.Compose() diff --git a/modules/mesh-grants/internal/buscompose/buildbacklog.go b/modules/mesh-grants/internal/buscompose/buildbacklog.go index f62cdae6..80e971e9 100644 --- a/modules/mesh-grants/internal/buscompose/buildbacklog.go +++ b/modules/mesh-grants/internal/buscompose/buildbacklog.go @@ -1,6 +1,6 @@ package buscompose -import "git.novox.be/novox/mesh-sdk/go/delivery" +import "git.novox.be/novox/mesh-delivery/sdk/buildstep" // The view alone reads the build seats' queues (novox/hq ADR 0525 §2, issue 1887 A8a): the shared workers' counts and the // oldest waiting ask, to show the backlog. It may not publish an ask: only the builder and the checker ask, by their seat use. @@ -18,8 +18,8 @@ const ( // Reads only: no consumer, no pull, no acknowledgement; the builder puts its asks by its seat use, as before. func BuildAskReads() []string { var out []string - for _, lang := range delivery.AskLanguages { - if stream, err := delivery.AskStream(lang); err == nil { + for _, lang := range buildstep.AskLanguages { + if stream, err := buildstep.AskStream(lang); err == nil { out = append(out, "$JS.API.STREAM.INFO."+stream, "$JS.API.STREAM.MSG.GET."+stream) } } @@ -27,18 +27,18 @@ func BuildAskReads() []string { } // BuildBacklogReads are the API subjects the view requests: the information of each build seat's stream and of each of its -// shared workers (delivery.AskStream, delivery.AskWorker), and the message of the stream by sequence. Reads only: no +// shared workers (buildstep.AskStream, buildstep.AskWorker), and the message of the stream by sequence. Reads only: no // message is pulled, none acknowledged, none published. func BuildBacklogReads() []string { var out []string - for _, lang := range delivery.AskLanguages { - stream, err := delivery.AskStream(lang) + for _, lang := range buildstep.AskLanguages { + stream, err := buildstep.AskStream(lang) if err != nil { continue } out = append(out, "$JS.API.STREAM.INFO."+stream, "$JS.API.STREAM.MSG.GET."+stream) - for _, kind := range delivery.AskKinds { - if w, err := delivery.AskWorker(lang, kind); err == nil { + for _, kind := range buildstep.AskKinds { + if w, err := buildstep.AskWorker(lang, kind); err == nil { out = append(out, "$JS.API.CONSUMER.INFO."+stream+"."+w) } } diff --git a/modules/mesh-grants/internal/buscompose/buscompose_test.go b/modules/mesh-grants/internal/buscompose/buscompose_test.go index 2d75af5c..38e2219c 100644 --- a/modules/mesh-grants/internal/buscompose/buscompose_test.go +++ b/modules/mesh-grants/internal/buscompose/buscompose_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "git.novox.be/novox/mesh-contracts/go/seats/meshbroker" "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshnodeenroller" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "maps" "os" "path/filepath" @@ -13,9 +14,12 @@ import ( "testing" "time" + "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient" + "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault" + "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "git.novox.be/novox/mesh-sdk/go/announce" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" "github.com/nats-io/nats.go/jetstream" ) @@ -56,8 +60,8 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) { // ADR 0567 §3, §9: a node reads every key of the seat's placement and its candidates (it composes with the whole // proposal laid over), and none of its open proposals; it writes neither. reads := strings.Join(NodeComposeReads("laptop"), " ") - for bucket, granted := range map[string]bool{delivery.PlacementBucket: true, delivery.PlacementCandidatesBucket: true, - delivery.PlacementProposalsBucket: false} { + for bucket, granted := range map[string]bool{meshmoduleassignerclient.PlacementBucket: true, PlacementCandidatesBucket: true, + PlacementProposalsBucket: false} { if strings.Contains(reads, "$JS.API.DIRECT.GET.KV_"+bucket+".>") != granted || strings.Contains(reads, "$KV."+bucket+".laptop") { t.Errorf("a node is granted %s: %v, want %v", bucket, !granted, granted) } @@ -281,8 +285,8 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) { since := time.Date(2026, 11, 2, 1, 0, 0, 0, time.UTC) users, err := Users(Records{Nodes: []string{"ace", "g14"}, Assigned: map[string][]Declared{"ace": {{Module: "mesh-vault", Holds: []Seat{vault}}}}, - NodeLogins: map[string]delivery.NodeLogin{ - "g14": {Node: "g14", Login: "node.g14", Rotating: "node.g14--b", Stage: delivery.RotationBegun, Since: since}, + NodeLogins: map[string]meshvault.NodeLoginsByNodeValue{ + "g14": {Node: "g14", Login: "node.g14", Rotating: "node.g14--b", Stage: meshvaultclient.RotationBegun, Since: since}, // A node that rotated before and rotates no more logs in as its second login alone. "ace": {Node: "ace", Login: "node.ace--b", Since: since}}}) if err != nil { @@ -317,19 +321,19 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) { t.Errorf("the two logins' permissions differ:\n%v\n%v", a, b) } for login, p := range perms { - node, _ := delivery.LoginNode(login) + node, _ := meshvaultclient.LoginNode(login) if !slices.Contains(p.Subscribe, "_INBOX.node."+node+".>") || slices.Contains(p.Subscribe, "_INBOX."+login+".>") && login != "node."+node { t.Errorf("%s's inbox: %v", login, p.Subscribe) } } // AC3: g14 reads node-logins key g14 and not ace, and writes neither; it confirms in its own login's name. get := func(key string) string { - return "$JS.API.DIRECT.GET.KV_" + delivery.NodeLoginsBucket + ".$KV." + delivery.NodeLoginsBucket + "." + key + return "$JS.API.DIRECT.GET.KV_" + NodeLoginsBucket + ".$KV." + NodeLoginsBucket + "." + key } confirm := func(login string) string { return "mesh.seat.mesh-vault.call.node-account-confirm." + CallerToken(login) } - for subject, want := range map[string]bool{get("g14"): true, get("ace"): false, "$KV." + delivery.NodeLoginsBucket + ".g14": false, + for subject, want := range map[string]bool{get("g14"): true, get("ace"): false, "$KV." + NodeLoginsBucket + ".g14": false, confirm("node.g14--b"): true, confirm("node.g14"): false} { if MayPublish(perms["node.g14--b"], subject) != want { t.Errorf("node.g14--b may publish %s: %v, want %v", subject, !want, want) @@ -339,7 +343,7 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) { t.Error("a node reads node-logins while the vault does not rotate") } // And nothing but the grants part names node-logins among its reads. - if !ReaderMay(GrantsModule, delivery.NodeLoginsRead) || ReaderMay("shop", delivery.NodeLoginsRead) { + if !ReaderMay(GrantsModule, NodeLoginsRead) || ReaderMay("shop", NodeLoginsRead) { t.Error("node-logins' named readers") } }) @@ -408,14 +412,14 @@ func TestTheCopiedComposerComposesAsTheControllers(t *testing.T) { } } if name == "ace.mesh-enrolment" && (!MayPublish(perms, call("sign", name)) || - !MayPublish(perms, "$KV."+delivery.EnrolmentsBucket+".node.lab") || !MayPublish(perms, "$KV."+delivery.EnrolmentsBucket+".address.10.0.0.7")) { + !MayPublish(perms, "$KV."+EnrolmentsBucket+".node.lab") || !MayPublish(perms, "$KV."+EnrolmentsBucket+".address.10.0.0.7")) { t.Errorf("mesh-enrolment does not sign or write its bucket: %v", perms.Publish) } - if name != "ace.mesh-enrolment" && MayPublish(perms, "$KV."+delivery.EnrolmentsBucket+".node.lab") { + if name != "ace.mesh-enrolment" && MayPublish(perms, "$KV."+EnrolmentsBucket+".node.lab") { t.Errorf("%s writes the enrolments", name) } } - if !ReaderMay(GrantsModule, delivery.EnrolmentsRead) || ReaderMay("shop", delivery.EnrolmentsRead) { + if !ReaderMay(GrantsModule, EnrolmentsRead) || ReaderMay("shop", EnrolmentsRead) { t.Error("the enrolments' named readers") } }) @@ -624,7 +628,7 @@ func healthChecks(t *testing.T) { }{ {"mesh-conditions", "mesh-healers.attempts", true}, {"mesh-conditions", HealersWould, false}, {"mesh-delivery-view", HealersWould, false}, // ADR 0577 clause 1: the node-engine's data items are disk-load's alone to read. - {"disk-load", delivery.EngineDataRead, true}, {"restic", delivery.EngineDataRead, false}, + {"disk-load", EngineDataRead, true}, {"restic", EngineDataRead, false}, } { granted, _ := ReadsAnnouncedBy(c.reader, []string{c.read}, announced) if (len(granted) == 1) != c.may { @@ -639,7 +643,7 @@ func healthChecks(t *testing.T) { // Issue 1955 L7: the grants part keeps the hearings it withholds; the self-check reads them. {Module: GrantsModule, State: []Bucket{bucket(GrantsModule, GrantsWithheldState)}}, {Module: "mesh-healers", State: []Bucket{bucket("mesh-healers", "findings"), bucket("mesh-healers", "attempts"), bucket("mesh-healers", "would")}}, - {Module: WatchmanModule, State: []Bucket{bucket(WatchmanModule, "findings")}, Reads: []string{delivery.NodeHealthRead}}, + {Module: WatchmanModule, State: []Bucket{bucket(WatchmanModule, "findings")}, Reads: []string{NodeHealthRead}}, {Module: "mesh-hand-acts", State: []Bucket{bucket("mesh-hand-acts", "warrants")}, Logs: []Log{{Module: "mesh-hand-acts", Name: "acts"}}}, // ADR 0570 clauses 1, 8 (issue 1954 L6-P5): mesh-provisioner keeps provisions and plans; the vault and a provider // read them. @@ -651,17 +655,17 @@ func healthChecks(t *testing.T) { {Module: "mesh-provisioner-client", ProvisionsNode: true}, // ADR 0571 clauses 2, 4 (issue 1955): the seat mesh-artifact-sweep says artifacts-gone; the assigner keeps seat-holders. {Module: "artifact-store-tools", Holds: []Seat{{Name: "mesh-artifact-sweep", Emits: []string{"artifacts-gone"}}}, - Reads: []string{delivery.SeatHoldersRead}}, - {Module: delivery.AssignerModule, State: []Bucket{bucket(delivery.AssignerModule, delivery.SeatHoldersState)}}, + Reads: []string{meshmoduleassignerclient.SeatHoldersRead}}, + {Module: AssignerModule, State: []Bucket{bucket(AssignerModule, meshmoduleassigner.StateSeatHolders)}}, // ADR 0571 clause 3: disk-load keeps the readings; the backup holder reads them. // ADR 0577 clause 1: and it reads its own node's keys of the node-engine's data items, which restic may not. - {Module: "disk-load", State: []Bucket{bucket("disk-load", "readings")}, Reads: []string{delivery.EngineDataRead}}, - {Module: "restic", Reads: []string{"disk-load.readings", delivery.EngineDataRead}, State: []Bucket{bucket("restic", "good")}}, + {Module: "disk-load", State: []Bucket{bucket("disk-load", "readings")}, Reads: []string{EngineDataRead}}, + {Module: "restic", Reads: []string{"disk-load.readings", EngineDataRead}, State: []Bucket{bucket("restic", "good")}}, // ADR 0571 clauses 1, 9: the indexer reads the builder's log through its own consumer; the catalogue module asks // the builder's tool. - {Module: delivery.BuilderModule, Logs: []Log{{Module: delivery.BuilderModule, Name: delivery.BuilderLog, MaxAgeDays: 30}}}, - {Module: delivery.IndexerModule, Reads: []string{delivery.BuildLogRead}}, - {Module: "mesh-catalog", Invokes: []string{delivery.BuilderModule + "." + delivery.BuildsSinceTool}}, + {Module: builds.BuilderModule, Logs: []Log{{Module: builds.BuilderModule, Name: builds.BuilderLog, MaxAgeDays: 30}}}, + {Module: IndexerModule, Reads: []string{builds.BuildLogRead}}, + {Module: "mesh-catalog", Invokes: []string{builds.BuilderModule + "." + builds.BuildsSinceTool}}, {Module: "telegram", Holds: []Seat{{Name: "telegram-sender", Accepts: []string{"send"}}}, Processes: []ProcessPrincipal{{Process: "w", Queues: []QueueOwner{{Process: "w", Stream: "M_WORK"}, {Process: "w", Stream: "KV_x"}}}}}, {Module: "nats", SnapshotsTheBus: true, Processes: []ProcessPrincipal{{Process: "nats-tools", @@ -785,9 +789,9 @@ func healthChecks(t *testing.T) { "a.shop.nats-tools": {"$JS.API.CONSUMER.DELETE.DEAD_LETTER_NOTICES.controller", "$KV.mesh-conditions_conditions.x", "$JS.API.STREAM.DELETE.ASSIGNMENTS"}, } // ADR 0571 clause 1: a read naming another module's log is issued to the reader as a log it reads, never writes. - if m := MembershipFor("a", Declared{Module: delivery.IndexerModule, Reads: []string{delivery.BuildLogRead, "x.y"}}, - Placements{Logs: map[string]bool{delivery.BuildLogRead: true}}); len(m.Logs) != 1 || m.Logs[0].Writes || - m.Logs[0].Stream != "LOG_mesh-delivery-builder_build-log" || m.Logs[0].Name != delivery.BuildLogRead { + if m := MembershipFor("a", Declared{Module: IndexerModule, Reads: []string{builds.BuildLogRead, "x.y"}}, + Placements{Logs: map[string]bool{builds.BuildLogRead: true}}); len(m.Logs) != 1 || m.Logs[0].Writes || + m.Logs[0].Stream != "LOG_mesh-delivery-builder_build-log" || m.Logs[0].Name != builds.BuildLogRead { t.Errorf("the indexer's membership issues the logs %+v", m.Logs) } // ADR 0565 clauses 6, 7 (issue 1953 L5-S4): a node's engine writes its own packages key and no other node's; only the @@ -957,13 +961,13 @@ func healthChecks(t *testing.T) { } } -// ADR 0593 §6 (D14): a named reader of delivery.ComposedRead reads every node's composed-, read-only, with the +// ADR 0593 §6 (D14): a named reader of ComposedRead reads every node's composed-, read-only, with the // listing and the creation advisory that find them, and its membership issues the family by its prefix; a module not // named is refused it; each node's engine writes its own node's bucket alone. func everyNodesCompositions(t *testing.T) { records := Records{Nodes: []string{"one", "two"}, Assigned: map[string][]Declared{ - "one": {{Module: "mesh-delivery-view", Reads: []string{delivery.ComposedRead}}, - {Module: "plex", Reads: []string{delivery.ComposedRead}}}, + "one": {{Module: "mesh-delivery-view", Reads: []string{ComposedRead}}, + {Module: "plex", Reads: []string{ComposedRead}}}, }} users, err := Users(records) if err != nil { @@ -1004,7 +1008,7 @@ func everyNodesCompositions(t *testing.T) { } // A bundle reading them (the provisioner, carried by the machine's runtime) reads them on the runtime's connection. carried := Principal{Kind: KindNodeTools, Node: "one", Module: "node-tools", Nodes: []string{"one", "two"}, - Carries: []Declared{{Module: "mesh-provisioner", Reads: []string{delivery.ComposedRead}}}} + Carries: []Declared{{Module: "mesh-provisioner", Reads: []string{ComposedRead}}}} runner, err := PermissionsFor(carried) if err != nil { t.Fatal(err) @@ -1013,9 +1017,9 @@ func everyNodesCompositions(t *testing.T) { !MayPublish(runner, "$JS.API.DIRECT.GET.KV_composed-two.$KV.composed-two.two.node") { t.Errorf("the runtime carrying the provisioner cannot read every node's compositions") } - m := MembershipFor("one", Declared{Module: "mesh-delivery-view", Reads: []string{delivery.ComposedRead}}, Placements{}) + m := MembershipFor("one", Declared{Module: "mesh-delivery-view", Reads: []string{ComposedRead}}, Placements{}) if !slices.ContainsFunc(m.State, func(s StateIssued) bool { - return s.Name == delivery.ComposedRead && s.PerNode && s.Bucket == delivery.ComposedBucketPrefix && !s.Writes + return s.Name == ComposedRead && s.PerNode && s.Bucket == ComposedBucketPrefix && !s.Writes }) { t.Errorf("the view's membership issues %+v", m.State) } diff --git a/modules/mesh-grants/internal/buscompose/composed.go b/modules/mesh-grants/internal/buscompose/composed.go index 26d65664..c47e16e8 100644 --- a/modules/mesh-grants/internal/buscompose/composed.go +++ b/modules/mesh-grants/internal/buscompose/composed.go @@ -2,14 +2,12 @@ package buscompose import ( "fmt" - - "git.novox.be/novox/mesh-sdk/go/delivery" ) // The compositions' buckets (novox/hq ADR 0539 clauses 2 and 4, as ADR 0593 §6 changes them): one per node, // `composed-`, which that node's engine alone writes, unsigned, with every composition it makes, and which the // grants part creates (EngineComposedBuckets). The provisioner, the assigner and the view read every node's -// (delivery.ComposedRead, a named-reader state); no engine applies a value read from it. +// (ComposedRead, a named-reader state); no engine applies a value read from it. // ComposedReads is what a node's node-engine publishes to keep and follow its compositions and wants (ADR 0593 §6, ADR // 0539 clause 9): the writes of its own composition bucket's keys (`..` and `.node`, all under @@ -17,7 +15,7 @@ import ( // assigner's bucket, and the indexer's candidates by one key's direct get (ADR 0593 §7: `compose-candidate` reads the // check's candidate itself; no listing and no watch); nothing of another node's, never the bucket's creation. func ComposedReads(node string) ([]string, error) { - bucket, err := delivery.ComposedBucketFor(node) + bucket, err := ComposedBucketFor(node) if err != nil { return nil, err } @@ -25,31 +23,31 @@ func ComposedReads(node string) ([]string, error) { return nil, fmt.Errorf("%q cannot be part of a subject: a permission is a subject pattern, and this would widen it", node) } out := append([]string{"$KV." + bucket + ".>"}, readGrants(bucket, "")...) - out = append(out, readGrants(delivery.AssignerWantsBucket, "want."+node+".>")...) + out = append(out, readGrants(AssignerWantsBucket, "want."+node+".>")...) return append(out, CandidateReads...), nil } // CandidateReads is what a node's engine is granted of the indexer's candidates and manifest verdicts to answer // `compose-candidate` (ADR 0593 §7, ADR 0578 §3): each bucket read whole as the builder's builds are (a key is read // through a short consumer), never written. -var CandidateReads = append(readGrants(delivery.CandidatesBucket, ""), readGrants(delivery.VerdictsBucket, "")...) +var CandidateReads = append(readGrants(CandidatesBucket, ""), readGrants(VerdictsBucket, "")...) // EngineComposedBuckets are the composition buckets of every node, as the node-engine's state (ADR 0593 §6): the grants -// part creates each, create-only, with the history ADR 0539 clause 4 keeps (delivery.ComposedHistory), as it creates a +// part creates each, create-only, with the history ADR 0539 clause 4 keeps (ComposedHistory), as it creates a // module's declared state. A node whose name a bucket cannot take is left out (its engine is refused its writes too). func EngineComposedBuckets(nodes []string) []Bucket { var out []Bucket for _, node := range sortedCopy(nodes) { - bucket, err := delivery.ComposedBucketFor(node) + bucket, err := ComposedBucketFor(node) if err != nil || !safeSubject.MatchString(node) { continue } - out = append(out, Bucket{Module: EngineModule, Name: delivery.ComposedState, Whole: bucket, History: delivery.ComposedHistory}) + out = append(out, Bucket{Module: EngineModule, Name: ComposedState, Whole: bucket, History: ComposedHistory}) } return out } -// composedFamily is every node's composition bucket, for a reader of delivery.ComposedRead. +// composedFamily is every node's composition bucket, for a reader of ComposedRead. func composedFamily(nodes []string) []string { var out []string for _, b := range EngineComposedBuckets(nodes) { diff --git a/modules/mesh-grants/internal/buscompose/engine.go b/modules/mesh-grants/internal/buscompose/engine.go index 41b5a1a1..4015ab85 100644 --- a/modules/mesh-grants/internal/buscompose/engine.go +++ b/modules/mesh-grants/internal/buscompose/engine.go @@ -3,8 +3,7 @@ package buscompose import ( nodeengineseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" - "git.novox.be/novox/mesh-sdk/go/delivery" - "git.novox.be/novox/mesh-sdk/go/provisions" + "git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient" ) // The node seat node-engine (novox/hq ADR 0563 §1): every node's engine holds it on its own bus account, and no module @@ -26,11 +25,7 @@ var EngineActsBucket = BucketName(EngineModule, EngineActsState) // EnginePackagesBucket is the engine's state of its node's declared packages (novox/hq ADR 0565 clause 6): one key, the // node's name, written by that node's engine alone. -var EnginePackagesBucket = BucketName(EngineModule, delivery.EnginePackagesState) - -// EngineDataBucket is the engine's state of its node's placed modules' data items (novox/hq ADR 0577 clause 1): one -// key `.` per module, written by that node's engine alone. -var EngineDataBucket = delivery.EngineDataBucket +var EnginePackagesBucket = BucketName(EngineModule, EnginePackagesState) // EngineFindingsBucket is the engine's state of the findings it raises about its own node (novox/hq ADR 0564 §2, ADR 0636 §1): // keys `machine..`, written by that node's engine alone and cleared by it; the condition keeper raises each as a @@ -75,13 +70,13 @@ func engineGrants(p Principal) (pub, sub []string) { // And the provisions its node's modules consume, its own node's keys alone, read (novox/hq ADR 0570 clause 6: the // consuming engine checks the vault's name and generation of what it is handed). if safeSubject.MatchString(p.Node) { - pub = append(pub, readGrants(provisions.ProvisionsBucket, p.Node+".>")...) + pub = append(pub, readGrants(meshprovisionerclient.ProvisionsBucket, p.Node+".>")...) } // And its node's own health report, its key `` alone (issue 774, the operator's alert table): what the watchman // reads for the machine's alerts. if safeSubject.MatchString(p.Node) { - pub = append(pub, append([]string{"$KV." + delivery.NodeHealthBucket + "." + p.Node}, - readGrants(delivery.NodeHealthBucket, p.Node)...)...) + pub = append(pub, append([]string{"$KV." + NodeHealthBucket + "." + p.Node}, + readGrants(NodeHealthBucket, p.Node)...)...) } // And its node's placed modules' data items, its own node's keys alone (ADR 0577 clause 1). if own, err := NodeKeysGrants(EngineDataBucket, p.Node); err == nil { diff --git a/modules/mesh-grants/internal/buscompose/health_grants.go b/modules/mesh-grants/internal/buscompose/health_grants.go index 4b9bb5db..2a383c41 100644 --- a/modules/mesh-grants/internal/buscompose/health_grants.go +++ b/modules/mesh-grants/internal/buscompose/health_grants.go @@ -6,8 +6,9 @@ import ( "sort" "strings" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshhealers" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshselfcheck" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/health" ) // **The health holders' grants** (novox/hq ADR 0564 clauses 2, 5 and 8, Limits 2, 4 and 8; issue 1952 S5; ADR 0590). @@ -22,7 +23,7 @@ import ( // controller's leftovers (ADR 0590 clause 6), never the backup's account `broker`, which only reads (ADR 0235). // HealersWould is what mesh-healers would have done while a controller was placed: read by nobody (ADR 0564 clause 8). -var HealersWould = health.HealersModule + "." + health.WouldState +var HealersWould = meshhealers.Seat + "." + meshhealers.StateWould // ControllerLeftoverConsumers are the ended controller's durable consumers left on the bus, one leftover of the module // mesh-delivery placed nowhere (issue 1126, milestone section 3), and mesh-catalog's of the controller's built-before, @@ -160,7 +161,7 @@ func WatchmanHears() []string { "mesh.mod.*.event.provisioner.failing", "mesh.mod.*.event.provisioner.recovered", "mesh.control.*.tools-alive", - "mesh.seat.mesh-self-check.event." + health.EventChecked, + "mesh.seat." + meshselfcheck.Seat + ".event." + meshselfcheck.EventChecked, "$JS.EVENT.ADVISORY.CONSUMER.DELETED.>", "mesh.mod.nats.event.bus.slow-consumer", "mesh.mod.nats.event.bus.connection-closed", diff --git a/modules/mesh-grants/internal/buscompose/names.go b/modules/mesh-grants/internal/buscompose/names.go new file mode 100644 index 00000000..bbb5cf61 --- /dev/null +++ b/modules/mesh-grants/internal/buscompose/names.go @@ -0,0 +1,75 @@ +package buscompose + +import ( + "fmt" + "regexp" +) + +// **The states and holders of other parts that the grants part names** (novox/hq ADR 0634 §1: no contract names a holder; ADR 0635 +// §4): each is the holder's own manifest's, as its `reads` and states say it, and the part reads or grants them by these names. +// TestTheNamesAreTheManifests checks those its own manifest lists in `reads`. +const ( + AssignerModule = "mesh-delivery-assigner" + IndexerModule = "mesh-delivery-indexer" + VaultModule = "mesh-vault" + // EnrolmentModule is the module holding the seat mesh-node-enroller. + EnrolmentModule = "mesh-enrolment" + + AssignerWantsBucket = AssignerModule + "_wants" + PlacementCandidatesBucket = AssignerModule + "_candidates" + PlacementProposalsBucket = AssignerModule + "_placement-proposals" + CandidatesBucket = IndexerModule + "_candidates" + ModulesBucket = IndexerModule + "_modules" + ManifestsBucket = IndexerModule + "_manifests" + VerdictsBucket = IndexerModule + "_verdicts" + + EnrolmentsState = "enrolments" + EnrolmentsBucket = EnrolmentModule + "_" + EnrolmentsState + EnrolmentsRead = EnrolmentModule + "." + EnrolmentsState + // EnrolmentLive and EnrolmentRedeeming are the stages of an enrolment the grants part reads (the seat's Stage). + EnrolmentLive = "live" + EnrolmentRedeeming = "redeeming" + + NodeLoginsState = "node-logins" + NodeLoginsBucket = VaultModule + "_" + NodeLoginsState + NodeLoginsRead = VaultModule + "." + NodeLoginsState + SignersBucket = VaultModule + "_signers" + // RotateNodeVerb and NodeAccountConfirmVerb are the seat mesh-vault's verbs the grants part grants. + RotateNodeVerb = "rotate-node" + NodeAccountConfirmVerb = "node-account-confirm" + + // The node-engine's states (its module is EngineModule). + EngineDataState = "data" + EngineDataBucket = EngineModule + "_" + EngineDataState + EngineDataRead = EngineModule + "." + EngineDataState + // EnginePackagesState is the engine's state of its node's declared packages. + EnginePackagesState = "packages" + NodeHealthBucket = EngineModule + "_health" + NodeHealthRead = EngineModule + ".health" + HaveBucket = "node-engine_have" + // HaveNodeWord is the word a node's own key in `have` carries after its name. + HaveNodeWord = "node" + + // ComposedState is the engine's per-node state of compositions, kept one bucket per node, `composed-`. + ComposedState = "composed" + ComposedRead = EngineModule + "." + ComposedState + ComposedBucketPrefix = "composed-" + ComposedHistory = 5 +) + +// ComposedReaders are the modules the engine's compositions are read by (the engine's `composed` state is named-reader). +var ComposedReaders = []string{AssignerModule, "mesh-delivery-view", "mesh-provisioner"} + +var composedNode = regexp.MustCompile(`^[A-Za-z0-9_-]+$`) + +// ComposedBucketFor is the name of a node's bucket of compositions: `composed-`, the node's name one a bucket and a +// subject token accept (letters, digits, '_' and '-'). +func ComposedBucketFor(node string) (string, error) { + if node == "" { + return "", fmt.Errorf("buscompose: the node name is empty; a node's composition bucket is named for its node") + } + if !composedNode.MatchString(node) { + return "", fmt.Errorf("buscompose: the node name %q is not one a bucket accepts: letters, digits, '_' and '-' only", node) + } + return ComposedBucketPrefix + node, nil +} diff --git a/modules/mesh-grants/internal/buscompose/names_test.go b/modules/mesh-grants/internal/buscompose/names_test.go new file mode 100644 index 00000000..4fd4e90e --- /dev/null +++ b/modules/mesh-grants/internal/buscompose/names_test.go @@ -0,0 +1,50 @@ +package buscompose + +import ( + "encoding/json" + "os" + "slices" + "strings" + "testing" +) + +// TestTheNamesAreTheManifests: the holders and states of other parts that the grants part names are the ones its manifest +// lists in `reads` and `invokes` (no contract names a holder: novox/hq ADR 0634 §1), and a state's bucket is its holder's +// name and the state's. +func TestTheNamesAreTheManifests(t *testing.T) { + raw, err := os.ReadFile("../../module.json") + if err != nil { + t.Fatal(err) + } + var m struct { + Module string `json:"module"` + Reads []string `json:"reads"` + Invokes []string `json:"invokes"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatal(err) + } + if m.Module != GrantsModule { + t.Errorf("the manifest's module is %q, the part names itself %q", m.Module, GrantsModule) + } + for _, c := range []struct{ list, name, bucket string }{ + {"reads", HaveBucket[:strings.Index(HaveBucket, "_")] + ".have", HaveBucket}, + {"reads", NodeLoginsRead, NodeLoginsBucket}, + {"reads", EnrolmentsRead, EnrolmentsBucket}, + {"invokes", AssignerModule + ".wants", AssignerWantsBucket}, + {"invokes", IndexerModule + ".modules", ModulesBucket}, + {"invokes", IndexerModule + ".manifests", ManifestsBucket}, + {"invokes", IndexerModule + ".verdicts", VerdictsBucket}, + } { + list := m.Reads + if c.list == "invokes" { + list = m.Invokes + } + if !slices.Contains(list, c.name) { + t.Errorf("the part names %s, its manifest's %s are %v", c.name, c.list, list) + } + if got := strings.Replace(c.name, ".", "_", 1); got != c.bucket { + t.Errorf("%s is kept in %s, the part names the bucket %s", c.name, got, c.bucket) + } + } +} diff --git a/modules/mesh-grants/internal/buscompose/nats.go b/modules/mesh-grants/internal/buscompose/nats.go index e970ecbe..29e806ff 100644 --- a/modules/mesh-grants/internal/buscompose/nats.go +++ b/modules/mesh-grants/internal/buscompose/nats.go @@ -25,7 +25,6 @@ import ( mv "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // A Kind is what a principal is, which decides the shape of its authority rather than its @@ -131,7 +130,7 @@ type Principal struct { // Objects are the bus names of its module's own object buckets, and ReadsObjects those of others it reads (novox/hq // ADR 0592 §5, §9). Objects, ReadsObjects []string - // Nodes are every node the records know, for a read of a state kept one bucket per node (delivery.ComposedRead, ADR + // Nodes are every node the records know, for a read of a state kept one bucket per node (ComposedRead, ADR // 0593 §6): set only where the principal or a module it carries reads one. Nodes []string // FilesFor are the nodes whose declaration-file buckets (novox/hq ADR 0486 §2) this principal may remove, for the bus @@ -395,8 +394,8 @@ var VaultSign = SeatVerb{Seat: "mesh-vault", Verb: mv.VerbSign} // principal reads its own node's key of node-logins (novox/hq ADR 0583 §1, §2). VaultNodeAccountConfirm is the verb a // node-engine confirms a switch with, in its new login's name (§3). var ( - VaultRotateNode = SeatVerb{Seat: "mesh-vault", Verb: delivery.RotateNodeVerb} - VaultNodeAccountConfirm = SeatVerb{Seat: "mesh-vault", Verb: delivery.NodeAccountConfirmVerb} + VaultRotateNode = SeatVerb{Seat: "mesh-vault", Verb: RotateNodeVerb} + VaultNodeAccountConfirm = SeatVerb{Seat: "mesh-vault", Verb: NodeAccountConfirmVerb} ) // EnrolmentRedeem is the seat mesh-enrolment's verb a joining node-engine calls as enrol. (novox/hq ADR 0587 §4, @@ -415,7 +414,7 @@ func NodeLoginsReads(node string) []string { if !safeSubject.MatchString(node) { return nil } - return readGrants(delivery.NodeLoginsBucket, node) + return readGrants(NodeLoginsBucket, node) } // perMachineEvents are a node-scoped seat's events about the holder itself, whose last token is the @@ -730,7 +729,7 @@ func PermissionsFor(p Principal) (Permissions, error) { pub = append(pub, WitnessSubjects(p)...) // And the keys of its own node in the have bucket, which it alone writes (novox/hq ADR 0429 §1, issue 1136): // what the node has of each module, read by the view and the watchman. Never the controller's. - haveKeys, err := NodeKeysGrants(delivery.HaveBucket, p.Node) + haveKeys, err := NodeKeysGrants(HaveBucket, p.Node) if err != nil { return Permissions{}, err } @@ -738,7 +737,7 @@ func PermissionsFor(p Principal) (Permissions, error) { // And every node's own facts, `.node`, its record among them (issue 1126, ADR 0555 §3): a node agent composing // its own modules reads the other nodes' public records, which its firewall and resolver need (ADR 0547 §2). Read // only: each node still writes its own keys alone. - pub = append(pub, readGrants(delivery.HaveBucket, "*."+delivery.HaveNodeWord)...) + pub = append(pub, readGrants(HaveBucket, "*."+HaveNodeWord)...) // And what it composes its own modules from (ADR 0547 §2, ADR 0567 §9): the seat mesh-placement's placement and // candidates, the indexer's modules and manifests, the builder's builds (the manifest of a build its want names), and mesh-config's resolved values of // its own node alone. Read only. @@ -797,7 +796,7 @@ func PermissionsFor(p Principal) (Permissions, error) { // halves, read and watched (ADR 0550 §2). Read only. if p.AsksTheVaultToSign { pub = append(pub, VaultSignCall(p)) - pub = append(pub, readGrants(delivery.SignersBucket, "")...) + pub = append(pub, readGrants(SignersBucket, "")...) } // And, while the vault rotates node accounts, its own node's key of node-logins, read and watched, and no other // key (novox/hq ADR 0583 §1 insight): the login it dials as; and the confirmation of a switch in its own name (§3). @@ -1103,7 +1102,7 @@ func PermissionsFor(p Principal) (Permissions, error) { pub = append(pub, objectGrants(objectNames(d.Objects), d.ReadsObjects)...) // A bundle reads every node's compositions on the runtime's own connection (ADR 0593 §6), which hears a node's // bucket made later by any stream's creation. - if slices.Contains(d.Reads, delivery.ComposedRead) && !slices.Contains(sub, familyCreatedAdvisory) { + if slices.Contains(d.Reads, ComposedRead) && !slices.Contains(sub, familyCreatedAdvisory) { sub = append(sub, familyCreatedAdvisory) } } @@ -1214,7 +1213,7 @@ func PermissionsFor(p Principal) (Permissions, error) { } } // And the vault's enrolment verbs to every principal but mesh-enrolment's (novox/hq ADR 0587 §1). - if p.VaultEnrols && !((p.Kind == KindModule || p.Kind == KindProcess) && p.Module == delivery.EnrolmentModule) { + if p.VaultEnrols && !((p.Kind == KindModule || p.Kind == KindProcess) && p.Module == EnrolmentModule) { for _, only := range EnrolmentOnly() { if slices.ContainsFunc(pub, func(a string) bool { return SubjectsOverlap(a, only) }) { deny = append(deny, only) diff --git a/modules/mesh-grants/internal/buscompose/provisioner.go b/modules/mesh-grants/internal/buscompose/provisioner.go index e2794634..c175520c 100644 --- a/modules/mesh-grants/internal/buscompose/provisioner.go +++ b/modules/mesh-grants/internal/buscompose/provisioner.go @@ -5,8 +5,8 @@ import ( "fmt" "strings" - "git.novox.be/novox/mesh-sdk/go/delivery" - "git.novox.be/novox/mesh-sdk/go/provisions" + "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient" "github.com/nats-io/nats.go" ) @@ -49,7 +49,7 @@ var ProvisionerReads = []string{ConfigResolvedBucket} // reason** (2026-10-30 recovery, issue 774): the client V3 watches every machine's plans to bind a provider's // consumers, and granted only its own machine's keys it read none. ADR 0570 clause 3 has providers act on the seat's // provision keys instead; once the client does, plans go back to its own machine's keys. -var ProvisionerReadsWhole = []string{delivery.PlacementBucket, provisions.PlansBucket} +var ProvisionerReadsWhole = []string{meshmoduleassignerclient.PlacementBucket, meshprovisionerclient.PlansBucket} // BoundState is a providing module's state of entries, one per consumer (mesh-sdk go/provider, issue 678): a // consumer's machine's client reads its own keys of it, `..[.]`, to fill @@ -147,7 +147,7 @@ func provisionerGrants(p Principal) (pub, sub []string, err error) { // 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, readGrants(HaveBucket, p.Node+".node")...) pub = append(pub, "prov.applied."+p.Node+".>") token := CallerToken(p.Username()) if token == "" { diff --git a/modules/mesh-grants/internal/buscompose/seattraffic.go b/modules/mesh-grants/internal/buscompose/seattraffic.go index 70356c86..5e369c6b 100644 --- a/modules/mesh-grants/internal/buscompose/seattraffic.go +++ b/modules/mesh-grants/internal/buscompose/seattraffic.go @@ -28,7 +28,7 @@ import ( // (`$KV...>`), so an asker reads the state of its own asks and no other asker's; and on a seat pulled by // kind, **what its workers gave up on**, the copies DEAD_LETTERS keeps of the seat's stream (DeadLetterReads, issue 734). -// BuildAskMaxDeliver bounds the deliveries of a build seat's ask (issue 1907): mesh-sdk delivery.AskMaxDeliver, written +// BuildAskMaxDeliver bounds the deliveries of a build seat's ask (issue 1907): sdk/buildstep AskMaxDeliver, written // here as the broker mirrors the catalogue rather than importing it. const BuildAskMaxDeliver = 5 @@ -82,7 +82,7 @@ func SeatTrafficOf(module string, holds, uses, watches []Seat) SeatTraffic { } if len(s.Accepts) > 0 && s.PulledByKind { // One worker per accepted kind, which every holder pulls in the order of Accepts (ADR 0525 §4, ADR 0536 §4): - // `SEAT___worker`, filtered to `accept.` (mesh-sdk delivery.AskWorker). + // `SEAT___worker`, filtered to `accept.` (sdk/buildstep AskWorker). for _, a := range s.Accepts { t.Workers = append(t.Workers, Worker{Stream: seatStreamName(s.Name), Consumer: WorkerName(s.Name, a), Filter: "mesh.seat." + s.Name + ".accept." + a}) @@ -252,7 +252,7 @@ func SeatTrafficObjects(users []Principal) ([]Stream, []Consumer) { } if byKind { // A build seat's ask is offered a bounded number of times (issue 1907): the holder that takes it past - // mesh-sdk delivery.AskRuns ends it failed saying it is poison, and the bound stops a run that kills + // sdk/buildstep AskRuns ends it failed saying it is poison, and the bound stops a run that kills // every node it reaches even when that holder dies too. c.MaxDeliver = BuildAskMaxDeliver c.Why = module + " holds " + seat + "; each holder pulls an ask when it has a free slot, and an ask that " + diff --git a/modules/mesh-grants/internal/buscompose/state.go b/modules/mesh-grants/internal/buscompose/state.go index 5e879a34..733b6159 100644 --- a/modules/mesh-grants/internal/buscompose/state.go +++ b/modules/mesh-grants/internal/buscompose/state.go @@ -2,8 +2,9 @@ package buscompose import ( "fmt" + "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "git.novox.be/novox/mesh-sdk/go/announce" - "git.novox.be/novox/mesh-sdk/go/delivery" "slices" "strings" "time" @@ -168,7 +169,7 @@ func stateGrants(a stateAccess) []string { out = append(out, announce.Subject(a.Module)) } for _, r := range a.Reads { - if r == delivery.ComposedRead { + if r == ComposedRead { // Every node's compositions, read-only (ADR 0593 §6), with the listing that finds their buckets. for _, bucket := range composedFamily(a.Nodes) { read(bucket, "") @@ -197,7 +198,7 @@ func stateGrants(a stateAccess) []string { // NodeKeyedReads are the states keyed `.>` whose reader reads its own machine's keys alone, whatever its `reads` // names: the node-engine's data items, each node's disk-load reading its own node's (novox/hq ADR 0577 clause 1). A // machine with no usable name reaches none of them. -var NodeKeyedReads = map[string]bool{delivery.EngineDataRead: true} +var NodeKeyedReads = map[string]bool{EngineDataRead: true} // StreamCreatedAdvisory is the subject the bus says a stream was created on (the server's own advisory), here for a // bucket's stream KV_. @@ -230,7 +231,7 @@ func stateCreatedHeard(a stateAccess) []string { add(BucketName(a.Module, name)) } for _, r := range a.Reads { - if r == delivery.ComposedRead { + if r == ComposedRead { // A node's bucket made after the reader began is heard by any stream's creation (ADR 0593 §6). if !seen[familyCreatedAdvisory] { seen[familyCreatedAdvisory] = true @@ -265,7 +266,7 @@ type stateAccess struct { ReadOnly []string Reads []string KeyedReads []KeyedRead - // Nodes are every node, whose bucket of a per-node state (delivery.ComposedRead) a read of it reaches. + // Nodes are every node, whose bucket of a per-node state (ComposedRead) a read of it reaches. Nodes []string } @@ -302,10 +303,10 @@ func stateIssuedFor(d Declared, node string) []StateIssued { out = append(out, issued) } for _, r := range d.Reads { - if r == delivery.ComposedRead { + if r == ComposedRead { // One bucket per node, issued as the family's prefix (ADR 0593 §6): the runtime reads every node's bucket as one // state, a node's bucket made later included. - out = append(out, StateIssued{Name: r, Bucket: delivery.ComposedBucketPrefix, PerNode: true}) + out = append(out, StateIssued{Name: r, Bucket: ComposedBucketPrefix, PerNode: true}) continue } if bucket, ok := bucketOfRead(r); ok { @@ -340,8 +341,8 @@ const ResolvedBucket = "mesh-config_resolved" // secret. Reads only; the node is checked as every subject token is by the caller's principal. func NodeComposeReads(node string) []string { var out []string - for _, bucket := range []string{delivery.PlacementBucket, delivery.PlacementCandidatesBucket, delivery.ModulesBucket, - delivery.ManifestsBucket, delivery.BuilderBuildsBucket} { + for _, bucket := range []string{meshmoduleassignerclient.PlacementBucket, PlacementCandidatesBucket, ModulesBucket, + ManifestsBucket, builds.BuilderBuildsBucket} { out = append(out, readGrants(bucket, "")...) } return append(out, readGrants(ResolvedBucket, node+".>")...) @@ -350,7 +351,7 @@ func NodeComposeReads(node string) []string { // NodeComposeAdvisories are the stream-created advisories of the seat mesh-placement's placement and candidates // (ADR 0567 §9): what a node agent hears to bind each once it is made. Those two and no wider. func NodeComposeAdvisories() []string { - return []string{StreamCreatedAdvisory(delivery.PlacementBucket), StreamCreatedAdvisory(delivery.PlacementCandidatesBucket)} + return []string{StreamCreatedAdvisory(meshmoduleassignerclient.PlacementBucket), StreamCreatedAdvisory(PlacementCandidatesBucket)} } // ServedReadGrants is the read of each key of mesh-config's resolved values named, `..` diff --git a/modules/mesh-grants/internal/buscompose/userlist_switch.go b/modules/mesh-grants/internal/buscompose/userlist_switch.go index 6ac0c873..708656a8 100644 --- a/modules/mesh-grants/internal/buscompose/userlist_switch.go +++ b/modules/mesh-grants/internal/buscompose/userlist_switch.go @@ -1,13 +1,13 @@ package buscompose import ( + "git.novox.be/novox/mesh-catalog/modules/disk-load/diskreadings" "slices" "strings" "time" + "git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate" "git.novox.be/novox/mesh-sdk/go/announce" - "git.novox.be/novox/mesh-sdk/go/delivery" - "git.novox.be/novox/mesh-sdk/go/health" ) // **The grants part's user list, sealed to the bus node** (novox/hq ADR 0547 clause 7, issue 1945). The grants part writes @@ -26,10 +26,10 @@ import ( // GrantsUserListBucket, GrantsUserListKey, GrantsUserListObjects and GrantsUserListObjectStream are delivery's names, // here where the grants are composed. const ( - GrantsUserListBucket = delivery.GrantsUserListBucket - GrantsUserListKey = delivery.GrantsUserListKey - GrantsUserListObjects = delivery.GrantsUserListObjects - GrantsUserListObjectStream = delivery.GrantsUserListObjectStream + GrantsUserListBucket = grantsstate.GrantsUserListBucket + GrantsUserListKey = grantsstate.GrantsUserListKey + GrantsUserListObjects = grantsstate.GrantsUserListObjects + GrantsUserListObjectStream = grantsstate.GrantsUserListObjectStream ) // GrantsUserListFollows is what the node-engine of the node holding mesh-broker may publish to follow the grants part's @@ -92,14 +92,14 @@ const ConfigModule = "mesh-config" var NamedReaders = map[string][]string{VaultBusAccounts: {GrantsModule}, GrantsAccounts: {VaultSeat}, HealersWould: {}, // ADR 0577 clause 1: the node-engine's data items are read by disk-load alone, each node's its own node's keys // (NodeKeyedReads); the engine announces nothing of its own, so its readers are named here. - delivery.EngineDataRead: {health.DiskLoadModule}, + EngineDataRead: {diskreadings.DiskLoadModule}, // ADR 0583 §1 insight: every node's login is read by the grants part alone, each engine its own node's key through its // machine principal (NodeLoginsReads), never through `reads`. - delivery.NodeLoginsRead: {GrantsModule}, + NodeLoginsRead: {GrantsModule}, // ADR 0587 §2: the open enrolments are read by their holder and, as a named reader, by the grants part. - delivery.EnrolmentsRead: {GrantsModule}, + EnrolmentsRead: {GrantsModule}, // ADR 0593 §6: every node's compositions are read by the provisioner, the assigner and the view alone. - delivery.ComposedRead: delivery.ComposedReaders} + ComposedRead: ComposedReaders} // ReaderMay says whether module may read the state read (`.`) by the named readers: always, for a state // that names none. diff --git a/modules/mesh-grants/internal/buscompose/users.go b/modules/mesh-grants/internal/buscompose/users.go index 0805cf13..8da754fc 100644 --- a/modules/mesh-grants/internal/buscompose/users.go +++ b/modules/mesh-grants/internal/buscompose/users.go @@ -6,9 +6,10 @@ import ( "sort" "strings" + "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient" + "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault" "git.novox.be/novox/mesh-sdk/go/announce" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // Every user the composed file should contain, derived from what the mesh knows. @@ -165,7 +166,7 @@ type Records struct { Engine *Declared // NodeLogins is each node's key of the vault's node-logins (novox/hq ADR 0583 §1): the login in use and the one // rotating, each composed as a user of the node's permissions. A node absent logs in as `node.` alone. - NodeLogins map[string]delivery.NodeLogin + NodeLogins map[string]meshvault.NodeLoginsByNodeValue // Announced is each module's last announcement of the states it keeps (novox/hq ADR 0495, issue 1790), as the // controller keeps it; a module absent has announced nothing yet. Nil where none was read. Announced map[string]announce.States @@ -361,7 +362,7 @@ func Users(r Records) ([]Principal, error) { for _, d := range out[i].Carries { reads = append(reads, d.Reads...) } - if slices.Contains(reads, delivery.ComposedRead) { + if slices.Contains(reads, ComposedRead) { out[i].Nodes = sortedCopy(r.Nodes) } } @@ -445,17 +446,17 @@ func asksSeatOf(r Records) []Seat { // not hold (a key naming another node's login is no login of this node's). func nodeLoginsOf(r Records, node string) []string { l, ok := r.NodeLogins[node] - if !ok || l.Node != node || (l.Login != delivery.FirstLogin(node) && l.Login != delivery.SecondLogin(node)) { + if !ok || l.Node != node || (l.Login != meshvaultclient.FirstLogin(node) && l.Login != meshvaultclient.SecondLogin(node)) { return []string{""} } login := func(name string) string { - if name == delivery.FirstLogin(node) { + if name == meshvaultclient.FirstLogin(node) { return "" } return name } out := []string{login(l.Login)} - if l.Rotates() && l.Rotating == delivery.OtherLogin(node, l.Login) { + if meshvaultclient.Rotates(l) && l.Rotating == meshvaultclient.OtherLogin(node, l.Login) { out = append(out, login(l.Rotating)) } return out diff --git a/modules/mesh-grants/internal/buscompose/writers.go b/modules/mesh-grants/internal/buscompose/writers.go index 9143f6a2..a894e6a5 100644 --- a/modules/mesh-grants/internal/buscompose/writers.go +++ b/modules/mesh-grants/internal/buscompose/writers.go @@ -4,8 +4,6 @@ import ( "fmt" "slices" "strings" - - "git.novox.be/novox/mesh-sdk/go/delivery" ) // The writers table (novox/hq to-be 45 §1, ADR 0227 rule 1), compiled in. @@ -179,13 +177,13 @@ var WritersTable = []WriterRow{ // The seat mesh-enrolment's open enrolments (novox/hq ADR 0587 §2, issue 774): its holder alone writes them, each a // compare-and-set at the revision read; the grants part reads them as a named reader. {State: "the open enrolments", Writer: "the seat mesh-enrolment's holder, compare-and-set", - KeptIn: "key-value " + delivery.EnrolmentsBucket + ", with per-key expiry", + KeptIn: "key-value " + EnrolmentsBucket + ", with per-key expiry", Others: "the grants part reads them (a named reader) and composes each enrol. user from them", - Subjects: kvOf(delivery.EnrolmentsBucket), Writes: moduleNamed(delivery.EnrolmentModule)}, + Subjects: kvOf(EnrolmentsBucket), Writes: moduleNamed(EnrolmentModule)}, // Which login each node uses (novox/hq ADR 0583 §1 insight): the vault alone writes it. - {State: "a node's logins", Writer: "the vault", KeptIn: "key-value " + delivery.NodeLoginsBucket, + {State: "a node's logins", Writer: "the vault", KeptIn: "key-value " + NodeLoginsBucket, Others: "the grants part reads every key, each node's engine its own node's key alone", - Subjects: kvOf(delivery.NodeLoginsBucket), Writes: moduleNamed(delivery.VaultModule)}, + Subjects: kvOf(NodeLoginsBucket), Writes: moduleNamed(VaultModule)}, // What a consumer gave up on (novox/hq issue 330): copied by the watchman from the server's notice (ADR 0564 // clause 5, ADR 0590 clause 2). {State: "a message a consumer gave up on", Writer: "the watchman", KeptIn: "the bus, the stream " + DeadLettersStream, @@ -194,14 +192,14 @@ var WritersTable = []WriterRow{ // What each machine has of each module (novox/hq ADR 0429 §1, design 47): written by that machine's node-engine under // its own node's keys; the view and the watchman read it. {State: "a machine's have", Writer: "the node-engine of the machine, under its own keys `.>`", - KeptIn: "key-value " + delivery.HaveBucket + ", twenty revisions of each key", - Others: "read through `reads` as node-engine.have", Subjects: kvOf(delivery.HaveBucket), Writes: ownNodeKeys}, + KeptIn: "key-value " + HaveBucket + ", twenty revisions of each key", + Others: "read through `reads` as node-engine.have", Subjects: kvOf(HaveBucket), Writes: ownNodeKeys}, // The acts a node's engine was asked through its node seat, and its own asks (novox/hq ADR 0563 §2, §3): the // node-engine's declared state, created by the grants part, written by each node's engine under its own keys. // A node's own health report (issue 774): its engine's own key, read by the watchman. {State: "a machine's health report", Writer: "the node-engine of the machine, its key `` alone", - KeptIn: "key-value " + delivery.NodeHealthBucket, Others: "read by the watchman through `reads`", - Subjects: kvOf(delivery.NodeHealthBucket), Writes: ownNodeKeys}, + KeptIn: "key-value " + NodeHealthBucket, Others: "read by the watchman through `reads`", + Subjects: kvOf(NodeHealthBucket), Writes: ownNodeKeys}, // A node's declared packages (novox/hq ADR 0565 clause 6): its engine's own key, read by pacman's verbs. {State: "a machine's declared packages", Writer: "the node-engine of the machine, its key `` alone", KeptIn: "key-value " + EnginePackagesBucket, Others: "read by pacman through `reads`", diff --git a/modules/mesh-grants/internal/grants/firstviews.go b/modules/mesh-grants/internal/grants/firstviews.go index 52170378..b9de04e3 100644 --- a/modules/mesh-grants/internal/grants/firstviews.go +++ b/modules/mesh-grants/internal/grants/firstviews.go @@ -14,8 +14,9 @@ import ( occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/keys" "git.novox.be/novox/mesh-sdk/go/views" ) @@ -276,24 +277,24 @@ func (v *Views) HearVerdict(ctx context.Context, event string, data []byte, logf if f == nil { return nil } - var verdict delivery.ManifestVerdict + var verdict meshcatalog.VerdictsByDigestValue var digest string var revision uint64 switch event { - case delivery.ManifestAcceptedEvent: - var a delivery.ManifestAccepted + case meshcatalog.EventManifestAccepted: + var a meshcatalog.ManifestAcceptedEvent if err := json.Unmarshal(data, &a); err != nil { return fmt.Errorf("%s does not read: %w", event, err) } - verdict = delivery.ManifestVerdict{V: delivery.Version, Module: a.Module, State: delivery.VerdictAccepted, Judge: a.Judge} - digest, revision = a.Digest, a.Revision - case delivery.ManifestRefusedEvent: - var r delivery.ManifestRefused + verdict = meshcatalog.VerdictsByDigestValue{V: keys.Version, Module: a.Module, State: meshcatalogclient.VerdictAccepted, Judge: a.Judge} + digest, revision = a.Digest, uint64(a.Revision) + case meshcatalog.EventManifestRefused: + var r meshcatalog.ManifestRefusedEvent if err := json.Unmarshal(data, &r); err != nil { return fmt.Errorf("%s does not read: %w", event, err) } - verdict = delivery.ManifestVerdict{V: delivery.Version, Module: r.Module, State: delivery.VerdictRefused, Judge: r.Judge, Problems: r.Problems} - digest, revision = r.Digest, r.Revision + verdict = meshcatalog.VerdictsByDigestValue{V: keys.Version, Module: r.Module, State: meshcatalogclient.VerdictRefused, Judge: r.Judge, Problems: r.Problems} + digest, revision = r.Digest, uint64(r.Revision) default: return fmt.Errorf("%q is not a verdict event of the indexer", event) } @@ -306,7 +307,7 @@ func (v *Views) HearVerdict(ctx context.Context, event string, data []byte, logf if revision == 0 { return gap(fmt.Sprintf("the indexer's %s of %s carries no revision", event, verdict.Module)) } - key, err := delivery.VerdictsKey(digest) + key, err := meshcatalogclient.VerdictsKey(digest) if err != nil { if revision <= held.Revision { return nil @@ -318,7 +319,7 @@ func (v *Views) HearVerdict(ctx context.Context, event string, data []byte, logf // holds for its manifest is passed over, so a consumer's backlog after a restart asks nothing. if revision < held.Revision { if raw, found := f.Get(key); found { - if was, err := delivery.DecodeManifestVerdict(raw); err == nil && sameVerdict(was, verdict) { + if was, err := meshcatalogclient.DecodeVerdict(raw); err == nil && sameVerdict(was, verdict) { return nil } } @@ -337,7 +338,7 @@ func (v *Views) HearVerdict(ctx context.Context, event string, data []byte, logf } // sameVerdict says two verdicts of one manifest say the same: its module, accepted or refused, the judge and the problems. -func sameVerdict(a, b delivery.ManifestVerdict) bool { +func sameVerdict(a, b meshcatalog.VerdictsByDigestValue) bool { return a.Module == b.Module && a.State == b.State && a.Judge == b.Judge && slices.Equal(a.Problems, b.Problems) } diff --git a/modules/mesh-grants/internal/grants/follow.go b/modules/mesh-grants/internal/grants/follow.go index b69a8905..40a71a0a 100644 --- a/modules/mesh-grants/internal/grants/follow.go +++ b/modules/mesh-grants/internal/grants/follow.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" ) // Watcher watches another part's state: changed hears each change after what stands, ended hears the watch end. A watch @@ -92,7 +92,7 @@ func (f *Follower) heard(state, key string, value []byte) bool { if state != ReadHave { return true } - node, module, isNode, err := delivery.ParseHaveKey(key) + node, module, isNode, err := nodeengineclient.ParseHaveKey(key) if err != nil { return false } diff --git a/modules/mesh-grants/internal/grants/grants.go b/modules/mesh-grants/internal/grants/grants.go index e9a03c65..ff296bf1 100644 --- a/modules/mesh-grants/internal/grants/grants.go +++ b/modules/mesh-grants/internal/grants/grants.go @@ -9,7 +9,10 @@ import ( occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate" + "git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay" + "git.novox.be/novox/mesh-sdk/go/keys" "mesh-grants/internal/buscompose" ) @@ -250,7 +253,7 @@ func (p *Part) loadedContents(in Inputs, read []User, readErr error) (map[string switch { case l == nil: return nil, "the bus node says no loaded list, and nats_users: " + readErr.Error() - case l.Source != delivery.GrantsUserListSource || l.Follows != delivery.FollowGrants || l.Error != "": + case l.Source != grantsstate.GrantsUserListSource || l.Follows != grantsstate.FollowGrants || l.Error != "": return nil, "the bus loaded no list of the grants part's (source " + l.Source + ", follows " + l.Follows + "), and nats_users: " + readErr.Error() } if kept, ok := p.sealedGrantsOf(l.Digest); ok { @@ -267,7 +270,7 @@ const GrantMissingCondition = "mesh-grants.grant-missing" // in the vault (drop). **The fingerprint is the indexer's, never a commit's** (issue 1126: link2pay, snake, f1-circuits // and txt-game, whose source commit has not moved since a build of the old form, have a done key of the new form at it, // and that is the fingerprint granted). Pure. -func MissingGrants(in Inputs, want map[string]delivery.Grant, drop []string, skip map[string]bool) []string { +func MissingGrants(in Inputs, want map[string]grantsstate.Grant, drop []string, skip map[string]bool) []string { placed := map[string]bool{} for _, ms := range in.Assigned { for _, m := range ms { @@ -279,11 +282,11 @@ func MissingGrants(in Inputs, want map[string]delivery.Grant, drop []string, ski // A module the mesh provides itself (the private network) is built by no repository, so the indexer holds no // fingerprint of it and the assigner wants none: the controller composes it with the node's own. It needs no grant, // and naming it missing raised a warning nothing could clear (sweep 2026-10-30 item 6). - if delivery.ProvidedByMesh(module) { + if overlay.ProvidedByMesh(module) { continue } fp, has := in.Fingerprints[module] - key, err := delivery.PartGrantKey(module, fp) + key, err := grantsstate.PartGrantKey(module, fp) if has && err == nil { if _, granted := want[key]; granted { continue @@ -348,7 +351,7 @@ func (p *Part) sayMissing(missing []string) { // GrantsOf is the grant of every module at the fingerprint the indexer's trunk names for it, by key, and the keys that // have none (drop): see the comment above. A module in skip (its manifest was refused) is neither. Pure. -func GrantsOf(in Inputs, recs buscompose.Records, skip map[string]bool) (map[string]delivery.Grant, []string, error) { +func GrantsOf(in Inputs, recs buscompose.Records, skip map[string]bool) (map[string]grantsstate.Grant, []string, error) { principals, err := buscompose.Users(recs) if err != nil { return nil, nil, err @@ -364,13 +367,13 @@ func GrantsOf(in Inputs, recs buscompose.Records, skip map[string]bool) (map[str } accounts[p.Module] = append(accounts[p.Module], p.Username()) } - out := map[string]delivery.Grant{} + out := map[string]grantsstate.Grant{} var drop []string for module, fp := range in.Fingerprints { - if skip[module] || !strings.HasPrefix(fp, delivery.SourcePrefix2) { + if skip[module] || !strings.HasPrefix(fp, meshcatalogclient.SourcePrefix2) { continue } - key, err := delivery.PartGrantKey(module, fp) + key, err := grantsstate.PartGrantKey(module, fp) if err != nil { continue } @@ -380,7 +383,7 @@ func GrantsOf(in Inputs, recs buscompose.Records, skip map[string]bool) (map[str continue } sort.Strings(names) - out[key] = delivery.Grant{Accounts: names} + out[key] = grantsstate.Grant{Accounts: names} } sort.Strings(drop) return out, drop, nil @@ -427,7 +430,7 @@ func (p *Part) writeGrants(res *Result, in Inputs, recs buscompose.Records, comp } for _, key := range sortedKeys(want) { g := want[key] - data, err := delivery.EncodeGrant(g) + data, err := grantsstate.EncodeGrant(g) if err != nil { res.Errors = append(res.Errors, fmt.Sprintf("the grant %s cannot be encoded: %v", key, err)) continue @@ -436,7 +439,7 @@ func (p *Part) writeGrants(res *Result, in Inputs, recs buscompose.Records, comp res.Errors = append(res.Errors, fmt.Sprintf("its own %s %s cannot be read: %v", StateGrants, key, err)) continue } else if found { - if was, err := delivery.DecodeGrant(raw); err == nil && delivery.SameGrant(was, withVersion(g)) { + if was, err := grantsstate.DecodeGrant(raw); err == nil && grantsstate.SameGrant(was, withVersion(g)) { continue } } @@ -449,7 +452,7 @@ func (p *Part) writeGrants(res *Result, in Inputs, recs buscompose.Records, comp continue } done.Written = append(done.Written, key) - module, fp, _ := delivery.ParsePartGrantKey(key) + module, fp, _ := grantsstate.ParsePartGrantKey(key) p.say(res, EventGrantWritten, map[string]any{"module": module, "fingerprint": fp, "accounts": g.Accounts}) } for _, key := range drop { @@ -464,7 +467,7 @@ func (p *Part) writeGrants(res *Result, in Inputs, recs buscompose.Records, comp } } -func withVersion(g delivery.Grant) delivery.Grant { - g.V = delivery.Version +func withVersion(g grantsstate.Grant) grantsstate.Grant { + g.V = keys.Version return g } diff --git a/modules/mesh-grants/internal/grants/inputs.go b/modules/mesh-grants/internal/grants/inputs.go index 7b7a6074..d1409733 100644 --- a/modules/mesh-grants/internal/grants/inputs.go +++ b/modules/mesh-grants/internal/grants/inputs.go @@ -17,9 +17,20 @@ import ( "sync" "time" + "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient" + "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshnodeenroller" + "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault" + "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" + "git.novox.be/novox/mesh-networking/modules/mesh-wireguard/go/overlay" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" + "mesh-grants/internal/buscompose" ) // The states of other parts the grants part reads, as its manifest's reads name them (`.`). @@ -37,13 +48,13 @@ const ( ReadAccounts = "mesh-vault.bus-accounts" // ReadPlacement is the seat mesh-placement's placement (ADR 0567 §1, §9): each node's modules and the seats // they hold. - ReadPlacement = delivery.PlacementRead + ReadPlacement = meshmoduleassignerclient.PlacementRead // ReadEnrolments is the seat mesh-enrolment's open enrolments, read as a named reader (ADR 0587 §2, §4): a key at // live or redeeming composes the user enrol.. - ReadEnrolments = delivery.EnrolmentsRead + ReadEnrolments = buscompose.EnrolmentsRead // ReadNodeLogins is the vault's node-logins, read as its named reader (ADR 0583 §1 insight): each node's login in use // and the one rotating, both composed as users of the node's permissions. - ReadNodeLogins = delivery.NodeLoginsRead + ReadNodeLogins = buscompose.NodeLoginsRead ) // Reads is every state the part reads, in the manifest's order. Not the controller's assignments: the seat's placement @@ -122,7 +133,7 @@ type Inputs struct { Assigned map[string][]string // Placed is each node's placement as the seat mesh-placement holds it (ADR 0567 §1); empty before any is read. // Where it is, a seat held once for the mesh is held where placement says, not wherever a module claiming it runs. - Placed map[string]delivery.Placement + Placed map[string]meshmoduleassigner.Placement // Manifests is each module's manifest at the indexer's trunk, by module. Manifests map[string]catalogue.Manifest // Bound is, by module, the streams of each bus-state entry the bus module said it created and has not dropped, from @@ -143,7 +154,7 @@ type Inputs struct { Values map[string]any // BusNode is the machine running the bus's module, and Loaded what its node agent says it loaded. BusNode string - Loaded *delivery.UserListWritten + Loaded *nodeengine.UserListWritten // Hashes are the vault's hashes of the bus accounts, by bus user (ReadAccounts); kept in memory, never written. Hashes map[string]HeldHash // Enrolling is every node whose enrolment key is at live or redeeming (ReadEnrolments, ADR 0587 §4, §5), sorted: one @@ -151,7 +162,7 @@ type Inputs struct { Enrolling []string // NodeLogins is each node's key of the vault's node-logins (ReadNodeLogins, ADR 0583 §1); a node absent logs in as // node. alone. - NodeLogins map[string]delivery.NodeLogin + NodeLogins map[string]meshvault.NodeLoginsByNodeValue // Refused is every manifest the indexer holds that the part cannot read or mesh-sdk's reader refuses, assigned or // not, each with why: its module counts as placed nowhere, so it is said loudly, never passed over (issue 1945). Refused []Gap @@ -257,13 +268,13 @@ func (c *Manifests) get(r Reader, digest string) (catalogue.Manifest, error) { if !found { return catalogue.Manifest{}, fmt.Errorf("the indexer holds no manifest %s", digest) } - v, err := delivery.DecodeManifest(raw) + v, err := meshcatalogclient.DecodeManifest(raw) if err != nil { return catalogue.Manifest{}, err } // Decoded, never judged (novox/hq ADR 0578 §3): the indexer's judge judged it, and its verdict is taken where the // module's value is read (Inputs); a field this part does not know is set aside. - m, err = catalogue.Decode([]byte(v.JSON)) + m, err = catalogue.Decode([]byte(v.Manifest)) if err != nil { return catalogue.Manifest{}, fmt.Errorf("the manifest %s does not decode: %w", digest, err) } @@ -312,7 +323,7 @@ func ReadInputs(r Reader, cache *Manifests) Inputs { if keys, err := r.Keys(ReadPlacement); err != nil { in.Gaps = append(in.Gaps, Gap{Input: ReadPlacement, Why: "refused or absent: nothing is placed (" + err.Error() + ")", Failed: true}) } else { - // Each node's keys in either form, assembled by the seat's one reader (delivery.PlacementsOf; research 072 + // Each node's keys in either form, assembled by the seat's one reader (meshmoduleassignerclient.PlacementsOf; research 072 // row 37): its node key and a key per module, or its legacy key `placement.`. values := map[string][]byte{} for _, k := range keys { @@ -320,26 +331,26 @@ func ReadInputs(r Reader, cache *Manifests) Inputs { if err != nil || !found { continue } - if _, _, _, err := delivery.ParsePlacementKey(k); err != nil { + if _, _, _, err := meshmoduleassignerclient.ParseKey(k); err != nil { in.Gaps = append(in.Gaps, Gap{Input: ReadPlacement + " " + k, Why: "not a placement key, so it places nothing"}) continue } values[k] = raw } - placed, err := delivery.PlacementsOf(values) + placed, err := meshmoduleassignerclient.PlacementsOf(values) if err != nil { in.Gaps = append(in.Gaps, Gap{Input: ReadPlacement, Why: "a key does not read, so its node is composed from what does: " + err.Error()}) } for node, p := range placed { if in.Placed == nil { - in.Placed = map[string]delivery.Placement{} + in.Placed = map[string]meshmoduleassigner.Placement{} } in.Placed[node] = p } } for node, p := range in.Placed { nodes[node] = true - for _, m := range p.Assigned() { + for _, m := range meshmoduleassignerclient.Assigned(p) { if !slices.Contains(in.Assigned[node], m) { in.Assigned[node] = append(in.Assigned[node], m) } @@ -356,7 +367,7 @@ func ReadInputs(r Reader, cache *Manifests) Inputs { in.Gaps = append(in.Gaps, Gap{Input: ReadHave, Why: "refused or absent: " + err.Error(), Failed: true}) } for _, k := range haveKeys { - if node, _, isNode, err := delivery.ParseHaveKey(k); err == nil && isNode { + if node, _, isNode, err := nodeengineclient.ParseHaveKey(k); err == nil && isNode { nodes[node] = true } } @@ -369,7 +380,7 @@ func ReadInputs(r Reader, cache *Manifests) Inputs { if raw, found, err := r.Get(ReadHave, in.BusNode+"."+BusModule); err != nil { in.Gaps = append(in.Gaps, Gap{Input: "the loaded list's have", Why: err.Error(), Failed: true}) } else if found { - if h, err := delivery.DecodeHave(raw); err == nil { + if h, err := nodeengineclient.DecodeHave(raw); err == nil { in.Loaded = h.UserList } } @@ -404,12 +415,12 @@ func ReadInputs(r Reader, cache *Manifests) Inputs { if !found { continue } - v, err := delivery.DecodeModule(raw) + v, err := meshcatalogclient.DecodeModule(raw) if err == nil && v.Manifest == "" { err = fmt.Errorf("the indexer's value names no manifest digest") } - if err == nil && strings.HasPrefix(v.FP2, delivery.SourcePrefix2) { - in.Fingerprints[module] = v.FP2 + if err == nil && strings.HasPrefix(v.Fp2, meshcatalogclient.SourcePrefix2) { + in.Fingerprints[module] = v.Fp2 } else if err == nil { why := v.None2 if why == "" { @@ -420,8 +431,8 @@ func ReadInputs(r Reader, cache *Manifests) Inputs { var m catalogue.Manifest if err == nil { // The indexer's refusal, or its wait for a judge that knows a field, carried in its value (ADR 0578 §3): held and named as a refused manifest. - if verdict := v.VerdictOf(module); verdict != nil && verdict.Held() { - err = errors.New(verdict.Why()) + if verdict := meshcatalogclient.ModuleVerdict(module, v); verdict != nil && meshcatalogclient.VerdictHeld(*verdict) { + err = errors.New(meshcatalogclient.VerdictWhy(*verdict)) } else if verdict == nil { // The indexer writes a changed manifest's value before it judges it, and a new digest has no verdict: until // the verdict is written the manifest is held (ADR 0614 §8). @@ -447,11 +458,11 @@ func ReadInputs(r Reader, cache *Manifests) Inputs { // A module the mesh provides itself has no repository the indexer reads: its manifest comes with mesh-sdk, and the // controller's Provide overwrites any row of it at every start, so the provided one always wins here too (issue 1945, // the private network on every machine). An indexer manifest of the same name is said as a gap, never used. - for _, name := range delivery.ProvidedModules() { + for _, name := range overlay.ProvidedModules() { if !slices.Contains(modules, name) { continue } - m, raw, err := delivery.ProvidedManifest(name) + m, raw, err := overlay.ProvidedManifest(name) in.Gaps = slices.DeleteFunc(in.Gaps, func(g Gap) bool { return g.Input == "manifest of "+name }) in.Refused = slices.DeleteFunc(in.Refused, func(g Gap) bool { return g.Input == "manifest of "+name }) if _, held := in.Digests[name]; held { @@ -477,19 +488,19 @@ func ReadInputs(r Reader, cache *Manifests) Inputs { // What each machine is wanted to run: the want's fingerprint, the builder's build of it, the manifest it built. var fromTrunk []string for key, raw := range each(r, ReadWants, &in.Gaps) { - node, module, err := delivery.ParseWantKey(key) + node, module, err := meshwantsclient.ParseWantKey(key) if err != nil || !slices.Contains(in.Assigned[node], module) { continue } - w, err := delivery.DecodeWant(raw) + w, err := meshwantsclient.DecodeWant(raw) if err != nil { fromTrunk = append(fromTrunk, node+"."+module) continue } - if w.Removed || w.FP == "" { + if w.Removed || w.Fp == "" { continue } - bk, err := delivery.BuilderBuildKey(module, w.FP) + bk, err := builds.BuilderBuildKey(module, w.Fp) if err != nil { continue } @@ -498,7 +509,7 @@ func ReadInputs(r Reader, cache *Manifests) Inputs { fromTrunk = append(fromTrunk, node+"."+module) continue } - b, err := delivery.DecodeBuilderBuild(braw) + b, err := builds.DecodeBuilderBuild(braw) if err != nil || b.Manifest == "" { fromTrunk = append(fromTrunk, node+"."+module) continue @@ -560,7 +571,7 @@ func readEnrolling(r Reader, gaps *[]Gap) ([]string, bool) { } var out []string for _, k := range keys { - node, isNode := delivery.EnrolmentKeyNode(k) + node, isNode := meshnodeenroller.ParseEnrolmentsByNodeNodeKey(k) if !isNode { continue } @@ -572,13 +583,13 @@ func readEnrolling(r Reader, gaps *[]Gap) ([]string, bool) { if !found { continue } - e, err := delivery.DecodeEnrolment(raw) + e, err := meshnodeenroller.DecodeEnrolmentsByNodeNodeValue(raw) if err != nil || e.Node != node { *gaps = append(*gaps, Gap{Input: ReadEnrolments + " " + k, Why: "does not read as the enrolment of " + node + ", so it composes no user"}) continue } - if e.Stage == delivery.EnrolmentLive || e.Stage == delivery.EnrolmentRedeeming { + if e.Stage == buscompose.EnrolmentLive || e.Stage == buscompose.EnrolmentRedeeming { out = append(out, node) } } @@ -590,13 +601,13 @@ func readEnrolling(r Reader, gaps *[]Gap) ([]string, bool) { // never a failed read: every node then logs in as node. alone, as before any rotation. A key that does not read is // named and leaves its node at node.; a read that fails is a failed read, since a rotating node's new login would be // left out. -func readNodeLogins(r Reader, gaps *[]Gap) map[string]delivery.NodeLogin { +func readNodeLogins(r Reader, gaps *[]Gap) map[string]meshvault.NodeLoginsByNodeValue { keys, err := r.Keys(ReadNodeLogins) if err != nil { *gaps = append(*gaps, Gap{Input: ReadNodeLogins, Why: "not read yet: every node logs in as node. (" + err.Error() + ")"}) return nil } - out := map[string]delivery.NodeLogin{} + out := map[string]meshvault.NodeLoginsByNodeValue{} for _, node := range keys { raw, found, err := r.Get(ReadNodeLogins, node) if err != nil { @@ -606,7 +617,7 @@ func readNodeLogins(r Reader, gaps *[]Gap) map[string]delivery.NodeLogin { if !found { continue } - l, err := delivery.DecodeNodeLogin(raw) + l, err := meshvaultclient.DecodeNodeLogin(raw) if err != nil || l.Node != node { *gaps = append(*gaps, Gap{Input: ReadNodeLogins + " " + node, Why: "does not read as the logins of " + node + ", so it logs in as node." + node}) @@ -622,7 +633,7 @@ func readNodeLogins(r Reader, gaps *[]Gap) map[string]delivery.NodeLogin { func (in Inputs) Rotating() map[string]bool { out := map[string]bool{} for _, l := range in.NodeLogins { - if l.Rotates() { + if meshvaultclient.Rotates(l) { out[l.Login], out[l.Rotating] = true, true } } @@ -680,11 +691,11 @@ func heldManifests(r Reader, in Inputs, cache *Manifests) map[string]catalogue.M if err != nil || !found { continue } - h, err := delivery.DecodeHave(raw) - if err != nil || h.FP == "" { + h, err := nodeengineclient.DecodeHave(raw) + if err != nil || h.Fp == "" { continue } - bk, err := delivery.BuilderBuildKey(module, h.FP) + bk, err := builds.BuilderBuildKey(module, h.Fp) if err != nil { continue } @@ -692,7 +703,7 @@ func heldManifests(r Reader, in Inputs, cache *Manifests) map[string]catalogue.M if err != nil || !found { continue } - b, err := delivery.DecodeBuilderBuild(braw) + b, err := builds.DecodeBuilderBuild(braw) if err != nil || b.Manifest == "" { continue } @@ -724,7 +735,7 @@ func (in Inputs) declared() map[string]catalogue.Manifest { // noVerdictYet is nil when the indexer's verdicts state holds an accepted verdict on the manifest, and the reason it is held // when it holds none yet, cannot be read, or refuses it. func noVerdictYet(r Reader, digest string) error { - key, err := delivery.VerdictsKey(digest) + key, err := meshcatalogclient.VerdictsKey(digest) if err != nil { return nil } @@ -735,12 +746,12 @@ func noVerdictYet(r Reader, digest string) error { if !found { return errors.New("the indexer has written no verdict on this manifest yet") } - v, err := delivery.DecodeManifestVerdict(raw) + v, err := meshcatalogclient.DecodeVerdict(raw) if err != nil { return fmt.Errorf("its verdict does not read: %w", err) } - if v.Held() { - return errors.New(v.Why()) + if meshcatalogclient.VerdictHeld(v) { + return errors.New(meshcatalogclient.VerdictWhy(v)) } return nil } diff --git a/modules/mesh-grants/internal/grants/part.go b/modules/mesh-grants/internal/grants/part.go index 7fe5eb05..5b727aee 100644 --- a/modules/mesh-grants/internal/grants/part.go +++ b/modules/mesh-grants/internal/grants/part.go @@ -13,8 +13,9 @@ import ( "sync" "time" + "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate" "git.novox.be/novox/mesh-sdk/go/membership" "mesh-grants/internal/buscompose" ) @@ -25,7 +26,7 @@ const ( StateMemberships = "memberships" StateAssertions = "assertions" // StateGrants is the part's grants by module and fingerprint, the key layout of the controller's (grants.go). - StateGrants = delivery.PartGrantsState + StateGrants = grantsstate.PartGrantsState KeyList = "list" // KeyPass is the last pass's result, and KeyAsked a tool's ask to compose again now, both in StateUserList. KeyPass = "pass" @@ -72,9 +73,9 @@ type UserListState struct { Digest string `json:"digest"` Users int `json:"users"` // LeftOut are the assignments the shadow list leaves out, each with why. - LeftOut []Gap `json:"left_out"` - Compare Comparison `json:"compare"` - Loaded *delivery.UserListWritten `json:"loaded,omitempty"` + LeftOut []Gap `json:"left_out"` + Compare Comparison `json:"compare"` + Loaded *nodeengine.UserListWritten `json:"loaded,omitempty"` // Credentials says per user whether the vault holds its hash and how its line would differ (ADR 0547 clause 9). Credentials Credentials `json:"credentials"` BusNode string `json:"bus_node,omitempty"` diff --git a/modules/mesh-grants/internal/grants/records.go b/modules/mesh-grants/internal/grants/records.go index cd5c06ea..64976b51 100644 --- a/modules/mesh-grants/internal/grants/records.go +++ b/modules/mesh-grants/internal/grants/records.go @@ -7,8 +7,8 @@ import ( "slices" "strings" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" "mesh-grants/internal/buscompose" ) @@ -258,7 +258,7 @@ func bucketsFor(m catalogue.Manifest, nodes []string) []buscompose.Bucket { if m.Module != buscompose.EngineModule { return out } - out = slices.DeleteFunc(out, func(b buscompose.Bucket) bool { return b.Name == delivery.ComposedState }) + out = slices.DeleteFunc(out, func(b buscompose.Bucket) bool { return b.Name == buscompose.ComposedState }) return append(out, buscompose.EngineComposedBuckets(nodes)...) } @@ -415,8 +415,8 @@ func hearsOf(m catalogue.Manifest, declared map[string]catalogue.Manifest, node // module claiming it wherever it is placed, as it was before placement: the seeded placements hold only the seats the // controller had on record (twelve on the control-node), and treating their holds as every holder left nine seats with // no holder in the user list of 2026-10-27 09:21 (issue 1945). A seat of every node is held wherever the module runs. -func heldWherePlaced(holds []buscompose.Seat, placed map[string]delivery.Placement, node, module string) []buscompose.Seat { - same := func(x delivery.Holding, h buscompose.Seat) bool { +func heldWherePlaced(holds []buscompose.Seat, placed map[string]meshmoduleassigner.Placement, node, module string) []buscompose.Seat { + same := func(x meshmoduleassigner.Holding, h buscompose.Seat) bool { return (x.Scope == "" || x.Scope == catalogue.ScopeMesh) && sameSeat(x.Claim, h.Name) && x.Kind == h.Kind } var out []buscompose.Seat @@ -427,12 +427,12 @@ func heldWherePlaced(holds []buscompose.Seat, placed map[string]delivery.Placeme } onRecord := false for _, p := range placed { - if slices.ContainsFunc(p.Holds, func(x delivery.Holding) bool { return same(x, h) }) { + if slices.ContainsFunc(p.Holds, func(x meshmoduleassigner.Holding) bool { return same(x, h) }) { onRecord = true break } } - if !onRecord || slices.ContainsFunc(placed[node].Holds, func(x delivery.Holding) bool { return same(x, h) && x.Module == module }) { + if !onRecord || slices.ContainsFunc(placed[node].Holds, func(x meshmoduleassigner.Holding) bool { return same(x, h) && x.Module == module }) { out = append(out, h) } } diff --git a/modules/mesh-grants/internal/grants/rootfree.go b/modules/mesh-grants/internal/grants/rootfree.go index 83bc8626..72931a69 100644 --- a/modules/mesh-grants/internal/grants/rootfree.go +++ b/modules/mesh-grants/internal/grants/rootfree.go @@ -9,7 +9,7 @@ import ( "sort" "strings" - "git.novox.be/novox/mesh-sdk/go/health" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" "mesh-grants/internal/buscompose" ) @@ -24,8 +24,8 @@ const CallRootFree = "seat:node-engine.root-free" // since the controller ended by its unassignment (novox/hq ADR 0590 §3, which ends ADR 0564 §9's selection by the // take-over key). const ( - EventKeeperRaised = health.KeeperModule + ".condition-raised" - EventKeeperCleared = health.KeeperModule + ".condition-cleared" + EventKeeperRaised = meshconditions.Seat + ".condition-raised" + EventKeeperCleared = meshconditions.Seat + ".condition-cleared" // KindRootNotFree is the condition kind whose events wake a pass. KindRootNotFree = "root-not-free" ) @@ -33,7 +33,7 @@ const ( // FromKeeper says a condition event heard is the keeper's, by its name (`.`) or its bus subject // (`mesh.seat..event.`). func FromKeeper(event string) bool { - return strings.HasPrefix(event, health.KeeperModule+".") || strings.HasPrefix(event, "mesh.seat."+health.KeeperModule+".event.") + return strings.HasPrefix(event, meshconditions.Seat+".") || strings.HasPrefix(event, "mesh.seat."+meshconditions.Seat+".event.") } // EventAccountIssued is the vault's word that it minted an account (novox/hq ADR 0561 §1): its hash is in the vault's diff --git a/modules/mesh-grants/internal/grants/sealed.go b/modules/mesh-grants/internal/grants/sealed.go index 20668056..a85b0510 100644 --- a/modules/mesh-grants/internal/grants/sealed.go +++ b/modules/mesh-grants/internal/grants/sealed.go @@ -11,7 +11,7 @@ import ( occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient" oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate" "mesh-grants/internal/buscompose" ) @@ -22,8 +22,8 @@ import ( // the users left out for want of a hash. The key always says FollowGrants: the bus node follows this list alone since the // controller ended by its unassignment (ADR 0590, issue 1126). Only the process writes them (Objects); a tool asks it. -// KeySealed is the key of the state user-list the bus node reads (delivery.GrantsUserListKey). -const KeySealed = delivery.GrantsUserListKey +// KeySealed is the key of the state user-list the bus node reads (grantsstate.GrantsUserListKey). +const KeySealed = grantsstate.GrantsUserListKey // Objects is the part's object bucket of sealed lists: the process's connection; nil in the tools. type Objects interface { @@ -69,14 +69,14 @@ func ComposeList(r buscompose.Records, hashes map[string]HeldHash, refused map[s } // Sealed is the part's key `user-list.sealed` as written, and whether there is one. -func (p *Part) Sealed() (delivery.GrantsUserList, uint64, bool) { +func (p *Part) Sealed() (grantsstate.GrantsUserList, uint64, bool) { raw, rev, found, err := p.Store.Get(StateUserList, KeySealed) if err != nil || !found { - return delivery.GrantsUserList{}, 0, false + return grantsstate.GrantsUserList{}, 0, false } - g, err := delivery.DecodeGrantsUserList(raw) + g, err := grantsstate.DecodeGrantsUserList(raw) if err != nil { - return delivery.GrantsUserList{}, 0, false + return grantsstate.GrantsUserList{}, 0, false } return g, rev, true } @@ -88,12 +88,12 @@ func (p *Part) writeSealed(res *Result, list []byte, users int, leftOut, dropped next := cur // The bus node follows this list alone (ADR 0571 clause 8): always since the controller ended by its unassignment // (novox/hq ADR 0590, issue 1126). - next.Follow = delivery.FollowGrants + next.Follow = grantsstate.FollowGrants key := "" if in.Loaded != nil { key = in.Loaded.Key } - digest := delivery.UserListDigest(list) + digest := grantsstate.UserListDigest(list) why, held := "", "" switch { case p.Objects == nil: @@ -110,10 +110,10 @@ func (p *Part) writeSealed(res *Result, list []byte, users int, leftOut, dropped why = p.holdsBack(in, leftOut, dropped, bus, lost, digest) held = why p.keepHashHold(res, digest) - case cur.Digest != digest || cur.SealedTo != delivery.SealingKeyID(key) || cur.Node != in.BusNode: - object, err := delivery.SealUserList(list, key) + case cur.Digest != digest || cur.SealedTo != grantsstate.SealingKeyID(key) || cur.Node != in.BusNode: + object, err := grantsstate.SealUserList(list, key) if err == nil { - err = p.Objects.PutObject(delivery.UserListObjectName(digest), object) + err = p.Objects.PutObject(grantsstate.UserListObjectName(digest), object) } if err != nil { res.Errors = append(res.Errors, "the sealed user list could not be put: "+err.Error()) @@ -123,7 +123,7 @@ func (p *Part) writeSealed(res *Result, list []byte, users int, leftOut, dropped if cur.Digest != digest { next.Revision++ } - next.Digest, next.Object, next.SealedTo, next.Node = digest, delivery.UserListObjectName(digest), delivery.SealingKeyID(key), in.BusNode + next.Digest, next.Object, next.SealedTo, next.Node = digest, grantsstate.UserListObjectName(digest), grantsstate.SealingKeyID(key), in.BusNode } p.sayHeld(held) if next.Digest == digest { @@ -137,7 +137,7 @@ func (p *Part) writeSealed(res *Result, list []byte, users int, leftOut, dropped return } next.At = p.now() - data, err := delivery.EncodeGrantsUserList(next) + data, err := grantsstate.EncodeGrantsUserList(next) if err == nil { _, err = p.Store.Put(StateUserList, KeySealed, data) } @@ -192,7 +192,7 @@ func (p *Part) sayHeld(why string) { // HeldCondition is the key of the warning raised while a list in force is held back. const HeldCondition = "mesh-grants.user-list-held" -func sameSealed(a, b delivery.GrantsUserList) bool { +func sameSealed(a, b grantsstate.GrantsUserList) bool { // Its time is no change, and no user left out is the same written as [] or null: the key read back says [] for the // nil a pass composes, which read as a change wrote the key again at every pass, and each write moved the bus node's // report and woke the next pass (issue 1126: the shadow list's revision 99270 to 99357 in 90 s, 2026-10-28). @@ -210,11 +210,11 @@ func sameSealed(a, b delivery.GrantsUserList) bool { // SealedState is what a pass says of the sealed list: the key as written, and why nothing was written when nothing was. type SealedState struct { - Written *delivery.GrantsUserList `json:"written,omitempty"` - Why string `json:"why,omitempty"` + Written *grantsstate.GrantsUserList `json:"written,omitempty"` + Why string `json:"why,omitempty"` } -func sealedSays(g delivery.GrantsUserList, had bool, why string) *SealedState { +func sealedSays(g grantsstate.GrantsUserList, had bool, why string) *SealedState { s := &SealedState{Why: why} if had { s.Written = &g diff --git a/modules/mesh-grants/internal/grants/tools.go b/modules/mesh-grants/internal/grants/tools.go index 247d4081..90a0d177 100644 --- a/modules/mesh-grants/internal/grants/tools.go +++ b/modules/mesh-grants/internal/grants/tools.go @@ -7,7 +7,9 @@ import ( "slices" "strings" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate" "git.novox.be/novox/mesh-sdk/go/membership" "mesh-grants/internal/buscompose" ) @@ -42,14 +44,14 @@ const WritesGrants = "the grants part writes grants.. into its state // Grants answers the tool grants: the grants the part wrote, one module's or all. func (p *Part) Grants(module string) (any, error) { out := map[string]any{"writes": WritesGrants} - read := func(get func() ([]string, func(string) ([]byte, bool, error), error)) (map[string]delivery.Grant, error) { + read := func(get func() ([]string, func(string) ([]byte, bool, error), error)) (map[string]grantsstate.Grant, error) { keys, one, err := get() if err != nil { return nil, err } - m := map[string]delivery.Grant{} + m := map[string]grantsstate.Grant{} for _, k := range keys { - mod, _, err := delivery.ParsePartGrantKey(k) + mod, _, err := grantsstate.ParsePartGrantKey(k) if err != nil || (module != "" && mod != module) { continue } @@ -60,7 +62,7 @@ func (p *Part) Grants(module string) (any, error) { if !found { continue } - g, err := delivery.DecodeGrant(raw) + g, err := grantsstate.DecodeGrant(raw) if err != nil { return nil, fmt.Errorf("%s: %w", k, err) } @@ -80,10 +82,10 @@ func (p *Part) Grants(module string) (any, error) { Module string `json:"module"` Accounts []string `json:"accounts,omitempty"` } - list := func(m map[string]delivery.Grant) []grant { + list := func(m map[string]grantsstate.Grant) []grant { out := []grant{} for _, k := range sortedKeys(m) { - mod, _, _ := delivery.ParsePartGrantKey(k) + mod, _, _ := grantsstate.ParsePartGrantKey(k) out = append(out, grant{Key: k, Module: mod, Accounts: m[k].Accounts}) } return out @@ -245,7 +247,7 @@ func (p *Part) UserList() (any, error) { } if st.BusNode != "" { if raw, found, err := p.Read.Get(ReadHave, st.BusNode+"."+BusModule); err == nil && found { - if h, err := delivery.DecodeHave(raw); err == nil { + if h, err := nodeengineclient.DecodeHave(raw); err == nil { out["loaded_now"] = h.UserList } } @@ -269,7 +271,7 @@ func (p *Part) AssertOne(module string) (any, error) { if !found { return nil, fmt.Errorf("the indexer holds no module %s", module) } - v, err := delivery.DecodeModule(raw) + v, err := meshcatalogclient.DecodeModule(raw) if err != nil || v.Manifest == "" { return nil, fmt.Errorf("the indexer names no manifest of %s", module) } diff --git a/modules/mesh-grants/internal/grants/userlist.go b/modules/mesh-grants/internal/grants/userlist.go index c3ea7b3b..6ddd2ce7 100644 --- a/modules/mesh-grants/internal/grants/userlist.go +++ b/modules/mesh-grants/internal/grants/userlist.go @@ -10,8 +10,8 @@ import ( "sort" "strings" + "git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" "mesh-grants/internal/buscompose" ) @@ -397,7 +397,7 @@ func Placed(in Inputs) func(user string) bool { return true case parts[0] == "node" || parts[0] == "provisioner": // A node's second login, node.--b, is its node's (ADR 0583 §1). - return slices.Contains(in.Nodes, strings.TrimSuffix(parts[1], delivery.SecondLoginSuffix)) + return slices.Contains(in.Nodes, strings.TrimSuffix(parts[1], meshvaultclient.SecondLoginSuffix)) case parts[0] == "person" || parts[0] == "enrol": return true }