param([Parameter(Mandatory=$true)][string]$OutputDirectory,
[string]$PreviousManifest='artifacts/galaxy-state-owner-native-20261024T021858/sources-before.json')
$ErrorActionPreference='Stop'
$taskRoot=(Resolve-Path (Join-Path $PSScriptRoot '..')).Path
$output=[IO.Path]::GetFullPath((Join-Path $taskRoot $OutputDirectory))
$artifactRoot=[IO.Path]::GetFullPath((Join-Path $taskRoot 'artifacts'))+[IO.Path]::DirectorySeparatorChar
if(!$output.StartsWith($artifactRoot,[StringComparison]::OrdinalIgnoreCase)){throw 'Validation output must be a fresh artifacts directory.'}
if(Test-Path -LiteralPath $output){throw 'Refusing to overwrite frozen native evidence.'}
$null=New-Item -ItemType Directory -Path $output
$previous=Get-Content -Raw -LiteralPath (Join-Path $taskRoot $PreviousManifest) | ConvertFrom-Json
$paths=@($previous | ForEach-Object {$_.path})
$paths+=@(Get-ChildItem -LiteralPath (Join-Path $taskRoot 'native/ManagedPlugin') -Recurse -File | Where-Object {
$_.Extension -in '.cs','.csproj','.props','.targets' -and $_.FullName -notmatch '[\\/](bin|obj)[\\/]'
} | ForEach-Object {$_.FullName})
$paths+=@(Get-ChildItem -LiteralPath (Join-Path $taskRoot 'artifacts/galaxy-query-research') -Filter 'compatibility.json' -Recurse -File | ForEach-Object {$_.FullName})
$paths=@($paths | Sort-Object -Unique)
function Manifest {
# Re-enumerate, so a new wildcard compile input created during publish is
# a source change too, even when every previously known file is unchanged.
$observedPaths=@($paths)
$observedPaths+=@(Get-ChildItem -LiteralPath (Join-Path $taskRoot 'native/ManagedPlugin') -Recurse -File | Where-Object {
$_.Extension -in '.cs','.csproj','.props','.targets' -and $_.FullName -notmatch '[\\/](bin|obj)[\\/]'
} | ForEach-Object {$_.FullName})
$observedPaths+=@(Get-ChildItem -LiteralPath (Join-Path $taskRoot 'artifacts/galaxy-query-research') -Filter 'compatibility.json' -Recurse -File | ForEach-Object {$_.FullName})
@($observedPaths | Sort-Object -Unique | ForEach-Object {
$file=Get-Item -LiteralPath $_
[ordered]@{path=$file.FullName;length=$file.Length;sha256=(Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()}
})
}
$before=Manifest
$before | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $output 'sources-before.json') -Encoding utf8
Copy-Item -LiteralPath (Join-Path $taskRoot 'native/ManagedPlugin/Checks/Program.cs') -Destination (Join-Path $output 'checks-source.cs')
dotnet publish (Join-Path $taskRoot 'native/ManagedPlugin/Standalone/Standalone.csproj') -c Release -r win-x64 -p:PublishAot=true -o (Join-Path $output 'publish') --nologo *> (Join-Path $output 'publish.txt')
if($LASTEXITCODE -ne 0){throw ('Native publish failed: '+(Join-Path $output 'publish.txt'))}
$after=Manifest
$after | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $output 'sources-after.json') -Encoding utf8
$left=@($before | ForEach-Object {'{0}|{1}|{2}' -f $_.path,$_.length,$_.sha256})
$right=@($after | ForEach-Object {'{0}|{1}|{2}' -f $_.path,$_.length,$_.sha256})
if(@(Compare-Object $left $right).Count -ne 0){throw 'Native publish inputs changed; this binary has no frozen source claim.'}
$dll=Join-Path $output 'publish/XFEEDNavigation.dll'
dotnet run --project (Join-Path $taskRoot 'native/ManagedPlugin/Checks/Checks.csproj') -c Release -- $taskRoot $dll (Join-Path $output 'checks.json') *> (Join-Path $output 'checks.txt')
if($LASTEXITCODE -ne 0){throw ('Native checks failed: '+(Join-Path $output 'checks.txt'))}
$checks=Get-Content -Raw -LiteralPath (Join-Path $output 'checks.json') | ConvertFrom-Json
$rows=@($checks.checks | Where-Object {$null -ne $_.masks})
$source=Get-Content -Raw -LiteralPath (Join-Path $output 'checks-source.cs')
$body=[regex]::Match($source,'(?s)var checks = .*?\{(.*?)\n\};').Groups[1].Value
$expected=@{}
foreach($match in [regex]::Matches($body,'\("([^"]+)",\s*(\d+)\)')){$expected['XFENavigation'+$match.Groups[1].Value+'SelfTest']=[uint32]$match.Groups[2].Value}
$expected['XFEPlatformSelfTest']=2047;$expected['XFEInteractionSelfTest']=255
if(!$checks.passed -or $checks.gameAccess -or $checks.iterations -ne 8 -or $rows.Count -ne $expected.Count){throw 'Native check scope mismatch.'}
foreach($row in $rows){if(!$expected.ContainsKey($row.check) -or $row.expected -ne $expected[$row.check] -or $row.masks.Count -ne 8 -or @($row.masks | Where-Object {$_ -ne $row.expected}).Count -ne 0){throw ('Native mask mismatch: '+$row.check)}}
$summary=[ordered]@{passed=$true;gameAccess=$false;installed=$false;frozenSourceInputs=$before.Count;publishInputsUnchanged=$true;
selfTestGroups=$rows.Count;iterations=8;passingInvocations=($rows.Count*8);declaredNamesAndExpectedMasksVerified=$true;
dllLength=(Get-Item -LiteralPath $dll).Length;dllSha256=(Get-FileHash -LiteralPath $dll -Algorithm SHA256).Hash.ToLowerInvariant();fullOriginalParityConfirmed=$false}
$summary | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $output 'summary.json') -Encoding utf8
$summary | ConvertTo-Json -Compress
param([Parameter(Mandatory=$true)][string]$OutputDirectory,
[string]$PreviousManifest='artifacts/galaxy-state-owner-native-20261024T021858/sources-before.json')
$ErrorActionPreference='Stop'
$taskRoot=(Resolve-Path (Join-Path $PSScriptRoot '..')).Path
$output=[IO.Path]::GetFullPath((Join-Path $taskRoot $OutputDirectory))
$artifactRoot=[IO.Path]::GetFullPath((Join-Path $taskRoot 'artifacts'))+[IO.Path]::DirectorySeparatorChar
if(!$output.StartsWith($artifactRoot,[StringComparison]::OrdinalIgnoreCase)){throw 'Validation output must be a fresh artifacts directory.'}
if(Test-Path -LiteralPath $output){throw 'Refusing to overwrite frozen native evidence.'}
$null=New-Item -ItemType Directory -Path $output
$previous=Get-Content -Raw -LiteralPath (Join-Path $taskRoot $PreviousManifest) | ConvertFrom-Json
$paths=@($previous | ForEach-Object {$_.path})
$paths+=@(Get-ChildItem -LiteralPath (Join-Path $taskRoot 'native/ManagedPlugin') -Recurse -File | Where-Object {
$_.Extension -in '.cs','.csproj','.props','.targets' -and $_.FullName -notmatch '[\\/](bin|obj)[\\/]'
} | ForEach-Object {$_.FullName})
$paths+=@(Get-ChildItem -LiteralPath (Join-Path $taskRoot 'artifacts/galaxy-query-research') -Filter 'compatibility.json' -Recurse -File | ForEach-Object {$_.FullName})
$paths=@($paths | Sort-Object -Unique)
function Manifest {
# Re-enumerate, so a new wildcard compile input created during publish is
# a source change too, even when every previously known file is unchanged.
$observedPaths=@($paths)
$observedPaths+=@(Get-ChildItem -LiteralPath (Join-Path $taskRoot 'native/ManagedPlugin') -Recurse -File | Where-Object {
$_.Extension -in '.cs','.csproj','.props','.targets' -and $_.FullName -notmatch '[\\/](bin|obj)[\\/]'
} | ForEach-Object {$_.FullName})
$observedPaths+=@(Get-ChildItem -LiteralPath (Join-Path $taskRoot 'artifacts/galaxy-query-research') -Filter 'compatibility.json' -Recurse -File | ForEach-Object {$_.FullName})
@($observedPaths | Sort-Object -Unique | ForEach-Object {
$file=Get-Item -LiteralPath $_
[ordered]@{path=$file.FullName;length=$file.Length;sha256=(Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant()}
})
}
$before=Manifest
$before | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $output 'sources-before.json') -Encoding utf8
Copy-Item -LiteralPath (Join-Path $taskRoot 'native/ManagedPlugin/Checks/Program.cs') -Destination (Join-Path $output 'checks-source.cs')
dotnet publish (Join-Path $taskRoot 'native/ManagedPlugin/Standalone/Standalone.csproj') -c Release -r win-x64 -p:PublishAot=true -o (Join-Path $output 'publish') --nologo *> (Join-Path $output 'publish.txt')
if($LASTEXITCODE -ne 0){throw ('Native publish failed: '+(Join-Path $output 'publish.txt'))}
$after=Manifest
$after | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath (Join-Path $output 'sources-after.json') -Encoding utf8
$left=@($before | ForEach-Object {'{0}|{1}|{2}' -f $_.path,$_.length,$_.sha256})
$right=@($after | ForEach-Object {'{0}|{1}|{2}' -f $_.path,$_.length,$_.sha256})
if(@(Compare-Object $left $right).Count -ne 0){throw 'Native publish inputs changed; this binary has no frozen source claim.'}
$dll=Join-Path $output 'publish/XFEEDNavigation.dll'
dotnet run --project (Join-Path $taskRoot 'native/ManagedPlugin/Checks/Checks.csproj') -c Release -- $taskRoot $dll (Join-Path $output 'checks.json') *> (Join-Path $output 'checks.txt')
if($LASTEXITCODE -ne 0){throw ('Native checks failed: '+(Join-Path $output 'checks.txt'))}
$checks=Get-Content -Raw -LiteralPath (Join-Path $output 'checks.json') | ConvertFrom-Json
$rows=@($checks.checks | Where-Object {$null -ne $_.masks})
$source=Get-Content -Raw -LiteralPath (Join-Path $output 'checks-source.cs')
$body=[regex]::Match($source,'(?s)var checks = .*?\{(.*?)\n\};').Groups[1].Value
$expected=@{}
foreach($match in [regex]::Matches($body,'\("([^"]+)",\s*(\d+)\)')){$expected['XFENavigation'+$match.Groups[1].Value+'SelfTest']=[uint32]$match.Groups[2].Value}
$expected['XFEPlatformSelfTest']=2047;$expected['XFEInteractionSelfTest']=255
if(!$checks.passed -or $checks.gameAccess -or $checks.iterations -ne 8 -or $rows.Count -ne $expected.Count){throw 'Native check scope mismatch.'}
foreach($row in $rows){if(!$expected.ContainsKey($row.check) -or $row.expected -ne $expected[$row.check] -or $row.masks.Count -ne 8 -or @($row.masks | Where-Object {$_ -ne $row.expected}).Count -ne 0){throw ('Native mask mismatch: '+$row.check)}}
$summary=[ordered]@{passed=$true;gameAccess=$false;installed=$false;frozenSourceInputs=$before.Count;publishInputsUnchanged=$true;
selfTestGroups=$rows.Count;iterations=8;passingInvocations=($rows.Count*8);declaredNamesAndExpectedMasksVerified=$true;
dllLength=(Get-Item -LiteralPath $dll).Length;dllSha256=(Get-FileHash -LiteralPath $dll -Algorithm SHA256).Hash.ToLowerInvariant();fullOriginalParityConfirmed=$false}
$summary | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $output 'summary.json') -Encoding utf8
$summary | ConvertTo-Json -Compress