Send a plan's tier to each machine once, and blame no module for its machine (hq issue 281)
mesh/merge-gate error: the check could not run: a throwaway postgres:17-alpine could not be raised: docker run --label mesh.build=build-1791318263948250337…
mesh/delivery delivered

This commit is contained in:
jochen
2026-10-06 22:20:43 +02:00
parent 9b6b0c5686
commit d6e0a8250a
6 changed files with 911 additions and 76 deletions
+26 -6
View File
@@ -255,7 +255,11 @@ func ungatedIn(ctx context.Context, open *stores, names []string, addedHolder st
// gatedSend sends one machine everything waiting there, under a gate that judges it all: what moved is
// answered, with the build each moved to, for the gate to judge and to put back. Refused while a plan that
// has started walks one of those builds elsewhere: that plan sends it here once its gate passed.
func gatedSend(ctx context.Context, open *stores, node string, own *inventory.CarriedMove) ([]inventory.CarriedMove, []string, error) {
//
// owns are the moves the send exists for — every module of a plan's tier whose first machine this is, in
// one send (novox/hq issue 281): a send carries the machine's whole declaration (ADR 0221), so a send per
// module was the same declaration sent again and again, each one setting aside the one before.
func gatedSend(ctx context.Context, open *stores, node string, owns []inventory.CarriedMove) ([]inventory.CarriedMove, []string, error) {
inv := open.inventory
f, err := readMoveFacts(ctx, inv)
if err != nil {
@@ -265,8 +269,11 @@ func gatedSend(ctx context.Context, open *stores, node string, own *inventory.Ca
if err != nil {
return nil, nil, err
}
own := func(module string) bool {
return slices.ContainsFunc(owns, func(o inventory.CarriedMove) bool { return o.Module == module })
}
for _, mv := range moves {
if own != nil && mv.Module == own.Module {
if own(mv.Module) {
continue
}
if id := f.walkedBy(mv.Module, node); id != "" {
@@ -274,10 +281,16 @@ func gatedSend(ctx context.Context, open *stores, node string, own *inventory.Ca
mv.Module, short(mv.To), node, id)
}
}
if own != nil && !slices.ContainsFunc(moves, func(mv inventory.CarriedMove) bool { return mv.Module == own.Module }) {
moves = append(moves, *own)
for _, o := range owns {
i := slices.IndexFunc(moves, func(mv inventory.CarriedMove) bool { return mv.Module == o.Module })
switch {
case i < 0:
moves = append(moves, o)
case moves[i].Build == "":
moves[i].Build = o.Build
}
}
if len(moves) == 0 && own == nil {
if len(moves) == 0 && len(owns) == 0 {
return nil, nil, nil
}
for i := range moves {
@@ -287,6 +300,7 @@ func gatedSend(ctx context.Context, open *stores, node string, own *inventory.Ca
}
}
}
sort.Slice(moves, func(i, j int) bool { return moves[i].Module < moves[j].Module })
sent, err := sendRollout(withScope(ctx, sendScope{judged: map[string]bool{node: true}}), open, []string{node})
if err != nil {
return nil, nil, err
@@ -328,6 +342,10 @@ func failCarried(ctx context.Context, open *stores, p *inventory.Plan, g *invent
machines = []string{c.Node}
}
state := &inventory.PlanModule{Build: c.Build, Previous: c.From, Commit: c.To}
// A module of the plan's tier sent in the same send (issue 281) keeps its own record of it.
if s := p.Modules[c.Module]; s != nil && except != "" && s.GatedBy == except && s.Build == c.Build {
state = s
}
p.Note = ""
gateFailed(ctx, open, p, c.Module, state, machines, g.Why)
notes = append(notes, p.Note)
@@ -563,7 +581,9 @@ func advanceRelease(ctx context.Context, open *stores, p *inventory.Plan) (bool,
return true, nil
}
p.Note = ""
failCarried(ctx, open, p, g, "")
batched, back := batchingRollbacks(ctx)
failCarried(batched, open, p, g, "")
sendRollbacks(ctx, open, p, back)
p.Note = fmt.Sprintf("failed its gate on %s: %s — %s", strings.Join(g.Machines, ", "), g.Why, p.Note)
fmt.Printf("%s: %s\n", p.ID, p.Note)
return true, nil