DSAR Workflow — Data Subject Access / Deletion / Portability Requests¶
Asset: M7 (workflow half — the register half is
compliance/data-inventory/). Covers GDPR Arts. 15–20 (access, rectification, erasure, portability) and the CCPA right-to-know / right-to-delete.Clocks: GDPR — 30 days from receipt (extendable +60 for complex requests, but you must tell the requester within the first 30). CCPA — 45 days (extendable +45). Confirm receipt within 10 days (CCPA) / promptly (GDPR). The clock starts at receipt, not at identity verification.
Step 0 — Intake & log¶
Any channel counts (email, support ticket, web form). On receipt:
- Create the request record: copy
dsar-record.template.md→compliance/dsar/<YYYY-MM-DD>-<requester-ref>.md. Assign an owner. - Record the received date and compute the due date. Acknowledge receipt.
- Classify the request type: access / deletion / rectification / portability / opt-out (CCPA sale-sharing — usually N/A for B2B SnowOps clients, record it).
Step 1 — Verify identity (before disclosing anything)¶
Verify proportionally to sensitivity: match the request channel to the email on record, or challenge via the account's authenticated session. Never disclose to an unverified requester — an over-eager DSAR response is itself a data leak (see the data-leak runbook). For agent requests (CCPA authorized agents, parents), require written authorization. Log the verification method in the record.
Step 2 — Locate the data (walk the inventory)¶
Open the client's compliance/data-inventory/data-inventory.yaml and, for every
processing_activities entry whose data_subjects covers the requester class,
execute its dsar_lookup instruction. That field exists precisely so this
step is mechanical, not archaeological.
- Record per system: found / not found, and the export or deletion handle.
- Cross-check with M4 Purview classification scan results where available — Purview catches data the register missed; if it does, that's a register gap: fix the inventory in the same PR.
Step 3 — Fulfil¶
| Request | Action |
|---|---|
| Access / know | Export from each system (machine-readable for portability — JSON/CSV). Redact other people's personal data from the export. |
| Deletion | Delete or anonymize per system via the dsar_lookup deletion handle. Where a system's retention has a legal/security basis (e.g. J1 audit logs, financial records), the exemption applies — document the exemption per system in the record instead of silently skipping. Deletion propagates to processors (the record's DPA list) and to backups per the L1 retention note below. |
| Rectification | Correct in the source system; downstream copies follow on sync. |
Backups note (honest mechanics): point-in-time backups (L1) are not selectively editable. The accepted practice: delete from live systems, let backup retention age the data out naturally, and ensure restore runbooks re-apply pending deletions if a backup containing the data is ever restored. State exactly this in the record — never claim backup purging that didn't happen.
Step 4 — Respond & close¶
- Send the response (export, deletion confirmation incl. documented exemptions, or rectification confirmation) before the due date.
- Complete the record: systems touched, exemptions, dates, responder. Commit it
to
compliance/dsar/— the git history is the Art. 30 evidence trail. - If fulfilment revealed an exposure or systemic gap, open the data-leak path / a remediation ticket via E7.
Escalation¶
- Identity can't be verified → respond explaining what's needed; clock pauses only for CCPA, not GDPR — escalate to {{DPO_CONTACT}} before day 20.
- Request is manifestly excessive/repetitive → GDPR allows refusal or a fee, but only with DPO sign-off, documented in the record.
- Any due date at risk → notify the requester of the extension within the original window, with the reason, and record it.
Cadence¶
- Quarterly: review
compliance/dsar/records for SLA performance + recurring gaps; re-validate the data inventory (validate.py) and itslast_reviewed. - Annually: tabletop one synthetic DSAR end to end (same spirit as K5).