diff --git a/orchestration/task_orchestrator.py b/orchestration/task_orchestrator.py index 15915d9f1..58cb22bac 100644 --- a/orchestration/task_orchestrator.py +++ b/orchestration/task_orchestrator.py @@ -2,7 +2,7 @@ from typing import Any, Callable, Optional from uuid import UUID from domain.tasks.subtasks import SubTask -from domain.tasks.tasks import Source, Task, TaskStatus +from domain.tasks.tasks import Source, Task from repositories.tasks.subtask_repository import SubTaskRepository from repositories.tasks.task_repository import TaskRepository from utilities.private import private @@ -146,11 +146,10 @@ class TaskOrchestrator: @private def _cascade(self, task_id: UUID) -> None: task = self._tasks.get(task_id) - # FAILED is terminal: once any SubTask has failed the Task is failed and - # stays failed, so skip the (potentially large) sibling roll-up entirely — - # no need to list and re-check the SubTasks. - if task.status is TaskStatus.FAILED: - return + # Always recompute, even from FAILED: a failed batch SubTask may be + # re-run (re-sending its own inputs), and its completion must be able + # to un-fail the parent (ADR-0055). While any child is still failed, + # the recompute keeps the Task failed anyway. statuses = [s.status for s in self._subtasks.list_by_task(task_id)] task.recalculate_from_subtasks(statuses) self._tasks.save(task)