The wanted accounts' layout leaves mesh-sdk for the grants part's package grantsstate (ADR 0634 section 1, ADR 0635 section 4, issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 18:43:21 +01:00
parent 78a1b5648b
commit 29bcaf2be5
8 changed files with 50 additions and 28 deletions
+4 -2
View File
@@ -15,6 +15,8 @@ import (
"sort" "sort"
"strings" "strings"
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
"git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/buscompose"
) )
@@ -23,13 +25,13 @@ const toolRunnerModule = "node-tools"
// ledgersSecret is the vault's name of node's `ledgers`. // ledgersSecret is the vault's name of node's `ledgers`.
func ledgersSecret(node string) (string, error) { func ledgersSecret(node string) (string, error) {
return OwnSecret(node, toolRunnerModule, buscompose.LedgersSecret) return OwnSecret(node, toolRunnerModule, grantsstate.LedgersSecret)
} }
// isLedgersSecret says secret is a machine's `ledgers`: `own/<node>/node-tools/ledgers`. // isLedgersSecret says secret is a machine's `ledgers`: `own/<node>/node-tools/ledgers`.
func isLedgersSecret(secret string) bool { func isLedgersSecret(secret string) bool {
parts := strings.Split(secret, "/") parts := strings.Split(secret, "/")
return len(parts) == 4 && parts[0] == "own" && parts[2] == toolRunnerModule && parts[3] == buscompose.LedgersSecret return len(parts) == 4 && parts[0] == "own" && parts[2] == toolRunnerModule && parts[3] == grantsstate.LedgersSecret
} }
// toolRunnerNodes are the machines whose tool runner is wanted, sorted. // toolRunnerNodes are the machines whose tool runner is wanted, sorted.
@@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
"git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/buscompose"
"git.novox.be/novox/mesh-sdk/go/seal" "git.novox.be/novox/mesh-sdk/go/seal"
) )
@@ -12,14 +14,14 @@ import (
// ledger of ledger.go (ADR 0085), which is another thing. // ledger of ledger.go (ADR 0085), which is another thing.
// nodeToolsWant is the wanted account of a machine's tool runner, as the grants part writes it. // nodeToolsWant is the wanted account of a machine's tool runner, as the grants part writes it.
func nodeToolsWant(node string) buscompose.WantedAccount { func nodeToolsWant(node string) grantsstate.WantedAccount {
w := aWant("node-tools") w := aWant("node-tools")
w.Kind, w.Node, w.User = buscompose.KindNodeTools, node, node+".node-tools" w.Kind, w.Node, w.User = buscompose.KindNodeTools, node, node+".node-tools"
return w return w
} }
// ledgerWant is the wanted ledger account of a module on a machine. // ledgerWant is the wanted ledger account of a module on a machine.
func ledgerWant(node, module string) buscompose.WantedAccount { func ledgerWant(node, module string) grantsstate.WantedAccount {
w := aWant(module) w := aWant(module)
w.Kind, w.Node, w.User, w.Claims = buscompose.KindLedger, node, "ledger."+node+"."+module, nil w.Kind, w.Node, w.User, w.Claims = buscompose.KindLedger, node, "ledger."+node+"."+module, nil
return w return w
@@ -34,7 +36,7 @@ func ledgersOf(t *testing.T, v testVault, priv string) (map[string]json.RawMessa
t.Fatal(err) t.Fatal(err)
} }
for _, o := range own.Own { for _, o := range own.Own {
if o.Module == "node-tools" && o.Name == buscompose.LedgersSecret { if o.Module == "node-tools" && o.Name == grantsstate.LedgersSecret {
value, err := seal.Open(o.Sealed, priv) value, err := seal.Open(o.Sealed, priv)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
+14 -12
View File
@@ -32,11 +32,13 @@ import (
"sync" "sync"
"time" "time"
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
"git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/buscompose"
) )
// WantedRead is the grants part's state of wanted accounts, as the vault's manifest reads it. // WantedRead is the grants part's state of wanted accounts, as the vault's manifest reads it.
const WantedRead = buscompose.GrantsAccounts const WantedRead = grantsstate.GrantsAccounts
// AccountIssuedEvent is what the vault says when it minted an account (mesh-sdk buscompose.AccountIssued). // AccountIssuedEvent is what the vault says when it minted an account (mesh-sdk buscompose.AccountIssued).
const AccountIssuedEvent = buscompose.AccountIssuedEvent const AccountIssuedEvent = buscompose.AccountIssuedEvent
@@ -46,7 +48,7 @@ const AccountIssuedEvent = buscompose.AccountIssuedEvent
type minting struct { type minting struct {
mu sync.Mutex mu sync.Mutex
// wants are the wanted accounts, by user, as last heard. // wants are the wanted accounts, by user, as last heard.
wants map[string]buscompose.WantedAccount wants map[string]grantsstate.WantedAccount
// heldBack are the wanted users the vault did not mint, with why (vault_status). // heldBack are the wanted users the vault did not mint, with why (vault_status).
heldBack map[string]string heldBack map[string]string
// owed are the wanted users whose mint failed, tried again at the pass (MintOwed). // owed are the wanted users whose mint failed, tried again at the pass (MintOwed).
@@ -92,7 +94,7 @@ func (v *Vault) MintState() MintState {
m := &v.minting m := &v.minting
m.mu.Lock() m.mu.Lock()
out := MintState{On: true, Why: mintingWhy, Wanted: len(m.wants), HeldBack: map[string]string{}, Minted: []Minted{}} out := MintState{On: true, Why: mintingWhy, Wanted: len(m.wants), HeldBack: map[string]string{}, Minted: []Minted{}}
wants := map[string]buscompose.WantedAccount{} wants := map[string]grantsstate.WantedAccount{}
for u, why := range m.heldBack { for u, why := range m.heldBack {
out.HeldBack[u] = why out.HeldBack[u] = why
if w, ok := m.wants[u]; ok { if w, ok := m.wants[u]; ok {
@@ -113,7 +115,7 @@ func (v *Vault) MintState() MintState {
} }
// holdsAccount says the vault holds a bus account as w's own secret: a value that is an account. // holdsAccount says the vault holds a bus account as w's own secret: a value that is an account.
func (v *Vault) holdsAccount(w buscompose.WantedAccount) bool { func (v *Vault) holdsAccount(w grantsstate.WantedAccount) bool {
secret, err := AccountSecret(w) secret, err := AccountSecret(w)
if err != nil { if err != nil {
return false return false
@@ -172,7 +174,7 @@ func (v *Vault) busAccount(secret, value string) bool {
// AccountSecret is the vault's name of the own secret a wanted account is held in. // AccountSecret is the vault's name of the own secret a wanted account is held in.
// A ledger account is the exception: it is kept as LedgerAccountSecret, and its machine's `ledgers` is made of it. // A ledger account is the exception: it is kept as LedgerAccountSecret, and its machine's `ledgers` is made of it.
func AccountSecret(w buscompose.WantedAccount) (string, error) { func AccountSecret(w grantsstate.WantedAccount) (string, error) {
if w.Kind == buscompose.KindLedger { if w.Kind == buscompose.KindLedger {
return LedgerAccountSecret(w.Node, w.Module) return LedgerAccountSecret(w.Node, w.Module)
} }
@@ -186,7 +188,7 @@ func (v *Vault) WantHeard(user string, raw []byte, deleted bool) {
v.wantGone(user) v.wantGone(user)
return return
} }
w, err := buscompose.ReadWantedAccount(user, raw) w, err := grantsstate.ReadWantedAccount(user, raw)
if err != nil { if err != nil {
v.log("the wanted account %s is not read, so nothing is minted for it: %v", user, err) v.log("the wanted account %s is not read, so nothing is minted for it: %v", user, err)
v.holdBack(user, "its wanted key is not readable: "+err.Error()) v.holdBack(user, "its wanted key is not readable: "+err.Error())
@@ -194,7 +196,7 @@ func (v *Vault) WantHeard(user string, raw []byte, deleted bool) {
} }
v.minting.mu.Lock() v.minting.mu.Lock()
if v.minting.wants == nil { if v.minting.wants == nil {
v.minting.wants = map[string]buscompose.WantedAccount{} v.minting.wants = map[string]grantsstate.WantedAccount{}
} }
v.minting.wants[user] = w v.minting.wants[user] = w
v.minting.mu.Unlock() v.minting.mu.Unlock()
@@ -218,7 +220,7 @@ func (e errHeldBack) Error() string { return e.why }
// mintWanted mints w when minting is on, the vault holds no value for it and adopted no hash of it; otherwise it says // mintWanted mints w when minting is on, the vault holds no value for it and adopted no hash of it; otherwise it says
// why not and, where that is a hold, keeps it for vault_status. // why not and, where that is a hold, keeps it for vault_status.
func (v *Vault) mintWanted(w buscompose.WantedAccount, by string) (Minted, error) { func (v *Vault) mintWanted(w grantsstate.WantedAccount, by string) (Minted, error) {
m, err := v.mintOnce(w, by) m, err := v.mintOnce(w, by)
var held errHeldBack var held errHeldBack
v.owe(w.User, err != nil && !errors.As(err, &held) && w.Valid() == nil) v.owe(w.User, err != nil && !errors.As(err, &held) && w.Valid() == nil)
@@ -226,7 +228,7 @@ func (v *Vault) mintWanted(w buscompose.WantedAccount, by string) (Minted, error
} }
// mintOnce is one try of mintWanted. // mintOnce is one try of mintWanted.
func (v *Vault) mintOnce(w buscompose.WantedAccount, by string) (Minted, error) { func (v *Vault) mintOnce(w grantsstate.WantedAccount, by string) (Minted, error) {
v.minting.mintMu.Lock() v.minting.mintMu.Lock()
defer v.minting.mintMu.Unlock() defer v.minting.mintMu.Unlock()
if err := w.Valid(); err != nil { if err := w.Valid(); err != nil {
@@ -274,7 +276,7 @@ func (v *Vault) mintOnce(w buscompose.WantedAccount, by string) (Minted, error)
if _, err := rand.Read(raw); err != nil { if _, err := rand.Read(raw); err != nil {
return Minted{}, err return Minted{}, err
} }
value, err := buscompose.HeldCredential(w, base64.RawURLEncoding.EncodeToString(raw)) value, err := grantsstate.HeldCredential(w, base64.RawURLEncoding.EncodeToString(raw))
if err != nil { if err != nil {
v.holdBack(w.User, err.Error()) v.holdBack(w.User, err.Error())
return Minted{}, err return Minted{}, err
@@ -407,7 +409,7 @@ func (v *Vault) IssueAccount(caller, user string) (any, error) {
return nil, fmt.Errorf("the grants part holds no wanted account %s: the vault mints only what placement composes "+ return nil, fmt.Errorf("the grants part holds no wanted account %s: the vault mints only what placement composes "+
"(grants_accounts lists them)", user) "(grants_accounts lists them)", user)
} }
w, err := buscompose.ReadWantedAccount(user, raw) w, err := grantsstate.ReadWantedAccount(user, raw)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -427,7 +429,7 @@ func (v *Vault) wantedUsers() []string {
return out return out
} }
func (v *Vault) wanted(user string) (buscompose.WantedAccount, bool) { func (v *Vault) wanted(user string) (grantsstate.WantedAccount, bool) {
v.minting.mu.Lock() v.minting.mu.Lock()
defer v.minting.mu.Unlock() defer v.minting.mu.Unlock()
w, ok := v.minting.wants[user] w, ok := v.minting.wants[user]
@@ -26,13 +26,14 @@ import (
"testing" "testing"
"time" "time"
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream" "github.com/nats-io/nats.go/jetstream"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/nacl/box" "golang.org/x/crypto/nacl/box"
"git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault" "git.novox.be/novox/mesh-contracts/identity-and-access/go/seats/meshvault"
"git.novox.be/novox/mesh-sdk/go/buscompose"
"git.novox.be/novox/mesh-sdk/go/seal" "git.novox.be/novox/mesh-sdk/go/seal"
"git.novox.be/novox/mesh-sdk/go/testbus" "git.novox.be/novox/mesh-sdk/go/testbus"
) )
@@ -592,7 +593,7 @@ func TestTheVaultWholeMintsOnTheBusWhoeverHoldsTheLease(t *testing.T) {
} }
var held struct{ Password string } var held struct{ Password string }
_ = json.Unmarshal([]byte(value), &held) _ = json.Unmarshal([]byte(value), &held)
shape, _ := buscompose.HeldCredential(x, held.Password) shape, _ := grantsstate.HeldCredential(x, held.Password)
if value != string(shape) { if value != string(shape) {
t.Errorf("the minted value %q is not the controller's shape %q", value, shape) t.Errorf("the minted value %q is not the controller's shape %q", value, shape)
} }
+10 -8
View File
@@ -6,17 +6,19 @@ import (
"testing" "testing"
"time" "time"
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
"git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/buscompose"
"git.novox.be/novox/mesh-sdk/go/seal" "git.novox.be/novox/mesh-sdk/go/seal"
) )
// aWant is a wanted account of a module on ace, as the grants part writes it. // aWant is a wanted account of a module on ace, as the grants part writes it.
func aWant(module string) buscompose.WantedAccount { func aWant(module string) grantsstate.WantedAccount {
return buscompose.WantedAccount{User: "ace." + module, Kind: buscompose.KindModule, Node: "ace", Module: module, return grantsstate.WantedAccount{User: "ace." + module, Kind: buscompose.KindModule, Node: "ace", Module: module,
URL: "nats://novox.mesh:4222", Fingerprint: "ab:cd", Claims: []buscompose.ClaimedSeat{{Seat: "s", Scope: "node"}}} URL: "nats://novox.mesh:4222", Fingerprint: "ab:cd", Claims: []grantsstate.ClaimedSeat{{Seat: "s", Scope: "node"}}}
} }
func wantRaw(t *testing.T, w buscompose.WantedAccount) []byte { func wantRaw(t *testing.T, w grantsstate.WantedAccount) []byte {
t.Helper() t.Helper()
raw, err := json.Marshal(w) raw, err := json.Marshal(w)
if err != nil { if err != nil {
@@ -59,7 +61,7 @@ func TestTheVaultMintsAWantedAccountOnceAndNothingItHolds(t *testing.T) {
} }
var got struct{ Password string } var got struct{ Password string }
_ = json.Unmarshal([]byte(value), &got) _ = json.Unmarshal([]byte(value), &got)
shape, _ := buscompose.HeldCredential(w, got.Password) shape, _ := grantsstate.HeldCredential(w, got.Password)
if len(got.Password) != 40 || value != string(shape) { if len(got.Password) != 40 || value != string(shape) {
t.Errorf("the minted value is %q, not the controller's shape %q", value, shape) t.Errorf("the minted value is %q, not the controller's shape %q", value, shape)
} }
@@ -164,7 +166,7 @@ func TestTheVaultMintsAWantedAccountOnceAndNothingItHolds(t *testing.T) {
{"a ledger account no longer wanted leaves ledgers", func(t *testing.T, v testVault, accounts *fakeAccounts, priv string) { {"a ledger account no longer wanted leaves ledgers", func(t *testing.T, v testVault, accounts *fakeAccounts, priv string) {
clock := time.Date(2026, 11, 3, 9, 0, 0, 0, time.UTC) clock := time.Date(2026, 11, 3, 9, 0, 0, 0, time.UTC)
v.Now = func() time.Time { return clock } v.Now = func() time.Time { return clock }
for _, w := range []buscompose.WantedAccount{nodeToolsWant("ace"), ledgerWant("ace", "m"), ledgerWant("ace", "k")} { for _, w := range []grantsstate.WantedAccount{nodeToolsWant("ace"), ledgerWant("ace", "m"), ledgerWant("ace", "k")} {
v.WantHeard(w.User, wantRaw(t, w), false) v.WantHeard(w.User, wantRaw(t, w), false)
} }
v.WantsRead() v.WantsRead()
@@ -244,7 +246,7 @@ func TestTheVaultMintsWhoeverHoldsTheLease(t *testing.T) {
} }
// An older controller's covered account, handed with origin issued: refused, the vault's value kept. // An older controller's covered account, handed with origin issued: refused, the vault's value kept.
x := aWant("mod-x") x := aWant("mod-x")
theirs, _ := buscompose.HeldCredential(x, "the-controllers-password") theirs, _ := grantsstate.HeldCredential(x, "the-controllers-password")
if _, err := handIssued(t, v, ControllerCaller, "own/ace/mod-x/broker", string(theirs)); err == nil || if _, err := handIssued(t, v, ControllerCaller, "own/ace/mod-x/broker", string(theirs)); err == nil ||
!strings.Contains(err.Error(), "the vault minted") { !strings.Contains(err.Error(), "the vault minted") {
t.Errorf("a controller's issued value over the vault's mint: %v", err) t.Errorf("a controller's issued value over the vault's mint: %v", err)
@@ -258,7 +260,7 @@ func TestTheVaultMintsWhoeverHoldsTheLease(t *testing.T) {
v.minting.wants[z.User] = z v.minting.wants[z.User] = z
v.minting.mu.Unlock() v.minting.mu.Unlock()
v.holdBack(z.User, "held back for a reason that has passed") v.holdBack(z.User, "held back for a reason that has passed")
value, _ := buscompose.HeldCredential(z, "moved-in") value, _ := grantsstate.HeldCredential(z, "moved-in")
if _, err := v.keep("own/ace/mod-z/broker", string(value), "the move", ControllerCaller, OriginMade); err != nil { if _, err := v.keep("own/ace/mod-z/broker", string(value), "the move", ControllerCaller, OriginMade); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -21,6 +21,8 @@ import (
"testing" "testing"
"time" "time"
"git.novox.be/novox/mesh-delivery/modules/mesh-grants/grantsstate"
stdio "git.novox.be/novox/mesh-sdk/go" stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/buscompose" "git.novox.be/novox/mesh-sdk/go/buscompose"
"git.novox.be/novox/mesh-sdk/go/seal" "git.novox.be/novox/mesh-sdk/go/seal"
@@ -935,7 +937,7 @@ func TestABusAccountIsKeptOnlyAsAnAccount(t *testing.T) {
t.Errorf("%s: kept %v, err %v", row.name, err == nil, err) t.Errorf("%s: kept %v, err %v", row.name, err == nil, err)
} }
} }
w := buscompose.WantedAccount{User: "novox.nats.nats-tools", Kind: buscompose.KindProcess, Node: "novox", w := grantsstate.WantedAccount{User: "novox.nats.nats-tools", Kind: buscompose.KindProcess, Node: "novox",
Module: "nats", Process: "nats-tools", URL: "nats://novox.mesh:4222", Fingerprint: "ab:cd"} Module: "nats", Process: "nats-tools", URL: "nats://novox.mesh:4222", Fingerprint: "ab:cd"}
if err := w.Valid(); err != nil { if err := w.Valid(); err != nil {
t.Fatal(err) t.Fatal(err)
+8
View File
@@ -3,6 +3,14 @@ module mesh-vault
go 1.26.0 go 1.26.0
require ( require (
git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient v0.0.0-00010101000000-000000000000 // indirect
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 // indirect
git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry v0.0.0-00010101000000-000000000000 // indirect
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 // indirect
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000 // indirect
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 // indirect
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/go/seats/nodeengine 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/go/clients/nodeengineclient 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/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
+3
View File
@@ -1,6 +1,9 @@
{ {
"module": "mesh-vault", "module": "mesh-vault",
"version": "1", "version": "1",
"packages": {
"grantsstate": "0.1"
},
"runs-as": "mesh-vault", "runs-as": "mesh-vault",
"provides": [ "provides": [
{ {