Say only a refusal as a plan's hold, not a tier's summary left in its note (issue 920, review round 1)
mesh/merge-gate head 612f493c4926 merged into trunk 5ba2ffe8e608 — pass: builds mesh-controller → novox; no bus step; every machine composes with the chang…
mesh/repo-check head 612f493c4926 merged into trunk 5ba2ffe8e608 — pass: its merge-check.sh passed
mesh/delivery delivered

This commit is contained in:
2026-10-21 19:01:42 +02:00
parent 2377d75ed8
commit 612f493c49
2 changed files with 35 additions and 2 deletions
+16
View File
@@ -109,3 +109,19 @@ func TestAPlanLineSaysTheRefusalOnceItsTierIsBuilt(t *testing.T) {
t.Errorf("920/AC2: a plan with a tier module not built reads %q", line)
}
}
// 920/AC3: a tier move writes the previous tier's summary into the note; with the new tier all built, the line still
// says "building for", for that note is no refusal.
func TestAPlanLineDoesNotHoldOnATiersSummary(t *testing.T) {
now := time.Date(2026, 10, 21, 19, 0, 0, 0, time.UTC)
built := now.Add(-time.Hour)
for _, note := range []string{"failed or refused its send (apply-failing says it): novox", "tier 0 built; sent mesh-sdk to novox first, in one send"} {
p := inventory.Plan{ID: "plan-s", Repository: "novox/mesh-sdk", Commit: "b2f5ebb2", State: inventory.PlanBuilding,
Tier: 1, Created: now.Add(-2 * time.Hour), Updated: now, TierEntered: now.Add(-time.Hour), Note: note,
Tiers: [][]string{{"a"}, {"b"}},
Modules: map[string]*inventory.PlanModule{"a": {State: "built", BuiltAt: &built}, "b": {State: "built", BuiltAt: &built}}}
if line := planLine(p, now); !strings.Contains(line, "building for 1h0m0s") || strings.Contains(line, "held") {
t.Errorf("920/AC3: a plan with the note %q reads %q", note, line)
}
}
}
+19 -2
View File
@@ -851,6 +851,23 @@ func startedBeside(ctx context.Context, inv *inventory.Inventory, p inventory.Pl
return behind, why, nil
}
// refusalNotePrefix and refusalNoteSuffix frame the note of a step the walk was refused: "tier N: <refusal> — tried
// again". Only a note so framed is a hold (novox/hq issue 920); a tier's summary left in the note is not.
const (
refusalNotePrefix = "tier "
refusalNoteSuffix = " — tried again"
)
// refusalNote is the note of a plan whose step at tier was refused with err.
func refusalNote(tier int, err error) string {
return refusalNotePrefix + fmt.Sprint(tier) + ": " + err.Error() + refusalNoteSuffix
}
// refused says a plan's note is a refusal the walk wrote.
func refused(p inventory.Plan) bool {
return strings.HasPrefix(p.Note, refusalNotePrefix) && strings.HasSuffix(p.Note, refusalNoteSuffix)
}
// deferredNote begins the note of a walk deferred behind another.
const deferredNote = "let go; starts once "
@@ -941,7 +958,7 @@ func advanceHeld(ctx context.Context, open *stores) (failed bool) {
// Kept in the plan, so `plans` says why it has not moved rather than the log alone;
// the state is left as it was and the step is tried again by the next look. Said and
// kept when it is new: the same refusal on every tick is one fact, not one per tick.
p.Note = "tier " + fmt.Sprint(p.Tier) + ": " + err.Error() + " — tried again"
p.Note = refusalNote(p.Tier, err)
// A refusal for the bus's planned step is a person's to end: S17 says it from its first moment.
if refusedForTheBus(err) {
busRefusedFirst.mark(p.ID, time.Now())
@@ -2039,7 +2056,7 @@ func planLineSaid(p inventory.Plan, now time.Time, pause pauseView, bound time.D
if p.State == inventory.PlanRolling {
what = p.Note
}
if p.State == inventory.PlanBuilding && p.Note != "" && currentTierBuilt(p) {
if p.State == inventory.PlanBuilding && refused(p) && currentTierBuilt(p) {
// Every build of the tier is in, so the plan waits on something else, and its note says what: a send
// refused ("a build waits there for its gate: <machine> (...)") is kept there. "building" with LATE
// hid that reason from `plans`, `status` and the stalled condition (novox/hq issue 920).