fix(children): exempt improvement children from orphan-dequeue sweep
This commit is contained in:
@@ -393,6 +393,9 @@ public sealed class TaskRepository
|
|||||||
{
|
{
|
||||||
var orphanIds = await _context.Tasks
|
var orphanIds = await _context.Tasks
|
||||||
.Where(t => t.ParentTaskId != null && t.Status == TaskStatus.Queued)
|
.Where(t => t.ParentTaskId != null && t.Status == TaskStatus.Queued)
|
||||||
|
// Agent-suggested improvement children (CreatedBy == ParentTaskId) legitimately
|
||||||
|
// queue under a non-planning parent — they are not orphaned planning-chain members.
|
||||||
|
.Where(t => t.CreatedBy == null || t.CreatedBy != t.ParentTaskId)
|
||||||
.Where(t => !_context.Tasks.Any(p =>
|
.Where(t => !_context.Tasks.Any(p =>
|
||||||
p.Id == t.ParentTaskId && p.PlanningPhase != PlanningPhase.None))
|
p.Id == t.ParentTaskId && p.PlanningPhase != PlanningPhase.None))
|
||||||
.Select(t => t.Id)
|
.Select(t => t.Id)
|
||||||
|
|||||||
@@ -61,15 +61,18 @@ public sealed class TaskRepositoryOrphanGuardTests : IDisposable
|
|||||||
// --- CreateChildAsync validation ---
|
// --- CreateChildAsync validation ---
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CreateChildAsync_Throws_When_Parent_Has_No_Planning_Phase()
|
public async Task CreateChildAsync_Succeeds_On_NonPlanning_Parent_For_Improvement_Children()
|
||||||
{
|
{
|
||||||
|
// The planning-phase guard was removed: improvement children attach to a
|
||||||
|
// non-planning parent, with CreatedBy stamped to the parent id by the caller.
|
||||||
var listId = await CreateListAsync();
|
var listId = await CreateListAsync();
|
||||||
var parent = MakeTask(listId, phase: PlanningPhase.None);
|
var parent = MakeTask(listId, phase: PlanningPhase.None);
|
||||||
await _tasks.AddAsync(parent);
|
await _tasks.AddAsync(parent);
|
||||||
|
|
||||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
var child = await _tasks.CreateChildAsync(parent.Id, "child", null, null, createdBy: parent.Id);
|
||||||
() => _tasks.CreateChildAsync(parent.Id, "child", null, null));
|
Assert.Equal(parent.Id, child.ParentTaskId);
|
||||||
Assert.Contains("not in a planning phase", ex.Message);
|
Assert.Equal(parent.Id, child.CreatedBy);
|
||||||
|
Assert.Equal(TaskStatus.Idle, child.Status);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -201,6 +204,25 @@ public sealed class TaskRepositoryOrphanGuardTests : IDisposable
|
|||||||
Assert.Equal(parent.Id, reloaded.ParentTaskId); // lineage stays
|
Assert.Equal(parent.Id, reloaded.ParentTaskId); // lineage stays
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Dequeue_Leaves_Queued_Improvement_Children_Alone()
|
||||||
|
{
|
||||||
|
// Improvement children (CreatedBy == ParentTaskId) queue under a non-planning
|
||||||
|
// parent on purpose — the orphan sweep must NOT reset them.
|
||||||
|
var listId = await CreateListAsync();
|
||||||
|
var parent = MakeTask(listId, phase: PlanningPhase.None);
|
||||||
|
await _tasks.AddAsync(parent);
|
||||||
|
var child = MakeTask(listId, status: TaskStatus.Queued);
|
||||||
|
child.ParentTaskId = parent.Id;
|
||||||
|
child.CreatedBy = parent.Id;
|
||||||
|
await _tasks.AddAsync(child);
|
||||||
|
|
||||||
|
var dequeued = await _tasks.DequeueOrphanedChildrenAsync();
|
||||||
|
|
||||||
|
Assert.Equal(0, dequeued);
|
||||||
|
Assert.Equal(TaskStatus.Queued, _ctx.Tasks.AsNoTracking().Single(t => t.Id == child.Id).Status);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Dequeue_Leaves_Idle_Children_Of_NonPlanning_Parent_Alone()
|
public async Task Dequeue_Leaves_Idle_Children_Of_NonPlanning_Parent_Alone()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user