From 436347d7129669517b3034b3bc29e80c72d9d1d5 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 17:15:36 +0100 Subject: [PATCH] =?UTF-8?q?The=20builder=20takes=20the=20Delivery=20SDK's?= =?UTF-8?q?=20buildstep=20for=20ids=20and=20fetch=20requests=20and=20the?= =?UTF-8?q?=20conditions=20client=20for=20its=20findings=20and=20the=20cle?= =?UTF-8?q?ared=20condition=20(ADR=200635=20=C2=A74,=20issue=201966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mesh-sdk go/buildstep and go/health's condition and finding names are no longer imported; the seat's condition-cleared is heard under the seat events pattern already held. Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- .../cmd/mesh-delivery-builder/branchend.go | 6 +- .../cmd/mesh-delivery-builder/findings.go | 75 +++++++++++-------- .../mesh-delivery-builder/integration_test.go | 7 +- .../cmd/mesh-delivery-builder/main.go | 11 ++- .../cmd/mesh-delivery-builder/oldform.go | 6 +- .../mesh-delivery-builder/publishbounds.go | 14 ++-- .../cmd/mesh-delivery-builder/refused_test.go | 30 ++++---- .../cmd/mesh-delivery-builder/versions.go | 9 ++- modules/mesh-delivery-builder/go.mod | 2 + .../internal/build/askagain.go | 7 +- .../internal/build/fixtures_test.go | 3 +- .../internal/build/r4e_test.go | 2 +- .../internal/build/run.go | 3 +- 13 files changed, 93 insertions(+), 82 deletions(-) diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/branchend.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/branchend.go index bcfc6256..4e2e1cd9 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/branchend.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/branchend.go @@ -9,8 +9,8 @@ import ( "github.com/nats-io/nats.go/jetstream" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" - "git.novox.be/novox/mesh-sdk/go/health" "mesh-delivery-builder/internal/build" "mesh-delivery-builder/internal/versions" @@ -77,7 +77,7 @@ func (h *branchEnds) heard(ctx context.Context, m jetstream.Msg) { } id := f.Repository + "/" + f.Branch if end.Done { - h.findings.clear(ctx, versions.RemovalNotDone, health.ScopeModule, id) + h.findings.clear(ctx, versions.RemovalNotDone, meshconditionsclient.ScopeModule, id) if kv := h.tagAsks(); kv != nil { if err := kv.Delete(ctx, versions.TagAskKey(f.Repository, f.Branch)); err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) { h.logf("the tag ask of %s %s stays in %s: %v", f.Repository, f.Branch, versions.TagAsksState, err) @@ -91,7 +91,7 @@ func (h *branchEnds) heard(ctx context.Context, m jetstream.Msg) { since = md.Timestamp } if h.now().Sub(since) >= versions.RemovalFindingAfter { - h.findings.raise(ctx, finding{kind: versions.RemovalNotDone, scope: health.ScopeModule, id: id, + h.findings.raise(ctx, finding{kind: versions.RemovalNotDone, scope: meshconditionsclient.ScopeModule, id: id, summary: end.Finding(f.Repository, f.Branch, since), headline: "A branch's tagged versions are not removed", explanation: "The branch ended, and the registry seats did not say removed for every version tagged for it within 15 minutes.", diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/findings.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/findings.go index 4d2e8a42..1d619da4 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/findings.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/findings.go @@ -7,8 +7,8 @@ import ( "github.com/nats-io/nats.go/jetstream" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" - "git.novox.be/novox/mesh-sdk/go/health" "git.novox.be/novox/mesh-sdk/go/process" "git.novox.be/novox/mesh-delivery/sdk/heard" @@ -18,7 +18,7 @@ import ( // findingsBucket is the builder's findings (novox/hq ADR 0564 §2): what it sees wrong now, written at its raise and // deleted at its clear, which the condition keeper raises and clears. -var findingsBucket = health.FindingsBucket(builds.BuilderModule) +var findingsBucket = meshconditionsclient.FindingsBucket(builds.BuilderModule) // findings writes the builder's findings into its state, once bound; until then each is said in the log only. type findings struct { @@ -40,7 +40,7 @@ type findingStore interface { type finding struct { kind, scope, id string summary, headline, explanation, resolved, needs string - severity health.Severity + severity string } func (f *findings) bucket() findingStore { @@ -69,43 +69,29 @@ func (f *findings) buildEnded(ctx context.Context, e builds.BuildLogEntry) { } switch e.Outcome { case builds.BuildFailed: - f.raise(ctx, finding{kind: BuildFailed, scope: health.ScopeModule, id: e.Module, + f.raise(ctx, finding{kind: BuildFailed, scope: meshconditionsclient.ScopeModule, id: e.Module, summary: "the trunk build of " + e.Module + " at " + e.FP + " (commit " + e.Commit + ") failed", headline: "A module's trunk build failed", explanation: "The builder built " + e.Module + " from the trunk and the build failed, so its new version is not delivered.", resolved: "A later trunk build of " + e.Module + " is done.", needs: "builder_builds_since shows the build; builder_build_again builds it again."}) case builds.BuildDone: - f.clear(ctx, BuildFailed, health.ScopeModule, e.Module) + f.clear(ctx, BuildFailed, meshconditionsclient.ScopeModule, e.Module) } } // raise writes fd; it is written again only when its words changed, so the keeper's condition moves only then. func (f *findings) raise(ctx context.Context, fd finding) { - key := health.FindingKey(fd.scope, fd.id, fd.kind) + o := meshconditionsclient.Observation{Scope: fd.scope, ID: fd.id, Kind: fd.kind, Severity: fd.severity, + Summary: fd.summary, Headline: fd.headline, Explanation: fd.explanation, Resolved: fd.resolved, Needs: fd.needs, + Source: "builder." + fd.kind} kv := f.bucket() if kv == nil { - f.logf("finding %s (not written, %s is not on the bus yet): %s", key, findingsBucket, fd.summary) + f.logf("finding %s (not written, %s is not on the bus yet): %s", o.Key(), findingsBucket, fd.summary) return } - if e, err := kv.Get(ctx, key); err == nil { - if old, err := health.DecodeFinding(e.Value()); err == nil && old.Summary == fd.summary { - return - } - } - at := f.now().UTC() - sev := fd.severity - if sev == "" { - sev = health.Warning - } - value, err := health.EncodeFinding(health.Finding{Key: key, Kind: fd.kind, Subject: health.Subject{Scope: fd.scope, ID: fd.id}, - Severity: sev, Summary: fd.summary, Headline: fd.headline, Explanation: fd.explanation, Resolved: fd.resolved, Needs: fd.needs, - Evidence: []health.Evidence{{At: at, Said: fd.summary}}, Source: "builder." + fd.kind, Observer: builds.BuilderModule, At: at}) - if err == nil { - _, err = kv.Put(ctx, key, value) - } - if err != nil { - f.logf("the finding %s cannot be written: %v", key, err) + if _, err := meshconditionsclient.Raise(findingBucket{ctx, kv}, builds.BuilderModule, "", o, f.now()); err != nil { + f.logf("the finding %s cannot be written: %v", o.Key(), err) } } @@ -115,13 +101,38 @@ func (f *findings) clear(ctx context.Context, kind, scope, id string) { if kv == nil { return } - key := health.FindingKey(scope, id, kind) - if _, err := kv.Get(ctx, key); errors.Is(err, jetstream.ErrKeyNotFound) { - return + if _, err := meshconditionsclient.Clear(findingBucket{ctx, kv}, scope, id, kind); err != nil { + f.logf("the finding %s cannot be cleared: %v", meshconditionsclient.Key(scope, id, kind), err) } - if err := kv.Delete(ctx, key); err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) { - f.logf("the finding %s cannot be cleared: %v", key, err) +} + +// findingBucket is the builder's findings store as the bucket the conditions client raises and clears in. +type findingBucket struct { + ctx context.Context + kv findingStore +} + +func (b findingBucket) Get(key string) ([]byte, bool, error) { + e, err := b.kv.Get(b.ctx, key) + switch { + case errors.Is(err, jetstream.ErrKeyNotFound): + return nil, false, nil + case err != nil: + return nil, false, err } + return e.Value(), true, nil +} + +func (b findingBucket) Put(key string, value []byte) error { + _, err := b.kv.Put(b.ctx, key, value) + return err +} + +func (b findingBucket) Delete(key string) error { + if err := b.kv.Delete(b.ctx, key); err != nil && !errors.Is(err, jetstream.ErrKeyNotFound) { + return err + } + return nil } // notHeard is the writer of the builder's finding of a family it does not hear (heard.Writer, ADR 0615 §5): written into @@ -162,10 +173,10 @@ func (f *findings) notHeard(ctx context.Context) heard.Writer { func (f *findings) floorSaid(ctx context.Context) func(versions.FloorWait, bool) { return func(w versions.FloorWait, raised bool) { if !raised { - f.clear(ctx, versions.FloorSilent, health.ScopeModule, w.Name) + f.clear(ctx, versions.FloorSilent, meshconditionsclient.ScopeModule, w.Name) return } - f.raise(ctx, finding{kind: versions.FloorSilent, scope: health.ScopeModule, id: w.Name, summary: w.Finding(), + f.raise(ctx, finding{kind: versions.FloorSilent, scope: meshconditionsclient.ScopeModule, id: w.Name, summary: w.Finding(), headline: "A version's floor is not known", explanation: "The builder numbers a name only once its registry seat, the forge bridge's tags and the indexer's requirements all answered; one of them did not.", resolved: "All three answered.", diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go index 92200937..c25a9159 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/integration_test.go @@ -26,6 +26,7 @@ import ( // The build runner the build modules pull and run asks with: mesh-build-go's own package (novox/hq ADR 0593 §1), // another repository's, by its published version, so the builder is tested against the real pulling code. runner "git.novox.be/novox/mesh-catalog/modules/mesh-build-go/buildstep" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" "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/contracts/go/seats/gopackageregistry" @@ -36,9 +37,7 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-forge-bridge/forgemsg" sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep" - "git.novox.be/novox/mesh-sdk/go/buildstep" "git.novox.be/novox/mesh-sdk/go/buscompose" - "git.novox.be/novox/mesh-sdk/go/health" "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" @@ -139,7 +138,7 @@ func (f *fakeBuildModules) step(module, name string, args map[string]any) (any, if args["base"] != nil { // a fetch with a base answers the merge commit (mesh-build-go FetchResult) merge = `,"merge":"` + strings.Repeat("9", 40) + `"` } - return json.RawMessage(`{"workspace":"w` + buildstep.NewID() + `","commit":"` + commit + `"` + merge + `}`), nil + return json.RawMessage(`{"workspace":"w` + sdkstep.NewID() + `","commit":"` + commit + `"` + merge + `}`), nil case f.failBuild.Load() && tool != "build_image_copy": // the build fails wherever it runs return nil, fmt.Errorf("go build of %v: boom", args["artifact"]) case tool == "build_image_copy": // the upstream copy answers the digest and the registry's reference (ADR 0498 clause 4) @@ -657,7 +656,7 @@ func TestBuilderMakesADoneKeyWithTheDigests(t *testing.T) { // 1607/AC7 if f := b.awaitFP(t, "netcheck", third, isState(builds.BuildFailed)); !strings.Contains(f.Error, "did not end within") { t.Errorf("the failed build = %+v, want its bound run out", f) } - failed := health.FindingKey(health.ScopeModule, "netcheck", BuildFailed) + failed := meshconditionsclient.Key(meshconditionsclient.ScopeModule, "netcheck", BuildFailed) reg.awaitFinding(t, failed, true) b.fake.hang.Store(nil) close(release) diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go index 3e614c9d..2c092824 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go @@ -345,12 +345,11 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro } heardGone(ctx, b, m, logger.Printf) } - // The seats' events (artifacts-gone, the registry seats' words, manifest-accepted and manifest-reads-changed) and the - // condition keeper's condition-cleared; the owners' views events are the views reader's. - for _, pattern := range []string{"mesh.seat.*.event.>", conditionClearedSubject} { - if err := hub.Handle(pattern, events); err != nil { - return err - } + // The seats' events (artifacts-gone, the registry seats' words, manifest-accepted and manifest-reads-changed and the + // seat mesh-conditions' condition-cleared, which this pattern covers: a consumer takes no two overlapping filters); the + // owners' views events are the views reader's. + if err := hub.Handle("mesh.seat.*.event.>", events); err != nil { + return err } if stopped != nil { return stopped diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/oldform.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/oldform.go index fa1189ce..79399856 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/oldform.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/oldform.go @@ -12,8 +12,8 @@ import ( "github.com/nats-io/nats.go/jetstream" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" - "git.novox.be/novox/mesh-sdk/go/health" "git.novox.be/novox/mesh-delivery/sdk/forms" @@ -97,11 +97,11 @@ func (o *oldForm) step(ctx context.Context, named []string, counted map[string][ } for _, n := range o.overdue { if !slices.Contains(overdue, n) { - o.findings.clear(ctx, versions.OldFormHeld, health.ScopeMachine, n) + o.findings.clear(ctx, versions.OldFormHeld, meshconditionsclient.ScopeMachine, n) } } for _, n := range overdue { - o.findings.raise(ctx, finding{kind: versions.OldFormHeld, scope: health.ScopeMachine, id: n, + o.findings.raise(ctx, finding{kind: versions.OldFormHeld, scope: meshconditionsclient.ScopeMachine, id: n, summary: fmt.Sprintf("the node-engine of %s has read only an older reference form since %s, so every build key still carries it", n, next.Since[n].Format(time.RFC3339)), headline: "A node holds the old reference form", diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/publishbounds.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/publishbounds.go index d3b6a9c9..7558f30e 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/publishbounds.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/publishbounds.go @@ -8,7 +8,7 @@ import ( "github.com/nats-io/nats.go/jetstream" - "git.novox.be/novox/mesh-sdk/go/health" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" "git.novox.be/novox/mesh-sdk/go/process" "git.novox.be/novox/mesh-delivery/sdk/heard" @@ -46,7 +46,7 @@ func (p *publishes) arm(ctx context.Context, e versions.Entry) { // unanswered raises publish-unanswered about e's name, saying why. func (p *publishes) unanswered(ctx context.Context, v versions.Entry, why string) { - p.findings.raise(ctx, finding{kind: versions.PublishUnanswered, scope: health.ScopeModule, id: v.Name, + p.findings.raise(ctx, finding{kind: versions.PublishUnanswered, scope: meshconditionsclient.ScopeModule, id: v.Name, summary: fmt.Sprintf("%s said neither published nor refused of %s@%s: %s", v.Seat, v.Name, v.Version, why), headline: "A registry seat did not answer a publish", explanation: "The builder built and numbered " + v.Name + " " + v.Version + " and asked " + v.Seat + " to publish it; no word of its holder is known, so the build says done while the version may not be in the registry.", @@ -66,14 +66,14 @@ func (p *publishes) heard(ctx context.Context, seat, event string, o versions.Ou } switch event { case versions.HeardPublished: - p.findings.clear(ctx, versions.PublishRefused, health.ScopeModule, o.Name) - p.findings.clear(ctx, versions.PublishUnanswered, health.ScopeModule, o.Name) + p.findings.clear(ctx, versions.PublishRefused, meshconditionsclient.ScopeModule, o.Name) + p.findings.clear(ctx, versions.PublishUnanswered, meshconditionsclient.ScopeModule, o.Name) case versions.HeardRefused: reason := o.Reason if reason == "" { reason = "it gave no reason" } - p.findings.raise(ctx, finding{kind: versions.PublishRefused, scope: health.ScopeModule, id: o.Name, + p.findings.raise(ctx, finding{kind: versions.PublishRefused, scope: meshconditionsclient.ScopeModule, id: o.Name, summary: fmt.Sprintf("%s refused to publish %s@%s (digest %s): %s", seat, o.Name, o.Version, o.Digest, reason), headline: "A registry seat refused a publish", explanation: "The builder built and numbered " + o.Name + " " + o.Version + ", and the holder of " + seat + " refused to store it, so that version is not in the registry.", @@ -84,7 +84,7 @@ func (p *publishes) heard(ctx context.Context, seat, event string, o versions.Ou // unread raises seat-outcome-unread about seat: what it said on subject is no outcome. read clears it. func (p *publishes) unread(ctx context.Context, seat, subject string, why error) { - p.findings.raise(ctx, finding{kind: versions.SeatOutcomeUnread, scope: health.ScopeModule, id: seat, + p.findings.raise(ctx, finding{kind: versions.SeatOutcomeUnread, scope: meshconditionsclient.ScopeModule, id: seat, summary: fmt.Sprintf("%s said on %s what is no outcome of a publish: %v", seat, subject, why), headline: "A registry seat said what the builder cannot read", explanation: "The builder hears each registry seat's published, refused and removed to move a numbered version; this one does not read, so the version it was about does not move.", @@ -93,7 +93,7 @@ func (p *publishes) unread(ctx context.Context, seat, subject string, why error) } func (p *publishes) read(ctx context.Context, seat string) { - p.findings.clear(ctx, versions.SeatOutcomeUnread, health.ScopeModule, seat) + p.findings.clear(ctx, versions.SeatOutcomeUnread, meshconditionsclient.ScopeModule, seat) } // ended is told each change of the state bounds: a bound whose time ended (the bus's marker) on a version still numbered diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/refused_test.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/refused_test.go index 2e36a1db..b391ef27 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/refused_test.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/refused_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "git.novox.be/novox/mesh-sdk/go/health" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient" "git.novox.be/novox/mesh-delivery/sdk/heard" "git.novox.be/novox/mesh-sdk/go/process" @@ -319,13 +319,13 @@ func aFailedTrunkBuildIsAFinding(t *testing.T) { ended := func(outcome, ref string) builds.BuildLogEntry { return builds.BuildLogEntry{Event: builds.BuildEndedEvent, Module: "m", FP: "f1", Commit: "c", Ref: ref, Outcome: outcome} } - key := health.FindingKey(health.ScopeModule, "m", BuildFailed) + key := meshconditionsclient.Key(meshconditionsclient.ScopeModule, "m", BuildFailed) f.buildEnded(context.Background(), ended(builds.BuildFailed, "refs/pull/3/head")) if len(st.vals) != 0 { t.Fatalf("a candidate's failed build is a finding: %v", st.vals) } f.buildEnded(context.Background(), ended(builds.BuildFailed, "")) - got, err := health.DecodeFinding(st.vals[key]) + got, err := meshconditionsclient.DecodeFinding(st.vals[key]) if err != nil || got.Observer != builds.BuilderModule || !strings.Contains(got.Summary, "trunk build of m at f1") { t.Fatalf("the finding = %+v, %v", got, err) } @@ -336,8 +336,8 @@ func aFailedTrunkBuildIsAFinding(t *testing.T) { // The floor finding: raised while a name waits on its floor, cleared once it is known. fw := &versions.FloorWaits{Said: f.floorSaid(context.Background())} fw.Wait("m/lib", versions.SeatGoPackages, "", versions.Floor{Silent: []string{versions.SourceTags}}, nil, time.Unix(0, 0)) - floorKey := health.FindingKey(health.ScopeModule, "m/lib", versions.FloorSilent) - if got, err := health.DecodeFinding(st.vals[floorKey]); err != nil || !strings.Contains(got.Summary, "bridge_tags did not answer") { + floorKey := meshconditionsclient.Key(meshconditionsclient.ScopeModule, "m/lib", versions.FloorSilent) + if got, err := meshconditionsclient.DecodeFinding(st.vals[floorKey]); err != nil || !strings.Contains(got.Summary, "bridge_tags did not answer") { t.Fatalf("the floor finding = %+v, %v", got, err) } fw.Answered("m/lib") @@ -357,33 +357,33 @@ func aFailedTrunkBuildIsAFinding(t *testing.T) { } pubs := &publishes{bounds: func() jetstream.KeyValue { return nil }, versions: vs, findings: f, bound: versions.PublishBound, logf: t.Logf} h := &seatHearer{n: &versions.Numberer{Versions: vs}, b: &build.Builder{}, logf: t.Logf, pub: pubs} - refusedKey := health.FindingKey(health.ScopeModule, "m/program", versions.PublishRefused) + refusedKey := meshconditionsclient.Key(meshconditionsclient.ScopeModule, "m/program", versions.PublishRefused) refused := &wakeMsg{subject: "mesh.seat.oci-artifact-registry.event.refused", data: []byte(`{"name":"m/program","version":"0.1.3","digest":"sha256:bb","reason":"bytes differ"}`)} if !h.heard(context.Background(), refused) || !refused.acked { t.Fatalf("a refusal: acked %v", refused.acked) } - if got, err := health.DecodeFinding(st.vals[refusedKey]); err != nil || !strings.Contains(got.Summary, "refused to publish m/program@0.1.3") || !strings.Contains(got.Summary, "bytes differ") { + if got, err := meshconditionsclient.DecodeFinding(st.vals[refusedKey]); err != nil || !strings.Contains(got.Summary, "refused to publish m/program@0.1.3") || !strings.Contains(got.Summary, "bytes differ") { t.Fatalf("a holder's refused: the finding = %+v, %v", got, err) } pubs.ended(context.Background(), purged{key: versions.PublishBoundKey("m/program", "0.1.4")}) - unansweredKey := health.FindingKey(health.ScopeModule, "m/program", versions.PublishUnanswered) - if got, err := health.DecodeFinding(st.vals[unansweredKey]); err != nil || !strings.Contains(got.Summary, "neither published nor refused of m/program@0.1.4") { + unansweredKey := meshconditionsclient.Key(meshconditionsclient.ScopeModule, "m/program", versions.PublishUnanswered) + if got, err := meshconditionsclient.DecodeFinding(st.vals[unansweredKey]); err != nil || !strings.Contains(got.Summary, "neither published nor refused of m/program@0.1.4") { t.Fatalf("a bound ended unanswered: the finding = %+v, %v", got, err) } // Review round 2: a bound that cannot be written is publish-unanswered at once; a watch of bounds that cannot start // is family-not-heard naming the state. delete(st.vals, unansweredKey) pubs.arm(context.Background(), versions.Entry{Name: "m/program", Version: "0.1.4", Seat: "oci-artifact-registry"}) - if got, err := health.DecodeFinding(st.vals[unansweredKey]); err != nil || !strings.Contains(got.Summary, "its bound could not be written") { + if got, err := meshconditionsclient.DecodeFinding(st.vals[unansweredKey]); err != nil || !strings.Contains(got.Summary, "its bound could not be written") { t.Fatalf("a bound not written: the finding = %+v, %v", got, err) } watchBounds(context.Background(), refusedWatch{err: errors.New("permissions violation")}, func(jetstream.KeyValueEntry) {}, f.notHeard(context.Background()), nil, t.Logf) - if got, err := health.DecodeFinding(st.vals[heard.Key(builds.BuilderModule, versions.BoundsState)]); err != nil || !strings.Contains(got.Summary, "permissions violation") { + if got, err := meshconditionsclient.DecodeFinding(st.vals[heard.Key(builds.BuilderModule, versions.BoundsState)]); err != nil || !strings.Contains(got.Summary, "permissions violation") { t.Fatalf("bounds not watched: the finding = %+v, %v", got, err) } delete(st.vals, heard.Key(builds.BuilderModule, versions.BoundsState)) malformed := &wakeMsg{subject: "mesh.seat.oci-artifact-registry.event.published", data: []byte(`{"version":"0.1.4"}`)} - unreadKey := health.FindingKey(health.ScopeModule, "oci-artifact-registry", versions.SeatOutcomeUnread) + unreadKey := meshconditionsclient.Key(meshconditionsclient.ScopeModule, "oci-artifact-registry", versions.SeatOutcomeUnread) if !h.heard(context.Background(), malformed) || !malformed.acked || st.vals[unreadKey] == nil { t.Fatalf("an outcome that does not read: acked %v, findings %v", malformed.acked, st.vals) } @@ -395,7 +395,7 @@ func aFailedTrunkBuildIsAFinding(t *testing.T) { notHeard := f.notHeard(context.Background()) unheardKey := heard.Key(builds.BuilderModule, "FORGE") _ = notHeard.Bind("FORGE", "a branch's end is not heard", func() error { return errors.New("consumer not found") }) - if got, err := health.DecodeFinding(st.vals[unheardKey]); err != nil || got.Kind != heard.Kind || !strings.Contains(got.Summary, "does not hear FORGE") { + if got, err := meshconditionsclient.DecodeFinding(st.vals[unheardKey]); err != nil || got.Kind != heard.Kind || !strings.Contains(got.Summary, "does not hear FORGE") { t.Fatalf("a consumer not there: the finding = %+v, %v", got, err) } _ = notHeard.Bind("FORGE", "a branch's end is not heard", func() error { return nil }) @@ -417,7 +417,7 @@ func aFailedTrunkBuildIsAFinding(t *testing.T) { newest := []string{forms.FormNeed("oci")} counted := map[string][]string{"a": newest, "b": nil} window.step(context.Background(), []string{"a", "b"}, counted) - heldKey := health.FindingKey(health.ScopeMachine, "b", versions.OldFormHeld) + heldKey := meshconditionsclient.Key(meshconditionsclient.ScopeMachine, "b", versions.OldFormHeld) if !window.w.Open || !slices.Equal(window.w.Holding, []string{"b"}) || st.vals[heldKey] != nil { t.Fatalf("one node lacking the newest form: %+v", window.w) } @@ -433,7 +433,7 @@ func aFailedTrunkBuildIsAFinding(t *testing.T) { } now = now.Add(versions.FindingAfter) window.step(context.Background(), []string{"a", "b"}, counted) - if got, err := health.DecodeFinding(st.vals[heldKey]); err != nil || !strings.Contains(got.Summary, "the node-engine of b") { + if got, err := meshconditionsclient.DecodeFinding(st.vals[heldKey]); err != nil || !strings.Contains(got.Summary, "the node-engine of b") { t.Fatalf("seven days held: the finding = %+v, %v", got, err) } counted["b"] = newest diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/versions.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/versions.go index de3e51e1..40d50d4e 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/versions.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/versions.go @@ -11,10 +11,10 @@ import ( "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" + "git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions" "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog" "git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" - "git.novox.be/novox/mesh-sdk/go/health" "git.novox.be/novox/mesh-sdk/go/process" "mesh-delivery-builder/internal/build" @@ -237,7 +237,7 @@ func emitEvent(ctx context.Context, js jetstream.JetStream, cred process.Account // The events on the builder's consumer of EVENTS that wake a floor (design 51 *The floor*, DLV-B3b). var ( manifestAcceptedSubject = meshcatalog.SubjectManifestAccepted - conditionClearedSubject = "mesh.mod." + health.KeeperModule + ".event." + health.EventCleared + conditionClearedSubject = meshconditions.SubjectConditionCleared ) // floorWake asks the floor of the names an event wakes again, by looking at every module again: a trunk build is @@ -256,11 +256,14 @@ func (f *floorWake) heard(ctx context.Context, m jetstream.Msg) bool { f.wake(ctx, versions.WakeAccepted, "", "") case conditionClearedSubject: f.ack(m) - var e health.Event + var e meshconditions.ConditionEvent if err := json.Unmarshal(m.Data(), &e); err != nil { f.logf("%s holds what is no condition, left: %v", m.Subject(), err) return true } + if e.Subject == nil { + return true + } for _, module := range conditionModules(e.Subject.ID) { f.wake(ctx, versions.WakeCleared, "", module) } diff --git a/modules/mesh-delivery-builder/go.mod b/modules/mesh-delivery-builder/go.mod index 53501b9f..06fe9813 100644 --- a/modules/mesh-delivery-builder/go.mod +++ b/modules/mesh-delivery-builder/go.mod @@ -56,5 +56,7 @@ require ( replace git.novox.be/novox/mesh-delivery/sdk => ../../sdk require git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep v0.0.0-00010101000000-000000000000 +require git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-00010101000000-000000000000 +require git.novox.be/novox/mesh-contracts/health-and-repair/go/seats/meshconditions v0.0.0-00010101000000-000000000000 replace git.novox.be/novox/mesh-delivery/contracts/go/seats/meshartifactsweep => ../../contracts/go/seats/meshartifactsweep diff --git a/modules/mesh-delivery-builder/internal/build/askagain.go b/modules/mesh-delivery-builder/internal/build/askagain.go index aa9147d3..072b6877 100644 --- a/modules/mesh-delivery-builder/internal/build/askagain.go +++ b/modules/mesh-delivery-builder/internal/build/askagain.go @@ -14,7 +14,6 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/buildrun" "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep" - "git.novox.be/novox/mesh-sdk/go/buildstep" ) // **An ask that can no longer be answered is asked again** (novox/hq issue 1945). A build seat's stream gives an ask up @@ -148,7 +147,7 @@ func (s AskSeen) at() time.Time { return s.At } -// AskedAt is when an ask id was made (buildstep.NewID: its milliseconds in base 36 before six hex digits), or zero. +// AskedAt is when an ask id was made (sdkstep.NewID: its milliseconds in base 36 before six hex digits), or zero. func AskedAt(id string) time.Time { if len(id) <= 6 { return time.Time{} @@ -335,7 +334,7 @@ func (b *Builder) answer(ctx context.Context, l *lane, ask sdkstep.StepAsk, aske return step, ask, ctx.Err() } old := ask.ID - ask.ID, ask.At = buildstep.NewID(), time.Now() + ask.ID, ask.At = sdkstep.NewID(), time.Now() if err := asked.note(ctx, ask.Language, ask.ID, fmt.Sprintf("put %s again after %s declined it", l.module, step.Node)); err != nil { return step, ask, fmt.Errorf("the ask of %s put again after a decline could not be written to the key: %w", l.module, err) } @@ -350,7 +349,7 @@ func (b *Builder) answer(ctx context.Context, l *lane, ask sdkstep.StepAsk, aske } asking++ old := ask.ID - ask.ID, ask.At = buildstep.NewID(), time.Now() + ask.ID, ask.At = sdkstep.NewID(), time.Now() if err := asked.note(ctx, ask.Language, ask.ID, fmt.Sprintf("asked %s again (asking %d): %s", l.module, asking, why)); err != nil { return step, ask, fmt.Errorf("the ask of %s asked again could not be written to the key: %w", l.module, err) } diff --git a/modules/mesh-delivery-builder/internal/build/fixtures_test.go b/modules/mesh-delivery-builder/internal/build/fixtures_test.go index cb04934c..956a831c 100644 --- a/modules/mesh-delivery-builder/internal/build/fixtures_test.go +++ b/modules/mesh-delivery-builder/internal/build/fixtures_test.go @@ -10,7 +10,6 @@ import ( "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds" sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep" - "git.novox.be/novox/mesh-sdk/go/buildstep" ) // result reads a captured step value of mesh-build-image (testdata/captured/README) and returns its result. @@ -44,7 +43,7 @@ func TestCapturedImageStepsDecodeWithTheBuildersOwnTypes(t *testing.T) { // revi }, Build: func(context.Context, map[string]any) (any, error) { return json.RawMessage(captured(t, tc.build)), nil }, } - ask := sdkstep.StepAsk{ID: buildstep.NewID(), Kind: sdkstep.AskDelivery, Language: sdkstep.AskImage, Asker: builds.BuilderModule, + ask := sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: sdkstep.AskDelivery, Language: sdkstep.AskImage, Asker: builds.BuilderModule, At: time.Now(), Build: &sdkstep.BuildJob{Module: "m", Path: "modules/m", Publish: tc.publish, Fetch: &sdkstep.Fetch{Repository: "novox/mesh-catalog", Commit: strings.Repeat("a", 40)}, Artifacts: []sdkstep.AskArtifact{{Artifact: "server"}}}} diff --git a/modules/mesh-delivery-builder/internal/build/r4e_test.go b/modules/mesh-delivery-builder/internal/build/r4e_test.go index 562010ce..73b1145e 100644 --- a/modules/mesh-delivery-builder/internal/build/r4e_test.go +++ b/modules/mesh-delivery-builder/internal/build/r4e_test.go @@ -10,7 +10,7 @@ import ( "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/builds" - "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/keys" ) diff --git a/modules/mesh-delivery-builder/internal/build/run.go b/modules/mesh-delivery-builder/internal/build/run.go index 50271829..fb6d164b 100644 --- a/modules/mesh-delivery-builder/internal/build/run.go +++ b/modules/mesh-delivery-builder/internal/build/run.go @@ -21,7 +21,6 @@ import ( sdkstep "git.novox.be/novox/mesh-delivery/sdk/buildstep" "git.novox.be/novox/mesh-delivery/sdk/forms" repotrunk "git.novox.be/novox/mesh-delivery/sdk/trunk" - "git.novox.be/novox/mesh-sdk/go/buildstep" "git.novox.be/novox/mesh-sdk/go/catalogue" "mesh-delivery-builder/internal/versions" @@ -869,7 +868,7 @@ func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m mes if needsFetch(l.tasks) { job.Fetch = &req } - ask := sdkstep.StepAsk{ID: buildstep.NewID(), Kind: kind, Language: language, Asker: builds.BuilderModule, At: time.Now(), Build: job} + ask := sdkstep.StepAsk{ID: sdkstep.NewID(), Kind: kind, Language: language, Asker: builds.BuilderModule, At: time.Now(), Build: job} if language == sdkstep.AskGo && job.Fetch != nil && len(m.Packages) > 0 { // The versions of the mesh packages the fingerprint was computed with (novox/hq ADR 0573 §2): the build module writes // them into the workspace, whatever the repository's go.mod names.