Issue an account whose credential is not sealed, and forget an unassigned module's accounts (issue 1945, ADR 0543)
Met a second time live on 2026-10-25 at 20:04 (note 35): mesh-build-go was
assigned to ace again, and its account rows, minted at 17:19, had outlived the
unassignment. The issue minted only when no row stood, so it minted and sealed
nothing; the node had no credential and the bus no user, and the module
crash-looped 20 times. The watch still said "the grants written".
- A bus account now says when its credential was sealed to its node (migration
0108; every account standing before is taken as sealed). An issue under the
assign act, the assignments' watch or a registration mints only an account
that is not issued, a row with no seal counting as none, under the store's
lock on the account across its look, mint and seal, and marks it sealed once
sealed. A seal that fails leaves it unsealed for the next issue.
- Unassigning a module forgets its bus accounts and their credentials on that
node, so an assignment again issues them afresh.
- Only sealed accounts count as minted for a composition and are named in a
grant, so the assigner, which now wants a node only once the grant names its
account (mesh-delivery, same branch), holds the node until it is.
- The assignment's log line says which accounts were issued and sealed, or that
they stood issued and sealed.
The process-only issue drops the atomic insert of the review of 9ff47f6a for
the lock, which also covers the seal. Tests: cases in the existing tables.
Research 072: the grants piece (memberships and credentials heard from placement).
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"github.com/novox/mesh-controller/internal/broker"
|
||||
"github.com/novox/mesh-controller/internal/chain"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
)
|
||||
|
||||
// An assignment that comes to stand issues its node's bus accounts and the module's grant (ADR 0543, issue 1945): on
|
||||
@@ -324,6 +325,48 @@ func TestTheAccountsAModuleSpeaksWithOnANode(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// An issue mints and seals an account only when it is not issued: no row, or a row with no sealed credential (issue
|
||||
// 1945, live on 2026-10-25 at 20:04: ace's mesh-build-go had a row from 17:19 and no credential), under the lock on
|
||||
// the account; a seal that fails leaves it unsealed for the next issue. A rotation always mints.
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
only bool
|
||||
minted, sealed bool
|
||||
sealErr error
|
||||
want string
|
||||
}{
|
||||
{"no row: minted unsealed, sealed, marked", true, false, false, nil, "lock mint-unsealed seal mark release"},
|
||||
{"a row sealed: nothing", true, true, true, nil, "lock release"},
|
||||
{"a row with no sealed credential: minted and sealed again", true, true, false, nil, "lock mint-unsealed seal mark release"},
|
||||
{"a seal that fails: not marked", true, false, false, errors.New("the vault did not take it"), "lock mint-unsealed seal release"},
|
||||
{"a rotation mints whatever stands", false, true, true, nil, "mint seal"},
|
||||
} {
|
||||
t.Run("issuing an account: "+c.name, func(t *testing.T) {
|
||||
acts := &anAccountStore{minted: c.minted, sealed: c.sealed}
|
||||
ctx, got := withIssued(t.Context())
|
||||
if c.only {
|
||||
ctx = onlyIfAbsent(ctx)
|
||||
}
|
||||
err := issueAccount(ctx, acts, inventory.BusUser{Username: "ace.mesh-build-go", Kind: inventory.BusModule},
|
||||
func(password string) error {
|
||||
acts.did("seal")
|
||||
if password == "" {
|
||||
t.Error("sealed no password")
|
||||
}
|
||||
return c.sealErr
|
||||
})
|
||||
if (err != nil) != (c.sealErr != nil) {
|
||||
t.Fatalf("error %v, want %v", err, c.sealErr)
|
||||
}
|
||||
if strings.Join(acts.acts, " ") != c.want {
|
||||
t.Fatalf("acts %q, want %q", strings.Join(acts.acts, " "), c.want)
|
||||
}
|
||||
if issued := len(got.list()) == 1; issued != (c.sealErr == nil && strings.Contains(c.want, "seal")) {
|
||||
t.Fatalf("said issued %v", got.list())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("a process may be issued only with its own broker secret", func(t *testing.T) {
|
||||
// mesh-mail's real shape: no `broker` of its own, its process declaring bus (review round 1 of issue 1181).
|
||||
if err := mayIssue(mailSenderWithProcess()); err != nil {
|
||||
@@ -342,3 +385,30 @@ func TestTheAccountsAModuleSpeaksWithOnANode(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// anAccountStore is an accountIssuer that writes down what was asked of it.
|
||||
type anAccountStore struct {
|
||||
minted, sealed bool
|
||||
acts []string
|
||||
}
|
||||
|
||||
func (a *anAccountStore) did(act string) { a.acts = append(a.acts, act) }
|
||||
func (a *anAccountStore) LockBusAccount(context.Context, string) (func(), error) {
|
||||
a.did("lock")
|
||||
return func() { a.did("release") }, nil
|
||||
}
|
||||
func (a *anAccountStore) BusAccountIssued(context.Context, string) (bool, bool, error) {
|
||||
return a.minted, a.sealed, nil
|
||||
}
|
||||
func (a *anAccountStore) MintBusPassword(context.Context, inventory.BusUser) (string, error) {
|
||||
a.did("mint")
|
||||
return "a-password", nil
|
||||
}
|
||||
func (a *anAccountStore) MintBusPasswordUnsealed(context.Context, inventory.BusUser) (string, error) {
|
||||
a.did("mint-unsealed")
|
||||
return "a-password", nil
|
||||
}
|
||||
func (a *anAccountStore) MarkBusAccountSealed(context.Context, string) error {
|
||||
a.did("mark")
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user