Skip to content

Retention Schedule

UK GDPR Art 5(1)(e) (storage limitation). Controls PRIV-03, PRIV-04, SEC-12. Status: implemented for the backend hard-delete spine delivered in infra PR-12 plus the account-erasure purge delivered in infra PR-14, plus the consent/event foundation delivered in infra PR-17, first product event hookpoints delivered in infra PR-18, worker lifecycle event hookpoints delivered in infra PR-19, the anonymized ML snapshot boundary delivered in infra PR-20, the backend boarding-pass-gated presence contract delivered in infra PR-21, message send delivered in infra PR-22, directory delivered in infra PR-23, connection requests delivered in infra PR-24, and professional profile fields delivered in infra PR-25, relevance ranking delivered in infra PR-26, cached AI rationale delivered in infra PR-27, stateless AI profile interview delivered in infra PR-28, the persistent connections list delivered in infra PR-29, and tagged pre-launch seed/purge tooling delivered in infra PR-30; durations remain placeholders [counsel required] until counsel ratifies the policy numbers.

Policy: policies/privacy-and-retention-policy.md, policies/backup-and-recovery-policy.md.

Evidence:

  • infra PR-12 (retention_jobs.py, worker.run_retention) implements worker sweeps for sessions, presences, non-matched candidates, and stale matched conversations/messages.
  • infra PR-14 (18047a3) implements authenticated DELETE /account, soft-delete with users.deleted_at, all-session revocation, active-only auth, LinkedIn no-revival guard, and retention_jobs.sweep_deleted_users.
  • infra PR-17 (7562d25) adds user_consents and app_events; both are tied to users by cascade so user hard delete removes consent rows and actor-linked upstream event rows.
  • infra PR-18 (b0590be) emits auth/presence/match/safety product events through try_emit_event.
  • infra PR-19 (94d4018) emits worker match.candidate_proposed / match.expired events as actor-scoped T1 rows through the same consent gate.
  • infra PR-20 (9e828db) adds ml_training_snapshots / ml_training_snapshot_rows: anonymous aggregate snapshot rows with no user FK, built from whitelisted T2 events only; source app_events still cascade on user erasure while snapshot rows survive as a distinct anonymous class.
  • infra PR-21 (52cd2c8) tightens product POST /presence to require boarding_pass_barcode + document_verified, reject manual/self-reported/ unverified product writes, and preserve the no-raw-pass-artifact boundary.
  • infra PR-22 (2680a01) implements backend message send with metadata-only message.sent events; message bodies remain in conversation_messages.
  • infra PR-23 (75d8337) implements the in-window directory without adding new retention classes.
  • infra PR-24 (a6cf2af) adds connection_requests with user-authored request notes, request status, presence references, timestamps, and optional conversation bridge.
  • infra PR-25 (0934291) adds self-authored profile summary and intent_tags to traveler_profiles; headline already existed on that table.
  • infra PR-26 (e40f0fd) adds internal-signal relevance ranking without adding a retained data class.
  • infra PR-27 (e43b60c) adds match_rationales, a cached rationale sentence per canonical pair plus minimized-input hash and expiry; prompts, provider metadata, and raw model responses are deliberately not stored.
  • infra PR-28 (d2db03f) adds the advisory /profile/interview route; the endpoint is stateless, returns 503 without an Anthropic key, and smoke tests verify interview content is not saved to profile summaries, events, or logs.
  • infra PR-29 (1fbcf6e) adds GET /connections over accepted connection_requests without creating a new table.
  • infra PR-30 adds users.is_seed, app.seed, app.purge_seed, and app.seed_smoke so pre-launch mock traveler graphs can be created, purged, and verified without targeting real is_seed=false accounts.
  • app.safety_smoke.py verifies each retention class, including that recent matched conversations are not prematurely deleted and RETENTION_ENABLED=false is a no-op.
  • app.account_erasure_smoke.py verifies unauthenticated rejection, generic response/no PII, multi-session revocation, no session revival, purge cascade, recent-deletion grace behavior, and disabled-retention no-op behavior.
  • Runtime knobs (labels only, values not stored here): RETENTION_ENABLED, RETENTION_SESSIONS_DAYS, RETENTION_PRESENCES_DAYS, RETENTION_CANDIDATES_DAYS, RETENTION_CONVERSATIONS_DAYS, RETENTION_ACCOUNT_ERASURE_DAYS.
