Queries · q280 · Head of Risk and Payments · week 2, night 2
“How many compliance/security incidents have been flagged, how severe are they, how long do they typically take to resolve, and is anything still sitting open a long time?”
Fictional people at a fictional company, each played by a language-model agent. Who they are and what they are told
Week 1; shared with Rosa Van den Berg, Head of Data
An enterprise prospect sent a security and compliance questionnaire covering account security, access control, privacy requests, data retention and audit trails. Sales wants answers backed by evidence from our own systems, not by policy documents.
The manager, at the start of week 2
Subject: Re: Questionnaire memo — good work, a few threads to pull
Claire,
This is a strong memo, and the gaps you flagged are exactly the kind of thing I'd rather surface now than have a prospect's security team find later. A few things stand out and I want to understand them better before we finalize anything.
The February 2025 login spike is the one that worries me most. A 10x jump in failures with no corresponding rise in flagged suspicious activity is a real detection gap, not just a reporting curiosity — if that was a credential-stuffing attempt, I need to know whether it actually got anywhere.
Questions for this week:
Keep the role/permission logging gap and the broken query as noted — no need to keep spending slots chasing those; I'll raise the first with engineering myself and we can note the second as a known limitation in the memo.
Thanks, Aylin
The analyst · week 2, night 2, request 2 of 3
Night 1 results need a careful read before I pass anything to Aylin:
Report 1 (concentration by merchant) is clean and striking: 3 merchants — Cragsley Outfitters (4,407 failed, 88.1% fail rate), Marrowby & Vane (1,978 failed, 86.1%), Studio Vrij (783 failed, 84.2%) — account for ~7,168 of the ~7,429 failed attempts we found last week (96.5%), while the next-highest merchant had just 17 failed attempts. These are the exact same three merchants Support Ops called out last week as having both the heaviest ticket volume and worst response times. But the pattern doesn't look like a credential-stuffing attack: distinct_emails_with_any_failed_attempt is nearly as high as total failed attempts (e.g. Cragsley: 2,786 distinct emails for 4,407 fails, max 8 fails on any single email, max 9 from any single IP, spread across 1,903 distinct IPs). That's wide, shallow failure across many accounts and many sources at once — much more consistent with a broken login flow/client bug affecting those 3 merchants specifically than with an external attack.
Report 2 (near-miss outcomes) is internally broken and I will not pass these numbers on: percentages are computed against a fixed denominator of 102, but the bucket counts (42+12+89+452=595) don't come close to summing to 102, and the engineer's note confirms email was matched globally rather than scoped per tenant (login_attempts.email is only unique per tenant) — so the "cohort" silently grew across tenants mid-query while the percentage base didn't update. This is exactly the kind of actor/identity mismatch Rosa flagged in week 1, now showing up in a different table. I'm retrying tonight with tenant-scoped (tenant_id, email) pairs and mutually-exclusive buckets that must sum to the true cohort size.
Report 3 (DSAR via updated_at) looks solid and resolves Aylin's question #2: all 3 rejected DSARs show updated_at well inside their 30-day due_at window (8.7, 3.8, and 23.7 days after receipt respectively) — plausibly within deadline, with the usual caveat that updated_at isn't a verified "decision" timestamp.
Tonight (3 slots): (1) retry the near-miss report correctly; (2) incidents/compliance_events volume, severity, and resolution time (question #3, deferred from last week); (3) how far back the five retention-policy-less tables actually go, to size the exposure for question #4.
The engineer · 1 dry run on the empty schema, then the submission
SELECT severity, count(*) AS total_events, count(*) FILTER (WHERE resolved) AS resolved_count, count(*) FILTER (WHERE NOT resolved) AS unresolved_count, round(100.0 * count(*) FILTER (WHERE resolved) / NULLIF(count(*), 0), 1) AS pct_resolved, round(avg(extract(epoch FROM (resolved_at - occurred_at)) / 86400.0) FILTER (WHERE resolved), 1) AS avg_days_to_resolve, round( (percentile_cont(0.5) WITHIN GROUP (ORDER BY extract(epoch FROM (resolved_at - occurred_at)) / 86400.0) FILTER (WHERE resolved))::numeric, 1 ) AS median_days_to_resolve, round( max(CASE WHEN NOT resolved THEN extract(epoch FROM (timestamp '2025-12-31' - occurred_at)) / 86400.0 END)::numeric, 0 ) AS oldest_unresolved_age_days, count(DISTINCT event_type) AS distinct_event_types FROM compliance_events WHERE occurred_at >= timestamp '2023-01-01' AND occurred_at < timestamp '2026-01-01' GROUP BY severity ORDER BY total_events DESC, severity
“Query compiles cleanly with the expected columns. Submitting.”
“Submitted. The report returns one row per compliance_events.severity with total/resolved/unresolved counts, resolution-rate and resolution-time stats (mean and median days), the oldest unresolved incident's age as of 2025-12-31, and the count of distinct event types — ordered by total volume descending.”
As Claire Dubois saw it, from the small dataset the agents worked on
severity | total_events | resolved_count | unresolved_count | pct_resolved | avg_days_to_resolve | median_days_to_resolve | oldest_unresolved_age_days | distinct_event_types ---------+--------------+----------------+------------------+--------------+---------------------+------------------------+----------------------------+--------------------- high | 4 | 4 | 0 | 100.0 | 7.2 | 9.6 | NULL | 2 medium | 4 | 4 | 0 | 100.0 | 17.9 | 19.4 | NULL | 1 (2 rows)
Changed only where the benchmark needs the same result on every system
SELECT severity, count(*) AS total_events, count(*) FILTER (WHERE resolved) AS resolved_count, count(*) FILTER (WHERE NOT resolved) AS unresolved_count, round(100.0 * count(*) FILTER (WHERE resolved) / NULLIF(count(*), 0), 1) AS pct_resolved, round(avg(extract(epoch FROM (resolved_at - occurred_at)) / 86400.0) FILTER (WHERE resolved), 1) AS avg_days_to_resolve, round( (percentile_cont(0.5) WITHIN GROUP (ORDER BY extract(epoch FROM (resolved_at - occurred_at)) / 86400.0) FILTER (WHERE resolved))::numeric, 1 ) AS median_days_to_resolve, round( max(CASE WHEN NOT resolved THEN extract(epoch FROM (timestamp '2025-12-31' - occurred_at)) / 86400.0 END)::numeric, 0 ) AS oldest_unresolved_age_days, 100.0 * count(*) FILTER (WHERE resolved) / NULLIF(count(*), 0) AS pct_resolved, avg({{seconds_between(occurred_at, resolved_at)}} / 86400.0) FILTER (WHERE resolved) AS avg_days_to_resolve, ({{percentile_cont_filter(0.5, {{seconds_between(occurred_at, resolved_at)}} / 86400.0, resolved)}}) AS median_days_to_resolve, max(CASE WHEN NOT resolved THEN {{seconds_between(occurred_at, timestamp '2025-12-31')}} / 86400.0 END) AS oldest_unresolved_age_days, count(DISTINCT event_type) AS distinct_event_typesFROM compliance_eventsWHERE occurred_at >= timestamp '2023-01-01' AND occurred_at < timestamp '2026-01-01'GROUP BY severityORDER BY total_events DESC, severityORDER BY total_events DESC NULLS FIRST, severity NULLS LAST
SELECT severity, count(*) AS total_events, count(*) FILTER (WHERE resolved) AS resolved_count, count(*) FILTER (WHERE NOT resolved) AS unresolved_count, 100.0 * count(*) FILTER (WHERE resolved) / NULLIF(count(*), 0) AS pct_resolved, avg(CAST(EXTRACT(EPOCH FROM (resolved_at - occurred_at)) AS NUMERIC(18,6)) / 86400.0) FILTER (WHERE resolved) AS avg_days_to_resolve, (PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CAST(EXTRACT(EPOCH FROM (resolved_at - occurred_at)) AS NUMERIC(18,6)) / 86400.0) FILTER (WHERE resolved)) AS median_days_to_resolve, max(CASE WHEN NOT resolved THEN CAST(EXTRACT(EPOCH FROM (timestamp '2025-12-31' - occurred_at)) AS NUMERIC(18,6)) / 86400.0 END) AS oldest_unresolved_age_days, count(DISTINCT event_type) AS distinct_event_types FROM compliance_events WHERE occurred_at >= timestamp '2023-01-01' AND occurred_at < timestamp '2026-01-01' GROUP BY severity ORDER BY total_events DESC NULLS FIRST, severity NULLS LAST
SELECT severity, count(*) AS total_events, count(*) FILTER (WHERE resolved) AS resolved_count, count(*) FILTER (WHERE NOT resolved) AS unresolved_count, 100.0 * count(*) FILTER (WHERE resolved) / NULLIF(count(*), 0) AS pct_resolved, avg(CAST(EXTRACT(EPOCH FROM (resolved_at - occurred_at)) AS NUMERIC(18,6)) / 86400.0) FILTER (WHERE resolved) AS avg_days_to_resolve, (PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CASE WHEN resolved THEN CAST(EXTRACT(EPOCH FROM (resolved_at - occurred_at)) AS NUMERIC(18,6)) / 86400.0 END)) AS median_days_to_resolve, max(CASE WHEN NOT resolved THEN CAST(EXTRACT(EPOCH FROM (timestamp '2025-12-31' - occurred_at)) AS NUMERIC(18,6)) / 86400.0 END) AS oldest_unresolved_age_days, count(DISTINCT event_type) AS distinct_event_types FROM compliance_events WHERE occurred_at >= timestamp '2023-01-01' AND occurred_at < timestamp '2026-01-01' GROUP BY severity ORDER BY total_events DESC NULLS FIRST, severity NULLS LAST
SELECT severity, count(*) AS total_events, count(*) FILTER (WHERE resolved) AS resolved_count, count(*) FILTER (WHERE NOT resolved) AS unresolved_count, 100.0 * count(*) FILTER (WHERE resolved) / NULLIF(count(*), 0) AS pct_resolved, avg(CAST(EXTRACT(EPOCH FROM (resolved_at - occurred_at)) AS NUMERIC(18,6)) / 86400.0) FILTER (WHERE resolved) AS avg_days_to_resolve, (PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY CAST(CAST(EXTRACT(EPOCH FROM (resolved_at - occurred_at)) AS NUMERIC(18,6)) / 86400.0 AS DOUBLE PRECISION)) FILTER (WHERE resolved)) AS median_days_to_resolve, max(CASE WHEN NOT resolved THEN CAST(EXTRACT(EPOCH FROM (timestamp '2025-12-31' - occurred_at)) AS NUMERIC(18,6)) / 86400.0 END) AS oldest_unresolved_age_days, count(DISTINCT event_type) AS distinct_event_types FROM compliance_events WHERE occurred_at >= timestamp '2023-01-01' AND occurred_at < timestamp '2026-01-01' GROUP BY severity ORDER BY total_events DESC NULLS FIRST, severity NULLS LAST
SELECT severity, count(*) AS total_events, count(*) FILTER (WHERE resolved) AS resolved_count, count(*) FILTER (WHERE NOT resolved) AS unresolved_count, 100.0 * count(*) FILTER (WHERE resolved) / NULLIF(count(*), 0) AS pct_resolved, avg(CAST(divideDecimal(toDecimal128(dateDiff('microsecond', occurred_at, resolved_at), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 86400.0) FILTER (WHERE resolved) AS avg_days_to_resolve, (quantileExactInclusiveIf(0.5)(toFloat64(CAST(divideDecimal(toDecimal128(dateDiff('microsecond', occurred_at, resolved_at), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 86400.0), resolved)) AS median_days_to_resolve, max(CASE WHEN NOT resolved THEN CAST(divideDecimal(toDecimal128(dateDiff('microsecond', occurred_at, timestamp '2025-12-31'), 0), toDecimal128(1000000, 0), 6) AS Nullable(Decimal(18, 6))) / 86400.0 END) AS oldest_unresolved_age_days, count(DISTINCT event_type) AS distinct_event_types FROM compliance_events WHERE occurred_at >= timestamp '2023-01-01' AND occurred_at < timestamp '2026-01-01' GROUP BY severity ORDER BY total_events DESC NULLS FIRST, severity 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 Claire Dubois (analyst for the Head of Risk and Payments): Title: Flagged compliance/security incidents: volume, severity and resolution time Question: How many compliance/security incidents have been flagged, how severe are they, how long do they typically take to resolve, and is anything still sitting open a long time? Grain: one row per compliance_events.severity value, across all tenants, all occurred_at dates from 2023-01-01 through 2025-12-31 Columns: - severity - total_events - resolved_count - unresolved_count - pct_resolved - avg_days_to_resolve (resolved_at - occurred_at, resolved events only) - median_days_to_resolve - oldest_unresolved_age_days (2025-12-31 minus occurred_at, for the oldest unresolved event in this severity) - distinct_event_types Details: Source: compliance_events. Use 2025-12-31 as the fixed reference date for age calculations (not CURRENT_DATE). This is the deferred 'incidents' question from last week — want a clear read on total volume, how severity breaks down, typical time-to-close, and whether any severity tier has old unresolved events that would look bad to a security reviewer. Order by total_events descending. Check your query with the warehouse's dry_run tool, then hand it in with submit.
dry_runSELECT severity, count(*) AS total_events, count(*) FILTER (WHERE resolved) AS resolved_count, count(*) FILTER (WHERE NOT resolved) AS unresolved_count, round(100.0 * count(*) FILTER (WHERE resolved) / NULLIF(count(*), 0), 1) AS pct_resolved, round(avg(extract(epoch FROM (resolved_at - occurred_at)) / 86400.0) FILTER (WHERE resolved), 1) AS avg_days_to_resolve, round( (percentile_cont(0.5) WITHIN GROUP (ORDER BY extract(epoch FROM (resolved_at - occurred_at)) / 86400.0) FILTER (WHERE resolved))::numeric, 1 ) AS median_days_to_resolve, round( max(CASE WHEN NOT resolved THEN extract(epoch FROM (timestamp '2025-12-31' - occurred_at)) / 86400.0 END)::numeric, 0 ) AS oldest_unresolved_age_days, count(DISTINCT event_type) AS distinct_event_types FROM compliance_events WHERE occurred_at >= timestamp '2023-01-01' AND occurred_at < timestamp '2026-01-01' GROUP BY severity ORDER BY total_events DESC, severity
STATUS: COMPILES (dry run on the empty schema, no data read) COLUMNS: severity, total_events, resolved_count, unresolved_count, pct_resolved, avg_days_to_resolve, median_days_to_resolve, oldest_unresolved_age_days, distinct_event_types
submitSELECT severity, count(*) AS total_events, count(*) FILTER (WHERE resolved) AS resolved_count, count(*) FILTER (WHERE NOT resolved) AS unresolved_count, round(100.0 * count(*) FILTER (WHERE resolved) / NULLIF(count(*), 0), 1) AS pct_resolved, round(avg(extract(epoch FROM (resolved_at - occurred_at)) / 86400.0) FILTER (WHERE resolved), 1) AS avg_days_to_resolve, round( (percentile_cont(0.5) WITHIN GROUP (ORDER BY extract(epoch FROM (resolved_at - occurred_at)) / 86400.0) FILTER (WHERE resolved))::numeric, 1 ) AS median_days_to_resolve, round( max(CASE WHEN NOT resolved THEN extract(epoch FROM (timestamp '2025-12-31' - occurred_at)) / 86400.0 END)::numeric, 0 ) AS oldest_unresolved_age_days, count(DISTINCT event_type) AS distinct_event_types FROM compliance_events WHERE occurred_at >= timestamp '2023-01-01' AND occurred_at < timestamp '2026-01-01' GROUP BY severity ORDER BY total_events DESC, severity
Submitted to tonight's batch. Your work on this request is done.