Skip to content

fix(release): pin the Windows upgrade baseline to a published build (… #3787

fix(release): pin the Windows upgrade baseline to a published build (…

fix(release): pin the Windows upgrade baseline to a published build (… #3787

# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
name: Windows recovery
# The paths below are a pre-filter, not this lane's real input. The real input
# is the import closure of the crash and owner-death recovery authorities in
# storage, runtime and Runtime Host, which reaches well past any list worth
# hand-maintaining. So they name those workspaces and every workspace they hold
# a TypeScript project reference to, plus the manifests, patches and scripts
# the unconditional install and clean steps consume. That keeps a change there
# reported before merge, and the nightly run covers the transitive edits the
# list cannot match.
#
# GitHub evaluates a path filter against the first 300 files of the diff only,
# so a pull request wider than that can skip the filter outright. A repository-wide sweep is exactly the change that touches every
# recovery authority at once, which is one more reason the main push below
# carries no filter at all.
on:
pull_request:
branches: [main]
paths:
- 'package.json'
- 'package-lock.json'
- 'patches/**'
- 'scripts/apply-dependency-patches.mjs'
- 'scripts/install-electron-with-retry.mjs'
- 'scripts/run-electron-installer.cjs'
- 'scripts/clean-build.mjs'
- 'scripts/clean-paths.mjs'
- 'scripts/windows-runtime-host-local-ipc-trust.ps1'
- 'tsconfig.base.json'
- 'tsconfig.lib.json'
- 'packages/core/package.json'
- 'packages/core/tsconfig.json'
- 'packages/core/src/**'
- 'packages/storage/package.json'
- 'packages/storage/tsconfig.json'
- 'packages/storage/src/**'
- 'packages/runtime/package.json'
- 'packages/runtime/tsconfig.json'
- 'packages/runtime/src/**'
- 'packages/runtime/scripts/**'
- 'packages/runtime-host/package.json'
- 'packages/runtime-host/tsconfig.json'
- 'packages/runtime-host/src/**'
- '.github/workflows/windows-recovery.yml'
# Unfiltered on purpose: required_status_checks is `strict: false`, so a pull
# request goes green against a stale base and only the merged result proves
# two independently green halves still agree. It is also the backstop for the
# 300-file case above. The nightly alone would find either a day later,
# against a batch of commits instead of one.
push:
branches: [main]
schedule:
# Offset from windows-sandbox-w0 so the Windows lanes do not overlap.
- cron: '17 8 * * *'
workflow_dispatch:
# Pull request pushes supersede each other, keyed on the pull request number
# because github.head_ref is a bare branch name two forks can share: a second
# contributor pushing to their own `main` would otherwise cancel the first
# contributor's run, and a cancelled check is not a failed one.
# Scheduled, manual and main-push runs fall back to the run id, since
# github.ref is refs/heads/main for all three and one shared group would let a
# dispatch queue behind the nightly and then be discarded while still pending.
# That deliberately stops main pushes from superseding each other: this lane is
# the only place a merged Windows regression is observed, so every merge needs
# its own evidence rather than only the newest one surviving.
concurrency:
group: windows-recovery-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
windows_recovery:
name: windows_recovery
runs-on: windows-latest
timeout-minutes: 45
steps:
- 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
- name: Install dependencies
run: npm.cmd ci
- name: Build test artifacts
run: npm.cmd run build:test
- name: Verify managed dependency alternate streams
shell: pwsh
run: |
node.exe --test --test-reporter=tap --test-concurrency=1 `
--test-name-pattern="NTFS alternate stream" `
packages/storage/dist/__tests__/managed-dependency-environment.test.js `
2>&1 | Tee-Object -FilePath "$env:RUNNER_TEMP/managed-dependency-ads.tap"
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) { exit $exitCode }
$output = Get-Content "$env:RUNNER_TEMP/managed-dependency-ads.tap"
if ($output -notcontains '# tests 3' -or $output -notcontains '# pass 3' -or $output -notcontains '# skipped 0') {
Write-Error 'Managed dependency ADS gate did not run exactly three passing Windows tests'
exit 1
}
- name: Verify root initialization replacement race
shell: pwsh
run: |
node.exe --test --test-reporter=tap --test-concurrency=1 `
--test-name-pattern="rejects replacement before opening the temporary marker" `
packages/storage/dist/__tests__/root-authority.test.js `
2>&1 | Tee-Object -FilePath "$env:RUNNER_TEMP/root-initialization-race.tap"
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) { exit $exitCode }
$output = Get-Content "$env:RUNNER_TEMP/root-initialization-race.tap"
if ($output -notcontains '# tests 1' -or $output -notcontains '# pass 1' -or $output -notcontains '# skipped 0') {
Write-Error 'Root initialization race gate did not run exactly one passing Windows test'
exit 1
}
- name: Verify Runtime Host Local IPC trust boundary
shell: pwsh
run: |
node.exe --test packages/runtime-host/dist/__tests__/control-endpoint.test.js
./scripts/windows-runtime-host-local-ipc-trust.ps1
- name: Verify Skill catalog on Windows
shell: pwsh
run: |
node.exe --test --test-reporter=tap --test-concurrency=1 `
packages/runtime-host/dist/__tests__/skill-catalog-coordinator.test.js `
packages/runtime-host/dist/__tests__/skill-catalog-protocol.test.js `
packages/runtime-host/dist/__tests__/skill-catalog-repository.test.js `
packages/runtime-host/dist/__tests__/skill-catalog-transaction.test.js `
packages/runtime-host/dist/__tests__/skill-catalog-two-client-uds.test.js `
2>&1 | Tee-Object -FilePath "$env:RUNNER_TEMP/skill-catalog.tap"
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) { exit $exitCode }
$output = Get-Content "$env:RUNNER_TEMP/skill-catalog.tap"
if ($output -notcontains '# tests 91' -or $output -notcontains '# pass 91' -or $output -notcontains '# skipped 0') {
Write-Error 'Skill catalog gate did not run exactly 91 passing Windows tests'
exit 1
}
- name: Verify SQLite crash recovery
shell: pwsh
run: |
node.exe --test --test-concurrency=1 `
packages/storage/dist/__tests__/sqlite-runtime-crash.test.js `
packages/storage/dist/__tests__/sqlite-long-term-memory-crash.test.js
- name: Verify Runtime continuation recovery
shell: pwsh
run: |
node.exe --test --test-concurrency=1 `
packages/runtime/dist/__tests__/runtime-resume-crash.test.js `
packages/runtime/dist/__tests__/runtime-continuation-crash.test.js
- name: Verify Runtime Host owner-death recovery
shell: pwsh
run: |
node.exe --test --test-reporter=tap --test-concurrency=1 `
--test-name-pattern="owner death|a killed Host is recovered exactly once" `
packages/runtime-host/dist/__tests__/artifact-two-client-uds.test.js `
packages/runtime-host/dist/__tests__/execution-host-queue.test.js `
2>&1 | Tee-Object -FilePath "$env:RUNNER_TEMP/runtime-host-recovery.tap"
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) { exit $exitCode }
$output = Get-Content "$env:RUNNER_TEMP/runtime-host-recovery.tap"
if ($output -notcontains '# tests 2' -or $output -notcontains '# pass 2' -or $output -notcontains '# skipped 0') {
Write-Error 'Runtime Host recovery gate did not run exactly two passing tests'
exit 1
}