Files
ClaudeDo/src/ClaudeDo.Worker/State/Interfaces/ITaskStateService.cs
T
Mika Kuns c25732cff8 feat(claude-do): depends_on als Menge statt Einzelfeld
Ein Task kann jetzt auf beliebig viele Vorgaenger warten. Die Einzelspalte
tasks.depends_on_task_id wird durch die Beziehungstabelle task_dependencies
ersetzt (Composite-PK, beide FKs ON DELETE CASCADE -- das reproduziert das
alte ON DELETE SET NULL: geloeschter Vorgaenger = Kante weg = Task wieder
pickbar). BlockedByTaskId bleibt unberuehrt.

- Migration AddTaskDependencies kopiert jede bestehende depends_on_task_id
  verlustfrei in die Tabelle, bevor die Spalte fallengelassen wird
- QueuePicker gatet per doppelt negiertem NOT EXISTS: kein Task mit 0 Kanten
  wird blockiert, alle Kanten muessen done sein, eine Kante auf einen
  verschwundenen Task blockiert weiter
- SetDependsOnAsync ersetzt die ganze Menge; Zykluspruefung ist jetzt eine
  BFS ueber den Graphen, faengt also auch A->B->C->A
- blockedReason nennt alle offenen Vorgaenger (External/TaskBlocking, von
  ExternalMcpService und TaskWaitMcpTools gemeinsam genutzt)
- add_task/update_task nehmen dependsOnTaskIds; die alte Skalarform bleibt
  als Ein-Element-Menge inkl. ""-Clear-Sentinel gueltig
- UI: Fan-in-Zeile bleibt flach und listet alle Vorgaenger im After-Label,
  die lineare Kette rendert unveraendert
2026-09-02 15:08:05 +02:00

52 lines
3.4 KiB
C#

namespace ClaudeDo.Worker.State;
public interface ITaskStateService
{
Task<TransitionResult> EnqueueAsync(string taskId, CancellationToken ct);
Task<TransitionResult> StartRunningAsync(string taskId, DateTime startedAt, CancellationToken ct);
Task<TransitionResult> CompleteAsync(string taskId, DateTime finishedAt, string? result, CancellationToken ct);
Task<TransitionResult> SubmitForReviewAsync(string taskId, DateTime finishedAt, string? result, CancellationToken ct);
Task<TransitionResult> SubmitInteractiveForReviewAsync(string taskId, DateTime finishedAt, CancellationToken ct);
Task<TransitionResult> SubmitForChildrenAsync(string taskId, DateTime finishedAt, string? result, CancellationToken ct);
Task<TransitionResult> FailAsync(
string taskId, DateTime finishedAt, string? error, CancellationToken ct,
string failureReason = "error", int? turnsUsed = null, int? maxTurns = null);
Task<TransitionResult> CancelAsync(string taskId, DateTime finishedAt, CancellationToken ct, bool allowFromIdle = false);
Task<TransitionResult> ResetToIdleAsync(string taskId, CancellationToken ct);
Task<TransitionResult> ApproveReviewAsync(string taskId, CancellationToken ct);
Task<TransitionResult> RejectToQueueAsync(string taskId, string feedback, CancellationToken ct);
Task<TransitionResult> RejectToIdleAsync(string taskId, CancellationToken ct);
Task<TransitionResult> ClearReviewFeedbackAsync(string taskId, CancellationToken ct);
Task<TransitionResult> ForceSetStatusAsync(string taskId, ClaudeDo.Data.Models.TaskStatus status, CancellationToken ct);
// Guarded "manual done toggle" affordance (task list checkbox): unlike ForceSetStatusAsync,
// these check the expected starting status in the same ExecuteUpdateAsync filter so a
// concurrent picker claim (Idle/Done -> Running) is not silently overwritten.
Task<TransitionResult> MarkDoneAsync(string taskId, DateTime finishedAt, CancellationToken ct);
Task<TransitionResult> UnmarkDoneAsync(string taskId, CancellationToken ct);
// Guarded "remove from queue" affordance. Expects the task to still be Queued; clears
// BlockedByTaskId in the same update so a queued chain child never ends up Idle-but-blocked.
Task<TransitionResult> DequeueToIdleAsync(string taskId, CancellationToken ct);
Task<TransitionResult> StartPlanningAsync(string parentId, CancellationToken ct);
Task<TransitionResult> FinalizePlanningAsync(string parentId, CancellationToken ct);
Task<TransitionResult> BlockOnAsync(string taskId, string predecessorTaskId, CancellationToken ct);
Task<TransitionResult> UnblockAsync(string taskId, CancellationToken ct);
// Replaces the task's whole dependency set; an empty list clears it. Rejects a self-reference,
// an unknown dependency id, and any link that would create a cycle in the dependency graph
// -- see TaskStateService for the walk.
Task<TransitionResult> SetDependsOnAsync(string taskId, IReadOnlyList<string> dependsOnTaskIds, CancellationToken ct);
// Surfaces a WaitingForChildren parent for review once all its children are terminal.
// Best-effort (swallows and logs failures) — safe to call after any child mutation,
// e.g. deleting the last non-terminal child (no terminal transition fires for a delete).
Task TryAdvanceParentAsync(string parentId);
Task<int> RecoverStaleRunningAsync(string reason, CancellationToken ct);
}