| index f7ffb98e1..a0bccc390 100644 |
| --- a/internal/release/adopt_test.go |
| +++ b/internal/release/adopt_test.go |
| @@ -195,6 +195,11 @@ func TestPassStableHeldByBakeWindow(t *testing.T) { |
| if err := Promote(ctx, s, priv, "bab", "myapp", "stable", "v1"); err != nil { |
| t.Fatalf("Promote: %v", err) |
| } |
| + // The app is DECLARED production (2026-08-04 spec §2): the window is no |
| + // longer unconditional — it is what the flag buys. |
| + if err := SetProduction(ctx, s, priv, "bab", "myapp", true, "test"); err != nil { |
| + t.Fatalf("SetProduction: %v", err) |
| + } |
| addRoute(t, reg, "h.example.com", "myapp", "", false) // "" = stable |
| |
| if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil { |
| @@ -689,3 +694,223 @@ func withCapturedStdout(t *testing.T, buf *bytes.Buffer, fn func()) { |
| w.Close() |
| <-done |
| } |
| + |
| +// ---- the production flag on the adopt path (2026-08-04 spec §6) ------------- |
| + |
| +// The default: no flag, no window (2026-08-04 spec §2). The whole fleet |
| +// runs on canary/* today, so nothing live loses protection — and a |
| +// rapid-iteration app's stable is just another channel. |
| +func TestPassNoProductionFlagInstallsImmediately(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + |
| + shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("bin")) |
| + if err := Promote(ctx, s, priv, "bab", "myapp", "stable", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, "h.example.com", "myapp", "", false) |
| + |
| + if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil { |
| + t.Fatalf("Pass: %v", err) |
| + } |
| + if v, _ := LiveVersion(opt, "h.example.com"); v != "v1" { |
| + t.Fatalf("live = %q, want v1 — no production flag means no window", v) |
| + } |
| +} |
| + |
| +// Fast-track: a production app's held head installs the moment its pointer |
| +// carries the root-signed waiver (spec §5). |
| +func TestPassFastTrackSkipsTheWindow(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + |
| + shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("bin")) |
| + if err := Promote(ctx, s, priv, "bab", "myapp", "stable", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := SetProduction(ctx, s, priv, "bab", "myapp", true, "test"); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, "h.example.com", "myapp", "", false) |
| + |
| + if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil { |
| + t.Fatal(err) |
| + } |
| + if v, _ := LiveVersion(opt, "h.example.com"); v != "" { |
| + t.Fatalf("live = %q, want held (production, no waiver yet)", v) |
| + } |
| + if _, err := FastTrack(ctx, s, priv, "bab", "myapp", "stable", "test"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil { |
| + t.Fatal(err) |
| + } |
| + if v, _ := LiveVersion(opt, "h.example.com"); v != "v1" { |
| + t.Fatalf("live = %q, want v1 — the waiver did not skip the window", v) |
| + } |
| +} |
| + |
| +// A production app whose flag OBJECT rots (exists, won't verify) keeps |
| +// baking — an attacker who can corrupt but not sign shortens nothing. |
| +func TestPassCorruptProductionFlagFailsTowardBaking(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + |
| + shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("bin")) |
| + if err := Promote(ctx, s, priv, "bab", "myapp", "stable", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if _, err := s.Put(ctx, ProductionKey("bab", "myapp"), []byte("not a wrapper"), ""); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, "h.example.com", "myapp", "", false) |
| + |
| + if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil { |
| + t.Fatalf("Pass: %v", err) |
| + } |
| + if v, _ := LiveVersion(opt, "h.example.com"); v != "" { |
| + t.Fatalf("live = %q, want held — a corrupt flag must read as production", v) |
| + } |
| +} |
| + |
| +// ORDERING, pinned: ObserveProduction runs BEFORE this pass's bake verdicts |
| +// are taken, so the bucket flag re-latches a wiped memory within ONE pass — |
| +// the whole basis of prodMemory's "a mixed-version rollout costs at most one |
| +// pass of this memory" comment. Simulates the old binary that re-marshals a |
| +// struct with no Production field and thereby deletes the latch. |
| +func TestPassReLatchesProductionMemoryInTheSamePass(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + |
| + shipRelease(t, s, "bab", "myapp", "v1", adoptPlatform, []byte("bin")) |
| + if err := Promote(ctx, s, priv, "bab", "myapp", "stable", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := SetProduction(ctx, s, priv, "bab", "myapp", true, "test"); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, "h.example.com", "myapp", "", false) |
| + |
| + if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil { |
| + t.Fatal(err) |
| + } |
| + st := LoadFirstSeen(FirstSeenPath(opt)) |
| + if len(st.Production) == 0 { |
| + t.Fatalf("first pass did not latch the production memory") |
| + } |
| + |
| + // The old binary's write: every channel observation survives, the |
| + // production memory (and with it the replay floor) is gone. |
| + st.Production = map[string]*prodMemory{} |
| + if err := st.Save(); err != nil { |
| + t.Fatalf("Save wiped clock: %v", err) |
| + } |
| + |
| + if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil { |
| + t.Fatal(err) |
| + } |
| + if v, _ := LiveVersion(opt, "h.example.com"); v != "" { |
| + t.Fatalf("live = %q, want still held — the bucket flag must re-latch BEFORE this pass's bake verdict, not after it", v) |
| + } |
| + if st := LoadFirstSeen(FirstSeenPath(opt)); len(st.Production) == 0 { |
| + t.Fatalf("the re-latch was not persisted") |
| + } |
| +} |
| + |
| +// A clock file that exists but does not parse is rewritten within ONE pass, |
| +// even when the pass observes nothing new — the taint-forced Save. Without |
| +// it a corrupt file would sit there forever, and every held head on the box |
| +// would fail toward baking pass after pass (ProductionBake's st.tainted). |
| +func TestPassRewritesATaintedClockInOnePass(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + _, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) // no routes: nothing new is observed this pass |
| + |
| + if err := os.WriteFile(FirstSeenPath(opt), []byte("{not json"), 0o644); err != nil { |
| + t.Fatal(err) |
| + } |
| + if st := LoadFirstSeen(FirstSeenPath(opt)); !st.tainted { |
| + t.Fatalf("setup: the corrupt clock did not read as tainted") |
| + } |
| + |
| + if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil { |
| + t.Fatalf("Pass: %v", err) |
| + } |
| + if st := LoadFirstSeen(FirstSeenPath(opt)); st.tainted { |
| + t.Fatalf("the pass left the clock file corrupt — a tainted clock must force its own Save") |
| + } |
| +} |
| + |
| +// prodReadStore counts Get calls for one exact key. |
| +type prodReadStore struct { |
| + Store |
| + key string |
| + n int |
| +} |
| + |
| +func (p *prodReadStore) Get(ctx context.Context, key string) ([]byte, string, error) { |
| + if key == p.key { |
| + p.n++ |
| + } |
| + return p.Store.Get(ctx, key) |
| +} |
| + |
| +// The 2-3 SECOND canary fast loop must never pay a production-flag bucket |
| +// read: canary/* has no window (DefaultHolds), so no canary-only pass ever |
| +// has a HELD head to ask about. The hourly pass pays exactly one read per |
| +// (account, app) with a held head — not one per channel, not one per route. |
| +func TestCanaryFastLoopPaysNoProductionRead(t *testing.T) { |
| + dir := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + opt := t.TempDir() |
| + reg := testRegistry(t) |
| + |
| + shipRelease(t, dir, "bab", "myapp", "v1", adoptPlatform, []byte("bin")) |
| + for _, ch := range []string{"canary/x", "canary/y", "stable"} { |
| + if err := Promote(ctx, dir, priv, "bab", "myapp", ch, "v1"); err != nil { |
| + t.Fatalf("Promote %s: %v", ch, err) |
| + } |
| + } |
| + if err := SetProduction(ctx, dir, priv, "bab", "myapp", true, "test"); err != nil { |
| + t.Fatal(err) |
| + } |
| + addRoute(t, reg, "cx.example.com", "myapp", "canary/x", false) |
| + addRoute(t, reg, "cy.example.com", "myapp", "canary/y", false) |
| + addRoute(t, reg, "s.example.com", "myapp", "stable", false) |
| + |
| + s := &prodReadStore{Store: dir, key: ProductionKey("bab", "myapp")} |
| + if err := PassFilter(ctx, reg, s, pub, opt, func(string) error { return nil }, isCanaryTestRoute); err != nil { |
| + t.Fatalf("PassFilter: %v", err) |
| + } |
| + if s.n != 0 { |
| + t.Fatalf("canary-only pass read the production flag %d times, want 0 — the fast loop must not pay bucket reads for a window canary/* does not have", s.n) |
| + } |
| + |
| + // Not vacuous: the full pass, which does resolve a held head, reads it — |
| + // once, for two channels and three routes. |
| + if err := Pass(ctx, reg, dir, pub, opt, func(string) error { return nil }); err != nil { |
| + t.Fatalf("Pass: %v", err) |
| + } |
| + s.n = 0 |
| + if err := Pass(ctx, reg, s, pub, opt, func(string) error { return nil }); err != nil { |
| + t.Fatalf("Pass: %v", err) |
| + } |
| + if s.n != 1 { |
| + t.Fatalf("full pass read the production flag %d times, want exactly 1 per (account, app)", s.n) |
| + } |
| +} |