81 lines
3.7 KiB
PowerShell
81 lines
3.7 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$RunDirectory
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$runRoot = [System.IO.Path]::GetFullPath($RunDirectory).TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar)
|
|
$manifestPath = Join-Path $runRoot "acceptance-run.json"
|
|
if (-not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
|
|
throw "Acceptance manifest not found: $manifestPath"
|
|
}
|
|
$manifest = Get-Content -Raw -LiteralPath $manifestPath | ConvertFrom-Json
|
|
if ($manifest.schema_version -ne 2) {
|
|
throw "Unsupported acceptance manifest schema version: $($manifest.schema_version)"
|
|
}
|
|
$wantedGates = @("Gate A", "Gate B", "Gate C", "Gate D")
|
|
$wantedScenarios = 1..18 | ForEach-Object { "T{0:D2}" -f $_ }
|
|
if (@($manifest.gates).Count -ne 4 -or @($manifest.scenarios).Count -ne 18) {
|
|
throw "Manifest must contain four Gates and eighteen scenarios"
|
|
}
|
|
$actualGates = @($manifest.gates | ForEach-Object id | Sort-Object)
|
|
$actualScenarios = @($manifest.scenarios | ForEach-Object id | Sort-Object)
|
|
if (Compare-Object $wantedGates $actualGates -SyncWindow 0) { throw "Manifest Gate IDs are incomplete or duplicated" }
|
|
if (Compare-Object $wantedScenarios $actualScenarios -SyncWindow 0) { throw "Manifest scenario IDs are incomplete or duplicated" }
|
|
|
|
$allEntries = @($manifest.gates) + @($manifest.scenarios)
|
|
foreach ($entry in $allEntries) {
|
|
if ($entry.status -notin @("PASS", "FAIL", "NOT_RUN")) {
|
|
throw "$($entry.id) has invalid status $($entry.status)"
|
|
}
|
|
$evidence = @($entry.evidence)
|
|
if ($entry.status -eq "NOT_RUN") {
|
|
if ($evidence.Count -ne 0 -or $entry.completed_at) {
|
|
throw "$($entry.id) is NOT_RUN but carries evidence or a completion time"
|
|
}
|
|
continue
|
|
}
|
|
if ($evidence.Count -eq 0 -or -not $entry.started_at -or -not $entry.completed_at) {
|
|
throw "$($entry.id) requires evidence plus start/completion timestamps"
|
|
}
|
|
foreach ($record in $evidence) {
|
|
if (-not $record.path -or $record.sha256 -notmatch '^[0-9a-f]{64}$' -or $record.bytes -lt 0) {
|
|
throw "$($entry.id) contains an invalid evidence record"
|
|
}
|
|
$fullPath = [System.IO.Path]::GetFullPath((Join-Path $runRoot $record.path))
|
|
$rootPrefix = $runRoot + [System.IO.Path]::DirectorySeparatorChar
|
|
if (-not $fullPath.StartsWith($rootPrefix, [System.StringComparison]::OrdinalIgnoreCase) -or
|
|
-not (Test-Path -LiteralPath $fullPath -PathType Leaf)) {
|
|
throw "$($entry.id) evidence is missing or outside the run directory: $($record.path)"
|
|
}
|
|
$item = Get-Item -LiteralPath $fullPath
|
|
$actualHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $fullPath).Hash.ToLowerInvariant()
|
|
if ($actualHash -ne $record.sha256 -or $item.Length -ne $record.bytes) {
|
|
throw "$($entry.id) evidence hash/size mismatch: $($record.path)"
|
|
}
|
|
}
|
|
}
|
|
|
|
$dependenciesByGate = [ordered]@{
|
|
"Gate A" = @("T01", "T02")
|
|
"Gate B" = @("Gate A")
|
|
"Gate C" = @("Gate B", "T08", "T09")
|
|
"Gate D" = @("Gate C", "T07", "T08", "T09")
|
|
}
|
|
foreach ($gateID in $dependenciesByGate.Keys) {
|
|
$gate = @($manifest.gates | Where-Object id -eq $gateID)[0]
|
|
if ($gate.status -ne "PASS") { continue }
|
|
foreach ($dependency in $dependenciesByGate[$gateID]) {
|
|
$dependencyEntry = if ($dependency.StartsWith("Gate ")) {
|
|
@($manifest.gates | Where-Object id -eq $dependency)[0]
|
|
} else {
|
|
@($manifest.scenarios | Where-Object id -eq $dependency)[0]
|
|
}
|
|
if ($dependencyEntry.status -ne "PASS") {
|
|
throw "$gateID is PASS while dependency $dependency is not PASS"
|
|
}
|
|
}
|
|
}
|
|
Write-Host "Acceptance manifest and evidence hashes verified: $manifestPath"
|