mesh-issues and mesh-knowledge: milestones are records of the issue tracker, and progress reads them (ADR 0493 §1 to §4, issue 1781)
mesh/delivery merged: source.main moved to novox/mesh-catalog@5a4ac4991395
mesh/delivery-group group feat/1781-milestone-records merged: every member is merged
mesh/check checking 76e4ec4dc680 on 26908a89506f | novox/mesh-catalog#506 in group feat/1781-milestone-records

mesh-issues keeps each design's milestone in the state milestones (history 5), says milestone-set and
milestone-moved, and serves milestones, milestone-set and milestone-move. An issue's milestone is its own link
of kind milestone, so at most one milestone per issue is checked inside the issue's own write; kind commit
links a fixing commit. The triage links no milestone, pull request or commit. import with milestones=N seeds
a design's table once. mesh-knowledge's progress reads issue-tracker.milestones and its table parse is gone.

Squashed from four commits so the branch history does not carry a compiled bundle that was committed by
mistake (6 MB); the path is now in .gitignore.
This commit is contained in:
2026-10-26 18:42:51 +01:00
parent 26908a8950
commit 76e4ec4dc6
24 changed files with 2912 additions and 196 deletions
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"math/big"
"slices"
"sort"
"strings"
"sync"
@@ -650,6 +651,22 @@ func (t *Tracker) link(by string, a LinkAsk) (*Linked, error) {
}
}
}
if by == GroomBy && slices.Contains(madeByPeople, kind) {
return nil, fmt.Errorf("the triage links no %s: that link is made by the one who did the work (ADR 0493 §4)", kind)
}
if kind == LinkMilestone && !a.Remove {
d, id, _ := parseMilestoneRef(target)
m, _, err := t.readMilestone(d, id)
if err != nil {
return nil, err
}
if m == nil {
return nil, fmt.Errorf("there is no milestone %s: it is made from its design's table (milestone-set, or import with milestones)", target)
}
if m.Status != MilestoneOpen {
return nil, fmt.Errorf("milestone %s is %s: a fix found on the way joins its design's open milestone, or a milestone of its own (ADR 0493 §3)", target, m.Status)
}
}
at := t.now()
is, err := t.change(a.Number, Act{Op: EventLinked, By: by}, func(is *Issue) error {
for i, l := range is.Links {
@@ -665,6 +682,12 @@ func (t *Tracker) link(by string, a LinkAsk) (*Linked, error) {
if a.Remove {
return fmt.Errorf("issue %d has no %s link to %s to remove", is.Number, kind, target)
}
if kind == LinkMilestone {
if in := milestoneOf(is); in != "" {
return fmt.Errorf("issue %d is in milestone %s already, and an issue belongs to at most one (ADR 0493 §2): "+
"remove that link first to move it", is.Number, in)
}
}
if len(is.Links) >= MostLinks {
return fmt.Errorf("issue %d has %d links, the most one issue keeps", is.Number, MostLinks)
}