From e3261c3e37ef46b2154dd0c0ca74999311966c6b Mon Sep 17 00:00:00 2001 From: Luke Mino-Altherr Date: Wed, 27 May 2026 18:47:23 -0700 Subject: [PATCH] Replace detector with thin caller of Comfy-Org/github-workflows Shrinks this workflow from ~210 lines to ~20 by delegating the detection logic to a centralized reusable workflow in Comfy-Org/github-workflows. Future changes ship from that one repo and propagate here automatically. --- .github/workflows/detect-unreviewed-merge.yml | 209 +----------------- 1 file changed, 8 insertions(+), 201 deletions(-) diff --git a/.github/workflows/detect-unreviewed-merge.yml b/.github/workflows/detect-unreviewed-merge.yml index f957328b5..f26f4171d 100644 --- a/.github/workflows/detect-unreviewed-merge.yml +++ b/.github/workflows/detect-unreviewed-merge.yml @@ -1,5 +1,8 @@ name: Detect Unreviewed Merge +# SOC 2 compliance — reusable workflow lives in Comfy-Org/github-workflows, +# tracking issues are filed in Comfy-Org/unreviewed-merges. + on: push: branches: [master] @@ -8,206 +11,10 @@ concurrency: group: detect-unreviewed-merge-${{ github.sha }} cancel-in-progress: false -permissions: - contents: read - pull-requests: read - jobs: detect: - runs-on: ubuntu-latest - steps: - - name: Check for unreviewed merge - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 - env: - UNREVIEWED_MERGES_TOKEN: ${{ secrets.UNREVIEWED_MERGES_TOKEN }} - with: - script: | - const sha = context.sha; - const { owner, repo } = context.repo; - const branch = context.ref.replace('refs/heads/', ''); - - // Find the PR associated with this merge commit - const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({ - owner, - repo, - commit_sha: sha, - }); - - const pr = prs.find(p => p.merged_at && p.base.ref === branch); - if (!pr) { - core.info('No merged PR found for this commit — skipping.'); - return; - } - - core.info(`Found PR #${pr.number}: ${pr.title}`); - - // Fetch full PR to get merged_by (not in pull-request-simple schema) - const { data: fullPr } = await github.rest.pulls.get({ - owner, - repo, - pull_number: pr.number, - }); - - // Check for approving reviews using latest review per reviewer. - // DISMISSED approvals (from "dismiss stale reviews on new commits") - // are intentionally NOT counted — OSS PRs require current approval. - const reviews = await github.paginate(github.rest.pulls.listReviews, { - owner, - repo, - pull_number: pr.number, - }); - - const latestByReviewer = new Map(); - for (const r of reviews) { - if (!r.user || r.state === 'COMMENTED') continue; - const prev = latestByReviewer.get(r.user.login); - if (!prev || new Date(r.submitted_at) > new Date(prev.submitted_at)) { - latestByReviewer.set(r.user.login, r); - } - } - - const hasApproval = Array.from(latestByReviewer.values()).some( - r => r.state === 'APPROVED' - ); - if (hasApproval) { - core.info('PR has an approving review — no action needed.'); - return; - } - - core.info('No approving review found. Checking for inline justification...'); - - // Search PR body and author comments for "Justification: " - const pattern = /^justification:\s*(.+)$/im; - let justification = null; - - if (pr.body) { - const match = pr.body.match(pattern); - if (match) justification = match[1].trim(); - } - - if (!justification) { - const comments = await github.paginate(github.rest.issues.listComments, { - owner, - repo, - issue_number: pr.number, - }); - - for (const c of comments) { - if (c.user && pr.user && c.user.login === pr.user.login) { - const match = c.body.match(pattern); - if (match) { - justification = match[1].trim(); - break; - } - } - } - } - - const hasJustification = !!justification; - core.info(hasJustification - ? `Found inline justification: ${justification}` - : 'No inline justification found.'); - - // Build issue body - const mergedBy = fullPr.merged_by ? fullPr.merged_by.login : 'unknown'; - const table = [ - '## Unreviewed Merge Detected', - '', - '| Field | Value |', - '|-------|-------|', - `| **Repository** | ${owner}/${repo} |`, - `| **PR** | ${owner}/${repo}#${pr.number} |`, - `| **Author** | @${pr.user.login} |`, - `| **Merged by** | @${mergedBy} |`, - `| **Merged at** | ${pr.merged_at} |`, - `| **Branch** | ${branch} |`, - ]; - - const policyRef = [ - '## Policy reference', - '', - 'Per the Secure Development Policy (Emergency Change Procedures), changes merged', - 'without prior approval must be justified and peer-reviewed within 1 business day.', - 'Unresolved items older than 3 days are escalated to engineering leadership.', - ]; - - let body; - if (hasJustification) { - body = [ - ...table, - '', - '## Justification (provided at merge time)', - '', - `> ${justification}`, - '', - '## Required actions', - '', - '- [x] **Justification** — Provided at merge time (see above)', - '- [ ] **Post-merge review** — A peer must review the PR and comment confirmation here (within 1 business day)', - '', - 'Once both items are checked, close this issue.', - '', - ...policyRef, - ].join('\n'); - } else { - body = [ - ...table, - '', - '## Required actions', - '', - '- [ ] **Justification** — Author must comment on this issue explaining why pre-merge approval was bypassed (within 1 business day)', - '- [ ] **Post-merge review** — A peer must review the PR and comment confirmation here (within 1 business day)', - '', - 'Once both items are checked, close this issue.', - '', - ...policyRef, - ].join('\n'); - } - - // Create issue in the tracking repo with the dedicated PAT - if (!process.env.UNREVIEWED_MERGES_TOKEN) { - core.setFailed('UNREVIEWED_MERGES_TOKEN secret is not configured'); - return; - } - - const { getOctokit } = require('@actions/github'); - const tracking = getOctokit(process.env.UNREVIEWED_MERGES_TOKEN); - - const repoLabel = `repo:${repo.toLowerCase()}`; - const statusLabel = hasJustification ? 'needs-review' : 'needs-justification'; - - // Idempotency check — avoid duplicate issues on workflow re-runs - const { data: search } = await tracking.rest.search.issuesAndPullRequests({ - q: `repo:Comfy-Org/unreviewed-merges in:title "PR #${pr.number}" label:${repoLabel}`, - }); - if (search.total_count > 0) { - core.info(`Tracking issue already exists: ${search.items[0].html_url}`); - return; - } - - let issue; - try { - issue = await tracking.rest.issues.create({ - owner: 'Comfy-Org', - repo: 'unreviewed-merges', - title: `[${repo}] PR #${pr.number} — ${pr.title}`, - body, - labels: ['unreviewed-merge', statusLabel, repoLabel], - assignees: [pr.user.login], - }); - } catch (e) { - if (e.status === 422) { - core.warning(`Retrying without assignee: ${e.message}`); - issue = await tracking.rest.issues.create({ - owner: 'Comfy-Org', - repo: 'unreviewed-merges', - title: `[${repo}] PR #${pr.number} — ${pr.title}`, - body, - labels: ['unreviewed-merge', statusLabel, repoLabel], - }); - } else { - throw e; - } - } - - core.info(`Created tracking issue: ${issue.data.html_url}`); + uses: Comfy-Org/github-workflows/.github/workflows/detect-unreviewed-merge.yml@v1 + with: + approval-mode: latest-per-reviewer + secrets: + UNREVIEWED_MERGES_TOKEN: ${{ secrets.UNREVIEWED_MERGES_TOKEN }}