package main import ( "strings" "testing" "time" ) // **A verdict holds for the trunk commit it judged** (novox/hq ADR 0323, issue 341). The check judges the head // merged into a named trunk commit; when another merge moves that trunk, a ready delivery goes back to proposed, // a rejected one stays rejected, and nothing is asked. A verdict for an old trunk is kept and shown and does not // count, so the table reaches a fixed point. A group's composed check goes out of date on a merge that is not one of // its members, and runs again for new heads instead of holding the group. const ( trunk0 = "a2c2c35000000000000000000000000000000000" merge1 = "d28ca95000000000000000000000000000000000" ) // judged is a verdict of a head merged into a trunk commit. func judged(repo string, number int, sha, gate, trunk string) CheckedEvent { c := verdict(repo, number, sha, gate) c.Trunk = shortOf(trunk, 12) c.ID = "check-of-" + sha + "-at-" + shortOf(trunk, 8) c.Took = "4m12s" c.Beside = map[string]string{"mesh-catalog": "0123456789ab"} return c } func TestATrunkMoveSendsAReadyDeliveryBackToProposed(t *testing.T) { const host = "novox/mesh-host" const h64, h66, h67 = "6464646464646464", "dfb1415dfb1415df", "6767676767676767" w := newWorld(t) for _, p := range []PullEvent{pr(host, 64, h64, "a"), pr(host, 66, h66, "b"), pr(host, 67, h67, "c")} { w.h.PullUpdated(p) } w.h.Checked(judged(host, 64, h64, "pass", trunk0)) w.h.Checked(judged(host, 66, h66, "pass", trunk0)) w.h.Checked(judged(host, 67, h67, "fail", trunk0)) id66, id67 := IDOf(host, h66), IDOf(host, h67) if s := w.state(id66); s != Ready { t.Fatalf("#66 checked green against the trunk is %s", s) } // #64 merges: the trunk moves to its merge commit. w.h.PullMerged(merged(host, 64, h64, merge1)) w.settleAll() d := w.delivery(id66) if d.State != Proposed { t.Fatalf("#66's verdict names trunk %s, the trunk is now %s, and it is %s", trunk0[:12], merge1[:12], d.State) } last := d.Transitions[len(d.Transitions)-1] if last.Event != EvTrunkMoved || !strings.Contains(last.Why, trunk0[:12]) || !strings.Contains(last.Why, merge1[:12]) { t.Fatalf("its transition was %s, %q: not trunk moved with both commits", last.Event, last.Why) } if d.Check == nil || d.Check.Trunk != trunk0[:12] { t.Fatalf("its out-of-date verdict was not kept to be shown: %+v", d.Check) } if s := w.state(id67); s != Rejected { t.Fatalf("a rejected delivery went to %s on the trunk's move", s) } // Out of date and not asked: no bound, so stalled does not list it, however long it waits. w.later(5 * time.Hour) w.settleAll() for _, l := range w.h.Stalled() { if l.ID == id66 { t.Fatalf("stalled lists a proposed delivery whose check is out of date and not asked: %+v", l) } } // A late verdict for the old trunk is kept, does not count, and moves nothing: the table's fixed point. before := len(w.delivery(id66).Transitions) late := judged(host, 66, h66, "pass", trunk0) late.ID = "check-late" w.h.Checked(late) w.settleAll() d = w.delivery(id66) if d.State != Proposed || len(d.Transitions) != before || d.Check == nil { t.Fatalf("a late verdict for an old trunk left it %s after %d transition(s), verdict %+v", d.State, len(d.Transitions)-before, d.Check) } if ts := settle(d, Facts{Now: w.now}); len(ts) != 0 { t.Fatalf("the table took %d more transition(s) on a verdict that does not count", len(ts)) } // The check asked again judges the head merged into the trunk's head: it counts. w.h.Checked(judged(host, 66, h66, "pass", merge1)) w.settleAll() if s := w.state(id66); s != Ready { t.Fatalf("a verdict judged against the trunk's head left it %s", s) } } // A rejected delivery judged again against another trunk — the merge tool's ask — is taken as its recheck. func TestARejectedDeliveryJudgedAgainstANewTrunkIsRecheckedByTheVerdict(t *testing.T) { const repo = "novox/mesh-lab" const head = "1a2b3c4d5e6f7a8b" w := newWorld(t) w.h.PullUpdated(pr(repo, 9, head, "x")) w.h.Checked(judged(repo, 9, head, "fail", trunk0)) id := IDOf(repo, head) if s := w.state(id); s != Rejected { t.Fatalf("it is %s", s) } w.h.PullMerged(merged(repo, 8, "8888888888888888", merge1)) w.h.Checked(judged(repo, 9, head, "pass", merge1)) w.settleAll() d := w.delivery(id) if d.State != Ready || d.Check.Trunk != merge1[:12] { t.Fatalf("judged green against the trunk's head it is %s with %+v", d.State, d.Check) } } // **A verdict names the trunk commit it judged** in `checks` and `show`, and the kept plan records it (ADR 0323 §3). func TestChecksAndShowNameTheHeadAndTheTrunkCommit(t *testing.T) { const repo = "novox/mesh-lab" const head = "1a2b3c4d5e6f7a8b" w := newWorld(t) w.h.PullUpdated(pr(repo, 9, head, "x")) w.h.Checked(judged(repo, 9, head, "pass", trunk0)) id := IDOf(repo, head) a, err := w.h.Checks(repo, 0, head) if err != nil { t.Fatal(err) } want := "head " + head[:12] + " merged into trunk " + trunk0[:12] if a.Judged != want || a.Verdict == nil || a.Verdict.Head != head[:12] || a.Verdict.Trunk != trunk0[:12] { t.Fatalf("checks says %q, verdict %+v; want %q", a.Judged, a.Verdict, want) } if !strings.Contains(a.Says, want) { t.Fatalf("checks' words do not say what was judged: %s", a.Says) } if a.Verdict.Beside["mesh-catalog"] != "0123456789ab" || a.Verdict.Took != "4m12s" { t.Fatalf("checks does not name the other repositories' commits or how long it took: %+v", a.Verdict) } shown, err := w.h.Show(id) if err != nil { t.Fatal(err) } d := shown.(map[string]any)["delivery"].(*Delivery) if d.Check.Head != head[:12] || d.Check.Trunk != trunk0[:12] { t.Fatalf("show gives the verdict %+v", d.Check) } w.h.PullMerged(merged(repo, 8, "8888888888888888", merge1)) if a, _ = w.h.Checks(repo, 0, head); !strings.Contains(a.OutOfDate, merge1[:12]) { t.Fatalf("checks does not say the verdict is out of date: %+v", a) } } // A head whose merge check the forge's holder put out of date is not a check asked and not answered: `stalled` does // not list it, however long it waits — rejected, or with no delivery here — until its check is asked (ADR 0323 §7). func TestAnOutOfDateCheckOnTheForgeIsNotSaidUnanswered(t *testing.T) { const repo = "novox/mesh-lab" const head = "1a2b3c4d5e6f7a8b9c0d" w := newWorld(t) w.h.PullUpdated(pr(repo, 9, head, "x")) w.h.Checked(judged(repo, 9, head, "fail", trunk0)) words := outOfDateWords(trunk0, merge1) pending := func(description, at string) map[string]HeadCheck { return map[string]HeadCheck{ctxGate: {State: "pending", Description: description, UpdatedAt: at}, ctxRepo: {State: "pending", Description: description, UpdatedAt: at}} } w.forge.open = []OpenPull{{Owner: "novox", Repo: "mesh-lab", Number: 9, Base: "main", HeadSHA: head, Required: []string{ctxGate, ctxRepo}, Statuses: []string{ctxGate, ctxRepo}, Checks: pending(words, "2026-10-06T12:01:00Z")}} w.h.SeeOpenPulls() w.later(3 * time.Hour) w.h.SeeOpenPulls() for _, l := range w.h.Stalled() { if strings.HasPrefix(l.ID, repo) { t.Fatalf("an out-of-date check nobody asked is said stalled: %+v", l) } } // Asked by the merge tool: pending anew, and its bound runs from then. w.forge.open[0].Checks = pending("waiting in the build queue since 15:01: asked by the merge tool", "2026-10-06T15:01:00Z") w.h.SeeOpenPulls() w.later(2 * time.Hour) w.h.SeeOpenPulls() said := false for _, l := range w.h.Stalled() { said = said || l.State == Unanswered } if !said { t.Fatal("a check the merge tool asked and that never answered is not said") } } // The merge tool's ask announces an out-of-date head again: its one-hour bound runs from then, and a check that never // answers is said stalled (ADR 0323 §7). func TestAnOutOfDateDeliveryAskedAgainIsBoundFromTheAsk(t *testing.T) { const repo = "novox/mesh-lab" const head = "1a2b3c4d5e6f7a8b" w := newWorld(t) w.h.PullUpdated(pr(repo, 9, head, "x")) w.h.Checked(judged(repo, 9, head, "pass", trunk0)) w.h.PullMerged(merged(repo, 8, "8888888888888888", merge1)) id := IDOf(repo, head) w.later(3 * time.Hour) w.h.PullUpdated(pr(repo, 9, head, "x")) // the merge tool asked its check w.later(30 * time.Minute) for _, l := range w.h.Stalled() { if l.ID == id { t.Fatalf("listed half an hour after its check was asked again: %+v", l) } } w.later(time.Hour) said := false for _, l := range w.h.Stalled() { if l.ID == id { said = true if !strings.Contains(l.Says, "asked again") || !strings.Contains(l.H2, "operator") { t.Fatalf("its stalled line says %+v", l) } } } if !said { t.Fatal("a check asked again that never answered is not said") } // Its verdict judged against the trunk's head: ready, and nothing bounds it as proposed. w.h.Checked(judged(repo, 9, head, "pass", merge1)) if d := w.delivery(id); d.State != Ready || d.AskedAgain != nil { t.Fatalf("judged again it is %s, asked again %v", d.State, d.AskedAgain) } }