RET-002 critical Soft Delete Implementation
Soft delete queries exclude deleted records by default
Queries automatically filter out soft-deleted records via ORM feature or global middleware; escape hatch exists for admin/audit
Question to ask
"Can deleted records leak into API responses?"
Verification guide
Severity: Critical
Queries should automatically exclude soft-deleted records so deleted data doesn't leak into user-facing features. There should also be a way to include deleted records when legitimately needed (admin, audits, compliance).
Check automatically:
- Check for global query scopes/filters:
# Prisma middleware that filters deleted records
grep -rE "prisma\.\$use|middleware.*deleted" --include="*.ts" src/ 2>/dev/null
# Prisma client extensions
grep -rE "@prisma/client/extension|defineExtension" --include="*.ts" src/ 2>/dev/null
- Check ORM built-in soft delete filtering:
# TypeORM - @DeleteDateColumn auto-filters
grep -rE "@DeleteDateColumn" --include="*.ts" src/ 2>/dev/null
# Sequelize paranoid - auto-filters
grep -rE "paranoid\s*:\s*true" --include="*.ts" --include="*.js" src/ 2>/dev/null
# Drizzle - manual WHERE clauses needed
grep -rE "where.*deletedAt.*null|where.*isDeleted.*false" --include="*.ts" src/ 2>/dev/null
- Check for manual WHERE clauses (if no ORM auto-filter):
# Manual soft delete filtering
grep -rE "deleted_at\s*IS\s*NULL|deletedAt\s*=\s*null|isDeleted\s*=\s*false" --include="*.ts" --include="*.sql" src/ 2>/dev/null
- Check for "include deleted" escape hatch:
# Ways to include deleted records when needed
grep -rE "withDeleted|includeDeleted|paranoid\s*:\s*false|unscoped|withTrashed" --include="*.ts" src/ 2>/dev/null
- Look for inconsistent patterns (some queries filter, others don't):
# Count queries that filter vs don't filter deleted records
# If using manual WHERE clauses, inconsistency is a red flag
Cross-reference with:
- RET-001 (soft delete must exist for this to apply)
- RET-005 (compliance queries may need to include deleted records)
Pass criteria:
- Default queries automatically exclude soft-deleted records (via ORM feature or global middleware)
- Explicit "include deleted" method exists for admin/audit use cases
- Pattern is consistent across the codebase
Fail criteria:
- Queries must manually add
deleted_at IS NULLeverywhere (error-prone) - Inconsistent pattern - some queries filter, others don't
- No way to access deleted records when legitimately needed (audits, compliance)
Evidence to capture:
- ORM soft delete filtering mechanism in use
- Whether filtering is automatic or manual
- "Include deleted" escape hatch location
- Any inconsistencies found