mesh-grants: compose the user list again when the vault says account-issued, so a minted account reaches the bus within one pass (issue 774, ADR 0561 §1)
This commit is contained in:
@@ -134,8 +134,9 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
})
|
||||
}
|
||||
// The controller makes this module's durable consumer on EVENTS from the manifest's consumes (filtered on the two
|
||||
// condition subjects); it is pulled and every message acknowledged, so its pending never grows. Where it is not there
|
||||
// yet the pass says so and hears again at the next change.
|
||||
// condition subjects and the vault's account-issued; ADR 0561 §6 gives that to the part from the lease write); it is
|
||||
// pulled and every message acknowledged, so its pending never grows. Where it is not there yet the pass says so and
|
||||
// hears again at the next change. The vault's bus-accounts is watched beside it, so a hash is composed either way.
|
||||
f.Hear = func(handle func(string, []byte)) error {
|
||||
cons, err := js.Consumer(ctx, "EVENTS", account.Node+"_"+account.Module)
|
||||
if err != nil {
|
||||
|
||||
@@ -1909,6 +1909,41 @@ func TestGrants(t *testing.T) {
|
||||
t.Errorf("a part with no bus address wrote %+v: %v", res.Accounts, res.Errors)
|
||||
}
|
||||
})
|
||||
|
||||
// 774/AC5 (novox/hq ADR 0561 §1): the vault's account-issued wakes a pass, so the next list carries the new user's hash
|
||||
// within one pass; an event of another kind does not.
|
||||
t.Run("the vault's account-issued composes the list again", func(t *testing.T) {
|
||||
b := fixture()
|
||||
var handle func(string, []byte)
|
||||
p := partOn(b)
|
||||
f := &grants.Follower{Part: p, Watch: &fakeWatcher{opened: make(chan string, 16)}, Logf: func(string, ...any) {},
|
||||
Hear: func(h func(string, []byte)) error { handle = h; return nil }}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan struct{})
|
||||
go func() { f.Run(ctx); close(done) }()
|
||||
defer func() { cancel(); <-done }()
|
||||
waitFor(t, func() bool { return b.count(grants.CallUsers) > 0 && handle != nil })
|
||||
held := func() bool {
|
||||
for _, c := range p.Last().UserList.Credentials.Users {
|
||||
if c.User == "a.mod-x" {
|
||||
return c.HashHeld
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if held() {
|
||||
t.Fatal("a.mod-x has a hash before the vault made one")
|
||||
}
|
||||
b.mu.Lock()
|
||||
b.states[grants.ReadAccounts] = map[string][]byte{"a.mod-x": []byte(`{"user":"a.mod-x","hash":"$2a$10$x","generation":1}`)}
|
||||
b.mu.Unlock()
|
||||
handle(buscompose.AccountIssuedSubject, []byte(`{"user":"a.mod-x","kind":"module","node":"a","module":"mod-x","generation":1}`))
|
||||
waitFor(t, held)
|
||||
if !slices.Contains(grants.Consumes, grants.EventAccountIssued) || !grants.IsAccountIssued(grants.EventAccountIssued) ||
|
||||
grants.IsAccountIssued("mesh.mod.mesh-vault.event.secret-changed") {
|
||||
t.Errorf("the part hears %v", grants.Consumes)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type fakeAsker struct {
|
||||
|
||||
@@ -28,9 +28,9 @@ type Follower struct {
|
||||
Logf func(string, ...any)
|
||||
// Asked watches the part's own ask to compose now (a tool's compose-now), waking at each; nil watches none.
|
||||
Asked func(wake func()) error
|
||||
// Hear hears the controller seat's condition events (Consumes) and hands each, by event name, with its body, to
|
||||
// handle; nil hears none. A condition of the kind root-not-free raised or cleared wakes a pass: a machine's
|
||||
// root-free word changed.
|
||||
// Hear hears the controller seat's condition events and the vault's account-issued (Consumes) and hands each, by event
|
||||
// name, with its body, to handle; nil hears none. A condition of the kind root-not-free raised or cleared wakes a pass:
|
||||
// a machine's root-free word changed; and so does an account the vault minted (ADR 0561 §1), whose hash the list carries.
|
||||
Hear func(handle func(event string, body []byte)) error
|
||||
|
||||
wake chan struct{}
|
||||
@@ -124,7 +124,7 @@ func (f *Follower) open() {
|
||||
}
|
||||
if f.Hear != nil && !f.heard_ {
|
||||
err := f.Hear(func(event string, body []byte) {
|
||||
if IsRootNotFree(body) {
|
||||
if IsAccountIssued(event) || IsRootNotFree(body) {
|
||||
f.Wake()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -23,8 +23,17 @@ const (
|
||||
KindRootNotFree = "root-not-free"
|
||||
)
|
||||
|
||||
// EventAccountIssued is the vault's word that it minted an account (novox/hq ADR 0561 §1): its hash is in the vault's
|
||||
// bus-accounts, so a pass composes the list with it.
|
||||
const EventAccountIssued = "mesh-vault." + buscompose.AccountIssuedEvent
|
||||
|
||||
// Consumes is every event the part hears, as its manifest's consumes name them.
|
||||
var Consumes = []string{EventConditionRaised, EventConditionCleared}
|
||||
var Consumes = []string{EventConditionRaised, EventConditionCleared, EventAccountIssued}
|
||||
|
||||
// IsAccountIssued says an event heard is the vault's account-issued, by its name or its bus subject.
|
||||
func IsAccountIssued(event string) bool {
|
||||
return event == EventAccountIssued || event == buscompose.AccountIssuedSubject
|
||||
}
|
||||
|
||||
// EventSubject is the bus subject of a controller seat event named `mesh-controller.<event>`.
|
||||
func EventSubject(event string) string {
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
],
|
||||
"consumes": [
|
||||
"mesh-controller.condition-raised",
|
||||
"mesh-controller.condition-cleared"
|
||||
"mesh-controller.condition-cleared",
|
||||
"mesh-vault.account-issued"
|
||||
],
|
||||
"uses": [
|
||||
"operator-channel"
|
||||
|
||||
Reference in New Issue
Block a user