docs(examples): load committed manifest and Rego in the Foundry example #3160
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Consolidated AI PR review pipeline for agent-governance-toolkit. | |
| # Runs 5 AI analysis agents in parallel on every PR: | |
| # code-review, security-scan, breaking-change, docs-sync, test-coverage | |
| # Results are posted as individual PR comments, then summarized by the | |
| # "Post unified summary" job into a single verdict table. | |
| # | |
| # Replaces: ai-code-review.yml, ai-security-scan.yml (PR job only), | |
| # ai-breaking-change-detector.yml, ai-docs-sync.yml, ai-test-generator.yml, | |
| # ai-pr-summary.yml | |
| name: AI PR Review | |
| # SECURITY: Uses pull_request_target for write access to post PR comments. | |
| # All checkouts pin to BASE ref (never HEAD) to prevent RCE via modified | |
| # composite actions in fork PRs. See workflow security audit. | |
| on: | |
| pull_request_target: | |
| types: [opened, synchronize, reopened] | |
| branches: [main] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| permissions: {} | |
| # SECURITY: pull_request_target runs in BASE context. Never checkout PR head ref. | |
| jobs: | |
| ai-agents: | |
| name: ${{ matrix.label }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: write | |
| models: read | |
| if: >- | |
| github.event.pull_request.draft != true && | |
| github.actor != 'dependabot[bot]' | |
| continue-on-error: true | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - agent-type: code-reviewer | |
| label: "Code Review" | |
| icon: "🔍" | |
| max-tokens: "2000" | |
| paths-changed: "any" | |
| custom-instructions: | | |
| You are reviewing microsoft/agent-governance-toolkit, a security-focused Python monorepo (8 packages, pytest, ruff). | |
| CRITICAL OUTPUT RULES -- your review MUST follow this exact format: | |
| 1. Start with: **TL;DR**: N blockers, M warnings. [one-sentence verdict]. | |
| 2. Summary table (one row per finding, one-line descriptions only): | |
| | # | Sev | Issue | Where | | |
| |---|-----|-------|-------| | |
| 3. Action items: one sentence per blocker only. No code blocks in the summary. | |
| 4. Warnings: list in table, mark as "fine as follow-up PRs." | |
| 5. Nits: do NOT include. Omit entirely. | |
| DO NOT write multi-paragraph explanations, conclusions, or suggestions sections. | |
| DO NOT use headers like "CRITICAL", "WARNING", "SUGGESTIONS", "Conclusion". | |
| DO NOT exceed 200 words total. The contributor knows how to code. | |
| If no issues found, reply only: No issues found. Clean change. | |
| Focus areas (what to flag, not what to write about): | |
| - Policy engine correctness (false negatives = security bypass) | |
| - Trust/identity: crypto, credentials, SPIFFE | |
| - Sandbox escape, thread safety, OWASP Agentic Top 10 | |
| - Backward compatibility (public API surface changes) | |
| - Pydantic model validation gaps | |
| - agent-type: security-scanner | |
| label: "Security Scan" | |
| icon: "🛡️" | |
| max-tokens: "1500" | |
| paths-changed: "code" | |
| custom-instructions: | | |
| You are a security analyst reviewing changes to microsoft/agent-governance-toolkit. | |
| BE CONCISE. Only report actual findings, not a methodology walkthrough. | |
| Skip categories with no issues. No boilerplate. | |
| Scan for: | |
| 1. Prompt injection defense bypass | |
| 2. Policy engine circumvention | |
| 3. Trust chain weaknesses | |
| 4. Credential exposure | |
| 5. Sandbox escape | |
| 6. Deserialization attacks | |
| 7. Race conditions | |
| 8. Supply chain risks | |
| Format (compact): | |
| ### Security Review | |
| | Severity | Finding | Fix | | |
| |----------|---------|-----| | |
| If NO security issues found, reply only: No security issues found. | |
| - agent-type: breaking-change-detector | |
| label: "Breaking Changes" | |
| icon: "🔄" | |
| max-tokens: "1000" | |
| paths-changed: "src" | |
| custom-instructions: | | |
| You are an API compatibility analyzer for microsoft/agent-governance-toolkit. | |
| These packages are published to PyPI/NuGet/crates.io. | |
| BE CONCISE. Only list actual breaking or potentially breaking changes. | |
| Do NOT list additive (non-breaking) changes. | |
| Format: | |
| ### API Compatibility | |
| | Severity | Change | Impact | | |
| |----------|--------|--------| | |
| If NO breaking changes: No breaking changes detected. | |
| - agent-type: docs-sync-checker | |
| label: "Docs Sync" | |
| icon: "📝" | |
| max-tokens: "1000" | |
| paths-changed: "src" | |
| custom-instructions: | | |
| You are a documentation freshness checker for microsoft/agent-governance-toolkit. | |
| BE CONCISE. Only flag actual issues, no suggestions or nice-to-haves. | |
| Check for: | |
| 1. New public APIs without docstrings | |
| 2. README sections outdated by this change | |
| 3. CHANGELOG missing entry for behavioral changes | |
| Format: | |
| ### Docs Sync | |
| - `func()` in `module.py` -- missing docstring | |
| - `README.md` -- section X needs update | |
| If everything is in sync: Documentation is in sync. | |
| - agent-type: test-generator | |
| label: "Test Coverage" | |
| icon: "🧪" | |
| max-tokens: "2000" | |
| paths-changed: "src" | |
| custom-instructions: | | |
| You are a test coverage advisor for microsoft/agent-governance-toolkit. | |
| CRITICAL OUTPUT RULES -- be concise: | |
| 1. Only list files with ACTUAL coverage gaps (skip files with adequate coverage). | |
| 2. For each gap, list the test name and a one-line description. No paragraphs. | |
| 3. Group by file. Skip files where changes are already well-tested. | |
| 4. DO NOT list every possible edge case. Focus on the 3-5 highest-value missing tests. | |
| 5. DO NOT write "General Recommendations" or "Summary" sections. | |
| 6. DO NOT exceed 300 words total. | |
| Format: | |
| ### `filename.py` | |
| - `test_name` -- one-line description of what it validates | |
| - `test_name` -- one-line description | |
| If coverage is adequate: Test coverage looks good. No gaps identified. | |
| Priority for gap detection: | |
| - Untested new public methods/classes | |
| - Security-critical paths without negative tests | |
| - Missing boundary/error cases on policy evaluation | |
| steps: | |
| - name: Fork safety check | |
| if: github.event.pull_request.head.repo.full_name != github.repository | |
| run: echo "::notice::Running on fork PR -- composite action resolved from base branch (safe)" | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| # SECURITY: pull_request_target defaults to base branch checkout (safe). | |
| persist-credentials: false | |
| fetch-depth: 0 | |
| - name: Run ${{ matrix.label }} | |
| uses: ./.github/actions/ai-agent-runner | |
| with: | |
| agent-type: ${{ matrix.agent-type }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| model: gpt-4o | |
| fallback-model: gpt-4o | |
| max-tokens: ${{ matrix.max-tokens }} | |
| context-mode: pr-diff | |
| output-mode: pr-comment | |
| custom-instructions: ${{ matrix.custom-instructions }} | |
| # ── Unified summary ──────────────────────────────────────────────────── | |
| # Waits for all agents, then posts/updates a single summary table. | |
| summary: | |
| name: Post unified summary | |
| runs-on: ubuntu-latest | |
| needs: [ai-agents] | |
| if: always() | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Collect agent comments and post summary | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| continue-on-error: true | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const prNumber = context.payload.pull_request.number; | |
| const SUMMARY_MARKER = '<!-- ai-pr-summary -->'; | |
| // RUN_MARKER intentionally uses GITHUB_RUN_ID only — not | |
| // GITHUB_RUN_ATTEMPT. The aggregator filters PR comments to ones | |
| // posted by *this* workflow run; including RUN_ATTEMPT would skip | |
| // comments from a previous attempt that may still be valid (e.g. | |
| // a flaky job that succeeded on its first attempt but failed an | |
| // unrelated job, triggering a re-run). RUN_ID is unique per run | |
| // and stable across attempts. | |
| const RUN_MARKER = `<!-- ai-agent-run:${process.env.GITHUB_RUN_ID} -->`; | |
| const AGENTS = [ | |
| { marker: '<!-- ai-agent:code-reviewer -->', icon: '🔍', label: 'Code Review' }, | |
| { marker: '<!-- ai-agent:security-scanner -->', icon: '🛡️', label: 'Security Scan' }, | |
| { marker: '<!-- ai-agent:breaking-change-detector -->', icon: '🔄', label: 'Breaking Changes' }, | |
| { marker: '<!-- ai-agent:docs-sync-checker -->', icon: '📝', label: 'Docs Sync' }, | |
| { marker: '<!-- ai-agent:test-generator -->', icon: '🧪', label: 'Test Coverage' }, | |
| ]; | |
| let allComments = []; | |
| let page = 1; | |
| while (true) { | |
| const { data: batch } = await github.rest.issues.listComments({ | |
| owner, repo, issue_number: prNumber, per_page: 100, page, | |
| }); | |
| if (!batch.length) break; | |
| allComments = allComments.concat(batch); | |
| if (batch.length < 100) break; | |
| page++; | |
| } | |
| function isWorkflowComment(comment) { | |
| return comment?.user?.login === 'github-actions[bot]'; | |
| } | |
| function parseAgentStatus(body) { | |
| if (!body || !body.includes('<!-- ai-agent-status:completed -->') || !body.includes(RUN_MARKER)) { | |
| return { status: '⚠️', statusLabel: 'Missing', detail: 'No current-run comment' }; | |
| } | |
| return { status: '✅', statusLabel: 'Completed', detail: 'Review comment posted; verify AI output' }; | |
| } | |
| const rows = AGENTS.map(agent => { | |
| const comment = allComments.find(c => isWorkflowComment(c) && c.body && c.body.includes(agent.marker) && c.body.includes(RUN_MARKER)); | |
| const verdict = comment ? parseAgentStatus(comment.body) : { status: '⚠️', statusLabel: 'Missing', detail: 'No current-run comment' }; | |
| return `| ${agent.icon} ${agent.label} | ${verdict.status} ${verdict.statusLabel} | ${verdict.detail} |`; | |
| }); | |
| const overallVerdict = rows.some(r => r.includes('⚠️')) | |
| ? '**Verdict: ⚠️ AI review incomplete; ready for human review**' | |
| : '**Verdict: ✅ Ready for human review**'; | |
| const summaryBody = [ | |
| SUMMARY_MARKER, | |
| '## PR Review Summary', | |
| '', | |
| '| Check | Status | Details |', | |
| '|-------|--------|---------|', | |
| ...rows, | |
| '', | |
| overallVerdict, | |
| '', | |
| '_AI review comments are untrusted advisory output. The summary reports workflow-generated completion status only, not model-authored pass/fail claims._', | |
| ].join('\n'); | |
| const existing = allComments.find(c => isWorkflowComment(c) && c.body && c.body.includes(SUMMARY_MARKER)); | |
| try { | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| owner, repo, comment_id: existing.id, body: summaryBody, | |
| }); | |
| core.info(`Updated summary comment ${existing.id}`); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, repo, issue_number: prNumber, body: summaryBody, | |
| }); | |
| core.info('Created summary comment'); | |
| } | |
| } catch (error) { | |
| core.warning(`Unable to post PR summary comment: ${error.message}`); | |
| if (error?.status === 403 && String(error?.message || '').includes('Resource not accessible')) { | |
| core.warning('Unable to post advisory AI review summary because this run token cannot write PR comments.'); | |
| } else { | |
| throw error; | |
| } | |
| } | |
| await core.summary | |
| .addHeading('PR Review Summary') | |
| .addRaw(summaryBody, true) | |
| .write(); |