From c24adccff0118b490f531ee9ffffb8c0f084bcce Mon Sep 17 00:00:00 2001 From: m4air Date: Tue, 29 Sep 2026 01:36:57 -0700 Subject: [PATCH] ci(ssh): qualify the Windows SSH provider on x64 as well as ARM64 --- .../diagnostic-windows-ssh-provider.yml | 48 ++++++---- .../invoke-provider-route.ps1 | 9 +- ....json => preview-native-inputs-arm64.json} | 0 .../preview-native-inputs-x64.json | 96 +++++++++++++++++++ .../preview-ssh/prove-preview-openssh.ps1 | 17 ++-- .../windows-provider-candidate.ts | 12 ++- .../windows-provider-loaded-images.ts | 9 +- ...test.ts => windows-provider-route.test.ts} | 13 +-- 8 files changed, 159 insertions(+), 45 deletions(-) rename config/ci/windows-ssh-provider/preview-ssh/{preview-native-inputs.json => preview-native-inputs-arm64.json} (100%) create mode 100644 config/ci/windows-ssh-provider/preview-ssh/preview-native-inputs-x64.json rename config/ci/windows-ssh-provider/{windows-arm-provider-route.test.ts => windows-provider-route.test.ts} (98%) diff --git a/.github/workflows/diagnostic-windows-ssh-provider.yml b/.github/workflows/diagnostic-windows-ssh-provider.yml index 12dcf7ca47f..5f044db4ae2 100644 --- a/.github/workflows/diagnostic-windows-ssh-provider.yml +++ b/.github/workflows/diagnostic-windows-ssh-provider.yml @@ -1,4 +1,4 @@ -name: Diagnostic stable Bun ARM SSH provider qualification +name: Diagnostic stable Bun Windows SSH provider qualification on: push: branches: [OrcaWin/np-windows-ssh-provider-diagnostic] @@ -7,7 +7,7 @@ permissions: contents: read actions: read concurrency: - group: arm-ssh-provider-${{ github.ref }} + group: windows-ssh-provider-${{ github.ref }} cancel-in-progress: false jobs: windows_watcher: @@ -17,7 +17,17 @@ jobs: ref: 2084c58ba5410106ce61153a9fb16cdb4b6e5301 qualify: needs: windows_watcher - runs-on: windows-11-arm + strategy: + fail-fast: false + matrix: + include: + - arch: arm64 + runner: windows-11-arm + archive: OpenSSH-ARM64.zip + - arch: x64 + runner: windows-2022 + archive: OpenSSH-Win64.zip + runs-on: ${{ matrix.runner }} timeout-minutes: 45 env: ORCA_BACKGROUND_LAUNCH: '1' @@ -63,29 +73,29 @@ jobs: shell: pwsh run: | $tools=Join-Path $pwd '.build/qualification-tools/config/ci/windows-ssh-provider' - node (Join-Path $tools 'verify-bun-output.mjs') candidate .build/producer . arm64 .build/ssh-provider-receipts/candidate.json $env:PRODUCER_RUN + node (Join-Path $tools 'verify-bun-output.mjs') candidate .build/producer . ${{ matrix.arch }} .build/ssh-provider-receipts/candidate.json $env:PRODUCER_RUN if($LASTEXITCODE -ne 0){throw 'Stable producer/candidate admission failed'} $receipt=(Resolve-Path .build/ssh-provider-receipts/candidate.json).Path $hash=(Get-FileHash -Algorithm SHA256 -LiteralPath $receipt).Hash.ToLowerInvariant() "CANDIDATE_RECEIPT=$receipt" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append "CANDIDATE_RECEIPT_SHA256=$hash" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - @{candidateReceiptSha256=$hash;producerRun=$env:PRODUCER_RUN;product=$env:QUALIFICATION_SOURCE_SHA;candidateOverride=$true;expectedSourcePin=$false;scope='diagnostic in-memory ARM executable pin and private cache only'} | ConvertTo-Json | Set-Content .build/ssh-provider-receipts/admission.json + @{candidateReceiptSha256=$hash;producerRun=$env:PRODUCER_RUN;product=$env:QUALIFICATION_SOURCE_SHA;candidateOverride=$true;expectedSourcePin=$false;scope='diagnostic in-memory ${{ matrix.arch }} executable pin and private cache only'} | ConvertTo-Json | Set-Content .build/ssh-provider-receipts/admission.json - name: Build production relay artifacts and native process table shell: pwsh run: | - node config/scripts/build-cli-runtime.mjs --platform win32 --arch arm64 + node config/scripts/build-cli-runtime.mjs --platform win32 --arch ${{ matrix.arch }} if($LASTEXITCODE -ne 0){throw 'CLI runtime build failed'} - node config/scripts/build-windows-process-tree-relay-addon.mjs --arch=arm64 + node config/scripts/build-windows-process-tree-relay-addon.mjs --arch=${{ matrix.arch }} if($LASTEXITCODE -ne 0){throw 'Native process-table build failed'} - $env:ORCA_REQUIRE_RELAY_NATIVE_ADDONS='arm64' + $env:ORCA_REQUIRE_RELAY_NATIVE_ADDONS='${{ matrix.arch }}' node config/scripts/build-relay.mjs if($LASTEXITCODE -ne 0){throw 'Relay build failed'} - name: Run watcher fault harness with production-pinned CLI Bun shell: pwsh timeout-minutes: 5 run: | - Write-Output 'Scope: production-pinned bundled CLI Bun; actual SSH provider qualification separately uses the admitted stable candidate.' | Tee-Object .build/ssh-provider-receipts/watcher-fault-arm64.log - node config/scripts/relay-watcher-fault-harness.mjs 2>&1 | Tee-Object -Append .build/ssh-provider-receipts/watcher-fault-arm64.log + Write-Output 'Scope: production-pinned bundled CLI Bun; actual SSH provider qualification separately uses the admitted stable candidate.' | Tee-Object .build/ssh-provider-receipts/watcher-fault-${{ matrix.arch }}.log + node config/scripts/relay-watcher-fault-harness.mjs 2>&1 | Tee-Object -Append .build/ssh-provider-receipts/watcher-fault-${{ matrix.arch }}.log if($LASTEXITCODE -ne 0){throw 'Production-pinned CLI Bun watcher fault harness failed'} - name: Parse and typecheck all fixture code before provisioning shell: pwsh @@ -99,7 +109,7 @@ jobs: & (Join-Path $tools 'preview-ssh/test-preview-diagnostics.ps1') $copied=[Collections.Generic.List[string]]::new() try { - foreach($name in @('windows-arm-provider-route.test.ts','windows-provider-candidate.ts','windows-provider-repaint.ts','windows-provider-loaded-images.ts')){ + foreach($name in @('windows-provider-route.test.ts','windows-provider-candidate.ts','windows-provider-repaint.ts','windows-provider-loaded-images.ts')){ $destination=Join-Path $pwd "src/main/ssh/$name" if(Test-Path -LiteralPath $destination){throw 'Fixture destination already exists'} Copy-Item -LiteralPath (Join-Path $tools $name) -Destination $destination @@ -116,23 +126,23 @@ jobs: run: | $tools=Join-Path $pwd '.build/qualification-tools/config/ci/windows-ssh-provider' $sourceRoot=$pwd.Path - $archive=Join-Path $env:RUNNER_TEMP 'preview-OpenSSH-ARM64.zip' - & "$env:WINDIR\System32\curl.exe" --fail --location --connect-timeout 15 --max-time 90 --output $archive 'https://github.com/PowerShell/Win32-OpenSSH/releases/download/10.0.0.0p2-Preview/OpenSSH-ARM64.zip' + $archive=Join-Path $env:RUNNER_TEMP 'preview-${{ matrix.archive }}' + & "$env:WINDIR\System32\curl.exe" --fail --location --connect-timeout 15 --max-time 90 --output $archive 'https://github.com/PowerShell/Win32-OpenSSH/releases/download/10.0.0.0p2-Preview/${{ matrix.archive }}' if($LASTEXITCODE -ne 0){throw 'SSH archive fetch failed'} $callback={param($user,$port,$identity,$known) - & (Join-Path $tools 'invoke-provider-route.ps1') -SourceRoot $sourceRoot -SourceCommit $env:QUALIFICATION_SOURCE_SHA -Username $user -Port $port -IdentityFile $identity -KnownHosts $known -ReceiptRoot "$env:RUNNER_TEMP\arm-provider-route" -CandidateReceipt $env:CANDIDATE_RECEIPT -CandidateReceiptSha256 $env:CANDIDATE_RECEIPT_SHA256 + & (Join-Path $tools 'invoke-provider-route.ps1') -SourceRoot $sourceRoot -SourceCommit $env:QUALIFICATION_SOURCE_SHA -Username $user -Port $port -IdentityFile $identity -KnownHosts $known -ReceiptRoot "$env:RUNNER_TEMP\provider-route" -CandidateReceipt $env:CANDIDATE_RECEIPT -CandidateReceiptSha256 $env:CANDIDATE_RECEIPT_SHA256 -Arch '${{ matrix.arch }}' }.GetNewClosure() - & (Join-Path $tools 'preview-ssh/prove-preview-openssh.ps1') -Archive $archive -Receipt "$env:RUNNER_TEMP\arm-provider-server.json" -ProductionRouteProbe $callback 2>&1 | Tee-Object .build/ssh-provider-receipts/native-route.log + & (Join-Path $tools 'preview-ssh/prove-preview-openssh.ps1') -Archive $archive -Arch '${{ matrix.arch }}' -Receipt "$env:RUNNER_TEMP\provider-server.json" -ProductionRouteProbe $callback 2>&1 | Tee-Object .build/ssh-provider-receipts/native-route.log - uses: actions/upload-artifact@v7 if: always() with: - name: stable-bun-arm-ssh-provider-receipts + name: stable-bun-${{ matrix.arch }}-ssh-provider-receipts path: | .build/ssh-provider-receipts/ .build/producer/results/ .build/producer/work/source-cache-receipt.json .build/producer/producer-run.json - ${{ runner.temp }}/arm-provider-server.json - ${{ runner.temp }}/arm-provider-route/production-route.json - ${{ runner.temp }}/arm-provider-route/repaint-events.json + ${{ runner.temp }}/provider-server.json + ${{ runner.temp }}/provider-route/production-route.json + ${{ runner.temp }}/provider-route/repaint-events.json retention-days: 7 diff --git a/config/ci/windows-ssh-provider/invoke-provider-route.ps1 b/config/ci/windows-ssh-provider/invoke-provider-route.ps1 index 5d81bdd1740..b4d975af52c 100644 --- a/config/ci/windows-ssh-provider/invoke-provider-route.ps1 +++ b/config/ci/windows-ssh-provider/invoke-provider-route.ps1 @@ -7,7 +7,8 @@ param( [Parameter(Mandatory=$true)][string]$KnownHosts, [Parameter(Mandatory=$true)][string]$ReceiptRoot, [Parameter(Mandatory=$true)][string]$CandidateReceipt, - [Parameter(Mandatory=$true)][string]$CandidateReceiptSha256 + [Parameter(Mandatory=$true)][string]$CandidateReceiptSha256, + [Parameter(Mandatory=$true)][ValidateSet('arm64','x64')][string]$Arch ) $ErrorActionPreference='Stop' if($env:GITHUB_ACTIONS -ne 'true' -or $env:ORCA_ISOLATED_SSH_CI -ne '1'){throw 'Disposable CI only'} @@ -22,7 +23,7 @@ if($knownExisted){$priorKnown=[IO.File]::ReadAllBytes($knownPath)} try { $observed=(& git rev-parse HEAD).Trim() if($LASTEXITCODE -ne 0 -or $observed -ne $SourceCommit){throw 'Source checkout mismatch'} - if(!(Test-Path 'out\relay\win32-arm64\relay.js')){throw 'Build real relay artifacts before server provisioning'} + if(!(Test-Path "out\relay\win32-$Arch\relay.js")){throw 'Build real relay artifacts before server provisioning'} New-Item -ItemType Directory -Path $ReceiptRoot -Force | Out-Null New-Item -ItemType Directory -Path (Split-Path $knownPath) -Force | Out-Null $pinned=[IO.File]::ReadAllText($KnownHosts) @@ -35,13 +36,13 @@ try { $env:ORCA_RELAY_PATH=Join-Path $SourceRoot 'out\relay' $env:ORCA_SSH_PROBE_CONFIG=Join-Path $ReceiptRoot 'config.json' @{sourceCommit=$SourceCommit;observedSourceCommit=$observed;username=$Username;port=$Port;identityFile=$IdentityFile;candidateReceiptPath=$CandidateReceipt;candidateReceiptSha256=$CandidateReceiptSha256;receiptPath=(Join-Path $ReceiptRoot 'production-route.json')} | ConvertTo-Json | Set-Content $env:ORCA_SSH_PROBE_CONFIG - foreach($name in @('windows-arm-provider-route.test.ts','windows-provider-candidate.ts','windows-provider-repaint.ts','windows-provider-loaded-images.ts')) { + foreach($name in @('windows-provider-route.test.ts','windows-provider-candidate.ts','windows-provider-repaint.ts','windows-provider-loaded-images.ts')) { $destination=Join-Path $SourceRoot "src\main\ssh\$name" if(Test-Path -LiteralPath $destination){throw 'Diagnostic source destination already exists'} Copy-Item -LiteralPath (Join-Path $PSScriptRoot $name) -Destination $destination $copiedPaths.Add($destination) } - & node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/ssh/windows-arm-provider-route.test.ts --no-file-parallelism --reporter=verbose + & node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/ssh/windows-provider-route.test.ts --no-file-parallelism --reporter=verbose if($LASTEXITCODE -ne 0){throw 'Production SSH route qualification failed'} $result=Get-Content (Join-Path $ReceiptRoot 'production-route.json') -Raw | ConvertFrom-Json if(!$result.sameShellState -or !$result.cleanupVerified -or !$result.sourcePinRestored -or !$result.candidateAdmission.candidateOverride -or $result.repaint.koreanRows -ne 8 -or $result.repaint.latinRows -ne 8 -or !$result.repaint.exactRowsOnly -or !$result.repaint.provider.modules){throw 'Provider route cleanup/evidence incomplete'} diff --git a/config/ci/windows-ssh-provider/preview-ssh/preview-native-inputs.json b/config/ci/windows-ssh-provider/preview-ssh/preview-native-inputs-arm64.json similarity index 100% rename from config/ci/windows-ssh-provider/preview-ssh/preview-native-inputs.json rename to config/ci/windows-ssh-provider/preview-ssh/preview-native-inputs-arm64.json diff --git a/config/ci/windows-ssh-provider/preview-ssh/preview-native-inputs-x64.json b/config/ci/windows-ssh-provider/preview-ssh/preview-native-inputs-x64.json new file mode 100644 index 00000000000..7aa9c408889 --- /dev/null +++ b/config/ci/windows-ssh-provider/preview-ssh/preview-native-inputs-x64.json @@ -0,0 +1,96 @@ +{ + "release": "10.0.0.0p2-Preview", + "archiveSha256": "23f50f3458c4c5d0b12217c6a5ddfde0137210a30fa870e98b29827f7b43aba5", + "files": [ + { + "name": "libcrypto.dll", + "sha256": "4652e861c0335ee80a51306ceab75aa35c8865b235f97ce7dd5a0fd9dab44b5d", + "machine": "0x8664", + "certificateTableBytes": 10312 + }, + { + "name": "scp.exe", + "sha256": "ca014ddb0a3c058719e7061eb604de7dfe1f7732954792ac8176e6c1fc99b4a3", + "machine": "0x8664", + "certificateTableBytes": 10312 + }, + { + "name": "sftp-server.exe", + "sha256": "63462c6904943f5f32ca363065f2eb7e883ee308f40c7c1637a307a253522151", + "machine": "0x8664", + "certificateTableBytes": 10312 + }, + { + "name": "sftp.exe", + "sha256": "97271ea46fa2eeb5e9e22cd5e919931727a2a11f79993c1ef151b4f618d9fd22", + "machine": "0x8664", + "certificateTableBytes": 10296 + }, + { + "name": "ssh-add.exe", + "sha256": "b6fec08648deaf7a77b50bc34ea8ac17c3cf240d06d0f1bffc60bf56d6f914b1", + "machine": "0x8664", + "certificateTableBytes": 10312 + }, + { + "name": "ssh-agent.exe", + "sha256": "138df27c7a8c35fbadde6fee35592336b04e058f4690b03f459ad79edd68ab63", + "machine": "0x8664", + "certificateTableBytes": 10272 + }, + { + "name": "ssh-keygen.exe", + "sha256": "b51fdd26be0f7c83398d18e5354a0acb0406a9de25516791758fe63bbe3ae870", + "machine": "0x8664", + "certificateTableBytes": 10272 + }, + { + "name": "ssh-keyscan.exe", + "sha256": "32eb6a2b80443207a2481085582c0ed01bbddcfef4b1f3f2cc680aa16d0f3135", + "machine": "0x8664", + "certificateTableBytes": 10296 + }, + { + "name": "ssh-pkcs11-helper.exe", + "sha256": "0f1ee63b38af0a96670ffc3f1c5421c4fba678d96ab0485854b550467b987ff5", + "machine": "0x8664", + "certificateTableBytes": 10272 + }, + { + "name": "ssh-shellhost.exe", + "sha256": "f090cb45e3b9dd830201993fc274e75a9be2058c1d4e900e85eff334dfd5a84c", + "machine": "0x8664", + "certificateTableBytes": 10296 + }, + { + "name": "ssh-sk-helper.exe", + "sha256": "4550082d459bccc5baf13cfe43c36c1e484bb012c4e0efb3990ae33a79e71c96", + "machine": "0x8664", + "certificateTableBytes": 10312 + }, + { + "name": "ssh.exe", + "sha256": "6890c128c86cc2c38aad9fcb32a82b851ff3d38c714a1f656b8e445d7cd5e1c6", + "machine": "0x8664", + "certificateTableBytes": 10296 + }, + { + "name": "sshd-auth.exe", + "sha256": "71b11418681e1ae8a3eb84494658f4ca66a7dac7ccc7674f3c74a36a3a5b7d14", + "machine": "0x8664", + "certificateTableBytes": 10272 + }, + { + "name": "sshd-session.exe", + "sha256": "5b28ad2046596454bd1e0b1ab19e8d66945def1d18ec9bec6f0ef538600a03cf", + "machine": "0x8664", + "certificateTableBytes": 10296 + }, + { + "name": "sshd.exe", + "sha256": "d66150486d472b8748cae2d6f5078a210dae91621447974bde028f077a4ef90c", + "machine": "0x8664", + "certificateTableBytes": 10272 + } + ] +} \ No newline at end of file diff --git a/config/ci/windows-ssh-provider/preview-ssh/prove-preview-openssh.ps1 b/config/ci/windows-ssh-provider/preview-ssh/prove-preview-openssh.ps1 index 4cc2f764989..759c33ecb02 100644 --- a/config/ci/windows-ssh-provider/preview-ssh/prove-preview-openssh.ps1 +++ b/config/ci/windows-ssh-provider/preview-ssh/prove-preview-openssh.ps1 @@ -1,7 +1,8 @@ # Ephemeral CI only. Preview ZIP server qualification, not inbox capability coverage. -param([Parameter(Mandatory=$true)][string]$Receipt,[Parameter(Mandatory=$true)][string]$Archive,[scriptblock]$ProductionRouteProbe) +param([Parameter(Mandatory=$true)][string]$Receipt,[Parameter(Mandatory=$true)][string]$Archive,[Parameter(Mandatory=$true)][ValidateSet('arm64','x64')][string]$Arch,[scriptblock]$ProductionRouteProbe) $ErrorActionPreference = 'Stop' -$report = @{scope='Microsoft Win32-OpenSSH 10.0.0.0p2-Preview ARM64 private loopback authentication and stock cmd.exe dispatch; NOT inbox server or relay deployment'; status='running'; imageVersion=$env:ImageVersion; cleanup=@('not-confirmed'); globalBootstrapCleanup='Not qualified: service bootstrap may create ProgramData SSH and OpenSSH registry entries; disposable CI VM destruction is the boundary'; observations=@(); stages=@(); diagnosticCaptureFailures=@()} +$target=@{arm64=@{os='Arm64';folder='OpenSSH-ARM64';machine='0xAA64';archive='698c6aec31c1dd0fb996206e8741f4531a97355686b5431ef347d531b07fcd42'};x64=@{os='X64';folder='OpenSSH-Win64';machine='0x8664';archive='23f50f3458c4c5d0b12217c6a5ddfde0137210a30fa870e98b29827f7b43aba5'}}[$Arch] +$report = @{scope='Microsoft Win32-OpenSSH 10.0.0.0p2-Preview $Arch private loopback authentication and stock cmd.exe dispatch; NOT inbox server or relay deployment'; status='running'; imageVersion=$env:ImageVersion; cleanup=@('not-confirmed'); globalBootstrapCleanup='Not qualified: service bootstrap may create ProgramData SSH and OpenSSH registry entries; disposable CI VM destruction is the boundary'; observations=@(); stages=@(); diagnosticCaptureFailures=@()} $script:receiptWritten=$false function Write-Stage([string]$Stage) { $timestamp=[DateTime]::UtcNow.ToString('o') @@ -18,7 +19,7 @@ function Write-Stage([string]$Stage) { } Write-Stage 'preflight-start' if(-not $script:receiptWritten){throw 'Initial progress receipt unavailable; refuse provisioning'} -if ($env:GITHUB_ACTIONS -ne 'true' -or $env:ORCA_ISOLATED_SSH_CI -ne '1' -or [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() -ne 'Arm64') { throw 'Requires isolated native ARM64 GitHub runner' } +if ($env:GITHUB_ACTIONS -ne 'true' -or $env:ORCA_ISOLATED_SSH_CI -ne '1' -or [Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() -ne $target.os) { throw "Requires isolated native $Arch GitHub runner" } $admin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $admin) { throw 'Administrative private service/account setup required' } Write-Stage 'existing-server-query-start' @@ -37,7 +38,7 @@ New-Item -ItemType Directory -Path $root | Out-Null Write-Stage 'private-directory-create-complete' $report.root=$root $createdUser=$false; $createdService=$false; $sid=$null; $ownedServerPid=$null -$sshDir=Join-Path $root 'OpenSSH-ARM64' +$sshDir=Join-Path $root $target.folder $sshdLog=Join-Path $root 'private-sshd.log' $serviceStartAttempt=$null function Diagnostic-Categories([string]$Text) { @@ -119,7 +120,7 @@ function Machine([string]$Path){ } try { Write-Stage 'preview-archive-verify-start' - $expectedArchive='698c6aec31c1dd0fb996206e8741f4531a97355686b5431ef347d531b07fcd42' + $expectedArchive=$target.archive if((Get-FileHash -LiteralPath $Archive -Algorithm SHA256).Hash.ToLowerInvariant() -ne $expectedArchive){throw 'Preview archive hash mismatch'} $report.archiveSha256=$expectedArchive Write-Stage 'preview-archive-verify-complete' @@ -128,7 +129,7 @@ try { [IO.Compression.ZipFile]::ExtractToDirectory($Archive,$root) Write-Stage 'preview-extract-complete' Write-Stage 'preview-native-input-verification-start' - $manifest=Get-Content -LiteralPath (Join-Path $PSScriptRoot 'preview-native-inputs.json') -Raw | ConvertFrom-Json + $manifest=Get-Content -LiteralPath (Join-Path $PSScriptRoot "preview-native-inputs-$Arch.json") -Raw | ConvertFrom-Json if($manifest.archiveSha256 -ne $expectedArchive -or $manifest.files.Count -ne 15){throw 'Preview input manifest mismatch'} $nativeFiles=@(Get-ChildItem -LiteralPath $sshDir -File | Where-Object {$_.Extension -in @('.exe','.dll')}) if($nativeFiles.Count -ne $manifest.files.Count){throw 'Unexpected preview native input count'} @@ -136,10 +137,10 @@ try { foreach($file in $nativeFiles){ $expected=@($manifest.files | Where-Object name -eq $file.Name) if($expected.Count -ne 1 -or (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant() -ne $expected[0].sha256){throw 'Preview native input hash mismatch'} - if((Machine $file.FullName) -ne '0xAA64'){throw 'Preview native input is not ARM64'} + if((Machine $file.FullName) -ne $target.machine){throw "Preview native input is not $Arch"} $signature=Get-AuthenticodeSignature -LiteralPath $file.FullName if($signature.Status -ne 'Valid' -or $signature.SignerCertificate.Subject -notmatch '(?:^|, )O=Microsoft Corporation(?:,|$)'){throw 'Preview native input Microsoft signature invalid'} - $verified+=@{name=$file.Name;sha256=$expected[0].sha256;machine='0xAA64';signature='Valid';publisher=$signature.SignerCertificate.Subject} + $verified+=@{name=$file.Name;sha256=$expected[0].sha256;machine=$target.machine;signature='Valid';publisher=$signature.SignerCertificate.Subject} } $report.nativeInputs=$verified Write-Stage 'preview-native-input-verification-complete' diff --git a/config/ci/windows-ssh-provider/windows-provider-candidate.ts b/config/ci/windows-ssh-provider/windows-provider-candidate.ts index 348646d1680..fe979cb79c1 100644 --- a/config/ci/windows-ssh-provider/windows-provider-candidate.ts +++ b/config/ci/windows-ssh-provider/windows-provider-candidate.ts @@ -24,17 +24,19 @@ export function installCandidateOverride(config: Record, state: assert.equal(receipt.patch, '276f475c90c6761c58b9f56b3f4bfafa079d0c29c5861b23d2320c9ff39c35fb') assert.equal(receipt.product, '2084c58ba5410106ce61153a9fb16cdb4b6e5301') assert.equal(String(receipt.producerRun), '36503596770') - assert.equal(receipt.architecture, 'arm64') + assert(process.arch === 'arm64' || process.arch === 'x64') + assert.equal(receipt.architecture, process.arch) + const platform = `win32-${process.arch}` as const assert(typeof receipt.binary === 'string' && /^[a-f0-9]{64}$/.test(receipt.sha256)) assert.equal( createHash('sha256').update(readFileSync(receipt.binary)).digest('hex'), receipt.sha256 ) - const cache = join(state, 'orcad-artifacts', 'bun', `v${ORCAD_BUN_VERSION}`, 'win32-arm64') + const cache = join(state, 'orcad-artifacts', 'bun', `v${ORCAD_BUN_VERSION}`, platform) mkdirSync(cache, { recursive: true }) copyFileSync(receipt.binary, join(cache, 'bun-runtime.exe')) - const original = ORCAD_BUN_RELEASE_ASSETS['win32-arm64'].executableSha256 - ORCAD_BUN_RELEASE_ASSETS['win32-arm64'].executableSha256 = receipt.sha256 + const original = ORCAD_BUN_RELEASE_ASSETS[platform].executableSha256 + ORCAD_BUN_RELEASE_ASSETS[platform].executableSha256 = receipt.sha256 return { receipt: { ...receipt, @@ -45,7 +47,7 @@ export function installCandidateOverride(config: Record, state: scope: 'diagnostic process only; private cache; production deployment validation retained' }, restore: () => { - ORCAD_BUN_RELEASE_ASSETS['win32-arm64'].executableSha256 = original + ORCAD_BUN_RELEASE_ASSETS[platform].executableSha256 = original } } } diff --git a/config/ci/windows-ssh-provider/windows-provider-loaded-images.ts b/config/ci/windows-ssh-provider/windows-provider-loaded-images.ts index c5a272c9a72..973e5db789a 100644 --- a/config/ci/windows-ssh-provider/windows-provider-loaded-images.ts +++ b/config/ci/windows-ssh-provider/windows-provider-loaded-images.ts @@ -6,6 +6,9 @@ import { runProcess } from '../../shared/child-process/run-process' import { readWindowsProcessTableFresh } from '../windows/windows-process-table' import { WINDOWS_CONPTY_FILES } from '../../shared/windows-conpty-release' +// Hashes for the runner's own architecture; the harness never cross-deploys. +const providerHashes = WINDOWS_CONPTY_FILES[process.arch === 'x64' ? 'x64' : 'arm64'] + export async function inspectProviderImages( daemon: { pid: number; creationTimeMs?: number }, relayDirectory: string @@ -90,12 +93,12 @@ export async function inspectProviderImages( ) assert.equal( createHash('sha256').update(readFileSync(image.path)).digest('hex'), - WINDOWS_CONPTY_FILES.arm64['OpenConsole.exe'] + providerHashes['OpenConsole.exe'] ) } assert.equal( createHash('sha256').update(readFileSync(evidence.modules[0])).digest('hex'), - WINDOWS_CONPTY_FILES.arm64['conpty.dll'] + providerHashes['conpty.dll'] ) const after = await readWindowsProcessTableFresh() for (const original of [daemon, ...consoles]) { @@ -112,5 +115,5 @@ export async function inspectProviderImages( assert(typeof row.creationTimeMs === 'number', 'descendant cleanup identity unavailable') return { pid: row.pid, creationTimeMs: row.creationTimeMs } }) - return { ...evidence, providerHashes: WINDOWS_CONPTY_FILES.arm64, daemon, descendantIdentities } + return { ...evidence, providerHashes, daemon, descendantIdentities } } diff --git a/config/ci/windows-ssh-provider/windows-arm-provider-route.test.ts b/config/ci/windows-ssh-provider/windows-provider-route.test.ts similarity index 98% rename from config/ci/windows-ssh-provider/windows-arm-provider-route.test.ts rename to config/ci/windows-ssh-provider/windows-provider-route.test.ts index 36d9a3ce966..8caef465439 100644 --- a/config/ci/windows-ssh-provider/windows-arm-provider-route.test.ts +++ b/config/ci/windows-ssh-provider/windows-provider-route.test.ts @@ -274,13 +274,14 @@ it('does not retain unknown identifiers, paths, numeric source echoes or incompl }) const configPath = process.env.ORCA_SSH_PROBE_CONFIG it( - 'native ARM OpenSSH candidate provider preserves all settled rows and shell state', + 'native OpenSSH candidate provider preserves all settled rows and shell state', { timeout: 600_000 }, async () => { if (!configPath || !process.env.ORCA_SSH_PROBE_STATE) throw new Error('Explicit private SSH fixture configuration is required') expect(process.platform).toBe('win32') - expect(process.arch).toBe('arm64') + expect(['arm64', 'x64']).toContain(process.arch) + const platform = `win32-${process.arch}` as const const config = record(JSON.parse(readFileSync(configPath!, 'utf8'))) const source = textField(config, 'sourceCommit') expect(source).toMatch(/^[a-f0-9]{40}$/) @@ -289,7 +290,7 @@ it( const port = config.port if (typeof port !== 'number' || !Number.isInteger(port)) throw new Error('Invalid private SSH port') - const instance = `arm-native-${randomUUID()}` + const instance = `${process.arch}-native-${randomUUID()}` const createConnection = (): SshConnection => new SshConnection( { @@ -347,7 +348,7 @@ it( instance ) stage = 'artifact-and-runtime-identity' - expect(result.platform).toBe('win32-arm64') + expect(result.platform).toBe(platform) expect(result.nodePath?.toLowerCase()).toContain('bun') if (!result.remoteRelayDir) throw new Error('Missing actual remote relay directory') const artifactHashes: Record = {} @@ -358,7 +359,7 @@ it( ...RELAY_WINDOWS_CONPTY_FILENAMES ]) { const expected = createHash('sha256') - .update(readFileSync(join(process.cwd(), 'out', 'relay', 'win32-arm64', filename))) + .update(readFileSync(join(process.cwd(), 'out', 'relay', platform, filename))) .digest('hex') const actual = createHash('sha256') .update(readFileSync(join(result.remoteRelayDir, filename))) @@ -370,7 +371,7 @@ it( const runtimePath = result.nodePath if (!runtimePath) throw new Error('Missing actual runtime executable') const runtimeHash = createHash('sha256').update(readFileSync(runtimePath)).digest('hex') - expect(runtimeHash).toBe(ORCAD_BUN_RELEASE_ASSETS['win32-arm64'].executableSha256) + expect(runtimeHash).toBe(ORCAD_BUN_RELEASE_ASSETS[platform].executableSha256) remoteRelayDirectory = result.remoteRelayDir deployedRuntime = runtimePath receipts.runtime = {