mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-12 22:13:05 +00:00
feat(ops98): veto audit/* no safety-net de auto-merge (PR único do audit fica sem automerge)
This commit is contained in:
parent
0cea0b7d8f
commit
b7ed186237
4 changed files with 44 additions and 3 deletions
|
|
@ -5,6 +5,8 @@
|
|||
# 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) — same RCE-guard class as the other workflows.
|
||||
# OPS98: `audit/*` branches (engineering-audit overnight run) are also skipped —
|
||||
# that single Ready PR merges by human hand only.
|
||||
#
|
||||
# GitHub's NATIVE auto-merge replaces the Forgejo-era poll CLI (removed in
|
||||
# OPS71 Fase 2): `enablePullRequestAutoMerge` (GraphQL, REBASE —
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@
|
|||
* mergeable. The OPS64 "never trust the rollup" pin is structural here: a
|
||||
* single sequential job posts one honest check-run, and the server enforces
|
||||
* it. Draft policy (OPS57) preserved: only `cursor/*` heads are marked
|
||||
* ready — any other draft is the actor's veto (skip, exit 0).
|
||||
* ready — any other draft is the actor's veto (skip, exit 0). OPS98: `audit/*`
|
||||
* heads are vetoed entirely (ready or draft) — the engineering-audit single PR
|
||||
* merges by human hand only.
|
||||
*
|
||||
* Idempotent: already merged / closed / non-main base → no-op success.
|
||||
* A GraphQL error (e.g. repo auto-merge disabled, PR in conflict) exits 1 so
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
*
|
||||
* GitHub's native auto-merge only fires with the required checks green, so
|
||||
* the safety net's only real decisions are: skip (non-main base, draft veto,
|
||||
* already merged) or mark-ready (cursor/* draft) before arming auto-merge.
|
||||
* already merged, audit/* veto) or mark-ready (cursor/* draft) before arming
|
||||
* auto-merge.
|
||||
*/
|
||||
|
||||
export const CURSOR_HEAD_PREFIX = 'cursor/'
|
||||
|
|
@ -14,6 +15,15 @@ export const CURSOR_HEAD_PREFIX = 'cursor/'
|
|||
/** OPS57: only `cursor/*` drafts get force-marked ready — any other draft is the actor's veto. */
|
||||
export const isCursorHead = (ref) => typeof ref === 'string' && ref.startsWith(CURSOR_HEAD_PREFIX)
|
||||
|
||||
/**
|
||||
* OPS98: branches `audit/*` belong to the engineering-audit overnight run —
|
||||
* its single PR must stay Ready WITHOUT auto-merge (the human is the final
|
||||
* gate). The veto is dominant: it applies to ready and draft states alike.
|
||||
*/
|
||||
export const AUDIT_HEAD_PREFIX = 'audit/'
|
||||
|
||||
export const isAuditHead = (ref) => typeof ref === 'string' && ref.startsWith(AUDIT_HEAD_PREFIX)
|
||||
|
||||
/**
|
||||
* OPS71-FLIP: the auto-merge MUST be armed with a real user PAT. Arming with
|
||||
* the built-in GITHUB_TOKEN makes the merge happen as `github-actions[bot]`,
|
||||
|
|
@ -41,6 +51,7 @@ export const automergeArmingToken = (env = process.env) => {
|
|||
*/
|
||||
export const decideAutomergeAction = (pr) => {
|
||||
if (!pr) return { action: 'skip', reason: 'pr-inexistente' }
|
||||
if (isAuditHead(pr.head?.ref)) return { action: 'skip', reason: 'audit-veto' }
|
||||
if (pr.merged) return { action: 'skip', reason: 'ja-mergeada' }
|
||||
if (pr.state !== 'OPEN') return { action: 'skip', reason: 'pr-nao-aberta' }
|
||||
if (pr.base?.ref !== 'main') return { action: 'skip', reason: 'base-nao-main' }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
AUDIT_HEAD_PREFIX,
|
||||
CURSOR_HEAD_PREFIX,
|
||||
automergeArmingToken,
|
||||
decideAutomergeAction,
|
||||
isAuditHead,
|
||||
isCursorHead,
|
||||
} from '../../scripts/lib/github-pr-flow.mjs'
|
||||
|
||||
|
|
@ -66,6 +68,29 @@ describe('github-pr-flow automerge decision (OPS71)', () => {
|
|||
expect(isCursorHead(undefined)).toBe(false)
|
||||
})
|
||||
|
||||
it('skips a ready audit/* PR — the single-PR overnight delivery (OPS98)', () => {
|
||||
const verdict = decideAutomergeAction(
|
||||
pr({ head: { ref: `${AUDIT_HEAD_PREFIX}pass-6`, sha: 'abc' } }),
|
||||
)
|
||||
expect(verdict).toEqual({ action: 'skip', reason: 'audit-veto' })
|
||||
})
|
||||
|
||||
it('audit/* veto dominates the draft logic — a draft audit/* never becomes mark-ready', () => {
|
||||
const verdict = decideAutomergeAction(
|
||||
pr({ draft: true, head: { ref: `${AUDIT_HEAD_PREFIX}pass-6`, sha: 'abc' } }),
|
||||
)
|
||||
expect(verdict).toEqual({ action: 'skip', reason: 'audit-veto' })
|
||||
})
|
||||
|
||||
it('isAuditHead only matches the exact audit/ prefix', () => {
|
||||
expect(isAuditHead('audit/pass-6')).toBe(true)
|
||||
expect(isAuditHead('OPS71-x')).toBe(false)
|
||||
expect(isAuditHead('cursor/x')).toBe(false)
|
||||
expect(isAuditHead('auditoria/x')).toBe(false)
|
||||
expect(isAuditHead('')).toBe(false)
|
||||
expect(isAuditHead(undefined)).toBe(false)
|
||||
})
|
||||
|
||||
it('the merge contract has no poll path: ready always arms native auto-merge', () => {
|
||||
// OPS64 pin, GitHub shape: the Forgejo rollup-lie bug is structural here
|
||||
// (single job → one honest check-run), and the server only merges with
|
||||
|
|
@ -73,8 +98,9 @@ describe('github-pr-flow automerge decision (OPS71)', () => {
|
|||
const actions = [
|
||||
decideAutomergeAction(pr()).action,
|
||||
decideAutomergeAction(pr({ draft: true, head: { ref: 'cursor/x', sha: 'a' } })).action,
|
||||
decideAutomergeAction(pr({ head: { ref: 'audit/pass-6', sha: 'a' } })).action,
|
||||
]
|
||||
expect(actions).toEqual(['enable-auto-merge', 'mark-ready'])
|
||||
expect(actions).toEqual(['enable-auto-merge', 'mark-ready', 'skip'])
|
||||
})
|
||||
|
||||
it('automergeArmingToken accepts a non-empty AUTOMERGE_PAT (OPS71-FLIP)', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue