diff --git a/src/db/retention.ts b/src/db/retention.ts index 459f8dc7b3..0dcf6293f0 100644 --- a/src/db/retention.ts +++ b/src/db/retention.ts @@ -21,6 +21,9 @@ export const RETENTION_POLICY: readonly RetentionRule[] = [ { table: "signal_snapshots", column: "generated_at", days: 90 }, { table: "score_previews", column: "generated_at", days: 90 }, { table: "repo_snapshots", column: "fetched_at", days: 90 }, + // One payloadJson blob per agent run (#3896); a per-run diagnostic snapshot with no cross-run rollup + // depending on it, so a shorter window than the audit/usage-log tables above is appropriate. + { table: "agent_context_snapshots", column: "created_at", days: 30 }, ]; export type PruneResult = { table: string; column: string; cutoff: string; deleted: number }; diff --git a/test/unit/retention.test.ts b/test/unit/retention.test.ts index 187930c9ae..4d69a338a8 100644 --- a/test/unit/retention.test.ts +++ b/test/unit/retention.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { createApp } from "../../src/api/routes"; import { getDb } from "../../src/db/client"; import { dedupeSignalSnapshots, pruneExpiredRecords, RETENTION_POLICY } from "../../src/db/retention"; -import { aiUsageEvents, webhookEvents } from "../../src/db/schema"; +import { agentContextSnapshots, aiUsageEvents, webhookEvents } from "../../src/db/schema"; import { processJob, runRetentionPrune } from "../../src/queue/processors"; import { createTestEnv } from "../helpers/d1"; @@ -102,6 +102,24 @@ describe("pruneExpiredRecords", () => { await expect(pruneExpiredRecords(env, { policy: [{ table: "webhook_events; DROP TABLE x", column: "received_at", days: 1 }] })).rejects.toThrow("Unsafe retention identifier"); }); + it("prunes agent_context_snapshots older than its window and keeps recent runs (#3896)", async () => { + const env = createTestEnv(); + const db = getDb(env.DB); + await db.insert(agentContextSnapshots).values([ + { id: "ctx-old", runId: "run-old", createdAt: daysAgo(40) }, + { id: "ctx-recent", runId: "run-recent", createdAt: daysAgo(2) }, + ]); + + const results = await pruneExpiredRecords(env, { + nowMs: NOW, + policy: [{ table: "agent_context_snapshots", column: "created_at", days: 30 }], + }); + + expect(results[0]?.deleted).toBe(1); + const rows = await env.DB.prepare("SELECT id FROM agent_context_snapshots").all<{ id: string }>(); + expect(rows.results.map((row) => row.id)).toEqual(["ctx-recent"]); + }); + it("the policy only targets append-only/log/snapshot tables (no current-state tables)", () => { const tables = RETENTION_POLICY.map((r) => r.table); for (const protectedTable of ["webhook_events", "repositories", "repository_settings", "pull_requests", "issues", "repository_ai_keys", "contributors"]) {