Repository navigation
fix(purchase): save purchase history once per commitment - #776
Conversation
A re-drive or root retry that adopts a commitment the first attempt already bought saved the same (provider, account_id, purchase_id) again, so spend, savings and YTD were double counted. SavePurchaseHistory now inserts only when no row exists for the key and ignores a conflict. Migration 000108 adds a partial unique index on (provider, account_id, purchase_id) for rows created after it runs, so two concurrent saves cannot both insert. Rows that exist today, duplicates included, are left alone: removing them deletes financial history and is a separate decision, and the cutoff keeps them from blocking the index. Integration tests (real Postgres) cover a repeated save, a row older than the index, an uncommitted twin in another transaction, created_at coming from the column default, and the migration applying over existing duplicates. Refs #704
|
Warning Review limit reached
This review includes 8 billable files and costs up to $2.00.
Reviews can continue after your included limit without a manual trigger. An admin must approve usage-based billing. Or wait 50 minutes for your next included review. View limit detailsLimit details: You’ve used the included review currently available. Your 86 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Review configuration: ⚙️ Run configuration
📒 Files selected for processing (8)
Comment |
SavePurchaseHistory saves once per (provider, account_id, purchase_id), so a successful purchase with an empty CommitmentID would be dropped silently after the first one. Return ErrEmptyPurchaseID instead; the caller already turns a history error into the audit-gap note on the execution, which stays completed. Closes #774
|
Merged as 57f42e0 (squash, match-head 316c83d). Reviewed SHAs: full review APPROVE at c48022f (archera-quotes-codex); Opus delta APPROVE at 316c83d (cc-coordinator-opus). CI 25/25 pass, state CLEAN, 0 unresolved threads. Local at 316c83d (git archive, Go 1.26.9): go build ./... exit 0; go test ./internal/config/... ./internal/purchase/... ./internal/api/... exit 0 (Postgres integration tests not run). |
What
Closes #704 (MON-06) and #774: a re-drive or root retry that adopts a commitment the first attempt already bought saved the same
(provider, account_id, purchase_id)again, so spend, savings and YTD were double counted.SavePurchaseHistory(internal/config/store_postgres.go) now inserts onlyWHERE NOT EXISTSa row for that key, with a bareON CONFLICT DO NOTHING. Migration000108adds a partial unique index on(provider, account_id, purchase_id) WHERE created_at > <migration time>. A skipped save is logged; the first row wins.Nothing is deleted. Existing duplicate rows stay (still double counted in old data). Removing them deletes financial history and is a separate owner decision. The partial predicate cannot say "no earlier twin", so the index covers rows created after the migration (the cutoff is
now()evaluated once in the migration and baked into the predicate), and theNOT EXISTSin the insert covers older rows.Proofs requested during planning
store_postgres.go:2089: bareON CONFLICT DO NOTHING, no conflict target, so no index inference and no "no unique or exclusion constraint matching the ON CONFLICT specification" error. The only unique constraints onpurchase_historyare theidprimary key (a fresh UUID per row) and the new index, so nothing unexpected is swallowed.created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), migration 000001) and is not in the INSERT column list; this is the only non-testINSERT INTO purchase_history.TestSavePurchaseHistory_CreatedAtIgnoresPurchaseTimestampsaves a record whose purchaseTimestampis a year old and assertscreated_atis within the last minute.Tests (
-tags integration, real Postgres via testcontainers; CI runs this tag)origin/maina repeated save leaves 2 rows (and the older-row case 2); with the fix 1.ConcurrentSaveWaitsForUncommittedTwin: another transaction inserts the key but has not committed; the save blocks on the unique index and no-ops after the commit. Without the index it returns at once and the test fails.NOT EXISTS); same purchase id in another account or provider is a different purchase.Migration108_AppliesOverExistingDuplicates: the migration applies with pre-existing duplicates, deletes nothing, and the index rejects a duplicate among new rows.Real Postgres 16 harness, not the deployed stack.
Notes
internal/purchase/execution.go,manager.go,reaper.go,handler_purchases.go(MON-02 work by another agent). The issue's third item, refusing a root retry when completed children exist, is left to that work.SavePurchaseHistoryreturnsErrEmptyPurchaseIDfor an empty purchase id (otherwise it would be dropped silently after the first). The caller already turns a history error into thehistory_write_failedaudit-gap note and the execution stayscompleted;TestManager_ExecuteAndFinalize_EmptyCommitmentID_CompletesWithAuditGapruns the realValidate()through that path. Both new tests fail with the guard disabled.Closes #774
Local independent review (2 passes): no actionable findings after the log line and extra subtests.
🤖 Generated with Claude Code