123 lines
11 KiB
PowerShell
123 lines
11 KiB
PowerShell
#Requires -Version 7.0
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory=$true)][string]$ReferenceReceipt,
|
|
[Parameter(Mandatory=$true)][string]$DesktopFingerprint,
|
|
[Parameter(Mandatory=$true)][string]$IcedRlib,
|
|
[Parameter(Mandatory=$true)][string]$UnicodeSegmentationRlib,
|
|
[Parameter(Mandatory=$true)][string]$SerdeJsonRlib,
|
|
[Parameter(Mandatory=$true)][string]$Output,
|
|
[string]$RuntimeDll='C:/Users/User/Documents/Git/ARCADIA-Studio/.local/toolchain/llvm-mingw-20260908-ucrt-x86_64/bin/libunwind.dll'
|
|
)
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference='Stop'
|
|
$pin='4c9bc87e4e66bba573dd63b23df5ea039f85d0ca'
|
|
$workspace=(Resolve-Path (Join-Path $PSScriptRoot '../scenario-label-fields-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=[IO.Path]::GetFullPath((Join-Path $root '.local/toolchain/cargo/registry/src/index.crates.io-1949cf8c6b5b557f/windows_x86_64_gnullvm-0.52.6/lib'))
|
|
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-Source {
|
|
Push-Location -LiteralPath $workspace
|
|
try{if((git rev-parse HEAD) -cne $pin -or $LASTEXITCODE -ne 0){throw 'Wrong experiment pin'};if(@(git status --porcelain).Count -or $LASTEXITCODE -ne 0){throw 'Experiment source must be clean'}}finally{Pop-Location}
|
|
}
|
|
Assert-Source
|
|
$RuntimeDll=(Resolve-Path -LiteralPath $RuntimeDll).Path
|
|
if((Get-Item -LiteralPath $RuntimeDll).Length -ne 190464 -or (Hash $RuntimeDll) -cne 'aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a'){throw 'Runtime differs from the pinned Windows libunwind input'}
|
|
$ReferenceReceipt=(Resolve-Path -LiteralPath $ReferenceReceipt).Path
|
|
$reference=Json $ReferenceReceipt
|
|
if($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 'A closed successful desktop receipt with executable binding is required'}
|
|
$evidence=Split-Path $ReferenceReceipt
|
|
$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'}
|
|
foreach($manifest in @('Cargo.lock','Cargo.toml','crates/oa-desktop/Cargo.toml')){
|
|
$records=@((Json $capturePath).files|Where-Object path -CEQ $manifest)
|
|
if($records.Count -ne 1 -or (Hash (Join-Path $workspace $manifest)) -cne $records[0].sha256){throw ('Reference dependency graph differs: '+$manifest)}
|
|
}
|
|
$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'}
|
|
$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=@{iced=$IcedRlib;unicode_segmentation=$UnicodeSegmentationRlib;serde_json=$SerdeJsonRlib}
|
|
$selectedRecords=@()
|
|
foreach($name in @('iced','unicode_segmentation','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 'unicode_segmentation'){'unicode-segmentation'}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')))
|
|
}
|
|
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 'label_harness.rs'
|
|
$sources=@($wrapper,(Join-Path $workspace 'crates/oa-desktop/src/behavior/scenario_label.rs'),(Join-Path $workspace 'crates/oa-desktop/src/behavior/scenario_label/tests.rs'),(Join-Path $workspace 'crates/oa-desktop/src/behavior/scenario_label/field_tests.rs'),(Join-Path $workspace 'harness/experiments/scenario-label-fields-20260913/README.md'))
|
|
foreach($fixture in @('.gitattributes','NotoSans-Regular.ttf','NotoSans-LICENSE','README.md')){$sources+=Join-Path $workspace ('crates/oa-desktop/src/behavior/scenario_label/fixtures/'+$fixture)}
|
|
$fixtureFont=Join-Path $workspace 'crates/oa-desktop/src/behavior/scenario_label/fixtures/NotoSans-Regular.ttf'
|
|
if((Get-Item -LiteralPath $fixtureFont).Length -ne 556216 -or (Hash $fixtureFont) -cne '2ec33f84606cbaa0a1a944488e14f97faf2f6a25ecdd8354f5358f06da13c7d9'){throw 'Ligature font fixture differs from pinned upstream bytes'}
|
|
function Inputs {
|
|
$paths=@($PSCommandPath,(Join-Path $PSScriptRoot 'run.ps1'),(Join-Path $PSScriptRoot 'validate-syntax.ps1'),(Join-Path $PSScriptRoot 'recipe.json'),(Join-Path $PSScriptRoot 'README.md'),(Join-Path $PSScriptRoot 'expected-tests.json'),$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-desktop/Cargo.toml'))+$sources+$selectedRecords
|
|
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);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 'scenario-label-harness.exe'
|
|
$argsForRustc=@('--edition=2024','--crate-name','scenario_label_harness','--test','-D','warnings',$wrapper,'--extern',('iced='+$selected.iced),'--extern',('unicode_segmentation='+$selected.unicode_segmentation),'--extern',('serde_json='+$selected.serde_json),'-L',('dependency='+$deps),'-L',('native='+$native),'-C',('linker='+$clang),'--emit',('link,dep-info='+(Join-Path $Output 'harness.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);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
|
|
$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' @{schema='scenario_label_field_build_v3';source_commit=$pin;recipe_sha256=(Hash (Join-Path $PSScriptRoot 'recipe.json'));run_script_sha256=(Hash (Join-Path $PSScriptRoot 'run.ps1'));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;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}
|
|
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 'Isolated harness compiled only; list and test execution remain separate.'
|