Skip to content

feat(usage): reconcile against the account ledger row by row, not total by total - #169

Merged
VickyXAI merged 1 commit into
mainfrom
feat/usage-reconciliation
Sep 5, 2026
Merged

feat(usage): reconcile against the account ledger row by row, not total by total#169
VickyXAI merged 1 commit into
mainfrom
feat/usage-reconciliation

Conversation

@VickyXAI

@VickyXAI VickyXAI commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Key-mode numbers in franklin stats are 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/usage reports what it did cost.

Every gateway response already carries x-blockrun-request-id and 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.

Account ledger — last 30 day(s)

  Charged:   $8.0020 across 214 request(s)  (BlockRun, authoritative)
  Pending:   3 request(s) with no charge yet — not free, not final
  Free:      11 request(s) (explicitly not charged)
  Mix:       180 chat, 48 service

Against Franklin's own journal

  Joined:    198 request(s); 2 where Franklin's estimate differs
    /v1/surf/market/ohlcv     gateway $0.0085  local $0.0075  -0.0010 (service — locally an estimate by construction)

  4 charged request(s) have no local row ($0.0400).
  Franklin never counted these, so they never reached --max-spend either.

The four properties that matter

property why a careless read is wrong
cost_state: pending usage exists, charge does not yet — can still be repriced. Summed as zero it understates spend and looks settled. An unrecognised state is treated as pending, never priced.
zero-cost rows included "you were not charged" is an answer; an absent row is indistinguishable from a dropped one. Never filtered.
unavailable_days days the gateway could not list. Swallowed, it makes two correct ledgers look like they disagree.
kind a service charge 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

…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
@VickyXAI
VickyXAI merged commit 140c014 into main Sep 5, 2026
6 checks passed
@VickyXAI
VickyXAI deleted the feat/usage-reconciliation branch September 5, 2026 23:55
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.

1 participant