mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-28 20:21:50 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Managed Git workspaces retain task and run obligations until cleanup is safe. > - Activity, expiration, retention and rehome share a run association query. > - That query repeatedly extracted three keys from wide historical JSON contexts. > - This pull request decodes those keys together and projects the six fields its consumers use. > - Native bindings, cleanup vetoes, lifecycle locks and fresh deletion checks stay in place. > - The benefit is less database work while preserving the same cleanup decisions. ## Linked Issues or Issue Description **What happened?** Workspace activity and eligibility queries repeatedly decompress large run contexts. An OR predicate extracts issue, task and workspace compatibility keys separately. The run scope also materializes columns its lifecycle consumers do not read. **Expected behavior** Read the same relevant runs and preserve all deletion and rehome vetoes with less repeated JSON work. Keep existing native, historical and pointer bindings independent. **Steps to reproduce** Run the synthetic PostgreSQL comparison below against baseline `db70c5acd92f50fa37fb4f0dea3da0e7685089a2`. Repeat the small, large-tree and combined controls documented in the PR. Related work: #17464 preserves workspace descendants. #17877 reconstructs native workspace obligations. This change preserves both contracts and adds no scheduling change. ## What Changed - Decode the three legacy context keys together with `jsonb_to_record`. A strict root-object guard preserves scalar, array and null behavior. Independent native and pointer bindings still match. - Materialize the full textual tree-ID set once. This avoids inflated recursive estimates triggering JIT compilation for the correlated decoder. - Project the six run lifecycle columns used by activity, expiration, orphan retention and rehome. - Add binding, lease, malformed-shape and fresh-read regression cases. Add a reproducible four-control benchmark and detailed evidence documentation. ## Verification - Full repository typecheck passed on the integrated final source. - Six relevant suites passed all 209 tests against real PostgreSQL, including the 20 new binding/shape cases, 25 eligibility cases, full Git expiration/restoration, task-directory retention and configuration, and isolated repository rehome. These cover conflicting bindings, company isolation, duplicate admission, pending cleanup, controller leases, non-object contexts and newly active consumers. - PostgreSQL 17.11 comparisons use two warmups and five alternating samples per variant. Each sample runs eight sequential workspace queries. Every complete caller result and six-column relevant-run multiset matches. - Stress fixture: 4,813 runs and about 332 MB of run storage, with unique wide contexts. Last-activity median: 1,071.54 to 743.45 ms per eight workspaces, 1.44 times faster. Closed eligibility: 814.29 to 713.86 ms, 1.14 times faster. Idle eligibility: 721.46 to 481.69 ms, 1.50 times faster. - Small-history control improves 1.23–1.36 times. The 250-descendant stress control improves 1.24–1.30 times. Combined small-history/large-tree activity and idle checks improve 1.19–1.22 times. Combined closed eligibility is neutral: 61.11 to 62.82 ms per eight, about 0.21 ms extra per workspace. - Stress last-activity ranges do not overlap: 920.27–1,111.22 ms before and 689.14–880.40 ms after. Closed-eligibility ranges overlap; its smaller median gain is directional. - Last-activity buffer accesses fall from 113,202 to 38,081. Eligibility run width falls from about 1,414 to 57 planner bytes. All final plans retain one history scan with no JIT. - Reproduce: `pnpm --filter @paperclipai/server exec tsx ../scripts/benchmark-workspace-activity.ts --baseline-ref=db70c5acd92f50fa37fb4f0dea3da0e7685089a2 --docker-postgres=17 --reject-new-jit --output-dir=/tmp/workspace-activity-stress`. - Repeat with `--small`, `--descendants=250`, and both flags, using distinct output directories. Each run creates and removes its own synthetic database. - Full CI passed on `bc4edd700f983e22f093d0a01e2f4c795075b982`: 54 successful checks, one neutral review and two expected Storybook skips, plus successful Snyk status. Greptile is 5/5 on this exact head. There are zero unresolved threads and the merge state is clean. [CI run](https://github.com/paperclipai/paperclip/actions/runs/40530494291). - Full repository typecheck and build passed on the integrated final source. The local full test command was stopped after native PostgreSQL initialization failed repeatedly, producing 16 failures and skipped database suites. It is incomplete, not a pass. Focused real-database suites used disposable Docker PostgreSQL. Current-head CI passed all full gates separately. ## Risks - Cleanup safety depends on complete run association. Tests compare every retained run field and exercise downstream vetoes, not only row counts. - PostgreSQL plan estimates matter. Rejected per-key index probes were up to 56 times slower on a small-history/large-tree control. An ANY variant also regressed. The final shape passes all four controls under default JIT settings. - The final combined eligibility control is neutral. Synthetic SQL timings do not establish deployed latency, provider behavior or actual compute sleep. - Review suggested a CASE/jsonb_typeof root guard as an optional alternative. The strict JSONPath guard is retained because its scalar, nested-array and independent-native behavior passes real PostgreSQL tests and all four measured controls. No correctness defect was identified. A replacement needs its own comparative measurements. - Future consumers that need another run column must extend this explicit projection. - No migration, index, startup job, polling change or deployment is included. ## Model Used OpenAI Codex, configured model `gpt-6.1-sol` with xhigh reasoning, tool use and code execution. Independent blast-radius review also used `gpt-6-astra` and `gpt-6.1-sol`. The runtime context window was not exposed. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
203 lines
16 KiB
TypeScript
203 lines
16 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
import { execFileSync } from "node:child_process";
|
|
import { promises as fs } from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
import { agents, applyPendingMigrations, companies, createDb, executionWorkspaces, issues, startEmbeddedPostgresTestDatabase, type Db } from "../packages/db/src/index.js";
|
|
import * as expiration from "../server/src/services/git-workspace-expiration.js";
|
|
|
|
type Query = { sql: string; params: unknown[] };
|
|
type Workspace = typeof executionWorkspaces.$inferSelect;
|
|
type Plan = { "Node Type": string; "Relation Name"?: string; "Index Name"?: string; "Actual Rows"?: number; "Actual Loops"?: number; "Shared Hit Blocks"?: number; "Shared Read Blocks"?: number; "Temp Read Blocks"?: number; "Temp Written Blocks"?: number; Plans?: Plan[] };
|
|
const baselineRef = process.argv.find(arg => arg.startsWith("--baseline-ref="))?.slice(15);
|
|
assert.ok(baselineRef, "Pass an explicit pre-fix --baseline-ref=<commit>");
|
|
const repositoryRoot = fileURLToPath(new URL("../", import.meta.url));
|
|
const gitRaw = (...args: string[]) => execFileSync("git", args, { encoding: "utf8", cwd: repositoryRoot });
|
|
const git = (...args: string[]) => gitRaw(...args).trim();
|
|
const baselineRevision = git("rev-parse", "--verify", `${baselineRef}^{commit}`);
|
|
const modulePath = "server/src/services/git-workspace-expiration.ts";
|
|
const treatmentSource = await fs.readFile(path.join(repositoryRoot, modulePath), "utf8");
|
|
const sha256 = (source: string) => createHash("sha256").update(source).digest("hex");
|
|
const baselineOnly = process.argv.includes("--baseline-only");
|
|
const dockerPostgres = process.argv.includes("--docker-postgres=17");
|
|
const small = process.argv.includes("--small");
|
|
const runsPerWorkspace = small ? 16 : 600;
|
|
const bindingRows = Math.max(1, Math.floor(runsPerWorkspace / 20));
|
|
const descendantsPerWorkspace = Number(process.argv.find(arg => arg.startsWith("--descendants="))?.slice(14) ?? 1);
|
|
assert.ok(Number.isInteger(descendantsPerWorkspace) && descendantsPerWorkspace >= 1 && descendantsPerWorkspace <= 500,
|
|
"--descendants must be an integer between 1 and 500");
|
|
const measuredSamples = Number(process.argv.find(arg => arg.startsWith("--samples="))?.slice(10) ?? 5);
|
|
const warmups = Number(process.argv.find(arg => arg.startsWith("--warmups="))?.slice(10) ?? 2);
|
|
assert.ok(Number.isInteger(measuredSamples) && measuredSamples >= 1 && measuredSamples <= 10);
|
|
assert.ok(Number.isInteger(warmups) && warmups >= 0 && warmups <= 5);
|
|
const outputDir = process.argv.find(arg => arg.startsWith("--output-dir="))?.slice(13) ?? await fs.mkdtemp(path.join(os.tmpdir(), "workspace-activity-evidence-"));
|
|
const require = createRequire(new URL("../server/package.json", import.meta.url));
|
|
const { sql } = require("drizzle-orm");
|
|
const { PgDialect } = require("drizzle-orm/pg-core");
|
|
const dialect = new PgDialect();
|
|
const now = new Date("2026-10-20T12:00:00Z");
|
|
const old = new Date("2026-10-01T12:00:00Z");
|
|
const keepAlive = setInterval(() => {}, 1000);
|
|
let database: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | undefined;
|
|
let db: Db | undefined;
|
|
let baselineDir: string | undefined;
|
|
|
|
async function startDatabase() {
|
|
if (!dockerPostgres) return startEmbeddedPostgresTestDatabase("workspace-activity-benchmark-");
|
|
const container = `paperclip-workspace-activity-${randomUUID()}`;
|
|
const docker = (...args: string[]) => execFileSync("docker", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
docker("run", "--detach", "--rm", "--name", container, "--publish", "127.0.0.1::5432", "--env", "POSTGRES_USER=paperclip", "--env", "POSTGRES_PASSWORD=paperclip", "--env", "POSTGRES_DB=paperclip", "postgres:17");
|
|
const cleanup = async () => { docker("rm", "--force", container); };
|
|
try {
|
|
let ready = false;
|
|
for (let attempt = 0; attempt < 100; attempt++) {
|
|
try { docker("exec", container, "pg_isready", "-h", "127.0.0.1", "-U", "paperclip"); ready = true; break; } catch { await new Promise(resolve => setTimeout(resolve, 100)); }
|
|
}
|
|
assert.ok(ready, "Disposable Docker PostgreSQL did not become ready");
|
|
const port = docker("port", container, "5432/tcp").split(":").at(-1);
|
|
const connectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`;
|
|
await applyPendingMigrations(connectionString);
|
|
return { connectionString, cleanup };
|
|
} catch (error) { await cleanup(); throw error; }
|
|
}
|
|
|
|
function capture(invoke: (db: Db) => Promise<unknown>): Promise<Query> {
|
|
let captured: Query | undefined;
|
|
const captureDb = new Proxy(db!, { get(target, key) {
|
|
if (key === "execute") return async (query: Parameters<typeof dialect.sqlToQuery>[0]) => { captured = dialect.sqlToQuery(query); return []; };
|
|
return Reflect.get(target, key);
|
|
}});
|
|
return invoke(captureDb).then(() => { assert.ok(captured); return captured; });
|
|
}
|
|
const normalize = (rows: unknown[][]) => rows.map(row => JSON.stringify(row)).sort();
|
|
const summarize = (samples: number[]) => {
|
|
const sorted = [...samples].sort((a, b) => a - b);
|
|
return { samples, medianMs: sorted[Math.floor(sorted.length / 2)], minMs: sorted[0], maxMs: sorted.at(-1) };
|
|
};
|
|
function scans(plan: Plan): unknown[] {
|
|
const flatten = (node: Plan): Plan[] => [node, ...(node.Plans ?? []).flatMap(flatten)];
|
|
return flatten(plan).filter(node => node["Relation Name"] === "heartbeat_runs" || node["Index Name"])
|
|
.map(({ Plans: _children, ...node }) => node);
|
|
}
|
|
|
|
try {
|
|
await fs.mkdir(outputDir, { recursive: true });
|
|
database = await startDatabase();
|
|
db = createDb(database.connectionString, { maxConnections: 1 });
|
|
// Keep historical legacy bindings independent from native bindings; current
|
|
// insert triggers would otherwise rewrite the fixture's issue_id from JSON.
|
|
await db.$client.unsafe("ALTER TABLE heartbeat_runs DISABLE TRIGGER heartbeat_runs_set_scope_kind");
|
|
baselineDir = await fs.mkdtemp(path.join(fileURLToPath(new URL("../server/scripts/", import.meta.url)), ".workspace-activity-baseline-"));
|
|
const original = new URL(`../${modulePath}`, import.meta.url);
|
|
const source = gitRaw("show", `${baselineRevision}:${modulePath}`);
|
|
const rewritten = source.replace(/from "(\.{1,2}\/[^\"]+)"/g,
|
|
(_match, specifier: string) => `from ${JSON.stringify(fileURLToPath(new URL(specifier, original)))}`);
|
|
const baselinePath = path.join(baselineDir, "git-workspace-expiration.ts");
|
|
await fs.writeFile(baselinePath, rewritten);
|
|
await fs.writeFile(path.join(outputDir, "baseline-git-workspace-expiration.ts"), source);
|
|
await fs.writeFile(path.join(outputDir, "treatment-git-workspace-expiration.ts"), treatmentSource);
|
|
const baseline = await import(pathToFileURL(baselinePath).href) as typeof expiration;
|
|
const companyId = randomUUID(), agentId = randomUUID(), otherCompanyId = randomUUID(), otherAgentId = randomUUID(), unrelatedIssueId = randomUUID();
|
|
await db.insert(companies).values([{ id: companyId, name: "Synthetic workspace activity", issuePrefix: "WAB" }, { id: otherCompanyId, name: "Other tenant", issuePrefix: "WAO" }]);
|
|
await db.insert(agents).values([{ id: agentId, companyId, name: "Fixture" }, { id: otherAgentId, companyId: otherCompanyId, name: "Other fixture" }]);
|
|
await db.insert(issues).values({ id: unrelatedIssueId, companyId, title: "Unrelated", updatedAt: old });
|
|
const workspaces: Workspace[] = [];
|
|
for (let n = 0; n < 8; n++) {
|
|
const issueId = randomUUID();
|
|
await db.insert(issues).values({ id: issueId, companyId, title: `Target ${n}`, status: "done", completedAt: old, updatedAt: old });
|
|
const [workspace] = await db.insert(executionWorkspaces).values({ companyId, sourceIssueId: issueId, mode: "isolated_workspace", strategyType: "git_worktree", name: `Target ${n}`, cwd: `/tmp/synthetic-workspace-activity-${n}`, updatedAt: old }).returning();
|
|
workspaces.push(workspace);
|
|
await db.insert(issues).values(Array.from({ length: descendantsPerWorkspace }, (_, child) => ({ companyId, parentId: issueId,
|
|
title: `Child ${n}/${child}`, status: "done", completedAt: old, updatedAt: old })));
|
|
}
|
|
const padding = randomBytes(24 * 1024).toString("base64");
|
|
// Most history is unrelated, as in the reported scan; contexts and results are
|
|
// incompressible and wide enough to live in TOAST. Existing indexes are retained.
|
|
for (let n = 0; n < 8; n++) {
|
|
const workspace = workspaces[n];
|
|
await db.$client.unsafe(`INSERT INTO heartbeat_runs (company_id, agent_id, status, scope_kind, issue_id, native_issue_id, created_at, started_at, finished_at, context_snapshot, result_json)
|
|
SELECT $1::uuid, $2::uuid, 'succeeded', CASE WHEN i <= ${bindingRows} THEN 'issue' ELSE 'company' END,
|
|
CASE WHEN i <= ${bindingRows} THEN $3::uuid END, CASE WHEN i > ${bindingRows} AND i <= ${bindingRows * 2} THEN $3::uuid END,
|
|
$6::timestamptz + i * interval '1 second', $6::timestamptz + i * interval '2 seconds', $6::timestamptz + i * interval '3 seconds',
|
|
jsonb_build_object('issueId', CASE WHEN i > ${bindingRows * 2} AND i <= ${bindingRows * 3} THEN $3::text ELSE $5::text END,
|
|
'taskId', CASE WHEN i > ${bindingRows * 3} AND i <= ${bindingRows * 4} THEN $3::text ELSE $5::text END,
|
|
'executionWorkspaceId', CASE WHEN i > ${bindingRows * 4} AND i <= ${bindingRows * 5} THEN $4::text ELSE 'unrelated' END,
|
|
'fixtureRun', ${n * runsPerWorkspace} + i, 'padding', $7::text),
|
|
jsonb_build_object('stdout', $7::text)
|
|
FROM generate_series(1, ${runsPerWorkspace}) i`, [companyId, agentId, workspace.sourceIssueId, workspace.id, unrelatedIssueId, old.toISOString(), padding]);
|
|
}
|
|
// Malformed JSON shapes and noncanonical IDs must retain ->> semantics, never
|
|
// throw on a cast or accidentally match another tenant's run.
|
|
const unusualContexts = [null, [], [{ issueId: workspaces[0].sourceIssueId }], [[{ issueId: workspaces[0].sourceIssueId }]], true,
|
|
12, "string", { issueId: workspaces[0].sourceIssueId!.toUpperCase() }, { issueId: 12 }, { issueId: { value: workspaces[0].sourceIssueId } },
|
|
{ taskId: workspaces[0].sourceIssueId }, { executionWorkspaceId: workspaces[0].id }];
|
|
for (const context of unusualContexts) {
|
|
await db.$client.unsafe(`INSERT INTO heartbeat_runs (company_id, agent_id, status, created_at, finished_at, context_snapshot) VALUES ($1::uuid, $2::uuid, 'succeeded', $3::timestamptz, $3::timestamptz, $4::jsonb)`, [companyId, agentId, old.toISOString(), JSON.stringify(context)]);
|
|
}
|
|
await db.$client.unsafe(`INSERT INTO heartbeat_runs (company_id, agent_id, status, created_at, context_snapshot) VALUES ($1::uuid, $2::uuid, 'running', $3::timestamptz, $4::jsonb)`, [otherCompanyId, otherAgentId, now.toISOString(), JSON.stringify({ issueId: workspaces[0].sourceIssueId, executionWorkspaceId: workspaces[0].id })]);
|
|
await db.$client.unsafe("ALTER TABLE heartbeat_runs ENABLE TRIGGER heartbeat_runs_set_scope_kind");
|
|
await db.$client.unsafe("ANALYZE");
|
|
const [settings] = await db.$client.unsafe("SELECT version(), current_setting('work_mem') AS work_mem, pg_total_relation_size('heartbeat_runs') AS total_run_bytes");
|
|
const results = [];
|
|
for (const kind of ["lastActivity", "eligibility", "idleEligibility"] as const) {
|
|
const invoke = (implementation: typeof expiration, d: Db, workspace: Workspace) => kind === "lastActivity"
|
|
? implementation.gitWorkspaceLastActivityAt(d, workspace)
|
|
: implementation.isGitWorkspaceExpirationEligible(d as never, workspace, now, kind === "idleEligibility" ? now : undefined);
|
|
const beforeQueries = await Promise.all(workspaces.map(workspace => capture(d => invoke(baseline, d, workspace))));
|
|
const afterQueries = baselineOnly ? beforeQueries : await Promise.all(workspaces.map(workspace => capture(d => invoke(expiration, d, workspace))));
|
|
await fs.writeFile(path.join(outputDir, `${kind}-queries.json`), JSON.stringify({ beforeQueries, afterQueries }, null, 2));
|
|
if (process.argv.includes("--reject-new-jit")) {
|
|
const [beforePlan] = await db.$client.unsafe(`EXPLAIN (FORMAT JSON) ${beforeQueries[0].sql}`, beforeQueries[0].params as never[]);
|
|
const [afterPlan] = await db.$client.unsafe(`EXPLAIN (FORMAT JSON) ${afterQueries[0].sql}`, afterQueries[0].params as never[]);
|
|
await fs.writeFile(path.join(outputDir, `${kind}-preflight-plans.json`), JSON.stringify({ beforePlan, afterPlan }, null, 2));
|
|
assert.ok(!afterPlan["QUERY PLAN"][0].JIT || beforePlan["QUERY PLAN"][0].JIT,
|
|
`${kind}: reject new JIT compilation before repeating a known expensive planner shape`);
|
|
}
|
|
const execute = async (queries: Query[]) => {
|
|
const rows = [];
|
|
const start = performance.now();
|
|
for (const query of queries) rows.push(await db!.$client.unsafe(query.sql, query.params as never[]).values());
|
|
return { ms: performance.now() - start, rows };
|
|
};
|
|
const beforeSamples: number[] = [], afterSamples: number[] = [];
|
|
for (let n = 0; n < (baselineOnly ? 1 : warmups + measuredSamples); n++) {
|
|
const order = n % 2 === 0 ? ["before", "after"] : ["after", "before"];
|
|
const iteration: Record<string, Awaited<ReturnType<typeof execute>>> = {};
|
|
for (const treatment of order) iteration[treatment] = await execute(treatment === "before" ? beforeQueries : afterQueries);
|
|
assert.deepEqual(iteration.after.rows, iteration.before.rows, `${kind}: complete ordered positional results differ`);
|
|
if (baselineOnly || n >= warmups) { beforeSamples.push(iteration.before.ms); afterSamples.push(iteration.after.ms); }
|
|
}
|
|
const [beforeExplain] = await db.$client.unsafe(`EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${beforeQueries[0].sql}`, beforeQueries[0].params as never[]);
|
|
const [afterExplain] = await db.$client.unsafe(`EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${afterQueries[0].sql}`, afterQueries[0].params as never[]);
|
|
const before = summarize(beforeSamples), after = summarize(afterSamples);
|
|
results.push({ kind, workspaceCount: workspaces.length, equalCompleteResults: true, before, after, speedup: before.medianMs / after.medianMs,
|
|
baselinePlan: beforeExplain["QUERY PLAN"], treatmentPlan: afterExplain["QUERY PLAN"] });
|
|
await fs.writeFile(path.join(outputDir, "partial-results.json"), JSON.stringify(results, null, 2));
|
|
}
|
|
for (const workspace of workspaces) {
|
|
const projection = sql`SELECT id, status, created_at, started_at, finished_at, controller_lease_expires_at FROM relevant_runs`;
|
|
const before = dialect.sqlToQuery(sql`${baseline.expirationScope(workspace)} ${projection}`);
|
|
const after = dialect.sqlToQuery(sql`${expiration.expirationScope(workspace)} ${projection}`);
|
|
const beforeRows = await db.$client.unsafe(before.sql, before.params as never[]).values();
|
|
const afterRows = await db.$client.unsafe(after.sql, after.params as never[]).values();
|
|
assert.deepEqual(normalize(afterRows), normalize(beforeRows), "Complete relevant-run multisets differ");
|
|
}
|
|
assert.equal(await fs.readFile(path.join(repositoryRoot, modulePath), "utf8"), treatmentSource, "Treatment source changed during measurement");
|
|
const report = { baselineRevision, treatmentRevision: git("rev-parse", "HEAD"),
|
|
baselineSourceSha256: sha256(source), treatmentSourceSha256: sha256(treatmentSource),
|
|
treatmentDiff: gitRaw("diff", "--", modulePath), baselineDependencies: "Unchanged current imports; only git-workspace-expiration.ts is loaded from baseline",
|
|
settings, fixture: { runRows: runsPerWorkspace * 8 + unusualContexts.length + 1, workspaceCount: 8, descendantsPerWorkspace, contextPaddingBytes: Buffer.byteLength(padding), synthetic: true, small },
|
|
acceptanceCriterion: "Complete result equality and materially lower stress-fixture medians; report small-control overhead separately",
|
|
measurement: { baselineOnly, measuredSamples: baselineOnly ? 1 : measuredSamples, warmups: baselineOnly ? 0 : warmups, alternatingOrder: true },
|
|
safety: { exactRelevantRunMultisets: true, completeOrderedCallerResults: true }, results };
|
|
await fs.writeFile(path.join(outputDir, "report.json"), JSON.stringify(report, null, 2));
|
|
console.log(JSON.stringify({ outputDir, baselineRevision, settings, safety: report.safety, results: results.map(({ baselinePlan, treatmentPlan, ...result }) => ({ ...result,
|
|
beforeScans: scans(baselinePlan[0].Plan), afterScans: scans(treatmentPlan[0].Plan) })) }, null, 2));
|
|
} finally {
|
|
try { await db?.$client.end({ timeout: 0 }); await database?.cleanup(); if (baselineDir) await fs.rm(baselineDir, { recursive: true, force: true }); }
|
|
finally { clearInterval(keepAlive); }
|
|
}
|