diff --git a/.github/workflows/call-images-build-ci.yml b/.github/workflows/call-images-build-ci.yml index 6167740b27..e88665756a 100644 --- a/.github/workflows/call-images-build-ci.yml +++ b/.github/workflows/call-images-build-ci.yml @@ -43,7 +43,7 @@ concurrency: jobs: build: name: ๐Ÿ’ฟ Image - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read packages: write diff --git a/.github/workflows/call-images-mirror-missing.yml b/.github/workflows/call-images-mirror-missing.yml index 3127bc68ef..1fe1e5e252 100644 --- a/.github/workflows/call-images-mirror-missing.yml +++ b/.github/workflows/call-images-mirror-missing.yml @@ -78,7 +78,7 @@ concurrency: jobs: sync: name: ๐Ÿชž Mirror - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read packages: write diff --git a/.github/workflows/call-lint.yml b/.github/workflows/call-lint.yml index d0d54bbff1..fce0906703 100644 --- a/.github/workflows/call-lint.yml +++ b/.github/workflows/call-lint.yml @@ -10,7 +10,7 @@ permissions: jobs: lint: name: ๐Ÿงน Lint - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 env: INFINITO_LINT_RUNNER: host diff --git a/.github/workflows/call-orchestrator.yml b/.github/workflows/call-orchestrator.yml index 7bf5546484..48453f6ab3 100644 --- a/.github/workflows/call-orchestrator.yml +++ b/.github/workflows/call-orchestrator.yml @@ -343,7 +343,7 @@ jobs: - test-deploy-chunk-2 - test-deploy-chunk-3 if: ${{ always() && github.ref == 'refs/heads/main' && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: issues: write actions: read @@ -374,7 +374,7 @@ jobs: - lint - security-codeql if: ${{ always() }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: ๐Ÿ›‘ Fail if any upstream job failed or was cancelled if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} diff --git a/.github/workflows/call-release-version.yml b/.github/workflows/call-release-version.yml index c6cc449a20..e16cfea5ca 100644 --- a/.github/workflows/call-release-version.yml +++ b/.github/workflows/call-release-version.yml @@ -30,7 +30,7 @@ env: jobs: check-ci-images: name: ๐Ÿ”Ž Check CI images - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: missing: ${{ steps.check.outputs.missing }} ci_tag: ${{ steps.citag.outputs.ci_tag }} @@ -123,7 +123,7 @@ jobs: needs.build-ci-images-if-missing.result == 'success' || needs.build-ci-images-if-missing.result == 'skipped' ) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read packages: write diff --git a/.github/workflows/call-test-deploy.yml b/.github/workflows/call-test-deploy.yml index b1da0d71dd..9fb7d25eff 100644 --- a/.github/workflows/call-test-deploy.yml +++ b/.github/workflows/call-test-deploy.yml @@ -163,7 +163,7 @@ env: jobs: discover: name: ๐Ÿ” Chunk ${{ inputs.index }}${{ inputs.marker }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 60 outputs: apps: ${{ steps.discover.outputs.apps }} @@ -251,7 +251,7 @@ jobs: deploy: name: "${{ matrix.label }}" if: needs.discover.outputs.apps != '' && needs.discover.outputs.apps != '[]' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 355 needs: discover diff --git a/.github/workflows/call-test-dns.yml b/.github/workflows/call-test-dns.yml index 133b4194bf..b45c0aacca 100644 --- a/.github/workflows/call-test-dns.yml +++ b/.github/workflows/call-test-dns.yml @@ -23,7 +23,7 @@ permissions: jobs: dns: name: ๐ŸŒ DNS - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - name: ๐Ÿงน Free disk space diff --git a/.github/workflows/call-test-install-make.yml b/.github/workflows/call-test-install-make.yml index 06acb0304f..36dcf5444d 100644 --- a/.github/workflows/call-test-install-make.yml +++ b/.github/workflows/call-test-install-make.yml @@ -10,7 +10,7 @@ permissions: jobs: test-install-make: name: ๐Ÿ› ๏ธ Makefile - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 30 env: NIX_CONFIG: | diff --git a/.github/workflows/call-test-install-pkgmgr.yml b/.github/workflows/call-test-install-pkgmgr.yml index 836eb87a31..e37e2281ea 100644 --- a/.github/workflows/call-test-install-pkgmgr.yml +++ b/.github/workflows/call-test-install-pkgmgr.yml @@ -10,7 +10,7 @@ permissions: jobs: test-install-pkgmgr: name: ๐Ÿ“ฆ PKGMGR - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 30 env: NIX_CONFIG: | diff --git a/.github/workflows/call-test-workspace.yml b/.github/workflows/call-test-workspace.yml index 102f25ae7d..f1021a8366 100644 --- a/.github/workflows/call-test-workspace.yml +++ b/.github/workflows/call-test-workspace.yml @@ -21,7 +21,7 @@ permissions: jobs: prepare: name: ๐Ÿง‘โ€๐Ÿ’ป Workspace Matrix - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 5 outputs: matrix: ${{ steps.matrix.outputs.matrix }} @@ -44,7 +44,7 @@ jobs: test-workspace: name: ${{ matrix.icon }} Workspace (${{ matrix.label }}) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 345 needs: prepare diff --git a/.github/workflows/call-test.yml b/.github/workflows/call-test.yml index ac26225993..47ed98878e 100644 --- a/.github/workflows/call-test.yml +++ b/.github/workflows/call-test.yml @@ -10,7 +10,7 @@ permissions: jobs: test: name: ๐Ÿงช Test - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 45 env: INFINITO_TEST_RUNNER: host diff --git a/.github/workflows/call-validation.yml b/.github/workflows/call-validation.yml index 5a1c32c004..6ff6dc2715 100644 --- a/.github/workflows/call-validation.yml +++ b/.github/workflows/call-validation.yml @@ -84,7 +84,7 @@ permissions: jobs: validation: name: โœ… Validation - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: โฌ‡๏ธ Checkout the calling ref for its tooling uses: actions/checkout@v7.0.0 @@ -148,7 +148,7 @@ jobs: wait-fork-prereq-run: name: โณ Wait for fork run if: ${{ inputs.fork_prereqs }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: WAIT_REQUIRED: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository && !inputs.trusted_pr }} steps: @@ -194,7 +194,7 @@ jobs: name: ๐Ÿšฆ Gate Dependencies needs: wait-fork-prereq-run if: ${{ !cancelled() && inputs.fork_prereqs }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v7.0.0 - name: โœ… Assert privileged prerequisites are ready diff --git a/.github/workflows/cron-cleanup-stale.yml b/.github/workflows/cron-cleanup-stale.yml index 35a5d0168e..d340ec5255 100644 --- a/.github/workflows/cron-cleanup-stale.yml +++ b/.github/workflows/cron-cleanup-stale.yml @@ -12,7 +12,7 @@ permissions: jobs: stale: name: ๐Ÿท๏ธ Close inactive issues and pull requests - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: issues: write pull-requests: write @@ -45,7 +45,7 @@ jobs: delete-stale-branches: name: ๐ŸŒฟ Delete inactive branches - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: write issues: write @@ -65,7 +65,7 @@ jobs: cleanup-ghcr-ci-images: name: ๐Ÿ“ฆ Delete old GHCR CI images - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read packages: write diff --git a/.github/workflows/cron-collection-source.yml b/.github/workflows/cron-collection-source.yml index 50dc753b10..be31adc04b 100644 --- a/.github/workflows/cron-collection-source.yml +++ b/.github/workflows/cron-collection-source.yml @@ -18,7 +18,7 @@ jobs: benchmark-collection-sources: name: โš–๏ธ Time Galaxy against the git fallback if: vars.CI_ENABLE_AUTO_UPDATES == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: HISTORY_PATH: build/collection-source-history.json steps: diff --git a/.github/workflows/cron-images-cleanup-ci.yml b/.github/workflows/cron-images-cleanup-ci.yml index 6f54549d13..57f076f6f4 100644 --- a/.github/workflows/cron-images-cleanup-ci.yml +++ b/.github/workflows/cron-images-cleanup-ci.yml @@ -22,7 +22,7 @@ concurrency: jobs: cleanup: name: ๐Ÿงน Delete old ci-* image versions - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: actions: read contents: read diff --git a/.github/workflows/cron-images-mirror-all.yml b/.github/workflows/cron-images-mirror-all.yml index 6b84158dfc..16427fd220 100644 --- a/.github/workflows/cron-images-mirror-all.yml +++ b/.github/workflows/cron-images-mirror-all.yml @@ -61,7 +61,7 @@ concurrency: jobs: sync: name: ๐Ÿชž Mirror - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read packages: write diff --git a/.github/workflows/cron-release-highest.yml b/.github/workflows/cron-release-highest.yml index 8e17fadbae..571e35e6cd 100644 --- a/.github/workflows/cron-release-highest.yml +++ b/.github/workflows/cron-release-highest.yml @@ -19,7 +19,7 @@ env: jobs: check-and-trigger: name: ๐Ÿ”Ž Backfill highest missing release tag - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read packages: read diff --git a/.github/workflows/cron-security-codeql.yml b/.github/workflows/cron-security-codeql.yml index 35f47e7b7f..bc8c1cee3e 100644 --- a/.github/workflows/cron-security-codeql.yml +++ b/.github/workflows/cron-security-codeql.yml @@ -34,7 +34,7 @@ jobs: # - https://gh.io/supported-runners-and-hardware-resources # - https://gh.io/using-larger-runners (GitHub.com only) # Consider using larger runners or machines with greater resources for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + runs-on: ubuntu-24.04 permissions: security-events: write packages: read diff --git a/.github/workflows/cron-security-fuzz.yml b/.github/workflows/cron-security-fuzz.yml index 56e4a1e9f1..fb3a90cb17 100644 --- a/.github/workflows/cron-security-fuzz.yml +++ b/.github/workflows/cron-security-fuzz.yml @@ -16,7 +16,7 @@ permissions: jobs: fuzz-yaml-bootstrap: name: ๐Ÿงฌ Fuzz the bootstrap YAML reader - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 30 steps: - name: โฌ‡๏ธ Checkout diff --git a/.github/workflows/cron-security-scorecard.yml b/.github/workflows/cron-security-scorecard.yml index 69eb85746f..33eb5cebfc 100644 --- a/.github/workflows/cron-security-scorecard.yml +++ b/.github/workflows/cron-security-scorecard.yml @@ -26,7 +26,7 @@ permissions: read-all jobs: analysis: name: ๐Ÿ” Scorecard analysis - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 # `publish_results: true` only works when run from the default branch. conditional can be removed if disabled. if: github.event.repository.default_branch == github.ref_name || github.event_name == 'pull_request' permissions: diff --git a/.github/workflows/cron-update.yml b/.github/workflows/cron-update.yml index 57f610fc05..f946f46cac 100644 --- a/.github/workflows/cron-update.yml +++ b/.github/workflows/cron-update.yml @@ -21,7 +21,7 @@ jobs: update-docker-image-versions: name: ๐Ÿณ Update Docker image versions if: vars.CI_ENABLE_AUTO_UPDATES == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: โฌ‡๏ธ Checkout uses: actions/checkout@v7.0.0 @@ -68,7 +68,7 @@ jobs: update-version-sources: name: ๐Ÿ”– Update declared version sources if: vars.CI_ENABLE_AUTO_UPDATES == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: โฌ‡๏ธ Checkout uses: actions/checkout@v7.0.0 @@ -115,7 +115,7 @@ jobs: update-pip-pins: name: ๐Ÿ Update pinned pip requirements if: vars.CI_ENABLE_AUTO_UPDATES == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: โฌ‡๏ธ Checkout uses: actions/checkout@v7.0.0 @@ -162,7 +162,7 @@ jobs: update-galaxy-pins: name: ๐ŸŒŒ Update pinned Ansible collections if: vars.CI_ENABLE_AUTO_UPDATES == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: โฌ‡๏ธ Checkout uses: actions/checkout@v7.0.0 @@ -209,7 +209,7 @@ jobs: update-repository-refs: name: ๐ŸŒฟ Update repository refs if: vars.CI_ENABLE_AUTO_UPDATES == 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: โฌ‡๏ธ Checkout uses: actions/checkout@v7.0.0 diff --git a/.github/workflows/entry-cancel-superseded.yml b/.github/workflows/entry-cancel-superseded.yml index dfd1df7907..defc9a7e5d 100644 --- a/.github/workflows/entry-cancel-superseded.yml +++ b/.github/workflows/entry-cancel-superseded.yml @@ -27,7 +27,7 @@ jobs: cancel-branch-runs: name: ๐Ÿšซ Cancel superseded branch runs if: ${{ github.event_name == 'push' && (github.ref_name != 'main' || (github.ref_name == 'main' && startsWith(github.event.head_commit.message, 'Release version '))) }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: actions: write contents: read @@ -52,7 +52,7 @@ jobs: cancel-pull-request-runs: name: ๐Ÿšซ Cancel superseded pull request runs if: ${{ github.event_name == 'pull_request_target' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: actions: write contents: read diff --git a/.github/workflows/entry-delete-branch.yml b/.github/workflows/entry-delete-branch.yml index 8b9113032e..b15f5c811d 100644 --- a/.github/workflows/entry-delete-branch.yml +++ b/.github/workflows/entry-delete-branch.yml @@ -20,7 +20,7 @@ permissions: jobs: cancel-branch-runs: if: ${{ github.event.ref_type == 'branch' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: actions: write contents: read diff --git a/.github/workflows/entry-manual-mirror-cleanup.yml b/.github/workflows/entry-manual-mirror-cleanup.yml index efc372ad24..c4a6fc24ee 100644 --- a/.github/workflows/entry-manual-mirror-cleanup.yml +++ b/.github/workflows/entry-manual-mirror-cleanup.yml @@ -40,7 +40,7 @@ permissions: jobs: cleanup: name: ๐Ÿ—‘๏ธ Delete private GHCR mirror packages - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read packages: write diff --git a/.github/workflows/entry-pr-change-orchestrate.yml b/.github/workflows/entry-pr-change-orchestrate.yml index 36d4aa63ee..2cf95bf625 100644 --- a/.github/workflows/entry-pr-change-orchestrate.yml +++ b/.github/workflows/entry-pr-change-orchestrate.yml @@ -29,7 +29,7 @@ jobs: detect-pr-scope: name: ๐Ÿ”Ž Detect PR scope if: ${{ github.event.pull_request.draft == false && (github.event_name == 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository) }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: scope: ${{ steps.resolve.outputs.scope }} run_ci_orchestrator: ${{ steps.resolve.outputs.run_ci_orchestrator }} @@ -59,7 +59,7 @@ jobs: - detect-pr-scope - validation if: ${{ needs.detect-pr-scope.outputs.run_ci_orchestrator == 'false' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: โœ… Confirm lightweight PR scope shell: bash @@ -73,7 +73,7 @@ jobs: - detect-pr-scope - validation if: ${{ needs.detect-pr-scope.outputs.run_ci_orchestrator == 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: whitelist: ${{ steps.resolve.outputs.whitelist }} steps: @@ -124,7 +124,7 @@ jobs: - detect-pr-scope - validation if: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && needs.detect-pr-scope.outputs.run_ci_orchestrator == 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: checkout_ref: ${{ steps.out.outputs.checkout_ref }} image_tag: ${{ steps.out.outputs.image_tag }} diff --git a/.github/workflows/entry-pr-closed-cancel-workflows.yml b/.github/workflows/entry-pr-closed-cancel-workflows.yml index c2f8e6ae12..f3d880ada1 100644 --- a/.github/workflows/entry-pr-closed-cancel-workflows.yml +++ b/.github/workflows/entry-pr-closed-cancel-workflows.yml @@ -15,7 +15,7 @@ permissions: jobs: cancel-pr-runs: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: actions: write contents: read diff --git a/.github/workflows/entry-pr-open-dependabot-close.yml b/.github/workflows/entry-pr-open-dependabot-close.yml index a3ced798c0..6d11bd73ce 100644 --- a/.github/workflows/entry-pr-open-dependabot-close.yml +++ b/.github/workflows/entry-pr-open-dependabot-close.yml @@ -19,7 +19,7 @@ jobs: close-when-disabled: name: ๐Ÿšซ Close Dependabot PR while gated if: github.event.pull_request.user.login == 'dependabot[bot]' && vars.CI_ENABLE_AUTO_UPDATES != 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: ๐Ÿ“ช Close PR env: diff --git a/.github/workflows/entry-push-latest.yml b/.github/workflows/entry-push-latest.yml index a22ee0ca8b..7bbddb2caa 100644 --- a/.github/workflows/entry-push-latest.yml +++ b/.github/workflows/entry-push-latest.yml @@ -21,7 +21,7 @@ permissions: jobs: run-policy: name: ๐Ÿงญ Decide CI policy - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 permissions: contents: read pull-requests: read @@ -59,7 +59,7 @@ jobs: name: โญ๏ธ Skip CI needs: run-policy if: needs.run-policy.outputs.should_run != 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: โญ๏ธ Explain skip shell: bash @@ -72,7 +72,7 @@ jobs: if: needs.run-policy.outputs.should_run == 'true' needs: run-policy name: ๐Ÿท๏ธ Detect release tag - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: is_version: ${{ steps.pick.outputs.is_version }} version_tag: ${{ steps.pick.outputs.version_tag }} @@ -91,7 +91,7 @@ jobs: if: needs.run-policy.outputs.should_run == 'true' name: โญ Resolve new-role priority needs: run-policy - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: priority: ${{ steps.resolve.outputs.priority }} steps: @@ -132,7 +132,7 @@ jobs: - run-policy - detect-release - ci-orchestrator - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 outputs: allowed: ${{ steps.guard.outputs.allowed }} steps: diff --git a/docs/contributing/actions/debugging/ci.md b/docs/contributing/actions/debugging/ci.md index 9ee6075aff..bb84bf1450 100644 --- a/docs/contributing/actions/debugging/ci.md +++ b/docs/contributing/actions/debugging/ci.md @@ -49,11 +49,11 @@ Some CI failures (most notably OOM-kills such as [#162](https://github.com/infin ### GitHub-Hosted Runner Specs -All [workflows](../../../../.github/workflows/) target `runs-on: ubuntu-latest`, which maps to the GitHub-hosted standard Linux runner. The effective ceiling depends on the repository's visibility and plan: +All [workflows](../../../../.github/workflows/) target `runs-on: ubuntu-24.04`, the GitHub-hosted standard Linux runner pinned to one Ubuntu release. GitHub still refreshes that image weekly; moving to the next release is a deliberate change of the label, and `tests/lint/repository/test_workflow_runner_pinned.py` rejects `*-latest` labels. The effective ceiling depends on the repository's visibility and plan: | Repository / plan | vCPU | RAM | SSD | |------------------------------------------|------|--------|-------| -| Public repo (this repo, `ubuntu-latest`) | 4 | 16 GB | 14 GB | +| Public repo (this repo, `ubuntu-24.04`) | 4 | 16 GB | 14 GB | | Private repo on Free / Pro / Team | 2 | 7 GB | 14 GB | | Private repo on Enterprise | 4 | 16 GB | 14 GB | @@ -70,7 +70,7 @@ For the authoritative specs, see the upstream docs for [standard hosted runners] Pick the profile that matches the runner you want to mimic: ```sh -# Public repo runner (ubuntu-latest, 4 vCPU / 16 GB): the default CI target +# Public repo runner (ubuntu-24.04, 4 vCPU / 16 GB): the default CI target INFINITO_MEM_LIMIT=16g INFINITO_MEMSWAP_LIMIT=16g INFINITO_CPUS=4 make # Private repo runner on Free/Pro/Team (2 vCPU / 7 GB) diff --git a/tests/lint/repository/test_workflow_runner_pinned.py b/tests/lint/repository/test_workflow_runner_pinned.py new file mode 100644 index 0000000000..4aa1838c1c --- /dev/null +++ b/tests/lint/repository/test_workflow_runner_pinned.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +import unittest + +from utils.cache.yaml import load_yaml_any + +from . import PROJECT_ROOT + +_WORKFLOWS_DIR = PROJECT_ROOT / ".github" / "workflows" + + +class TestWorkflowRunnerPinned(unittest.TestCase): + def test_no_job_runs_on_a_latest_label(self) -> None: + offenders: list[str] = [] + for path in sorted(_WORKFLOWS_DIR.glob("*.y*ml")): + document = load_yaml_any(str(path), default_if_missing={}) or {} + for job_id, job in (document.get("jobs") or {}).items(): + if not isinstance(job, dict) or "runs-on" not in job: + continue + if "-latest" in str(job["runs-on"]): + offenders.append(f"{path.name}: {job_id}: {job['runs-on']}") + + if offenders: + self.fail( + f"{len(offenders)} job(s) run on a moving '*-latest' runner label. " + "GitHub moves that label to the next OS release on its own " + "schedule (ubuntu-latest jumped to 26.04 mid-run); pin the " + "release, e.g. 'ubuntu-24.04', and move it in a dedicated " + "change.\n" + "\n".join(offenders) + ) + + +if __name__ == "__main__": + unittest.main()