Pre deploy validator
Skill henriquescastilho/my-claude/.claude/skills/pre-deploy-validator
Setup público e sanitizado de um Claude Code full-stack: 8 sub-agents com roteamento por modelo, 792 skills, hooks de segurança, MCP servers e metodologia opinativa. Desenhado para um agente de IA se auto-configurar.
npx -y skills add henriquescastilho/my-claude --skill pre-deploy-validatorAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use ANTES de qualquer deploy em produção. Roda checklist de 23 validações baseadas em falhas reais detectadas em projetos anteriores (CRM_OS, Valesa, etc). Detecta env vars hardcoded errado, CSP/CORS misconfig, JWT claims mismatch, migrations slot conflict, rotas Hono não montadas, RBAC lazy, mocks em prod, e mais. Invocável com `/pre-deploy-check` ou automaticamente quando o usuário menciona deploy/release.
SKILL.md
9.9 KB, ~2.8k tokens by cl100k_base, as published. Nobody here has run it
Pre-Deploy Validator
Checklist de validações que DEVE rodar antes de qualquer gcloud run deploy, terraform apply em prod, ou tag de release. Baseada em falhas reais.
Quando invocar
- Antes de
docker buildx build --pushpra image de prod - Antes de
gcloud run deploypra service em prod - Antes de tagear release (v1.0.0+)
- Quando user fala "deploy", "subir em prod", "release"
Checklist (23 items)
🔴 P0 — Bloqueia deploy
1. Env vars Vite no build apontam pro DOMÍNIO PÚBLICO, não run.app interno
# ANTES de docker buildx, confirmar:
echo "VITE_SERVER_URL=https://meudominio.com" # NÃO https://servico-xxxx.run.app
echo "VITE_AUTH_URL=https://meudominio.com"
Por quê: CSP connect-src 'self' bloqueia chamadas pro run.app. Browser entra em retry infinito → ERR_INSUFFICIENT_RESOURCES.
2. CSP connect-src inclui o domínio do backend
Inspecionar apps/web/nginx.conf ou middleware CSP:
connect-src 'self' https://meudominio.com
3. CSP nonce placeholder substituído ou removido
grep -rn "%%CSP_NONCE%%" apps/web/dist apps/web/index.html
# Deve retornar 0 matches (ou remover o placeholder)
4. Migrations slot livre (sem colisão entre PRs)
# Confirmar próximo slot:
ls packages/db/migrations/*.sql | tail -3
# Pegar último N, usar N+1 na nova migration
# Verificar _journal.json sincronizado com files
5. Rotas Hono novas estão MONTADAS em index.ts
# Pra cada novo arquivo em apps/server/src/routes/, confirmar:
grep "novoArquivo" apps/server/src/index.ts
# Se não tem, é 404 silencioso em prod
6. Paths internos não duplicam o segmento do mount
// BUG: router.get('/deals/:id') montado em app.route('/api/deals') = /api/deals/deals/:id
// CORRETO: router.get('/:id') montado em app.route('/api/deals') = /api/deals/:id
Comando de verificação:
for f in apps/server/src/routes/*.ts; do
recurso=$(basename $f .ts | sed 's/-.*//')
grep -q "router\.\(get\|post\|patch\|delete\)('/$recurso" "$f" && echo "BUG: $f duplica /$recurso"
done
7. Cloud Run traffic apontado pra LATEST após deploy
gcloud run services update-traffic <service> --to-latest --region=<region> --project=<project>
# Sem isso, deploy cria rev nova mas tráfego fica na anterior
🟡 P1 — Investigar antes
8. JWT claims que código backend espera estão sendo emitidos
// Decodar JWT real do user e verificar claims:
const decoded = jwt.decode(token);
// Se backend espera org_id mas JWT só tem app_metadata.org_id, ou vice-versa,
// é bug. GoTrue auto-hospedado precisa Custom Access Token Hook pra injetar claims.
9. RBAC Guard é SÍNCRONO (não lazy/toast-then-redirect)
// BUG: renderiza componente, dispara toast, redireciona depois → flash conteúdo sensível
// CORRETO: redirect imediato ANTES de render
if (role && !allow.includes(role)) return <Navigate to="/dashboard" replace />;
if (!role) return <Skeleton />; // loading
10. Mocks/placeholders foram removidos
grep -rn "MOCK_\|FAKE_\|hardcoded\|TODO.*mock\|Ana Lima\|Carlos Mendes\|R\$ 297" apps/web/src
# Esperado: zero matches em prod
11. Rate limit é DISTRIBUÍDO (Redis), não in-memory
grep -rn "new Map()" apps/server/src/middleware/rate-limit*
# Se aparecer Map(), não escala entre instâncias Cloud Run
12. Dev secrets com fail-fast em produção
// BUG: process.env['SECRET'] ?? 'dev-fallback-min-32-chars'
// CORRETO:
function requireEnv(name: string): string {
const v = process.env[name];
if (!v && process.env['NODE_ENV'] === 'production') {
throw new Error(`Missing required env var: ${name}`);
}
return v || `dev-fallback-${name.toLowerCase()}`;
}
13. CORS allowlist explícita, sem * com credentials
Backend Hono cors():
cors({ origin: ['https://meudominio.com'], credentials: true })
14. Security headers HSTS+CSP+XFO+nosniff+Referrer
curl -sI https://meudominio.com/ | grep -iE "strict-transport|x-frame|content-security|x-content|referrer"
# Esperado: 5 headers presentes
15. PostgREST/serviços internos NÃO expostos publicamente
Verificar URL Map / Cloud Armor — /rest/v1/* deve estar bloqueado ou autenticado.
⚪ P2 — Polish
16. Frontmatter status: implemented tem código real
# Pra spec com status: implemented, grep palavras-chave do nome:
grep -rn "<slug-keyword>" apps/server/src apps/web/src packages/
# Se zero matches, frontmatter mente
17. Worktrees git órfãos limpados
git worktree list | grep -v "$(pwd)"
du -sh ../proj-* # Cada worktree ~500MB
18. Branches mergeadas deletadas
gh pr list --state merged --limit 50 --json headRefName -q '.[].headRefName' | while read b; do
gh api repos/OWNER/REPO/branches/$b >/dev/null 2>&1 && echo "$b ainda existe"
done
19. PostgREST schema cache atualizado após mudança DB
NOTIFY pgrst, 'reload schema';
-- OU redeploy do service postgrest
20. Maestri agents não ficam idle aguardando "GO"
Briefings devem ter "GO autoridade total modo overnight, decide+implementa+valida". Caso contrário agent fica esperando confirmação.
21. Smoke E2E REAL via browser (não só curl)
Playwright headless executa o JS bundle. Curl só testa HTTP path — pode dar false positive enquanto bundle JS crasha em runtime.
22. Cloud SQL Private IP + VPC Connector (não 0.0.0.0/0)
gcloud sql instances describe <inst> --format='value(ipAddresses[].type)'
# Esperado: ['PRIVATE'] (sem PUBLIC/PRIMARY)
23. Backup automatizado configurado
Cloud SQL → automated backups daily + PITR ≥7d.
Output
Reporta cada item ✅ ou ❌, com fix sugerido pra cada ❌. Se algum P0 falha, BLOQUEAR DEPLOY.
Lições adicionais
- Validar build args ANTES do push: rodar
docker run --rm <image> grep -r "run.app" /usr/share/nginx/htmlpra detectar URL interna vazada - CDN/LB cache: após deploy, fazer hard refresh + cache_bust query string pra validar bundle novo
--to-latesté obrigatório: Cloud Run não promove tráfego automaticamente, mesmo com--platform=managedgh pr mergesem--delete-branch: deixa branches acumulando — sempre usar- Múltiplos PRs paralelos com mesma migration slot: precisam rebasear sequencialmente, não simultâneo
- Buildx cache do runner stage: quando
Dockerfileé multi-stage (builder+runner),buildxcom driverdocker-containercacheia o runner stage mesmo quando o builder produz dist nova com hash de conteúdo igual. Bundle antigo pode ser servido apesar de--no-cacheno buildx. Mitigação obrigatória: adicionarARG CACHEBUST=1no Dockerfile antes doCOPY --from=builder ..., e passar--build-arg CACHEBUST=$(date +%s)em TODO build de prod. Sem isso, prod pode servir bundle com env vars ou código de PRs anteriores. (Descoberto em CRM_OS web:v23 — bundle antigo persistiu mesmo após PR mergeado com fix.) - Cloud Armor URL Map removal NÃO basta: se o path matcher é apenas removido do URL Map, o tráfego cai no
defaultService(frontend SPA). Pra bloqueio explícito 403, adicionar regra Cloud Armordeny-403no path. Defense in depth. - PoC de security audit pode deixar resíduo no DB: agents de pentest fazem INSERT/UPDATE pra provar exploit. Sempre exigir que o agent REVERTA cada mudança ao final + logue PoC com timestamp pra cleanup posterior.
- State de auth descentralizado = dessincronia visual: se múltiplos hooks consomem
sessionpor caminhos diferentes (auth-context vs cache singleton vs localStorage direto), eles ficam dessincronizados no primeiro render. Sintoma típico: sidebar mostra org/user, dashboard mostra "Selecione organização". Regra: único source of truth via auth-context React; todos os hooks DERIVAM dele. Cache singleton em módulo só serve pra memoização secundária após session estabilizada, nunca como primeiro source. - Cloud Armor rule com expression vazio bloqueia TUDO: ao criar rule via
gcloud compute security-policies rules createse o flag--expressionfor omitido ou aceito vazio, a rule ficamatch: { expr: { expression: '' }}que matcha tudo. Validar SEMPRE após criar:gcloud compute security-policies rules describe <priority> --format='value(match.expr.expression)'— não pode estar vazio. ??não trata string vazia como falsy:const X = import.meta.env.VITE_FOO ?? 'fallback'falha quando Vite injeta''(de--build-arg VITE_FOO=sem valor).??só faz fallback em null/undefined. Use(env as string \| undefined)?.trim() \|\| 'fallback'. Validar bundle pós-build:curl bundle.js \| grep -oE 'https://[^"]*'deve listar URLs absolutas esperadas. Bug encontrado em CRM_OS web:v29 (login quebrado) — fix em PR #116.- Fix de Zod schema sem atualizar consumers do parsed value: ao trocar tipo de validação (ex:
z.string().date()→z.string().datetime()), buscar TODOS os call sites que consomem o valor parseado — formato muda. Em CRM_OS PR #117 corrigiu schema de/api/admin/audit-logdedate(YYYY-MM-DD) pradatetime(ISO completo), masbuildFilters()continuou fazendonew Date(\${from}T00:00:00.000Z`)(concatenava sufixo presumindo formato date) — virouDate(NaN), drizzle quebrava com RangeError, endpoint passou de 400 pra 500. Fix completo em PR #118. **Regra:** após mudar schema,grep` o nome do campo no arquivo todo + buscar consumers que assumem formato antigo. Pre-deploy: rodar a request real com payload do frontend antes de declarar pronto.