diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/bound.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/bound.go new file mode 100644 index 00000000..ab5b8c77 --- /dev/null +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/bound.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "sync" + + "github.com/nats-io/nats.go/jetstream" + + "git.novox.be/novox/mesh-sdk/go/busretry" + "git.novox.be/novox/mesh-sdk/go/process" +) + +// brokerSeat is the seat whose verb `show` answers a `requires` entry's bus names, and whose `recreated` and `dropped` +// say it changed (design 47 G §577): the manifest names `seat:mesh-broker.show` in `invokes` and the two events in +// `consumes`. +const brokerSeat = "mesh-broker" + +// performedLocal is the local name of the state performed in the manifest's `requires`. +const performedLocal = "performed" + +// keptStore is the part of a key-value bucket the bridge's stores use. +type keptStore interface { + Get(ctx context.Context, key string) (jetstream.KeyValueEntry, error) + Put(ctx context.Context, key string, value []byte) (uint64, error) + PutString(ctx context.Context, key string, value string) (uint64, error) + Delete(ctx context.Context, key string, opts ...jetstream.KVDeleteOpt) error +} + +// boundBucket is a `requires` entry's bucket opened by its binding, never by an old name (design 47 G §577): each use +// asks the Binding for the bucket's bus name and opens it, again after `show` answers another name (a recreated entry). +// While `show` has not answered, every use fails with the Binding's error naming the entry, so nothing that needs it is +// done. +type boundBucket struct { + binding *process.Binding + local string + js process.BucketOpener + + mu sync.Mutex + name string + kv jetstream.KeyValue +} + +func (s *boundBucket) open(ctx context.Context) (jetstream.KeyValue, error) { + names, err := s.binding.Names(s.local) + if err != nil { + return nil, err + } + if names.Bucket == "" { + return nil, fmt.Errorf("the bus entry %q is bound to no bucket", s.local) + } + s.mu.Lock() + defer s.mu.Unlock() + if s.kv != nil && s.name == names.Bucket { + return s.kv, nil + } + kv, err := s.js.KeyValue(ctx, names.Bucket) + if err != nil { + return nil, fmt.Errorf("the bus entry %q, bucket %s: %w", s.local, names.Bucket, err) + } + s.name, s.kv = names.Bucket, kv + return kv, nil +} + +func (s *boundBucket) Get(ctx context.Context, key string) (jetstream.KeyValueEntry, error) { + kv, err := s.open(ctx) + if err != nil { + return nil, err + } + return kv.Get(ctx, key) +} + +func (s *boundBucket) Put(ctx context.Context, key string, value []byte) (uint64, error) { + kv, err := s.open(ctx) + if err != nil { + return 0, err + } + return kv.Put(ctx, key, value) +} + +func (s *boundBucket) PutString(ctx context.Context, key string, value string) (uint64, error) { + return s.Put(ctx, key, []byte(value)) +} + +func (s *boundBucket) Delete(ctx context.Context, key string, opts ...jetstream.KVDeleteOpt) error { + kv, err := s.open(ctx) + if err != nil { + return err + } + return kv.Delete(ctx, key, opts...) +} + +// keptCallOf is call as a kept call (ADR 0668 §1): asked again through a timeout or no responders until answered or +// busretry.Bound has passed. +func keptCallOf(ctx context.Context, call caller) func(key string, body any) (json.RawMessage, error) { + return func(key string, body any) (json.RawMessage, error) { + return busretry.DoRepeatable("the call "+key, func() (json.RawMessage, error) { return call(ctx, key, body) }, nil) + } +} diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/bound_test.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/bound_test.go new file mode 100644 index 00000000..ab1c150c --- /dev/null +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/bound_test.go @@ -0,0 +1,61 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "strings" + "testing" + + "github.com/nats-io/nats.go/jetstream" + + seat "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-contracts/go/seats/meshdelivery" + "git.novox.be/novox/mesh-sdk/go/process" + + "mesh-delivery-forge-bridge/internal/gitfixture" +) + +// openerFake is a bus that opens no bucket and records each name asked. +type openerFake struct{ asked []string } + +func (o *openerFake) KeyValue(_ context.Context, bucket string) (jetstream.KeyValue, error) { + o.asked = append(o.asked, bucket) + return nil, errors.New("no bucket in a unit test") +} + +// performedIsOpenedByItsBinding: design 47 G §577, unit (forge bridge): the state performed is opened by the bus name +// the seat mesh-broker's show answers for its local name, never by an old name; until show answers no bucket is opened, +// the use names the entry, and nothing owed is performed. +func performedIsOpenedByItsBinding(t *testing.T) { + var shows []string + answered := false + show := func(key string, body any) (json.RawMessage, error) { + b, _ := json.Marshal(body) + shows = append(shows, key+" "+string(b)) + if !answered { + return nil, errors.New("nats: no responders available for request") + } + return json.RawMessage(`{"state":"created","binding":{"local":"performed","storage":"key-value","bucket":"bound-performed"}}`), nil + } + binding := process.NewBinding(brokerSeat, forgeBridgeModule, show, nil, performedLocal) + bus := &openerFake{} + kv := &boundBucket{binding: binding, local: performedLocal, js: bus} + binding.Ask(performedLocal) + if _, err := kv.Get(context.Background(), "k"); err == nil || !strings.Contains(err.Error(), `"performed"`) || len(bus.asked) != 0 { + t.Errorf("unbound: %v, opened %v; want an error naming the entry and no bucket opened", err, bus.asked) + } + f := &seatFake{} + o, pub, kept := newOwing(f) + o.waits = binding.Waiting + gate := gitfixture.Seat(t, seat.Seat, "events", "owed is said, a merge", "event:owed")[0] + if _, err := o.heard(context.Background(), seat.Seat, gate); err == nil || len(f.calls) != 0 || len(pub.data) != 0 || len(kept) != 0 { + t.Errorf("unbound: %v, called %v, said %d; want the act left unperformed and unsaid", err, f.calls, len(pub.data)) + } + answered = true + binding.Ask(performedLocal) + _, _ = kv.Get(context.Background(), "k") + want := `seat:mesh-broker.show {"local-name":"performed","module":"mesh-delivery-forge-bridge"}` + if len(bus.asked) != 1 || bus.asked[0] != "bound-performed" || shows[len(shows)-1] != want { + t.Errorf("bound: opened %v after %v; want bound-performed, asked as %s", bus.asked, shows, want) + } +} diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/gate_test.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/gate_test.go index 8ab15e0c..20c20e48 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/gate_test.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/gate_test.go @@ -65,6 +65,7 @@ func newOwing(f *seatFake) (*owing, *fakePub, memPerformed) { // the bridge performs from the seat's groups every act said and not answered. func TestWhatIsOwedIsPerformedSaidAndKept(t *testing.T) { t.Run("the names are the manifest's", theNamesAreTheManifests) + t.Run("performed is opened by its binding", performedIsOpenedByItsBinding) gate := func(name string) []byte { return gitfixture.Seat(t, seat.Seat, "events", name, "event:owed")[0] } answer := func(verb, name string) json.RawMessage { return gitfixture.Given(t, name, "verb:"+verb+".answer") } for _, c := range []struct { diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/held.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/held.go index a0338539..a5b72909 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/held.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/held.go @@ -12,7 +12,7 @@ import ( const headsBucket = forgeBridgeModule + "_heads" // kvHeads is the bucket heads as the bridge's headStore. -type kvHeads struct{ kv jetstream.KeyValue } +type kvHeads struct{ kv keptStore } func (s kvHeads) Get(ctx context.Context, key string) (string, bool, error) { e, err := s.kv.Get(ctx, key) diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/integration_test.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/integration_test.go index bfb7b416..34fa9523 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/integration_test.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/integration_test.go @@ -39,7 +39,8 @@ import ( // From module.json: the seat git's events heard through its durable on EVENTS; what the gate and the checker owe heard // through its durable on each seat's own stream owed (SEAT__owed); the seat git's verbs, the seat mesh-delivery's // groups and the seat mesh-hand-acts' hand-act called on mesh.seat..call.. (or its plain fallback); -// its own states heads, performed, inputs, end-asks and owed-asks; its stream forge. It reads no state of the gate's or +// its own states heads, inputs, end-asks and owed-asks, and performed under the name its binding gives (the seat +// mesh-broker's show); its stream forge. It reads no state of the gate's or // the checker's (design 47, acceptance 49 and 53). `**` is written `>` on the wire. The controller's own user keeps all // rights, as it has. const busConf = ` @@ -59,12 +60,12 @@ accounts { "$JS.API.STREAM.INFO.KV_mesh-delivery-forge-bridge_heads" "$JS.FC.KV_mesh-delivery-forge-bridge_heads.>" "$KV.mesh-delivery-forge-bridge_heads.>" - "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-forge-bridge_performed.>" - "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-forge-bridge_performed.>" - "$JS.API.DIRECT.GET.KV_mesh-delivery-forge-bridge_performed.>" - "$JS.API.STREAM.INFO.KV_mesh-delivery-forge-bridge_performed" - "$JS.FC.KV_mesh-delivery-forge-bridge_performed.>" - "$KV.mesh-delivery-forge-bridge_performed.>" + "$JS.API.CONSUMER.CREATE.KV_mod_mesh-delivery-forge-bridge_performed_b1.>" + "$JS.API.CONSUMER.DELETE.KV_mod_mesh-delivery-forge-bridge_performed_b1.>" + "$JS.API.DIRECT.GET.KV_mod_mesh-delivery-forge-bridge_performed_b1.>" + "$JS.API.STREAM.INFO.KV_mod_mesh-delivery-forge-bridge_performed_b1" + "$JS.FC.KV_mod_mesh-delivery-forge-bridge_performed_b1.>" + "$KV.mod_mesh-delivery-forge-bridge_performed_b1.>" "$JS.API.CONSUMER.CREATE.KV_mesh-delivery-forge-bridge_inputs.>" "$JS.API.CONSUMER.DELETE.KV_mesh-delivery-forge-bridge_inputs.>" "$JS.API.DIRECT.GET.KV_mesh-delivery-forge-bridge_inputs.>" @@ -94,6 +95,8 @@ accounts { "$JS.ACK.SEAT_mesh-delivery-checker_owed.n1_mesh-delivery-forge-bridge.>" "$JS.API.CONSUMER.INFO.SEAT_mesh-delivery-checker_owed.n1_mesh-delivery-forge-bridge" "$JS.API.CONSUMER.MSG.NEXT.SEAT_mesh-delivery-checker_owed.n1_mesh-delivery-forge-bridge" + "mesh.seat.mesh-broker.call.show.n1~mesh-delivery-forge-bridge" + "mesh.seat.mesh-broker.tool.show" "mesh.seat.git.call.status.n1~mesh-delivery-forge-bridge" "mesh.seat.git.tool.status" "mesh.seat.git.call.comment.n1~mesh-delivery-forge-bridge" @@ -201,6 +204,9 @@ func fileAnswer(path, ref, content string, found bool) string { return string(b) } +// boundPerformed is the bus name the stand-in seat mesh-broker's show answers for the state performed. +const boundPerformed = "mod_mesh-delivery-forge-bridge_performed_b1" + // startBed is startBridge with a step between the streams and the consumers: prepare writes what the streams hold before // the bridge's consumers are made, as the grants part makes them, from now (ADR 0443 §1), and reply sets the answer of the // seat git's open-pulls from then on. A stand-in holds the seats the bridge calls: git (every verb, trees r0 by default, @@ -232,8 +238,9 @@ func startBed(t *testing.T, openReply string, prepare func(ctx context.Context, t.Fatal(err) } } - // The bridge's states, made from the manifest's state entries and its requires before the process starts. - for _, name := range []string{headsBucket, performedBucket, inputsBucket, groupkeys.EndAsksBucket, groupkeys.OwedAsksBucket} { + // The bridge's states, made from the manifest's state entries and its requires before the process starts; the state + // performed under the bus name the seat mesh-broker's show answers for it (boundPerformed), never an old name (§577). + for _, name := range []string{headsBucket, boundPerformed, inputsBucket, groupkeys.EndAsksBucket, groupkeys.OwedAsksBucket} { if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: name, History: 1}); err != nil { t.Fatal(err) } @@ -259,6 +266,7 @@ func startBed(t *testing.T, openReply string, prepare func(ctx context.Context, answers := map[string]string{ "git.open-pulls": openReply, "git.trees": `{"root":"r0","trees":{"":"t0","modules/a":"t1"}}`, "mesh-delivery.groups": `{"groups":[]}`, "mesh-hand-acts.hand-act": `{"recorded":true}`, + "mesh-broker.show": `{"state":"created","binding":{"local":"performed","storage":"key-value","template":"state","bucket":"` + boundPerformed + `"}}`, } for _, verb := range []string{"status", "comment", "view", "note", "merge-pull"} { answers["git."+verb] = string(gitfixture.Given(t, verb+" answers", "verb:"+verb+".answer")) diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/main.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/main.go index 6a9c63ea..91ff4208 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/main.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/main.go @@ -148,12 +148,22 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // Everything written on FORGE, an event's and a sync's, keeps the head in the bucket. rec := &recorder{pub: js, heads: heads, logger: logger} said := func(ctx context.Context) error { return syncOpen(ctx, call, rec, heads, &turn, logger) } - performedKV, err := process.EssentialBucket(ctx, js, "bridge", performedBucket, "keep what it performed of what is owed to the forge") - if err != nil { + // The module's one pull of its durable consumer on the shared events stream is its event hub (below). + hub := process.NewEventHub(logger.Printf) + // The state performed is a `requires` entry: opened by its binding, asked of the seat mesh-broker's show at this start + // as a kept call and again on its recreated and dropped (design 47 G §577). Until show answers, nothing owed is + // performed and the bridge says it waits, naming the entry. + binding := process.NewBinding(brokerSeat, forgeBridgeModule, keptCallOf(ctx, call), logger.Printf, performedLocal) + if err := binding.Register(hub); err != nil { return err } + binding.Ask(performedLocal) + if w := binding.Waiting(); w != "" { + logger.Printf("the bridge %s: nothing owed to the forge is performed until it is bound", w) + } + performedKV := &boundBucket{binding: binding, local: performedLocal, js: js} // What the gate and the checker owe the forge, performed through the seat git (design 47 *What the gate owes the forge*). - owe := &owing{call: call, pub: js, performed: kvPerformed{performedKV}, now: time.Now, logger: logger} + owe := &owing{call: call, pub: js, performed: kvPerformed{performedKV}, waits: binding.Waiting, now: time.Now, logger: logger} // A delivery group's member names its group, and its end is said once (ADR 0633 §4), from the seat mesh-delivery's groups. groups := &grouper{groups: groupsVia(call), send: sendVia(js), done: kvHeads{performedKV}, logger: logger} // A person says a group's end again with bridge_group_end_again, which writes the state end-asks (ADR 0633 §4): watched, and @@ -209,9 +219,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro go onReconnect(ctx, again, said, logger) // What a merge read of each module directory, by its tree (issue 1126): a merge reads only the directories it changed. inputs := newKept(process.OptionalBucket(ctx, js, inputsBucket, "a merge's module directories are kept in memory only, so the first merge after a start reads every one", logger.Printf), logger) - // The module's one pull of its durable consumer on the shared events stream is its event hub; the bridge has the one - // handler, for every event its consumes filter lets through. Essential: the forge's events are what the bridge carries. - hub := process.NewEventHub(logger.Printf) + // The hub's handler of the forge's events, for every event its consumes filter lets through. Essential: the forge's + // events are what the bridge carries. // The forge's events are the seat git's (novox/hq design 47 *The seat `git`*), whichever module holds it. if err := hub.Handle("mesh.seat."+git.Seat+".event.>", func(m jetstream.Msg) { hear(&turn, func() { diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/names_test.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/names_test.go index f2e9e83f..43162be9 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/names_test.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/names_test.go @@ -44,7 +44,7 @@ func theNamesAreTheManifests(t *testing.T) { own = append(own, forgeBridgeModule+"_"+kept.Name) } } - for _, b := range []string{headsBucket, performedBucket, inputsBucket} { + for _, b := range []string{headsBucket, inputsBucket} { if !slices.Contains(own, b) { t.Errorf("the bridge keeps %s, its manifest's states are %v", b, own) } diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/owed.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/owed.go index 9384e1c4..0ff09c15 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/owed.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/owed.go @@ -25,11 +25,10 @@ import ( "mesh-delivery-forge-bridge/internal/groups" ) -// performedBucket is the module's state performed (manifest `requires`, the template state): one key per owed act it +// The state performed (manifest `requires`, the template state, opened by its binding: bound.go) keeps one key per owed act it // performed, the act's outcome and when, with the owed event as heard (novox/hq design 47 *What the gate owes the // forge*). It is a saving and a read, never what keeps an act from happening twice: every act is safe to repeat at the // forge. The group ends' bookkeeping (`published.`, `groups.`, groups.go) is in it too, under the same 7 days. -const performedBucket = forgeBridgeModule + "_performed" // performedKey is the key of one act in performed: the seat that said it and its id, the id in unpadded base64url, since // an id may hold what a key may not (`#`, `/`). @@ -50,7 +49,7 @@ type performedStore interface { Put(ctx context.Context, key string, value []byte) error } -type kvPerformed struct{ kv jetstream.KeyValue } +type kvPerformed struct{ kv keptStore } func (s kvPerformed) Get(ctx context.Context, key string) ([]byte, bool, error) { e, err := s.kv.Get(ctx, key) @@ -75,8 +74,10 @@ type owing struct { call caller pub publisher performed performedStore - now func() time.Time - logger *log.Logger + // waits, when set, says why the state performed cannot be used yet (its binding, design 47 G §577); empty when it can. + waits func() string + now func() time.Time + logger *log.Logger } // readerOf is the reading of the seat of's owed event into the seat git's verb. @@ -102,6 +103,9 @@ func (o *owing) heard(ctx context.Context, of string, data []byte) (forgemsg.Owe return o.end(ctx, forgemsg.OwedEnded{Of: of, ID: c.ID, Outcome: forgemsg.OwedUndecodable, Words: err.Error()}, data) } + if w := o.waiting(); w != "" { + return forgemsg.OwedEnded{Of: c.Of, ID: c.ID}, fmt.Errorf("the bridge %s", w) + } if o.held(ctx, c.Of, c.ID) { // said twice, or said again while its outcome was on its way: performed once, the outcome said once return forgemsg.OwedEnded{Of: c.Of, ID: c.ID}, nil @@ -145,6 +149,14 @@ func (o *owing) hear(ctx context.Context, of string, m delivered) { _ = m.Ack() } +// waiting is why the state performed cannot be used yet, or empty. +func (o *owing) waiting() string { + if o.waits == nil { + return "" + } + return o.waits() +} + // perform calls the seat git's verb of one act and says what it came to. func (o *owing) perform(ctx context.Context, c bridge.OwedCall, data []byte) (forgemsg.OwedEnded, error) { answer, callErr := o.call(ctx, "seat:"+git.Seat+"."+c.Verb, c.Input) @@ -197,6 +209,9 @@ func (o *owing) held(ctx context.Context, of, id string) bool { // catchUp reads the seat mesh-delivery's groups and performs every act said there with no recorded outcome that // performed does not hold either (design 47 *The bridge catches up from the seat*). It answers how many it performed. func (o *owing) catchUp(ctx context.Context) (int, error) { + if w := o.waiting(); w != "" { + return 0, fmt.Errorf("the bridge %s", w) + } of, err := groups.Read(ctx, o.call) if err != nil { return 0, err diff --git a/modules/mesh-delivery-forge-bridge/manifest_test.go b/modules/mesh-delivery-forge-bridge/manifest_test.go index 034778b3..cdd979e1 100644 --- a/modules/mesh-delivery-forge-bridge/manifest_test.go +++ b/modules/mesh-delivery-forge-bridge/manifest_test.go @@ -101,8 +101,9 @@ func TestManifestDeclarations(t *testing.T) { // 1235/AC1 } // What the gate and the checker owe the forge is each seat's event owed, heard on its own stream (design 47 *What the // gate owes the forge*), never read from their state. The forge's events are the seat git's (design 47 *The seat git*); forge.delete: a branch's end, said as a - // branch-deleted kind (design 51, DLV-FB1). - if want := []string{"git.forge-delete", "git.pull-closed", "git.pull-merged", "git.pull-updated", "mesh-delivery-checker.owed", "mesh-delivery.owed"}; !reflect.DeepEqual(sorted(m.Consumes), want) { + // branch-deleted kind (design 51, DLV-FB1). The state performed's binding is asked again on the seat mesh-broker's + // recreated and dropped, and asked of its show (design 47 G §577). + if want := []string{"git.forge-delete", "git.pull-closed", "git.pull-merged", "git.pull-updated", "mesh-broker.dropped", "mesh-broker.recreated", "mesh-delivery-checker.owed", "mesh-delivery.owed"}; !reflect.DeepEqual(sorted(m.Consumes), want) { t.Errorf("consumes = %v, want %v", m.Consumes, want) } // A group is read from the seat mesh-delivery's verb groups (design 47, acceptance 49): the bridge reads no state of @@ -111,7 +112,7 @@ func TestManifestDeclarations(t *testing.T) { // 1235/AC1 t.Errorf("reads = %v, want none", m.Reads) } if want := []string{"seat:git.comment", "seat:git.file", "seat:git.merge-pull", "seat:git.note", "seat:git.open-pulls", - "seat:git.pull", "seat:git.status", "seat:git.tags", "seat:git.trees", "seat:git.view", "seat:mesh-delivery.groups", "seat:mesh-hand-acts.hand-act"}; !reflect.DeepEqual(sorted(m.Invokes), want) { + "seat:git.pull", "seat:git.status", "seat:git.tags", "seat:git.trees", "seat:git.view", "seat:mesh-broker.show", "seat:mesh-delivery.groups", "seat:mesh-hand-acts.hand-act"}; !reflect.DeepEqual(sorted(m.Invokes), want) { t.Errorf("invokes = %v, want %v", m.Invokes, want) } if b, ok := m.OwnSecrets["broker"]; !ok || len(m.OwnSecrets) != 1 || b != "${dir:state}/broker" { diff --git a/modules/mesh-delivery-forge-bridge/module.json b/modules/mesh-delivery-forge-bridge/module.json index 99fcd8f7..a98c4328 100644 --- a/modules/mesh-delivery-forge-bridge/module.json +++ b/modules/mesh-delivery-forge-bridge/module.json @@ -8,7 +8,9 @@ "git.pull-closed", "git.forge-delete", "mesh-delivery.owed", - "mesh-delivery-checker.owed" + "mesh-delivery-checker.owed", + "mesh-broker.recreated", + "mesh-broker.dropped" ], "streams": [ { "name": "forge", "events": ["forge.**", "owed.ended"] } @@ -40,7 +42,8 @@ "seat:git.trees", "seat:git.file", "seat:git.pull", - "seat:git.tags" + "seat:git.tags", + "seat:mesh-broker.show" ], "requires": [ {