diff --git a/.changeset/fn-8258-pg-quarantine.md b/.changeset/fn-8258-pg-quarantine.md new file mode 100644 index 0000000000..8c907a7a77 --- /dev/null +++ b/.changeset/fn-8258-pg-quarantine.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Preserve archived shared-branch landing proof during PostgreSQL promotion checks. +category: fix +dev: Completes PostgreSQL quarantine rescues and prevents archived landed members from blocking branch-group promotion. diff --git a/packages/core/src/task-store/archive-lifecycle-2.ts b/packages/core/src/task-store/archive-lifecycle-2.ts index 132af7af66..60ccc2b72b 100644 --- a/packages/core/src/task-store/archive-lifecycle-2.ts +++ b/packages/core/src/task-store/archive-lifecycle-2.ts @@ -96,6 +96,7 @@ export async function taskToArchiveEntryImpl(store: TaskStore, task: Task, archi missionId: task.missionId, sliceId: task.sliceId, assigneeUserId: task.assigneeUserId, + mergeDetails: task.mergeDetails, }; } diff --git a/packages/core/src/task-store/remaining-ops-6.ts b/packages/core/src/task-store/remaining-ops-6.ts index 550ed51a64..c5aba1636d 100644 --- a/packages/core/src/task-store/remaining-ops-6.ts +++ b/packages/core/src/task-store/remaining-ops-6.ts @@ -100,8 +100,14 @@ export async function listBranchGroupsImpl(store: TaskStore, options?: { status? return (rows as BranchGroupRow[]).map((row) => store.rowToBranchGroup(row)); } +/* +FNXC:BranchGroupCompletion 2026-07-18-01:55: +Archived shared-branch members remain part of promotion completion: an unlanded archived +member blocks promotion, while an archived member with persisted landing proof permits it. +The PostgreSQL path must therefore load archived tasks before applying group membership. +*/ export async function listTasksByBranchGroupImpl(store: TaskStore, groupId: string): Promise { - const tasks = await store.listTasks({ includeArchived: false, slim: true }); + const tasks = await store.listTasks({ includeArchived: true, slim: false }); // Membership filter (incl. legacy synthetic-groupId fallback) is shared with // the dashboard list route via `filterTasksByBranchGroup` so semantics can't // drift between the two call sites (Fix #8/#9). diff --git a/packages/core/src/task-store/serialization.ts b/packages/core/src/task-store/serialization.ts index c1d9132a40..85ae29f476 100644 --- a/packages/core/src/task-store/serialization.ts +++ b/packages/core/src/task-store/serialization.ts @@ -374,6 +374,7 @@ export function archiveEntryToTask( missionId: entry.missionId, sliceId: entry.sliceId, assigneeUserId: entry.assigneeUserId, + mergeDetails: slim ? undefined : entry.mergeDetails, }; } diff --git a/packages/engine/src/__tests__/agent-tools-intake-column.test.ts b/packages/engine/src/__tests__/agent-tools-intake-column.test.ts index b7233b7066..57499a46d3 100644 --- a/packages/engine/src/__tests__/agent-tools-intake-column.test.ts +++ b/packages/engine/src/__tests__/agent-tools-intake-column.test.ts @@ -1,10 +1,8 @@ -import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { mkdtempSync } from "node:fs"; +import { afterEach, beforeEach, expect, it } from "vitest"; import { readFile } from "node:fs/promises"; -import { rm } from "node:fs/promises"; import { join } from "node:path"; -import { tmpdir } from "node:os"; -import { TaskStore, type WorkflowIr } from "@fusion/core"; +import { type TaskStore, type WorkflowIr } from "@fusion/core"; +import { createTaskStoreForTest, pgDescribe, type PgTestHarness } from "../../../core/src/__test-utils__/pg-test-harness.js"; import { createTaskCreateTool } from "../agent-tools.js"; /* @@ -15,22 +13,18 @@ non-triage `intake`-trait column (e.g. "Inbox") must capture new cards there, in (bootstrap-stub PROMPT.md, no Planner spec generation), while the default builtin:coding workflow keeps landing cards in "triage" byte-identically. */ -describe("createTaskCreateTool intake-column wiring", () => { - let rootDir: string; - let globalDir: string; +/* FNXC:PgMigrationQuarantine 2026-07-17-18:15: FN-8258 keeps intake-column behavior on a real PostgreSQL-backed TaskStore, replacing removed SQLite inMemoryDb setup. */ +pgDescribe("createTaskCreateTool intake-column wiring", () => { + let harness: PgTestHarness; let store: TaskStore; beforeEach(async () => { - rootDir = mkdtempSync(join(tmpdir(), "kb-engine-agent-tools-intake-")); - globalDir = mkdtempSync(join(tmpdir(), "kb-engine-agent-tools-intake-global-")); - store = new TaskStore(rootDir, globalDir, { inMemoryDb: true }); - await store.init(); + harness = await createTaskStoreForTest({ prefix: "fusion_agent_tools_intake" }); + store = harness.store; }); afterEach(async () => { - store.close(); - await rm(rootDir, { recursive: true, force: true }); - await rm(globalDir, { recursive: true, force: true }); + await harness?.teardown(); }); function inboxWorkflowIr(name: string): WorkflowIr { @@ -132,7 +126,7 @@ describe("createTaskCreateTool intake-column wiring", () => { expect((result as { isError?: boolean }).isError).toBeFalsy(); const taskId = (result.details as { taskId: string }).taskId; - const prompt = await readFile(join(rootDir, ".fusion", "tasks", taskId, "PROMPT.md"), "utf-8"); + const prompt = await readFile(join(harness.rootDir, ".fusion", "tasks", taskId, "PROMPT.md"), "utf-8"); expect(prompt).toBe(`# ${taskId}\n\nInbox bootstrap prompt task\n`); }); }); diff --git a/packages/engine/src/__tests__/agent-workflow-tools-exposure.test.ts b/packages/engine/src/__tests__/agent-workflow-tools-exposure.test.ts index dbb1228287..eb280eb90c 100644 --- a/packages/engine/src/__tests__/agent-workflow-tools-exposure.test.ts +++ b/packages/engine/src/__tests__/agent-workflow-tools-exposure.test.ts @@ -1,7 +1,4 @@ import { describe, it, expect } from "vitest"; -import { mkdtemp, rm } from "node:fs/promises"; -import { join } from "node:path"; -import { tmpdir } from "node:os"; import { createWorkflowAuthoringTools, createWorkflowListTool, @@ -13,7 +10,8 @@ import { createWorkflowDeleteTool, } from "../index.js"; import { createWorkflowSettingsTool } from "../agent-tools.js"; -import { parseWorkflowIr, TaskStore } from "@fusion/core"; +import { parseWorkflowIr, type TaskStore } from "@fusion/core"; +import { createTaskStoreForTest, pgDescribe } from "../../../core/src/__test-utils__/pg-test-harness.js"; /** * U11 / R12 drift guard (engine half): the workflow-authoring tool surface that @@ -69,7 +67,7 @@ describe("workflow tool exposure (engine factories)", () => { * prompt-injectable agent lane. The executor lane omits the option (project- * owner escape hatch) and the flags pass through unchanged. */ -describe("fn_workflow_validate dry-run", () => { +pgDescribe("fn_workflow_validate dry-run", () => { const run = (tool: { execute: (...a: any[]) => Promise }, params: unknown) => tool.execute("call-1", params, undefined, undefined, undefined) as Promise<{ isError?: boolean; details: any; content: { type: string; text?: string }[]; @@ -85,15 +83,13 @@ describe("fn_workflow_validate dry-run", () => { edges: [{ from: "start", to: "end", condition: "success" as const }], }); + /* FNXC:PgMigrationQuarantine 2026-07-17-18:15: validation must use the production PostgreSQL store boundary rather than removed SQLite constructor paths. */ async function withStore(fn: (store: TaskStore) => Promise): Promise { - const dir = await mkdtemp(join(tmpdir(), "workflow-validate-tool-")); - const store = new TaskStore(dir); - await store.init(); + const harness = await createTaskStoreForTest({ prefix: "fusion_workflow_validate" }); try { - return await fn(store); + return await fn(harness.store); } finally { - await store.close?.().catch(() => {}); - await rm(dir, { recursive: true, force: true }); + await harness.teardown(); } } diff --git a/packages/engine/src/__tests__/executor-task-done-invariant.test.ts b/packages/engine/src/__tests__/executor-task-done-invariant.test.ts index bfd369ccee..a8b2cf13ff 100644 --- a/packages/engine/src/__tests__/executor-task-done-invariant.test.ts +++ b/packages/engine/src/__tests__/executor-task-done-invariant.test.ts @@ -1,12 +1,11 @@ import { beforeEach, afterEach, describe, expect, it, vi } from "vitest"; -import { mkdtempSync, mkdirSync } from "node:fs"; -import { rm } from "node:fs/promises"; +import { mkdirSync } from "node:fs"; import { join } from "node:path"; -import { tmpdir } from "node:os"; import "./executor-test-helpers.js"; import { TaskExecutor } from "../executor.js"; import * as worktreePool from "../worktree-pool.js"; -import { TaskStore } from "@fusion/core"; +import { type TaskStore } from "@fusion/core"; +import { createTaskStoreForTest, pgDescribe, type PgTestHarness } from "../../../core/src/__test-utils__/pg-test-harness.js"; import { createMockStore, mockedCreateFnAgent, mockedExec, mockedExecSync, resetExecutorMocks } from "./executor-test-helpers.js"; const fn416Prompt = `# Task: FN-416 - Assign ready implementation task to active owner @@ -766,23 +765,23 @@ Assign or route exactly one ready implementation task to an eligible active owne }); }); -describe("FN-5241 executor handoff auditing", () => { +/* FNXC:PgMigrationQuarantine 2026-07-18-01:20: FN-8258 runs handoff-audit invariants against the PostgreSQL TaskStore and its async audit boundary, replacing the removed SQLite constructor. */ +pgDescribe("FN-5241 executor handoff auditing", () => { let rootDir: string; - let globalDir: string; + let harness: PgTestHarness; let store: TaskStore; beforeEach(async () => { - rootDir = mkdtempSync(join(tmpdir(), "fn-5241-executor-")); - globalDir = join(rootDir, ".fusion-global"); - store = new TaskStore(rootDir, globalDir); - await store.init(); + // Reset the executor subprocess mock before PG setup so the harness can pass psql through. resetExecutorMocks(); + harness = await createTaskStoreForTest({ prefix: "fusion_executor_handoff_audit" }); + rootDir = harness.rootDir; + store = harness.store; vi.spyOn(worktreePool, "isUsableTaskWorktree").mockResolvedValue(true); }); afterEach(async () => { - store.close(); - await rm(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 50 }); + await harness?.teardown(); }); async function createExecutorTask(taskDoneRetryCount = 0) { @@ -869,11 +868,11 @@ describe("FN-5241 executor handoff auditing", () => { // Forensic intent preserved via the mechanism that actually fires now: the merge boundary records a // task:move into in-review (the review-seam task:handoff("workflow-graph-review") event and the // review-seam merge-queue enqueue no longer occur on this path). - const moveToReview = store - .getRunAuditEvents({ taskId: task.id, mutationType: "task:move", limit: 20 }) + const moveToReview = (await store + .getRunAuditEventsAsync({ taskId: task.id, mutationType: "task:move", limit: 20 })) .find((event) => (event.metadata as { to?: string })?.to === "in-review"); expect(moveToReview).toBeDefined(); - expect(store.getRunAuditEvents({ taskId: task.id, mutationType: "task:handoff", limit: 10 })).toHaveLength(0); + expect(await store.getRunAuditEventsAsync({ taskId: task.id, mutationType: "task:handoff", limit: 10 })).toHaveLength(0); }); it("fails a no-fn_task_done retry-budget-exhausted run in place without moving to in-review", async () => { @@ -906,7 +905,7 @@ describe("FN-5241 executor handoff auditing", () => { // FN-5241 review-seam handoff auditing (task:handoff "workflow-graph-review" / // "max-task-done-retries-exhausted") + merge-queue enqueue are superseded — none of them fire here. expect(latest?.column).not.toBe("in-review"); - expect(store.getRunAuditEvents({ taskId: task.id, mutationType: "task:handoff", limit: 10 })).toHaveLength(0); - expect(store.peekMergeQueue()).toEqual([]); + expect(await store.getRunAuditEventsAsync({ taskId: task.id, mutationType: "task:handoff", limit: 10 })).toHaveLength(0); + expect(await store.peekMergeQueue()).toEqual([]); }); }); diff --git a/packages/engine/src/__tests__/executor-test-helpers.ts b/packages/engine/src/__tests__/executor-test-helpers.ts index cb3d61a483..bac16c91b1 100644 --- a/packages/engine/src/__tests__/executor-test-helpers.ts +++ b/packages/engine/src/__tests__/executor-test-helpers.ts @@ -190,6 +190,7 @@ vi.mock("../worktree-stale-registration.js", async () => { vi.mock("node:child_process", async () => { const { promisify } = await import("node:util"); const { EventEmitter } = await import("node:events"); + const actual = await vi.importActual("node:child_process"); const execSyncFn = vi.fn(); const spawnFn = vi.fn((cmd: string, opts?: any) => { const child = new EventEmitter() as any; @@ -220,6 +221,12 @@ vi.mock("node:child_process", async () => { }); const execFn: any = vi.fn((cmd: string, opts: any, cb: any) => { + /* + FNXC:PgMigrationQuarantine 2026-07-18-01:30: + FN-8258's PG-backed executor audit suite shares this helper. Let its harness run + psql for isolated fixture DDL while retaining mocked executor subprocess commands. + */ + if (/^psql\s/.test(cmd.trim())) return actual.exec(cmd, opts as any, cb as any); const callback = typeof opts === "function" ? opts : cb; const forwardedOpts = typeof opts === "function" ? undefined : opts; try { diff --git a/packages/engine/src/__tests__/group-merge-coordinator.test.ts b/packages/engine/src/__tests__/group-merge-coordinator.test.ts index 4cc30c1ddc..c6e74238dd 100644 --- a/packages/engine/src/__tests__/group-merge-coordinator.test.ts +++ b/packages/engine/src/__tests__/group-merge-coordinator.test.ts @@ -5,7 +5,8 @@ import { join } from "node:path"; import { tmpdir } from "node:os"; import { describe, expect, it, afterEach, beforeEach } from "vitest"; -import { TaskStore } from "@fusion/core"; +import { type TaskStore } from "@fusion/core"; +import { createTaskStoreForTest, pgDescribe, type PgTestHarness } from "../../../core/src/__test-utils__/pg-test-harness.js"; import { evaluateBranchGroupCompletion, evaluateBranchGroupPromotion, @@ -392,33 +393,32 @@ describe("promoteBranchGroup", () => { * `promoteBranchGroup` exercises the actual `listTasksByBranchGroup` membership scan that * previously silently dropped an archived-but-unlanded member from `total`. */ -describe("promoteBranchGroup with a real TaskStore (FN-7534 archived-member regression)", () => { - function makeTmpDir(): string { - return mkdtempSync(join(tmpdir(), "fusion-branch-group-archive-")); - } - +/* +FNXC:PgMigrationQuarantine 2026-07-18-01:50: +FN-8258 exercises archived shared-branch members through the PostgreSQL TaskStore, including +awaited branch-group writes, instead of the removed SQLite constructor path. +*/ +pgDescribe("promoteBranchGroup with a real TaskStore (FN-7534 archived-member regression)", () => { let rootDir: string; - let storeRootDir: string; - let globalDir: string; + let harness: PgTestHarness; let store: TaskStore; beforeEach(async () => { rootDir = makeRepo(); - storeRootDir = makeTmpDir(); - globalDir = join(storeRootDir, ".fusion-global"); - store = new TaskStore(storeRootDir, globalDir); - await store.init(); + harness = await createTaskStoreForTest({ prefix: "fusion_branch_group_archive" }); + store = harness.store; }); - afterEach(() => { - store.close(); + afterEach(async () => { + await harness?.teardown(); }); it("returns reason: incomplete when an archived member never landed onto the group branch", async () => { - const group = store.createBranchGroup({ + const group = await store.createBranchGroup({ sourceType: "planning", sourceId: "PS-archived-gate", branchName: "fusion/groups/archived-gate", + autoMerge: true, }); const landedTask = await store.createTask({ description: "landed member" }); @@ -447,7 +447,7 @@ describe("promoteBranchGroup with a real TaskStore (FN-7534 archived-member regr }); it("still promotes when the only archived member had already landed before archival", async () => { - const group = store.createBranchGroup({ + const group = await store.createBranchGroup({ sourceType: "planning", sourceId: "PS-archived-landed-gate", branchName: "fusion/groups/archived-landed-gate", @@ -472,6 +472,17 @@ describe("promoteBranchGroup with a real TaskStore (FN-7534 archived-member regr await store.moveTask(task.id, "done"); await store.archiveTask(task.id); + // The completion invariant includes archived members and their persisted landing proof. + expect(await store.listTasksByBranchGroup(group.id)).toEqual([ + expect.objectContaining({ + id: task.id, + mergeDetails: expect.objectContaining({ + mergeConfirmed: true, + mergeTargetBranch: group.branchName, + }), + }), + ]); + const result = await promoteBranchGroup({ rootDir, groupId: group.id, diff --git a/packages/engine/src/__tests__/hybrid-executor-multi-node-routing.test.ts b/packages/engine/src/__tests__/hybrid-executor-multi-node-routing.test.ts index 3eb7624d71..038a1de57e 100644 --- a/packages/engine/src/__tests__/hybrid-executor-multi-node-routing.test.ts +++ b/packages/engine/src/__tests__/hybrid-executor-multi-node-routing.test.ts @@ -128,6 +128,11 @@ describe("HybridExecutor multi-node routing", () => { await central.updateProject(projectA.id, { status: "active" }); await central.updateProject(projectB.id, { status: "initializing" }); + // FNXC:PgMigrationQuarantine 2026-07-18-01:45: central node rows persist across test + // instances, so isolate the local-only gate input from a previous test's remote registration. + vi.spyOn(central, "listNodes").mockResolvedValueOnce([{ id: "local-only", type: "local" }] as any); + vi.spyOn(central, "listProjects").mockResolvedValue([projectA, projectB] as any); + // Gate intentionally OFF: ProjectEngineManager handles local // multi-project; running HybridExecutor here duplicates InProcessRuntime // creation and adds ~7s to cold start. diff --git a/packages/engine/src/__tests__/mission-factory-parity.integration.test.ts b/packages/engine/src/__tests__/mission-factory-parity.integration.test.ts index edefc57b4e..6224115b5b 100644 --- a/packages/engine/src/__tests__/mission-factory-parity.integration.test.ts +++ b/packages/engine/src/__tests__/mission-factory-parity.integration.test.ts @@ -604,7 +604,7 @@ describe("MissionFactory Parity: Engine Scheduler/Autopilot", () => { missionStore.addMission(mission); await autopilot.watchMission(mission.id); - autopilot.unwatchMission(mission.id); + await autopilot.unwatchMission(mission.id); expect(missionStore.updateMission).toHaveBeenCalledWith("M-TEST1", { autopilotState: "inactive", diff --git a/packages/engine/src/__tests__/routine-runner.test.ts b/packages/engine/src/__tests__/routine-runner.test.ts index 2034c19cc2..3bf03e5ad8 100644 --- a/packages/engine/src/__tests__/routine-runner.test.ts +++ b/packages/engine/src/__tests__/routine-runner.test.ts @@ -13,22 +13,28 @@ import { mkdtempSync, writeFileSync } from "node:fs"; import { mkdir, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { spawnSync } from "node:child_process"; +const backupCommandState = vi.hoisted(() => ({ + result: { success: true, output: "Backup created" }, + error: null as Error | null, +})); -/** - * Write a real SQLite database file so the production backup path's `PRAGMA quick_check` - * verification passes (mirrors packages/core/src/__tests__/backup.test.ts's fixture helper). - * Falls back to a placeholder file when the `sqlite3` CLI is unavailable — in that case - * verification also no-ops so the backup still succeeds. - */ -function writeTestDb(path: string): void { - const result = spawnSync("sqlite3", [path, "CREATE TABLE IF NOT EXISTS t(x); INSERT INTO t VALUES (1);"], { - encoding: "utf-8", - }); - if (result.error || result.status !== 0) { - writeFileSync(path, "dummy database content"); - } -} +vi.mock("@fusion/core", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + runBackupCommand: vi.fn(async () => { + if (backupCommandState.error) throw backupCommandState.error; + return backupCommandState.result; + }), + }; +}); + +/* +FNXC:PgMigrationQuarantine 2026-07-18-01:40: +FN-8258 keeps RoutineRunner's cron/manual delegation assertions while replacing the removed +SQLite-file fixture with the async PostgreSQL backup command boundary. Backup implementation +is covered in core; this suite verifies routine dispatch, result persistence, and live output. +*/ // Default settings inline to avoid @fusion/core build dependency during tests const DEFAULT_SETTINGS: Settings = { @@ -134,6 +140,8 @@ function createMockAgentStore(): AgentStore { function createMockTaskStore(overrides: { fusionDir?: string; settings?: Partial } = {}): TaskStore { return { getFusionDir: vi.fn().mockReturnValue(overrides.fusionDir ?? "/tmp/.fusion"), + // FNXC:PgMigrationQuarantine 2026-07-18-01:35: backup routines resolve PostgreSQL settings through this async-era TaskStore accessor, so the fake must mirror the production contract. + getGlobalSettingsDir: vi.fn().mockReturnValue(overrides.fusionDir ?? "/tmp/.fusion"), getSettings: vi.fn().mockResolvedValue({ ...DEFAULT_SETTINGS, ...overrides.settings }), on: vi.fn(), off: vi.fn(), @@ -167,6 +175,11 @@ function createRoutineRunner(options?: Partial): RoutineRu } describe("RoutineRunner", () => { + beforeEach(() => { + backupCommandState.result = { success: true, output: "Backup created" }; + backupCommandState.error = null; + }); + describe("executeRoutine", () => { it("successfully executes a routine with trigger type 'cron'", async () => { const routine = createMockRoutine({ id: "routine-1", name: "Test Routine" }); @@ -273,13 +286,14 @@ describe("RoutineRunner", () => { routineStore, taskStore: createMockTaskStore({ fusionDir }), }); + backupCommandState.error = new Error("backup command unavailable"); try { const result = await runner.executeRoutine("routine-backup-missing-db", "cron"); expect(result.success).toBe(false); - expect(result.error).toContain("project DB"); - expect(result.error).toContain(`source: ${join(fusionDir, "fusion.db")}`); + expect(result.error).toContain("project PostgreSQL"); + expect(result.error).toContain(`project state: ${fusionDir}`); expect(result.error).toContain("cause:"); expect(result.error).not.toBe(""); expect(routineStore.completeRoutineExecution).toHaveBeenCalledWith( @@ -287,7 +301,7 @@ describe("RoutineRunner", () => { expect.objectContaining({ success: false, error: result.error, - output: expect.stringContaining("project DB"), + output: "", }), ); } finally { @@ -446,7 +460,7 @@ describe("RoutineRunner", () => { const tempDir = mkdtempSync(join(tmpdir(), "routine-backup-parity-")); const fusionDir = join(tempDir, ".fusion"); await mkdir(fusionDir, { recursive: true }); - writeTestDb(join(fusionDir, "fusion.db")); + writeFileSync(join(fusionDir, "postgres-backed-fixture"), "routine backup dispatch fixture"); try { for (const triggerType of ["cron", "api"] as const) { @@ -477,7 +491,7 @@ describe("RoutineRunner", () => { const tempDir = mkdtempSync(join(tmpdir(), "routine-backup-live-")); const fusionDir = join(tempDir, ".fusion"); await mkdir(fusionDir, { recursive: true }); - writeTestDb(join(fusionDir, "fusion.db")); + writeFileSync(join(fusionDir, "postgres-backed-fixture"), "routine backup dispatch fixture"); try { const routine = createMockRoutine({ diff --git a/packages/engine/src/__tests__/self-healing-meta-archive-guards.test.ts b/packages/engine/src/__tests__/self-healing-meta-archive-guards.test.ts index b1b11be764..f01057e855 100644 --- a/packages/engine/src/__tests__/self-healing-meta-archive-guards.test.ts +++ b/packages/engine/src/__tests__/self-healing-meta-archive-guards.test.ts @@ -51,7 +51,7 @@ const canRun = hasGit && hasPg; const archived = await fixture.selfHeal.autoArchiveResolvedMetaTasks(); expect(archived).toBe(0); expect((await fixture.store.getTask(meta.id))?.column).not.toBe("archived"); - const events = fixture.store.getRunAuditEvents({ limit: 200 }).filter((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); + const events = (await fixture.store.getRunAuditEventsAsync({ limit: 200 })).filter((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); expect(events).toHaveLength(1); expect((events[0]?.metadata as any)?.blockedBy).toEqual(expect.arrayContaining(["branch-has-unique-commits"])); } finally { @@ -65,7 +65,7 @@ const canRun = hasGit && hasPg; try { const archived = await fixture.selfHeal.autoArchiveResolvedMetaTasks(); expect(archived).toBe(0); - const event = fixture.store.getRunAuditEvents({ limit: 200 }).find((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); + const event = (await fixture.store.getRunAuditEventsAsync({ limit: 200 })).find((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); expect((event?.metadata as any)?.blockedBy).toEqual(expect.arrayContaining(["recent-executor-activity"])); } finally { await fixture.cleanup(); @@ -77,7 +77,7 @@ const canRun = hasGit && hasPg; await fixture.store.updateTask(meta.id, { taskDoneRetryCount: 2 } as any); try { await fixture.selfHeal.autoArchiveResolvedMetaTasks(); - const event = fixture.store.getRunAuditEvents({ limit: 200 }).find((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); + const event = (await fixture.store.getRunAuditEventsAsync({ limit: 200 })).find((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); expect((event?.metadata as any)?.blockedBy).toEqual(expect.arrayContaining(["task-done-retry-pending"])); } finally { await fixture.cleanup(); @@ -90,13 +90,13 @@ const canRun = hasGit && hasPg; try { await fixture.selfHeal.autoArchiveResolvedMetaTasks(); await fixture.selfHeal.autoArchiveResolvedMetaTasks(); - let events = fixture.store.getRunAuditEvents({ limit: 200 }).filter((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); + let events = (await fixture.store.getRunAuditEventsAsync({ limit: 200 })).filter((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); expect(events).toHaveLength(1); expect((events[0]?.metadata as any)?.blockedBy).toEqual(expect.arrayContaining(["task-done-retry-pending"])); await fixture.store.updateTask(meta.id, { taskDoneRetryCount: 0, status: "merging" } as any); await fixture.selfHeal.autoArchiveResolvedMetaTasks(); - events = fixture.store.getRunAuditEvents({ limit: 200 }).filter((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); + events = (await fixture.store.getRunAuditEventsAsync({ limit: 200 })).filter((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); expect(events).toHaveLength(2); expect(events.some((event) => (event.metadata as any)?.blockedBy?.includes("merge-in-progress"))).toBe(true); } finally { @@ -113,7 +113,7 @@ const canRun = hasGit && hasPg; await fixture.store.updateTask(meta.id, updates as any); try { await fixture.selfHeal.autoArchiveResolvedMetaTasks(); - const event = fixture.store.getRunAuditEvents({ limit: 200 }).find((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); + const event = (await fixture.store.getRunAuditEventsAsync({ limit: 200 })).find((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); expect((event?.metadata as any)?.blockedBy).toEqual(expect.arrayContaining(["merge-in-progress"])); } finally { await fixture.cleanup(); @@ -128,7 +128,7 @@ const canRun = hasGit && hasPg; activeSessionRegistry.registerPath(activePath, { taskId: meta.id, kind: "executor", ownerKey: meta.id }); try { await fixture.selfHeal.autoArchiveResolvedMetaTasks(); - const event = fixture.store.getRunAuditEvents({ limit: 200 }).find((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); + const event = (await fixture.store.getRunAuditEventsAsync({ limit: 200 })).find((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); expect((event?.metadata as any)?.blockedBy).toEqual(expect.arrayContaining(["active-session"])); } finally { activeSessionRegistry.unregisterPath(activePath); @@ -141,7 +141,7 @@ const canRun = hasGit && hasPg; await fixture.store.updateTask(meta.id, { taskDoneRetryCount: 1, status: "merging" } as any); try { await fixture.selfHeal.autoArchiveResolvedMetaTasks(); - const event = fixture.store.getRunAuditEvents({ limit: 200 }).find((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); + const event = (await fixture.store.getRunAuditEventsAsync({ limit: 200 })).find((e) => e.mutationType === "task:auto-archive-meta-resolved-skipped"); expect((event?.metadata as any)?.blockedBy).toEqual(expect.arrayContaining(["task-done-retry-pending", "merge-in-progress"])); } finally { await fixture.cleanup(); @@ -154,7 +154,7 @@ const canRun = hasGit && hasPg; const archived = await fixture.selfHeal.autoArchiveResolvedMetaTasks(); expect(archived).toBe(1); expect((await fixture.store.getTask(meta.id))?.column).toBe("archived"); - const audits = fixture.store.getRunAuditEvents({ limit: 200 }); + const audits = await fixture.store.getRunAuditEventsAsync({ limit: 200 }); expect(audits.some((event) => event.mutationType === "task:auto-archived-meta-resolved")).toBe(true); expect(audits.some((event) => event.mutationType === "task:auto-archive-meta-resolved-skipped")).toBe(false); } finally { @@ -163,7 +163,7 @@ const canRun = hasGit && hasPg; }); it("emits stalled skipped event when guards block stalled archive", async () => { - vi.useFakeTimers(); + // FNXC:PgMigrationQuarantine 2026-07-17-18:30: pin Date for stale-archive thresholds while retaining real timers so PostgreSQL fixture I/O cannot deadlock. const now = new Date("2026-05-18T12:00:00.000Z"); vi.setSystemTime(now); const { fixture, meta } = await createResolvedMetaPair({ metaTaskStallAutoCloseMs: 60_000 }); @@ -172,7 +172,7 @@ const canRun = hasGit && hasPg; try { const archived = await fixture.selfHeal.autoArchiveStalledMetaTasks(); expect(archived).toBe(0); - const event = fixture.store.getRunAuditEvents({ limit: 200 }).find((e) => e.mutationType === "task:auto-archive-meta-stalled-skipped"); + const event = (await fixture.store.getRunAuditEventsAsync({ limit: 200 })).find((e) => e.mutationType === "task:auto-archive-meta-stalled-skipped"); expect(event).toBeTruthy(); expect((event?.metadata as any)?.blockedBy).toEqual(expect.arrayContaining(["task-done-retry-pending"])); } finally { @@ -182,7 +182,7 @@ const canRun = hasGit && hasPg; }); it("dedupes stalled skipped audits until the guard reason changes", async () => { - vi.useFakeTimers(); + // FNXC:PgMigrationQuarantine 2026-07-17-18:30: pin Date for stale-archive thresholds while retaining real timers so PostgreSQL fixture I/O cannot deadlock. const now = new Date("2026-05-18T12:00:00.000Z"); vi.setSystemTime(now); const { fixture, meta } = await createResolvedMetaPair({ metaTaskStallAutoCloseMs: 60_000 }); @@ -191,13 +191,13 @@ const canRun = hasGit && hasPg; try { await fixture.selfHeal.autoArchiveStalledMetaTasks(); await fixture.selfHeal.autoArchiveStalledMetaTasks(); - let events = fixture.store.getRunAuditEvents({ limit: 200 }).filter((e) => e.mutationType === "task:auto-archive-meta-stalled-skipped"); + let events = (await fixture.store.getRunAuditEventsAsync({ limit: 200 })).filter((e) => e.mutationType === "task:auto-archive-meta-stalled-skipped"); expect(events).toHaveLength(1); expect((events[0]?.metadata as any)?.blockedBy).toEqual(expect.arrayContaining(["task-done-retry-pending"])); await fixture.store.updateTask(meta.id, { taskDoneRetryCount: 0, status: "merging" } as any); await fixture.selfHeal.autoArchiveStalledMetaTasks(); - events = fixture.store.getRunAuditEvents({ limit: 200 }).filter((e) => e.mutationType === "task:auto-archive-meta-stalled-skipped"); + events = (await fixture.store.getRunAuditEventsAsync({ limit: 200 })).filter((e) => e.mutationType === "task:auto-archive-meta-stalled-skipped"); expect(events).toHaveLength(2); expect(events.some((event) => (event.metadata as any)?.blockedBy?.includes("merge-in-progress"))).toBe(true); } finally { diff --git a/packages/engine/src/__tests__/triage-token-usage.test.ts b/packages/engine/src/__tests__/triage-token-usage.test.ts index dab2cf3007..552cc1547d 100644 --- a/packages/engine/src/__tests__/triage-token-usage.test.ts +++ b/packages/engine/src/__tests__/triage-token-usage.test.ts @@ -1,10 +1,7 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { mkdtempSync } from "node:fs"; -import { rm } from "node:fs/promises"; -import { join } from "node:path"; -import { tmpdir } from "node:os"; +import { afterEach, beforeEach, expect, it, vi } from "vitest"; import type { AgentSession } from "@earendil-works/pi-coding-agent"; -import { aggregateTokenAnalytics, Database, type Task, type TaskStore } from "@fusion/core"; +import { aggregateTokenAnalytics, type Task, type TaskStore } from "@fusion/core"; +import { createTaskStoreForTest, pgDescribe, type PgTestHarness } from "../../../core/src/__test-utils__/pg-test-harness.js"; import { TriageProcessor } from "../triage.js"; interface MockSessionStats { @@ -59,34 +56,9 @@ async function flushAsyncRecorders(): Promise { await new Promise((resolve) => setImmediate(resolve)); } -function insertUsageTask(db: Database, task: Task): void { - const usage = task.tokenUsage; - if (!usage) throw new Error("expected token usage"); - db.prepare( - `INSERT INTO tasks - (id, description, "column", createdAt, updatedAt, - tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens, - tokenUsageCacheWriteTokens, tokenUsageTotalTokens, tokenUsageLastUsedAt, - modelProvider, modelId, tokenUsageModelProvider, tokenUsageModelId, tokenUsagePerModel) - VALUES (?, 'desc', 'todo', '2026-01-01T00:00:00.000Z', '2026-01-01T00:00:00.000Z', - ?, ?, ?, ?, ?, ?, null, null, ?, ?, ?)`, - ).run( - task.id, - usage.inputTokens, - usage.outputTokens, - usage.cachedTokens, - usage.cacheWriteTokens, - usage.totalTokens, - usage.lastUsedAt, - usage.modelProvider ?? null, - usage.modelId ?? null, - JSON.stringify(usage.perModel ?? []), - ); -} - -describe("triage session token usage recording", () => { - let tmpDir: string | undefined; - let db: Database | undefined; +/* FNXC:PgMigrationQuarantine 2026-07-17-18:25: FN-8258 proves token analytics through AsyncDataLayer project-scoped rows, replacing removed raw SQLite Database seeding. */ +pgDescribe("triage session token usage recording", () => { + let harness: PgTestHarness; beforeEach(() => { vi.useRealTimers(); @@ -94,12 +66,7 @@ describe("triage session token usage recording", () => { }); afterEach(async () => { - db?.close(); - if (tmpDir) { - await rm(tmpDir, { recursive: true, force: true }); - } - tmpDir = undefined; - db = undefined; + await harness?.teardown(); }); it("records a triage-only Anthropic planning model and surfaces it in by-model analytics", async () => { @@ -136,18 +103,17 @@ describe("triage session token usage recording", () => { }), ]); - tmpDir = mkdtempSync(join(tmpdir(), "kb-triage-token-analytics-")); - db = new Database(join(tmpDir, ".fusion")); - db.init(); - insertUsageTask(db, store._task); + harness = await createTaskStoreForTest({ prefix: "fusion_triage_token_usage" }); + const persisted = await harness.store.createTask({ description: "triage analytics" }); + await harness.store.updateTask(persisted.id, { tokenUsage: store._task.tokenUsage }); - const byModel = await aggregateTokenAnalytics(db, { groupBy: "model" }); + const byModel = await aggregateTokenAnalytics(harness.layer, { groupBy: "model" }); expect(byModel.totals).toMatchObject({ totalTokens: 165, nTasks: 1 }); expect(byModel.groups).toEqual([ expect.objectContaining({ key: "claude-sonnet-4-5", totalTokens: 165, nTasks: 1 }), ]); - const byProvider = await aggregateTokenAnalytics(db, { groupBy: "provider" }); + const byProvider = await aggregateTokenAnalytics(harness.layer, { groupBy: "provider" }); expect(byProvider.groups).toEqual([ expect.objectContaining({ key: "anthropic", totalTokens: 165, nTasks: 1 }), ]); diff --git a/packages/engine/src/__tests__/workflow-graph-task-runner.test.ts b/packages/engine/src/__tests__/workflow-graph-task-runner.test.ts index 183ca3f11f..adad89ee80 100644 --- a/packages/engine/src/__tests__/workflow-graph-task-runner.test.ts +++ b/packages/engine/src/__tests__/workflow-graph-task-runner.test.ts @@ -1,10 +1,7 @@ import { describe, expect, it, vi } from "vitest"; import { EventEmitter } from "node:events"; -import { mkdtempSync, rmSync } from "node:fs"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; import type { Settings, TaskDetail, WorkflowDefinition, WorkflowIr } from "@fusion/core"; -import { TaskStore } from "@fusion/core"; +import { createTaskStoreForTest, PG_AVAILABLE } from "../../../core/src/__test-utils__/pg-test-harness.js"; import { NotificationService } from "../notification/notification-service.js"; import { WorkflowGraphTaskRunner, type WorkflowGraphRunnerStore } from "../workflow-graph-task-runner.js"; @@ -268,10 +265,10 @@ describe("WorkflowGraphTaskRunner (CU-U2)", () => { expect(result.reason).toMatch(/workflow-missing/); }); - it("persists a valid workflow through the store and launches it through the graph runner", async () => { - const rootDir = mkdtempSync(join(tmpdir(), "fn-7113-workflow-run-")); - const globalDir = mkdtempSync(join(tmpdir(), "fn-7113-workflow-global-")); - const store = new TaskStore(rootDir, globalDir, { inMemoryDb: true }); + // FNXC:PgMigrationQuarantine 2026-07-17-18:15: FN-8258 exercises persisted workflow selection against the PostgreSQL AsyncDataLayer rather than removed SQLite inMemoryDb construction. + (PG_AVAILABLE ? it : it.skip)("persists a valid workflow through the store and launches it through the graph runner", async () => { + const harness = await createTaskStoreForTest({ prefix: "fusion_workflow_graph_runner" }); + const store = harness.store; try { const invalidIr: WorkflowIr = { version: "v2", @@ -313,8 +310,7 @@ describe("WorkflowGraphTaskRunner (CU-U2)", () => { expect(result.disposition).toBe("completed"); expect(calls).toEqual(["custom:lint", "execute", "review", "merge", "custom:notify"]); } finally { - rmSync(rootDir, { recursive: true, force: true }); - rmSync(globalDir, { recursive: true, force: true }); + await harness.teardown(); } }); diff --git a/packages/engine/src/hybrid-executor-gate.ts b/packages/engine/src/hybrid-executor-gate.ts index fff7e4c5cc..94b0bb7c39 100644 --- a/packages/engine/src/hybrid-executor-gate.ts +++ b/packages/engine/src/hybrid-executor-gate.ts @@ -25,7 +25,13 @@ export async function shouldUseHybridExecutor(centralCore: CentralCore): Promise try { const nodes = await centralCore.listNodes(); - if (nodes.length > 1) { + /* + FNXC:HybridExecutorRouting 2026-07-18-01:35: + Local project registrations may create more than one local node record. HybridExecutor + only adds value when a remote node is routable, so node count must not enable duplicate + local runtimes for a local-only multi-project installation. + */ + if (nodes.some((node) => node.type === "remote")) { return { enabled: true, reason: "multi-node" }; } diff --git a/packages/engine/vitest.config.ts b/packages/engine/vitest.config.ts index 8308f68e9d..8119d7db69 100644 --- a/packages/engine/vitest.config.ts +++ b/packages/engine/vitest.config.ts @@ -295,7 +295,7 @@ export default defineConfig({ // SQLite-path gate test evicted + quarantined (see engine-core comment + ledger). "node_modules/**", "dist/**", - // FNXC:PgMigrationQuarantine 2026-07-17-16:50: FN-8252 rescued the semantic async-store holdouts through production PostgreSQL seams and current async harnesses; retain only the remaining paired quarantines. + // FNXC:PgMigrationQuarantine 2026-07-18-02:10: FN-8258 rescued the ten remaining VAL-REMOVAL-005 holdouts through PostgreSQL harnesses and production-shaped async contracts; retain only unresolved paired quarantines. // FNXC:PgMigrationQuarantine 2026-07-14-08:00: // FNXC:WorkflowStepInstancePersistence 2026-07-16-20:35: FN-8157 restores this PG-backed foreach suite through async store persistence, so it must execute in engine-default. // VAL-REMOVAL-005 deleted the SQLite Database class. These engine-default files fail @@ -303,19 +303,9 @@ export default defineConfig({ // getDatabase, walCheckpoint) that throw/return-empty in backend mode, or have mock // drift from the async-satellite cutover. Quarantined on sight per AGENTS.md. "src/__tests__/backlog-pressure-reporter.test.ts", - "src/__tests__/mission-factory-parity.integration.test.ts", - "src/__tests__/workflow-graph-task-runner.test.ts", - "src/__tests__/agent-tools-intake-column.test.ts", - "src/__tests__/agent-workflow-tools-exposure.test.ts", "src/__tests__/dependency-blocked-todo-reporter.test.ts", - "src/__tests__/executor-task-done-invariant.test.ts", "src/__tests__/goal-injection-diagnostics-wiring.test.ts", - "src/__tests__/group-merge-coordinator.test.ts", - "src/__tests__/hybrid-executor-multi-node-routing.test.ts", "src/__tests__/merger-cwd-fallback-removed.test.ts", - "src/__tests__/routine-runner.test.ts", - "src/__tests__/self-healing-meta-archive-guards.test.ts", - "src/__tests__/triage-token-usage.test.ts", /* FNXC:EngineTests 2026-07-18-08:15: heartbeat-error-recovery timed out at 30s on full-suite shard 1 diff --git a/scripts/lib/test-quarantine.json b/scripts/lib/test-quarantine.json index aa468042c2..0a1c50ef34 100644 --- a/scripts/lib/test-quarantine.json +++ b/scripts/lib/test-quarantine.json @@ -1,76 +1,26 @@ { - "$comment": "Flaky-test quarantine ledger (deletion ratchet \u2014 see AGENTS.md 'Flaky tests: quarantine on sight' and docs/testing.md 'Quarantine ledger and the deletion ratchet'). A test observed failing without a corresponding real bug is quarantined ON SIGHT: add an entry here AND a matching one-line `exclude` entry in that package's vitest config, in the same commit. Every entry needs a non-empty `reason` (link the failing run) and a `quarantinedAt` date \u2014 the entry expires 14 days later, at which point the test file is DELETED unless someone rescues it with evidence it catches real regressions plus a root-cause fix (never appeasement). There is deliberately no loader module and no automation around this file: it is a dated record, the vitest config is the enforcement.", + "$comment": "Flaky-test quarantine ledger (deletion ratchet — see AGENTS.md 'Flaky tests: quarantine on sight' and docs/testing.md 'Quarantine ledger and the deletion ratchet'). A test observed failing without a corresponding real bug is quarantined ON SIGHT: add an entry here AND a matching one-line `exclude` entry in that package's vitest config, in the same commit. Every entry needs a non-empty `reason` (link the failing run) and a `quarantinedAt` date — the entry expires 14 days later, at which point the test file is DELETED unless someone rescues it with evidence it catches real regressions plus a root-cause fix (never appeasement). There is deliberately no loader module and no automation around this file: it is a dated record, the vitest config is the enforcement.", "entries": [ { "file": "packages/engine/src/__tests__/backlog-pressure-reporter.test.ts", "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", "quarantinedAt": "2026-07-14" }, - { - "file": "packages/engine/src/__tests__/mission-factory-parity.integration.test.ts", - "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", - "quarantinedAt": "2026-07-14" - }, - { - "file": "packages/engine/src/__tests__/workflow-graph-task-runner.test.ts", - "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", - "quarantinedAt": "2026-07-14" - }, - { - "file": "packages/engine/src/__tests__/agent-tools-intake-column.test.ts", - "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", - "quarantinedAt": "2026-07-14" - }, - { - "file": "packages/engine/src/__tests__/agent-workflow-tools-exposure.test.ts", - "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", - "quarantinedAt": "2026-07-14" - }, { "file": "packages/engine/src/__tests__/dependency-blocked-todo-reporter.test.ts", "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", "quarantinedAt": "2026-07-14" }, - { - "file": "packages/engine/src/__tests__/executor-task-done-invariant.test.ts", - "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", - "quarantinedAt": "2026-07-14" - }, { "file": "packages/engine/src/__tests__/goal-injection-diagnostics-wiring.test.ts", "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", "quarantinedAt": "2026-07-14" }, - { - "file": "packages/engine/src/__tests__/group-merge-coordinator.test.ts", - "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", - "quarantinedAt": "2026-07-14" - }, - { - "file": "packages/engine/src/__tests__/hybrid-executor-multi-node-routing.test.ts", - "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", - "quarantinedAt": "2026-07-14" - }, { "file": "packages/engine/src/__tests__/merger-cwd-fallback-removed.test.ts", "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", "quarantinedAt": "2026-07-14" }, - { - "file": "packages/engine/src/__tests__/routine-runner.test.ts", - "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", - "quarantinedAt": "2026-07-14" - }, - { - "file": "packages/engine/src/__tests__/self-healing-meta-archive-guards.test.ts", - "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", - "quarantinedAt": "2026-07-14" - }, - { - "file": "packages/engine/src/__tests__/triage-token-usage.test.ts", - "reason": "VAL-REMOVAL-005 PG migration: fails due to SQLite removal or sync API incompatibility in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.", - "quarantinedAt": "2026-07-14" - }, { "file": "packages/engine/src/__tests__/reliability-interactions/integration-worktree-state.test.ts", "reason": "VAL-REMOVAL-005 PG migration: reliability fixture uses PG-backed store but sync APIs (getRunAuditEvents, getDatabase) fail in backend mode. Failing run: https://github.com/Runfusion/Fusion/actions/runs/29344576232. Mirrored in packages/engine/vitest.config.ts.",