DB Olympics

Queries · q072 · Head of Risk and Payments · week 1, night 3

Audit log coverage for identity and access-control tables

“Which identity/access-control tables and actions actually appear in the audit trail, and under what naming — specifically, are role/permission grant and revocation events logged at all?”

Manager · asked the questionAylin DemirHead of Risk and Payments. Previously in fraud operations at a bank. Responsible for payment performance and for keeping bad actors out.
Analyst · planned the reportClaire DuboisTime-series minded: weekly and daily trends, before and after, seasonality.
Engineer · wrote the SQLOmarPragmatic: the shortest correct query, aggregates first and joins dimensions afterwards, FILTER clauses for conditional counts.

Fictional people at a fictional company, each played by a language-model agent. Who they are and what they are told

  1. A business event lands on the Head of Risk and Payments's desk

    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.

  2. Aylin Demir writes to Claire Dubois

    The manager, at the start of week 1

    Subject: Evidence we need for the security/compliance questionnaire (this week's priority)

    Hi Claire,

    This questionnaire is going to a serious enterprise prospect, and Sales wants it backed by evidence from our own systems, not by our policy wording. Rosa and I are co-owning this, so your reports will feed both the questionnaire answers themselves and a broader readiness review Rosa's side is doing. Given you only get a handful of report slots a night, let's be selective and prioritize what a security reviewer will actually probe on. In my own words, here's what I need to know:

    1. Who can get into a tenant's account, and how tightly is that controlled? I want a sense of how access is granted and revoked in practice — e.g. how many people hold elevated/admin-type access, whether access grants look current and reviewed, and whether we can show that departed or inactive staff lose access promptly.
    2. Do we actually enforce strong login security, or does it just sit in a policy doc? I'd like evidence on real login behavior — things like how much of our staff and customer access is protected by extra verification steps, and whether we can see failed-login and suspicious-access patterns being caught rather than ignored.
    3. When someone asks us to access, correct, or delete their data, do we actually meet our own deadlines? This is the one I most expect to be pressed on. I want to see how data subject requests are tracked end to end — how many come in, how quickly they're handled, and whether any are overdue or slipping past the commitments we've made.
    4. Is there a real, tamper-evident trail of who did what, and is it kept long enough to be useful? I want evidence that meaningful changes and sensitive data access are actually being logged consistently (not just in theory), and that we're not quietly losing history before our stated retention period is up.
    5. If something does go wrong, do we notice? I'd like a sense of how many flagged security or compliance incidents we've had, how quickly they get resolved, and whether any are sitting open for a long time — that's the kind of thing a prospect's security team will zero in on.

    Given the slot constraints, please treat questions 3 and 1 as the top two for this week — those are the ones Sales says the prospect calls out by name. If we only get through three of the five, drop 5 first and fold it into next week.

    One more thing: since this report touches account security and personal data, please keep the output at a safe aggregate level — no need to name individual customers or staff unless I specifically ask.

    Let's sync briefly before you lock in Thursday night's batch so nothing gets wasted on a bounced or bundled request.

    Thanks, Aylin

    A note from the Head of Data, Rosa Van den Berg

    Hi Claire,

    Before we hand evidence to this prospect, I'd want us to be confident the underlying records are actually solid enough to stand behind in writing. A few things from my side:

    1. Completeness and continuity of the audit trail itself – do we have reason to believe the security/access logs are gap-free over the period we'd quote, or are there known stretches where logging was thin, delayed, or archived in a way that could make "no incidents" look like "no visibility"?
    2. One actor, one identity – when we pull evidence on who accessed or changed what, can we actually tie an action back to a single, consistent user record, or do duplicate/merged accounts and inconsistent actor references risk us over- or under-counting who touched sensitive data?
    3. Timestamp trust – for anything time-bound (response times on data requests, retention windows, how quickly access was revoked), are we confident the timestamps we'd cite are clean and consistently recorded, rather than something we'd need to caveat?

    If any of these raise flags on your end, I'd rather we know before it goes into a customer-facing answer than after.

    Thanks, Rosa

  3. Claire Dubois plans the night

    The analyst · week 1, night 3, request 1 of 3

    Night 2 take-aways:

    Report 1 (DSAR reconciliation) resolved the mystery well: the "overdue" cases from report 1 night-1 are exactly 3 rejected erasure requests (oldest from 2023-05-26) that were decided but never got a completed_at timestamp written — a record-keeping gap on rejections, not evidence of 2.5-year-old unresolved requests. The 2 remaining open ones (in_progress/received) are from Dec 2025 and not yet due. Good news, but I can't yet tell Aylin when those 3 rejections were actually decided, which is exactly the kind of timestamp gap Rosa warned about — I want to check whether the audit/change-history trail can supply a real decision timestamp before we write anything definitive.

    Report 2 (elevated access by category): reassuring nuance — the 2 deactivated staff retain only the broad baseline categories (catalog/orders/customers/support, ~87-99% of all staff), not the more sensitive ones (billing, compliance, settings, crm — all 0 non-active holders). Combined with last night's avg_roles_per_user ≈ 1.02, this points to a roughly two-tier model: a broad base role nearly everyone has, and a second, more powerful role held by ~45-52% of staff. That's useful signal on "how tightly is access controlled" for the memo.

    Report 3 (role grant/revocation activity) came back as all zeros for all 36 months — either a genuine, serious audit-trail gap on user_roles changes (bad news for both Aylin's "is access reviewed" question and Rosa's audit-completeness concern), or my filter (entity_table = 'user_roles') simply doesn't match how these events are actually logged. I need to find out which before concluding anything, since this is too important a finding to assert on a guess.

    Tonight (3 slots): (1) a diagnostic sweep of audit_log/audit_log_archive entity_table × action for the identity/access-control tables, to find out how (or whether) role and permission changes are actually logged — this resolves last night's zero-result mystery and is foundational before I can answer Aylin's "is access reviewed" question honestly; (2) cross-reference the 3 rejected DSARs against change_history/audit_log to recover a real decision timestamp, closing Rosa's timestamp-trust gap on the one finding most likely to be pressed on; (3) open priority #2 (login security) with real MFA adoption evidence by user segment, since #2 hasn't been touched yet and only one night remains after tonight before the Thursday sync.

    Audit log coverage for identity and access-control tables
    Question
    Which identity/access-control tables and actions actually appear in the audit trail, and under what naming — specifically, are role/permission grant and revocation events logged at all?
    One row per
    combination of entity_table and action, counted across audit_log UNION audit_log_archive, restricted to entity_table in (users, roles, user_roles, role_permissions, permissions, teams, team_members, api_keys, invitations, mfa_devices, sessions, service_accounts, personal_access_tokens), all dates 2023-01-01 through 2025-12-31
    Columns
    • entity_table
    • action
    • row_count
    • distinct_entity_id_count
    • min_occurred_at
    • max_occurred_at
  4. Omar writes the SQL

    The engineer · 1 dry run on the empty schema, then the submission

    Dry run 1 · STATUS: COMPILES (dry run on the empty schema, no data read)
    WITH combined AS (
      SELECT entity_table, action, entity_id, occurred_at
      FROM audit_log
      WHERE occurred_at >= DATE '2023-01-01' AND occurred_at < DATE '2026-01-01'
      UNION ALL
      SELECT entity_table, action, entity_id, occurred_at
      FROM audit_log_archive
      WHERE occurred_at >= DATE '2023-01-01' AND occurred_at < DATE '2026-01-01'
    )
    SELECT
      entity_table,
      action,
      COUNT(*) AS row_count,
      COUNT(DISTINCT entity_id) AS distinct_entity_id_count,
      MIN(occurred_at) AS min_occurred_at,
      MAX(occurred_at) AS max_occurred_at
    FROM combined
    WHERE entity_table IN (
      'users','user',
      'roles','role',
      'user_roles','user_role',
      'role_permissions','role_permission',
      'permissions','permission',
      'teams','team',
      'team_members','team_member',
      'api_keys','api_key',
      'invitations','invitation',
      'mfa_devices','mfa_device',
      'sessions','session',
      'service_accounts','service_account',
      'personal_access_tokens','personal_access_token'
    )
    GROUP BY entity_table, action
    ORDER BY entity_table, action

    “Query compiles. Submitting.”

    Submitted · the SQL of the last dry run, unchanged · Submitted to tonight's batch. Your work on this request is done.

    “Submitted. The query unions audit_log and audit_log_archive, restricts to the 13 requested identity/access-control tables plus their singular/plural counterparts (to catch naming mismatches like user_role vs user_roles), and aggregates row counts, distinct entity IDs, and the occurred_at range per entity_table × action for 2023–2025. This will directly show whether user_roles create/delete events exist under a different spelling, only as update actions, or are genuinely absent.”

  5. The report comes back overnight

    As Claire Dubois saw it, from the small dataset the agents worked on

    entity_table | action | row_count | distinct_entity_id_count | min_occurred_at            | max_occurred_at           
    -------------+--------+-----------+--------------------------+----------------------------+---------------------------
    users        | delete |        31 |                       31 | 2023-05-15 14:13:16.012883 | 2025-11-10 15:18:24.435098
    users        | export |         9 |                        9 | 2023-06-09 16:34:46.861497 | 2025-09-05 15:45:58.012648
    users        | login  |     74825 |                      547 | 2023-01-01 06:38:54.555406 | 2025-12-31 18:11:47.560027
    users        | logout |     22529 |                      546 | 2023-01-01 10:11:26.298722 | 2025-12-31 22:19:26.215605
    users        | update |        39 |                       39 | 2023-11-11 15:54:50.148121 | 2025-10-27 10:28:33       
    (5 rows)
  6. What came of it

    The memo and the queries that followed; links found by title, so inferred

    Later queries that build on this report:

    It builds on:

  7. It becomes benchmark query q072

    Changed only where the benchmark needs the same result on every system

    • determinismthe standard leaves the place of NULLs in a sort to the implementation: NULLS LAST ascending, NULLS FIRST descending, as in the reference results (2×)
    The benchmark text, against the submitted SQL
    WITH combined AS (  SELECT entity_table, action, entity_id, occurred_at  FROM audit_log  WHERE occurred_at >= DATE '2023-01-01' AND occurred_at < DATE '2026-01-01'  UNION ALL  SELECT entity_table, action, entity_id, occurred_at  FROM audit_log_archive  WHERE occurred_at >= DATE '2023-01-01' AND occurred_at < DATE '2026-01-01')SELECT  entity_table,  action,  COUNT(*) AS row_count,  COUNT(DISTINCT entity_id) AS distinct_entity_id_count,  MIN(occurred_at) AS min_occurred_at,  MAX(occurred_at) AS max_occurred_atFROM combinedWHERE entity_table IN (  'users','user',  'roles','role',  'user_roles','user_role',  'role_permissions','role_permission',  'permissions','permission',  'teams','team',  'team_members','team_member',  'api_keys','api_key',  'invitations','invitation',  'mfa_devices','mfa_device',  'sessions','session',  'service_accounts','service_account',  'personal_access_tokens','personal_access_token')GROUP BY entity_table, actionORDER BY entity_table, actionORDER BY entity_table NULLS LAST, action NULLS LAST
    WITH combined AS (
      SELECT entity_table, action, entity_id, occurred_at
      FROM audit_log
      WHERE occurred_at >= DATE '2023-01-01' AND occurred_at < DATE '2026-01-01'
      UNION ALL
      SELECT entity_table, action, entity_id, occurred_at
      FROM audit_log_archive
      WHERE occurred_at >= DATE '2023-01-01' AND occurred_at < DATE '2026-01-01'
    )
    SELECT
      entity_table,
      action,
      COUNT(*) AS row_count,
      COUNT(DISTINCT entity_id) AS distinct_entity_id_count,
      MIN(occurred_at) AS min_occurred_at,
      MAX(occurred_at) AS max_occurred_at
    FROM combined
    WHERE entity_table IN (
      'users','user',
      'roles','role',
      'user_roles','user_role',
      'role_permissions','role_permission',
      'permissions','permission',
      'teams','team',
      'team_members','team_member',
      'api_keys','api_key',
      'invitations','invitation',
      'mfa_devices','mfa_device',
      'sessions','session',
      'service_accounts','service_account',
      'personal_access_tokens','personal_access_token'
    )
    GROUP BY entity_table, action
    ORDER BY entity_table NULLS LAST, action NULLS LAST
    10 ms100 msHyper16.0 msDuckDB16.7 msGizmoSQL20.4 msclickhouse_brew44.1 msFirebolt37.5 msDataFusion53.0 msClickHouse43.2 ms
  8. Everything, as recorded

    The agents' own records, as the role play wrote them; only the personal data of the person who ran it is redacted

    Omar's whole session, 7 turns
    • The request, as Omar received it
      Report request from Claire Dubois (analyst for the Head of Risk and Payments):
      
      Title: Audit log coverage for identity and access-control tables
      Question: Which identity/access-control tables and actions actually appear in the audit trail, and under what naming — specifically, are role/permission grant and revocation events logged at all?
      Grain: one row per combination of entity_table and action, counted across audit_log UNION audit_log_archive, restricted to entity_table in (users, roles, user_roles, role_permissions, permissions, teams, team_members, api_keys, invitations, mfa_devices, sessions, service_accounts, personal_access_tokens), all dates 2023-01-01 through 2025-12-31
      Columns:
      - entity_table
      - action
      - row_count
      - distinct_entity_id_count
      - min_occurred_at
      - max_occurred_at
      Details: Sources: audit_log and audit_log_archive (same shape, union them). Purpose: last night's 'role grant and revocation activity' report came back as all zeros for entity_table='user_roles' with action in (create, delete) across all 36 months, which is either a real and significant audit-trail gap or a mismatch in how these events are actually tagged (e.g. logged under a different entity_table spelling, or only as action='update' with changed_columns rather than create/delete rows). This report settles which it is. If any entity_table values close to this list but not exact matches exist (e.g. singular/plural variants), please include them too and note it. Order by entity_table, action.
      
      Check your query with the warehouse's dry_run tool, then hand it in with submit.
    • Omar calls dry_run
      WITH combined AS (
        SELECT entity_table, action, entity_id, occurred_at
        FROM audit_log
        WHERE occurred_at >= DATE '2023-01-01' AND occurred_at < DATE '2026-01-01'
        UNION ALL
        SELECT entity_table, action, entity_id, occurred_at
        FROM audit_log_archive
        WHERE occurred_at >= DATE '2023-01-01' AND occurred_at < DATE '2026-01-01'
      )
      SELECT
        entity_table,
        action,
        COUNT(*) AS row_count,
        COUNT(DISTINCT entity_id) AS distinct_entity_id_count,
        MIN(occurred_at) AS min_occurred_at,
        MAX(occurred_at) AS max_occurred_at
      FROM combined
      WHERE entity_table IN (
        'users','user',
        'roles','role',
        'user_roles','user_role',
        'role_permissions','role_permission',
        'permissions','permission',
        'teams','team',
        'team_members','team_member',
        'api_keys','api_key',
        'invitations','invitation',
        'mfa_devices','mfa_device',
        'sessions','session',
        'service_accounts','service_account',
        'personal_access_tokens','personal_access_token'
      )
      GROUP BY entity_table, action
      ORDER BY entity_table, action
    • The warehouse answers
      STATUS: COMPILES (dry run on the empty schema, no data read)
      COLUMNS: entity_table, action, row_count, distinct_entity_id_count, min_occurred_at, max_occurred_at
    • OmarQuery compiles. Submitting.
    • Omar calls submit
      WITH combined AS (
        SELECT entity_table, action, entity_id, occurred_at
        FROM audit_log
        WHERE occurred_at >= DATE '2023-01-01' AND occurred_at < DATE '2026-01-01'
        UNION ALL
        SELECT entity_table, action, entity_id, occurred_at
        FROM audit_log_archive
        WHERE occurred_at >= DATE '2023-01-01' AND occurred_at < DATE '2026-01-01'
      )
      SELECT
        entity_table,
        action,
        COUNT(*) AS row_count,
        COUNT(DISTINCT entity_id) AS distinct_entity_id_count,
        MIN(occurred_at) AS min_occurred_at,
        MAX(occurred_at) AS max_occurred_at
      FROM combined
      WHERE entity_table IN (
        'users','user',
        'roles','role',
        'user_roles','user_role',
        'role_permissions','role_permission',
        'permissions','permission',
        'teams','team',
        'team_members','team_member',
        'api_keys','api_key',
        'invitations','invitation',
        'mfa_devices','mfa_device',
        'sessions','session',
        'service_accounts','service_account',
        'personal_access_tokens','personal_access_token'
      )
      GROUP BY entity_table, action
      ORDER BY entity_table, action
    • The warehouse answers
      Submitted to tonight's batch. Your work on this request is done.
    • OmarSubmitted. The query unions `audit_log` and `audit_log_archive`, restricts to the 13 requested identity/access-control tables plus their singular/plural counterparts (to catch naming mismatches like `user_role` vs `user_roles`), and aggregates row counts, distinct entity IDs, and the occurred_at range per `entity_table` × `action` for 2023–2025. This will directly show whether `user_roles` create/delete events exist under a different spelling, only as `update` actions, or are genuinely absent.