name: CI master on: push: branches: [master] workflow_dispatch: inputs: suite: description: Manual CI suite to run required: true default: larger-runner-benchmark type: choice options: - larger-runner-benchmark - consolidated-runner-benchmark # A master push may carry only the two self-hosted standby drills and the Wine # apt cache seeder; those drills outlast the interval between master merges, so # push is exempt from cancellation (see ci-failover-runbook). workflow_dispatch # keeps cancelling: a re-dispatched runner benchmark holds up to 12 larger # runners for 15 minutes in this same group. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name != 'push' }} permissions: contents: read env: PRIMARY_NODE_VERSION: '24' # CI runs must never report to the production telemetry endpoint baked # into apps/cli/cordis.yml (AppCLIEntry disables the row when set). DSH_TELEMETRY_DISABLED: '1' jobs: # 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" serial-linux-selfhosted: if: github.event_name == 'push' && github.ref == 'refs/heads/master' name: serial / linux (self-hosted standby) runs-on: [self-hosted, linux, x64, vm-backup] steps: # DSH_ARCHIVE_BASE_REF below compares the frozen-archive gate against # github.event.before, so full history is required: depth 2 would miss it # on multi-commit or force pushes; full fetch is cheap here because # checkout resolves against the VM's local mirror. - uses: actions/checkout@v6 with: fetch-depth: 0 - 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 persistent pnpm store run: echo "PNPM_CONFIG_STORE_DIR=$HOME/.local/share/pnpm/store" >> "$GITHUB_ENV" - name: Install (immutable) run: pnpm install --frozen-lockfile # The persistent VM image owns Playwright's Linux system packages; this # step also proves that browser provisioning remains usable for failover. - name: Install Playwright Chromium run: pnpm --filter @deepseek-ai/dsh-web-frontend exec playwright install chromium - name: Prepare bubblewrap (unrestrict userns) run: bash scripts/prepare-ci-bubblewrap.sh - name: Run complete unsharded primary Node CI serially env: DSH_ARCHIVE_BASE_REF: ${{ github.event.before }} 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:linux-primary # TODO(hosted-serial-ci): Re-enable the one remaining disabled hosted serial # reference job (serial-macos) before release. Re-enabling serial-macos does # not restore a Linux hosted-cache producer: decide whether to add a master # seeder or remove the restore-only steps if cold starts become a concern. serial-macos: if: false name: serial / macos runs-on: macos-latest steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 with: dest: ${{ runner.temp }}/setup-pnpm - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} - name: Install (immutable) run: pnpm install --frozen-lockfile - name: Run complete unsharded primary Node CI serially env: DSH_COVERAGE_MAX_WORKERS: '1' DSH_E2E_MAX_WORKERS: '1' DSH_GATE_CONCURRENCY: '1' DSH_PUBLINT_CONCURRENCY: '1' 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_WINDOWS variable — see the failover # runbook, no merge required). Push-triggered, so this lane always executes # the base branch's own workflow definition. This workflow never listens to # pull_request, so the drill does not appear in PR checks. 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: 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: 120 steps: - uses: actions/checkout@v6 - name: Enable Developer Mode (symlink support) shell: pwsh run: >- reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" - 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 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 Windows gate inventory serially shell: pwsh env: DSH_COVERAGE_MAX_WORKERS: '1' DSH_GATE_CONCURRENCY: '1' DSH_PUBLINT_CONCURRENCY: '1' 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. larger-runner-benchmark: if: github.event_name == 'workflow_dispatch' && inputs.suite == 'larger-runner-benchmark' runs-on: ${{ matrix.runner }} timeout-minutes: 15 strategy: fail-fast: false max-parallel: 12 matrix: include: - platform: linux cores: '4' runner: dsh-ubuntu-24-04-4core workload: typecheck - platform: linux cores: '8' runner: dsh-ubuntu-24-04-8core workload: typecheck - platform: linux cores: '16' runner: dsh-ubuntu-24-04-16core workload: typecheck - platform: linux cores: '32' runner: dsh-ubuntu-24-04-32core workload: typecheck - platform: linux cores: '64' runner: dsh-ubuntu-24-04-64core workload: typecheck - platform: linux cores: '96' runner: dsh-ubuntu-24-04-96core workload: typecheck - platform: windows cores: '4' runner: dsh-windows-2025-4core workload: production-site - platform: windows cores: '8' runner: dsh-windows-2025-8core workload: production-site - platform: windows cores: '16' runner: dsh-windows-2025-16core workload: production-site - platform: windows cores: '32' runner: dsh-windows-2025-32core workload: production-site - platform: windows cores: '64' runner: dsh-windows-2025-64core workload: production-site - platform: windows cores: '96' runner: dsh-windows-2025-96core workload: production-site steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 with: dest: ${{ runner.temp }}/setup-pnpm # The benchmark's Windows lanes deliberately skip the store cache like # the independent native Windows job; an empty input disables caching. - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} cache: ${{ matrix.platform == 'linux' && 'pnpm' || '' }} - name: Report runner capacity run: >- node -e "const os = require('node:os'); console.log(JSON.stringify({ arch: process.arch, cpus: os.cpus().length, memoryGiB: Math.round(os.totalmem() / 2 ** 30) }))" - name: Install (immutable) run: pnpm install --frozen-lockfile - name: Run critical Linux typecheck lane if: matrix.platform == 'linux' run: pnpm run typecheck - name: Run critical Windows production-site lane if: matrix.platform == 'windows' run: pnpm run docs:build # Manual comparison of the intended low-fanout topology. Linux runs the # complete unsharded primary aggregate with bounded in-runner parallelism; # Windows runs both blocking build targets concurrently through run-gates. consolidated-runner-benchmark: if: github.event_name == 'workflow_dispatch' && inputs.suite == 'consolidated-runner-benchmark' runs-on: ${{ matrix.runner }} timeout-minutes: 15 strategy: fail-fast: false max-parallel: 12 matrix: include: - platform: linux cores: '4' runner: dsh-ubuntu-24-04-4core workers: '4' - platform: linux cores: '8' runner: dsh-ubuntu-24-04-8core workers: '8' - platform: linux cores: '16' runner: dsh-ubuntu-24-04-16core workers: '16' - platform: linux cores: '32' runner: dsh-ubuntu-24-04-32core workers: '32' - platform: linux cores: '64' runner: dsh-ubuntu-24-04-64core workers: '32' - platform: linux cores: '96' runner: dsh-ubuntu-24-04-96core workers: '32' - platform: windows cores: '4' runner: dsh-windows-2025-4core workers: '2' - platform: windows cores: '8' runner: dsh-windows-2025-8core workers: '2' - platform: windows cores: '16' runner: dsh-windows-2025-16core workers: '2' - platform: windows cores: '32' runner: dsh-windows-2025-32core workers: '2' - platform: windows cores: '64' runner: dsh-windows-2025-64core workers: '2' - platform: windows cores: '96' runner: dsh-windows-2025-96core workers: '2' steps: - uses: actions/checkout@v6 - uses: pnpm/action-setup@v4 with: dest: ${{ runner.temp }}/setup-pnpm # Unlike the larger-runner suite, both platforms cache the store here: # the consolidated topology measures cache mechanics as workload. - uses: actions/setup-node@v6 with: node-version: ${{ env.PRIMARY_NODE_VERSION }} cache: pnpm - name: Report runner capacity run: >- node -e "const os = require('node:os'); console.log(JSON.stringify({ arch: process.arch, cpus: os.cpus().length, memoryGiB: Math.round(os.totalmem() / 2 ** 30) }))" - name: Install and prepare Linux if: matrix.platform == 'linux' run: | pnpm install --frozen-lockfile & install_pid=$! bash scripts/prepare-ci-bubblewrap.sh & sandbox_pid=$! install_status=0 wait "$install_pid" || install_status=$? sandbox_status=0 wait "$sandbox_pid" || sandbox_status=$? if (( install_status != 0 )); then exit "$install_status"; fi exit "$sandbox_status" - name: Install (immutable) if: matrix.platform == 'windows' shell: pwsh run: pnpm install --frozen-lockfile - name: Run complete unsharded primary Node CI concurrently if: matrix.platform == 'linux' env: DSH_COVERAGE_MAX_WORKERS: ${{ matrix.workers }} DSH_GATE_CONCURRENCY: ${{ matrix.workers }} DSH_OXLINT_THREADS: ${{ matrix.workers }} DSH_PUBLINT_CONCURRENCY: ${{ matrix.workers }} DSH_SNAPSHOT_MAX_CONCURRENCY: ${{ matrix.workers }} run: pnpm run check:ci - name: Run blocking Windows builds concurrently if: matrix.platform == 'windows' shell: pwsh env: DSH_GATE_CONCURRENCY: ${{ matrix.workers }} run: pnpm run check:ci:windows-blocking