183 lines
15 KiB
PowerShell
183 lines
15 KiB
PowerShell
#Requires -Version 7.0
|
|
[CmdletBinding(DefaultParameterSetName='Build')]
|
|
param(
|
|
[Parameter(Mandatory=$true,ParameterSetName='Build')][ValidatePattern('^[0-9a-f]{40}$')][string]$SourcePin,
|
|
[Parameter(Mandatory=$true,ParameterSetName='Build')][string]$Gate,
|
|
[Parameter(Mandatory=$true,ParameterSetName='Build')][string]$CoreRlib,
|
|
[Parameter(Mandatory=$true,ParameterSetName='Build')][string]$SerdeJsonRlib,
|
|
[Parameter(Mandatory=$true,ParameterSetName='Build')][string]$WindowsImportLibDirectory,
|
|
[Parameter(Mandatory=$true,ParameterSetName='Build')][string]$Output,
|
|
[Parameter(Mandatory=$true,ParameterSetName='Build')][string]$NativeVerifier,
|
|
[Parameter(Mandatory=$true,ParameterSetName='Build')][ValidatePattern('^[0-9a-f]{64}$')][string]$NativeVerifierSha256,
|
|
[Parameter(Mandatory=$true,ParameterSetName='Build')][string]$RuntimeDll,
|
|
[Parameter(Mandatory=$true,ParameterSetName='Validate')][switch]$ValidateOnly
|
|
)
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference='Stop'
|
|
function Fingerprint-Value([string]$Hex) {
|
|
if($Hex -cnotmatch '^[0-9a-f]{16}$'){throw 'Cargo fingerprint must be a complete 16-hex sidecar.'}
|
|
$bytes=[Convert]::FromHexString($Hex)
|
|
if(-not [BitConverter]::IsLittleEndian){[Array]::Reverse($bytes)}
|
|
[BitConverter]::ToUInt64($bytes,0)
|
|
}
|
|
function Assert-Dependency($Fingerprint,[string]$Name,[UInt64]$Value) {
|
|
$edges=@($Fingerprint.deps|Where-Object {$_[1] -ceq $Name})
|
|
if($edges.Count -ne 1 -or [UInt64]$edges[0][3] -ne $Value){throw "Cargo dependency fingerprint differs or is ambiguous: $Name"}
|
|
}
|
|
function Select-OracleSources($Inputs,$SourcePaths) {
|
|
if(@($SourcePaths).Count -ne 5 -or @($SourcePaths|Sort-Object -Unique).Count -ne 5){throw 'Exactly five distinct canonical oracle source paths required'}
|
|
$selected=@(foreach($path in $SourcePaths){
|
|
if([IO.Path]::GetFullPath($path) -cne $path){throw 'Oracle source path is not canonical'}
|
|
$matches=@($Inputs|Where-Object {$_.path -ieq $path})
|
|
if($matches.Count -ne 1){throw ('Oracle source missing or duplicated in build capture: '+$path)}
|
|
$matches[0]
|
|
})
|
|
return $selected
|
|
}
|
|
if($ValidateOnly) {
|
|
foreach($script in @($PSCommandPath,(Join-Path $PSScriptRoot 'check.ps1'))){
|
|
$tokens=$null;$errors=$null
|
|
[void][Management.Automation.Language.Parser]::ParseFile($script,[ref]$tokens,[ref]$errors)
|
|
if(@($errors).Count){throw ($errors|Out-String)}
|
|
}
|
|
$fingerprint=Fingerprint-Value 'ab2fa54704e8737e'
|
|
if($fingerprint -ne [UInt64]9111881576180166571){throw 'Little-endian fingerprint oracle failed.'}
|
|
$sample='{"deps":[[1,"serde_json",false,9111881576180166571]]}'|ConvertFrom-Json
|
|
Assert-Dependency $sample 'serde_json' $fingerprint
|
|
if((Fingerprint-Value 'ffffffffffffffff') -ne [UInt64]::MaxValue){throw 'Full-width unsigned fingerprint oracle failed.'}
|
|
Assert-Dependency ('{"deps":[[1,"oa_core",false,18446744073709551615]]}'|ConvertFrom-Json) 'oa_core' ([UInt64]::MaxValue)
|
|
$rejected=0
|
|
foreach($probe in @(
|
|
{Fingerprint-Value ''},
|
|
{Fingerprint-Value 'not-a-fingerprint'},
|
|
{Assert-Dependency $sample 'oa_core' $fingerprint},
|
|
{Assert-Dependency $sample 'serde_json' ([UInt64]1)},
|
|
{Assert-Dependency ('{"deps":[[1,"serde_json",false,1],[2,"serde_json",false,1]]}'|ConvertFrom-Json) 'serde_json' ([UInt64]1)}
|
|
)) {try {& $probe|Out-Null} catch {$rejected++;continue};throw 'A fingerprint refusal probe unexpectedly passed.'}
|
|
$sourcePaths=@('C:\probe\oracle\build.ps1','C:\probe\oracle\check.ps1','C:\probe\oracle\main.rs','C:\probe\incident-workspace.arcadia','C:\verifier\verify.ps1')
|
|
$sourceRecords=@($sourcePaths|ForEach-Object {[pscustomobject]@{path=$_;bytes=1;sha256=('0'*64)}})
|
|
if(@(Select-OracleSources $sourceRecords $sourcePaths).Count -ne 5){throw 'Canonical source membership oracle failed'}
|
|
$sourceRefusals=0
|
|
$uncanonical=@($sourceRecords|ForEach-Object {[pscustomobject]@{path=$_.path;bytes=$_.bytes;sha256=$_.sha256}})
|
|
$uncanonical[3].path='C:\probe\oracle\..\incident-workspace.arcadia'
|
|
$membershipCases=@(
|
|
@{name='missing_fixture';records=@($sourceRecords|Where-Object {$_.path -ne $sourcePaths[3]})},
|
|
@{name='unnormalized_fixture';records=$uncanonical},
|
|
@{name='duplicate_fixture';records=(@($sourceRecords)+@($sourceRecords[3]))}
|
|
)
|
|
foreach($case in $membershipCases){
|
|
$didReject=$false;try{Select-OracleSources $case.records $sourcePaths|Out-Null}catch{$didReject=$true}
|
|
if(-not $didReject){throw ('Source membership admitted '+$case.name)};$sourceRefusals++
|
|
}
|
|
[ordered]@{status='passed';parsed_scripts=2;positive_cases=5;negative_cases=$rejected;source_membership_negative_cases=$sourceRefusals;script_sha256=(Get-FileHash -LiteralPath $PSCommandPath -Algorithm SHA256).Hash.ToLowerInvariant();check_script_sha256=(Get-FileHash -LiteralPath (Join-Path $PSScriptRoot 'check.ps1') -Algorithm SHA256).Hash.ToLowerInvariant();compiler_executed=$false;gate_admitted=$false;scope='Syntax and synthetic fingerprint guards only; no dependency selection, compilation or oracle execution'}|ConvertTo-Json
|
|
return
|
|
}
|
|
|
|
# Reuse the frozen native verifier; preserve its complete five-stage admission.
|
|
$NativeVerifier=(Get-Item -LiteralPath $NativeVerifier).FullName
|
|
if((Get-FileHash -LiteralPath $NativeVerifier -Algorithm SHA256).Hash.ToLowerInvariant() -cne $NativeVerifierSha256){throw 'Explicit reviewed native verifier changed'}
|
|
$RuntimeDll=(Get-Item -LiteralPath $RuntimeDll).FullName
|
|
if((Get-Item -LiteralPath $RuntimeDll).Length -ne 190464 -or (Get-FileHash -LiteralPath $RuntimeDll -Algorithm SHA256).Hash.ToLowerInvariant() -cne 'aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a'){throw 'Explicit runtime DLL differs from pinned bytes'}
|
|
. $NativeVerifier
|
|
$nativePin=$SourcePin
|
|
$Gate=(Resolve-Path -LiteralPath $Gate).Path
|
|
$expected=Read-NativeJson (Join-Path $Gate 'expected-inputs.json')
|
|
$nativeInputCount=[int]$expected.count
|
|
$build=Assert-BuildEvidence $Gate
|
|
Assert-CurrentNativeSource $Gate
|
|
if(Test-Path -LiteralPath $Output){throw 'Preserve existing build evidence; Output must be new.'}
|
|
$Output=[IO.Path]::GetFullPath($Output)
|
|
$deps=[IO.Path]::GetFullPath((Join-Path $build.target 'debug/deps'))
|
|
$fingerprints=[IO.Path]::GetFullPath((Join-Path $build.target 'debug/.fingerprint'))
|
|
$core=(Get-Item -LiteralPath $CoreRlib).FullName
|
|
$serde=(Get-Item -LiteralPath $SerdeJsonRlib).FullName
|
|
if([IO.Path]::GetDirectoryName($core) -ine $deps -or [IO.Path]::GetDirectoryName($serde) -ine $deps){throw 'Explicit rlibs must belong to the gated target dependency directory.'}
|
|
if([IO.Path]::GetFileName($core) -cnotmatch '^liboa_core-([0-9a-f]{16})\.rlib$'){throw 'Expected an actual oa-core rlib.'}
|
|
$coreSuffix=$Matches[1]
|
|
if([IO.Path]::GetFileName($serde) -cnotmatch '^libserde_json-([0-9a-f]{16})\.rlib$'){throw 'Expected an actual serde_json rlib.'}
|
|
$serdeSuffix=$Matches[1]
|
|
$coreFingerprint=Join-Path $fingerprints ('oa-core-'+$coreSuffix)
|
|
$serdeFingerprint=Join-Path $fingerprints ('serde_json-'+$serdeSuffix)
|
|
$coreRecord=Read-NativeJson (Join-Path $coreFingerprint 'lib-oa_core.json')
|
|
$coreValue=Fingerprint-Value ((Get-Content -Raw -LiteralPath (Join-Path $coreFingerprint 'lib-oa_core')).Trim())
|
|
$serdeValue=Fingerprint-Value ((Get-Content -Raw -LiteralPath (Join-Path $serdeFingerprint 'lib-serde_json')).Trim())
|
|
Assert-Dependency $coreRecord 'serde_json' $serdeValue
|
|
$matching=@(foreach($folder in Get-ChildItem -LiteralPath $fingerprints -Directory -Filter 'oa-desktop-*') {
|
|
$json=Join-Path $folder.FullName 'bin-arcadia-studio.json'
|
|
if(-not (Test-Path -LiteralPath $json)){continue}
|
|
$suffix=$folder.Name.Substring('oa-desktop-'.Length)
|
|
$exe=Join-Path $deps ('arcadia_studio-'+$suffix+'.exe')
|
|
if(-not (Test-Path -LiteralPath $exe)){continue}
|
|
if((Get-Item -LiteralPath $exe).Length -eq $build.binary.bytes -and (Get-NativeHash $exe) -ceq $build.binary.sha256){[pscustomobject]@{folder=$folder.FullName;exe=$exe;json=$json;suffix=$suffix}}
|
|
})
|
|
if($matching.Count -ne 1){throw 'The successful GUI binary must identify exactly one production Cargo fingerprint; do not guess a dependency chain.'}
|
|
$desktop=$matching[0]
|
|
$desktopRecord=Read-NativeJson $desktop.json
|
|
Assert-Dependency $desktopRecord 'oa_core' $coreValue
|
|
if((Get-NativeHash $build.binary.path) -cne $build.binary.sha256 -or (Get-Item -LiteralPath $build.binary.path).Length -ne $build.binary.bytes){throw 'Current GUI artifact changed since successful gate.'}
|
|
|
|
$native=(Resolve-Path -LiteralPath $WindowsImportLibDirectory).Path
|
|
if(-not (Test-Path -LiteralPath $native -PathType Container)){throw 'Native import-library directory is required.'}
|
|
if(-not @(Get-ChildItem -LiteralPath $native -File -Recurse|Where-Object {$_.Extension -in @('.a','.lib')}).Count){throw 'No actual native import libraries found.'}
|
|
# Gate already binds this process-local environment script and external configuration.
|
|
. $build.externalEnvironmentPath
|
|
$rustc=(Get-Command rustc -CommandType Application).Source
|
|
$clang=(Get-Command clang -CommandType Application).Source
|
|
$gateVersions=Read-NativeJson (Join-Path $Gate 'toolchain-versions.json')
|
|
if($rustc -ine $gateVersions.rustcPath -or $clang -ine $gateVersions.clangPath){throw 'Resolved compiler/linker paths differ from the successful gate.'}
|
|
$source=(Get-Item -LiteralPath (Join-Path $PSScriptRoot 'main.rs')).FullName
|
|
$fixture=(Get-Item -LiteralPath (Join-Path $PSScriptRoot '../incident-workspace.arcadia')).FullName
|
|
if((Get-NativeHash $fixture) -cne '74cd2c1dd5eed7eb19895a752bb8ff9ce93655f7638a5312c96f56218568ebb3'){throw 'Embedded original fixture changed.'}
|
|
$oracleSourcePaths=@($PSCommandPath,$source,$fixture,(Join-Path $PSScriptRoot 'check.ps1'),$NativeVerifier)|ForEach-Object {[IO.Path]::GetFullPath($_)}
|
|
function Oracle-BuildInputs {
|
|
$paths=@($oracleSourcePaths)+@($rustc,$clang,$build.externalEnvironmentPath,$desktop.exe,$build.binary.path,$RuntimeDll)
|
|
# Complete rustc dependency search inputs, including proc-macro DLLs; exclude test EXEs/logs.
|
|
$paths+=@(Get-ChildItem -LiteralPath $deps -File|Where-Object {$_.Extension -in @('.rlib','.rmeta','.dll','.lib','.a')}|ForEach-Object {$_.FullName})
|
|
$paths+=@(Get-ChildItem -LiteralPath $native -File -Recurse|ForEach-Object {$_.FullName})
|
|
foreach($folder in @($coreFingerprint,$serdeFingerprint,$desktop.folder)){$paths+=@(Get-ChildItem -LiteralPath $folder -File|ForEach-Object {$_.FullName})}
|
|
$paths+=@((Join-Path $deps ('oa_core-'+$coreSuffix+'.d')),(Join-Path $deps ('serde_json-'+$serdeSuffix+'.d')),(Join-Path $deps ('arcadia_studio-'+$desktop.suffix+'.d')))
|
|
$paths+=@(Get-ChildItem -LiteralPath $Gate -File|Where-Object {$_.Extension -in @('.json','.ps1','.log','.md')}|ForEach-Object {$_.FullName})
|
|
@($paths|ForEach-Object {[IO.Path]::GetFullPath($_)}|Sort-Object -Unique|ForEach-Object {$item=Get-Item -LiteralPath $_;[ordered]@{path=$item.FullName;bytes=$item.Length;sha256=(Get-NativeHash $item.FullName)}})
|
|
}
|
|
[void][IO.Directory]::CreateDirectory($Output)
|
|
$before=@(Oracle-BuildInputs)
|
|
$sourceRecords=@(Select-OracleSources $before $oracleSourcePaths)
|
|
Write-NativeJson (Join-Path $Output 'inputs-before.json') $before
|
|
$PSNativeCommandUseErrorActionPreference=$false
|
|
$rustcVersion=@(& $rustc --version --verbose 2>&1|ForEach-Object {$_.ToString()});$rustcVersionExit=$LASTEXITCODE
|
|
$clangVersion=@(& $clang --version 2>&1|ForEach-Object {$_.ToString()});$clangVersionExit=$LASTEXITCODE
|
|
Write-NativeJson (Join-Path $Output 'toolchain.json') ([ordered]@{rustc=$rustc;rustc_version=$rustcVersion;rustc_version_exit=$rustcVersionExit;clang=$clang;clang_version=$clangVersion;clang_version_exit=$clangVersionExit})
|
|
if($rustcVersionExit -ne 0 -or $clangVersionExit -ne 0){throw 'Actual compiler/linker version query failed; preserve evidence.'}
|
|
if(($rustcVersion -join "`n") -cne (@($gateVersions.rustc) -join "`n") -or ($clangVersion -join "`n") -cne (@($gateVersions.clang) -join "`n")){throw 'Actual compiler/linker versions differ from the successful gate.'}
|
|
$exe=Join-Path $Output 'incident-source-oracle.exe'
|
|
$compilerArgs=@('--edition=2024','-D','warnings',$source,'--extern',('oa_core='+$core),'--extern',('serde_json='+$serde),'-L',('dependency='+$deps),'-L',('native='+$native),'-C',('linker='+$clang),'-o',$exe)
|
|
$start=[DateTime]::UtcNow.ToString('o')
|
|
& $rustc @compilerArgs *> (Join-Path $Output 'compile.log')
|
|
$exit=$LASTEXITCODE
|
|
$end=[DateTime]::UtcNow.ToString('o')
|
|
$after=@();$failure=$null;$unchanged=$false;$runtimeSource=$null;$runtimeCopy=$null
|
|
try {
|
|
$after=@(Oracle-BuildInputs)
|
|
[void](Select-OracleSources $after $oracleSourcePaths)
|
|
Write-NativeJson (Join-Path $Output 'inputs-after.json') $after
|
|
Assert-NativeNames @($before|ForEach-Object {$_.path}) @($after|ForEach-Object {$_.path})
|
|
$byPath=@{};foreach($f in $before){$byPath[$f.path]=$f}
|
|
foreach($f in $after){$old=$byPath[$f.path];if($old.bytes -ne $f.bytes -or $old.sha256 -cne $f.sha256){throw "Build input changed: $($f.path)"}}
|
|
Assert-CurrentNativeSource $Gate
|
|
[void](Assert-BuildEvidence $Gate)
|
|
if($exit -eq 0){
|
|
$runtimeSource=@($before|Where-Object {$_.path -ieq $RuntimeDll})
|
|
if($runtimeSource.Count -ne 1){throw 'Runtime source missing from build manifest'}
|
|
$runtimeSource=$runtimeSource[0]
|
|
$runtimePath=Join-Path $Output 'libunwind.dll'
|
|
[IO.File]::Copy($RuntimeDll,$runtimePath,$false)
|
|
$runtimeCopy=[ordered]@{path=$runtimePath;bytes=(Get-Item -LiteralPath $runtimePath).Length;sha256=(Get-NativeHash $runtimePath)}
|
|
if($runtimeCopy.bytes -ne $runtimeSource.bytes -or $runtimeCopy.sha256 -cne $runtimeSource.sha256){throw 'Adjacent runtime copy differs from captured source'}
|
|
}
|
|
$unchanged=$true
|
|
} catch {$failure=$_.Exception.Message}
|
|
$binary=if(Test-Path -LiteralPath $exe){$item=Get-Item -LiteralPath $exe;[ordered]@{path=$exe;bytes=$item.Length;sha256=(Get-NativeHash $exe)}}else{$null}
|
|
Write-NativeJson (Join-Path $Output 'build-receipt.json') ([ordered]@{schema='scenario_spacing_oracle_build_v2';source_commit=$SourcePin;native_verifier=[ordered]@{path=$NativeVerifier;sha256=$NativeVerifierSha256};runtime_source=$runtimeSource;runtime=$runtimeCopy;source_files=$sourceRecords;gate=$Gate;gate_build_receipt_sha256=(Get-NativeHash (Join-Path $Gate 'build-receipt.json'));command=@($rustc)+$compilerArgs;start_utc=$start;end_utc=$end;exit_code=$exit;inputs_unchanged=$unchanged;postflight_failure=$failure;binary=$binary;fingerprint_chain=[ordered]@{desktop_fingerprint=$desktop.folder;desktop_exe=$desktop.exe;core=$core;core_fingerprint_value=$coreValue;serde_json=$serde;serde_fingerprint_value=$serdeValue};before_manifest_sha256=(Get-NativeHash (Join-Path $Output 'inputs-before.json'));after_manifest_sha256=if(Test-Path -LiteralPath (Join-Path $Output 'inputs-after.json')){Get-NativeHash (Join-Path $Output 'inputs-after.json')}else{$null};toolchain_sha256=(Get-NativeHash (Join-Path $Output 'toolchain.json'));compile_log_sha256=(Get-NativeHash (Join-Path $Output 'compile.log'));oracle_executed=$false;scope='Standalone oracle compilation only; gate hash plus production desktop -> core -> serde Cargo fingerprint linkage and unchanged link inputs. No native acceptance.'})
|
|
if($exit -ne 0 -or -not $unchanged -or $null -eq $binary -or $binary.bytes -le 0 -or $null -eq $runtimeCopy -or $null -ne $failure){throw 'Oracle build or provenance failed; preserve all output.'}
|
|
Write-Output 'Standalone oracle built and hash-bound; oracle and native workflows have not run.'
|