Commit Graph

6 Commits

Author SHA1 Message Date
Phil Larson
a5c3476cb2 fix: harden pinned worktree recovery cleanup (#3402)
## Summary
- serialize pinned-path classification, orphan preservation, quarantine
reconciliation, and recreation under one reservation
- preserve cross-filesystem orphans atomically beside the configured
worktree root and retain the newest 10 generated entries per recovery
root
- exclude recovery containers from pool and self-healing scans, with
fail-closed symlink and active-session guards
- document recovery location and retention behavior

## Test plan
- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/worktree-acquisition.test.ts
src/__tests__/worktree-paths.test.ts src/__tests__/worktree-pool.test.ts
src/__tests__/self-healing-tempdir-sweep.test.ts --silent=passed-only
--reporter=dot`
- `pnpm --filter @fusion/engine typecheck`
- `pnpm --filter @fusion/engine build`
- `pnpm test:gate:static`
- `pnpm check:changesets --strict`
- `pnpm check:fnxc-future-dates`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Preserves orphaned pinned worktrees during recovery, including across
filesystems.
* Retains the 10 most recent recovery entries and safely skips active or
invalid entries.
* Keeps recovery data separate from normal worktree discovery, cleanup,
and capacity checks.
* Adds safeguards for path containment, active-session ownership, and
concurrent recovery.

* **Bug Fixes**
* Prevents pinned worktree data from being lost during recreation or
quarantine cleanup.
* Ensures recovery cleanup failures do not interrupt worktree
acquisition.

* **Documentation**
* Documented orphan recovery, retention, fallback behavior, and cleanup
safeguards.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-09 19:39:39 -10:00
Phil Larson
73f428c5bf fix: preserve orphaned pinned worktrees before recovery (#3380)
## Summary

- recover task-ID-pinned worktrees when their directory remains but Git
metadata/registration is gone
- preserve the orphan directory under `.fusion/recovery/worktrees`
before recreating the worktree
- retain existing fail-closed behavior for active, foreign, repo-root,
or out-of-root paths

## Problem

A task-pinned worktree can lose `.git` metadata while leaving build
artifacts behind. Fusion classifies that path as incomplete or
unregistered, but then calls `git worktree remove --force`. Git cannot
remove a directory it no longer recognizes as a worktree, so acquisition
aborts and the scheduler can repeat the same recovery indefinitely.

The observed reproduction left `.build` and `.swiftpm` under the pinned
path after Git registration was gone.

## Fix

For an inactive path that is both:

1. inside the configured worktree root, and
2. classified as incomplete or unregistered,

hold the shared worktree-path reservation across classification,
preservation, and recreation. Recovery directories are created one
canonical, project-contained component at a time, then the orphan is
atomically moved into `.fusion/recovery/worktrees` and the pinned
worktree is recreated. Moving rather than deleting preserves any unknown
task artifacts for operator inspection. Other classifications continue
through the existing guarded Git-removal path.

## Verification

- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/worktree-acquisition.test.ts --silent=passed-only
--reporter=dot` — 30 passed
- `pnpm --filter @fusion/engine typecheck`
- `pnpm --filter @fusion/engine build`
- `pnpm test:gate:static`
- `pnpm check:changesets`
- `git diff --check`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Bug Fixes**
- Improved recovery of task-pinned worktrees when incomplete or stale
directories occupy the expected location.
- Preserves eligible inactive or unregistered worktree contents in a
recovery area instead of deleting them.
- Prevents unsafe recovery through symbolic links and handles concurrent
recovery attempts reliably.
  - Supports recovery when directories span different storage devices.
- Ensures interrupted or invalid worktree states can be recreated safely
without disrupting active sessions.

- **Documentation**
- Added a patch changeset documenting the improved worktree recovery
behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-09 15:06:36 -10:00
gsxdsm
a06a4988d9 fix(worktree): stop terminally failing tasks over a stale worktree base
A stale base is an optimization miss, not an execution failure. FN-8693's
dispatch-time refresh refused dirty checkouts and own-commit rebase conflicts
with executionSafe:false, and the refusal threw out of acquireTaskWorktree into
execute()'s generic terminal sink — parking the task `failed` and paging the
operator. Run-audit for 2026-08-01..09: 99 of 136 execution failures were these
refusals (74 dirty-worktree, 25 stale-base-conflict), and the bounded
non-parking lane built for them fired 0 times because it only ever saw refusals
published as typed graph node values and no code node enables refreshStaleBase.

82 of the 99 landed within five minutes of "Task marked done by agent": they
were code-review-remediation re-entries into execute() on the task's own warm
worktree — exactly the checkout the refresh must leave alone. Dispatch-time
rebase has no conflict resolution, so on a busy main it could only ever fail;
the merge lane already rebases with AI arbitration before landing and
deliberately leaves refreshStaleBase off.

- refreshReusedWorktreeBase: dirty tree, own-commit conflict, unresolvable base
  and compensated persistence failures now return skipped/executionSafe — keep
  the local base and run. Only an unproven tree (failed compensation, so a
  half-rebased checkout may be on disk) still refuses.
- Check whether a mutation is needed before consulting the working tree: a
  worktree already on the current base was refused just for carrying WIP.
- executor: catch WorktreeBaseRefreshError first and route it into
  holdForWorktreeBaseRefresh, one shared non-parking lane the graph path now
  uses too, so the two entry points cannot drift.
- run-audit: worktree:base-refresh-skipped separates a declined refresh from a
  genuine block.

reset-to-base — the actual FN-8693 requirement — is preserved and tested.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 17:04:55 -07:00
Phil Larson
2286a7a378 fix: refresh dependent worktrees after local merges (#3381)
## Summary

- apply Fusion's existing stale-base reconciliation to freshly
reacquired and pooled execution worktrees
- advance retained task branches to the current local integration commit
after dependencies land
- keep planning and Worktrunk behavior unchanged while preserving
dirty/conflict fail-closed handling

## Problem

A dependent task can be planned before its dependency lands. If the
dependency merges and its branch is deleted, a later execution retry may
recreate the dependent worktree from its already-existing task branch.
That branch can still point at the pre-dependency commit.

Fusion already refreshes reused execution worktrees, but fresh
acquisition returned without calling the same reconciliation primitive.
The dependent task therefore executed without the landed dependency
output even though Fusion marked the dependency complete.

## Fix

When `refreshStaleBase` is enabled, run `refreshReusedWorktreeBase`
after a native fresh or pooled worktree is acquired and before cleanup,
init, or session execution. Track the actual backend used by injected
and fallback creators so a native fallback still refreshes while
Worktrunk-managed paths remain excluded. The existing primitive:

- resolves the current local integration branch without requiring a
remote
- resets branches with no task-owned commits
- rebases branches with task-owned commits
- blocks dirty or conflicting worktrees
- persists the integration commit as `baseCommitSha`

If refresh blocks a pooled checkout, clear the task's durable binding
before releasing the checkout for reuse.

Planning callers do not enable `refreshStaleBase`, so planning worktrees
remain unchanged.

## Verification

- `pnpm --filter @fusion/engine exec vitest run
src/__tests__/worktree-base-refresh.test.ts
src/__tests__/worktree-acquisition.test.ts --silent=passed-only
--reporter=dot` — 34 passed
- `pnpm --filter @fusion/engine typecheck`
- `pnpm --filter @fusion/engine build`
- `pnpm test:gate:static`
- `pnpm check:changesets`
- `git diff --check`


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved worktree acquisition by refreshing stale branches against the
current integration branch.
* Added refresh support for recreated, pooled, and native fallback
worktrees.
* Prevented task execution when refresh fails and safely released
affected pooled worktrees.
  * Avoided unnecessary refreshes for newly created Worktrunk worktrees.

* **Tests**
* Added coverage for stale-base refresh behavior across supported
acquisition scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-09 13:51:28 -10:00
gsxdsm
e78c09c6f4 FN-8825: move secrets fingerprints into private Git metadata
Store secrets environment fingerprint metadata in private Git directories so refresh-enabled worktrees remain clean.

- Reconcile and durably migrate legacy root fingerprint sidecars before strict worktree refreshes.
- Fail closed for malformed, tracked, conflicting, or non-durable fingerprint records and protect cleanup integrity.
- Add coverage and document the private fingerprint record contract.

Files changed: .changeset/fn-8825-secrets-env-fingerprint.md      |   7 +
 docs/secrets.md                                    |   5 +-
 .../secrets-env-materialization.test.ts            |  45 ++-
 .../src/__tests__/secrets-env-writer.test.ts       | 325 ++++++++++++++-
 .../worktree-acquisition-secrets-env.test.ts       |  61 ++-
 packages/engine/src/worktree/secrets-env-writer.ts | 434 ++++++++++++++++-----
 .../engine/src/worktree/worktree-acquisition.ts    |  22 +-
 7 files changed, 799 insertions(+), 100 deletions(-)

Fusion-Task-Id: FN-8825

Fusion-Task-Lineage: eb7148bd-1da7-4448-8abf-498ddaebd4c0

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-08-07 21:18:24 -07:00
gsxdsm
cb57093d03 refactor: domain folder layout (types, API, core, engine) (#2398)
## Summary

Wave 17 organizes Fusion into **domain folders** (stacks on #2397).

### Layout
- **core/types/** — board, task, agents, settings, merge, workflow,
mesh, …
- **core/src/** — agents, ai, async-stores, workflows, tasks, config,
db, …
- **dashboard/app/api/** — client, tasks, agents, git, missions,
planning, …
- **engine/src/** — agents, auth, execution, merge, missions, overseer,
worktree, …

Root keepers retained for large entrypoints (`store.ts`, `executor.ts`,
`merger.ts`, …).

Public barrels (`@fusion/core`, `@fusion/engine`, `app/api.ts` → legacy)
stay stable.

## Test plan
- [x] `@fusion/core` typecheck
- [x] `@fusion/engine` typecheck (pre-existing playwright-core noise
only)
- [ ] CI merge gate

**Stack:** #2394 → #2397 → **this PR**
2026-08-03 00:20:53 -07:00