feat(migrations): canonicalize socials twitter.com → x.com + merge twin rows
Summary
Data-migration companion to api#755: rewrites all socials.profile_url twitter.com/* keys to the canonical x.com/* domain and merges rows where both spellings exist, so X scrape write-backs land on the rows artists are actually connected to.
Tracking issue: recoupable/chat#1851 (PR #2 of 2 required). Canonical-domain decision (x.com) recorded there 2026-07-06.
What it does
- Twin merge (403 pairs, measured 2026-07-06 prod): the
x.comrow is the keeper. Backfills any fresher scrape fields from the loser, repoints all 7 FK columns referencingsocials.id(account_socials.social_id,social_posts.social_id,post_comments.social_id,social_fans.artist_social_id,social_fans.fan_social_id,fan_segments.fan_social_id,agent_status.social_id) with delete-guards on each table's unique/logical key, then deletes the loser. - In-place rename (23,622 twinless rows):
twitter.com/<handle>→x.com/<handle>. - Post-condition: raises if any
twitter.com/*key survives, so a partial run cannot commit silently.
Verification (dry-run against prod inside a rolled-back transaction, 2026-07-06)
| Check | Result |
|---|---|
| All statements execute, no constraint violations | ✅ |
twitter.com/* rows remaining | 0 |
x.com/* rows after | 76,072 (= 52,450 existing + 23,622 renamed — exact) |
| Issue's Done-when fixture: the 3 artist-connected rows from chat#1851 | all 3 repointed to x.com/* keepers |
x.com/disclosure keeper carries the fresh scrape | 483,103 followers (was orphaned; connected row showed 0) |
| DB state after rollback | untouched (24,025 / 52,450 — confirmed) |
Merge sequencing
api#755 merges first (stops new twitter.com keys being written), then this migration cleans up existing rows. Running this first would also work but re-drift until the api change deploys.
🤖 Generated with Claude Code
Summary by cubic
Canonicalizes socials from twitter.com to x.com by merging duplicate rows and repointing foreign keys so X scraper write-backs attach to artist-connected records. Twinless twitter rows are renamed in place, and the migration fails if any twitter.com keys remain.
- Migration
- Run after recoupable/api#755 (normalizes new writes to x.com) to complete chat#1851.
- Post-check aborts if any twitter.com rows survive to prevent partial application.