test(core): fast harness reset, sqlite-migrator template clones, top-3 harness adoptions

Slow-test trim, OPTIMIZE tier (harness owner). Profiling showed 163ms
of the shared harness's 169ms per-test beforeEach was one TRUNCATE over
~110 tables (per-table catalog/fsync constants, row-independent). The
reset is now a single DO block: transaction-local
session_replication_role=replica, DELETE only from non-empty tables,
and sequence resets reproducing RESTART IDENTITY exactly — 169ms → 10ms
per test across every harness file, with the legacy TRUNCATE retained
as an automatic error fallback for non-superuser roles. sqlite-migrator
(the live SQLite→PG upgrade path; all 43 tests kept incl.
VAL-MIGRATE-001..006) now provisions targets from a golden-template
clone via new createBaselinedPgTestDatabase/createEmptyPgTestDatabase
exports — the dry-run test keeps a pristine empty DB so its
no-schemas-left-behind assertions stay meaningful — 148.8s → ~26s.
taskstore-lifecycle, data-layer, and taskstore-remaining migrate onto
the shared harness with byte-identical test bodies (187s → ~11s
combined); data-layer's close() test closes a private layer so the
shared pool survives. Seeding hoists were measured and deliberately
skipped: the harness fix already exceeded their projected savings and a
beforeAll seed would weaken the clean-DB-per-test contract.
Verified: 287 tests green across the touched files plus untouched
harness consumers (schema-applier 80/80, mission-store 62/62).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-08-15 20:58:35 -07:00
parent ea90eeed1c
commit 0935e27902
5 changed files with 288 additions and 464 deletions

View File

