473 lines
20 KiB
PowerShell
473 lines
20 KiB
PowerShell
param(
|
||
[string]$ReturnRoot = "",
|
||
[string]$ReviewRegisterPath = "",
|
||
[string]$ArtifactRoot = "",
|
||
[switch]$FailOnBlocked
|
||
)
|
||
|
||
$ErrorActionPreference = "Stop"
|
||
|
||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss-fff"
|
||
|
||
function Resolve-LatestArtifactDir {
|
||
param([string]$RootRelativePath)
|
||
$root = Join-Path $repoRoot $RootRelativePath
|
||
$latest = Get-ChildItem -LiteralPath $root -Directory -ErrorAction SilentlyContinue |
|
||
Sort-Object Name -Descending |
|
||
Select-Object -First 1
|
||
if ($latest) {
|
||
return $latest.FullName
|
||
}
|
||
return ""
|
||
}
|
||
|
||
if ([string]::IsNullOrWhiteSpace($ReturnRoot)) {
|
||
$latestKit = Resolve-LatestArtifactDir -RootRelativePath "project-docs\qa-artifacts\phase13-reviewer-kit-export"
|
||
if (-not [string]::IsNullOrWhiteSpace($latestKit)) {
|
||
$ReturnRoot = Join-Path $latestKit "reviewer-kits\return-templates"
|
||
}
|
||
}
|
||
if (-not [string]::IsNullOrWhiteSpace($ReturnRoot) -and -not [System.IO.Path]::IsPathRooted($ReturnRoot)) {
|
||
$ReturnRoot = Join-Path $repoRoot $ReturnRoot
|
||
}
|
||
|
||
if ([string]::IsNullOrWhiteSpace($ReviewRegisterPath)) {
|
||
$ReviewRegisterPath = Join-Path $repoRoot "project-docs\phase13-legal-knowledge-template-library\inventories\expert-review-register.csv"
|
||
}
|
||
if (-not [System.IO.Path]::IsPathRooted($ReviewRegisterPath)) {
|
||
$ReviewRegisterPath = Join-Path $repoRoot $ReviewRegisterPath
|
||
}
|
||
|
||
if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) {
|
||
$ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase13-review-return-preflight"
|
||
}
|
||
if (-not [System.IO.Path]::IsPathRooted($ArtifactRoot)) {
|
||
$ArtifactRoot = Join-Path $repoRoot $ArtifactRoot
|
||
}
|
||
|
||
$artifactDir = Join-Path $ArtifactRoot $timestamp
|
||
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
|
||
|
||
$summaryJson = Join-Path $artifactDir "phase13-review-return-preflight-summary.json"
|
||
$summaryMd = Join-Path $artifactDir "phase13-review-return-preflight-summary.md"
|
||
$sourceManifestCsv = Join-Path $artifactDir "return-source-manifest.csv"
|
||
$validationBoardCsv = Join-Path $artifactDir "return-validation-board.csv"
|
||
$blockersCsv = Join-Path $artifactDir "return-preflight-blockers.csv"
|
||
$diffPreviewCsv = Join-Path $artifactDir "return-diff-preview.csv"
|
||
$mergePlanCsv = Join-Path $artifactDir "return-merge-plan.csv"
|
||
$gitStatusPath = Join-Path $artifactDir "git-status.txt"
|
||
|
||
$checks = New-Object System.Collections.Generic.List[object]
|
||
$sourceRows = New-Object System.Collections.Generic.List[object]
|
||
$validationRows = New-Object System.Collections.Generic.List[object]
|
||
$blockerRows = New-Object System.Collections.Generic.List[object]
|
||
$diffRows = New-Object System.Collections.Generic.List[object]
|
||
$mergeRows = New-Object System.Collections.Generic.List[object]
|
||
|
||
$reviewRequiredColumns = @("review_id", "asset_type", "asset_id", "asset_name", "reviewer_role", "reviewer", "review_date", "review_result", "required_changes", "evidence_path", "allow_review_import", "allow_production_release", "notes")
|
||
$ackRequiredColumns = @("work_order_id", "recipient_role", "recipient", "handoff_owner", "acknowledged", "ack_date", "package_path", "missing_materials", "notes")
|
||
$allowedReviewResults = @("PENDING", "CHANGES_REQUIRED", "APPROVED_FOR_REVIEW_IMPORT", "APPROVED_FOR_PRODUCTION", "REJECTED")
|
||
|
||
function Add-Check {
|
||
param(
|
||
[string]$Key,
|
||
[string]$Label,
|
||
[ValidateSet("pass", "fail", "warn", "skip", "blocked")]
|
||
[string]$Status,
|
||
[string]$Detail,
|
||
[bool]$Required = $true,
|
||
[hashtable]$Meta = @{}
|
||
)
|
||
|
||
$checks.Add([ordered]@{
|
||
key = $Key
|
||
label = $Label
|
||
status = $Status
|
||
required = $Required
|
||
detail = $Detail
|
||
meta = $Meta
|
||
}) | Out-Null
|
||
}
|
||
|
||
function Add-Blocker {
|
||
param([string]$Kind, [string]$Id, [string]$Severity, [string]$Blocker, [string]$Evidence)
|
||
$blockerRows.Add([pscustomobject][ordered]@{
|
||
kind = $Kind
|
||
id = $Id
|
||
severity = $Severity
|
||
blocker = $Blocker
|
||
evidence = $Evidence
|
||
}) | Out-Null
|
||
}
|
||
|
||
function Save-Json {
|
||
param([string]$Path, [object]$Value)
|
||
$Value | ConvertTo-Json -Depth 50 | Set-Content -Encoding UTF8 -Path $Path
|
||
}
|
||
|
||
function Import-CsvRows {
|
||
param([string]$Path)
|
||
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
||
return @()
|
||
}
|
||
return @(Import-Csv -LiteralPath $Path)
|
||
}
|
||
|
||
function Get-CsvHeader {
|
||
param([string]$Path)
|
||
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
||
return @()
|
||
}
|
||
$firstLine = Get-Content -LiteralPath $Path -Encoding UTF8 -TotalCount 1
|
||
if ([string]::IsNullOrWhiteSpace($firstLine)) {
|
||
return @()
|
||
}
|
||
return @($firstLine.Split(",") | ForEach-Object { $_.Trim().Trim('"') })
|
||
}
|
||
|
||
function Test-RequiredColumns {
|
||
param([string]$Path, [string[]]$Columns)
|
||
$header = Get-CsvHeader -Path $Path
|
||
return @($Columns | Where-Object { $header -notcontains $_ })
|
||
}
|
||
|
||
function Test-Truthy {
|
||
param([string]$Value)
|
||
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||
return $false
|
||
}
|
||
return @("true", "yes", "1", "是", "已签收") -contains $Value.Trim().ToLowerInvariant()
|
||
}
|
||
|
||
function Test-PendingName {
|
||
param([string]$Value)
|
||
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||
return $true
|
||
}
|
||
return ($Value -match "待指定|待确认|待复核|占位|示例")
|
||
}
|
||
|
||
function Convert-OverallStatusText {
|
||
param([string]$Status)
|
||
switch ($Status) {
|
||
"PASS" { return "通过" }
|
||
"WARN" { return "通过但有警告" }
|
||
"FAIL" { return "未通过" }
|
||
"BLOCKED" { return "阻塞" }
|
||
default { return $Status }
|
||
}
|
||
}
|
||
|
||
function Convert-CheckStatusText {
|
||
param([string]$Status)
|
||
switch ($Status) {
|
||
"pass" { return "通过" }
|
||
"fail" { return "失败" }
|
||
"warn" { return "警告" }
|
||
"skip" { return "跳过" }
|
||
"blocked" { return "阻塞" }
|
||
default { return $Status }
|
||
}
|
||
}
|
||
|
||
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
|
||
Add-Check -Key "gstack" -Label "gstack 前置检查" -Status "fail" -Detail "gstack 缺失"
|
||
} else {
|
||
Add-Check -Key "gstack" -Label "gstack 前置检查" -Status "pass" -Detail "GSTACK_OK"
|
||
}
|
||
|
||
$branch = (git -C $repoRoot rev-parse --abbrev-ref HEAD).Trim()
|
||
$commit = (git -C $repoRoot rev-parse HEAD).Trim()
|
||
$remoteUrl = (git -C $repoRoot remote get-url origin).Trim()
|
||
$gitStatus = git -C $repoRoot status --short
|
||
($gitStatus -join [Environment]::NewLine) | Set-Content -Encoding UTF8 -Path $gitStatusPath
|
||
|
||
if ($remoteUrl -match "192\.168\.3\.90") {
|
||
Add-Check -Key "origin_remote" -Label "origin 远端仓库" -Status "pass" -Detail "origin 指向 192.168.3.90" -Meta @{ branch = $branch; commit = $commit; remote = $remoteUrl }
|
||
} else {
|
||
Add-Check -Key "origin_remote" -Label "origin 远端仓库" -Status "fail" -Detail "origin 未指向 192.168.3.90" -Meta @{ branch = $branch; commit = $commit; remote = $remoteUrl }
|
||
}
|
||
|
||
if ($gitStatus) {
|
||
Add-Check -Key "working_tree" -Label "工作区状态" -Status "warn" -Required $false -Detail "工作区存在未提交改动;回收预检按证据记录" -Meta @{ artifact = $gitStatusPath }
|
||
} else {
|
||
Add-Check -Key "working_tree" -Label "工作区状态" -Status "pass" -Detail "工作区干净" -Meta @{ artifact = $gitStatusPath }
|
||
}
|
||
|
||
if (Test-Path -LiteralPath $ReturnRoot -PathType Container) {
|
||
Add-Check -Key "return_root" -Label "回收文件目录" -Status "pass" -Detail "回收文件目录存在" -Meta @{ path = $ReturnRoot }
|
||
} else {
|
||
Add-Check -Key "return_root" -Label "回收文件目录" -Status "fail" -Detail "回收文件目录不存在" -Meta @{ path = $ReturnRoot }
|
||
}
|
||
|
||
$registerRows = Import-CsvRows -Path $ReviewRegisterPath
|
||
$registerByReviewId = @{}
|
||
foreach ($registerRow in $registerRows) {
|
||
if ($registerRow.review_id) {
|
||
$registerByReviewId[$registerRow.review_id] = $registerRow
|
||
}
|
||
}
|
||
|
||
$returnFiles = @(
|
||
[pscustomobject]@{ kind = "legal"; path = Join-Path $ReturnRoot "legal-review-result-return.csv"; required_columns = $reviewRequiredColumns },
|
||
[pscustomobject]@{ kind = "business"; path = Join-Path $ReturnRoot "business-review-result-return.csv"; required_columns = $reviewRequiredColumns },
|
||
[pscustomobject]@{ kind = "ack"; path = Join-Path $ReturnRoot "handoff-ack-return.csv"; required_columns = $ackRequiredColumns }
|
||
)
|
||
|
||
$allReviewReturns = New-Object System.Collections.Generic.List[object]
|
||
$ackReturns = @()
|
||
foreach ($returnFile in $returnFiles) {
|
||
$exists = Test-Path -LiteralPath $returnFile.path -PathType Leaf
|
||
$missing = if ($exists) { Test-RequiredColumns -Path $returnFile.path -Columns $returnFile.required_columns } else { $returnFile.required_columns }
|
||
$rows = if ($exists) { Import-CsvRows -Path $returnFile.path } else { @() }
|
||
$sourceRows.Add([pscustomobject][ordered]@{
|
||
kind = $returnFile.kind
|
||
path = $returnFile.path
|
||
exists = $exists
|
||
rows = $rows.Count
|
||
missing_columns = ($missing -join ";")
|
||
status = if ($exists -and $missing.Count -eq 0) { "ready" } else { "invalid" }
|
||
}) | Out-Null
|
||
if (-not $exists) {
|
||
Add-Blocker -Kind $returnFile.kind -Id $returnFile.path -Severity "P0" -Blocker "回收文件缺失" -Evidence $returnFile.path
|
||
} elseif ($missing.Count -gt 0) {
|
||
Add-Blocker -Kind $returnFile.kind -Id $returnFile.path -Severity "P0" -Blocker "回收文件字段缺失" -Evidence ($missing -join ",")
|
||
}
|
||
if ($returnFile.kind -eq "ack") {
|
||
$ackReturns = $rows
|
||
} else {
|
||
foreach ($row in $rows) {
|
||
$allReviewReturns.Add($row) | Out-Null
|
||
}
|
||
}
|
||
}
|
||
$sourceRows | Export-Csv -LiteralPath $sourceManifestCsv -NoTypeInformation -Encoding UTF8
|
||
|
||
foreach ($row in $allReviewReturns) {
|
||
$reviewResult = if ($row.review_result) { $row.review_result.Trim() } else { "" }
|
||
$reviewId = [string]$row.review_id
|
||
$inRegister = $registerByReviewId.ContainsKey($reviewId)
|
||
$resultAllowed = $allowedReviewResults -contains $reviewResult
|
||
$reviewerReady = -not (Test-PendingName -Value $row.reviewer)
|
||
$dateReady = -not [string]::IsNullOrWhiteSpace($row.review_date)
|
||
$evidenceReady = -not [string]::IsNullOrWhiteSpace($row.evidence_path)
|
||
$completed = ($reviewResult -ne "PENDING" -and $resultAllowed -and $reviewerReady -and $dateReady -and $evidenceReady)
|
||
$mergeAction = if ($completed) { "READY_FOR_DRY_RUN_APPLY" } else { "HOLD" }
|
||
|
||
if (-not $inRegister) {
|
||
Add-Blocker -Kind "review_return" -Id $reviewId -Severity "P0" -Blocker "复核编号不在专家复核登记表中" -Evidence $reviewId
|
||
}
|
||
if (-not $resultAllowed) {
|
||
Add-Blocker -Kind "review_return" -Id $reviewId -Severity "P0" -Blocker "复核结果枚举非法" -Evidence $reviewResult
|
||
}
|
||
if ($reviewResult -eq "PENDING") {
|
||
Add-Blocker -Kind "review_return" -Id $reviewId -Severity "P0" -Blocker "复核结果仍为 PENDING" -Evidence $reviewId
|
||
} elseif (-not $completed) {
|
||
Add-Blocker -Kind "review_return" -Id $reviewId -Severity "P0" -Blocker "复核结果缺少复核人、日期或证据路径" -Evidence "reviewer=$($row.reviewer); date=$($row.review_date); evidence=$($row.evidence_path)"
|
||
}
|
||
|
||
$validationRows.Add([pscustomobject][ordered]@{
|
||
kind = "review_return"
|
||
review_id = $reviewId
|
||
asset_id = $row.asset_id
|
||
reviewer_role = $row.reviewer_role
|
||
reviewer = $row.reviewer
|
||
review_result = $reviewResult
|
||
in_register = $inRegister
|
||
result_allowed = $resultAllowed
|
||
reviewer_ready = $reviewerReady
|
||
date_ready = $dateReady
|
||
evidence_ready = $evidenceReady
|
||
completed = $completed
|
||
merge_action = $mergeAction
|
||
}) | Out-Null
|
||
|
||
$current = if ($inRegister) { $registerByReviewId[$reviewId] } else { $null }
|
||
$diffRows.Add([pscustomobject][ordered]@{
|
||
review_id = $reviewId
|
||
field = "review_result"
|
||
current_value = if ($current) { $current.review_result } else { "" }
|
||
returned_value = $reviewResult
|
||
merge_action = $mergeAction
|
||
}) | Out-Null
|
||
|
||
$mergeRows.Add([pscustomobject][ordered]@{
|
||
review_id = $reviewId
|
||
asset_type = $row.asset_type
|
||
asset_id = $row.asset_id
|
||
merge_action = $mergeAction
|
||
next_gate = "apply-phase13-review-results.ps1"
|
||
notes = if ($completed) { "可进入 DryRun 回填校验" } else { "等待真实复核结果齐套" }
|
||
}) | Out-Null
|
||
}
|
||
|
||
foreach ($ack in $ackReturns) {
|
||
$acknowledged = Test-Truthy -Value $ack.acknowledged
|
||
$dateReady = -not [string]::IsNullOrWhiteSpace($ack.ack_date)
|
||
$recipientReady = -not (Test-PendingName -Value $ack.recipient)
|
||
$completed = ($acknowledged -and $dateReady -and $recipientReady)
|
||
if (-not $completed) {
|
||
Add-Blocker -Kind "ack_return" -Id $ack.work_order_id -Severity "P0" -Blocker "签收回收未完成" -Evidence "acknowledged=$($ack.acknowledged); ack_date=$($ack.ack_date); recipient=$($ack.recipient)"
|
||
}
|
||
$validationRows.Add([pscustomobject][ordered]@{
|
||
kind = "ack_return"
|
||
review_id = $ack.work_order_id
|
||
asset_id = ""
|
||
reviewer_role = $ack.recipient_role
|
||
reviewer = $ack.recipient
|
||
review_result = if ($acknowledged) { "ACKNOWLEDGED" } else { "PENDING" }
|
||
in_register = $true
|
||
result_allowed = $true
|
||
reviewer_ready = $recipientReady
|
||
date_ready = $dateReady
|
||
evidence_ready = $true
|
||
completed = $completed
|
||
merge_action = if ($completed) { "READY_FOR_PROGRESS_GATE" } else { "HOLD" }
|
||
}) | Out-Null
|
||
}
|
||
|
||
$validationRows | Export-Csv -LiteralPath $validationBoardCsv -NoTypeInformation -Encoding UTF8
|
||
$blockerRows | Export-Csv -LiteralPath $blockersCsv -NoTypeInformation -Encoding UTF8
|
||
$diffRows | Export-Csv -LiteralPath $diffPreviewCsv -NoTypeInformation -Encoding UTF8
|
||
$mergeRows | Export-Csv -LiteralPath $mergePlanCsv -NoTypeInformation -Encoding UTF8
|
||
|
||
$completedReviewReturns = @($validationRows | Where-Object { $_.kind -eq "review_return" -and $_.completed }).Count
|
||
$pendingReviewReturns = @($validationRows | Where-Object { $_.kind -eq "review_return" -and -not $_.completed }).Count
|
||
$completedAckReturns = @($validationRows | Where-Object { $_.kind -eq "ack_return" -and $_.completed }).Count
|
||
$pendingAckReturns = @($validationRows | Where-Object { $_.kind -eq "ack_return" -and -not $_.completed }).Count
|
||
$mergeReadyRows = @($mergeRows | Where-Object { $_.merge_action -eq "READY_FOR_DRY_RUN_APPLY" }).Count
|
||
|
||
if ($sourceRows.Count -eq 3 -and @($sourceRows | Where-Object { $_.status -eq "ready" }).Count -eq 3) {
|
||
Add-Check -Key "return_files" -Label "回收文件结构" -Status "pass" -Detail "回收文件结构齐套"
|
||
} else {
|
||
Add-Check -Key "return_files" -Label "回收文件结构" -Status "fail" -Detail "回收文件结构缺失或字段不完整"
|
||
}
|
||
|
||
if ($validationRows.Count -gt 0) {
|
||
Add-Check -Key "validation_board" -Label "回收预检看板" -Status "pass" -Detail "已生成 $($validationRows.Count) 条回收预检记录"
|
||
} else {
|
||
Add-Check -Key "validation_board" -Label "回收预检看板" -Status "fail" -Detail "未生成回收预检记录"
|
||
}
|
||
|
||
if ($completedReviewReturns -gt 0 -and $pendingReviewReturns -eq 0 -and $pendingAckReturns -eq 0) {
|
||
Add-Check -Key "return_completion" -Label "真实回收结果齐套" -Status "pass" -Detail "复核与签收回收结果均已齐套"
|
||
} else {
|
||
Add-Check -Key "return_completion" -Label "真实回收结果齐套" -Status "blocked" -Detail "completed_reviews=$completedReviewReturns; pending_reviews=$pendingReviewReturns; completed_ack=$completedAckReturns; pending_ack=$pendingAckReturns"
|
||
}
|
||
|
||
if ($blockerRows.Count -eq 0) {
|
||
Add-Check -Key "return_blockers" -Label "回收预检阻塞项" -Status "pass" -Detail "无回收预检阻塞项"
|
||
} else {
|
||
Add-Check -Key "return_blockers" -Label "回收预检阻塞项" -Status "blocked" -Detail "回收预检阻塞项=$($blockerRows.Count)"
|
||
}
|
||
|
||
$failedRequired = @($checks | Where-Object { $_.required -and $_.status -eq "fail" }).Count
|
||
$blockedRequired = @($checks | Where-Object { $_.required -and $_.status -eq "blocked" }).Count
|
||
$warnings = @($checks | Where-Object { $_.status -eq "warn" }).Count
|
||
|
||
$overallStatus = if ($failedRequired -gt 0) {
|
||
"FAIL"
|
||
} elseif ($blockedRequired -gt 0) {
|
||
"BLOCKED"
|
||
} elseif ($warnings -gt 0) {
|
||
"WARN"
|
||
} else {
|
||
"PASS"
|
||
}
|
||
|
||
$metrics = [ordered]@{
|
||
legal_return_rows = @($validationRows | Where-Object { $_.kind -eq "review_return" -and $_.reviewer_role -eq "法务" }).Count
|
||
business_return_rows = @($validationRows | Where-Object { $_.kind -eq "review_return" -and $_.reviewer_role -eq "业务" }).Count
|
||
ack_return_rows = @($validationRows | Where-Object { $_.kind -eq "ack_return" }).Count
|
||
validation_rows = $validationRows.Count
|
||
completed_review_returns = $completedReviewReturns
|
||
pending_review_returns = $pendingReviewReturns
|
||
completed_ack_returns = $completedAckReturns
|
||
pending_ack_returns = $pendingAckReturns
|
||
merge_ready_rows = $mergeReadyRows
|
||
blocker_count = $blockerRows.Count
|
||
}
|
||
|
||
$summary = [ordered]@{
|
||
name = "phase13-review-return-preflight"
|
||
status = $overallStatus
|
||
generated_at = (Get-Date).ToUniversalTime().ToString("o")
|
||
branch = $branch
|
||
commit = $commit
|
||
remote = $remoteUrl
|
||
return_root = $ReturnRoot
|
||
review_register_path = $ReviewRegisterPath
|
||
checks = $checks.ToArray()
|
||
metrics = $metrics
|
||
artifacts = [ordered]@{
|
||
summary_json = $summaryJson
|
||
summary_md = $summaryMd
|
||
source_manifest = $sourceManifestCsv
|
||
validation_board = $validationBoardCsv
|
||
blockers = $blockersCsv
|
||
diff_preview = $diffPreviewCsv
|
||
merge_plan = $mergePlanCsv
|
||
git_status = $gitStatusPath
|
||
}
|
||
totals = [ordered]@{
|
||
total_checks = $checks.Count
|
||
failed_required = $failedRequired
|
||
blocked_required = $blockedRequired
|
||
warnings = $warnings
|
||
}
|
||
}
|
||
Save-Json -Path $summaryJson -Value $summary
|
||
|
||
$md = New-Object System.Collections.Generic.List[string]
|
||
$md.Add("# 阶段 13 回收结果预检与合并计划门禁") | Out-Null
|
||
$md.Add("") | Out-Null
|
||
$md.Add("- 门禁状态:$(Convert-OverallStatusText -Status $overallStatus)($overallStatus)") | Out-Null
|
||
$md.Add("- 生成时间:$($summary.generated_at)") | Out-Null
|
||
$md.Add("- 分支:$branch") | Out-Null
|
||
$md.Add("- 提交:$commit") | Out-Null
|
||
$md.Add("- 回收目录:$ReturnRoot") | Out-Null
|
||
$md.Add("- 产物目录:$artifactDir") | Out-Null
|
||
$md.Add("") | Out-Null
|
||
$md.Add("## 统计") | Out-Null
|
||
$md.Add("") | Out-Null
|
||
$md.Add("| 指标 | 数量 |") | Out-Null
|
||
$md.Add("|---|---:|") | Out-Null
|
||
$md.Add("| 法务回收记录 | $($metrics.legal_return_rows) |") | Out-Null
|
||
$md.Add("| 业务回收记录 | $($metrics.business_return_rows) |") | Out-Null
|
||
$md.Add("| 签收回收记录 | $($metrics.ack_return_rows) |") | Out-Null
|
||
$md.Add("| 预检记录 | $($metrics.validation_rows) |") | Out-Null
|
||
$md.Add("| 已完成复核回收 | $($metrics.completed_review_returns) |") | Out-Null
|
||
$md.Add("| 待完成复核回收 | $($metrics.pending_review_returns) |") | Out-Null
|
||
$md.Add("| 待完成签收回收 | $($metrics.pending_ack_returns) |") | Out-Null
|
||
$md.Add("| 可进入 DryRun 回填 | $($metrics.merge_ready_rows) |") | Out-Null
|
||
$md.Add("| 阻塞项 | $($metrics.blocker_count) |") | Out-Null
|
||
$md.Add("") | Out-Null
|
||
$md.Add("## 检查项") | Out-Null
|
||
$md.Add("") | Out-Null
|
||
$md.Add("| 检查项 | 必需 | 状态 | 说明 |") | Out-Null
|
||
$md.Add("|---|---:|---|---|") | Out-Null
|
||
foreach ($check in $checks) {
|
||
$requiredText = if ($check.required) { "是" } else { "否" }
|
||
$md.Add("| $($check.label) | $requiredText | $(Convert-CheckStatusText -Status $check.status) | $($check.detail) |") | Out-Null
|
||
}
|
||
$md.Add("") | Out-Null
|
||
$md.Add("## 当前结论") | Out-Null
|
||
$md.Add("") | Out-Null
|
||
if ($overallStatus -eq "BLOCKED") {
|
||
$md.Add("回收预检和合并计划已生成,但当前回收模板仍为 PENDING,不能进入真实回填。") | Out-Null
|
||
} elseif ($overallStatus -eq "PASS") {
|
||
$md.Add("回收结果已通过预检,可进入 DryRun 回填校验。") | Out-Null
|
||
} else {
|
||
$md.Add("回收预检未完全通过,请查看阻塞项和合并计划。") | Out-Null
|
||
}
|
||
$md | Set-Content -Encoding UTF8 -Path $summaryMd
|
||
|
||
Write-Host "PHASE13_REVIEW_RETURN_PREFLIGHT_$overallStatus $artifactDir"
|
||
if ($overallStatus -eq "FAIL") {
|
||
throw "阶段 13 回收结果预检门禁未通过。查看:$summaryMd"
|
||
}
|
||
if ($overallStatus -eq "BLOCKED" -and $FailOnBlocked) {
|
||
throw "阶段 13 回收结果预检门禁被阻塞。查看:$summaryMd"
|
||
}
|