From dcb7ba016cbac879a0cf4b797108c166289306da Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 12 Nov 2026 06:17:01 +0100 Subject: [PATCH] FB12: the forge bridge opens its state performed by its binding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The state performed is a requires entry, so the bridge reaches it by mesh-sdk's process.Binding: the seat mesh-broker's show asked at the start as a kept call and again on its recreated and dropped, the bucket opened by the name show answers. Until it answers, nothing owed is performed and the bridge says it waits, naming the entry. The old name _performed goes. mesh-sdk's Binding is cherry-picked on feat/47-G-fix11-b3c from feat/47-G-fix11-a7 (fa0a107). Builds: design 47 G §577 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h --- .../cmd/mesh-delivery-forge-bridge/bound.go | 100 ++++++++++++++++++ .../mesh-delivery-forge-bridge/bound_test.go | 61 +++++++++++ .../mesh-delivery-forge-bridge/gate_test.go | 1 + .../cmd/mesh-delivery-forge-bridge/held.go | 2 +- .../integration_test.go | 26 +++-- .../cmd/mesh-delivery-forge-bridge/main.go | 21 ++-- .../mesh-delivery-forge-bridge/names_test.go | 2 +- .../cmd/mesh-delivery-forge-bridge/owed.go | 25 ++++- .../manifest_test.go | 7 +- .../mesh-delivery-forge-bridge/module.json | 7 +- 10 files changed, 225 insertions(+), 27 deletions(-) create mode 100644 modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/bound.go create mode 100644 modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/bound_test.go 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": [ {