fix(worker,ui): route details-pane task delete through worker to advance blocked parents

Deleting a child from the details pane hard-deleted straight from the UI
process via TaskRepository, bypassing TaskStateService.TryAdvanceParentAsync
entirely. Deleting the last child of a WaitingForChildren parent that way
left it wedged forever. WorkerHub.DeleteTask now mirrors the MCP delete_task
tool (running-task guard, FK-friendly error, advance-parent call), and the
UI goes through it.

TryAdvanceParentAsync also short-circuited when zero children remained,
treating "no children left" as "nothing to evaluate" instead of "all done" -
removed the early return so an empty child list (vacuously) counts as all
terminal.
This commit is contained in:
mika kuns
2026-08-06 13:25:49 +02:00
parent 0d1e3b9a6f
commit 9e2a15e421
11 changed files with 324 additions and 12 deletions
@@ -479,4 +479,42 @@ public sealed class TaskStateServiceTests : IDisposable
Assert.Equal(TaskStatus.Queued, t2.Status);
Assert.Equal(c1, t2.BlockedByTaskId);
}
// ─── TryAdvanceParentAsync ────────────────────────────────────────────
[Fact]
public async Task TryAdvanceParentAsync_ZeroChildren_AdvancesToWaitingForReview()
{
// Mirrors deleting the last remaining child: no terminal transition fires for it,
// so the caller invokes TryAdvanceParentAsync directly with zero children left.
var parent = await SeedTaskAsync(TaskStatus.WaitingForChildren, phase: PlanningPhase.Finalized);
await _sut.TryAdvanceParentAsync(parent);
Assert.Equal(TaskStatus.WaitingForReview, await GetStatusAsync(parent));
}
[Fact]
public async Task TryAdvanceParentAsync_AllChildrenTerminal_AdvancesToWaitingForReview()
{
var parent = await SeedTaskAsync(TaskStatus.WaitingForChildren, phase: PlanningPhase.Finalized);
await SeedTaskAsync(TaskStatus.Done, parentId: parent);
await SeedTaskAsync(TaskStatus.Done, parentId: parent);
await _sut.TryAdvanceParentAsync(parent);
Assert.Equal(TaskStatus.WaitingForReview, await GetStatusAsync(parent));
}
[Fact]
public async Task TryAdvanceParentAsync_SomeChildrenStillRunning_DoesNotAdvance()
{
var parent = await SeedTaskAsync(TaskStatus.WaitingForChildren, phase: PlanningPhase.Finalized);
await SeedTaskAsync(TaskStatus.Done, parentId: parent);
await SeedTaskAsync(TaskStatus.Running, parentId: parent);
await _sut.TryAdvanceParentAsync(parent);
Assert.Equal(TaskStatus.WaitingForChildren, await GetStatusAsync(parent));
}
}