Let the vault mint every wanted bus account whoever holds the controller's lease, keep its mint over a controller's issued value, and tell a node which modules it judged handed, so no account waits on a node reporting to the controller (issue 774)

This commit is contained in:
2026-10-31 09:49:15 +01:00
parent 92d6a5e9a0
commit a40b4d3d3f
12 changed files with 181 additions and 120 deletions
@@ -323,9 +323,29 @@ func (v *Vault) AcceptTake(caller, call, sealed string) (Changed, error) {
if origin == "" {
origin = OriginAccepted
}
if origin == OriginIssued {
// **The vault's mint wins** (issue 774): during the delivery a controller older than the hand-over may still mint
// a covered account and hand it here; it never replaces an account the vault minted, judged under the store's
// lock, so the value and the hash the bus loaded stay the vault's.
return v.keepWith(c.secret, string(value), someoneOr(c.by, caller), caller, origin, keepOptions{refuse: v.vaultMinted})
}
return v.keep(c.secret, string(value), someoneOr(c.by, caller), caller, origin)
}
// vaultMinted refuses a value handed for a secret holding an account the vault minted itself (mint.go): one is never
// replaced but by the vault's own rotation.
func (v *Vault) vaultMinted(before Record) string {
if before.Origin != OriginMade || before.ChangedBy != "the vault" || before.MovedFrom != nil {
return ""
}
if was, err := openAtRest(before, v); err == nil && BusAccountValue(before.Name, was) {
return before.Name + " holds a bus account the vault minted (generation " + fmt.Sprint(before.Generation) +
"): a value the controller issued never replaces it, so nothing was taken; the vault is the one issuer " +
"(issue 774), and a new value is the vault's rotation"
}
return ""
}
// ---- secret-ask ---------------------------------------------------------------------------------------------
// AskOf is what an ask is for, and where.
+9 -17
View File
@@ -1,28 +1,20 @@
package main
// The switch on the controller's lease (novox/hq ADR 0561 §3): minting is on only while the lease is handed to a holder
// that is no controller, and off for a controller's value, a key deleted or expired, or one that cannot be read. The
// write that hands it away turns it on and mints every wanted account the vault holds no value for; a lease handed back
// turns it off. No clock and no setting turns it. A key's age passing is heard by no watch, so the lease is read afresh
// before every mint (LeaseRead): a lease that expired since mints nothing.
// The controller's lease as the vault reads it (novox/hq ADR 0561 §3): handed away only to a holder that is no
// controller, and not for a controller's value, a key deleted or expired, or one that cannot be read. It turns retirement
// (retire.go) and rotation (rotation.go), never minting: the vault mints every wanted account it holds no value for,
// whoever holds the lease (mint.go, issue 774). No clock and no setting turns it. A key's age passing is heard by no
// watch, so the lease is read afresh before a retirement or a rotation (LeaseRead).
import "git.novox.be/novox/mesh-sdk/go/buscompose"
// LeaseHeard is the watch of the controller's lease. When minting turns on or off, every wanted account is judged again:
// minted when on, held back with the new why when off.
// LeaseHeard is the watch of the controller's lease: it records whether it is handed away, and says when that turned.
func (v *Vault) LeaseHeard(raw []byte, found bool, readErr error) {
if !v.setLease(raw, found, readErr) {
return
}
on, why := v.mintingOn()
v.log("minting is %s: %s", map[bool]string{true: "ON", false: "off"}[on], why)
for _, user := range v.wantedUsers() {
if w, ok := v.wanted(user); ok {
if _, err := v.mintWanted(w, "the lease write"); err != nil {
v.log("%s is not minted: %v", user, err)
}
}
}
on, why := v.leaseHanded()
v.log("the controller's lease is %s: %s", map[bool]string{true: "HANDED AWAY", false: "not handed away"}[on], why)
}
// setLease records what the lease says, and answers whether minting turned on or off by it.
@@ -39,7 +31,7 @@ func (v *Vault) setLease(raw []byte, found bool, readErr error) bool {
return flipped
}
// leaseAfresh reads the lease again before a mint, where the vault can: a key that expired since is heard by no watch.
// leaseAfresh reads the lease again before a retirement or a rotation, where the vault can: a key that expired since is heard by no watch.
func (v *Vault) leaseAfresh() {
if v.LeaseRead == nil {
return
+61 -28
View File
@@ -10,10 +10,14 @@ package main
// bcrypt hash into `bus-accounts`; and says `account-issued`, then `secret-changed`. The node's engine asks `own` again
// on `secret-changed` and composes.
//
// **One issuer at every moment** (§3): minting is on only while the controller's lease is handed to a holder that is no
// controller (mesh-sdk LeaseHandedAway). Until then the controller mints, the vault keeps hashing what it is handed, and
// vault_status names every wanted account it holds back and why. **Nothing is minted anew** (§4): an account whose value
// the vault holds, or whose hash it adopted, is never minted; the node accounts and the controller's own are never wanted.
// **The vault is the one issuer** (issue 774, research 072's gap "vault mints after the lease"): it mints every wanted
// account it holds no value for, whoever holds the controller's lease, and the controller mints none once a vault is
// installed (mesh-controller forwards `module issue` and its assignment's issue to issue-account). No account depends
// on a node reporting to the controller. The lease still turns retirement and rotation (retire.go, rotation.go), and
// vault_status names it. **Never two issuers of one value** (§3, §4): an account whose value the vault holds, or whose
// hash it adopted, is never minted; the node accounts and the controller's own are never wanted; a value the controller
// moves in never replaces one held (move.go), and one it hands with origin issued never replaces an account the vault
// minted (AcceptTake): during the delivery an older controller may still mint, and the vault's value wins.
// **Never two values for one user** (§7): a user whose hash the vault already wrote from another of its secrets is held
// back, said, and never replaced but by a rotation.
@@ -43,8 +47,9 @@ const (
// AccountIssuedEvent is what the vault says when it minted an account (mesh-sdk buscompose.AccountIssued).
const AccountIssuedEvent = buscompose.AccountIssuedEvent
// minting is the switch and what the vault knows of the wanted accounts, kept in memory and read again from the bus at
// every start: the grants part's state and the lease are the record, never this.
// minting is what the vault knows of the wanted accounts and of the controller's lease, kept in memory and read again
// from the bus at every start: the grants part's state and the lease are the record, never this. on, why and since are
// the lease (handed away or not), which turns retirement and rotation, never minting.
type minting struct {
mu sync.Mutex
on bool
@@ -76,41 +81,77 @@ type Minted struct {
Hashed bool `json:"hashed"`
}
// MintState is what vault_status says of minting.
// MintState is what vault_status says of minting: always on (the vault is the one issuer, issue 774), and the lease,
// which turns retirement and rotation.
type MintState struct {
On bool `json:"on"`
Why string `json:"why"`
On bool `json:"on"`
Why string `json:"why"`
// Lease is whether the controller's lease is handed away, LeaseWhy why, and Since when that last turned.
Lease bool `json:"lease_handed_away"`
LeaseWhy string `json:"lease_why"`
Since time.Time `json:"since,omitzero"`
Wanted int `json:"wanted"`
HeldBack map[string]string `json:"held_back"`
Minted []Minted `json:"minted"`
}
// MintState answers how minting stands.
// mintingWhy is why the vault mints, whoever holds the lease.
const mintingWhy = "the vault is the one issuer of bus accounts: it mints every wanted account it holds no value for " +
"and adopted no hash of, whoever holds the controller's lease (issue 774)"
// MintState answers how minting stands. A user held back whose account the vault holds a value for meanwhile (moved in,
// handed, or minted by another path) is not said held back: it is not (issue 774, 2026-10-31).
func (v *Vault) MintState() MintState {
m := &v.minting
m.mu.Lock()
defer m.mu.Unlock()
out := MintState{On: m.on, Why: m.why, Since: m.since, Wanted: len(m.wants), HeldBack: map[string]string{}, Minted: []Minted{}}
if out.Why == "" {
out.Why = "the controller's lease has not been read yet, so nothing is minted"
out := MintState{On: true, Why: mintingWhy, Lease: m.on, LeaseWhy: m.why, Since: m.since, Wanted: len(m.wants),
HeldBack: map[string]string{}, Minted: []Minted{}}
if out.LeaseWhy == "" {
out.LeaseWhy = leaseUnread
}
wants := map[string]buscompose.WantedAccount{}
for u, why := range m.heldBack {
out.HeldBack[u] = why
if w, ok := m.wants[u]; ok {
wants[u] = w
}
}
for _, x := range m.minted {
out.Minted = append(out.Minted, x)
}
m.mu.Unlock()
for u, w := range wants {
if v.holdsAccount(w) {
delete(out.HeldBack, u)
}
}
sort.Slice(out.Minted, func(i, j int) bool { return out.Minted[i].User < out.Minted[j].User })
return out
}
// mintingOn says whether the vault mints now, and why.
func (v *Vault) mintingOn() (bool, string) {
// 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 {
secret, err := AccountSecret(w)
if err != nil {
return false
}
rec, held, err := v.Store.Read(secret)
if err != nil || !held {
return false
}
value, err := openAtRest(rec, v)
return err == nil && BusAccountValue(secret, value)
}
// leaseUnread is why nothing is retired or rotated before the lease is read.
const leaseUnread = "the controller's lease has not been read yet, so nothing is retired or rotated"
// leaseHanded says whether the controller's lease is handed away, and why: what retirement and rotation wait for.
func (v *Vault) leaseHanded() (bool, string) {
v.minting.mu.Lock()
defer v.minting.mu.Unlock()
if v.minting.why == "" {
return false, "the controller's lease has not been read yet, so nothing is minted"
return false, leaseUnread
}
return v.minting.on, v.minting.why
}
@@ -248,11 +289,6 @@ func (v *Vault) mintOnce(w buscompose.WantedAccount, by string) (Minted, error)
v.holdBack(w.User, why)
return Minted{}, errHeldBack{why}
}
v.leaseAfresh()
if on, why := v.mintingOn(); !on {
v.holdBack(w.User, "held back: "+why)
return Minted{}, errHeldBack{why}
}
raw := make([]byte, 30)
if _, err := rand.Read(raw); err != nil {
return Minted{}, err
@@ -360,8 +396,9 @@ func heldBackOrMinted(m Minted, err error) (any, error) {
}
// IssueAccount is the seat's verb issue-account (ADR 0561 §9): one wanted user minted now, its key read afresh from the
// grants part's state. The recovery when a want was not heard: it mints nothing a want would not, and is refused while
// minting is off, for a node's account and the controller's (never minted here, §4), and for a user nobody wants.
// grants part's state. The recovery when a want was not heard, and what the controller's `module issue` forwards to
// (issue 774): it mints nothing a want would not, answers "held already" for an account it holds, and is refused for a
// node's account and the controller's (never minted here, §4) and for a user nobody wants.
func (v *Vault) IssueAccount(caller, user string) (any, error) {
user = strings.TrimSpace(user)
if user == "" {
@@ -371,10 +408,6 @@ func (v *Vault) IssueAccount(caller, user string) (any, error) {
return nil, fmt.Errorf("%s is never minted by the vault: a node's account and the controller's keep the hash they "+
"have (ADR 0561 §4)", user)
}
v.leaseAfresh()
if on, why := v.mintingOn(); !on {
return nil, fmt.Errorf("not minted: %s", why)
}
raw, found, err := []byte(nil), false, error(nil)
if v.WantRead != nil {
raw, found, err = v.WantRead(user)
@@ -5,9 +5,9 @@ package main
// The vault whole against a real bus (novox/hq ADR 0561 §1 to §4, issue 774's acceptance 12): the built binary, played
// over its real stdin and stdout as the node's tool runtime plays it (tools/call with the caller in _meta; mesh/state.*
// and mesh/publish answered from a real nats-server's key-value buckets and subjects), its store in a directory of its
// own. A want written to the grants part's state is minted only once the controller's lease is handed away: the hash is
// in bus-accounts, own answers the value sealed to the node, the value is the controller's shape and its hash's
// password, account-issued is said; an adopted user is never minted, and a lease handed back mints nothing.
// own. A want written to the grants part's state is minted whoever holds the controller's lease (issue 774: the vault is
// the one issuer): the hash is in bus-accounts, own answers the value sealed to the node, the value is the controller's
// shape and its hash's password, account-issued is said; an adopted user is never minted.
import (
"bufio"
@@ -297,7 +297,7 @@ func eventually(t *testing.T, what string, ok func() bool) {
}
// 774/AC12.
func TestTheVaultWholeMintsOnTheBusOnlyFromTheLeaseWrite(t *testing.T) {
func TestTheVaultWholeMintsOnTheBusWhoeverHoldsTheLease(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
dir := t.TempDir()
@@ -358,21 +358,16 @@ func TestTheVaultWholeMintsOnTheBusOnlyFromTheLeaseWrite(t *testing.T) {
_ = json.Unmarshal(raw, &s)
return s.Minting
}
// The controller holds the lease: a want is held back, nothing is minted.
// The controller holds the lease, and the want is minted all the same.
p.put(t, "mesh-controller.lease", LeaseKey, map[string]any{"instance": "novox/1/2026-10-27", "epoch": 57})
x, y, z := aWant("mod-x"), aWant("mod-y"), aWant("mod-z")
x, y := aWant("mod-x"), aWant("mod-y")
p.put(t, "mesh-vault.bus-accounts", y.User, AccountHash{User: y.User, Hash: "$2a$04$adoptedadoptedadoptedadoptedadoptedadoptedadopte", Adopted: true})
p.put(t, "mesh-grants.accounts", x.User, x)
p.put(t, "mesh-grants.accounts", y.User, y)
eventually(t, "the want held back under the controller's lease", func() bool {
return strings.Contains(status().HeldBack[x.User], "controller holds the lease")
})
if p.hashOf(t, x.User) != nil {
t.Fatal("minted under the controller's lease")
eventually(t, "the account minted under the controller's lease", func() bool { return p.hashOf(t, x.User) != nil })
if st := status(); !st.On || st.Lease || len(st.HeldBack) != 0 {
t.Errorf("vault_status says %+v", st)
}
// The write that hands the lease away mints it.
p.put(t, "mesh-controller.lease", LeaseKey, map[string]any{"handed-to": "the operator's hand-over"})
eventually(t, "the account minted at the lease write", func() bool { return p.hashOf(t, x.User) != nil })
nodePub, nodePriv := aSealingKey(t)
if _, err := p.call(Seat+".node-key", "node.ace", map[string]any{"key": nodePub}); err != nil {
t.Fatal(err)
@@ -410,13 +405,4 @@ func TestTheVaultWholeMintsOnTheBusOnlyFromTheLeaseWrite(t *testing.T) {
if !strings.Contains(said, AccountIssuedEvent+` {"`) || !strings.Contains(said, `"user":"ace.mod-x"`) || strings.Contains(said, held.Password) {
t.Errorf("the vault said: %s", said)
}
// Handed back to a controller: a new want mints nothing.
p.put(t, "mesh-controller.lease", LeaseKey, map[string]any{"instance": "novox/2/2026-10-28", "epoch": 58})
p.put(t, "mesh-grants.accounts", z.User, z)
eventually(t, "the new want held back once the lease is the controller's again", func() bool {
return strings.Contains(status().HeldBack[z.User], "controller holds the lease")
})
if p.hashOf(t, z.User) != nil {
t.Error("minted after the lease was handed back")
}
}
+35 -32
View File
@@ -106,51 +106,55 @@ func TestTheVaultMintsAWantedAccountOnceAndNothingItHolds(t *testing.T) {
}
}
// 774/AC8 (novox/hq ADR 0561 §3): while the controller holds the lease nothing is minted and vault_status says why and
// names each wanted account held back (never an adopted one); the write that hands the lease away mints them; a lease
// handed back, expired or unreadable turns minting off again, read afresh before every mint.
func TestNothingIsMintedUntilTheLeaseIsHandedAway(t *testing.T) {
// 774/AC8, after research 072's gap "vault mints after the lease": the vault is the one issuer, so a wanted account is
// minted while the controller still holds the lease (never an adopted one), vault_status says minting is on and the
// lease apart, and a want held back is not said held back once the vault holds its value. During the delivery an older
// controller may still mint: its value handed with origin issued never replaces the account the vault minted.
func TestTheVaultMintsWhoeverHoldsTheLease(t *testing.T) {
v, accounts, _ := mintingVault(t, false)
accounts.keys = map[string]AccountHash{"ace.mod-y": {User: "ace.mod-y", Hash: "$2a$04$adopted", Adopted: true}}
for _, m := range []string{"mod-x", "mod-y"} {
w := aWant(m)
v.WantHeard(w.User, wantRaw(t, w), false)
}
st := v.MintState()
if st.On || !strings.Contains(st.Why, "controller holds the lease") || len(st.HeldBack) != 1 ||
!strings.Contains(st.HeldBack["ace.mod-x"], "controller holds the lease") {
t.Fatalf("under the controller's lease vault_status says %+v", st)
}
if _, held, _ := v.Store.Read("own/ace/mod-x/broker"); held {
t.Fatal("minted while the controller holds the lease")
}
// Handed away: the wanted account is minted at that write, the adopted one is not.
lease := []byte(`{"handed-to":"the operator's hand-over"}`)
v.LeaseRead = func() ([]byte, bool, error) { return lease, true, nil }
v.LeaseHeard(lease, true, nil)
if _, held, _ := v.Store.Read("own/ace/mod-x/broker"); !held {
t.Fatalf("not minted at the lease write: %+v", v.MintState())
minted, held, _ := v.Store.Read("own/ace/mod-x/broker")
if !held {
t.Fatalf("not minted under the controller's lease: %+v", v.MintState())
}
if _, held, _ := v.Store.Read("own/ace/mod-y/broker"); held {
t.Error("an adopted account was minted at the lease write")
t.Error("an adopted account was minted")
}
if st := v.MintState(); !st.On || len(st.HeldBack) != 0 {
t.Errorf("after the lease write vault_status says %+v", st)
st := v.MintState()
if !st.On || st.Lease || !strings.Contains(st.LeaseWhy, "controller holds the lease") || len(st.HeldBack) != 0 {
t.Errorf("under the controller's lease vault_status says %+v", st)
}
// The key expired (no watch hears a key's age pass): read afresh before the next mint, nothing is minted.
v.LeaseRead = func() ([]byte, bool, error) { return nil, false, nil }
// An older controller's covered account, handed with origin issued: refused, the vault's value kept.
x := aWant("mod-x")
theirs, _ := buscompose.HeldCredential(x, "the-controllers-password")
if _, err := handIssued(t, v, ControllerCaller, "own/ace/mod-x/broker", string(theirs)); err == nil ||
!strings.Contains(err.Error(), "the vault minted") {
t.Errorf("a controller's issued value over the vault's mint: %v", err)
}
if rec, _, _ := v.Store.Read("own/ace/mod-x/broker"); rec.Generation != minted.Generation || rec.ChangedBy != "the vault" {
t.Errorf("the vault's minted account was replaced: %+v", rec)
}
// Held back, and then held (moved in, handed): no longer said held back.
z := aWant("mod-z")
v.WantHeard(z.User, wantRaw(t, z), false)
if _, held, _ := v.Store.Read("own/ace/mod-z/broker"); held {
t.Error("minted after the lease's key expired")
v.minting.mu.Lock()
v.minting.wants[z.User] = z
v.minting.mu.Unlock()
v.holdBack(z.User, "held back for a reason that has passed")
value, _ := buscompose.HeldCredential(z, "moved-in")
if _, err := v.keep("own/ace/mod-z/broker", string(value), "the move", ControllerCaller, OriginMade); err != nil {
t.Fatal(err)
}
if st := v.MintState(); st.On || !strings.Contains(st.HeldBack["ace.mod-z"], "no lease is written") {
t.Errorf("after the key expired vault_status says %+v", st)
if st := v.MintState(); st.HeldBack[z.User] != "" {
t.Errorf("an account the vault holds is said held back: %+v", st.HeldBack)
}
}
// 774/AC10 issue-account (novox/hq ADR 0561 §9): the verb mints one wanted user now, read afresh; it is refused while
// minting is off, for a node's account and the controller's, and for a user nobody wants; a user held already is said so.
// 774/AC10 issue-account (novox/hq ADR 0561 §9): the verb mints one wanted user now, read afresh, whoever holds the lease;
// it is refused for a node's account and the controller's, and for a user nobody wants; a user held already is said so.
func TestIssueAccountMintsOneWantedUserNowAndRefusesTheRest(t *testing.T) {
v, _, _ := mintingVault(t, false)
w := aWant("mod-x")
@@ -162,7 +166,6 @@ func TestIssueAccountMintsOneWantedUserNowAndRefusesTheRest(t *testing.T) {
return nil, false, nil
}
for user, says := range map[string]string{
"ace.mod-x": "controller holds the lease",
"node.ace": "never minted",
"controller": "never minted",
} {
@@ -170,7 +173,7 @@ func TestIssueAccountMintsOneWantedUserNowAndRefusesTheRest(t *testing.T) {
t.Errorf("%s: %v, want a refusal saying %q", user, err, says)
}
}
v.LeaseHeard([]byte(`{"handed-to":"the operator's hand-over"}`), true, nil)
// Minted while the controller still holds the lease (issue 774): `module issue` forwards here.
if _, err := v.IssueAccount("person.jochen", "ace.nobody"); err == nil || !strings.Contains(err.Error(), "no wanted account") {
t.Errorf("a user nobody wants: %v", err)
}
+28 -11
View File
@@ -8,7 +8,6 @@ package main
// provisioner is left out: its client fills its secrets from `get` (ADR 0349 §17).
import (
"encoding/json"
"errors"
"fmt"
"slices"
@@ -38,6 +37,11 @@ type OwnAnswer struct {
Own []NodeOwnSecret `json:"own"`
Credentials []NodeCredential `json:"credentials,omitempty"`
Missing []CredentialAsked `json:"missing,omitempty"`
// Handed is, by module of the machine, whether the vault judged it handed to the provisioner (catalogue.AssignmentHands
// of the plan in its assignment), and so left its own secrets out of Own: the node composing itself hands exactly
// those (mesh-sdk compose/frombus HandedReader, issue 774). Every module it holds a secret of, and every module
// placed there whose plan reads; one it could not judge is not named, and the node judges it as before.
Handed map[string]bool `json:"handed,omitempty"`
}
// CredentialAsked names one pair credential by its ends, as a node composing its own asks for it (mesh-sdk
@@ -163,6 +167,15 @@ func (p *planReader) placedOn(module string) (bool, error) {
return slices.Contains(p.placed, module), p.placedErr
}
// placedModules is the machine's placed modules, read once for the answer.
func (p *planReader) placedModules() ([]string, error) {
if p.placement == nil {
return nil, errors.New("the machine's placement is not read here")
}
p.placedOn("")
return p.placed, p.placedErr
}
// read is module's assignment on the machine, nil when there is none, or why the vault reads none.
func (p *planReader) read(module string) ([]byte, string, error) {
if p.why != "" {
@@ -283,7 +296,7 @@ func (v *Vault) ownOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, e
}
plan := v.plansOf(node)
var handings []Handing
out := OwnAnswer{Node: node, Key: key}
out := OwnAnswer{Node: node, Key: key, Handed: map[string]bool{}}
for _, r := range all {
parts := strings.Split(r.Name, "/")
if len(parts) != 4 || parts[0] != "own" || parts[1] != node {
@@ -293,6 +306,7 @@ func (v *Vault) ownOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, e
if err != nil {
return OwnAnswer{}, err
}
out.Handed[parts[2]] = handed
if handed {
continue
}
@@ -308,6 +322,17 @@ func (v *Vault) ownOfNode(caller string, asked ...CredentialAsked) (OwnAnswer, e
out.Own = append(out.Own, NodeOwnSecret{Module: parts[2], Name: parts[3], Origin: composeOrigin(r.Origin),
Generation: r.Generation, Sealed: sealed})
}
// And every module placed there that it holds no secret of yet, where its plan reads: the node hands it as judged here.
if placed, err := plan.placedModules(); err == nil {
for _, module := range placed {
if _, judged := out.Handed[module]; judged {
continue
}
if handed, err := plan.handed(module); err == nil {
out.Handed[module] = handed
}
}
}
sort.Slice(out.Own, func(i, j int) bool {
if out.Own[i].Module != out.Own[j].Module {
return out.Own[i].Module < out.Own[j].Module
@@ -392,15 +417,7 @@ func (p *planReader) handed(module string) (bool, error) {
// handed) hands nothing, and that module's files and own secrets stay its node's composition. Whether a plan exists
// is never the rule; a reader that asks only who binds whom reads the provisions and says so ("not handing", which
// the catalogue check checks/planhands holds every module to).
func handsOf(raw []byte) (bool, error) {
var a struct {
Provisioning *catalogue.Plan `json:"provisioning"`
}
if err := json.Unmarshal(raw, &a); err != nil {
return false, err
}
return a.Provisioning != nil && a.Provisioning.Hands(), nil
}
func handsOf(raw []byte) (bool, error) { return catalogue.AssignmentHands(raw) }
// hands is handsOf for a value already read as the controller's: one that cannot be read hands nothing.
func hands(raw []byte) bool {
@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
@@ -286,4 +287,8 @@ func TestOwnLeavesOutOnlyAModuleWhosePlanHandsIt(t *testing.T) {
if len(got.Own) != 1 || got.Own[0].Module != "grafana" || got.Own[0].Name != "admin" {
t.Errorf("anchor was answered %+v; want grafana's admin alone (its plan keys a route and hands nothing)", got.Own)
}
// And it says how it judged each, so the node composing itself hands exactly those (issue 774, postgres on novox).
if want := map[string]bool{"grafana": false, "web": true, "db": true}; !reflect.DeepEqual(got.Handed, want) {
t.Errorf("own judged %v handed, want %v", got.Handed, want)
}
}
+2 -2
View File
@@ -136,7 +136,7 @@ func (v *Vault) RetirePass() {
return
}
v.leaseAfresh()
on, leaseWhy := v.mintingOn()
on, leaseWhy := v.leaseHanded()
m := &v.minting
m.mu.Lock()
read := m.retiring.wantsRead
@@ -239,7 +239,7 @@ func (v *Vault) RetireAccounts(caller string, users []string, why string) (any,
// As the passes: never while the controller holds the lease (it forgets the accounts it mints), nor before the wanted
// accounts are read whole (an account not read yet is not known to be gone).
v.leaseAfresh()
if on, why := v.mintingOn(); !on {
if on, why := v.leaseHanded(); !on {
return nil, fmt.Errorf("nothing was retired: %s", why)
}
v.minting.mu.Lock()
@@ -42,7 +42,7 @@ func (v *Vault) BeginRotation(user string) (Rotating, error) {
if nodeOrController(user) {
return Rotating{}, fmt.Errorf("%s does not rotate here: a node's account and the controller's keep the hash they have (ADR 0561 §4)", user)
}
if on, why := v.mintingOn(); !on {
if on, why := v.leaseHanded(); !on {
return Rotating{}, fmt.Errorf("no rotation begins: %s", why)
}
v.minting.mu.Lock()
+6 -5
View File
@@ -306,9 +306,9 @@ func definitions(env *Env) []toolDef {
}},
{Name: Seat + ".issue-account",
Description: "Mint one bus account the grants part wants, now (novox/hq ADR 0561 §9): the recovery when the vault did not " +
"hear a want. Reads the wanted key afresh from mesh-grants.accounts and mints it as a want would: only while " +
"the controller's lease is handed away, never for a node's account or the controller's, never a user nobody " +
"wants, and never anew for one whose value the vault holds or whose hash it adopted (it says so). Answers " +
"hear a want, and what the controller's `module issue` forwards to (issue 774). Reads the wanted key afresh from " +
"mesh-grants.accounts and mints it as a want would, whoever holds the controller's lease: never for a node's " +
"account or the controller's, never a user nobody wants, and never anew for one whose value the vault holds or whose hash it adopted (it says so). Answers " +
"whether it minted, the secret and generation, never the value or the hash.",
Input: map[string]any{"user": str("the bus user, e.g. ace.mesh-build-go")},
run: func(caller string, a map[string]any) (any, error) {
@@ -633,8 +633,9 @@ func definitions(env *Env) []toolDef {
{Name: "vault_status",
Description: "What needs a person's eye in the vault: operator copies still sealed to an earlier key, the operator " +
"key the mesh's export holds if the vault refused it, how often the record of hand-outs was rolled, and " +
"minting (novox/hq ADR 0561 §3, §9): whether the vault mints bus accounts now and why (only while the " +
"controller's lease is handed away), every wanted account it holds back with why, and those it minted.",
"minting (novox/hq ADR 0561 §9, issue 774): the vault mints every wanted bus account it holds no value for, " +
"whoever holds the controller's lease; whether that lease is handed away (it turns retirement and rotation), " +
"every wanted account it holds back with why (never one whose value it holds), and those it minted.",
run: func(string, map[string]any) (any, error) {
if env.Vault == nil {
return map[string]any{"ready": false, "error": fmt.Sprint(env.Broken)}, nil
+1 -1
View File
@@ -3,7 +3,7 @@ module mesh-vault
go 1.26.0
require (
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082917-2d1d8def7535
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a
github.com/nats-io/nats-server/v2 v2.11.17
github.com/nats-io/nats.go v1.54.0
golang.org/x/crypto v0.57.0
+4
View File
@@ -1,5 +1,9 @@
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082917-2d1d8def7535 h1:Bgrm3YfWCxr+FEm03kEP5fDIe9hNbe+j0CDVatGAGKg=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082917-2d1d8def7535/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082933-085f17e935db h1:PJ1V9tDjNqp/aogm/r52RRePxcHyW58f7lYBceMMY9c=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031082933-085f17e935db/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a h1:DcSvfEmdPBKqCg5OmXRNb269EkkPe7BsEp+RykEhkgE=
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261031084708-acbf80ad910a/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=