18 KiB
Impl: CI/PR do Forgejo Actions de volta para o GitHub Actions — tracker de Issues permanece no Forgejo
Status: aprovado
Atualizado em: 2026-08-19
Issue: #97
Intenção: docs/plans/ops71-ci-github-actions-tracker-forgejo.md
Appetite restante: herdado (~2–3 dias eng; este impl é o item inteiro — OPS72 roda depois com depends)
Leitura da intenção
- Outcome: código/PR/CI no GitHub Actions (mesma cascata de verificações,
job único
checks), tracker de Issues no Forgejo por API, deploy manual (workflow_dispatch) com dois jobs (verifyhosted full →deployself-hosted no homeserver), rollupCI (PR) / checkscomo required check do GitHub, workstation livre da suíte. - O que NÃO negociar: tracker (Issues/labels/claims) permanece no Forgejo;
estrutura de job único (OPS62) preservada — nada de matrix; deploy NÃO volta
a ser automático; sem migrar Issues; sem segundo deploy (nada de Vercel);
nunca merge com CI vermelho;
pnpm pushcontinua sendo o caminho canônico. - O que reavaliar: a intenção sugere "auto-merge nativo do GitHub ou
safety net equivalente" e "
gh workflow run(ou API GitHub)". Reavaliado:ghnão existe em nenhuma máquina (OPS50) e a filosofia do repo é plain-Node stdlib — os helpers novos são scripts Node zero-dep sobre a REST do GitHub, no mesmo molde deforgejo-api.mjs. O auto-merge é o nativo do GitHub (GraphQLenablePullRequestAutoMerge, rebase) — o servidor espera o required check; o OPS64 pin (varrer statuses individuais) era defesa contra o rollup mentiroso do runner Forgejo, que não existe no GitHub (o check-runCI (PR) / checksde um job único é o veredito honesto, e o auto-merge nativo só mergeia com required checks verdes — garantia do servidor).
Abordagem recomendada
flowchart LR
W[worktree local] -->|pnpm push -u origin HEAD| GH[github.com/fsolla/teqo]
GH -->|PR Ready base main Closes #N| CI["ci-pr.yml — job único checks (mesma cascata OPS62)"]
CI -->|required check CI (PR) / checks| AM["agent-pr-ready-automerge.yml — auto-merge nativo (GraphQL, rebase)"]
AM -->|merge| MAIN[main no GitHub]
MAIN -->|PR closed merged| FLIP["issue-done / plan-issue-ready — flip no Forgejo via FORGEJO_API_TOKEN"]
MAIN -->|dispatch manual| V[deploy.yml verify hosted full suite]
V -->|needs| D["deploy runs-on self-hosted homeserver — deploy-homeserver.sh local"]
D --> P[(teqo-1313 produção)]
Opções consideradas:
- A — origin vira GitHub; Forgejo como remote secundário congelado; TEQO_REPO_URL → GitHub (repo público, sem auth).
origin/mainé o base dos gates locais (ci-scope/gate-ci) — precisa apontar para o repo que recebe os merges, senão o diff local compara contra um main defasado. O repo GitHub é público (verificado:private: false) → o homeserver clona sem credencial. - B — auto-merge via GraphQL nativo. GitHub espera o required check no servidor; nenhum poll; sem semáforo portado. Draft
cursor/*→ Ready via PATCH; draft não-cursor/*= veto (OPS57, preservado). - C — deploy.yml com
verify(hosted, suíte full sem skips) →deploy(needs, self-hosted homeserver) executandodeploy-homeserver.sh <sha>localmente (sem SSH). - D —
forgejo-issue-transition.mjs/agent-promote-related-on-merge.mjsganhamPR_BODYenv (mecanismo queagent-promote-relatedjá usa): o body do PR vem do evento GitHub; o script flipa no Forgejo por API sem ler o PR do Forgejo (PR lá não existe mais). - E —
configure-branch-protection.mjsreescrito para a REST do GitHub com lib puragithub-branch-protection.mjs(regra desejada + drift), no mesmo molde debranch-protection.mjs;enforce_admins: truepreserva o invariante "nunca merge com CI vermelho" inclusive para admin (o rule-based do Forgejo valia para todos).
Recomendação: A+B+C+D+E — porque cada peça reusa o molde existente (plain Node stdlib, libs puras unit-testadas, drift idempotente) e o aceite da intenção exige exatamente esse desenho; nada disso exige binário novo (gh) nem semáforo reimplementado.
Rejeitadas:
- Instalar
ghe usargh pr create/merge/run— binário novo em todas as máquinas + workflows, contra a convenção zero-dep do repo; a REST cobre tudo. - Poll-and-merge portado (
waitForChecksdo Forgejo) como mecanismo único — reimplementa o semáforo que a intenção manda cortar; o OPS64 pin era contra o bug do rollup do Forgejo, inexistente no GitHub com job único + auto-merge nativo. - Mirror ativo do Forgejo (
pnpm pushpara dois remotes / sync pós-merge) — nada lê o git do Forgejo em runtime (deploy usa TEQO_REPO_URL=GitHub; flips são API); o repo Forgejo congela em main no ponto do cutover (tracker intacto por API, anti-goal "nunca fecha o tracker" preservado). - Deletar os arquivos do Forgejo nesta entrega — decisão do gate (2026-08-19): o sistema GitHub Actions nasce PRIMEIRO e é validado ao vivo (PR real com CI verde → auto-merge → flips → deploy manual); a remoção de
.forgejo/workflows/+ scripts mortos é entrega sucessora, só depois de tudo funcionando. O repo Forgejo congelado no main pré-cutover mantém os workflows como rollback (reversível: religar runner + PRs lá); apagar na primeira entrega queimaria a via de volta. - Manter
ci-classify-production.mjscomo check informativo no dispatch — o hosted não alcança o homeserver (sem SSH) e overifyroda a suíte full sem skips; a classificação não muda nada → removido só na entrega de remoção. TEQO_REPO_URL→ Forgejo local com mirror — dependência extra de sync; o repo GitHub é público, clone direto sem auth.- Fork-PR no CI — repo público: fork PR rodaria a suíte full em minutes hosted de graça;
if:same-repo no jobchecks(mesma guarda de RCE dos outros workflows).
Cutover em duas fases (gate 2026-08-19)
O GitHub é só o host do Actions; o Forgejo permanece o tracker principal (Issues, labels, claims, flips — tudo via API, intacto). A remoção dos arquivos do Forgejo só acontece após validação ao vivo do fluxo GitHub:
- Fase 1 (esta entrega — OPS71): cria todo o sistema GitHub Actions (workflows, scripts, libs, docs). Nada do Forgejo é apagado —
.forgejo/workflows/,forgejo-pr-automerge.mjs,forgejo-dispatch.mjs,ci-classify-production.mjs+ specs permanecem no repo (dormentes: o repo Forgejo não recebe mais push). - Cutover manual (humano, nesta ordem — (a)–(c) ANTES do push do OPS71, senão o PR mergearia sem required check): (a) remotes locais (
origin→ GitHub,forgejosecundário); (b) PATGITHUB_TOKENlocal + secrets no GitHub (FORGEJO_API_TOKEN,CURSOR_API_KEY) +allow_auto_merge: true; (c)pnpm configure:branch-protection(GitHub); (d) desligar o runner do Forgejo (reversível — é o que para o schedule doci.ymlantigo, que não tem mais sentido e disputaria a workstation; pode ser logo após o primeiro PR GitHub validado); (e) runner self-hosted no homeserver (deploy; não bloqueia PRs — pode ficar para depois); (f) push do OPS71 → PR no GitHub → CI verde → auto-merge → flip da #97 (valida os flips no merge do próprio OPS71) → deploy manual dispatch quando o runner do homeserver estiver pronto. - Fase 2 (entrega sucessora, registrada no fechamento desta Issue): remove
.forgejo/workflows/*, scripts mortos e docs residuais do fluxo antigo. Só depois do fluxo GitHub comprovado de ponta a ponta.
Componentes / mudanças
.github/workflows/ci-pr.yml(novo): port do.forgejo/workflows/ci-pr.yml— job únicochecks, mesmos steps/guards/services (postgres-int/postgres-buildpor nome na rede do job), skips viaci-scope.mjs(envGITHUB_BASE_REFidêntico). Deltas GitHub:concurrency: { group: ci-pr-${{ github.head_ref }}, cancel-in-progress: true }(fail-fast nativo em push novo — o OPS62 não cancelava só porque o Forgejo não tem API; aqui é grátis),if:same-repo no job (fork não queima minutes),permissions: contents: read(default), e2e selected preservado (OPS72 ajusta a política depois —depends)..github/workflows/deploy.yml(novo):workflow_dispatch(sem inputs — ref default main).verify(ubuntu-latest, timeout 50):check-test-locations→ lint → format → typecheck → knip → cycles → unit → int (migrate+seed nos services) → build → e2e full (4 workers; guardas de contrato de PR ficam de fora — não são verificação de produção).deploy(needs: [verify],runs-on: [self-hosted, homeserver], timeout 60,permissions: contents: read): checkout →bash scripts/deploy-homeserver.sh "$GITHUB_SHA".scripts/deploy-homeserver.sh:TEQO_REPO_URLdefault →https://github.com/fsolla/teqo.git; no workspace existente,git remote set-url origin "$TEQO_REPO_URL"idempotente antes do fetch (o clone velho aponta para o Forgejo local); doc de invocação: execução local no runner (não mais pipe via ssh) — o</dev/nullnorun --rmdo migrate permanece. Fluxo interno intacto (HEAD guard, flock, already-deployed, migrator→migrate→runner→rollout, smoke)..github/workflows/agent-pr-ready-automerge.yml(novo) +scripts/github-pr-automerge.mjs(novo, plain Node) +scripts/lib/github-api.mjs(novo, REST+GraphQL zero-dep): lê PR (REST); base != main → skip; draftcursor/*→PATCH draft:false; draft outro → skip (veto OPS57); senão GraphQLenablePullRequestAutoMerge(mergeMethod: REBASE)nonode_iddo PR. Erro do GraphQL → exit 1 (job vermelho, visível).permissions: { contents: write, pull-requests: write }.scripts/github-pr.mjs(novo, plain Node): criação de PR para agentes/humano —GITHUB_TOKENenv;--head <branch> --title <t> --body-file <f>(Ready, basemain— sem opção draft, regra do repo). Substitui oforgejo_create_pull_requestdo MCP no fluxo de entrega.scripts/lib/github-branch-protection.mjs(novo, puro):DESIRED_RULEGitHub (required_status_checks.checks = [{ context: 'CI (PR) / checks' }], strict=false, 0 reviews,enforce_admins: true, sem restrictions) +ruleMatches/planBranchProtectionRule(mesmo contrato do atual).scripts/configure-branch-protection.mjs: reescrito para a REST do GitHub (PUT /repos/{owner}/{repo}/branches/main/protection), idempotente (read → plan → apply → verify),--dry-runpreservado.scripts/forgejo-issue-transition.mjs: aceitaPR_BODYenv (body do PR vem do evento GitHub); sem PR_BODY mantém a leitura via API do Forgejo (fallback — os workflows do Forgejo ainda estão vivos até a Fase 2); comentário do flip atualizado ("deploy manual via dispatch, ver AGENT-OPS")..github/workflows/issue-done-on-main-merge.yml/plan-issue-ready-on-main-merge.yml(novos):pull_request: closed+if:merged && base main && same-repo (guarda RCE: checkout de fork + PAT = RCE — preservada);PR_BODY: ${{ github.event.pull_request.body }};FORGEJO_API_TOKENsecret; semGITHUB_TOKENde Issues do GitHub (flip é no Forgejo)..github/workflows/archive-cursor-agent.yml(novo): port direto do.forgejo/workflows/(CURSOR_API_KEY).docs/AGENT-OPS.md,.agents/rules/agent-pr-workflow.mdc,.agents/skills/work-issue/(SKILL + execution-pipeline),.agents/skills/agent-work-issue/SKILL.md,.agents/skills/capture-review-debts/SKILL.md(linha do fechamento),AGENTS.md,docs/ops/teqo-1313-deploy.md: fluxo de PR/CI/merge/deploy reescrito para GitHub (canônico); seção de transição nomeando a Fase 2; tabela de secrets (GitHub agora:FORGEJO_API_TOKEN,CURSOR_API_KEY, localGITHUB_TOKENPAT); passos manuais do cutover na ordem (incl. desligar o runner do Forgejo e por quê — o schedule doci.ymlantigo pararia de ter sentido).- Preservados nesta entrega (Fase 2 remove):
.forgejo/workflows/*(6 arquivos),scripts/forgejo-pr-automerge.mjs,scripts/forgejo-dispatch.mjs,scripts/ci-classify-production.mjs,scripts/lib/dockerignore.mjs,scripts/lib/branch-protection.mjs+ specs (ciClassifyProduction,dockerignoreMatch,branchProtection) — dormentes, documentados como rollback e alvo da entrega de remoção. - Testes novos:
tests/unit/githubApi.unit.spec.ts(normalização de shapes + endpoints REST/GraphQL, molde doforgejoApi.unit.spec.ts),tests/unit/githubBranchProtection.unit.spec.ts(regra desejada + drift, molde dobranchProtection.unit.spec.ts), e pure-functions da política de draft/skip do automerger (scripts/lib/github-pr-flow.mjs+ spec — o pin OPS57/OPS64 do jeito GitHub). - Migration: sem migration (nenhum schema).
- Access / Consent: nenhum.
- UI: Impeccable N/A.
Fases verificáveis
- Libs + scripts base —
github-api.mjs,github-pr-flow.mjs(puro),github-branch-protection.mjs(puro),github-pr.mjs,github-pr-automerge.mjs,forgejo-issue-transition.mjs(PR_BODY),configure-branch-protection.mjs(GitHub) + specs unit. Gate:pnpm test:unit. - Workflows —
.github/workflows/*(6). Nada do.forgejo/workflows/é tocado nesta fase (Fase 2 do cutover). Gate:pnpm gate:fast+pnpm check:cycles+ knip. - Docs/skills + changelog — AGENTS.md, AGENT-OPS, agent-pr-workflow.mdc, skills, runbook (com seção de transição),
docs/changelog/2026-08-19-ops71.md+pnpm changelog:build. - Gates finais + entrega —
pnpm gate:cicompleto (unit+int+build), format, knip, cycles;pnpm push -u origin HEAD→ PR no GitHub vianode scripts/github-pr.mjs→ CI verde no GitHub → auto-merge → flip da #97. Passos manuais do cutover listados para o humano (Fase 1 do cutover: remotes, secrets, branch protection, runner homeserver, shutdown runner Forgejo) — o PR em si não depende deles (CI roda no GitHub com secrets de teste), mas a ordem recomendada é: remotes → secrets → configure → push → PR.
Fase 2 — entrega sucessora (pós-validação)
Registrada no fechamento desta Issue (capture-review-debts): remove .forgejo/workflows/*, forgejo-pr-automerge.mjs, forgejo-dispatch.mjs, ci-classify-production.mjs, dockerignore.mjs (+ specs órfãs) e qualquer doc residual do fluxo Forgejo. Critério de entrada: um ciclo completo validado no GitHub (PR → CI → auto-merge → flip → deploy manual).
Rabbit holes / Não escopo (engenharia)
- Portar
waitForChecks/statuses para o GitHub — não há; auto-merge nativo. - Re-engenharia do
deploy-homeserver.sh(fluxo docker/compose/migrate) — só a origem do clone e o modo de invocação mudam. - Sincronizar o repo Forgejo com main — congelado (decidido, anti-goal do tracker preservado).
- Migrar Issues/labels/claims — anti-goal explícito.
- OPS72 (e2e local afetado) — item sucessor com
depends: [OPS71]. - Pool/
agent-pool*— dormente (OPS65); o textogh premagent-pool-prompt.mjsé de feature morta — deixo como está (fora do serializes). - Secrets de GitHub (
FORGEJO_API_TOKEN,CURSOR_API_KEY,GITHUB_TOKENPAT local) — passo manual documentado, não código.
Riscos e mitigação
- Nome do check-run
CI (PR) / checks: o workflowname: CI (PR)+ jobchecksgeram exatamente esse check-run no GitHub; o required check usa o mesmo literal — pin no spec da branch protection e verificação ao vivo no primeiro PR (se o nome divergir, o configure aponta o drift e o PR não mergea — fail-closed, nunca merge sem verificação). - Auto-merge GraphQL com
GITHUB_TOKEN: permissões declaradas (contents+pull-requests: write) cobrem PATCH draft e a mutation; se o GraphQL recusar (ex.: auto-merge desabilitado no repo), o job fica vermelho com a mensagem — mitigação: passo manualallow_auto_merge: trueno repo (Settings ou API) listado no cutover. - PR com conflito: auto-merge fica armado e o PR espera — igual ao hoje (waitForChecks lançava "não mergeável"); nenhum merge errado é possível (servidor).
- Repo público → fork PR:
if:same-repo no jobchecks+ setting de fork workflows no repo (passo manual) — fork não queima minutes nem acessa secrets. - Runner do homeserver ainda não instalado: o
deployfica indisponível até o passo manual; PRs seguem mergeando (CI hosted não depende do runner). O PR deste item não publica nada sozinho — o deploy é dispatch manual. - Schedule do
ci.ymlantigo rodando na workstation durante a transição: com o repo Forgejo congelado, o gate do Forgejo lerá um deployed SHA inexistente no clone → fail-open vermelho → suíte full a cada 30 min na workstation. Mitigado: o cutover desliga o runner do Forgejo logo após o merge (passo (d), reversível) — antes do primeiro deploy via GitHub. gate:cilocal vsorigin/main: o remote muda para GitHub; sem o passo manual dos remotes, o gate local compara contra o Forgejo defasado → risco de diff falso. Mitigado: o passo de remotes é o PRIMEIRO do cutover e o runbook avisa; o*-impl.mddocumenta o comando exato.- TEQO_REPO_URL no homeserver: clone velho com origin Forgejo →
set-urlidempotente no script cobre; repo público → sem credencial. - Knip com os scripts do Forgejo preservados: nada órfão é criado nesta entrega (todos os scripts seguem referenciados pelos YAMLs do
.forgejo/workflows/, que permanecem) — sem risco de knip flagrar a deleção adiada.
Aceite de engenharia
- Aceite de produto da intenção ainda coberto (CI/PR GitHub, tracker Forgejo, deploy manual verify→self-hosted, rollup required check)
- Cutover em duas fases (gate 2026-08-19): sistema GitHub criado e validado primeiro; arquivos do Forgejo preservados nesta entrega, remoção em entrega sucessora pós-validação
- Invariantes AGENTS/engineering-standards (sem migration, sem schema; plain Node stdlib nos helpers de CI; zero-dep nos workflows)
- Testes de domínio previstos: github-api (normalização/endpoints), github-branch-protection (drift), github-pr-flow (draft policy/skips — pin OPS57/OPS64 no jeito GitHub)