Skip to content

[FEAT] Override manual de identidade de contribuidor (alias → canônico) #245

Description

@clickmatos

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

  • Nova tabela (migration Supabase) para alias de identidade por org: alias_key (identificador cru) → canonical_key (identidade correta), com created_by/created_at para auditoria
  • Endpoint de leitura autenticado que o CLI consulta uma vez no início do push (reaproveitando o OAuth já existente)
  • resolve_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 conseguiu
  • Ação de UI em HyperEngineers.tsx (na lista "unidentified" já existente) para um admin declarar "esses dois são a mesma pessoa"
  • Nenhuma fusão automática/probabilística é introduzida — toda fusão via alias exige confirmação humana explícita

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-192resolve_active_users(), onde a chave de agrupamento automática é calculada (linha ~165)
  • platform/lib/queries/org-summary.ts:1134-1205computeHyperEngineers, 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions