17 KiB
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 |
| D19 | Storage local gravável pelo uid 1001 em volume novo e existente (local) | PASS | 16 out 2026, Docker Desktop/WSL2: .github/scripts/storage-permissions.sh casos A–F em volumes descartáveis. Corrige STORAGE_NOT_WRITABLE no upload. Ver 23-storage-permissions.md. Não se aplica a homologação (bucket Supabase, D7) |
| D20 | Web e worker recusam subir com storage local sem escrita | PASS | mesmo script, caso F: saída com código 1 e STORAGE_NOT_WRITABLE |
| 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 | 56 |
| 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.
- DNS de
hml-ada.elopro.appe certificado TLS. - Projeto Supabase de homologação, com contas de teste e bucket privado.
- Banco de homologação com
DATABASE_URLe a decisão do shim conferida. - Servidor com Docker,
HML_DEPLOY_PATHe os segredos dos ambientes do GitHub. - DSN do Sentry de homologação.
Bloqueios para discutir produção
Além de tudo acima, concluído:
- Smoke real completo, com evidência, em homologação.
- Ensaio de rollback executado, com evidência.
- SSO do Elo Pro integrado e validado no navegador.
- Schema real do Elo Pro confirmado e
identity-port.tsapontando para ele. - Tokens oficiais do Design System. O azul
#0B3D91continua 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.