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
+1 -1
View File
@@ -27,7 +27,7 @@ operator, it is accepted only on the operator's Approve, given on a channel that
| `import` | `dry-run`, `commit` | hq's decision records and research efforts at the commit the records module stands at, each whole at its own number |
| `return` | `number`, `reason` | drafted clauses sent back to be drafted again; refused for a record whose clauses another record changed or links by number |
| `withdraw` | `number`, `kind`, `why` | a reserved or proposed record withdrawn, as a new version naming why; its number stays spent. A pending review is cleared and a waiting operator's ask is ended. Refused for a record already accepted, superseded or withdrawn |
| `progress` | `of` (`mesh`, `design <n>`, `milestone <design>/<id>`, `issue <n>`, `decision <n>` or `ADR <n>`, `research <n>`), `detail` (`minimal` or `detailed`), `bound` | how far it has come (hq issue 1947): each child done ✓, moving ▶, waiting ⏸ or stuck ✗ with when it last moved, worked out from the issue tracker's rows and mesh-delivery's deliveries and groups, never written (ADR 0424 §1). `minimal` is one line per milestone, for a report every five minutes (two or three bus calls); `detailed` the report's tree with each issue's pull requests, the groups holding them and what it depends on. Milestones are read from the design's `## Milestones` table through the records module, cached by hq's commit, until the issue tracker holds milestone records (issue 1781); what the table does not say for certain is said in `warnings` |
| `progress` | `of` (`mesh`, `design <n>`, `milestone <design>/<id>`, `issue <n>`, `decision <n>` or `ADR <n>`, `research <n>`), `detail` (`minimal` or `detailed`), `bound` | how far it has come (hq issue 1947): each child done ✓, moving ▶, waiting ⏸ or stuck ✗ with when it last moved, worked out from the issue tracker's rows and mesh-delivery's deliveries and groups, never written (ADR 0424 §1). `minimal` is one line per milestone, for a report every five minutes (two or three bus calls); `detailed` the report's tree with each issue's pull requests, the groups holding them and what it depends on. A design's title and state are read through the records module, cached by hq's commit; its milestones and their issues are the issue tracker's milestone records (`issue-tracker.milestones`, issue 1781), asked once per answer; a design with none is refused, naming the seed (`issue-tracker.import` with `milestones`) |
`ask`, `get`, `holds` and `history` stay within `bound` bytes (4096 unless asked for, at most 65536) and say what they cut
and how to read the rest. The module's own tool `knowledge_health` answers its health (below).
@@ -1,15 +1,9 @@
package main
// A design as the progress verb reads it (novox/hq issue 1947): its title, its state as its frontmatter says it, the
// decisions it names, and its milestones from its `## Milestones` table. Designs are still documents in hq, read
// through the records module, until ADR 0423 makes them records of the knowledge base (issue 428); then this reads
// those records instead.
//
// Milestones are read from the design's table until the issue tracker holds milestone records (ADR 0493 §1, §4,
// built under issue 1781); when 1781 is delivered, the table reader is replaced by a read of those records and this
// parse is deleted (the lead of design 47, 2026-10-26). The Issues cell is prose, so what the parse cannot read
// for certain it says as a warning, never hides: an issue in two milestones (against ADR 0493 §2), a range written
// in words, a row naming no issue.
// A design as the progress verb reads it (novox/hq issue 1947): its title, its state as its frontmatter says it and
// the decisions it names, read through the records module until ADR 0423 makes designs records of the knowledge base
// (issue 428). Its milestones are the issue tracker's records (ADR 0493 §1, issue 1781), read in milestones.go: the
// table parse that guessed them from prose moved to the tracker's seed and is gone from here.
import (
"fmt"
@@ -30,31 +24,24 @@ type Design struct {
Status string
Decisions []string
Milestones []Milestone
Warnings []string
}
// Milestone is one row of a design's milestone table.
// Milestone is one milestone of a design, as the issue tracker keeps it.
type Milestone struct {
ID string
Outcome string
Issues []int
// Status is open or closed, with its Reason; a closed milestone holding no issue is done.
Status string
Reason string
}
var (
designFile = regexp.MustCompile(`^([0-9]{2,3})-[a-z0-9-]+\.md$`)
titleLine = regexp.MustCompile(`^#\s+[0-9]+\s+[—-]\s+(.+)$`)
decisionItem = regexp.MustCompile(`ADR ([0-9]{4})`)
// notAnIssue is a number the Issues cell names that is not an issue: a record, a clause, a test, a release, a pull
// request, a document.
notAnIssue = regexp.MustCompile(`(?i)(ADR|research|acceptance tests?|tests?|§|clauses?|release|design|to-be|as-is|playbook|version)\s*[0-9][0-9, and]*|[a-z-]*#[0-9]+|[0-9]{4}-[0-9]{2}-[0-9]{2}|[0-9]{1,2}:[0-9]{2}`)
rangeWords = regexp.MustCompile(`\b([0-9]{3,5}) to ([0-9]{3,5})\b`)
butOne = regexp.MustCompile(`\bbut ([0-9]{3,5})\b`)
issueNum = regexp.MustCompile(`\b[0-9]{3,5}\b`)
)
// MostRange is the widest range in words the parse expands; a wider one is said and not expanded.
const MostRange = 60
// parseDesign reads a design document.
func parseDesign(path, content string) *Design {
d := &Design{Path: path}
@@ -78,117 +65,14 @@ func parseDesign(path, content string) *Design {
}
}
}
inMilestones, header := false, -1
issueCol, outcomeCol := -1, -1
seen := map[int]string{}
for i := body; i < len(lines); i++ {
l := strings.TrimSpace(lines[i])
if d.Title == "" {
if m := titleLine.FindStringSubmatch(l); m != nil {
d.Title = strings.TrimSpace(m[1])
}
for i := body; i < len(lines) && d.Title == ""; i++ {
if m := titleLine.FindStringSubmatch(strings.TrimSpace(lines[i])); m != nil {
d.Title = strings.TrimSpace(m[1])
}
if strings.HasPrefix(l, "## ") {
inMilestones = strings.EqualFold(strings.TrimSpace(strings.TrimPrefix(l, "## ")), "Milestones")
header = -1
continue
}
if !inMilestones || !strings.HasPrefix(l, "|") {
continue
}
cells := cellsOf(l)
if header < 0 {
header = i
issueCol, outcomeCol = -1, -1
for c, h := range cells {
switch strings.ToLower(h) {
case "issues", "issue":
issueCol = c
case "outcome":
outcomeCol = c
}
}
continue
}
if i == header+1 && strings.Trim(l, "|-: ") == "" {
continue
}
if issueCol < 0 || issueCol >= len(cells) || len(cells) == 0 {
continue
}
m := Milestone{ID: cells[0]}
if outcomeCol >= 0 && outcomeCol < len(cells) {
m.Outcome = cells[outcomeCol]
}
var warn []string
m.Issues, warn = issuesOf(cells[issueCol])
for _, w := range warn {
d.Warnings = append(d.Warnings, fmt.Sprintf("milestone %s: %s", m.ID, w))
}
if len(m.Issues) == 0 {
d.Warnings = append(d.Warnings, fmt.Sprintf("milestone %s names no issue", m.ID))
}
for _, n := range m.Issues {
if other, ok := seen[n]; ok && other != m.ID {
d.Warnings = append(d.Warnings, fmt.Sprintf("issue %d is in milestones %s and %s; an issue belongs to at most one (ADR 0493 §2)", n, other, m.ID))
continue
}
seen[n] = m.ID
}
d.Milestones = append(d.Milestones, m)
}
return d
}
// issuesOf is the issue numbers a table's Issues cell names, in order, and what it could not read for certain.
func issuesOf(cell string) ([]int, []string) {
var warn []string
s := notAnIssue.ReplaceAllString(cell, " ")
var nums []int
for _, m := range rangeWords.FindAllStringSubmatch(s, -1) {
lo, _ := strconv.Atoi(m[1])
hi, _ := strconv.Atoi(m[2])
if hi < lo || hi-lo > MostRange {
warn = append(warn, fmt.Sprintf("%q is a range in words the reader does not expand", m[0]))
continue
}
warn = append(warn, fmt.Sprintf("%q is a range in words, read as the %d issues %d to %d", m[0], hi-lo+1, lo, hi))
for n := lo; n <= hi; n++ {
nums = append(nums, n)
}
}
but := map[int]bool{}
for _, m := range butOne.FindAllStringSubmatch(s, -1) {
n, _ := strconv.Atoi(m[1])
but[n] = true
}
s = rangeWords.ReplaceAllString(s, " ")
s = butOne.ReplaceAllString(s, " ")
for _, m := range issueNum.FindAllString(s, -1) {
n, _ := strconv.Atoi(m)
nums = append(nums, n)
}
out, have := []int{}, map[int]bool{}
for _, n := range nums {
if !have[n] && !but[n] {
have[n] = true
out = append(out, n)
}
}
return out, warn
}
func cellsOf(row string) []string {
row = strings.TrimSpace(row)
row = strings.TrimPrefix(row, "|")
row = strings.TrimSuffix(row, "|")
parts := strings.Split(row, "|")
for i := range parts {
parts[i] = strings.TrimSpace(parts[i])
}
return parts
}
func lastPart(p string) string {
if i := strings.LastIndex(p, "/"); i >= 0 {
return p[i+1:]
@@ -196,11 +80,12 @@ func lastPart(p string) string {
return p
}
// milestone finds a milestone by its id, as the table writes it, without regard to case or spaces.
// milestone finds a milestone by its id, without regard to case, or to spaces against hyphens.
func (d *Design) milestone(id string) *Milestone {
want := strings.ToLower(strings.Join(strings.Fields(id), " "))
// The tracker keeps an id's words joined by hyphens (3 fix is 3-fix); either is taken.
want := strings.ToLower(strings.Join(strings.Fields(id), "-"))
for i := range d.Milestones {
if strings.ToLower(strings.Join(strings.Fields(d.Milestones[i].ID), " ")) == want {
if strings.ToLower(strings.Join(strings.Fields(d.Milestones[i].ID), "-")) == want {
return &d.Milestones[i]
}
}
@@ -1129,9 +1129,8 @@ func Tools(k *KB, caller func(map[string]any) string) []stdio.Tool {
"and never written by anyone, with when it last moved. detail minimal (the default) gives one line per " +
"milestone, cheap enough for a report every five minutes; detailed gives the report's tree: each " +
"milestone not done with its issues, their pull requests, the groups holding them and what each waits " +
"for, for about every thirty. Milestones are read from the design's milestone table until the issue " +
"tracker holds milestone records (issue 1781); what the table does not say for certain is said in " +
"warnings. Within a bound it states, 4 KiB unless asked for more, saying what it cut.",
"for, for about every thirty. A design's milestones and their issues are the issue tracker's milestone " +
"records (its milestones); a design with none is refused, naming the seed. Within a bound it states, 4 KiB unless asked for more, saying what it cut.",
Input: schema([]string{"of"}, map[string]any{
"of": text("what: mesh, design 47, milestone 47/R6, issue 1945, decision 493 (or ADR 0493) or research 72"),
"detail": map[string]any{"type": "string", "enum": []string{DetailMinimal, DetailDetailed},
@@ -80,11 +80,11 @@ func TestItDeclaresAndHoldsTheKnowledgeBaseSeatOnceForTheMesh(t *testing.T) {
}
if !reflect.DeepEqual(m.Invokes, []string{"seat:issue-tracker.reserve", "claude-code.claude_code_judge", "claude-code.claude_code_judged", "records.records_status",
"records.records_list", "records.records_read", "seat:operator-channel.notify", "seat:issue-tracker.list", "seat:issue-tracker.get",
"seat:mesh-delivery.deliveries", "seat:mesh-delivery.show", "seat:mesh-delivery.groups"}) {
"seat:issue-tracker.milestones", "seat:mesh-delivery.deliveries", "seat:mesh-delivery.show", "seat:mesh-delivery.groups"}) {
t.Fatalf("invokes %v", m.Invokes)
}
src := ""
for _, f := range []string{"main.go", "hq.go", "progress.go"} {
for _, f := range []string{"main.go", "hq.go", "progress.go", "milestones.go"} {
src += readFile(t, f)
}
for _, inv := range m.Invokes {
@@ -0,0 +1,53 @@
package main
// A design's milestones are records of the issue tracker (novox/hq ADR 0493 §1, issue 1781): progress asks
// `issue-tracker.milestones` once per answer, on the bus, for each milestone with its issues, instead of reading the
// design's table through the records module.
import (
"encoding/json"
"fmt"
)
// MilestonesVerb is the tracker's verb that answers the milestones.
const MilestonesVerb = "seat:issue-tracker.milestones"
// trackerMilestone is a row of `issue-tracker.milestones`.
type trackerMilestone struct {
Design int `json:"design"`
ID string `json:"id"`
Outcome string `json:"outcome"`
Status string `json:"status"`
Reason string `json:"reason"`
Issues []int `json:"issues"`
}
// withMilestones is each design with its milestones as the tracker holds them, in their table's order; design 0 asks
// every design's. The designs are copies: the cache keeps what hq says, never what the tracker said.
func (k *KB) withMilestones(ds []*Design, design int) ([]*Design, error) {
body := map[string]any{}
if design != 0 {
body["design"] = design
}
raw, err := k.Seats(MilestonesVerb, body)
if err != nil {
return nil, fmt.Errorf("the issue tracker does not answer its milestones: %v", err)
}
var l struct {
Milestones []trackerMilestone `json:"milestones"`
}
if err := json.Unmarshal(raw, &l); err != nil {
return nil, fmt.Errorf("issue-tracker.milestones answered something else: %v", err)
}
by := map[int][]Milestone{}
for _, m := range l.Milestones {
by[m.Design] = append(by[m.Design], Milestone{ID: m.ID, Outcome: m.Outcome, Issues: m.Issues, Status: m.Status, Reason: m.Reason})
}
out := make([]*Design, 0, len(ds))
for _, d := range ds {
c := *d
c.Milestones = by[d.Number]
out = append(out, &c)
}
return out, nil
}
@@ -5,8 +5,9 @@ package main
// level, in two forms: minimal, one line per child, cheap enough for every five minutes; detailed, the tree of a
// lead's report with the pull requests, the held groups and what waits on whom, for about every thirty.
//
// It keeps no state of its own. It reads, each a request and reply over the bus: the designs through the records
// module (design.go, cached by hq's commit), the issues from the issue tracker (`issue-tracker.list`, whose rows
// It keeps no state of its own. It reads, each a request and reply over the bus: the designs' titles and states through
// the records module (design.go, cached by hq's commit), their milestones from the issue tracker (milestones.go,
// issue 1781), the issues from the issue tracker (`issue-tracker.list`, whose rows
// say each issue's delivery and when it last moved), and the deliveries and groups from mesh-delivery.
import (
@@ -287,6 +288,9 @@ func milestoneItems(d *Design, rows map[int]trackerRow, missing map[int]bool) ([
}
}
rollUp(&mi, kids)
if m.Status == "closed" && len(kids) == 0 {
mi.State, mi.Why = StateDone, "closed: "+m.Reason
}
mi.Children = kids
items = append(items, mi)
}
@@ -303,9 +307,15 @@ func (k *KB) progressDesign(p *Progress, number int, milestone string) error {
if err != nil {
return err
}
if ds, err = k.withMilestones(ds, number); err != nil {
return err
}
d := ds[0]
p.Source = fmt.Sprintf("the milestone table of %s at hq %s (until the tracker's milestone records, issue 1781)", d.Path, short(commit))
p.Warnings = append(p.Warnings, d.Warnings...)
if len(d.Milestones) == 0 {
return fmt.Errorf("design %d has no milestone records in the issue tracker: seed them from its table with "+
"issue-tracker.import milestones %d", number, number)
}
p.Source = fmt.Sprintf("the issue tracker's milestone records of design %d, and %s at hq %s", number, d.Path, short(commit))
ms := d
if milestone != "" {
m := d.milestone(milestone)
@@ -354,7 +364,10 @@ func (k *KB) progressMesh(p *Progress) error {
if err != nil {
return err
}
p.Source = fmt.Sprintf("the designs in %s at hq %s", DesignsFolder, short(commit))
if ds, err = k.withMilestones(ds, 0); err != nil {
return err
}
p.Source = fmt.Sprintf("the issue tracker's milestone records, and the designs in %s at hq %s", DesignsFolder, short(commit))
var with []*Design
var without []string
for _, d := range ds {
@@ -12,12 +12,12 @@ import (
)
// 1947/AC1: the knowledge base running whole, as the runtime launches it, answers progress over the bus: it reads
// design 47 through the records module and asks the issue tracker and mesh-delivery on their seats' subjects,
// design 47 through the records module, its milestones from the issue tracker (issue 1781), and asks the issue tracker and mesh-delivery on their seats' subjects,
// played here from what the live seats answered (testdata/progress).
func TestProgressOverTheBus(t *testing.T) {
w := newBusWorld(t)
seats := newSeatsWorld(t)
for _, key := range []string{"seat:issue-tracker.list", "seat:issue-tracker.get", "seat:mesh-delivery.deliveries",
for _, key := range []string{"seat:issue-tracker.list", "seat:issue-tracker.get", MilestonesVerb, "seat:mesh-delivery.deliveries",
"seat:mesh-delivery.groups", "seat:mesh-delivery.show"} {
key := key
if _, err := w.nc.Subscribe("ask."+key, func(m *nats.Msg) {
@@ -45,7 +45,7 @@ func TestProgressOverTheBus(t *testing.T) {
}
lines := strings.Split(p.Tree, "\n")
if len(raw) > DefaultBound || p.Detail != DetailMinimal || !strings.HasPrefix(lines[0], "✗ design 47 · Delivery: from a commit to delivered — ") ||
!strings.Contains(p.Tree, "✗ R6 4/7 1945 fixing since") || !strings.Contains(strings.Join(p.Warnings, "\n"), "issue 1209 is in milestones R3b and R3c") {
!strings.Contains(p.Tree, "✗ R6 4/7 1945 fixing since") || !strings.Contains(p.Source, "the issue tracker's milestone records of design 47") {
t.Fatalf("%d bytes:\n%s\n%v", len(raw), p.Tree, p.Warnings)
}
raw, err = b.call(Seat+".progress", map[string]any{"of": "milestone 47/R6", "detail": "detailed"})
@@ -63,7 +63,7 @@ func TestProgressOverTheBus(t *testing.T) {
}
seats.mu.Lock()
defer seats.mu.Unlock()
if seats.asked["seat:issue-tracker.list"] != 2 || seats.asked["seat:mesh-delivery.groups"] != 1 {
if seats.asked["seat:issue-tracker.list"] != 2 || seats.asked["seat:mesh-delivery.groups"] != 1 || seats.asked[MilestonesVerb] != 2 {
t.Fatalf("asked %v", seats.asked)
}
}
@@ -45,8 +45,10 @@ type seatsWorld struct {
mu sync.Mutex
rows map[int]json.RawMessage
linked map[string][]int
asked map[string]int
files map[string]json.RawMessage
// milestones is the tracker's milestone records (issue 1781).
milestones json.RawMessage
asked map[string]int
files map[string]json.RawMessage
}
func newSeatsWorld(t *testing.T) *seatsWorld {
@@ -61,6 +63,7 @@ func newSeatsWorld(t *testing.T) *seatsWorld {
_ = json.Unmarshal(r, &n)
s.rows[n.Number] = r
}
s.milestones = readFixture(t, "tracker-milestones-47.json")
for _, f := range []string{"deliveries.json", "groups.json", "show-car-hunter-2.json"} {
s.files[f] = readFixture(t, f)
}
@@ -86,9 +89,15 @@ func (s *seatsWorld) ask(key string, body any) (json.RawMessage, error) {
Linked string `json:"linked"`
Number int `json:"number"`
ID string `json:"id"`
Design int `json:"design"`
}
_ = json.Unmarshal(raw, &a)
switch key {
case MilestonesVerb:
if a.Design != 0 && a.Design != 47 {
return json.RawMessage(`{"milestones": []}`), nil
}
return s.milestones, nil
case "seat:issue-tracker.list":
numbers := a.Numbers
if a.Linked != "" {
@@ -131,31 +140,37 @@ func (c *countingRecords) Read(p string) (string, string, error) {
return content, c.commit, err
}
// 1947/AC2: the milestone table of design 47, as hq holds it, read into milestones with their issues, and what the
// prose does not say for certain said as warnings.
func TestTheDesignReaderReadsTheMilestoneTable(t *testing.T) {
// 1947/AC2, as issue 1781 changed it: a design's title, state and decisions are hq's; its milestones are the issue
// tracker's records, ordered as its table, an id found with spaces or hyphens, and a closed one holding no issue done.
func TestADesignsMilestonesAreTheTrackersRecords(t *testing.T) {
content, _, err := progressRecords().Read(DesignsFolder + "/47-delivery-from-commit-to-delivered.md")
if err != nil {
t.Fatal(err)
}
d := parseDesign(DesignsFolder+"/47-delivery-from-commit-to-delivered.md", content)
if d.Number != 47 || d.Title != "Delivery: from a commit to delivered" || d.Status != "in-progress" || !contains(d.Decisions, "ADR 0276") {
t.Fatalf("design %d %q %q %v", d.Number, d.Title, d.Status, d.Decisions)
if d.Number != 47 || d.Title != "Delivery: from a commit to delivered" || d.Status != "in-progress" || !contains(d.Decisions, "ADR 0276") ||
len(d.Milestones) != 0 {
t.Fatalf("design %d %q %q %v, %d milestones read from hq", d.Number, d.Title, d.Status, d.Decisions, len(d.Milestones))
}
if len(d.Milestones) != 29 {
t.Fatalf("%d milestones", len(d.Milestones))
seats := newSeatsWorld(t)
k := &KB{Seats: seats.ask}
ds, err := k.withMilestones([]*Design{d}, 47)
if err != nil {
t.Fatal(err)
}
if len(ds[0].Milestones) != 29 || len(d.Milestones) != 0 || seats.asked[MilestonesVerb] != 1 {
t.Fatalf("%d milestones; the cached design holds %d", len(ds[0].Milestones), len(d.Milestones))
}
for _, c := range []struct {
id string
want []int
}{
{"1", []int{973, 525}},
{"2b", []int{1025, 1019}},
{"R6", []int{1942, 1945, 1943, 1944, 1946, 1906, 1130}},
{"r4d", []int{}},
{"3 fix", []int{1082, 1049, 1047, 1083, 1079, 1084}},
} {
m := d.milestone(c.id)
m := ds[0].milestone(c.id)
if m == nil {
t.Fatalf("no milestone %s", c.id)
}
@@ -163,25 +178,10 @@ func TestTheDesignReaderReadsTheMilestoneTable(t *testing.T) {
t.Errorf("milestone %s: %v, want %v", c.id, m.Issues, c.want)
}
}
if r4a := d.milestone("R4a"); len(r4a.Issues) != 14 || !containsInt(r4a.Issues, 1360) {
t.Errorf("R4a's range 1353 to 1364 read as %v", r4a.Issues)
}
warned := strings.Join(d.Warnings, "\n")
for _, w := range []string{"issue 1209 is in milestones R3b and R3c", "issue 971 is in milestones 6 and R4c",
`"1353 to 1364" is a range in words, read as the 12 issues`, "milestone R4d names no issue"} {
if !strings.Contains(warned, w) {
t.Errorf("no warning %q in\n%s", w, warned)
}
}
// Design 57's range with an exception, and a design with no milestone table.
content, _, _ = progressRecords().Read(DesignsFolder + "/57-building-config-and-provisioning.md")
d57 := parseDesign(DesignsFolder+"/57-building-config-and-provisioning.md", content)
if m := d57.milestone("1"); m == nil || containsInt(m.Issues, 659) || !containsInt(m.Issues, 678) || !containsInt(m.Issues, 685) {
t.Errorf("design 57 milestone 1: %+v", d57.Milestones)
}
content, _, _ = progressRecords().Read(DesignsFolder + "/53-the-issue-tracker.md")
if d53 := parseDesign(DesignsFolder+"/53-the-issue-tracker.md", content); len(d53.Milestones) != 0 || d53.Status != "in-progress" {
t.Errorf("design 53: %+v", d53.Milestones)
closed := &Design{Milestones: []Milestone{{ID: "6", Status: "closed", Reason: "folded into release 4"}, {ID: "R4d", Status: "open"}}}
items, _ := milestoneItems(closed, nil, nil)
if items[0].State != StateDone || items[1].State != StateWaiting {
t.Fatalf("%+v", items)
}
}
@@ -207,8 +207,8 @@ func TestDesignsAreReadAgainOnlyWhenHqMoves(t *testing.T) {
if rec.reads != 3 {
t.Fatalf("read %d documents for the mesh, want 3", rec.reads)
}
if !strings.Contains(p.Tree, "3 designs in progress, 2 with milestones") || !strings.Contains(p.Tree, "design 47 · Delivery: from a commit to delivered") ||
!strings.Contains(p.Tree, "1 designs in progress name no milestones (issue 845): 53") {
if !strings.Contains(p.Tree, "3 designs in progress, 1 with milestones") || !strings.Contains(p.Tree, "design 47 · Delivery: from a commit to delivered") ||
!strings.Contains(p.Tree, "2 designs in progress name no milestones (issue 845): 53, 57") {
t.Fatalf("mesh:\n%s", p.Tree)
}
for range 2 {
@@ -303,7 +303,7 @@ func TestProgressOfADesignMinimalAndDetailed(t *testing.T) {
t.Fatal(err)
}
raw, _ := json.Marshal(p)
if len(raw) > DefaultBound || p.Detail != DetailMinimal || !strings.Contains(p.Source, "issue 1781") {
if len(raw) > DefaultBound || p.Detail != DetailMinimal || !strings.Contains(p.Source, "the issue tracker's milestone records of design 47") {
t.Fatalf("%d bytes, %s, %s", len(raw), p.Detail, p.Source)
}
if seats.asked["seat:issue-tracker.list"] != 1 || seats.asked["seat:mesh-delivery.deliveries"] != 0 || seats.asked["seat:issue-tracker.get"] != 0 {
@@ -316,7 +316,7 @@ func TestProgressOfADesignMinimalAndDetailed(t *testing.T) {
if !strings.HasPrefix(lines[0], "✗ design 47 · Delivery: from a commit to delivered — ") {
t.Fatalf("first line %q", lines[0])
}
for _, want := range []string{"✗ R6 4/7 1945 fixing since", "✗ 3 fix 0/1 1049 fixing, worker fix-chain-cat@shanks stale since"} {
for _, want := range []string{"✗ R6 4/7 1945 fixing since", "✗ 3-fix 0/1 1049 fixing, worker fix-chain-cat@shanks stale since"} {
if !strings.Contains(p.Tree, want) {
t.Errorf("no %q in\n%s", want, p.Tree)
}
@@ -346,7 +346,7 @@ func TestProgressOfADesignMinimalAndDetailed(t *testing.T) {
if raw, _ := json.Marshal(small); len(raw) > LeastBound || !strings.Contains(small.Cut, "raise bound") {
t.Fatalf("%d bytes within %d; cut %q", len(raw), LeastBound, small.Cut)
}
for _, bad := range []ProgressAsk{{Of: "design"}, {Of: "milestone 47/R99"}, {Of: "design 47", Detail: "full"}} {
for _, bad := range []ProgressAsk{{Of: "design"}, {Of: "milestone 47/R99"}, {Of: "design 47", Detail: "full"}, {Of: "design 57"}} {
if _, err := k.Progress(bad); err == nil {
t.Errorf("%+v was answered", bad)
}
@@ -5,3 +5,6 @@ Captured for the progress verb's tests (novox/hq issue 1947), never written by h
2026-10-26 16:2x (before rows said `moved`).
- `deliveries.json`, `groups.json`, `show-car-hunter-2.json`: parts of `mesh-delivery.deliveries`, `.groups` and
`.show` as the live delivery seat answered them on 2026-10-26 16:2x, cut to the members named here.
- `tracker-milestones-47.json`: the answer of `issue-tracker.milestones` for design 47 (issue 1781), made by running
the tracker's own table parse (mesh-issues `parseTable`, its seed) over `hq/`'s design 47, each issue two rows name
left in neither, as the seed refuses it; the tracker held no milestone records yet to capture from.
@@ -0,0 +1,418 @@
{
"milestones": [
{
"design": 47,
"id": "1",
"issues": [
973,
525
],
"order": 1,
"outcome": "A merge during a running delivery is never dropped and needs no person's release; the merge window lives on the bus; a rollout that shares nothing with an open one runs beside it",
"ref": "47/1",
"status": "open"
},
{
"design": 47,
"id": "2",
"issues": [
974,
975,
976
],
"order": 2,
"outcome": "A ready mark is acted on within seconds, and each pull request has at most one waiting check",
"ref": "47/2",
"status": "open"
},
{
"design": 47,
"id": "2b",
"issues": [
1025,
1019
],
"order": 3,
"outcome": "build-agent and route-proxy live whole in mesh-controller (ADR 0420, accepted 2026-10-17): the controller accepts a reviewed move, mesh-delivery announces each merge with its Approve, and the two modules move; built as two releases before the rule that a milestone is one release",
"ref": "47/2b",
"status": "open"
},
{
"design": 47,
"id": "3",
"issues": [
977,
978,
1022,
895,
1046,
605,
1021
],
"order": 4,
"outcome": "A feature with a new seat verb across the controller and the catalogue is one group, one batch, one delivery plan and one Approve: no order lines, no ready mark, each module built and sent once",
"ref": "47/3",
"status": "open"
},
{
"design": 47,
"id": "4",
"issues": [
980,
1065,
1066,
1067,
1068
],
"order": 5,
"outcome": "A delivery backs up the data it moves first, and a failed backup holds it and is said",
"ref": "47/4",
"status": "open"
},
{
"design": 47,
"id": "3-fix",
"issues": [
1082,
1049,
1047,
1083,
1079,
1084
],
"order": 6,
"outcome": "A group approved together merges in one pass and goes out in one delivery plan, also when its members' tests read each other's repositories",
"ref": "47/3-fix",
"status": "open"
},
{
"design": 47,
"id": "5",
"issues": [
981
],
"order": 7,
"outcome": "One read of a module on a node names the commit, build and rollout that put it there",
"ref": "47/5",
"status": "open"
},
{
"design": 47,
"id": "5b",
"issues": [
1117,
1124,
1128
],
"order": 8,
"outcome": "Decided: building and delivering leave the controller for parts that watch and write keys in the repository `mesh-delivery`, landing in six releases (ADR 0429); the forge module makes that repository from its config, protected before any team reaches it (ADR 0428)",
"ref": "47/5b",
"status": "open"
},
{
"design": 47,
"id": "6",
"issues": [],
"order": 9,
"outcome": "Folded into release 4 by ADR 0429 §10 (the fetch over SSH in R4c, the cut-over in R4e): installed toolchains, builders fetching over SSH with their own key, and an SDK change shipping with its users are built in the builder's new home, so the builder is changed once",
"ref": "47/6",
"status": "open"
},
{
"design": 47,
"id": "R1a",
"issues": [
1131
],
"order": 10,
"outcome": "The node-engine, the controller and the parts can read one layout of the have, presence, pulse and stuck keys: mesh-sdk ships it before its users",
"ref": "47/R1a",
"status": "open"
},
{
"design": 47,
"id": "R1b",
"issues": [
1132,
1142,
1136,
1145,
1146,
1147,
1160,
1162
],
"order": 11,
"outcome": "Every node writes what it runs of each module, on its own keys and nobody else's, and the bus says to one part when a connection opens and closes",
"ref": "47/R1b",
"status": "open"
},
{
"design": 47,
"id": "R1c",
"issues": [
1134,
1137
],
"order": 12,
"outcome": "The operator sees every module on every node, with its build, phase and health, in `mesh-delivery-cli` (aliased `nox-delivery`) and its web view",
"ref": "47/R1c",
"status": "open"
},
{
"design": 47,
"id": "R2a",
"issues": [
1153
],
"order": 13,
"outcome": "The watchman and the controller can read one layout of the stuck keys: mesh-sdk ships it before its users",
"ref": "47/R2a",
"status": "open"
},
{
"design": 47,
"id": "R2b",
"issues": [
1135,
1154,
1155,
1156,
1157,
1159
],
"order": 14,
"outcome": "A part, node or check that stops moving is raised as a condition within two minutes, with no timer in a part",
"ref": "47/R2b",
"status": "open"
},
{
"design": 47,
"id": "R2c",
"issues": [
1183
],
"order": 15,
"outcome": "The controller says when it last heard each machine from presence alone, and no longer hears the alive message",
"ref": "47/R2c",
"status": "open"
},
{
"design": 47,
"id": "R2d",
"issues": [
1158
],
"order": 16,
"outcome": "The node-engine sends no alive message",
"ref": "47/R2d",
"status": "open"
},
{
"design": 47,
"id": "R3a",
"issues": [
1200,
1201,
1202,
1203,
1204,
1208
],
"order": 17,
"outcome": "The gate, the bridge, the indexer and the teller can read one layout of the source revision, candidates, modules, merges, asks, answers, owed acts, forge messages and conditions, and one function computes the source fingerprint: mesh-sdk ships them before their users (ADR 0437)",
"ref": "47/R3a",
"status": "open"
},
{
"design": 47,
"id": "R3b",
"issues": [
1210,
1211,
1212,
1213,
1214,
1215,
1216,
1217,
1218
],
"order": 18,
"outcome": "The controller finds the delivery seat's holder by its claim and records each build's fingerprint inputs; the router takes conditions on their one subject; the operator sees no change (ADR 0437)",
"ref": "47/R3b",
"status": "open"
},
{
"design": 47,
"id": "R3c",
"issues": [
1235,
1236,
1237,
1238,
1239,
1240,
1241,
1234,
1223,
1233,
1242
],
"order": 19,
"outcome": "The forge bridge, the indexer and the teller run beside today's mesh-delivery and write their keys; the view shows modules against the controller's source fingerprints; nothing is merged by them yet (ADR 0437)",
"ref": "47/R3c",
"status": "open"
},
{
"design": 47,
"id": "R3d",
"issues": [
1321,
1322,
1323,
1324,
1325,
1326,
1317,
1314,
1315,
1316
],
"order": 20,
"outcome": "The gate's keys can carry what the forge needs and the controller answers a what-if as data, so the gate has what it reads before it runs; a failed delivery plan asks the operator Retry on the phone, and a seat whose holder is assigned nowhere passes to its only claimant; the operator sees only the Retry notice (ADR 0450, ADR 0449)",
"ref": "47/R3d",
"status": "open"
},
{
"design": 47,
"id": "R3e",
"issues": [],
"order": 21,
"outcome": "A group reaches the mesh as one source revision written by the gate; the merge's Approve is asked by the teller and answered on the phone until issue 953; the view shows the open asks; today's module mesh-delivery is carried over into the gate when it first holds the seat and ends; no step needs the operator's terminal (ADR 0437, ADR 0450)",
"ref": "47/R3e",
"status": "open"
},
{
"design": 47,
"id": "R4a",
"issues": [
1353,
1354,
1355,
1356,
1357,
1358,
1359,
1360,
1361,
1362,
1363,
1364,
1298,
1308
],
"order": 22,
"outcome": "The shared layouts of release 4 are on mesh-sdk's main before their users pin them: the catalogue, the validator and the composition over a plain input, the fingerprint's new form, and the key layouts of the builder, the checker, the kept verdicts, the manifests and the candidates; the node-engine reports the user namespaces capability (ADR 0453 clauses 1, 2, 3)",
"ref": "47/R4a",
"status": "open"
},
{
"design": 47,
"id": "R4b",
"issues": [
1375,
1337
],
"order": 23,
"outcome": "mesh-controller and the node-engine use the catalogue, the validator and the composition from mesh-sdk and delete their copies; the controller composes every node both ways on the live facts and the two agree before this is delivered; it writes the grants and credential bindings the assigner reads; its unit level meets its bound (ADR 0453 clauses 1, 3)",
"ref": "47/R4b",
"status": "open"
},
{
"design": 47,
"id": "R4c",
"issues": [
1309,
1287,
1288,
1289,
1290,
1291,
1292,
1251,
1293
],
"order": 24,
"outcome": "The new parts run dark beside today's: the indexer writes the manifests by digest and the fingerprints in the new form beside today's (its own, first delivery), then the build modules (the Go build module, the Node and TypeScript build module: toolchain, sandbox, fetch, digest, test, build), the builder, the checker writing `mesh/check` as a status the forge does not require, and the assigner's composition check writing into the checker's report; the build seat's accounts are decided by a record before the build modules' pull requests are opened (ADR 0453 clauses 4, 6, 7, 8, 11, 12, 16; ADR 0465 §4); the builder is ADR 0478's. It is delivered in ADR 0465 §5's order: mesh-sdk's layouts; the forge bridge and the indexer with the controller's facts in format 2; the build modules; the builder; the checker; the bus user list's composer moved into mesh-sdk; the assigner's composition check; the fetch and run steps. The build modules each run as an account of their own with a forge key known read-only on one forge user (ADR 0466); each builds with the node's installed toolchain at the agreed version and does not refuse on a node with another (ADR 0477, issue 1572), answers a long step with its id and finishes it on a key, is called on a named machine, and runs on the four nodes that run the build agent today (ADR 0469)",
"ref": "47/R4c",
"status": "open"
},
{
"design": 47,
"id": "R4d",
"issues": [],
"order": 25,
"outcome": "Four modules stop being built from a Dockerfile: route-proxy, nats, n8n and mail's autoconfiguration module (automx, replaced by a Go program), each rework its own release step, nats in a delivery of its own; free between R4a and R4e and never in the same delivery as another release (ADR 0452, ADR 0453 clauses 13, 14, 16)",
"ref": "47/R4d",
"status": "open"
},
{
"design": 47,
"id": "R4e",
"issues": [
1304
],
"order": 26,
"outcome": "The controller asks no build: a build is keyed by its fingerprint, the new form becomes the only one as one group, and a merge builds nothing its check built; builders fetch over SSH, so an SDK change ships with its users; the vendored copies leave mesh-controller and mesh-sdk; delivered only once the builder's artifact digests equal build-agent's (ADR 0453 clauses 2, 4, 5, 6)",
"ref": "47/R4e",
"status": "open"
},
{
"design": 47,
"id": "R4f",
"issues": [
709,
1300,
1301,
1302
],
"order": 27,
"outcome": "A pull request carries one required status that says what it proved and how long each part took; the controller's composition check and its replays are deleted with the two old statuses; the controller's deletions R3e makes possible go; each level's times are measured, and a bound fails a repository from the first check that meets it (ADR 0447, ADR 0450 §8, ADR 0453 clauses 4, 9, 10)",
"ref": "47/R4f",
"status": "open"
},
{
"design": 47,
"id": "R4g",
"issues": [
1887,
1295,
1296,
1303
],
"order": 28,
"outcome": "The last milestone of release 4 and release 5 as one, with one switch-over and no rehearsal (ADR 0524 §1): builds and checks are asks on streams that each build node pulls when it has a free slot, deliveries first, with nothing holding checks and no controller queue (ADR 0525); the assigner is made with its composition check, writes each node's wants compared with what the controller sends and every difference, and the view answers the asks that need no proof of who answered (ADR 0528, ADR 0437 §10); no module's fingerprint takes the builder (ADR 0530); the scripts of the repositories a module is built from, build-agent with its socket and Dockerfile code, the toolchain images and the controller's build code go, and the builder delivers its own module, the build modules and the controller from its keys (ADR 0453 clauses 5, 8, 10; ADR 0524 §5, §7); the ways to deliver without the builder are recorded (ADR 0526); hq's four checks are Go tests (ADR 0527); each repository's script goes (ADR 0529); the word for the composition check's predecessor is struck. Its pull requests are opened only after five readings, the revert path read, not shown on a test merge (ADR 0524 §3)",
"ref": "47/R4g",
"status": "open"
},
{
"design": 47,
"id": "R6",
"issues": [
1942,
1945,
1943,
1944,
1946,
1906,
1130
],
"order": 29,
"outcome": "Each node applies from its wants the compositions its own node agent composes (ADR 0547 §2; as released it applied the controller's signed ones), a new version reaches the module's canary first and the rest once it is healthy there, a move a person must allow is asked before its want, the view says what is moving, settled, stuck and delivered, and the plans are deleted from the code; switched over by release 5's last plan (ADR 0539)",
"ref": "47/R6",
"status": "open"
}
],
"said": "generated"
}
+1
View File
@@ -87,6 +87,7 @@
"seat:operator-channel.notify",
"seat:issue-tracker.list",
"seat:issue-tracker.get",
"seat:issue-tracker.milestones",
"seat:mesh-delivery.deliveries",
"seat:mesh-delivery.show",
"seat:mesh-delivery.groups"