GDPR Article 5(2) requires the controller to demonstrate accountability — when a data protection authority asks "did you process this erasure request?" you need a system-of-record answer, not a search through stdout logs or a best-effort reconstruction from the absence of data. GDPR Article 17 does not just require you to delete data; it implicitly requires you to prove you deleted it. ISO 27001:2022 A.5.28 covers evidence of control operation. A deletion that leaves no receipt is operationally indistinguishable from a deletion that never happened, and log files that get rotated before the DPA inquiry cannot serve as evidence.
Low because the erasure itself may be technically complete, but the absence of a durable receipt makes compliance undemonstrable to regulators and forces costly manual reconstruction from backup diffs during any audit.
Create an erasure_audit_log table that persists independently of the erased contact row. Return the receipt_token to the requester for future reference:
-- In supabase/migrations/YYYYMMDDHHMMSS_erasure_audit_log.sql
CREATE TABLE erasure_audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
contact_id UUID NOT NULL, -- retain the ID (not PII) for audit linkage
requested_by TEXT NOT NULL,
requested_at TIMESTAMPTZ NOT NULL,
processed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
tables_affected TEXT[] NOT NULL,
receipt_token TEXT UNIQUE NOT NULL DEFAULT encode(gen_random_bytes(16), 'hex')
);
Return receipt_token to whoever triggered the erasure so they can reference the specific event in any future regulatory correspondence. The contact_id UUID retained here is not PII — it is a pseudonymous identifier with no PII attached after erasure.
ID: compliance-consent-engine.data-subject-rights.deletion-receipt
Severity: low
What to look for: After a deletion/erasure request is processed, check whether the system produces a durable record confirming that the request was processed. This is important both for internal compliance auditing ("we processed this GDPR request on this date") and for responding to data subjects who ask for confirmation. The receipt should not contain the erased personal data itself — just the fact that the erasure was performed, when, by whom, and which records were affected.
Pass criteria: The erasure service creates a permanent log entry in an erasure audit table (or equivalent persistent store) containing: the request identifier, the timestamp of processing, which tables were affected, and who triggered the request. This receipt is distinct from the erased contact record and survives the deletion. Count all fields on the erasure receipt — at least 4 must be present (contact_id, requested_by, processed_at, tables_affected).
Fail criteria: No erasure receipt is generated. Deletion happens but leaves no evidence it was performed — if the DPA asks "did you process this erasure request?", there is no system-of-record answer.
Skip (N/A) when: GDPR not applicable.
Detail on fail: "processErasureRequest() performs deletion but writes no audit record — no way to demonstrate compliance for a specific request" or "Erasure logged only to stdout — no persistent record survives log rotation"
Remediation: Write a persistent receipt:
CREATE TABLE erasure_audit_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
contact_id UUID NOT NULL, -- retain the ID (not PII), it's needed for the audit trail
requested_by TEXT NOT NULL,
requested_at TIMESTAMPTZ NOT NULL,
processed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
tables_affected TEXT[] NOT NULL,
receipt_token TEXT UNIQUE NOT NULL DEFAULT encode(gen_random_bytes(16), 'hex')
);
Return receipt_token to the requester so they can reference the specific erasure event.