fix(billing): invoice reads authoritative ledger (reconcile 416 vs 861)#32
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/invoicecredits_usednow sums-amountfromcredit_transactionsover the period (authoritative ledger) instead ofapi_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 driftedmonthly_calls_countfor quota → over-states remaining, under-charges. Make the counter atomic with the ledger (or derive remaining fromuser_credits.credits_balance) before real money flows.🤖 Generated with Claude Code