The builder takes the Delivery SDK's buildstep for ids and fetch requests and the conditions client for its findings and the cleared condition (ADR 0635 §4, issue 1966)
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
This commit is contained in:
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"}}}}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user