Commit Graph

125 Commits

Author SHA1 Message Date
gsxdsm
0dea25061d Merge remote-tracking branch 'origin/main' into conflict-resolution-1717
# Conflicts:
#	packages/engine/src/__tests__/executor-recovery.test.ts
#	packages/engine/src/agent-tools.ts
#	packages/engine/src/executor.ts
#	packages/engine/src/merger-ai.ts
#	packages/engine/src/project-engine.ts
#	packages/engine/src/worktree-acquisition.ts
2026-06-23 16:12:10 -07:00
gsxdsm
9218613552 FN-6949: fix auto-merge lifecycle finalization
Make auto-merge completion idempotent when landed tasks are left outside done.

- Add shared auto-merge finalization logic that refreshes task rows, clears stale queue/blocker state, and recovery-rehomes proven landed tasks to done.
- Route direct AI merge, merge-confirmed fast paths, and self-healing recovery through the shared finalizer with run-audit events for reconciled or blocked column mismatches.
- Extend lifecycle and recovery tests to cover todo/in-review mismatches, finalization failures, stale blockers, and cleanup idempotency.
- Add the required patch changeset for the published Fusion package.

Files changed:
 .changeset/fn-6949-auto-merge-lifecycle.md         |   5 +
 .../src/__tests__/merge-error-recovery.test.ts     |  50 ++++-
 packages/engine/src/__tests__/merger-ai.test.ts    |   8 +-
 .../src/__tests__/merger-merge-lifecycle.test.ts   | 155 +++++++++++++++
 .../ai-merge-cleanup-enoent-idempotent.test.ts     |   2 +-
 packages/engine/src/__tests__/self-healing.test.ts | 194 +++++++++++--------
 packages/engine/src/auto-merge-finalization.ts     | 208 +++++++++++++++++++++
 packages/engine/src/merger-ai.ts                   |  47 +++--
 packages/engine/src/project-engine.ts              |  87 +++++----
 packages/engine/src/run-audit.ts                   |   4 +
 packages/engine/src/self-healing.ts                |  92 ++++-----
 11 files changed, 663 insertions(+), 189 deletions(-)

Fusion-Task-Id: FN-6949

Fusion-Task-Lineage: db784ec0-68e9-4a37-9728-daa6f133e294
2026-06-22 23:18:28 -07:00
gsxdsm
3a71237624 fix(review): address PR #1717 Phase C merge-loop review feedback
- merger-ai: resolve+persist concrete landedSha when a sub-repo is recognized
  already-landed via the Fusion-Task-Id trailer fallback, so finalize no longer
  drops it and mis-finalizes a fully-landed workspace task as a no-op
- project-engine: manual-merge land-lease busy errors reject the resolver without
  burning mergeRetries; clear stale busy-reenqueue counter on real partial land;
  persist retry count before arming the backoff timer (fail closed on write error)
- cli/dashboard + task: use shared isWorkspaceTask predicate instead of inlining
- base-commit-capture: POSIX single-quote shell escaping for integration ref
- git-repository: validate workspace.json repos elements are strings
- merger-ai: drop dead store param from landOneRepo
- tests: assert the 60s backoff cap across cycles; exercise the real runAiMerge
  merge door; fix non-git-root assertion; re-export real workspace error classes
  in the merger-ai mock (fixes 24 pre-existing instanceof-undefined failures);
  remove generic fake-timer smoke test now covered by the live engine assertion

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 03:04:21 -07:00
gsxdsm
627bdcfb0a fix(review): Phase C merge-loop hardening — double-land, lease clobber, retry storm
5-persona review of the Phase-C per-repo merge loop. No P0; the no-push invariant
and retry/park accounting verified clean. Fixed:

Land mechanics (merger-ai.ts / active-session-registry.ts):
- persistRepoLandedSha no longer swallows the DB write: a failed landedSha write
  after the ref advanced now escalates to WorkspacePartialLandError so the engine
  parks/retries instead of silently re-landing (duplicate squash). isRepoLanded
  gains a landedSha-independent fallback — it scans the integration ref for this
  task's Fusion-Task-Id trailer (a squash commit is NOT a branch descendant, so a
  branch-ancestor check is provably wrong), so an actually-landed repo is skipped
  on retry.
- The land lease is now taskId-aware across kinds: any foreign-task holder on a
  sub-repo path is contention (a merging task can't run over an executing task's
  acquire lease), and registerPath throws ActiveSessionPathHeldByForeignTaskError
  instead of silently clobbering a different task's entry.
- The per-repo loop is wrapped in try/finally(setStatus(null)) so the busy/partial
  throws can't leave the task stuck 'merging'. WorkspacePartialLandError is a real
  exported class (not a .name-mutated Error). finalizeWorkspaceTask re-reads fresh
  and no longer swallows the mergeDetails write (TOCTOU). isRepoLanded exported for
  Phase D.

Dispatch + doors (project-engine.ts / dashboard.ts / task.ts / @fusion/core):
- getTask-null in the partial-land catch fails closed (park) instead of defaulting
  retries to 0 and scheduling an indefinite retry storm.
- The merge-confirmed reachability fast-path skips workspace tasks (its
  representative commitSha is a sub-repo squash sha, unreachable in the root cwd —
  it was demoting fully-merged tasks); they're verified by per-repo landedSha.
- The CLI/dashboard merge doors now return merged:true on full land (were hardcoded
  merged:false). WorkspaceRepoLandBusyError re-enqueues with backoff WITHOUT burning
  the mergeRetries quota (bounded busy counter) so contention can't park a healthy
  task. Backoff capped at 60s. shouldRetryWorkspacePartialLand folded into
  shouldRetryAutoMergeConflict. Catch switched to instanceof. New canonical
  isWorkspaceTask predicate in @fusion/core.

Gate green: build, typecheck, lint, test:gate (649+58); workspace-merger + oracle
+ project-engine 174.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 01:11:52 -07:00
gsxdsm
64e87f9a12 feat(workspace): Phase C U3 — per-repo land lease (serialize same-sub-repo lands)
landWorkspaceTask now holds a per-repo land lease around each landOneRepo call:
a new activeSessionRegistry kind "workspace-repo-land" keyed on the sub-repo
absolute path, registered synchronously before the per-repo try and released in
a finally (on success and failure, only yanking our own taskId+ownerKey entry —
never a foreign/different-kind entry). Two workspace tasks landing the same
sub-repo serialize; the loser throws the retryable WorkspaceRepoLandBusyError,
which reuses the U2 partial-land retry/park machinery (consume a mergeRetry,
backoff re-enqueue up to MAX skipping landed repos, then operator-park). Disjoint
sub-repos never falsely serialize.

