Background
The platform has substantial data-protection machinery: migrations/039_soft_delete_data_retention.sql, and routes for adminDataRetention.ts, adminErasure.ts, tenantErasure.ts, userErasure.ts, and tenantDataExport.ts. A recent merge (PR #1290) addressed PII handling.
Soft deletion introduces a specific hazard: the row is still there, so every read path must exclude it. Correctness depends on nothing forgetting.
Why This Matters
An erasure request is a legal commitment. Soft-delete means the data physically remains, so a single query that omits the deleted-at filter silently continues serving records the platform has told a user — and possibly a regulator — were erased. It fails open, produces no error, and is invisible until someone notices their data still visible after deletion.
The same class of bug affects ordinary deletion: a tenant who removes a document expects it gone, not merely hidden from one view.
Scope
- Inventory every table with a soft-delete column and every read path touching them — routes, repositories, admin views, exports, search indexes, and reports.
- Verify each read path excludes soft-deleted records. Prefer a mechanism that is correct by default (a repository-level filter or a database view) over relying on every future query remembering.
- Verify erasure covers derived and denormalised data — search indexes, caches, aggregates, outbox payloads, and logs — not just the primary row.
- Verify data export excludes erased records and does not leak another user's data.
- Verify retention jobs actually run and hard-delete on schedule, and that they cannot delete records still under a legal or financial hold. Financial records typically must be retained even after an account erasure — confirm the intended policy and make sure the implementation matches it.
- Add tests proving a soft-deleted record is unreachable via every enumerated read path.
- Document the retention policy per data category, if it is not already written down.
Acceptance Criteria
Out of Scope
- Changing the retention policy itself (document what exists; raise a separate issue to change it).
- Frontend erasure UI.
CI — this PR must pass the Backend (lint, test) job
Extracted from .github/workflows/ci.yml. These are the exact steps CI runs, in order, from the backend/ directory on Node 22. Run them locally and get them green before pushing.
cd backend
npm ci # the backend uses npm — NOT pnpm
npm run lint # eslint .
npm run test:ci # vitest run (network-dependent suites excluded)
npm run openapi:validate # redocly lint docs/openapi.yml
A red job blocks the PR. If you add or change an endpoint, update backend/docs/openapi.yml in the same PR — openapi:validate is a hard gate.
Background
The platform has substantial data-protection machinery:
migrations/039_soft_delete_data_retention.sql, and routes foradminDataRetention.ts,adminErasure.ts,tenantErasure.ts,userErasure.ts, andtenantDataExport.ts. A recent merge (PR #1290) addressed PII handling.Soft deletion introduces a specific hazard: the row is still there, so every read path must exclude it. Correctness depends on nothing forgetting.
Why This Matters
An erasure request is a legal commitment. Soft-delete means the data physically remains, so a single query that omits the deleted-at filter silently continues serving records the platform has told a user — and possibly a regulator — were erased. It fails open, produces no error, and is invisible until someone notices their data still visible after deletion.
The same class of bug affects ordinary deletion: a tenant who removes a document expects it gone, not merely hidden from one view.
Scope
Acceptance Criteria
npm run lintandnpm run test:cipass.Out of Scope
CI — this PR must pass the
Backend (lint, test)jobExtracted from
.github/workflows/ci.yml. These are the exact steps CI runs, in order, from thebackend/directory on Node 22. Run them locally and get them green before pushing.A red job blocks the PR. If you add or change an endpoint, update
backend/docs/openapi.ymlin the same PR —openapi:validateis a hard gate.