171 lines
15 KiB
PowerShell
171 lines
15 KiB
PowerShell
#Requires -Version 7.0
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{40}$')][string]$SourcePin,
|
|
[Parameter(Mandatory=$true)][string]$ReferenceReceipt,
|
|
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$ReferenceReceiptSha256,
|
|
[Parameter(Mandatory=$true)][string]$DesktopFingerprint,
|
|
[Parameter(Mandatory=$true)][string]$CoreRlib,
|
|
[Parameter(Mandatory=$true)][string]$SerdeJsonRlib,
|
|
[Parameter(Mandatory=$true)][string]$Output,
|
|
[Parameter(Mandatory=$true)][string]$WindowsImportLibDirectory,
|
|
[Parameter(Mandatory=$true)][string]$NativeVerifier,
|
|
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$NativeVerifierSha256,
|
|
[Parameter(Mandatory=$true)][string]$RuntimeDll
|
|
)
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference='Stop'
|
|
$pin='b10b28cefd1d0794073ba241a0c8de9a83e85f68'
|
|
if($SourcePin -cne $pin){throw 'Exact reviewedb10 source required'}
|
|
$workspace=(Resolve-Path (Join-Path $PSScriptRoot '../scenario-spacing-ytr83r-20260913')).Path
|
|
$root=(Resolve-Path (Join-Path $PSScriptRoot '../..')).Path
|
|
$deps=[IO.Path]::GetFullPath((Join-Path $root 'target/debug/deps'))
|
|
$fingerprints=[IO.Path]::GetFullPath((Join-Path $root 'target/debug/.fingerprint'))
|
|
$native=(Resolve-Path -LiteralPath $WindowsImportLibDirectory).Path
|
|
function Hash([string]$Path){(Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()}
|
|
function Json([string]$Path){Get-Content -Raw -LiteralPath $Path|ConvertFrom-Json}
|
|
function Save([string]$Name,$Value){
|
|
$path=Join-Path $Output $Name
|
|
$stream=[IO.File]::Open($path,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::Read)
|
|
try{$bytes=[Text.UTF8Encoding]::new($false).GetBytes(($Value|ConvertTo-Json -Depth 20)+"`n");$stream.Write($bytes,0,$bytes.Length)}finally{$stream.Dispose()}
|
|
}
|
|
function Assert-ReviewedHash([string]$Actual,[string]$Expected) {
|
|
if($Expected -cnotmatch '^[0-9a-f]{64}$' -or $Actual -cne $Expected){throw 'Explicit reviewed artifact hash differs'}
|
|
}
|
|
function Assert-ListPreparation($Reference,$Selection,$ExpectedInputs) {
|
|
if($Reference.base -cne 'b10b28cefd1d0794073ba241a0c8de9a83e85f68' -or $Reference.stage -cne 'list' -or $Reference.cargoExitCode -ne 0 -or -not $Reference.selectionValid -or -not $Reference.inputsUnchanged -or -not $Reference.testBinaryBindingValid -or $null -ne $Reference.postflightFailure){throw 'Closed successful exactb10 LIST required'}
|
|
if($Selection.source_commit -cne $Reference.base -or $Selection.expected_desktop_count -ne 1095 -or $Selection.expected_count -ne 301 -or $ExpectedInputs.base -cne $Reference.base -or $ExpectedInputs.count -ne 612 -or @($ExpectedInputs.files).Count -ne 612){throw 'Wrong1095/301/612 list preparation'}
|
|
if(($Reference.listSummaries|Measure-Object tests -Sum).Sum -ne 1095 -or ($Reference.listSummaries|Measure-Object benchmarks -Sum).Sum -ne 0){throw 'Actual list totals differ'}
|
|
}
|
|
function Select-SourceRecords($Inputs,$Paths) {
|
|
if(@($Paths).Count -ne @($Paths|Sort-Object -Unique).Count){throw 'Duplicate expected source path'}
|
|
@(foreach($path in $Paths){
|
|
if([IO.Path]::GetFullPath($path) -cne $path){throw 'Noncanonical expected source path'}
|
|
$rows=@($Inputs|Where-Object {$_.path -ceq $path});if($rows.Count -ne 1){throw 'Missing or duplicate build source record'};$rows[0]
|
|
})
|
|
}
|
|
function Assert-Source {
|
|
Push-Location -LiteralPath $workspace
|
|
try{if((git rev-parse HEAD) -cne $pin -or $LASTEXITCODE -ne 0){throw 'Wrong core reference source pin'};if(@(git status --porcelain).Count -or $LASTEXITCODE -ne 0){throw 'Reference source must be clean'}}finally{Pop-Location}
|
|
}
|
|
Assert-Source
|
|
$ReferenceReceipt=(Resolve-Path -LiteralPath $ReferenceReceipt).Path
|
|
Assert-ReviewedHash (Hash $ReferenceReceipt) $ReferenceReceiptSha256
|
|
$reference=Json $ReferenceReceipt
|
|
if($reference.base -cne $pin -or $reference.stage -cne 'list' -or $reference.workspace -ine $workspace -or [IO.Path]::GetFullPath($reference.target) -ine [IO.Path]::GetFullPath((Join-Path $root 'target'))){throw 'Reference must be the exactb10 LIST in the original spacing workspace/target'}
|
|
if($reference.cargoExitCode -ne 0 -or -not $reference.selectionValid -or -not $reference.inputsUnchanged -or -not $reference.testBinaryBindingValid -or $null -ne $reference.postflightFailure){throw 'A closed successful desktop receipt with executable binding is required'}
|
|
$evidence=Split-Path $ReferenceReceipt
|
|
# Reuse read-only source/name helpers only. Do not require the five native stages here.
|
|
$NativeVerifier=(Resolve-Path -LiteralPath $NativeVerifier).Path
|
|
Assert-ReviewedHash (Hash $NativeVerifier) $NativeVerifierSha256
|
|
. $NativeVerifier
|
|
$nativePin=$pin;$nativeInputCount=612
|
|
Assert-CurrentNativeSource $evidence
|
|
$selection=Json (Join-Path $evidence 'expected-tests.json')
|
|
$expectedInputs=Json (Join-Path $evidence 'expected-inputs.json')
|
|
Assert-ListPreparation $reference $selection $expectedInputs
|
|
Assert-NativeNames $selection.desktop_tests $reference.listedTests
|
|
foreach($binding in $reference.boundScriptAndInputHashes.PSObject.Properties){if((Hash (Join-Path $evidence $binding.Name)) -cne $binding.Value){throw 'Bound listing recipe changed'}}
|
|
if((Hash (Join-Path $evidence 'list.log')) -cne $reference.rawLogSha256){throw 'Actual listing log changed'}
|
|
|
|
$versionPath=Join-Path $evidence 'toolchain-versions.json'
|
|
$versions=Json $versionPath
|
|
if((Hash $versionPath) -cne $reference.toolchainVersionsSha256){throw 'Changed toolchain record'}
|
|
$capturePath=Join-Path $evidence ('inputs-complete-'+$reference.stage+'-after.json')
|
|
if((Hash $capturePath) -cne $reference.afterCaptureSha256){throw 'Changed source capture'}
|
|
$beforeCapture=Join-Path $evidence 'inputs-complete-list-before.json'
|
|
if((Hash $beforeCapture) -cne $reference.beforeCaptureSha256){throw 'Changed before capture'}
|
|
Assert-InputPair (Json $beforeCapture) (Json $capturePath)
|
|
$locks=@((Json $capturePath).files|Where-Object path -CEQ 'Cargo.lock')
|
|
if($locks.Count -ne 1 -or (Hash (Join-Path $workspace 'Cargo.lock')) -cne $locks[0].sha256){throw 'Reference dependency graph differs from experiment lockfile'}
|
|
$DesktopFingerprint=(Resolve-Path -LiteralPath $DesktopFingerprint).Path
|
|
$desktop=Json $DesktopFingerprint
|
|
$folder=Split-Path $DesktopFingerprint -Parent
|
|
if((Split-Path $DesktopFingerprint -Leaf) -cne 'test-bin-arcadia-studio.json' -or (Split-Path $folder -Leaf) -cnotmatch '^oa-desktop-([0-9a-f]{16})$'){throw 'Explicit desktop test fingerprint required'}
|
|
if((Split-Path $folder -Parent) -ine $fingerprints -or @($reference.testExecutables).Count -ne 1){throw 'Fingerprint must be under the gated target and list exactly one test executable'}
|
|
$testExe=Join-Path $deps ('arcadia_studio-'+$Matches[1]+'.exe')
|
|
$bound=@($reference.testExecutables|Where-Object {$_.path -ieq $testExe})
|
|
if($bound.Count -ne 1 -or (Get-Item $testExe).Length -ne $bound[0].bytes -or (Hash $testExe) -cne $bound[0].sha256){throw 'Fingerprint must belong to the exact reference test executable'}
|
|
$selected=@{oa_core=$CoreRlib;serde_json=$SerdeJsonRlib}
|
|
$selectedRecords=@()
|
|
foreach($name in @('oa_core','serde_json')){
|
|
$file=(Resolve-Path -LiteralPath $selected[$name]).Path
|
|
if((Split-Path $file -Parent) -ine $deps -or (Split-Path $file -Leaf) -cnotmatch ('^lib'+$name+'-([0-9a-f]{16})\.rlib$')){throw "Wrong explicit rlib: $name"}
|
|
$package=if($name -ceq 'oa_core'){'oa-core'}else{$name}
|
|
$sidecar=Join-Path $fingerprints ($package+'-'+$Matches[1]+'/lib-'+$name)
|
|
$hex=(Get-Content -Raw -LiteralPath $sidecar).Trim()
|
|
if($hex -cnotmatch '^[0-9a-f]{16}$'){throw 'Invalid Cargo fingerprint sidecar'}
|
|
$bytes=[Convert]::FromHexString($hex);if(-not [BitConverter]::IsLittleEndian){[Array]::Reverse($bytes)}
|
|
$value=[BitConverter]::ToUInt64($bytes,0)
|
|
$edges=@($desktop.deps|Where-Object {$_[1] -ceq $name})
|
|
if($edges.Count -ne 1 -or [UInt64]$edges[0][3] -ne $value){throw "Reference dependency edge differs: $name"}
|
|
$record=Json ($sidecar+'.json')
|
|
if($record.rustc -ne $desktop.rustc){throw 'Mixed Cargo compiler fingerprints'}
|
|
$selected[$name]=$file
|
|
$selectedRecords+=@($sidecar,($sidecar+'.json'),(Join-Path $deps ($name+'-'+([IO.Path]::GetFileNameWithoutExtension($file).Split('-')[-1])+'.d')))
|
|
}
|
|
# Verify the selected core's serde_json edge as well as the desktop's two edges.
|
|
$coreName=[IO.Path]::GetFileNameWithoutExtension($selected.oa_core).Substring('lib'.Length)
|
|
$coreJson=Json (Join-Path $fingerprints ($coreName.Replace('oa_core-','oa-core-')+'/lib-oa_core.json'))
|
|
$serdeEdge=@($desktop.deps|Where-Object {$_[1] -ceq 'serde_json'})
|
|
$coreSerdeEdge=@($coreJson.deps|Where-Object {$_[1] -ceq 'serde_json'})
|
|
if($coreSerdeEdge.Count -ne 1 -or [UInt64]$coreSerdeEdge[0][3] -ne [UInt64]$serdeEdge[0][3]){throw 'Core serde_json linkage differs from selected desktop dependency'}
|
|
$coreInputs=@((Json $capturePath).files)
|
|
if($coreInputs.Count -ne 612){throw 'Closed receipt does not enumerate core source inputs'}
|
|
foreach($inputFile in $coreInputs){if((Hash (Join-Path $workspace $inputFile.path)) -cne $inputFile.sha256){throw ('Core source differs from compiled reference: '+$inputFile.path)}}
|
|
if(Test-Path -LiteralPath $Output){throw 'Output must be a new evidence directory'}
|
|
$Output=[IO.Path]::GetFullPath($Output)
|
|
$environment=$versions.externalEnvironmentPath
|
|
if((Hash $environment) -cne $versions.externalEnvironmentSha256){throw 'Changed compiler environment'}
|
|
. $environment
|
|
$rustc=(Get-Command rustc -CommandType Application).Source
|
|
$clang=(Get-Command clang -CommandType Application).Source
|
|
if($rustc -ine $versions.rustcPath -or $clang -ine $versions.clangPath){throw 'Compiler/linker paths differ from the reference'}
|
|
$toolchain=Join-Path $env:RUSTUP_HOME ('toolchains/'+$env:RUSTUP_TOOLCHAIN)
|
|
$sysroot=Join-Path $toolchain 'lib/rustlib/x86_64-pc-windows-gnullvm/lib'
|
|
$wrapper=Join-Path $PSScriptRoot 'main.rs'
|
|
$sourceManifest=Json (Join-Path $PSScriptRoot 'source-inputs.json')
|
|
$sources=@(foreach($f in $sourceManifest.files){$p=(Resolve-Path -LiteralPath (Join-Path $PSScriptRoot $f.path)).Path;if((Get-Item -LiteralPath $p).Length -ne $f.bytes -or (Hash $p) -cne $f.sha256){throw 'Prepared comparator source changed'};$p})
|
|
$runtimeSourcePaths=@(($sources+@($PSCommandPath,$NativeVerifier,(Join-Path $PSScriptRoot 'source-inputs.json'))+@($coreInputs|ForEach-Object {Join-Path $workspace $_.path}))|ForEach-Object {(Resolve-Path -LiteralPath $_).Path}|Sort-Object -Unique)
|
|
$runtimeHash='aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a'
|
|
$RuntimeDll=(Resolve-Path -LiteralPath $RuntimeDll).Path
|
|
if((Get-Item -LiteralPath $RuntimeDll).Length -ne 190464 -or (Hash $RuntimeDll) -cne $runtimeHash){throw 'Wrong pinned runtime DLL'}
|
|
function Inputs {
|
|
$paths=@((Join-Path $PSScriptRoot 'source-inputs.json'),$NativeVerifier,$PSCommandPath,$ReferenceReceipt,$DesktopFingerprint,$versionPath,$capturePath,$testExe,$environment,$rustc,$clang,$RuntimeDll,(Join-Path $toolchain 'bin/rustc.exe'),(Join-Path $workspace 'Cargo.lock'),(Join-Path $workspace 'Cargo.toml'),(Join-Path $workspace 'crates/oa-core/Cargo.toml'))+$sources+$selectedRecords+@($coreInputs|ForEach-Object {Join-Path $workspace $_.path})
|
|
$paths+=@($reference.boundScriptAndInputHashes.PSObject.Properties|ForEach-Object {Join-Path $evidence $_.Name})+@((Join-Path $evidence 'list.log'),$beforeCapture)
|
|
foreach($directory in @($deps,$native,$sysroot)){$paths+=@(Get-ChildItem -LiteralPath $directory -File|Where-Object {$_.Extension -in @('.rlib','.rmeta','.dll','.lib','.a')}|ForEach-Object FullName)}
|
|
@($paths|ForEach-Object {(Resolve-Path -LiteralPath $_).Path}|Sort-Object -Unique|ForEach-Object {$item=Get-Item -LiteralPath $_;[ordered]@{path=$item.FullName;bytes=$item.Length;sha256=(Hash $item.FullName)}})
|
|
}
|
|
[void][IO.Directory]::CreateDirectory($Output)
|
|
$before=@(Inputs);$sourceRecords=@(Select-SourceRecords $before $runtimeSourcePaths);Save 'inputs-before.json' $before
|
|
$PSNativeCommandUseErrorActionPreference=$false
|
|
$rustcVersion=@(& $rustc --version --verbose 2>&1|ForEach-Object ToString);$rustcExit=$LASTEXITCODE
|
|
$clangVersion=@(& $clang --version 2>&1|ForEach-Object ToString);$clangExit=$LASTEXITCODE
|
|
Save 'toolchain.json' @{rustc=$rustcVersion;rustc_exit=$rustcExit;clang=$clangVersion;clang_exit=$clangExit}
|
|
if($rustcExit -ne 0 -or $clangExit -ne 0 -or ($rustcVersion -join "`n") -cne (@($versions.rustc) -join "`n") -or ($clangVersion -join "`n") -cne (@($versions.clang) -join "`n")){throw 'Compiler/linker versions differ'}
|
|
$exe=Join-Path $Output 'details-source-oracle.exe'
|
|
$argsForRustc=@('--edition=2024','--crate-name','details_source_oracle','-D','warnings',$wrapper,'--extern',('oa_core='+$selected.oa_core),'--extern',('serde_json='+$selected.serde_json),'-L',('dependency='+$deps),'-L',('native='+$native),'-C',('linker='+$clang),'--emit',('link,dep-info='+(Join-Path $Output 'details-source-oracle.d')),'-o',$exe)
|
|
$start=[DateTime]::UtcNow.ToString('o')
|
|
& $rustc @argsForRustc *> (Join-Path $Output 'compile.log')
|
|
$exit=$LASTEXITCODE
|
|
$end=[DateTime]::UtcNow.ToString('o')
|
|
$unchanged=$false;$failure=$null
|
|
try{
|
|
$after=@(Inputs);[void](Select-SourceRecords $after $runtimeSourcePaths);Save 'inputs-after.json' $after
|
|
if(($before|ConvertTo-Json -Depth 6 -Compress) -cne ($after|ConvertTo-Json -Depth 6 -Compress)){throw 'A source/link input changed'}
|
|
Assert-Source
|
|
Assert-CurrentNativeSource $evidence
|
|
$unchanged=$true
|
|
}catch{$failure=$_.Exception.Message}
|
|
$binary=if(Test-Path -LiteralPath $exe){$item=Get-Item $exe;@{path=$exe;bytes=$item.Length;sha256=(Hash $exe)}}else{$null}
|
|
if($exit -eq 0 -and $unchanged){
|
|
try{
|
|
$runtimeInput=@($before|Where-Object {$_.path -ieq (Resolve-Path -LiteralPath $RuntimeDll).Path})
|
|
[IO.File]::Copy($RuntimeDll,(Join-Path $Output 'libunwind.dll'),$false)
|
|
if($runtimeInput.Count -ne 1 -or (Hash (Join-Path $Output 'libunwind.dll')) -cne $runtimeInput[0].sha256){throw 'Staged runtime differs from bound input'}
|
|
}catch{$unchanged=$false;$failure=$_.Exception.Message}
|
|
}
|
|
Save 'build-receipt.json' @{source_commit=$pin;native_verifier=@{path=$NativeVerifier;sha256=$NativeVerifierSha256};reference_receipt=$ReferenceReceipt;reference_receipt_sha256=(Hash $ReferenceReceipt);command=@($rustc)+$argsForRustc;start_utc=$start;end_utc=$end;exit_code=$exit;inputs_unchanged=$unchanged;postflight_failure=$failure;binary=$binary;source_files=$sourceRecords;runtime_path=(Join-Path $Output 'libunwind.dll');runtime_bytes=(Get-Item -LiteralPath $RuntimeDll).Length;runtime_sha256=(Hash $RuntimeDll);before_manifest_sha256=(Hash (Join-Path $Output 'inputs-before.json'));after_manifest_sha256=if(Test-Path (Join-Path $Output 'inputs-after.json')){Hash (Join-Path $Output 'inputs-after.json')}else{$null};toolchain_sha256=(Hash (Join-Path $Output 'toolchain.json'));compile_log_sha256=(Hash (Join-Path $Output 'compile.log'));tests_executed=$false;scope='Core-only comparator compilation bound to the exact successfulb10 desktop LIST, not a GUI/native gate. Source/cache link inputs are unchanged; execution remains separate.'}
|
|
if($exit -ne 0 -or -not $unchanged -or $null -eq $binary -or $binary.bytes -le 0){throw 'Isolated compilation or input provenance failed; preserve evidence'}
|
|
Write-Output 'Details comparator compiled only; self-check and saved-source comparison remain separate. No GUI/native acceptance.'
|