docs(work): archive shipped template epics and PRDs
Move the 8 shipped template epics and their 9 PRDs (incl.
coverage-architecture) to docs/work/archive/{epics,prds}/ so dispatch
context and prioritization only see live Veect work. The state builder
already walks docs/work/epics/ + docs/work/prds/ only; the one work-CLI
script that matched archive paths (bump-updated-timestamps.mjs, staged
docs/work/**/*.md) now excludes docs/work/archive/ so archived content
stays byte-identical.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
---
|
||||
id: 04-retention-purge-job
|
||||
epic: compliance-manifests-pii-retention-subprocessors
|
||||
title: Background retention purge job in core-shared
|
||||
type: technical-story
|
||||
status: done
|
||||
feature: core-shared
|
||||
depends-on: [01-pii-retention-type-primitives]
|
||||
blocks: []
|
||||
created: 2026-05-18T17:52:09Z
|
||||
updated: 2026-05-18T18:58:23.920Z
|
||||
---
|
||||
|
||||
## Goal
|
||||
|
||||
Add `core-shared/payload/retention-purge/retention-purge.job.ts` — a module that walks every Payload collection's `custom.retention.purgeSchedule`, registers a per-collection scheduled job via `IJobQueue`, queries rows whose active-retention period has elapsed, then either pseudonymizes or hard-deletes each row while emitting one `IAuditLog.record(...)` entry per row. Optional `auditLog` is skipped gracefully when `core-audit` is not wired.
|
||||
|
||||
## Why
|
||||
|
||||
Retention without automated purge is a compliance statement with no enforcement. The job makes `custom.retention` actionable: declarations in code become real deletes on schedule.
|
||||
|
||||
## Done when
|
||||
|
||||
- `packages/core-shared/src/payload/retention-purge/retention-purge.job.ts` exists; receives `IJobQueue` + `SanitizedConfig` (+ optional `IAuditLog`) via constructor/factory.
|
||||
- At registration time, one scheduled job is created per collection that declares `custom.retention.purgeSchedule`.
|
||||
- Job body queries by `createdAt` (trigger `from-creation`) or `updatedAt` (trigger `from-last-access`), applies `pseudonymize` (NULL PII fields) or `hard-delete` (Payload cascade delete) per `postDeletion.action`.
|
||||
- Each processed row emits `auditLog.record({ action: "DELETE", subject: row.id, actor: "system", reason: "retention-policy" })`; when `auditLog` is undefined the emission is silently skipped.
|
||||
- Unit tests with an in-memory Payload mock cover: schedule registration per collection, row matching per trigger type, audit emission, pseudonymize vs hard-delete branches, and graceful auditLog skip.
|
||||
- `pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff` all pass.
|
||||
|
||||
## In scope
|
||||
|
||||
- `packages/core-shared/src/payload/retention-purge/retention-purge.job.ts` + sibling unit test.
|
||||
- Uses existing `IJobQueue` from `core-shared/jobs` and `IAuditLog` from `core-audit` (optional injection).
|
||||
|
||||
## Out of scope
|
||||
|
||||
- App-side boot wiring — the module is the deliverable; consumers integrate it into their `bindAll()`.
|
||||
- `lastAccessedAt` field hook for true "from-last-access" tracking (deferred; see PRD Q2).
|
||||
- Advisory lock for concurrent purge guard (deferred; see PRD Q4).
|
||||
|
||||
## Tasks
|
||||
|
||||
- [x] Add `retention-purge.job.ts` + unit tests to `packages/core-shared/src/payload/retention-purge/` — walks `custom.retention.purgeSchedule` per collection, registers scheduled jobs via `IJobQueue`, executes pseudonymize or hard-delete per `postDeletion.action`, emits optional audit entry per row, unit tests cover all branches including graceful auditLog skip — all gates pass on this commit.
|
||||
Reference in New Issue
Block a user