User story
Como operador de uma análise (Iris team ou cliente), eu quero declarar manualmente que dois identificadores de commit (nome/e-mail diferentes) são a mesma pessoa real, para que contribuidores não apareçam fragmentados em duas entradas de Hyper Engineer quando a resolução automática (email → GitHub login) não consegue ligá-los.
Contexto
A fusão de identidade hoje ("Hyper Engineers", PRs #204/#206/#207) é 100% automática e baseada em match exato de e-mail/login do GitHub:
iris/platform/identity.py::resolve_active_users() — agrupa por gh_username or email_local_part, resolvido via padrão noreply → bulk commit-list scan da API do GitHub → fallback por e-mail.
getOrgActiveContributors (platform/lib/queries/org-summary.ts:115) — chave = github?.toLowerCase() ?? name.toLowerCase(), match exato.
computeHyperEngineers (org-summary.ts:1201-1205) — mesma família de match exato (nome↔github, e-mail↔github, ou nome cru).
Nenhum dos três caminhos faz comparação de similaridade de nome (prefixo/substring/fuzzy). Se os e-mails de duas identidades não estiverem vinculados à mesma conta no GitHub, elas nunca se fundem — mesmo sendo a mesma pessoa real.
Exemplo real observado: no repo RocketBus/maestro-service, "Ramon" (autor em merges via botão do GitHub) e "RamonFrancisco" (autor em merges locais empurrados direto) muito provavelmente são a mesma pessoa, mas caem como duas entradas de Hyper Engineer distintas.
Este gap já é conhecido internamente — platform/src/app/[tenant]/dashboard/sections/HyperEngineers.tsx (lista de "unidentified") já tem um comentário nomeando exatamente esse cenário ("two aliases of the same person iris/cli.py couldn't tie together"), mas nunca foi construído um mecanismo pra resolvê-lo.
Investigação confirmou que é greenfield: não existe tabela, endpoint ou UI de override manual de identidade em nenhum lugar da plataforma hoje (/[tenant]/team gerencia organization_members, que é um conceito totalmente separado — contas de login da plataforma, não identidade de autoria de commit).
Acceptance criteria
Scope / non-goals
Dentro do escopo: override manual, explícito, auditável e reversível.
Fora do escopo: qualquer heurística de fuzzy-matching ou similaridade de nome para fusão automática — risco de falso positivo (atribuir trabalho de uma pessoa a outra) é maior que o custo de deixar duas entradas separadas até que um humano confirme.
Se encaixa em qual estágio?
Stage 2 (refinamento da plataforma atual)
Implementation notes
iris/platform/identity.py:101-192 — resolve_active_users(), onde a chave de agrupamento automática é calculada (linha ~165)
platform/lib/queries/org-summary.ts:1134-1205 — computeHyperEngineers, normalizeEmailIdentity
platform/src/types/org-summary.ts:203-210 — tipo HyperEngineer (flat, sem campo de alias/variantes — precisa expandir ou manter separado)
platform/src/app/[tenant]/dashboard/sections/HyperEngineers.tsx:49-102 — UI read-only atual, comentário já cita o gap (linhas 54-58)
platform/supabase/migrations/ — padrão de migration raw SQL (sem Prisma no projeto)
- Storage atual de active_users é um blob
JSONB opaco em analysis_runs.active_users (platform/supabase/migrations/010_active_users.sql:2) — avaliar se o alias deve viver em tabela própria (recomendado) em vez de dentro desse blob
User story
Como operador de uma análise (Iris team ou cliente), eu quero declarar manualmente que dois identificadores de commit (nome/e-mail diferentes) são a mesma pessoa real, para que contribuidores não apareçam fragmentados em duas entradas de Hyper Engineer quando a resolução automática (email → GitHub login) não consegue ligá-los.
Contexto
A fusão de identidade hoje ("Hyper Engineers", PRs #204/#206/#207) é 100% automática e baseada em match exato de e-mail/login do GitHub:
iris/platform/identity.py::resolve_active_users()— agrupa porgh_username or email_local_part, resolvido via padrão noreply → bulk commit-list scan da API do GitHub → fallback por e-mail.getOrgActiveContributors(platform/lib/queries/org-summary.ts:115) — chave =github?.toLowerCase() ?? name.toLowerCase(), match exato.computeHyperEngineers(org-summary.ts:1201-1205) — mesma família de match exato (nome↔github, e-mail↔github, ou nome cru).Nenhum dos três caminhos faz comparação de similaridade de nome (prefixo/substring/fuzzy). Se os e-mails de duas identidades não estiverem vinculados à mesma conta no GitHub, elas nunca se fundem — mesmo sendo a mesma pessoa real.
Exemplo real observado: no repo RocketBus/maestro-service, "Ramon" (autor em merges via botão do GitHub) e "RamonFrancisco" (autor em merges locais empurrados direto) muito provavelmente são a mesma pessoa, mas caem como duas entradas de Hyper Engineer distintas.
Este gap já é conhecido internamente —
platform/src/app/[tenant]/dashboard/sections/HyperEngineers.tsx(lista de "unidentified") já tem um comentário nomeando exatamente esse cenário ("two aliases of the same person iris/cli.py couldn't tie together"), mas nunca foi construído um mecanismo pra resolvê-lo.Investigação confirmou que é greenfield: não existe tabela, endpoint ou UI de override manual de identidade em nenhum lugar da plataforma hoje (
/[tenant]/teamgerenciaorganization_members, que é um conceito totalmente separado — contas de login da plataforma, não identidade de autoria de commit).Acceptance criteria
alias_key(identificador cru) →canonical_key(identidade correta), comcreated_by/created_atpara auditoriaresolve_active_users()aplica o override de alias como passo final, depois da resolução automática — nunca substitui a lógica automática, só corrige o que ela não conseguiuHyperEngineers.tsx(na lista "unidentified" já existente) para um admin declarar "esses dois são a mesma pessoa"Scope / non-goals
Dentro do escopo: override manual, explícito, auditável e reversível.
Fora do escopo: qualquer heurística de fuzzy-matching ou similaridade de nome para fusão automática — risco de falso positivo (atribuir trabalho de uma pessoa a outra) é maior que o custo de deixar duas entradas separadas até que um humano confirme.
Se encaixa em qual estágio?
Stage 2 (refinamento da plataforma atual)
Implementation notes
iris/platform/identity.py:101-192—resolve_active_users(), onde a chave de agrupamento automática é calculada (linha ~165)platform/lib/queries/org-summary.ts:1134-1205—computeHyperEngineers,normalizeEmailIdentityplatform/src/types/org-summary.ts:203-210— tipoHyperEngineer(flat, sem campo de alias/variantes — precisa expandir ou manter separado)platform/src/app/[tenant]/dashboard/sections/HyperEngineers.tsx:49-102— UI read-only atual, comentário já cita o gap (linhas 54-58)platform/supabase/migrations/— padrão de migration raw SQL (sem Prisma no projeto)JSONBopaco emanalysis_runs.active_users(platform/supabase/migrations/010_active_users.sql:2) — avaliar se o alias deve viver em tabela própria (recomendado) em vez de dentro desse blob