Skip to content

fix(db): skip updated_at and stats bookkeeping in audit_logs - #2905

Merged
riderx merged 2 commits into
mainfrom
fix/skip-updated-at-audit-noise
Aug 7, 2026
Merged

fix(db): skip updated_at and stats bookkeeping in audit_logs#2905
riderx merged 2 commits into
mainfrom
fix/skip-updated-at-audit-noise

Conversation

@riderx

@riderx riderx commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary (AI generated)

  • Skip audit_logs writes when an UPDATE only touches updated_at on any audited table
  • Keep apps/orgs stats refresh bookkeeping silent (stats_updated_at / stats_refresh_requested_at + updated_at)
  • Delete matching historical noise rows in bounded batches during migration

Motivation (AI generated)

Stats refresh and pure updated_at bumps were polluting audit_logs with bookkeeping noise that has no user-facing audit value and wastes TOAST/WAL on Capgo-EU.

Business Impact (AI generated)

Smaller, clearer org audit feeds and less storage/WAL pressure from high-frequency dashboard/stats bookkeeping writes.

Test Plan (AI generated)

  • bun run supabase:with-env -- bunx vitest run tests/audit-logs.test.ts tests/chart-refresh-rpc.test.ts
  • Confirm migration applies on a fresh local reset
  • Spot-check prod after deploy: no new updated_at-only / stats-only UPDATE rows in audit_logs

Generated with AI

Made with Cursor


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Reduced audit log noise from routine timestamp, statistics, counter, and internal bookkeeping updates.
    • Prevented large version-related details from being stored in audit records.
    • Preserved relevant actor and record information for meaningful audit events.
    • Cleaned up matching historical audit entries in manageable batches.

Stop writing audit rows for pure updated_at bumps and apps/orgs stats refresh fields, and delete matching historical noise in bounded batches.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_1adafaa6-179f-4d2d-979e-bfaed38edb24)

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1392fd3c-d6f7-46c8-9ca0-8300243d786d

📥 Commits

Reviewing files that changed from the base of the PR and between 1ddfb7a and b557c56.

📒 Files selected for processing (2)
  • supabase/migrations/20260807093220_skip_updated_at_audit_noise.sql
  • tests/audit-logs.test.ts

📝 Walkthrough

Walkthrough

The migration updates public.audit_log_trigger(), filters bookkeeping noise, shapes audit records, and adds bounded historical cleanup with hourly scheduling. Integration tests verify that organization and app bookkeeping updates do not create audit entries.

Changes

Audit log filtering and cleanup

Layer / File(s) Summary
Trigger actor resolution and filtering
supabase/migrations/20260807093220_skip_updated_at_audit_noise.sql
The trigger resolves authenticated users or valid API keys. It suppresses configured app_versions, timestamp-only, and statistics-related updates.
Audit record shaping and persistence
supabase/migrations/20260807093220_skip_updated_at_audit_noise.sql
The trigger removes large app_versions values, maps record identifiers, and stores mutation, actor, organization, and changed-field metadata.
Historical cleanup and regression validation
supabase/migrations/20260807093220_skip_updated_at_audit_noise.sql, tests/audit-logs.test.ts
A bounded cleanup function removes matching historical rows and runs hourly. Integration tests verify unchanged audit counts after bookkeeping updates.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MutatingStatement
  participant audit_log_trigger
  participant AuthContext
  participant audit_logs
  participant cleanup_scheduler
  MutatingStatement->>audit_log_trigger: invoke row trigger
  audit_log_trigger->>AuthContext: resolve user or API key
  audit_log_trigger->>audit_log_trigger: evaluate changed fields
  audit_log_trigger->>audit_logs: insert qualifying audit event
  cleanup_scheduler->>audit_logs: delete historical bookkeeping rows
Loading

Possibly related PRs

Suggested labels: codex

Suggested reviewers: dalanir

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main database change: suppressing audit entries for updated_at and stats bookkeeping updates.
Description check ✅ Passed The description explains the change, motivation, impact, and test plan, but it omits the repository checklist and screenshots section.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot added the codex label Aug 7, 2026
@codspeed-hq

