From 8c3916bd7bae87083d66d48dedac8cb144743940 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 13:28:44 +0100 Subject: [PATCH] DLV-C3, DLV-C6 (tag-wanted): the checker's whole-module test hears tag-wanted at the definition's subject and builds on the published A case of the existing group test (no new integration test): the bed's streams are the manifest's (checks with preview.written, asks with verdict and tag-wanted at the mesh-delivery-checker definition's subjects) and the shared events stream with the registry seats' published. A group whose mesh-sdk member has no preview yet is read at its head, says tag-wanted whose body decodes strictly into the definition's type, is not judged before the Go seat's published, and then builds mesh-delivery against v0.1.15-feat-x.1. The verdict half of DLV-C6 follows DLV-C2. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../mesh-delivery-checker/integration_test.go | 88 ++++++++++++++++++- 1 file changed, 85 insertions(+), 3 deletions(-) diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go index 823deb6c..0fdf3095 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go @@ -3,6 +3,7 @@ package main import ( + "bytes" "context" "encoding/json" "errors" @@ -22,6 +23,9 @@ import ( "github.com/nats-io/nats.go/jetstream" gobuild "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep" + "git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry" + "git.novox.be/novox/mesh-contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-contracts/go/seats/npmpackageregistry" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" "git.novox.be/novox/mesh-sdk/go/announce" "git.novox.be/novox/mesh-sdk/go/delivery" @@ -91,6 +95,8 @@ type fakeBuildModules struct { beside map[string][]string // names are the nodes' names the last check ask carried, for the names check (ADR 0567 §12). names []string + // packages are, by repository, the package versions the last Go check ask of it carried (StepAsk.Packages). + packages map[string]delivery.PackageVersions } func (f *fakeBuildModules) count(tool string) int { @@ -197,6 +203,12 @@ func (f *fakeBuildModules) serve(t *testing.T, module string, kept jetstream.Key if a.Check != nil { f.mu.Lock() f.names = a.Check.NodeNames + if repository, _ := a.Check.Fetch["repository"].(string); a.Language == delivery.AskGo { + if f.packages == nil { + f.packages = map[string]delivery.PackageVersions{} + } + f.packages[repository] = a.Packages + } f.mu.Unlock() } return gobuild.RunAsk(ctx, steps, a) @@ -220,6 +232,15 @@ func (b *bed) serveForge(t *testing.T, nc *nats.Conn) { var a map[string]string _ = json.Unmarshal(m.Data, &a) switch { + case tool == "gitea_get_file" && strings.HasSuffix(a["path"], "module.json"): + // A manifest at a member's head (design 51 *Tagged versions*): the bed's own, else the forge's 404. + if content, ok := b.heads.Load(a["owner"] + "/" + a["repo"] + " " + a["path"] + " " + a["ref"]); ok { + answer, _ := json.Marshal(map[string]any{"result": map[string]any{"file": map[string]any{"content": content}}, "error": "", "node": "n0"}) + _ = m.Respond(answer) + return + } + answer, _ := json.Marshal(map[string]any{"result": nil, "error": "Gitea API /repos/x/contents/" + a["path"] + ": 404 Not Found", "node": "n0"}) + _ = m.Respond(answer) case tool == "gitea_get_file" && b.stalePin.Load(): content := strings.Replace(string(mustRead(t, "forge-get-file-go-mod")), "cb54756c3865", "9aaa5c80ac5c", 1) var result map[string]any @@ -252,6 +273,8 @@ func mustRead(t *testing.T, name string) []byte { type bed struct { stalePin atomic.Bool + // heads are the manifests the forge holds at a member's head, by " ". + heads sync.Map ctx context.Context js jetstream.JetStream buckets map[string]jetstream.KeyValue @@ -390,8 +413,20 @@ func startBed(t *testing.T) *bed { if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: buildrun.DeadLettersStream, Subjects: []string{"mesh.events.dead.>"}}); err != nil { t.Fatal(err) } - // The checker's stream of events, which the indexer and the forge bridge hear. - if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "CHECKS", Subjects: []string{"mesh.stream.mesh-delivery-checker.event.>"}}); err != nil { + // The checker's streams as its manifest declares them (ADR 0619, ADR 0620): checks, which the indexer and the forge + // bridge hear, and asks, with verdict and tag-wanted at the subjects the mesh-delivery-checker definition names. + if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "CHECKS", Subjects: []string{meshdeliverychecker.HolderSubjectPreviewWritten(delivery.CheckerModule)}}); err != nil { + t.Fatal(err) + } + if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "ASKS", Subjects: []string{meshdeliverychecker.HolderSubjectVerdict(delivery.CheckerModule), + meshdeliverychecker.HolderSubjectTagWanted(delivery.CheckerModule)}}); err != nil { + t.Fatal(err) + } + // The shared events stream with the package registry seats' published, and the checker's durable consumer of it. + if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: process.EventsStream, Subjects: []string{gopackageregistry.SubjectPublished, npmpackageregistry.SubjectPublished}}); err != nil { + t.Fatal(err) + } + if _, err := js.CreateOrUpdateConsumer(ctx, process.EventsStream, jetstream.ConsumerConfig{Durable: "n1_mesh-delivery-checker", AckPolicy: jetstream.AckExplicitPolicy}); err != nil { t.Fatal(err) } for name, history := range map[string]int{delivery.CheckerVerdictsBucket: delivery.CheckerVerdictsHistory, delivery.CheckerKeptBucket: delivery.CheckerKeptHistory, @@ -830,6 +865,51 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing. if v := b.verdict(t, key, ended); v.State != delivery.VerdictPass { t.Fatalf("verdict = %+v", v) } + + // DLV-C3, DLV-C6 (design 51 *Tagged versions*, ADR 0607 §3, §5, ADR 0620): a group whose mesh-sdk member declares a Go + // package, read at its head with no preview yet, says tag-wanted on the checker's stream asks, at the subject and in the body the mesh-delivery-checker + // definition names; once the Go seat's published of the tagged version is heard, the other member is built against it. + const sdkRepo = "novox/mesh-sdk" + b.setTrunk(t, map[string]string{repo: trunk, sdkRepo: trunk}) + // The member carries no preview of its merge yet, so its manifest is read at its head (the package new in the group). + b.heads.Store(sdkRepo+" module.json "+head2, `{"module":"mesh-sdk","build":{"artifacts":[{"name":"go","kind":"package","language":"go","paths":["go"]}]}}`) + asks, err := b.js.OrderedConsumer(b.ctx, "ASKS", jetstream.OrderedConsumerConfig{FilterSubjects: []string{meshdeliverychecker.HolderSubjectTagWanted(delivery.CheckerModule)}}) + if err != nil { + t.Fatal(err) + } + sdk := pull(sdkRepo, 12, head2) + sdk.Modules, sdk.Fps = []string{"mesh-sdk"}, map[string]delivery.CandidateModule{"mesh-sdk": {FP2: "src2:" + strings.Repeat("e", 64)}} + key = b.candidate(t, "feat/x", pull(repo, 8, head1), sdk) + msg, err := asks.Next(jetstream.FetchMaxWait(20 * time.Second)) + if err != nil { + t.Fatalf("no tag-wanted on the stream asks: %v", err) + } + var wanted meshdeliverychecker.TagWantedHolderEvent + dec := json.NewDecoder(bytes.NewReader(msg.Data())) + dec.DisallowUnknownFields() + if err := dec.Decode(&wanted); err != nil { + t.Fatalf("tag-wanted %s is not the definition's body: %v", msg.Data(), err) + } + if want := (meshdeliverychecker.TagWantedHolderEvent{Repository: sdkRepo, Branch: "feat/x", Head: head2, Module: "mesh-sdk", Package: "go", Pull: 12}); wanted != want { + t.Errorf("tag-wanted = %+v, want %+v", wanted, want) + } + if v, err := b.buckets[delivery.CheckerVerdictsBucket].Get(b.ctx, key); err == nil && !strings.Contains(string(v.Value()), `"running"`) { + t.Fatalf("the group was judged before its tagged version was published: %s", v.Value()) + } + published, _ := json.Marshal(gopackageregistry.PublishedEvent{Holder: "gitea", Kind: "package", Name: "mesh-sdk/go", Commit: head2, Tag: "feat-x", Version: "0.1.15-feat-x.1", + Digest: "sha256:" + strings.Repeat("0", 64)}) + if _, err := b.js.Publish(b.ctx, gopackageregistry.SubjectPublished, published); err != nil { + t.Fatal(err) + } + if v := b.verdict(t, key, ended); v.State != delivery.VerdictPass { + t.Fatalf("the group's verdict = %+v", v) + } + b.fake.mu.Lock() + got := b.fake.packages[repo] + b.fake.mu.Unlock() + if got[check.SDKModulePath] != "v0.1.15-feat-x.1" { + t.Errorf("mesh-delivery was built against %v, want mesh-sdk's Go module at the group's tagged version v0.1.15-feat-x.1", got) + } } // triesStore is the tries bucket as the tools write it. @@ -938,7 +1018,9 @@ func TestATryIsJudgedAndItsEndWrittenWithNoStatusOwed(t *testing.T) { // the readable ones, which is what grants the forge module its read of owed (ADR 0495 §2-§3, ADR 0521; R4e finding R2-1). func TestTheCheckerSaysOwedReadableOnTheBus(t *testing.T) { // R2-1 b := startBed(t) - if _, err := b.js.CreateStream(b.ctx, jetstream.StreamConfig{Name: "EVENTS", Subjects: []string{"mesh.mod.*.event.>"}}); err != nil { + // The bed's shared events stream carries the registry seats' published; the module events go on it too. + if _, err := b.js.UpdateStream(b.ctx, jetstream.StreamConfig{Name: process.EventsStream, Subjects: []string{"mesh.mod.*.event.>", + gopackageregistry.SubjectPublished, npmpackageregistry.SubjectPublished}}); err != nil { t.Fatal(err) } b.start(t, nil)