Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ jobs:
node-version: 20
cache: npm

# Fail-fast: este guard usa só git+node nativo, sem deps. Roda antes de
# `npm ci` para abortar PRs que reintroduzem o comando proibido em
# segundos em vez de minutos.
- name: Migrations sync guard (desync 332 vs 209 — ver docs/DEPLOYMENT.md)
run: node scripts/check-no-db-push.mjs

- name: Install dependencies
run: npm ci

Expand Down
161 changes: 134 additions & 27 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,147 @@
# Deployment Guide
# Deployment Guide — Promo_Gifts

## Prerequisites
- Node.js 18+
- PostgreSQL 14+
- Redis 7+
> **Última revisão:** 2026-05-12 (pós-redeploy Fase 2, T3 migrations audit)
> **Status do redeploy:** ver `docs/redeploy/REDEPLOY-FASE2-EXECUTION-LOG.md`

## Steps
---

1. Install dependencies:
```bash
npm install
```
## ⚠️ Avisos críticos antes de qualquer deploy

2. Configure environment:
```bash
cp .env.example .env
```
### 1. NÃO use `supabase db push`

O diretório `supabase/migrations/` tem **332 arquivos**; o banco de produção registra **209 migrations**. **Interseção: zero.** As duas histórias divergiram há meses (várias ferramentas — Lovable, SQL Editor, scripts ad-hoc — gravaram em momentos diferentes).

**Consequência prática:**

3. Run migrations:
```bash
npx supabase db push
# ❌ NUNCA rode isso:
supabase db push --project-ref doufsxqlfjyuvxuezpln
# Vai tentar aplicar 332 migrations num banco que já tem schema diferente
# → conflitos em cascata → corrupção potencial
```

