#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. }