package main // progress with since: only what changed in a window under of (the operator, 2026-10-28: "can the progress tool also // report the progress made since the last call, or maybe better, the last 60 minutes"). A window, not a caller's last // call: the knowledge base keeps no state of its own, so it reads what the other seats keep, each a request and reply // over the bus: // // - the issue tracker's moves over the window, from its own log (`issue-tracker.changes`): each issue opened or // moved, from what to what, by whom and when; // - the issue tracker's milestones with their last move, and a moved one's history (`issue-tracker.milestones` // with one): what it was before, what it is now, who moved it and when; // - each issue that moved in the window whole (`issue-tracker.get`): when it was opened, its notes and links with who // and when, its worker since when, and when it was closed; // - mesh-delivery's deliveries and each one's transitions and its check (`mesh-delivery.deliveries`, `.show`). // // When the tracker cannot say its moves (a tracker without `changes`), a move is said by the status it reached, with its // worker's since or its closed time, not from what it moved, and the answer says so. mesh-delivery keeps no time a // merge reached its machines, so "on the machines" is as it reads now, said too. import ( "encoding/json" "fmt" "sort" "strconv" "strings" "time" ) // MostSince is the longest window since asks: the deliveries mesh-delivery lists end a day back. const MostSince = 24 * time.Hour // parseSince reads a window such as 60m, 5m or 2h; empty is none. func parseSince(s string) (time.Duration, error) { s = strings.TrimSpace(s) if s == "" { return 0, nil } d, err := time.ParseDuration(s) if err != nil || d <= 0 || d > MostSince { return 0, fmt.Errorf("since is a window such as 60m, 5m or 2h, more than nothing and at most %s, not %q", "24h", s) } return d, nil } // sinceScope is what an answer about a window is about: the issues under of, as the tracker answered their rows, and // the milestones; every says that of is the mesh, so every delivery counts. type sinceScope struct { rows map[int]trackerRow milestones []sinceMilestone every bool } type sinceMilestone struct { design int m Milestone } // hold keeps the rows and the designs' milestones for a window's answer; on a nil scope it does nothing. func (s *sinceScope) hold(rows map[int]trackerRow, ds ...*Design) { if s == nil { return } for n, r := range rows { s.rows[n] = r } for _, d := range ds { for _, m := range d.Milestones { s.milestones = append(s.milestones, sinceMilestone{design: d.Number, m: m}) } } } // change is one thing that changed in the window, with its changes, newest last. type change struct { line string last time.Time events []event } type event struct { at time.Time line string // brief is the event in the change's one line, when it differs from line. brief string } // window is the time between from and to. type window struct{ from, to time.Time } func (w window) holds(t time.Time) bool { return !t.IsZero() && !t.Before(w.from) && !t.After(w.to) } // progressSince replaces the answer's tree with what changed in the window under p.scope. func (k *KB) progressSince(p *Progress, d time.Duration) { w := window{from: p.ReadAt.Add(-d), to: p.ReadAt} p.Since, p.Items = d.String(), nil var milestones, issues, pulls []change milestones = k.milestoneChanges(p, w) issues = k.issueChanges(p, w) pulls = k.pullChanges(p, w) for _, cs := range [][]change{milestones, issues, pulls} { sort.SliceStable(cs, func(i, j int) bool { return cs[i].last.After(cs[j].last) }) } all := append(append(milestones, issues...), pulls...) for _, c := range all { p.Changes += len(c.events) } var b strings.Builder noun := "changes" if p.Changes == 1 { noun = "change" } fmt.Fprintf(&b, "%d %s in the last %d min under %s (since %s)\n", p.Changes, noun, int(d.Minutes()), p.Of, when(&w.from)) for i, c := range all { branch, under := "├── ", "│ " if i == len(all)-1 { branch, under = "└── ", " " } b.WriteString(branch + c.line + "\n") if p.Detail != DetailDetailed { continue } for j, e := range c.events { br := "├── " if j == len(c.events)-1 { br = "└── " } b.WriteString(under + br + e.line + "\n") } } p.Tree = strings.TrimRight(b.String(), "\n") p.Source += "; the window from the tracker's milestones and their history, its issues' notes, links, workers and " + "closes, and mesh-delivery's transitions and checks" } // milestoneChanges is each milestone moved in the window, from its history: what it was and what it is. func (k *KB) milestoneChanges(p *Progress, w window) []change { var out []change asked := 0 for _, sm := range p.scope.milestones { m := sm.m if m.Moved == nil || !w.holds(m.Moved.At) { continue } ref := fmt.Sprintf("%d/%s", sm.design, m.ID) now := m.Status if m.Reason != "" { now += ": " + m.Reason } was := "a state its history does not say" if asked < MostGets { asked++ if prev, err := k.milestoneBefore(ref); err != nil { p.Warnings = append(p.Warnings, fmt.Sprintf("issue-tracker.milestones one %s: %v; said without what it was", ref, err)) } else if prev != "" { was = prev } } mark := marks[StateMoving] if m.Status == "closed" { mark = marks[StateDone] } line := fmt.Sprintf("%s milestone %s %s → %s · by %s %s", mark, ref, was, now, m.Moved.By, when(&m.Moved.At)) out = append(out, change{line: line, last: m.Moved.At, events: []event{{at: m.Moved.At, line: fmt.Sprintf("moved %s by %s", when(&m.Moved.At), m.Moved.By)}}}) } return out } // milestoneBefore is a milestone's status before its last move, from the tracker's history of it. func (k *KB) milestoneBefore(ref string) (string, error) { raw, err := k.Seats(MilestonesVerb, map[string]any{"one": ref}) if err != nil { return "", err } var r struct { History []struct { Status string `json:"status"` } `json:"history"` } if err := json.Unmarshal(raw, &r); err != nil { return "", fmt.Errorf("answered something else: %v", err) } if len(r.History) < 2 { return "", nil } return r.History[len(r.History)-2].Status, nil } // issueGotWhole is the part of `issue-tracker.get` a window reads. type issueGotWhole struct { Status string `json:"status"` Opened time.Time `json:"opened"` OpenedBy string `json:"opened-by"` Closed *time.Time `json:"closed"` FixedBy []string `json:"fixed-by"` Worker *struct { By string `json:"by"` Since time.Time `json:"since"` } `json:"worker"` Notes []struct { At time.Time `json:"at"` By string `json:"by"` Text string `json:"text"` } `json:"notes"` Links []struct { At time.Time `json:"at"` By string `json:"by"` Kind string `json:"kind"` Target string `json:"target"` } `json:"links"` } // issueChanges is each issue under of that moved in the window, read whole: opened, moved, noted, linked, closed. func (k *KB) issueChanges(p *Progress, w window) []change { var moved []trackerRow for _, r := range p.scope.rows { if r.Moved != nil && w.holds(*r.Moved) { moved = append(moved, r) } } sort.Slice(moved, func(i, j int) bool { return moved[i].Moved.After(*moved[j].Moved) }) var moves map[int][]trackerMove var merr error if len(moved) > 0 { if moves, merr = k.trackerMoves(w); merr != nil { p.Warnings = append(p.Warnings, fmt.Sprintf("issue-tracker.changes: %v; a move is said by the status it reached, "+ "its worker's since and its closed time, not from what it moved", merr)) } } var out []change for i, r := range moved { it, _ := issueItem(r) if it.State == "" { it.State = StateDone } head := fmt.Sprintf("%s %d %s — ", marks[it.State], r.Number, clip(r.Title, 80)) fallback := func(why string) { p.Warnings = append(p.Warnings, why) line := fmt.Sprintf("moved %s, now %s", when(r.Moved), r.Status) if r.Worker != "" { line += " (" + r.Worker + ")" } out = append(out, change{line: head + line, last: *r.Moved, events: []event{{at: *r.Moved, line: line}}}) } if i >= MostGets { fallback(fmt.Sprintf("more than %d issues moved; the rest are said by their rows' moved time", MostGets)) continue } raw, err := k.Seats("seat:issue-tracker.get", map[string]any{"number": r.Number}) if err != nil { fallback(fmt.Sprintf("issue-tracker.get %d: %v; said by its row's moved time", r.Number, err)) continue } var g issueGotWhole if err := json.Unmarshal(raw, &g); err != nil { fallback(fmt.Sprintf("issue-tracker.get %d answered something else: %v; said by its row's moved time", r.Number, err)) continue } var evs []event var says []string if merr == nil { for _, m := range moves[r.Number] { if m.Kind == "opened" || m.Kind == "imported" { evs = append(evs, event{at: m.At, line: fmt.Sprintf("%s %s by %s", m.Kind, when(&m.At), m.By)}) says = append(says, m.Kind+" by "+m.By+" "+when(&m.At)) continue } who, line := m.By, fmt.Sprintf("%s → %s %s by %s", m.From, m.To, when(&m.At), m.By) if m.Worker != "" { who, line = m.Worker, line+", worker "+m.Worker } short := fmt.Sprintf("%s → %s by %s %s", m.From, m.To, who, when(&m.At)) if (m.To == "resolved" || m.To == "wontfix") && len(g.FixedBy) > 0 { short += ", fixed by " + strings.Join(g.FixedBy, ", ") } evs = append(evs, event{at: m.At, line: line}) says = append(says, short) } } if merr != nil && w.holds(g.Opened) { evs = append(evs, event{at: g.Opened, line: fmt.Sprintf("opened %s by %s", when(&g.Opened), g.OpenedBy)}) says = append(says, "opened by "+g.OpenedBy+" "+when(&g.Opened)) } if merr != nil && g.Worker != nil && w.holds(g.Worker.Since) && (g.Status == "diagnosing" || g.Status == "fixing") { s := fmt.Sprintf("→ %s by %s %s", g.Status, g.Worker.By, when(&g.Worker.Since)) evs = append(evs, event{at: g.Worker.Since, line: s}) says = append(says, s) } if merr != nil && g.Closed != nil && w.holds(*g.Closed) { s := fmt.Sprintf("→ %s %s", g.Status, when(g.Closed)) if len(g.FixedBy) > 0 { s += ", fixed by " + strings.Join(g.FixedBy, ", ") } evs = append(evs, event{at: *g.Closed, line: s}) says = append(says, s) } notes := 0 var last string for _, n := range g.Notes { if w.holds(n.At) { notes++ last = fmt.Sprintf("the last %s by %s", when(&n.At), n.By) evs = append(evs, event{at: n.At, line: fmt.Sprintf("noted %s by %s: %s", when(&n.At), n.By, clip(n.Text, 80))}) } } if notes == 1 { says = append(says, "1 note, "+last) } else if notes > 1 { says = append(says, fmt.Sprintf("%d notes, %s", notes, last)) } links := 0 for _, l := range g.Links { if w.holds(l.At) { links++ evs = append(evs, event{at: l.At, line: fmt.Sprintf("linked %s %s by %s %s", l.Kind, l.Target, l.By, when(&l.At))}) } } if links > 0 { says = append(says, fmt.Sprintf("%d links", links)) } if len(evs) == 0 { // It moved by what the tracker records of nobody: its fix's delivery said a new state. s := fmt.Sprintf("moved %s, now %s", when(r.Moved), it.Why) evs, says = []event{{at: *r.Moved, line: s}}, []string{s} } if it.State == StateStuck { says = append(says, "stuck: "+it.Why) } sort.SliceStable(evs, func(i, j int) bool { return evs[i].at.Before(evs[j].at) }) out = append(out, change{line: head + strings.Join(says, "; "), last: *r.Moved, events: evs}) } return out } // deliveryWhole is the part of `mesh-delivery.show` a window reads. type deliveryWhole struct { Delivery struct { Repository string `json:"repository"` Number int `json:"number"` Issues []int `json:"issues"` State string `json:"state"` Check *struct { At time.Time `json:"at"` Gate string `json:"gate"` Summary string `json:"summary"` } `json:"check"` Transitions []struct { At time.Time `json:"at"` Event string `json:"event"` To string `json:"to"` Why string `json:"why"` } `json:"transitions"` } `json:"delivery"` Machines string `json:"machines"` Waits string `json:"waits"` } // pullChanges is each pull request naming an issue under of (every one, for the mesh) that was opened, checked, // merged, held or closed in the window. func (k *KB) pullChanges(p *Progress, w window) []change { raw, err := k.Seats("seat:mesh-delivery.deliveries", map[string]any{}) if err != nil { p.Warnings = append(p.Warnings, fmt.Sprintf("mesh-delivery does not answer, so no pull request is said: %v", err)) return nil } var dl struct { Deliveries []deliveryRow `json:"deliveries"` } if err := json.Unmarshal(raw, &dl); err != nil { p.Warnings = append(p.Warnings, fmt.Sprintf("mesh-delivery.deliveries answered something else, so no pull request is said: %v", err)) return nil } var out []change shown, reached := 0, false for _, d := range dl.Deliveries { // A delivery's since is its last transition; one not final may still have been checked since. if final(d.State) && !w.holds(d.Since) { continue } if shown == MostShows { p.Warnings = append(p.Warnings, fmt.Sprintf("more than %d deliveries moved; the rest are not read", MostShows)) break } shown++ raw, err := k.Seats("seat:mesh-delivery.show", map[string]any{"id": d.ID}) if err != nil { p.Warnings = append(p.Warnings, fmt.Sprintf("mesh-delivery.show %s: %v", d.ID, err)) continue } var s deliveryWhole if err := json.Unmarshal(raw, &s); err != nil { p.Warnings = append(p.Warnings, fmt.Sprintf("mesh-delivery.show %s answered something else: %v", d.ID, err)) continue } under := p.scope.every var names []string for _, n := range s.Delivery.Issues { names = append(names, strconv.Itoa(n)) if _, ok := p.scope.rows[n]; ok { under = true } } if !under { continue } var evs []event for _, t := range s.Delivery.Transitions { if !w.holds(t.At) { continue } switch { case t.Event == "announced": evs = append(evs, event{at: t.At, line: "opened " + when(&t.At)}) case t.To == "merged" || t.To == "closed" || strings.HasPrefix(t.To, "held") || t.To == "delivered": evs = append(evs, event{t.At, fmt.Sprintf("%s %s: %s", t.To, when(&t.At), clip(t.Why, 80)), t.To + " " + when(&t.At)}) } } if c := s.Delivery.Check; c != nil && w.holds(c.At) { evs = append(evs, event{c.At, fmt.Sprintf("check %s %s: %s", c.Gate, when(&c.At), clip(c.Summary, 80)), fmt.Sprintf("check %s %s", c.Gate, when(&c.At))}) } if len(evs) == 0 { continue } sort.SliceStable(evs, func(i, j int) bool { return evs[i].at.Before(evs[j].at) }) var says []string for _, e := range evs { if e.brief != "" { says = append(says, e.brief) } else { says = append(says, e.line) } } state := s.Delivery.State mark := marks[StateMoving] switch { case state == "merged" || state == "delivered": mark = marks[StateDone] case strings.HasPrefix(state, "held"): mark = marks[StateStuck] case state == "closed": mark = marks[StateWaiting] } repo := s.Delivery.Repository if i := strings.LastIndex(repo, "/"); i >= 0 { repo = repo[i+1:] } line := fmt.Sprintf("%s %s#%d (%s) %s", mark, repo, s.Delivery.Number, strings.Join(names, ", "), strings.Join(says, " · ")) switch { case state == "merged" && s.Machines != "": line += ", " + s.Machines reached = true case !final(state): line += ", now " + state if s.Waits != "" { line += ", waits for " + s.Waits } } out = append(out, change{line: line, last: evs[len(evs)-1].at, events: evs}) } if reached { p.Warnings = append(p.Warnings, "mesh-delivery keeps no time a merge reached its machines: \"on the machines\" is as it reads now") } return out } // trackerMove is one change of `issue-tracker.changes`: an issue opened or moved, from what to what, by whom and when. type trackerMove struct { Number int `json:"number"` Kind string `json:"kind"` From string `json:"from"` To string `json:"to"` By string `json:"by"` Worker string `json:"worker"` At time.Time `json:"at"` } // trackerMoves asks the tracker for every issue's moves in the window, from its own log, by issue, oldest first. func (k *KB) trackerMoves(w window) (map[int][]trackerMove, error) { raw, err := k.Seats(ChangesVerb, map[string]any{"since": w.to.Sub(w.from).String()}) if err != nil { return nil, err } var r struct { Changes []trackerMove `json:"changes"` } if err := json.Unmarshal(raw, &r); err != nil { return nil, fmt.Errorf("answered something else: %v", err) } out := map[int][]trackerMove{} for _, m := range r.Changes { if w.holds(m.At) { out[m.Number] = append(out[m.Number], m) } } return out, nil } // ChangesVerb is the tracker's verb that answers its issues' moves over a window. const ChangesVerb = "seat:issue-tracker.changes"