The lease is for serialization / clean-room-collision avoidance, not ref
correctness — advanceIntegrationBranchRef's CAS already makes interleaved
update-ref safe. Distinct from the execution-phase "workspace-repo-acquire" lease
(different kind, different lifecycle phase, each ignores the other's entry).

3 new tests (serialize, independence, release-on-failure); oracle (56) + U1/U2
(idempotency) stay green. Gate: build, typecheck, lint, test:gate (649+58).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 23:56:41 -07:00
gsxdsm
7544346320 feat(workspace): Phase C U2 — per-repo landed predicate, finalize-once, auto-retry-then-park
landWorkspaceTask now tracks per-repo landing and finalizes the task exactly once.
After a repo lands, its advanced integration tip is persisted as
workspaceWorktrees[repo].landedSha (fresh-read merge, siblings untouched). Before
landing, isRepoLanded skips a repo iff its landedSha is present AND an ancestor of
(or equal to) its local integration ref — so a retry after a partial land never
re-advances an already-landed ref. finalizeWorkspaceTask runs only when every
acquired repo is landed: it builds an aggregate MergeResult (representative
commitSha + a workspaceLandedShas map in MergeDetails) and calls the existing
task-global finalizeTask once, satisfying the task:merged consumer. No premature
done on the first repo.

Partial lands surface as WorkspacePartialLandError; the engine consumes a
mergeRetry and re-enqueues landWorkspaceTask (skipping landed repos) with the
existing conflict-retry backoff up to MAX, then operator-parks (status:failed) —
mirroring shouldRetryAutoMergeConflict (new exported shouldRetryWorkspacePartialLand
seam). The defense-in-depth WorkspaceTaskMergeError still hard-fails without
burning retries; manual merges fall through to rejectMergeResolvers.

types: workspaceWorktrees entry gains landedSha?; MergeDetails gains
workspaceLandedShas?. 6 new idempotency/predicate/finalize-once/retry-park tests;
oracle (52) + U1 (5) stay green. Gate: build, typecheck, lint, test:gate (649+58).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 23:46:44 -07:00
gsxdsm
744ed098a5 feat(workspace): Phase C U1 — per-repo merge loop (landOneRepo + landWorkspaceTask)
Extracts the per-repo land mechanics out of runAiMerge's inline clean-room
closure into an exported landOneRepo(store, repoRootDir, branch, integrationBranch,
ctx): pre-merge prune (rooted at the sub-repo), the clean-room temp worktree,
mergeAndReview, landSquash, and the CAS concurrent-advance retry that advances ONE
local integration ref — no remote push. runAiMerge is rewired as the single-repo
caller (its task-global finalization unchanged); the merger-ai suite (56 tests)
stays green as the byte-for-byte oracle.

landWorkspaceTask loops a workspace task's acquired sub-repos (sorted keys),
re-resolving each repo's integration branch with the shared override stripped
({...settings, integrationBranch: undefined, baseBranch: undefined}) so each
sub-repo lands on its own origin/HEAD, calls landOneRepo per repo, and aggregates
repo-tagged results — land-as-you-go on each repo's LOCAL ref (D2/D5). It does NOT
finalize/move the task (finalize-once + landed-tracking + idempotent retry are U2).

Door routing (KTD2): the engine dispatch and the user-facing CLI `fn task merge`
+ dashboard merge doors route workspace tasks to landWorkspaceTask so manual merge
works; store.mergeTask, aiMergeTask, and the runAiMerge chokepoint guard keep
throwing WorkspaceTaskMergeError as defense-in-depth.

New two-repo fixture tests: both repos land + no-push assertion, per-repo
override-stripped resolution onto distinct branches, repo-B conflict partial land
(task not moved), defense-in-depth throws. Gate green: typecheck, lint, build,
test:gate (649+58).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 23:31:43 -07:00
gsxdsm
316d2659b8 fix(review): workspace-merge park must use status:'failed' to avoid re-enqueue loop (U0)
Post-fix verification review (correctness + adversarial + reliability, unanimous
P0) found that the earlier retry-burn fix introduced an infinite loop: parking a
WorkspaceTaskMergeError task with status:null + mergeRetries:0 passes every
auto-merge eligibility gate (canMergeTask short-circuits only on status==='failed'),
so the cooldown sweep re-enqueues it every tick → guard re-throws → re-park, forever.

- Park with status:'failed' (keep mergeRetries:0). canMergeTask now blocks the
  auto-sweep; a human's manual merge still works because it flows through the
  manual-resolver branch (rejectMergeResolvers), which bypasses canMergeTask — so
  'failed' does not block manual retry (the original comment's worry was wrong).
- Detect the error via `err instanceof Error && err.name === "WorkspaceTaskMergeError"`,
  matching the VerificationError/MergeAbortedError convention and bundle-safe across
  the @fusion/core→@fusion/engine boundary (drops the now-unused class import).
- Document that the dispatch door guard is a fast-fail only; the unconditional
  chokepoint guard inside runAiMerge is the authoritative enforcement.
- Add a regression test asserting the auto-merge park sets status:'failed' (not null).

Gate green: lint, typecheck, build, test:gate (649+58), project-engine (81).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 20:29:46 -07:00
gsxdsm
7240b77c67 fix(review): harden R7 workspace merge guard + deprecation warning (U0)
Applies ce-code-review (autofix) feedback — 5 reviewers, P1s corroborated.

F1 [P1, ×4 reviewers] Guard the merge chokepoint, not just the 4 doors. The
per-caller `getTask().catch(()=>null); if(t) assert` pattern failed open on a
transient read, and runAiMerge re-read the task unguarded — so a workspace
task could reach git work against the non-git root. Added a named
WorkspaceTaskMergeError and call assertNotWorkspaceTaskMerge inside runAiMerge
(the sole merge path) and the deprecated aiMergeTask body; door guards remain
as fast-fail defense-in-depth.

F2 [P1] The dispatch catch treated the guard throw as a merge failure and set
mergeRetries=MAX, permanently blocking manual retry. It now recognizes
WorkspaceTaskMergeError and parks without burning retries.

F3 [P2] Deprecation-warning test asserted toBeLessThanOrEqual(1) — vacuously
true on zero emissions. Now resets the per-project flag and asserts the
warning fires exactly once and not again on a second deterministic merge.

F6 [P2] The once-per-process warning flag suppressed the notice for all other
projects in a multi-project host; now keyed per project (Set by cwd).

F5/F7/F8 [P3] @deprecated propagated to the aiMergeTask barrel re-export; CLI
runTaskMerge guard moved inside the formatted try/catch; FNXC placeholder
timestamps corrected; test .at(-1) -> length index.

Documented as residual (deferred to master-plan U8, not bugs in U0's window):
self-healing auto-finalize + store.mergeTask are additional merge-completing
paths not hardened here — workspace tasks are not end-to-end runnable until
master-plan Phase A, and U8 makes self-healing workspace-aware.

Gate green: typecheck (29 projects), lint, build, test:gate (649+58),
affected tests (206+4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:19:55 -07:00
gsxdsm
a6252e518d refactor(merger): unify on runAiMerge as the sole merge path (U0)
Make runAiMerge (the FN-5633 clean-room AI merge path, already the default)
the sole merge path; soft-deprecate the legacy aiMergeTask pipeline. Phase 0
of the workspace-mode master plan — a standalone merge-consolidation refactor
that lands first so downstream workspace work targets one merge function.

U1 — collapse the dispatch: project-engine.ts now calls runAiMerge
unconditionally; the two direct callers that bypassed the dispatch
(dashboard.ts onMergeImpl in --no-engine mode, task.ts runTaskMerge /
`fn task merge`) now route to runAiMerge too. Export runAiMerge from
@fusion/engine.

U2 — soft-deprecate: aiMergeTask is @deprecated (body retained for a later
deletion pass; shared helpers runAiMerge imports, e.g.
captureSingleCommitLandedMetadata, left intact). merger.mode "deterministic"
is annotated deprecated and made inert (type + field kept — published
@runfusion/fusion surface); the dispatch logs a one-time deprecation warning
and routes to runAiMerge. Changeset added (minor).

U3 — R7 workspace merge-boundary guard: shared @fusion/core predicate
assertNotWorkspaceTaskMerge(task) rejects tasks with populated
workspaceWorktrees at all four merge entry points (dispatch, store.mergeTask,
onMergeImpl, runTaskMerge) with an error naming master-plan U6. Covers the
window until per-repo merge support lands; U6 removes it.

U4 — deterministic-mode blast-radius audit: no production project, CI config,
or seeded setting pins merger.mode "deterministic"; only four engine tests
used it to drive the dispatch to aiMergeTask as a mockable seam — migrated to
mock runAiMerge instead. Other module-level aiMergeTask mocks were dead under
the default "ai" mode or test aiMergeTask directly (body retained), so they
are unaffected.

Also removes an unused acquireWorkspaceRepoWorktree import inherited from the
foundation branch (executor.ts) that was failing lint; master-plan U1 re-adds
it with its per-repo usage.

Merge gate green: lint, typecheck (29 projects), build, test:gate
(649 + 58), plus the migrated (114) and new predicate (4) tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 18:58:06 -07:00
gsxdsm
e2a3a37b26 FN-6569: make auto-merge retry cap configurable
Adds a project setting that controls how many auto-merge conflict retries run before recovery paths give up.

- Add maxAutoMergeRetries to project settings defaults, schema, types, and dashboard controls.
- Resolve the retry cap in engine merge handling, self-healing recovery, stall detection, and blocker fanout logic.
- Cover custom retry caps with core, dashboard, and engine regression tests.

Files changed:
 .changeset/fn-6569-max-auto-merge-retries.md       |  5 ++
 docs/architecture.md                               |  2 +-
 docs/settings-reference.md                         |  1 +
 .../core/src/__tests__/settings-defaults.test.ts   | 12 ++++
 packages/core/src/in-review-stall.ts               | 16 ++++-
 packages/core/src/index.ts                         |  1 +
 packages/core/src/settings-schema.ts               |  6 ++
 packages/core/src/task-priority.ts                 |  4 +-
 packages/core/src/types.ts                         |  8 +++
 .../dashboard/app/components/SettingsModal.tsx     |  8 +++
 .../components/settings/sections/MergeSection.tsx  | 26 +++++++
 .../app/hooks/__tests__/useBlockerFanout.test.ts   |  5 +-
 packages/dashboard/app/hooks/useBlockerFanout.ts   |  3 +-
 .../auto-merge-retry-cap-settings.test.ts          | 79 ++++++++++++++++++++++
 packages/engine/src/project-engine.ts              | 77 +++++++++++++--------
 packages/engine/src/self-healing.ts                | 30 +++++---
 16 files changed, 236 insertions(+), 47 deletions(-)

Fusion-Task-Id: FN-6569
Fusion-Task-Lineage: 42242d6a-68bc-41f1-b2d9-af2e6f168eed
2026-06-17 04:51:31 -07:00
gsxdsm
bffae81a98 FN-6277: track and reconcile legacy auto-merge stamps
Track legacy auto-merge stamp provenance and add safe operator cleanup.

- Add autoMergeProvenance storage and migration support so user overrides can be distinguished from legacy review-entry stamps.
- Mark existing ambiguous in-review autoMerge=true tasks as legacy stamps without changing behavior, and expose a dry-run/apply reconciliation API to clear them safely.
- Emit a non-mutating advisory when global auto-merge is disabled while legacy stamped review tasks remain.
- Cover migration, persistence, reconciliation, movement, merge resolution, and advisory behavior with regression tests and docs.

Files changed:
 .../fn-6277-legacy-automerge-stamp-cleanup.md      |   5 +
 docs/architecture.md                               |   2 +-
 docs/settings-reference.md                         |   2 +-
 packages/core/src/__tests__/db-migrate.test.ts     |  30 ++--
 packages/core/src/__tests__/db.test.ts             |  44 +++---
 packages/core/src/__tests__/goals-schema.test.ts   |   2 +-
 packages/core/src/__tests__/insight-store.test.ts  |  10 +-
 .../legacy-automerge-stamp-reconcile.test.ts       | 144 +++++++++++++++++++
 .../src/__tests__/merge-request-record.test.ts     |   2 +-
 packages/core/src/__tests__/mission-store.test.ts  |   2 +-
 packages/core/src/__tests__/run-audit.test.ts      |   4 +-
 .../core/src/__tests__/store-merge-queue.test.ts   |   2 +-
 packages/core/src/__tests__/store-movement.test.ts |  14 ++
 packages/core/src/__tests__/task-documents.test.ts |   2 +-
 packages/core/src/__tests__/task-merge.test.ts     |  16 ++-
 packages/core/src/db.ts                            |  10 +-
 packages/core/src/index.ts                         |   1 +
 packages/core/src/store.ts                         | 157 ++++++++++++++++++++-
 packages/core/src/task-merge.ts                    |   8 +-
 packages/core/src/types.ts                         |  10 +-
 .../automerge-toggle-legacy-advisory.test.ts       | 128 +++++++++++++++++
 packages/engine/src/project-engine.ts              |  68 ++++++++-
 22 files changed, 593 insertions(+), 70 deletions(-)

Fusion-Task-Id: FN-6277

Fusion-Task-Lineage: 22d36519-f2ba-4ca7-8a09-12fc803c9a5b
2026-06-13 05:06:05 -07:00
Phil Larson
8541db3185 fix(engine): normalize dashboard research providers 2026-06-12 15:57:12 -07:00
gsxdsm
b6800e8622 fix(FN-6035): address workflow primitive review feedback 2026-06-08 21:16:32 -07:00
gsxdsm
1922b5efc2 merge: main (workflow editor 109 / cli_sessions 110-111 / workflow_settings 112) — renumber PR-entity migration to 113, union core exports, TaskCard prNode + cliSessionState badges, executor PrNodeDeps + CliAgentRuntime options 2026-06-05 23:06:46 -07:00
gsxdsm
c13f9a123b feat(pr): node-agnostic PR reconcile + hold-release firing (U4)
Adds PrReconciler — a per-repo, self-owned polling loop (started from the
runtime layer in project-engine.ts, NOT the scheduler) that ETag-probes
GitHub, deep-fetches on change, persists mirror state, clears unverified
on first reconcile, and fires releaseHeldTaskByEvent(github:pr-<event>)
for transitions (changes-requested/approved/conflict/conflict-cleared/
merged/closed). Drops terminal entities; persists an audit event on error.
GitHub ops injected via PrReconcileGithubOps at the 3 CLI sites; engine
never imports the dashboard client. scheduler.ts stays PR-free (R20),
pinned by a regression test. 8 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 20:05:29 -07:00
gsxdsm
4e16145aab feat(pr): pr-create/pr-respond/pr-merge node kinds + handlers (U3)
Adds the three first-class PR workflow node kinds and their handlers via
createPrNodeHandlers(deps), registered in createDefaultNodeHandlers
(fail-closed pr-nodes-unwired when absent). GitHub ops are injected as
callbacks (PrNodeGithubOps) at all three CLI sites (daemon/serve/dashboard)
so the engine never imports the dashboard client (FN-3049). pr-create
routes open/failed as outcomes; pr-merge passes expectedHeadOid and never
writes 'merged' (reconcile corroborates); pr-respond delegates to an
injected respond callback (U5 fills the body). 10 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 19:55:31 -07:00
gsxdsm
58723312d8 feat: bootstrap cli-agent runtime and wire executor, transport, chat, and recovery seams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 03:24:41 -07:00
gsxdsm
fcebab56fc Update packages/engine/src/project-engine.ts
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-06-03 21:18:45 -07:00
gsxdsm
18ba5c0c59 Address PR review feedback (#1363)
- db.ts: restrict migration-105 orphan-step cleanup to JSON arrays
  (json_type guard so json_each can't expand objects/strings)
- project-engine.ts: requestInterpreterMerge throws on null task lookup
  instead of casting null into MergeResult (seam converts to clean failure)
- executor.ts: truncate dual-observe shadow stage walk at the live terminal
  stage so healthy in-review tasks don't record a phantom merge transition

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 21:02:01 -07:00
gsxdsm
759e2e67af Merge remote-tracking branch 'origin/main' into gsxdsm/workflowbuilder 2026-06-03 19:52:37 -07:00
gsxdsm
8bb02f7d3c fix(engine,core): merge-seam multi-waiter + autoMerge gate; selection lock; settings toggle
Resolve the two needs-human findings from PR #1363 review, plus surface the flag.

Merge seam (project-engine.ts):
- manualMergeResolvers is now a per-task LIST of waiters. Both the dashboard
  "merge now" path and the interpreter merge seam call onMerge, so a single
  resolver per task let the second caller overwrite (and strand) the first.
  All resolve/reject/requeue/late-resolver/shutdown sites drain the whole list.
- New requestInterpreterMerge() honors auto-merge eligibility: when autoMerge
  is off (or the task isn't merge-ready) it returns merged:false instead of
  forcing the merge, so a graph merge node can't override an autoMerge-off
  project — it parks the task in review for a human. setMergeRequester now wires
  the interpreter to this gate rather than the human bypass.

Selection race (store.ts):
- selectTaskWorkflow/clearTaskWorkflowSelection now hold one withTaskLock across
  their whole mutate sequence. Extracted updateTaskUnlocked() (the per-task lock
  is non-reentrant, so they couldn't wrap the public updateTask without
  deadlocking) and call that inside the lock.

Settings:
- Add "Workflow Graph Engine (run custom workflows)" to the Experimental
  Features list so the workflowGraphExecutor flag is a labeled toggle in
  Settings → Experimental, not just a raw key.

Tests: interpreter-merge-seam.test.ts (multi-waiter resolve/reject + autoMerge
eligibility gate); existing merge lifecycle/bypass/selection suites still pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 17:53:40 -07:00
gsxdsm
46f525bd28 Merge main: reconcile duplicate pausedReason persistence fix + dashboard deps
Main independently landed the same pausedReason persistence fix (column in
the task INSERT/upsert, TaskRow type, rowToTask mapping) with the column
ordered between paused and userPaused; adopt main's ordering and drop this
branch's duplicate entries so the values array stays aligned. Union the
dashboard package.json deps (main's compound-engineering plugin alongside
this branch's sorted pi-coding-agent/cli-printing-press/cursor-runtime).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 16:37:18 -07:00
gsxdsm
d1cd8b6ed8 merge main: unify CONCEPTS.md vocabularies and reconcile core exports
Both sides bootstrapped CONCEPTS.md — kept main's broader structure (Missions,
Merge lifecycle, CE sessions) and folded the branch-group vocabulary into its
Merge-lifecycle cluster (enriched Shared branch group + Self-healing sweep;
added Branch assignment mode, Landed, Group promotion). Union-merged core
index/store imports; took main's AGENTS.md discoverability wording. Also adds
execFile to the two child_process test mocks that only stubbed exec (the
coordinator's argv-based git calls now require it).
2026-06-03 15:57:41 -07:00
gsxdsm
f3bc757d22 fix(FN-branch-group): address PR review feedback (#1357)
- abandon route: guard already-abandoned groups (matches CLI)
- CLI branch-group list: single task fetch via filterTasksByBranchGroup (N+1)
- branch-name validator: git check-ref-format parity (//, dot-segments, .lock, @, @{, trailing /.)
- updateBranchGroup: validate renamed branchName too
- already-merged-detector: escape regex metachars in git log --grep; non-vacuous prose-mention assertion
- group PR callbacks: thread per-project cwd through SyncGroupPrFn/reconcile/github helpers (multi-project correctness)
- merger: group-PR sync is fire-and-forget (never blocks merge completion); deterministic test handle
- coordinator: sibling PR reuse only when open; reconcile skips member fetch on read-only path; argv-based git calls (no shell)
- task-lifecycle: legacy group-PR path links open PRs only; branch probes via execFile argv (injection hardening)
- mission/planning: branchContext.groupId only stamped for actual shared-mode members (groupId now optional)
- UI: Abandon reachable whenever PR is open (decoupled from completion); promote stays completion-gated
- tests: deterministic concurrency gate, real reconcile path in e2e, Surface Enumeration sections
2026-06-03 13:51:13 -07:00
gsxdsm
d9272abd0f fix(FN-branch-group): promotion lock, PR repair, audit on failure, typed sync block
Review residuals #3/#4/#6/#10: per-group in-process promotion lock (concurrent
route+auto promotion could double-create PRs), finalized-but-PR-less groups can
be repaired by re-promotion without re-merging, auto-promotion failures emit
merge:branch-group-promotion-failed instead of silent swallow, exported
reconcileBranchGroupPr for out-of-band merged reconciliation, and the merger
sync block drops its (store as any) casts (TaskStore already carries the
methods).
2026-06-03 12:25:11 -07:00
gsxdsm
44e77fda5c feat(engine): graph interpreter owns the full task lifecycle behind the flag (CU-U3, CU-U4)
Real engine seams: execute delegates to the legacy implementation phase via a
completion interceptor that stops execute() at the implementation-complete
boundary (no double review/merge); review performs the in-review handoff; merge
resolves through ProjectEngine.onMerge over the same serialized merge queue
(wired via a late-bound setMergeRequester, mirroring setMergeEnqueuer). Custom
graph nodes run on the proven WorkflowStep machinery (readonly tool policy,
verdict parsing). Adds a 'planning' seam to the vocabulary (no-op for
pre-specified tasks; custom planning is a prompt node today).

Entry point: execute() routes graph-selected tasks through the runner when
experimentalFeatures.workflowGraphExecutor is on, with process-wide routing
claims (FN-4811 posture), duplicate-dispatch dropping, pre-run errors falling
back to legacy, and mid-run errors parking the task in review (never re-running
the implementation, never stranding the task).

Flag off by default: all 587 executor tests pass unchanged.
2026-06-03 11:07:30 -07:00
gsxdsm
415470c7bd feat(FN-branch-group): sync group PR as members land + terminal lifecycle (U6)
Push the single group PR's body (member checklist, x/N landed) on each member
landing via an injected SyncGroupPrFn — new updatePr/closePr GitHubClient
helpers (gh CLI + API parity); refreshPrInBackground is task-scoped/wrong
direction and intentionally not reused. Sync failures are non-fatal+retryable;
out-of-band closed/merged PRs reconcile prState instead of erroring. New
POST /branch-groups/:id/abandon closes the PR best-effort and marks the group
abandoned. Also fixes the U5-introduced stub-context regression in the U4
dashboard bridge test (missing options).
2026-06-03 10:20:30 -07:00
gsxdsm
ad468813d5 fix(engine): honor per-task auto-merge override when global auto-merge is off
Tasks with autoMerge explicitly enabled never auto-merged when the
project-level setting was disabled: the merge enqueue gate
(allowInReviewMergeProcessing) and all 19 in-review self-healing sweeps
checked only settings.autoMerge, and the board stall-signal hydration
passed the raw global into the diagnostic gates.

Introduce allowsAutoMergeProcessing(task, settings) in core — additive
relative to the global setting so configs with global auto-merge ON are
unchanged (explicit autoMerge:false tasks still flow to the merger's
manual-required parking) — and use it at the enqueue gate, every
self-healing sweep, and the store's stall/stalled signal contexts.
2026-06-03 10:18:24 -07:00
gsxdsm
b1454c198e feat(FN-branch-group): create single real GitHub PR on group promotion (U5)
Group promotion in PR mode previously flipped prState to 'open' without ever
calling GitHub — prNumber/prUrl were never populated. Add an injected
CreateGroupPrFn (mirrors the processPullRequestMerge seam, no engine→dashboard
import): coordinator creates-or-reuses exactly one PR per group, persists
prNumber/prUrl/prState, and leaves state untouched on GitHub failure so
re-promotion retries. Idempotent via persisted prNumber +
getBranchGroupByBranchName. Wired at all three CLI engine-construction sites
(daemon/dashboard/serve).
2026-06-03 10:01:53 -07:00
gsxdsm
508b9c44d0 fix(FN-branch-group): add engine.promoteBranchGroup bridge method (U4)
The dashboard promote route called engine.promoteBranchGroup(groupId) as a
method that never existed — only a standalone coordinator function did — so
the route was dead, masked by a vi.fn mock in the test. Add the real method on
ProjectEngine delegating to the coordinator (resolving store/cwd/settings like
attemptBranchGroupPromotion), and de-mock the test so it now fails if the
method goes missing. No PR-creation behavior yet (U5).
2026-06-03 09:46:54 -07:00
gsxdsm
a1b7556c7b FN-5924: throttle OAuth expiry alerts across restarts
Persist OAuth expiry alert state so repeated provider expiry warnings are suppressed for 12 hours across engine restarts.

- add a persisted OAuth alert state store under ~/.fusion/agent and share it between the expiry monitor and startup validity logger
- throttle repeated oauth-token-expired notifications and startup expiry warnings per provider for 12 hours, while clearing stale state when providers disappear or change
- cover persisted throttling, restart behavior, failure handling, and wiring updates in engine notification tests
- document the persisted 12-hour OAuth alert throttle and add a patch changeset for @runfusion/fusion

Files changed:
 .changeset/fn-5924-oauth-alert-throttle.md         |   5 +
 docs/settings-reference.md                         |   4 +-
 packages/engine/src/__tests__/project-engine-soft-delete-merge-abort.test.ts |   3 +
 packages/engine/src/__tests__/project-engine.test.ts    |  17 ++-
 packages/engine/src/__tests__/reliability-interactions/soft-delete-in-flight-abort.test.ts            |   3 +
 packages/engine/src/auth-storage.ts                |   6 +-
 packages/engine/src/notification/__tests__/oauth-alert-state.test.ts            |  72 ++++++++++
 packages/engine/src/notification/__tests__/oauth-expiry-monitor.test.ts         | 148 ++++++++++++++++---
 packages/engine/src/notification/__tests__/oauth-validity-logger.test.ts        | 159 ++++++++++++++++++---
 packages/engine/src/notification/index.ts          |   3 +
 packages/engine/src/notification/oauth-alert-state.ts   | 144 +++++++++++++++++++
 packages/engine/src/notification/oauth-expiry-monitor.ts       |  12 +-
 packages/engine/src/notification/oauth-validity-logger.ts      |  16 ++-
 packages/engine/src/project-engine.ts              |  13 +-
 14 files changed, 554 insertions(+), 51 deletions(-)

Fusion-Task-Id: FN-5924
Fusion-Task-Lineage: 83255a25-40c7-44d4-8302-b068ae51250e
2026-06-02 23:13:16 -07:00
gsxdsm
e561290174 FN-5846: route shared-group member finalization to shared branch
Ensure shared branch-group members keep their routed shared-branch target through merge finalization and recovery.

- ignore a shared member's default-base branch when resolving branch-group merge routing
- reroute merge-confirmed fast paths and self-healing recovery flows to the branch-group integration branch
- stamp merge target metadata, record member landings, and add regression coverage plus architecture/changeset updates

Files changed:
 .changeset/fn-5846-shared-group-merge-routing.md   |   5 +
 docs/architecture.md                               |   4 +-
 packages/engine/src/__tests__/group-merge-coordinator.test.ts  |  26 +++
 packages/engine/src/__tests__/reliability-interactions/shared-branch-group-lifecycle.test.ts  |  73 +++++--
 packages/engine/src/group-merge-coordinator.ts     |  11 +-
 packages/engine/src/project-engine.ts              |  49 ++++-
 packages/engine/src/self-healing.ts                | 212 ++++++++++++++++++++-
 7 files changed, 357 insertions(+), 23 deletions(-)

Fusion-Task-Id: FN-5846

Fusion-Task-Lineage: c8fbf8b8-3cbe-4ee5-96d9-9040bd4f0df5
2026-06-01 20:19:37 -07:00
gsxdsm
3373c0beed FN-5830: re-land shared branch promotion gate and API
Reintroduce shared-branch-group completion gating and promotion endpoints with reliability coverage.

- add GroupMergeCoordinator promotion gate behavior and shared branch group promotion logic updates
- expose promotion flow wiring through engine index and project engine APIs
- expand coordinator tests and add reliability interaction coverage for branch-group promotion
- document the new reliability backstop and add a changeset for @runfusion/fusion

Files changed:
 .changeset/fn-5830-branch-group-promotion.md       |   5 +
 AGENTS.md                                          |   1 +
 docs/architecture.md                               |   3 +-
 .../src/__tests__/group-merge-coordinator.test.ts  | 170 ++++++++++++++++++-
 .../branch-group-promotion.test.ts                 | 166 +++++++++++++++++++
 packages/engine/src/group-merge-coordinator.ts     | 183 ++++++++++++++++++++-
 packages/engine/src/index.ts                       |   4 +
 packages/engine/src/project-engine.ts              |  37 +++++
 8 files changed, 566 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-5830

Fusion-Task-Lineage: e9823f51-df0e-4da8-8f51-58dfefdc293d
2026-06-01 05:39:01 -07:00
gsxdsm
35ce9bd244 FN-5819: allow shared-group members to merge into group branch with auto-merge off
Allow shared branch-group members to continue member→group integration even when project auto-merge is disabled.

- add core helper to detect shared branch-group member integrations and export it
- update engine in-review enqueue/handoff/startup flows to permit shared-member integration while still honoring global/engine pause gates
- keep self-healing from pulling shared-member in-review tasks backward during auto-merge-off maintenance
- expand project-engine and reliability-interaction tests for shared-member autoMerge-off behavior and add FN-5819 backstop docs updates

Files changed:
 AGENTS.md                                          |  2 +
 docs/architecture.md                               |  3 +-
 packages/core/src/__tests__/task-merge.test.ts     | 37 +++++++++
 packages/core/src/index.ts                         |  1 +
 packages/core/src/task-merge.ts                    | 13 +++
 packages/engine/src/__tests__/project-engine.test.ts    | 64 ++++++++++++++
 packages/engine/src/__tests__/reliability-interactions/branch-group-merge-routing.test.ts             | 48 +++++++++++
 packages/engine/src/__tests__/reliability-interactions/shared-group-member-integration.test.ts        | 97 ++++++++++++++++++++++
 packages/engine/src/project-engine.ts              | 35 ++++----
 packages/engine/src/self-healing.ts                |  3 +-
 10 files changed, 284 insertions(+), 19 deletions(-)

Fusion-Task-Id: FN-5819

Fusion-Task-Lineage: b5eeced7-7c47-4573-9ab4-ca577bec65e1
2026-06-01 02:42:36 -07:00
Phil Larson
3dee395d67 fix(engine): block dirty integration merge landings
Refuse AI merge landing on a checked-out dirty integration worktree by default. This prevents Fusion from stashing/restoring unrelated project-root edits into main and then marking tasks done against contaminated state.

Fusion-Task-Id: FN-5780
2026-05-31 13:12:13 -07:00
gsxdsm
c9b159f4e5 FN-5743: cut over merge dequeue authority to merge-request queue
Shift merge dequeue enforcement to merge-request queue/marker authority with reliability coverage updates.

- Enforce merge eligibility and dequeue ownership via merge-request queue/marker checks in engine and scheduler paths.
- Add hard-cancel behavior coverage to ensure queued merge requests are canceled when tasks are user-canceled.
- Extend core merge-request record/store tests and reliability interaction suites for dual-observe and cancel-on-hard-cancel seams.
- Document the FN-5741/FN-5743 reliability backstop updates in AGENTS.md and architecture docs.

Files changed:
 AGENTS.md                                          |  2 +
 docs/architecture.md                               |  2 +
 packages/core/src/__tests__/merge-request-record.test.ts     | 18 ++++
 packages/core/src/store.ts                         | 13 +++
 packages/engine/src/__tests__/reliability-interactions/dual-observe-merge-seam.test.ts                | 57 +++++++++++++
 packages/engine/src/__tests__/reliability-interactions/merge-request-cancel-on-hard-cancel.test.ts    | 87 +++++++++++++++++++
 packages/engine/src/project-engine.ts              | 98 +++++++++++++++++++++-
 packages/engine/src/scheduler.ts                   | 12 ++-
 8 files changed, 284 insertions(+), 5 deletions(-)

Fusion-Task-Id: FN-5743

Fusion-Task-Lineage: b9a49aeb-ed73-42fe-924d-25c7097d1bb9
2026-05-30 20:48:16 -07:00
gsxdsm
8609669a56 FN-5742: add dual-observe merge-request parity seam
Add a dual-observe merge-request seam that preserves legacy authority while emitting parity signals.

- Add scheduler parity helpers for dependency satisfaction and shadow lease state, keeping legacy scheduling decisions authoritative.
- Extend ProjectEngine/run-audit merge dequeue flow with shadow-candidate selection and parity audit emission.
- Add reliability interaction coverage for dual-observe dependency parity, shadow dequeue parity, lease parity, and runnable guard behavior.
- Document FN-5742 reliability backstop coverage in architecture docs.

Files changed:
 docs/architecture.md                               |   2 +
 packages/engine/src/__tests__/reliability-interactions/dual-observe-merge-seam.test.ts                | 193 +++++++++++++++++++++
 packages/engine/src/__tests__/scheduler.test.ts    |  64 +++++++
 packages/engine/src/project-engine.ts              |  42 +++++
 packages/engine/src/run-audit.ts                   |   3 +
 packages/engine/src/scheduler.ts                   | 158 +++++++++++++++--
 6 files changed, 448 insertions(+), 14 deletions(-)

Fusion-Task-Id: FN-5742

Fusion-Task-Lineage: dc4c0212-fe75-4860-bf06-2765a0b18d38
2026-05-30 18:52:19 -07:00
Phil Larson
6b57e7cbf6 fix(engine): resume execution after review revise 2026-05-29 12:57:53 -07:00
gsxdsm
a014c6d42d FN-5697: retry transient auto-merge failures and fix migration versioning
Treat transient auto-merge/provider abort errors as bounded retries while preserving correct schema migration ordering.

- Add transient merge retry handling with capped exponential backoff, queue re-enqueue, and exhaustion logging before failing tasks.
- Extend task/core types and evaluator evidence plumbing for merge transient retry tracking and MergeTransientRetryExhausted visibility.
- Add regression coverage for transient auto-merge retries and exhaustion behavior in merge error recovery tests.
- Resolve migration collision by promoting workflow_steps.gateMode migration to version 77, shifting subsequent migrations, and bumping schema version to 95.
- Add a changeset and architecture note documenting transient retry behavior.

Files changed:
 .changeset/fn-5697-auto-merge-transient-retry.md   |  5 ++
 docs/architecture.md                               |  1 +
 packages/core/src/db.ts                            | 85 +++++++++++----------
 packages/core/src/eval-types.ts                    |  1 +
 packages/core/src/store.ts                         | 19 +++--
 packages/core/src/types.ts                         |  7 ++
 packages/engine/src/__tests__/evaluator-evidence.test.ts       |  1 +
 packages/engine/src/__tests__/merge-error-recovery.test.ts     | 87 ++++++++++++++++++++++
 packages/engine/src/evaluator-evidence.ts          |  1 +
 packages/engine/src/project-engine.ts              | 68 +++++++++++++++++
 10 files changed, 231 insertions(+), 44 deletions(-)

Fusion-Task-Id: FN-5697

Fusion-Task-Lineage: c8be7374-7cb6-444e-9920-9227e05a43dc
2026-05-29 10:23:59 -07:00
gsxdsm
29ac58f8d0 feat(FN-5633): standalone AI merge path (clean-room merge + AI reviewer)
New default merge path (merger.mode="ai"), self-contained in merger-ai.ts and
dispatched from ProjectEngine.onMerge instead of the legacy aiMergeTask pipeline
(kept for merger.mode="deterministic").

Flow: clean-room detached worktree at the target branch tip → AI agent merges
the task branch + squashes (resolving conflicts) → fresh read-only AI reviewer
audits with corrective retries (blocking vs advisory; advisory lands, unfixable
correctness hard-fails via AiMergeBlockedError; fail-safe verdict parsing) →
land via `git merge --ff-only` when the checkout is on the target (else
update-ref CAS) → sync the local checkout (stash → ff → restore; AI reconciles
a conflicting restore and keeps the original edits in a backup stash;
un-stashable dirt advances the ref + warns) → finalize (delete task branch —
never the integration branch — task→done, remove temp worktree).

- Per-task target branch honored (falls back to the default integration
  branch); local checkout synced only when on that target.
- Structurally immune to the dirty-clobber and stale-base/non-FF bug classes of
  the legacy path (clean room + FF-by-construction).
- Progress surfaced on the task status pill + task log stream.
- Clear error when the target branch has no local ref.

Settings: merger.mode / merger.reviewerModel / merger.maxReviewPasses, surfaced
in Settings → Merge; legacy merge-mechanics settings hidden when AI mode is on.

Tests: merger-ai.test.ts (verdict parser, clean merge, blocking hard-fail,
advisory land, empty no-op, target-branch isolation, missing-target error,
landSquash clean/other-branch/dirty-restore/AI-resolved). Legacy
merge-orchestration tests pinned to deterministic mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 18:50:23 -07:00
gsxdsm
e75c4dae28 fix(FN-5627): suppress ntfy notifications for transient merge failures the engine auto-recovers
Even with FN-5627's merger TOCTOU fix + transient-failure self-healing
sweep + safety-fallback auto-prerebase landed, the merger can still hit
transient failure classes (lease handoff races, brief same-SHA non-FF
advances) for tasks whose branches are particularly out-of-sync. The
self-healing sweep auto-recovers them within bounded budget \u2014 but each
individual failure cycle was firing a ntfy alarm before the recovery
cleared the failed state, producing user-facing alarm spam for tasks
that were never actually stuck.

Two layers of fix:

1. NotificationService.handleTaskUpdated now classifies task.error via
   the new shared classifyTransientMergeError helper before scheduling
   the deferred failure notification. Transient classes
   (lease-handoff-target-not-queued, spurious-concurrent-advance-same-sha)
   get logged as suppressed and never schedule a ntfy timer.

2. Defense-in-depth: fireDeferredFailureNotification re-classifies the
   error at dispatch time, so a failure scheduled before the suppression
   landed on a newer cycle still suppresses if the error matches a
   transient class.

The classifier itself moved from self-healing.ts to a new logger-free
transient-merge-error-classifier.ts module so consumers in
NotificationService don't pull createLogger through the import chain and
break test mocks of ../logger.js (per project-memory rule about new
modules using createLogger). self-healing.ts re-exports the symbol for
backward compatibility.

Log prefix for the recovery actions also changed from
'[FN-5627] Auto-recovering...' to 'Auto-recovered:' so that
NotificationService.maybeSuppressTransientFailedNotification's existing
/^Auto-recovered:/ log-prefix check cancels any already-scheduled failure
notification when the sweep runs mid-grace-window.

Tests (3 new):
- transient lease-handoff-target-not-queued failure NOT notified
- transient spurious-concurrent-advance-same-sha failure NOT notified
- genuine different-SHAs concurrent-advance still notifies (control)

Engine suite: 6166 tests pass.

Fusion-Task-Id: FN-5627
2026-05-28 15:39:23 -07:00
gsxdsm
230f6f45b3 feat(FN-5627): auto-recover from fast-path foreign-commit refusal
Instead of immediately parking the task as failed when the auto-merge
fast-path detects a non-ancestor commitSha (the symptom of a pre-FN-5627
TOCTOU poisoning), clear the poisoned mergeDetails fields and re-enqueue
for a fresh aiMergeTask attempt. The branch typically still has the work
intact; the merger just needs to redo the squash + ref-advance with the
now-fixed flow.

Recovery semantics:
- mergeRetries < MAX_AUTO_MERGE_RETRIES (3): clear poisoned fields
  (commitSha, mergedAt, landedFiles, filesChanged, insertions, deletions,
  noOpVerifiedShortCircuit, landedFilesAttributionRestricted, mergeConfirmed),
  increment mergeRetries, clear status/error, re-enqueue via
  internalEnqueueMerge. Emit new merger:fast-path-auto-recovered audit event.
- mergeRetries >= MAX_AUTO_MERGE_RETRIES: terminal park as failed (existing
  behavior), with merger:fast-path-blocked-foreign-commit audit event
  carrying budgetExhausted=true.

The recoverable path keeps the task in in-review with status=null, so
downstream consumers (dashboard banner, ntfy notifications) don't surface
a transient failure for what should be a self-healing event. The terminal
path remains FN-4538/FN-5488 compatible: status=failed at retry ceiling
on in-review is recognized by clearStaleBlockedBy fast paths so
downstream todos don't deadlock.

Tests updated and added:
- FN-5627 auto-recover test: asserts mergeRetries=1, status=null, all
  poisoned fields cleared, no moveTask('done'), no task:merged emit.
- FN-5627 budget-exhausted test: asserts status=failed with descriptive
  error, mergeRetries=3 input, no moveTask('done').

Fusion-Task-Id: FN-5627
2026-05-28 13:19:26 -07:00
gsxdsm
b2d547eae5 fix(FN-5627): close merger TOCTOU + add fast-path reachability gate
The merger persisted `mergeConfirmed: true` + `commitSha` to the task row
as soon as the local squash commit was built, BEFORE running
`git update-ref refs/heads/<integration>` to actually advance the
integration branch. If the ref-advance then failed for any reason (lock
contention, hook rejection, packed-refs race, or a misclassified non-CAS
error via the merger-ref-update-advance.ts string heuristic), the task row
was poisoned: the auto-merge scheduler's mergeConfirmed fast-path would
silently promote the never-landed work to 'done' on the next tick,
including emitting task:merged and closing the linked GitHub tracking
issue. The 'expected SHA == observed SHA' log signature on FN-5625 was a
red herring — the ref-advance had failed for non-race reasons but the
string heuristic in merger-ref-update-advance.ts classified it as
'concurrent-advance', and the downstream IntegrationBranchConcurrentAdvanceError
routed through the unsafe 'merge already confirmed' recovery path.

This silently dropped real work on at least 9 tasks across 2026-05-27/28
(FN-5596, FN-5597, FN-5599, FN-5612, FN-5613, FN-5614, FN-5616, FN-5623,
FN-5625) and likely affected older now-archived tasks for which evidence
has been pruned.

Three-layer fix:

1. merger.ts (~9752): in reuseTaskWorktreeMerge mode, persist
   `mergeConfirmed: false` initially. After advanceIntegrationBranchRef
   returns advanced=true, do a follow-up updateTask to flip the flag.
   Other merge paths (legacy in-place, verified no-op fast-paths,
   owned-commit recovery) advance the ref BEFORE the mergeDetails write
   and remain unchanged.

2. project-engine.ts (~1378): defense-in-depth reachability gate on the
   auto-merge 'merge already confirmed' fast-path. Before moveTask to
   'done', verify `git merge-base --is-ancestor <commitSha>
   refs/heads/<integration>` succeeds. On failure, clear mergeConfirmed,
   set status='failed' with descriptive error, leave task in 'in-review',
   and emit `merger:fast-path-blocked-foreign-commit` run-audit event.
   Legitimate no-op merges (no commitSha) bypass the gate; ancient tasks
   missing mergeTargetBranch also bypass to avoid false-positive parks.

3. merger-ref-update-advance.ts (~189): replace fragile string heuristic
   ('is at' / 'expected' / 'cannot lock ref' in stderr) with structured
   detection. After update-ref fails, re-read the ref: if observed ==
   expected, classify as `ref-update-refused` (no race occurred); only
   classify as `concurrent-advance` when ref actually moved. Eliminates
   the misleading 'expected X observed X' same-SHA pair.

Tests: 3 new regression tests covering all three layers. Full engine
suite: 6150 tests pass.

Fixes:
- FN-5625 (autopilot validator trigger fix lost)
- FN-5623 (`fn goals` CLI lost)
- FN-5616 (source-issue close handlers lost)
- FN-5614 (`fn update` collision retry lost)
- FN-5613 (dashboard reload banner lost)
- FN-5612 (bundled-plugin-install lost)
- FN-5599 (tablet modal width lost)
- FN-5597 (ntfy notifier priority lost)
- FN-5596 (PR tab spacing test lost)

Fusion-Task-Id: FN-5627
2026-05-28 13:02:30 -07:00
gsxdsm
da34bd06e3 feat(FN-5595): add oauth relogin banner with validity logger
This merge implements an OAuth relogin banner feature (FN-5595) that displays in the dashboard when OAuth tokens expire. The feature includes a new `OAuthReloginBanner` component with styling and tests, an OAuth validity logger in the engine for tracking token state, and corresponding API route inte

Fusion-Task-Id: FN-5595

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
Fusion-Task-Id: FN-5595
2026-05-27 21:31:12 -07:00
gsxdsm
4a99e3fba2 feat(FN-5455): merge fusion/fn-5455 2026-05-22 21:08:00 -07:00
Fusion (runfusion.ai)
025683ca60 feat(FN-5438): add manual merge blocker bypass mode for queued tasks
Adds a manual merge blocker mode (FN-5438) that prevents automatic merging and provides a bypass mechanism to resume, wired through the merger, project engine, and task workflow API routes. Includes tests across core, engine route registration, and project engine layers, plus a changeset and documen

Fusion-Task-Id: FN-5438
2026-05-21 18:29:03 -07:00
Fusion (runfusion.ai)
c60045df22 feat(FN-5223): anchor staleness and stall detectors to engine activation ti
The merge introduces an engine-activation timestamp as the staleness floor for task age calculations, replacing arbitrary wall-clock thresholds with a runtime-relative anchor. Step 1 adds settings defaults, Steps 2–4 wire the floor helper through project engine, in-process runtime, and task store hy

Fusion-Task-Id: FN-5223
2026-05-20 14:29:07 -07:00
Fusion (runfusion.ai)
ab0a253004 feat(FN-5264): abort in-flight executor/merge/triage work on soft-delete (F
Implements in-flight abort for soft-deleted tasks across all three execution lanes: executor, merger, and triage now check for `deletedAt` before proceeding and emit `task:soft-delete-in-flight-abort` audits rather than continuing work on a deleted task. The 829-line addition is dominated by integra

Fusion-Task-Id: FN-5264

Fusion-Task-Lineage: 4ee8e63b-abf0-43e2-9130-39a05434d8f9
2026-05-20 01:16:06 -07:00
Fusion (runfusion.ai)
b078a8135e feat(FN-5232): add deterministic verification-followup deduplication to pre
Implements deterministic followup deduplication for the project engine, routing eval and PR followups through a new `verification-followup-dedup` helper that excludes the parent task to prevent self-referential loops, with tests covering eval-followups, merge-error-recovery, PR comments, and the new

Fusion-Task-Id: FN-5232
2026-05-19 19:28:05 -07:00