Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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**:

```
┌─────────────────────────────┐
│ push origin main │
└────────────┬────────────────┘
┌─────────┴──────────┐
│ │
▼ ▼
Lovable Cloud Vercel
│ │
▼ ▼
promogifts.com.br promo-gifts-beta.vercel.app
[PRODUÇÃO] [STAGING/PREVIEW]
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

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.** Cache no-op se a env var não estiver definida (`src/lib/cache.ts`).

---

## 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
179 changes: 179 additions & 0 deletions docs/redeploy/REDEPLOY-FASE2-CHECKLIST-UI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# 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):
```bash
# ATENÇÃO: rodar DENTRO do diretório do repo (não em /tmp)
echo "AKIAIOSFODNN7EXAMPLE" > fake-secret.txt
git add -f fake-secret.txt && git commit -m "test secret"
git push

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid pushing the dummy secret test to main

When this optional validation is run before branch protection is configured, the push can succeed instead of producing GH013: GitHub documents that pattern pairs such as AWS keys require both the ID and secret in the same file, but this writes only the access-key ID. In that case the maintainer has just pushed fake-secret.txt to main, and the following git reset HEAD~1 only cleans the local branch, not the remote; use a disposable branch/repo or include a safe provider-documented test token that is guaranteed to be blocked.

Useful? React with 👍 / 👎.

# Esperado: erro "GH013: Repository rule violations found - push declined"
git reset HEAD~1 && rm 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 (digite cada um — autocomplete vai sugerir após primeiro run de cada workflow):
- `Verify push to main is from PR merge` (workflow `branch-protection-sentinel.yml`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove the push-only sentinel from required PR checks

This checklist tells the maintainer to require Verify push to main is from PR merge, but I checked .github/workflows/branch-protection-sentinel.yml and that workflow only runs on push to main (plus workflow_dispatch), not on pull_request. If this is added as a required status check for main, PR heads will never produce that check, so normal PR merges will be stuck waiting for a missing status.

Useful? React with 👍 / 👎.

- `Gitleaks — Secret Scan` (workflow `security.yml`)
- `Smoke tests (rotas + health-check)` (workflow `ci.yml`, job `smoke`)
- `Lint, Typecheck & Test` (workflow `ci.yml`, job `quality`)
- `CodeQL` (workflow `codeql.yml`)

> **Se algum nome não aparecer no autocomplete**, é porque o workflow ainda não rodou nessa branch — pode digitar manual. Mas idealmente espere que rode 1× para evitar typo.

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