DSAR handling¶
Lebanese law has no operational DPA today. EU residents may visit and exercise GDPR Art. 15 (access), Art. 16 (rectification), or Art. 17 (erasure) rights against us via Art. 3(2) extraterritorial reach. This runbook is the process for handling those requests at hobby scale.
Intake¶
privacy@961tech.dev is the documented mailbox. Per ADR-0014 D3, this is the only intake surface until #40 lawyer review lands a formal privacy policy.
Validate identity (don't skip)¶
A bad-faith DSAR is data exfiltration. Validate:
- For email-subscribed users (#14 alerts): respond from the address they originally used to subscribe. Send a confirmation link to that address; only proceed if they click it.
- For build-cookie-only Visitors (ADR-0003): identity is the cookie value itself. Either they have it (paste it in the response) or they don't — there is no "linked email" to validate against.
- For Auth.js account holders (post-#11): require the user to be currently signed in and confirm the request from their account UI. Mail-only requests are insufficient for account holders.
If validation fails: respond explaining what's needed. Don't process.
Export the data (Art. 15)¶
Sanity scope:
- For an email subscriber, the only data is the row in EmailSubscription (when #14 lands) plus any clicks attributed to them via shared device fingerprint (extremely unlikely — IP is hashed).
- For an Auth.js account, export the User, Account, Session rows + any saved builds (#12) + any alerts (#14) + any audit-log rows where they were the actor.
Format: structured JSON — one file per table. Send via password-protected ZIP over the validated email channel.
-- Sketch query for a known userId:
SELECT row_to_json(u) FROM "User" u WHERE id = '<id>';
SELECT json_agg(s) FROM "EmailSubscription" s WHERE "userId" = '<id>';
SELECT json_agg(b) FROM "Build" b WHERE "userId" = '<id>';
SELECT json_agg(a) FROM "AuditLog" a WHERE "actorUserId" = '<id>';
Erasure (Art. 17)¶
Two paths:
- Email subscription only: hard-delete the EmailSubscription row. No retention obligation that prevents this.
- Account holder: soft-delete User.deletedAt (when schema lands), null out PII columns, keep audit_log rows for forensic continuity but with actorUserId redacted via UPDATE "AuditLog" SET "actorUserId" = NULL WHERE "actorUserId" = '<id>'. We commit to "non-recoverable within 30 days" (ADR-0014 D4 grace period).
Rectification (Art. 16)¶
Email change → update via the auth-provider profile (since Auth.js + OAuth means we don't store the email separately when #11 ships).
Log the decision¶
Every DSAR response → audit_log row:
await audit({
event: 'admin.dsar',
payload: {
requesterEmail: '<sha256-hash>', // never log raw
requestType: 'access' | 'erasure' | 'rectification',
decision: 'fulfilled' | 'declined-validation' | 'declined-out-of-scope',
note: '<short>',
},
});
SLA¶
GDPR is 30 days from receipt; we should respond within 7 to leave headroom. Lebanese law has no statutory window today.
Escalation¶
If the request looks legally complex (subpoena, court order, cross-border data transfer concerns), pause and refer to the lawyer per ADR-0014 D3. Don't fulfill until guidance lands.