plans(OPS71/OPS72): CI volta ao GitHub Actions com tracker no Forgejo + e2e local afetado #99

Merged
fsolla merged 1 commit from plans/plan-issue-11 into main 2026-08-19 19:27:21 +00:00
2 changed files with 387 additions and 0 deletions

View file

@ -0,0 +1,241 @@
---
id: OPS71
depends: []
serializes:
[
'.github/workflows/',
'.forgejo/workflows/',
'docs/AGENT-OPS.md',
'.agents/skills/work-issue/',
'.agents/rules/agent-pr-workflow.mdc',
]
priority: P1
model: cursor-grok-4.5-high
---
# CI/PR do Forgejo Actions de volta para o GitHub Actions — tracker de Issues permanece no Forgejo
Status: rascunho
Atualizado em: 2026-08-19
Issue: #97
Priority: P1
Model: cursor-grok-4.5-high
Impeccable: A — N/A (sem UI)
Rascunho UI: N/A — sem UI
Appetite: ~2–3 dias eng; um outcome verificável (CI verde no GitHub, tracker vivo no Forgejo, deploy preservado)
Responsável: —
## Intenção
O Forgejo Runner roda na workstation (Ryzen 7 7800X3D) e **está pedindo muito
da máquina**: a suíte inteira (build + int + e2e) roda localmente a cada PR e
na janela de main, disputando CPU com o dev server, o Docker local, o browser
e os worktrees paralelos. O OPS65 já tentou mitigar (janela de 30 min + matar
o pool), mas o custo por PR continua pesado — e ele cresce com o número de PRs
abertos, que é exatamente o que queremos aumentar.
A decisão: **migrar o CI de volta para o GitHub Actions** (runners hosted,
custo zero na workstation, um runner por PR — PRs avançam em paralelo de
verdade). **O tracker de Issues permanece no Forgejo local** (`git.solla.dev`,
já público via Cloudflare — confirmado HTTP 200 hoje): o CI no GitHub fala com
o tracker por API (`FORGEJO_API_TOKEN` + `FORGEJO_API_URL`), como os scripts
locais já fazem. Não é voltar ao mundo pré-OPS50 — é um novo meio-termo
deliberado: **código/PR/CI no GitHub, Issues/labels no Forgejo**.
Mantemos a **estrutura de verificações** construída nos OPS61/OPS62/OPS64
(job único `checks` sequencial, guards no início, skips de escopo via
`ci-scope.mjs`, rollup `CI (PR) / checks` como gate de merge) — o que muda é o
host onde ela roda. E a política de e2e muda junto (ver OPS72: e2e local
apenas para os testes afetados, não full no CI de PR). **O deploy deixa de ser
automático** (OPS53/OPS65 viram histórico): vira uma **action manual
(`workflow_dispatch`)** que roda **todas as verificações full** antes de
publicar — ver "Questões em aberto" (questão 1) e o fluxo abaixo.
## Persona e fluxo
- **Persona / contexto:** o dono da máquina (dev/coordenador do repo) rodando
dev local, worktrees e múltiplos PRs; os agentes (humanos e pool) que abrem
PRs e acompanham o CI até o merge; o coordenador que dispara o deploy quando
decide publicar.
- **Job principal:** cada PR custa ~0 da workstation; o CI roda no GitHub em
paralelo para todos os PRs; o merge continua automático e seguro; publicar
em produção é uma ação manual com verificação full.
- **Fluxo desejado:**
1. Agente claima Issue no Forgejo (igual hoje — tracker intocado).
2. Worktree/branch local; `pnpm push` para o **GitHub**; PR aberto no
**GitHub** (Ready, base `main`, `Closes #N`).
3. GitHub Actions roda o job único `checks` (mesma cascata de hoje, com e2e
apenas nas áreas afetadas — OPS72) e posta o status no PR.
4. Merge automático quando o rollup `CI (PR) / checks` fica verde
(auto-merge nativo ou safety net equivalente).
5. Pós-merge: Issue flipada para `done`/`in-prod` no **Forgejo** (workflow
GitHub Actions → API do Forgejo); `Related #N` promove `blocked` →
`ready` no Forgejo.
6. Deploy **manual** (workflow_dispatch): rodar verificações full → o job
`deploy` executa `scripts/deploy-homeserver.sh` no homeserver (runner
self-hosted lá, ou via SSH da workstation como fallback — questão 1).
- **Anti-goals de produto:** NÃO migrar o tracker (Issues/labels/claims ficam
no Forgejo); NÃO re-introduzir jobs paralelos com matrix no CI (a estrutura
de job único do OPS62 vale no GitHub também); NÃO tocar em schema/migrations
de produto; NÃO re-publicar Vercel ou qualquer segundo deploy; deploy NÃO
volta a ser automático a cada merge.
## Objetivo e aceite
- O CI de PR (`ci-pr.yml`) roda no GitHub Actions com a **mesma cascata de
verificações** de hoje (guards → lint → format → typecheck → knip → cycles →
unit → int → build → e2e **selected/afetado**, com skips de escopo), como
**um job único `checks`**.
- O **deploy** vira uma action **manual** (`workflow_dispatch` no GitHub
Actions) com **dois jobs no mesmo workflow**: `verify` (hosted
`ubuntu-latest`, suíte **full** incl. e2e full) → `deploy`
(`needs: [verify]`, `runs-on: [self-hosted, homeserver]` — runner do GitHub
no homeserver executa `scripts/deploy-homeserver.sh` localmente, sem SSH).
O hosted nunca toca o homeserver; o self-hosted não conta minutos hosted.
Sem verificador de main (`ci.yml`) — só o pre-deploy roda full
(decisão do usuário, gate 2026-08-19).
- O rollup `CI (PR) / checks` continua sendo o required check de `main` e o
gate do merge automático (agora no GitHub).
- PRs são abertas no GitHub; agentes acompanham o CI até o merge em `main`
(skills atualizadas — work-issue/agent-work-issue/execution-pipeline,
AGENT-OPS, agent-pr-workflow).
- As Issues **não migram**: labels, claims, `pnpm issue`/`agent:*` continuam
falando com o Forgejo; flips pós-merge funcionam via API (verificado: o
Forgejo é alcançável publicamente).
- A workstation não roda mais a suíte de CI; o Forgejo runner é desligado.
- Deploy manual com verificação full preservado (ver questão 1).
- Um repo GitHub sincronizado (histórico atual de `main` presente — hoje o
repo GitHub está parado em 2026-08-13).
## Dados (intenção)
- **Vou apresentar dados?** Não.
- **Decisões desbloqueadas:** nenhuma KPI nova. (O "custo da workstation" é
observável pela própria máquina; não precisa de métrica nova.)
## Direção no codebase (hipótese)
- **Áreas prováveis:**
- `.github/workflows/` (novo): `ci-pr.yml` (job único `checks`, e2e
selected — OPS72), `deploy.yml` (**`workflow_dispatch` manual, dois jobs:
`verify` hosted full → `deploy` `runs-on: [self-hosted, homeserver]`**),
`agent-pr-ready-automerge.yml` (safety net), `issue-done-on-main-merge.yml`,
`plan-issue-ready-on-main-merge.yml`, `archive-cursor-agent.yml`. Sem
`ci.yml` (verificador de main eliminado — decisão do gate).
- `scripts/`: adaptar `forgejo-pr-automerge.mjs` (ou substituir por
auto-merge nativo do GitHub / API `merge_when_checks_succeed`),
`forgejo-issue-transition.mjs` e `agent-promote-related-on-merge.mjs`
(leem o PR — agora do GitHub — e flipam no Forgejo),
`forgejo-dispatch.mjs` → `gh workflow run` (ou API GitHub), novos helpers
GitHub (`scripts/lib/`); `configure-branch-protection.mjs` → proteção no
GitHub.
- `scripts/deploy-homeserver.sh` — revisar o modo de invocação (runner no
homeserver: sem `ssh`, execução local; via workstation: SSH mantido) e o
`TEQO_REPO_URL` (hoje `localhost:3000` do Forgejo no homeserver — com o
código no GitHub, apontar para o GitHub ou manter o espelho local).
- `.agents/skills/**`, `.agents/rules/agent-pr-workflow.mdc`,
`docs/AGENT-OPS.md`, `AGENTS.md` — o paradigma de PR passa a apontar para
o GitHub (PR/CI), tracker Forgejo.
- **Precedente a olhar:** `docs/plans/ops50-ci-github-para-forgejo.md` (o
espelho inverso — o que foi migrado para o Forgejo e como), `ops62`,
`ops65`, `ops61` (branch protection/contratos de merge), `docs/ops/teqo-1313-deploy.md`,
e o projeto **`infra-solla`** (ver "Nota para o executor").
- **Risco de acoplamento:** o deploy (OPS53) hoje depende do runner local da
workstation (SSH para o homeserver) e o build de produção **não pode sair do
homeserver** (migrate no banco de prod antes do build — OPS66, estático na
rede do compose, registry local `localhost:5000`). O GitHub hosted não
alcança nada disso — por isso o job `deploy` roda no runner self-hosted do
homeserver (decisão do gate). O `ci-classify-production.mjs` (gate OPS65)
morre com o deploy manual (sem janela automática) — pode ser removido ou
reutilizado no dispatch como check informativo de "produção mudou desde o
último deploy".
## Nota para o executor — projeto `infra-solla`
Para detalhes do homeserver (stack, registry, tunel, runner, rede, `ssh
homeserver` via tailnet), **leia o projeto `infra-solla`** (fora deste repo):
`STATE.md` e `plano-infra-final.md` §"Arquitetura de deploy". É a fonte usada
pelo OPS53 para corrigir a premissa de onde rodava o runner, e é onde o
passo manual "instalar o runner self-hosted do GitHub no homeserver" deve ser
registrado/anotado. Não invente topologia: confirme lá (homeserver 8c/16GB,
laptop; `~/stack/docker-compose.yml`; registry `localhost:5000` com htpasswd;
cloudflared; `ssh homeserver` pela tailnet) antes de decidir como o runner
self-hosted se instala e como o `deploy.yml` o alcança.
## Dependências
- OPS72 (e2e local afetado + CI de PR sem e2e full) — **serializa com este
item** (ambos tocam `ci-pr.yml` e a skill work-issue); OPS72 declara
`depends: [OPS71]` para rodar depois e evitar rebase cruzado. Decisão de
gate: CI de PR mantém e2e **selected** (afetado) — confirmado.
- #85 (OPS70, gate skipped no schedule do Forgejo) — **absorvida**: com o
deploy manual, o gate de schedule deixa de existir; fechar como superada
quando este item mergear.
- #87 (OPS70, guard de importMap) — independente, permanece na fila; o
executor dela deve mirar os workflows novos do GitHub.
- #72 (S3-FOLLOWUP, e2e local `--no-deps` colide no seedTestUser) — não é
duplicada; o OPS72 (e2e local afetado) cita a limitação existente.
## Fora de escopo
- Migrar Issues/labels do Forgejo (anti-goal explícito).
- Mudar a estrutura de verificações (job único, guards, skips) — preservada.
- Alterar o `e2e-affected`/`ci-scope` em si — mecânica já existe; só muda
onde/quando roda (OPS72).
- Repositório do Forgejo: continua como tracker; se virar mirror read-only de
main ou parar de receber push — decisão do impl, mas nunca fecha o tracker.
- Upgrade do Forgejo no homeserver (decisão de infra separada).
## Rabbit holes de produto
- **Re-engenharia do deploy.** O job `deploy` dependia do runner local; sem
decisão, o "preservar deploy" vira um projeto de infra. **Corte neste item:**
uma das opções da questão 1, fechada no gate, sem inventar terceiro caminho.
- **Reescrever o contrato de merge.** Auto-merge do GitHub é nativo, mas o
wait-por-checks/rollup do OPS64 foi calibrado para o Forgejo; portar
errado = merge com CI vermelho. **Corte:** usar o mecanismo nativo do GitHub
(o rollup `CI (PR) / checks` vira o required check lá) e pin unit-testado no
script, sem reimplementar semáforos novos.
- **"Já que estamos no GitHub, migra as Issues também".** O usuário foi
explícito: tracker fica no Forgejo. **Corte:** qualquer proposta de migrar
o tracker é rejeitada neste item.
## Questões em aberto (produto)
- **Como fica o deploy?** **Decisão do usuário (gate 2026-08-19):** deploy
**manual** via `workflow_dispatch` no GitHub Actions — não é mais automático
a cada merge. O job de deploy roda **todas as verificações full** antes de
publicar. **Formato (confirmado):** **dois jobs no mesmo `deploy.yml`** —
`verify` no hosted `ubuntu-latest` (suíte full) e `deploy` com
`needs: [verify]` rodando em **runner self-hosted do GitHub no homeserver**
(conecta outbound ao GitHub — funciona atrás do Cloudflare tunnel; o job
executa `scripts/deploy-homeserver.sh` localmente, sem SSH; não conta
minutos hosted). O build de produção **não pode rodar fora do homeserver**
(migrate pré-build no banco de prod — OPS66, estático na rede do compose,
registry local `localhost:5000`) — por isso o hosted só verifica e o
self-hosted só build/deploy. Fallbacks registrados (não escolhidos): runner
na workstation via SSH (B) ou runbook manual no homeserver (C).
- **E o ci.yml (verificador de main)?** **Decidido (gate 2026-08-19):**
**eliminar** — fica só o pre-deploy (`verify` do dispatch manual) como
verificação full. Motivo: custo mínimo de Actions; o deploy é deliberado e
roda full na hora; se um dia o merge em main quiser rede de regressão, o
`ci.yml` volta com 1 linha de trigger (sem deploy).
- **Fatiamento do lote?** **Opções:** A) OPS71 (este, CI+PR+merge+deploy) e
OPS72 (e2e local afetado) separados com `depends`; B) um único item com tudo.
**Recomendação:** A — cada um tem outcome verificável sozinho e o OPS72
(skill work-issue + ci-pr sem e2e full) é pequeno. _(assumido — validar)_
- **Repo GitHub defasado.** Hoje parado em 2026-08-13 (pré-OPS50). **Opções:**
A) re-sincronizar via push do estado local (histórico é ancestral comum —
fast-forward limpo); B) import novo (histórico duplicado/PRs antigas órfãs).
**Recomendação:** A. _(assumido — validar)_
## Referências
- GitHub Issue: #97 (OPS71)
- Rascunho UI (gate): N/A
- `.forgejo/workflows/` (a remover após cutover), `docs/plans/ops50-ci-github-para-forgejo.md`,
`docs/plans/ops62-ci-fail-fast-forgejo.md`, `docs/plans/ops65-ci-main-janela-30min-e-matar-pool.md`,
`docs/plans/ops61-forgejo-contratos-merge-labels-e-branch-protection.md`,
`scripts/forgejo-pr-automerge.mjs`, `scripts/forgejo-issue-transition.mjs`,
`scripts/forgejo-dispatch.mjs`, `scripts/configure-branch-protection.mjs`,
`scripts/ci-classify-production.mjs`, `docs/ops/teqo-1313-deploy.md`

