carlos / platform Public

zdd-5 task 10: a held build no longer refuses a wake for an instance with no good build

Spec §8.5: an instance with no good build has no fallback, which is today's
behaviour. decideWake and planCheck now refuse such an instance only for a
hold-down; a record alone plans the installed build as a first wake with no
fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 3baca10e949df24edb8c869375dc597fc6c4aac7 parent ad3cb1f
2 files changed, +52 −8
  • internal/rollout/wakeplan.go +16 −6
  • internal/rollout/wakeplan_test.go +36 −2
diff --git a/internal/rollout/wakeplan.go b/internal/rollout/wakeplan.go
index df6e0248d..c7f66911c 100644
--- a/internal/rollout/wakeplan.go
+++ b/internal/rollout/wakeplan.go
@@ -33,7 +33,8 @@ type wakeInputs struct {
// this instance has never served is a first wake; in overlap mode it carries
// the good build as its fallback. A build that failed in hold mode on this
// instance refuses the wake: nothing falls back automatically once a hold
-// build has run against the database (spec §6.4, §8.3). An error is a
+// build has run against the database (spec §6.4, §8.3). An instance with no
+// good build is never refused by a record alone (spec §8.5). An error is a
// staging refusal for the caller to report.
func decideWake(in wakeInputs) (activator.WakePlan, error) {
rt := in.Route
@@ -54,10 +55,15 @@ func decideWake(in wakeInputs) (activator.WakePlan, error) {
if hasGood && installed == rt.GoodVersion {
return recovery, nil
}
- if in.Book.Blocks(acct, app, installed, host, false) {
- if !hasGood {
- return activator.WakePlan{Refuse: "build " + installed + " failed on this box and this instance has no good build"}, nil
- }
+ // A record holds the installed build for this instance: boot the good
+ // build instead. With no good build (every instance, until it first
+ // serves after the upgrade) there is nothing to fall back to, which is
+ // today's behaviour and not a regression (spec §8.5, §8.2): the wake
+ // plans the installed build as a first wake with no fallback, as it
+ // would had no record been written. Refusing it would turn one
+ // sibling's failure into an outage for every sleeping sibling. A
+ // hold-down was refused above and stays refused (spec §6.4).
+ if hasGood && in.Book.Blocks(acct, app, installed, host, false) {
return recovery, nil
}
path := release.VersionPath(in.OptDir, acct, installed, app)
@@ -149,15 +155,19 @@ func (m *Manager) Plan(ctx context.Context, rt registry.Route) activator.WakePla
// §7.4, §8.1–8.4): a first wake's build that a failure record now holds for
// this instance (a sibling's failure since the plan, or a hold-down) may not
// boot. The good build always may: it is what a held build falls back to.
+// An instance with no good build is refused only by a hold-down: a record
+// alone would not move it onto anything, and it has no fallback by design
+// until it first serves (spec §8.5), so refusing it would be an outage.
// The book is in memory; nothing here blocks.
func (m *Manager) planCheck(rt registry.Route, plan activator.WakePlan) func(build string) error {
acct, app, host := release.RouteAccount(rt), rt.App, rt.Host
boot := plan.Boot.Version
+ hasGood := rt.GoodVersion != "" && rt.GoodDigest != ""
return func(build string) error {
if !plan.FirstWake || build != boot {
return nil
}
- if m.d.Book.HeldDown(acct, app, build, host) || m.d.Book.Blocks(acct, app, build, host, false) {
+ if m.d.Book.HeldDown(acct, app, build, host) || (hasGood && m.d.Book.Blocks(acct, app, build, host, false)) {
return fmt.Errorf("build %s is held by a failure recorded since the wake was planned", build)
}
return nil
diff --git a/internal/rollout/wakeplan_test.go b/internal/rollout/wakeplan_test.go
index ab2f50b92..0b3adca5c 100644
--- a/internal/rollout/wakeplan_test.go
+++ b/internal/rollout/wakeplan_test.go
@@ -54,8 +54,14 @@ func TestDecideWake(t *testing.T) {
if p, _ := decideWake(inputs(wakeRoute("v2", "v1"), held, overlap)); p.Boot.Version != "v1" || p.FirstWake {
t.Fatalf("installed build held by a record: boot the good build, got %+v", p)
}
- if p, _ := decideWake(inputs(wakeRoute("v2", ""), held, overlap)); p.Refuse == "" {
- t.Fatal("held, with no good build: nothing to boot")
+ // Spec §8.5: an instance with no good build has no fallback, which is
+ // today's behaviour. A sibling's record must not refuse it outright.
+ if p, _ := decideWake(inputs(wakeRoute("v2", ""), held, overlap)); p.Refuse != "" || p.Boot.Version != "v2" || !p.FirstWake ||
+ p.Fallback != nil || p.Mode != activator.ModeOverlap {
+ t.Fatalf("held, no good build, not held down: boot the installed build as a first wake with no fallback, got %+v", p)
+ }
+ if p, _ := decideWake(inputs(wakeRoute("v2", ""), down, overlap)); p.Refuse == "" {
+ t.Fatal("held down with no good build: it stays down (spec §6.4)")
}
if p, _ := decideWake(inputs(wakeRoute("v2", "v1"), down, overlap)); p.Refuse == "" {
t.Fatal("held down after a hold failure: no automatic fallback (spec §6.4)")
@@ -450,6 +456,34 @@ func TestAPlansCheckRefusesABuildHeldSinceThePlan(t *testing.T) {
}
}
+// Spec §8.5: a record written after the plan does not refuse an instance with
+// no good build either. It is not moved onto anything else, and refusing it
+// would turn a sibling's failure into an outage. A hold-down still refuses.
+func TestAPlansCheckPassesAHostWithNoGoodBuildWhenARecordLandsAfterThePlan(t *testing.T) {
+ h := newHarness(t, "a")
+ h.stage("v2")
+ h.install("v2", "a")
+ r := h.rows.get("a")
+ r.GoodVersion, r.GoodDigest = "", ""
+ h.rows.put(r)
+ p := h.m.Plan(context.Background(), h.rows.get("a"))
+ if !p.FirstWake || p.Boot.Version != "v2" || p.Fallback != nil || p.Check == nil {
+ t.Fatalf("plan %+v: want a first wake onto v2, no fallback, with its check", p)
+ }
+ if _, err := h.book.Record(FailureRecord{Account: "bab", App: "blog", Build: "v2", Host: "b", At: t0}); err != nil {
+ t.Fatal(err)
+ }
+ if err := p.Check("v2"); err != nil {
+ t.Fatalf("no good build, a sibling's record since the plan: the check must pass, got %v", err)
+ }
+ if _, err := h.book.Record(FailureRecord{Account: "bab", App: "blog", Build: "v2", Host: "a", HeldDown: []string{"a"}, At: t0}); err != nil {
+ t.Fatal(err)
+ }
+ if err := p.Check("v2"); err == nil {
+ t.Fatal("held down on this instance since the plan: the check must still refuse")
+ }
+}
+
// Spec §2.9 (rev 17), §6.4, §8.3: an overlap attempt on a foreign-layout row
// runs as a hold, and the activator says so (SwapResult.Mode). Its failure
// is a hold build's failure: held down, so no wake boots the good build in