fix(executor+engine-tests): preflight premise-stale exit and serialize reliability suite

Root-cause prevention for the FN-5521 failure class where PROMPT.md describes
work that is already done on HEAD and the executor still marches through
plan/review/test/doc, then merger burns hours retrying against unrelated
flakes in @fusion/engine reliability-interactions.

- Executor system prompt: add a Preflight escape hatch. When Step 0
  reproduces and finds HEAD already matches the desired state, the agent
  marks Step 0 done, marks remaining steps skipped, and calls fn_task_done
  with a `PREMISE STALE:` summary. Reuses the existing
  evaluateTaskDoneRefusal/skipped semantics and the merger's
  empty-own-diff fast-path — no new tools or refusal classes needed.

- packages/engine/vitest.config.ts: split into two projects. engine-default
  retains full parallelism; engine-reliability scopes
  src/__tests__/reliability-interactions/** to
  poolOptions.threads.singleThread so event-ordering assertions stop
  flaking under workspace-concurrent merge-gate load (the
  `expected 24 to be less than 19` rowid interleaving in
  merge-reuse-task-worktree).
This commit is contained in:
gsxdsm
2026-05-22 22:26:18 -07:00
parent 23a0c27bd5
commit 8a3afcf9d4
3 changed files with 49 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
---
"@runfusion/fusion": minor
---
feat(executor+engine-tests): preflight premise-stale exit and serialize reliability-interactions
- Executor: teach the system prompt a Preflight escape hatch. When Step 0 reproduces the issue described in PROMPT.md and finds the work is already done (HEAD matches the desired state), the agent now marks Step 0 done, marks remaining steps `skipped`, and calls `fn_task_done` with a `PREMISE STALE: …` summary. Skipped steps already pass `evaluateTaskDoneRefusal`, and the merger's empty-own-diff fast-path auto-finalizes the zero-diff branch — no new tool or refusal class is needed. This stops the executor from looping through plan/review/test/doc when PROMPT.md is out of sync with HEAD (the failure mode that exhausted FN-5521 across four worktrees).
- Engine vitest: split `packages/engine/vitest.config.ts` into two projects. `engine-default` keeps the full-parallelism layout for the bulk of the suite; `engine-reliability` scopes `src/__tests__/reliability-interactions/**` to `poolOptions.threads.singleThread: true` so the contention-sensitive event-ordering assertions (e.g. `merge-reuse-task-worktree`'s newest-first audit ordering check) no longer flake under workspace-concurrent merge-gate runs. Within-file order was already linear; this only removes inter-file parallelism for ~99 files that always shared a single git/SQLite contention surface.

View File

@@ -789,6 +789,16 @@ You have tools to report progress. The board updates in real-time.
- After completing a step: \`fn_task_update(step=N, status="done")\`
- If skipping a step: \`fn_task_update(step=N, status="skipped")\`
**Preflight escape hatch — stale premise.**
PROMPT.md is captured at task-creation time; HEAD may have moved on since then. During Preflight (Step 0), reproduce the failure or symptom described in the PROMPT. If reproduction shows the work is **already done or the premise no longer matches HEAD** — for example, the test that PROMPT claims is failing already passes on the current base, or the file PROMPT says to change already contains the described change — do NOT march through the remaining steps producing empty commits. Instead:
1. Call \`fn_task_log\` with a clear premise-stale finding: what PROMPT.md claimed vs. what HEAD actually shows (include the exact reproduction command + its result).
2. Mark Step 0 done: \`fn_task_update(step=0, status="done")\`.
3. Mark every remaining step skipped with a one-line reason: \`fn_task_update(step=N, status="skipped")\`.
4. Call \`fn_task_done\` with a summary that begins \`PREMISE STALE:\` followed by the concrete reason (e.g. \`PREMISE STALE: targeted reproduction passes unchanged on HEAD; PROMPT claimed MOBILE_MEDIA_QUERY had been expanded but useViewportMode.ts:9 still exports the legacy value\`).
This path exists specifically to prevent the executor from looping when PROMPT.md is out of sync with HEAD. Use it only after running the actual reproduction — do not invoke it to dodge real work.
**Logging important actions:** \`fn_task_log(message="what happened")\`
**Out-of-scope work found during execution:** \`fn_task_create(description="what needs doing")\`

View File

@@ -35,6 +35,36 @@ export default defineConfig({
// Real-git integration tests need more than the default 5 s under concurrent
// load (other packages run tests at the same time via pnpm recursive).
testTimeout: 30_000,
// Split into two projects so the reliability-interactions suite (real
// worktrees + real git, contention-sensitive event ordering) runs
// single-threaded without throttling the rest of the engine suite.
projects: [
{
extends: true,
test: {
name: "engine-default",
include: ["src/**/*.test.ts"],
exclude: [
"src/__tests__/reliability-interactions/**/*.test.ts",
"node_modules/**",
"dist/**",
],
},
},
{
extends: true,
test: {
name: "engine-reliability",
include: ["src/__tests__/reliability-interactions/**/*.test.ts"],
// These tests assert event ordering across real worktrees. Parallel
// execution under merger load caused subprocess-guard timeouts and
// SQLite rowid interleaving (e.g. FN-5521 hit
// `expected 24 to be less than 19` in merge-reuse-task-worktree).
// Serialize at the file level; within-file order is already linear.
poolOptions: { threads: { singleThread: true } },
},
},
],
coverage: {
enabled: false,
reporter: ["text", "html", "json"],