#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()} }