Files
adaptador-planilhas/docs/spreadsheet-adapter/21-checklist-validacao.md
T
camilafagundesandClaude Opus 5 5b11496cd9
ci / lint, typecheck, testes e build (push) Has been cancelled
ci / Postgres e Redis reais (push) Has been cancelled
ci / imagem Docker Linux (push) Has been cancelled
Runbook único de homologação, checklist de evidências e ADR do ambiente
Havia dois runbooks sobrepostos, escritos para o piloto, e nenhum documento do
ambiente de homologação.

- `20-homologacao-runbook.md` consolida os dois e cobre DNS, TLS, Nginx,
  Supabase de homologação, banco com a decisão do shim, bucket privado,
  containers, worker, observabilidade, flag, rollback, diagnóstico e smoke.
  Os dois runbooks antigos ficam no repositório marcados como histórico,
  preservando o registro das decisões.
- Rollback de imagem com comandos: identificar o que está no ar, listar tags,
  desligar a flag antes de trocar, subir só web e worker com `--no-deps`,
  conferir saúde, religar a flag. As migrations de segurança — RLS e auditoria
  append-only — estão declaradas sem caminho de volta: desfazê-las afrouxa o
  isolamento entre workspaces, então um rollback que precise disso está errado
  por definição.
- `21-checklist-validacao.md` separa o que foi executado do que depende de
  infraestrutura, com a evidência de cada item e a tabela dos quatro defeitos
  encontrados. Nada está marcado PASS por inspeção de código.
- ADR 0007 registra o ambiente lógico e a autenticação de homologação, com as
  alternativas descartadas e o motivo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-15 20:40:08 -03:00

164 lines
16 KiB
Markdown

