You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(exchange): approved RI exchanges reserve stale quotes outside the shared daily-cap lock #656
Manual RI approvals do not atomically reserve the execution ceiling. This remains separate from the same-day AUTO concurrency fix in LeanerCloud/cloud-commitments-go#41.
Current behaviour
At platform baseline 3131779, token and session approval transition pending to processing in internal/api/handler_ri_exchange.go:2295 and :2355 using the unlocked status UPDATE in internal/config/store_postgres.go:2918. executeApprovedExchange then reads daily spend separately at internal/api/handler_ri_exchange.go:2605, computes headroom at :2623 and passes it to Execute at :2633. The processing row contains the initial quote rather than the full fresh-quote ceiling. Its own quote is also included in the aggregate used to check that quote again.
Steps to verify the gap
Use fixture exchange clients and real local PostgreSQL; do not make purchases. With daily cap1000, transition two pending rows whose initial quotes are100 each to processing. Coordinate both daily reads to return200 before execution. Each gets ceiling800 and can accept a fresh quote700, totaling1400. Separately, approve one initial600 quote under cap1000: its own processing600 is included in spend, then600 is added again and the legitimate exchange is rejected. These are code-derived scenarios, not observed production purchases.
Expected behaviour
Manual and AUTO exchange paths share one atomic global reservation protocol. Fresh accepted payments stay within the reserved ceiling, and an approval does not double-count its own row.
Proposed fix and acceptance
Atomically reserve or replace the pending row under the same global advisory lock introduced for go#41, exclude its own previous amount when computing headroom, reserve the complete permitted ceiling and settle actual payment on the same ID. Add deterministic two-approval and AUTO-plus-approval interleaving tests, the legitimate600 approval, and real PostgreSQL lock verification. Relevant paths: internal/api/handler_ri_exchange.go and internal/config/store_postgres.go.
References and severity
Refs LeanerCloud/cloud-commitments-go#41 and #348. High severity when triggered; limited overlap-dependent impact. This issue records source inspection, not a completed failing regression or a production incident.
Verified (real PostgreSQL 16, local image, fixture exchange clients, no purchases or cloud calls):
two concurrent approvals reserve ceilings summing exactly the daily headroom; the race test holds the global advisory lock from a third transaction and fails 5/5 when the lock is removed, passes 10/10 with it (independent reviewer reproduced)
a legitimate $600 approval under a $1000 cap is no longer rejected for counting its own row
AUTO reservation and an approval racing divide the headroom instead of doubling it
the fresh accepted amount settles on the same row; the full ceiling bounds Execute
unit packages config/api/server/analytics/mocks pass; all 25 required CI checks green at the merged head
Not verified: behaviour against live AWS (fixtures only), and whether pkg/exchange enforces MaxPaymentDueUSD against the fresh quote (library not part of this change).
Recommend closing. Follow-up: #692 (stale processing rows now hold headroom until settled; AUTO only logs them).
Activity
fix(exchange): make the approval lock test deterministic and hide res…
test(exchange): fix lint findings in the approved reservation integra…
test(exchange): avoid account-id literals the git-secrets allowlist r…
fix(exchange): reserve approved RI exchange ceiling atomically and ke…
cristim commentedon Oct 9, 2026
Deployed: merged in #690 as cf0b1a0 (squash, head 6bafb61).
Verified (real PostgreSQL 16, local image, fixture exchange clients, no purchases or cloud calls):
Not verified: behaviour against live AWS (fixtures only), and whether pkg/exchange enforces MaxPaymentDueUSD against the fresh quote (library not part of this change).
Recommend closing. Follow-up: #692 (stale processing rows now hold headroom until settled; AUTO only logs them).