|
6 | 6 | push: |
7 | 7 | branches: [main] |
8 | 8 | paths: |
9 | | - - scripts/buildroot/** |
10 | | - - scripts/assetCatalog.json |
11 | | - - .github/workflows/buildroot-guest.yml |
| 9 | + - "scripts/buildroot/**" |
| 10 | + - ".github/workflows/buildroot-guest.yml" |
12 | 11 |
|
13 | 12 | permissions: |
14 | 13 | contents: read |
15 | 14 |
|
16 | 15 | jobs: |
17 | 16 | reproduce: |
| 17 | + name: reproduce-${{ matrix.slot }} |
18 | 18 | runs-on: ubuntu-24.04 |
19 | 19 | timeout-minutes: 120 |
| 20 | + strategy: |
| 21 | + fail-fast: false |
| 22 | + matrix: |
| 23 | + slot: [a, b] |
| 24 | + env: |
| 25 | + PYPROC_BUILDROOT_WORKSPACE: ${{ runner.temp }}/pyproc-buildroot-${{ matrix.slot }} |
20 | 26 | steps: |
21 | | - - uses: actions/checkout@v4 |
22 | | - - uses: actions/setup-node@v4 |
| 27 | + - uses: actions/checkout@v5 |
| 28 | + - uses: actions/setup-node@v5 |
23 | 29 | with: |
24 | 30 | node-version: 22 |
25 | 31 | cache: npm |
26 | 32 | - run: npm ci |
27 | 33 | - name: Build pinned Buildroot guest and legal material |
28 | 34 | run: npm run assets:buildroot |
29 | | - - name: Upload reproducible guest bundle |
| 35 | + - name: Upload independent build evidence |
30 | 36 | uses: actions/upload-artifact@v4 |
31 | 37 | with: |
32 | | - name: pyproc-buildroot-guest |
33 | | - path: | |
34 | | - .cache/buildrootGuest/dist/ |
35 | | - .cache/buildrootGuest/output/legal-info/ |
| 38 | + name: pyproc-buildroot-guest-${{ matrix.slot }} |
| 39 | + path: ${{ env.PYPROC_BUILDROOT_WORKSPACE }}/dist/ |
36 | 40 | if-no-files-found: error |
37 | 41 | retention-days: 30 |
| 42 | + - name: Upload complete legal material |
| 43 | + if: matrix.slot == 'a' |
| 44 | + uses: actions/upload-artifact@v4 |
| 45 | + with: |
| 46 | + name: pyproc-buildroot-legal-info |
| 47 | + path: ${{ env.PYPROC_BUILDROOT_WORKSPACE }}/output/legal-info/ |
| 48 | + if-no-files-found: error |
| 49 | + compression-level: 0 |
| 50 | + retention-days: 30 |
| 51 | + |
| 52 | + verify: |
| 53 | + name: verify-byte-identical-builds |
| 54 | + needs: reproduce |
| 55 | + runs-on: ubuntu-24.04 |
| 56 | + timeout-minutes: 15 |
| 57 | + steps: |
| 58 | + - uses: actions/download-artifact@v4 |
| 59 | + with: |
| 60 | + name: pyproc-buildroot-guest-a |
| 61 | + path: .cache/repro/a |
| 62 | + - uses: actions/download-artifact@v4 |
| 63 | + with: |
| 64 | + name: pyproc-buildroot-guest-b |
| 65 | + path: .cache/repro/b |
| 66 | + - name: Verify image, manifest, SBOM, and legal gate |
| 67 | + run: | |
| 68 | + set -euo pipefail |
| 69 | + cmp .cache/repro/a/buildroot-pyproc-i686.bin .cache/repro/b/buildroot-pyproc-i686.bin |
| 70 | + cmp .cache/repro/a/build-manifest.json .cache/repro/b/build-manifest.json |
| 71 | + cmp .cache/repro/a/buildroot.cyclonedx.json .cache/repro/b/buildroot.cyclonedx.json |
| 72 | + mkdir -p .cache/repro/verified |
| 73 | + cp .cache/repro/a/* .cache/repro/verified/ |
| 74 | + node --input-type=module <<'NODE' |
| 75 | + import { createHash } from "node:crypto"; |
| 76 | + import { readFile, writeFile } from "node:fs/promises"; |
| 77 | + const base = ".cache/repro/verified"; |
| 78 | + const manifest = JSON.parse(await readFile(`${base}/build-manifest.json`, "utf8")); |
| 79 | + const image = await readFile(`${base}/buildroot-pyproc-i686.bin`); |
| 80 | + const actualSha256 = createHash("sha256").update(image).digest("hex"); |
| 81 | + if (actualSha256 !== manifest.output.sha256) throw new Error("verified image SHA-256 mismatch"); |
| 82 | + if (image.byteLength !== manifest.output.byteLength) throw new Error("verified image byteLength mismatch"); |
| 83 | + if (manifest.evidence.legalWarnings.length) throw new Error("verified legal-info warnings are not empty"); |
| 84 | + const receipt = { |
| 85 | + schemaVersion: 1, |
| 86 | + recipe: manifest.recipe, |
| 87 | + githubRunId: process.env.GITHUB_RUN_ID, |
| 88 | + headSha: process.env.GITHUB_SHA, |
| 89 | + independentBuilds: ["a", "b"], |
| 90 | + byteIdentical: true, |
| 91 | + output: manifest.output, |
| 92 | + }; |
| 93 | + await writeFile(`${base}/reproducibility-manifest.json`, `${JSON.stringify(receipt, null, 2)}\n`); |
| 94 | + NODE |
| 95 | + - name: Upload verified reproducible guest |
| 96 | + uses: actions/upload-artifact@v4 |
| 97 | + with: |
| 98 | + name: pyproc-buildroot-guest-verified |
| 99 | + path: .cache/repro/verified/ |
| 100 | + if-no-files-found: error |
| 101 | + retention-days: 90 |
0 commit comments