KB3: the overview hears git.pull-merged and the runner's group-ended, not the gate's group and merged

Builds: design 47 G §431
This commit is contained in:
2026-11-12 04:59:17 +01:00
parent ea87dd80bb
commit f17001d920
3 changed files with 17 additions and 17 deletions
@@ -35,7 +35,7 @@ const (
// OverviewEvents are the events the page consumes, by source, as the manifest declares them.
var OverviewEvents = map[string][]string{
SourceTracker: {"mesh-issues.opened", "mesh-issues.moved", "mesh-issues.milestone-set", "mesh-issues.milestone-moved"},
SourceDelivery: {"mesh-delivery-gate.group", "mesh-delivery-gate.transition", "mesh-delivery-gate.merged"},
SourceDelivery: {"git.pull-merged", "mesh-delivery-gate.transition", "mesh-pipeline-runner.group-ended"},
SourceAsks: {"messenger.offered", "messenger.closed"},
}
@@ -344,6 +344,7 @@ func (o *Overview) Heard(source, event string, body json.RawMessage) {
Design int `json:"design"`
ID string `json:"id"`
Group string `json:"group"`
Branch string `json:"branch"`
Outcome string `json:"outcome"`
Issues []int `json:"issues"`
Members []string `json:"members"`
@@ -383,18 +384,17 @@ func (o *Overview) Heard(source, event string, body json.RawMessage) {
case SourceDelivery:
id := firstNonEmpty(f.Group, f.ID)
switch name {
case "group":
g := o.groups[id]
g.ID, g.State, g.Why = id, firstNonEmpty(f.State, f.To, g.State), firstNonEmpty(f.Why, g.Why)
g.Members = firstStrings(f.Members, g.Members)
o.groups[id] = g
case "pull-merged":
// A merged pull request leaves the group named by its head branch (a group's id is its branch name): it waits for
// the operator no longer (design 47 G §431).
delete(o.groups, f.Branch)
case "transition":
if g, ok := o.groups[id]; ok {
g.State = firstNonEmpty(f.To, f.State, g.State)
g.Why = firstNonEmpty(f.Why, g.Why)
o.groups[id] = g
}
case "merged":
case "group-ended":
delete(o.groups, id)
}
case SourceAsks:
@@ -25,7 +25,7 @@ func newOverviewWorld(t *testing.T) *overviewWorld {
w := &overviewWorld{now: time.Date(2026, 11, 4, 12, 0, 0, 0, time.UTC), fail: map[string]bool{}, answers: map[string]string{
TrackerMilestonesVerb: `{"milestones":[{"design":47,"id":"1","outcome":"A merge is never dropped","status":"open","issues":[973,525]},{"design":47,"id":"2","outcome":"Done thing","status":"done","issues":[1]}]}`,
TrackerListVerb: `[{"number":973,"title":"The window","status":"fixing"},{"number":525,"title":"The queue","status":"open"}]`,
DeliveryGroupsVerb: `[{"id":"fix/a","state":"held","why":"a member is held","members":["x@1"]}]`,
DeliveryGroupsVerb: `[{"id":"fix/a","state":"held","why":"a member is held","members":["x@1"]},{"id":"fix/b","state":"checking","members":["y@2"]}]`,
AsksVerb: `{"asks":[{"key":"mesh-knowledge.1","asker":"mesh-knowledge","headline":"Approve ADR 0301","state":"open","offered_on":["telegram"]},{"key":"ask-0","asker":"x","headline":"old","state":"answered"}]}`,
}}
w.o = NewOverview(func(key string, body any) (json.RawMessage, error) {
@@ -68,7 +68,7 @@ func TestThePageModelHasThreeBlocksKeptByEventsAndAsksNoVerbAgain(t *testing.T)
t.Fatalf("the design block: %+v", m.Designs)
}
if len(m.Accepted) != 2 || len(m.Waiting) != 2 || len(m.Operator.Records) != 1 || m.Operator.Records[0].Record != "ADR 0303" ||
len(m.Operator.Asks) != 1 || m.Operator.Asks[0].Key != "mesh-knowledge.1" || len(m.Operator.Groups) != 1 {
len(m.Operator.Asks) != 1 || m.Operator.Asks[0].Key != "mesh-knowledge.1" || len(m.Operator.Groups) != 2 {
t.Fatalf("%+v", m)
}
asked := map[string]int{}
@@ -76,20 +76,20 @@ func TestThePageModelHasThreeBlocksKeptByEventsAndAsksNoVerbAgain(t *testing.T)
asked[v] = w.o.Calls(v)
}
// Events change the blocks.
w.o.Heard(SourceDelivery, "mesh-delivery-gate.group", json.RawMessage(`{"group":"fix/b","state":"checking","members":["y@2"]}`))
w.o.Heard(SourceDelivery, "git.pull-merged", json.RawMessage(`{"repository":"novox/y","number":2,"branch":"fix/b"}`))
w.o.Heard(SourceDelivery, "mesh-delivery-gate.transition", json.RawMessage(`{"group":"fix/a","to":"approved"}`))
w.o.Heard(SourceAsks, "messenger.offered", json.RawMessage(`{"id":"2","asker":"mesh-delivery","headline":"Approve the group","channels":["telegram"]}`))
w.o.Heard(SourceTracker, "mesh-issues.opened", json.RawMessage(`{"number":1999,"title":"New","status":"open"}`))
w.o.Heard(SourceTracker, "mesh-issues.milestone-moved", json.RawMessage(`{"design":47,"id":"1","to":"done","issues":[973,525,1999]}`))
m, _ = w.o.Model()
if len(m.Operator.Groups) != 2 || m.Operator.Groups[0].State != "approved" || len(m.Operator.Asks) != 2 || len(m.Designs) != 0 {
if len(m.Operator.Groups) != 1 || m.Operator.Groups[0].State != "approved" || len(m.Operator.Asks) != 2 || len(m.Designs) != 0 {
t.Fatalf("after the events: %+v", m)
}
w.o.Heard(SourceDelivery, "mesh-delivery-gate.merged", json.RawMessage(`{"group":"fix/a"}`))
w.o.Heard(SourceDelivery, "mesh-pipeline-runner.group-ended", json.RawMessage(`{"group":"fix/a","branch":"main","outcome":"passed"}`))
w.o.Heard(SourceAsks, "messenger.closed", json.RawMessage(`{"id":"1","asker":"mesh-knowledge","how":"answered"}`))
m, _ = w.o.Model()
if len(m.Operator.Groups) != 1 || m.Operator.Groups[0].ID != "fix/b" || len(m.Operator.Asks) != 1 || m.Operator.Asks[0].Key != "mesh-delivery.2" {
t.Fatalf("after merged and closed: %+v", m.Operator)
if len(m.Operator.Groups) != 0 || len(m.Operator.Asks) != 1 || m.Operator.Asks[0].Key != "mesh-delivery.2" {
t.Fatalf("after the group ended and the ask closed: %+v", m.Operator)
}
for v, n := range asked {
if w.o.Calls(v) != n {
@@ -146,7 +146,7 @@ func TestASourceThatDoesNotAnswerIsSaidAndAskedAgainOnABackoff(t *testing.T) {
w.now = w.o.state[SourceDelivery].Mark.Next
w.o.Due()
m, _ = w.o.Model()
if len(w.o.Unanswered()) != 0 || len(m.Operator.Groups) != 1 {
if len(w.o.Unanswered()) != 0 || len(m.Operator.Groups) != 2 {
t.Fatalf("%+v", m)
}
// A subscription that goes silent marks the source from the subscription and asks nothing while it is silent.
+2 -2
View File
@@ -217,9 +217,9 @@
"mesh-issues.moved",
"mesh-issues.milestone-set",
"mesh-issues.milestone-moved",
"mesh-delivery-gate.group",
"git.pull-merged",
"mesh-delivery-gate.transition",
"mesh-delivery-gate.merged",
"mesh-pipeline-runner.group-ended",
"messenger.offered",
"messenger.closed",
"mesh-conditions.findings-asked"