Repository navigation
fix(exchange): refuse a quote with no PaymentDue instead of treating it as $0 - #2076
Merged
Merged
Conversation
…it as $0 An ExchangeQuoteSummary whose PaymentDueUSD is nil means the AWS response carried no PaymentDue at all; a zero-cost exchange arrives as an explicit "0.000000" and parses to a non-nil zero. Every cap layer collapsed the two: getValidatedQuote skipped the per-exchange cap, processRecommendation recorded the exchange as costing "0" (so the daily cap added nothing and a manual pending record carried "0"), and resolvePaymentDue substituted a zero inside Execute so both the initial and the pre-accept re-quote checks passed. An unpriced exchange could reach AcceptReservedInstancesExchangeQuote with no effective ceiling. Fail closed at every consumer: - getValidatedQuote skips the recommendation with "quote reported no PaymentDue" before the cap compare; processRecommendation no longer defaults the amount to "0". - requirePaymentDue replaces resolvePaymentDue; checkInitialQuote and checkReQuote return its error before Accept is called. - acceptedAmountFromQuote falls back to the initial quoted amount, never "0", when a fresh quote carries no amount. Regression tests drive RunAutoExchange (auto and manual) and the real executeWithAPI with an unpriced quote and assert nothing executes and nothing is recorded; an explicit zero still proceeds. Closes #1964 Refs #1448 (A09-004) Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01Fu9uWjxtDFx5HDKeMRt1jC
…tedAmount The helper returned "0" when a fresh Execute quote carried no payment amount, and its comment explained that as a zero-cost exchange where AWS returned nil. This commit's own change disproves that premise: a genuine zero arrives as an explicit "0.000000" and parses to a non-nil zero, while an absent PaymentDue is now refused by checkInitialQuote and checkReQuote before Accept runs. Execute can therefore no longer return successfully with an empty amount, so the branch is unreachable and its comment asserts something untrue. A future reader would take it as evidence that an empty amount is normal. Now mirrors exchange.acceptedAmountFromQuote and returns the caller's fallback instead of fabricating a figure. Found by adversarial review of the parent commit. Co-Authored-By: claude-flow <ruv@ruv.net> Claude-Session: https://claude.ai/code/session_01Fu9uWjxtDFx5HDKeMRt1jC
Contributor
|
Warning Review limit reached
On-demand reviews are free for the next 12 days. After that, they cost $0.25 per reviewed file. Or wait 51 minutes for your next included review. View limit detailsLimit details: You’ve used the included review currently available. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (5)
Comment |
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.
What
Closes #1964. Refs #1448 (finding A09-004), which stays open as a multi-item tracker.
In the auto-exchange path the per-exchange spend cap was evaluated only when a quote carried a
PaymentDue. An absent one leftPaymentDueUSDnil, so the cap check was skipped, the exchange was recorded as costing"0", it contributed nothing to the daily-cap total, and it proceeded to execution. The last guard, the cap handed toExecute, was defeated by the same nil on the fresh re-quote, so every layer of the cap stack collapsed on one missing field.resolvePaymentDuebecomesrequirePaymentDueand returns an error, propagated by both pre-accept checks.The bypass is fully closed, not narrowed
An AWS quote that is otherwise valid but carries no
PaymentDueis now refused in three places: at the sweep, where the recommendation is skipped with no record, no token and noExecute; atcheckInitialQuote; and atcheckReQuote, immediately beforeAcceptReservedInstancesExchangeQuote. That call has a single production call site, insideexecuteWithAPI, so the sweep, the execute endpoint, the approval path and the sanity tool all pass through both checks.The daily-cap SQL sums
payment_dueover completed and processing rows, and every writer of those rows now carries a real amount rather than a fabricated"0".A genuine zero still works
This is the load-bearing distinction, so it is worth stating precisely. AWS sends an explicit
"0.000000"for a zero-cost exchange, which parses to a non-nil zero and keeps working. Only an absent field yields nil, and only nil is refused. A whitespace-only value fails the parse and errors the quote call, which is also fail-closed.A positive control drives the real parser with an explicit
"0.000000"and passes both before and after this change. Two of the six mutations below attack this distinction from each side.Verification
Four refusal tests were observed failing on the pre-fix code with the exact symptoms of the defect:
Executecalled when it must not be, a pending record written, the skip list empty, and the ledger recording"0"where"30.000000"was due.An independent reviewer reproduced that, then ran six mutations. All were killed:
checkReQuotesubstitutes zero on errorcheckInitialQuotesubstitutes zero on erroracceptedAmountFromQuotereturns"0""0.000000"to nilThe last two matter most: they show the parse layer itself is covered, not just its consumers.
go test -count=1 -race ./exchange/inpkg/go vet,gofmt -lgocyclo -over 10on both touched filesgo build ./...from the rootinternal/api,internal/server,providers/aws/ladderLint could not be reproduced locally. The pinned golangci-lint fails to typecheck against the host toolchain on this machine. The fix tree and an
origin/mainarchive produce byte-identical lint output under both available runners, so nothing lint-visible changed, but CI is the arbiter.No live AWS reproduction is possible: an unpriced quote cannot be provoked on a real account. The fake-EC2 tests drive the real
executeWithAPIwith the real SDK output type, which is the closest available evidence.Second commit
Adversarial review found that
handlerAcceptedAmountininternal/apistill returned"0"for an empty amount, with a comment explaining it as a zero-cost exchange where AWS returned nil. This PR disproves that premise, and the branch is now unreachable, so it was left asserting something untrue that a future reader would take as evidence. It now mirrors itspkg/exchangesibling and returns the caller's fallback.