Pular para conteúdo

0030 — Skill ship: deploy do main em produção

Status CONCLUÍDA
Branch spec/0030-skill-ship-deploy
Criada em 2026-08-08
Apps afetados ferramental (.claude/skills/ship/) · api/web/e2e sem impacto

Status: RASCUNHO → APROVADA → EM IMPLEMENTAÇÃO → CONCLUÍDA

Esta é uma spec de ferramental (uma skill do Claude Code), não de feature. Não cria rota, modelo nem tela — os apps entram como "sem impacto". É o comando futuro de deploy de rotina que a spec 0028 deixou explicitamente de fora (§3 daquela spec).

1. Problema

A stack de produção (spec 0028) está no ar em https://app.garantora.com.br/ (box srv01 = 179.198.118.166, repo em /opt/garantora/infra/prd). Mas não há um comando de deploy de rotina: subir uma versão nova do código hoje é fazer à mão, na box, a sequência git pull → make up → (backup → migrate) → verificar — fácil de errar a ordem, esquecer o backup antes de uma migration, ou aplicar sem olhar o que está indo para o ar. A spec 0028 deixou o Makefile sem alvo deploy de propósito, para que o desenho do deploy fosse tratado por completo aqui.

2. Objetivo

Uma skill ship que, chamada da máquina do desenvolvedor, faz o deploy do que está em origin/main para a produção com segurança: mostra o que vai mudar (commits e migrations), pede confirmação, e então atualiza o código na box, rebuilda, faz backup antes de migrar (só quando há migration pendente) e verifica que subiu de verdade.

3. Fora de escopo

  • Publicar o código (git push). O ship não faz push (respeita a regra do CLAUDE.md). Ele deploya o que já está em origin/main e recusa se o main local estiver à frente — você dá o push primeiro. Separa "publicar" de "implantar".
  • Rollback automatizado. O ship não reverte sozinho. Em falha, ele para e relata, e o README/skill documenta o rollback manual (redeploy do commit anterior + make restore do dump gerado antes da migration). Automatizar rollback fica para outra spec, se preciso.
  • Seed. make seed é one-time do provisionamento (já feito). O ship nunca semeia.
  • Provisionar a box. Instalar Docker/make, DNS, .env, firewall — é a spec 0028. O ship pressupõe a box já provisionada e a stack já existente.
  • Outros ambientes. ship é só produção (srv01). Dev-remote tem o seu próprio fluxo (/devremote-rebuild).
  • CI/CD. Continua sem pipeline; ship é acionado por pessoa, com gate de confirmação.

4. Regras de negócio (comportamento da skill)

Numeradas para citação nos critérios de aceite.

  • RN1. Alvo fixo de produção. O ship opera sobre srv01 (root@179.198.118.166), repo em /opt/garantora, stack em infra/prd, domínio app.garantora.com.br. Constantes na skill (um ponto só para mudar se a box mudar).
  • RN2. Deploya origin/main, nunca o local. Antes de tudo, git fetch origin e compara: se o main local estiver à frente de origin/main (commits não publicados), o ship recusa e instrui a dar git push primeiro. Não faz push por conta própria (§3). O que vai para produção é exatamente origin/main.
  • RN3. Pré-visualização + confirmação (gate humano). Antes de tocar a box, o ship mostra: (a) os commits que entram — git log <deployado>..origin/main --oneline, onde <deployado> é o HEAD atual de /opt/garantora na box; (b) se há migration pendente (RN4); (c) se haverá backup (RN5). Só prossegue com confirmação explícita do usuário. Sem confirmação, nada muda na produção.
  • RN4. Detecção de migration pendente (sem rebuildar antes). O ship detecta migration olhando o diff: git diff --name-only <deployado>..origin/main -- apps/api/alembic/versions/. Se surgirem arquivos novos ali, há migration pendente; senão, não há. É sinal pré-voo, não exige o código novo já buildado.
  • RN5. Backup antes de migrar (só quando há migration). Havendo migration pendente (RN4), o ship roda make backup antes do make migrate, garantindo um dump imediatamente anterior à mudança de schema. Sem migration pendente, pula backup e migrate.
  • RN6. Sequência de deploy. Na ordem, após a confirmação: (1) git pull --ff-only em /opt/garantora (com agent forwarding para o repo privado — RN8); (2) make up (rebuild + recreate das imagens api/web); (3) se migration pendente: make backup → make migrate. Nunca migra antes de rebuildar; nunca migra sem backup.
  • RN7. Verificação pós-deploy. Ao fim, o ship confere e relata: HEAD de /opt/garantora == origin/main; https://app.garantora.com.br/api/v1/health responde {"status":"ok","db":"ok"}; https://app.garantora.com.br/ responde 200 com TLS válido. Qualquer falha aqui é reportada como falha do deploy (não "deu certo").
  • RN8. Acesso SSH sem deixar rastro. O pull na box precisa de auth no GitHub via agent forwarding (ssh -A). Se a chave (default ~/.ssh/id_ed25519, configurável) não estiver no ssh-agent, o ship a adiciona para a operação e a remove ao final, restaurando o estado anterior do agent — não deixa a chave carregada.
  • RN9. Falhou, para. Qualquer passo que falhe (recusa da RN2, pull não-ff, build quebrado, migration com erro, healthcheck vermelho) interrompe o ship com mensagem clara do que falhou e do estado em que a produção ficou. Não continua "no escuro".
  • RN10. Idempotente quando não há nada novo. Se origin/main == HEAD deployado, o ship diz "produção já está no origin/main" e encerra sem rebuildar.

