summaryrefslogtreecommitdiff
path: root/internal/scheduler/story_orchestrator.go
diff options
context:
space:
mode:
authorAgent <agent@claudomator>2026-07-09 19:45:21 +0000
committerAgent <agent@claudomator>2026-07-09 19:45:21 +0000
commita90ac76f3ccf0bf1259b7ef45e5f331d2b4ec26f (patch)
tree03b5895d5edc5d0e45721ae3257c877652cc44cb /internal/scheduler/story_orchestrator.go
parent7b51e781f4b5ded41e8d3ac2b566abf2c1b00cdc (diff)
refactor(scheduler): migrate story fix loop off RootTaskID re-pointing onto CurrentAttempt resolution
Diffstat (limited to 'internal/scheduler/story_orchestrator.go')
-rw-r--r--internal/scheduler/story_orchestrator.go36
1 files changed, 19 insertions, 17 deletions
diff --git a/internal/scheduler/story_orchestrator.go b/internal/scheduler/story_orchestrator.go
index c3d5215..8061b35 100644
--- a/internal/scheduler/story_orchestrator.go
+++ b/internal/scheduler/story_orchestrator.go
@@ -221,9 +221,9 @@ func (o *StoryOrchestrator) processStory(ctx context.Context, st *story.Story) {
return
}
- root, err := o.Store.GetTask(st.RootTaskID)
+ root, err := task.CurrentAttempt(o.Store, st.RootTaskID)
if err != nil {
- o.logf("story orchestrator: get root task", "storyID", st.ID, "rootTaskID", st.RootTaskID, "error", err)
+ o.logf("story orchestrator: resolve current root attempt", "storyID", st.ID, "rootTaskID", st.RootTaskID, "error", err)
return
}
root = o.autoAccept(st, root)
@@ -495,24 +495,27 @@ const maxFixAttempts = 3
// structural discoverability/audit trail — the rejected root is already
// COMPLETED, so this dependency is immediately satisfied), whose
// instructions carry the original story spec/acceptance criteria plus the
-// arbitration's rejection reasoning, then re-points st.RootTaskID at the new
-// task and resets st.Status to IN_PROGRESS. The very next tick re-enters
-// processStory's normal Builder->Evaluators->Arbitration flow against the
-// new root, completely unchanged — no new pipeline, the existing one
-// re-entered.
+// arbitration's rejection reasoning, then resets st.Status to IN_PROGRESS.
+// st.RootTaskID is never written here — it is an immutable anchor set once
+// at story creation; every caller that needs "the task currently
+// representing the story's root" resolves it via task.CurrentAttempt, which
+// walks forward through however many fix-attempt links exist. The very next
+// tick re-enters processStory's normal Builder->Evaluators->Arbitration flow
+// against whatever CurrentAttempt now resolves to — completely unchanged, no
+// new pipeline, the existing one re-entered.
//
// Idempotency is structural, mirroring every other stage in this file: it
// looks for an existing builder-role dependent of the rejected root before
// spawning a new one, so calling this repeatedly (or after a restart between
-// "task spawned" and "story updated") never spawns duplicates. The
+// "task spawned" and "story status reset") never spawns duplicates. The
// maxFixAttempts cap is only checked in the "spawn a new one" branch — a
-// dependent that was already committed to being spawned still gets
-// re-pointed to, regardless of the cap, since refusing to do so would leave
-// a task dangling with nothing tracking it.
+// dependent that was already committed to being spawned still gets used,
+// regardless of the cap, since refusing to do so would leave a task
+// dangling with nothing tracking it.
func (o *StoryOrchestrator) ensureFixAttempt(ctx context.Context, st *story.Story) {
- oldRoot, err := o.Store.GetTask(st.RootTaskID)
+ oldRoot, err := task.CurrentAttempt(o.Store, st.RootTaskID)
if err != nil {
- o.logf("story orchestrator: fix attempt: get rejected root task", "storyID", st.ID, "rootTaskID", st.RootTaskID, "error", err)
+ o.logf("story orchestrator: fix attempt: resolve current root attempt", "storyID", st.ID, "rootTaskID", st.RootTaskID, "error", err)
return
}
@@ -547,10 +550,9 @@ func (o *StoryOrchestrator) ensureFixAttempt(ctx context.Context, st *story.Stor
fix = nt
}
- st.RootTaskID = fix.ID
st.Status = "IN_PROGRESS"
if err := o.Store.UpdateStory(st); err != nil {
- o.logf("story orchestrator: fix attempt: repoint story root", "storyID", st.ID, "error", err)
+ o.logf("story orchestrator: fix attempt: reset story status", "storyID", st.ID, "error", err)
}
}
@@ -626,9 +628,9 @@ func (o *StoryOrchestrator) fixAttemptDepth(root *task.Task) int {
// (auto-accepted from READY exactly like every other task in this pipeline),
// hands off to finalizeRetro to emit KindRetroCaptured.
func (o *StoryOrchestrator) processRetro(ctx context.Context, st *story.Story) {
- root, err := o.Store.GetTask(st.RootTaskID)
+ root, err := task.CurrentAttempt(o.Store, st.RootTaskID)
if err != nil {
- o.logf("story orchestrator: retro: get root task", "storyID", st.ID, "rootTaskID", st.RootTaskID, "error", err)
+ o.logf("story orchestrator: retro: resolve current root attempt", "storyID", st.ID, "rootTaskID", st.RootTaskID, "error", err)
return
}