From d8569a496fcb037eec00a02f78ef2fe1c507d713 Mon Sep 17 00:00:00 2001 From: Christian Klotz Date: Mon, 21 Sep 2026 09:16:28 +0200 Subject: [PATCH] fix(coding-agent): dispatch compatibility checks from PR gate --- .../pi-dev-model-catalog-compatibility.yml | 148 ------------------ .github/workflows/pr-gate.yml | 51 +++++- 2 files changed, 50 insertions(+), 149 deletions(-) delete mode 100644 .github/workflows/pi-dev-model-catalog-compatibility.yml diff --git a/.github/workflows/pi-dev-model-catalog-compatibility.yml b/.github/workflows/pi-dev-model-catalog-compatibility.yml deleted file mode 100644 index 931de0729..000000000 --- a/.github/workflows/pi-dev-model-catalog-compatibility.yml +++ /dev/null @@ -1,148 +0,0 @@ -name: pi.dev model catalog compatibility - -on: - pull_request_target: - branches: [main] - types: [opened, reopened, synchronize, ready_for_review] - -permissions: {} - -concurrency: - group: pi-dev-model-catalog-compatibility-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - authorize: - name: Authorize contributor - runs-on: ubuntu-latest - permissions: - contents: read - outputs: - approved: ${{ steps.authorize.outputs.approved }} - steps: - - name: Check contributor approval - id: authorize - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const trustedBots = new Set(['dependabot[bot]', 'sentry[bot]', 'claude[bot]']); - const author = context.payload.pull_request.user.login; - let approved = trustedBots.has(author); - - if (!approved && !author.endsWith('[bot]')) { - try { - const { data } = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username: author, - }); - approved = ['admin', 'maintain', 'write'].includes(data.permission); - } catch { - approved = false; - } - } - - if (!approved && !author.endsWith('[bot]')) { - const { data } = await github.rest.repos.getContent({ - owner: context.repo.owner, - repo: context.repo.repo, - path: '.github/APPROVED_CONTRIBUTORS', - ref: context.payload.repository.default_branch, - }); - if (!('content' in data) || typeof data.content !== 'string') { - throw new Error('Expected .github/APPROVED_CONTRIBUTORS to be a file'); - } - const capabilities = new Map(); - for (const rawLine of Buffer.from(data.content, 'base64').toString('utf8').split('\n')) { - const line = rawLine.trim(); - if (!line || line.startsWith('#')) continue; - - const parts = line.split(/\s+/); - if (parts.length !== 2) continue; - - const [username, capability] = parts; - const normalizedCapability = capability.toLowerCase(); - if (normalizedCapability === 'issue' || normalizedCapability === 'pr') { - capabilities.set(username.toLowerCase(), normalizedCapability); - } - } - approved = capabilities.get(author.toLowerCase()) === 'pr'; - } - - core.setOutput('approved', String(approved)); - - dispatch: - if: ${{ needs.authorize.outputs.approved == 'true' && !github.event.pull_request.draft }} - name: Dispatch private compatibility tests - needs: authorize - runs-on: ubuntu-latest - permissions: - statuses: write - steps: - - name: Set pending status - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - PI_SHA: ${{ github.event.pull_request.head.sha }} - SOURCE_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - with: - github-token: ${{ github.token }} - script: | - await github.rest.repos.createCommitStatus({ - owner: context.repo.owner, - repo: context.repo.repo, - sha: process.env.PI_SHA, - state: 'pending', - context: 'pi.dev / model catalog compatibility', - description: 'Waiting for private pi.dev compatibility tests', - target_url: process.env.SOURCE_RUN_URL, - }); - - - name: Dispatch tests - id: dispatch - continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - PI_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }} - PI_SHA: ${{ github.event.pull_request.head.sha }} - PI_PR_NUMBER: ${{ github.event.pull_request.number }} - with: - github-token: ${{ secrets.PI_DEV_PAT }} - script: | - const sha = process.env.PI_SHA; - const [headOwner, headRepo, ...extraParts] = process.env.PI_REPOSITORY.split('/'); - - if (!headOwner || !headRepo || extraParts.length > 0 || !/^[0-9a-f]{40}$/.test(sha)) { - core.setFailed('Invalid pull request repository or commit SHA'); - return; - } - - await github.rest.repos.createDispatchEvent({ - owner: 'earendil-works', - repo: 'pi.dev', - event_type: 'pi-model-catalog-compatibility', - client_payload: { - repository: `${headOwner}/${headRepo}`, - sha, - pr_number: Number(process.env.PI_PR_NUMBER), - }, - }); - - - name: Report dispatch failure - if: steps.dispatch.outcome == 'failure' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - PI_SHA: ${{ github.event.pull_request.head.sha }} - SOURCE_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - with: - github-token: ${{ github.token }} - script: | - await github.rest.repos.createCommitStatus({ - owner: context.repo.owner, - repo: context.repo.repo, - sha: process.env.PI_SHA, - state: 'error', - context: 'pi.dev / model catalog compatibility', - description: 'Could not dispatch pi.dev compatibility tests', - target_url: process.env.SOURCE_RUN_URL, - }); - core.setFailed('Could not dispatch pi.dev compatibility tests'); diff --git a/.github/workflows/pr-gate.yml b/.github/workflows/pr-gate.yml index bc6f7eef1..8adce3c29 100644 --- a/.github/workflows/pr-gate.yml +++ b/.github/workflows/pr-gate.yml @@ -2,17 +2,24 @@ name: PR Gate on: pull_request_target: - types: [opened] + types: [opened, reopened, synchronize, ready_for_review] + +concurrency: + group: pr-gate-${{ github.event.pull_request.number }} + cancel-in-progress: true jobs: check-contributor: runs-on: ubuntu-latest + outputs: + approved: ${{ steps.check.outputs.approved }} permissions: contents: read issues: write pull-requests: write steps: - name: Check if contributor is approved + id: check uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | @@ -22,9 +29,11 @@ jobs: const prAuthor = context.payload.pull_request.user.login; const defaultBranch = context.payload.repository.default_branch; const isBotAuthor = prAuthor.endsWith('[bot]'); + core.setOutput('approved', 'false'); if (TRUSTED_BOT_AUTHORS.has(prAuthor)) { console.log(`Skipping trusted bot: ${prAuthor}`); + core.setOutput('approved', 'true'); return; } @@ -101,6 +110,7 @@ jobs: const permission = await getPermission(prAuthor); if (!isBotAuthor && ['admin', 'maintain', 'write'].includes(permission)) { console.log(`${prAuthor} is a collaborator with ${permission} access`); + core.setOutput('approved', 'true'); return; } @@ -110,6 +120,7 @@ jobs: if (!isBotAuthor && capability === 'pr') { console.log(`${prAuthor} is approved for PRs`); + core.setOutput('approved', 'true'); return; } @@ -126,3 +137,41 @@ jobs: ].join('\n'); await closePullRequest(message); + + dispatch-pi-dev: + if: ${{ needs.check-contributor.outputs.approved == 'true' && !github.event.pull_request.draft }} + name: Dispatch pi.dev compatibility tests + needs: check-contributor + runs-on: ubuntu-latest + permissions: {} + steps: + - name: Dispatch tests + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + PI_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }} + PI_SHA: ${{ github.event.pull_request.head.sha }} + PI_PR_NUMBER: ${{ github.event.pull_request.number }} + with: + github-token: ${{ secrets.PI_DEV_PAT }} + script: | + const [owner, repo, ...extraParts] = process.env.PI_REPOSITORY.split('/'); + const sha = process.env.PI_SHA; + const prNumber = Number(process.env.PI_PR_NUMBER); + + if (!owner || !repo || extraParts.length > 0 || !/^[0-9a-f]{40}$/.test(sha)) { + throw new Error('Invalid pull request repository or commit SHA'); + } + if (!Number.isInteger(prNumber)) { + throw new Error('Invalid pull request number'); + } + + await github.rest.repos.createDispatchEvent({ + owner: 'earendil-works', + repo: 'pi.dev', + event_type: 'pi-model-catalog-compatibility', + client_payload: { + repository: `${owner}/${repo}`, + sha, + pr_number: prNumber, + }, + });