Queries · q324 · Data Protection Officer · week 2, night 1
“Is 'zero portability requests in 3 years' a real zero, or are shopper data exports being fulfilled outside the formal DSAR process (a logging blind spot)?”
Fictional people at a fictional company, each played by a language-model agent. Who they are and what they are told
Week 2; shared with Henrik Aalto, Chief Information Security Officer
A data protection authority asked how we handle privacy: how many privacy requests we receive and how fast we complete them, how consent is recorded, and what remains after an account is deleted.
The manager, at the start of week 2
Subject: Week 2 — DPA inquiry changes our priorities
Priyanka,
Good work — this is exactly the kind of digging I wanted. A few things surprised me and now matter more because of the regulator inquiry (which I'm handling jointly with Henrik Aalto, CISO — our response needs to be accurate and defensible, not just reassuring).
What struck me:
So, for this week, please prioritize work that lets us answer the DPA's three questions honestly: volume and speed of privacy requests, how consent is captured and whether it's reliable, and what data persists post-deletion. That's the deliverable Henrik and I need to brief the board audit committee before we respond.
My questions:
If all four can't fit, start with 2 and 4 — those go directly into the regulator response.
Thanks, Ines
Subject: Security angle for the DPA response
Hi Priyanka,
A few things from my side that would strengthen the privacy response:
Happy to compare notes before you send anything to data engineering.
Henrik
The analyst · week 2, night 1, request 2 of 3
Ines said to lead with priorities 2 and 4 if slots are tight, so tonight is entirely those two. For priority 2, I'm not just re-stating last week's 60-request total — Claire Dubois (Risk & Payments) posted ~48 requests company-wide in the same channel, which doesn't match my count, so before anything goes to the regulator I need an auditable type-x-status breakdown that either reconciles the two numbers or pinpoints exactly where they diverge. I'm also treating "zero portability requests ever" as a hypothesis to test rather than a fact: since data_subject_requests.data_export_id links to data_exports, I can check whether export jobs are actually being run for shoppers outside the formal DSAR table — if so, that's a logging blind spot, not a true zero. For priority 4, last week I only checked 2 of the 6 policy-covered tables (email_log confirmed broken, audit_log mostly fine with a narrow gap); tonight I'm closing out the remaining four (events, job_runs, page_views, system_health_checks) at a compact one-row-per-table grain so Ines has the full current picture of "what persists past its stated window," not just the one confirmed violation. I'm deferring the legal-hold evidence question (priority 1), the consent anomaly (priority 3), and Henrik's three asks to night 2, since those need either external evidence (disputes) or more design thought (what "touches security data" means for a request) that I don't want to rush tonight.
The engineer · 2 dry runs on the empty schema, then the submission
SELECT CASE WHEN dsr.id IS NOT NULL THEN 'Y' ELSE 'N' END AS linked_to_dsar, COUNT(*) AS export_count, COUNT(DISTINCT de.subject_user_id) AS distinct_subject_users, MIN(de.requested_at) AS earliest_requested_at, MAX(de.requested_at) AS latest_requested_at, COUNT(*) FILTER (WHERE de.status = 'completed') AS completed_export_count, AVG(de.row_count) FILTER (WHERE de.row_count IS NOT NULL) AS avg_row_count FROM data_exports de LEFT JOIN data_subject_requests dsr ON dsr.data_export_id = de.id GROUP BY 1 ORDER BY linked_to_dsar ASC
SELECT CASE WHEN dsr.id IS NOT NULL THEN 'Y' ELSE 'N' END AS linked_to_dsar, COUNT(*) AS export_count, COUNT(DISTINCT de.subject_user_id) AS distinct_subject_users, MIN(de.requested_at) AS earliest_requested_at, MAX(de.requested_at) AS latest_requested_at, MIN(CAST(de.requested_at AS date)) AS earliest_requested_at, MAX(CAST(de.requested_at AS date)) AS latest_requested_at, COUNT(*) FILTER (WHERE de.status = 'completed') AS completed_export_count, AVG(de.row_count) FILTER (WHERE de.row_count IS NOT NULL) AS avg_row_count ROUND(AVG(de.row_count) FILTER (WHERE de.row_count IS NOT NULL), 1) AS avg_row_countFROM data_exports deLEFT JOIN data_subject_requests dsr ON dsr.data_export_id = de.idGROUP BY 1ORDER BY linked_to_dsar ASCORDER BY linked_to_dsar DESC
“This query compiles correctly and matches the requested grain, columns, and ordering (Y before N).”
“Submitted. One note for Priyanka: the catalog doesn't enumerate fixed status codes for data_exports.status, so I used 'completed' as the "finished" indicator — flagged this in the submission note in case the warehouse uses a different literal value.”
As Priyanka Desai saw it, from the small dataset the agents worked on
linked_to_dsar | export_count | distinct_subject_users | earliest_requested_at | latest_requested_at | completed_export_count | avg_row_count ---------------+--------------+------------------------+-----------------------+---------------------+------------------------+-------------- Y | 11 | 11 | 2022-08-25 | 2025-09-01 | 0 | 1456.1 (1 rows)
The memo and the queries that followed; links found by title, so inferred
Later queries that build on this report:
It builds on:
Changed only where the benchmark needs the same result on every system
SELECT CASE WHEN dsr.id IS NOT NULL THEN 'Y' ELSE 'N' END AS linked_to_dsar, COUNT(*) AS export_count, COUNT(DISTINCT de.subject_user_id) AS distinct_subject_users, MIN(CAST(de.requested_at AS date)) AS earliest_requested_at, MAX(CAST(de.requested_at AS date)) AS latest_requested_at, COUNT(*) FILTER (WHERE de.status = 'completed') AS completed_export_count, ROUND(AVG(de.row_count) FILTER (WHERE de.row_count IS NOT NULL), 1) AS avg_row_count AVG(CAST(de.row_count AS NUMERIC(18,6))) FILTER (WHERE de.row_count IS NOT NULL) AS avg_row_countFROM data_exports deLEFT JOIN data_subject_requests dsr ON dsr.data_export_id = de.idGROUP BY 1ORDER BY linked_to_dsar DESCORDER BY linked_to_dsar DESC NULLS FIRST
SELECT CASE WHEN dsr.id IS NOT NULL THEN 'Y' ELSE 'N' END AS linked_to_dsar, COUNT(*) AS export_count, COUNT(DISTINCT de.subject_user_id) AS distinct_subject_users, MIN(CAST(de.requested_at AS date)) AS earliest_requested_at, MAX(CAST(de.requested_at AS date)) AS latest_requested_at, COUNT(*) FILTER (WHERE de.status = 'completed') AS completed_export_count, AVG(CAST(de.row_count AS NUMERIC(18,6))) FILTER (WHERE de.row_count IS NOT NULL) AS avg_row_count FROM data_exports de LEFT JOIN data_subject_requests dsr ON dsr.data_export_id = de.id GROUP BY 1 ORDER BY linked_to_dsar DESC NULLS FIRST
The agents' own records, as the role play wrote them; only the personal data of the person who ran it is redacted
Report request from Priyanka Desai (analyst for the Data Protection Officer): Title: Portability requests: formal DSAR count vs. unlinked data exports Question: Is 'zero portability requests in 3 years' a real zero, or are shopper data exports being fulfilled outside the formal DSAR process (a logging blind spot)? Grain: one row per linked-to-DSAR status (2 rows: linked, not linked) Columns: - linked to a data_subject_requests record (Y/N) - export count - distinct subject users (count of distinct subject_user_id) - earliest requested_at (date) - latest requested_at (date) - completed export count (status indicating the export finished) - average row_count (for exports where row_count is not null) Details: Source: data_exports LEFT JOIN data_subject_requests ON data_subject_requests.data_export_id = data_exports.id. 'Linked' = a matching data_subject_requests row exists (regardless of that row's request_type); 'not linked' = no such row. Platform-wide, full history, no date filter. If the 'not linked' row has a meaningful count, that indicates shopper data exports happen outside the DSAR table entirely, which would mean request_type='portability' showing 0 in data_subject_requests is a process/logging gap rather than genuine zero demand. Order by linked_to_dsar (Y first, then N). Check your query with the warehouse's dry_run tool, then hand it in with submit.
dry_runSELECT CASE WHEN dsr.id IS NOT NULL THEN 'Y' ELSE 'N' END AS linked_to_dsar, COUNT(*) AS export_count, COUNT(DISTINCT de.subject_user_id) AS distinct_subject_users, MIN(de.requested_at) AS earliest_requested_at, MAX(de.requested_at) AS latest_requested_at, COUNT(*) FILTER (WHERE de.status = 'completed') AS completed_export_count, AVG(de.row_count) FILTER (WHERE de.row_count IS NOT NULL) AS avg_row_count FROM data_exports de LEFT JOIN data_subject_requests dsr ON dsr.data_export_id = de.id GROUP BY 1 ORDER BY linked_to_dsar ASC
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: linked_to_dsar, export_count, distinct_subject_users, earliest_requested_at, latest_requested_at, completed_export_count, avg_row_count
dry_runSELECT CASE WHEN dsr.id IS NOT NULL THEN 'Y' ELSE 'N' END AS linked_to_dsar, COUNT(*) AS export_count, COUNT(DISTINCT de.subject_user_id) AS distinct_subject_users, MIN(CAST(de.requested_at AS date)) AS earliest_requested_at, MAX(CAST(de.requested_at AS date)) AS latest_requested_at, COUNT(*) FILTER (WHERE de.status = 'completed') AS completed_export_count, ROUND(AVG(de.row_count) FILTER (WHERE de.row_count IS NOT NULL), 1) AS avg_row_count FROM data_exports de LEFT JOIN data_subject_requests dsr ON dsr.data_export_id = de.id GROUP BY 1 ORDER BY linked_to_dsar DESC
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: linked_to_dsar, export_count, distinct_subject_users, earliest_requested_at, latest_requested_at, completed_export_count, avg_row_count
submitSELECT CASE WHEN dsr.id IS NOT NULL THEN 'Y' ELSE 'N' END AS linked_to_dsar, COUNT(*) AS export_count, COUNT(DISTINCT de.subject_user_id) AS distinct_subject_users, MIN(CAST(de.requested_at AS date)) AS earliest_requested_at, MAX(CAST(de.requested_at AS date)) AS latest_requested_at, COUNT(*) FILTER (WHERE de.status = 'completed') AS completed_export_count, ROUND(AVG(de.row_count) FILTER (WHERE de.row_count IS NOT NULL), 1) AS avg_row_count FROM data_exports de LEFT JOIN data_subject_requests dsr ON dsr.data_export_id = de.id GROUP BY 1 ORDER BY linked_to_dsar DESC
Submitted to tonight's batch. Your work on this request is done.