From ddafa11c200414aea8e05a9ff5c14da6cf51ebb3 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Tue, 18 Aug 2026 14:28:49 -0300 Subject: [PATCH 1/2] =?UTF-8?q?plan(OPS62):=20CI=20fail-fast=20no=20Forgej?= =?UTF-8?q?o=20=E2=80=94=20upgrade=20do=20servidor=20+=20watchdog=20cancel?= =?UTF-8?q?a=20o=20run=20em=20qualquer=20job=20vermelho?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit plan(OPS63): doc-guard no pre-push — changelog append-only, agregado e marcadores de conflito no gate local --- docs/plans/ops62-ci-fail-fast-forgejo.md | 94 +++++++++++++++++++++++ docs/plans/ops63-doc-guard-no-pre-push.md | 81 +++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 docs/plans/ops62-ci-fail-fast-forgejo.md create mode 100644 docs/plans/ops63-doc-guard-no-pre-push.md diff --git a/docs/plans/ops62-ci-fail-fast-forgejo.md b/docs/plans/ops62-ci-fail-fast-forgejo.md new file mode 100644 index 00000000..a6b10a80 --- /dev/null +++ b/docs/plans/ops62-ci-fail-fast-forgejo.md @@ -0,0 +1,94 @@ +--- +id: OPS62 +depends: [] +serializes: ['.forgejo/workflows/ci-pr.yml', '.forgejo/workflows/ci.yml'] +priority: P2 +model: composer-2.5 +--- + +# OPS62 — CI: cancelar jobs irmãos quando um job falha (fail-fast no Forgejo runner) + +Status: rascunho +Atualizado em: 2026-08-18 +Issue: #59 +Priority: P2 +Model: composer-2.5 +Impeccable: A — N/A (sem superfície UI) +Rascunho UI: N/A — sem UI +Appetite: ~1–2 dias eng (inclui upgrade do Forgejo no homeserver + watchdog) +Responsável: — + +## Intenção + +O CI roda no nosso próprio runner (Forgejo + runner self-hosted, OPS50/OPS53). Hoje, quando um job falha, os irmãos continuam rodando até o fim — o run só "termina" quando o último job acaba, e o `checks` (gate) só decide no fim. No runner compartilhado isso ocupa a fila (atrasa checks de outros PRs) e o agente espera um veredito que já estava decidido no primeiro vermelho. Queremos fail-fast: **qualquer job** com falha (lint, format, e2e, o que for) → cancela o resto do run imediatamente, libera o runner e o agente recupera para consertar e tentar de novo. + +**Decisão (gate 2026-08-18):** fazer o upgrade do Forgejo no homeserver para uma versão com a API de cancel de runs e entregar o fail-fast intra-run completo via watchdog — não ficar na mitigação parcial. + +## Persona e fluxo + +- **Persona / contexto:** agente (ou humano) com um PR aberto no CI; um job falhou (ex.: lint) e os demais seguem rodando. +- **Job principal:** saber o veredito e liberar o runner o quanto antes — sem pagar o tempo de jobs que não podem mais mudar o resultado. +- **Fluxo desejado:** + 1. Push → CI inicia os jobs do run (fase 1 em paralelo). + 2. Um job falha — **qualquer um** (lint, format, typecheck, unit, e2e, …). + 3. O run cancela todos os irmãos ainda em execução (intra-run fail-fast). + 4. `checks` fica vermelho sem esperar o fim dos demais; o agente conserta o job que falhou. + 5. Novo push já substitui o run anterior (comportamento nativo existente) e o ciclo repete. +- **Anti-goals de produto:** + - Não perder diagnóstico: cancelar jobs não pode apagar logs do que já rodou (o conserto precisa deles). + - Não mudar o que o CI verifica (mesmos jobs, mesma cascata fase 1→2, `checks` continua o gate). + - Não criar gate/bypass novo — só o `--no-verify` documentado. + +## Objetivo e aceite + +- Um run de PR com **qualquer job vermelho** (lint, format, typecheck, knip, cycles, unit, int, build, e2e, docs-guards) cancela os irmãos em execução (fail-fast intra-run) — medido por: run com falha termina logo após o primeiro job vermelho, sem esperar o último job natural. +- A suíte verificada não muda: mesmos jobs, mesma cascata, `checks` como gate único. +- Logs dos jobs que já rodaram permanecem consultáveis após o cancelamento. +- O Forgejo do homeserver roda uma versão com a API de cancel de runs (`GET /actions/runs/{run}/jobs` + `POST /actions/runs/{run}/cancel`), sem regressão nos workflows existentes (OPS50 port, automerge, pool, deploy OPS53). + +## Dados (intenção) + +Dados: N/A — chore de DX/processo; sem métrica de produto. (Ganho: tempo de veredito e ocupação da fila do runner — medição fica a critério da execução.) + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** infra do Forgejo no homeserver (upgrade de versão — domínio OPS50/OPS51; fora do repo, com runbook próprio); um watchdog (job ou script) que consulta status de **todos os jobs** do run e cancela o run via API na primeira falha — aplicável a ci-pr.yml e ci.yml; `.forgejo/workflows/ci-pr.yml` e `ci.yml` (matrix e2e hoje com `fail-fast: false` deliberado — OPS34; cascade fase 2 já pula por `needs` + `if`). +- **Precedente a olhar:** `docs/plans/ops50-ci-github-para-forgejo*.md` (decisão "Forgejo has no cancel-run plumbing; job timeouts cover that role" — este item a substitui), `docs/plans/ci-e2e-paralelizar-job*.md` (shards), `docs/plans/ops61-forgejo-contratos-merge-labels-e-branch-protection.md` (contrato de token: GITHUB_TOKEN 403 → PAT). +- **Risco de acoplamento:** cancelar via API exige token com permissão de escrita em actions — contrato de OPS61 diz que o token nativo 403 em API do Forgejo; provável PAT (`POOL_GITHUB_TOKEN` como precedente). Upgrade do Forgejo é infra de produção no homeserver: janela/risco, verificação dos workflows existentes após o upgrade (automerge, pool, deploy). + +**Contexto verificado (evidência para o gate):** +- Forgejo deployado: `9.0.3+gitea-1.22.0` — sem API de cancel: `GET/POST /actions/runs/{run}/cancel` e `GET /actions/runs/{run}/jobs` dão 404; só existe `GET /actions/tasks` (status por job do run). A rota web `POST /actions/runs/{run}/cancel` existe mas exige sessão + CSRF (retorna "Invalid CSRF token") — inutilizável de job com token. +- Forgejo atual (code.forgejo.org) expõe `GET /actions/runs/{run_id}/jobs` + `POST /actions/runs/{run_id}/cancel` → watchdog viável **após upgrade**. +- Novo push/PR sync **já cancela o run anterior do mesmo workflow** (nativo; observado statuses `cancelled` nos tasks) — não cobre intra-run, mas elimina o desperdício do ciclo consertar→push. +- `concurrency:` não resolve: mesmo em Forgejo novo, a config não afeta jobs dentro do mesmo workflow (e cancelar run anterior já é nativo). +- Cascade fase 2 já funciona na prática (jobs `skipped` após falha de fase 1 — observado em runs reais). + +## Dependências + +- Nenhuma dura. Soft: contrato de token de OPS61 (PAT, não GITHUB_TOKEN) para o watchdog. + +## Fora de escopo + +- Mudar a suíte verificada (jobs/cascata/`checks`) → CI atual é o verificado e fica como está. +- Migrar o Forgejo de volta para o GitHub ou outra hospedagem (o upgrade é no homeserver atual). +- Mexer no deploy (OPS53) ou na política "Dono do PR, dono do CI". + +## Rabbit holes de produto + +- **"Instalo a action de fail-fast do GitHub".** Não roda no Forgejo (OPS50). **Corte:** watchdog com API nativa do Forgejo pós-upgrade. +- **"Aproveito o upgrade para mudar mais coisa no Forgejo".** Upgrade de produção já tem risco próprio. **Corte:** só a versão mínima necessária para a API de cancel + verificação dos workflows existentes. +- **"Cancelar tudo, até os logs".** Job cancelado sem log quebra o conserto. **Corte:** cancelamento preserva o que já rodou (padrão das fail-fast actions). +- **"Watchdog cancela só fase 2 / só e2e".** O pedido é fail-fast de **qualquer** job — lint vermelho também cancela o resto. **Corte:** o watchdog observa todos os jobs do run, sem filtro de fase. + +## Questões em aberto (produto) + +- **Fail-fast total (upgrade + watchdog) ou mitigação parcial sem infra?** **Decidido no gate (2026-08-18):** upgrade do Forgejo + watchdog — fail-fast intra-run completo, qualquer job. Mitigação parcial (e2e `fail-fast: true` + cascade) fica como fallback documentado, não como destino. +- **Perder os logs do shard 2 de e2e quando o run é cancelado?** **Decidido no gate:** aceitar — o spec que falhou é conhecido no report do shard 1 e o agente reroda localmente; o cancelamento preserva logs do que já rodou. + +## Referências + +- `.forgejo/workflows/ci-pr.yml` / `ci.yml` — alvo do fail-fast; cascade fase 2; comentário OPS50 +- `docs/plans/ops50-ci-github-para-forgejo.md` / `-impl.md` — decisão "no cancel-run plumbing" +- `docs/plans/ci-e2e-paralelizar-job.md` — shards e `fail-fast: false` deliberado +- `docs/plans/ops61-forgejo-contratos-merge-labels-e-branch-protection.md` — contrato de token (GITHUB_TOKEN 403 → PAT `POOL_GITHUB_TOKEN`) +- `docs/AGENT-OPS.md` — política "Dono do PR, dono do CI" \ No newline at end of file diff --git a/docs/plans/ops63-doc-guard-no-pre-push.md b/docs/plans/ops63-doc-guard-no-pre-push.md new file mode 100644 index 00000000..89cd35fc --- /dev/null +++ b/docs/plans/ops63-doc-guard-no-pre-push.md @@ -0,0 +1,81 @@ +--- +id: OPS63 +depends: [] +serializes: ['scripts/gate-ci.mjs'] +priority: P2 +model: composer-2.5 +--- + +# OPS63 — Doc-guard também no pre-push (changelog append-only, agregado e marcadores de conflito) + +Status: rascunho +Atualizado em: 2026-08-18 +Issue: #60 +Priority: P2 +Model: composer-2.5 +Impeccable: A — N/A (sem superfície UI) +Rascunho UI: N/A — sem UI +Appetite: ~0,5 dia eng; um outcome verificável (push local roda os checks de docs) +Responsável: — + +## Intenção + +O CI tem o job `docs-guards` (OPS44): changelog é append-only, o agregado está sincronizado com `docs/changelog/` e nenhum marcador de conflito entrou em markdown. São verificações rápidas (diff de git, sem banco), mas só rodam no CI — um push com changelog quebrado descobre o problema lá, cicla o run e ocupa a fila do runner. No pre-push, a mesma verificação custa segundos e não chega a subir. + +## Persona e fluxo + +- **Persona / contexto:** agente (ou humano) fechando uma entrega que escreve `docs/changelog/-.md` e roda `pnpm changelog:build` antes do push (OPS44). +- **Job principal:** saber **antes do push** que o changelog respeita append-only, o agregado está up to date e nenhum marcador de conflito entrou em markdown. +- **Fluxo desejado:** + 1. Entrega pronta com entrada de changelog. + 2. `pnpm push` (ou `git push` via `.husky/pre-push`) roda o gate local. + 3. Os checks de docs rodam em segundos, junto da cascata existente. + 4. Falha → mensagem acionável antes do push; correção no mesmo passo. + 5. Push limpo não cicla o CI à toa. +- **Anti-goals de produto:** + - Não criar bypass novo (o escape `changelog-rewrite:` continua sendo contrato do body do PR no CI). + - Não rodar `plans-only-closes` localmente (depende do body do PR — sem sentido pré-push). + - Não duplicar lógica: reusar os scripts de guard existentes (default `origin/main` já funciona local). + +## Objetivo e aceite + +- `pnpm gate:push` (via `pnpm push` **e** `.husky/pre-push`) roda os três checks de docs: changelog append-only, agregado up to date (`--check`) e sem marcadores de conflito em markdown — junto da cascata atual. +- Falha bloqueia o push com a mesma mensagem acionável do CI. +- O CI não muda: o job `docs-guards` continua dono do escape `changelog-rewrite:` (body do PR). + +## Dados (intenção) + +Dados: N/A — chore de DX/processo; sem métrica de produto. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `scripts/gate-ci.mjs` (espelho local do ci-pr.yml — hoje omite `docs-guards` e `plans-only-closes`; adicionar a etapa de docs chamando os scripts existentes). Reuso direto de `scripts/check-changelog-append-only.mjs`, `scripts/build-changelog.mjs --check` e `scripts/check-docs-conflict-markers.mjs`. `.husky/pre-push` e `scripts/git-push.mjs` seguem delegando a `gate:ci` (sem mudança). `.forgejo/workflows/ci-pr.yml` intocado. +- **Precedente a olhar:** `docs/plans/gate-push-local.md` (origem do gate), `docs/plans/gate-push-sem-e2e.md` (OPS59 — também edita gate-ci.mjs), contrato OPS44 em `docs/AGENT-OPS.md`. +- **Risco de acoplamento:** mesmo arquivo que OPS59 edita (`scripts/gate-ci.mjs`) — mudança aditiva (OPS59 remove o bloco e2e; este item adiciona a etapa de docs), sem conflito conceitual; rebase obrigatório. `check-changelog-append-only.mjs` local não honra o escape (sem body de PR) — restaurações legítimas (padrão D8) falham no pre-push; bypass documentado necessário (ver questão em aberto). + +## Dependências + +- Nenhuma dura. Serializa com OPS59 (Issue #46) em `scripts/gate-ci.mjs` — ordem suave, não lock. + +## Fora de escopo + +- `plans-only-closes` (contrato de body de PR — CI-only por design). +- Mudar o que o CI roda (job `docs-guards` e escape `changelog-rewrite:` permanecem como estão). +- Criar flag de bypass local para o guard. + +## Rabbit holes de produto + +- **"Aproveito e rodo o plans-only-closes também".** A regra decide sobre o body do PR; pré-push não tem PR. **Corte:** só os três checks de diff. +- **"Local também honra o changelog-rewrite".** Criaria bypass local para uma regra que existe como contrato de PR/CI. **Corte:** falha local com mensagem clara + bypass existente (`git push --no-verify` direto) para o caso raro. + +## Questões em aberto (produto) + +- **O que fazer com restauração legítima (D8) no pre-push, já que o escape `changelog-rewrite:` só existe no body do PR?** **Decidido no gate (2026-08-18):** A — falha local com mensagem indicando o bypass documentado (`git push --no-verify` direto, já existente); o guard local é honesto e o CI continua sendo a via do escape. + +## Referências + +- `scripts/gate-ci.mjs` — o espelho local a editar (omite `docs-guards` hoje) +- `.forgejo/workflows/ci-pr.yml` — job `docs-guards` (linhas 369–386) e `plans-only-closes` +- `scripts/check-changelog-append-only.mjs`, `scripts/build-changelog.mjs --check`, `scripts/check-docs-conflict-markers.mjs` — guards reutilizáveis (default `origin/main`) +- `docs/plans/gate-push-local.md` — origem do gate; `docs/plans/gate-push-sem-e2e.md` — OPS59, mesmo arquivo +- `docs/AGENT-OPS.md` — contrato OPS44 (append-only, escape `changelog-rewrite:`) \ No newline at end of file -- 2.45.2 From 80af2da0ca56d96a95910519b468eb52c8cad4da Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Tue, 18 Aug 2026 14:30:37 -0300 Subject: [PATCH 2/2] style: prettier nos planos OPS62/OPS63 --- docs/plans/ops62-ci-fail-fast-forgejo.md | 3 ++- docs/plans/ops63-doc-guard-no-pre-push.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/plans/ops62-ci-fail-fast-forgejo.md b/docs/plans/ops62-ci-fail-fast-forgejo.md index a6b10a80..e2aaefbe 100644 --- a/docs/plans/ops62-ci-fail-fast-forgejo.md +++ b/docs/plans/ops62-ci-fail-fast-forgejo.md @@ -57,6 +57,7 @@ Dados: N/A — chore de DX/processo; sem métrica de produto. (Ganho: tempo de v - **Risco de acoplamento:** cancelar via API exige token com permissão de escrita em actions — contrato de OPS61 diz que o token nativo 403 em API do Forgejo; provável PAT (`POOL_GITHUB_TOKEN` como precedente). Upgrade do Forgejo é infra de produção no homeserver: janela/risco, verificação dos workflows existentes após o upgrade (automerge, pool, deploy). **Contexto verificado (evidência para o gate):** + - Forgejo deployado: `9.0.3+gitea-1.22.0` — sem API de cancel: `GET/POST /actions/runs/{run}/cancel` e `GET /actions/runs/{run}/jobs` dão 404; só existe `GET /actions/tasks` (status por job do run). A rota web `POST /actions/runs/{run}/cancel` existe mas exige sessão + CSRF (retorna "Invalid CSRF token") — inutilizável de job com token. - Forgejo atual (code.forgejo.org) expõe `GET /actions/runs/{run_id}/jobs` + `POST /actions/runs/{run_id}/cancel` → watchdog viável **após upgrade**. - Novo push/PR sync **já cancela o run anterior do mesmo workflow** (nativo; observado statuses `cancelled` nos tasks) — não cobre intra-run, mas elimina o desperdício do ciclo consertar→push. @@ -91,4 +92,4 @@ Dados: N/A — chore de DX/processo; sem métrica de produto. (Ganho: tempo de v - `docs/plans/ops50-ci-github-para-forgejo.md` / `-impl.md` — decisão "no cancel-run plumbing" - `docs/plans/ci-e2e-paralelizar-job.md` — shards e `fail-fast: false` deliberado - `docs/plans/ops61-forgejo-contratos-merge-labels-e-branch-protection.md` — contrato de token (GITHUB_TOKEN 403 → PAT `POOL_GITHUB_TOKEN`) -- `docs/AGENT-OPS.md` — política "Dono do PR, dono do CI" \ No newline at end of file +- `docs/AGENT-OPS.md` — política "Dono do PR, dono do CI" diff --git a/docs/plans/ops63-doc-guard-no-pre-push.md b/docs/plans/ops63-doc-guard-no-pre-push.md index 89cd35fc..5dfb142d 100644 --- a/docs/plans/ops63-doc-guard-no-pre-push.md +++ b/docs/plans/ops63-doc-guard-no-pre-push.md @@ -78,4 +78,4 @@ Dados: N/A — chore de DX/processo; sem métrica de produto. - `.forgejo/workflows/ci-pr.yml` — job `docs-guards` (linhas 369–386) e `plans-only-closes` - `scripts/check-changelog-append-only.mjs`, `scripts/build-changelog.mjs --check`, `scripts/check-docs-conflict-markers.mjs` — guards reutilizáveis (default `origin/main`) - `docs/plans/gate-push-local.md` — origem do gate; `docs/plans/gate-push-sem-e2e.md` — OPS59, mesmo arquivo -- `docs/AGENT-OPS.md` — contrato OPS44 (append-only, escape `changelog-rewrite:`) \ No newline at end of file +- `docs/AGENT-OPS.md` — contrato OPS44 (append-only, escape `changelog-rewrite:`) -- 2.45.2