summaryrefslogtreecommitdiff
path: root/internal/executor/executor.go
diff options
context:
space:
mode:
authorClaudomator Agent <agent@claudomator.local>2026-03-24 22:24:40 +0000
committerClaudomator Agent <agent@claudomator.local>2026-03-24 22:24:40 +0000
commit8b1a710b655994f8ffb5747422088de5b88572e1 (patch)
tree892eb0144dd0e29bc14c88d1dc466362e197fe84 /internal/executor/executor.go
parentad339148be084b425ded24bb21ad7435c59d3072 (diff)
feat: auto-create validation task on story DEPLOYED (ADR-007)
Diffstat (limited to 'internal/executor/executor.go')
-rw-r--r--internal/executor/executor.go48
1 files changed, 48 insertions, 0 deletions
diff --git a/internal/executor/executor.go b/internal/executor/executor.go
index 2ab17a7..745c6d6 100644
--- a/internal/executor/executor.go
+++ b/internal/executor/executor.go
@@ -37,6 +37,7 @@ type Store interface {
GetStory(id string) (*task.Story, error)
ListTasksByStory(storyID string) ([]*task.Task, error)
UpdateStoryStatus(id string, status task.StoryState) error
+ CreateTask(t *task.Task) error
}
// LogPather is an optional interface runners can implement to provide the log
@@ -490,6 +491,53 @@ func (p *Pool) triggerStoryDeploy(ctx context.Context, storyID string) {
return
}
p.logger.Info("story transitioned to DEPLOYED", "storyID", storyID)
+ go p.createValidationTask(ctx, storyID)
+}
+
+// createValidationTask creates a validation subtask from the story's ValidationJSON
+// and transitions the story to VALIDATING.
+func (p *Pool) createValidationTask(ctx context.Context, storyID string) {
+ story, err := p.store.GetStory(storyID)
+ if err != nil {
+ p.logger.Error("createValidationTask: failed to get story", "storyID", storyID, "error", err)
+ return
+ }
+ if story.ValidationJSON == "" {
+ p.logger.Warn("createValidationTask: story has no ValidationJSON, skipping", "storyID", storyID)
+ return
+ }
+
+ var spec map[string]interface{}
+ if err := json.Unmarshal([]byte(story.ValidationJSON), &spec); err != nil {
+ p.logger.Error("createValidationTask: failed to parse ValidationJSON", "storyID", storyID, "error", err)
+ return
+ }
+
+ instructions := fmt.Sprintf("Validate the deployment for story %q.\n\nValidation spec:\n%s", story.Name, story.ValidationJSON)
+
+ now := time.Now().UTC()
+ vtask := &task.Task{
+ ID: uuid.New().String(),
+ Name: fmt.Sprintf("validation: %s", story.Name),
+ StoryID: storyID,
+ State: task.StateQueued,
+ Agent: task.AgentConfig{Type: "claude", Instructions: instructions},
+ Tags: []string{},
+ DependsOn: []string{},
+ CreatedAt: now,
+ UpdatedAt: now,
+ }
+
+ if err := p.store.CreateTask(vtask); err != nil {
+ p.logger.Error("createValidationTask: failed to create task", "storyID", storyID, "error", err)
+ return
+ }
+ if err := p.store.UpdateStoryStatus(storyID, task.StoryValidating); err != nil {
+ p.logger.Error("createValidationTask: failed to update story status", "storyID", storyID, "error", err)
+ return
+ }
+ p.logger.Info("validation task created and story transitioned to VALIDATING", "storyID", storyID, "taskID", vtask.ID)
+ p.Submit(ctx, vtask) //nolint:errcheck
}
// UndrainingAgent resets the drain state and failure counter for the given agent type.