Relatório de engenharia · Dudata Hub

PR #739: o que o review em waves achou e o que muda no fluxo

O PR "edit Social Listening details from Entregas" nasceu com CI verde e ficou 5 dias sem review. Três waves de review agressivo acharam 2 problemas altos reais que o CI escondia, derrubaram 2 falsos positivos por prova executada, e o código foi mergeado e deployado em produção. Este relatório fecha o balanço e propõe o que vira regra.

PR #739 merge 572f6fa deploy run 32884452936 2026-08-25 em produção

O contexto em 30 segundos

A Dudata entrega relatórios de Social Listening (análise de menções e conversas em redes sociais) para os clientes. O Hub é o sistema interno que organiza essas entregas. Toda entrega tem três dados de cabeçalho: título, briefing e prazo, a data combinada com o cliente.

Até este PR, esses campos ficavam travados depois da criação. Um prazo digitado errado, ou renegociado com o cliente, não tinha como ser corrigido pelo produto, e a data errada seguia na tela para todo mundo que organiza o trabalho por ela. O PR #739 cria essa edição no Backoffice, só para entregas de Social Listening ainda vivas (as encerradas ficam somente leitura), e grava cada edição na trilha de auditoria.

19
achados na wave 1
2
falsos positivos derrubados por prova
8
commits de correção até o merge
7/7
eixos E2E aprovados em navegador real
01

Qualidade do código inicial

O código veio do Cursor em 5 commits (2026-08-20): 15 arquivos, +1019/−2. Entregava a tela de Entregas no Backoffice, a mutation deliveries.updateDetails e o predicado canEditDeliveryDetails em shared, com testes. A estrutura estava certa: autorização server-side pelos helpers de authz.ts, transição validada por assertTransition, sem máquina de estados paralela, copy e idioma conforme a convenção. Os ADRs 0008, 0010, 0011 e 0020 foram respeitados. Isso não é pouco: o guardrail de arquitetura funcionou.

Os problemas eram todos de segunda ordem. Nenhum aparecia no CI, e é exatamente por isso que importam.

SeveridadeProblema
AltoTrilha de auditoria incompleta. A mutation não gravava activityLogs. Toda mutation vizinha grava.
AltoDuplo submit possível. void onSubmit(values) no handleSubmit quebrava o isSubmitting do dialog. Bug real de UX que nenhum teste pegou.
MédioDuplicação em três frentes: helpers de data copiados pela 3ª vez, hook de mutation paralelo ao canônico useDeliveryActions, fixtures de teste copiadas em 4 arquivos (~60 linhas cada).
MédioTestes com buracos: sem caso de entrega inexistente, sem dueIso com whitespace, títulos em português (contra a convenção), asserções frouxas (length > 0) e uma tautologia.
MédioBranch 20 commits atrás da main, com 1 conflito real acumulado (a main tinha refatorado os handlers para o padrão *Impl).
Balanço: base sólida onde os ADRs vigiam, furos onde só leitura de código alcança. CI verde comprou 5 dias de falsa confiança. O gap não era de arquitetura, era de review.
02

As três waves de review

Wave 1: 5 reviewers paralelos, um por dimensão (backend, web, testes, arquitetura, integração). Wave 2: 3 reviewers sobre o estado corrigido e mergeado com a main. Wave 3: veredito de aprovação. Os fixes rodaram em pipeline, sem barreira, com a regra de 1 dono por arquivo por vez. Escopo novo foi anexado a agente em voo por mensagem, sem matar e recriar.

Achados por wave e severidade

A contagem cai a zero em três rodadas. É o critério de parada.

Wave 1
1
1
7
6
4
19
Wave 2
1
2
1
4
Wave 3
zero gaps · aprovação
0
Bloqueante Alto Médio Baixo Falso positivo

Wave 2 teve ainda apontamentos cosméticos, fora da contagem. Passe o cursor nos segmentos para o detalhe.

Os dois falsos positivos, e o que eles provam

wave 1 · reviewer de backend

