docs(OPS21): REVALIDATE_SECRET na Vercel production — runbook /api/revalidate restaurado
This commit is contained in:
parent
733b019f2b
commit
e54ed44fd7
2 changed files with 78 additions and 0 deletions
|
|
@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
---
|
||||
|
||||
**Recently resolved (2026-08-10):** **OPS21** (Issue #538) — `REVALIDATE_SECRET` adicionada na **Vercel production** (projeto `solla/jorgesolla`): diagnóstico D6 confirmado (env ausente em `vercel env ls production`; `POST /api/revalidate` respondia `500` "not configured" em prod, quebrando o runbook pós-seed/mudança direta de DB). Segredo gerado com `openssl rand -hex 32` (valor via stdin, nunca argv), `vercel env add REVALIDATE_SECRET production`; **fallback necessário:** o deploy existente congelava as envs em runtime → `vercel redeploy` do último deploy de produção (2×, pois o primeiro `env add` via pipe herdou o `\n` do `openssl` e gravou valor de 66 chars — removido e re-adicionado limpo). Validação real em prod: sem secret → `401`; secret errado → `401`; secret certo → `200 { revalidated: true, tag: 'posts' }`; tag desconhecida → `400`. Preview não adicionada (projeto sem Git conectado — `vercel env add ... preview` exige branch, precedente D6). Sem código/migration/access/UI. Impl [`revalidate-secret-vercel-production-impl.md`](plans/revalidate-secret-vercel-production-impl.md).
|
||||
|
||||
**Recently resolved (2026-08-10):** **B184** (Issue #514) — no mobile (<768px), o filtro de listas `/campanha` virou o **padrão sem moldura do chassis `CampaignListOmnibox` (B127) — todas as listas**: label oculta (`input` carrega o nome via `aria-label`), campo sem borda/sombra (`md:` restaura o desktop), **form do filtro sticky sob a top bar** com linha separadora edge-to-edge, "Limpar" vira **X circular dentro do input** (aparece com chips OU busca digitada; slot sempre renderizado com `invisible` para a largura não pular; hit target 44px + círculo visual 28px; live region própria "Busca e filtros limpos." — limpar só busca não navega e o pending region ficaria mudo para AT), botão texto "Limpar" desktop-only. **Por que sticky no `<form>`:** sticky não escapa do containing block (o pai) — a coluna do input dentro do wrapper curto do omnibox "grudava" ~12px e rolava junto (medido); o form é o único wrapper filho do `CampaignPageShell` (altura total). **Exceção agenda (C101, merge anterior):** a `/campanha/agenda` já tinha o strip sticky próprio (`activity-agenda-filter-strip`) — mantido; o chassis ainda a cobre no campo/X/Limpar. Em municípios, adicionalmente: **"Salvar filtro" vira icon button no header mobile** (slot C94/C95, `SaveMunicipalityFilterControl presentation='icon'`; popover de nomear intacto; nome de rename é o contrato 2.5.3) e **cards edge-to-edge sem borda** com uma linha separando (`border-b last:border-b-0`, `-mx-4`, gate `md:`). "Nova atualização" (atualizações) — único trailing visível no mobile — foi para o header (mesmo padrão); `campaignMobileHeaderIconClassName` extraída (3º consumidor). E2E novo em `campaignMunicipalities.e2e.spec.ts` (390×844): label oculta, borda 0, sticky, X (limpa chips+busca, foco volta sem reabrir popover), salvar→renomear no header, cards com separador, desktop invertido (1280). Débito registrado no caminho: **D7** (sticky dos headers da tabela desktop morto — wrapper `overflow-x-auto` é o scroll container; medido). Sem migration, access, Consent. Intenção [`municipios-mobile-sem-moldura.md`](plans/municipios-mobile-sem-moldura.md); impl [`municipios-mobile-sem-moldura-impl.md`](plans/municipios-mobile-sem-moldura-impl.md).
|
||||
|
||||
**Recently resolved (2026-08-09):** **C103** (Issue #504) — o formulário de criação rápida da agenda ficou **usável de ponta a ponta no celular**: no narrow (mesmo breakpoint 640px do calendário), o sheet de criação abre **do topo** (`swipeDirection="up"` na primitiva `Drawer` — que já suportava top sheet; zero mudança em `Drawer.tsx`) preenchendo `100dvh-1rem`, o conteúdo **rola** num scroll container e o rodapé ("Mais detalhes" + "Salvar") é **fixo** — verificado em smoke com teclado aberto (viewport 280px: sheet encolhe via `dvh`, conteúdo rola 187/187px e o Salvar permanece visível). O form ganhou `variant: 'popover' | 'sheet'`: no sheet, **labels viram `sr-only`** (a11y e `getByLabelText` intactos), placeholders dão contexto ("Adicionar título _", "Município _", "Local (opcional)", "Adicionar responsáveis"), obrigatórios marcados com asterisco (título/município no placeholder, Início no trigger), campos empilhados **sem gaps com `divide-y`** e inputs/triggers sem borda (lista iOS do canvas). O **seletor de data/hora abre como bottom sheet aninhado** no mobile (`ActivityDateTimeField` ganhou `isNarrow`/`label`/`required`: conteúdo compartilhado Calendar+selects renderizado em `Drawer` com header "Início — <data>" + "Pronto"; desktop continua popover). Props backward-compatible: `placeholder`/`className` no `StrictCombobox`, `labelClassName`/`emptyText`/`triggerClassName` no `ResponsibleMultiSelect`. Decisão do smoke: swipe handle **removido** do top sheet (primitiva o posiciona no rodapé, contradizendo o gesto de swipe-up; continua nos bottom sheets). E2E mobile novo (viewport 390×844): sheet do topo (`y ≤ 1`), Salvar visível sem scroll, picker em bottom sheet com Pronto, evento aparece após salvar; unit narrow cobre placeholders/asterisco/Pronto. Desktop visualmente inalterado (B181 segue na fila para o clip do popover ≤720px). Sem migration/access/Consent/schema. Intenção [`agenda-mobile-form-criacao-usavel.md`](plans/agenda-mobile-form-criacao-usavel.md); impl [`agenda-mobile-form-criacao-usavel-impl.md`](plans/agenda-mobile-form-criacao-usavel-impl.md).
|
||||
|
|
|
|||
76
docs/plans/revalidate-secret-vercel-production-impl.md
Normal file
76
docs/plans/revalidate-secret-vercel-production-impl.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# Impl: OPS21 — Adicionar REVALIDATE_SECRET na Vercel production (runbook /api/revalidate)
|
||||
|
||||
Status: aprovado
|
||||
Atualizado em: 2026-08-10
|
||||
Issue: #538
|
||||
Intenção: body da Issue #538 (ops pura, sem plano de intenção separado)
|
||||
Appetite restante: herdado (ops ~30min + passos de validação)
|
||||
|
||||
## Leitura da intenção
|
||||
|
||||
- **Outcome:** `POST /api/revalidate` responde `200` em produção com o secret correto (hoje `500`), e o runbook pós-seed / mudança direta de DB (AGENTS.md "Posts & Tags") volta a funcionar: bust do cache `posts` / `election-tse` / `global_privacy-policy` / `municipality-catalog` em produção.
|
||||
- **O que NÃO negociar:** o secret nunca é impresso/logado/commitado; `vercel env add` só no projeto correto (`solla/jorgesolla`); nenhum código muda (issue diz "Sem código").
|
||||
- **O que reavaliar:** "documentar no Vercel" — não há campo de descrição em `vercel env`; documentação = registrar a entrega no `docs/CHANGELOG-AGENTS.md` (padrão do repo) e deixar a env visível em `vercel env ls production`. Preview é **opcional** e o precedente D6 mostra que `vercel env add ... preview` exige branch (`branch_not_found`) porque o projeto não tem Git conectado (deploys CLI).
|
||||
|
||||
## Diagnóstico (evidência, 2026-08-10)
|
||||
|
||||
| Elo | Estado | Evidência |
|
||||
| --- | ------ | --------- |
|
||||
| 1. Env `REVALIDATE_SECRET` (Vercel prod) | **AUSENTE** | `vercel env ls production` (projeto `solla/jorgesolla`, link em `/home/fsolla/Code/teqo/.vercel`): não listada |
|
||||
| 2. Endpoint prod | **500** | `curl -X POST https://pt.jorgesolla.com.br/api/revalidate -H "x-revalidate-secret: <errado>"` → `HTTP 500` (corpo: `REVALIDATE_SECRET is not configured` — route.ts:54-58) |
|
||||
| 3. Comportamento esperado pós-fix | 401/200 | secret errado → `401`; secret certo → `200 { revalidated: true }` |
|
||||
|
||||
## Abordagem recomendada
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Gerar secret<br/>openssl rand -hex 32"] --> B["vercel env add REVALIDATE_SECRET production"]
|
||||
B --> C["preview: tentar; fallback D6<br/>(exige branch — sem Git conectado)"]
|
||||
B --> D["curl validação:<br/>sem secret → 401<br/>secret errado → 401<br/>secret certo → 200"]
|
||||
D --> E["docs: impl plan +<br/>CHANGELOG-AGENTS entry"]
|
||||
E --> F["gate:fast → pnpm push →<br/>PR Closes #538 → merge"]
|
||||
```
|
||||
|
||||
**Opções consideradas:** A) gerar com `openssl rand -hex 32` | B) `openssl rand -base64 32` | C) segredo legível digitado manualmente
|
||||
**Recomendação:** A — 64 chars hex, compatível com o header HTTP sem encoding, documentado em `.env.example` (`Generate with e.g. openssl rand -hex 32`).
|
||||
**Rejeitadas:** B porque `+` `/` `=` do base64 pedem encoding cuidadoso em headers (funcionaria, mas sem ganho); C porque segredo de baixa entropia é fraco contra brute-force do endpoint público.
|
||||
|
||||
**Opções (environments):** A) só production | B) production + preview
|
||||
**Recomendação:** A — production é o que o runbook usa; preview não tem deploy ativo de código (sem Git conectado, deploys CLI) e `vercel env add ... preview` exige branch (precedente D6: `branch_not_found`). Tentativa opcional de preview documentada como falha esperada, não como passo obrigatório.
|
||||
**Rejeitadas:** B como obrigatório — ver acima.
|
||||
|
||||
**Opções (validação):** A) curl no endpoint prod real | B) `vercel redeploy` + curl
|
||||
**Recomendação:** A — env vars runtime (não `NEXT_PUBLIC_*`) são lidas em `process.env` a cada invocação; se após `env add` o endpoint ainda responder `500`, disparar `vercel redeploy <latest prod url> --prod` e revalidar (fallback documentado, não passo padrão).
|
||||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **Código:** nenhum (issue "Sem código" — route.ts, `.env.example` e AGENTS.md já documentam `REVALIDATE_SECRET`).
|
||||
- **Vercel ops:** `vercel env add REVALIDATE_SECRET production` (projeto `solla/jorgesolla`, link `/home/fsolla/Code/teqo/.vercel`), valor via stdin (não em argv — histórico de shell).
|
||||
- **Docs:** `docs/plans/revalidate-secret-vercel-production-impl.md` (este) + entrada curta em `docs/CHANGELOG-AGENTS.md` (padrão "Recently resolved").
|
||||
- **Migration:** nenhuma. **Access / Consent:** N/A. **UI:** N/A (Impeccable A).
|
||||
|
||||
## Fases verificáveis
|
||||
|
||||
1. **Ops** — gerar secret, `vercel env add` production, tentativa preview (esperado falhar).
|
||||
2. **Validação prod** — curl: secret ausente → `401`; secret errado → `401`; secret certo → `200 { revalidated: true, tag: 'posts' }`; tag inválida → `400`. Fallback: `vercel redeploy` se `500` persistir.
|
||||
3. **Docs + gates** — CHANGELOG-AGENTS entry; `pnpm gate:fast`; `pnpm push -u origin HEAD`; PR `--base main` com `Closes #538`; merge + `gh pr checks --watch --required`.
|
||||
|
||||
## Rabbit holes / Não escopo (engenharia)
|
||||
|
||||
- Rotacionar `PAYLOAD_SECRET` ou outras envs (fora do escopo).
|
||||
- Codificar o valor do secret em qualquer arquivo do repo (nunca).
|
||||
- Adicionar descrição/suporte de env no Vercel (não existe campo).
|
||||
- Mudar o endpoint `/api/revalidate` (funciona; só falta a env).
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- **Env adicionada no projeto errado:** `vercel` CLI já logado (`franciscosolla`), projeto verificado via `vercel env ls production` antes e depois (nome `solla/jorgesolla`).
|
||||
- **Segredo vazado em shell history:** valor passado via stdin (`vercel env add` aceita stdin), nunca em argv.
|
||||
- **Endpoint continua 500 após env add:** fallback `vercel redeploy <url> --prod` (env runtime pode estar congelada no deploy existente); revalidar.
|
||||
- **Preview impossível (`branch_not_found`):** documentado como esperado (precedente D6), não bloqueia.
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [ ] Aceite de produto da intenção ainda coberto (runbook revalida em prod com 200)
|
||||
- [ ] Invariantes AGENTS/engineering-standards (sem código, sem migration, sem access)
|
||||
- [ ] Validação real em produção: 401/401/200/400 conforme fases
|
||||
Loading…
Reference in a new issue