diff --git a/server/src/agents/lifecycle.rs b/server/src/agents/lifecycle.rs index 93a08a49..0e42e2f2 100644 --- a/server/src/agents/lifecycle.rs +++ b/server/src/agents/lifecycle.rs @@ -411,6 +411,9 @@ fn map_stage_move_to_event( (Stage::Merge { .. }, "current") => Ok(PipelineEvent::MergeAborted), // Story 971: send MergeFailure story back to Coding so a coder can fix it. (Stage::MergeFailure { .. }, "current") => Ok(PipelineEvent::FixupRequested), + // Operator override on the exhausted-respawn terminal state: still + // a coder fixup, but reached via the budget-exhausted path. + (Stage::MergeFailureFinal { .. }, "current") => Ok(PipelineEvent::FixupRequested), // Story 972: send MergeFailure story back to Qa for a QA agent to re-review. (Stage::MergeFailure { .. }, "qa") => Ok(PipelineEvent::ReQueuedForQa), // Story 974: reopen a Done story for a post-merge hotfix. diff --git a/server/src/pipeline_state/transition.rs b/server/src/pipeline_state/transition.rs index a956147d..a2194291 100644 --- a/server/src/pipeline_state/transition.rs +++ b/server/src/pipeline_state/transition.rs @@ -307,6 +307,15 @@ pub fn transition(state: Stage, event: PipelineEvent) -> Result Ok(Coding), + // ── FixupRequested: MergeFailureFinal → Coding (operator override) + // + // The exhausted-respawn-budget terminal state is not actually + // terminal as far as the operator is concerned; a human can decide + // the gate failure is fixable and send the story back for another + // coder attempt. The budget counter is a mergemaster bookkeeping + // detail, not a hard ceiling. + (MergeFailureFinal { .. }, FixupRequested) => Ok(Coding), + // ── ReQueuedForQa: MergeFailure → Qa (re-review) ──────────────── (MergeFailure { .. }, ReQueuedForQa) => Ok(Qa),