#Requires -Version 5.1

[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [ValidateSet('ValidatePlan', 'ExecuteAuthorizedGateA')]
    [string] $Action,

    [Parameter(Mandatory = $true)]
    [string] $PlanPath,

    [Parameter(Mandatory = $true)]
    [ValidatePattern('^[0-9A-Fa-f]{64}$')]
    [string] $ExpectedPlanSha256,

    [string] $AuthorizationPath,

    [ValidatePattern('^$|^[0-9A-Fa-f]{64}$')]
    [string] $ExpectedAuthorizationSha256,

    [string] $EvidencePath
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$utf8NoBom = [Text.UTF8Encoding]::new($false, $true)
$repositoryRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
$expectedRemote = 'https://gitea.comtropy.synology.me/Wickedness/MBN_STOCK_WEBVIEW.git'
$expectedUrl = 'https://legacy-parity.mbn.local/index.html'
$expectedPackageName = 'Wickedness.MBNStockWebView.LegacyParity'
$expectedPackageFamily = 'Wickedness.MBNStockWebView.LegacyParity_qbv3jkvsn3aj0'
$expectedPackageFullName = 'Wickedness.MBNStockWebView.LegacyParity_0.1.0.0_x64__qbv3jkvsn3aj0'
$expectedExecutableName = 'MBN_STOCK_WEBVIEW.LegacyParityApp.exe'
$approvedSceneDirectory = 'C:\Users\MD\source\repos\MBN_STOCK_N\MBN_STOCK_N\bin\Debug\Cuts'
$approvedCutPath = Join-Path $approvedSceneDirectory '5001.t2s'
$approvedCutSha256 = '99CE3B689A42D8C42BEB09A86FA10C2D7C1AEF4F50D324D81276C1A1E4C4D8A7'
$approvedMsixPath = Join-Path $repositoryRoot 'src\MBN_STOCK_WEBVIEW.LegacyParityApp\AppPackages\MBN_STOCK_WEBVIEW.LegacyParityApp_0.1.0.0_x64_Test\MBN_STOCK_WEBVIEW.LegacyParityApp_0.1.0.0_x64.msix'
$approvedMsixSha256 = '0E44F87FDD31852B7DDFCD6CF3A6325DA674F1F5A23AD39CB262A725AEB84367'
$approvedDatabaseConfigPath = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData)) 'MBN_STOCK_WEBVIEW\Config\database.local.json'
$userProfilePath = [Environment]::GetFolderPath([Environment+SpecialFolder]::UserProfile)
$approvedNodePath = [IO.Path]::GetFullPath((Join-Path $userProfilePath '.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe'))
$expectedWindowTitle = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('Vi1TdG9jayDspp3qtozsoJXrs7TshqHstpzsi5zsiqTthZwgZm9yIOunpOydvOqyveygnFRWICgyNi4wMy4yNik='))
$expectedMarketText = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('7L2U7Iqk7ZS8'))
$expectedStockName = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('7IK87ISx7KCE7J6Q'))
$expectedCutLabel = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('MeyXtO2MkOq4sOuzuF/tmITsnqzqsIA='))
$expectedGraphicType = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('MeyXtO2MkOq4sOuzuA=='))
$expectedSubtype = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('7ZiE7J6s6rCA'))
$liveAuthorizationName = 'MBN_STOCK_PLAYOUT_AUTHORIZE_LIVE_OUTPUT'
$liveAuthorizationValue = 'I_AUTHORIZE_LIVE_PROGRAM_OUTPUT_FOR_THIS_LAUNCH'
$capabilityName = 'MBN_LEGACY_PGM_GATE_A_PREPARE_CAPABILITY'
$pgmProcessIdName = 'MBN_LEGACY_PGM_GATE_A_PGM_PID'
$pgmStartTimeUtcTicksName = 'MBN_LEGACY_PGM_GATE_A_PGM_START_TIME_UTC_TICKS'
$gateExpiresAtUtcTicksName = 'MBN_LEGACY_PGM_GATE_A_EXPIRES_AT_UTC_TICKS'
# This stays false until the native bridge independently consumes and enforces the
# one-shot capability. Changing it requires a clean pushed commit and a newly sealed plan.
$nativeOneShotPrepareGateReady = $true
$script:application = $null
$script:launchAttempted = $false
$script:applicationLaunched = $false
$script:gateConfigInstalled = $false
$script:configRestored = $false
$script:configRestoreAttempts = 0
$script:nodeInvocations = 0
$script:nodeExitCode = $null
$script:nodeStillRunning = $false
$script:phase = 'plan-validation'
$script:status = 'NOT_STARTED'
$script:failure = $null
$script:backupPath = $null
$script:nodeEvidencePath = $null
$script:screenshotPath = $null
$script:plan = $null
$script:authorization = $null
$script:planHash = $ExpectedPlanSha256.ToUpperInvariant()
$script:authorizationHash = $null
$script:gateCapability = $null
$script:gateCapabilitySha256 = $null
$script:gateCapabilityGenerations = 0
$script:nodePermitRequests = 0
$script:nodePermitDeliveries = 0
$script:nodePermitClientProcessId = $null
$script:packagePermitRequests = 0
$script:packagePermitDeliveries = 0
$script:packagePermitClientProcessId = $null
$script:authorizationExpiresAtUtcTicks = $null
$script:startedAtUtc = [DateTime]::UtcNow
$script:fileLeases = New-Object 'Collections.Generic.List[IO.FileStream]'

if ($null -eq ('LegacyPgmPreparePackageIdentity' -as [type])) {
Add-Type -TypeDefinition @'
using System;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.Text;
public static class LegacyPgmPreparePackageIdentity
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    private static extern int GetPackageFullName(IntPtr process, ref int length, StringBuilder value);
    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool GetNamedPipeClientProcessId(IntPtr pipe, out uint clientProcessId);
    public static string Read(IntPtr process)
    {
        int length = 0;
        if (GetPackageFullName(process, ref length, null) != 122 || length < 2 || length > 4096)
            throw new InvalidOperationException("Package identity is unavailable.");
        var value = new StringBuilder(length);
        if (GetPackageFullName(process, ref length, value) != 0 || value.Length == 0)
            throw new InvalidOperationException("Package identity could not be read.");
        return value.ToString();
    }
    public static int ReadNamedPipeClientProcessId(SafePipeHandle pipe)
    {
        if (pipe == null || pipe.IsInvalid || pipe.IsClosed)
            throw new InvalidOperationException("Named pipe handle is unavailable.");
        uint processId;
        if (!GetNamedPipeClientProcessId(pipe.DangerousGetHandle(), out processId) || processId == 0 || processId > Int32.MaxValue)
            throw new InvalidOperationException("Named pipe client identity is unavailable.");
        return (int)processId;
    }
}
'@
}

function Fail([string] $Message) { throw [InvalidOperationException]::new($Message) }

function New-CurrentUserOneShotPipe([string] $Purpose) {
    if ([string]::IsNullOrWhiteSpace($Purpose) -or $Purpose -cnotmatch '^[A-Za-z]+$') {
        Fail 'The one-shot pipe purpose is invalid.'
    }
    $identity = [Security.Principal.WindowsIdentity]::GetCurrent()
    if ($null -eq $identity.User) { Fail 'The current user SID is unavailable for pipe isolation.' }
    $security = [IO.Pipes.PipeSecurity]::new()
    $security.SetOwner($identity.User)
    $security.SetAccessRuleProtection($true, $false)
    $security.AddAccessRule([IO.Pipes.PipeAccessRule]::new(
        $identity.User,
        [IO.Pipes.PipeAccessRights]::ReadWrite,
        [Security.AccessControl.AccessControlType]::Allow))
    $name = "MBN_STOCK_WEBVIEW.GateA.$Purpose.$(([Guid]::NewGuid().ToString('N')).ToUpperInvariant())"
    $options = [IO.Pipes.PipeOptions]::Asynchronous -bor [IO.Pipes.PipeOptions]::WriteThrough
    $stream = [IO.Pipes.NamedPipeServerStream]::new(
        $name,
        # Node's libuv named-pipe client opens a duplex handle. The protocol is
        # still parent-write-only; InOut is required only for Windows handle compatibility.
        [IO.Pipes.PipeDirection]::InOut,
        1,
        [IO.Pipes.PipeTransmissionMode]::Byte,
        $options,
        4096,
        4096,
        $security)
    try {
        return [pscustomobject][ordered]@{
            Name = $name
            Stream = $stream
            Connection = $stream.WaitForConnectionAsync()
        }
    }
    catch {
        $stream.Dispose()
        throw
    }
}

function Close-OneShotPipe([object] $Pipe) {
    if ($null -eq $Pipe) { return }
    try { $Pipe.Stream.Dispose() } catch { }
}

function FullPath([string] $Path, [string] $Name) {
    if ([string]::IsNullOrWhiteSpace($Path) -or -not [IO.Path]::IsPathRooted($Path)) {
        Fail "$Name must be an absolute path."
    }
    return [IO.Path]::GetFullPath($Path)
}

