65 lines
6.7 KiB
PowerShell
65 lines
6.7 KiB
PowerShell
#Requires -Version 7.0
|
|
param([ValidateRange(1,99)][int]$Run=1)
|
|
$ErrorActionPreference='Stop'
|
|
. (Join-Path $PSScriptRoot 'verify.ps1')
|
|
$manifestPath=Join-Path $PSScriptRoot 'input-manifest.json'
|
|
$stagePath=Join-Path $PSScriptRoot 'staging-record.json'
|
|
$manifest=Read-NativeJson $manifestPath
|
|
$stage=Read-NativeJson $stagePath
|
|
if ($manifest.source_commit -notmatch '^[0-9a-f]{40}$' -or $stage.source_commit -cne $manifest.source_commit) { throw 'Wrong staged candidate' }
|
|
Assert-NativeFiles @($stage.manifest)
|
|
Assert-NativeFiles $manifest.immutable_files
|
|
$stageHash=Get-NativeHash $stagePath
|
|
$bindingPath=Join-Path $PSScriptRoot 'fresh-node-binding.json'
|
|
$bindingBefore=if(Test-Path -LiteralPath $bindingPath){Get-NativeRecord $bindingPath}else{$null}
|
|
$working=if ($Run -eq 1) {$manifest.initial_working.path} else {$manifest.saved_working_path}
|
|
$runRoot=Join-Path $PSScriptRoot ('runs/run-{0:d2}' -f $Run)
|
|
$profile=Join-Path $PSScriptRoot 'profile'
|
|
$cwd=Join-Path $PSScriptRoot 'unrelated-cwd'
|
|
$previousHash=$null
|
|
if (Test-Path -LiteralPath $runRoot) { throw 'Never overwrite existing run evidence' }
|
|
if ($Run -eq 1) {
|
|
if (Test-Path -LiteralPath (Join-Path $PSScriptRoot 'runs')) { throw 'First run requires fresh runs directory' }
|
|
Assert-NativeFiles @($manifest.initial_working)
|
|
if (-not $manifest.saved_working_prelaunch_absent -or (Test-Path -LiteralPath $manifest.saved_working_path)) {throw 'Save As source destination must be absent before first launch'}
|
|
foreach($dir in @($profile,$cwd)) { if (-not (Test-Path -LiteralPath $dir -PathType Container) -or @(Get-ChildItem -LiteralPath $dir -Force).Count) { throw 'First run requires staged empty private directories' } }
|
|
} else {
|
|
$prior=Join-Path $PSScriptRoot ('runs/run-{0:d2}' -f ($Run-1))
|
|
$priorClose=Read-NativeJson (Join-Path $prior 'normal-close.json')
|
|
$priorLaunch=Read-NativeJson (Join-Path $prior 'launch.json')
|
|
if ($priorClose.exit_code -ne 0 -or $priorClose.stdout.bytes -ne 0 -or $priorClose.stderr.bytes -ne 0 -or -not $priorClose.immutable_inputs_unchanged -or $priorClose.source_commit -cne $manifest.source_commit -or $priorClose.launch_sha256 -cne (Get-NativeHash (Join-Path $prior 'launch.json')) -or $priorLaunch.manifest_sha256 -cne (Get-NativeHash $manifestPath) -or $priorLaunch.pid -ne $priorClose.pid -or $priorLaunch.started_utc -cne $priorClose.started_utc) { throw 'Previous successful bound close required' }
|
|
Assert-NativeFiles @($priorClose.working_file,$priorClose.stdout,$priorClose.stderr)
|
|
$previousHash=Get-NativeHash (Join-Path $prior 'normal-close.json')
|
|
}
|
|
$package=Join-Path $PSScriptRoot 'package'
|
|
$childPath=$package+';'+(Join-Path $env:SystemRoot 'System32')+';'+$env:SystemRoot+';'+(Join-Path $env:SystemRoot 'System32/Wbem')
|
|
$state=[ordered]@{run=$Run;source_commit=$manifest.source_commit;evaluator_source_commit=$manifest.evaluator_source_commit;manifest_sha256=(Get-NativeHash $manifestPath);staging_sha256=$stageHash;script_sha256=(Get-NativeHash $PSCommandPath);native_plan_sha256=$manifest.native_plan_sha256;previous_close_sha256=$previousHash;binary=$manifest.binary;runtime=$manifest.runtime;working_file_before=(Get-NativeRecord $working);saved_working_path=$manifest.saved_working_path;fresh_node_binding_before=$bindingBefore;localappdata=$profile;working_directory=$cwd;child_path=$childPath;arguments=@();pid=$null;started_utc=$null;environment_scope='Child ProcessStartInfo only'}
|
|
$process=$null;$outStream=$null;$errStream=$null
|
|
try {
|
|
[void][IO.Directory]::CreateDirectory((Join-Path $runRoot 'logs'))
|
|
$stdout=Join-Path $runRoot 'logs/studio.stdout.log';$stderr=Join-Path $runRoot 'logs/studio.stderr.log'
|
|
$outStream=[IO.File]::Open($stdout,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::Read)
|
|
$errStream=[IO.File]::Open($stderr,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::Read)
|
|
$info=[Diagnostics.ProcessStartInfo]::new();$info.FileName=$manifest.binary.path;$info.WorkingDirectory=$cwd;$info.UseShellExecute=$false;$info.CreateNoWindow=$true;$info.WindowStyle=[Diagnostics.ProcessWindowStyle]::Hidden;$info.RedirectStandardOutput=$true;$info.RedirectStandardError=$true
|
|
$info.Environment['LOCALAPPDATA']=$profile;$info.Environment['PATH']=$childPath
|
|
$process=[Diagnostics.Process]::new();$process.StartInfo=$info
|
|
if (-not $process.Start()) { throw 'Process start failed' }
|
|
$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 $runRoot '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
|
|
$unchanged=$true;$verificationError=$null
|
|
try { if($null -ne $bindingBefore){Assert-NativeFiles @($bindingBefore)}; Assert-NativeFiles $manifest.immutable_files; Assert-NativeFiles @($stage.manifest);if ((Get-NativeHash $stagePath) -cne $stageHash) {throw 'Staging receipt changed'} } catch {$unchanged=$false;$verificationError=$_.Exception.Message}
|
|
$working=$manifest.saved_working_path
|
|
Write-NativeJson (Join-Path $runRoot 'normal-close.json') ([ordered]@{run=$Run;source_commit=$manifest.source_commit;evaluator_source_commit=$manifest.evaluator_source_commit;launch_sha256=(Get-NativeHash (Join-Path $runRoot 'launch.json'));pid=$state.pid;started_utc=$state.started_utc;observed_exit_utc=[DateTime]::UtcNow.ToString('o');exit_code=$process.ExitCode;immutable_inputs_unchanged=$unchanged;verification_error=$verificationError;baseline_file=(Get-NativeRecord (Join-Path $PSScriptRoot 'baseline/B.input.json'));working_file=(Get-NativeRecord $working);fresh_node_binding_after=$(if(Test-Path -LiteralPath $bindingPath){Get-NativeRecord $bindingPath}else{$null});stdout=(Get-NativeRecord $stdout);stderr=(Get-NativeRecord $stderr);scope='Observed process exit only; close cause and native interaction acceptance require root observation. No forced termination by observer.'})
|
|
if (-not $unchanged) {throw $verificationError}
|
|
if ($process.ExitCode -ne 0 -or (Get-Item -LiteralPath $stdout).Length -ne 0 -or (Get-Item -LiteralPath $stderr).Length -ne 0) {throw 'Nonzero process exit or nonempty GUI logs; preserve evidence and investigate'}
|
|
} catch {
|
|
Write-NativeJson (Join-Path $runRoot 'observer-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()}
|
|
}
|