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.`. 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.`. 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 }