Preserve paired review state after infra failures

This commit is contained in:
Eyejoker
2026-04-05 07:42:22 +09:00
parent e560fe6536
commit bf3ed696fe
2 changed files with 108 additions and 1 deletions

View File

@@ -583,4 +583,73 @@ describe('paired execution context', () => {
}),
);
});
it('keeps reviewer tasks review_ready when reviewer execution fails without a terminal verdict', () => {
vi.mocked(db.getPairedTaskById).mockReturnValue(
buildPairedTask({
status: 'in_review',
}),
);
completePairedExecutionContext({
taskId: 'task-1',
role: 'reviewer',
status: 'failed',
summary: 'runtime exploded before verdict',
});
expect(db.updatePairedTask).toHaveBeenCalledWith(
'task-1',
expect.objectContaining({
status: 'review_ready',
updated_at: expect.any(String),
}),
);
});
it('keeps arbiter tasks arbiter_requested when arbiter execution fails without a terminal verdict', () => {
vi.mocked(db.getPairedTaskById).mockReturnValue(
buildPairedTask({
status: 'in_arbitration',
}),
);
completePairedExecutionContext({
taskId: 'task-1',
role: 'arbiter',
status: 'failed',
summary: 'runtime exploded before verdict',
});
expect(db.updatePairedTask).toHaveBeenCalledWith(
'task-1',
expect.objectContaining({
status: 'arbiter_requested',
updated_at: expect.any(String),
}),
);
});
it('still resets owner tasks to active after failed execution', () => {
vi.mocked(db.getPairedTaskById).mockReturnValue(
buildPairedTask({
status: 'merge_ready',
}),
);
completePairedExecutionContext({
taskId: 'task-1',
role: 'owner',
status: 'failed',
summary: 'push failed',
});
expect(db.updatePairedTask).toHaveBeenCalledWith(
'task-1',
expect.objectContaining({
status: 'active',
updated_at: expect.any(String),
}),
);
});
});

View File

@@ -466,8 +466,46 @@ export function completePairedExecutionContext(args: {
return;
}
}
const now = new Date().toISOString();
if (role === 'reviewer') {
const fallbackStatus =
task.status === 'in_review' || task.status === 'review_ready'
? 'review_ready'
: task.status;
if (fallbackStatus !== task.status) {
updatePairedTask(taskId, { status: fallbackStatus, updated_at: now });
logger.warn(
{
taskId,
role,
previousStatus: task.status,
nextStatus: fallbackStatus,
},
'Preserved reviewer task in review-ready state after failed execution',
);
}
return;
}
if (role === 'arbiter') {
const fallbackStatus =
task.status === 'in_arbitration' || task.status === 'arbiter_requested'
? 'arbiter_requested'
: task.status;
if (fallbackStatus !== task.status) {
updatePairedTask(taskId, { status: fallbackStatus, updated_at: now });
logger.warn(
{
taskId,
role,
previousStatus: task.status,
nextStatus: fallbackStatus,
},
'Preserved arbiter task in arbitration-requested state after failed execution',
);
}
return;
}
if (task.status !== 'active') {
const now = new Date().toISOString();
updatePairedTask(taskId, { status: 'active', updated_at: now });
logger.info(
{ taskId, role, previousStatus: task.status },