refactor(data): retire legacy TaskStatus values and backfill existing rows
Slice 6 of the worker state and queue consolidation refactor. * Drop Manual, Planning, Planned, Draft, Waiting from the TaskStatus enum and from the EF value converter; only the lifecycle values remain (Idle, Queued, Running, Done, Failed, Cancelled). * Add migration RetireLegacyTaskStatus that rewrites existing rows: manual/draft -> idle, planning -> idle+planning_phase=active, planned -> idle+planning_phase=finalized, waiting -> queued+blocked_by derived from sort_order via a CTE with LAG(). * Reroute every call site that compared/set legacy values to the new three-field model (Status + PlanningPhase + BlockedByTaskId), including the planning repo helpers, MCP services, the planning chain coordinator, and the UI view-models. TaskRowViewModel now exposes PlanningPhase to drive the planning badge. * Refresh Worker/CLAUDE.md and Data/CLAUDE.md, the docs/plan.md status section, and the planning verification notes in docs/open.md.
This commit is contained in:
@@ -39,16 +39,10 @@ public sealed class PlanningChainCoordinator
|
||||
if (children.Count == 0)
|
||||
throw new InvalidOperationException("Parent has no subtasks.");
|
||||
|
||||
// Eligibility: new layout uses Status=Idle. Tolerate legacy Manual/Planned/Draft
|
||||
// values during this slice — they will be migrated away in slice 6.
|
||||
var bad = children.FirstOrDefault(c =>
|
||||
c.Status != TaskStatus.Idle &&
|
||||
c.Status != TaskStatus.Manual &&
|
||||
c.Status != TaskStatus.Planned &&
|
||||
c.Status != TaskStatus.Draft);
|
||||
var bad = children.FirstOrDefault(c => c.Status != TaskStatus.Idle);
|
||||
if (bad is not null)
|
||||
throw new InvalidOperationException(
|
||||
$"Child {bad.Id} is in status {bad.Status}; expected Idle (or legacy Manual/Planned/Draft).");
|
||||
$"Child {bad.Id} is in status {bad.Status}; expected Idle.");
|
||||
|
||||
// Worker queue picker requires the "agent" tag — attach it so children are pickable.
|
||||
var agentTag = await ctx.Tags.FirstOrDefaultAsync(t => t.Name == "agent", ct);
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed class PlanningMcpService
|
||||
var child = await _tasks.CreateChildAsync(ctx.ParentTaskId, title, description, tags, commitType, cancellationToken);
|
||||
await BroadcastTaskUpdatedAsync(child.Id, cancellationToken);
|
||||
await BroadcastTaskUpdatedAsync(ctx.ParentTaskId, cancellationToken);
|
||||
return new CreatedChildDto(child.Id, "Draft");
|
||||
return new CreatedChildDto(child.Id, child.Status.ToString());
|
||||
}
|
||||
|
||||
[McpServerTool, Description("List all child tasks under the current planning session's parent task.")]
|
||||
@@ -68,9 +68,9 @@ public sealed class PlanningMcpService
|
||||
}
|
||||
|
||||
private static readonly TaskStatus[] EditableStatuses =
|
||||
{ TaskStatus.Draft, TaskStatus.Idle, TaskStatus.Manual, TaskStatus.Queued };
|
||||
{ TaskStatus.Idle, TaskStatus.Queued };
|
||||
|
||||
[McpServerTool, Description("Update a child task in the active planning session. Can change title, description, tags (replaces the full set), commit type, and status. Status must be one of: Draft, Idle, Manual, Queued.")]
|
||||
[McpServerTool, Description("Update a child task in the active planning session. Can change title, description, tags (replaces the full set), commit type, and status. Status must be one of: Idle, Queued.")]
|
||||
public async Task<ChildTaskDto> UpdateChildTask(
|
||||
string taskId,
|
||||
string? title,
|
||||
@@ -97,7 +97,7 @@ public sealed class PlanningMcpService
|
||||
if (!Enum.TryParse<TaskStatus>(status, ignoreCase: true, out var parsed))
|
||||
throw new InvalidOperationException($"Unknown status '{status}'.");
|
||||
if (!EditableStatuses.Contains(parsed))
|
||||
throw new InvalidOperationException($"Status '{parsed}' cannot be set via MCP. Allowed: Draft, Idle, Manual, Queued.");
|
||||
throw new InvalidOperationException($"Status '{parsed}' cannot be set via MCP. Allowed: Idle, Queued.");
|
||||
newStatus = parsed;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,8 +81,9 @@ public sealed class PlanningSessionManager
|
||||
?? throw new InvalidOperationException($"Task {taskId} not found.");
|
||||
if (task.ParentTaskId is not null)
|
||||
throw new InvalidOperationException("Cannot start a planning session on a child task.");
|
||||
if (task.Status != TaskStatus.Manual)
|
||||
throw new InvalidOperationException($"Task is in status {task.Status}; only Manual can start planning.");
|
||||
if (task.Status != TaskStatus.Idle || task.PlanningPhase != PlanningPhase.None)
|
||||
throw new InvalidOperationException(
|
||||
$"Task is in status {task.Status}/{task.PlanningPhase}; only Idle+None can start planning.");
|
||||
|
||||
var list = await lists.GetByIdAsync(task.ListId, ct)
|
||||
?? throw new InvalidOperationException($"List {task.ListId} not found.");
|
||||
@@ -232,7 +233,7 @@ public sealed class PlanningSessionManager
|
||||
var (tasks, _, settings, ctx) = CreateRepos();
|
||||
await using var __ = ctx;
|
||||
var children = await tasks.GetChildrenAsync(taskId, ct);
|
||||
return children.Count(c => c.Status == TaskStatus.Draft);
|
||||
return children.Count(c => c.Status == TaskStatus.Idle);
|
||||
}
|
||||
|
||||
public async Task DiscardAsync(string taskId, CancellationToken ct)
|
||||
@@ -261,8 +262,9 @@ public sealed class PlanningSessionManager
|
||||
|
||||
var task = await tasks.GetByIdAsync(taskId, ct)
|
||||
?? throw new InvalidOperationException($"Task {taskId} not found.");
|
||||
if (task.Status != TaskStatus.Planning)
|
||||
throw new InvalidOperationException($"Task is in status {task.Status}; resume requires Planning.");
|
||||
if (task.PlanningPhase != PlanningPhase.Active)
|
||||
throw new InvalidOperationException(
|
||||
$"Task planning phase is {task.PlanningPhase}; resume requires Active planning.");
|
||||
if (string.IsNullOrEmpty(task.PlanningSessionId))
|
||||
throw new InvalidOperationException("No Claude session ID captured yet; cannot resume.");
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class PlanningTokenAuthMiddleware
|
||||
|
||||
var token = auth.Substring("Bearer ".Length).Trim();
|
||||
var parent = await tasks.FindByPlanningTokenAsync(token, ctx.RequestAborted);
|
||||
if (parent is null || parent.Status != ClaudeDo.Data.Models.TaskStatus.Planning)
|
||||
if (parent is null || parent.PlanningPhase != ClaudeDo.Data.Models.PlanningPhase.Active)
|
||||
{
|
||||
ctx.Response.StatusCode = 401;
|
||||
await ctx.Response.WriteAsync("Invalid or expired planning token");
|
||||
|
||||
Reference in New Issue
Block a user