teqo-backup/docs/testing-audits/2026-08-25-run2.md

129 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Testing audit — 2026-08-25 (run 2)
Segunda execução da skill `/testing-audit` na mesma data da primeira noite (`2026-08-25.md`, PR #907). Como o arquivo `<YYYY-MM-DD>.md` já existia commitado com o relatório da run 1, esta noite usa o sufixo `-run2` para preservar os dois relatórios — única divergência do template (anotada aqui por transparência).
## Sumário executivo
- **4 movimentos int→unit aplicados (29 testes)** com conservação exata: unit+int = 3361 testes em todos os passos. Nenhum teste removido, nenhum enfraquecido.
- `payloadTransaction` (10) — stub puro do wrapper de transação, zero Payload/DB.
- `contactPhoneNormalization` (4) — hook do Contact exercitado via stub.
- `municipalityUpdateCreateSchema` (2) — pins puros de zod extraídos do spec int que boota Payload.
- `campaignAuthIdentity` (13) — helpers de identidade (telefone/schema/whatsapp/guarda de migração) extraídos do `campaignAuth.int.spec`; RBAC do arquivo intacto.
- **1 fix no tooling first-party**: `parseVitestStdout` no core do metrics — suite vermelha agora imprime o retrato (contrato do próprio script), tolerando o sufixo `ELIFECYCLE` do pnpm. +5 testes unit no spec do core.
- **Recidiva do flake #906 com nova assinatura**: após 7 runs full verdes no início da noite, a família Google Calendar passou a falhar de forma persistente sob a mesma carga; com `VITEST_MAX_WORKERS=1` o fracasso colapsa em UM teste determinístico (`googleCalendarSyncAction` "no-op…not-configured") causado por ~38 linhas de resíduo em `google_calendar_sync` deixadas pelo spec C114 num único run full. Reset completo do DB de teste (`db:reset`, OPS88) não resolveu — a família recria o resíduo dentro do próprio run. Evidência em #906 via relatório (sem escrita no tracker).
- Nenhuma edição em: intocáveis absolutos (LGPD/RBAC/lockdown de liderança), intocáveis estruturais (ci-scope, e2e-manifest, workflows, husky), specs e2e, e nenhuma remoção autônoma de teste.
- 0 passos revertidos.
## Metodologia e tetos respeitados
Fases: baseline (13:43) → inventário paralelo (3 leitores Task read-only, ~25min) → rubrica com validação de cada achado contra o fonte (~25min) → loop de melhorias sequencial (5 passos) → fecho. Tetos: budget ~6h ✓ (uso real ≈ 2h); ≤6 melhorias ✓ (5); ≤45min inventário ✓; ≤30min/passo ✓ (passo mais longo: ~8min); debug de AFTER ≤10min ✓ (nenhum AFTER falhou — as falhas posteriores são do flake conhecido, não das edições).
## Retrato da suíte
### Baseline (13:43–13:44, SHA 8199e2ee)
| camada | arquivos | testes | parede |
| ------ | -------: | -----: | ---------: |
| unit | 277 | 2620 | 30,9s |
| int | 80 | 741 | 51,4s |
| e2e | 44 | 208 | não medido |
### Final (SHA deste PR, 008b1ee7)
| camada | arquivos | testes | parede |
| ------ | -------: | -----------: | ---------: |
| unit | 281 (+4) | 2654 (+34)\* | 40,8s |
| int | 78 (−2) | 712 (−29) | ~184s† |
| e2e | 44 (=) | 208 (=) | não medido |
\* +29 movidos +5 novos do `parseVitestStdout`. Conservação global nos passos de movimento: unit+int = 3361 constante.
† parede int na medição vermelha (flake); a parede verde do mesmo SHA foi 51,4s.
Top-8 unit mais lentas no SHA final (tabela completa: `node scripts/testing-audit-metrics.mjs unit --top 15`):
| arquivo | testes | duração | teste mais lento |
| --------------------------------------------------- | -----: | ------: | ---------------------------------------------------------------- |
| tests/unit/activityOverlay.unit.spec.tsx | 14 | 4,7s | troca dia no calendário e hora/minuto nos selects inline… (2,0s) |
| tests/unit/petitionLead.unit.spec.tsx | 4 | 2,6s | fires exactly one Lead… (1,2s) |
| tests/unit/municipalityRelationEditor.unit.spec.tsx | 9 | 2,4s | serializes same-cell mutations… (0,7s) |
| tests/unit/campaignCellEditOverlay.unit.spec.ts | 19 | 2,0s | opens 'tendência' in a Drawer… (0,3s) |
| tests/unit/eslintConventions.unit.spec.ts | 12 | 2,0s | enforces PascalCase… (1,7s) |
| tests/unit/campaignQuickActionsDrawer.unit.spec.tsx | 15 | 1,8s | posts home-search… (0,4s) |
| tests/unit/campaignColumnPicker.unit.spec.ts | 8 | 1,4s | batches a whole editing session… (0,4s) |
| tests/unit/codebaseConventions.unit.spec.ts | 25 | 1,3s | keeps conflict markers out… (0,9s) |
Cobertura diagnóstica disponível sob flag (nunca regra): `pnpm vitest run --config ./vitest.unit.config.mts --coverage <filtro>`.
## Disposições
| id | camada | candidato | disposição | prioridade | prova/decisão |
| ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------- | ---------- | ---------- | ----------------------------------------------------------------------------------------------------------- |
| M1 | int→unit | payloadTransaction.int.spec (10 testes, stub de `Payload['db']`, zero DB) | mover ✓ | alta | `tests/int/payloadTransaction.int.spec.ts:23-43` stubs; conservação +10/−10 |
| M2 | int→unit | contactPhoneNormalization.int.spec (hook do Contact via stub, `importOriginal`) | mover ✓ | alta | `tests/int/contactPhoneNormalization.int.spec.ts:24-35`; conservação +4/−4 |
| M3 | int→unit | 2 pins zod de municipalityUpdateCreateSchema dentro de spec que boota Payload | mover ✓ | alta | `tests/int/campaignMunicipalityUpdate.int.spec.ts:71-140` puros; Local API (:142+) permanece int |
| M4 | int→unit | helpers puros de identidade no campaignAuth.int.spec (telefone/schema/whatsapp/migração) | mover ✓ | alta | blocos :441-452 e :551-603 sem payload; RBAC :454-549 intocado; conservação +13/−13 |
| F1 | tooling | metrics CLI morria em suite vermelha (sufixo ELIFECYCLE do pnpm quebrava o JSON.parse) | reforçar ✓ | média | contrato do próprio script ("a red suite still prints its portrait"); `parseVitestStdout` no core + 5 tests |
| P1 | unit | catálogos quick-action repinados (registry × domínio × v2) | proposta | baixa | U1/U3: seams distintos como na run 1 (K1) — duplicata não comprovada |
| P2 | unit | scan de conflict-markers pinado em codebaseConventions + conflictMarkers.unit + CI | proposta | baixa | U2: o describe do codebaseConventions roda `git ls-files` inteiro a cada run |
| P3 | unit | 6 specs do agent-pool sobre scripts dormentes (OPS65) | proposta | baixa | U6: decisão de produto (remover ou suíte "dormant" separada) |
| P4 | int | origin policy pinada 2× (campaignInvite.int :393-550 × campaignInviteOrigin.unit) | proposta | média | I6: casos únicos em cada lado — merge exige toque em arquivo com conteúdo consent (369-391 intocável) |
| P5 | int | campaignInvite.int :298-607 blocos puros (token/whatsapp/schema) | proposta | média | I5: split parcial em arquivo com consent e RBAC — risco > valor à noite |
| P6 | int | cleanup de fixtures pinado 2× (campaignFixtures × campaignFixturesRowCounts) | proposta | baixa | I7: variante row-count estritamente mais forte; remoção exige prova forte |
| P7 | unit | campaignLoginAction asserta mock contra mock (`objectContaining({ login: mocks.login })`) | manter | baixa | U4: pin fraco, inofensivo; os pins reais (mapeamento/TTL) têm valor; remover asserção não é reforçar |
| P8 | unit | campaignAuthMemoization reimplementa `react.cache` num Map caseiro | manter | baixa | U5: asserção de identidade de promise (:69) é o sinal real; limitação documentada |
| P9 | unit | eslintConventions/codebaseConventions/campaignCompositionCleanup pesados (ESLint real, scans full-tree) | manter | baixa | U7/U8/U10: locks de convenção com valor alto; otimização é proposta, não melhoria noturna |
| E1–E12 | e2e | inventário: duplicatas com int (C113 agenda feed, C130 party facet, TTL×3, C143, C88), http-disfarçados, setup.e2e prewarm morto | proposta | variada | leitor e2e; e2e nunca editado/executado à noite — trilho OPS87/medição para as duplicatas HTTP |
Intocáveis varridos e intactos: consentimento/LGPD (hash do consent no campaignInvite mantido no lugar), RBAC (`campaignAuth` :454-549, lockdown de liderança, I9 triple-pin mantido), intocáveis estruturais sem diff.
## Evidências verde antes/depois
| passo | antes | depois | commit |
| ----- | --------------------------------------- | ---------------------------------------------------------- | -------- |
| M1 | 13:54:34 unit 277/2620 ✓ · int 80/741 ✓ | 13:56:04 unit 278/2630 ✓ · int 79/731 ✓ (conservação 3361) | 7de9822f |
| M2 | = anterior | 13:57:42 unit 279/2634 ✓ · int 78/727 ✓ (conservação 3361) | adcfb39a |
| M3 | = anterior | 13:59:40 unit 280/2636 ✓ · int 78/725 ✓ (conservação 3361) | d8866d90 |
| M4 | = anterior | 14:01:47 unit 281/2649 ✓ · int 78/712 ✓ (conservação 3361) | 62a7dab8 |
| F1 | spec core isolado 10/10 ✓ | 14:09:29 spec core 15/15 ✓ · unit full 281/2654 ✓ | 008b1ee7 |
Provas executadas bare (nunca piped). A partir de 14:05 os runs full de int passaram a falhar APENAS na família #906 (ver abaixo) — as provas AFTER de M1–M4 são anteriores ao surto e verdes.
## Flake #906 — recidiva com nova assinatura (evidência desta noite)
- 7 runs full verdes (13:43–14:03) → 8 runs seguintes com falhas na família Google Calendar: 25 falhas com workers default (14:05, 14:07, 14:12), 25 com `VITEST_MAX_WORKERS=4` (14:10), 1 falha com `VITEST_MAX_WORKERS=1` (14:14, 14:18, 14:22, 14:36).
- A família isolada segue verde (14:13:45 `googleCalendarSync` 25/25 em 7,8s) — mesma assinatura "verde isolada, vermelho full" da noite anterior.
- **Nova evidência causal**: `google_calendar_sync` acumulou **38 linhas de resíduo** após um único run full; o teste `googleCalendarSyncAction.int.spec.ts:121-146` ("no-op…not-configured") apaga só 1 doc stale (`limit: 1` no :124-131) e o action então lê outro doc residual → `expected 'synced' to be 'not-configured'` (:145). A hipótese: o afterChange do engine recria docs de config depois do cleanup do spec (corrida hook × passes), e o resíduo faz a falha persistir entre runs.
- `db:reset` completo (OPS88, 14:12) NÃO curou — a família recria o resíduo dentro do próprio run.
- **Conclusão**: recidiva do #906 com evidência de resíduo nova; os fixes pertencem ao trilho da Issue (Fase 1 planejada: assertar `outcome.status === 'synced'` na família; a esta evidência soma-se: cleanup do no-op deve apagar TODOS os docs stale, e a família precisa de tear-down resiliente ao hook). Nada editado nesta família à noite.
## Propostas não executadas (com razão)
| proposta | por quê não executou |
| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| P1/P2/P3 (unit: catálogos re-pinados, scan de markers, agent-pool dormente) | decisão de produto/consolidação exige humano; scans de convenção têm valor de lock |
| P4/P5 (int: origin policy 2×, blocos puros do campaignInvite) | split parcial em arquivo com consent/RBAC — risco > valor no turno noturno |
| P6 (int: cleanup de fixtures 2×) | remoção exige prova forte; variante row-count é mais forte, mas duplicata parcial |
| E1–E12 (e2e) | e2e é inventariado, nunca executado/editado à noite; trilho OPS87 + medição para as HTTP |
| Otimizações de latência (U7/U8/U10: cache de scans, tier de conventions) | refactor de specs de convenção sem ganho de cobertura — proposta, não melhoria segura |
Descartados pela triage (sem valor real): P7/P8/P9 (manter — ver disposições); dedup de sugestões wizard×home-search (I8, seams distintos); triple-pin de lockdown (I9, intocável).
## Estado final do PR
**PR [#919](https://github.com/fsolla/teqo/pull/919)** — branch `agent/testing-audit-2026-08-25-run2`, base `main`. CI `checks` **green** no SHA final (3337ef03, `success` ~15:39Z); `mergeStateStatus: CLEAN`, mergeable.
**Automerge: desarmado e verificado (exit 0) às 15:25:10.** Nas re-verificações seguintes, a rede à API do GitHub instabilizou (timeouts intermitentes no GraphQL) e o fallback fail-closed da skill converteu o PR para **draft** (~15:42Z, `autoMergeRequest: null` mantido) — veto estrutural do safety-net. **Estado final protegido: DRAFT + CI green + mergeable.** Passo humano pela manhã: `gh pr ready 919` (o safety-net re-arma pelo fluxo normal) e decisão humana de merge, conforme o contrato "sem auto-merge armado" desta skill.
Logística do fecho (transparência):
- HTTPS a `github.com` ficou inacessível da máquina (~14:50–15:17, conexão ao 443 timeout; `api.github.com` ok) — o push foi feito por SSH (`ssh://git@github.com/fsolla/teqo.git`), 15:17:23.
- Após 3 gates `gate:push` consecutivos barrados APENAS pelo flake int (#906, assinaturas rotativas), o push usou o escape documentado `git push --no-verify` (o mesmo gate já havia passado verde 2× neste SHA; o CI do PR é o gate real — e ficou green).
## Fase 5 — desarme do auto-merge
Protocolo determinístico desta skill: criação ready → desarme → verificação `autoMergeRequest === null && isDraft === false` num ÚNICO comando (`scripts/testing-audit-disarm.mjs`), repetido imediatamente após cada push, nunca "aguardar o CI" para desarmar. Execução desta noite:
1. 15:17:33 — criação+desarme em um comando: PR #919 criado Ready, mas a verificação e a conversão para draft falharam por rede (`fetch failed` intermitente, mesma janela do bloqueio HTTPS); exit 1 com aviso de intervenção.
2. 15:18:39 — retry imediato `--pr 919 --draft-on-failure`: status verificado e impresso — `{"number":919,"isDraft":false,"mergeable":"MERGEABLE","mergeStateStatus":"BLOCKED","autoMergeRequest":null}` — desarmado e Ready, exit 0.
3. 15:22 — push do relatório final (SHA 3337ef03, `synchronize` rearma) → re-desarme imediato verificado exit 0 (15:22:22); re-verificação às 15:25:10 exit 0 (o workflow `PR Ready + auto-merge` do novo SHA já havia concluído `success`).
4. 15:41–15:44 — re-verificações do fecho sob rede instável: leituras GraphQL falharam 4×; o `--draft-on-failure` converteu a PR para **draft** (fail-closed) mantendo `autoMergeRequest: null` — fallback §4 da Fase 5 aplicado: **humano vira ready pela manhã** (`gh pr ready 919`).