summaryrefslogtreecommitdiff
path: root/internal/storage/db.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/storage/db.go')
-rw-r--r--internal/storage/db.go30
1 files changed, 30 insertions, 0 deletions
diff --git a/internal/storage/db.go b/internal/storage/db.go
index a16ad4e..d571c2e 100644
--- a/internal/storage/db.go
+++ b/internal/storage/db.go
@@ -287,6 +287,36 @@ func (s *DB) ListSubtasks(parentID string) ([]*task.Task, error) {
return tasks, rows.Err()
}
+// ListDependents returns tasks that directly depend on taskID — i.e. those
+// whose depends_on_json contains taskID. depends_on_json has no reverse
+// index, so this is a full-table scan; the codebase already accepts this
+// tradeoff for other JSON-blob columns at self-hosted scale (see the
+// "Additive migration strategy is fragile" design-debt note in CLAUDE.md).
+// Only direct dependents are returned — callers that need the full
+// transitive downstream subtree (e.g. cascade-cancellation) must recurse.
+func (s *DB) ListDependents(taskID string) ([]*task.Task, error) {
+ rows, err := s.db.Query(`SELECT id, name, description, elaboration_input, project, repository_url, config_json, priority, timeout_ns, retry_json, tags_json, depends_on_json, parent_task_id, state, created_at, updated_at, rejection_comment, question_json, summary, interactions_json FROM tasks`)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var dependents []*task.Task
+ for rows.Next() {
+ t, err := scanTaskRows(rows)
+ if err != nil {
+ return nil, err
+ }
+ for _, depID := range t.DependsOn {
+ if depID == taskID {
+ dependents = append(dependents, t)
+ break
+ }
+ }
+ }
+ return dependents, rows.Err()
+}
+
// UpdateTaskState atomically updates a task's state, enforcing valid
// transitions. The transition is attributed to the system actor; callers
// acting on behalf of a user should use UpdateTaskStateBy.