feat(FN-4885): complete Step 7 — finalize docs and changeset
Fusion-Task-Id: FN-4885 Fusion-Task-Lineage: 31110eed-5ca3-4d67-8f11-581185056dc6
This commit is contained in:
committed by
gsxdsm
parent
5793c4bcd7
commit
3d0cce72f2
5
.changeset/fn-4885-in-review-stall-deadlock.md
Normal file
5
.changeset/fn-4885-in-review-stall-deadlock.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Self-healing now auto-disposes in-review tasks whose identical stall (same code + reason) repeats past `inReviewStallDeadlockThreshold` (default 3) by pausing the task with `pausedReason="in-review-stall-deadlock"` and emitting a `task:in-review-stall-deadlock-disposed` run-audit event, preventing infinite stall-log churn (e.g., repeated `merge-blocker: Failed to create worktree after 3 attempts` loops).
|
||||||
@@ -202,7 +202,7 @@ Port 4040 is the production dashboard port. A user's live dashboard session is t
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
- Merge deadlock self-healing now layers `recoverAlreadyMergedReviewTasks()`, `clearStaleBlockedBy()`, and `reclaimSelfOwnedBranchConflicts()` in `packages/engine/src/self-healing.ts`, plus the paused-aware in-review scope filter in `packages/engine/src/scheduler.ts` (`inReviewWithWorktree` excludes `paused` tasks). `reclaimSelfOwnedBranchConflicts()` now also recovers paused `branch-conflict-unrecoverable` review rows when ownership is self-proven, auto-reclaims `fusion/<task-id>` branches that are still live-mapped but have zero unique commits vs main by force-removing the stale worktree and deleting the branch, and clears `task.worktree`/`task.branch` so retries recreate a fresh checkout. `inspectBranchConflict()` backs this with a patch-id fallback for degraded/empty `git cherry` output before declaring zero-unique-commit subsumption, treats ghost live-worktree admin entries (mapping path missing on disk) as `stale-resolved`, and classifies tips already reachable from the integration target as `tip-already-merged` so stale cached `baseCommitSha` values are invalidated instead of enumerating main's forward progress as stranded commits. FN-4546 adds a separate `reclaim-stale-active-branches` stage in startup and maintenance sweeps to close the gap where an active task owns a `fusion/<task-id>` branch with zero unique commits but has no usable worktree mapping; that stage now prunes the stale branch, clears task branch/worktree/base metadata, and emits `branch:stale-active-reclaim` audit telemetry. FN-4601 hardens executor recovery on this path: the no-`fn_task_done` retry loop now re-validates live worktree/branch bindings before recreating a session and treats missing/incomplete/unregistered-worktree session-start failures as recoverable by clearing stale metadata and requeueing to `todo` with preserved progress. Orphan `fusion/*` branches are still resolved by prune-or-rescue logic (subsumed branches pruned; unique-commit branches rescued into triage tasks instead of force delete). Completion fan-out now runs synchronously on `in-review → done` via `SelfHealingManager.reconcileCompletedTask()`, so downstream stale `blockedBy` links and residual `fusion/<task-id>` branch/worktree artifacts are reconciled immediately instead of waiting for periodic sweeps. Finalize integrity evidence classification now includes a benign `no-changes-finalized` outcome for verification-only done tasks with no owned commits and no surviving branch, allowing stale `modifiedFiles` snapshots to be cleared without raising `missing-evidence` warnings. When `worktrunk.enabled` is true, self-healing maintenance defers native prune/idle cleanup/worktree-cap layout sweeps to the worktrunk backend while keeping branch-level reclaim and orphan-branch rescue native.
|
- Merge deadlock self-healing now layers `recoverAlreadyMergedReviewTasks()`, `clearStaleBlockedBy()`, and `reclaimSelfOwnedBranchConflicts()` in `packages/engine/src/self-healing.ts`, plus the paused-aware in-review scope filter in `packages/engine/src/scheduler.ts` (`inReviewWithWorktree` excludes `paused` tasks). `reclaimSelfOwnedBranchConflicts()` now also recovers paused `branch-conflict-unrecoverable` review rows when ownership is self-proven, auto-reclaims `fusion/<task-id>` branches that are still live-mapped but have zero unique commits vs main by force-removing the stale worktree and deleting the branch, and clears `task.worktree`/`task.branch` so retries recreate a fresh checkout. `inspectBranchConflict()` backs this with a patch-id fallback for degraded/empty `git cherry` output before declaring zero-unique-commit subsumption, treats ghost live-worktree admin entries (mapping path missing on disk) as `stale-resolved`, and classifies tips already reachable from the integration target as `tip-already-merged` so stale cached `baseCommitSha` values are invalidated instead of enumerating main's forward progress as stranded commits. FN-4546 adds a separate `reclaim-stale-active-branches` stage in startup and maintenance sweeps to close the gap where an active task owns a `fusion/<task-id>` branch with zero unique commits but has no usable worktree mapping; that stage now prunes the stale branch, clears task branch/worktree/base metadata, and emits `branch:stale-active-reclaim` audit telemetry. FN-4601 hardens executor recovery on this path: the no-`fn_task_done` retry loop now re-validates live worktree/branch bindings before recreating a session and treats missing/incomplete/unregistered-worktree session-start failures as recoverable by clearing stale metadata and requeueing to `todo` with preserved progress. Orphan `fusion/*` branches are still resolved by prune-or-rescue logic (subsumed branches pruned; unique-commit branches rescued into triage tasks instead of force delete). Completion fan-out now runs synchronously on `in-review → done` via `SelfHealingManager.reconcileCompletedTask()`, so downstream stale `blockedBy` links and residual `fusion/<task-id>` branch/worktree artifacts are reconciled immediately instead of waiting for periodic sweeps. Finalize integrity evidence classification now includes a benign `no-changes-finalized` outcome for verification-only done tasks with no owned commits and no surviving branch, allowing stale `modifiedFiles` snapshots to be cleared without raising `missing-evidence` warnings. `surfaceInReviewStalls()` also now auto-disposes repeated identical in-review stalls (same code + reason) once `inReviewStallDeadlockThreshold` is reached (default `3`) by pausing with `pausedReason: "in-review-stall-deadlock"`, setting `status: "failed"`, and emitting `task:in-review-stall-deadlock-disposed` audit telemetry. When `worktrunk.enabled` is true, self-healing maintenance defers native prune/idle cleanup/worktree-cap layout sweeps to the worktrunk backend while keeping branch-level reclaim and orphan-branch rescue native.
|
||||||
- Restart recovery is coordinated through `RestartRecoveryCoordinator` (`packages/engine/src/restart-recovery-coordinator.ts`), which classifies interrupted `in-progress` runs at runtime startup: no-progress `fn_task_done` failures are safely requeued to `todo`, then remaining orphaned work is resumed via the executor. Session-start unusable-worktree detection now treats all `assertValidWorktreeSession()` variants (`missing worktree`, `incomplete worktree`, and `unregistered git worktree`) as recoverable for self-healing auto-requeue, including zero-progress `in-review` failures that now retry with a bounded `worktreeSessionRetryCount` cap (`MAX_WORKTREE_SESSION_RETRIES=3`) before escalating for human inspection.
|
- Restart recovery is coordinated through `RestartRecoveryCoordinator` (`packages/engine/src/restart-recovery-coordinator.ts`), which classifies interrupted `in-progress` runs at runtime startup: no-progress `fn_task_done` failures are safely requeued to `todo`, then remaining orphaned work is resumed via the executor. Session-start unusable-worktree detection now treats all `assertValidWorktreeSession()` variants (`missing worktree`, `incomplete worktree`, and `unregistered git worktree`) as recoverable for self-healing auto-requeue, including zero-progress `in-review` failures that now retry with a bounded `worktreeSessionRetryCount` cap (`MAX_WORKTREE_SESSION_RETRIES=3`) before escalating for human inspection.
|
||||||
|
|
||||||
## Engine Process Rules
|
## Engine Process Rules
|
||||||
|
|||||||
@@ -318,6 +318,7 @@ Default notes:
|
|||||||
| `specStalenessEnabled` | `boolean` | `false` | Enforce automatic re-planning for stale plans. |
|
| `specStalenessEnabled` | `boolean` | `false` | Enforce automatic re-planning for stale plans. |
|
||||||
| `specStalenessMaxAgeMs` | `number` | `21600000` | Spec staleness threshold in ms (6 hours). |
|
| `specStalenessMaxAgeMs` | `number` | `21600000` | Spec staleness threshold in ms (6 hours). |
|
||||||
| `taskStuckTimeoutMs` | `number` | `undefined` | Inactivity timeout for stuck-task recovery. |
|
| `taskStuckTimeoutMs` | `number` | `undefined` | Inactivity timeout for stuck-task recovery. |
|
||||||
|
| `inReviewStallDeadlockThreshold` | `number` | `3` | Minimum number of identical consecutive in-review stall log entries (same stall code + reason) before self-healing auto-disposes the task by pausing it with `pausedReason="in-review-stall-deadlock"` and marking status `failed`. Set to `0` to disable. |
|
||||||
| `stalePausedReviewThresholdMs` | `number` | `86400000` | Threshold in ms for surfacing paused `in-review` tasks as stale paused review diagnostics (24 hours). `0` or `undefined` disables stale paused review surfacing/logging. |
|
| `stalePausedReviewThresholdMs` | `number` | `86400000` | Threshold in ms for surfacing paused `in-review` tasks as stale paused review diagnostics (24 hours). `0` or `undefined` disables stale paused review surfacing/logging. |
|
||||||
| `staleInProgressWarningMs` | `number` | `14400000` | Task-age staleness warning threshold in ms for `in-progress` tasks (4 hours). `0` or `undefined` disables warning-level surfacing. |
|
| `staleInProgressWarningMs` | `number` | `14400000` | Task-age staleness warning threshold in ms for `in-progress` tasks (4 hours). `0` or `undefined` disables warning-level surfacing. |
|
||||||
| `staleInProgressCriticalMs` | `number` | `86400000` | Task-age staleness critical threshold in ms for `in-progress` tasks (24 hours). `0` or `undefined` disables critical-level surfacing. |
|
| `staleInProgressCriticalMs` | `number` | `86400000` | Task-age staleness critical threshold in ms for `in-progress` tasks (24 hours). `0` or `undefined` disables critical-level surfacing. |
|
||||||
@@ -946,6 +947,7 @@ To clear all overrides, set `promptOverrides` to `null`:
|
|||||||
"mergeStrategy": "direct",
|
"mergeStrategy": "direct",
|
||||||
"autoResolveConflicts": true,
|
"autoResolveConflicts": true,
|
||||||
"taskStuckTimeoutMs": 600000,
|
"taskStuckTimeoutMs": 600000,
|
||||||
|
"inReviewStallDeadlockThreshold": 3,
|
||||||
"runStepsInNewSessions": true,
|
"runStepsInNewSessions": true,
|
||||||
"maxParallelSteps": 2
|
"maxParallelSteps": 2
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IN_REVIEW_STALL_DEADLOCK_LOG_PREFIX, type InReviewStallCode, type InReviewStallSignal, type Task } from "@fusion/core";
|
import type { InReviewStallCode, InReviewStallSignal, Task } from "@fusion/core";
|
||||||
|
|
||||||
import { MAX_AUTO_MERGE_RETRIES } from "../hooks/useBlockerFanout";
|
import { MAX_AUTO_MERGE_RETRIES } from "../hooks/useBlockerFanout";
|
||||||
|
|
||||||
@@ -91,6 +91,8 @@ export function getInReviewStallCopy(
|
|||||||
|
|
||||||
const ACTIVE_MERGE_STATUSES: ReadonlySet<Task["status"]> = new Set(["merging", "merging-pr", "merging-fix"]);
|
const ACTIVE_MERGE_STATUSES: ReadonlySet<Task["status"]> = new Set(["merging", "merging-pr", "merging-fix"]);
|
||||||
|
|
||||||
|
const IN_REVIEW_STALL_DEADLOCK_LOG_PREFIX = "In-review stall auto-disposed [";
|
||||||
|
|
||||||
const IN_REVIEW_STALL_DEADLOCK_COPY: InReviewStallDeadlockCopy = {
|
const IN_REVIEW_STALL_DEADLOCK_COPY: InReviewStallDeadlockCopy = {
|
||||||
headline: "In-review deadlock auto-disposed",
|
headline: "In-review deadlock auto-disposed",
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ describe("reliability interactions: in-review stall deadlock disposition", () =>
|
|||||||
expect(task.pausedReason).toBe("in-review-stall-deadlock");
|
expect(task.pausedReason).toBe("in-review-stall-deadlock");
|
||||||
expect(task.status).toBe("failed");
|
expect(task.status).toBe("failed");
|
||||||
|
|
||||||
const disposedEntries = task.log.filter((entry) =>
|
const disposedEntries = task.log.filter((entry: { action: string }) =>
|
||||||
entry.action.startsWith("In-review stall auto-disposed [merge-blocker]:"),
|
entry.action.startsWith("In-review stall auto-disposed [merge-blocker]:"),
|
||||||
);
|
);
|
||||||
expect(disposedEntries).toHaveLength(1);
|
expect(disposedEntries).toHaveLength(1);
|
||||||
@@ -143,7 +143,7 @@ describe("reliability interactions: in-review stall deadlock disposition", () =>
|
|||||||
|
|
||||||
expect(task.pausedReason).not.toBe("in-review-stall-deadlock");
|
expect(task.pausedReason).not.toBe("in-review-stall-deadlock");
|
||||||
expect((store.updateTask as any).mock.calls.length).toBe(0);
|
expect((store.updateTask as any).mock.calls.length).toBe(0);
|
||||||
expect(task.log.some((entry) => entry.action.startsWith("In-review stall auto-disposed ["))).toBe(false);
|
expect(task.log.some((entry: { action: string }) => entry.action.startsWith("In-review stall auto-disposed ["))).toBe(false);
|
||||||
expect(((store as any).__auditEvents as any[]).some((event) => event.mutationType === "task:in-review-stall-deadlock-disposed")).toBe(false);
|
expect(((store as any).__auditEvents as any[]).some((event) => event.mutationType === "task:in-review-stall-deadlock-disposed")).toBe(false);
|
||||||
|
|
||||||
manager.stop();
|
manager.stop();
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ export type DatabaseMutationType =
|
|||||||
| "task:auto-recover-completion-fanout"
|
| "task:auto-recover-completion-fanout"
|
||||||
| "task:auto-recover-worktree-session-exhausted"
|
| "task:auto-recover-worktree-session-exhausted"
|
||||||
| "task:auto-recover-starved-refinement"
|
| "task:auto-recover-starved-refinement"
|
||||||
|
| "task:in-review-stall-deadlock-disposed"
|
||||||
| "task:finalize-unproven-blocked"
|
| "task:finalize-unproven-blocked"
|
||||||
| "task:integrity-reconcile-modified-files"
|
| "task:integrity-reconcile-modified-files"
|
||||||
| "task:integrity-warning"
|
| "task:integrity-warning"
|
||||||
|
|||||||
Reference in New Issue
Block a user