View file

@ -0,0 +1,146 @@
---
id: OPS72
depends: [OPS71]
serializes: ['ci-pr.yml', '.agents/skills/work-issue/']
priority: P1
model: composer-2.5
---
# E2E local apenas para os testes afetados — CI de PR sem e2e full
Status: rascunho
Atualizado em: 2026-08-19
Issue: #98
Priority: P1
Model: composer-2.5
Impeccable: A — N/A (sem UI)
Rascunho UI: N/A — sem UI
Appetite: ~0,5–1 dia eng; um outcome verificável (skill work-issue exige e2e afetado local; CI de PR não roda e2e full)
Responsável: —
## Intenção
O e2e é a etapa mais cara do CI de PR (build `.next-e2e` + playwright +
suíte inteira). Com o CI voltando para o GitHub Actions (OPS71), o custo por
PR deixa de ser a CPU da workstation — mas o tempo de veredito por PR continua
importando: e2e full é a maior fatia do job único `checks` (OPS62), e a
política de hoje (OPS59: "e2e roda UMA vez, no CI; local opcional") significa
que cada PR paga a suíte e2e inteira, inclusive specs em superfícies que o PR
não tocou.
Queremos: **e2e no CI de PR apenas para os testes afetados** (novos testes
criados + specs da mesma superfície trabalhada — a mecânica `e2e selected` já
existe via `ci-scope.mjs`/manifest do OPS59), e o **restante da cobertura e2e
roda localmente** — também apenas nos afetados — como passo do fluxo do agente
antes do PR (atualizar a skill `work-issue` para exigir isso). O e2e **full**
fica para o **deploy manual** (workflow_dispatch do OPS71) e para o verificador
de main — como rede de segurança antes de publicar — sem nunca rodar full por
PR.
**Decisão do gate (2026-08-19):** o CI de PR **continua rodando o e2e das
áreas afetadas** (modo `selected`) — confirmado pelo usuário; o e2e local
afetado é o passo da skill; o full fica no deploy/verificador de main.
## Persona e fluxo
- **Persona / contexto:** o agente (humano no worktree ou worker do pool) que
implementa uma Issue e abre o PR; o dono da máquina que não quer o CI
ocupando o runner por 40+ min por PR.
- **Job principal:** saber que os testes novos/afetados do seu PR passam,
com custo mínimo de CI e feedback local rápido.
- **Fluxo desejado:**
1. Agente implementa; roda os gates locais de sempre (`pnpm gate:push`).
2. **Novo passo obrigatório:** roda `pnpm test:e2e:affected` (ou o modo
selecionado equivalente) — cobre os novos testes criados + specs da
mesma superfície trabalhada.
3. CI de PR roda a cascata **sem e2e full**: apenas o conjunto afetado
(modo `selected` — já suportado pelo classifier; decisão do gate).
4. Deploy manual (OPS71) e verificador de main rodam e2e **full** antes de
publicar — a rede de segurança final inegociável.
- **Anti-goals de produto:** NÃO remover o e2e do deploy/verificador de main;
NÃO deixar o agente pular o e2e local sem justificativa; NÃO reintroduzir
"e2e é só no CI" (OPS59 era isso e custa caro por PR).
## Objetivo e aceite
- Skill `work-issue` (e `execution-pipeline.md`, `agent-work-issue` onde
aplicável) passam a exigir, antes do `pnpm push`/PR: **e2e local afetado**
(`pnpm test:e2e:affected`), com o critério "novos testes + mesma superfície"
documentado.
- CI de PR: e2e roda em modo `selected` (manifest afetado) — nunca `full`. O
classificador de escopo (`ci-scope.mjs`) é a fonte da decisão.
- Deploy manual e verificador de main: e2e **full** preservado (rede de
segurança pré-deploy/pré-publicação).
- A skill `work-issue` documenta a limitação da #72 (e2e local com `--no-deps`
- projetos paralelos colide no `seedTestUser` — usar `--workers=1` ou a
cadeia padrão de projetos).
- Gate local (`pnpm gate:push`) intocado — e2e continua fora do pre-push; o
e2e local é passo **explícito do fluxo da skill**, não do hook.
## Dados (intenção)
- **Vou apresentar dados?** Não.
- **Decisões desbloqueadas:** o agente decide o conjunto afetado com base no
diff da superfície — critério documentado na skill, não métrica nova.
## Direção no codebase (hipótese)
- **Áreas prováveis:**
- `.agents/skills/work-issue/SKILL.md` e `execution-pipeline.md` — novo
passo "e2e local afetado" antes do push; delta para `agent-work-issue`
quando fizer sentido.
- `.forgejo/workflows/ci-pr.yml` → (OPS71) `.github/workflows/ci-pr.yml` —
etapa e2e apenas `selected`; remover `full` do modo PR.
- `.github/workflows/ci.yml` (verificador de main) e `deploy.yml` (dispatch
manual) — e2e `full` preservado neles.
- `docs/AGENT-OPS.md`, `AGENTS.md` (política "e2e = CI" do OPS59 vira
"e2e afetado = local + CI selected; full = deploy/verificador de main").
- **Precedente a olhar:** `docs/plans/ops59-gate-local-sem-e2e.md` (política
atual), `ci-scope.mjs` (modos `full/selected/none`), `e2e-affected-manifest.mjs`,
`docs/plans/ops62-ci-fail-fast-forgejo.md`.
- **Risco de acoplamento:** toca a mesma superfície do OPS71 (workflows + a
skill work-issue é citada pelo fluxo de PR) — por isso `depends: [OPS71]` e
serialização explícita (OPS71 serializa `ci-pr.yml` e `work-issue`).
## Dependências
- OPS71 (CI no GitHub) — este item edita o `ci-pr.yml` novo e a skill que o
OPS71 já aponta para o GitHub; `depends: [OPS71]`.
- #72 (S3-FOLLOWUP) — citada como limitação documentada; não é duplicada nem
absorvida (fica na fila).
## Fora de escopo
- Mudar a mecânica do classifier/manifest (`ci-scope.mjs`,
`e2e-affected-manifest.mjs`) — funciona; só muda onde/em que modo roda.
- Implementar o fix da #72.
- Remover o e2e do deploy manual / verificador de main.
## Rabbit holes de produto
- **"Se é afetado, nem precisa do CI e2e".** O CI selected é a rede de
segurança quando o agente erra o critério (esqueceu um spec da mesma
superfície) — e é o que o usuário pediu para manter. **Corte:** CI selected
permanece; local é complementar, não substituto.
- **"Full só no deploy cria gap de regressão".** Aceito e deliberado: o full
gateia a publicação (deploy manual) e o verificador de main; PRs afetados
cobrem a superfície do diff. **Corte:** não reintroduzir full por PR.
## Questões em aberto (produto)
- **CI de PR: e2e selected ou nenhum e2e?** **Decidido no gate (2026-08-19):**
e2e **selected** (manifest afetado) no CI de PR — o usuário confirmou
("O CI PR deve continuar fazendo o e2e das áreas afetadas").
- **O e2e local afetado vale também para o pool (`agent-work-issue`)?**
**Opções:** A) sim, mesmo passo obrigatório; B) pool roda só o CI selected
(workers Cloud sem worktree local com browsers instalados).
**Recomendação:** A quando o worktree local tiver browser; senão B com
justificativa registrada. _(assumido — validar)_
## Referências
- GitHub Issue: #98 (OPS72)
- Rascunho UI (gate): N/A
- `.agents/skills/work-issue/SKILL.md`, `.agents/skills/work-issue/execution-pipeline.md`,
`scripts/ci-scope.mjs`, `scripts/lib/e2e-affected-manifest.mjs`,
`docs/plans/ops59-gate-local-sem-e2e.md`, `docs/plans/ops62-ci-fail-fast-forgejo.md`