feat: deduct_credits_with_audit RPC for atomic wallet + meter writes

Merged
SW
5mo ago
+77-0

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_credits and inserts into usage_events together; defaults source='api', agent_type='main', and token/tool counts to 0 if missing.
    • EXECUTE granted to authenticated and service_role.
  • Migration

    • No schema changes; run the migration to create the function.
    • Replace deduct_credits + manual usage_events insert with this RPC.