feat(usage): reconcile against the account ledger row by row, not total by total - #169
Merged
Conversation
…al by total Key-mode numbers in `franklin stats` are catalog estimates. The account gateway settles against a prepaid balance and returns no charge on the response, so Franklin books what a call SHOULD have cost and has never had a way to check it. GET /v1/usage reports what it DID cost. Every gateway response already carries x-blockrun-request-id and nothing read it. Franklin now records it on each usage row, so the two ledgers are joined per request. Comparing totals is a weaker check: two errors of opposite sign produce a total that matches and a ledger wrong in two places. Four properties of the feed turn a careless read into a confident wrong answer, and all four are load-bearing here: A `pending` row is usage whose charge does not exist yet and can still be repriced. Summing it as zero understates spend AND looks settled. An unrecognised cost_state is treated as pending, never priced — the safe error is "not known yet", not "settled at whatever came through". Zero-cost rows are included on purpose. "You were not charged" is an answer; an absent row is indistinguishable from one the client dropped. unavailable_days names days the gateway could not list. Swallowed, it makes two correct ledgers look like they disagree. kind says whether a row is checkable at all. A service charge is a per-call figure only the gateway holds, so a local estimate for one is a guess by construction; the output labels those rather than letting a mismatch read as a defect. It also names what it could not check. A charged request with no local row is real spend that never reached --max-spend. Local rows with no request id — wallet-mode, free-path, or recorded before this version — are counted and reported, because "0 mismatches" from a journal that could not be joined is a different statement from "0 mismatches". The cursor is opaque and is followed, never parsed. Six mutations, six caught: unknown state as priced, pending summed as settled, zero-cost rows filtered, unavailable_days swallowed, unjoinable rows counted as agreeing, cursor not followed. 761 local tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rm7cRGtC7wCofhYuHRo21h
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.
Key-mode numbers in
franklin statsare catalog estimates — the gateway settles against a prepaid balance and returns no charge, so Franklin books what a call should have cost and has never had a way to check it.GET /v1/usagereports what it did cost.Every gateway response already carries
x-blockrun-request-idand nothing read it. Franklin now records it per usage row, so the ledgers join per request. Comparing totals is the weaker check: two errors of opposite sign give a matching total and a ledger wrong in two places.The four properties that matter
cost_state: pendingunavailable_dayskindservicecharge is a per-call figure only the gateway holds, so a local estimate is a guess by construction. Those rows are labelled rather than read as defects.The cursor is opaque — followed, never parsed.
It names what it could not check
Charged requests with no local row are real spend that never reached
--max-spend. Local rows with no request id (wallet-mode, free-path, or recorded before this version) are counted and reported — "0 mismatches" from a journal that could not be joined is a different statement from "0 mismatches".Six mutations, six caught. 761 local tests pass.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Rm7cRGtC7wCofhYuHRo21h