# Native Windows PowerShell 5.1 contracts, without entering any distribution. param([string] $Source = (Join-Path (Split-Path $PSScriptRoot -Parent) 'scripts/windows/openai4s.ps1')) $ErrorActionPreference = 'Stop' $parseTokens = $null $parseErrors = $null $ast = [System.Management.Automation.Language.Parser]::ParseFile($Source, [ref]$parseTokens, [ref]$parseErrors) if ($parseErrors.Count) { throw ($parseErrors | Out-String) } $ast.FindAll({ param($node) $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -in @('Select-Distro', 'Get-WslBootstrapArgs', 'ConvertTo-NativeArgument', 'Start-Bootstrap', 'Show-RunningBuild', 'Assert-AppPage', 'Invoke-WslCaptureNative', 'Get-WslArkCliPath', 'Enter-Utf8NativeOutput', 'Exit-Utf8NativeOutput', 'Invoke-Bootstrap') }, $false) | ForEach-Object { Invoke-Expression $_.Extent.Text } function Stop-WithGuidance([string]$Message, $Lines) { throw $Message } function Test-DistroHasInstall($Name) { return $Name -eq 'Ubuntu-existing' } $env:OPENAI4S_WSL_DISTRO = '' function Get-WslDistros { @([pscustomobject]@{Name='docker-desktop';Version=2;IsDefault=$true}) } $refused = $false try { Select-Distro | Out-Null } catch { $refused = $_.Exception.Message -match 'no user-owned' } if (-not $refused) { throw 'infrastructure-only list was not refused' } function Get-WslDistros { @( [pscustomobject]@{Name='docker-desktop';Version=2;IsDefault=$true}, [pscustomobject]@{Name='Ubuntu-24.04';Version=2;IsDefault=$false}, [pscustomobject]@{Name='Ubuntu-existing';Version=2;IsDefault=$false} ) } if ((Select-Distro) -ne 'Ubuntu-existing') { throw 'existing user data lost selection priority' } $env:OPENAI4S_WSL_DISTRO = 'docker-desktop' $refused = $false try { Select-Distro | Out-Null } catch { $refused = $_.Exception.Message -match 'not installed' } if (-not $refused) { throw 'explicit infrastructure distro was not refused' } $normal = @(Get-WslBootstrapArgs 'Ubuntu' '/package with spaces/bootstrap.sh' @('preflight')) if ($normal -contains '-u') { throw 'normal launch must not switch users' } $prepare = @(Get-WslBootstrapArgs 'Ubuntu' '/package with spaces/bootstrap.sh' @('prepare') 'root') if ($prepare[2] -ne '-u' -or $prepare[3] -ne 'root') { throw 'preparation did not select root explicitly' } if ($prepare[-2] -ne '/package with spaces/bootstrap.sh') { throw 'bootstrap path lost argv boundaries' } if ((ConvertTo-NativeArgument 'two words') -ne '"two words"') { throw 'native argv lost spaces' } if ((ConvertTo-NativeArgument 'a"b\') -ne '"a\"b\\"') { throw 'native argv lost quote/backslash escaping' } if ((ConvertTo-NativeArgument '') -ne '""') { throw 'native argv lost an empty argument' } if ((ConvertTo-NativeArgument '-d') -ne '-d') { throw 'WSL switch must not be quoted' } function Start-Process { param($FilePath, $ArgumentList, $WindowStyle, [switch]$PassThru) if ($FilePath -ne 'wsl.exe' -or $WindowStyle -ne 'Hidden' -or -not $PassThru) { throw 'server must retain a hidden WSL process' } if ($ArgumentList -notlike '*"/package with spaces/bootstrap.sh"*') { throw 'background launch lost bootstrap path boundaries' } return [pscustomobject]@{ HasExited = $false } } Start-Bootstrap 'Ubuntu' '/package with spaces/bootstrap.sh' @('serve', 'current') | Out-Null # Invoke-WslCaptureNative folds stderr into Output on purpose, so a status # capture is not a JSON document: wsl.exe's NAT localhost-proxy warning must not # be read as "a different build is running". $script:told = @() function Write-Host { param($Object, $ForegroundColor) $script:told += [string] $Object } function Invoke-BootstrapCapture { param($Distro, $BootstrapLinux, $BootstrapArgs) return [pscustomobject]@{ ExitCode = 0 Lines = @( 'wsl: Detected localhost proxy configuration.', '{"running":true,"pid":42,"version":"9.9.9","bundle_id":"beef"}' ) } } Show-RunningBuild 'Ubuntu' '/b.sh' 'OpenAI4S-9.9.9' 'beef' if ($script:told.Count -ne 0) { throw 'a warning line was read as a different build' } Show-RunningBuild 'Ubuntu' '/b.sh' 'OpenAI4S-9.9.9' 'cafe' if ($script:told.Count -eq 0) { throw 'a genuinely older build was not reported' } if ((($script:told) -join ' ') -notmatch 'version 9\.9\.9') { throw 'the running version was lost' } # The page check must not go through the machine's WinINET proxy, and the team # sign-in page is the application answering too. $script:proxyDuringCall = 'unset' [System.Net.WebRequest]::DefaultWebProxy = New-Object System.Net.WebProxy('http://127.0.0.1:7897') $script:sentinel = [System.Net.WebRequest]::DefaultWebProxy function Invoke-WebRequest { param($Uri, $TimeoutSec, [switch]$UseBasicParsing) $script:proxyDuringCall = [System.Net.WebRequest]::DefaultWebProxy return [pscustomobject]@{ StatusCode = 200; Content = 'OpenAI4S — sign in' } } Assert-AppPage 'http://172.20.0.2:8760/?token=synthetic' if ($null -ne $script:proxyDuringCall) { throw 'the page check went through the system proxy' } if ([System.Net.WebRequest]::DefaultWebProxy -ne $script:sentinel) { throw 'the system proxy was not restored' } # Exercise actual native UTF-8 bytes under an OEM console encoding. This runs # in Windows CI without WSL and catches the failure a mocked string cannot. $testRoot = Join-Path ([IO.Path]::GetTempPath()) ('openai4s-native-' + [Guid]::NewGuid().ToString('N')) $savedPath = $env:PATH $savedEncoding = [Console]::OutputEncoding $savedArkCli = $env:OPENAI4S_ARKCLI_PATH try { New-Item -ItemType Directory -Path $testRoot | Out-Null $env:PATH = $testRoot + ';' + $savedPath # PowerShell 6+ cannot compile a console application with Add-Type, so the # native fixture is Windows PowerShell only -- the host OpenAI4S.cmd runs. if ($PSVersionTable.PSEdition -eq 'Desktop') { Add-Type -OutputAssembly (Join-Path $testRoot 'wsl.exe') -OutputType ConsoleApplication -TypeDefinition @' using System; using System.Text; public class WslFixture { public static int Main(string[] args) { byte[] bytes = Encoding.UTF8.GetBytes("/mnt/c/Users/\u4e2d\u6587 folder\n"); Console.OpenStandardOutput().Write(bytes, 0, bytes.Length); byte[] warning = Encoding.UTF8.GetBytes("wsl: harmless diagnostic\n"); Console.OpenStandardError().Write(warning, 0, warning.Length); return args.Length > 0 && args[0] == "fail" ? 7 : 0; } } '@ [Console]::OutputEncoding = [Text.Encoding]::GetEncoding(437) $expectedPath = '/mnt/c/Users/' + [char]0x4e2d + [char]0x6587 + ' folder' foreach ($mode in @('ok', 'fail')) { $result = Invoke-WslCaptureNative @($mode) $expectedCode = if ($mode -eq 'fail') { 7 } else { 0 } if ($result.ExitCode -ne $expectedCode) { throw 'native exit code was lost' } if ($result.Output -notcontains $expectedPath) { throw 'native UTF-8 path was corrupted' } if ([Console]::OutputEncoding.CodePage -ne 437) { throw 'console encoding was not restored' } if ($ErrorActionPreference -ne 'Stop') { throw 'error preference was not restored' } } # The streaming path (preflight, install, CLI passthrough) decodes too. $script:told = @() if ((Invoke-Bootstrap 'Ubuntu' '/b.sh' @('preflight')) -ne 0) { throw 'streamed exit code was lost' } if ($script:told -notcontains $expectedPath) { throw 'streamed native UTF-8 path was corrupted' } if ([Console]::OutputEncoding.CodePage -ne 437) { throw 'console encoding was not restored after streaming' } if ($ErrorActionPreference -ne 'Stop') { throw 'error preference was not restored after streaming' } } else { Write-Output "PowerShell $($PSVersionTable.PSVersion): native console fixture skipped (Windows PowerShell only)" } # Get-Command resolves an Application by name and PATHEXT, not by content. New-Item -ItemType File -Path (Join-Path $testRoot 'arkcli.exe') | Out-Null New-Item -ItemType File -Path (Join-Path $testRoot 'arkcli.cmd') | Out-Null # Brackets are legal in a folder name and wildcards to Get-Command. $bracketCli = Join-Path (Join-Path $testRoot 'Tools [x64]') 'arkcli.exe' [IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($bracketCli)) | Out-Null [IO.File]::WriteAllBytes($bracketCli, [byte[]] @()) $script:translationFails = $false function ConvertTo-WslPath($Distro, $WindowsPath, [switch] $Optional) { if ($WindowsPath -notin @((Join-Path $testRoot 'arkcli.exe'), $bracketCli)) { throw 'wrong Windows CLI selected' } if (-not $Optional) { throw 'an Ark CLI translation failure must not use the package guidance' } if ($script:translationFails) { return '' } return '/windows tools/arkcli.exe' } $env:OPENAI4S_ARKCLI_PATH = '' $WslArkCli = Get-WslArkCliPath 'Ubuntu' if ($WslArkCli -ne '/windows tools/arkcli.exe') { throw 'Windows PATH CLI was not discovered' } $forwarded = @(Get-WslBootstrapArgs 'Ubuntu' '/b.sh' @('serve')) # Discovered, not chosen: a CLI installed inside WSL must keep precedence. if ($forwarded -notcontains 'OPENAI4S_ARKCLI_FALLBACK_PATH=/windows tools/arkcli.exe') { throw 'CLI path lost argv boundary' } if ($forwarded -like 'OPENAI4S_ARKCLI_PATH=*') { throw 'a discovered Windows CLI overrode the WSL CLI' } function Start-Process { param($FilePath, $ArgumentList, $WindowStyle, [switch]$PassThru) $script:nativeLine = $ArgumentList return [pscustomobject]@{ HasExited = $false } } Start-Bootstrap 'Ubuntu' '/b.sh' @('serve') | Out-Null if ($script:nativeLine -notlike '*"OPENAI4S_ARKCLI_FALLBACK_PATH=/windows tools/arkcli.exe"*') { throw 'CLI path lost its native argv boundary' } # An optional CLI WSL cannot reach is skipped; a configured one is refused. $script:translationFails = $true if ((Get-WslArkCliPath 'Ubuntu') -ne '') { throw 'an unreachable PATH CLI was forwarded' } $env:OPENAI4S_ARKCLI_PATH = Join-Path $testRoot 'arkcli.exe' $refused = $false try { Get-WslArkCliPath 'Ubuntu' | Out-Null } catch { $refused = $_.Exception.Message -match 'cannot reach' } if (-not $refused) { throw 'an unreachable configured CLI was not refused' } $script:translationFails = $false $env:OPENAI4S_ARKCLI_PATH = '/opt/ark/bin/arkcli' if ((Get-WslArkCliPath 'Ubuntu') -ne '/opt/ark/bin/arkcli') { throw 'explicit WSL path lost precedence' } $env:OPENAI4S_ARKCLI_PATH = Join-Path $testRoot 'arkcli.exe' $WslArkCli = Get-WslArkCliPath 'Ubuntu' if ($WslArkCli -ne '/windows tools/arkcli.exe') { throw 'explicit Windows path was not translated' } if (@(Get-WslBootstrapArgs 'Ubuntu' '/b.sh' @('serve')) -notcontains 'OPENAI4S_ARKCLI_PATH=/windows tools/arkcli.exe') { throw 'an explicit Windows CLI was not forwarded as the override' } $env:OPENAI4S_ARKCLI_PATH = $bracketCli if ((Get-WslArkCliPath 'Ubuntu') -ne '/windows tools/arkcli.exe') { throw 'a bracketed Windows path was read as a wildcard' } $npmDir = Join-Path $testRoot 'npm' [IO.Directory]::CreateDirectory($npmDir) | Out-Null [IO.File]::WriteAllBytes((Join-Path $npmDir 'arkcli.cmd'), [byte[]] @()) $env:PATH = $npmDir + ';' + $testRoot + ';' + $savedPath $env:OPENAI4S_ARKCLI_PATH = 'arkcli' if ((Get-WslArkCliPath 'Ubuntu') -ne '/windows tools/arkcli.exe') { throw 'an earlier npm .cmd hid arkcli.exe' } $env:PATH = $testRoot + ';' + $savedPath foreach ($invalid in @((Join-Path $testRoot 'missing\arkcli.exe'), (Join-Path $testRoot 'arkcli.cmd'), 'arkcli*', '*')) { $env:OPENAI4S_ARKCLI_PATH = $invalid $refused = $false try { Get-WslArkCliPath 'Ubuntu' | Out-Null } catch { $refused = $_.Exception.Message -match 'does not name' } if (-not $refused) { throw "an unusable configured CLI was accepted: $invalid" } } $env:OPENAI4S_ARKCLI_PATH = '' $env:PATH = Join-Path $testRoot 'no-such-dir' if ((Get-WslArkCliPath 'Ubuntu') -ne '') { throw 'a missing optional CLI was not ignored' } } finally { # Best effort, so a cleanup failure never replaces the assertion that failed. $env:PATH = $savedPath $env:OPENAI4S_ARKCLI_PATH = $savedArkCli [Console]::OutputEncoding = $savedEncoding $resolvedTestRoot = [IO.Path]::GetFullPath($testRoot) $tempRoot = [IO.Path]::GetFullPath([IO.Path]::GetTempPath()).TrimEnd('\') + '\' if ($resolvedTestRoot.StartsWith($tempRoot, [StringComparison]::OrdinalIgnoreCase)) { Remove-Item -LiteralPath $resolvedTestRoot -Recurse -Force -ErrorAction SilentlyContinue } } Write-Output "PowerShell $($PSVersionTable.PSVersion): launcher contracts passed"