Files
ARCADIA-Studio/harness/experiments/canvas-camera-a57c334-20260912/launch-native-a57c334-camera.ps1
T

184 lines
9.8 KiB
PowerShell

#Requires -Version 7.0
# PREPARED ONLY. Root decides when to invoke. Keep this logger alive until normal GUI close.
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$nativeRoot = Join-Path $PSScriptRoot 'native-a57c334-camera'
$nativeFreeze = 'a57c334b302eb13352b4bb65df1c75d94d002348'
$nativeManifestHash = '293892C2F430D6EADB58C6678F3C1B346742B3AA4D2FE5CE3729F896EAD2C2DB'
$nativeInputHash = '47FEC9CADC0E7FAE99D789376C111C02EEAAB7FAC3740DE48174A06F61FE16B3'
$nativeGuiHash = '69DBB90FA299B3B3D90140C7934891F79166025893AF9B35B2B8D754A37A0D40'
$nativeDllHash = 'AA282FA47BB2C4E210CEA5E470CDBDCA58568F0BE0BC73E404D86AB9CD95EF6A'
$nativeBin = Join-Path $nativeRoot 'bin'
$nativeProfile = Join-Path $nativeRoot 'profile'
$nativeCwd = Join-Path $nativeRoot 'unrelated-cwd'
$nativePath = 'C:\Windows\System32;C:\Windows;' + $nativeBin
$nativeReceipt = Join-Path $nativeRoot 'launch.json'
$nativeTerminal = Join-Path $nativeRoot 'normal-close.json'
$nativeStdout = Join-Path $nativeRoot 'logs/studio.stdout.log'
$nativeStderr = Join-Path $nativeRoot 'logs/studio.stderr.log'
$nativeUtf8 = [Text.UTF8Encoding]::new($false)
function Read-NativeItem([string]$Path) {
$item = Get-Item -LiteralPath $Path -Force
$ancestor = $item
while ($null -ne $ancestor) {
if (($ancestor.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
throw "Reparse path requires separate review: $($ancestor.FullName)"
}
$ancestor = if ($ancestor -is [IO.FileInfo]) { $ancestor.Directory } else { $ancestor.Parent }
}
return $item
}
function Read-NativeFile([string]$Path) {
$item = Read-NativeItem $Path
if ($item.PSIsContainer) { throw "Expected file: $Path" }
return [ordered]@{ path = $item.FullName; bytes = $item.Length; sha256 = (Get-FileHash -LiteralPath $item.FullName -Algorithm SHA256).Hash }
}
function Write-NativeRecord($Record, [IO.FileStream]$Stream) {
$bytes = $nativeUtf8.GetBytes(($Record | ConvertTo-Json -Depth 12) + "`n")
$Stream.Position = 0
$Stream.SetLength(0)
$Stream.Write($bytes, 0, $bytes.Length)
$Stream.Flush($true)
}
function Open-NativeOutput([string]$Path) {
return [IO.File]::Open($Path, [IO.FileMode]::CreateNew, [IO.FileAccess]::ReadWrite, [IO.FileShare]::Read)
}
# Source provenance is the closed a57 gate, never current dirty sources or target/debug.
$nativeManifestFile = Read-NativeFile (Join-Path $nativeRoot 'preparation-manifest.json')
if ($nativeManifestFile.sha256 -ne $nativeManifestHash) { throw 'Preparation manifest changed.' }
$nativeManifest = Get-Content -LiteralPath $nativeManifestFile.path -Raw | ConvertFrom-Json
if ($nativeManifest.source_commit -ne $nativeFreeze -or $nativeManifest.input_manifest_sha256 -ne $nativeInputHash -or $nativeManifest.frozen_input_count -ne 529) { throw 'Unexpected frozen input identity.' }
foreach ($file in $nativeManifest.files) {
$path = [IO.Path]::GetFullPath((Join-Path $nativeRoot $file.target))
if (-not $path.StartsWith($nativeRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) { throw 'Manifest path escapes private root.' }
$actual = Read-NativeFile $path
if ($actual.sha256 -ne $file.sha256 -or $actual.bytes -ne $file.bytes) { throw "Prepared input changed: $($file.target)" }
}
$nativeInputs = Read-NativeFile (Join-Path $nativeRoot 'provenance/inputs.json')
if ($nativeInputs.sha256 -ne $nativeInputHash) { throw 'Frozen input manifest changed.' }
$nativeBuildFile = Read-NativeFile (Join-Path $nativeRoot 'provenance/studio-build-status.json')
$nativeBuild = Get-Content -LiteralPath $nativeBuildFile.path -Raw | ConvertFrom-Json
if ($nativeBuild.exit -ne 0 -or $nativeBuild.name -ne 'canvas-union-windows-20260911/studio-build' -or [string]::IsNullOrWhiteSpace($nativeBuild.completed_utc)) { throw 'Matching completed build required.' }
$nativeGui = Read-NativeFile (Join-Path $nativeBin 'arcadia-studio.exe')
$nativeDll = Read-NativeFile (Join-Path $nativeBin 'libunwind.dll')
if ($nativeGui.sha256 -ne $nativeGuiHash -or $nativeGui.bytes -ne 113028608 -or $nativeDll.sha256 -ne $nativeDllHash -or $nativeDll.bytes -ne 190464) { throw 'Immutable GUI/runtime mismatch.' }
if (-not [string]::Equals($env:SystemRoot, 'C:\Windows', [StringComparison]::OrdinalIgnoreCase)) { throw 'This recorded launch requires SystemRoot C:\Windows.' }
foreach ($directory in @($nativeProfile, $nativeCwd, (Join-Path $nativeRoot 'logs'), 'C:\Windows\System32')) {
if (-not (Read-NativeItem $directory).PSIsContainer) { throw "Required directory missing: $directory" }
}
foreach ($empty in @($nativeProfile, $nativeCwd)) {
if (@(Get-ChildItem -LiteralPath $empty -Force).Count -ne 0) { throw "Fresh empty directory required: $empty" }
}
foreach ($output in @($nativeReceipt, $nativeTerminal, $nativeStdout, $nativeStderr)) {
if (Test-Path -LiteralPath $output) { throw "Never overwrite prior launch evidence: $output" }
}
$nativeState = [ordered]@{
source_commit = $nativeFreeze
scope = 'Independent pre-grapheme a57 camera/clipping/zoom/hit session; no package or new combined Unicode acceptance implied.'
preparation_manifest = $nativeManifestFile
build_inputs_manifest = $nativeInputs
build_status_file = $nativeBuildFile
build_status = $nativeBuild
studio = $nativeGui
runtime = $nativeDll
launch_script = (Read-NativeFile $PSCommandPath)
process_path = $nativePath
isolated_localappdata = $nativeProfile
working_directory = $nativeCwd
arguments = @()
environment_scope = 'Child ProcessStartInfo only; other normal inherited environment retained.'
window_style = 'Hidden'
use_shell_execute = $false
create_no_window = $true
stdout = $nativeStdout
stderr = $nativeStderr
pid = $null
launch_requested_utc = $null
process_started_utc = $null
state = 'preflight_passed'
acceptance = 'Pending root observation and independent source/model checks.'
}
$nativeReceiptStream = $null
$nativeOutStream = $null
$nativeErrStream = $null
$nativeProcess = $null
$nativeStarted = $false
try {
# Exclusive receipt reservation also serializes accidental concurrent invocations.
$nativeReceiptStream = Open-NativeOutput $nativeReceipt
Write-NativeRecord $nativeState $nativeReceiptStream
$nativeOutStream = Open-NativeOutput $nativeStdout
$nativeErrStream = Open-NativeOutput $nativeStderr
$nativeInfo = [Diagnostics.ProcessStartInfo]::new()
$nativeInfo.FileName = $nativeGui.path
$nativeInfo.WorkingDirectory = $nativeCwd
$nativeInfo.UseShellExecute = $false
$nativeInfo.CreateNoWindow = $true
$nativeInfo.WindowStyle = [Diagnostics.ProcessWindowStyle]::Hidden
$nativeInfo.RedirectStandardOutput = $true
$nativeInfo.RedirectStandardError = $true
$nativeInfo.Environment['PATH'] = $nativePath
$nativeInfo.Environment['LOCALAPPDATA'] = $nativeProfile
foreach ($file in @($nativeGui, $nativeDll, $nativeManifestFile, $nativeInputs, $nativeBuildFile)) {
if ((Read-NativeFile $file.path).sha256 -ne $file.sha256) { throw "Launch-critical input changed: $($file.path)" }
}
$nativeProcess = [Diagnostics.Process]::new()
$nativeProcess.StartInfo = $nativeInfo
$nativeState.launch_requested_utc = [DateTime]::UtcNow.ToString('o')
if (-not $nativeProcess.Start()) { throw 'Process.Start returned false.' }
$nativeStarted = $true
$nativeState.pid = $nativeProcess.Id
# Pump both raw streams concurrently; no line decoding/newline normalization.
$nativeOutCopy = $nativeProcess.StandardOutput.BaseStream.CopyToAsync($nativeOutStream)
$nativeErrCopy = $nativeProcess.StandardError.BaseStream.CopyToAsync($nativeErrStream)
$nativeState.process_started_utc = $nativeProcess.StartTime.ToUniversalTime().ToString('o')
$nativeState.state = 'launched'
Write-NativeRecord $nativeState $nativeReceiptStream
$nativeReceiptStream.Dispose()
$nativeReceiptStream = $null
$nativeState | ConvertTo-Json -Depth 12 | Write-Output
# Deliberately stays attached as a logger. Root uses a running shell session and closes GUI normally.
$nativeProcess.WaitForExit()
[Threading.Tasks.Task]::WaitAll([Threading.Tasks.Task[]]@($nativeOutCopy, $nativeErrCopy))
$nativeOutStream.Flush($true)
$nativeErrStream.Flush($true)
$nativeOutStream.Dispose()
$nativeOutStream = $null
$nativeErrStream.Dispose()
$nativeErrStream = $null
$nativeClosed = [ordered]@{
source_commit = $nativeFreeze
pid = $nativeState.pid
process_started_utc = $nativeState.process_started_utc
observed_exit_utc = [DateTime]::UtcNow.ToString('o')
exit_code = $nativeProcess.ExitCode
scope = 'Process termination and closed raw logs only; UI acceptance and reason for close require root observation.'
stdout = (Read-NativeFile $nativeStdout)
stderr = (Read-NativeFile $nativeStderr)
studio_readback = (Read-NativeFile $nativeGui.path)
runtime_readback = (Read-NativeFile $nativeDll.path)
}
$nativeCloseStream = Open-NativeOutput $nativeTerminal
try { Write-NativeRecord $nativeClosed $nativeCloseStream } finally { $nativeCloseStream.Dispose() }
$nativeClosed | ConvertTo-Json -Depth 12 | Write-Output
} catch {
if ($null -ne $nativeReceiptStream) {
$nativeState.state = if ($nativeStarted) { 'launched_record_error' } else { 'launch_failed' }
$nativeState['error'] = $_.Exception.Message
Write-NativeRecord $nativeState $nativeReceiptStream
}
throw
} finally {
if ($null -ne $nativeReceiptStream) { $nativeReceiptStream.Dispose() }
if ($null -ne $nativeOutStream) { $nativeOutStream.Dispose() }
if ($null -ne $nativeErrStream) { $nativeErrStream.Dispose() }
if ($null -ne $nativeProcess) { $nativeProcess.Dispose() }
# Never kill an application here. Root owns every native interaction and any recovery.
}