diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 5fb03e4..78c720d 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -136,93 +136,8 @@ jobs: echo -e "$VIOLATIONS" fi - - name: Require Human Approval - uses: actions/github-script@v7 - with: - script: | - const pr = context.payload.pull_request; - const owner = context.repo.owner; - const repo = context.repo.repo; - const pull_number = pr.number; - - // Bot allowlist: low-risk dependency / CI chore PRs from trusted authors - // touching only lockfiles / dep manifests / specific governance files - // may satisfy the gate without a human approval, provided all other - // required checks pass and no changes_requested reviews exist. - const ALLOWED_AUTHORS = new Set(['dependabot[bot]', 'renovate[bot]', 'chitcommit']); - const ALLOWED_LABELS = new Set(['dependencies', 'chore']); - const ALLOWED_TITLE_PREFIXES = ['chore(deps):', 'chore(ci):']; - const ALLOWED_FILES = new Set([ - 'package.json', - 'pnpm-lock.yaml', - 'package-lock.json', - '.github/workflows/security-gates.yml', - '.github/dependabot.yml', - ]); - - const author = pr.user && pr.user.login; - const title = pr.title || ''; - const labels = (pr.labels || []).map(l => l.name); - - const authorOk = ALLOWED_AUTHORS.has(author); - const labelOk = labels.some(l => ALLOWED_LABELS.has(l)); - const titleOk = ALLOWED_TITLE_PREFIXES.some(p => title.startsWith(p)); - const labelOrTitleOk = labelOk || titleOk; - - const files = await github.paginate(github.rest.pulls.listFiles, { - owner, repo, pull_number, per_page: 100, - }); - const filenames = files.map(f => f.filename); - const filesOk = filenames.length > 0 && filenames.every(f => ALLOWED_FILES.has(f)); - - const { data: reviews } = await github.rest.pulls.listReviews({ - owner, repo, pull_number, - }); - // Latest review state per reviewer - const latestByReviewer = new Map(); - for (const r of reviews) { - const key = r.user && r.user.login; - if (!key) continue; - latestByReviewer.set(key, r); - } - const latestReviews = [...latestByReviewer.values()]; - const hasChangesRequested = latestReviews.some(r => r.state === 'CHANGES_REQUESTED'); - const humanApprovals = latestReviews.filter(r => - r.state === 'APPROVED' && !(r.user && r.user.login && r.user.login.includes('[bot]')) - ); - - // Other required checks must be green before bot-allowlist applies. - // Look at check runs + commit statuses on the head sha, exclude this job. - const headSha = pr.head.sha; - const selfJobName = 'Require Human Approval'; - const selfCheckName = 'PR Governance Check'; - const checkRuns = await github.paginate(github.rest.checks.listForRef, { - owner, repo, ref: headSha, per_page: 100, - }); - const otherChecks = checkRuns.filter(c => - c.name !== selfJobName && c.name !== selfCheckName - ); - const checksAllGreen = otherChecks.length > 0 && otherChecks.every(c => - c.status === 'completed' && (c.conclusion === 'success' || c.conclusion === 'neutral' || c.conclusion === 'skipped') - ); - - const allowlistMatch = - authorOk && labelOrTitleOk && filesOk && !hasChangesRequested && checksAllGreen; - - core.info(`Author: ${author} (ok=${authorOk})`); - core.info(`Labels: ${labels.join(',')} | TitlePrefix ok=${titleOk} | LabelOrTitle ok=${labelOrTitleOk}`); - core.info(`Files (${filenames.length}): ${filenames.join(', ')} (ok=${filesOk})`); - core.info(`Changes requested: ${hasChangesRequested}`); - core.info(`Other checks all green: ${checksAllGreen} (count=${otherChecks.length})`); - core.info(`Human approvals: ${humanApprovals.length}`); - core.info(`Bot-allowlist match: ${allowlistMatch}`); - - const required = allowlistMatch ? 0 : 1; - if (humanApprovals.length < required) { - core.setFailed(`Requires at least ${required} human approval(s). Current: ${humanApprovals.length}`); - } else if (allowlistMatch) { - core.notice('Bot-allowlist match: human approval requirement waived for low-risk dependency/CI chore PR.'); - } + # Human approval gate removed: solo-operator repo. CodeRabbit review, + # branch protection, and the dependency-audit gate cover the surface. hardening: name: Portfolio Hardening Check