Node-engine: logins, signers, enrolment tokens and redeems, and the grants part's sealed user list come from the vault and node-enroller clients and the grants state package; no file imports go/delivery (ADR 0634 §1, §4; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 15:47:59 +01:00
parent 4bfa94dfef
commit 13e2fe14aa
22 changed files with 248 additions and 219 deletions
+38 -39
View File
@@ -5,15 +5,14 @@ import (
"encoding/json"
"errors"
"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/seal"
"github.com/novox/mesh-host/internal/agent"
engcomp "github.com/novox/mesh-host/internal/compositions"
"strings"
"sync"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/seal"
"github.com/novox/mesh-host/internal/agent"
)
const node = "novox"
@@ -126,50 +125,50 @@ func TestTheBusNodeWritesTheGrantsPartsUserListAlone(t *testing.T) {
// this node runs declares; the list a composition carries is never written, whatever becomes of the grants key.
// B17: the file's place is read from what runs, as the agent holds it, never from a composition bucket.
{name: "the grants part's list is written into the file the bus module it runs declares",
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: delivery.FollowGrants, content: three}},
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: grantsstate.FollowGrants, content: three}},
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
{name: "a bus module it runs by digest still names the file",
before: []put{{module: "nats", fp: fpOf("a"), content: two, byDigest: true}}, grants: []gput{{follow: delivery.FollowGrants, content: three}},
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
before: []put{{module: "nats", fp: fpOf("a"), content: two, byDigest: true}}, grants: []gput{{follow: grantsstate.FollowGrants, content: three}},
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
{name: "a newer composition's list is not written",
before: []put{{module: "nats", fp: fpOf("a"), content: two}},
after: []put{{module: "nats", fp: fpOf("b"), content: five}},
grants: []gput{{follow: delivery.FollowGrants, content: three}},
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
grants: []gput{{follow: grantsstate.FollowGrants, content: three}},
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
{name: "a composition without the list is ignored", before: []put{{module: "web", fp: fpOf("a"), content: ""}},
grants: []gput{{follow: delivery.FollowGrants, content: three}}},
grants: []gput{{follow: grantsstate.FollowGrants, content: three}}},
{name: "another node's composition declares no file",
before: []put{{module: "nats", fp: fpOf("a"), content: two, asNode: "ace"}}, grants: []gput{{follow: delivery.FollowGrants, content: three}}},
before: []put{{module: "nats", fp: fpOf("a"), content: two, asNode: "ace"}}, grants: []gput{{follow: grantsstate.FollowGrants, content: three}}},
{name: "an unreadable grants key with none heard before writes nothing, never the composition's list",
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{unreadable: true}},
onDisk: two, wantErr: "key is not heard", wantRefused: true, wantSource: delivery.GrantsUserListSource},
onDisk: two, wantErr: "key is not heard", wantRefused: true, wantSource: grantsstate.GrantsUserListSource},
{name: "a refused watch with no key kept writes nothing and says it is not watched",
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grantsRefused: true, wantUnwatched: true,
onDisk: two, wantErr: "key is not heard", wantRefused: true, wantSource: delivery.GrantsUserListSource},
onDisk: two, wantErr: "key is not heard", wantRefused: true, wantSource: grantsstate.GrantsUserListSource},
{name: "a refused watch keeps the key last heard",
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grantsRefused: true, wantUnwatched: true,
kept: &gput{follow: delivery.FollowGrants, content: three},
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
kept: &gput{follow: grantsstate.FollowGrants, content: three},
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
{name: "a grants key that turns unreadable keeps the loaded list",
before: []put{{module: "nats", fp: fpOf("a"), content: two}},
grants: []gput{{follow: delivery.FollowGrants, content: three}, {unreadable: true, after: true}},
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
grants: []gput{{follow: grantsstate.FollowGrants, content: three}, {unreadable: true, after: true}},
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
{name: "a grants key that is deleted keeps the loaded list",
before: []put{{module: "nats", fp: fpOf("a"), content: two}},
grants: []gput{{follow: delivery.FollowGrants, content: three}, {deleted: true, after: true}},
wantWrites: []string{three}, wantUsers: 4, wantSource: delivery.GrantsUserListSource, wantFollows: delivery.FollowGrants},
grants: []gput{{follow: grantsstate.FollowGrants, content: three}, {deleted: true, after: true}},
wantWrites: []string{three}, wantUsers: 4, wantSource: grantsstate.GrantsUserListSource, wantFollows: grantsstate.FollowGrants},
{name: "a grants list that drops more than half is refused and the file kept",
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: delivery.FollowGrants, content: one}},
onDisk: five, wantUsers: 2, wantErr: "drops more than half", wantRefused: true, wantSource: delivery.GrantsUserListSource},
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: grantsstate.FollowGrants, content: one}},
onDisk: five, wantUsers: 2, wantErr: "drops more than half", wantRefused: true, wantSource: grantsstate.GrantsUserListSource},
{name: "a failed write is said and kept", before: []put{{module: "nats", fp: fpOf("a"), content: two}},
grants: []gput{{follow: delivery.FollowGrants, content: three}}, failWrite: true,
wantWrites: []string{three}, wantUsers: 4, wantErr: "disk full", wantSource: delivery.GrantsUserListSource},
grants: []gput{{follow: grantsstate.FollowGrants, content: three}}, failWrite: true,
wantWrites: []string{three}, wantUsers: 4, wantErr: "disk full", wantSource: grantsstate.GrantsUserListSource},
{name: "a grants list that does not open to its digest is refused and the file kept",
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: delivery.FollowGrants, content: three, badDigest: true}},
onDisk: two, wantErr: "could not be had", wantRefused: true, wantSource: delivery.GrantsUserListSource},
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: grantsstate.FollowGrants, content: three, badDigest: true}},
onDisk: two, wantErr: "could not be had", wantRefused: true, wantSource: grantsstate.GrantsUserListSource},
{name: "a grants list sealed to another key is refused and the file kept",
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: delivery.FollowGrants, content: three, otherKey: true}},
onDisk: two, wantErr: "sealed to another key", wantRefused: true, wantSource: delivery.GrantsUserListSource},
before: []put{{module: "nats", fp: fpOf("a"), content: two}}, grants: []gput{{follow: grantsstate.FollowGrants, content: three, otherKey: true}},
onDisk: two, wantErr: "sealed to another key", wantRefused: true, wantSource: grantsstate.GrantsUserListSource},
} {
t.Run(c.name, func(t *testing.T) {
var mu sync.Mutex
@@ -194,7 +193,7 @@ func TestTheBusNodeWritesTheGrantsPartsUserListAlone(t *testing.T) {
}
for i, g := range gs {
if g.unreadable || g.deleted {
u := agent.Update{Key: delivery.GrantsUserListKey, Value: []byte("not a user-list key"), Revision: uint64(i + 1), Deleted: g.deleted}
u := agent.Update{Key: grantsstate.GrantsUserListKey, Value: []byte("not a user-list key"), Revision: uint64(i + 1), Deleted: g.deleted}
grantsAfter = append(grantsAfter, u)
continue
}
@@ -203,21 +202,21 @@ func TestTheBusNodeWritesTheGrantsPartsUserListAlone(t *testing.T) {
other, _ := seal.GenerateKeyPair()
to = other.PublicKey
}
digest := delivery.UserListDigest([]byte(g.content))
digest := grantsstate.UserListDigest([]byte(g.content))
if g.badDigest {
digest = delivery.UserListDigest([]byte("another list"))
digest = grantsstate.UserListDigest([]byte("another list"))
}
object, err := delivery.SealUserList([]byte(g.content), to)
object, err := grantsstate.SealUserList([]byte(g.content), to)
if err != nil {
t.Fatal(err)
}
sealed[delivery.UserListObjectName(digest)] = object
raw, err := delivery.EncodeGrantsUserList(delivery.GrantsUserList{Follow: g.follow, Digest: digest,
Object: delivery.UserListObjectName(digest), SealedTo: delivery.SealingKeyID(to), Node: node, Revision: uint64(i + 1)})
sealed[grantsstate.UserListObjectName(digest)] = object
raw, err := grantsstate.EncodeGrantsUserList(grantsstate.GrantsUserList{Follow: g.follow, Digest: digest,
Object: grantsstate.UserListObjectName(digest), SealedTo: grantsstate.SealingKeyID(to), Node: node, Revision: uint64(i + 1)})
if err != nil {
t.Fatal(err)
}
u := agent.Update{Key: delivery.GrantsUserListKey, Value: raw, Revision: uint64(i + 1)}
u := agent.Update{Key: grantsstate.GrantsUserListKey, Value: raw, Revision: uint64(i + 1)}
switch {
case c.kept != nil && i == len(gs)-1:
keptRaw = raw
@@ -296,7 +295,7 @@ func TestTheBusNodeWritesTheGrantsPartsUserListAlone(t *testing.T) {
}
if len(grantsAfter) > 0 {
for until := time.Now().Add(5 * time.Second); ; time.Sleep(5 * time.Millisecond) {
if _, st := fl.State(); st.Source == delivery.GrantsUserListSource || time.Now().After(until) {
if _, st := fl.State(); st.Source == grantsstate.GrantsUserListSource || time.Now().After(until) {
break
}
}
@@ -355,11 +354,11 @@ func TestTheBusNodeWritesTheGrantsPartsUserListAlone(t *testing.T) {
t.Fatalf("an apply while following %s holds the composition's list: %v", c.wantFollows, hasList)
}
// A list on the machine that cannot be read stops the apply; it is never left out to be removed.
if c.wantFollows == delivery.FollowGrants {
if c.wantFollows == grantsstate.FollowGrants {
readErr := New(Env{Node: node, OnDisk: func(json.RawMessage) ([]byte, error) {
return nil, errors.New("permission denied")
}})
readErr.grants = &delivery.GrantsUserList{Follow: delivery.FollowGrants}
readErr.grants = &grantsstate.GrantsUserList{Follow: grantsstate.FollowGrants}
if _, err := readErr.Hold(cmp); err == nil || !strings.Contains(err.Error(), "could not be read") {
t.Fatalf("an unreadable list on the machine was held as %v", err)
}