Files
ARCADIA-Studio/harness/experiments/scenario-comparators-gui-b10b28c-20260913/details/check.ps1
T

101 lines
11 KiB
PowerShell

#Requires -Version 7.0
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)][string]$OracleExe,
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$OracleSha256,
[Parameter(Mandatory=$true)][string]$BuildReceipt,
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$BuildReceiptSha256,
[Parameter(Mandatory=$true)][ValidateSet('self-check','name','typed')][string]$Mode,
[Parameter(Mandatory=$true)][string]$Output,
[string]$Baseline,
[ValidatePattern('^[0-9a-f]{64}$')][string]$BaselineSha256,
[string]$Actual,
[string]$Target,
[string]$NewName,
[string]$ExpectedProject,
[ValidateSet('baseline','1','3a','3b','3c','3d')][string]$Checkpoint
)
Set-StrictMode -Version Latest
$ErrorActionPreference='Stop'
function Hash([string]$Path){(Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()}
function Record([string]$Path){$p=Get-Item -LiteralPath $Path;if($p.PSIsContainer -or ($p.Attributes -band [IO.FileAttributes]::ReparsePoint)){throw 'Plain file required'};[ordered]@{path=$p.FullName;bytes=$p.Length;sha256=(Hash $p.FullName)}}
function Read-Json([string]$Path){Get-Content -Raw -LiteralPath $Path|ConvertFrom-Json}
function Write-NewJson([string]$Path,$Value){$f=[IO.File]::Open($Path,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::Read);try{$b=[Text.UTF8Encoding]::new($false).GetBytes(($Value|ConvertTo-Json -Depth 20)+"`n");$f.Write($b,0,$b.Length);$f.Flush($true)}finally{$f.Dispose()}}
function Assert-Records($Files){foreach($x in $Files){$y=Record $x.path;if($x.bytes -ne $y.bytes -or $x.sha256 -cne $y.sha256){throw ('Changed bound input: '+$x.path)}}}
if(Test-Path -LiteralPath $Output){throw 'Output must be new; preserve earlier evidence'}
if((Hash $OracleExe) -cne $OracleSha256 -or (Hash $BuildReceipt) -cne $BuildReceiptSha256){throw 'Reviewed EXE/build receipt hashes differ'}
$build=Read-Json $BuildReceipt
$exe=(Resolve-Path -LiteralPath $OracleExe).Path
$buildDir=Split-Path -Parent ((Resolve-Path -LiteralPath $BuildReceipt).Path)
if($build.source_commit -cne 'b10b28cefd1d0794073ba241a0c8de9a83e85f68' -or $build.exit_code -ne 0 -or -not $build.inputs_unchanged -or $null -ne $build.postflight_failure -or $build.binary.path -ine $exe -or $build.binary.sha256 -cne $OracleSha256 -or (Get-Item -LiteralPath $exe).Length -ne $build.binary.bytes){throw 'Successful exactb10 comparator build required'}
$proofFiles=@([ordered]@{name='inputs-before.json';sha=$build.before_manifest_sha256},[ordered]@{name='inputs-after.json';sha=$build.after_manifest_sha256},[ordered]@{name='toolchain.json';sha=$build.toolchain_sha256},[ordered]@{name='compile.log';sha=$build.compile_log_sha256})
foreach($f in $proofFiles){if((Hash (Join-Path $buildDir $f.name)) -cne $f.sha){throw 'Build evidence changed'}}
$buildBefore=@(Read-Json (Join-Path $buildDir 'inputs-before.json'));$buildAfter=@(Read-Json (Join-Path $buildDir 'inputs-after.json'))
if(($buildBefore|ConvertTo-Json -Depth 8 -Compress) -cne ($buildAfter|ConvertTo-Json -Depth 8 -Compress)){throw 'Build input manifests differ'}
if(@($build.source_files).Count -lt 612){throw 'Complete source input binding missing'}
$buildPaths=@{};foreach($f in $buildBefore){if($buildPaths.ContainsKey($f.path)){throw 'Duplicate build input'};$buildPaths[$f.path]=$f}
foreach($f in $build.source_files){if(-not $buildPaths.ContainsKey($f.path) -or $buildPaths[$f.path].bytes -ne $f.bytes -or $buildPaths[$f.path].sha256 -cne $f.sha256){throw 'Source record not bound by build manifest'}}
if((Hash $build.reference_receipt) -cne $build.reference_receipt_sha256){throw 'Closed list reference changed'}
$reference=Read-Json $build.reference_receipt
$referenceCapture=Join-Path (Split-Path -Parent $build.reference_receipt) 'inputs-complete-list-after.json'
if($reference.base -cne $build.source_commit -or $reference.stage -cne 'list' -or $reference.cargoExitCode -ne 0 -or -not $reference.inputsUnchanged -or -not $reference.selectionValid -or (Hash $referenceCapture) -cne $reference.afterCaptureSha256){throw 'Exact successful list/source capture missing'}
$compiledSources=Read-Json $referenceCapture
if($compiledSources.count -ne 612 -or @($compiledSources.files).Count -ne 612){throw 'Expected all612 compiled source inputs'}
foreach($f in $compiledSources.files){$path=(Resolve-Path -LiteralPath (Join-Path $reference.workspace $f.path)).Path;$match=@($build.source_files|Where-Object {$_.path -ieq $path});if($match.Count -ne 1 -or $match[0].bytes -ne $f.bytes -or $match[0].sha256 -cne $f.sha256){throw 'A compiled source file is missing from the build/runtime source binding'}}
Assert-Records $build.source_files
$runtime=Join-Path (Split-Path -Parent $exe) 'libunwind.dll'
if($build.runtime_bytes -ne 190464 -or $build.runtime_path -ine $runtime -or $build.runtime_sha256 -cne 'aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a' -or (Hash $runtime) -cne $build.runtime_sha256 -or (Get-Item -LiteralPath $runtime).Length -ne $build.runtime_bytes){throw 'Actual adjacent runtime DLL differs from build binding'}
$catalog=Read-Json (Join-Path $PSScriptRoot 'expected-catalog.json')
$sourceManifest=Read-Json (Join-Path $PSScriptRoot 'source-inputs.json')
foreach($f in $sourceManifest.files){$path=(Resolve-Path -LiteralPath (Join-Path $PSScriptRoot $f.path)).Path;$matching=@($build.source_files|Where-Object {$_.path -ieq $path});if($matching.Count -ne 1 -or $matching[0].bytes -ne $f.bytes -or $matching[0].sha256 -cne $f.sha256){throw 'Current comparator source is not the compiled source'}}
$paths=@($build.reference_receipt,$referenceCapture,$exe,$runtime,$BuildReceipt,$PSCommandPath,(Join-Path $PSScriptRoot 'source-inputs.json'))+@($proofFiles|ForEach-Object {Join-Path $buildDir $_.name})+@($build.source_files|ForEach-Object path)
# Only one admitted source file per native fixture; all successful parse inputs are bound.
$inputs=@();$extra=@();$request=[ordered]@{mode=$Mode;expected_origin='Declared before reading actual output; no expectation inferred from Actual'}
switch($Mode){
'self-check' {
if($Baseline -or $BaselineSha256 -or $Actual -or $Target -or $NewName -or $ExpectedProject -or $Checkpoint){throw 'Self-check accepts no native comparison inputs'}
$inputs=@((Join-Path $PSScriptRoot 'fixtures/incident-workspace.arcadia'),(Join-Path $PSScriptRoot 'fixtures/service-details.arcadia'))
}
'name' {
if([string]::IsNullOrWhiteSpace($Baseline) -or [string]::IsNullOrWhiteSpace($BaselineSha256) -or [string]::IsNullOrWhiteSpace($Actual) -or $Target -notmatch '^[0-9a-fA-F-]{36}$' -or [string]::IsNullOrEmpty($NewName) -or $ExpectedProject -or $Checkpoint){throw 'Name mode requires independent baseline SHA, Actual, exact element Target and predetermined NewName only'}
if((Hash $Baseline) -cne $BaselineSha256){throw 'Pre-edit baseline changed'}
$inputs=@($Baseline,$Actual);$extra=@($Target,$NewName);$request.element=$Target;$request.new_name=$NewName;$request.baseline_sha256=$BaselineSha256
}
'typed' {
if([string]::IsNullOrWhiteSpace($ExpectedProject) -or [string]::IsNullOrWhiteSpace($Actual) -or [string]::IsNullOrWhiteSpace($Checkpoint) -or $Baseline -or $BaselineSha256 -or $Target -or $NewName){throw 'Typed mode requires ExpectedProject, exact Checkpoint and Actual only'}
$approved=$catalog.checkpoints.$Checkpoint
if((Hash $ExpectedProject) -cne $approved.sha256 -or (Get-Item -LiteralPath $ExpectedProject).Length -ne $approved.bytes){throw 'Expected JSON is not the independently approved checkpoint bytes'}
$inputs=@($ExpectedProject,$Actual);$extra=@($Checkpoint);$request.checkpoint=$Checkpoint;$request.expected_sha256=$approved.sha256
}
}
$inputs=@($inputs|ForEach-Object {(Resolve-Path -LiteralPath $_).Path});$paths+=@($inputs)
$before=@($paths|ForEach-Object {(Resolve-Path -LiteralPath $_).Path}|Sort-Object -Unique|ForEach-Object {Record $_})
$Output=[IO.Path]::GetFullPath($Output);[void][IO.Directory]::CreateDirectory($Output)
$copies=@();for($i=0;$i -lt $inputs.Count;$i++){$copy=Join-Path $Output ('input-'+$i+[IO.Path]::GetExtension($inputs[$i]));[IO.File]::Copy($inputs[$i],$copy,$false);if((Hash $copy) -cne (Hash $inputs[$i])){throw 'Input snapshot copy differs'};$copies+=@($copy)}
Write-NewJson (Join-Path $Output 'request.json') $request
Write-NewJson (Join-Path $Output 'inputs-before.json') $before
$argsForOracle=@($Mode)+$copies+@((Join-Path $Output 'project-comparison'))+$extra
$start=[DateTime]::UtcNow.ToString('o');$exit=$null;$failure=$null;$process=$null;$stdout=$null;$stderr=$null;$unchanged=$false;$semantic=$false
try {
$info=[Diagnostics.ProcessStartInfo]::new();$info.FileName=$exe;$info.WorkingDirectory=$Output;$info.UseShellExecute=$false;$info.CreateNoWindow=$true;$info.WindowStyle=[Diagnostics.ProcessWindowStyle]::Hidden;$info.RedirectStandardOutput=$true;$info.RedirectStandardError=$true
foreach($arg in $argsForOracle){$info.ArgumentList.Add($arg)}
$info.Environment['PATH']=(Split-Path -Parent $exe)+';'+(Join-Path $env:SystemRoot 'System32')+';'+$env:SystemRoot
$stdout=[IO.File]::Open((Join-Path $Output 'stdout.log'),[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::Read)
$stderr=[IO.File]::Open((Join-Path $Output 'stderr.log'),[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::Read)
$process=[Diagnostics.Process]::new();$process.StartInfo=$info;if(-not $process.Start()){throw 'Comparator process did not start'}
$outCopy=$process.StandardOutput.BaseStream.CopyToAsync($stdout);$errCopy=$process.StandardError.BaseStream.CopyToAsync($stderr)
$process.WaitForExit();[Threading.Tasks.Task]::WaitAll([Threading.Tasks.Task[]]@($outCopy,$errCopy));$exit=$process.ExitCode
$stdout.Flush($true);$stderr.Flush($true)
} catch {$failure=$_.Exception.Message} finally {if($stdout){$stdout.Dispose()};if($stderr){$stderr.Dispose()};if($process){$process.Dispose()}}
try {
Assert-Records $before
for($i=0;$i -lt $copies.Count;$i++){if((Hash $copies[$i]) -cne (Hash $inputs[$i])){throw 'Comparison snapshot changed'}}
$after=@($before|ForEach-Object {Record $_.path});Write-NewJson (Join-Path $Output 'inputs-after.json') $after;$unchanged=$true
$result=Join-Path $Output ('project-comparison/'+$(if($Mode -eq 'self-check'){'self-check.json'}else{'comparison.json'}))
if(Test-Path -LiteralPath $result){$v=Read-Json $result;$semantic=$v.status -ceq 'passed';if($Mode -ne 'self-check'){$semantic=$semantic -and $v.mode -ceq $Mode -and $v.full_project_exact}}
} catch {if($null -eq $failure){$failure=$_.Exception.Message}}
$artifacts=@(Get-ChildItem -LiteralPath $Output -File -Recurse|ForEach-Object {Record $_.FullName})
Write-NewJson (Join-Path $Output 'receipt.json') ([ordered]@{source_commit=$build.source_commit;command=@($exe)+$argsForOracle;mode=$Mode;start_utc=$start;end_utc=[DateTime]::UtcNow.ToString('o');exit_code=$exit;failure=$failure;inputs_unchanged=$unchanged;semantic_pass=$semantic;runtime_binding=@($before|Where-Object {$_.path -ieq $runtime})[0];oracle_sha256=$OracleSha256;build_receipt_sha256=$BuildReceiptSha256;artifacts=$artifacts;scope='Complete admitted saved-source Project comparison only; no renderer, source editor, native action or runtime/history claim'})
if($exit -ne 0 -or -not $unchanged -or -not $semantic -or $null -ne $failure){throw 'Comparator or input stability failed; preserve outputs'}
Write-Output 'Saved-source Details comparison passed; runtime/history/native evidence remains separate.'