bb11e493f7c48dcafaa63ac29a5c0274b7e030b2
901 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1cf86baa1c |
refactor: package code organization wave 18 (executor pure peels) (#3317)
## Summary
Wave 18 continues the package code-organization program after wave 17
domain folders (U4 Slice A from
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`).
### What changed
Peel **pure, behavior-preserving** helpers out of
`packages/engine/src/executor.ts` into domain modules under
`packages/engine/src/executor/`, with **stable re-exports** from
`executor.ts` so deep imports and `vi.mock("../executor.js")` keep
working.
| New module | Symbols |
|------------|---------|
| `executor/task-done-refusal.ts` | `evaluateTaskDoneRefusal`,
`determineRevisionResetStart`, skip-bypass refusal helper |
| `executor/workflow-feedback-paths.ts` |
`extractReferencedPathsFromWorkflowFeedback`,
`isAlwaysAllowedScopeLeakPath`, `workflowPathMatchesDeclaredScope` |
| `executor/workflow-step-verdict.ts` |
`FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE`, `parseWorkflowStepVerdict`
/ `parseWorkflowStepOutput`, step outcome types |
| `executor/await-input-parse.ts` | `parseAwaitInputSentinel`,
`parseAwaitInputQuestionToolCall` |
| `executor/no-commit-eligibility.ts` | `getNoCommitEligibilityReason`
(+ prompt heuristics) |
`executor.ts` live LOC ~**22817 → ~22427** (first pure-peel batch; more
peels needed to approach the 2k cap).
### Shims
- `old path` `executor.ts` public exports → `new path` `executor/*.ts` →
delete-when consumer deep-imports are re-pointed (not this PR)
### Test plan
- [x] `@fusion/engine` typecheck
- [x] Oracle: task-done refusal, skip-bypass, workflow malformed
verdict, scope-leak allowlist, executor-step-session, executor-prompt
- [x] `vitest --project=engine-core` (merge-gate curated suite)
- [ ] CI merge gate
**Stack:** wave17 (merged) → **this PR**
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Improved recognition of workflow outcomes from structured and
conversational responses.
* Added support for extracting questions from await-input responses and
tool calls.
* Improved workflow feedback handling for referenced files and declared
scope patterns.
* Added clearer guidance for task execution, approvals, verification,
and available tools.
* **Bug Fixes**
* Prevented completion when required review approvals are missing or
revisions remain pending.
* Improved handling of workflows that legitimately require no code
changes.
* Added clearer refusal messages and more reliable revision restarts.
* Sanitized repository paths in Git remediation instructions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
|
||
|
|
b28b6d1053 |
fix: fail closed on incomplete external checkout routes (#3401)
## Summary - fail closed when a persisted external remediation route lacks a concrete checkout path - verify recovery, remediation, dependency-abort cleanup, and completion validation use the live persisted task route - use unique missing-checkout fixtures and exact observed-path assertions ## Test plan - `pnpm --filter @fusion/engine exec vitest run src/__tests__/verify-worktree-invariants-missing.test.ts src/__tests__/executor-triage-column-audit.test.ts --silent=passed-only --reporter=dot` - `pnpm --filter @fusion/engine exec vitest run src/__tests__/executor-fast-mode-workflows.test.ts -t 'completed-task recovery captures the live external checkout|pre-merge remediation' --silent=passed-only --reporter=dot` - `pnpm --filter @fusion/engine typecheck` - `pnpm exec eslint packages/engine/src/executor.ts packages/engine/src/__tests__/verify-worktree-invariants-missing.test.ts packages/engine/src/__tests__/executor-triage-column-audit.test.ts packages/engine/src/__tests__/executor-fast-mode-workflows.test.ts` - `pnpm check:fnxc-future-dates` - `pnpm check:changesets --strict` - `git diff --check` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved recovery for externally executed tasks by using the latest routing information instead of outdated task data. * External remediation now stops safely when a checkout location is missing or invalid, preventing execution in an unintended location. * Improved cleanup behavior to preserve operator-owned checkouts. * Enhanced validation and error reporting for missing or invalid checkout paths. * **Tests** * Expanded coverage for recovery, remediation safety, checkout ownership, and worktree validation scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
e573178e31 |
fix(agents): never let a built-in workflow role agent be unroutable
The board stopped moving. Work items churned held -> running -> held at ~3.5/sec across every task, pinning a core and writing ~19k workflowWorkItem audit rows/hour while nothing executed. Hold reason: workflow-principal-role-pool-exhausted:executor. provisionBuiltinWorkflowRoleAgents seeded the four permanent owners (triage, executor, reviewer, merger) with runtimeConfig.enabled=false, while the router's available() treats enabled===false as unavailable. The only permanent principals for every built-in role were unroutable BY CONSTRUCTION — shipped that way, so any instance without operator-created role agents deadlocks at its first workflow node. Nothing self-recovers: a pool only changes by operator action. Routability of these four is an invariant, not a setting. Unlike an operator's agent, disabling one does not opt an agent out — it removes the only thing that can run that stage, and there is no fallback. - seed built-ins enabled; converge existing rows on provisioning - enforceBuiltinWorkflowRoleRoutability coerces enabled back at the durable writeAgent seam, so no REST/UI/plugin/restore path can reintroduce the deadlock. Other runtimeConfig keys are preserved; operator-owned agents keep their off switch - share the static routability predicate (isWorkflowPrincipalEligible) between provisioning and the router so the two cannot drift apart again Also fix the spin itself: a principal hold had no cooldown, so the scheduler re-dispatched instantly and the run re-entered only to re-fence and re-park. It now records a backoff ladder (15s -> 5m) checked before graph entry, and logs once per distinct reason instead of every pass — the same self-recovering shape as holdForSessionContention. The hold never increments `attempt`, so no existing guard could ever fire. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
26ea9fd40a |
fix: preserve external checkout routing through recovery (#3400)
## Summary - keep persisted operator-routed external checkouts authoritative during executor recovery, remediation, verification, and cleanup - fail closed when a configured external route is invalid instead of falling back to a Fusion-managed worktree - prevent Fusion from cleaning up operator-owned external checkouts - add dashboard and executor regression coverage for the routing handoffs ## Test plan - `pnpm --filter @fusion/engine exec vitest run src/__tests__/verify-worktree-invariants-missing.test.ts` - `pnpm --filter @fusion/engine exec vitest run src/__tests__/external-execution-checkout.test.ts src/__tests__/executor-triage-column-audit.test.ts` - `pnpm --filter @fusion/engine exec vitest run src/__tests__/executor-fast-mode-workflows.test.ts -t 'external execution|authoritative executor route|completed-task recovery captures the live external|pre-merge remediation reuses the live external'` - `FUSION_DASHBOARD_DEEP=1 pnpm --filter @fusion/dashboard exec vitest run src/__tests__/routes-tasks-near-duplicate.test.ts -t 'PATCH external-checkout persists one clean Git checkout for execution and review'` - `pnpm --filter @fusion/engine typecheck` - `pnpm --filter @fusion/dashboard typecheck` - `pnpm test:gate:static` - `pnpm check:changesets --strict` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Bug Fixes** - Improved external checkout routing across execution, verification, recovery, retries, and remediation. - Operations now use the latest persisted checkout details, preventing stale routing information from directing work to the wrong location. - Invalid or missing checkout routes fail safely with clear verification errors. - External checkouts are protected from unintended managed worktree or branch cleanup. - **Documentation** - Clarified external checkout routing and validation behavior. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
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> |
||
|
|
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 --> |
||
|
|
477f3faf0a |
feat: support operator-routed external task checkouts (#3398)
## Summary - add an explicit API route that persists one clean external Git checkout for task execution and enforced review - fence execution to the checkout's persisted branch and fail closed when the route becomes invalid - allow completion invariants to validate explicitly routed checkouts outside the project worktree directory ## Test plan - `pnpm --filter @fusion/engine exec vitest run src/__tests__/external-execution-checkout.test.ts src/__tests__/review-checkout.test.ts src/__tests__/engine-no-blocking-shellout.test.ts --silent=passed-only --reporter=dot` - `pnpm --filter @fusion/engine exec vitest run src/__tests__/executor-fast-mode-workflows.test.ts -t 'prepares a persisted external execution checkout' --silent=passed-only --reporter=dot` - `FUSION_DASHBOARD_DEEP=1 pnpm --filter @fusion/dashboard exec vitest run src/__tests__/routes-tasks-near-duplicate.test.ts -t 'PATCH external-checkout' --project dashboard-api --silent=passed-only --reporter=dot` - `pnpm --filter @fusion/engine typecheck` - `pnpm --filter @fusion/dashboard exec tsc --noEmit` - `pnpm verify:fast` - `pnpm test:gate` (605 non-PostgreSQL tests pass; local PostgreSQL suites cannot authenticate because the configured client returns an empty password) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for routing task execution and review through operator-selected external Git checkouts. * External checkouts are validated for valid Git repositories, attached branches, clean status, and branch consistency. * Tasks can clear previously configured external checkout routing. * Valid routed checkouts are used directly without creating a separate worktree. * **Bug Fixes** * Invalid, incomplete, dirty, or mismatched checkout configurations now fail early with clear validation errors. * Missing tasks return the appropriate not-found response. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: gsxdsm <gsxdsm@users.noreply.github.com> |
||
|
|
3aa32a846c |
FN-8910: allow held tasks to remediate review findings
Allow pre-merge remediation to proceed under project-level merge holds while preserving task-level operator holds. - Restrict remediation holds to operator-authored task-level auto-merge settings - Record remediation and revision-budget refusals for parked review tasks - Keep fire-and-forget remediation failures in their review lane - Cover shared-branch recovery behavior and document the policy Files changed: .changeset/fn-8910-premerge-remediation-hold.md | 7 ++ docs/workflow-steps.md | 2 + packages/core/src/__tests__/task-merge.test.ts | 44 ++++++------- packages/core/src/merge/task-merge.ts | 20 +++--- .../__tests__/executor-graph-requeue-gate.test.ts | 60 ++++++++++++++++- ...cutor-live-branch-group-auto-merge-hold.test.ts | 63 ++++++++++++++++-- .../workflow-graph-optional-step-fix.test.ts | 24 +++++-- .../src/__tests__/workflow-task-runtime.test.ts | 10 ++- packages/engine/src/executor.ts | 76 ++++++++++++++++++---- 9 files changed, 244 insertions(+), 62 deletions(-) Fusion-Task-Id: FN-8910 Fusion-Task-Lineage: b5e10f87-67cf-4acf-b125-91be5ade17a8 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
6bd178bdcf |
FN-8864: add durable agent activity stream
Add a persisted, project-scoped agent activity feed with query and live delivery surfaces. - Store sequenced, attributed activity events with privacy-safe metadata and retention. - Emit activity across agents, workflow execution, reviews, approvals, merges, and recovery. - Provide paginated API history and resilient SSE tailing with coverage and documentation. - Renumber the activity migration to 0049 after reconciling main’s 0048 GitHub check-state migration. Files changed: .changeset/fn-8864-agent-activity-events.md | 7 + docs/architecture.md | 8 + docs/diagnostics.md | 4 + docs/storage.md | 1 + .../__tests__/agent-activity-attribution.test.ts | 21 + .../agent-activity-metadata-hygiene.test.ts | 60 +++ .../src/__tests__/agent-activity-writers.test.ts | 94 +++++ .../postgres/agent-activity-events.pg.test.ts | 57 +++ .../src/__tests__/postgres/schema-applier.test.ts | 34 +- packages/core/src/agents/agent-store.ts | 24 ++ packages/core/src/agents/approval-request-store.ts | 15 +- packages/core/src/index.ts | 4 + .../0049_fn_8864_agent_activity_events.sql | 24 ++ packages/core/src/postgres/schema-applier.ts | 15 +- packages/core/src/postgres/schema/project.ts | 19 +- packages/core/src/store.ts | 17 + .../core/src/task-store/agent-activity-outbox.ts | 75 ++++ .../src/task-store/async/async-agent-activity.ts | 71 ++++ packages/core/src/types.ts | 2 + packages/core/src/types/agents/agents.ts | 79 ++++ packages/dashboard/app/api.ts | 54 +++ .../src/__tests__/agent-activity-route.test.ts | 68 ++++ .../src/__tests__/sse-agent-activity.test.ts | 315 +++++++++++++++ packages/dashboard/src/routes/README.md | 2 +- .../src/routes/register-setup-activity-routes.ts | 19 +- packages/dashboard/src/sse.ts | 139 ++++++- .../src/__tests__/agent-activity-writers.test.ts | 442 +++++++++++++++++++++ packages/engine/src/executor.ts | 110 ++++- packages/engine/src/merger.ts | 15 +- packages/engine/src/self-healing.ts | 35 +- 30 files changed, 1809 insertions(+), 21 deletions(-) Fusion-Task-Id: FN-8864 Fusion-Task-Lineage: 4938f35b-a0bc-4eb3-905c-178fed859cc6 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
29bb6d0dc2 |
FN-8868: restore durable agent activity telemetry
Restore Activity telemetry for durable agent sessions. - Emit session-start and usage events across durable agent lanes. - Count durable agent sessions and user messages in Activity analytics. - Cover lifecycle wiring and telemetry persistence with tests. Files changed: .../fn-8868-durable-agent-activity-telemetry.md | 7 + docs/dashboard-guide.md | 8 +- .../message-store-user-message-telemetry.test.ts | 73 ++++++++++ ...mmand-center-activity-durable-agents.pg.test.ts | 158 +++++++++++++++++++++ packages/core/src/board/activity-analytics.ts | 12 +- packages/core/src/stores/message-store.ts | 16 +++ packages/core/src/task-store/async/async-events.ts | 11 ++ .../dashboard/src/__tests__/chat-manager.test.ts | 88 ++++++++++++ packages/dashboard/src/chat.ts | 17 +++ .../__tests__/agent-usage-telemetry-lanes.test.ts | 84 +++++++++++ .../__tests__/agent-usage-telemetry-wiring.test.ts | 119 ++++++++++++++++ .../src/__tests__/agent-usage-telemetry.test.ts | 33 +++++ .../engine/src/__tests__/executor-prompt.test.ts | 8 ++ packages/engine/src/__tests__/merger-ai.test.ts | 11 ++ .../src/__tests__/merger-merge-lifecycle.test.ts | 21 ++- packages/engine/src/__tests__/reviewer.test.ts | 58 +++++++- .../src/__tests__/step-session-executor.test.ts | 37 ++++- packages/engine/src/__tests__/triage.test.ts | 31 ++++ packages/engine/src/agent-heartbeat.ts | 43 ++++++ packages/engine/src/agents/agent-logger.ts | 30 +++- .../engine/src/agents/agent-usage-telemetry.ts | 30 ++++ packages/engine/src/execution/reviewer.ts | 41 +++++- .../engine/src/execution/step-session-executor.ts | 12 ++ packages/engine/src/executor.ts | 27 +++- packages/engine/src/merge/merger-ai.ts | 7 + packages/engine/src/merger.ts | 120 +++++++++++++++- packages/engine/src/triage.ts | 5 + 27 files changed, 1085 insertions(+), 22 deletions(-) Fusion-Task-Id: FN-8868 Fusion-Task-Lineage: 0aa2ee4e-4d40-4adc-a510-bf8f4b1c0233 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
9c43736794 |
FN-8870: add structural approval and report mail
Add typed report and approval metadata to the mailbox contract. - Define and validate structural mail kinds, report sections, and approval references. - Let agents send validated reports while reserving approval mail for engine emission. - Emit idempotent, fail-soft approval notifications and document the contract. Files changed: .changeset/fn-8870-structural-mail-contract.md | 7 ++ docs/agents.md | 2 + docs/architecture.md | 4 + .../message-metadata-structural-mail.test.ts | 25 ++++ packages/core/src/index.gate.ts | 2 +- packages/core/src/index.ts | 2 +- packages/core/src/types.ts | 30 +++++ packages/core/src/types/messaging/messages.ts | 21 ++++ ...gent-tools-send-message-structural-mail.test.ts | 34 +++++ .../src/__tests__/approval-mail-emission.test.ts | 137 +++++++++++++++++++++ packages/engine/src/agent-heartbeat.ts | 3 + packages/engine/src/agent-tools.ts | 28 ++++- packages/engine/src/agents/approval-mail.ts | 40 ++++++ packages/engine/src/executor.ts | 3 + 14 files changed, 334 insertions(+), 4 deletions(-) Fusion-Task-Id: FN-8870 Fusion-Task-Lineage: 7d30b2f9-c004-4975-86b1-9600e2c8d76f Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
0a2fe913c6 |
FN-8863: fix standalone pre-merge remediation holds
Allow standalone tasks to recover from failed pre-merge steps when project auto-merge is disabled. - Add a remediation-specific auto-merge hold for shared members and explicit user holds. - Restore Plan Review replans and Code Review fix handoffs for standalone tasks. - Cover hold behavior and document the operator-consent policy. Files changed: .changeset/fn-8863-remediation-auto-merge-hold.md | 7 ++ docs/dashboard-guide.md | 2 +- packages/core/src/__tests__/task-merge.test.ts | 38 ++++++++ packages/core/src/index.gate.ts | 1 + packages/core/src/index.ts | 1 + packages/core/src/merge/task-merge.ts | 18 ++++ ...cutor-live-branch-group-auto-merge-hold.test.ts | 27 ++++++ .../workflow-graph-optional-step-fix.test.ts | 106 +++++++++++++++++++++ packages/engine/src/executor.ts | 16 +++- 9 files changed, 212 insertions(+), 4 deletions(-) Fusion-Task-Id: FN-8863 Fusion-Task-Lineage: 930bf37a-3173-4a3b-84ca-575f7f5d94b9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
75796ebe6f |
FN-8850: capture task completion recommendations
Capture bounded, task-ready executor follow-ups at accepted completion. - Guide executor prompts to submit recommendations or an explicit empty list at completion. - Enforce default and disabled recommendation caps in completion handling. - Document recommendation behavior and cover prompt and validation contracts. Files changed: .../fn-8850-populate-task-recommendations.md | 7 +++ docs/dashboard-guide.md | 2 +- packages/core/src/agents/agent-prompts.ts | 16 ++++-- .../__tests__/ephemeral-task-create-gate.test.ts | 3 +- .../engine/src/__tests__/executor-prompt.test.ts | 67 ++++++++++++++++++++++ .../executor-task-recommendations.test.ts | 39 ++++++++++++- packages/engine/src/executor.ts | 44 ++++++++++++-- 7 files changed, 162 insertions(+), 16 deletions(-) Fusion-Task-Id: FN-8850 Fusion-Task-Lineage: 55a99d05-33fa-464c-869c-18a9cf7e495a Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
7611ce7baa |
FN-8841: route Plan Review no-op verdicts to terminal close
Allow validated duplicate and no-op Plan Review verdicts to complete work without implementation dispatch. - Add terminal no-op routes to built-in coding workflows and validate reviewer evidence. - Persist close verdicts, safely hold failed terminalizations, and guard pause races. - Document the verdict contract and cover route, validation, and completion behavior. - Add a patch changeset for the published Fusion package. Files changed: .changeset/fn-8841-plan-review-no-op.md | 7 + docs/workflow-steps.md | 14 + .../core/src/__tests__/builtin-workflows.test.ts | 24 ++ packages/core/src/types/task/task-review.ts | 7 +- packages/core/src/types/workflow/workflow-steps.ts | 2 +- .../src/workflows/builtin-coding-workflow-ir.ts | 3 + .../src/workflows/builtin-plan-review-group.ts | 3 +- .../builtin-stepwise-coding-workflow-ir.ts | 3 + packages/core/src/workflows/builtin-workflows.ts | 5 + .../engine/src/__tests__/plan-review-no-op.test.ts | 289 +++++++++++++++++++ .../workflow-step-verdict-parsing.test.ts | 18 ++ packages/engine/src/executor.ts | 318 +++++++++++++++++---- .../src/workflows/workflow-graph-executor.ts | 103 ++++++- .../src/workflows/workflow-graph-task-runner.ts | 6 + 14 files changed, 741 insertions(+), 61 deletions(-) Fusion-Task-Id: FN-8841 Fusion-Task-Lineage: bde2e001-9cd0-496e-8367-36540c9ac31d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
f36e23848f |
FN-8840: recognize duplicate redirects in task titles
Recognize exact duplicate redirects in either task title or PROMPT.md while preserving conflicting decisions for operator resolution. - Resolve duplicate markers consistently across intake, scheduling, execution, dashboard, and replication paths. - Accept task-ID prefixes beyond FN- and expose duplicate-marker state to the UI. - Fail closed during stale-decision recovery when prompt, title, or persisted canonical redirects disagree. - Add regression coverage and operator documentation for title-based redirects. Files changed: .changeset/fn-8840-duplicate-title-redirect.md | 7 ++ docs/settings-reference.md | 2 +- docs/task-management.md | 11 +- .../__tests__/explicit-duplicate-marker.test.ts | 30 ++++- .../src/__tests__/mesh-task-replication.test.ts | 6 + .../src/duplicates/explicit-duplicate-marker.ts | 66 ++++++++--- packages/core/src/index.gate.ts | 3 + packages/core/src/index.ts | 3 + packages/core/src/mesh/mesh-task-replication.ts | 2 +- ...-task-workflow-routes.awaiting-planning.test.ts | 78 +++++++++++-- .../src/routes/register-task-workflow-routes.ts | 26 ++++- .../executor-explicit-duplicate-recovery.test.ts | 75 ++++++++++++ .../__tests__/merged-intake-hold-column.test.ts | 19 +++ .../scheduler-explicit-duplicate-marker.test.ts | 130 +++++++++++++++++++++ .../self-healing-stale-duplicate-decision.test.ts | 67 +++++++++++ .../triage-explicit-duplicate-marker.test.ts | 51 ++++++++ packages/engine/src/execution/hold-release.ts | 10 +- packages/engine/src/executor.ts | 11 +- packages/engine/src/scheduler.ts | 15 ++- packages/engine/src/self-healing.ts | 55 ++++++--- packages/engine/src/triage.ts | 98 +++++++++++++--- 21 files changed, 687 insertions(+), 78 deletions(-) Fusion-Task-Id: FN-8840 Fusion-Task-Lineage: d08a3e84-1851-4fba-bfa9-507116ad6219 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
e2522ebbb0 |
FN-8839: rebase fresh worktrees onto integration branch
Refresh newly created worktrees against the configured integration branch without relying on ambient root HEAD. - Resolve rebase targets through the canonical integration-branch resolver - Log skipped refreshes, fetch failures, and successful or conflicted rebases without blocking setup - Cover configured, remote-default, fallback, and failure rebase behavior Files changed: .changeset/fn-8839-worktree-integration-rebase.md | 7 + .../engine/src/__tests__/executor-worktree.test.ts | 167 +++++++++++++++++++++ packages/engine/src/executor.ts | 91 ++++++----- 3 files changed, 226 insertions(+), 39 deletions(-) Fusion-Task-Id: FN-8839 Fusion-Task-Lineage: e104a261-39b2-4ab1-aaf5-075764162b4b Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
d450dbe971 |
FN-8829: add task recommendations
Add persistent task recommendations that agents can create, resolve, and display in task details. - Persist recommendation state and expose task recommendation API routes. - Generate recommendations from executor task completions with duplicate suppression. - Add localized dashboard recommendation tab and settings control with coverage. Files changed: .changeset/fn-8829-recommendations.md | 7 + docs/dashboard-guide.md | 1 + docs/settings-reference.md | 1 + .../postgres/settings-persistence.pg.test.ts | 10 + .../postgres/task-recommendations.pg.test.ts | 191 +++++++++ .../core/src/__tests__/settings-parity.test.ts | 2 + packages/core/src/config/settings-schema.ts | 2 + packages/core/src/index.ts | 2 +- .../0047_fn_8829_task_recommendations.sql | 3 + packages/core/src/postgres/schema-applier.ts | 32 +- packages/core/src/postgres/schema/project.ts | 2 + packages/core/src/store.ts | 12 +- packages/core/src/task-store/persistence.ts | 4 +- packages/core/src/task-store/serialization.ts | 1 + packages/core/src/task-store/settings-ops.ts | 22 + packages/core/src/task-store/task-mutation-ops.ts | 78 +++- packages/core/src/task-store/task-row-mappers.ts | 2 +- packages/core/src/task-store/task-update.ts | 51 ++- packages/core/src/types.ts | 4 + packages/core/src/types/settings/settings-scope.ts | 6 + packages/core/src/types/task/task-core.ts | 18 + .../__tests__/App.openTasksInRightSidebar.test.ts | 3 +- packages/dashboard/app/__tests__/api-tasks.test.ts | 31 ++ packages/dashboard/app/api/legacy.ts | 1 + packages/dashboard/app/api/tasks/tasks.ts | 24 ++ .../dashboard/app/components/TaskDetailModal.tsx | 43 +- .../app/components/TaskRecommendationsTab.css | 71 ++++ .../app/components/TaskRecommendationsTab.tsx | 127 ++++++ .../__tests__/SettingsModal.general.test.tsx | 11 + .../TaskDetailModal.recommendations.test.tsx | 111 +++++ .../app/components/settings/section-keys.ts | 1 + .../settings/sections/GeneralSection.tsx | 14 + .../settings-default-descriptions.test.tsx | 1 + packages/dashboard/app/hooks/useModalManager.ts | 6 + packages/dashboard/app/plugins/types.ts | 7 +- .../__tests__/task-recommendation-routes.test.ts | 472 +++++++++++++++++++++ .../src/routes/register-task-workflow-routes.ts | 263 +++++++++++- .../executor-task-recommendations.test.ts | 128 ++++++ packages/engine/src/executor.ts | 66 ++- packages/i18n/locales/en/app.json | 15 +- packages/i18n/locales/es/app.json | 16 +- packages/i18n/locales/fr/app.json | 16 +- packages/i18n/locales/ko/app.json | 16 +- packages/i18n/locales/zh-CN/app.json | 16 +- packages/i18n/locales/zh-TW/app.json | 16 +- packages/i18n/src/resources.d.ts | 12 + 46 files changed, 1908 insertions(+), 30 deletions(-) Fusion-Task-Id: FN-8829 Fusion-Task-Lineage: 5f60a1fb-9cf8-4577-9bfd-c20a2d402333 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
3dd824d04e |
FN-8823: respect shared member auto-merge holds
Honor project and member auto-merge consent consistently throughout shared-branch integration. - Apply project autoMerge settings and explicit member overrides to shared-member hold decisions. - Expose shared-member integration hold state and controls in branch-group dashboard and lifecycle APIs. - Add regression coverage, operator documentation, and a patch changeset. Files changed: .changeset/fn-8823-shared-member-consent.md | 7 ++ docs/dashboard-guide.md | 4 +- packages/core/src/__tests__/task-merge.test.ts | 57 +++++++++++++++ packages/core/src/index.gate.ts | 3 + packages/core/src/index.ts | 3 + packages/core/src/merge/task-merge.ts | 81 ++++++++++++++++++--- .../dashboard/app/api/tasks/tasks-lifecycle.ts | 11 +++ .../dashboard/app/components/BranchGroupCard.css | 84 ++++++++++++++++++++++ .../dashboard/app/components/BranchGroupCard.tsx | 58 ++++++++++++++- packages/dashboard/app/components/ListView.tsx | 14 +++- .../dashboard/app/components/TaskDetailModal.tsx | 20 +++++- .../components/__tests__/BranchGroupCard.test.tsx | 44 +++++++++++- .../components/__tests__/TaskDetailModal.test.tsx | 24 ++++++- .../app/components/dashboard/MainContent.tsx | 2 +- .../app/components/useRightDockController.tsx | 2 +- packages/dashboard/app/hooks/useModalManager.ts | 6 ++ .../src/__tests__/routes-branch-groups.test.ts | 42 ++++++----- .../src/routes/register-branch-groups-routes.ts | 9 ++- ...cutor-live-branch-group-auto-merge-hold.test.ts | 21 +++--- .../src/__tests__/group-merge-coordinator.test.ts | 7 +- .../workflow-graph-executor-handlers.test.ts | 33 ++++++--- packages/engine/src/executor.ts | 27 ++++--- packages/engine/src/project-engine.ts | 9 ++- packages/engine/src/self-healing.ts | 16 ++--- .../src/workflow-node-runners/merge-runner.ts | 13 ++-- 25 files changed, 503 insertions(+), 94 deletions(-) Fusion-Task-Id: FN-8823 Fusion-Task-Lineage: 19a8ed3f-26e1-4c8e-8782-ca366718a3f2 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
0cce560e4a |
fix: stop three bookkeeping faults from terminalizing or mis-scoping a run
Found by a subsystem audit of FN-8764's work-item and role-routing design, prompted by three production deadlocks already fixed in it. 1. executor.ts — closing out the continuation could skip handleGraphFailure. The two transitions that close a run's continuation sat outside the interpreter try/catch with no handler, unlike their siblings in the same function. The row is usually ALREADY terminal by then: the run's first fence write retires the continuation it resumed on, which is what makes the handover atomic. So `succeeded -> failed` hit the store's terminal guard and threw, escaping executeWorkflowGraph and skipping handleGraphFailure — a failed run's card was left sitting in its wip column, unparked, with no error recorded. Closing the continuation is bookkeeping and must never pre-empt the lifecycle action. 2. executor.ts — capacity attemptId dropped its run-id fallback. `resolvedRunId` is optional by construction (a definition load failure leaves it undefined) and this interpolated it raw, producing the literal attempt id `undefined:<nodeInstance>` shared by every task in the project that hit that failure. The lease is keyed on (projectId, attemptId) and returns "acquired" for a pre-existing row regardless of agent, so colliding tasks bypass both the project and per-agent caps and one task's release deletes another's live lease. The two durable writes on either side already used the fallback. 3. workflow-task-runtime.ts — failWorkItem dropped a promise bare. The write is deliberately fire-and-forget, but an unhandled rejection (most likely the terminal guard when a peer already closed the row) crossed into process-level unhandled-rejection territory while the caller had already returned "failed" as if it were persisted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
560256bb73 |
fix: resume the graph at a top-level node, not a foreach template node
A principal fence written for a node inside a foreach template stores the
TEMPLATE node id (step-execute) with the materialized instance in
nodeInstanceId (steps#0:step-execute). The template node lives under the
foreach's config.template and is never in ir.nodes, so handing it to the
interpreter as a start node resolved to nothing and threw WorkflowIrError.
executeWorkflowGraph's catch turned that into a terminal graph failure, so a
healthy card was parked on every dispatch:
[workflow-graph] FN-8825 could not resolve workflow — parking task instead of
legacy fallback: interpreter-error: Workflow IR missing start node
Latent since FN-8764 introduced these fences, and reachable only once a
step-execute fence could become the task's sole active continuation — which the
atomic-handover change in
|
||
|
|
dd40691ca2 |
fix: make workflow continuation writes atomic, not conflict-recovery
Code review of
|
||
|
|
cdf81a2244 |
fix(FN-8826): retry partial workflow progress after restart
Fusion-Task-Id: FN-8826 |
||
|
|
ef8828f145 |
fix: unblock workflow execution stalled by silent role-routing deadlocks
Every task sat in progress with no session, no log, and no error after the FN-8764 role-agent rollout. Two independent deadlocks, both invisible: 1. The in-process runtime built its AgentStore but never passed it into TaskExecutorOptions, so the executor's fail-closed role-routing gate refused every classified node (execute/step-execute/review/merge). 2. A resumed run keeps the continuation work item it woke on active until the interpreter returns, so the next node's principal-fence upsert violated idx_workflow_work_items_one_active_task_continuation — a different index than its ON CONFLICT target — and raised. The run re-suspended on every dispatch; only an operator bouncing the card to the hold column cleared it. Both refusals were swallowed as recoverable "principal holds" that write no log, audit row, or task error, which is why a fully deadlocked board looked idle. - Wire agentStore into the executor; assert the shared instance at every runtime seam in the PG composition test. - Supersede an active work item for a node the run has already left, then retry the fence write once; never touch a claim on the node currently executing. - Record task:workflow-run-suspended and task:workflow-continuation-superseded; log principal holds, routing-unavailable faults, and fence-write errors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
eaadd153b1 |
FN-8764: route workflow stages through durable role agents
Route workflow stages through task-scoped durable role agents. - Persist normalized multi-role agents and workflow principal fences with migrations. - Route planning, execution, review, and merge workflow nodes through authorized permanent principals with capacity leasing and recovery. - Retire ephemeral workflow-stage workers and expose role-aware agent configuration, workflow editing, and documentation. - Preserve lifecycle-column ratchet coverage by centralizing workflow-role classification rather than adding test exemptions. Files changed: .changeset/fn-8764-workflow-role-agents.md | 7 + CONCEPTS.md | 3 + docs/agents.md | 6 + docs/architecture.md | 6 + docs/cli-reference.md | 2 + docs/dashboard-guide.md | 4 + docs/settings-reference.md | 6 +- docs/storage.md | 2 + docs/workflow-steps.md | 6 + .../src/__tests__/extension-agent-update.test.ts | 11 +- packages/cli/src/__tests__/extension.test.ts | 18 +- packages/cli/src/extension.ts | 41 +- .../core/src/__tests__/agent-permissions.test.ts | 12 + .../core/src/__tests__/agent-role-policy.test.ts | 7 + packages/core/src/__tests__/agent-roles.test.ts | 21 + .../legacy-column-collection-gating-ledger.test.ts | 19 +- .../src/__tests__/postgres/schema-applier.test.ts | 16 +- .../core/src/__tests__/settings-parity.test.ts | 9 +- .../workflow-agent-node-classification.test.ts | 25 + .../src/__tests__/workflow-work-item-cas.test.ts | 38 ++ packages/core/src/agents/agent-permissions.ts | 11 +- packages/core/src/agents/agent-role-policy.ts | 39 +- packages/core/src/agents/agent-store.ts | 190 ++++++- .../core/src/async-stores/async-agent-store.ts | 6 + packages/core/src/config/settings-schema.ts | 5 +- packages/core/src/index.gate.ts | 2 +- packages/core/src/index.ts | 7 +- .../0045_fn_8764_multi_role_workflow_agents.sql | 20 + .../0046_fn_8764_workflow_principal_fence.sql | 49 ++ packages/core/src/postgres/schema-applier.ts | 22 +- packages/core/src/postgres/schema/project.ts | 21 + packages/core/src/store.ts | 2 +- .../task-store/async/async-workflow-workitems.ts | 49 +- packages/core/src/task-store/row-types.ts | 4 + packages/core/src/task-store/settings-helpers.ts | 16 +- packages/core/src/task-store/settings-ops-2.ts | 13 +- packages/core/src/task-store/settings-ops.ts | 16 +- packages/core/src/task-store/task-row-mappers.ts | 4 + .../src/task-store/workflow-task-create-ops.ts | 6 +- .../src/task-store/workflow-workitems-ops-2.ts | 25 +- packages/core/src/types.ts | 2 + packages/core/src/types/agents/agents.ts | 45 +- packages/core/src/types/merge/merge-queue.ts | 17 + packages/core/src/types/settings/settings-scope.ts | 9 +- packages/core/src/workflows/workflow-ir-types.ts | 58 +++ packages/core/src/workflows/workflow-ir.ts | 19 + .../dashboard/app/components/AgentDetailView.css | 14 + .../dashboard/app/components/AgentDetailView.tsx | 34 +- .../dashboard/app/components/NewAgentDialog.tsx | 28 +- .../app/components/WorkflowNodeEditor.tsx | 19 + .../__tests__/AgentDetailView.core.test.tsx | 4 +- .../app/components/__tests__/AgentsView.test.tsx | 2 +- .../__tests__/SettingsModal.general.test.tsx | 86 --- .../__tests__/SettingsModal.test-harness.tsx | 1 - .../components/agent-presets/agentCreatePayload.ts | 9 +- .../app/components/settings/section-keys.ts | 1 - .../settings/sections/GeneralSection.tsx | 8 - .../settings-default-descriptions.test.tsx | 1 - .../app/components/workflow-flow-mapping.ts | 7 + packages/dashboard/src/mission-routes.ts | 26 +- .../src/routes/__tests__/agent-core-routes.test.ts | 23 +- .../src/routes/register-agent-core-routes.ts | 42 +- ...gister-agent-import-export-generation-routes.ts | 21 - .../engine/src/__tests__/agent-action-gate.test.ts | 33 ++ .../engine/src/__tests__/agent-assignment.test.ts | 370 ------------- .../src/__tests__/ephemeral-worker-manager.test.ts | 575 --------------------- ...ecutor-ephemeral-disabled-dispatch-gate.test.ts | 223 -------- .../__tests__/executor-fast-mode-workflows.test.ts | 58 +++ .../engine/src/__tests__/log-severity-manifest.ts | 1 - .../__tests__/log-severity-spam-contract.test.ts | 3 - .../resolved-read-with-literal-filter.test.ts | 4 - .../__tests__/scheduler-ephemeral-toggle.test.ts | 175 ------- .../__tests__/scheduler-workflow-cutover.test.ts | 19 - .../src/__tests__/workflow-agent-capacity.test.ts | 47 ++ .../src/__tests__/workflow-agent-routing.test.ts | 137 +++++ .../src/__tests__/workflow-graph-foreach.test.ts | 15 + .../__tests__/workflow-graph-task-runner.test.ts | 73 +++ .../src/__tests__/workflow-task-runtime.test.ts | 95 ++++ .../src/__tests__/workflow-work-scheduler.test.ts | 20 + packages/engine/src/agents/agent-action-gate.ts | 64 +++ packages/engine/src/agents/agent-assignment.ts | 135 ----- packages/engine/src/agents/agent-reflection.ts | 1 + .../engine/src/agents/ephemeral-worker-manager.ts | 429 --------------- .../engine/src/agents/workflow-agent-capacity.ts | 113 ++++ .../engine/src/agents/workflow-agent-router.ts | 185 +++++++ packages/engine/src/execution/reviewer.ts | 26 +- packages/engine/src/executor.ts | 501 +++++++++++++++--- packages/engine/src/index.ts | 1 - packages/engine/src/merger.ts | 20 +- packages/engine/src/pi.ts | 11 + packages/engine/src/runtimes/in-process-runtime.ts | 37 -- packages/engine/src/scheduler.ts | 114 +--- packages/engine/src/triage.ts | 196 ++++++- .../src/workflows/workflow-graph-executor.ts | 109 +++- .../engine/src/workflows/workflow-graph-loop.ts | 13 +- .../src/workflows/workflow-graph-task-runner.ts | 12 + .../engine/src/workflows/workflow-task-runtime.ts | 125 ++++- .../src/workflows/workflow-work-scheduler.ts | 8 +- 98 files changed, 2722 insertions(+), 2468 deletions(-) Fusion-Task-Id: FN-8764 Fusion-Task-Lineage: 5527fccb-342d-46f6-8108-bbf89142efec Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
6bacfd74f5 |
fix(FN-8817): stop no-op lifecycle bounce
Honor verified intentional no-ops and preserve durable merger parks during workflow graph unwind. Fusion-Task-Id: FN-8817 |
||
|
|
4f4aef7173 |
FN-8811: preserve explicit shared-member review holds
Keep shared branch-group integration moving unless an operator explicitly holds the task. - Track auto-merge provenance and distinguish explicit user holds from inherited mission policy. - Preserve manual holds across workflow recovery, merge coordination, API updates, and dashboard status. - Add regression coverage, document the behavior, and quarantine the observed flaky test. Files changed: .changeset/fn-8811-shared-member-review-hold.md | 7 ++ docs/architecture.md | 4 +- docs/dashboard-guide.md | 1 + .../mission-store.sync-auto-merge.test.ts | 7 +- .../__tests__/postgres/mission-store.pg.test.ts | 1 + .../__tests__/postgres/store-movement.pg.test.ts | 20 ++++ packages/core/src/__tests__/task-merge.test.ts | 14 +++ .../core/src/async-stores/async-mission-store.ts | 6 +- packages/core/src/index.gate.ts | 1 + packages/core/src/index.ts | 1 + packages/core/src/merge/task-merge.ts | 20 +++- packages/core/src/missions/mission-store.ts | 6 +- packages/core/src/task-store/serialization.ts | 2 +- packages/core/src/task-store/task-creation.ts | 8 +- packages/core/src/types/task/task-core.ts | 12 ++- .../components/__tests__/TaskDetailModal.test.tsx | 63 ++++++++++++ .../dashboard/src/__tests__/routes-tasks.test.ts | 47 +++++++++ .../src/routes/register-task-workflow-routes.ts | 15 ++- ...cutor-live-branch-group-auto-merge-hold.test.ts | 87 +++++++++++++++++ .../src/__tests__/group-merge-coordinator.test.ts | 99 ++++++++++++++++++- .../engine/src/__tests__/project-engine.test.ts | 57 ++++++++++- .../self-healing-paused-abort-recovery.test.ts | 52 +++++++++- packages/engine/src/__tests__/self-healing.test.ts | 106 +++++++++++++++++++++ .../workflow-graph-executor-handlers.test.ts | 23 +++++ packages/engine/src/executor.ts | 37 ++++++- packages/engine/src/project-engine.ts | 25 +++-- packages/engine/src/self-healing.ts | 71 ++++++++++++-- .../src/workflow-node-runners/merge-runner.ts | 24 ++++- .../src/workflows/workflow-graph-executor.ts | 4 + .../src/workflows/workflow-graph-task-runner.ts | 6 ++ .../engine/src/workflows/workflow-node-handlers.ts | 5 +- packages/engine/vitest.config.ts | 11 ++- scripts/lib/test-quarantine.json | 5 + 33 files changed, 789 insertions(+), 58 deletions(-) Fusion-Task-Id: FN-8811 Fusion-Task-Lineage: 5c1609bf-3132-4988-a254-fedec6c0e33d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
ec10411c4c |
FN-8795: persist structured workflow review findings
Persist normalized actionable findings from review workflow nodes. - Normalize bounded finding IDs, text, locations, and severities in workflow results. - Surface individual findings for Review-tab selection and same-task revision. - Preserve findings through workflow retries and document the advisory contract. Files changed: .changeset/fn-8795-structured-review-findings.md | 7 +++ docs/dashboard-guide.md | 2 +- docs/workflow-steps.md | 4 +- .../src/__tests__/workflow-step-results.test.ts | 32 +++++++++++++- packages/core/src/index.gate.ts | 4 ++ packages/core/src/index.ts | 6 ++- packages/core/src/types.ts | 4 ++ packages/core/src/types/task/task-review.ts | 5 +++ packages/core/src/types/workflow/workflow-steps.ts | 20 +++++++++ .../core/src/workflows/workflow-step-results.ts | 50 +++++++++++++++++++++- .../dashboard/app/components/TaskReviewTab.tsx | 12 +++++- .../src/routes/register-task-workflow-routes.ts | 26 ++++++++++- .../workflow-malformed-verdict-gate.test.ts | 12 ++++++ packages/engine/src/executor.ts | 46 +++++++++++++++++--- .../src/workflows/workflow-graph-executor.ts | 11 +++++ 15 files changed, 227 insertions(+), 14 deletions(-) Fusion-Task-Id: FN-8795 Fusion-Task-Lineage: 09003b01-3f9a-4387-b6a7-f29066ce52f6 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
1dc636b8b4 |
FN-8785: deduplicate queued dependency and scope logs
Persist queue episodes atomically so repeated scheduler and self-healing passes do not duplicate diagnostics. - Add a queued-episode signature with PostgreSQL migration and task serialization support. - Route dependency and file-scope queue transitions through the atomic deduplication API. - Cover repeated and concurrent queue transitions, and update scheduler mocks for the new store API. Files changed: .changeset/fn-8785-queued-log-deduplication.md | 7 + docs/architecture.md | 1 + .../postgres/queued-episode-transition.pg.test.ts | 158 +++++++++++++++++++++ .../src/__tests__/postgres/schema-applier.test.ts | 9 +- .../core/src/postgres/migrations/0000_initial.sql | 1 + .../0044_fn_8785_queued_episode_signature.sql | 3 + packages/core/src/postgres/schema-applier.ts | 12 +- packages/core/src/postgres/schema/project.ts | 1 + packages/core/src/store.ts | 5 +- packages/core/src/task-store/audit-ops.ts | 80 +++++++++++ packages/core/src/task-store/persistence.ts | 2 + packages/core/src/task-store/serialization.ts | 1 + packages/core/src/types/task/task-core.ts | 5 + ...executor-outer-dispatch-dependency-gate.test.ts | 39 +++-- .../engine/src/__tests__/executor-test-helpers.ts | 7 + .../__tests__/scheduler-overlap-starvation.test.ts | 43 +++++- .../__tests__/scheduler-workflow-cutover.test.ts | 35 +++-- .../self-healing-completion-fanout.test.ts | 37 +++++ packages/engine/src/__tests__/self-healing.test.ts | 25 +++- packages/engine/src/executor.ts | 16 ++- packages/engine/src/scheduler.ts | 26 ++-- packages/engine/src/self-healing.ts | 117 +++++---------- 22 files changed, 491 insertions(+), 139 deletions(-) Fusion-Task-Id: FN-8785 Fusion-Task-Lineage: 8d68c243-f24e-4db4-bd1d-b7eda01309f6 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
9939897aab |
fix: prevent stale planning approvals and review churn (#3327)
## Summary Planning can no longer approve or execute against evidence from a superseded dependency episode. Dependency mutations, approval decisions, recovery, and execution admission now share serialized lifecycle rules, so stale planner work cannot restore an invalid approval or release an unplanned task. Review also converges instead of discovering one blocker per round. Planning performs a repository-grounded completeness pass up front; Plan Review batches all independently discoverable blockers and carries an episode-scoped decision ledger across revisions; code review traces changed invariants through production consumers and tests. Repeated feedback still advances the safety budget, while provider failures and superseded episodes stay outside the remediation ledger. The dashboard now exposes manual approval only for the intended exhausted-review state, and refusal/recovery audit events make rejected lifecycle transitions diagnosable without leaking prompt content. ## Validation - `pnpm verify:fast` — scoped typechecks/builds, CLI build, and boot smoke passed. - Focused Core and Engine regression suites — 511 tests passed. - `pnpm lint`, strict changeset validation, Core/Engine typechecks, and package builds passed. Fixes #3325. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Improved Plan Review approvals, rejections, and replan-cap handling across task workflows. * Added cumulative feedback and attempt tracking across repeated planning reviews. * Added safer recovery for stalled planning handoffs and interrupted approval updates. * **Bug Fixes** * Prevented stale approvals and unplanned execution after dependency changes. * Improved concurrent approval handling, retryability, and refusal-record deduplication. * Refined dashboard approval indicators and responsive approval views. * **Quality Improvements** * Strengthened planning and code-review completeness checks and blocking-finding coverage. * Preserved review history while clearly marking outdated approvals. <!-- end of auto-generated comment: release notes by coderabbit.ai --> |
||
|
|
7824150715 |
FN-8769: protect default-branch mission merges
Keep mission group members behind manual release controls when their target is the default branch. - Create deterministic intermediate branches for project-default mission groups. - Gate default-branch group routing and auto-merge exemptions behind the normal manual-release flow. - Cover intermediate and default-branch group behavior with core and engine tests. Files changed: ...fn-8769-default-branch-group-auto-merge-gate.md | 7 ++ docs/architecture.md | 2 +- docs/missions.md | 2 +- .../mission-store.sync-auto-merge.test.ts | 4 +- packages/core/src/__tests__/task-merge.test.ts | 16 ++- .../core/src/async-stores/async-mission-store.ts | 12 ++- packages/core/src/merge/task-merge.ts | 23 +++- packages/core/src/missions/mission-store.ts | 11 +- ...cutor-live-branch-group-auto-merge-hold.test.ts | 16 ++- .../src/__tests__/group-merge-coordinator.test.ts | 118 ++++++++++++++++++++- .../engine/src/__tests__/project-engine.test.ts | 16 ++- packages/engine/src/executor.ts | 4 +- .../engine/src/merge/group-merge-coordinator.ts | 13 +++ packages/engine/src/project-engine.ts | 15 ++- 14 files changed, 235 insertions(+), 24 deletions(-) Fusion-Task-Id: FN-8769 Fusion-Task-Lineage: dff96c8e-ca96-437c-94bf-9691bdf572e9 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
4ff41a723c |
fix: self-heal executor credential resolution for custom providers
Stop synthesizing credentialInstanceId "default" into executor sessions, soft-fail unresolved instances to the legacy unscoped auth path, and collapse-match renamed custom-provider auth slugs so task execute matches chat. |
||
|
|
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** |
||
|
|
73fe461db5 |
fix: demote routine engine log noise to debug
Keep the default TUI for lifecycle transitions (Starting/Specifying/Worktree created/merge/move). Demote expected skips, schedule-trigger echoes, session setup bookkeeping, warm worktree reuse, and fn_run_verification command-fail detail. Pin via severity manifest and contract tests. |
||
|
|
1e7f510ee2 |
fix: stop blocking tasks on open-PR file claims — board tasks are the only blockers
Remove the FN-8700 PR/file-claim blocking mechanism end to end (operator decision after FN-8728 parked on unrelated PR #2398): - Drop the AGENTS.md claim-check rule and scripts/check-file-claimed.mjs - Executor prompt + fn_task_done no longer accept pr:N refs or treat open PRs as blocked-exit reasons - execution-block-classifier classifies on Fusion task dependencies only; legacy pr refs are discarded, reason prose never makes a block durable - Remove the session-log BLOCKED promotion and the gh-backed reconcile-external-pr-blockers self-healing sweep - Legacy file-claim parks are no longer honored, so previously PR-blocked rows recover via normal paths Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9e0f29abc9 |
fix: durable parks for file-claim and PR blockers (FN-8700)
Classify blocked exits so check-file-claimed / open-PR collisions never auto-replan. Park failed with externalBlockers metadata (pr:N supported), promote BLOCKED session logs instead of incomplete-step requeue, thrash- exhaust after 3 identical durable blocks, and clear parks when gh reports blocking PRs merged or closed. |
||
|
|
6e98e16e70 |
fix: reject DUPLICATE-only PROMPT at dispatch (FN-8704)
FN-8704 failed at the graph parse node because PROMPT.md was only "DUPLICATE: FN-8676". Filesystem validation treated non-empty as planned and admitted the card into WIP, which then looped on parse failure. Treat a sole DUPLICATE redirect as unplanned: block dispatch and hold release, badge as awaiting planning, and if parse still sees that shape rebound to needs-replan with feedback instead of parking failed. |
||
|
|
60706ed5e4 |
fix: demote high-frequency TUI log lines to debug
Session setup, track bookkeeping, intentional skill exclusions, token-cache metrics, zero-count recovery summaries, and expected-missing PROMPT seed reads were flooding the default log pane. Gate them behind FUSION_DEBUG so only state transitions and operator-actionable warnings remain visible. |
||
|
|
01d65805c3 |
FN-8693: refresh reused worktree bases before execution
Refresh reused execution worktrees against the current integration baseline. - Rebase or reset clean reused worktrees before coding sessions while preserving task commits. - Persist and audit refreshed base SHAs, and block unsafe refresh states before execution. - Cover executor, graph, and heartbeat refresh paths with regression tests. Files changed: .changeset/fn-8693-stale-worktree-base.md | 7 + docs/architecture.md | 1 + .../src/__tests__/agent-heartbeat-worktree.test.ts | 28 ++++ .../__tests__/ce-workflow-step-executor.test.ts | 44 ++++++ .../src/__tests__/worktree-base-refresh.test.ts | 90 ++++++++++++ packages/engine/src/agent-heartbeat.ts | 35 ++++- packages/engine/src/executor.ts | 67 ++++++++- packages/engine/src/merger.ts | 5 + packages/engine/src/run-audit.ts | 11 ++ packages/engine/src/workflow-graph-executor.ts | 32 ++++- packages/engine/src/worktree-acquisition.ts | 28 +++- packages/engine/src/worktree-base-refresh.ts | 158 +++++++++++++++++++++ 12 files changed, 498 insertions(+), 8 deletions(-) Fusion-Task-Id: FN-8693 Fusion-Task-Lineage: e39a441f-39b5-4723-b503-753e921018f3 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
04c2bb4707 |
FN-8654: rotate credential instances after provider limits
Retry provider-limit failures with eligible credential instances before falling back to existing pauses and backoff. - Add a runtime-shared credential rotator with cooldown, exhaustion, and audit handling. - Wire credential rotation into executor and heartbeat retry lanes while preserving user pause controls. - Document the behavior and cover rotation, recovery, and retry paths. Files changed: .changeset/fn-8654-credential-instance-rotation.md | 7 + AGENTS.md | 1 + docs/architecture.md | 2 +- docs/settings-reference.md | 4 + .../__tests__/credential-instance-rotation.test.ts | 88 +++++++++++ .../__tests__/credential-rotation-lanes.test.ts | 20 +++ .../__tests__/credential-rotation-recovery.test.ts | 19 +++ .../__tests__/credential-rotation-wiring.test.ts | 15 ++ .../__tests__/rate-limit-retry-rotation.test.ts | 50 ++++++ .../src/__tests__/usage-limit-detector.test.ts | 14 ++ packages/engine/src/agent-heartbeat.ts | 102 +++++++++++- .../engine/src/credential-instance-rotation.ts | 175 +++++++++++++++++++++ packages/engine/src/executor.ts | 141 +++++++++++++++-- packages/engine/src/index.ts | 7 + packages/engine/src/project-engine.ts | 5 + packages/engine/src/rate-limit-retry.ts | 32 +++- packages/engine/src/runtimes/in-process-runtime.ts | 29 +++- packages/engine/src/usage-limit-detector.ts | 18 ++- 18 files changed, 699 insertions(+), 30 deletions(-) Fusion-Task-Id: FN-8654 Fusion-Task-Lineage: 44d63441-270c-4949-8c34-47ec4c9992e4 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
4f09758dc8 |
FN-8681: retarget executor step credential instances
Enable executor step sessions to use selected and rotated credential instances. - Pass task-selected credential instances into step-session execution. - Re-resolve live credential targets after usage-limit retries using the effective agent runtime configuration. - Retarget future sessions safely and cover retry behavior. - Document the runtime behavior and add a patch changeset. Files changed: .changeset/fn-8681-credential-instance-retarget.md | 7 + docs/settings-reference.md | 7 +- .../src/__tests__/step-session-executor.test.ts | 211 +++++++++++++++++++++ packages/engine/src/executor.ts | 21 ++ packages/engine/src/step-session-executor.ts | 94 ++++++++- 5 files changed, 332 insertions(+), 8 deletions(-) Fusion-Task-Id: FN-8681 Fusion-Task-Lineage: 99724283-6f5d-4890-b7f8-65af1d88b12c Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
8a6949dd24 |
FN-8661: resolve selected credential instances for sessions
Resolve requested provider credential instances before creating agent sessions. - Thread lane credential instance selections through planning, validation, execution, review, and merge sessions. - Resolve selected instances into runtime credential stores while retaining provider-default fallback behavior. - Preserve selected credentials for mission validation, executor retries, and spawned child agents. Files changed: .../fn-8661-credential-instance-resolution.md | 7 ++ AGENTS.md | 1 + docs/architecture.md | 2 +- docs/secrets.md | 2 + docs/settings-reference.md | 1 + .../dashboard/src/__tests__/routes-auth.test.ts | 80 +++++++++++++++++++ .../dashboard/src/routes/register-model-routes.ts | 78 +++++++++++++++++++ .../src/__tests__/agent-session-helpers.test.ts | 16 ++++ .../credential-instance-resolution.test.ts | 49 ++++++++++++ packages/engine/src/agent-heartbeat.ts | 1 + packages/engine/src/agent-runtime.ts | 9 ++- packages/engine/src/agent-session-helpers.ts | 67 +++++++++++----- packages/engine/src/auth-storage.ts | 90 ++++++++++++++++++---- packages/engine/src/executor.ts | 29 ++++++- packages/engine/src/merger-ai.ts | 2 + packages/engine/src/merger.ts | 5 ++ packages/engine/src/mission-execution-loop.ts | 4 +- packages/engine/src/pi.ts | 7 +- packages/engine/src/pr-response-run-ops.ts | 1 + packages/engine/src/reviewer.ts | 7 ++ packages/engine/src/triage.ts | 2 + 21 files changed, 420 insertions(+), 40 deletions(-) Fusion-Task-Id: FN-8661 Fusion-Task-Lineage: 1e34a3ce-0857-4619-9746-ce0dc12dc2ba Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai> |
||
|
|
5a19d1da6e |
fix: count only actively running tasks against worktree capacity
Retained directories on queued, paused, blocked, or terminal tasks no longer consume scheduler slots. Agent concurrency and worktree capacity now count the same canonical live-task population through one project admission ceiling (resolveActiveTaskCapacityLimit) with an atomic reserveIfAvailable claim, so planning, execute, and merge lanes cannot each observe and claim the final worktree slot independently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7bdeaa8b0c |
fix(engine): the new worktree ledgers count terminal lanes by NAME — renamed boards stall (#3296)
## Census **Before: `COLUMN guards (the backlog): 2`, `--strict` RED. After: `BACKLOG ZERO`, all five gates green.** Two commits from last night's `maxWorktrees` rollout copied the same holder ledger, both with literals: | commit | file | gate | |---|---|---| | `374956ef23` | `triage.ts` | planning admission | | `6c7467a78d` | `executor.ts` | `fn_spawn_agent` | ```ts t.column !== "done" && t.column !== "archived" ``` ## What it costs Both exclude terminal lanes because a finished card's worktree is **cleanup-owned, not capacity**. On a renamed board neither literal matches, so every finished card keeps counting as a live holder. The count only grows, the gate reaches zero room on a board with free slots, and planning admission is withheld forever / every spawn is refused. That is the **mirror** of the breach these commits fixed, and strictly worse: 8 planners on a 4-slot board is visible; a permanent stall is silent. The recorded reason even names the worktree budget, which the operator then checks and finds has room. ## The conversion `resolveProjectColumnsForRoles(store, ["complete", "archived"])` — project-level, because the ledger spans the whole board with no single task to resolve against. Matches triage's existing use in `sweepStalePlanningStatuses` and executor's at the wip gates. Legacy-seeded, so a default board still excludes exactly `done` and `archived` — byte-identical there. ## Both conversions were UNCOVERED when written Measured with #3214's blinding procedure **before** writing tests: reverting either to the literals left **all 19 tests in the capacity suites green**. Nothing in the tree could tell the conversion from what it replaced — which is how the literals got there in the first place. Each now has a renamed-board case that fails when blinded: ``` triage converted 2 passed | BLINDED 1 failed | 1 passed | restored 2 passed executor converted 8 passed | BLINDED 1 failed | 7 passed | restored 8 passed ``` ## The pairing earned itself immediately Both new cases assert an **absence** (no throttle / no refusal), so each is paired with a positive proving the gate still fires on the same renamed board when a card genuinely holds the last worktree. That caught a real defect in my own fixture: the candidate scan resolves each task's **own workflow selection**, not `listWorkflowDefinitions`, so my first version fell back to the default board where `drafting` isn't a hold lane. No card was eligible, nothing throttled, and the absence assertion **passed for the wrong reason**. The positive failed and exposed it. Recorded at the fixture so the next reader doesn't reintroduce it. ## Verification ``` 42 tests across 6 capacity suites pass check-fnxc-future-dates green check-inert-sync-lane-conversions green check-lane-wiring green check-sql-column-literals green census --strict green (BACKLOG ZERO restored) ``` No changeset: internal engine fix, no published-package surface change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6c7467a78d |
fix(engine): spawned children gate on maxWorktrees too — the spawn note promised both dimensions
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
500f40e65b |
fix: descriptive waiting badges (Queued to revise / Queued behind FN-X) + dependency-free blocked exits replan calmly
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
10a0c5848f |
fix(executor): planner-evacuation lanes come from the emitter — executor leaves the inert list (16 → 12) (#3137)
`executor.ts` was the last file besides `triage.ts` and `scheduler.ts`
on `check-inert-sync-lanes`, holding **4 guards that read as converted
and behave as literals**. Neither cause turned out to be "needs an async
resolver".
## 1. Two of the four were in code with no caller
`isPlannerColumnFor` is a **private method with zero production
callers**. `tsc` reports it unused; the only things reaching it were two
tests casting through `executor as unknown as { … }`, which is exactly
what let it look alive. Its doc comment described the
planning-evacuation branch — but that branch calls
`isBackwardMoveOutOfPlanning` and never called this.
Deleted, along with the two tests whose subject it was. Converting
guards in unreachable code would have "fixed" behaviour that cannot run
and left two more sites to maintain; a test whose subject has no caller
pins nothing.
## 2. The other two no longer need to resolve anything
`isBackwardMoveOutOfPlanning` resolved its own lanes via
`resolvePlannerLanes`, whose selection reader returns `undefined`
unconditionally under PostgreSQL — so it answered with the **default
board for every task**, and both its guards were inert.
Its comment justified the sync resolver by the synchronous `task:moved`
emitter. That was true and **is no longer binding**: the emitter now
resolves lanes once, asynchronously (`moves.ts` →
`resolveWorkflowIrForTask`), and hands them on the payload — which #3112
already reads in this same listener. Reading a parameter is as
synchronous as reading `from`, so nothing reorders and no listener
resolves.
`lanes` is **required, not optional**. An optional parameter that the
one production caller happens to pass is the seam-with-no-supplier shape
this program keeps finding; required means a future caller fails
typecheck instead of silently getting a default board. When the emitter
itself could not resolve, the legacy ids answer — exactly what
`resolvePlannerLanes` degraded to anyway.
## Measured
| | before | after |
|---|---|---|
| `check-inert-sync-lanes` | **16** guards, 3 files | **12** guards, 2
files |
| `executor.ts` on that list | 4 | **0 — off the list** |
| census | 18 | 18 (`--strict`: every file matches baseline exactly) |
**The census is deliberately unchanged.** This targets the inert
population, which the census cannot see by construction: those guards
already read as converted. That gap is the argument in #3082 — 12 guards
still behave as literals while the census shows them as done.
## The producer half, which I nearly shipped without
The predicate's own suite covers it thoroughly — and every case calls it
**directly**. Mutation testing exposed that this proves nothing about
the listener: replacing the listener's `lanes` argument with `undefined`
left `planning-evacuation` at **20/20 green**. That is the fifth failure
shape in this program's learnings verbatim — a converted consumer with
an unconverted producer passing every instrument.
So there is now a case driving the **real listener** on a board whose
planner lanes share no id with the legacy pair (`queued` holds,
`drafting` intakes), withdrawing a card to a non-lifecycle column — the
reported symptom (`todo -> Ideas`) in that board's vocabulary.
## Verification
- engine `tsc` — **0 errors**
- `executor-planner-lanes-resolved` — **12 passed**
- `executor-archive-releases-active-session` — **14 passed**; listener
passing `undefined` → **1 failed | 13 passed**
- `planning-evacuation` + `triage-planning-wake` + archive suite — **47
passed**
- `check-inert-flag-seams`, `check-fnxc-future-dates`, census `--strict`
— exit 0
- `eslint` on changed files — 0 errors
The predicate tests are also **stronger than before**, not merely
adapted: they now build lanes with `toTaskMoveLanes`, the same function
`moves.ts` uses for the payload. Previously they reached the predicate
through the store-backed sync reader, so renamed-lane assertions passed
in the harness while the real path could never see a renamed lane.
## Not done here
The inert baseline still reads 29 against a tree of 12 and the gate
advises re-recording. I left it: a stale allowance is a real hazard, but
re-recording is a one-line change that conflicts with every lane, and it
should land once rather than in each of our branches.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
4b61170a51 |
fix(executor): read task:moved lanes from the payload (executor.ts 4 → 0) (#3112)
**Stacked on #3109** — merge that first; this is its first consumer. ## Census | Metric | Before | After | |---|---:|---:| | COLUMN guards (backlog) | 47 | **43** | | `executor.ts` | 4 | **0** | `executor.ts` is off the census top-files list. ## Why these four could not be converted in place This listener is synchronous and its branches **start execution**, dispose worktrees and release sessions. An await ahead of them defers the `execute()` dispatch itself. The sync IR resolver isn't an option either — it answers with the default workflow under PostgreSQL, so a guard written through it is inert. Reading the lanes the emitter already resolved costs nothing and leaves the prologue synchronous. This listener is the reason #3109 has the shape it does. ## The archive branch is the one with teeth `to === "archived"` matched nothing on a board with a renamed terminal lane, so **archiving never released the task's active-session registry entry** — and that entry is what blocks a **successor** task from acquiring the same path. Not cosmetic: the next task wanting that path fails to register. ## Verification - **Revert-proof:** the new case drives a `shipped` terminal lane (matching no legacy id) and asserts the release. Reverting the branch to the literal leaves the entry held — `expected [Array(1)] to have a length of 0`. - 43 executor suites — **483 green** - **`pnpm test:gate` green**; eslint clean ## Note on shape Lanes are read as **single ids, not sets**, because each branch here is a lane-identity test on one column — exactly what the literals were. Widening to membership would change behaviour, not just vocabulary. Fail-soft to the legacy ids when the emit path could not resolve, matching every other consumer of this payload. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
15a664a8f5 |
docs(engine): flag executor's four task:moved literals — the obvious conversion is provably inert (#3104)
The largest unclaimed census cluster. **Nothing in this file said why the sync-lane pass skipped it**, and that silence is the hazard: the obvious next move is to convert these the way `scheduler.ts`'s ten were converted, which would make them **inert rather than fixed**. ## The literals are genuinely wrong — this is not a "non-issue" flag All four sit in one synchronous `task:moved` listener, and on a renamed board: - execution **never starts** on a move into the board's own wip lane; - terminal session release **never runs** on a move into its archive lane; - both `from` guards never fire, so **in-flight work is not aborted** when a card leaves implementation. Nothing errors. The engine simply stops reacting. ## Why the obvious fix is inert — proved, not argued `task:moved` is emitted synchronously, so an `await` here reorders this handler against every other subscriber. That points at the sync IR path, which cannot answer for a renamed board for **two independent reasons** (`sync-workflow-ir-second-blocker.test.ts`, #3103): 1. `getTaskWorkflowSelectionImpl` returns `undefined` unconditionally under PostgreSQL, so `resolveTaskWorkflowIrSync` always takes its `!workflowId` branch. 2. Even **with** a selection, the custom-workflow branch loads its IR through `store.db`, whose implementation is an **unconditional throw** — so it falls into the catch and returns the default IR anyway. **A renamed lane is a custom workflow, so (2) alone is decisive.** The sync path can never serve this listener's case, whatever the selection reader is fixed to do. That is the part the existing notes across this repo miss, and it is why flagging beats attempting here. `check-inert-sync-lane-conversions` already baselines **twenty** guards in exactly that state in `scheduler.ts`. These four must not join them. ## Census **Unchanged at 4, deliberately.** Marking them DELIBERATE-LITERAL would buy a smaller number by asserting the code is *fine*. It is not fine — it is *blocked*. Those are different claims with different expiries, and the census should keep pointing here until the block is lifted. An unconverted literal is visible; an inert conversion leaves the backlog and takes the evidence with it. ## Measured - Comment-only change. - `src/__tests__/executor*` — **84 files / 853 tests pass**. - `tsc --noEmit -p packages/engine` clean; census `--strict`, `check-inert-sync-lane-conversions`, `check-fnxc-future-dates` clean. ## Unblocking, for whoever takes it Either an async listener contract — a behaviour change to handler ordering, not a column conversion — or a sync reader that answers for **custom** workflows *and* survives a writer on another node. All three constraints are written up in `sync-workflow-ir-second-blocker.test.ts` (#3103). --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
eb0ee4ae98 |
fleet: executor.ts 7 → 4 lifecycle-column guards (3 converted, 4 flagged out of scope) (#3048)
Claiming `packages/engine/src/executor.ts` from the census work order.
## Census before/after
| File | Before | After |
|---|---:|---:|
| `packages/engine/src/executor.ts` | 7 | **4** |
Measured with `scripts/lifecycle-column-census.mjs` (kind `column`
only), not grep.
## Converted (3)
**L17258 — the completed-task watchdog never armed on a renamed board.**
It required the card to sit in a literal `in-progress`. This does not
error; the watchdog simply never fires, which is the silent-guard class
this program exists to remove. The branch immediately above already
resolves the same lane through `resolveWipTargetForTask`, and there is
even an FNXC note there saying `latestColumn` must come from that
resolved value — so the comparison now asks the same resolver rather
than an id.
**L14940 (×2) — the duplicate-handoff finalize never ran on a renamed
review lane.** `fromColumn`/`toColumn` are parsed out of the store's
rejection message (`Invalid transition: 'X' → 'Y'`), so they carry
whatever ids that workflow declares. Comparing them to the literal
`in-review` meant a renamed lane never matched and
`finalizeAlreadyReviewedTask` was skipped, leaving the card
mid-transition with nothing to complete it. Now resolves the task's own
review role, falling back to the legacy literal when the workflow cannot
be read — so behaviour is unchanged wherever the vocabulary is
unreadable.
## Flagged, not converted (4) — per the fleet rule that behavior changes
are out of scope
**L3557 / L3581 / L3632 / L3642** are branch conditions inside the
**synchronous** `store.on("task:moved")` listener. Resolving a task's
workflow requires an `await`, which is not available in a sync
listener's condition. Moving the test into the deferred body would widen
the branch to every non-forward move and then re-narrow it — a
**behaviour change to the planning-evacuation path**, not a vocabulary
conversion. Converting them properly means making the listener async,
which wants its own commit and its own test.
I flagged rather than guessed, which is why this is 7 → 4 and not 7 → 0.
## On test coverage, stated plainly
Both converted sites are pure resolver swaps in `async` contexts,
verified by tsc, the census delta, and the existing executor suites (48
tests green). I did **not** add new fixtures: this is the file where I
twice wrote tests that passed against the *unconverted* code —
`recoverCompletedTask`'s seven early-return guards make negative
assertions succeed trivially — and reverted both times rather than claim
coverage I did not have. A fixture that genuinely drives L17258 needs a
satisfied `workflowStepResults` so the run does not divert into graph
re-entry; that is worth doing, and it is worth doing honestly rather
than as a green-looking placeholder.
## Verification
- census: `executor.ts` 7 → 4
- `tsc --noEmit` on `@fusion/engine` clean; `pnpm lint` clean
- `executor-graph-boundary`, `executor-task-done-summary`,
`executor-triage-column-audit`, `executor-step-session` — 48 tests green
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
189f237a07 |
fix(security): make the approval and permission gates actually enforce (#2457)
## Why
An AI agent autonomously deleted a live task (FN-8609) via
`fn_task_delete`. Investigating that led to two audits, which found the
gate machinery is well-built and its *enforcement* well-tested — but the
*decision boundary* was unguarded and the tools that matter never
reached the gate at all.
The headline defect: all ~76 tools in `packages/cli/src/extension.ts`
are delivered through `hostExtensionPaths` → `DefaultResourceLoader`,
which never passes through `wrapToolsWithActionGate`. `fn_task_delete`
*was* classified as gated in `gating-classifications.ts` — that
classification was dead code. Roughly 15 destructive tools were ungated
even for an operator who had locked their policy all the way down.
Second: `POST /api/approvals/:id/decision` took the approver identity
from the request body, validated for shape only, with no check that the
decider was human or differed from the requester. Combined with a bash
tool whose command string was never inspected (only `params.cwd`), an
agent could read the daemon token from `~/.fusion/settings.json`, find
its own pending request, and approve it.
## Scope
This does **not** tighten defaults. The `unrestricted` preset is
byte-for-byte unchanged — `git diff` on `agent-permission-policy.ts` is
empty — and regression tests assert that an out-of-the-box install
behaves exactly as before. The bug was never "the default is too
permissive"; it was "strict policy doesn't enforce." This makes turning
security up actually work.
The one deliberate exception: the containment that stops an agent
escalating its *own* privileges (reading the daemon token / credentials,
calling the approvals API to self-approve) applies at every preset
including `unrestricted`. That is a privilege-escalation boundary rather
than a permission preference — if it only engaged under strict policy it
would not have prevented the incident that prompted this.
## What changed
8 bisectable commits:
- **Approval lifecycle** — self-approval blocked via server-derived
deciders; same-verdict replay 409s; decide re-reads and re-validates
inside the transaction; expiry TTLs; `markCompleted` ownership check;
session identity registry in core.
- **Engine gates enforce for real** — unclassified tools resolve to a
policy-governed category instead of hardcoded `allow`; missing-policy
fail-open closed; bash containment floor + exact-command approval
binding.
- **Dashboard decision routes** — stop trusting client-supplied actors
(decision, bypass-review, worktrunk → 403 on forged actors).
- **`fn serve` authenticated by default** — auto-mints a token following
the existing `fn dashboard` precedent; `--no-auth` opts out.
- **Sibling entry points closed** — user-sourced hard-cancel moves, ACP
execute-once approvals, plugin task-store gating.
- **pi-extension principal resolution** — the extension resolves the
acting principal and can withhold or policy-gate the previously ungated
destructive tools.
- **Root-cause bonus fix** — `findLatestByDedupeKey` was broken in
PostgreSQL backend mode (already-parsed jsonb fed through a string-only
parser), so approved-grant redemption **never matched in production**,
minting duplicate requests. This explains the live DB state of 17
approved / 0 completed. *(Also cherry-picked to `main` as `a9b30013bb`,
since it is an active production defect on its own.)*
- **Review follow-ups** (`627f1b1fa8`) — operator-configured
provisioning privilege and a configurable grant TTL; see below.
## Review follow-ups
**Provisioning privilege is operator-configured, not role-derived.**
`isCallerPrivileged` had gone from `caller.reportsTo == null` (every
top-level agent privileged — permanent escalation by creating a
manager-less agent) to `caller.role === "ceo"`, which swapped an
implicit rule for a magic string: any agent config can claim that role,
while an operator who genuinely wants a privileged agent had no
supported way to say so. Privilege now derives solely from
`agentProvisioning.trustedAgentIds` / `trustedRoles` and fails closed
when settings are unresolvable.
It is also no longer forwarded to `resolveAgentProvisioningPolicy` as
`isPrivileged`, because that flag short-circuits ahead of
`alwaysApproveDelete` — a trusted caller was bypassing delete approval
entirely. The policy applies the same trusted rules itself, in the right
order. The function now governs only the org-chart escape hatch (acting
outside your own direct reports).
**Grant TTL defaults to 1 hour and is configurable.** Approval →
redemption is not instantaneous: an operator approving from their phone,
an engine restart, a queued lane, or a task waiting on a worktree all
routinely exceeded 15 minutes, after which the grant expired and the
agent silently re-requested. One hour remains far short of the
"redeemable forever" hazard the TTL exists to bound. Override via
`FUSION_APPROVAL_GRANT_TTL_MS` or `configureApprovalRequestTtls()`;
invalid overrides are ignored rather than widening the window to
infinity or collapsing it to zero.
## Behavior changes requiring operator review before rollout
1. `fn serve` requires a bearer token by default (`--no-auth` opts out);
unauthenticated clients get 401.
2. Agents can no longer run withheld destructive tools
(`fn_task_delete`, `fn_task_bypass_review`,
mission/milestone/slice/feature/workflow deletes, `experiment_finalize`,
`skills_install`). Operators keep them via CLI/dashboard. **This is the
incident fix.**
3. Agents get provisioning privilege only when the operator lists them
in `agentProvisioning.trustedAgentIds` / `trustedRoles`; the
provisioning gate is now live in production. Previously-implicit
privilege (top-level position, or a `ceo` role) no longer grants
anything on its own.
4. Decision replay 409s (was 200); pending approvals expire after 24h,
approved grants after 1h (configurable); bash approvals bind per exact
command.
5. Forged/body actors on decision, bypass-review, worktrunk routes →
403; `archive-all-done` requires `{confirm:true}` (external scripts
affected).
6. `fn_secret_get` approvals grant exactly one reveal (previously
granted nothing and looped forever); ACP approvals are execute-once
(previously infinite reuse).
7. Bash containment denies token/credential/approvals-API commands in
all agent sessions at every preset.
## Verification
Independently re-run against the branch, not just self-reported:
- 5 typechecks (core, engine, cli, dashboard `tsconfig.json` +
`tsconfig.app.json`) — clean
- `pnpm lint` — clean
- `pnpm test:gate` — 379 passed
- `pnpm build --force` — green (a plain `pnpm build` skips packages as
unchanged and does **not** compile the branch)
- `pnpm check:changesets` — clean
- ~650 file-scoped tests including new negative-path suites for the
decision boundary, which previously had **zero** test coverage
`packages/engine/src/__tests__/plugin-runner.test.ts` fails 56/80 —
**verified pre-existing**, reproducing identically at base commit
`93a403af67` on `main`. Not in the merge gate.
### A mutation check that failed to fail
Worth recording, because it nearly shipped an untested security fix. The
first mutation check on the provisioning change reintroduced the `ceo`
hardcode and **all 17 tests still passed** — the tests asserted through
the policy path, which can no longer observe `isCallerPrivileged` at
all, precisely because `isPrivileged` is no longer forwarded there.
Org-chart cases that do exercise the function were added; the hardcode
now fails exactly 1 of 19, and restoring is green. A green mutation run
is only meaningful if the test can actually see the code under test.
## Known limitations (stated, not papered over)
- The bash containment floor is string-matching: a cost-raiser, not a
sandbox. Quoting, encoding, `$HOME`, symlinks, or an interpreter
one-liner can evade it. The durable protection is the decision route
refusing agent-originated deciders — the filter is the belt, not the
braces.
- Approval expiry is lazy (evaluated at decide/complete/redeem), not
swept, so an expired pending row stays visible in lists until touched.
- The extension's require-approval path returns a pending message but
cannot suspend a pi session mid-turn; engine-side pause hooks cover
engine lanes only.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Security**
* Hardened approval and permission gating with server-side decider
attribution, self-approval blocking, ownership checks, replay/race
protection, and status/TTL enforcement.
* Added fail-closed behavior for sensitive/unclassified tools and
sandbox provisioning approvals.
* Blocked credential/approval access via bash containment; plugin
destructive task operations now require explicit permission.
* **New Features**
* `fn serve` now defaults to bearer-token auth, with `--no-auth` as the
explicit opt-out.
* **Bug Fixes**
* Improved task move-source attribution (`moveSource: "user"`) and
tightened dashboard archive/bypass confirmation and operator attribution
behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|