From aebdd3fd01cb72e0c4b16bc5b6db0f0c5e53fba6 Mon Sep 17 00:00:00 2001 From: Rodrigo Bronzelle Date: Tue, 10 Mar 2026 15:47:40 -0300 Subject: [PATCH 1/3] feat(vet): implement auto-vet patch generation and upload process --- .github/workflows/dependabot-auto-vet.yml | 132 +++++++++++++++++----- 1 file changed, 101 insertions(+), 31 deletions(-) diff --git a/.github/workflows/dependabot-auto-vet.yml b/.github/workflows/dependabot-auto-vet.yml index 3547dfe7c..4dc306cc9 100644 --- a/.github/workflows/dependabot-auto-vet.yml +++ b/.github/workflows/dependabot-auto-vet.yml @@ -23,6 +23,7 @@ jobs: CODEX_MODEL: "gpt-5-codex" CRITERIA: "safe-to-deploy" CONTEXT_FILE: "supply-chain/vet/VETTING_POLICY.md" + RETENTION_DAYS: "90" # Prompt size guards (avoid accidental huge contexts) @@ -367,38 +368,64 @@ jobs: exit 0 # ------------------------- - # Commit & push (even if some crates unvetted) + # Patch artifact publication # ------------------------- - - name: Commit audit changes if any (signed) - id: signed_commit - if: github.event_name == 'pull_request' - uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 - with: - sign-commits: true - commit-message: "chore(vet): apply automated audits" - add-paths: | - supply-chain - branch: ${{ github.event.pull_request.head.ref }} - base: ${{ github.event.pull_request.base.ref }} - - - name: Expose commit outputs - id: commit + - name: Generate auto-vet patch + id: patch if: always() run: | set -euo pipefail - op="${{ steps.signed_commit.outputs.pull-request-operation }}" - sha="${{ steps.signed_commit.outputs.pull-request-head-sha }}" + patch_path="vet/auto-vet.patch" - if [ "$op" = "none" ] || [ -z "$sha" ]; then - echo "changed=false" >> "$GITHUB_OUTPUT" - echo "pushed=false" >> "$GITHUB_OUTPUT" + if git diff --quiet -- supply-chain; then + echo "has_patch=false" >> "$GITHUB_OUTPUT" + echo "patch_path=" >> "$GITHUB_OUTPUT" + echo "patch_bytes=0" >> "$GITHUB_OUTPUT" exit 0 fi - echo "changed=true" >> "$GITHUB_OUTPUT" - echo "sha=$sha" >> "$GITHUB_OUTPUT" - echo "pushed=true" >> "$GITHUB_OUTPUT" + git diff --binary --patch -- supply-chain > "$patch_path" + + if [ ! -s "$patch_path" ]; then + echo "Expected patch file at $patch_path, but it was empty." >&2 + exit 1 + fi + + patch_bytes="$(wc -c < "$patch_path" | tr -d '[:space:]')" + echo "has_patch=true" >> "$GITHUB_OUTPUT" + echo "patch_path=$patch_path" >> "$GITHUB_OUTPUT" + echo "patch_bytes=$patch_bytes" >> "$GITHUB_OUTPUT" + + - name: Upload auto-vet patch artifact + id: upload_patch + if: github.event_name == 'pull_request' && steps.patch.outputs.has_patch == 'true' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: dependabot-auto-vet-patch-pr-${{ github.event.pull_request.number }} + path: ${{ steps.patch.outputs.patch_path }} + if-no-files-found: error + retention-days: ${{ env.RETENTION_DAYS }} + + - name: Expose patch outputs + id: patch_meta + if: always() + run: | + set -euo pipefail + + generated="${{ steps.patch.outputs.has_patch || 'false' }}" + uploaded="false" + if [ "${{ steps.upload_patch.outcome || 'skipped' }}" = "success" ]; then + uploaded="true" + fi + + echo "generated=$generated" >> "$GITHUB_OUTPUT" + echo "uploaded=$uploaded" >> "$GITHUB_OUTPUT" + echo "artifact_name=dependabot-auto-vet-patch-pr-${{ github.event.pull_request.number || 'manual' }}" >> "$GITHUB_OUTPUT" + echo "artifact_id=${{ steps.upload_patch.outputs.artifact-id || '' }}" >> "$GITHUB_OUTPUT" + echo "artifact_url=${{ steps.upload_patch.outputs.artifact-url || '' }}" >> "$GITHUB_OUTPUT" + echo "run_url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> "$GITHUB_OUTPUT" + echo "retention_days=${{ env.RETENTION_DAYS }}" >> "$GITHUB_OUTPUT" # ------------------------- # PR Comment (consolidated) @@ -418,9 +445,13 @@ jobs: const importChanged = `${{ steps.detect_import_changes.outputs.import_changed || 'false' }}` === 'true'; const codexInitOk = `${{ steps.codex_init_status.outputs.codex_init_ok || 'false' }}` === 'true'; - const changed = `${{ steps.commit.outputs.changed || 'false' }}` === 'true'; - const pushed = `${{ steps.commit.outputs.pushed || 'false' }}` === 'true'; - const sha = `${{ steps.commit.outputs.sha || '' }}`.trim(); + const patchGenerated = `${{ steps.patch_meta.outputs.generated || 'false' }}` === 'true'; + const patchUploaded = `${{ steps.patch_meta.outputs.uploaded || 'false' }}` === 'true'; + const artifactName = `${{ steps.patch_meta.outputs.artifact_name || '' }}`.trim(); + const artifactId = `${{ steps.patch_meta.outputs.artifact_id || '' }}`.trim(); + const artifactUrl = `${{ steps.patch_meta.outputs.artifact_url || '' }}`.trim(); + const runUrl = `${{ steps.patch_meta.outputs.run_url || '' }}`.trim(); + const retentionDays = `${{ steps.patch_meta.outputs.retention_days || '' }}`.trim(); const vetAfterStatus = `${{ steps.verify_after.outputs.status }}`.trim(); const lines = []; @@ -435,18 +466,51 @@ jobs: } lines.push(''); - if (changed) { - lines.push(`- **Audit files updated:** yes`); - if (sha) lines.push(`- **Commit:** ${sha}`); - lines.push(`- **Pushed to PR branch:** ${pushed ? 'yes' : 'no (push may be restricted for this actor/branch)'}`); + if (patchGenerated) { + lines.push(`- **Patch generated:** yes`); + lines.push(`- **Artifact uploaded:** ${patchUploaded ? 'yes' : 'no'}`); + if (artifactName) lines.push(`- **Artifact name:** ${artifactName}`); + if (artifactId) lines.push(`- **Artifact ID:** ${artifactId}`); + if (retentionDays) lines.push(`- **Retention:** ${retentionDays} days`); + if (artifactUrl) { + lines.push(`- **Artifact download:** ${artifactUrl}`); + } else if (runUrl) { + lines.push(`- **Workflow run:** ${runUrl}`); + } } else { - lines.push(`- **Audit files updated:** no changes to commit`); + lines.push(`- **Patch generated:** no audit files were produced`); } if (!hasCases) { lines.push(`- **cargo vet import updates:** ${importChanged ? 'detected (no diffs required)' : 'none detected'}`); } + lines.push(''); + lines.push('CI did not commit anything. Review the patch locally and create the final signed commit yourself.'); + + if (patchUploaded) { + const downloadUrl = artifactUrl || ''; + lines.push(''); + lines.push('### Apply the patch locally'); + lines.push('The patch artifact is attached to this workflow run as a zip archive. Download it, extract `auto-vet.patch`, review the result, then create your signed commit.'); + if (!artifactUrl && runUrl) { + lines.push(`If direct artifact download is unavailable here, open the workflow run and download the artifact from there: ${runUrl}`); + } + lines.push(''); + lines.push('```bash'); + lines.push('git checkout '); + lines.push('curl -L \\'); + lines.push(' -H "Authorization: Bearer " \\'); + lines.push(' -o auto-vet-artifact.zip \\'); + lines.push(` ${downloadUrl}`); + lines.push('unzip -p auto-vet-artifact.zip auto-vet.patch > auto-vet.patch'); + lines.push('git apply --index auto-vet.patch'); + lines.push('git status'); + lines.push('git commit -S -m "chore(vet): apply automated audits"'); + lines.push('git push'); + lines.push('```'); + } + if (vetted.length) { lines.push(''); lines.push('### ✅ Auto-certified'); @@ -470,6 +534,12 @@ jobs: body: lines.join('\n') }); + - name: Fail if patch publication did not complete + if: github.event_name == 'pull_request' && steps.patch_meta.outputs.generated == 'true' && steps.patch_meta.outputs.uploaded != 'true' + run: | + echo "Auto-vet patch generation succeeded, but the patch artifact was not uploaded." + exit 1 + # Optional: fail the job if anything unvetted remain - name: Fail if unvetted remain (optional gate) if: steps.vet_import.outputs.has_cases == 'true' && steps.reason.outputs.any_unvetted == 'true' From 61ff5b6f5b5ba989dffa77f3d780db9fde3377f8 Mon Sep 17 00:00:00 2001 From: Rodrigo Bronzelle Date: Wed, 11 Mar 2026 17:12:47 -0300 Subject: [PATCH 2/3] ci(vet): update patch download suggestion --- .github/workflows/dependabot-auto-vet.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-vet.yml b/.github/workflows/dependabot-auto-vet.yml index 4dc306cc9..2e8f3e9b5 100644 --- a/.github/workflows/dependabot-auto-vet.yml +++ b/.github/workflows/dependabot-auto-vet.yml @@ -452,6 +452,8 @@ jobs: const artifactUrl = `${{ steps.patch_meta.outputs.artifact_url || '' }}`.trim(); const runUrl = `${{ steps.patch_meta.outputs.run_url || '' }}`.trim(); const retentionDays = `${{ steps.patch_meta.outputs.retention_days || '' }}`.trim(); + const runId = `${{ github.run_id }}`.trim(); + const prNumber = `${{ github.event.pull_request.number || '' }}`.trim(); const vetAfterStatus = `${{ steps.verify_after.outputs.status }}`.trim(); const lines = []; @@ -471,6 +473,8 @@ jobs: lines.push(`- **Artifact uploaded:** ${patchUploaded ? 'yes' : 'no'}`); if (artifactName) lines.push(`- **Artifact name:** ${artifactName}`); if (artifactId) lines.push(`- **Artifact ID:** ${artifactId}`); + if (prNumber) lines.push(`- **PR number:** ${prNumber}`); + if (runId) lines.push(`- **Run ID:** ${runId}`); if (retentionDays) lines.push(`- **Retention:** ${retentionDays} days`); if (artifactUrl) { lines.push(`- **Artifact download:** ${artifactUrl}`); @@ -497,13 +501,24 @@ jobs: lines.push(`If direct artifact download is unavailable here, open the workflow run and download the artifact from there: ${runUrl}`); } lines.push(''); + lines.push('Preferred: GitHub CLI'); + lines.push('```bash'); + lines.push('git checkout '); + lines.push(`gh run download ${runId} -n ${artifactName}`); + lines.push(`git apply --index ${artifactName}/auto-vet.patch`); + lines.push('git status'); + lines.push('git commit -S -m "chore(vet): apply automated audits"'); + lines.push('git push'); + lines.push('```'); + lines.push(''); + lines.push('Fallback: direct artifact download'); lines.push('```bash'); lines.push('git checkout '); lines.push('curl -L \\'); lines.push(' -H "Authorization: Bearer " \\'); lines.push(' -o auto-vet-artifact.zip \\'); lines.push(` ${downloadUrl}`); - lines.push('unzip -p auto-vet-artifact.zip auto-vet.patch > auto-vet.patch'); + lines.push('unzip -p auto-vet-artifact.zip vet/auto-vet.patch > auto-vet.patch'); lines.push('git apply --index auto-vet.patch'); lines.push('git status'); lines.push('git commit -S -m "chore(vet): apply automated audits"'); From 66b16970a96be6adfff3a1d30e85493d118b9a8f Mon Sep 17 00:00:00 2001 From: Rodrigo Bronzelle Date: Wed, 11 Mar 2026 17:35:12 -0300 Subject: [PATCH 3/3] ci(vet): fix patch name --- .github/workflows/dependabot-auto-vet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dependabot-auto-vet.yml b/.github/workflows/dependabot-auto-vet.yml index 2e8f3e9b5..84a961499 100644 --- a/.github/workflows/dependabot-auto-vet.yml +++ b/.github/workflows/dependabot-auto-vet.yml @@ -505,7 +505,7 @@ jobs: lines.push('```bash'); lines.push('git checkout '); lines.push(`gh run download ${runId} -n ${artifactName}`); - lines.push(`git apply --index ${artifactName}/auto-vet.patch`); + lines.push(`git apply --index auto-vet.patch`); lines.push('git status'); lines.push('git commit -S -m "chore(vet): apply automated audits"'); lines.push('git push');