Human provenance — where identity lives, column by column
Every research claim built on this database ultimately rests on one question: who did this, and how do we know? This page is the adversarially-reviewed map of every identity-bearing column — who writes it, from what upstream field, what NULL means, and which heal advances it. When a column here disagrees with the code, the code wins and this page is a bug; file it like one.
The four identity chains
Chain |
Source of truth |
Stable key |
Written by |
|---|---|---|---|
Forge (GitHub/GitLab) |
The forge’s numeric user id |
|
|
Git (commits) |
Commit author email |
email → |
The commit resolver (4 strategies, R1–R13 contract) |
Email (mailing lists) |
The sender’s RFC-822 From address |
email/canonical/alias → |
Drain-time resolve + the sender backfill ( |
Jira |
The Server-era stable username ( |
|
|
Rules that hold everywhere:
SR-6 — never fabricate identity. A link is written only on an unambiguous match or a deliberate, labeled mint. Ambiguity stays NULL with the raw identity preserved.
Automation never gets an identity.
collector.IsAutomationEmailand its SQL twinaveloxis_data.is_automation_emailgate every attribution path (bots, the Jira/GitBox relays, list addresses). Ungated minting once handed 83,746 messages to ajira@apache.orgphantom contributor; the ledgered heal (v0.29.0 heal automation-phantom contributors) repaired it and the gates keep it repaired. Downstream analytics queries use the same function as a row filter over tens of millions of message rows, so it is declaredPARALLEL SAFEand its list-address lookup is served by theidx_rgls_email_lowerexpression index (v0.29.8). Before that, every calling query ran single-threaded and each call scanned the whole list table: 340 s against 3.3 s on a synthetic 3M-row benchmark (local PostgreSQL 18; production timings differ).Soft-delete only. A merged/phantom contributor row is marked
cntrb_deleted = 1, never removed (R10 — FK integrity). Every read path filtersCOALESCE(cntrb_deleted, 0) = 0.
Issues — the multi-provider entity
One logical ticket = one issues row keyed (repo_id, external_key). Three providers can describe the same ticket, ranked:
Rank |
Provider |
May write |
data_source |
|---|---|---|---|
1 |
Forge API |
everything on its own rows; untouchable by lower ranks |
|
2 |
Jira API |
state, reporter, title, |
|
3 |
Mail projection |
creates synthetics; state via notification actions, event-time guarded |
|
There is no duplication window by construction:
syntheticIssueID(external_key) is a deterministic negative id, so
mail and the Jira collector minting the same ticket independently
converge on the same row in either arrival order. A real Jira internal
id lives in issues.jira_issue_id — synthetics keep the negative
platform_issue_id forever (a positive Jira id could collide with a
GitHub databaseId under the unique arbiter, and the
projection-duplicate detector keys on the sign).
Identity columns on issues:
Column |
Written by |
NULL means |
|---|---|---|
|
Forge collection (rank 1); Jira backfill/collector (rank 2, synthetic rows); mail projection (rank 3, human senders only — the relay is never a reporter) |
Reporter identity not yet resolvable. Jira synthetics: run |
|
Forge event derivation (v0.26.5) |
No close event observed (or history-capped). |
Messages — one body table, five origins
messages.platform_id + msg_kind say what a row IS:
platform_id |
msg_kind |
Origin |
|
|
|---|---|---|---|---|
1/2 |
1–3 |
Forge comments/reviews |
The staged processor’s resolver |
GraphQL node ID |
4 |
1 |
Native Jira comment |
|
(empty) |
6 |
4 |
Mailing-list body |
Drain-time resolve + |
The RFC-822 Message-ID — the join key to |
Email-row specifics:
msg_sender_emailis the RAW parsed sender — provenance, not a resolution.cntrb_idis the resolution; NULL = not yet resolvable and the hourly backfill keeps retrying as identities accrue (measured: a full pass is ~5–10 minutes; the alias arm alone reaches 163K bodies nothing else can).data_sourceholds the LIST ADDRESS.msg_textis raw forever;msg_text_cleanis the quote-stripped variant (email rows only; NULL = read the raw text).
Duplicate messages across origins are LINKED, not deleted: a
GitBox mirror mail’s native twin is on email_message.linked_*
(v0.28.20); a Jira notification’s native comment twin is on
email_message.linked_msg_id (v0.29.0, stamped at collection time by
the pilot-validated ±2-minute match — in EITHER arrival order: the
Jira collector links the nearest unlinked notification when it stores
a native comment, and the mailing-list projection reverse-links the
nearest unclaimed native comment when a [Commented] notification
arrives after Jira collection already ran). Read-time precedence is
always native > notification, keyed off those stamps — never off
fuzzy matching at query time. issues.comment_count counts LOGICAL
comments: refs whose message is a superseded notification (its
email_message row carries linked_msg_id) are excluded from the
recount, so a matched pair counts once. The ~7% of notifications
with no native match are the sole record and still count.
jira_identities — the banked usernames
Every Jira identity observed via the REST API, raw and permanent:
jira_name (the Server-era stable username — absent from Jira
Cloud’s API, which is why this table is collected early),
jira_user_key (the internal JIRAUSERnnnn key), display_name, and a
nullable cntrb_id with match_method:
match_method |
Meaning |
|---|---|
|
|
|
|
|
Jira-only person — a contributor row was minted so networks include them |
(empty) |
Unmatched or AMBIGUOUS — preserved raw, linked to nothing (SR-6) |
contributors_aliases — the email bridge
The one table every email-shaped identity flows through. Four writer
families, each stamping its own tool_source (v0.29.0 — previously
every path claimed to be the commit resolver): the commit resolver’s
two alias paths, the mailing-list sender-resolve linker, and the
v0.20.2 rename-merge loser insert. alias_email is UNIQUE — the
structural bound that keeps attribution fan-out at one contributor per
address.
Which heal advances what
Gap |
Heal |
Cadence |
|---|---|---|
Unattributed email bodies |
|
plus the hourly ticker ( |
Unstripped email history |
|
once; ingest strips forward |
Permanently-open Jira synthetics |
ledgered migrate step ( |
once; the projection + collector keep state forward |
Jira reporter/comment identity |
|
once soon (the Cloud-migration window); the collector keeps it forward |
Phantom automation contributors |
ledgered migrate step ( |
once; the gates prevent recurrence |
Mis-drained mailing-list rows (cross-system drain) |
ledgered migrate step ( |
once; the system-scoped drain claim prevents recurrence |
Validated invariants (Part G, 2026-09-01)
The five-project main-vs-branch validation (tomcat, hbase, kafka, beam, arrow — full collection + full-history mailing lists + Jira sync on two scratch databases) closed with these properties holding, now the documented contract for the identity chains above:
I1 — zero duplicate
(repo_id, external_key)issues in any arrival order (mail-then-API and API-then-mail both converge on one row via the deterministic negative id).I2 —
platform_issue_id < 0exactly for mail/Jira-born rows; native forge rows never carry a synthetic id.I3 — every Jira-touched issue whose key has conversational mail keeps at least one
issue_message_refbridge (68,332 of 68,444 API issues bridged; the 112 residual are provably mail-less).I4 —
email_message.linked_msg_idcovers 98.8% of comment notifications on the overlap window (pilot floor: 94.4%).I5/I6 — no automation-phantom contributors; no message attribution points at an automation sender.
I7 — mail/Jira state writers never alter a native forge row (arrow’s 28K native issues byte-stable across sides), and API-owned rows only yield to strictly newer mail events (
trackerActionEventGuardSQL).Analytics A/B — network growth is connective (largest-component share 0.97–1.00 on both sides), and text features read the quote-stripped bodies via
COALESCE(msg_text_clean, msg_text). That COALESCE consumer lives in the aveloxis-analytics repository (data/sql/features/static/messages_text_stats.sql, pinned by its own tests) — deliberately NOT in this repository: no in-repo production surface serves message text (the Augur-compat message endpoint is count-only, and the strip CLI’s worklist read is the stripper’s raw INPUT). A future in-repo text-serving endpoint must read the COALESCE.