feat(FN-5329): remove orphan rescue and branch-recovery primitives from eng

Removes the branch-recovery CLI surface, orphan-rescue engine primitives, and their associated tests (over 1,500 lines deleted), while restoring a minimal prune-only orphan branch sweep with proper git audit mutation types. Documentation across `cli-reference.md`, `task-management.md`, and `AGENTS.m

Fusion-Task-Id: FN-5329
This commit is contained in:
Fusion (runfusion.ai)
2026-05-20 16:42:50 -07:00
committed by gsxdsm
parent 0e5cb4d292
commit fd202e9356
33 changed files with 131 additions and 1644 deletions

View File

@@ -685,7 +685,6 @@ Guardrails: this routine does **not** retry merges, does **not** apply to mixed/
- `clearStaleBlockedBy()` clears `blockedBy` (and transient `status`) on todo tasks when their blocker is missing, done, archived, paused in-review, or failed in-review with merge retries exhausted. FN-3924 extends this with a dependency-integrity guard: if a task has explicit dependencies and `blockedBy` is not one of the currently unresolved deps, the stale marker is cleared. FN-4091 broadens the sweep to active `in-progress` and un-paused `in-review` tasks as well, but those repairs only null `blockedBy` (they do not rewrite scheduler-owned queued state). This repairs rows corrupted by historical overlap re-stamping and lets scheduler re-evaluate from live dependency state. The ad-hoc `scripts/recover-stale-blocked-by.mjs` remains a manual backstop for filesystem/db audits, not the primary repair path.
- `inspectBranchConflict()` now treats self-owned zero-attribution collisions as reclaimable (instead of foreign) when ownership is proven by task/worktree identity, so stranded self-branches do not enter unrecoverable loops.
- `reclaimSelfOwnedBranchConflicts()` includes paused `branch-conflict-unrecoverable` tasks (not just todo/in-progress), clearing paused/error state in one update and requeueing only when parked in `in-review`.
- `cleanupOrphanedBranches()` uses a three-way decision table: (1) subsumed/no-unique-commits branches are pruned with `branch:orphan-prune`, (2) unique-commit branches with no matching task row are rescued as new triage tasks with `branch:orphan-rescued`, and (3) unique-commit branches tied to archived tasks are left intact with one-time acknowledgement metadata. FN-5188 adds a fresh-DB gate: when `__meta.bootstrappedAt` is current-process-fresh and the task table is empty, orphan rescue/prune is skipped entirely and emits `self-healing:orphan-rescue-skipped-fresh-db`.
- Together, `recoverAlreadyMergedReviewTasks()`, `clearStaleBlockedBy()`, and paused-aware in-review scheduling prevent merge-deadlock loops by finalizing already-landed work, clearing stale dependency blockers, reclaiming self-owned conflicts, and avoiding paused review cards re-blocking overlap dispatch.
- Merge commit attribution is ownership-aware: a `mergeDetails.commitSha` is trusted only when reachable from `HEAD` **and** attributable to the task via `Fusion-Task-Id` trailer or task-ID-bearing subject. Reachable-but-unowned SHAs are rejected to prevent sibling done tasks from sharing misleading merge metadata.
- FN-4948 adds a task-worktree pre-commit branch-identity guard: provisioning paths (`NativeWorktreeBackend.create`, executor branch creation, and `StepSessionExecutor.createStepWorktree`) install a `pre-commit` hook plus `fusion-task-id` metadata under the worktree's git-path. Commits are refused unless HEAD matches `fusion/<task-id>` or the allowlist (`fusion/step-<n>-<slug>` by default).
@@ -1560,7 +1559,7 @@ The GitHub tracking state listener now attaches to every registered project stor
- `inspectBranchConflict` classifies branch collisions as `stale`, `stale-resolved`, `reclaimable`, or `live-foreign`.
- Dispatch preflight (`acquireTaskWorktree`/executor) now auto-reclaims `reclaimable` self-owned conflicts and emits `branch:auto-reclaim` run-audit events with task/branch/worktree/tip/stranded-commit metadata.
- Self-healing also runs `reclaimSelfOwnedBranchConflicts()` across idle `todo` + `in-progress` tasks; successful reclaim keeps stranded commits intact and failed reclaim escalates to `in-review`/`failed` with `branch-conflict-unrecoverable`.
- Cross-task collisions (`live-foreign`) remain manual by design and still surface `fn task branch-recovery <taskId>` as the escape hatch.
- Cross-task collisions (`live-foreign`) remain manual by design; operators resolve conflicting branches/worktrees with standard git tooling, then retry the task.
### Merge strategies
- Setting type: `MergeStrategy = "direct" | "pull-request"` (`types.ts`)

View File

@@ -530,43 +530,9 @@ fn task unarchive FN-001
fn task delete FN-001 --force
```
### Branch conflict recovery
### Branch conflict handling
Use `fn task branch-recovery` when executor branch allocation fails because the canonical task branch is already checked out elsewhere. By default, the command lists every recovery candidate for the task, including the branch tip SHA, attached worktree path (if any), and patch-id-aware stranded commit subjects unique versus `main` (`git cherry`). Fully-subsumed branches show `stranded commits: none`.
```bash
fn task branch-recovery FN-001
fn task branch-recovery FN-001 --reclaim fusion/fn-001-2
fn task branch-recovery FN-001 --discard fusion/fn-001-2 --yes
```
| Option | Description |
|---|---|
| `--reclaim <branch>` | Point the task at an existing canonical or sibling branch so the next executor run resumes from that branch without rewriting commits. |
| `--discard <branch>` | Delete a stranded sibling branch and its worktree. Fusion refuses to run this destructive action unless `--yes` is also supplied. |
| `--yes` | Confirm destructive discard when `--discard` is used. |
Example inspect output:
```bash
fn task branch-recovery FN-001
Branch recovery candidates for FN-001
Canonical branch: fusion/fn-001
Current task branch: fusion/fn-001
Current task worktree: /repo/.worktrees/fn-001
• fusion/fn-001 (canonical)
tip: 0123456789abcdef0123456789abcdef01234567
worktree: /repo/.worktrees/fn-001
stranded commits:
- 0123456789ab fix: preserve stranded commits
• fusion/fn-001-2
tip: fedcba9876543210fedcba9876543210fedcba98
worktree: (not attached to a worktree)
stranded commits: none
```
See [Task Management → Branch conflict recovery](./task-management.md#branch-conflict-recovery) for the operator workflow and [Settings Reference → executorAllowSiblingBranchRename](./settings-reference.md#executorallowsiblingbranchrename) for the legacy opt-out setting.
When executor branch allocation fails because `fusion/<task-id>` is already checked out, Fusion marks the task failed/investigable and logs conflict details (existing worktree path, tip SHA, stranded commits). Operators should inspect and resolve conflicting local branches/worktrees with standard git tooling, then retry the task.
### GitHub integration

View File

@@ -266,7 +266,7 @@ Sandbox backend precedence is:
| `testCommand` | `string` | `undefined` | Merge-time test command (hard gate). When unset, Fusion auto-detects from lockfile. |
| `buildCommand` | `string` | `undefined` | Merge-time build command (hard gate). |
| `recycleWorktrees` | `boolean` | `false` | Default: off (opt-in). Reuse worktrees from a pool for faster startup. |
| `executorAllowSiblingBranchRename` | `boolean` | `false` | Opt back into the legacy executor behavior that silently allocates sibling branches (`fusion/<task-id>-2`, `-2-2`, …) when the canonical task branch is already checked out elsewhere. When disabled (default), branch conflicts fail loudly, leave the task in `todo` with `status: "failed"`, and expose stranded commits for explicit recovery via [`fn task branch-recovery`](./cli-reference.md#fn-task). See [Task Management → Branch conflict recovery](./task-management.md#branch-conflict-recovery). The dashboard Settings modal exposes the same toggle with warning copy because this legacy mode is discouraged. |
| `executorAllowSiblingBranchRename` | `boolean` | `false` | Opt back into the legacy executor behavior that silently allocates sibling branches (`fusion/<task-id>-2`, `-2-2`, …) when the canonical task branch is already checked out elsewhere. When disabled (default), branch conflicts fail loudly and leave the task in `todo` with `status: "failed"` so operators can resolve conflicting branches/worktrees with git tooling before retrying. See [Task Management → Branch conflict handling](./task-management.md#branch-conflict-handling). The dashboard Settings modal exposes the same toggle with warning copy because this legacy mode is discouraged. |
| `worktreeNaming` | `"random" \| "task-id" \| "task-title"` | `"random"` | Naming mode for new worktree directories. |
#### Worktree backend settings

View File

@@ -410,41 +410,11 @@ Recommended pattern:
Do **not** patch `.fusion/fusion.db` directly without synchronizing `.fusion/tasks/*/task.json` through a supported store-backed path.
## Branch conflict recovery
## Branch conflict handling
When the executor tries to allocate the canonical task branch (`fusion/<task-id>`) and finds that branch already checked out in another live worktree, Fusion now fails loudly by default instead of silently renaming the run onto `fusion/<task-id>-2`, `-3`, or similar sibling branches. See [CLI Reference → Branch conflict recovery](./cli-reference.md#branch-conflict-recovery) for the command reference and [Settings Reference → executorAllowSiblingBranchRename](./settings-reference.md#executorallowsiblingbranchrename) for the legacy opt-out setting.
When executor branch allocation finds `fusion/<task-id>` already checked out elsewhere, Fusion fails loudly by default instead of silently renaming to sibling branches. The task is moved back to `todo` with `status: "failed"`, and logs include the conflicting worktree path, tip SHA, and stranded commits.
1. **When this happens**
The executor refuses the branch allocation, moves the task from `in-progress` back to `todo`, sets `status: "failed"`, preserves the branch/worktree recovery metadata, and records the existing tip SHA plus stranded commit subjects in the task lifecycle log and structured agent log.
2. **Inspect candidates**
Run the recovery command with no flags to list every matching canonical or sibling branch. The output includes the tip SHA, any attached worktree path, and patch-id-aware stranded commit subjects unique to the branch versus `main` (`git cherry`). Fully-subsumed branches now report no stranded commits.
```bash
fn task branch-recovery FN-001
```
3. **Reclaim**
Reclaim points the task back at an existing canonical or sibling branch so the next executor run resumes there instead of allocating a new sibling. No commits are rewritten; Fusion only updates the task metadata.
```bash
fn task branch-recovery FN-001 --reclaim fusion/fn-001-2
```
4. **Discard**
Discard deletes a stranded sibling branch and its worktree when you have confirmed the old work is no longer needed. `--yes` is mandatory because the action is destructive.
```bash
fn task branch-recovery FN-001 --discard fusion/fn-001-2 --yes
```
5. **Opt-out / legacy mode**
If you must preserve the pre-FN-4068 behavior for a legacy workflow, enable [`executorAllowSiblingBranchRename`](./settings-reference.md#executorallowsiblingbranchrename). That restores silent suffixing onto sibling branches, but it is discouraged because it recreates the same hidden-work / data-loss pattern that motivated loud branch-conflict recovery in the first place.
Fusion no longer provides a dedicated task-branch conflict CLI command. Resolve conflicting local branches/worktrees with standard git tooling, then retry the task. The legacy [`executorAllowSiblingBranchRename`](./settings-reference.md#executorallowsiblingbranchrename) setting still exists as an opt-in escape hatch for older workflows.
## Task Execution Modes

View File

@@ -45,7 +45,7 @@
| 3 | `src/__tests__/merger-diff-volume-gate.test.ts` | 13.96s | integration + gate logic | keep | Keep. |
| 4 | `src/__tests__/self-healing-already-merged.real-git.test.ts` | 10.21s | real-git recovery | keep | Keep. |
| 5 | `src/__tests__/merger-autostash-cleanup.test.ts` | 9.79s | sweep* paths | keep | Keep. |
| 6 | `src/__tests__/branch-conflicts-recovery.test.ts` | 9.39s | branch recovery classification | keep | Keep. |
| 6 | `src/__tests__/branch-conflicts-recovery.test.ts` | 9.39s | branch conflict classification | keep | Keep. |
| 7 | `src/__tests__/merger-autostash-orphan-surface.test.ts` | 8.11s | autostash orphan surface | keep | Keep. |
| 8 | `src/__tests__/merger-squash-audit.test.ts` | 7.66s | squash audit | keep | Keep. |
| 9 | `src/__tests__/self-healing-stale-merge-stats.real-git.test.ts` | 7.01s | merge metadata recovery | keep | Keep. |