Files
ARCADIA-Studio/harness/experiments/navigation-rename-native-a49-20260913/launch.ps1
T

100 lines
7.2 KiB
PowerShell

#Requires -Version 7.0
# Prepared only. Run in a persistent shell; this observer waits for normal close.
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$nativeRoot = $PSScriptRoot
$nativePin = 'a49b0ec7ac4538a372139175386a881645c1a59e'
$nativePackage = Join-Path $nativeRoot 'package'
$nativeProfile = Join-Path $nativeRoot 'profile'
$nativeCwd = Join-Path $nativeRoot 'unrelated-cwd'
$nativeManifest = Get-Content -LiteralPath (Join-Path $nativeRoot 'input-manifest.json') -Raw | ConvertFrom-Json
function Get-NativeHash([string]$Path) { return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() }
function Assert-NativeFiles($Files) {
foreach ($file in $Files) {
$path = [IO.Path]::GetFullPath((Join-Path $nativeRoot $file.path))
if (-not $path.StartsWith($nativeRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) { throw 'Manifest path escapes staging directory.' }
$item = Get-Item -LiteralPath $path -Force
if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 -or $item.Length -ne $file.bytes -or (Get-NativeHash $path) -cne $file.sha256) { throw "Changed launch input: $path" }
}
}
function Write-NativeJson([string]$Path, $Value) {
$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 15) + "`n")
$stream.Write($bytes, 0, $bytes.Length)
$stream.Flush($true)
} finally { $stream.Dispose() }
}
if ($nativeManifest.source_commit -cne $nativePin -or $nativeManifest.verified_input_count -ne 577 -or -not $nativeManifest.before_after_input_files_unchanged) { throw 'Wrong prepared source binding.' }
Assert-NativeFiles $nativeManifest.immutable_files
Assert-NativeFiles $nativeManifest.fixture_files
$build = Get-Content -LiteralPath (Join-Path $nativeRoot 'evidence/build-receipt.json') -Raw | ConvertFrom-Json
$before = Get-Content -LiteralPath (Join-Path $nativeRoot 'evidence/inputs-complete-before.json') -Raw | ConvertFrom-Json
$after = Get-Content -LiteralPath (Join-Path $nativeRoot 'evidence/inputs-complete-after.json') -Raw | ConvertFrom-Json
if ($build.base -cne $nativePin -or $build.cargoExitCode -ne 0 -or -not $build.selectionValid -or $before.base -cne $nativePin -or $after.base -cne $nativePin -or $before.count -ne 577 -or $after.count -ne 577 -or @($before.files).Count -ne 577 -or @($after.files).Count -ne 577) { throw 'Matching successful attempt4 build and 577 input records required.' }
$inputRows = @{}
foreach ($file in $before.files) {
if ($inputRows.ContainsKey($file.path)) { throw 'Duplicate before input.' }
$inputRows[$file.path] = $file
}
foreach ($file in $after.files) {
if (-not $inputRows.ContainsKey($file.path)) { throw 'Unexpected/duplicate after input.' }
$old = $inputRows[$file.path]
if ($old.bytes -ne $file.bytes -or $old.sha256 -cne $file.sha256) { throw 'Build inputs changed.' }
$inputRows.Remove($file.path)
}
if ($inputRows.Count -ne 0) { throw 'Missing after inputs.' }
if ((Get-NativeHash (Join-Path $nativePackage 'libunwind.dll')) -cne 'aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a') { throw 'Unexpected runtime.' }
foreach ($directory in @($nativeProfile,$nativeCwd)) {
if (Test-Path -LiteralPath $directory) { throw "Fresh launch directory required: $directory" }
}
foreach ($name in @('launch.json','normal-close.json','launch-failure.json','logs')) {
if (Test-Path -LiteralPath (Join-Path $nativeRoot $name)) { throw "Refusing to overwrite launch evidence: $name" }
}
if ([string]::IsNullOrWhiteSpace($env:SystemRoot) -or -not (Test-Path -LiteralPath (Join-Path $env:SystemRoot 'System32') -PathType Container)) { throw 'Normal Windows SystemRoot required.' }
$nativePath = $nativePackage + ';' + (Join-Path $env:SystemRoot 'System32') + ';' + $env:SystemRoot + ';' + (Join-Path $env:SystemRoot 'System32/Wbem')
$nativeOut = Join-Path $nativeRoot 'logs/studio.stdout.log'
$nativeErr = Join-Path $nativeRoot 'logs/studio.stderr.log'
$state = [ordered]@{source_commit=$nativePin;manifest_sha256=(Get-NativeHash (Join-Path $nativeRoot 'input-manifest.json'));script_sha256=(Get-NativeHash $PSCommandPath);executable=(Join-Path $nativePackage 'arcadia-studio.exe');binary_sha256=(Get-NativeHash (Join-Path $nativePackage 'arcadia-studio.exe'));localappdata=$nativeProfile;working_directory=$nativeCwd;child_path=$nativePath;arguments=@();environment_scope='Child ProcessStartInfo only';fixture_entry=(Join-Path $nativeRoot 'fixture/project.arcadia');pid=$null;started_utc=$null}
$process = $null; $outStream = $null; $errStream = $null
try {
foreach ($directory in @($nativeProfile,$nativeCwd,(Join-Path $nativeRoot 'logs'))) { [void][IO.Directory]::CreateDirectory($directory) }
$outStream = [IO.File]::Open($nativeOut,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::Read)
$errStream = [IO.File]::Open($nativeErr,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::Read)
$info = [Diagnostics.ProcessStartInfo]::new()
$info.FileName = $state.executable
$info.WorkingDirectory = $nativeCwd
$info.UseShellExecute = $false
$info.CreateNoWindow = $true
$info.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden
$info.RedirectStandardOutput = $true
$info.RedirectStandardError = $true
$info.Environment['LOCALAPPDATA'] = $nativeProfile
$info.Environment['PATH'] = $nativePath
$process = [Diagnostics.Process]::new()
$process.StartInfo = $info
if (-not $process.Start()) { throw 'Process.Start returned false.' }
$outCopy = $process.StandardOutput.BaseStream.CopyToAsync($outStream)
$errCopy = $process.StandardError.BaseStream.CopyToAsync($errStream)
$state.pid = $process.Id
$state.started_utc = $process.StartTime.ToUniversalTime().ToString('o')
Write-NativeJson (Join-Path $nativeRoot 'launch.json') $state
$state | ConvertTo-Json -Depth 15 | Write-Output
$process.WaitForExit()
[Threading.Tasks.Task]::WaitAll([Threading.Tasks.Task[]]@($outCopy,$errCopy))
$outStream.Flush($true); $errStream.Flush($true)
$outStream.Dispose(); $outStream = $null
$errStream.Dispose(); $errStream = $null
Assert-NativeFiles $nativeManifest.immutable_files
Write-NativeJson (Join-Path $nativeRoot 'normal-close.json') ([ordered]@{source_commit=$nativePin;pid=$state.pid;started_utc=$state.started_utc;observed_exit_utc=[DateTime]::UtcNow.ToString('o');exit_code=$process.ExitCode;immutable_inputs_unchanged=$true;stdout_sha256=(Get-NativeHash $nativeOut);stderr_sha256=(Get-NativeHash $nativeErr);scope='Observed bound process exit without forced termination. Close cause and native acceptance require user/root observation; fixture edits are expected and compared separately.'})
} catch {
Write-NativeJson (Join-Path $nativeRoot 'launch-failure.json') ([ordered]@{utc=[DateTime]::UtcNow.ToString('o');pid=$state.pid;error=$_.Exception.Message;owned_process_not_killed=$true})
throw
} finally {
if ($null -ne $outStream) { $outStream.Dispose() }
if ($null -ne $errStream) { $errStream.Dispose() }
if ($null -ne $process) { $process.Dispose() }
}