From 921cf3fdb6667f163eef6557eb411e9af3b5556f Mon Sep 17 00:00:00 2001 From: Ben Schellenberger Date: Thu, 16 Apr 2026 02:50:26 -0400 Subject: [PATCH 1/3] git: add bounded rewrite scenario orchestration Introduce detect/intervene/verify rerun orchestration with max-attempt guards so rewrite pipelines can fail predictably and report pass-by-pass state. Made-with: Cursor --- README.md | 30 ++++ git/rewrite_scenario.go | 120 +++++++++++++++ git/rewrite_scenario_test.go | 279 +++++++++++++++++++++++++++++++++++ 3 files changed, 429 insertions(+) create mode 100644 git/rewrite_scenario.go create mode 100644 git/rewrite_scenario_test.go diff --git a/README.md b/README.md index 6eeeadb..70da815 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,36 @@ go vet ./... go test -race -count=1 ./... ``` +## Rewrite Orchestration (git-fcuk flow) + +The `git` package now includes bounded multi-pass rewrite orchestration via +`RunRewriteScenario`, designed for flows that must: + +1. Detect whether intervention is needed. +2. Intervene (run one rewrite pass). +3. Verify the repo is clean. +4. Rerun from detect until clean or attempts are exhausted. + +Use it for safety-first rewrite loops where you need deterministic stop +conditions and pass-by-pass telemetry (`RewriteScenarioResult.Passes`). + +### Validate locally + +```bash +# Targeted rewrite orchestration tests +go test -count=1 ./git -run RunRewriteScenario + +# Full module sanity +go test -count=1 ./... +``` + +Expected outcome: + +- Targeted run: `ok github.com/git-fire/git-harness/git ...` +- Full run: all packages return `ok` with no failing tests +- If a rewrite loop never reaches clean within `MaxAttempts`, callers receive + `ErrRewriteAttemptsExceeded` + ## Stability `v0.x` releases may change APIs; pin a minor or patch version in consumers. diff --git a/git/rewrite_scenario.go b/git/rewrite_scenario.go new file mode 100644 index 0000000..0c9fb44 --- /dev/null +++ b/git/rewrite_scenario.go @@ -0,0 +1,120 @@ +package git + +import ( + "context" + "errors" + "fmt" +) + +// ErrRewriteAttemptsExceeded indicates a rewrite run remained dirty after all +// configured attempts were used. +var ErrRewriteAttemptsExceeded = errors.New("rewrite scenario exceeded maximum attempts") + +// RewriteScenarioOptions configures detect -> intervene -> verify orchestration. +// +// The flow is: +// 1. Detect whether rewrite intervention is required. +// 2. Intervene when required. +// 3. Verify whether the repo is now clean. +// 4. Repeat until clean or attempts are exhausted. +type RewriteScenarioOptions struct { + // MaxAttempts bounds total detect passes. Must be >= 1. + MaxAttempts int + + // Detect reports whether intervention is needed for the attempt. + // Return true when the repository still needs rewrite intervention. + Detect func(ctx context.Context, attempt int) (needsIntervention bool, err error) + + // Intervene performs one rewrite intervention pass for the attempt. + Intervene func(ctx context.Context, attempt int) error + + // Verify checks whether the repository is clean after intervention. + // Return true when no further rewrite intervention is needed. + Verify func(ctx context.Context, attempt int) (clean bool, err error) +} + +// RewriteAttempt captures one detect pass in the orchestration loop. +type RewriteAttempt struct { + Attempt int // 1-based pass number + NeedsIntervention bool // Detect outcome + Intervened bool // Whether Intervene ran + CleanAfterVerify bool // Verify outcome when Intervene ran +} + +// RewriteScenarioResult captures pass-by-pass outcomes. +type RewriteScenarioResult struct { + Clean bool + Attempts int + Passes []RewriteAttempt +} + +// RunRewriteScenario executes bounded detect -> intervene -> verify passes until +// the repository is clean or attempts are exhausted. +func RunRewriteScenario(ctx context.Context, opts RewriteScenarioOptions) (RewriteScenarioResult, error) { + result := RewriteScenarioResult{} + + if ctx == nil { + ctx = context.Background() + } + if opts.MaxAttempts < 1 { + return result, fmt.Errorf("max attempts must be >= 1") + } + if opts.Detect == nil { + return result, fmt.Errorf("detect callback is required") + } + if opts.Intervene == nil { + return result, fmt.Errorf("intervene callback is required") + } + if opts.Verify == nil { + return result, fmt.Errorf("verify callback is required") + } + + for attempt := 1; attempt <= opts.MaxAttempts; attempt++ { + if err := ctx.Err(); err != nil { + result.Attempts = attempt - 1 + return result, fmt.Errorf("rewrite scenario cancelled before attempt %d: %w", attempt, err) + } + + pass := RewriteAttempt{Attempt: attempt} + + needsIntervention, err := opts.Detect(ctx, attempt) + if err != nil { + result.Attempts = attempt + result.Passes = append(result.Passes, pass) + return result, fmt.Errorf("detect failed on attempt %d: %w", attempt, err) + } + pass.NeedsIntervention = needsIntervention + + if !needsIntervention { + result.Attempts = attempt + result.Clean = true + result.Passes = append(result.Passes, pass) + return result, nil + } + + if err := opts.Intervene(ctx, attempt); err != nil { + result.Attempts = attempt + result.Passes = append(result.Passes, pass) + return result, fmt.Errorf("intervene failed on attempt %d: %w", attempt, err) + } + pass.Intervened = true + + clean, err := opts.Verify(ctx, attempt) + if err != nil { + result.Attempts = attempt + result.Passes = append(result.Passes, pass) + return result, fmt.Errorf("verify failed on attempt %d: %w", attempt, err) + } + pass.CleanAfterVerify = clean + result.Passes = append(result.Passes, pass) + + if clean { + result.Attempts = attempt + result.Clean = true + return result, nil + } + } + + result.Attempts = opts.MaxAttempts + return result, fmt.Errorf("%w: attempts=%d", ErrRewriteAttemptsExceeded, opts.MaxAttempts) +} diff --git a/git/rewrite_scenario_test.go b/git/rewrite_scenario_test.go new file mode 100644 index 0000000..1289a79 --- /dev/null +++ b/git/rewrite_scenario_test.go @@ -0,0 +1,279 @@ +package git + +import ( + "context" + "errors" + "strings" + "testing" +) + +func TestRunRewriteScenario_CleanOnFirstDetect(t *testing.T) { + detectCalls := 0 + interveneCalls := 0 + verifyCalls := 0 + + result, err := RunRewriteScenario(context.Background(), RewriteScenarioOptions{ + MaxAttempts: 3, + Detect: func(_ context.Context, attempt int) (bool, error) { + detectCalls++ + if attempt != 1 { + t.Fatalf("attempt = %d, want 1", attempt) + } + return false, nil + }, + Intervene: func(_ context.Context, _ int) error { + interveneCalls++ + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + verifyCalls++ + return true, nil + }, + }) + if err != nil { + t.Fatalf("RunRewriteScenario() error = %v", err) + } + + if !result.Clean { + t.Fatalf("result.Clean = false, want true") + } + if result.Attempts != 1 { + t.Fatalf("result.Attempts = %d, want 1", result.Attempts) + } + if len(result.Passes) != 1 { + t.Fatalf("len(result.Passes) = %d, want 1", len(result.Passes)) + } + pass := result.Passes[0] + if pass.NeedsIntervention { + t.Fatalf("pass.NeedsIntervention = true, want false") + } + if pass.Intervened { + t.Fatalf("pass.Intervened = true, want false") + } + if pass.CleanAfterVerify { + t.Fatalf("pass.CleanAfterVerify = true, want false when verify not run") + } + if detectCalls != 1 || interveneCalls != 0 || verifyCalls != 0 { + t.Fatalf("calls = detect:%d intervene:%d verify:%d, want 1/0/0", detectCalls, interveneCalls, verifyCalls) + } +} + +func TestRunRewriteScenario_MultiPassUntilVerifyClean(t *testing.T) { + detectCalls := 0 + interveneCalls := 0 + verifyCalls := 0 + + result, err := RunRewriteScenario(context.Background(), RewriteScenarioOptions{ + MaxAttempts: 4, + Detect: func(_ context.Context, _ int) (bool, error) { + detectCalls++ + return true, nil + }, + Intervene: func(_ context.Context, _ int) error { + interveneCalls++ + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + verifyCalls++ + return verifyCalls == 2, nil + }, + }) + if err != nil { + t.Fatalf("RunRewriteScenario() error = %v", err) + } + + if !result.Clean { + t.Fatalf("result.Clean = false, want true") + } + if result.Attempts != 2 { + t.Fatalf("result.Attempts = %d, want 2", result.Attempts) + } + if len(result.Passes) != 2 { + t.Fatalf("len(result.Passes) = %d, want 2", len(result.Passes)) + } + if result.Passes[0].CleanAfterVerify { + t.Fatalf("pass 1 clean after verify = true, want false") + } + if !result.Passes[1].CleanAfterVerify { + t.Fatalf("pass 2 clean after verify = false, want true") + } + if detectCalls != 2 || interveneCalls != 2 || verifyCalls != 2 { + t.Fatalf("calls = detect:%d intervene:%d verify:%d, want 2/2/2", detectCalls, interveneCalls, verifyCalls) + } +} + +func TestRunRewriteScenario_RerunDetectUntilClean(t *testing.T) { + detectCalls := 0 + interveneCalls := 0 + verifyCalls := 0 + + result, err := RunRewriteScenario(context.Background(), RewriteScenarioOptions{ + MaxAttempts: 3, + Detect: func(_ context.Context, _ int) (bool, error) { + detectCalls++ + return detectCalls == 1, nil + }, + Intervene: func(_ context.Context, _ int) error { + interveneCalls++ + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + verifyCalls++ + return false, nil + }, + }) + if err != nil { + t.Fatalf("RunRewriteScenario() error = %v", err) + } + + if !result.Clean { + t.Fatalf("result.Clean = false, want true") + } + if result.Attempts != 2 { + t.Fatalf("result.Attempts = %d, want 2", result.Attempts) + } + if len(result.Passes) != 2 { + t.Fatalf("len(result.Passes) = %d, want 2", len(result.Passes)) + } + if !result.Passes[0].NeedsIntervention || !result.Passes[0].Intervened { + t.Fatalf("pass 1 should include intervention, got %+v", result.Passes[0]) + } + if result.Passes[1].NeedsIntervention || result.Passes[1].Intervened { + t.Fatalf("pass 2 should be clean detect with no intervention, got %+v", result.Passes[1]) + } + if detectCalls != 2 || interveneCalls != 1 || verifyCalls != 1 { + t.Fatalf("calls = detect:%d intervene:%d verify:%d, want 2/1/1", detectCalls, interveneCalls, verifyCalls) + } +} + +func TestRunRewriteScenario_ExceedsMaxAttempts(t *testing.T) { + result, err := RunRewriteScenario(context.Background(), RewriteScenarioOptions{ + MaxAttempts: 3, + Detect: func(_ context.Context, _ int) (bool, error) { + return true, nil + }, + Intervene: func(_ context.Context, _ int) error { + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + return false, nil + }, + }) + if err == nil { + t.Fatal("RunRewriteScenario() error = nil, want max-attempts error") + } + if !errors.Is(err, ErrRewriteAttemptsExceeded) { + t.Fatalf("error = %v, want ErrRewriteAttemptsExceeded", err) + } + if result.Clean { + t.Fatalf("result.Clean = true, want false") + } + if result.Attempts != 3 { + t.Fatalf("result.Attempts = %d, want 3", result.Attempts) + } + if len(result.Passes) != 3 { + t.Fatalf("len(result.Passes) = %d, want 3", len(result.Passes)) + } +} + +func TestRunRewriteScenario_CallbackErrors(t *testing.T) { + t.Run("detect error", func(t *testing.T) { + result, err := RunRewriteScenario(context.Background(), RewriteScenarioOptions{ + MaxAttempts: 2, + Detect: func(_ context.Context, _ int) (bool, error) { + return false, errors.New("boom-detect") + }, + Intervene: func(_ context.Context, _ int) error { + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + return true, nil + }, + }) + if err == nil || !strings.Contains(err.Error(), "detect failed") { + t.Fatalf("error = %v, want detect failure", err) + } + if result.Attempts != 1 { + t.Fatalf("result.Attempts = %d, want 1", result.Attempts) + } + }) + + t.Run("intervene error", func(t *testing.T) { + result, err := RunRewriteScenario(context.Background(), RewriteScenarioOptions{ + MaxAttempts: 2, + Detect: func(_ context.Context, _ int) (bool, error) { + return true, nil + }, + Intervene: func(_ context.Context, _ int) error { + return errors.New("boom-intervene") + }, + Verify: func(_ context.Context, _ int) (bool, error) { + return true, nil + }, + }) + if err == nil || !strings.Contains(err.Error(), "intervene failed") { + t.Fatalf("error = %v, want intervene failure", err) + } + if result.Attempts != 1 { + t.Fatalf("result.Attempts = %d, want 1", result.Attempts) + } + if len(result.Passes) != 1 || !result.Passes[0].NeedsIntervention { + t.Fatalf("passes = %+v, want one pass with intervention required", result.Passes) + } + }) + + t.Run("verify error", func(t *testing.T) { + result, err := RunRewriteScenario(context.Background(), RewriteScenarioOptions{ + MaxAttempts: 2, + Detect: func(_ context.Context, _ int) (bool, error) { + return true, nil + }, + Intervene: func(_ context.Context, _ int) error { + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + return false, errors.New("boom-verify") + }, + }) + if err == nil || !strings.Contains(err.Error(), "verify failed") { + t.Fatalf("error = %v, want verify failure", err) + } + if result.Attempts != 1 { + t.Fatalf("result.Attempts = %d, want 1", result.Attempts) + } + if len(result.Passes) != 1 || !result.Passes[0].Intervened { + t.Fatalf("passes = %+v, want one pass with intervene=true", result.Passes) + } + }) +} + +func TestRunRewriteScenario_InvalidConfiguration(t *testing.T) { + _, err := RunRewriteScenario(context.Background(), RewriteScenarioOptions{ + MaxAttempts: 0, + Detect: func(_ context.Context, _ int) (bool, error) { + return false, nil + }, + Intervene: func(_ context.Context, _ int) error { + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + return true, nil + }, + }) + if err == nil || !strings.Contains(err.Error(), "max attempts") { + t.Fatalf("error = %v, want max-attempts validation error", err) + } + + _, err = RunRewriteScenario(context.Background(), RewriteScenarioOptions{ + MaxAttempts: 1, + Intervene: func(_ context.Context, _ int) error { + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + return true, nil + }, + }) + if err == nil || !strings.Contains(err.Error(), "detect callback") { + t.Fatalf("error = %v, want detect callback validation error", err) + } +} From e8f8fef8b58eac4db966df7b178be8acd6421c8a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 16 Apr 2026 07:46:07 +0000 Subject: [PATCH 2/3] git: honor cancellation between rewrite scenario phases Check ctx.Err after detect (when intervention needed) and after intervene so later callbacks are skipped when cancelled. Add tests for pre-cancelled context, nil context fallback, and mid-attempt cancellation before intervene and verify. Co-authored-by: Ben Schellenberger --- git/rewrite_scenario.go | 12 +++ git/rewrite_scenario_test.go | 138 +++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/git/rewrite_scenario.go b/git/rewrite_scenario.go index 0c9fb44..c87105e 100644 --- a/git/rewrite_scenario.go +++ b/git/rewrite_scenario.go @@ -92,6 +92,12 @@ func RunRewriteScenario(ctx context.Context, opts RewriteScenarioOptions) (Rewri return result, nil } + if err := ctx.Err(); err != nil { + result.Attempts = attempt + result.Passes = append(result.Passes, pass) + return result, fmt.Errorf("rewrite scenario cancelled before intervene on attempt %d: %w", attempt, err) + } + if err := opts.Intervene(ctx, attempt); err != nil { result.Attempts = attempt result.Passes = append(result.Passes, pass) @@ -99,6 +105,12 @@ func RunRewriteScenario(ctx context.Context, opts RewriteScenarioOptions) (Rewri } pass.Intervened = true + if err := ctx.Err(); err != nil { + result.Attempts = attempt + result.Passes = append(result.Passes, pass) + return result, fmt.Errorf("rewrite scenario cancelled before verify on attempt %d: %w", attempt, err) + } + clean, err := opts.Verify(ctx, attempt) if err != nil { result.Attempts = attempt diff --git a/git/rewrite_scenario_test.go b/git/rewrite_scenario_test.go index 1289a79..2f648a8 100644 --- a/git/rewrite_scenario_test.go +++ b/git/rewrite_scenario_test.go @@ -277,3 +277,141 @@ func TestRunRewriteScenario_InvalidConfiguration(t *testing.T) { t.Fatalf("error = %v, want detect callback validation error", err) } } + +func TestRunRewriteScenario_CancelledContext(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + result, err := RunRewriteScenario(ctx, RewriteScenarioOptions{ + MaxAttempts: 2, + Detect: func(_ context.Context, _ int) (bool, error) { + return true, nil + }, + Intervene: func(_ context.Context, _ int) error { + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + return false, nil + }, + }) + if err == nil { + t.Fatal("RunRewriteScenario() error = nil, want cancellation error") + } + if !errors.Is(err, context.Canceled) { + t.Fatalf("RunRewriteScenario() error = %v, want context.Canceled", err) + } + if result.Attempts != 0 { + t.Fatalf("result.Attempts = %d, want 0", result.Attempts) + } +} + +func TestRunRewriteScenario_NilContextFallback(t *testing.T) { + detectCalls := 0 + result, err := RunRewriteScenario(nil, RewriteScenarioOptions{ + MaxAttempts: 1, + Detect: func(_ context.Context, attempt int) (bool, error) { + detectCalls++ + if attempt != 1 { + t.Fatalf("attempt = %d, want 1", attempt) + } + return false, nil + }, + Intervene: func(_ context.Context, _ int) error { + t.Fatal("Intervene should not run when detect reports clean") + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + t.Fatal("Verify should not run when detect reports clean") + return true, nil + }, + }) + if err != nil { + t.Fatalf("RunRewriteScenario() error = %v", err) + } + if !result.Clean { + t.Fatalf("result.Clean = false, want true") + } + if detectCalls != 1 { + t.Fatalf("detectCalls = %d, want 1", detectCalls) + } +} + +func TestRunRewriteScenario_CancelledAfterDetectSkipsIntervene(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + + interveneCalls := 0 + verifyCalls := 0 + + result, err := RunRewriteScenario(ctx, RewriteScenarioOptions{ + MaxAttempts: 2, + Detect: func(_ context.Context, _ int) (bool, error) { + cancel() + return true, nil + }, + Intervene: func(_ context.Context, _ int) error { + interveneCalls++ + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + verifyCalls++ + return true, nil + }, + }) + if err == nil { + t.Fatal("RunRewriteScenario() error = nil, want cancellation error") + } + if !errors.Is(err, context.Canceled) { + t.Fatalf("RunRewriteScenario() error = %v, want context.Canceled", err) + } + if !strings.Contains(err.Error(), "cancelled before intervene") { + t.Fatalf("error = %v, want message about intervene phase", err) + } + if result.Attempts != 1 { + t.Fatalf("result.Attempts = %d, want 1", result.Attempts) + } + if len(result.Passes) != 1 || !result.Passes[0].NeedsIntervention { + t.Fatalf("passes = %+v, want one pass with detect outcome recorded", result.Passes) + } + if interveneCalls != 0 || verifyCalls != 0 { + t.Fatalf("calls = intervene:%d verify:%d, want 0/0", interveneCalls, verifyCalls) + } +} + +func TestRunRewriteScenario_CancelledAfterInterveneSkipsVerify(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + + verifyCalls := 0 + + result, err := RunRewriteScenario(ctx, RewriteScenarioOptions{ + MaxAttempts: 2, + Detect: func(_ context.Context, _ int) (bool, error) { + return true, nil + }, + Intervene: func(_ context.Context, _ int) error { + cancel() + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + verifyCalls++ + return true, nil + }, + }) + if err == nil { + t.Fatal("RunRewriteScenario() error = nil, want cancellation error") + } + if !errors.Is(err, context.Canceled) { + t.Fatalf("RunRewriteScenario() error = %v, want context.Canceled", err) + } + if !strings.Contains(err.Error(), "cancelled before verify") { + t.Fatalf("error = %v, want message about verify phase", err) + } + if result.Attempts != 1 { + t.Fatalf("result.Attempts = %d, want 1", result.Attempts) + } + if len(result.Passes) != 1 || !result.Passes[0].Intervened { + t.Fatalf("passes = %+v, want one pass with intervene=true", result.Passes) + } + if verifyCalls != 0 { + t.Fatalf("verifyCalls = %d, want 0", verifyCalls) + } +} From 4e4e9362e2aff24c0f0f2a022eeabd784198d9bc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 16 Apr 2026 08:04:56 +0000 Subject: [PATCH 3/3] git: surface cancellation after verify on final attempt When verify reports not clean on the last attempt and the context is cancelled during verify, return a cancellation error instead of ErrRewriteAttemptsExceeded. Add a regression test. Co-authored-by: Ben Schellenberger --- git/rewrite_scenario.go | 5 +++++ git/rewrite_scenario_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/git/rewrite_scenario.go b/git/rewrite_scenario.go index c87105e..7c8c3a1 100644 --- a/git/rewrite_scenario.go +++ b/git/rewrite_scenario.go @@ -125,6 +125,11 @@ func RunRewriteScenario(ctx context.Context, opts RewriteScenarioOptions) (Rewri result.Clean = true return result, nil } + + if err := ctx.Err(); err != nil { + result.Attempts = attempt + return result, fmt.Errorf("rewrite scenario cancelled after verify on attempt %d: %w", attempt, err) + } } result.Attempts = opts.MaxAttempts diff --git a/git/rewrite_scenario_test.go b/git/rewrite_scenario_test.go index 2f648a8..2ad34fa 100644 --- a/git/rewrite_scenario_test.go +++ b/git/rewrite_scenario_test.go @@ -415,3 +415,39 @@ func TestRunRewriteScenario_CancelledAfterInterveneSkipsVerify(t *testing.T) { t.Fatalf("verifyCalls = %d, want 0", verifyCalls) } } + +func TestRunRewriteScenario_CancelledInVerifyOnLastAttempt(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + + result, err := RunRewriteScenario(ctx, RewriteScenarioOptions{ + MaxAttempts: 1, + Detect: func(_ context.Context, _ int) (bool, error) { + return true, nil + }, + Intervene: func(_ context.Context, _ int) error { + return nil + }, + Verify: func(_ context.Context, _ int) (bool, error) { + cancel() + return false, nil + }, + }) + if err == nil { + t.Fatal("RunRewriteScenario() error = nil, want cancellation error") + } + if !errors.Is(err, context.Canceled) { + t.Fatalf("RunRewriteScenario() error = %v, want context.Canceled", err) + } + if !strings.Contains(err.Error(), "cancelled after verify") { + t.Fatalf("error = %v, want message about post-verify cancellation", err) + } + if result.Attempts != 1 { + t.Fatalf("result.Attempts = %d, want 1", result.Attempts) + } + if len(result.Passes) != 1 || !result.Passes[0].Intervened || result.Passes[0].CleanAfterVerify { + t.Fatalf("passes = %+v, want one pass with verify outcome recorded", result.Passes) + } + if errors.Is(err, ErrRewriteAttemptsExceeded) { + t.Fatal("error should not be ErrRewriteAttemptsExceeded when context was cancelled") + } +}