Files
T

185 lines
17 KiB
PowerShell

#Requires -Version 7.0
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
# Source pin and count are supplied by the reviewed gate at staging; never inferred from HEAD.
function Get-NativeHash([string]$Path) { (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant() }
function Write-NativeJson([string]$Path, $Value) {
$stream=[IO.File]::Open($Path,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::Read)
try {$bytes=[Text.UTF8Encoding]::new($false).GetBytes(($Value|ConvertTo-Json -Depth 30)+"`n");$stream.Write($bytes,0,$bytes.Length);$stream.Flush($true)} finally {$stream.Dispose()}
}
function Assert-NativeFiles([string]$Root,$Files) {
$prefix=[IO.Path]::GetFullPath($Root)+[IO.Path]::DirectorySeparatorChar
foreach($file in $Files){
$path=[IO.Path]::GetFullPath((Join-Path $Root $file.path))
if(-not $path.StartsWith($prefix,[StringComparison]::OrdinalIgnoreCase)){throw 'Manifest path escapes staging directory.'}
$item=Get-Item -LiteralPath $path -Force
if($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 -or $item.Length -ne $file.bytes -or (Get-NativeHash $path) -cne $file.sha256){throw "Changed input: $path"}
}
}
function Get-NativeRecord([string]$Root,[string]$Path){
$item=Get-Item -LiteralPath $Path
[ordered]@{path=[IO.Path]::GetRelativePath($Root,$item.FullName).Replace('\','/');bytes=$item.Length;sha256=(Get-NativeHash $Path)}
}
function Assert-InputPair($Before,$After){
foreach($record in @($Before,$After)){if($record.base -cne $nativePin -or $record.count -ne $nativeInputCount -or @($record.files).Count -ne $nativeInputCount){throw 'Complete current-pin source records required.'}}
$rows=@{}
foreach($file in $Before.files){if($rows.ContainsKey($file.path)){throw 'Duplicate source input.'};$rows[$file.path]=$file}
foreach($file in $After.files){
if(-not $rows.ContainsKey($file.path)){throw 'Unexpected or duplicate source input.'}
$old=$rows[$file.path];if($old.bytes -ne $file.bytes -or $old.sha256 -cne $file.sha256){throw 'Source input changed.'};$rows.Remove($file.path)
}
if($rows.Count){throw 'Missing source inputs.'}
}
function Assert-BuildEvidence([string]$Gate){
Assert-SelectionEvidence $Gate
$expectedWorkspace=(Read-NativeJson (Join-Path $Gate 'expected-inputs.json')).workspace
$expectedTarget='C:\Users\User\Documents\Git\ARCADIA-Studio\target'
$initial=Get-Content -Raw -LiteralPath (Join-Path $Gate 'inputs-complete-before.json')|ConvertFrom-Json
Assert-InputPair (Read-NativeJson (Join-Path $Gate 'expected-inputs.json')) $initial
Assert-StrictPreflightEvidence $Gate $initial
$previous='strict-preflight'
foreach($stage in @('list','focused','all','clippy','build')){
$receiptPath=Join-Path $Gate ($stage+'-receipt.json')
$r=Get-Content -Raw -LiteralPath $receiptPath|ConvertFrom-Json
if($r.stage -cne $stage -or $r.base -cne $nativePin -or $r.workspace -ine $expectedWorkspace -or $r.target -ine $expectedTarget -or $r.cargoExitCode -ne 0 -or -not $r.selectionValid -or -not $r.inputsUnchanged -or -not $r.testBinaryBindingValid -or $null -ne $r.postflightFailure){throw "Successful unchanged current-pin stage required: $stage"}
if($stage -eq 'clippy' -and $r.command -cne 'cargo clippy -p oa-desktop -p oa-server --all-targets --locked --offline -- -D warnings'){throw 'Combined desktop/server strict acceptance required.'}
foreach($binding in $r.boundScriptAndInputHashes.PSObject.Properties){if((Get-NativeHash (Join-Path $Gate $binding.Name)) -cne $binding.Value){throw "Gate binding changed: $($binding.Name)"}}
if((Get-NativeHash (Join-Path $Gate ($stage+'.log'))) -cne $r.rawLogSha256){throw 'Gate raw log changed.'}
if((Get-NativeHash (Join-Path $Gate 'toolchain-versions.json')) -cne $r.toolchainVersionsSha256){throw 'Toolchain receipt changed.'}
if($null -ne $previous){if($r.priorStage.stage -cne $previous -or $r.priorStage.receiptSha256 -cne (Get-NativeHash (Join-Path $Gate ($previous+'-receipt.json'))) -or $r.priorStage.rawLogSha256 -cne (Get-NativeHash (Join-Path $Gate ($previous+'.log')))){throw 'Broken sequential gate chain.'}}
$beforePath=Join-Path $Gate ('inputs-complete-'+$stage+'-before.json');$afterPath=Join-Path $Gate ('inputs-complete-'+$stage+'-after.json')
if((Get-NativeHash $beforePath) -cne $r.beforeCaptureSha256 -or (Get-NativeHash $afterPath) -cne $r.afterCaptureSha256){throw 'Gate capture changed.'}
$before=Get-Content -Raw -LiteralPath $beforePath|ConvertFrom-Json;$after=Get-Content -Raw -LiteralPath $afterPath|ConvertFrom-Json
Assert-InputPair $initial $before;Assert-InputPair $before $after
$previous=$stage
}
if($null -eq $r.binary -or $r.binary.bytes -le 0 -or $r.binary.sha256 -notmatch '^[0-9a-f]{64}$'){throw 'Successful build must bind an actual binary.'}
return $r
}
function Read-NativeJson([string]$Path) { Get-Content -Raw -LiteralPath $Path | ConvertFrom-Json }
function Assert-NativeNames($Expected,$Actual) {
$a=@($Expected);$b=@($Actual)
if($a.Count -ne $b.Count -or @($a|Sort-Object -Unique).Count -ne $a.Count -or @($b|Sort-Object -Unique).Count -ne $b.Count -or @(Compare-Object $a $b -CaseSensitive).Count){throw 'Exact unique membership differs.'}
}
function Assert-NativeBinaries($Expected,$Actual) {
$a=@($Expected);$b=@($Actual)
if($a.Count -ne 1 -or $b.Count -ne 1 -or $a[0].path -ine $b[0].path -or $a[0].bytes -le 0 -or $a[0].bytes -ne $b[0].bytes -or $a[0].sha256 -cne $b[0].sha256 -or $a[0].sha256 -notmatch '^[0-9a-f]{64}$'){throw 'Exact test executable continuity differs.'}
}
function Assert-SelectionEvidence([string]$Gate) {
if($nativePin -cne 'b10b28cefd1d0794073ba241a0c8de9a83e85f68' -or $nativeInputCount -ne 612){throw 'Exact reviewed b10 source and612 inputs required.'}
$selection=Read-NativeJson (Join-Path $Gate 'expected-tests.json')
if($selection.source_commit -cne $nativePin -or $selection.expected_count -ne 301 -or $selection.expected_desktop_count -ne 1095 -or $selection.expected_filtered -ne 794 -or @($selection.tests).Count -ne 301 -or @($selection.desktop_tests).Count -ne 1095 -or @($selection.ignored_tests).Count -ne 4){throw 'Wrong 301/1095/4 selection contract.'}
$inputs=Read-NativeJson (Join-Path $Gate 'expected-inputs.json')
if($inputs.base -cne $nativePin -or $inputs.count -ne $nativeInputCount){throw 'Wrong pinned source contract'}
Assert-InputPair $inputs $inputs
$previous=$null
foreach($stage in @('list','focused','all','clippy','build')) {
$r=Read-NativeJson (Join-Path $Gate ($stage+'-receipt.json'))
if($null -ne $previous){Assert-NativeBinaries $previous.testExecutables $r.testExecutablesBefore;Assert-NativeBinaries $r.testExecutablesBefore $r.testExecutables
if($r.priorStage.rawLogSha256 -cne $previous.rawLogSha256){throw 'Prior raw-log chain differs.'}
} else {Assert-NativeBinaries $r.testExecutables $r.testExecutables}
if($stage -eq 'focused'){Assert-BatchedFocusedEvidence $Gate $r $selection;$previous=$r;continue}
if($stage -eq 'list') {
Assert-NativeNames $selection.desktop_tests $r.listedTests
if(($r.listSummaries|Measure-Object -Property tests -Sum).Sum -ne 1095 -or ($r.listSummaries|Measure-Object -Property benchmarks -Sum).Sum -ne 0){throw 'Wrong actual listing count.'}
}
if($stage -in @('focused','all')) {
$expected=if($stage -eq 'focused'){$selection.tests}else{$selection.desktop_tests}
Assert-NativeNames $expected @($r.executedTests|ForEach-Object {$_.name})
$ignored=@($r.executedTests|Where-Object {$_.result -eq 'ignored'}|ForEach-Object {$_.name})
if(@($r.executedTests|Where-Object {$_.result -notin @('ok','ignored')}).Count -or @($r.testResultSummaries).Count -ne 1){throw 'Unexpected actual test result.'}
$s=$r.testResultSummaries[0]
$pass=if($stage -eq 'focused'){301}else{1091};$ignore=if($stage -eq 'focused'){0}else{4};$filtered=if($stage -eq 'focused'){794}else{0}
if($s.status -cne 'ok' -or $s.passed -ne $pass -or $s.failed -ne 0 -or $s.ignored -ne $ignore -or $s.measured -ne 0 -or $s.filtered -ne $filtered -or $ignored.Count -ne $ignore){throw 'Wrong actual pass/ignored/filtered totals.'}
if($stage -eq 'all'){Assert-NativeNames $selection.ignored_tests $ignored}
}
$previous=$r
}
}
function Assert-CurrentNativeSource([string]$Gate) {
$expected=Read-NativeJson (Join-Path $Gate 'expected-inputs.json')
$enumeration=Read-NativeJson (Join-Path $Gate 'enumeration.json')
$external=Read-NativeJson (Join-Path $Gate 'external-inputs.json')
if($expected.base -cne $nativePin -or $enumeration.base -cne $nativePin -or $external.base -cne $nativePin){throw 'Wrong source-input pin.'}
Assert-InputPair $expected $expected
Push-Location -LiteralPath $expected.workspace
try {
$head=git rev-parse HEAD
if($LASTEXITCODE -ne 0 -or $head -cne $nativePin){throw 'Source HEAD differs.'}
$status=@(git status --porcelain)
if($LASTEXITCODE -ne 0 -or $status.Count){throw 'Source worktree is not frozen and clean.'}
$roots=@($enumeration.rootInputs);$scopes=@($enumeration.scopes)
$tracked=@(git ls-files -- @roots @scopes)
if($LASTEXITCODE -ne 0){throw 'Source Git enumeration failed.'}
$present=@(rg --files @scopes)
if($LASTEXITCODE -ne 0){throw 'Source filesystem enumeration failed.'}
$actual=@(($present+$roots)|ForEach-Object {$_.Replace('\','/')}|Sort-Object -Unique)
Assert-NativeNames @($enumeration.gitPaths) @($tracked|ForEach-Object {$_.Replace('\','/')})
Assert-NativeNames @($expected.files|ForEach-Object {$_.path}) $actual
Assert-NativeFiles $expected.workspace $expected.files
foreach($f in $external.files) {
$exists=Test-Path -LiteralPath $f.path
if($exists -ne $f.present){throw "External config presence changed: $($f.path)"}
if($exists -and ((Get-Item -LiteralPath $f.path).Length -ne $f.bytes -or (Get-NativeHash $f.path) -cne $f.sha256)){throw "External input changed: $($f.path)"}
}
} finally {Pop-Location}
}
function Assert-BatchedFocusedEvidence([string]$Gate,$Receipt,$Selection) {
if($Receipt.executionMode -cne 'bounded-exact-batches-v1'){throw 'Explicit bounded focused recovery required'}
$planPath=Join-Path $Gate 'batches.json';$plan=Read-NativeJson $planPath
if((Get-NativeHash $planPath) -cne $Receipt.batchPlanSha256 -or $plan.schema -cne 'scenario_details_exact_batches_v1' -or $plan.source_commit -cne $nativePin -or $plan.focused_count -ne 301 -or $plan.desktop_count -ne 1095 -or $plan.max_command_characters -ne 12000){throw 'Wrong focused batch plan/list binding'}
if(@($plan.batches).Count -ne 3 -or @($Receipt.batchReceipts).Count -ne @($plan.batches).Count -or @($Receipt.testResultSummaries).Count -ne @($plan.batches).Count){throw 'Missing focused batch receipts or actual summaries'}
$union=@();$previous=$null;$raw=[Collections.Generic.List[byte]]::new()
$initial=Read-NativeJson (Join-Path $Gate 'inputs-complete-before.json')
for($i=0;$i -lt @($plan.batches).Count;$i++){
$batch=$plan.batches[$i];$binding=$Receipt.batchReceipts[$i];$name=('focused-batch{0:d2}' -f ($i+1))
if(@($batch.tests).Count -ne @(107,101,93)[$i]){throw 'Reviewed 107/101/93 partition required'}
if($batch.id -cne $name -or $binding.stage -cne $name){throw 'Batch sequence differs'}
$path=Join-Path $Gate ($name+'-receipt.json');$log=Join-Path $Gate ($name+'.log');$r=Read-NativeJson $path
if((Get-NativeHash $path) -cne $binding.receiptSha256 -or (Get-NativeHash $log) -cne $binding.rawLogSha256 -or $r.rawLogSha256 -cne $binding.rawLogSha256){throw 'Batch receipt or log changed'}
if($r.schema -cne 'scenario_details_exact_batch_v1' -or $r.stage -cne $name -or $r.base -cne $nativePin -or $r.workspace -ine $Receipt.workspace -or $r.target -ine $Receipt.target -or $r.cargoExitCode -ne 0 -or -not $r.selectionValid -or -not $r.inputsUnchanged -or -not $r.testBinaryBindingValid -or $null -ne $r.postflightFailure -or $r.listReceiptSha256 -cne (Get-NativeHash (Join-Path $Gate 'list-receipt.json')) -or $r.batchPlanSha256 -cne $Receipt.batchPlanSha256){throw 'Batch did not pass with exact source/binary bindings'}
if(($null -eq $previous) -ne ($null -eq $r.priorBatch)){throw 'Wrong first/prior batch binding'}
if($null -ne $previous -and ($r.priorBatch.stage -cne $previous.stage -or $r.priorBatch.receiptSha256 -cne $previous.receiptSha256 -or $r.priorBatch.rawLogSha256 -cne $previous.rawLogSha256)){throw 'Broken batch chain'}
$args=@('test','-p','oa-desktop','--bin','arcadia-studio','--locked','--offline','--','--exact')+@($batch.tests)+@('--test-threads=1')
if(Compare-Object $args @($r.arguments) -SyncWindow 0 -CaseSensitive){throw 'Actual batch arguments differ'}
$length=0;foreach($arg in @($plan.cargo_path)+$args){$length+=$arg.Length+3}
if($length -gt 12000 -or $length -ne $batch.command_characters -or $r.commandCharacters -ne $length){throw 'Batch command exceeds reviewed bound'}
Assert-NativeNames @($batch.tests) @($r.executedTests|ForEach-Object {$_.name})
if(@($r.executedTests|Where-Object {$_.result -cne 'ok'}).Count -or @($r.testResultSummaries).Count -ne 1){throw 'Batch contains failed/ignored outcomes'}
$s=$r.testResultSummaries[0];$n=@($batch.tests).Count
if($n -le 0 -or $s.status -cne 'ok' -or $s.passed -ne $n -or $s.failed -ne 0 -or $s.ignored -ne 0 -or $s.measured -ne 0 -or $s.filtered -ne (1095-$n)){throw 'Wrong per-batch actual totals'}
foreach($field in @('status','passed','failed','ignored','measured','filtered')){if($Receipt.testResultSummaries[$i].$field -cne $s.$field){throw 'Aggregate summary differs from actual batch'}}
foreach($side in @('before','after')){$capture=Join-Path $Gate ('inputs-complete-'+$name+'-'+$side+'.json');$property=$side+'CaptureSha256';if((Get-NativeHash $capture) -cne $r.$property){throw 'Batch capture changed'};Assert-InputPair $initial (Read-NativeJson $capture)}
foreach($bindingInput in $r.boundScriptAndInputHashes.PSObject.Properties){if((Get-NativeHash (Join-Path $Gate $bindingInput.Name)) -cne $bindingInput.Value){throw 'Batch script/input binding changed'}}
if($r.toolchainVersionsSha256 -cne $Receipt.toolchainVersionsSha256){throw 'Batch toolchain binding differs'}
Assert-NativeBinaries $Receipt.testExecutablesBefore $r.testExecutablesBefore
Assert-NativeBinaries $r.testExecutablesBefore $r.testExecutables
Assert-NativeBinaries $r.testExecutables $Receipt.testExecutables
$union+=@($r.executedTests|ForEach-Object {$_.name});$raw.AddRange([IO.File]::ReadAllBytes($log));$previous=$binding
}
Assert-NativeNames $Selection.tests $union
Assert-NativeNames $union @($Receipt.executedTests|ForEach-Object {$_.name})
if(@($Receipt.executedTests|Where-Object {$_.result -cne 'ok'}).Count -or @($Receipt.ignoredTests).Count){throw 'Aggregate contains nonpassing tests'}
$combined=[Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($raw.ToArray())).ToLowerInvariant()
if($combined -cne $Receipt.rawLogSha256){throw 'Focused raw log is not the exact batch concatenation'}
}
function Assert-StrictPreflightEvidence([string]$Gate,$Initial) {
$r=Read-NativeJson (Join-Path $Gate 'strict-preflight-receipt.json')
$expected=Read-NativeJson (Join-Path $Gate 'expected-inputs.json')
if($r.schema -cne 'scenario_strict_preflight_v1' -or $r.stage -cne 'strict-preflight' -or $r.base -cne $nativePin -or $r.workspace -ine $expected.workspace -or $r.target -ine 'C:\Users\User\Documents\Git\ARCADIA-Studio\target' -or $r.cargoExitCode -ne 0 -or -not $r.passed -or -not $r.inputsUnchanged -or $null -ne $r.postflightFailure){throw 'Successful exact-pin strict preflight required'}
if($r.command -cne 'cargo clippy -p oa-desktop -p oa-server --all-targets --locked --offline -- -D warnings'){throw 'Wrong strict preflight command'}
if((Get-NativeHash (Join-Path $Gate 'run.ps1')) -cne '9f3540a6984858448ad1f9bcf6e3326b6ec2b4b3ee897189876c56ebeaaea6cc' -or (Get-NativeHash (Join-Path $Gate 'strict-preflight.ps1')) -cne '152621fa4efe0a96adbeea43c4a08be21a63f5d4982846a9ad46de52ddbc725f'){throw 'Reviewed regular/preflight recipe differs'}
foreach($binding in $r.boundScriptAndInputHashes.PSObject.Properties){if((Get-NativeHash (Join-Path $Gate $binding.Name)) -cne $binding.Value){throw 'Preflight input/recipe binding changed'}}
if((Get-NativeHash (Join-Path $Gate 'strict-preflight.log')) -cne $r.rawLogSha256 -or (Get-NativeHash (Join-Path $Gate 'toolchain-versions.json')) -cne $r.toolchainVersionsSha256){throw 'Preflight raw output/toolchain record changed'}
$versions=Read-NativeJson (Join-Path $Gate 'toolchain-versions.json')
if($versions.base -cne $nativePin -or $versions.externalEnvironmentPath -ine $r.externalEnvironmentPath -or $versions.externalEnvironmentSha256 -cne $r.externalEnvironmentSha256){throw 'Preflight environment binding differs'}
foreach($side in @('before','after')){
$capture=Join-Path $Gate ('inputs-complete-strict-preflight-'+$side+'.json');$field=$side+'CaptureSha256'
if((Get-NativeHash $capture) -cne $r.$field){throw 'Preflight capture changed'}
Assert-InputPair $Initial (Read-NativeJson $capture)
}
}