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:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user