Files
mesh-catalog/modules/mesh-delivery/cmd/mesh-delivery/table.go
T
jschoubben 3d7f9a3e66
mesh/merge-gate pass: builds mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
mesh/delivery-group group feat/459-a-stop-re-walks-the-others delivered: every member is delivered
mesh-delivery: say a shared-module leave-out truly, hold back only what was stopped by an act, and say a held walk where it is read (review of #221)
A merge left out for sharing a module read as containing the stopped merge; the
safety net also held back merges stranded by their walk, which nobody stopped;
a held walk said why only in the log; and a stop of a merge in a batch did not
ask the batch.
2026-10-11 20:27:55 +02:00

675 lines
31 KiB
Go

package main
import (
"errors"
"fmt"
"strings"
"time"
)
// The state table (novox/hq ADR 0239 decision 2, to-be 47): every transition a delivery may take, each with
// its guard, compiled once. The code moves a delivery only through Apply, which finds the row for the
// delivery's state and the event, runs its guard and refuses anything else, naming what was asked. A test
// walks the table: every row it holds, and every pair it does not.
// State is where a delivery is.
type State string
// The states, in the order a delivery passes through them. None is a delivery not yet made: the rows from
// it are how one comes to exist.
const (
None State = ""
Proposed State = "proposed"
Checked State = "checked"
Ready State = "ready"
Rejected State = "rejected"
Published State = "published"
Delivering State = "delivering"
Held State = "held"
Delivered State = "delivered"
Failed State = "failed"
Superseded State = "superseded"
Stopped State = "stopped"
// Retired is history: a merge this owner heard only from the forge's word, long after it happened —
// replayed from the bus when it first held the seat. It reached the machines before this owner existed,
// and nothing is delivered, released or put back for it (novox/hq issue 313).
Retired State = "retired"
// Covered is a merge a later one that contains it delivered: every module it moves was sent to every machine
// running it by the finished walk of a later merge into the same trunk, whose own check passed and which the
// forge says contains this one. It is not walked itself, and it is no failure: its pull request says covered,
// as a success (novox/hq ADR 0270). A revert merged in between is still possible: covered says what was
// delivered, not what runs.
Covered State = "covered"
)
// AllStates are every state a delivery can be in, for the table's own test and its verb.
var AllStates = []State{Proposed, Checked, Ready, Rejected, Published, Delivering, Held, Delivered, Failed,
Superseded, Stopped, Retired, Covered}
// Final is whether nothing follows a state.
func (s State) Final() bool {
return s == Delivered || s == Failed || s == Superseded || s == Stopped || s == Retired || s == Covered
}
// Reached is whether a final state says the change reached the machines, or had nothing to reach them
// with: what a group counts as delivered.
func (s State) Reached() bool { return s == Delivered || s == Retired || s == Covered }
// Event is what moves a delivery.
type Event string
// The events. Observed ones are taken by settle whenever their guard holds; the others are acts — a
// person's, or healer H2's — taken only when asked.
const (
EvAnnounced Event = "announced" // the forge announced a pull request's head
EvAppeared Event = "appeared" // a trunk commit the mesh is delivering, with no pull request known
EvAdopted Event = "adopted" // a walk already running at the switch, or on the controller's own path
EvChecked Event = "checked" // its check's verdict arrived
EvAccepted Event = "accepted" // the verdict passes
EvRefused Event = "refused" // the verdict fails, or the check could not run
EvRecheck Event = "recheck" // a person asked for it again, or its group changed
EvReask Event = "re-ask" // healer H2 asked a check asked and not answered once more
EvNewHead Event = "new-head" // a newer head of the same pull request
EvClosed Event = "closed" // the pull request closed unmerged
EvMerged Event = "merged" // it reached the trunk
EvMergedUnchecked Event = "merged-unchecked" // it reached the trunk without a passing check
EvGo Event = "go" // its walk started
EvHold Event = "hold" // something holds it for a person
EvRelease Event = "release" // a person's word that it goes on
EvDone Event = "done" // every machine of its deploy plan took it, or nothing was to be sent
EvFailed Event = "failed" // its walk failed: a gate, a build, a machine
EvSuperseded Event = "superseded" // a newer delivery to the same trunk took over its walk
EvStop Event = "stop" // a person stopped it, or its group did
EvHistory Event = "history" // made from the forge's word for a merge long past: history
EvRetire Event = "retire" // a person's word that a held delivery is history, never to be walked
EvCovered Event = "covered" // a later merge into the same trunk delivered every module it moves
EvLifted Event = "lifted" // what held it for a person is no longer so: its group's composed check passed, or a walk now answers its merge
EvRewalk Event = "rewalk" // the walk that carried a later commit for it failed, or was stopped for another merge (ADR 0299): it waits for the walk that walks it again
)
// Facts are what a guard reads beyond the delivery itself: the moment, and who asks, for an act.
type Facts struct {
Now time.Time
By string
Why string
}
// Row is one transition the table holds.
type Row struct {
From []State
Event Event
To State
// Guard says, in words, what must be true; Holds checks it, answering why not.
Guard string
Holds func(d *Delivery, f Facts) error
// Act says the row is taken only when asked — a person's or H2's — never by settle.
Act bool
// Says is the transition's why when nobody gave one: the fact that moved it, rather than the guard.
Says func(d *Delivery) string
}
func unless(cond bool, why string) error {
if cond {
return nil
}
return fmt.Errorf("%s", why)
}
var notFinal = []State{Proposed, Checked, Ready, Rejected, Published, Delivering, Held}
// Table is every transition there is. The order within one state is the order settle tries them.
var Table = []Row{
{From: []State{None}, Event: EvAnnounced, To: Proposed,
Guard: "a pull request's head the forge announced",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Number > 0 && d.Commit != "", "no pull request's head")
}},
{From: []State{None}, Event: EvAppeared, To: Held,
Guard: "a commit on the trunk, its walk waiting, and no pull request known: it waits for a person",
Holds: func(d *Delivery, f Facts) error {
return unless(d.MergedAs != "" && d.HeldWhy != "", "no trunk commit, or nothing says why it is held")
}},
{From: []State{None}, Event: EvAdopted, To: Delivering,
Guard: "a walk already running: at the switch, or on the controller's own path",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Walk != nil && d.Walk.Started(), "no running walk")
}},
// History is taken before anything else of a delivery off the trunk: a merge long past, heard only from
// the forge's word, is neither checked, held nor walked (novox/hq issue 313).
{From: []State{Proposed, Checked, Ready, Rejected}, Event: EvHistory, To: Retired,
Guard: "history: made from the forge's word for a merge on the trunk heard " + historyLate.String() + " or more " +
"after it happened — replayed when this owner first held the seat — and no walk of it open",
Holds: func(d *Delivery, f Facts) error {
if d.History == "" {
return errors.New("nothing says it is history")
}
return isHistory(d)
}},
{From: []State{Held}, Event: EvRetire, To: Retired, Act: true,
Guard: "a person's word, with why, for history only: made from the forge's word for a merge on the trunk heard " +
historyLate.String() + " or more after it happened, and no walk of it open — never released or delivered",
Holds: func(d *Delivery, f Facts) error {
if f.Why == "" || f.By == "" {
return errors.New("a retire says who and why")
}
return isHistory(d)
}},
{From: []State{Proposed, Checked, Ready, Rejected}, Event: EvNewHead, To: Superseded,
Guard: "a newer head of the same pull request",
Holds: func(d *Delivery, f Facts) error { return unless(d.NewerHead != "", "no newer head") }},
{From: []State{Proposed, Checked, Ready, Rejected}, Event: EvClosed, To: Stopped,
Guard: "the pull request closed unmerged",
Holds: func(d *Delivery, f Facts) error { return unless(d.ClosedUnmerged, "the pull request is open") }},
{From: []State{Ready}, Event: EvMerged, To: Published,
Guard: "on the trunk its modules follow: merged there, and its walk opened — or nothing for a walk to move",
Holds: func(d *Delivery, f Facts) error {
return unless(d.MergedAs != "" && (d.Walk != nil || d.MovesNothing()), "not on the trunk yet")
}},
{From: []State{Proposed}, Event: EvChecked, To: Checked,
Guard: "the verdict names this commit",
Holds: func(d *Delivery, f Facts) error { return unless(d.Check != nil, "no verdict") }},
{From: []State{Checked}, Event: EvAccepted, To: Ready,
Guard: "the gate passed or warned, and the repository's own check did not fail",
Holds: func(d *Delivery, f Facts) error { return unless(d.Check.Passes(), "the verdict does not pass") }},
{From: []State{Checked}, Event: EvRefused, To: Rejected,
Guard: "the gate failed or could not run, or the repository's own check failed",
Holds: func(d *Delivery, f Facts) error { return unless(!d.Check.Passes(), "the verdict passes") }},
{From: []State{Proposed, Checked, Rejected, Held}, Event: EvDone, To: Delivered,
Guard: "merged, and its plan builds nothing for a walk to move — nothing at all, or only a new module the " +
"controller builds and sends nowhere: nothing to deliver and nothing for a person to decide, whatever its check said",
Holds: func(d *Delivery, f Facts) error {
return unless(d.MergedAs != "" && d.MovesNothing() && d.Walk == nil, "not merged, or a walk moves it")
},
Says: func(d *Delivery) string { return "merged, and nothing for a walk to move: " + d.Plan.Summary }},
{From: []State{Ready, Held}, Event: EvCovered, To: Covered,
Guard: "merged, no walk of its own open, and every module it moves sent to every machine running it by the " +
"finished walk of a later merge into the same trunk, whose own check passed and which the forge says contains " +
"it: a later merge that contains it was delivered",
Holds: func(d *Delivery, f Facts) error {
switch {
case d.MergedAs == "" || d.CoveredBy == "":
return errors.New("not merged, or nothing later carried its modules")
case d.Walk != nil && (d.Walk.State == walkBuilding || d.Walk.State == walkRolling):
return fmt.Errorf("its own walk %s is open: the controller delivers it", d.Walk.ID)
}
return nil
},
Says: func(d *Delivery) string {
return "covered: a later merge that contains it was delivered: " + d.CoveredBy
}},
{From: []State{Proposed, Checked, Rejected}, Event: EvMergedUnchecked, To: Held,
Guard: "merged without a passing check — a verdict known with it is taken first: only a person decides that it goes on",
Holds: func(d *Delivery, f Facts) error { return unless(d.MergedAs != "", "not merged") }},
{From: []State{Rejected, Ready}, Event: EvRecheck, To: Proposed, Act: true,
Guard: "a person asked, with why, or its group changed",
Holds: func(d *Delivery, f Facts) error { return unless(f.Why != "", "a recheck says why") }},
{From: []State{Proposed}, Event: EvReask, To: Proposed, Act: true,
Guard: "its check was asked and no verdict came within its bound, and it was not re-asked before: healer H2 " +
"asks it once more, with why — after that it is the operator's",
Holds: func(d *Delivery, f Facts) error {
switch {
case f.Why == "" || f.By == "":
return errors.New("a re-ask says who and why")
case d.Check != nil:
return errors.New("its verdict is known")
case d.CheckAsk == nil:
return errors.New("its check was never asked: the tick asks it, not H2")
case d.CheckAsk.Reasked > 0:
return errors.New("its check was re-asked once already: the operator's")
}
return nil
}},
{From: []State{Published}, Event: EvSuperseded, To: Superseded,
Guard: "its walk was superseded and names no walk that took it over: a walk that took it over answers its merge, and it follows that walk (novox/hq ADR 0276)",
Holds: func(d *Delivery, f Facts) error {
if d.Walk != nil && d.Walk.State == walkSuperseded && d.Walk.TakenOverBy != "" {
return fmt.Errorf("its walk was taken over by %s, which answers its merge: it follows that walk", d.Walk.TakenOverBy)
}
return unless(d.Walk != nil && d.Walk.State == walkSuperseded, "its walk is not superseded")
}},
{From: []State{Published}, Event: EvDone, To: Delivered,
Guard: "nothing for a walk to move; or its walk, carrying its own commit, ended done before its word having built " +
"every module it moves (novox/hq issue 474)",
Holds: func(d *Delivery, f Facts) error {
if d.Walk == nil && d.MovesNothing() {
return nil
}
moved, _ := d.walkMovedItsModules()
return unless(d.endedUnstarted() && !d.carriedLater() && moved, "a walk moves it")
},
Says: func(d *Delivery) string {
if d.Walk == nil {
return "nothing for a walk to move"
}
return fmt.Sprintf("its walk %s ended done before its word, having built every module it moves", d.Walk.ID)
}},
{From: []State{Published, Ready}, Event: EvHold, To: Held,
Guard: "something holds it for a person: its group's composed check did not pass for the heads that merged, " +
"or it merged and no walk was opened for it",
Holds: func(d *Delivery, f Facts) error {
return unless(d.HeldWhy != "" && d.MergedAs != "", "nothing holds it, or it is not on the trunk")
}},
{From: []State{Published}, Event: EvGo, To: Delivering,
Guard: "its walk started: let go by this owner in its group's order, by a person, or on the controller's own path " +
"— not a failed walk that carried a later commit for it, after which it waits for a walk of its own commit",
Holds: func(d *Delivery, f Facts) error {
if d.Walk != nil && d.Walk.State == walkFailed && d.carriedLater() {
return errors.New("its walk, which carried a later commit for it, failed: it waits for a walk of its own commit")
}
if d.walkStopped() {
return errors.New("its walk was stopped: it waits for the walk that walks its merge again, or ends stopped")
}
return unless(d.Walk != nil && d.Walk.Started(), "its walk waits")
}},
// **A walk that ended before its word settles its delivery** (novox/hq issue 474): a batch's walk the
// controller judged to move nothing it holds is done when it is cut, and a merge it carried was left published,
// waiting for a start that cannot come. Covered when that walk, carrying a later merge for it, built its modules,
// or when a later delivered merge carried them (ADR 0270's covered test); stopped, saying why, when neither.
{From: []State{Published}, Event: EvCovered, To: Covered,
Guard: "its walk ended done before its word, and its modules were delivered by a later merge that contains it: " +
"that walk, carrying it, built them, or a later merge's finished walk sent them everywhere (ADR 0270)",
Holds: func(d *Delivery, f Facts) error {
if !d.endedUnstarted() {
return errors.New("its walk has not ended before its word")
}
moved, _ := d.walkMovedItsModules()
return unless(d.carriedLater() && moved || d.MergedAs != "" && d.CoveredBy != "",
"nothing later delivered its modules")
},
Says: func(d *Delivery) string {
if moved, _ := d.walkMovedItsModules(); d.carriedLater() && moved {
return "covered: a later merge that contains it was delivered: " + d.Carrier
}
return "covered: a later merge that contains it was delivered: " + d.CoveredBy
}},
{From: []State{Published}, Event: EvStop, To: Stopped,
Guard: "its walk ended done before its word without building every module it moves, and no later merge that " +
"contains it delivered them: no start can come, and nothing delivered it (novox/hq issue 474); or the walk " +
"it waits on was stopped through this owner and walks its merge again nowhere — left out, or the walk " +
"ended whole (novox/hq ADR 0299)",
Holds: func(d *Delivery, f Facts) error {
if d.walkStopped() && !d.walkedAgain() {
return nil
}
return unless(d.endedUnstarted() && d.Stranded != "", "its walk has not ended unstarted, or something may cover it")
},
Says: func(d *Delivery) string {
if d.walkStopped() {
return d.stoppedWords()
}
return d.Stranded
}},
{From: []State{Held}, Event: EvGo, To: Delivering,
Guard: "its walk started on a word that was not this owner's: the controller's own path, which waits for " +
"nobody, or a person's `plans go`",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Walk != nil && d.Walk.Started() && (!d.Walk.Waited() || d.Walk.LetGoByAPerson()),
"its walk was not started by a person or the controller's own path")
}},
{From: []State{Held}, Event: EvLifted, To: Published,
Guard: "held only for its group's composed check, which has since passed or warned for the heads that merged, or " +
"held because no walk answered its merge, and one now does: it is published again and waits for its turn",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Lifted != "" && d.HeldWhy == "" && d.MergedAs != "" && walkCanGoOn(d), "nothing lifted its hold")
},
Says: func(d *Delivery) string { return "lifted: " + d.Lifted }},
{From: []State{Held}, Event: EvRelease, To: Delivering, Act: true,
Guard: "a person's decision, with why",
Holds: func(d *Delivery, f Facts) error {
return unless(f.Why != "" && f.By != "", "a release is a person's word, with why")
}},
{From: []State{Delivering}, Event: EvSuperseded, To: Superseded,
Guard: "its walk was superseded and names no walk that took it over: a walk that took it over answers its merge, and it follows that walk (novox/hq ADR 0276)",
Holds: func(d *Delivery, f Facts) error {
if d.Walk != nil && d.Walk.State == walkSuperseded && d.Walk.TakenOverBy != "" {
return fmt.Errorf("its walk was taken over by %s, which answers its merge: it follows that walk", d.Walk.TakenOverBy)
}
return unless(d.Walk != nil && d.Walk.State == walkSuperseded, "its walk is not superseded")
}},
{From: []State{Delivering}, Event: EvDone, To: Delivered,
Guard: "its walk is done, carrying its own commit: every machine of its deploy plan passed, or was left as its policy says",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Walk != nil && d.Walk.State == walkDone && !d.carriedLater(), "its walk is not done, or carried a later commit for it")
}},
{From: []State{Delivering}, Event: EvCovered, To: Covered,
Guard: "its walk is done, carrying a later merge of its branch that contains its merge: answered by its successor in " +
"one walk (novox/hq ADR 0276)",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Walk != nil && d.Walk.State == walkDone && d.carriedLater(), "its walk is not done, or carried its own commit")
},
Says: func(d *Delivery) string {
return "covered: a later merge that contains it was delivered: " + d.Carrier
}},
{From: []State{Delivering}, Event: EvStop, To: Stopped,
Guard: "its walk was stopped through this owner, and walks its merge again nowhere: left out, or the walk ended " +
"whole (novox/hq ADR 0299)",
Holds: func(d *Delivery, f Facts) error {
if d.walkedAgain() {
return fmt.Errorf("its walk was stopped without it, and %s walks its merge again", d.Walk.WalkedAgainBy)
}
return unless(d.walkStopped(), "its walk was not stopped")
},
Says: func(d *Delivery) string { return d.stoppedWords() }},
{From: []State{Delivering}, Event: EvFailed, To: Failed,
Guard: "its walk failed, carrying its own commit: a gate on a first machine (what it carried put back), a build, a machine",
Holds: func(d *Delivery, f Facts) error {
return unless(d.Walk != nil && d.Walk.State == walkFailed && d.Walk.StoppedBy == "" && !d.carriedLater(),
"its walk did not fail, or carried a later commit for it")
}},
{From: []State{Delivering}, Event: EvRewalk, To: Published,
Guard: "its walk failed carrying a later merge of its branch for it: a failed walk marks no merge delivered, and this " +
"merge waits, published, for a walk of its own commit (novox/hq ADR 0276 decision 3); or its walk was stopped " +
"for another merge, and its merge is walked again without that one (novox/hq ADR 0299)",
Holds: func(d *Delivery, f Facts) error {
if d.walkedAgain() {
return nil
}
return unless(d.Walk != nil && d.Walk.State == walkFailed && d.Walk.StoppedBy == "" && d.carriedLater(),
"its walk did not fail, or carried its own commit")
},
Says: func(d *Delivery) string {
if d.walkedAgain() {
return fmt.Sprintf("its walk %s was stopped for another merge (%s): %s walks its merge again without that one",
d.Walk.ID, d.Walk.StoppedWhy, d.Walk.WalkedAgainBy)
}
return fmt.Sprintf("its walk %s, which carried %s for it, failed: it waits for a walk of its own commit",
d.Walk.ID, shortOf(d.AnsweredBy, 12))
}},
{From: notFinal, Event: EvStop, To: Stopped, Act: true,
Guard: "a person, with why, or its group stopped by a member before it",
Holds: func(d *Delivery, f Facts) error { return unless(f.Why != "" && f.By != "", "a stop says who and why") }},
}
// walkCanGoOn is whether a delivery published again would be delivered by what it has: its own walk still
// open, or nothing for a walk to move.
func walkCanGoOn(d *Delivery) bool {
if d.Walk == nil {
return d.MovesNothing()
}
return d.Walk.State == walkBuilding || d.Walk.State == walkRolling
}
// historyLate is how long after its merge a merge heard only from the forge's word is history: the forge
// announces a merge within seconds, so one heard this late was replayed from the bus's history.
const historyLate = 15 * time.Minute
// isHistory is whether a delivery is history, answering why not: made from the forge's word at its merge, on
// the trunk, its merge heard historyLate or more after it happened, and no walk of it open or waiting — a
// walk the controller keeps for it is a delivery, never history.
func isHistory(d *Delivery) error {
switch {
case d.MergedAs == "":
return errors.New("it is not on the trunk")
case !d.FromForgesWord():
return errors.New("this owner heard its head: it is not history made from the forge's word")
case d.MergedAt.IsZero() || d.Created.Sub(d.MergedAt) < historyLate:
return fmt.Errorf("its merge was heard %s after it happened, within %s: a live merge, not history",
d.Created.Sub(d.MergedAt).Round(time.Second), historyLate)
case d.Walk != nil && (d.Walk.Waits || d.Walk.State == walkBuilding || d.Walk.State == walkRolling):
return fmt.Errorf("its walk %s is open: the controller delivers it", d.Walk.ID)
}
return nil
}
// rowFor is the row a state and event name; nil when the table holds none.
func rowFor(from State, ev Event, act bool) *Row {
for i := range Table {
r := &Table[i]
if r.Event != ev || r.Act != act {
continue
}
for _, s := range r.From {
if s == from {
return r
}
}
}
return nil
}
// ErrRefused is a transition the table does not hold, or whose guard does not.
type ErrRefused struct {
ID string
From State
Event Event
Why string
}
func (e ErrRefused) Error() string {
from := string(e.From)
if from == "" {
from = "nothing"
}
return fmt.Sprintf("%s: %s from %s is refused — %s", e.ID, e.Event, from, e.Why)
}
// Apply takes one transition, or refuses it. The delivery keeps the transition, bounded.
func Apply(d *Delivery, ev Event, act bool, f Facts) (Transition, error) {
r := rowFor(d.State, ev, act)
if r == nil {
kind := "observed"
if act {
kind = "asked"
}
return Transition{}, ErrRefused{ID: d.ID, From: d.State, Event: ev,
Why: "the table holds no such transition (" + kind + ")"}
}
if err := r.Holds(d, f); err != nil {
return Transition{}, ErrRefused{ID: d.ID, From: d.State, Event: ev, Why: "its guard does not hold: " + err.Error()}
}
why := f.Why
if why == "" && r.Says != nil {
why = r.Says(d)
}
if why == "" {
why = r.Guard
}
t := Transition{At: f.Now, From: d.State, To: r.To, Event: ev, Why: why, By: f.By}
d.State, d.Since = r.To, f.Now
d.Transitions = append(d.Transitions, t)
if len(d.Transitions) > keptTransitions {
d.Transitions = d.Transitions[len(d.Transitions)-keptTransitions:]
}
return t, nil
}
// keptTransitions is how many transitions a delivery keeps; its note on the commit keeps every one.
const keptTransitions = 100
// settle takes every observed transition whose guard holds, in the table's order, until none does — so a
// delivery always stands where its facts put it, whatever order they arrived in.
func settle(d *Delivery, f Facts) []Transition {
var taken []Transition
for range len(Table) + 1 {
moved := false
for i := range Table {
r := &Table[i]
if r.Act || !contains(r.From, d.State) {
continue
}
if t, err := Apply(d, r.Event, false, f); err == nil {
taken = append(taken, t)
moved = true
break
}
}
if !moved {
break
}
}
return taken
}
func contains(states []State, s State) bool {
for _, x := range states {
if x == s {
return true
}
}
return false
}
// Bounds are how long a delivery may be in a state before `stalled` lists it, and what healer H2 may do
// then — always a transition the table holds, never one of its own (novox/hq ADR 0239 decision 9).
type Bound struct {
For time.Duration
// H2 is the transitions H2 may take after the bound, by event; empty when the state is the operator's.
H2 []Event
Says string
}
// Bounds are every state's.
var Bounds = map[State]Bound{
Proposed: {For: time.Hour, H2: []Event{EvReask}, Says: "its check was asked and not answered: H2 asks it once " +
"more, then the check's own watchdog (S6) and the operator speak for it"},
Checked: {For: time.Minute, Says: "a verdict is decided at once"},
Published: {For: 30 * time.Minute, H2: []Event{EvSuperseded, EvGo}, Says: "its walk waits for its turn or its word"},
Held: {For: 24 * time.Hour, Says: "it waits for the operator"},
Delivering: {For: 2 * time.Hour, H2: []Event{EvDone, EvSuperseded, EvFailed}, Says: "its walk runs"},
}
// checkGrace is how long a proposed delivery is left to the controller's own check of its announcement before
// this owner asks the check itself: longer than a check takes from announcement to verdict. A head adopted while
// it already waited — announced again from the bus's history — is asked once it has waited this long too.
const checkGrace = 15 * time.Minute
// boundSince is when a delivery's bound runs from: its state's start, except for a proposed delivery whose
// check this owner asked — its bound is "asked and not answered", so it runs from the ask. One never asked
// runs from its start plus the grace before it is asked: an ask that cannot be made is still listed.
func boundSince(d *Delivery) time.Time {
if d.State == Proposed {
if d.CheckAsk != nil {
return d.CheckAsk.At
}
return d.Since.Add(checkGrace)
}
return d.Since
}
// heldUnwalked is the bound of a delivery held because no walk answers its merge (novox/hq issue 362): none. It
// waited the ten minutes a walk opens within already, and nothing but a person moves it now.
var heldUnwalked = Bound{For: 0, Says: "merged, and no walk answers its merge: the operator's"}
// rewalkBound is the bound of a merge whose walk carried a later commit for it and failed: a walk of its own
// commit opens within the ten minutes a walk opens within (ADR 0276 decision 3), or nothing but a person moves it.
var rewalkBound = Bound{For: unwalked, Says: "the walk that carried a later commit for it failed, and no walk of " +
"its own commit opened: the operator's"}
// boundOf is a delivery's bound: its state's, or none for a delivery held because no walk answers it.
func boundOf(d *Delivery) (Bound, bool) {
if d.State == Held && d.Unwalked && d.Walk == nil {
return heldUnwalked, true
}
if d.waitsForItsOwnWalk() {
return rewalkBound, true
}
if (d.State == Published || d.State == Delivering) && d.HeldBack != "" {
// Its walk carries a stopped merge and is not let go (ADR 0299): said with why, past the bound of its state.
b := Bounds[d.State]
b.For, b.Says = Bounds[Published].For, d.HeldBack // a walk not let go waits for a word, whatever its state
return b, true
}
if d.State == Published && d.TurnAwaits != "" && d.TurnAwaitsUntil != nil {
// Its turn waits for a later merge to carry a member before it: bounded by that carrier's own bound, and
// said with it, never the generic "waits for its turn or its word" (novox/hq issue 445).
b := Bounds[Published]
b.For, b.Says = max(b.For, d.TurnAwaitsUntil.Sub(boundSince(d))), d.TurnAwaits
return b, true
}
b, bounded := Bounds[d.State]
return b, bounded
}
// pastBound is whether a delivery has been where it is longer than its state's bound.
func pastBound(d *Delivery, now time.Time) bool {
b, bounded := boundOf(d)
return bounded && !d.State.Final() && (b.For == 0 || now.Sub(boundSince(d)) > b.For)
}
// StepState is where one machine stands in a delivering walk.
type StepState string
// The machine steps (ADR 0239 decision 2): sent, judged, passed or failed, and put back.
const (
StepSent StepState = "sent"
StepJudging StepState = "judging"
StepPassed StepState = "passed"
StepFailed StepState = "failed"
StepRolledBack StepState = "rolled-back"
)
// StepTable is every move a machine's step may make. From "" is a step first heard of.
var StepTable = map[StepState][]StepState{
"": {StepSent, StepJudging, StepPassed, StepFailed, StepRolledBack},
StepSent: {StepJudging, StepPassed, StepFailed},
StepJudging: {StepPassed, StepFailed},
StepFailed: {StepRolledBack},
}
// StepMay is whether a step may move from one state to another: directly, or through states the walk's
// record passed between two readings of it (sent, judged and failed and put back, read once).
func StepMay(from, to StepState) bool {
if from == to {
return true
}
seen := map[StepState]bool{}
next := []StepState{from}
for len(next) > 0 {
s := next[0]
next = next[1:]
for _, t := range StepTable[s] {
if t == to {
return true
}
if !seen[t] {
seen[t] = true
next = append(next, t)
}
}
}
return false
}
// TableText is the table as the `table` verb answers it.
func TableText() map[string]any {
var rows []map[string]any
for _, r := range Table {
from := make([]string, 0, len(r.From))
for _, s := range r.From {
if s == None {
from = append(from, "(none)")
continue
}
from = append(from, string(s))
}
kind := "observed"
if r.Act {
kind = "asked"
}
rows = append(rows, map[string]any{"from": strings.Join(from, ", "), "event": r.Event, "to": r.To,
"guard": r.Guard, "taken": kind})
}
bounds := map[string]any{}
for s, b := range Bounds {
var h2 []string
for _, e := range b.H2 {
h2 = append(h2, string(e))
}
bounds[string(s)] = map[string]any{"bound": b.For.String(), "h2": h2, "says": b.Says}
}
steps := map[string][]StepState{}
for from, to := range StepTable {
name := string(from)
if name == "" {
name = "(first heard)"
}
steps[name] = to
}
return map[string]any{"transitions": rows, "bounds": bounds, "machine-steps": steps}
}