From 1d9f9cfa2cbaa7fa222c595e5f423ad49b7eb84f Mon Sep 17 00:00:00 2001 From: jochen Date: Tue, 3 Nov 2026 23:21:55 +0100 Subject: [PATCH] =?UTF-8?q?The=20check=20names=20a=20changed=20build=20mod?= =?UTF-8?q?ule=20that=20invokes=20a=20verb=20no=20running=20build=20serves?= =?UTF-8?q?=20as=20one=20it=20cannot=20run=20(ADR=200614=20=C2=A77;=20issu?= =?UTF-8?q?e=20774)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What the running builds serve is read when a changed build module is met: the node-engine's haves, the builder's build of each, its manifest. The modules it builds are composed from their manifests, as for any changed build module, and the composition's warnings name the verb. Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn --- .../cmd/mesh-delivery-checker/judge.go | 60 ++++++++++++++++++- .../internal/check/build_change.go | 31 ++++++++++ .../internal/check/composition_test.go | 23 ++++++- .../internal/check/judge.go | 6 ++ 4 files changed, 116 insertions(+), 4 deletions(-) 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 047e9ab0..d826ebee 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go @@ -17,6 +17,8 @@ import ( "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/process" + "git.novox.be/novox/mesh-delivery/sdk/graph" + "mesh-delivery-checker/internal/check" ) @@ -95,6 +97,58 @@ func (n nodeNames) Names(ctx context.Context) ([]string, error) { return out, nil } +// runningServes says what the running builds serve (novox/hq ADR 0614 §7), read when a check asks it: every module's have +// in the node-engine's have that runs healthy, the builder's build of its fingerprint, and the verbs its manifest serves +// (graph.Served). A build whose manifest cannot be read serves nothing here, said in the log. +type runningServes struct { + have *nodeNames + builds check.BuildStates + read check.ManifestReader + logf func(format string, args ...any) +} + +func (r runningServes) Served(ctx context.Context) (func(string) bool, error) { + if r.have == nil || r.have.kv == nil || r.builds == nil || r.read == nil { + return nil, fmt.Errorf("the node-engine's have, the builder's builds or the indexer's manifests cannot be read") + } + lister, err := r.have.kv.ListKeys(ctx) + if err != nil { + return nil, err + } + haves := map[string]map[string]delivery.Have{} + for key := range lister.Keys() { + node, module, isNode, err := delivery.ParseHaveKey(key) + if err != nil || isNode { + continue + } + e, err := r.have.kv.Get(ctx, key) + if err != nil { + continue + } + if h, err := delivery.DecodeHave(e.Value()); err == nil { + if haves[node] == nil { + haves[node] = map[string]delivery.Have{} + } + haves[node][module] = h + } + } + served, unread := graph.Served(haves, func(module, fp string) ([]string, bool) { + b, found, err := r.builds.Build(ctx, module, fp) + if err != nil || !found || b.Manifest == "" { + return nil, false + } + m, err := r.read.Manifest(ctx, b.Manifest) + if err != nil { + return nil, false + } + return graph.ServesOf(m), true + }, nil) + if len(unread) > 0 && r.logf != nil { + r.logf("the manifest of the running build of %v cannot be read, so it serves nothing the check counts (ADR 0614 §7)", unread) + } + return served, nil +} + func newJudge(d judgeDeps) check.Judge { var nodes interface { Names(ctx context.Context) ([]string, error) @@ -102,8 +156,12 @@ func newJudge(d judgeDeps) check.Judge { if d.nodes != nil { nodes = d.nodes } - return &check.Examiner{Nodes: nodes, Compositions: d.compositions, Asks: d.asks, Toolchains: d.tools, + ex := &check.Examiner{Nodes: nodes, Compositions: d.compositions, Asks: d.asks, Toolchains: d.tools, Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Packages: d.repos, Published: d.published, Forge: d.forge, Runs: d.repos, Builds: d.builds, BuildEnds: d.ends, BuildWait: d.buildWait, Announce: d.announce, Trunks: d.trunks, Manifests: d.repos, Read: d.repos, Predefined: d.predefined, Logf: d.logf} + if d.nodes != nil && d.builds != nil && d.repos != nil { + ex.Serving = runningServes{have: d.nodes, builds: d.builds, read: d.repos, logf: d.logf} + } + return ex } // manifestKV reads a manifest by its digest. diff --git a/modules/mesh-delivery-checker/internal/check/build_change.go b/modules/mesh-delivery-checker/internal/check/build_change.go index d22339e4..9c4ede7b 100644 --- a/modules/mesh-delivery-checker/internal/check/build_change.go +++ b/modules/mesh-delivery-checker/internal/check/build_change.go @@ -28,6 +28,9 @@ import ( // trunk's base; the base's own build is waited on. // // A module the trunk holds no record of yet is not built by the builder until it is merged, so it is not waited on either. +// +// A changed build module whose manifest invokes a seat verb or module tool no running build serves is one the check cannot +// run (ADR 0614 §7): the modules it builds are composed from their manifests, as above, and the words name the verb. // buildsOfTheCheck are the builds the check waits on before it composes (module to fingerprint) and the words for the // changed modules it composes without waiting on their build, one line each, sorted. A manifest that cannot be read leaves @@ -88,6 +91,14 @@ func (st *state) buildsOfTheCheck(ctx context.Context) (map[string]string, []str words = append(words, fmt.Sprintf("the change alters the build module %s: %s, which it builds, are built by the running %s, not the changed one, which the check cannot run, so the check does not wait on their builds; they are built again once the agreed toolchain moves (ADR 0603 §3, §10)", bm, strings.Join(built, ", "), bm)) } + if unserved := st.unservedVerbs(ctx, changed[bm]); len(unserved) > 0 { + what := "nothing it builds is in the change" + if len(built) > 0 { + what = strings.Join(built, ", ") + ", which it builds, are composed from their manifests" + } + words = append(words, fmt.Sprintf("the changed build module %s invokes %s, which no running build serves, so the check cannot run it: %s (ADR 0614 §7)", + bm, strings.Join(unserved, ", "), what)) + } } g := graph.OfManifests(changed) for _, base := range slices.Sorted(maps.Keys(changed)) { @@ -109,3 +120,23 @@ func buildsWith(m catalogue.Manifest, language string) bool { return a.Kind == catalogue.ArtifactBundle && a.Language == language }) } + +// unservedVerbs are the seat verbs and module tools m invokes (graph.InvokesOf) that no running build serves, sorted; none +// when nothing reads what runs, or what runs cannot be read (said in the log: nothing is said that was not read). +func (st *state) unservedVerbs(ctx context.Context, m catalogue.Manifest) []string { + if st.j.Serving == nil { + return nil + } + served, err := st.j.Serving.Served(ctx) + if err != nil { + st.j.logf("%s: what the running builds serve cannot be read, so no build module is named for a verb it invokes: %v", st.c.Key, err) + return nil + } + var out []string + for _, v := range graph.InvokesOf(m) { + if !served(v) { + out = append(out, v) + } + } + return out +} diff --git a/modules/mesh-delivery-checker/internal/check/composition_test.go b/modules/mesh-delivery-checker/internal/check/composition_test.go index 72ec4ecb..9ba4c332 100644 --- a/modules/mesh-delivery-checker/internal/check/composition_test.go +++ b/modules/mesh-delivery-checker/internal/check/composition_test.go @@ -127,6 +127,13 @@ func (f *fakeEnds) Ended(_ context.Context, builds map[string]string) (map[strin func (f *fakeEnds) Done(context.Context, map[string]string) bool { return false } +// fixedServing are the verbs the running builds serve. +type fixedServing []string + +func (f fixedServing) Served(context.Context) (func(string) bool, error) { + return func(v string) bool { return slices.Contains(f, v) }, nil +} + // knownRuns are the modules the trunk holds a record of (the indexer's modules): any commit. type knownRuns []string @@ -154,10 +161,17 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) { 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 want []string - waited string // the modules waited on, sorted - failed string // the failure's test, when the check fails on a build - tests bool // the tests fail: the check fails before it would wait + waited string // the modules waited on, sorted + failed string // the failure's test, when the check fails on a build + tests bool // the tests fail: the check fails before it would wait + served []string // what the running builds serve (ADR 0614 §7); nil reads nothing of what runs }{ + {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"}, + 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"}}, @@ -211,6 +225,9 @@ func aChangeToABuildModuleOrBaseNamesTheModulesBuiltOnIt(t *testing.T) { if row.runs != nil { b.ex.Runs = row.runs } + if row.served != nil { + b.ex.Serving = fixedServing(row.served) + } read, fps := fixedManifests{}, map[string]delivery.CandidateModule{} for module, m := range row.manifest { d := dig(string(rune('a' + len(read)))) diff --git a/modules/mesh-delivery-checker/internal/check/judge.go b/modules/mesh-delivery-checker/internal/check/judge.go index cb77a6e5..3480dca7 100644 --- a/modules/mesh-delivery-checker/internal/check/judge.go +++ b/modules/mesh-delivery-checker/internal/check/judge.go @@ -122,6 +122,12 @@ type Examiner struct { // Read reads a kept manifest by its digest: the change's module graph is worked out from them (graph_cycle.go, novox/hq // ADR 0603 §2); nil works out no graph. Read ManifestReader + // Serving says, from what runs, whether a running healthy build serves a seat verb or module tool (novox/hq ADR 0614 + // §7): the node-engines' haves, the builder's build of each, its manifest (graph.Served). Nil reads nothing, and no + // changed build module is named for a verb it invokes. + Serving interface { + Served(ctx context.Context) (func(invoke string) bool, error) + } // Predefined reads the catalogue's predefined keys the indexer keeps (manifests_judge.go); nil reads none. Predefined PredefinedList // Compositions makes a check's composition (ADR 0593 §7): the engines' answers (EngineCompositions); nil leaves the