The tracker derives a fix's delivered from the delivery view's event, not the gate's gone states (issue 1946)

This commit is contained in:
2026-10-24 16:50:50 +02:00
parent 1a271a1fb1
commit eb79996336
9 changed files with 289 additions and 57 deletions
@@ -62,17 +62,17 @@ func TestALinkedFixDeliveredIsNotedOnceNamingItsWorker(t *testing.T) {
linkPR(t, h, n, "mesh-catalog#226")
active := get(t, h, n).Worker.Active
merged := now.Add(-20 * time.Minute)
transition(t, h, 225, "published", "", "abc", merged)
transition(t, h, 225, "published", "", "abc1234", merged)
if v, _ := h.View(n); v.Delivery != "fix merged, not delivered: mesh-catalog#225" {
t.Fatalf("a linked pull request's delivery is not measured: %q", v.Delivery)
}
delivered := now.Add(-10 * time.Minute)
transition(t, h, 225, "delivered", "", "abc", delivered)
transition(t, h, 225, "delivered", "", "abc1234", delivered)
if countEvents(h, EventReadyToVerify) != 0 {
t.Fatal("said delivered while mesh-catalog#226 is not")
}
transition(t, h, 226, "delivered", "", "def", delivered.Add(time.Minute))
transition(t, h, 226, "delivered", "", "def", delivered.Add(time.Minute))
transition(t, h, 226, "delivered", "", "def1234", delivered.Add(time.Minute))
transition(t, h, 226, "delivered", "", "def1234", delivered.Add(time.Minute))
is := get(t, h, n)
var said []string
for _, nt := range is.Notes {
@@ -119,7 +119,7 @@ func TestDeliveredNotResolvedIsWarnedAfterTheBoundAndClearedWhenItLeavesFixing(t
t.Fatal(err)
}
linkPR(t, h, n, "mesh-catalog#9")
transition(t, h, 9, "delivered", "", "abc", *now)
transition(t, h, 9, "delivered", "", "abc1234", *now)
*now = now.Add(59 * time.Minute)
if _, err := h.Sweep(); err != nil || len(note.said) != 0 {
t.Fatalf("warned under the bound: %q %v", note.said, err)
@@ -169,7 +169,7 @@ func TestDeliveredNotResolvedIsWarnedAfterTheBoundAndClearedWhenItLeavesFixing(t
t.Fatal(err)
}
linkPR(t, h2, m, "mesh-catalog#9")
transition(t, h2, 9, "delivered", "", "abc", *now2)
transition(t, h2, 9, "delivered", "", "abc1234", *now2)
*now2 = now2.Add(11 * time.Minute)
if _, err := h2.Sweep(); err != nil || len(n2.said) != 1 {
t.Fatalf("the setting's bound was not read: %q %v", n2.said, err)
@@ -188,7 +188,7 @@ func TestDeliveredNotResolvedIsRetriedAndClearedAtTheSweep(t *testing.T) {
t.Fatal(err)
}
linkPR(t, h, n, "mesh-catalog#9")
transition(t, h, 9, "delivered", "", "abc", *now)
transition(t, h, 9, "delivered", "", "abc1234", *now)
*now = now.Add(2 * time.Hour)
_, _ = h.Sweep()
note.err = nil
@@ -355,7 +355,7 @@ func TestAPullRequestClosedUnmergedIsWithdrawn(t *testing.T) {
}
// A second fix linked and delivered is all the issue waits for: the withdrawn one is no fix.
linkPR(t, h, n, "mesh-catalog#10")
transition(t, h, 10, "delivered", "", "abc", *now)
transition(t, h, 10, "delivered", "", "abc1234", *now)
if v := mustView(t, h, n); !v.DeliveredNotResolved || v.Delivery != "delivered to every machine it should reach" {
t.Fatalf("%+v %q", v.DeliveredNotResolved, v.Delivery)
}
@@ -378,7 +378,7 @@ func TestOnePullRequestLinkedToSeveralIssues(t *testing.T) {
linkPR(t, h, n, "mesh-catalog#9")
ns = append(ns, n)
}
transition(t, h, 9, "delivered", "", "abc", *now)
transition(t, h, 9, "delivered", "", "abc1234", *now)
if countEvents(h, EventReadyToVerify) != 2 {
t.Fatalf("%d ready-to-verify", countEvents(h, EventReadyToVerify))
}
@@ -0,0 +1,96 @@
package main
import (
"encoding/json"
"testing"
"time"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// R6-C (ADR 0539 §17): the gate keeps no state after the merge, so an issue's fix is delivered when the delivery view says its
// source revision reached the machines (`mesh-delivery-view.delivered`: {revision, group, heads}), matched to the fix by the
// group it merged in or by the commit its merge landed as. The same revision twice is once.
func mergedFix(t *testing.T, h *harness, number int, group, mergedAs string) {
t.Helper()
body := map[string]any{"repository": "novox/mesh-catalog", "number": number, "to": "merged", "merged_as": mergedAs,
"at": time.Date(2026, 10, 24, 12, 0, 0, 0, time.UTC)}
if group != "" {
body["group"] = group
}
raw, _ := json.Marshal(body)
if err := h.OnTransition(stdio.Envelope{Key: "mesh-delivery-gate.transition", Body: raw}); err != nil {
t.Fatal(err)
}
}
func viewSays(t *testing.T, h *harness, revision int, group string, heads map[string]string) {
t.Helper()
raw, _ := json.Marshal(map[string]any{"revision": revision, "group": group, "heads": heads})
if err := h.OnDelivered(stdio.Envelope{Key: DeliveredEvent, Body: raw}); err != nil {
t.Fatal(err)
}
}
func fixIssue(t *testing.T, h *harness, pr string) uint64 {
t.Helper()
n := h.open(t, "a fix to deliver "+pr)
fixedAtFixing(t, h, n, pr)
return n
}
func TestAFixIsDeliveredWhenTheViewSaysTheGroupThatMovedTheSourceReachedTheMachines(t *testing.T) {
h := newHarness(t)
in := fixIssue(t, h, "mesh-catalog#20")
other := fixIssue(t, h, "mesh-catalog#21")
mergedFix(t, h, 20, "feat/r6", "aaaaaaaaaaaa1111")
mergedFix(t, h, 21, "feat/other", "bbbbbbbbbbbb2222")
if v, _ := h.View(in); v.Delivery != "fix merged, not delivered: mesh-catalog#20" {
t.Fatalf("before the view speaks: %q", v.Delivery)
}
viewSays(t, h, 7, "feat/r6", map[string]string{"novox/mesh-sdk": "cccccccccccc3333"})
if v, _ := h.View(in); v.Delivery != "delivered to every machine it should reach" {
t.Fatalf("the group's fix after the view spoke: %q", v.Delivery)
}
if v, _ := h.View(other); v.Delivery != "fix merged, not delivered: mesh-catalog#21" {
t.Fatalf("a fix of another group was delivered by a revision that is not its own: %q", v.Delivery)
}
if n := countEvents(h, EventReadyToVerify); n != 1 {
t.Fatalf("%d ready-to-verify, wanted one", n)
}
}
func TestAFixIsDeliveredWhenTheViewSaysTheHeadItsMergeLandedAs(t *testing.T) {
h := newHarness(t)
n := fixIssue(t, h, "mesh-catalog#22")
mergedFix(t, h, 22, "", "dddddddddddd4444eeee")
viewSays(t, h, 8, "some-other-group", map[string]string{"novox/mesh-sdk": "dddddddddddd4444eeee"})
if v, _ := h.View(n); v.Delivery != "fix merged, not delivered: mesh-catalog#22" {
t.Fatalf("a head of another repository delivered it: %q", v.Delivery)
}
viewSays(t, h, 9, "some-other-group", map[string]string{"novox/mesh-catalog": "dddddddddddd"})
if v, _ := h.View(n); v.Delivery != "delivered to every machine it should reach" {
t.Fatalf("the head its merge landed as: %q", v.Delivery)
}
}
func TestTheSameRevisionTwiceIsOnceAndTheGatesOwnDeliveredIsNoWord(t *testing.T) {
h := newHarness(t)
n := fixIssue(t, h, "mesh-catalog#23")
// The gate keeps no state after the merge: a transition to delivered, from an older gate, says nothing.
raw, _ := json.Marshal(map[string]any{"repository": "novox/mesh-catalog", "number": 23, "to": "delivered", "merged_as": "ffffffffffff5555"})
if err := h.OnTransition(stdio.Envelope{Key: "mesh-delivery-gate.transition", Body: raw}); err != nil {
t.Fatal(err)
}
if v, _ := h.View(n); v.Delivery != "nothing heard from the delivery of mesh-catalog#23" {
t.Fatalf("the gate's delivered counted: %q", v.Delivery)
}
mergedFix(t, h, 23, "feat/x", "ffffffffffff5555")
for range 2 {
viewSays(t, h, 10, "feat/x", nil)
}
if is := get(t, h, n); len(is.Delivered) != 1 || countEvents(h, EventReadyToVerify) != 1 {
t.Fatalf("delivered %v, %d ready-to-verify", is.Delivered, countEvents(h, EventReadyToVerify))
}
}
+123 -14
View File
@@ -1,8 +1,10 @@
package main
// Whether an issue's fix is delivered (novox/hq ADR 0281). The tracker hears mesh-delivery's `transition` event
// for every pull request an issue's fixed-by names and keeps, per pull request, the state the delivery last said
// — merged or not, held and why, covered by a later merge, delivered. From that it derives one line for `get` and
// Whether an issue's fix is delivered (novox/hq ADR 0281, cut by ADR 0539 §17). The tracker hears the gate's `transition`
// event for every pull request an issue's fixed-by names and keeps, per pull request, the state the gate last said — merged
// or not, held and why, closed — and the delivery view's `delivered` event for what reached the machines: the gate keeps no
// state after the merge, so a source revision's delivered state is the view's, said once per revision with the group that
// moved it and the merged heads. From that it derives one line for `get` and
// `list`: a measured fact, never set by a verb. Delivered is not resolved: when every fix of an issue not yet
// resolved is delivered, the issue gains a note and the event `ready-to-verify`, and an agent proves its
// acceptance live before resolving it. A fix merged and not delivered past a bound is said on the issue and to
@@ -41,6 +43,11 @@ type Fix struct {
// no module; Opened when its pull request was first heard (novox/hq ADR 0313 §2, §4).
Class string `json:"class,omitempty"`
Opened *time.Time `json:"opened,omitempty"`
// Repository, Group and MergedAs are what the gate said of its merge, which the view's `delivered` event is matched to:
// the repository (owner/name), the group that moved the source, and the commit the merge landed as.
Repository string `json:"repository,omitempty"`
Group string `json:"group,omitempty"`
MergedAs string `json:"merged-as,omitempty"`
}
// heardFix is what the delivery said of a pull request at one moment: its state, why, whether it is merged, and
@@ -54,6 +61,10 @@ type heardFix struct {
Class string
Opened *time.Time
MergedAt *time.Time
// Repository, Group and MergedAs: see Fix.
Repository string
Group string
MergedAs string
}
// news says whether what was heard adds anything to what a fix keeps.
@@ -64,13 +75,15 @@ func (h heardFix) news(f *Fix, kept string) bool {
if h.Opened != nil && (f.Opened == nil || !f.Opened.Equal(*h.Opened)) {
return true
}
if h.Group != "" && f.Group != h.Group || h.MergedAs != "" && f.MergedAs != h.MergedAs || h.Repository != "" && f.Repository != h.Repository {
return true
}
return h.MergedAt != nil && (f.MergedAt == nil || !f.MergedAt.Equal(*h.MergedAt))
}
// reached says whether a delivery's state says the change reached the machines (mesh-delivery's Reached).
func reached(state string) bool {
return state == "delivered" || state == "covered" || state == "retired"
}
// reached says whether a fix's state says the change reached the machines: only the view's `delivered` event says it
// (OnDelivered); the gate's states end at the merge.
func reached(state string) bool { return state == "delivered" }
// Whether a pull request is merged is what the delivery says of the pull request itself — `merged_as` on a
// transition, the pull request's `merged` in checks — never read from the state: `stopped` is also what a pull
@@ -79,7 +92,9 @@ func reached(state string) bool {
// withdrawn says whether a fix is a pull request closed without merging: no fix at all, neither awaiting delivery,
// delivered, nor open.
func withdrawn(f *Fix) bool { return f != nil && f.State == "stopped" && !f.Merged }
func withdrawn(f *Fix) bool {
return f != nil && (f.State == "closed" || f.State == "stopped") && !f.Merged
}
// liveFixes is an issue's fixes less those withdrawn: what its delivery, readiness to verify, the stuck sweep and a
// abandoned issue are measured on. fixPullRequests still names the withdrawn ones, so a pull request reopened is heard.
@@ -138,17 +153,15 @@ func DeliveryLine(is *Issue) string {
return ""
}
var held, failed, merged, open, unheard []string
covered := false
for _, pr := range prs {
f := fixOf(is, pr)
switch {
case contains(is.Delivered, pr):
covered = covered || (f != nil && f.State == "covered")
case f == nil:
unheard = append(unheard, pr)
case f.State == "held":
held = append(held, strings.TrimSpace(pr+": "+f.Why))
case f.State == "failed" || f.State == "stopped":
case f.State == "failed" || f.State == "stopped" || f.State == "closed":
failed = append(failed, strings.TrimSpace(pr+" "+f.State+": "+f.Why))
case f.Merged:
merged = append(merged, pr)
@@ -167,8 +180,6 @@ func DeliveryLine(is *Issue) string {
return "fix not merged: " + strings.Join(open, ", ")
case len(unheard) > 0:
return "nothing heard from the delivery of " + strings.Join(unheard, ", ")
case covered:
return "delivered to every machine it should reach (by a later merge that contains it)"
}
return "delivered to every machine it should reach"
}
@@ -215,6 +226,15 @@ func (t *Tracker) recordFix(pr string, h heardFix, only uint64) ([]uint64, error
if h.Class != "" {
f.Class = h.Class
}
if h.Repository != "" {
f.Repository = h.Repository
}
if h.Group != "" {
f.Group = h.Group
}
if h.MergedAs != "" {
f.MergedAs = h.MergedAs
}
if h.Opened != nil {
o := h.Opened.UTC()
f.Opened = &o
@@ -374,17 +394,22 @@ func (t *Tracker) OnTransition(e stdio.Envelope) error {
Class string `json:"class"`
Opened *time.Time `json:"opened"`
MergedAt *time.Time `json:"merged_at"`
Group string `json:"group"`
}
if err := json.Unmarshal(e.Body, &body); err != nil || body.To == "" || body.Number <= 0 {
return nil
}
if body.To == "delivered" {
return nil // the gate keeps no state after the merge; what reached the machines is the view's (OnDelivered)
}
at := body.At.UTC()
if at.IsZero() {
at = t.now()
}
merged := body.MergedAs != ""
changed, err := t.recordFix(prKey(body.Repository, body.Number), heardFix{State: body.To, Why: body.Why, Merged: merged,
At: at, Class: body.Class, Opened: nonZero(body.Opened), MergedAt: nonZero(body.MergedAt)}, 0)
At: at, Class: body.Class, Opened: nonZero(body.Opened), MergedAt: nonZero(body.MergedAt),
Repository: strings.ToLower(body.Repository), Group: body.Group, MergedAs: body.MergedAs}, 0)
if err != nil {
return err
}
@@ -396,6 +421,90 @@ func (t *Tracker) OnTransition(e stdio.Envelope) error {
return nil
}
// DeliveredEvent is the view's `delivered` (mesh-delivery-view, ADR 0539 §17 and design 47 R6): said once per source revision
// when every want, have and assignment is at the fingerprint and healthy.
const DeliveredEvent = "mesh-delivery-view.delivered"
// OnDelivered is the handler of the view's `delivered` event: {revision, group, heads}. Every merged fix of a pull request the
// group moved, or whose merge is the head the revision holds for its repository, reached the machines at that revision. The same
// revision twice changes nothing (a delivered fix stays delivered), and a fix is matched by the group it merged in or by its own
// merge commit, so a revision that carries a later head of a repository does not cover an earlier merge the tracker never heard
// delivered: that waits for its own revision's event.
func (t *Tracker) OnDelivered(e stdio.Envelope) error {
var body struct {
Revision uint64 `json:"revision"`
Group string `json:"group"`
Heads map[string]string `json:"heads"`
// At is when the view judged the revision delivered, when it says so; else this tracker's moment of hearing it.
At time.Time `json:"at"`
}
if err := json.Unmarshal(e.Body, &body); err != nil || (body.Group == "" && len(body.Heads) == 0) {
return nil
}
all, err := t.allIssues()
if err != nil {
return err
}
at := body.At.UTC()
if at.IsZero() {
at = t.now()
}
var changed []uint64
for n, is := range all {
for _, pr := range liveFixes(is) {
f := fixOf(is, pr)
if f == nil || !f.Merged || contains(is.Delivered, pr) || !deliveredBy(f, body.Group, body.Heads) {
continue
}
pr := pr
if _, err := t.change(n, Act{Op: "delivery", By: Seat}, func(i *Issue) error {
g := fixOf(i, pr)
if g == nil || contains(i.Delivered, pr) {
return nil
}
g.State, g.Why, g.Since = "delivered", fmt.Sprintf("source revision %d reached the machines", body.Revision), at
i.Delivered = append(i.Delivered, pr)
return nil
}); err != nil {
return err
}
if len(changed) == 0 || changed[len(changed)-1] != n {
changed = append(changed, n)
}
}
}
sort.Slice(changed, func(i, j int) bool { return changed[i] < changed[j] })
for _, n := range changed {
if err := t.afterFix(n); err != nil {
return err
}
}
return nil
}
// deliveredBy says whether a merged fix is one a delivered revision names: the group it merged in is the group that moved the
// source, or its merge commit is the head the revision holds for its repository.
func deliveredBy(f *Fix, group string, heads map[string]string) bool {
if group != "" && f.Group == group {
return true
}
for repository, head := range heads {
if f.MergedAs != "" && strings.EqualFold(repository, f.Repository) && sameCommitIn(f.MergedAs, head) {
return true
}
}
return false
}
// sameCommitIn is whether two commits are one, however either is abbreviated (at least seven characters).
func sameCommitIn(a, b string) bool {
a, b = strings.ToLower(a), strings.ToLower(b)
if len(a) > len(b) {
a, b = b, a
}
return len(a) >= 7 && strings.HasPrefix(b, a)
}
// Delivery is a pull request's delivery as mesh-delivery's `checks` answers it; a test stubs the asking.
type Delivery struct {
State string
@@ -52,6 +52,19 @@ func transition(t *testing.T, h *harness, number int, to, why, mergedAs string,
// transitionOn is a delivery's transition said under an event's name.
func transitionOn(t *testing.T, h *harness, event string, number int, to, why, mergedAs string, at time.Time) {
t.Helper()
if to == "delivered" || to == "covered" || to == "retired" {
// The gate keeps no state after the merge (ADR 0539 §17): what reached the machines is the view's `delivered`, matched
// to the fix by the commit its merge landed as. The fix is first heard merged, as it is in the real flow.
if mergedAs == "" {
mergedAs = "abc1234"
}
transitionOn(t, h, event, number, "merged", why, mergedAs, at)
raw, _ := json.Marshal(map[string]any{"revision": 1, "heads": map[string]string{"novox/mesh-catalog": mergedAs}, "at": at})
if err := h.OnDelivered(stdio.Envelope{Key: DeliveredEvent, Body: raw}); err != nil {
t.Fatal(err)
}
return
}
raw, _ := json.Marshal(map[string]any{"repository": "novox/mesh-catalog", "number": number, "to": to, "why": why,
"merged_as": mergedAs, "at": at})
if err := h.OnTransition(stdio.Envelope{Key: event, Body: raw}); err != nil {
@@ -93,11 +106,11 @@ func TestAnIssueKnowsWhetherItsFixIsDelivered(t *testing.T) {
if got := line(); got != "fix not merged: mesh-catalog#9" {
t.Fatal(got)
}
transition(t, h, 9, "published", "", "abc", at)
transition(t, h, 9, "published", "", "abc1234", at)
if got := line(); got != "fix merged, not delivered: mesh-catalog#9" {
t.Fatal(got)
}
transition(t, h, 9, "held", "dnsmasq waits for a person's push on two machines", "abc", at)
transition(t, h, 9, "held", "dnsmasq waits for a person's push on two machines", "abc1234", at)
if got := line(); got != "held: mesh-catalog#9: dnsmasq waits for a person's push on two machines" {
t.Fatal(got)
}
@@ -106,7 +119,7 @@ func TestAnIssueKnowsWhetherItsFixIsDelivered(t *testing.T) {
t.Fatalf("%+v", rows.Rows[0])
}
for i := 0; i < 2; i++ {
transition(t, h, 9, "delivered", "", "abc", at)
transition(t, h, 9, "delivered", "", "abc1234", at)
}
if got := line(); got != "delivered to every machine it should reach" {
t.Fatal(got)
@@ -124,15 +137,6 @@ func TestAnIssueKnowsWhetherItsFixIsDelivered(t *testing.T) {
if len(verify) != 1 || !strings.Contains(verify[0], "Delivered is not resolved") {
t.Fatalf("%q", verify)
}
// Covered by a later merge counts as delivered, and says so; a resolved issue is not said ready to verify.
h2 := newHarness(t)
m := h2.open(t, "x")
resolve(t, h2, m, "mesh-catalog#9")
transition(t, h2, 9, "covered", "", "abc", at)
if v, _ := h2.View(m); !strings.HasPrefix(v.Delivery, "delivered to every machine it should reach (by a later merge") ||
countEvents(h2, EventReadyToVerify) != 0 {
t.Fatalf("%q", v.Delivery)
}
}
// A fix merged and not delivered past the bound is said once on the issue and the operator's channel, and
@@ -144,7 +148,7 @@ func TestAStuckFixIsSaidOnceAndClearedWhenDelivered(t *testing.T) {
note := &notified{}
h.Notify = note.notify
merged := time.Date(2026, 10, 10, 9, 0, 0, 0, time.UTC) // three hours before the harness's clock
transition(t, h, 9, "held", "waits for a person", "abc", merged)
transition(t, h, 9, "held", "waits for a person", "abc1234", merged)
note.err = errStuck
if said, err := h.Sweep(); err != nil || said != 0 {
t.Fatalf("%d %v", said, err)
@@ -164,7 +168,7 @@ func TestAStuckFixIsSaidOnceAndClearedWhenDelivered(t *testing.T) {
if stuck != 1 || len(note.said) != 1 || !strings.HasPrefix(note.said[0], "issue-tracker.fix-stuck.365 warning") {
t.Fatalf("%d notes, said %q", stuck, note.said)
}
transition(t, h, 9, "delivered", "", "abc", merged)
transition(t, h, 9, "delivered", "", "abc1234", merged)
if len(note.clear) != 1 {
t.Fatalf("not cleared: %q", note.clear)
}
@@ -174,7 +178,7 @@ func TestAStuckFixIsSaidOnceAndClearedWhenDelivered(t *testing.T) {
fixedAtFixing(t, h2, m, "mesh-catalog#9")
n2 := &notified{}
h2.Notify = n2.notify
transition(t, h2, 9, "delivering", "", "abc", time.Date(2026, 10, 10, 11, 30, 0, 0, time.UTC))
transition(t, h2, 9, "delivering", "", "abc1234", time.Date(2026, 10, 10, 11, 30, 0, 0, time.UTC))
if said, _ := h2.Sweep(); said != 0 || len(n2.said) != 0 {
t.Fatal("said stuck under the bound")
}
@@ -218,13 +222,13 @@ func TestDeliveredStaysDeliveredAndClearsAreRetried(t *testing.T) {
note := &notified{}
h.Notify = note.notify
merged := time.Date(2026, 10, 10, 9, 0, 0, 0, time.UTC)
transition(t, h, 9, "held", "waits", "abc", merged)
transition(t, h, 9, "held", "waits", "abc1234", merged)
if said, _ := h.Sweep(); said != 1 {
t.Fatal("not said stuck")
}
note.err = errStuck
transition(t, h, 9, "delivered", "", "abc", merged.Add(time.Hour))
transition(t, h, 9, "held", "an older word", "abc", merged.Add(30*time.Minute)) // out of order
transition(t, h, 9, "delivered", "", "abc1234", merged.Add(time.Hour))
transition(t, h, 9, "held", "an older word", "abc1234", merged.Add(30*time.Minute)) // out of order
if v, _ := h.View(n); v.Delivery != "delivered to every machine it should reach" {
t.Fatal(v.Delivery)
}
@@ -243,7 +247,7 @@ func TestDeliveredStaysDeliveredAndClearsAreRetried(t *testing.T) {
}
n2 := &notified{}
h2.Notify = n2.notify
transition(t, h2, 9, "held", "waits", "abc", merged)
transition(t, h2, 9, "held", "waits", "abc1234", merged)
_, _ = h2.Sweep()
if _, err := h2.Move(MoveAsk{Number: m, Status: StatusLocated, FixedBy: []string{"mesh-catalog#10"}}); err != nil {
t.Fatal(err)
@@ -284,7 +288,7 @@ func TestAResolvedIssueEndsItsStuckWarningAndIsNeverSaidStuck(t *testing.T) {
fixedAtFixing(t, h, n, "mesh-catalog#9")
note := &notified{}
h.Notify = note.notify
transition(t, h, 9, "failed", "the gate failed on one machine", "abc", merged)
transition(t, h, 9, "failed", "the gate failed on one machine", "abc1234", merged)
if said, err := h.Sweep(); err != nil || said != 1 || len(note.said) != 1 {
t.Fatalf("not said stuck: %d %v %q", said, err, note.said)
}
@@ -314,7 +318,7 @@ func TestAResolvedIssueEndsItsStuckWarningAndIsNeverSaidStuck(t *testing.T) {
fixedAtFixing(t, h, n, "mesh-catalog#9")
note := &notified{err: errStuck}
h.Notify = note.notify
transition(t, h, 9, "failed", "the gate failed", "abc", merged)
transition(t, h, 9, "failed", "the gate failed", "abc1234", merged)
if _, err := h.change(n, Act{Op: "warned", By: Seat}, func(i *Issue) error {
i.Fixes[0].StuckSaid, i.StuckOpen = true, true
return nil
@@ -331,7 +335,7 @@ func TestAResolvedIssueEndsItsStuckWarningAndIsNeverSaidStuck(t *testing.T) {
}
m := h.open(t, "y")
fixedAtFixing(t, h, m, "mesh-catalog#10")
transition(t, h, 10, "failed", "the gate failed", "abc", merged)
transition(t, h, 10, "failed", "the gate failed", "abc1234", merged)
if _, err := h.Move(MoveAsk{Number: m, Status: StatusResolved, FixedBy: []string{"mesh-catalog#10"}}); err != nil {
t.Fatal(err)
}
@@ -38,10 +38,17 @@ func (c *fixClock) later(d time.Duration) time.Time { c.at = c.at.Add(d); return
// delivery says one transition of a pull request as mesh-delivery says it, with what a fix's time is read by.
func (c *fixClock) delivery(t *testing.T, number int, to, class string, opened, mergedAt *time.Time) {
t.Helper()
delivered := to == "delivered"
if delivered {
// What reached the machines is the view's `delivered` (ADR 0539 §17): the fix is heard merged, then delivered.
to = "merged"
}
body := map[string]any{"repository": "novox/mesh-catalog", "number": number, "to": to, "at": c.at}
if mergedAt != nil {
if mergedAt != nil || delivered {
body["merged_as"] = "m" + strings.Repeat("a", 11)
body["merged_at"] = *mergedAt
if mergedAt != nil {
body["merged_at"] = *mergedAt
}
}
if class != "" {
body["class"] = class
@@ -53,6 +60,12 @@ func (c *fixClock) delivery(t *testing.T, number int, to, class string, opened,
if err := c.OnTransition(stdio.Envelope{Key: "mesh-delivery-gate.transition", Body: raw}); err != nil {
t.Fatal(err)
}
if delivered {
view, _ := json.Marshal(map[string]any{"revision": number, "heads": map[string]string{"novox/mesh-catalog": "m" + strings.Repeat("a", 11)}, "at": c.at})
if err := c.OnDelivered(stdio.Envelope{Key: DeliveredEvent, Body: view}); err != nil {
t.Fatal(err)
}
}
}
func at(t time.Time) *time.Time { return &t }
@@ -161,6 +161,7 @@ func hearOpened(t *Tracker) {
for _, event := range transitionEvents {
hearings = append(hearings, hearing{event, t.OnTransition})
}
hearings = append(hearings, hearing{DeliveredEvent, t.OnDelivered})
for _, s := range hearings {
for wait := 2 * time.Second; ; wait = min(wait*2, time.Minute) {
time.Sleep(wait)
@@ -110,7 +110,7 @@ func TestItDeclaresAndHoldsTheIssueTrackerSeatOnceForTheMesh(t *testing.T) {
}
// The duplicate check hears the tracker's own opens and asks the agent module on its node (ADR 0278 §1, §5);
// the import and a reservation read the record through the records module (issue 424).
if !reflect.DeepEqual(m.Consumes, []string{"mesh-issues." + EventOpened, "mesh-issues." + EventMoved, "mesh-delivery-gate.transition"}) ||
if !reflect.DeepEqual(m.Consumes, []string{"mesh-issues." + EventOpened, "mesh-issues." + EventMoved, "mesh-delivery-gate.transition", DeliveredEvent}) ||
!reflect.DeepEqual(m.Invokes, []string{"claude-code.claude_code_judge", "seat:mesh-delivery.checks", "seat:operator-channel.notify",
"gitea.gitea_pull_reviews", "records.records_status", "records.records_list", "records.records_read", "mesh-knowledge.knowledge_highest"}) {
t.Fatalf("consumes %v, invokes %v", m.Consumes, m.Invokes)
@@ -472,11 +472,8 @@ func TestAnIssueIsReadyOnlyWhenWhatItDependsOnIsDelivered(t *testing.T) {
if asked != 1 || ready(h, w) {
t.Fatalf("ready before its fix is delivered (asked %d)", asked)
}
tr := stdio.Envelope{Key: "mesh-delivery-gate.transition", Body: json.RawMessage(`{"repository":"novox/mesh-catalog","number":9,"to":"delivered"}`)}
for i := 0; i < 2; i++ {
if err := h.OnTransition(tr); err != nil {
t.Fatal(err)
}
deliverNine(t, h)
}
if !ready(h, w) || !reflect.DeepEqual(get(t, h, dep).Delivered, []string{"mesh-catalog#9"}) {
t.Fatal("not ready once delivered")
@@ -771,10 +768,7 @@ func TestReadyIsSaidOnceFollowsDuplicatesAndAskingFailsLoudly(t *testing.T) {
if got := pullRequests(get(t, h, b)); !reflect.DeepEqual(got, []string{"mesh-catalog#9"}) {
t.Fatalf("%v", got)
}
tr := stdio.Envelope{Body: json.RawMessage(`{"repository":"novox/mesh-catalog","number":9,"to":"delivered"}`)}
if err := h.OnTransition(tr); err != nil {
t.Fatal(err)
}
deliverNine(t, h)
readies := 0
for _, e := range h.said() {
if e.Event == EventReady {
@@ -824,3 +818,17 @@ func TestAVerdictNamingIssueZeroIsNone(t *testing.T) {
t.Fatalf("%+v", is)
}
}
// deliverNine says pull request 9 of mesh-catalog merged, then delivered by the view's event (ADR 0539 §17).
func deliverNine(t *testing.T, h *harness) {
t.Helper()
merged := stdio.Envelope{Key: "mesh-delivery-gate.transition", Body: json.RawMessage(
`{"repository":"novox/mesh-catalog","number":9,"to":"merged","merged_as":"abc1234abc"}`)}
if err := h.OnTransition(merged); err != nil {
t.Fatal(err)
}
view := stdio.Envelope{Key: DeliveredEvent, Body: json.RawMessage(`{"revision":3,"heads":{"novox/mesh-catalog":"abc1234abc"}}`)}
if err := h.OnDelivered(view); err != nil {
t.Fatal(err)
}
}
+2 -1
View File
@@ -57,7 +57,8 @@
"consumes": [
"mesh-issues.opened",
"mesh-issues.moved",
"mesh-delivery-gate.transition"
"mesh-delivery-gate.transition",
"mesh-delivery-view.delivered"
],
"invokes": [
"claude-code.claude_code_judge",