package release import ( "context" "encoding/json" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/registry" "os" "path/filepath" "testing" "time" ) func TestPlan14RecoveryUnknownHistory(t *testing.T) { for _, state := range []string{"missing", "corrupt", "incomplete", "pending", "unknown-bake", "denied", "denied-incomplete", "denied-unsynced", "denied-corrupt-programs", "outcome-with-corrupt-host", "outcome-with-corrupt-peer"} { t.Run(state, func(t *testing.T) { h, s, b := rootHistoryFixture(t) at := time.Date(2026, 10, 13, 0, 0, 0, 0, time.UTC) p := &ChannelPointer{Version: "new", PromotedAt: at} denied := state == "denied" || state == "denied-incomplete" || state == "denied-unsynced" || state == "denied-corrupt-programs" || state == "outcome-with-corrupt-host" || state == "outcome-with-corrupt-peer" if state == "denied" || state == "denied-incomplete" || state == "denied-unsynced" || state == "denied-corrupt-programs" { if err := h.AddRefusal(b, Refusal{Host: b.Host, App: b.App, Version: p.Version, PointerPromotedAt: at, RefusedAt: at}); err != nil { t.Fatal(err) } } if state == "incomplete" || state == "denied-incomplete" { r, err := h.read(b) if err != nil { t.Fatal(err) } r.Refusals.Complete = false if err := h.write(r); err != nil { t.Fatal(err) } } switch state { case "missing": if err := os.Remove(filepath.Join(s.dir, historyKey(b))); err != nil { t.Fatal(err) } case "corrupt": if err := os.WriteFile(filepath.Join(s.dir, historyKey(b)), []byte("{"), 0600); err != nil { t.Fatal(err) } case "pending": if err := h.Begin(b, HistoryAttempt{ID: "old-attempt", Version: "old", Source: "source", Started: at}); err != nil { t.Fatal(err) } case "denied-corrupt-programs": r, err := h.read(b) if err != nil { t.Fatal(err) } r.Programs.Values = []string{""} raw, err := json.Marshal(r) if err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(s.dir, historyKey(b)), raw, 0600); err != nil { t.Fatal(err) } case "denied-unsynced": h.Store = &historyFailNewStore{healthyRetryStore: s, remaining: 100} case "outcome-with-corrupt-host", "outcome-with-corrupt-peer": outcome := HistoryRecovery{Binding: b, Attempt: HistoryAttempt{ID: "failed", Version: p.Version, Source: "source", Started: at, PromotedAt: at, SHA256: "digest", Platform: AdoptPlatform()}, Failed: true} if err := h.RetainRecovery(outcome); err != nil { t.Fatal(err) } key := historyKey(b) if state == "outcome-with-corrupt-peer" { key = "corrupt.outcome" } if err := os.WriteFile(filepath.Join(s.dir, key), []byte("{"), 0600); err != nil { t.Fatal(err) } } blocked, err := h.Blocks(b, p) if err != nil || blocked != denied { t.Fatalf("%s: blocked=%v error=%v", state, blocked, err) } if state == "missing" || state == "corrupt" || state == "incomplete" || state == "pending" { if _, err := h.Refusals(b); err == nil { t.Fatal("recovery policy granted mint completeness") } } }) } } func TestPlan14D1VerifiedRetryClear(t *testing.T) { for _, state := range []string{"retry", "same-promotion", "older-promotion", "other-version", "visible-sync-failure"} { t.Run(state, func(t *testing.T) { h, s, b := rootHistoryFixture(t) at := time.Date(2026, 10, 13, 0, 0, 0, 0, time.UTC) for _, kind := range []string{RefusalNotUp, RefusalWrongProgram, RefusalNotProven} { if err := h.AddRefusal(b, Refusal{Host: b.Host, App: b.App, Version: "new", Kind: kind, PointerPromotedAt: at, RefusedAt: at}); err != nil { t.Fatal(err) } } p := &ChannelPointer{Version: "new", PromotedAt: at.Add(time.Second)} switch state { case "same-promotion": p.PromotedAt = at case "older-promotion": p.PromotedAt = at.Add(-time.Second) case "other-version": p.Version = "other" case "visible-sync-failure": h.Store = &historyFailNewStore{healthyRetryStore: s, remaining: 1} } err := h.RetireD1(b, p) if state == "visible-sync-failure" { if err == nil { t.Fatal("unsynced clear accepted") } if err = h.RetireD1(b, p); err != nil { t.Fatal(err) } } else if err != nil { t.Fatal(err) } refs, err := h.Refusals(b) if err != nil { t.Fatal(err) } count := 3 if state == "retry" || state == "visible-sync-failure" { count = 2 } if len(refs) != count { t.Fatalf("refusals: %+v", refs) } for _, kind := range []string{RefusalWrongProgram, RefusalNotProven} { found := false for _, r := range refs { found = found || r.Kind == kind } if !found { t.Fatalf("retry cleared %s", kind) } } }) } } func TestPlan14RootHistoryVerifiedRetryBeforeAttempt(t *testing.T) { for _, state := range []string{"signed-retry", "unsigned", "same-promotion", "already-live", "record-change"} { t.Run(state, func(t *testing.T) { h, _, b := rootHistoryFixture(t) seedRootBake(t, h, b, time.Now().UTC().Add(-time.Hour)) st := NewDirStore(t.TempDir()) priv, pub := testKeys(t) opt := t.TempDir() reg := testRegistry(t) addRoute(t, reg, b.Host, b.App, "canary/x", false) shipRelease(t, st, b.Account, b.App, "new", adoptPlatform, []byte("new")) at := time.Now().UTC().Truncate(time.Second).Add(-time.Minute) if err := h.AddRefusal(b, Refusal{Host: b.Host, App: b.App, Version: "new", PointerPromotedAt: at, RefusedAt: at}); err != nil { t.Fatal(err) } promotion := at.Add(time.Second) if state == "same-promotion" { promotion = at } if state == "unsigned" { priv, _ = testKeys(t) } writeBackdatedPointer(t, st, priv, b.Account, b.App, "canary/x", "new", promotion) if state == "already-live" { if err := StageArtifact(opt, b.Account, "new", b.App, []byte("new")); err != nil { t.Fatal(err) } if err := PointLive(opt, b.Host, b.Account, "new", b.App); err != nil { t.Fatal(err) } } prepared, reads := 0, 0 deps := &RootHistoryPass{History: h, Resolve: func(context.Context, registry.Route) (HistoryBinding, string, error) { reads++ if state == "record-change" && reads > 1 { return b, "changed", nil } return b, "record", nil }, Installed: func(context.Context, registry.Route, string) (string, error) { return "", nil }, Prepare: func(context.Context, HistoryBinding, HistoryAttempt, hostwrite.Identity) error { prepared++ r, err := h.read(b) if err != nil { t.Fatal(err) } if len(r.Refusals.Values) != 0 { t.Fatal("verified retry was not cleared before attempt preparation") } return os.ErrPermission }, } effects := 0 _ = Pass(t.Context(), reg, st, pub, opt, func(string) error { effects++; return nil }, WithRootHistory(deps)) want := 0 if state == "signed-retry" { want = 1 } if prepared != want || effects != 0 { t.Fatalf("prepared=%d effects=%d", prepared, effects) } r, err := h.read(b) if err != nil { t.Fatal(err) } retained := 1 if state == "signed-retry" || state == "already-live" { retained = 0 } if len(r.Refusals.Values) != retained { t.Fatalf("wrong refusal clear: %+v", r) } }) } }