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
This commit is contained in:
2026-11-04 16:46:32 +01:00
parent f9adc9d44c
commit 4dbb6b2aec
7 changed files with 86 additions and 3 deletions
@@ -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))
@@ -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)
}
}
@@ -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)
}
}
@@ -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
}
@@ -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.
@@ -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)
}
@@ -5,6 +5,7 @@
"tools": [
"builder_announce_again",
"builder_ask_again",
"builder_bounds",
"builder_build_again",
"builder_builds_since",
"builder_engine_reads",