FN-8258: complete PostgreSQL quarantine rescues
Complete PostgreSQL-backed rescue coverage while retaining archived shared-branch landing proof. - Preserve merge details when archiving and restoring tasks for branch-group promotion. - Migrate remaining quarantine tests and mocks to PostgreSQL-aware boundaries. - Remove rescued tests from the engine quarantine configuration and ledger. Files changed: .changeset/fn-8258-pg-quarantine.md | 7 +++ .../core/src/task-store/archive-lifecycle-2.ts | 1 + packages/core/src/task-store/remaining-ops-6.ts | 8 ++- packages/core/src/task-store/serialization.ts | 1 + .../__tests__/agent-tools-intake-column.test.ts | 26 ++++------ .../agent-workflow-tools-exposure.test.ts | 18 +++---- .../engine/src/__tests__/executor-task-done-invariant.test.ts | 33 ++++++------ .../engine/src/__tests__/executor-test-helpers.ts | 7 +++ .../src/__tests__/group-merge-coordinator.test.ts | 43 ++++++++++------ .../hybrid-executor-multi-node-routing.test.ts | 5 ++ .../mission-factory-parity.integration.test.ts | 2 +- .../engine/src/__tests__/routine-runner.test.ts | 56 +++++++++++++-------- .../self-healing-meta-archive-guards.test.ts | 28 +++++------ .../src/__tests__/triage-token-usage.test.ts | 58 +++++----------------- .../__tests__/workflow-graph-task-runner.test.ts | 16 +++--- packages/engine/src/hybrid-executor-gate.ts | 8 ++- packages/engine/vitest.config.ts | 12 +---- scripts/lib/test-quarantine.json | 52 +------------------ 18 files changed, 165 insertions(+), 216 deletions(-) Fusion-Task-Id: FN-8258 Fusion-Task-Lineage: 121c2b52-ad50-4475-b925-7a36ecfaf28b Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8258-pg-quarantine.md
Normal file
7
.changeset/fn-8258-pg-quarantine.md
Normal file
@@ -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.
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Task[]> {
|
||||
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).
|
||||
|
||||
@@ -374,6 +374,7 @@ export function archiveEntryToTask(
|
||||
missionId: entry.missionId,
|
||||
sliceId: entry.sliceId,
|
||||
assigneeUserId: entry.assigneeUserId,
|
||||
mergeDetails: slim ? undefined : entry.mergeDetails,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<any> }, 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<T>(fn: (store: TaskStore) => Promise<T>): Promise<T> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<typeof import("node:child_process")>("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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<typeof import("@fusion/core")>();
|
||||
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<Settings> } = {}): 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<RoutineRunnerOptions>): 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({
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<void> {
|
||||
await new Promise<void>((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 }),
|
||||
]);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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" };
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.",
|
||||
|
||||
Reference in New Issue
Block a user