# DB Cleanup Audit This document describes the non-destructive database cleanup audit for finding orphan records, legacy schema leftovers, and soft-deleted row volume. ## Manual Audit The cleanup audit is intentionally run as one-time read-only SQL on the target database instead of a permanent application command. It should check the same categories listed below and must not delete or update rows. If rows are found, first capture affected-row details and a backup/export, then apply cleanup through a reviewed migration or operations runbook. ## What It Checks Orphan and broken-reference checks: - active `asset_variants` with missing or soft-deleted `assets` parents - active `pwa_caches` with missing or soft-deleted `projects` parents - active `access_logs` with missing or soft-deleted `projects` or `users` parents - active `access_logs` with `NULL projectId` or `NULL userId` as warnings, because admin/system or public/anonymous access may be valid - active `production_presentation_access` grants with missing, soft-deleted, or `NULL` `projectId`/`userId` Legacy schema checks: - old normalized constructor tables: `page_elements`, `page_links`, `transitions` - known removed columns: `assets.is_deleted`, `assets.deleted_at_time`, `projects.is_deleted`, `projects.deleted_at_time`, `projects.phase`, `projects.entry_page_slug`, `projects.transition_settings` Soft-delete summary: - every table with a `deletedAt` column is scanned for soft-deleted row count - the report includes oldest and newest `deletedAt` timestamps per table ## Retention Policy Keep soft-deleted rows indefinitely by default. Reason: the project uses Sequelize paranoid models, deletion volume is expected to be low, and restoring accidental deletes is more valuable than speculative storage cleanup. Any physical deletion must be a separate migration or operations runbook with: - a fresh backup - explicit affected-row query - rollback or restore plan - table-specific retention threshold ## Prevention Asset deletion is handled in `AssetsService`. Because PostgreSQL foreign-key `ON DELETE CASCADE` does not fire for Sequelize paranoid soft deletes, `AssetsService.remove()` and `AssetsService.deleteByIds()` soft-delete active `asset_variants` rows in the same transaction as the parent `assets` soft delete. This prevents active variants from remaining attached to soft-deleted assets after commit. ## Local Audit Result Last local run: 2026-07-02. Result: - failed orphan checks: `0` - warning orphan checks: `0` - legacy schema candidates: `0` - tables with soft-deleted rows: `4` Soft-deleted rows were present in local `assets`, `projects`, `tour_pages`, and `production_presentation_access`. No deletion was performed.