Defines tombstone-based soft delete with a restore window, reserved unique keys, default queries that exclude deleted rows, scheduled hard purge, and purge evidence that can be reconciled with erasure workflows.
Package status: reference context ready for human review. The contract and test scenarios are complete, but no claim is made that an adopting implementation has passed them.
Decision
User-owned records are not physically removed at delete time. The application writes a tombstone (deleted_at, actor, reason) and a restore deadline. Default reads omit tombstoned rows. Natural keys stay reserved so a colliding create cannot hijack identity. After the restore window, restore is refused. A scheduled job hard-purges rows past purge_after and writes purge evidence. Audit records of the delete and purge follow the related audit-log block; erasure fulfillment may shorten windows only through that workflow.
Scope
- User-owned rows that support undo, such as notes, drafts, or similar aggregates in the adopting schema.
- List/get query defaults, restore API, unique-key reservation, purge job, and evidence event.
- Interaction with erasure: purge evidence versus retained audit.
Outside this block
- Multi-object cascade graphs beyond the single aggregate in the profile.
- Backup media retention after hard purge.
- Legal hold that must freeze purge; that is an input from compliance, not defined here.
Contract
- Default list and get paths apply
exclude_deletedunless the caller hascap-notes-admin. - Delete sets
deleted_at,deleted_by,restore_until, andpurge_after; it does not drop the row. - While tombstoned, the live unique key (owner, slug) remains reserved and colliding creates return
slug_reserved_until_purge. - Restore before
restore_untilclears the tombstone and returns the row to default queries. - Restore after
restore_untilis rejected withrestore_window_elapsed. - Hard purge after
purge_afterremoves the row and emitsdata.purge.completedwith table, id, and job name.
Implementation guidance
- Exclude tombstones in the repository, not only one list. Keep slugs reserved while tombstoned.
- Purge is idempotent and bounded by
batch_size. Mint new ids after purge.
Failure handling and safeguards
- Delete transactions without
purge_afterfail. Purge without evidence is skipped. - Restore-versus-purge races return
already_purged, never a half-restored row.
Verification and operations
- Tests: hide, restore, reserved slug, elapsed window, purge evidence.
- Alert on purge lag. Profile restore windows are not a legal standard.
The executable-looking examples in this package are fixtures and acceptance contracts. Run
python tools/validate.py from the collection root to check package structure and metadata; then
implement and execute the scenarios in the target repository.
Adoption assumptions
- Names and numeric values in
example.yamlare an adoption profile, not universal defaults. - The adopting team must map actors, data classes, error vocabulary, and ownership to its system.
References
- No external normative source is required; this package defines a project decision.