mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
cancel-in-progress is evaluated against the newly triggered run, so exempting push means one master merge does not cancel the drill still running from the previous one — not that a drill always finishes. A benchmark dispatched on master shares the group and does cancel a mid-flight drill. Record that bound in the runbook and drop the overstated wording from the workflow comment and the spec name. Also repair two gates that fail on master and block every pull request: the telemetry note referenced an SDK proposal deleted in 408721954a, and the ui-settings-general README pair carried stale recorded hashes after both sides were updated together in aa1ec02bc6.
932 lines
36 KiB
YAML
932 lines
36 KiB
YAML
name: CI
|
||
|
||
on:
|
||
push:
|
||
branches: [master]
|
||
pull_request:
|
||
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
|
||
|
||
# Cancel a superseded run on every event EXCEPT push. A push run carries the two
|
||
# self-hosted standby drills, which take longer than the interval between master
|
||
# merges, so cancelling supersedes a drill before it reaches a verdict and the
|
||
# lane yields no readiness evidence. Must be decided here: cancellation applies
|
||
# to the whole superseded run, so a job-level group cannot exempt its job.
|
||
# Negated rather than `== 'pull_request'` so workflow_dispatch keeps cancelling:
|
||
# a re-dispatched runner benchmark holds up to 12 larger runners for 15 minutes
|
||
# and shares this group with the drills on master, so queueing it would delay
|
||
# them. The guarantee is narrow — evaluated against the newly triggered run, so a
|
||
# dispatch on master still cancels a mid-flight drill, and a newer pending push
|
||
# displaces an older one. Bounds and rationale:
|
||
# .agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md
|
||
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:
|
||
|
||
# TODO(hosted-serial-ci): Re-enable the three hosted serial reference jobs before release.
|
||
# The self-hosted standby remains active on every master push.
|
||
|
||
# Three enterprise jobs isolate coverage, static analysis, and the
|
||
# build-backed consumer tail. The consumer job owns the only Linux build so
|
||
# all three jobs enter runner allocation independently.
|
||
#
|
||
# FAILOVER: each Linux enterprise job resolves its pool through the
|
||
# DSH_CI_FAILOVER repository variable. Unset (normal), the expressions
|
||
# pick the hosted enterprise pools below. Setting the variable to
|
||
# 'selfhosted' (repo Settings → Actions → Variables; writer-manageable
|
||
# repository state — not PR-editable, no merge required) retargets all
|
||
# three onto the in-house
|
||
# vm-backup pool and re-running the failed jobs is the entire switch —
|
||
# see .agents/notes/implemented/process/2026-07-26-ci-failover-runbook.md. The
|
||
# in-house pool's readiness is re-proven on every master push by the
|
||
# serial-linux-selfhosted standby lane below.
|
||
node-24:
|
||
if: github.event_name == 'pull_request'
|
||
runs-on: >-
|
||
${{ vars.DSH_CI_FAILOVER == 'selfhosted'
|
||
&& github.event.pull_request.user.login != 'dependabot[bot]'
|
||
&& fromJSON('["self-hosted", "linux", "x64", "vm-backup"]')
|
||
|| 'dsh-ubuntu-24-04-16core' }}
|
||
name: node 24 / static
|
||
env:
|
||
DSH_GATE_CONCURRENCY: '8'
|
||
steps:
|
||
# Fetch complete history so the archive gate can read the trusted PR base from a reused shallow checkout.
|
||
- uses: actions/checkout@v6
|
||
with:
|
||
fetch-depth: 0
|
||
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"
|
||
|
||
# Pull requests consume the default-branch cache but do not put cache
|
||
# compression and upload on the paid latency-critical path. Skipped
|
||
# under failover — see the coverage lane's identical rationale.
|
||
- uses: actions/cache/restore@v4
|
||
if: vars.DSH_CI_FAILOVER != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]'
|
||
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-
|
||
|
||
- name: Install (immutable)
|
||
run: pnpm install --frozen-lockfile
|
||
|
||
- name: Run static gates
|
||
env:
|
||
DSH_ARCHIVE_BASE_REF: ${{ github.event.pull_request.base.sha }}
|
||
run: pnpm run check:ci:static
|
||
|
||
node-24-coverage:
|
||
if: github.event_name == 'pull_request'
|
||
runs-on: >-
|
||
${{ vars.DSH_CI_FAILOVER == 'selfhosted'
|
||
&& github.event.pull_request.user.login != 'dependabot[bot]'
|
||
&& fromJSON('["self-hosted", "linux", "x64", "vm-backup"]')
|
||
|| 'dsh-ubuntu-24-04-16core' }}
|
||
name: node 24 / coverage
|
||
env:
|
||
# The hosted 16-core runner uses six coverage workers. The failover pool
|
||
# shares one 64-core VM across six always-on runner instances, so each
|
||
# instance may use eight while keeping the worst case at 8 × 6 = 48
|
||
# workers; process-bound suites remain isolated in forks.
|
||
DSH_COVERAGE_MAX_WORKERS: ${{ vars.DSH_CI_FAILOVER == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '8' || '6' }}
|
||
DSH_GATE_CONCURRENCY: '3'
|
||
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"
|
||
|
||
# Skipped under failover: the self-hosted VM's persistent pnpm store
|
||
# already serves warm installs, while restoring the hosted archive
|
||
# would spend ~52 s pulling ~180 MB into that populated store.
|
||
- uses: actions/cache/restore@v4
|
||
if: vars.DSH_CI_FAILOVER != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]'
|
||
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-
|
||
|
||
- name: Install dependencies and prepare bubblewrap
|
||
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: Run exhaustive coverage
|
||
run: pnpm run check:ci:coverage
|
||
|
||
node-24-consumers:
|
||
if: github.event_name == 'pull_request'
|
||
runs-on: >-
|
||
${{ vars.DSH_CI_FAILOVER == 'selfhosted'
|
||
&& github.event.pull_request.user.login != 'dependabot[bot]'
|
||
&& fromJSON('["self-hosted", "linux", "x64", "vm-backup"]')
|
||
|| 'dsh-ubuntu-24-04-16core' }}
|
||
name: node 24 / snapshots and artifacts
|
||
env:
|
||
DSH_GATE_CONCURRENCY: '8'
|
||
DSH_NODE_COMPAT_SKIP_TYPECHECK: '1'
|
||
DSH_OXLINT_THREADS: '8'
|
||
DSH_PUBLINT_CONCURRENCY: '8'
|
||
# Failover halves snapshot concurrency for the shared 64-core VM.
|
||
DSH_SNAPSHOT_MAX_CONCURRENCY: ${{ vars.DSH_CI_FAILOVER == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '12' || '32' }}
|
||
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"
|
||
|
||
# Skipped under failover — see the coverage lane's identical rationale.
|
||
- uses: actions/cache/restore@v4
|
||
if: vars.DSH_CI_FAILOVER != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]'
|
||
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-
|
||
|
||
# Pull requests restore the cache normally produced by serial-linux on
|
||
# master; they do not pay compression and upload on the required path.
|
||
- uses: actions/cache/restore@v4
|
||
if: vars.DSH_CI_FAILOVER != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]'
|
||
with:
|
||
path: ~/.cache/ms-playwright
|
||
key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-playwright-
|
||
|
||
- name: Install dependencies and prepare bubblewrap
|
||
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 Playwright Chromium and hosted dependencies
|
||
if: vars.DSH_CI_FAILOVER != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]'
|
||
run: pnpm --filter @deepseek-ai/dsh-frontend exec playwright install --with-deps chromium
|
||
|
||
# The persistent VM image owns Playwright's Linux system packages; do
|
||
# not mutate the shared host with apt on every failover run.
|
||
- name: Install Playwright Chromium on the failover VM
|
||
if: vars.DSH_CI_FAILOVER == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]'
|
||
run: pnpm --filter @deepseek-ai/dsh-frontend exec playwright install chromium
|
||
|
||
- name: Run compatibility, snapshot, and artifact gates
|
||
run: pnpm run check:ci:consumers
|
||
|
||
|
||
node-compat:
|
||
if: github.event_name == 'pull_request'
|
||
# Each compatibility contract receives an independent standard hosted job.
|
||
runs-on: ${{ matrix.runner }}
|
||
name: ${{ matrix.name }}
|
||
env:
|
||
DSH_GATE_CONCURRENCY: ${{ matrix.gate_concurrency }}
|
||
DSH_NODE_COMPAT_SKIP_TYPECHECK: '1'
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- node: '22.19'
|
||
name: node 22.19
|
||
runner: ubuntu-latest
|
||
gate_concurrency: '1'
|
||
- node: 26
|
||
name: node 26
|
||
runner: ubuntu-latest
|
||
gate_concurrency: '1'
|
||
steps:
|
||
- uses: actions/checkout@v6
|
||
|
||
- uses: pnpm/action-setup@v4
|
||
with:
|
||
dest: ${{ runner.temp }}/setup-pnpm
|
||
|
||
- uses: actions/setup-node@v6
|
||
with:
|
||
node-version: ${{ matrix.node }}
|
||
cache: pnpm
|
||
|
||
- name: Install (immutable)
|
||
run: pnpm install --frozen-lockfile
|
||
|
||
- name: Run compatibility smokes
|
||
run: pnpm run check:node-compat
|
||
|
||
python-sdk:
|
||
if: github.event_name == 'pull_request'
|
||
runs-on: ubuntu-latest
|
||
name: python 3.10 / keyless SDK
|
||
steps:
|
||
- uses: actions/checkout@v6
|
||
|
||
- uses: actions/setup-python@v6.3.0
|
||
with:
|
||
python-version: '3.10'
|
||
cache: pip
|
||
|
||
- name: Install uv
|
||
run: python -m pip install uv==0.11.23
|
||
|
||
- name: Run complete keyless Python suite
|
||
run: uv run --python 3.10 --group test --project python/sdk pytest
|
||
|
||
# One native target makes the complete release-shaped Python path required
|
||
# without duplicating platform-independent behavior across the release matrix.
|
||
# The reusable builder owns the executable, snapshot, wheel, clean-install,
|
||
# GLIBC, and manylinux checks; release validation retains all native targets.
|
||
python-runtime:
|
||
if: github.event_name == 'pull_request'
|
||
name: python runtime / release-shaped Linux x64
|
||
uses: ./.github/workflows/build-exe-for-python-sdk.yml
|
||
with:
|
||
targets: node24-linux-x64
|
||
ci: true
|
||
|
||
# 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. 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
|
||
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. Under normal operation it 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-native:
|
||
if: github.event_name == 'pull_request'
|
||
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:
|
||
DSH_COVERAGE_MAX_WORKERS: '2'
|
||
DSH_GATE_CONCURRENCY: '2'
|
||
DSH_PUBLINT_CONCURRENCY: '8'
|
||
steps:
|
||
- uses: actions/checkout@v6
|
||
with:
|
||
persist-credentials: false
|
||
|
||
- 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 }}
|
||
|
||
# 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
|
||
|
||
- name: Run complete native Windows gate inventory
|
||
shell: pwsh
|
||
run: pnpm run check:ci:windows-complete
|
||
|
||
# The hosted reference jobs below are temporarily disabled; the self-hosted
|
||
# standby remains active. Each enabled host executes the complete, unsharded
|
||
# primary Node aggregate with one gate worker, giving reviewers a simple
|
||
# cross-platform oracle for completeness and timing.
|
||
serial-linux:
|
||
if: false
|
||
name: serial / linux
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v6
|
||
with:
|
||
fetch-depth: 2
|
||
|
||
- 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"
|
||
|
||
# Master refreshes the pnpm store cache that pull requests restore without saving.
|
||
# The store cache stays a hand-rolled actions/cache step rather than
|
||
# setup-node's `cache: pnpm`: the enterprise pull-request jobs above
|
||
# restore exactly this key and path, and setup-node's built-in cache
|
||
# uses its own key format — converting this producer would silently
|
||
# starve their documented restore-only optimization.
|
||
- uses: actions/cache@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 produces the hosted Chromium cache restored by pull requests.
|
||
- uses: actions/cache@v4
|
||
with:
|
||
path: ~/.cache/ms-playwright
|
||
key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }}
|
||
restore-keys: |
|
||
${{ runner.os }}-playwright-
|
||
|
||
- name: Install (immutable)
|
||
run: pnpm install --frozen-lockfile
|
||
|
||
- name: Install Playwright Chromium and system dependencies
|
||
run: pnpm --filter @deepseek-ai/dsh-frontend exec playwright install --with-deps 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
|
||
|
||
# Hot-standby drill for the in-house self-hosted pool: every master move
|
||
# re-runs the complete unsharded aggregate on the persistent 64-core VM,
|
||
# continuously proving that environment can take over a required lane if
|
||
# the hosted pools degrade (the switch is then 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. (Under failover, pull_request jobs do reach these
|
||
# runners with the PR merge ref's workflow — the boundary there is
|
||
# repository membership: private, forking disabled, Dependabot excluded.)
|
||
# Non-blocking for
|
||
# pull requests; 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-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:
|
||
# Full history + DSH_ARCHIVE_BASE_REF below: same frozen-archive
|
||
# comparison as serial-linux. Depth 2 would miss github.event.before
|
||
# 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-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
|
||
|
||
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 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:
|
||
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
|
||
|
||
- 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
|
||
|
||
# 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
|
||
# check.)
|
||
# `if: always()` is load-bearing: without it a failed dependency
|
||
# would SKIP this job, and GitHub counts a skipped required check as passing
|
||
# — so this job always runs and fails on any non-success result, including
|
||
# 'cancelled' and 'skipped'.
|
||
all-checks-passed:
|
||
name: all checks passed
|
||
# This bookkeeping-only verdict must not depend on custom-pool
|
||
# provisioning — and under failover it follows the same selector as the
|
||
# worker jobs it aggregates, so a standard-hosted outage cannot strand
|
||
# the branch-protection verdict either.
|
||
runs-on: >-
|
||
${{ vars.DSH_CI_FAILOVER == 'selfhosted'
|
||
&& 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, python-runtime, windows]
|
||
if: always() && github.event_name == 'pull_request'
|
||
steps:
|
||
- name: Fail if any needed job did not succeed
|
||
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')
|
||
run: |
|
||
echo "::error::Needed job results: ${{ join(needs.*.result, ', ') }}"
|
||
exit 1
|
||
- name: All checks passed
|
||
run: echo "All needed jobs succeeded (${{ join(needs.*.result, ', ') }})"
|