Set-StrictMode -Version Latest
|
$ErrorActionPreference = 'Stop'
|
|
function Invoke-AnaSemiWrapperSelftestV006 {
|
[CmdletBinding()]
|
param(
|
[Parameter(Mandatory = $true)][string]$RunId,
|
[Parameter(Mandatory = $true)][string]$RunRoot,
|
[Parameter(Mandatory = $true)][string]$RunnerAssemblyPath,
|
[Parameter(Mandatory = $true)][string]$ExpectedVectorsPath,
|
[Parameter(Mandatory = $true)][string]$ChildPowerShellPath,
|
[Parameter(Mandatory = $true)][string]$AuthorizationId,
|
[Parameter(Mandatory = $true)][ValidateSet('SELFTEST_ONLY')][string]$Mode
|
)
|
|
function Throw-Contract([string]$StopCode) {
|
throw [InvalidOperationException]::new($StopCode)
|
}
|
function Get-Sha256File([string]$Path) {
|
$stream = [IO.File]::Open($Path, [IO.FileMode]::Open, [IO.FileAccess]::Read, [IO.FileShare]::None)
|
try {
|
$sha = [Security.Cryptography.SHA256]::Create()
|
try { return ([BitConverter]::ToString($sha.ComputeHash($stream)).Replace('-', '').ToLowerInvariant()) }
|
finally { $sha.Dispose() }
|
}
|
finally { $stream.Dispose() }
|
}
|
function Assert-Leaf([string]$Path) {
|
if (-not [IO.Path]::IsPathRooted($Path) -or -not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
Throw-Contract 'STOP_AUTHORIZATION_MISSING_OR_DRIFT'
|
}
|
$item = Get-Item -LiteralPath $Path -Force
|
if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
|
Throw-Contract 'STOP_AUTHORIZATION_MISSING_OR_DRIFT'
|
}
|
}
|
|
if ($Mode -cne 'SELFTEST_ONLY' -or [string]::IsNullOrWhiteSpace($AuthorizationId)) {
|
Throw-Contract 'STOP_AUTHORIZATION_MISSING_OR_DRIFT'
|
}
|
if ($RunId -cne 'RUN-ANA-SEMI-ROOT-PREFLIGHT-WRAPPER-SELFTEST-20260724-001') {
|
Throw-Contract 'STOP_AUTHORIZATION_MISSING_OR_DRIFT'
|
}
|
if (-not [IO.Path]::IsPathRooted($RunRoot) -or (Test-Path -LiteralPath $RunRoot)) {
|
Throw-Contract 'STOP_TARGET_ALREADY_EXISTS'
|
}
|
Assert-Leaf $RunnerAssemblyPath
|
Assert-Leaf $ExpectedVectorsPath
|
Assert-Leaf $ChildPowerShellPath
|
|
$vectors = [IO.File]::ReadAllText($ExpectedVectorsPath, [Text.Encoding]::UTF8) | ConvertFrom-Json
|
if ($vectors.schema_id -cne 'ANA-SEMI-V007-EXPECTED-VECTORS-JCS-V001' -or
|
$vectors.professional_design.id -cne 'DESIGN-ANA-SEMI-ROOT-IDENTITY-GRANT-PREFLIGHT-V007') {
|
Throw-Contract 'STOP_PACKAGE_CONTRACT_DRIFT'
|
}
|
|
$resolvedAssembly = [IO.Path]::GetFullPath($RunnerAssemblyPath)
|
$loadedBefore = @([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -ceq 'AnaSemi.NativeProcessRunnerV006' })
|
if ($loadedBefore.Count -ne 0) { Throw-Contract 'STOP_ASSEMBLY_ALREADY_LOADED' }
|
try { $assembly = [Reflection.Assembly]::LoadFile($resolvedAssembly) }
|
catch { Throw-Contract 'STOP_ASSEMBLY_LOAD_FAILED' }
|
if ($null -eq $assembly -or $assembly.FullName -cne 'AnaSemi.NativeProcessRunnerV006, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' -or
|
[IO.Path]::GetFullPath($assembly.Location) -cne $resolvedAssembly) {
|
Throw-Contract 'STOP_ASSEMBLY_PUBLIC_API_DRIFT'
|
}
|
$loadedAfter = @([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GetName().Name -ceq 'AnaSemi.NativeProcessRunnerV006' })
|
if ($loadedAfter.Count -ne 1 -or -not [object]::ReferenceEquals($loadedAfter[0], $assembly)) {
|
Throw-Contract 'STOP_ASSEMBLY_LOAD_CONFLICT'
|
}
|
|
$requiredTypes = @(
|
'AnaSemi.NativeProcessContractExceptionV006',
|
'AnaSemi.NativeProcessResultV006',
|
'AnaSemi.NativeProcessRunnerV006',
|
'AnaSemi.ProcessResultEnvelopeValidatorV006',
|
'AnaSemi.ProcessResultSidecarCsvV006',
|
'AnaSemi.WindowsCommandLineV006'
|
)
|
$actualTypes = @($assembly.GetExportedTypes() | ForEach-Object { $_.FullName })
|
[Array]::Sort($requiredTypes, [StringComparer]::Ordinal)
|
[Array]::Sort($actualTypes, [StringComparer]::Ordinal)
|
if ([string]::Join([char]0, $requiredTypes) -cne [string]::Join([char]0, $actualTypes)) {
|
Throw-Contract 'STOP_ASSEMBLY_PUBLIC_API_DRIFT'
|
}
|
|
# A future, separately authorized synthetic turn supplies the reviewed run-root
|
# package and invokes the public runner. This implementation turn only freezes
|
# the load, authorization and API boundary; it never starts the child itself.
|
$assemblyHash = Get-Sha256File $resolvedAssembly
|
if ($assemblyHash.Length -ne 64) { Throw-Contract 'STOP_ASSEMBLY_PATH_OR_HASH_DRIFT' }
|
Throw-Contract 'STOP_AUTHORIZATION_MISSING_OR_DRIFT'
|
}
|