#requires -Version 7.0 param([Parameter(Mandatory=$true)][string]$ServerExe) $ErrorActionPreference = 'Stop' $taskRoot = $PSScriptRoot $exe = (Resolve-Path -LiteralPath $ServerExe).Path $runRoot = Join-Path $taskRoot 'rest-strict' $storePath = Join-Path $runRoot 'store' New-Item -ItemType Directory -Path $runRoot,$storePath | Out-Null $payload = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'coverage-probe.json') -Raw | ConvertFrom-Json -AsHashtable $testToken = 'release-coverage-probe-20260906-local-only' $tokenPath = Join-Path $runRoot 'synthetic-token.txt' [IO.File]::WriteAllText($tokenPath,$testToken) $listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback,0) $listener.Start() $probePort = $listener.LocalEndpoint.Port $listener.Stop() $base = "http://127.0.0.1:$probePort" $script:probeProcess = $null $script:sequence = 0 $script:launch = 0 $codes = @($payload.expected | ForEach-Object { $_.code }) function Assert-Probe($condition,[string]$message) { if (-not $condition) { throw $message } } function Start-ProbeServer { $script:launch++ # PowerShell startup prepends PSHOME; reset only this process before launching. $env:PATH = 'C:/Windows/System32;C:/Windows;C:\Users\User\Documents\Git\ARCADIA-Studio\.local\native-865483b\extracted\ArcadiaStudio-0.2.0-preview.lifecycle-childwork.20260911-x86_64-pc-windows-gnullvm' $startOptions = @{ WorkingDirectory = Join-Path $PSScriptRoot 'unrelated-cwd' FilePath = $exe ArgumentList = @('--bind',"127.0.0.1:$probePort",'--data-dir',('"'+$storePath+'"'),'--token-file',('"'+$tokenPath+'"')) PassThru = $true RedirectStandardOutput = Join-Path $runRoot "server-$script:launch.stdout.log" RedirectStandardError = Join-Path $runRoot "server-$script:launch.stderr.log" } # WindowStyle is Windows-only; Unix keeps the child attached to this probe. if ($IsWindows) { $startOptions.WindowStyle = 'Hidden' } $script:probeProcess = Start-Process @startOptions @{pid=$script:probeProcess.Id;exe=$exe;arguments=$startOptions.ArgumentList;cwd=$startOptions.WorkingDirectory;PATH=$env:PATH;started=(Get-Date -Format o)} | ConvertTo-Json -Depth 10 | Set-Content (Join-Path $runRoot "server-$script:launch.launch.json") for ($attempt = 0; $attempt -lt 40; $attempt++) { if ($script:probeProcess.HasExited) { throw 'Probe server exited before readiness' } try { $ready=Invoke-WebRequest "$base/health/ready" -TimeoutSec 1; if ($ready.StatusCode -eq 200) { return } } catch {} Start-Sleep -Milliseconds 250 } throw 'Probe server did not become ready' } function Stop-ProbeServer { if ($null -ne $script:probeProcess -and -not $script:probeProcess.HasExited) { Stop-Process -InputObject $script:probeProcess $script:probeProcess.WaitForExit() } if ($null -ne $script:probeProcess) { @{pid=$script:probeProcess.Id;has_exited=$script:probeProcess.HasExited;exit_code=$script:probeProcess.ExitCode;stopped=(Get-Date -Format o);method='owned probe Stop-Process after assertions'} | ConvertTo-Json | Set-Content (Join-Path $runRoot "server-$script:launch.stop.json") } $script:probeProcess = $null } function Call-Probe([string]$name,[string]$method,[string]$path,$body=$null,[string]$etag='',[int]$expectedStatus=200) { $script:sequence++ $stem = Join-Path $runRoot ('{0:D2}-{1}' -f $script:sequence,$name) $headers = @{Authorization="Bearer $testToken"} if ($etag) { $headers['If-Match']=$etag } $request = @{Uri="$base$path";Method=$method;Headers=$headers;SkipHttpErrorCheck=$true;TimeoutSec=40} if ($null -ne $body) { $json=ConvertTo-Json -InputObject $body -Depth 100 -Compress [IO.File]::WriteAllText("$stem.request.json",$json) $request.ContentType='application/json' $request.Body=[Text.Encoding]::UTF8.GetBytes($json) } $response=Invoke-WebRequest @request $content=[string]$response.Content [IO.File]::WriteAllText("$stem.response.txt",$content) $tag=[string]($response.Headers.ETag | Select-Object -First 1) @{method=$method;path=$path;status=[int]$response.StatusCode;etag=$tag} | ConvertTo-Json | Set-Content -LiteralPath "$stem.meta.json" Assert-Probe ($response.StatusCode -eq $expectedStatus) "$name expected $expectedStatus; got $($response.StatusCode): $content" $data=$null try { $data=ConvertFrom-Json -InputObject $content -AsHashtable } catch {} return @{content=$content;data=$data;etag=$tag;headers=$response.Headers} } function Tuples($issues) { return (@($issues | Where-Object { $_.code -in $codes } | ForEach-Object { "$($_.code)|$($_.object_id)|$($_.field)" } | Sort-Object) -join "`n") } function Assert-Coverage($response,[bool]$located=$false) { $found=@($response.data.issues | Where-Object { $_.code -in $codes }) Assert-Probe ((Tuples $found) -ceq (Tuples $payload.expected)) 'Five exact coverage tuples differ' Assert-Probe (@($response.data.issues | Where-Object severity -eq 'error').Count -eq 0) 'Unexpected structural error' foreach ($issue in $found) { Assert-Probe ($issue.severity -eq 'warning') 'Coverage is not warning severity' if ($located) { Assert-Probe ($issue.path -eq 'work.arcadia' -and $issue.line -ge 1 -and $issue.column -ge 1 -and $null -ne $issue.span) 'Missing submitted-source location' } } } function Assert-Bundle($actual,$expected) { Assert-Probe ($actual.entry -ceq $expected.entry -and $actual.files.Count -eq $expected.files.Count) 'Bundle entry/file count changed' foreach ($key in $expected.files.Keys) { Assert-Probe ($actual.files[$key] -ceq $expected.files[$key]) "Source bytes changed: $key" } } try { @{exe=$exe;sha256=(Get-FileHash -LiteralPath $exe -Algorithm SHA256).Hash;port=$probePort;started=(Get-Date -Format o)} | ConvertTo-Json | Set-Content (Join-Path $runRoot 'run.json') Start-ProbeServer $created=Call-Probe 'create' 'POST' '/api/v1/projects' @{name='Coverage probe host'} '' 201 $path=[string]($created.headers.Location | Select-Object -First 1) Assert-Probe ($path -like '/api/v1/projects/*') 'Create omitted resource Location' $before=Call-Probe 'before-model' 'GET' "$path/model" $beforeBundle=Call-Probe 'before-bundle' 'GET' "$path/source-project" $candidate=Call-Probe 'incomplete-candidate' 'POST' "$path/validation" @{format='source-project';source_project=$payload.incomplete} Assert-Probe $candidate.data.valid 'Incomplete model should be valid' Assert-Coverage $candidate $true $after=Call-Probe 'candidate-no-commit-model' 'GET' "$path/model" $afterBundle=Call-Probe 'candidate-no-commit-bundle' 'GET' "$path/source-project" Assert-Probe ($after.content -ceq $before.content -and $after.etag -ceq $before.etag) 'Candidate changed accepted model/revision' Assert-Bundle $afterBundle.data $beforeBundle.data $committed=Call-Probe 'commit-incomplete' 'PUT' "$path/source-project" $payload.incomplete $before.etag Assert-Coverage $committed $true Assert-Probe ($committed.data.changed -and $committed.etag -cne $before.etag) 'Warning-only commit did not advance revision' $model=Call-Probe 'incomplete-model' 'GET' "$path/model" $bundle=Call-Probe 'incomplete-bundle' 'GET' "$path/source-project" Assert-Bundle $bundle.data $payload.incomplete $source=Call-Probe 'incomplete-canonical' 'GET' "$path/source" $validation=Call-Probe 'incomplete-validation' 'GET' "$path/validation" Assert-Coverage $validation $jsonCheck=Call-Probe 'json-parity' 'POST' "$path/validation" @{format='json';model=$model.data} $textCheck=Call-Probe 'text-parity' 'POST' "$path/validation" @{format='arcadia';source=$source.content} Assert-Coverage $jsonCheck Assert-Coverage $textCheck Assert-Probe ($jsonCheck.data.valid -and $textCheck.data.valid) 'JSON/text parity candidate invalid' $invalid=Call-Probe 'invalid-candidate' 'POST' "$path/validation" @{format='source-project';source_project=$payload.invalid} Assert-Probe (-not $invalid.data.valid -and @($invalid.data.issues | Where-Object { $_.code -eq 'invalid_endpoints' -and $_.severity -eq 'error' }).Count -ge 1) 'Invalid endpoint not diagnosed' Assert-Probe ((Tuples $invalid.data.issues) -eq '') 'Structural error did not gate coverage' $null=Call-Probe 'invalid-write-rejected' 'PUT' "$path/source-project" $payload.invalid $model.etag 422 $unchanged=Call-Probe 'rejection-model' 'GET' "$path/model" $unchangedBundle=Call-Probe 'rejection-bundle' 'GET' "$path/source-project" Assert-Probe ($unchanged.content -ceq $model.content -and $unchanged.etag -ceq $model.etag) 'Rejected write changed model/revision' Assert-Bundle $unchangedBundle.data $bundle.data $fixed=Call-Probe 'repair-write' 'PUT' "$path/source-project" $payload.repaired $model.etag Assert-Probe ((Tuples $fixed.data.issues) -eq '') 'Repair left coverage warnings' $fixedModel=Call-Probe 'repaired-model' 'GET' "$path/model" $fixedBundle=Call-Probe 'repaired-bundle' 'GET' "$path/source-project" Assert-Bundle $fixedBundle.data $payload.repaired $fixedValidation=Call-Probe 'repaired-validation' 'GET' "$path/validation" Stop-ProbeServer Start-ProbeServer $reopened=Call-Probe 'restart-model' 'GET' "$path/model" $reopenedBundle=Call-Probe 'restart-bundle' 'GET' "$path/source-project" $reopenedValidation=Call-Probe 'restart-validation' 'GET' "$path/validation" Assert-Probe ($reopened.content -ceq $fixedModel.content -and $reopened.etag -ceq $fixedModel.etag) 'Restart changed model/revision' Assert-Bundle $reopenedBundle.data $fixedBundle.data Assert-Probe ($reopenedValidation.content -ceq $fixedValidation.content) 'Restart changed validation' $again=Call-Probe 'restore-incomplete-by-replacement' 'PUT' "$path/source-project" $payload.incomplete $reopened.etag Assert-Coverage $again $true @{result='passed';requests=$script:sequence;resource=$path;initial_revision=$committed.data.revision;repaired_revision=$fixed.data.revision;final_revision=$again.data.revision;repair_issues=$fixed.data.issues;evidence=$runRoot} | ConvertTo-Json -Depth 20 | Set-Content (Join-Path $runRoot 'result.json') Get-Content (Join-Path $runRoot 'result.json') } catch { $_ | Out-String | Set-Content (Join-Path $runRoot 'failure.txt') throw } finally { Stop-ProbeServer }