codspeed-hq Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 43 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing fix/skip-updated-at-audit-noise (b557c56) with main (0fdf275)2

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (0c456b2) during the generation of this report, so 0fdf275 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@supabase/migrations/20260807093220_skip_updated_at_audit_noise.sql`:
- Around line 216-270: Replace the unbounded transactional cleanup loop in the
migration with only a strict, small deployment-budget cleanup. Move the
remaining audit-log processing into process_all_cron_tasks, register the task
through a follow-up migration rather than creating a new cron job, and enforce a
per-run batch limit so each invocation performs bounded work.
- Around line 153-172: The audit_log_trigger() table-mapping CASE must branch on
TG_OP before accessing trigger records, using only OLD for DELETE and NEW/OLD
handling for other operations; return OLD directly on DELETE instead of
COALESCE(NEW, OLD). Add DELETE assertions covering orgs, apps, channels,
app_versions, and org_users, including the currently untested apps and channels
paths.
- Around line 5-208: Make the identifier-resolution CASE in audit_log_trigger
DELETE-safe by using OLD-only values for deletes or returning before the block;
do not dereference NEW on DELETE. Add the required audit-path and cleanup
analysis documenting execution frequency, roles, cardinalities, indexes, and
worst-case EXPLAIN (ANALYZE, BUFFERS) plans. Replace the 1,000-row migration
loop with the existing resumable cleanup mechanism if total WAL or lock duration
must be bounded.

In `@tests/audit-logs.test.ts`:
- Around line 364-422: The organization stats_updated_at-only and
updated_at-only tests currently modify shared ORG_ID and compare aggregate
counts, making them vulnerable to parallel interference. Create a uniquely named
organization fixture for this test scope, use its ID in both tests, and verify
the row exists before recording each baseline audit-log count.
- Around line 364-454: Extend the audit-log coverage alongside the existing
stats_updated_at-only tests by adding organization and app UPDATE cases for
stats_refresh_requested_at. Assert that changing only this field creates no
UPDATE audit log, and include updated_at in the update payload and expected
behavior when that matches the production update shape. Reuse the existing
audit_logs queries and identifiers from the organization and app test cases.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d548c999-c6c0-4269-a114-0c9436f4b492

📥 Commits

Reviewing files that changed from the base of the PR and between 0c456b2 and 1ddfb7a.

📒 Files selected for processing (2)
  • supabase/migrations/20260807093220_skip_updated_at_audit_noise.sql
  • tests/audit-logs.test.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • Cap-go/capacitor-updater (manual)

Comment thread supabase/migrations/20260807093220_skip_updated_at_audit_noise.sql
Comment thread supabase/migrations/20260807093220_skip_updated_at_audit_noise.sql Outdated
Comment on lines +216 to +270
DO $$
DECLARE
batch_no integer := 0;
deleted_batch integer;
deleted_total bigint := 0;
v_batch_size integer := 1000;
v_max_batches integer := 1000000;
BEGIN
PERFORM pg_catalog.set_config('statement_timeout', '0', true);

LOOP
batch_no := batch_no + 1;
EXIT WHEN batch_no > v_max_batches;

DELETE FROM public.audit_logs
WHERE ctid IN (
SELECT al.ctid
FROM public.audit_logs AS al
WHERE al.operation = 'UPDATE'
AND al.changed_fields IS NOT NULL
AND (
-- Pure updated_at-only rows on any audited table
NOT EXISTS (
SELECT 1
FROM pg_catalog.unnest(al.changed_fields) AS changed_field(field_name)
WHERE changed_field.field_name IS DISTINCT FROM 'updated_at'
)
OR (
-- Stats refresh bookkeeping on apps/orgs
al.table_name = ANY (ARRAY['apps', 'orgs']::text[])
AND al.changed_fields && ARRAY['stats_refresh_requested_at', 'stats_updated_at']::text[]
AND NOT EXISTS (
SELECT 1
FROM pg_catalog.unnest(al.changed_fields) AS changed_field(field_name)
WHERE changed_field.field_name <> ALL (
ARRAY['stats_refresh_requested_at', 'stats_updated_at', 'updated_at']::text[]
)
)
)
)
ORDER BY al.created_at
LIMIT v_batch_size
);

GET DIAGNOSTICS deleted_batch = ROW_COUNT;
deleted_total := deleted_total + deleted_batch;
EXIT WHEN deleted_batch = 0;
END LOOP;

RAISE NOTICE
'skip_updated_at_audit_noise: deleted=% batches=% batch_size=%',
deleted_total,
LEAST(batch_no, v_max_batches),
v_batch_size;
END;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Move the full cleanup out of the migration transaction.

Each DELETE is limited to 1,000 rows, but every loop iteration remains in the same migration transaction. The loop can delete up to 1,000,000,000 rows with statement_timeout disabled. This does not bound total WAL, lock lifetime, rollback cost, or replication impact.

Keep only a strict deployment budget in this migration. Process the remaining rows through process_all_cron_tasks, registered by a migration, with a per-run batch limit.

Based on learnings, Supabase migrations run inside a transaction. As per coding guidelines, “Do not create new cron jobs; add required work to process_all_cron_tasks through a new migration.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/20260807093220_skip_updated_at_audit_noise.sql` around
lines 216 - 270, Replace the unbounded transactional cleanup loop in the
migration with only a strict, small deployment-budget cleanup. Move the
remaining audit-log processing into process_all_cron_tasks, register the task
through a follow-up migration rather than creating a new cron job, and enforce a
per-run batch limit so each invocation performs bounded work.

Sources: Coding guidelines, Learnings

Comment thread tests/audit-logs.test.ts Outdated
Comment thread tests/audit-logs.test.ts Outdated
Address review: DELETE-safe identifier mapping, cron-drained cleanup with a small migration budget, and isolated bookkeeping skip tests including stats_refresh_requested_at.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Aug 7, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_99d13a59-27dc-4709-b1c3-1eb959e6b330)

@riderx
riderx merged commit b8e9443 into main Aug 7, 2026
76 of 85 checks passed
@riderx
riderx deleted the fix/skip-updated-at-audit-noise branch August 7, 2026 10:02
@sonarqubecloud

sonarqubecloud Bot commented Aug 7, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant