From ebe1248f0bf54423bcab2d19ef030c8610ce5b26 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 00:06:59 +0100 Subject: [PATCH] Gate: read the forge bridge's messages from its own package forgemsg, and pin mesh-sdk at main (issue 1585, ADR 0633) Forge, its kinds and the group's end moved out of mesh-sdk into the forge bridge's own package (mesh-delivery modules/mesh-delivery-forge-bridge/forgemsg, the operator, 2026-11-04: logic one module owns lives with it). The gate imports it at the mesh-delivery head 4e4601a1 that holds the move, and the suffix it passes by before decoding is the package's own ForgeGroupEndEvent, not a constant of its own. mesh-sdk is pinned at 5a38a12, origin/main. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn --- .../cmd/mesh-delivery-gate/bridge_test.go | 15 ++++++++------- .../cmd/mesh-delivery-gate/bus_test.go | 13 +++++++------ .../cmd/mesh-delivery-gate/holder_test.go | 6 +++--- .../cmd/mesh-delivery-gate/issue1275_test.go | 3 ++- .../cmd/mesh-delivery-gate/issue1348_test.go | 9 +++++---- .../cmd/mesh-delivery-gate/merges.go | 18 ++++++++++-------- modules/mesh-delivery-gate/go.mod | 3 ++- modules/mesh-delivery-gate/go.sum | 6 ++++-- 8 files changed, 41 insertions(+), 32 deletions(-) diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/bridge_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/bridge_test.go index 1749fe5a1..e27803c32 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/bridge_test.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/bridge_test.go @@ -7,6 +7,7 @@ import ( "strings" "sync" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" sdk "git.novox.be/novox/mesh-sdk/go/delivery" ) @@ -24,7 +25,7 @@ type fakeMerges struct { manual bool failed error // said are the messages the bridge wrote on FORGE, in order. - said []sdk.Forge + said []forgemsg.Forge } func newFakeMerges(f *fakeForge) *fakeMerges { @@ -76,11 +77,11 @@ func (m *fakeMerges) Put(group string, v sdk.Merge) error { err = f.refuse } f.mu.Unlock() - msg := sdk.Forge{Repository: p.Repository, Number: p.Number, Head: p.Head.Head} + msg := forgemsg.Forge{Repository: p.Repository, Number: p.Number, Head: p.Head.Head} if err != nil { - msg.Kind, msg.Act, msg.Words = sdk.ForgeRefused, "merge", err.Error() + msg.Kind, msg.Act, msg.Words = forgemsg.ForgeRefused, "merge", err.Error() } else { - msg.Kind, msg.Merge, msg.Root = sdk.ForgeMerged, strings.Repeat("c", 40), "root" + msg.Kind, msg.Merge, msg.Root = forgemsg.ForgeMerged, strings.Repeat("c", 40), "root" msg.Trees = map[string]string{"app": "tree"} } m.say(msg) @@ -89,12 +90,12 @@ func (m *fakeMerges) Put(group string, v sdk.Merge) error { } // say writes a message on FORGE and has the gate hear it, as the runtime does. -func (m *fakeMerges) say(msg sdk.Forge) { - body, err := sdk.EncodeForge(msg) +func (m *fakeMerges) say(msg forgemsg.Forge) { + body, err := forgemsg.EncodeForge(msg) if err != nil { panic(err) } - event, err := sdk.ForgeEvent(msg.Repository, msg.Kind) + event, err := forgemsg.ForgeEvent(msg.Repository, msg.Kind) if err != nil { panic(err) } diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/bus_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/bus_test.go index d4f8777a9..44237dcb3 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/bus_test.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/bus_test.go @@ -16,6 +16,7 @@ import ( "testing" "time" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/asks" sdk "git.novox.be/novox/mesh-sdk/go/delivery" @@ -582,7 +583,7 @@ func TestAPassingVerdictAsksTheApproveAndItsApproveMergesWithinSecondsOverARealB t.Fatalf("the gate asked the forge to merge %d times: the bridge merges", n) } // The bridge says it merged; the mesh's source then holds the merge, in one write. - b.forgeSays(t, sdk.Forge{Kind: sdk.ForgeMerged, Repository: "novox/app", Number: 7, Head: head, Merge: strings.Repeat("b", 40), + b.forgeSays(t, forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/app", Number: 7, Head: head, Merge: strings.Repeat("b", 40), Root: "r", Trees: map[string]string{"app": "t"}}) // The forge's own word that the pull request merged follows the bridge's (its webhook): the delivery ends merged. b.publish(t, "mesh.mod.gitea.event.pull.merged", merged("novox/app", 7, head, strings.Repeat("b", 40))) @@ -677,13 +678,13 @@ func (b *testBus) answer(t *testing.T, key, option string) time.Time { } // forgeSays is the forge bridge's message on FORGE, as its stream carries it. -func (b *testBus) forgeSays(t *testing.T, f sdk.Forge) { +func (b *testBus) forgeSays(t *testing.T, f forgemsg.Forge) { t.Helper() - raw, err := sdk.EncodeForge(f) + raw, err := forgemsg.EncodeForge(f) if err != nil { t.Fatal(err) } - event, err := sdk.ForgeEvent(f.Repository, f.Kind) + event, err := forgemsg.ForgeEvent(f.Repository, f.Kind) if err != nil { t.Fatal(err) } @@ -742,7 +743,7 @@ func TestAGroupOfTwoWithGreenChecksGetsOneAskAndOnItsApproveBothMergeOneAfterThe t.Fatalf("the second member was handed before the first merged: %+v", again.Pulls) } one := first.Pulls[0] - b.forgeSays(t, sdk.Forge{Kind: sdk.ForgeMerged, Repository: one.Repository, Number: one.Number, Head: one.Head.Head, + b.forgeSays(t, forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: one.Repository, Number: one.Number, Head: one.Head.Head, Merge: strings.Repeat("a", 40), Root: "r", Trees: map[string]string{"x": "t"}}) var both sdk.Merge rt.until("the second member to be handed", eventWithin, func() bool { both = mergeAt(b, "feat/x"); return len(both.Pulls) == 2 }) @@ -750,7 +751,7 @@ func TestAGroupOfTwoWithGreenChecksGetsOneAskAndOnItsApproveBothMergeOneAfterThe t.Fatalf("the second merge handed is %+v", both.Pulls) } two := both.Pulls[1] - b.forgeSays(t, sdk.Forge{Kind: sdk.ForgeMerged, Repository: two.Repository, Number: two.Number, Head: two.Head.Head, + b.forgeSays(t, forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: two.Repository, Number: two.Number, Head: two.Head.Head, Merge: strings.Repeat("b", 40), Root: "r", Trees: map[string]string{"x": "t"}}) rt.until("source.main to hold both merges", eventWithin, func() bool { e, err := b.kvs["source"].Get(ctx, sdk.SourceMainKey) diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/holder_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/holder_test.go index fd87a0172..7bea50f41 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/holder_test.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/holder_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - sdk "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" ) // The holder, end to end over fakes (novox/hq ADR 0239 *How it is checked*, cut by ADR 0539 §17): a pull request's head @@ -241,9 +241,9 @@ func TestAMergedGroupEndsMergedWithSourceMainMoved(t *testing.T) { t.Fatalf("source.main is %+v, wanted both merges", w.source.main) } // A merge that leaves no module.json carries no trees (issue 1367): the gate reads it, and refuses nothing. - msg := sdk.Forge{Kind: sdk.ForgeMerged, Repository: "novox/a", Number: 9, Head: strings.Repeat("e", 40), + msg := forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/a", Number: 9, Head: strings.Repeat("e", 40), Merge: strings.Repeat("f", 40), Root: "r"} - body, err := sdk.EncodeForge(msg) + body, err := forgemsg.EncodeForge(msg) if err != nil { t.Fatalf("a merged message with no trees is refused: %v", err) } diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1275_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1275_test.go index 73d88678f..b6143c64c 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1275_test.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1275_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" "git.novox.be/novox/mesh-sdk/go/asks" sdk "git.novox.be/novox/mesh-sdk/go/delivery" ) @@ -765,6 +766,6 @@ func allGreenFor(w *world, ms ...member) { // bridgeMerged is the forge bridge's word that it merged a member. func bridgeMerged(w *world, repository string, number int, head, merge string) { - w.merges.say(sdk.Forge{Kind: sdk.ForgeMerged, Repository: repository, Number: number, Head: head, Merge: merge, Root: "root", + w.merges.say(forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: repository, Number: number, Head: head, Merge: merge, Root: "root", Trees: map[string]string{"app": "tree"}}) } diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1348_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1348_test.go index 98a9cac5b..aebe95ed6 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1348_test.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/issue1348_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" "git.novox.be/novox/mesh-sdk/go/asks" sdk "git.novox.be/novox/mesh-sdk/go/delivery" ) @@ -100,7 +101,7 @@ func TestAnAnswerHandsTheFirstMemberOnlyAndTheBridgesMergedHandsTheNext(t *testi if len(w.merges.puts) != 1 { t.Fatalf("the next member was handed before the bridge said the first merged: %d merges written", len(w.merges.puts)) } - w.merges.say(sdk.Forge{Kind: sdk.ForgeMerged, Repository: gCat, Number: 2, Head: gHk, Merge: strings.Repeat("a", 40), Root: "r", Trees: map[string]string{"x": "t"}}) + w.merges.say(forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: gCat, Number: 2, Head: gHk, Merge: strings.Repeat("a", 40), Root: "r", Trees: map[string]string{"x": "t"}}) w.settleAll() if len(w.merges.puts) != 2 { t.Fatalf("the second member was not handed after the first merged: %d merges written", len(w.merges.puts)) @@ -115,7 +116,7 @@ func TestAnAnswerHandsTheFirstMemberOnlyAndTheBridgesMergedHandsTheNext(t *testi // AC2: the bridge's refused hands no member, and the group is said urgent, naming the member and the forge's words. func TestTheBridgesRefusalHandsNoMemberAndIsSaidUrgent(t *testing.T) { w := answeredFirst(t) - w.merges.say(sdk.Forge{Kind: sdk.ForgeRefused, Repository: gCat, Number: 2, Head: gHk, Act: "merge", + w.merges.say(forgemsg.Forge{Kind: forgemsg.ForgeRefused, Repository: gCat, Number: 2, Head: gHk, Act: "merge", Words: "the forge refused gitea_merge_pull_request: mesh/repo-check (missing)"}) w.later(time30s) w.settleAll() @@ -137,7 +138,7 @@ func TestTheBridgesRefusalHandsNoMemberAndIsSaidUrgent(t *testing.T) { // the group, even after the first member merged. func TestTheNextMemberIsNotHandedWhileARequiredStatusOfTheGroupIsNotASuccess(t *testing.T) { w := answeredFirst(t) - w.merges.say(sdk.Forge{Kind: sdk.ForgeMerged, Repository: gCat, Number: 2, Head: gHk, Merge: strings.Repeat("a", 40), Root: "r", Trees: map[string]string{"x": "t"}}) + w.merges.say(forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: gCat, Number: 2, Head: gHk, Merge: strings.Repeat("a", 40), Root: "r", Trees: map[string]string{"x": "t"}}) setChecks(w, gCtl, gHc, "success", "") // the second member's repository check went missing meanwhile w.settleAll() if len(w.merges.puts) != 1 { @@ -193,7 +194,7 @@ func TestARefusalThatRepeatsWithNoTrunkMoveIsNotAskedAgainWithoutEnd(t *testing. w := answeredFirst(t) d := w.h.deliveries[IDOf(gCat, gHk)] refuse := func(took string) { - w.h.ForgeRefused(sdk.Forge{Kind: sdk.ForgeRefused, Repository: gCat, Number: 2, Head: gHk, Act: "merge", + w.h.ForgeRefused(forgemsg.Forge{Kind: forgemsg.ForgeRefused, Repository: gCat, Number: 2, Head: gHk, Act: "merge", Words: "novox/mesh-catalog#2 is not merged: its verdict names no trunk commit it was judged against, and main is now f70a99dbf485. " + "The check of head d2d2d2d2d2d2 merged into trunk f70a99dbf485 was asked; the last check took " + took + ". Ask again once both statuses pass"}) } diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/merges.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/merges.go index f8628af46..aa5c17af0 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/merges.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/merges.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" stdio "git.novox.be/novox/mesh-sdk/go" sdk "git.novox.be/novox/mesh-sdk/go/delivery" ) @@ -312,8 +313,9 @@ func (h *Holder) mergeValue(name, key string, approved map[string]string, target return m, len(m.Pulls) > 0 && state == sdk.MergeMerged } -// forgeGroupEnd ends the subject of a group end on FORGE: the four-part form of a pull request's message has no such tail. -const forgeGroupEnd = ".forge.group.merged" +// forgeGroupEnd ends the subject of a group end on FORGE, the forge bridge's event name: the four-part form of a pull +// request's message has no such tail. +const forgeGroupEnd = "." + forgemsg.ForgeGroupEndEvent // Forged takes a message of the forge bridge on FORGE: the gate follows a merge and the refusal of a merge, and nothing else. // @@ -323,21 +325,21 @@ func (h *Holder) Forged(event string, body []byte) { if strings.HasSuffix(event, forgeGroupEnd) { return } - f, err := sdk.DecodeForge(body) + f, err := forgemsg.DecodeForge(body) if err != nil { h.Refuse(fmt.Errorf("a message of the forge bridge is not readable: %v", err)) return } switch { - case f.Kind == sdk.ForgeMerged: + case f.Kind == forgemsg.ForgeMerged: h.ForgeMerged(f) - case f.Kind == sdk.ForgeRefused && f.Act == "merge": + case f.Kind == forgemsg.ForgeRefused && f.Act == "merge": h.ForgeRefused(f) } } // memberOfForge is the delivery a forge message is about: at the head it names. -func (h *Holder) memberOfForge(f sdk.Forge) *Delivery { +func (h *Holder) memberOfForge(f forgemsg.Forge) *Delivery { if d := h.deliveries[IDOf(f.Repository, f.Head)]; d != nil && d.Number == f.Number { return d } @@ -346,7 +348,7 @@ func (h *Holder) memberOfForge(f sdk.Forge) *Delivery { // ForgeMerged takes the bridge's word that a pull request the gate handed to it merged: the merge is announced once, // with the ask, its asker the teller and the heads (issue 1025), and the next member's turn comes at the next look. -func (h *Holder) ForgeMerged(f sdk.Forge) { +func (h *Holder) ForgeMerged(f forgemsg.Forge) { h.mu.Lock() defer h.mu.Unlock() d := h.memberOfForge(f) @@ -363,7 +365,7 @@ func (h *Holder) ForgeMerged(f sdk.Forge) { // ForgeRefused takes the bridge's word that the forge refused a merge: nothing after it is merged, and the refusal is said // as a stalled line (ADR 0441 §3, §4). The merge is handed again only at the next event that decides whether it may // merge (mergeAgainNow). -func (h *Holder) ForgeRefused(f sdk.Forge) { +func (h *Holder) ForgeRefused(f forgemsg.Forge) { h.mu.Lock() defer h.mu.Unlock() d := h.memberOfForge(f) diff --git a/modules/mesh-delivery-gate/go.mod b/modules/mesh-delivery-gate/go.mod index c0544f393..1f467a4f1 100644 --- a/modules/mesh-delivery-gate/go.mod +++ b/modules/mesh-delivery-gate/go.mod @@ -3,7 +3,8 @@ module mesh-delivery-gate go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 + git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg v0.0.0-20261104230501-4e4601a1eb90 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659 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-gate/go.sum b/modules/mesh-delivery-gate/go.sum index abad29696..6120abbd2 100644 --- a/modules/mesh-delivery-gate/go.sum +++ b/modules/mesh-delivery-gate/go.sum @@ -1,5 +1,7 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929 h1:RtAFbUJRP1qO9AIMM2ue+cXNzqj7WZ7IOlSqNvttwuU= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103174208-b7abe203a929/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg v0.0.0-20261104230501-4e4601a1eb90 h1:8akSVoPjgngMbC1JIstcUzfKyYs13gDaboDRKC4NQ/A= +git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg v0.0.0-20261104230501-4e4601a1eb90/go.mod h1:Xxg9VI4y755tznAS2PygQFe2V3/aIzjRn3/06+Br9nQ= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659 h1:tK7MzxVm/ECNg1Yblhh1ynVEthbZCg2mSPW0wQZC0b0= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=