diff options
| author | Agent <agent@claudomator> | 2026-07-09 19:45:21 +0000 |
|---|---|---|
| committer | Agent <agent@claudomator> | 2026-07-09 19:45:21 +0000 |
| commit | a90ac76f3ccf0bf1259b7ef45e5f331d2b4ec26f (patch) | |
| tree | 03b5895d5edc5d0e45721ae3257c877652cc44cb /internal/scheduler/story_orchestrator_test.go | |
| parent | 7b51e781f4b5ded41e8d3ac2b566abf2c1b00cdc (diff) | |
refactor(scheduler): migrate story fix loop off RootTaskID re-pointing onto CurrentAttempt resolution
Diffstat (limited to 'internal/scheduler/story_orchestrator_test.go')
| -rw-r--r-- | internal/scheduler/story_orchestrator_test.go | 33 |
1 files changed, 19 insertions, 14 deletions
diff --git a/internal/scheduler/story_orchestrator_test.go b/internal/scheduler/story_orchestrator_test.go index 0ace7e7..200e38e 100644 --- a/internal/scheduler/story_orchestrator_test.go +++ b/internal/scheduler/story_orchestrator_test.go @@ -660,7 +660,15 @@ func seedNeedsFixStory(t *testing.T) (*fakeStoryStore, *story.Story, *task.Task, // at it (RootTaskID updated, Status reset to IN_PROGRESS) so the very next // tick re-enters the normal Builder->Evaluators->Arbitration flow against // the new attempt. -func TestStoryOrchestrator_NeedsFix_SpawnsFixAttemptAndRepointsRoot(t *testing.T) { +// TestStoryOrchestrator_NeedsFix_SpawnsFixAttempt proves the core mechanism: +// a story at NEEDS_FIX gets a new builder-role fix-attempt task spawned +// (depending on the rejected root), and the story's Status resets to +// IN_PROGRESS so the very next tick re-enters the normal +// Builder->Evaluators->Arbitration flow against the new attempt -- +// discovered via task.CurrentAttempt resolving forward from st.RootTaskID, +// which is never mutated (it's an immutable anchor set once at story +// creation). +func TestStoryOrchestrator_NeedsFix_SpawnsFixAttempt(t *testing.T) { store, st, oldRoot, _, _ := seedNeedsFixStory(t) pool := &fakePool{} @@ -689,8 +697,8 @@ func TestStoryOrchestrator_NeedsFix_SpawnsFixAttemptAndRepointsRoot(t *testing.T if got == nil { t.Fatal("story not found") } - if got.RootTaskID != fix.ID { - t.Errorf("story RootTaskID = %q, want the new fix-attempt task %q", got.RootTaskID, fix.ID) + if got.RootTaskID != oldRoot.ID { + t.Errorf("story RootTaskID = %q, want unchanged (still %q -- it's an immutable anchor now, resolved via task.CurrentAttempt)", got.RootTaskID, oldRoot.ID) } if got.Status != "IN_PROGRESS" { t.Errorf("story Status = %q, want IN_PROGRESS", got.Status) @@ -744,10 +752,9 @@ func TestStoryOrchestrator_NeedsFix_IdempotentAcrossTicks(t *testing.T) { // TestStoryOrchestrator_EnsureFixAttempt_FindsExistingSpawnedAttempt proves // ensureFixAttempt's own structural idempotency: simulating a restart -// between "fix task spawned" and "story RootTaskID/Status updated to point -// at it" -- calling ensureFixAttempt again must find the already-spawned -// builder-role dependent rather than spawning a second one, and still -// complete the re-pointing. +// between "fix task spawned" and "story Status reset" -- calling +// ensureFixAttempt again must find the already-spawned builder-role +// dependent rather than spawning a second one, and still reset Status. func TestStoryOrchestrator_EnsureFixAttempt_FindsExistingSpawnedAttempt(t *testing.T) { store, st, oldRoot, _, _ := seedNeedsFixStory(t) @@ -768,8 +775,8 @@ func TestStoryOrchestrator_EnsureFixAttempt_FindsExistingSpawnedAttempt(t *testi if fixAttempts[0].ID != preexisting.ID { t.Errorf("expected ensureFixAttempt to reuse the pre-existing task %s, got a different one %s", preexisting.ID, fixAttempts[0].ID) } - if st.RootTaskID != preexisting.ID { - t.Errorf("story RootTaskID = %q, want the pre-existing fix attempt %q", st.RootTaskID, preexisting.ID) + if st.RootTaskID != oldRoot.ID { + t.Errorf("story RootTaskID = %q, want unchanged (still %q -- it's an immutable anchor now)", st.RootTaskID, oldRoot.ID) } if st.Status != "IN_PROGRESS" { t.Errorf("story Status = %q, want IN_PROGRESS", st.Status) @@ -779,7 +786,9 @@ func TestStoryOrchestrator_EnsureFixAttempt_FindsExistingSpawnedAttempt(t *testi // TestStoryOrchestrator_NeedsFix_CapsAtMaxFixAttempts proves the safety net: // once a chain of maxFixAttempts consecutive fix attempts already exists, // ensureFixAttempt stops spawning new ones and leaves the story at -// NEEDS_FIX, exactly like today's fully-manual behavior. +// NEEDS_FIX, exactly like today's fully-manual behavior. st.RootTaskID stays +// at oldRoot.ID throughout (it's an immutable anchor); ensureFixAttempt +// resolves the chain's tip itself via task.CurrentAttempt. func TestStoryOrchestrator_NeedsFix_CapsAtMaxFixAttempts(t *testing.T) { store, st, oldRoot, _, _ := seedNeedsFixStory(t) @@ -797,10 +806,6 @@ func TestStoryOrchestrator_NeedsFix_CapsAtMaxFixAttempts(t *testing.T) { } prev = nt } - st.RootTaskID = prev.ID - if err := store.UpdateStory(st); err != nil { - t.Fatalf("persist chained root: %v", err) - } orch.Tick(context.Background()) |
