Files
RemLink/scripts/validation/Test-AcceptanceRun.ps1
T
qsc20001102 142e5dc7d6
ci / Go checks (ubuntu-latest) (push) Has been cancelled
ci / Go checks (windows-latest) (push) Has been cancelled
初版功能完成
2026-08-29 13:12:17 +08:00

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"