From 84e617d3a9a864f1ed70093e7121e76ec0ba4960 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 17:00:25 +0100 Subject: [PATCH] =?UTF-8?q?Checker:=20off=20mesh-sdk=20go/delivery=20onto?= =?UTF-8?q?=20the=20seats'=20types=20and=20the=20clients=20(verdict,=20kep?= =?UTF-8?q?t,=20preview,=20owed,=20candidates,=20modules,=20manifests,=20p?= =?UTF-8?q?ackages,=20source,=20turns,=20haves),=20its=20holders=20and=20b?= =?UTF-8?q?uckets=20its=20own=20constants=20checked=20against=20its=20mani?= =?UTF-8?q?fest=20(D-11),=20the=20check=20ask's=20fetch=20typed=20with=20t?= =?UTF-8?q?he=20group=20package=20and=20the=20check=20answer's=20fetch=20r?= =?UTF-8?q?ead=20as=20the=20Delivery=20SDK's=20FetchAnswer,=20a=20merge=20?= =?UTF-8?q?with=20no=20program=20told=20by=20merge=20(B2g;=20ADR=200634=20?= =?UTF-8?q?=C2=A71,=20=C2=A72,=20=C2=A76;=20issue=201966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../cmd/mesh-delivery-checker-tools/main.go | 15 +- .../cmd/mesh-delivery-checker/bus.go | 8 +- .../cmd/mesh-delivery-checker/engines.go | 41 +-- .../mesh-delivery-checker/integration_test.go | 254 ++++++++++-------- .../cmd/mesh-delivery-checker/judge.go | 87 +++--- .../cmd/mesh-delivery-checker/judge_test.go | 49 ++-- .../cmd/mesh-delivery-checker/lazyturns.go | 10 +- .../cmd/mesh-delivery-checker/main.go | 71 ++--- .../cmd/mesh-delivery-checker/nodes_test.go | 2 +- .../cmd/mesh-delivery-checker/packages.go | 26 +- .../cmd/mesh-delivery-checker/tags.go | 11 +- .../cmd/mesh-delivery-checker/toolchains.go | 4 +- modules/mesh-delivery-checker/go.mod | 18 +- .../internal/check/adr548_test.go | 38 +-- .../internal/check/askagain.go | 34 +-- .../internal/check/binaries.go | 18 +- .../internal/check/binaries_test.go | 36 ++- .../internal/check/bounded.go | 4 +- .../internal/check/build_change.go | 4 +- .../internal/check/builds.go | 40 +-- .../internal/check/case.go | 46 ++-- .../internal/check/checker.go | 212 ++++++++------- .../internal/check/checker_test.go | 126 ++++----- .../internal/check/composition.go | 30 ++- .../internal/check/composition_test.go | 80 +++--- .../internal/check/digestform_test.go | 13 +- .../internal/check/engines.go | 29 +- .../internal/check/engines_test.go | 41 +-- .../internal/check/every_failure_test.go | 31 ++- .../internal/check/examiner_test.go | 187 +++++++------ .../internal/check/failure_words_test.go | 6 +- .../internal/check/gate_test.go | 10 +- .../internal/check/graph_cycle.go | 4 +- .../internal/check/judge.go | 198 +++++++------- .../internal/check/judged_test.go | 19 +- .../internal/check/kept.go | 25 +- .../internal/check/kept_test.go | 32 +-- .../internal/check/line.go | 76 +++--- .../internal/check/line_test.go | 49 ++-- .../internal/check/lostask.go | 19 +- .../internal/check/lostask_test.go | 57 ++-- .../internal/check/manifests_judge.go | 27 +- .../internal/check/names.go | 90 +++++++ .../internal/check/owed.go | 49 ++++ .../internal/check/pins.go | 13 +- .../internal/check/pins_test.go | 59 ++-- .../internal/check/preview.go | 19 +- .../internal/check/rank_test.go | 15 +- .../internal/check/read.go | 85 +++--- .../internal/check/read_test.go | 73 ++--- .../internal/check/renames.go | 11 +- .../internal/check/results.go | 58 +--- .../internal/check/seatdefs.go | 11 +- .../internal/check/series.go | 30 ++- .../internal/check/tagwanted.go | 23 +- .../internal/check/try.go | 27 +- .../internal/check/try_test.go | 16 +- .../internal/check/turns.go | 25 +- .../internal/check/verdictevent.go | 8 +- .../mesh-delivery-checker/manifest_test.go | 26 +- 60 files changed, 1499 insertions(+), 1226 deletions(-) create mode 100644 modules/mesh-delivery-checker/internal/check/names.go create mode 100644 modules/mesh-delivery-checker/internal/check/owed.go diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main.go index 369df35e..845be149 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker-tools/main.go @@ -10,13 +10,14 @@ import ( "sync/atomic" "time" + "mesh-delivery-checker/internal/check" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" stdio "git.novox.be/novox/mesh-sdk/go" - "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/views" - - "mesh-delivery-checker/internal/check" ) // reads counts the values this call read from the bus: the runtime launches the tools for one call, so the count is the call's. @@ -68,7 +69,7 @@ func (r runsOf) CommitOf(module string) string { if err != nil || len(raw) == 0 { return "" } - m, err := delivery.DecodeModule(raw) + m, err := meshcatalogclient.DecodeModule(raw) if err != nil { return "" } @@ -292,11 +293,11 @@ func main() { tries := stdio.State(check.TriesState) source := stdio.State("mesh-delivery-gate.source") trunks := func() (map[string]string, error) { - e, err := source.Get(delivery.SourceMainKey) + e, err := source.Get(meshdeliveryclient.SourceMainKey) if err != nil || e == nil { return nil, err } - s, err := delivery.DecodeSource(e.Value) + s, err := meshdeliveryclient.DecodeSource(e.Value) return s.Heads, err } r.Trunks = trunks @@ -330,7 +331,7 @@ func main() { tools := append(append(append(Tools(r), TryTools(trying)...), SeatTryTools(trying)...), RecoveryTools(r, written)...) // checker_first_views and checker_first_view_again: where the checker's first views of the indexer's and the assigner's // states stand, and asking one again by hand (ADR 0615, DLV-M2). - tools = append(tools, views.StatusTools(delivery.CheckerModule, "checker_first_views", "checker_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, time.Now)...) + tools = append(tools, views.StatusTools(check.CheckerModule, "checker_first_views", "checker_first_view_again", stdio.State(views.StatusState), meshhandactsclient.Recorder{Ask: stdio.Ask}, time.Now)...) if err := stdio.Serve("", logged(tools)); err != nil { fmt.Fprintf(os.Stderr, "[mesh-delivery-checker-tools] %v\n", err) os.Exit(1) diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/bus.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/bus.go index 62eccf09..52fe097a 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/bus.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/bus.go @@ -7,8 +7,8 @@ import ( "github.com/nats-io/nats.go/jetstream" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/process" "mesh-delivery-checker/internal/check" @@ -110,7 +110,7 @@ func follow(ctx context.Context, w jetstream.KeyWatcher, each func(jetstream.Key type manifestReader struct{ kv jetstream.KeyValue } func (m manifestReader) Manifest(ctx context.Context, digest string) (catalogue.Manifest, error) { - key, err := delivery.ManifestsKey(digest) + key, err := meshcatalogclient.ManifestsKey(digest) if err != nil { return catalogue.Manifest{}, err } @@ -118,11 +118,11 @@ func (m manifestReader) Manifest(ctx context.Context, digest string) (catalogue. if err != nil { return catalogue.Manifest{}, fmt.Errorf("manifests.%s: %w", key, err) } - kept, err := delivery.DecodeManifest(e.Value()) + kept, err := meshcatalogclient.DecodeManifest(e.Value()) if err != nil { return catalogue.Manifest{}, err } - return parseManifest(kept.JSON) + return parseManifest(kept.Manifest) } // parseManifest decodes a manifest the indexer kept, as every reader does (novox/hq ADR 0578 §3): judged by nothing here, diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/engines.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/engines.go index bb068040..48697172 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/engines.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/engines.go @@ -5,19 +5,24 @@ import ( "encoding/json" "errors" "fmt" - nodeengineseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" "sort" "sync" "time" + nodeengineseat "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + "github.com/nats-io/nats.go/jetstream" + "mesh-delivery-checker/internal/check" + nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-delivery/sdk/composer" - "git.novox.be/novox/mesh-sdk/go/buildstep" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // The checker's composition of a check on the bus (novox/hq ADR 0593 §7, §9; check.EngineCompositions): each placed @@ -65,18 +70,18 @@ func (p placedNodes) Placed() ([]string, error) { defer cancel() kv, err := p.kv(ctx) if err != nil { - return nil, fmt.Errorf("the state %s cannot be read: %w", delivery.PlacementBucket, err) + return nil, fmt.Errorf("the state %s cannot be read: %w", buildrun.PlacementBucket, err) } lister, err := kv.ListKeys(ctx) if errors.Is(err, jetstream.ErrNoKeysFound) { return nil, nil // a placement that places nothing } if err != nil { - return nil, fmt.Errorf("the state %s cannot be listed: %w", delivery.PlacementBucket, err) + return nil, fmt.Errorf("the state %s cannot be listed: %w", buildrun.PlacementBucket, err) } seen := map[string]bool{} for key := range lister.Keys() { - if node, _, _, err := delivery.ParsePlacementKey(key); err == nil { + if node, _, _, err := meshmoduleassignerclient.ParseKey(key); err == nil { seen[node] = true } } @@ -89,20 +94,20 @@ func (p placedNodes) Placed() ([]string, error) { } // seatSandbox runs a compose job on the Go build seat's check stream, as the checker's own ask, and answers the -// composer's verdict from the step's result (delivery.ComposeAnswer). +// composer's verdict from the step's result (buildstep.ComposeAnswer). type seatSandbox struct{ asker buildrun.Asker } -func (s seatSandbox) Compose(ctx context.Context, job delivery.ComposeJob) (composer.Verdict, error) { - ask := delivery.StepAsk{ID: buildstep.NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: delivery.CheckerModule, +func (s seatSandbox) Compose(ctx context.Context, job buildstep.ComposeJob) (composer.Verdict, error) { + ask := buildstep.StepAsk{ID: buildstep.NewID(), Kind: buildstep.AskCheck, Language: buildstep.AskGo, Asker: check.CheckerModule, At: time.Now().UTC(), Compose: &job} step, err := s.asker.Ask(ctx, ask) if err != nil { return composer.Verdict{}, err } - if step.State != delivery.StepPassed { + if step.State != buildstep.StepPassed { return composer.Verdict{}, fmt.Errorf("the compose ask %s ended %s: %s", ask.ID, step.State, step.Reason) } - var a delivery.ComposeAnswer + var a buildstep.ComposeAnswer if err := json.Unmarshal(step.Result, &a); err != nil { return composer.Verdict{}, fmt.Errorf("the compose ask %s answered no compose answer: %w", ask.ID, err) } @@ -114,17 +119,17 @@ func (s seatSandbox) Compose(ctx context.Context, job delivery.ComposeJob) (comp } // manifestVerdicts are the indexer's refusals of the trunk's manifests, by module, from its modules' values -// (delivery.Module.VerdictOf, ADR 0578 §3): what the sandbox's composer holds a refused module by. +// (meshcatalog.ModulesByModuleValue.VerdictOf, ADR 0578 §3): what the sandbox's composer holds a refused module by. type manifestVerdicts struct { mu sync.Mutex - by map[string]delivery.ManifestVerdict + by map[string]meshcatalog.VerdictsByDigestValue } -func (m *manifestVerdicts) put(module string, v *delivery.ManifestVerdict) { +func (m *manifestVerdicts) put(module string, v *meshcatalog.VerdictsByDigestValue) { m.mu.Lock() defer m.mu.Unlock() if m.by == nil { - m.by = map[string]delivery.ManifestVerdict{} + m.by = map[string]meshcatalog.VerdictsByDigestValue{} } if v == nil { delete(m.by, module) @@ -132,13 +137,13 @@ func (m *manifestVerdicts) put(module string, v *delivery.ManifestVerdict) { } // The sandbox's composer may be the node-engine of an older delivery, which does not know the verdict waits: it is handed a // wait as the refusal it is held as. - m.by[module] = v.AsRefused() + m.by[module] = meshcatalogclient.VerdictAsRefused(*v) } -func (m *manifestVerdicts) all() map[string]delivery.ManifestVerdict { +func (m *manifestVerdicts) all() map[string]meshcatalog.VerdictsByDigestValue { m.mu.Lock() defer m.mu.Unlock() - out := make(map[string]delivery.ManifestVerdict, len(m.by)) + out := make(map[string]meshcatalog.VerdictsByDigestValue, len(m.by)) for k, v := range m.by { out[k] = v } 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 02dcae26..0f6f616a 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 @@ -22,18 +22,30 @@ import ( "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" + "mesh-delivery-checker/internal/check" + gobuild "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep" + "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" - "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/nodeengine" ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs" + "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-sdk/go/announce" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/keys" "git.novox.be/novox/mesh-sdk/go/process" "git.novox.be/novox/mesh-sdk/go/views/viewstest" - - "mesh-delivery-checker/internal/check" ) // busConf is the shape of the composed accounts file (mesh-controller internal/broker): one account with JetStream, the @@ -98,7 +110,7 @@ type fakeBuildModules struct { // 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 + packages map[string]buildstep.PackageVersions } func (f *fakeBuildModules) count(tool string) int { @@ -158,7 +170,7 @@ func (f *fakeBuildModules) result(t *testing.T, tool string, args map[string]any // does since R4g (ADR 0525; issue 1887 A5): each ask's job runs with step bodies answering the captured results, and a // check takes the kept verdicts of the checker's kept bucket by their digests. func (f *fakeBuildModules) serve(t *testing.T, module string, kept jetstream.KeyValue) { - kv, err := f.js.CreateKeyValue(f.ctx, jetstream.KeyValueConfig{Bucket: delivery.StepsBucket(module), History: 1}) + kv, err := f.js.CreateKeyValue(f.ctx, jetstream.KeyValueConfig{Bucket: buildrun.StepsBucket(module), History: 1}) if err != nil { t.Fatal(err) } @@ -178,7 +190,7 @@ func (f *fakeBuildModules) serve(t *testing.T, module string, kept jetstream.Key } steps := gobuild.AskSteps{Fetch: call("fetch"), Test: call("test"), Kept: func(ctx context.Context, level, digest string) (int, bool, error) { - key, err := delivery.KeptKey(level, digest) + key, err := meshdeliverycheckerclient.KeptKey(level, digest) if err != nil { return 0, false, err } @@ -189,8 +201,8 @@ func (f *fakeBuildModules) serve(t *testing.T, module string, kept jetstream.Key if err != nil { return 0, false, err } - v, err := delivery.DecodeKept(e.Value()) - return v.Tests, err == nil, err + v, err := meshdeliverycheckerclient.DecodeKept(e.Value()) + return int(v.Tests), err == nil, err }} if module == "mesh-build-go" { steps.Digest = call("digest") @@ -201,13 +213,13 @@ func (f *fakeBuildModules) serve(t *testing.T, module string, kept jetstream.Key } p := &gobuild.Puller{Source: &gobuild.JetStreamAsks{JS: f.js, Language: language}, Language: language, Node: "n1", Put: put, Capacity: func() int { return 2 }, PullWait: 200 * time.Millisecond, - Run: func(ctx context.Context, a delivery.StepAsk) (any, error) { + Run: func(ctx context.Context, a buildstep.StepAsk) (any, error) { if a.Check != nil { f.mu.Lock() f.names = a.Check.NodeNames - if repository, _ := a.Check.Fetch["repository"].(string); a.Language == delivery.AskGo { + if repository := a.Check.Fetch.Repository; a.Language == buildstep.AskGo { if f.packages == nil { - f.packages = map[string]delivery.PackageVersions{} + f.packages = map[string]buildstep.PackageVersions{} } f.packages[repository] = a.Packages } @@ -311,13 +323,13 @@ func (b *bed) said(t *testing.T, words string) { // the seat verb's caller-named and plain subjects); the others never answer. It answers what it was asked. func (b *bed) engine(t *testing.T, nodes ...string) func() []ne.ComposeCandidateInput { t.Helper() - kv, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: delivery.PlacementBucket, History: 1}) + kv, err := b.js.CreateKeyValue(b.ctx, jetstream.KeyValueConfig{Bucket: buildrun.PlacementBucket, History: 1}) if err != nil { t.Fatal(err) } for _, n := range nodes { - pk, _ := delivery.PlacementNodeKey(n) - raw, _ := json.Marshal(delivery.Placement{Node: n}) + pk, _ := meshmoduleassignerclient.NodeKey(n) + raw, _ := meshmoduleassignerclient.EncodeNode(meshmoduleassigner.PlacementByNodeNodeValue{Node: n}) if _, err := kv.Put(b.ctx, pk, raw); err != nil { t.Fatal(err) } @@ -412,7 +424,7 @@ func startBed(t *testing.T) *bed { if _, err := js.CreateOrUpdateConsumer(ctx, stream, jetstream.ConsumerConfig{Durable: "n1_mesh-delivery-checker", AckPolicy: jetstream.AckExplicitPolicy}); err != nil { t.Fatal(err) } - kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: delivery.ToolchainsBucket(module), History: 1}) + kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: buildrun.ToolchainsBucket(module), History: 1}) if err != nil { t.Fatal(err) } @@ -424,11 +436,11 @@ func startBed(t *testing.T) *bed { } // 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 { + if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "CHECKS", Subjects: []string{meshdeliverychecker.HolderSubjectPreviewWritten(check.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 { + if _, err := js.CreateStream(ctx, jetstream.StreamConfig{Name: "ASKS", Subjects: []string{meshdeliverychecker.HolderSubjectVerdict(check.CheckerModule), + meshdeliverychecker.HolderSubjectTagWanted(check.CheckerModule)}}); err != nil { t.Fatal(err) } // The shared events stream with the seats' and modules' events (the package registry seats' published among them), and the checker's durable consumer of it. @@ -438,11 +450,11 @@ func startBed(t *testing.T) *bed { 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, - delivery.CheckerOwedBucket: delivery.CheckerOwedHistory, delivery.PreviewsBucket: delivery.PreviewsHistory, delivery.CandidatesBucket: delivery.CandidatesHistory, - delivery.ModulesBucket: delivery.ModulesHistory, delivery.ManifestsBucket: delivery.ManifestsHistory, delivery.SourceBucket: delivery.SourceHistory, - delivery.PresenceBucket: delivery.PresenceHistory, delivery.HaveBucket: delivery.HaveHistory, check.TriesBucket: 1, - delivery.TurnsBucket: delivery.TurnsHistory, delivery.BuilderBuildsBucket: 1} { + for name, history := range map[string]int{check.CheckerVerdictsBucket: 5, check.CheckerKeptBucket: 1, + check.CheckerOwedBucket: 5, check.PreviewsBucket: 5, check.CandidatesBucket: 10, + check.ModulesBucket: 20, check.ManifestsBucket: 1, check.SourceBucket: 64, + check.PresenceBucket: 20, check.HaveBucket: 20, check.TriesBucket: 1, + check.TurnsBucket: 5, builds.BuilderBuildsBucket: 1} { kv, err := js.CreateKeyValue(ctx, jetstream.KeyValueConfig{Bucket: name, History: uint8(history)}) if err != nil { t.Fatal(err) @@ -451,17 +463,17 @@ func startBed(t *testing.T) *bed { } // The indexer and the assigner as the owners of the states the checker follows: their read tools and views families // (ADR 0615, DLV-M2). - for _, st := range []string{candidatesState, modulesState, manifestsState, delivery.PackagesState, delivery.IndexerPredefinedState, seatDefinitionsState} { - viewstest.Serve(t, ctx, js, ctl, delivery.IndexerModule, st, delivery.IndexerModule+".indexer_read_"+strings.ReplaceAll(st, "-", "_"), "n1_"+delivery.CheckerModule) + for _, st := range []string{candidatesState, modulesState, manifestsState, meshcatalog.StatePackages, check.IndexerPredefinedState, seatDefinitionsState} { + viewstest.Serve(t, ctx, js, ctl, check.IndexerModule, st, check.IndexerModule+".indexer_read_"+strings.ReplaceAll(st, "-", "_"), "n1_"+check.CheckerModule) } - viewstest.Serve(t, ctx, js, ctl, delivery.AssignerModule, delivery.PlacementState, delivery.AssignerModule+".assigner_read_placement", "n1_"+delivery.CheckerModule) + viewstest.Serve(t, ctx, js, ctl, buildrun.AssignerModule, meshmoduleassigner.StatePlacement, buildrun.AssignerModule+".assigner_read_placement", "n1_"+check.CheckerModule) // 1929/AC1: the indexer's value of the repository's module, with no TypeScript manifest, so the Go build module alone // judges the repository (an unknown repository is judged by both, issue 1887 A12). - known, err := delivery.EncodeModule(delivery.Module{Repository: repo, Commit: trunk, Path: "modules/m", None2: "the fingerprint is computed with its chain"}) + known, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Repository: repo, Commit: trunk, Path: "modules/m", None2: "the fingerprint is computed with its chain"}) if err != nil { t.Fatal(err) } - if _, err := b.buckets[delivery.ModulesBucket].Put(ctx, "m", known); err != nil { + if _, err := b.buckets[check.ModulesBucket].Put(ctx, "m", known); err != nil { t.Fatal(err) } b.setOnline(t, "n1", true) @@ -471,8 +483,8 @@ func startBed(t *testing.T) *bed { } b.serveForge(t, ctl) b.fake = &fakeBuildModules{js: js, ctx: ctx, calls: map[string]int{}} - b.fake.serve(t, "mesh-build-go", b.buckets[delivery.CheckerKeptBucket]) - b.fake.serve(t, "mesh-build-node", b.buckets[delivery.CheckerKeptBucket]) + b.fake.serve(t, "mesh-build-go", b.buckets[check.CheckerKeptBucket]) + b.fake.serve(t, "mesh-build-node", b.buckets[check.CheckerKeptBucket]) b.report(t, "mesh-build-go", "n1", "go", "go1.27.1", "1") b.report(t, "mesh-build-node", "n1", "typescript", "tsc5", "1") b.setTrunk(t, map[string]string{repo: trunk}) @@ -481,19 +493,19 @@ func startBed(t *testing.T) *bed { func (b *bed) setTrunk(t *testing.T, heads map[string]string) { t.Helper() - data, err := delivery.EncodeSource(delivery.Source{Group: "g", Heads: heads}) + data, err := meshdeliveryclient.EncodeSource(meshdelivery.SourceByMainValue{Group: "g", Heads: heads}) if err != nil { t.Fatal(err) } - if _, err := b.buckets[delivery.SourceBucket].Put(b.ctx, delivery.SourceMainKey, data); err != nil { + if _, err := b.buckets[check.SourceBucket].Put(b.ctx, meshdeliveryclient.SourceMainKey, data); err != nil { t.Fatal(err) } } func (b *bed) setOnline(t *testing.T, node string, online bool) { t.Helper() - data, _ := delivery.EncodePresence(delivery.Presence{Online: online}) - if _, err := b.buckets[delivery.PresenceBucket].Put(b.ctx, node, data); err != nil { + data, _ := json.Marshal(meshwatchdogs.PresenceByNodeValue{V: keys.Version, Online: online}) + if _, err := b.buckets[check.PresenceBucket].Put(b.ctx, node, data); err != nil { t.Fatal(err) } } @@ -502,35 +514,35 @@ func (b *bed) setOnline(t *testing.T, node string, online bool) { // check ask (ADR 0567 §12); with no record the checker asks no check. func (b *bed) setNode(t *testing.T, node string) { t.Helper() - data, err := delivery.EncodeNodeRecord(delivery.NodeRecord{V: 1, Node: node}) + data, err := nodeengineclient.EncodeHaveNode(nodeengine.HaveByNodeNodeValue{Record: &nodeengine.NodeRecord{Node: node}}) if err != nil { t.Fatal(err) } - key, _ := delivery.HaveNodeKey(node) - if _, err := b.buckets[delivery.HaveBucket].Put(b.ctx, key, data); err != nil { + key, _ := nodeengineclient.HaveNodeKey(node) + if _, err := b.buckets[check.HaveBucket].Put(b.ctx, key, data); err != nil { t.Fatal(err) } } func (b *bed) setHealth(t *testing.T, node, module string) { t.Helper() - data, err := delivery.EncodeHave(delivery.Have{Phase: delivery.PhaseRunning, Health: delivery.HealthOK}) + data, err := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK}) if err != nil { t.Fatal(err) } - key, _ := delivery.HaveKey(node, module) - if _, err := b.buckets[delivery.HaveBucket].Put(b.ctx, key, data); err != nil { + key, _ := nodeengineclient.HaveKey(node, module) + if _, err := b.buckets[check.HaveBucket].Put(b.ctx, key, data); err != nil { t.Fatal(err) } } func (b *bed) report(t *testing.T, module, node, language, version, recipe string) { t.Helper() - data, err := delivery.EncodeToolchain(delivery.Toolchain{Language: language, Version: version, Recipe: recipe, At: time.Now().UTC()}) + data, err := buildstep.EncodeToolchain(buildstep.Toolchain{Language: language, Version: version, Recipe: recipe, At: time.Now().UTC()}) if err != nil { t.Fatal(err) } - key, _ := delivery.ToolchainKey(node, language) + key, _ := buildstep.ToolchainKey(node, language) if _, err := b.tools[module].Put(b.ctx, key, data); err != nil { t.Fatal(err) } @@ -540,30 +552,30 @@ func (b *bed) report(t *testing.T, module, node, language, version, recipe strin } // candidate writes candidates. as the indexer does (the SDK's EncodeCandidate is the indexer's own encoder). -func (b *bed) candidate(t *testing.T, group string, pulls ...delivery.CandidatePull) string { +func (b *bed) candidate(t *testing.T, group string, pulls ...meshcatalog.CandidatesByGroupValuePullsItem) string { t.Helper() - heads := make([]delivery.Head, len(pulls)) + heads := make([]meshdeliveryclient.Head, len(pulls)) for i, p := range pulls { - heads[i] = p.Head + heads[i] = meshdeliveryclient.Head{Repository: p.Repository, Number: int(p.Number), Head: p.Head} } - hash, err := delivery.HeadsHash(heads) + hash, err := meshdeliveryclient.HeadsHash(heads) if err != nil { t.Fatal(err) } - data, err := delivery.EncodeCandidate(delivery.Candidate{V: delivery.Version, Group: group, HeadsHash: hash, Pulls: pulls, FPSet: delivery.FingerprintSet(pulls)}) + data, err := json.Marshal(meshcatalog.CandidatesByGroupValue{V: keys.Version, Group: group, Heads: hash, Pulls: pulls, Fpset: meshcatalogclient.FingerprintSet(pulls)}) if err != nil { t.Fatal(err) } - key, _ := delivery.CandidatesKey(group) - if _, err := b.buckets[delivery.CandidatesBucket].Put(b.ctx, key, data); err != nil { + key, _ := meshcatalogclient.CandidatesKey(group) + if _, err := b.buckets[check.CandidatesBucket].Put(b.ctx, key, data); err != nil { t.Fatal(err) } - vk, _ := delivery.CheckerVerdictKey(group, hash) + vk, _ := meshdeliverycheckerclient.VerdictKey(group, hash) return vk } -func pull(repository string, number int, head string) delivery.CandidatePull { - return delivery.CandidatePull{Head: delivery.Head{Repository: repository, Number: number, Head: head}, Modules: []string{}} +func pull(repository string, number int, head string) meshcatalog.CandidatesByGroupValuePullsItem { + return meshcatalog.CandidatesByGroupValuePullsItem{Repository: repository, Number: int64(number), Head: head, Modules: []string{}} } // start runs the program as the checker of a node. @@ -586,15 +598,15 @@ func (b *bed) start(t *testing.T, env map[string]string) { } // verdict reads the verdict key until pred holds of it, within 20 s. -func (b *bed) verdict(t *testing.T, key string, pred func(delivery.Verdict) bool) delivery.Verdict { +func (b *bed) verdict(t *testing.T, key string, pred func(meshdeliverychecker.VerdictsByGroupHeadsValue) bool) meshdeliverychecker.VerdictsByGroupHeadsValue { t.Helper() - var last delivery.Verdict + var last meshdeliverychecker.VerdictsByGroupHeadsValue for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) { - e, err := b.buckets[delivery.CheckerVerdictsBucket].Get(b.ctx, key) + e, err := b.buckets[check.CheckerVerdictsBucket].Get(b.ctx, key) if err != nil { continue } - if last, err = delivery.DecodeVerdict(e.Value()); err == nil && pred(last) { + if last, err = meshdeliverycheckerclient.DecodeVerdict(e.Value()); err == nil && pred(last) { return last } } @@ -602,22 +614,24 @@ func (b *bed) verdict(t *testing.T, key string, pred func(delivery.Verdict) bool return last } -func ended(v delivery.Verdict) bool { return v.State != delivery.VerdictRunning } +func ended(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool { + return v.State != meshdeliverycheckerclient.VerdictRunning +} // owed reads the status the checker owes on a head. -func (b *bed) owed(t *testing.T, number int, head, state string) delivery.Owed { +func (b *bed) owed(t *testing.T, number int, head, state string) meshdeliverychecker.OwedByRepositoryNumberHeadActValue { t.Helper() - key, err := delivery.CheckerOwedKey(repo, number, head) + key, err := check.CheckerOwedKey(repo, number, head) if err != nil { t.Fatal(err) } - var last delivery.Owed + var last meshdeliverychecker.OwedByRepositoryNumberHeadActValue for deadline := time.Now().Add(20 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) { - e, err := b.buckets[delivery.CheckerOwedBucket].Get(b.ctx, key) + e, err := b.buckets[check.CheckerOwedBucket].Get(b.ctx, key) if err != nil { continue } - if last, err = delivery.DecodeCheckerOwed(e.Value()); err == nil && last.State == state { + if last, err = check.DecodeCheckerOwed(e.Value()); err == nil && last.State == state { return last } } @@ -633,25 +647,25 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300 // ADR 0603 §10: the candidate changes the module m, whose manifest the trunk holds; the builder has not built m's // candidate fingerprint yet, so the check waits on the builder's key before it composes. manifest := `{"module":"m"}` - digest := delivery.ManifestDigest([]byte(manifest)) - stored, err := delivery.EncodeManifest(delivery.Manifest{Module: "m", JSON: manifest}) + digest := meshcatalogclient.ManifestDigest([]byte(manifest)) + stored, err := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "m", Manifest: manifest}) if err != nil { t.Fatal(err) } - mkey, _ := delivery.ManifestsKey(digest) - if _, err := b.buckets[delivery.ManifestsBucket].Put(b.ctx, mkey, stored); err != nil { + mkey, _ := meshcatalogclient.ManifestsKey(digest) + if _, err := b.buckets[check.ManifestsBucket].Put(b.ctx, mkey, stored); err != nil { t.Fatal(err) } - known, err := delivery.EncodeModule(delivery.Module{Repository: repo, Commit: trunk, Path: "modules/m", Manifest: digest, None2: "the fingerprint is computed with its chain"}) + known, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Repository: repo, Commit: trunk, Path: "modules/m", Manifest: digest, None2: "the fingerprint is computed with its chain"}) if err != nil { t.Fatal(err) } - if _, err := b.buckets[delivery.ModulesBucket].Put(b.ctx, "m", known); err != nil { + if _, err := b.buckets[check.ModulesBucket].Put(b.ctx, "m", known); err != nil { t.Fatal(err) } fp := "src2:" + strings.Repeat("f", 64) p := pull(repo, 7, head1) - p.Modules, p.Fps = []string{"m"}, map[string]delivery.CandidateModule{"m": {FP2: fp, Manifest: digest}} + p.Modules, p.Fps = []string{"m"}, map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"m": {Fp2: fp, Manifest: digest}} key := b.candidate(t, "feat/x", p) // ADR 0593 §7, §9 (D12): n1 and n2 are placed; n1's engine answers compose-candidate on the bus, n2's never does. asked := b.engine(t, "n1", "n2") @@ -660,34 +674,36 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300 if got := asked(); len(got) != 0 { t.Fatalf("the engines were asked %+v before m's build ended", got) } - bkey, _ := delivery.BuilderBuildKey("m", fp) - building, _ := json.Marshal(delivery.BuilderBuild{V: 1, State: delivery.BuildBuilding}) - if _, err := b.buckets[delivery.BuilderBuildsBucket].Put(b.ctx, bkey, building); err != nil { + bkey, _ := builds.BuilderBuildKey("m", fp) + building, _ := json.Marshal(builds.BuilderBuild{V: 1, State: builds.BuildBuilding}) + if _, err := b.buckets[builds.BuilderBuildsBucket].Put(b.ctx, bkey, building); err != nil { t.Fatal(err) } - if v := b.verdict(t, key, func(delivery.Verdict) bool { return true }); v.State != delivery.VerdictRunning || len(asked()) != 0 { + if v := b.verdict(t, key, func(meshdeliverychecker.VerdictsByGroupHeadsValue) bool { return true }); v.State != meshdeliverycheckerclient.VerdictRunning || len(asked()) != 0 { t.Fatalf("verdict %s and %d asks while m builds: the check composes only once its build ended", v.State, len(asked())) } // m's build fails: the check fails naming it. Built again (builder_build_again writes the key done), the check asks // itself again for the same heads, with no recheck, and passes. - failedBuild, _ := json.Marshal(delivery.BuilderBuild{V: 1, State: delivery.BuildFailed, Error: "go build: undefined: x"}) - if _, err := b.buckets[delivery.BuilderBuildsBucket].Put(b.ctx, bkey, failedBuild); err != nil { + failedBuild, _ := json.Marshal(builds.BuilderBuild{V: 1, State: builds.BuildFailed, Error: "go build: undefined: x"}) + if _, err := b.buckets[builds.BuilderBuildsBucket].Put(b.ctx, bkey, failedBuild); err != nil { t.Fatal(err) } - if v := b.verdict(t, key, ended); v.State != delivery.VerdictFail || v.Failed.Test != check.BuildFailedTest+"m" { + if v := b.verdict(t, key, ended); v.State != meshdeliverycheckerclient.VerdictFail || v.Failed.Test != check.BuildFailedTest+"m" { t.Fatalf("verdict %+v, want it failed on m's build", v) } - built, _ := json.Marshal(delivery.BuilderBuild{V: 1, State: delivery.BuildDone, Manifest: digest}) - if _, err := b.buckets[delivery.BuilderBuildsBucket].Put(b.ctx, bkey, built); err != nil { + built, _ := json.Marshal(builds.BuilderBuild{V: 1, State: builds.BuildDone, Manifest: digest}) + if _, err := b.buckets[builds.BuilderBuildsBucket].Put(b.ctx, bkey, built); err != nil { t.Fatal(err) } b.said(t, "the builds of m it failed on are done again, so it is checked again") - v := b.verdict(t, key, func(v delivery.Verdict) bool { return v.State == delivery.VerdictPass }) + v := b.verdict(t, key, func(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool { + return v.State == meshdeliverycheckerclient.VerdictPass + }) // The check asked again takes the packages the failed check ran from their kept verdicts. - if v.State != delivery.VerdictPass || v.Levels[delivery.LevelUnit].Kept != 2 || v.Levels[delivery.LevelIntegration].Kept != 2 || v.Trunk[repo] != trunk { + if v.State != meshdeliverycheckerclient.VerdictPass || v.Levels[buildstep.LevelUnit].Kept != 2 || v.Levels[buildstep.LevelIntegration].Kept != 2 || v.Trunk[repo] != trunk { t.Fatalf("verdict = %+v", v) } - if c := v.Composition; c == nil || c.State != delivery.CompositionPass || !slices.Equal(c.Machines, []string{"n1"}) || + if c := v.Composition; c == nil || c.State != meshdeliverycheckerclient.CompositionPass || !slices.Equal(c.Machines, []string{"n1"}) || !strings.Contains(c.Words, "n2: "+nec.NoAnswer) || len(v.Machines) != 1 || v.Machines[0] != "n1" { t.Errorf("composition %+v machines %v", v.Composition, v.Machines) } @@ -704,7 +720,7 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300 t.Errorf("the check ask named the nodes %v, want [n1] from the have's node record", names) } o := b.owed(t, 7, head1, "success") - if o.Context != delivery.StatusContextCheck || !strings.HasPrefix(o.Text, "pass 111111111111 on aaaaaaaaaaaa") || !strings.Contains(o.Text, "unit 0 run, 2 kept") { + if o.Context != meshdeliveryclient.StatusContextCheck || !strings.HasPrefix(o.Text, "pass 111111111111 on aaaaaaaaaaaa") || !strings.Contains(o.Text, "unit 0 run, 2 kept") { t.Errorf("owed = %+v", o) } // Two checks of the same heads: the one failed on m's build and the one it asked itself again; the second runs no test. @@ -715,17 +731,17 @@ func TestCheckerJudgesACandidateAndOwesItsStatus(t *testing.T) { // N6/AC5, 1300 } // What passed is kept once under each level's digest. for _, k := range []string{"unit." + digU, "integration." + digI} { - if _, err := b.buckets[delivery.CheckerKeptBucket].Get(b.ctx, k); err != nil { + if _, err := b.buckets[check.CheckerKeptBucket].Get(b.ctx, k); err != nil { t.Errorf("kept.%s: %v", k, err) } } // The preview of the head merged onto the trunk, with the tree and the manifest digest the fetch named. - pk, _ := delivery.PreviewKey(repo, head1, trunk) - e, err := b.buckets[delivery.PreviewsBucket].Get(b.ctx, pk) + pk, _ := meshdeliverycheckerclient.PreviewKey(repo, head1, trunk) + e, err := b.buckets[check.PreviewsBucket].Get(b.ctx, pk) if err != nil { t.Fatalf("previews.%s: %v", pk, err) } - pv, err := delivery.DecodePreview(e.Value()) + pv, err := meshdeliverycheckerclient.DecodePreview(e.Value()) if err != nil || pv.Merge != merged || pv.Modules["modules/m"].Tree != "1111111111111111111111111111111111111111" { t.Errorf("preview = %+v, %v", pv, err) } @@ -744,7 +760,7 @@ func TestACheckOrACarryOfEqualInputsRunsNothing(t *testing.T) { // 1287/AC5, 128 tests, kept := b.fake.count("mesh-build-go.build_go_test"), keptCount(t, b) second := b.candidate(t, "feat/y", pull(repo, 8, head2)) v := b.verdict(t, second, ended) - if v.State != delivery.VerdictPass || v.Levels[delivery.LevelUnit].Run != 0 || v.Levels[delivery.LevelUnit].Kept != 2 || v.Levels[delivery.LevelIntegration].Run != 0 { + if v.State != meshdeliverycheckerclient.VerdictPass || v.Levels[buildstep.LevelUnit].Run != 0 || v.Levels[buildstep.LevelUnit].Kept != 2 || v.Levels[buildstep.LevelIntegration].Run != 0 { t.Fatalf("verdict = %+v, want everything taken from the kept verdicts", v) } if got := b.fake.count("mesh-build-go.build_go_test"); got != tests { @@ -756,22 +772,22 @@ func TestACheckOrACarryOfEqualInputsRunsNothing(t *testing.T) { // 1287/AC5, 128 // §3: the gate's turn on the repository holds a new check off; its delete claims it. at := time.Now().UTC() - turn, err := delivery.EncodeTurn(delivery.Turn{Repository: repo, Waiting: []delivery.TurnWait{{Group: "fix/other", Heads: "0123456789ab", Approved: at, Since: at}}}) + turn, err := meshdeliveryclient.EncodeTurn(meshdelivery.TurnsByRepositoryValue{Repository: repo, Waiting: []meshdelivery.TurnsByRepositoryValueWaitingItem{{Group: "fix/other", Heads: "0123456789ab", Approved: at, Since: at}}}) if err != nil { t.Fatal(err) } - tk, _ := delivery.TurnsKey(repo) - turnRev, err := b.buckets[delivery.TurnsBucket].Put(b.ctx, tk, turn) + tk, _ := meshdeliveryclient.TurnsKey(repo) + turnRev, err := b.buckets[check.TurnsBucket].Put(b.ctx, tk, turn) if err != nil { t.Fatal(err) } b.said(t, fmt.Sprintf("merge turn %s changed (revision %d)", tk, turnRev)) // the checker heard the turn third := b.candidate(t, "feat/z", pull(repo, 9, "3333333333333333333333333333333333333333")) b.said(t, third+": not claimed: the approved merge of fix/other") // it held the check off - if _, err := b.buckets[delivery.CheckerVerdictsBucket].Get(b.ctx, third); !errors.Is(err, jetstream.ErrKeyNotFound) { + if _, err := b.buckets[check.CheckerVerdictsBucket].Get(b.ctx, third); !errors.Is(err, jetstream.ErrKeyNotFound) { t.Fatalf("verdicts.%s exists (%v) while a merge turn holds its repository", third, err) } - if err := b.buckets[delivery.TurnsBucket].Delete(b.ctx, tk); err != nil { + if err := b.buckets[check.TurnsBucket].Delete(b.ctx, tk); err != nil { t.Fatal(err) } b.verdict(t, third, ended) @@ -780,8 +796,8 @@ func TestACheckOrACarryOfEqualInputsRunsNothing(t *testing.T) { // 1287/AC5, 128 tests = b.fake.count("mesh-build-go.build_go_test") const moved = "abababababababababababababababababababab" b.setTrunk(t, map[string]string{repo: moved}) - c := b.verdict(t, first, func(v delivery.Verdict) bool { return ended(v) && v.Trunk[repo] == moved }) - if c.State != delivery.VerdictPass || c.CarriedFrom[repo] != trunk || c.Levels[delivery.LevelUnit].Run != 0 || c.Levels[delivery.LevelIntegration].Run != 0 { + c := b.verdict(t, first, func(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool { return ended(v) && v.Trunk[repo] == moved }) + if c.State != meshdeliverycheckerclient.VerdictPass || c.CarriedFrom[repo] != trunk || c.Levels[buildstep.LevelUnit].Run != 0 || c.Levels[buildstep.LevelIntegration].Run != 0 { t.Fatalf("the carried verdict = %+v, want a pass carried from %s with no test run", c, trunk[:12]) } if got := b.fake.count("mesh-build-go.build_go_test"); got != tests { @@ -794,7 +810,7 @@ func TestACheckOrACarryOfEqualInputsRunsNothing(t *testing.T) { // 1287/AC5, 128 func keptCount(t *testing.T, b *bed) int { t.Helper() - keys, err := b.buckets[delivery.CheckerKeptBucket].Keys(b.ctx) + keys, err := b.buckets[check.CheckerKeptBucket].Keys(b.ctx) if err != nil { return 0 } @@ -809,7 +825,7 @@ func TestAHeadThatDoesNotMergeFailsNamingTheFilesAndRunsNoTest(t *testing.T) { / key := b.candidate(t, "feat/x", pull(repo, 7, head1)) b.start(t, nil) v := b.verdict(t, key, ended) - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "files in conflict: a.go, sub/b.go") { + if v.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(v.Failed.Words, "files in conflict: a.go, sub/b.go") { t.Fatalf("verdict = %+v", v) } if o := b.owed(t, 7, head1, "failure"); !strings.HasPrefix(o.Text, "failed: n1: ") || !strings.Contains(o.Text, "files in conflict: a.go, sub/b.go") { @@ -818,10 +834,10 @@ func TestAHeadThatDoesNotMergeFailsNamingTheFilesAndRunsNoTest(t *testing.T) { / if n := b.fake.count("mesh-build-go.build_go_digest") + b.fake.count("mesh-build-go.build_go_test"); n != 0 { t.Errorf("%d digest and test steps ran after a conflict", n) } - pk, _ := delivery.PreviewKey(repo, head1, trunk) - if e, err := b.buckets[delivery.PreviewsBucket].Get(b.ctx, pk); err != nil { + pk, _ := meshdeliverycheckerclient.PreviewKey(repo, head1, trunk) + if e, err := b.buckets[check.PreviewsBucket].Get(b.ctx, pk); err != nil { t.Errorf("previews.%s: %v", pk, err) - } else if pv, err := delivery.DecodePreview(e.Value()); err != nil || strings.Join(pv.Conflicts, ",") != "a.go,sub/b.go" || pv.Merge != "" { + } else if pv, err := meshdeliverycheckerclient.DecodePreview(e.Value()); err != nil || strings.Join(pv.Conflicts, ",") != "a.go,sub/b.go" || pv.Merge != "" { t.Errorf("preview = %+v, %v, want the files in conflict alone", pv, err) } } @@ -834,13 +850,13 @@ func TestAnHqPullRequestGetsAVerdictAndAStatus(t *testing.T) { b.setTrunk(t, map[string]string{repo: trunk, "novox/hq": trunk}) hq := b.candidate(t, "feat/hq", pull("novox/hq", 3, head1)) b.start(t, nil) - if v := b.verdict(t, hq, ended); v.State != delivery.VerdictPass { + if v := b.verdict(t, hq, ended); v.State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("hq's verdict = %+v", v) } - key, _ := delivery.CheckerOwedKey("novox/hq", 3, head1) + key, _ := check.CheckerOwedKey("novox/hq", 3, head1) for deadline := time.Now().Add(20 * time.Second); ; time.Sleep(50 * time.Millisecond) { - if e, err := b.buckets[delivery.CheckerOwedBucket].Get(b.ctx, key); err == nil { - if o, err := delivery.DecodeCheckerOwed(e.Value()); err == nil && o.State == "success" { + if e, err := b.buckets[check.CheckerOwedBucket].Get(b.ctx, key); err == nil { + if o, err := check.DecodeCheckerOwed(e.Value()); err == nil && o.State == "success" { return } } @@ -855,16 +871,16 @@ func TestAnHqPullRequestGetsAVerdictAndAStatus(t *testing.T) { func TestACheckLeftRunningIsTakenOverWhenTheCheckerStarts(t *testing.T) { // N6/AC2 b := startBed(t) key := b.candidate(t, "feat/x", pull(repo, 7, head1)) - group, heads, _ := delivery.ParseCheckerVerdictKey(key) - left, err := delivery.EncodeVerdict(delivery.Verdict{Group: group, Heads: heads, Trunk: map[string]string{repo: trunk}, State: delivery.VerdictRunning}) + group, heads, _ := meshdeliverycheckerclient.ParseVerdictKey(key) + left, err := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: group, Heads: heads, Trunk: map[string]string{repo: trunk}, State: meshdeliverycheckerclient.VerdictRunning}) if err != nil { t.Fatal(err) } - if _, err := b.buckets[delivery.CheckerVerdictsBucket].Create(b.ctx, key, left); err != nil { + if _, err := b.buckets[check.CheckerVerdictsBucket].Create(b.ctx, key, left); err != nil { t.Fatal(err) } b.start(t, nil) - if v := b.verdict(t, key, ended); v.State != delivery.VerdictPass { + if v := b.verdict(t, key, ended); v.State != meshdeliverycheckerclient.VerdictPass { t.Errorf("verdict = %+v", v) } } @@ -877,7 +893,7 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing. b.stalePin.Store(true) key := b.candidate(t, "feat/x", pull(repo, 7, head1)) b.start(t, nil) - if v := b.verdict(t, key, ended); v.State != delivery.VerdictPass { + if v := b.verdict(t, key, ended); v.State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("verdict = %+v", v) } @@ -888,12 +904,12 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing. 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)}}) + asks, err := b.js.OrderedConsumer(b.ctx, "ASKS", jetstream.OrderedConsumerConfig{FilterSubjects: []string{meshdeliverychecker.HolderSubjectTagWanted(check.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)}} + sdk.Modules, sdk.Fps = []string{"mesh-sdk"}, map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"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 { @@ -908,7 +924,7 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing. 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"`) { + if v, err := b.buckets[check.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", Module: check.SDKModulePath, Commit: head2, Tag: "feat-x", Version: "0.1.15-feat-x.1", @@ -916,7 +932,7 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing. 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 { + if v := b.verdict(t, key, ended); v.State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("the group's verdict = %+v", v) } b.fake.mu.Lock() @@ -928,7 +944,7 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing. // DLV-C2, DLV-C6 (design 26 *Out of step*, ADR 0620): each check's end says one verdict per pull request on the stream // asks, at the definition's subject and in its body (decoded strictly): the lone pull request's, then the group's two. - verdicts, err := b.js.OrderedConsumer(b.ctx, "ASKS", jetstream.OrderedConsumerConfig{FilterSubjects: []string{meshdeliverychecker.HolderSubjectVerdict(delivery.CheckerModule)}}) + verdicts, err := b.js.OrderedConsumer(b.ctx, "ASKS", jetstream.OrderedConsumerConfig{FilterSubjects: []string{meshdeliverychecker.HolderSubjectVerdict(check.CheckerModule)}}) if err != nil { t.Fatal(err) } @@ -949,8 +965,8 @@ func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing. } said[e.Pull] = e } - for pull, want := range map[int64]meshdeliverychecker.VerdictHolderEvent{7: {Repository: repo, Pull: 7, Head: head1, Verdict: delivery.VerdictPass}, - 8: {Repository: repo, Pull: 8, Head: head1, Verdict: delivery.VerdictPass}, 12: {Repository: sdkRepo, Pull: 12, Head: head2, Verdict: delivery.VerdictPass}} { + for pull, want := range map[int64]meshdeliverychecker.VerdictHolderEvent{7: {Repository: repo, Pull: 7, Head: head1, Verdict: meshdeliverycheckerclient.VerdictPass}, + 8: {Repository: repo, Pull: 8, Head: head1, Verdict: meshdeliverycheckerclient.VerdictPass}, 12: {Repository: sdkRepo, Pull: 12, Head: head2, Verdict: meshdeliverycheckerclient.VerdictPass}} { if got := said[pull]; got.Repository != want.Repository || got.Head != want.Head || got.Verdict != want.Verdict || len(got.OutOfStep) != 0 { t.Errorf("verdict of pull %d = %+v, want %+v", pull, got, want) } @@ -1009,7 +1025,7 @@ func TestATryIsJudgedAndItsEndWrittenWithNoStatusOwed(t *testing.T) { if b.fake.count("mesh-build-go.build_go_fetch") == 0 { t.Errorf("the try fetched nothing") } - for _, name := range []string{delivery.CheckerOwedBucket, delivery.CheckerVerdictsBucket, delivery.PreviewsBucket} { + for _, name := range []string{check.CheckerOwedBucket, check.CheckerVerdictsBucket, check.PreviewsBucket} { if keys, _ := b.buckets[name].Keys(b.ctx); len(keys) != 0 { t.Errorf("a try wrote %s: %v", name, keys) } @@ -1021,7 +1037,7 @@ func TestATryIsJudgedAndItsEndWrittenWithNoStatusOwed(t *testing.T) { if err != nil { t.Fatal(err) } - steps, err := b.js.KeyValue(b.ctx, delivery.StepsBucket("mesh-build-go")) + steps, err := b.js.KeyValue(b.ctx, buildrun.StepsBucket("mesh-build-go")) if err != nil { t.Fatal(err) } diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go index c0a1eac6..4249589e 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go @@ -13,16 +13,19 @@ import ( "github.com/nats-io/nats.go/jetstream" - "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" + "mesh-delivery-checker/internal/check" + "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" nodeengine "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-sdk/go/keys" "git.novox.be/novox/mesh-sdk/go/process" "git.novox.be/novox/mesh-delivery/sdk/graph" - - "mesh-delivery-checker/internal/check" ) // judgeDeps are the parts the judge of a check is made of. @@ -57,22 +60,22 @@ type judgeDeps struct { logf func(format string, args ...any) } -// keptPredefined reads the indexer's key predefined.list (delivery.PredefinedKept), bound once its state appears. +// keptPredefined reads the indexer's key predefined.list (the seat mesh-catalog's state predefined), bound once its state appears. type keptPredefined struct{ kv jetstream.KeyValue } func (k keptPredefined) PredefinedList(ctx context.Context) (json.RawMessage, error) { - e, err := k.kv.Get(ctx, delivery.PredefinedListKey) + e, err := k.kv.Get(ctx, meshcatalog.PredefinedByListKey()) if errors.Is(err, jetstream.ErrKeyNotFound) { return nil, nil } if err != nil { return nil, err } - var kept delivery.PredefinedKept - if err := json.Unmarshal(e.Value(), &kept); err != nil { + kept, err := meshcatalog.DecodePredefinedByListValue(e.Value()) + if err != nil { return nil, err } - return kept.List, nil + return json.Marshal(kept.List) } // keptSeatDefinitions reads a pull request's entries of the indexer's seat definitions from its first view: each whose @@ -103,7 +106,7 @@ func seatDefinitionsOf(kv jetstream.KeyValue) keptSeatDefinitions { } func (k keptSeatDefinitions) Unparsed(ctx context.Context, repository string, pull int) ([]check.UnparsedSeat, error) { - suffix := ".pull." + delivery.EscapeName(repository) + "." + strconv.Itoa(pull) + suffix := ".pull." + keys.EscapeName(repository) + "." + strconv.Itoa(pull) keys, err := k.keys(ctx) if err != nil { return nil, err @@ -143,14 +146,14 @@ type nodeNames struct{ kv jetstream.KeyValue } // openNodeNames opens the node-engine's have for the nodes' names alone: it is not watched, so no check waits on a node. func openNodeNames(ctx context.Context, js process.BucketOpener, logf func(string, ...any)) *nodeNames { - return &nodeNames{kv: process.OptionalBucket(ctx, js, delivery.HaveBucket, "no check is asked until it is: the names check needs the nodes' names", logf)} + return &nodeNames{kv: process.OptionalBucket(ctx, js, check.HaveBucket, "no check is asked until it is: the names check needs the nodes' names", logf)} } func (n nodeNames) Names(ctx context.Context) ([]string, error) { if n.kv == nil { - return nil, fmt.Errorf("the bucket %s could not be opened at the checker's start", delivery.HaveBucket) + return nil, fmt.Errorf("the bucket %s could not be opened at the checker's start", check.HaveBucket) } - lister, err := n.kv.ListKeysFiltered(ctx, "*."+delivery.HaveNodeWord) + lister, err := n.kv.ListKeysFiltered(ctx, "*."+check.HaveNodeWord) if err != nil { return nil, err } @@ -264,7 +267,7 @@ func (r *repoModules) Manifest(ctx context.Context, digest string) (catalogue.Ma if known { return m, nil } - key, err := delivery.ManifestsKey(digest) + key, err := meshcatalogclient.ManifestsKey(digest) if err != nil { return catalogue.Manifest{}, err } @@ -275,11 +278,11 @@ func (r *repoModules) Manifest(ctx context.Context, digest string) (catalogue.Ma if !found { return catalogue.Manifest{}, fmt.Errorf("the indexer keeps no manifest %s", digest) } - kept, err := delivery.DecodeManifest(raw) + kept, err := meshcatalogclient.DecodeManifest(raw) if err != nil { return catalogue.Manifest{}, err } - if m, err = parseManifest(kept.JSON); err != nil { + if m, err = parseManifest(kept.Manifest); err != nil { return catalogue.Manifest{}, err } r.mu.Lock() @@ -311,19 +314,19 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted return } r.mu.Unlock() - m, err := delivery.DecodeModule(value) + m, err := meshcatalogclient.DecodeModule(value) if err != nil { return } if r.verdicts != nil { - r.verdicts.put(key, m.VerdictOf(key)) + r.verdicts.put(key, meshcatalogclient.ModuleVerdict(key, m)) } r.mu.Lock() r.repoOf[key] = m.Repository r.typed[key] = m.Manifest r.commit[key] = m.Commit r.path[key] = m.Path - r.named[key] = m.ArtifactPaths + r.named[key] = m.Paths _, known := r.langs[m.Manifest] r.mu.Unlock() if m.Manifest == "" || known { @@ -331,10 +334,10 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted } set := map[string]bool{} var pkgs []string - if mk, err := delivery.ManifestsKey(m.Manifest); err == nil { + if mk, err := meshcatalogclient.ManifestsKey(m.Manifest); err == nil { if raw, _, found, err := r.manifests.Get(ctx, mk); err == nil && found { - if kept, err := delivery.DecodeManifest(raw); err == nil { - if man, err := parseManifest(kept.JSON); err == nil && man.Build != nil { + if kept, err := meshcatalogclient.DecodeManifest(raw); err == nil { + if man, err := parseManifest(kept.Manifest); err == nil && man.Build != nil { for _, a := range man.Build.Artifacts { if a.Language != "" { set[a.Language] = true @@ -365,7 +368,7 @@ func (r *repoModules) ModuleDir(module string) (string, bool) { } // PackagePaths are the paths, relative to the repository, that the TypeScript package artifacts of a repository's modules -// are built from, sorted: the named paths the indexer keeps in each module's value (delivery.Module.ArtifactPaths; only the +// are built from, sorted: the named paths the indexer keeps in each module's value (meshcatalog.ModulesByModuleValue.ArtifactPaths; only the // indexer reads them from a manifest, the lead's decision L8), under the module's directory. None for a module whose value // holds no paths. func (r *repoModules) PackagePaths(repository string) []string { @@ -380,7 +383,7 @@ func (r *repoModules) PackagePaths(repository string) []string { for _, name := range r.tsPkgs[r.typed[module]] { for _, p := range r.named[module][name] { p = strings.Trim(strings.TrimPrefix(p, "./"), "/") - if dir != "" && dir != delivery.RootPath { + if dir != "" && dir != meshcatalogclient.RootPath { p = dir + "/" + p } out = append(out, p) @@ -420,16 +423,16 @@ func (r *repoModules) BuildModules(repository string) []string { // buildReader reads the builder's build of a module at a fingerprint (mesh-delivery-builder_builds). type buildReader struct{ kv kvStore } -func (r buildReader) Build(ctx context.Context, module, fp2 string) (delivery.BuilderBuild, bool, error) { - key, err := delivery.BuilderBuildKey(module, fp2) +func (r buildReader) Build(ctx context.Context, module, fp2 string) (builds.BuilderBuild, bool, error) { + key, err := builds.BuilderBuildKey(module, fp2) if err != nil { - return delivery.BuilderBuild{}, false, err + return builds.BuilderBuild{}, false, err } value, _, found, err := r.kv.Get(ctx, key) if err != nil || !found { - return delivery.BuilderBuild{}, false, err + return builds.BuilderBuild{}, false, err } - b, err := delivery.DecodeBuilderBuild(value) + b, err := builds.DecodeBuilderBuild(value) return b, err == nil, err } @@ -438,30 +441,30 @@ func (r buildReader) Build(ctx context.Context, module, fp2 string) (delivery.Bu // the fact and polls nothing (novox/hq ADR 0603 §10). type buildEnds struct{ kv jetstream.KeyValue } -func (e buildEnds) Ended(ctx context.Context, builds map[string]string) (map[string]delivery.BuilderBuild, error) { - return e.until(ctx, builds, func(b delivery.BuilderBuild) bool { - return b.State == delivery.BuildDone || b.State == delivery.BuildFailed +func (e buildEnds) Ended(ctx context.Context, asked map[string]string) (map[string]builds.BuilderBuild, error) { + return e.until(ctx, asked, func(b builds.BuilderBuild) bool { + return b.State == builds.BuildDone || b.State == builds.BuildFailed }) } -func (e buildEnds) Done(ctx context.Context, builds map[string]string) bool { - done, err := e.until(ctx, builds, func(b delivery.BuilderBuild) bool { return b.State == delivery.BuildDone }) - return err == nil && len(done) == len(builds) +func (e buildEnds) Done(ctx context.Context, asked map[string]string) bool { + done, err := e.until(ctx, asked, func(b builds.BuilderBuild) bool { return b.State == builds.BuildDone }) + return err == nil && len(done) == len(asked) } // until hears the builds' keys until each holds a build that ended holds, or ctx ends, and answers those that do, by module. -func (e buildEnds) until(ctx context.Context, builds map[string]string, ended func(delivery.BuilderBuild) bool) (map[string]delivery.BuilderBuild, error) { +func (e buildEnds) until(ctx context.Context, asked map[string]string, ended func(builds.BuilderBuild) bool) (map[string]builds.BuilderBuild, error) { byKey := map[string]string{} - keys := make([]string, 0, len(builds)) - for module, fp2 := range builds { - key, err := delivery.BuilderBuildKey(module, fp2) + keys := make([]string, 0, len(asked)) + for module, fp2 := range asked { + key, err := builds.BuilderBuildKey(module, fp2) if err != nil { return nil, err } byKey[key] = module keys = append(keys, key) } - out := map[string]delivery.BuilderBuild{} + out := map[string]builds.BuilderBuild{} if len(keys) == 0 { return out, nil } @@ -485,7 +488,7 @@ func (e buildEnds) until(ctx context.Context, builds map[string]string, ended fu if !ours { continue } - b, err := delivery.DecodeBuilderBuild(entry.Value()) + b, err := builds.DecodeBuilderBuild(entry.Value()) if err == nil && ended(b) { out[module] = b } else { @@ -497,7 +500,7 @@ func (e buildEnds) until(ctx context.Context, builds map[string]string, ended fu } // ManifestDigests is the manifest digest of every module the indexer holds, by module: the catalogue the indexer's judge -// judges a change's manifests in (novox/hq ADR 0578 §2). +// judges a change's manifests in (novox/hq ADR 0578 §2).8 §2). func (r *repoModules) ManifestDigests() map[string]string { r.mu.Lock() defer r.mu.Unlock() diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge_test.go index 5b9d04cd..dd5a7abd 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge_test.go @@ -12,8 +12,11 @@ import ( "testing" "time" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" + "git.novox.be/novox/mesh-sdk/go/keys" "mesh-delivery-checker/internal/check" ) @@ -27,7 +30,7 @@ func (m memManifests) Get(_ context.Context, key string) ([]byte, uint64, bool, func putModule(t *testing.T, r *repoModules, name, repository, manifest string) { t.Helper() - data, err := delivery.EncodeModule(delivery.Module{Repository: repository, Commit: strings.Repeat("a", 40), FP: "src1:" + strings.Repeat("a", 64), Manifest: delivery.ManifestDigest([]byte(manifest))}) + data, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Repository: repository, Commit: strings.Repeat("a", 40), Fp: "src1:" + strings.Repeat("a", 64), Manifest: meshcatalogclient.ManifestDigest([]byte(manifest))}) if err != nil { t.Fatal(err) } @@ -58,11 +61,11 @@ func TestARepositoryWithATypeScriptModuleIsJudgedByTheNodeBuildModuleToo(t *test tsManifest := string(tsRaw) store := memManifests{} for _, m := range []string{goManifest, tsManifest} { - data, err := delivery.EncodeManifest(delivery.Manifest{Module: "demo", JSON: m}) + data, err := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "demo", Manifest: m}) if err != nil { t.Fatal(err) } - store[delivery.ManifestDigest([]byte(m))] = data + store[meshcatalogclient.ManifestDigest([]byte(m))] = data } r := &repoModules{manifests: store} putModule(t, r, "mesh-delivery-checker", "novox/mesh-delivery", goManifest) @@ -89,15 +92,15 @@ const sdkManifest = `{"module":"mesh-sdk","version":"1","slug":"sdk","build":{"a func TestTheTypeScriptPackagesPathsComeFromTheIndexersModulesNotTheManifest(t *testing.T) { // ADR 0500 §5 with lead decision L8 store := memManifests{} - data, err := delivery.EncodeManifest(delivery.Manifest{Module: "mesh-sdk", JSON: sdkManifest}) + data, err := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "mesh-sdk", Manifest: sdkManifest}) if err != nil { t.Fatal(err) } - store[delivery.ManifestDigest([]byte(sdkManifest))] = data + store[meshcatalogclient.ManifestDigest([]byte(sdkManifest))] = data r := &repoModules{manifests: store} put := func(path string, paths map[string][]string) { - v, err := delivery.EncodeModule(delivery.Module{Repository: "novox/mesh-sdk", Commit: strings.Repeat("a", 40), FP: "src1:" + strings.Repeat("a", 64), - Manifest: delivery.ManifestDigest([]byte(sdkManifest)), Path: path, ArtifactPaths: paths}) + v, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Repository: "novox/mesh-sdk", Commit: strings.Repeat("a", 40), Fp: "src1:" + strings.Repeat("a", 64), + Manifest: meshcatalogclient.ManifestDigest([]byte(sdkManifest)), Path: path, Paths: paths}) if err != nil { t.Fatal(err) } @@ -133,10 +136,11 @@ func TestThePublishedVersionsAreTheIndexersPackagesState(t *testing.T) { t.Errorf("read and empty, the known packages = %#v, want an empty map", got) } put := func(module, repository, dir, commit string) { - key, _ := delivery.PackagesKey(module) - raw, err := delivery.EncodePublishedPackages(delivery.PublishedPackages{Module: module, Repository: repository, Dir: dir, + key := meshcatalog.PackagesByModuleKey(keys.EscapeName(module)) + now := time.Now().UTC() + raw, err := json.Marshal(meshcatalog.PackagesByModuleValue{V: keys.Version, Module: module, Repository: repository, Dir: dir, Commit: commit, Requires: strings.Repeat("e", 64), - Packages: []delivery.PackageDigest{{Package: module, Digest: strings.Repeat("d", 64)}}, At: time.Now().UTC()}) + Packages: []meshcatalog.PackagesByModuleValuePackagesItem{{Package: module, Digest: strings.Repeat("d", 64)}}, At: &now}) if err != nil { t.Fatal(err) } @@ -146,30 +150,31 @@ func TestThePublishedVersionsAreTheIndexersPackagesState(t *testing.T) { put(sdk, "novox/mesh-sdk", "go", strings.Repeat("a", 40)) put(build, "novox/mesh-catalog", "modules/mesh-build-go", strings.Repeat("b", 40)) p.Put("broken", []byte("{"), false) - if got := p.LatestPackages(); !reflect.DeepEqual(got, delivery.PackageVersions{sdk: strings.Repeat("a", 40), build: strings.Repeat("b", 40)}) { + if got := p.LatestPackages(); !reflect.DeepEqual(got, buildstep.PackageVersions{sdk: strings.Repeat("a", 40), build: strings.Repeat("b", 40)}) { t.Errorf("latest packages = %v", got) } - key, _ := delivery.PackagesKey(build) + key := meshcatalog.PackagesByModuleKey(keys.EscapeName(build)) p.Put(key, nil, true) if got := p.LatestPackages(); len(got) != 1 || got[sdk] == "" { t.Errorf("after a delete: %v", got) } // A series key names its package by its ecosystem name, the judge's known packages (DLV-I14, design 32), and is no // module's published version. - series, err := delivery.EncodeSeriesPackage(delivery.SeriesPackage{Name: sdk, Package: "mesh-sdk/go", Series: "0.1", At: time.Now().UTC()}) + now := time.Now().UTC() + series, err := json.Marshal(meshcatalog.PackagesByPackageSeriesValue{V: keys.Version, Name: sdk, Package: "mesh-sdk/go", Series: "0.1", At: &now}) if err != nil { t.Fatal(err) } - p.Put(delivery.SeriesPackageKey(sdk, "0.1"), series, false) + p.Put(meshcatalog.PackagesByPackageSeriesKey(keys.EscapeName(sdk), "0.1"), series, false) if got := p.PackageNames(); !reflect.DeepEqual(got, map[string]string{sdk: "mesh-sdk/go"}) || len(p.LatestPackages()) != 1 { t.Errorf("the known packages = %v, the latest %v", got, p.LatestPackages()) } t.Run("a pull request's unparsed seat definitions are its entries of the indexer's seat-definitions", func(t *testing.T) { state := map[string]string{ "web-api.trunk": `{"seat":"web-api","source":"trunk","digest":"d"}`, - "web-api.pull." + delivery.EscapeName("novox/web") + ".7": `{"seat":"web-api","source":"pull","module":"web-contracts","directory":"go","unparsed":"seat.json: bad"}`, - "mail-api.pull." + delivery.EscapeName("novox/web") + ".7": `{"seat":"mail-api","source":"pull","digest":"e"}`, - "web-api.pull." + delivery.EscapeName("novox/web") + ".17": `{"seat":"web-api","source":"pull","unparsed":"another pull request's"}`, + "web-api.pull." + keys.EscapeName("novox/web") + ".7": `{"seat":"web-api","source":"pull","module":"web-contracts","directory":"go","unparsed":"seat.json: bad"}`, + "mail-api.pull." + keys.EscapeName("novox/web") + ".7": `{"seat":"mail-api","source":"pull","digest":"e"}`, + "web-api.pull." + keys.EscapeName("novox/web") + ".17": `{"seat":"web-api","source":"pull","unparsed":"another pull request's"}`, } k := keptSeatDefinitions{keys: func(context.Context) ([]string, error) { return slices.Collect(maps.Keys(state)), nil }, get: func(_ context.Context, key string) ([]byte, error) { return []byte(state[key]), nil }} @@ -189,11 +194,11 @@ func TestAManifestFallingBackToAPredefinedKeyIsRead(t *testing.T) { t.Fatal(err) } manifest := string(raw) - data, err := delivery.EncodeManifest(delivery.Manifest{Module: "xterm", JSON: manifest}) + data, err := meshcatalogclient.EncodeManifest(meshcatalog.ManifestsByDigestValue{Module: "xterm", Manifest: manifest}) if err != nil { t.Fatal(err) } - store := memManifests{delivery.ManifestDigest([]byte(manifest)): data} + store := memManifests{meshcatalogclient.ManifestDigest([]byte(manifest)): data} var said []string r := &repoModules{manifests: store, logf: func(f string, a ...any) { said = append(said, fmt.Sprintf(f, a...)) }} putModule(t, r, "xterm", "novox/mesh-catalog", manifest) @@ -208,8 +213,8 @@ func TestAManifestFallingBackToAPredefinedKeyIsRead(t *testing.T) { } // The sandbox's composer may be an older node-engine, which knows no verdict waits: it is handed a wait as a refusal. var held manifestVerdicts - held.put("nats", &delivery.ManifestVerdict{Module: "nats", State: delivery.VerdictManifestWaits, Unknown: []string{"core-credentials"}, Judge: "j"}) - if v := held.all()["nats"]; v.State != delivery.VerdictRefused || len(v.Problems) != 1 || len(v.Unknown) != 0 { + held.put("nats", &meshcatalog.VerdictsByDigestValue{Module: "nats", State: meshcatalogclient.VerdictManifestWaits, Unknown: []string{"core-credentials"}, Judge: "j"}) + if v := held.all()["nats"]; v.State != meshcatalogclient.VerdictRefused || len(v.Problems) != 1 || len(v.Unknown) != 0 { t.Errorf("a waiting verdict is handed to the sandbox as %+v", v) } } diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/lazyturns.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/lazyturns.go index 4d84c22b..b39611bc 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/lazyturns.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/lazyturns.go @@ -3,9 +3,9 @@ package main import ( "sync" - "git.novox.be/novox/mesh-sdk/go/delivery" - "mesh-delivery-checker/internal/check" + + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" ) // lazyTurns is the gate's merge turns as the checker reads them (novox/hq ADR 0548 §3), opened when the gate's bucket is @@ -33,18 +33,18 @@ func (l *lazyTurns) ensure() { if !l.said { l.said = true l.logf("NO MERGE TURN HOLDS A CHECK: the gate's bucket %s cannot be read (%v); every check is claimed as if no approved merge waited, "+ - "and the bucket is opened again before each claim until it is there", delivery.TurnsBucket, err) + "and the bucket is opened again before each claim until it is there", check.TurnsBucket, err) } return } l.opened = true if l.said { - l.logf("the gate's merge turns are read from %s from now on: an approved merge's turn holds checks off its repositories again", delivery.TurnsBucket) + l.logf("the gate's merge turns are read from %s from now on: an approved merge's turn holds checks off its repositories again", check.TurnsBucket) } } // Holding opens the bucket when it is not yet, then answers what the turns hold. -func (l *lazyTurns) Holding(repositories []string, heads string) (string, delivery.TurnWait, bool) { +func (l *lazyTurns) Holding(repositories []string, heads string) (string, meshdelivery.TurnsByRepositoryValueWaitingItem, bool) { l.ensure() return l.turns.Holding(repositories, heads) } diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go index 55a908d7..d7b49ed3 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/main.go @@ -10,7 +10,6 @@ import ( "encoding/json" "errors" "fmt" - sdkfindings "git.novox.be/novox/mesh-delivery/sdk/findings" "io" "log" "os" @@ -19,23 +18,29 @@ import ( "syscall" "time" + sdkfindings "git.novox.be/novox/mesh-delivery/sdk/findings" + "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" + "mesh-delivery-checker/internal/check" + + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-sdk/go/announce" - "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/process" "git.novox.be/novox/mesh-sdk/go/seatlist" "git.novox.be/novox/mesh-sdk/go/views" - - "mesh-delivery-checker/internal/check" ) // eventSubject is the subject of an event of the checker's stream checks (the bus names the stream CHECKS): the indexer's // watch of the previews bucket and the forge bridge's watch of the owed bucket are woken by them. func eventSubject(event string) string { - return process.ModuleStreamSubject(delivery.CheckerModule, event) + return process.ModuleStreamSubject(check.CheckerModule, event) } const ( @@ -66,11 +71,11 @@ func durationEnv(getenv func(string) string, name string, def time.Duration) (ti } // The checker's own states of its first views (views.StatusState) and of its findings, where the condition keeper raises a -// first view unanswered past its bound (ADR 0615 §6); and the indexer's states it follows (delivery.CandidatesBucket, +// first view unanswered past its bound (ADR 0615 §6); and the indexer's states it follows (check.CandidatesBucket, // ModulesBucket, ManifestsBucket). var ( - firstViewsBucket = delivery.CheckerModule + "_" + views.StatusState - findingsBucket = delivery.CheckerModule + "_findings" + firstViewsBucket = check.CheckerModule + "_" + views.StatusState + findingsBucket = check.CheckerModule + "_findings" ) const ( @@ -120,7 +125,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro defer conn.Close() // Its own states: a check without one of them would be unwritten, so each stops the start, naming its grant (issue 1945). buckets := map[string]jetstream.KeyValue{} - for _, name := range []string{delivery.CheckerVerdictsBucket, delivery.CheckerKeptBucket, delivery.CheckerOwedBucket, delivery.PreviewsBucket, + for _, name := range []string{check.CheckerVerdictsBucket, check.CheckerKeptBucket, check.CheckerOwedBucket, check.PreviewsBucket, check.TriesBucket} { if buckets[name], err = process.EssentialBucket(ctx, js, "checker", name, "judge or keep a check"); err != nil { return err @@ -146,7 +151,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // The module has one durable consumer of EVENTS: one hub pulls it for the views reader and for the registry seats' // published, started once both are registered. hub := process.NewEventHub(logger.Printf) - reader := &views.ProcessReader{Module: delivery.CheckerModule, Call: caller.Call, Logf: logger.Printf, Hub: hub} + reader := &views.ProcessReader{Module: check.CheckerModule, Call: caller.Call, Logf: logger.Printf, Hub: hub} seats := seatList{reader: followSeatList(seatlist.ProcessBus(caller.Call, hub), logger.Printf)} findings := process.AwaitBucket(ctx, js, conn, findingsBucket, "a first view unanswered past its bound is said in the log alone", logger.Printf) reader.Findings = func() views.FindingWriter { return sdkfindings.Writer(ctx, findings.KV) } @@ -157,14 +162,14 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro followed = errors.Join(followed, err) return p } - candidates := followOwner(delivery.IndexerModule, candidatesState, "indexer_read_candidates", indexerViews) - modules := followOwner(delivery.IndexerModule, modulesState, "indexer_read_modules", indexerViews) - manifests := followOwner(delivery.IndexerModule, manifestsState, "indexer_read_manifests", indexerViews) - packages := followOwner(delivery.IndexerModule, delivery.PackagesState, "indexer_read_packages", indexerViews) - predefined := followOwner(delivery.IndexerModule, delivery.IndexerPredefinedState, "indexer_read_predefined", indexerViews) - placement := followOwner(delivery.AssignerModule, delivery.PlacementState, "assigner_read_placement", assignerViews) + candidates := followOwner(check.IndexerModule, candidatesState, "indexer_read_candidates", indexerViews) + modules := followOwner(check.IndexerModule, modulesState, "indexer_read_modules", indexerViews) + manifests := followOwner(check.IndexerModule, manifestsState, "indexer_read_manifests", indexerViews) + packages := followOwner(check.IndexerModule, meshcatalog.StatePackages, "indexer_read_packages", indexerViews) + predefined := followOwner(check.IndexerModule, check.IndexerPredefinedState, "indexer_read_predefined", indexerViews) + placement := followOwner(buildrun.AssignerModule, meshmoduleassigner.StatePlacement, "assigner_read_placement", assignerViews) // The seventh: the indexer's seat definitions, so a pull request whose head's definition does not parse fails. - seatDefs := followOwner(delivery.IndexerModule, seatDefinitionsState, "indexer_read_seat_definitions", indexerViews) + seatDefs := followOwner(check.IndexerModule, seatDefinitionsState, "indexer_read_seat_definitions", indexerViews) if followed != nil { return followed } @@ -200,7 +205,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // The state may not be on the bus yet when a delivery started the checker (the grants part creates it, issue 1790): it // is bound when the bus says it was created, and the latest hearing is written then. hearingKV := process.AwaitBucket(ctx, js, conn, check.HearingBucket, "checker_status cannot say whether DEAD_LETTERS is heard", logger.Printf) - hearing := buildrun.NewDeadLetterHearing(ctx, buildrun.JetStreamDeadLetters{JS: js}, []string{delivery.AskGo, delivery.AskNode}, dead, + hearing := buildrun.NewDeadLetterHearing(ctx, buildrun.JetStreamDeadLetters{JS: js}, []string{buildstep.AskGo, buildstep.AskNode}, dead, logger.Printf, buildrun.SayHearing(ctx, hearingKV, logger.Printf)) defer hearing.Stop() // Each ask's step is heard by its own watch and, at every step end of its build module and every reconnect, read @@ -211,8 +216,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro asksKV := process.AwaitBucket(ctx, js, conn, check.AsksBucket, "checker_ask_again refuses every check, as it cannot tell whether its ask waits in a stream", logger.Printf) ch := &check.Checker{ Asks: awaitedKV{asksKV}, - Verdicts: kvStore{buckets[delivery.CheckerVerdictsBucket]}, - Owed: kvStore{buckets[delivery.CheckerOwedBucket]}, + Verdicts: kvStore{buckets[check.CheckerVerdictsBucket]}, + Owed: kvStore{buckets[check.CheckerOwedBucket]}, Trunks: trunks, Turns: lazy, Bound: checkBound, @@ -222,10 +227,10 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro } // The builder's builds say how the candidates' fingerprints stand (issue 1684); where its bucket is not there yet the line // says nothing of the builds, until the checker starts again. - var builds check.BuildStates + var buildStates check.BuildStates var ends check.BuildEnds - if kv := process.OptionalBucket(ctx, js, delivery.BuilderBuildsBucket, "the line says nothing of the builds, and a check composes without waiting on them, until the checker starts again", logger.Printf); kv != nil { - builds, ends = buildReader{kvStore{kv}}, buildEnds{kv} + if kv := process.OptionalBucket(ctx, js, builds.BuilderBuildsBucket, "the line says nothing of the builds, and a check composes without waiting on them, until the checker starts again", logger.Printf); kv != nil { + buildStates, ends = buildReader{kvStore{kv}}, buildEnds{kv} } if ends != nil { ch.Rebuilds = ends // a check failed on a build is asked again once that build is done again (ADR 0603 §10) @@ -276,8 +281,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro } ch.Judge = newJudge(judgeDeps{ asks: buildrun.Asker{Publisher: asks, Waiter: waiter, Bound: stepBound}, tools: tools, trunks: trunks, - kept: kvStore{buckets[delivery.CheckerKeptBucket]}, previews: kvStore{buckets[delivery.PreviewsBucket]}, - repos: repos, published: published, builds: builds, ends: ends, buildWait: set.buildWait, forge: forgeReads{call: caller.Call}, logf: logger.Printf, + kept: kvStore{buckets[check.CheckerKeptBucket]}, previews: kvStore{buckets[check.PreviewsBucket]}, + repos: repos, published: published, builds: buildStates, ends: ends, buildWait: set.buildWait, forge: forgeReads{call: caller.Call}, logf: logger.Printf, announce: announce, compositions: compositions, tags: tags, nodes: openNodeNames(ctx, js, logger.Printf), predefined: keptPredefined{predefined}, @@ -333,9 +338,9 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // the assigner's own presence says whether a composition is being made (compositionWatch, ADR 0537 §4). A check is no // longer held for a node: its asks wait in the build seats' streams, which redeliver a lost node's ask (ADR 0525 §6, // issue 1912), so the node-engine's have is not watched here. - if kv := process.OptionalBucket(ctx, js, delivery.PresenceBucket, "no node counts as online until the checker starts again", logger.Printf); kv != nil { + if kv := process.OptionalBucket(ctx, js, check.PresenceBucket, "no node counts as online until the checker starts again", logger.Printf); kv != nil { if pw, err := settle(ctx, kv, func(e jetstream.KeyValueEntry) { presence.Put(e.Key(), e.Value(), !opOf(e)) }); err != nil { - process.NotWatched(logger.Printf, delivery.PresenceBucket, err, "no node counts as online until the checker starts again") + process.NotWatched(logger.Printf, check.PresenceBucket, err, "no node counts as online until the checker starts again") } else { go func() { errs <- follow(ctx, pw, func(e jetstream.KeyValueEntry) { @@ -348,18 +353,18 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro } // The gate's source says the trunk head of each repository, which a pull request is merged onto. Where it is not there // yet no trunk is known, so nothing is checked. - if kv := process.OptionalBucket(ctx, js, delivery.SourceBucket, "no trunk is known, so nothing is checked until the checker starts again", logger.Printf); kv != nil { + if kv := process.OptionalBucket(ctx, js, check.SourceBucket, "no trunk is known, so nothing is checked until the checker starts again", logger.Printf); kv != nil { sw, err := settle(ctx, kv, func(e jetstream.KeyValueEntry) { - if e.Key() == delivery.SourceMainKey { + if e.Key() == meshdeliveryclient.SourceMainKey { trunks.Put(e.Value(), !opOf(e)) } }) if err != nil { - process.NotWatched(logger.Printf, delivery.SourceBucket, err, "no trunk is known, so nothing is checked until the checker starts again") + process.NotWatched(logger.Printf, check.SourceBucket, err, "no trunk is known, so nothing is checked until the checker starts again") } else { go func() { errs <- follow(ctx, sw, func(e jetstream.KeyValueEntry) { - if e.Key() == delivery.SourceMainKey && trunks.Put(e.Value(), !opOf(e)) { + if e.Key() == meshdeliveryclient.SourceMainKey && trunks.Put(e.Value(), !opOf(e)) { ch.TrunkMoved(ctx) // the trunk moved: its statuses go pending, and a verdict for the old trunk is judged again } }) @@ -371,7 +376,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // Where the gate's bucket is not there yet it is read as empty, and opened again before each claim until it is there // (lazyTurns), so a gate delivered after the checker started holds checks from its first turn on. lazy.open = func() error { - kv, err := js.KeyValue(ctx, delivery.TurnsBucket) + kv, err := js.KeyValue(ctx, check.TurnsBucket) if err != nil { return err } @@ -444,7 +449,7 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro }() // The checker's own verdicts, updates only, in one watch: one checker_ask_again wrote asks its check again // (Checker.VerdictWritten); a deleted key of heads a candidate holds is judged again (Checker.VerdictDeleted, issue 1733). - vw, err := buckets[delivery.CheckerVerdictsBucket].WatchAll(ctx, jetstream.UpdatesOnly()) + vw, err := buckets[check.CheckerVerdictsBucket].WatchAll(ctx, jetstream.UpdatesOnly()) if err != nil { return fmt.Errorf("the checker's verdicts cannot be watched, so no check can be asked again by hand and a deleted verdict would never be written again: %w", err) } diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/nodes_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/nodes_test.go index 550d4025..aa903e5b 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/nodes_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/nodes_test.go @@ -14,7 +14,7 @@ func TestTheCheckerWatchesNoHave(t *testing.T) { if err != nil { t.Fatal(err) } - for _, s := range []string{"delivery.HaveBucket", "buildrun.HaveSet"} { + for _, s := range []string{"check.HaveBucket", "buildrun.HaveSet"} { if strings.Contains(string(raw), s) { t.Errorf("the checker program still reads %s", s) } diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/packages.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/packages.go index d56cb78c..19b4118c 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/packages.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/packages.go @@ -1,9 +1,11 @@ package main import ( + "strings" "sync" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // publishedPackages is the indexer's state `packages` as the checker follows it (novox/hq ADR 0593 §2): by mesh Go @@ -11,13 +13,13 @@ import ( // package's version; the checker reads it, so a Go check builds every mesh package at the version the mesh publishes // (check.Examiner.Published) and computes none. // -// Its series keys (delivery.SeriesPackage, decoded with delivery.DecodeSeriesPackage) name each package by its name in its +// Its series keys (meshcatalog.PackagesByPackageSeriesValue, decoded with meshcatalog.DecodePackagesByPackageSeriesValue) name each package by its name in its // own ecosystem and the `/` it is built from (design 32): the known packages the judge takes // (PackageNames). type publishedPackages struct { mu sync.Mutex - byKey map[string]delivery.PublishedPackages - series map[string]delivery.SeriesPackage + byKey map[string]meshcatalog.PackagesByModuleValue + series map[string]meshcatalog.PackagesByPackageSeriesValue unread map[string]string // answered is whether the state's first reading ended (Answered): until then the checker knows no package's name. answered bool @@ -36,7 +38,7 @@ func (p *publishedPackages) Put(key string, raw []byte, deleted bool) { p.mu.Lock() defer p.mu.Unlock() if p.byKey == nil { - p.byKey, p.series, p.unread = map[string]delivery.PublishedPackages{}, map[string]delivery.SeriesPackage{}, map[string]string{} + p.byKey, p.series, p.unread = map[string]meshcatalog.PackagesByModuleValue{}, map[string]meshcatalog.PackagesByPackageSeriesValue{}, map[string]string{} } delete(p.byKey, key) delete(p.series, key) @@ -44,8 +46,8 @@ func (p *publishedPackages) Put(key string, raw []byte, deleted bool) { if deleted { return } - if delivery.IsSeriesPackageKey(key) { - s, err := delivery.DecodeSeriesPackage(raw) + if isSeriesPackageKey(key) { + s, err := meshcatalog.DecodePackagesByPackageSeriesValue(raw) if err != nil { p.unread[key] = err.Error() return @@ -53,7 +55,7 @@ func (p *publishedPackages) Put(key string, raw []byte, deleted bool) { p.series[key] = s return } - v, err := delivery.DecodePublishedPackages(raw) + v, err := meshcatalog.DecodePackagesByModuleValue(raw) if err != nil { p.unread[key] = err.Error() return @@ -64,10 +66,10 @@ func (p *publishedPackages) Put(key string, raw []byte, deleted bool) { // LatestPackages is the latest published version of every mesh Go module the indexer holds, by module path: the commit // its repository was published at. A module the state does not hold is left out: a check builds it at the version // go.mod names. -func (p *publishedPackages) LatestPackages() delivery.PackageVersions { +func (p *publishedPackages) LatestPackages() buildstep.PackageVersions { p.mu.Lock() defer p.mu.Unlock() - out := delivery.PackageVersions{} + out := buildstep.PackageVersions{} for _, v := range p.byKey { if v.Module != "" && v.Commit != "" { out[v.Module] = v.Commit @@ -93,3 +95,7 @@ func (p *publishedPackages) PackageNames() map[string]string { } return out } + +// isSeriesPackageKey says a key of the indexer's state packages is a package's series (`.`): a module's key +// is its path escaped, which holds no dot (keys.EscapeName). +func isSeriesPackageKey(key string) bool { return strings.Contains(key, ".") } diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/tags.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/tags.go index e75b2b4b..1f8782ec 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/tags.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/tags.go @@ -11,11 +11,10 @@ import ( "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" - "git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry" - "git.novox.be/novox/mesh-contracts/go/seats/npmpackageregistry" - "git.novox.be/novox/mesh-sdk/go/delivery" - "mesh-delivery-checker/internal/check" + + "git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry" ) // publishedSeats are the two package registry seats' published, by subject (their contracts in mesh-contracts): heard on @@ -46,7 +45,7 @@ func (t tagAsks) Want(ctx context.Context, w check.TagWanted) error { msg := nats.NewMsg(eventSubject(check.EventTagWanted)) id := fmt.Sprintf("tag-wanted.%s.%s.%s.%s", w.Repository, w.Head, w.Module, w.Package) msg.Header.Set("x-event-id", id) - msg.Header.Set("x-source", delivery.CheckerModule) + msg.Header.Set("x-source", check.CheckerModule) msg.Header.Set("x-node", t.node) msg.Header.Set("x-time", t.now().UTC().Format(time.RFC3339Nano)) msg.Header.Set("content-type", "application/json") @@ -65,7 +64,7 @@ func (t tagAsks) Say(ctx context.Context, v check.VerdictEvent) error { msg := nats.NewMsg(eventSubject(check.EventVerdict)) id := fmt.Sprintf("verdict.%s.%d.%s.%s.%x", v.Repository, v.Pull, v.Head, v.Verdict, sha256.Sum256([]byte(strings.Join(v.OutOfStep, "\n")))) msg.Header.Set("x-event-id", id) - msg.Header.Set("x-source", delivery.CheckerModule) + msg.Header.Set("x-source", check.CheckerModule) msg.Header.Set("x-node", t.node) msg.Header.Set("x-time", t.now().UTC().Format(time.RFC3339Nano)) msg.Header.Set("content-type", "application/json") diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/toolchains.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/toolchains.go index bfce12e0..7ac53a3b 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/toolchains.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/toolchains.go @@ -12,8 +12,6 @@ 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-builder/buildrun" ) @@ -58,7 +56,7 @@ func (w *toolchainWatch) ensure(module string) error { if w.watched[module] { return nil } - name := delivery.ToolchainsBucket(module) + name := buildrun.ToolchainsBucket(module) kv, err := w.js.KeyValue(w.ctx, name) switch { case errors.Is(err, jetstream.ErrBucketNotFound): diff --git a/modules/mesh-delivery-checker/go.mod b/modules/mesh-delivery-checker/go.mod index cc0535e9..4b97c771 100644 --- a/modules/mesh-delivery-checker/go.mod +++ b/modules/mesh-delivery-checker/go.mod @@ -4,21 +4,29 @@ go 1.26.0 require ( git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry v0.0.0-00010101000000-000000000000 + git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000 git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261104174359-05b3e2e1b94f github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 ) -require git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep v0.0.0-20261104174638-125929dbc459 - require ( git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-20261104174344-ae5266e77639 - git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry v0.0.0-20261104174344-ae5266e77639 git.novox.be/novox/mesh-contracts/go/seats/meshconfig v0.0.0-20261104174344-ae5266e77639 - git.novox.be/novox/mesh-contracts/go/seats/meshdeliverychecker v0.0.0-20261104174344-ae5266e77639 git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-20261104174344-ae5266e77639 - git.novox.be/novox/mesh-contracts/go/seats/npmpackageregistry v0.0.0-20261104174344-ae5266e77639 git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun 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 diff --git a/modules/mesh-delivery-checker/internal/check/adr548_test.go b/modules/mesh-delivery-checker/internal/check/adr548_test.go index 6b72ac11..d88c4f6b 100644 --- a/modules/mesh-delivery-checker/internal/check/adr548_test.go +++ b/modules/mesh-delivery-checker/internal/check/adr548_test.go @@ -6,7 +6,11 @@ import ( "testing" "time" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" ) // One check per delivery (novox/hq ADR 0548): the checker's part. A merge turn holds new checks off its repository (§3); @@ -17,7 +21,7 @@ import ( func turnOf(t testing.TB, repo, group, heads string) []byte { t.Helper() at := time.Date(2026, 10, 26, 9, 0, 0, 0, time.UTC) - data, err := delivery.EncodeTurn(delivery.Turn{Repository: repo, Waiting: []delivery.TurnWait{{Group: group, Heads: heads, Approved: at, Since: at}}}) + data, err := meshdeliveryclient.EncodeTurn(meshdelivery.TurnsByRepositoryValue{Repository: repo, Waiting: []meshdelivery.TurnsByRepositoryValueWaitingItem{{Group: group, Heads: heads, Approved: at, Since: at}}}) if err != nil { t.Fatal(err) } @@ -26,7 +30,7 @@ func turnOf(t testing.TB, repo, group, heads string) []byte { func turnKey(t testing.TB, repo string) string { t.Helper() - k, err := delivery.TurnsKey(repo) + k, err := meshdeliveryclient.TurnsKey(repo) if err != nil { t.Fatal(err) } @@ -45,10 +49,10 @@ func waitFor(t testing.TB, what string, ok func() bool) { } // pullWithFP is a pull request touching the module m at the fingerprint `src2:` and 64 times the hex digit d. -func pullWithFP(repo string, number int, head string, d string) delivery.CandidatePull { +func pullWithFP(repo string, number int, head string, d string) meshcatalog.CandidatesByGroupValuePullsItem { p := pullOf(repo, number, head) p.Modules = []string{"m"} - p.Fps = map[string]delivery.CandidateModule{"m": {FP2: "src2:" + strings.Repeat(d, 64)}} + p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"m": {Fp2: "src2:" + strings.Repeat(d, 64)}} return p } @@ -76,7 +80,7 @@ func TestOneCheckPerDelivery(t *testing.T) { } r.ch.Reconsider(ctx) // what the watch of turns does on the delete r.ch.Wait() - if j.count() != 1 || r.verdict(t, keyOf(t, "feat/x", cand)).State != delivery.VerdictPass { + if j.count() != 1 || r.verdict(t, keyOf(t, "feat/x", cand)).State != meshdeliverycheckerclient.VerdictPass { t.Errorf("judged %d times after the turn went, want once", j.count()) } }, @@ -86,7 +90,7 @@ func TestOneCheckPerDelivery(t *testing.T) { turns := &Turns{} r.ch.Turns = turns cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1)) - c, _ := delivery.DecodeCandidate(cand) + c, _ := meshcatalog.DecodeCandidatesByGroupValue(cand) turns.Put(turnKey(t, repoSDK), turnOf(t, repoSDK, "fix/other", "0123456789ab"), false) turns.Put(turnKey(t, repoA), turnOf(t, repoA, "feat/x", GateHeads(c)), false) r.ch.Candidate(ctx, "feat%2Fx", cand) @@ -113,7 +117,7 @@ func TestOneCheckPerDelivery(t *testing.T) { r.ch.Reconsider(ctx) close(release) r.ch.Wait() - if j.count() != 1 || r.verdict(t, keyOf(t, "feat/x", cand)).State != delivery.VerdictPass { + if j.count() != 1 || r.verdict(t, keyOf(t, "feat/x", cand)).State != meshdeliverycheckerclient.VerdictPass { t.Errorf("judged %d times; the running check is not ended by the turn", j.count()) } }, @@ -144,7 +148,7 @@ func TestOneCheckPerDelivery(t *testing.T) { if j.count() != 2 || kinds[0] != KindNew || kinds[1] != KindTrunk || from[0] != nil || from[1][repoA] != trunkA { t.Fatalf("judged %d times as %v carried from %v; want a whole check, then one carry from %s", j.count(), kinds, from, trunkA[:12]) } - if v.State != delivery.VerdictPass || v.Trunk[repoA] != trunkA2 || v.CarriedFrom[repoA] != trunkA { + if v.State != meshdeliverycheckerclient.VerdictPass || v.Trunk[repoA] != trunkA2 || v.CarriedFrom[repoA] != trunkA { t.Errorf("the verdict is %s on %v carried from %v; want a pass on the new trunk carried from the old", v.State, v.Trunk, v.CarriedFrom) } if s := owedState(t, r, repoA, 7, head1); s != "success" { @@ -160,7 +164,7 @@ func TestOneCheckPerDelivery(t *testing.T) { j := &judgeFunc{f: func(_ context.Context, c *Case, _ int) (Outcome, error) { kept = append(kept, c.Composition != nil) v := passing() - v.Composition = &delivery.Composition{State: delivery.CompositionPass, SDK: "v0.1.14-x", FPSet: c.FPSet} + v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionPass, Sdk: "v0.1.14-x", Fpset: c.FPSet} return Outcome{Verdict: v}, nil }} r := newRig(t, j) @@ -220,7 +224,7 @@ func TestOneCheckPerDelivery(t *testing.T) { j := &judgeFunc{f: func(_ context.Context, c *Case, call int) (Outcome, error) { kinds, from = append(kinds, c.Kind), append(from, c.CarriedFrom) if call == 2 { - return Outcome{Verdict: delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: "ace", + return Outcome{Verdict: meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Machine: "ace", Words: repoA + "#7 " + ConflictWords + "go.mod"}}}, nil } return Outcome{Verdict: passing()}, nil @@ -260,7 +264,7 @@ func TestOneCheckPerDelivery(t *testing.T) { t.Errorf("the line names %s on %s first, want the head on the new trunk", head, trunk) } cs.CarriedFrom = map[string]string{repoA: trunkA2} - pending := Line(delivery.Verdict{State: delivery.VerdictRunning}, cs, p, nil, nil, nil) + pending := Line(meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictRunning}, cs, p, nil, nil, nil) if !strings.HasPrefix(pending, "being carried to 111111111111 on aaaaaaaaaaaa | from abababababab") { t.Errorf("the pending line of a carry = %q", pending) } @@ -274,10 +278,10 @@ func TestOneCheckPerDelivery(t *testing.T) { r.ch.Candidate(ctx, "feat%2Fa", candA) r.ch.Candidate(ctx, "feat%2Fc", candC) r.ch.Wait() - a, _ := delivery.DecodeCandidate(candA) - c, _ := delivery.DecodeCandidate(candC) + a, _ := meshcatalog.DecodeCandidatesByGroupValue(candA) + c, _ := meshcatalog.DecodeCandidatesByGroupValue(candC) at := time.Date(2026, 10, 26, 9, 0, 0, 0, time.UTC) - turn, err := delivery.EncodeTurn(delivery.Turn{Repository: repoA, Waiting: []delivery.TurnWait{ + turn, err := meshdeliveryclient.EncodeTurn(meshdelivery.TurnsByRepositoryValue{Repository: repoA, Waiting: []meshdelivery.TurnsByRepositoryValueWaitingItem{ {Group: "feat/a", Heads: GateHeads(a), Approved: at, Since: at}, {Group: "feat/c", Heads: GateHeads(c), Approved: at.Add(time.Minute), Since: at}}}) if err != nil { t.Fatal(err) @@ -322,7 +326,7 @@ func TestOneCheckPerDelivery(t *testing.T) { if call == 1 { return Outcome{Verdict: passing()}, nil } - return Outcome{Verdict: delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Test: "unit: m/a.TestX", Words: "want 4"}}}, nil + return Outcome{Verdict: meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Test: "unit: m/a.TestX", Words: "want 4"}}}, nil }} r := newRig(t, j) cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1)) @@ -332,7 +336,7 @@ func TestOneCheckPerDelivery(t *testing.T) { r.ch.TrunkMoved(ctx) r.ch.Wait() v := r.verdict(t, keyOf(t, "feat/x", cand)) - if v.State != delivery.VerdictFail || v.CarriedFrom != nil || !strings.HasPrefix(v.Failed.Words, "want 4") || + if v.State != meshdeliverycheckerclient.VerdictFail || v.CarriedFrom != nil || !strings.HasPrefix(v.Failed.Words, "want 4") || !strings.Contains(v.Failed.Words, repoA+" "+trunkA[:12]+" to "+trunkA2[:12]) { t.Errorf("the failed carry is %s carried from %v with words %q; want the test's words, then the move", v.State, v.CarriedFrom, v.Failed.Words) } diff --git a/modules/mesh-delivery-checker/internal/check/askagain.go b/modules/mesh-delivery-checker/internal/check/askagain.go index 7540bbeb..0c4ea8ce 100644 --- a/modules/mesh-delivery-checker/internal/check/askagain.go +++ b/modules/mesh-delivery-checker/internal/check/askagain.go @@ -7,7 +7,9 @@ import ( "fmt" "strings" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" ) // **A check is asked again by hand** (the lost check of 2026-10-28; tools-are-how-a-module-is-spoken-to: every recovery @@ -23,7 +25,7 @@ import ( // AsksState is the checker's state of the asks each running check put, by verdict key (CheckAsks); AsksBucket its bucket. const ( AsksState = "asks" - AsksBucket = delivery.CheckerModule + "_" + AsksState + AsksBucket = CheckerModule + "_" + AsksState ) // CheckAsks are the asks a check put, in order, as the state asks keeps them while it runs; its end deletes the key. @@ -70,8 +72,8 @@ const AskedAgainWords = EndedWords + "asked again by hand (" + AskAgainTool + ") var errAskedAgain = errors.New("its verdict was asked again by hand (" + AskAgainTool + ")") // IsAskedAgain says whether a verdict was written by AskAgainTool. -func IsAskedAgain(v delivery.Verdict) bool { - return IsEnded(v) && strings.HasPrefix(v.Failed.Words, AskedAgainWords) +func IsAskedAgain(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool { + return IsEnded(v) && strings.HasPrefix(failedOf(v).Words, AskedAgainWords) } // VerdictEntry is the verdicts state as the ask-again tool writes it: a key's value and revision (nil and 0 for none), @@ -104,7 +106,7 @@ func (r Reader) AskAgain(w VerdictEntry, group, why string) (AskedAgain, error) if !ok { return AskedAgain{}, fmt.Errorf("no candidate is held for the group %q", group) } - key, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash) + key, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads) if err != nil { return AskedAgain{}, err } @@ -115,11 +117,11 @@ func (r Reader) AskAgain(w VerdictEntry, group, why string) (AskedAgain, error) if len(raw) == 0 { return AskedAgain{}, fmt.Errorf("%s holds no verdict: the checker claims its check when it looks at the candidate (%s)", key, StatusReadTool) } - v, err := delivery.DecodeVerdict(raw) + v, err := meshdeliverycheckerclient.DecodeVerdict(raw) if err != nil { return AskedAgain{}, fmt.Errorf("%s holds what is not a verdict: %w", key, err) } - if v.State != delivery.VerdictRunning { + if v.State != meshdeliverycheckerclient.VerdictRunning { return AskedAgain{}, fmt.Errorf("%s is %s, not running: a verdict stands until a push or a trunk move asks it again", key, v.State) } steps := r.readSteps() @@ -150,9 +152,9 @@ func (r Reader) AskAgain(w VerdictEntry, group, why string) (AskedAgain, error) } } words := AskedAgainWords + why - ended := delivery.Verdict{Group: v.Group, Heads: v.Heads, Trunk: v.Trunk, State: delivery.VerdictFail, - Failed: delivery.FailedFirst{Machine: "checker", Words: words}} - data, err := delivery.EncodeVerdict(ended) + ended := meshdeliverychecker.VerdictsByGroupHeadsValue{Group: v.Group, Heads: v.Heads, Trunk: v.Trunk, State: meshdeliverycheckerclient.VerdictFail, + Failed: &FailedFirst{Machine: "checker", Words: words}} + data, err := meshdeliverycheckerclient.EncodeVerdict(ended) if err != nil { return AskedAgain{}, err } @@ -168,7 +170,7 @@ func (r Reader) AskAgain(w VerdictEntry, group, why string) (AskedAgain, error) // the check of those heads running in this process, which then considers them again; with none running, the candidate // that holds them is considered at once. Any other verdict is the checker's own, and left. func (ch *Checker) VerdictWritten(ctx context.Context, key string, value []byte) { - v, err := delivery.DecodeVerdict(value) + v, err := meshdeliverycheckerclient.DecodeVerdict(value) if err != nil || !IsAskedAgain(v) { return } @@ -177,22 +179,22 @@ func (ch *Checker) VerdictWritten(ctx context.Context, key string, value []byte) if l, ok := ch.live[key]; ok { l.end(errAskedAgain) ch.mu.Unlock() - ch.logf("%s: %s; the check running for it is ended and asked again", key, v.Failed.Words) + ch.logf("%s: %s; the check running for it is ended and asked again", key, failedOf(v).Words) return } var group string - var cand delivery.Candidate + var cand meshcatalog.CandidatesByGroupValue for k, c := range ch.known { - if vk, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash); err == nil && vk == key { + if vk, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads); err == nil && vk == key { group, cand = k, c break } } ch.mu.Unlock() if group == "" { - ch.logf("%s: %s, but no candidate holds these heads, so nothing is asked", key, v.Failed.Words) + ch.logf("%s: %s, but no candidate holds these heads, so nothing is asked", key, failedOf(v).Words) return } - ch.logf("%s: %s; asked again", key, v.Failed.Words) + ch.logf("%s: %s; asked again", key, failedOf(v).Words) ch.consider(ctx, group, cand) } diff --git a/modules/mesh-delivery-checker/internal/check/binaries.go b/modules/mesh-delivery-checker/internal/check/binaries.go index 5329c61d..d649b757 100644 --- a/modules/mesh-delivery-checker/internal/check/binaries.go +++ b/modules/mesh-delivery-checker/internal/check/binaries.go @@ -7,28 +7,18 @@ import ( // A change that adds or modifies a compiled program fails (issue 1577): it is build output, `go build` in a module's folder // writes one beside go.mod, and `git add -A` takes it. The build module's fetch step, which holds the merged clone, reads the -// first bytes of each changed file and reports the programs (mesh-sdk buildstep.MergeInto); the checker judges the answer. - -// NoProgramsReported is the warning of a build module whose fetch answered no programs field: an older module, which cannot -// tell, so the check does not refuse a program it did not look for. -const NoProgramsReported = "the build module did not report programs: a compiled program in the change is not refused" +// first bytes of each changed file and reports the programs (the build modules' fetch tools); the checker judges the answer. // checkBinariesOf fails a pull request whose change adds or modifies a compiled program, naming each file. It judges the -// first fetch answer of the pull request, whichever build module gave it; a build module that reports no programs leaves a -// warning on the check and passes it. +// first fetch answer of the pull request, whichever build module gave it. A fetch that made the merge and names no program has +// none: the answer's `programs` is an omitted empty list (B2g), and `merge` tells a preview was made. func (st *state) checkBinariesOf(t *target) verdictPtr { key := fmt.Sprintf("%s#%d", t.pull.Repository, t.pull.Number) if t.fetched.Merge == "" { return nil // no merge was made: nothing was changed to judge } - if t.fetched.Programs == nil { - st.c.note(fmt.Sprintf("%s: %s (%s)", key, NoProgramsReported, t.module)) - st.c.warn(key, fmt.Sprintf("a compiled program is not refused: %s reported no programs", t.module)) - st.j.logf("%s: %s (%s)", key, NoProgramsReported, t.module) - return nil - } var found []string - for _, p := range *t.fetched.Programs { + for _, p := range t.fetched.Programs { if p.Commit != "" { found = append(found, fmt.Sprintf("%s: a compiled program added in %s and removed later: it stays in history; squash the branch (issue 1577)", p.Path, short12(p.Commit))) continue diff --git a/modules/mesh-delivery-checker/internal/check/binaries_test.go b/modules/mesh-delivery-checker/internal/check/binaries_test.go index 2a3ab5e5..9e9376c5 100644 --- a/modules/mesh-delivery-checker/internal/check/binaries_test.go +++ b/modules/mesh-delivery-checker/internal/check/binaries_test.go @@ -1,52 +1,46 @@ package check import ( - "slices" "strings" "testing" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) func TestAChangeWhoseFetchReportsProgramsFailsNamingEachFile(t *testing.T) { // 1577 - prog := func(p ...string) *[]ProgramFile { - out := []ProgramFile{} + prog := func(p ...string) []buildstep.Program { + var out []buildstep.Program // no program is the omitted empty list of the typed answer for _, f := range p { path, commit, _ := strings.Cut(f, "@") // `path@commit`: added by a commit of the branch and removed later - out = append(out, ProgramFile{Path: path, Kind: "ELF", Commit: commit}) + out = append(out, buildstep.Program{Path: path, Kind: "ELF", Commit: commit}) } - return &out + return out } for _, c := range []struct { name string - programs *[]ProgramFile + programs []buildstep.Program fail []string // paths named; none passes - warned bool // the build module reported no programs field }{ - {"none found", prog(), nil, false}, - {"one program", prog("modules/m/m"), []string{"modules/m/m"}, false}, - {"two programs", prog("a/x", "b/y"), []string{"a/x", "b/y"}, false}, - {"added and removed in the branch", prog("gone/prog@0123456789abcdef0123"), []string{"gone/prog: a compiled program added in 0123456789ab and removed later: it stays in history; squash the branch (issue 1577)"}, false}, - {"an older build module reports nothing", nil, nil, true}, + {"none found", prog(), nil}, + {"one program", prog("modules/m/m"), []string{"modules/m/m"}}, + {"two programs", prog("a/x", "b/y"), []string{"a/x", "b/y"}}, + {"added and removed in the branch", prog("gone/prog@0123456789abcdef0123"), []string{"gone/prog: a compiled program added in 0123456789ab and removed later: it stays in history; squash the branch (issue 1577)"}}, } { b := newBench(t) b.fetch.Merge, b.fetch.Programs = "mmmm", c.programs cs := caseOf(t, pullOf(repoA, 7, head1)) v := b.judge(t, cs).Verdict - if got := slices.ContainsFunc(cs.Notes(), func(n string) bool { return strings.Contains(n, NoProgramsReported) }); got != c.warned { - t.Errorf("%s: warned = %v, want %v (%v)", c.name, got, c.warned, cs.Notes()) - } - line := Line(v, cs, cs.Pulls[0], nil, nil, nil) - if got := strings.Contains(line, "a compiled program is not refused: mesh-build-go reported no programs"); got != c.warned { - t.Errorf("%s: the status line holds the warning = %v, want %v: %s", c.name, got, c.warned, line) + if len(cs.Notes()) > 0 { + t.Errorf("%s: notes = %v: a merge with no program is not a fetch that reported none", c.name, cs.Notes()) } if len(c.fail) == 0 { - if v.State != delivery.VerdictPass { + if v.State != meshdeliverycheckerclient.VerdictPass { t.Errorf("%s: verdict = %+v", c.name, v) } continue } - if v.State != delivery.VerdictFail { + if v.State != meshdeliverycheckerclient.VerdictFail { t.Errorf("%s: verdict = %+v, want a failure", c.name, v) continue } diff --git a/modules/mesh-delivery-checker/internal/check/bounded.go b/modules/mesh-delivery-checker/internal/check/bounded.go index 3d432feb..5e8918c9 100644 --- a/modules/mesh-delivery-checker/internal/check/bounded.go +++ b/modules/mesh-delivery-checker/internal/check/bounded.go @@ -6,7 +6,7 @@ import ( "sync" "sync/atomic" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // ReadWorkers is how many values a read tool asks the bus for at once. Each value is a round trip from the tool through the @@ -60,7 +60,7 @@ func capKeys(keys []string, narrow string) ([]string, string) { // longer id is the later and equal lengths compare as text. A key that is no step key goes last. func newestFirst(keys []string) { id := func(k string) (string, bool) { - _, id, err := delivery.ParseStepKey(k) + _, id, err := buildstep.ParseStepKey(k) return id, err == nil } sort.Slice(keys, func(i, j int) bool { diff --git a/modules/mesh-delivery-checker/internal/check/build_change.go b/modules/mesh-delivery-checker/internal/check/build_change.go index 9c4ede7b..998e9f98 100644 --- a/modules/mesh-delivery-checker/internal/check/build_change.go +++ b/modules/mesh-delivery-checker/internal/check/build_change.go @@ -41,10 +41,10 @@ func (st *state) buildsOfTheCheck(ctx context.Context) (map[string]string, []str readable := true for _, p := range st.c.Pulls { for module, cm := range p.Fps { - if cm.FP2 == "" || cm.Manifest == "" { + if cm.Fp2 == "" || cm.Manifest == "" { continue // the builder builds no fingerprint of it (candidates.go: none of the new form, or no manifest) } - wait[module] = cm.FP2 + wait[module] = cm.Fp2 if !readable || st.j.Read == nil { readable = false continue diff --git a/modules/mesh-delivery-checker/internal/check/builds.go b/modules/mesh-delivery-checker/internal/check/builds.go index 2afa2d42..8fe9abf8 100644 --- a/modules/mesh-delivery-checker/internal/check/builds.go +++ b/modules/mesh-delivery-checker/internal/check/builds.go @@ -9,7 +9,9 @@ import ( "strings" "time" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" ) // The states of a build the status line says (ADR 0484 clause 7). @@ -22,18 +24,18 @@ const ( // BuildStates reads the builder's build of a module at a fingerprint of the new form (builds..): the // value and whether the key exists. type BuildStates interface { - Build(ctx context.Context, module, fp2 string) (delivery.BuilderBuild, bool, error) + Build(ctx context.Context, module, fp2 string) (builds.BuilderBuild, bool, error) } // NoteOf is the state of a build as the line says it: done is built, failed is failed, and one queued, building or not // begun is waiting. -func NoteOf(b delivery.BuilderBuild, found bool) string { +func NoteOf(b builds.BuilderBuild, found bool) string { switch { case !found: return BuildWaiting - case b.State == delivery.BuildDone: + case b.State == builds.BuildDone: return BuildBuilt - case b.State == delivery.BuildFailed: + case b.State == builds.BuildFailed: return BuildFailed } return BuildWaiting @@ -48,8 +50,8 @@ func (j *Examiner) buildNotes(ctx context.Context, c *Case) []BuildNote { fps := map[string]string{} for _, p := range c.Pulls { for module, cm := range p.Fps { - if cm.FP2 != "" { - fps[module] = cm.FP2 + if cm.Fp2 != "" { + fps[module] = cm.Fp2 } } } @@ -79,7 +81,7 @@ const DefaultBuildWait = 30 * time.Minute type BuildEnds interface { // Ended waits until the build of every module at its fingerprint (module to fingerprint) is done or failed, or ctx ends, // and answers the builds that ended, by module. An error is only builds that cannot be heard at all. - Ended(ctx context.Context, builds map[string]string) (map[string]delivery.BuilderBuild, error) + Ended(ctx context.Context, builds map[string]string) (map[string]builds.BuilderBuild, error) // Done waits until the build of every module at its fingerprint is done (a failed one built again, builder_build_again), // and says whether they all are; false when ctx ends first or they cannot be heard. Done(ctx context.Context, builds map[string]string) bool @@ -91,19 +93,19 @@ const BuildFailedTest = "build: " // failedBuilds are the candidate's builds a failed verdict names as failed (BuildFailedTest), by module, at the fingerprint // the case carries for it; none for a verdict that names no failed build. -func failedBuilds(v delivery.Verdict, cs *Case) map[string]string { - if v.State != delivery.VerdictFail { +func failedBuilds(v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case) map[string]string { + if v.State != meshdeliverycheckerclient.VerdictFail { return nil } out := map[string]string{} - for _, f := range append([]delivery.FailedFirst{v.Failed}, v.Failures...) { + for _, f := range append([]FailedFirst{failedOf(v)}, failedList(v.Failures)...) { module, named := strings.CutPrefix(f.Test, BuildFailedTest) if !named { continue } for _, p := range cs.Pulls { - if cm, ok := p.Fps[module]; ok && cm.FP2 != "" { - out[module] = cm.FP2 + if cm, ok := p.Fps[module]; ok && cm.Fp2 != "" { + out[module] = cm.Fp2 } } } @@ -124,13 +126,13 @@ func (j *Examiner) buildWait() time.Duration { // builds of the candidate and composes with it. A failed build fails the check, naming the module and the builder's words; // a build not ended within the bound is named, and the check composes without it. It answers the words of the composition's // warnings: what the check composes without waiting on its build, and why. -func (st *state) awaitBuilds(ctx context.Context, v *delivery.Verdict) []string { +func (st *state) awaitBuilds(ctx context.Context, v *meshdeliverychecker.VerdictsByGroupHeadsValue) []string { wait, words := st.buildsOfTheCheck(ctx) if len(wait) == 0 { return words } modules := slices.Sorted(maps.Keys(wait)) - if v.State == delivery.VerdictFail { + if v.State == meshdeliverycheckerclient.VerdictFail { // The check has failed already: its red status is never held back by a build (the lead, 2026-11-03). return append(words, fmt.Sprintf("the check failed before its builds were waited on, so it composes at once, without waiting on the builds of %s (ADR 0603 §10)", strings.Join(modules, ", "))) } @@ -154,10 +156,10 @@ func (st *state) awaitBuilds(ctx context.Context, v *delivery.Verdict) []string switch { case !done: late = append(late, module) - case b.State == delivery.BuildFailed: - f := delivery.FailedFirst{Test: BuildFailedTest + module, Words: fmt.Sprintf("the candidate's build at %s failed (rebuild with builder_build_again; the check asks itself again when the build is done): %s", wait[module], b.Error)} - if v.State != delivery.VerdictFail { - v.State, v.Failed = delivery.VerdictFail, f + case b.State == builds.BuildFailed: + f := FailedFirst{Test: BuildFailedTest + module, Words: fmt.Sprintf("the candidate's build at %s failed (rebuild with builder_build_again; the check asks itself again when the build is done): %s", wait[module], b.Error)} + if v.State != meshdeliverycheckerclient.VerdictFail { + v.State, v.Failed = meshdeliverycheckerclient.VerdictFail, &f continue } addFailure(v, f) diff --git a/modules/mesh-delivery-checker/internal/check/case.go b/modules/mesh-delivery-checker/internal/check/case.go index 936e282b..7aee3f1f 100644 --- a/modules/mesh-delivery-checker/internal/check/case.go +++ b/modules/mesh-delivery-checker/internal/check/case.go @@ -9,7 +9,11 @@ import ( "sync" "time" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // Pull is one pull request the checker judges: its head, the trunk head of its repository it is merged onto and the @@ -20,7 +24,7 @@ type Pull struct { Head string Trunk string Modules []string - Fps map[string]delivery.CandidateModule + Fps map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue } // Case is one check: a candidate's pull requests that are judged (those of a repository whose trunk is not known are left @@ -48,7 +52,7 @@ type Case struct { // seriesOf are, by pull request, the changes of a package series it makes (series.go), named in its status line. seriesOf map[string][]string // seriesChanges are, by pull request, the same changes as structures (series.go), named in the verdict and its event. - seriesChanges map[string][]delivery.SeriesChange + seriesChanges map[string][]meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem notes []string // asks are the ids of the asks the check put on the build seats' streams, in order: a try names them (Try.Asks). asks []string @@ -56,7 +60,7 @@ type Case struct { // the check waits for it: the node that took it, the phase it says, its end. A try writes them into its record, so a // running try names its asks and where each stands (issue 1890). Called from the check's goroutines; it must not block // for long. - Watch func(askID string, step *delivery.Step) + Watch func(askID string, step *buildstep.Step) // Progress says what the check is doing, so a check that ran past its bound names the step it was stuck at. Progress *Progress // Kind is what asked the check (KindNew, KindNodes, KindTrunk): a carry after a trunk or sibling move is asked on the @@ -65,11 +69,11 @@ type Case struct { // CarriedFrom are, for the carry of a verdict that passed, the trunk and sibling commits it was judged on (ADR 0548 §5); // nil for a whole check. CarriedFrom map[string]string - // FPSet is the candidate's fingerprint set (Candidate.FPSet): a composition names the set it judged. + // FPSet is the candidate's fingerprint set (Candidate.Fpset): a composition names the set it judged. FPSet string // Composition is, for a carry, the composition that passed on the same fingerprint set: it is kept and not asked again, // since no fingerprint of a module it composed changed (ADR 0548 §5). Nil composes. - Composition *delivery.Composition + Composition *meshdeliverychecker.VerdictsByGroupHeadsValueComposition } // Asked records the id of an ask the check put. @@ -110,18 +114,18 @@ func (p *Progress) Now() (stage, node string) { // The checker judges pull requests into main only (ADR 0484 clause 11): the pull message carries no base, a merge into // another branch is a closed message, so every candidate is into the trunk; a repository whose trunk head is not known has // none to merge onto. It answers nil when no pull request is left to judge. -func CaseOf(c delivery.Candidate, trunk map[string]string) *Case { - key, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash) +func CaseOf(c meshcatalog.CandidatesByGroupValue, trunk map[string]string) *Case { + key, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads) if err != nil { return nil } - cs := &Case{Key: key, Group: c.Group, HeadsHash: c.HeadsHash, Heads: GateHeads(c), Trunk: map[string]string{}, Progress: &Progress{}, FPSet: c.FPSet} + cs := &Case{Key: key, Group: c.Group, HeadsHash: c.Heads, Heads: GateHeads(c), Trunk: map[string]string{}, Progress: &Progress{}, FPSet: c.Fpset} for _, p := range c.Pulls { switch head, known := trunk[p.Repository]; { case !known || head == "": cs.Left = append(cs.Left, fmt.Sprintf("%s#%d: %s", p.Repository, p.Number, UntrunkedWordsOf([]string{p.Repository}))) default: - cs.Pulls = append(cs.Pulls, Pull{Repository: p.Repository, Number: p.Number, Head: p.Head.Head, Trunk: head, Modules: p.Modules, Fps: p.Fps}) + cs.Pulls = append(cs.Pulls, Pull{Repository: p.Repository, Number: int(p.Number), Head: p.Head, Trunk: head, Modules: p.Modules, Fps: p.Fps}) cs.Trunk[p.Repository] = head } } @@ -151,8 +155,8 @@ func UntrunkedWordsOf(repos []string) string { } // IsUntrunked says whether a verdict says its heads cannot be judged for want of a trunk head (UntrunkedWords). -func IsUntrunked(v delivery.Verdict) bool { - return v.State == delivery.VerdictFail && v.Failed.Machine == "checker" && strings.HasPrefix(v.Failed.Words, UntrunkedWords) +func IsUntrunked(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool { + return v.State == meshdeliverycheckerclient.VerdictFail && failedOf(v).Machine == "checker" && strings.HasPrefix(failedOf(v).Words, UntrunkedWords) } // **A candidate outside the mesh is not the checker's** (sweep 2026-10-30 item 7): the forge carries repositories the mesh @@ -168,7 +172,7 @@ const OutsideWords = "outside the mesh: the gate's source.main names no trunk he // Outside says every pull request of c is of a repository trunk names no head of and touches no module. Never with // trunk unknown (nil): before the gate's source is read nothing is outside. -func Outside(c delivery.Candidate, trunk map[string]string) bool { +func Outside(c meshcatalog.CandidatesByGroupValue, trunk map[string]string) bool { if trunk == nil || len(c.Pulls) == 0 { return false } @@ -182,11 +186,11 @@ func Outside(c delivery.Candidate, trunk map[string]string) bool { // leftOf are the pull requests of a candidate whose repository has no trunk head known, with Trunk empty, in repository and // number order; nil when every repository's is known. -func leftOf(c delivery.Candidate, trunk map[string]string) []Pull { +func leftOf(c meshcatalog.CandidatesByGroupValue, trunk map[string]string) []Pull { var out []Pull for _, p := range c.Pulls { if trunk[p.Repository] == "" { - out = append(out, Pull{Repository: p.Repository, Number: p.Number, Head: p.Head.Head, Modules: p.Modules, Fps: p.Fps}) + out = append(out, Pull{Repository: p.Repository, Number: int(p.Number), Head: p.Head, Modules: p.Modules, Fps: p.Fps}) } } sort.SliceStable(out, func(i, j int) bool { @@ -199,15 +203,15 @@ func leftOf(c delivery.Candidate, trunk map[string]string) []Pull { } // GateHeads is the hash the gate matches the members an Approve covers by (mesh-catalog mesh-delivery-gate onestatus.go -// headsOf): delivery.HeadsHash over every pull request of the candidate, hq's included, as +// headsOf): meshdeliveryclient.Heads over every pull request of the candidate, hq's included, as // `#=`. A lone pull request is a group of one. Empty for a candidate // with no pull request. -func GateHeads(c delivery.Candidate) string { - heads := make([]delivery.Head, 0, len(c.Pulls)) +func GateHeads(c meshcatalog.CandidatesByGroupValue) string { + heads := make([]meshdeliveryclient.Head, 0, len(c.Pulls)) for _, p := range c.Pulls { - heads = append(heads, delivery.Head{Repository: strings.ToLower(p.Repository), Number: p.Number, Head: strings.ToLower(p.Head.Head)}) + heads = append(heads, meshdeliveryclient.Head{Repository: strings.ToLower(p.Repository), Number: int(p.Number), Head: strings.ToLower(p.Head)}) } - hash, err := delivery.HeadsHash(heads) + hash, err := meshdeliveryclient.HeadsHash(heads) if err != nil { return "" } @@ -227,7 +231,7 @@ type Trunks struct { func (t *Trunks) Put(value []byte, deleted bool) bool { var heads map[string]string if !deleted { - s, err := delivery.DecodeSource(value) + s, err := meshdeliveryclient.DecodeSource(value) if err != nil { return false } diff --git a/modules/mesh-delivery-checker/internal/check/checker.go b/modules/mesh-delivery-checker/internal/check/checker.go index f42b47fc..c6d5fa60 100644 --- a/modules/mesh-delivery-checker/internal/check/checker.go +++ b/modules/mesh-delivery-checker/internal/check/checker.go @@ -11,7 +11,14 @@ import ( "sync" "time" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" + "git.novox.be/novox/mesh-sdk/go/keys" ) // DefaultBound is how long a check may run before it is ended failed naming the step it was stuck at (ADR 0484, issue 1678): @@ -38,7 +45,7 @@ type Announcer func(ctx context.Context, event, key string) // Outcome is what judging a case gives: the verdict (state, failure, levels, times, composition; the checker adds the // group, the heads and the trunk) and, for the line, what the check knows of the builds of the modules it touches. type Outcome struct { - Verdict delivery.Verdict + Verdict meshdeliverychecker.VerdictsByGroupHeadsValue Builds []BuildNote // Slowest names the slowest tests of a level that went over its bound ("unit pkg.Test 1.2s"). Slowest []string @@ -80,7 +87,7 @@ type Checker struct { // Turns are the gate's merge turns (ADR 0548 §3): no check is claimed of a repository a turn names, but the check of the // waiting delivery's own heads; nil holds none. Turns interface { - Holding(repositories []string, heads string) (string, delivery.TurnWait, bool) + Holding(repositories []string, heads string) (string, meshdelivery.TurnsByRepositoryValueWaitingItem, bool) } // Bound is how long one check may run; none when 0 (DefaultBound). Bound time.Duration @@ -105,17 +112,17 @@ type Checker struct { once sync.Once wg sync.WaitGroup mu sync.Mutex - known map[string]delivery.Candidate // by candidates key - inflight map[string]bool // by verdict key - again map[string]bool // by verdict key: asked again while it ran - tried map[string]bool // by verdict key: looked at since this process started - epoch uint64 // changes of the nodes seen since this process started - nodesFail map[string]uint64 // by verdict key: the epoch a failure of the nodes was written at - held map[string]string // by verdict key: the turn that holds its check off, as last said - live map[string]liveCheck // by verdict key: the check running in this process, ended when its heads go - settling map[string]*window // by candidates key: the window its heads settle in - rebuilt map[string]bool // by verdict key: every failed build its verdict names is done again - awaiting map[string]bool // by verdict key: its failed builds are heard (awaitRebuilt) + known map[string]meshcatalog.CandidatesByGroupValue // by candidates key + inflight map[string]bool // by verdict key + again map[string]bool // by verdict key: asked again while it ran + tried map[string]bool // by verdict key: looked at since this process started + epoch uint64 // changes of the nodes seen since this process started + nodesFail map[string]uint64 // by verdict key: the epoch a failure of the nodes was written at + held map[string]string // by verdict key: the turn that holds its check off, as last said + live map[string]liveCheck // by verdict key: the check running in this process, ended when its heads go + settling map[string]*window // by candidates key: the window its heads settle in + rebuilt map[string]bool // by verdict key: every failed build its verdict names is done again + awaiting map[string]bool // by verdict key: its failed builds are heard (awaitRebuilt) // outsideSaid are the candidates outside the mesh said in the log, by group and heads. outsideSaid map[string]bool } @@ -155,8 +162,8 @@ const DefaultSettle = 15 * time.Second const EndedWords = "ended: " // IsEnded says whether a verdict was ended without being judged (EndedWords). -func IsEnded(v delivery.Verdict) bool { - return v.State == delivery.VerdictFail && v.Failed.Machine == "checker" && strings.HasPrefix(v.Failed.Words, EndedWords) +func IsEnded(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool { + return v.State == meshdeliverycheckerclient.VerdictFail && failedOf(v).Machine == "checker" && strings.HasPrefix(failedOf(v).Words, EndedWords) } // superseded is the cause a running check is ended with: the heads that replaced its own, or none when its group went. @@ -171,7 +178,7 @@ func (s superseded) Error() string { func (ch *Checker) init() { ch.once.Do(func() { - ch.known, ch.inflight, ch.again, ch.tried = map[string]delivery.Candidate{}, map[string]bool{}, map[string]bool{}, map[string]bool{} + ch.known, ch.inflight, ch.again, ch.tried = map[string]meshcatalog.CandidatesByGroupValue{}, map[string]bool{}, map[string]bool{}, map[string]bool{} ch.nodesFail, ch.held = map[string]uint64{}, map[string]string{} ch.live, ch.settling = map[string]liveCheck{}, map[string]*window{} ch.rebuilt, ch.awaiting = map[string]bool{}, map[string]bool{} @@ -216,19 +223,19 @@ func (ch *Checker) Wait() { ch.wg.Wait() } // checked. A value that is not a candidate is said and left. func (ch *Checker) Candidate(ctx context.Context, key string, value []byte) { ch.init() - c, err := delivery.DecodeCandidate(value) + c, err := meshcatalog.DecodeCandidatesByGroupValue(value) if err != nil { ch.logf("candidates.%s holds what is not a candidate, left: %v", key, err) return } - vkey, _ := delivery.CheckerVerdictKey(c.Group, c.HeadsHash) + vkey, _ := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads) ch.mu.Lock() prev, had := ch.known[key] ch.known[key] = c // A check of heads this candidate no longer holds is ended at once (issue 1890): a member's new head, a member joining // or leaving. ch.endOthers(key, vkey, GateHeads(c)) - moved := !had || prev.HeadsHash != c.HeadsHash + moved := !had || prev.Heads != c.Heads if ch.Settle > 0 && (moved || ch.settling[key] != nil) { // The group's heads changed: its check waits until they have been the same for Settle, so a group whose members // are announced one after another is checked once, whole. A later change starts the window again. The wait is a @@ -279,9 +286,9 @@ func (ch *Checker) VerdictDeleted(ctx context.Context, key string) { return } var group string - var cand delivery.Candidate + var cand meshcatalog.CandidatesByGroupValue for k, c := range ch.known { - if vk, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash); err == nil && vk == key { + if vk, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads); err == nil && vk == key { group, cand = k, c break } @@ -324,7 +331,7 @@ func (ch *Checker) NodesChanged(ctx context.Context) { func (ch *Checker) Reconsider(ctx context.Context) { ch.init() ch.mu.Lock() - known := make(map[string]delivery.Candidate, len(ch.known)) + known := make(map[string]meshcatalog.CandidatesByGroupValue, len(ch.known)) for k, c := range ch.known { known[k] = c } @@ -341,7 +348,7 @@ func (ch *Checker) heads() map[string]string { return ch.Trunks.Heads() } -func (ch *Checker) consider(ctx context.Context, key string, c delivery.Candidate) { +func (ch *Checker) consider(ctx context.Context, key string, c meshcatalog.CandidatesByGroupValue) { heads := ch.heads() if Outside(c, heads) { ch.outside(ctx, c) @@ -422,10 +429,10 @@ func (ch *Checker) consider(ctx context.Context, key string, c delivery.Candidat // UntrunkedWords, the members whose trunk is known too, as their group is not judged. It is logged once per head: when a // status or the verdict is written, never at a look that finds them already said. Once the trunk head is known the verdict // is stale (it names no trunk) and the heads are judged whole (prepare, claim). -func (ch *Checker) untrunked(ctx context.Context, c delivery.Candidate, left []Pull) { - key, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash) +func (ch *Checker) untrunked(ctx context.Context, c meshcatalog.CandidatesByGroupValue, left []Pull) { + key, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads) if err != nil { - ch.logf("candidates of %s: no verdict key for heads %q, so it cannot be said that they cannot be judged: %v", c.Group, c.HeadsHash, err) + ch.logf("candidates of %s: no verdict key for heads %q, so it cannot be said that they cannot be judged: %v", c.Group, c.Heads, err) return } repos := make([]string, 0, len(left)) @@ -433,37 +440,37 @@ func (ch *Checker) untrunked(ctx context.Context, c delivery.Candidate, left []P repos = append(repos, p.Repository) } words := UntrunkedWordsOf(repos) - v := delivery.Verdict{Group: c.Group, Heads: GateHeads(c), Trunk: map[string]string{}, State: delivery.VerdictFail, - Failed: delivery.FailedFirst{Machine: "checker", Words: words}} - data, err := delivery.EncodeVerdict(v) + v := meshdeliverychecker.VerdictsByGroupHeadsValue{Group: c.Group, Heads: GateHeads(c), Trunk: map[string]string{}, State: meshdeliverycheckerclient.VerdictFail, + Failed: &FailedFirst{Machine: "checker", Words: words}} + data, err := meshdeliverycheckerclient.EncodeVerdict(v) if err != nil { ch.logf("%s: %s, and the verdict cannot say so: %v", key, words, err) return } said, err := putOnce(ctx, ch.Verdicts, key, data, func(cur []byte) bool { - held, err := delivery.DecodeVerdict(cur) - return err == nil && IsUntrunked(held) && held.Failed.Words == words // the same words stand; anything else is replaced + held, err := meshdeliverycheckerclient.DecodeVerdict(cur) + return err == nil && IsUntrunked(held) && failedOf(held).Words == words // the same words stand; anything else is replaced }) if err != nil { ch.logf("%s: %s, and the verdict cannot say so: %v", key, words, err) } for _, cp := range c.Pulls { - okey, err := delivery.CheckerOwedKey(cp.Repository, cp.Number, cp.Head.Head) + okey, err := CheckerOwedKey(cp.Repository, int(cp.Number), cp.Head) if err != nil { ch.logf("%s: %v", key, err) continue } - o := delivery.Owed{Act: delivery.OwedStatus, Context: delivery.StatusContextCheck, State: "error", + o := meshdeliverychecker.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: meshdeliveryclient.StatusContextCheck, State: "error", Target: strings.TrimSuffix(ch.Link, "/") + "/" + key, Text: clipLine(words, MaxLine)} - data, err := delivery.EncodeCheckerOwed(o) + data, err := EncodeCheckerOwed(o) if err != nil { ch.logf("%s: the status of %s cannot be written: %v", key, okey, err) continue } wrote, err := putOnce(ctx, ch.Owed, okey, data, func(cur []byte) bool { - held, err := delivery.DecodeCheckerOwed(cur) - o.V = delivery.Version - return err == nil && delivery.SameOwed(held, o) + held, err := DecodeCheckerOwed(cur) + o.V = keys.Version + return err == nil && held == o }) if err != nil { ch.logf("%s: the status of %s cannot be written: %v", key, okey, err) @@ -488,11 +495,11 @@ func (ch *Checker) untrunked(ctx context.Context, c delivery.Candidate, left []P // outside takes a candidate outside the mesh (case.go Outside): no verdict, and every status owed for its pull requests // taken away, so the forge module stops writing one into a repository the mesh's forge account may not write. Said once // per heads in the log. -func (ch *Checker) outside(ctx context.Context, c delivery.Candidate) { +func (ch *Checker) outside(ctx context.Context, c meshcatalog.CandidatesByGroupValue) { var names []string for _, p := range c.Pulls { - names = append(names, fmt.Sprintf("%s#%d@%s", p.Repository, p.Number, short12(p.Head.Head))) - key, err := delivery.CheckerOwedKey(p.Repository, p.Number, p.Head.Head) + names = append(names, fmt.Sprintf("%s#%d@%s", p.Repository, p.Number, short12(p.Head))) + key, err := CheckerOwedKey(p.Repository, int(p.Number), p.Head) if err != nil { continue } @@ -508,7 +515,7 @@ func (ch *Checker) outside(ctx context.Context, c delivery.Candidate) { ch.logf("%s: the status owed of a pull request outside the mesh could not be taken away: %v", key, err) } } - said := c.Group + "@" + c.HeadsHash + said := c.Group + "@" + c.Heads ch.mu.Lock() first := !ch.outsideSaid[said] ch.outsideSaid[said] = true @@ -538,9 +545,9 @@ func (ch *Checker) prepare(ctx context.Context, group string, cs *Case) { if err != nil || !found { return } - v, err := delivery.DecodeVerdict(value) + v, err := meshdeliverycheckerclient.DecodeVerdict(value) switch { - case err != nil || v.State == delivery.VerdictRunning: + case err != nil || v.State == meshdeliverycheckerclient.VerdictRunning: return case IsNodesFailure(v): cs.Kind = KindNodes @@ -555,9 +562,9 @@ func (ch *Checker) prepare(ctx context.Context, group string, cs *Case) { return // ended unjudged when its heads went (issue 1890), or never judged for want of a trunk head (issue 1422): checked whole } cs.Kind = KindTrunk - if v.State == delivery.VerdictPass && ch.stale(v, cs) { + if v.State == meshdeliverycheckerclient.VerdictPass && ch.stale(v, cs) { cs.CarriedFrom = judgedOn(v) - if c := v.Composition; c != nil && c.State == delivery.CompositionPass && c.FPSet != "" && c.FPSet == cs.FPSet { + if c := v.Composition; c != nil && c.State == meshdeliverycheckerclient.CompositionPass && c.Fpset != "" && c.Fpset == cs.FPSet { kept := *c cs.Composition = &kept } @@ -566,7 +573,7 @@ func (ch *Checker) prepare(ctx context.Context, group string, cs *Case) { // stale says whether a verdict was judged on commits that are no longer the case's: a trunk it merged moved, or a sibling it // read at its trunk's head (ADR 0548 §6). -func (ch *Checker) stale(v delivery.Verdict, cs *Case) bool { +func (ch *Checker) stale(v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case) bool { return !sameTrunk(v.Trunk, cs.Trunk) || besideMoved(v.Beside, ch.besideNow()) } @@ -603,7 +610,7 @@ func besideMoved(beside, now map[string]string) bool { } // judgedOn are the trunk and sibling commits a verdict was judged on, by repository. -func judgedOn(v delivery.Verdict) map[string]string { +func judgedOn(v meshdeliverychecker.VerdictsByGroupHeadsValue) map[string]string { out := make(map[string]string, len(v.Trunk)+len(v.Beside)) for r, c := range v.Beside { out[r] = c @@ -616,7 +623,7 @@ func judgedOn(v delivery.Verdict) map[string]string { // carried is what a carry's verdict names as carried from (ADR 0548 §5): the commits it was judged on before, of the // repositories the new verdict still judges or reads; nil when none of them moved. -func carried(from map[string]string, v delivery.Verdict) map[string]string { +func carried(from map[string]string, v meshdeliverychecker.VerdictsByGroupHeadsValue) map[string]string { out, moved := map[string]string{}, false for r, c := range from { now, ok := v.Trunk[r] @@ -636,7 +643,7 @@ func carried(from map[string]string, v delivery.Verdict) map[string]string { // moveWords say a carry's moves, for the words of a failure: "carried from to of ", each moved // repository, sorted. -func moveWords(from map[string]string, v delivery.Verdict) string { +func moveWords(from map[string]string, v meshdeliverychecker.VerdictsByGroupHeadsValue) string { var moves []string for r, c := range from { now, ok := v.Trunk[r] @@ -651,7 +658,7 @@ func moveWords(from map[string]string, v delivery.Verdict) string { return strings.Join(moves, ", ") } -func (ch *Checker) candidate(key string, fallback delivery.Candidate) delivery.Candidate { +func (ch *Checker) candidate(key string, fallback meshcatalog.CandidatesByGroupValue) meshcatalog.CandidatesByGroupValue { ch.mu.Lock() defer ch.mu.Unlock() if c, ok := ch.known[key]; ok { @@ -670,8 +677,8 @@ type claimed struct{ rev uint64 } // - a pass or a failure for the trunk heads the case has stands; one for other trunk heads (the trunk moved) is taken // again by compare-and-set on the same key, and so is a failure of the nodes after the nodes changed. func (ch *Checker) claim(ctx context.Context, cs *Case, first bool, epoch uint64) (*claimed, error) { - running := delivery.Verdict{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: delivery.VerdictRunning} - data, err := delivery.EncodeVerdict(running) + running := meshdeliverychecker.VerdictsByGroupHeadsValue{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: meshdeliverycheckerclient.VerdictRunning} + data, err := meshdeliverycheckerclient.EncodeVerdict(running) if err != nil { return nil, err } @@ -692,22 +699,22 @@ func (ch *Checker) claim(ctx context.Context, cs *Case, first bool, epoch uint64 } return &claimed{rev}, nil } - cur, err := delivery.DecodeVerdict(value) + cur, err := meshdeliverycheckerclient.DecodeVerdict(value) if err != nil { return nil, fmt.Errorf("%s holds what is not a verdict: %w", cs.Key, err) } retake := false switch { - case cur.State == delivery.VerdictRunning: + case cur.State == meshdeliverycheckerclient.VerdictRunning: if first { return &claimed{rev}, nil } return nil, nil case IsEnded(cur), ch.stale(cur, cs): retake = true // heads held again after their check was ended unjudged (issue 1890), or a trunk that moved - case cur.State == delivery.VerdictFail && ch.takeRebuilt(cs.Key): + case cur.State == meshdeliverycheckerclient.VerdictFail && ch.takeRebuilt(cs.Key): retake = true // every failed build it names is done again (ADR 0603 §10) - case cur.State == delivery.VerdictFail && IsNodesFailure(cur): + case cur.State == meshdeliverycheckerclient.VerdictFail && IsNodesFailure(cur): ch.mu.Lock() at, ok := ch.nodesFail[cs.Key] ch.mu.Unlock() @@ -775,15 +782,15 @@ const ConflictWords = "does not merge into the trunk: files in conflict: " // IsConflictFailure says whether a verdict failed because its head did not merge onto its trunk (ADR 0548 §7): it has no // pass to carry, so its next check, at a push or at a later trunk move, is a whole check of its head. -func IsConflictFailure(v delivery.Verdict) bool { - return v.State == delivery.VerdictFail && strings.Contains(v.Failed.Words, ConflictWords) +func IsConflictFailure(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool { + return v.State == meshdeliverycheckerclient.VerdictFail && strings.Contains(failedOf(v).Words, ConflictWords) } // IsNodesFailure says whether a verdict failed because a part of the mesh the check reads was not heard (UnheardWords): // it is judged again when the nodes change. No check fails for want of a build node any more: its asks wait in the build // seats' streams (ADR 0525 §6, issue 1912). -func IsNodesFailure(v delivery.Verdict) bool { - return v.State == delivery.VerdictFail && strings.Contains(v.Failed.Words, UnheardWords) +func IsNodesFailure(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool { + return v.State == meshdeliverycheckerclient.VerdictFail && strings.Contains(failedOf(v).Words, UnheardWords) } // try claims the check, judges it within its bound under run and writes the verdict and the owed statuses. A check that ends @@ -808,14 +815,14 @@ func (ch *Checker) try(ctx, run context.Context, key string, cs *Case) { } began := ch.now() if cs.Watch == nil && ch.Asks != nil { - cs.Watch = func(_ string, step *delivery.Step) { + cs.Watch = func(_ string, step *buildstep.Step) { if step == nil { // put: kept before its wait starts ch.recordAsks(ctx, cs.Key, cs.Asks()) } } defer ch.forgetAsks(ctx, cs.Key) } - ch.writeOwed(ctx, cs, delivery.Verdict{State: delivery.VerdictRunning}, Outcome{}) + ch.writeOwed(ctx, cs, meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictRunning}, Outcome{}) out := ch.judge(run, cs) // An ask the build seat gave up on, or whose step the build module's next process ended because it was replaced under // it, judged nothing (issue 734, IsLostAsk): the check is asked again under the same claim, its status pending, and after @@ -823,11 +830,11 @@ func (ch *Checker) try(ctx, run context.Context, key string, cs *Case) { // read at the next nudge (HeardEnds) and judged. for try := 1; IsLostAsk(out.Verdict) && run.Err() == nil; try++ { if try >= LostAskTries { - out.Verdict.Failed.Words = strings.TrimSpace(out.Verdict.Failed.Words + "\nasked " + fmt.Sprint(LostAskTries) + + out.Verdict.Failed.Words = strings.TrimSpace(failedOf(out.Verdict).Words + "\nasked " + fmt.Sprint(LostAskTries) + " times and given up each time: " + UnheardWords) break } - ch.logf("%s: %s; asked again (%d of %d)", cs.Key, out.Verdict.Failed.Words, try+1, LostAskTries) + ch.logf("%s: %s; asked again (%d of %d)", cs.Key, failedOf(out.Verdict).Words, try+1, LostAskTries) out = ch.judge(run, cs) } if ctx.Err() != nil || errors.Is(context.Cause(run), errAskedAgain) { @@ -851,23 +858,26 @@ func (ch *Checker) try(ctx, run context.Context, key string, cs *Case) { if cs.CarriedFrom != nil { // A carry (ADR 0548 §5): a pass names the commits it was carried from; a failure names the move it failed on. switch v.State { - case delivery.VerdictPass: + case meshdeliverycheckerclient.VerdictPass: v.CarriedFrom = carried(cs.CarriedFrom, v) - case delivery.VerdictFail: + case meshdeliverycheckerclient.VerdictFail: if moves := moveWords(cs.CarriedFrom, v); moves != "" { - v.Failed.Words = strings.TrimSpace(v.Failed.Words + "\nfailed on the carry over the move of " + moves + ": the move changed what these tests read") + v.Failed.Words = strings.TrimSpace(failedOf(v).Words + "\nfailed on the carry over the move of " + moves + ": the move changed what these tests read") } } } - if v.Took.Queue == 0 && !cs.Queued.IsZero() && began.After(cs.Queued) { + if tookOf(v).Queue == 0 && !cs.Queued.IsZero() && began.After(cs.Queued) { + if v.Took == nil { + v.Took = &meshdeliverychecker.VerdictsByGroupHeadsValueTook{} + } v.Took.Queue = began.Sub(cs.Queued).Milliseconds() } - data, err := delivery.EncodeVerdict(v) + data, err := meshdeliverycheckerclient.EncodeVerdict(v) if err != nil { ch.logf("%s: the verdict cannot be written: %v", cs.Key, err) - v = delivery.Verdict{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: delivery.VerdictFail, - Failed: delivery.FailedFirst{Machine: "checker", Words: "the verdict could not be written: " + err.Error()}} - if data, err = delivery.EncodeVerdict(v); err != nil { + v = meshdeliverychecker.VerdictsByGroupHeadsValue{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: meshdeliverycheckerclient.VerdictFail, + Failed: &FailedFirst{Machine: "checker", Words: "the verdict could not be written: " + err.Error()}} + if data, err = meshdeliverycheckerclient.EncodeVerdict(v); err != nil { return } } @@ -902,9 +912,9 @@ func (ch *Checker) try(ctx, run context.Context, key string, cs *Case) { // be taken back (buildrun.Asker has no withdraw), and are no longer waited for. No status is written: the heads that // replaced these have their own check, whose pending status stands. func (ch *Checker) end(ctx context.Context, cs *Case, cl *claimed, why superseded) { - v := delivery.Verdict{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: delivery.VerdictFail, - Failed: delivery.FailedFirst{Machine: "checker", Words: why.Error()}} - data, err := delivery.EncodeVerdict(v) + v := meshdeliverychecker.VerdictsByGroupHeadsValue{Group: cs.Group, Heads: cs.Heads, Trunk: cs.Trunk, State: meshdeliverycheckerclient.VerdictFail, + Failed: &FailedFirst{Machine: "checker", Words: why.Error()}} + data, err := meshdeliverycheckerclient.EncodeVerdict(v) if err == nil { _, err = ch.Verdicts.Update(ctx, cs.Key, data, cl.rev) } @@ -926,32 +936,32 @@ func (ch *Checker) judge(ctx context.Context, cs *Case) Outcome { var errBound = errors.New("the check ran past its bound") -func failure(machine, words string) delivery.Verdict { - return delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: machine, Words: words}} +func failure(machine, words string) meshdeliverychecker.VerdictsByGroupHeadsValue { + return meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Machine: machine, Words: words}} } // writeOwed writes, for each pull request judged, the status the forge bridge performs: pending while the check runs, success // or failure at its end (ADR 0484 clause 2). A key already holding the same value is not written again, so the bridge // performs one act for each change; an older head's key is never touched. -func (ch *Checker) writeOwed(ctx context.Context, cs *Case, v delivery.Verdict, out Outcome) { +func (ch *Checker) writeOwed(ctx context.Context, cs *Case, v meshdeliverychecker.VerdictsByGroupHeadsValue, out Outcome) { for _, p := range cs.Pulls { - key, err := delivery.CheckerOwedKey(p.Repository, p.Number, p.Head) + key, err := CheckerOwedKey(p.Repository, int(p.Number), p.Head) if err != nil { ch.logf("%s: %v", cs.Key, err) continue } - state := map[string]string{delivery.VerdictRunning: "pending", delivery.VerdictPass: "success", delivery.VerdictFail: "failure"}[v.State] - o := delivery.Owed{Act: delivery.OwedStatus, Context: delivery.StatusContextCheck, State: state, + state := map[string]string{meshdeliverycheckerclient.VerdictRunning: "pending", meshdeliverycheckerclient.VerdictPass: "success", meshdeliverycheckerclient.VerdictFail: "failure"}[v.State] + o := meshdeliverychecker.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: meshdeliveryclient.StatusContextCheck, State: state, Target: strings.TrimSuffix(ch.Link, "/") + "/" + cs.Key, Text: Line(v, cs, p, out.Builds, out.Slowest, out.Judged)} - data, err := delivery.EncodeCheckerOwed(o) + data, err := EncodeCheckerOwed(o) if err != nil { ch.logf("%s: the status of %s cannot be written: %v", cs.Key, key, err) continue } wrote, err := putOnce(ctx, ch.Owed, key, data, func(cur []byte) bool { - held, err := delivery.DecodeCheckerOwed(cur) - o.V = delivery.Version - return err == nil && delivery.SameOwed(held, o) + held, err := DecodeCheckerOwed(cur) + o.V = keys.Version + return err == nil && held == o }) if err != nil { ch.logf("%s: the status of %s cannot be written: %v", cs.Key, key, err) @@ -997,7 +1007,7 @@ func (ch *Checker) Sweep(ctx context.Context) error { ch.mu.Lock() current := map[string]bool{} for _, c := range ch.known { - if k, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash); err == nil { + if k, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads); err == nil { current[k] = true } } @@ -1010,13 +1020,13 @@ func (ch *Checker) Sweep(ctx context.Context) error { if err != nil || !found { continue } - cur, err := delivery.DecodeVerdict(value) - if err != nil || cur.State != delivery.VerdictRunning { + cur, err := meshdeliverycheckerclient.DecodeVerdict(value) + if err != nil || cur.State != meshdeliverycheckerclient.VerdictRunning { continue } - cur.State = delivery.VerdictFail - cur.Failed = delivery.FailedFirst{Machine: "checker", Words: EndedWords + "closed at start: left running, and no candidate holds these heads any more, so nothing will check them"} - data, err := delivery.EncodeVerdict(cur) + cur.State = meshdeliverycheckerclient.VerdictFail + cur.Failed = &FailedFirst{Machine: "checker", Words: EndedWords + "closed at start: left running, and no candidate holds these heads any more, so nothing will check them"} + data, err := meshdeliverycheckerclient.EncodeVerdict(cur) if err != nil { return err } @@ -1035,7 +1045,7 @@ func (ch *Checker) Sweep(ctx context.Context) error { func (ch *Checker) TrunkMoved(ctx context.Context) { ch.init() ch.mu.Lock() - known := make(map[string]delivery.Candidate, len(ch.known)) + known := make(map[string]meshcatalog.CandidatesByGroupValue, len(ch.known)) for k, c := range ch.known { known[k] = c } @@ -1050,23 +1060,23 @@ func (ch *Checker) TrunkMoved(ctx context.Context) { if err != nil || !found { continue // never judged: no status to put out of date } - v, err := delivery.DecodeVerdict(value) + v, err := meshdeliverycheckerclient.DecodeVerdict(value) if err == nil && !ch.stale(v, cs) { continue } - if err == nil && v.State == delivery.VerdictPass { + if err == nil && v.State == meshdeliverycheckerclient.VerdictPass { cs.CarriedFrom = judgedOn(v) // the status says it is being carried to the new commit (ADR 0548 §6) } - ch.writeOwed(ctx, cs, delivery.Verdict{State: delivery.VerdictRunning}, Outcome{}) + ch.writeOwed(ctx, cs, meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictRunning}, Outcome{}) } ch.Reconsider(ctx) } // owePending owes mesh/check pending on the heads of a verdict judged on commits that moved while it ran: a pass says it is // being carried to the trunk's head now (ADR 0548 §6), anything else that it is checked again. -func (ch *Checker) owePending(ctx context.Context, judged *Case, v delivery.Verdict) { +func (ch *Checker) owePending(ctx context.Context, judged *Case, v meshdeliverychecker.VerdictsByGroupHeadsValue) { next := judged - ckey, err := delivery.CandidatesKey(v.Group) + ckey, err := meshcatalogclient.CandidatesKey(v.Group) ch.mu.Lock() c, ok := ch.known[ckey] ch.mu.Unlock() @@ -1075,10 +1085,10 @@ func (ch *Checker) owePending(ctx context.Context, judged *Case, v delivery.Verd next = now } } - if v.State == delivery.VerdictPass { + if v.State == meshdeliverycheckerclient.VerdictPass { next.CarriedFrom = judgedOn(v) } - ch.writeOwed(ctx, next, delivery.Verdict{State: delivery.VerdictRunning}, Outcome{}) + ch.writeOwed(ctx, next, meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictRunning}, Outcome{}) } // movedSince says whether a trunk the case was judged on is no longer the trunk's head. @@ -1134,7 +1144,7 @@ func (ch *Checker) hearFailedBuilds(ctx context.Context, group string, cs *Case) if err != nil || !found { return } - if v, err := delivery.DecodeVerdict(value); err == nil { + if v, err := meshdeliverycheckerclient.DecodeVerdict(value); err == nil { if builds := failedBuilds(v, cs); builds != nil { ch.awaitRebuilt(ctx, group, cs.Key, builds) } diff --git a/modules/mesh-delivery-checker/internal/check/checker_test.go b/modules/mesh-delivery-checker/internal/check/checker_test.go index 60e203af..3ead2d80 100644 --- a/modules/mesh-delivery-checker/internal/check/checker_test.go +++ b/modules/mesh-delivery-checker/internal/check/checker_test.go @@ -9,8 +9,14 @@ import ( "testing" "time" - "git.novox.be/novox/mesh-contracts/go/seats/meshdeliverychecker" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" + "git.novox.be/novox/mesh-sdk/go/keys" ) const hqRepo = "novox/hq" @@ -27,30 +33,30 @@ const ( ) // candidateOf is a candidate of a group with the given pull requests, as the indexer writes one. -func candidateOf(t testing.TB, group string, pulls ...delivery.CandidatePull) []byte { +func candidateOf(t testing.TB, group string, pulls ...meshcatalog.CandidatesByGroupValuePullsItem) []byte { t.Helper() - heads := make([]delivery.Head, len(pulls)) + heads := make([]meshdeliveryclient.Head, len(pulls)) for i, p := range pulls { - heads[i] = p.Head + heads[i] = meshdeliveryclient.Head{Repository: p.Repository, Number: int(p.Number), Head: p.Head} } - hash, err := delivery.HeadsHash(heads) + hash, err := meshdeliveryclient.HeadsHash(heads) if err != nil { t.Fatal(err) } - c := delivery.Candidate{V: delivery.Version, Group: group, HeadsHash: hash, Pulls: pulls, FPSet: delivery.FingerprintSet(pulls)} - data, err := delivery.EncodeCandidate(c) + c := meshcatalog.CandidatesByGroupValue{V: keys.Version, Group: group, Heads: hash, Pulls: pulls, Fpset: meshcatalogclient.FingerprintSet(pulls)} + data, err := json.Marshal(c) if err != nil { t.Fatal(err) } return data } -func pullOf(repo string, number int, head string) delivery.CandidatePull { - return delivery.CandidatePull{Head: delivery.Head{Repository: repo, Number: number, Head: head}, Modules: []string{}} +func pullOf(repo string, number int, head string) meshcatalog.CandidatesByGroupValuePullsItem { + return meshcatalog.CandidatesByGroupValuePullsItem{Repository: repo, Number: int64(number), Head: head, Modules: []string{}} } // newRepoPullOf is a pull request of a new repository of the mesh: its trunk not named yet, its first module added. -func newRepoPullOf(repo string, number int, head string) delivery.CandidatePull { +func newRepoPullOf(repo string, number int, head string) meshcatalog.CandidatesByGroupValuePullsItem { p := pullOf(repo, number, head) p.Modules = []string{"mod-u"} return p @@ -73,9 +79,9 @@ func (j *judgeFunc) Judge(ctx context.Context, c *Case) (Outcome, error) { func (j *judgeFunc) count() int { j.mu.Lock(); defer j.mu.Unlock(); return j.calls } -func passing() delivery.Verdict { - return delivery.Verdict{State: delivery.VerdictPass, Levels: map[string]delivery.LevelResult{ - delivery.LevelUnit: {Run: 2, Kept: 3}, delivery.LevelIntegration: {Run: 1}}} +func passing() meshdeliverychecker.VerdictsByGroupHeadsValue { + return meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictPass, Levels: map[string]meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue{ + buildstep.LevelUnit: {Run: 2, Kept: 3}, buildstep.LevelIntegration: {Run: 1}}} } func passJudge() *judgeFunc { @@ -99,20 +105,20 @@ func newRig(t testing.TB, j Judge) *rig { func (r *rig) setTrunks(t testing.TB, heads map[string]string) bool { t.Helper() - data, err := delivery.EncodeSource(delivery.Source{Group: "g", Heads: heads}) + data, err := meshdeliveryclient.EncodeSource(meshdelivery.SourceByMainValue{Group: "g", Heads: heads}) if err != nil { t.Fatal(err) } return r.trunks.Put(data, false) } -func (r *rig) verdict(t testing.TB, key string) delivery.Verdict { +func (r *rig) verdict(t testing.TB, key string) meshdeliverychecker.VerdictsByGroupHeadsValue { t.Helper() raw, _, found, _ := r.verdicts.Get(context.Background(), key) if !found { t.Fatalf("no verdict %s; the bucket holds %v", key, r.verdicts.vals) } - v, err := delivery.DecodeVerdict(raw) + v, err := meshdeliverycheckerclient.DecodeVerdict(raw) if err != nil { t.Fatal(err) } @@ -121,11 +127,11 @@ func (r *rig) verdict(t testing.TB, key string) delivery.Verdict { func keyOf(t testing.TB, group string, value []byte) string { t.Helper() - c, err := delivery.DecodeCandidate(value) + c, err := meshcatalog.DecodeCandidatesByGroupValue(value) if err != nil { t.Fatal(err) } - k, err := delivery.CheckerVerdictKey(group, c.HeadsHash) + k, err := meshdeliverycheckerclient.VerdictKey(group, c.Heads) if err != nil { t.Fatal(err) } @@ -139,7 +145,7 @@ func TestACandidateNotYetCheckedGetsItsVerdictKeyCreatedOnceAndIsJudged(t *testi r.ch.Candidate(ctx, "feat%2Fx", cand) r.ch.Wait() key := keyOf(t, "feat/x", cand) - if v := r.verdict(t, key); v.State != delivery.VerdictPass || v.Group != "feat/x" || v.Trunk[repoA] != trunkA { + if v := r.verdict(t, key); v.State != meshdeliverycheckerclient.VerdictPass || v.Group != "feat/x" || v.Trunk[repoA] != trunkA { t.Fatalf("verdict = %+v", v) } if r.verdicts.creates[key] != 1 || j.count() != 1 { @@ -171,14 +177,14 @@ func TestASecondInstancesCreateIsRefusedAndItDoesNothing(t *testing.T) { // N6/A r := newRig(t, j) cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1)) key := keyOf(t, "feat/x", cand) - other, _ := delivery.EncodeVerdict(delivery.Verdict{Group: "feat/x", Heads: strings.SplitN(key, ".", 2)[1], State: delivery.VerdictRunning}) + other, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: "feat/x", Heads: strings.SplitN(key, ".", 2)[1], State: meshdeliverycheckerclient.VerdictRunning}) r.ch.Verdicts = racyKV{r.verdicts, other} r.ch.Candidate(ctx, "feat%2Fx", cand) r.ch.Wait() if j.count() != 0 { t.Errorf("the instance that lost the create judged %d times, want none", j.count()) } - if v := r.verdict(t, key); v.State != delivery.VerdictRunning { + if v := r.verdict(t, key); v.State != meshdeliverycheckerclient.VerdictRunning { t.Errorf("the other instance's key was changed: %+v", v) } } @@ -188,13 +194,13 @@ func TestARestartWithARunningKeyJudgesItAgain(t *testing.T) { // N6/AC2 r := newRig(t, j) cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1)) key := keyOf(t, "feat/x", cand) - left, _ := delivery.EncodeVerdict(delivery.Verdict{Group: "feat/x", Heads: strings.SplitN(key, ".", 2)[1], Trunk: map[string]string{repoA: trunkA}, State: delivery.VerdictRunning}) + left, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: "feat/x", Heads: strings.SplitN(key, ".", 2)[1], Trunk: map[string]string{repoA: trunkA}, State: meshdeliverycheckerclient.VerdictRunning}) if _, err := r.verdicts.Create(ctx, key, left); err != nil { t.Fatal(err) } r.ch.Candidate(ctx, "feat%2Fx", cand) r.ch.Wait() - if j.count() != 1 || r.verdict(t, key).State != delivery.VerdictPass { + if j.count() != 1 || r.verdict(t, key).State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("judged %d times, verdict %+v, want the left check judged once and passed", j.count(), r.verdict(t, key)) } } @@ -212,7 +218,7 @@ func TestACheckPastItsBoundFailsNamingTheStuckStep(t *testing.T) { // N6/AC3 r.ch.Candidate(ctx, "feat%2Fx", cand) r.ch.Wait() v := r.verdict(t, keyOf(t, "feat/x", cand)) - if v.State != delivery.VerdictFail || v.Failed.Machine != "shanks" || !strings.Contains(v.Failed.Words, "stuck at: test the unit level of mesh-delivery") { + if v.State != meshdeliverycheckerclient.VerdictFail || v.Failed.Machine != "shanks" || !strings.Contains(v.Failed.Words, "stuck at: test the unit level of mesh-delivery") { t.Fatalf("verdict = %+v, want a failure naming shanks and the stuck step", v) } } @@ -242,7 +248,7 @@ func TestAnUnheardForgeFailsWithTheReasonAndIsJudgedAgainWhenTheNodesChange(t *t } r.ch.NodesChanged(ctx) r.ch.Wait() - if j.count() != 2 || r.verdict(t, key).State != delivery.VerdictPass { + if j.count() != 2 || r.verdict(t, key).State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("after the nodes changed: judged %d times, verdict %+v", j.count(), r.verdict(t, key)) } } @@ -275,7 +281,7 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi t.Errorf("said %d times in the log, want once for the head: %q", n, logged) } reader := func() Reader { - ckey, _ := delivery.CandidatesKey("feat/y") + ckey, _ := meshcatalogclient.CandidatesKey("feat/y") return Reader{CandidateState: fakeState{ckey: cand}.read(), VerdictState: fakeState(r.verdicts.vals).read(), Trunks: func() (map[string]string, error) { return r.trunks.Heads(), nil }} } @@ -294,7 +300,7 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi r.setTrunks(t, map[string]string{repoA: trunkA, repoSDK: trunkS, unknown: trunkA2}) r.ch.TrunkMoved(ctx) r.ch.Wait() - if v := r.verdict(t, key); j.count() != 1 || v.State != delivery.VerdictPass || v.Trunk[unknown] != trunkA2 { + if v := r.verdict(t, key); j.count() != 1 || v.State != meshdeliverycheckerclient.VerdictPass || v.Trunk[unknown] != trunkA2 { t.Errorf("judged %d times, verdict %+v, want one pass on %s", j.count(), v, trunkA2) } if s := owedState(t, r, unknown, 4, head1); s != "success" { @@ -306,15 +312,15 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi // running verdict left for heads that cannot be judged is replaced; a verdict key deleted is judged again on its delete. for _, row := range []struct { name string - pulls []delivery.CandidatePull + pulls []meshcatalog.CandidatesByGroupValuePullsItem running bool // a running verdict is left under the key first deleted bool // the key is deleted after its verdict judged int want string // the verdict's state, and for a failure the repository it names }{ - {name: "a member the checker cannot claim", pulls: []delivery.CandidatePull{pullOf(repoA, 9, head2), pullOf("novox/other", 2, head2)}, want: "novox/other"}, - {name: "a running verdict left for heads that cannot be judged", pulls: []delivery.CandidatePull{newRepoPullOf(unknown, 5, head2)}, running: true, want: unknown}, - {name: "a verdict key deleted", pulls: []delivery.CandidatePull{pullOf(repoA, 5, head2)}, deleted: true, judged: 2, want: delivery.VerdictPass}, + {name: "a member the checker cannot claim", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 9, head2), pullOf("novox/other", 2, head2)}, want: "novox/other"}, + {name: "a running verdict left for heads that cannot be judged", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{newRepoPullOf(unknown, 5, head2)}, running: true, want: unknown}, + {name: "a verdict key deleted", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 5, head2)}, deleted: true, judged: 2, want: meshdeliverycheckerclient.VerdictPass}, } { t.Run(row.name, func(t *testing.T) { j := passJudge() @@ -322,8 +328,8 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi cand := candidateOf(t, "feat/z", row.pulls...) key := keyOf(t, "feat/z", cand) if row.running { - group, heads, _ := delivery.ParseCheckerVerdictKey(key) - raw, _ := delivery.EncodeVerdict(delivery.Verdict{Group: group, Heads: heads, State: delivery.VerdictRunning}) + group, heads, _ := meshdeliverycheckerclient.ParseVerdictKey(key) + raw, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: group, Heads: heads, State: meshdeliverycheckerclient.VerdictRunning}) _, _ = r.verdicts.Create(ctx, key, raw) } r.ch.Candidate(ctx, "feat%2Fz", cand) @@ -334,8 +340,8 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi r.ch.Wait() } v := r.verdict(t, key) - if row.want == delivery.VerdictPass { - if v.State != delivery.VerdictPass || j.count() != row.judged { + if row.want == meshdeliverycheckerclient.VerdictPass { + if v.State != meshdeliverycheckerclient.VerdictPass || j.count() != row.judged { t.Errorf("verdict %s after %d judgings, want a pass after %d", v.State, j.count(), row.judged) } return @@ -344,7 +350,7 @@ func TestARepositoryWithNoTrunkCannotBeJudgedLoudlyUntilItsTrunkIsKnown(t *testi t.Errorf("verdict %+v after %d judgings, want a failure naming %s and none judged", v, j.count(), row.want) } for _, p := range row.pulls { - if s := owedState(t, r, p.Repository, p.Number, p.Head.Head); s != "error" { + if s := owedState(t, r, p.Repository, int(p.Number), p.Head); s != "error" { t.Errorf("%s#%d owes %q, want error", p.Repository, p.Number, s) } } @@ -391,7 +397,7 @@ func TestATrunkMoveRewritesTheSameVerdictKeyAndTheHeadsStatus(t *testing.T) { // r.ch.Candidate(ctx, "feat%2Fx", cand) r.ch.Wait() _, rev1, _, _ := r.verdicts.Get(ctx, key) - owedKey, _ := delivery.CheckerOwedKey(repoA, 7, head1) + owedKey, _ := CheckerOwedKey(repoA, 7, head1) _, orev1, _, _ := r.owed.Get(ctx, owedKey) if !r.setTrunks(t, map[string]string{repoA: trunkA2, repoSDK: trunkS}) { t.Fatal("the trunks did not change") @@ -427,12 +433,12 @@ func TestTheStatusIsPendingWhileTheCheckRunsAndEndsSuccessOrFailure(t *testing.T var seen []string var r *rig j := &judgeFunc{f: func(ctx context.Context, c *Case, _ int) (Outcome, error) { - key, _ := delivery.CheckerOwedKey(repoA, 7, head1) + key, _ := CheckerOwedKey(repoA, 7, head1) raw, _, _, _ := r.owed.Get(ctx, key) - o, _ := delivery.DecodeCheckerOwed(raw) + o, _ := DecodeCheckerOwed(raw) seen = append(seen, o.State) - c.series(repoA+"#7", []string{"web/pkg series 0.1 → 0.2"}, []delivery.SeriesChange{{Module: "web", Artifact: "pkg", From: "0.1", To: "0.2"}}) - return Outcome{Verdict: delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Test: "m/a.TestX"}}, + c.series(repoA+"#7", []string{"web/pkg series 0.1 → 0.2"}, []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem{{Module: "web", Artifact: "pkg", From: "0.1", To: "0.2"}}) + return Outcome{Verdict: meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Test: "m/a.TestX"}}, OutOfStep: map[string][]string{repoA + "#7": {"go/seats/x/contract.go"}}}, nil }} r = newRig(t, j) @@ -440,19 +446,19 @@ func TestTheStatusIsPendingWhileTheCheckRunsAndEndsSuccessOrFailure(t *testing.T r.ch.Says = says r.ch.Candidate(ctx, "feat/x", candidateOf(t, "feat/x", pullOf(repoA, 7, head1))) r.ch.Wait() - key, _ := delivery.CheckerOwedKey(repoA, 7, head1) + key, _ := CheckerOwedKey(repoA, 7, head1) raw, _, _, _ := r.owed.Get(ctx, key) - o, err := delivery.DecodeCheckerOwed(raw) + o, err := DecodeCheckerOwed(raw) if err != nil { t.Fatal(err) } - if len(seen) != 1 || seen[0] != "pending" || o.State != "failure" || o.Context != delivery.StatusContextCheck || !strings.HasPrefix(o.Target, "https://view.invalid/checks/") { + if len(seen) != 1 || seen[0] != "pending" || o.State != "failure" || o.Context != meshdeliveryclient.StatusContextCheck || !strings.HasPrefix(o.Target, "https://view.invalid/checks/") { t.Errorf("while running %v, at the end %+v", seen, o) } // DLV-C2 (design 26 *Out of step*): the check's end says one verdict event for the pull request, naming its out-of-step // files; the running status says none. if len(says.said) != 1 || says.said[0].Repository != repoA || says.said[0].Pull != 7 || says.said[0].Head != head1 || - says.said[0].Verdict != delivery.VerdictFail || strings.Join(says.said[0].OutOfStep, ",") != "go/seats/x/contract.go" || + says.said[0].Verdict != meshdeliverycheckerclient.VerdictFail || strings.Join(says.said[0].OutOfStep, ",") != "go/seats/x/contract.go" || // The join (A2j finding 2): and its series changes, as the seat's event names them. len(says.said[0].Series) != 1 || says.said[0].Series[0] != (meshdeliverychecker.VerdictHolderEventSeriesItem{Module: "web", Artifact: "pkg", From: "0.1", To: "0.2"}) { t.Errorf("verdict events %+v", says.said) @@ -480,14 +486,14 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) { r := newRig(t, j) cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1)) live := keyOf(t, "feat/x", cand) - dead, _ := delivery.CheckerVerdictKey("feat/gone", "0123456789ab") + dead, _ := meshdeliverycheckerclient.VerdictKey("feat/gone", "0123456789ab") for _, k := range []string{live, dead} { - group, heads, _ := delivery.ParseCheckerVerdictKey(k) - raw, _ := delivery.EncodeVerdict(delivery.Verdict{Group: group, Heads: heads, State: delivery.VerdictRunning}) + group, heads, _ := meshdeliverycheckerclient.ParseVerdictKey(k) + raw, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: group, Heads: heads, State: meshdeliverycheckerclient.VerdictRunning}) _, _ = r.verdicts.Create(ctx, k, raw) } r.ch.init() - var c delivery.Candidate + var c meshcatalog.CandidatesByGroupValue _ = json.Unmarshal(cand, &c) r.ch.known["feat%2Fx"] = c if err := r.ch.Sweep(ctx); err != nil { @@ -496,7 +502,7 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) { if v := r.verdict(t, dead); !IsEnded(v) || !strings.Contains(v.Failed.Words, "closed at start") { t.Errorf("the dead key = %+v", v) } - if v := r.verdict(t, live); v.State != delivery.VerdictRunning { + if v := r.verdict(t, live); v.State != meshdeliverycheckerclient.VerdictRunning { t.Errorf("the live key = %+v, want it left for the check to take over", v) } @@ -519,7 +525,7 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) { r.ch.Candidate(ctx, "feat%2Fx", alone) <-started r.ch.Candidate(ctx, "feat%2Fx", both) - var bothC delivery.Candidate + var bothC meshcatalog.CandidatesByGroupValue _ = json.Unmarshal(both, &bothC) select { case err := <-ended: @@ -533,10 +539,10 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) { if v := r.verdict(t, live); !IsEnded(v) || v.Failed.Words != EndedWords+"superseded by "+GateHeads(bothC) { t.Errorf("the superseded verdict = %+v", v.Failed) } - if v := r.verdict(t, keyOf(t, "feat/x", both)); v.State != delivery.VerdictPass { + if v := r.verdict(t, keyOf(t, "feat/x", both)); v.State != meshdeliverycheckerclient.VerdictPass { t.Errorf("the group's verdict = %s", v.State) } - okey, _ := delivery.CheckerOwedKey(repoA, 7, head1) + okey, _ := CheckerOwedKey(repoA, 7, head1) if raw, _, _, _ := r.owed.Get(ctx, okey); !strings.Contains(string(raw), `"success"`) { t.Errorf("the head's status = %s, want the group's success, never the ended check's", raw) } @@ -554,7 +560,7 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) { // The member leaves again: the ended heads are held again and checked whole. r.ch.Candidate(ctx, "feat%2Fx", alone) r.ch.Wait() - if v := r.verdict(t, live); v.State != delivery.VerdictPass || j.count() != 3 { + if v := r.verdict(t, live); v.State != meshdeliverycheckerclient.VerdictPass || j.count() != 3 { t.Errorf("the heads held again = %s after %d judgings, want a pass of a third", v.State, j.count()) } @@ -575,7 +581,7 @@ func TestHeadsNoCandidateHoldsAreNeverLeftRunning(t *testing.T) { if _, _, found, _ := r.verdicts.Get(ctx, keyOf(t, "feat/y", sdk)); found || j.count() != 1 { t.Errorf("the partial set has a verdict (%v) or the judge ran %d times, want the whole set alone", found, j.count()) } - if v := r.verdict(t, keyOf(t, "feat/y", whole)); v.State != delivery.VerdictPass { + if v := r.verdict(t, keyOf(t, "feat/y", whole)); v.State != meshdeliverycheckerclient.VerdictPass { t.Errorf("the whole set = %s", v.State) } } @@ -588,7 +594,7 @@ func TestTheForgeBridgeIsToldOnceForEachChangeOfAStatus(t *testing.T) { // 1683 cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1)) r.ch.Candidate(ctx, "feat%2Fx", cand) r.ch.Wait() - key, _ := delivery.CheckerOwedKey(repoA, 7, head1) + key, _ := CheckerOwedKey(repoA, 7, head1) want := []string{EventOwedWritten + " " + key, EventOwedWritten + " " + key} // pending, then success if len(told) != 2 || told[0] != want[0] || told[1] != want[1] { t.Fatalf("told %v, want the status said when pending and when success", told) @@ -609,8 +615,8 @@ func TestACandidateOutsideTheMeshIsJudgedNothingAndOwesNothing(t *testing.T) { var logged []string r.ch.Logf = func(f string, a ...any) { logged = append(logged, fmt.Sprintf(f, a...)) } const theirs = "someone/own-app" - okey, _ := delivery.CheckerOwedKey(theirs, 55, head1) - before, _ := delivery.EncodeCheckerOwed(delivery.Owed{Act: delivery.OwedStatus, Context: delivery.StatusContextCheck, State: "error", Text: "cannot be judged"}) + okey, _ := CheckerOwedKey(theirs, 55, head1) + before, _ := EncodeCheckerOwed(meshdeliverychecker.OwedByRepositoryNumberHeadActValue{Act: meshdeliveryclient.OwedStatus, Context: meshdeliveryclient.StatusContextCheck, State: "error", Text: "cannot be judged"}) if _, err := r.owed.Create(ctx, okey, before); err != nil { t.Fatal(err) } @@ -629,7 +635,7 @@ func TestACandidateOutsideTheMeshIsJudgedNothingAndOwesNothing(t *testing.T) { if n := countWith(logged, "outside the mesh"); n != 1 { t.Errorf("said %d times in the log, want once: %q", n, logged) } - ckey, _ := delivery.CandidatesKey("feat/theirs") + ckey, _ := meshcatalogclient.CandidatesKey("feat/theirs") st, err := Reader{CandidateState: fakeState{ckey: cand}.read(), VerdictState: fakeState(r.verdicts.vals).read(), Trunks: func() (map[string]string, error) { return r.trunks.Heads(), nil }}.Status() if err != nil { diff --git a/modules/mesh-delivery-checker/internal/check/composition.go b/modules/mesh-delivery-checker/internal/check/composition.go index 0501844b..9acc7e50 100644 --- a/modules/mesh-delivery-checker/internal/check/composition.go +++ b/modules/mesh-delivery-checker/internal/check/composition.go @@ -3,26 +3,40 @@ package check import ( "strings" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" ) // withComposition is v with the check's composition taken in: a failed one fails the check, naming its machine. -func withComposition(v *delivery.Verdict, comp delivery.CompositionVerdict) { - v.Composition = comp.Composition() +func withComposition(v *meshdeliverychecker.VerdictsByGroupHeadsValue, comp meshwants.CompositionsByGroupHeadsValue) { + v.Composition = compositionOf(comp) + if v.Took == nil { + v.Took = &meshdeliverychecker.VerdictsByGroupHeadsValueTook{} + } v.Took.Composition = comp.Took - if comp.State == delivery.CompositionPass && len(comp.Warnings) > 0 && v.Composition != nil { + if comp.State == meshdeliverycheckerclient.CompositionPass && len(comp.Warnings) > 0 && v.Composition != nil { v.Composition.Words = strings.Join(comp.Warnings, "; ") // passed with a warning (ADR 0593 §9) } - if comp.State == delivery.CompositionFail { + if comp.State == meshdeliverycheckerclient.CompositionFail { machine := comp.Machine if machine == "" { machine = "the composition" } - f := delivery.FailedFirst{Machine: machine, Words: comp.Words} - if v.State == delivery.VerdictFail { + f := FailedFirst{Machine: machine, Words: comp.Words} + if v.State == meshdeliverycheckerclient.VerdictFail { addFailure(v, f) // the tests failed too: the composition is named after them (the operator, 2026-11-02) return } - v.State, v.Failed = delivery.VerdictFail, f + v.State, v.Failed = meshdeliverycheckerclient.VerdictFail, &f } } + +// compositionOf is what the checker writes into its verdict from the assigner's composition answer c: nil while c waits (the +// check is still running). +func compositionOf(c meshwants.CompositionsByGroupHeadsValue) *meshdeliverychecker.VerdictsByGroupHeadsValueComposition { + if c.State == compositionWaits { + return nil + } + return &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: c.State, Words: c.Words, Sdk: c.Sdk, Machines: c.Machines} +} diff --git a/modules/mesh-delivery-checker/internal/check/composition_test.go b/modules/mesh-delivery-checker/internal/check/composition_test.go index 9ba4c332..45c9b3e1 100644 --- a/modules/mesh-delivery-checker/internal/check/composition_test.go +++ b/modules/mesh-delivery-checker/internal/check/composition_test.go @@ -8,24 +8,28 @@ import ( "strings" "testing" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + buildsrec "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // fakeCompositions answers a check's composition from memory, recording the keys asked. type fakeCompositions struct { - v delivery.CompositionVerdict + v meshwants.CompositionsByGroupHeadsValue err error asked []string // waits answers only when its context ends. waits bool } -func (f *fakeCompositions) Compose(ctx context.Context, c *Case) (delivery.CompositionVerdict, error) { +func (f *fakeCompositions) Compose(ctx context.Context, c *Case) (meshwants.CompositionsByGroupHeadsValue, error) { f.asked = append(f.asked, c.Key) if f.waits { <-ctx.Done() - return delivery.CompositionVerdict{}, ctx.Err() + return meshwants.CompositionsByGroupHeadsValue{}, ctx.Err() } return f.v, f.err } @@ -35,12 +39,12 @@ func (f *fakeCompositions) Compose(ctx context.Context, c *Case) (delivery.Compo func TestAPassingCheckTakesTheAssignersComposition(t *testing.T) { t.Run("a change to a build module or base names the modules built on it", aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt) b := newBench(t) - f := &fakeCompositions{v: delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionPass, SDK: "v0.1.14-x", Machines: []string{"g14"}, Took: 40}} + f := &fakeCompositions{v: meshwants.CompositionsByGroupHeadsValue{Group: "feat/x", Heads: "0123456789ab", State: meshdeliverycheckerclient.CompositionPass, Sdk: "v0.1.14-x", Machines: []string{"g14"}, Took: 40}} b.ex.Compositions = f cs := caseOf(t, pullOf(repoA, 7, head1)) cs.FPSet = "fps-of-the-candidate" v := b.judge(t, cs).Verdict - if v.State != delivery.VerdictPass || v.Composition == nil || v.Composition.State != delivery.CompositionPass || v.Composition.SDK != "v0.1.14-x" || v.Took.Composition != 40 { + if v.State != meshdeliverycheckerclient.VerdictPass || v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionPass || v.Composition.Sdk != "v0.1.14-x" || v.Took.Composition != 40 { t.Fatalf("verdict = %+v, composition %+v", v, v.Composition) } if len(f.asked) != 1 || f.asked[0] != cs.Key { @@ -49,32 +53,32 @@ func TestAPassingCheckTakesTheAssignersComposition(t *testing.T) { if !compositionMentions(v, "passed") { t.Errorf("the line says %q", compositionWords(v.Composition, v.Took.Composition)) } - if v.Composition.FPSet == "" || v.Composition.FPSet != cs.FPSet { - t.Errorf("the composition names the fingerprint set %q, want the candidate's %q (ADR 0548 §5)", v.Composition.FPSet, cs.FPSet) + if v.Composition.Fpset == "" || v.Composition.Fpset != cs.FPSet { + t.Errorf("the composition names the fingerprint set %q, want the candidate's %q (ADR 0548 §5)", v.Composition.Fpset, cs.FPSet) } // ADR 0548 §5: a carry whose fingerprints did not change keeps the composition it passed and asks the assigner nothing. carry := caseOf(t, pullOf(repoA, 7, head1)) carry.CarriedFrom, carry.Composition = map[string]string{repoA: trunkA2}, v.Composition - if kept := b.judge(t, carry).Verdict; kept.Composition == nil || kept.Composition.SDK != "v0.1.14-x" || len(f.asked) != 1 { + if kept := b.judge(t, carry).Verdict; kept.Composition == nil || kept.Composition.Sdk != "v0.1.14-x" || len(f.asked) != 1 { t.Errorf("the carry's composition is %+v and the assigner was asked %d times; want the kept one and no second ask", kept.Composition, len(f.asked)) } } -func compositionMentions(v delivery.Verdict, word string) bool { +func compositionMentions(v meshdeliverychecker.VerdictsByGroupHeadsValue, word string) bool { return strings.Contains(compositionWords(v.Composition, v.Took.Composition), word) } // 1297/AC2 at the checker: a failed composition fails the check, naming the machine it failed at and its words. func TestAFailedCompositionFailsTheCheck(t *testing.T) { b := newBench(t) - b.ex.Compositions = &fakeCompositions{v: delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionFail, SDK: "x", + b.ex.Compositions = &fakeCompositions{v: meshwants.CompositionsByGroupHeadsValue{Group: "feat/x", Heads: "0123456789ab", State: meshdeliverycheckerclient.CompositionFail, Sdk: "x", Machine: "g14", Words: "g14 runs web, which the change removes from its source"}} v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - if v.State != delivery.VerdictFail || v.Failed.Machine != "g14" || v.Composition == nil || v.Composition.State != delivery.CompositionFail { + if v.State != meshdeliverycheckerclient.VerdictFail || v.Failed.Machine != "g14" || v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionFail { t.Fatalf("verdict = %+v", v) } v.Group, v.Heads = "feat/x", "0123456789ab" // the checker names them when it writes the verdict - if _, err := delivery.EncodeVerdict(v); err != nil { + if _, err := meshdeliverycheckerclient.EncodeVerdict(v); err != nil { t.Errorf("the verdict cannot be written: %v", err) } } @@ -85,11 +89,11 @@ func TestACompositionNotHadIsNotRun(t *testing.T) { f := &fakeCompositions{err: errors.New("the check's bound passed")} b.ex.Compositions = f v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - if v.State != delivery.VerdictPass || v.Composition == nil || v.Composition.State != delivery.CompositionNotRun { + if v.State != meshdeliverycheckerclient.VerdictPass || v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionNotRun { t.Fatalf("verdict = %+v", v) } b2 := newBench(t) - f2 := &fakeCompositions{v: delivery.CompositionVerdict{State: delivery.CompositionFail, Words: "w", SDK: "x"}} + f2 := &fakeCompositions{v: meshwants.CompositionsByGroupHeadsValue{State: meshdeliverycheckerclient.CompositionFail, Words: "w", Sdk: "x"}} b2.ex.Compositions = f2 cs := caseOf(t, pullOf(repoA, 7, head1)) cs.Try = &TryOptions{} @@ -113,12 +117,12 @@ type fakeEnds struct { early bool } -func (f *fakeEnds) Ended(_ context.Context, builds map[string]string) (map[string]delivery.BuilderBuild, error) { - f.asked = builds +func (f *fakeEnds) Ended(_ context.Context, asked map[string]string) (map[string]buildsrec.BuilderBuild, error) { + f.asked = asked f.early = f.composed != nil && len(f.composed.asked) > 0 - out := map[string]delivery.BuilderBuild{} - for module, fp := range builds { - if b, ok := f.builds[module+" "+fp]; ok && (b.State == delivery.BuildDone || b.State == delivery.BuildFailed) { + out := map[string]buildsrec.BuilderBuild{} + for module, fp := range asked { + if b, ok := f.builds[module+" "+fp]; ok && (b.State == buildsrec.BuildDone || b.State == buildsrec.BuildFailed) { out[module] = b } } @@ -154,12 +158,12 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) { fp := "src2:" + dig("f") goBuild := &catalogue.Build{Artifacts: []catalogue.Artifact{{Name: "bin", Kind: catalogue.ArtifactBundle, Language: "go"}}} tsBuild := &catalogue.Build{Artifacts: []catalogue.Artifact{{Name: "app", Kind: catalogue.ArtifactBundle, Language: "typescript"}}} - done := delivery.BuilderBuild{State: delivery.BuildDone} + done := buildsrec.BuilderBuild{State: buildsrec.BuildDone} for _, row := range []struct { name string manifest map[string]catalogue.Manifest - builds map[string]delivery.BuilderBuild // by module, at fp; nil leaves the check without the builds' ends - runs knownRuns // nil: the trunk holds every module + builds map[string]buildsrec.BuilderBuild // by module, at fp; nil leaves the check without the builds' ends + runs knownRuns // nil: the trunk holds every module want []string waited string // the modules waited on, sorted failed string // the failure's test, when the check fails on a build @@ -169,37 +173,37 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) { {name: "the Go build module invokes a verb no running build serves", manifest: map[string]catalogue.Manifest{"mesh-build-go": {Module: "mesh-build-go", Build: goBuild, Invokes: []string{"seat:package-registry.publish", "seat:artifact-store.put"}}, "web": {Module: "web", Build: goBuild}}, - builds: map[string]delivery.BuilderBuild{"mesh-build-go": done}, served: []string{"seat:artifact-store.put"}, + builds: map[string]buildsrec.BuilderBuild{"mesh-build-go": done}, served: []string{"seat:artifact-store.put"}, want: []string{"the changed build module mesh-build-go invokes seat:package-registry.publish, which no running build serves, " + "so the check cannot run it: web, which it builds, are composed from their manifests (ADR 0614 §7)"}, waited: "mesh-build-go"}, {name: "the Go build module changes", manifest: map[string]catalogue.Manifest{"mesh-build-go": {Module: "mesh-build-go", Build: goBuild}, "web": {Module: "web", Build: goBuild}, "mail": {Module: "mail", Build: goBuild}, "docs": {Module: "docs"}}, - builds: map[string]delivery.BuilderBuild{"mesh-build-go": done, "docs": done}, + builds: map[string]buildsrec.BuilderBuild{"mesh-build-go": done, "docs": done}, want: []string{"the build module mesh-build-go: mail, web, which it builds"}, waited: "docs, mesh-build-go"}, {name: "the Node build module changes", manifest: map[string]catalogue.Manifest{"mesh-build-node": {Module: "mesh-build-node", Build: goBuild}, "panel": {Module: "panel", Build: tsBuild}, "web": {Module: "web", Build: goBuild}}, - builds: map[string]delivery.BuilderBuild{"mesh-build-node": done, "web": done}, + builds: map[string]buildsrec.BuilderBuild{"mesh-build-node": done, "web": done}, want: []string{"the build module mesh-build-node: panel, which it builds"}, waited: "mesh-build-node, web"}, {name: "a base changes", manifest: map[string]catalogue.Manifest{"image": {Module: "image"}, "web": {Module: "web", Build: &catalogue.Build{On: []catalogue.BuildsOn{{Arg: "BASE", Module: "image", Artifact: "img"}}}}}, - builds: map[string]delivery.BuilderBuild{"image": done}, + builds: map[string]buildsrec.BuilderBuild{"image": done}, want: []string{"alters image, a base of web", "waits on the build of image alone"}, waited: "image"}, {name: "neither changes", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}}, - builds: map[string]delivery.BuilderBuild{"web": done}, waited: "web"}, + builds: map[string]buildsrec.BuilderBuild{"web": done}, waited: "web"}, {name: "a build fails", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}, "mail": {Module: "mail", Build: goBuild}}, - builds: map[string]delivery.BuilderBuild{"web": {State: delivery.BuildFailed, Error: "go build: undefined: x"}, "mail": done}, + builds: map[string]buildsrec.BuilderBuild{"web": {State: buildsrec.BuildFailed, Error: "go build: undefined: x"}, "mail": done}, waited: "mail, web", failed: "build: web"}, {name: "the tests fail", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}}, - builds: map[string]delivery.BuilderBuild{"web": {State: delivery.BuildBuilding}}, tests: true, + builds: map[string]buildsrec.BuilderBuild{"web": {State: buildsrec.BuildBuilding}}, tests: true, want: []string{"composes at once, without waiting on the builds of web"}}, {name: "a build does not end within the bound", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}}, - builds: map[string]delivery.BuilderBuild{"web": {State: delivery.BuildBuilding}}, + builds: map[string]buildsrec.BuilderBuild{"web": {State: buildsrec.BuildBuilding}}, want: []string{"the builds of web did not end within the build-wait-bound"}, waited: "web"}, {name: "a module the trunk does not hold", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}, "new": {Module: "new", Build: goBuild}}, - builds: map[string]delivery.BuilderBuild{"web": done}, runs: knownRuns{"web"}, + builds: map[string]buildsrec.BuilderBuild{"web": done}, runs: knownRuns{"web"}, want: []string{"new: the trunk holds no record of it yet"}, waited: "web"}, {name: "the builds cannot be heard", manifest: map[string]catalogue.Manifest{"web": {Module: "web", Build: goBuild}}, want: []string{"the builds of web cannot be heard"}}, @@ -212,7 +216,7 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) { return r, nil } } - comps := &fakeCompositions{v: delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionPass, SDK: "x", Machines: []string{"g14"}}} + comps := &fakeCompositions{v: meshwants.CompositionsByGroupHeadsValue{Group: "feat/x", Heads: "0123456789ab", State: meshdeliverycheckerclient.CompositionPass, Sdk: "x", Machines: []string{"g14"}}} b.ex.Compositions = comps var ends *fakeEnds if row.builds != nil { @@ -228,10 +232,10 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) { if row.served != nil { b.ex.Serving = fixedServing(row.served) } - read, fps := fixedManifests{}, map[string]delivery.CandidateModule{} + read, fps := fixedManifests{}, map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{} for module, m := range row.manifest { d := dig(string(rune('a' + len(read)))) - read[d], fps[module] = m, delivery.CandidateModule{FP2: fp, Manifest: d} + read[d], fps[module] = m, meshcatalog.CandidatesByGroupValuePullsItemFpsValue{Fp2: fp, Manifest: d} } b.ex.Read = read p := pullOf(catalogueRepository, 9, head1) @@ -246,12 +250,12 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) { } } switch { - case row.failed != "" && (v.State != delivery.VerdictFail || v.Failed.Test != row.failed || !strings.Contains(v.Failed.Words, "undefined: x") || + case row.failed != "" && (v.State != meshdeliverycheckerclient.VerdictFail || v.Failed.Test != row.failed || !strings.Contains(v.Failed.Words, "undefined: x") || !strings.Contains(v.Failed.Words, "the check asks itself again when the build is done")): t.Errorf("%s: the verdict is %s failing %+v, want it failed naming %s, the builder's words and how it is asked again", row.name, v.State, v.Failed, row.failed) - case row.tests && v.State != delivery.VerdictFail: + case row.tests && v.State != meshdeliverycheckerclient.VerdictFail: t.Errorf("%s: %+v, want the tests' failure", row.name, v) - case row.failed == "" && !row.tests && v.State != delivery.VerdictPass: + case row.failed == "" && !row.tests && v.State != meshdeliverycheckerclient.VerdictPass: t.Errorf("%s: %+v", row.name, v) } words := compositionWords(v.Composition, v.Took.Composition) diff --git a/modules/mesh-delivery-checker/internal/check/digestform_test.go b/modules/mesh-delivery-checker/internal/check/digestform_test.go index c06a2f98..0e1a3f2e 100644 --- a/modules/mesh-delivery-checker/internal/check/digestform_test.go +++ b/modules/mesh-delivery-checker/internal/check/digestform_test.go @@ -5,7 +5,8 @@ import ( "strings" "testing" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // 1930/AC3: given a check ask whose stored end keeps the digest's packages as a list of {package, digest} (issue 1930), @@ -19,17 +20,17 @@ func TestKeptVerdictsAreReadFromTheDigestsListForm(t *testing.T) { t.Run(tc.name, func(t *testing.T) { b := newBench(t) var stored []string - b.asks.Answer = func(a delivery.StepAsk) delivery.Step { + b.asks.Answer = func(a buildstep.StepAsk) buildstep.Step { s := b.answer(a) - if tc.old && s.State == delivery.StepPassed { + if tc.old && s.State == buildstep.StepPassed { s.Result = oldDigestForm(t, s.Result) } stored = append(stored, string(s.Result)) return s } first := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - u := first.Levels[delivery.LevelUnit] - if first.State != delivery.VerdictPass || u.Run == 0 || u.Kept != 0 { + u := first.Levels[buildstep.LevelUnit] + if first.State != meshdeliverycheckerclient.VerdictPass || u.Run == 0 || u.Kept != 0 { t.Fatalf("the first check = %+v", first) } if len(stored) == 0 { @@ -41,7 +42,7 @@ func TestKeptVerdictsAreReadFromTheDigestsListForm(t *testing.T) { } b.steps.calls = nil second := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - if s := second.Levels[delivery.LevelUnit]; second.State != delivery.VerdictPass || s.Kept != u.Run || s.Run != 0 { + if s := second.Levels[buildstep.LevelUnit]; second.State != meshdeliverycheckerclient.VerdictPass || s.Kept != u.Run || s.Run != 0 { t.Fatalf("the second check's unit level = %+v, want %d kept and none run", s, u.Run) } if n := len(b.steps.tool("test")); n != 0 { diff --git a/modules/mesh-delivery-checker/internal/check/engines.go b/modules/mesh-delivery-checker/internal/check/engines.go index a5a4529e..8384cf89 100644 --- a/modules/mesh-delivery-checker/internal/check/engines.go +++ b/modules/mesh-delivery-checker/internal/check/engines.go @@ -9,10 +9,15 @@ import ( "sync" "time" + "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-delivery/sdk/composer" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // **A check's composition is the checker's, made from what each node's engine answers** (novox/hq ADR 0593 §7, §9). @@ -35,7 +40,7 @@ type EngineAsker interface { // Sandbox runs a compose job on the Go build seat and answers the composer's verdict. type Sandbox interface { - Compose(ctx context.Context, job delivery.ComposeJob) (composer.Verdict, error) + Compose(ctx context.Context, job buildstep.ComposeJob) (composer.Verdict, error) } // EngineCompositions is the checker's composition of a check (Examiner.Compositions). @@ -54,7 +59,7 @@ type EngineCompositions struct { // Manifests are the indexer's trunk manifest digests by module, and Verdicts its refusals of them: the catalogue the // sandbox composes with, before the case's own manifests are laid over. Manifests TrunkManifests - Verdicts func() map[string]delivery.ManifestVerdict + Verdicts func() map[string]meshcatalog.VerdictsByDigestValue } // composerHead is the node-engine member's head of a case, or empty. @@ -66,16 +71,16 @@ func composerHead(c *Case) string { } // Compose asks every placed node and judges their answers (JudgeEngines). An error is only a case it cannot ask at all. -func (e EngineCompositions) Compose(ctx context.Context, c *Case) (delivery.CompositionVerdict, error) { +func (e EngineCompositions) Compose(ctx context.Context, c *Case) (meshwants.CompositionsByGroupHeadsValue, error) { began := time.Now() - key, err := delivery.CompositionKey(c.Group, c.HeadsHash) + key, err := nodeengineclient.CompositionKey(c.Group, c.Heads) if err != nil { - return delivery.CompositionVerdict{}, err + return meshwants.CompositionsByGroupHeadsValue{}, err } var nodes []string if e.Placed != nil { if nodes, err = e.Placed(); err != nil { - return delivery.CompositionVerdict{}, fmt.Errorf("the placement cannot be read, so no node can be asked: %w", err) + return meshwants.CompositionsByGroupHeadsValue{}, fmt.Errorf("the placement cannot be read, so no node can be asked: %w", err) } } bound := e.Bound @@ -126,7 +131,7 @@ func (e EngineCompositions) Compose(ctx context.Context, c *Case) (delivery.Comp } else if e.Composer != nil && e.Composer() != "" { composerName = e.Composer() } - v := JudgeEngines(c.Group, c.HeadsHash, composerName, nodes, answers, silent) + v := JudgeEngines(c.Group, c.Heads, composerName, nodes, answers, silent) if head != "" && e.Sandbox == nil { v.Warnings = append(v.Warnings, "the group changes the node-engine's composition code and was composed by the running engines") } @@ -138,8 +143,8 @@ func (e EngineCompositions) Compose(ctx context.Context, c *Case) (delivery.Comp // answer is refused, naming each node and each module refused with its words (the first refusing node is the machine); // passed otherwise. Every node in nodes without an answer (silent, by node with why, for the journal only) is named // with nec.NoAnswer as a warning. composer names what composed, for the line. Pure. -func JudgeEngines(group, heads, composerName string, nodes []string, answers map[string]ne.ComposeCandidateAnswer, silent map[string]string) delivery.CompositionVerdict { - v := delivery.CompositionVerdict{Group: group, Heads: heads, State: delivery.CompositionPass, SDK: composerName} +func JudgeEngines(group, heads, composerName string, nodes []string, answers map[string]ne.ComposeCandidateAnswer, silent map[string]string) meshwants.CompositionsByGroupHeadsValue { + v := meshwants.CompositionsByGroupHeadsValue{Group: group, Heads: heads, State: meshdeliverycheckerclient.CompositionPass, Sdk: composerName} sorted := append([]string(nil), nodes...) sort.Strings(sorted) var fails []string @@ -177,7 +182,7 @@ func JudgeEngines(group, heads, composerName string, nodes []string, answers map fails = append(fails, "not composed on "+node+": "+strings.Join(words, "; ")) } if len(fails) > 0 { - v.State, v.Words = delivery.CompositionFail, strings.Join(fails, "; ") + v.State, v.Words = meshdeliverycheckerclient.CompositionFail, strings.Join(fails, "; ") } if len(sorted) == 0 { v.Warnings = append(v.Warnings, "no node is placed, so the change was composed on none") @@ -232,7 +237,7 @@ func (e EngineCompositions) inSandbox(ctx context.Context, c *Case, head, node s if err != nil { return a, err } - job := delivery.ComposeJob{Fetch: map[string]any{"repository": nodeEngineRepo, "commit": head}, Dir: delivery.RootPath, + job := buildstep.ComposeJob{Fetch: buildstep.Fetch{Repository: nodeEngineRepo, Commit: head}, Dir: meshcatalogclient.RootPath, Package: composer.Package, Request: raw, Reader: "the node-engine head of " + c.Group} verdict, err := e.Sandbox.Compose(ctx, job) if err != nil { diff --git a/modules/mesh-delivery-checker/internal/check/engines_test.go b/modules/mesh-delivery-checker/internal/check/engines_test.go index b5072813..d157f802 100644 --- a/modules/mesh-delivery-checker/internal/check/engines_test.go +++ b/modules/mesh-delivery-checker/internal/check/engines_test.go @@ -9,10 +9,15 @@ import ( "testing" "time" + "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" nec "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" ne "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-delivery/sdk/composer" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // fakeEngines answers compose-candidate by node from memory; a node it holds no answer for never answers. @@ -45,12 +50,12 @@ func (f *fakeEngines) ComposeCandidate(ctx context.Context, node string, ask ne. // fakeSandbox answers a compose job with a verdict, recording the job. type fakeSandbox struct { mu sync.Mutex - jobs []delivery.ComposeJob + jobs []buildstep.ComposeJob v func(node string) composer.Verdict err error } -func (f *fakeSandbox) Compose(_ context.Context, job delivery.ComposeJob) (composer.Verdict, error) { +func (f *fakeSandbox) Compose(_ context.Context, job buildstep.ComposeJob) (composer.Verdict, error) { var req composer.Request if err := json.Unmarshal(job.Request, &req); err != nil { return composer.Verdict{}, err @@ -80,8 +85,8 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) { Own: ne.Module{Module: nec.Own, State: nec.ModuleComposed, Digest: "sha256:" + strings.Repeat("b", 64)}} } cs := caseOf(t, pullOf(repoA, 7, head1)) - key, _ := delivery.CompositionKey(cs.Group, cs.HeadsHash) - compose := func(e *fakeEngines, nodes ...string) delivery.CompositionVerdict { + key, _ := nodeengineclient.CompositionKey(cs.Group, cs.HeadsHash) + compose := func(e *fakeEngines, nodes ...string) meshwants.CompositionsByGroupHeadsValue { t.Helper() ec := EngineCompositions{Asker: e, Placed: func() ([]string, error) { return nodes, nil }, Bound: 50 * time.Millisecond, Composer: func() string { return strings.Repeat("7", 40) }} @@ -99,16 +104,16 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) { {Module: "mail", State: nec.ModuleHeld, Words: "held as today"}} e := &fakeEngines{answers: map[string]ne.ComposeCandidateAnswer{"anchor": composed("anchor"), "laptop": refusing}} v := compose(e, "anchor", "laptop") - if v.State != delivery.CompositionFail || v.Machine != "laptop" || !strings.Contains(v.Words, "not composed on laptop: web: a required value is unset") || + if v.State != meshdeliverycheckerclient.CompositionFail || v.Machine != "laptop" || !strings.Contains(v.Words, "not composed on laptop: web: a required value is unset") || strings.Contains(v.Words, "mail") || len(v.Machines) != 2 { t.Errorf("verdict = %+v", v) } if e.asked["anchor"].Candidate != key || e.asked["laptop"].Input { t.Errorf("asked %+v, want the check's candidate %s and no input", e.asked, key) } - var ver delivery.Verdict + var ver meshdeliverychecker.VerdictsByGroupHeadsValue withComposition(&ver, v) - if ver.State != delivery.VerdictFail || ver.Failed.Machine != "laptop" { + if ver.State != meshdeliverycheckerclient.VerdictFail || ver.Failed.Machine != "laptop" { t.Errorf("the check's verdict = %+v", ver) } }) @@ -122,7 +127,7 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) { if took := time.Since(began); took > 2*time.Second { t.Errorf("the nodes were not asked in parallel within the bound: %s", took) } - if v.State != delivery.CompositionPass || v.SDK != strings.Repeat("7", 40) || len(v.Machines) != 1 { + if v.State != meshdeliverycheckerclient.CompositionPass || v.Sdk != strings.Repeat("7", 40) || len(v.Machines) != 1 { t.Fatalf("verdict = %+v", v) } for _, node := range []string{"ace", "g14", "laptop"} { @@ -130,16 +135,16 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) { t.Errorf("%s is not named: %v", node, v.Warnings) } } - var ver delivery.Verdict - ver.State = delivery.VerdictPass + var ver meshdeliverychecker.VerdictsByGroupHeadsValue + ver.State = meshdeliverycheckerclient.VerdictPass withComposition(&ver, v) - if ver.State != delivery.VerdictPass || !strings.Contains(composition(ver.Composition, 0, false), "laptop: "+nec.NoAnswer) { + if ver.State != meshdeliverycheckerclient.VerdictPass || !strings.Contains(composition(ver.Composition, 0, false), "laptop: "+nec.NoAnswer) { t.Errorf("the line = %q", composition(ver.Composition, 0, false)) } }) t.Run("a node-engine member is composed by the composer at its head over each node's input", func(t *testing.T) { - c, err := delivery.DecodeCandidate(candidateOf(t, "feat/x", pullOf(nodeEngineRepo, 3, head2), pullOf(repoA, 7, head1))) + c, err := meshcatalog.DecodeCandidatesByGroupValue(candidateOf(t, "feat/x", pullOf(nodeEngineRepo, 3, head2), pullOf(repoA, 7, head1))) if err != nil { t.Fatal(err) } @@ -163,11 +168,11 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) { job := sb.jobs[0] var req composer.Request _ = json.Unmarshal(job.Request, &req) - if job.Fetch["repository"] != nodeEngineRepo || job.Fetch["commit"] != head2 || job.Package != composer.Package || + if job.Fetch.Repository != nodeEngineRepo || job.Fetch.Commit != head2 || job.Package != composer.Package || string(req.Input) != `{"nodes":[{"name":"anchor"}]}` || req.Cases[0].Manifests["web"].Digest == "" { t.Errorf("job = %+v, request %+v", job, req) } - if v.State != delivery.CompositionFail || v.SDK != head2 || !strings.Contains(v.Words, "the new composer refuses it") { + if v.State != meshdeliverycheckerclient.CompositionFail || v.Sdk != head2 || !strings.Contains(v.Words, "the new composer refuses it") { t.Errorf("verdict = %+v", v) } // ADR 0593 §9 (ADR 0601 §4's kind): a node the composer did not judge is named as no answer with its words, by the @@ -185,7 +190,7 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) { } v, err = ec.Compose(context.Background(), host) named := len(v.Warnings) == 1 && strings.Contains(v.Warnings[0], "anchor: "+nec.NoAnswer+" (its node-engine sends no running state for web)") - if err != nil || (v.State == delivery.CompositionPass) != row.pass || named != row.pass { + if err != nil || (v.State == meshdeliverycheckerclient.CompositionPass) != row.pass || named != row.pass { t.Errorf("%+v: verdict %+v, %v", row.m, v, err) } } @@ -193,7 +198,7 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) { // run fails the composition, naming the head, never passing as a silent node. sb.err = errors.New("the build of mesh-compose failed") v, err = ec.Compose(context.Background(), host) - if err != nil || v.State != delivery.CompositionFail || !strings.Contains(v.Words, head2) || + if err != nil || v.State != meshdeliverycheckerclient.CompositionFail || !strings.Contains(v.Words, head2) || !strings.Contains(v.Words, "the build of mesh-compose failed") || len(v.Warnings) != 0 { t.Errorf("a composer that cannot run: %+v, %v", v, err) } @@ -208,7 +213,7 @@ func TestTheCompositionIsMadeFromTheEnginesAnswers(t *testing.T) { b := newBench(t) b.ex.Compositions = ec v := b.judge(t, cs).Verdict - if v.Composition == nil || v.Composition.State != delivery.CompositionNotRun || !strings.Contains(v.Composition.Words, "no such bucket") { + if v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionNotRun || !strings.Contains(v.Composition.Words, "no such bucket") { t.Errorf("the verdict's composition = %+v", v.Composition) } }) diff --git a/modules/mesh-delivery-checker/internal/check/every_failure_test.go b/modules/mesh-delivery-checker/internal/check/every_failure_test.go index e0c03bd1..67638802 100644 --- a/modules/mesh-delivery-checker/internal/check/every_failure_test.go +++ b/modules/mesh-delivery-checker/internal/check/every_failure_test.go @@ -6,23 +6,26 @@ import ( "strings" "testing" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // failingResult is a level's test result in which the named tests failed, each in its own package. func failingResult(t *testing.T, tests int, failed ...Failure) json.RawMessage { t.Helper() - raw, err := json.Marshal(Tested{Level: delivery.LevelIntegration, Tests: tests, Elapsed: 2, Failed: failed, Passed: len(failed) == 0}) + raw, err := json.Marshal(Tested{Level: buildstep.LevelIntegration, Tests: tests, Elapsed: 2, Failed: failed, Passed: len(failed) == 0}) if err != nil { t.Fatal(err) } return raw } -func judgeLevel(t *testing.T, tests ...json.RawMessage) (*delivery.Verdict, *state) { +func judgeLevel(t *testing.T, tests ...json.RawMessage) (*meshdeliverychecker.VerdictsByGroupHeadsValue, *state) { t.Helper() - st := &state{j: &Examiner{}, c: &Case{}, levels: map[string]*acc{delivery.LevelUnit: {}, delivery.LevelIntegration: {}}, nodes: map[string]bool{}} - v, _ := st.level(t.Context(), &target{pull: Pull{Repository: "novox/x"}}, delivery.LevelAnswer{Level: delivery.LevelIntegration, Tests: tests}) + st := &state{j: &Examiner{}, c: &Case{}, levels: map[string]*acc{buildstep.LevelUnit: {}, buildstep.LevelIntegration: {}}, nodes: map[string]bool{}} + v, _ := st.level(t.Context(), &target{pull: Pull{Repository: "novox/x"}}, buildstep.LevelAnswer{Level: buildstep.LevelIntegration, Tests: tests}) return settled(joinFailures(v)), st } @@ -48,29 +51,29 @@ func TestTwoFailingTestsInTwoPackagesAreTwoEntries(t *testing.T) { if len(v.Failures) != 2 || v.Failed.Test != "integration: x/a.TestA" || !strings.Contains(v.Failed.Words, "a_test.go:3: logged") { t.Errorf("%s: verdict %+v", name, v) } - if run := st.levels[delivery.LevelIntegration].run; run != 10 { + if run := st.levels[buildstep.LevelIntegration].run; run != 10 { t.Errorf("%s: %d tests counted, want all 10", name, run) } - if _, err := delivery.EncodeVerdict(delivery.Verdict{Group: "g", Heads: "0123456789ab", State: v.State, Failed: v.Failed, Failures: v.Failures}); err != nil { + if _, err := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: "g", Heads: "0123456789ab", State: v.State, Failed: v.Failed, Failures: v.Failures}); err != nil { t.Errorf("%s: the verdict is not written: %v", name, err) } } // Design 26 *Out of step* (DLV-C2): the out-of-step files the compare step names, in each result, are the outcome's for // the pull request, each once and sorted; a level naming none leaves the outcome with none. step := func(files ...string) json.RawMessage { - raw, _ := json.Marshal(Tested{Level: delivery.LevelUnit, Tests: 1, Passed: false, Why: "out of step", OutOfStep: files}) + raw, _ := json.Marshal(Tested{Level: buildstep.LevelUnit, Tests: 1, Passed: false, Why: "out of step", OutOfStep: files}) return raw } _, st := judgeLevel(t, step("go/seats/b/contract.go", "go/seats/a/contract.go"), step("go/seats/a/contract.go")) - if got := st.outcome(delivery.Verdict{State: delivery.VerdictFail}).OutOfStep["novox/x#0"]; strings.Join(got, ",") != "go/seats/a/contract.go,go/seats/b/contract.go" { + if got := st.outcome(meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail}).OutOfStep["novox/x#0"]; strings.Join(got, ",") != "go/seats/a/contract.go,go/seats/b/contract.go" { t.Errorf("out of step %v", got) } - if _, st := judgeLevel(t, failingResult(t, 2, a)); st.outcome(delivery.Verdict{State: delivery.VerdictFail}).OutOfStep != nil { + if _, st := judgeLevel(t, failingResult(t, 2, a)); st.outcome(meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail}).OutOfStep != nil { t.Errorf("a level naming no out-of-step file has %v", st.outOfStep) } } -// Issue 1921: past delivery.MaxFailures the list names the first fifty and how many more, the status line says how many +// Issue 1921: past meshdeliverycheckerclient.MaxFailures the list names the first fifty and how many more, the status line says how many // failed and names the first, cut so "and N more" stays whole, and each entry's line is cut to MaxFailureLine. func TestFailuresAreCappedWithHowManyMore(t *testing.T) { var fs []Failure @@ -78,7 +81,7 @@ func TestFailuresAreCappedWithHowManyMore(t *testing.T) { fs = append(fs, Failure{Package: fmt.Sprintf("x/p%d", i), Test: "TestT", Cause: strings.Repeat("y", 500)}) } v, _ := judgeLevel(t, failingResult(t, 60, fs...)) - if len(v.Failures) != delivery.MaxFailures+1 || v.Failures[delivery.MaxFailures].Test != "and 10 more" { + if len(v.Failures) != meshdeliverycheckerclient.MaxFailures+1 || v.Failures[meshdeliverycheckerclient.MaxFailures].Test != "and 10 more" { t.Fatalf("%d entries, last %+v", len(v.Failures), v.Failures[len(v.Failures)-1]) } if len(v.Failures[0].Words) > MaxFailureLine { @@ -93,14 +96,14 @@ func TestFailuresAreCappedWithHowManyMore(t *testing.T) { t.Errorf("line %q", line) } // A composition failing after them is counted in the last entry (the operator, 2026-11-02). - withComposition(v, delivery.CompositionVerdict{State: delivery.CompositionFail, Machine: "g14", Words: "route-proxy is defined twice"}) + withComposition(v, meshwants.CompositionsByGroupHeadsValue{State: meshdeliverycheckerclient.CompositionFail, Machine: "g14", Words: "route-proxy is defined twice"}) if n := failedCount(*v); n != 61 || v.Failed.Test != "integration: x/p0.TestT" { t.Errorf("after the composition failedCount = %d, failed %+v", n, v.Failed) } // A verdict that names no list (a conflict, a refused step, a verdict written before issue 1921) is one failed line, as // before. - one := delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: "g14", Words: "files in conflict: go.mod\nmore"}} + one := meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Machine: "g14", Words: "files in conflict: go.mod\nmore"}} if got := failedLines(one); len(got) != 1 || got[0] != "g14: files in conflict: go.mod" { t.Fatalf("%q", got) } diff --git a/modules/mesh-delivery-checker/internal/check/examiner_test.go b/modules/mesh-delivery-checker/internal/check/examiner_test.go index b5d20493..53cf9215 100644 --- a/modules/mesh-delivery-checker/internal/check/examiner_test.go +++ b/modules/mesh-delivery-checker/internal/check/examiner_test.go @@ -20,10 +20,17 @@ import ( gobuild "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep" "git.novox.be/novox/mesh-contracts/go/seats/meshconfig" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-delivery/sdk/composer" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // captured reads a result the build module's own code marshalled (testdata/captured/README). @@ -80,10 +87,10 @@ func (f *fakeSteps) tool(name string) []stepCall { return out } -type entries []delivery.NodeToolchain +type entries []buildstep.NodeToolchain -func (e entries) Entries(language string) []delivery.NodeToolchain { - var out []delivery.NodeToolchain +func (e entries) Entries(language string) []buildstep.NodeToolchain { + var out []buildstep.NodeToolchain for _, x := range e { if x.Language == language { out = append(out, x) @@ -104,8 +111,8 @@ func (l langs) BuildModules(repository string) []string { func goEntries(nodes ...string) entries { var e entries for _, n := range nodes { - e = append(e, delivery.NodeToolchain{Node: n, Toolchain: delivery.Toolchain{Language: "go", Version: "go1.27.1", Recipe: "7"}}) - e = append(e, delivery.NodeToolchain{Node: n, Toolchain: delivery.Toolchain{Language: "typescript", Version: "node24", Recipe: "3"}}) + e = append(e, buildstep.NodeToolchain{Node: n, Toolchain: buildstep.Toolchain{Language: "go", Version: "go1.27.1", Recipe: "7"}}) + e = append(e, buildstep.NodeToolchain{Node: n, Toolchain: buildstep.Toolchain{Language: "typescript", Version: "node24", Recipe: "3"}}) } return e } @@ -140,8 +147,8 @@ var toolPrefix = map[string]string{buildrun.GoBuildModule: "build_go_", buildrun // answer runs a check ask as the build module that pulls it does (gobuild.RunAsk), with the fake tools on shanks and the // bench's kept verdicts, and answers the step it writes. -func (b *bench) answer(a delivery.StepAsk) delivery.Step { - module, _ := delivery.BuildModuleOf(a.Language) +func (b *bench) answer(a buildstep.StepAsk) buildstep.Step { + module, _ := buildstep.BuildModuleOf(a.Language) const node = "shanks" tool := func(name string) gobuild.StepFunc { return func(ctx context.Context, args map[string]any) (any, error) { @@ -155,7 +162,7 @@ func (b *bench) answer(a delivery.StepAsk) delivery.Step { steps := gobuild.AskSteps{Fetch: tool("fetch"), Test: tool("test"), Kept: func(ctx context.Context, level, digest string) (int, bool, error) { k, found, err := (Kept{Store: b.kept}).Lookup(ctx, level, digest) - return k.Tests, found, err + return int(k.Tests), found, err }} if module == buildrun.GoBuildModule { steps.Digest = tool("digest") @@ -164,16 +171,16 @@ func (b *bench) answer(a delivery.StepAsk) delivery.Step { if len(a.Needs) > 0 { if b.declines > 0 { b.declines-- - result, _ := json.Marshal(delivery.StepDeclined{Declined: true, Needs: a.Needs, Lacks: a.Needs}) - return delivery.Step{ID: a.ID, Tool: a.Kind + " ask", Node: "ace", State: delivery.StepRefused, Started: began, Ended: began, + result, _ := json.Marshal(buildstep.StepDeclined{Declined: true, Needs: a.Needs, Lacks: a.Needs}) + return buildstep.Step{ID: a.ID, Tool: a.Kind + " ask", Node: "ace", State: buildstep.StepRefused, Started: began, Ended: began, Reason: "declined", Result: result} } steps.Has = func(string) bool { return true } } out, err := gobuild.RunAsk(context.Background(), steps, a) - step := delivery.Step{ID: a.ID, Tool: a.Kind + " ask", Node: node, State: delivery.StepPassed, Started: began, Ended: began.Add(2 * time.Second)} + step := buildstep.Step{ID: a.ID, Tool: a.Kind + " ask", Node: node, State: buildstep.StepPassed, Started: began, Ended: began.Add(2 * time.Second)} if err != nil { - step.State, step.Reason = delivery.StepFailed, err.Error() + step.State, step.Reason = buildstep.StepFailed, err.Error() return step } step.Result, _ = json.Marshal(out) @@ -188,8 +195,8 @@ func newBench(t testing.TB) *bench { b.fetch.Modules, b.fetch.Changed, b.fetch.Trees = []string{"."}, []string{"README.md"}, nil one := captured[Digested](t, "go-digest") b.digest = map[string]Digested{ - delivery.LevelUnit: {Level: "unit", Packages: map[string]string{"fixture/slow": digA}}, - delivery.LevelIntegration: {Level: "integration", Packages: map[string]string{"fixture/slow": digB}}, + buildstep.LevelUnit: {Level: "unit", Packages: map[string]string{"fixture/slow": digA}}, + buildstep.LevelIntegration: {Level: "integration", Packages: map[string]string{"fixture/slow": digB}}, } _ = one slow := captured[Tested](t, "go-test-slow") @@ -212,10 +219,10 @@ func newBench(t testing.TB) *bench { return b } -func caseOf(t testing.TB, pulls ...delivery.CandidatePull) *Case { +func caseOf(t testing.TB, pulls ...meshcatalog.CandidatesByGroupValuePullsItem) *Case { t.Helper() raw := candidateOf(t, "feat/x", pulls...) - c, err := delivery.DecodeCandidate(raw) + c, err := meshcatalog.DecodeCandidatesByGroupValue(raw) if err != nil { t.Fatal(err) } @@ -257,10 +264,10 @@ func TestACleanMergeIsFetchedAtItsTrunkAndBothLevelsPass(t *testing.T) { // 1679 cs := caseOf(t, pullOf(repoA, 7, head1)) out := b.judge(t, cs) v := out.Verdict - if v.State != delivery.VerdictPass || v.Levels[delivery.LevelUnit].Run != 2 || v.Levels[delivery.LevelIntegration].Run != 2 { + if v.State != meshdeliverycheckerclient.VerdictPass || v.Levels[buildstep.LevelUnit].Run != 2 || v.Levels[buildstep.LevelIntegration].Run != 2 { t.Fatalf("verdict = %+v", v) } - if v.Composition == nil || v.Composition.State != delivery.CompositionNotRun { + if v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionNotRun { t.Errorf("composition = %+v, want not run", v.Composition) } if len(v.Machines) != 1 || v.Machines[0] != "shanks" { @@ -292,7 +299,7 @@ func TestACleanMergeIsFetchedAtItsTrunkAndBothLevelsPass(t *testing.T) { // 1679 out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) asks := b.asks.Asks() if c.fails != "" { - if out.Verdict.State != delivery.VerdictFail || len(asks) != 0 || !strings.Contains(out.Verdict.Failed.Words, c.fails) { + if out.Verdict.State != meshdeliverycheckerclient.VerdictFail || len(asks) != 0 || !strings.Contains(out.Verdict.Failed.Words, c.fails) { t.Errorf("names %v, %v: verdict %+v, %d asks", c.names, c.err, out.Verdict, len(asks)) } } else if len(asks) == 0 || strings.Join(asks[0].Check.NodeNames, ",") != "anchor,laptop" { @@ -311,7 +318,7 @@ func (n namesOf) Names(context.Context) ([]string, error) { return n.names, n.er func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1 b := newBench(t) - b.digest[delivery.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/slow": digA, "fixture/other": digC}} + b.digest[buildstep.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/slow": digA, "fixture/other": digC}} if _, err := (Kept{Store: b.kept}).Keep(context.Background(), "unit", digC, keptValue("fixture/other", 40)); err != nil { t.Fatal(err) } @@ -320,7 +327,7 @@ func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1 if len(tests) != 2 || strings.Join(packagesOf(tests[0]), ",") != "fixture/slow" || tests[0].args["level"] != "unit" { t.Fatalf("test calls = %+v, want the unit level of fixture/slow alone, then the integration level", tests) } - if u := out.Verdict.Levels[delivery.LevelUnit]; u.Run != 2 || u.Kept != 40 { + if u := out.Verdict.Levels[buildstep.LevelUnit]; u.Run != 2 || u.Kept != 40 { t.Errorf("unit = %+v, want 2 run and 40 kept", u) } @@ -340,11 +347,11 @@ func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1 } return d, nil } - b.digest[delivery.LevelIntegration] = Digested{Level: "integration", - Needs: []delivery.PackageNeeds{{Package: "fixture/node", Needs: []string{delivery.NeedNodeBuildNode}}}} + b.digest[buildstep.LevelIntegration] = Digested{Level: "integration", + Needs: []buildstep.PackageNeeds{{Package: "fixture/node", Needs: []string{buildstep.NeedNodeBuildNode}}}} out = b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) asks := b.asks.Asks() - if out.Verdict.State != delivery.VerdictPass || len(asks) != 3 || len(asks[0].Needs) != 0 { + if out.Verdict.State != meshdeliverycheckerclient.VerdictPass || len(asks) != 3 || len(asks[0].Needs) != 0 { t.Fatalf("verdict %+v, asks %+v: want a pass of three asks, the first naming no need", out.Verdict, asks) } for _, a := range asks { // ADR 0584: every check ask names the agreed toolchain, so a node off it declines @@ -353,7 +360,7 @@ func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1 } } for _, a := range asks[1:] { - if strings.Join(a.Needs, ",") != delivery.NeedNodeBuildNode || strings.Join(a.Check.Levels, ",") != delivery.LevelIntegration || strings.Join(a.Check.Packages, ",") != "fixture/node" { + if strings.Join(a.Needs, ",") != buildstep.NeedNodeBuildNode || strings.Join(a.Check.Levels, ",") != buildstep.LevelIntegration || strings.Join(a.Check.Packages, ",") != "fixture/node" { t.Errorf("a placed ask %+v, want the integration level needing node-build-node", a) } } @@ -362,13 +369,13 @@ func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1 t.Errorf("test calls = %+v, want the unit level, the integration level of fixture/slow and fixture/outside, then fixture/node alone", tests) } slow := captured[Tested](t, "go-test-slow") - if i := out.Verdict.Levels[delivery.LevelIntegration]; i.Run != 2*slow.Tests || i.Kept != 0 { + if i := out.Verdict.Levels[buildstep.LevelIntegration]; int(i.Run) != 2*slow.Tests || i.Kept != 0 { t.Errorf("integration = %+v, want %d run and nothing counted kept", i, 2*slow.Tests) } // And where no node has the need, the check fails once the declines outlast the bound, naming the need. b.kept, b.declines = newMemKV(), 1000 b.ex.Kept, b.ex.DeclineBound = Kept{Store: b.kept}, 200*time.Millisecond - if v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict; v.State != delivery.VerdictFail || !strings.Contains(fmt.Sprint(v), "needs a node with node-build-node") { + if v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict; v.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(failedOf(v).Words, "needs a node with node-build-node") { t.Errorf("with no node having the need: %+v", v) } } @@ -376,7 +383,7 @@ func TestOnlyThePackageWithoutAKeptVerdictRuns(t *testing.T) { // 1288/AC1 func TestAReAskRunsOnlyWhatFailedAndWhatChanged(t *testing.T) { // 1288/AC2, AC3 b := newBench(t) failing := captured[Tested](t, "go-test-failing") - b.digest[delivery.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/failing/bad": digA, "fixture/failing/good": digB}} + b.digest[buildstep.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/failing/bad": digA, "fixture/failing/good": digB}} b.test = func(c stepCall) (any, error) { if c.args["level"] == "unit" { return failing, nil @@ -385,7 +392,7 @@ func TestAReAskRunsOnlyWhatFailedAndWhatChanged(t *testing.T) { // 1288/AC2, AC3 } cs := caseOf(t, pullOf(repoA, 7, head1)) first := b.judge(t, cs).Verdict - if first.State != delivery.VerdictFail || first.Failed.Test != "unit: fixture/failing/bad.TestThreeIsFour" { + if first.State != meshdeliverycheckerclient.VerdictFail || first.Failed.Test != "unit: fixture/failing/bad.TestThreeIsFour" { t.Fatalf("first = %+v", first) } if _, _, found, _ := b.kept.Get(context.Background(), "unit."+digB); !found { @@ -402,7 +409,7 @@ func TestAReAskRunsOnlyWhatFailedAndWhatChanged(t *testing.T) { // 1288/AC2, AC3 t.Fatalf("the re-ask ran %+v, want only fixture/failing/bad", tests) } // The trunk moved and one digest changed: that package runs, the unchanged one is taken. - b.digest[delivery.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/failing/bad": digA, "fixture/failing/good": digD}} + b.digest[buildstep.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"fixture/failing/bad": digA, "fixture/failing/good": digD}} b.steps.calls = nil b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) tests = b.steps.tool("test") @@ -421,7 +428,7 @@ func TestANodePackageIsNeverKeptAndRunsEveryCheck(t *testing.T) { // 1288/AC4, 1 cs := caseOf(t, pullOf(repoA, 7, head1)) for i := 0; i < 2; i++ { v := b.judge(t, cs).Verdict - if v.State != delivery.VerdictPass || v.Levels[delivery.LevelUnit].Kept != 0 || v.Levels[delivery.LevelUnit].Run != 1 { + if v.State != meshdeliverycheckerclient.VerdictPass || v.Levels[buildstep.LevelUnit].Kept != 0 || v.Levels[buildstep.LevelUnit].Run != 1 { t.Fatalf("round %d: verdict = %+v", i, v) } } @@ -445,11 +452,11 @@ func TestANodePackageIsNeverKeptAndRunsEveryCheck(t *testing.T) { // 1288/AC4, 1 func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) { for _, conflicts := range []bool{false, true} { b := newBench(t) - comps := &fakeCompositions{v: delivery.CompositionVerdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.CompositionFail, SDK: "x", + comps := &fakeCompositions{v: meshwants.CompositionsByGroupHeadsValue{Group: "feat/x", Heads: "0123456789ab", State: meshdeliverycheckerclient.CompositionFail, Sdk: "x", Machine: "g14", Words: "route-proxy is defined twice"}, waits: conflicts} b.ex.Compositions = comps b.fetch.Changed = []string{"modules/m/internal/b/b_test.go"} - b.digest[delivery.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"m/internal/a": digA, "m/internal/b": digB}} + b.digest[buildstep.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"m/internal/a": digA, "m/internal/b": digB}} conflict := captured[Fetched](t, "go-fetch-conflict") b.steps.handlers["build_go_fetch"] = func(c stepCall) (any, error) { if conflicts && c.args["repository"] == repoSDK { @@ -467,9 +474,9 @@ func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) { return captured[Tested](t, "go-test-slow"), nil } cs := caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2)) - done := make(chan delivery.Verdict, 1) + done := make(chan meshdeliverychecker.VerdictsByGroupHeadsValue, 1) go func() { done <- b.judge(t, cs).Verdict }() - var v delivery.Verdict + var v meshdeliverychecker.VerdictsByGroupHeadsValue select { case v = <-done: case <-time.After(5 * time.Second): @@ -477,14 +484,14 @@ func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) { } var got []string for _, f := range v.Failures { - got = append(got, firstFailed(delivery.Verdict{Failed: f})) + got = append(got, firstFailed(meshdeliverychecker.VerdictsByGroupHeadsValue{Failed: &FailedFirst{Test: f.Test, Machine: f.Machine, Words: f.Words}})) } tests := []string{"unit: fixture/failing/bad.TestThreeIsFour", "integration: fixture/failing/bad.TestThreeIsFour"} want := append(append(slices.Clone(tests), tests...), "g14: route-proxy is defined twice") // both members fail their tests if conflicts { want = append([]string{"shanks: novox/mesh-sdk#12 does not merge into the trunk: files in conflict: a.go"}, tests...) } - if v.State != delivery.VerdictFail || len(got) != len(want) { + if v.State != meshdeliverycheckerclient.VerdictFail || len(got) != len(want) { t.Fatalf("conflicts %v: verdict %+v: failures\n%s\nwant\n%s", conflicts, v, strings.Join(got, "\n"), strings.Join(want, "\n")) } for i := range want { @@ -494,9 +501,9 @@ func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) { } keys, _ := b.prev.Keys(context.Background()) switch { - case !conflicts && (len(keys) != 2 || v.Composition == nil || v.Composition.State != delivery.CompositionFail): + case !conflicts && (len(keys) != 2 || v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionFail): t.Errorf("tests alone failed: previews %v, composition %+v, want both previews and the composition read", keys, v.Composition) - case conflicts && (len(comps.asked) != 0 || v.Composition == nil || v.Composition.State != delivery.CompositionNotRun || + case conflicts && (len(comps.asked) != 0 || v.Composition == nil || v.Composition.State != meshdeliverycheckerclient.CompositionNotRun || !strings.Contains(v.Composition.Words, "novox/mesh-sdk#12")): t.Errorf("a member did not merge: asked %v, composition %+v, want not run naming novox/mesh-sdk#12", comps.asked, v.Composition) } @@ -510,7 +517,7 @@ func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) { t.Errorf("the unit level ran %v and %d digests, want the changed test's package, then the rest, at both levels", unit, len(b.steps.tool("digest"))) } v.Group, v.Heads = "feat/x", "0123456789ab" - if _, err := delivery.EncodeVerdict(v); err != nil { + if _, err := meshdeliverycheckerclient.EncodeVerdict(v); err != nil { t.Errorf("conflicts %v: the verdict cannot be written: %v", conflicts, err) } if line := Line(v, cs, cs.Pulls[0], nil, nil, nil); !strings.HasPrefix(line, fmt.Sprintf("failed (%d): %s", len(want), want[0][:20])) || @@ -523,9 +530,9 @@ func TestEveryFailureOfEveryMemberLevelAndTheCompositionIsNamed(t *testing.T) { func TestTheChangesOwnPassingTestRunsFirstAndTheRestFollow(t *testing.T) { // 1289/AC3 b := newBench(t) b.fetch.Changed = []string{"modules/m/internal/b/b_test.go", "modules/m/internal/a/a.go"} - b.digest[delivery.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"m/internal/a": digA, "m/internal/b": digB}} + b.digest[buildstep.LevelUnit] = Digested{Level: "unit", Packages: map[string]string{"m/internal/a": digA, "m/internal/b": digB}} out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) - if out.Verdict.State != delivery.VerdictPass { + if out.Verdict.State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("verdict = %+v", out.Verdict) } var unit []string @@ -544,7 +551,7 @@ func TestAHeadThatDoesNotMergeEndsFailedNamingTheFilesAndRunsNoTest(t *testing.T b.fetch = captured[Fetched](t, "go-fetch-conflict") out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) f := out.Verdict.Failed - if out.Verdict.State != delivery.VerdictFail || f.Machine != "shanks" || !strings.Contains(f.Words, "files in conflict: a.go, sub/b.go") { + if out.Verdict.State != meshdeliverycheckerclient.VerdictFail || f.Machine != "shanks" || !strings.Contains(f.Words, "files in conflict: a.go, sub/b.go") { t.Fatalf("verdict = %+v", out.Verdict) } if n := len(b.steps.tool("digest")) + len(b.steps.tool("test")); n != 0 { @@ -556,7 +563,7 @@ func TestALevelWhoseDigestIsNotNamedRunsWholeAndKeepsNothing(t *testing.T) { // b := newBench(t) b.steps.handlers["build_go_digest"] = func(stepCall) (any, error) { return nil, fmt.Errorf("go list failed") } out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) - if out.Verdict.State != delivery.VerdictPass { + if out.Verdict.State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("verdict = %+v", out.Verdict) } tests := b.steps.tool("test") @@ -580,7 +587,7 @@ func TestALevelOverItsBoundIsNamedWithItsSlowestTestsAndKeepsItsVerdict(t *testi } out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) v := out.Verdict - if v.State != delivery.VerdictPass || strings.Join(v.OverBound, ",") != "unit,integration" { + if v.State != meshdeliverycheckerclient.VerdictPass || strings.Join(v.OverBound, ",") != "unit,integration" { t.Fatalf("verdict = %+v", v) } if len(out.Slowest) == 0 || !strings.Contains(out.Slowest[0], "TestTakesAWhile") { @@ -594,7 +601,7 @@ func TestSkippedTestsAreCountedPerLevel(t *testing.T) { // 1300/AC6, 1251 return testedWith(t, "go-test-slow", func(r *Tested) { r.Level = c.args["level"].(string); r.Skipped = 3 }), nil } v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - if v.Levels[delivery.LevelUnit].Skipped != 3 || v.Levels[delivery.LevelIntegration].Skipped != 3 { + if v.Levels[buildstep.LevelUnit].Skipped != 3 || v.Levels[buildstep.LevelIntegration].Skipped != 3 { t.Errorf("levels = %+v", v.Levels) } } @@ -603,7 +610,7 @@ func TestAGroupIsOneVerdictAndEachMembersTestsReadTheOthersAtTheirHeads(t *testi b := newBench(t) cs := caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2)) v := b.judge(t, cs).Verdict - if v.State != delivery.VerdictPass { + if v.State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("verdict = %+v", v) } fetch := b.steps.tool("fetch") @@ -618,7 +625,7 @@ func TestAGroupIsOneVerdictAndEachMembersTestsReadTheOthersAtTheirHeads(t *testi } found := false for _, r := range forA { - if r.Repository == repoSDK && r.Commit == head2 && r.Ref == "refs/pull/12/head" { + if r.Repository == repoSDK && r.Commit == head2 { found = true } } @@ -637,17 +644,17 @@ const ( func TestACleanMergeWritesItsPreviewWithTheTreesManifestsAndLocks(t *testing.T) { // 1445/AC1 b := newBench(t) - b.fetch.Trees = map[string]MergeTree{ + b.fetch.Trees = map[string]buildstep.ModuleTree{ "modules/avahi": {Tree: "7777777777777777777777777777777777777777", Manifest: manA, Locks: map[string]string{"go.sum": "ff00"}}, "modules/bare": {Tree: "8888888888888888888888888888888888888888"}, // no manifest digest: left out } b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) - key, _ := delivery.PreviewKey(repoA, head1, trunkA) + key, _ := meshdeliverycheckerclient.PreviewKey(repoA, head1, trunkA) raw, _, found, _ := b.prev.Get(context.Background(), key) if !found { t.Fatalf("no preview %s; the bucket holds %v", key, b.prev.vals) } - pv, err := delivery.DecodePreview(raw) + pv, err := meshdeliverycheckerclient.DecodePreview(raw) if err != nil { t.Fatal(err) } @@ -661,12 +668,12 @@ func TestAHeadThatDoesNotMergeWritesAPreviewOfItsConflictsAlone(t *testing.T) { b := newBench(t) b.fetch = captured[Fetched](t, "go-fetch-conflict") b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) - key, _ := delivery.PreviewKey(repoA, head1, trunkA) + key, _ := meshdeliverycheckerclient.PreviewKey(repoA, head1, trunkA) raw, _, found, _ := b.prev.Get(context.Background(), key) if !found { t.Fatalf("no preview for a head that does not merge; the bucket holds %v", b.prev.vals) } - pv, err := delivery.DecodePreview(raw) + pv, err := meshdeliverycheckerclient.DecodePreview(raw) if err != nil || strings.Join(pv.Conflicts, ",") != "a.go,sub/b.go" || pv.Merge != "" || len(pv.Modules) != 0 { t.Errorf("preview = %+v, %v, want the files in conflict and no merge", pv, err) } @@ -677,12 +684,12 @@ func TestAPreviewIsWrittenOnceAndAnExistingKeyStands(t *testing.T) { b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) b.fetch.Merge = "5656565656565656565656565656565656565656" b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) - key, _ := delivery.PreviewKey(repoA, head1, trunkA) + key, _ := meshdeliverycheckerclient.PreviewKey(repoA, head1, trunkA) if b.prev.creates[key] != 1 { t.Errorf("the preview was created %d times, want once", b.prev.creates[key]) } raw, _, _, _ := b.prev.Get(context.Background(), key) - if pv, _ := delivery.DecodePreview(raw); pv.Merge == b.fetch.Merge { + if pv, _ := meshdeliverycheckerclient.DecodePreview(raw); pv.Merge == b.fetch.Merge { t.Error("the second merge commit replaced the first") } } @@ -693,15 +700,15 @@ func TestTheIndexerIsToldOfAPreviewOnceWhenItIsCreated(t *testing.T) { // 1445/A b.ex.Announce = func(_ context.Context, event, key string) { told = append(told, event+" "+key) } b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) - key, _ := delivery.PreviewKey(repoA, head1, trunkA) + key, _ := meshdeliverycheckerclient.PreviewKey(repoA, head1, trunkA) if len(told) != 1 || told[0] != EventPreviewWritten+" "+key { t.Errorf("told %v, want once of %s", told, key) } } -type fakeBuilds map[string]delivery.BuilderBuild +type fakeBuilds map[string]builds.BuilderBuild -func (f fakeBuilds) Build(_ context.Context, module, fp2 string) (delivery.BuilderBuild, bool, error) { +func (f fakeBuilds) Build(_ context.Context, module, fp2 string) (builds.BuilderBuild, bool, error) { b, ok := f[module+" "+fp2] return b, ok, nil } @@ -712,13 +719,13 @@ func TestTheOutcomeSaysIfEachCandidatesModuleIsBuiltFailedOrWaiting(t *testing.T fpB := "src2:" + strings.Repeat("b", 64) fpC := "src2:" + strings.Repeat("c", 64) b.ex.Builds = fakeBuilds{ - "avahi " + fpA: {State: delivery.BuildDone}, - "mesh-tools " + fpB: {State: delivery.BuildFailed, Error: "boom"}, + "avahi " + fpA: {State: builds.BuildDone}, + "mesh-tools " + fpB: {State: builds.BuildFailed, Error: "boom"}, // netcheck has no key yet } p := pullOf(repoA, 7, head1) p.Modules = []string{"avahi", "mesh-tools", "netcheck", "nofp"} - p.Fps = map[string]delivery.CandidateModule{"avahi": {FP2: fpA}, "mesh-tools": {FP2: fpB}, "netcheck": {FP2: fpC}, "nofp": {None2: "toolchain not yet reported"}} + p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"avahi": {Fp2: fpA}, "mesh-tools": {Fp2: fpB}, "netcheck": {Fp2: fpC}, "nofp": {None2: "toolchain not yet reported"}} out := b.judge(t, caseOf(t, p)) var got []string for _, n := range out.Builds { @@ -732,7 +739,7 @@ func TestTheOutcomeSaysIfEachCandidatesModuleIsBuiltFailedOrWaiting(t *testing.T // trunksOf are trunk heads of every repository, as the gate's source names them. func trunksOf(t *testing.T, heads map[string]string) *Trunks { t.Helper() - data, err := delivery.EncodeSource(delivery.Source{Group: "g", Heads: heads}) + data, err := meshdeliveryclient.EncodeSource(meshdelivery.SourceByMainValue{Group: "g", Heads: heads}) if err != nil { t.Fatal(err) } @@ -808,8 +815,8 @@ func TestACoreRepositorysPassingPackageIsKeptUnderADigestCoveringItsSiblings(t * // The same siblings again: everything is taken, nothing runs. b.steps.calls = nil v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - if n := len(b.steps.tool("test")); n != 0 || v.Levels[delivery.LevelUnit].Run != 0 || v.Levels[delivery.LevelUnit].Kept == 0 { - t.Fatalf("%d test steps, unit %+v, want none run and the kept verdict taken", n, v.Levels[delivery.LevelUnit]) + if n := len(b.steps.tool("test")); n != 0 || v.Levels[buildstep.LevelUnit].Run != 0 || v.Levels[buildstep.LevelUnit].Kept == 0 { + t.Fatalf("%d test steps, unit %+v, want none run and the kept verdict taken", n, v.Levels[buildstep.LevelUnit]) } // A sibling's commit moves: a new digest, so the package runs again and is kept under it. b.ex.Trunks = trunksOf(t, map[string]string{repoSDK: trunkS2, "novox/mesh-catalog": trunkA}) @@ -917,15 +924,15 @@ func TestNoStepOfACheckCarriesPublish(t *testing.T) { // ADR 0500 §4, 1309 (the func TestAnAskWhoseStepNeverEndsEndsTheCheckLoudly(t *testing.T) { b := newBench(t) // A node took the ask and its step stays running: the bound runs from its start (review round 2 item 1). - b.asks.Answer = func(a delivery.StepAsk) delivery.Step { - return delivery.Step{ID: a.ID, Tool: "check ask", Node: "shanks", State: delivery.StepRunning, Started: time.Now()} + b.asks.Answer = func(a buildstep.StepAsk) buildstep.Step { + return buildstep.Step{ID: a.ID, Tool: "check ask", Node: "shanks", State: buildstep.StepRunning, Started: time.Now()} } b.ex.Asks = buildrun.Asker{Publisher: b.asks, Waiter: b.asks, Bound: 20 * time.Millisecond} out := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) - if f := out.Verdict.Failed; out.Verdict.State != delivery.VerdictFail || !strings.Contains(f.Words, "on shanks did not end within") { + if f := out.Verdict.Failed; out.Verdict.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(f.Words, "on shanks did not end within") { t.Fatalf("verdict = %+v", out.Verdict) } - if n := len(b.asks.Asks()); n != 1 || b.asks.Asks()[0].Kind != delivery.AskCheck || b.asks.Asks()[0].Check == nil { + if n := len(b.asks.Asks()); n != 1 || b.asks.Asks()[0].Kind != buildstep.AskCheck || b.asks.Asks()[0].Check == nil { t.Fatalf("asks = %+v, want one check ask", b.asks.Asks()) } } @@ -936,16 +943,20 @@ func TestACheckIsOneAskAndAReJudgeGoesOnTheRecheckStream(t *testing.T) { b := newBench(t) b.judge(t, caseOf(t, pullOf(repoA, 7, head1))) asks := b.asks.Asks() - if len(asks) != 1 || asks[0].Kind != delivery.AskCheck || asks[0].Language != delivery.AskGo || asks[0].Asker != delivery.CheckerModule { + if len(asks) != 1 || asks[0].Kind != buildstep.AskCheck || asks[0].Language != buildstep.AskGo || asks[0].Asker != CheckerModule { t.Fatalf("asks = %+v, want one Go check ask", asks) } - if j := asks[0].Check; j == nil || !j.Kept || strings.Join(j.Levels, ",") != "unit,integration" || j.Fetch["repository"] != repoA { + if j := asks[0].Check; j == nil || !j.Kept || strings.Join(j.Levels, ",") != "unit,integration" || j.Fetch.Repository != repoA { t.Fatalf("the job = %+v", asks[0].Check) } + // B2g: the job carries the toolchain the checker agreed (ADR 0584), with its language, so a node on another declines it. + if tc := asks[0].Check.Toolchain; tc == nil || *tc != (buildstep.ToolchainUsed{Language: "go", Version: "go1.27.1", Recipe: "7"}) { + t.Errorf("the job's toolchain = %+v, want the agreed go toolchain", tc) + } cs := caseOf(t, pullOf(repoA, 7, head1)) cs.Kind = KindTrunk b.judge(t, cs) - if asks := b.asks.Asks(); len(asks) != 2 || asks[1].Kind != delivery.AskRecheck { + if asks := b.asks.Asks(); len(asks) != 2 || asks[1].Kind != buildstep.AskRecheck { t.Fatalf("asks = %+v, want the re-judge on recheck", asks) } } @@ -971,7 +982,7 @@ func TestACheckAskWaitingInItsStreamIsNotBounded(t *testing.T) { default: } b.asks.End(answer(b.asks.Asks()[0])) // a node took it and ran it - if out := <-done; out.Verdict.State != delivery.VerdictPass || len(b.asks.Asks()) != 1 { + if out := <-done; out.Verdict.State != meshdeliverycheckerclient.VerdictPass || len(b.asks.Asks()) != 1 { t.Fatalf("verdict %+v after %d asks, want a pass from the one ask", out.Verdict, len(b.asks.Asks())) } } @@ -981,13 +992,13 @@ func TestACheckAskWaitingInItsStreamIsNotBounded(t *testing.T) { func TestASecondCheckOfTheSameHeadsKeepsWhatTheFirstPassed(t *testing.T) { b := newBench(t) first := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - u := first.Levels[delivery.LevelUnit] - if first.State != delivery.VerdictPass || u.Run == 0 || u.Kept != 0 { + u := first.Levels[buildstep.LevelUnit] + if first.State != meshdeliverycheckerclient.VerdictPass || u.Run == 0 || u.Kept != 0 { t.Fatalf("the first check = %+v", first) } b.steps.calls = nil second := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - if s := second.Levels[delivery.LevelUnit]; second.State != delivery.VerdictPass || s.Kept != u.Run || s.Run != 0 { + if s := second.Levels[buildstep.LevelUnit]; second.State != meshdeliverycheckerclient.VerdictPass || s.Kept != u.Run || s.Run != 0 { t.Fatalf("the second check's unit level = %+v, want %d kept and none run", s, u.Run) } if n := len(b.steps.tool("test")); n != 0 { @@ -1171,9 +1182,9 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) { if row.seats != "" { b.ex.SeatList = fakeSeatList{read: row.seats == "read"} } - var judged []delivery.ComposeJob + var judged []buildstep.ComposeJob answer := b.asks.Answer - b.asks.Answer = func(a delivery.StepAsk) delivery.Step { + b.asks.Answer = func(a buildstep.StepAsk) buildstep.Step { if a.Compose == nil { return answer(a) } @@ -1188,8 +1199,8 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) { verdict, _ := json.Marshal(composer.Verdict{V: composer.Version, SDK: "exe:1", Cases: []composer.CaseVerdict{{Name: JudgeCase, Manifests: map[string]composer.ManifestRead{"web": read, "mail": {Accepted: row.untouched == "", Refused: row.untouched}}}, {Name: JudgeTrunk, Manifests: map[string]composer.ManifestRead{"web": {Accepted: true}, "mail": {Accepted: row.trunkMail == "", Refused: row.trunkMail}}}}}) - result, _ := json.Marshal(delivery.ComposeAnswer{Key: "k", Verdict: verdict}) - return delivery.Step{ID: a.ID, State: delivery.StepPassed, Result: result} + result, _ := json.Marshal(buildstep.ComposeAnswer{Key: "k", Verdict: verdict}) + return buildstep.Step{ID: a.ID, State: buildstep.StepPassed, Result: result} } p := pullOf(repoA, 7, head1) if row.ownJudge { @@ -1197,15 +1208,15 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) { b.fetch.Changed = []string{JudgeDir + "/judge/judge.go"} } p.Modules = []string{"web"} - p.Fps = map[string]delivery.CandidateModule{"web": {FP2: "src2:" + strings.Repeat("f", 64), Manifest: web}} + p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"web": {Fp2: "src2:" + strings.Repeat("f", 64), Manifest: web}} cs := caseOf(t, p) v := b.judge(t, cs).Verdict if line := Line(v, cs, cs.Pulls[0], nil, nil, nil); row.seriesWords != "" && !strings.Contains(line, row.seriesWords) || row.seriesWords == "" && strings.Contains(line, "series") { t.Errorf("%s: the line %q, want it to name %q", row.name, line, row.seriesWords) } // The join (A2j finding 2): the verdict names each series change as {module, artifact, from, to}. - if want := []delivery.SeriesChange(nil); row.series == "0.2" { - if want = []delivery.SeriesChange{{Module: "web", Artifact: "pkg", From: "0.1", To: "0.2"}}; !reflect.DeepEqual(v.Series, want) { + if want := []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem(nil); row.series == "0.2" { + if want = []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem{{Module: "web", Artifact: "pkg", From: "0.1", To: "0.2"}}; !reflect.DeepEqual(v.Series, want) { t.Errorf("%s: the verdict's series %+v, want %+v", row.name, v.Series, want) } } else if len(v.Series) != 0 { @@ -1217,18 +1228,18 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) { t.Errorf("%s: the case says %v, want the new indexer's manifest said accepted by the trunk's manifest validator", row.name, cs.notes) } if row.seats == "unread" { - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, row.fails) || len(judged) != 0 { + if v.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(v.Failed.Words, row.fails) || len(judged) != 0 { t.Errorf("%s: %+v, judged %d, want a fail saying %q before any judge", row.name, v.Failed, len(judged), row.fails) } continue } - if row.ownJudge && (len(judged) != 2 || judged[0].Fetch["commit"] != head1 || judged[1].Fetch["commit"] != trunk) { + if row.ownJudge && (len(judged) != 2 || judged[0].Fetch.Commit != head1 || judged[1].Fetch.Commit != trunk) { t.Fatalf("%s: judged %+v, want the head's judge and then the trunk's", row.name, judged) } if row.ownJudge { judged = judged[1:] } - if len(judged) != 1 || judged[0].Fetch["repository"] != DeliveryRepository || judged[0].Fetch["commit"] != trunk || + if len(judged) != 1 || judged[0].Fetch.Repository != DeliveryRepository || judged[0].Fetch.Commit != trunk || judged[0].Package != JudgePackage || judged[0].Dir != JudgeDir { t.Fatalf("%s: judged %+v (verdict %+v)", row.name, judged, v) } @@ -1250,9 +1261,9 @@ func TestTheIndexersJudgeJudgesTheChangesManifests(t *testing.T) { req.Packages, req.Input, row.known) } switch { - case row.fails == "" && v.State != delivery.VerdictPass: + case row.fails == "" && v.State != meshdeliverycheckerclient.VerdictPass: t.Errorf("%s: %+v", row.name, v) - case row.fails != "" && (v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, row.fails)): + case row.fails != "" && (v.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(v.Failed.Words, row.fails)): t.Errorf("%s: %+v, want a fail saying %q", row.name, v.Failed, row.fails) } } diff --git a/modules/mesh-delivery-checker/internal/check/failure_words_test.go b/modules/mesh-delivery-checker/internal/check/failure_words_test.go index 9c01774d..5358d7a2 100644 --- a/modules/mesh-delivery-checker/internal/check/failure_words_test.go +++ b/modules/mesh-delivery-checker/internal/check/failure_words_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" ) // The failure the build module reported for group r4g on 2026-10-23: the test logged a line, then failed with a multi-line @@ -53,7 +53,7 @@ func TestWordsLongerThanTheBoundKeepTheirLastLines(t *testing.T) { // 1901/AC1 if !strings.HasSuffix(v.Failed.Words, "the cause is here") || len(v.Failed.Words) > MaxWords+200 { t.Errorf("%d bytes, ends %q", len(v.Failed.Words), v.Failed.Words[len(v.Failed.Words)-30:]) } - _ = delivery.VerdictFail + _ = meshdeliverycheckerclient.VerdictFail } // An older build module sends no "cause": its answer is still accepted and the verdict's words are its output, as before @@ -85,7 +85,7 @@ func TestAnOlderBuildModulesFailureIsAcceptedWithItsWholeOutput(t *testing.T) { } { t.Run(c.name, func(t *testing.T) { v := levelFailure(&target{module: c.module}, "unit", Tested{By: c.by, Failed: []Failure{{Package: "modules/gitea", Test: "fails", Cause: c.cause, Output: c.output}}}) - if v.State != delivery.VerdictFail { + if v.State != meshdeliverycheckerclient.VerdictFail { t.Errorf("1901/AC8: the verdict is %q, want it failed", v.State) } line := firstFailed(*v) diff --git a/modules/mesh-delivery-checker/internal/check/gate_test.go b/modules/mesh-delivery-checker/internal/check/gate_test.go index 83381544..744f5510 100644 --- a/modules/mesh-delivery-checker/internal/check/gate_test.go +++ b/modules/mesh-delivery-checker/internal/check/gate_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" ) // What the gate reads of the checker (R4e, lead's interface with dev-r4e-gate): the verdict's heads are the gate's hash over @@ -14,7 +14,7 @@ import ( func owedState(t testing.TB, r *rig, repo string, number int, head string) string { t.Helper() - key, err := delivery.CheckerOwedKey(repo, number, head) + key, err := CheckerOwedKey(repo, number, head) if err != nil { t.Fatal(err) } @@ -22,7 +22,7 @@ func owedState(t testing.TB, r *rig, repo string, number int, head string) strin if !found { return "" } - o, err := delivery.DecodeCheckerOwed(raw) + o, err := DecodeCheckerOwed(raw) if err != nil { t.Fatal(err) } @@ -37,7 +37,7 @@ func TestTheVerdictsHeadsAreTheGatesHashOverLowercaseHeads(t *testing.T) { // ga cand := candidateOf(t, "feat/x", pullOf("Novox/Mesh-Delivery", 7, upper)) r.ch.Candidate(ctx, "feat%2Fx", cand) r.ch.Wait() - want, _ := delivery.HeadsHash([]delivery.Head{{Repository: repoA, Number: 7, Head: head1}}) + want, _ := meshdeliveryclient.HeadsHash([]meshdeliveryclient.Head{{Repository: repoA, Number: 7, Head: head1}}) if v := r.verdict(t, keyOf(t, "feat/x", cand)); v.Heads != want { t.Errorf("heads = %q, want the gate's hash of the lone pull request, a group of one, %q", v.Heads, want) } @@ -49,7 +49,7 @@ func TestAGroupsVerdictHeadsCoverEveryMember(t *testing.T) { // gate (b): hq's m cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2), pullOf(hqRepo, 3, head2)) r.ch.Candidate(ctx, "feat%2Fx", cand) r.ch.Wait() - want, _ := delivery.HeadsHash([]delivery.Head{{Repository: repoA, Number: 7, Head: head1}, {Repository: repoSDK, Number: 12, Head: head2}, + want, _ := meshdeliveryclient.HeadsHash([]meshdeliveryclient.Head{{Repository: repoA, Number: 7, Head: head1}, {Repository: repoSDK, Number: 12, Head: head2}, {Repository: hqRepo, Number: 3, Head: head2}}) if v := r.verdict(t, keyOf(t, "feat/x", cand)); v.Heads != want { t.Errorf("heads = %q, want %q", v.Heads, want) diff --git a/modules/mesh-delivery-checker/internal/check/graph_cycle.go b/modules/mesh-delivery-checker/internal/check/graph_cycle.go index 444f0b1e..411064ef 100644 --- a/modules/mesh-delivery-checker/internal/check/graph_cycle.go +++ b/modules/mesh-delivery-checker/internal/check/graph_cycle.go @@ -7,9 +7,9 @@ import ( "slices" "strings" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" "git.novox.be/novox/mesh-delivery/sdk/composer" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-delivery/sdk/graph" ) @@ -29,7 +29,7 @@ type ManifestReader interface { // refuseCycle fails the check for a cycle of the change's module graph, naming it; nil when the case touches no manifest, // when nothing reads manifests, or when there is none. A manifest that cannot be read fails the check, said, never passes it. -func (st *state) refuseCycle(ctx context.Context) *delivery.Verdict { +func (st *state) refuseCycle(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue { if st.j.Manifests == nil || st.j.Read == nil || st.c.Try != nil { return nil } diff --git a/modules/mesh-delivery-checker/internal/check/judge.go b/modules/mesh-delivery-checker/internal/check/judge.go index e119b636..bccd846f 100644 --- a/modules/mesh-delivery-checker/internal/check/judge.go +++ b/modules/mesh-delivery-checker/internal/check/judge.go @@ -10,9 +10,11 @@ import ( "strings" "time" + "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" - "git.novox.be/novox/mesh-sdk/go/buildstep" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // goModule is the build module the Go repositories are judged by. @@ -48,20 +50,20 @@ const ( ) // verdictPtr is a verdict of failure a step of the check ends with. -type verdictPtr = *delivery.Verdict +type verdictPtr = *meshdeliverychecker.VerdictsByGroupHeadsValue func failed(machine, words string) verdictPtr { return fail(machine, words) } // Asks puts a check's ask on its build seat's stream and waits for its step (buildrun.Asker; ADR 0525 §1, §6): the // checker chooses no node, starts no step and holds none; whichever build node pulls the ask runs the check. type Asks interface { - Put(ctx context.Context, a delivery.StepAsk) error - Wait(ctx context.Context, a delivery.StepAsk) (delivery.Step, error) + Put(ctx context.Context, a buildstep.StepAsk) error + Wait(ctx context.Context, a buildstep.StepAsk) (buildstep.Step, error) } // ToolchainEntries are the nodes' toolchain entries of a language (buildrun.ToolchainSet). type ToolchainEntries interface { - Entries(language string) []delivery.NodeToolchain + Entries(language string) []buildstep.NodeToolchain } // BuildModules says which build modules judge a repository: the Go one always, the Node one when the repository holds a @@ -71,7 +73,7 @@ type BuildModules interface { } // askLanguage is the build seat language of each build module the checker asks. -var askLanguage = map[string]string{buildrun.GoBuildModule: delivery.AskGo, buildrun.NodeBuildModule: delivery.AskNode} +var askLanguage = map[string]string{buildrun.GoBuildModule: buildstep.AskGo, buildrun.NodeBuildModule: buildstep.AskNode} // Examiner judges a case through the build seats (ADR 0484 clauses 3, 4, 10, 13; ADR 0525 §1, §3): for each pull request // and build module it puts one check ask whose job fetches the head merged onto its trunk head, then runs the unit level @@ -97,9 +99,9 @@ type Examiner struct { // indexer's modules (novox/hq ADR 0573 §2): a check builds against those, or against a member's head for a package // its group publishes (§4), whatever go.mod names. Nil names none, and go.mod's versions are built. Published interface { - LatestPackages() delivery.PackageVersions + LatestPackages() buildstep.PackageVersions } - // KnownPackages names the packages the indexer's packages state knows (its series keys, delivery.SeriesPackage), by the + // KnownPackages names the packages the indexer's packages state knows (its series keys, meshcatalog.PackagesByPackageSeriesValue), by the // name in its own ecosystem, each with the `/` it is built from: the names a manifest's `packages` is // judged against (manifests_judge.go, design 32). Nil, or PackageNames answering nil while the state is not read yet, // hands the judge no names, and it judges none; an empty map is a state read and empty, against which every name fails. @@ -126,7 +128,7 @@ type Examiner struct { ModuleDir(module string) (string, bool) } // Packages names, of a repository, the paths (repository-relative) its TypeScript package artifacts are built from, from - // the indexer's modules (delivery.Module.ArtifactPaths, lead decision L8); nil serves no group package. + // the indexer's modules (meshcatalog.ModulesByModuleValue.ArtifactPaths, lead decision L8); nil serves no group package. Packages interface { PackagePaths(repository string) []string } @@ -169,7 +171,7 @@ type Examiner struct { // CompositionReader makes a check's composition verdict; an error is only a case that cannot be composed at all. type CompositionReader interface { - Compose(ctx context.Context, c *Case) (delivery.CompositionVerdict, error) + Compose(ctx context.Context, c *Case) (meshwants.CompositionsByGroupHeadsValue, error) } func (j *Examiner) now() time.Time { @@ -185,6 +187,15 @@ func (j *Examiner) logf(format string, args ...any) { } } +// fetchBeside are the siblings as the fetch is asked to place them. +func fetchBeside(refs []BesideRef) []buildstep.FetchBeside { + out := make([]buildstep.FetchBeside, 0, len(refs)) + for _, r := range refs { + out = append(out, buildstep.FetchBeside{Repository: r.Repository, Commit: r.Commit}) + } + return out +} + // besideLines are the siblings as the digest step echoes them: ` `, sorted. func besideLines(refs []BesideRef) []string { out := make([]string, 0, len(refs)) @@ -203,10 +214,10 @@ type target struct { node string toolchain string // agreed is the toolchain the ask names, so a node off it declines the check (ADR 0584); nil when none is agreed. - agreed *delivery.ToolchainUsed + agreed *buildstep.ToolchainUsed fetched Fetched // fetchArgs are the arguments of the fetch its check ask runs. - fetchArgs map[string]any + fetchArgs buildstep.Fetch // priority are the Go packages (import paths) or the Node module directories whose test files the merge changes. changed []string // beside are the lines ` ` of the siblings the workspace was given, sorted: its tests may read them, @@ -279,7 +290,7 @@ type state struct { // Judge runs the case (it is the Judge of a Checker). A failure of the change, of a merge or of a step is a verdict; an error is only a case that cannot // be judged at all. func (j *Examiner) Judge(ctx context.Context, c *Case) (Outcome, error) { - st := &state{j: j, c: c, levels: map[string]*acc{delivery.LevelUnit: {}, delivery.LevelIntegration: {}}, nodes: map[string]bool{}, + st := &state{j: j, c: c, levels: map[string]*acc{buildstep.LevelUnit: {}, buildstep.LevelIntegration: {}}, nodes: map[string]bool{}, beside: map[string]string{}} // Every member and level runs to its end, and every manifest the change touches is judged by the indexer's judge (ADR // 0578 §2), also after a failure: the verdict names every failure, not only the first (the operator, 2026-11-02). @@ -288,12 +299,12 @@ func (j *Examiner) Judge(ctx context.Context, c *Case) (Outcome, error) { if v := settled(joinFailures(tests, st.judgeManifests(ctx), st.refuseCycle(ctx), st.judgeRenames(ctx), st.refuseUnparsed(ctx))); v != nil { return st.finish(ctx, *v), nil } - return st.finish(ctx, delivery.Verdict{State: delivery.VerdictPass}), nil + return st.finish(ctx, meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictPass}), nil } // finish is the outcome of the verdict with the state of the builds the candidate's modules carry and the assigner's // composition of it, read also when the tests failed, so a failed composition is named beside them. -func (st *state) finish(ctx context.Context, v delivery.Verdict) Outcome { +func (st *state) finish(ctx context.Context, v meshdeliverychecker.VerdictsByGroupHeadsValue) Outcome { out := st.outcome(v) st.composed(ctx, &out.Verdict) out.Builds = st.j.buildNotes(ctx, st.c) @@ -303,7 +314,7 @@ func (st *state) finish(ctx context.Context, v delivery.Verdict) Outcome { // composed makes the check's composition from the engines' answers into its verdict (ADR 0593 §7, §9): a failed // composition fails the check, naming the machine it failed at; a node that did not answer is a warning. A try is not // composed (it has no candidate); a composition that cannot be made leaves it not run, said. -func (st *state) composed(ctx context.Context, v *delivery.Verdict) { +func (st *state) composed(ctx context.Context, v *meshdeliverychecker.VerdictsByGroupHeadsValue) { if st.c.Composition != nil && st.c.Try == nil { // A carry whose candidate's fingerprints did not change keeps the composition it passed (ADR 0548 §5). kept := *st.c.Composition @@ -316,7 +327,7 @@ func (st *state) composed(ctx context.Context, v *delivery.Verdict) { } if len(st.unpreviewed) > 0 { // An engine would only say it cannot read a manifest the indexer will not have: the check does not ask it. - v.Composition = &delivery.Composition{State: delivery.CompositionNotRun, Words: "no preview of a merge of " + + v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionNotRun, Words: "no preview of a merge of " + strings.Join(st.unpreviewed, ", ") + " (its head does not merge or its fetch did not answer), so the change cannot be composed"} return } @@ -325,41 +336,41 @@ func (st *state) composed(ctx context.Context, v *delivery.Verdict) { if err != nil { // Said in the verdict, never left as a composition not yet run (review finding 4). st.j.logf("%s: the composition could not be made, so it is said as not run: %v", st.c.Key, err) - v.Composition = &delivery.Composition{State: delivery.CompositionNotRun, Words: "not made: " + err.Error()} + v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionNotRun, Words: "not made: " + err.Error()} return } comp.Warnings = append(comp.Warnings, words...) // ADR 0603 §10 withComposition(v, comp) - if v.Composition != nil && v.Composition.State != delivery.CompositionNotRun { - v.Composition.FPSet = st.c.FPSet // a carry keeps it while the fingerprints stay the same (ADR 0548 §5) + if v.Composition != nil && v.Composition.State != meshdeliverycheckerclient.CompositionNotRun { + v.Composition.Fpset = st.c.FPSet // a carry keeps it while the fingerprints stay the same (ADR 0548 §5) } } // outcome fills the verdict's levels, times, machines and composition from what ran. -func (st *state) outcome(v delivery.Verdict) Outcome { +func (st *state) outcome(v meshdeliverychecker.VerdictsByGroupHeadsValue) Outcome { v.Series = st.c.allSeries() // the series changes, as structures (A2j finding 2) - if v.State == delivery.VerdictPass || len(v.Levels) == 0 { - v.Levels = map[string]delivery.LevelResult{} + if v.State == meshdeliverycheckerclient.VerdictPass || len(v.Levels) == 0 { + v.Levels = map[string]meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue{} for level, a := range st.levels { - v.Levels[level] = delivery.LevelResult{Run: a.run, Kept: a.kept, Skipped: a.skipped} + v.Levels[level] = meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue{Run: int64(a.run), Kept: int64(a.kept), Skipped: int64(a.skipped)} } } ms := func(s float64) int64 { return int64(s * 1000) } - u, i := st.levels[delivery.LevelUnit], st.levels[delivery.LevelIntegration] - v.Took = delivery.Took{Clone: st.clone.Milliseconds(), Unit: ms(u.elapsed), Lint: ms(u.lint), IntegrationSetup: ms(i.setup), IntegrationRun: ms(i.elapsed)} + u, i := st.levels[buildstep.LevelUnit], st.levels[buildstep.LevelIntegration] + v.Took = &meshdeliverychecker.VerdictsByGroupHeadsValueTook{Clone: st.clone.Milliseconds(), Unit: ms(u.elapsed), Lint: ms(u.lint), IntegrationSetup: ms(i.setup), IntegrationRun: ms(i.elapsed)} for node := range st.nodes { v.Machines = append(v.Machines, node) } sort.Strings(v.Machines) - for _, level := range []string{delivery.LevelUnit, delivery.LevelIntegration} { + for _, level := range []string{buildstep.LevelUnit, buildstep.LevelIntegration} { if st.levels[level].over { v.OverBound = append(v.OverBound, level) } } // The composition is made from the engines' answers (ADR 0593 §7): until it is, a verdict says it was not run (ADR 0484 clause 6). - v.Composition = &delivery.Composition{State: delivery.CompositionNotRun} + v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionNotRun} var slow []string - for _, level := range []string{delivery.LevelUnit, delivery.LevelIntegration} { + for _, level := range []string{buildstep.LevelUnit, buildstep.LevelIntegration} { a := st.levels[level] if !a.over { continue @@ -402,7 +413,7 @@ func (st *state) stepOutOfStep(p Pull, files []string) { } // fail is a verdict of failure at a machine. -func fail(machine, words string) *delivery.Verdict { +func fail(machine, words string) *meshdeliverychecker.VerdictsByGroupHeadsValue { v := failure(machine, words) return &v } @@ -412,14 +423,14 @@ func (st *state) levelsOf() []string { if st.c.Try != nil && st.c.Try.Level != "" { return []string{st.c.Try.Level} // a try of one level runs that level alone } - return []string{delivery.LevelUnit, delivery.LevelIntegration} + return []string{buildstep.LevelUnit, buildstep.LevelIntegration} } // judgeAll puts one check ask per pull request and build module the repository needs, the mesh-sdk member first, and // judges each answer. A head that does not merge, a pin that is not allowed or a level that fails is a failure of that // member; every other member is still judged, and the verdict names every failure (the operator, 2026-11-02), unjoined // to its first lines (settled does that). Only a check whose context ended stops. -func (st *state) judgeAll(ctx context.Context) *delivery.Verdict { +func (st *state) judgeAll(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue { // The mesh-sdk member first: whether its merge changes the TypeScript package's paths decides whether the other members' // Node fetches take the group's package (ADR 0500 §5). pulls := slices.Clone(st.c.Pulls) @@ -437,12 +448,12 @@ func (st *state) judgeAll(ctx context.Context) *delivery.Verdict { if v := st.tagged(ctx); v != nil { return v } - var groupPackage map[string]any - var failures []*delivery.Verdict + var groupPackage *buildstep.FetchPackage + var failures []*meshdeliverychecker.VerdictsByGroupHeadsValue for _, p := range pulls { if ctx.Err() != nil { if len(failures) == 0 { // an ask that ended with the context said it already - failures = append(failures, fail(delivery.CheckerModule, ctx.Err().Error())) + failures = append(failures, fail(CheckerModule, ctx.Err().Error())) } break } @@ -450,16 +461,16 @@ func (st *state) judgeAll(ctx context.Context) *delivery.Verdict { modules := st.j.Modules.BuildModules(p.Repository) for _, bm := range modules { t := &target{pull: p, module: bm} - if tc, none := delivery.AgreedToolchain(st.j.Toolchains.Entries(buildrun.LanguageOf[bm])); none == "" { + if tc, none := buildstep.AgreedToolchain(st.j.Toolchains.Entries(buildrun.LanguageOf[bm])); none == "" { t.toolchain = tc.Version - t.agreed = &delivery.ToolchainUsed{Version: tc.Version, Recipe: tc.Recipe} + t.agreed = &buildstep.ToolchainUsed{Language: buildrun.LanguageOf[bm], Version: tc.Version, Recipe: tc.Recipe} } - args := map[string]any{"repository": p.Repository, "commit": p.Head, "ref": refOf(p.Number), "base": p.Trunk} + args := buildstep.Fetch{Repository: p.Repository, Commit: p.Head, Ref: refOf(p.Number), Base: p.Trunk} if bm == buildrun.NodeBuildModule { if np, ok := st.tags.nodePackageFor(p.Repository); ok { - args["package"] = map[string]any{"name": np.Name, "version": np.Version} // the group's tagged version (ADR 0607 §3) + args.Package = &buildstep.FetchPackage{Name: np.Name, Version: np.Version} // the group's tagged version (ADR 0607 §3) } else if groupPackage != nil && p.Repository != SDKRepository { - args["package"] = groupPackage + args.Package = groupPackage } } if bm == buildrun.GoBuildModule { @@ -469,7 +480,7 @@ func (st *state) judgeAll(ctx context.Context) *delivery.Verdict { continue } if len(beside) > 0 { - args["beside"] = beside + args.Beside = fetchBeside(beside) t.beside = besideLines(beside) besideAtTrunk(st.c, beside, st.beside) } @@ -497,7 +508,7 @@ func (st *state) judgeAll(ctx context.Context) *delivery.Verdict { // TypeScript version is heard, the form the running checker hands while the new build modules are delivered. Its // removal is a subtask of the milestone after this one's delivery (issue 1966). if p.Repository == SDKRepository && len(st.c.Pulls) > 1 && len(st.tags.node) == 0 && st.j.changesPackage(p, merged) { - groupPackage = map[string]any{"repository": p.Repository, "commit": p.Head, "ref": refOf(p.Number)} + groupPackage = &buildstep.FetchPackage{Repository: p.Repository, Commit: p.Head, Ref: refOf(p.Number)} } } return joinFailures(failures...) @@ -511,17 +522,17 @@ const DefaultDeclineBound = 30 * time.Minute // each level's digest, kept verdicts and tests. When the answer leaves packages unmet, // their tests needing what that node lacks (issue 890), the passes are kept and the levels from the first such one are // asked again of a node that has it (placed). -func (st *state) ask(ctx context.Context, t *target, merged *[]Fetched) *delivery.Verdict { +func (st *state) ask(ctx context.Context, t *target, merged *[]Fetched) *meshdeliverychecker.VerdictsByGroupHeadsValue { return st.askNeeding(ctx, t, merged, st.levelsOf(), nil, nil) } // askNeeding is ask with the levels to run, what the node must have and the packages it tests: none for the first ask // of a target; for the placed one the unmet packages' needs and those packages alone, so no package the first ask judged // is run or counted again (ADR 0552 §4). -func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, levels, needs, packages []string) *delivery.Verdict { +func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, levels, needs, packages []string) *meshdeliverychecker.VerdictsByGroupHeadsValue { p := t.pull placed := len(needs) > 0 - job := &delivery.CheckJob{Fetch: t.fetchArgs, Levels: levels, Kept: true, Packages: packages, Toolchain: t.agreed} + job := &buildstep.CheckJob{Fetch: t.fetchArgs, Levels: levels, Kept: true, Packages: packages, Toolchain: t.agreed} if st.j.Nodes != nil { names, err := st.j.Nodes.Names(ctx) if err == nil && len(names) == 0 { @@ -538,16 +549,16 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l if st.c.Try != nil && st.c.Try.Run != "" { job.Run, job.Kept = st.c.Try.Run, false // a try of some tests runs them alone: a partial run proves no package } - kind := delivery.AskCheck + kind := buildstep.AskCheck if st.c.Kind == KindTrunk { - kind = delivery.AskRecheck // a re-judge after a trunk move goes after every new head's check (ADR 0536 §4) + kind = buildstep.AskRecheck // a re-judge after a trunk move goes after every new head's check (ADR 0536 §4) } - a := delivery.StepAsk{ID: buildstep.NewID(), Kind: kind, Language: askLanguage[t.module], Asker: delivery.CheckerModule, + a := buildstep.StepAsk{ID: buildstep.NewID(), Kind: kind, Language: askLanguage[t.module], Asker: CheckerModule, At: st.j.now().UTC(), Check: job, Needs: needs} - if a.Language == delivery.AskGo { + if a.Language == buildstep.AskGo { a.Packages = st.packagesOf(p.Repository) } - var step delivery.Step + var step buildstep.Step var firstDecline time.Time for { what := fmt.Sprintf("the %s ask %s of %s#%d", kind, a.ID, p.Repository, p.Number) @@ -563,7 +574,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l if watch := st.c.Watch; watch != nil { id := a.ID watch(id, nil) // put: a try names it at once, not only when it ends (issue 1890) - waiting = buildrun.WithStepSeen(ctx, func(s delivery.Step) { watch(id, &s) }) + waiting = buildrun.WithStepSeen(ctx, func(s buildstep.Step) { watch(id, &s) }) } var err error step, err = st.j.Asks.Wait(waiting, a) @@ -573,7 +584,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l } return fail(t.module, fmt.Sprintf("the check of %s#%d: %v", p.Repository, p.Number, err)) } - d, declined := delivery.DeclinedOf(step) + d, declined := buildstep.DeclinedOf(step) if !declined { break } @@ -598,7 +609,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l st.j.logf("%s#%d: the ask %s was declined on %s, which lacks %s; asked again", p.Repository, p.Number, a.ID, step.Node, strings.Join(d.Lacks, ", ")) a.ID, a.At = buildstep.NewID(), now.UTC() } - var failures []*delivery.Verdict + var failures []*meshdeliverychecker.VerdictsByGroupHeadsValue // The fetch and the digests are what of the ask's time its levels' running did not take (Took.Clone). took := time.Duration(0) if !step.Started.IsZero() && step.Ended.After(step.Started) { @@ -607,15 +618,15 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l t.node = step.Node st.nodes[step.Node] = true st.c.Progress.Set(fmt.Sprintf("judge the check of %s#%d", p.Repository, p.Number), step.Node) - if step.State != delivery.StepPassed { + if step.State != buildstep.StepPassed { return fail(step.Node, fmt.Sprintf("the check of %s#%d %s: %s", p.Repository, p.Number, step.State, step.Reason)) } - var answer delivery.CheckAnswer + var answer buildstep.CheckAnswer if err := json.Unmarshal(step.Result, &answer); err != nil { return fail(step.Node, fmt.Sprintf("the check of %s#%d answered what is not a check: %s", p.Repository, p.Number, step.Result)) } - if err := json.Unmarshal(answer.Fetch, &t.fetched); err != nil || t.fetched.Workspace == "" { - return fail(step.Node, fmt.Sprintf("the fetch of %s#%d answered no workspace: %s", p.Repository, p.Number, answer.Fetch)) + if t.fetched = answer.Fetch; t.fetched.Workspace == "" { + return fail(step.Node, fmt.Sprintf("the fetch of %s#%d answered no workspace: %s", p.Repository, p.Number, step.Result)) } if len(t.fetched.Conflicts) > 0 { if st.c.Try == nil { @@ -650,7 +661,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l } } st.clone += max(0, took-time.Duration(ran*float64(time.Second))) - if unmet := delivery.UnmetNeeds(answer.Levels); len(unmet) > 0 && !placed { + if unmet := buildstep.UnmetNeeds(answer.Levels); len(unmet) > 0 && !placed { // Every level ran, its passes kept, and some packages were left to a node with what their tests need: the levels // from the first that left one are asked of such a node, which takes the kept passes and tests those (issue 890). var again, packages []string @@ -673,7 +684,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l } // unmetNames names the unmet packages and their needs. -func unmetNames(unmet []delivery.PackageNeeds) string { +func unmetNames(unmet []buildstep.PackageNeeds) string { var out []string for _, u := range unmet { out = append(out, u.Package+" (needs "+strings.Join(u.Needs, ", ")+")") @@ -682,7 +693,7 @@ func unmetNames(unmet []delivery.PackageNeeds) string { } // unmetOf names the unmet packages of every level. -func unmetOf(levels []delivery.LevelAnswer) string { +func unmetOf(levels []buildstep.LevelAnswer) string { var out []string for _, la := range levels { if len(la.Unmet) > 0 { @@ -696,7 +707,7 @@ func unmetOf(levels []delivery.LevelAnswer) string { // of the packages that passed, the kept ones taken are counted, and every test result is read and counted, also after // one that did not pass, so the verdict names every failed test of the level (issue 1921). It answers the level's // running time (its tests' and setup's). -func (st *state) level(ctx context.Context, t *target, la delivery.LevelAnswer) (*delivery.Verdict, float64) { +func (st *state) level(ctx context.Context, t *target, la buildstep.LevelAnswer) (*meshdeliverychecker.VerdictsByGroupHeadsValue, float64) { level := la.Level a := st.levels[level] if a == nil { @@ -930,18 +941,18 @@ func firstLine(s string) string { const MaxFailureLine = 300 // levelFailure turns a level that did not pass into a verdict: Failed names what failed first with its words whole, -// and Failures every failed test of the level's results with its first line, at most delivery.MaxFailures and an entry +// and Failures every failed test of the level's results with its first line, at most meshdeliverycheckerclient.MaxFailures and an entry // saying how many more (issue 1921). -func levelFailure(t *target, level string, results ...Tested) *delivery.Verdict { - v := delivery.Verdict{State: delivery.VerdictFail} - var all []delivery.FailedFirst +func levelFailure(t *target, level string, results ...Tested) *meshdeliverychecker.VerdictsByGroupHeadsValue { + v := meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail} + var all []FailedFirst for _, res := range results { if len(res.Failed) == 0 { why := res.Why if why == "" { why = "the " + level + " level did not pass" } - all = append(all, delivery.FailedFirst{Test: level + ": " + t.pull.Repository, Words: why}) + all = append(all, FailedFirst{Test: level + ": " + t.pull.Repository, Words: why}) continue } for _, f := range res.Failed { @@ -949,75 +960,76 @@ func levelFailure(t *target, level string, results ...Tested) *delivery.Verdict if t.module == buildrun.NodeBuildModule && f.Package == "" { name = res.Module } - all = append(all, delivery.FailedFirst{Test: level + ": " + name, Words: failureWordsAfter(f, hidWords(t.module, res.By, f))}) + all = append(all, FailedFirst{Test: level + ": " + name, Words: failureWordsAfter(f, hidWords(t.module, res.By, f))}) } } if len(all) == 0 { // no result: a level said failed with nothing to name - all = append(all, delivery.FailedFirst{Test: level + ": " + t.pull.Repository, Words: "the " + level + " level did not pass"}) + all = append(all, FailedFirst{Test: level + ": " + t.pull.Repository, Words: "the " + level + " level did not pass"}) } - v.Failed = all[0] - v.Failures = all // whole: settled cuts them to their first lines once every failure of the check is joined + first := all[0] + v.Failed = &first + v.Failures = failureItems(all) // whole: settled cuts them to their first lines once every failure of the check is joined return &v } // joinFailures is one failed verdict of every failure of vs, in the order given (a nil one passed), unsettled: Failed is // the first whole, and Failures every failure whole, or none when the one failure is no level's (a conflict, a refused // step: one line, as before issue 1921). Nil when none failed. -func joinFailures(vs ...*delivery.Verdict) *delivery.Verdict { - var out *delivery.Verdict - var all []delivery.FailedFirst +func joinFailures(vs ...*meshdeliverychecker.VerdictsByGroupHeadsValue) *meshdeliverychecker.VerdictsByGroupHeadsValue { + var out *meshdeliverychecker.VerdictsByGroupHeadsValue + var all []FailedFirst listed := false for _, v := range vs { if v == nil { continue } if out == nil { - out = &delivery.Verdict{State: delivery.VerdictFail, Failed: v.Failed} + out = &meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: v.Failed} } if len(v.Failures) > 0 { - all, listed = append(all, v.Failures...), true + all, listed = append(all, failedList(v.Failures)...), true continue } - all = append(all, v.Failed) + all = append(all, failedOf(*v)) } if out != nil && (listed || len(all) > 1) { - out.Failures = all + out.Failures = failureItems(all) } return out } // settled is a joined verdict as it is written: each failure of its list cut to its first line, at most -// delivery.MaxFailures and then how many more (failureLines). -func settled(v *delivery.Verdict) *delivery.Verdict { +// meshdeliverycheckerclient.MaxFailures and then how many more (failureLines). +func settled(v *meshdeliverychecker.VerdictsByGroupHeadsValue) *meshdeliverychecker.VerdictsByGroupHeadsValue { if v != nil && len(v.Failures) > 0 { - v.Failures = failureLines(v.Failures) + v.Failures = failureItems(failureLines(failedList(v.Failures))) } return v } // addFailure is a settled list of failures with one more at its end: the first failure is put first when the list is -// empty (a verdict of one failure keeps none), and past delivery.MaxFailures the last entry counts one more. -func addFailure(v *delivery.Verdict, f delivery.FailedFirst) { +// empty (a verdict of one failure keeps none), and past meshdeliverycheckerclient.MaxFailures the last entry counts one more. +func addFailure(v *meshdeliverychecker.VerdictsByGroupHeadsValue, f FailedFirst) { if len(v.Failures) == 0 { - v.Failures = failureLines([]delivery.FailedFirst{v.Failed}) + v.Failures = failureItems(failureLines([]FailedFirst{failedOf(*v)})) } - if n := len(v.Failures); n > delivery.MaxFailures { - v.Failures[n-1].Test = fmt.Sprintf("and %d more", failedCount(*v)-delivery.MaxFailures+1) + if n := len(v.Failures); n > meshdeliverycheckerclient.MaxFailures { + v.Failures[n-1].Test = fmt.Sprintf("and %d more", failedCount(*v)-meshdeliverycheckerclient.MaxFailures+1) return } - v.Failures = append(v.Failures, failureLines([]delivery.FailedFirst{f})...) + v.Failures = append(v.Failures, failureItems(failureLines([]FailedFirst{f}))...) } -// failureLines is every failure with the first line of its words, cut to MaxFailureLine, at most delivery.MaxFailures +// failureLines is every failure with the first line of its words, cut to MaxFailureLine, at most meshdeliverycheckerclient.MaxFailures // and then one entry saying how many more failed. -func failureLines(all []delivery.FailedFirst) []delivery.FailedFirst { - n := min(len(all), delivery.MaxFailures) - out := make([]delivery.FailedFirst, 0, n+1) +func failureLines(all []FailedFirst) []FailedFirst { + n := min(len(all), meshdeliverycheckerclient.MaxFailures) + out := make([]FailedFirst, 0, n+1) for _, f := range all[:n] { - out = append(out, delivery.FailedFirst{Test: f.Test, Machine: f.Machine, Words: clipLine(firstLine(f.Words), MaxFailureLine)}) + out = append(out, FailedFirst{Test: f.Test, Machine: f.Machine, Words: clipLine(firstLine(f.Words), MaxFailureLine)}) } if more := len(all) - n; more > 0 { - out = append(out, delivery.FailedFirst{Test: fmt.Sprintf("and %d more", more)}) + out = append(out, FailedFirst{Test: fmt.Sprintf("and %d more", more)}) } return out } diff --git a/modules/mesh-delivery-checker/internal/check/judged_test.go b/modules/mesh-delivery-checker/internal/check/judged_test.go index e915b390..40a8ebaf 100644 --- a/modules/mesh-delivery-checker/internal/check/judged_test.go +++ b/modules/mesh-delivery-checker/internal/check/judged_test.go @@ -5,7 +5,8 @@ import ( "strings" "testing" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // The judged_by answer of mesh-build-go's digest and test steps (ADR 0535 §5; the milestone file's interface for issue @@ -84,7 +85,7 @@ func TestAnInstalledModuleWithoutTheRunnerJudgesAsTodayAndTheLineSaysSo(t *testi b := candidateBench(t, false) cs := caseOf(t, pullOf(catalogueRepository, 9, head1)) out := b.judge(t, cs) - if out.Verdict.State != delivery.VerdictPass { + if out.Verdict.State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("verdict = %+v", out.Verdict) } // As today: the digests key kept verdicts, and the levels are asked without the runner the module ignored. @@ -112,7 +113,7 @@ func TestACandidateJudgedCheckTakesNoKeptVerdictAndWritesNone(t *testing.T) { // t.Fatal(err) } out := b.judge(t, caseOf(t, pullOf(catalogueRepository, 9, head1))) - if out.Verdict.State != delivery.VerdictPass { + if out.Verdict.State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("verdict = %+v", out.Verdict) } tests := b.steps.tool("test") @@ -124,7 +125,7 @@ func TestACandidateJudgedCheckTakesNoKeptVerdictAndWritesNone(t *testing.T) { // t.Errorf("the %v level was asked for %v, want it whole", c.args["level"], c.args["packages"]) } } - if u := out.Verdict.Levels[delivery.LevelUnit]; u.Kept != 0 { + if u := out.Verdict.Levels[buildstep.LevelUnit]; u.Kept != 0 { t.Errorf("unit = %+v, want nothing taken", u) } if keys, _ := b.kept.Keys(context.Background()); len(keys) != 1 || keys[0] != "unit."+digA { @@ -148,8 +149,8 @@ func TestTheLineNamesTheCandidateThatJudged(t *testing.T) { // 1898/AC6 } // A long failing line keeps the program it was judged by; only the lesser parts go. v := out.Verdict - v.State = delivery.VerdictFail - v.Failed = delivery.FailedFirst{Test: "unit: git.novox.be/novox/mesh-catalog/modules/mesh-build-go/cmd/mesh-build-go.TestADigestFoldsTheSiblings", Words: strings.Repeat("x", 60)} + v.State = meshdeliverycheckerclient.VerdictFail + v.Failed = &FailedFirst{Test: "unit: git.novox.be/novox/mesh-catalog/modules/mesh-build-go/cmd/mesh-build-go.TestADigestFoldsTheSiblings", Words: strings.Repeat("x", 60)} if l := Line(v, cs, p, out.Builds, out.Slowest, out.Judged); !strings.Contains(l, "judged by the candidate's") { t.Errorf("the long line %q dropped the program", l) } @@ -174,7 +175,7 @@ func TestACandidateThatDoesNotCompileFailsTheCheckWithTheCompilersWords(t *testi b.steps.handlers["build_go_digest"] = func(stepCall) (any, error) { return nil, errorString(refusal) } b.test = func(stepCall) (any, error) { return nil, errorString(refusal) } out := b.judge(t, caseOf(t, pullOf(catalogueRepository, 9, head1))) - if out.Verdict.State != delivery.VerdictFail || !strings.Contains(out.Verdict.Failed.Words, "undefined: x") { + if out.Verdict.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(out.Verdict.Failed.Words, "undefined: x") { t.Fatalf("verdict = %+v, want a failure naming the compile error", out.Verdict) } if n := len(b.steps.tool("test")); n != 1 { @@ -195,9 +196,9 @@ func TestTheStatusTheForgeShowsNamesTheProgramThatJudged(t *testing.T) { // 1898 r := newRig(t, j) r.ch.Candidate(ctx, "feat/x", candidateOf(t, "feat/x", pullOf(repoA, 7, head1))) r.ch.Wait() - key, _ := delivery.CheckerOwedKey(repoA, 7, head1) + key, _ := CheckerOwedKey(repoA, 7, head1) raw, _, _, _ := r.owed.Get(ctx, key) - o, err := delivery.DecodeCheckerOwed(raw) + o, err := DecodeCheckerOwed(raw) if err != nil { t.Fatal(err) } diff --git a/modules/mesh-delivery-checker/internal/check/kept.go b/modules/mesh-delivery-checker/internal/check/kept.go index 7f58dd50..5b581634 100644 --- a/modules/mesh-delivery-checker/internal/check/kept.go +++ b/modules/mesh-delivery-checker/internal/check/kept.go @@ -7,7 +7,8 @@ import ( "sort" "time" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" ) // Kept is the checker's bucket of kept verdicts (ADR 0444, ADR 0484 clause 13): a passing verdict per package and level, @@ -17,31 +18,31 @@ import ( type Kept struct{ Store KV } // Lookup is the kept verdict of a level under a digest, or none. -func (k Kept) Lookup(ctx context.Context, level, digest string) (delivery.Kept, bool, error) { - key, err := delivery.KeptKey(level, digest) +func (k Kept) Lookup(ctx context.Context, level, digest string) (meshdeliverychecker.KeptByLevelDigestValue, bool, error) { + key, err := meshdeliverycheckerclient.KeptKey(level, digest) if err != nil { - return delivery.Kept{}, false, err + return meshdeliverychecker.KeptByLevelDigestValue{}, false, err } value, _, found, err := k.Store.Get(ctx, key) if err != nil || !found { - return delivery.Kept{}, false, err + return meshdeliverychecker.KeptByLevelDigestValue{}, false, err } - v, err := delivery.DecodeKept(value) + v, err := meshdeliverycheckerclient.DecodeKept(value) if err != nil { - return delivery.Kept{}, false, fmt.Errorf("%s holds what is not a kept verdict: %w", key, err) + return meshdeliverychecker.KeptByLevelDigestValue{}, false, fmt.Errorf("%s holds what is not a kept verdict: %w", key, err) } return v, true, nil } // Keep writes v under level and digest by a create. It answers false, and writes nothing, when the key exists: the first // value stands. -func (k Kept) Keep(ctx context.Context, level, digest string, v delivery.Kept) (bool, error) { - key, err := delivery.KeptKey(level, digest) +func (k Kept) Keep(ctx context.Context, level, digest string, v meshdeliverychecker.KeptByLevelDigestValue) (bool, error) { + key, err := meshdeliverycheckerclient.KeptKey(level, digest) if err != nil { return false, err } v.Level = level - data, err := delivery.EncodeKept(v) + data, err := meshdeliverycheckerclient.EncodeKept(v) if err != nil { return false, err } @@ -77,8 +78,8 @@ func (k Kept) KeepPasses(ctx context.Context, repository, level, toolchain strin if failedIn(res, p.Package) { continue } - made, err := k.Keep(ctx, level, digest, delivery.Kept{State: delivery.VerdictPass, Repository: repository, Package: p.Package, - Level: level, Tests: p.Tests, Took: int64(p.Elapsed * 1000), Toolchain: toolchain, At: at}) + made, err := k.Keep(ctx, level, digest, meshdeliverychecker.KeptByLevelDigestValue{State: meshdeliverycheckerclient.VerdictPass, Repository: repository, Package: p.Package, + Level: level, Tests: int64(p.Tests), Took: int64(p.Elapsed * 1000), Toolchain: toolchain, At: at.UTC().Format(time.RFC3339)}) if err != nil { return kept, err } diff --git a/modules/mesh-delivery-checker/internal/check/kept_test.go b/modules/mesh-delivery-checker/internal/check/kept_test.go index 8fc9e788..64ba37c7 100644 --- a/modules/mesh-delivery-checker/internal/check/kept_test.go +++ b/modules/mesh-delivery-checker/internal/check/kept_test.go @@ -6,7 +6,9 @@ import ( "testing" "time" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) var at0 = time.Date(2026, 10, 21, 19, 0, 0, 0, time.UTC) @@ -16,23 +18,23 @@ const ( digB = "bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22bb22" ) -func keptValue(pkg string, tests int) delivery.Kept { - return delivery.Kept{State: delivery.VerdictPass, Repository: "novox/mesh-delivery", Package: pkg, Tests: tests, Took: 120, Toolchain: "go1.26.0", At: at0} +func keptValue(pkg string, tests int) meshdeliverychecker.KeptByLevelDigestValue { + return meshdeliverychecker.KeptByLevelDigestValue{State: meshdeliverycheckerclient.VerdictPass, Repository: "novox/mesh-delivery", Package: pkg, Tests: int64(tests), Took: 120, Toolchain: "go1.26.0", At: at0.UTC().Format(time.RFC3339)} } func TestAPassIsKeptByACreateUnderItsLevelAndDigest(t *testing.T) { // 1287/AC1 ctx, st := context.Background(), newMemKV() k := Kept{Store: st} - made, err := k.Keep(ctx, delivery.LevelUnit, digA, keptValue("m/a", 3)) + made, err := k.Keep(ctx, buildstep.LevelUnit, digA, keptValue("m/a", 3)) if err != nil || !made { t.Fatalf("Keep = %v, %v, want made", made, err) } - key, _ := delivery.KeptKey(delivery.LevelUnit, digA) + key, _ := meshdeliverycheckerclient.KeptKey(buildstep.LevelUnit, digA) if st.creates[key] != 1 { t.Fatalf("the key %s was created %d times, want once", key, st.creates[key]) } - got, found, err := k.Lookup(ctx, delivery.LevelUnit, digA) - if err != nil || !found || got.Package != "m/a" || got.Tests != 3 || got.Level != delivery.LevelUnit || got.Toolchain != "go1.26.0" { + got, found, err := k.Lookup(ctx, buildstep.LevelUnit, digA) + if err != nil || !found || got.Package != "m/a" || got.Tests != 3 || got.Level != buildstep.LevelUnit || got.Toolchain != "go1.26.0" { t.Fatalf("Lookup = %+v, %v, %v", got, found, err) } } @@ -40,14 +42,14 @@ func TestAPassIsKeptByACreateUnderItsLevelAndDigest(t *testing.T) { // 1287/AC1 func TestASecondKeepUnderTheSameDigestIsRefusedAndTheFirstStands(t *testing.T) { // 1287/AC2 ctx, st := context.Background(), newMemKV() k := Kept{Store: st} - if made, err := k.Keep(ctx, delivery.LevelUnit, digA, keptValue("m/a", 3)); err != nil || !made { + if made, err := k.Keep(ctx, buildstep.LevelUnit, digA, keptValue("m/a", 3)); err != nil || !made { t.Fatal(made, err) } - made, err := k.Keep(ctx, delivery.LevelUnit, digA, keptValue("m/other", 9)) + made, err := k.Keep(ctx, buildstep.LevelUnit, digA, keptValue("m/other", 9)) if err != nil || made { t.Fatalf("the second Keep = %v, %v, want refused without an error", made, err) } - got, _, _ := k.Lookup(ctx, delivery.LevelUnit, digA) + got, _, _ := k.Lookup(ctx, buildstep.LevelUnit, digA) if got.Package != "m/a" || got.Tests != 3 { t.Errorf("the first value did not stand: %+v", got) } @@ -103,14 +105,14 @@ func TestOnlyTheSkippingPackageOfAResultIsWithheld(t *testing.T) { // issue 890 func TestAKeptValueOfAnotherDigestIsNotRead(t *testing.T) { // 1287/AC7 ctx, st := context.Background(), newMemKV() k := Kept{Store: st} - if _, err := k.Keep(ctx, delivery.LevelUnit, digA, keptValue("m/a", 3)); err != nil { + if _, err := k.Keep(ctx, buildstep.LevelUnit, digA, keptValue("m/a", 3)); err != nil { t.Fatal(err) } // A toolchain change changes the digest, so the new digest finds nothing and the old key is not deleted. - if _, found, _ := k.Lookup(ctx, delivery.LevelUnit, digB); found { + if _, found, _ := k.Lookup(ctx, buildstep.LevelUnit, digB); found { t.Error("a digest that was not kept was found") } - if _, found, _ := k.Lookup(ctx, delivery.LevelIntegration, digA); found { + if _, found, _ := k.Lookup(ctx, buildstep.LevelIntegration, digA); found { t.Error("a kept unit verdict was found at the integration level") } if keys, _ := st.Keys(ctx); len(keys) != 1 { @@ -121,8 +123,8 @@ func TestAKeptValueOfAnotherDigestIsNotRead(t *testing.T) { // 1287/AC7 func TestAKeptVerdictThatIsNotAPassIsRefused(t *testing.T) { k := Kept{Store: newMemKV()} v := keptValue("m/a", 1) - v.State = delivery.VerdictFail - if _, err := k.Keep(context.Background(), delivery.LevelUnit, digA, v); err == nil { + v.State = meshdeliverycheckerclient.VerdictFail + if _, err := k.Keep(context.Background(), buildstep.LevelUnit, digA, v); err == nil { t.Error("a failure was kept") } } diff --git a/modules/mesh-delivery-checker/internal/check/line.go b/modules/mesh-delivery-checker/internal/check/line.go index dc81ac58..6167710a 100644 --- a/modules/mesh-delivery-checker/internal/check/line.go +++ b/modules/mesh-delivery-checker/internal/check/line.go @@ -6,7 +6,9 @@ import ( "strings" "unicode/utf8" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // MaxLine is the most characters a status's line has: the forge shows a status's description in a short space, and the full @@ -33,8 +35,8 @@ func short12(s string) string { } // firstFailed is what failed first, in words: a conflict, a refused step or a test, named before anything else. -func firstFailed(v delivery.Verdict) string { - f := v.Failed +func firstFailed(v meshdeliverychecker.VerdictsByGroupHeadsValue) string { + f := failedOf(v) switch { case f.Test != "": if w := firstWordsLine(f.Words); w != "" { @@ -51,7 +53,7 @@ func firstFailed(v delivery.Verdict) string { // failedLines is everything a verdict names as failed, one line each: every failed test with its first line (issue // 1921), or what failed first when the verdict names no list (a conflict, a refused step, an older verdict). -func failedLines(v delivery.Verdict) []string { +func failedLines(v meshdeliverychecker.VerdictsByGroupHeadsValue) []string { if len(v.Failures) == 0 { if f := firstFailed(v); f != "" { return []string{f} @@ -60,7 +62,7 @@ func failedLines(v delivery.Verdict) []string { } lines := make([]string, 0, len(v.Failures)) for _, f := range v.Failures { - lines = append(lines, firstFailed(delivery.Verdict{Failed: f})) + lines = append(lines, firstFailed(meshdeliverychecker.VerdictsByGroupHeadsValue{Failed: &FailedFirst{Machine: f.Machine, Test: f.Test, Words: f.Words}})) } return lines } @@ -68,7 +70,7 @@ func failedLines(v delivery.Verdict) []string { // failedHead is the head of a failed verdict's line: what failed first, and when more did, how many failed and "and N // more" after it (the operator, 2026-11-02), so a line cut to MaxLine still says that more failed. The words of the first // are cut to fit room characters when room is not negative; the count and "and N more" are never cut. -func failedHead(v delivery.Verdict, room int) string { +func failedHead(v meshdeliverychecker.VerdictsByGroupHeadsValue, room int) string { first := firstFailed(v) n := failedCount(v) if n <= 1 { @@ -93,13 +95,13 @@ func clipRunes(s string, n int) string { return string(r[:n-1]) + "…" } -// failedCount is how many failures a verdict names, the ones after delivery.MaxFailures counted too. -func failedCount(v delivery.Verdict) int { +// failedCount is how many failures a verdict names, the ones after meshdeliverycheckerclient.MaxFailures counted too. +func failedCount(v meshdeliverychecker.VerdictsByGroupHeadsValue) int { n := len(v.Failures) - if n > delivery.MaxFailures { + if n > meshdeliverycheckerclient.MaxFailures { var more int if _, err := fmt.Sscanf(v.Failures[n-1].Test, "and %d more", &more); err == nil { - return delivery.MaxFailures + more + return meshdeliverycheckerclient.MaxFailures + more } } return n @@ -150,7 +152,7 @@ func (p pieces) join(drop int) string { return joinSet(parts...) } -func levelWords(name string, r delivery.LevelResult) string { +func levelWords(name string, r meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue) string { s := fmt.Sprintf("%s %d run, %d kept", name, r.Run, r.Kept) if r.Skipped > 0 { s += fmt.Sprintf(", %d skipped", r.Skipped) @@ -160,14 +162,16 @@ func levelWords(name string, r delivery.LevelResult) string { // compositionWords says the composition: not yet run, or once the engines answered (ADR 0593 §7) passed, with the nodes // that gave no answer, or failed, with the time it took and the machines it composed (issue 1300). -func compositionWords(c *delivery.Composition, took int64) string { return composition(c, took, false) } +func compositionWords(c *meshdeliverychecker.VerdictsByGroupHeadsValueComposition, took int64) string { + return composition(c, took, false) +} // composition is compositionWords; short counts the machines and leaves the SDK commit to the report. -func composition(c *delivery.Composition, took int64, short bool) string { - if c != nil && c.State == delivery.CompositionNotRun && c.Words != "" { +func composition(c *meshdeliverychecker.VerdictsByGroupHeadsValueComposition, took int64, short bool) string { + if c != nil && c.State == meshdeliverycheckerclient.CompositionNotRun && c.Words != "" { return "composition not judged: " + c.Words } - if c == nil || c.State == delivery.CompositionNotRun { + if c == nil || c.State == meshdeliverycheckerclient.CompositionNotRun { return "composition not yet run" } in := "" @@ -186,14 +190,14 @@ func composition(c *delivery.Composition, took int64, short bool) string { } } warned := "" - if c.State == delivery.CompositionPass && c.Words != "" { + if c.State == meshdeliverycheckerclient.CompositionPass && c.Words != "" { warned = " (" + c.Words + ")" // a node that did not answer (ADR 0593 §9) } switch { - case c.State == delivery.CompositionPass && short: + case c.State == meshdeliverycheckerclient.CompositionPass && short: return "composition passed" + in + on + warned // what it composed with is in the report - case c.State == delivery.CompositionPass: - return "composition passed" + in + " with " + short12(c.SDK) + on + warned + case c.State == meshdeliverycheckerclient.CompositionPass: + return "composition passed" + in + " with " + short12(c.Sdk) + on + warned } return "composition failed" + in + on + ": " + c.Words } @@ -211,7 +215,7 @@ func buildWords(builds []BuildNote) string { // carriedWords say the commits a verdict was carried from, for the line of pull request p (ADR 0548 §8): its own trunk's // commit as twelve characters, a sibling's with its name before it; only those that moved, sorted. Empty when none did. -func carriedWords(from map[string]string, v delivery.Verdict, cs *Case, p Pull) string { +func carriedWords(from map[string]string, v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case, p Pull) string { var own string var others []string for r, old := range from { @@ -243,7 +247,7 @@ func carriedWords(from map[string]string, v delivery.Verdict, cs *Case, p Pull) } // parts makes the pieces of the line of a pull request of a case. -func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) pieces { +func parts(v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) pieces { var pc pieces pc.judged = judged[pullKey(p)] pc.series = seriesWords(cs, p) @@ -252,7 +256,7 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s } commits := short12(p.Head) + " on " + short12(p.Trunk) switch v.State { - case delivery.VerdictRunning: + case meshdeliverycheckerclient.VerdictRunning: pc.head = "checking " + commits if cs != nil && cs.CarriedFrom != nil { // Out of date and being carried to the new commit (ADR 0548 §6): pending, and saying so. @@ -261,7 +265,7 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s pc.carried = "from " + w } } - case delivery.VerdictPass: + case meshdeliverycheckerclient.VerdictPass: pc.head = "pass " + commits if w := carriedWords(v.CarriedFrom, v, cs, p); w != "" { pc.carried = "carried from " + w // the counts that follow say what the carry ran and kept @@ -271,16 +275,16 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s pc.commits = commits } var counts []string - for _, level := range []string{delivery.LevelUnit, delivery.LevelIntegration} { + for _, level := range []string{buildstep.LevelUnit, buildstep.LevelIntegration} { if r, ok := v.Levels[level]; ok { counts = append(counts, levelWords(level, r)) } } pc.counts = strings.Join(counts, " | ") var short []string - for _, level := range []string{delivery.LevelUnit, delivery.LevelIntegration} { + for _, level := range []string{buildstep.LevelUnit, buildstep.LevelIntegration} { if r, ok := v.Levels[level]; ok { - name := map[string]string{delivery.LevelUnit: "unit", delivery.LevelIntegration: "int"}[level] + name := map[string]string{buildstep.LevelUnit: "unit", buildstep.LevelIntegration: "int"}[level] w := fmt.Sprintf("%s %d run %d kept", name, r.Run, r.Kept) if r.Skipped > 0 { w += fmt.Sprintf(" %d skipped", r.Skipped) @@ -289,8 +293,8 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s } } pc.shortCounts = strings.Join(short, " | ") - if v.State != delivery.VerdictRunning { - t := v.Took + if v.State != meshdeliverycheckerclient.VerdictRunning { + t := tookOf(v) var times []string for _, x := range []struct { name string @@ -329,13 +333,13 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s } } pc.builds = buildWords(builds) - if v.State != delivery.VerdictRunning { - pc.composition = compositionWords(v.Composition, v.Took.Composition) - pc.shortComposition = composition(v.Composition, v.Took.Composition, true) + if v.State != meshdeliverycheckerclient.VerdictRunning { + pc.composition = compositionWords(v.Composition, tookOf(v).Composition) + pc.shortComposition = composition(v.Composition, tookOf(v).Composition, true) } if cs != nil && len(cs.Pulls) > 1 { pc.group = fmt.Sprintf("group %s of %d pull requests", cs.Group, len(cs.Pulls)) - if v.State != delivery.VerdictPass { + if v.State != meshdeliverycheckerclient.VerdictPass { pc.group = fmt.Sprintf("%s#%d in group %s", p.Repository, p.Number, cs.Group) } } @@ -343,7 +347,7 @@ func parts(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []s } // Report is the full line of a pull request's status, with no limit: what the read tools and the full report say. -func Report(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) string { +func Report(v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) string { return parts(v, cs, p, builds, slowest, judged).join(0) } @@ -355,9 +359,9 @@ func Report(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest [] // builds, then the group drop first, then the times are given in their compact form, then dropped, and the line is cut at last. // The program that judged is never dropped: a line naming it gives the counts in their compact form, then drops them and // what follows, and is cut in its first part rather than lose it. -func Line(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) string { +func Line(v meshdeliverychecker.VerdictsByGroupHeadsValue, cs *Case, p Pull, builds []BuildNote, slowest []string, judged map[string]string) string { pc := parts(v, cs, p, builds, slowest, judged) - if v.State == delivery.VerdictFail { + if v.State == meshdeliverycheckerclient.VerdictFail { // The head gives way first to the commits and what is never dropped: its count and "and N more" stay whole. kept := joinSet(pc.commits, pc.carried, pc.warning, pc.judged, pc.series) pc.head = failedHead(v, MaxLine-utf8.RuneCountInString(" | "+kept)) @@ -379,7 +383,7 @@ func Line(v delivery.Verdict, cs *Case, p Pull, builds []BuildNote, slowest []st return string(head[:room]) + "…" + rest } } - if v.State == delivery.VerdictFail { + if v.State == meshdeliverycheckerclient.VerdictFail { // A failure's head, sized above, and the commits fit: the counts and the composition are in the report. if s := joinSet(pc.head, pc.commits, pc.carried, pc.warning, pc.judged, pc.series); utf8.RuneCountInString(s) <= MaxLine { return s diff --git a/modules/mesh-delivery-checker/internal/check/line_test.go b/modules/mesh-delivery-checker/internal/check/line_test.go index 66cbbdb4..0ae89f10 100644 --- a/modules/mesh-delivery-checker/internal/check/line_test.go +++ b/modules/mesh-delivery-checker/internal/check/line_test.go @@ -6,19 +6,22 @@ import ( "testing" "unicode/utf8" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) -func lineCase(t testing.TB, pulls ...delivery.CandidatePull) (*Case, Pull) { +func lineCase(t testing.TB, pulls ...meshcatalog.CandidatesByGroupValuePullsItem) (*Case, Pull) { cs := caseOf(t, pulls...) return cs, cs.Pulls[0] } -func passVerdict() delivery.Verdict { - return delivery.Verdict{State: delivery.VerdictPass, - Levels: map[string]delivery.LevelResult{delivery.LevelUnit: {Run: 12, Kept: 301}, delivery.LevelIntegration: {Run: 3, Kept: 40}}, - Took: delivery.Took{Queue: 800, Clone: 4100, Unit: 400, IntegrationSetup: 3200, IntegrationRun: 12000}, - Composition: &delivery.Composition{State: delivery.CompositionNotRun}} +func passVerdict() meshdeliverychecker.VerdictsByGroupHeadsValue { + return meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictPass, + Levels: map[string]meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue{buildstep.LevelUnit: {Run: 12, Kept: 301}, buildstep.LevelIntegration: {Run: 3, Kept: 40}}, + Took: &meshdeliverychecker.VerdictsByGroupHeadsValueTook{Queue: 800, Clone: 4100, Unit: 400, IntegrationSetup: 3200, IntegrationRun: 12000}, + Composition: &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionNotRun}} } func TestAPassOpensWithTheVerdictAndSaysTheCommitsTheCountsTheTimesAndThatTheCompositionWasNotRun(t *testing.T) { // 1300/AC1 @@ -44,17 +47,17 @@ func TestAPassOpensWithTheVerdictAndSaysTheCommitsTheCountsTheTimesAndThatTheCom func TestAFailureNamesTheTestTheConflictOrTheRefusedStepFirst(t *testing.T) { // 1300/AC2 cs, p := lineCase(t, pullOf(repoA, 7, head1)) - long := delivery.FailedFirst{Test: "integration: " + strings.Repeat("pkg/", 50) + "TestSomething", Words: strings.Repeat("because ", 40)} + long := FailedFirst{Test: "integration: " + strings.Repeat("pkg/", 50) + "TestSomething", Words: strings.Repeat("because ", 40)} for name, c := range map[string]struct { - v delivery.Verdict + v meshdeliverychecker.VerdictsByGroupHeadsValue want string }{ - "test": {delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Test: "unit: m/a.TestX", Words: "want 4"}}, "failed: unit: m/a.TestX: want 4"}, - "conflict": {delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: "shanks", Words: "novox/mesh-delivery#7 does not merge into the trunk: files in conflict: go.mod"}}, "failed: shanks: novox/mesh-delivery#7 does not merge into the trunk: files in conflict: go.mod"}, - "refused": {delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Machine: "mesh-build-go", Words: "no go build node qualifies: builders disagree"}}, "failed: mesh-build-go: no go build node qualifies"}, + "test": {meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Test: "unit: m/a.TestX", Words: "want 4"}}, "failed: unit: m/a.TestX: want 4"}, + "conflict": {meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Machine: "shanks", Words: "novox/mesh-delivery#7 does not merge into the trunk: files in conflict: go.mod"}}, "failed: shanks: novox/mesh-delivery#7 does not merge into the trunk: files in conflict: go.mod"}, + "refused": {meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Machine: "mesh-build-go", Words: "no go build node qualifies: builders disagree"}}, "failed: mesh-build-go: no go build node qualifies"}, // ADR 0591: a first failure longer than the forge's limit is shortened; "failed (N)" and "and N-1 more" are never cut. - "many, the first too long": {delivery.Verdict{State: delivery.VerdictFail, Failed: long, Failures: []delivery.FailedFirst{long, {Machine: "g14", Words: "route-proxy is defined twice"}, - {Test: "unit: m/b.TestY", Words: "boom"}}}, "failed (3): integration: pkg/pkg/"}, + "many, the first too long": {meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &long, Failures: failureItems([]FailedFirst{long, {Machine: "g14", Words: "route-proxy is defined twice"}, + {Test: "unit: m/b.TestY", Words: "boom"}})}, "failed (3): integration: pkg/pkg/"}, } { line := Line(c.v, cs, p, nil, nil, nil) if more := len(c.v.Failures) - 1; more > 0 && (!strings.Contains(line, fmt.Sprintf("…; and %d more | 111111111111", more)) || utf8.RuneCountInString(line) > MaxLine) { @@ -79,7 +82,7 @@ func TestALevelOverItsBoundIsNamedWithItsSlowestTestsAndTheVerdictIsNotChanged(t func TestSkippedTestsAreSaidAndNeverAFlatPass(t *testing.T) { // 1300/AC6 cs, p := lineCase(t, pullOf(repoA, 7, head1)) v := passVerdict() - v.Levels[delivery.LevelUnit] = delivery.LevelResult{Run: 12, Kept: 301, Skipped: 2} + v.Levels[buildstep.LevelUnit] = meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue{Run: 12, Kept: 301, Skipped: 2} if line := Line(v, cs, p, nil, nil, nil); !strings.Contains(line, "unit 12 run, 301 kept, 2 skipped") { t.Errorf("line = %q", line) } @@ -108,7 +111,7 @@ func TestTheBuildsOfTheTouchedModulesAreSaidAndDroppedFirstWhenTheLineIsTooLong( t.Errorf("line %q is too long", line) } short := passVerdict() - short.Took = delivery.Took{} + short.Took = &meshdeliverychecker.VerdictsByGroupHeadsValueTook{} if l := Line(short, cs, p, builds[:1], nil, nil); !strings.Contains(l, "builds: avahi built") { t.Errorf("a short line keeps its builds: %q", l) } @@ -116,7 +119,7 @@ func TestTheBuildsOfTheTouchedModulesAreSaidAndDroppedFirstWhenTheLineIsTooLong( func TestALineIsNeverLongerThanTheLimit(t *testing.T) { // 1300/AC1 cs, p := lineCase(t, pullOf(repoA, 7, head1)) - v := delivery.Verdict{State: delivery.VerdictFail, Failed: delivery.FailedFirst{Test: "integration: " + strings.Repeat("pkg/", 50) + ".TestSomething", Words: strings.Repeat("because ", 40)}} + v := meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &FailedFirst{Test: "integration: " + strings.Repeat("pkg/", 50) + ".TestSomething", Words: strings.Repeat("because ", 40)}} if line := Line(v, cs, p, nil, nil, nil); utf8.RuneCountInString(line) > MaxLine || !strings.HasPrefix(line, "failed: integration: ") { t.Errorf("line = %q (%d)", line, utf8.RuneCountInString(line)) } @@ -124,7 +127,7 @@ func TestALineIsNeverLongerThanTheLimit(t *testing.T) { // 1300/AC1 func TestARunningCheckIsPending(t *testing.T) { cs, p := lineCase(t, pullOf(repoA, 7, head1)) - if line := Line(delivery.Verdict{State: delivery.VerdictRunning}, cs, p, nil, nil, nil); line != "checking 111111111111 on aaaaaaaaaaaa" { + if line := Line(meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictRunning}, cs, p, nil, nil, nil); line != "checking 111111111111 on aaaaaaaaaaaa" { t.Errorf("line = %q", line) } } @@ -132,7 +135,7 @@ func TestARunningCheckIsPending(t *testing.T) { func TestThePassedCompositionIsSaidOnlyWhenItWasRun(t *testing.T) { // ADR 0484 clause 6 cs, p := lineCase(t, pullOf(repoA, 7, head1)) v := passVerdict() - v.Composition = &delivery.Composition{State: delivery.CompositionPass, SDK: "afbe371003a570f088be4a394d83aa7a9458f7ab"} + v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionPass, Sdk: "afbe371003a570f088be4a394d83aa7a9458f7ab"} if r := Report(v, cs, p, nil, nil, nil); !strings.Contains(r, "composition passed with afbe371003a5") { t.Errorf("report = %q", r) } @@ -146,7 +149,7 @@ func TestTheCompositionsTimeAndMachinesAreSaidOnceTheAssignerReportsThem(t *test cs, p := lineCase(t, pullOf(repoA, 7, head1)) v := passVerdict() v.Took.Composition = 2400 - v.Composition = &delivery.Composition{State: delivery.CompositionPass, SDK: "afbe371003a570f088be4a394d83aa7a9458f7ab", Machines: []string{"g14", "ace", "novox", "shanks"}} + v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionPass, Sdk: "afbe371003a570f088be4a394d83aa7a9458f7ab", Machines: []string{"g14", "ace", "novox", "shanks"}} r := Report(v, cs, p, nil, nil, nil) for _, want := range []string{"composition passed in 2.4s with afbe371003a5 on 4 machines: ace, g14, novox, shanks"} { if !strings.Contains(r, want) { @@ -156,8 +159,8 @@ func TestTheCompositionsTimeAndMachinesAreSaidOnceTheAssignerReportsThem(t *test if l := Line(v, cs, p, nil, nil, nil); !strings.Contains(l, "composition passed in 2.4s") || !strings.Contains(l, "on 4 machines") || utf8.RuneCountInString(l) > MaxLine { t.Errorf("line = %q", l) } - v.State, v.Failed = delivery.VerdictFail, delivery.FailedFirst{Machine: "novox", Words: "composition refused"} - v.Composition = &delivery.Composition{State: delivery.CompositionFail, Words: "novox: two seats claim dns", Machines: []string{"novox"}} + v.State, v.Failed = meshdeliverycheckerclient.VerdictFail, &FailedFirst{Machine: "novox", Words: "composition refused"} + v.Composition = &meshdeliverychecker.VerdictsByGroupHeadsValueComposition{State: meshdeliverycheckerclient.CompositionFail, Words: "novox: two seats claim dns", Machines: []string{"novox"}} if r := Report(v, cs, p, nil, nil, nil); !strings.Contains(r, "composition failed in 2.4s on novox: novox: two seats claim dns") { t.Errorf("failed composition report = %q", r) } @@ -169,7 +172,7 @@ func TestTheLineSaysTheLintTime(t *testing.T) { test := b.test b.test = func(c stepCall) (any, error) { r, err := test(c) - if res, ok := r.(Tested); ok && res.Level == delivery.LevelUnit { + if res, ok := r.(Tested); ok && res.Level == buildstep.LevelUnit { res.Lint = 1.5 return res, err } diff --git a/modules/mesh-delivery-checker/internal/check/lostask.go b/modules/mesh-delivery-checker/internal/check/lostask.go index bc1db982..8eccda34 100644 --- a/modules/mesh-delivery-checker/internal/check/lostask.go +++ b/modules/mesh-delivery-checker/internal/check/lostask.go @@ -3,9 +3,10 @@ package check import ( "strings" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" - "git.novox.be/novox/mesh-sdk/go/buildstep" - "git.novox.be/novox/mesh-sdk/go/delivery" + sdkrun "git.novox.be/novox/mesh-delivery/sdk/buildrun" ) // **An ask the build seat gave up on ends its step** (novox/hq issue 734): mesh-sdk buildrun.LostAskWaiter hands the @@ -22,18 +23,18 @@ const LostAskTries = 3 // IsLostAsk says whether a verdict failed because its ask judged nothing: the build seat gave it up (buildrun.LostWords), or // the build module that ran its step was replaced under it, and its next process ended the step it left running -// (buildstep.RestartReason; a delivery of the build module, as on 2026-10-28). Neither is the change's failure: the check +// (sdkrun.RestartReason; a delivery of the build module, as on 2026-10-28). Neither is the change's failure: the check // is asked again, as for a given-up ask. -func IsLostAsk(v delivery.Verdict) bool { - return v.State == delivery.VerdictFail && (strings.Contains(v.Failed.Words, buildrun.LostWords) || - strings.Contains(v.Failed.Words, buildstep.RestartReason)) +func IsLostAsk(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool { + return v.State == meshdeliverycheckerclient.VerdictFail && (strings.Contains(failedOf(v).Words, buildrun.LostWords) || + strings.Contains(failedOf(v).Words, sdkrun.RestartReason)) } // IsNotTaken says whether a verdict failed because no build node took its ask within the bound (buildrun.NotTakenWords). -func IsNotTaken(v delivery.Verdict) bool { - return v.State == delivery.VerdictFail && strings.Contains(v.Failed.Words, buildrun.NotTakenWords) +func IsNotTaken(v meshdeliverychecker.VerdictsByGroupHeadsValue) bool { + return v.State == meshdeliverycheckerclient.VerdictFail && strings.Contains(failedOf(v).Words, buildrun.NotTakenWords) } // HearingBucket is the checker's state hearing: whether it hears DEAD_LETTERS (buildrun.Hearing under // buildrun.HearingKey), which checker_status says (dead_letters). -const HearingBucket = delivery.CheckerModule + "_" + buildrun.HearingState +const HearingBucket = CheckerModule + "_" + buildrun.HearingState diff --git a/modules/mesh-delivery-checker/internal/check/lostask_test.go b/modules/mesh-delivery-checker/internal/check/lostask_test.go index 3c547e46..4fb61cf9 100644 --- a/modules/mesh-delivery-checker/internal/check/lostask_test.go +++ b/modules/mesh-delivery-checker/internal/check/lostask_test.go @@ -8,9 +8,11 @@ import ( "testing" "time" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" - "git.novox.be/novox/mesh-sdk/go/buildstep" - "git.novox.be/novox/mesh-sdk/go/delivery" + sdkrun "git.novox.be/novox/mesh-delivery/sdk/buildrun" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // losingAsks is the build seat over a bus that gives up the first `lose` asks put (DEAD_LETTERS keeps them, as on @@ -18,7 +20,7 @@ import ( // gives up (an ask no node takes). type losingAsks struct { *buildrun.FakeAsks - answer func(delivery.StepAsk) delivery.Step + answer func(buildstep.StepAsk) buildstep.Step dead *buildrun.DeadLetters never bool @@ -47,7 +49,7 @@ func (l *losingAsks) PublishAsk(ctx context.Context, subject, id string, data [] } go l.dead.Heard(g) default: - a, err := delivery.DecodeStepAsk(data) + a, err := buildstep.DecodeStepAsk(data) if err != nil { return err } @@ -146,32 +148,33 @@ func TestAnAskGivenUpOrNeverTakenEndsItsTryAsErrorAndItsHeadIsAskedAnew(t *testi } // troubled is the build seat as a check meets it when its step's end does not reach its wait: the first `restarts` asks' -// steps are ended by the build module's next process (buildstep.RestartReason), and the first `deaf` asks' watches hear +// steps are ended by the build module's next process (sdkrun.RestartReason), and the first `deaf` asks' watches hear // nothing though their steps end (the lost check of 2026-10-28). Its steps state is read through AskSteps and Steps. type troubled struct { *buildrun.FakeAsks - answer func(delivery.StepAsk) delivery.Step + answer func(buildstep.StepAsk) buildstep.Step mu sync.Mutex restarts, deaf int deafIDs map[string]bool - steps map[string]delivery.Step + steps map[string]buildstep.Step } func (tr *troubled) PublishAsk(ctx context.Context, subject, id string, data []byte) error { if err := tr.FakeAsks.PublishAsk(ctx, subject, id, data); err != nil { return err } - a, err := delivery.DecodeStepAsk(data) + a, err := buildstep.DecodeStepAsk(data) if err != nil { return err } step := tr.answer(a) - step.What = buildstep.AskWhat(a) + read, _ := buildstep.AskAs[buildstep.Ask](a) + step.What = sdkrun.AskWhat(read) tr.mu.Lock() if tr.restarts > 0 { tr.restarts-- - step.State, step.Reason, step.Result = delivery.StepFailed, buildstep.RestartReason, nil + step.State, step.Reason, step.Result = buildstep.StepFailed, sdkrun.RestartReason, nil } deaf := tr.deaf > 0 if deaf { @@ -186,21 +189,21 @@ func (tr *troubled) PublishAsk(ctx context.Context, subject, id string, data []b return nil } -func (tr *troubled) WatchAsk(ctx context.Context, module, askID string) (<-chan delivery.Step, error) { +func (tr *troubled) WatchAsk(ctx context.Context, module, askID string) (<-chan buildstep.Step, error) { tr.mu.Lock() deaf := tr.deafIDs[askID] tr.mu.Unlock() if deaf { - return make(chan delivery.Step), nil // open, and silent for good + return make(chan buildstep.Step), nil // open, and silent for good } return tr.FakeAsks.WatchAsk(ctx, module, askID) } -func (tr *troubled) AskSteps(_ context.Context, _, askID string) ([]delivery.Step, error) { +func (tr *troubled) AskSteps(_ context.Context, _, askID string) ([]buildstep.Step, error) { tr.mu.Lock() defer tr.mu.Unlock() if s, ok := tr.steps[askID]; ok { - return []delivery.Step{s}, nil + return []buildstep.Step{s}, nil } return nil, nil } @@ -212,9 +215,9 @@ func (tr *troubled) Steps(running bool) StateRead { vals := map[string]json.RawMessage{} for id, s := range tr.steps { if running { - s.State, s.Ended, s.Started = delivery.StepRunning, time.Time{}, time.Now() + s.State, s.Ended, s.Started = buildstep.StepRunning, time.Time{}, time.Now() } - raw, _ := delivery.EncodeStep(s) + raw, _ := buildstep.EncodeStep(s) vals[s.Node+"."+id] = raw } return StateRead{Keys: func() ([]string, error) { @@ -262,13 +265,13 @@ func TestACheckWhoseAskJudgedNothingIsAskedAgainOrRead(t *testing.T) { state string asks int }{ - {name: "given up once", lose: 1, state: delivery.VerdictPass, asks: 2}, - {name: "given up every time", lose: LostAskTries, state: delivery.VerdictFail, asks: LostAskTries}, - {name: "its build module replaced under it", restarts: 1, state: delivery.VerdictPass, asks: 2}, - {name: "replaced under every ask", restarts: LostAskTries, state: delivery.VerdictFail, asks: LostAskTries}, - {name: "its watch deaf, a step of its build module ends", deaf: 1, how: "nudge", state: delivery.VerdictPass, asks: 1}, - {name: "its watch deaf, asked again by hand", deaf: 1, how: "hand", state: delivery.VerdictPass, asks: 2}, - {name: "its watch deaf, asked again by hand after its build module replaced it", deaf: 1, restarts: 1, how: "hand", state: delivery.VerdictPass, asks: 2}, + {name: "given up once", lose: 1, state: meshdeliverycheckerclient.VerdictPass, asks: 2}, + {name: "given up every time", lose: LostAskTries, state: meshdeliverycheckerclient.VerdictFail, asks: LostAskTries}, + {name: "its build module replaced under it", restarts: 1, state: meshdeliverycheckerclient.VerdictPass, asks: 2}, + {name: "replaced under every ask", restarts: LostAskTries, state: meshdeliverycheckerclient.VerdictFail, asks: LostAskTries}, + {name: "its watch deaf, a step of its build module ends", deaf: 1, how: "nudge", state: meshdeliverycheckerclient.VerdictPass, asks: 1}, + {name: "its watch deaf, asked again by hand", deaf: 1, how: "hand", state: meshdeliverycheckerclient.VerdictPass, asks: 2}, + {name: "its watch deaf, asked again by hand after its build module replaced it", deaf: 1, restarts: 1, how: "hand", state: meshdeliverycheckerclient.VerdictPass, asks: 2}, } { t.Run(row.name, func(t *testing.T) { b := newBench(t) @@ -280,7 +283,7 @@ func TestACheckWhoseAskJudgedNothingIsAskedAgainOrRead(t *testing.T) { asked = func() int { return len(l.Asks()) } } else { tr = &troubled{FakeAsks: &buildrun.FakeAsks{}, answer: b.answer, restarts: row.restarts, deaf: row.deaf, - deafIDs: map[string]bool{}, steps: map[string]delivery.Step{}} + deafIDs: map[string]bool{}, steps: map[string]buildstep.Step{}} heard = &buildrun.HeardEnds{Inner: tr, Read: tr} b.ex.Asks = buildrun.Asker{Publisher: tr, Waiter: heard, Bound: 5 * time.Second} asked = func() int { return len(tr.Asks()) } @@ -295,7 +298,7 @@ func TestACheckWhoseAskJudgedNothingIsAskedAgainOrRead(t *testing.T) { for deadline := time.Now().Add(10 * time.Second); heard.Waiting() == 0 && time.Now().Before(deadline); { time.Sleep(2 * time.Millisecond) } - if v := r.verdict(t, key); v.State != delivery.VerdictRunning { + if v := r.verdict(t, key); v.State != meshdeliverycheckerclient.VerdictRunning { t.Fatalf("a check whose step ended unheard = %s, want running until it is read", v.State) } } @@ -305,7 +308,7 @@ func TestACheckWhoseAskJudgedNothingIsAskedAgainOrRead(t *testing.T) { heard.Nudge(buildrun.GoBuildModule) case "hand": cands := newMemKV() - ckey, _ := delivery.CandidatesKey("feat/x") + ckey, _ := meshcatalogclient.CandidatesKey("feat/x") _, _ = cands.Create(ctx, ckey, cand) none := StateRead{Keys: func() ([]string, error) { return nil, nil }, Get: func(string) (json.RawMessage, error) { return nil, nil }} rd := Reader{CandidateState: kvRead(cands), VerdictState: kvRead(r.verdicts), AskState: kvRead(asksKept), @@ -336,7 +339,7 @@ func TestACheckWhoseAskJudgedNothingIsAskedAgainOrRead(t *testing.T) { if _, _, kept, _ := asksKept.Get(ctx, key); kept { t.Errorf("the asks of the ended check are still kept under %s", key) } - if row.state == delivery.VerdictFail && (!IsLostAsk(v) || !IsNodesFailure(v)) { + if row.state == meshdeliverycheckerclient.VerdictFail && (!IsLostAsk(v) || !IsNodesFailure(v)) { t.Errorf("a check that judged nothing every time = %q, want lost and judged again when the nodes change", v.Failed.Words) } if row.how == "hand" { diff --git a/modules/mesh-delivery-checker/internal/check/manifests_judge.go b/modules/mesh-delivery-checker/internal/check/manifests_judge.go index 41b6b21a..703c71b0 100644 --- a/modules/mesh-delivery-checker/internal/check/manifests_judge.go +++ b/modules/mesh-delivery-checker/internal/check/manifests_judge.go @@ -9,10 +9,11 @@ import ( "slices" "strings" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-delivery/sdk/composer" - "git.novox.be/novox/mesh-sdk/go/buildstep" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-delivery/sdk/packagename" ) @@ -20,7 +21,7 @@ import ( // A change's manifests are judged by the indexer's judge (novox/hq ADR 0578 §2): the checker asks the Go build seat to // build the judge's command (mesh-delivery's modules/mesh-delivery-indexer, cmd/mesh-judge) at mesh-delivery's trunk, or // at the group's mesh-delivery member head when that member changes the judge, and to run it in its sandbox over the -// catalogue as the change leaves it, exactly as the composer is run (delivery.ComposeJob, ADR 0482 §4). A manifest the +// catalogue as the change leaves it, exactly as the composer is run (buildstep.ComposeJob, ADR 0482 §4). A manifest the // change touches that the judge refuses fails the check, naming the manifest and each problem. The checker links no // judge of its own. @@ -106,7 +107,7 @@ func judgeRequest(trunk map[string]string, pulls []Pull) (req composer.Request, // judgeManifests runs the indexer's judge over the case's manifests and fails the check for each touched manifest it // refuses. A case that touches no manifest, or an examiner that reads no trunk manifests, judges nothing; a judge that // cannot be asked or does not answer fails the check, said, never passes it. -func (st *state) judgeManifests(ctx context.Context) *delivery.Verdict { +func (st *state) judgeManifests(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue { if st.j.Manifests == nil || st.c.Try != nil { return nil } @@ -195,14 +196,14 @@ func (st *state) judgeManifests(ctx context.Context) *delivery.Verdict { // runJudge asks the Go build seat to build the judge at the commit and run it over the request, and reads its change and // trunk cases; a failed verdict when it cannot be asked or does not answer. -func (st *state) runJudge(ctx context.Context, req composer.Request, touched []string, commit, at string) (change, trunk composer.CaseVerdict, v *delivery.Verdict) { +func (st *state) runJudge(ctx context.Context, req composer.Request, touched []string, commit, at string) (change, trunk composer.CaseVerdict, v *meshdeliverychecker.VerdictsByGroupHeadsValue) { raw, err := json.Marshal(req) if err != nil { return change, trunk, fail(goModule, err.Error()) } - job := delivery.ComposeJob{Fetch: map[string]any{"repository": DeliveryRepository, "commit": commit}, Dir: JudgeDir, + job := buildstep.ComposeJob{Fetch: buildstep.Fetch{Repository: DeliveryRepository, Commit: commit}, Dir: JudgeDir, Package: JudgePackage, Request: raw, Reader: "the indexer's judge at " + at} - a := delivery.StepAsk{ID: buildstep.NewID(), Kind: delivery.AskCheck, Language: delivery.AskGo, Asker: delivery.CheckerModule, + a := buildstep.StepAsk{ID: buildstep.NewID(), Kind: buildstep.AskCheck, Language: buildstep.AskGo, Asker: CheckerModule, At: st.j.now().UTC(), Compose: &job} st.c.Progress.Set(fmt.Sprintf("the judge ask %s of the manifests of %s", a.ID, strings.Join(touched, ", ")), "") st.c.Asked(a.ID) @@ -231,7 +232,7 @@ const CatalogSeat = "mesh-catalog" // as well. The verdicts read are the trunk's manifest validator's: its change case, where refused, held (not accepted // with no reason) or with a field set aside (a field the running manifest validator does not know waits, ADR 0601 §3) // each fail it, naming the module and the problems. -func (st *state) refuseUnmovableIndexer(ctx context.Context, req composer.Request, touched []string, cv composer.CaseVerdict, judgedAt string) *delivery.Verdict { +func (st *state) refuseUnmovableIndexer(ctx context.Context, req composer.Request, touched []string, cv composer.CaseVerdict, judgedAt string) *meshdeliverychecker.VerdictsByGroupHeadsValue { if st.j.Read == nil { return nil } @@ -258,7 +259,7 @@ func (st *state) refuseUnmovableIndexer(ctx context.Context, req composer.Reques if trunk == "" { return fail(goModule, "the new indexer cannot be judged by the trunk's manifest validator: the trunk head of "+DeliveryRepository+" is not known") } - var v *delivery.Verdict + var v *meshdeliverychecker.VerdictsByGroupHeadsValue judgedAt = "mesh-delivery's trunk " + trunk if cv, _, v = st.runJudge(ctx, req, touched, trunk, judgedAt); v != nil { return v @@ -285,11 +286,11 @@ func (st *state) refuseUnmovableIndexer(ctx context.Context, req composer.Reques } // judgeAnswer reads the judge's change and trunk cases from its step, or why it cannot. -func judgeAnswer(step delivery.Step) (change, trunk composer.CaseVerdict, why string) { - if step.State != delivery.StepPassed { +func judgeAnswer(step buildstep.Step) (change, trunk composer.CaseVerdict, why string) { + if step.State != buildstep.StepPassed { return change, trunk, fmt.Sprintf("its step %s: %s", step.State, step.Reason) } - var a delivery.ComposeAnswer + var a buildstep.ComposeAnswer if err := json.Unmarshal(step.Result, &a); err != nil { return change, trunk, "its step's answer cannot be read: " + err.Error() } @@ -400,7 +401,7 @@ func (st *state) knownPackages(ctx context.Context) []string { } dirs := []string{d.dir} if d.dir == "" { - dirs = []string{"modules/" + d.module, delivery.RootPath} + dirs = []string{"modules/" + d.module, meshcatalogclient.RootPath} } var why []string for _, dir := range dirs { diff --git a/modules/mesh-delivery-checker/internal/check/names.go b/modules/mesh-delivery-checker/internal/check/names.go new file mode 100644 index 00000000..fbc015d7 --- /dev/null +++ b/modules/mesh-delivery-checker/internal/check/names.go @@ -0,0 +1,90 @@ +package check + +import ( + "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshwatchdogs" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" + checker "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" +) + +// The holders and buckets the checker names. No contract names a holder (novox/hq ADR 0625 §4, ADR 0634 §1), so these are its +// own constants, each a state of a seat (the seat's state name beside the holder's module name) and each declared in its +// manifest (module.json: its own states, its `reads`); a unit row checks them against the manifest (TestTheNamesAreTheManifests). +const ( + // CheckerModule is this part's module; its states verdicts, kept, owed and previews are the seat mesh-delivery-checker's. + CheckerModule = "mesh-delivery-checker" + CheckerVerdictsBucket = CheckerModule + "_" + checker.StateVerdicts + CheckerKeptBucket = CheckerModule + "_" + checker.StateKept + CheckerOwedBucket = CheckerModule + "_" + checker.StateOwed + PreviewsBucket = CheckerModule + "_" + checker.StatePreviews + + // GateModule holds the seat mesh-delivery's states source and turns, which the checker reads (reads: mesh-delivery-gate.source, + // mesh-delivery-gate.turns). + GateModule = "mesh-delivery-gate" + SourceBucket = GateModule + "_" + meshdelivery.StateSource + TurnsBucket = GateModule + "_" + meshdelivery.StateTurns + + // IndexerModule holds the seat mesh-catalog's states, which the checker follows by their views (consumes: + // mesh-delivery-indexer.views.*). + IndexerModule = meshcatalogclient.Module + CandidatesBucket = IndexerModule + "_" + meshcatalog.StateCandidates + ModulesBucket = IndexerModule + "_" + meshcatalog.StateModules + ManifestsBucket = IndexerModule + "_" + meshcatalog.StateManifests + IndexerPredefinedState = meshcatalog.StatePredefined + + // WatchmanModule holds the seat mesh-watchdogs' presence (reads: mesh-delivery-watchman.presence). + WatchmanModule = "mesh-delivery-watchman" + PresenceBucket = WatchmanModule + "_" + meshwatchdogs.StatePresence + + // HaveBucket is the node-engine's state have (reads: node-engine.have); a node's own key ends in HaveNodeWord. + HaveBucket = "node-engine_" + nodeengine.StateHave + HaveNodeWord = "node" + + // compositionWaits is the state of the assigner's composition answer that waits for what it composes from. + compositionWaits = "waits" +) + +// FailedFirst names what failed: a test (its package and name) or a machine, with the words of the failure. It is the shape of +// the verdict's `failed` and of each entry of its `failures` (the seat's two types of it have the same fields), so the check +// judges with one and converts at the verdict's edge (failureItems, failedList). +type FailedFirst = checker.VerdictsByGroupHeadsValueFailed + +// failedOf is the first failure a verdict names: the zero value of a verdict that names none. +func failedOf(v checker.VerdictsByGroupHeadsValue) FailedFirst { + if v.Failed == nil { + return FailedFirst{} + } + return *v.Failed +} + +// tookOf is the time of each part of a check a verdict names: the zero value of a verdict that names none. +func tookOf(v checker.VerdictsByGroupHeadsValue) checker.VerdictsByGroupHeadsValueTook { + if v.Took == nil { + return checker.VerdictsByGroupHeadsValueTook{} + } + return *v.Took +} + +func failureItems(list []FailedFirst) []checker.VerdictsByGroupHeadsValueFailuresItem { + if list == nil { + return nil + } + out := make([]checker.VerdictsByGroupHeadsValueFailuresItem, len(list)) + for i, f := range list { + out[i] = checker.VerdictsByGroupHeadsValueFailuresItem(f) + } + return out +} + +func failedList(items []checker.VerdictsByGroupHeadsValueFailuresItem) []FailedFirst { + if items == nil { + return nil + } + out := make([]FailedFirst, len(items)) + for i, f := range items { + out[i] = FailedFirst(f) + } + return out +} diff --git a/modules/mesh-delivery-checker/internal/check/owed.go b/modules/mesh-delivery-checker/internal/check/owed.go new file mode 100644 index 00000000..6849e26f --- /dev/null +++ b/modules/mesh-delivery-checker/internal/check/owed.go @@ -0,0 +1,49 @@ +package check + +import ( + "fmt" + + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" +) + +// The checker's owed state holds the one act it owes, the status mesh/check on a pull request at a head: the gate's owed act +// with the context mesh/check. Its value has the shape of the gate's (the seat mesh-delivery's owed), so its rules and its +// codec are the mesh-delivery client's; only the checker reads it, so the checker's own narrowing to its one act lives here. + +// CheckerOwedAct is the act the checker owes. +const CheckerOwedAct = meshdeliveryclient.OwedStatus + "=" + meshdeliveryclient.StatusContextCheck + +// CheckerOwedKey is the key, in the checker's owed state, of the status the checker owes on a pull request at a head. +func CheckerOwedKey(repository string, number int, head string) (string, error) { + return meshdeliveryclient.OwedKey(repository, number, head, CheckerOwedAct) +} + +func checkCheckerOwed(o meshdeliverychecker.OwedByRepositoryNumberHeadActValue) error { + if o.Act != meshdeliveryclient.OwedStatus || o.Context != meshdeliveryclient.StatusContextCheck { + return fmt.Errorf("the checker owes only a status with the context %s, not the act %q with the context %q", meshdeliveryclient.StatusContextCheck, o.Act, o.Context) + } + return nil +} + +// EncodeCheckerOwed is o as the gate's owed value, refused for any act but the checker's status. +func EncodeCheckerOwed(o meshdeliverychecker.OwedByRepositoryNumberHeadActValue) ([]byte, error) { + if err := checkCheckerOwed(o); err != nil { + return nil, err + } + return meshdeliveryclient.EncodeOwed(meshdelivery.OwedByRepositoryNumberHeadActValue(o)) +} + +// DecodeCheckerOwed reads an owed act of the checker's state. +func DecodeCheckerOwed(data []byte) (meshdeliverychecker.OwedByRepositoryNumberHeadActValue, error) { + g, err := meshdeliveryclient.DecodeOwed(data) + if err != nil { + return meshdeliverychecker.OwedByRepositoryNumberHeadActValue{}, err + } + o := meshdeliverychecker.OwedByRepositoryNumberHeadActValue(g) + if err := checkCheckerOwed(o); err != nil { + return meshdeliverychecker.OwedByRepositoryNumberHeadActValue{}, err + } + return o, nil +} diff --git a/modules/mesh-delivery-checker/internal/check/pins.go b/modules/mesh-delivery-checker/internal/check/pins.go index dd64c493..1ec0015d 100644 --- a/modules/mesh-delivery-checker/internal/check/pins.go +++ b/modules/mesh-delivery-checker/internal/check/pins.go @@ -5,11 +5,12 @@ import ( "errors" "maps" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" + "git.novox.be/novox/mesh-delivery/sdk/trunk" ) // **A check builds against the group's own package heads, not against what a member names** (novox/hq ADR 0573 §2, §4; -// issue 1585). Every Go check ask carries the version of each mesh package to build against (delivery.StepAsk.Packages): +// issue 1585). Every Go check ask carries the version of each mesh package to build against (buildstep.StepAsk.Packages): // the head of the group's member that publishes it, else its latest published version as the indexer holds it. The build // module writes them into the workspace, whatever version or commit a member's go.mod names, so no member is refused // for naming no version of the group's package, an older head of it, a merge commit or a commit on no pull request: that @@ -37,8 +38,8 @@ type Forge interface { // also a package new in the group, which has no published version yet), else that member's head (mesh-sdk's Go module // whenever mesh-sdk is a member). A package of repository itself comes with the tree the check fetched, and is left out. // Nil for none. -func (st *state) packagesOf(repository string) delivery.PackageVersions { - versions := delivery.PackageVersions{} +func (st *state) packagesOf(repository string) buildstep.PackageVersions { + versions := buildstep.PackageVersions{} if st.j.Published != nil { maps.Copy(versions, st.j.Published.LatestPackages()) } @@ -51,8 +52,8 @@ func (st *state) packagesOf(repository string) delivery.PackageVersions { versions[pkg] = "" } for pkg := range versions { - repo, _, ok := delivery.PackageRepository(pkg) - if !ok || delivery.SameRepository(repo, repository) { + repo, _, ok := trunk.PackageRepository(pkg) + if !ok || trunk.SameRepository(repo, repository) { delete(versions, pkg) continue } diff --git a/modules/mesh-delivery-checker/internal/check/pins_test.go b/modules/mesh-delivery-checker/internal/check/pins_test.go index 890eb6d8..cfaec3a0 100644 --- a/modules/mesh-delivery-checker/internal/check/pins_test.go +++ b/modules/mesh-delivery-checker/internal/check/pins_test.go @@ -11,21 +11,22 @@ import ( "testing" "time" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" - "git.novox.be/novox/mesh-sdk/go/buildstep" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // published is the latest published version of each mesh package, as the indexer's modules say (Examiner.Published). -type published delivery.PackageVersions +type published buildstep.PackageVersions -func (p published) LatestPackages() delivery.PackageVersions { - return maps.Clone(delivery.PackageVersions(p)) +func (p published) LatestPackages() buildstep.PackageVersions { + return maps.Clone(buildstep.PackageVersions(p)) } -// packagesOfFetch is the package versions the fetch of repository was handed (delivery.StepAsk.Packages, issue 1585). -func packagesOfFetch(t *testing.T, b *bench, repository string) delivery.PackageVersions { +// packagesOfFetch is the package versions the fetch of repository was handed (buildstep.StepAsk.Packages, issue 1585). +func packagesOfFetch(t *testing.T, b *bench, repository string) buildstep.PackageVersions { t.Helper() for _, f := range b.steps.tool("fetch") { if f.args["repository"] == repository { @@ -50,7 +51,7 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes b.fetch.Modules = []string{"modules/m"} b.ex.Published = published{SDKModulePath: trunkS, contract: trunkA} v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict - if v.State != delivery.VerdictPass { + if v.State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("verdict = %+v", v) } if got := packagesOfFetch(t, b, repoA); len(got) != 1 || got[SDKModulePath] != head2 { @@ -61,7 +62,7 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes } b = newBench(t) b.ex.Published = published{SDKModulePath: trunkS, contract: trunkA} - if v := b.judge(t, caseOf(t, pullOf("novox/mesh-catalog", 7, head1))).Verdict; v.State != delivery.VerdictPass { + if v := b.judge(t, caseOf(t, pullOf("novox/mesh-catalog", 7, head1))).Verdict; v.State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("a pull request alone: verdict = %+v", v) } if got := packagesOfFetch(t, b, "novox/mesh-catalog"); len(got) != 2 || got[SDKModulePath] != trunkS || got[contract] != trunkA { @@ -76,27 +77,27 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes {Name: "go", Kind: catalogue.ArtifactPackage, Language: "go", Paths: []string{"go"}}, {Name: "go-gopackageregistry", Kind: catalogue.ArtifactPackage, Language: "go", Paths: []string{"go/seats/gopackageregistry"}}}}} digest, contractsDigest := strings.Repeat("c", 64), strings.Repeat("d", 64) - sdkPull := func() delivery.CandidatePull { + sdkPull := func() meshcatalog.CandidatesByGroupValuePullsItem { p := pullOf(repoSDK, 12, head2) p.Modules = []string{"mesh-sdk"} - p.Fps = map[string]delivery.CandidateModule{"mesh-sdk": {FP2: "src2:" + strings.Repeat("f", 64), Manifest: digest}} + p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"mesh-sdk": {Fp2: "src2:" + strings.Repeat("f", 64), Manifest: digest}} return p } - unpreviewed := func() delivery.CandidatePull { // a package new in the group: no preview carries its manifest yet + unpreviewed := func() meshcatalog.CandidatesByGroupValuePullsItem { // a package new in the group: no preview carries its manifest yet p := sdkPull() - p.Fps = map[string]delivery.CandidateModule{"mesh-sdk": {FP2: "src2:" + strings.Repeat("f", 64)}} + p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"mesh-sdk": {Fp2: "src2:" + strings.Repeat("f", 64)}} return p } - contractsPull := func() delivery.CandidatePull { + contractsPull := func() meshcatalog.CandidatesByGroupValuePullsItem { p := pullOf("novox/mesh-catalog", 4, strings.Repeat("3", 40)) p.Modules = []string{"mesh-contracts"} - p.Fps = map[string]delivery.CandidateModule{"mesh-contracts": {FP2: "src2:" + strings.Repeat("e", 64), Manifest: contractsDigest}} + p.Fps = map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"mesh-contracts": {Fp2: "src2:" + strings.Repeat("e", 64), Manifest: contractsDigest}} return p } const sdkGo, contractsGo, seatGo = SDKModulePath, "git.novox.be/novox/mesh-catalog/go", "git.novox.be/novox/mesh-catalog/go/seats/gopackageregistry" for _, row := range []struct { name string - pulls []delivery.CandidatePull + pulls []meshcatalog.CandidatesByGroupValuePullsItem published []string // the seats that publish the members' heads wants int fails string @@ -107,19 +108,19 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes // noModule: the Go seat's published names no Go module path (ruling 1 on A2d's finding 1: never worked out). noModule bool }{ - {name: "a group", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), sdkPull()}, + {name: "a group", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 7, head1), sdkPull()}, published: []string{"go-package-registry", "npm-package-registry"}, wants: 2, built: map[string]string{sdkGo: "v0.1.15-feat-x.1"}, node: map[string]any{"name": "lib", "version": "0.1.15-feat-x.1"}}, - {name: "a package new in the group, read at its head", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), unpreviewed()}, + {name: "a package new in the group, read at its head", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 7, head1), unpreviewed()}, published: []string{"go-package-registry", "npm-package-registry"}, wants: 2, built: map[string]string{sdkGo: "v0.1.15-feat-x.1"}}, - {name: "one head, two Go packages (as mesh-contracts' head)", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), contractsPull()}, + {name: "one head, two Go packages (as mesh-contracts' head)", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 7, head1), contractsPull()}, published: []string{"go-package-registry"}, wants: 2, built: map[string]string{contractsGo: "v0.1.15-feat-x.1", seatGo: "v0.1.3-feat-x.1"}}, - {name: "a lone pull request", pulls: []delivery.CandidatePull{sdkPull()}}, - {name: "a Go published naming no module", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), sdkPull()}, + {name: "a lone pull request", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{sdkPull()}}, + {name: "a Go published naming no module", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 7, head1), sdkPull()}, published: []string{"go-package-registry", "npm-package-registry"}, wants: 2, noModule: true, fails: "no published of the tagged version of go of mesh-sdk (novox/mesh-sdk#12) on go-package-registry within 50ms"}, - {name: "a package never published", pulls: []delivery.CandidatePull{pullOf(repoA, 7, head1), sdkPull()}, + {name: "a package never published", pulls: []meshcatalog.CandidatesByGroupValuePullsItem{pullOf(repoA, 7, head1), sdkPull()}, published: []string{"go-package-registry"}, wants: 2, fails: "no published of the tagged version of lib of mesh-sdk (novox/mesh-sdk#12) on npm-package-registry within 50ms"}, } { @@ -131,7 +132,7 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes b.ex.Forge = headFiles{repoSDK + " module.json " + head2: string(raw)} tags := &recordedTags{heard: &PublishedHeard{}, publish: row.published, noModule: row.noModule} b.ex.Tags, b.ex.PublishBound = tags, 50*time.Millisecond - b.asks.Answer = func(a delivery.StepAsk) delivery.Step { + b.asks.Answer = func(a buildstep.StepAsk) buildstep.Step { if len(tags.wanted) != row.wants { t.Errorf("%s: a member was asked before every tag-wanted was said: %d of %d", row.name, len(tags.wanted), row.wants) } @@ -147,9 +148,9 @@ func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *tes } } switch { - case row.fails == "" && v.State != delivery.VerdictPass: + case row.fails == "" && v.State != meshdeliverycheckerclient.VerdictPass: t.Errorf("%s: verdict = %+v", row.name, v) - case row.fails != "" && (v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, row.fails)): + case row.fails != "" && (v.State != meshdeliverycheckerclient.VerdictFail || !strings.Contains(v.Failed.Words, row.fails)): t.Errorf("%s: verdict = %+v, want it to fail saying %q", row.name, v, row.fails) case row.fails != "" && len(b.steps.tool("fetch")) != 0: t.Errorf("%s: a member was built though a package was not published", row.name) @@ -247,7 +248,7 @@ func TestTheSiblingsAreWhereTheTestsStateThem(t *testing.T) { // review D2, ADR t.Errorf("the siblings named for the verdict = %v, want the node-engine at %s", out.Beside, hostRuns[:12]) } got := besideOf(t, b, SDKRepository) - if c := got[catalogue]; c.Commit != head2 || c.Ref != "refs/pull/12/head" { + if c := got[catalogue]; c.Commit != head2 { t.Errorf("the catalogue beside mesh-sdk in a group = %+v, want the member's head %s", c, head2) } if h := got[host]; h.Commit != hostRuns { @@ -258,7 +259,7 @@ func TestTheSiblingsAreWhereTheTestsStateThem(t *testing.T) { // review D2, ADR b = newBench(t) b.ex.Trunks = trunksOf(t, map[string]string{SDKRepository: trunkS, catalogue: trunkA, host: trunkA2, networking: trunkA2}) b.judge(t, caseOf(t, pullOf(host, 8, head1), pullOf(networking, 3, head2))) - if n := besideOf(t, b, host)[networking]; n.Commit != head2 || n.Ref != "refs/pull/3/head" { + if n := besideOf(t, b, host)[networking]; n.Commit != head2 { t.Errorf("mesh-networking beside the node-engine in a group = %+v, want the member's head %s", n, head2) } // Alone, mesh-sdk's tests read the catalogue's trunk, and the verdict names it. @@ -327,7 +328,7 @@ func TestACatalogueChangeHasTheCatalogueBesideItAtItsTrunk(t *testing.T) { func TestAMeshLabHeadGetsACheck(t *testing.T) { const lab = "novox/mesh-lab" raw := candidateOf(t, "fix/x", pullOf(lab, 3, head1)) - c, err := delivery.DecodeCandidate(raw) + c, err := meshcatalog.DecodeCandidatesByGroupValue(raw) if err != nil { t.Fatal(err) } @@ -336,7 +337,7 @@ func TestAMeshLabHeadGetsACheck(t *testing.T) { t.Fatalf("the case = %+v, want mesh-lab#3 judged", cs) } b := newBench(t) - if v := b.judge(t, cs).Verdict; v.State != delivery.VerdictPass { + if v := b.judge(t, cs).Verdict; v.State != meshdeliverycheckerclient.VerdictPass { t.Fatalf("verdict = %+v", v) } if fetched := b.steps.tool("fetch"); len(fetched) == 0 || fetched[0].args["repository"] != lab { diff --git a/modules/mesh-delivery-checker/internal/check/preview.go b/modules/mesh-delivery-checker/internal/check/preview.go index a3b185aa..c8897276 100644 --- a/modules/mesh-delivery-checker/internal/check/preview.go +++ b/modules/mesh-delivery-checker/internal/check/preview.go @@ -7,7 +7,8 @@ import ( "regexp" "slices" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" ) var digest64 = regexp.MustCompile(`^[0-9a-f]{64}$`) @@ -19,10 +20,10 @@ var digest64 = regexp.MustCompile(`^[0-9a-f]{64}$`) // indexer gives its module no fingerprint and says why. A head that does not merge has a preview of the files in conflict // alone, so the indexer says the candidate's modules have no fingerprint because it does not merge (issue 1445). It // answers false when no fetch was made. -func PreviewOf(p Pull, fetched []Fetched) (delivery.Preview, bool) { +func PreviewOf(p Pull, fetched []Fetched) (meshdeliverychecker.PreviewsByRepositoryHeadTrunkValue, bool) { var merge string var conflicts []string - mods := map[string]delivery.PreviewModule{} + mods := map[string]meshdeliverychecker.PreviewsByRepositoryHeadTrunkValueModulesValue{} for _, f := range fetched { if len(f.Conflicts) > 0 { conflicts = append(conflicts, f.Conflicts...) @@ -30,7 +31,7 @@ func PreviewOf(p Pull, fetched []Fetched) (delivery.Preview, bool) { } if len(conflicts) > 0 { slices.Sort(conflicts) - return delivery.Preview{Repository: p.Repository, Head: p.Head, Trunk: p.Trunk, Conflicts: slices.Compact(conflicts)}, true + return meshdeliverychecker.PreviewsByRepositoryHeadTrunkValue{Repository: p.Repository, Head: p.Head, Trunk: p.Trunk, Conflicts: slices.Compact(conflicts)}, true } for _, f := range fetched { if f.Merge == "" { @@ -43,13 +44,13 @@ func PreviewOf(p Pull, fetched []Fetched) (delivery.Preview, bool) { if t.Tree == "" || !digest64.MatchString(t.Manifest) { continue } - mods[dir] = delivery.PreviewModule{Tree: t.Tree, Manifest: t.Manifest, Locks: t.Locks, Paths: t.Paths, Packages: t.Packages} + mods[dir] = meshdeliverychecker.PreviewsByRepositoryHeadTrunkValueModulesValue{Tree: t.Tree, Manifest: t.Manifest, Locks: t.Locks, Paths: t.Paths, Packages: t.Packages} } } if merge == "" { - return delivery.Preview{}, false + return meshdeliverychecker.PreviewsByRepositoryHeadTrunkValue{}, false } - pv := delivery.Preview{Repository: p.Repository, Head: p.Head, Trunk: p.Trunk, Merge: merge} + pv := meshdeliverychecker.PreviewsByRepositoryHeadTrunkValue{Repository: p.Repository, Head: p.Head, Trunk: p.Trunk, Merge: merge} if len(mods) > 0 { pv.Modules = mods } @@ -67,10 +68,10 @@ func (j *Examiner) writePreview(ctx context.Context, p Pull, fetched []Fetched) if !ok { return } - key, err := delivery.PreviewKey(p.Repository, p.Head, p.Trunk) + key, err := meshdeliverycheckerclient.PreviewKey(p.Repository, p.Head, p.Trunk) if err == nil { var data []byte - if data, err = delivery.EncodePreview(pv); err == nil { + if data, err = meshdeliverycheckerclient.EncodePreview(pv); err == nil { _, err = j.Previews.Create(ctx, key, data) if errors.Is(err, ErrConflict) { return diff --git a/modules/mesh-delivery-checker/internal/check/rank_test.go b/modules/mesh-delivery-checker/internal/check/rank_test.go index e436dd47..14705018 100644 --- a/modules/mesh-delivery-checker/internal/check/rank_test.go +++ b/modules/mesh-delivery-checker/internal/check/rank_test.go @@ -6,7 +6,10 @@ import ( "sync" "testing" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" ) // The checker's ranked start of checks (ADR 0536 §4, issue 1897): a freed place goes to a head never judged (or asked again @@ -47,7 +50,7 @@ func TestCheckerStatusNamesTheWaitingChecksByKind(t *testing.T) { // 1897/AC4 cands := fakeState{} put := func(n int, head string) { cand := candidateOf(t, group(n), pullOf(repoA, n, head)) - ckey, _ := delivery.CandidatesKey(group(n)) + ckey, _ := meshcatalogclient.CandidatesKey(group(n)) cands[ckey] = cand r.ch.Candidate(ctx, group(n), cand) } @@ -58,7 +61,7 @@ func TestCheckerStatusNamesTheWaitingChecksByKind(t *testing.T) { // 1897/AC4 put(4, head1) // judged on the new trunk: stands r.ch.Wait() cand3 := candidateOf(t, group(3), pullOf(repoA, 3, head1)) // never judged - ckey3, _ := delivery.CandidatesKey(group(3)) + ckey3, _ := meshcatalogclient.CandidatesKey(group(3)) cands[ckey3] = cand3 verdicts := fakeState{} for k, v := range r.verdicts.vals { @@ -80,10 +83,10 @@ func TestCheckerStatusNamesTheWaitingChecksByKind(t *testing.T) { // 1897/AC4 // is still the one the mesh runs, though the node-engine's trunk has moved on (ADR 0548 §6). func TestCheckerStatusReadsTheNodeEngineAtItsRunningCommit(t *testing.T) { // 568/21 cand := candidateOf(t, group(1), pullOf(repoA, 1, head1)) - ckey, _ := delivery.CandidatesKey(group(1)) + ckey, _ := meshcatalogclient.CandidatesKey(group(1)) vkey := keyOf(t, group(1), cand) - dc, _ := delivery.DecodeCandidate(cand) - judged, err := delivery.EncodeVerdict(delivery.Verdict{Group: group(1), Heads: dc.HeadsHash, Trunk: map[string]string{repoA: trunkA}, Beside: map[string]string{nodeEngineRepo: "run111"}, State: delivery.VerdictPass, Levels: passing().Levels}) + dc, _ := meshcatalog.DecodeCandidatesByGroupValue(cand) + judged, err := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: group(1), Heads: dc.Heads, Trunk: map[string]string{repoA: trunkA}, Beside: map[string]string{nodeEngineRepo: "run111"}, State: meshdeliverycheckerclient.VerdictPass, Levels: passing().Levels}) if err != nil { t.Fatal(err) } diff --git a/modules/mesh-delivery-checker/internal/check/read.go b/modules/mesh-delivery-checker/internal/check/read.go index 1fc0efbe..6871c7ce 100644 --- a/modules/mesh-delivery-checker/internal/check/read.go +++ b/modules/mesh-delivery-checker/internal/check/read.go @@ -8,8 +8,13 @@ import ( "strings" "time" + "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // The read tools (novox/hq ADR 0484 clauses 8 and 13, issue 1686): what the checker stored, as stored, with the line of the @@ -68,28 +73,28 @@ type VerdictRead struct { Note string `json:"note,omitempty"` } -func (r Reader) candidate(group string) (delivery.Candidate, bool) { - key, err := delivery.CandidatesKey(group) +func (r Reader) candidate(group string) (meshcatalog.CandidatesByGroupValue, bool) { + key, err := meshcatalogclient.CandidatesKey(group) if err != nil || r.CandidateState.Get == nil { - return delivery.Candidate{}, false + return meshcatalog.CandidatesByGroupValue{}, false } raw, err := r.CandidateState.Get(key) if err != nil || len(raw) == 0 { - return delivery.Candidate{}, false + return meshcatalog.CandidatesByGroupValue{}, false } - c, err := delivery.DecodeCandidate(raw) + c, err := meshcatalog.DecodeCandidatesByGroupValue(raw) return c, err == nil } func (r Reader) read(key string, raw json.RawMessage) (VerdictRead, error) { out := VerdictRead{Key: key, Verdict: raw} - v, err := delivery.DecodeVerdict(raw) + v, err := meshdeliverycheckerclient.DecodeVerdict(raw) if err != nil { return out, fmt.Errorf("%s holds what is not a verdict: %w", key, err) } out.Failed = failedLines(v) c, ok := r.candidate(v.Group) - if !ok || (c.HeadsHash != v.Heads && GateHeads(c) != v.Heads) { + if !ok || (c.Heads != v.Heads && GateHeads(c) != v.Heads) { out.Note = "the candidate no longer holds these heads" return out, nil } @@ -98,7 +103,7 @@ func (r Reader) read(key string, raw json.RawMessage) (VerdictRead, error) { if cs == nil { if IsUntrunked(v) { for _, p := range leftOf(c, v.Trunk) { - out.Reports[pullKey(p)] = v.Failed.Words + out.Reports[pullKey(p)] = failedOf(v).Words } } return out, nil @@ -126,7 +131,7 @@ func (r Reader) Verdicts(group string) (out []VerdictRead, cut string, err error var matching []string for _, k := range keys { if group != "" { - if g, _, err := delivery.ParseCheckerVerdictKey(k); err != nil || g != group { + if g, _, err := meshdeliverycheckerclient.ParseVerdictKey(k); err != nil || g != group { continue } } @@ -169,14 +174,14 @@ func (r Reader) Pull(repository string, number int) (VerdictRead, error) { // The candidates are read in batches, each in parallel and taken in key order, so the read stops at the batch that holds it. for from := 0; from < len(keys); from += ReadWorkers * 4 { batch := keys[from:min(from+ReadWorkers*4, len(keys))] - cands := make([]delivery.Candidate, len(batch)) + cands := make([]meshcatalog.CandidatesByGroupValue, len(batch)) ok := make([]bool, len(batch)) _ = parallel(len(batch), func(i int) error { raw, err := r.CandidateState.Get(batch[i]) if err != nil || len(raw) == 0 { return nil } - c, err := delivery.DecodeCandidate(raw) + c, err := meshcatalog.DecodeCandidatesByGroupValue(raw) cands[i], ok[i] = c, err == nil return nil }) @@ -185,10 +190,10 @@ func (r Reader) Pull(repository string, number int) (VerdictRead, error) { continue } for _, p := range c.Pulls { - if p.Repository != repository || p.Number != number { + if p.Repository != repository || int(p.Number) != number { continue } - vkey, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash) + vkey, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads) if err != nil { return VerdictRead{}, err } @@ -215,7 +220,7 @@ func (r Reader) Kept(level, digest string) (map[string]json.RawMessage, string, return nil, "", fmt.Errorf("the kept state cannot be read here") } if digest != "" { - key, err := delivery.KeptKey(level, digest) + key, err := meshdeliverycheckerclient.KeptKey(level, digest) if err != nil { return nil, "", err } @@ -235,7 +240,7 @@ func (r Reader) Kept(level, digest string) (map[string]json.RawMessage, string, sort.Strings(keys) var matching []string for _, k := range keys { - if l, _, err := delivery.ParseKeptKey(k); err == nil && (level == "" || l == level) { + if l, _, err := meshdeliverycheckerclient.ParseKeptKey(k); err == nil && (level == "" || l == level) { matching = append(matching, k) } } @@ -280,7 +285,7 @@ func (r Reader) Previews(repository, head string) ([]PreviewRead, []string, stri parts := map[string]part{} var matching, bad []string for _, k := range keys { - repo, h, trunk, err := delivery.ParsePreviewKey(k) + repo, h, trunk, err := meshdeliverycheckerclient.ParsePreviewKey(k) if err != nil { bad = append(bad, k) continue @@ -398,7 +403,7 @@ func (r Reader) turns(st *Status) *Turns { } // heldBy says the turn that holds a candidate's check off, or "". -func heldBy(t *Turns, c delivery.Candidate, heads map[string]string) string { +func heldBy(t *Turns, c meshcatalog.CandidatesByGroupValue, heads map[string]string) string { cs := CaseOf(c, heads) if t == nil || cs == nil { return "" @@ -451,7 +456,7 @@ func (r Reader) Status() (Status, error) { st.Notes = append(st.Notes, fmt.Sprintf("read the first %d candidates of %d", MaxReads, total)) } type judged struct { - c delivery.Candidate + c meshcatalog.CandidatesByGroupValue raw json.RawMessage ok bool } @@ -461,11 +466,11 @@ func (r Reader) Status() (Status, error) { if err != nil || len(raw) == 0 { return nil } - c, err := delivery.DecodeCandidate(raw) + c, err := meshcatalog.DecodeCandidatesByGroupValue(raw) if err != nil { return nil } - vkey, err := delivery.CheckerVerdictKey(c.Group, c.HeadsHash) + vkey, err := meshdeliverycheckerclient.VerdictKey(c.Group, c.Heads) if err != nil { return nil } @@ -501,10 +506,10 @@ func (r Reader) Status() (Status, error) { } continue } - v, err := delivery.DecodeVerdict(raw) + v, err := meshdeliverycheckerclient.DecodeVerdict(raw) switch { case err != nil: - case v.State == delivery.VerdictRunning: + case v.State == meshdeliverycheckerclient.VerdictRunning: if why := read.lostWhy(c); why != "" { st.Lost[c.Group] = why } @@ -545,7 +550,7 @@ type stepsRead struct { type moduleStep struct { module string - step delivery.Step + step buildstep.Step } // readSteps reads the newest MaxReads keys of each build module's steps state. @@ -579,14 +584,14 @@ func (r Reader) readSteps() stepsRead { if cut != "" { out.notes = append(out.notes, "the steps of "+m+": read the newest "+fmt.Sprint(MaxReads)+" of its keys; a step older than those is not shown") } - steps := make([]delivery.Step, len(keys)) + steps := make([]buildstep.Step, len(keys)) decoded := make([]bool, len(keys)) _ = parallel(len(keys), func(i int) error { raw, err := sr.Get(keys[i]) if err != nil || len(raw) == 0 { return nil } - step, err := delivery.DecodeStep(raw) + step, err := buildstep.DecodeStep(raw) steps[i], decoded[i] = step, err == nil return nil }) @@ -596,7 +601,7 @@ func (r Reader) readSteps() stepsRead { } step := steps[i] out.all = append(out.all, moduleStep{module: m, step: step}) - if step.State != delivery.StepRunning { + if step.State != buildstep.StepRunning { continue } rs := RunningStep{Module: m, Node: step.Node, ID: step.ID, Tool: step.Tool, What: step.What} @@ -615,9 +620,9 @@ func (r Reader) readSteps() stepsRead { // asksOf says whether a step is an ask of one of the candidate's pull requests: its What names `@` // (buildstep.AskWhat). -func asksOf(c delivery.Candidate, what string) bool { +func asksOf(c meshcatalog.CandidatesByGroupValue, what string) bool { for _, p := range c.Pulls { - if p.Repository != "" && strings.Contains(what, p.Repository+"@"+short12(p.Head.Head)) { + if p.Repository != "" && strings.Contains(what, p.Repository+"@"+short12(p.Head)) { return true } } @@ -626,16 +631,16 @@ func asksOf(c delivery.Candidate, what string) bool { // lostWhy says why a candidate whose verdict is running has a check that nothing will judge, or "": no step of its pull // requests can still run, and its newest one ended. -func (s stepsRead) lostWhy(c delivery.Candidate) string { +func (s stepsRead) lostWhy(c meshcatalog.CandidatesByGroupValue) string { for _, rs := range s.running { if asksOf(c, rs.What) { return "" } } - var last *delivery.Step + var last *buildstep.Step for i := range s.all { st := s.all[i].step - if st.State != delivery.StepRunning && asksOf(c, st.What) && (last == nil || st.Ended.After(last.Ended)) { + if st.State != buildstep.StepRunning && asksOf(c, st.What) && (last == nil || st.Ended.After(last.Ended)) { last = &s.all[i].step } } @@ -653,9 +658,9 @@ const DefaultStepBound = 30 * time.Minute // leftWhy says why a key in the running state runs no step any more, or "" when it can still be running: its build module // is not on its node or is not running there (the node-engine's have), or it started longer ago than the step bound, when // the checker stopped waiting for it. -func (r Reader) leftWhy(module string, step delivery.Step, now time.Time, bound time.Duration) string { +func (r Reader) leftWhy(module string, step buildstep.Step, now time.Time, bound time.Duration) string { if r.HaveState.Get != nil { - key, err := delivery.HaveKey(step.Node, module) + key, err := nodeengineclient.HaveKey(step.Node, module) if err == nil { raw, err := r.HaveState.Get(key) switch { @@ -663,7 +668,7 @@ func (r Reader) leftWhy(module string, step delivery.Step, now time.Time, bound case len(raw) == 0: return fmt.Sprintf("%s is not on %s", module, step.Node) default: - if h, err := delivery.DecodeHave(raw); err == nil && h.Phase != delivery.PhaseRunning { + if h, err := nodeengineclient.DecodeHave(raw); err == nil && h.Phase != nodeengineclient.PhaseRunning { return fmt.Sprintf("%s on %s is %s", module, step.Node, h.Phase) } } @@ -680,17 +685,17 @@ func (r Reader) leftWhy(module string, step delivery.Step, now time.Time, bound // buildsNotEnded are the builder's builds of the candidate's changed modules that are neither done nor failed, as // Status.BuildsNotEnded says them; empty when none, or when the builder's builds are not readable here. -func (r Reader) buildsNotEnded(c delivery.Candidate) string { +func (r Reader) buildsNotEnded(c meshcatalog.CandidatesByGroupValue) string { if r.BuildState.Get == nil { return "" } var out []string for _, p := range c.Pulls { for module, cm := range p.Fps { - if cm.FP2 == "" || cm.Manifest == "" { + if cm.Fp2 == "" || cm.Manifest == "" { continue } - key, err := delivery.BuilderBuildKey(module, cm.FP2) + key, err := builds.BuilderBuildKey(module, cm.Fp2) if err != nil { continue } @@ -700,8 +705,8 @@ func (r Reader) buildsNotEnded(c delivery.Candidate) string { } state := "not begun" if len(raw) > 0 { - b, err := delivery.DecodeBuilderBuild(raw) - if err != nil || b.State == delivery.BuildDone || b.State == delivery.BuildFailed { + b, err := builds.DecodeBuilderBuild(raw) + if err != nil || b.State == builds.BuildDone || b.State == builds.BuildFailed { continue } state = b.State diff --git a/modules/mesh-delivery-checker/internal/check/read_test.go b/modules/mesh-delivery-checker/internal/check/read_test.go index 033ba635..ec037edb 100644 --- a/modules/mesh-delivery-checker/internal/check/read_test.go +++ b/modules/mesh-delivery-checker/internal/check/read_test.go @@ -9,7 +9,14 @@ import ( "testing" "time" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient" + "git.novox.be/novox/mesh-contracts/go/seats/nodeengine" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + buildsrec "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) type fakeState map[string][]byte @@ -40,7 +47,7 @@ func readerOf(t *testing.T) (Reader, string) { cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1), pullOf(hqRepo, 3, head2)) r.ch.Candidate(context.Background(), "feat%2Fx", cand) r.ch.Wait() - ckey, _ := delivery.CandidatesKey("feat/x") + ckey, _ := meshcatalogclient.CandidatesKey("feat/x") verdicts, kept := fakeState{}, fakeState{} for k, v := range r.verdicts.vals { verdicts[k] = v @@ -58,7 +65,7 @@ func TestTheVerdictToolsAnswerTheStoredVerdictWithTheFullReportOfEachPullRequest if err != nil || len(vs) != 1 || vs[0].Key != key { t.Fatalf("verdicts = %+v, %v", vs, err) } - if _, err := delivery.DecodeVerdict(vs[0].Verdict); err != nil { + if _, err := meshdeliverycheckerclient.DecodeVerdict(vs[0].Verdict); err != nil { t.Errorf("the verdict is not as stored: %v", err) } if got := vs[0].Reports["novox/mesh-delivery#7"]; !strings.HasPrefix(got, "pass 111111111111 on aaaaaaaaaaaa") || !strings.Contains(got, "composition not yet run") { @@ -68,9 +75,9 @@ func TestTheVerdictToolsAnswerTheStoredVerdictWithTheFullReportOfEachPullRequest t.Errorf("another group's verdicts = %+v", only) } // A failed verdict is answered with every failure it names, one line each (the operator, 2026-11-02). - raw, _ := delivery.EncodeVerdict(delivery.Verdict{Group: "feat/x", Heads: "0123456789ab", State: delivery.VerdictFail, - Failed: delivery.FailedFirst{Test: "unit: m/a.TestA", Words: "a_test.go:3: boom\nmore"}, - Failures: []delivery.FailedFirst{{Test: "unit: m/a.TestA", Words: "a_test.go:3: boom"}, {Machine: "g14", Words: "route-proxy is defined twice"}}}) + raw, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: "feat/x", Heads: "0123456789ab", State: meshdeliverycheckerclient.VerdictFail, + Failed: &FailedFirst{Test: "unit: m/a.TestA", Words: "a_test.go:3: boom\nmore"}, + Failures: failureItems([]FailedFirst{{Test: "unit: m/a.TestA", Words: "a_test.go:3: boom"}, {Machine: "g14", Words: "route-proxy is defined twice"}})}) if got, _ := rd.read(key, raw); strings.Join(got.Failed, " | ") != "unit: m/a.TestA: a_test.go:3: boom | g14: route-proxy is defined twice" { t.Errorf("failed = %q", got.Failed) } @@ -104,7 +111,7 @@ func TestTheKeptToolAnswersTheKeysAsStoredByLevelAndByDigest(t *testing.T) { // t.Fatalf("one kept = %v, %v", one, err) } for _, raw := range one { - if k, err := delivery.DecodeKept(raw); err != nil || k.Package != "m/a" { + if k, err := meshdeliverycheckerclient.DecodeKept(raw); err != nil || k.Package != "m/a" { t.Errorf("kept value = %+v, %v", k, err) } } @@ -118,12 +125,12 @@ func TestTheKeptToolAnswersTheKeysAsStoredByLevelAndByDigest(t *testing.T) { // // 1680 live acceptance: checker_previews shows the preview key of an open pull request, as stored, narrowed by repository and head. func TestThePreviewsToolShowsTheStoredPreviewOfAPullRequest(t *testing.T) { - pv := delivery.Preview{Repository: repoA, Head: head1, Trunk: head2, Merge: "m1", Modules: map[string]delivery.PreviewModule{}} - data, err := delivery.EncodePreview(pv) + pv := meshdeliverychecker.PreviewsByRepositoryHeadTrunkValue{Repository: repoA, Head: head1, Trunk: head2, Merge: "m1", Modules: map[string]meshdeliverychecker.PreviewsByRepositoryHeadTrunkValueModulesValue{}} + data, err := meshdeliverycheckerclient.EncodePreview(pv) if err != nil { t.Fatal(err) } - key, _ := delivery.PreviewKey(repoA, head1, head2) + key, _ := meshdeliverycheckerclient.PreviewKey(repoA, head1, head2) rd := Reader{PreviewState: fakeState{key: data, "junk": []byte("{}")}.read()} got, bad, _, err := rd.Previews(repoA, head1[:8]) if err != nil || len(got) != 1 || got[0].Key != key || got[0].Head != head1 || got[0].Trunk != head2 || string(got[0].Preview) != string(data) { @@ -143,29 +150,29 @@ func TestThePreviewsToolShowsTheStoredPreviewOfAPullRequest(t *testing.T) { // checker_status reads the steps in flight by node and the candidates without a verdict; since R4g nothing holds the checker // and it has no capacity of its own (ADR 0525 §4, §7; 1685 closed by the hold's deletion). func TestTheStatusToolReadsRunningSteps(t *testing.T) { - running, _ := delivery.EncodeStep(delivery.Step{ID: "abc1", Tool: "build_go_test", Node: "shanks", State: delivery.StepRunning, Started: time.Date(2026, 10, 22, 15, 0, 0, 0, time.UTC)}) - passed, _ := delivery.EncodeStep(delivery.Step{ID: "abc2", Tool: "build_go_test", Node: "ace", State: delivery.StepPassed}) + running, _ := buildstep.EncodeStep(buildstep.Step{ID: "abc1", Tool: "build_go_test", Node: "shanks", State: buildstep.StepRunning, Started: time.Date(2026, 10, 22, 15, 0, 0, 0, time.UTC)}) + passed, _ := buildstep.EncodeStep(buildstep.Step{ID: "abc2", Tool: "build_go_test", Node: "ace", State: buildstep.StepPassed}) cand := candidateOf(t, "feat/x", pullOf(repoA, 7, head1)) - ckey, _ := delivery.CandidatesKey("feat/x") + ckey, _ := meshcatalogclient.CandidatesKey("feat/x") // ADR 0603 §10: feat/y's check runs and waits on the builds of its changed modules: web building, mail not begun; api // is done and named nowhere. fp, man := "src2:"+strings.Repeat("f", 64), strings.Repeat("e", 64) py := pullOf(repoA, 8, head2) - py.Modules, py.Fps = []string{"api", "mail", "web"}, map[string]delivery.CandidateModule{"web": {FP2: fp, Manifest: man}, "mail": {FP2: fp, Manifest: man}, "api": {FP2: fp, Manifest: man}} + py.Modules, py.Fps = []string{"api", "mail", "web"}, map[string]meshcatalog.CandidatesByGroupValuePullsItemFpsValue{"web": {Fp2: fp, Manifest: man}, "mail": {Fp2: fp, Manifest: man}, "api": {Fp2: fp, Manifest: man}} candY := candidateOf(t, "feat/y", py) - ykey, _ := delivery.CandidatesKey("feat/y") - runningV, _ := delivery.EncodeVerdict(delivery.Verdict{Group: "feat/y", Heads: "0123456789ab", State: delivery.VerdictRunning}) + ykey, _ := meshcatalogclient.CandidatesKey("feat/y") + runningV, _ := meshdeliverycheckerclient.EncodeVerdict(meshdeliverychecker.VerdictsByGroupHeadsValue{Group: "feat/y", Heads: "0123456789ab", State: meshdeliverycheckerclient.VerdictRunning}) builds := fakeState{} - for module, state := range map[string]string{"web": delivery.BuildBuilding, "api": delivery.BuildDone} { - k, _ := delivery.BuilderBuildKey(module, fp) - builds[k], _ = json.Marshal(delivery.BuilderBuild{V: 1, State: state}) + for module, state := range map[string]string{"web": buildsrec.BuildBuilding, "api": buildsrec.BuildDone} { + k, _ := buildsrec.BuilderBuildKey(module, fp) + builds[k], _ = json.Marshal(buildsrec.BuilderBuild{V: 1, State: state}) } rd := Reader{ StepStates: map[string]StateRead{"mesh-build-go": fakeState{"shanks.abc1": running, "ace.abc2": passed}.read()}, CandidateState: fakeState{ckey: cand, ykey: candY}.read(), VerdictState: fakeState{keyOf(t, "feat/y", candY): runningV}.read(), BuildState: builds.read(), - HaveState: fakeState{"shanks.mesh-build-go": haveOf(t, delivery.PhaseRunning)}.read(), + HaveState: fakeState{"shanks.mesh-build-go": haveOf(t, nodeengineclient.PhaseRunning)}.read(), HearingState: fakeState{"dead-letters": []byte(`{"heard":false,"streams":["SEAT_NODE_BUILD_GO"],` + `"why":"DEAD_LETTERS is not heard, so an ask a build seat gives up on is waited for until its bound: SEAT_NODE_BUILD_GO: permissions violation",` + `"at":"2026-10-26T13:00:00Z"}`)}.read(), @@ -195,9 +202,9 @@ func TestTheStatusToolReadsRunningSteps(t *testing.T) { } } -func haveOf(t *testing.T, phase delivery.Phase) []byte { +func haveOf(t *testing.T, phase string) []byte { t.Helper() - raw, err := delivery.EncodeHave(delivery.Have{Phase: phase, Health: delivery.HealthOK}) + raw, err := nodeengineclient.EncodeHave(nodeengine.HaveByNodeModuleValue{Phase: phase, Health: nodeengineclient.HealthOK}) if err != nil { t.Fatal(err) } @@ -206,7 +213,7 @@ func haveOf(t *testing.T, phase delivery.Phase) []byte { func stepOf(t *testing.T, id, node string, started time.Time) []byte { t.Helper() - raw, err := delivery.EncodeStep(delivery.Step{ID: id, Tool: "build_go_fetch", Node: node, State: delivery.StepRunning, Started: started}) + raw, err := buildstep.EncodeStep(buildstep.Step{ID: id, Tool: "build_go_fetch", Node: node, State: buildstep.StepRunning, Started: started}) if err != nil { t.Fatal(err) } @@ -228,8 +235,8 @@ func TestTheStatusCountsOnlyStepsThatCanStillRunAndSaysWhyTheOthersAreLeft(t *te }.read(), }, HaveState: fakeState{ - "g14.mesh-build-go": haveOf(t, delivery.PhaseRunning), - "shanks.mesh-build-go": haveOf(t, delivery.PhaseFailed), + "g14.mesh-build-go": haveOf(t, nodeengineclient.PhaseRunning), + "shanks.mesh-build-go": haveOf(t, nodeengineclient.PhaseFailed), }.read(), StepBound: 30 * time.Minute, Now: func() time.Time { return now }, @@ -258,7 +265,7 @@ func TestTheStatusCountsOnlyStepsThatCanStillRunAndSaysWhyTheOthersAreLeft(t *te // 1887/A10: a group of hq's pull requests alone is judged by the checker since R4g, so without a verdict it is unjudged. func TestTheStatusCountsAnHQOnlyGroupAsUnjudged(t *testing.T) { cand := candidateOf(t, "pb-x", pullOf(hqRepo, 3, head2)) - ckey, _ := delivery.CandidatesKey("pb-x") + ckey, _ := meshcatalogclient.CandidatesKey("pb-x") st, err := Reader{CandidateState: fakeState{ckey: cand}.read(), VerdictState: fakeState{}.read()}.Status() if err != nil { t.Fatal(err) @@ -268,7 +275,7 @@ func TestTheStatusCountsAnHQOnlyGroupAsUnjudged(t *testing.T) { } // ADR 0548 §3: a candidate whose check an approved merge's turn holds off is named with the turn. held := candidateOf(t, "feat/x", pullOf(repoA, 7, head1)) - hkey, _ := delivery.CandidatesKey("feat/x") + hkey, _ := meshcatalogclient.CandidatesKey("feat/x") st, err = Reader{CandidateState: fakeState{hkey: held}.read(), VerdictState: fakeState{}.read(), Trunks: func() (map[string]string, error) { return map[string]string{repoA: trunkA}, nil }, TurnState: fakeState{turnKey(t, repoA): turnOf(t, repoA, "fix/other", "0123456789ab")}.read()}.Status() @@ -311,13 +318,13 @@ func TestReadToolsReadOnlyWhatTheyNeedInParallelAndSayWhenCut(t *testing.T) { // kept := fakeState{} for i := 0; i < 1800; i++ { level := map[bool]string{true: "unit", false: "integration"}[i%9 != 0] - k, _ := delivery.KeptKey(level, fmt.Sprintf("%064x", i)) + k, _ := meshdeliverycheckerclient.KeptKey(level, fmt.Sprintf("%064x", i)) kept[k] = []byte(`{"v":1}`) } previews := fakeState{} for i := 0; i < 1500; i++ { repo := map[bool]string{true: repoA, false: repoSDK}[i < 300] - k, _ := delivery.PreviewKey(repo, fmt.Sprintf("%08x%032x", i, 0), trunkA) + k, _ := meshdeliverycheckerclient.PreviewKey(repo, fmt.Sprintf("%08x%032x", i, 0), trunkA) previews[k] = []byte(`{"v":1}`) } for _, row := range []struct { @@ -372,15 +379,15 @@ func TestReadToolsReadOnlyWhatTheyNeedInParallelAndSayWhenCut(t *testing.T) { // steps := fakeState{} for i := 0; i < 1500; i++ { id := fmt.Sprintf("mvo5%04dabcdef", i) - step := delivery.Step{ID: id, Tool: "t", Node: "g14", State: delivery.StepPassed} + step := buildstep.Step{ID: id, Tool: "t", Node: "g14", State: buildstep.StepPassed} if i == 1499 { - step.State, step.Started = delivery.StepRunning, now.Add(-time.Minute) + step.State, step.Started = buildstep.StepRunning, now.Add(-time.Minute) } - raw, _ := delivery.EncodeStep(step) + raw, _ := buildstep.EncodeStep(step) steps["g14."+id] = raw } sb := &slowBucket{vals: steps} - st, err := Reader{StepStates: map[string]StateRead{"mesh-build-go": sb.read()}, HaveState: fakeState{"g14.mesh-build-go": haveOf(t, delivery.PhaseRunning)}.read(), + st, err := Reader{StepStates: map[string]StateRead{"mesh-build-go": sb.read()}, HaveState: fakeState{"g14.mesh-build-go": haveOf(t, nodeengineclient.PhaseRunning)}.read(), Now: func() time.Time { return now }}.Status() if err != nil || sb.reads.Load() != MaxReads || len(st.Running) != 1 || sb.maxFlight.Load() < 2 { t.Errorf("status: %d reads (want %d), running %+v, in flight %d, %v", sb.reads.Load(), MaxReads, st.Running, sb.maxFlight.Load(), err) diff --git a/modules/mesh-delivery-checker/internal/check/renames.go b/modules/mesh-delivery-checker/internal/check/renames.go index 0d8bfc67..dd2525db 100644 --- a/modules/mesh-delivery-checker/internal/check/renames.go +++ b/modules/mesh-delivery-checker/internal/check/renames.go @@ -9,8 +9,9 @@ import ( "strings" "git.novox.be/novox/mesh-contracts/go/seats/meshconfig" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // A config value renamed (design 32 *A config value renamed*, DLV-C5): the checker asks the seat mesh-config's verb @@ -52,15 +53,15 @@ func renamesOf(was, now catalogue.Manifest) renameChange { } // judgeRenames fails the check for a rename the seat refuses, a window a change would drop, or an ask that cannot be made. -func (st *state) judgeRenames(ctx context.Context) *delivery.Verdict { +func (st *state) judgeRenames(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue { if st.c.Try != nil { return nil } - var out *delivery.Verdict + var out *meshdeliverychecker.VerdictsByGroupHeadsValue failed := func(words string) { - f := delivery.FailedFirst{Machine: meshconfig.Seat, Words: words} + f := FailedFirst{Machine: meshconfig.Seat, Words: words} if out == nil { - out = &delivery.Verdict{State: delivery.VerdictFail, Failed: f} + out = &meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &f} return } addFailure(out, f) diff --git a/modules/mesh-delivery-checker/internal/check/results.go b/modules/mesh-delivery-checker/internal/check/results.go index 218aa4db..67b5ec91 100644 --- a/modules/mesh-delivery-checker/internal/check/results.go +++ b/modules/mesh-delivery-checker/internal/check/results.go @@ -1,11 +1,7 @@ package check import ( - "encoding/json" - "strings" - - "git.novox.be/novox/mesh-sdk/go/buildstep" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // The shapes of what the build modules' steps answer, as their code writes them (mesh-catalog modules/mesh-build-go @@ -13,52 +9,10 @@ import ( // cmd/mesh-build-node check.go TestResult, fetch.go FetchResult) with the fields the fetch step adds for a merge (ADR 0484 // clause 3). Only the fields the checker reads are named; the rest is ignored. -// MergeTree is what a fetch that merged says of one module directory: the tree sha, the digest of its manifest, the -// hashes of its lock files by name and the objects of its named paths. A bare string is the tree alone. -type MergeTree struct { - Tree string `json:"tree"` - Manifest string `json:"manifest,omitempty"` - Locks map[string]string `json:"locks,omitempty"` - // Paths are, of a module whose every artifact names its paths, each named path's object; Tree is then their named - // tree (mesh-sdk buildstep.ModuleTree, novox/hq ADR 0499 clause 2). - Paths map[string]string `json:"paths,omitempty"` - // Packages are the mesh packages the directory's go.sum names (mesh-sdk buildstep.ModuleTree, novox/hq ADR 0573 §2). - Packages []string `json:"packages,omitempty"` -} - -// UnmarshalJSON takes a string as the tree alone, or an object. -func (t *MergeTree) UnmarshalJSON(data []byte) error { - if s := strings.TrimSpace(string(data)); strings.HasPrefix(s, `"`) { - return json.Unmarshal(data, &t.Tree) - } - type plain MergeTree - return json.Unmarshal(data, (*plain)(t)) -} - -// Fetched is the answer of a build module's fetch step. -type Fetched struct { - Workspace string `json:"workspace"` - Commit string `json:"commit"` - Modules []string `json:"modules"` - // What a fetch with a base adds: the trunk commit, the merge commit, the files in conflict (a conflict is an answer), - // the paths the merge changes and the trees of the module directories. - Base string `json:"base,omitempty"` - Merge string `json:"merge,omitempty"` - Conflicts []string `json:"conflicts,omitempty"` - Changed []string `json:"changed,omitempty"` - Trees map[string]MergeTree `json:"trees,omitempty"` - // Programs are the changed files whose first bytes are an executable binary, read by the build module in its workspace - // (issue 1577); nil for a build module that does not report it, a pointer to an empty list for one that found none. - Programs *[]ProgramFile `json:"programs,omitempty"` -} - -// ProgramFile is a changed file that is a compiled program, and its format (ELF, Mach-O or PE). -type ProgramFile struct { - Path string `json:"path"` - Kind string `json:"kind"` - // Commit is set for a program a commit of the branch added and a later one removed: it stays in history once merged. - Commit string `json:"commit,omitempty"` -} +// Fetched is the answer of a build module's fetch step, whole: the Delivery SDK's FetchAnswer, which a check answer keeps +// (B2g; the build seats' part FetchAnswer): the workspace, the merge preview with its conflicts, changed paths, programs and +// trees. A conflict is an answer. +type Fetched = buildstep.FetchAnswer // GoPackage is one package of a Go test result. type GoPackage struct { @@ -128,5 +82,5 @@ type Digested struct { // JudgedBy is the program that ran the digest, as Tested.JudgedBy says. JudgedBy string `json:"judged_by,omitempty"` // Needs are the packages whose tests need something of the node at this level (issue 890). - Needs []delivery.PackageNeeds `json:"needs,omitempty"` + Needs []buildstep.PackageNeeds `json:"needs,omitempty"` } diff --git a/modules/mesh-delivery-checker/internal/check/seatdefs.go b/modules/mesh-delivery-checker/internal/check/seatdefs.go index 4669fd27..bda77725 100644 --- a/modules/mesh-delivery-checker/internal/check/seatdefs.go +++ b/modules/mesh-delivery-checker/internal/check/seatdefs.go @@ -4,7 +4,8 @@ import ( "context" "fmt" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" ) // A pull request whose head's seat definition does not parse fails its check, naming the seat and where it lives (design @@ -25,15 +26,15 @@ type SeatDefinitions interface { // refuseUnparsed fails the check of each pull request with a seat definition that does not parse; a state that cannot be // read fails it too, saying so. -func (st *state) refuseUnparsed(ctx context.Context) *delivery.Verdict { +func (st *state) refuseUnparsed(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue { if st.j.SeatDefinitions == nil || st.c.Try != nil { return nil } - var out *delivery.Verdict + var out *meshdeliverychecker.VerdictsByGroupHeadsValue failed := func(words string) { - f := delivery.FailedFirst{Machine: SeatDefinitionsMachine, Words: words} + f := FailedFirst{Machine: SeatDefinitionsMachine, Words: words} if out == nil { - out = &delivery.Verdict{State: delivery.VerdictFail, Failed: f} + out = &meshdeliverychecker.VerdictsByGroupHeadsValue{State: meshdeliverycheckerclient.VerdictFail, Failed: &f} return } addFailure(out, f) diff --git a/modules/mesh-delivery-checker/internal/check/series.go b/modules/mesh-delivery-checker/internal/check/series.go index 6574b700..1d61bb60 100644 --- a/modules/mesh-delivery-checker/internal/check/series.go +++ b/modules/mesh-delivery-checker/internal/check/series.go @@ -6,8 +6,8 @@ import ( "slices" "strings" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // A change of a package series is named in the verdict, so the review of the change sees it (design 32 *A package, its @@ -19,7 +19,7 @@ import ( func (st *state) seriesChanges(ctx context.Context) { for _, p := range st.c.Pulls { var words []string - var changes []delivery.SeriesChange + var changes []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem for _, module := range p.Modules { was, now, ok, err := st.manifestPair(ctx, p, module) if err != nil { @@ -72,13 +72,13 @@ func (st *state) manifestPair(ctx context.Context, p Pull, module string) (was, } // seriesLine is a series change as the status line names it: `/ series → `. -func seriesLine(c delivery.SeriesChange) string { +func seriesLine(c meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) string { return fmt.Sprintf("%s/%s series %s → %s", c.Module, c.Artifact, c.From, c.To) } // seriesMoved are the artifacts of module whose series the head's manifest changes from the trunk's, by artifact. An // artifact new on the head is no series change (the lead's ruling): it has no series on the trunk. -func seriesMoved(module string, was, now catalogue.Manifest) []delivery.SeriesChange { +func seriesMoved(module string, was, now catalogue.Manifest) []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem { if was.Build == nil || now.Build == nil { return nil } @@ -86,37 +86,39 @@ func seriesMoved(module string, was, now catalogue.Manifest) []delivery.SeriesCh for _, a := range was.Build.Artifacts { before[a.Name] = a.SeriesOf() } - var out []delivery.SeriesChange + var out []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem for _, a := range now.Build.Artifacts { if old, ok := before[a.Name]; ok && old != a.SeriesOf() { - out = append(out, delivery.SeriesChange{Module: module, Artifact: a.Name, From: old, To: a.SeriesOf()}) + out = append(out, meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem{Module: module, Artifact: a.Name, From: old, To: a.SeriesOf()}) } } - slices.SortFunc(out, func(a, b delivery.SeriesChange) int { return strings.Compare(a.Artifact, b.Artifact) }) + slices.SortFunc(out, func(a, b meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) int { + return strings.Compare(a.Artifact, b.Artifact) + }) return out } // series keeps, of a pull request (pullKey), the words of the series changes it makes and the changes themselves. -func (c *Case) series(key string, words []string, changes []delivery.SeriesChange) { +func (c *Case) series(key string, words []string, changes []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) { c.notesMu.Lock() defer c.notesMu.Unlock() if c.seriesOf == nil { - c.seriesOf, c.seriesChanges = map[string][]string{}, map[string][]delivery.SeriesChange{} + c.seriesOf, c.seriesChanges = map[string][]string{}, map[string][]meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem{} } c.seriesOf[key], c.seriesChanges[key] = words, changes } // SeriesChanges are the series changes a pull request (pullKey) makes, as the verdict and its event name them. -func (c *Case) SeriesChanges(key string) []delivery.SeriesChange { +func (c *Case) SeriesChanges(key string) []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem { c.notesMu.Lock() defer c.notesMu.Unlock() return slices.Clone(c.seriesChanges[key]) } // allSeries are the series changes of every pull request of the case, each module's artifact once, by module and -// artifact: what the verdict names (delivery.Verdict.Series). -func (c *Case) allSeries() []delivery.SeriesChange { - var out []delivery.SeriesChange +// artifact: what the verdict names (meshdeliverychecker.VerdictsByGroupHeadsValue.Series). +func (c *Case) allSeries() []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem { + var out []meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem seen := map[string]bool{} for _, p := range c.Pulls { for _, s := range c.SeriesChanges(pullKey(p)) { @@ -126,7 +128,7 @@ func (c *Case) allSeries() []delivery.SeriesChange { } } } - slices.SortFunc(out, func(a, b delivery.SeriesChange) int { + slices.SortFunc(out, func(a, b meshdeliverychecker.VerdictsByGroupHeadsValueSeriesItem) int { return strings.Compare(a.Module+"/"+a.Artifact, b.Module+"/"+b.Artifact) }) return out diff --git a/modules/mesh-delivery-checker/internal/check/tagwanted.go b/modules/mesh-delivery-checker/internal/check/tagwanted.go index bc0595c7..4dd1a301 100644 --- a/modules/mesh-delivery-checker/internal/check/tagwanted.go +++ b/modules/mesh-delivery-checker/internal/check/tagwanted.go @@ -10,9 +10,10 @@ import ( "sync" "time" - "git.novox.be/novox/mesh-contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-sdk/go/catalogue" - "git.novox.be/novox/mesh-sdk/go/delivery" ) // EventTagWanted is the checker's event that asks the builder for a group member's tagged versions (design 51 *Tagged @@ -158,11 +159,11 @@ func (st *state) manifestAtHead(ctx context.Context, p Pull, module string) (m c } } if dirs == nil { - dirs = []string{"modules/" + module, delivery.RootPath} + dirs = []string{"modules/" + module, meshcatalogclient.RootPath} } for _, d := range dirs { file := "module.json" - if t := strings.Trim(d, "/"); t != "" && t != delivery.RootPath { + if t := strings.Trim(d, "/"); t != "" && t != meshcatalogclient.RootPath { file = t + "/module.json" } text, err := st.j.Forge.File(ctx, p.Repository, file, p.Head) @@ -248,7 +249,7 @@ func (st *state) declaredPackages(ctx context.Context) ([]declaredPackage, error // packages by module path in Go's form (StepAsk.Packages), and the TypeScript packages a member's Node fetch takes from the // registry, by the repository that publishes them. type groupVersions struct { - goPackages delivery.PackageVersions + goPackages buildstep.PackageVersions node map[string]NodePackage } @@ -286,7 +287,7 @@ func (g *groupVersions) took(d declaredPackage, p TaggedPublished) { switch d.language { case "go": if g.goPackages == nil { - g.goPackages = delivery.PackageVersions{} + g.goPackages = buildstep.PackageVersions{} } g.goPackages[p.Module] = goVersion(p.Version) // Heard took no Go published without its module case "typescript": @@ -300,13 +301,13 @@ func (g *groupVersions) took(d declaredPackage, p TaggedPublished) { // tagged asks a group's tagged versions before any member is built (design 51 *Tagged versions*): one tag-wanted per // package a member declares, then each package's published awaited within the publish bound. A lone pull request and a // try ask nothing. A package not published within the bound fails the check naming it. -func (st *state) tagged(ctx context.Context) *delivery.Verdict { +func (st *state) tagged(ctx context.Context) *meshdeliverychecker.VerdictsByGroupHeadsValue { if st.j.Tags == nil || st.c.Try != nil || len(st.c.Pulls) < 2 { return nil } decl, err := st.declaredPackages(ctx) if err != nil { - return fail(delivery.CheckerModule, err.Error()) + return fail(CheckerModule, err.Error()) } if len(decl) == 0 { return nil @@ -316,7 +317,7 @@ func (st *state) tagged(ctx context.Context) *delivery.Verdict { for _, d := range decl { t := TagWanted{Repository: d.pull.Repository, Branch: st.c.Group, Head: d.pull.Head, Module: d.module, Package: d.name, Pull: int64(d.pull.Number)} if err := st.j.Tags.Want(ctx, t); err != nil { - return fail(delivery.CheckerModule, fmt.Sprintf("the tagged version of %s of %s (%s) cannot be asked: %v", d.name, d.module, pullKey(d.pull), err)) + return fail(CheckerModule, fmt.Sprintf("the tagged version of %s of %s (%s) cannot be asked: %v", d.name, d.module, pullKey(d.pull), err)) } names = append(names, d.module+"/"+d.name) } @@ -341,9 +342,9 @@ func (st *state) tagged(ctx context.Context) *delivery.Verdict { } if len(late) > 0 { if ctx.Err() != nil { - return fail(delivery.CheckerModule, ctx.Err().Error()) + return fail(CheckerModule, ctx.Err().Error()) } - return fail(delivery.CheckerModule, fmt.Sprintf("no published of the tagged version of %s within %s, so no member is built (design 51 *Tagged versions*)", strings.Join(late, "; "), bound)) + return fail(CheckerModule, fmt.Sprintf("no published of the tagged version of %s within %s, so no member is built (design 51 *Tagged versions*)", strings.Join(late, "; "), bound)) } return nil } diff --git a/modules/mesh-delivery-checker/internal/check/try.go b/modules/mesh-delivery-checker/internal/check/try.go index 990cf686..d0155e5d 100644 --- a/modules/mesh-delivery-checker/internal/check/try.go +++ b/modules/mesh-delivery-checker/internal/check/try.go @@ -13,8 +13,11 @@ import ( "sync" "time" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" + "git.novox.be/novox/mesh-sdk/go/keys" ) // A try is a pushed head judged by the checker as a pull request's head is, before its pull request is opened (ADR 0501 @@ -29,7 +32,7 @@ const ( TryEndTool = "checker_try_end" // TriesState is the checker's state of tries: the bucket mesh-delivery-checker_tries, keys try-. TriesState = "tries" - TriesBucket = delivery.CheckerModule + "_" + TriesState + TriesBucket = CheckerModule + "_" + TriesState // MaxTries and MaxTriesWaiting are checked when a try is asked, mesh-wide: a new try is refused while MaxTries tries run // on a build node or MaxTriesWaiting wait for one (queued, or their asks not yet taken by a node). It is no bound on // what runs later: tries let in while waiting may later all run at once, and each node's build capacity bounds what @@ -78,11 +81,11 @@ type Try struct { Said string `json:"said,omitempty"` // Took is the time from its start to its end, as Go writes a duration. Took string `json:"took,omitempty"` - // Failed is every test that failed, each with its first line, at most delivery.MaxFailures and then how many more + // Failed is every test that failed, each with its first line, at most meshdeliverycheckerclient.MaxFailures and then how many more // (issue 1921); or what else failed first (a conflict, a refused step). - Failed []string `json:"failed,omitempty"` - Levels map[string]delivery.LevelResult `json:"levels,omitempty"` - Machines []string `json:"machines,omitempty"` + Failed []string `json:"failed,omitempty"` + Levels map[string]meshdeliverychecker.VerdictsByGroupHeadsValueLevelsValue `json:"levels,omitempty"` + Machines []string `json:"machines,omitempty"` // Asks are the ids of the asks the try put on the build seats' streams, each ending on the step key . of its // build module: the delivery view counts those steps as a try's, not a candidate's check (build_usage). Asks []string `json:"asks,omitempty"` @@ -122,7 +125,7 @@ type tryWriter struct { } // heard takes an ask put (step nil) or a value of its step key in, and writes the record when it changed. -func (w *tryWriter) heard(askID string, step *delivery.Step) { +func (w *tryWriter) heard(askID string, step *buildstep.Step) { w.mu.Lock() defer w.mu.Unlock() if w.stopped { @@ -180,7 +183,7 @@ func (t Try) HoldsANode() bool { if len(t.Steps) == 0 { return true } - return slices.ContainsFunc(t.Steps, func(s TryStep) bool { return s.State == delivery.StepRunning }) + return slices.ContainsFunc(t.Steps, func(s TryStep) bool { return s.State == buildstep.StepRunning }) } // Over says whether the try came to its end. @@ -199,7 +202,7 @@ func DecodeTry(raw []byte) (Try, error) { } func encodeTry(t Try) ([]byte, error) { - t.V = delivery.Version + t.V = keys.Version return json.Marshal(t) } @@ -232,7 +235,7 @@ func AskTry(store TryStore, trunks map[string]string, a TryAsk, now time.Time) ( return "", fmt.Errorf("%q is not a repository, /", a.Repository) case !tryHeadOK.MatchString(head): return "", fmt.Errorf("the head %q is not a full commit, 40 or 64 hex digits", a.Head) - case a.Level != "" && a.Level != delivery.LevelUnit && a.Level != delivery.LevelIntegration: + case a.Level != "" && a.Level != buildstep.LevelUnit && a.Level != buildstep.LevelIntegration: return "", fmt.Errorf("the level %q is neither unit nor integration", a.Level) case a.Tests != "" && !tryTestsOK.MatchString(a.Tests): return "", fmt.Errorf("the tests %q are not a go test -run pattern", a.Tests) @@ -530,9 +533,9 @@ func (tr *Tryer) run(ctx context.Context, key string) { // Not the change's: its ask was given up or never taken (issue 734), or a part of the mesh was not heard. t.State = TryError if IsLostAsk(v) || IsNotTaken(v) { - t.Failed = []string{v.Failed.Words} + t.Failed = []string{failedOf(v).Words} } - case v.State == delivery.VerdictPass: + case v.State == meshdeliverycheckerclient.VerdictPass: t.State = TryPass default: t.State = TryFail diff --git a/modules/mesh-delivery-checker/internal/check/try_test.go b/modules/mesh-delivery-checker/internal/check/try_test.go index be2ea659..0a2856ef 100644 --- a/modules/mesh-delivery-checker/internal/check/try_test.go +++ b/modules/mesh-delivery-checker/internal/check/try_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/sdk/buildstep" ) // memTries is the tries state as the tools see it, over a memKV. @@ -72,7 +72,7 @@ func TestATryIsAskedQueuedOncePerHeadAndAtMostFourAtOnce(t *testing.T) { // ADR // their asks not taken) are bounded apart, MaxTries and MaxTriesWaiting; the refusal names both counts. onNode := func(i int) Try { return Try{ID: fmt.Sprintf("try-%d", i), Repository: repoA, Head: fmt.Sprintf("%040x", i), Trunk: trunkA, State: TryRunning, - Steps: []TryStep{{Ask: "a", State: delivery.StepRunning}}} + Steps: []TryStep{{Ask: "a", State: buildstep.StepRunning}}} } waitingFor := func(i int) Try { return Try{ID: fmt.Sprintf("try-%d", i), Repository: repoA, Head: fmt.Sprintf("%040x", i), Trunk: trunkA, State: TryRunning, @@ -133,7 +133,7 @@ func TestATryIsJudgedAsAPullRequestsHeadAndOwesTheForgeNothing(t *testing.T) { / tr.Put(ctx, id, raw) tr.Wait() got := readTry(t, kv, id) - if got.State != TryPass || got.Took == "" || got.Head != head1 || got.Trunk != trunkA || got.Said == "" || got.Levels[delivery.LevelUnit].Run != 2 { + if got.State != TryPass || got.Took == "" || got.Head != head1 || got.Trunk != trunkA || got.Said == "" || got.Levels[buildstep.LevelUnit].Run != 2 { t.Fatalf("try = %+v", got) } // Issue 1890: while it runs, the try names its ask as soon as it is put, and then where its step stands; the end @@ -142,17 +142,17 @@ func TestATryIsJudgedAsAPullRequestsHeadAndOwesTheForgeNothing(t *testing.T) { / written.running[2].Steps[0].Node != "shanks" { t.Errorf("the running try's writes = %+v, want its ask named while it ran, then its node", written.running) } - if len(got.Steps) != 1 || got.Steps[0].Node != "shanks" || got.Steps[0].State != delivery.StepPassed { + if len(got.Steps) != 1 || got.Steps[0].Node != "shanks" || got.Steps[0].State != buildstep.StepPassed { t.Errorf("the ended try's steps = %+v", got.Steps) } // The try names the asks it put, so the delivery view counts their steps as a try's (build_usage). if len(got.Asks) != 1 { t.Errorf("asks = %v, want the one check ask the try put", got.Asks) - } else if _, err := delivery.AskStepKey("shanks", got.Asks[0]); err != nil { + } else if _, err := buildstep.AskStepKey("shanks", got.Asks[0]); err != nil { t.Errorf("the try names %q, which is no ask id: %v", got.Asks[0], err) } fetch := b.steps.tool("fetch") - if len(fetch) != 1 || fetch[0].args["commit"] != head1 || fetch[0].args["base"] != trunkA || fetch[0].args["ref"] != "" { + if len(fetch) != 1 || fetch[0].args["commit"] != head1 || fetch[0].args["base"] != trunkA || fetch[0].args["ref"] != nil { t.Errorf("fetch = %+v, want the head merged onto the trunk, with no pull request's ref", fetch) } if keys, _ := b.prev.Keys(ctx); len(keys) != 0 { @@ -170,7 +170,7 @@ func TestATryIsJudgedAsAPullRequestsHeadAndOwesTheForgeNothing(t *testing.T) { / func TestATryOfOneLevelAndSomeTestsRunsThemAloneAndKeepsNothing(t *testing.T) { ctx := context.Background() kv := newMemKV() - id, err := AskTry(memTries{kv}, tryTrunks, TryAsk{Repository: repoA, Head: head1, Level: delivery.LevelIntegration, Tests: "TestBus|TestStore"}, time.Now()) + id, err := AskTry(memTries{kv}, tryTrunks, TryAsk{Repository: repoA, Head: head1, Level: buildstep.LevelIntegration, Tests: "TestBus|TestStore"}, time.Now()) if err != nil { t.Fatal(err) } @@ -180,7 +180,7 @@ func TestATryOfOneLevelAndSomeTestsRunsThemAloneAndKeepsNothing(t *testing.T) { tr.Put(ctx, id, raw) tr.Wait() tests := b.steps.tool("test") - if len(tests) != 1 || tests[0].args["level"] != delivery.LevelIntegration || tests[0].args["run"] != "TestBus|TestStore" { + if len(tests) != 1 || tests[0].args["level"] != buildstep.LevelIntegration || tests[0].args["run"] != "TestBus|TestStore" { t.Fatalf("tests = %+v, want the integration level alone with the run pattern", tests) } if n := len(b.steps.tool("digest")); n != 0 { diff --git a/modules/mesh-delivery-checker/internal/check/turns.go b/modules/mesh-delivery-checker/internal/check/turns.go index 4873e333..cb8da5e0 100644 --- a/modules/mesh-delivery-checker/internal/check/turns.go +++ b/modules/mesh-delivery-checker/internal/check/turns.go @@ -5,7 +5,8 @@ import ( "strings" "sync" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery" ) // Turns holds the gate's merge turns (novox/hq ADR 0548 §3), by repository, as a watch of the gate's turns bucket puts them @@ -13,26 +14,26 @@ import ( // approved merge goes before a check not yet started. A bucket not there is read as empty. type Turns struct { mu sync.Mutex - byRepo map[string]delivery.Turn + byRepo map[string]meshdelivery.TurnsByRepositoryValue } // Put takes a value of the turns bucket in, or its delete, and answers whether what the checker holds changed. A value that // is not a turn is dropped as a delete would be: a turn the checker cannot read holds nothing. func (t *Turns) Put(key string, value []byte, deleted bool) bool { - repository, err := delivery.ParseTurnsKey(key) + repository, err := meshdeliveryclient.ParseTurnsKey(key) if err != nil { return false } t.mu.Lock() defer t.mu.Unlock() if t.byRepo == nil { - t.byRepo = map[string]delivery.Turn{} + t.byRepo = map[string]meshdelivery.TurnsByRepositoryValue{} } cur, had := t.byRepo[repository] if !deleted { - if turn, err := delivery.DecodeTurn(value); err == nil { + if turn, err := meshdeliveryclient.DecodeTurn(value); err == nil { t.byRepo[repository] = turn - return !had || !delivery.SameTurn(cur, turn) + return !had || !meshdeliveryclient.SameTurn(cur, turn) } } delete(t.byRepo, repository) @@ -41,9 +42,9 @@ func (t *Turns) Put(key string, value []byte, deleted bool) bool { // Holding names the first of the repositories (by name) a turn holds off the check of the heads, and the delivery that // waits first in it; false when none does. -func (t *Turns) Holding(repositories []string, heads string) (string, delivery.TurnWait, bool) { +func (t *Turns) Holding(repositories []string, heads string) (string, meshdelivery.TurnsByRepositoryValueWaitingItem, bool) { if t == nil { - return "", delivery.TurnWait{}, false + return "", meshdelivery.TurnsByRepositoryValueWaitingItem{}, false } sorted := make([]string, 0, len(repositories)) for _, r := range repositories { @@ -54,7 +55,7 @@ func (t *Turns) Holding(repositories []string, heads string) (string, delivery.T defer t.mu.Unlock() for _, r := range sorted { turn, ok := t.byRepo[r] - if !ok || !delivery.TurnHolds(turn, heads) { + if !ok || !meshdeliveryclient.TurnHolds(turn, heads) { continue } for _, w := range turn.Waiting { @@ -63,14 +64,14 @@ func (t *Turns) Holding(repositories []string, heads string) (string, delivery.T } } } - return "", delivery.TurnWait{}, false + return "", meshdelivery.TurnsByRepositoryValueWaitingItem{}, false } // All is every turn held now, by repository (a copy). -func (t *Turns) All() map[string]delivery.Turn { +func (t *Turns) All() map[string]meshdelivery.TurnsByRepositoryValue { t.mu.Lock() defer t.mu.Unlock() - out := make(map[string]delivery.Turn, len(t.byRepo)) + out := make(map[string]meshdelivery.TurnsByRepositoryValue, len(t.byRepo)) for r, turn := range t.byRepo { out[r] = turn } diff --git a/modules/mesh-delivery-checker/internal/check/verdictevent.go b/modules/mesh-delivery-checker/internal/check/verdictevent.go index 12434b3e..fe558a2a 100644 --- a/modules/mesh-delivery-checker/internal/check/verdictevent.go +++ b/modules/mesh-delivery-checker/internal/check/verdictevent.go @@ -3,8 +3,8 @@ package check import ( "context" - "git.novox.be/novox/mesh-contracts/go/seats/meshdeliverychecker" - "git.novox.be/novox/mesh-sdk/go/delivery" + "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliverycheckerclient" + "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdeliverychecker" ) // EventVerdict is the checker's event that says a pull request's check ended, with the generated files its steps found out @@ -23,8 +23,8 @@ type VerdictSayer interface { // sayVerdict says, for each pull request of a check that ended in pass or fail, one verdict event naming its head and its // out-of-step files. A try owes no pull request anything and says none (ADR 0501 §5). A publish that fails is said in the // log: the verdict and the status are written already, and the next check of the heads says it again. -func (ch *Checker) sayVerdict(ctx context.Context, cs *Case, v delivery.Verdict, out Outcome) { - if ch.Says == nil || cs.Try != nil || (v.State != delivery.VerdictPass && v.State != delivery.VerdictFail) { +func (ch *Checker) sayVerdict(ctx context.Context, cs *Case, v meshdeliverychecker.VerdictsByGroupHeadsValue, out Outcome) { + if ch.Says == nil || cs.Try != nil || (v.State != meshdeliverycheckerclient.VerdictPass && v.State != meshdeliverycheckerclient.VerdictFail) { return } for _, p := range cs.Pulls { diff --git a/modules/mesh-delivery-checker/manifest_test.go b/modules/mesh-delivery-checker/manifest_test.go index 6fe40f36..30e5f4a7 100644 --- a/modules/mesh-delivery-checker/manifest_test.go +++ b/modules/mesh-delivery-checker/manifest_test.go @@ -10,6 +10,8 @@ import ( "slices" "strings" "testing" + + "mesh-delivery-checker/internal/check" ) // The controller's manifest reader is mesh-controller internal/catalogue (Manifest, manifest.go: Consumes :521, @@ -101,7 +103,7 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1 if m.Module != "mesh-delivery-checker" { t.Errorf("module = %q", m.Module) } - // delivery.CheckerKeptHistory, CheckerOwedHistory, PreviewsHistory, CheckerVerdictsHistory; the names follow the SDK's buckets. + // The histories of the checker's four seat states (checked against internal/check names below); the names follow the seat mesh-delivery-checker. if want := []state{{Name: "asks", History: 1}, {Name: "hearing", History: 1}, {Name: "kept", History: 1}, {Name: "owed", History: 5}, {Name: "previews", History: 5}, {Name: "tries", History: 1}, {Name: "verdicts", History: 5}, {Name: "first-views", History: 1}, {Name: "findings", History: 1}}; !reflect.DeepEqual(m.State, want) { t.Errorf("state = %+v, want %+v", m.State, want) } @@ -165,6 +167,28 @@ func TestManifestDeclarations(t *testing.T) { // 1678/AC1 t.Errorf("the manifest declares %q: the checker has none", k) } } + // D-11 (ADR 0634 §1): the holders and buckets internal/check names, no contract naming a holder, are the manifest's: a state + // the checker reads is a `reads` (.) and a state of the indexer it follows a `consumes` of its views family. + for bucket, read := range map[string]string{check.SourceBucket: "mesh-delivery-gate.source", check.TurnsBucket: "mesh-delivery-gate.turns", + check.PresenceBucket: "mesh-delivery-watchman.presence", check.HaveBucket: "node-engine.have"} { + if strings.Replace(bucket, "_", ".", 1) != read || !slices.Contains(m.Reads, read) { + t.Errorf("the bucket %s is not the declared read %s", bucket, read) + } + } + for bucket, view := range map[string]string{check.CandidatesBucket: "candidates", check.ModulesBucket: "modules", check.ManifestsBucket: "manifests"} { + if bucket != check.IndexerModule+"_"+view || !slices.Contains(m.Consumes, check.IndexerModule+".views."+view) { + t.Errorf("the bucket %s is not a followed view of the indexer's %s", bucket, view) + } + } + if !slices.Contains(m.Consumes, check.IndexerModule+".views."+check.IndexerPredefinedState) { + t.Errorf("the indexer's state %s is followed, and not declared", check.IndexerPredefinedState) + } + for bucket, history := range map[string]int{check.CheckerVerdictsBucket: 5, check.CheckerKeptBucket: 1, check.CheckerOwedBucket: 5, check.PreviewsBucket: 5} { + own := strings.TrimPrefix(bucket, check.CheckerModule+"_") + if own == bucket || !slices.ContainsFunc(m.State, func(st state) bool { return st.Name == own && st.History == history }) { + t.Errorf("the bucket %s is not a state of the manifest at history %d", bucket, history) + } + } } func TestManifestProcess(t *testing.T) { // 1678/AC1