Answer the review of hq issues 348 and 349: gaps, parts, the newest merge in any state
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered

- A reopened fault keeps its gaps: it was there at a send unless the send
  fell in one, so a send that breaks a machine recovered before it still
  fails its gate (A2).
- An undecided part holds only the conditions that name it (A4).
- D2 holds a silent resolver for the next run again, refused or not: a
  burst of refusals is also a restart (A3).
- A late merge is planned at the newest planned merge of its branch in any
  state, not only an open one (A1); merge times to the nanosecond (A5).
This commit is contained in:
2026-10-09 15:00:56 +02:00
committed by jochen
parent c3c56a69e2
commit 58cb586c37
11 changed files with 347 additions and 179 deletions
+14 -23
View File
@@ -188,7 +188,7 @@ func planOfMerge(m link.SourceMoved, moved []string, edges []inventory.Edge) inv
for _, name := range set {
modules[name] = &inventory.PlanModule{}
}
merged, _ := time.Parse(time.RFC3339, m.MergedAt)
merged, _ := time.Parse(time.RFC3339Nano, m.MergedAt)
return inventory.Plan{
ID: fmt.Sprintf("plan-%d", time.Now().UnixNano()),
Repository: m.Owner + "/" + m.Repo,
@@ -228,8 +228,8 @@ func planOfMerge(m link.SourceMoved, moved []string, edges []inventory.Edge) inv
// it — and that later-made plan of the earlier commit superseded the later merge's, and built what it
// folded in from the commit before the later merge: twice on 2026-10-09, once a security fix. So where
// both plans know when their merge was made, that decides; only where one does not do the plans' own
// times. A merge older than an open plan of its branch never reaches here at its own commit: it is
// planned at the newest open plan's commit, which contains it (newestOnTheBranch).
// times. A merge older than the newest planned merge of its branch never reaches here at its own commit:
// it is planned at that merge's commit, which contains it (laterOnTheBranch).
func supersededBy(newer inventory.Plan, open []inventory.Plan, rollsOut func(string) bool) ([]string, []inventory.Plan) {
folded := map[string]bool{}
var closed []inventory.Plan
@@ -274,27 +274,18 @@ func earlierOnTheBranch(a, b inventory.Plan) bool {
return a.Created.Before(b.Created)
}
// newestOnTheBranch is the open plan of m's repository and branch whose merge was made after m's, the
// newest of them; false when none was. Merges into one branch are made one on the other, so that plan's
// commit contains m's change: m is planned there, and the newest commit of the branch is what is built
// (novox/hq issue 349). Pure.
func newestOnTheBranch(m link.SourceMoved, open []inventory.Plan) (inventory.Plan, bool) {
merged, err := time.Parse(time.RFC3339, m.MergedAt)
if err != nil {
return inventory.Plan{}, false
// laterOnTheBranch says the plan newest answers a merge into m's branch made after m: then newest's commit
// contains m's change, and m is planned there, so the newest commit of the branch is what is built (novox/hq
// issue 349). newest is the newest merge's plan of the branch in any state: a later plan already done
// built what it moved at its commit, and m planned at its own would build m's dependents back at the older
// one. Pure.
func laterOnTheBranch(m link.SourceMoved, newest inventory.Plan) bool {
merged, err := time.Parse(time.RFC3339Nano, m.MergedAt)
if err != nil || newest.Release != nil || newest.Commit == m.Commit {
return false
}
var newest inventory.Plan
found := false
for _, p := range open {
if !p.Open() || p.Release != nil || p.Merged.IsZero() || !strings.EqualFold(p.Repository, m.Owner+"/"+m.Repo) ||
p.Branch != m.Base || !p.Merged.After(merged) {
continue
}
if !found || p.Merged.After(newest.Merged) {
newest, found = p, true
}
}
return newest, found
return strings.EqualFold(newest.Repository, m.Owner+"/"+m.Repo) && newest.Branch == m.Base &&
newest.Merged.After(merged)
}
// gates is what the next tier needs running from this one: a module of the tier that a later