Data class Table(s) Live retention Hard-delete target Mechanism today Remaining gap
Session rows app_sessions Until expiry + grace 30d after expiry/revoke PR-12 worker sweep deletes expired/revoked rows after RETENTION_SESSIONS_DAYS Idle timeout/rotation policy remains separate (SEC-12)
Presence rows flight_presences TTL (default 4h, max 48h) 7d after expires_at PR-12 worker sweep deletes expired rows after RETENTION_PRESENCES_DAYS; PR-21 ensures product-created rows are boarding-pass-derived document_verified rows with no raw pass material retained Counsel ratifies duration and C25 boarding-pass processing posture
Non-matched candidates match_candidates Until terminal dismissal/expiry 30d after updated_at for dismissed/expired candidates PR-12 worker sweep deletes non-matched terminal rows after RETENTION_CANDIDATES_DAYS Counsel ratifies duration
Matched conversations + messages match_candidates, conversations, conversation_messages Life of match 90d after last conversation activity PR-12 worker sweep deletes the owning matched candidate after RETENTION_CONVERSATIONS_DAYS; FK cascade removes conversation and messages; PR-22 adds backend message send into the existing table Counsel ratifies duration
Connection requests connection_requests Pending until accepted/declined/expired; accepted rows may act as durable connection evidence On account erasure; independent duration TBD PR-24 creates directional request rows with note, status, presence references, timestamps, and optional conversation bridge; user hard delete cascades requester/target rows Request-note retention/minimization pending counsel C28/C4; no independent sweep exists yet
Persistent connection records connection_requests (accepted rows) plus conversation bridge Persists beyond trip window as a durable professional contact On account erasure; independent duration TBD PR-24 accepted requests persist as accepted connection rows and bridge to the conversation substrate; PR-29 exposes them through self-scoped, block-aware, minimized GET /connections Independent duration pending counsel C28; dedicated contacts model/job still future
Profile fields traveler_profiles Life of account On account deletion after erasure grace PR-25 stores self-authored headline, summary, and intent tags on traveler_profiles; user hard delete cascades the row Counsel confirms profile-field retention and correction/export process
AI rationale cache match_rationales Until expires_at for the pair/input hash On account erasure or cache expiry; independent maximum duration TBD PR-27 stores only a generated sentence, canonical pair user IDs, minimized-input hash, created_at, and expires_at; user hard delete cascades either side Counsel confirms cache duration and transparency language under C27; no independent sweep exists yet beyond expiry checks on read
AI profile interview turns None in Partile DB (transient route/provider processing) Not retained by the Partile endpoint Not applicable unless the user saves a suggested summary into traveler_profiles later PR-28 interview_smoke.py verifies prompt content is not saved to profile summaries, events, or logs; suggested output is advisory Mobile interview UI remains upcoming; provider/DPA/caching terms pending C27
Blocks user_blocks While account active On account deletion PR-14 account purge cascades rows on DELETE FROM users Counsel confirms safety-retention stance
Reports user_reports While open; archive on resolve 180d after resolution; currently cascades on account purge Durable report capture implemented in PR-12; PR-14 account purge cascades current rows Moderation workflow and counsel C7 report-retention policy
LinkedIn-derived PII external_identities Life of account On account deletion after erasure grace PR-14 account purge cascades rows after RETENTION_ACCOUNT_ERASURE_DAYS Counsel confirms grace duration and notice wording
Consent grants user_consents Life of account On account deletion after erasure grace PR-17 table cascades on user hard delete; event_consent_smoke.py verifies cascade Counsel confirms notice-version evidence duration
Upstream app events app_events While account active On account deletion for actor-linked rows PR-17 actor_user_id cascades on user hard delete; event_consent_smoke.py verifies cascade; PR-18 event_hookpoint_smoke.py verifies auth/presence/match/safety emission through the consent gate; PR-19 worker_event_hookpoint_smoke.py verifies actor-scoped worker match lifecycle emission through the consent gate; PR-22 adds message.sent T1 metadata-only events; PR-24 adds connection.requested T1 and connection.accepted/connection.declined T2 events; PR-27/PR-28 smokes verify rationale/interview content is not emitted to events Counsel C8/C9 ratification still future; ranking/AI event hookpoints are not currently built
Anonymous ML training snapshots ml_training_snapshots, ml_training_snapshot_rows Independent anonymous-aggregate class Not tied to account erasure; independent retention TBD PR-20 ml_snapshot_smoke.py verifies T2-only k-anonymous rows, low-k suppression, stripping non-whitelisted fields, source app_events cascade on user delete, and snapshot rows survive erasure Counsel C8/C9 final anonymization standard; decide independent snapshot retention duration before real model training
Tagged pre-launch seed accounts users.is_seed = true plus related rows Pre-launch testing only Purge before GA and before accepting real users PR-30 app.seed creates only tagged mock accounts; app.purge_seed deletes seed-related event object refs and seed users, then relies on FK cascades; app.seed_smoke verifies seed, idempotency, purge, and a real marker row survives ACT-004 purge-before-GA gate remains open; no real PII may be seeded
Soft-deleted accounts users.status, users.deleted_at Grace window after user requests deletion Default 7d after deleted_at PR-14 worker sweep hard-deletes status='deleted' users after RETENTION_ACCOUNT_ERASURE_DAYS Counsel confirms grace duration; DSAR process still separate
OAuth state / handoff codes Redis 10 min / 120 s self-expiring Redis TTL and single-use consume OK

Erasure design → readiness/data-inventory-and-retention.md. Counsel duration review → counsel-queue.md C4.