## Context Fases 0–1 e provedores de produção (Resend + R2) estão em `main` com CI verde. OpenSpec ativo foi limpo: specs promovidas e changes arquivadas. Gaps remanescentes da Fase 0: - Critério de saída §18 exige hello-world em staging; só existe workflow CI. - `docker-compose.yml` sobe apenas PostgreSQL; app local usa `artisan serve`. - README declara PHP 8.5+; Docker/CI usam 8.4; Composer aceita `^8.3`. - `composer quality` não roda `npm audit`; CI unit/feature usam `coverage: none` (SPEC §13.7 pede 80% Domain/Application). - `User` bloqueia inativos em `canAccessPanel`, mas não implementa `MustVerifyEmail`; reset de senha Filament não está validado por teste. - Destino de staging escolhido: VPS própria com Dokploy (não Railway). ## Goals / Non-Goals **Goals:** - Staging em Dokploy com a mesma imagem FrankenPHP por SHA para web, queue e scheduler. - Deploy automático em `main` após CI: build → GHCR → Dokploy API → migrate → health → smoke. - Produção via promoção manual da mesma digest SHA (alias `:production`), sem rebuild. - Backup PostgreSQL diário (retenção ≥14 dias), restore documentado e rollback por SHA. - Compose local com app FrankenPHP + Postgres (pendente fora da fatia deploy). - PHP 8.4 canônico em docs/Docker/CI (pendente). - Gates com `npm audit` e cobertura Domain/Application ≥ 80% (pendente). - E-mail verificado + reset de senha seguros no painel (ADM-01 / SPEC §12.1) (pendente). - Strict types em PHP próprio faltante (pendente). **Non-Goals:** - Deploy automático em produção; provisionamento de VPS para clientes. - Fase 2 (briefing/CRM/E2E-01/02). - Redis, worker mode, CDN automation, signed private media. ## Decisions ### D1 — Dokploy Compose na VPS, imagem imutável no GHCR GitHub Actions (após CI verde em `main`) constrói **uma** imagem `ghcr.io//:` (+ alias `:staging`), faz push privado e chama `POST /api/compose.deploy` no Dokploy com `x-api-key`. Compose compartilhado (`docker-compose.deploy.yml`) referencia `${APP_IMAGE}` / `IMAGE_TAG` para `web`, `queue`, `scheduler` e job `migrate` one-shot (`php artisan migrate --force`). Dokploy mantém **duas** stacks Compose (staging e production) com `IMAGE_TAG` distinto. PostgreSQL é serviço Dokploy separado por ambiente (não na imagem da app). Produção: `workflow_dispatch` com SHA + confirmação `PRODUCTION` move alias `:production` para o **mesmo digest** já publicado e dispara `compose.deploy` na stack de produção. Repo privado no GitHub Free não tem required reviewers de Environment; aprovação humana = disparo manual explícito (GitHub Pro opcional depois). *Alternativas rejeitadas:* Railway (Pro para GHCR privado + desvio de plataforma); rebuild por serviço no Dokploy (quebra “mesma imagem” SPEC §14.3/§15.2); tag só `:latest` (rollback frágil); deploy automático direto em produção. ### D2 — Processos e health | Serviço | Comando | |---|---| | web | entrypoint padrão FrankenPHP (`CMD` da imagem) | | queue | `php artisan queue:work --sleep=2 --tries=3` | | scheduler | `php artisan schedule:work` | | migrate | one-shot antes/ao lado do deploy | Healthcheck Docker/Dokploy e smoke pós-deploy usam `GET /up` (sem auth, sem secrets). Smoke mínimo: `/up`, home pública `/`, `/admin/login` respondem 200. ### D3 — Secrets e providers GitHub Actions guarda só orquestração: `DOKPLOY_URL`, API key, compose IDs, URLs públicas de smoke. Env Laravel (`APP_KEY`, DB, Resend, R2) vive **somente** no Dokploy, isolado por ambiente. Nunca embeds em layer. Local continua `MAIL_MAILER=log` e disco `public`. ### D3b — Backup e restore PostgreSQL staging/produção: backup diário via Dokploy → destino S3-compatible, retenção mínima 14 dias (SPEC §16.3). Restore documentado e testado em staging antes da primeira promoção a produção. ### D4 — Compose local com app Adicionar serviço `app` (build do `Dockerfile`) dependendo de `postgres` healthy, porta 8000, volumes só para storage local se útil. Documentar `docker compose up` como caminho preferido; `artisan serve` permanece opcional para iteração rápida sem rebuild. ### D5 — PHP 8.4 canônico Alinhar README, CI (`setup-php` 8.4) e `Dockerfile` `ARG PHP_VERSION=8.4`. Manter `composer.json` `^8.3` (compatibilidade de instalação), mas documentação e runtime canônicos = 8.4. ### D6 — Quality gates - `composer quality` / job `static`: adicionar `npm audit --omit=dev` (ou política documentada equivalente) após `npm ci` onde assets forem necessários; falha bloqueia merge. - Jobs `unit` (e, se aplicável, coverage dedicada): habilitar cobertura e falhar se Domain+Application < 80%. Escopo limitado a `app/Domain` e `app/Application` (SPEC §13.7). Views/migrations/framework fora. ### D7 — Auth parity - `User` implementa `MustVerifyEmail`; `canAccessPanel` exige ativo **e** e-mail verificado. - Seed local marca admin/assistant como verificados. - Habilitar fluxo de reset Filament/Laravel; feature tests: unverified denied, verified ok, reset request não revela existência de e-mail. - Correção pontual de `declare(strict_types=1);` em providers/arquivos próprios faltantes. ### D8 — Rollback Rollback = mover alias do ambiente (`:staging` ou `:production`) para tag SHA anterior no GHCR e `compose.deploy`. Falha de healthcheck/smoke impede promoção. Sem rebuild. ### D9 — Trusted proxies atrás do Traefik `bootstrap/app.php` confia em proxies (`trustProxies(at: '*')`) para honrar `X-Forwarded-*` do Traefik/Dokploy. Staging/produção usam `SESSION_SECURE_COOKIE=true` com HTTPS. ## Risks / Trade-offs - **[GHCR privado + pull na VPS]** → configurar registry no Dokploy com PAT `read:packages`; documentar checklist. - **[Migrate one-shot falha]** → web/queue/scheduler dependem de migrate exit 0; manter migrations backward-compatible. - **[GitHub Free sem required reviewers]** → promoção humana via `workflow_dispatch` + input `PRODUCTION`; Pro opcional. - **[Cobertura 80% com Domain quase vazio]** → medir só namespaces existentes; baseline sobe conforme Fase 2 adiciona Domain (pendente). - **[npm audit ruido]** → `--omit=dev` + allowlist documentada se necessário (pendente). - **[E-mail verification em staging]** → seed/users de staging com `email_verified_at`; Resend para reset real quando configurado (pendente). - **[Compose local rebuild lento]** → documentar serve opcional; CI permanece fonte FrankenPHP (pendente). ## Migration Plan 1. Fatia deploy: Compose deploy, workflows, smoke, trusted proxies, docs Dokploy/backup/rollback; CI verde. 2. Criar projeto Dokploy + Postgres (staging + produção) + Compose apps; registrar GHCR. 3. Primeiro push de imagem SHA → staging; smoke `/up` + home + login; testar rollback e backup/restore. 4. Promoção manual para produção após domínio/TLS/`APP_URL` confirmados. 5. Fatias restantes da change (auth/coverage/npm/Compose local/PHP docs) em PRs seguintes. 6. Atualizar SPEC §18 Fase 0 apenas com itens comprovados; evidência no PR. ## Open Questions - Domínio público exato do staging/produção (DNS) — preencher na implementação com valor do operador. - Se Dokploy Compose API exigir `compose.update` env para `IMAGE_TAG` a cada deploy: preferir aliases `:staging`/`:production` estáveis no Compose Dokploy para evitar rewrite de env.