Take-in and forge contracts live with their owners: the indexer's takein package, the forge bridge's forgemsg package; mesh-sdk pinned at main (issue 1585, ADR 0633)
The operator, 2026-11-04: logic one module owns lives inside that module as a package of its own, so the take-in step changes no mesh-sdk. TakeIn, its keys and event names, PendingWait and PendingAnswer move to modules/mesh-delivery-indexer/takein (its merges a takein.Merge of its own); Forge whole, with its group fields, ForgeGroupEnd and GroupMerge, moves to modules/mesh-delivery-forge-bridge/forgemsg. Both are nested Go modules that their readers take by replace, as buildrun is. Every importer moves in this commit: the indexer, the bridge, the watchman and the view. Forge's private checks (the repository name, the version) are copied small; mesh-sdk main still holds an unused Forge until its own fold removes it. The indexer, bridge, watchman, view and checker pin mesh-sdk 5a38a12 (origin/main), no feat/take-in commit. The bridge's integration bus now grants the state end-asks that bridge_group_end_again added: without it every test of the package failed on a permissions violation. Test budget: two new test functions (TestForge, TestTakeInEvents) and the two the join added are paid by merging four pairs in internal/bridge; the repository stays at 806 unit tests. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
This commit is contained in:
@@ -3,7 +3,7 @@ module mesh-delivery-checker
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8
|
||||
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
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-20261102193506-d221c633799a h1:oxgksCiq272/afXbUION/dxtAS5MPGnmyq6+AZQxmXg=
|
||||
git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-20261102193506-d221c633799a/go.mod h1:kpsX4Pgss5EeuBvel1zWa2ogj7MkR3jZso6oa/J0YUQ=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 h1:XEbIVImp6yGjA91rp0iZBoDdL5KfamjOuDLwWvqh0hc=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
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=
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -120,8 +121,8 @@ func TestARefusedActIsWrittenOnForgeOnceAndNotRetried(t *testing.T) { // 1341/AC
|
||||
if len(pub.subjects) != 1 || pub.subjects[0] != "mesh.stream.mesh-delivery-forge-bridge.event.forge.novox.mesh-delivery.refused" {
|
||||
t.Fatalf("published on %v", pub.subjects)
|
||||
}
|
||||
m, err := delivery.DecodeForge(pub.data[0])
|
||||
if err != nil || m.Kind != delivery.ForgeRefused || m.Act != "comment.1" || m.Number != 12 || m.Head != headA || m.Words != "the pull request is locked" {
|
||||
m, err := forgemsg.DecodeForge(pub.data[0])
|
||||
if err != nil || m.Kind != forgemsg.ForgeRefused || m.Act != "comment.1" || m.Number != 12 || m.Head != headA || m.Words != "the pull request is locked" {
|
||||
t.Errorf("message %+v err %v", m, err)
|
||||
}
|
||||
f.refuse = nil
|
||||
@@ -188,8 +189,8 @@ func TestARefusedMergeIsWrittenOnForgeOnceAndNotRetried(t *testing.T) { // 1342/
|
||||
if len(f.calls) != 1 || len(pub.data) != 1 {
|
||||
t.Fatalf("calls %d messages %d, want one each", len(f.calls), len(pub.data))
|
||||
}
|
||||
m, err := delivery.DecodeForge(pub.data[0])
|
||||
if err != nil || m.Kind != delivery.ForgeRefused || m.Repository != "novox/mesh-delivery" || m.Number != 12 || m.Head != headA ||
|
||||
m, err := forgemsg.DecodeForge(pub.data[0])
|
||||
if err != nil || m.Kind != forgemsg.ForgeRefused || m.Repository != "novox/mesh-delivery" || m.Number != 12 || m.Head != headA ||
|
||||
m.Act != "merge" || m.Words != "novox/mesh-delivery#12 is not merged: its head moved" {
|
||||
t.Errorf("message %+v err %v", m, err)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ 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"
|
||||
|
||||
groupkeys "mesh-delivery-forge-bridge/internal/groups"
|
||||
@@ -58,7 +59,7 @@ 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 delivery.Forge) (delivery.Merge, uint64, bool) {
|
||||
func (g *grouper) groupOf(ctx context.Context, f forgemsg.Forge) (delivery.Merge, uint64, bool) {
|
||||
if g == nil || g.merges == nil || f.Branch == "" {
|
||||
return delivery.Merge{}, 0, false
|
||||
}
|
||||
@@ -110,9 +111,9 @@ func membersOf(m delivery.Merge) []delivery.Head {
|
||||
|
||||
// member sets the group and its approved members on a merged message's body (ADR 0633 §4); any other body, and a
|
||||
// merge of no group, is left as it is.
|
||||
func (g *grouper) member(ctx context.Context, data []byte) ([]byte, delivery.Forge, bool) {
|
||||
f, err := delivery.DecodeForge(data)
|
||||
if err != nil || f.Kind != delivery.ForgeMerged {
|
||||
func (g *grouper) member(ctx context.Context, data []byte) ([]byte, forgemsg.Forge, bool) {
|
||||
f, err := forgemsg.DecodeForge(data)
|
||||
if err != nil || f.Kind != forgemsg.ForgeMerged {
|
||||
return data, f, false
|
||||
}
|
||||
m, _, ok := g.groupOf(ctx, f)
|
||||
@@ -120,7 +121,7 @@ func (g *grouper) member(ctx context.Context, data []byte) ([]byte, delivery.For
|
||||
return data, f, false
|
||||
}
|
||||
f.Group, f.Members = m.Group, membersOf(m)
|
||||
out, err := delivery.EncodeForge(f)
|
||||
out, err := forgemsg.EncodeForge(f)
|
||||
if err != nil {
|
||||
g.logger.Printf("%s#%d: its merged message cannot name the group %q, written without: %v", f.Repository, f.Number, m.Group, err)
|
||||
return data, f, false
|
||||
@@ -129,7 +130,7 @@ func (g *grouper) member(ctx context.Context, data []byte) ([]byte, delivery.For
|
||||
}
|
||||
|
||||
// published keeps that a member's merged message is written, and says the group's end when it was the last.
|
||||
func (g *grouper) published(ctx context.Context, f delivery.Forge) {
|
||||
func (g *grouper) published(ctx context.Context, f forgemsg.Forge) {
|
||||
if err := g.done.Put(ctx, groupkeys.PublishedKey(f.Repository, f.Number, f.Merge), 1); err != nil {
|
||||
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
|
||||
@@ -163,7 +164,7 @@ func (g *grouper) end(ctx context.Context, key string, rev uint64, value []byte,
|
||||
return false
|
||||
}
|
||||
}
|
||||
end := delivery.ForgeGroupEnd{Group: m.Group}
|
||||
end := forgemsg.ForgeGroupEnd{Group: m.Group}
|
||||
for _, member := range membersOf(m) {
|
||||
at := slices.IndexFunc(m.Pulls, func(p delivery.MergePull) bool {
|
||||
return delivery.SameRepository(p.Repository, member.Repository) && p.Number == member.Number
|
||||
@@ -174,14 +175,14 @@ func (g *grouper) end(ctx context.Context, key string, rev uint64, value []byte,
|
||||
if _, ok, err := g.done.Get(ctx, groupkeys.PublishedKey(member.Repository, member.Number, m.Pulls[at].Commit)); err != nil || !ok {
|
||||
return false // its merged message is not written yet: said when it is (published)
|
||||
}
|
||||
end.Members = append(end.Members, delivery.GroupMerge{Repository: member.Repository, Number: member.Number, Merge: m.Pulls[at].Commit})
|
||||
end.Members = append(end.Members, forgemsg.GroupMerge{Repository: member.Repository, Number: member.Number, Merge: m.Pulls[at].Commit})
|
||||
}
|
||||
// The members in the order the gate merged them, so the last commit names the end.
|
||||
slices.SortStableFunc(end.Members, func(a, b delivery.GroupMerge) int {
|
||||
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 })
|
||||
})
|
||||
data, err := delivery.EncodeForgeGroupEnd(end)
|
||||
data, err := forgemsg.EncodeForgeGroupEnd(end)
|
||||
if err != nil {
|
||||
g.logger.Printf("the end of the group %q cannot be written: %v", m.Group, err)
|
||||
return false
|
||||
@@ -190,7 +191,7 @@ func (g *grouper) end(ctx context.Context, key string, rev uint64, value []byte,
|
||||
if again != "" {
|
||||
id += "." + again
|
||||
}
|
||||
if err := g.send(ctx, forgeSubject(delivery.ForgeGroupEndEvent), data, id); err != nil {
|
||||
if err := g.send(ctx, forgeSubject(forgemsg.ForgeGroupEndEvent), data, id); err != nil {
|
||||
g.logger.Printf("the end of the group %q is not written on FORGE, written at the gate's next write or by bridge_group_end_again: %v", m.Group, err)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
@@ -50,7 +51,7 @@ func TestAGroupsMembersNameItAndItsEndIsSaidOnce(t *testing.T) { // 1585/T16, T1
|
||||
return v, 7, ok, nil
|
||||
}}
|
||||
mergedOf := func(repo string, number int, head, commit string) []byte {
|
||||
data, err := delivery.EncodeForge(delivery.Forge{Kind: delivery.ForgeMerged, Repository: repo, Branch: "feat/take-in", Number: number, Head: head, Merge: commit, Root: "r"})
|
||||
data, err := forgemsg.EncodeForge(forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: repo, Branch: "feat/take-in", Number: number, Head: head, Merge: commit, Root: "r"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -63,7 +64,7 @@ func TestAGroupsMembersNameItAndItsEndIsSaidOnce(t *testing.T) { // 1585/T16, T1
|
||||
// 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, ""))
|
||||
data, f, ok := g.member(ctx, mergedOf("novox/mesh-sdk", 37, h1, c1))
|
||||
if back, err := delivery.DecodeForge(data); !ok || err != nil || back.Group != "feat/take-in" || len(back.Members) != 2 {
|
||||
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)
|
||||
@@ -82,7 +83,7 @@ func TestAGroupsMembersNameItAndItsEndIsSaidOnce(t *testing.T) { // 1585/T16, T1
|
||||
if len(pub.subjects) != 1 || pub.subjects[0] != "mesh.stream.mesh-delivery-forge-bridge.event.forge.group.merged" || pub.ids[0] != "group-end.feat/take-in."+c2 {
|
||||
t.Fatalf("ends %v ids %v, want one forge.group.merged with id group-end.feat/take-in.%s", pub.subjects, pub.ids, c2)
|
||||
}
|
||||
end, err := delivery.DecodeForgeGroupEnd(pub.data[0])
|
||||
end, err := forgemsg.DecodeForgeGroupEnd(pub.data[0])
|
||||
if err != nil || len(end.Members) != 2 || end.Members[0].Merge != c1 || end.Members[1].Merge != c2 {
|
||||
t.Errorf("the end = %+v %v, want both members in their merge order", end, err)
|
||||
}
|
||||
|
||||
+20
-13
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"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"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
)
|
||||
@@ -36,7 +37,7 @@ import (
|
||||
//
|
||||
// The lists are what mesh-controller's broker.PermissionsFor answers (internal/broker/nats.go:577) for a module principal
|
||||
// with the manifest of module.json: consumes the three gitea.pull events, invokes gitea.gitea_open_pulls,
|
||||
// gitea.gitea_commit_trees and gitea.gitea_get_file, state heads, and the stream forge; `**` is written `>` on the wire (modstreams.go
|
||||
// gitea.gitea_commit_trees and gitea.gitea_get_file, state heads, performed, inputs and end-asks (ADR 0633 §5's hand end), and the stream forge; `**` is written `>` on the wire (modstreams.go
|
||||
// WirePattern). The consumer's durable is <node>_<module> on EVENTS (nats.go consumerDurable). The controller's own
|
||||
// user keeps all rights, as it has.
|
||||
const busConf = `
|
||||
@@ -83,6 +84,12 @@ accounts {
|
||||
"$JS.FC.KV_mesh-delivery-forge-bridge_inputs.>"
|
||||
"$KV.mesh-delivery-forge-bridge_performed.>"
|
||||
"$KV.mesh-delivery-forge-bridge_inputs.>"
|
||||
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-forge-bridge_end-asks.>"
|
||||
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-forge-bridge_end-asks.>"
|
||||
"$JS.API.DIRECT.GET.KV_mesh-delivery-forge-bridge_end-asks.>"
|
||||
"$JS.API.STREAM.INFO.KV_mesh-delivery-forge-bridge_end-asks"
|
||||
"$JS.FC.KV_mesh-delivery-forge-bridge_end-asks.>"
|
||||
"$KV.mesh-delivery-forge-bridge_end-asks.>"
|
||||
"$JS.API.CONSUMER.CREATE.KV_mesh-delivery-gate_merges.>"
|
||||
"$JS.API.CONSUMER.DELETE.KV_mesh-delivery-gate_merges.>"
|
||||
"$JS.API.DIRECT.GET.KV_mesh-delivery-gate_merges.>"
|
||||
@@ -321,13 +328,13 @@ func TestBridgeWritesPullMessages(t *testing.T) { // 1235/AC2
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := delivery.DecodeForge(raw.Data)
|
||||
f, err := forgemsg.DecodeForge(raw.Data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := delivery.Forge{V: delivery.Version, Kind: "pull", Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head,
|
||||
want := forgemsg.Forge{V: delivery.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 !delivery.SameForge(f, want) {
|
||||
if !forgemsg.SameForge(f, want) {
|
||||
t.Errorf("message = %+v, want %+v", f, want)
|
||||
}
|
||||
|
||||
@@ -416,16 +423,16 @@ func TestBridgeWritesMergedMessage(t *testing.T) { // 1236/AC4, 1448/AC5
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := delivery.DecodeForge(raw.Data)
|
||||
f, err := forgemsg.DecodeForge(raw.Data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := delivery.Forge{V: delivery.Version, Kind: "merged", Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head,
|
||||
want := forgemsg.Forge{V: delivery.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).
|
||||
Locks: map[string]map[string]string{"modules/a": {"go.sum": hex.EncodeToString(sha256Sum(sum))}}}
|
||||
if !delivery.SameForge(f, want) {
|
||||
if !forgemsg.SameForge(f, want) {
|
||||
t.Errorf("message = %+v, want %+v", f, want)
|
||||
}
|
||||
select {
|
||||
@@ -478,7 +485,7 @@ func TestBridgeSaysTheOpenPullsAtStart(t *testing.T) { // 1237/AC1
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f, err := delivery.DecodeForge(raw.Data); err != nil || f.Action != "updated" || f.DirsKnown || f.Branch != "feat/x" {
|
||||
if f, err := forgemsg.DecodeForge(raw.Data); err != nil || f.Action != "updated" || f.DirsKnown || f.Branch != "feat/x" {
|
||||
t.Errorf("%s: %+v err %v", repo, f, err)
|
||||
}
|
||||
}
|
||||
@@ -548,20 +555,20 @@ func TestBridgeReadsTheForgeAtStartAndAfterAReconnectAndHearsNoOlderEvent(t *tes
|
||||
t.Fatalf("FORGE holds %d messages (%v), want %d", info.State.Msgs, err, want)
|
||||
}
|
||||
}
|
||||
named := func(seq uint64) []delivery.OpenPull {
|
||||
named := func(seq uint64) []forgemsg.OpenPull {
|
||||
t.Helper()
|
||||
raw, err := forge.GetMsg(b.ctx, seq)
|
||||
if err != nil || raw.Subject != openSubject {
|
||||
t.Fatalf("message %d: %v %v, want the open message", seq, raw, err)
|
||||
}
|
||||
f, err := delivery.DecodeForge(raw.Data)
|
||||
f, err := forgemsg.DecodeForge(raw.Data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return f.Open
|
||||
}
|
||||
bothOpen := []delivery.OpenPull{{Repository: "novox/mesh-catalog", Number: 300}, {Repository: "novox/mesh-delivery", Number: 12}}
|
||||
same := func(got []delivery.OpenPull) bool {
|
||||
bothOpen := []forgemsg.OpenPull{{Repository: "novox/mesh-catalog", Number: 300}, {Repository: "novox/mesh-delivery", Number: 12}}
|
||||
same := func(got []forgemsg.OpenPull) bool {
|
||||
return len(got) == 2 && got[0] == bothOpen[0] && got[1] == bothOpen[1]
|
||||
}
|
||||
|
||||
@@ -713,7 +720,7 @@ func forgeMerged(t *testing.T, b *bed) []string {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if f, err := delivery.DecodeForge(raw.Data); err == nil && f.Kind == delivery.ForgeMerged {
|
||||
if f, err := forgemsg.DecodeForge(raw.Data); err == nil && f.Kind == forgemsg.ForgeMerged {
|
||||
out = append(out, f.Repository)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"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"
|
||||
@@ -95,7 +96,7 @@ func handleGrouped(ctx context.Context, m delivered, pub publisher, trees bridge
|
||||
if id := m.Headers().Get("x-event-id"); id != "" {
|
||||
opts = append(opts, jetstream.WithMsgID(id))
|
||||
}
|
||||
var member delivery.Forge
|
||||
var member forgemsg.Forge
|
||||
grouped := false
|
||||
if g != nil {
|
||||
msg.Data, member, grouped = g.member(ctx, msg.Data)
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
|
||||
"mesh-delivery-forge-bridge/internal/bridge"
|
||||
)
|
||||
@@ -85,7 +85,7 @@ func TestReadableEventIsWrittenOnForgeAndAcked(t *testing.T) { // 1235/AC2 (unit
|
||||
if len(pub.subjects) != 1 || pub.subjects[0] != "mesh.stream.mesh-delivery-forge-bridge.event.forge.novox.mesh-delivery.pull" {
|
||||
t.Fatalf("published on %v", pub.subjects)
|
||||
}
|
||||
f, err := delivery.DecodeForge(pub.data[0])
|
||||
f, err := forgemsg.DecodeForge(pub.data[0])
|
||||
if err != nil || f.Number != 12 || f.Branch != "feat/x" || f.Head != head || f.Action != "updated" || !f.DirsKnown {
|
||||
t.Errorf("message %+v err %v", f, err)
|
||||
}
|
||||
@@ -172,7 +172,7 @@ func TestMergeCallsTheToolOnceAndWritesTheMergedMessage(t *testing.T) { // 1236/
|
||||
if len(pub.subjects) != 1 || pub.subjects[0] != "mesh.stream.mesh-delivery-forge-bridge.event.forge.novox.mesh-delivery.merged" {
|
||||
t.Fatalf("published on %v", pub.subjects)
|
||||
}
|
||||
f, err := delivery.DecodeForge(pub.data[0])
|
||||
f, err := forgemsg.DecodeForge(pub.data[0])
|
||||
if err != nil || f.Root != "r0" || f.Trees[""] != "t0" || f.Trees["modules/a"] != "t1" || f.Merge == "" {
|
||||
t.Errorf("message %+v err %v", f, err)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
|
||||
"mesh-delivery-forge-bridge/internal/bridge"
|
||||
)
|
||||
@@ -41,7 +41,7 @@ func (r *recorder) Publish(ctx context.Context, subject string, payload []byte,
|
||||
if err != nil {
|
||||
return ack, err
|
||||
}
|
||||
f, derr := delivery.DecodeForge(payload)
|
||||
f, derr := forgemsg.DecodeForge(payload)
|
||||
if derr != nil {
|
||||
return ack, nil
|
||||
}
|
||||
@@ -101,7 +101,7 @@ func syncOpen(ctx context.Context, call caller, pub publisher, heads headStore,
|
||||
}
|
||||
var differing []bridge.Message
|
||||
for _, m := range msgs {
|
||||
f, err := delivery.DecodeForge(m.Data)
|
||||
f, err := forgemsg.DecodeForge(m.Data)
|
||||
if err != nil {
|
||||
differing = append(differing, m)
|
||||
continue
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
)
|
||||
|
||||
// openAnswer is gitea_open_pulls's answer as mesh-catalog modules/gitea/client.ts:369-381 builds its entries inside {pulls}.
|
||||
@@ -43,7 +43,7 @@ func TestSyncOpenWritesOneUpdatedMessagePerOpenPull(t *testing.T) { // 1237/AC1,
|
||||
}
|
||||
want := []string{"mesh.stream.mesh-delivery-forge-bridge.event.forge.novox.mesh-delivery.pull", "mesh.stream.mesh-delivery-forge-bridge.event.forge.novox.mesh-catalog.pull"}
|
||||
for i, w := range want {
|
||||
f, err := delivery.DecodeForge(pub.data[i])
|
||||
f, err := forgemsg.DecodeForge(pub.data[i])
|
||||
if pub.subjects[i] != w || err != nil || f.Action != "updated" || f.DirsKnown || f.Branch != "feat/x" {
|
||||
t.Errorf("message %d on %s = %+v err %v", i, pub.subjects[i], f, err)
|
||||
}
|
||||
@@ -112,8 +112,8 @@ func TestSyncOpenWritesPullsForNewHeadsThenOneOpenMessageNamingAll(t *testing.T)
|
||||
if pub.subjects[1] != "mesh.stream.mesh-delivery-forge-bridge.event.forge.open" {
|
||||
t.Fatalf("last message on %s, want the open message after the pull messages", pub.subjects[1])
|
||||
}
|
||||
f, err := delivery.DecodeForge(pub.data[1])
|
||||
want := []delivery.OpenPull{{Repository: "novox/mesh-catalog", Number: 300}, {Repository: "novox/mesh-delivery", Number: 12}}
|
||||
f, err := forgemsg.DecodeForge(pub.data[1])
|
||||
want := []forgemsg.OpenPull{{Repository: "novox/mesh-catalog", Number: 300}, {Repository: "novox/mesh-delivery", Number: 12}}
|
||||
if err != nil || len(f.Open) != 2 || f.Open[0] != want[0] || f.Open[1] != want[1] {
|
||||
t.Errorf("open message = %+v err %v, want it to name %v, the pull request whose head did not differ among them", f, err, want)
|
||||
}
|
||||
@@ -140,10 +140,10 @@ func TestWhatIsWrittenOnFORGEIsKeptInTheBucket(t *testing.T) { // 1279/AC6
|
||||
pub := &fakePub{}
|
||||
heads := newHeads()
|
||||
r := &recorder{pub: pub, heads: heads, logger: log.New(&bytes.Buffer{}, "", 0)}
|
||||
write := func(f delivery.Forge) {
|
||||
write := func(f forgemsg.Forge) {
|
||||
t.Helper()
|
||||
ev, _ := delivery.ForgeEvent(f.Repository, f.Kind)
|
||||
raw, err := delivery.EncodeForge(f)
|
||||
ev, _ := forgemsg.ForgeEvent(f.Repository, f.Kind)
|
||||
raw, err := forgemsg.EncodeForge(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -151,19 +151,19 @@ func TestWhatIsWrittenOnFORGEIsKeptInTheBucket(t *testing.T) { // 1279/AC6
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
p := delivery.Forge{Kind: delivery.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Action: delivery.PullUpdated}
|
||||
p := forgemsg.Forge{Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Action: forgemsg.PullUpdated}
|
||||
write(p)
|
||||
if heads.get("novox.mesh-delivery.12") != head {
|
||||
t.Fatalf("bucket %v", heads.vals)
|
||||
}
|
||||
p.Action = delivery.PullClosed
|
||||
p.Action = forgemsg.PullClosed
|
||||
write(p)
|
||||
if _, kept := heads.vals["novox.mesh-delivery.12"]; kept {
|
||||
t.Errorf("a closed pull request is still kept: %v", heads.vals)
|
||||
}
|
||||
p.Action = delivery.PullUpdated
|
||||
p.Action = forgemsg.PullUpdated
|
||||
write(p)
|
||||
write(delivery.Forge{Kind: delivery.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Merge: "m", Root: "r", Trees: map[string]string{"": "t"}})
|
||||
write(forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Merge: "m", Root: "r", Trees: map[string]string{"": "t"}})
|
||||
if _, kept := heads.vals["novox.mesh-delivery.12"]; kept {
|
||||
t.Errorf("a merged pull request is still kept: %v", heads.vals)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,413 @@
|
||||
// Package forgemsg is the forge bridge's contract on its own stream `forge` (novox/hq ADR 0633 §4): its messages (Forge,
|
||||
// one per pull request, merge, refusal or list of open pull requests) and a delivery group's end (ForgeGroupEnd). The
|
||||
// forge bridge writes them; the indexer and the gate read them. It is the forge bridge's own package: one module owns
|
||||
// these types, so they live with it and not in mesh-sdk (the operator, 2026-11-04).
|
||||
package forgemsg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// ForgeStream is the forge bridge's stream as its manifest declares it; the broker derives the real names.
|
||||
const ForgeStream = "forge"
|
||||
|
||||
// The kinds of forge message, the last part of a forge event's name.
|
||||
const (
|
||||
ForgePull = "pull"
|
||||
ForgeMerged = "merged"
|
||||
ForgeRefused = "refused"
|
||||
ForgeOpen = "open"
|
||||
)
|
||||
|
||||
// OpenPull names an open pull request into the trunk: the repository as `<owner>/<name>` and the number.
|
||||
type OpenPull struct {
|
||||
Repository string `json:"repository"`
|
||||
Number int `json:"number"`
|
||||
}
|
||||
|
||||
// OpenEvent is the name of an open message: `forge.open`. It names no repository: it says every open pull request of
|
||||
// every repository the forge bridge reads (novox/hq ADR 0443 §5).
|
||||
func OpenEvent() string { return "forge." + ForgeOpen }
|
||||
|
||||
// The actions of a pull message.
|
||||
const (
|
||||
PullOpened = "opened"
|
||||
PullUpdated = "updated"
|
||||
PullClosed = "closed"
|
||||
)
|
||||
|
||||
// checkRepository refuses a repository that is not `<owner>/<name>`: exactly one slash, each part a valid name.
|
||||
func checkRepository(repository string) error {
|
||||
owner, name, ok := strings.Cut(repository, "/")
|
||||
if !ok || strings.Contains(name, "/") {
|
||||
return fmt.Errorf("delivery: the repository %q is not <owner>/<name>, with exactly one slash", repository)
|
||||
}
|
||||
if err := checkName("repository owner", owner); err != nil {
|
||||
return fmt.Errorf("delivery: the repository %q: %w", repository, err)
|
||||
}
|
||||
if err := checkName("repository name", name); err != nil {
|
||||
return fmt.Errorf("delivery: the repository %q: %w", repository, err)
|
||||
}
|
||||
if strings.Contains(owner, "/") {
|
||||
return fmt.Errorf("delivery: the repository %q is not <owner>/<name>, with exactly one slash", repository)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkForgeKind(kind string) error {
|
||||
switch kind {
|
||||
case ForgePull, ForgeMerged, ForgeRefused, ForgeOpen:
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("delivery: the forge kind %q is not pull, merged, refused or open", kind)
|
||||
}
|
||||
|
||||
// ForgeEvent is the name of a forge message of the given kind: `forge.<owner>.<name>.<kind>`.
|
||||
func ForgeEvent(repository, kind string) (string, error) {
|
||||
if err := checkForgeKind(kind); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if kind == ForgeOpen {
|
||||
return "", fmt.Errorf("delivery: an open message names no repository, its event is %s", OpenEvent())
|
||||
}
|
||||
if err := checkRepository(repository); err != nil {
|
||||
return "", err
|
||||
}
|
||||
owner, name, _ := strings.Cut(repository, "/")
|
||||
return "forge." + owner + "." + name + "." + kind, nil
|
||||
}
|
||||
|
||||
// ParseForgeEvent is the inverse of ForgeEvent.
|
||||
func ParseForgeEvent(event string) (repository, kind string, err error) {
|
||||
if event == OpenEvent() {
|
||||
return "", ForgeOpen, nil
|
||||
}
|
||||
parts := strings.Split(event, ".")
|
||||
if len(parts) != 4 || parts[0] != "forge" {
|
||||
return "", "", fmt.Errorf("delivery: %q is not a forge event, forge.<owner>.<name>.<kind>", event)
|
||||
}
|
||||
repository = parts[1] + "/" + parts[2]
|
||||
if _, err := ForgeEvent(repository, parts[3]); err != nil {
|
||||
return "", "", fmt.Errorf("delivery: %q is not a forge event: %w", event, err)
|
||||
}
|
||||
return repository, parts[3], nil
|
||||
}
|
||||
|
||||
// Forge is a message of the forge bridge's stream, trimmed to repository, branch, pull request and head. A pull
|
||||
// message says Action; a merged message carries the merge commit, the tree of every directory holding a
|
||||
// module.json and the repository's root tree; a refused message names the owed act it answers and the forge's own
|
||||
// words. A pull message may also carry the module directories its head changes (Dirs, in the order the forge said) and
|
||||
// DirsKnown, true when the forge could say them: known and empty is DirsKnown with no Dirs, not known is neither
|
||||
// (the forge's module_dirs and module_dirs_said, mesh-catalog modules/gitea/trunk.ts). Other messages carry neither.
|
||||
type Forge struct {
|
||||
V int `json:"v"`
|
||||
Kind string `json:"kind"`
|
||||
Repository string `json:"repository"`
|
||||
Branch string `json:"branch,omitempty"`
|
||||
Number int `json:"number,omitempty"`
|
||||
Head string `json:"head,omitempty"`
|
||||
Action string `json:"action,omitempty"`
|
||||
Dirs []string `json:"dirs,omitempty"`
|
||||
DirsKnown bool `json:"dirs-known,omitempty"`
|
||||
Merge string `json:"merge,omitempty"`
|
||||
Trees map[string]string `json:"trees,omitempty"`
|
||||
Root string `json:"root,omitempty"`
|
||||
// Manifests are the module.json bytes, exact, of each module directory: of the merge's trees on a merged message,
|
||||
// of Dirs at the head on a pull message. Locks are, of each such directory, its lock files' hex sha256 by path
|
||||
// relative to it (novox/hq ADR 0465 clause 1). Older writers send neither.
|
||||
Manifests map[string]string `json:"manifests,omitempty"`
|
||||
Locks map[string]map[string]string `json:"locks,omitempty"`
|
||||
// Paths are, of each module directory whose manifest names paths on every artifact, each named path's git object
|
||||
// id at the same commit as its manifest, by path relative to the directory (novox/hq ADR 0499 clause 3). The
|
||||
// indexer names the paths; a message without them gives such a module none. Older writers send none.
|
||||
Paths map[string]map[string]string `json:"paths,omitempty"`
|
||||
// Packages are, of each module directory, the mesh packages its lock file names (MeshPackagesOf; novox/hq ADR 0573
|
||||
// §2): the indexer keeps them in the module's inputs and takes each one's latest published version into its
|
||||
// fingerprint. Absent for a directory that uses none, and from an older writer.
|
||||
Packages map[string][]string `json:"packages,omitempty"`
|
||||
Act string `json:"act,omitempty"`
|
||||
Words string `json:"words,omitempty"`
|
||||
Open []OpenPull `json:"open,omitempty"`
|
||||
// RetiredPlacements is the placement files a bridge before ADR 0567 carried (ADR 0549 §2, ADR 0559 §2): placement is
|
||||
// the seat mesh-placement's state now and no repository holds it (ADR 0567 §11), so a message carrying them is
|
||||
// refused, loudly, rather than read with them dropped.
|
||||
RetiredPlacements json.RawMessage `json:"placements,omitempty"`
|
||||
RetiredRead bool `json:"placements-read,omitempty"`
|
||||
// Predefined is the catalogue's predefined.json, exact, at the merge commit of a merged message (novox/hq ADR 0565
|
||||
// §2): what the indexer keeps as its state `predefined`. PredefinedRead says the bridge read the merge and it holds
|
||||
// no predefined.json. Carried on a merged message only; an older bridge sends neither, and the indexer keeps what it
|
||||
// held.
|
||||
Predefined json.RawMessage `json:"predefined,omitempty"`
|
||||
PredefinedRead bool `json:"predefined-read,omitempty"`
|
||||
// Group is the delivery group a merged message's pull request was merged in, and Members every approved member of
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// checkGroup refuses a group on anything but a merged message, a group without its members or members without a
|
||||
// group, a member that is not a repository's pull request at a head, a member named twice, and members that leave out
|
||||
// the message's own pull request.
|
||||
func (f Forge) checkGroup() error {
|
||||
if f.Group == "" && len(f.Members) == 0 {
|
||||
return nil
|
||||
}
|
||||
if f.Kind != ForgeMerged {
|
||||
return fmt.Errorf("delivery: only a merged message names its delivery group, not a %s message", f.Kind)
|
||||
}
|
||||
if f.Group == "" || len(f.Members) == 0 {
|
||||
return fmt.Errorf("delivery: a merged message names its group and the group's members together, or neither")
|
||||
}
|
||||
self := false
|
||||
seen := map[string]bool{}
|
||||
for _, m := range f.Members {
|
||||
if err := checkRepository(m.Repository); err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Number <= 0 || m.Head == "" {
|
||||
return fmt.Errorf("delivery: the member %s of the group %q needs its pull request number and head", m.Repository, f.Group)
|
||||
}
|
||||
k := fmt.Sprintf("%s#%d", m.Repository, m.Number)
|
||||
if seen[k] {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkPredefined refuses the predefined list on anything but a merged message, a list that is not one JSON object, and
|
||||
// a list said both present and absent.
|
||||
func (f Forge) checkPredefined() error {
|
||||
if len(f.Predefined) == 0 && !f.PredefinedRead {
|
||||
return nil
|
||||
}
|
||||
if f.Kind != ForgeMerged {
|
||||
return fmt.Errorf("delivery: only a merged message carries the predefined list, not a %s message", f.Kind)
|
||||
}
|
||||
if len(f.Predefined) > 0 && f.PredefinedRead {
|
||||
return fmt.Errorf("delivery: a merged message carries the predefined list and says the merge holds none (predefined-read)")
|
||||
}
|
||||
var list map[string]json.RawMessage
|
||||
if len(f.Predefined) > 0 && json.Unmarshal(f.Predefined, &list) != nil {
|
||||
return fmt.Errorf("delivery: the predefined list a merged message carries is not one JSON object")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f Forge) check() error {
|
||||
if err := checkForgeKind(f.Kind); err != nil {
|
||||
return err
|
||||
}
|
||||
if f.Kind == ForgeOpen {
|
||||
return f.checkOpen()
|
||||
}
|
||||
if len(f.Open) > 0 {
|
||||
return fmt.Errorf("delivery: only an open message names open pull requests")
|
||||
}
|
||||
if len(f.RetiredPlacements) > 0 || f.RetiredRead {
|
||||
return fmt.Errorf("delivery: a forge message carries placement files, which no repository holds since ADR 0567: " +
|
||||
"placement is the seat mesh-placement's state")
|
||||
}
|
||||
if err := f.checkPredefined(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.checkGroup(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkRepository(f.Repository); err != nil {
|
||||
return err
|
||||
}
|
||||
switch f.Kind {
|
||||
case ForgePull:
|
||||
if f.Number <= 0 {
|
||||
return fmt.Errorf("delivery: a pull message needs its pull request number")
|
||||
}
|
||||
if f.Head == "" {
|
||||
return fmt.Errorf("delivery: a pull message needs its head")
|
||||
}
|
||||
switch f.Action {
|
||||
case PullOpened, PullUpdated, PullClosed:
|
||||
default:
|
||||
return fmt.Errorf("delivery: the pull action %q is not opened, updated or closed", f.Action)
|
||||
}
|
||||
if len(f.Dirs) > 0 && !f.DirsKnown {
|
||||
return fmt.Errorf("delivery: a pull message with module directories must say it knows them (dirs-known)")
|
||||
}
|
||||
dirs := map[string]bool{}
|
||||
for _, d := range f.Dirs {
|
||||
dirs[d] = true
|
||||
}
|
||||
if err := f.checkInputs(func(d string) bool { return dirs[d] }, "its module directories"); err != nil {
|
||||
return err
|
||||
}
|
||||
case ForgeMerged:
|
||||
if len(f.Dirs) > 0 || f.DirsKnown {
|
||||
return fmt.Errorf("delivery: a merged message carries no module directories")
|
||||
}
|
||||
if f.Number <= 0 {
|
||||
return fmt.Errorf("delivery: a merged message needs its pull request number")
|
||||
}
|
||||
if f.Head == "" {
|
||||
return fmt.Errorf("delivery: a merged message needs its head")
|
||||
}
|
||||
if f.Merge == "" {
|
||||
return fmt.Errorf("delivery: a merged message needs its merge commit")
|
||||
}
|
||||
if f.Root == "" {
|
||||
return fmt.Errorf("delivery: a merged message needs the repository's root tree")
|
||||
}
|
||||
// No trees is a merge that leaves the repository with no module.json anywhere (a documents repository, or one
|
||||
// whose last module was removed): it is written all the same, so the indexer takes the merge in and drops the
|
||||
// repository's modules (novox/hq issue 1126). An older writer always sent at least one tree, so none is unambiguous;
|
||||
// such a message carries no manifests, locks or paths (checkInputs below: each names a directory of its trees).
|
||||
for dir, tree := range f.Trees {
|
||||
if tree == "" {
|
||||
return fmt.Errorf("delivery: a merged message's tree of the directory %q is empty", dir)
|
||||
}
|
||||
}
|
||||
if err := f.checkInputs(func(d string) bool { _, ok := f.Trees[d]; return ok }, "its trees"); err != nil {
|
||||
return err
|
||||
}
|
||||
case ForgeRefused:
|
||||
if len(f.Dirs) > 0 || f.DirsKnown {
|
||||
return fmt.Errorf("delivery: a refused message carries no module directories")
|
||||
}
|
||||
if len(f.Manifests) > 0 || len(f.Locks) > 0 || len(f.Paths) > 0 {
|
||||
return fmt.Errorf("delivery: a refused message carries no manifests, lock hashes or named paths")
|
||||
}
|
||||
if f.Act == "" {
|
||||
return fmt.Errorf("delivery: a refused message needs the act it answers")
|
||||
}
|
||||
if f.Words == "" {
|
||||
return fmt.Errorf("delivery: a refused message needs the forge's words")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkInputs refuses a manifest or a lock hash of a directory that known does not know (of is what it knows them
|
||||
// by), an empty manifest and an empty lock path or hash, naming the directory.
|
||||
func (f Forge) checkInputs(known func(dir string) bool, of string) error {
|
||||
for dir, m := range f.Manifests {
|
||||
if !known(dir) {
|
||||
return fmt.Errorf("delivery: the manifest of the directory %q is not of %s", dir, of)
|
||||
}
|
||||
if m == "" {
|
||||
return fmt.Errorf("delivery: the manifest of the directory %q is empty", dir)
|
||||
}
|
||||
}
|
||||
for dir, locks := range f.Locks {
|
||||
if !known(dir) {
|
||||
return fmt.Errorf("delivery: the lock hashes of the directory %q are not of %s", dir, of)
|
||||
}
|
||||
for path, hash := range locks {
|
||||
if path == "" || hash == "" {
|
||||
return fmt.Errorf("delivery: the lock %q of the directory %q has an empty path or hash", path, dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
for dir, paths := range f.Paths {
|
||||
if !known(dir) {
|
||||
return fmt.Errorf("delivery: the named paths of the directory %q are not of %s", dir, of)
|
||||
}
|
||||
for path, object := range paths {
|
||||
if path == "" || object == "" {
|
||||
return fmt.Errorf("delivery: the named path %q of the directory %q has an empty path or object", path, dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkOpen refuses an open message that names a repository, a branch, a head or anything of the other kinds, or
|
||||
// an open pull request that is not `<owner>/<name>` and a number.
|
||||
func (f Forge) checkOpen() error {
|
||||
if f.Repository != "" || f.Branch != "" || f.Number != 0 || f.Head != "" || f.Action != "" || len(f.Dirs) > 0 || f.DirsKnown ||
|
||||
f.Merge != "" || len(f.Trees) > 0 || len(f.Manifests) > 0 || len(f.Locks) > 0 || len(f.Paths) > 0 || f.Root != "" || f.Act != "" || f.Words != "" || f.Group != "" || len(f.Members) > 0 {
|
||||
return fmt.Errorf("delivery: an open message carries only the open pull requests it names")
|
||||
}
|
||||
for _, p := range f.Open {
|
||||
if err := checkRepository(p.Repository); err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Number <= 0 {
|
||||
return fmt.Errorf("delivery: the open pull request of %s has no number", p.Repository)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeForge is f as JSON at Version; it refuses a message missing what its kind needs.
|
||||
func EncodeForge(f Forge) ([]byte, error) {
|
||||
if err := f.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.V = Version
|
||||
return json.Marshal(f)
|
||||
}
|
||||
|
||||
// DecodeForge reads a forge message; it refuses an unknown version (ErrUnknownVersion) and what EncodeForge refuses.
|
||||
func DecodeForge(data []byte) (Forge, error) {
|
||||
var f Forge
|
||||
if err := decode(data, &f); err != nil {
|
||||
return Forge{}, err
|
||||
}
|
||||
if err := f.check(); err != nil {
|
||||
return Forge{}, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// SameForge says whether two forge messages are the same value.
|
||||
func SameForge(a, b Forge) bool { return reflect.DeepEqual(a, b) }
|
||||
|
||||
var nameOK = regexp.MustCompile(`^[-/_=a-zA-Z0-9]+$`)
|
||||
|
||||
// checkName refuses a name that is empty or holds a dot, a wildcard, whitespace or a character a bus key refuses
|
||||
// (mesh-sdk delivery's rule for a name).
|
||||
func checkName(kind, name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("delivery: the %s name is empty", kind)
|
||||
}
|
||||
if !nameOK.MatchString(name) {
|
||||
return fmt.Errorf("delivery: the %s name %q holds a dot, a wildcard, whitespace or a character a key refuses", kind, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decode reads a value of Version, refusing another or none with delivery.ErrUnknownVersion, as mesh-sdk delivery's
|
||||
// values are read.
|
||||
func decode(data []byte, into any) error {
|
||||
var probe struct {
|
||||
V *int `json:"v"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &probe); err != nil {
|
||||
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)
|
||||
}
|
||||
if *probe.V != Version {
|
||||
return fmt.Errorf("%w: the value is version %d, this reader reads %d", delivery.ErrUnknownVersion, *probe.V, Version)
|
||||
}
|
||||
if err := json.Unmarshal(data, into); err != nil {
|
||||
return fmt.Errorf("delivery: value does not fit its shape: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package forgemsg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
const oddManifest = "{\n\t\"module\": \"a\",\n \"z\": 1, \"a\": [ \"x\",\"y\" ],\n \"why\": \"é — <b> & \\n\"\n}\n\n"
|
||||
|
||||
func sampleForges() []Forge {
|
||||
return []Forge{
|
||||
{Kind: ForgePull, Repository: "novox/mesh-sdk", Branch: "feat/x", Number: 36, Head: "aaaa", Action: "opened"},
|
||||
{Kind: ForgeMerged, Repository: "novox/mesh-sdk", Number: 36, Head: "aaaa", Merge: "cccc",
|
||||
Trees: map[string]string{"modules/a": "t1"}, Root: "t0"},
|
||||
{Kind: ForgeRefused, Repository: "novox/mesh-sdk", Act: "novox/mesh-sdk.36.aaaa.view", Words: "403 forbidden"},
|
||||
{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"}}},
|
||||
}
|
||||
}
|
||||
|
||||
func mergedWithInputs() Forge {
|
||||
return Forge{Kind: ForgeMerged, Repository: "novox/mesh-sdk", Number: 36, Head: "aaaa", Merge: "cccc", Root: "t0",
|
||||
Trees: map[string]string{"a": "t1", "b": "t2"},
|
||||
Manifests: map[string]string{"a": oddManifest},
|
||||
Locks: map[string]map[string]string{"a": {"go.sum": strings.Repeat("ab", 32)}}}
|
||||
}
|
||||
|
||||
// TestForge holds the forge bridge's contract, moved whole from mesh-sdk go/delivery with its tests (1200/AC1, AC3,
|
||||
// AC4; 1279/AC5; 1442/AC1-AC4; 1585/T1).
|
||||
func TestForge(t *testing.T) {
|
||||
t.Run("events", func(t *testing.T) {
|
||||
ev, err := ForgeEvent("novox/mesh-sdk", ForgeMerged)
|
||||
if err != nil || ev != "forge.novox.mesh-sdk.merged" {
|
||||
t.Fatalf("forge event = %q, %v", ev, err)
|
||||
}
|
||||
if repo, kind, err := ParseForgeEvent(ev); err != nil || repo != "novox/mesh-sdk" || kind != ForgeMerged {
|
||||
t.Fatalf("parse forge event = %q %q %v", repo, kind, err)
|
||||
}
|
||||
for _, bad := range []struct{ repo, kind string }{
|
||||
{"", "pull"}, {"novox", "pull"}, {"a/b/c", "pull"}, {"/b", "pull"}, {"a/", "pull"},
|
||||
{"a.b/c", "pull"}, {"a/b c", "pull"}, {"a/b", "closed"}, {"a/b", ""}, {"a/b", ForgeOpen},
|
||||
} {
|
||||
if _, err := ForgeEvent(bad.repo, bad.kind); err == nil {
|
||||
t.Errorf("forge event %q %q accepted", bad.repo, bad.kind)
|
||||
}
|
||||
}
|
||||
for _, bad := range []string{"", "forge.a.b", "forge.a.b.closed", "x.a.b.pull", "forge.a.b.pull.x", "forge..b.pull", ForgeGroupEndEvent} {
|
||||
if _, _, err := ParseForgeEvent(bad); err == nil {
|
||||
t.Errorf("parse forge event %q accepted", bad)
|
||||
}
|
||||
}
|
||||
if repo, kind, err := ParseForgeEvent(OpenEvent()); OpenEvent() != "forge.open" || err != nil || repo != "" || kind != ForgeOpen {
|
||||
t.Errorf("open event %q = %q %q %v", OpenEvent(), repo, kind, err)
|
||||
}
|
||||
})
|
||||
t.Run("round trip", func(t *testing.T) {
|
||||
fs := append(sampleForges(), mergedWithInputs())
|
||||
for _, f := range fs {
|
||||
data, err := EncodeForge(f)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", f.Kind, err)
|
||||
}
|
||||
var probe map[string]any
|
||||
if err := json.Unmarshal(data, &probe); err != nil || probe["v"] != float64(1) {
|
||||
t.Fatalf("%s: json %s does not carry v:1", f.Kind, data)
|
||||
}
|
||||
f.V = Version
|
||||
if got, err := DecodeForge(data); err != nil || !SameForge(got, f) {
|
||||
t.Errorf("forge %s round trip: %+v %v", f.Kind, got, err)
|
||||
}
|
||||
}
|
||||
// An empty list is an open message too: nothing is open.
|
||||
if data, err := EncodeForge(Forge{Kind: ForgeOpen, Open: []OpenPull{}}); err != nil {
|
||||
t.Errorf("empty open: %v", err)
|
||||
} else if f, err := DecodeForge(data); err != nil || f.Kind != ForgeOpen || len(f.Open) != 0 {
|
||||
t.Errorf("empty open round trip: %+v %v", f, err)
|
||||
}
|
||||
ge := ForgeGroupEnd{Group: "feat/take-in", Members: []GroupMerge{{Repository: "novox/mesh-sdk", Number: 37, Merge: "eeee"}}}
|
||||
data, err := EncodeForgeGroupEnd(ge)
|
||||
ge.V = Version
|
||||
if got, err2 := DecodeForgeGroupEnd(data); err != nil || err2 != nil || !SameForgeGroupEnd(got, ge) {
|
||||
t.Errorf("group end round trip: %+v %v %v", got, err, err2)
|
||||
}
|
||||
// An older bridge's message reads, and is written back byte for byte.
|
||||
old := `{"v":1,"kind":"merged","repository":"novox/mesh-sdk","number":36,"head":"aaaa","merge":"cccc","trees":{"a":"t1"},"root":"t0"}`
|
||||
if f, err := DecodeForge([]byte(old)); err != nil || f.Manifests != nil {
|
||||
t.Fatalf("%+v, %v", f, err)
|
||||
} else if data, err := EncodeForge(f); err != nil || string(data) != old {
|
||||
t.Errorf("%s, %v, want exactly %s", data, err, old)
|
||||
}
|
||||
// A merge leaving the repository with no module.json is a message too (issue 1126).
|
||||
f := sampleForges()[1]
|
||||
f.Trees = nil
|
||||
if data, err := EncodeForge(f); err != nil {
|
||||
t.Errorf("a merged message with no trees is refused: %v", err)
|
||||
} else if got, err := DecodeForge(data); err != nil || len(got.Trees) != 0 {
|
||||
t.Errorf("a merged message with no trees comes back as %+v, %v", got, err)
|
||||
}
|
||||
for name, dec := range map[string]func([]byte) error{
|
||||
"forge": func(b []byte) error { _, err := DecodeForge(b); return err },
|
||||
"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)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("refused", func(t *testing.T) {
|
||||
at := func(i int, mut func(*Forge)) Forge { f := sampleForges()[i]; mut(&f); return f }
|
||||
in := func(mut func(*Forge)) Forge { f := mergedWithInputs(); mut(&f); return f }
|
||||
pullIn := func(mut func(*Forge)) Forge {
|
||||
f := Forge{Kind: ForgePull, Repository: "novox/mesh-sdk", Number: 1, Head: "h", Action: PullOpened,
|
||||
Dirs: []string{"a"}, DirsKnown: true, Manifests: map[string]string{"a": oddManifest},
|
||||
Locks: map[string]map[string]string{"a": {"go.sum": strings.Repeat("0", 64)}}}
|
||||
mut(&f)
|
||||
return f
|
||||
}
|
||||
for _, c := range []struct {
|
||||
name, want string
|
||||
f Forge
|
||||
}{
|
||||
{"merged without merge commit", "merge", at(1, func(f *Forge) { f.Merge = "" })},
|
||||
{"merged without root", "root", at(1, func(f *Forge) { f.Root = "" })},
|
||||
{"merged with a manifest and no trees", "modules/a", at(1, func(f *Forge) { f.Trees = nil; f.Manifests = map[string]string{"modules/a": "{}"} })},
|
||||
{"merged with an empty tree", "modules/a", at(1, func(f *Forge) { f.Trees = map[string]string{"modules/a": ""} })},
|
||||
{"pull with an unknown action", "reopened", at(0, func(f *Forge) { f.Action = "reopened" })},
|
||||
{"pull without head", "head", at(0, func(f *Forge) { f.Head = "" })},
|
||||
{"refused without words", "words", at(2, func(f *Forge) { f.Words = "" })},
|
||||
{"refused without act", "act", at(2, func(f *Forge) { f.Act = "" })},
|
||||
{"unknown kind", "closed", Forge{Kind: "closed", Repository: "a/b"}},
|
||||
{"bad repository", "nope", Forge{Kind: ForgePull, Repository: "nope", Number: 1, Head: "h", Action: "opened"}},
|
||||
{"open with a repository", "only the open", Forge{Kind: ForgeOpen, Repository: "a/b", Open: []OpenPull{{Repository: "a/b", Number: 1}}}},
|
||||
{"open with a bad entry", "", Forge{Kind: ForgeOpen, Open: []OpenPull{{Repository: "a", Number: 1}}}},
|
||||
{"open with a zero number", "no number", Forge{Kind: ForgeOpen, Open: []OpenPull{{Repository: "a/b"}}}},
|
||||
{"pull naming open pull requests", "only an open message", Forge{Kind: ForgePull, Repository: "a/b", Number: 1, Head: "h", Action: "updated", Open: []OpenPull{{Repository: "a/b", Number: 1}}}},
|
||||
{"manifest outside trees", `"zz"`, in(func(f *Forge) { f.Manifests["zz"] = "{}" })},
|
||||
{"locks outside trees", `"yy"`, in(func(f *Forge) { f.Locks["yy"] = map[string]string{"go.sum": "h"} })},
|
||||
{"empty manifest", `"b"`, in(func(f *Forge) { f.Manifests["b"] = "" })},
|
||||
{"empty lock hash", `"a"`, in(func(f *Forge) { f.Locks["a"]["go.sum"] = "" })},
|
||||
{"named paths outside trees", `"other"`, in(func(f *Forge) { f.Paths = map[string]map[string]string{"other": {"src": "o"}} })},
|
||||
{"a named path with no object", `"src"`, in(func(f *Forge) { f.Paths = map[string]map[string]string{"a": {"src": ""}} })},
|
||||
{"placement files", "mesh-placement", in(func(f *Forge) { f.RetiredPlacements = []byte(`{"anchor":"{}"}`) })},
|
||||
{"placements read", "mesh-placement", in(func(f *Forge) { f.RetiredRead = true })},
|
||||
{"predefined not an object", "one JSON object", in(func(f *Forge) { f.Predefined = []byte(`["domain"]`) })},
|
||||
{"predefined both", "says the merge holds none", in(func(f *Forge) { f.Predefined = []byte(`{}`); f.PredefinedRead = true })},
|
||||
{"pull with the predefined list", "only a merged message", Forge{Kind: ForgePull, Repository: "novox/mesh-sdk", Number: 1, Head: "h", Action: PullOpened, PredefinedRead: true}},
|
||||
{"pull manifest outside dirs", `"b"`, pullIn(func(f *Forge) { f.Manifests["b"] = "{}" })},
|
||||
{"pull locks outside dirs", `"c"`, pullIn(func(f *Forge) { f.Locks["c"] = map[string]string{"go.sum": "h"} })},
|
||||
{"refused with manifests", "no manifests", Forge{Kind: ForgeRefused, Repository: "a/b", Act: "x", Words: "no", Manifests: map[string]string{"a": "{}"}}},
|
||||
{"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"}}
|
||||
})},
|
||||
{"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 = "" })},
|
||||
{"merged with members leaving itself out", "leave out", at(4, func(f *Forge) { f.Members = f.Members[1:] })},
|
||||
{"merged with a member twice", "twice", at(4, func(f *Forge) { f.Members = append(f.Members, f.Members[0]) })},
|
||||
{"open with a group", "only the open pull requests", Forge{Kind: ForgeOpen, Group: "g"}},
|
||||
} {
|
||||
if _, err := EncodeForge(c.f); err == nil || !strings.Contains(err.Error(), c.want) {
|
||||
t.Errorf("%s: %v, want a refusal naming %q", c.name, err, c.want)
|
||||
}
|
||||
}
|
||||
// A member's repository in another case still names the merged pull request (10c91fba).
|
||||
f := sampleForges()[4]
|
||||
f.Members[0].Repository = "Novox/Mesh-SDK"
|
||||
if _, err := EncodeForge(f); err != nil {
|
||||
t.Errorf("a member in another case: %v", err)
|
||||
}
|
||||
for _, c := range []struct {
|
||||
name, want string
|
||||
e ForgeGroupEnd
|
||||
}{
|
||||
{"group end without members", "no member", ForgeGroupEnd{Group: "g"}},
|
||||
{"group end without a group", "names its group", ForgeGroupEnd{Members: []GroupMerge{{Repository: "a/b", Number: 1, Merge: "m"}}}},
|
||||
{"group end with a member without its merge", "merge commit", ForgeGroupEnd{Group: "g", Members: []GroupMerge{{Repository: "a/b", Number: 1}}}},
|
||||
{"group end with a member twice", "twice", ForgeGroupEnd{Group: "g", Members: []GroupMerge{{Repository: "a/b", Number: 1, Merge: "m"}, {Repository: "a/b", Number: 1, Merge: "m"}}}},
|
||||
} {
|
||||
if _, err := EncodeForgeGroupEnd(c.e); err == nil || !strings.Contains(err.Error(), c.want) {
|
||||
t.Errorf("%s: %v, want a refusal naming %q", c.name, err, c.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659
|
||||
@@ -0,0 +1,14 @@
|
||||
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/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||
github.com/nats-io/nats.go v1.54.0/go.mod h1:y+DZoD1oBOYfZTU681eTUiUjI0vbqYGixNVFHcjHJ0k=
|
||||
github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg=
|
||||
github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
@@ -0,0 +1,78 @@
|
||||
package forgemsg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ForgeGroupEndEvent is the name of a delivery group's end on the forge bridge's stream (novox/hq ADR 0633 §4): the
|
||||
// bridge publishes it once every approved member of the group is merged, after the last member's merged message. It
|
||||
// has three parts, so no repository's forge event (`forge.<owner>.<name>.<kind>`) is ever this name.
|
||||
const ForgeGroupEndEvent = "forge.group.merged"
|
||||
|
||||
// GroupMerge is one merged member of a delivery group: the repository as `<owner>/<name>`, its pull request and the
|
||||
// merge commit the forge made of it.
|
||||
type GroupMerge struct {
|
||||
Repository string `json:"repository"`
|
||||
Number int `json:"number"`
|
||||
Merge string `json:"merge"`
|
||||
}
|
||||
|
||||
// ForgeGroupEnd is the body of ForgeGroupEndEvent: the group and every member merged in it, with its merge commit.
|
||||
// The indexer takes the members it holds in as one source revision once it hears it (ADR 0633 §4).
|
||||
type ForgeGroupEnd struct {
|
||||
V int `json:"v"`
|
||||
Group string `json:"group"`
|
||||
Members []GroupMerge `json:"members"`
|
||||
}
|
||||
|
||||
func (e ForgeGroupEnd) check() error {
|
||||
if e.Group == "" {
|
||||
return fmt.Errorf("delivery: a group's end names its group")
|
||||
}
|
||||
if len(e.Members) == 0 {
|
||||
return fmt.Errorf("delivery: the end of the group %q names no member merged in it", e.Group)
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, m := range e.Members {
|
||||
if err := checkRepository(m.Repository); err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Number <= 0 || m.Merge == "" {
|
||||
return fmt.Errorf("delivery: the member %s of the group %q's end needs its pull request number and merge commit", m.Repository, e.Group)
|
||||
}
|
||||
k := fmt.Sprintf("%s#%d", m.Repository, m.Number)
|
||||
if seen[k] {
|
||||
return fmt.Errorf("delivery: the end of the group %q names its member %s twice", e.Group, k)
|
||||
}
|
||||
seen[k] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeForgeGroupEnd is e as JSON at Version; it refuses an end with no group or no member, and a member without its
|
||||
// pull request or merge commit.
|
||||
func EncodeForgeGroupEnd(e ForgeGroupEnd) ([]byte, error) {
|
||||
if err := e.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
e.V = Version
|
||||
return json.Marshal(e)
|
||||
}
|
||||
|
||||
// DecodeForgeGroupEnd reads a group's end; it refuses an unknown version (ErrUnknownVersion) and what
|
||||
// EncodeForgeGroupEnd refuses.
|
||||
func DecodeForgeGroupEnd(data []byte) (ForgeGroupEnd, error) {
|
||||
var e ForgeGroupEnd
|
||||
if err := decode(data, &e); err != nil {
|
||||
return ForgeGroupEnd{}, err
|
||||
}
|
||||
if err := e.check(); err != nil {
|
||||
return ForgeGroupEnd{}, err
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// SameForgeGroupEnd says whether two group ends are the same value.
|
||||
func SameForgeGroupEnd(a, b ForgeGroupEnd) bool { return reflect.DeepEqual(a, b) }
|
||||
@@ -3,12 +3,13 @@ module mesh-delivery-forge-bridge
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8
|
||||
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
|
||||
)
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg v0.0.0-00010101000000-000000000000
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
github.com/klauspost/compress v1.20.0 // indirect
|
||||
@@ -20,3 +21,5 @@ require (
|
||||
golang.org/x/sys v0.48.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
)
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg => ./forgemsg
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 h1:XEbIVImp6yGjA91rp0iZBoDdL5KfamjOuDLwWvqh0hc=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
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=
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -145,11 +146,11 @@ func MergeActs(value []byte) ([]Act, error) {
|
||||
// RefusedMessage is the refused message the bridge writes on FORGE when the forge refuses an act: the act, the pull
|
||||
// request and head it was on, and the forge's own words.
|
||||
func RefusedMessage(a Act, words string) (Message, error) {
|
||||
event, err := delivery.ForgeEvent(a.Repository, delivery.ForgeRefused)
|
||||
event, err := forgemsg.ForgeEvent(a.Repository, forgemsg.ForgeRefused)
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
}
|
||||
body, err := delivery.EncodeForge(delivery.Forge{Kind: delivery.ForgeRefused, Repository: a.Repository, Number: a.Number,
|
||||
body, err := forgemsg.EncodeForge(forgemsg.Forge{Kind: forgemsg.ForgeRefused, Repository: a.Repository, Number: a.Number,
|
||||
Head: a.Head, Act: a.Name, Words: words})
|
||||
if err != nil {
|
||||
return Message{}, err
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -111,18 +112,12 @@ func TestOwedActWithoutAPullRequestNeedsOneOnlyWhereTheToolDoes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOwedActOfADottedRepositoryIsUnescaped(t *testing.T) {
|
||||
key, v := owedValue(t, "novox/my.repo", 3, "comment.1", delivery.Owed{Act: delivery.OwedComment, Text: "x"})
|
||||
a, err := OwedAct(key, v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a.Call.Args["repo"] != "my.repo" || a.Repository != "novox/my.repo" {
|
||||
t.Errorf("act = %+v", a)
|
||||
}
|
||||
}
|
||||
|
||||
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"})
|
||||
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"})
|
||||
if _, err := OwedAct(key, v); err == nil {
|
||||
@@ -166,13 +161,11 @@ func TestMergeActsHandOnlyTheMemberHandedWithEveryApprovedHead(t *testing.T) { /
|
||||
if acts[0].Name != "merge" || acts[0].Number != 12 || acts[0].Head != headA {
|
||||
t.Errorf("act = %+v", acts[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeActsOfAMergeWithoutApprovedHeadsAreRefused(t *testing.T) {
|
||||
v := mergeValue(t, delivery.Merge{Group: "g", Ask: "a", Pulls: []delivery.MergePull{
|
||||
// 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}}})
|
||||
if _, err := MergeActs(v); err == nil {
|
||||
t.Error("a merge with no approved heads was taken: the forge's tool would refuse it with nothing to say which")
|
||||
t.Error("a merge with no approved heads was taken")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,13 +178,13 @@ func TestRefusedMessageNamesTheActAndTheForgesWords(t *testing.T) { // 1341/AC3,
|
||||
if m.Event != "forge.novox.mesh-delivery.refused" {
|
||||
t.Errorf("event = %q", m.Event)
|
||||
}
|
||||
f, err := delivery.DecodeForge(m.Data)
|
||||
f, err := forgemsg.DecodeForge(m.Data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := delivery.Forge{V: delivery.Version, Kind: delivery.ForgeRefused, Repository: "novox/mesh-delivery", Number: 12, Head: headA,
|
||||
want := forgemsg.Forge{V: delivery.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 !delivery.SameForge(f, want) {
|
||||
if !forgemsg.SameForge(f, want) {
|
||||
t.Errorf("message = %+v", f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
)
|
||||
|
||||
// ErrUnreadable is wrapped by every error that says an event cannot be turned into a message; it is never
|
||||
@@ -110,11 +110,11 @@ func ConvertKept(subject string, data []byte, trees Trees, files Files, kept Kep
|
||||
var action string
|
||||
switch subject {
|
||||
case UpdatedSubject:
|
||||
action = delivery.PullUpdated
|
||||
action = forgemsg.PullUpdated
|
||||
case ClosedSubject:
|
||||
action = delivery.PullClosed
|
||||
action = forgemsg.PullClosed
|
||||
case MergedSubject:
|
||||
action = delivery.PullClosed // unless it merged into the trunk, below
|
||||
action = forgemsg.PullClosed // unless it merged into the trunk, below
|
||||
default:
|
||||
return Message{}, fmt.Errorf("%w: %s", ErrNotHandled, subject)
|
||||
}
|
||||
@@ -136,29 +136,29 @@ func ConvertKept(subject string, data []byte, trees Trees, files Files, kept Kep
|
||||
return merged(subject, p, trees, files, kept)
|
||||
}
|
||||
said := ""
|
||||
if action == delivery.PullUpdated && p.Base != "" && p.Base != Trunk {
|
||||
if action == forgemsg.PullUpdated && p.Base != "" && p.Base != Trunk {
|
||||
// The checker judges pull requests into main only, and source.main names main's head only (novox/hq ADR 0484 §11): a
|
||||
// pull request into another base is no candidate, as it is none at the start (OpenPulls) and at its merge. It is
|
||||
// written closed, so a candidate an older head made is taken out, and said (issue 1422).
|
||||
action = delivery.PullClosed
|
||||
action = forgemsg.PullClosed
|
||||
said = fmt.Sprintf("%s/%s#%d at %s is into %s, not %s: written closed, so it is no candidate; the checker judges pull requests into %s only (novox/hq ADR 0484 §11)",
|
||||
p.Owner, p.Repo, p.Number, p.HeadSHA[:12], p.Base, Trunk, Trunk)
|
||||
}
|
||||
f := delivery.Forge{Kind: delivery.ForgePull, Repository: p.Owner + "/" + p.Repo, Branch: p.Head, Number: p.Number,
|
||||
f := forgemsg.Forge{Kind: forgemsg.ForgePull, Repository: p.Owner + "/" + p.Repo, Branch: p.Head, Number: p.Number,
|
||||
Head: p.HeadSHA, Action: action}
|
||||
// The directories are the forge's word only when it said it could read them (module_dirs_said).
|
||||
if action == delivery.PullUpdated && p.ModuleDirsSaid {
|
||||
if action == forgemsg.PullUpdated && p.ModuleDirsSaid {
|
||||
f.Dirs, f.DirsKnown = p.ModuleDirs, true
|
||||
var err error
|
||||
if f.Manifests, f.Locks, _, err = inputsRead(p.Owner, p.Repo, p.HeadSHA, p.ModuleDirs, files); err != nil {
|
||||
return Message{}, fmt.Errorf("%w: %s: the manifests of %s/%s at the head %s: %v", ErrRetry, subject, p.Owner, p.Repo, p.HeadSHA, err)
|
||||
}
|
||||
}
|
||||
event, err := delivery.ForgeEvent(f.Repository, delivery.ForgePull)
|
||||
event, err := forgemsg.ForgeEvent(f.Repository, forgemsg.ForgePull)
|
||||
if err != nil {
|
||||
return Message{}, unreadable(subject, "%v", err)
|
||||
}
|
||||
body, err := delivery.EncodeForge(f)
|
||||
body, err := forgemsg.EncodeForge(f)
|
||||
if err != nil {
|
||||
return Message{}, unreadable(subject, "%v", err)
|
||||
}
|
||||
@@ -313,11 +313,11 @@ func merged(subject string, p pull, trees Trees, files Files, kept Kept) (Messag
|
||||
if len(locks) == 0 {
|
||||
locks = nil
|
||||
}
|
||||
event, err := delivery.ForgeEvent(repository, delivery.ForgeMerged)
|
||||
event, err := forgemsg.ForgeEvent(repository, forgemsg.ForgeMerged)
|
||||
if err != nil {
|
||||
return Message{}, unreadable(subject, "%v", err)
|
||||
}
|
||||
f := delivery.Forge{Kind: delivery.ForgeMerged, Repository: repository, Branch: p.Head, Number: p.Number,
|
||||
f := forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: repository, Branch: p.Head, Number: p.Number,
|
||||
Head: p.HeadSHA, Merge: p.MergeSHA, Root: root, Trees: dirs, Manifests: manifests, Locks: locks}
|
||||
if repository == CatalogRepository {
|
||||
// The catalogue's predefined list at the merge (novox/hq ADR 0565 §2): the indexer keeps it for mesh-config.
|
||||
@@ -331,7 +331,7 @@ func merged(subject string, p pull, trees Trees, files Files, kept Kept) (Messag
|
||||
f.Predefined = json.RawMessage(raw)
|
||||
}
|
||||
}
|
||||
body, err := delivery.EncodeForge(f)
|
||||
body, err := forgemsg.EncodeForge(f)
|
||||
if err != nil {
|
||||
return Message{}, unreadable(subject, "%v", err)
|
||||
}
|
||||
@@ -373,12 +373,12 @@ func OpenPulls(answer []byte) (msgs []Message, skipped []string, err error) {
|
||||
skipped = append(skipped, name+": it names no owner, repo, number, branch or 40-hex head")
|
||||
continue
|
||||
}
|
||||
f := delivery.Forge{Kind: delivery.ForgePull, Repository: p.Owner + "/" + p.Repo, Branch: p.Head, Number: p.Number,
|
||||
Head: p.HeadSHA, Action: delivery.PullUpdated}
|
||||
event, err := delivery.ForgeEvent(f.Repository, delivery.ForgePull)
|
||||
f := forgemsg.Forge{Kind: forgemsg.ForgePull, Repository: p.Owner + "/" + p.Repo, Branch: p.Head, Number: p.Number,
|
||||
Head: p.HeadSHA, Action: forgemsg.PullUpdated}
|
||||
event, err := forgemsg.ForgeEvent(f.Repository, forgemsg.ForgePull)
|
||||
var body []byte
|
||||
if err == nil {
|
||||
body, err = delivery.EncodeForge(f)
|
||||
body, err = forgemsg.EncodeForge(f)
|
||||
}
|
||||
if err != nil {
|
||||
skipped = append(skipped, name+": "+err.Error())
|
||||
@@ -392,12 +392,12 @@ func OpenPulls(answer []byte) (msgs []Message, skipped []string, err error) {
|
||||
// Track says what a message the bridge wrote changes in the head it keeps for each pull request (the bucket heads,
|
||||
// novox/hq ADR 0443 §5): the key `<owner>.<name>.<number>`, and the head to keep under it, or "" to take the key away
|
||||
// (a closed pull request, a merged one). ok is false for a message that keeps no head.
|
||||
func Track(f delivery.Forge) (key, head string, ok bool) {
|
||||
func Track(f forgemsg.Forge) (key, head string, ok bool) {
|
||||
key = strings.ReplaceAll(f.Repository, "/", ".") + "." + strconv.Itoa(f.Number)
|
||||
switch {
|
||||
case f.Kind == delivery.ForgePull && f.Action != delivery.PullClosed:
|
||||
case f.Kind == forgemsg.ForgePull && f.Action != forgemsg.PullClosed:
|
||||
return key, f.Head, true
|
||||
case f.Kind == delivery.ForgePull || f.Kind == delivery.ForgeMerged:
|
||||
case f.Kind == forgemsg.ForgePull || f.Kind == forgemsg.ForgeMerged:
|
||||
return key, "", true
|
||||
}
|
||||
return "", "", false
|
||||
@@ -413,22 +413,22 @@ func OpenMessage(answer []byte) (Message, error) {
|
||||
if err := json.Unmarshal(answer, &a); err != nil || a.Pulls == nil {
|
||||
return Message{}, fmt.Errorf("the answer of gitea_open_pulls is not {pulls: [...]}: %v", err)
|
||||
}
|
||||
open := []delivery.OpenPull{}
|
||||
open := []forgemsg.OpenPull{}
|
||||
for _, p := range *a.Pulls {
|
||||
if p.Base != Trunk || p.Owner == "" || p.Repo == "" || p.Number <= 0 {
|
||||
continue
|
||||
}
|
||||
open = append(open, delivery.OpenPull{Repository: p.Owner + "/" + p.Repo, Number: p.Number})
|
||||
open = append(open, forgemsg.OpenPull{Repository: p.Owner + "/" + p.Repo, Number: p.Number})
|
||||
}
|
||||
slices.SortFunc(open, func(a, b delivery.OpenPull) int {
|
||||
slices.SortFunc(open, func(a, b forgemsg.OpenPull) int {
|
||||
if c := strings.Compare(a.Repository, b.Repository); c != 0 {
|
||||
return c
|
||||
}
|
||||
return a.Number - b.Number
|
||||
})
|
||||
body, err := delivery.EncodeForge(delivery.Forge{Kind: delivery.ForgeOpen, Open: open})
|
||||
body, err := forgemsg.EncodeForge(forgemsg.Forge{Kind: forgemsg.ForgeOpen, Open: open})
|
||||
if err != nil {
|
||||
return Message{}, fmt.Errorf("the open pull requests cannot be said: %w", err)
|
||||
}
|
||||
return Message{Event: delivery.OpenEvent(), Data: body}, nil
|
||||
return Message{Event: forgemsg.OpenEvent(), Data: body}, nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -54,13 +55,13 @@ func TestUpdatedBecomesAPullMessage(t *testing.T) { // 1235/AC2 (the conversion;
|
||||
if msg.Event != "forge.novox.mesh-delivery.pull" {
|
||||
t.Errorf("event = %q", msg.Event)
|
||||
}
|
||||
f, err := delivery.DecodeForge(msg.Data)
|
||||
f, err := forgemsg.DecodeForge(msg.Data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := delivery.Forge{V: delivery.Version, Kind: delivery.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12,
|
||||
Head: head, Action: delivery.PullUpdated, Dirs: []string{"modules/a"}, DirsKnown: true, Manifests: map[string]string{"modules/a": "{}"}}
|
||||
if !delivery.SameForge(f, want) {
|
||||
want := forgemsg.Forge{V: delivery.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)
|
||||
}
|
||||
}
|
||||
@@ -70,7 +71,7 @@ func TestDirsNotSaidAreNotKnown(t *testing.T) { // 1235/AC3
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, _ := delivery.DecodeForge(msg.Data)
|
||||
f, _ := forgemsg.DecodeForge(msg.Data)
|
||||
if f.DirsKnown || len(f.Dirs) != 0 {
|
||||
t.Errorf("dirs = %v known = %v, want not known", f.Dirs, f.DirsKnown)
|
||||
}
|
||||
@@ -79,7 +80,7 @@ func TestDirsNotSaidAreNotKnown(t *testing.T) { // 1235/AC3
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, _ = delivery.DecodeForge(msg.Data)
|
||||
f, _ = forgemsg.DecodeForge(msg.Data)
|
||||
if !f.DirsKnown || len(f.Dirs) != 0 {
|
||||
t.Errorf("dirs = %v known = %v, want known and empty", f.Dirs, f.DirsKnown)
|
||||
}
|
||||
@@ -88,7 +89,7 @@ func TestDirsNotSaidAreNotKnown(t *testing.T) { // 1235/AC3
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f, _ = delivery.DecodeForge(msg.Data); f.DirsKnown || len(f.Dirs) != 0 {
|
||||
if f, _ = forgemsg.DecodeForge(msg.Data); f.DirsKnown || len(f.Dirs) != 0 {
|
||||
t.Errorf("dirs %v known %v without module_dirs_said", f.Dirs, f.DirsKnown)
|
||||
}
|
||||
}
|
||||
@@ -101,13 +102,13 @@ func TestClosedBecomesAClosedPullMessage(t *testing.T) { // 1235/AC4
|
||||
if msg.Event != "forge.novox.mesh-delivery.pull" {
|
||||
t.Errorf("event = %q", msg.Event)
|
||||
}
|
||||
f, err := delivery.DecodeForge(msg.Data)
|
||||
f, err := forgemsg.DecodeForge(msg.Data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := delivery.Forge{V: delivery.Version, Kind: delivery.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12,
|
||||
Head: head, Action: delivery.PullClosed}
|
||||
if !delivery.SameForge(f, want) {
|
||||
want := forgemsg.Forge{V: delivery.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)
|
||||
}
|
||||
// Issue 1422, ADR 0484 §11: a head pushed to a pull request into another base is written closed, read nothing for, and said;
|
||||
@@ -116,12 +117,12 @@ func TestClosedBecomesAClosedPullMessage(t *testing.T) { // 1235/AC4
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f, err = delivery.DecodeForge(msg.Data); err != nil || !delivery.SameForge(f, want) || !strings.Contains(msg.Said, "is into nox-mesh, not main") {
|
||||
if f, err = forgemsg.DecodeForge(msg.Data); err != nil || !forgemsg.SameForge(f, want) || !strings.Contains(msg.Said, "is into nox-mesh, not main") {
|
||||
t.Errorf("an update into nox-mesh is %+v (%v), said %q; want it closed and said", f, err, msg.Said)
|
||||
}
|
||||
for _, base := range []any{"main", nil} {
|
||||
msg, err = Convert(updatedSubject, updated(map[string]any{"base": base}), nil, anyFiles)
|
||||
if f, _ = delivery.DecodeForge(msg.Data); err != nil || f.Action != delivery.PullUpdated || msg.Said != "" {
|
||||
if f, _ = forgemsg.DecodeForge(msg.Data); err != nil || f.Action != forgemsg.PullUpdated || msg.Said != "" {
|
||||
t.Errorf("an update with base %v is %+v (%v), said %q; want it updated", base, f, err, msg.Said)
|
||||
}
|
||||
}
|
||||
@@ -170,12 +171,18 @@ func mustDrop(body []byte, key string) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
func TestOtherSubjectsAreNotHandled(t *testing.T) { // 1235
|
||||
func TestOtherSubjectsAreNotHandled(t *testing.T) { // 1235, 1236
|
||||
for _, s := range []string{"mesh.mod.gitea.event.something.else", "mesh.mod.other.event.pull.updated"} {
|
||||
if _, err := Convert(s, updated(nil), nil, anyFiles); !errors.Is(err, ErrNotHandled) {
|
||||
t.Errorf("%s: err = %v, want ErrNotHandled", s, err)
|
||||
}
|
||||
}
|
||||
// 1236: a merged event without a full merge commit is unreadable, and the forge is not asked.
|
||||
var calls []call
|
||||
_, err := Convert(mergedSubject, mergedBody(map[string]any{"merge_commit_sha": "abc"}), treesOf(&calls, "r0", map[string]string{"a": "t"}, nil), anyFiles)
|
||||
if !errors.Is(err, ErrUnreadable) || len(calls) != 0 {
|
||||
t.Errorf("merged without a merge commit: err = %v calls = %v", err, calls)
|
||||
}
|
||||
}
|
||||
|
||||
const merge = "89abcdef0123456789abcdef0123456789abcdef"
|
||||
@@ -222,13 +229,13 @@ func TestMergeIntoMainBecomesAMergedMessage(t *testing.T) { // 1236/AC1
|
||||
if msg.Event != "forge.novox.mesh-delivery.merged" {
|
||||
t.Errorf("event = %q", msg.Event)
|
||||
}
|
||||
f, err := delivery.DecodeForge(msg.Data)
|
||||
f, err := forgemsg.DecodeForge(msg.Data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := delivery.Forge{V: delivery.Version, Kind: delivery.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12,
|
||||
want := forgemsg.Forge{V: delivery.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 !delivery.SameForge(f, want) {
|
||||
if !forgemsg.SameForge(f, want) {
|
||||
t.Errorf("message = %+v, want %+v", f, want)
|
||||
}
|
||||
if len(calls) != 1 || calls[0] != (call{"novox", "mesh-delivery", merge}) {
|
||||
@@ -242,13 +249,13 @@ func TestMergeIntoAnotherBranchIsAClosedPull(t *testing.T) { // 1236/AC2
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := delivery.DecodeForge(msg.Data)
|
||||
f, err := forgemsg.DecodeForge(msg.Data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := delivery.Forge{V: delivery.Version, Kind: delivery.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12,
|
||||
Head: head, Action: delivery.PullClosed}
|
||||
if msg.Event != "forge.novox.mesh-delivery.pull" || !delivery.SameForge(f, want) {
|
||||
want := forgemsg.Forge{V: delivery.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)
|
||||
}
|
||||
if len(calls) != 0 {
|
||||
@@ -277,22 +284,14 @@ func TestNoModuleJSONAnywhereWritesAMergedMessageWithNoTrees(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := delivery.DecodeForge(msg.Data)
|
||||
want := delivery.Forge{V: delivery.Version, Kind: delivery.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12,
|
||||
f, err := forgemsg.DecodeForge(msg.Data)
|
||||
want := forgemsg.Forge{V: delivery.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" || !delivery.SameForge(f, want) || !strings.Contains(msg.Said, "no module.json") {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergedWithoutAMergeCommitIsUnreadable(t *testing.T) { // 1236
|
||||
var calls []call
|
||||
_, err := Convert(mergedSubject, mergedBody(map[string]any{"merge_commit_sha": "abc"}), treesOf(&calls, "r0", map[string]string{"a": "t"}, nil), anyFiles)
|
||||
if !errors.Is(err, ErrUnreadable) || len(calls) != 0 {
|
||||
t.Errorf("err = %v calls = %v", err, calls)
|
||||
}
|
||||
}
|
||||
|
||||
// openAnswer is gitea_open_pulls's answer as mesh-catalog modules/gitea/client.ts:369-381 builds each entry
|
||||
// (owner, repo, number, base, head, head_sha, html_url, required, statuses, checks, labels) inside {pulls}.
|
||||
func openAnswer(pulls ...map[string]any) []byte {
|
||||
@@ -311,12 +310,12 @@ func TestOpenPullsAreUpdatedPullMessagesWithUnknownDirs(t *testing.T) { // 1237/
|
||||
if err != nil || len(skipped) != 0 || len(msgs) != 2 {
|
||||
t.Fatalf("msgs %d skipped %v err %v", len(msgs), skipped, err)
|
||||
}
|
||||
for i, want := range []delivery.Forge{
|
||||
{V: delivery.Version, Kind: delivery.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Action: delivery.PullUpdated},
|
||||
{V: delivery.Version, Kind: delivery.ForgePull, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 300, Head: h2, Action: delivery.PullUpdated},
|
||||
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},
|
||||
} {
|
||||
f, err := delivery.DecodeForge(msgs[i].Data)
|
||||
if err != nil || !delivery.SameForge(f, want) {
|
||||
f, err := forgemsg.DecodeForge(msgs[i].Data)
|
||||
if err != nil || !forgemsg.SameForge(f, want) {
|
||||
t.Errorf("message %d = %+v err %v, want %+v", i, f, err, want)
|
||||
}
|
||||
if f.DirsKnown {
|
||||
@@ -328,14 +327,11 @@ func TestOpenPullsAreUpdatedPullMessagesWithUnknownDirs(t *testing.T) { // 1237/
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenPullIntoAnotherBranchGetsNoMessage(t *testing.T) { // 1237/AC4
|
||||
msgs, skipped, err := OpenPulls(openAnswer(openEntry("mesh-delivery", 12, "feat/y", "feat/x", head)))
|
||||
if err != nil || len(msgs) != 0 || len(skipped) != 0 {
|
||||
t.Errorf("msgs %d skipped %v err %v, want none", len(msgs), skipped, err)
|
||||
func TestOpenPullsUnreadableEntryIsNamedAndTheRestGo(t *testing.T) { // 1237, 1237/AC4
|
||||
// 1237/AC4: an open pull request into another branch gets no message.
|
||||
if msgs, skipped, err := OpenPulls(openAnswer(openEntry("mesh-delivery", 12, "feat/y", "feat/x", head))); err != nil || len(msgs) != 0 || len(skipped) != 0 {
|
||||
t.Errorf("into another branch: msgs %d skipped %v err %v, want none", len(msgs), skipped, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenPullsUnreadableEntryIsNamedAndTheRestGo(t *testing.T) { // 1237
|
||||
msgs, skipped, err := OpenPulls(openAnswer(openEntry("a", 1, "main", "b", "short"), openEntry("mesh-delivery", 12, "main", "feat/x", head)))
|
||||
if err != nil || len(msgs) != 1 || len(skipped) != 1 || !strings.Contains(skipped[0], "novox/a#1") {
|
||||
t.Errorf("msgs %d skipped %v err %v", len(msgs), skipped, err)
|
||||
@@ -355,15 +351,15 @@ func TestOpenMessageNamesEveryOpenPullIntoTheTrunk(t *testing.T) { // 1279/AC6
|
||||
if err != nil || m.Event != "forge.open" {
|
||||
t.Fatalf("message %+v err %v", m, err)
|
||||
}
|
||||
f, err := delivery.DecodeForge(m.Data)
|
||||
f, err := forgemsg.DecodeForge(m.Data)
|
||||
// Every one into the trunk, whether its head reads or not (it is open); none into another branch; in a fixed order.
|
||||
want := []delivery.OpenPull{{Repository: "novox/a", Number: 1}, {Repository: "novox/mesh-catalog", Number: 300}, {Repository: "novox/mesh-delivery", Number: 12}}
|
||||
if err != nil || f.Kind != delivery.ForgeOpen || len(f.Open) != 3 || f.Open[0] != want[0] || f.Open[1] != want[1] || f.Open[2] != want[2] {
|
||||
want := []forgemsg.OpenPull{{Repository: "novox/a", Number: 1}, {Repository: "novox/mesh-catalog", Number: 300}, {Repository: "novox/mesh-delivery", Number: 12}}
|
||||
if err != nil || f.Kind != forgemsg.ForgeOpen || len(f.Open) != 3 || f.Open[0] != want[0] || f.Open[1] != want[1] || f.Open[2] != want[2] {
|
||||
t.Errorf("open message = %+v err %v, want %v", f, err, want)
|
||||
}
|
||||
// Nothing open is a message too: the indexer then drops every candidate.
|
||||
m, err = OpenMessage([]byte(`{"pulls":[]}`))
|
||||
if f, derr := delivery.DecodeForge(m.Data); err != nil || derr != nil || f.Kind != delivery.ForgeOpen || len(f.Open) != 0 {
|
||||
if f, derr := forgemsg.DecodeForge(m.Data); err != nil || derr != nil || f.Kind != forgemsg.ForgeOpen || len(f.Open) != 0 {
|
||||
t.Errorf("empty answer: %+v %v %v", f, err, derr)
|
||||
}
|
||||
for _, bad := range []string{`{"pulls":null}`, `{}`, `x`} {
|
||||
@@ -374,24 +370,24 @@ func TestOpenMessageNamesEveryOpenPullIntoTheTrunk(t *testing.T) { // 1279/AC6
|
||||
}
|
||||
|
||||
func TestTrackSaysWhichHeadOfWhichPullRequestWasWritten(t *testing.T) { // 1279/AC6
|
||||
pullMsg := func(action string) delivery.Forge {
|
||||
return delivery.Forge{Kind: delivery.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Action: action}
|
||||
pullMsg := func(action string) forgemsg.Forge {
|
||||
return forgemsg.Forge{Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Action: action}
|
||||
}
|
||||
for _, a := range []string{delivery.PullOpened, delivery.PullUpdated} {
|
||||
for _, a := range []string{forgemsg.PullOpened, forgemsg.PullUpdated} {
|
||||
if key, h, ok := Track(pullMsg(a)); key != "novox.mesh-delivery.12" || h != head || !ok {
|
||||
t.Errorf("%s: Track = %q %q %v, want the head kept under a bucket key", a, key, h, ok)
|
||||
}
|
||||
}
|
||||
// A closed pull request and a merged one are no longer kept: their key goes.
|
||||
if key, h, ok := Track(pullMsg(delivery.PullClosed)); key != "novox.mesh-delivery.12" || h != "" || !ok {
|
||||
if key, h, ok := Track(pullMsg(forgemsg.PullClosed)); key != "novox.mesh-delivery.12" || h != "" || !ok {
|
||||
t.Errorf("closed: Track = %q %q %v", key, h, ok)
|
||||
}
|
||||
merged := delivery.Forge{Kind: delivery.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Merge: "m", Root: "r", Trees: map[string]string{"": "t"}}
|
||||
merged := forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head, Merge: "m", Root: "r", Trees: map[string]string{"": "t"}}
|
||||
if key, h, ok := Track(merged); key != "novox.mesh-delivery.12" || h != "" || !ok {
|
||||
t.Errorf("merged: Track = %q %q %v", key, h, ok)
|
||||
}
|
||||
// The open message and the rest hold no head.
|
||||
if _, _, ok := Track(delivery.Forge{Kind: delivery.ForgeOpen}); ok {
|
||||
if _, _, ok := Track(forgemsg.Forge{Kind: forgemsg.ForgeOpen}); ok {
|
||||
t.Error("an open message was tracked")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
)
|
||||
|
||||
// The bytes the forge answers in these tests are two real files of this repository, read from the working tree: the
|
||||
@@ -69,7 +69,7 @@ func TestMergedCarriesManifestsAndLocks(t *testing.T) { // 1448/AC1
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := delivery.DecodeForge(msg.Data)
|
||||
f, err := forgemsg.DecodeForge(msg.Data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -90,7 +90,7 @@ func TestMergedCarriesManifestsAndLocks(t *testing.T) { // 1448/AC1
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f, err = delivery.DecodeForge(msg.Data); err != nil {
|
||||
if f, err = forgemsg.DecodeForge(msg.Data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(f.RetiredPlacements) > 0 || f.RetiredRead || strings.Contains(string(msg.Data), `"placements`) {
|
||||
@@ -108,7 +108,7 @@ func TestMergedCarriesManifestsAndLocks(t *testing.T) { // 1448/AC1
|
||||
}
|
||||
forge.have[PredefinedFile] = []byte(`{"font-size":{"type":"integer","value":13}}`)
|
||||
msg, _ = Convert(mergedSubject, mergedBody(map[string]any{"repo": "mesh-catalog"}), treesOf(&calls, "r0", map[string]string{"modules/a": "t1"}, nil), forge.get)
|
||||
if f, err = delivery.DecodeForge(msg.Data); err != nil || string(f.Predefined) != string(forge.have[PredefinedFile]) || f.PredefinedRead {
|
||||
if f, err = forgemsg.DecodeForge(msg.Data); err != nil || string(f.Predefined) != string(forge.have[PredefinedFile]) || f.PredefinedRead {
|
||||
t.Errorf("predefined = %s (%v)", f.Predefined, err)
|
||||
}
|
||||
forge.calls = nil
|
||||
@@ -133,7 +133,7 @@ func TestPullWithKnownDirsCarriesTheSameAtTheHead(t *testing.T) { // 1448/AC2
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, _ := delivery.DecodeForge(msg.Data)
|
||||
f, _ := forgemsg.DecodeForge(msg.Data)
|
||||
if !reflect.DeepEqual(f.Manifests, map[string]string{"modules/a": string(manifest)}) || len(f.Locks) != 1 || len(f.Packages) != 0 {
|
||||
t.Errorf("manifests %v locks %v packages %v", f.Manifests, f.Locks, f.Packages)
|
||||
}
|
||||
@@ -146,7 +146,7 @@ func TestPullWithKnownDirsCarriesTheSameAtTheHead(t *testing.T) { // 1448/AC2
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, _ = delivery.DecodeForge(msg.Data)
|
||||
f, _ = forgemsg.DecodeForge(msg.Data)
|
||||
if f.Manifests != nil || f.Locks != nil || len(forge.calls) != 0 {
|
||||
t.Errorf("manifests %v locks %v calls %v", f.Manifests, f.Locks, forge.calls)
|
||||
}
|
||||
@@ -175,7 +175,7 @@ func TestAForgeErrorOrAMissingManifestIsRetryAndALockNotFoundIsLeftOut(t *testin
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f, _ := delivery.DecodeForge(msg.Data); f.Locks != nil {
|
||||
if f, _ := forgemsg.DecodeForge(msg.Data); f.Locks != nil {
|
||||
t.Errorf("locks = %v, want none for lock files the forge does not have", f.Locks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
)
|
||||
|
||||
// ADR 0495 clause 1 with the lead's decision L8 of R4e: the forge bridge is no reader of a manifest's `paths`. A merge of a
|
||||
@@ -26,7 +26,7 @@ func TestAMergedMessageCarriesNoNamedPathsTheBridgeDerived(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f, err := delivery.DecodeForge(msg.Data)
|
||||
f, err := forgemsg.DecodeForge(msg.Data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
@@ -102,15 +104,15 @@ func TestIndexerWhole(t *testing.T) { // 1238/AC2, AC10; 1585/T14, T15
|
||||
}
|
||||
})
|
||||
h1, h2 := "0123456789abcdef0123456789abcdef01234567", "89abcdef0123456789abcdef0123456789abcdef"
|
||||
for _, f := range []delivery.Forge{
|
||||
{Kind: delivery.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: h1, Action: delivery.PullUpdated, Dirs: []string{"modules/a"}, DirsKnown: true},
|
||||
{Kind: delivery.ForgePull, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 300, Head: h2, Action: delivery.PullUpdated, Dirs: []string{"modules/c"}, DirsKnown: true},
|
||||
for _, f := range []forgemsg.Forge{
|
||||
{Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: h1, Action: forgemsg.PullUpdated, Dirs: []string{"modules/a"}, DirsKnown: true},
|
||||
{Kind: forgemsg.ForgePull, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 300, Head: h2, Action: forgemsg.PullUpdated, Dirs: []string{"modules/c"}, DirsKnown: true},
|
||||
} {
|
||||
data, err := delivery.EncodeForge(f)
|
||||
data, err := forgemsg.EncodeForge(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
event, _ := delivery.ForgeEvent(f.Repository, delivery.ForgePull)
|
||||
event, _ := forgemsg.ForgeEvent(f.Repository, forgemsg.ForgePull)
|
||||
if _, err := js.Publish(ctx, "mesh.stream.mesh-delivery-forge-bridge.event."+event, data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -143,43 +145,43 @@ func TestIndexerWhole(t *testing.T) { // 1238/AC2, AC10; 1585/T14, T15
|
||||
t.Fatal(err)
|
||||
}
|
||||
const ix = delivery.IndexerModule
|
||||
m1 := delivery.Forge{Kind: delivery.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: h1,
|
||||
m1 := forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: h1,
|
||||
Merge: "1111111111111111111111111111111111111111", Root: "r0",
|
||||
Trees: map[string]string{"modules/" + ix: "tree-indexer-2", "modules/a": "tree-a-2"},
|
||||
Manifests: map[string]string{"modules/" + ix: string(manifest), "modules/a": `{"module":"a","version":"1"}`},
|
||||
Locks: map[string]map[string]string{"modules/" + ix: {"go.sum": "3b5028f0c1e9bde04dd766abd63eb5931bc516bae695cf46f00abcd8510b0381"}}}
|
||||
merged := func(f delivery.Forge) uint64 {
|
||||
merged := func(f forgemsg.Forge) uint64 {
|
||||
t.Helper()
|
||||
data, err := delivery.EncodeForge(f)
|
||||
data, err := forgemsg.EncodeForge(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
event, _ := delivery.ForgeEvent(f.Repository, delivery.ForgeMerged)
|
||||
event, _ := forgemsg.ForgeEvent(f.Repository, forgemsg.ForgeMerged)
|
||||
ack, err := js.Publish(ctx, "mesh.stream.mesh-delivery-forge-bridge.event."+event, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return ack.Sequence
|
||||
}
|
||||
m1Key := delivery.PendingKey(merged(m1))
|
||||
m1Key := takein.PendingKey(merged(m1))
|
||||
takeIns, err := js.Stream(ctx, "TAKE_IN")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// said is the last take-in event under kind and key, within 20 s.
|
||||
said := func(kind, key string) (delivery.TakeIn, bool) {
|
||||
event, _ := delivery.TakeInEvent(kind, key)
|
||||
said := func(kind, key string) (takein.TakeIn, bool) {
|
||||
event, _ := takein.TakeInEvent(kind, key)
|
||||
for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
|
||||
if msg, err := takeIns.GetLastMsgForSubject(ctx, process.ModuleStreamSubject(ix, event)); err == nil {
|
||||
if ti, err := delivery.DecodeTakeIn(msg.Data); err == nil {
|
||||
if ti, err := takein.DecodeTakeIn(msg.Data); err == nil {
|
||||
return ti, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return delivery.TakeIn{}, false
|
||||
return takein.TakeIn{}, false
|
||||
}
|
||||
held, ok := said(delivery.TakeInPending, m1Key)
|
||||
if !ok || held.What != delivery.TakeInRevision || held.WaitsFor == "" || held.WaitsFor == first || len(held.Merges) != 1 {
|
||||
held, ok := said(takein.TakeInPending, m1Key)
|
||||
if !ok || held.What != takein.TakeInRevision || held.WaitsFor == "" || held.WaitsFor == first || len(held.Merges) != 1 {
|
||||
t.Fatalf("take-in.pending.%s = %+v (said %v) within 20 s, want the revision waiting for another build than %s", m1Key, held, ok, first)
|
||||
}
|
||||
named := held.WaitsFor
|
||||
@@ -226,14 +228,14 @@ func TestIndexerWhole(t *testing.T) { // 1238/AC2, AC10; 1585/T14, T15
|
||||
// A group's first member arrives meanwhile; its end is still to come (the knowledge base's remark: it stays across the restart).
|
||||
members := []delivery.Head{{Repository: "novox/mesh-catalog", Number: 40, Head: "4040404040404040404040404040404040404040"},
|
||||
{Repository: "novox/mesh-controller", Number: 41, Head: "4141414141414141414141414141414141414141"}}
|
||||
g1 := delivery.Forge{Kind: delivery.ForgeMerged, Repository: "novox/mesh-catalog", Branch: "feat/g", Number: 40, Head: members[0].Head,
|
||||
g1 := forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-catalog", Branch: "feat/g", Number: 40, Head: members[0].Head,
|
||||
Merge: "4000000000000000000000000000000000000000", Root: "r0", Trees: map[string]string{"modules/c": "tree-c-2"},
|
||||
Manifests: map[string]string{"modules/c": `{"module":"c","version":"1"}`}, Group: "feat/g", Members: members}
|
||||
g2 := delivery.Forge{Kind: delivery.ForgeMerged, Repository: "novox/mesh-controller", Branch: "feat/g", Number: 41, Head: members[1].Head,
|
||||
g2 := forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-controller", Branch: "feat/g", Number: 41, Head: members[1].Head,
|
||||
Merge: "4100000000000000000000000000000000000000", Root: "r0", Trees: map[string]string{"modules/k": "tree-k-1"},
|
||||
Manifests: map[string]string{"modules/k": `{"module":"k","version":"1"}`}, Group: "feat/g", Members: members}
|
||||
g1Key, groupKey := delivery.PendingKey(merged(g1)), delivery.GroupTakeInKey("feat/g")
|
||||
if gathered, ok := said(delivery.TakeInPending, groupKey); !ok || gathered.What != delivery.TakeInGroup || len(gathered.Merges) != 1 {
|
||||
g1Key, groupKey := takein.PendingKey(merged(g1)), takein.GroupTakeInKey("feat/g")
|
||||
if gathered, ok := said(takein.TakeInPending, groupKey); !ok || gathered.What != takein.TakeInGroup || len(gathered.Merges) != 1 {
|
||||
t.Fatalf("take-in.pending.%s = %+v (said %v) within 20 s, want the group with its first member", groupKey, gathered, ok)
|
||||
}
|
||||
// indexer_pending, the tool over the same state, names both waits.
|
||||
@@ -241,29 +243,29 @@ func TestIndexerWhole(t *testing.T) { // 1238/AC2, AC10; 1585/T14, T15
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
waits := func() []delivery.PendingWait {
|
||||
waits := func() []takein.PendingWait {
|
||||
t.Helper()
|
||||
answer, err := index.PendingTools(kvStore{pendingKV}, nil, time.Now)[0].Run(map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return answer.(delivery.PendingAnswer).Waits
|
||||
return answer.(takein.PendingAnswer).Waits
|
||||
}
|
||||
var w []delivery.PendingWait
|
||||
var w []takein.PendingWait
|
||||
for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
|
||||
if w = waits(); len(w) == 2 && w[0].WaitsFor == named && w[1].Said != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(w) != 2 || w[0].Key != m1Key || w[0].WaitsFor != named || !slices.Contains(w[0].Holds, "a") || !slices.Contains(w[0].Holds, ix) ||
|
||||
w[1].Key != groupKey || w[1].What != delivery.TakeInGroup || len(w[1].Merges) != 1 || !strings.HasSuffix(w[1].Said, "pending."+groupKey) {
|
||||
w[1].Key != groupKey || w[1].What != takein.TakeInGroup || len(w[1].Merges) != 1 || !strings.HasSuffix(w[1].Said, "pending."+groupKey) {
|
||||
t.Fatalf("indexer_pending = %+v, want the revision %s waiting for %s holding a and the indexer, then the group gathered", w, m1Key, named)
|
||||
}
|
||||
|
||||
// The named indexer starts (the node-engine now runs that build): it takes the revision in, and leaves the group.
|
||||
running.Store(&named)
|
||||
restart()
|
||||
if taken, ok := said(delivery.TakeInTaken, m1Key); !ok || len(taken.Merges) != 1 || taken.Merges[0].Merge != m1.Merge {
|
||||
if taken, ok := said(takein.TakeInTaken, m1Key); !ok || len(taken.Merges) != 1 || taken.Merges[0].Merge != m1.Merge {
|
||||
t.Fatalf("take-in.taken.%s = %+v (said %v) within 20 s of the start of %s", m1Key, taken, ok, named)
|
||||
}
|
||||
for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
|
||||
@@ -286,15 +288,15 @@ func TestIndexerWhole(t *testing.T) { // 1238/AC2, AC10; 1585/T14, T15
|
||||
|
||||
// I8b: the group's second member and its end: one take-in of both, said under the group's key.
|
||||
merged(g2)
|
||||
end, err := delivery.EncodeForgeGroupEnd(delivery.ForgeGroupEnd{Group: "feat/g", Members: []delivery.GroupMerge{
|
||||
end, err := forgemsg.EncodeForgeGroupEnd(forgemsg.ForgeGroupEnd{Group: "feat/g", Members: []forgemsg.GroupMerge{
|
||||
{Repository: g1.Repository, Number: g1.Number, Merge: g1.Merge}, {Repository: g2.Repository, Number: g2.Number, Merge: g2.Merge}}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := js.Publish(ctx, "mesh.stream.mesh-delivery-forge-bridge.event."+delivery.ForgeGroupEndEvent, end); err != nil {
|
||||
if _, err := js.Publish(ctx, "mesh.stream.mesh-delivery-forge-bridge.event."+forgemsg.ForgeGroupEndEvent, end); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if taken, ok := said(delivery.TakeInTaken, groupKey); !ok || taken.What != delivery.TakeInGroup || len(taken.Merges) != 2 {
|
||||
if taken, ok := said(takein.TakeInTaken, groupKey); !ok || taken.What != takein.TakeInGroup || len(taken.Merges) != 2 {
|
||||
t.Fatalf("take-in.taken.%s = %+v (said %v) within 20 s of the group's end, want both members", groupKey, taken, ok)
|
||||
}
|
||||
var hasC, hasK bool
|
||||
@@ -307,7 +309,7 @@ func TestIndexerWhole(t *testing.T) { // 1238/AC2, AC10; 1585/T14, T15
|
||||
t.Errorf("after the group's end: modules.c %v, modules.k %v, indexer_pending %+v: want both taken in and nothing waiting", hasC, hasK, w)
|
||||
}
|
||||
// One take-in: neither member was said as a revision of its own.
|
||||
single, _ := delivery.TakeInEvent(delivery.TakeInPending, g1Key)
|
||||
single, _ := takein.TakeInEvent(takein.TakeInPending, g1Key)
|
||||
if _, err := takeIns.GetLastMsgForSubject(ctx, process.ModuleStreamSubject(ix, single)); err == nil {
|
||||
t.Errorf("the group's first member %s was said pending as a revision of its own", g1Key)
|
||||
}
|
||||
@@ -443,7 +445,7 @@ func TestIndexerWritesModulesAtAMerge(t *testing.T) { // 1239/AC9; I1, I3: the m
|
||||
builds.end(t, "novox/mesh-controller", "build-agent", "modules/build-agent")
|
||||
})
|
||||
const merge = "89abcdef0123456789abcdef0123456789abcdef"
|
||||
data, err := delivery.EncodeForge(delivery.Forge{Kind: delivery.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12,
|
||||
data, err := forgemsg.EncodeForge(forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12,
|
||||
Head: "0123456789abcdef0123456789abcdef01234567", Merge: merge, Root: "r0", Trees: map[string]string{"modules/a": "newtree"},
|
||||
Manifests: map[string]string{"modules/a": `{"module":"a","version":"1"}`}})
|
||||
if err != nil {
|
||||
@@ -497,7 +499,7 @@ func TestIndexerKeepsManifestsAtAMerge(t *testing.T) { // 1444/AC4, 1449/AC4, 15
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
forge := delivery.Forge{Kind: delivery.ForgeMerged, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 301,
|
||||
forge := forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 301,
|
||||
Head: "0123456789abcdef0123456789abcdef01234567", Merge: "89abcdef0123456789abcdef0123456789abcdef", Root: "r0",
|
||||
Trees: map[string]string{"modules/avahi": "newtree"},
|
||||
Manifests: map[string]string{"modules/avahi": string(manifest)},
|
||||
@@ -505,7 +507,7 @@ func TestIndexerKeepsManifestsAtAMerge(t *testing.T) { // 1444/AC4, 1449/AC4, 15
|
||||
for name, raw := range judged {
|
||||
forge.Manifests["modules/"+name], forge.Trees["modules/"+name] = raw, "tree-"+name
|
||||
}
|
||||
data, err := delivery.EncodeForge(forge)
|
||||
data, err := forgemsg.EncodeForge(forge)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -599,7 +601,7 @@ func TestIndexerWritesAGoModuleAgainWhenTheBuildModuleReportsItsToolchain(t *tes
|
||||
js, ctx, _, modules := startIndexer(t, func(ctx context.Context, builds buildLog) {
|
||||
builds.end(t, "novox/mesh-catalog", "avahi", "modules/avahi")
|
||||
})
|
||||
data, err := delivery.EncodeForge(delivery.Forge{Kind: delivery.ForgeMerged, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 301,
|
||||
data, err := forgemsg.EncodeForge(forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 301,
|
||||
Head: "0123456789abcdef0123456789abcdef01234567", Merge: "89abcdef0123456789abcdef0123456789abcdef", Root: "r0",
|
||||
Trees: map[string]string{"modules/avahi": "newtree"},
|
||||
Manifests: map[string]string{"modules/avahi": string(manifest)},
|
||||
@@ -686,7 +688,7 @@ func TestIndexerGivesACandidateItsFingerprintWhenTheCheckerCreatesThePreviewsBuc
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
pull, err := delivery.EncodeForge(delivery.Forge{Kind: delivery.ForgePull, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 301, Head: head, Action: delivery.PullUpdated,
|
||||
pull, err := forgemsg.EncodeForge(forgemsg.Forge{Kind: forgemsg.ForgePull, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 301, Head: head, Action: forgemsg.PullUpdated,
|
||||
Dirs: []string{"modules/avahi"}, DirsKnown: true, Manifests: map[string]string{"modules/avahi": string(manifest)}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -856,7 +858,7 @@ func TestIndexerCountsOnlyThePlacedBuildNodesToolchains(t *testing.T) {
|
||||
place(ctx, t, "n1", "mesh-build-go")
|
||||
place(ctx, t, "n2")
|
||||
})
|
||||
data, err := delivery.EncodeForge(delivery.Forge{Kind: delivery.ForgeMerged, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 302,
|
||||
data, err := forgemsg.EncodeForge(forgemsg.Forge{Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-catalog", Branch: "feat/x", Number: 302,
|
||||
Head: "0123456789abcdef0123456789abcdef01234567", Merge: "89abcdef0123456789abcdef0123456789abcdef", Root: "r0",
|
||||
Trees: map[string]string{"modules/avahi": "newtree"},
|
||||
Manifests: map[string]string{"modules/avahi": string(manifest)},
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun"
|
||||
"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/delivery"
|
||||
|
||||
@@ -157,14 +158,14 @@ type delivered interface {
|
||||
// handleGroupEnd takes a group's end in (index.EndGroup): a body that does not read is terminated and said; an end
|
||||
// whose members are not all held yet, or while the build this program runs is not known, is left for redelivery.
|
||||
func handleGroupEnd(ctx context.Context, m delivered, ix *index.Indexer, h *index.Heard, logger *log.Logger) {
|
||||
end, err := delivery.DecodeForgeGroupEnd(m.Data())
|
||||
end, err := forgemsg.DecodeForgeGroupEnd(m.Data())
|
||||
if err != nil {
|
||||
logger.Printf("%s: not a group's end, dropped: %v", m.Subject(), err)
|
||||
h.Outcome, h.Words = index.HeardDropped, err.Error()
|
||||
_ = m.Term()
|
||||
return
|
||||
}
|
||||
h.Kind, h.Branch = delivery.ForgeGroupEndEvent, end.Group
|
||||
h.Kind, h.Branch = forgemsg.ForgeGroupEndEvent, end.Group
|
||||
applied, words, err := ix.EndGroup(ctx, end)
|
||||
h.Written, h.Late = applied.Written, applied.Late
|
||||
if err != nil {
|
||||
@@ -197,11 +198,11 @@ func handle(ctx context.Context, m delivered, ix *index.Indexer, heard index.Sto
|
||||
}
|
||||
}()
|
||||
// A delivery group's end (novox/hq ADR 0633 §4) has a body of its own: told apart by its name, before the decode.
|
||||
if strings.HasSuffix(m.Subject(), "."+delivery.ForgeGroupEndEvent) {
|
||||
if strings.HasSuffix(m.Subject(), "."+forgemsg.ForgeGroupEndEvent) {
|
||||
handleGroupEnd(ctx, m, ix, &h, logger)
|
||||
return
|
||||
}
|
||||
f, err := delivery.DecodeForge(m.Data())
|
||||
f, err := forgemsg.DecodeForge(m.Data())
|
||||
if err != nil {
|
||||
logger.Printf("%s: not a forge message, dropped: %v", m.Subject(), err)
|
||||
h.Outcome, h.Words = index.HeardDropped, err.Error()
|
||||
@@ -213,7 +214,7 @@ func handle(ctx context.Context, m delivered, ix *index.Indexer, heard index.Sto
|
||||
h.Commit = f.Merge
|
||||
}
|
||||
var applied index.Applied
|
||||
if f.Kind == delivery.ForgeMerged && h.Sequence > 0 {
|
||||
if f.Kind == forgemsg.ForgeMerged && h.Sequence > 0 {
|
||||
// A merge is taken in only by the indexer its revision names (ADR 0633 §1): kept in pending until then.
|
||||
applied, h.Words, err = ix.Arrive(ctx, h.Sequence, f)
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,7 @@ 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-indexer/internal/index"
|
||||
@@ -73,8 +74,8 @@ const head = "0123456789abcdef0123456789abcdef01234567"
|
||||
func TestAMessageIsIndexedAndAcked(t *testing.T) { // 1238/AC2 (unit part)
|
||||
st := &memStore{vals: map[string][]byte{}}
|
||||
ix := &index.Indexer{Store: st, Builds: index.NewBuilds()}
|
||||
data, _ := delivery.EncodeForge(delivery.Forge{Kind: delivery.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head,
|
||||
Action: delivery.PullUpdated, Dirs: []string{"modules/a"}, DirsKnown: true})
|
||||
data, _ := forgemsg.EncodeForge(forgemsg.Forge{Kind: forgemsg.ForgePull, Repository: "novox/mesh-delivery", Branch: "feat/x", Number: 12, Head: head,
|
||||
Action: forgemsg.PullUpdated, Dirs: []string{"modules/a"}, DirsKnown: true})
|
||||
m := &fakeMsg{subject: "mesh.stream.mesh-delivery-forge-bridge.event.forge.novox.mesh-delivery.pull", data: data}
|
||||
heard := &memStore{vals: map[string][]byte{}}
|
||||
handle(context.Background(), m, ix, heard, log.New(&bytes.Buffer{}, "", 0))
|
||||
@@ -84,7 +85,7 @@ func TestAMessageIsIndexedAndAcked(t *testing.T) { // 1238/AC2 (unit part)
|
||||
}
|
||||
// Issue 1126 note 76: what the indexer heard of FORGE is readable (indexer_heard): subject, sequence, deliveries, time,
|
||||
// repository, commit and what it did.
|
||||
if h := heardOf(t, heard); h.Subject != m.subject || h.Sequence != 41 || h.Delivered != 2 || h.Published.Hour() != 13 || h.Kind != delivery.ForgePull ||
|
||||
if h := heardOf(t, heard); h.Subject != m.subject || h.Sequence != 41 || h.Delivered != 2 || h.Published.Hour() != 13 || h.Kind != forgemsg.ForgePull ||
|
||||
h.Repository != "novox/mesh-delivery" || h.Number != 12 || h.Commit != head || h.Outcome != index.HeardApplied || h.Handled.IsZero() {
|
||||
t.Errorf("heard = %+v", h)
|
||||
}
|
||||
@@ -111,12 +112,12 @@ func TestAMessageThatIsNoForgeMessageIsTerminatedAndLogged(t *testing.T) { // 12
|
||||
}{{`{"v":1,"group":"feat/x","members":[{"repository":"novox/mesh-sdk","number":3,"merge":"` + head + `"}]}`, 0, ""},
|
||||
{`{"v":1,"group":"feat/x"}`, 1, "not a group's end"}} {
|
||||
logs.Reset()
|
||||
m := &fakeMsg{subject: "mesh.stream.mesh-delivery-forge-bridge.event." + delivery.ForgeGroupEndEvent, data: []byte(c.data)}
|
||||
m := &fakeMsg{subject: "mesh.stream.mesh-delivery-forge-bridge.event." + forgemsg.ForgeGroupEndEvent, data: []byte(c.data)}
|
||||
handle(context.Background(), m, &index.Indexer{Store: st, Builds: index.NewBuilds()}, heard, log.New(&logs, "", 0))
|
||||
if m.termd != c.termd || m.acked+m.termd != 1 || !strings.Contains(logs.String(), c.logged) {
|
||||
t.Errorf("group end %s: terminated %d acked %d log %q", c.data, m.termd, m.acked, logs.String())
|
||||
}
|
||||
if h := heardOf(t, heard); h.Kind != delivery.ForgeGroupEndEvent && c.termd == 0 {
|
||||
if h := heardOf(t, heard); h.Kind != forgemsg.ForgeGroupEndEvent && c.termd == 0 {
|
||||
t.Errorf("heard of a group's end = %+v", h)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"mesh-delivery-indexer/internal/fingerprint"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
// commitTreesTool is the forge's tool for a commit's trees (mesh-catalog modules/gitea): with `paths`, by module directory
|
||||
// (the root spelled ""), the paths relative to it, it answers {root, trees, paths} with paths by directory then path, each
|
||||
// path's git object id; a path not at the commit fails the call naming it (the interface agreed in R4e; mesh-sdk
|
||||
// delivery.Forge.Paths; novox/hq ADR 0499 clause 3).
|
||||
// forgemsg.Forge.Paths; novox/hq ADR 0499 clause 3).
|
||||
const commitTreesTool = "gitea.gitea_commit_trees"
|
||||
|
||||
// toolCaller calls a module's tool, `<module>.<tool>`, as the indexer's own account (mesh-sdk process.Conn.Call).
|
||||
@@ -258,8 +259,8 @@ const (
|
||||
)
|
||||
|
||||
// openVia asks the forge which pull requests into the trunk are open now (gitea_open_pulls), every repository's.
|
||||
func openVia(call toolCaller) func(ctx context.Context) ([]delivery.OpenPull, error) {
|
||||
return func(context.Context) ([]delivery.OpenPull, error) {
|
||||
func openVia(call toolCaller) func(ctx context.Context) ([]forgemsg.OpenPull, error) {
|
||||
return func(context.Context) ([]forgemsg.OpenPull, error) {
|
||||
raw, err := call(openPullsTool, map[string]any{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -275,10 +276,10 @@ func openVia(call toolCaller) func(ctx context.Context) ([]delivery.OpenPull, er
|
||||
if err := json.Unmarshal(raw, &o); err != nil || o.Pulls == nil {
|
||||
return nil, fmt.Errorf("the answer of %s is not {pulls}: %v", openPullsTool, err)
|
||||
}
|
||||
out := []delivery.OpenPull{}
|
||||
out := []forgemsg.OpenPull{}
|
||||
for _, p := range *o.Pulls {
|
||||
if p.Base == trunkBranch && p.Number > 0 {
|
||||
out = append(out, delivery.OpenPull{Repository: p.Owner + "/" + p.Repo, Number: p.Number})
|
||||
out = append(out, forgemsg.OpenPull{Repository: p.Owner + "/" + p.Repo, Number: p.Number})
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
|
||||
+8
-7
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"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/buscompose"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
@@ -71,10 +72,10 @@ func TestAMeshSDKMergeMovesOnlyTheModulesImportingAChangedPackage(t *testing.T)
|
||||
}
|
||||
serveForge(t, js.Conn(), listing, goSumContent)
|
||||
})
|
||||
merged := func(repo string, number int, merge string, f delivery.Forge) {
|
||||
merged := func(repo string, number int, merge string, f forgemsg.Forge) {
|
||||
t.Helper()
|
||||
f.Kind, f.Repository, f.Branch, f.Number, f.Head, f.Merge = delivery.ForgeMerged, repo, "feat/x", number, "0123456789abcdef0123456789abcdef01234567", merge
|
||||
data, err := delivery.EncodeForge(f)
|
||||
f.Kind, f.Repository, f.Branch, f.Number, f.Head, f.Merge = forgemsg.ForgeMerged, repo, "feat/x", number, "0123456789abcdef0123456789abcdef01234567", merge
|
||||
data, err := forgemsg.EncodeForge(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -101,10 +102,10 @@ func TestAMeshSDKMergeMovesOnlyTheModulesImportingAChangedPackage(t *testing.T)
|
||||
"tsconfig.json": "5555555555555555555555555555555555555555"}}
|
||||
}
|
||||
// mesh-sdk at m1, then the catalogue's two modules: their go.sum names mesh-sdk, read by the indexer itself.
|
||||
merged("novox/mesh-sdk", 70, m1, delivery.Forge{Root: sdkRoot1, Trees: map[string]string{"": sdkRoot1},
|
||||
merged("novox/mesh-sdk", 70, m1, forgemsg.Forge{Root: sdkRoot1, Trees: map[string]string{"": sdkRoot1},
|
||||
Manifests: map[string]string{"": string(sdkManifest)}, Paths: sdkObjects("6666666666666666666666666666666666666666")})
|
||||
module("mesh-sdk", func(m delivery.Module) bool { return m.Commit == m1 })
|
||||
merged("novox/mesh-catalog", 301, catalogMerge, delivery.Forge{Root: "r0",
|
||||
merged("novox/mesh-catalog", 301, catalogMerge, forgemsg.Forge{Root: "r0",
|
||||
Trees: map[string]string{"modules/importer": "t1", "modules/plain": "t2"},
|
||||
Manifests: map[string]string{"modules/importer": named("importer"), "modules/plain": named("plain")},
|
||||
Locks: map[string]map[string]string{"modules/importer": {"go.sum": goSumHash}, "modules/plain": {"go.sum": goSumHash}}})
|
||||
@@ -140,11 +141,11 @@ func TestAMeshSDKMergeMovesOnlyTheModulesImportingAChangedPackage(t *testing.T)
|
||||
// over its named tree, is not written (issue 1758, ADR 0499 clause 6; the operator, 2026-10-25: a module's value moves
|
||||
// only with the module).
|
||||
const goOnly = "4444444444444444444444444444444444444444"
|
||||
merged("novox/mesh-sdk", 72, goOnly, delivery.Forge{Root: "cccccccccccccccccccccccccccccccccccccccc",
|
||||
merged("novox/mesh-sdk", 72, goOnly, forgemsg.Forge{Root: "cccccccccccccccccccccccccccccccccccccccc",
|
||||
Trees: map[string]string{"": "cccccccccccccccccccccccccccccccccccccccc"}, Manifests: map[string]string{"": string(sdkManifest)},
|
||||
Paths: sdkObjects("6666666666666666666666666666666666666666")})
|
||||
// mesh-sdk merges a change of buildstep alone (its version moves: src changed too).
|
||||
merged("novox/mesh-sdk", 71, m2, delivery.Forge{Root: sdk2, Trees: map[string]string{"": sdk2},
|
||||
merged("novox/mesh-sdk", 71, m2, forgemsg.Forge{Root: sdk2, Trees: map[string]string{"": sdk2},
|
||||
Manifests: map[string]string{"": string(sdkManifest)}, Paths: sdkObjects("7777777777777777777777777777777777777777")})
|
||||
sdkAt2 := module("mesh-sdk", func(m delivery.Module) bool { return m.Commit == m2 })
|
||||
if sdkAt2.Inputs2 == nil || !strings.HasPrefix(sdkAt2.Inputs2.Tree, delivery.NamedTreePrefix) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
"github.com/nats-io/nats.go"
|
||||
@@ -65,13 +66,13 @@ func sayAccepted(js jetstream.JetStream, cred process.Account, logf func(string,
|
||||
|
||||
// sayTakeIn publishes a take-in event on the indexer's own stream take-in (novox/hq ADR 0633 §5):
|
||||
// `mesh.stream.<module>.event.take-in.<kind>.<key>`. A publish that fails is the caller's to say.
|
||||
func sayTakeIn(js jetstream.JetStream, cred process.Account) func(context.Context, delivery.TakeIn) error {
|
||||
return func(ctx context.Context, t delivery.TakeIn) error {
|
||||
event, err := delivery.TakeInEvent(t.Kind, t.Key)
|
||||
func sayTakeIn(js jetstream.JetStream, cred process.Account) func(context.Context, takein.TakeIn) error {
|
||||
return func(ctx context.Context, t takein.TakeIn) error {
|
||||
event, err := takein.TakeInEvent(t.Kind, t.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := delivery.EncodeTakeIn(t)
|
||||
data, err := takein.EncodeTakeIn(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ module mesh-delivery-indexer
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8
|
||||
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
|
||||
)
|
||||
@@ -24,6 +24,14 @@ require (
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun => ../mesh-delivery-builder/buildrun
|
||||
|
||||
require git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/sdk => ../../sdk
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein => ./takein
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg => ../mesh-delivery-forge-bridge/forgemsg
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 h1:XEbIVImp6yGjA91rp0iZBoDdL5KfamjOuDLwWvqh0hc=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
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=
|
||||
|
||||
@@ -7,12 +7,13 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
func manifestPull(t *testing.T, number int, head, manifest string) delivery.Forge {
|
||||
f := delivery.Forge{V: delivery.Version, Kind: delivery.ForgePull, Repository: catalogueRepo, Branch: "feat/x", Number: number, Head: head,
|
||||
Action: delivery.PullUpdated, Dirs: []string{"modules/avahi"}, DirsKnown: true}
|
||||
func manifestPull(t *testing.T, number int, head, manifest string) forgemsg.Forge {
|
||||
f := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgePull, Repository: catalogueRepo, Branch: "feat/x", Number: number, Head: head,
|
||||
Action: forgemsg.PullUpdated, Dirs: []string{"modules/avahi"}, DirsKnown: true}
|
||||
if manifest != "" {
|
||||
f.Manifests = map[string]string{"modules/avahi": manifest}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -98,7 +100,7 @@ type Indexer struct {
|
||||
// ReadOpen asks the forge which pull requests into the trunk are open now (gitea_open_pulls). A catalogue pull message
|
||||
// that finds the candidates of another pull request asks it and deletes those of a pull request no longer open, so a
|
||||
// closed message the indexer missed heals at the next one (issue 1126); nil asks none.
|
||||
ReadOpen func(ctx context.Context) ([]delivery.OpenPull, error)
|
||||
ReadOpen func(ctx context.Context) ([]forgemsg.OpenPull, error)
|
||||
|
||||
mu sync.Mutex // guards source, toolchains and previews, which the watches and the forge messages all reach
|
||||
source sourceNow
|
||||
@@ -106,7 +108,7 @@ type Indexer struct {
|
||||
toolchains map[string]map[string]delivery.Toolchain // language, then node (toolchains.go)
|
||||
tried map[string]bool // repository, commit and directory a registered module's manifest was tried at (registered.go)
|
||||
unread map[string]bool // registered modules whose last read the forge could not answer, read again by RetryUnread
|
||||
moving map[string]delivery.Forge // by modules key: a merge naming a module another repository holds, waiting for that trunk to drop it (moves.go)
|
||||
moving map[string]forgemsg.Forge // by modules key: a merge naming a module another repository holds, waiting for that trunk to drop it (moves.go)
|
||||
retrying atomic.Bool // a RetryUnread runs in the background (kickRetry)
|
||||
retryAgain atomic.Bool // an event asked for one more RetryUnread after the running one
|
||||
retryWG sync.WaitGroup // the background RetryUnread, for tests to wait on
|
||||
@@ -125,7 +127,7 @@ type Indexer struct {
|
||||
// takes every merge in at once, as before the take-in step.
|
||||
Pending Store
|
||||
// SayTakeIn publishes a take-in event on the indexer's own stream take-in (ADR 0633 §5); nil says none.
|
||||
SayTakeIn func(ctx context.Context, t delivery.TakeIn) error
|
||||
SayTakeIn func(ctx context.Context, t takein.TakeIn) error
|
||||
pendingMu sync.Mutex // one take-in step at a time: a merge, a group's end, the start and a hand act (pending.go)
|
||||
}
|
||||
|
||||
@@ -139,23 +141,23 @@ func (ix *Indexer) logf(format string, args ...any) {
|
||||
// pull request, a closed one or a merged message takes it out; a group left with none is deleted. A change is written
|
||||
// by compare-and-set on the revision read, and read again on a conflict. A lone pull request is a group of one. A
|
||||
// merged message also writes the fingerprint of each module of its repository (modules.go).
|
||||
func (ix *Indexer) Apply(ctx context.Context, f delivery.Forge) error {
|
||||
func (ix *Indexer) Apply(ctx context.Context, f forgemsg.Forge) error {
|
||||
_, err := ix.ApplyMessage(ctx, f)
|
||||
return err
|
||||
}
|
||||
|
||||
// ApplyMessage is Apply, and answers which modules a merged message wrote and kept (for the indexer's heard state). Each
|
||||
// applied merged message is said in one line: its repository, commit and pull request, and the modules written.
|
||||
func (ix *Indexer) ApplyMessage(ctx context.Context, f delivery.Forge) (Applied, error) {
|
||||
func (ix *Indexer) ApplyMessage(ctx context.Context, f forgemsg.Forge) (Applied, error) {
|
||||
ix.kickJudge(ctx)
|
||||
var applied Applied
|
||||
if f.Kind == delivery.ForgeOpen {
|
||||
if f.Kind == forgemsg.ForgeOpen {
|
||||
return applied, ix.applyOpen(ctx, f)
|
||||
}
|
||||
if f.Kind != delivery.ForgePull && f.Kind != delivery.ForgeMerged {
|
||||
if f.Kind != forgemsg.ForgePull && f.Kind != forgemsg.ForgeMerged {
|
||||
return applied, nil
|
||||
}
|
||||
if f.Kind == delivery.ForgeMerged && ix.Modules != nil {
|
||||
if f.Kind == forgemsg.ForgeMerged && ix.Modules != nil {
|
||||
w, err := ix.applyMerged(ctx, f)
|
||||
applied = Applied{Written: w.written, Late: w.late}
|
||||
if err != nil {
|
||||
@@ -184,7 +186,7 @@ func (ix *Indexer) ApplyMessage(ctx context.Context, f delivery.Forge) (Applied,
|
||||
}
|
||||
var digests map[string]string
|
||||
var held map[string]delivery.Module
|
||||
if f.Kind == delivery.ForgePull {
|
||||
if f.Kind == forgemsg.ForgePull {
|
||||
var err error
|
||||
if digests, err = ix.keepManifests(ctx, f); err != nil {
|
||||
return applied, err
|
||||
@@ -251,8 +253,8 @@ func (ix *Indexer) ApplyMessage(ctx context.Context, f delivery.Forge) (Applied,
|
||||
// applyOpen takes an open message into the candidates (novox/hq ADR 0443 §5): every pull request it does not name is
|
||||
// taken out of its group, and a group left with none is deleted. What the message names is left as it is. A pull request
|
||||
// opened after the bridge read the forge is heard after this message and added again by its own.
|
||||
func (ix *Indexer) applyOpen(ctx context.Context, f delivery.Forge) error {
|
||||
named := make(map[delivery.OpenPull]bool, len(f.Open))
|
||||
func (ix *Indexer) applyOpen(ctx context.Context, f forgemsg.Forge) error {
|
||||
named := make(map[forgemsg.OpenPull]bool, len(f.Open))
|
||||
for _, p := range f.Open {
|
||||
named[p] = true
|
||||
}
|
||||
@@ -270,7 +272,7 @@ func (ix *Indexer) applyOpen(ctx context.Context, f delivery.Forge) error {
|
||||
return nil, false, fmt.Errorf("candidates.%s holds what is not a candidate: %w", key, err)
|
||||
}
|
||||
kept := slices.DeleteFunc(slices.Clone(c.Pulls), func(p delivery.CandidatePull) bool {
|
||||
return !named[delivery.OpenPull{Repository: p.Repository, Number: p.Number}]
|
||||
return !named[forgemsg.OpenPull{Repository: p.Repository, Number: p.Number}]
|
||||
})
|
||||
switch {
|
||||
case len(kept) == len(c.Pulls):
|
||||
@@ -337,14 +339,14 @@ func write(ctx context.Context, st Store, key string, compute func(value []byte,
|
||||
// its manifest and why it has no fingerprint of the new form (NoneNoPreview), and the candidate its FPSet (issue 1451); a
|
||||
// pull request whose head is held again without its directories keeps the Fps it had. digests are the manifests' digests by
|
||||
// directory; held are the modules the modules bucket holds, which name the module of a changed directory (moduleOfDir).
|
||||
func (ix *Indexer) next(cur *delivery.Candidate, f delivery.Forge, digests map[string]string, held map[string]delivery.Module) *delivery.Candidate {
|
||||
func (ix *Indexer) next(cur *delivery.Candidate, f forgemsg.Forge, digests map[string]string, held map[string]delivery.Module) *delivery.Candidate {
|
||||
var pulls []delivery.CandidatePull
|
||||
var base uint64
|
||||
if cur != nil {
|
||||
pulls, base = slices.Clone(cur.Pulls), cur.Base
|
||||
}
|
||||
at := slices.IndexFunc(pulls, func(p delivery.CandidatePull) bool { return p.Repository == f.Repository && p.Number == f.Number })
|
||||
if f.Kind == delivery.ForgeMerged || f.Action == delivery.PullClosed {
|
||||
if f.Kind == forgemsg.ForgeMerged || f.Action == forgemsg.PullClosed {
|
||||
if at < 0 {
|
||||
return cur
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/process"
|
||||
)
|
||||
@@ -27,7 +28,7 @@ var (
|
||||
|
||||
// writePredefined keeps the predefined list a merged message carries, unless the key names a later merge: a merge older
|
||||
// than the one it names never replaces it (ADR 0348 §7). The same list at the same merge writes nothing (ADR 0289 §1).
|
||||
func (ix *Indexer) writePredefined(ctx context.Context, f delivery.Forge) error {
|
||||
func (ix *Indexer) writePredefined(ctx context.Context, f forgemsg.Forge) error {
|
||||
if ix.Predefined == nil || len(f.Predefined) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -203,8 +204,8 @@ func (ix *Indexer) dropConfigCandidate(ctx context.Context, repository string, n
|
||||
|
||||
// configCandidateOfPull is a pull message's part in config-candidates: a head whose manifests the message carries is written,
|
||||
// a closed one deleted. A head held again without its directories leaves the key as it is.
|
||||
func (ix *Indexer) configCandidateOfPull(ctx context.Context, f delivery.Forge, held map[string]delivery.Module) error {
|
||||
if f.Action == delivery.PullClosed {
|
||||
func (ix *Indexer) configCandidateOfPull(ctx context.Context, f forgemsg.Forge, held map[string]delivery.Module) error {
|
||||
if f.Action == forgemsg.PullClosed {
|
||||
return ix.dropConfigCandidate(ctx, f.Repository, f.Number)
|
||||
}
|
||||
if !f.DirsKnown {
|
||||
@@ -220,7 +221,7 @@ func (ix *Indexer) configCandidateOfPull(ctx context.Context, f delivery.Forge,
|
||||
// configCandidatesAfterMerge writes again every open pull request's key that names a module the merge changed, since what
|
||||
// it adds is judged against the trunk (ADR 0565 §2), from the head's manifests its candidate keeps; and deletes the merged
|
||||
// pull request's.
|
||||
func (ix *Indexer) configCandidatesAfterMerge(ctx context.Context, f delivery.Forge, changed []string) error {
|
||||
func (ix *Indexer) configCandidatesAfterMerge(ctx context.Context, f forgemsg.Forge, changed []string) error {
|
||||
if ix.ConfigCandidates == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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/process"
|
||||
)
|
||||
@@ -77,7 +78,7 @@ func TestTheControlRecords(t *testing.T) {
|
||||
t.Errorf("a head declaring nothing new = %+v, want an empty set", c)
|
||||
}
|
||||
closed := pull(catalogueRepo, 7, "feat/y", strings.Repeat("d", 40))
|
||||
closed.Action = delivery.PullClosed
|
||||
closed.Action = forgemsg.PullClosed
|
||||
if err := ix.Apply(ctx, closed); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -91,9 +92,9 @@ func buildRecord(t *testing.T, repo, module, path, at string) []byte {
|
||||
return logged(t, "build-"+module, module, repo, path, "", "", at)
|
||||
}
|
||||
|
||||
func pull(repo string, number int, branch, head string, dirs ...string) delivery.Forge {
|
||||
return delivery.Forge{V: delivery.Version, Kind: delivery.ForgePull, Repository: repo, Branch: branch, Number: number, Head: head,
|
||||
Action: delivery.PullUpdated, Dirs: dirs, DirsKnown: dirs != nil}
|
||||
func pull(repo string, number int, branch, head string, dirs ...string) forgemsg.Forge {
|
||||
return forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgePull, Repository: repo, Branch: branch, Number: number, Head: head,
|
||||
Action: forgemsg.PullUpdated, Dirs: dirs, DirsKnown: dirs != nil}
|
||||
}
|
||||
|
||||
func newIndexer(t *testing.T) (*Indexer, *memStore, *strings.Builder) {
|
||||
@@ -212,13 +213,13 @@ func TestClosedAndMergedTakeAPullOutAndTheLastDeletesTheKey(t *testing.T) { // 1
|
||||
ctx := context.Background()
|
||||
_ = ix.Apply(ctx, pull("novox/mesh-delivery", 12, "feat/x", h1, "modules/a"))
|
||||
_ = ix.Apply(ctx, pull("novox/mesh-catalog", 300, "feat/x", h2, "modules/c"))
|
||||
gone := func(repo string, n int, head string) delivery.Forge {
|
||||
gone := func(repo string, n int, head string) forgemsg.Forge {
|
||||
if how == "closed" {
|
||||
f := pull(repo, n, "feat/x", head)
|
||||
f.Action = delivery.PullClosed
|
||||
f.Action = forgemsg.PullClosed
|
||||
return f
|
||||
}
|
||||
return delivery.Forge{V: delivery.Version, Kind: delivery.ForgeMerged, Repository: repo, Branch: "feat/x", Number: n, Head: head, Merge: h3, Root: "r", Trees: map[string]string{"a": "t"}}
|
||||
return forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgeMerged, Repository: repo, Branch: "feat/x", Number: n, Head: head, Merge: h3, Root: "r", Trees: map[string]string{"a": "t"}}
|
||||
}
|
||||
if err := ix.Apply(ctx, gone("novox/mesh-delivery", 12, h1)); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -330,8 +331,8 @@ func TestAnOddBranchNameKeysEscaped(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func open(pulls ...delivery.OpenPull) delivery.Forge {
|
||||
return delivery.Forge{V: delivery.Version, Kind: delivery.ForgeOpen, Open: pulls}
|
||||
func open(pulls ...forgemsg.OpenPull) forgemsg.Forge {
|
||||
return forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgeOpen, Open: pulls}
|
||||
}
|
||||
|
||||
// 1279/AC7 (ADR 0443 §5): on an open message, candidates lose every pull request it does not name, a group left empty is
|
||||
@@ -339,7 +340,7 @@ func open(pulls ...delivery.OpenPull) delivery.Forge {
|
||||
func TestAnOpenMessageDropsEveryCandidateItDoesNotName(t *testing.T) { // 1279/AC7
|
||||
ix, st, _ := newIndexer(t)
|
||||
ctx := context.Background()
|
||||
for _, f := range []delivery.Forge{
|
||||
for _, f := range []forgemsg.Forge{
|
||||
pull("novox/mesh-delivery", 12, "feat/x", h1, "modules/a"), pull("novox/mesh-catalog", 300, "feat/x", h2, "modules/c"),
|
||||
pull("novox/mesh-delivery", 13, "feat/y", h2, "modules/a"),
|
||||
pull("novox/mesh-catalog", 280, "feat/old", h3, "modules/c"),
|
||||
@@ -351,7 +352,7 @@ func TestAnOpenMessageDropsEveryCandidateItDoesNotName(t *testing.T) { // 1279/A
|
||||
before, _ := read(t, st, "feat/x")
|
||||
|
||||
// Open: mesh-delivery#12 only (and a pull request no group holds, which changes nothing).
|
||||
if err := ix.Apply(ctx, open(delivery.OpenPull{Repository: "novox/mesh-delivery", Number: 12}, delivery.OpenPull{Repository: "novox/mesh-catalog", Number: 999})); err != nil {
|
||||
if err := ix.Apply(ctx, open(forgemsg.OpenPull{Repository: "novox/mesh-delivery", Number: 12}, forgemsg.OpenPull{Repository: "novox/mesh-catalog", Number: 999})); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c, ok := read(t, st, "feat/x")
|
||||
@@ -374,7 +375,7 @@ func TestAnOpenMessageDropsEveryCandidateItDoesNotName(t *testing.T) { // 1279/A
|
||||
}
|
||||
// The same open message again changes nothing (no write), and an empty one drops every group.
|
||||
writes := st.writes
|
||||
if err := ix.Apply(ctx, open(delivery.OpenPull{Repository: "novox/mesh-delivery", Number: 12}, delivery.OpenPull{Repository: "novox/mesh-catalog", Number: 300})); err != nil {
|
||||
if err := ix.Apply(ctx, open(forgemsg.OpenPull{Repository: "novox/mesh-delivery", Number: 12}, forgemsg.OpenPull{Repository: "novox/mesh-catalog", Number: 300})); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st.writes != writes {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -48,7 +49,7 @@ func (ix *Indexer) keepManifest(ctx context.Context, module, raw string) (string
|
||||
|
||||
// keepManifests writes the manifest of each directory a message carries, and answers the digest of each by directory.
|
||||
// A manifest that cannot be kept is left out and said in the log, so the module reads as one with no manifest.
|
||||
func (ix *Indexer) keepManifests(ctx context.Context, f delivery.Forge) (map[string]string, error) {
|
||||
func (ix *Indexer) keepManifests(ctx context.Context, f forgemsg.Forge) (map[string]string, error) {
|
||||
digests := make(map[string]string, len(f.Manifests))
|
||||
for dir, raw := range f.Manifests {
|
||||
module := manifestModule(raw) // the name the manifest gives itself: one digest always has one module
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -37,7 +38,7 @@ func manifestsIndexer(t *testing.T, recs ...[2]any) (*Indexer, *memStore, *memSt
|
||||
return ix, mods, manifests
|
||||
}
|
||||
|
||||
func withManifests(f delivery.Forge, manifests map[string]string, locks map[string]map[string]string) delivery.Forge {
|
||||
func withManifests(f forgemsg.Forge, manifests map[string]string, locks map[string]map[string]string) forgemsg.Forge {
|
||||
f.Manifests, f.Locks = manifests, locks
|
||||
return f
|
||||
}
|
||||
@@ -81,8 +82,8 @@ func sameManifestAppliedAgainIsWrittenOnce(t *testing.T) { // 1449/AC2
|
||||
if err := ix.Apply(context.Background(), f); err != nil {
|
||||
t.Errorf("a second merge: %v", err)
|
||||
}
|
||||
pull := delivery.Forge{V: delivery.Version, Kind: delivery.ForgePull, Repository: catalogueRepo, Branch: "feat/x", Number: 301, Head: h1,
|
||||
Action: delivery.PullUpdated, Dirs: []string{"modules/avahi"}, DirsKnown: true, Manifests: map[string]string{"modules/avahi": avahi}}
|
||||
pull := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgePull, Repository: catalogueRepo, Branch: "feat/x", Number: 301, Head: h1,
|
||||
Action: forgemsg.PullUpdated, Dirs: []string{"modules/avahi"}, DirsKnown: true, Manifests: map[string]string{"modules/avahi": avahi}}
|
||||
if err := ix.Apply(context.Background(), pull); err != nil {
|
||||
t.Errorf("a pull message: %v", err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
|
||||
@@ -31,20 +32,20 @@ import (
|
||||
//
|
||||
// A merge never moves a module backwards (order.go): a merged message older than the repository's taken merge writes and
|
||||
// deletes nothing, and is said; it is asked once, before anything is written.
|
||||
func (ix *Indexer) applyMerged(ctx context.Context, f delivery.Forge) (*mergeWrite, error) {
|
||||
func (ix *Indexer) applyMerged(ctx context.Context, f forgemsg.Forge) (*mergeWrite, error) {
|
||||
return ix.applyManifests(ctx, f, true)
|
||||
}
|
||||
|
||||
// applyManifests is applyMerged; drop says whether the modules of the repository the message holds no manifest for are
|
||||
// deleted. A message that names only some of a repository's modules (a registered module read on its own, registered.go)
|
||||
// drops none.
|
||||
func (ix *Indexer) applyManifests(ctx context.Context, f delivery.Forge, drop bool) (*mergeWrite, error) {
|
||||
func (ix *Indexer) applyManifests(ctx context.Context, f forgemsg.Forge, drop bool) (*mergeWrite, error) {
|
||||
return ix.applyMoving(ctx, f, drop, nil)
|
||||
}
|
||||
|
||||
// applyMoving is applyManifests; moves names the modules keys the message may take from the repository that holds them
|
||||
// (a move handed over, moves.go). Every other module held of another repository is left, said and kept waiting.
|
||||
func (ix *Indexer) applyMoving(ctx context.Context, f delivery.Forge, drop bool, moves map[string]bool) (*mergeWrite, error) {
|
||||
func (ix *Indexer) applyMoving(ctx context.Context, f forgemsg.Forge, drop bool, moves map[string]bool) (*mergeWrite, error) {
|
||||
w := newMergeWrite(f)
|
||||
if drop {
|
||||
if err := ix.lateMerge(ctx, w); err != nil || w.late != "" {
|
||||
@@ -180,7 +181,7 @@ func withV(m delivery.Module) delivery.Module { m.V = delivery.Version; return m
|
||||
// namedObjects are, by module directory, the git objects of the paths each manifest of the merge names for every artifact
|
||||
// (catalogue Manifest.NamedPaths): those the message carries, else asked of the forge at the merge commit in one call
|
||||
// (Named). A forge that cannot say gives, for each directory asked, its words, so the module's reason names them.
|
||||
func (ix *Indexer) namedObjects(ctx context.Context, f delivery.Forge) (map[string]map[string]string, map[string]string) {
|
||||
func (ix *Indexer) namedObjects(ctx context.Context, f forgemsg.Forge) (map[string]map[string]string, map[string]string) {
|
||||
objects := map[string]map[string]string{}
|
||||
need := map[string][]string{}
|
||||
for dir, raw := range f.Manifests {
|
||||
@@ -220,7 +221,7 @@ func (ix *Indexer) namedObjects(ctx context.Context, f delivery.Forge) (map[stri
|
||||
// held(key, module) says whether the trunk holds it. A module of another repository, or of none known, is never touched.
|
||||
func (ix *Indexer) dropRemoved(ctx context.Context, repository string, held map[string]delivery.Module, trunkHolds func(key string, m delivery.Module) bool) error {
|
||||
var handed []string
|
||||
elsewhere := map[string]delivery.Forge{}
|
||||
elsewhere := map[string]forgemsg.Forge{}
|
||||
for _, key := range slices.Sorted(maps.Keys(held)) {
|
||||
cur := held[key]
|
||||
if cur.Repository == "" || !delivery.SameRepository(cur.Repository, repository) || trunkHolds(key, cur) {
|
||||
@@ -354,7 +355,7 @@ func unreadInputs(tree string, locks, paths map[string]string, packages []string
|
||||
// goSumPackages are the version line's packages of the merge's directory dir (ADR 0573 §2, ADR 0593 §2): the mesh Go
|
||||
// modules of other repositories its go.sum names, and those of its own repository its go.mod replaces by a directory
|
||||
// (siblingPackages), which no go.sum line names. A directory with no go.sum is no Go module and has none.
|
||||
func (ix *Indexer) goSumPackages(ctx context.Context, f delivery.Forge, dir string, cur delivery.Module, has bool) []string {
|
||||
func (ix *Indexer) goSumPackages(ctx context.Context, f forgemsg.Forge, dir string, cur delivery.Module, has bool) []string {
|
||||
others := ix.otherPackages(ctx, f, dir, cur, has)
|
||||
if f.Locks[dir]["go.sum"] == "" && len(f.Packages[dir]) == 0 {
|
||||
return others
|
||||
@@ -368,7 +369,7 @@ func (ix *Indexer) goSumPackages(ctx context.Context, f delivery.Forge, dir stri
|
||||
|
||||
// siblingPackages are the mesh Go modules of the merge's own repository the go.mod of dir replaces by a directory, read
|
||||
// from the forge (GoMod); where it cannot be read, the held module's own-repository packages stand, said.
|
||||
func (ix *Indexer) siblingPackages(ctx context.Context, f delivery.Forge, dir string, cur delivery.Module, has bool) []string {
|
||||
func (ix *Indexer) siblingPackages(ctx context.Context, f forgemsg.Forge, dir string, cur delivery.Module, has bool) []string {
|
||||
held := func() []string {
|
||||
if !has || cur.Inputs2 == nil {
|
||||
return nil
|
||||
@@ -395,7 +396,7 @@ func (ix *Indexer) siblingPackages(ctx context.Context, f delivery.Forge, dir st
|
||||
// otherPackages are the mesh Go modules of other repositories the go.sum of the merge's directory dir names: those the
|
||||
// message carries (an older bridge), else the held module's while its go.sum's hash is unchanged, else the go.sum read
|
||||
// from the forge (GoSum).
|
||||
func (ix *Indexer) otherPackages(ctx context.Context, f delivery.Forge, dir string, cur delivery.Module, has bool) []string {
|
||||
func (ix *Indexer) otherPackages(ctx context.Context, f forgemsg.Forge, dir string, cur delivery.Module, has bool) []string {
|
||||
if carried := f.Packages[dir]; len(carried) > 0 {
|
||||
return fingerprint.OtherPackages(carried, f.Repository) // its own repository's come with its tree
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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/process"
|
||||
@@ -71,12 +72,12 @@ func rec(t *testing.T, id, module, repo, path, ref, state, at, inputs string) [2
|
||||
// mergedMsg is a merged message of the repository with these module directories' trees, each with a manifest naming its
|
||||
// module, the directory's last element ("r" for the root), as the forge bridge sends it (one manifest per directory that
|
||||
// holds a module.json).
|
||||
func mergedMsg(repo string, trees map[string]string) delivery.Forge {
|
||||
func mergedMsg(repo string, trees map[string]string) forgemsg.Forge {
|
||||
manifests := map[string]string{}
|
||||
for dir := range trees {
|
||||
manifests[dir] = `{"module":"` + nameOfDir(dir) + `","version":"1"}`
|
||||
}
|
||||
return delivery.Forge{V: delivery.Version, Kind: delivery.ForgeMerged, Repository: repo, Branch: "feat/x", Number: 12, Head: h1, Merge: merge, Root: "r0", Trees: trees, Manifests: manifests}
|
||||
return forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgeMerged, Repository: repo, Branch: "feat/x", Number: 12, Head: h1, Merge: merge, Root: "r0", Trees: trees, Manifests: manifests}
|
||||
}
|
||||
|
||||
func nameOfDir(dir string) string {
|
||||
@@ -459,7 +460,7 @@ func TestAModuleMovesBetweenRepositoriesOnlyWhenItsTrunkDropsIt(t *testing.T) {
|
||||
gone.Trees, gone.Manifests = nil, nil
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
merges []delivery.Forge
|
||||
merges []forgemsg.Forge
|
||||
repository string // modules.a's repository at the end, "" for none
|
||||
deletedKeyA bool
|
||||
// Issue 1367: a fresh indexer, no wait in memory, the source naming heads; trunk is the module directories the forge
|
||||
@@ -472,20 +473,20 @@ func TestAModuleMovesBetweenRepositoriesOnlyWhenItsTrunkDropsIt(t *testing.T) {
|
||||
stored bool
|
||||
bGoneAtEnd bool
|
||||
}{
|
||||
{name: "named while its trunk holds it: left", merges: []delivery.Forge{held, named}, repository: from},
|
||||
{name: "named, then dropped by its trunk: handed over in place", merges: []delivery.Forge{held, named, dropped}, repository: to},
|
||||
{name: "dropped by its trunk, then named: deleted, then written new", merges: []delivery.Forge{held, dropped, named}, repository: to, deletedKeyA: true},
|
||||
{name: "dropped by its trunk with no merge naming it: deleted", merges: []delivery.Forge{held, dropped}, deletedKeyA: true},
|
||||
{name: "named while its trunk holds it: left", merges: []forgemsg.Forge{held, named}, repository: from},
|
||||
{name: "named, then dropped by its trunk: handed over in place", merges: []forgemsg.Forge{held, named, dropped}, repository: to},
|
||||
{name: "dropped by its trunk, then named: deleted, then written new", merges: []forgemsg.Forge{held, dropped, named}, repository: to, deletedKeyA: true},
|
||||
{name: "dropped by its trunk with no merge naming it: deleted", merges: []forgemsg.Forge{held, dropped}, deletedKeyA: true},
|
||||
{name: "no module.json left, another trunk holds it, nothing waits in memory: written from that trunk, never deleted",
|
||||
merges: []delivery.Forge{held, gone}, heads: true, trunk: []string{"modules/a", "modules/other"}, repository: to, bGoneAtEnd: true},
|
||||
merges: []forgemsg.Forge{held, gone}, heads: true, trunk: []string{"modules/a", "modules/other"}, repository: to, bGoneAtEnd: true},
|
||||
{name: "no module.json left, no other trunk holds it: deleted",
|
||||
merges: []delivery.Forge{held, gone}, heads: true, deletedKeyA: true, bGoneAtEnd: true},
|
||||
merges: []forgemsg.Forge{held, gone}, heads: true, deletedKeyA: true, bGoneAtEnd: true},
|
||||
{name: "taken in by repository, a head with no module.json: handed over from the other trunk",
|
||||
merges: []delivery.Forge{held}, heads: true, trunk: []string{"modules/a"}, takeIn: from, answer: "handed over", stored: true, repository: to, bGoneAtEnd: true},
|
||||
merges: []forgemsg.Forge{held}, heads: true, trunk: []string{"modules/a"}, takeIn: from, answer: "handed over", stored: true, repository: to, bGoneAtEnd: true},
|
||||
{name: "taken in by repository, a head with modules: answered by name",
|
||||
merges: []delivery.Forge{held}, heads: true, trunk: []string{"modules/a"}, takeIn: to, answer: "by name", repository: from},
|
||||
merges: []forgemsg.Forge{held}, heads: true, trunk: []string{"modules/a"}, takeIn: to, answer: "by name", repository: from},
|
||||
{name: "taken in by repository, no head named: answered so",
|
||||
merges: []delivery.Forge{held}, heads: true, takeIn: "novox/nobody", answer: "names no head", repository: from},
|
||||
merges: []forgemsg.Forge{held}, heads: true, takeIn: "novox/nobody", answer: "names no head", repository: from},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mods := &deletesSeen{memStore: newMem()}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -14,9 +15,9 @@ import (
|
||||
// holds it at the head the latest source names: the module directories there are asked of the forge (Trunk) and the manifest of
|
||||
// each directory named like the module is read (Read). It answers the merge to hand the module over with, or none: a forge
|
||||
// that cannot say leaves the module to be dropped as it was, said (issue 1367).
|
||||
func (ix *Indexer) heldByAnotherTrunk(ctx context.Context, module, repository string) (delivery.Forge, bool) {
|
||||
func (ix *Indexer) heldByAnotherTrunk(ctx context.Context, module, repository string) (forgemsg.Forge, bool) {
|
||||
if ix.Trunk == nil || ix.Read == nil {
|
||||
return delivery.Forge{}, false
|
||||
return forgemsg.Forge{}, false
|
||||
}
|
||||
ix.mu.Lock()
|
||||
heads := maps.Clone(ix.source.heads)
|
||||
@@ -45,7 +46,7 @@ func (ix *Indexer) heldByAnotherTrunk(ctx context.Context, module, repository st
|
||||
if read.Trees[dir] == "" || manifestModule(raw) != module {
|
||||
continue
|
||||
}
|
||||
f := delivery.Forge{V: delivery.Version, Kind: delivery.ForgeMerged, Repository: other, Merge: head, Root: read.Root,
|
||||
f := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgeMerged, Repository: other, Merge: head, Root: read.Root,
|
||||
Trees: map[string]string{dir: read.Trees[dir]}, Manifests: map[string]string{dir: raw}}
|
||||
if l := read.Locks[dir]; len(l) > 0 {
|
||||
f.Locks = map[string]map[string]string{dir: l}
|
||||
@@ -57,7 +58,7 @@ func (ix *Indexer) heldByAnotherTrunk(ctx context.Context, module, repository st
|
||||
return f, true
|
||||
}
|
||||
}
|
||||
return delivery.Forge{}, false
|
||||
return forgemsg.Forge{}, false
|
||||
}
|
||||
|
||||
// nameOfDirectory is the last element of a module directory.
|
||||
@@ -76,8 +77,8 @@ func nameOfDirectory(dir string) string { return dir[strings.LastIndex(dir, "/")
|
||||
|
||||
// waitToMove keeps, by modules key, the part of a merged message naming one module directory the indexer holds of another
|
||||
// repository. A later merge of the same repository replaces it.
|
||||
func (ix *Indexer) waitToMove(key string, f delivery.Forge, dir string) {
|
||||
one := delivery.Forge{V: f.V, Kind: f.Kind, Repository: f.Repository, Branch: f.Branch, Number: f.Number, Merge: f.Merge, Root: f.Root,
|
||||
func (ix *Indexer) waitToMove(key string, f forgemsg.Forge, dir string) {
|
||||
one := forgemsg.Forge{V: f.V, Kind: f.Kind, Repository: f.Repository, Branch: f.Branch, Number: f.Number, Merge: f.Merge, Root: f.Root,
|
||||
Trees: map[string]string{dir: f.Trees[dir]}, Manifests: map[string]string{dir: f.Manifests[dir]}}
|
||||
if l := f.Locks[dir]; len(l) > 0 {
|
||||
one.Locks = map[string]map[string]string{dir: l}
|
||||
@@ -91,7 +92,7 @@ func (ix *Indexer) waitToMove(key string, f delivery.Forge, dir string) {
|
||||
ix.mu.Lock()
|
||||
defer ix.mu.Unlock()
|
||||
if ix.moving == nil {
|
||||
ix.moving = map[string]delivery.Forge{}
|
||||
ix.moving = map[string]forgemsg.Forge{}
|
||||
}
|
||||
ix.moving[key] = one
|
||||
}
|
||||
@@ -117,7 +118,7 @@ func (ix *Indexer) handOver(ctx context.Context, key string) error {
|
||||
}
|
||||
|
||||
// handOverFrom writes the module at key from the merge f, which names it, in place.
|
||||
func (ix *Indexer) handOverFrom(ctx context.Context, key string, f delivery.Forge) error {
|
||||
func (ix *Indexer) handOverFrom(ctx context.Context, key string, f forgemsg.Forge) error {
|
||||
w, err := ix.applyMoving(ctx, f, false, map[string]bool{key: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("modules.%s, moving to %s: %w", key, f.Repository, err)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"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/process"
|
||||
)
|
||||
@@ -46,7 +47,7 @@ func (w *mergeWrite) taken(digest string) {
|
||||
w.fresh[digest] = true
|
||||
}
|
||||
|
||||
func newMergeWrite(f delivery.Forge) *mergeWrite {
|
||||
func newMergeWrite(f forgemsg.Forge) *mergeWrite {
|
||||
return &mergeWrite{repository: f.Repository, merge: f.Merge}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -70,7 +71,7 @@ func moduleAt(held map[string]delivery.Module, repository, dir string) (string,
|
||||
// moduleOfDir is the module of a changed directory of a pull request: the module its manifest in the message names, else
|
||||
// the one the modules bucket holds at that directory of the repository, else the directory's last element (the
|
||||
// catalogue's rule for nearly every manifest).
|
||||
func moduleOfDir(held map[string]delivery.Module, f delivery.Forge, dir string) string {
|
||||
func moduleOfDir(held map[string]delivery.Module, f forgemsg.Forge, dir string) string {
|
||||
if name := manifestModule(f.Manifests[dir]); name != "" {
|
||||
return name
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
|
||||
@@ -21,7 +23,7 @@ import (
|
||||
// build of the indexer writes only what building and moving that indexer needs, and the rest stays pending for it.
|
||||
const (
|
||||
// PendingState is the indexer's state of held merges: the bucket mesh-delivery-indexer_pending, keyed
|
||||
// delivery.PendingKey, so the key order is the merge order. An old `state` entry (the indexer is foundation, ADR
|
||||
// takein.PendingKey, so the key order is the merge order. An old `state` entry (the indexer is foundation, ADR
|
||||
// 0595 §11), history 1, reached by the indexer alone.
|
||||
PendingState = "pending"
|
||||
PendingBucket = delivery.IndexerModule + "_" + PendingState
|
||||
@@ -66,23 +68,23 @@ type PendingEntry struct {
|
||||
Key string
|
||||
Revision uint64
|
||||
Value Pending
|
||||
Forge delivery.Forge
|
||||
Forge forgemsg.Forge
|
||||
}
|
||||
|
||||
// DecodePending reads one pending value and its merged message.
|
||||
func DecodePending(data []byte) (Pending, delivery.Forge, error) {
|
||||
func DecodePending(data []byte) (Pending, forgemsg.Forge, error) {
|
||||
var p Pending
|
||||
if err := json.Unmarshal(data, &p); err != nil {
|
||||
return p, delivery.Forge{}, fmt.Errorf("not a pending value: %w", err)
|
||||
return p, forgemsg.Forge{}, fmt.Errorf("not a pending value: %w", err)
|
||||
}
|
||||
if p.V != 1 {
|
||||
return p, delivery.Forge{}, fmt.Errorf("a pending value of version %d, not 1", p.V)
|
||||
return p, forgemsg.Forge{}, fmt.Errorf("a pending value of version %d, not 1", p.V)
|
||||
}
|
||||
f, err := delivery.DecodeForge(p.Forge)
|
||||
f, err := forgemsg.DecodeForge(p.Forge)
|
||||
if err != nil {
|
||||
return p, f, fmt.Errorf("its merged message: %w", err)
|
||||
}
|
||||
if f.Kind != delivery.ForgeMerged {
|
||||
if f.Kind != forgemsg.ForgeMerged {
|
||||
return p, f, fmt.Errorf("a pending value holds a %s message, not a merged one", f.Kind)
|
||||
}
|
||||
return p, f, nil
|
||||
@@ -143,7 +145,7 @@ func WaitsOf(entries []PendingEntry) Waits {
|
||||
w.Groups[e.Value.Group] = append(w.Groups[e.Value.Group], e)
|
||||
for _, m := range e.Forge.Members {
|
||||
if _, ok := blocked[strings.ToLower(m.Repository)]; !ok {
|
||||
blocked[strings.ToLower(m.Repository)] = delivery.GroupTakeInKey(e.Value.Group)
|
||||
blocked[strings.ToLower(m.Repository)] = takein.GroupTakeInKey(e.Value.Group)
|
||||
}
|
||||
}
|
||||
continue
|
||||
@@ -158,10 +160,10 @@ func WaitsOf(entries []PendingEntry) Waits {
|
||||
}
|
||||
|
||||
// mergesOf are the merges of entries, oldest first.
|
||||
func mergesOf(entries []PendingEntry) []delivery.GroupMerge {
|
||||
out := make([]delivery.GroupMerge, len(entries))
|
||||
func mergesOf(entries []PendingEntry) []takein.Merge {
|
||||
out := make([]takein.Merge, len(entries))
|
||||
for i, e := range entries {
|
||||
out[i] = delivery.GroupMerge{Repository: e.Forge.Repository, Number: e.Forge.Number, Merge: e.Forge.Merge}
|
||||
out[i] = takein.Merge{Repository: e.Forge.Repository, Number: e.Forge.Number, Merge: e.Forge.Merge}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -177,8 +179,8 @@ func sinceOf(entries []PendingEntry) time.Time {
|
||||
}
|
||||
|
||||
// groupWait is the take-in body of a group being gathered.
|
||||
func groupWait(kind, group string, held []PendingEntry) delivery.TakeIn {
|
||||
t := delivery.TakeIn{Kind: kind, Key: delivery.GroupTakeInKey(group), What: delivery.TakeInGroup, Group: group,
|
||||
func groupWait(kind, group string, held []PendingEntry) takein.TakeIn {
|
||||
t := takein.TakeIn{Kind: kind, Key: takein.GroupTakeInKey(group), What: takein.TakeInGroup, Group: group,
|
||||
Merges: mergesOf(held), Since: sinceOf(held)}
|
||||
if len(held) > 0 {
|
||||
t.Members = held[len(held)-1].Forge.Members
|
||||
@@ -187,8 +189,8 @@ func groupWait(kind, group string, held []PendingEntry) delivery.TakeIn {
|
||||
}
|
||||
|
||||
// revisionWait is the take-in body of a revision: its key is its oldest merge's.
|
||||
func revisionWait(kind string, ready []PendingEntry, waitsFor string) delivery.TakeIn {
|
||||
return delivery.TakeIn{Kind: kind, Key: ready[0].Key, What: delivery.TakeInRevision, Merges: mergesOf(ready),
|
||||
func revisionWait(kind string, ready []PendingEntry, waitsFor string) takein.TakeIn {
|
||||
return takein.TakeIn{Kind: kind, Key: ready[0].Key, What: takein.TakeInRevision, Merges: mergesOf(ready),
|
||||
WaitsFor: waitsFor, Since: sinceOf(ready)}
|
||||
}
|
||||
|
||||
@@ -196,7 +198,7 @@ func revisionWait(kind string, ready []PendingEntry, waitsFor string) delivery.T
|
||||
// (issue 1790), and until then every merge is taken in at once, so the build this program runs is not needed. A read
|
||||
// that fails otherwise counts as on the bus: the step that follows answers the error.
|
||||
func (ix *Indexer) pendingOnBus(ctx context.Context) bool {
|
||||
_, _, _, err := ix.Pending.Get(ctx, delivery.PendingKey(0))
|
||||
_, _, _, err := ix.Pending.Get(ctx, takein.PendingKey(0))
|
||||
return !errors.Is(err, process.ErrNotYet)
|
||||
}
|
||||
|
||||
@@ -216,7 +218,7 @@ func (ix *Indexer) ownBuildNow(ctx context.Context) string {
|
||||
|
||||
// sayTakeIn publishes a take-in event and keeps what it said on the entries, so indexer_pending reads the last event of
|
||||
// each wait from the state. A publish that fails is said in the log: the next evaluation says it again.
|
||||
func (ix *Indexer) sayTakeIn(ctx context.Context, t delivery.TakeIn) bool {
|
||||
func (ix *Indexer) sayTakeIn(ctx context.Context, t takein.TakeIn) bool {
|
||||
if ix.SayTakeIn == nil {
|
||||
return true
|
||||
}
|
||||
@@ -229,7 +231,7 @@ func (ix *Indexer) sayTakeIn(ctx context.Context, t delivery.TakeIn) bool {
|
||||
|
||||
// markSaid writes on each entry the wait it was said under, by compare-and-set; a lost one is left (its next
|
||||
// evaluation says it again).
|
||||
func (ix *Indexer) markSaid(ctx context.Context, entries []PendingEntry, t *delivery.TakeIn, change func(*Pending)) {
|
||||
func (ix *Indexer) markSaid(ctx context.Context, entries []PendingEntry, t *takein.TakeIn, change func(*Pending)) {
|
||||
for _, e := range entries {
|
||||
p := e.Value
|
||||
if t != nil {
|
||||
@@ -253,7 +255,7 @@ func (ix *Indexer) markSaid(ctx context.Context, entries []PendingEntry, t *deli
|
||||
// holds is evaluated (Evaluate): taken in whole when its revision names the build this program runs, or held for the
|
||||
// indexer it names. It answers what was taken in, and a line saying what waits. A message is never written while the
|
||||
// build this program runs is not known (ErrOwnBuildUnknown).
|
||||
func (ix *Indexer) Arrive(ctx context.Context, seq uint64, f delivery.Forge) (Applied, string, error) {
|
||||
func (ix *Indexer) Arrive(ctx context.Context, seq uint64, f forgemsg.Forge) (Applied, string, error) {
|
||||
if ix.Pending == nil || ix.Modules == nil || !ix.pendingOnBus(ctx) {
|
||||
// The state this delivery brings is not on the bus yet (issue 1790): the merge is taken in at once, as before,
|
||||
// whether or not the build this program runs is known.
|
||||
@@ -266,11 +268,11 @@ func (ix *Indexer) Arrive(ctx context.Context, seq uint64, f delivery.Forge) (Ap
|
||||
}
|
||||
ix.pendingMu.Lock()
|
||||
defer ix.pendingMu.Unlock()
|
||||
raw, err := delivery.EncodeForge(f)
|
||||
raw, err := forgemsg.EncodeForge(f)
|
||||
if err != nil {
|
||||
return Applied{}, "", fmt.Errorf("%w: %v", ErrUnreadable, err)
|
||||
}
|
||||
key := delivery.PendingKey(seq)
|
||||
key := takein.PendingKey(seq)
|
||||
data, err := encodePending(Pending{Forge: raw, Group: f.Group, Since: ix.now(), LastWriter: own})
|
||||
if err != nil {
|
||||
return Applied{}, "", err
|
||||
@@ -290,7 +292,7 @@ func (ix *Indexer) Arrive(ctx context.Context, seq uint64, f delivery.Forge) (Ap
|
||||
return Applied{}, "", err
|
||||
}
|
||||
if held := WaitsOf(entries).Groups[f.Group]; len(held) > 0 {
|
||||
t := groupWait(delivery.TakeInPending, f.Group, held)
|
||||
t := groupWait(takein.TakeInPending, f.Group, held)
|
||||
if ix.sayTakeIn(ctx, t) {
|
||||
ix.markSaid(ctx, held, &t, nil)
|
||||
}
|
||||
@@ -304,7 +306,7 @@ func (ix *Indexer) Arrive(ctx context.Context, seq uint64, f delivery.Forge) (Ap
|
||||
// one revision. A member named and neither held nor taken in yet answers an error, so the end is delivered again once
|
||||
// the member arrived. An end of a group taken in already (every member's merge at or behind its repository's taken
|
||||
// merge) changes nothing and is said: it is never delivered again for ever.
|
||||
func (ix *Indexer) EndGroup(ctx context.Context, end delivery.ForgeGroupEnd) (Applied, string, error) {
|
||||
func (ix *Indexer) EndGroup(ctx context.Context, end forgemsg.ForgeGroupEnd) (Applied, string, error) {
|
||||
if ix.Pending == nil || !ix.pendingOnBus(ctx) {
|
||||
return Applied{}, "no pending state: the group's merges were taken in one by one", nil
|
||||
}
|
||||
@@ -327,7 +329,7 @@ func (ix *Indexer) EndGroup(ctx context.Context, end delivery.ForgeGroupEnd) (Ap
|
||||
held = append(held, e)
|
||||
}
|
||||
}
|
||||
isHeld := func(m delivery.GroupMerge) bool {
|
||||
isHeld := func(m forgemsg.GroupMerge) bool {
|
||||
return slices.ContainsFunc(held, func(e PendingEntry) bool {
|
||||
return delivery.SameRepository(e.Forge.Repository, m.Repository) && e.Forge.Number == m.Number
|
||||
})
|
||||
@@ -357,7 +359,7 @@ func (ix *Indexer) EndGroup(ctx context.Context, end delivery.ForgeGroupEnd) (Ap
|
||||
}
|
||||
// The group's wait ends first (take-in.taken under its group key); a revision it then is, pending for another
|
||||
// indexer, is said pending under its oldest merge's key by the evaluation that follows.
|
||||
t := groupWait(delivery.TakeInTaken, end.Group, held)
|
||||
t := groupWait(takein.TakeInTaken, end.Group, held)
|
||||
said := &t
|
||||
if !ix.sayTakeIn(ctx, t) {
|
||||
said = nil // said again by the take-in or the hold that follows (saidWaits)
|
||||
@@ -368,7 +370,7 @@ func (ix *Indexer) EndGroup(ctx context.Context, end delivery.ForgeGroupEnd) (Ap
|
||||
}
|
||||
|
||||
// takenAlready says whether a group member's merge is at or behind its repository's taken merge.
|
||||
func (ix *Indexer) takenAlready(ctx context.Context, m delivery.GroupMerge) (bool, error) {
|
||||
func (ix *Indexer) takenAlready(ctx context.Context, m forgemsg.GroupMerge) (bool, error) {
|
||||
if ix.Taken == nil {
|
||||
return false, nil
|
||||
}
|
||||
@@ -622,21 +624,21 @@ func byWords(by string) string {
|
||||
}
|
||||
|
||||
// saidWaits are the taken events of every wait the entries were said under that is not ended yet.
|
||||
func saidWaits(entries []PendingEntry) []delivery.TakeIn {
|
||||
func saidWaits(entries []PendingEntry) []takein.TakeIn {
|
||||
by := map[string][]PendingEntry{}
|
||||
for _, e := range entries {
|
||||
if e.Value.Said != "" && e.Value.SaidKind == delivery.TakeInPending {
|
||||
if e.Value.Said != "" && e.Value.SaidKind == takein.TakeInPending {
|
||||
by[e.Value.Said] = append(by[e.Value.Said], e)
|
||||
}
|
||||
}
|
||||
var out []delivery.TakeIn
|
||||
var out []takein.TakeIn
|
||||
for _, key := range slices.Sorted(maps.Keys(by)) {
|
||||
held := by[key]
|
||||
if strings.HasPrefix(key, "g.") {
|
||||
out = append(out, groupWait(delivery.TakeInTaken, held[0].Value.Group, held))
|
||||
out = append(out, groupWait(takein.TakeInTaken, held[0].Value.Group, held))
|
||||
continue
|
||||
}
|
||||
t := revisionWait(delivery.TakeInTaken, held, held[0].Value.WaitsFor)
|
||||
t := revisionWait(takein.TakeInTaken, held, held[0].Value.WaitsFor)
|
||||
t.Key = key
|
||||
out = append(out, t)
|
||||
}
|
||||
@@ -666,18 +668,18 @@ func (ix *Indexer) holdFor(ctx context.Context, ready []PendingEntry, names, own
|
||||
}
|
||||
}
|
||||
}
|
||||
t := revisionWait(delivery.TakeInPending, ready, names)
|
||||
t := revisionWait(takein.TakeInPending, ready, names)
|
||||
var ended, unmarked []PendingEntry
|
||||
said, changed := false, false
|
||||
for _, e := range ready {
|
||||
switch {
|
||||
case e.Value.Said == t.Key && e.Value.SaidKind == delivery.TakeInPending:
|
||||
case e.Value.Said == t.Key && e.Value.SaidKind == takein.TakeInPending:
|
||||
said = true
|
||||
changed = changed || e.Value.WaitsFor != names
|
||||
if e.Value.WaitsFor != names {
|
||||
unmarked = append(unmarked, e)
|
||||
}
|
||||
case e.Value.Said != "" && e.Value.SaidKind == delivery.TakeInPending:
|
||||
case e.Value.Said != "" && e.Value.SaidKind == takein.TakeInPending:
|
||||
ended = append(ended, e)
|
||||
unmarked = append(unmarked, e)
|
||||
default:
|
||||
@@ -722,28 +724,28 @@ func copyKey(ctx context.Context, from *overlayStore, to Store, key string) erro
|
||||
// PendingWaits is what indexer_pending answers of the pending state's entries (ADR 0633 §5): the ready merges as one
|
||||
// revision, each group being gathered, and the merges behind a group, each with the modules it holds back and the
|
||||
// last take-in event said of it.
|
||||
func PendingWaits(entries []PendingEntry) []delivery.PendingWait {
|
||||
func PendingWaits(entries []PendingEntry) []takein.PendingWait {
|
||||
w := WaitsOf(entries)
|
||||
var out []delivery.PendingWait
|
||||
var out []takein.PendingWait
|
||||
if len(w.Ready) > 0 {
|
||||
out = append(out, waitOf(w.Ready, delivery.TakeInRevision, ""))
|
||||
out = append(out, waitOf(w.Ready, takein.TakeInRevision, ""))
|
||||
}
|
||||
for _, g := range slices.Sorted(maps.Keys(w.Groups)) {
|
||||
out = append(out, waitOf(w.Groups[g], delivery.TakeInGroup, ""))
|
||||
out = append(out, waitOf(w.Groups[g], takein.TakeInGroup, ""))
|
||||
}
|
||||
for _, g := range slices.Sorted(maps.Keys(w.Behind)) {
|
||||
out = append(out, waitOf(w.Behind[g], delivery.TakeInRevision, g))
|
||||
out = append(out, waitOf(w.Behind[g], takein.TakeInRevision, g))
|
||||
}
|
||||
slices.SortStableFunc(out, func(a, b delivery.PendingWait) int { return a.Since.Compare(b.Since) })
|
||||
slices.SortStableFunc(out, func(a, b takein.PendingWait) int { return a.Since.Compare(b.Since) })
|
||||
return out
|
||||
}
|
||||
|
||||
func waitOf(entries []PendingEntry, what, behind string) delivery.PendingWait {
|
||||
pw := delivery.PendingWait{Key: entries[0].Key, What: what, Merges: mergesOf(entries), Behind: behind, Since: sinceOf(entries),
|
||||
func waitOf(entries []PendingEntry, what, behind string) takein.PendingWait {
|
||||
pw := takein.PendingWait{Key: entries[0].Key, What: what, Merges: mergesOf(entries), Behind: behind, Since: sinceOf(entries),
|
||||
WaitsFor: entries[len(entries)-1].Value.WaitsFor, Holds: []string{}}
|
||||
if what == delivery.TakeInGroup {
|
||||
if what == takein.TakeInGroup {
|
||||
pw.Group = entries[0].Value.Group
|
||||
pw.Key = delivery.GroupTakeInKey(pw.Group)
|
||||
pw.Key = takein.GroupTakeInKey(pw.Group)
|
||||
pw.Members = entries[len(entries)-1].Forge.Members
|
||||
pw.WaitsFor = ""
|
||||
}
|
||||
@@ -755,7 +757,7 @@ func waitOf(entries []PendingEntry, what, behind string) delivery.PendingWait {
|
||||
}
|
||||
}
|
||||
if e.Value.Said != "" && e.Value.SaidAt.After(pw.SaidAt) {
|
||||
pw.Said, pw.SaidAt = delivery.TakeInStream+"."+e.Value.SaidKind+"."+e.Value.Said, e.Value.SaidAt
|
||||
pw.Said, pw.SaidAt = takein.TakeInStream+"."+e.Value.SaidKind+"."+e.Value.Said, e.Value.SaidAt
|
||||
}
|
||||
}
|
||||
slices.Sort(pw.Holds)
|
||||
@@ -785,7 +787,7 @@ func (ix *Indexer) TakeInPendingByHand(ctx context.Context, key, by, why string)
|
||||
switch {
|
||||
case strings.HasPrefix(key, "g."):
|
||||
for g, held := range w.Groups {
|
||||
if delivery.GroupTakeInKey(g) == key {
|
||||
if takein.GroupTakeInKey(g) == key {
|
||||
take = held
|
||||
}
|
||||
}
|
||||
@@ -838,7 +840,7 @@ func (ix *Indexer) DropPending(ctx context.Context, key, by, why string) TakenIn
|
||||
return r
|
||||
}
|
||||
ix.logf("take-in: %s (%s#%d at %s) dropped by hand, asked by %s: %s; nothing of it is taken in", key, e.Forge.Repository, e.Forge.Number, e.Forge.Merge, by, why)
|
||||
if e.Value.Said != "" && e.Value.SaidKind == delivery.TakeInPending {
|
||||
if e.Value.Said != "" && e.Value.SaidKind == takein.TakeInPending {
|
||||
var same []PendingEntry
|
||||
for _, o := range entries {
|
||||
if o.Key != key && o.Value.Said == e.Value.Said {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/process"
|
||||
)
|
||||
@@ -19,8 +21,8 @@ type takeInBed struct {
|
||||
ix *Indexer
|
||||
mods *memStore
|
||||
pending *memStore
|
||||
said []delivery.TakeIn
|
||||
base delivery.Forge
|
||||
said []takein.TakeIn
|
||||
base forgemsg.Forge
|
||||
logs *strings.Builder
|
||||
}
|
||||
|
||||
@@ -36,7 +38,7 @@ func newTakeInBed(t *testing.T) *takeInBed {
|
||||
b := &takeInBed{ix: ix, mods: mods, pending: newMem(), base: f, logs: &strings.Builder{}}
|
||||
ix.Verdicts = newMem()
|
||||
ix.Pending = b.pending
|
||||
ix.SayTakeIn = func(_ context.Context, t delivery.TakeIn) error { b.said = append(b.said, t); return nil }
|
||||
ix.SayTakeIn = func(_ context.Context, t takein.TakeIn) error { b.said = append(b.said, t); return nil }
|
||||
logf := ix.Logf
|
||||
ix.Logf = func(format string, args ...any) {
|
||||
b.logs.WriteString(format + "\n")
|
||||
@@ -65,7 +67,7 @@ func (b *takeInBed) commit(t *testing.T, module string) string {
|
||||
}
|
||||
|
||||
// merge is a catalogue merge at commit c whose trees of avahi and the indexer are given ("" keeps the base's).
|
||||
func (b *takeInBed) merge(number int, c, avahi, indexer string) delivery.Forge {
|
||||
func (b *takeInBed) merge(number int, c, avahi, indexer string) forgemsg.Forge {
|
||||
f := b.base
|
||||
f.Number, f.Merge, f.Head = number, c, c
|
||||
f.Trees = map[string]string{}
|
||||
@@ -81,7 +83,7 @@ func (b *takeInBed) merge(number int, c, avahi, indexer string) delivery.Forge {
|
||||
return f
|
||||
}
|
||||
|
||||
func (b *takeInBed) arrive(t *testing.T, seq uint64, f delivery.Forge) {
|
||||
func (b *takeInBed) arrive(t *testing.T, seq uint64, f forgemsg.Forge) {
|
||||
t.Helper()
|
||||
if _, _, err := b.ix.Arrive(context.Background(), seq, f); err != nil {
|
||||
t.Fatalf("arrive %d: %v", seq, err)
|
||||
@@ -144,7 +146,7 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
|
||||
if n := len(b.ix.Verdicts.(*memStore).vals); n > verdicts+1 {
|
||||
t.Errorf("verdicts %d, want at most %d: only the new indexer manifest's", n, verdicts+1)
|
||||
}
|
||||
if want := []string{delivery.PendingKey(2)}; !reflect.DeepEqual(b.keys(), want) || !slices.Equal(b.events(), []string{"pending " + delivery.PendingKey(2)}) || b.said[0].WaitsFor != named {
|
||||
if want := []string{takein.PendingKey(2)}; !reflect.DeepEqual(b.keys(), want) || !slices.Equal(b.events(), []string{"pending " + takein.PendingKey(2)}) || b.said[0].WaitsFor != named {
|
||||
t.Fatalf("pending %v, said %+v: want m.2 pending for %s", b.keys(), b.said, named)
|
||||
}
|
||||
// T5: the merge delivered again writes no second key and says nothing again.
|
||||
@@ -162,7 +164,7 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
|
||||
if len(b.keys()) != 0 || b.fp(t, delivery.IndexerModule) != own || b.commit(t, "avahi") != sha('e') {
|
||||
t.Fatalf("revert: pending %v, indexer %s, avahi at %s: want all taken in", b.keys(), b.fp(t, delivery.IndexerModule), b.commit(t, "avahi"))
|
||||
}
|
||||
if want := []string{"pending " + delivery.PendingKey(2), "taken " + delivery.PendingKey(2)}; !slices.Equal(b.events(), want) {
|
||||
if want := []string{"pending " + takein.PendingKey(2), "taken " + takein.PendingKey(2)}; !slices.Equal(b.events(), want) {
|
||||
t.Errorf("said %v, want %v", b.events(), want)
|
||||
}
|
||||
})
|
||||
@@ -192,7 +194,7 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
|
||||
if _, _, err := next.TakeInAtStart(ctx, func(context.Context) bool { return true }); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if asked < 2 || !reflect.DeepEqual(b.keys(), []string{delivery.PendingKey(5)}) || b.commit(t, "avahi") != sha('c') {
|
||||
if asked < 2 || !reflect.DeepEqual(b.keys(), []string{takein.PendingKey(5)}) || b.commit(t, "avahi") != sha('c') {
|
||||
t.Errorf("start: asked %d, pending %v, avahi at %s: want the revision taken in, the group left", asked, b.keys(), b.commit(t, "avahi"))
|
||||
}
|
||||
})
|
||||
@@ -204,7 +206,7 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
|
||||
b.ix.Contains = func(_ context.Context, _, commit, in string) (bool, error) {
|
||||
return strings.IndexByte(order, commit[0]) <= strings.IndexByte(order, in[0]), nil
|
||||
}
|
||||
member := func(repo string, number int, c string) delivery.Forge {
|
||||
member := func(repo string, number int, c string) forgemsg.Forge {
|
||||
f := b.merge(number, c, "", "")
|
||||
f.Group = "feat/take-in"
|
||||
f.Members = []delivery.Head{{Repository: catalogueRepo, Number: 40, Head: sha('1')}, {Repository: "novox/mesh-sdk", Number: 41, Head: sha('2')}}
|
||||
@@ -223,10 +225,10 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
|
||||
alone.Repository, alone.Trees, alone.Manifests, alone.Locks = "novox/hq", map[string]string{}, nil, nil
|
||||
b.arrive(t, 2, alone)
|
||||
b.arrive(t, 3, b.merge(51, sha('6'), "tree-avahi-9", ""))
|
||||
if !reflect.DeepEqual(b.keys(), []string{delivery.PendingKey(1), delivery.PendingKey(3)}) {
|
||||
if !reflect.DeepEqual(b.keys(), []string{takein.PendingKey(1), takein.PendingKey(3)}) {
|
||||
t.Fatalf("pending %v: want the member and the merge behind it", b.keys())
|
||||
}
|
||||
end := delivery.ForgeGroupEnd{Group: "feat/take-in", Members: []delivery.GroupMerge{{Repository: catalogueRepo, Number: 40, Merge: sha('7')}, {Repository: "novox/mesh-sdk", Number: 41, Merge: sha('b')}}}
|
||||
end := forgemsg.ForgeGroupEnd{Group: "feat/take-in", Members: []forgemsg.GroupMerge{{Repository: catalogueRepo, Number: 40, Merge: sha('7')}, {Repository: "novox/mesh-sdk", Number: 41, Merge: sha('b')}}}
|
||||
if _, _, err := b.ix.EndGroup(ctx, end); err == nil {
|
||||
t.Fatal("an end before its last member is taken in")
|
||||
}
|
||||
@@ -250,10 +252,10 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
|
||||
g.Group, g.Trees = "feat/two", b.merge(0, "", "", "tree-indexer-7").Trees
|
||||
g.Members = g.Members[:1]
|
||||
b.arrive(t, 9, g)
|
||||
if _, _, err := b.ix.EndGroup(ctx, delivery.ForgeGroupEnd{Group: "feat/two", Members: []delivery.GroupMerge{{Repository: catalogueRepo, Number: 40, Merge: sha('c')}}}); err != nil {
|
||||
if _, _, err := b.ix.EndGroup(ctx, forgemsg.ForgeGroupEnd{Group: "feat/two", Members: []forgemsg.GroupMerge{{Repository: catalogueRepo, Number: 40, Merge: sha('c')}}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := []string{"pending g.feat/two", "taken g.feat/two", "pending " + delivery.PendingKey(9)}; !slices.Equal(b.events(), want) {
|
||||
if want := []string{"pending g.feat/two", "taken g.feat/two", "pending " + takein.PendingKey(9)}; !slices.Equal(b.events(), want) {
|
||||
t.Errorf("a group naming another indexer: said %v, want %v", b.events(), want)
|
||||
}
|
||||
})
|
||||
@@ -266,12 +268,12 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
|
||||
b.ix.ownMu.Unlock()
|
||||
// The other indexer claims the merge between this one's read and its claim.
|
||||
b.pending.beforeWrite = func() {
|
||||
v := b.pending.vals[delivery.PendingKey(2)]
|
||||
v := b.pending.vals[takein.PendingKey(2)]
|
||||
var p Pending
|
||||
_ = json.Unmarshal(v, &p)
|
||||
p.Taking = "the other indexer"
|
||||
data, _ := json.Marshal(p)
|
||||
b.pending.put(delivery.PendingKey(2), data)
|
||||
b.pending.put(takein.PendingKey(2), data)
|
||||
}
|
||||
if _, _, err := b.ix.TakeInAtStart(ctx, func(context.Context) bool { return false }); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -284,32 +286,32 @@ func TestTakeIn(t *testing.T) { // 1585/T3-T13 (ADR 0633)
|
||||
b := newTakeInBed(t)
|
||||
b.arrive(t, 2, b.merge(21, sha('c'), "tree-avahi-3", "tree-indexer-2"))
|
||||
b.arrive(t, 3, b.merge(22, sha('d'), "tree-avahi-4", "tree-indexer-2"))
|
||||
var answer delivery.PendingAnswer
|
||||
var answer takein.PendingAnswer
|
||||
for _, tool := range PendingTools(b.pending, nil, nil) {
|
||||
if tool.Name == PendingTool {
|
||||
out, err := tool.Run(map[string]any{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
answer = out.(delivery.PendingAnswer)
|
||||
answer = out.(takein.PendingAnswer)
|
||||
}
|
||||
}
|
||||
if len(answer.Waits) != 1 || answer.Waits[0].Key != delivery.PendingKey(2) || !slices.Equal(answer.Waits[0].Holds, []string{"avahi", delivery.IndexerModule}) ||
|
||||
answer.Waits[0].Said != "take-in.pending."+delivery.PendingKey(2) || len(answer.Waits[0].Keys) != 2 {
|
||||
if len(answer.Waits) != 1 || answer.Waits[0].Key != takein.PendingKey(2) || !slices.Equal(answer.Waits[0].Holds, []string{"avahi", delivery.IndexerModule}) ||
|
||||
answer.Waits[0].Said != "take-in.pending."+takein.PendingKey(2) || len(answer.Waits[0].Keys) != 2 {
|
||||
t.Fatalf("indexer_pending = %+v", answer)
|
||||
}
|
||||
b.said = nil
|
||||
if r := b.ix.DropPending(ctx, delivery.PendingKey(2), "lead@shanks", "replaced by m.3"); !r.Stored {
|
||||
if r := b.ix.DropPending(ctx, takein.PendingKey(2), "lead@shanks", "replaced by m.3"); !r.Stored {
|
||||
t.Fatalf("drop: %+v", r)
|
||||
}
|
||||
if len(b.said) < 2 || !b.said[0].Dropped || b.said[0].Key != delivery.PendingKey(2) || b.said[0].By != "lead@shanks" || b.said[1].Key != delivery.PendingKey(3) {
|
||||
if len(b.said) < 2 || !b.said[0].Dropped || b.said[0].Key != takein.PendingKey(2) || b.said[0].By != "lead@shanks" || b.said[1].Key != takein.PendingKey(3) {
|
||||
t.Fatalf("drop said %+v: want m.2 taken (dropped, by) and m.3 pending", b.said)
|
||||
}
|
||||
b.said = nil
|
||||
if r := b.ix.TakeInPendingByHand(ctx, delivery.PendingKey(3), "lead@shanks", "the named indexer can never run"); !r.Stored {
|
||||
if r := b.ix.TakeInPendingByHand(ctx, takein.PendingKey(3), "lead@shanks", "the named indexer can never run"); !r.Stored {
|
||||
t.Fatalf("hand take-in: %+v", r)
|
||||
}
|
||||
if len(b.keys()) != 0 || b.commit(t, "avahi") != sha('d') || len(b.said) != 1 || b.said[0].Kind != delivery.TakeInTaken || b.said[0].By != "lead@shanks" {
|
||||
if len(b.keys()) != 0 || b.commit(t, "avahi") != sha('d') || len(b.said) != 1 || b.said[0].Kind != takein.TakeInTaken || b.said[0].By != "lead@shanks" {
|
||||
t.Errorf("hand take-in: pending %v, avahi at %s, said %+v", b.keys(), b.commit(t, "avahi"), b.said)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// PendingTools are the indexer's tools of what waits for a take-in (novox/hq ADR 0633 §5): indexer_pending reads the
|
||||
@@ -46,7 +46,7 @@ func PendingTools(pending Store, ask func(key string, value []byte) (TakeInAsk,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return delivery.PendingAnswer{Waits: PendingWaits(entries), Unread: unread}, nil
|
||||
return takein.PendingAnswer{Waits: PendingWaits(entries), Unread: unread}, nil
|
||||
}},
|
||||
{Name: TakeInPendingTool,
|
||||
Description: "Take a wait in by hand with the running indexer and its judge (ADR 0633 §5): the revision under its m. key, " +
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"mesh-delivery-indexer/internal/fingerprint"
|
||||
@@ -38,7 +39,7 @@ func avahiOnTools(t *testing.T) string {
|
||||
}
|
||||
|
||||
// avahiPull is the pull message of the catalogue's pull request 301 touching avahi, with its manifest.
|
||||
func avahiPull(t *testing.T, head string) delivery.Forge {
|
||||
func avahiPull(t *testing.T, head string) forgemsg.Forge {
|
||||
t.Helper()
|
||||
f := manifestPull(t, 301, head, avahiOnTools(t))
|
||||
f.Manifests = map[string]string{"modules/avahi": avahiOnTools(t)}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
@@ -125,7 +126,7 @@ func (ix *Indexer) indexAt(ctx context.Context, r Registration, force, reread bo
|
||||
t.why = fmt.Sprintf("the manifest at %q of %s at %s names the module %q", dir, repository, commit, name)
|
||||
return t, nil
|
||||
}
|
||||
f := delivery.Forge{V: delivery.Version, Kind: delivery.ForgeMerged, Repository: repository, Merge: commit, Root: read.Root,
|
||||
f := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgeMerged, Repository: repository, Merge: commit, Root: read.Root,
|
||||
Trees: map[string]string{dir: read.Trees[dir]},
|
||||
Manifests: map[string]string{dir: raw}}
|
||||
if l := read.Locks[dir]; len(l) > 0 {
|
||||
@@ -396,7 +397,7 @@ func (ix *Indexer) TakeInRepository(ctx context.Context, repository string) Take
|
||||
out.Answer = "the modules state cannot be read: " + err.Error()
|
||||
return out
|
||||
}
|
||||
f := delivery.Forge{V: delivery.Version, Kind: delivery.ForgeMerged, Repository: out.Repository, Merge: head}
|
||||
f := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgeMerged, Repository: out.Repository, Merge: head}
|
||||
if _, err := ix.ApplyMessage(ctx, f); err != nil {
|
||||
out.Answer = fmt.Sprintf("the merge of %s at %.12s could not be taken in: %v", repository, head, err)
|
||||
return out
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"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/process"
|
||||
)
|
||||
@@ -19,13 +20,13 @@ import (
|
||||
// say keeps the key as it is and says so.
|
||||
|
||||
// writeTaken writes the repository's taken key for the merge f carries, unless it names that merge or a later one.
|
||||
func (ix *Indexer) writeTaken(ctx context.Context, f delivery.Forge) error {
|
||||
func (ix *Indexer) writeTaken(ctx context.Context, f forgemsg.Forge) error {
|
||||
return ix.writeTakenKnown(ctx, f, "")
|
||||
}
|
||||
|
||||
// writeTakenKnown is writeTaken where the merge is known not to be older than notOlder (lateMerge asked it, issue 1126): a
|
||||
// key naming that merge is written over without asking the forge again.
|
||||
func (ix *Indexer) writeTakenKnown(ctx context.Context, f delivery.Forge, notOlder string) error {
|
||||
func (ix *Indexer) writeTakenKnown(ctx context.Context, f forgemsg.Forge, notOlder string) error {
|
||||
if ix.Taken == nil || f.Merge == "" {
|
||||
return nil
|
||||
}
|
||||
@@ -101,7 +102,7 @@ func (ix *Indexer) FillTaken(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
for _, repository := range slices.Sorted(maps.Keys(pending)) {
|
||||
err := ix.writeTaken(ctx, delivery.Forge{Repository: repository, Merge: pending[repository]})
|
||||
err := ix.writeTaken(ctx, forgemsg.Forge{Repository: repository, Merge: pending[repository]})
|
||||
if err == nil {
|
||||
ix.mu.Lock()
|
||||
if ix.untaken[repository] == pending[repository] {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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/process"
|
||||
|
||||
@@ -91,7 +92,7 @@ func newFormIndexer(t *testing.T, mods ...mod) (*Indexer, *memStore) {
|
||||
}
|
||||
|
||||
// newFormIndexerUnmerged is the indexer and the catalogue merge of these modules, not yet applied.
|
||||
func newFormIndexerUnmerged(t *testing.T, mods ...mod) (*Indexer, *memStore, delivery.Forge) {
|
||||
func newFormIndexerUnmerged(t *testing.T, mods ...mod) (*Indexer, *memStore, forgemsg.Forge) {
|
||||
t.Helper()
|
||||
var recs [][2]any
|
||||
trees, manifests := map[string]string{}, map[string]string{}
|
||||
@@ -468,7 +469,7 @@ func TestAModuleKeepsTheToolchainsItsFingerprintWasComputedWith(t *testing.T) {
|
||||
tools, _ := moduleOf(t, mods, "mesh-tools")
|
||||
sdkMerge := func(commit, tree string) delivery.Module {
|
||||
t.Helper()
|
||||
f := delivery.Forge{V: delivery.Version, Kind: delivery.ForgeMerged, Repository: "novox/mesh-sdk", Branch: "feat/x", Number: 3, Head: h1,
|
||||
f := forgemsg.Forge{V: delivery.Version, Kind: forgemsg.ForgeMerged, Repository: "novox/mesh-sdk", Branch: "feat/x", Number: 3, Head: h1,
|
||||
Merge: commit, Root: tree, Trees: map[string]string{"": tree}, Manifests: map[string]string{"": `{"module":"mesh-sdk","version":"1"}`}}
|
||||
if err := ix.Apply(context.Background(), f); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
@@ -319,7 +320,7 @@ func TestTheIndexerJudgesEveryManifestOnce(t *testing.T) {
|
||||
t.Fatalf("the start pass: %+v, this judge's own refusal %+v (%v)", verdict(atStart), verdict(ownRefusal), err)
|
||||
}
|
||||
byPull := stale("tools", `{"module":"tools","version":"8"}`)
|
||||
pull := delivery.Forge{Kind: delivery.ForgePull, Repository: "novox/mesh-tools", Number: 3, Branch: "main",
|
||||
pull := forgemsg.Forge{Kind: forgemsg.ForgePull, Repository: "novox/mesh-tools", Number: 3, Branch: "main",
|
||||
Manifests: map[string]string{"modules/tools": `{"module":"tools","version":"8"}`}}
|
||||
if _, err := ix.ApplyMessage(ctx, pull); err != nil || verdict(byPull).Refused() || verdict(byPull).Judge != ix.judgeID() {
|
||||
t.Fatalf("a pull carrying a digest an older judge refused: %+v %v", verdict(byPull), err)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
@@ -147,7 +148,7 @@ func TestManifestDeclarations(t *testing.T) { // 1238/AC1, 1444 (reads the build
|
||||
}
|
||||
// 1585/T11 (ADR 0633 §1, §5): its one stream, take-in, an old streams entry (the indexer is foundation, ADR 0595 §11),
|
||||
// carries the take-in events.
|
||||
if want := []streamDecl{{Name: delivery.TakeInStream, Events: []string{"take-in.**"}, MaxAgeDays: 7}}; !reflect.DeepEqual(m.Streams, want) {
|
||||
if want := []streamDecl{{Name: takein.TakeInStream, Events: []string{"take-in.**"}, MaxAgeDays: 7}}; !reflect.DeepEqual(m.Streams, want) {
|
||||
t.Errorf("streams = %+v, want %+v", m.Streams, want)
|
||||
}
|
||||
// No other bus section, no other seat (ADR 0437 §4).
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
module git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104002338-5a38a1221659
|
||||
@@ -0,0 +1,14 @@
|
||||
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/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
|
||||
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
|
||||
github.com/nats-io/nats.go v1.54.0 h1:vsXoOxjHp/GmPUN+EcI7uOf/uB+iAP+kEsAFNQN0yzA=
|
||||
github.com/nats-io/nats.go v1.54.0/go.mod h1:y+DZoD1oBOYfZTU681eTUiUjI0vbqYGixNVFHcjHJ0k=
|
||||
github.com/nats-io/nkeys v0.4.16 h1:rd5oAuLOb8mnAycB0xleuEBNS1pVVnN0fv/FF34Eypg=
|
||||
github.com/nats-io/nkeys v0.4.16/go.mod h1:llLgWoI0o4z/Q57q2R1kHfmocyhGV6VG/U18Glg1Afs=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
golang.org/x/crypto v0.57.0 h1:3ZVCjf8Ggz7zneR/EHRVx68Ctf+2pmIMP2UFhh9cC6M=
|
||||
golang.org/x/crypto v0.57.0/go.mod h1:Fdz0i5U6CoizGwLda9DttjSk6qlZo25zYNtR+ycvuZA=
|
||||
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||
@@ -0,0 +1,251 @@
|
||||
// Package takein is the indexer's take-in contract (novox/hq ADR 0633 §1, §5): the events it publishes on its own stream
|
||||
// `take-in` while a source revision waits for the indexer it names or a delivery group is gathered, their keys, and the
|
||||
// answer of its tool indexer_pending. The indexer writes them; the watchman and the view read them. It is the indexer's
|
||||
// own package: one module owns these types, so they live with it and not in mesh-sdk (the operator, 2026-11-04).
|
||||
package takein
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// Merge is one merge a take-in event or a wait holds: the repository as `<owner>/<name>`, its pull request and the
|
||||
// merge commit the forge made of it.
|
||||
type Merge struct {
|
||||
Repository string `json:"repository"`
|
||||
Number int `json:"number"`
|
||||
Merge string `json:"merge"`
|
||||
}
|
||||
|
||||
// checkRepository refuses a repository that is not `<owner>/<name>`: one slash, neither part empty.
|
||||
func checkRepository(repository string) error {
|
||||
owner, name, ok := strings.Cut(repository, "/")
|
||||
if !ok || owner == "" || name == "" || strings.Contains(name, "/") {
|
||||
return fmt.Errorf("delivery: the repository %q is not <owner>/<name>, with exactly one slash", repository)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decode reads a value of delivery.Version, refusing another or none with delivery.ErrUnknownVersion, as mesh-sdk
|
||||
// delivery's values are read.
|
||||
func decode(data []byte, into any) error {
|
||||
var probe struct {
|
||||
V *int `json:"v"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &probe); err != nil {
|
||||
return fmt.Errorf("delivery: value is not JSON: %w", err)
|
||||
}
|
||||
if probe.V == nil || *probe.V != delivery.Version {
|
||||
return fmt.Errorf("%w: this reader reads %d", delivery.ErrUnknownVersion, delivery.Version)
|
||||
}
|
||||
if err := json.Unmarshal(data, into); err != nil {
|
||||
return fmt.Errorf("delivery: value does not fit its shape: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// The take-in events (novox/hq ADR 0633 §1, §5): the indexer publishes them on its own stream, which keeps the last
|
||||
// message per subject, `take-in.<kind>.<key>`. A wait starts with `take-in.pending` and ends with `take-in.taken`,
|
||||
// whoever ended it (the named indexer, a revert, a hand take-in or a drop). The stream's name is the indexer's manifest's;
|
||||
// these names are its events, relative to it.
|
||||
const (
|
||||
TakeInStream = "take-in"
|
||||
TakeInPending = "pending"
|
||||
TakeInTaken = "taken"
|
||||
)
|
||||
|
||||
// What waits in a take-in event: a source revision for the indexer build it names, or a delivery group for its end.
|
||||
const (
|
||||
TakeInRevision = "revision"
|
||||
TakeInGroup = "group"
|
||||
)
|
||||
|
||||
// PendingKey is the key of one held merge in the indexer's pending state, and the key a revision's take-in events
|
||||
// carry (its oldest merge's): `m.<the merge's stream sequence, 20 digits>`, so the key order is the merge order.
|
||||
func PendingKey(seq uint64) string { return fmt.Sprintf("m.%020d", seq) }
|
||||
|
||||
// GroupTakeInKey is the key a group's take-in events carry while it is gathered: `g.<group>`, with every character a
|
||||
// bus subject cannot carry (a space, `*`, `>`) written `_`. The event's body names the group exactly.
|
||||
func GroupTakeInKey(group string) string {
|
||||
return "g." + strings.Map(func(r rune) rune {
|
||||
if r == '*' || r == '>' || r <= ' ' || r == 0x7f {
|
||||
return '_'
|
||||
}
|
||||
return r
|
||||
}, group)
|
||||
}
|
||||
|
||||
// TakeInEvent is the name of a take-in event of the given kind and key: `take-in.<pending|taken>.<key>`.
|
||||
func TakeInEvent(kind, key string) (string, error) {
|
||||
if kind != TakeInPending && kind != TakeInTaken {
|
||||
return "", fmt.Errorf("delivery: the take-in kind %q is not pending or taken", kind)
|
||||
}
|
||||
if err := checkTakeInKey(key); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return TakeInStream + "." + kind + "." + key, nil
|
||||
}
|
||||
|
||||
// ParseTakeInEvent is the inverse of TakeInEvent.
|
||||
func ParseTakeInEvent(event string) (kind, key string, err error) {
|
||||
parts := strings.SplitN(event, ".", 3)
|
||||
if len(parts) != 3 || parts[0] != TakeInStream {
|
||||
return "", "", fmt.Errorf("delivery: %q is not a take-in event, take-in.<pending|taken>.<key>", event)
|
||||
}
|
||||
if _, err := TakeInEvent(parts[1], parts[2]); err != nil {
|
||||
return "", "", fmt.Errorf("delivery: %q is not a take-in event: %w", event, err)
|
||||
}
|
||||
return parts[1], parts[2], nil
|
||||
}
|
||||
|
||||
func checkTakeInKey(key string) error {
|
||||
if !strings.HasPrefix(key, "m.") && !strings.HasPrefix(key, "g.") {
|
||||
return fmt.Errorf("delivery: the take-in key %q is neither m.<sequence> nor g.<group>", key)
|
||||
}
|
||||
for _, tok := range strings.Split(key, ".") {
|
||||
if tok == "" || strings.ContainsAny(tok, "*> \t\r\n") {
|
||||
return fmt.Errorf("delivery: the take-in key %q is not a bus subject's tokens", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TakeIn is the body of a take-in event. Kind is the event's kind and Key its key. What says what waits: a revision
|
||||
// (Key its oldest merge's PendingKey, WaitsFor the indexer build it names) or a group being gathered (Key its
|
||||
// GroupTakeInKey, Members every approved member with its head, Merges the members held so far). Merges are the merges
|
||||
// it holds, oldest first; Since is when the wait began. A `taken` event names the same once the wait ended; By and Why
|
||||
// name a hand act (a hand take-in, or a drop, Dropped), and are carried by nothing else.
|
||||
type TakeIn struct {
|
||||
V int `json:"v"`
|
||||
Kind string `json:"kind"`
|
||||
Key string `json:"key"`
|
||||
What string `json:"what"`
|
||||
Merges []Merge `json:"merges"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Members []delivery.Head `json:"members,omitempty"`
|
||||
WaitsFor string `json:"waits-for,omitempty"`
|
||||
Since time.Time `json:"since"`
|
||||
By string `json:"by,omitempty"`
|
||||
Why string `json:"why,omitempty"`
|
||||
Dropped bool `json:"dropped,omitempty"`
|
||||
}
|
||||
|
||||
func (t TakeIn) check() error {
|
||||
if _, err := TakeInEvent(t.Kind, t.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(t.Merges) == 0 {
|
||||
return fmt.Errorf("delivery: the take-in event %s names no merge", t.Key)
|
||||
}
|
||||
for _, m := range t.Merges {
|
||||
if err := checkRepository(m.Repository); err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Merge == "" {
|
||||
return fmt.Errorf("delivery: the take-in event %s names a merge of %s without its commit", t.Key, m.Repository)
|
||||
}
|
||||
}
|
||||
if t.Since.IsZero() {
|
||||
return fmt.Errorf("delivery: the take-in event %s does not say since when it waits", t.Key)
|
||||
}
|
||||
switch t.What {
|
||||
case TakeInRevision:
|
||||
if !strings.HasPrefix(t.Key, "m.") {
|
||||
return fmt.Errorf("delivery: a revision's take-in key is its oldest merge's m.<sequence>, not %q", t.Key)
|
||||
}
|
||||
if t.Kind == TakeInPending && t.WaitsFor == "" {
|
||||
return fmt.Errorf("delivery: a pending revision %s names the indexer build it waits for", t.Key)
|
||||
}
|
||||
case TakeInGroup:
|
||||
if t.Group == "" {
|
||||
return fmt.Errorf("delivery: a group's take-in event %s names its group", t.Key)
|
||||
}
|
||||
if t.Key != GroupTakeInKey(t.Group) {
|
||||
return fmt.Errorf("delivery: the take-in key of the group %q is %s, not %s", t.Group, GroupTakeInKey(t.Group), t.Key)
|
||||
}
|
||||
if t.WaitsFor != "" {
|
||||
return fmt.Errorf("delivery: a group being gathered waits for its end, not for an indexer build")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("delivery: a take-in event's what %q is not revision or group", t.What)
|
||||
}
|
||||
if t.Kind != TakeInTaken && (t.By != "" || t.Why != "" || t.Dropped) {
|
||||
return fmt.Errorf("delivery: only a taken event names the hand act that ended a wait")
|
||||
}
|
||||
if (t.Why != "" || t.Dropped) && t.By == "" {
|
||||
return fmt.Errorf("delivery: a hand act's take-in event names who acted (by)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeTakeIn is t as JSON at Version; it refuses what its kind and what do not carry.
|
||||
func EncodeTakeIn(t TakeIn) ([]byte, error) {
|
||||
if err := t.check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.V = delivery.Version
|
||||
return json.Marshal(t)
|
||||
}
|
||||
|
||||
// DecodeTakeIn reads a take-in event's body; it refuses an unknown version (ErrUnknownVersion) and what EncodeTakeIn
|
||||
// refuses.
|
||||
func DecodeTakeIn(data []byte) (TakeIn, error) {
|
||||
var t TakeIn
|
||||
if err := decode(data, &t); err != nil {
|
||||
return TakeIn{}, err
|
||||
}
|
||||
if err := t.check(); err != nil {
|
||||
return TakeIn{}, err
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// SameTakeIn says whether two take-in bodies are the same value.
|
||||
func SameTakeIn(a, b TakeIn) bool { return reflect.DeepEqual(a, b) }
|
||||
|
||||
// PendingWait is one wait the indexer's tool indexer_pending answers (novox/hq ADR 0633 §5), as the view reads it to
|
||||
// name a module waiting for its revision's indexer. Key and What are a take-in event's: a revision under its oldest
|
||||
// merge's key, waiting for the indexer build WaitsFor (empty while it is worked out), or a group being gathered under
|
||||
// its group key, with Members every approved member and Merges those held so far. A revision that waits behind a
|
||||
// group being gathered names that group's key in Behind. Keys are the pending keys of its merges, in Merges' order (indexer_drop_pending drops one). Holds are the modules it holds back: every module of its
|
||||
// merges' trees. Said is the last take-in event said of it (`take-in.<kind>.<key>`) and SaidAt when.
|
||||
type PendingWait struct {
|
||||
Key string `json:"key"`
|
||||
What string `json:"what"`
|
||||
Merges []Merge `json:"merges"`
|
||||
Keys []string `json:"keys"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Members []delivery.Head `json:"members,omitempty"`
|
||||
Behind string `json:"behind,omitempty"`
|
||||
WaitsFor string `json:"waits-for,omitempty"`
|
||||
Since time.Time `json:"since"`
|
||||
Holds []string `json:"holds"`
|
||||
Said string `json:"said,omitempty"`
|
||||
SaidAt time.Time `json:"said-at,omitzero"`
|
||||
}
|
||||
|
||||
// PendingAnswer is indexer_pending's answer: every wait, oldest first, and the pending keys that do not read.
|
||||
type PendingAnswer struct {
|
||||
Waits []PendingWait `json:"waits"`
|
||||
Unread []string `json:"unread,omitempty"`
|
||||
}
|
||||
|
||||
// DecodePendingAnswer reads indexer_pending's answer, refusing a wait that is not a revision or a group, or names no
|
||||
// key.
|
||||
func DecodePendingAnswer(data []byte) (PendingAnswer, error) {
|
||||
var a PendingAnswer
|
||||
if err := json.Unmarshal(data, &a); err != nil {
|
||||
return a, fmt.Errorf("delivery: not an answer of indexer_pending: %w", err)
|
||||
}
|
||||
for _, w := range a.Waits {
|
||||
if w.Key == "" || (w.What != TakeInRevision && w.What != TakeInGroup) {
|
||||
return a, fmt.Errorf("delivery: indexer_pending answers a wait %q of %q, not a revision or a group", w.Key, w.What)
|
||||
}
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package takein
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// sampleTakeIns are a pending revision, a group being gathered and a hand take-in's taken (1585/T2).
|
||||
func sampleTakeIns(since time.Time) []TakeIn {
|
||||
m := []Merge{{Repository: "novox/mesh-delivery", Number: 9, Merge: "eeee"}}
|
||||
return []TakeIn{
|
||||
{Kind: TakeInPending, Key: PendingKey(12), What: TakeInRevision, Merges: m, WaitsFor: "src2:abc", Since: since},
|
||||
{Kind: TakeInPending, Key: GroupTakeInKey("feat/take-in"), What: TakeInGroup, Merges: m, Group: "feat/take-in",
|
||||
Members: []delivery.Head{{Repository: "novox/mesh-delivery", Number: 9, Head: "ffff"}, {Repository: "novox/mesh-sdk", Number: 37, Head: "dddd"}}, Since: since},
|
||||
{Kind: TakeInTaken, Key: PendingKey(12), What: TakeInRevision, Merges: m, WaitsFor: "src2:abc", Since: since,
|
||||
By: "lead-47@shanks", Why: "the named indexer can never run"},
|
||||
}
|
||||
}
|
||||
|
||||
// TestTakeInEvents holds the indexer's take-in contract, moved from mesh-sdk go/delivery with its tests (1585/T2, T12).
|
||||
func TestTakeInEvents(t *testing.T) {
|
||||
// The take-in events, `take-in.<pending|taken>.<key>`, a group's key with its dots and no subject wildcard.
|
||||
if PendingKey(42) != "m.00000000000000000042" || GroupTakeInKey("feat/a.b *>") != "g.feat/a.b___" {
|
||||
t.Errorf("take-in keys %q %q", PendingKey(42), GroupTakeInKey("feat/a.b *>"))
|
||||
}
|
||||
for _, c := range []struct{ kind, key string }{{TakeInPending, PendingKey(7)}, {TakeInTaken, GroupTakeInKey("feat/a.b")}} {
|
||||
ev, err := TakeInEvent(c.kind, c.key)
|
||||
if err != nil || ev != "take-in."+c.kind+"."+c.key {
|
||||
t.Fatalf("take-in event = %q, %v", ev, err)
|
||||
}
|
||||
if k, key, err := ParseTakeInEvent(ev); err != nil || k != c.kind || key != c.key {
|
||||
t.Errorf("parse take-in event %q = %q %q %v", ev, k, key, err)
|
||||
}
|
||||
}
|
||||
for _, bad := range []string{"", "take-in.pending", "take-in.held.m.1", "take-in.taken.x.1", "take-in.taken.m..1", "forge.taken.m.1"} {
|
||||
if _, _, err := ParseTakeInEvent(bad); err == nil {
|
||||
t.Errorf("parse take-in event %q accepted", bad)
|
||||
}
|
||||
}
|
||||
// The bodies round-trip: a pending revision, a group being gathered, and a hand take-in's taken with its by.
|
||||
since := time.Date(2026, 11, 4, 23, 0, 0, 0, time.UTC)
|
||||
for _, ti := range sampleTakeIns(since) {
|
||||
data, err := EncodeTakeIn(ti)
|
||||
if err != nil {
|
||||
t.Fatalf("take-in %s %s: %v", ti.Kind, ti.What, err)
|
||||
}
|
||||
ti.V = delivery.Version
|
||||
if got, err := DecodeTakeIn(data); err != nil || !SameTakeIn(got, ti) {
|
||||
t.Errorf("take-in %s round trip: %+v %v", ti.Key, got, err)
|
||||
}
|
||||
}
|
||||
for _, in := range []string{`{"key":"m.1"}`, `{"v":2,"key":"m.1"}`} {
|
||||
if _, err := DecodeTakeIn([]byte(in)); !errors.Is(err, delivery.ErrUnknownVersion) {
|
||||
t.Errorf("decode of %s = %v, want delivery.ErrUnknownVersion", in, err)
|
||||
}
|
||||
}
|
||||
// A body that does not hold is refused, naming why.
|
||||
for _, c := range []struct {
|
||||
name, want string
|
||||
i int
|
||||
mut func(*TakeIn)
|
||||
}{
|
||||
{"group take-in without its group", "names its group", 1, func(t *TakeIn) { t.Group = "" }},
|
||||
{"group take-in under another group's key", "g.other", 1, func(t *TakeIn) { t.Key = GroupTakeInKey("other") }},
|
||||
{"pending revision waiting for no build", "waits for", 0, func(t *TakeIn) { t.WaitsFor = "" }},
|
||||
{"pending with a hand act", "only a taken event", 0, func(t *TakeIn) { t.By = "someone@node" }},
|
||||
{"hand act without its by", "by", 2, func(t *TakeIn) { t.By = "" }},
|
||||
{"take-in without merges", "no merge", 0, func(t *TakeIn) { t.Merges = nil }},
|
||||
{"take-in without since", "since", 0, func(t *TakeIn) { t.Since = time.Time{} }},
|
||||
{"a merge of no repository", "owner", 0, func(t *TakeIn) { t.Merges = []Merge{{Repository: "nope", Merge: "m"}} }},
|
||||
} {
|
||||
ti := sampleTakeIns(since)[c.i]
|
||||
c.mut(&ti)
|
||||
if _, err := EncodeTakeIn(ti); err == nil || !strings.Contains(err.Error(), c.want) {
|
||||
t.Errorf("%s: %v, want a refusal naming %q", c.name, err, c.want)
|
||||
}
|
||||
}
|
||||
// indexer_pending's answer reads back; a wait of neither kind is refused.
|
||||
pa := PendingAnswer{Waits: []PendingWait{{Key: PendingKey(12), What: TakeInRevision, Merges: sampleTakeIns(since)[0].Merges,
|
||||
Keys: []string{PendingKey(12)}, WaitsFor: "src2:abc", Since: since, Holds: []string{"mesh-delivery-indexer"}, Said: "take-in.pending." + PendingKey(12), SaidAt: since}}}
|
||||
if data, err := json.Marshal(pa); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if got, err := DecodePendingAnswer(data); err != nil || !reflect.DeepEqual(got, pa) {
|
||||
t.Errorf("pending answer round trip: %+v %v", got, err)
|
||||
}
|
||||
if _, err := DecodePendingAnswer([]byte(`{"waits":[{"key":"m.1","what":"held"}]}`)); err == nil {
|
||||
t.Error("a pending answer with a wait of neither kind accepted")
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"mesh-delivery-view/internal/view"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||
@@ -340,10 +341,10 @@ func main() {
|
||||
}
|
||||
|
||||
// pendingOf asks the indexer's tool indexer_pending (declared in invokes): what it holds back and why (ADR 0633 §5).
|
||||
func pendingOf() (delivery.PendingAnswer, error) {
|
||||
func pendingOf() (takein.PendingAnswer, error) {
|
||||
raw, err := stdio.Ask(delivery.IndexerModule+".indexer_pending", map[string]any{})
|
||||
if err != nil {
|
||||
return delivery.PendingAnswer{}, err
|
||||
return takein.PendingAnswer{}, err
|
||||
}
|
||||
return delivery.DecodePendingAnswer(raw)
|
||||
return takein.DecodePendingAnswer(raw)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||
@@ -106,10 +107,10 @@ func tiersToolSaysEachModulesWait(t *testing.T) {
|
||||
// wait's since; a held module the revision does not move is named in the first tier with that wait alone, a group's wait
|
||||
// says its group, and a tool that cannot be asked is said and names no module.
|
||||
heldSince := time.Date(2026, 11, 4, 22, 0, 0, 0, time.UTC)
|
||||
r.Pending = func() (delivery.PendingAnswer, error) {
|
||||
return delivery.PendingAnswer{Waits: []delivery.PendingWait{
|
||||
{Key: "m.00000000000000000042", What: delivery.TakeInRevision, WaitsFor: "src2:abc", Since: heldSince, Holds: []string{"web", "newmod"}},
|
||||
{Key: "g.feat", What: delivery.TakeInGroup, Group: "feat", Since: heldSince.Add(time.Hour), Holds: []string{"lib"}}}}, nil
|
||||
r.Pending = func() (takein.PendingAnswer, error) {
|
||||
return takein.PendingAnswer{Waits: []takein.PendingWait{
|
||||
{Key: "m.00000000000000000042", What: takein.TakeInRevision, WaitsFor: "src2:abc", Since: heldSince, Holds: []string{"web", "newmod"}},
|
||||
{Key: "g.feat", What: takein.TakeInGroup, Group: "feat", Since: heldSince.Add(time.Hour), Holds: []string{"lib"}}}}, nil
|
||||
}
|
||||
out, err = run(t, r, view.TiersTool, map[string]any{"revision": float64(9)})
|
||||
held, _ := out.(view.Tiers)
|
||||
@@ -126,7 +127,7 @@ func tiersToolSaysEachModulesWait(t *testing.T) {
|
||||
if nm := held.Tiers[0][len(held.Tiers[0])-1]; nm.Module != "newmod" || len(nm.Waits) != 1 || nm.Waits[0].Edge != view.TakeInEdge {
|
||||
t.Errorf("the first tier = %+v, want newmod, which the revision does not move, named with its take-in wait alone", held.Tiers[0])
|
||||
}
|
||||
r.Pending = func() (delivery.PendingAnswer, error) { return delivery.PendingAnswer{}, errors.New("no answer") }
|
||||
r.Pending = func() (takein.PendingAnswer, error) { return takein.PendingAnswer{}, errors.New("no answer") }
|
||||
if out, err = run(t, r, view.TiersTool, map[string]any{"revision": float64(9)}); err != nil || !strings.Contains(out.(view.Tiers).Pending, "no answer") ||
|
||||
strings.Contains(fmt.Sprint(out.(view.Tiers).Tiers), view.TakeInEdge) {
|
||||
t.Errorf("view_tiers with no answer of indexer_pending = %+v, %v", out, err)
|
||||
|
||||
@@ -3,7 +3,7 @@ module mesh-delivery-view
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8
|
||||
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
|
||||
)
|
||||
@@ -21,6 +21,11 @@ require (
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
)
|
||||
|
||||
require git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein v0.0.0-00010101000000-000000000000
|
||||
git.novox.be/novox/mesh-delivery/sdk v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/sdk => ../../sdk
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein => ../mesh-delivery-indexer/takein
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 h1:XEbIVImp6yGjA91rp0iZBoDdL5KfamjOuDLwWvqh0hc=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
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=
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
@@ -59,7 +60,7 @@ type Reader struct {
|
||||
// ProvisionsState the provisioner's keys: what view_tiers reads (tiers.go). Unset reads none, said in the answer.
|
||||
GraphState, SourceState, ProvisionsState StateRead
|
||||
// Pending reads the indexer's waits through its tool indexer_pending (ADR 0633 §5); unset names no module held back.
|
||||
Pending func() (delivery.PendingAnswer, error)
|
||||
Pending func() (takein.PendingAnswer, error)
|
||||
// BuildLog reads the builder's log from a time (stdio.Log(delivery.BuildLogRead).Since); unset reads none.
|
||||
BuildLog LogSince
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/graph"
|
||||
@@ -227,7 +228,7 @@ func (r Reader) heldBack(out *Tiers) (map[string]TierWait, []string) {
|
||||
}
|
||||
for _, w := range a.Waits {
|
||||
words := "the indexer holds back " + w.Key + ", which waits for the indexer build " + w.WaitsFor
|
||||
if w.What == delivery.TakeInGroup {
|
||||
if w.What == takein.TakeInGroup {
|
||||
words = "the indexer gathers the group " + w.Group + " and its end has not come"
|
||||
} else if w.Behind != "" {
|
||||
words = "the indexer holds back " + w.Key + " behind " + w.Behind
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/provisions"
|
||||
@@ -379,10 +380,10 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
|
||||
// The indexer's stream take-in (ADR 0633 §5): a revision or a group it holds back is a wait, ended by take-in.taken. The
|
||||
// durable consumer <node>_<module> is made by the controller from this module's consumes; the whole stream is heard at
|
||||
// the start, so a wait that began while the watchman was away is read with its own since.
|
||||
switch tc, err := cred.EventConsumer(ctx, js, catalogue.StreamBusName(delivery.TakeInStream)); {
|
||||
switch tc, err := cred.EventConsumer(ctx, js, catalogue.StreamBusName(takein.TakeInStream)); {
|
||||
case err != nil:
|
||||
logger.Printf("the consumer %s_%s on %s does not exist yet, so no take-in wait is said until the watchman starts again: %v",
|
||||
cred.Node, cred.Module, catalogue.StreamBusName(delivery.TakeInStream), err)
|
||||
cred.Node, cred.Module, catalogue.StreamBusName(takein.TakeInStream), err)
|
||||
default:
|
||||
// The replay has caught up when a message says none is pending after it, or when the consumer had none pending at its
|
||||
// bind (the stream holds nothing it has not acknowledged): the waits past their bound are said then, not before.
|
||||
|
||||
@@ -3,12 +3,13 @@ module mesh-delivery-watchman
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104221203-10c91fba4a28
|
||||
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
|
||||
)
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein v0.0.0-00010101000000-000000000000
|
||||
github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
github.com/klauspost/compress v1.20.1 // indirect
|
||||
@@ -22,3 +23,5 @@ require (
|
||||
)
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun => ../mesh-delivery-builder/buildrun
|
||||
|
||||
replace git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein => ../mesh-delivery-indexer/takein
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8 h1:XEbIVImp6yGjA91rp0iZBoDdL5KfamjOuDLwWvqh0hc=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261103221822-85ba0a9a70d8/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104221203-10c91fba4a28 h1:Zoi+myMvKaRYdM8w/96W6GnoAiTvgwvEF1QPA6HelaU=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104221203-10c91fba4a28/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
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.8.0-default-no-op h1:1BOWQJweNyvZMlpAHXGLiZQn9S+QXGcz3xh94lC0w6E=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.8.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/provisions"
|
||||
)
|
||||
@@ -36,11 +37,11 @@ func TestTheWatchmansFindings(t *testing.T) {
|
||||
}{
|
||||
{"ADR 0633 §5 (1585/T21): a revision waiting for an indexer build is a finding past 60 minutes from its since, not within; a group past 30", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
|
||||
w.TakeInCaughtUp() // a live message, heard after the replay
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInPending, revKey), takeInBody(t, delivery.TakeInPending, revKey, t0))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInPending, revKey), takeInBody(t, takein.TakeInPending, revKey, t0))
|
||||
if o.ttls[signalKey("take-in", revKey)] != TakeInRevisionBound || len(o.findings) != 0 {
|
||||
t.Fatalf("a revision's wait: ttls %v, findings %v", o.ttls, o.findings)
|
||||
}
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInPending, groupKey), takeInBody(t, delivery.TakeInPending, groupKey, t0))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInPending, groupKey), takeInBody(t, takein.TakeInPending, groupKey, t0))
|
||||
if o.ttls[signalKey("take-in", groupKey)] != TakeInGroupBound {
|
||||
t.Fatalf("a group's wait: ttls %v", o.ttls)
|
||||
}
|
||||
@@ -53,41 +54,41 @@ func TestTheWatchmansFindings(t *testing.T) {
|
||||
{"ADR 0633 §5 (1585/T21): the take-in.taken of a named indexer, a hand take-in or a drop clears the finding", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
|
||||
w.TakeInCaughtUp() // a live message, heard after the replay
|
||||
for _, by := range []string{"", "operator@shanks"} {
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInPending, revKey), takeInBody(t, delivery.TakeInPending, revKey, t0))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInPending, revKey), takeInBody(t, takein.TakeInPending, revKey, t0))
|
||||
expire(w, c, signalKey("take-in", revKey), TakeInRevisionBound)
|
||||
if len(o.findings) != 1 {
|
||||
t.Fatalf("findings past the bound: %v", o.findings)
|
||||
}
|
||||
taken, err := delivery.EncodeTakeIn(delivery.TakeIn{Kind: delivery.TakeInTaken, Key: revKey, What: delivery.TakeInRevision, Since: t0,
|
||||
Merges: []delivery.GroupMerge{{Repository: "novox/a", Merge: "c0ffee"}}, By: by, Why: map[bool]string{true: "dropped", false: ""}[by != ""], Dropped: by != ""})
|
||||
taken, err := takein.EncodeTakeIn(takein.TakeIn{Kind: takein.TakeInTaken, Key: revKey, What: takein.TakeInRevision, Since: t0,
|
||||
Merges: []takein.Merge{{Repository: "novox/a", Merge: "c0ffee"}}, By: by, Why: map[bool]string{true: "dropped", false: ""}[by != ""], Dropped: by != ""})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInTaken, revKey), taken)
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInTaken, revKey), taken)
|
||||
c.t = t0
|
||||
}
|
||||
}, map[string]string{}},
|
||||
{"ADR 0633 §5 (1585/T21): a group's take-in.taken ends its 30 minutes, and the revision then pending under m.<seq> starts its own 60", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
|
||||
w.TakeInCaughtUp() // a live message, heard after the replay
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInPending, groupKey), takeInBody(t, delivery.TakeInPending, groupKey, t0))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInPending, groupKey), takeInBody(t, takein.TakeInPending, groupKey, t0))
|
||||
expire(w, c, signalKey("take-in", groupKey), 25*time.Minute)
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInTaken, groupKey), takeInBody(t, delivery.TakeInTaken, groupKey, t0))
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInPending, revKey), takeInBody(t, delivery.TakeInPending, revKey, c.t))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInTaken, groupKey), takeInBody(t, takein.TakeInTaken, groupKey, t0))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInPending, revKey), takeInBody(t, takein.TakeInPending, revKey, c.t))
|
||||
if len(o.findings) != 0 || o.ttls[signalKey("take-in", revKey)] != TakeInRevisionBound {
|
||||
t.Fatalf("after the hand-over: findings %v, ttls %v", o.findings, o.ttls)
|
||||
}
|
||||
expire(w, c, signalKey("take-in", revKey), TakeInRevisionBound)
|
||||
}, map[string]string{FindingKey(ScopeDelivery, revKey, KindTakeInWaits): Warning}},
|
||||
{"ADR 0633 §5 (1585/T21): a pending event heard again at the start, since past its bound, is said once the replay has caught up, not before", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInPending, revKey), takeInBody(t, delivery.TakeInPending, revKey, t0.Add(-2*time.Hour)))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInPending, revKey), takeInBody(t, takein.TakeInPending, revKey, t0.Add(-2*time.Hour)))
|
||||
if len(o.findings) != 0 || len(o.pulses) != 0 {
|
||||
t.Fatalf("a replayed old wait before the replay caught up: findings %v, pulses %v", o.findings, o.pulses)
|
||||
}
|
||||
w.TakeInCaughtUp()
|
||||
}, map[string]string{FindingKey(ScopeDelivery, revKey, KindTakeInWaits): Warning}},
|
||||
{"ADR 0633 §5 (1585/T21): a replayed pending followed by its taken never raises", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInPending, revKey), takeInBody(t, delivery.TakeInPending, revKey, t0.Add(-2*time.Hour)))
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInTaken, revKey), takeInBody(t, delivery.TakeInTaken, revKey, t0.Add(-2*time.Hour)))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInPending, revKey), takeInBody(t, takein.TakeInPending, revKey, t0.Add(-2*time.Hour)))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInTaken, revKey), takeInBody(t, takein.TakeInTaken, revKey, t0.Add(-2*time.Hour)))
|
||||
w.TakeInCaughtUp()
|
||||
if o.findingWrites != 0 || len(o.pulses) != 0 {
|
||||
t.Fatalf("a wait that ended in the replay wrote %d findings and left pulses %v", o.findingWrites, o.pulses)
|
||||
@@ -95,17 +96,17 @@ func TestTheWatchmansFindings(t *testing.T) {
|
||||
}, map[string]string{}},
|
||||
{"ADR 0633 §5 (1585/T21): a drop clears the wait, and the merges left under it said pending again with the same since are raised from it", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
|
||||
w.TakeInCaughtUp() // a live message, heard after the replay
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInPending, revKey), takeInBody(t, delivery.TakeInPending, revKey, t0))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInPending, revKey), takeInBody(t, takein.TakeInPending, revKey, t0))
|
||||
expire(w, c, signalKey("take-in", revKey), TakeInRevisionBound)
|
||||
if len(o.findings) != 1 {
|
||||
t.Fatalf("findings past the bound: %v", o.findings)
|
||||
}
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInTaken, revKey), takeInBody(t, delivery.TakeInTaken, revKey, t0))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInTaken, revKey), takeInBody(t, takein.TakeInTaken, revKey, t0))
|
||||
if len(o.findings) != 0 {
|
||||
t.Fatalf("the drop left %v", o.findings)
|
||||
}
|
||||
next := "m.00000000000000000043"
|
||||
w.TakeIn(takeInEvent(t, delivery.TakeInPending, next), takeInBody(t, delivery.TakeInPending, next, t0))
|
||||
w.TakeIn(takeInEvent(t, takein.TakeInPending, next), takeInBody(t, takein.TakeInPending, next, t0))
|
||||
}, map[string]string{FindingKey(ScopeDelivery, "m.00000000000000000043", KindTakeInWaits): Warning}},
|
||||
{"ADR 0578 §1: a module whose value carries a refusal of its manifest is a finding until it is accepted", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
|
||||
value := func(verdict string, problems ...string) []byte {
|
||||
@@ -609,7 +610,7 @@ const (
|
||||
|
||||
func takeInEvent(t *testing.T, kind, key string) string {
|
||||
t.Helper()
|
||||
e, err := delivery.TakeInEvent(kind, key)
|
||||
e, err := takein.TakeInEvent(kind, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -619,14 +620,14 @@ func takeInEvent(t *testing.T, kind, key string) string {
|
||||
// takeInBody is a take-in event's body for a key: a group's when it starts with g.
|
||||
func takeInBody(t *testing.T, kind, key string, since time.Time) []byte {
|
||||
t.Helper()
|
||||
body := delivery.TakeIn{Kind: kind, Key: key, What: delivery.TakeInRevision, Since: since,
|
||||
Merges: []delivery.GroupMerge{{Repository: "novox/a", Number: 1, Merge: "c0ffee"}}}
|
||||
body := takein.TakeIn{Kind: kind, Key: key, What: takein.TakeInRevision, Since: since,
|
||||
Merges: []takein.Merge{{Repository: "novox/a", Number: 1, Merge: "c0ffee"}}}
|
||||
if key[0] == 'g' {
|
||||
body.What, body.Group = delivery.TakeInGroup, "feat/take-in"
|
||||
} else if kind == delivery.TakeInPending {
|
||||
body.What, body.Group = takein.TakeInGroup, "feat/take-in"
|
||||
} else if kind == takein.TakeInPending {
|
||||
body.WaitsFor = "src2:abc"
|
||||
}
|
||||
b, err := delivery.EncodeTakeIn(body)
|
||||
b, err := takein.EncodeTakeIn(body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||
)
|
||||
|
||||
// KindTakeInWaits is a source revision or a delivery group the indexer holds back and has not taken in within its bound
|
||||
@@ -28,12 +28,12 @@ const takeInRow = "take-in"
|
||||
// a pending one starts the wait for its key, whose bound runs from the event's since, and a taken one ends it. A group's
|
||||
// taken event ends the group's wait only: the revision it becomes is its own key and starts its own, longer wait.
|
||||
func (w *Watchman) TakeIn(event string, body []byte) {
|
||||
kind, key, err := delivery.ParseTakeInEvent(event)
|
||||
kind, key, err := takein.ParseTakeInEvent(event)
|
||||
if err != nil {
|
||||
w.say("a take-in event is left: %v", err)
|
||||
return
|
||||
}
|
||||
t, err := delivery.DecodeTakeIn(body)
|
||||
t, err := takein.DecodeTakeIn(body)
|
||||
if err != nil {
|
||||
w.say("the body of %s cannot be read, left: %v", event, err)
|
||||
return
|
||||
@@ -41,14 +41,14 @@ func (w *Watchman) TakeIn(event string, body []byte) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
finding := FindingKey(ScopeDelivery, key, KindTakeInWaits)
|
||||
if kind == delivery.TakeInTaken {
|
||||
if kind == takein.TakeInTaken {
|
||||
delete(w.takeInHeld, key)
|
||||
w.unsignal(takeInRow, key)
|
||||
w.unfind(finding)
|
||||
return
|
||||
}
|
||||
bound, what := TakeInRevisionBound, "a source revision waits for the indexer build "+t.WaitsFor
|
||||
if t.What == delivery.TakeInGroup {
|
||||
if t.What == takein.TakeInGroup {
|
||||
bound, what = TakeInGroupBound, "a delivery group is gathered and its end has not come"
|
||||
}
|
||||
var merges []string
|
||||
|
||||
Reference in New Issue
Block a user