feat: deduct_credits_with_audit RPC for atomic wallet + meter writes
Summary
Adds a new PL/pgSQL function `deduct_credits_with_audit(p_account_id, p_amount, p_event_id, p_event)` that debits `credits_usage.remaining_credits` and inserts the corresponding `usage_events` audit row inside a single transaction.
Mirrors open-agents' `recordUsage` pattern (apps/web/lib/db/usage.ts:60) which wraps the same two writes in `db.transaction(...)` to address cubic's concern about wallet/meter drift on partial failure.
The existing `deduct_credits(account_id, amount)` only updates the wallet. PostgREST cannot execute multi-statement transactions, so a separate `INSERT INTO usage_events` from the api would not be atomic. This function consolidates both writes into one round trip.
No schema changes
`usage_events` was already widened in `20260515000000_usage_events_unify_with_credits.sql` to absorb chat callers (`source='api'`, `credits_deducted_cents` column). The 2026-05-15 migration's design intent ā "chat/research will write the existing 'main' default ā no schema change needed" ā already accounts for this work. This PR only adds the function.
Why now
Unblocks the chat-workflow credits gap tracked in recoupable/api#605 ā every chat turn on `/api/chat/workflow` is currently free because `runAgentWorkflow` never calls any credit-deduction step. This RPC is the foundation; the api-side step + workflow wiring lands in a follow-up PR on `recoupable/api`.
Test plan
- After merge, apply migration via `supabase db push` (or whatever the project uses) and verify function exists: `select proname from pg_proc where proname = 'deduct_credits_with_audit';`
- Smoke test in SQL editor: pick a test account, call `select public.deduct_credits_with_audit(''::uuid, 5, 'test_' || gen_random_uuid(), '{"source":"api","model_id":"anthropic/claude-haiku-4.5","input_tokens":10,"output_tokens":3}'::jsonb);` and confirm both `credits_usage.remaining_credits` dropped by 5 AND a new `usage_events` row landed
- Force a row-level failure (e.g. pass an invalid `p_event_id` to violate the PK constraint after an existing row) and confirm the wallet debit is rolled back
š¤ Generated with Claude Code
Summary by cubic
Add public.deduct_credits_with_audit to atomically debit wallet credits and write a usage_events row in one transaction. This prevents wallet/meter drift and enables charging for chat workflow turns.
-
New Features
- PL/pgSQL function:
deduct_credits_with_audit(p_account_id uuid, p_amount integer, p_event_id text, p_event jsonb). - Debits
credits_usage.remaining_creditsand inserts intousage_eventstogether; defaultssource='api',agent_type='main', and token/tool counts to 0 if missing. - EXECUTE granted to
authenticatedandservice_role.
- PL/pgSQL function:
-
Migration
- No schema changes; run the migration to create the function.
- Replace
deduct_credits+ manualusage_eventsinsert with this RPC.