Skip to content

Flip WhatsApp URNs to the new scheme model (management command, BSUID contacts only)#6710

Closed
norkans7 wants to merge 1 commit into
mainfrom
dev-n7/whatsapp-urn-flip
Closed

Flip WhatsApp URNs to the new scheme model (management command, BSUID contacts only)#6710
norkans7 wants to merge 1 commit into
mainfrom
dev-n7/whatsapp-urn-flip

Conversation

@norkans7

@norkans7 norkans7 commented Jul 7, 2026

Copy link
Copy Markdown

What

Adds a flip_whatsapp_urns management command (not a data migration) that flips existing contact URNs to the new scheme model, only for contacts that have a bsuid URN:

  • whatsapp:<e164 digits>tel:+<e164 digits> (the phone identity)
  • bsuid:<CC.alphanumeric>whatsapp:<CC.alphanumeric> (the business-scoped user id)

Scoping to contacts with a bsuid means a contact reachable only by a plain whatsapp: phone URN (no bsuid) is left untouched — we only migrate contacts that actually have a business-scoped id to take over the whatsapp: slot.

Dropping redundant whatsapp URNs

When a contact's all-digit whatsapp:<digits> URN would collide with a tel:+<digits> URN already on the same contact, that whatsapp URN is redundant (the BSUID is taking over the whatsapp: slot). Rather than leaving it behind, the command moves its Msg / Call / ChannelEvent references onto the surviving tel URN and deletes it. Collisions with a tel: URN on a different contact are left untouched.

Batching, concurrency & re-runnability

  • Runs in id-ordered batches (cursor by URN id, so skipped collisions don't stall the loop).
  • The collision lookup is scoped by org_id and identity; if a concurrent write inserts a colliding identity between the check and the update, that batch is logged and skipped (the command is safe to re-run and retries it).
  • Bumps modified_on on flipped contacts so the search indexer picks them up.
  • Safe to re-run: only all-digit whatsapp paths are flipped, and once a contact's bsuid is gone it's no longer processed.

URN model changes

  • URN.validate / URN.normalize: the whatsapp scheme now holds either a phone number (legacy) or a business-scoped id (CC.alphanumeric), matching gocommon (Accept business-scoped user IDs as WhatsApp URNs gocommon#218). The BSUID pattern is shared via URN.BSUID_PATH_REGEX.
  • URN.format: business-scoped whatsapp ids are shown as-is; legacy all-digit whatsapp paths still get friendly phone formatting.
  • ContactURN gains CONTENT_RELATIONS + has_content() / reassign_content_to(), centralising "what references a URN" (previously hardcoded in Contact._full_release, now shared) so the command's reassign-then-delete uses the model.

Why a command instead of a migration

Lets ops run and observe the flip deliberately (and re-run it) rather than having it fire implicitly during deploy.

Tests

temba/contacts/management/commands/tests.py — both flip directions; the "no bsuid → not flipped" scoping; whatsapp→tel and bsuid→whatsapp collision skips; dropping a redundant whatsapp URN and reassigning its message reference onto the tel URN; a multi-batch id-cursor test (patched BATCH_SIZE) that would loop forever on a cursor regression; and a re-apply idempotency check. Plus updated test_urn.py for the new whatsapp validate/normalize/display semantics.

🤖 Generated with Claude Code

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown

Claude finished @norkans7's task in 4m 20s —— View job


PR Review

  • Load PR context
  • Read changed files
  • Analyze migration correctness & idempotency
  • Analyze URN model changes
  • Check test coverage
  • Post review

Summary

Overall this looks solid — the two-pass flip, digits_only=True guard on the whatsapp→tel pass, and per-batch collision skip are correct and make the migration re-runnable. The id-cursor batching correctly advances past skipped collisions so the loop can't get stuck. Tests cover the substantive cases (both flip directions, both collision shapes, non-digit whatsapp left alone, idempotency). URN model changes match gocommon.

A few things worth surfacing (all inline):

  1. Concurrency during migrationatomic=False + no per-batch try/except means a concurrent insert of a colliding (org_id, identity) between the existing fetch and bulk_update will raise IntegrityError and abort the batch. Re-running recovers, but consider a maintenance window or per-batch error handling. Also the existing query could scope by org_id__in=… — currently it pulls matches across all orgs.
  2. Display regression for collision-skipped legacy whatsapp URNs — removing the whatsapp branch from URN.format means any leftover whatsapp:<digits> row (collision case) will render as bare digits instead of the previous friendly phone format. Probably fine, but explicit.
  3. URN.validate still accepts bare digits for whatsapp — necessary during migration, but leaves the API able to keep inserting legacy-shape rows indefinitely. Worth a follow-up to tighten once traffic stops writing the old form.
  4. Batching test coverage — the migration test only exercises a single batch. Overriding BATCH_SIZE to 2 in a dedicated test would exercise the cursor/collision-advancement logic that's the trickiest part of _flip_scheme.

Nothing here is a merge blocker — no correctness bugs found in the flip logic itself, and the merge-order note (after #6709) is already called out in the PR body.
branch

Comment thread temba/contacts/migrations/0217_flip_whatsapp_tel_urns.py Outdated
Comment thread temba/contacts/models.py Outdated
Comment thread temba/contacts/models.py
Comment thread temba/contacts/tests/test_migrations.py Outdated
@norkans7 norkans7 changed the title Flip existing WhatsApp URNs to the new scheme model Flip WhatsApp URNs to the new scheme model (management command, BSUID contacts only) Jul 7, 2026
@norkans7
norkans7 force-pushed the dev-n7/whatsapp-urn-flip branch 2 times, most recently from f75d460 to 50da2be Compare July 7, 2026 13:24
Adds a flip_whatsapp_urns management command that flips existing contact URNs
for contacts that have a bsuid URN:
  * whatsapp:<e164 digits> -> tel:+<e164 digits> (the phone identity)
  * bsuid:<CC.alphanumeric> -> whatsapp:<CC.alphanumeric> (the business-scoped id)

Runs in id-ordered batches, skips rows whose target identity already exists for
the org (scoping the lookup by org_id, and skipping a batch on a concurrent-write
IntegrityError), reindexes flipped contacts, and is safe to re-run.

When a contact's all-digit whatsapp URN duplicates a tel URN it already has, the
whatsapp URN is redundant (the bsuid takes over the whatsapp slot), so its
message/call/event references are moved onto the tel URN and it's dropped rather
than left behind.

URN.validate/format/normalize are updated so the whatsapp scheme holds either a
phone number (legacy, still shown as a friendly phone number) or a business-scoped
id, matching gocommon.
@norkans7
norkans7 force-pushed the dev-n7/whatsapp-urn-flip branch from 50da2be to d4b5c34 Compare July 7, 2026 13:40
@norkans7

Copy link
Copy Markdown
Author

Replaced by #6727

@norkans7 norkans7 closed this Jul 15, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant