Skip to content

fix(idempotency): guard claim + order inserts against reindex duplicates#45

Merged
lucca65 merged 1 commit into
masterfrom
fix/claim-order-idempotency
Jun 22, 2026
Merged

fix(idempotency): guard claim + order inserts against reindex duplicates#45
lucca65 merged 1 commit into
masterfrom
fix/claim-order-idempotency

Conversation

@lucca65

@lucca65 lucca65 commented Jun 22, 2026

Copy link
Copy Markdown
Member

Problem

claimAction and transferSale did bare .insert() with no idempotency. Every re-indexed block (microfork, pm2 restart resume, manual reindex) re-inserted the claim/order as a fresh row. User-visible symptoms:

  • Duplicate claims that look un-approved — the reindex copy is a fresh pending row with no checks, while the original was already approved.
  • Phantom shop purchases — real orders appear 2-4×, inflating purchase history.

Prod magnitude (muda): ~3987 extra claim rows, ~5503 extra order rows. Only token.js (transfers/mints) had the dedup guard; claims and orders were left bare.

Fix

Mirror the existing token.js count-guard:

  • claimAction: count on (created_tx, action_id, claimer_id) before insert; return if it exists.
  • transferSale: guard at the top of the withTransaction on (created_tx, from_id, product_id) → returns early, so it also skips the duplicate products.units stock decrement (secondary bug).

A single maker can't claim the same action twice in one tx, and a checkout is one transfersale per product, so these natural keys collapse true duplicates without dropping real rows.

DB-level enforcement

Paired backend migrations add the matching unique indexes (claims_dedup_idx, orders_dedup_idx) and dedup existing prod rows — backend PR #334. Deploy this (event-source) first to stop new duplicates, then run the backend migration.

🤖 Generated with Claude Code

claimAction and transferSale did bare inserts with no idempotency, so every
re-indexed block re-inserted the claim/order as a fresh row -- surfacing as
duplicate claims that look un-approved and phantom shop purchases that drain
balances in the UI. Add a count-guard on the natural key before insert; the
transferSale guard sits at the top of the withTransaction so it also skips the
duplicate product stock decrement. Mirrors the existing token.js dedup guard.
DB-level enforcement lands via the backend claims_dedup_idx / orders_dedup_idx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lucca65
lucca65 merged commit ab52494 into master Jun 22, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant