diff --git a/modules/mesh-grants/cmd/mesh-grants/integration_test.go b/modules/mesh-grants/cmd/mesh-grants/integration_test.go index 25a157c0..0189b327 100644 --- a/modules/mesh-grants/cmd/mesh-grants/integration_test.go +++ b/modules/mesh-grants/cmd/mesh-grants/integration_test.go @@ -96,9 +96,17 @@ func TestTheGrantsPartWhole(t *testing.T) { verdict, _ := delivery.EncodeManifestVerdict(delivery.ManifestVerdict{Module: module, State: delivery.VerdictAccepted, Judge: "j", At: time.Now()}) put(grants.ReadVerdicts, digest, json.RawMessage(verdict)) } - manifest("nats", `{"module":"nats","version":"1","claims":[{"name":"mesh-broker"}],"own-secrets":{"broker":"/var/lib/nats/broker"}}`) + manifest("nats", `{"module":"nats","version":"1","claims":[{"name":"mesh-broker"}],"own-secrets":{"broker":"/var/lib/nats/broker"},`+ + `"emits":["created","done","dropped","recreated"],"streams":[{"name":"broker-events","events":["created","dropped","recreated"],"max-age-days":30,"max-mib":256}]}`) manifest("mod-c", `{"module":"mod-c","version":"1","own-secrets":{"broker":"/var/lib/c/broker"},"consumes":["nats.done"]}`) - pl, _ := delivery.EncodePlacement(delivery.Placement{Node: "a", Modules: []delivery.PlacedModule{{Module: "nats"}, {Module: "mod-c"}}}) + // The part itself, placed as on the mesh, so its own membership names the bus module's stream it hears (DLV-G1). + own, err := os.ReadFile("../../module.json") + if err != nil { + t.Fatal(err) + } + manifest(grants.Module, string(own)) + pl, _ := delivery.EncodePlacement(delivery.Placement{Node: "a", Modules: []delivery.PlacedModule{{Module: "nats"}, {Module: "mod-c"}, + {Module: grants.Module}}}) if _, err := kvs[grants.ReadPlacement].Put(ctx, "placement.a", pl); err != nil { t.Fatal(err) } @@ -189,6 +197,41 @@ func TestTheGrantsPartWhole(t *testing.T) { if n := said.Load(); n != 0 { t.Errorf("%d memberships were said on their subject", n) } + // DLV-G1, 1966/AC34 (novox/hq ADR 0595 §1, §7): the bus module's created, said on its own stream, reaches the part on + // its durable consumer there, and the binding it carries is in the module's membership; dropped takes it out. + until("the part's durable consumer on the bus module's stream", func() bool { + e, err := kvs[grants.Module+"."+grants.StateMemberships].Get(ctx, "a."+grants.Module) + if err != nil { + return false + } + m, err := membership.DecodeGrantsMembership(e.Value()) + if err != nil || len(m.Hears) == 0 { + return false + } + _, err = js.Consumer(ctx, m.Hears[0].Stream, m.Hears[0].Consumer) + return err == nil + }) + bindingOf := func() []membership.BusBinding { + e, err := kvs[grants.Module+"."+grants.StateMemberships].Get(ctx, "a.mod-c") + if err != nil { + return nil + } + m, _ := membership.DecodeGrantsMembership(e.Value()) + return m.Bindings + } + sayBroker := func(event string, bind membership.BusBinding) { + body, _ := json.Marshal(grants.BrokerEvent{V: 1, Module: "mod-c", Local: "kept", Storage: "key-value", Template: "state", Binding: bind}) + if _, err := js.Publish(ctx, "mesh.stream.nats.event."+event, body); err != nil { + t.Fatal(err) + } + } + sayBroker("created", membership.BusBinding{Local: "kept", Storage: "key-value", Template: "state", Bucket: "mod-c_kept"}) + until("mod-c's binding in its membership after created", func() bool { + b := bindingOf() + return len(b) == 1 && b[0].Bucket == "mod-c_kept" + }) + sayBroker("dropped", membership.BusBinding{Local: "kept"}) + until("mod-c's binding gone after dropped", func() bool { return len(bindingOf()) == 0 }) for _, bucket := range []string{"mesh-controller_lease", grants.Module + "_lease", grants.Module + "_takeover"} { if _, err := js.KeyValue(ctx, bucket); !errors.Is(err, jetstream.ErrBucketNotFound) { t.Errorf("the bucket %s: %v, want none", bucket, err) diff --git a/modules/mesh-grants/cmd/mesh-grants/main.go b/modules/mesh-grants/cmd/mesh-grants/main.go index 9cfc0c70..ee122fcd 100644 --- a/modules/mesh-grants/cmd/mesh-grants/main.go +++ b/modules/mesh-grants/cmd/mesh-grants/main.go @@ -121,16 +121,42 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // The part makes this module's own durable consumer on EVENTS from the manifest's consumes (filtered on the two // condition subjects and the vault's account-issued; ADR 0561 §6), as it makes every module's; 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. + // + // Beside EVENTS, the module streams its consumes reach (the bus module's own stream, whose created, recreated and + // dropped carry the bindings: ADR 0595 §1, §7), each on the durable consumer its own membership names; one not + // there yet is opened at the next change, and one opened is never opened twice. + opened := map[string]bool{} f.Hear = func(handle func(string, []byte)) error { - cons, err := js.Consumer(ctx, "EVENTS", account.Node+"_"+account.Module) - if err != nil { - return fmt.Errorf("the consumer %s_%s on EVENTS: %w", account.Node, account.Module, err) + consume := func(stream, name string) error { + if opened[stream] { + return nil + } + cons, err := js.Consumer(ctx, stream, name) + if err != nil { + return fmt.Errorf("the consumer %s on %s: %w", name, stream, err) + } + if _, err := cons.Consume(func(m jetstream.Msg) { + handle(m.Subject(), m.Data()) + _ = m.Ack() + }, jetstream.ConsumeErrHandler(func(_ jetstream.ConsumeContext, err error) { logger.Printf("hearing the bus: %v", err) })); err != nil { + return err + } + opened[stream] = true + return nil } - _, err = cons.Consume(func(m jetstream.Msg) { - handle(m.Subject(), m.Data()) - _ = m.Ack() - }, jetstream.ConsumeErrHandler(func(_ jetstream.ConsumeContext, err error) { logger.Printf("hearing the bus: %v", err) })) - return err + if err := consume("EVENTS", account.Node+"_"+account.Module); err != nil { + return err + } + hears, err := grants.OwnHears(ownStore{conn}, account.Node) + if err != nil { + return err + } + for _, h := range hears { + if err := consume(h.Stream, h.Consumer); err != nil { + return err + } + } + return nil } logger.Printf("started on the account %s: publishing memberships, writing grants and the user list sealed to the bus node, which it follows, and creating the account consumers and the declared buckets, logs and streams the bus lacks; a change to a held stream is only said", account.User) f.Run(ctx) diff --git a/modules/mesh-grants/grants_test.go b/modules/mesh-grants/grants_test.go index 9cac9179..c1e51ce6 100644 --- a/modules/mesh-grants/grants_test.go +++ b/modules/mesh-grants/grants_test.go @@ -2197,6 +2197,79 @@ func TestGrants(t *testing.T) { } }) + // DLV-G1, 1966/AC32-AC33 (novox/hq ADR 0595 §1, §7; design 25 § 12.2): the bus module's created writes the binding into + // the module's membership on every machine it is assigned, one assigned later included; recreated rewrites it; dropped + // removes it; before created, on a refused, or from an emitter holding no mesh-broker, none. + t.Run("bindings: written on created, rewritten on recreated, removed on dropped, kept in memberships", func(t *testing.T) { + b := fixture() + p := partOn(b) + p.Compose() + bindingsOn := func(key string) []membership.BusBinding { + raw, _, found, _ := b.ownGet(grants.StateMemberships, key) + if !found { + t.Fatalf("no membership %s", key) + } + bare, _ := membership.BareGrantsMembership(raw) + var m membership.Membership + _ = json.Unmarshal(bare, &m) + return m.Bindings + } + if got := bindingsOn("a.mod-x"); len(got) != 0 { + t.Fatalf("a binding before created: %+v", got) + } + hear := func(event string, bind membership.BusBinding) bool { + body, _ := json.Marshal(grants.BrokerEvent{V: 1, Module: "mod-x", Local: "records", Storage: "key-value", Template: "state", Binding: bind}) + changed, err := p.HearBroker(event, body, p.Brokers()) + if err != nil { + t.Fatalf("%s: %v", event, err) + } + return changed + } + first := membership.BusBinding{Local: "records", Storage: "key-value", Template: "state", Bucket: "mod-x_records"} + for _, event := range []string{"mesh.stream.nats.event.refused", "mesh.stream.mod-y.event.created", "mesh.stream.nats.event.drifted"} { + if hear(event, first) { + t.Errorf("%s changed the bindings", event) + } + } + // The part's own consumer on the bus module's stream, and every module's on a module stream it hears, is created by + // the part since the controller ended (ADR 0363 §5, ADR 0590): here mod-y's on mod-x's THINGS. + heard := fixture() + heard.manifestOf("mod-y", strings.Replace(yManifest, `"reads"`, `"consumes":["mod-x.thing.added"],"reads"`, 1)) + hp := partOn(heard) + hp.Consumers = heard + hp.Compose() + if !slices.Contains(heard.consumerCreates, "THINGS.b_mod-y") { + t.Errorf("the consumer of a heard module stream was not created: %v", heard.consumerCreates) + } + if !hear("mesh.stream.nats.event.created", first) || hear("mesh.stream.nats.event.created", first) { + t.Fatal("created: not kept once") + } + b.assign("b", "mod-x") + p.Compose() + for _, key := range []string{"a.mod-x", "b.mod-x"} { + if got := bindingsOn(key); len(got) != 1 || got[0].Bucket != "mod-x_records" { + t.Errorf("%s after created: %+v", key, got) + } + } + if got := bindingsOn("a.nats"); len(got) != 0 { + t.Errorf("another module's membership carries the binding: %+v", got) + } + again := first + again.Bucket = "mod-x_records-2" + hear("mesh.stream.nats.event.recreated", again) + p.Compose() + if got := bindingsOn("b.mod-x"); len(got) != 1 || got[0].Bucket != "mod-x_records-2" { + t.Errorf("after recreated: %+v", got) + } + if !hear("mesh.stream.nats.event.dropped", membership.BusBinding{Local: "records"}) { + t.Fatal("dropped changed nothing") + } + p.Compose() + if got := bindingsOn("a.mod-x"); len(got) != 0 { + t.Errorf("after dropped: %+v", got) + } + }) + // 774/AC4 (novox/hq ADR 0561 §2): every account the vault mints that the records compose is written to the state // accounts once, with the bus's address and a module's claims, read by the vault alone; a key is deleted when placement // no longer names its user, and never in a pass whose reads failed. diff --git a/modules/mesh-grants/internal/buscompose/derived.go b/modules/mesh-grants/internal/buscompose/derived.go index c1d23fbb..001726ac 100644 --- a/modules/mesh-grants/internal/buscompose/derived.go +++ b/modules/mesh-grants/internal/buscompose/derived.go @@ -248,23 +248,30 @@ func ConsumersOf(users []Principal) []ModuleConsumer { } // AccountConsumers are the durable consumers an account needs (novox/hq ADR 0561 §6): of ConsumersOf, each module's on -// EVENTS and each process's on its own work queue, never one on a module stream it hears (those the controller makes with -// the stream's grant). The grants part creates them from the lease write, with these configurations. +// EVENTS, each process's on its own work queue, and each module's on a module stream it hears (ADR 0363 §5), which the +// controller made with the stream's grant until it ended (ADR 0590): a restricted stream's only for a module of its own +// account, as ComposedHears composes it. The grants part creates them, with these configurations. func AccountConsumers(users []Principal) []ModuleConsumer { - queues := map[string]bool{} + queues, heard := map[string]bool{}, map[string]bool{} for _, p := range users { for _, q := range p.Queues { queues[q.Stream] = true } + for _, h := range p.Hears { + heard[h.Stream] = true + } for _, d := range p.Carries { for _, q := range d.Queues { queues[q.Stream] = true } + for _, h := range ComposedHears(d) { + heard[h.Stream] = true + } } } var out []ModuleConsumer for _, mc := range ConsumersOf(users) { - if mc.Consumer.Stream == consumerStream(Principal{Kind: KindModule}) || queues[mc.Consumer.Stream] { + if mc.Consumer.Stream == consumerStream(Principal{Kind: KindModule}) || queues[mc.Consumer.Stream] || heard[mc.Consumer.Stream] { out = append(out, mc) } } diff --git a/modules/mesh-grants/internal/grants/bindings.go b/modules/mesh-grants/internal/grants/bindings.go new file mode 100644 index 00000000..e88cd060 --- /dev/null +++ b/modules/mesh-grants/internal/grants/bindings.go @@ -0,0 +1,198 @@ +package grants + +import ( + "encoding/json" + "fmt" + "sort" + "strings" + + "git.novox.be/novox/mesh-sdk/go/membership" + "mesh-grants/internal/buscompose" +) + +// **The binding travels on the bus module's events** (novox/hq ADR 0595 §1, §7; design 25 § 12.2; DLV-G1). The bus +// module (the holder of the seat mesh-broker) says `created` when it created one entry of a module's bus state, +// `recreated` when it rebuilt one, and `dropped` when it removed one; each carries the entry's names on the bus. The +// grants part hears them on its durable consumer of the bus module's own stream (its manifest's `consumes`), writes the +// binding into the module's membership on every machine the module is assigned, rewrites it on `recreated` and removes +// it on `dropped`. What it heard is kept in its own state memberships, one key per module (BoundKey), so a binding +// outlives the 30 days its event is kept and reaches a machine the module is assigned to later. A `refused` create +// hands no binding: only these three events are read. + +// The bus module's events the part hears, by their local names (ADR 0595 §6; ADR 0620 §2: the holder's own events, +// never a seat's subject). +const ( + BrokerCreated = "created" + BrokerRecreated = "recreated" + BrokerDropped = "dropped" +) + +// BoundPrefix starts the key of the part's own state memberships that keeps one module's bindings, `bound.`: +// never a `.` key, since no machine is named `bound` (a membership's runtime reads its own key alone). +const BoundPrefix = "bound." + +// BoundKey is the key keeping module's bindings. +func BoundKey(module string) string { return BoundPrefix + module } + +// BrokerEvent is the body of the bus module's `created`, `recreated` and `dropped`, the fields the part reads. +// +// It stands until the seat mesh-broker's definition is generated in mesh-contracts (ADR 0613 §1, §5; design 26): this +// part then reads the generated type, with the same fields. +type BrokerEvent struct { + V int `json:"v"` + Module string `json:"module"` + Local string `json:"local"` + Storage string `json:"storage"` + Template string `json:"template,omitempty"` + // Binding is the entry's names on the bus: whole on `created` and `recreated`; on `dropped` only its local name counts. + Binding membership.BusBinding `json:"binding"` +} + +// Bound is the value of BoundKey: every binding of one module the bus module said it created and has not dropped, by +// local name. +type Bound struct { + V int `json:"v"` + Module string `json:"module"` + Bindings map[string]membership.BusBinding `json:"bindings"` +} + +// brokerEvent is the local name of an event heard from the holder of mesh-broker, by its name or its bus subject, and +// whether it is one; an emitter that holds no mesh-broker seat is never one. +func brokerEvent(event string, brokers map[string]bool) (string, bool) { + emitter, name := "", "" + switch { + case buscompose.OnAModuleStream(event): + // mesh.stream..event. + rest := strings.TrimPrefix(event, "mesh.stream.") + emitter, name, _ = strings.Cut(rest, ".event.") + case strings.HasPrefix(event, "mesh.mod."): + rest := strings.TrimPrefix(event, "mesh.mod.") + emitter, name, _ = strings.Cut(rest, ".event.") + default: + emitter, name, _ = strings.Cut(event, ".") + } + if !brokers[emitter] { + return "", false + } + switch name { + case BrokerCreated, BrokerRecreated, BrokerDropped: + return name, true + } + return "", false +} + +// HearBroker applies one event heard to the kept bindings: a `created` or `recreated` writes the entry's binding, a +// `dropped` removes it. It answers whether the kept bindings changed (so a pass composes again), or why the event was +// not applied; an event that is no bus module's is no change and no error. brokers are the modules holding mesh-broker. +func (p *Part) HearBroker(event string, body []byte, brokers map[string]bool) (bool, error) { + name, ok := brokerEvent(event, brokers) + if !ok { + return false, nil + } + var e BrokerEvent + if err := json.Unmarshal(body, &e); err != nil { + return false, fmt.Errorf("the bus module's %s does not read: %w", name, err) + } + local := e.Local + if local == "" { + local = e.Binding.Local + } + if e.Module == "" || local == "" || strings.Contains(e.Module, ".") { + return false, fmt.Errorf("the bus module's %s names no module or no entry (%q, %q)", name, e.Module, local) + } + b, err := p.bound(e.Module) + if err != nil { + return false, err + } + was, had := b.Bindings[local] + switch name { + case BrokerDropped: + if !had { + return false, nil + } + delete(b.Bindings, local) + default: + bind := e.Binding + bind.Local = local + if bind.Storage == "" { + bind.Storage = e.Storage + } + if bind.Template == "" { + bind.Template = e.Template + } + if had && sameBinding(was, bind) { + return false, nil + } + b.Bindings[local] = bind + } + raw, _ := json.Marshal(b) + if _, err := p.Store.Put(StateMemberships, BoundKey(e.Module), raw); err != nil { + return false, fmt.Errorf("its own %s %s cannot be written: %w", StateMemberships, BoundKey(e.Module), err) + } + return true, nil +} + +// bound reads the kept bindings of module; none kept is an empty value. +func (p *Part) bound(module string) (Bound, error) { + b := Bound{V: 1, Module: module, Bindings: map[string]membership.BusBinding{}} + raw, _, found, err := p.Store.Get(StateMemberships, BoundKey(module)) + if err != nil { + return b, fmt.Errorf("its own %s %s cannot be read: %w", StateMemberships, BoundKey(module), err) + } + if found { + if err := json.Unmarshal(raw, &b); err != nil { + return b, fmt.Errorf("its own %s %s does not read: %w", StateMemberships, BoundKey(module), err) + } + if b.Bindings == nil { + b.Bindings = map[string]membership.BusBinding{} + } + } + return b, nil +} + +// bindingsOf is module's kept bindings in local-name order, for its memberships; nil when none. +func (p *Part) bindingsOf(module string) ([]membership.BusBinding, error) { + b, err := p.bound(module) + if err != nil || len(b.Bindings) == 0 { + return nil, err + } + locals := make([]string, 0, len(b.Bindings)) + for l := range b.Bindings { + locals = append(locals, l) + } + sort.Strings(locals) + out := make([]membership.BusBinding, 0, len(locals)) + for _, l := range locals { + out = append(out, b.Bindings[l]) + } + return out, nil +} + +func sameBinding(a, b membership.BusBinding) bool { + ra, _ := json.Marshal(a) + rb, _ := json.Marshal(b) + return string(ra) == string(rb) +} + +// OwnHears are the module streams the part's own membership on node names it hearing (its consumes of another module's +// stream, the bus module's among them), each with its durable consumer: read from its own state memberships, which the +// part writes; none yet is an error, so the hearing is opened at the next change. +func OwnHears(s Store, node string) ([]membership.HeardIssued, error) { + key := buscompose.MembershipKey(node, Module) + raw, _, found, err := s.Get(StateMemberships, key) + if err != nil { + return nil, fmt.Errorf("its own membership %s cannot be read: %w", key, err) + } + if !found { + return nil, fmt.Errorf("its own membership %s is not composed yet, so the module streams it hears are not known", key) + } + bare, err := membership.BareGrantsMembership(raw) + if err != nil { + return nil, fmt.Errorf("its own membership %s does not read: %w", key, err) + } + var m membership.Membership + if err := json.Unmarshal(bare, &m); err != nil { + return nil, fmt.Errorf("its own membership %s does not read: %w", key, err) + } + return m.Hears, nil +} diff --git a/modules/mesh-grants/internal/grants/follow.go b/modules/mesh-grants/internal/grants/follow.go index 9e01a74d..b69a8905 100644 --- a/modules/mesh-grants/internal/grants/follow.go +++ b/modules/mesh-grants/internal/grants/follow.go @@ -128,6 +128,15 @@ func (f *Follower) open() { } if f.Hear != nil && !f.heard_ { err := f.Hear(func(event string, body []byte) { + // The bus module's created, recreated or dropped: the binding it carries is kept, and a pass composes the + // memberships with it (bindings.go). + if changed, err := f.Part.HearBroker(event, body, f.Part.Brokers()); err != nil { + f.logf("%v: not applied, said again at its redelivery", err) + return + } else if changed { + f.Wake() + return + } // An account the vault minted. if IsAccountIssued(event) { f.Wake() @@ -241,6 +250,11 @@ func (f *Follower) Run(ctx context.Context) { f.logf("published %d memberships: %v", len(res.Published), res.Published) } f.said(res) + // A consumer this pass created may be the part's own, which its hearing waits on: one more pass opens it, so + // the first event on it is heard without waiting for an unrelated change. + if f.Hear != nil && !f.heard_ && res.Consumers != nil && len(res.Consumers.Created) > 0 { + f.Wake() + } if res.Written { f.logf("the shadow list moved to revision %d: %d users, %d differing from the loaded list", res.Revision, res.UserList.Users, len(res.UserList.Compare.Differing())) diff --git a/modules/mesh-grants/internal/grants/part.go b/modules/mesh-grants/internal/grants/part.go index 16b7dd41..c2b6249e 100644 --- a/modules/mesh-grants/internal/grants/part.go +++ b/modules/mesh-grants/internal/grants/part.go @@ -12,6 +12,7 @@ import ( "time" "git.novox.be/novox/mesh-sdk/go/asks" + "git.novox.be/novox/mesh-sdk/go/catalogue" "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/membership" "mesh-grants/internal/buscompose" @@ -194,6 +195,33 @@ type Part struct { missingKnown bool // hashHold are the users whose hash changes held the pass's list, or nil (sealed.go). hashHold []string + // brokers are the modules whose manifest claims mesh-broker at the last pass (bindings.go), guarded by mu. + brokers map[string]bool +} + +// keepBrokers keeps the modules whose manifest claims the seat mesh-broker: the only emitters whose created, recreated +// and dropped the part applies (bindings.go). +func (p *Part) keepBrokers(in Inputs) { + brokers := map[string]bool{} + for module, m := range in.Manifests { + if m.ClaimsSeat(catalogue.BrokerSeat) { + brokers[module] = true + } + } + p.mu.Lock() + p.brokers = brokers + p.mu.Unlock() +} + +// Brokers are the modules holding mesh-broker as the last pass read them. +func (p *Part) Brokers() map[string]bool { + p.mu.Lock() + defer p.mu.Unlock() + out := map[string]bool{} + for m := range p.brokers { + out[m] = true + } + return out } // Ask is the value of KeyAsked: a tool's ask to compose now, and, from grants_assert, to create one module's missing @@ -239,6 +267,7 @@ func (p *Part) Compose() Result { p.mu.Unlock() }() in := ReadInputs(p.Read, &p.cache) + p.keepBrokers(in) for _, g := range in.Refused { res.Refused = append(res.Refused, g.Input+": "+g.Why) } @@ -342,6 +371,15 @@ func (p *Part) Compose() Result { for _, key := range sortedKeys(ms) { // The last membership is the state's own (ADR 0571 clause 7), never the assignments stream's, which goes with the // controller's leftovers (novox/hq ADR 0590 §6). + // The bus module's bindings of the module, as it said them created (bindings.go, ADR 0595 §1, §7). + if binds, err := p.bindingsOf(ms[key].Module); err != nil { + res.Errors = append(res.Errors, err.Error()) + } else if binds != nil { + withBinds := ms[key] + withBinds.Membership.Bindings = binds + withBinds.Digest = digestOf(withBinds.Membership) + ms[key] = withBinds + } was, _, found, err := p.Store.Get(StateMemberships, key) if err != nil { res.NotPublished = append(res.NotPublished, key+": not published: the membership last published cannot be read from its own "+StateMemberships+": "+err.Error()) diff --git a/modules/mesh-grants/internal/grants/rootfree.go b/modules/mesh-grants/internal/grants/rootfree.go index 3d4c22e9..1eb3ae0f 100644 --- a/modules/mesh-grants/internal/grants/rootfree.go +++ b/modules/mesh-grants/internal/grants/rootfree.go @@ -39,7 +39,8 @@ func FromKeeper(event string) bool { const EventAccountIssued = "mesh-vault." + buscompose.AccountIssuedEvent // Consumes is every event the part hears, as its manifest's consumes name them. -var Consumes = []string{EventKeeperRaised, EventKeeperCleared, EventAccountIssued} +var Consumes = []string{EventKeeperRaised, EventKeeperCleared, EventAccountIssued, BusModule + "." + BrokerCreated, + BusModule + "." + BrokerDropped, BusModule + "." + BrokerRecreated} // IsAccountIssued says an event heard is the vault's account-issued, by its name or its bus subject. func IsAccountIssued(event string) bool { diff --git a/modules/mesh-grants/module.json b/modules/mesh-grants/module.json index f7fae6e5..b361aed6 100644 --- a/modules/mesh-grants/module.json +++ b/modules/mesh-grants/module.json @@ -18,7 +18,10 @@ "consumes": [ "mesh-conditions.condition-raised", "mesh-conditions.condition-cleared", - "mesh-vault.account-issued" + "mesh-vault.account-issued", + "nats.created", + "nats.dropped", + "nats.recreated" ], "uses": [ "operator-channel"