chore(ops OPS57): forgejo-pr-automerge — alargar gatilho para todas as PRs same-repo (draft não-cursor/* = veto) #48
7 changed files with 166 additions and 10 deletions
|
|
@ -80,4 +80,9 @@ There is no stage→main promote. Merging to `main` is the delivery; `ci.yml` ve
|
|||
|
||||
## Safety net (GitHub Action)
|
||||
|
||||
`agent-pr-ready-automerge.yml` — PRs `cursor/*` → `main`: se Draft → `gh pr ready`; sempre arma `gh pr merge --auto --rebase`. Cobre o caso em que o agente (ou o default draft do Cloud) esqueceu. Não há opt-out por skill (audit incluído).
|
||||
`agent-pr-ready-automerge.yml` — PRs same-repo → `main` (todas as branches):
|
||||
se draft `cursor/*` → `gh pr ready`; sempre arma `gh pr merge --auto --rebase`.
|
||||
Cobre o caso em que o agente (ou o default draft do Cloud) esqueceu e PRs
|
||||
humanas/worktree com merge falho silencioso (incidente OPS52). **Draft de
|
||||
branch não-`cursor/*` nunca é tocado** — o draft é o veto do ator (skip do CLI).
|
||||
Não há opt-out por skill (audit incluído).
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
# Safety net: agent PRs (cursor/* → main) must be Ready + auto-merge.
|
||||
# Covers Cloud ManagePullRequest defaulting to draft and agents that forget
|
||||
# to arm the merge. No skill opt-out (engineering-audit included).
|
||||
# Safety net: every same-repo PR to main must be Ready + auto-merge.
|
||||
# Covers Cloud ManagePullRequest defaulting to draft, agents that forget to
|
||||
# arm the merge, and human/worktree PRs (the OPS52 incident was a human PR
|
||||
# whose merge failed silently). Draft PRs from non-`cursor/*` branches are
|
||||
# never touched — the draft flag is the actor's veto (the CLI skips them).
|
||||
# Fork PRs are excluded (the repo is public; a merged fork PR would deploy).
|
||||
#
|
||||
# Forgejo port (OPS50): Forgejo's API has no auto-merge scheduling, so the
|
||||
# workflow runs scripts/forgejo-pr-automerge.mjs (plain Node): marks draft →
|
||||
|
|
@ -8,7 +11,7 @@
|
|||
# then merges by rebase. Same semantics as `gh pr merge --auto --rebase`,
|
||||
# implemented as poll + merge. GitHub's cancel-workflow-on-failure action was
|
||||
# dropped (no GITHUB_RUN_ID plumbing on Forgejo).
|
||||
name: Agent PR Ready + auto-merge
|
||||
name: PR Ready + auto-merge
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
|
@ -17,9 +20,13 @@ on:
|
|||
|
||||
jobs:
|
||||
ready-automerge:
|
||||
# Same-repo only: a fork PR merged here would push attacker code to main
|
||||
# (and deploy to production). The draft policy lives in the CLI (skips
|
||||
# non-`cursor/*` drafts) so it reads the PR state from the API, not from
|
||||
# webhook payload fields.
|
||||
if: >-
|
||||
github.event.pull_request.state == 'open' &&
|
||||
startsWith(github.event.pull_request.head.ref, 'cursor/')
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ Arquivos de "lista" que N agentes escrevem em paralelo (changelog, plans, migra
|
|||
| `ci.yml` | push / dispatch `main` | service Postgres | Full sempre; fail-fast cancela o run no 1º job vermelho; rollup `checks`; job `deploy` (runs-on `host`) publica no homeserver via `scripts/deploy-homeserver.sh` (OPS53) |
|
||||
| `issue-done-on-main-merge.yml` | PR merged → `main` | — | `Closes`/`Fixes` → `done` + `in-prod` |
|
||||
| `plan-issue-ready-on-main-merge.yml` | PR merged → `main` | — | `Related #N` aguardando plano → `ready` (OPS18; soft-skip) |
|
||||
| `agent-pr-ready-automerge.yml` | PR `cursor/*` → `main` (open/sync/…) | — | Draft→Ready + `gh pr merge --auto --rebase` (safety net; audit incluso) |
|
||||
| `agent-pr-ready-automerge.yml` | PR same-repo → `main` (open/sync/…) | — | Safety net: Ready + `gh pr merge --auto --rebase` (todas as branches; draft `cursor/*` → Ready; draft não-`cursor/*` = veto, skip) |
|
||||
| `agent-pool.yml` | schedule / PR closed `main` / dispatch | — | Supervisor do pool (`POOL_GITHUB_TOKEN`) |
|
||||
|
||||
Action runtimes: `actions/checkout@v5`, `actions/setup-node@v5`, `pnpm/action-setup@v6` e `styfle/cancel-workflow-action@0.13.1` usam Node 24 nativo — sem `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24`.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
**Recently resolved (2026-08-18):** **OPS56**: o `forgejo-pr-automerge` nunca mais reporta falso positivo "já mergeado" com PR aberto. Raiz confirmada no source do servidor (Forgejo 9.0.3, tag v9.0.3): o endpoint `POST /pulls/{n}/merge` responde **200 com corpo vazio** no sucesso (falhas são 404/405/409/500), e o `request()` do `forgejo-api.mjs` convertia isso em `null` — o `autoMerge()` devolvia `null` cru e o CLI imprimia "já mergeado" (exit 0) independente do estado real do PR; o incidente (PR #27 da OPS52, aberto das 01:59 às 02:45) deixou o merge falhar silenciosamente com o PR aberto. Correção: o veredito de `autoMerge` agora é **re-read do PR** após a tentativa (`{ attempted, merged, pr }`) — 200-vazio → confirma no `GET /pulls/{n}`; POST falhou e o PR segue aberto → **re-throw** (falha ruidosa, exit 1); raça perdida (outro ator mergeou) → "já mergeado" só com o PR realmente mergeado; PR fechado sem merge → "fechado sem merge — skip" honesto. `waitForChecks` passou a esperar o `mergeable` amadurecer (false → throw imediato de conflito; null → segue no poll), mesmo timeout de 30 min. CLI sem shadowing de `pr` (const redeclarada). Testes: 6 specs novas em `forgejoApi.unit.spec.ts` (veredito por re-read, POST 405 + PR aberto → reject, raça concorrente, no-op de já-mergeado, poll de mergeable null, fail-fast de mergeable false). Gates: 2318 unit + 791 int + tsc/lint/prettier/knip/check:cycles verdes. Débito deferido: alargar o gatilho do `agent-pr-ready-automerge.yml` para branches não-`cursor/*` (decisão de ops separada).
|
||||
|
||||
**Recently resolved (2026-08-18):** **OPS57** — o safety net do `forgejo-pr-automerge` agora cobre **todas as PRs same-repo para `main`**, não só `cursor/*`. Motivação (débito deferido na OPS56): dos 25 PRs mergeados no repo, **zero** eram `cursor/*` — 9 de `plans/*`, 11 de worktrees humanos, 5 de `refs/pull/N/head` (era da migração) — o filtro antigo nunca cobriu nenhum PR real, e o incidente OPS52 (PR #27, humano, merge falhou silenciosamente) era exatamente a classe excluída. Mudanças: `if:` do job em `agent-pr-ready-automerge.yml` → `state == 'open' && head.repo.full_name == github.repository` (guarda de fork — o repo é público e uma PR de fork mergeada dispararia o deploy de prod; campos já usados no payload, sem dependência de `is_draft` do webhook); política de draft movida para o CLI `forgejo-pr-automerge.mjs` com o head ref do **re-read da API** — draft `cursor/*` → Ready (pool inalterado); draft não-`cursor/*` → skip exit 0 (**draft = veto do ator, PR humana nunca é forçada**); PRs ready não-`cursor/*` (humanas/plans) passam a mergear com CI verde — redundante no caminho feliz (o fluxo humano já arma `--auto --rebase`) e é o que pega a classe OPS52; `ready_for_review` em PR draft re-dispara o job e mergea. Docs sincronizadas (`docs/AGENT-OPS.md` tabela CI por alvo, `.agents/rules/agent-pr-workflow.mdc` seção Safety net). Rejeitado: `!is_draft` no YAML (campo do payload não verificado, falha fail-open) e allowlist de prefixos (códigos humanos sem prefixo comum — OPS/S1/PUB5). A própria PR desta entrega ainda usa o workflow de `main` (antigo); a próxima PR exercita o gatilho alargado. Sem migration/access/Consent/UI. Testes: nenhum novo (lógica de veredito já coberta pelos specs da OPS56; branch de draft é 3 linhas no CLI); gates unit/int verdes.
|
||||
|
||||
**Recently resolved (2026-08-17):** **OPS51** — o banco de produção saiu do Neon para o Postgres do homeserver (`teqo_1313`, container `teqo-1313`), sem mudança de config: o `~/stack/teqo-1313.env` já apontava para o banco preparado (criado 16/08 com role própria + cadeia completa de 56 migrations + zero conteúdo — verificado), então restaurar = a produção "apareceu" no container. **Divergência deliberada da intenção (questão A/B validada como B):** rejeitado criar `teqo_prod` novo — o argumento "o banco do 1313 tem vida própria" não se sustenta (o site 1313 É a produção deste repo) e o OPS53 já trata `teqo_1313` como produção. Execução 100% no homeserver (PII nunca tocou a workstation): URL do Neon puxada do Vercel (`vercel env pull`, projeto `jorgesolla`, `DATABASE_URL_UNPOOLED` → `NEON_DATABASE_URL` em `~/stack/.env` chmod 600), `pg_dump -Fc` (Neon PG 17.10) → `/srv/hdd/backups/teqo-neon-pre-migracao/teqo-neon-full-20260817-204800.dump` (5,8 MB, sha256 `f12196f2…`), `pg_restore --no-owner --no-privileges` como a role `teqo_1313` (drop schema + grant, padrão do `db-pull`), verificado: contagens por tabela idênticas ao Neon (1919 contatos, 39 posts, 1487 assinaturas, 1484 subscrições, 35 campaign_users, 392 lideranças, 2 pledges, 435 municípios, 6 atividades; supporter segue 0 — blocker LGPD ainda de pé), sequences no topo (`last_value == max(id)`), objetos com dono `teqo_1313`, `payload migrate` no-op (56 = 56, serviço de maintenance `teqo-1313-migrate`), 3 credenciais WebAuthn + 34 sessions + 5 chaves Consent restauradas (RP ID não mudou — biometria segue valendo). Smoke sem deploy: `/`, artigo com conteúdo restaurado, `/campanha/login`, `/admin`, `POST /api/revalidate` (`posts` + `global_privacy-policy`), WebAuthn `login-options`, barreira 307 sem sessão — verdes; smoke de browser (login, biometria, convites) validado manualmente. **Rollback documentado:** Neon nunca foi escrito (só lido); voltar = re-apontar `DATABASE_URL` do `teqo-1313.env` para o Neon + `compose up -d`. **Débito registrado (→ OPS52):** as 40 rows de `media` têm URLs relativas (`/api/media/file/…`) sem arquivos em lugar nenhum — capas dão 500 no homeserver e **404 no próprio Vercel/produção atual** (estado pré-existente; migração manteve paridade). Repo: plano da intenção concluído, `AGENTS.md` e help de `db:pull`/`check-push-chain` sem a referência ao Neon; infra-solla `STATE.md` atualizado à parte.
|
||||
|
||||
**Recently resolved (2026-08-17):** **OPS53** — o deploy do site 1313 virou etapa do CI: mergir em `main` com a suite verde == site atualizado em `jorgesolla1313.com.br`, sem passo manual. O job `deploy` em `.forgejo/workflows/ci.yml` roda no host do runner (workstation, `runs-on: host`) e streama `scripts/deploy-homeserver.sh` para o homeserver via SSH (`ssh homeserver "bash -s -- <sha>"`); o script remoto (bash puro, homeserver sem dependências novas) faz: guard de HEAD (`git ls-remote` de `main` == SHA do job, senão skip "stale"), `flock` de serialização, workspace `~/teqo-deploy` (clone do Forgejo local), `docker login localhost:5000` (REGISTRY_USER/PASSWORD de `~/stack/.env`), build dos stages `runner`+`migrator` no homeserver (`--network stack_default` para a geração estática alcançar o `postgres:5432` do compose; secrets de `~/stack/teqo-1313.env` via BuildKit, sem camadas/logs), push no registry local, swap dos tags de imagem no compose (com backup `docker-compose.yml.pre-<sha>`), **migrate pelo serviço de maintenance `teqo-1313-migrate` antes do rollout**, `docker compose up -d`, espera do healthcheck e smoke (`/`, `/campanha/login`, `/admin`, barreira 307, WebAuthn `login-options` com body JSON, `POST /api/revalidate` com o secret real — rehearsal validado contra o site vivo). Falha pós-swap → rollback automático best-effort (restore do backup + `up -d` da imagem anterior) + job vermelho. **Divergência deliberada da intenção (fato novo):** a intenção assumia o runner no homeserver; verificado (`infra-solla/STATE.md` + ao vivo) que o runner é a `workstation-runner` na workstation e o stack de produção vive no homeserver — a abordagem A foi corrigida para "job `runs-on: host` na workstation + todo o deploy no homeserver via SSH", o que manteve **zero secrets novos no Forgejo** e zero mudança de infra no compose. Nenhum secret no repo; envs seguem chmod-600 no homeserver. Runbook (rollback manual, falhas conhecidas, decisão de segurança do host label): `docs/ops/teqo-1313-deploy.md` (novo). Guarda estrutural nova: `tests/unit/deployScript.unit.spec.ts` (bash -n + invariantes do pipeline: HEAD guard, BuildKit secrets, ordem migrate→rollout, smoke). AGENTS.md/AGENT-OPS.md atualizados. Primeiro deploy real = o merge deste próprio item (tracer bullet); infra-solla `STATE.md` anotado à parte.
|
||||
|
|
|
|||
1
docs/changelog/2026-08-18-ops57.md
Normal file
1
docs/changelog/2026-08-18-ops57.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
**Recently resolved (2026-08-18):** **OPS57** — o safety net do `forgejo-pr-automerge` agora cobre **todas as PRs same-repo para `main`**, não só `cursor/*`. Motivação (débito deferido na OPS56): dos 25 PRs mergeados no repo, **zero** eram `cursor/*` — 9 de `plans/*`, 11 de worktrees humanos, 5 de `refs/pull/N/head` (era da migração) — o filtro antigo nunca cobriu nenhum PR real, e o incidente OPS52 (PR #27, humano, merge falhou silenciosamente) era exatamente a classe excluída. Mudanças: `if:` do job em `agent-pr-ready-automerge.yml` → `state == 'open' && head.repo.full_name == github.repository` (guarda de fork — o repo é público e uma PR de fork mergeada dispararia o deploy de prod; campos já usados no payload, sem dependência de `is_draft` do webhook); política de draft movida para o CLI `forgejo-pr-automerge.mjs` com o head ref do **re-read da API** — draft `cursor/*` → Ready (pool inalterado); draft não-`cursor/*` → skip exit 0 (**draft = veto do ator, PR humana nunca é forçada**); PRs ready não-`cursor/*` (humanas/plans) passam a mergear com CI verde — redundante no caminho feliz (o fluxo humano já arma `--auto --rebase`) e é o que pega a classe OPS52; `ready_for_review` em PR draft re-dispara o job e mergea. Docs sincronizadas (`docs/AGENT-OPS.md` tabela CI por alvo, `.agents/rules/agent-pr-workflow.mdc` seção Safety net). Rejeitado: `!is_draft` no YAML (campo do payload não verificado, falha fail-open) e allowlist de prefixos (códigos humanos sem prefixo comum — OPS/S1/PUB5). A própria PR desta entrega ainda usa o workflow de `main` (antigo); a próxima PR exercita o gatilho alargado. Sem migration/access/Consent/UI. Testes: nenhum novo (lógica de veredito já coberta pelos specs da OPS56; branch de draft é 3 linhas no CLI); gates unit/int verdes.
|
||||
130
docs/plans/ops57-forgejo-pr-automerge-alargar-gatilho-impl.md
Normal file
130
docs/plans/ops57-forgejo-pr-automerge-alargar-gatilho-impl.md
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# Impl: OPS57-guard — forgejo-pr-automerge: alargar gatilho do workflow para branches não-cursor/\*
|
||||
|
||||
Status: aprovado
|
||||
Atualizado em: 2026-08-18
|
||||
Issue: #44
|
||||
Intenção: sem plano linkado — body é spec (frontmatter + título + decisão de ops aberta)
|
||||
Appetite restante: pequeno (1 workflow + 1 script + docs)
|
||||
|
||||
## Leitura da intenção
|
||||
|
||||
- **Outcome:** o safety net do `forgejo-pr-automerge` cobre **todas** as PRs
|
||||
para `main` (não só `cursor/*`) — uma falha de merge de PR humano (classe do
|
||||
incidente OPS52, PR #27) vira **ruidosa sem run manual**, em vez de depender
|
||||
do script/skill que o fluxo humano já roda.
|
||||
- **O que NÃO negociar:** PRs **draft de atores não-pool nunca são forçadas a
|
||||
ready** (o draft é o controle humano de veto); script continua plain-Node
|
||||
zero-dep (roda dentro do Actions sem `pnpm install`); idempotência; merge por
|
||||
rebase.
|
||||
- **O que reavaliar:** a decisão de ops em aberto — "todas as PRs para main"
|
||||
vs "padrões explícitos". Evidência do histórico real do repo (25 PRs
|
||||
mergeadas, via API): **zero** PRs `cursor/*`; 9 de `plans/*`; 11 de worktrees
|
||||
humanos (`OPS*`/`S1`/`PUB5`); 5 de `refs/pull/N/head` (artefatos da era da
|
||||
migração). Ou seja: o filtro `cursor/*` atual **nunca cobriu nenhum PR real**
|
||||
do repo — o safety net nunca disparou. O incidente que motivou a OPS56 (PR
|
||||
#27, humano) é exatamente a classe que o filtro exclui.
|
||||
|
||||
## Abordagem recomendada
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[PR event → main] --> B{job if: state open + same-repo?}
|
||||
B -->|fork ou fechado| Z[skip — sem job]
|
||||
B -->|same-repo| C[script: re-read do PR]
|
||||
C --> D{draft?}
|
||||
D -->|sim + head cursor/*| E[mark ready → waitForChecks → merge rebase]
|
||||
D -->|sim + head não-cursor| F[log + exit 0 — draft preservado]
|
||||
D -->|não| G[waitForChecks → merge rebase]
|
||||
G -->|falhou| H[exit 1 — ruidoso]
|
||||
```
|
||||
|
||||
**Opções consideradas:**
|
||||
|
||||
- A. Alargar o `if:` do job para `state == 'open' && head.repo.full_name ==
|
||||
github.repository` e mover a política de draft para o **script** (draft→ready
|
||||
só para head `cursor/*`; draft não-cursor → skip exit 0), com o head ref lido
|
||||
do **re-read da API** (fonte única da verdade já usada pelo CLI).
|
||||
- B. Alargar o `if:` com `(startsWith(head.ref, 'cursor/') ||
|
||||
!github.event.pull_request.is_draft)` — política toda no YAML, script intacto.
|
||||
- C. Allowlist de prefixos de branch no YAML (ex.: `OPS*`, `S*`, `plans/*`).
|
||||
|
||||
**Recomendação: A** — o script já re-read o PR no primeiro passo
|
||||
(`getPullRequest` devolve `head.ref`/`isDraft`), então a decisão de draft usa
|
||||
campos **verificados da API**, não campos do payload do webhook. O campo de
|
||||
draft do payload do webhook do Forgejo (`is_draft` vs `draft`, GitHub usa
|
||||
`draft`) não está documentado no reference do Forgejo — depender dele (opção B)
|
||||
é um risco de **fail-open**: campo undefined → `!undefined` → job roda em PRs
|
||||
draft → script marca ready → merge forçado de PR humano. A opção A degrada
|
||||
sempre para o comportamento seguro. O `if:` mínimo (state + same-repo) só
|
||||
precisa de campos já usados no workflow atual (`state` já está lá) e do
|
||||
`head.repo.full_name`, que é o modelo API do payload (o MCP/API devolve
|
||||
`head.repo.full_name` no próprio PR).
|
||||
|
||||
**Rejeitadas:** B porque depende de nome de campo não verificado com modo de
|
||||
falha fail-open (o pior: forçar ready+merge de draft humano); C porque os códigos
|
||||
humanos não têm prefixo comum (`OPS57-…`, `S1-…`, `PUB5-…`) e `plans/*` mudaria
|
||||
a cada novo padrão — allowlist é manutenção, o denylist natural (draft) é
|
||||
estável.
|
||||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **`agent-pr-ready-automerge.yml`** (`.forgejo/workflows/`): `if:` do job →
|
||||
`github.event.pull_request.state == 'open' && github.event.pull_request.head.repo.full_name == github.repository` (guarda de fork — o repo é **público**, e fork PR mergeada dispararia o deploy de prod em main); header comment reescrito (safety net cobre todas as PRs same-repo para main; draft é o veto).
|
||||
- **`scripts/forgejo-pr-automerge.mjs`**: no passo draft, decide pelo `pr.head.ref` do re-read: `cursor/*` → marca Ready (pool, inalterado); não-`cursor/*` → log `draft fora de cursor/* — skip (não força PR humano)` + exit 0 (PR fica draft até o ator marcar ready; o evento `ready_for_review` re-dispara o job que então mergea).
|
||||
- **`docs/AGENT-OPS.md`** (tabela "CI por alvo", linha do workflow) e
|
||||
**`.agents/rules/agent-pr-workflow.mdc`** (seção "Safety net"): descrição do
|
||||
trigger e da semântica de draft.
|
||||
- **Migration:** sem. **Access/Consent/UI:** n/a (workflow/scripts de ops).
|
||||
|
||||
### Efeito no auto-merge de PRs humanos (a decisão da Issue)
|
||||
|
||||
Toda PR humana já nasce **Ready** e o fluxo humano arma `gh pr merge --auto
|
||||
--rebase` imediatamente (execution-pipeline passo 5) — o safety net alargado é
|
||||
**redundante no caminho feliz** e é exatamente o que pega a classe do incidente
|
||||
OPS52 (merge falhou com PR aberto). O humano que quiser vetar o auto-merge
|
||||
mantém a PR em **draft** — o workflow não toca draft não-`cursor/*`. Semântica
|
||||
nova: uma PR humana **ready** que nunca foi armada manualmente passa a ser
|
||||
mergeada quando o CI verde — que é o contrato implícito de toda PR para `main`
|
||||
neste repo (todo ator já auto-mergea).
|
||||
|
||||
## Fases verificáveis
|
||||
|
||||
1. **Workflow + script + docs** — mudanças acima (4 arquivos).
|
||||
2. **Gates** — `pnpm gate:fast`; changelog `docs/changelog/2026-08-18-ops57.md`
|
||||
- `pnpm changelog:build`; `pnpm push`; PR Ready + merge.
|
||||
3. **Validação no mundo real (pós-merge)** — o workflow que roda é o de `main`:
|
||||
esta PR ainda usa o filtro antigo (manual, sem auto-merge do safety net); a
|
||||
**próxima** PR (pool ou humana) exercita o gatilho alargado pela primeira
|
||||
vez — observar a run no Actions é a verificação.
|
||||
|
||||
## Rabbit holes / Não escopo
|
||||
|
||||
- Retry/backoff dentro do `waitForChecks` (rejeitado na OPS56 — falha ruidosa
|
||||
é o aceite).
|
||||
- Bump do timeout de 30 min do `waitForChecks` para suíte cheia: estourar é
|
||||
ruidoso (exit 1) e o próximo evento/run retenta — não mexe.
|
||||
- Branch protection de `main`: a API do Forgejo retorna **0 regras** (o gate de
|
||||
`checks` hoje vive no script, não no servidor) — verificação à parte, não é
|
||||
esta entrega.
|
||||
- Pool: nada a fazer (quando o pool criar PRs `cursor/*`, o fluxo é idêntico
|
||||
ao atual).
|
||||
- Usar `pull_request_target` ou outras mudanças de segurança do workflow.
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- `head.repo.full_name` ausente no payload → `undefined == github.repository`
|
||||
→ skip (fail-closed, direção segura).
|
||||
- Corrida humano×workflow no merge → `autoMerge` re-read + idempotente
|
||||
("já mergeado" honesto, OPS56).
|
||||
- Conversão para draft no meio do wait → merge 405 → re-read → re-throw
|
||||
(ruidoso; fechar a PR é o aborto limpo).
|
||||
- PR humana pronta "antes da hora" (humano ainda mexendo) → mesma semântica do
|
||||
`--auto` que o próprio fluxo arma; escape: draft.
|
||||
- PR de fork com CI verde → excluída no `if:`; e o token automático de fork é
|
||||
read-only de qualquer forma (defesa em profundidade).
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [ ] Aceite de produto da intenção ainda coberto (safety net para branches não-`cursor/*`)
|
||||
- [ ] Invariantes: zero-dep do script; idempotência; rebase; draft não-pool intocado
|
||||
- [ ] Docs sincronizadas (AGENT-OPS, agent-pr-workflow.mdc) — sem drift do filtro
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Plain-Node CLI (no pnpm) — agent PR safety net: wait for checks and merge by
|
||||
* Plain-Node CLI (no pnpm) — PR safety net: wait for checks and merge by
|
||||
* rebase (the Forgejo equivalent of `gh pr merge --auto --rebase`, which the
|
||||
* Forgejo API does not offer as a scheduled operation). Idempotent: already
|
||||
* merged → no-op success. The verdict comes from re-reading the PR (Forgejo's
|
||||
|
|
@ -7,6 +7,10 @@
|
|||
* alone cannot prove the merge) — "já mergeado" is only printed for a PR the
|
||||
* server actually reports as merged; a failed merge exits 1 loudly.
|
||||
*
|
||||
* Draft policy (OPS57): only `cursor/*` heads get marked ready. A draft from
|
||||
* any other branch is the actor's veto — the CLI skips it (exit 0) so the
|
||||
* workflow never forces a human/plans PR to ready.
|
||||
*
|
||||
* node scripts/forgejo-pr-automerge.mjs --pr <N>
|
||||
*/
|
||||
|
||||
|
|
@ -43,8 +47,15 @@ try {
|
|||
process.exit(0)
|
||||
}
|
||||
if (pr.isDraft) {
|
||||
console.log(`[forgejo-pr-automerge] PR #${prNumber} é draft — marcando Ready`)
|
||||
await api.markPullRequestReady(prNumber)
|
||||
if (pr.head.ref.startsWith('cursor/')) {
|
||||
console.log(`[forgejo-pr-automerge] PR #${prNumber} é draft — marcando Ready`)
|
||||
await api.markPullRequestReady(prNumber)
|
||||
} else {
|
||||
console.log(
|
||||
`[forgejo-pr-automerge] PR #${prNumber} é draft fora de cursor/* — skip (não força PR humano)`,
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
} else {
|
||||
console.log(`[forgejo-pr-automerge] PR #${prNumber} já Ready`)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue