# 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 }