Files
mesh-delivery/modules/mesh-delivery-builder/internal/versions/publish.go
T
jschoubben 4dbb6b2aec 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
2026-11-04 16:46:32 +01:00

108 lines
4.5 KiB
Go

package versions
import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"
)
// **A publish that ends badly is loud** (design 51 *Health and conditions*; review round 1): the builder raises the
// finding publish-refused when a holder says refused, and publish-unanswered when a version it built and numbered is
// neither published nor refused within PublishBound. Each is about the name, so a later published of any version of it
// clears both. A seat's outcome that does not read is the finding seat-outcome-unread about the seat, cleared by its
// next outcome that reads.
//
// The bound is a key of the builder's state bounds per version (PublishBoundKey), written with its own time to live
// when the build is done and the version is still numbered, deleted at the holder's word; the bus's marker of its end,
// heard on the watch, is what raises publish-unanswered while the version is still numbered. No timer.
const (
// BoundsState is the builder's state of its expiring keys, each with its own time to live: a publish's bound
// (PublishBoundKey) and a node's seven days holding the old form's window (OldFormBoundKey). Its end, the bus's
// marker heard on the watch, is what acts.
BoundsState = "bounds"
BoundsBucket = "mesh-delivery-builder_bounds"
// PublishBound is the build module's own wait for the holder's word (design 51 *How a publish ends*: at most
// 10 minutes), so a version unanswered past it is one the holder never answered.
PublishBound = 10 * time.Minute
PublishRefused = "publish-refused"
PublishUnanswered = "publish-unanswered"
SeatOutcomeUnread = "seat-outcome-unread"
)
// The prefixes of the keys of BoundsState.
const (
publishBoundPrefix = "publish."
oldFormBoundPrefix = "old-form."
)
// PublishBoundKey is the bound of name's version: `publish.<versions key>`.
func PublishBoundKey(name, version string) string { return publishBoundPrefix + Key(name, version) }
// OldFormBoundKey is the bound of node's seven days holding the old form's window: `old-form.<node>`.
func OldFormBoundKey(node string) string { return oldFormBoundPrefix + EscapeToken(node) }
// PublishBoundOf is the versions key a publish's bound names, and whether key is one; a key that is no publish's
// bound is an old form's (IsOldFormBound) or nothing the builder wrote.
func PublishBoundOf(key string) (string, bool) { return strings.CutPrefix(key, publishBoundPrefix) }
// 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
}