Skip to content

Silent save-failure after irreversible Execute can bypass MaxPaymentDailyUSD #1310

Description

@cristim

Problem

In processAutoExchange (pkg/exchange/auto.go), after the irreversible AWS Execute call succeeds, the completed-record save error is only logged, never surfaced:

// pkg/exchange/auto.go:441-447
if err := params.Store.SaveRIExchangeRecord(ctx, record); err != nil {
    logging.Errorf("failed to save completed exchange record for %s: %v", rec.SourceRIID, err)
}

outcome.RecordID = record.ID
outcome.ExchangeID = exchangeID
return outcome

This is the same fail-open shape as COR-04 (PR #1229, just merged) but on a different code path:

  • AWS exchange has been committed (money irreversibly spent)
  • DB row is missing (status="completed" never persisted)
  • Next iteration of the for _, rec := range recs loop calls GetRIExchangeDailySpend, which sums status = 'completed' rows -- and now under-reports total spend by exactly this exchange's payment_due
  • The under-reported dailySpent allows the cap check newTotal.Cmp(dailyCap) > 0 to pass on the next iteration when it should have rejected, blowing past MaxPaymentDailyUSD by the missing amount

Likelihood is low (DB write failure on Postgres is rare on the same connection that just queried), but the impact is direct cap bypass on a money path. This is also the exact shape feedback_no_silent_fallbacks.md calls out.

Acceptance criteria

SaveRIExchangeRecord failure on the completed-record path must either:

  1. Block the loop from continuing (return an error from RunAutoExchange, halting subsequent exchanges until the audit trail is reliable), OR
  2. Be reflected in the outcome and the next iteration's cap check (e.g., maintain an in-process tally that the cap check adds on top of GetRIExchangeDailySpend), so an in-memory sum carries forward when the DB row is missing.

Mirror the existing dailySpent / payment-due failure-closed branches' shape: logging.Errorf + outcome.Error set + return. The caller (RunAutoExchange) should stop processing further recommendations once any audit save has failed during an auto run; otherwise an operator has no way to know the cap budget left.

Regression test: drive processAutoExchange with a mock store whose SaveRIExchangeRecord fails only for completed-status records, run two back-to-back recommendations whose sum exceeds MaxPaymentDailyUSD, and assert the second exchange is rejected (cap check blocks) even though the first row never made it to the DB.

References

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions