Files
mesh-catalog/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/model.go
T
jschoubben 5bc9578634 Move source.main for a merge heard only from the forge, so the checker judges on the trunk that is (issue 888)
The checker reads the trunk only from the gate's source.main, and the gate moved it only in finish, for a merge it handed to
the forge bridge on an Approve. A pull request merged at the forge directly moved every other delivery's trunk (trunkMoved)
and left source.main behind, so the checker kept judging candidates on the trunk before it (catalogue 89e15c7b after
6c0024e4, 2026-10-23).

settleForgeMerges now writes such a merge into source.main by compare-and-set, never a merge replayed as history or older
than one of its repository already heard; an approved delivery merged at the forge before the gate handed it is moved by
finish with its group, once.
2026-10-23 12:23:40 +02:00

1131 lines
51 KiB
Go

package main
import (
"encoding/json"
"fmt"
"maps"
"regexp"
"sort"
"strings"
"time"
)
// What mesh-delivery owns (novox/hq ADR 0239): the delivery — one commit in one repository — and the
// delivery group above it. Each is kept whole, one key of the module's own state per delivery and per
// group, written only by this holder.
// Delivery is one commit in one repository, from its pull request's head to every machine.
type Delivery struct {
// ID is owner/repository@<twelve characters of its commit>.
ID string `json:"id"`
Repository string `json:"repository"`
Commit string `json:"commit"`
// The pull request, when there is one: its number, title, base, head branch and page.
Number int `json:"number,omitempty"`
Title string `json:"title,omitempty"`
Base string `json:"base,omitempty"`
Branch string `json:"branch,omitempty"`
HTMLURL string `json:"html_url,omitempty"`
CloneURL string `json:"clone_url,omitempty"`
// The diffset, as the forge said it: what the planner maps onto modules.
Paths []string `json:"paths,omitempty"`
PathsTruncated bool `json:"paths_truncated,omitempty"`
Removed []string `json:"removed,omitempty"`
ModuleDirs []string `json:"module_dirs,omitempty"`
ModuleDirsSaid bool `json:"module_dirs_said,omitempty"`
// After are the repositories its pull request's description names in `after: <repository>` lines. They are not read
// (novox/hq ADR 0419 §2): kept only to say once on the pull request that the line is not read. AfterSaid is whether
// that was said, for this pull request.
After []string `json:"after,omitempty"`
AfterSaid bool `json:"after_said,omitempty"`
// Comments is how many comments this owner owed on the pull request at this head, which numbers the next.
Comments int `json:"comments,omitempty"`
// Issues are the issues its title and body name ("issue 948"), shown to the operator who is asked to approve its merge.
Issues []int `json:"issues,omitempty"`
// Group is the delivery group it is in: its head branch name. Empty for a lone delivery.
Group string `json:"group,omitempty"`
State State `json:"state"`
Since time.Time `json:"since"`
Created time.Time `json:"created"`
// Plan is its delivery plan, as the controller's planner computed it (the build plan, the deploy plan,
// the steps that are not an ordinary send).
Plan *DeliveryPlan `json:"plan,omitempty"`
// Check is its own check's verdict.
Check *Verdict `json:"check,omitempty"`
// Required are the statuses its base requires, as the forge last said them (gitea_open_pulls): mesh/check makes it
// the checker's (issue 1302). Empty until the forge said them.
Required []string `json:"required,omitempty"`
// CheckState is its head's mesh/check as the forge last said it (pending, success, failure, error): a pending one
// after a pass is the checker judging it again, and no Approve is asked meanwhile (issue 1302).
CheckState string `json:"check_state,omitempty"`
// CheckAsk is this owner's own ask of its check, when the forge's announcement did not bring one: a head
// adopted while it already waited, or one whose announcement the controller missed.
CheckAsk *CheckAsk `json:"check_ask,omitempty"`
// TrunkNow is its trunk's head as this owner last heard it move while it was open: the commit the last merge
// into its trunk landed as (novox/hq ADR 0323 §7). A verdict that names another trunk commit is out of date.
// Empty until a merge into its trunk is heard after it was made.
TrunkNow string `json:"trunk_now,omitempty"`
// BaseSHA is the base branch's head the forge last announced its head with (ADR 0323): sent with a check this
// owner asks, for a verdict no build judges to name its trunk commit.
BaseSHA string `json:"base_sha,omitempty"`
// AskedAgain is when its head was announced again while its verdict was out of date — the merge tool's ask of
// its check against the trunk's head (ADR 0323 §5) — and no verdict has come since: its one-hour bound runs from
// then. Nil while nothing is asked, and cleared by a verdict or another move of its trunk.
AskedAgain *time.Time `json:"asked_again,omitempty"`
// The facts the table's guards read. NewerHead is the head that superseded it; ClosedUnmerged the pull
// request closed; MergedAs the commit it landed on the trunk as; HeldWhy what holds it for a person.
NewerHead string `json:"newer_head,omitempty"`
ClosedUnmerged bool `json:"closed_unmerged,omitempty"`
// Others are the other open pull requests announced at this head while this one was live (issue 1114), as they were
// announced: the record is theirs to take over when this one's pull request closes unmerged.
Others []PullEvent `json:"others,omitempty"`
MergedAs string `json:"merged_as,omitempty"`
MergedAt time.Time `json:"merged_at"`
// InSource says the merge was looked at for source.main: written there, or left as older than the trunk's head (issue 888).
// A merge the gate handed to the bridge on an Approve is moved there by finish instead (merges.go).
InSource bool `json:"in_source,omitempty"`
HeldWhy string `json:"held_why,omitempty"`
// BackupsAgainAfter is the revision of its walk when a person released it for a failed backup: a record at that
// revision or before still holds the failure the controller was asked to clear, and does not hold it again
// (novox/hq ADR 0404, issue 1068).
BackupsAgainAfter int64 `json:"backups_again_after,omitempty"`
// FromForge is a delivery made from the forge's word at its merge: its head was never heard by this owner.
// History is why it is history, once it is (novox/hq issue 313).
FromForge bool `json:"from_forge,omitempty"`
History string `json:"history,omitempty"`
// CoveredBy says which later merges into the same trunk delivered every module it moves, once they did
// (novox/hq issue 319); Lifted why what held it for a person no longer holds (issue 316).
CoveredBy string `json:"covered_by,omitempty"`
Lifted string `json:"lifted,omitempty"`
// Stranded says why a merge whose walk ended done before its word, without moving every module it moves, is
// stopped: nothing later delivered them either (novox/hq issue 474). Set once the cover test says no.
Stranded string `json:"stranded,omitempty"`
// CoverTold is whether the forge was told that such a stop was covered since: its status, its view and its note
// (novox/hq issue 480). Kept, so a restart tells it once, and a stop covered before this was built is told too.
CoverTold bool `json:"cover_told,omitempty"`
// DeliveredBy says, for a delivery that failed or was stopped, the later finished walk of its repository whose
// commit contains its merge and which built every module it moves — each it did not build, its own walk said
// unchanged: its change reached the machines after all (novox/hq issue 747). Its state stays final, as a covered
// stop's does (issue 480), and it reads covered: its group counts it as delivered.
DeliveredBy string `json:"delivered_by,omitempty"`
// StoppedFor is, for a member its group stopped, the member whose end stopped it (novox/hq issue 747): once that
// member's change reached the machines, the stop has lapsed, and holds no walk of its repository back.
StoppedFor string `json:"stopped_for,omitempty"`
// HeldBack is why the walk it waits on is not let go: it carries a merge whose delivery was stopped, and is
// asked to leave it out (novox/hq ADR 0299). Said by `waits`, and by `stalled` past its bound.
HeldBack string `json:"held_back,omitempty"`
// ContainedIn is the forge's answer, by a later merge commit, to whether that commit contains this one's
// merge (gitea_contains): asked once per pair, and kept (ADR 0270).
ContainedIn map[string]bool `json:"contained_in,omitempty"`
// AnsweredBy is the commit of its repository the walk it follows carries for it: its own merge, or a later merge
// of its branch that contains it (novox/hq ADR 0276). Unwalked says it is held because no walk answered it.
AnsweredBy string `json:"answered_by,omitempty"`
// Carrier says, for a merge a later one carries, which: its delivery and walk, in words.
Carrier string `json:"carrier,omitempty"`
Unwalked bool `json:"unwalked,omitempty"`
// Walk is the walk that delivers it, as last read: its own trunk commit's, or one that carries a later merge
// of its branch; Steps the machines it reached.
Walk *WalkSeen `json:"walk,omitempty"`
Steps []Step `json:"steps,omitempty"`
// Time is its delivery time, class and phases (novox/hq ADR 0282), once its walk's end is known.
Time *DeliveryTime `json:"delivery_time,omitempty"`
// LetGo is when this owner asked the controller to start its walk, and why.
LetGo time.Time `json:"let_go"`
LetGoWhy string `json:"let_go_why,omitempty"`
// Ready is what this owner did with the operator's Approve of its merge into main, and with the merge (novox/hq ADR
// 0421, ready.go). The json key is the one the ready mark's record had, so rows written before still read.
Ready *ReadyRun `json:"ready,omitempty"`
// ReviewedBy and ReviewedAt are the review record its pull request's description holds: the line
// `Reviewed-by: <who> at <full sha>` (novox/hq ADR 0421 §5, issue 1022), as written. Read at every head; never a
// hold: no Approve is asked until it names the head (novox/hq ADR 0496, issue 1803).
ReviewedBy string `json:"reviewed_by,omitempty"`
ReviewedAt string `json:"reviewed_at,omitempty"`
// PushedBy is the session that pushed its head, as its pull request's description names it in a line
// `Pushed-by: <who@node>` (playbook 07): named with its branch when a new head is needed (novox/hq ADR 0505 §5).
PushedBy string `json:"pushed_by,omitempty"`
// FreshWanted is whether a passing verdict that arrived out of date is to have its check asked against the
// trunk's head, before the operator is asked (novox/hq ADR 0421 §3, issue 1022). Cleared when it is asked.
FreshWanted bool `json:"fresh_wanted,omitempty"`
Transitions []Transition `json:"transitions,omitempty"`
// Owed are the forge's and the bus's effects of its transitions not yet done: said only after the
// delivery is kept, retried until done.
Owed []Effect `json:"owed,omitempty"`
}
// Transition is one move of the table, kept.
type Transition struct {
At time.Time `json:"at"`
From State `json:"from"`
To State `json:"to"`
Event Event `json:"event"`
Why string `json:"why"`
By string `json:"by,omitempty"`
}
// Verdict is a check's verdict: the gate's and the repository's own. ID is the build seat's ask that ran
// it, On the machine it ran on, At when this owner heard it; Report is the check's own account, as bounded
// as the controller says it. All of it is what `checks` answers, whole, beside the forge's short statuses.
type Verdict struct {
ID string `json:"id,omitempty"`
On string `json:"on,omitempty"`
At time.Time `json:"at"`
Gate string `json:"gate"`
Summary string `json:"summary"`
Modules []string `json:"modules,omitempty"`
Dependents []string `json:"dependents,omitempty"`
Repo string `json:"repo,omitempty"`
RepoSaid string `json:"repo_summary,omitempty"`
Report string `json:"report,omitempty"`
// Head and Trunk are what it judged (novox/hq ADR 0323 §3): the head merged into the trunk commit, twelve
// characters each. Trunk is empty for a verdict of a build seat that judged the head alone. Beside are the
// commits of the other repositories the check read, by directory; Took how long it ran.
Head string `json:"head,omitempty"`
Trunk string `json:"trunk,omitempty"`
Beside map[string]string `json:"beside,omitempty"`
Took string `json:"took,omitempty"`
// Status is the forge status it was read from when it is not the controller's verdict: mesh/check, the checker's
// (novox/hq ADR 0447, issue 1302). Empty for the controller's `checked`.
Status string `json:"status,omitempty"`
}
// Judged is what a verdict judged, in words: "head 1a2b3c4d5e6f merged into trunk 7a8b9c0d1e2f" (ADR 0323 §3);
// empty for a verdict that names no trunk commit.
func (v *Verdict) Judged() string {
if v == nil || v.Trunk == "" {
return ""
}
return fmt.Sprintf("head %s merged into trunk %s", shortOf(v.Head, 12), shortOf(v.Trunk, 12))
}
// outOfDate is why a delivery's verdict no longer counts: it names a trunk commit that is not its trunk's head
// as this owner heard it (novox/hq ADR 0323 §7). Empty when it counts — or names no trunk commit, as a verdict
// of a build seat from before the rule does, which counts as it did.
func (d *Delivery) outOfDate() string {
if d.Check == nil || d.Check.Trunk == "" || d.TrunkNow == "" || sameCommit(d.Check.Trunk, d.TrunkNow) {
return ""
}
return outOfDateWords(d.Check.Trunk, d.TrunkNow)
}
// outOfDateWords is a verdict out of date, as the forge's holder says it on the pull request (ADR 0323 §4).
func outOfDateWords(judged, now string) string {
return fmt.Sprintf("out of date: judged against trunk %s, the trunk is now %s; judged again on a recheck or when "+
"it is merged", shortOf(judged, 12), shortOf(now, 12))
}
// CheckAsk is a head's own check, asked of the controller by this owner (or re-asked by healer H2).
type CheckAsk struct {
// ID is the ask's id as the controller answered it.
ID string `json:"id"`
At time.Time `json:"at"`
By string `json:"by"`
Why string `json:"why"`
// Reasked is how many times H2 asked it again: once at most, then the operator's.
Reasked int `json:"reasked,omitempty"`
}
// GateComposed is the gate layer of a group member's own verdict when the group's composed check judges it instead
// (novox/hq ADR 0403 §1): no gate was run for the member alone.
const GateComposed = "composed"
// Passes is whether the verdict lets the delivery be ready: the gate passed or warned, and the
// repository's own check did not fail or error.
func (v *Verdict) Passes() bool {
if v == nil {
return false
}
// A group member's own gate is the group's composed check's (novox/hq ADR 0403 §1): the controller says the layer
// "composed", and the member counts as passed here once its repository's check did not fail, as the composed
// check, asked after every member is ready, then judges them together.
gate := v.Gate == "pass" || v.Gate == "warning" || v.Gate == GateComposed
repo := v.Repo == "" || v.Repo == "pass" || v.Repo == "warning"
return gate && repo
}
// DeliveryPlan is a delivery plan as the controller's planner says it (mesh-controller link.ChangePlan).
type DeliveryPlan struct {
Repository string `json:"repository"`
Base string `json:"base"`
Head string `json:"head"`
Moved []string `json:"moved,omitempty"`
Dependents []string `json:"dependents,omitempty"`
New []string `json:"new,omitempty"`
Unread []string `json:"unread,omitempty"`
Tiers [][]string `json:"tiers,omitempty"`
Machines []MachinePlan `json:"machines,omitempty"`
Steps []string `json:"steps,omitempty"`
Summary string `json:"summary"`
// Class is the class of the slowest-budgeted module it moves, as the planner says it: core, leaf, or none when
// it moves no module (novox/hq ADR 0313 §2). Empty in a plan from a controller that did not say it.
Class string `json:"class,omitempty"`
// Trunk is the trunk commit the head was merged into when its verdict judged it (novox/hq ADR 0323 §3).
Trunk string `json:"trunk,omitempty"`
}
// receiving are the machines the plan sends a module to, in the plan's order; none when the plan names no
// machines, as a plan kept before they were said may not.
func (p *DeliveryPlan) receiving(module string) []string {
if p == nil {
return nil
}
var out []string
for _, m := range p.Machines {
if containsString(m.Receives, module) {
out = append(out, m.Machine)
}
}
return out
}
// MachinePlan is one machine's part of a plan.
type MachinePlan struct {
Machine string `json:"machine"`
Receives []string `json:"receives,omitempty"`
Waits []string `json:"waits,omitempty"`
}
// waitsForItsOwnWalk is whether it is published again after the walk that carried a later commit for it failed.
func (d *Delivery) waitsForItsOwnWalk() bool {
return d.State == Published && d.Walk != nil && d.Walk.State == walkFailed && d.carriedLater()
}
// carriedLater is whether the walk it follows carries a later merge of its branch for it, not its own.
func (d *Delivery) carriedLater() bool {
return d.AnsweredBy != "" && d.MergedAs != "" && !sameCommit(d.AnsweredBy, d.MergedAs)
}
// MovesNothing is whether the delivery's plan moves no module the mesh holds: nothing for a walk to do. A
// module it adds is not moved: the controller builds and registers it and sends it nowhere, and opens no walk
// for it (novox/hq issues 300 and 319). A delivery with no plan known is not said to move nothing.
func (d *Delivery) MovesNothing() bool {
return d.Plan != nil && len(d.Plan.Moved) == 0
}
// Step is one machine's part of a delivering walk.
type Step struct {
Machine string `json:"machine"`
Module string `json:"module"`
Build string `json:"build,omitempty"`
State StepState `json:"state"`
Since time.Time `json:"since"`
Why string `json:"why,omitempty"`
}
// Effect is one thing owed outside the state: an event said, a line appended to a commit's note, the view
// on the pull request refreshed, a status set.
type Effect struct {
Kind string `json:"kind"`
Event string `json:"event,omitempty"` // an emit's module event, when not transition or group (issue 1025)
Commit string `json:"commit,omitempty"`
Line string `json:"line,omitempty"`
Context string `json:"context,omitempty"`
State string `json:"state,omitempty"`
Tries int `json:"tries,omitempty"`
Last string `json:"last,omitempty"`
Since time.Time `json:"since"`
// Next is when a failed effect is tried again: later after every failure, up to retryCap.
Next *time.Time `json:"next,omitempty"`
}
// The kinds of effect.
const (
EffectEmit = "emit"
EffectNote = "note"
EffectView = "view"
EffectStatus = "status"
// EffectComment is a comment on the pull request; its Context is the sequence number of the comment on the head, which
// keeps two comments on one head two acts (mesh-sdk go/delivery OwedCommentAct).
EffectComment = "comment"
)
// FromForgesWord is whether the delivery was made from the forge's word at its merge: said by FromForge, or —
// for one made before FromForge was kept — by its first transition.
func (d *Delivery) FromForgesWord() bool {
if d.FromForge {
return true
}
return len(d.Transitions) > 0 && d.Transitions[0].Event == EvAnnounced &&
strings.Contains(d.Transitions[0].Why, "so it is made from the forge's word")
}
// shownOnItsPullRequest is whether its view and status are kept on its pull request: not for history, whose
// pull request the forge may no longer hold — or holds under its number another one.
// coveredStop is whether a delivery that ended stopped or failed was covered since, and reads covered. A stop
// because its walk ended done before its word without building its modules (novox/hq issue 474) is covered when a
// later merge into its trunk that contains it was delivered, carrying every module it moves to every machine running
// it (ADR 0270's cover test, kept as CoveredBy; issue 480). Any stop or failure is covered when a later finished walk
// of its repository whose commit contains its merge built its modules (DeliveredBy, novox/hq issue 747). Its state
// stays final, as a covered superseded member stays superseded (issue 442), and it reads covered, by that merge or
// walk. A person's stop with a cover found only while it was held keeps its error.
func (d *Delivery) coveredStop() bool {
switch d.State {
case Stopped:
return d.Stranded != "" && d.CoveredBy != "" || d.DeliveredBy != ""
case Failed, Superseded:
return d.DeliveredBy != ""
}
return false
}
// strandedEnded is whether a stop for a walk that built none of its modules (novox/hq issue 474), found covered by no
// merge, waits for nothing that can come: every module its walk left unbuilt is one no later merge can carry, because
// the pull request removed it from the catalogue (its module.json is among the removed paths) or its plan sends it to
// no machine ("sent nowhere"). It is a reading of the stop, as covered is (issue 480): the state stays stopped, and
// `stalled` no longer lists it, so the condition that listed it ends by itself (issue 1581).
func (d *Delivery) strandedEnded() bool {
if d.State != Stopped || d.Stranded == "" || d.CoveredBy != "" {
return false
}
missing := d.notMovedBy()
for _, m := range missing {
if !d.cannotBeCarried(m) {
return false
}
}
return len(missing) > 0
}
// cannotBeCarried is whether no later merge can deliver a module this delivery's plan moved: it was removed, or the
// plan sends it nowhere.
func (d *Delivery) cannotBeCarried(module string) bool {
if d.Plan == nil {
return false
}
for _, p := range d.Removed {
if strings.HasSuffix(p, "/"+module+"/module.json") {
return true
}
}
return strings.Contains(d.Plan.Summary, ", sent nowhere") && len(d.Plan.receiving(module)) == 0
}
// strandedEndedSaid is how an ended stop reads, in `show`.
func (d *Delivery) strandedEndedSaid() string {
return "ended: it waited for a later merge to carry " + strings.Join(d.notMovedBy(), ", ") +
", and none can: the module was removed from the catalogue or is built for no machine (novox/hq issue 1581)"
}
// coveredStopSaid is how a covered stop or failure reads: on the forge's status, in its view and note, and in `show`.
func (d *Delivery) coveredStopSaid() string {
if d.State == Superseded {
return "delivered after its walk was superseded: " + d.DeliveredBy
}
ended := "its stop"
if d.State == Failed {
ended = "its failure"
}
if d.Stranded != "" && d.CoveredBy != "" {
return "covered after " + ended + ": a later merge that contains it was delivered: " + d.CoveredBy
}
return "covered after " + ended + ": a later walk that contains it was delivered: " + d.DeliveredBy
}
// memberReached is whether a group counts a member as delivered: its change reached the machines, or had nothing to
// reach them with — delivered, history or covered, a superseded member a later delivered walk carried (issue 442),
// or a stop or failure covered since (issues 480 and 747).
func memberReached(d *Delivery) bool {
return d.State.Reached() || d.State == Superseded && d.CoveredBy != "" || d.coveredStop()
}
// stoppedFor is the member whose end stopped this one in its group: kept since novox/hq issue 747, and read from the
// stop's reason for a stop made before ("its group <id> stopped: <member> …").
func (d *Delivery) stoppedFor() string {
if d.StoppedFor != "" || d.State != Stopped || d.Group == "" {
return d.StoppedFor
}
prefix := "its group " + d.Group + " stopped: "
for i := len(d.Transitions) - 1; i >= 0; i-- {
t := d.Transitions[i]
if t.Event != EvStop {
continue
}
if t.By != byOwner || !strings.HasPrefix(t.Why, prefix) {
return ""
}
member, _, _ := strings.Cut(strings.TrimPrefix(t.Why, prefix), " ")
return strings.TrimRight(member, ",;:")
}
return ""
}
// stopLapsed is whether a member its group stopped for another member is stopped for nothing any more: that member's
// change reached the machines since — delivered, or its failure covered by a later walk (novox/hq issue 747). Its own
// change is still to deliver, by a later walk of its repository that contains it; its stop holds none back. find
// gives a delivery by its id.
func stopLapsed(d *Delivery, find func(string) *Delivery) bool {
if d.State != Stopped || d.coveredStop() {
return false
}
by := d.stoppedFor()
if by == "" || by == d.ID {
return false
}
o := find(by)
return o != nil && memberReached(o)
}
func (d *Delivery) shownOnItsPullRequest() bool {
return d.Number > 0 && d.History == "" && d.State != Retired
}
// Owner and Repo split the repository.
func (d *Delivery) Owner() string { o, _, _ := strings.Cut(d.Repository, "/"); return o }
func (d *Delivery) Repo() string { _, r, _ := strings.Cut(d.Repository, "/"); return r }
// IDOf is a delivery's id: the repository and twelve characters of its commit.
func IDOf(repository, commit string) string {
return strings.ToLower(repository) + "@" + shortOf(commit, 12)
}
func shortOf(commit string, n int) string {
if len(commit) > n {
return commit[:n]
}
return commit
}
// sameCommit is whether two commits are one, however either is abbreviated.
func sameCommit(a, b string) bool {
if a == "" || b == "" {
return false
}
if len(a) > len(b) {
a, b = b, a
}
return len(a) >= 7 && strings.HasPrefix(strings.ToLower(b), strings.ToLower(a))
}
// reviewedLine is the review record of a pull request's description (novox/hq ADR 0421 §5, issue 1022):
// `Reviewed-by: <who> at <full sha>`, on a line of its own, who as who@node.
var reviewedLine = regexp.MustCompile(`(?im)^\s*Reviewed-by:\s*(\S+)\s+at\s+([0-9a-f]{40}|[0-9a-f]{64})\b`)
// ReviewIn reads the review record of a pull request's description: who reviewed and the full sha named. The last such
// line counts; empty when there is none.
func ReviewIn(body string) (who, sha string) {
for _, m := range reviewedLine.FindAllStringSubmatch(body, -1) {
who, sha = m[1], strings.ToLower(m[2])
}
return who, sha
}
// pushedLine is the line naming the session that pushed a pull request's head: `Pushed-by: <who@node>` (ADR 0505 §5).
var pushedLine = regexp.MustCompile(`(?im)^\s*Pushed-by:\s*([A-Za-z0-9_.\-]+(?:@[A-Za-z0-9_.\-]+)?)\s*$`)
// PushedIn reads it: the last such line, or "" when there is none.
func PushedIn(body string) string {
who := ""
for _, m := range pushedLine.FindAllStringSubmatch(body, -1) {
who = m[1]
}
return who
}
// authorOf is a delivery's author as the operator reads it: "the author of <branch>", and the session that pushed it
// when its description names one ("who on node": the router refuses an @ in its words).
func authorOf(d *Delivery) string {
who := "the author of " + d.Branch
if d.Branch == "" {
who = "its author"
}
if d.PushedBy != "" {
who += " (" + strings.Replace(d.PushedBy, "@", " on ", 1) + ")"
}
return who
}
// reviewWords is what the delivery's review record says of its head, for the ask: reviewed by whom, or, first, that it
// is not reviewed at this head (the ask is held then, ADR 0496). ok is whether it is reviewed at this head.
func reviewWords(d *Delivery) (words string, ok bool) {
switch {
case d.ReviewedBy == "":
return "not reviewed at this head: the description holds no Reviewed-by line", false
case !strings.EqualFold(d.ReviewedAt, d.Commit):
return fmt.Sprintf("not reviewed at this head: the Reviewed-by line names %s, the head is %s",
shortOf(d.ReviewedAt, 12), shortOf(d.Commit, 12)), false
}
// The router refuses an @ in an ask's words: who@node is said "who on node".
return "reviewed by " + strings.Replace(d.ReviewedBy, "@", " on ", 1) + " at " + shortOf(d.Commit, 12), true
}
// afterLines are the `after: <repository>` lines of a pull request's description.
var afterLines = regexp.MustCompile(`(?im)^\s*after:\s*([A-Za-z0-9_.\-/]+)\s*$`)
// AfterIn reads them.
func AfterIn(body string) []string {
var out []string
for _, m := range afterLines.FindAllStringSubmatch(body, -1) {
out = append(out, strings.TrimSuffix(m[1], ".git"))
}
sort.Strings(out)
return out
}
// The walk: the controller's plan of one trunk commit, as its `plan-moved` and `delivery-walks` say it
// (mesh-controller inventory.Plan). Read, never written here.
// The states of a walk as the controller keeps them.
const (
walkBuilding = "building"
walkRolling = "rolling"
walkDone = "done"
walkFailed = "failed"
walkSuperseded = "superseded"
)
// Walk is a walk whole, as the controller says it.
type Walk struct {
ID string `json:"id"`
Repository string `json:"repository"`
Branch string `json:"branch,omitempty"`
Commit string `json:"commit"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
State string `json:"state"`
Tier int `json:"tier"`
Tiers [][]string `json:"tiers"`
Modules map[string]*WalkModule `json:"modules"`
Note string `json:"note,omitempty"`
Revision int64 `json:"revision"`
Release *WalkRelease `json:"release,omitempty"`
Delivery *WalkDelivery `json:"delivery,omitempty"`
// Backups is what the backups the controller asks before it sends anything came to (novox/hq ADR 0404, issue
// 1068); absent when the plan moves no backed-up item.
Backups *WalkBackups `json:"backups,omitempty"`
// Phases is the walk's time from its merge, phase by phase, as the controller works it out (novox/hq ADR
// 0282): what a delivery's time is kept from. Absent from a controller that does not say it.
Phases *WalkPhases `json:"phases,omitempty"`
}
// WalkBackups is the backups of a walk before its send, as the controller's plan record says them: state pending,
// done or failed (failed as soon as any entry failed), each backup asked, and the machines with no backup holder.
type WalkBackups struct {
State string `json:"state"`
Asked []WalkBackupAsked `json:"asked,omitempty"`
NoHolder []string `json:"no_holder,omitempty"`
}
// WalkBackupAsked is one backup asked of a machine for a module: its state, and the backup's own words.
type WalkBackupAsked struct {
Machine string `json:"machine"`
Module string `json:"module"`
State string `json:"state"`
AskedAt *time.Time `json:"asked_at,omitempty"`
Words string `json:"words,omitempty"`
}
// WalkBackupFailure is one backup that failed or stayed silent past its bound, as a delivery keeps it.
type WalkBackupFailure struct {
Machine string `json:"machine"`
Module string `json:"module"`
Words string `json:"words"`
}
// WalkModule is one module of a walk.
type WalkModule struct {
State string `json:"state,omitempty"`
SentAt *time.Time `json:"sent_at,omitempty"`
First []string `json:"first,omitempty"`
FirstAt *time.Time `json:"first_at,omitempty"`
Commit string `json:"commit,omitempty"`
Why string `json:"why,omitempty"`
Build string `json:"build,omitempty"`
Gate *WalkGate `json:"gate,omitempty"`
GatedBy string `json:"gated_by,omitempty"`
// Made is what the walk's build of it did (novox/hq ADR 0350 §4): "reused from <check build>", naming the pull
// request's head, the trunk commit and the digests, or "built: <why>". Empty from a controller before it.
Made string `json:"made,omitempty"`
}
// WalkGate is a module's judging on its first machine.
type WalkGate struct {
Machines []string `json:"machines"`
From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
Since *time.Time `json:"since,omitempty"`
Passes int `json:"passes,omitempty"`
Verdict string `json:"verdict,omitempty"`
Why string `json:"why,omitempty"`
JudgedAt *time.Time `json:"judged_at,omitempty"`
Rollback string `json:"rollback,omitempty"`
Carried []WalkCarry `json:"carried,omitempty"`
}
// WalkCarry is one build a gated send carried.
type WalkCarry struct {
Module string `json:"module"`
Node string `json:"node"`
From string `json:"from,omitempty"`
To string `json:"to"`
Build string `json:"build,omitempty"`
}
// WalkRelease is a backlog walk's way through the machines.
type WalkRelease struct {
Order []string `json:"order"`
Next int `json:"next"`
Gate *WalkGate `json:"gate,omitempty"`
Done []string `json:"done,omitempty"`
By string `json:"by,omitempty"`
}
// WalkDelivery is what a walk waits for, and which merges it answers (novox/hq ADR 0276).
type WalkDelivery struct {
Awaits string `json:"awaits"`
Go *time.Time `json:"go,omitempty"`
By string `json:"by,omitempty"`
Why string `json:"why,omitempty"`
Stopped string `json:"stopped,omitempty"`
StoppedWhy string `json:"stopped_why,omitempty"`
// Merges are every merge the walk answers: each merge commit, its repository, and the commit of that
// repository the walk carries — the merge itself, or a later merge of its branch that contains it. Absent
// from a controller that does not say it: the walk then answers the merge of its own commit, and a merge
// the forge says its commit contains.
Merges []WalkMerge `json:"merges,omitempty"`
// TakenOverBy is, on a walk folded before it started, the walk that took it over and answers every merge
// it answered.
TakenOverBy string `json:"taken_over_by,omitempty"`
// WalkedAgainBy is, on a walk stopped without some of its merges, the batch that walks its other merges again,
// and LeftOut the merges it left out (novox/hq ADR 0299): a delivery of a merge left out ends stopped with it,
// one of a merge walked again follows that batch's walk.
WalkedAgainBy string `json:"walked_again_by,omitempty"`
LeftOut []WalkLeftOut `json:"left_out,omitempty"`
// Batch is set while the record is a batch, not yet a walk: its state `assembling` or `queued`. A batch, once
// cut, is the walk under the same id: its next record is `building`. A merge this owner reads in a batch is
// not held as unwalked while the batch is heard of.
Batch *WalkBatch `json:"batch,omitempty"`
// Unchanged are the modules the walk did not build because their build source holds none of the files its
// merges changed, each with why (novox/hq issue 611, ADR 0267 §5): the build the mesh holds is theirs. A delivery
// plan that read them whole names them; this says they matched what runs, not that the walk left them unbuilt.
// Absent from a controller that does not say it.
Unchanged map[string]string `json:"unchanged,omitempty"`
}
// WalkMerge is one merge a walk answers.
type WalkMerge struct {
Repository string `json:"repository"`
Commit string `json:"commit"`
// Carried is the commit of the repository the walk carries for it: Commit itself, or a later merge of its
// branch that contains it. Empty means Commit.
Carried string `json:"carried,omitempty"`
}
// WalkLeftOut is a merge a stop left out (novox/hq ADR 0299): a merge stopped, or a later merge of its
// repository's branch that contains it, Contains naming the stopped merge.
type WalkLeftOut struct {
Repository string `json:"repository"`
Commit string `json:"commit"`
Contains string `json:"contains,omitempty"`
// Shares are the modules it moves that a stopped merge moves too, when that is why it was left out: Contains
// then names that stopped merge, which it does not contain.
Shares []string `json:"shares,omitempty"`
}
// leftOut is the merge of a repository a stop left out, when it did.
func (w Walk) leftOut(repository, merge string) (WalkLeftOut, bool) {
if w.Delivery == nil || merge == "" {
return WalkLeftOut{}, false
}
for _, o := range w.Delivery.LeftOut {
if strings.EqualFold(o.Repository, repository) && sameCommit(o.Commit, merge) {
return o, true
}
}
return WalkLeftOut{}, false
}
// WalkBatch is a batch's window: when it closes unless another merge arrives, when it closes at the latest,
// and, once closed, the walk it waits behind.
type WalkBatch struct {
ClosesAt *time.Time `json:"closes_at,omitempty"`
AtMost *time.Time `json:"at_most,omitempty"`
Behind string `json:"behind,omitempty"`
}
// The states a batch's record is in before it is a walk (novox/hq ADR 0276).
const (
walkAssembling = "assembling"
walkQueued = "queued"
)
// IsBatch is whether the record is a batch still to be cut into a walk.
func (w Walk) IsBatch() bool { return w.State == walkAssembling || w.State == walkQueued }
// supersededNote reads the walk that took a superseded walk over from its note, for a controller that does not
// say TakenOverBy: "superseded at tier N by plan-… (…)".
var supersededNote = regexp.MustCompile(`^superseded at tier \d+ by (plan-[0-9A-Za-z_-]+)`)
// takenOverBy is the walk that took this one over, said or read from its note; empty for none.
func (w Walk) takenOverBy() string {
if w.State != walkSuperseded {
return ""
}
if w.Delivery != nil && w.Delivery.TakenOverBy != "" {
return w.Delivery.TakenOverBy
}
if m := supersededNote.FindStringSubmatch(w.Note); m != nil {
return m[1]
}
return ""
}
// carriedFor is the commit the walk carries for a merge of a repository, and whether it answers that merge by
// what it says: the merge named among its merges, or its own commit.
func (w Walk) carriedFor(repository, merge string) (string, bool) {
if w.Delivery != nil {
for _, m := range w.Delivery.Merges {
if strings.EqualFold(m.Repository, repository) && sameCommit(m.Commit, merge) {
if m.Carried != "" {
return m.Carried, true
}
return m.Commit, true
}
}
}
if strings.EqualFold(w.Repository, repository) && sameCommit(w.Commit, merge) {
return w.Commit, true
}
return "", false
}
// WalkSeen is what a delivery keeps of its walk.
type WalkSeen struct {
ID string `json:"id"`
Commit string `json:"commit"`
State string `json:"state"`
Note string `json:"note,omitempty"`
Revision int64 `json:"revision"`
Waits bool `json:"waits,omitempty"`
WaitedFor string `json:"waited_for,omitempty"`
GoBy string `json:"go_by,omitempty"`
StoppedBy string `json:"stopped_by,omitempty"`
// StoppedWhy is why it was stopped; WalkedAgainBy, on a walk stopped without some merges, the batch that walks
// this delivery's merge again; Contains, when this delivery's merge was left out for containing a stopped
// merge, that merge (novox/hq ADR 0299).
StoppedWhy string `json:"stopped_why,omitempty"`
WalkedAgainBy string `json:"walked_again_by,omitempty"`
Contains string `json:"contains,omitempty"`
// Shares are, when this delivery's merge was left out for moving modules a stopped merge moves, those modules:
// built at the branch, which holds the stopped merge, they would deliver it (ADR 0299).
Shares []string `json:"shares,omitempty"`
Tier int `json:"tier"`
Tiers int `json:"tiers"`
Updated time.Time `json:"updated"`
// Branch is the branch the walk's commit was merged into; empty in a record kept before it was read.
Branch string `json:"branch,omitempty"`
// Created is when the controller made the walk: a delivery follows the newest walk that answers it.
Created time.Time `json:"created"`
// TakenOverBy is the walk that took a superseded one over (novox/hq ADR 0276): its deliveries follow it.
TakenOverBy string `json:"taken_over_by,omitempty"`
// Moved are the modules the walk sent, as its record names them; MovedSaid says they were read, which a
// record kept before novox/hq issue 474 did not. What a walk that ended before its word delivered is read
// from them.
Moved []string `json:"moved,omitempty"`
MovedSaid bool `json:"moved_said,omitempty"`
// Unchanged are the modules the walk said matched what runs, with why (novox/hq issue 611): not built, and
// nothing for it to deliver.
Unchanged map[string]string `json:"unchanged,omitempty"`
// Built is, per module, what the walk's build of it did (novox/hq ADR 0350 §4): "reused from <check build>" or
// "built: <why>" — what `show` says beside the build phase.
Built map[string]string `json:"built,omitempty"`
// BackupsFailed are the backups before the send that failed, each with the backup's words (ADR 0404).
BackupsFailed []WalkBackupFailure `json:"backups_failed,omitempty"`
}
// backupWords says the failed backups: the machine, the module and the backup's own words.
func (w *WalkSeen) backupWords() string {
var parts []string
for _, f := range w.BackupsFailed {
parts = append(parts, fmt.Sprintf("%s on %s: %s", f.Module, f.Machine, f.Words))
}
return "a backup before the send failed, and nothing was sent: " + strings.Join(parts, "; ")
}
// Seen is a walk as a delivery keeps it.
func (w Walk) Seen() *WalkSeen {
s := &WalkSeen{ID: w.ID, Commit: w.Commit, State: w.State, Note: w.Note, Revision: w.Revision, Tier: w.Tier,
Tiers: len(w.Tiers), Updated: w.Updated}
if d := w.Delivery; d != nil {
s.WaitedFor = d.Awaits
s.Waits = d.Awaits != "" && d.Go == nil
s.GoBy = d.By
s.StoppedBy, s.StoppedWhy = d.Stopped, d.StoppedWhy
if len(d.Unchanged) > 0 {
s.Unchanged = maps.Clone(d.Unchanged)
}
}
s.Branch = w.Branch
s.Created = w.Created
s.TakenOverBy = w.takenOverBy()
// Only a module sent, or passed on its first machine, was built for the machines: one the record names with
// why it was not sent, or a walk that never started, built nothing (review of novox/hq issue 474).
for name, m := range w.Modules {
if m != nil && (m.SentAt != nil || m.Gate != nil && m.Gate.Verdict == "passed") {
s.Moved = append(s.Moved, name)
}
if m != nil && m.Made != "" {
if s.Built == nil {
s.Built = map[string]string{}
}
s.Built[name] = m.Made
}
}
sort.Strings(s.Moved)
s.MovedSaid = true
if w.Backups != nil {
for _, a := range w.Backups.Asked {
if a.State == "failed" {
s.BackupsFailed = append(s.BackupsFailed, WalkBackupFailure{Machine: a.Machine, Module: a.Module, Words: a.Words})
}
}
}
return s
}
// endedUnstarted is whether a published delivery's walk ended done without ever being let go: a batch's walk the
// controller judged to move nothing it holds is done when it is cut (novox/hq issue 474). No start can come for it,
// so its record settles the delivery.
func (d *Delivery) endedUnstarted() bool {
return d.State == Published && d.Walk != nil && d.Walk.State == walkDone && !d.Walk.Started()
}
// walkStopped is whether the walk it follows was stopped through this owner, and walkedAgain whether its merge is
// walked again by another walk since (novox/hq ADR 0299).
func (d *Delivery) walkStopped() bool {
return d.Walk != nil && d.Walk.State == walkFailed && d.Walk.StoppedBy != ""
}
func (d *Delivery) walkedAgain() bool { return d.walkStopped() && d.Walk.WalkedAgainBy != "" }
// stoppedWords is why a delivery ends with the walk it follows stopped: the stop, and the stopped merge its own
// contains when it was left out for that.
func (d *Delivery) stoppedWords() string {
s := fmt.Sprintf("its walk %s was stopped by %s", d.Walk.ID, d.Walk.StoppedBy)
if d.Walk.StoppedWhy != "" {
s += ": " + d.Walk.StoppedWhy
}
switch {
case len(d.Walk.Shares) > 0:
s += fmt.Sprintf("; its merge moves %s, as the stopped merge %s does: built at the branch, which holds that "+
"merge, it would deliver it, so it was left out with it (novox/hq ADR 0299)", strings.Join(d.Walk.Shares, ", "),
shortOf(d.Walk.Contains, 12))
case d.Walk.Contains != "":
s += fmt.Sprintf("; its merge contains the stopped merge %s, so it cannot be walked without delivering it, "+
"and was left out with it (novox/hq ADR 0299)", shortOf(d.Walk.Contains, 12))
}
return s
}
// walkMovedItsModules is whether the walk it follows answered every module its plan moves, and whether that is
// known: each built, or said unchanged — its build source holds none of the files the walk's merges changed, so the
// build the mesh holds is its build (novox/hq issue 611) — from the modules the walk's record names, or, for a
// record kept before they were, from a walk of no tier, which moved nothing. Unknown when its plan is not known.
func (d *Delivery) walkMovedItsModules() (moved, known bool) {
if d.Walk == nil || d.Plan == nil {
return false, false
}
if !d.Walk.MovedSaid && d.Walk.Tiers > 0 {
return false, false
}
return len(d.notMovedBy()) == 0, true
}
// notMovedBy is the modules its plan moves that the walk it follows neither built nor said unchanged: what a walk
// that ended without building them left undelivered.
func (d *Delivery) notMovedBy() []string {
var out []string
if d.Walk == nil || d.Plan == nil {
return nil
}
for _, m := range d.Plan.Moved {
if _, unchanged := d.Walk.Unchanged[m]; !containsString(d.Walk.Moved, m) && !unchanged {
out = append(out, m)
}
}
return out
}
// unchangedOfIts is the modules its plan moves that the walk it follows said matched what runs, sorted (novox/hq
// issue 611).
func (d *Delivery) unchangedOfIts() []string {
var out []string
if d.Walk == nil || d.Plan == nil {
return nil
}
for _, m := range d.Plan.Moved {
if _, unchanged := d.Walk.Unchanged[m]; unchanged && !containsString(d.Walk.Moved, m) {
out = append(out, m)
}
}
sort.Strings(out)
return out
}
// builtNoneOfIts is whether the walk it follows built none of the modules its plan moves and said each unchanged:
// its merge moves nothing, whichever commit the walk carried (ADR 0270 §1).
func (d *Delivery) builtNoneOfIts() bool {
return d.Plan != nil && len(d.Plan.Moved) > 0 && len(d.unchangedOfIts()) == len(d.Plan.Moved)
}
// Started is whether the walk is past its wait: it waited for nobody, or was let go — whatever came of it
// since. A walk superseded or stopped while it waited never started.
func (s *WalkSeen) Started() bool { return s != nil && !s.Waits && (s.WaitedFor == "" || s.GoBy != "") }
// Waited is whether it waited for a word at all.
func (s *WalkSeen) Waited() bool { return s != nil && s.WaitedFor != "" }
// LetGoByAPerson is whether a person's `plans go` started it.
func (s *WalkSeen) LetGoByAPerson() bool { return s != nil && strings.HasPrefix(s.GoBy, "a person") }
// Open is whether the controller still works it.
func (w Walk) Open() bool { return w.State == walkBuilding || w.State == walkRolling }
// The steps that say a module reached every machine running it: sent to all at once, or to the rest after its
// first machines passed.
const (
stepEveryMachine = "every machine running it"
stepTheRest = "the rest running it"
)
// StepsOf are the machine steps a walk's record says, for one delivery: every module it moved, on its first
// machines from its gate, and the rest once sent.
func StepsOf(w Walk) []Step {
var out []Step
names := make([]string, 0, len(w.Modules))
for n := range w.Modules {
names = append(names, n)
}
sort.Strings(names)
for _, name := range names {
m := w.Modules[name]
if m == nil || m.FirstAt == nil {
if m != nil && m.SentAt != nil && m.FirstAt == nil && m.Why == "" {
out = append(out, Step{Machine: stepEveryMachine, Module: name, Build: m.Build,
State: StepSent, Since: *m.SentAt})
}
continue
}
gate := m.Gate
state, why, since := StepSent, "", *m.FirstAt
if gate != nil {
switch {
case gate.Rollback == "rolled-back":
state, why = StepRolledBack, gate.Why
case gate.Verdict == "failed":
state, why = StepFailed, gate.Why
case gate.Verdict == "passed":
state, why = StepPassed, gate.Why
case gate.Since != nil:
state = StepJudging
why = fmt.Sprintf("%d healthy judging(s) so far", gate.Passes)
}
if gate.JudgedAt != nil {
since = *gate.JudgedAt
}
}
for _, machine := range m.First {
out = append(out, Step{Machine: machine, Module: name, Build: m.Build, State: state, Since: since, Why: why})
}
if m.SentAt != nil && state == StepPassed {
out = append(out, Step{Machine: stepTheRest, Module: name, Build: m.Build, State: StepSent,
Since: *m.SentAt})
}
}
return out
}
// Group is a delivery group: two or more deliveries sharing a head branch name, one level only.
type Group struct {
ID string `json:"id"`
Members []string `json:"members"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
// Check is its composed check: asked of the controller for these member commits, and its verdict.
Check *GroupCheck `json:"check,omitempty"`
// SaidGate is, by member id, why the member's mesh/merge-gate was last said pending (a group the controller judges;
// a group on mesh/check is owed none, issue 1302): written again only when it
// changes (novox/hq ADR 0403 §1, §6, issue 978). Cleared once a composed verdict stands at the heads, which the
// forge module then sets as every member's gate.
SaidGate map[string]string `json:"said_gate,omitempty"`
// Closed is set once a member reached the trunk: a group delivering takes no new member.
Closed bool `json:"closed,omitempty"`
// Said is the derived state last said, so a change of it is said once.
Said string `json:"said,omitempty"`
// Hold is why the group's merge or its Approve's ask is held back by a member's required status that is not a
// success (issue 1275); nil when nothing is.
Hold *GroupHold `json:"hold,omitempty"`
// SaidStatus is the mesh/delivery-group status last written for it: a change of the status a state takes is
// written too, though the state did not change (novox/hq issue 902).
SaidStatus string `json:"said_status,omitempty"`
// SaidReview is the wait for a review last said of the group while it was held for one (ADR 0496 §2): a change of
// the members still waiting is said too, though the state did not change.
SaidReview string `json:"said_review,omitempty"`
Owed []Effect `json:"owed,omitempty"`
}
// GroupHold is a group held back from its Approve or its merge because a member's required status is not a success.
type GroupHold struct {
// Kind is "unasked" (the Approve is not asked) or "blocked" (the approved merge is not started: nothing is merged).
Kind string `json:"kind"`
Member string `json:"member"`
Says string `json:"says"`
Since time.Time `json:"since"`
}
// GroupCheck is a group's composed check.
type GroupCheck struct {
Asked string `json:"asked"`
At time.Time `json:"at"`
Commits map[string]string `json:"commits"`
Verdict string `json:"verdict,omitempty"`
Summary string `json:"summary,omitempty"`
// Trunks are the trunk commit each member's head was merged into, by member id (novox/hq ADR 0323 §6).
Trunks map[string]string `json:"trunks,omitempty"`
// OutOfDate is why its verdict no longer counts: a merge that is not one of the group's members moved a
// member's trunk, at OutOfDateAt. It counts as outstanding (ADR 0239 §18), and runs again once a member is
// judged again after it.
OutOfDate string `json:"out_of_date,omitempty"`
OutOfDateAt time.Time `json:"out_of_date_at"`
}
// Commits is each member's commit, by id.
func commitsOf(ds []*Delivery) map[string]string {
out := map[string]string{}
for _, d := range ds {
out[d.ID] = d.Commit
}
return out
}
// sameMembers is whether two member→commit maps are one.
func sameMembers(a, b map[string]string) bool {
if len(a) != len(b) {
return false
}
for k, v := range a {
if b[k] != v {
return false
}
}
return true
}
func mustJSON(v any) json.RawMessage {
raw, _ := json.Marshal(v)
return raw
}
// supersededByItsWalk is whether a delivery ended superseded because its walk was: its last move was a supersession
// from published or delivering, not a newer head of its pull request's (issue 1786).
func (d *Delivery) supersededByItsWalk() bool {
if d.State != Superseded || len(d.Transitions) == 0 {
return false
}
t := d.Transitions[len(d.Transitions)-1]
return t.Event == EvSuperseded && (t.From == Published || t.From == Delivering)
}