Skip to content

spec: domain column rationalization (Stage 3, #4159)#4215

Merged
bokelley merged 2 commits intomainfrom
bokelley/4159-domain-rationalization-design
May 8, 2026
Merged

spec: domain column rationalization (Stage 3, #4159)#4215
bokelley merged 2 commits intomainfrom
bokelley/4159-domain-rationalization-design

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 8, 2026

Summary

A spec / design doc for the third and final stage of the domain-cleanup work that started with the Media.net escalation #321. Stage 1 (auto-populate, #4157) and Stage 2 (member self-service, #4179) shipped. Stage 3 is the schema-level rationalization.

Recommendation: Option B — collapse member_profiles.primary_brand_domain into organization_domains.is_primary. The ~5 real DBA/holding-co cases migrate to AAO's existing parent/child org-hierarchy model.

Anchored on data

Survey of prod (155 profiles):

  • 53 (34%) match: brand_primary == membership_primary
  • 64 (41%) NULL primary_brand_domain
  • 38 (25%) divergent — categorized in the spec:
    • ~10 cosmetic (www.foo.com vs foo.com) → fixed by canonicalization during migration
    • ~14 personal-tier members where membership-primary is just absent → insert org_domains row with source='manual'
    • ~5 real DBA/holding-co cases → migrate to parent/child orgs
    • ~3 junk → ignore

So ~85% is bugs or trivially resolvable. The conceptual "two primaries" distinction is mostly unintentional drift, not real product requirement.

What this PR is and isn't

  • Is: Specs/design doc + survey results + migration plan + consumer rewrite list (24 source files referencing primary_brand_domain).
  • Is not: Any actual code change. Implementation begins after Brian's review of the 5 real-divergence cases.

Decision needed

The 5 real-divergence cases. Spec lists each. For each: do we (a) move them to parent/child orgs, or (b) just pick one domain as primary?

🤖 Generated with Claude Code

bokelley and others added 2 commits May 8, 2026 06:55
Option B recommendation: collapse member_profiles.primary_brand_domain
into organization_domains.is_primary. Surveys current divergence (38 of
155 profiles) and categorizes each. Most are bugs or trivially
resolvable; ~5 real DBA/holding-co cases would migrate to AAO's existing
parent/child org-hierarchy model.

Migration plan: stage 0 audit + canonicalization, stage 1 dual-read with
resolver, stage 2 drop column, stage 3 single canonical writer.

Open for review on issue #4159 — wants Brian's steer on the ~5 real
divergence cases before implementation begins.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brian signed off on Option B 2026-05-08. Updates:
- Status: Decided.
- Auto-link safety check: findPayingOrgForDomain walks any verified
  organization_domains row, not just is_primary. Migration safe as long
  as old primaries stay as verified non-primary rows.
- Per-case dispositions for the 6 non-trivial divergence cases. None
  need the parent/child hierarchy model.
- Sequencing: Stage 0 preconditions, Stage 1 resolver, Stage 2 drop
  column, Stage 3 canonical writer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit fec4b19 into main May 8, 2026
13 checks passed
@bokelley bokelley deleted the bokelley/4159-domain-rationalization-design branch May 8, 2026 11:10
bokelley added a commit that referenced this pull request May 8, 2026
* feat(scripts): Stage 0 domain-cleanup script (#4159)

Two phases for the precondition work before #4159's Stage 1 resolver:

- canonicalize-www: strip www. prefix from primary_brand_domain values
  where the apex form is already in organization_domains for the same
  org. ~10 such cases per the 2026-05-08 survey.
- per-case-fixes: hand-tuned for the 6 non-trivial divergence cases
  (DanAds, iPROM, Transfon, Mission Media, Triton, Mangrove). Each
  guards on expected before-state and aborts on drift, so re-runs after
  manual changes don't stomp.

Dry-run default; --apply to write. Both phases are independently
runnable. Per-case writes BEGIN/COMMIT inside FOR UPDATE on the org
row to serialize against concurrent WorkOS webhooks.

Spec: specs/domain-column-rationalization.md (merged in #4215).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* address expert review on Stage 0 script

- Post-write invariant assertion: throw + ROLLBACK if not exactly one
  is_primary=true row remains. Prevents silent email_domain=NULL
  corruption if a future case demotes without promoting. (code-reviewer)
- Wire up expected_organization_domains_before guard that was declared
  but never read. Each per-case fix now asserts the rows it depends on
  match before-state. (code-reviewer)
- Reapply path: when re-run after partial application, run the
  org_domains writes idempotently to converge drift, but skip the
  brand_primary update (already at after-state). Was silent-skip before;
  drift in org_domains-only would have been missed. (code-reviewer)
- requires_external_proof flag on Mission Media. The DBA assertion
  enables auto-link for @winstarinteractive.com — script refuses to
  --apply without --allow-external-proof. (security-reviewer)
- --only=Name1,Name2 filter for staged rollout per case. (security-
  reviewer)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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