microsoft/teams.net
Publicmirrored fromhttps://github.com/microsoft/teams.netAvailable
.github/workflows/copilot-attestation-check.yml
99lines · modecode
| 1 | name: Copilot PR Attestation Check |
| 2 | |
| 3 | on: |
| 4 | pull_request: |
| 5 | branches: ['main'] |
| 6 | types: [opened, synchronize, reopened] |
| 7 | issue_comment: |
| 8 | types: [created, edited] |
| 9 | pull_request_review_comment: |
| 10 | types: [created, edited] |
| 11 | |
| 12 | permissions: |
| 13 | pull-requests: write |
| 14 | issues: write |
| 15 | contents: read |
| 16 | |
| 17 | jobs: |
| 18 | check-attestation: |
| 19 | name: Check Copilot Attestation |
| 20 | runs-on: ubuntu-latest |
| 21 | if: | |
| 22 | (github.event_name == 'pull_request' && github.event.pull_request.user.login == 'Copilot') || |
| 23 | (github.event_name == 'issue_comment' && contains(github.event.comment.html_url, '/pull/')) || |
| 24 | (github.event_name == 'pull_request_review_comment') |
| 25 | steps: |
| 26 | - name: Check for attestation comment |
| 27 | uses: actions/github-script@v7 |
| 28 | with: |
| 29 | script: | |
| 30 | // Handle both pull_request and issue_comment events |
| 31 | const prNumber = context.payload.pull_request?.number || context.payload.issue?.number; |
| 32 | |
| 33 | // For issue_comment events, we need to fetch the PR details |
| 34 | let pullRequest; |
| 35 | if (context.payload.pull_request) { |
| 36 | pullRequest = context.payload.pull_request; |
| 37 | } else { |
| 38 | const pr = await github.rest.pulls.get({ |
| 39 | owner: context.repo.owner, |
| 40 | repo: context.repo.repo, |
| 41 | pull_number: prNumber |
| 42 | }); |
| 43 | pullRequest = pr.data; |
| 44 | } |
| 45 | |
| 46 | const prCreator = pullRequest.user.login; |
| 47 | |
| 48 | // Skip if not a Copilot PR |
| 49 | if (prCreator !== 'Copilot') { |
| 50 | console.log(`Skipping - PR not created by Copilot (creator: ${prCreator})`); |
| 51 | return; |
| 52 | } |
| 53 | |
| 54 | console.log(`PR #${prNumber} created by: ${prCreator}`); |
| 55 | console.log(`Event triggered by: ${context.payload.sender.login}`); |
| 56 | |
| 57 | // Get all assignees excluding Copilot (these should be human requestors) |
| 58 | const assignees = pullRequest.assignees || []; |
| 59 | const humanAssignees = assignees |
| 60 | .filter(a => a.login !== prCreator) |
| 61 | .map(a => a.login); |
| 62 | |
| 63 | console.log(`Human assignees (potential requestors): ${humanAssignees.join(', ')}`); |
| 64 | |
| 65 | // Get all comments on the PR |
| 66 | const comments = await github.rest.issues.listComments({ |
| 67 | owner: context.repo.owner, |
| 68 | repo: context.repo.repo, |
| 69 | issue_number: prNumber |
| 70 | }); |
| 71 | |
| 72 | console.log(`Found ${comments.data.length} comments`); |
| 73 | |
| 74 | // Check if any comment from human assignees contains attestation |
| 75 | const attestationPhrases = [ |
| 76 | 'I attest that I have verified', |
| 77 | 'I attest that no verification is required' |
| 78 | ]; |
| 79 | |
| 80 | const attestations = comments.data.filter(comment => { |
| 81 | const isFromHumanAssignee = humanAssignees.includes(comment.user.login); |
| 82 | const containsAttestation = attestationPhrases.some(phrase => |
| 83 | comment.body.includes(phrase) |
| 84 | ); |
| 85 | |
| 86 | if (isFromHumanAssignee && containsAttestation) { |
| 87 | console.log(`✓ Found attestation in comment #${comment.id} from ${comment.user.login}`); |
| 88 | console.log(`Comment: ${comment.body.substring(0, 100)}...`); |
| 89 | } |
| 90 | |
| 91 | return isFromHumanAssignee && containsAttestation; |
| 92 | }); |
| 93 | |
| 94 | if (attestations.length > 0) { |
| 95 | console.log(`✓ Attestation found from: ${attestations.map(a => a.user.login).join(', ')}`); |
| 96 | } else { |
| 97 | console.log(`✗ No attestation found from any human assignee`); |
| 98 | core.setFailed(`At least one human assignee (${humanAssignees.join(', ')}) must attest with either "I attest that I have verified" or "I attest that no verification is required"`); |
| 99 | } |
| 100 | |