From 5d8d79ce929dec262af124a819e68b9e99a6466a Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Mon, 10 Aug 2026 22:30:46 +0800 Subject: [PATCH] ci: unify Windows CI on native self-hosted runners Replace the Wine-emulated windows job and the independent windows-native job with a single required native Windows job that runs check:ci:windows-complete on the hosted larger runner, falling back to the self-hosted [self-hosted, dsh-win-ci, windows] pool under failover (DSH_CI_FAILOVER=selfhosted). The serial-windows standby mirrors serial-linux-selfhosted: master-only, unsharded, self-hosted, absent from all-checks-passed. Remove wine-apt-cache, the check:windows-wine script reference, and the dual-lane topology the two Windows notes described. --- .github/AGENTS.md | 2 +- .github/workflows/ci.yml | 177 ++++++++++----------------------------- package.json | 1 - 3 files changed, 46 insertions(+), 134 deletions(-) diff --git a/.github/AGENTS.md b/.github/AGENTS.md index c18f5b5948..b2721fb84e 100644 --- a/.github/AGENTS.md +++ b/.github/AGENTS.md @@ -1,3 +1,3 @@ # AGENTS.md — GitHub Actions -Run jobs on Windows runners (`windows-*` labels) under native `pwsh`. The pull-request `windows` job is the deliberate exception: it runs Windows Node under Wine on hosted Linux and blocks `all checks passed`; `windows-native` runs automatically on `windows-2025` but reports independently — see the [dual-lane Agent Note](../.agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md). +Run jobs on Windows runners (`windows-*` labels) under native `pwsh`. The required pull-request `windows` job and the master `serial-windows` standby both run on real Windows; the self-hosted pool uses labels `[self-hosted, dsh-win-ci, windows]`. Under failover (`DSH_CI_FAILOVER=selfhosted`) the `windows` job retargets onto the self-hosted pool. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26a3d5be38..8a0482e103 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -313,123 +313,18 @@ jobs: - name: Run complete keyless Python suite run: uv run --python 3.10 --group test --project python/sdk pytest - # The required pull-request Windows signal: the two blocking win32 surfaces - # (workspace build, production site) execute with real, checksum-verified - # Windows Node under Wine on standard hosted Linux. The independent - # windows-native job below keeps the complete native-kernel inventory — - # including the observational portability gates this lane does not run — - # on real windows-2025. This job only provisions runner state (caches, - # apt); scripts/wine-windows-gates.sh owns the gate logic and is the same - # script the optional local gate `pnpm run check:windows-wine` runs. - # Current topology and fidelity limits live in - # .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md + # Required pull-request Windows signal on real Windows. Under normal + # operation this runs on the hosted larger runner; under failover + # (DSH_CI_FAILOVER=selfhosted) it retargets onto the in-house self-hosted + # Windows pool. Dependabot PRs are excluded from the self-hosted pool and + # stay queued for the hosted runner — see the failover runbook. windows: if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - name: windows node 24 / wine blocking - timeout-minutes: 15 - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - - - uses: pnpm/action-setup@v4 - with: - dest: ${{ runner.temp }}/setup-pnpm - - - uses: actions/setup-node@v6 - with: - node-version: ${{ env.PRIMARY_NODE_VERSION }} - - - name: Configure pnpm store path - id: pnpm-store - run: | - store_root="$HOME/.local/share/pnpm/store" - echo "PNPM_CONFIG_STORE_DIR=$store_root" >> "$GITHUB_ENV" - store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent) - echo "path=$store_path" >> "$GITHUB_OUTPUT" - - - uses: actions/cache/restore@v4 - with: - path: ${{ steps.pnpm-store.outputs.path }} - key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm- - - # Master's wine-apt-cache job seeds the default-branch scope every pull - # request can read; a save from this job only reaches reruns of the - # same merge ref. - - name: Compose Wine apt cache key - id: wine-cache-key - run: echo "key=wine-debs-${ImageOS:-linux}-${ImageVersion:-v0}" >> "$GITHUB_OUTPUT" - - - uses: actions/cache@v4 - with: - path: ~/wine-debs - key: ${{ steps.wine-cache-key.outputs.key }} - - # Runner provisioning only — a developer machine installs Wine through - # its own package manager; the gate script assumes a wine binary and - # fails loud without one. Wine from the apt cache when present; else - # download the full dependency closure once and keep it for the next - # run. The `wine` dispatcher package (not bare `wine64`) is what puts a - # binary on PATH. - - name: Install Wine - run: | - if compgen -G "$HOME/wine-debs/*.deb" > /dev/null; then - sudo apt-get install -y --no-install-recommends "$HOME"/wine-debs/*.deb - else - sudo apt-get update - sudo apt-get install -y --no-install-recommends --download-only wine - mkdir -p "$HOME/wine-debs" - cp /var/cache/apt/archives/*.deb "$HOME/wine-debs/" 2>/dev/null || true - sudo apt-get install -y --no-install-recommends wine - fi - - - name: Run the Wine Windows gates - run: bash scripts/wine-windows-gates.sh - - - name: Shut down wineserver - if: always() - run: wineserver -k 2>/dev/null || true - - # Master seeds the Wine apt-archive cache in the default-branch scope, - # which every pull request's windows job can restore; saves from - # pull-request runs are scoped to their own merge ref and help nobody - # else. Runs in seconds when the image version already has a cache. - wine-apt-cache: - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - name: wine apt cache - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Compose Wine apt cache key - id: wine-cache-key - run: echo "key=wine-debs-${ImageOS:-linux}-${ImageVersion:-v0}" >> "$GITHUB_OUTPUT" - - - uses: actions/cache@v4 - id: wine-cache - with: - path: ~/wine-debs - key: ${{ steps.wine-cache-key.outputs.key }} - - - name: Download the Wine dependency closure - if: steps.wine-cache.outputs.cache-hit != 'true' - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends --download-only wine - mkdir -p "$HOME/wine-debs" - cp /var/cache/apt/archives/*.deb "$HOME/wine-debs/" - du -sh "$HOME/wine-debs" - - # Every pull request also gets a real Windows-kernel signal. This job keeps - # its own unmasked conclusion but is deliberately absent from - # all-checks-passed.needs, so it never delays or changes that required verdict. - # See the dual Wine/native pull-request CI decision: - # .agents/notes/implemented/process/2026-08-08-native-windows-pull-request-ci.md - windows-native: - if: github.event_name == 'pull_request' - runs-on: dsh-windows-2025-16core + runs-on: >- + ${{ vars.DSH_CI_FAILOVER == 'selfhosted' + && github.event.pull_request.user.login != 'dependabot[bot]' + && fromJSON('["self-hosted", "dsh-win-ci", "windows"]') + || 'dsh-windows-2025-16core' }} name: windows node 24 / native complete timeout-minutes: 60 env: @@ -455,8 +350,10 @@ jobs: with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - # Extracting the many-file pnpm store cache is slower than a clean install, - # and saving it adds more latency after the gates. + # Extracting the many-file pnpm store cache is slower than a clean + # install on hosted Windows runners, and saving it adds latency after + # the gates. The self-hosted VM's persistent store makes caching + # redundant. - name: Install (immutable) shell: pwsh run: pnpm install --frozen-lockfile @@ -621,14 +518,22 @@ jobs: DSH_SNAPSHOT_MAX_CONCURRENCY: '1' run: pnpm run check:ci + # Hot-standby drill for the in-house self-hosted Windows pool: every master + # move re-runs the complete unsharded Windows gate inventory on the persistent + # VM, continuously proving that environment can take over the required + # `windows` lane if the hosted pool degrades (the switch is setting the + # writer-manageable DSH_CI_FAILOVER variable — see the failover runbook, no + # merge required). Push-triggered, so this lane always executes the base + # branch's own workflow definition. Non-blocking for pull requests; absent + # from all-checks-passed.needs by design — the required `windows` job owns + # the PR verdict. No cache steps because the VM's persistent pnpm store + # and tool caches make them redundant (and saving here would poison the + # hosted cache namespace with self-hosted paths). serial-windows: - # Real-Windows signal on the in-house self-hosted Windows pool. This lane - # runs the complete primary Node CI on actual Windows (in contrast to the - # Wine-emulated `windows` job), so win32-native output is exercised where - # it matters. Selector matches our self-hosted runners by label. - if: github.event_name == 'pull_request' - name: serial / windows + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + name: serial / windows (self-hosted standby) runs-on: [self-hosted, dsh-win-ci, windows] + timeout-minutes: 60 steps: - uses: actions/checkout@v6 @@ -646,20 +551,30 @@ jobs: with: node-version: ${{ env.PRIMARY_NODE_VERSION }} + - name: Configure persistent pnpm store + shell: pwsh + run: | + $storeRoot = "$env:LOCALAPPDATA\pnpm\store" + echo "PNPM_CONFIG_STORE_DIR=$storeRoot" >> $env:GITHUB_ENV + - name: Install (immutable) shell: pwsh run: pnpm install --frozen-lockfile - - name: Run complete unsharded primary Node CI serially + # The persistent VM image owns Playwright's Windows system dependencies; + # this step also proves that browser provisioning remains usable for + # failover. + - name: Install Playwright Chromium + shell: pwsh + run: pnpm --filter @deepseek-ai/dsh-frontend exec playwright install chromium + + - name: Run complete unsharded Windows gate inventory serially shell: pwsh env: DSH_COVERAGE_MAX_WORKERS: '1' - DSH_E2E_MAX_WORKERS: '1' DSH_GATE_CONCURRENCY: '1' - DSH_OXLINT_THREADS: '1' DSH_PUBLINT_CONCURRENCY: '1' - DSH_SNAPSHOT_MAX_CONCURRENCY: '1' - run: pnpm run check:ci + run: pnpm run check:ci:windows-complete # Manual, bounded comparison of the actual critical Linux and Windows lanes. # The named pools are restricted at the organization level to this repository. @@ -870,9 +785,7 @@ jobs: # Single stable required check for branch protection: require "all checks # passed" instead of enumerating matrix legs whose names change as lanes and # node versions evolve. Every blocking job in THIS workflow must be listed in - # `needs`. The required Wine job is listed as `windows`; `windows-native` is - # deliberately absent so its independent result never delays or changes this - # verdict. (`needs` cannot reach across workflow files; e2e.yml stays its own + # `needs`. (`needs` cannot reach across workflow files; e2e.yml stays its own # check.) # `if: always()` is load-bearing: without it a failed dependency # would SKIP this job, and GitHub counts a skipped required check as passing @@ -889,7 +802,7 @@ jobs: && github.event.pull_request.user.login != 'dependabot[bot]' && fromJSON('["self-hosted", "linux", "x64", "vm-backup"]') || 'ubuntu-latest' }} - needs: [node-24, node-24-coverage, node-24-consumers, node-compat, python-sdk, windows, serial-windows] + needs: [node-24, node-24-coverage, node-24-consumers, node-compat, python-sdk, windows] if: always() && github.event_name == 'pull_request' steps: - name: Fail if any needed job did not succeed diff --git a/package.json b/package.json index d417354739..9c4d98f0c3 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "check:ci:windows-blocking": "tsx scripts/run-gates.ts ci-windows-blocking", "check:ci:windows-complete": "tsx scripts/run-gates.ts ci-windows-complete", "check:ci:windows-observational": "tsx scripts/run-gates.ts ci-windows-observational", - "check:windows-wine": "bash scripts/wine-windows-gates.sh", "check:node-compat": "tsx scripts/run-gates.ts node-compat", "knip": "knip --treat-config-hints-as-errors", "publint": "tsx scripts/publint-all.ts",