121 lines
8.4 KiB
PowerShell
121 lines
8.4 KiB
PowerShell
#requires -Version 7.4
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$ArtifactMessages,
|
|
[Parameter(Mandatory = $true)][ValidatePattern('^[A-Za-z0-9_-]+$')][string]$RunName,
|
|
[ValidateRange(1,5)][int]$Iterations = 3,
|
|
[switch]$RunProbe
|
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
$PSNativeCommandUseErrorActionPreference = $false
|
|
$repoRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $PSScriptRoot))
|
|
function ProjectPath([string]$Path) {
|
|
$full = [IO.Path]::GetFullPath($Path, $repoRoot)
|
|
if (-not $full.StartsWith($repoRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) {
|
|
throw "Path escapes this project: $Path"
|
|
}
|
|
return $full
|
|
}
|
|
function Hash([string]$Path) { (Get-FileHash -Algorithm SHA256 -LiteralPath $Path).Hash.ToLowerInvariant() }
|
|
function WriteJson([string]$Path, $Value) {
|
|
if (Test-Path -LiteralPath $Path) { throw "Refusing overwrite: $Path" }
|
|
[IO.File]::WriteAllText($Path, ($Value | ConvertTo-Json -Depth 40) + "`n", [Text.UTF8Encoding]::new($false))
|
|
}
|
|
function VerifyInputs {
|
|
$manifest = Get-Content -Raw -Encoding UTF8 -LiteralPath (Join-Path $PSScriptRoot 'inputs.json') | ConvertFrom-Json
|
|
foreach ($entry in $manifest.files) {
|
|
$path = Join-Path $PSScriptRoot $entry.path
|
|
if ((Hash $path) -ne $entry.sha256 -or (Get-Item -LiteralPath $path).Length -ne $entry.bytes) {
|
|
throw "Frozen input changed: $($entry.path)"
|
|
}
|
|
}
|
|
}
|
|
$messagesPath = ProjectPath $ArtifactMessages
|
|
$runRoot = Join-Path $PSScriptRoot "runs/$RunName"
|
|
if (Test-Path -LiteralPath $runRoot) { throw "Exclusive run name already exists: $RunName" }
|
|
New-Item -ItemType Directory -Path $runRoot | Out-Null
|
|
try {
|
|
VerifyInputs
|
|
$messagesHash = Hash $messagesPath
|
|
$messages = @(Get-Content -Encoding UTF8 -LiteralPath $messagesPath | Where-Object { $_.Trim() } | ForEach-Object { $_ | ConvertFrom-Json -ErrorAction Stop })
|
|
$finished = @($messages | Where-Object { $_.reason -eq 'build-finished' })
|
|
if ($finished.Count -ne 1 -or -not $finished[0].success) { throw 'Require one successful terminal Cargo build-finished record' }
|
|
$bindings = @()
|
|
foreach ($crateName in @('oa_core','serde_json')) {
|
|
$artifacts = @($messages | Where-Object { $_.reason -eq 'compiler-artifact' -and $_.target.name -eq $crateName -and $_.target.kind -contains 'lib' -and -not $_.profile.test })
|
|
if ($artifacts.Count -ne 1) { throw "Require unique actual Cargo library artifact: $crateName" }
|
|
$artifact = $artifacts[0]
|
|
if ($crateName -eq 'oa_core' -and (ProjectPath $artifact.target.src_path) -ne (ProjectPath 'crates/oa-core/src/lib.rs')) {
|
|
throw 'oa_core artifact belongs to another checkout'
|
|
}
|
|
$rlibs = @($artifact.filenames | Where-Object { $_.EndsWith('.rlib') })
|
|
if ($rlibs.Count -ne 1) { throw "Require one emitted rlib for $crateName" }
|
|
$rlib = ProjectPath $rlibs[0]
|
|
$sha = Hash $rlib
|
|
$dir = Split-Path -Parent $rlib
|
|
$bound = $rlib
|
|
if ((Split-Path -Leaf $dir) -ne 'deps') {
|
|
$deps = Join-Path $dir 'deps'
|
|
$matchesByBytes = @(Get-ChildItem -LiteralPath $deps -Filter "lib$crateName-*.rlib" -File | Where-Object { (Hash $_.FullName) -eq $sha })
|
|
if ($matchesByBytes.Count -ne 1) { throw "No unique byte-hash binding for top-level $crateName rlib" }
|
|
$bound = $matchesByBytes[0].FullName
|
|
if (-not [Linq.Enumerable]::SequenceEqual[byte]([IO.File]::ReadAllBytes($rlib), [IO.File]::ReadAllBytes($bound))) { throw 'Artifact bytes differ despite hash match' }
|
|
$dir = $deps
|
|
}
|
|
$namePattern = '^lib' + [regex]::Escape($crateName) + '-([a-f0-9]+)\.rlib$'
|
|
if ((Split-Path -Leaf $bound) -notmatch $namePattern) { throw 'Cannot bind fingerprint from exact dependency filename' }
|
|
$suffix = $Matches[1]
|
|
$packageName = if ($crateName -eq 'oa_core') { 'oa-core' } else { 'serde_json' }
|
|
$fingerprint = Join-Path (Split-Path -Parent $dir) ".fingerprint/$packageName-$suffix"
|
|
$fingerprints = @()
|
|
foreach ($name in @("lib-$crateName", "lib-$crateName.json")) {
|
|
$original = Join-Path $fingerprint $name
|
|
$destination = Join-Path $runRoot "$crateName-$name"
|
|
Copy-Item -LiteralPath $original -Destination $destination
|
|
if ((Hash $original) -ne (Hash $destination)) { throw 'Fingerprint copy differs' }
|
|
$fingerprints += [ordered]@{ original = $original; copy = $destination; sha256 = Hash $original }
|
|
}
|
|
$bindings += [ordered]@{ crate = $crateName; artifact = $artifact; rlib = $rlib; dependencyRlib = $bound; sha256 = $sha; dependencyDirectory = $dir; fingerprints = $fingerprints }
|
|
}
|
|
$deps = @($bindings.dependencyDirectory | Sort-Object -Unique)
|
|
if ($deps.Count -ne 1) { throw 'Dependency artifacts do not share a directory' }
|
|
$rustc = Join-Path $repoRoot '.local/toolchain/rustup/toolchains/stable-x86_64-pc-windows-gnullvm/bin/rustc.exe'
|
|
$linker = Join-Path $repoRoot '.local/toolchain/llvm-mingw-20260826-ucrt-x86_64/bin/clang.exe'
|
|
$runtime = Join-Path (Split-Path -Parent $linker) 'libunwind.dll'
|
|
$env:PATH = "$(Split-Path -Parent $rustc);$(Split-Path -Parent $linker);$env:SystemRoot\System32;$env:SystemRoot"
|
|
$env:CARGO_BUILD_JOBS = '1'
|
|
$env:RUST_BACKTRACE = '0'
|
|
Copy-Item -LiteralPath $messagesPath -Destination (Join-Path $runRoot 'cargo-artifacts.jsonl')
|
|
if ((Hash $messagesPath) -ne $messagesHash -or (Hash (Join-Path $runRoot 'cargo-artifacts.jsonl')) -ne $messagesHash) { throw 'Cargo messages changed during binding' }
|
|
WriteJson (Join-Path $runRoot 'bindings.json') ([ordered]@{ artifactMessages = $messagesPath; artifactMessagesSha256 = $messagesHash; bindings = $bindings; compiler = $rustc; compilerSha256 = Hash $rustc; linker = $linker; linkerSha256 = Hash $linker; runtime = $runtime; runtimeSha256 = Hash $runtime; inputsSha256 = Hash (Join-Path $PSScriptRoot 'inputs.json'); runnerSha256 = Hash $PSCommandPath })
|
|
function Recorded([string]$Name, [string]$Executable, [string[]]$Arguments) {
|
|
$stdout = Join-Path $runRoot "$Name.stdout.log"
|
|
$stderr = Join-Path $runRoot "$Name.stderr.log"
|
|
$receipt = Join-Path $runRoot "$Name.status.json"
|
|
foreach ($file in @($stdout,$stderr,$receipt)) { if (Test-Path -LiteralPath $file) { throw "Refusing overwrite $file" } }
|
|
$start = [DateTime]::UtcNow
|
|
$clock = [Diagnostics.Stopwatch]::StartNew()
|
|
& $Executable @Arguments 1> $stdout 2> $stderr
|
|
$code = $LASTEXITCODE
|
|
$clock.Stop()
|
|
WriteJson $receipt ([ordered]@{ startedUtc = $start.ToString('o'); finishedUtc = [DateTime]::UtcNow.ToString('o'); exitCode = $code; wallSeconds = $clock.Elapsed.TotalSeconds; executable = $Executable; executableSha256 = Hash $Executable; arguments = $Arguments; stdoutSha256 = Hash $stdout; stderrSha256 = Hash $stderr })
|
|
if ($code -ne 0) { throw "Recorded phase $Name failed with exit $code" }
|
|
}
|
|
Recorded 'compiler-version' $rustc @('--version','--verbose')
|
|
$binary = Join-Path $runRoot 'alias-document-probe.exe'
|
|
$compileArguments = @('--edition=2024','--crate-name','alias_document_probe','-Dwarnings','-C','opt-level=0','-C','debug-assertions=yes','-C',"linker=$linker",(Join-Path $PSScriptRoot 'probe.rs'),'-L',"dependency=$($deps[0])",'-o',$binary)
|
|
foreach ($binding in $bindings) { $compileArguments += @('--extern',"$($binding.crate)=$($binding.rlib)") }
|
|
Recorded 'compile' $rustc $compileArguments
|
|
VerifyInputs
|
|
foreach ($binding in $bindings) {
|
|
if ((Hash $binding.rlib) -ne $binding.sha256 -or (Hash $binding.dependencyRlib) -ne $binding.sha256) { throw 'Bound dependency changed during compilation' }
|
|
}
|
|
if ($RunProbe) {
|
|
Recorded 'probe' $binary @((Join-Path $PSScriptRoot 'inputs/release1-baseline/project.arcadia'),(Join-Path $PSScriptRoot 'inputs/cli-preview.json'),$Iterations.ToString())
|
|
VerifyInputs
|
|
}
|
|
WriteJson (Join-Path $runRoot 'result.json') ([ordered]@{ compiled = $true; probeExecuted = [bool]$RunProbe; binarySha256 = Hash $binary; allFrozenInputsUnchanged = $true; scope = 'Private copied Document with actual bound core artifacts; no UI, persistence writes or prepared-capability benchmark' })
|
|
} catch {
|
|
WriteJson (Join-Path $runRoot 'failure.json') ([ordered]@{ atUtc = [DateTime]::UtcNow.ToString('o'); error = $_.ToString(); scope = 'Only recorded phase exits establish execution; never overwrite or silently retry this run name' })
|
|
throw
|
|
}
|