5. Impacto por app

.claude/skills/ship/ (o trabalho desta spec)

  • SKILL.md — frontmatter (name: ship, description, when_to_use, argument-hint)
  • o fluxo: pré-voo (RN2, RN4), pré-visualização e confirmação (RN3), deploy (RN6), verificação (RN7). O gate de confirmação é conduzido pela skill (Claude mostra o diff e pede OK), não por um script mudo.
  • scripts/ (conforme a implementação decidir):
  • um script de pré-voo que coleta, da box, o HEAD deployado e devolve o diff de commits + se há migration pendente, para a skill apresentar;
  • um script de deploy que executa a sequência da RN6 na box (pull → up → [backup → migrate]) e o healthcheck da RN7. Constantes (host, path, domínio, chave) no topo.
  • Sem alvo deploy no Makefile de infra/prd — a orquestração (fetch/compare, gate, agent forwarding, verificação) é da skill; o Makefile segue só com os tijolos (up, migrate, backup, …). Alternativa aceitável na implementação: adicionar um alvo make deploy simples que o script da skill chame, se ficar mais limpo — mas o gate e o agent forwarding continuam na skill.

apps/api · apps/web · apps/e2e

Sem impacto. A skill não altera código de aplicação; só orquestra o deploy do que já está em origin/main.

6. Contrato de API

Sem impacto. Nenhuma rota nova ou alterada. A skill apenas consome /api/v1/health na verificação pós-deploy.

7. Critérios de aceite

Verificáveis por quem não escreveu a skill.

Estado: exercitado num /ship real em 2026-08-08 (deploy de 536457a → 5b67a0c, sem migration). O caminho principal (pré-voo → gate → deploy → verificação) está provado. CA1/CA4/CA6 têm lógica pronta mas não foram disparados (exigiriam um commit local não publicado, uma migration nova, e uma falha real, respectivamente) — ver Registro.

  • [ ] CA1. Com main local à frente de origin/main, o ship recusa o deploy e instrui a dar git push — nada muda na box. (RN2) — lógica pronta (rev-list --count), não exercitada (exigiria commit local não publicado).
  • [x] CA2. Com origin/main à frente do que está deployado, o ship mostra os commits que entram e se há migration pendente. Verificado: o pré-voo real listou os 2 commits pendentes e detectou "sem migration" (MIGRATE=0). O "só prossegue após confirmação" é o gate da SKILL (Passo 2). (RN3, RN4)
  • [x] CA3. Deploy sem migration: o ship fez pull → make up, pulou backup e migrate (log "sem migration pendente — backup/migrate pulados") e a verificação passou. Verificado no deploy real. (RN5, RN6, RN7)
  • [ ] CA4. Num deploy com migration nova, o ship roda make backup antes de make migrate e um dump novo existe. (RN5, RN6) — lógica pronta; não exercitada (o deploy real não carregava migration). Fica para o 1º deploy com mudança de schema.
  • [x] CA5. Fim do deploy: HEAD de /opt/garantora == origin/main (5b67a0c), /api/v1/health = {"db":"ok"} e / = HTTP 200 TLS válido. Verificado. (RN7)
  • [ ] CA6. Se um passo falha (ex.: build quebrado), o ship para e relata o passo e o estado; não segue como se tivesse dado certo. (RN9) — set -e nos scripts garante a parada; não exercitado com falha real.
  • [x] CA7. A chave não estava no ssh-agent; o ship a carregou e removeu ao final (log "chave removida do ssh-agent (estado restaurado)"). Verificado. (RN8)
  • [x] CA8. Com origin/main == HEAD deployado, o ship encerra dizendo que produção já está atualizada, sem rebuildar. (RN10) — o pré-voo tem o exit 4 para isso; a lógica (DEPLOYED == ORIGIN) foi confirmada por leitura no run real (hoje há 2 commits à frente, então caiu no exit 0, correto).

8. Plano de implementação

A ordem api → web → e2e do template não se aplica (é ferramental). Ordem real:

  • [x] .claude/skills/ship/SKILL.md — frontmatter + fluxo (pré-voo → gate → deploy → verificação)
  • [x] .claude/skills/ship/scripts/ — config.sh (constantes, override por SHIP_*), preflight.sh (fetch/compare + diff + migration pendente) e deploy.sh (pull → up → [backup → migrate], com gestão do ssh-agent). Healthcheck ficou na SKILL (curl externo).
  • [x] Validação estática + pré-voo real: bash -n nos três scripts OK; pré-voo rodado de verdade (read-only) contra a produção — listou os commits pendentes e MIGRATE=0. O caminho de deploy não foi exercitado (é ação do usuário, com gate).
  • [x] make test/make lint — não afetados (nenhum código de app tocado)
  • [x] README de infra/prd atualizado: aponta o /ship como o deploy de rotina e o rollback manual (antes dizia "comando futuro").

make test/make lint e o browser do template não se aplicam a ferramental; a validação é o ensaio da skill num deploy real, combinado com o usuário.

9. Decisões em aberto — resolvidas

  • [x] DA1 — Migrations no deploy. ✅ Só quando há pendente (detecção por diff dos arquivos em alembic/versions/, RN4); aplica após confirmação e backup.
  • [x] DA2 — Backup antes de migrar. ✅ Sim, e só quando há migration (RN5).
  • [x] DA3 — Origem do deploy. ✅ Deploya origin/main; recusa se o local estiver à frente (você dá push antes). O ship não faz push (RN2, §3).
  • [x] DA4 — Confirmação. ✅ Gate humano com diff de commits + migrations pendentes antes de tocar produção (RN3).
  • [x] DA5 — Chave SSH. ✅ Agent forwarding; a skill adiciona a chave se faltar e remove ao final, restaurando o agent (RN8). Caminho da chave configurável (default ~/.ssh/id_ed25519).
  • [x] DA6 — Rollback. ✅ Fora de escopo automatizar (§3); documentar o manual (redeploy do commit anterior + make restore do dump).

10. Registro

Implementado em 2026-08-08. Skill ship criada em .claude/skills/ship/:

  • SKILL.md conduz o fluxo (pré-voo → apresentar+confirmar → deploy → verificar) e documenta o rollback manual. O gate humano (RN3) é da skill: mostra o relatório do pré-voo e espera "sim" antes do deploy.
  • scripts/config.sh — constantes (host 179.198.118.166, /opt/garantora, domínio app.garantora.com.br, chave), cada uma com override SHIP_*.
  • scripts/preflight.sh — read-only: git fetch, recusa se o local está à frente (exit 3), "nada a fazer" se igual (exit 4), senão lista os commits e detecta migration pelo diff em alembic/versions/ (imprime MIGRATE=0/1, exit 0).
  • scripts/deploy.sh — pós-confirmação: gerencia o ssh-agent (adiciona a chave se faltar, remove no trap EXIT), e numa sessão ssh -A faz pull → make up → [backup → migrate se MIGRATE=1].

Desvios/decisões da implementação:

  1. Healthcheck na SKILL, não no deploy.sh. A verificação (RN7) é curl ao domínio público — melhor rodar de fora (da máquina do dev), pelo caminho real, do que de dentro da box. Ficou no Passo 4 da SKILL.
  2. Sem alvo make deploy. Como previa a spec (§5), a orquestração ficou toda na skill; o Makefile de infra/prd segue só com os tijolos (up, backup, migrate, …).
  3. Constantes no config.sh (sourced pelos dois scripts) em vez de repetidas — um ponto só de verdade, com overrides SHIP_* para teste.

Validação: bash -n nos três scripts OK; o pré-voo foi exercitado contra a produção real (read-only) e comportou-se certo — hoje a box está em 536457a e origin/main em 5b67a0c, então listou os 2 commits pendentes (o chore/0028) e MIGRATE=0 (sem migration). O deploy em si não foi rodado — é ação de produção, com o gate de confirmação da skill; fica para um /ship real do usuário, que fecha CA3/CA5/CA6/CA7.

e2e: sem impacto — ferramental não tem spec Playwright.


Primeiro /ship real (2026-08-08). Rodado de ponta a ponta: pré-voo (exit 0, MIGRATE=0, 2 commits) → gate (usuário confirmou) → deploy.sh (background) → verificação. Resultado: git pull 536457a → 5b67a0c, make up (imagens rebuildadas, cache hit pois só Makefile/spec mudaram; containers recriados e healthy), backup/migrate pulados (sem migration), HEAD na box == origin/main (5b67a0c), chave adicionada e removida do ssh-agent no fim. Verificação externa: /api/v1/health = {"db":"ok"}, / = HTTP 200 TLS 0. Fechou CA2, CA3, CA5, CA7, CA8. Restam CA1 (recusa), CA4 (deploy com migration) e CA6 (falha) — lógica pronta, disparam quando a condição ocorrer. Status → CONCLUÍDA.

Nota: o deploy levou à produção os commits do chore/0028 (que estavam em origin/main); a própria skill ship ainda não está em origin/main (é este trabalho, não commitado) — ela rodou da árvore local. Entra em produção no próximo ship depois de mesclada.