From e612fbebe7ead1e3e30bfd3abe85af9ecbc520e5 Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 12:44:18 +0100 Subject: [PATCH] DLV-B3: a name waiting on its floor is the builder's finding floor-silent, cleared once all three sources answer Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../cmd/mesh-delivery-builder/findings.go | 18 ++++++++++++++++++ .../cmd/mesh-delivery-builder/main.go | 4 ++-- .../cmd/mesh-delivery-builder/refused_test.go | 11 +++++++++++ .../internal/versions/floor.go | 13 ++++++++++++- 4 files changed, 43 insertions(+), 3 deletions(-) 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 dc45246d..7fe0f73a 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/findings.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/findings.go @@ -10,6 +10,8 @@ import ( "git.novox.be/novox/mesh-sdk/go/delivery" "git.novox.be/novox/mesh-sdk/go/health" "git.novox.be/novox/mesh-sdk/go/process" + + "mesh-delivery-builder/internal/versions" ) // findingsBucket is the builder's findings (novox/hq ADR 0564 §2): what it sees wrong now, written at its raise and @@ -119,3 +121,19 @@ func (f *findings) clear(ctx context.Context, kind, scope, id string) { f.logf("the finding %s cannot be cleared: %v", key, err) } } + +// floorSaid is the floor finding (design 51 *Health and conditions*: a floor source silent): raised for a name waiting on +// its floor, about that name, cleared once all three sources answered. +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) + return + } + f.raise(ctx, finding{kind: versions.FloorSilent, scope: health.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.", + needs: "builder_floor_waits names the silent source; builder_floor_again asks again by hand."}) + } +} 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 1e53357d..c817114a 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/main.go @@ -113,7 +113,8 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro // The builder numbers each trunk build at its claim (novox/hq design 51 *The versions*): its state versions, bound once // the bus holds it; the floor's three sources asked as its own account; each new number said as version.numbered. versionsKV := process.AwaitBucket(ctx, js, conn, versions.Bucket, "no trunk build is numbered: each is built unnumbered and published by its digest", logger.Printf) - floorWaits := &versions.FloorWaits{Logf: logger.Printf} + found := &findings{kv: process.AwaitBucket(ctx, js, conn, findingsBucket, "the builder's findings are said in its log only", logger.Printf), logf: logger.Printf, now: time.Now} + floorWaits := &versions.FloorWaits{Logf: logger.Printf, Said: found.floorSaid(context.WithoutCancel(ctx))} // The names waiting on their floor are kept in the state floor-waits (DLV-B4): a restarted builder takes them in, so // their silent sources' events still wake them; until the state is bound they are kept here only. process.AwaitBucket(ctx, js, conn, versions.FloorWaitsBucket, "the names waiting on their floor are kept in memory only, and builder_floor_waits shows none", logger.Printf). @@ -168,7 +169,6 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro b.Trunk = trunks.head // The builder's log (ADR 0571 §1): each build's start and end in its module log `build-log`, read by the view, the // indexer and the assigner through their reads, and by the operator's command nox builds through the tool builder_builds_since. - found := &findings{kv: process.AwaitBucket(ctx, js, conn, findingsBucket, "the builder's findings are said in its log only", logger.Printf), logf: logger.Printf, now: time.Now} b.Log = func(e delivery.BuildLogEntry) { if err := appendLog(context.WithoutCancel(ctx), js, cred, e); err != nil { logger.Printf("%v", err) 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 067e26aa..21f6ac58 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 @@ -217,6 +217,17 @@ func aFailedTrunkBuildIsAFinding(t *testing.T) { if _, ok := st.vals[key]; ok { t.Error("a later done trunk build leaves build-failed") } + // 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") { + t.Fatalf("the floor finding = %+v, %v", got, err) + } + fw.Answered("m/lib") + if _, ok := st.vals[floorKey]; ok { + t.Error("a known floor leaves floor-silent") + } } // memFindings is a findings bucket in memory. diff --git a/modules/mesh-delivery-builder/internal/versions/floor.go b/modules/mesh-delivery-builder/internal/versions/floor.go index 62aab55a..8beea79b 100644 --- a/modules/mesh-delivery-builder/internal/versions/floor.go +++ b/modules/mesh-delivery-builder/internal/versions/floor.go @@ -135,8 +135,13 @@ type FloorWaits struct { keeper FloorKeeper // Logf says a wait that could not be kept; nil says nothing. Logf func(string, ...any) + // Said is told each wait recorded (raised) and each answered (not raised): the builder's finding floor-silent. + Said func(w FloorWait, raised bool) } +// FloorSilent is the kind of the floor finding. +const FloorSilent = "floor-silent" + // Keep binds the state: the waits it holds that are not known here are taken in (a restarted builder's), every wait // known here is written to it, and every change from now on is. func (fw *FloorWaits) Keep(k FloorKeeper, held []FloorWait) { @@ -200,16 +205,22 @@ func (fw *FloorWaits) Wait(name, seat, holder string, f Floor, why map[string]st k := fw.keeper fw.mu.Unlock() fw.put(k, w) + if fw.Said != nil { + fw.Said(w, true) + } return w } // Answered drops name: its floor is known. It says whether the name waited, so its finding is cleared. func (fw *FloorWaits) Answered(name string) bool { fw.mu.Lock() - _, had := fw.waits[name] + w, had := fw.waits[name] delete(fw.waits, name) k := fw.keeper fw.mu.Unlock() + if had && fw.Said != nil { + fw.Said(w, false) + } if had && k != nil { if err := k.Delete(FloorWaitKey(name)); err != nil && fw.Logf != nil { fw.Logf("the floor wait of %s is not cleared from %s: %v", name, FloorWaitsState, err)