Name issues not closed first, and say a quiet worker before a decision, as ADR 0285 says
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds mesh-issues, messenger → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds mesh-issues, messenger → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
The router's order put a wontfix issue before an open one, and a stale worker on an issue waiting for a decision said both; review of #191.
This commit is contained in:
@@ -604,7 +604,7 @@ func Tools(t *Tracker) []stdio.Tool {
|
||||
}},
|
||||
{Name: Seat + ".tracking",
|
||||
Description: "Which issues track which conditions: for every condition key an issue links (link kind condition), " +
|
||||
"or only the keys given, the issues that link it, those not resolved first, each with its status, " +
|
||||
"or only the keys given, the issues that link it, those not closed (resolved or wontfix) first, each with its status, " +
|
||||
"severity, what it needs, who works on it since when and whether they went quiet (stale), and where its " +
|
||||
"fix is (fix: not-merged, merged, held, failed, unheard or delivered; empty when none is named). The " +
|
||||
"operator's channel asks it to say in a condition's message which issue tracks it. Refused: a key not of a condition " +
|
||||
|
||||
@@ -115,10 +115,12 @@ func (h *Holder) warmTracking() {
|
||||
now := h.Now()
|
||||
h.mu.Lock()
|
||||
tried := h.track.tried
|
||||
h.mu.Unlock()
|
||||
if !tried.IsZero() && now.Sub(tried) >= 0 && now.Sub(tried) < TrackingAskEvery {
|
||||
h.mu.Unlock()
|
||||
return
|
||||
}
|
||||
h.track.tried = now // marked before asking, so two callers at once ask once
|
||||
h.mu.Unlock()
|
||||
got, err := h.askTracking()
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
@@ -162,9 +164,10 @@ func withTracking(m Message, r *Record, what string, issues []TrackedIssue, know
|
||||
return m
|
||||
}
|
||||
line, verdict := trackingWords(issues, known, now, loc)
|
||||
explanation, since, _ := strings.Cut(m.Body, "\n")
|
||||
if !strings.Contains(m.Body, "\n") {
|
||||
explanation, since = "", m.Body
|
||||
// since is the last line, which never holds a line break; the explanation is everything before it.
|
||||
explanation, since := "", m.Body
|
||||
if i := strings.LastIndex(m.Body, "\n"); i >= 0 {
|
||||
explanation, since = m.Body[:i], m.Body[i+1:]
|
||||
}
|
||||
if rest, ok := strings.CutPrefix(explanation, verdictNothing); ok {
|
||||
switch verdict {
|
||||
@@ -186,12 +189,17 @@ func withTracking(m Message, r *Record, what string, issues []TrackedIssue, know
|
||||
|
||||
// needsAnswer is the "Needs you:" sentence for the issues that wait for the operator's answer.
|
||||
func needsAnswer(issues []TrackedIssue) string {
|
||||
var ns []string
|
||||
var nums []uint64
|
||||
for _, is := range issues {
|
||||
if waitsForOperator(is) {
|
||||
ns = append(ns, fmt.Sprint(is.Number))
|
||||
nums = append(nums, is.Number)
|
||||
}
|
||||
}
|
||||
sort.Slice(nums, func(i, j int) bool { return nums[i] < nums[j] })
|
||||
var ns []string
|
||||
for _, n := range nums {
|
||||
ns = append(ns, fmt.Sprint(n))
|
||||
}
|
||||
if len(ns) == 1 {
|
||||
return "answer issue " + ns[0] + "."
|
||||
}
|
||||
@@ -209,7 +217,7 @@ func trackingWords(issues []TrackedIssue, known bool, now time.Time, loc *time.L
|
||||
}
|
||||
sorted := append([]TrackedIssue(nil), issues...)
|
||||
sort.SliceStable(sorted, func(i, j int) bool {
|
||||
ri, rj := sorted[i].Status == "resolved", sorted[j].Status == "resolved"
|
||||
ri, rj := closed(sorted[i]), closed(sorted[j])
|
||||
if ri != rj {
|
||||
return !ri
|
||||
}
|
||||
@@ -307,14 +315,13 @@ func issueState(is TrackedIssue, now time.Time, loc *time.Location) string {
|
||||
case waitsForOperator(is):
|
||||
return "it waits for your answer"
|
||||
}
|
||||
s := "nobody is working on it"
|
||||
if is.Worker != "" && is.Active != nil {
|
||||
s = "nobody has worked on it since " + when(*is.Active, now, loc)
|
||||
return "nobody has worked on it since " + when(*is.Active, now, loc)
|
||||
}
|
||||
if is.Needs == "decision" {
|
||||
s += "; it waits for a decision first"
|
||||
return "nobody is working on it; it waits for a decision first"
|
||||
}
|
||||
return s
|
||||
return "nobody is working on it"
|
||||
}
|
||||
|
||||
// workerWords is who@node as said: "fix-400 on g14".
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// The alert says which issue tracks its condition and where that issue stands (novox/hq issue 400, ADR 0285):
|
||||
// A condition's message says which issue tracks it and where that issue stands (novox/hq issue 400, ADR 0285):
|
||||
// one case per wording, each through the message as composed, and the content rule passes every line.
|
||||
|
||||
var trackNow = time.Date(2026, 10, 10, 19, 30, 0, 0, time.UTC)
|
||||
@@ -29,7 +29,7 @@ func trackedMessage(issues []TrackedIssue, known bool) Message {
|
||||
return withTracking(m, r, "quiet-reminder", issues, known, trackNow, time.UTC)
|
||||
}
|
||||
|
||||
func TestAnAlertSaysWhereTheIssueThatTracksItStands(t *testing.T) {
|
||||
func TestAConditionsMessageSaysWhereTheIssueThatTracksItStands(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
issues []TrackedIssue
|
||||
@@ -65,6 +65,11 @@ func TestAnAlertSaysWhereTheIssueThatTracksItStands(t *testing.T) {
|
||||
"Tracked as issue 386: it was resolved, yet this still happens.", "A person repaired"},
|
||||
{"it will not be fixed", []TrackedIssue{{Number: 386, Status: "wontfix"}}, true,
|
||||
"Tracked as issue 386: it will not be fixed.", "A person repaired"},
|
||||
{"one not fixed after one open", []TrackedIssue{{Number: 100, Status: "wontfix"}, {Number: 200, Status: "open"}}, true,
|
||||
"Tracked as issue 200: nobody is working on it. Also as issue 100: it will not be fixed.", "A person repaired"},
|
||||
{"its worker went quiet on one that waits for a decision", []TrackedIssue{{Number: 400, Status: "fixing", Needs: "decision",
|
||||
Worker: "fix-400@g14", Since: at(9, 0), Active: at(9, 30), Stale: true}}, true,
|
||||
"Tracked as issue 400: nobody has worked on it since 09:30.", "A person repaired"},
|
||||
{"it waits for the operator", []TrackedIssue{{Number: 336, Status: "open", Needs: "operator"}}, true,
|
||||
"Tracked as issue 336: it waits for your answer.", "Needs you: answer issue 336. A person repaired"},
|
||||
{"it waits for a decision", []TrackedIssue{{Number: 400, Status: "open", Needs: "decision"}}, true,
|
||||
@@ -152,7 +157,7 @@ func TestAClearingASilenceAndANoticeSayNoIssue(t *testing.T) {
|
||||
|
||||
// Through the router: the tracker is asked outside the work, its word is kept, a word older than TrackingKept is
|
||||
// not used, and a tracker that fails is said as not checked.
|
||||
func TestTheRouterAsksTheTrackerAndSaysItsWordInTheAlert(t *testing.T) {
|
||||
func TestTheRouterAsksTheTrackerAndSaysItsWordInTheMessage(t *testing.T) {
|
||||
h, tg, dt, c, _ := newHolder(t)
|
||||
h.Zone = func() *time.Location { return time.UTC }
|
||||
asked := 0
|
||||
|
||||
Reference in New Issue
Block a user