spec: domain column rationalization (Stage 3, #4159)#4215
Merged
Conversation
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
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>
bokelley
added a commit
that referenced
this pull request
May 9, 2026
* feat(scripts): Stage 0.3 — insert-missing-rows phase (#4159) Adds the third Stage 0 phase. For each member_profiles row where primary_brand_domain is set but no matching organization_domains row exists for the same org, inserts as source='manual', verified=true, is_primary=true. Trust model: these domains were claimed via the brand-claim verify flow (DNS publication of brand.json) — different proof from WorkOS DNS, but the script asserts them as verified-equivalent for the purpose of auto-link routing. Pre-Stage-0 these orgs already routed via the member_profile field; this just makes the trust explicit on the row. Cross-org collisions (some other org owns the domain) are surfaced as warnings + exit code 2, not silently stomped. Five collision-shaped tests cover the candidate-discovery and ON CONFLICT DO NOTHING paths. 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.3 script Security (must-fix): - Filter candidates through assertClaimableBrandDomain before insert. Stale primary_brand_domain values can include junk (Mangrove had linkedin.com pre-fix). Without the filter, we'd elevate those to verified+is_primary, enabling auto-link from those domains. Mangrove- shaped rows now get classified as non-claimable and surfaced in the exit-code-2 tally for manual reset. Code review (nice-to-haves): - Track race-lost as its own counter and exit code 2 on any race loss. - LOWER() on insert + LOWER() on email_domain mirror — symmetry with the LOWER-based candidate match. - Drop unused is_personal column from candidate query. - New test asserts the demote-rollback path: pre-seed an is_primary=true row, simulate ON CONFLICT race-loss, assert the existing primary survives. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
May 9, 2026
* feat(server): Stage 1 brand-domain resolver (#4159) Introduces the single read surface for an org's brand-identity primary domain. Reads organization_domains.is_primary=true first (the canonical Stage 1 source after Stage 0's backfill), falls back to member_profiles.primary_brand_domain for any org Stage 0 missed. Logs a warn on every fallback so we can spot remaining drift before Stage 2 drops the column. Two surfaces: - getBrandPrimaryDomain(orgId): single-org lookup - getBrandPrimaryDomainsForOrgs(orgIds): batched, for list views No call site changes here. Subsequent PRs migrate the ~14 read sites identified in the spec. Spec: specs/domain-column-rationalization.md (#4215). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * address expert review on Stage 1 resolver Code-reviewer: - Refactor test setup so beforeAll/afterAll runs once per file (was once per describe — duplicate runMigrations + double pool init). - Aggregate batch fallback warns into one log line per call instead of one-per-row. Keeps signal usable on large batches. - Add tests for verified=false is_primary=true (documents that the resolver doesn't filter on verified — verified is enforced at write time) and multiple-is_primary=true rows (documents the don't-crash contract on data anomalies). - Tighten doc comment: missing orgs are absent from the result map, not present with undefined values. Security-reviewer: - Add explicit AUTHORIZATION note to the file's doc comment. - Replace LIMIT 1 (without ORDER BY) with full-result + multi-primary detection. logger.error if the Stage 0 "exactly one" invariant is broken; still return a valid primary so callers don't crash on the anomaly. Same shape applied to the batch variant. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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_domainintoorganization_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):
www.foo.comvsfoo.com) → fixed by canonicalization during migrationSo ~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
primary_brand_domain).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