function SamePath([string] $Left, [string] $Right) {
    return [string]::Equals(
        [IO.Path]::GetFullPath($Left).TrimEnd('\'),
        [IO.Path]::GetFullPath($Right).TrimEnd('\'),
        [StringComparison]::OrdinalIgnoreCase)
}

function Test-Within([string] $Child, [string] $Parent) {
    $childFull = [IO.Path]::GetFullPath($Child)
    $parentPrefix = [IO.Path]::GetFullPath($Parent).TrimEnd('\') + '\'
    return $childFull.StartsWith($parentPrefix, [StringComparison]::OrdinalIgnoreCase)
}

function Assert-NoReparse([string] $Path, [string] $Name) {
    $current = [IO.Path]::GetFullPath($Path)
    while (-not [string]::IsNullOrWhiteSpace($current)) {
        if ([IO.File]::Exists($current) -or [IO.Directory]::Exists($current)) {
            if (([IO.File]::GetAttributes($current) -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
                Fail "$Name contains a reparse point."
            }
        }
        $parent = [IO.Directory]::GetParent($current)
        if ($null -eq $parent) { break }
        $current = $parent.FullName
    }
}

function Convert-Hash([byte[]] $Bytes) {
    $algorithm = [Security.Cryptography.SHA256]::Create()
    try { return ([BitConverter]::ToString($algorithm.ComputeHash($Bytes))).Replace('-', '') }
    finally { $algorithm.Dispose() }
}

function Read-SealedJson([string] $Path, [string] $ExpectedHash, [string] $Name) {
    $full = FullPath $Path $Name
    if (-not [IO.File]::Exists($full)) { Fail "$Name does not exist." }
    Assert-NoReparse $full $Name
    $bytes = [IO.File]::ReadAllBytes($full)
    try {
        if ($bytes.Length -eq 0 -or $bytes.Length -gt 2MB) { Fail "$Name has an unsafe size." }
        if ((Convert-Hash $bytes) -cne $ExpectedHash.ToUpperInvariant()) { Fail "$Name raw-byte SHA-256 changed." }
        if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
            Fail "$Name must be UTF-8 without BOM."
        }
        try { $text = $utf8NoBom.GetString($bytes) }
        catch { Fail "$Name is not strict UTF-8." }
        try { $value = $text | ConvertFrom-Json -ErrorAction Stop }
        catch { Fail "$Name is not valid JSON." }
        return [pscustomobject]@{ Path = $full; Length = $bytes.Length; Value = $value }
    }
    finally { [Array]::Clear($bytes, 0, $bytes.Length) }
}

function Require([object] $Object, [string] $Name, [string] $Context) {
    if ($null -eq $Object -or -not ($Object.PSObject.Properties.Name -ccontains $Name)) {
        Fail "$Context.$Name is required."
    }
    return $Object.$Name
}

function Assert-ExactProperties([object] $Object, [string[]] $Expected, [string] $Context) {
    if ($null -eq $Object) { Fail "$Context is required." }
    $actual = @($Object.PSObject.Properties.Name)
    if ($actual.Count -ne $Expected.Count) { Fail "$Context property count is not exact." }
    foreach ($name in $Expected) {
        if (@($actual | Where-Object { $_ -ceq $name }).Count -ne 1) { Fail "$Context property set is not exact: $name." }
    }
}

function Assert-JsonInt([object] $Value, [int] $Expected, [string] $Name) {
    if ($Value -isnot [int] -or [int]$Value -ne $Expected) { Fail "$Name must be JSON integer $Expected." }
}

function Assert-JsonBool([object] $Value, [bool] $Expected, [string] $Name) {
    if ($Value -isnot [bool] -or [bool]$Value -ne $Expected) { Fail "$Name must be JSON boolean $Expected." }
}

function Assert-JsonString([object] $Value, [string] $Expected, [string] $Name) {
    if ($Value -isnot [string] -or [string]$Value -cne $Expected) { Fail "$Name must be the exact JSON string." }
}

function Assert-FilePin([object] $Pin, [string] $Name) {
    Assert-ExactProperties $Pin @('path','length','sha256') $Name
    if ($Pin.path -isnot [string] -or $Pin.sha256 -isnot [string] -or
        ($Pin.length -isnot [int] -and $Pin.length -isnot [long])) {
        Fail "$Name pin JSON types are invalid."
    }
    $path = FullPath ([string]$Pin.path) "$Name.path"
    if (-not [IO.File]::Exists($path)) { Fail "$Name is missing." }
    Assert-NoReparse $path $Name
    $item = Get-Item -LiteralPath $path -Force
    if ([int64]$Pin.length -ne [int64]$item.Length -or
        [string]$Pin.sha256 -cnotmatch '^[0-9A-F]{64}$' -or
        (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash -cne [string]$Pin.sha256) {
        Fail "$Name pin changed."
    }
    return $path
}

function Add-ReadLease([string] $Path, [string] $Name) {
    try {
        $stream = [IO.File]::Open($Path,[IO.FileMode]::Open,[IO.FileAccess]::Read,[IO.FileShare]::Read)
        [void]$script:fileLeases.Add($stream)
    }
    catch { Fail "$Name could not be frozen against write/delete for this Gate A run." }
}

function Get-RuntimeTreePin([string] $Root) {
    $fullRoot = FullPath $Root 'package.installLocation'
    Assert-NoReparse $fullRoot 'package.installLocation'
    $files = @([IO.Directory]::GetFiles($fullRoot, '*', [IO.SearchOption]::AllDirectories))
    if ($files.Count -eq 0 -or $files.Count -gt 4096) { Fail 'The runtime tree count is unsafe.' }
    $rootPrefix = $fullRoot.TrimEnd('\') + '\'
    $names = @($files | ForEach-Object {
        Assert-NoReparse $_ 'runtime file'
        if (-not $_.StartsWith($rootPrefix, [StringComparison]::OrdinalIgnoreCase)) { Fail 'Runtime file escaped root.' }
        $name = $_.Substring($rootPrefix.Length).Replace('\','/')
        if ($name.Contains('|') -or $name.Contains("`r") -or $name.Contains("`n")) { Fail 'Unsafe runtime filename.' }
        $name
    })
    [Array]::Sort($names, [StringComparer]::Ordinal)
    $builder = [Text.StringBuilder]::new()
    foreach ($name in $names) {
        $file = Join-Path $fullRoot $name.Replace('/','\')
        $item = Get-Item -LiteralPath $file -Force
        [void]$builder.Append($name).Append('|').Append(([int64]$item.Length).ToString([Globalization.CultureInfo]::InvariantCulture)).Append('|').Append((Get-FileHash $file -Algorithm SHA256).Hash).Append("`n")
    }
    $bytes = $utf8NoBom.GetBytes($builder.ToString())
    try { return [pscustomobject]@{ fileCount = $names.Length; manifestSha256 = Convert-Hash $bytes } }
    finally { [Array]::Clear($bytes,0,$bytes.Length) }
}

function Add-RuntimeTreeReadLeases([string] $Root) {
    $fullRoot = FullPath $Root 'package.installLocation'
    Assert-NoReparse $fullRoot 'package.installLocation'
    $files = @([IO.Directory]::GetFiles($fullRoot, '*', [IO.SearchOption]::AllDirectories))
    if ($files.Count -eq 0 -or $files.Count -gt 4096) {
        Fail 'The runtime tree lease count is unsafe.'
    }
    [Array]::Sort($files, [StringComparer]::OrdinalIgnoreCase)
    foreach ($file in $files) {
        Assert-NoReparse $file 'runtime file lease'
        Add-ReadLease $file 'runtime file'
    }
}

function Assert-ClosedBudgets([object] $Budgets, [string] $Context) {
    $names = @('connect','prepare','takeIn','next','pageNext','takeOut','databaseWrite','appClose','retry')
    Assert-ExactProperties $Budgets $names $Context
    $expected = [ordered]@{connect=1;prepare=1;takeIn=0;next=0;pageNext=0;takeOut=0;databaseWrite=0;appClose=0;retry=0}
    foreach ($name in $expected.Keys) { Assert-JsonInt $Budgets.$name ([int]$expected[$name]) "$Context.$name" }
}

function Assert-Plan([object] $Plan) {
    Assert-ExactProperties $Plan @(
        'schemaVersion','kind','roundId','createdAtUtc','repository','package','automation',
        'configuration','cut','selector','k3d','pgm','webView','approvalPolicy','budgets','safety') 'plan'
    Assert-JsonInt $Plan.schemaVersion 1 'plan.schemaVersion'
    Assert-JsonString $Plan.kind 'LegacyPgmPrepareGatePlan' 'plan.kind'
    if ($Plan.roundId -isnot [string] -or [string]$Plan.roundId -cnotmatch '^MBNWEB-[0-9]{8}-[A-Z0-9][A-Z0-9-]{0,31}$') { Fail 'Plan roundId is invalid.' }
    Assert-ExactProperties $Plan.repository @('path','branch','commit','upstream','remoteUrl') 'plan.repository'
    if (-not (SamePath ([string]$Plan.repository.path) $repositoryRoot) -or
        [string]$Plan.repository.branch -cne 'main' -or [string]$Plan.repository.upstream -cne 'origin/main' -or
        [string]$Plan.repository.remoteUrl -cne $expectedRemote -or [string]$Plan.repository.commit -cnotmatch '^[0-9a-f]{40}$') {
        Fail 'Plan repository pin is invalid.'
    }
    Assert-ExactProperties $Plan.package @(
        'name','familyName','fullName','publisher','version','architecture','applicationId',
        'installLocation','executable','msix','runtimeTree') 'plan.package'
    if ([string]$Plan.package.name -cne $expectedPackageName -or
        [string]$Plan.package.familyName -cne $expectedPackageFamily -or
        [string]$Plan.package.fullName -cne $expectedPackageFullName -or
        [string]$Plan.package.publisher -cne 'CN=Comtrophy' -or [string]$Plan.package.version -cne '0.1.0.0' -or
        [string]$Plan.package.architecture -cne 'X64' -or [string]$Plan.package.applicationId -cne 'LegacyParityApp') {
        Fail 'Plan package pin is invalid.'
    }
    $expectedInstallLocation = Join-Path $repositoryRoot 'src\MBN_STOCK_WEBVIEW.LegacyParityApp\bin\x64\Release\net8.0-windows10.0.19041.0\win-x64'
    if (-not (SamePath ([string]$Plan.package.installLocation) $expectedInstallLocation)) {
        Fail 'Plan package install location is not the exact Release x64 output.'
    }
    if (-not (SamePath ([string]$Plan.package.executable.path) (Join-Path $expectedInstallLocation $expectedExecutableName))) {
        Fail 'Plan package executable escaped the exact Release x64 install location.'
    }
    if (-not (SamePath ([string]$Plan.package.msix.path) $approvedMsixPath) -or
        [string]$Plan.package.msix.sha256 -cne $approvedMsixSha256) {
        Fail 'Plan MSIX is not the exact Release x64 AppPackages artifact.'
    }
    Assert-ExactProperties $Plan.automation @('orchestrator','node','helper') 'plan.automation'
    if (-not (SamePath ([string]$Plan.automation.node.path) $approvedNodePath) -or
        [IO.Path]::GetFileName([string]$Plan.automation.node.path) -cne 'node.exe') {
        Fail 'Plan Node runtime is not the approved canonical runtime.'
    }
    Assert-ExactProperties $Plan.configuration @('original','gate','database','sceneDirectory') 'plan.configuration'
    if (SamePath ([string]$Plan.configuration.original.path) ([string]$Plan.configuration.gate.path)) {
        Fail 'Plan original and Gate configuration paths must differ.'
    }
    if (-not (SamePath ([string]$Plan.configuration.database.path) $approvedDatabaseConfigPath)) {
        Fail 'Plan database configuration is not the exact local application configuration.'
    }
    Assert-ExactProperties $Plan.cut @('sceneCode','pageNumberOneBased','file') 'plan.cut'
    Assert-JsonString $Plan.cut.sceneCode '5001' 'plan.cut.sceneCode'
    Assert-JsonInt $Plan.cut.pageNumberOneBased 1 'plan.cut.pageNumberOneBased'
    if (-not [IO.Directory]::Exists([string]$Plan.configuration.sceneDirectory) -or
        -not (SamePath ([string]$Plan.configuration.sceneDirectory) $approvedSceneDirectory) -or
        -not (SamePath ([string]$Plan.cut.file.path) $approvedCutPath) -or
        [string]$Plan.cut.file.sha256 -cne $approvedCutSha256) {
        Fail 'Plan scene directory or 5001 cut path is not exact.'
    }
    Assert-ExactProperties $Plan.selector @(
        'marketText','groupCode','stockName','stockCode','cutLabel','graphicType','subtype','normalizedSubtype','sceneAlias') 'plan.selector'
    $selectorExpected = [ordered]@{
        marketText=$expectedMarketText;groupCode='KOSPI';stockName=$expectedStockName;stockCode='005930'
        cutLabel=$expectedCutLabel;graphicType=$expectedGraphicType;subtype=$expectedSubtype
        normalizedSubtype='CURRENT';sceneAlias='5001'
    }
    foreach ($name in $selectorExpected.Keys) {
        Assert-JsonString $Plan.selector.$name ([string]$selectorExpected[$name]) "plan.selector.$name"
    }
    Assert-ExactProperties $Plan.k3d @('native','interop') 'plan.k3d'
    Assert-ExactProperties $Plan.pgm @(
        'processId','startTimeUtc','processName','executable','windowTitle','listenerAddress','host','port') 'plan.pgm'
    Assert-JsonInt $Plan.pgm.port 30001 'plan.pgm.port'
    Assert-JsonString $Plan.pgm.windowTitle 'PGM' 'plan.pgm.windowTitle'
    Assert-JsonString $Plan.pgm.listenerAddress '0.0.0.0' 'plan.pgm.listenerAddress'
    Assert-JsonString $Plan.pgm.host '127.0.0.1' 'plan.pgm.host'
    if ($Plan.pgm.processId -isnot [int] -or [int]$Plan.pgm.processId -le 0 -or
        $Plan.pgm.startTimeUtc -isnot [string] -or $Plan.pgm.processName -isnot [string]) { Fail 'Plan PGM pin is invalid.' }
    Assert-ExactProperties $Plan.webView @('address','port','url') 'plan.webView'
    Assert-JsonString $Plan.webView.address '127.0.0.1' 'plan.webView.address'
    Assert-JsonString $Plan.webView.url $expectedUrl 'plan.webView.url'
    if ($Plan.webView.port -isnot [int] -or [int]$Plan.webView.port -lt 1024 -or [int]$Plan.webView.port -gt 65535 -or
        [int]$Plan.webView.port -eq 30001) { Fail 'Plan WebView pin is invalid.' }
    Assert-ExactProperties $Plan.approvalPolicy @(
        'requiresSeparateAuthorization','maximumAgeSeconds','maximumClockSkewSeconds') 'plan.approvalPolicy'
    Assert-JsonBool $Plan.approvalPolicy.requiresSeparateAuthorization $true 'plan.approvalPolicy.requiresSeparateAuthorization'
    Assert-JsonInt $Plan.approvalPolicy.maximumAgeSeconds 900 'plan.approvalPolicy.maximumAgeSeconds'
    Assert-JsonInt $Plan.approvalPolicy.maximumClockSkewSeconds 5 'plan.approvalPolicy.maximumClockSkewSeconds'
    Assert-ClosedBudgets $Plan.budgets 'plan.budgets'
    Assert-ExactProperties $Plan.safety @(
        'automaticConnectOnly','nodeHelperInvocationMaximum','nodeHelperFailStopRequired',
        'packageWrapperFailStopRequired',
        'persistentGateConfigModeDryRun',
        'restoreOriginalBeforePrepare','nativeOneShotPrepareGateRequired',
        'leaveApplicationAndSessionUntouched','forceCloseForbidden',
        'automaticSessionCleanupForbiddenAfterLaunch','retryForbidden') 'plan.safety'
    Assert-JsonInt $Plan.safety.nodeHelperInvocationMaximum 1 'plan.safety.nodeHelperInvocationMaximum'
    foreach ($property in @($Plan.safety.PSObject.Properties | Where-Object {
        [string]$_.Name -cne 'nodeHelperInvocationMaximum'
    })) {
        Assert-JsonBool $property.Value $true "plan.safety.$($property.Name)"
    }
}

function Parse-CanonicalUtc([string] $Text, [string] $Name) {
    $value = [DateTimeOffset]::MinValue
    if (-not [DateTimeOffset]::TryParseExact(
        $Text, "yyyy-MM-dd'T'HH:mm:ss.fff'Z'", [Globalization.CultureInfo]::InvariantCulture,
        [Globalization.DateTimeStyles]::AssumeUniversal, [ref]$value)) { Fail "$Name is not canonical UTC." }
    return $value.ToUniversalTime()
}

function Assert-OriginalConfigDryRun([object] $Pin) {
    $capture = Read-SealedJson ([string]$Pin.path) ([string]$Pin.sha256) 'original config'
    $modeProperties = @($capture.Value.PSObject.Properties | Where-Object {
        [string]::Equals($_.Name, 'mode', [StringComparison]::OrdinalIgnoreCase)
    })
    if ($modeProperties.Count -ne 1 -or
        -not [string]::Equals([string]$modeProperties[0].Value, 'DryRun', [StringComparison]::Ordinal)) {
        Fail 'The original persistent playout configuration is not uniquely DryRun.'
    }
}

function Assert-GateConfigSafety([object] $Configuration) {
    $capture = Read-SealedJson ([string]$Configuration.gate.path) ([string]$Configuration.gate.sha256) 'Gate config'
    $config = $capture.Value
    $names = @(
        'mode','host','port','tcpMode','clientPort','outputChannel','layoutIndex',
        'legacySceneFadeDuration','legacySceneBackgroundKind','legacySceneBackgroundAssetPath',
        'legacySceneBackgroundVideoLoopCount','legacySceneBackgroundVideoLoopInfinite',
        'legacyBackgroundDirectory','sceneDirectory','testProcessWindowTitlePattern',
        'testSceneAllowlist','trustedLiveOutputEnabled','queueCapacity',
        'connectTimeoutMilliseconds','operationTimeoutMilliseconds','disconnectTimeoutMilliseconds',
        'processPollIntervalMilliseconds','reconnectDelayMilliseconds','maximumReconnectAttempts',
        'reconnectEnabled','maximumAutomaticRefreshesPerTakeIn')
    Assert-ExactProperties $config $names 'Gate config JSON'
    if ($config.testSceneAllowlist -isnot [Array]) { Fail 'Gate config.testSceneAllowlist must be a JSON array.' }
    $allowlist = @($config.testSceneAllowlist)
    Assert-JsonString $config.mode 'DryRun' 'Gate config.mode'
    Assert-JsonString $config.host '127.0.0.1' 'Gate config.host'
    Assert-JsonInt $config.port 30001 'Gate config.port'
    Assert-JsonInt $config.tcpMode 1 'Gate config.tcpMode'
    Assert-JsonInt $config.clientPort 0 'Gate config.clientPort'
    Assert-JsonInt $config.layoutIndex 10 'Gate config.layoutIndex'
    Assert-JsonInt $config.legacySceneFadeDuration 6 'Gate config.legacySceneFadeDuration'
    Assert-JsonString $config.legacySceneBackgroundKind 'None' 'Gate config.legacySceneBackgroundKind'
    Assert-JsonInt $config.legacySceneBackgroundVideoLoopCount 2004 'Gate config.legacySceneBackgroundVideoLoopCount'
    Assert-JsonBool $config.legacySceneBackgroundVideoLoopInfinite $true 'Gate config.legacySceneBackgroundVideoLoopInfinite'
    Assert-JsonBool $config.trustedLiveOutputEnabled $true 'Gate config.trustedLiveOutputEnabled'
    Assert-JsonInt $config.queueCapacity 64 'Gate config.queueCapacity'
    Assert-JsonInt $config.connectTimeoutMilliseconds 5000 'Gate config.connectTimeoutMilliseconds'
    Assert-JsonInt $config.operationTimeoutMilliseconds 5000 'Gate config.operationTimeoutMilliseconds'
    Assert-JsonInt $config.disconnectTimeoutMilliseconds 3000 'Gate config.disconnectTimeoutMilliseconds'
    Assert-JsonInt $config.processPollIntervalMilliseconds 1000 'Gate config.processPollIntervalMilliseconds'
    Assert-JsonInt $config.reconnectDelayMilliseconds 1000 'Gate config.reconnectDelayMilliseconds'
    Assert-JsonInt $config.maximumReconnectAttempts 0 'Gate config.maximumReconnectAttempts'
    Assert-JsonBool $config.reconnectEnabled $false 'Gate config.reconnectEnabled'
    Assert-JsonInt $config.maximumAutomaticRefreshesPerTakeIn 0 'Gate config.maximumAutomaticRefreshesPerTakeIn'
    if ($null -ne $config.outputChannel -or $null -ne $config.legacySceneBackgroundAssetPath -or
        $null -ne $config.legacyBackgroundDirectory -or $null -ne $config.testProcessWindowTitlePattern -or
        -not (SamePath ([string]$config.sceneDirectory) ([string]$Configuration.sceneDirectory)) -or
        $allowlist.Count -ne 1 -or [string]$allowlist[0] -cne '5001' -or
        $allowlist[0] -isnot [string]) {
        Fail 'The Gate config is not persistent DryRun with the closed 5001-only safety settings.'
    }
}

function Assert-Authorization([object] $Authorization, [object] $Plan) {
    Assert-ExactProperties $Authorization @(
        'schemaVersion','kind','roundId','planSha256','authorizedAtUtc','expiresAtUtc',
        'authorizationStatement','currentK3dLicenseValid',
        'runningTornadoIsLicensedMainProgram','currentPgmApproved','budgets') 'authorization'
    Assert-JsonInt $Authorization.schemaVersion 1 'authorization.schemaVersion'
    Assert-JsonString $Authorization.kind 'LegacyPgmPrepareGateAuthorization' 'authorization.kind'
    Assert-JsonString $Authorization.roundId ([string]$Plan.roundId) 'authorization.roundId'
    Assert-JsonString $Authorization.planSha256 $script:planHash 'authorization.planSha256'
    Assert-JsonString $Authorization.authorizationStatement 'I_AUTHORIZE_CURRENT_PGM_LIVE_CONNECT_ONCE_AND_PREPARE_5001_PAGE_1_ONCE' 'authorization.authorizationStatement'
    Assert-JsonBool $Authorization.currentK3dLicenseValid $true 'authorization.currentK3dLicenseValid'
    Assert-JsonBool $Authorization.runningTornadoIsLicensedMainProgram $true 'authorization.runningTornadoIsLicensedMainProgram'
    Assert-JsonBool $Authorization.currentPgmApproved $true 'authorization.currentPgmApproved'
    if ($Authorization.authorizedAtUtc -isnot [string] -or $Authorization.expiresAtUtc -isnot [string]) {
        Fail 'Authorization UTC fields must be JSON strings.'
    }
    Assert-ClosedBudgets $Authorization.budgets 'authorization.budgets'
    $authorized = Parse-CanonicalUtc ([string]$Authorization.authorizedAtUtc) 'authorization.authorizedAtUtc'
    $expires = Parse-CanonicalUtc ([string]$Authorization.expiresAtUtc) 'authorization.expiresAtUtc'
    $now = [DateTimeOffset]::UtcNow
    if ($expires -le $authorized -or ($expires - $authorized).TotalSeconds -gt 900 -or
        $now -lt $authorized.AddSeconds(-5) -or $now -ge $expires.AddSeconds(-5)) { Fail 'Gate A authorization is not currently unexpired.' }
    return $expires
}

function Invoke-Git([string[]] $Arguments) {
    $output = & git -C $repositoryRoot @Arguments 2>&1
    if ($LASTEXITCODE -ne 0) { Fail "Git read failed: $($Arguments -join ' ')." }
    return (($output | ForEach-Object { [string]$_ }) -join "`n").Trim()
}

function Assert-CleanPushedCommit([object] $Repository) {
    if ((Invoke-Git @('status','--porcelain=v1','--untracked-files=all')).Length -ne 0 -or
        (Invoke-Git @('rev-parse','HEAD')) -cne [string]$Repository.commit -or
        (Invoke-Git @('symbolic-ref','--short','HEAD')) -cne 'main' -or
        (Invoke-Git @('rev-parse','@{upstream}')) -cne [string]$Repository.commit -or
        (Invoke-Git @('remote','get-url','origin')) -cne $expectedRemote -or
        (Invoke-Git @('rev-list','--left-right','--count','HEAD...@{upstream}')) -cne "0`t0") {
        Fail 'The repository is not the exact clean pushed main commit.'
    }
}

function Assert-NoUnsafeEnvironment {
    foreach ($scope in @([EnvironmentVariableTarget]::Process,[EnvironmentVariableTarget]::User,[EnvironmentVariableTarget]::Machine)) {
        foreach ($nameObject in [Environment]::GetEnvironmentVariables($scope).Keys) {
            $name = [string]$nameObject
            if ($name -like 'MBN_STOCK_PLAYOUT_*' -or $name -like 'MBN_STOCK_K3D_*' -or
                $name -like 'WEBVIEW2_*' -or $name -like 'COREWEBVIEW2_*' -or
                $name -like 'DOTNET_*' -or $name -like 'CORECLR_*' -or
                $name -like 'COR_*' -or $name -like 'COMPlus_*' -or
                $name -like 'MBN_STOCK_ORACLE_*' -or $name -like 'MBN_STOCK_MARIADB_*' -or
                $name -like 'MBN_STOCK_DB_*' -or
                $name -in @($capabilityName,$pgmProcessIdName,$pgmStartTimeUtcTicksName,$gateExpiresAtUtcTicksName)) {
                Fail "A reserved launch environment variable is already present: $scope/$name."
            }
        }
    }
}

function Assert-Pgm([object] $Pgm) {
    $process = Get-Process -Id ([int]$Pgm.processId) -ErrorAction Stop
    $process.Refresh()
    $start = [DateTime]::ParseExact([string]$Pgm.startTimeUtc, "yyyy-MM-dd'T'HH:mm:ss.fffffff'Z'", [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::AssumeUniversal).ToUniversalTime()
    $exe = FullPath ([string]$Pgm.executable.path) 'PGM executable'
    if ($process.HasExited -or [string]$process.ProcessName -cne [string]$Pgm.processName -or
        -not (SamePath $process.Path $exe) -or $process.StartTime.ToUniversalTime().Ticks -ne $start.Ticks -or
        [string]$process.MainWindowTitle -cne 'PGM') { Fail 'The current PGM process identity changed.' }
    $listeners = @(Get-NetTCPConnection -State Listen -LocalPort 30001 -ErrorAction Stop)
    if ($listeners.Count -ne 1 -or [int]$listeners[0].OwningProcess -ne $process.Id -or
        [string]$listeners[0].LocalAddress -cne '0.0.0.0') { Fail 'The current PGM listener identity changed.' }
    $tornadoProcesses = @(Get-Process -ErrorAction Stop | Where-Object { [string]$_.ProcessName -like 'Tornado2*' })
    if ($tornadoProcesses.Count -ne 1 -or $tornadoProcesses[0].Id -ne $process.Id) {
        Fail 'The Tornado process set is not the single frozen current PGM.'
    }
}

function Assert-K3dRegistration([object] $K3d) {
    $inspector = Join-Path $PSScriptRoot 'Inspect-K3DRegistration.ps1'
    if (-not [IO.File]::Exists($inspector)) { Fail 'The read-only K3D registration inspector is unavailable.' }
    $reports = @(& $inspector)
    if ($reports.Count -ne 1) { Fail 'The K3D registration inspection result is not unique.' }
    $report = $reports[0]
    if ([string]$report.Status -cne 'Valid' -or [string]$report.RegistryHive -cne 'HKLM' -or
        [string]$report.RegistryView -cne 'Registry64' -or [string]$report.CurrentUserOverrides -cne 'None' -or
        $report.ComActivated -ne $false -or -not (SamePath ([string]$report.TypeLibPath) ([string]$K3d.native.path))) {
        Fail 'The exact read-only K3D x64 registration pin changed.'
    }
    $classes = @($report.Classes)
    if ($classes.Count -ne 2 -or @($classes | Where-Object {
        -not (SamePath ([string]$_.ServerPath) ([string]$K3d.native.path)) -or
        [string]$_.ServerMachine -cne 'AMD64' -or [string]$_.ThreadingModel -cne 'Apartment' -or
        $_.ReciprocalMapping -ne $true
    }).Count -ne 0) { Fail 'The K3D COM class registration pin changed.' }
    $nativeDirectory = [IO.Directory]::GetParent([string]$K3d.native.path)
    $vendorRoot = $nativeDirectory.Parent.Parent.Parent
    if ($null -eq $vendorRoot -or
        -not (SamePath (Join-Path $vendorRoot.FullName 'Bin\x64\C#\Interop.K3DAsyncEngineLib.dll') ([string]$K3d.interop.path))) {
        Fail 'The K3D interop assembly is not the exact registered vendor sibling.'
    }
}

function Assert-ExactPackageRegistration([object] $Package) {
    $packages = @(Get-AppxPackage -Name $expectedPackageName -ErrorAction Stop)
    if ($packages.Count -ne 1 -or -not $packages[0].IsDevelopmentMode -or
        [string]$packages[0].SignatureKind -cne 'None' -or [string]$packages[0].Status -cne 'Ok' -or
        [string]$packages[0].PackageFullName -cne $expectedPackageFullName -or
        [string]$packages[0].PackageFamilyName -cne $expectedPackageFamily -or
        [string]$packages[0].Publisher -cne 'CN=Comtrophy' -or
        [string]$packages[0].Version -cne '0.1.0.0' -or [string]$packages[0].Architecture -cne 'X64' -or
        -not (SamePath ([string]$packages[0].InstallLocation) ([string]$Package.installLocation))) {
        Fail 'Exact Release x64 development package registration is absent.'
    }
}

function Assert-ReadOnlyPlanEnvironment([object] $Plan) {
    Assert-CleanPushedCommit $Plan.repository
    Assert-NoUnsafeEnvironment
    if (-not (SamePath (Assert-FilePin $Plan.automation.orchestrator 'Gate orchestrator') $PSCommandPath) -or
        -not (SamePath (Assert-FilePin $Plan.automation.helper 'Node helper') (Join-Path $PSScriptRoot 'Test-LegacyPgmPrepareGate.mjs'))) {
        Fail 'Automation file identity changed.'
    }
    [void](Assert-FilePin $Plan.automation.node 'Node runtime')
    [void](Assert-FilePin $Plan.package.msix 'Release x64 MSIX')
    [void](Assert-FilePin $Plan.package.executable 'package executable')
    $tree = Get-RuntimeTreePin ([string]$Plan.package.installLocation)
    if ($tree.fileCount -ne [int]$Plan.package.runtimeTree.fileCount -or
        $tree.manifestSha256 -cne [string]$Plan.package.runtimeTree.manifestSha256) {
        Fail 'Package runtime tree changed.'
    }
    [void](Assert-FilePin $Plan.configuration.original 'original config')
    Assert-OriginalConfigDryRun $Plan.configuration.original
    [void](Assert-FilePin $Plan.configuration.gate 'Gate config')
    Assert-GateConfigSafety $Plan.configuration
    [void](Assert-FilePin $Plan.configuration.database 'database config')
    [void](Assert-FilePin $Plan.cut.file '5001 cut')
    [void](Assert-FilePin $Plan.k3d.native 'K3D native binary')
    [void](Assert-FilePin $Plan.k3d.interop 'K3D interop assembly')
    [void](Assert-FilePin $Plan.pgm.executable 'PGM executable')
    Assert-NoReparse ([string]$Plan.configuration.sceneDirectory) 'scene directory'
    Assert-K3dRegistration $Plan.k3d
    Assert-Pgm $Plan.pgm
    Assert-ExactPackageRegistration $Plan.package
    if (@(Get-Process -Name ([IO.Path]::GetFileNameWithoutExtension($expectedExecutableName)) -ErrorAction SilentlyContinue).Count -ne 0 -or
        @(Get-NetTCPConnection -State Listen -LocalPort ([int]$Plan.webView.port) -ErrorAction SilentlyContinue).Count -ne 0) {
        Fail 'An existing application or CDP endpoint is present.'
    }
    return [pscustomobject][ordered]@{
        filePinsValidated = 12
        runtimeTreeValidated = $true
        packageRegistrationValidated = $true
        currentPgmValidated = $true
        k3dRegistrationValidatedReadOnly = $true
        k3dComActivated = $false
    }
}

function Assert-Application([Diagnostics.Process] $Process, [string] $Executable, [bool] $RequireReadyWindow = $true) {
    $Process.Refresh()
    if ($Process.HasExited -or -not (SamePath $Process.Path $Executable) -or
        [LegacyPgmPreparePackageIdentity]::Read($Process.Handle) -cne $expectedPackageFullName) {
        Fail 'The package application identity changed.'
    }
    if ($RequireReadyWindow) {
        if ($Process.MainWindowHandle -eq [IntPtr]::Zero -or
            [string]$Process.MainWindowTitle -cne $expectedWindowTitle) {
            Fail 'The package application window is not ready or changed identity.'
        }
    }
}

function Test-DescendsFrom([int] $ProcessId, [int] $AncestorId) {
    $seen = [Collections.Generic.HashSet[int]]::new(); $current = $ProcessId
    while ($current -gt 0 -and $seen.Add($current)) {
        if ($current -eq $AncestorId) { return $true }
        $row = Get-CimInstance Win32_Process -Filter "ProcessId = $current" -ErrorAction SilentlyContinue
        if ($null -eq $row) { return $false }; $current = [int]$row.ParentProcessId
    }
    return $false
}

function Assert-Cdp([int] $Port, [int] $AppPid) {
    $listeners = @(Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction Stop)
    if ($listeners.Count -ne 1 -or [string]$listeners[0].LocalAddress -cne '127.0.0.1') { Fail 'The exact IPv4 loopback CDP listener is unavailable.' }
    $owner = Get-Process -Id ([int]$listeners[0].OwningProcess) -ErrorAction Stop
    if ([IO.Path]::GetFileName($owner.Path) -cne 'msedgewebview2.exe' -or -not (Test-DescendsFrom $owner.Id $AppPid)) {
        Fail 'The CDP listener is not owned by the exact app WebView child.'
    }
}

function Wait-ExactTarget([int] $Port, [int] $AppPid) {
    $deadline = [DateTime]::UtcNow.AddSeconds(30)
    while ([DateTime]::UtcNow -lt $deadline) {
        Assert-Application $script:application ([string]$script:plan.package.executable.path)
        Assert-Pgm $script:plan.pgm
        try {
            $readinessListeners = @(Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue)
            if ($readinessListeners.Count -eq 0) {
                Start-Sleep -Milliseconds 250
                continue
            }
            Assert-Cdp $Port $AppPid
            $client = [Net.WebClient]::new()
            try {
                $client.Proxy = $null
                $text = $client.DownloadString("http://127.0.0.1:$Port/json/list")
                $targets = @($text | ConvertFrom-Json -ErrorAction Stop)
            }
            finally { $client.Dispose() }
            $exact = @($targets | Where-Object { [string]$_.type -ceq 'page' -and [string]$_.url -ceq $expectedUrl })
            if ($exact.Count -gt 1) { Fail 'More than one exact WebView target exists.' }
            if ($exact.Count -eq 1) {
                $uri = [Uri]([string]$exact[0].webSocketDebuggerUrl)
                if ($uri.Scheme -cne 'ws' -or $uri.Host -cne '127.0.0.1' -or $uri.Port -ne $Port -or
                    -not $uri.AbsolutePath.StartsWith('/devtools/page/', [StringComparison]::Ordinal)) { Fail 'The exact target WebSocket URL is unsafe.' }
                return
            }
        }
        catch [InvalidOperationException] { throw }
        catch { }
        Start-Sleep -Milliseconds 250
    }
    Fail 'The exact WebView target did not appear before timeout.'
}

function Write-BytesExclusive([string] $Path, [byte[]] $Bytes) {
    $stream = [IO.File]::Open($Path,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::Read)
    try { $stream.Write($Bytes,0,$Bytes.Length); $stream.Flush($true) } finally { $stream.Dispose() }
}

function New-GateCapability {
    $script:gateCapabilityGenerations++
    if ($script:gateCapabilityGenerations -ne 1) {
        Fail 'The native Gate A capability generation budget was exceeded.'
    }
    $bytes = New-Object byte[] 32
    $rng = [Security.Cryptography.RandomNumberGenerator]::Create()
    try {
        $rng.GetBytes($bytes)
        return ([BitConverter]::ToString($bytes)).Replace('-','')
    }
    finally {
        $rng.Dispose()
        [Array]::Clear($bytes,0,$bytes.Length)
    }
}

function Install-GateConfiguration([object] $Configuration, [string] $RoundId) {
    $original = Assert-FilePin $Configuration.original 'original config'
    $gate = Assert-FilePin $Configuration.gate 'Gate config'
    if (-not (SamePath $original (Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData)) 'MBN_STOCK_WEBVIEW\Config\playout.local.json'))) {
        Fail 'The original config is not the exact application config path.'
    }
    $script:backupPath = Join-Path ([IO.Path]::GetDirectoryName($original)) ("playout.local.$RoundId.backup")
    $temporary = $original + '.' + [Guid]::NewGuid().ToString('N') + '.gate.tmp'
    if ([IO.File]::Exists($script:backupPath) -or [IO.File]::Exists($temporary)) { Fail 'A stale Gate config artifact exists.' }
    $bytes = [IO.File]::ReadAllBytes($gate)
    try { Write-BytesExclusive $temporary $bytes }
    finally { [Array]::Clear($bytes,0,$bytes.Length) }
    try {
        [IO.File]::Replace($temporary,$original,$script:backupPath,$true)
        $script:gateConfigInstalled = $true
    }
    finally { if ([IO.File]::Exists($temporary)) { [IO.File]::Delete($temporary) } }
    if ((Get-FileHash $original -Algorithm SHA256).Hash -cne [string]$Configuration.gate.sha256 -or
        (Get-FileHash $script:backupPath -Algorithm SHA256).Hash -cne [string]$Configuration.original.sha256) {
        Fail 'Atomic Gate config installation could not be verified.'
    }
}

function Restore-OriginalConfiguration([object] $Configuration) {
    if (-not $script:gateConfigInstalled -or $script:configRestored) { return }
    $script:configRestoreAttempts++
    if ($script:configRestoreAttempts -ne 1) { Fail 'The exact original config restore one-shot budget was exceeded.' }
    $original = [string]$Configuration.original.path
    if (-not [IO.File]::Exists($script:backupPath) -or
        (Get-FileHash $script:backupPath -Algorithm SHA256).Hash -cne [string]$Configuration.original.sha256 -or
        (Get-FileHash $original -Algorithm SHA256).Hash -cne [string]$Configuration.gate.sha256) {
        Fail 'The exact config restore precondition changed.'
    }
    [IO.File]::Replace($script:backupPath,$original,$null,$true)
    if ((Get-FileHash $original -Algorithm SHA256).Hash -cne [string]$Configuration.original.sha256 -or
        [IO.File]::Exists($script:backupPath)) { Fail 'The original config restore is not exact.' }
    $script:configRestored = $true
}

function Assert-PackagePermitClient(
    [object] $Pipe,
    [DateTime] $NotBeforeUtc,
    [string] $ExpectedExecutable) {
    $clientProcessId = [LegacyPgmPreparePackageIdentity]::ReadNamedPipeClientProcessId(
        $Pipe.Stream.SafePipeHandle)
    $process = Get-Process -Id $clientProcessId -ErrorAction Stop
    $process.Refresh()
    if ($process.HasExited -or -not (SamePath $process.Path $ExpectedExecutable) -or
        $process.StartTime.ToUniversalTime() -lt $NotBeforeUtc.AddSeconds(-1) -or
        [LegacyPgmPreparePackageIdentity]::Read($process.Handle) -cne $expectedPackageFullName) {
        Fail 'The package launch permit pipe client identity is not exact.'
    }
    $script:packagePermitClientProcessId = $clientProcessId
    return $process
}

function Stop-PackageWrapperFailStop([Diagnostics.Process] $Process) {
    # This stops only the short-lived package-context control wrapper. It never
    # closes, terminates, disconnects, or commands the app, PGM, Tornado, or K3D.
    if ($null -eq $Process) { return }
    $exitObserved = $false
    try { $exitObserved = [bool]$Process.WaitForExit(0) } catch { }
    if (-not $exitObserved) {
        try { $Process.Kill() }
        catch {
            try { $exitObserved = [bool]$Process.WaitForExit(0) } catch { }
            if (-not $exitObserved) {
                Fail 'OUTCOME_UNKNOWN: The package-context wrapper could not be fail-stopped or proven exited.'
            }
        }
        if (-not $exitObserved) {
            try { $exitObserved = [bool]$Process.WaitForExit(5000) } catch { }
        }
    }
    if (-not $exitObserved) {
        Fail 'OUTCOME_UNKNOWN: The package-context wrapper was not proven stopped.'
    }
}

function Start-Package([object] $Plan) {
    $command = Get-Command Invoke-CommandInDesktopPackage -ErrorAction SilentlyContinue
    if ($null -eq $command -or [string]$command.Source -cne 'Appx') { Fail 'The Appx package-context launch helper is required.' }
    if ([string]$script:gateCapability -cnotmatch '^[0-9A-F]{64}$') {
        Fail 'The native Gate A capability is absent before package launch.'
    }
    if ($script:authorizationExpiresAtUtcTicks -isnot [long] -or
        [long]$script:authorizationExpiresAtUtcTicks -le [DateTime]::UtcNow.Ticks) {
        Fail 'The native Gate A authorization expiry is absent or expired before package launch.'
    }
    $pgmStartUtc = [DateTime]::ParseExact(
        [string]$Plan.pgm.startTimeUtc,
        "yyyy-MM-dd'T'HH:mm:ss.fffffff'Z'",
        [Globalization.CultureInfo]::InvariantCulture,
        [Globalization.DateTimeStyles]::AssumeUniversal).ToUniversalTime()
    $environment = [ordered]@{
        MBN_STOCK_PLAYOUT_MODE='Live'; MBN_STOCK_PLAYOUT_HOST='127.0.0.1'; MBN_STOCK_PLAYOUT_PORT='30001'
        MBN_STOCK_PLAYOUT_TCP_MODE='1'; MBN_STOCK_PLAYOUT_CLIENT_PORT='0'; MBN_STOCK_PLAYOUT_LAYOUT_INDEX='10'
        MBN_STOCK_PLAYOUT_LEGACY_FADE_DURATION='6'; MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_KIND='None'
        MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_VIDEO_LOOP_COUNT='2004'; MBN_STOCK_PLAYOUT_LEGACY_BACKGROUND_VIDEO_LOOP_INFINITE='true'
        MBN_STOCK_PLAYOUT_SCENE_DIRECTORY=[string]$Plan.configuration.sceneDirectory
        MBN_STOCK_PLAYOUT_QUEUE_CAPACITY='64'; MBN_STOCK_PLAYOUT_CONNECT_TIMEOUT_MS='5000'
        MBN_STOCK_PLAYOUT_OPERATION_TIMEOUT_MS='5000'; MBN_STOCK_PLAYOUT_DISCONNECT_TIMEOUT_MS='3000'
        MBN_STOCK_PLAYOUT_PROCESS_POLL_INTERVAL_MS='1000'; MBN_STOCK_PLAYOUT_RECONNECT_DELAY_MS='1000'
        MBN_STOCK_PLAYOUT_MAXIMUM_RECONNECT_ATTEMPTS='0'; MBN_STOCK_PLAYOUT_RECONNECT_ENABLED='false'
        MBN_STOCK_K3D_NATIVE_SHA256=[string]$Plan.k3d.native.sha256
        MBN_STOCK_K3D_INTEROP_SHA256=[string]$Plan.k3d.interop.sha256
        $liveAuthorizationName=$liveAuthorizationValue
        $capabilityName=[string]$script:gateCapability
        $pgmProcessIdName=([int]$Plan.pgm.processId).ToString([Globalization.CultureInfo]::InvariantCulture)
        $pgmStartTimeUtcTicksName=$pgmStartUtc.Ticks.ToString([Globalization.CultureInfo]::InvariantCulture)
        $gateExpiresAtUtcTicksName=([long]$script:authorizationExpiresAtUtcTicks).ToString([Globalization.CultureInfo]::InvariantCulture)
        WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS="--remote-debugging-address=127.0.0.1 --remote-debugging-port=$([int]$Plan.webView.port)"
    }
    $encodedExe = [Convert]::ToBase64String($utf8NoBom.GetBytes([string]$Plan.package.executable.path))
    $encodedDir = [Convert]::ToBase64String($utf8NoBom.GetBytes([string]$Plan.package.installLocation))
    $launchPipe = New-CurrentUserOneShotPipe 'Package'
    $pipeName = [string]$launchPipe.Name
    $child = @"
`$exe=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('$encodedExe'))
`$dir=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('$encodedDir'))
`$client=[IO.Pipes.NamedPipeClientStream]::new('.', '$pipeName', [IO.Pipes.PipeDirection]::In, [IO.Pipes.PipeOptions]::None)
try {
    `$client.Connect(15000)
    `$lengthBytes=New-Object byte[] 4
    `$offset=0
    while(`$offset -lt 4){`$read=`$client.Read(`$lengthBytes,`$offset,4-`$offset);if(`$read -le 0){exit 92};`$offset+=`$read}
    `$length=[BitConverter]::ToInt32(`$lengthBytes,0)
    if(`$length -le 0 -or `$length -gt 65536){exit 93}
    `$payload=New-Object byte[] `$length
    `$offset=0
    while(`$offset -lt `$length){`$read=`$client.Read(`$payload,`$offset,`$length-`$offset);if(`$read -le 0){exit 94};`$offset+=`$read}
    if(`$client.ReadByte() -ne -1){exit 95}
    `$strictUtf8=New-Object Text.UTF8Encoding(`$false,`$true)
    `$map=`$strictUtf8.GetString(`$payload)|ConvertFrom-Json -ErrorAction Stop
}
finally { if(`$null-ne`$client){`$client.Dispose()} }
`$start=New-Object Diagnostics.ProcessStartInfo
`$start.FileName=`$exe;`$start.WorkingDirectory=`$dir;`$start.UseShellExecute=`$false
foreach(`$name in @(`$start.EnvironmentVariables.Keys)){if(`$name -like 'MBN_STOCK_PLAYOUT_*' -or `$name -like 'MBN_STOCK_K3D_*' -or `$name -like 'MBN_LEGACY_PGM_GATE_A_*' -or `$name -like 'MBN_STOCK_ORACLE_*' -or `$name -like 'MBN_STOCK_MARIADB_*' -or `$name -like 'MBN_STOCK_DB_*' -or `$name -like 'WEBVIEW2_*' -or `$name -like 'COREWEBVIEW2_*' -or `$name -like 'DOTNET_*' -or `$name -like 'CORECLR_*' -or `$name -like 'COR_*' -or `$name -like 'COMPlus_*'){`$start.EnvironmentVariables.Remove(`$name)}}
foreach(`$property in `$map.PSObject.Properties){`$start.EnvironmentVariables[`$property.Name]=[string]`$property.Value}
try { if(`$null -eq [Diagnostics.Process]::Start(`$start)){exit 91} }
finally {
    foreach(`$name in @(`$start.EnvironmentVariables.Keys)){if(`$name -like 'MBN_STOCK_PLAYOUT_*' -or `$name -like 'MBN_STOCK_K3D_*' -or `$name -like 'MBN_LEGACY_PGM_GATE_A_*' -or `$name -like 'MBN_STOCK_ORACLE_*' -or `$name -like 'MBN_STOCK_MARIADB_*' -or `$name -like 'MBN_STOCK_DB_*' -or `$name -like 'WEBVIEW2_*' -or `$name -like 'COREWEBVIEW2_*' -or `$name -like 'DOTNET_*' -or `$name -like 'CORECLR_*' -or `$name -like 'COR_*' -or `$name -like 'COMPlus_*'){`$start.EnvironmentVariables.Remove(`$name)}}
    if(`$null-ne`$payload){[Array]::Clear(`$payload,0,`$payload.Length)}
    if(`$null-ne`$lengthBytes){[Array]::Clear(`$lengthBytes,0,`$lengthBytes.Length)}
}
"@
    $encodedChild = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($child))
    $notBefore = [DateTime]::UtcNow
    $script:launchAttempted = $true
    $wrapperExecutable = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
    $launcher = [Management.Automation.PowerShell]::Create()
    $wrapper = $null
    $async = $null
    try {
        [void]$launcher.AddCommand('Import-Module')
        [void]$launcher.AddParameter('Name','Appx')
        [void]$launcher.AddParameter('ErrorAction','Stop')
        [void]$launcher.AddStatement()
        [void]$launcher.AddCommand('Invoke-CommandInDesktopPackage')
        [void]$launcher.AddParameter('PackageFamilyName',$expectedPackageFamily)
        [void]$launcher.AddParameter('AppId','LegacyParityApp')
        [void]$launcher.AddParameter('Command',$wrapperExecutable)
        [void]$launcher.AddParameter('Args',"-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand $encodedChild")
        [void]$launcher.AddParameter('PreventBreakaway')
        [void]$launcher.AddParameter('ErrorAction','Stop')
        $async = $launcher.BeginInvoke()
        $pipeDeadline = [DateTime]::UtcNow.AddSeconds(20)
        while (-not $launchPipe.Connection.IsCompleted -and [DateTime]::UtcNow -lt $pipeDeadline) {
            if ($async.IsCompleted) {
                [void]$launcher.EndInvoke($async)
                Fail 'The package-context wrapper exited before requesting its one-shot launch permit.'
            }
            Assert-Pgm $Plan.pgm
            Start-Sleep -Milliseconds 50
        }
        if (-not $launchPipe.Connection.IsCompleted) { Fail 'The package-context wrapper did not request its launch permit.' }
        $launchPipe.Connection.GetAwaiter().GetResult()
        $script:packagePermitRequests++
        if ($script:packagePermitRequests -ne 1 -or -not $launchPipe.Stream.IsConnected) {
            Fail 'The package launch permit request budget or connection state is invalid.'
        }
        $wrapper = Assert-PackagePermitClient $launchPipe $notBefore $wrapperExecutable
        [void](Assert-Authorization $script:authorization $Plan)
        Assert-Pgm $Plan.pgm
        if (@(Get-Process -Name ([IO.Path]::GetFileNameWithoutExtension($expectedExecutableName)) -ErrorAction SilentlyContinue).Count -ne 0 -or
            @(Get-NetTCPConnection -State Listen -LocalPort ([int]$Plan.webView.port) -ErrorAction SilentlyContinue).Count -ne 0) {
            Fail 'The app or CDP precondition changed before package launch permit delivery.'
        }
        $payload = $utf8NoBom.GetBytes(($environment | ConvertTo-Json -Compress))
        $lengthBytes = [BitConverter]::GetBytes([int]$payload.Length)
        try {
            if ($payload.Length -le 0 -or $payload.Length -gt 65536) { Fail 'The package launch environment payload is unsafe.' }
            $launchPipe.Stream.Write($lengthBytes,0,$lengthBytes.Length)
            $launchPipe.Stream.Write($payload,0,$payload.Length)
            $launchPipe.Stream.Flush()
            $script:packagePermitDeliveries++
        }
        finally {
            [Array]::Clear($payload,0,$payload.Length)
            [Array]::Clear($lengthBytes,0,$lengthBytes.Length)
            Close-OneShotPipe $launchPipe
        }
        if ($script:packagePermitDeliveries -ne 1) { Fail 'The package launch permit was not delivered exactly once.' }
        $wrapperDeadline = [DateTime]::UtcNow.AddSeconds(20)
        while (-not $async.IsCompleted -and [DateTime]::UtcNow -lt $wrapperDeadline) { Start-Sleep -Milliseconds 50 }
        if (-not $async.IsCompleted) { Fail 'The package-context wrapper exceeded its one-shot launch timeout.' }
        [void]$launcher.EndInvoke($async)
        if ($launcher.HadErrors -or $launcher.Streams.Error.Count -ne 0) { Fail 'The package-context wrapper reported a launch error.' }
        if ($null -eq $wrapper -or -not $wrapper.WaitForExit(0)) {
            Fail 'OUTCOME_UNKNOWN: The package-context wrapper pipeline completed without proving the exact wrapper exited.'
        }
    }
    catch {
        $caught = $_
        Close-OneShotPipe $launchPipe
        $failStopFailure = $null
        try { if ($null -ne $wrapper) { Stop-PackageWrapperFailStop $wrapper } }
        catch { $failStopFailure = $_ }
        try { if ($null -ne $async -and -not $async.IsCompleted) { $launcher.Stop() } } catch { }
        if ($null -ne $failStopFailure) { throw $failStopFailure }
        throw $caught
    }
    finally { $launcher.Dispose() }
    $deadline = [DateTime]::UtcNow.AddSeconds(30)
    while ([DateTime]::UtcNow -lt $deadline) {
        $items = @(Get-Process -Name ([IO.Path]::GetFileNameWithoutExtension($expectedExecutableName)) -ErrorAction SilentlyContinue | Where-Object { -not $_.HasExited })
        if ($items.Count -gt 1) { Fail 'More than one LegacyParityApp process appeared.' }
        if ($items.Count -eq 1 -and $items[0].StartTime.ToUniversalTime() -ge $notBefore.AddSeconds(-1)) {
            $script:application = $items[0]
            $script:applicationLaunched = $true
            Assert-Application $items[0] ([string]$Plan.package.executable.path) $false
            $items[0].Refresh()
            if ($items[0].MainWindowHandle -ne [IntPtr]::Zero -and
                [string]$items[0].MainWindowTitle -ceq $expectedWindowTitle) {
                return $items[0]
            }
        }
        Start-Sleep -Milliseconds 100
    }
    Fail 'The exact package application did not start before timeout.'
}

function Quote-Argument([string] $Value) {
    if ($Value -notmatch '[\s"]') { return $Value }
    return '"' + ([regex]::Replace($Value, '(\\*)"', '$1$1\"') -replace '(\\+)$','$1$1') + '"'
}

function Test-ExactStringSequence([object[]] $Actual, [string[]] $Expected) {
    if ($Actual.Count -ne $Expected.Count) { return $false }
    for ($index = 0; $index -lt $Expected.Count; $index++) {
        if ($Actual[$index] -isnot [string] -or [string]$Actual[$index] -cne $Expected[$index]) {
            return $false
        }
    }
    return $true
}

function Assert-NodeScreenshot([object] $Screenshot, [string] $ExpectedPath) {
    Assert-ExactProperties $Screenshot @('path','bytes','sha256') 'Node evidence.screenshot'
    if ($Screenshot.path -isnot [string] -or $Screenshot.sha256 -isnot [string] -or
        ($Screenshot.bytes -isnot [int] -and $Screenshot.bytes -isnot [long])) {
        Fail 'OUTCOME_UNKNOWN: Node screenshot evidence JSON types are invalid.'
    }
    $path = FullPath ([string]$Screenshot.path) 'Node evidence.screenshot.path'
    if (-not (SamePath $path $ExpectedPath) -or -not [IO.File]::Exists($path)) {
        Fail 'OUTCOME_UNKNOWN: Node screenshot path is not the exclusive requested PNG path.'
    }
    Assert-NoReparse $path 'Node screenshot'
    Add-ReadLease $path 'Node screenshot'
    $bytes = [IO.File]::ReadAllBytes($path)
    try {
        $signature = [byte[]](0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A)
        if ($bytes.Length -lt 1024 -or [int64]$Screenshot.bytes -ne [int64]$bytes.LongLength -or
            [string]$Screenshot.sha256 -cnotmatch '^[0-9A-F]{64}$' -or
            (Convert-Hash $bytes) -cne [string]$Screenshot.sha256) {
            Fail 'OUTCOME_UNKNOWN: Node screenshot length or SHA-256 does not match the actual PNG.'
        }
        for ($index = 0; $index -lt $signature.Length; $index++) {
            if ($bytes[$index] -ne $signature[$index]) {
                Fail 'OUTCOME_UNKNOWN: Node screenshot does not have the exact PNG signature.'
            }
        }
    }
    finally { [Array]::Clear($bytes,0,$bytes.Length) }
}

function Assert-NodePreparedFinalState([object] $FinalState) {
    Assert-ExactProperties $FinalState @(
        'revision','isBusy','statusMessage','statusKind','playlist','playout') 'Node evidence.finalState'
    if (($FinalState.revision -isnot [int] -and $FinalState.revision -isnot [long]) -or
        [int64]$FinalState.revision -lt 0) {
        Fail 'OUTCOME_UNKNOWN: Node final revision is invalid.'
    }
    Assert-JsonBool $FinalState.isBusy $false 'Node evidence.finalState.isBusy'
    if ($FinalState.playlist -isnot [Array]) {
        Fail 'OUTCOME_UNKNOWN: Node final playlist must be a JSON array.'
    }
    $playlist = @($FinalState.playlist)
    if ($playlist.Count -ne 1) { Fail 'OUTCOME_UNKNOWN: Node final playlist is not exactly one row.' }
    $row = $playlist[0]
    Assert-ExactProperties $row @(
        'rowId','isEnabled','isActive','marketText','stockName','graphicType','subtype','pageText') 'Node evidence.finalState.playlist[0]'
    if ($row.rowId -isnot [string] -or [string]::IsNullOrWhiteSpace([string]$row.rowId)) {
        Fail 'OUTCOME_UNKNOWN: Node final playlist row identity is invalid.'
    }
    Assert-JsonBool $row.isEnabled $true 'Node evidence.finalState.playlist[0].isEnabled'
    Assert-JsonBool $row.isActive $true 'Node evidence.finalState.playlist[0].isActive'
    Assert-JsonString $row.marketText $expectedMarketText 'Node evidence.finalState.playlist[0].marketText'
    Assert-JsonString $row.stockName $expectedStockName 'Node evidence.finalState.playlist[0].stockName'
    Assert-JsonString $row.graphicType $expectedGraphicType 'Node evidence.finalState.playlist[0].graphicType'
    Assert-JsonString $row.subtype $expectedSubtype 'Node evidence.finalState.playlist[0].subtype'
    Assert-JsonString $row.pageText '1/1' 'Node evidence.finalState.playlist[0].pageText'

    $playout = $FinalState.playout
    Assert-ExactProperties $playout @(
        'mode','connectionState','phase','isConnected','isCommandAvailable','liveTakeInAllowed',
        'isPlayCompletionPending','isTakeOutCompletionPending','outcomeUnknown','preparedCode',
        'onAirCode','currentEntryId','pageIndexZeroBased','pageCount','pageSize','itemCount',
        'currentPageItemCount','isLastPage','nextKind','isBusy','refreshActive',
        'refreshCompletedCount','refreshMaximumCount','refreshLimitReached','message') 'Node evidence.finalState.playout'
    Assert-JsonString $playout.mode 'live' 'Node evidence.finalState.playout.mode'
    Assert-JsonString $playout.connectionState 'connected' 'Node evidence.finalState.playout.connectionState'
    Assert-JsonString $playout.phase 'prepared' 'Node evidence.finalState.playout.phase'
    Assert-JsonBool $playout.isConnected $true 'Node evidence.finalState.playout.isConnected'
    Assert-JsonBool $playout.isCommandAvailable $true 'Node evidence.finalState.playout.isCommandAvailable'
    Assert-JsonBool $playout.liveTakeInAllowed $false 'Node evidence.finalState.playout.liveTakeInAllowed'
    Assert-JsonBool $playout.isPlayCompletionPending $false 'Node evidence.finalState.playout.isPlayCompletionPending'
    Assert-JsonBool $playout.isTakeOutCompletionPending $false 'Node evidence.finalState.playout.isTakeOutCompletionPending'
    Assert-JsonBool $playout.outcomeUnknown $false 'Node evidence.finalState.playout.outcomeUnknown'
    Assert-JsonString $playout.preparedCode '5001' 'Node evidence.finalState.playout.preparedCode'
    if ($null -ne $playout.onAirCode) { Fail 'OUTCOME_UNKNOWN: Node final onAirCode must be JSON null.' }
    Assert-JsonString $playout.currentEntryId ([string]$row.rowId) 'Node evidence.finalState.playout.currentEntryId'
    Assert-JsonInt $playout.pageIndexZeroBased 0 'Node evidence.finalState.playout.pageIndexZeroBased'
    Assert-JsonInt $playout.pageCount 1 'Node evidence.finalState.playout.pageCount'
    Assert-JsonInt $playout.itemCount 1 'Node evidence.finalState.playout.itemCount'
    Assert-JsonInt $playout.currentPageItemCount 1 'Node evidence.finalState.playout.currentPageItemCount'
    Assert-JsonBool $playout.isLastPage $true 'Node evidence.finalState.playout.isLastPage'
    Assert-JsonString $playout.nextKind 'endOfPlaylist' 'Node evidence.finalState.playout.nextKind'
    Assert-JsonBool $playout.isBusy $false 'Node evidence.finalState.playout.isBusy'
    Assert-JsonBool $playout.refreshActive $false 'Node evidence.finalState.playout.refreshActive'
}

function Assert-NodeSuccessEvidence([object] $Node, [object] $Plan) {
    Assert-ExactProperties $Node @(
        'schemaVersion','result','startedAtUtc','completedAtUtc','planSha256','roundId','target',
        'commandBudget','observed','checkpoints','screenshot','finalState','error','authorizationSha256') 'Node evidence'
    Assert-JsonInt $Node.schemaVersion 1 'Node evidence.schemaVersion'
    Assert-JsonString $Node.result 'PASS_PREPARED' 'Node evidence.result'
    Assert-JsonString $Node.planSha256 $script:planHash 'Node evidence.planSha256'
    Assert-JsonString $Node.authorizationSha256 $script:authorizationHash 'Node evidence.authorizationSha256'
    Assert-JsonString $Node.roundId ([string]$Plan.roundId) 'Node evidence.roundId'
    if ($null -ne $Node.error) { Fail 'OUTCOME_UNKNOWN: Node success evidence contains an error.' }
    Assert-ExactProperties $Node.target @('expectedUrl','port','id') 'Node evidence.target'
    Assert-JsonString $Node.target.expectedUrl $expectedUrl 'Node evidence.target.expectedUrl'
    Assert-JsonInt $Node.target.port ([int]$Plan.webView.port) 'Node evidence.target.port'
    if ($Node.target.id -isnot [string] -or [string]::IsNullOrWhiteSpace([string]$Node.target.id)) {
        Fail 'OUTCOME_UNKNOWN: Node target identity is missing.'
    }
    Assert-ClosedBudgets $Node.commandBudget 'Node evidence.commandBudget'
    Assert-ExactProperties $Node.observed @(
        'prepareDispatchPossible','prepareIssued','busyObserved','jitPermitRequested',
        'jitPermitReceived','outboundTypes','blocked') 'Node evidence.observed'
    Assert-JsonBool $Node.observed.prepareDispatchPossible $true 'Node evidence.observed.prepareDispatchPossible'
    Assert-JsonBool $Node.observed.prepareIssued $true 'Node evidence.observed.prepareIssued'
    Assert-JsonBool $Node.observed.busyObserved $true 'Node evidence.observed.busyObserved'
    Assert-JsonBool $Node.observed.jitPermitRequested $true 'Node evidence.observed.jitPermitRequested'
    Assert-JsonBool $Node.observed.jitPermitReceived $true 'Node evidence.observed.jitPermitReceived'
    if ($script:nodePermitRequests -ne 1 -or $script:nodePermitDeliveries -ne 1 -or
        $script:nodePermitClientProcessId -le 0) {
        Fail 'OUTCOME_UNKNOWN: The parent-owned JIT permit ledger is not exactly one verified delivery.'
    }
    if ($Node.observed.outboundTypes -isnot [Array] -or $Node.observed.blocked -isnot [Array] -or
        @($Node.observed.blocked).Count -ne 0) {
        Fail 'OUTCOME_UNKNOWN: Node success ledger is not an unblocked JSON array ledger.'
    }
    $actualOutbound = @($Node.observed.outboundTypes)
    $withoutSelection = @('ready','search-stocks','cut-pointer-down','drop-selected-cuts','gate-a-prepare')
    $withSelection = @('ready','search-stocks','select-stock','cut-pointer-down','drop-selected-cuts','gate-a-prepare')
    if (-not (Test-ExactStringSequence $actualOutbound $withoutSelection) -and
        -not (Test-ExactStringSequence $actualOutbound $withSelection)) {
        Fail 'OUTCOME_UNKNOWN: Node success outbound ledger order is not exact.'
    }
    if ($Node.checkpoints -isnot [Array]) { Fail 'OUTCOME_UNKNOWN: Node checkpoints must be a JSON array.' }
    Assert-NodePreparedFinalState $Node.finalState
    Assert-NodeScreenshot $Node.screenshot $script:screenshotPath
}

function Get-GateCapabilityHash([string] $Capability) {
    if ($Capability -cnotmatch '^[0-9A-F]{64}$') { Fail 'The native Gate A capability is invalid.' }
    $bytes = [Text.Encoding]::ASCII.GetBytes($Capability)
    try { return Convert-Hash $bytes }
    finally { [Array]::Clear($bytes,0,$bytes.Length) }
}

function Assert-NodePermitClient(
    [object] $Pipe,
    [Diagnostics.Process] $Process,
    [long] $ExpectedStartTimeUtcTicks,
    [string] $ExpectedExecutable) {
    $clientProcessId = [LegacyPgmPreparePackageIdentity]::ReadNamedPipeClientProcessId(
        $Pipe.Stream.SafePipeHandle)
    if ($clientProcessId -ne $Process.Id) {
        Fail 'The JIT permit pipe was claimed by a process other than the spawned Node helper.'
    }
    $Process.Refresh()
    if ($Process.HasExited -or -not (SamePath $Process.Path $ExpectedExecutable) -or
        $Process.StartTime.ToUniversalTime().Ticks -ne $ExpectedStartTimeUtcTicks) {
        Fail 'The JIT permit pipe Node process identity changed.'
    }
    $row = Get-CimInstance Win32_Process -Filter "ProcessId = $clientProcessId" -ErrorAction Stop
    if ($null -eq $row -or [int]$row.ParentProcessId -ne $PID -or
        -not (SamePath ([string]$row.ExecutablePath) $ExpectedExecutable)) {
        Fail 'The JIT permit pipe client is not the exact direct child Node helper.'
    }
    $script:nodePermitClientProcessId = $clientProcessId
}

function Stop-NodeHelperFailStop([Diagnostics.Process] $Process, [object] $Pipe) {
    # This revokes and stops only the control-plane Node helper. It never closes,
    # disconnects, terminates, or otherwise commands the app, PGM, Tornado, or K3D.
    Close-OneShotPipe $Pipe
    $script:gateCapability = $null
    if ($null -eq $Process) { return }
    $script:nodeStillRunning = $true
    $exitObserved = $false
    try { $exitObserved = [bool]$Process.WaitForExit(0) } catch { }
    if (-not $exitObserved) {
        try { $Process.Kill() }
        catch {
            try { $exitObserved = [bool]$Process.WaitForExit(0) } catch { }
            if (-not $exitObserved) {
                Fail 'OUTCOME_UNKNOWN: The Node helper could not be fail-stopped or proven exited after parent revocation.'
            }
        }
        if (-not $exitObserved) {
            try { $exitObserved = [bool]$Process.WaitForExit(5000) } catch { }
        }
    }
    if (-not $exitObserved) {
        Fail 'OUTCOME_UNKNOWN: The Node helper was not proven stopped after parent revocation.'
    }
    $script:nodeStillRunning = $false
    try { $script:nodeExitCode = [int]$Process.ExitCode } catch { }
}

function Start-NodeHelper([object] $Plan) {
    $script:nodeInvocations++
    if ($script:nodeInvocations -ne 1) { Fail 'The Node helper one-shot budget was exceeded.' }
    if ([string]$script:gateCapability -cnotmatch '^[0-9A-F]{64}$' -or
        [string]$script:gateCapabilitySha256 -cnotmatch '^[0-9A-F]{64}$' -or
        (Get-GateCapabilityHash ([string]$script:gateCapability)) -cne [string]$script:gateCapabilitySha256) {
        Fail 'The native Gate A capability is absent before the Node helper starts.'
    }
    $permitPipe = New-CurrentUserOneShotPipe 'Node'
    $args = @(
        [string]$Plan.automation.helper.path,
        '--port',[string]$Plan.webView.port,'--plan',(FullPath $PlanPath 'PlanPath'),
        '--plan-sha256',$script:planHash,'--authorization',(FullPath $AuthorizationPath 'AuthorizationPath'),
        '--authorization-sha256',$script:authorizationHash,
        '--output',$script:nodeEvidencePath,'--screenshot',$script:screenshotPath,
        '--capability-sha256',[string]$script:gateCapabilitySha256,
        '--permit-pipe',[string]$permitPipe.Name,
        '--timeout-ms','90000')
    $info = [Diagnostics.ProcessStartInfo]::new()
    $info.FileName = [string]$Plan.automation.node.path
    $info.WorkingDirectory = $repositoryRoot
    $info.UseShellExecute = $false
    $info.CreateNoWindow = $true
    $info.Arguments = (($args | ForEach-Object { Quote-Argument ([string]$_) }) -join ' ')
    foreach ($name in @($info.EnvironmentVariables.Keys)) {
        if ([string]$name -like 'NODE_*' -or [string]$name -like 'MBN_LEGACY_PGM_GATE_A_*' -or
            [string]$name -like 'MBN_STOCK_ORACLE_*' -or [string]$name -like 'MBN_STOCK_MARIADB_*' -or
            [string]$name -like 'MBN_STOCK_DB_*' -or
            [string]$name -like 'DOTNET_*' -or [string]$name -like 'CORECLR_*' -or
            [string]$name -like 'COR_*' -or [string]$name -like 'COMPlus_*' -or
            [string]$name -in @('OPENSSL_CONF','OPENSSL_MODULES')) {
            $info.EnvironmentVariables.Remove([string]$name)
        }
    }
    $process = $null
    try { $process = [Diagnostics.Process]::Start($info) }
    catch {
        Close-OneShotPipe $permitPipe
        $script:gateCapability = $null
        throw
    }
    if ($null -eq $process) {
        Close-OneShotPipe $permitPipe
        $script:gateCapability = $null
        Fail 'The one Node helper invocation did not start.'
    }
    try {
        $process.Refresh()
        $nodeStartTimeUtcTicks = $process.StartTime.ToUniversalTime().Ticks
        $deadline = [DateTime]::UtcNow.AddSeconds(105)
        while (-not $process.HasExited -and [DateTime]::UtcNow -lt $deadline) {
            Assert-Application $script:application ([string]$Plan.package.executable.path)
            Assert-Pgm $Plan.pgm
            Assert-Cdp ([int]$Plan.webView.port) $script:application.Id
            if ($script:nodePermitDeliveries -eq 0 -and $permitPipe.Connection.IsCompleted) {
                $permitPipe.Connection.GetAwaiter().GetResult()
                $script:nodePermitRequests++
                if ($script:nodePermitRequests -ne 1 -or -not $permitPipe.Stream.IsConnected) {
                    Fail 'The Node JIT permit request budget or connection state is invalid.'
                }
                Assert-NodePermitClient `
                    $permitPipe `
                    $process `
                    $nodeStartTimeUtcTicks `
                    ([string]$Plan.automation.node.path)
                $currentExpiry = Assert-Authorization $script:authorization $Plan
                if ($currentExpiry.UtcDateTime.Ticks -ne [long]$script:authorizationExpiresAtUtcTicks) {
                    Fail 'The authorization expiry changed at the JIT permit boundary.'
                }
                Assert-Application $script:application ([string]$Plan.package.executable.path)
                Assert-Pgm $Plan.pgm
                Assert-Cdp ([int]$Plan.webView.port) $script:application.Id
                $bytes = [Text.Encoding]::ASCII.GetBytes([string]$script:gateCapability)
                try {
                    if ($bytes.Length -ne 64 -or (Convert-Hash $bytes) -cne [string]$script:gateCapabilitySha256) {
                        Fail 'The JIT permit bearer changed before delivery.'
                    }
                    $permitPipe.Stream.Write($bytes,0,$bytes.Length)
                    $permitPipe.Stream.Flush()
                    $script:nodePermitDeliveries++
                }
                finally {
                    [Array]::Clear($bytes,0,$bytes.Length)
                    $script:gateCapability = $null
                    Close-OneShotPipe $permitPipe
                }
                if ($script:nodePermitDeliveries -ne 1) {
                    Fail 'The Node JIT permit delivery budget was not exactly consumed.'
                }
            }
            Start-Sleep -Milliseconds 100
            $process.Refresh()
        }
        if (-not $process.HasExited) {
            Fail 'OUTCOME_UNKNOWN: Node helper exceeded its outer timeout; parent fail-stop is required without any app or playout cleanup command.'
        }
        Close-OneShotPipe $permitPipe
        $script:gateCapability = $null
        if (-not $process.WaitForExit(0)) {
            Fail 'OUTCOME_UNKNOWN: Node helper exit could not be positively observed.'
        }
        $process.Refresh()
        $script:nodeExitCode = [int]$process.ExitCode
        if (-not [IO.File]::Exists($script:nodeEvidencePath)) {
            Fail 'OUTCOME_UNKNOWN: Node helper produced no exclusive evidence file.'
        }
        Add-ReadLease $script:nodeEvidencePath 'Node evidence'
        if ($script:nodeExitCode -eq 0) {
            # The last monitor sample can precede Node exit. Re-pin every live identity
            # immediately before accepting the prepared success evidence.
            Assert-Pgm $Plan.pgm
            Assert-Application $script:application ([string]$Plan.package.executable.path)
            Assert-Cdp ([int]$Plan.webView.port) $script:application.Id
            $node = Read-SealedJson $script:nodeEvidencePath (Get-FileHash $script:nodeEvidencePath -Algorithm SHA256).Hash 'Node evidence'
            try { Assert-NodeSuccessEvidence $node.Value $Plan }
            catch {
                if ($_.Exception.Message.StartsWith('OUTCOME_UNKNOWN:',[StringComparison]::Ordinal)) { throw }
                Fail ('OUTCOME_UNKNOWN: Node success evidence is not the exact sealed PREPARED result: ' + $_.Exception.Message)
            }
            return
        }
        if ($script:nodeExitCode -eq 2) {
            try {
                $known = ([IO.File]::ReadAllText($script:nodeEvidencePath,$utf8NoBom) | ConvertFrom-Json -ErrorAction Stop)
                if ([int]$known.schemaVersion -ne 1 -or [string]$known.result -cne 'KNOWN_FAILURE' -or
                    [string]$known.planSha256 -cne $script:planHash -or
                    [string]$known.authorizationSha256 -cne $script:authorizationHash -or
                    $known.observed.prepareDispatchPossible -isnot [bool] -or
                    $known.observed.prepareIssued -isnot [bool] -or
                    $known.observed.busyObserved -isnot [bool] -or
                    $known.observed.jitPermitRequested -isnot [bool] -or
                    $known.observed.jitPermitReceived -isnot [bool]) {
                    Fail 'OUTCOME_UNKNOWN: Node known-failure evidence is not exact.'
                }
            }
            catch {
                if ($_.Exception.Message.StartsWith('OUTCOME_UNKNOWN:',[StringComparison]::Ordinal)) { throw }
                Fail 'OUTCOME_UNKNOWN: Node known-failure evidence could not be validated.'
            }
            Fail 'KNOWN_FAILURE: Node helper stopped without a successful PREPARE.'
        }
        Fail "OUTCOME_UNKNOWN: Node helper exited with code $($script:nodeExitCode)."
    }
    catch {
        $caught = $_
        Stop-NodeHelperFailStop $process $permitPipe
        throw $caught
    }
}

function Write-Evidence([string] $Path) {
    $nodePin = $null
    $prepareMayHaveBeenDispatched = $script:nodeInvocations -gt 0
    $nodeObserved = $null
    $screenshotPin = $null
    if (-not [string]::IsNullOrWhiteSpace($script:nodeEvidencePath) -and [IO.File]::Exists($script:nodeEvidencePath)) {
        $item = Get-Item -LiteralPath $script:nodeEvidencePath -Force
        $nodePin = [ordered]@{ path=$script:nodeEvidencePath; length=[int64]$item.Length; sha256=(Get-FileHash $script:nodeEvidencePath -Algorithm SHA256).Hash }
        try {
            $nodeValue = ([IO.File]::ReadAllText($script:nodeEvidencePath,$utf8NoBom) | ConvertFrom-Json -ErrorAction Stop)
            $trustedNodeIdentity = [int]$nodeValue.schemaVersion -eq 1 -and
                [string]$nodeValue.planSha256 -ceq $script:planHash -and
                [string]$nodeValue.authorizationSha256 -ceq $script:authorizationHash
            $nodeObserved = [ordered]@{
                trustedIdentity = $trustedNodeIdentity
                result = [string]$nodeValue.result
                prepareDispatchPossible = $(if ($nodeValue.observed.prepareDispatchPossible -is [bool]) { [bool]$nodeValue.observed.prepareDispatchPossible } else { $null })
                prepareIssued = $(if ($nodeValue.observed.prepareIssued -is [bool]) { [bool]$nodeValue.observed.prepareIssued } else { $null })
                busyObserved = $(if ($nodeValue.observed.busyObserved -is [bool]) { [bool]$nodeValue.observed.busyObserved } else { $null })
                jitPermitRequested = $(if ($nodeValue.observed.jitPermitRequested -is [bool]) { [bool]$nodeValue.observed.jitPermitRequested } else { $null })
                jitPermitReceived = $(if ($nodeValue.observed.jitPermitReceived -is [bool]) { [bool]$nodeValue.observed.jitPermitReceived } else { $null })
                outboundTypes = @($nodeValue.observed.outboundTypes)
                blocked = @($nodeValue.observed.blocked)
                finalState = $nodeValue.finalState
                screenshot = $nodeValue.screenshot
            }
            if ($script:nodeExitCode -eq 2 -and $trustedNodeIdentity -and
                [string]$nodeValue.result -ceq 'KNOWN_FAILURE' -and
                $nodeValue.observed.prepareDispatchPossible -is [bool] -and
                [bool]$nodeValue.observed.prepareDispatchPossible -eq $false -and
                $nodeValue.observed.prepareIssued -is [bool] -and
                [bool]$nodeValue.observed.prepareIssued -eq $false) {
                $prepareMayHaveBeenDispatched = $false
            }
        }
        catch {
            $prepareMayHaveBeenDispatched = $true
        }
    }
    if (-not [string]::IsNullOrWhiteSpace($script:screenshotPath) -and [IO.File]::Exists($script:screenshotPath)) {
        $shot = Get-Item -LiteralPath $script:screenshotPath -Force
        $screenshotPin = [ordered]@{ path=$script:screenshotPath; length=[int64]$shot.Length; sha256=(Get-FileHash $script:screenshotPath -Algorithm SHA256).Hash }
    }
    $evidence = [ordered]@{
        schemaVersion=1; kind='LegacyPgmPrepareGateEvidence'; roundId=$(if($null-ne$script:plan){[string]$script:plan.roundId}else{$null})
        status=$script:status; phase=$script:phase; startedAtUtc=$script:startedAtUtc.ToString('o')
        completedAtUtc=[DateTime]::UtcNow.ToString('o'); planSha256=$script:planHash
        authorizationSha256=$script:authorizationHash; appLaunched=$script:applicationLaunched
        appProcessId=$(if($null-ne$script:application){$script:application.Id}else{$null})
        launchAttempted=$script:launchAttempted; nodeHelperInvocations=$script:nodeInvocations
        nodeExitCode=$script:nodeExitCode; nodeStillRunning=$script:nodeStillRunning
        nodePermitRequests=$script:nodePermitRequests
        nodePermitDeliveries=$script:nodePermitDeliveries
        nodePermitClientProcessId=$script:nodePermitClientProcessId
        capabilitySha256=$script:gateCapabilitySha256
        packagePermitRequests=$script:packagePermitRequests
        packagePermitDeliveries=$script:packagePermitDeliveries
        packagePermitClientProcessId=$script:packagePermitClientProcessId
        nodeEvidence=$nodePin; screenshot=$screenshotPin; observed=$nodeObserved
        authorizedBudgets=[ordered]@{connect=1;prepare=1;takeIn=0;next=0;pageNext=0;takeOut=0;databaseWrite=0;appClose=0;retry=0}
        gateConfigInstalled=$script:gateConfigInstalled
        originalConfigRestored=$script:configRestored
        manualConfigRestoreRequired=($script:gateConfigInstalled -and -not $script:configRestored)
        prepareMayHaveBeenDispatched=$prepareMayHaveBeenDispatched
        configRestoreAttempts=$script:configRestoreAttempts
        automaticSessionCleanupAttemptedAfterLaunch=$false; error=$script:failure
    }
    $json = ($evidence | ConvertTo-Json -Depth 8) + [Environment]::NewLine
    $bytes = $utf8NoBom.GetBytes($json)
    try { Write-BytesExclusive $Path $bytes } finally { [Array]::Clear($bytes,0,$bytes.Length) }
}

$planCapture = Read-SealedJson $PlanPath $script:planHash 'PlanPath'
$script:plan = $planCapture.Value
Assert-Plan $script:plan

if ($Action -ceq 'ValidatePlan') {
    $script:phase = 'read-only-plan-environment-validation'
    $validation = Assert-ReadOnlyPlanEnvironment $script:plan
    [pscustomobject][ordered]@{
        result='PLAN_VALID'; roundId=[string]$script:plan.roundId; planPath=$planCapture.Path
        planLength=$planCapture.Length; planSha256=$script:planHash
        filePinsValidated=$validation.filePinsValidated
        runtimeTreeValidated=$validation.runtimeTreeValidated
        packageRegistrationValidated=$validation.packageRegistrationValidated
        currentPgmValidated=$validation.currentPgmValidated
        k3dRegistrationValidatedReadOnly=$validation.k3dRegistrationValidatedReadOnly
        authorizationRead=$false; packageLaunches=0; appCommandsIssued=0; playoutCommandsIssued=0
        comActivations=0; vendorCommandsIssued=0
    }
    return
}

if ([string]::IsNullOrWhiteSpace($AuthorizationPath) -or
    [string]::IsNullOrWhiteSpace($ExpectedAuthorizationSha256) -or
    [string]::IsNullOrWhiteSpace($EvidencePath)) { Fail 'ExecuteAuthorizedGateA requires authorization path/hash and a new evidence path.' }
$script:authorizationHash = $ExpectedAuthorizationSha256.ToUpperInvariant()
$evidenceFull = FullPath $EvidencePath 'EvidencePath'
if ((Test-Within $planCapture.Path $repositoryRoot) -or
    (Test-Within (FullPath $AuthorizationPath 'AuthorizationPath') $repositoryRoot) -or
    (Test-Within $evidenceFull $repositoryRoot)) {
    Fail 'Plan, authorization and live evidence must remain outside the Git repository.'
}
if ([IO.File]::Exists($evidenceFull) -or [IO.Directory]::Exists($evidenceFull)) { Fail 'EvidencePath already exists.' }
$evidenceParent = [IO.Path]::GetDirectoryName($evidenceFull); [void][IO.Directory]::CreateDirectory($evidenceParent); Assert-NoReparse $evidenceParent 'EvidencePath parent'
$script:nodeEvidencePath = Join-Path $evidenceParent (([IO.Path]::GetFileNameWithoutExtension($evidenceFull)) + '.node.json')
$script:screenshotPath = Join-Path $evidenceParent (([IO.Path]::GetFileNameWithoutExtension($evidenceFull)) + '.png')
if ([IO.File]::Exists($script:nodeEvidencePath) -or [IO.Directory]::Exists($script:nodeEvidencePath) -or
    [IO.File]::Exists($script:screenshotPath) -or [IO.Directory]::Exists($script:screenshotPath)) { Fail 'Node evidence or screenshot path already exists.' }

try {
    $script:phase = 'authorization'
    $authorizationCapture = Read-SealedJson $AuthorizationPath $script:authorizationHash 'AuthorizationPath'
    $script:authorization = $authorizationCapture.Value
    $expiresAt = Assert-Authorization $script:authorization $script:plan
    $script:authorizationExpiresAtUtcTicks = [long]$expiresAt.UtcDateTime.Ticks
    $script:phase = 'native-one-shot-gate-readiness'
    if (-not $nativeOneShotPrepareGateReady) {
        Fail 'Execution is disabled until the native one-shot PREPARE capability gate is implemented, tested, committed and pushed.'
    }
    $script:phase = 'frozen-preflight'
    Assert-CleanPushedCommit $script:plan.repository
    Assert-NoUnsafeEnvironment
    if (-not (SamePath (Assert-FilePin $script:plan.automation.orchestrator 'Gate orchestrator') $PSCommandPath) -or
        -not (SamePath (Assert-FilePin $script:plan.automation.helper 'Node helper') (Join-Path $PSScriptRoot 'Test-LegacyPgmPrepareGate.mjs'))) { Fail 'Automation file identity changed.' }
    [void](Assert-FilePin $script:plan.automation.node 'Node runtime')
    [void](Assert-FilePin $script:plan.package.msix 'Release x64 MSIX')
    $appExe = Assert-FilePin $script:plan.package.executable 'package executable'
    $tree = Get-RuntimeTreePin ([string]$script:plan.package.installLocation)
    if ($tree.fileCount -ne [int]$script:plan.package.runtimeTree.fileCount -or $tree.manifestSha256 -cne [string]$script:plan.package.runtimeTree.manifestSha256) { Fail 'Package runtime tree changed.' }
    Add-RuntimeTreeReadLeases ([string]$script:plan.package.installLocation)
    $leasedTree = Get-RuntimeTreePin ([string]$script:plan.package.installLocation)
    if ($leasedTree.fileCount -ne [int]$script:plan.package.runtimeTree.fileCount -or
        $leasedTree.manifestSha256 -cne [string]$script:plan.package.runtimeTree.manifestSha256) {
        Fail 'Package runtime tree changed while its read leases were acquired.'
    }
    [void](Assert-FilePin $script:plan.configuration.original 'original config')
    Assert-OriginalConfigDryRun $script:plan.configuration.original
    [void](Assert-FilePin $script:plan.configuration.gate 'Gate config')
    Assert-GateConfigSafety $script:plan.configuration
    [void](Assert-FilePin $script:plan.configuration.database 'database config')
    [void](Assert-FilePin $script:plan.cut.file '5001 cut')
    [void](Assert-FilePin $script:plan.k3d.native 'K3D native binary')
    [void](Assert-FilePin $script:plan.k3d.interop 'K3D interop assembly')
    [void](Assert-FilePin $script:plan.pgm.executable 'PGM executable')
    Assert-K3dRegistration $script:plan.k3d
    Assert-NoReparse ([string]$script:plan.configuration.sceneDirectory) 'scene directory'
    Add-ReadLease ([string]$script:plan.automation.helper.path) 'Node helper'
    Add-ReadLease ([string]$script:plan.automation.node.path) 'Node runtime'
    Add-ReadLease ([string]$script:plan.package.executable.path) 'package executable'
    Add-ReadLease ([string]$script:plan.configuration.gate.path) 'Gate config source'
    Add-ReadLease ([string]$script:plan.configuration.database.path) 'database config'
    Add-ReadLease ([string]$script:plan.cut.file.path) '5001 cut'
    Add-ReadLease ([string]$script:plan.k3d.native.path) 'K3D native binary'
    Add-ReadLease ([string]$script:plan.k3d.interop.path) 'K3D interop assembly'
    Add-ReadLease ([string]$script:plan.pgm.executable.path) 'PGM executable'
    Assert-Pgm $script:plan.pgm
    $packages = @(Get-AppxPackage -Name $expectedPackageName -ErrorAction Stop)
    if ($packages.Count -ne 1 -or -not $packages[0].IsDevelopmentMode -or [string]$packages[0].SignatureKind -cne 'None' -or
        [string]$packages[0].Status -cne 'Ok' -or [string]$packages[0].PackageFullName -cne $expectedPackageFullName -or
        [string]$packages[0].PackageFamilyName -cne $expectedPackageFamily -or [string]$packages[0].Publisher -cne 'CN=Comtrophy' -or
        [string]$packages[0].Version -cne '0.1.0.0' -or [string]$packages[0].Architecture -cne 'X64' -or
        -not (SamePath ([string]$packages[0].InstallLocation) ([string]$script:plan.package.installLocation))) { Fail 'Exact Release x64 development package registration is absent.' }
    if (@(Get-Process -Name ([IO.Path]::GetFileNameWithoutExtension($expectedExecutableName)) -ErrorAction SilentlyContinue).Count -ne 0 -or
        @(Get-NetTCPConnection -State Listen -LocalPort ([int]$script:plan.webView.port) -ErrorAction SilentlyContinue).Count -ne 0) { Fail 'An existing application or CDP endpoint is present.' }
    [void](Assert-Authorization $script:authorization $script:plan)
    $script:phase = 'gate-config-install'
    Install-GateConfiguration $script:plan.configuration ([string]$script:plan.roundId)
    $script:phase = 'native-capability-generation'
    $script:gateCapability = New-GateCapability
    $script:gateCapabilitySha256 = Get-GateCapabilityHash ([string]$script:gateCapability)
    $script:phase = 'package-launch-auto-connect'
    $script:application = Start-Package $script:plan
    $script:applicationLaunched = $true
    $script:phase = 'wait-exact-target'
    Wait-ExactTarget ([int]$script:plan.webView.port) $script:application.Id
    $script:phase = 'restore-original-config'
    Restore-OriginalConfiguration $script:plan.configuration
    [void](Assert-Authorization $script:authorization $script:plan)
    Assert-Pgm $script:plan.pgm; Assert-Application $script:application $appExe; Assert-Cdp ([int]$script:plan.webView.port) $script:application.Id
    $script:phase = 'prepare5001-once'
    Start-NodeHelper $script:plan
    $script:status = 'PREPARED_KNOWN_SUCCESS'
    $script:phase = 'prepared-session-left-open'
}
catch {
    $script:failure = $_.Exception.Message
    if ($script:failure.StartsWith('KNOWN_FAILURE:',[StringComparison]::Ordinal)) { $script:status='KNOWN_FAILURE' }
    elseif (-not $script:launchAttempted) { $script:status='PRECONDITION_FAILED' }
    else { $script:status='OUTCOME_UNKNOWN' }
    if (-not $script:launchAttempted -and $script:gateConfigInstalled -and
        -not $script:configRestored -and $script:configRestoreAttempts -eq 0) {
        try { Restore-OriginalConfiguration $script:plan.configuration } catch { $script:failure += ' Pre-launch one-shot local config restore failed: ' + $_.Exception.Message }
    }
}

Write-Evidence $evidenceFull
$evidenceItem = Get-Item -LiteralPath $evidenceFull -Force
$evidenceSha256 = (Get-FileHash -LiteralPath $evidenceFull -Algorithm SHA256).Hash
foreach ($lease in $script:fileLeases) { try { $lease.Dispose() } catch { } }
$script:fileLeases.Clear()
if ($script:status -cne 'PREPARED_KNOWN_SUCCESS') { Fail "$($script:status): $($script:failure) Evidence: $evidenceFull SHA-256: $evidenceSha256" }
[pscustomobject][ordered]@{
    result=$script:status; roundId=[string]$script:plan.roundId; planSha256=$script:planHash
    authorizationSha256=$script:authorizationHash; applicationProcessId=$script:application.Id
    authorizedConnectMaximum=1; prepare5001Page1Observed=1; nodeHelperInvocations=1
    packagePermitDeliveries=$script:packagePermitDeliveries; nodePermitDeliveries=$script:nodePermitDeliveries
    takeIn=0; next=0; pageNext=0; takeOut=0; databaseWrite=0; appClose=0; retry=0
    originalConfigRestored=$script:configRestored; applicationLeftOpen=$true
    evidence=$evidenceFull; evidenceLength=[int64]$evidenceItem.Length; evidenceSha256=$evidenceSha256
    nodeEvidence=$script:nodeEvidencePath; screenshot=$script:screenshotPath
}
