Fix FN-5053 restores workspace test stability for the verification gate by adjusting the duplicate guard and task workflow route registration, with updated coverage in the branch-conflicts and diff-volume-gate test suites.
Fusion-Task-Id: FN-5053
Fusion-Task-Lineage: cc8a561a-fd56-4275-b06c-0966c80c2868
The merger maintains a single mergeActive slot per project; while a task
holds it, no other in-review task can be picked up. When the merger
sets task.status="merging" but the task is then completed via a
self-healing recovery path instead of the merger's own completeTask(),
the status field is never cleared. The mergeActive slot stays leaked
indefinitely, and the entire merger queue stalls.
Concrete failure: FN-5052 landed via FN-4499 auto-recover-branch-misbound
on 2026-05-18 15:48 with mergeConfirmed=true. Status field stayed at
"merging". 12.5 hours later, FN-5083, FN-5060, FN-5053, FN-5092, and
every other in-review task were waiting on the merger queue with no
merger activity at all.
Two self-healing → done paths were missing the status clear:
- recoverBranchMisboundInReviewTasks() (self-healing.ts ~5031):
branch-tip-misbound recovery moves an in-review task to done after
detecting its work landed on main via a different commit. The merger's
completeTask() correctly clears status before moveTask(); this path
did not.
- proven-no-op finalize in recoverNoOpReviewTasks() (self-healing.ts
~3460): for tasks proven already on main (FN-4500 classifier), the
updateTask({ mergeDetails, modifiedFiles }) patch did not include
status: null. Same leak class.
Both now follow the pattern from merger.ts completeTask() and
project-engine.ts auto-merge already-confirmed:
updateTask({ status: null, error: null, paused: false }) before
moveTask("done").
FN-5052 task.json patched to clear the existing stale status as part
of this commit so the running engine releases its mergeActive slot
on next sweep.
Fusion-Task-Id: FN-5092
When task.baseCommitSha becomes stale (because main has advanced past
it via legitimate merges of OTHER tasks), git log baseSha..branchName
includes commits that are already on main with foreign task-id
attribution. classifyForeignOnlyContamination then returns
kind="ambiguous" — own commits AND foreign commits in range — and the
contamination recovery skips, blocking the merger handoff
indefinitely.
This stranded FN-5092 after manual merges of FN-5060/FN-5090/FN-5053
moved main forward without updating downstream tasks' baseCommitSha.
Fix: when git merge-base(branch, main) is a descendant of the
persisted baseSha, prefer the live merge-base for the contamination
classification range. Fail open to persisted baseSha on any git
error. Also pass the effective base to classifyBootstrapMisbinding
so both classifiers see consistent input.
Fusion-Task-Id: FN-5090
Squash-merge of fusion/fn-5053 (7 commits including retry duplicates).
Drops stale conflict in packages/core/src/index.ts (FN-5053 branch
re-exported symbols that main already exports) and packages/core/src/store.ts
(branch had a stale, narrower updateTask signature; main is authoritative).
Manual CEO recovery after the trailerless-commit attribution bug
(now fixed by FN-5090 in 92be8225e) stranded this task in in-review.
Fusion-Task-Id: FN-5053
The contamination detector relied solely on the Fusion-Task-Id commit
trailer to attribute commits to a task. Agent-driven commits do not
currently get the trailer written (no commit-msg hook installs it), so
filterFilesToOwnTaskCommits saw 0 of N commits attributed even when
every commit subject was clearly feat(FN-XXXX): / fix(FN-XXXX): work
for that task. This drove attributedFileCount=0 reports, false-positive
task:worktree-contamination-detected events, and indefinite
foreign-only-contamination-skipped + completion-handoff-limbo loops
that stranded FN-5053, FN-5060, FN-5083 (manually merged) for hours.
Hotfix:
- extractAttributedTaskId now accepts both Fusion-Task-Id and a
generic Task-Id trailer.
- New extractTaskIdFromSubject recognizes conventional-commit
(feat(<id>):), bracketed ([<id>]) and legacy colon (<id>:) styles
for any uppercase task prefix (FN, KB, RF, PROJ, JIRA, ...).
- filterFilesToOwnTaskCommits falls back to subject-derived
attribution when the trailer is missing, with case-insensitive
match against opts.taskId.
Project-agnostic by design: the subject regex matches any
[A-Z]+-\d+ pattern, so projects with non-FN taskPrefix are
covered automatically.
Two new tests cover the FN-5083 production repro and the
KB-/lowercase/legacy variants.
Fusion-Task-Id: FN-5090