The migration's own docblock justified an irreversible delete with a mechanism
that cannot fire: after the dedupe the deleted row's Price id is always still
claimed, by the survivor, so AdoptStripePrice's `claimed` callback always skips
it. The rebuild is real, but it goes through a fresh createPrice() under the
deleted row's own tuple-specific idempotency key, not through adoption. A
comment whose whole job is to justify deleting production rows has to name the
mechanism that runs, not the one that structurally cannot — this is the fourth
false justification this branch has carried, and the incident the whole
feature exists for started the same way.
The delete itself left no trace: down() restores the index, not the rows, and
two rows on one Price necessarily have different tuples, so at most one can
match what the Price actually charges — after the one real run there would be
nothing to check the survivor against. Added a Log::warning per duplicate
group naming the shared Price, the row kept, and every row thrown away.
Test: read back stripe_price_id and amount_cents on the surviving and
unrelated rows instead of only checking they still exist, closing the "kept
the right row but damaged it" gap. Added a second duplicate group of three
rows sharing one Price so the migration's foreach runs more than once and a
single iteration deletes more than one row. Re-verified the "lowest id
survives" assertion is load-bearing by flipping min('id') to max('id') during
this fix and confirming the test fails there, then reverting.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>