diff --git a/docs/plans/ops86-blast-radius-testes-prs.md b/docs/plans/ops86-blast-radius-testes-prs.md new file mode 100644 index 00000000..37627e79 --- /dev/null +++ b/docs/plans/ops86-blast-radius-testes-prs.md @@ -0,0 +1,70 @@ +# OPS86 — Blast radius dos testes: PRs quebram testes que não rodaram no CI do PR + +Status: rascunho +Atualizado em: 2026-08-24 +Issue: #832 +Priority: P1 +Impeccable: A — N/A sem UI +Rascunho UI: N/A — sem UI +Appetite: ~1 dia eng; um outcome verificável +Responsável: — + +## Intenção + +Os PRs estão quebrando testes que o CI do PR não executou — e os PRs que mais quebram são exatamente os que hoje rodam menos. Um diff que mexe em coleção, global, migração ou regra de acesso muda o RBAC e a superfície inteira do produto, mas a política atual (OPS72) manda esse tipo de diff para o modo "verificação máxima" — que, por custo, significa **zero e2e no PR**. O teste de verdade só acontece depois do merge, no deploy. Além disso, o CI tem um mapa de qual código cobre qual teste e2e, mas pedaços críticos (regras de acesso, schemas de formulário, push, transcrição de áudio) estão fora desse mapa — o CI nem sabe que o diff tocou área sensível e não roda nada. E no nível unit/int, o modo "rodar só o que mudou" pode selecionar zero testes e mesmo assim ficar verde. O outcome desejado: **nenhum PR de código passa no CI sem executar exatamente os testes que o diff pode quebrar** — e2e e unit/int —, com falha visível (e lista de specs) quando a cobertura não existe. + +## Persona e fluxo + +- **Persona / contexto:** quem abre PR no repositório (humano ou agente) e usa o CI como resposta à pergunta "esse diff está seguro para mergear?"; e o verify do deploy, que hoje é a única rede de segurança para os PRs de maior risco. +- **Job principal:** saber, antes do merge, se o diff quebrou algo — sem rodar a suíte inteira a cada PR. +- **Fluxo desejado:** abre-se um PR com diff de alto risco (coleções, migrações, RBAC, schemas) → o CI roda um conjunto **curado e representativo** de testes e2e (acesso/RBAC + smoke da superfície afetada) mais os unit/int que o diff pode tocar → verde ou vermelho com a lista exata de specs. Se o diff tocar código sem mapeamento declarado, o CI não fica verde em silêncio: cai num fallback explícito (roda a superfície, sinaliza a lacuna) ou falha pedindo mapeamento. Se a seleção de unit/int por dependência vier vazia, o CI roda o conjunto de fallback — nunca "0 testes, tudo verde". +- **Anti-goals de produto:** não voltar a rodar e2e full por PR (o custo volta a travar o fluxo); não piorar o custo médio do job; não quebrar o invólucro "dono do PR, dono do CI" — a rede de segurança do verify no deploy permanece como está; não transformar o mapeamento em um catálogo de tudo (manutenção infinita sem ganho). + +## Objetivo e aceite + +- PR com diff de alto risco (coleções/globals/migrações/acesso) executa no CI um conjunto curado de e2e representativo — nunca zero — e o resultado aparece no check do PR. +- Nenhum módulo de risco (regras de acesso, schemas, autenticação/push, IA) fica fora do mapa: diff nesses arquivos nunca cai em "nenhum e2e" sem explicação. +- O modo "só o que mudou" dos testes unit/int nunca roda 0 testes em silêncio: seleção vazia dispara fallback explícito, e CI não fica verde com zero execuções. +- Código tocado sem cobertura declarada não passa com aviso de log: ou roda o fallback, ou falha apontando a lacuna (fail-closed para área de risco). +- Guardrails: PR nunca roda e2e full (a suíte completa segue só no verify do deploy); custo médio do job não sobe em relação ao hoje; classificador e mapa só mudam com o teste de invariante existente verde. + +## Dados (intenção) + +- **Vou apresentar dados?** Não — este item é sobre verificação, não sobre métricas; o sucesso é observável no comportamento do CI, não num painel. +- **Decisões desbloqueadas:** nenhuma de negócio. (Se a Direção quiser acompanhar lacunas de cobertura ao longo do tempo, isso é outro item — com decisão de consumo nomeada.) + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** o classificador de blast radius (`scripts/lib/test-affected-core.mjs` — high-risk→`full`), a porta de e2e no workflow do PR (`.github/workflows/ci-pr.yml`, gated em `selected`), o mapa de e2e (`scripts/e2e-affected.*` + manifest), a classificação de escopo (`scripts/ci-scope.mjs`, hoje só loga lacunas), e o gate de testes (`scripts/gate-ci.mjs`, `--changed` + `--passWithNoTests`). +- **Precedente a olhar:** OPS72 (política full→zero e2e no PR), OPS83 (o que já foi feito em seleção de testes), e o teste de invariante do classificador (`ciSkipInvariants.unit.spec.ts`) como o pin de que mudanças no próprio mecanismo são verificadas. +- **Risco de acoplamento:** o executor deve mexer no mecanismo **sem** mexer no contrato de deploy (verify full no homeserver) e sem relaxar os guards de banco local. Módulos fora do mapa hoje incluem, como hipótese: `src/utilities/campaignAccess.ts` + `src/utilities/access/*`, `src/lib/schemas/*`, `src/utilities/campaignPushClient.ts`, `src/utilities/ai/*`, `src/lib/formData.ts`, `src/components/ui/*`. + +## Dependências + +- Nenhuma dura. OPS72 e OPS83 já entregues (base onde o item se apoia, mas nada em voo). + +## Fora de escopo + +- Migração de testes unit para int/HTTP (OPS87 — separado). +- Setup compartilhado de banco no CI (OPS88 — separado). +- Reduzir tempo de build/e2e (apenas manter o custo médio atual). +- Qualquer mudança no verify do deploy ou no fluxo pós-merge. + +## Rabbit holes de produto + +- **"Mapear tudo".** Se alguém "só completar", o mapa vira um catálogo de 400 linhas com manutenção infinita. **Corte neste item:** mapear só o que o classificador já trata como risco e hoje cai em zero/não-mapeado; o resto usa o fallback explícito. +- **"Fallback = rodar tudo".** Vira e2e full de fato, matando o custo. **Corte neste item:** fallback é conjunto curado + unit/int full quando necessário — unit/int full é barato, e2e full nunca. +- **"Só melhorar o log".** Continua verde com 0 testes rodados. **Corte neste item:** toda lacuna de cobertura em área de risco termina em fallback executado ou falha; aviso solto não conta. + +## Questões em aberto (produto) + +- **Seleção vazia no modo "só o que mudou": o fallback deve rodar unit/int full ou apenas a superfície?** **Opções:** A) unit/int full sempre que a seleção vier vazia; B) conjunto da superfície do diff; C) falhar pedindo mapeamento. **Recomendação:** A — unit/int full é barato e cobre com honestidade máxima; o conjunto curado fica para o e2e. _(assumido — validar com produto)_ +- **O conjunto curado de e2e para diff de alto risco: fixo e estável ou derivado por heurística de nomes?** **Opções:** A) curadoria explícita e estável (acesso/RBAC + smoke por superfície), revisável como o mapa atual; B) heurística automática por convenção de nomes. **Recomendação:** A — previsível e idempotente; heurística de nomes vira outro rabbit hole de manutenção. +- **Código de risco sem mapeamento: falhar o PR ou rodar fallback e sinalizar?** **Opções:** A) fail-closed (falha apontando a lacuna) para path de risco; B) fallback + sinalização, mapeamento vira dívida; C) manter só o log. **Recomendação:** A para paths que o classificador já trata como risco — o custo de um falso positivo é rastrear uma linha no mapa; o custo de falso negativo é o bug que este item existe para evitar. _(assumido — validar com produto)_ + +## Referências + +- OPS72 — política atual: modo `full` → PR sem e2e (`.github/workflows/ci-pr.yml`, comentário no passo "E2E tests (blast radius…)"). +- OPS83 — seleção de testes unit/int (precedente recente de `changed`). +- Arquivos para o executor abrir primeiro (pistas, não contrato): `scripts/lib/test-affected-core.mjs`, `scripts/ci-scope.mjs`, `scripts/gate-ci.mjs`, `scripts/e2e-affected.*`, `.github/workflows/ci-pr.yml`, `tests/unit/ciSkipInvariants.unit.spec.ts`. +- `AGENTS.md` — convenção "dono do PR, dono do CI" e guardrails de banco/testes (tocadas por este item). diff --git a/docs/plans/ops87-piramide-e2e-http-sem-browser.md b/docs/plans/ops87-piramide-e2e-http-sem-browser.md new file mode 100644 index 00000000..742444f2 --- /dev/null +++ b/docs/plans/ops87-piramide-e2e-http-sem-browser.md @@ -0,0 +1,73 @@ +# OPS87 — Pirâmide de verificação: migrar asserções de servidor dos e2e pesados para o paradigma HTTP sem browser (escalar o OPS35) + medir + +Status: rascunho +Atualizado em: 2026-08-24 +Issue: #833 +Priority: P1 +Impeccable: A — N/A (sem UI) +Rascunho UI: N/A — sem UI +Appetite: ~1–2 dias eng; um outcome verificável +Responsável: — + +## Intenção + +O gate de deploy roda a suíte e2e full (~10 min só de e2e) e ela é frágil sob 4 workers. Muita coisa ali existe para garantir comportamento de **servidor** — quem pode ver o quê, o que a página renderiza antes de qualquer JS, o que persiste, para onde redireciona, o que devolve 404 — mas cada teste paga browser, hidratação e interação client mesmo quando a asserção é 100% server-side. Isso é caro duas vezes: tempo de espera de quem deploya e classes de flake que nascem da interação. + +O precedente já existe e está em produção: o OPS35 (#600) entregou o paradigma HTTP full-stack sem browser (fixture `request` do Playwright, login real via REST, HTML renderizado asserido direto), mas só a família Territórios migrou — o resto da suíte continuou crescendo em browser. Queremos escalar o paradigma para as famílias pesadas com fatia de servidor destacável, **medindo o ganho por família**: mesma cobertura, fração do tempo, suíte mais estável. Não é reescrever a suíte; é achar a pirâmide certa — o que é servidor deixa de pagar browser. + +## Persona e fluxo + +- **Persona / contexto:** a equipe que aciona o deploy verify e espera o gate verde; o agente/humano que trava em suíte frágil quando o código mudado era só de servidor. +- **Job principal:** obter as mesmas garantias de servidor com uma fração do tempo e menos flake. +- **Fluxo desejado:** acionar o verify → a parte de servidor das famílias pesadas roda sem browser → o relatório registra o tempo de cada família (browser vs HTTP) → gate verde mais rápido, mesma cobertura. +- **Anti-goals de produto:** não virar "reescrita de toda a suíte"; não remover teste sem equivalente em outro nível; não criar segundo framework de teste; não enfraquecer a garantia de falha de client (console.error/pageerror continua exclusivo de browser). + +## Objetivo e aceite + +- O tempo do e2e full cai e cada família migrada tem o ganho registrado (tempo browser vs tempo HTTP) — o ganho é o outcome, não a migração em si. +- A cobertura não diminui: nada é removido sem equivalente em outro nível (int ou HTTP) e as asserções migram 1:1. +- A suíte fica mais estável nas famílias migradas (menos interação client = menos classes de flake), sem reduzir o que se assegura. +- A migração anda em lotes e o deploy não fica bloqueado no meio de um lote. +- Guardrails que valem para o item: RBAC continua assegurado no conteúdo renderizado; auth é real (mesma sessão/cookie) sem duplicar lógica de login; fixtures e ownership existentes são reutilizados. + +## Dados (intenção) + +- **Vou apresentar dados?** Sim, superfície neste item — medição por família (tempo browser vs tempo HTTP, por spec migrado). +- **Decisões desbloqueadas:** o humano/equipe decide se o paradigma vale escalar para as próximas famílias e onde parar (critério de parada), com número real de cada família na mesa. +- **Forma:** _adiada ao plano de implementação_ — restrição de produto aqui: comparativo por família, tempo medido de verdade, nunca estimativa. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** a suíte e2e de campanha (specs `campaign*`) e a fixture HTTP já existente do OPS35 — o executor revisa a classificação das famílias, não a re-inventa. +- **Precedente a olhar:** OPS35 (#600, closed) — `campaignTerritoriesHttp.e2e.spec.ts` + fixture de login REST/cookie `campaign-token`; `setup.e2e.spec.ts` roda sem browser no mesmo job/servidor — sem infra nova. +- **Risco de acoplamento:** não duplicar o que o int já cobre (dados/loaders — o int **não** substitui asserção de HTML renderizado/SSR; o HTTP sim); o guard de falhas client (console.error/pageerror) fica só em browser. + +## Dependências + +- OPS86 (classificador de e2e corrigido passa a selecionar os novos specs HTTP no CI) — suave; este item se beneficia, não depende para começar. + +## Fora de escopo + +- Mudar o job de CI/deploy (OPS88 — item separado). +- Consertar flakes individuais de famílias que não foram migradas. +- Migrar interação/hidratação: WebAuthn, PWA, viewports, chat, colunas/filtros salvos, autosave, guard de console. +- Chamar server actions por HTTP cru. + +## Rabbit holes de produto + +- **Reescrever a suíte inteira.** Se alguém "só completar", vira migração total em browser → meses, sem parada. **Corte neste item:** só famílias com fatia de servidor destacável, um lote por vez, appetite fixo. +- **Migrar e não medir.** Sem o comparativo por família não há aceite — volta a ser opinião. **Corte neste item:** medição por família é condição de aceite, não cortesia. +- **Duplicar o int.** Asserções que o int já cobre migradas de novo geram cobertura dobrada sem ganho de tempo. **Corte neste item:** só o que o int não cobre (HTML renderizado/SSR/rotas) migra para HTTP. + +## Questões em aberto (produto) + +- **Por onde começar?** **Opções:** (a) maior retorno + baixo risco primeiro (concepts, permission-profile, people, home-actions) e o journey RBAC de municípios em lote dedicado; (b) por tamanho decrescente; (c) pelas famílias mais flaky. **Recomendação:** (a) — colhe o ganho cedo e valida a medição antes do lote grande. _(assumido — validar com produto)_ +- **Onde para?** **Opções:** (a) parada pelo appetite (~1–2 dias; lote que não couber vira item novo); (b) continuar até esgotar as famílias A/C. **Recomendação:** (a) — o appetite manda; o resto fica documentado como próximo lote. +- **Meta numérica para o e2e full?** **Opções:** (a) meta fixa (ex.: <5 min); (b) registrar o ganho por família sem meta no primeiro lote e decidir meta depois. **Recomendação:** (b) — o número real do primeiro lote calibra uma meta honesta; meta arbitrária antes dos dados é risco de corte de garantia. + +## Referências + +- GitHub Issue #600 (OPS35, closed) — precedente HTTP full-stack em produção. +- Specs-família citadas na Direção (pistas para o executor, não contrato). +- OPS86 / OPS88 — dependência e vizinho de escopo. +- `AGENTS.md` — pipeline de verificação de deploy é convenção travada; este item não a altera. diff --git a/docs/plans/ops88-ci-verify-setup-unico.md b/docs/plans/ops88-ci-verify-setup-unico.md new file mode 100644 index 00000000..38af18fb --- /dev/null +++ b/docs/plans/ops88-ci-verify-setup-unico.md @@ -0,0 +1,72 @@ +# OPS88 — Verify do CI (PR e deploy): um Postgres, um build, migrate/seed sem repetição + +Status: rascunho +Atualizado em: 2026-08-24 +Issue: #834 +Priority: P1 +Impeccable: A — N/A sem UI +Rascunho UI: N/A — sem UI +Appetite: ~0,5–1 dia eng; um outcome verificável +Responsável: — + +## Intenção + +O verify — o check do PR e o verify do deploy — reconstrói o mesmo estado várias vezes na mesma run: dois Postgres (um deles existe só para o e2e não herdar resíduo dos testes de integração), dois migrates explícitos, dois seeds, dois builds — e cada build ainda re-roda o migrate por baixo. No fim, o build "default" nem é servido por nada no CI: o e2e roda sobre o build `.next-e2e`, e o deploy de verdade compila no homeserver com a URL real. + +Queremos um setup único e compartilhado: um Postgres por run com um reset determinístico entre fases (limpa o schema, migrate, seed minimal), e um build só quando build+e2e rodam juntos. Menos tempo de máquina por run, menos pontos de falha, mesma garantia — o que protege o e2e do resíduo do int não é o segundo container, é o reset entre fases. + +## Persona e fluxo + +- **Persona / contexto:** mantenedor do pipeline (humano ou agente) que espera a cor da run do PR antes de mergear, ou que dispara o deploy manual e espera o verify fechar; contexto: fila de checks, minutos contados, re-run por falha. +- **Job principal:** saber que o PR está pronto para merge e que a publicação é segura, sem pagar o custo de reconstruir o mesmo estado quatro vezes numa run só. +- **Fluxo desejado:** numa run de PR que toca código, os testes de integração rodam num Postgres; um reset determinístico limpa o banco entre fases; o e2e serve o mesmo build compilado para produção (`.next-e2e`) — nada compilado duas vezes "só para conferir". No deploy verify, o mesmo fluxo, e a publicação continua gated pelo verify full. Se uma fase falha, a falha aponta a fase, e a re-run parte de um estado limpo. +- **Anti-goals de produto:** não virar "rodar menos testes" nem "pular o verify"; não virar paralelização do job único (fail-fast estrutural continua valendo); não mexer no deploy do homeserver. + +## Objetivo e aceite + +- Uma run de PR com build+e2e compila o app uma única vez e roda integração, build e e2e no mesmo Postgres, com reset determinístico entre fases — sem segundo container. +- O verify do deploy usa o mesmo setup único e continua gateando a publicação (rede de segurança inegociável). +- Nenhuma perda de garantia: os pins de contagem dos testes de integração rodam antes do reset; o e2e continua contra o build de produção, nunca dev; migrate-before-build (OPS66) preservado. +- Mensurável: migrates/seeds/builds repetidos caem (4× migrate → ~2×), run mais curta, e o ganho não volta em re-run. + +## Dados (intenção) + +- **Vou apresentar dados?** Não — sem superfície de dados para usuário; tempos de run são telemetria de CI, não dado de produto. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `.github/workflows/ci-pr.yml` e `.github/workflows/deploy.yml` (passos de setup/seed/build), `tests/helpers/` (lease/reset/seed), `playwright.config.ts`, scripts de banco/build em `package.json`. +- **Precedente a olhar:** `docs/plans/ci-e2e-paralelizar-job.md` (2026-08-10, timings por fase) e `docs/ops/teqo-1313-deploy.md` (contrato build-after-migrate). +- **Risco de acoplamento:** o reset determinístico precisa cobrir exatamente o que o e2e não pode herdar do int (linhas fora de ownership, pins de contagem exata, resíduo de run abortada) — quem executa deve provar o reset antes de confiar no banco único; a ordem int → reset → build+e2e é o contrato novo. + +## Dependências + +- Nenhuma dura. Beneficia-se de OPS86 (classifier) e OPS87 (famílias HTTP no mesmo job) — suaves. + +## Fora de escopo + +- Sharding/paralelização do verify — fail-fast estrutural se mantém. +- Mudar workers/retries dos jobs de teste. +- Migrar testes de nível (OPS87). +- Ajustar o classifier de e2e (OPS86). +- Qualquer mudança no deploy homeserver, que segue compilando com a URL real. + +## Rabbit holes de produto + +- **"Já que estou aqui, reduzo a suíte."** Se o setup compartilhado virar desculpa para rodar menos testes ou tirar o e2e do build de produção, a garantia cai. **Corte:** nada de rodar menos testes; e2e sempre no build de produção. +- **"Economizar o reset."** Se o reset determinístico for visto como custo e removido, o resíduo do int volta a contaminar o e2e — o risco exato que o segundo container existia para resolver. **Corte:** o reset entre fases é a proteção; se não couber no appetite, o item encolhe para o build único e a separação de Postgres fica como está. +- **"Unificar os dois builds sempre."** O build default só tem valor quando é o único build da run (PRs sem e2e); colapsá-lo à força exigiria decidir env em tempo de build. **Corte:** colapsar só quando build+e2e rodam juntos. + +## Questões em aberto (produto) + +- **O que garante o e2e limpo: isolamento físico ou reset determinístico?** **Opções:** A) um Postgres com reset entre fases | B) manter dois containers. **Recomendação:** A — o reset é o que protege; o segundo container é o workaround de hoje, e o resíduo do int é finito e conhecido (fixtures, pins de contagem, claims). +- **O build default continua existindo?** **Opções:** A) sim, quando é o único build necessário (runs sem e2e) | B) sempre um build só. **Recomendação:** A — o custo de mantê-lo só existe nas runs que já rodam sem e2e. _(assumido — validar com produto)_ +- **O verify do deploy muda de rigor?** **Opções:** A) mantém o fluxo full, só com setup compartilhado | B) reduz para o setup novo. **Recomendação:** A — a rede de segurança da publicação é inegociável. + +## Referências + +- `docs/plans/ci-e2e-paralelizar-job.md` — timings por fase e histórico do job e2e +- `.github/workflows/ci-pr.yml` e `.github/workflows/deploy.yml` — passos atuais de Postgres/seed/build +- `tests/helpers/campaignFixtures.ts` e `tests/helpers/testDatabaseLease.ts` — resíduo conhecido do int e leases de banco +- `playwright.config.ts` — qual build o e2e serve +- `docs/ops/teqo-1313-deploy.md` — contrato build-after-migrate (OPS66)