4. Build:
```bash
npm run build
**Fonte da verdade do schema é o BANCO em produção**, não o repo.

Detalhes completos: [`docs/redeploy/REDEPLOY-T3-MIGRATIONS-AUDIT.md`](redeploy/REDEPLOY-T3-MIGRATIONS-AUDIT.md).

### 2. Como aplicar mudanças de schema corretamente

Para qualquer DDL nova:

**Opção A (recomendada) — MCP do Supabase:**
- Ferramenta `apply_migration` aplica direto e registra em `supabase_migrations.schema_migrations`.

**Opção B — Dashboard:**
- SQL Editor → cole o SQL → Run. Funciona, mas não registra na history.

**Opção C — Branching de banco:**
- Criar branch dev no Supabase, validar, depois `merge_branch` para prod.

Cada novo arquivo `supabase/migrations/*.sql` no repo serve como **registro histórico** da intenção; ele NÃO é re-aplicado em prod.

#### ⚠️ Exceção: policies em `storage.objects`

Operações em `storage.objects` (RLS policies, alterações de schema) **não funcionam via MCP/`apply_migration`**. A tabela pertence ao role `supabase_storage_admin` e o role acessível por MCP (`postgres`) não é membro. Confirmação em `docs/storage/PUBLIC_BUCKETS.md` com 3 tentativas registradas.

Para essas mudanças use:

1. **Dashboard Supabase** → Storage → Policies → New policy (caminho oficial; passa pelo role correto internamente)
2. Adicione um arquivo `supabase/migrations/<timestamp>_descritivo.sql` no repo **só como registro documental** (com comentário no topo: `-- APLICADO MANUALMENTE VIA DASHBOARD em <data>; não re-aplicar`)
3. Não use `merge_branch` para esse arquivo — manter `supabase/migrations/` em sync continua sendo apenas histórico, conforme política da seção #1 deste doc

---

## Arquitetura de deploy

Push para `main` no GitHub dispara **dois deploys independentes**:

```text
┌─────────────────────────────┐
│ push origin main │
└────────────┬────────────────┘
┌─────────┴──────────┐
│ │
▼ ▼
Lovable Cloud Vercel
│ │
▼ ▼
promogifts.com.br promo-gifts-beta.vercel.app
[PRODUÇÃO] [STAGING/PREVIEW]
```

5. Deploy:
- **Produção real:** `promogifts.com.br` (Lovable, custom domain)
- **Staging gratuito:** `*.vercel.app` (Vercel project `prj_lfv6J41d3UY4YhcGE4y1aJo8T339`)
- **Rollback rápido:** se Lovable falhar, apontar DNS de `promogifts.com.br` para a Vercel

Cobertura completa em [`docs/redeploy/REDEPLOY-T2.5-FOLLOWUP.md`](redeploy/REDEPLOY-T2.5-FOLLOWUP.md).

---

## Prerequisites locais

- Node.js 18+ (recomendado: 20 LTS, que CI usa)
- npm 10+
- Conta Supabase com acesso ao projeto `doufsxqlfjyuvxuezpln`

**Não usa Redis.** O projeto não tem camada de cache externa — cache de UI fica a cargo do TanStack Query (`@tanstack/react-query`) no cliente.

---

## Build local

```bash
npm run deploy
npm install
npm run build # vite build → dist/
npm run preview # serve dist em http://localhost:4173
```

## Production Checklist
- [ ] Environment variables configured
- [ ] Database migrated
- [ ] SSL certificates installed
- [ ] CDN configured
- [ ] Monitoring enabled
---

## Variáveis de ambiente

Veja `.env.e2e.example` para o conjunto necessário em CI.

Para frontend:
- `VITE_SUPABASE_URL`
- `VITE_SUPABASE_PUBLISHABLE_KEY`

Para edge functions e backend (configuradas no Supabase Dashboard, não no repo):
- ver inventário em `recovery/block19_secrets_inventory.md` e `recovery/block22_edge_secrets_inventory.md`

---

## Production Checklist (pré-redeploy)

- [ ] Branch protection ativa em `main` (ver `docs/BRANCH_PROTECTION.md`)
- [ ] Dependabot Security Alerts + Secret Scanning + Push Protection ativos (ver `docs/SECURITY_ALERTS.md`)
- [ ] Advisor de segurança Supabase com 0 ERRORs (ver `mcp__get_advisors`)
- [ ] Buckets públicos zerados (ver `docs/storage/PUBLIC_BUCKETS.md`)
- [ ] Policy `recibos_authenticated_read` criada em `storage.objects`
- [ ] CI verde no commit que vai entrar em prod
- [ ] Smoke E2E passando em `promogifts.com.br` após deploy

---

## Rollback

Se um deploy quebrar produção:

1. **Frontend:** reverter o commit em `main` via `git revert` + push → Lovable redeploys
2. **Schema (Postgres):** restaurar via Supabase point-in-time recovery (PITR) — dashboard → Database → Backups
3. **Storage (arquivos):** ⚠️ **PITR NÃO recupera arquivos do Storage** — restaura apenas a tabela `storage.objects` (metadados). Os objetos físicos ficam num backend S3-compatible separado, fora do escopo do backup. Estratégia atual:
- Buckets em uso (`recibos-entrega`, `scripts`) **não têm versionamento ativo**
- Para incidentes: tentar reconciliação manual via `storage.objects` metadata + backup externo (se existir)
- **Recomendação P2 para Fase 3:** habilitar versionamento de bucket OU job periódico de cópia para R2/S3 externo. Tracking em issue própria a abrir
4. **Edge functions:** redeploy do commit anterior via MCP `deploy_edge_function` (preferido) ou `supabase functions deploy` se tiver CLI local

---

## Referências cruzadas

- `docs/redeploy/REDEPLOY-FASE2-EXECUTION-LOG.md` — fase atual do redeploy
- `docs/redeploy/REDEPLOY-T3-MIGRATIONS-AUDIT.md` — detalhe do desync de migrations
- `docs/redeploy/REDEPLOY-T2.5-FOLLOWUP.md` — arquitetura Lovable + Vercel
- `docs/BRANCH_PROTECTION.md` — política de proteção de branch
- `docs/SECURITY_ALERTS.md` — Dependabot + CodeQL + Secret Scanning
- `docs/storage/PUBLIC_BUCKETS.md` — política de buckets
195 changes: 195 additions & 0 deletions docs/redeploy/REDEPLOY-FASE2-CHECKLIST-UI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Redeploy Fase 2 — Checklist UI Final

> **Para quem:** Joaquim (único maintainer, não-dev)
> **Tempo total:** ~10 minutos
> **Objetivo:** fechar as 3 pendências da Fase 2 que **só** funcionam via UI (limitação técnica documentada, não preguiça).
> **Quando rodar:** quando a PR consolidada do Claude (que adiciona o CI guard e atualiza docs) estiver mergeada em `main`.

---

## ⏱ Resumo de cliques

| Passo | UI | Cliques | Tempo |
|---|---|---|---|
| 1 — Policy de leitura do bucket recibos | Supabase Dashboard | ~7 | 2 min |
| 2 — Dependabot Alerts + Secret Scanning | GitHub Settings → Security | ~5 toggles | 2 min |
| 3 — Branch Protection em `main` | GitHub Settings → Branches | ~10 checkboxes | 5 min |

Total: ~10 min. Faça **na ordem** abaixo (alguns passos dependem do anterior).

---

## Passo 1 — Storage policy `recibos_authenticated_read`

### Por que é manual

Tentei criar via MCP/SQL 3 vezes e Supabase bloqueia (`42501: must be owner of relation objects`). A tabela `storage.objects` pertence ao role `supabase_storage_admin` e nenhum role acessível via MCP é membro dele. Detalhes em `docs/storage/PUBLIC_BUCKETS.md`.

As outras 2 policies do mesmo bucket (`_write`, `_update`) também foram criadas via dashboard antes — então o caminho é conhecido.

### Cliques exatos

1. Abra: **<https://supabase.com/dashboard/project/doufsxqlfjyuvxuezpln/storage/policies>**
2. Procure a seção/tabela **`objects`** (schema **`storage`**)
3. Clique no botão **`+ New policy`** (canto direito do bloco `storage.objects`)
4. Escolha o template **`For full customization`** (não use os templates pré-prontos — eles adicionam cláusulas que não queremos)
5. Preencha o formulário **exatamente assim**:

| Campo | Valor |
|---|---|
| Policy name | `recibos_authenticated_read` |
| Allowed operation | ☑ SELECT (deixar UPDATE/INSERT/DELETE desmarcados) |
| Target roles | `authenticated` (remover `public` se vier marcado) |
| USING expression | `bucket_id = 'recibos-entrega'` |
| WITH CHECK expression | (deixar vazio) |

6. Clique **`Review`** → confira o SQL gerado deve ser equivalente a:
```sql
CREATE POLICY "recibos_authenticated_read" ON storage.objects
FOR SELECT TO authenticated USING (bucket_id = 'recibos-entrega');
```
7. Clique **`Save policy`**

### Validação

Se quiser confirmar (opcional), no SQL Editor:

```sql
SELECT policyname, cmd, roles::text
FROM pg_policies
WHERE schemaname='storage' AND tablename='objects' AND policyname LIKE 'recibos%';
```

Esperado: **3 linhas** (`_read`, `_write`, `_update`).

---

## Passo 2 — Security Analysis (issue #80)

### Cliques exatos

1. Abra: **<https://github.com/adm01-debug/Promo_Gifts/settings/security_analysis>**
2. Ative na ordem (clique no botão `Enable` de cada um):
- ☑ **Dependency graph** (provável que já esteja on)
- ☑ **Dependabot alerts**
- ☑ **Dependabot security updates**
- ☑ **Secret scanning**
- ☑ **Push protection** (esse é o crítico — bloqueia secret ANTES do push subir)

Não precisa configurar nada além disso. Os toggles são instantâneos.

### Validação

- <https://github.com/adm01-debug/Promo_Gifts/security/dependabot> — deve responder com listagem (vazia ou populada — qualquer dos dois é OK)
- Teste push protection (opcional — pode pular):

> ⚠️ **Use SEMPRE uma branch descartável**, nunca `main`. Alguns padrões de secret só são detectados quando ID + secret aparecem **no mesmo arquivo** (GitHub doc oficial). Se rodar o teste sem esse cuidado e o GitHub não bloquear, o "secret" fictício acaba no remoto e `git reset HEAD~1` só limpa local.

```bash
# Cria branch descartável a partir de main:
git checkout -b test/secret-scan-validation

# Par AWS Access Key ID + Secret no MESMO arquivo (padrão genuinamente detectado):
cat > fake-secret.txt <<'EOF'
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
EOF
git add -f fake-secret.txt && git commit -m "test: secret scanning push protection"
git push -u origin test/secret-scan-validation

# Esperado: "remote: error: GH013: Repository rule violations found ... push declined due to a detected secret"

# Limpeza local + remoto (se push tiver passado por acidente, deleta a branch remota junto):
git checkout main
git branch -D test/secret-scan-validation
git push origin --delete test/secret-scan-validation 2>/dev/null || true
rm -f fake-secret.txt
```

### Fechar issue

Após ativar, vá em <https://github.com/adm01-debug/Promo_Gifts/issues/80> e clique `Close issue` com comentário tipo: *"Ativado em 2026-05-12. Dependabot + Secret Scanning + Push Protection on."*

---

## Passo 3 — Branch Protection em `main` (issue #78)

### ⚠️ Nuance importante para você (único maintainer)

A doc original da Fase 2 pediu `Require approvals = 1`. **Se você ativar isso sendo único maintainer, NENHUMA PR sua vai conseguir mergar** (GitHub não deixa você aprovar a própria PR). Duas opções:

- **Opção A (recomendada para você agora):** `Require approvals = 0` mas mantém **todas as outras travas** (status checks obrigatórios, sem force push, sem delete, conversation resolved). Isso bloqueia push direto e exige CI verde, sem exigir 2ª pessoa.
- **Opção B:** Manter `= 1` mas habilitar **"Allow specified actors to bypass required pull requests"** e adicionar você mesmo como bypass. Menos rigoroso.

A instrução abaixo segue a Opção A.

### Cliques exatos

1. Abra: **<https://github.com/adm01-debug/Promo_Gifts/settings/branches>**
2. Clique **`Add branch ruleset`** (ou `Add classic branch protection rule` se a UI ainda mostrar isso)
3. **Ruleset name**: `main-protection`
4. **Enforcement status**: `Active`
5. **Target branches** → `Add target` → `Include default branch`
6. Em **Branch protections**, marcar:

| Toggle | Estado | Configuração detalhada |
|---|---|---|
| Restrict deletions | ✅ ON | — |
| Block force pushes | ✅ ON | — |
| Require linear history | ⬜ OFF (opcional) | — |
| Require a pull request before merging | ✅ ON | **Required approvals: 0** · Dismiss stale ✅ · Require conversation resolution ✅ |
| Require status checks to pass | ✅ ON | Require branches to be up to date ✅. **Add checks** abaixo |
| Block creations | ⬜ OFF | — |
| Require deployments to succeed | ⬜ OFF | — |
| Require code scanning results | ⬜ OFF por ora | Pode ligar depois quando CodeQL tiver baseline |

7. Em **Require status checks**, adicione **apenas os checks que rodam em PR** (digite cada um — autocomplete vai sugerir após primeiro run em uma PR):
- `Gitleaks — Secret Scan` (workflow `security.yml`, roda em `pull_request` + `push`)
- `Smoke tests (rotas + health-check)` (workflow `ci.yml`, job `smoke`, roda em `pull_request` + `push`)
- `Lint, Typecheck & Test` (workflow `ci.yml`, job `quality`, roda em `pull_request` + `push`)
- `Analyze (javascript-typescript)` (workflow `codeql.yml`, job `analyze` com matrix `language: javascript-typescript`, roda em `pull_request` + `push`. **Não use** "CodeQL" — esse é o nome do workflow, não do check. O check publica com o nome do job expandido pela matrix.)

> ⚠️ **NÃO adicione** `Verify push to main is from PR merge` (workflow `branch-protection-sentinel.yml`) como required check. Esse workflow tem trigger `push:` apenas — não roda em PR. Se for marcado como required, **todas as PRs ficam presas esperando um check que nunca aparece no head da PR**. O sentinel continua executando pós-merge para auditar o padrão; não precisa ser gate de PR.
>
> Se algum dos 4 checks acima não aparecer no autocomplete, é porque o workflow ainda não rodou em nenhuma PR — abra/atualize qualquer PR pequena para disparar 1×, depois adicione.

8. Em **Bypass list**: deixar vazio (não dê bypass nem para admin — assim mantém disciplina).
9. **Create** (botão no topo).

### Validação

```bash
# Tentar push direto — deve falhar:
git checkout main && git pull
git commit --allow-empty -m "test direct push"
git push origin main
# Esperado: ! [remote rejected] main -> main (protected branch hook declined)
git reset HEAD~1
```

### Fechar issue

Após validar, vá em <https://github.com/adm01-debug/Promo_Gifts/issues/78> e clique `Close issue` com comentário: *"Branch protection ativa em main com 5 required checks. Validado em 2026-05-12."*

---

## Após os 3 passos

Avise o Claude (este chat ou próximo). Ele vai:

1. Re-rodar os advisors do Supabase
2. Confirmar a 3ª policy criada
3. Verificar issues #78 e #80 fechadas
4. Atualizar `docs/redeploy/REDEPLOY-FASE2-EXECUTION-LOG.md` para marcar a Fase 2 como **100% concluída**
5. Propor início da Fase 3 (T24-T30): E2E coverage, CI estabilidade, observability, qualidade, docs finais

---

## Troubleshooting

| Sintoma | Causa provável | Solução |
|---|---|---|
| Não vejo botão `+ New policy` no Supabase | Estou no projeto errado | URL deve ter `doufsxqlfjyuvxuezpln` |
| Required check não aparece no autocomplete do GitHub | Workflow nunca rodou na branch ainda | Crie qualquer PR pequena para disparar; depois adicione |
| Push em main funciona mesmo após branch protection | Você é admin e bypass está ativo | Confirme Bypass list vazia |
| Issue #78 já está fechada | Alguém marcou antes da validação real | Reabrir; validar com um `git push` direto e simples (sem `--force`) — a rejeição "protected branch hook declined" já comprova proteção ativa; depois fechar com evidência |
Loading
Loading