Context
This one has two separate broken references in server.ts, which is a strong sign of leftover work-in-progress from more than one implementation attempt:
ts
import { startRetentionScheduler } from "./lib/retention/scheduler"; // module doesn't exist
// ...
startRetentionScheduler(); // line 67 — throws on boot, see Issue #1's sibling problem
// ...
schedulePruner(); // line 174 — not imported, not defined anywhere in the file. Undefined function call.
lib/retention/ doesn't exist. schedulePruner isn't defined or imported anywhere in the repository — not as a local function, not as an import. Whichever of these two calls is reached first will throw at server startup.
The database already has real tables that need a retention policy: Event, DeadLetterEvent, and WebhookDelivery (see prisma/schema.prisma) will grow unboundedly with no pruning today.
Scope of work
Decide on one retention mechanism, not two. Read both call sites, figure out whether this looks like two people implementing the same feature independently or a scheduler + a one-shot prune that were meant to compose, and consolidate into a single, coherent lib/retention/ module. Document your reasoning in the PR description.
Build lib/retention/scheduler.ts exporting startRetentionScheduler(), which runs on an interval (configurable, e.g. RETENTION_INTERVAL_MS) and prunes rows older than a configurable age threshold from Event, DeadLetterEvent, and WebhookDelivery.
Respect RETENTION_ENABLED — the removed schedulePruner() call was commented as "no-op if RETENTION_ENABLED=false"; preserve that toggle semantics in whatever you build.
Safety rails: batch deletes (don't DELETE FROM "Event" unbounded on a large table), a hard cap on rows deleted per run, and structured logging of what was pruned (counts per table, oldest/newest timestamp affected) so an operator can audit what happened.
Rebuild the retention CLI scripts that existed before cleanup — retention and retention:dry-run npm scripts, backed by a real scripts/retention.ts that can be run manually. --dry-run must report exactly what would be deleted without deleting anything.
Tests: a test that seeds rows at various ages and confirms only rows past the threshold are removed, a test for the RETENTION_ENABLED=false no-op path, and a test for the batch-cap safety rail.
Out of scope
Archiving pruned data to cold storage before deletion (flag this as a possible follow-up issue in your PR if you think it's needed, don't build it here).
Retention policies configurable per-contract or per-event-type — a single global age threshold is sufficient for this issue.
Acceptance criteria
npm run dev:ws boots without throwing on either retention call
Exactly one retention mechanism exists (no duplicate/competing pruning logic)
RETENTION_ENABLED=false fully disables pruning, verified by a test
npm run retention:dry-run reports intended deletions without touching the database
npm run retention actually prunes, respecting the batch cap
New tests pass under npm run test
Context
This one has two separate broken references in server.ts, which is a strong sign of leftover work-in-progress from more than one implementation attempt:
ts
import { startRetentionScheduler } from "./lib/retention/scheduler"; // module doesn't exist
// ...
startRetentionScheduler(); // line 67 — throws on boot, see Issue #1's sibling problem
// ...
schedulePruner(); // line 174 — not imported, not defined anywhere in the file. Undefined function call.
lib/retention/ doesn't exist. schedulePruner isn't defined or imported anywhere in the repository — not as a local function, not as an import. Whichever of these two calls is reached first will throw at server startup.
The database already has real tables that need a retention policy: Event, DeadLetterEvent, and WebhookDelivery (see prisma/schema.prisma) will grow unboundedly with no pruning today.
Scope of work
Decide on one retention mechanism, not two. Read both call sites, figure out whether this looks like two people implementing the same feature independently or a scheduler + a one-shot prune that were meant to compose, and consolidate into a single, coherent lib/retention/ module. Document your reasoning in the PR description.
Build lib/retention/scheduler.ts exporting startRetentionScheduler(), which runs on an interval (configurable, e.g. RETENTION_INTERVAL_MS) and prunes rows older than a configurable age threshold from Event, DeadLetterEvent, and WebhookDelivery.
Respect RETENTION_ENABLED — the removed schedulePruner() call was commented as "no-op if RETENTION_ENABLED=false"; preserve that toggle semantics in whatever you build.
Safety rails: batch deletes (don't DELETE FROM "Event" unbounded on a large table), a hard cap on rows deleted per run, and structured logging of what was pruned (counts per table, oldest/newest timestamp affected) so an operator can audit what happened.
Rebuild the retention CLI scripts that existed before cleanup — retention and retention:dry-run npm scripts, backed by a real scripts/retention.ts that can be run manually. --dry-run must report exactly what would be deleted without deleting anything.
Tests: a test that seeds rows at various ages and confirms only rows past the threshold are removed, a test for the RETENTION_ENABLED=false no-op path, and a test for the batch-cap safety rail.
Out of scope
Archiving pruned data to cold storage before deletion (flag this as a possible follow-up issue in your PR if you think it's needed, don't build it here).
Retention policies configurable per-contract or per-event-type — a single global age threshold is sufficient for this issue.
Acceptance criteria
npm run dev:ws boots without throwing on either retention call
Exactly one retention mechanism exists (no duplicate/competing pruning logic)
RETENTION_ENABLED=false fully disables pruning, verified by a test
npm run retention:dry-run reports intended deletions without touching the database
npm run retention actually prunes, respecting the batch cap
New tests pass under npm run test