# Checklist de validação e evidências — homologação
Estado em 15 de outubro de 2026, nesta máquina de desenvolvimento, sem acesso ao servidor do ecossistema, ao Supabase de homologação nem ao banco do Elo Pro.
**Sem runtime de contêiner nesta máquina.** O Docker Desktop está instalado e foi iniciado, mas o WSL2 não tem distribuição instalada (`wsl --list` reporta nenhuma), então o engine Linux não sobe. Não há Postgres, Redis, Podman nem nerdctl locais. O que exige contêiner ou serviço real foi validado por **simulação fiel do layout de runtime da imagem** (apenas `node_modules` de produção + `src` + `scripts` + `migrations` + `tsconfig.json`, executado com `node --import tsx`) e, onde nem isso alcança, está PENDENTE para o CI.
Regra de preenchimento: **PASS** só com evidência executada. Sem infraestrutura ou credencial, o item é **PENDENTE** — nunca aprovado por inspeção de código.
## A. Verificação local do repositório
| # | Item | Resultado | Evidência |
| --- | --- | --- | --- |
| A1 | `npm run lint` | PASS | sem apontamentos |
| A2 | `npm run typecheck` | PASS | `tsc --noEmit` sem erro |
| A3 | `npm test` | PASS | 17 arquivos, **102 testes**, 1 skip proposital (smoke real) |
| A4 | `npm run build` | PASS | 25 rotas + Proxy, zero avisos, saída `standalone` com 70 MB contendo só `.next`, `node_modules`, `migrations`, `src`, `package.json` e `server.js` |
| A5 | Testes HTTP das rotas (novos) | PASS | `tests/http/routes.test.ts`, 12 casos |
| A6 | Testes do proxy (novos) | PASS | `tests/http/proxy.test.ts`, 7 casos |
| A7 | Política de ambiente e workspace (novos) | PASS | `tests/unit/staging-policy.test.ts`, 22 casos |
| A8 | Suíte com Postgres e Redis reais | PENDENTE | `npm run test:services` reporta 15 ignorados sem `ADAPTER_TEST_DATABASE_URL`/`ADAPTER_TEST_REDIS_URL`. O CI executa no job `services` |
| A9 | Sintaxe do compose local | PASS | `docker compose -f docker-compose.yml config` sem erro |
| A10 | Sintaxe do compose de homologação | PASS | `docker compose -f docker-compose.staging.yml config` resolve todas as variáveis |
| A11 | Build da imagem Docker | PENDENTE | Docker Desktop está instalado nesta máquina, foi iniciado durante o trabalho e o engine Linux não chegou a responder (`500` no pipe `dockerDesktopLinuxEngine`). O CI executa no job `image` |
| A12 | `docker compose up` local | PENDENTE | mesmo motivo de A11 |
| A13 | Artefato `standalone` roda de verdade | PASS | `node .next/standalone/server.js` sobe, responde e atende as rotas (ver B6, C14, D3) |
| A14 | Deps de produção cobrem a árvore traçada | PASS | `npm ci --omit=dev` em diretório limpo: 318 pacotes contra 153 traçados, **zero divergência de versão** entre os compartilhados; 3 pacotes existem só na traçada (`@emnapi/runtime`, `isarray`, `supports-color`) e o `COPY` do Docker mescla sem removê-los |
| A15 | Worker e scripts rodam sem devDependencies | PASS | os 13 pacotes externos usados por `src/` e `scripts/` existem no install de produção; `tsx` 4.23.15 e `esbuild` 0.28.2 presentes |
| A16 | Scripts operacionais executam no layout de runtime | PASS | no layout simulado: `verify-config`, `migrate`, `migrate --dry-run`, `seed-dev`, `cleanup-temp`, `set-flag` (recusa workspace inexistente), `seed-staging` (recusa em development) — todos com o código de saída correto |
## B. Fail-fast de configuração
| # | Item | Resultado | Evidência |
| --- | --- | --- | --- |
| B1 | Configuração incompleta é recusada | PASS | `tests/unit/staging-policy.test.ts`: `assertDeployedEnv` lista `DATABASE_URL` e `REDIS_URL` ausentes |
| B2 | `ADAPTER_DEV_AUTH=1` recusado fora de development | PASS | teste de política e `devAuthEnabled()` |
| B3 | `ADAPTER_JOB_MODE=inline` recusado em staging e produção | PASS | teste de política; `jobRunsInline` retorna `false` |
| B4 | Shim de identidade exige decisão explícita | PASS | ausência de `ADAPTER_APPLY_IDENTITY_SHIM` entra em `configIssues` |
| B5 | Sem `DATABASE_URL` nunca cai em PGlite | PASS | `requireDatabaseUrl` lança em staging e produção |
| B6 | O processo **sai** com código 1, em vez de responder 500 | PASS | executado no artefato construído: `ADAPTER_ENV=staging node .next/standalone/server.js` imprimiu a lista de variáveis, saiu com **código 1** e a porta ficou sem nada ouvindo. Antes desta entrega o mesmo comando deixava o servidor no ar respondendo 500 em todas as rotas, inclusive `/api/health/live` |
| B7 | `npm run verify:config` recusa e explica | PASS | em `ADAPTER_ENV=staging` sem variáveis: 9 itens nomeados, saída com código 1 |
| B8 | O worker também recusa configuração ausente | PASS | no layout de runtime, `ADAPTER_ENV=staging` sem variáveis: saída com código 1 antes de tocar banco ou Redis |
| B9 | Contêiner da web e do worker saem com código ≠ 0 | PENDENTE | passo `Configuração ausente encerra com código diferente de zero` do job `image`, que inspeciona `.State.ExitCode` dos dois |
## C. Autenticação e multi-tenant
| # | Item | Resultado | Evidência |
| --- | --- | --- | --- |
| C1 | API do módulo sem sessão responde 401 | PASS | `tests/http/routes.test.ts` |
| C2 | Página protegida sem sessão vai ao login | PASS | `tests/http/proxy.test.ts` |
| C3 | Cookie de workspace não é prova de autorização | PASS | `resolveWorkspace` ignora dica sem associação real; teste com dica forjada e com injeção |
| C4 | Gravar workspace de terceiro é recusado | PASS | `POST /api/auth/workspace` → 403 e cookie não gravado |
| C5 | Associações listadas são só as do usuário | PASS | teste com Eva, que só vê Beta |
| C6 | Módulo desligado bloqueia o workspace | PASS | teste com Diego em Beta |
| C7 | Papel sem permissão não altera configuração | PASS | teste com Bruno (agent) |
| C8 | Login de homologação indisponível fora de staging | PASS | `/api/auth/staging/session` → 404 |
| C16 | Dev auth não liga em homologação, em nenhuma combinação | PASS | `tests/unit/dev-auth-guard.test.ts`, 18 casos: matriz de ambientes na função, recusa na validação de configuração, e o provedor local fora da lista de provedores em staging e produção. Um token de dev válido é recusado quando o ambiente é staging |
| C9 | Isolamento entre workspaces na suíte de aplicação | PASS | `tests/security/isolation.test.ts`, 6 casos |
| C10 | RLS em PostgreSQL real, papel `app_user` sem BYPASSRLS | PENDENTE | `tests/services/postgres.test.ts` escrito; roda no CI e no banco de homologação |
| C11 | Login real no navegador, com conta do Supabase de homologação | PENDENTE | exige o projeto Supabase de homologação. O caminho equivalente foi executado no nível de development (C14) |
| C14 | Login real e isolamento no artefato em execução | PASS | contra `node server.js`: sessão emitida, `GET /api/auth/workspace` devolveu só o workspace da identidade, `POST` com workspace de terceiro → **403** sem gravar cookie, `POST` com o próprio → 200, `/adaptador` → 200 |
| C15 | Cookie de workspace forjado é ignorado em execução | PASS | três tentativas com a sessão de Ana (Alpha): cookie apontando Beta, cookie com `' or 1=1 --` e cookie com UUID inexistente. Em todas a resolução permaneceu em Alpha; Beta nunca foi alcançado |
| C12 | Troca de workspace na interface | PENDENTE | exige duas associações reais em homologação |
| C13 | Sessão expirada volta ao login | PENDENTE | exige sessão real |
## D. Infraestrutura e processamento
| # | Item | Resultado | Evidência |
| --- | --- | --- | --- |
| D1 | Rate limiting devolve 429 com `Retry-After` | PASS | `tests/http/routes.test.ts` |
| D2 | Cabeçalhos de segurança configurados | PASS | `next.config.ts`: CSP, HSTS, `X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`, COOP |
| D18 | Smoke prepara a fase autenticada do Supabase | PASS no script / PENDENTE na execução | `scripts/smoke-staging.ts` tem as duas fases; sem `ADAPTER_SMOKE_EMAIL`/`PASSWORD` a fase 2 imprime PENDENTE. Credenciais listadas na seção 13 do runbook |
| D3 | Cabeçalhos presentes na resposta HTTP real | PASS no artefato / PENDENTE atrás do Nginx | resposta de `/api/health/live` trouxe CSP, HSTS, `X-Frame-Options: DENY`, `Referrer-Policy`, `Permissions-Policy`, COOP, `X-Content-Type-Options` e `Cache-Control: no-store`. Falta confirmar que o Nginx não sobrescreve |
| D4 | Redis e Supabase reutilizados por processo | PASS | singleton em `src/infra/redis/index.ts` e `src/infra/storage/index.ts` |
| D5 | Namespace separado por aplicativo e ambiente | PASS | `resourceNamespace`; teste confirma que staging ≠ production |
| D6 | Listagem de bucket paginada | PASS | `src/infra/storage/supabase.ts` pagina com `offset` e desce em subpastas |
| D7 | Bucket privado conferido | PENDENTE | `npm run check:bucket` exige o bucket real |
| D8 | Lock entre processos com Redis real | PENDENTE | `tests/services/redis.test.ts`; roda no CI |
| D9 | Processamento assíncrono entre processos | PENDENTE | `.github/scripts/async-journey.sh` percorre web → Redis → worker → download no compose; roda no job `image` |
| D9b | Jornada assíncrona pelo loop real do worker | PASS | `tests/integration/worker.test.ts`: a web enfileira e devolve `queued`, o download falha antes do tick, o tick conclui e os três formatos saem; o modelo original não muda |
| D9c | Job sobrevive à perda da fila do Redis | PASS | fila drenada à mão: a varredura do Postgres acha o job `queued` e conclui |
| D9d | Worker relê o contexto do dono | PASS | flag do módulo desligada entre o enfileiramento e o tick: o job não é processado |
| D10 | Worker recusa iniciar com migration pendente | PASS por construção | `pendingMigrations` no start; a execução real é PENDENTE |
| D11 | Desligamento gracioso do worker | PASS no loop / PENDENTE no sinal | `shutdown()` para de aceitar trabalho, espera o tick corrente sair, e o job que não começou fica `queued` com o lock reservado para ele; concluído, o lock é liberado. A **entrega do SIGTERM** ao processo é PENDENTE: no Windows não há sinal real, e o job `image` do CI confere `SIGTERM recebido`, `[worker] encerrado` e exit 0 em menos de 25 s |
| D17 | Heartbeat do worker e healthcheck | PASS | worker no layout de runtime gravou o heartbeat; `docker/worker-health.mjs` aprovou com 1 s e reprova quando o arquivo falta |
| D12 | Download XLSX, CSV e PDF ponta a ponta | PASS no local / PENDENTE no ambiente | `tests/integration/journey.test.ts` cobre o fluxo; falta a jornada em homologação |
| D13 | Modelo original não é alterado | PASS | `tests/unit/xlsx-fixtures.test.ts` compara bytes |
| D14 | TTL de temporários aplicado | PASS no local / PENDENTE no ambiente | `tests/integration/journey.test.ts`; falta observar o vencimento real |
| D15 | Health check responde `ready` com dependências satisfeitas | PASS | primeira chamada com banco frio: `{"status":"ready"}` com `config`, `database`, `migrations`, `redis`, `storage` e `job_mode` em `ok`, sem connection string na resposta |
| D16 | Health check aponta a dependência que falhou | PASS | campo `reasons` traz só o nome do erro e os ids de migration pendente |
## E. Observabilidade
| # | Item | Resultado | Evidência |
| --- | --- | --- | --- |
| E1 | Payload do Sentry sem conteúdo de célula | PASS | `tests/unit/pilot-policy.test.ts` com sentinela |
| E2 | Ambiente e release por ambiente | PASS | `sentryInitOptions` usa `ADAPTER_ENV` e `SENTRY_RELEASE` |
| E3 | `onRequestError` registrado | PASS | export em `src/instrumentation.ts` |
| E4 | Evento real chega ao projeto Sentry de homologação | PENDENTE | exige DSN real |
| E5 | Sourcemap com stack legível | PENDENTE | exige `SENTRY_ORG`, `SENTRY_PROJECT` e `SENTRY_AUTH_TOKEN` no build |
| E6 | Auditoria sem conteúdo de célula | PASS | `tests/integration/rls.test.ts` e `sanitizeAuditMetadata` |
## F. Ambiente e deploy
| # | Item | Resultado | Evidência |
| --- | --- | --- | --- |
| F1 | Dockerfile multistage com `standalone` | PASS | `Dockerfile` + `output: "standalone"` |
| F2 | Scripts operacionais funcionam sem devDependencies | PASS | executados no layout de runtime simulado (ver A15 e A16), não apenas por construção |
| F12 | Job de build Docker Linux no CI com validação da imagem | PASS no workflow / PENDENTE na execução | job `image`: build, conteúdo, usuário não-root, ausência de segredo/docs/testes, carga dos scripts, fail-fast dos dois processos, liveness com cabeçalhos, compose completo, jornada assíncrona entre processos e desligamento gracioso |
| F13 | Rollback de imagem documentado | PASS | runbook seção 10: identificar a imagem no ar, listar tags, desligar a flag antes, trocar só web e worker com `--no-deps`, conferir saúde, religar a flag. `0003_rls.sql` e `0004_audit_append_only.sql` declaradas sem caminho de volta |
| F3 | Node e gerenciador fixados | PASS | `.nvmrc` 24.11.1, `engines.node >=22.12 <25`, `packageManager npm@11.6.2` |
| F4 | Compose isolado dos outros aplicativos | PASS | nome de projeto, rede e volume próprios; portas só em `127.0.0.1`; deploy com `--no-deps` |
| F5 | `.env.staging.example` sem segredo | PASS | todos os campos sensíveis vazios |
| F6 | Migration, deploy e flag em jobs separados com aprovação | PASS | `.github/workflows/deploy-staging.yml`, ambientes `hml-migracao`, `hml-deploy`, `hml-flag` |
| F7 | Deploy real em `hml-ada.elopro.app` | PENDENTE | exige servidor, DNS e segredos |
| F8 | DNS e TLS do subdomínio | PENDENTE | exige acesso ao DNS |
| F9 | Nginx do novo `server` | PENDENTE | exige acesso ao servidor |
| F10 | Smoke de homologação | PENDENTE | `npm run smoke:staging` exige o ambiente |
| F11 | Ensaio de rollback | PENDENTE | exige duas imagens publicadas e o ambiente |
## Resumo
| | Quantidade |
| --- | --- |
| PASS com evidência executada | 54 |
| PENDENTE por falta de infraestrutura ou credencial | 20 |
| FAIL | 0 |
Nada foi marcado PASS por inspeção de código. Os PENDENTE que dependem só de Docker (A11, A12, A8, B9, C10, D8, D9, F12) executam no CI no primeiro push; os demais dependem de infraestrutura do ecossistema.
## Defeitos encontrados nesta rodada de validação
Os quatro apareceram justamente ao exercitar o layout de runtime da imagem, e nenhum aparecia na suíte de testes ou no build.
| # | Defeito | Efeito em homologação | Correção |
| --- | --- | --- | --- |
| 1 | O CLI do `tsx` cria processo filho | SIGTERM do `docker stop` pararia no pai; o worker nunca rodaria o handler, o lock esperaria o TTL de 120 s e o `stop_grace_period` só atrasaria um SIGKILL | entrypoint usa `node --import tsx`; PID conferido igual |
| 2 | Top-level await nos scripts com `--import tsx` | `migrate`, `worker`, `seed-staging`, `set-flag`, `cleanup` e `check-bucket` falhariam com `Transform failed`. O worker não subiria e a migração não rodaria | `runScript`/`runWithDb` em `scripts/_bootstrap.ts`; nenhum script usa top-level await |
| 3 | `import "server-only"` em `worker-loop.ts` | o pacote lança fora do bundler do Next: o worker morreria no start | removido do módulo compartilhado, com o motivo registrado no arquivo |
| 4 | `@react-pdf/renderer` carregado no import de `process-job.ts` | `ERR_PACKAGE_PATH_NOT_EXPORTED` ao resolver a hifenização fora do bundler: derrubava o worker e o `cleanup` | import tardio dentro do ramo de PDF, que só o download usa |
## Bloqueios para liberar homologação
Todos externos ao repositório. Nenhum depende de código.
1. DNS de `hml-ada.elopro.app` e certificado TLS.
2. Projeto Supabase de homologação, com contas de teste e bucket privado.
3. Banco de homologação com `DATABASE_URL` e a decisão do shim conferida.
4. Servidor com Docker, `HML_DEPLOY_PATH` e os segredos dos ambientes do GitHub.
5. DSN do Sentry de homologação.
## Bloqueios para discutir produção
Além de tudo acima, concluído:
1. Smoke real completo, com evidência, em homologação.
2. Ensaio de rollback executado, com evidência.
3. SSO do Elo Pro integrado e validado no navegador.
4. Schema real do Elo Pro confirmado e `identity-port.ts` apontando para ele.
5. Tokens oficiais do Design System. O azul `#0B3D91` continua provisório.
## Evidências a anexar
Para cada item PENDENTE que for executado, registre aqui: data, quem executou, comando ou tela, e saída obtida. Resposta de `GET /api/health/ready` (sem segredo), log do worker concluindo um job, print do download dos três formatos, print do evento sanitizado no Sentry e log do ensaio de rollback.