Skip to content

[DEBT] PR enrichment degrades silently when the GraphQL step fails; the report carries no signal #236

Description

@rafaeldearaujop

[DEBT] PR enrichment degrades silently when the GraphQL step fails; the report carries no signal

Em uma frase: dois runs do mesmo engine, no mesmo repo, minutos
um do outro, produziram merge_strategy_dominant_share 1.0 e 0.845,
flow_pr_count ausente e 52, "In PR" 0 e 1 — porque o passo de
enriquecimento via gh api graphql falhou uma vez e caiu para [] sem
deixar rastro no JSON nem no relatório.

Referências arquivo:linha apontam para main @ da16235.


Qual é a dívida?

iris/ingestion/github_reader.py busca PRs em até três passos
(_fetch_prs, :95-145): (1) gh pr list básico; (2) enriquecimento via
gh api graphql (_fetch_pr_enrichment_graphql, :184) que traz o parent
count do merge commit
e as refs de commits por PR; (3) reviews via
gh pr list --json number,reviews (:140). O próprio código documenta que
o passo 2 é sujeito a "GitHub GraphQL 504 timeouts" (:89-91).

Quando o passo 2 falha, o código faz except (CalledProcessError, FileNotFoundError): return [] (:235-240) e segue. Consequências
observadas no output:

Campo Com enriquecimento Sem (degradado) Por quê
merge_strategy_dominant_share 0.845 (ground truth por parent count) 1.0 (heurística de mensagem (#N) → squash) merge_strategy_detector.py:25-32 cai no fallback
flow_pr_count, flow_efficiency_*, time_in_phase_median_hours presentes (52 PRs) ausentes flow precisa das refs de commits
acceptance_by_origin.*.commits_in_prs / origin_funnel "In PR" 1 0 idem
pr_merged_count 84 84 passo 1 funcionou — a degradação é parcial

Nenhum campo do JSON sinaliza que houve degradação (grep -i "error\|warn\|degrad" nas chaves → vazio), e o relatório apresenta os
números degradados com a mesma confiança dos completos. A mesma classe de
degradação silenciosa existe em cli.py:497-499 (except Exception: prs = []) para a busca inteira.

Por que é dívida?

  • Confiança sem base: o leitor não consegue distinguir "este repo usa
    squash em 100% dos merges" de "o enriquecimento falhou". São conclusões
    opostas sobre a confiabilidade das métricas por commit (o próprio
    merge_strategy existe para avisar quando squash "erodes per-commit
    signal").
  • Séries temporais ruidosas: na platform, um run degradado vira um ponto
    fora da curva em flow_* e em merge strategy, indistinguível de mudança
    real.
  • Contradiz "deterministic outputs" (CLAUDE.md, Coding Principles):
    o mesmo input produz outputs diferentes conforme a rede, e o output não
    diz qual dos dois é.

Como chegou aqui?

Degradação graciosa foi a escolha certa para a ausência de gh
(is_gh_available(), :62-64) — Iris deve funcionar sem PRs. A mesma
estratégia foi estendida para falhas transitórias de um passo
intermediário sem acrescentar o sinal correspondente.

Plano de migração

  • github_reader.py: registrar qual passo degradou (basic,
    enrichment, reviews) e propagar até o cli.py.
  • ReportMetrics: campo pr_enrichment_degraded: list[str] | None
    (vazio/None quando tudo funcionou) — iris/models/metrics.py; emitido
    por to_dict().
  • writer.py/narrative.py: caveat no topo da seção de PRs quando o
    campo não for vazio (chaves i18n EN + PT-BR): "PR enrichment
    incomplete: merge strategy is heuristic; flow metrics unavailable".
  • merge_strategy_detector.py: quando o parent count estiver ausente por
    degradação (não por repo sem PRs), rotular merge_strategy como
    unknown em vez de aplicar a heurística — ou manter a heurística e
    marcar dominant_share como estimado. Decidir na PR.
  • platform/src/types/metrics.ts: tipo do campo; UI: ícone/tooltip nos
    painéis de flow e merge strategy quando degradado.
  • docs/METRICS.md §15/§28: documentar o campo e o comportamento.
  • Retry único com backoff no passo 2 antes de degradar (barato; reduz a
    frequência do problema sem remover o sinal).
  • Teste: simular falha do passo 2 (monkeypatch em
    _fetch_pr_enrichment_graphql) e asseverar o flag e o caveat.

Deadline / gatilho pra pagar

Antes de qualquer análise de flow ou merge strategy ser usada em
decisão (ambas dependem do passo que falha silenciosamente). Custo baixo;
valor alto em confiança.


Evidência

Observado uma vez em dois runs consecutivos idênticos (mesmo engine
main @ da16235, mesmo repo, mesma janela de 90 dias, gh real), com os
valores da tabela acima; não reproduzido em dois runs subsequentes — é
intermitente, consistente com o 504 documentado em github_reader.py:89-91.
Reprodução determinística: monkeypatch de _fetch_pr_enrichment_graphql
para lançar CalledProcessError e comparar merge_strategy_dominant_share
e flow_pr_count com e sem a falha.

Análise adversária

# Ataque Resposta Status
1 Intermitente e observado uma vez — vale uma issue? A causa é estrutural (except → [] sem sinal) e o código já documenta o timeout. A frequência não importa quando o efeito é um relatório que afirma o oposto da verdade sem avisar. Vale.
2 Falhar o run inteiro seria mais honesto que degradar. Contraria a decisão de funcionar sem gh. O meio-termo — degradar com sinal — preserva as duas coisas. Decidido: degradar com sinal.
3 Retry resolve sem mexer no schema. Reduz frequência, não elimina; e o problema é a invisibilidade, não a frequência. Retry entra como complemento. Ambos.
4 Campo novo quebra a ingestão? Não: /api/ingest usa .passthrough() (route.ts:45) e additionalProperties: true (openapi.yaml:489); armazenado em JSONB. Tipo TS é follow-up da mesma issue. Mitigado.

Fora do escopo

  • Substituir gh por chamadas REST/GraphQL diretas.
  • Cache persistente de PRs entre runs (window_cache.py é in-process por desenho).

Issues relacionadas (verificado contra as 63 issues do repo, abertas e fechadas — sem duplicata)

Referências

  • iris/ingestion/github_reader.py:62-64,89-91,95-145,184,222-240,288-300; iris/cli.py:497-499; iris/analysis/merge_strategy_detector.py:25-32,73-89; iris/analysis/flow_efficiency.py; iris/models/metrics.py.
  • docs/METRICS.md §15 (PR lifecycle), §25 (Flow Efficiency), §28 (Merge Strategy).
  • CLAUDE.md → Coding Principles ("Favor deterministic outputs"), "Focus Areas (Stage 2) → Ingestion: reliability".

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: tech-debtCódigo sub-ótimo conhecido, workaround, ou cleanup pendente

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions