Skip to content

feat: resolve contributor emails to akrites members by verified domain (CM-1826) - #4917

Merged
mbani01 merged 3 commits into
mainfrom
feat/email-domain-affiliation-resolver
Oct 12, 2026
Merged

mbani01 merged 3 commits into
mainfrom
feat/email-domain-affiliation-resolver

Conversation

@mbani01

@mbani01 mbani01 commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Second affiliation resolver for the Akrites member contributors epic (CM-1807): email → Akrites member via the member's verified CDP primary domains. Confidence email_domain, score 0.7. Pure function over a batch; nothing is persisted, scheduled or wired (that is CM-1828).

  • resolveEmailDomainAffiliations(cdpQx, emails, members = AKRITES_MEMBERS) returns a Map keyed by email:<lowercased email>.
  • Domain table loaded once per call via the existing fetchManyOrganizationVerifiedPrimaryDomains, walked in registry order so output is stable regardless of DB row order.
  • Public email providers and disposable domains never match, even when CDP lists them under a member (gmail/outlook/live/hotmail/gmial/arcor are all verified under members today).
  • Subdomains match the parent member domain; the walk stops at the registrable domain (foo.co.uk never tries co.uk).
  • A domain verified under two different members (meetup.com: IBM + Microsoft) is excluded for both and logged once per call. Same member under several org ids keeps the first in registry order.
  • Alternative domains deliberately excluded: they add ~0.06 pts of coverage for typo squats and marketing sites.

Expected coverage once wired: ~55k of 1.74M distinct contributor emails, Google / Microsoft / Red Hat / IBM on top, zero public-provider matches.

Test plan

  • 12 unit tests in __tests__/resolveEmailDomainAffiliations.test.ts (exact, subdomain, registrable-domain stop, public provider, disposable, unknown, invalid, cross-member conflict, same-member determinism, single batched query, case/dedupe)
  • npx vitest run src/member-contributors: 8 files, 89 tests pass
  • oxlint --deny-warnings, oxfmt, package-level tsc clean on touched files

🤖 Generated with Claude Code

…n (CM-1826)

Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI balanced review requested due to automatic review settings October 12, 2026 16:14
@cursor

cursor Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

PR Summary

Low Risk
New isolated resolver with no production wiring yet; matching rules reduce false positives from public email and cross-member domain conflicts.

Overview
Adds a batch email-domain affiliation resolver for Akrites member contributors: resolveEmailDomainAffiliations maps contributor emails to members using CDP verified primary domains, with email_domain confidence and score 0.7.

The resolver loads verified domains once per call, walks subdomains up to the registrable domain, and skips public/disposable domains and domains claimed by more than one member (with a single warn). Results are keyed as email:<lowercased email> via the new ResolvedEmailDomainAffiliation type. Not wired into jobs or persistence in this PR.

Unit tests cover matching, exclusions, conflicts, determinism, and batched CDP fetch behavior.

Reviewed by Cursor Bugbot for commit 3fce8ad. Bugbot is set up for automated code reviews on this repo. Configure here.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Provider and disposable-domain subdomains can still be accepted when stored directly as primary domains.

1 open finding
What changed in this PR

Adds email-domain affiliation resolution for Akrites member contributors.

Changes:

  • Resolves normalized emails using verified primary domains.
  • Excludes conflicts, public providers, and disposable domains.
  • Adds comprehensive unit tests.
File Description
types.ts Defines email-domain affiliation results.
resolveEmailDomainAffiliations.ts Implements domain-based resolution.
resolveEmailDomainAffiliations.test.ts Tests matching, exclusions, and determinism.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI balanced review requested due to automatic review settings October 12, 2026 16:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The resolver is scoped, deterministic, thoroughly tested, and the previous exclusion issue is correctly fixed.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

@mbani01 mbani01 self-assigned this Oct 12, 2026
Copilot AI balanced review requested due to automatic review settings October 12, 2026 17:37
@mbani01
mbani01 merged commit 117b234 into main Oct 12, 2026
14 checks passed
@mbani01
mbani01 deleted the feat/email-domain-affiliation-resolver branch October 12, 2026 17:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Public-suffix and overlapping-domain cases can produce incorrect affiliations.

2 open findings

🧠 Review effort: Balanced

Comment on lines +58 to +59
const registrable = registrableDomain(domain)
if (!registrable || !domain.endsWith(registrable)) return [domain]
Comment on lines +31 to +35
if (isIneligibleDomain(domain) || conflictingDomains.has(domain)) continue
const owner = owners.get(domain)
if (!owner) {
owners.set(domain, { akritesMember: member.name, cdpOrganizationId })
} else if (owner.akritesMember !== member.name) {
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.

3 participants