mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-11 04:00:38 +00:00
Add a benchmark lane (`vitest.bench.config.ts`, `pnpm run test:bench`, gate mode `ci-bench`) and a required `node 24 / benchmarks` CI job that runs it alone. Benchmarks synthesize their input in-process from fixed parameters and fail on documented budgets: - `open-generation.bench.ts`: a 200-turn released-v0 log with 500 text and 125 reasoning deltas per reply (127,400 events, ~2.8 MB) encoded through the frozen v0 codec; the migrating first `open()` must finish within 2,000 ms in a child process capped at 128 MB of old space, and a fresh process must open the published current generation within 500 ms. - `conversation-fold.bench.client.ts`: 200 replies whose compact streams hold 2,000 text + 500 reasoning deltas each, folded through every Chat Definition by the real assembler; the fold must finish within 150 ms and stay within 3x the fold of the same window with 100 deltas per reply. On this commit both gates fail: the migration exhausts the 128 MB heap (4.8 s and 696 MB peak RSS without the cap; the pre-stack decode of the same bytes took 34 ms and 168 MB) and the fold scales 11x with the delta count. The stacked fixes bring both paths to O(records).
724 lines
31 KiB
YAML
724 lines
31 KiB
YAML
name: CI
|
|
|
|
on:
|
|
pull_request:
|
|
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'
|
|
|
|
# Cancel a superseded pull-request run on a new push so a fresh head does not
|
|
# queue a second full 9-job run behind a stale one (paid enterprise runners
|
|
# would otherwise stack with no auto-cancellation).
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
|
|
# 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 (Linux): each Linux enterprise job resolves its pool through the
|
|
# DSH_CI_FAILOVER_LINUX 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 in ci-master.yml. The Windows failover
|
|
# switch is the separate DSH_CI_FAILOVER_WINDOWS variable on the windows-native
|
|
# job below.
|
|
node-24:
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: >-
|
|
${{ vars.DSH_CI_FAILOVER_LINUX == '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'
|
|
# Stop the aggregate at the first blocking gate failure so a red run
|
|
# does not keep burning enterprise runner time on the remaining gates.
|
|
DSH_GATE_FAIL_FAST: '1'
|
|
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-${{ github.run_id }}-${{ github.run_attempt }}
|
|
|
|
- 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_LINUX != '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_LINUX == '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:
|
|
# Partitioning replaces the instrumented share; this budget gives the
|
|
# exempt-heavy gate two workers on both hosted and failover runners.
|
|
DSH_COVERAGE_MAX_WORKERS: '6'
|
|
DSH_COVERAGE_PARTITIONS: '4'
|
|
DSH_GATE_CONCURRENCY: '3'
|
|
# A gate failure aborts the sibling gate instead of waiting out its
|
|
# multi-minute instrumented run.
|
|
DSH_GATE_FAIL_FAST: '1'
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }}
|
|
|
|
- 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_LINUX != '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-bench:
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: >-
|
|
${{ vars.DSH_CI_FAILOVER_LINUX == 'selfhosted'
|
|
&& github.event.pull_request.user.login != 'dependabot[bot]'
|
|
&& fromJSON('["self-hosted", "linux", "x64", "vm-backup"]')
|
|
|| 'dsh-ubuntu-24-04-16core' }}
|
|
name: node 24 / benchmarks
|
|
# Wall-clock budgets need an otherwise idle runner, so this job runs the
|
|
# benchmark lane alone instead of joining a concurrent gate aggregate.
|
|
timeout-minutes: 30
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.run_attempt }}
|
|
|
|
- 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
|
|
if: vars.DSH_CI_FAILOVER_LINUX != '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 performance benchmarks
|
|
run: pnpm run check:ci:bench
|
|
|
|
node-24-consumers:
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: >-
|
|
${{ vars.DSH_CI_FAILOVER_LINUX == '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: '10'
|
|
DSH_NODE_COMPAT_SKIP_TYPECHECK: '1'
|
|
DSH_OXLINT_THREADS: '8'
|
|
DSH_PUBLINT_CONCURRENCY: '8'
|
|
DSH_WEB_SNAPSHOT_WORKERS: '6'
|
|
# A failing gate aborts its running siblings: a failing build stops the
|
|
# independent Node compatibility smoke, and a failing reader (e.g.
|
|
# publint) stops the remaining artifact consumers.
|
|
DSH_GATE_FAIL_FAST: '1'
|
|
# Failover halves snapshot concurrency for the shared 64-core VM.
|
|
DSH_SNAPSHOT_MAX_CONCURRENCY: ${{ vars.DSH_CI_FAILOVER_LINUX == '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-${{ github.run_id }}-${{ github.run_attempt }}
|
|
|
|
- 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 restore the pnpm store and Playwright caches without paying
|
|
# compression and upload on the required path. No master job saves these
|
|
# hosted cache keys, so each restore-keys fallback hits the matching archived
|
|
# entry until it evicts, after which the store is cold. Skipped under failover
|
|
# — the self-hosted VM's persistent store is already warm.
|
|
- uses: actions/cache/restore@v4
|
|
if: vars.DSH_CI_FAILOVER_LINUX != '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-
|
|
|
|
# Skipped under failover: the VM's persistent browser cache is already warm.
|
|
- uses: actions/cache/restore@v4
|
|
if: vars.DSH_CI_FAILOVER_LINUX != '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_LINUX != 'selfhosted' || github.event.pull_request.user.login == 'dependabot[bot]'
|
|
run: pnpm --filter @deepseek-ai/dsh-web-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_LINUX == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]'
|
|
run: pnpm --filter @deepseek-ai/dsh-web-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'
|
|
# A failed smoke aborts the remaining compatibility gates instead of
|
|
# letting the build-backed legs run against an already-red aggregate.
|
|
DSH_GATE_FAIL_FAST: '1'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- node: '22.19'
|
|
name: node 22.19
|
|
runner: ubuntu-latest
|
|
gate_concurrency: '1'
|
|
# Pinned inside 24.0-24.11.1: those releases carry the v1 internal
|
|
# loader while reporting major 24, and every other job tracks the
|
|
# latest 24, which is v2. A bare `24` here would retest that same v2.
|
|
- node: '24.9'
|
|
name: node 24.9
|
|
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-${{ github.run_id }}-${{ github.run_attempt }}
|
|
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ matrix.node }}
|
|
cache: pnpm
|
|
|
|
- name: Install (immutable)
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Run compatibility smokes
|
|
env:
|
|
DSH_BUILD_CLIENT_PROFILE: official
|
|
run: pnpm run check:node-compat
|
|
|
|
# Kept out of the gate aggregate: the shape a Node release carries only
|
|
# changes with the Node version, so this belongs to the version matrix
|
|
# rather than to every commit's checks.
|
|
- name: Check Loader internal shape detection
|
|
run: pnpm exec vitest run packages/boot/app-boot/tests/loader-shape.compat.spec.ts
|
|
|
|
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
|
|
|
|
# The reusable builder owns each published executable, wheel, clean-install,
|
|
# keyless black-box, and trusted real-API path. All native release targets are
|
|
# required because a platform wheel cannot be validated by another carrier.
|
|
python-runtime:
|
|
if: github.event_name == 'pull_request'
|
|
name: python runtime / release-shaped matrix
|
|
uses: ./.github/workflows/build-exe-for-python-sdk.yml
|
|
with:
|
|
targets: node24-linux-x64,node24-linux-arm64,node24-macos-arm64,node24-macos-x64,node24-win-x64
|
|
ci: true
|
|
secrets:
|
|
DEEPSEEK_API_KEY_EXTERNAL: ${{ secrets.DEEPSEEK_API_KEY_EXTERNAL }}
|
|
|
|
# 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-${{ github.run_id }}-${{ github.run_attempt }}
|
|
|
|
- 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 in ci-master.yml 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
|
|
# The restored archive is the full --download-only closure of
|
|
# `wine` for this runner image, so installing the .debs directly
|
|
# with dpkg needs no repository access. apt-get would instead
|
|
# re-download the same 100+ MB closure from the mirror, which has
|
|
# stalled the job past its budget on a degraded runner network.
|
|
# If the archive cannot satisfy the closure, fall back to the apt
|
|
# network install.
|
|
if ! sudo DEBIAN_FRONTEND=noninteractive dpkg -i "$HOME"/wine-debs/*.deb; then
|
|
sudo DEBIAN_FRONTEND=noninteractive dpkg --configure -a || true
|
|
sudo apt-get install -y --no-install-recommends "$HOME"/wine-debs/*.deb
|
|
fi
|
|
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
|
|
|
|
# Every pull request also gets real Windows-kernel signals. The former
|
|
# monolithic windows-native job is split into smaller jobs so one slow
|
|
# coverage gate does not hold up build/static results, while the total
|
|
# per-job process count stays lower. Observational checks are non-blocking.
|
|
# Dependabot PRs are excluded from the self-hosted pool and stay queued for
|
|
# the hosted runner.
|
|
windows-build:
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: >-
|
|
${{ vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted'
|
|
&& github.event.pull_request.user.login != 'dependabot[bot]'
|
|
&& fromJSON('["self-hosted", "dsh-win-ci", "windows"]')
|
|
|| 'dsh-windows-2025-16core' }}
|
|
name: windows node 24 / build
|
|
timeout-minutes: 60
|
|
env:
|
|
# A failing build or site aborts the sibling gate on the same runner.
|
|
DSH_GATE_FAIL_FAST: '1'
|
|
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-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.PRIMARY_NODE_VERSION }}
|
|
- name: Install (immutable)
|
|
shell: pwsh
|
|
# See 2026-08-30-windows-refs-store-block-clone-install for the
|
|
# ReFS block-clone rationale; detect the workspace filesystem and
|
|
# pass --package-import-method=clone only on ReFS.
|
|
run: >-
|
|
$drive = (Split-Path -Qualifier $env:GITHUB_WORKSPACE).TrimEnd(':');
|
|
$fs = (Get-Volume -DriveLetter $drive).FileSystem;
|
|
if ($fs -eq 'ReFS') {
|
|
corepack pnpm install --frozen-lockfile --package-import-method=clone
|
|
} else {
|
|
pnpm install --frozen-lockfile
|
|
}
|
|
- name: Run blocking Windows builds
|
|
shell: pwsh
|
|
run: pnpm run check:ci:windows-blocking
|
|
|
|
windows-coverage:
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: >-
|
|
${{ vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted'
|
|
&& github.event.pull_request.user.login != 'dependabot[bot]'
|
|
&& fromJSON('["self-hosted", "dsh-win-ci", "windows"]')
|
|
|| 'dsh-windows-2025-16core' }}
|
|
name: windows node 24 / coverage
|
|
timeout-minutes: 120
|
|
env:
|
|
DSH_COVERAGE_MAX_WORKERS: '6'
|
|
DSH_COVERAGE_PARTITIONS: '4'
|
|
DSH_COVERAGE_TEST_TIMEOUT_MS: '90000'
|
|
DSH_GATE_CONCURRENCY: '3'
|
|
# A gate failure aborts the sibling gate instead of waiting out its
|
|
# multi-minute instrumented run.
|
|
DSH_GATE_FAIL_FAST: '1'
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
# Checkout removes the gitignored duration history, so restore it from
|
|
# the GitHub cache before coverage and save the updated one afterwards.
|
|
# The coordinator then weights partitions by measured durations on the
|
|
# second run instead of degrading to a file-count split. GitHub cache
|
|
# entries are immutable, so the save key is unique per run and the
|
|
# restore matches the newest entry through the stable prefix.
|
|
- name: Restore coverage duration history
|
|
uses: actions/cache/restore@v4
|
|
with:
|
|
path: .coverage-times.json
|
|
key: coverage-times-${{ github.run_id }}
|
|
restore-keys: |
|
|
coverage-times-
|
|
- 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-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.PRIMARY_NODE_VERSION }}
|
|
- name: Install (immutable)
|
|
shell: pwsh
|
|
# See 2026-08-30-windows-refs-store-block-clone-install for the
|
|
# ReFS block-clone rationale; detect the workspace filesystem and
|
|
# pass --package-import-method=clone only on ReFS.
|
|
run: >-
|
|
$drive = (Split-Path -Qualifier $env:GITHUB_WORKSPACE).TrimEnd(':');
|
|
$fs = (Get-Volume -DriveLetter $drive).FileSystem;
|
|
if ($fs -eq 'ReFS') {
|
|
corepack pnpm install --frozen-lockfile --package-import-method=clone
|
|
} else {
|
|
pnpm install --frozen-lockfile
|
|
}
|
|
# No build before coverage, matching the Linux lane: workspace imports
|
|
# resolve to src through the tsconfig paths map, and the lib-consuming
|
|
# suites (webworker-packer image-loadable, webworker-runtime
|
|
# transform-corpus, client ui-trajectory client-bundle) self-skip on
|
|
# unbuilt checkouts.
|
|
- name: Run Windows coverage
|
|
shell: pwsh
|
|
run: pnpm run check:ci:coverage
|
|
- name: Save coverage duration history
|
|
# Coverage flakes must not prevent the cache from building; the
|
|
# measured durations remain useful even when a partition failed. The
|
|
# per-run key keeps every save a fresh immutable cache entry.
|
|
if: always()
|
|
uses: actions/cache/save@v4
|
|
with:
|
|
path: .coverage-times.json
|
|
key: coverage-times-${{ github.run_id }}
|
|
|
|
windows-native-tests:
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: >-
|
|
${{ vars.DSH_CI_FAILOVER_WINDOWS == '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 tests
|
|
timeout-minutes: 60
|
|
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-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.PRIMARY_NODE_VERSION }}
|
|
- name: Install (immutable)
|
|
shell: pwsh
|
|
# See 2026-08-30-windows-refs-store-block-clone-install for the
|
|
# ReFS block-clone rationale; detect the workspace filesystem and
|
|
# pass --package-import-method=clone only on ReFS.
|
|
run: >-
|
|
$drive = (Split-Path -Qualifier $env:GITHUB_WORKSPACE).TrimEnd(':');
|
|
$fs = (Get-Volume -DriveLetter $drive).FileSystem;
|
|
if ($fs -eq 'ReFS') {
|
|
corepack pnpm install --frozen-lockfile --package-import-method=clone
|
|
} else {
|
|
pnpm install --frozen-lockfile
|
|
}
|
|
- name: Run Windows-specific native tests
|
|
shell: pwsh
|
|
run: >-
|
|
pnpm exec vitest run
|
|
--no-file-parallelism
|
|
--testTimeout 90000
|
|
packages/shell/tool-pwsh/tests/loader.spec.ts
|
|
packages/workflow/workflow-worker-thread/tests/workflow-worker-thread.spec.ts
|
|
packages/workflow/tool-ralph/tests/integration.spec.ts
|
|
packages/subprocess/subprocess-local/tests/process-exit.spec.ts
|
|
|
|
windows-observational:
|
|
if: github.event_name == 'pull_request'
|
|
continue-on-error: true
|
|
runs-on: >-
|
|
${{ vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted'
|
|
&& github.event.pull_request.user.login != 'dependabot[bot]'
|
|
&& fromJSON('["self-hosted", "dsh-win-ci", "windows"]')
|
|
|| 'dsh-windows-2025-16core' }}
|
|
name: windows node 24 / observational
|
|
timeout-minutes: 60
|
|
env:
|
|
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-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.PRIMARY_NODE_VERSION }}
|
|
- name: Install (immutable)
|
|
shell: pwsh
|
|
# See 2026-08-30-windows-refs-store-block-clone-install for the
|
|
# ReFS block-clone rationale; detect the workspace filesystem and
|
|
# pass --package-import-method=clone only on ReFS.
|
|
run: >-
|
|
$drive = (Split-Path -Qualifier $env:GITHUB_WORKSPACE).TrimEnd(':');
|
|
$fs = (Get-Volume -DriveLetter $drive).FileSystem;
|
|
if ($fs -eq 'ReFS') {
|
|
corepack pnpm install --frozen-lockfile --package-import-method=clone
|
|
} else {
|
|
pnpm install --frozen-lockfile
|
|
}
|
|
- name: Run Windows observational gates
|
|
shell: pwsh
|
|
run: pnpm run check:ci:windows-observational
|
|
|
|
# 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; the master-only jobs in
|
|
# ci-master.yml are intentionally not part of this PR verdict.)
|
|
# `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 Linux failover it follows the same selector as
|
|
# the worker jobs it aggregates, so a standard-hosted outage cannot strand
|
|
# the branch-protection verdict either. It retargets with the Linux switch
|
|
# (DSH_CI_FAILOVER_LINUX), not the Windows one, because it aggregates the
|
|
# required Linux workers and runs on the vm-backup pool.
|
|
runs-on: >-
|
|
${{ vars.DSH_CI_FAILOVER_LINUX == '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-bench, node-24-consumers, node-compat, python-sdk, python-runtime, windows, windows-build, windows-native-tests]
|
|
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, ', ') }})"
|