Skip to content

fix(billing): invoice reads authoritative ledger (reconcile 416 vs 861)#32

Merged
WayforthOfficial merged 1 commit into
mainfrom
fix/invoice-authoritative-ledger
Jun 21, 2026
Merged

fix(billing): invoice reads authoritative ledger (reconcile 416 vs 861)#32
WayforthOfficial merged 1 commit into
mainfrom
fix/invoice-authoritative-ledger

Conversation

@WayforthOfficial

Copy link
Copy Markdown
Owner

Billing surfaces didn't reconcile on a real account. balance-delta 861 (authoritative, atomic ledger) vs invoice 416 (drifted denormalized counter) vs /keys/usage 36 (raw call count). Full forensics in the commit body.

Fix: /billing/invoice credits_used now sums -amount from credit_transactions over the period (authoritative ledger) instead of api_keys.monthly_calls_count (current) / COUNT(*) (past). June now reports 861 = balance delta.

Flagged follow-up (not in this PR): compute_calls_remaining() uses the same drifted monthly_calls_count for quota → over-states remaining, under-charges. Make the counter atomic with the ledger (or derive remaining from user_credits.credits_balance) before real money flows.

🤖 Generated with Claude Code

…t drifted counters

Billing surfaces didn't reconcile on a real account: balance deducted 861 since
restore (correct), but /billing/invoice showed credits_used=416 for the whole
month — impossible if both count the same credits.

Root cause: split-brain accounting. The credit balance is maintained atomically
by check_and_deduct_credits (user_credits.credits_balance ⇄ credit_transactions)
— the authoritative ledger, internally consistent (72 debits = 861 = exact
balance delta, zero phantom deductions). But /billing/invoice read
api_keys.monthly_calls_count (current month) / COUNT(*) of tx (past months) —
neither authoritative:
  * monthly_calls_count is incremented by _increment_calls in a SEPARATE,
    non-atomic, post-call path. It drifts low: /search debits carry NULL
    api_key_id so they never increment it (36 cr missed), the LLM path
    increments by 1 instead of credit cost, and even the /execute portion read
    416 vs an 825 ledger sum.
  * COUNT(*) is a call count, not a credit sum.

Invoice now sums -amount from credit_transactions over the period (both current
and past months), tying credits_used out to the balance delta exactly (June: 861).

NOTE (follow-up, not in this commit): compute_calls_remaining() derives quota
from the same drifted monthly_calls_count, so quota/remaining over-states by the
drift (~445 cr here) and under-charges. That counter should be made atomic with
the ledger (or remaining derived from user_credits.credits_balance) before real
money flows. /billing/balance is already correct (reads user_credits directly).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

2 participants