From 95fabbfcde7600e5b1a809b571938b60db759141 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 16:13:00 +0100 Subject: [PATCH] =?UTF-8?q?Forge=20bridge:=20the=20gate's=20merges=20and?= =?UTF-8?q?=20owed=20acts=20are=20the=20seat's=20types=20and=20the=20deliv?= =?UTF-8?q?ery=20client's,=20the=20version=20and=20the=20repository=20chec?= =?UTF-8?q?k=20the=20keys'=20and=20the=20trunk's;=20the=20holders=20and=20?= =?UTF-8?q?buckets=20it=20names=20are=20its=20own=20constants=20checked=20?= =?UTF-8?q?against=20its=20manifest=20(D-11;=20ADR=200634=20=C2=A71,=20?= =?UTF-8?q?=C2=A72;=20issue=201966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../mesh-delivery-forge-bridge-tools/main.go | 19 +++--- .../main_test.go | 33 +++++----- .../cmd/mesh-delivery-forge-bridge/gate.go | 4 +- .../mesh-delivery-forge-bridge/gate_test.go | 44 +++++++------- .../cmd/mesh-delivery-forge-bridge/groups.go | 36 +++++------ .../mesh-delivery-forge-bridge/groups_test.go | 37 ++++++------ .../cmd/mesh-delivery-forge-bridge/held.go | 4 +- .../integration_test.go | 32 +++++----- .../cmd/mesh-delivery-forge-bridge/kept.go | 4 +- .../cmd/mesh-delivery-forge-bridge/main.go | 9 ++- .../cmd/mesh-delivery-forge-bridge/names.go | 16 +++++ .../mesh-delivery-forge-bridge/names_test.go | 44 ++++++++++++++ .../forgemsg/forge.go | 18 +++--- .../forgemsg/forge_test.go | 11 ++-- .../forgemsg/go.mod | 5 ++ modules/mesh-delivery-forge-bridge/go.mod | 3 + .../internal/bridge/acts.go | 35 +++++------ .../internal/bridge/acts_test.go | 60 ++++++++++--------- .../internal/bridge/bridge.go | 4 +- .../internal/bridge/bridge_test.go | 16 ++--- .../internal/groups/groups.go | 23 +++---- 21 files changed, 270 insertions(+), 187 deletions(-) create mode 100644 modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/names.go create mode 100644 modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/names_test.go diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge-tools/main.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge-tools/main.go index d5a1335d..75346e29 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge-tools/main.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge-tools/main.go @@ -17,8 +17,10 @@ import ( "sync" "time" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" + "git.novox.be/novox/mesh-delivery/sdk/trunk" stdio "git.novox.be/novox/mesh-sdk/go" - "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" @@ -26,6 +28,9 @@ import ( "mesh-delivery-forge-bridge/internal/groups" ) +// gateMergesState is the gate's state merges, which the tools read (reads: mesh-delivery-gate.merges in the manifest). +const gateMergesState = "mesh-delivery-gate.merges" + // Tool names. const ( GroupsTool = "bridge_groups" @@ -125,18 +130,18 @@ func MergedOf(raws map[string][]byte, repository, merge string, trees bridge.Tre return nil, fmt.Errorf("%q is not owner/name", repository) } for _, key := range slices.Sorted(maps.Keys(raws)) { - m, err := delivery.DecodeMerge(raws[key]) + m, err := meshdeliveryclient.DecodeMerge(raws[key]) if err != nil { continue } - at := slices.IndexFunc(m.Pulls, func(p delivery.MergePull) bool { - return delivery.SameRepository(p.Repository, repository) && p.Commit == merge + at := slices.IndexFunc(m.Pulls, func(p seat.MergesByGroupValuePullsItem) bool { + return trunk.SameRepository(p.Repository, repository) && p.Commit == merge }) if at < 0 { continue } p := m.Pulls[at] - msg, err := bridge.MergedMessage(owner, repo, p.Number, m.Group, p.Head.Head, merge, trees, files) + msg, err := bridge.MergedMessage(owner, repo, int(p.Number), m.Group, p.Head, merge, trees, files) if err != nil { return nil, fmt.Errorf("the merged message of %s at %s cannot be built: %w", repository, merge, err) } @@ -144,7 +149,7 @@ func MergedOf(raws map[string][]byte, repository, merge string, trees bridge.Tre if err != nil { return nil, err } - if _, approved := m.Approved[delivery.ApprovedKey(f.Repository, f.Number)]; approved && len(m.Approved) >= 2 { + if _, approved := m.Approved[meshdeliveryclient.ApprovedKey(f.Repository, f.Number)]; approved && len(m.Approved) >= 2 { f.Group, f.Members = m.Group, groups.MembersOf(m) } out, err := forgemsg.EncodeForge(f) @@ -253,7 +258,7 @@ func askCaller(_ context.Context, key string, body any) (json.RawMessage, error) } func main() { - merges, performed, asks := stdio.State(delivery.GateModule+".merges"), stdio.State(groups.PerformedState), stdio.State(groups.EndAsksState) + merges, performed, asks := stdio.State(gateMergesState), stdio.State(groups.PerformedState), stdio.State(groups.EndAsksState) tools := Tools(askEnd(asks, 25*time.Second), time.Now, func() ([]groups.Row, []string, error) { return read(merges, performed, asks) }, func(repository, merge string) (json.RawMessage, error) { diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge-tools/main_test.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge-tools/main_test.go index 46f3af72..3bcd2e10 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge-tools/main_test.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge-tools/main_test.go @@ -8,8 +8,9 @@ import ( "testing" "time" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" - "git.novox.be/novox/mesh-sdk/go/delivery" "mesh-delivery-forge-bridge/internal/bridge" @@ -22,19 +23,19 @@ import ( func TestTheBridgeToolsReadTheGroupsAndAskTheEndAgain(t *testing.T) { h1, h2 := strings.Repeat("1", 40), strings.Repeat("2", 40) c1, c2 := strings.Repeat("c", 40), strings.Repeat("d", 40) - enc := func(group string, state2 delivery.MergeState, c string) []byte { - v, err := delivery.EncodeMerge(delivery.Merge{Group: group, Ask: "ask", Approved: map[string]string{"novox/a@1": h1, "novox/b@2": h2}, - Pulls: []delivery.MergePull{{Head: delivery.Head{Repository: "novox/a", Number: 1, Head: h1}, State: delivery.MergeMerged, Commit: c1}, - {Head: delivery.Head{Repository: "novox/b", Number: 2, Head: h2}, State: state2, Commit: c}}}) + enc := func(group string, state2 meshdeliveryclient.MergeState, c string) []byte { + v, err := meshdeliveryclient.EncodeMerge(seat.MergesByGroupValue{Group: group, Ask: "ask", Approved: map[string]string{"novox/a@1": h1, "novox/b@2": h2}, + Pulls: []seat.MergesByGroupValuePullsItem{{Repository: "novox/a", Number: 1, Head: h1, State: string(meshdeliveryclient.MergeMerged), Commit: c1}, + {Repository: "novox/b", Number: 2, Head: h2, State: string(state2), Commit: c}}}) if err != nil { t.Fatal(err) } return v } - lone, _ := delivery.EncodeMerge(delivery.Merge{Group: "solo", Ask: "ask", Approved: map[string]string{"novox/a@5": h1}, - Pulls: []delivery.MergePull{{Head: delivery.Head{Repository: "novox/a", Number: 5, Head: h1}, State: delivery.MergeMerged, Commit: c1}}}) - merges := map[string][]byte{"ended": enc("ended", delivery.MergeMerged, c2), "ready": enc("ready", delivery.MergeMerged, c2), - "gathered": enc("gathered", delivery.MergeMerging, ""), "solo": lone, "bad": []byte("{")} + lone, _ := meshdeliveryclient.EncodeMerge(seat.MergesByGroupValue{Group: "solo", Ask: "ask", Approved: map[string]string{"novox/a@5": h1}, + Pulls: []seat.MergesByGroupValuePullsItem{{Repository: "novox/a", Number: 5, Head: h1, State: string(meshdeliveryclient.MergeMerged), Commit: c1}}}) + merges := map[string][]byte{"ended": enc("ended", meshdeliveryclient.MergeMerged, c2), "ready": enc("ready", meshdeliveryclient.MergeMerged, c2), + "gathered": enc("gathered", meshdeliveryclient.MergeMerging, ""), "solo": lone, "bad": []byte("{")} performed := map[string]bool{} for _, c := range []string{c1, c2} { performed[groups.PublishedKey("novox/a", 1, c)], performed[groups.PublishedKey("novox/b", 2, c)] = true, true @@ -98,7 +99,7 @@ func TestTheBridgeToolsReadTheGroupsAndAskTheEndAgain(t *testing.T) { if a := all["ready"].LastEndAsk; a == nil || a.By != "lead@shanks" || a.Why != "lost" || !a.Written || a.Answer == "" || all["ended"].LastEndAsk != nil { t.Errorf("ready's last end ask = %+v, ended's %+v: want ready's ask and its answer, none for ended", a, all["ended"].LastEndAsk) } - if r := all["gathered"]; !r.Open || !strings.Contains(r.Words, "gathered") || r.Members[1].State != string(delivery.MergeMerging) { + if r := all["gathered"]; !r.Open || !strings.Contains(r.Words, "gathered") || r.Members[1].State != string(meshdeliveryclient.MergeMerging) { t.Errorf("gathered = %+v", r) } if open := rowsOf(map[string]any{"open": true}); len(open) != 2 || open["ended"].Group != "" { @@ -146,11 +147,11 @@ func TestTheBridgeToolsReadTheGroupsAndAskTheEndAgain(t *testing.T) { } return f } - lone, _ := delivery.EncodeMerge(delivery.Merge{Group: "solo", Ask: "a", Approved: map[string]string{"novox/a@5": h1}, - Pulls: []delivery.MergePull{{Head: delivery.Head{Repository: "novox/a", Number: 5, Head: h1}, State: delivery.MergeMerged, Commit: c1}}}) - group, _ := delivery.EncodeMerge(delivery.Merge{Group: "feat/g", Ask: "a", Approved: map[string]string{"novox/a@6": h1, "novox/b@7": h2}, - Pulls: []delivery.MergePull{{Head: delivery.Head{Repository: "novox/a", Number: 6, Head: h1}, State: delivery.MergeMerged, Commit: c1}, - {Head: delivery.Head{Repository: "novox/b", Number: 7, Head: h2}, State: delivery.MergeMerging}}}) + lone, _ := meshdeliveryclient.EncodeMerge(seat.MergesByGroupValue{Group: "solo", Ask: "a", Approved: map[string]string{"novox/a@5": h1}, + Pulls: []seat.MergesByGroupValuePullsItem{{Repository: "novox/a", Number: 5, Head: h1, State: string(meshdeliveryclient.MergeMerged), Commit: c1}}}) + group, _ := meshdeliveryclient.EncodeMerge(seat.MergesByGroupValue{Group: "feat/g", Ask: "a", Approved: map[string]string{"novox/a@6": h1, "novox/b@7": h2}, + Pulls: []seat.MergesByGroupValuePullsItem{{Repository: "novox/a", Number: 6, Head: h1, State: string(meshdeliveryclient.MergeMerged), Commit: c1}, + {Repository: "novox/b", Number: 7, Head: h2, State: string(meshdeliveryclient.MergeMerging)}}}) for _, c := range []struct { name string raws map[string][]byte @@ -162,7 +163,7 @@ func TestTheBridgeToolsReadTheGroupsAndAskTheEndAgain(t *testing.T) { {name: "a lone pull request", raws: map[string][]byte{"solo": lone}, repository: "novox/a", merge: c1, want: published("a", 5, h1, "solo", c1)}, {name: "a member of a group", raws: map[string][]byte{"feat~g": group}, repository: "novox/a", merge: c1, want: func() forgemsg.Forge { f := published("a", 6, h1, "feat/g", c1) - f.Group, f.Members = "feat/g", []delivery.Head{{Repository: "novox/a", Number: 6, Head: h1}, {Repository: "novox/b", Number: 7, Head: h2}} + f.Group, f.Members = "feat/g", []meshdeliveryclient.Head{{Repository: "novox/a", Number: 6, Head: h1}, {Repository: "novox/b", Number: 7, Head: h2}} return f }()}, {name: "a merge the gate does not hold", raws: map[string][]byte{"solo": lone, "bad": []byte("{")}, repository: "novox/a", merge: c2, fail: "hold no merge"}, diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/gate.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/gate.go index 78ac3613..a005d465 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/gate.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/gate.go @@ -9,15 +9,13 @@ import ( "github.com/nats-io/nats.go/jetstream" - "git.novox.be/novox/mesh-sdk/go/delivery" - "mesh-delivery-forge-bridge/internal/bridge" ) // performedBucket is the module's state bucket performed, `_` as heads is: for each key of the gate's // buckets the bridge has acted on, the revision it acted at (manifest state entry performed, novox/hq ADR 0437 §8). // Its keys are `owed.` and `merges.`; its value is the revision in decimal. -const performedBucket = delivery.ForgeBridgeModule + "_performed" +const performedBucket = forgeBridgeModule + "_performed" // kvRevisions is the bucket performed as the performer's revisions. type kvRevisions struct{ kv jetstream.KeyValue } 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 fae4fb07..7fced163 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 @@ -9,8 +9,9 @@ import ( "strings" "testing" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" - "git.novox.be/novox/mesh-sdk/go/delivery" ) const ( @@ -52,13 +53,13 @@ func newPerformer(f *forgeFake) (*performer, *fakePub, memRevisions, *bytes.Buff } // The values are the SDK's own encoding (mesh-sdk go/delivery EncodeOwed, EncodeMerge), the keys its OwedKey. -func owedKV(t *testing.T, repository string, number int, act string, o delivery.Owed) (string, []byte) { +func owedKV(t *testing.T, repository string, number int, act string, o seat.OwedByRepositoryNumberHeadActValue) (string, []byte) { t.Helper() - key, err := delivery.OwedKey(repository, number, headA, act) + key, err := meshdeliveryclient.OwedKey(repository, number, headA, act) if err != nil { t.Fatal(err) } - v, err := delivery.EncodeOwed(o) + v, err := meshdeliveryclient.EncodeOwed(o) if err != nil { t.Fatal(err) } @@ -66,10 +67,11 @@ func owedKV(t *testing.T, repository string, number int, act string, o delivery. } func TestAnOwedStatusIsSetOnceForARevision(t *testing.T) { // 1341/AC1 + t.Run("the names are the manifest's", theNamesAreTheManifests) f := &forgeFake{} p, _, _, _ := newPerformer(f) key, v := owedKV(t, "novox/mesh-delivery", 12, "status=mesh/merge-gate", - delivery.Owed{Act: delivery.OwedStatus, Context: "mesh/merge-gate", State: "success", Text: "all green", Target: "https://forge.example/p/12"}) + seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: "mesh/merge-gate", State: "success", Text: "all green", Target: "https://forge.example/p/12"}) p.owed(context.Background(), key, 7, v) if len(f.calls) != 1 || f.calls[0].key != "gitea.gitea_commit_status" || f.calls[0].body != `{"context":"mesh/merge-gate","description":"all green","owner":"novox","repo":"mesh-delivery","sha":"`+headA+`","state":"success","target_url":"https://forge.example/p/12"}` { @@ -90,11 +92,11 @@ func TestViewNoteAndCommentEachCallTheirToolOnce(t *testing.T) { // 1341/AC2 p, _, _, _ := newPerformer(f) for _, c := range []struct { act string - o delivery.Owed + o seat.OwedByRepositoryNumberHeadActValue }{ - {"view", delivery.Owed{Act: delivery.OwedView, Text: "the view"}}, - {"note", delivery.Owed{Act: delivery.OwedNote, Text: "a line", Ref: "mesh-plan"}}, - {"comment.1", delivery.Owed{Act: delivery.OwedComment, Text: "hello"}}, + {"view", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedView, Text: "the view"}}, + {"note", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedNote, Text: "a line", Ref: "mesh-plan"}}, + {"comment.1", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedComment, Text: "hello"}}, } { key, v := owedKV(t, "novox/mesh-delivery", 12, c.act, c.o) p.owed(context.Background(), key, 1, v) @@ -112,7 +114,7 @@ func TestViewNoteAndCommentEachCallTheirToolOnce(t *testing.T) { // 1341/AC2 func TestARefusedActIsWrittenOnForgeOnceAndNotRetried(t *testing.T) { // 1341/AC3 f := &forgeFake{refuse: map[string]string{"gitea.gitea_add_comment": "the pull request is locked"}} p, pub, _, _ := newPerformer(f) - key, v := owedKV(t, "novox/mesh-delivery", 12, "comment.1", delivery.Owed{Act: delivery.OwedComment, Text: "hello"}) + key, v := owedKV(t, "novox/mesh-delivery", 12, "comment.1", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedComment, Text: "hello"}) p.owed(context.Background(), key, 3, v) p.owed(context.Background(), key, 3, v) if len(f.calls) != 1 { @@ -147,28 +149,28 @@ func TestAnOwedValueThatCannotBeReadIsLoggedAndNotRetried(t *testing.T) { } } -func mergeKV(t *testing.T, m delivery.Merge) []byte { +func mergeKV(t *testing.T, m seat.MergesByGroupValue) []byte { t.Helper() - v, err := delivery.EncodeMerge(m) + v, err := meshdeliveryclient.EncodeMerge(m) if err != nil { t.Fatal(err) } return v } -func twoMembers(a, b delivery.MergeState) delivery.Merge { - return delivery.Merge{Group: "g", Ask: "merge.g.0123456789ab", - Approved: map[string]string{delivery.ApprovedKey("novox/mesh-sdk", 4): headB, delivery.ApprovedKey("novox/mesh-delivery", 12): headA}, - Pulls: []delivery.MergePull{ - {Head: delivery.Head{Repository: "novox/mesh-delivery", Number: 12, Head: headA}, State: a, Commit: map[bool]string{true: "c1"}[a == delivery.MergeMerged]}, - {Head: delivery.Head{Repository: "novox/mesh-sdk", Number: 4, Head: headB}, State: b, Commit: map[bool]string{true: "c2"}[b == delivery.MergeMerged]}, +func twoMembers(a, b meshdeliveryclient.MergeState) seat.MergesByGroupValue { + return seat.MergesByGroupValue{Group: "g", Ask: "merge.g.0123456789ab", + Approved: map[string]string{meshdeliveryclient.ApprovedKey("novox/mesh-sdk", 4): headB, meshdeliveryclient.ApprovedKey("novox/mesh-delivery", 12): headA}, + Pulls: []seat.MergesByGroupValuePullsItem{ + {Repository: "novox/mesh-delivery", Number: 12, Head: headA, State: string(a), Commit: map[bool]string{true: "c1"}[a == meshdeliveryclient.MergeMerged]}, + {Repository: "novox/mesh-sdk", Number: 4, Head: headB, State: string(b), Commit: map[bool]string{true: "c2"}[b == meshdeliveryclient.MergeMerged]}, }} } func TestAMergeRevisionMergesOnlyTheMemberHanded(t *testing.T) { // 1342/AC1 f := &forgeFake{} p, _, _, _ := newPerformer(f) - m := twoMembers(delivery.MergeMerging, delivery.MergeMerged) + m := twoMembers(meshdeliveryclient.MergeMerging, meshdeliveryclient.MergeMerged) p.merges(context.Background(), "g", 5, mergeKV(t, m)) want := `{"approved_heads":["novox/mesh-delivery@` + headA + `","novox/mesh-sdk@` + headB + `"],"head":"` + headA + `","number":12,"owner":"novox","repo":"mesh-delivery"}` if len(f.calls) != 1 || f.calls[0].key != "gitea.gitea_merge_pull_request" || f.calls[0].body != want { @@ -183,7 +185,7 @@ func TestAMergeRevisionMergesOnlyTheMemberHanded(t *testing.T) { // 1342/AC1 func TestARefusedMergeIsWrittenOnForgeOnceAndNotRetried(t *testing.T) { // 1342/AC2 f := &forgeFake{refuse: map[string]string{"gitea.gitea_merge_pull_request": "novox/mesh-delivery#12 is not merged: its head moved"}} p, pub, _, _ := newPerformer(f) - v := mergeKV(t, twoMembers(delivery.MergeMerging, delivery.MergeMerged)) + v := mergeKV(t, twoMembers(meshdeliveryclient.MergeMerging, meshdeliveryclient.MergeMerged)) p.merges(context.Background(), "g", 5, v) p.merges(context.Background(), "g", 5, v) if len(f.calls) != 1 || len(pub.data) != 1 { @@ -204,7 +206,7 @@ func TestARefusedMergeIsWrittenOnForgeOnceAndNotRetried(t *testing.T) { // 1342/ func TestAMergeWithTwoHandedStopsAtTheFirstRefusal(t *testing.T) { // ADR 0441 §3 f := &forgeFake{refuse: map[string]string{"gitea.gitea_merge_pull_request": "no"}} p, pub, _, _ := newPerformer(f) - p.merges(context.Background(), "g", 1, mergeKV(t, twoMembers(delivery.MergeMerging, delivery.MergeMerging))) + p.merges(context.Background(), "g", 1, mergeKV(t, twoMembers(meshdeliveryclient.MergeMerging, meshdeliveryclient.MergeMerging))) if len(f.calls) != 1 || len(pub.data) != 1 { t.Errorf("calls %d messages %d, want the first refusal to stop the pass", len(f.calls), len(pub.data)) } diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/groups.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/groups.go index c8026a15..c840f23a 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/groups.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/groups.go @@ -8,8 +8,10 @@ import ( "github.com/nats-io/nats.go/jetstream" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/sdk/trunk" groupkeys "mesh-delivery-forge-bridge/internal/groups" ) @@ -57,29 +59,29 @@ func kvMerges(kv jetstream.KeyValue) mergesRead { // groupOf is the gate's merge of the group a merged message's branch names, when the message's pull request is one of // its approved members and the group has more than one: a lone pull request is no group. -func (g *grouper) groupOf(ctx context.Context, f forgemsg.Forge) (delivery.Merge, uint64, bool) { +func (g *grouper) groupOf(ctx context.Context, f forgemsg.Forge) (seat.MergesByGroupValue, uint64, bool) { if g == nil || g.merges == nil || f.Branch == "" { - return delivery.Merge{}, 0, false + return seat.MergesByGroupValue{}, 0, false } - key, err := delivery.MergesKey(f.Branch) + key, err := meshdeliveryclient.MergesKey(f.Branch) if err != nil { - return delivery.Merge{}, 0, false + return seat.MergesByGroupValue{}, 0, false } value, rev, found, err := g.merges(ctx, key) if err != nil { g.logger.Printf("%s#%d: the gate's merges.%s cannot be read, so its merged message names no group: %v", f.Repository, f.Number, key, err) - return delivery.Merge{}, 0, false + return seat.MergesByGroupValue{}, 0, false } if !found { - return delivery.Merge{}, 0, false + return seat.MergesByGroupValue{}, 0, false } - m, err := delivery.DecodeMerge(value) + m, err := meshdeliveryclient.DecodeMerge(value) if err != nil { g.logger.Printf("%s#%d: the gate's merges.%s is not a merge, so its merged message names no group: %v", f.Repository, f.Number, key, err) - return delivery.Merge{}, 0, false + return seat.MergesByGroupValue{}, 0, false } - if _, ok := m.Approved[delivery.ApprovedKey(f.Repository, f.Number)]; !ok || len(m.Approved) < 2 { - return delivery.Merge{}, 0, false + if _, ok := m.Approved[meshdeliveryclient.ApprovedKey(f.Repository, f.Number)]; !ok || len(m.Approved) < 2 { + return seat.MergesByGroupValue{}, 0, false } return m, rev, true } @@ -110,7 +112,7 @@ func (g *grouper) published(ctx context.Context, f forgemsg.Forge) { g.logger.Printf("%s#%d: that its merged message is written is not kept, so the group's end waits for the next act: %v", f.Repository, f.Number, err) return } - key, err := delivery.MergesKey(f.Branch) + key, err := meshdeliveryclient.MergesKey(f.Branch) if err != nil { return } @@ -130,7 +132,7 @@ func (g *grouper) ended(ctx context.Context, key string, rev uint64, value []byt } func (g *grouper) end(ctx context.Context, key string, rev uint64, value []byte, again string) bool { - m, err := delivery.DecodeMerge(value) + m, err := meshdeliveryclient.DecodeMerge(value) if err != nil || len(m.Approved) < 2 { return false } @@ -141,8 +143,8 @@ func (g *grouper) end(ctx context.Context, key string, rev uint64, value []byte, } end := forgemsg.ForgeGroupEnd{Group: m.Group} for _, member := range groupkeys.MembersOf(m) { - at := slices.IndexFunc(m.Pulls, func(p delivery.MergePull) bool { - return delivery.SameRepository(p.Repository, member.Repository) && p.Number == member.Number + at := slices.IndexFunc(m.Pulls, func(p seat.MergesByGroupValuePullsItem) bool { + return trunk.SameRepository(p.Repository, member.Repository) && int(p.Number) == member.Number }) if at < 0 || m.Pulls[at].Commit == "" { return false // not every member is merged (a merge commit is written only for a merged pull): the group stays gathered @@ -154,8 +156,8 @@ func (g *grouper) end(ctx context.Context, key string, rev uint64, value []byte, } // The members in the order the gate merged them, so the last commit names the end. slices.SortStableFunc(end.Members, func(a, b forgemsg.GroupMerge) int { - return slices.IndexFunc(m.Pulls, func(p delivery.MergePull) bool { return p.Commit == a.Merge }) - - slices.IndexFunc(m.Pulls, func(p delivery.MergePull) bool { return p.Commit == b.Merge }) + return slices.IndexFunc(m.Pulls, func(p seat.MergesByGroupValuePullsItem) bool { return p.Commit == a.Merge }) - + slices.IndexFunc(m.Pulls, func(p seat.MergesByGroupValuePullsItem) bool { return p.Commit == b.Merge }) }) data, err := forgemsg.EncodeForgeGroupEnd(end) if err != nil { diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/groups_test.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/groups_test.go index a81c2c41..b4100d67 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/groups_test.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/groups_test.go @@ -8,8 +8,9 @@ import ( "testing" "time" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" - "git.novox.be/novox/mesh-sdk/go/delivery" groupkeys "mesh-delivery-forge-bridge/internal/groups" ) @@ -29,19 +30,19 @@ func TestAGroupsMembersNameItAndItsEndIsSaidOnce(t *testing.T) { // 1585/T16, T1 ctx := context.Background() h1, h2 := strings.Repeat("1", 40), strings.Repeat("2", 40) c1, c2 := strings.Repeat("c", 40), strings.Repeat("d", 40) - merge := func(states ...delivery.MergePull) []byte { - v, err := delivery.EncodeMerge(delivery.Merge{Group: "feat/take-in", Ask: "ask", Pulls: states, + merge := func(states ...seat.MergesByGroupValuePullsItem) []byte { + v, err := meshdeliveryclient.EncodeMerge(seat.MergesByGroupValue{Group: "feat/take-in", Ask: "ask", Pulls: states, Approved: map[string]string{"novox/mesh-sdk@37": h1, "novox/mesh-delivery@9": h2}}) if err != nil { t.Fatal(err) } return v } - sdk := func(state delivery.MergeState, commit, words string) delivery.MergePull { - return delivery.MergePull{Head: delivery.Head{Repository: "novox/mesh-sdk", Number: 37, Head: h1}, State: state, Commit: commit, Words: words} + sdk := func(state meshdeliveryclient.MergeState, commit, words string) seat.MergesByGroupValuePullsItem { + return seat.MergesByGroupValuePullsItem{Repository: "novox/mesh-sdk", Number: 37, Head: h1, State: string(state), Commit: commit, Words: words} } - dlv := func(state delivery.MergeState, commit string) delivery.MergePull { - return delivery.MergePull{Head: delivery.Head{Repository: "novox/mesh-delivery", Number: 9, Head: h2}, State: state, Commit: commit} + dlv := func(state meshdeliveryclient.MergeState, commit string) seat.MergesByGroupValuePullsItem { + return seat.MergesByGroupValuePullsItem{Repository: "novox/mesh-delivery", Number: 9, Head: h2, State: string(state), Commit: commit} } gate := map[string][]byte{} var logs bytes.Buffer @@ -63,17 +64,17 @@ func TestAGroupsMembersNameItAndItsEndIsSaidOnce(t *testing.T) { // 1585/T16, T1 t.Errorf("a lone pull request names the group %q", f.Group) } // The first member: its message names the group and both members; the group is not ended. - gate["feat/take-in"] = merge(sdk(delivery.MergeMerged, c1, ""), dlv(delivery.MergeMerging, "")) + gate["feat/take-in"] = merge(sdk(meshdeliveryclient.MergeMerged, c1, ""), dlv(meshdeliveryclient.MergeMerging, "")) data, f, ok := g.member(ctx, mergedOf("novox/mesh-sdk", 37, h1, c1)) if back, err := forgemsg.DecodeForge(data); !ok || err != nil || back.Group != "feat/take-in" || len(back.Members) != 2 { t.Fatalf("a member's message: %+v %v", back, err) } g.published(ctx, f) // A refusal of the other member is no end (ADR 0441 §3: the group merges on from it later). - gate["feat/take-in"] = merge(sdk(delivery.MergeMerged, c1, ""), delivery.MergePull{Head: delivery.Head{Repository: "novox/mesh-delivery", Number: 9, Head: h2}, State: delivery.MergeRefused, Words: "405"}) + gate["feat/take-in"] = merge(sdk(meshdeliveryclient.MergeMerged, c1, ""), seat.MergesByGroupValuePullsItem{Repository: "novox/mesh-delivery", Number: 9, Head: h2, State: string(meshdeliveryclient.MergeRefused), Words: "405"}) g.ended(ctx, "feat/take-in", 8, gate["feat/take-in"]) // Merged on at the gate before its merged message is written: still no end; then its message ends the group. - gate["feat/take-in"] = merge(sdk(delivery.MergeMerged, c1, ""), dlv(delivery.MergeMerged, c2)) + gate["feat/take-in"] = merge(sdk(meshdeliveryclient.MergeMerged, c1, ""), dlv(meshdeliveryclient.MergeMerged, c2)) g.ended(ctx, "feat/take-in", 9, gate["feat/take-in"]) if len(pub.subjects) != 0 { t.Fatalf("an end before every member's merged message is written: %v", pub.subjects) @@ -97,12 +98,12 @@ func TestAGroupsMembersNameItAndItsEndIsSaidOnce(t *testing.T) { // 1585/T16, T1 } // 1585/T19: an ask to say a group's end again is answered by the bridge from the gate's merges, with the ask's time on // the message id; not written while a member is not merged, with the reason; not for a group the gate does not hold. - pull := func(repo string, n int, head string, state delivery.MergeState, commit string) delivery.MergePull { - return delivery.MergePull{Head: delivery.Head{Repository: repo, Number: n, Head: head}, State: state, Commit: commit} + pull := func(repo string, n int, head string, state meshdeliveryclient.MergeState, commit string) seat.MergesByGroupValuePullsItem { + return seat.MergesByGroupValuePullsItem{Repository: repo, Number: int64(n), Head: head, State: string(state), Commit: commit} } - askMerge := func(second delivery.MergeState, commit2 string) []byte { - v, err := delivery.EncodeMerge(delivery.Merge{Group: "feat/take-in", Ask: "ask", Approved: map[string]string{"novox/mesh-sdk@37": h1, "novox/mesh-delivery@9": h2}, - Pulls: []delivery.MergePull{pull("novox/mesh-sdk", 37, h1, delivery.MergeMerged, c1), pull("novox/mesh-delivery", 9, h2, second, commit2)}}) + askMerge := func(second meshdeliveryclient.MergeState, commit2 string) []byte { + v, err := meshdeliveryclient.EncodeMerge(seat.MergesByGroupValue{Group: "feat/take-in", Ask: "ask", Approved: map[string]string{"novox/mesh-sdk@37": h1, "novox/mesh-delivery@9": h2}, + Pulls: []seat.MergesByGroupValuePullsItem{pull("novox/mesh-sdk", 37, h1, meshdeliveryclient.MergeMerged, c1), pull("novox/mesh-delivery", 9, h2, second, commit2)}}) if err != nil { t.Fatal(err) } @@ -116,9 +117,9 @@ func TestAGroupsMembersNameItAndItsEndIsSaidOnce(t *testing.T) { // 1585/T16, T1 written bool answer string }{ - {"merged and published: written again", map[string][]byte{"feat/take-in": askMerge(delivery.MergeMerged, c2)}, true, true, "written again"}, - {"a member's message not written", map[string][]byte{"feat/take-in": askMerge(delivery.MergeMerged, c2)}, false, false, "not written"}, - {"a member not merged", map[string][]byte{"feat/take-in": askMerge(delivery.MergeMerging, "")}, true, false, "not written"}, + {"merged and published: written again", map[string][]byte{"feat/take-in": askMerge(meshdeliveryclient.MergeMerged, c2)}, true, true, "written again"}, + {"a member's message not written", map[string][]byte{"feat/take-in": askMerge(meshdeliveryclient.MergeMerged, c2)}, false, false, "not written"}, + {"a member not merged", map[string][]byte{"feat/take-in": askMerge(meshdeliveryclient.MergeMerging, "")}, true, false, "not written"}, {"no merge at the gate", map[string][]byte{}, true, false, "holds no merge"}, } { pub, done := &idPub{}, memRevisions{} 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 050935af..a0338539 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 @@ -5,13 +5,11 @@ import ( "errors" "github.com/nats-io/nats.go/jetstream" - - "git.novox.be/novox/mesh-sdk/go/delivery" ) // headsBucket is the module's state bucket heads, `_` as the indexer's buckets are named: the head last // written on FORGE for each pull request (manifest state entry heads, novox/hq ADR 0443 §5). -const headsBucket = delivery.ForgeBridgeModule + "_heads" +const headsBucket = forgeBridgeModule + "_heads" // kvHeads is the bucket heads as the bridge's headStore. type kvHeads struct{ kv jetstream.KeyValue } 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 b07c8d0a..2c54ba13 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 @@ -20,8 +20,10 @@ import ( "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/keys" "git.novox.be/novox/mesh-sdk/go/process" ) @@ -232,8 +234,8 @@ func startBed(t *testing.T, openReply string, prepare func(ctx context.Context, t.Fatal(err) } // Its other state bucket, and the gate's buckets the bridge reads, made by the controller from the gate's manifest - // (mesh-sdk go/delivery MergesBucket, OwedBucket with their histories). - for name, history := range map[string]uint8{performedBucket: 1, inputsBucket: 1, delivery.MergesBucket: delivery.MergesHistory, delivery.OwedBucket: delivery.OwedHistory} { + // (the gate's merges and owed with their histories). + for name, history := range map[string]uint8{performedBucket: 1, inputsBucket: 1, mergesBucket: 10, owedBucket: 5} { if _, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: name, History: history}); err != nil { t.Fatal(err) } @@ -334,7 +336,7 @@ func TestBridgeWritesPullMessages(t *testing.T) { // 1235/AC2 if err != nil { t.Fatal(err) } - want := forgemsg.Forge{V: delivery.Version, Kind: "pull", Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, + want := forgemsg.Forge{V: keys.Version, Kind: "pull", Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Action: "updated", Dirs: []string{"modules/a"}, DirsKnown: true, Manifests: map[string]string{"modules/a": "{}"}} if !forgemsg.SameForge(f, want) { t.Errorf("message = %+v, want %+v", f, want) @@ -465,7 +467,7 @@ func TestBridgeWritesMergedMessage(t *testing.T) { // 1236/AC4, 1448/AC5 if err != nil { t.Fatal(err) } - want := forgemsg.Forge{V: delivery.Version, Kind: "merged", Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, + want := forgemsg.Forge{V: keys.Version, Kind: "merged", Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Merge: merge, Root: "r0", Trees: map[string]string{"": "t0", "modules/a": "t1"}, Manifests: map[string]string{"": string(manifest), "modules/a": string(manifest)}, // No mesh package: only the indexer computes what a fingerprint takes (ADR 0593 §2). @@ -690,15 +692,15 @@ func TestBridgeMergesAGroupOneMemberAtATime(t *testing.T) { // 1342/AC3 t.Fatal(err) } - kv, err := b.js.KeyValue(b.ctx, delivery.MergesBucket) + kv, err := b.js.KeyValue(b.ctx, mergesBucket) if err != nil { t.Fatal(err) } - approved := map[string]string{delivery.ApprovedKey("novox/mesh-delivery", 12): head, delivery.ApprovedKey("novox/mesh-sdk", 4): headB} - pullA := delivery.MergePull{Head: delivery.Head{Repository: "novox/mesh-delivery", Number: 12, Head: head}, State: delivery.MergeMerging} - pullB := delivery.MergePull{Head: delivery.Head{Repository: "novox/mesh-sdk", Number: 4, Head: headB}, State: delivery.MergeMerging} - put := func(pulls ...delivery.MergePull) { - v, err := delivery.EncodeMerge(delivery.Merge{Group: "g", Ask: "merge.g.0123456789ab", Approved: approved, Pulls: pulls}) + approved := map[string]string{meshdeliveryclient.ApprovedKey("novox/mesh-delivery", 12): head, meshdeliveryclient.ApprovedKey("novox/mesh-sdk", 4): headB} + pullA := seat.MergesByGroupValuePullsItem{Repository: "novox/mesh-delivery", Number: 12, Head: head, State: string(meshdeliveryclient.MergeMerging)} + pullB := seat.MergesByGroupValuePullsItem{Repository: "novox/mesh-sdk", Number: 4, Head: headB, State: string(meshdeliveryclient.MergeMerging)} + put := func(pulls ...seat.MergesByGroupValuePullsItem) { + v, err := meshdeliveryclient.EncodeMerge(seat.MergesByGroupValue{Group: "g", Ask: "merge.g.0123456789ab", Approved: approved, Pulls: pulls}) if err != nil { t.Fatal(err) } @@ -726,7 +728,7 @@ func TestBridgeMergesAGroupOneMemberAtATime(t *testing.T) { // 1342/AC3 // The gate hands the next member only after the bridge reported the one before merged (ADR 0441 §5). for deadline := time.Now().Add(10 * time.Second); time.Now().Before(deadline) && len(forgeMerged(t, b)) < 1; time.Sleep(50 * time.Millisecond) { } - pullA.State, pullA.Commit = delivery.MergeMerged, mergeA + pullA.State, pullA.Commit = string(meshdeliveryclient.MergeMerged), mergeA put(pullA, pullB) next("mesh-sdk") var got []string @@ -786,15 +788,15 @@ func TestBridgePerformsNoMeshCheckAct(t *testing.T) { // R2-1, ADR 0521 if err := b.ctl.Flush(); err != nil { t.Fatal(err) } - kv, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: delivery.CheckerOwedBucket, History: delivery.CheckerOwedHistory}) + kv, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: "mesh-delivery-checker_owed", History: 5}) if err != nil { t.Fatal(err) } - key, err := delivery.CheckerOwedKey("novox/mesh-delivery", 12, head) + key, err := meshdeliveryclient.OwedKey("novox/mesh-delivery", 12, head, "status=mesh/check") if err != nil { t.Fatal(err) } - v, err := delivery.EncodeCheckerOwed(delivery.Owed{Act: delivery.OwedStatus, Context: delivery.StatusContextCheck, State: "success", Text: "pass", Target: "https://view.example/checks/g.0123456789ab"}) + v, err := meshdeliveryclient.EncodeOwed(seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: meshdeliveryclient.StatusContextCheck, State: "success", Text: "pass", Target: "https://view.example/checks/g.0123456789ab"}) if err != nil { t.Fatal(err) } diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/kept.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/kept.go index ceaec666..f72cc7b9 100644 --- a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/kept.go +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/kept.go @@ -10,8 +10,6 @@ import ( "github.com/nats-io/nats.go/jetstream" - "git.novox.be/novox/mesh-sdk/go/delivery" - "mesh-delivery-forge-bridge/internal/bridge" ) @@ -19,7 +17,7 @@ import ( // directory, its manifest and lock-file hashes, by the directory's git tree hash, so a merge reads the forge only for the // directories it changed (issue 1126; internal/bridge kept.go). A tree hash names its bytes, so a value is never stale; // a merge adds a value only for each directory it changed (a few kilobytes each). -const inputsBucket = delivery.ForgeBridgeModule + "_inputs" +const inputsBucket = forgeBridgeModule + "_inputs" // keptTimeout bounds one read or write of the bucket: the bucket is a saving, never a reason to fail a merge. const keptTimeout = 2 * time.Second 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 3c6860fc..e412765d 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 @@ -1,7 +1,7 @@ // Command mesh-delivery-forge-bridge is delivery's forge bridge (novox/hq ADR 0437 §4, issue 1235): it hears the forge // module's pull request events and writes each as a pull message on its stream FORGE. It runs beside today's // mesh-delivery and merges nothing. It serves no request subject and holds no timer: every reaction is an event -// (ADR 0437 §8); a refused act is tried again only on the next event touching its delivery. +// (ADR 0437 §8); a refused act is tried again only on the next event touching its meshdeliveryclient. package main import ( @@ -22,7 +22,6 @@ import ( "github.com/nats-io/nats.go/jetstream" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" - "git.novox.be/novox/mesh-sdk/go/delivery" "mesh-delivery-forge-bridge/internal/bridge" groupkeys "mesh-delivery-forge-bridge/internal/groups" @@ -35,7 +34,7 @@ const eventsStream = "EVENTS" // The bridge's own stream FORGE takes `mesh.stream..event.` (mesh-control internal/broker // modstreams.go ModuleStreamSubject). func forgeSubject(event string) string { - return process.ModuleStreamSubject(delivery.ForgeBridgeModule, event) + return process.ModuleStreamSubject(forgeBridgeModule, event) } func main() { @@ -158,7 +157,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro for _, w := range []struct { bucket string react func(ctx context.Context, key string, rev uint64, value []byte) - }{{delivery.OwedBucket, perf.owed}, {delivery.MergesBucket, func(ctx context.Context, key string, rev uint64, value []byte) { + }{{owedBucket, perf.owed}, {mergesBucket, func(ctx context.Context, key string, rev uint64, value []byte) { perf.merges(ctx, key, rev, value) groups.ended(ctx, key, rev, value) }}} { @@ -168,7 +167,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro if kv == nil { continue } - if w.bucket == delivery.MergesBucket { + if w.bucket == mergesBucket { groups.merges = kvMerges(kv) } go func() { diff --git a/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/names.go b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/names.go new file mode 100644 index 00000000..4e59a705 --- /dev/null +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/names.go @@ -0,0 +1,16 @@ +package main + +import groupkeys "mesh-delivery-forge-bridge/internal/groups" + +// The holders and buckets the forge bridge names, taken from its own manifest (module.json: its name, its `reads`, its +// states); a unit row checks them against it (TestTheNamesAreTheManifests). No contract names a holder +// (novox/hq ADR 0625 §4, ADR 0634 §1). +const ( + forgeBridgeModule = groupkeys.Module + gateModule = "mesh-delivery-gate" + + // mergesBucket and owedBucket are the gate's states merges and owed, read by the bridge (reads: mesh-delivery-gate.merges, + // mesh-delivery-gate.owed). + mergesBucket = gateModule + "_merges" + owedBucket = gateModule + "_owed" +) 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 new file mode 100644 index 00000000..95746226 --- /dev/null +++ b/modules/mesh-delivery-forge-bridge/cmd/mesh-delivery-forge-bridge/names_test.go @@ -0,0 +1,44 @@ +package main + +import ( + "encoding/json" + "os" + "slices" + "testing" +) + +// theNamesAreTheManifests (a subtest of TestAnOwedStatusIsSetOnceForARevision): the holders and buckets the bridge names +// are its manifest's (its module, its `reads`, its states), since no contract names a holder (novox/hq ADR 0634 §1). +func theNamesAreTheManifests(t *testing.T) { + raw, err := os.ReadFile("../../module.json") + if err != nil { + t.Fatal(err) + } + var m struct { + Module string `json:"module"` + Reads []string `json:"reads"` + State []struct { + Name string `json:"name"` + } `json:"state"` + } + if err := json.Unmarshal(raw, &m); err != nil { + t.Fatal(err) + } + if m.Module != forgeBridgeModule { + t.Errorf("the manifest's module is %q, the bridge names itself %q", m.Module, forgeBridgeModule) + } + for _, read := range []string{gateModule + ".merges", gateModule + ".owed"} { + if !slices.Contains(m.Reads, read) { + t.Errorf("the bridge reads %s, its manifest's reads are %v", read, m.Reads) + } + } + var own []string + for _, s := range m.State { + own = append(own, forgeBridgeModule+"_"+s.Name) + } + for _, b := range []string{headsBucket, performedBucket, 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/forgemsg/forge.go b/modules/mesh-delivery-forge-bridge/forgemsg/forge.go index bf640bd0..21e83f3f 100644 --- a/modules/mesh-delivery-forge-bridge/forgemsg/forge.go +++ b/modules/mesh-delivery-forge-bridge/forgemsg/forge.go @@ -11,11 +11,13 @@ import ( "regexp" "strings" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + "git.novox.be/novox/mesh-delivery/sdk/trunk" + "git.novox.be/novox/mesh-sdk/go/keys" ) // Version is the version of every value this package writes and the only one it reads: mesh-sdk's delivery values'. -const Version = delivery.Version +const Version = keys.Version // ForgeStream is the forge bridge's stream as its manifest declares it; the broker derives the real names. const ForgeStream = "forge" @@ -156,8 +158,8 @@ type Forge struct { // that group with its approved head, this one included (novox/hq ADR 0633 §4): the forge bridge sets both from the // gate's merges state, and the indexer gathers the group's merges until the group's end (ForgeGroupEnd). A merge // with no group carries neither, and an older bridge sends neither; an older reader ignores both. - Group string `json:"group,omitempty"` - Members []delivery.Head `json:"members,omitempty"` + Group string `json:"group,omitempty"` + Members []meshdeliveryclient.Head `json:"members,omitempty"` } // checkGroup refuses a group on anything but a merged message, a group without its members or members without a @@ -187,7 +189,7 @@ func (f Forge) checkGroup() error { return fmt.Errorf("delivery: the group %q names its member %s twice", f.Group, k) } seen[k] = true - self = self || (delivery.SameRepository(m.Repository, f.Repository) && m.Number == f.Number) + self = self || (trunk.SameRepository(m.Repository, f.Repository) && m.Number == f.Number) } if !self { return fmt.Errorf("delivery: the members of the group %q leave out the merged pull request %s#%d", f.Group, f.Repository, f.Number) @@ -395,7 +397,7 @@ func checkName(kind, name string) error { return nil } -// decode reads a value of Version, refusing another or none with delivery.ErrUnknownVersion, as mesh-sdk delivery's +// decode reads a value of Version, refusing another or none with keys.ErrUnknownVersion, as mesh-sdk delivery's // values are read. func decode(data []byte, into any) error { var probe struct { @@ -405,10 +407,10 @@ func decode(data []byte, into any) error { return fmt.Errorf("delivery: value is not JSON: %w", err) } if probe.V == nil { - return fmt.Errorf("%w: no version in the value, this reader reads %d", delivery.ErrUnknownVersion, Version) + return fmt.Errorf("%w: no version in the value, this reader reads %d", keys.ErrUnknownVersion, Version) } if *probe.V != Version { - return fmt.Errorf("%w: the value is version %d, this reader reads %d", delivery.ErrUnknownVersion, *probe.V, Version) + return fmt.Errorf("%w: the value is version %d, this reader reads %d", keys.ErrUnknownVersion, *probe.V, Version) } if err := json.Unmarshal(data, into); err != nil { return fmt.Errorf("delivery: value does not fit its shape: %w", err) diff --git a/modules/mesh-delivery-forge-bridge/forgemsg/forge_test.go b/modules/mesh-delivery-forge-bridge/forgemsg/forge_test.go index 1ee2382a..9f2262cd 100644 --- a/modules/mesh-delivery-forge-bridge/forgemsg/forge_test.go +++ b/modules/mesh-delivery-forge-bridge/forgemsg/forge_test.go @@ -6,7 +6,8 @@ import ( "strings" "testing" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + "git.novox.be/novox/mesh-sdk/go/keys" ) const oddManifest = "{\n\t\"module\": \"a\",\n \"z\": 1, \"a\": [ \"x\",\"y\" ],\n \"why\": \"é — & \\n\"\n}\n\n" @@ -20,7 +21,7 @@ func sampleForges() []Forge { {Kind: ForgeOpen, Open: []OpenPull{{Repository: "novox/mesh-sdk", Number: 36}, {Repository: "novox/mesh-control", Number: 7}}}, // 1585/T1: a group member's merged message names its group and every approved member (ADR 0633 §4). {Kind: ForgeMerged, Repository: "novox/mesh-sdk", Number: 37, Head: "dddd", Merge: "eeee", Root: "t0", - Group: "feat/take-in", Members: []delivery.Head{{Repository: "novox/mesh-sdk", Number: 37, Head: "dddd"}, {Repository: "novox/mesh-delivery", Number: 9, Head: "ffff"}}}, + Group: "feat/take-in", Members: []meshdeliveryclient.Head{{Repository: "novox/mesh-sdk", Number: 37, Head: "dddd"}, {Repository: "novox/mesh-delivery", Number: 9, Head: "ffff"}}}, } } @@ -107,8 +108,8 @@ func TestForge(t *testing.T) { "group end": func(b []byte) error { _, err := DecodeForgeGroupEnd(b); return err }, } { for _, in := range []string{`{"group":"g"}`, `{"v":2,"group":"g"}`} { - if err := dec([]byte(in)); !errors.Is(err, delivery.ErrUnknownVersion) { - t.Errorf("%s decode of %s = %v, want delivery.ErrUnknownVersion", name, in, err) + if err := dec([]byte(in)); !errors.Is(err, keys.ErrUnknownVersion) { + t.Errorf("%s decode of %s = %v, want keys.ErrUnknownVersion", name, in, err) } } } @@ -158,7 +159,7 @@ func TestForge(t *testing.T) { {"open with locks", "only the open", Forge{Kind: ForgeOpen, Locks: map[string]map[string]string{"a": {"go.sum": "h"}}}}, // 1585/T1: a group only on a merged message, with its members, the message's own pull request among them. {"pull with a group", "only a merged message names its delivery group", at(0, func(f *Forge) { - f.Group, f.Members = "g", []delivery.Head{{Repository: "novox/mesh-sdk", Number: 36, Head: "aaaa"}} + f.Group, f.Members = "g", []meshdeliveryclient.Head{{Repository: "novox/mesh-sdk", Number: 36, Head: "aaaa"}} })}, {"merged with a group and no members", "together", at(4, func(f *Forge) { f.Members = nil })}, {"merged with members and no group", "together", at(4, func(f *Forge) { f.Group = "" })}, diff --git a/modules/mesh-delivery-forge-bridge/forgemsg/go.mod b/modules/mesh-delivery-forge-bridge/forgemsg/go.mod index f54fcd87..05e4246f 100644 --- a/modules/mesh-delivery-forge-bridge/forgemsg/go.mod +++ b/modules/mesh-delivery-forge-bridge/forgemsg/go.mod @@ -3,3 +3,8 @@ module git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forge go 1.26.0 require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659 + +require ( + git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000 +) diff --git a/modules/mesh-delivery-forge-bridge/go.mod b/modules/mesh-delivery-forge-bridge/go.mod index a79a64b0..f51b6c71 100644 --- a/modules/mesh-delivery-forge-bridge/go.mod +++ b/modules/mesh-delivery-forge-bridge/go.mod @@ -3,6 +3,9 @@ module mesh-delivery-forge-bridge go 1.26.0 require ( + git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174359-05b3e2e1b94f github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 diff --git a/modules/mesh-delivery-forge-bridge/internal/bridge/acts.go b/modules/mesh-delivery-forge-bridge/internal/bridge/acts.go index 32aae566..65e58eff 100644 --- a/modules/mesh-delivery-forge-bridge/internal/bridge/acts.go +++ b/modules/mesh-delivery-forge-bridge/internal/bridge/acts.go @@ -5,8 +5,9 @@ import ( "slices" "strings" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // Call is a call of a forge tool: the tool by its manifest key and its arguments. @@ -26,7 +27,7 @@ type Act struct { Call Call } -// The forge's tools, by manifest key; their arguments are the ones of mesh-catalog modules/gitea: delivery.ts +// The forge's tools, by manifest key; their arguments are the ones of mesh-catalog modules/gitea: meshdeliveryclient.ts // commitStatusTool (owner, repo, sha, context, state, description, target_url), tools/index.ts gitea_delivery_view // (owner, repo, number, body), gitea_note_append (owner, repo, sha, ref, line), gitea_add_comment (owner, repo, // number, body), merge.ts gitea_merge_pull_request (owner, repo, number, head, approved_heads). @@ -50,15 +51,15 @@ func ownerRepo(repository string) (owner, repo string) { // it. A key that is no owed key, a value that is no owed act, a value whose act is not the key's, and a view or a comment // on no pull request are errors, each saying why. func OwedAct(key string, value []byte) (Act, error) { - repository, number, head, name, err := delivery.ParseOwedKey(key) + repository, number, head, name, err := meshdeliveryclient.ParseOwedKey(key) if err != nil { return Act{}, err } - o, err := delivery.DecodeOwed(value) + o, err := meshdeliveryclient.DecodeOwed(value) if err != nil { return Act{}, fmt.Errorf("the owed act %s: %w", key, err) } - kind, context, err := delivery.ParseOwedAct(name) + kind, context, err := meshdeliveryclient.ParseOwedAct(name) if err != nil { return Act{}, err } @@ -74,26 +75,26 @@ func OwedAct(key string, value []byte) (Act, error) { return nil } switch kind { - case delivery.OwedStatus: + case meshdeliveryclient.OwedStatus: if o.Context != context { return Act{}, fmt.Errorf("the owed status %s says the context %q, not the %q its key names", key, o.Context, context) } - if o.Context == delivery.StatusContextCheck { + if o.Context == meshdeliveryclient.StatusContextCheck { // No tool writes mesh/check (R4e finding K1), and the bridge performs no act of it: the forge module writes it from // the checker's state mesh-delivery-checker.owed, read by a watch (ADR 0521, finding R2-1). return Act{}, fmt.Errorf("the owed status %s is %s, which the bridge does not perform: the forge module writes it from mesh-delivery-checker.owed", - key, delivery.StatusContextCheck) + key, meshdeliveryclient.StatusContextCheck) } a.Call = Call{Tool: commitStatusTool, Args: map[string]any{"owner": owner, "repo": repo, "sha": head, "context": o.Context, "state": o.State, "description": o.Text, "target_url": o.Target}} - case delivery.OwedView: + case meshdeliveryclient.OwedView: if err := needsPull(); err != nil { return Act{}, err } a.Call = Call{Tool: deliveryViewTool, Args: map[string]any{"owner": owner, "repo": repo, "number": number, "body": o.Text}} - case delivery.OwedNote: + case meshdeliveryclient.OwedNote: a.Call = Call{Tool: noteAppendTool, Args: map[string]any{"owner": owner, "repo": repo, "sha": head, "ref": o.Ref, "line": o.Text}} - case delivery.OwedComment: + case meshdeliveryclient.OwedComment: if err := needsPull(); err != nil { return Act{}, err } @@ -102,19 +103,19 @@ func OwedAct(key string, value []byte) (Act, error) { return a, nil } -// MergeActs turns the value of a merges key (mesh-sdk go/delivery EncodeMerge) into the merges to perform: one for each +// MergeActs turns the value of a merges key (the delivery client's EncodeMerge) into the merges to perform: one for each // pull request in the state merging, in the order the value names them, at the head it expects and with the approved // heads of every member of the group, `/@` sorted (the merge tool's approved_heads). A pull // request merged or refused already gets none. A value with no approved heads is an error: the forge's merge tool // needs them for a group. func MergeActs(value []byte) ([]Act, error) { - m, err := delivery.DecodeMerge(value) + m, err := meshdeliveryclient.DecodeMerge(value) if err != nil { return nil, err } - var handed []delivery.MergePull + var handed []seat.MergesByGroupValuePullsItem for _, p := range m.Pulls { - if p.State == delivery.MergeMerging { + if meshdeliveryclient.MergeState(p.State) == meshdeliveryclient.MergeMerging { handed = append(handed, p) } } @@ -136,8 +137,8 @@ func MergeActs(value []byte) ([]Act, error) { var acts []Act for _, p := range handed { owner, repo := ownerRepo(p.Repository) - acts = append(acts, Act{Name: MergeAct, Repository: p.Repository, Number: p.Number, Head: p.Head.Head, - Call: Call{Tool: mergePullTool, Args: map[string]any{"owner": owner, "repo": repo, "number": p.Number, "head": p.Head.Head, + acts = append(acts, Act{Name: MergeAct, Repository: p.Repository, Number: int(p.Number), Head: p.Head, + Call: Call{Tool: mergePullTool, Args: map[string]any{"owner": owner, "repo": repo, "number": int(p.Number), "head": p.Head, "approved_heads": approved}}}) } return acts, nil diff --git a/modules/mesh-delivery-forge-bridge/internal/bridge/acts_test.go b/modules/mesh-delivery-forge-bridge/internal/bridge/acts_test.go index c9b9fe2d..a1cb4669 100644 --- a/modules/mesh-delivery-forge-bridge/internal/bridge/acts_test.go +++ b/modules/mesh-delivery-forge-bridge/internal/bridge/acts_test.go @@ -5,8 +5,10 @@ import ( "strings" "testing" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/keys" ) const ( @@ -15,13 +17,13 @@ const ( ) // owedValue is an owed value as the SDK writes it (mesh-sdk go/delivery EncodeOwed), and its key as OwedKey spells it. -func owedValue(t *testing.T, repository string, number int, act string, o delivery.Owed) (string, []byte) { +func owedValue(t *testing.T, repository string, number int, act string, o seat.OwedByRepositoryNumberHeadActValue) (string, []byte) { t.Helper() - key, err := delivery.OwedKey(repository, number, headA, act) + key, err := meshdeliveryclient.OwedKey(repository, number, headA, act) if err != nil { t.Fatal(err) } - b, err := delivery.EncodeOwed(o) + b, err := meshdeliveryclient.EncodeOwed(o) if err != nil { t.Fatal(err) } @@ -30,7 +32,7 @@ func owedValue(t *testing.T, repository string, number int, act string, o delive func TestOwedStatusIsACommitStatusCall(t *testing.T) { // 1341/AC1, K1 key, v := owedValue(t, "novox/mesh-delivery", 12, "status=mesh/merge-gate", - delivery.Owed{Act: delivery.OwedStatus, Context: "mesh/merge-gate", State: "pending", Text: "waiting", Target: "https://forge.example/novox/mesh-delivery/pulls/12"}) + seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: "mesh/merge-gate", State: "pending", Text: "waiting", Target: "https://forge.example/novox/mesh-delivery/pulls/12"}) a, err := OwedAct(key, v) if err != nil { t.Fatal(err) @@ -46,26 +48,26 @@ func TestOwedStatusIsACommitStatusCall(t *testing.T) { // 1341/AC1, K1 // R4e findings K1 and R2-1 (issue 1301 S4; ADR 0503 §3, ADR 0521): no tool writes mesh/check, since a tool is told no // caller and node-tools invokes every tool; the forge module writes it from the checker's owed state, and the bridge // performs no mesh/check act. - ck, cv := checkerKV(t, delivery.Owed{Act: delivery.OwedStatus, Context: delivery.StatusContextCheck, State: "success", Text: "pass", Target: "https://view.example/checks/g.1"}) + ck, cv := checkerKV(t, seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: meshdeliveryclient.StatusContextCheck, State: "success", Text: "pass", Target: "https://view.example/checks/g.1"}) if a, err := OwedAct(ck, cv); err == nil || !strings.Contains(err.Error(), "mesh-delivery-checker.owed") { t.Fatalf("OwedAct(mesh/check) = %+v, %v; want refused, naming where the forge module reads it", a, err) } // Nor through the gate's own owed bucket: the context is refused whichever key carries it. gk, gv := owedValue(t, "novox/mesh-delivery", 12, "status=mesh/check", - delivery.Owed{Act: delivery.OwedStatus, Context: delivery.StatusContextCheck, State: "success", Text: "pass", Target: "https://x.example"}) + seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: meshdeliveryclient.StatusContextCheck, State: "success", Text: "pass", Target: "https://x.example"}) if a, err := OwedAct(gk, gv); err == nil || !strings.Contains(err.Error(), "mesh-delivery-checker.owed") { t.Fatalf("OwedAct(gate's mesh/check) = %+v, %v; want refused", a, err) } } -// checkerKV is the status the checker owes, as it writes it (mesh-sdk go/delivery CheckerOwedKey, EncodeCheckerOwed). -func checkerKV(t *testing.T, o delivery.Owed) (string, []byte) { +// checkerKV is the status the checker owes, as it writes it (the gate's owed key with the act status=mesh/check). +func checkerKV(t *testing.T, o seat.OwedByRepositoryNumberHeadActValue) (string, []byte) { t.Helper() - key, err := delivery.CheckerOwedKey("novox/mesh-delivery", 12, headA) + key, err := meshdeliveryclient.OwedKey("novox/mesh-delivery", 12, headA, "status=mesh/check") if err != nil { t.Fatal(err) } - v, err := delivery.EncodeCheckerOwed(o) + v, err := meshdeliveryclient.EncodeOwed(o) if err != nil { t.Fatal(err) } @@ -75,14 +77,14 @@ func checkerKV(t *testing.T, o delivery.Owed) (string, []byte) { func TestOwedViewNoteAndCommentCalls(t *testing.T) { // 1341/AC2 cases := []struct { act string - o delivery.Owed + o seat.OwedByRepositoryNumberHeadActValue want Call }{ - {"view", delivery.Owed{Act: delivery.OwedView, Text: "the view"}, + {"view", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedView, Text: "the view"}, Call{Tool: "gitea.gitea_delivery_view", Args: map[string]any{"owner": "novox", "repo": "mesh-delivery", "number": 12, "body": "the view"}}}, - {"note", delivery.Owed{Act: delivery.OwedNote, Text: "a line", Ref: "mesh-plan"}, + {"note", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedNote, Text: "a line", Ref: "mesh-plan"}, Call{Tool: "gitea.gitea_note_append", Args: map[string]any{"owner": "novox", "repo": "mesh-delivery", "sha": headA, "ref": "mesh-plan", "line": "a line"}}}, - {"comment.2", delivery.Owed{Act: delivery.OwedComment, Text: "hello"}, + {"comment.2", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedComment, Text: "hello"}, Call{Tool: "gitea.gitea_add_comment", Args: map[string]any{"owner": "novox", "repo": "mesh-delivery", "number": 12, "body": "hello"}}}, } for _, c := range cases { @@ -98,11 +100,11 @@ func TestOwedViewNoteAndCommentCalls(t *testing.T) { // 1341/AC2 } func TestOwedActWithoutAPullRequestNeedsOneOnlyWhereTheToolDoes(t *testing.T) { - key, v := owedValue(t, "novox/mesh-delivery", 0, "note", delivery.Owed{Act: delivery.OwedNote, Text: "l", Ref: "mesh-plan"}) + key, v := owedValue(t, "novox/mesh-delivery", 0, "note", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedNote, Text: "l", Ref: "mesh-plan"}) if _, err := OwedAct(key, v); err != nil { t.Errorf("a note on a commit no pull request names: %v", err) } - key, v = owedValue(t, "novox/mesh-delivery", 0, "view", delivery.Owed{Act: delivery.OwedView, Text: "v"}) + key, v = owedValue(t, "novox/mesh-delivery", 0, "view", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedView, Text: "v"}) if _, err := OwedAct(key, v); err == nil { t.Error("a view of no pull request was taken") } @@ -110,12 +112,12 @@ func TestOwedActWithoutAPullRequestNeedsOneOnlyWhereTheToolDoes(t *testing.T) { func TestOwedActThatDisagreesWithItsKeyIsRefused(t *testing.T) { // A dotted repository's key is unescaped, and its act names the repository whole. - dk, dv := owedValue(t, "novox/my.repo", 3, "comment.1", delivery.Owed{Act: delivery.OwedComment, Text: "x"}) + dk, dv := owedValue(t, "novox/my.repo", 3, "comment.1", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedComment, Text: "x"}) if a, err := OwedAct(dk, dv); err != nil || a.Call.Args["repo"] != "my.repo" || a.Repository != "novox/my.repo" { t.Errorf("a dotted repository's act = %+v, %v", a, err) } - key, _ := owedValue(t, "novox/mesh-delivery", 12, "view", delivery.Owed{Act: delivery.OwedView, Text: "v"}) - v, _ := delivery.EncodeOwed(delivery.Owed{Act: delivery.OwedComment, Text: "v"}) + key, _ := owedValue(t, "novox/mesh-delivery", 12, "view", seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedView, Text: "v"}) + v, _ := meshdeliveryclient.EncodeOwed(seat.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedComment, Text: "v"}) if _, err := OwedAct(key, v); err == nil { t.Error("a comment under a view's key was taken") } @@ -127,9 +129,9 @@ func TestOwedActThatDisagreesWithItsKeyIsRefused(t *testing.T) { } } -func mergeValue(t *testing.T, m delivery.Merge) []byte { +func mergeValue(t *testing.T, m seat.MergesByGroupValue) []byte { t.Helper() - b, err := delivery.EncodeMerge(m) + b, err := meshdeliveryclient.EncodeMerge(m) if err != nil { t.Fatal(err) } @@ -137,10 +139,10 @@ func mergeValue(t *testing.T, m delivery.Merge) []byte { } func TestMergeActsHandOnlyTheMemberHandedWithEveryApprovedHead(t *testing.T) { // 1342/AC1 - approved := map[string]string{delivery.ApprovedKey("novox/mesh-sdk", 4): headB, delivery.ApprovedKey("novox/mesh-delivery", 12): headA} - v := mergeValue(t, delivery.Merge{Group: "g", Ask: "merge.g.0123456789ab", Approved: approved, Pulls: []delivery.MergePull{ - {Head: delivery.Head{Repository: "novox/mesh-sdk", Number: 4, Head: headB}, State: delivery.MergeMerged, Commit: "c0"}, - {Head: delivery.Head{Repository: "novox/mesh-delivery", Number: 12, Head: headA}, State: delivery.MergeMerging}, + approved := map[string]string{meshdeliveryclient.ApprovedKey("novox/mesh-sdk", 4): headB, meshdeliveryclient.ApprovedKey("novox/mesh-delivery", 12): headA} + v := mergeValue(t, seat.MergesByGroupValue{Group: "g", Ask: "merge.g.0123456789ab", Approved: approved, Pulls: []seat.MergesByGroupValuePullsItem{ + {Repository: "novox/mesh-sdk", Number: 4, Head: headB, State: string(meshdeliveryclient.MergeMerged), Commit: "c0"}, + {Repository: "novox/mesh-delivery", Number: 12, Head: headA, State: string(meshdeliveryclient.MergeMerging)}, }}) acts, err := MergeActs(v) if err != nil { @@ -158,8 +160,8 @@ func TestMergeActsHandOnlyTheMemberHandedWithEveryApprovedHead(t *testing.T) { / t.Errorf("act = %+v", acts[0]) } // A merge with no approved heads is refused: the forge's tool would refuse it with nothing to say which. - v = mergeValue(t, delivery.Merge{Group: "g", Ask: "a", Pulls: []delivery.MergePull{ - {Head: delivery.Head{Repository: "novox/mesh-delivery", Number: 12, Head: headA}, State: delivery.MergeMerging}}}) + v = mergeValue(t, seat.MergesByGroupValue{Group: "g", Ask: "a", Pulls: []seat.MergesByGroupValuePullsItem{ + {Repository: "novox/mesh-delivery", Number: 12, Head: headA, State: string(meshdeliveryclient.MergeMerging)}}}) if _, err := MergeActs(v); err == nil { t.Error("a merge with no approved heads was taken") } @@ -178,7 +180,7 @@ func TestRefusedMessageNamesTheActAndTheForgesWords(t *testing.T) { // 1341/AC3, if err != nil { t.Fatal(err) } - want := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgeRefused, Repository: "novox/mesh-delivery", Number: 12, Head: headA, + want := forgemsg.Forge{V: keys.Version, Kind: forgemsg.ForgeRefused, Repository: "novox/mesh-delivery", Number: 12, Head: headA, Act: "merge", Words: "novox/mesh-delivery#12 is not merged: its head moved"} if !forgemsg.SameForge(f, want) { t.Errorf("message = %+v", f) diff --git a/modules/mesh-delivery-forge-bridge/internal/bridge/bridge.go b/modules/mesh-delivery-forge-bridge/internal/bridge/bridge.go index 6af03fb1..a920e158 100644 --- a/modules/mesh-delivery-forge-bridge/internal/bridge/bridge.go +++ b/modules/mesh-delivery-forge-bridge/internal/bridge/bridge.go @@ -17,7 +17,7 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/keys" ) // ErrUnreadable is wrapped by every error that says an event cannot be turned into a message; it is never @@ -118,7 +118,7 @@ func branchDeleted(subject string, data []byte) (Message, error) { case branch == Trunk: return Message{}, fmt.Errorf("%w: %s of the trunk %s", ErrNotHandled, subject, Trunk) } - body, err := json.Marshal(BranchDeleted{V: delivery.Version, Kind: ForgeBranchDeleted, Repository: d.Owner + "/" + d.Repo, Branch: branch, Head: d.SHA}) + body, err := json.Marshal(BranchDeleted{V: keys.Version, Kind: ForgeBranchDeleted, Repository: d.Owner + "/" + d.Repo, Branch: branch, Head: d.SHA}) if err != nil { return Message{}, unreadable(subject, "%v", err) } diff --git a/modules/mesh-delivery-forge-bridge/internal/bridge/bridge_test.go b/modules/mesh-delivery-forge-bridge/internal/bridge/bridge_test.go index 0c504bb5..9da7668e 100644 --- a/modules/mesh-delivery-forge-bridge/internal/bridge/bridge_test.go +++ b/modules/mesh-delivery-forge-bridge/internal/bridge/bridge_test.go @@ -7,7 +7,7 @@ import ( "testing" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/keys" ) const head = "0123456789abcdef0123456789abcdef01234567" @@ -59,7 +59,7 @@ func TestUpdatedBecomesAPullMessage(t *testing.T) { // 1235/AC2 (the conversion; if err != nil { t.Fatal(err) } - want := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, + want := forgemsg.Forge{V: keys.Version, Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Action: forgemsg.PullUpdated, Dirs: []string{"modules/a"}, DirsKnown: true, Manifests: map[string]string{"modules/a": "{}"}} if !forgemsg.SameForge(f, want) { t.Errorf("message = %+v, want %+v", f, want) @@ -105,7 +105,7 @@ func TestClosedBecomesAClosedPullMessage(t *testing.T) { // 1235/AC4 if err != nil { t.Fatal(err) } - want := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, + want := forgemsg.Forge{V: keys.Version, Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Action: forgemsg.PullClosed} if !forgemsg.SameForge(f, want) { t.Errorf("message = %+v, want %+v", f, want) @@ -252,7 +252,7 @@ func TestMergeIntoMainBecomesAMergedMessage(t *testing.T) { // 1236/AC1 if err != nil { t.Fatal(err) } - want := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, + want := forgemsg.Forge{V: keys.Version, Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Merge: merge, Root: "r0", Trees: answer, Manifests: map[string]string{"modules/a": "{}", "modules/b": "{}"}} if !forgemsg.SameForge(f, want) { t.Errorf("message = %+v, want %+v", f, want) @@ -272,7 +272,7 @@ func TestMergeIntoAnotherBranchIsAClosedPull(t *testing.T) { // 1236/AC2 if err != nil { t.Fatal(err) } - want := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, + want := forgemsg.Forge{V: keys.Version, Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Action: forgemsg.PullClosed} if msg.Event != "forge.novox.mesh-delivery.pull" || !forgemsg.SameForge(f, want) { t.Errorf("event %q message %+v, want a closed pull message", msg.Event, f) @@ -304,7 +304,7 @@ func TestNoModuleJSONAnywhereWritesAMergedMessageWithNoTrees(t *testing.T) { t.Fatal(err) } f, err := forgemsg.DecodeForge(msg.Data) - want := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, + want := forgemsg.Forge{V: keys.Version, Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Merge: merge, Root: "r0"} if err != nil || msg.Event != "forge.novox.mesh-delivery.merged" || !forgemsg.SameForge(f, want) || !strings.Contains(msg.Said, "no module.json") { t.Errorf("event %q message %+v said %q, %v; want a merged message with no trees", msg.Event, f, msg.Said, err) @@ -330,8 +330,8 @@ func TestOpenPullsAreUpdatedPullMessagesWithUnknownDirs(t *testing.T) { // 1237/ t.Fatalf("msgs %d skipped %v err %v", len(msgs), skipped, err) } for i, want := range []forgemsg.Forge{ - {V: delivery.Version, Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Action: forgemsg.PullUpdated}, - {V: delivery.Version, Kind: forgemsg.ForgePull, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 300, Head: h2, Action: forgemsg.PullUpdated}, + {V: keys.Version, Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Action: forgemsg.PullUpdated}, + {V: keys.Version, Kind: forgemsg.ForgePull, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 300, Head: h2, Action: forgemsg.PullUpdated}, } { f, err := forgemsg.DecodeForge(msgs[i].Data) if err != nil || !forgemsg.SameForge(f, want) { diff --git a/modules/mesh-delivery-forge-bridge/internal/groups/groups.go b/modules/mesh-delivery-forge-bridge/internal/groups/groups.go index 8b731a47..e3ddbc03 100644 --- a/modules/mesh-delivery-forge-bridge/internal/groups/groups.go +++ b/modules/mesh-delivery-forge-bridge/internal/groups/groups.go @@ -16,17 +16,20 @@ import ( "strings" "time" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" ) // The bridge's states the tools read and write. const ( + // Module is the forge bridge's own name (its manifest's module, checked by the bridge's names test). + Module = "mesh-delivery-forge-bridge" // PerformedState is the bridge's state of what it performed and published. PerformedState = "performed" // EndAsksState is the state of asks to say a group's end again, keyed by the gate's merges key of the group. EndAsksState = "end-asks" // EndAsksBucket is its bucket on the bus. - EndAsksBucket = delivery.ForgeBridgeModule + "_" + EndAsksState + EndAsksBucket = Module + "_" + EndAsksState ) // Key is the key of performed that says a group's end is written: `groups.`, its value the revision of the @@ -75,7 +78,7 @@ func NewEndAsk(group, why, by string, at time.Time) (string, []byte, error) { if group == "" { return "", nil, errors.New("name the group whose end is said again (group)") } - key, err := delivery.MergesKey(group) + key, err := meshdeliveryclient.MergesKey(group) if err != nil { return "", nil, err } @@ -134,7 +137,7 @@ func Rows(merges map[string][]byte, performed map[string]bool, endedAt map[strin lastAsk[key] = &a } for key, raw := range merges { - m, err := delivery.DecodeMerge(raw) + m, err := meshdeliveryclient.DecodeMerge(raw) if err != nil { unread = append(unread, key) continue @@ -145,9 +148,9 @@ func Rows(merges map[string][]byte, performed map[string]bool, endedAt map[strin r := Row{Key: key, Group: m.Group} allMerged := true for _, p := range m.Pulls { - pub := p.Commit != "" && performed[PublishedKey(p.Repository, p.Number, p.Commit)] - r.Members = append(r.Members, RowMember{Repository: p.Repository, Number: p.Number, State: string(p.State), Commit: p.Commit, Published: pub}) - if p.State != delivery.MergeMerged || !pub { + pub := p.Commit != "" && performed[PublishedKey(p.Repository, int(p.Number), p.Commit)] + r.Members = append(r.Members, RowMember{Repository: p.Repository, Number: int(p.Number), State: p.State, Commit: p.Commit, Published: pub}) + if meshdeliveryclient.MergeState(p.State) != meshdeliveryclient.MergeMerged || !pub { allMerged = false } } @@ -175,8 +178,8 @@ func Rows(merges map[string][]byte, performed map[string]bool, endedAt map[strin func sortRows(rows []Row) { sort.Slice(rows, func(i, j int) bool { return rows[i].Key < rows[j].Key }) } // MembersOf are a merge's approved members with their heads, in the order of their keys. -func MembersOf(m delivery.Merge) []delivery.Head { - var out []delivery.Head +func MembersOf(m seat.MergesByGroupValue) []meshdeliveryclient.Head { + var out []meshdeliveryclient.Head for _, k := range slices.Sorted(func(yield func(string) bool) { for k := range m.Approved { if !yield(k) { @@ -192,7 +195,7 @@ func MembersOf(m delivery.Merge) []delivery.Head { if err != nil { continue } - out = append(out, delivery.Head{Repository: k[:at], Number: n, Head: m.Approved[k]}) + out = append(out, meshdeliveryclient.Head{Repository: k[:at], Number: n, Head: m.Approved[k]}) } return out }