From afaf096ea0922eca5063764c46adbbfd4889afa8 Mon Sep 17 00:00:00 2001 From: Justin Kim Date: Thu, 29 Oct 2026 23:04:48 +0900 Subject: [PATCH 1/3] ci(pr): move PR pipeline to ubuntu-26.04 with distro Meson - ci-pr.yml: all ubuntu legs -> ubuntu-26.04 / ubuntu-26.04-arm - remove 7 pinned setup-meson (1.12.0) steps; install distro meson per OS - lockstep: telemetry fixtures + test-ci-run-telemetry, build-timeouts, early-size-gate, RELEASE_PROCESS required contexts, size-attribution policy-identity token - setup-meson action retained (android/ios/size-attribution/ci-main) Approved candidate tree: 1f74b21df4a43e773a2846b542874ea7aab8230b --- .github/workflows/ci-pr.yml | 102 +++++------ .github/workflows/lint-pr.yml | 4 +- docs/RELEASE_PROCESS.md | 16 +- scripts/ci/check-clang-tidy-ratchet.py | 2 +- .../run-clock-skew-jobs.json | 10 +- .../run-missing-data-jobs.json | 10 +- .../run-queued-jobs.json | 10 +- .../ci-telemetry-fixtures/run-rerun-jobs.json | 2 +- .../run-skip-cascade-jobs.json | 42 ++--- .../run-success-jobs.json | 50 +++--- scripts/ci/collect-size-attribution.py | 5 +- scripts/ci/test-ci-pr-build-timeouts.py | 2 +- scripts/ci/test-ci-run-telemetry.py | 168 +++++++++--------- scripts/ci/test-early-size-gate.sh | 2 +- scripts/ci/test-gate-skip-exit-codes.sh | 2 +- 15 files changed, 206 insertions(+), 221 deletions(-) diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index ca76266b4..903d4a95c 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -5,12 +5,12 @@ name: CI PR # Execution flow: # rc-changelog-freeze # └─ lint -# ├─ tsan-native (ubuntu-latest gcc, advisory compile smoke) +# ├─ tsan-native (ubuntu-26.04 gcc, advisory compile smoke) # │ └─ sanitizer-matrix (multi-OS ASan/UBSan) -# └─ tsan (ubuntu-latest gcc+clang, gating TSan) -# └─ build-primary (ubuntu-latest gcc, SBOM/size/path-a gates) -# ├─ build-arm (ubuntu-24.04-arm gcc, ABI #681) ──┐ -# └─ build-mbedtls (ubuntu-latest gcc, crypto) ──┼─ build-matrix +# └─ tsan (ubuntu-26.04 gcc+clang, gating TSan) +# └─ build-primary (ubuntu-26.04 gcc, SBOM/size/path-a gates) +# ├─ build-arm (ubuntu-26.04-arm gcc, ABI #681) ──┐ +# └─ build-mbedtls (ubuntu-26.04 gcc, crypto) ──┼─ build-matrix # # CodeQL runs in its own workflow (GitHub default-setup); not invoked from here. @@ -45,7 +45,7 @@ jobs: # ========================================================================== build-scope: name: Detect build-triggering changes - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 outputs: build-required: ${{ steps.scope.outputs.build-required }} steps: @@ -95,7 +95,7 @@ jobs: # otherwise registered inside the skipped Meson build. docs-policy: name: Docs / policy validation - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 with: @@ -110,7 +110,7 @@ jobs: # ========================================================================== rc-changelog-freeze: name: RC changelog freeze gate - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 with: @@ -132,16 +132,16 @@ jobs: base-ref: ${{ github.base_ref }} # ========================================================================== - # Phase 2a: Build Primary (ubuntu-latest + gcc) + # Phase 2a: Build Primary (ubuntu-26.04 + gcc) # Fast-path job that gates critical checks: SBOM, binary size, Path A example. # Gated on the tsan leg; the first build to run and unblocks build-arm, # build-mbedtls, and build-matrix. # ========================================================================== build-primary: - name: Build / ubuntu-latest / gcc + name: Build / ubuntu-26.04 / gcc needs: [tsan, build-scope] if: ${{ needs.build-scope.outputs.build-required == 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false steps: @@ -155,10 +155,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev uncrustify abigail-tools b3sum - - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson + sudo apt-get install -y meson ninja-build libmbedtls-dev uncrustify abigail-tools b3sum - name: Configure run: meson setup builddir -Dtests=true -DmbedTLS=disabled @@ -308,14 +305,14 @@ jobs: ./builddir-path-a/pcap_skeleton # ========================================================================== - # Phase: Build ARM (ubuntu-24.04-arm gcc, gated by build-primary) + # Phase: Build ARM (ubuntu-26.04-arm gcc, gated by build-primary) # ABI compliance check (#681); must pass before build-matrix proceeds. # ========================================================================== build-arm: - name: Build / ubuntu-24.04-arm / gcc + name: Build / ubuntu-26.04-arm / gcc needs: [build-primary, build-scope] if: ${{ needs.build-scope.outputs.build-required == 'true' }} - runs-on: ubuntu-24.04-arm + runs-on: ubuntu-26.04-arm strategy: fail-fast: false @@ -325,10 +322,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev uncrustify abigail-tools b3sum - - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson + sudo apt-get install -y meson ninja-build libmbedtls-dev uncrustify abigail-tools b3sum - name: Configure run: meson setup builddir -Dtests=true -DmbedTLS=disabled @@ -360,7 +354,7 @@ jobs: matrix: include: # Linux - Clang - - os: ubuntu-latest + - os: ubuntu-26.04 compiler: clang cc: clang timeout_minutes: 60 @@ -384,7 +378,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev uncrustify abigail-tools b3sum + sudo apt-get install -y meson ninja-build libmbedtls-dev uncrustify abigail-tools b3sum if [ "${{ matrix.compiler }}" = "clang" ]; then sudo apt-get install -y clang fi @@ -392,7 +386,7 @@ jobs: - name: Install dependencies (macOS) if: runner.os == 'macOS' run: | - brew install ninja mbedtls uncrustify coreutils gnu-tar + brew install meson ninja mbedtls uncrustify coreutils gnu-tar echo "$(brew --prefix coreutils)/libexec/gnubin" >> "$GITHUB_PATH" echo "$(brew --prefix gnu-tar)/libexec/gnubin" >> "$GITHUB_PATH" @@ -402,10 +396,7 @@ jobs: # pkgconfiglite ships from a SourceForge mirror without a # checksum; chocolatey's default policy now blocks empty # checksums for non-secure sources, so opt in explicitly. - choco install pkgconfiglite uncrustify -y --allow-empty-checksums - - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson + choco install pkgconfiglite uncrustify meson -y --allow-empty-checksums - name: Configure (Linux / macOS) if: runner.os != 'Windows' @@ -520,10 +511,10 @@ jobs: # Optional crypto coverage; must pass before build-matrix proceeds. # ========================================================================== build-mbedtls: - name: mbedtls-enabled / ubuntu-latest / gcc + name: mbedtls-enabled / ubuntu-26.04 / gcc needs: [build-primary, build-scope] if: ${{ needs.build-scope.outputs.build-required == 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 @@ -531,10 +522,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build pkg-config libmbedtls-dev - - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson + sudo apt-get install -y meson ninja-build pkg-config libmbedtls-dev - name: Configure with mbedTLS enabled run: meson setup builddir-mbedtls -Dtests=true -DmbedTLS=enabled @@ -548,9 +536,10 @@ jobs: # WL_MBEDTLS_ENABLED consumer, a project-wide argument (meson.build) # -- so compiling exactly those two targets preserves the # crypto-compiler coverage this job uniquely asserts; the rest of the - # default target set is covered by the other jobs. Verified on meson - # 1.12.0: `meson compile -C dir ` builds only the named - # targets. + # default target set is covered by the other jobs. The scoping + # behaviour below was verified on meson 1.12.0: `meson compile -C dir + # ` builds only the named targets (the PR runner's own meson + # version is the distro-provided one and is not pinned). - name: Build with mbedTLS enabled run: meson compile -C builddir-mbedtls test_cryptographic_hashes test_symbol_digests @@ -583,17 +572,17 @@ jobs: matrix: include: # Linux - GCC - - os: ubuntu-latest + - os: ubuntu-26.04 compiler: gcc cc: gcc # Linux - Clang - - os: ubuntu-latest + - os: ubuntu-26.04 compiler: clang cc: clang # Linux ARM64 - GCC - - os: ubuntu-24.04-arm + - os: ubuntu-26.04-arm compiler: gcc cc: gcc @@ -612,7 +601,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev abigail-tools + sudo apt-get install -y meson ninja-build libmbedtls-dev abigail-tools if [ "${{ matrix.compiler }}" = "clang" ]; then sudo apt-get install -y clang fi @@ -620,13 +609,10 @@ jobs: - name: Install dependencies (macOS) if: runner.os == 'macOS' run: | - brew install ninja mbedtls coreutils gnu-tar + brew install meson ninja mbedtls coreutils gnu-tar echo "$(brew --prefix coreutils)/libexec/gnubin" >> "$GITHUB_PATH" echo "$(brew --prefix gnu-tar)/libexec/gnubin" >> "$GITHUB_PATH" - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson - - name: Configure with sanitizers run: > meson setup builddir-san @@ -667,10 +653,10 @@ jobs: # (thrd_create, mtx_lock) are uninstrumented and cause false SEGV. # ========================================================================== tsan: - name: TSan / ubuntu-latest / ${{ matrix.compiler }} + name: TSan / ubuntu-26.04 / ${{ matrix.compiler }} needs: [lint, build-scope] if: ${{ needs.build-scope.outputs.build-required == 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: @@ -689,14 +675,11 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev + sudo apt-get install -y meson ninja-build libmbedtls-dev if [ "${{ matrix.compiler }}" = "clang" ]; then sudo apt-get install -y clang fi - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson - - name: Configure with TSan run: | meson setup builddir-tsan \ @@ -746,10 +729,10 @@ jobs: # - Issue #826 (native C11 runtime under TSan is not a valid signal) # ========================================================================== tsan-native: - name: TSan-native / ubuntu-latest / gcc + name: TSan-native / ubuntu-26.04 / gcc needs: [lint, build-scope] if: ${{ needs.build-scope.outputs.build-required == 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 @@ -760,10 +743,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev - - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson + sudo apt-get install -y meson ninja-build libmbedtls-dev - name: Configure with TSan (native threads) run: | @@ -783,8 +763,10 @@ jobs: # thread_c11.c is a source of the `wirelog` shared-library target, so # compiling exactly that target preserves the documented compile/link # coverage; the remaining default targets' backend coverage comes from - # the ordinary non-TSan matrix. Verified on meson 1.12.0: `meson - # compile -C dir wirelog` builds only the library. + # the ordinary non-TSan matrix. The scoping behaviour below was + # verified on meson 1.12.0: `meson compile -C dir wirelog` builds only + # the library (the PR runner's own meson version is the + # distro-provided one and is not pinned). - name: Build with TSan (native threads) run: meson compile -C builddir-tsan-native wirelog diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml index 2b9f7f0fa..1709c1d9a 100644 --- a/.github/workflows/lint-pr.yml +++ b/.github/workflows/lint-pr.yml @@ -24,7 +24,7 @@ jobs: # ========================================================================== editorconfig-check: name: EditorConfig check - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 @@ -65,7 +65,7 @@ jobs: uncrustify-check: name: uncrustify check needs: [editorconfig-check] - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 with: diff --git a/docs/RELEASE_PROCESS.md b/docs/RELEASE_PROCESS.md index 1a9dd4290..6866442a3 100644 --- a/docs/RELEASE_PROCESS.md +++ b/docs/RELEASE_PROCESS.md @@ -417,22 +417,22 @@ the cutover commit sets `project_version` to `1.0.0-rc1`. - Tag protection on `v1.x.*`. - Required status checks: - Stable policy plan: `default`, `abi`, `asan`, `tsan`, `perf`, - plus `mbedtls-enabled / ubuntu-latest / gcc`. + plus `mbedtls-enabled / ubuntu-26.04 / gcc`. - `perf` must be represented by a dedicated always-emitting release perf context that runs with `WIRELOG_PERF_REQUIRE=1`; do not rely on path-filtered contexts for branch protection. - Currently known concrete PR contexts to require at cutover (unless replaced by a later policy-consolidation change): `lint / EditorConfig check`, `lint / uncrustify check`, - `Build / ubuntu-latest / gcc`, `Build / ubuntu-24.04-arm / gcc`, + `Build / ubuntu-26.04 / gcc`, `Build / ubuntu-26.04-arm / gcc`, `RC changelog freeze gate`, `CLA signoff gate`, - `mbedtls-enabled / ubuntu-latest / gcc`, - `Build / ubuntu-latest / clang`, `Build / macos-latest / clang`, + `mbedtls-enabled / ubuntu-26.04 / gcc`, + `Build / ubuntu-26.04 / clang`, `Build / macos-latest / clang`, `Build / windows-latest / msvc`, - `Sanitizers / ubuntu-latest / gcc`, - `Sanitizers / ubuntu-latest / clang`, + `Sanitizers / ubuntu-26.04 / gcc`, + `Sanitizers / ubuntu-26.04 / clang`, `Sanitizers / macos-latest / clang`, - `TSan / ubuntu-latest / gcc`. + `TSan / ubuntu-26.04 / gcc`. #### Synthetic PR verification plan (Phase B) @@ -488,7 +488,7 @@ separate policy change. ### mbedTLS-enabled validation policy The stable check name for optional crypto validation is -`mbedtls-enabled / ubuntu-latest / gcc`. The check is validation +`mbedtls-enabled / ubuntu-26.04 / gcc`. The check is validation coverage only: it proves that `-DmbedTLS=enabled` still configures, builds, and runs the crypto tests against a system PSA Crypto provider. It does **not** change the default release artifact posture, which diff --git a/scripts/ci/check-clang-tidy-ratchet.py b/scripts/ci/check-clang-tidy-ratchet.py index 42e662d99..767112a2d 100755 --- a/scripts/ci/check-clang-tidy-ratchet.py +++ b/scripts/ci/check-clang-tidy-ratchet.py @@ -42,7 +42,7 @@ command line. The architecture guard is not redundant with the OS one: `build-arm` -(`.github/workflows/ci-pr.yml`, ubuntu-24.04-arm) is Linux, uninstrumented +(`.github/workflows/ci-pr.yml`, ubuntu-26.04-arm) is Linux, uninstrumented and not MSVC, and runs an unfiltered `meson test`. It is protected today only by clang-tidy 22 being absent from that image, which is an accident of the runner rather than a guarantee. `clang-analyzer-*` results are diff --git a/scripts/ci/ci-telemetry-fixtures/run-clock-skew-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-clock-skew-jobs.json index a84089b17..6bc1f019f 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-clock-skew-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-clock-skew-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-11T08:33:37Z", "id": 103200409721, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "RC changelog freeze gate", "runner_group_name": "default", @@ -62,7 +62,7 @@ "created_at": "2026-09-11T08:33:37Z", "id": 103200409951, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", @@ -118,7 +118,7 @@ "created_at": "2026-09-11T08:33:37Z", "id": 103200409955, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Docs / policy validation", "runner_group_name": "default", @@ -174,7 +174,7 @@ "created_at": "2026-09-11T08:35:15Z", "id": 103200838630, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / EditorConfig check", "runner_group_name": "GitHub Actions", @@ -246,7 +246,7 @@ "created_at": "2026-09-11T08:43:11Z", "id": 103202876645, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / uncrustify check", "runner_group_name": "default", diff --git a/scripts/ci/ci-telemetry-fixtures/run-missing-data-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-missing-data-jobs.json index 435cb36b7..dc4ce3d97 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-missing-data-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-missing-data-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-10T10:00:01Z", "id": 301, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", @@ -21,9 +21,9 @@ "created_at": "2026-09-10T10:00:11Z", "id": 302, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Build / ubuntu-latest / gcc", + "name": "Build / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1", "started_at": null, @@ -45,9 +45,9 @@ "created_at": "2026-09-10T10:02:00Z", "id": 303, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan / ubuntu-latest / gcc", + "name": "TSan / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1", "started_at": "2026-09-10T10:02:05Z", diff --git a/scripts/ci/ci-telemetry-fixtures/run-queued-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-queued-jobs.json index 26e5c42ce..e9e6c07c3 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-queued-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-queued-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-10T10:00:01Z", "id": 201, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", @@ -54,9 +54,9 @@ "created_at": "2026-09-10T10:00:18Z", "id": 202, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Build / ubuntu-latest / gcc", + "name": "Build / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1", "started_at": "2026-09-10T10:00:30Z", @@ -94,9 +94,9 @@ "created_at": "2026-09-10T10:00:18Z", "id": 203, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan / ubuntu-latest / gcc", + "name": "TSan / ubuntu-26.04 / gcc", "runner_group_name": null, "runner_name": null, "started_at": null, diff --git a/scripts/ci/ci-telemetry-fixtures/run-rerun-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-rerun-jobs.json index 38a585940..4de5cf031 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-rerun-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-rerun-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-10T10:00:01Z", "id": 401, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", diff --git a/scripts/ci/ci-telemetry-fixtures/run-skip-cascade-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-skip-cascade-jobs.json index b4f1684c1..a572797e7 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-skip-cascade-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-skip-cascade-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-11T06:49:48Z", "id": 103174726297, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", @@ -62,7 +62,7 @@ "created_at": "2026-09-11T06:49:48Z", "id": 103174726522, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Docs / policy validation", "runner_group_name": "default", @@ -118,7 +118,7 @@ "created_at": "2026-09-11T06:49:48Z", "id": 103174726533, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "RC changelog freeze gate", "runner_group_name": "default", @@ -174,7 +174,7 @@ "created_at": "2026-09-11T06:50:17Z", "id": 103174840940, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / EditorConfig check", "runner_group_name": "default", @@ -246,7 +246,7 @@ "created_at": "2026-09-11T06:50:36Z", "id": 103174910112, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / uncrustify check", "runner_group_name": "GitHub Actions", @@ -326,9 +326,9 @@ "created_at": "2026-09-11T06:50:52Z", "id": 103174971848, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Sanitizers / ubuntu-latest / clang", + "name": "Sanitizers / ubuntu-26.04 / clang", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000055705", "started_at": "2026-09-11T06:50:54Z", @@ -438,9 +438,9 @@ "created_at": "2026-09-11T06:50:52Z", "id": 103174971875, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Build / ubuntu-latest / gcc", + "name": "Build / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000055706", "started_at": "2026-09-11T06:50:55Z", @@ -582,9 +582,9 @@ "created_at": "2026-09-11T06:50:52Z", "id": 103174971879, "labels": [ - "ubuntu-24.04-arm" + "ubuntu-26.04-arm" ], - "name": "Sanitizers / ubuntu-24.04-arm / gcc", + "name": "Sanitizers / ubuntu-26.04-arm / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000055708", "started_at": "2026-09-11T06:50:57Z", @@ -694,9 +694,9 @@ "created_at": "2026-09-11T06:50:52Z", "id": 103174971902, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Sanitizers / ubuntu-latest / gcc", + "name": "Sanitizers / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000055707", "started_at": "2026-09-11T06:50:55Z", @@ -918,9 +918,9 @@ "created_at": "2026-09-11T07:14:47Z", "id": 103180567746, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "mbedtls-enabled / ubuntu-latest / gcc", + "name": "mbedtls-enabled / ubuntu-26.04 / gcc", "runner_group_name": null, "runner_name": null, "started_at": "2026-09-11T07:14:47Z", @@ -933,9 +933,9 @@ "created_at": "2026-09-11T07:14:47Z", "id": 103180567967, "labels": [ - "ubuntu-24.04-arm" + "ubuntu-26.04-arm" ], - "name": "Build / ubuntu-24.04-arm / gcc", + "name": "Build / ubuntu-26.04-arm / gcc", "runner_group_name": null, "runner_name": null, "started_at": "2026-09-11T07:14:47Z", @@ -961,9 +961,9 @@ "created_at": "2026-09-11T07:34:52Z", "id": 103185485539, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan / ubuntu-latest / ${{ matrix.compiler }}", + "name": "TSan / ubuntu-26.04 / ${{ matrix.compiler }}", "runner_group_name": null, "runner_name": null, "started_at": "2026-09-11T07:34:52Z", @@ -976,9 +976,9 @@ "created_at": "2026-09-11T07:34:52Z", "id": 103185485852, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan-native / ubuntu-latest / gcc", + "name": "TSan-native / ubuntu-26.04 / gcc", "runner_group_name": null, "runner_name": null, "started_at": "2026-09-11T07:34:52Z", diff --git a/scripts/ci/ci-telemetry-fixtures/run-success-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-success-jobs.json index 42ba0b693..eecbde01d 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-success-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-success-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-10T18:27:12Z", "id": 102996159107, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", @@ -62,7 +62,7 @@ "created_at": "2026-09-10T18:27:12Z", "id": 102996159417, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "RC changelog freeze gate", "runner_group_name": "GitHub Actions", @@ -118,7 +118,7 @@ "created_at": "2026-09-10T18:27:12Z", "id": 102996159476, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Docs / policy validation", "runner_group_name": "GitHub Actions", @@ -174,7 +174,7 @@ "created_at": "2026-09-10T18:36:40Z", "id": 102999400767, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / EditorConfig check", "runner_group_name": "default", @@ -246,7 +246,7 @@ "created_at": "2026-09-10T18:50:56Z", "id": 103004228926, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / uncrustify check", "runner_group_name": "GitHub Actions", @@ -326,9 +326,9 @@ "created_at": "2026-09-10T18:59:50Z", "id": 103007248983, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Build / ubuntu-latest / gcc", + "name": "Build / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054702", "started_at": "2026-09-10T19:22:16Z", @@ -470,9 +470,9 @@ "created_at": "2026-09-10T18:59:50Z", "id": 103007249006, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Sanitizers / ubuntu-latest / gcc", + "name": "Sanitizers / ubuntu-26.04 / gcc", "runner_group_name": "default", "runner_name": "i406-d2", "started_at": "2026-09-10T19:19:52Z", @@ -582,9 +582,9 @@ "created_at": "2026-09-10T18:59:50Z", "id": 103007249042, "labels": [ - "ubuntu-24.04-arm" + "ubuntu-26.04-arm" ], - "name": "Sanitizers / ubuntu-24.04-arm / gcc", + "name": "Sanitizers / ubuntu-26.04-arm / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054683", "started_at": "2026-09-10T19:14:42Z", @@ -806,9 +806,9 @@ "created_at": "2026-09-10T18:59:50Z", "id": 103007249070, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Sanitizers / ubuntu-latest / clang", + "name": "Sanitizers / ubuntu-26.04 / clang", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054699", "started_at": "2026-09-10T19:21:25Z", @@ -918,9 +918,9 @@ "created_at": "2026-09-10T19:42:53Z", "id": 103021643946, "labels": [ - "ubuntu-24.04-arm" + "ubuntu-26.04-arm" ], - "name": "Build / ubuntu-24.04-arm / gcc", + "name": "Build / ubuntu-26.04-arm / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054763", "started_at": "2026-09-10T19:45:59Z", @@ -1014,9 +1014,9 @@ "created_at": "2026-09-10T19:42:53Z", "id": 103021644050, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "mbedtls-enabled / ubuntu-latest / gcc", + "name": "mbedtls-enabled / ubuntu-26.04 / gcc", "runner_group_name": "default", "runner_name": "semantic-reasoning-i401-6-2", "started_at": "2026-09-10T19:49:48Z", @@ -1414,9 +1414,9 @@ "created_at": "2026-09-10T19:59:58Z", "id": 103027282864, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Build / ubuntu-latest / clang", + "name": "Build / ubuntu-26.04 / clang", "runner_group_name": "default", "runner_name": "semantic-reasoning-i401-6", "started_at": "2026-09-10T20:31:06Z", @@ -1558,9 +1558,9 @@ "created_at": "2026-09-10T20:25:47Z", "id": 103035976181, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan / ubuntu-latest / gcc", + "name": "TSan / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054867", "started_at": "2026-09-10T20:52:56Z", @@ -1654,9 +1654,9 @@ "created_at": "2026-09-10T20:25:47Z", "id": 103035976299, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan-native / ubuntu-latest / gcc", + "name": "TSan-native / ubuntu-26.04 / gcc", "runner_group_name": "default", "runner_name": "i406-d2", "started_at": "2026-09-10T20:58:46Z", @@ -1750,9 +1750,9 @@ "created_at": "2026-09-10T20:25:47Z", "id": 103035976439, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan / ubuntu-latest / clang", + "name": "TSan / ubuntu-26.04 / clang", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054868", "started_at": "2026-09-10T20:53:12Z", diff --git a/scripts/ci/collect-size-attribution.py b/scripts/ci/collect-size-attribution.py index a994bdfbb..2b8a6d201 100644 --- a/scripts/ci/collect-size-attribution.py +++ b/scripts/ci/collect-size-attribution.py @@ -111,7 +111,10 @@ def read_policy_identity(repo: Path) -> dict[str, str]: options = (repo / "meson_options.txt").read_text(encoding="utf-8") setup = (repo / ".github/actions/setup-meson/action.yml").read_text(encoding="utf-8") required = ( - ("runs-on: ubuntu-latest", workflow), + # Pins the PR-CI profile token in ci-pr.yml. Deliberately NOT the + # attribution measurement environment, which this file pins to + # ubuntu-24.04 (see require_os_ubuntu_2404 below). + ("runs-on: ubuntu-26.04", workflow), ("meson setup builddir -Dtests=true -DmbedTLS=disabled", workflow), ("meson compile -C builddir wirelog", workflow), ("buildtype=release", meson), diff --git a/scripts/ci/test-ci-pr-build-timeouts.py b/scripts/ci/test-ci-pr-build-timeouts.py index ac5532adb..4be280e34 100644 --- a/scripts/ci/test-ci-pr-build-timeouts.py +++ b/scripts/ci/test-ci-pr-build-timeouts.py @@ -13,7 +13,7 @@ WORKFLOW = Path(__file__).resolve().parents[2] / ".github/workflows/ci-pr.yml" EXPECTED_TIMEOUTS = { - "ubuntu-latest": 60, + "ubuntu-26.04": 60, "macos-latest": 60, "windows-latest": 120, } diff --git a/scripts/ci/test-ci-run-telemetry.py b/scripts/ci/test-ci-run-telemetry.py index 0bd3624b6..6211f4944 100755 --- a/scripts/ci/test-ci-run-telemetry.py +++ b/scripts/ci/test-ci-run-telemetry.py @@ -139,7 +139,7 @@ def _did_not_run_as(self, conclusion, name=None): """A run-success job restamped the way a job that never ran is.""" run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = name or "Sanitizers / ubuntu-latest / clang" + victim = name or "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = conclusion @@ -157,7 +157,7 @@ def test_the_cli_line_names_the_same_division_as_the_report(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["conclusion"] = "startup_failure" job["started_at"] = job["created_at"] job["completed_at"] = job["created_at"] @@ -268,7 +268,7 @@ def test_a_job_that_did_not_run_cannot_stretch_the_pr_feedback(self): # never-run job's completion stamp is bookkeeping. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -288,7 +288,7 @@ def test_a_job_that_did_not_run_keeps_its_release_latency(self): # created), so cancelling it still leaves a measurable release latency. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "TSan-native / ubuntu-latest / gcc" + victim = "TSan-native / ubuntu-26.04 / gcc" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -317,7 +317,7 @@ def test_a_job_level_conclusion_that_may_precede_dispatch_is_weighed(self): for conclusion in ("neutral", "action_required"): with self.subTest(conclusion=conclusion): mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = conclusion @@ -337,7 +337,7 @@ def test_a_conclusion_this_tool_has_not_met_is_weighed_not_assumed(self): # was written must not default to "ran" and publish a zero. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "a_conclusion_from_the_future" @@ -359,7 +359,7 @@ def test_an_outcome_only_execution_reaches_is_never_weighed(self): for conclusion in tool.CONCLUSIONS_THAT_RAN: with self.subTest(conclusion=conclusion): mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = conclusion @@ -393,8 +393,8 @@ def test_the_summary_terms_do_not_reorder_with_the_job_list(self): # phrase follows whatever order the API returned the jobs in. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victims = ["Sanitizers / ubuntu-latest / clang", - "Sanitizers / ubuntu-latest / gcc", + victims = ["Sanitizers / ubuntu-26.04 / clang", + "Sanitizers / ubuntu-26.04 / gcc", "Sanitizers / macos-latest / clang"] for name, conclusion in zip(victims, ("startup_failure", "stale", "cancelled")): @@ -424,7 +424,7 @@ def test_an_evidence_decided_conclusion_keeps_its_evidence(self): # with nothing saying so. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "TSan / ubuntu-latest / clang" # the last to finish + victim = "TSan / ubuntu-26.04 / clang" # the last to finish for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "stale" @@ -439,7 +439,7 @@ def test_a_job_that_did_not_run_is_not_counted_as_executed(self): # of six blanks below it. One test decides all three. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -476,7 +476,7 @@ def test_skipped_jobs_have_no_durations(self): def test_first_failure_latency_is_measured(self): metric = self.report["metrics"]["first_failure_latency"] self.assertEqual(metric["status"], "measured") - self.assertEqual(metric["evidence"]["job"], "Build / ubuntu-latest / gcc") + self.assertEqual(metric["evidence"]["job"], "Build / ubuntu-26.04 / gcc") self.assertEqual(metric["seconds"], 1499) # The step matters: a size gate failing at minute 25 is #1573's case, # and the job name alone does not distinguish it from a test failure. @@ -490,7 +490,7 @@ def test_run_wall_clock_ignores_skipped_completions(self): def test_a_skipped_job_reports_no_release_latency(self): by_name = {j["name"]: j for j in self.report["jobs"]} - metric = by_name["Build / ubuntu-24.04-arm / gcc"]["metrics"][ + metric = by_name["Build / ubuntu-26.04-arm / gcc"]["metrics"][ "scheduler_release_latency"] self.assertEqual(metric["status"], "unavailable") self.assertIn("skipped", metric["reason"]) @@ -529,21 +529,21 @@ def test_the_latest_dependency_wins_when_there_are_several(self): def test_a_skipped_dependency_makes_the_latency_unavailable(self): run, jobs = fixture("run-skip-cascade") - needs = {"Build / ubuntu-latest / gcc": [ - "mbedtls-enabled / ubuntu-latest / gcc"]} + needs = {"Build / ubuntu-26.04 / gcc": [ + "mbedtls-enabled / ubuntu-26.04 / gcc"]} report = tool.analyze(run, jobs, needs) by_name = {j["name"]: j for j in report["jobs"]} - metric = by_name["Build / ubuntu-latest / gcc"]["metrics"][ + metric = by_name["Build / ubuntu-26.04 / gcc"]["metrics"][ "scheduler_release_latency"] self.assertEqual(metric["status"], "unavailable") self.assertIn("batched", metric["reason"]) def test_a_dependency_absent_from_the_run_is_named(self): run, jobs = fixture("run-skip-cascade") - needs = {"Build / ubuntu-latest / gcc": ["a job that never ran"]} + needs = {"Build / ubuntu-26.04 / gcc": ["a job that never ran"]} report = tool.analyze(run, jobs, needs) by_name = {j["name"]: j for j in report["jobs"]} - metric = by_name["Build / ubuntu-latest / gcc"]["metrics"][ + metric = by_name["Build / ubuntu-26.04 / gcc"]["metrics"][ "scheduler_release_latency"] self.assertEqual(metric["status"], "unavailable") self.assertIn("a job that never ran", metric["reason"]) @@ -590,10 +590,10 @@ class InFlightAndMissingData(unittest.TestCase): def test_queued_run_invents_nothing(self): report = tool.analyze(*fixture("run-queued")) by_name = {j["name"]: j for j in report["jobs"]} - queued = by_name["TSan / ubuntu-latest / gcc"] + queued = by_name["TSan / ubuntu-26.04 / gcc"] self.assertEqual(queued["metrics"]["queue_delay"]["status"], "unavailable") self.assertEqual(queued["metrics"]["job_wall"]["status"], "unavailable") - running = by_name["Build / ubuntu-latest / gcc"] + running = by_name["Build / ubuntu-26.04 / gcc"] self.assertEqual(running["metrics"]["queue_delay"]["seconds"], 12) self.assertEqual(running["metrics"]["job_wall"]["status"], "unavailable") self.assertEqual(report["metrics"]["run_wall_clock"]["status"], "partial") @@ -606,7 +606,7 @@ def test_missing_fields_are_reported_not_guessed(self): with self.subTest(phase=phase): self.assertEqual(metric["status"], "unavailable") self.assertEqual(no_steps["clock_offset_estimate"]["status"], "unavailable") - never_started = by_name["Build / ubuntu-latest / gcc"] + never_started = by_name["Build / ubuntu-26.04 / gcc"] self.assertEqual(never_started["metrics"]["queue_delay"]["status"], "unavailable") self.assertEqual(report["t0_source"], "created_at") @@ -665,7 +665,7 @@ def test_a_renamed_step_reports_unavailable_not_zero(self): mutated = copy.deepcopy(jobs) renamed = 0 for job in mutated["jobs"]: - if job["name"] != "Build / ubuntu-latest / gcc": + if job["name"] != "Build / ubuntu-26.04 / gcc": continue for step in job["steps"]: if step["name"] == "Test": @@ -674,7 +674,7 @@ def test_a_renamed_step_reports_unavailable_not_zero(self): self.assertEqual(renamed, 1, "fixture must contain the step to rename") report = tool.analyze(run, mutated) job = next(j for j in report["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") self.assertEqual(job["phases"]["tests"]["status"], "unavailable") self.assertIn("Verify build outputs", report["unmapped_steps"]) @@ -736,13 +736,13 @@ def _with_broken_test_step(self, started, completed): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) job = next(j for j in mutated["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") for step in job["steps"]: if step["name"] == "Test": step["started_at"], step["completed_at"] = started, completed report = tool.analyze(run, mutated) return next(j for j in report["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") def test_a_backwards_step_is_named_not_silently_dropped(self): job = self._with_broken_test_step("2026-09-10T19:50:00Z", @@ -761,7 +761,7 @@ def test_an_unusable_step_reaches_the_run_summary(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) job = next(j for j in mutated["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") for step in job["steps"]: if step["name"] == "Test": step["completed_at"] = step["started_at"] @@ -827,7 +827,7 @@ class SkippedSteps(unittest.TestCase): def test_a_skipped_step_does_not_feed_the_phase_it_matched(self): report = tool.analyze(*fixture("run-success")) job = next(j for j in report["jobs"] - if j["name"] == "Sanitizers / ubuntu-latest / gcc") + if j["name"] == "Sanitizers / ubuntu-26.04 / gcc") phase = job["phases"]["deps_install"] # The macOS install is skipped on a Linux runner; only the Linux one # actually installed anything. @@ -888,12 +888,12 @@ def test_a_timed_out_job_counts_as_the_first_failure(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) job = next(j for j in mutated["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") job["conclusion"] = "timed_out" report = tool.analyze(run, mutated) metric = report["metrics"]["first_failure_latency"] self.assertEqual(metric["status"], "measured") - self.assertEqual(metric["evidence"]["job"], "Build / ubuntu-latest / gcc") + self.assertEqual(metric["evidence"]["job"], "Build / ubuntu-26.04 / gcc") def test_a_cancelled_job_is_not_a_failure_but_is_acknowledged(self): run, jobs = fixture("run-success") @@ -912,7 +912,7 @@ def test_a_timeout_says_why_no_step_is_named(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) job = next(j for j in mutated["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") job["conclusion"] = "timed_out" for step in job["steps"]: step["conclusion"] = "cancelled" @@ -953,7 +953,7 @@ def test_a_job_missing_from_the_graph_is_not_called_a_root(self): by_name = {j["name"]: j for j in report["jobs"]} # Absence from a partial graph is an extractor gap, not rootness; the # two were conflated and 17 of 18 jobs claimed to be roots. - absent = by_name["Sanitizers / ubuntu-latest / gcc"]["metrics"][ + absent = by_name["Sanitizers / ubuntu-26.04 / gcc"]["metrics"][ "scheduler_release_latency"] self.assertIn("absent from the supplied dependency graph", absent["reason"]) @@ -998,7 +998,7 @@ def test_the_reusable_lint_workflow_is_expanded_into_its_jobs(self): # The caller's dependents attach to the callee's sink. In the # reordered graph the tsan leg is lint's direct dependent. self.assertIn("lint / uncrustify check", - self.graph["TSan / ubuntu-latest / ${{ matrix.compiler }}"]) + self.graph["TSan / ubuntu-26.04 / ${{ matrix.compiler }}"]) def test_every_job_has_an_entry_and_roots_are_explicit(self): # A job missing from the graph must be a gap, never mistaken for a @@ -1033,7 +1033,7 @@ def test_every_node_resolves_against_a_real_run(self): def test_matrix_placeholders_match_expanded_names(self): pattern = tool.name_pattern("Build / ${{ matrix.os }} / ${{ m }}") self.assertTrue(pattern.match("Build / windows-latest / msvc")) - self.assertFalse(pattern.match("Sanitizers / ubuntu-latest / gcc")) + self.assertFalse(pattern.match("Sanitizers / ubuntu-26.04 / gcc")) def test_an_unreadable_needs_form_is_refused_not_read_as_a_root(self): # Block lists and wrapped flow lists are legal YAML this parser does @@ -1058,12 +1058,12 @@ def test_a_literal_node_wins_over_a_matrix_pattern(self): # Otherwise correctness depends on which job happens to be declared # first, and #1574 reorders jobs. graph = {"Build / ${{ matrix.os }} / ${{ matrix.compiler }}": ["Wide"], - "Build / ubuntu-latest / gcc": ["Narrow"], + "Build / ubuntu-26.04 / gcc": ["Narrow"], "Wide": [], "Narrow": []} - jobs = ["Build / ubuntu-latest / gcc", "Build / macos-latest / clang", + jobs = ["Build / ubuntu-26.04 / gcc", "Build / macos-latest / clang", "Wide", "Narrow"] edges, drift = tool.match_graph_to_jobs(graph, jobs) - self.assertEqual(edges["Build / ubuntu-latest / gcc"], ["Narrow"]) + self.assertEqual(edges["Build / ubuntu-26.04 / gcc"], ["Narrow"]) self.assertEqual(edges["Build / macos-latest / clang"], ["Wide"]) self.assertEqual(drift["nodes_without_job"], []) @@ -1083,13 +1083,13 @@ def test_a_dependency_never_claims_a_job_that_resolved_elsewhere(self): graph = {"Sanitizers / ${{ matrix.os }} / ${{ matrix.compiler }}": [], "Sanitizers / tsan / ${{ matrix.compiler }}": ["Sanitizers / ${{ matrix.os }} / ${{ matrix.compiler }}"]} - jobs = ["Sanitizers / ubuntu-latest / gcc", "Sanitizers / tsan / gcc"] + jobs = ["Sanitizers / ubuntu-26.04 / gcc", "Sanitizers / tsan / gcc"] edges, _ = tool.match_graph_to_jobs(graph, jobs) for job, deps in edges.items(): with self.subTest(job=job): self.assertNotIn(job, deps, "a job cannot depend on itself") self.assertEqual(edges["Sanitizers / tsan / gcc"], - ["Sanitizers / ubuntu-latest / gcc"]) + ["Sanitizers / ubuntu-26.04 / gcc"]) def test_drift_is_reported_when_the_run_and_the_graph_disagree(self): edges, drift = tool.match_graph_to_jobs( @@ -1111,7 +1111,7 @@ def _analyze(self, name): def test_the_walk_follows_dependencies_not_the_nearest_finisher(self): path = self._analyze("run-success")["critical_path"]["observed"] chain = path["evidence"]["chain"] - self.assertEqual(chain[-1], "TSan / ubuntu-latest / clang") + self.assertEqual(chain[-1], "TSan / ubuntu-26.04 / clang") # TSan's real dependency is the lint sink, so the walk hops there; # the build matrix jobs finish nearby but are not a dependency of # TSan, and taking one would produce a plausible but wrong path. @@ -1127,9 +1127,9 @@ def test_the_observed_path_counts_queueing_and_the_graph_path_does_not(self): # different node sets and must not be equal. self.assertGreater(paths["graph"]["seconds"], paths["observed"]["seconds"]) - self.assertIn("Build / ubuntu-latest / gcc", + self.assertIn("Build / ubuntu-26.04 / gcc", paths["graph"]["evidence"]["chain"]) - self.assertNotIn("Build / ubuntu-latest / gcc", + self.assertNotIn("Build / ubuntu-26.04 / gcc", paths["observed"]["evidence"]["chain"]) def test_a_skip_cascade_whose_chain_avoids_it_stays_measured(self): @@ -1140,7 +1140,7 @@ def test_a_skip_cascade_whose_chain_avoids_it_stays_measured(self): # would attach a caveat that does not apply to the value shown. paths = self._analyze("run-skip-cascade")["critical_path"] self.assertEqual(paths["observed"]["status"], "measured") - self.assertNotIn("Build / ubuntu-24.04-arm / gcc", + self.assertNotIn("Build / ubuntu-26.04-arm / gcc", paths["observed"]["evidence"]["chain"]) def _with_skipped(self, job_name): @@ -1181,7 +1181,7 @@ def test_the_observed_walk_does_not_step_through_a_cancelled_job(self): # The walk routes around it rather than stopping: the same job still # ends the path, reached through the other dependency that did run. self.assertEqual(path["evidence"]["chain"][-1], - "TSan / ubuntu-latest / clang") + "TSan / ubuntu-26.04 / clang") self.assertIn("Detect build-triggering changes", path["evidence"]["chain"]) @@ -1198,7 +1198,7 @@ def test_the_observed_walk_stops_rather_than_inventing_a_hop(self): def test_a_job_outside_the_graph_stops_the_walk_as_partial(self): run, jobs = fixture("run-success") graph = tool.extract_graph(self.WORKFLOW) - del graph["TSan / ubuntu-latest / ${{ matrix.compiler }}"] + del graph["TSan / ubuntu-26.04 / ${{ matrix.compiler }}"] edges, drift = tool.match_graph_to_jobs( graph, [j["name"] for j in jobs["jobs"]]) report = tool.analyze(run, jobs, edges, drift) @@ -1264,7 +1264,7 @@ def test_an_untimed_job_off_the_graph_chain_still_qualifies_the_total(self): # A leaf the longest path legitimately routes around. Weighted at # zero during selection, it is precisely the node that ends up off # the winning chain, so a caveat scoped to the chain never fires. - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["started_at"] = None graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( @@ -1275,8 +1275,8 @@ def test_an_untimed_job_off_the_graph_chain_still_qualifies_the_total(self): self.assertEqual(path["status"], "partial") self.assertIn("routed around", path["reason"]) self.assertEqual(path["evidence"]["untimed_jobs_off_chain"], - ["Sanitizers / ubuntu-latest / clang"]) - self.assertNotIn("Sanitizers / ubuntu-latest / clang", + ["Sanitizers / ubuntu-26.04 / clang"]) + self.assertNotIn("Sanitizers / ubuntu-26.04 / clang", path["evidence"]["chain"]) def test_an_untimed_last_finisher_is_not_walked_past_in_silence(self): @@ -1286,7 +1286,7 @@ def test_an_untimed_last_finisher_is_not_walked_past_in_silence(self): # The observed walk starts at the last job to finish that was # timed, so untiming the actual last finisher moves the start # down one branch and the job never appears on the result. - if job["name"] == "TSan / ubuntu-latest / clang": + if job["name"] == "TSan / ubuntu-26.04 / clang": job["started_at"] = None graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( @@ -1295,8 +1295,8 @@ def test_an_untimed_last_finisher_is_not_walked_past_in_silence(self): drift)["critical_path"]["observed"] self.assertEqual(path["status"], "partial") self.assertEqual(path["evidence"]["untimed_jobs_off_chain"], - ["TSan / ubuntu-latest / clang"]) - self.assertNotIn("TSan / ubuntu-latest / clang", + ["TSan / ubuntu-26.04 / clang"]) + self.assertNotIn("TSan / ubuntu-26.04 / clang", path["evidence"]["chain"]) def test_a_skipped_job_is_never_counted_as_untimed(self): @@ -1416,7 +1416,7 @@ def test_a_completed_job_is_settled_whatever_the_run_status_says(self): unreadable = copy.deepcopy(run) unreadable["status"] = "something-new" mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["started_at"] = None @@ -1517,7 +1517,7 @@ def test_a_step_that_ran_outranks_a_span_that_says_otherwise(self): # then reports is true: the step evidence establishes it ran. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1536,7 +1536,7 @@ def test_a_step_of_no_duration_is_still_evidence(self): # those as unusable would discard the commonest shape there is. _, jobs = fixture("run-success") template = next(job["steps"][0] for job in jobs["jobs"] - if job["name"] == "Sanitizers / ubuntu-latest / clang") + if job["name"] == "Sanitizers / ubuntu-26.04 / clang") instant = dict(template, completed_at=template["started_at"]) self.assertEqual(tool.first_step_start({"steps": [instant]}), template["started_at"]) @@ -1548,7 +1548,7 @@ def test_a_start_stamped_at_creation_is_not_a_dispatch(self): # on a runner, and lists it as missing data besides. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1579,7 +1579,7 @@ def test_a_weighed_conclusion_with_never_run_stamps_reads_as_never_run( "a_conclusion_from_the_future"): with self.subTest(conclusion=conclusion): mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = conclusion @@ -1615,7 +1615,7 @@ def test_a_start_later_than_creation_alone_proves_the_job_ran(self): # was created. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1635,7 +1635,7 @@ def test_a_job_cancelled_while_it_waited_did_not_run(self): # instantly -- the misreading this whole rule exists to prevent. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1653,7 +1653,7 @@ def test_a_step_still_running_is_evidence_the_job_ran(self): # stamps would discard the only evidence there is. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1671,7 +1671,7 @@ def test_a_step_with_only_a_completion_is_not_evidence(self): # No start stamp to cite, so nothing says when the job began. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1688,7 +1688,7 @@ def test_a_skipped_or_backwards_step_is_not_evidence(self): # report disowns two sections later. _, jobs = fixture("run-success") template = next(job["steps"][0] for job in jobs["jobs"] - if job["name"] == "Sanitizers / ubuntu-latest / clang") + if job["name"] == "Sanitizers / ubuntu-26.04 / clang") self.assertIsNone(tool.first_step_start( {"steps": [dict(template, conclusion="skipped")]})) self.assertIsNone(tool.first_step_start( @@ -1700,7 +1700,7 @@ def test_a_skipped_or_backwards_step_is_not_evidence(self): def test_the_earliest_step_is_the_one_reported(self): _, jobs = fixture("run-success") steps = next(job["steps"] for job in jobs["jobs"] - if job["name"] == "Sanitizers / ubuntu-latest / clang") + if job["name"] == "Sanitizers / ubuntu-26.04 / clang") self.assertGreater(len(steps), 1) timed = [step["started_at"] for step in steps if step.get("started_at") and step.get("completed_at") @@ -1773,7 +1773,7 @@ def test_a_cancelled_job_that_ran_still_qualifies_the_critical_path(self): # so, or a total gets published as final with that job missing. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1791,7 +1791,7 @@ def test_a_cancelled_job_whose_runner_picked_it_up_is_not_excused(self): # and the caption does not cover them. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1808,7 +1808,7 @@ def test_a_cancelled_job_that_lost_a_stamp_is_not_excused(self): # degraded cells from the block. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1827,7 +1827,7 @@ def test_a_cancelled_job_whose_steps_ran_is_not_excused(self): # so phase seconds are printed for a job the caption would excuse. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1903,7 +1903,7 @@ def test_a_finished_job_in_a_live_run_is_not_told_it_may_still_start(self): live = copy.deepcopy(run) live["status"] = "in_progress" mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["started_at"] = None @@ -1927,7 +1927,7 @@ def test_a_job_whose_steps_ran_is_not_told_it_never_started(self): # is refuted by the same row that carries it. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["started_at"] = None @@ -1966,7 +1966,7 @@ def _held_for_approval(self, run_status): run["status"] = run_status mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["status"] = "waiting" job["conclusion"] = None job["started_at"] = None @@ -1984,7 +1984,7 @@ def test_a_job_held_for_approval_is_not_missing_data(self): # one-sided assertion let one of them drift back once already. for key in ("observed", "graph"): evidence = paths[key]["evidence"] - self.assertIn("Sanitizers / ubuntu-latest / clang", + self.assertIn("Sanitizers / ubuntu-26.04 / clang", evidence["unfinished_jobs_off_chain"], key) self.assertNotIn("untimed_jobs_off_chain", evidence, key) @@ -1995,7 +1995,7 @@ def test_a_job_still_waiting_in_a_finished_run_is_missing_data(self): paths = self._held_for_approval("completed") for key in ("observed", "graph"): evidence = paths[key]["evidence"] - self.assertIn("Sanitizers / ubuntu-latest / clang", + self.assertIn("Sanitizers / ubuntu-26.04 / clang", evidence["untimed_jobs_off_chain"], key) self.assertNotIn("unfinished_jobs_off_chain", evidence, key) @@ -2035,7 +2035,7 @@ def test_a_run_status_the_tool_cannot_read_is_not_read_as_running(self): # nobody can wait for. paths = self._held_for_approval("something-new") for key in ("observed", "graph"): - self.assertIn("Sanitizers / ubuntu-latest / clang", + self.assertIn("Sanitizers / ubuntu-26.04 / clang", paths[key]["evidence"]["untimed_jobs_off_chain"], key) @@ -2064,14 +2064,14 @@ def test_every_in_flight_status_takes_the_quiet_branch(self): run["status"] = "in_progress" mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["status"] = status job["conclusion"] = None job["started_at"] = None job["completed_at"] = None running, untimed = tool.untimed_executed_jobs( tool.analyze(run, mutated)["jobs"], run_status="in_progress") - self.assertIn("Sanitizers / ubuntu-latest / clang", running) + self.assertIn("Sanitizers / ubuntu-26.04 / clang", running) self.assertEqual(untimed, []) def test_a_job_that_completed_without_a_start_is_not_called_unfinished(self): @@ -2098,7 +2098,7 @@ def test_a_job_that_failed_without_starting_is_still_missing_data(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["conclusion"] = "failure" job["started_at"] = None # No step evidence, so only the conclusion check can keep @@ -2108,7 +2108,7 @@ def test_a_job_that_failed_without_starting_is_still_missing_data(self): edges, drift = tool.match_graph_to_jobs( graph, [j["name"] for j in mutated["jobs"]]) path = tool.analyze(run, mutated, edges, drift)["critical_path"]["graph"] - self.assertIn("Sanitizers / ubuntu-latest / clang", + self.assertIn("Sanitizers / ubuntu-26.04 / clang", path["evidence"]["untimed_jobs_off_chain"]) @staticmethod @@ -2131,7 +2131,7 @@ def test_a_job_cancelled_before_it_started_is_not_missing_data(self): # will never acquire one. Both alarms would be false. run, jobs = fixture("run-success") mutated = self._cancelled_before_starting( - jobs, "Sanitizers / ubuntu-latest / clang") + jobs, "Sanitizers / ubuntu-26.04 / clang") graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( graph, [j["name"] for j in mutated["jobs"]]) @@ -2148,21 +2148,21 @@ def test_a_status_the_tool_does_not_know_takes_the_loud_branch(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["status"] = "something-new" job["started_at"] = None graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( graph, [j["name"] for j in mutated["jobs"]]) path = tool.analyze(run, mutated, edges, drift)["critical_path"]["graph"] - self.assertIn("Sanitizers / ubuntu-latest / clang", + self.assertIn("Sanitizers / ubuntu-26.04 / clang", path["evidence"]["untimed_jobs_off_chain"]) def test_a_finished_job_with_no_duration_is_still_missing_data(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["started_at"] = None # completed, never started graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( @@ -2178,7 +2178,7 @@ def test_the_observed_reason_names_both_ways_it_can_be_wrong(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "TSan / ubuntu-latest / clang": + if job["name"] == "TSan / ubuntu-26.04 / clang": job["started_at"] = None graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( @@ -2318,7 +2318,7 @@ def test_labels_are_not_a_source_of_truth(self): disguised = [j for j in jobs["jobs"] if j.get("runner_name") == "semantic-reasoning-i401-6-2"] self.assertTrue(disguised, "fixture must keep the disguised runner") - self.assertIn("ubuntu-latest", disguised[0]["labels"]) + self.assertIn("ubuntu-26.04", disguised[0]["labels"]) self.assertEqual(tool.population_of(disguised[0]), "self-hosted") def test_windows_runner_is_self_hosted(self): @@ -2447,7 +2447,7 @@ def test_the_markdown_says_why_a_number_is_not_final(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["started_at"] = None graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( @@ -2614,7 +2614,7 @@ def test_a_job_cancelled_after_it_ran_is_not_excused(self): # one short. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -2635,7 +2635,7 @@ def test_a_job_excused_from_durations_is_excused_from_the_block(self): # the ones that are not absent by design. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["status"] = "completed" diff --git a/scripts/ci/test-early-size-gate.sh b/scripts/ci/test-early-size-gate.sh index f8ba86f8c..01f26ac19 100755 --- a/scripts/ci/test-early-size-gate.sh +++ b/scripts/ci/test-early-size-gate.sh @@ -148,7 +148,7 @@ assert 'main size report is uploaded through always()' grep -A6 -F 'name: Upload assert 'main artifacts have unique platform-qualified names' grep -Fq 'name: wirelog-size-monitor-${{ matrix.os }}' "$main_wf" assert 'baseline verifier trusts only canonical ubuntu artifact' grep -Fq 'wirelog-size-monitor-ubuntu-latest' "$root/scripts/ci/verify-size-baseline.py" assert 'main workflow grants no write permission' sh -c '! grep -Eq "^[[:space:]]+(actions|contents):[[:space:]]+write" "$1"' sh "$main_wf" -assert 'PR required build job identity remains unchanged' grep -Fq 'name: Build / ubuntu-latest / gcc' "$wf" +assert 'PR required build job identity remains unchanged' grep -Fq 'name: Build / ubuntu-26.04 / gcc' "$wf" # The production size gate has format-aware readers only for ELF and Mach-O. # Keep enforcing the platform-independent wiring assertions above, but do not diff --git a/scripts/ci/test-gate-skip-exit-codes.sh b/scripts/ci/test-gate-skip-exit-codes.sh index 6eca12ac6..ac3572952 100755 --- a/scripts/ci/test-gate-skip-exit-codes.sh +++ b/scripts/ci/test-gate-skip-exit-codes.sh @@ -226,7 +226,7 @@ unset coverage_padding coverage_large coverage_near # still safe -- and prefer adding a behavioural case over widening again. # -A6, not -A2: check-abi-manifest.sh's arm64 arch-skip prints five diagnostic # lines between its SKIP echo and its exit, and that branch is the one live on -# the ubuntu-24.04-arm required check. Two other branches sit at exactly the +# the ubuntu-26.04-arm required check. Two other branches sit at exactly the # old two-line limit, so the window had no margin and any added `echo` would # have silently dropped a branch out of coverage. # From 9a62e4ea00caa4fa12fd0ecca9de557ad9bb0a52 Mon Sep 17 00:00:00 2001 From: Justin Kim Date: Fri, 30 Oct 2026 03:37:51 +0900 Subject: [PATCH 2/3] ci(pr): adapt SBOM + sanitizer test steps to distro meson 1.10 Live PR CI (run 40888053298) exposed two distro-meson incompatibilities: 1. meson 1.10.1 has no 'meson test --exclude', so the sanitizer-matrix step now derives 'everything except sbom_snapshot' at run time from 'meson introspect --tests' (positional dir in 1.10.x) + grep -Fxv, keeping the other sbom-suite contract tests running. The PR-workflow assertion in test-required-gates.sh moves to a new sanitizer_excludes_snapshot_dynamic helper; ci-main.yml and tier1-sanitizers.yml keep the original --exclude assertion (pinned meson 1.12 via setup-meson). 2. sbom/snapshot.txt regenerated for the 26.04 profile: removing the setup-meson action references from ci-pr.yml drops the scanned local-action entry (12 -> 11 workflow references); the baseline now byte-matches a clean CI checkout scan (syft 1.50.0). --- .github/workflows/ci-pr.yml | 11 +++++++++-- sbom/snapshot.txt | 1 - scripts/ci/test-required-gates.sh | 21 +++++++++++++++++++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index 903d4a95c..2caad6526 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -638,8 +638,15 @@ jobs: - name: Test with sanitizers # The required, pinned-Syft SBOM gate runs in build-primary. A host - # Syft here can make its redundant scan time out under sanitizer load. - run: meson test -C builddir-san --exclude sbom_snapshot --print-errorlogs + # Syft here can make its redundant scan time out under sanitizer load, + # so sbom_snapshot is excluded here; every other test still runs, + # including the other sbom-suite contract checks. + # Distro meson (1.10.x on ubuntu-26.04) has no `meson test --exclude`, + # and `--no-suite sbom` would drop those contract checks too, so the + # exclusion is built from the configured test list at run time. + run: | + rest="$(meson introspect --tests builddir-san | jq -r '.[].name' | grep -Fxv sbom_snapshot | tr '\n' ' ')" + meson test -C builddir-san $rest --print-errorlogs env: ASAN_OPTIONS: abort_on_error=1:halt_on_error=1 UBSAN_OPTIONS: abort_on_error=1:halt_on_error=1:print_stacktrace=1 diff --git a/sbom/snapshot.txt b/sbom/snapshot.txt index 1eb19844e..6d8e7fa69 100644 --- a/sbom/snapshot.txt +++ b/sbom/snapshot.txt @@ -9,7 +9,6 @@ ./.github/actions/setup-meson@UNKNOWN:NOASSERTION ./.github/actions/setup-meson@UNKNOWN:NOASSERTION ./.github/actions/setup-meson@UNKNOWN:NOASSERTION -./.github/actions/setup-meson@UNKNOWN:NOASSERTION ./.github/workflows/fuzz-evidence-shard.yml@UNKNOWN:NOASSERTION ./.github/workflows/lint-main.yml@UNKNOWN:NOASSERTION ./.github/workflows/lint-pr.yml@UNKNOWN:NOASSERTION diff --git a/scripts/ci/test-required-gates.sh b/scripts/ci/test-required-gates.sh index 59c85a41f..1fc86be57 100755 --- a/scripts/ci/test-required-gates.sh +++ b/scripts/ci/test-required-gates.sh @@ -150,7 +150,11 @@ assert 'ci-pr.yml SBOM step sets WIRELOG_SBOM_REQUIRED' \ # The mandatory PR SBOM scan is distinct from the opportunistic scan in full # sanitizer suites. Exclude only that exact test: --no-suite sbom also drops -# the locale and snapshot contract tests. +# the locale and snapshot contract tests. Workflows still on the pinned +# setup-meson meson (1.12) use `meson test --exclude`; the PR workflow runs +# distro meson (1.10.x on ubuntu-26.04), which has no `--exclude`, so it +# builds the same "everything except sbom_snapshot" set from the configured +# test list at run time (sanitizer_excludes_snapshot_dynamic). sanitizer_excludes_snapshot() { local file=$1 job=$2 builddir=$3 awk -v job=" $job:" \ @@ -165,8 +169,21 @@ sanitizer_excludes_snapshot() { END { exit found == 1 ? 0 : 1 } ' "$root/.github/workflows/$file" } +sanitizer_excludes_snapshot_dynamic() { + local file=$1 job=$2 builddir=$3 + # Both lines must sit inside the named step: the list line excludes + # exactly sbom_snapshot by name, and the run line consumes that list. + awk -v job=" $job:" -v builddir="$builddir" ' + $0 == job { inside = 1; next } + inside && /^ [[:alnum:]_-]+:/ { exit } + inside && index($0, "meson introspect --tests " builddir) \ + && index($0, "grep -Fxv sbom_snapshot") { list = 1 } + inside && index($0, "meson test -C " builddir " $rest --print-errorlogs") { run = 1 } + END { exit (list && run) ? 0 : 1 } + ' "$root/.github/workflows/$file" +} assert 'ci-pr.yml sanitizer excludes only the duplicate SBOM snapshot' \ - sanitizer_excludes_snapshot ci-pr.yml sanitizer-matrix builddir-san + sanitizer_excludes_snapshot_dynamic ci-pr.yml sanitizer-matrix builddir-san assert 'ci-main.yml sanitizer excludes only the duplicate SBOM snapshot' \ sanitizer_excludes_snapshot ci-main.yml sanitizer-matrix builddir assert 'tier1-sanitizers.yml sanitizer excludes only the duplicate SBOM snapshot' \ From 68dcd01103d09f4a79f10f962f83e5ca5dbfa2f4 Mon Sep 17 00:00:00 2001 From: Justin Kim Date: Fri, 30 Oct 2026 09:11:15 +0900 Subject: [PATCH 3/3] ci(pr): expose Chocolatey meson install dir on Windows PATH The Chocolatey meson package is an MSI whose PATH entry is written to the machine registry -- invisible to the job's already-snapshotted environment -- and it creates no chocolatey shim, so every later step on windows-latest failed with "The term 'meson' is not recognized" (run 40932685444). Resolve the install dir (choco registry record first, documented default locations next, bounded scan last), verify meson and ninja by absolute path with exit-code checks, and publish the dir on GITHUB_PATH -- the same mechanism the former setup-meson action used. Fails loudly if either tool is missing or broken. --- .github/workflows/ci-pr.yml | 49 ++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index 2caad6526..86eba5d10 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -392,11 +392,58 @@ jobs: - name: Install dependencies (Windows) if: runner.os == 'Windows' + shell: pwsh run: | # pkgconfiglite ships from a SourceForge mirror without a # checksum; chocolatey's default policy now blocks empty # checksums for non-secure sources, so opt in explicitly. - choco install pkgconfiglite uncrustify meson -y --allow-empty-checksums + # `ninja` is named explicitly: meson requires it to spawn the + # build, and the block below verifies it is resolvable no + # matter how each package delivered it. + choco install pkgconfiglite uncrustify meson ninja -y --allow-empty-checksums + if ($LASTEXITCODE -ne 0) { throw "choco install failed with exit code $LASTEXITCODE" } + # The Chocolatey meson package is an MSI. Its PATH entry goes + # into the machine registry, which this job's already-snapshotted + # environment never sees, and chocolatey creates no shim for it, + # so `meson` was unresolvable in every later step (run + # 40932685444). Resolve the install directory (chocolatey's own + # registry record first, the MSI's documented default locations + # next, filesystem search last) and publish it on GITHUB_PATH + # -- the same mechanism the former setup-meson action used. + # Fails loudly if either tool is missing or broken. + $mesonDir = $null + $reg = Get-ItemProperty 'HKLM:\SOFTWARE\chocolatey\installed\meson' -ErrorAction SilentlyContinue + if ($reg.installLocation -and (Test-Path (Join-Path $reg.installLocation 'meson.exe'))) { + $mesonDir = $reg.installLocation + } + if (-not $mesonDir) { + foreach ($cand in 'C:\Program Files\Meson', 'C:\Program Files\meson', 'C:\Program Files (x86)\Meson', 'C:\Program Files (x86)\meson') { + if (Test-Path (Join-Path $cand 'meson.exe')) { $mesonDir = $cand; break } + } + } + if (-not $mesonDir) { + $found = Get-ChildItem 'C:\Program Files', 'C:\Program Files (x86)' -Recurse -Filter 'meson.exe' -ErrorAction SilentlyContinue | + Select-Object -First 1 + if (-not $found) { throw 'chocolatey reported success but meson.exe was not found' } + $mesonDir = Split-Path $found.FullName -Parent + } + $mesonExe = Join-Path $mesonDir 'meson.exe' + if (-not (Test-Path $mesonExe)) { throw "meson.exe not found in $mesonDir" } + & $mesonExe --version + if ($LASTEXITCODE -ne 0) { throw "meson --version failed with exit code $LASTEXITCODE" } + # GITHUB_PATH changes only take effect in later steps, so verify + # ninja here by full path (MSI-bundled copy) or via the + # chocolatey shim if the package delivered it that way. + $ninjaExe = Join-Path $mesonDir 'ninja.exe' + if (Test-Path $ninjaExe) { + & $ninjaExe --version + if ($LASTEXITCODE -ne 0) { throw "ninja --version failed with exit code $LASTEXITCODE" } + } else { + if (-not (Get-Command ninja -ErrorAction SilentlyContinue)) { throw 'ninja was not resolvable after chocolatey install' } + ninja --version + if ($LASTEXITCODE -ne 0) { throw "ninja --version failed with exit code $LASTEXITCODE" } + } + Add-Content $env:GITHUB_PATH $mesonDir - name: Configure (Linux / macOS) if: runner.os != 'Windows'