Skip to content

fix(db): speed up usage credit ledger RLS#2074

Merged
riderx merged 4 commits intomainfrom
codex/fix-usage-credit-ledger-rls
May 7, 2026
Merged

fix(db): speed up usage credit ledger RLS#2074
riderx merged 4 commits intomainfrom
codex/fix-usage-credit-ledger-rls

Conversation

@riderx
Copy link
Copy Markdown
Member

@riderx riderx commented May 7, 2026

Summary (AI generated)

  • Added an initPlan-style usage-credit RLS helper that resolves readable orgs once per statement from legacy org grants, RBAC user/group bindings, and RBAC API-key bindings.
  • Replaced the SELECT policies for usage_overage_events, usage_credit_consumptions, usage_credit_grants, and usage_credit_transactions so usage_credit_ledger no longer runs expensive per-row identity resolution.
  • Added a pgTAP regression test to keep those policies on the optimized helper and away from per-row get_identity_org_allowed() checks.

Motivation (AI generated)

Production CPU stayed elevated after the bundle-list fix because usage_credit_ledger queries were still spending about 1.2-1.5 seconds in RLS checks. The slow path started around the same incident window and came from per-row permission checks on usage-credit base tables, not from new ledger data volume.

Business Impact (AI generated)

This reduces database CPU on billing/usage-credit ledger reads while keeping access gated by Capgo's existing legacy/RBAC, 2FA, password-policy, API-key expiration, and API-key org-scope checks. It protects the console from another expensive RLS scan without weakening billing-data access.

Test Plan (AI generated)

  • bun lint
  • bun lint:backend
  • Commit hook: bun run cli:build && vue-tsc --noEmit
  • PGSSLMODE=disable bunx supabase test db supabase/tests/54_test_usage_credit_rls_performance.sql --db-url postgresql://postgres:postgres@127.0.0.1:56882/postgres
  • Rollback-only prod transaction: usage_credit_ledger purchase query drops to about 110 ms from about 1.2-1.5 s
  • GitHub CI

Generated with AI

Summary by CodeRabbit

  • Security

    • Tightened row-level access for usage-credit features so anonymous and authenticated requests can SELECT only rows for organizations they’re authorized to view; write operations are explicitly denied where appropriate. Access checks were optimized to resolve allowed orgs more efficiently.
  • Tests

    • Added tests validating the new access-control wiring, privilege grants, runtime behavior, and policy counts/performance assumptions.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds public.usage_credit_readable_org_ids() to compute readable org IDs per request, grants execute to auth roles, updates SELECT RLS policies on four usage-credit tables to check org_id membership against that helper, and adds pgTAP tests verifying function content, privileges, and policy wiring.

Changes

Usage Credit RLS Performance Optimization

Layer / File(s) Summary
Helper Function Implementation
supabase/migrations/20260507165636_fast_usage_credit_rls_policies.sql
public.usage_credit_readable_org_ids() PL/pgSQL function resolves API-key/user context, aggregates candidate org IDs from legacy grants and RBAC bindings (including groups), applies API-key org-scope restrictions, filters via public.check_min_rights(), and returns a uuid[] of readable orgs.
Function Permissions & Documentation
supabase/migrations/20260507165636_fast_usage_credit_rls_policies.sql
Set owner to postgres; revoke EXECUTE from PUBLIC; grant EXECUTE to anon, authenticated, and service_role; mark SECURITY DEFINER and SET search_path TO ''; add a descriptive comment.
RLS Policy Wiring
supabase/migrations/20260507165636_fast_usage_credit_rls_policies.sql
Replace SELECT policies for usage_overage_events, usage_credit_consumptions, usage_credit_grants, and usage_credit_transactions so reads are allowed only when row.org_id IS IN public.usage_credit_readable_org_ids() output (NULL treated as empty array).
RLS Deny Policies
supabase/migrations/20260507165636_fast_usage_credit_rls_policies.sql
Add restrictive deny INSERT/UPDATE/DELETE policies for anon and authenticated on each of the four usage-credit tables.
Tests & Verification
supabase/tests/54_test_usage_credit_rls_performance.sql
pgTAP tests validate function existence and that its definition includes check_min_rights and API-key RBAC candidate logic; assert PUBLIC lacks EXECUTE while anon/authenticated have it; confirm 4 SELECT policies reference the helper and none reference get_identity_org_allowed; assert counts of restrictive deny write policies; call helper as anon (expect empty uuid[]); exercise API-key RBAC fixtures and behavior.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RLS_Policy
  participant Helper
  participant Table
  Client->>RLS_Policy: SELECT usage_credit rows
  RLS_Policy->>Helper: usage_credit_readable_org_ids()
  Helper-->>RLS_Policy: uuid[] of allowed org_ids
  RLS_Policy->>Table: check row.org_id IN uuid[]
  Table-->>Client: return authorized rows
