Locked 2026-05-27 (M-AUDIT-LOG-V1 Story 8). This document lists every retention window applied to workspace data, the legal/operational reason for the window, and the cron that enforces it.
audit_events — 7 years
What: every row in the audit_events table.
Why: financial / dispute defence + GDPR audit trail. Stripe disputes can land up to ~5 years after the original charge; chasing a dispute without an audit row means the operator can't reconstruct what staff or the customer did. 7 years is the standard financial-record window and aligns with EU/CA bookkeeping rules.
Enforcement: audit-retention-tick cron runs nightly at 03:30 UTC.
Deletes any row where occurred_at < now() - interval '7 years'. Pure
hard delete — no archival staging area; if a workspace needs longer
retention, ops can pause the cron per-workspace via env toggle (V1.5+
backlog).
Source of truth:
lib/audit/retention.ts(cutoff calculation + delete query)app/api/cron/audit-retention-tick/route.ts(cron entrypoint)vercel.json→audit-retention-tickschedule entry
GDPR redaction (right-to-be-forgotten)
When a customer triggers self-delete via customer_account_self_deleted
or an operator runs anonymize_customer, the cascade walks every
customer-shaped JSON column on the workspace and replaces PII with the
sentinel '[anonymized]'. The audit ROW itself is preserved (immutability
invariant + the 7-year retention above) but the embedded PII is scrubbed.
Implementation:
lib/audit/redact-customer.ts— pure JSON walker + audit_events query- M-CUSTOMERS-V1 composes this helper with the equivalent walkers for
bookings.metadata,customers.notes, etc.
Other retention windows (cross-references)
These windows live in other docs but are listed here for completeness so ops has a single retention surface to consult:
| Domain | Window | Source |
|---|---|---|
| Booking proof photos (close_outcome=clean) | 72 hours after booking close | docs/07-operations/05-v1-optimization-pass.md |
| Booking proof photos (close_outcome=with_issue) | Operator-controlled, retained until manual delete | same |
email_queue rows | 90 days (purged by data-retention-tick) | M-NOTIFY epic |
payment_intents / refunds | 7 years (matches audit_events) | M-PAY epic |
| Workspace soft-delete grace window | 90 days before hard-purge | M10.8 |
magic_link_tokens / customer_portal_otps | 24 hours TTL + auto-purge | M-CA epic |
Operator-visible retention controls
Workspace Admins can:
- Adjust the per-workspace data-retention setting at Settings → Data &
Privacy (within bounds enforced by
privacy_retention_months_updatedaudit events — see M10.5). - Trigger a manual purge for their workspace (M10.5).
Audit-event retention is NOT operator-tunable in V1; it's a platform policy. If an operator needs longer retention for a legal hold, ops disables the cron for that workspace via support workflow.