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
108 lines
4.5 KiB
Go
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
|
|
}
|