fix(FN-5627): always rebase behind branches before squash (safety fallback)

The FN-5627 default-threshold fix changed the prerebase threshold default
from 0 (never fire on commit-count) to 1 (fire on any divergence). But
that only affected projects WITHOUT an explicit threshold. Projects with
user-set values like 'prerebaseDivergenceThreshold: 50' continued to
skip prerebase for small divergences (e.g., 4 commits behind), so the
squash built against stale base and update-ref refused non-FF \u2014
producing the same-SHA spurious-concurrent-advance signature that
stranded FN-5626/FN-5628/FN-5633.

Root distinction missed earlier:
- prerebaseDivergenceThreshold is for USER-VISIBLE SEVERITY REPORTING
  (this branch is N commits behind, warn me).
- Engine correctness requires a SAFETY INVARIANT (any branch behind main
  MUST be rebased before squash or update-ref will fail).

These are independent concerns. The safety invariant must not be gated
on the user's threshold.

decideAutoPrerebase() now returns fire=true with reason
'safety-fallback-any-divergence' whenever commitsBehind > 0, after the
hot-file and threshold checks. The threshold path still wins the reason
label when its condition trips, so user-visible severity reporting is
unchanged for non-pathological cases.

Full opt-out remains prerebaseAutoEnabled=false (skips the safety
fallback; user accepts behind-branch merges will fail).
prerebaseDivergenceThreshold=0 is no longer a complete opt-out from the
commit-count gate \u2014 it only suppresses the threshold-based reason label.

Tests (4 updated/new):
- safety-fallback-any-divergence reason added to AutoPrerebaseDecision
- 4 commits behind with threshold=50 fires via safety fallback
- prerebaseAutoEnabled=false respects full opt-out
- threshold trip still wins reason label
- commitsBehind=0 returns no-divergence (unchanged)

Engine suite: 6163 tests pass.

In-flight: FN-5626, FN-5628, FN-5633 manually SQL-reset to mergeRetries=0,
status=null, error=null, transientRecoveryCount=0 so the next merger tick
(after engine restart picks up this code) auto-prerebases via safety
fallback and lands the work. Future occurrences self-heal automatically.

Fusion-Task-Id: FN-5627
This commit is contained in:
gsxdsm
2026-05-28 15:04:41 -07:00
parent b96b0bc79a
commit 694970b2f1
3 changed files with 104 additions and 14 deletions

View File

@@ -0,0 +1,24 @@
---
"@runfusion/fusion": patch
---
fix(FN-5627): always rebase behind branches before squash regardless of user-configured prerebase threshold
After the FN-5627 default-threshold fix landed (threshold=1 default), tasks were still getting stuck at `mergeRetries=3` with `Integration branch main advanced concurrently (expected X, observed X)` errors because user projects with explicit `prerebaseDivergenceThreshold` values higher than the branch's commits-behind count still skipped prerebase entirely.
Example: a project with `prerebaseDivergenceThreshold: 50` for low-noise PR experience would skip prerebase on a task branched 4 commits behind main. The squash commit then doesn't descend from current main, and `git update-ref` correctly refuses the non-fast-forward advance — producing the misleading same-SHA error signature that stranded FN-5626, FN-5628, FN-5633.
Root distinction missed in the earlier fix: the user-configurable `prerebaseDivergenceThreshold` controls the *user-visible severity reporting* ("this branch is N commits behind"), while engine correctness requires a *safety invariant* ("any branch behind main MUST be rebased before squash, or update-ref will fail"). These are independent concerns.
New behavior:
- After the hot-file and threshold checks, `decideAutoPrerebase()` now returns `fire: true` with `reason: "safety-fallback-any-divergence"` whenever `commitsBehind > 0`.
- The threshold-based path still wins when tripped (so user-visible audit `reason` reflects the configured policy when applicable).
- Full opt-out remains `prerebaseAutoEnabled: false` — that case skips the safety fallback too, and the user accepts that behind-branch merges will fail.
- `prerebaseDivergenceThreshold: 0` is no longer a complete opt-out from the commit-count gate — it only suppresses the threshold-based reason label. Safety fallback still fires.
Tests:
- New `safety-fallback-any-divergence` reason added to `AutoPrerebaseDecision.reason` union.
- 4 commits behind with threshold=50 → fires via safety fallback (was: skipped).
- `prerebaseAutoEnabled=false` → no fire (full opt-out preserved).
- Configured threshold tripping still wins the `reason` label.
- Branch fully up-to-date (commitsBehind=0) → no-divergence (unchanged).