teqo-backup/docs/plans/ops46-infra-teste-e2e-local-allocator.md
Francisco Solla bf25100ed5 chore(OPS45): e2e fixtures — purge de resíduo no claim de município + robustez dos int specs (Closes #723)
O cleanup do campaignE2EFixtures estava completo em runs normais (proxy auto-own
cobre contact/stateDeputy); o lixo vinha de runs crashados, cujo cleanup nunca
roda. O claim e2e agora purga resíduo como o int já fazia, e a purga ganhou a
classe dobradinha (stateDeputy vinculados via municipality.stateDeputies + fichas
órfãs com ref-check incluindo stateDeputy — FK ON DELETE RESTRICT).

peopleList.int.spec.ts passa a varrer todas as páginas (lista global de 25/pág)
com dedupe; personDelete/peopleList usam personName() nos nomes fixos, imunes ao
invariante global de nome de dobradinha.
2026-08-13 14:18:54 -03:00

75 lines
4.2 KiB
Markdown

# OPS46 — Infra de teste e2e: e2e local quebrado (next/cache) + allocator de município com wrap
Status: rascunho
Atualizado em: 2026-08-13
Issue: #? (a criar via agent:register)
Intenção: débitos do OPS45 (capture-review-debts autônomo)
## Fase 1 — e2e local não coleta: `Cannot find module 'next/cache'` (máquina do humano)
**Sintoma:** `pnpm exec playwright test --list` falha em todo spec — `Error: Cannot
find module '<repo>/node_modules/next/cache' imported from src/utilities/documents.ts`.
Reproduz no worktree OPS45, no main repo (`/home/fsolla/Code/teqo`) e sob Node
v20.20.2 / v24.14.0 / v24.18.0 — pré-existente ao OPS45 (não é do diff).
**Mecanismo medido:** `import { revalidateTag } from 'next/cache'` (ESM, subpath
sem extensão) falha no resolver ESM nativo do Node — `next/package.json` NÃO tem
`exports`, então `next/cache` (arquivo `cache.js`) não resolve em ESM puro
(`node --input-type=module -e "import('next/cache')"` → ERR_MODULE_NOT_FOUND;
`import('next/cache.js')` → OK). O carregamento do graph do fixture e2e
(`src/payload.config.ts` → `documents.ts`) passa por esse import; o transpiler
do Playwright não reescreve bare specifier sem exports. CI passa (mesma versão
de next/playwright) — investigar o que difere no ambiente CI.
**Ações prováveis:** descobrir o deltas CI vs máquina (versão de node do runner,
flags de resolução, `NODE_OPTIONS`, corepack) e/ou tornar o import resolvível
(registrar alias `next/cache` → `next/cache.js` no carregador do Playwright,
ou mover `revalidateTag` para um shim com subpath com exports). Evidência de
referência: reproduzir em docker com node 24 latest (mesmo do CI) para isolar.
**Rabbit holes:** não é o OPS45; não mexer nos fixtures; não é npm registry.
## Fase 2 — Allocator de município com wrap de módulo: colisão de claim deleta linhas vivas
**Sintoma latente:** `nextval % municipalityCatalog.length` (435) na sequência
persistente compartilhada `campaign_fixture_municipality_alloc` (int + e2e).
A sequência nunca é resetada; após 435 claims no MESMO banco (DBs persistentes
de worktree acumulam — o problema do OPS45), dois runs concorrentes podem
receber o MESMO índice e cada um purga as linhas vivas do outro (o purge-on-claim
— int desde OPS19/D10 F1, e2e desde OPS45 — transforma a colisão em corrupção).
**Ações prováveis:** registry de claims com geração (`index, generation`,
UNIQUE em index, `ON CONFLICT` retry) ou fail-loud no wrap; medir primeiro a
contagem real de claims por run completo (int + e2e) para dimensionar.
**Rabbit holes:** não alterar o contrato de nomes/URLs; não tocar no
`testMunicipalityAllocatorConventions.unit.spec.ts` sem migrar os guards.
## Já resolvido no OPS45 (não reabrir)
- stateDeputy no ref-check de fichas órfãs + teste da forma compartilhada
(supporter+dobradinha) — `purgeMunicipalityResidue` não crasha mais.
- `stateDeputies: []` gated no resíduo (sem write por claim limpo).
- `loadAllPeopleRows` com loop auto-curável + dedupe por contactID.
## Explicitamente fora (deste lote)
- **Contas staff residuais** ('Coordenadora C128-<uuid>') — sem âncora de
município e sem padrão seguro (emails `@example.com` coincidem com runs
vivos); absorvidas pela paginação do peopleList. Gatilho: se voltar a
quebrar um spec global, reavaliar sweep por created_at + ausência de joins.
- **`activity.tasks[].responsible` fora do ref-check** — FK SET NULL,
silencioso e auto-curável; shape improvável (atividade residual em município
não claimado referenciando ficha purgada). Gatilho: 2ª referência a Contact
fora do check.
- **personName letteriza o runID** (invisível ao discoverMarkedRoots) —
latente até um spec alimentar personName via action real (sem proxy).
Gatilho: o 1º uso de personName fora do `createContact`.
- **Import do módulo vitest no fixture e2e** — funciona hoje (hooks inertes
fora do runner); gatilho: vitest mudar o entry / quebrar o import.
## Aceite de engenharia
- [ ] Fase 1: `pnpm exec playwright test --list` coleta na máquina do humano
- [ ] Fase 2: sem colisão de claim após N wraps (teste de stress ou fail-loud)
- [ ] Guards: `testMunicipalityAllocatorConventions` verde após a mudança