mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-15 18:53:06 +00:00
129 lines
16 KiB
Markdown
129 lines
16 KiB
Markdown
# 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`).
|