From 869f07b3b0a2abdd21474486dcf17797f3ce2f67 Mon Sep 17 00:00:00 2001 From: backryun Date: Fri, 31 Jul 2026 06:34:42 +0900 Subject: [PATCH] chore(ci): adopt Ubuntu 26.04 runners --- .github/workflows/ci.yml | 166 ++++++------------ .github/workflows/claude.yml | 4 +- .github/workflows/codeql.yml | 2 +- .github/workflows/dast-smoke.yml | 6 +- .github/workflows/deploy-vps.yml | 2 +- .github/workflows/docker-publish.yml | 10 +- .github/workflows/electron-release.yml | 16 +- .github/workflows/lock-released-branch.yml | 4 +- .github/workflows/mutation-redundancy.yml | 8 +- .github/workflows/nightly-compat.yml | 20 +-- .github/workflows/nightly-llm-security.yml | 13 +- .github/workflows/nightly-mutation.yml | 15 +- .github/workflows/nightly-property.yml | 8 +- .github/workflows/nightly-release-green.yml | 21 +-- .github/workflows/nightly-resilience.yml | 32 +--- .github/workflows/nightly-schemathesis.yml | 6 +- .github/workflows/npm-publish.yml | 12 +- .github/workflows/opencode-plugin-ci.yml | 12 +- .github/workflows/opencode-provider-ci.yml | 12 +- .github/workflows/quality.yml | 76 +++----- .github/workflows/scorecard.yml | 4 +- .github/workflows/semgrep.yml | 2 +- .github/workflows/wiki-sync.yml | 9 +- scripts/build/build-next-isolated.mjs | 2 +- tests/unit/build/check-workflows.test.ts | 7 +- ...nightly-compat-node26-webpack-8090.test.ts | 7 +- tests/unit/vps-runner-variable-scope.test.ts | 9 +- 27 files changed, 169 insertions(+), 316 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 542f44b973..5f6ee7f0dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ env: jobs: changes: name: Change Classification - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 outputs: code: ${{ steps.classify.outputs.code }} docs: ${{ steps.classify.outputs.docs }} @@ -35,13 +35,10 @@ jobs: workflow: ${{ steps.classify.outputs.workflow }} testsOnly: ${{ steps.classify.outputs.testsOnly }} steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} # Refuse a PR that targets its own head branch before spending anything on it. #8912 has # head == base == release/v3.8.50: no diff, can never merge, and it sits in the queue with # a full check board attached on every push to that branch. One field comparison. @@ -77,7 +74,7 @@ jobs: lint: name: Lint - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 needs: changes # P3 (plano mestre): a release-PR viva fica DRAFT o ciclo inteiro — jobs pesados pulam # drafts (ciclo v3.8.44: 123 runs pesados re-disparados por merges na release, 88 cancelados). @@ -91,13 +88,9 @@ jobs: API_KEY_SECRET: ci-lint-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - run: npm run check:node-runtime - run: npm run audit:deps @@ -171,7 +164,7 @@ jobs: quality-gate: name: Quality Ratchet - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 # needs lint so eslint-results artifact is available (same inventory as the # blocking lint step). Allow lint failure so other ratchets still run. needs: [changes, test-coverage, lint] @@ -191,13 +184,9 @@ jobs: contents: read security-events: read steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - name: Restore ESLint file cache uses: actions/cache@v6 @@ -289,7 +278,7 @@ jobs: # SonarQube needs SONAR_TOKEN/SONAR_HOST_URL secrets. quality-extended: name: Quality Gates (Extended) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 needs: changes # P3 (plano mestre): a release-PR viva fica DRAFT o ciclo inteiro — jobs pesados pulam # drafts (ciclo v3.8.44: 123 runs pesados re-disparados por merges na release, 88 cancelados). @@ -299,14 +288,10 @@ jobs: # fetch-depth: 0 — the OpenAPI breaking-change gate (oasdiff) reads the base # spec via `git show :docs/openapi.yaml`; a shallow clone # would lack the base ref and the gate would self-skip (base-unresolved). - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry # Dead-code, cognitive-complexity, type-coverage foram promovidos ao job # quality-gate (bloqueante) na Fase 7 INT — não rodam aqui para evitar duplo custo. @@ -409,20 +394,16 @@ jobs: docs-sync-strict: name: Docs Sync (Strict) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 needs: changes # P3 (plano mestre): a release-PR viva fica DRAFT o ciclo inteiro — jobs pesados pulam # drafts (ciclo v3.8.44: 123 runs pesados re-disparados por merges na release, 88 cancelados). # Run when docs OR code change: API/route code can break doc/OpenAPI contract gates. if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && (needs.changes.outputs.docs == 'true' || needs.changes.outputs.code == 'true')) }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - run: npm run check:docs-all # Previously-orphaned contract gates (existed as files, never wired anywhere). @@ -442,7 +423,7 @@ jobs: docs-lint: name: Docs Lint (prose — advisory) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 needs: changes # P3 (plano mestre): a release-PR viva fica DRAFT o ciclo inteiro — jobs pesados pulam # drafts (ciclo v3.8.44: 123 runs pesados re-disparados por merges na release, 88 cancelados). @@ -452,13 +433,9 @@ jobs: # existing doc corpus is brought up to style. Promote to blocking once it converges. continue-on-error: true steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - name: markdownlint (docs + root, advisory) run: npx --yes markdownlint-cli2 "docs/**/*.md" "*.md" "!docs/i18n" "!docs/research" || true - name: Vale prose lint (Microsoft style, advisory) @@ -473,7 +450,7 @@ jobs: i18n-ui-coverage: name: i18n UI Coverage - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 needs: changes # P3 (plano mestre): a release-PR viva fica DRAFT o ciclo inteiro — jobs pesados pulam # drafts (ciclo v3.8.44: 123 runs pesados re-disparados por merges na release, 88 cancelados). @@ -483,14 +460,10 @@ jobs: # fetch-depth: 0 — the value-drift gate diffs en.json against the merge base to # find rewritten English strings. On a shallow clone the base ref is missing and # the gate self-skips (base-unresolved), so it would never actually run. - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - run: node scripts/i18n/check-ui-keys-coverage.mjs --threshold=65 # #8463: a rewritten English value used to leave its 39 translations behind @@ -506,17 +479,13 @@ jobs: # without needing app-boot/Playwright infra. Same gating as i18n-ui-coverage. i18n-glossary-zhcn: name: i18n Glossary (zh-CN) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 needs: changes if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && (needs.changes.outputs.i18n == 'true' || needs.changes.outputs.code == 'true')) }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - run: node scripts/i18n/check-glossary-consistency.mjs --locale=zh-CN - run: node scripts/i18n/check-glossary-consistency.mjs --locale=zh-TW @@ -528,7 +497,7 @@ jobs: # idioma (a matrix antiga subia 40 artifacts cujo result.txt colidia no merge-multiple). i18n: name: i18n Validation (all languages) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 needs: changes # P3 (plano mestre): a release-PR viva fica DRAFT o ciclo inteiro — jobs pesados pulam # drafts (ciclo v3.8.44: 123 runs pesados re-disparados por merges na release, 88 cancelados). @@ -536,7 +505,7 @@ jobs: if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && needs.changes.outputs.i18n == 'true') }} continue-on-error: true steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-python@v7 @@ -571,15 +540,12 @@ jobs: pr-test-policy: name: PR Test Policy if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == false }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - name: Fetch base branch run: git fetch --no-tags origin "${GITHUB_BASE_REF}" - name: Validate source changes include tests @@ -606,17 +572,17 @@ jobs: # online), the heavy jobs run on the dedicated 32-core VPS runners (label # omni-release) instead of queueing on the 20-concurrent-job hosted pool. # Safety: fork PRs NEVER reach the self-hosted runner — the expression falls - # back to ubuntu-latest unless the PR head repo is this repository (push / + # back to ubuntu-26.04 unless the PR head repo is this repository (push / # dispatch events are own-origin by definition). Any failure path (VM down, - # var unset/false) also falls back to ubuntu-latest. - runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }} + # var unset/false) also falls back to ubuntu-26.04. + runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-26.04' }} needs: changes if: ${{ github.event_name != 'pull_request' || (needs.changes.outputs.code == 'true' && github.event.pull_request.draft == false) }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: ${{ env.CI_NODE_VERSION }} cache: npm @@ -656,18 +622,14 @@ jobs: package-artifact: name: Package Artifact - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 needs: build env: JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - run: npm run check:node-runtime - name: Download Next.js build artifact @@ -703,15 +665,15 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-26.04, windows-latest] env: JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation CSC_IDENTITY_AUTO_DISCOVERY: "false" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: ${{ env.CI_NODE_VERSION }} cache: npm @@ -750,14 +712,14 @@ jobs: test-unit: name: Unit Tests (${{ matrix.shard }}/8) - # Same dynamic-runner rule as Build (own-origin only; fallback ubuntu-latest). + # Same dynamic-runner rule as Build (own-origin only; fallback ubuntu-26.04). # PINNED to hosted, deliberately not on the USE_VPS_RUNNER switch (gap 19). One variable # governed the build and the test jobs, which want OPPOSITE machines: the build needs the # .113's RAM, the tests need the hosted runner's link. Measured on 2026-07-29 — # actions/setup-node took 20m06s on .113 with 4 concurrent runners versus 16s hosted (npm # cache restore saturating the link), while the tests themselves tied, 2m54 vs 2m31. So # self-hosted is strictly worse here and there is nothing to configure. - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 25 # needs: changes (not build) — this job never downloads the next-build artifact; # gating it on Build only serialized ~20min of wall-clock for nothing. Jobs that @@ -775,13 +737,9 @@ jobs: API_KEY_SECRET: ci-test-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - run: npm run check:node-runtime # QW-d (plano mestre): fonte única — o MESMO npm script dos runs locais (adiciona o @@ -811,31 +769,27 @@ jobs: test-bun-sqlite: name: Bun SQLite Compatibility - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 10 needs: changes if: ${{ github.event_name != 'pull_request' || (needs.changes.outputs.code == 'true' && github.event.pull_request.draft == false) }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - run: npm run test:bun:db test-vitest: name: Vitest (MCP / autoCombo / UI components) - # Same dynamic-runner rule as Build (own-origin only; fallback ubuntu-latest). + # Same dynamic-runner rule as Build (own-origin only; fallback ubuntu-26.04). # PINNED to hosted, deliberately not on the USE_VPS_RUNNER switch (gap 19). One variable # governed the build and the test jobs, which want OPPOSITE machines: the build needs the # .113's RAM, the tests need the hosted runner's link. Measured on 2026-07-29 — # actions/setup-node took 20m06s on .113 with 4 concurrent runners versus 16s hosted (npm # cache restore saturating the link), while the tests themselves tied, 2m54 vs 2m31. So # self-hosted is strictly worse here and there is nothing to configure. - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 # needs: changes (not build) — no artifact consumed; see test-unit note. needs: changes @@ -845,13 +799,9 @@ jobs: API_KEY_SECRET: ci-test-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry # The second test runner (CLAUDE.md: "Both test runners must pass") — was never # wired into CI until the 2026-06-09 quality audit (Fase 6A.2). @@ -879,7 +829,7 @@ jobs: # the release gate can still exercise them via workflow_dispatch when needed). test-coverage: name: Coverage - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 # 10min was sized before #7114 added the lcov reporter (Codecov/Sonar need it); # merging 8 shard JSONs + text+json+lcov now takes ~10-12min — three consecutive # release-tip runs died at exactly 10m as job-timeout "cancelled" (2026-07-15/16). @@ -890,13 +840,9 @@ jobs: JWT_SECRET: ci-test-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-test-api-key-secret-long steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - name: Download all shard coverage uses: actions/download-artifact@v8 @@ -980,14 +926,14 @@ jobs: sonarqube: name: SonarQube - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 needs: test-coverage if: ${{ !cancelled() && needs.test-coverage.result == 'success' }} env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 @@ -1032,7 +978,7 @@ jobs: coverage-pr-comment: name: PR Coverage Comment - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 if: ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.draft == false && github.event.pull_request.head.repo.fork == false && needs.changes.outputs.code == 'true' }} needs: - changes @@ -1111,7 +1057,7 @@ jobs: test-e2e: name: E2E Tests (${{ matrix.shard }}/9) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 # Build artifact from the `build` job is downloaded instead of rebuilding # (~5min saved per shard). 9 shards (up from 6) reduces tests per shard by # ~33%. Playwright browser is cached across runs (~1.5min saved per shard). @@ -1133,13 +1079,9 @@ jobs: DISABLE_SQLITE_AUTO_BACKUP: "true" OMNIROUTE_PLAYWRIGHT_SKIP_BUILD: "1" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - run: npm run check:node-runtime - name: Cache Playwright browsers @@ -1188,7 +1130,7 @@ jobs: test-integration: name: Integration Tests (${{ matrix.shard }}/2) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 # needs: changes (not build) — no artifact consumed; see test-unit note. needs: changes @@ -1204,13 +1146,9 @@ jobs: DATA_DIR: /tmp/omniroute-ci-${{ matrix.shard }} DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - run: npm run check:node-runtime # (tsx/esm = QW-b; o alinhamento de ESCOPO do integration com o npm script fica p/ follow-up) @@ -1218,7 +1156,7 @@ jobs: test-security: name: Security Tests - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 # needs: changes (not build) — no artifact consumed; see test-unit note. needs: changes if: ${{ github.event_name != 'pull_request' || (needs.changes.outputs.code == 'true' && github.event.pull_request.draft == false) }} @@ -1227,20 +1165,16 @@ jobs: API_KEY_SECRET: ci-test-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - uses: ./.github/actions/npm-ci-retry - run: npm run check:node-runtime - run: npm run test:security ci-summary: name: CI Dashboard - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 if: ${{ !cancelled() }} needs: - changes diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 903eedd613..eb21c1cbca 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -21,7 +21,7 @@ jobs: (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: contents: read pull-requests: read @@ -30,7 +30,7 @@ jobs: actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 25fb72db24..db79d51e0e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,7 +13,7 @@ permissions: jobs: analyze: name: Analyze (javascript-typescript) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: security-events: write actions: read diff --git a/.github/workflows/dast-smoke.yml b/.github/workflows/dast-smoke.yml index f4e2d65155..7dc0496a9a 100644 --- a/.github/workflows/dast-smoke.yml +++ b/.github/workflows/dast-smoke.yml @@ -18,7 +18,7 @@ concurrency: cancel-in-progress: true jobs: dast-smoke: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 # ADVISORY while this new gate matures (repo convention: advisory -> blocking). # Flip to blocking (remove continue-on-error) once it's proven stable across a few PRs. continue-on-error: true @@ -33,10 +33,6 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 - with: - node-version: "24" - cache: npm - run: npm ci - name: Build CLI bundle env: diff --git a/.github/workflows/deploy-vps.yml b/.github/workflows/deploy-vps.yml index bcd5b61c78..a2c4ddfd71 100644 --- a/.github/workflows/deploy-vps.yml +++ b/.github/workflows/deploy-vps.yml @@ -15,7 +15,7 @@ jobs: (github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success') && vars.DEPLOY_ENABLED == 'true' name: Deploy OmniRoute to VPS - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Check VPS SSH reachability from runner id: reach diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 022a9f270f..7cbe2f6bfd 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -33,7 +33,7 @@ permissions: jobs: prepare: name: Resolve Docker release metadata - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 outputs: version: ${{ steps.version.outputs.version }} promote_latest: ${{ steps.version.outputs.promote_latest }} @@ -42,7 +42,7 @@ jobs: IMAGE_NAME: diegosouzapw/omniroute steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.version) || '' }} @@ -145,7 +145,7 @@ jobs: GHCR_IMAGE_NAME: ghcr.io/diegosouzapw/omniroute steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.version) || '' }} @@ -233,7 +233,7 @@ jobs: - prepare - build if: needs.prepare.outputs.skip != 'true' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: contents: read packages: write @@ -245,7 +245,7 @@ jobs: PROMOTE_LATEST: ${{ needs.prepare.outputs.promote_latest }} steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/v{0}', inputs.version) || '' }} diff --git a/.github/workflows/electron-release.yml b/.github/workflows/electron-release.yml index f1e3f869e6..10877c38ae 100644 --- a/.github/workflows/electron-release.yml +++ b/.github/workflows/electron-release.yml @@ -20,14 +20,14 @@ permissions: jobs: validate: name: Validate version - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: contents: read outputs: version: ${{ steps.validate.outputs.version }} steps: - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 @@ -78,17 +78,17 @@ jobs: target: mac-arm64 ext: -arm64.dmg - platform: linux - runner: ubuntu-latest + runner: ubuntu-26.04 target: linux ext: .AppImage deb_ext: .deb steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Setup Node - uses: actions/setup-node@v7 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: 24 cache: npm @@ -239,12 +239,12 @@ jobs: # Now: attach everything that did build, then fail the job loudly (see the last # step) so an incomplete channel is visible instead of silent. if: ${{ !cancelled() && needs.validate.result == 'success' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: contents: write # softprops/action-gh-release creates the GitHub Release steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 @@ -329,7 +329,7 @@ jobs: # of passing unnoticed — the v3.8.49 release had ZERO assets and every gate was # green, because nothing ever asserted the release HAS binaries. if: ${{ !cancelled() && needs.release.result == 'success' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: contents: read steps: diff --git a/.github/workflows/lock-released-branch.yml b/.github/workflows/lock-released-branch.yml index 0ab0d183d2..f6dbdb702b 100644 --- a/.github/workflows/lock-released-branch.yml +++ b/.github/workflows/lock-released-branch.yml @@ -40,7 +40,7 @@ jobs: # ───────────────────────────────────────────────────────────────────────── lock-branch: if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Lock release/ branch env: @@ -97,7 +97,7 @@ jobs: # ───────────────────────────────────────────────────────────────────────── guard-no-push-after-release: if: github.event_name == 'push' - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Reject push if matching release tag exists env: diff --git a/.github/workflows/mutation-redundancy.yml b/.github/workflows/mutation-redundancy.yml index 0960846c5f..dfd4831d34 100644 --- a/.github/workflows/mutation-redundancy.yml +++ b/.github/workflows/mutation-redundancy.yml @@ -22,7 +22,7 @@ permissions: jobs: stryker-nobail: name: Stryker disableBail (batch ${{ matrix.batch.name }}) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: @@ -41,13 +41,9 @@ jobs: mutate: "open-sse/handlers/chatCore/telemetryHelpers.ts,open-sse/handlers/chatCore/memorySkillsInjection.ts,open-sse/handlers/chatCore/semanticCache.ts" timeout-minutes: 300 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: "24" - cache: npm - run: npm ci - name: Run Stryker (disableBail) env: diff --git a/.github/workflows/nightly-compat.yml b/.github/workflows/nightly-compat.yml index 9515d6b218..cb94a1a127 100644 --- a/.github/workflows/nightly-compat.yml +++ b/.github/workflows/nightly-compat.yml @@ -28,11 +28,11 @@ concurrency: jobs: resolve-branch: name: Resolve active release branch - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 outputs: target: ${{ steps.branch.outputs.target }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false @@ -58,15 +58,15 @@ jobs: compat-build-26: name: Node 26 Compatibility Build - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 25 needs: resolve-branch steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.resolve-branch.outputs.target }} persist-credentials: false - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: "26" cache: npm @@ -75,7 +75,7 @@ jobs: # CI_NODE_VERSION=24). It failed every nightly with the runner-reclaimed # signature ("The runner has received a shutdown signal" / "The operation was # canceled", no exit code) always at the same Turbopack compile phase — a - # classic OOM kill on the memory-constrained ubuntu-latest runner. Turbopack's + # classic OOM kill on the memory-constrained 16 GB hosted runner. Turbopack's # native (Rust, off-V8-heap) allocation is NOT bounded by --max-old-space-size # and peaks far higher than webpack on this large module graph (#6409), and is # heavier still under Node 26. Use the documented webpack fallback here: it still @@ -88,7 +88,7 @@ jobs: compat-tests: name: Node ${{ matrix.node }} Compat Tests (${{ matrix.shard }}/4) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 25 needs: resolve-branch strategy: @@ -102,11 +102,11 @@ jobs: DISABLE_SQLITE_AUTO_BACKUP: "true" TEST_SHARD: ${{ matrix.shard }}/4 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ needs.resolve-branch.outputs.target }} persist-credentials: false - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: ${{ matrix.node }} cache: npm @@ -116,7 +116,7 @@ jobs: report: name: Open / update tracking issue on failure - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 if: ${{ !cancelled() && (needs.compat-tests.result == 'failure' || needs.compat-build-26.result == 'failure') }} needs: [resolve-branch, compat-build-26, compat-tests] permissions: diff --git a/.github/workflows/nightly-llm-security.yml b/.github/workflows/nightly-llm-security.yml index 140a92d2b9..42ca4c3ede 100644 --- a/.github/workflows/nightly-llm-security.yml +++ b/.github/workflows/nightly-llm-security.yml @@ -10,13 +10,11 @@ permissions: jobs: promptfoo-guard: name: promptfoo — injection guard (block mode, no secret) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: { node-version: "24", cache: npm } - run: npm ci - name: Build CLI bundle env: @@ -46,7 +44,7 @@ jobs: garak: name: garak probes (skip without provider secret) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 # NOTE: the `secrets` context is NOT available in a job-level `if:` — referencing # it there makes GitHub reject the file on push (startup_failure on every push). # Map the secret into a job-level env and gate each step on a presence check, so @@ -63,13 +61,10 @@ jobs: echo "run=false" >> "$GITHUB_OUTPUT" echo "::notice::PROMPTFOO_PROVIDER_KEY not set — skipping garak probes (advisory)." fi - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false if: steps.gate.outputs.run == 'true' - - uses: actions/setup-node@v7 - if: steps.gate.outputs.run == 'true' - with: { node-version: "24", cache: npm } - run: npm ci if: steps.gate.outputs.run == 'true' - name: Build CLI bundle diff --git a/.github/workflows/nightly-mutation.yml b/.github/workflows/nightly-mutation.yml index 9ffb5c22b5..3d8a88a43a 100644 --- a/.github/workflows/nightly-mutation.yml +++ b/.github/workflows/nightly-mutation.yml @@ -10,7 +10,7 @@ permissions: jobs: stryker: name: Stryker mutation (batch ${{ matrix.batch.name }} — advisory) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 # Mutation testing is expensive. History of the budget: # - Full 8-module set TIMED OUT at the 180min cap (run 27705123780 = exactly 180min). # The two god-files chatCore.ts/combo.ts dominated ~2/3 of the mutants and were @@ -104,13 +104,9 @@ jobs: # scripts/quality/mutation-radiography.mjs both merge per file). timeout-minutes: ${{ matrix.batch.timeout || 180 }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: "24" - cache: npm - run: npm ci - name: Restore Stryker incremental cache uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 @@ -145,15 +141,12 @@ jobs: name: Mutation score ratchet (blocking) needs: stryker if: always() - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 15 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: "24" - name: Download all mutation reports uses: actions/download-artifact@v8 with: diff --git a/.github/workflows/nightly-property.yml b/.github/workflows/nightly-property.yml index 2ef7a543af..dda787f404 100644 --- a/.github/workflows/nightly-property.yml +++ b/.github/workflows/nightly-property.yml @@ -8,15 +8,11 @@ permissions: issues: write jobs: property-random-seed: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: "24" - cache: npm - run: npm ci - name: fast-check random seed (high runs) id: prop diff --git a/.github/workflows/nightly-release-green.yml b/.github/workflows/nightly-release-green.yml index f435bf5acd..ec03f4ca84 100644 --- a/.github/workflows/nightly-release-green.yml +++ b/.github/workflows/nightly-release-green.yml @@ -68,13 +68,13 @@ jobs: # this runs on the dedicated VPS runner — clean env (no operator OMNIROUTE_API_KEY, # no local noauth CLIs => zero machine-specific false positives) and no contention. # Nightly cron normally finds the var false (VM off) and falls back to hosted. - runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-release"]')) || 'ubuntu-latest' }} + runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-release"]')) || 'ubuntu-26.04' }} env: JWT_SECRET: ci-nightly-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-nightly-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false @@ -116,7 +116,7 @@ jobs: git checkout "$TARGET" git log -1 --oneline - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: "24" cache: npm @@ -217,19 +217,19 @@ jobs: # On a push, only run for a push to main — a push to release/* is handled by # release-green above. Schedule/dispatch always run (they also sweep main). if: ${{ github.event_name != 'push' || github.ref_name == 'main' }} - runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-release"]')) || 'ubuntu-latest' }} + runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && fromJSON('["self-hosted","omni-release"]')) || 'ubuntu-26.04' }} env: JWT_SECRET: ci-nightly-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-nightly-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: main # literal — no injection surface; scheduled runs default to the repo default branch (a release/v*), so pin main explicitly fetch-depth: 0 persist-credentials: false - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: "24" cache: npm @@ -331,12 +331,12 @@ jobs: bank-ratchet-shrinks: name: Bank ratchet shrinks if: ${{ github.event_name != 'push' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: contents: write pull-requests: write steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 @@ -371,11 +371,6 @@ jobs: git checkout "$TARGET" git log -1 --oneline - - uses: actions/setup-node@v7 - with: - node-version: "24" - cache: npm - - uses: ./.github/actions/npm-ci-retry - name: Ratchet the baselines down diff --git a/.github/workflows/nightly-resilience.yml b/.github/workflows/nightly-resilience.yml index c98c6df7b1..6961e32f9d 100644 --- a/.github/workflows/nightly-resilience.yml +++ b/.github/workflows/nightly-resilience.yml @@ -10,43 +10,31 @@ permissions: jobs: heap: name: Heap-growth gate - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: "24" - cache: npm - run: npm ci - run: npm run test:heap chaos: name: Resilience chaos (fault injection) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: "24" - cache: npm - run: npm ci - run: npm run test:chaos k6-soak: name: k6 load/soak - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: "24" - cache: npm - run: npm ci - name: Build CLI bundle env: @@ -78,7 +66,7 @@ jobs: a11y: name: A11y axe (nightly, freeze-and-alert) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 # The Playwright webServer (`start` mode) builds Next via build-next-isolated.mjs and # boots the standalone server itself (waits on /api/monitoring/health, 15min webServer # timeout). Unlike the per-PR test-e2e job, this nightly job has no pre-built artifact, @@ -92,13 +80,9 @@ jobs: DISABLE_SQLITE_AUTO_BACKUP: "true" REQUIRE_AXE: "1" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: "24" - cache: npm - run: npm ci - name: Cache Playwright browsers uses: actions/cache@v6.1.0 diff --git a/.github/workflows/nightly-schemathesis.yml b/.github/workflows/nightly-schemathesis.yml index 2bdf91a1c0..97fa0af711 100644 --- a/.github/workflows/nightly-schemathesis.yml +++ b/.github/workflows/nightly-schemathesis.yml @@ -10,14 +10,12 @@ permissions: jobs: schemathesis: name: Schemathesis — OpenAPI contract fuzz (advisory) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 timeout-minutes: 30 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: { node-version: "24", cache: npm } - run: npm ci - name: Build CLI bundle env: diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 16dc215a92..d4df45faef 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -62,7 +62,7 @@ jobs: # mid-"Creating an optimized production build" while v3.8.48 had still fit in 16min. # This job never runs on `pull_request`, so the fork-safety clause is always true here; # it is kept verbatim so the expression stays greppable against ci.yml. - runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }} + runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-26.04' }} permissions: actions: read # find + download the CI run's next-build artifact for this SHA contents: write # gh release upload (attach SBOM to the GitHub Release) @@ -70,7 +70,7 @@ jobs: packages: write # publish to npm.pkg.github.com steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false # Need full tag history to compare against highest semver when @@ -78,7 +78,7 @@ jobs: fetch-depth: 0 - name: Setup Node.js - uses: actions/setup-node@v7 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: ${{ env.NPM_PUBLISH_NODE_VERSION }} registry-url: https://registry.npmjs.org @@ -339,20 +339,20 @@ jobs: echo "✅ Action finished for GitHub Packages" publish-opencode-plugin: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: contents: read id-token: write # npm provenance steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 # Full history needed for auto-bump: git diff against previous release tag - name: Setup Node.js - uses: actions/setup-node@v7 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: ${{ env.NPM_PUBLISH_NODE_VERSION }} registry-url: https://registry.npmjs.org diff --git a/.github/workflows/opencode-plugin-ci.yml b/.github/workflows/opencode-plugin-ci.yml index f1c99fe426..946b8f9d0b 100644 --- a/.github/workflows/opencode-plugin-ci.yml +++ b/.github/workflows/opencode-plugin-ci.yml @@ -26,16 +26,16 @@ defaults: jobs: test: name: Test (Node ${{ matrix.node }}) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: node: ["22", "24"] steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: ${{ matrix.node }} cache: npm @@ -46,13 +46,13 @@ jobs: build: name: Build - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 needs: test steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: "22" cache: npm diff --git a/.github/workflows/opencode-provider-ci.yml b/.github/workflows/opencode-provider-ci.yml index 5fe7d1fe78..b7c4dfc42f 100644 --- a/.github/workflows/opencode-provider-ci.yml +++ b/.github/workflows/opencode-provider-ci.yml @@ -26,16 +26,16 @@ defaults: jobs: test: name: Test (Node ${{ matrix.node }}) - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: node: ["20", "22", "24"] steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: ${{ matrix.node }} cache: npm @@ -45,13 +45,13 @@ jobs: build: name: Build - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 needs: test steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: "20" cache: npm diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 7a9a1d2f87..9999a38897 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -25,20 +25,17 @@ jobs: # path filters share existence reasons: code / docs / i18n / workflow. changes: name: Change Classification - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 outputs: code: ${{ steps.classify.outputs.code }} docs: ${{ steps.classify.outputs.docs }} i18n: ${{ steps.classify.outputs.i18n }} workflow: ${{ steps.classify.outputs.workflow }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - id: classify env: EVENT_NAME: ${{ github.event_name }} @@ -61,19 +58,16 @@ jobs: name: Build (advisory) needs: changes if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true') }} - # Dynamic runner — same fork-safe rule as ci.yml / fast-gates. - runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-latest' }} + # Fork-safe fallback uses Ubuntu 26.04's bundled Node 24 without setup-node. + runs-on: ${{ (vars.USE_VPS_RUNNER == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)) && fromJSON('["self-hosted","omni-release"]') || 'ubuntu-26.04' }} # #7307: advisory for the first week of release-PR runs; remove # continue-on-error after the production-build signal is stable. continue-on-error: true steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm + - run: node -e 'if (process.versions.node.split(".")[0] !== process.env.CI_NODE_VERSION) throw new Error("Expected Node " + process.env.CI_NODE_VERSION)' - uses: ./.github/actions/npm-ci-retry - run: npm run check:node-runtime - run: npm run build @@ -88,15 +82,11 @@ jobs: name: Docs Gates (fast-path) needs: changes if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && (needs.changes.outputs.docs == 'true' || needs.changes.outputs.code == 'true')) }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - run: npm ci # One walk of src/app/api for openapi-routes + docs-symbols (both still fail independently). - run: npm run check:api-docs-refs @@ -111,7 +101,7 @@ jobs: # Dynamic runner (same rule as ci.yml): use the self-hosted VPS pool only when the # release captain has USE_VPS_RUNNER=true AND this is not a fork PR (own-origin # branches only — a fork PR must never execute on the LAN runner). Var unset/false - # or a fork PR falls back to ubuntu-latest, so this is inert until the flag flips. + # or a fork PR falls back to ubuntu-26.04, so this is inert until the flag flips. # PINNED to hosted (gap 19). This job carried the USE_VPS_RUNNER expression, and that # expression was DEAD CONFIGURATION: across 160 quality.yml runs the job never once landed on # a self-hosted runner — every non-skipped sample is `GitHub Actions NNNN`. The classifier is @@ -125,7 +115,7 @@ jobs: # # With this pinned, USE_VPS_RUNNER governs ONLY build-like jobs — one variable, one coherent # purpose. That is what gap 19 asked for; a second variable turned out to be unnecessary. - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 # tsx gates (known-symbols, route-guard-membership) import modules that open # SQLite on load; provide DB env so a fresh CI DB initializes cleanly. env: @@ -133,14 +123,10 @@ jobs: API_KEY_SECRET: ci-lint-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - run: npm ci - name: Restore ESLint file cache uses: actions/cache@v6 @@ -204,7 +190,7 @@ jobs: # selector returns __RUN_ALL__ — full-suite authority is the parallel # `fast-unit` 4-shard job (test:unit:ci:shard; was 2-shard, #6781), NOT an # unsharded re-run here. Stacking unsharded test:unit:ci on top of fast-unit - # doubled wall time (~16 min extra on ubuntu-latest) without extra coverage. + # doubled wall time (~16 min extra on the hosted runner) without extra coverage. # # BLOCKING for the *impacted subset* (flipped 2026-06-17). Fail-safe full # coverage remains required via `Unit Tests fast-path` (fast-unit). @@ -272,26 +258,22 @@ jobs: name: Vitest (fast-path) needs: changes if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true') }} - # Dynamic runner — see fast-gates (own-origin + flag; fork/unset → ubuntu-latest). + # Dynamic runner — see fast-gates (own-origin + flag; fork/unset → ubuntu-26.04). # PINNED to hosted, deliberately not on the USE_VPS_RUNNER switch (gap 19). One variable # governed the build and the test jobs, which want OPPOSITE machines: the build needs the # .113's RAM, the tests need the hosted runner's link. Measured on 2026-07-29 — # actions/setup-node took 20m06s on .113 with 4 concurrent runners versus 16s hosted (npm # cache restore saturating the link), while the tests themselves tied, 2m54 vs 2m31. So # self-hosted is strictly worse here and there is nothing to configure. - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 env: JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-lint-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - run: npm ci # WS5.2/5.3: JUnit feeds Trunk Flaky Tests — the fast-path runs on EVERY PR, # which is where flaky-detection volume actually comes from (ci.yml's heavy @@ -310,9 +292,9 @@ jobs: name: Unit Tests fast-path (${{ matrix.shard }}/4) needs: changes if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true') }} - # Dynamic runner — see fast-gates (own-origin + flag; fork/unset → ubuntu-latest). + # Dynamic runner — see fast-gates (own-origin + flag; fork/unset → ubuntu-26.04). # This is the heaviest fast-path job; 4-way sharding (was 2, #6781) halves the - # critical path again (~8.5min → ~4.5min on ubuntu-latest; ~2min on the 8-slot + # critical path again (~8.5min → ~4.5min hosted; ~2min on the 8-slot # runner box). Node's native --test-shard=N/total takes any denominator — only # this matrix and the TEST_SHARD env below encode the shard count. # PINNED to hosted, deliberately not on the USE_VPS_RUNNER switch (gap 19). One variable @@ -321,7 +303,7 @@ jobs: # actions/setup-node took 20m06s on .113 with 4 concurrent runners versus 16s hosted (npm # cache restore saturating the link), while the tests themselves tied, 2m54 vs 2m31. So # self-hosted is strictly worse here and there is nothing to configure. - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: @@ -331,13 +313,9 @@ jobs: API_KEY_SECRET: ci-lint-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - run: npm ci # QW-d: fonte única — o mesmo npm script do CI pesado/local. Fecha dois drifts do # comando inline antigo: os dirs `memory` e `usage` estavam FORA do glob (testes @@ -362,16 +340,12 @@ jobs: name: No new ESLint warnings needs: changes if: ${{ github.event_name != 'pull_request' || ((github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) && needs.changes.outputs.code == 'true') }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 continue-on-error: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: actions/setup-node@v7 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - run: npm ci - name: Restore ESLint file cache uses: actions/cache@v6 @@ -401,21 +375,17 @@ jobs: name: Merge integrity (changelog + generated skills) # Always on non-draft PRs — CHANGELOG/skills can break on docs-only merges too. if: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false || startsWith(github.head_ref, 'mergify/merge-queue/')) }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 continue-on-error: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }} env: JWT_SECRET: ci-lint-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-lint-api-key-secret-long DISABLE_SQLITE_AUTO_BACKUP: "true" steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 persist-credentials: false - - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6 - with: - node-version: ${{ env.CI_NODE_VERSION }} - cache: npm - run: npm ci - name: CHANGELOG integrity (nenhum bullet da base pode sumir no merge-result) run: npm run check:changelog-integrity diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a0b371bd9b..0d040392fb 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -11,7 +11,7 @@ permissions: read-all jobs: analysis: name: Scorecard analysis - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 permissions: # security-events: write removed — Scorecard findings are advisory and no longer # uploaded to the code-scanning Security tab (they are supply-chain/posture scores, @@ -21,7 +21,7 @@ jobs: contents: read steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index 82ac6151ad..cd2be45b1c 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -14,7 +14,7 @@ concurrency: cancel-in-progress: true jobs: semgrep: - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 container: image: semgrep/semgrep steps: diff --git a/.github/workflows/wiki-sync.yml b/.github/workflows/wiki-sync.yml index 9ef2cdee2d..07ee7efdf0 100644 --- a/.github/workflows/wiki-sync.yml +++ b/.github/workflows/wiki-sync.yml @@ -34,15 +34,10 @@ concurrency: jobs: sync-wiki: name: Sync wiki with docs - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - name: Checkout repo - uses: actions/checkout@v7 - - - name: Setup Node - uses: actions/setup-node@v7 - with: - node-version: "24" + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Clone wiki env: diff --git a/scripts/build/build-next-isolated.mjs b/scripts/build/build-next-isolated.mjs index 8d2e0da139..80e0364327 100644 --- a/scripts/build/build-next-isolated.mjs +++ b/scripts/build/build-next-isolated.mjs @@ -124,7 +124,7 @@ function runNextBuild() { export function resolveNextBuildBundlerFlag(baseEnv = process.env) { // Turbopack is the default production bundler (Next 16 stable). Benchmarked on // this codebase: 2-3x faster than the single-threaded webpack pass (17min -> 9min - // on a 32-core box; ~20min -> 7min on ubuntu-latest), artifact validated + // on a 32-core box; ~20min -> 7min on the GitHub-hosted runner), artifact validated // end-to-end (standalone smoke + e2e/package/electron CI jobs). Webpack stays as // the explicit escape hatch (=0) for bundler-compat regressions. return baseEnv.OMNIROUTE_USE_TURBOPACK === "0" ? "--webpack" : "--turbopack"; diff --git a/tests/unit/build/check-workflows.test.ts b/tests/unit/build/check-workflows.test.ts index 40eb377fd3..f249b9030b 100644 --- a/tests/unit/build/check-workflows.test.ts +++ b/tests/unit/build/check-workflows.test.ts @@ -333,10 +333,11 @@ test("#7307 quality.yml adds an advisory production build for release PR code ch buildJob[0], /github\.event\.pull_request\.head\.repo\.full_name == github\.repository/ ); - assert.match(buildJob[0], /fromJSON\('\["self-hosted","omni-release"\]'\) \|\| 'ubuntu-latest'/); + assert.match(buildJob[0], /fromJSON\('\["self-hosted","omni-release"\]'\) \|\| 'ubuntu-26\.04'/); assert.match(buildJob[0], /continue-on-error: true/); - assert.match(buildJob[0], /uses: actions\/checkout@[0-9a-f]{40} # v7/); - assert.match(buildJob[0], /uses: actions\/setup-node@[0-9a-f]{40} # v7/); + assert.match(buildJob[0], /uses: actions\/checkout@[0-9a-f]{40} # v7\.0\.1/); + assert.doesNotMatch(buildJob[0], /uses: actions\/setup-node@/); + assert.match(buildJob[0], /process\.env\.CI_NODE_VERSION/); assert.match(buildJob[0], /uses: \.\/\.github\/actions\/npm-ci-retry/); assert.match(buildJob[0], /npm run check:node-runtime/); assert.match(buildJob[0], /npm run build/); diff --git a/tests/unit/nightly-compat-node26-webpack-8090.test.ts b/tests/unit/nightly-compat-node26-webpack-8090.test.ts index 5ae7d9caa0..8bf4388809 100644 --- a/tests/unit/nightly-compat-node26-webpack-8090.test.ts +++ b/tests/unit/nightly-compat-node26-webpack-8090.test.ts @@ -7,7 +7,7 @@ * nightly it failed with the runner-reclaimed signature — "The runner has received * a shutdown signal" / "The operation was canceled", no exit code, always at the * same Turbopack compile phase — the classic OOM-kill pattern on the - * memory-constrained ubuntu-latest runner. + * memory-constrained 16 GB hosted runner. * * Turbopack's native (Rust, off-V8-heap) allocation is NOT bounded by * --max-old-space-size and peaks far higher than webpack on OmniRoute's large module @@ -44,10 +44,7 @@ function extractJobBlock(yaml: string, jobName: string): string { } test("#8090 compat-build-26 uses the webpack fallback (OMNIROUTE_USE_TURBOPACK=0) to avoid Node 26 OOM", () => { - const wf = fs.readFileSync( - path.join(repoRoot, ".github/workflows/nightly-compat.yml"), - "utf-8" - ); + const wf = fs.readFileSync(path.join(repoRoot, ".github/workflows/nightly-compat.yml"), "utf-8"); const jobBlock = extractJobBlock(wf, "compat-build-26"); assert.match( diff --git a/tests/unit/vps-runner-variable-scope.test.ts b/tests/unit/vps-runner-variable-scope.test.ts index 9d5fd60c89..4bf0ec0c70 100644 --- a/tests/unit/vps-runner-variable-scope.test.ts +++ b/tests/unit/vps-runner-variable-scope.test.ts @@ -58,7 +58,7 @@ test("USE_VPS_RUNNER never governs a test-like job", () => { offenders.map((j) => `${j.file}:${j.job}`), [], "self-hosted is strictly worse for these — setup-node measured 20m06s there vs 16s hosted, " + - "while the tests themselves tie. Pin the job to ubuntu-latest instead of switching it." + "while the tests themselves tie. Pin the job to ubuntu-26.04 instead of switching it." ); }); @@ -68,7 +68,10 @@ test("it still governs the build-like jobs — the ones that actually need the R jobs.includes("build"), "the build is the whole reason the variable exists: 18-20 GB working set vs a 16 GB hosted runner" ); - assert.ok(jobs.includes("publish"), "npm-publish falls back to a full build when the artifact is missing"); + assert.ok( + jobs.includes("publish"), + "npm-publish falls back to a full build when the artifact is missing" + ); }); test("fast-gates specifically stays hosted", () => { @@ -78,5 +81,5 @@ test("fast-gates specifically stays hosted", () => { const block = wf.split(/^ {2}fast-gates:\s*$/m)[1] ?? ""; const runsOn = /^ {4}runs-on:\s*(.+)$/m.exec(block); assert.ok(runsOn, "fast-gates must declare runs-on"); - assert.match(runsOn[1].trim(), /^ubuntu-latest$/, "fast-gates must be pinned, not switched"); + assert.match(runsOn[1].trim(), /^ubuntu-26\.04$/, "fast-gates must be pinned, not switched"); });