From 4dbb6b2aec1b915eae6e3905c8f19a11b1032c3e Mon Sep 17 00:00:00 2001 From: jochen Date: Wed, 4 Nov 2026 16:46:32 +0100 Subject: [PATCH] F-delivery: builder_bounds reads the builder's state bounds Every state a module keeps has a read tool (the lead's ruling). Each key of bounds now holds what it bounds and when it ends, and builder_bounds lists them with the time left. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig --- .../cmd/mesh-delivery-builder-tools/main.go | 15 +++++ .../cmd/mesh-delivery-builder/oldform.go | 2 +- .../mesh-delivery-builder/publishbounds.go | 2 +- .../internal/versions/publish.go | 57 +++++++++++++++++++ .../internal/versions/versions_test.go | 10 ++++ .../mesh-delivery-builder/manifest_test.go | 2 +- modules/mesh-delivery-builder/module.json | 1 + 7 files changed, 86 insertions(+), 3 deletions(-) diff --git a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder-tools/main.go b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder-tools/main.go index 3612b747..ef26ec2f 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder-tools/main.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder-tools/main.go @@ -398,7 +398,22 @@ func main() { } return e.Value, true, nil }} + bs := stdio.State(versions.BoundsState) + br := versions.Reader{Keys: bs.Keys, Get: func(key string) (json.RawMessage, bool, error) { + e, err := bs.Get(key) + if err != nil || e == nil { + return nil, false, err + } + return e.Value, true, nil + }} tools := Tools(r, put, askQueue(stdio.State(build.QueuesState), 25*time.Second), buildAgain, stdio.Log(delivery.BuilderLog).Since, time.Now, vr, tags, tr, fr, ofr) + // The builder's expiring keys (state bounds): what each bounds and when it ends. + tools = append(tools, stdio.Tool{Name: versions.BoundsTool, Description: versions.BoundsDescription, + Input: map[string]any{"type": "object", "properties": map[string]any{}}, + Run: func(map[string]any) (any, error) { + rows, err := versions.AnswerBounds(br, time.Now()) + return map[string]any{"bounds": rows}, err + }}) // What each counted node's engine reads, as the builder keeps it (ADR 0618 §2, ADR 0624 §1): listed, one node asked // again, or one node's key dropped. tools = append(tools, enginereads.Tool(EngineReadsTool, "the builder", enginereads.KeptState(stdio.State("engine-reads")), true, time.Now)) 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 24d6c32a..20dfb4e5 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/oldform.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/oldform.go @@ -153,7 +153,7 @@ func (o *oldForm) keepBounds(ctx context.Context, before, next versions.OldFormW if left < time.Second { left = time.Second // the bus's least time to live } - if _, err := kv.Create(ctx, versions.OldFormBoundKey(n), []byte(`{}`), jetstream.KeyTTL(left)); err != nil && !errors.Is(err, jetstream.ErrKeyExists) { + if _, err := kv.Create(ctx, versions.OldFormBoundKey(n), versions.EncodeBound("the seven days "+n+" holds the old form's window", o.now().Add(left)), jetstream.KeyTTL(left)); err != nil && !errors.Is(err, jetstream.ErrKeyExists) { o.logf("the old form's bound of %s is not written, so its seven days are seen at the next change only: %v", n, err) } } 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 313f31a1..7f4340df 100644 --- a/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/publishbounds.go +++ b/modules/mesh-delivery-builder/cmd/mesh-delivery-builder/publishbounds.go @@ -31,7 +31,7 @@ func (p *publishes) arm(ctx context.Context, e versions.Entry) { p.logf("the publish bound of %s@%s is not written (%s is not on the bus yet), so it is not raised if no holder answers", e.Name, e.Version, versions.BoundsBucket) return } - if _, err := kv.Create(ctx, versions.PublishBoundKey(e.Name, e.Version), []byte(`{}`), jetstream.KeyTTL(p.bound)); err != nil && !errors.Is(err, jetstream.ErrKeyExists) { + if _, err := kv.Create(ctx, versions.PublishBoundKey(e.Name, e.Version), versions.EncodeBound("the publish of "+e.Name+"@"+e.Version+" to "+e.Seat, time.Now().Add(p.bound)), jetstream.KeyTTL(p.bound)); err != nil && !errors.Is(err, jetstream.ErrKeyExists) { p.logf("the publish bound of %s@%s is not written, so it is not raised if no holder answers: %v", e.Name, e.Version, err) } } diff --git a/modules/mesh-delivery-builder/internal/versions/publish.go b/modules/mesh-delivery-builder/internal/versions/publish.go index 1458bf01..d185f6bf 100644 --- a/modules/mesh-delivery-builder/internal/versions/publish.go +++ b/modules/mesh-delivery-builder/internal/versions/publish.go @@ -1,6 +1,9 @@ package versions import ( + "encoding/json" + "fmt" + "sort" "strings" "time" ) @@ -48,3 +51,57 @@ func PublishBoundOf(key string) (string, bool) { return strings.CutPrefix(key, p // IsOldFormBound says key is a node's bound of the old form's window: its end steps the window, which raises the // finding for every node past FindingAfter. func IsOldFormBound(key string) bool { return strings.HasPrefix(key, oldFormBoundPrefix) } + +// BoundsTool is builder_bounds: every key of BoundsState, what it bounds and when it ends. +const BoundsTool = "builder_bounds" + +// BoundsDescription is builder_bounds' description. +const BoundsDescription = "The builder's expiring keys, its state " + BoundsState + " (design 51 *Health and conditions*): each key with what it " + + "bounds (a version's publish, whose end unanswered is the finding " + PublishUnanswered + "; or a node's seven days holding the old " + + "form's window, whose end is the finding " + OldFormHeld + "), when it ends, and how long is left. The bus ends each key by its own time to live." + +// Bound is a key's value in BoundsState: what it bounds, in plain words, and when it ends. +type Bound struct { + Bounds string `json:"bounds"` + Ends time.Time `json:"ends"` +} + +// EncodeBound is a Bound's bytes. +func EncodeBound(what string, ends time.Time) []byte { + raw, _ := json.Marshal(Bound{Bounds: what, Ends: ends.UTC()}) + return raw +} + +// BoundRow is one row of builder_bounds. +type BoundRow struct { + Key string `json:"key"` + Bounds string `json:"bounds"` + Ends time.Time `json:"ends,omitzero"` + Left string `json:"left,omitempty"` +} + +// AnswerBounds is builder_bounds: every key, sorted, with what it bounds and when it ends; a value that does not read +// says so in bounds. +func AnswerBounds(r Reader, now time.Time) ([]BoundRow, error) { + keys, err := r.Keys() + if err != nil { + return nil, fmt.Errorf("the state %s cannot be listed: %w", BoundsState, err) + } + sort.Strings(keys) + out := []BoundRow{} + for _, k := range keys { + raw, ok, err := r.Get(k) + if err != nil || !ok { + continue + } + var b Bound + row := BoundRow{Key: k} + if json.Unmarshal(raw, &b) != nil || b.Bounds == "" { + row.Bounds = "a value that does not read: " + string(raw) + } else { + row.Bounds, row.Ends, row.Left = b.Bounds, b.Ends, b.Ends.Sub(now).Round(time.Second).String() + } + out = append(out, row) + } + return out, nil +} diff --git a/modules/mesh-delivery-builder/internal/versions/versions_test.go b/modules/mesh-delivery-builder/internal/versions/versions_test.go index 160207ee..ac973f59 100644 --- a/modules/mesh-delivery-builder/internal/versions/versions_test.go +++ b/modules/mesh-delivery-builder/internal/versions/versions_test.go @@ -308,6 +308,16 @@ func TestTheBuildersVersions(t *testing.T) { if _, ok := kept[FloorWaitKey("m/held")]; ok { t.Error("an answered floor stays in floor-waits") } + // The lead's ruling (review round 1): builder_bounds lists each key of the state bounds with what it bounds and + // when it ends. + at := time.Date(2026, 11, 4, 17, 0, 0, 0, time.UTC) + bounds := keeperMem{PublishBoundKey("m/program", "0.1.4"): EncodeBound("the publish of m/program@0.1.4 to oci-artifact-registry", at.Add(PublishBound)), + OldFormBoundKey("g14"): EncodeBound("the seven days g14 holds the old form's window", at.Add(FindingAfter))} + rows, err := AnswerBounds(Reader{Keys: bounds.keys, Get: bounds.get}, at) + if err != nil || len(rows) != 2 || rows[0].Key != OldFormBoundKey("g14") || rows[1].Left != "10m0s" || + !strings.Contains(rows[1].Bounds, "m/program@0.1.4") || !rows[0].Ends.Equal(at.Add(FindingAfter)) { + t.Fatalf("builder_bounds = %+v, %v", rows, err) + } }) t.Run("old form", func(t *testing.T) { // One kind, a first form and a newer one. diff --git a/modules/mesh-delivery-builder/manifest_test.go b/modules/mesh-delivery-builder/manifest_test.go index 6fd75c9a..73b47afd 100644 --- a/modules/mesh-delivery-builder/manifest_test.go +++ b/modules/mesh-delivery-builder/manifest_test.go @@ -147,7 +147,7 @@ func TestManifestDeclarations(t *testing.T) { // 1607/AC1 t.Errorf("secrets-owner = %q", m.SecretsOwner) } // 1945: the builder's tools (builder_seat_queue: note 62), served by its tools bundle, and the event it says when it asks an ask again. - if want := []string{"builder_announce_again", "builder_ask_again", "builder_build_again", "builder_builds_since", "builder_engine_reads", "builder_first_view_again", "builder_first_views", + if want := []string{"builder_announce_again", "builder_ask_again", "builder_bounds", "builder_build_again", "builder_builds_since", "builder_engine_reads", "builder_first_view_again", "builder_first_views", "builder_floor_again", "builder_floor_waits", "builder_old_form", "builder_publish_again", "builder_seat_queue", "builder_stuck", "builder_tag", "builder_tag_asks", "builder_untag", "builder_versions"}; !reflect.DeepEqual(sorted(m.Tools), want) { t.Errorf("tools = %v, want %v", m.Tools, want) } diff --git a/modules/mesh-delivery-builder/module.json b/modules/mesh-delivery-builder/module.json index 2533dda8..5420e469 100644 --- a/modules/mesh-delivery-builder/module.json +++ b/modules/mesh-delivery-builder/module.json @@ -5,6 +5,7 @@ "tools": [ "builder_announce_again", "builder_ask_again", + "builder_bounds", "builder_build_again", "builder_builds_since", "builder_engine_reads",