@@ -671,6 +671,104 @@ function ensureSchemaTemplate(): Promise<string> {
return ready;
}
/*
* FNXC:PgTestTemplateDb 2026-07-17-22:34:
* PostgreSQL can retain a just-closed baseline connection briefly. Terminate
* stale template sessions immediately before copying; the module-local copy
* mutex ensures this never interrupts a sibling copy using the same source.
*
* FNXC:PgTestHarness 2026-07-18-17:40:
* Keep terminate + DROP + CREATE TEMPLATE on one maintenance session and
* retry the short "source database is being accessed by other users" window
* (seen after switching admin DDL off shell psql). Split sessions left a race
* where a late-closing baseline/pool client reattached between terminate and
* CREATE DATABASE ... TEMPLATE.
*
* FNXC:PgTestHarnessBaselinedDb 2026-08-15-03:52:
* Extracted from createTaskStoreForTest so createBaselinedPgTestDatabase can
* share the identical serialized, retried clone path.
*/
async function cloneDatabaseFromTemplate(dbName: string, template: string): Promise<void> {
await serializeTemplateCopy(async () => {
const maxAttempts = 5;
let lastError: unknown;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await withMaintenanceSql(async (client) => {
await client`
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = ${template} AND pid <> pg_backend_pid()
`;
await client.unsafe(`DROP DATABASE IF EXISTS "${dbName}" WITH (FORCE)`).catch(() => {});
await client.unsafe(`CREATE DATABASE "${dbName}" TEMPLATE "${template}"`);
});
lastError = undefined;
break;
} catch (error) {
lastError = error;
const message = error instanceof Error ? error.message : String(error);
const contended = /being accessed by other users/i.test(message);
if (!contended || attempt === maxAttempts) {
throw error;
}
await new Promise((resolve) => setTimeout(resolve, 25 * attempt));
}
}
if (lastError) throw lastError;
});
}
/*
FNXC:PgTestHarnessBaselinedDb 2026-08-15-03:52:
Slow-test fix for tests that need a baselined DATABASE but no TaskStore (e.g.
sqlite-migrator.test.ts, whose 43 integration tests each paid a fresh CREATE
DATABASE plus a full in-migrator applySchemaBaseline DDL run — ~3.5s/test).
Cloning from the run-shared golden template yields a database with the exact
applySchemaBaseline end-state (schema + markers), so the migrator's own
idempotent baseline call becomes a marker-check no-op. Callers own connections
to the returned URL; drop() force-drops the database.
*/
export async function createBaselinedPgTestDatabase(prefix = "fusion_test"): Promise<{
readonly dbName: string;
readonly testUrl: string;
drop(): Promise<void>;
}> {
const dbName = uniqueDbName(prefix);
const template = await ensureGoldenTemplate();
await cloneDatabaseFromTemplate(dbName, template);
return {
dbName,
testUrl: `${PG_TEST_URL_BASE}/${dbName}`,
drop: async () => {
await adminExecAsync(`DROP DATABASE IF EXISTS "${dbName}" WITH (FORCE)`);
},
};
}
/*
FNXC:PgTestHarnessBaselinedDb 2026-08-15-03:52:
Companion to createBaselinedPgTestDatabase for tests whose CONTRACT is a
pristine target (e.g. VAL-MIGRATE-005: a dry-run against an external database
must leave no schemas/tables/markers behind — pre-applied baseline would make
that assertion vacuous). Plain CREATE DATABASE, no template, no baseline.
*/
export async function createEmptyPgTestDatabase(prefix = "fusion_test"): Promise<{
readonly dbName: string;
readonly testUrl: string;
drop(): Promise<void>;
}> {
const dbName = uniqueDbName(prefix);
await adminExecAsync(`CREATE DATABASE "${dbName}"`);
return {
dbName,
testUrl: `${PG_TEST_URL_BASE}/${dbName}`,
drop: async () => {
await adminExecAsync(`DROP DATABASE IF EXISTS "${dbName}" WITH (FORCE)`);
},
};
}
/**
* FNXC:TestMigrationTail 2026-06-24-16:00:
* Create a fresh, isolated PostgreSQL database with the Fusion schema applied,
@@ -721,47 +819,7 @@ export async function createTaskStoreForTest(options?: {
const template = options?.copyFromGolden
? await ensureGoldenTemplate()
: await ensureSchemaTemplate();
await serializeTemplateCopy(async () => {
/*
* FNXC:PgTestTemplateDb 2026-07-17-22:34:
* PostgreSQL can retain a just-closed baseline connection briefly. Terminate
* stale template sessions immediately before copying; the module-local copy
* mutex ensures this never interrupts a sibling copy using the same source.
*
* FNXC:PgTestHarness 2026-07-18-17:40:
* Keep terminate + DROP + CREATE TEMPLATE on one maintenance session and
* retry the short "source database is being accessed by other users" window
* (seen after switching admin DDL off shell psql). Split sessions left a race
* where a late-closing baseline/pool client reattached between terminate and
* CREATE DATABASE ... TEMPLATE.
*/
const maxAttempts = 5;
let lastError: unknown;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await withMaintenanceSql(async (client) => {
await client`
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = ${template} AND pid <> pg_backend_pid()
`;
await client.unsafe(`DROP DATABASE IF EXISTS "${dbName}" WITH (FORCE)`).catch(() => {});
await client.unsafe(`CREATE DATABASE "${dbName}" TEMPLATE "${template}"`);
});
lastError = undefined;
break;
} catch (error) {
lastError = error;
const message = error instanceof Error ? error.message : String(error);
const contended = /being accessed by other users/i.test(message);
if (!contended || attempt === maxAttempts) {
throw error;
}
await new Promise((resolve) => setTimeout(resolve, 25 * attempt));
}
}
if (lastError) throw lastError;
});
await cloneDatabaseFromTemplate(dbName, template);
const testUrl = `${PG_TEST_URL_BASE}/${dbName}`;
// The database already carries the full schema (copied from the template),
@@ -945,6 +1003,48 @@ const ALL_APPLICATION_TABLES = [
];
const TRUNCATE_ALL_SQL = `TRUNCATE TABLE ${ALL_APPLICATION_TABLES.join(", ")} RESTART IDENTITY CASCADE`;
/*
FNXC:PgTestHarnessResetSpeed 2026-08-15-03:52:
Slow-test fix (harness-wide): the per-test reset was a single TRUNCATE ... RESTART
IDENTITY CASCADE over all ~110 application tables. Profiled at 163ms of the 169ms
shared-harness beforeEach (mission-store.pg: 62 tests -> ~10s of pure TRUNCATE),
because TRUNCATE pays a per-table constant (new relfilenode + catalog churn +
fsync) regardless of row count — and in a typical test only a handful of tables
hold rows. Replace it with one DO block that:
1. switches session_replication_role to 'replica' (transaction-local) so FK
triggers are inert and deletion order is irrelevant;
2. DELETEs only tables that actually contain rows (EXISTS probe per table is
~0.05ms; empty tables are skipped entirely);
3. resets EVERY sequence in the three application schemas to its declared
start value, reproducing RESTART IDENTITY exactly (including sequences
advanced by insert-then-delete tests whose tables ended empty — the full
TRUNCATE reset those too, so the sweep must be unconditional).
Observable semantics are identical: every application table is empty and every
identity restarts, so ID-reuse assertions (KB-001) keep holding. The caller
falls back to the legacy full TRUNCATE if this fast path errors (e.g. a
non-superuser test role that may not set session_replication_role).
*/
const FAST_RESET_SQL = `DO $fusion_reset$
DECLARE
tbl text;
has_rows boolean;
BEGIN
PERFORM set_config('session_replication_role', 'replica', true);
FOREACH tbl IN ARRAY ARRAY[${ALL_APPLICATION_TABLES.map((t) => `'${t}'`).join(", ")}] LOOP
EXECUTE 'SELECT EXISTS(SELECT 1 FROM ' || tbl || ')' INTO has_rows;
IF has_rows THEN
EXECUTE 'DELETE FROM ' || tbl;
END IF;
END LOOP;
PERFORM setval(c.oid, s.seqstart, false)
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_sequence s ON s.seqrelid = c.oid
WHERE c.relkind = 'S'
AND n.nspname IN ('${PROJECT_SCHEMA}', '${CENTRAL_SCHEMA}', '${ARCHIVE_SCHEMA}');
END
$fusion_reset$`;
export function createSharedPgTaskStoreTestHarness(options?: {
readonly poolMax?: number;
readonly prefix?: string;
@@ -1036,7 +1136,13 @@ export function createSharedPgTaskStoreTestHarness(options?: {
beforeEach: async () => {
if (!harness || !store) throw new Error("SharedPgTaskStoreHarness: beforeAll not called yet");
// Wipe all application data and reset sequences in one statement.
await harness.adminDb.execute(sql.raw(TRUNCATE_ALL_SQL));
// FNXC:PgTestHarnessResetSpeed 2026-08-15-03:52: fast DELETE-based reset
// (see FAST_RESET_SQL) with the legacy full TRUNCATE as an error fallback.
try {
await harness.adminDb.execute(sql.raw(FAST_RESET_SQL));
} catch {
await harness.adminDb.execute(sql.raw(TRUNCATE_ALL_SQL));
}
// Re-seed the singleton config row with default project settings so the
// store sees a clean project on every test.
const defaults = await ensureDefaults();

View File

@@ -28,11 +28,8 @@
* gate stays green without a running server.
*/
import { describe, it, expect, afterEach, beforeAll } from "vitest";
import postgres from "postgres";
import { drizzle } from "drizzle-orm/postgres-js";
import { it, expect, beforeAll, beforeEach, afterEach, afterAll } from "vitest";
import { sql } from "drizzle-orm";
import { execSync } from "node:child_process";
import {
createAsyncDataLayer,
recordRunAuditEvent,
@@ -42,74 +39,55 @@ import {
} from "../../postgres/data-layer.js";
import { createConnectionSetFromUrl } from "../../postgres/connection.js";
import type { ResolvedBackend } from "../../postgres/backend-resolver.js";
import { applySchemaBaseline } from "../../postgres/schema-applier.js";
import * as schema from "../../postgres/schema/index.js";
const PG_ADMIN_URL =
process.env.FUSION_PG_TEST_ADMIN_URL ?? "postgresql://localhost:5432/postgres";
const PG_TEST_URL_BASE =
process.env.FUSION_PG_TEST_URL_BASE ?? "postgresql://localhost:5432";
const PG_AVAILABLE =
process.env.FUSION_PG_TEST_SKIP !== "1" && Boolean(PG_TEST_URL_BASE);
const pgDescribe = PG_AVAILABLE ? describe : describe.skip;
/**
* FNXC:AsyncDataLayer 2026-06-24-10:00:
* Create a uniquely-named fresh database for each test so tests are hermetic
* and never touch existing data. Mirrors the schema-applier test harness.
*/
function uniqueDbName(): string {
return `fusion_data_test_${process.pid}_${Math.random().toString(36).slice(2, 8)}`;
}
import {
pgDescribe,
createSharedPgTaskStoreTestHarness,
type SharedPgTaskStoreHarness,
} from "../../__test-utils__/pg-test-harness.js";
/*
FNXC:PgTestAuthFix 2026-07-14-00:00:
The inline adminExec used process.env.USER for the psql -U flag, which is 'runner' on GitHub Actions (not 'postgres'). Use the PG_TEST_URL_BASE connection string instead so credentials are always correct.
FNXC:AsyncDataLayer 2026-08-15-03:52:
Slow-test fix: this file hand-rolled CREATE DATABASE + full applySchemaBaseline
PER TEST (~4.6s/test, 65s for the file). The transaction primitives under test
write only data, so each describe block now shares one golden-template database
with the harness's per-test reset. Transaction-visibility semantics are
unchanged: the layer pool and the harness adminDb connection remain SEPARATE
sessions, which is what the VAL-DATA-004 concurrent-reader assertions rely on.
The close() lifecycle test builds a PRIVATE layer against the shared database
so closing it cannot break the harness's pooled layer for later tests.
`ctx` keeps its original shape so test bodies stay byte-identical.
*/
function adminExec(statement: string): void {
// psql via execSync for DDL that the postgres.js connection pool can't run
// (CREATE/DROP DATABASE cannot run inside a transaction). Short deterministic
// DDL — the acceptable execSync use per AGENTS.md.
execSync(
`psql "${PG_TEST_URL_BASE}/postgres" -v ON_ERROR_STOP=1 -c "${statement.replace(/"/g, '\\"')}"`,
{ stdio: "pipe", env: process.env },
);
}
interface TestLayer {
dbName: string;
testUrl: string;
layer: AsyncDataLayer;
adminSql: ReturnType<typeof postgres>;
adminDb: ReturnType<typeof drizzle>;
readonly layer: AsyncDataLayer;
readonly adminDb: ReturnType<SharedPgTaskStoreHarness["adminDb"]>;
}
async function setupFreshLayer(): Promise<TestLayer> {
const dbName = uniqueDbName();
try {
adminExec(`DROP DATABASE IF EXISTS "${dbName}"`);
} catch {
// ignore — may not exist
}
adminExec(`CREATE DATABASE "${dbName}"`);
const testUrl = `${PG_TEST_URL_BASE}/${dbName}`;
const h = createSharedPgTaskStoreTestHarness({ prefix: "fusion_data" });
// Apply the baseline schema so run_audit_events + tasks exist.
const schemaBackend: ResolvedBackend = {
mode: "external",
runtimeUrl: testUrl,
migrationUrl: testUrl,
migrationUrlOverridden: false,
/**
* Register the shared-harness lifecycle inside a pgDescribe block and return a
* `ctx` whose `layer`/`adminDb` getters resolve live from the harness, so the
* original `ctx.layer` / `ctx.adminDb` test bodies read unchanged.
*/
function useSharedLayer(): TestLayer {
beforeAll(h.beforeAll);
beforeEach(h.beforeEach);
afterEach(h.afterEach);
afterAll(h.afterAll);
return {
get layer() {
return h.layer();
},
get adminDb() {
return h.adminDb();
},
};
const schemaConnections = await createConnectionSetFromUrl(schemaBackend, {
poolMax: 1,
connectTimeoutSeconds: 5,
});
await applySchemaBaseline(schemaConnections.migration);
await schemaConnections.close();
}
// Now build the data layer against the migrated database.
/** Build a private AsyncDataLayer against the shared harness database. */
async function createPrivateLayer(): Promise<AsyncDataLayer> {
const testUrl = h.testUrl();
const dataBackend: ResolvedBackend = {
mode: "external",
runtimeUrl: testUrl,
@@ -120,32 +98,7 @@ async function setupFreshLayer(): Promise<TestLayer> {
poolMax: 5,
connectTimeoutSeconds: 5,
});
const layer = createAsyncDataLayer(connections);
// Admin connection for direct row inspection (outside the data layer).
const adminSql = postgres(testUrl, { max: 2, prepare: false, onnotice: () => {} });
const adminDb = drizzle(adminSql);
return { dbName, testUrl, layer, adminSql, adminDb };
}
async function teardownLayer(ctx: TestLayer | null): Promise<void> {
if (!ctx) return;
try {
await ctx.layer.close();
} catch {
// best-effort
}
try {
await ctx.adminSql.end({ timeout: 5 });
} catch {
// best-effort
}
try {
adminExec(`DROP DATABASE IF EXISTS "${ctx.dbName}"`);
} catch {
// best-effort
}
return createAsyncDataLayer(connections);
}
/** Count rows in project.run_audit_events via the admin connection. */
@@ -168,15 +121,9 @@ async function readAuditRows(
}
pgDescribe("AsyncDataLayer: VAL-DATA-002 — transaction atomicity (commit)", () => {
let ctx: TestLayer | null = null;
afterEach(async () => {
await teardownLayer(ctx);
ctx = null;
});
const ctx = useSharedLayer();
it("commits a multi-statement mutation with all writes visible after commit", async () => {
ctx = await setupFreshLayer();
const runId = "run-commit-multi";
const auditA: RunAuditEventInput = {
runId,
@@ -207,7 +154,6 @@ pgDescribe("AsyncDataLayer: VAL-DATA-002 — transaction atomicity (commit)", ()
});
it("transactionImmediate with a single write commits it", async () => {
ctx = await setupFreshLayer();
const runId = "run-commit-single";
await ctx.layer.transactionImmediate(async (tx) => {
await recordRunAuditEventWithinTransaction(tx, {
@@ -225,15 +171,9 @@ pgDescribe("AsyncDataLayer: VAL-DATA-002 — transaction atomicity (commit)", ()
});
pgDescribe("AsyncDataLayer: VAL-DATA-003 — transaction atomicity (rollback)", () => {
let ctx: TestLayer | null = null;
afterEach(async () => {
await teardownLayer(ctx);
ctx = null;
});
const ctx = useSharedLayer();
it("rolls back all writes when the callback throws, including the audit row", async () => {
ctx = await setupFreshLayer();
const runId = "run-rollback-throw";
const before = await countAuditRows(ctx.adminDb);
expect(before).toBe(0);
@@ -259,7 +199,6 @@ pgDescribe("AsyncDataLayer: VAL-DATA-003 — transaction atomicity (rollback)",
});
it("rolls back when a constraint is violated mid-transaction (primary-key collision)", async () => {
ctx = await setupFreshLayer();
const runId = "run-rollback-pk";
const before = await countAuditRows(ctx.adminDb);
expect(before).toBe(0);
@@ -313,15 +252,9 @@ pgDescribe("AsyncDataLayer: VAL-DATA-003 — transaction atomicity (rollback)",
});
pgDescribe("AsyncDataLayer: VAL-DATA-004 — concurrent transactions do not observe partial writes", () => {
let ctx: TestLayer | null = null;
afterEach(async () => {
await teardownLayer(ctx);
ctx = null;
});
const ctx = useSharedLayer();
it("a concurrent reader outside the writer's transaction does not see uncommitted writes", async () => {
ctx = await setupFreshLayer();
const runId = "run-concurrent-iso";
// Hold a transaction open with an uncommitted write, then verify a
@@ -349,7 +282,6 @@ pgDescribe("AsyncDataLayer: VAL-DATA-004 — concurrent transactions do not obse
});
it("a concurrent read via a separate pool transaction does not see uncommitted writes", async () => {
ctx = await setupFreshLayer();
const runId = "run-concurrent-iso-2";
// Use a barrier to coordinate: the writer holds its transaction open until
@@ -378,7 +310,6 @@ pgDescribe("AsyncDataLayer: VAL-DATA-004 — concurrent transactions do not obse
});
it("two concurrent writers both commit their own rows without cross-contamination", async () => {
ctx = await setupFreshLayer();
const runA = "run-concurrent-A";
const runB = "run-concurrent-B";
@@ -413,15 +344,9 @@ pgDescribe("AsyncDataLayer: VAL-DATA-004 — concurrent transactions do not obse
});
pgDescribe("AsyncDataLayer: run-audit-event-within-transaction behavior", () => {
let ctx: TestLayer | null = null;
afterEach(async () => {
await teardownLayer(ctx);
ctx = null;
});
const ctx = useSharedLayer();
it("the standalone recordRunAuditEvent wraps the insert in its own transaction", async () => {
ctx = await setupFreshLayer();
const event = await recordRunAuditEvent(ctx.layer, {
runId: "run-standalone",
agentId: "agent-standalone",
@@ -440,7 +365,6 @@ pgDescribe("AsyncDataLayer: run-audit-event-within-transaction behavior", () =>
});
it("records metadata as jsonb and round-trips it", async () => {
ctx = await setupFreshLayer();
const metadata = { filesChanged: 5, nested: { deep: [1, 2, 3] }, flag: true };
await recordRunAuditEvent(ctx.layer, {
runId: "run-metadata",
@@ -459,7 +383,6 @@ pgDescribe("AsyncDataLayer: run-audit-event-within-transaction behavior", () =>
});
it("an audit row paired with a task-like mutation rolls back together", async () => {
ctx = await setupFreshLayer();
const runId = "run-paired-rollback";
// Simulate the atomicWriteTaskJsonWithAudit pattern: a "task mutation"
@@ -495,20 +418,13 @@ pgDescribe("AsyncDataLayer: run-audit-event-within-transaction behavior", () =>
});
pgDescribe("AsyncDataLayer: interface stability and connectivity", () => {
let ctx: TestLayer | null = null;
afterEach(async () => {
await teardownLayer(ctx);
ctx = null;
});
const ctx = useSharedLayer();
it("ping() succeeds against a healthy backend", async () => {
ctx = await setupFreshLayer();
await expect(ctx.layer.ping()).resolves.toBeUndefined();
});
it("the db member executes a raw query", async () => {
ctx = await setupFreshLayer();
const result = (await ctx.layer.db.execute(
sql`SELECT 1 AS val`,
)) as unknown as Array<{ val: number }>;
@@ -516,26 +432,14 @@ pgDescribe("AsyncDataLayer: interface stability and connectivity", () => {
});
it("close() releases the pool without error", async () => {
ctx = await setupFreshLayer();
await expect(ctx.layer.close()).resolves.toBeUndefined();
// Prevent teardownLayer from double-closing.
const captured = ctx;
ctx = null;
// The admin connection is still ours to close.
try {
await captured!.adminSql.end({ timeout: 5 });
} catch {
// best-effort
}
try {
adminExec(`DROP DATABASE IF EXISTS "${captured!.dbName}"`);
} catch {
// best-effort
}
// FNXC:AsyncDataLayer 2026-08-15-03:52: close a PRIVATE layer built
// against the shared database — closing the harness's pooled layer would
// break every later test in the file.
const layer = await createPrivateLayer();
await expect(layer.close()).resolves.toBeUndefined();
});
it("exposes the stable AsyncDataLayer contract (db, transaction, transactionImmediate, ping, close)", async () => {
ctx = await setupFreshLayer();
expect(typeof ctx.layer.db).toBe("object");
expect(typeof ctx.layer.transaction).toBe("function");
expect(typeof ctx.layer.transactionImmediate).toBe("function");

View File

@@ -28,7 +28,6 @@ import { describe, it, expect, afterEach, beforeEach } from "vitest";
import postgres from "postgres";
import { drizzle } from "drizzle-orm/postgres-js";
import { sql } from "drizzle-orm";
import { execSync } from "node:child_process";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
@@ -43,6 +42,10 @@ import {
type MigrationProgressEvent,
} from "../../postgres/sqlite-migrator.js";
import { applySchemaBaseline } from "../../postgres/schema-applier.js";
import {
createBaselinedPgTestDatabase,
createEmptyPgTestDatabase,
} from "../../__test-utils__/pg-test-harness.js";
const PG_TEST_URL_BASE =
process.env.FUSION_PG_TEST_URL_BASE ?? "postgresql://localhost:5432";
@@ -80,25 +83,13 @@ describe("SQLite migration CLI progress", () => {
});
});
/**
* FNXC:PostgresMigration 2026-06-24-09:05:
* Create a uniquely-named fresh PostgreSQL database. Mirrors the
* schema-applier test harness.
*/
function uniqueDbName(): string {
return `fusion_migrate_test_${process.pid}_${Math.random().toString(36).slice(2, 8)}`;
}
/*
FNXC:PgTestAuthFix 2026-07-14-00:00:
The inline adminExec used process.env.USER for the psql -U flag, which is 'runner' on GitHub Actions (not 'postgres'). Use the PG_TEST_URL_BASE connection string instead so credentials are always correct.
FNXC:PostgresMigration 2026-08-15-03:52:
Database naming/creation/drop now lives in the shared pg-test-harness
(createBaselinedPgTestDatabase); the former inline psql adminExec (see
FNXC:PgTestAuthFix 2026-07-14-00:00) is gone with it, which also removes this
file's only execSync shellout.
*/
function adminExec(statement: string): void {
execSync(
`psql "${PG_TEST_URL_BASE}/postgres" -v ON_ERROR_STOP=1 -c "${statement.replace(/"/g, '\\"')}"`,
{ stdio: "pipe", env: process.env },
);
}
/** A subset of the tasks table schema (the columns the migration tests touch). */
const TASKS_SQLITE_DDL = `
@@ -440,24 +431,28 @@ interface TestCtx {
sqlConn: ReturnType<typeof postgres>;
db: ReturnType<typeof drizzle>;
fusionDir: string;
dropDb: () => Promise<void>;
}
/*
FNXC:PostgresMigration 2026-08-15-03:52:
Slow-test fix: the per-test target used to be an EMPTY database, so every test
paid the migrator's full internal applySchemaBaseline DDL run (~3s). Clone the
target from the harness's run-shared golden template instead — that template IS
the applySchemaBaseline end-state (schema + version markers), so the migrator's
idempotent baseline call degrades to a marker-check no-op while every assertion,
including the explicit applySchemaBaseline() calls below, sees the identical
target state.
*/
async function setupCtx(): Promise<TestCtx> {
const fusionDir = mkdtempSync(join(tmpdir(), "fusion-migrate-"));
buildPopulatedSqliteProject(fusionDir);
buildPopulatedSqliteArchive(fusionDir);
const dbName = uniqueDbName();
try {
adminExec(`DROP DATABASE IF EXISTS "${dbName}"`);
} catch {
// ignore
}
adminExec(`CREATE DATABASE "${dbName}"`);
const testUrl = `${PG_TEST_URL_BASE}/${dbName}`;
const sqlConn = postgres(testUrl, { max: 3, prepare: false, onnotice: () => {} });
const baselined = await createBaselinedPgTestDatabase("fusion_migrate_test");
const sqlConn = postgres(baselined.testUrl, { max: 3, prepare: false, onnotice: () => {} });
const db = drizzle(sqlConn);
return { dbName, sqlConn, db, fusionDir };
return { dbName: baselined.dbName, sqlConn, db, fusionDir, dropDb: baselined.drop };
}
async function teardownCtx(ctx: TestCtx | null): Promise<void> {
@@ -468,7 +463,7 @@ async function teardownCtx(ctx: TestCtx | null): Promise<void> {
// best-effort
}
try {
adminExec(`DROP DATABASE IF EXISTS "${ctx.dbName}"`);
await ctx.dropDb();
} catch {
// best-effort
}
@@ -1876,8 +1871,18 @@ pgDescribe("SQLite-to-PostgreSQL migrator", () => {
// VAL-MIGRATE-005 — dry-run reports without writing
it("dry-run reports the plan without modifying PostgreSQL", async () => {
/*
FNXC:PostgresMigration 2026-08-15-03:52:
This test's contract is a PRISTINE external target (no schemas, no marker
table), so it cannot use the shared golden-template ctx.db whose baseline is
pre-applied — it provisions its own empty database.
*/
const empty = await createEmptyPgTestDatabase("fusion_migrate_pristine");
const emptyConn = postgres(empty.testUrl, { max: 3, prepare: false, onnotice: () => {} });
const emptyDb = drizzle(emptyConn);
try {
const report = await migrateTest(
ctx!.db,
emptyDb,
[{ sqlitePath: join(ctx!.fusionDir, "fusion.db"), pgSchema: "project" as const }],
{ dryRun: true },
);
@@ -1892,7 +1897,7 @@ pgDescribe("SQLite-to-PostgreSQL migrator", () => {
FNXC:PostgresMigration 2026-07-14-23:47:
VAL-MIGRATE-005 applies to catalog state as well as copied rows. A preview against a pristine external target must leave no schemas, tables, or migration marker behind after it reports the plan.
*/
const catalog = (await ctx!.db.execute(sql`
const catalog = (await emptyDb.execute(sql`
SELECT
to_regnamespace('project')::text AS project_schema,
to_regclass('project.tasks')::text AS tasks_table,
@@ -1913,6 +1918,10 @@ pgDescribe("SQLite-to-PostgreSQL migrator", () => {
// No sequences should have been bumped in dry-run.
expect(report.sequenceBumps).toHaveLength(0);
} finally {
await emptyConn.end({ timeout: 5 }).catch(() => {});
await empty.drop().catch(() => {});
}
});
// VAL-SEARCH-002 (search_vector population) — generated column auto-populates

View File

@@ -20,15 +20,13 @@
* gate stays green without a running server.
*/
import { describe, it, expect, afterEach } from "vitest";
import postgres from "postgres";
import { drizzle } from "drizzle-orm/postgres-js";
import { sql, eq } from "drizzle-orm";
import { execSync } from "node:child_process";
import { createAsyncDataLayer, type AsyncDataLayer } from "../../postgres/data-layer.js";
import { createConnectionSetFromUrl } from "../../postgres/connection.js";
import type { ResolvedBackend } from "../../postgres/backend-resolver.js";
import { applySchemaBaseline } from "../../postgres/schema-applier.js";
import { it, expect, beforeAll, beforeEach, afterEach, afterAll } from "vitest";
import { eq } from "drizzle-orm";
import type { AsyncDataLayer } from "../../postgres/data-layer.js";
import {
pgDescribe,
createSharedPgTaskStoreTestHarness,
} from "../../__test-utils__/pg-test-harness.js";
import * as schema from "../../postgres/schema/index.js";
import { insertTaskRow, softDeleteTaskRow } from "../../task-store/async/async-persistence.js";
import {
@@ -49,87 +47,16 @@ import {
import { recordRunAuditEventWithinTransaction } from "../../postgres/data-layer.js";
import type { MergeQueueRow } from "../../task-store/row-types.js";
const PG_TEST_URL_BASE =
process.env.FUSION_PG_TEST_URL_BASE ?? "postgresql://localhost:5432";
const PG_AVAILABLE =
process.env.FUSION_PG_TEST_SKIP !== "1" && Boolean(PG_TEST_URL_BASE);
const pgDescribe = PG_AVAILABLE ? describe : describe.skip;
function uniqueDbName(): string {
return `fusion_u13_test_${process.pid}_${Math.random().toString(36).slice(2, 8)}`;
}
/*
FNXC:PgTestAuthFix 2026-07-14-00:00:
The inline adminExec used process.env.USER for the psql -U flag, which is 'runner' on GitHub Actions (not 'postgres'). Use the PG_TEST_URL_BASE connection string instead so credentials are always correct.
FNXC:TaskStoreLifecycle 2026-08-15-03:52:
Slow-test fix: this file hand-rolled CREATE DATABASE + full applySchemaBaseline
PER TEST (~4.4s/test, 70s for the file). The helpers under test write only data
(no DDL), so the shared per-file harness (one golden-template DB + per-test
reset) preserves isolation with the schema built once. `ctx` keeps its original
shape so every test body is byte-identical.
*/
function adminExec(statement: string): void {
execSync(
`psql "${PG_TEST_URL_BASE}/postgres" -v ON_ERROR_STOP=1 -c "${statement.replace(/"/g, '\\"')}"`,
{ stdio: "pipe", env: process.env },
);
}
interface TestCtx {
dbName: string;
testUrl: string;
layer: AsyncDataLayer;
adminSql: ReturnType<typeof postgres>;
adminDb: ReturnType<typeof drizzle>;
}
async function setupCtx(): Promise<TestCtx> {
const dbName = uniqueDbName();
try {
adminExec(`DROP DATABASE IF EXISTS "${dbName}"`);
} catch {
// may not exist
}
adminExec(`CREATE DATABASE "${dbName}"`);
const testUrl = `${PG_TEST_URL_BASE}/${dbName}`;
const schemaBackend: ResolvedBackend = {
mode: "external",
runtimeUrl: testUrl,
migrationUrl: testUrl,
migrationUrlOverridden: false,
};
const schemaConnections = await createConnectionSetFromUrl(schemaBackend, {
poolMax: 1,
connectTimeoutSeconds: 5,
});
await applySchemaBaseline(schemaConnections.migration);
await schemaConnections.close();
const connections = await createConnectionSetFromUrl(schemaBackend, {
poolMax: 5,
connectTimeoutSeconds: 5,
});
const layer = createAsyncDataLayer(connections);
const adminSql = postgres(testUrl, { max: 2, prepare: false, onnotice: () => {} });
const adminDb = drizzle(adminSql);
return { dbName, testUrl, layer, adminSql, adminDb };
}
async function teardownCtx(ctx: TestCtx | null): Promise<void> {
if (!ctx) return;
try {
await ctx.layer.close();
} catch {
// best-effort
}
try {
await ctx.adminSql.end({ timeout: 5 });
} catch {
// best-effort
}
try {
adminExec(`DROP DATABASE IF EXISTS "${ctx.dbName}"`);
} catch {
// best-effort
}
}
/** A minimal task record with the NOT NULL columns filled. */
@@ -162,17 +89,20 @@ async function seedTaskWithParent(
}
pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
let ctx: TestCtx | null = null;
const h = createSharedPgTaskStoreTestHarness({ prefix: "fusion_u13" });
let ctx!: TestCtx;
afterEach(async () => {
await teardownCtx(ctx);
ctx = null;
beforeAll(h.beforeAll);
beforeEach(async () => {
await h.beforeEach();
ctx = { layer: h.layer() };
});
afterEach(h.afterEach);
afterAll(h.afterAll);
// ── VAL-DATA-010: Lineage-integrity gate blocks parent delete with live children ──
it("findLiveLineageChildren returns live children of a parent (VAL-DATA-010)", async () => {
ctx = await setupCtx();
// Parent + two live children + one archived child.
await insertTaskRow(ctx.layer, makeMinimalTask("KB-PARENT"), { lineageId: null });
await seedTaskWithParent(ctx.layer, "KB-CHILD-1", "KB-PARENT", "todo");
@@ -186,7 +116,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
});
it("lineage gate blocks parent delete when live children exist (VAL-DATA-010)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-PARENT"), { lineageId: null });
await seedTaskWithParent(ctx.layer, "KB-LIVE", "KB-PARENT", "todo");
@@ -206,7 +135,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
// ── VAL-DATA-011: removeLineageReferences clears children ──
it("removeLineageReferences clears lineage edges so parent can be deleted (VAL-DATA-011)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-PARENT"), { lineageId: null });
await seedTaskWithParent(ctx.layer, "KB-CHILD", "KB-PARENT", "todo");
@@ -247,7 +175,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
// ── VAL-DATA-012: Archived/soft-deleted children do not block parent delete ──
it("archived children do not block parent delete (VAL-DATA-012)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-PARENT"), { lineageId: null });
// An archived child (column = 'archived' but not soft-deleted).
await seedTaskWithParent(ctx.layer, "KB-ARCHIVED", "KB-PARENT", "archived");
@@ -267,7 +194,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
});
it("soft-deleted children do not block parent delete (VAL-DATA-012)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-PARENT"), { lineageId: null });
// A live child that we then soft-delete.
await seedTaskWithParent(ctx.layer, "KB-SOFTDEL", "KB-PARENT", "todo");
@@ -290,7 +216,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
// ── VAL-DATA-013: Handoff-to-review mergeQueue transactional invariant ──
it("handoff-to-review: column move + mergeQueue insert + audit are atomic (VAL-DATA-013)", async () => {
ctx = await setupCtx();
// Seed a task in a non-review column.
await insertTaskRow(ctx.layer, makeMinimalTask("KB-HANDOFF", "in-progress"), {
lineageId: null,
@@ -343,7 +268,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
});
it("handoff-to-review: a failing audit rolls back the column move and queue insert (VAL-DATA-013)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-ROLLBACK", "in-progress"), {
lineageId: null,
});
@@ -400,7 +324,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
// ── VAL-DATA-014: Merge-queue lease semantics ──
it("merge-queue lease is acquired priority-first (urgent before normal)", async () => {
ctx = await setupCtx();
// Seed three tasks in-review, enqueued at slightly different times so the
// priority ordering is deterministic regardless of FIFO tiebreak.
const t0 = "2026-01-01T00:00:00Z";
@@ -443,7 +366,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
});
it("merge-queue lease is FIFO within the same priority", async () => {
ctx = await setupCtx();
const t0 = "2026-01-01T00:00:00Z";
const t1 = "2026-01-01T00:00:01Z";
const t2 = "2026-01-01T00:00:02Z";
@@ -476,7 +398,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
});
it("expired leases recover without incrementing attemptCount (VAL-DATA-014)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-EXPIRE", "in-review"), { lineageId: null });
await enqueueMergeQueue(ctx.layer, "KB-EXPIRE");
@@ -509,7 +430,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
});
it("failure release increments attemptCount, success removes the row (VAL-DATA-014)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-OK", "in-review"), { lineageId: null });
await insertTaskRow(ctx.layer, makeMinimalTask("KB-FAIL", "in-review"), { lineageId: null });
// Enqueue OK first so it is the queue head (FIFO within same priority).
@@ -547,7 +467,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
});
it("release by a non-holder is rejected (ownership check)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-OWN", "in-review"), { lineageId: null });
await enqueueMergeQueue(ctx.layer, "KB-OWN");
await acquireMergeQueueLease(ctx.layer, "worker-1", { leaseDurationMs: 60_000 });
@@ -558,7 +477,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
});
it("cleanupStaleMergeQueueRows removes entries whose task left in-review", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-STALE", "in-review"), { lineageId: null });
await enqueueMergeQueue(ctx.layer, "KB-STALE");
@@ -580,7 +498,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
});
it("enqueue rejects a task not in in-review column", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-REJECT", "todo"), { lineageId: null });
await expect(enqueueMergeQueue(ctx.layer, "KB-REJECT")).rejects.toThrow();
@@ -592,7 +509,6 @@ pgDescribe("U13 taskstore-lifecycle (PostgreSQL)", () => {
});
it("peekMergeQueue orders priority-first then FIFO", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-A", "in-review"), { lineageId: null });
await insertTaskRow(ctx.layer, makeMinimalTask("KB-B", "in-review"), { lineageId: null });
await insertTaskRow(ctx.layer, makeMinimalTask("KB-C", "in-review"), { lineageId: null });

View File

@@ -20,15 +20,13 @@
* gate stays green without a running server.
*/
import { describe, it, expect, afterEach } from "vitest";
import postgres from "postgres";
import { drizzle } from "drizzle-orm/postgres-js";
import { eq, sql } from "drizzle-orm";
import { execSync } from "node:child_process";
import { createAsyncDataLayer, type AsyncDataLayer } from "../../postgres/data-layer.js";
import { createConnectionSetFromUrl } from "../../postgres/connection.js";
import type { ResolvedBackend } from "../../postgres/backend-resolver.js";
import { applySchemaBaseline } from "../../postgres/schema-applier.js";
import { it, expect, beforeAll, beforeEach, afterEach, afterAll } from "vitest";
import { eq } from "drizzle-orm";
import type { AsyncDataLayer } from "../../postgres/data-layer.js";
import {
pgDescribe,
createSharedPgTaskStoreTestHarness,
} from "../../__test-utils__/pg-test-harness.js";
import * as schema from "../../postgres/schema/index.js";
import { insertTaskRow, softDeleteTaskRow } from "../../task-store/async/async-persistence.js";
import {
@@ -95,112 +93,22 @@ import {
countSearchTasksLike,
} from "../../task-store/async/async-search.js";
const PG_TEST_URL_BASE =
process.env.FUSION_PG_TEST_URL_BASE ?? "postgresql://localhost:5432";
const PG_AVAILABLE =
process.env.FUSION_PG_TEST_SKIP !== "1" && Boolean(PG_TEST_URL_BASE);
const pgDescribe = PG_AVAILABLE ? describe : describe.skip;
/** FNXC:MultiProjectIsolation 2026-07-16-00:05: the project every harness row is owned by. */
const TEST_PROJECT_ID = "proj_test_u14";
function uniqueDbName(): string {
return `fusion_u14_test_${process.pid}_${Math.random().toString(36).slice(2, 8)}`;
}
/*
FNXC:PgTestAuthFix 2026-07-14-00:00:
The inline adminExec used process.env.USER for the psql -U flag, which is 'runner' on GitHub Actions (not 'postgres'). Use the PG_TEST_URL_BASE connection string instead so credentials are always correct.
FNXC:TaskStoreRemaining 2026-08-15-03:52:
Slow-test fix: this file hand-rolled CREATE DATABASE + full applySchemaBaseline
PER TEST (~1.9s/test, 52s for the file). The helpers under test write only data,
so the shared per-file harness (one golden-template DB + per-test reset) keeps
isolation with the schema built once. The FNXC:MultiProjectIsolation 2026-07-16
production-shape binding is preserved by passing `projectId: TEST_PROJECT_ID`
to the harness, which threads the `fusion.project_id` GUC into the runtime
connections and the layer exactly as the old inline setup did. `ctx` keeps its
original `{ layer }` shape so test bodies stay byte-identical.
*/
function adminExec(statement: string): void {
execSync(
`psql "${PG_TEST_URL_BASE}/postgres" -v ON_ERROR_STOP=1 -c "${statement.replace(/"/g, '\\"')}"`,
{ stdio: "pipe", env: process.env },
);
}
interface TestCtx {
dbName: string;
testUrl: string;
layer: AsyncDataLayer;
adminSql: ReturnType<typeof postgres>;
adminDb: ReturnType<typeof drizzle>;
}
async function setupCtx(): Promise<TestCtx> {
const dbName = uniqueDbName();
try {
adminExec(`DROP DATABASE IF EXISTS "${dbName}"`);
} catch {
// may not exist
}
adminExec(`CREATE DATABASE "${dbName}"`);
const testUrl = `${PG_TEST_URL_BASE}/${dbName}`;
const schemaBackend: ResolvedBackend = {
mode: "external",
runtimeUrl: testUrl,
migrationUrl: testUrl,
migrationUrlOverridden: false,
};
const schemaConnections = await createConnectionSetFromUrl(schemaBackend, {
poolMax: 1,
connectTimeoutSeconds: 5,
});
await applySchemaBaseline(schemaConnections.migration);
await schemaConnections.close();
/*
FNXC:MultiProjectIsolation 2026-07-16-00:05:
Bind the layer to a project, as production does. createConnectionSetFromUrl sets the
`fusion.project_id` GUC per connection when given a projectId, and falls back to
`fusion.project_bypass=on` when not; an unbound harness therefore ran with RLS bypassed and
wrote blank project_ids that the migration-0006 trigger rewrote to '__legacy_unscoped__', so
helpers scoping on `layer.projectId ?? ""` never found the rows they had just written. That is
a shape production forbids -- AgentStore.backendProjectId throws on an unbound id -- so the
tests, not the product, were wrong.
*/
const connections = await createConnectionSetFromUrl(schemaBackend, {
poolMax: 5,
connectTimeoutSeconds: 5,
projectId: TEST_PROJECT_ID,
});
const layer = createAsyncDataLayer(connections, { projectId: TEST_PROJECT_ID });
/*
FNXC:MultiProjectIsolation 2026-07-16-00:05:
The admin connection seeds and inspects rows the bound layer then reads, so it must sit in the
SAME partition. Without the GUC its writes are blank, the migration-0006 trigger stamps them
__legacy_unscoped__, and the bound layer scoping on TEST_PROJECT_ID cannot see its own fixtures.
*/
const adminSql = postgres(testUrl, {
max: 2,
prepare: false,
onnotice: () => {},
connection: { "fusion.project_id": TEST_PROJECT_ID },
});
const adminDb = drizzle(adminSql);
return { dbName, testUrl, layer, adminSql, adminDb };
}
async function teardownCtx(ctx: TestCtx | null): Promise<void> {
if (!ctx) return;
try {
await ctx.layer.close();
} catch {
// best-effort
}
try {
await ctx.adminSql.end({ timeout: 5 });
} catch {
// best-effort
}
try {
adminExec(`DROP DATABASE IF EXISTS "${ctx.dbName}"`);
} catch {
// best-effort
}
}
/** A minimal task record with the NOT NULL columns filled. */
@@ -217,17 +125,23 @@ function makeMinimalTask(id: string, column = "todo"): Record<string, unknown> {
}
pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
let ctx: TestCtx | null = null;
afterEach(async () => {
await teardownCtx(ctx);
ctx = null;
const h = createSharedPgTaskStoreTestHarness({
prefix: "fusion_u14",
projectId: TEST_PROJECT_ID,
});
let ctx!: TestCtx;
beforeAll(h.beforeAll);
beforeEach(async () => {
await h.beforeEach();
ctx = { layer: h.layer() };
});
afterEach(h.afterEach);
afterAll(h.afterAll);
// ── VAL-CROSS-014: Soft-deleting a child task allows parent deletion ──
it("soft-deleting a child allows parent deletion (VAL-CROSS-014)", async () => {
ctx = await setupCtx();
// Seed a parent + a live child.
await insertTaskRow(ctx.layer, makeMinimalTask("KB-PARENT"), { lineageId: null });
await insertTaskRow(
@@ -259,7 +173,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
// ── VAL-CROSS-015: Archive scopes docs/artifacts out of live views ──
it("archiving a parent scopes documents out of live views but preserves them (VAL-CROSS-015)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-DOC-PARENT"), { lineageId: null });
// Create a document on the live task.
@@ -288,7 +201,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("archiving a parent scopes artifacts out of live views but preserves them (VAL-CROSS-015)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-ART-PARENT"), { lineageId: null });
// Register an artifact on the live task.
@@ -323,7 +235,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
// ── Comments/attachments round-trip on active tasks ──
it("task documents round-trip on active tasks (upsert + read + update)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-DOC-RT"), { lineageId: null });
// Initial create.
@@ -355,7 +266,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("enforces task-document CAS atomically for creates and updates", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-DOC-CAS"), { lineageId: null });
expect(taskDocumentContentHash("line 1\r\nline 2")).toMatch(/^sha256:[0-9a-f]{64}$/);
@@ -452,7 +362,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("artifacts round-trip on active tasks (register + read)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-ART-RT"), { lineageId: null });
const artifact = await insertArtifactRow(ctx.layer, {
@@ -478,7 +387,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("document upsert is rejected against archived tasks", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-ARCH-DOC"), { lineageId: null });
await softDeleteTaskRow(ctx.layer, "KB-ARCH-DOC", new Date().toISOString());
@@ -491,7 +399,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("reads retained archived documents and serializes exactly one additive CAS publisher", async () => {
ctx = await setupCtx();
const taskId = "KB-ARCH-PUBLISH";
const task = makeMinimalTask(taskId);
await insertTaskRow(ctx.layer, task, { lineageId: null });
@@ -578,7 +485,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("rejects malformed, live, missing, and inconsistent archived publication parents", async () => {
ctx = await setupCtx();
const taskId = "KB-ARCH-REJECT";
const task = makeMinimalTask(taskId);
await insertTaskRow(ctx.layer, task, { lineageId: null });
@@ -611,7 +517,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
// ── Audit mutations and run-audit events commit/roll back together ──
it("activity log entries round-trip (record + query)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-ACT"), { lineageId: null });
await recordActivityLogEntry(ctx.layer.db, ctx.layer.projectId ?? "", {
@@ -629,7 +534,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("run-audit events query by taskId", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-AUDIT"), { lineageId: null });
// Record a run-audit event directly.
@@ -656,7 +560,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
// ── Branch groups ──
it("branch groups round-trip (create + read + update + list)", async () => {
ctx = await setupCtx();
const created = await createBranchGroup(ctx.layer.db, {
sourceType: "mission",
sourceId: "miss-1",
@@ -686,7 +589,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("ensureBranchGroupForSource reuses existing group for same branch", async () => {
ctx = await setupCtx();
const g1 = await ensureBranchGroupForSource(
ctx.layer.db,
"mission",
@@ -704,7 +606,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("PR entities round-trip (ensure + update + list active)", async () => {
ctx = await setupCtx();
const created = await ensurePrEntityForSource(ctx.layer.db, {
sourceType: "task",
sourceId: "task-1",
@@ -743,7 +644,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("PR thread outcomes round-trip (record + read)", async () => {
ctx = await setupCtx();
const pr = await ensurePrEntityForSource(ctx.layer.db, {
sourceType: "task",
sourceId: "task-thread",
@@ -761,7 +661,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
// ── Workflow work-items ──
it("workflow work items round-trip (upsert + transition + terminal guard)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-WF"), { lineageId: null });
const item = await upsertWorkflowWorkItem(ctx.layer, {
@@ -790,7 +689,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("workflow work item upsert is idempotent on composite key", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-WF-IDEM"), { lineageId: null });
const item1 = await upsertWorkflowWorkItem(ctx.layer, {
@@ -812,7 +710,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("completion handoff markers round-trip (record + read)", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-HANDOFF"), { lineageId: null });
await recordCompletionHandoff(ctx.layer.db, "KB-HANDOFF", "engine");
@@ -821,7 +718,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("listDueWorkflowWorkItems returns items with expired/null leases", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-DUE"), { lineageId: null });
await upsertWorkflowWorkItem(ctx.layer, {
@@ -839,7 +735,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
// ── Goal citations / usage events / plugin activations ──
it("goal citations dedup on (goalId, surface, sourceRef)", async () => {
ctx = await setupCtx();
const inserted1 = await recordGoalCitations(ctx.layer.db, [
{ goalId: "g1", agentId: "a1", surface: "task_document", sourceRef: "doc:1", snippet: "cite 1" },
]);
@@ -862,7 +757,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("usage events round-trip (emit + query)", async () => {
ctx = await setupCtx();
const inserted = await emitUsageEvent(ctx.layer.db, ctx.layer.projectId ?? "", {
kind: "tool_call",
taskId: "KB-USAGE",
@@ -880,7 +774,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("usage events fail-soft on unknown kind", async () => {
ctx = await setupCtx();
const inserted = await emitUsageEvent(ctx.layer.db, ctx.layer.projectId ?? "", {
// @ts-expect-error — intentionally invalid kind
kind: "bogus_kind",
@@ -889,7 +782,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("plugin activations round-trip (record)", async () => {
ctx = await setupCtx();
const activation = await recordPluginActivation(ctx.layer.db, {
pluginId: "roadmap",
source: "npm",
@@ -902,7 +794,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
// ── Archive snapshots ──
it("archived task snapshots round-trip (upsert + find + list + filter)", async () => {
ctx = await setupCtx();
const entry = {
id: "KB-ARCH-SNAP",
lineageId: "lineage-1",
@@ -937,7 +828,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("searchTasksLike finds tasks by token and respects soft-delete", async () => {
ctx = await setupCtx();
await insertTaskRow(
ctx.layer,
{ ...makeMinimalTask("KB-SEARCH-1"), title: "implement auth" },
@@ -963,7 +853,6 @@ pgDescribe("U14 taskstore-remaining (PostgreSQL)", () => {
});
it("searchTasksLike returns empty for empty queries", async () => {
ctx = await setupCtx();
await insertTaskRow(ctx.layer, makeMinimalTask("KB-EMPTY"), { lineageId: null });
const results = await searchTasksLike(ctx.layer.db, "");