Skip to content

fix(spend): let settlement evidence beat a skip header; book BlockrunClient costs per call - #92

Merged
VickyXAI merged 1 commit into
mainfrom
fix/settlement-skip-hardening
Oct 27, 2026
Merged

VickyXAI merged 1 commit into
mainfrom
fix/settlement-skip-hardening

Conversation

@VickyXAI

Copy link
Copy Markdown
Contributor

Follow-ups from the review of #89 (merged).

Problem

  1. A skip header could hide a real charge. settlementSkipReason() treated any X-Settlement-Skipped / X-Payment-Waived as "nothing moved", even on a response whose receipt proves a settlement (a transaction, or extra.chargedAmount > 0 on batch). getSpending() and costUsd would then report $0 for money that moved. The gateways never send both today, but nothing pinned that.
  2. Opposite values counted as a skip. X-Settlement-Skipped: false (or 0, no, none) was read as a skip reason.
  3. BlockrunClient kept one pendingCostUsd per client. With two calls in flight on one client, a skipped answer to one zeroed the other's pending cost, and that real charge was never booked.

Change

  • src/receipt.ts: a skip reason is ignored when the response carries settlement evidence: a successful PAYMENT-RESPONSE with a non-empty transaction or a positive charge, or an X-Payment-Receipt hash. The conflict is logged to stderr. false / 0 / no / none are never a reason. A failed receipt (success: false) is not evidence.
  • Gateway check: blockrun.ai's free-fallback answer sends PAYMENT-RESPONSE { success: true, transaction: "" } with no amount and no X-Payment-Receipt (blockrun src/app/api/v1/chat/completions/route.ts ~4742–4777). The pm empty-result and rpc unexecuted routes return before settling. So every skip the gateways send today still books $0, and that real free-fallback shape is now a test case.
  • src/blockrun.ts: signFrom402() returns { paymentPayload, costUsd }, and all four booking sites pass that call's own cost to recordSpending(). pendingCostUsd is gone.

Tests

  • settlement-skipped-booking.test.ts:
    • New booked shapes, run on every EVM site: false/0; skip + receipt with a transaction; skip + receipt with chargedAmount; skip + X-Payment-Receipt.
    • New skipped shape, run on every EVM site: free-fallback with the empty-transaction receipt the gateway actually sends.
    • Predicate tests: stderr on a conflict, a failed receipt is not evidence, and none falls through to X-Payment-Waived.
    • Concurrent BlockrunClient calls (one skipped, one booked) book exactly one quote.
  • settlement-skipped-solana.test.ts: receipt-wins shape on all 7 Solana sites.
  • 87 fail on main. npm test: 1065 passed, 12 skipped. Build, lint and typecheck are clean.

Note: #91 also edits CHANGELOG [Unreleased], so whichever merges second needs a one-line rebase.

…Client costs per call

Follow-ups from the review of #89:

- settlementSkipReason(): a response that names a skipped settlement but also
  proves it settled (successful PAYMENT-RESPONSE with a transaction or a
  positive charge, or an X-Payment-Receipt hash) is not skipped; the conflict
  goes to stderr. blockrun.ai's free-fallback receipt has an empty transaction
  and no amount (chat/completions/route.ts), so today's skips still book $0.
- Header values false / 0 / no / none are not a skip reason.
- BlockrunClient: signFrom402() returns the signed cost with the payload and
  recordSpending() books that, replacing the per-client pendingCostUsd that a
  skipped call could zero under a concurrent call.

Tests: four booked shapes and one skipped shape (free-fallback as sent) across
every EVM site, a receipt-wins shape on every Solana site, predicate and
stderr tests, and a concurrent BlockrunClient test. 87 fail on main.
@VickyXAI
VickyXAI force-pushed the fix/settlement-skip-hardening branch from 0270b22 to ee03ad4 Compare October 27, 2026 06:08
@VickyXAI
VickyXAI merged commit 9ec2a2d into main Oct 27, 2026
3 checks passed
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