fix: Re-queue approved phases before dispatch to survive server restarts

The in-memory phaseQueue (Map) in DefaultPhaseDispatchManager is lost on
server restart. After approving a phase review, dispatchNextPhase() found
nothing in the empty queue, so the next unblocked phase never started.

Now the orchestrator re-queues all approved phases for the initiative from
the DB before attempting to dispatch, making the queue self-healing.
This commit is contained in:
Lukas May
2026-03-05 21:04:39 +01:00
parent 5f36fcf4ce
commit 573e1b40d2

View File

@@ -234,6 +234,9 @@ export class ExecutionOrchestrator {
await this.mergePhaseIntoInitiative(phaseId);
await this.phaseDispatchManager.completePhase(phaseId);
// Re-queue approved phases (self-healing: survives server restarts that wipe in-memory queue)
await this.requeueApprovedPhases(phase.initiativeId);
// Check if this was the last phase — if so, trigger initiative review
const dispatched = await this.phaseDispatchManager.dispatchNextPhase();
if (!dispatched.success) {
@@ -305,6 +308,9 @@ export class ExecutionOrchestrator {
await this.mergePhaseIntoInitiative(phaseId);
await this.phaseDispatchManager.completePhase(phaseId);
// Re-queue approved phases (self-healing: survives server restarts that wipe in-memory queue)
await this.requeueApprovedPhases(phase.initiativeId);
// Check if this was the last phase — if so, trigger initiative review
const dispatched = await this.phaseDispatchManager.dispatchNextPhase();
if (!dispatched.success) {
@@ -395,6 +401,25 @@ export class ExecutionOrchestrator {
return { taskId: task.id };
}
/**
* Re-queue approved phases for an initiative into the in-memory dispatch queue.
* Self-healing: ensures phases aren't lost if the server restarted since the
* initial queueAllPhases() call.
*/
private async requeueApprovedPhases(initiativeId: string): Promise<void> {
const phases = await this.phaseRepository.findByInitiativeId(initiativeId);
for (const p of phases) {
if (p.status === 'approved') {
try {
await this.phaseDispatchManager.queuePhase(p.id);
log.info({ phaseId: p.id }, 're-queued approved phase');
} catch {
// Already queued or status changed — safe to ignore
}
}
}
}
/**
* Check if all phases for an initiative are completed.
* If so, set initiative to pending_review and emit event.