"O patch com dueIso ?? undefined impede limpar o prazo"

Era um dos 2 bloqueantes da wave 1. Um teste convex-test executado na hora provou o contrário: o Convex 1.42 remove o campo quando recebe undefined. O achado caiu. Zero mudança de código.

e2e · ferramenta de navegador

"O briefing editado não persiste"

O app estava certo. O fill da ferramenta não dispara o onChange sintético do React em <textarea> (em <input> funciona). Disparar new Event('input') à mão persistiu o valor. Zero mudança de código.

Os dois casos provam a mesma regra: alegação sobre comportamento de runtime não vale nada sem prova executada. Corrigir qualquer um dos dois sem provar teria colocado código errado em cima de código certo. A prova custou minutos e evitou os dois retrabalhos.
03

Do código ao deploy

Follow-ups em aberto (nenhum bloqueia)

ItemDetalhe
Tool MCPExpor updateDetails como tool MCP e atualizar docs/api/mcp-deliveries.md. Hoje a UI edita e o agente não.
MobileO card da fila no mobile tem o mesmo viés corrigido no web: mostra data de abertura em vez do prazo (fila-vm.ts:114).
DueDatePickerO padrão Calendar+Popover está copiado em 3 features. Extrair componente compartilhado.
Escape no dialogEscape fecha o dialog junto com o popover de data. Comportamento Radix padrão, UX menor.
04

O que vira regra no fluxo

Cada proposta abaixo sai de um fato deste PR, não de princípio abstrato. O custo do experimento foi real: 3 waves mais o diagnóstico de 2 falsos positivos. O retorno também: 2 problemas altos reais que iam para produção com CI verde, e um deploy que saiu no mesmo dia.

  1. Review em waves como gate pré-merge

    Wave 1 multi-dimensão com reviewers paralelos, fixes em pipeline com 1 dono por arquivo, wave final como critério de parada (zero gaps). Não é para todo PR: é para PR que toca mutation de domínio ou fluxo crítico.

    Evidência: 19 → 4 → 0 achados; os 2 altos reais eram invisíveis ao CI.

  2. Alegação de runtime exige prova executada

    Reviewer que afirma comportamento de runtime entrega o teste ou o comando que prova. Sem prova, o achado não bloqueia. A regra vale nos dois sentidos: também protege código certo de correção errada.

    Evidência: o falso bloqueante do dueIso e o falso positivo do E2E, ambos derrubados em minutos de prova.

  3. Branch não passa de 2 dias úteis sem sync com a main

    Merge da main cedo e sempre. O conflito do padrão *Impl existiu porque a branch ficou 20 commits para trás; com sync no dia seguinte ele não existiria.

    Evidência: 5 dias parada, 1 conflito real, resolução manual no dia do merge.

  4. Mudou seed, roda os testes consumidores antes do push

    Mudança de seed é mudança de contrato para todo teste que lê o seed. Rodar localmente os testes consumidores do seed vira passo obrigatório do checklist de push.

    Evidência: a única quebra de CI de todo o processo veio daí (corrigida em fc3cf25).

  5. Convenção codificada é convenção barata

    Título de teste em inglês, asserção frouxa, fixture duplicada: tudo isso virou achado de review humano quando devia ser check automático. Cada convenção que der para codificar sai do custo da wave e entra no CI.

    Evidência: parte dos 6 médios da wave 1 era convenção pura, detectável por script.

  6. E2E de fluxo crítico antes do merge, com eventos React reais

    Teste unitário não pegou o duplo submit; o navegador pegou. E2E do fluxo crítico entra antes do merge, com ferramenta que dispara onChange sintético de verdade em <textarea>, senão o próprio teste gera falso positivo.

    Evidência: o bug do isSubmitting e o falso positivo do fill, no mesmo dia.

Recomendação: adotar as regras 2, 3 e 4 já, porque custam quase zero. As regras 1 e 6 entram como gate apenas para PR de domínio crítico. A regra 5 é backlog de tooling contínuo.