Skip to content

fix(exchange): enforce daily-cap integrity on manual path, actual-paid ledger, and headroom (adversarial-review follow-ups)#1457

Open
cristim wants to merge 2 commits into
mainfrom
fix/ri-exchange-daily-cap-integrity
Open

fix(exchange): enforce daily-cap integrity on manual path, actual-paid ledger, and headroom (adversarial-review follow-ups)#1457
cristim wants to merge 2 commits into
mainfrom
fix/ri-exchange-daily-cap-integrity

Conversation

@cristim

@cristim cristim commented Jul 17, 2026

Copy link
Copy Markdown
Member

Summary

Five verified money-integrity defects in the RI Exchange daily-cap subsystem, fixed with regression tests.

  • H1 checkDailyCap in handler_ri_exchange.go was treating an unparseable paymentDue as $0 and proceeding. Now fails closed with a blocking reason (mirrors the existing dailySpent parse-failure path).
  • H2 Both executeApprovedExchange and processAutoExchange passed the full perExchangeCap as MaxPaymentDueUSD to Execute. A fresh re-quote could accept an amount that exceeds remaining daily headroom. Fixed: effectiveCap = min(perExchangeCap, dailyCap - dailySpent).
  • H3 The fresh quote returned by Execute was discarded (_, _). Completed records persisted the stale pre-execution quote. GetRIExchangeDailySpend sums ledger amounts, so the daily spend was systematically under-counted. Fixed: new CompleteRIExchangeWithPayment method persists the accepted amount; both paths use it.
  • H4 Failed CompleteRIExchange/SaveRIExchangeRecord writes after money moved were log-and-continue. The daily ledger would under-count, allowing subsequent exchanges to bypass the cap. Fixed: 3-attempt retry; auto run halts / handler returns HTTP 500 on persistent failure.
  • M5 GetRIExchangeDailySpend only summed status = 'completed' rows. Concurrent approvals both read the same spend total before either write commits (TOCTOU). Fixed: SQL now includes 'processing' rows via COALESCE(completed_at, updated_at) date filter.

Test plan

  • Each defect has a named regression test (TestCheckDailyCap_UnparseablePaymentDue_FailsClosed, TestProcessAutoExchange_EffectiveCap_BoundedByDailyHeadroom, TestProcessAutoExchange_AcceptedAmountFromFreshQuote, TestProcessAutoExchange_LedgerSaveFailure_HaltsAndReturnsError, TestRunAutoExchange_LedgerSaveFailure_StopsAfterFirstExchange, TestExecuteApprovedExchange_EffectiveCap_BoundedByDailyHeadroom, TestExecuteApprovedExchange_AcceptedAmountFromFreshQuote, TestExecuteApprovedExchange_LedgerWriteFailure_ReturnsError)
  • All 5734 tests (709 pkg + 5025 internal) pass
  • All pre-commit hooks pass (gofmt, go vet, gocyclo -over 10, gosec)
  • No migration needed (SQL change is read-only query update)

@cristim cristim added triaged Item has been triaged priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/many Affects most users effort/l Weeks type/bug Defect labels Jul 17, 2026
@cristim

cristim commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8badd686-d4d8-4885-9d42-b62729c48d90

📥 Commits

Reviewing files that changed from the base of the PR and between 1d9f1b8 and 62e4823.

📒 Files selected for processing (13)
  • internal/analytics/collector_test.go
  • internal/api/exchange_helpers_test.go
  • internal/api/handler.go
  • internal/api/handler_ri_exchange.go
  • internal/api/handler_ri_exchange_test.go
  • internal/config/interfaces.go
  • internal/config/store_postgres.go
  • internal/config/store_postgres_nil_db_test.go
  • internal/config/store_postgres_pgxmock_test.go
  • internal/mocks/stores.go
  • internal/server/test_helpers_test.go
  • pkg/exchange/auto.go
  • pkg/exchange/auto_test.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ri-exchange-daily-cap-integrity

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

cristim added 2 commits July 17, 2026 21:11
…d ledger, and headroom (adversarial-review follow-ups)

H1: checkDailyCap fails closed on unparseable paymentDue (was treating as $0).
H2: executeApprovedExchange and processAutoExchange bound Execute MaxPaymentDueUSD
    by min(perExchangeCap, dailyCap-dailySpent) so re-quotes cannot breach headroom.
H3: Add CompleteRIExchangeWithPayment to persist the accepted (not pre-execution)
    amount; both auto and manual paths use the fresh quote from Execute.
H4: Retry ledger write 3x after money moves; halt auto run / return 500 on
    persistent failure to prevent cap bypass via missing ledger rows.
M5: GetRIExchangeDailySpend includes processing rows (COALESCE on completed_at/
    updated_at) to close concurrent-approval TOCTOU window.

Each defect has a regression test that fails pre-fix and passes post-fix.
…uteHeadroom

Fixes golangci-lint errcheck failure on handler_ri_exchange.go:1351 where
`exchange.ParseDecimalRat` error was discarded with `_`. The fix extracts a
`checkCapsAndComputeHeadroom` helper that bundles the three cap-validation
checks (MaxDailyUSD==0, checkDailyCap, MaxPerExchangeUSD==0) and the headroom
calculation, properly returning a reason string on any parse failure (fail
closed). This also brings `executeApprovedExchange` back under gocyclo -over 10
(complexity 8) after the helper reduces it from the 12 that errcheck enforcement
would have caused.

Rebased onto main (after PR #1456 merged; no conflicts in store_postgres.go).
@cristim
cristim force-pushed the fix/ri-exchange-daily-cap-integrity branch from d78f295 to 62e4823 Compare July 17, 2026 18:36
@cristim

cristim commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/l Weeks impact/many Affects most users priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant