# Plano 15 — CI/CD, Deploy e Operação (WHM/cPanel)

> **Milestone:** M4 · **Depende de:** 14 · **Habilita:** produção
> **Objetivo:** publicar de forma atômica e reversível no servidor cPanel real, com a borda do WHM
> correta, e provar que o banco do V7 é recuperável (backup, restore em base limpa, PITR).
>
> **Revisão 2026-10-19 ([ADR-042](../adr/ADR-042-sem-retrocompatibilidade-conteudo-migrado-a-mao.md)):**
> o V7 é projeto **novo, do zero** e **sem retrocompatibilidade**. O conteúdo do legado entra
> **manualmente, caso a caso**, pelo CMS; não há ETL, quarentena `sys_migration`, `legacy_mapping`,
> `migrate:legacy` nem transição de hash. O que era "migração do legado" (§3 antigo) **saiu do
> plano**; o que era "PG 10.23 → 18" virou **backup/restore/PITR do próprio PG 18**.

---

## 1. Pipeline (GitLab CI)

```
stages: check → build → deploy
```

- **check**: `make check E2E=1` — o **caminho único** de validação (lint/fmt corretivo, PHPStan `max`
  sem baseline, gates de fronteira/grep-friendly/migrations/vendor, cobertura ≥90%, PHPUnit, e2e
  Playwright + axe + sniff, perf). Artefatos: `build/coverage/` (Cobertura) e `build/check/`.
  Estágios "por ferramenta" não existem de propósito (ADR-001: um caminho, um veredito).
- **build**: os assets (`.br`/`.gz` determinísticos) e o **SBOM** CycloneDX saem do próprio
  `make check` (pré-condições + `sbom`); o estágio `build` deixou de existir. Sem imagem de produção
  própria — **mesma imagem dev/prod** (AGENTS.md; ADR-031 pina base/digest) e
  `composer install --no-dev` no deploy.
- **deploy**: manual, `main`/`master`, `environment: production`, executa `./up_prod.sh --no-pull`
  (nunca `git reset --hard`, nunca migração de dados).
- Runner shell padrão da casa (tag `deploy`) com Docker + compose no host.

## 2. Deploy em cPanel (topologia real)

```
Apache 2.4 do WHM :443 (edge: TLS/domínios/AutoSSL) ──ProxyPass──▶ web (Caddy) ──fastcgi──▶ php-fpm
                                                    + postgres/valkey/storage internos (sem porta)
```

- Uma instância por cliente (`COMPOSE_PROJECT_NAME` único) — ADR-005; layout WHM padrão
  (`public_html` + `git clone --depth=1`).
- Deploy atômico: pull → `composer install --no-dev --optimize-autoloader --classmap-authoritative`
  → **backup (`pg_dump -Fc`)** → `migrate:status` + `artisan migrate --force --isolated` →
  `artisan optimize` → healthcheck `/up` (sem `storage:link`: domínio no `s3`, ADR-030).
  Falha em qualquer fase → rollback (trap).
- `php artisan optimize` é obrigatório (ADR-008). Single-tenant: não há troca de contexto em runtime;
  as pré-condições (zero `env()` fora de `config/`, zero closure em rotas) são validadas no CI.
  Rollback roda `optimize:clear`.
- **Segurança no deploy** (ADR-020): headers/CSP no vhost; TLS 1.3/HSTS; `composer audit`/`npm audit`
  no `make check`; SBOM no build; segredos só por env (nunca no repositório).
- Runbook: subir/reverter/restaurar backup/limpar cache/logs/renovar certificado.

## 3. Dados: base nova + recuperação (substitui a migração do legado)

**Não há migração de dados.** A base do V7 nasce de `migrate` (dado de referência vindo de migration
idempotente, ADR-017/ADR-041). O que a operação **precisa** garantir:

- **Backup antes de migrar**: `pg_dump -Fc` no `up_prod.sh`, antes do `artisan migrate`; nome
  determinístico, retenção (dias/GiB) e cópia fora do host (S3/RustFS — ADR-030). Dump que falha
  **aborta o deploy**.
- **Restore ensaiado em base limpa** a partir do dump, com verificação (`migrate:status`, contagens de
  referência, checksums de mídia).
- **PITR**: WAL arquivado + roteiro de recuperação pontual, com RTO medido.
- **DR completo** (host perdido → containers + dump + storage) com checklist e dono.

**Conteúdo e usuários:** entrada manual pelo CMS (ADR-042). Usuários recriados com Argon2id e um
perfil por usuário (ADR-033/ADR-037).

**Preservação de URL:** `seo.redirects` (ADR-027) com curadoria **manual** das URLs críticas via
superfície mínima de console; a rota viva nunca é sombreada; `http.not_found` (ADR-024) aponta o que
ainda está quente na primeira semana.

## 4. Atividades

- [x] `.gitlab-ci.yml` com `check`/`build`/`deploy` (runner shell tag `deploy`, `make check E2E=1`,
      artefatos de cobertura/logs, `sbom.cdx.json`, deploy manual via `up_prod.sh --no-pull`).
- [x] `./up_prod.sh` com lock, `--dry-run/--no-pull/--skip-migrate/--branch/--allow-dirty`, 10 fases,
      JSON log, healthcheck `/up` e rollback por `trap` (spec 17).
- [x] `php artisan optimize` no deploy; `optimize:clear` no rollback; **sem** `storage:link`
      (ADR-030 — arquivos de domínio no `s3`, docroot sem symlink).
- [x] `composer audit`/`npm audit` no `make check` + SBOM determinístico (etapa `sbom` do check).
- [x] Higiene de repositório e nome derivado (spec 18: `tools/project-name.sh`, `.gitattributes`,
      contexto Docker mínimo) — dentro do `make check`.
- [ ] **Backup antes do `migrate`** (`pg_dump -Fc`) com retenção e cópia fora do host.
- [ ] **Restore em base limpa** + **PITR** ensaiados e medidos (RTO).
- [ ] Runbook de operação (`wiki/runbooks/deploy-e-recuperacao.md`) + política de retenção.
- [ ] Domínio/AutoSSL/`ProxyPass` do WHM documentados passo a passo (com o `TrustProxies`/`TrustHosts`
      da spec 16) e conferidos na conta real.
- [ ] Rollback ensaiado em conta cPanel de homologação (falha injetada no meio do deploy).
- [ ] Superfície mínima de curadoria de `seo.redirects` (comando de console; UI no backlog).
- [ ] Checklist de go-live do cliente (páginas críticas 200 + metadata + a11y; usuários/perfis).
- [ ] Modelo de ameaça leve (STRIDE) por módulo com PII/arquivos.

## 5. Testes e evidências

- [ ] Pipeline verde de ponta a ponta em branch de staging (`make check E2E=1` inclusive).
- [ ] Deploy/rollback ensaiado em cPanel de homologação (falha injetada → volta ao estado anterior).
- [ ] `./up_prod.sh` 2× seguidas termina 0 e **não altera estado** (ADR-001).
- [ ] Backup gerado antes do `migrate`; restore em base limpa reproduz o estado do dump.
- [ ] PITR: recuperação pontual a partir do WAL, com RTO medido e registrado.
- [ ] Headers de segurança (ADR-020) presentes em produção e **sem** `unsafe-inline` na CSP (e2e).
- [ ] URL antiga cadastrada em `seo.redirects` responde **301**; rota viva nunca é sombreada.
- [ ] Primeira semana: `http.not_found` revisado e URLs quentes cadastradas (ou descartadas).
- [ ] `grep -rn "sys_migration\|legacy_mapping\|migrate:legacy" src/ config/` **vazio** (ADR-042).

## 6. Definition of Done

- [ ] Deploy atômico, observável e reversível numa conta cPanel real.
- [ ] Banco do V7 recuperável: backup antes de migrar, restore em base limpa e PITR ensaiados.
- [ ] Runbook e política de backup publicados e ensaiados.
- [ ] Cliente no ar com páginas críticas verificadas e 301s curados.

## 7. Riscos

| Risco | Mitigação |
|---|---|
| Migração de schema sem backup | `pg_dump -Fc` obrigatório antes do `migrate`; falha aborta o deploy |
| Conteúdo manual esquecido/incompleto | Checklist de páginas críticas + `http.not_found` na 1ª semana |
| Link antigo sem 301 | Curadoria de `seo.redirects` por lista de URLs críticas (ADR-027) |
| Deploy sem rollback | `trap` + `optimize:clear` + healthcheck `/up` + ensaio com falha injetada |
| Cache de config/rota inválido | `optimize` no deploy e no CI (ADR-008) |
| Divergência entre clientes | Uma instância por cliente, mesma imagem versionada |
| Backup só no host (host perdido = tudo perdido) | Cópia em S3/RustFS (ADR-030) + ensaio de DR |
