#Requires -Version 7.0 param([Parameter(Mandatory=$true)][ValidateSet('list','focused','all','clippy','build')][string]$Stage) Set-StrictMode -Version Latest $ErrorActionPreference='Stop' $PSNativeCommandUseErrorActionPreference=$false $taskWorkspace='C:\Users\User\Documents\Git\ARCADIA-Studio\.local\scenario-workspace-ytr83r-20260913' $taskTarget='C:\Users\User\Documents\Git\ARCADIA-Studio\target' $taskPin='2c9cd2b3e661ef85f11590c7128dcb2f502d0c26' $toolchainEnv='C:\Users\User\Documents\Git\ARCADIA-Studio\.local\toolchain\env.ps1' function Hash-Input([string]$Path){(Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()} function Same-Names($Left,$Right){$a=@($Left|Sort-Object);$b=@($Right|Sort-Object);return $a.Count -eq $b.Count -and @(Compare-Object $a $b).Count -eq 0} $taskLog=Join-Path $PSScriptRoot ($Stage+'.log') $taskReceipt=Join-Path $PSScriptRoot ($Stage+'-receipt.json') if((Test-Path -LiteralPath $taskLog)-or(Test-Path -LiteralPath $taskReceipt)){throw 'Do not replace evidence; prepare a new gate directory for retries.'} $clearancePath=Join-Path $PSScriptRoot 'package-clearance.json' $clearance=Get-Content -Raw -LiteralPath $clearancePath|ConvertFrom-Json if($clearance.base -cne $taskPin -or $clearance.cargoExitCode -ne 0 -or [IO.Path]::GetFullPath($clearance.target) -ine $taskTarget -or -not $clearance.dependenciesRetained){throw 'Parent-owned successful current-pin package clearance is required.'} $selection=Get-Content -Raw -LiteralPath (Join-Path $PSScriptRoot 'expected-tests.json')|ConvertFrom-Json $expected=@($selection.tests|Sort-Object -Unique) if($selection.source_commit -cne $taskPin -or $selection.expected_count -ne 224 -or $expected.Count -ne 224 -or @($selection.tests).Count -ne 224){throw 'Wrong exact focused membership.'} $expectedCount=224 $commonFiles=@('run.ps1','capture.ps1','expected-tests.json','expected-inputs.json','enumeration.json','external-inputs.json','inputs-complete-before.json','package-clearance.json','server-provenance.json') $boundScripts=@{} foreach($file in $commonFiles){$boundScripts[$file]=Hash-Input (Join-Path $PSScriptRoot $file)} $previous=@{focused='list';all='focused';clippy='all';build='clippy'} $priorBinding=$null if($previous.ContainsKey($Stage)){ $priorName=$previous[$Stage] $priorPath=Join-Path $PSScriptRoot ($priorName+'-receipt.json') $prior=Get-Content -Raw -LiteralPath $priorPath|ConvertFrom-Json if($prior.stage -cne $priorName -or $prior.base -cne $taskPin -or $prior.workspace -ine $taskWorkspace -or $prior.target -ine $taskTarget -or $prior.cargoExitCode -ne 0 -or -not $prior.selectionValid -or -not $prior.inputsUnchanged){throw 'Exact prior sequential stage did not pass.'} foreach($file in $commonFiles){if($prior.boundScriptAndInputHashes.$file -cne $boundScripts[$file]){throw ('Prior recipe/input binding changed: '+$file)}} foreach($property in $prior.boundScriptAndInputHashes.PSObject.Properties){if((Hash-Input (Join-Path $PSScriptRoot $property.Name)) -cne $property.Value){throw ('Prior transitive binding changed: '+$property.Name)};$boundScripts[$property.Name]=$property.Value} if((Hash-Input (Join-Path $PSScriptRoot ($priorName+'.log'))) -cne $prior.rawLogSha256){throw 'Prior raw output changed.'} $priorBinding=[ordered]@{stage=$priorName;receiptSha256=(Hash-Input $priorPath);rawLogSha256=$prior.rawLogSha256} } Push-Location -LiteralPath $taskWorkspace try { & (Join-Path $PSScriptRoot 'capture.ps1') -Stage ($Stage+'-before') . $toolchainEnv $env:CARGO_TARGET_DIR=$taskTarget $env:CARGO_TERM_COLOR='never' $PSNativeCommandUseErrorActionPreference=$false $versionPath=Join-Path $PSScriptRoot 'toolchain-versions.json' if($Stage -eq 'list'){ if(Test-Path -LiteralPath $versionPath){throw 'Preserve prior toolchain version evidence.'} $cargoVersion=@(& cargo --version --verbose 2>&1);$cargoVersionExit=$LASTEXITCODE $rustcVersion=@(& rustc --version --verbose 2>&1);$rustcVersionExit=$LASTEXITCODE $clippyVersion=@(& cargo clippy --version 2>&1);$clippyVersionExit=$LASTEXITCODE $clangVersion=@(& clang --version 2>&1);$clangVersionExit=$LASTEXITCODE [ordered]@{base=$taskPin;capturedUtc=[DateTime]::UtcNow.ToString('o');externalEnvironmentPath=$toolchainEnv;externalEnvironmentSha256=(Hash-Input $toolchainEnv);cargo=@($cargoVersion|ForEach-Object {$_.ToString()});cargoExitCode=$cargoVersionExit;rustc=@($rustcVersion|ForEach-Object {$_.ToString()});rustcExitCode=$rustcVersionExit;clippy=@($clippyVersion|ForEach-Object {$_.ToString()});clippyExitCode=$clippyVersionExit;clang=@($clangVersion|ForEach-Object {$_.ToString()});clangExitCode=$clangVersionExit;cargoPath=(Get-Command cargo).Source;rustcPath=(Get-Command rustc).Source;clangPath=(Get-Command clang).Source;cargoHome=$env:CARGO_HOME;rustupHome=$env:RUSTUP_HOME;rustupToolchain=$env:RUSTUP_TOOLCHAIN;buildJobs=$env:CARGO_BUILD_JOBS;incremental=$env:CARGO_INCREMENTAL}|ConvertTo-Json -Depth 8|Set-Content -LiteralPath $versionPath -Encoding utf8 if($cargoVersionExit -ne 0 -or $rustcVersionExit -ne 0 -or $clippyVersionExit -ne 0 -or $clangVersionExit -ne 0){throw 'Toolchain version query failed.'} } $versionsHash=Hash-Input $versionPath if($priorBinding -and $prior.toolchainVersionsSha256 -cne $versionsHash){throw 'Toolchain version receipt changed.'} $taskArgs=switch($Stage){ 'list' {@('test','-p','oa-desktop','--all-targets','--locked','--offline','--','--list')} 'focused' {@('test','-p','oa-desktop','--bin','arcadia-studio','--locked','--offline','--','--exact')+$expected+@('--test-threads=1')} 'all' {@('test','-p','oa-desktop','--all-targets','--locked','--offline','--','--test-threads=1')} 'clippy' {@('clippy','-p','oa-desktop','--all-targets','--locked','--offline','--','-D','warnings')} 'build' {@('build','-p','oa-desktop','--bin','arcadia-studio','--locked','--offline')} } $taskStart=[DateTime]::UtcNow.ToString('o') & cargo @taskArgs 2>&1|Tee-Object -FilePath $taskLog $taskExit=$LASTEXITCODE $taskEnd=[DateTime]::UtcNow.ToString('o') $taskText=Get-Content -Raw -LiteralPath $taskLog $taskValid=$true;$taskFailure=$null $listed=@();$executed=@();$matched=@();$ignored=@();$summaries=@();$listSummaries=@() if($Stage -eq 'list'){ $listed=@([regex]::Matches($taskText,'(?m)^([^\r\n]+): test\r?$')|ForEach-Object {$_.Groups[1].Value}) $matched=@($listed|Where-Object {$_ -cin $expected}|Sort-Object) $listSummaries=@([regex]::Matches($taskText,'(?m)^(\d+) tests, (\d+) benchmarks\r?$')|ForEach-Object {[ordered]@{tests=[int]$_.Groups[1].Value;benchmarks=[int]$_.Groups[2].Value}}) $taskValid=$listed.Count -ge $expectedCount -and @($listed|Sort-Object -Unique).Count -eq $listed.Count -and (Same-Names $expected $matched) -and (Same-Names @($selection.desktop_tests) $listed) -and $listSummaries.Count -gt 0 -and ($listSummaries|ForEach-Object {$_.tests}|Measure-Object -Sum).Sum -eq $listed.Count -and ($listSummaries|ForEach-Object {$_.benchmarks}|Measure-Object -Sum).Sum -eq 0 } if($Stage -in @('focused','all')){ $executed=@([regex]::Matches($taskText,'(?m)^test ([^\r\n]+?) \.\.\. (ok|FAILED|ignored)[^\r\n]*\r?$')|ForEach-Object {[ordered]@{name=$_.Groups[1].Value;result=$_.Groups[2].Value}}) $summaries=@([regex]::Matches($taskText,'test result: (ok|FAILED)\. (\d+) passed; (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out;')|ForEach-Object {[ordered]@{status=$_.Groups[1].Value;passed=[int]$_.Groups[2].Value;failed=[int]$_.Groups[3].Value;ignored=[int]$_.Groups[4].Value;measured=[int]$_.Groups[5].Value;filtered=[int]$_.Groups[6].Value}}) $ignored=@($executed|Where-Object {$_.result -eq 'ignored'}|ForEach-Object {$_.name}) $passedCount=@($executed|Where-Object {$_.result -eq 'ok'}).Count $failedCount=@($executed|Where-Object {$_.result -eq 'FAILED'}).Count $taskValid=$summaries.Count -gt 0 -and $failedCount -eq 0 -and @($summaries|Where-Object {$_.status -ne 'ok' -or $_.failed -ne 0 -or $_.measured -ne 0}).Count -eq 0 -and ($summaries|ForEach-Object {$_. passed}|Measure-Object -Sum).Sum -eq $passedCount -and ($summaries|ForEach-Object {$_. ignored}|Measure-Object -Sum).Sum -eq $ignored.Count if($Stage -in @('focused','all')){ $matched=@($executed|Where-Object {$_.name -cin $expected -and $_.result -eq 'ok'}|ForEach-Object {$_.name}|Sort-Object) $taskValid=$taskValid -and (Same-Names $expected $matched) } if($Stage -eq 'focused'){$taskValid=$taskValid -and $executed.Count -eq $expectedCount -and $passedCount -eq $expectedCount -and $ignored.Count -eq 0 -and $summaries.Count -eq 1 -and $summaries[0].filtered -eq (@($prior.listedTests).Count - $expectedCount)} if($Stage -eq 'all'){ $listPath=Join-Path $PSScriptRoot 'list-receipt.json' $listHash=Hash-Input $listPath $listReceipt=Get-Content -Raw -LiteralPath $listPath|ConvertFrom-Json if($prior.priorStage.stage -cne 'list' -or $prior.priorStage.receiptSha256 -cne $listHash){throw 'Desktop listing changed after focused execution.'} $boundScripts['list-receipt.json']=$listHash $taskValid=$taskValid -and (Same-Names @($listReceipt.listedTests) @($executed|ForEach-Object {$_.name})) -and ($summaries|ForEach-Object {$_. filtered}|Measure-Object -Sum).Sum -eq 0 -and (Same-Names @($selection.ignored_tests) $ignored) } } $inputsUnchanged=$false try { & (Join-Path $PSScriptRoot 'capture.ps1') -Stage ($Stage+'-after') foreach($file in $boundScripts.Keys){if((Hash-Input (Join-Path $PSScriptRoot $file)) -cne $boundScripts[$file]){throw ('Gate recipe/input changed: '+$file)}} if((Hash-Input $versionPath) -cne $versionsHash){throw 'Toolchain version receipt changed.'} if($priorBinding -and (Hash-Input (Join-Path $PSScriptRoot ($priorBinding.stage+'-receipt.json'))) -cne $priorBinding.receiptSha256){throw 'Prior receipt changed during gate.'} $inputsUnchanged=$true }catch{$taskFailure=$_.Exception.Message} $binary=$null if($Stage -eq 'build' -and $taskExit -eq 0){$path=Join-Path $taskTarget 'debug/arcadia-studio.exe';$item=Get-Item -LiteralPath $path;$binary=[ordered]@{path=$item.FullName;bytes=$item.Length;sha256=(Hash-Input $path);mtimeUtc=$item.LastWriteTimeUtc.ToString('o')}} [ordered]@{stage=$Stage;command=('cargo '+($taskArgs -join ' '));arguments=$taskArgs;base=$taskPin;workspace=$taskWorkspace;target=$taskTarget;startUtc=$taskStart;endUtc=$taskEnd;cargoExitCode=$taskExit;selectionValid=$taskValid;inputsUnchanged=$inputsUnchanged;postflightFailure=$taskFailure;boundScriptAndInputHashes=$boundScripts;priorStage=$priorBinding;externalEnvironmentPath=$toolchainEnv;externalEnvironmentSha256=(Hash-Input $toolchainEnv);toolchainVersionsSha256=$versionsHash;rawLogSha256=(Hash-Input $taskLog);beforeCaptureSha256=(Hash-Input (Join-Path $PSScriptRoot ('inputs-complete-'+$Stage+'-before.json')));afterCaptureSha256=if(Test-Path -LiteralPath (Join-Path $PSScriptRoot ('inputs-complete-'+$Stage+'-after.json'))){Hash-Input (Join-Path $PSScriptRoot ('inputs-complete-'+$Stage+'-after.json'))}else{$null};listedTests=$listed;executedTests=$executed;testResultSummaries=$summaries;matchedRegressionTests=$matched;ignoredTests=$ignored;listSummaries=$listSummaries;binary=$binary}|ConvertTo-Json -Depth 14|Set-Content -LiteralPath $taskReceipt -Encoding utf8 if($taskExit -ne 0){exit $taskExit} if(-not $taskValid -or -not $inputsUnchanged){throw 'Gate membership or input binding failed; inspect receipt and raw output.'} } finally {Pop-Location}