Queries · q328 · Data Protection Officer · week 2, night 2
“For the 3 accounts under a payment-dispute legal hold, what internal evidence (payment status, support ticket activity) suggests whether the underlying dispute is still active or has gone stale?”
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 2, request 3 of 3
Two things from last night need resolving before I trust the numbers for the regulator: (1) report 1 showed DSAR received_at dates as early as 2020-04-24 and 2022-08-25 — before the stated 2023-01-01 warehouse history start — which could be legitimate backfilled compliance records carried over from the pre-migration system, or could be a data quality artifact; I need to see these specific rows and compare received_at to created_at to tell which. (2) report 2 showed all 11 data_exports linked to a DSAR, but completed_export_count was 0, and the engineer flagged uncertainty about whether 'completed' is even the right status string for that table — if genuinely zero exports ever finished, that's a real fulfillment failure (shoppers who filed access requests may never have actually received their data), separate from the "portability doesn't exist as a request_type" question I already closed. Both are quick, cheap diagnostics. With priorities 2 and 4 now largely resolved (volumes reconcile to 60 pending the pre-2023 check; retention is fine everywhere except email_log, confirmed last night), I'm using my third slot to start priority 1 for Ines: looking for whatever internal evidence exists (payment status, support ticket activity) on the 3 held accounts, since there's no explicit "dispute" table and the legal holds aren't linked to a specific order/payment — I have to reconstruct it from the subject_user_id.
The engineer · 1 dry run on the empty schema, then the submission
WITH holds AS ( SELECT lh.id AS legal_hold_id, lh.tenant_id, lh.subject_user_id, lh.placed_at, t.name AS tenant_name FROM legal_holds lh JOIN tenants t ON t.id = lh.tenant_id WHERE lh.is_active = true AND t.name IN ('Marrowby & Vane', 'Rue Lafayette Studio', 'Cragsley Outfitters') ), latest_payment AS ( SELECT h.legal_hold_id, p.status, p.updated_at, p.amount, p.currency_code, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY p.updated_at DESC) AS rn FROM holds h JOIN orders o ON o.user_id = h.subject_user_id AND o.tenant_id = h.tenant_id JOIN payments p ON p.order_id = o.id ), latest_payment_top AS ( SELECT * FROM latest_payment WHERE rn = 1 ), open_tickets AS ( SELECT h.legal_hold_id, COUNT(*) AS open_ticket_count FROM holds h JOIN tickets tk ON tk.requester_user_id = h.subject_user_id AND tk.tenant_id = h.tenant_id WHERE tk.status NOT IN ('solved','closed') GROUP BY h.legal_hold_id ), latest_ticket AS ( SELECT h.legal_hold_id, tk.last_message_at, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY tk.last_message_at DESC) AS rn FROM holds h JOIN tickets tk ON tk.requester_user_id = h.subject_user_id AND tk.tenant_id = h.tenant_id ), latest_ticket_top AS ( SELECT * FROM latest_ticket WHERE rn = 1 ) SELECT h.tenant_name, h.subject_user_id, CAST(h.placed_at AS date) AS hold_placed_date, DATE '2026-01-05' - CAST(h.placed_at AS date) AS days_since_placed, lp.status AS latest_payment_status, CAST(lp.updated_at AS date) AS latest_payment_updated_date, lp.amount AS latest_payment_amount, lp.currency_code AS latest_payment_currency, COALESCE(ot.open_ticket_count, 0) AS open_ticket_count, CAST(lt.last_message_at AS date) AS latest_ticket_activity_date, DATE '2026-01-05' - CAST(lt.last_message_at AS date) AS days_since_ticket_activity FROM holds h LEFT JOIN latest_payment_top lp ON lp.legal_hold_id = h.legal_hold_id LEFT JOIN open_tickets ot ON ot.legal_hold_id = h.legal_hold_id LEFT JOIN latest_ticket_top lt ON lt.legal_hold_id = h.legal_hold_id ORDER BY days_since_placed DESC, h.tenant_name
“Query compiles with the expected 11 columns at the requested grain. Submitting.”
“Submitted. The report joins the 3 payment-dispute legal holds (Marrowby & Vane, Rue Lafayette Studio, Cragsley Outfitters) to each subject user's most recent payment and ticket activity as indirect evidence of dispute freshness, ordered by days since the hold was placed (most stale first). Note: since legal_holds doesn't link to a specific payment/ticket for these rows, this is an approximation using the user's overall latest activity, as I flagged in the submit note.”
As Priyanka Desai saw it, from the small dataset the agents worked on
tenant_name | subject_user_id | hold_placed_date | days_since_placed | latest_payment_status | latest_payment_updated_date | latest_payment_amount | latest_payment_currency | open_ticket_count | latest_ticket_activity_date | days_since_ticket_activity ---------------------+-----------------+------------------+-------------------+-----------------------+-----------------------------+-----------------------+-------------------------+-------------------+-----------------------------+--------------------------- Marrowby & Vane | 1933 | 2023-05-27 | 954 | partially_refunded | 2025-03-11 | 530.9000 | GBP | 0 | NULL | NULL Rue Lafayette Studio | 5911 | 2024-07-10 | 544 | captured | 2024-12-05 | 86.9900 | EUR | 0 | NULL | NULL Cragsley Outfitters | 6446 | 2025-03-22 | 289 | partially_refunded | 2025-07-08 | 90.9100 | USD | 0 | NULL | NULL (3 rows)
The memo and the queries that followed; links found by title, so inferred
It builds on:
Changed only where the benchmark needs the same result on every system
WITH holds AS ( SELECT lh.id AS legal_hold_id, lh.tenant_id, lh.subject_user_id, lh.placed_at, t.name AS tenant_name SELECT lh.id AS legal_hold_id, lh.tenant_id AS tenant_id, lh.subject_user_id AS subject_user_id, lh.placed_at AS placed_at, t.name AS tenant_name FROM legal_holds lh JOIN tenants t ON t.id = lh.tenant_id WHERE lh.is_active = true AND t.name IN ('Marrowby & Vane', 'Rue Lafayette Studio', 'Cragsley Outfitters')),latest_payment AS ( SELECT h.legal_hold_id, p.status, p.updated_at, p.amount, p.currency_code, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY p.updated_at DESC) AS rn SELECT h.legal_hold_id AS legal_hold_id, p.status AS status, p.updated_at AS updated_at, p.amount AS amount, p.currency_code AS currency_code, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY p.updated_at DESC NULLS FIRST, p.id DESC NULLS FIRST) AS rn FROM holds h JOIN orders o ON o.user_id = h.subject_user_id AND o.tenant_id = h.tenant_id JOIN payments p ON p.order_id = o.id),latest_payment_top AS ( SELECT * FROM latest_payment WHERE rn = 1),open_tickets AS ( SELECT h.legal_hold_id, COUNT(*) AS open_ticket_count SELECT h.legal_hold_id AS legal_hold_id, COUNT(*) AS open_ticket_count FROM holds h JOIN tickets tk ON tk.requester_user_id = h.subject_user_id AND tk.tenant_id = h.tenant_id WHERE tk.status NOT IN ('solved','closed') GROUP BY h.legal_hold_id),latest_ticket AS ( SELECT h.legal_hold_id, tk.last_message_at, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY tk.last_message_at DESC) AS rn SELECT h.legal_hold_id AS legal_hold_id, tk.last_message_at AS last_message_at, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY tk.last_message_at DESC NULLS FIRST) AS rn FROM holds h JOIN tickets tk ON tk.requester_user_id = h.subject_user_id AND tk.tenant_id = h.tenant_id),latest_ticket_top AS ( SELECT * FROM latest_ticket WHERE rn = 1)SELECT h.tenant_name, h.subject_user_id, h.tenant_name AS tenant_name, h.subject_user_id AS subject_user_id, CAST(h.placed_at AS date) AS hold_placed_date, DATE '2026-01-05' - CAST(h.placed_at AS date) AS days_since_placed, lp.status AS latest_payment_status, CAST(lp.updated_at AS date) AS latest_payment_updated_date, lp.amount AS latest_payment_amount, lp.currency_code AS latest_payment_currency, COALESCE(ot.open_ticket_count, 0) AS open_ticket_count, CAST(lt.last_message_at AS date) AS latest_ticket_activity_date, DATE '2026-01-05' - CAST(lt.last_message_at AS date) AS days_since_ticket_activityFROM holds hLEFT JOIN latest_payment_top lp ON lp.legal_hold_id = h.legal_hold_idLEFT JOIN open_tickets ot ON ot.legal_hold_id = h.legal_hold_idLEFT JOIN latest_ticket_top lt ON lt.legal_hold_id = h.legal_hold_idORDER BY days_since_placed DESC, h.tenant_nameORDER BY days_since_placed DESC NULLS FIRST, h.tenant_name NULLS LAST, h.legal_hold_id NULLS LAST
WITH holds AS ( SELECT lh.id AS legal_hold_id, lh.tenant_id AS tenant_id, lh.subject_user_id AS subject_user_id, lh.placed_at AS placed_at, t.name AS tenant_name FROM legal_holds lh JOIN tenants t ON t.id = lh.tenant_id WHERE lh.is_active = true AND t.name IN ('Marrowby & Vane', 'Rue Lafayette Studio', 'Cragsley Outfitters') ), latest_payment AS ( SELECT h.legal_hold_id AS legal_hold_id, p.status AS status, p.updated_at AS updated_at, p.amount AS amount, p.currency_code AS currency_code, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY p.updated_at DESC NULLS FIRST, p.id DESC NULLS FIRST) AS rn FROM holds h JOIN orders o ON o.user_id = h.subject_user_id AND o.tenant_id = h.tenant_id JOIN payments p ON p.order_id = o.id ), latest_payment_top AS ( SELECT * FROM latest_payment WHERE rn = 1 ), open_tickets AS ( SELECT h.legal_hold_id AS legal_hold_id, COUNT(*) AS open_ticket_count FROM holds h JOIN tickets tk ON tk.requester_user_id = h.subject_user_id AND tk.tenant_id = h.tenant_id WHERE tk.status NOT IN ('solved','closed') GROUP BY h.legal_hold_id ), latest_ticket AS ( SELECT h.legal_hold_id AS legal_hold_id, tk.last_message_at AS last_message_at, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY tk.last_message_at DESC NULLS FIRST) AS rn FROM holds h JOIN tickets tk ON tk.requester_user_id = h.subject_user_id AND tk.tenant_id = h.tenant_id ), latest_ticket_top AS ( SELECT * FROM latest_ticket WHERE rn = 1 ) SELECT h.tenant_name AS tenant_name, h.subject_user_id AS subject_user_id, CAST(h.placed_at AS date) AS hold_placed_date, DATE '2026-01-05' - CAST(h.placed_at AS date) AS days_since_placed, lp.status AS latest_payment_status, CAST(lp.updated_at AS date) AS latest_payment_updated_date, lp.amount AS latest_payment_amount, lp.currency_code AS latest_payment_currency, COALESCE(ot.open_ticket_count, 0) AS open_ticket_count, CAST(lt.last_message_at AS date) AS latest_ticket_activity_date, DATE '2026-01-05' - CAST(lt.last_message_at AS date) AS days_since_ticket_activity FROM holds h LEFT JOIN latest_payment_top lp ON lp.legal_hold_id = h.legal_hold_id LEFT JOIN open_tickets ot ON ot.legal_hold_id = h.legal_hold_id LEFT JOIN latest_ticket_top lt ON lt.legal_hold_id = h.legal_hold_id ORDER BY days_since_placed DESC NULLS FIRST, h.tenant_name NULLS LAST, h.legal_hold_id NULLS LAST
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: Evidence check on the 3 legal-hold-linked chargeback disputes Question: For the 3 accounts under a payment-dispute legal hold, what internal evidence (payment status, support ticket activity) suggests whether the underlying dispute is still active or has gone stale? Grain: one row per legal hold (3 rows: Marrowby & Vane, Rue Lafayette Studio, Cragsley Outfitters) Columns: - tenant name - subject_user_id - hold placed_at (date) - days since placed (2026-01-05 minus placed_at) - latest payment status for this user (most recent by updated_at, via orders.user_id = subject_user_id joined to payments) - latest payment updated_at (date) - latest payment amount and currency - open ticket count for this user (tickets.requester_user_id, status not in solved/closed) - most recent ticket last_message_at for this user (date) - days since that last ticket activity Details: Source: legal_holds (the 3 active rows identified last week) joined by subject_user_id to orders -> payments (most recent payment by updated_at for that user) and to tickets (requester_user_id = subject_user_id). legal_holds has no populated entity_schema/entity_table/entity_id for these rows, so there's no direct link to a specific disputed payment — this report approximates by looking at the user's most recent payment activity and most recent support ticket activity as indirect evidence of whether the case is still live or has gone quiet. Use literal date 2026-01-05 for age math. Order by days since placed, descending. Check your query with the warehouse's dry_run tool, then hand it in with submit.
dry_runWITH holds AS ( SELECT lh.id AS legal_hold_id, lh.tenant_id, lh.subject_user_id, lh.placed_at, t.name AS tenant_name FROM legal_holds lh JOIN tenants t ON t.id = lh.tenant_id WHERE lh.is_active = true AND t.name IN ('Marrowby & Vane', 'Rue Lafayette Studio', 'Cragsley Outfitters') ), latest_payment AS ( SELECT h.legal_hold_id, p.status, p.updated_at, p.amount, p.currency_code, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY p.updated_at DESC) AS rn FROM holds h JOIN orders o ON o.user_id = h.subject_user_id AND o.tenant_id = h.tenant_id JOIN payments p ON p.order_id = o.id ), latest_payment_top AS ( SELECT * FROM latest_payment WHERE rn = 1 ), open_tickets AS ( SELECT h.legal_hold_id, COUNT(*) AS open_ticket_count FROM holds h JOIN tickets tk ON tk.requester_user_id = h.subject_user_id AND tk.tenant_id = h.tenant_id WHERE tk.status NOT IN ('solved','closed') GROUP BY h.legal_hold_id ), latest_ticket AS ( SELECT h.legal_hold_id, tk.last_message_at, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY tk.last_message_at DESC) AS rn FROM holds h JOIN tickets tk ON tk.requester_user_id = h.subject_user_id AND tk.tenant_id = h.tenant_id ), latest_ticket_top AS ( SELECT * FROM latest_ticket WHERE rn = 1 ) SELECT h.tenant_name, h.subject_user_id, CAST(h.placed_at AS date) AS hold_placed_date, DATE '2026-01-05' - CAST(h.placed_at AS date) AS days_since_placed, lp.status AS latest_payment_status, CAST(lp.updated_at AS date) AS latest_payment_updated_date, lp.amount AS latest_payment_amount, lp.currency_code AS latest_payment_currency, COALESCE(ot.open_ticket_count, 0) AS open_ticket_count, CAST(lt.last_message_at AS date) AS latest_ticket_activity_date, DATE '2026-01-05' - CAST(lt.last_message_at AS date) AS days_since_ticket_activity FROM holds h LEFT JOIN latest_payment_top lp ON lp.legal_hold_id = h.legal_hold_id LEFT JOIN open_tickets ot ON ot.legal_hold_id = h.legal_hold_id LEFT JOIN latest_ticket_top lt ON lt.legal_hold_id = h.legal_hold_id ORDER BY days_since_placed DESC, h.tenant_name
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: tenant_name, subject_user_id, hold_placed_date, days_since_placed, latest_payment_status, latest_payment_updated_date, latest_payment_amount, latest_payment_currency, open_ticket_count, latest_ticket_activity_date, days_since_ticket_activity
submitWITH holds AS ( SELECT lh.id AS legal_hold_id, lh.tenant_id, lh.subject_user_id, lh.placed_at, t.name AS tenant_name FROM legal_holds lh JOIN tenants t ON t.id = lh.tenant_id WHERE lh.is_active = true AND t.name IN ('Marrowby & Vane', 'Rue Lafayette Studio', 'Cragsley Outfitters') ), latest_payment AS ( SELECT h.legal_hold_id, p.status, p.updated_at, p.amount, p.currency_code, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY p.updated_at DESC) AS rn FROM holds h JOIN orders o ON o.user_id = h.subject_user_id AND o.tenant_id = h.tenant_id JOIN payments p ON p.order_id = o.id ), latest_payment_top AS ( SELECT * FROM latest_payment WHERE rn = 1 ), open_tickets AS ( SELECT h.legal_hold_id, COUNT(*) AS open_ticket_count FROM holds h JOIN tickets tk ON tk.requester_user_id = h.subject_user_id AND tk.tenant_id = h.tenant_id WHERE tk.status NOT IN ('solved','closed') GROUP BY h.legal_hold_id ), latest_ticket AS ( SELECT h.legal_hold_id, tk.last_message_at, ROW_NUMBER() OVER (PARTITION BY h.legal_hold_id ORDER BY tk.last_message_at DESC) AS rn FROM holds h JOIN tickets tk ON tk.requester_user_id = h.subject_user_id AND tk.tenant_id = h.tenant_id ), latest_ticket_top AS ( SELECT * FROM latest_ticket WHERE rn = 1 ) SELECT h.tenant_name, h.subject_user_id, CAST(h.placed_at AS date) AS hold_placed_date, DATE '2026-01-05' - CAST(h.placed_at AS date) AS days_since_placed, lp.status AS latest_payment_status, CAST(lp.updated_at AS date) AS latest_payment_updated_date, lp.amount AS latest_payment_amount, lp.currency_code AS latest_payment_currency, COALESCE(ot.open_ticket_count, 0) AS open_ticket_count, CAST(lt.last_message_at AS date) AS latest_ticket_activity_date, DATE '2026-01-05' - CAST(lt.last_message_at AS date) AS days_since_ticket_activity FROM holds h LEFT JOIN latest_payment_top lp ON lp.legal_hold_id = h.legal_hold_id LEFT JOIN open_tickets ot ON ot.legal_hold_id = h.legal_hold_id LEFT JOIN latest_ticket_top lt ON lt.legal_hold_id = h.legal_hold_id ORDER BY days_since_placed DESC, h.tenant_name
Submitted to tonight's batch. Your work on this request is done.