diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 44496a6e..14262138 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -230,7 +230,7 @@ jobs: - name: Log deploy deferral if: steps.tip.outputs.stale != 'true' && steps.cooldown.outputs.active == 'true' run: | - echo "Deploy deferred: last production deploy is younger than 30 minutes." + echo "Deploy deferred: last production deploy is younger than 15 minutes." echo "Requeue will retry in ${{ steps.cooldown.outputs.wait_seconds }}s." # `vercel build` shells out to pnpm (packageManager in package.json); without # these steps the runner only has npm → spawn pnpm ENOENT. diff --git a/docs/plans/ci-deploy-cooldown-15min-impl.md b/docs/plans/ci-deploy-cooldown-15min-impl.md new file mode 100644 index 00000000..5955067f --- /dev/null +++ b/docs/plans/ci-deploy-cooldown-15min-impl.md @@ -0,0 +1,70 @@ +# Impl: CI — cooldown de deploy de produção: 30 → 15 min + +Status: aprovado +Atualizado em: 2026-08-02 +Issue: #259 +Intenção: docs/plans/ci-deploy-cooldown-15min.md +Appetite restante: herdado (~0,25 dia) — só constante + pins + +## Leitura da intenção + +- **Outcome:** intervalo mínimo entre deploys READY de produção passa de 30 para **15 minutos**; deferral + requeue inalterados; logs/pins honestos. +- **O que NÃO negociar:** não remover cooldown; não mudar fonte da idade (Vercel READY production); não tocar `ci-pr.yml` / required checks / preview Git. +- **O que reavaliar:** hipótese do plano de intenção confirma — dono é `DEPLOY_COOLDOWN_MS` em `scripts/lib/vercel-deploy-cooldown.mjs`; CLI já deriva minutos do constante. + +## Abordagem recomendada + +```mermaid +flowchart LR + A[ci.yml deploy job] --> B[vercel-deploy-cooldown.mjs] + B --> C[evaluateDeployCooldown] + C --> D{DEPLOY_COOLDOWN_MS} + D -->|"< 15 min"| E[defer + requeue] + D -->|">= 15 min"| F[vercel deploy --prod] +``` + +**Opções consideradas:** + +- **A)** Alterar só `DEPLOY_COOLDOWN_MS` + pins — mínimo diff, CLI auto-ajusta log. +- **B)** Parametrizar via env `DEPLOY_COOLDOWN_MINUTES` no workflow — flexível, mas YAGNI e duplica fonte da verdade. +- **C)** Documentar 15 min só no workflow, constante 30 no script — mente nos testes. + +**Recomendação:** **A** — uma constante nomeada já é o contrato OPS11; workflow só precisa do echo humano. + +**Rejeitadas:** B (over-engineering); C (pins mentirosos). + +### Componentes / mudanças + +- **`DEPLOY_COOLDOWN_MS`** (`scripts/lib/vercel-deploy-cooldown.mjs`): `30 * 60 * 1000` → `15 * 60 * 1000`. +- **`.github/workflows/ci.yml`**: echo de deferral "30 minutes" → "15 minutes". +- **`tests/unit/vercelDeployCooldown.unit.spec.ts`**: pin da constante + descrições; ajustar `waitSeconds` no caso 10 min atrás (20 min → 5 min residual). +- **Migration:** sem migration. +- **Access / Consent:** N/A. +- **UI:** N/A (Impeccable A). + +### Dados → forma + +N/A — política de infra. + +## Fases verificáveis + +1. **Constante + workflow log** — alterar `DEPLOY_COOLDOWN_MS` e echo em `ci.yml`. +2. **Pins unitários** — atualizar `vercelDeployCooldown.unit.spec.ts`. +3. **Gates** — `pnpm gate:fast`; entrega com `pnpm push`. + +## Rabbit holes / Não escopo (engenharia) + +- Reescrever `docs/plans/ci-deploy-cooldown-vercel.md` (histórico OPS11). +- Parametrizar cooldown por env sem pedido de produto. +- Batch de merges / teto diário de deploys. + +## Riscos e mitigação + +- **429 `api-upload-free` volta:** observar pós-merge; reabrir com evidência (assumido na intenção). +- **Echo hardcoded no workflow:** aceito — único lugar; constante é fonte da verdade para lógica. + +## Aceite de engenharia + +- [x] Aceite de produto da intenção ainda coberto +- [x] Invariantes AGENTS/engineering-standards +- [x] Testes de domínio previstos (unit pins existentes) diff --git a/scripts/lib/vercel-deploy-cooldown.mjs b/scripts/lib/vercel-deploy-cooldown.mjs index 86199037..2900fb1d 100644 --- a/scripts/lib/vercel-deploy-cooldown.mjs +++ b/scripts/lib/vercel-deploy-cooldown.mjs @@ -3,7 +3,7 @@ * deployment and computes whether CI should defer another upload (OPS11). */ -export const DEPLOY_COOLDOWN_MS = 30 * 60 * 1000 +export const DEPLOY_COOLDOWN_MS = 15 * 60 * 1000 const VERCEL_API_BASE = 'https://api.vercel.com' diff --git a/tests/unit/vercelDeployCooldown.unit.spec.ts b/tests/unit/vercelDeployCooldown.unit.spec.ts index e8e1bd11..f2b52709 100644 --- a/tests/unit/vercelDeployCooldown.unit.spec.ts +++ b/tests/unit/vercelDeployCooldown.unit.spec.ts @@ -27,8 +27,8 @@ const captureFetch = (response: Response) => { } describe('vercelDeployCooldown', () => { - it('uses a 30-minute cooldown constant', () => { - expect(DEPLOY_COOLDOWN_MS).toBe(30 * 60 * 1000) + it('uses a 15-minute cooldown constant', () => { + expect(DEPLOY_COOLDOWN_MS).toBe(15 * 60 * 1000) }) it('lastProductionReadyAtFromDeployment prefers ready over createdAt', () => { @@ -45,25 +45,25 @@ describe('vercelDeployCooldown', () => { }) }) - it('computeCooldownWait defers when the last deploy is younger than 30 minutes', () => { + it('computeCooldownWait defers when the last deploy is younger than 15 minutes', () => { const now = 1_700_000_000_000 const last = now - 10 * 60 * 1000 expect(computeCooldownWait(last, now)).toEqual({ active: true, - waitSeconds: 20 * 60, + waitSeconds: 5 * 60, ageMs: 10 * 60 * 1000, }) }) - it('computeCooldownWait clears when the last deploy is older than 30 minutes', () => { + it('computeCooldownWait clears when the last deploy is older than 15 minutes', () => { const now = 1_700_000_000_000 - const last = now - 31 * 60 * 1000 + const last = now - 16 * 60 * 1000 expect(computeCooldownWait(last, now)).toEqual({ active: false, waitSeconds: 0, - ageMs: 31 * 60 * 1000, + ageMs: 16 * 60 * 1000, }) })