Loading

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Cap-go/capgo#1966: Related changes to API-key helpers and execute grant hardening used by this PR.
  • Cap-go/capgo#2073: Adds SECURITY DEFINER helpers that resolve API keys/RBAC and produce readable ID arrays (closely related helper pattern).
  • Cap-go/capgo#2061: Modifies public.check_min_rights() which is invoked by the new helper to filter candidate orgs.

Poem

🐰 I hopped through rows and rights,

I gathered orgs beneath the lights,
One helper now, no per-row chore,
Policies sing, queries soar,
Tiny rabbit, big speed boost.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(db): speed up usage credit ledger RLS' is concise, specific, and directly summarizes the main performance optimization of the changeset.
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.
Description check ✅ Passed The PR description is mostly complete and well-structured with all required template sections filled out appropriately.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-usage-credit-ledger-rls

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

@codspeed-hq
Copy link
Copy Markdown
Contributor

codspeed-hq Bot commented May 7, 2026

Merging this PR will not alter performance

✅ 28 untouched benchmarks


Comparing codex/fix-usage-credit-ledger-rls (a5b3a53) with main (4cff0be)

Open in CodSpeed

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d5cab9436

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +15 to +16
IF v_user_id IS NULL THEN
SELECT public.get_apikey_header() INTO v_api_key_text;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve API-key RBAC access in mixed-auth requests

usage_credit_readable_org_ids() only parses capgkey inside IF v_user_id IS NULL, so when a request has both a JWT and an API key, v_api_key remains null and API-key principals are never added to candidate_orgs. The previous policy path still let check_min_rights(...) evaluate API-key RBAC bindings even with an authenticated user, so this change can newly deny usage-credit rows for clients that rely on API-key-only org permissions while also sending a user session token.

Useful? React with 👍 / 👎.

@riderx riderx force-pushed the codex/fix-usage-credit-ledger-rls branch from 4d5cab9 to c1ff285 Compare May 7, 2026 17:17
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/20260507165636_fast_usage_credit_rls_policies.sql`:
- Around line 125-175: Add explicit deny policies for INSERT, UPDATE, and DELETE
on each of the four tables (usage_overage_events, usage_credit_consumptions,
usage_credit_grants, usage_credit_transactions) instead of relying on implicit
deny: for each table create policies named e.g. "Deny insert for org members",
"Deny update for org members", and "Deny delete for org members" using the
pattern AS RESTRICTIVE FOR INSERT/UPDATE/DELETE TO "anon", "authenticated" WITH
CHECK (false) so anon/authenticated roles are explicitly forbidden from writing
to those tables.
- Around line 33-45: The issue is that for RBAC-only API keys (where
v_api_key.mode is NULL), v_check_user_id remains NULL due to the condition in
the assignment, causing access checks to fail downstream. To fix it, modify the
condition that assigns v_check_user_id so that it also sets v_check_user_id to
v_user_id when v_api_key.mode is NULL, thus passing the RBAC API key's user ID
through to the permission checks like in the legacy mode. This ensures RBAC-only
API keys have their user ID correctly used in the check_min_rights call.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c038df2d-d3b2-4938-9703-5b00d1759768

📥 Commits

Reviewing files that changed from the base of the PR and between 1b1c89d and 4d5cab9.

📒 Files selected for processing (2)
  • supabase/migrations/20260507165636_fast_usage_credit_rls_policies.sql
  • supabase/tests/54_test_usage_credit_rls_performance.sql

Comment thread supabase/migrations/20260507165636_fast_usage_credit_rls_policies.sql Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/tests/54_test_usage_credit_rls_performance.sql`:
- Around line 53-70: The current assertion only checks that pg_policies.qual
mentions usage_credit_readable_org_ids, which would allow a direct per-row call;
update the check on pg_policies.qual to enforce the initPlan/subselect form.
Modify the WHERE clause that references pg_policies.qual to require a pattern
that includes a subselect wrapping the helper (e.g. qual LIKE
'%(SELECT%usage_credit_readable_org_ids%') or use a regex match to ensure the
policy contains a SELECT subquery calling usage_credit_readable_org_ids, so the
test only passes when the initPlan-style readable-org helper is used.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e42731a-7556-477e-90d0-3e5b19fc3b43

📥 Commits

Reviewing files that changed from the base of the PR and between 4d5cab9 and 13bb753.

📒 Files selected for processing (2)
  • supabase/migrations/20260507165636_fast_usage_credit_rls_policies.sql
  • supabase/tests/54_test_usage_credit_rls_performance.sql
✅ Files skipped from review due to trivial changes (1)
  • supabase/migrations/20260507165636_fast_usage_credit_rls_policies.sql

Comment thread supabase/tests/54_test_usage_credit_rls_performance.sql
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 7, 2026

@riderx riderx merged commit fb0482a into main May 7, 2026
40 checks passed
@riderx riderx deleted the codex/fix-usage-credit-ledger-rls branch May 7, 2026 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant