# Contributor Resolution Contributor resolution is the process of mapping platform user references (login, email, avatar, etc.) to canonical contributor records in the database. Aveloxis has two layers of resolution that operate at different phases of the collection pipeline. --- ## Two layers ### Layer 1: API-phase resolution (during collection) Operates during issue, PR, event, and message collection. Maps platform user references to `cntrb_id` UUIDs. ### Layer 2: Git-phase resolution (after facade) Operates after the facade phase. Maps git commit author emails to GitHub user accounts. --- ## API-phase resolution: ContributorResolver The `ContributorResolver` resolves platform user references to canonical `cntrb_id` UUIDs using a three-tier strategy. ### Tier 1: In-memory cache The cache maps three lookups: - **Platform user ID** -> `cntrb_id` - **Email** -> `cntrb_id` - **Login** -> `cntrb_id` On a cache hit, no database query is needed. The cache is write-through: any new contributor inserted into the database is also added to the cache. The cache persists across repos within the same process lifetime, so contributors who appear in multiple repos are resolved without repeated DB lookups. ### Tier 2: Database lookup If the cache misses, the resolver queries the `contributor_identities` table: ```sql SELECT cntrb_id FROM aveloxis_data.contributor_identities WHERE platform_id = $1 AND platform_user_id = $2 ``` The unique constraint on `(platform_id, platform_user_id)` ensures this lookup returns at most one result. ### Tier 3: Create new If no existing contributor is found, a new one is created: 1. A deterministic `cntrb_id` UUID is generated via **GithubUUID** 2. A row is inserted into `contributors` with all available profile fields 3. A row is inserted into `contributor_identities` mapping the platform identity to the contributor 4. Both the cache and the database are updated --- ## GithubUUID: Deterministic contributor IDs Aveloxis generates `cntrb_id` UUIDs deterministically from the platform user ID. The UUID encodes: | Byte(s) | Content | |---|---| | 0 | Platform ID (`1` = GitHub, `2` = GitLab) | | 1-4 | Platform user ID (big-endian, zero-padded) | | 5-15 | Zero-filled | ### Properties - **Deterministic:** The same GitHub/GitLab user always gets the same UUID, regardless of which Aveloxis instance creates it. - **Augur-compatible:** The encoding scheme matches Augur's `GithubUUID` function, so contributor IDs are byte-compatible between the two systems. - **Cross-platform safe:** GitHub and GitLab users with the same numeric ID get different UUIDs (different platform byte). - **Large ID support:** GitHub user IDs up to 2^32 are supported in 4 bytes. ### Example GitHub user ID `12345` on platform `1` (GitHub): ``` Byte 0: 0x01 (GitHub) Bytes 1-4: 0x00003039 (12345 big-endian) Bytes 5-15: 0x00000000000000000000 UUID: 01003039-0000-0000-0000-000000000000 ``` --- ## Git-phase resolution: Commit resolver After the facade phase inserts commit rows, the commit resolver maps git commit author emails to GitHub user accounts. This is the Go implementation of the [augur-contributor-resolver](https://github.com/augurlabs/augur-contributor-resolver) scripts. ### Resolution strategy (cheapest first) The resolver processes unresolved commit emails in order of cost: #### 1. Noreply email parse (free) GitHub noreply emails have a predictable format: ``` 12345+username@users.noreply.github.com username@users.noreply.github.com ``` The parser extracts: - **Login** from the username portion - **`gh_user_id`** from the numeric prefix (if present) No API call is needed. The contributor can be resolved entirely from the email string. #### 2. Database lookup (free) Checks existing records: - `contributors.cntrb_email` -- direct email match - `contributors.cntrb_canonical` -- canonical email match - `contributors_aliases.alias_email` -- alias email match #### 3. GitHub Commits API (1 API call) ``` GET /repos/{owner}/{repo}/commits/{sha} ``` The response includes the linked GitHub user object with all profile fields (`gh_user_id`, `gh_node_id`, `gh_avatar_url`, all `gh_*` URLs, etc.). This is the most reliable method because it uses the commit SHA itself to find the linked user. #### 4. GitHub Search API (1 API call) ``` GET /search/users?q={email}+in:email ``` For remaining non-noreply emails that were not found by the Commits API. The Search API has a lower rate limit (30 requests/minute), so it is used as a last resort. ### Post-resolution actions For each resolved commit author: 1. **Update commit rows:** `cmt_author_platform_username` is set on all commit rows with that author's email 2. **Create/update contributor:** A contributor row is created (or updated) with the deterministic GithubUUID and all `gh_*` profile fields 3. **Detect login renames:** If the same `gh_user_id` maps to a different login than what is in the database, the contributor's `gh_login` is updated 4. **Create alias:** An entry in `contributors_aliases` maps the commit email to the contributor's canonical email 5. **Bulk backfill:** After all commits are resolved, a SQL join sets `cmt_ght_author_id` from `cmt_author_platform_username` -> `contributors.gh_login` --- ## Login rename detection GitHub users can rename their accounts. When this happens: - The old login still appears in historical API responses and git commits - The new login appears in current API responses - The `gh_user_id` remains the same Aveloxis detects renames by comparing: ``` IF existing_contributor.gh_user_id == resolved_user.gh_user_id AND existing_contributor.gh_login != resolved_user.gh_login THEN update gh_login to the new value ``` This keeps contributor records current without creating duplicate entries. --- ## Canonical email enrichment Canonical emails are set through two paths: 1. **Primary (v0.14.4+)**: `EnrichThinContributors` calls `GET /users/{login}` and sets `cntrb_canonical` directly from the public email (filtering noreply addresses). This is the main source of canonical emails. 2. **Fallback**: `ResolveEmailsToCanonical` runs after commit resolution for contributors that have `gh_login` but were not yet enriched. Limited to 500 per pass (`CanonicalBatchSize`). Both paths mark contributors via `cntrb_last_enriched_at` to prevent re-querying users with private emails (where the canonical will always be null). Contributors are retried after 30 days. --- ## Alias creation The `contributors_aliases` table maps alternate email addresses to a contributor's canonical email. Aliases are created during commit resolution: ```sql INSERT INTO aveloxis_data.contributors_aliases (cntrb_id, canonical_email, alias_email, cntrb_active) VALUES ($1, $2, $3, 1) ON CONFLICT (alias_email) DO NOTHING; ``` The alias table is consulted during database lookups (tier 2 of the API-phase resolver), enabling future resolution of the same email without API calls. --- ## Contributor breadth worker Every 6 hours, the scheduler runs the breadth worker to discover cross-repo contributor activity: 1. Selects up to 100 contributors, prioritizing those never processed, then oldest 2. For each contributor, calls `GET /users/{login}/events` 3. Each event (PushEvent, PullRequestEvent, IssuesEvent, etc.) is stored in `contributor_repo` This maps contributors to their activity across repos outside the tracked set, providing a broader picture of contributor engagement. --- ## Resolution flow diagram ``` Commit email from git log | v ┌─────────────────┐ │ Noreply parse? │──yes──> Extract login + user_id └────────┬─────────┘ Create/update contributor │ no v ┌─────────────────┐ │ DB lookup? │──yes──> Return existing cntrb_id │ (email, alias) │ └────────┬─────────┘ │ no v ┌─────────────────┐ │ Commits API? │──yes──> Create contributor with GithubUUID │ GET /commits/sha │ Create alias └────────┬─────────┘ │ no v ┌─────────────────┐ │ Search API? │──yes──> Create contributor with GithubUUID │ GET /search/users│ Create alias └────────┬─────────┘ │ no v Unresolved (stored in unresolved_commit_emails) ``` --- ## Next steps - [Facade Commits](facade-commits.md) -- how git log data is parsed and stored - [Staged Pipeline](staged-pipeline.md) -- how staging enables bulk contributor resolution - [Overview](overview.md) -- system architecture overview