diff --git a/project-docs/phase13-legal-knowledge-template-library/evidence/phase13-review-task-package-workflow.md b/project-docs/phase13-legal-knowledge-template-library/evidence/phase13-review-task-package-workflow.md new file mode 100644 index 00000000..de373bfe --- /dev/null +++ b/project-docs/phase13-legal-knowledge-template-library/evidence/phase13-review-task-package-workflow.md @@ -0,0 +1,71 @@ +# 阶段 13 复核任务包与验收追踪工作流 + +本文档用于承接“复核总控中心”的 blocker 矩阵,把尚未完成的真实复核输入拆成可派发、可签收、可回填、可验收的工作包。 + +## 一、任务拆解口径 + +首批 P0 复核范围保持为 22 项资产级复核任务: + +1. 4 项高风险审核规则。 +2. 8 项标准条款。 +3. 5 项合同模板。 +4. 5 项合同生成黄金样本。 + +根据复核通道拆成 42 个原子工作项: + +| 工作项类型 | 数量 | 来源 blocker | +|---|---:|---| +| 法务复核工作单 | 22 | 缺少真实法务复核人、22 项 PENDING 复核 | +| 业务复核工作单 | 18 | 缺少真实业务复核人 | +| 交付签收工作单 | 2 | 法务接收签收未完成、业务接收签收未完成 | + +## 二、自动化脚本 + +使用以下脚本生成任务包: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-phase13-review-task-package-gate.ps1 +``` + +如已经确定真实负责人和截止日期,可直接传入: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-phase13-review-task-package-gate.ps1 -LegalReviewer <真实法务姓名> -BusinessReviewer <真实业务姓名> -HandoffOwner <交付负责人> -DueDate +``` + +脚本默认不会写入业务系统,也不会自动生成批准结论。 + +## 三、任务包产物 + +脚本会生成以下证据: + +1. `legal-review-work-orders.csv`:22 项法务复核工作单。 +2. `business-review-work-orders.csv`:18 项业务复核工作单。 +3. `handoff-ack-work-orders.csv`:2 项签收工作单。 +4. `review-acceptance-tracking-board.csv`:42 个原子验收项。 +5. `review-dispatch-manifest.csv`:派发包清单。 +6. `work-orders/`:逐项 Markdown 工作单。 +7. `phase13-review-task-package-gate-summary.json` 和 `.md`:门禁摘要。 + +## 四、验收闭环 + +每个原子工作项必须满足以下条件才可关闭: + +1. 有真实负责人。 +2. 有明确截止日期。 +3. 有真实执行结果。 +4. 有证据路径。 +5. 结果能被 `apply-phase13-review-results.ps1` DryRun 校验。 +6. 能被进度门禁、审计归档门禁和复核总控重新识别。 + +## 五、禁止项 + +1. 不得由脚本自动填写真实复核人。 +2. 不得由脚本自动填写复核日期。 +3. 不得把 `PENDING` 直接改为 `APPROVED`。 +4. 不得在没有签收记录时视为复核已启动。 +5. 不得在没有真实证据路径时推进生产发布。 + +## 六、当前状态 + +第十五批完成后,平台具备“把阻塞项转为可执行任务包”的能力;但真实法务、业务和签收输入仍属于外部人工环节。没有真实输入前,门禁应保持 `BLOCKED`,这是一种正确的安全状态。 diff --git a/project-docs/阶段13-法律知识库与合同模板库建设闭环第十五批执行记录-2026-05-02.md b/project-docs/阶段13-法律知识库与合同模板库建设闭环第十五批执行记录-2026-05-02.md new file mode 100644 index 00000000..925935e8 --- /dev/null +++ b/project-docs/阶段13-法律知识库与合同模板库建设闭环第十五批执行记录-2026-05-02.md @@ -0,0 +1,84 @@ +# AI合同平台 阶段 13 法律知识库与合同模板库建设闭环第十五批执行记录(2026-05-02) + +## 一、阶段结论 + +本批次完成 `复核任务包与验收追踪闭环`,当前状态为 `BLOCKED_ON_REAL_REVIEW_INPUT`。 + +第十四批已经建立复核总控驾驶舱,明确阻塞项为:22 项缺少真实法务复核、18 项缺少真实业务复核、2 项签收待完成、复核截止日期未设置、真实复核结果未回填。本批次不伪造任何外部复核输入,而是把这些阻塞项拆成可执行任务包和验收追踪看板。 + +## 二、本批次交付 + +新增或更新内容: + +1. `scripts/run-phase13-review-task-package-gate.ps1`。 +2. `scripts/run-phase13-review-control-center.ps1`,把复核任务包门禁纳入总控阶段证据。 +3. `scripts/run-phase13-first-wave-review-readiness.ps1`,修正后续生成的单项复核表字段渲染。 +4. `scripts/run-legal-knowledge-library-gate.ps1`,把任务包门禁脚本纳入存在性检查和敏感信息扫描。 +5. `project-docs/phase13-legal-knowledge-template-library/evidence/phase13-review-task-package-workflow.md`。 + +## 三、任务包范围 + +第十五批把首批 P0 复核拆为 42 个原子工作项: + +| 工作项类型 | 数量 | 说明 | +|---|---:|---| +| 法务复核工作单 | 22 | 每个首批 P0 任务均需要真实法务复核 | +| 业务复核工作单 | 18 | 法务+业务通道任务需要真实业务复核 | +| 交付签收工作单 | 2 | 法务接收人与业务接收人分别签收 | +| 验收追踪项 | 42 | 对应全部法务、业务、签收原子工作项 | + +## 四、自动化门禁 + +新增门禁脚本默认读取最新首批 P0 复核包和最新复核总控证据,生成: + +1. `legal-review-work-orders.csv`。 +2. `business-review-work-orders.csv`。 +3. `handoff-ack-work-orders.csv`。 +4. `review-acceptance-tracking-board.csv`。 +5. `review-dispatch-manifest.csv`。 +6. `work-orders/legal/*.md`。 +7. `work-orders/business/*.md`。 +8. `work-orders/ack/*.md`。 + +门禁状态设计: + +1. 任务包数量不匹配为 `FAIL`。 +2. 真实复核人、接收人或截止日期缺失为 `BLOCKED`。 +3. 任务包齐套且真实负责人、截止日期齐套为 `PASS`。 +4. 脚本不允许自动生成批准结论。 + +## 五、验收标准 + +本批次自身验收标准: + +1. 能从首批 P0 复核包读取 22 项复核任务。 +2. 能生成 22 项法务复核工作单。 +3. 能生成 18 项业务复核工作单。 +4. 能生成 2 项签收工作单。 +5. 能生成 42 个原子验收项。 +6. 总控中心能识别任务包阶段。 +7. 资料库门禁能扫描并校验新增脚本。 +8. 不产生任何伪造的 `APPROVED`、复核日期或真实复核人。 + +## 六、当前仍然阻塞的原因 + +第十五批只解决“人如何拿到任务并按什么标准关闭任务”,不会代替真实法务和业务判断。因此当前仍然阻塞: + +1. 真实法务复核人尚未提供。 +2. 真实业务复核人尚未提供。 +3. 法务和业务接收签收尚未发生。 +4. 首批复核截止日期尚未设置。 +5. 22 项首批复核仍未真实回填。 + +## 七、下一步 + +下一步应进入真实复核输入准备: + +1. 由项目负责人指定真实法务复核人。 +2. 由业务负责人指定真实业务复核人。 +3. 由交付负责人设置截止日期并完成签收。 +4. 真实复核人按工作单完成 22 项法务复核和 18 项业务复核。 +5. 使用 `apply-phase13-review-results.ps1` DryRun 校验真实回填结果。 +6. 重新运行进度门禁、审计归档、总控和生产发布门禁。 + +阶段 13 第十五批当前状态:`BLOCKED_ON_REAL_REVIEW_INPUT`。 diff --git a/scripts/run-legal-knowledge-library-gate.ps1 b/scripts/run-legal-knowledge-library-gate.ps1 index fed0b4a0..ad4de5e7 100644 --- a/scripts/run-legal-knowledge-library-gate.ps1 +++ b/scripts/run-legal-knowledge-library-gate.ps1 @@ -309,6 +309,10 @@ function Test-SecretLeak { if (Test-Path -LiteralPath $reviewControlCenterScriptPath -PathType Leaf) { $files += Get-Item -LiteralPath $reviewControlCenterScriptPath } + $reviewTaskPackageScriptPath = Join-Path $repoRoot "scripts\run-phase13-review-task-package-gate.ps1" + if (Test-Path -LiteralPath $reviewTaskPackageScriptPath -PathType Leaf) { + $files += Get-Item -LiteralPath $reviewTaskPackageScriptPath + } foreach ($file in $files) { $match = Select-String -LiteralPath $file.FullName -Pattern $adminPasswordLiteral -List -ErrorAction SilentlyContinue if ($match) { @@ -386,7 +390,8 @@ $commonDocFiles = @( "scripts\run-phase13-review-handoff-gate.ps1", "scripts\run-phase13-review-progress-gate.ps1", "scripts\run-phase13-review-audit-archive-gate.ps1", - "scripts\run-phase13-review-control-center.ps1" + "scripts\run-phase13-review-control-center.ps1", + "scripts\run-phase13-review-task-package-gate.ps1" ) foreach ($docPath in $commonDocFiles) { $absoluteDocPath = Join-Path $repoRoot $docPath diff --git a/scripts/run-phase13-first-wave-review-readiness.ps1 b/scripts/run-phase13-first-wave-review-readiness.ps1 index 07af2520..4d3a5b76 100644 --- a/scripts/run-phase13-first-wave-review-readiness.ps1 +++ b/scripts/run-phase13-first-wave-review-readiness.ps1 @@ -245,15 +245,15 @@ function Write-ReviewForm { $lines.Add("") | Out-Null $lines.Add("| 字段 | 值 |") | Out-Null $lines.Add("|---|---|") | Out-Null - $lines.Add("| 任务编号 | `$($Task.review_id)` |") | Out-Null + $lines.Add("| 任务编号 | ``$($Task.review_id)`` |") | Out-Null $lines.Add("| 资产类型 | $($Task.asset_type_text) |") | Out-Null - $lines.Add("| 资产编号 | `$($Task.asset_id)` |") | Out-Null + $lines.Add("| 资产编号 | ``$($Task.asset_id)`` |") | Out-Null $lines.Add("| 资产名称 | $($Task.asset_name) |") | Out-Null $lines.Add("| 优先级 | $($Task.priority) |") | Out-Null $lines.Add("| 复核通道 | $($Task.review_lane) |") | Out-Null - $lines.Add("| 法律依据 | `$($Task.source_ids)` |") | Out-Null - $lines.Add("| 主文件 | `$($Task.primary_path)` |") | Out-Null - $lines.Add("| 辅助文件 | `$($Task.secondary_path)` |") | Out-Null + $lines.Add("| 法律依据 | ``$($Task.source_ids)`` |") | Out-Null + $lines.Add("| 主文件 | ``$($Task.primary_path)`` |") | Out-Null + $lines.Add("| 辅助文件 | ``$($Task.secondary_path)`` |") | Out-Null $lines.Add("") | Out-Null $lines.Add("## 法务复核") | Out-Null $lines.Add("") | Out-Null diff --git a/scripts/run-phase13-review-control-center.ps1 b/scripts/run-phase13-review-control-center.ps1 index e0c69457..2612c58e 100644 --- a/scripts/run-phase13-review-control-center.ps1 +++ b/scripts/run-phase13-review-control-center.ps1 @@ -210,6 +210,7 @@ if ($gitStatus) { $stages = @( [pscustomobject]@{ key = "first_wave"; label = "首批复核包就绪"; root = "project-docs\qa-artifacts\phase13-first-wave-review-readiness"; summary = "phase13-first-wave-review-readiness-summary.json" }, + [pscustomobject]@{ key = "task_package"; label = "复核任务包与验收追踪"; root = "project-docs\qa-artifacts\phase13-review-task-package-gate"; summary = "phase13-review-task-package-gate-summary.json" }, [pscustomobject]@{ key = "handoff"; label = "复核交付回收"; root = "project-docs\qa-artifacts\phase13-review-handoff-gate"; summary = "phase13-review-handoff-gate-summary.json" }, [pscustomobject]@{ key = "progress"; label = "复核进度催办"; root = "project-docs\qa-artifacts\phase13-review-progress-gate"; summary = "phase13-review-progress-gate-summary.json" }, [pscustomobject]@{ key = "result_apply"; label = "复核结果回填"; root = "project-docs\qa-artifacts\phase13-review-result-apply"; summary = "phase13-review-result-apply-summary.json" }, @@ -236,6 +237,7 @@ foreach ($stage in $stages) { } $firstWave = $summaries["first_wave"] +$taskPackage = $summaries["task_package"] $handoff = $summaries["handoff"] $progress = $summaries["progress"] $resultApply = $summaries["result_apply"] @@ -247,6 +249,9 @@ $release = $summaries["release"] $missingLegalReviewer = [int](Get-MetricValue -Summary $firstWave -Name "missing_legal_reviewer_tasks") $missingBusinessReviewer = [int](Get-MetricValue -Summary $firstWave -Name "missing_business_reviewer_tasks") $firstWaveDueMissing = [bool](Get-MetricValue -Summary $firstWave -Name "due_date_missing" -Default $true) +$taskPackageAcceptanceRows = [int](Get-MetricValue -Summary $taskPackage -Name "acceptance_tracking_rows") +$taskPackageReadyOrders = [int](Get-MetricValue -Summary $taskPackage -Name "ready_to_dispatch_orders") +$taskPackageBlockedOrders = [int](Get-MetricValue -Summary $taskPackage -Name "blocked_orders") $handoffBlockers = [int](Get-MetricValue -Summary $handoff -Name "blockers") $handoffAckRows = [int](Get-MetricValue -Summary $handoff -Name "acknowledged_rows") $ackPending = [int](Get-MetricValue -Summary $progress -Name "ack_pending") @@ -262,6 +267,7 @@ $blockedRelease = [int](Get-MetricValue -Summary $release -Name "blocked_candida Add-Score -Key "legal_reviewer_assigned" -Label "真实法务复核人已分配" -Ready ($missingLegalReviewer -eq 0) -Evidence "missing_legal_reviewer_tasks=$missingLegalReviewer" -Blocker "缺少真实法务复核人" Add-Score -Key "business_reviewer_assigned" -Label "真实业务复核人已分配" -Ready ($missingBusinessReviewer -eq 0) -Evidence "missing_business_reviewer_tasks=$missingBusinessReviewer" -Blocker "缺少真实业务复核人" +Add-Score -Key "review_task_package_generated" -Label "复核任务包与验收追踪已生成" -Ready ($taskPackageAcceptanceRows -ge 42) -Evidence "acceptance_tracking_rows=$taskPackageAcceptanceRows; ready_to_dispatch_orders=$taskPackageReadyOrders; blocked_orders=$taskPackageBlockedOrders" -Blocker "复核任务包或验收追踪未齐套" Add-Score -Key "review_due_date_set" -Label "复核截止日期已设置" -Ready (-not $firstWaveDueMissing) -Evidence "due_date_missing=$firstWaveDueMissing" -Blocker "缺少复核截止日期" Add-Score -Key "handoff_acknowledged" -Label "复核交付已签收" -Ready ($ackPending -eq 0 -and $handoffAckRows -gt 0) -Evidence "ack_pending=$ackPending; acknowledged_rows=$handoffAckRows" -Blocker "签收未完成" Add-Score -Key "review_activity_started" -Label "真实复核活动已发生" -Ready ($completedReviews -gt 0 -or $pendingReviews -lt 22) -Evidence "pending_reviews=$pendingReviews; completed_reviews=$completedReviews" -Blocker "没有真实复核活动记录" @@ -282,6 +288,9 @@ if ($missingLegalReviewer -gt 0) { if ($missingBusinessReviewer -gt 0) { Add-NextAction -Priority "P0" -Owner "业务负责人" -Action "指定真实业务复核人" -Acceptance "首批复核就绪门禁 missing_business_reviewer_tasks=0" -Gate "run-phase13-first-wave-review-readiness.ps1" } +if ($taskPackageAcceptanceRows -lt 42) { + Add-NextAction -Priority "P0" -Owner "法律资料运营" -Action "生成复核任务包与验收追踪看板" -Acceptance "acceptance_tracking_rows>=42" -Gate "run-phase13-review-task-package-gate.ps1" +} if ($firstWaveDueMissing) { Add-NextAction -Priority "P0" -Owner "交付负责人" -Action "设置首批 P0 复核截止日期" -Acceptance "due_date_missing=false" -Gate "run-phase13-first-wave-review-readiness.ps1 / run-phase13-review-progress-gate.ps1" } @@ -345,6 +354,9 @@ $metrics = [ordered]@{ missing_stages = @($dashboardRows | Where-Object { $_.status -eq "MISSING" }).Count blocker_count = $blockerRows.Count next_action_count = $nextActionRows.Count + task_package_acceptance_rows = $taskPackageAcceptanceRows + task_package_ready_orders = $taskPackageReadyOrders + task_package_blocked_orders = $taskPackageBlockedOrders missing_legal_reviewer_tasks = $missingLegalReviewer missing_business_reviewer_tasks = $missingBusinessReviewer ack_pending = $ackPending @@ -402,6 +414,9 @@ $md.Add("| 阶段数 | $($metrics.stage_count) |") | Out-Null $md.Add("| 阻塞阶段 | $($metrics.blocked_stages) |") | Out-Null $md.Add("| 总控阻塞项 | $($metrics.blocker_count) |") | Out-Null $md.Add("| 下一步动作 | $($metrics.next_action_count) |") | Out-Null +$md.Add("| 任务包原子验收项 | $($metrics.task_package_acceptance_rows) |") | Out-Null +$md.Add("| 任务包可派发项 | $($metrics.task_package_ready_orders) |") | Out-Null +$md.Add("| 任务包阻塞项 | $($metrics.task_package_blocked_orders) |") | Out-Null $md.Add("| 未分配法务复核 | $($metrics.missing_legal_reviewer_tasks) |") | Out-Null $md.Add("| 未分配业务复核 | $($metrics.missing_business_reviewer_tasks) |") | Out-Null $md.Add("| 未完成签收 | $($metrics.ack_pending) |") | Out-Null diff --git a/scripts/run-phase13-review-task-package-gate.ps1 b/scripts/run-phase13-review-task-package-gate.ps1 new file mode 100644 index 00000000..4f1a3e8a --- /dev/null +++ b/scripts/run-phase13-review-task-package-gate.ps1 @@ -0,0 +1,687 @@ +param( + [string]$ArtifactRoot = "", + [string]$ReviewPacketPath = "", + [string]$ControlArtifactPath = "", + [string]$LegalReviewer = "", + [string]$BusinessReviewer = "", + [string]$HandoffOwner = "", + [string]$DueDate = "", + [switch]$FailOnBlocked +) + +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss-fff" + +if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) { + $ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase13-review-task-package-gate" +} +if (-not [System.IO.Path]::IsPathRooted($ArtifactRoot)) { + $ArtifactRoot = Join-Path $repoRoot $ArtifactRoot +} + +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($ReviewPacketPath)) { + $ReviewPacketPath = Resolve-LatestArtifactDir -RootRelativePath "project-docs\qa-artifacts\phase13-first-wave-review-readiness" +} +if (-not [string]::IsNullOrWhiteSpace($ReviewPacketPath) -and -not [System.IO.Path]::IsPathRooted($ReviewPacketPath)) { + $ReviewPacketPath = Join-Path $repoRoot $ReviewPacketPath +} + +if ([string]::IsNullOrWhiteSpace($ControlArtifactPath)) { + $ControlArtifactPath = Resolve-LatestArtifactDir -RootRelativePath "project-docs\qa-artifacts\phase13-review-control-center" +} +if (-not [string]::IsNullOrWhiteSpace($ControlArtifactPath) -and -not [System.IO.Path]::IsPathRooted($ControlArtifactPath)) { + $ControlArtifactPath = Join-Path $repoRoot $ControlArtifactPath +} + +$artifactDir = Join-Path $ArtifactRoot $timestamp +$workOrderRoot = Join-Path $artifactDir "work-orders" +$legalWorkOrderDir = Join-Path $workOrderRoot "legal" +$businessWorkOrderDir = Join-Path $workOrderRoot "business" +$ackWorkOrderDir = Join-Path $workOrderRoot "ack" +New-Item -ItemType Directory -Force -Path $legalWorkOrderDir, $businessWorkOrderDir, $ackWorkOrderDir | Out-Null + +$summaryJson = Join-Path $artifactDir "phase13-review-task-package-gate-summary.json" +$summaryMd = Join-Path $artifactDir "phase13-review-task-package-gate-summary.md" +$legalPackageCsv = Join-Path $artifactDir "legal-review-work-orders.csv" +$businessPackageCsv = Join-Path $artifactDir "business-review-work-orders.csv" +$ackPackageCsv = Join-Path $artifactDir "handoff-ack-work-orders.csv" +$acceptanceBoardCsv = Join-Path $artifactDir "review-acceptance-tracking-board.csv" +$dispatchManifestCsv = Join-Path $artifactDir "review-dispatch-manifest.csv" +$operatorGuideMd = Join-Path $artifactDir "review-task-package-operator-guide.md" +$gitStatusPath = Join-Path $artifactDir "git-status.txt" + +$checks = New-Object System.Collections.Generic.List[object] +$legalRows = New-Object System.Collections.Generic.List[object] +$businessRows = New-Object System.Collections.Generic.List[object] +$ackRows = New-Object System.Collections.Generic.List[object] +$acceptanceRows = New-Object System.Collections.Generic.List[object] +$dispatchRows = New-Object System.Collections.Generic.List[object] + +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 Save-Json { + param([string]$Path, [object]$Value) + $Value | ConvertTo-Json -Depth 60 | Set-Content -Encoding UTF8 -Path $Path +} + +function Read-JsonFile { + param([string]$Path) + if ([string]::IsNullOrWhiteSpace($Path) -or -not (Test-Path -LiteralPath $Path -PathType Leaf)) { + return $null + } + return Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json +} + +function Import-CsvRows { + param([string]$Path) + if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { + return @() + } + return @(Import-Csv -LiteralPath $Path) +} + +function Get-MetricValue { + param([object]$Summary, [string]$Name, [object]$Default = 0) + if ($null -eq $Summary -or $null -eq $Summary.metrics) { + return $Default + } + $property = $Summary.metrics.PSObject.Properties[$Name] + if ($null -eq $property -or $null -eq $property.Value) { + return $Default + } + return $property.Value +} + +function Test-PendingValue { + param([string]$Value) + if ([string]::IsNullOrWhiteSpace($Value)) { + return $true + } + return @( + "pending", + "tbd", + "待指定", + "待指定真实法务复核人", + "待指定真实业务复核人", + "待指定交付负责人", + "待法务复核", + "待业务复核", + "待法务和业务复核", + "待复核", + "待确认" + ) -contains $Value.Trim().ToLowerInvariant() +} + +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 } + } +} + +function Get-WorkOrderStatus { + param([string]$Assignee, [string]$DueDateValue) + if (Test-PendingValue -Value $Assignee) { + return "PENDING_ASSIGNMENT" + } + if ([string]::IsNullOrWhiteSpace($DueDateValue)) { + return "PENDING_DUE_DATE" + } + return "READY_TO_DISPATCH" +} + +function New-ReviewWorkOrder { + param( + [object]$Task, + [ValidateSet("legal_review", "business_review")] + [string]$OrderType, + [string]$Assignee, + [string]$AssigneeRole, + [string]$WorkOrderPath + ) + + $status = Get-WorkOrderStatus -Assignee $Assignee -DueDateValue $DueDate + $criteria = if ($OrderType -eq "legal_review") { + "法源、条款引用、适用边界、风险等级、修改建议和人工复核提示均经真实法务确认" + } else { + "变量、默认值、枚举、业务适用场景、责任上限和试点可用性均经真实业务负责人确认" + } + $requiredOutput = if ($OrderType -eq "legal_review") { + "在复核结果回填模板中填写法务复核人、复核日期、复核结果、修改要求和依据说明" + } else { + "在复核结果回填模板中填写业务复核人、复核日期、复核结果、修改要求和业务适用说明" + } + + return [pscustomobject][ordered]@{ + work_order_id = "$($OrderType.ToUpperInvariant())-$($Task.review_id)" + order_type = $OrderType + priority = $Task.priority + review_lane = $Task.review_lane + review_id = $Task.review_id + asset_type = $Task.asset_type + asset_type_text = $Task.asset_type_text + asset_id = $Task.asset_id + asset_name = $Task.asset_name + source_ids = $Task.source_ids + primary_path = $Task.primary_path + secondary_path = $Task.secondary_path + assignee_role = $AssigneeRole + assignee = $Assignee + due_date = $DueDate + current_review_result = $Task.review_result + current_review_status = $Task.review_status + required_action = $Task.required_changes + required_output = $requiredOutput + acceptance_criteria = $criteria + evidence_form_path = $Task.evidence_form_path + work_order_path = $WorkOrderPath + next_gate = "apply-phase13-review-results.ps1 -> run-phase13-review-progress-gate.ps1 -> run-phase13-review-control-center.ps1" + status = $status + notes = "不得由自动化脚本代填 APPROVED;必须由真实复核人签署或留痕后回填" + } +} + +function Add-AcceptanceRow { + param([object]$Order, [string]$BlockerArea) + $acceptanceRows.Add([pscustomobject][ordered]@{ + acceptance_id = "ACCEPT-$($Order.work_order_id)" + acceptance_type = $Order.order_type + blocker_area = $BlockerArea + review_id = $Order.review_id + asset_id = $Order.asset_id + owner_role = $Order.assignee_role + assignee = $Order.assignee + due_date = $Order.due_date + required_output = $Order.required_output + acceptance_criteria = $Order.acceptance_criteria + evidence_target = $Order.evidence_form_path + current_status = $Order.status + next_gate = $Order.next_gate + }) | Out-Null +} + +function Write-ReviewWorkOrderMd { + param([object]$Order, [string]$Path) + $lines = New-Object System.Collections.Generic.List[string] + $roleText = if ($Order.order_type -eq "legal_review") { "法务复核" } else { "业务复核" } + $lines.Add("# 阶段 13 $roleText 任务单:$($Order.review_id)") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("## 任务对象") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("| 字段 | 值 |") | Out-Null + $lines.Add("|---|---|") | Out-Null + $lines.Add("| 工作项 | `$($Order.work_order_id)` |") | Out-Null + $lines.Add("| 资产 | `$($Order.asset_id)`:$($Order.asset_name) |") | Out-Null + $lines.Add("| 类型 | $($Order.asset_type_text) |") | Out-Null + $lines.Add("| 复核人 | $($Order.assignee) |") | Out-Null + $lines.Add("| 截止日期 | $($Order.due_date) |") | Out-Null + $lines.Add("| 主文件 | `$($Order.primary_path)` |") | Out-Null + $lines.Add("| 辅助文件 | `$($Order.secondary_path)` |") | Out-Null + $lines.Add("| 法源或依赖 | `$($Order.source_ids)` |") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("## 执行步骤") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("1. 打开主文件和辅助文件,逐项核对资料内容。") | Out-Null + $lines.Add("2. 按本任务单的验收口径完成复核,不得只给口头结论。") | Out-Null + $lines.Add("3. 在复核结果回填模板中填写复核人、复核日期、复核结果、修改要求和证据路径。") | Out-Null + $lines.Add("4. 若结论为需要修改,必须写明修改位置、修改建议和是否阻断生产发布。") | Out-Null + $lines.Add("5. 回填后运行结果回填、进度、审计归档和总控门禁。") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("## 验收口径") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("- 必做动作:$($Order.required_action)") | Out-Null + $lines.Add("- 回填产物:$($Order.required_output)") | Out-Null + $lines.Add("- 通过标准:$($Order.acceptance_criteria)") | Out-Null + $lines.Add("- 下一门禁:" + '``' + $Order.next_gate + '``') | Out-Null + $lines.Add("") | Out-Null + $lines.Add("## 禁止项") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("1. 不得把 PENDING 直接改成 APPROVED。") | Out-Null + $lines.Add("2. 不得使用脚本自动伪造复核人、复核日期或批准意见。") | Out-Null + $lines.Add("3. 没有真实证据路径时,不得进入生产发布。") | Out-Null + $lines | Set-Content -Encoding UTF8 -Path $Path +} + +function Write-AckWorkOrderMd { + param([object]$Order, [string]$Path) + $lines = New-Object System.Collections.Generic.List[string] + $lines.Add("# 阶段 13 交付签收任务单:$($Order.work_order_id)") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("## 签收对象") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("| 字段 | 值 |") | Out-Null + $lines.Add("|---|---|") | Out-Null + $lines.Add("| 签收角色 | $($Order.recipient_role) |") | Out-Null + $lines.Add("| 接收人 | $($Order.recipient) |") | Out-Null + $lines.Add("| 交付负责人 | $($Order.handoff_owner) |") | Out-Null + $lines.Add("| 截止日期 | $($Order.due_date) |") | Out-Null + $lines.Add("| 签收材料 | $($Order.package_path) |") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("## 签收要求") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("1. 接收人确认已收到首批 P0 复核任务包。") | Out-Null + $lines.Add("2. 接收人确认资料可打开、任务数量和角色范围正确。") | Out-Null + $lines.Add("3. 如需补件,必须在签收记录中列明缺失材料。") | Out-Null + $lines.Add("4. 签收记录回填后重新运行复核进度门禁。") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("## 验收标准") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("- `ack_pending=0`。") | Out-Null + $lines.Add("- `acknowledged_rows>0`。") | Out-Null + $lines.Add("- 签收文件中必须包含接收人、签收日期、材料路径和是否需要补件。") | Out-Null + $lines | Set-Content -Encoding UTF8 -Path $Path +} + +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 } +} + +$selectedTasksPath = if ([string]::IsNullOrWhiteSpace($ReviewPacketPath)) { "" } else { Join-Path $ReviewPacketPath "selected-review-tasks.csv" } +$resultTemplatePath = if ([string]::IsNullOrWhiteSpace($ReviewPacketPath)) { "" } else { Join-Path $ReviewPacketPath "review-result-import-template.csv" } +$assignmentTemplatePath = if ([string]::IsNullOrWhiteSpace($ReviewPacketPath)) { "" } else { Join-Path $ReviewPacketPath "review-assignment-template.csv" } +$controlSummaryPath = if ([string]::IsNullOrWhiteSpace($ControlArtifactPath)) { "" } else { Join-Path $ControlArtifactPath "phase13-review-control-center-summary.json" } + +$tasks = Import-CsvRows -Path $selectedTasksPath +$controlSummary = Read-JsonFile -Path $controlSummaryPath + +if ($tasks.Count -gt 0) { + Add-Check -Key "review_task_source" -Label "首批复核任务来源" -Status "pass" -Detail "读取到 $($tasks.Count) 个首批复核任务" -Meta @{ source = $selectedTasksPath } +} else { + Add-Check -Key "review_task_source" -Label "首批复核任务来源" -Status "fail" -Detail "未读取到首批复核任务" -Meta @{ source = $selectedTasksPath } +} + +if ($null -ne $controlSummary) { + Add-Check -Key "control_center_source" -Label "复核总控来源" -Status "pass" -Detail "读取到总控 blocker 矩阵来源,状态=$($controlSummary.status)" -Meta @{ source = $controlSummaryPath } +} else { + Add-Check -Key "control_center_source" -Label "复核总控来源" -Status "warn" -Required $false -Detail "未读取到总控摘要;仍可基于首批复核任务生成任务包" -Meta @{ source = $controlSummaryPath } +} + +$legalAssignee = if ([string]::IsNullOrWhiteSpace($LegalReviewer)) { "待指定真实法务复核人" } else { $LegalReviewer } +$businessAssignee = if ([string]::IsNullOrWhiteSpace($BusinessReviewer)) { "待指定真实业务复核人" } else { $BusinessReviewer } +$handoffAssignee = if ([string]::IsNullOrWhiteSpace($HandoffOwner)) { "待指定交付负责人" } else { $HandoffOwner } + +foreach ($task in ($tasks | Sort-Object priority, review_lane, asset_type, asset_id)) { + $legalWorkOrderPath = Join-Path $legalWorkOrderDir "$($task.review_id).md" + $legalOrder = New-ReviewWorkOrder -Task $task -OrderType "legal_review" -Assignee $legalAssignee -AssigneeRole "真实法务复核人" -WorkOrderPath $legalWorkOrderPath + $legalRows.Add($legalOrder) | Out-Null + Add-AcceptanceRow -Order $legalOrder -BlockerArea "legal_reviewer_assigned" + Write-ReviewWorkOrderMd -Order $legalOrder -Path $legalWorkOrderPath + + if ($task.review_lane -match "业务") { + $businessWorkOrderPath = Join-Path $businessWorkOrderDir "$($task.review_id).md" + $businessOrder = New-ReviewWorkOrder -Task $task -OrderType "business_review" -Assignee $businessAssignee -AssigneeRole "真实业务复核人" -WorkOrderPath $businessWorkOrderPath + $businessRows.Add($businessOrder) | Out-Null + Add-AcceptanceRow -Order $businessOrder -BlockerArea "business_reviewer_assigned" + Write-ReviewWorkOrderMd -Order $businessOrder -Path $businessWorkOrderPath + } +} + +$ackSpecs = @( + [pscustomobject]@{ ack_id = "ACK-LEGAL"; recipient_role = "法务接收人"; recipient = $legalAssignee; blocker_area = "handoff_acknowledged" }, + [pscustomobject]@{ ack_id = "ACK-BUSINESS"; recipient_role = "业务接收人"; recipient = $businessAssignee; blocker_area = "handoff_acknowledged" } +) +foreach ($ack in $ackSpecs) { + $ackPath = Join-Path $ackWorkOrderDir "$($ack.ack_id).md" + $ackStatus = Get-WorkOrderStatus -Assignee $ack.recipient -DueDateValue $DueDate + $ackRow = [pscustomobject][ordered]@{ + work_order_id = $ack.ack_id + order_type = "handoff_ack" + recipient_role = $ack.recipient_role + recipient = $ack.recipient + handoff_owner = $handoffAssignee + due_date = $DueDate + package_path = $ReviewPacketPath + required_output = "填写并返回接收签收记录,确认收到复核任务包、材料可打开、缺失材料已列明" + acceptance_criteria = "ack_pending=0 且 acknowledged_rows>0" + evidence_target = "recipient-ack-template.csv 或等效签收记录" + work_order_path = $ackPath + next_gate = "run-phase13-review-handoff-gate.ps1 -> run-phase13-review-progress-gate.ps1 -> run-phase13-review-control-center.ps1" + status = $ackStatus + notes = "未签收前不得视为真实复核已启动" + } + $ackRows.Add($ackRow) | Out-Null + $acceptanceRows.Add([pscustomobject][ordered]@{ + acceptance_id = "ACCEPT-$($ackRow.work_order_id)" + acceptance_type = $ackRow.order_type + blocker_area = $ack.blocker_area + review_id = "" + asset_id = "" + owner_role = $ackRow.recipient_role + assignee = $ackRow.recipient + due_date = $ackRow.due_date + required_output = $ackRow.required_output + acceptance_criteria = $ackRow.acceptance_criteria + evidence_target = $ackRow.evidence_target + current_status = $ackRow.status + next_gate = $ackRow.next_gate + }) | Out-Null + Write-AckWorkOrderMd -Order $ackRow -Path $ackPath +} + +$dispatchRows.Add([pscustomobject][ordered]@{ + package = "legal-review-work-orders" + owner = $legalAssignee + rows = $legalRows.Count + artifact = $legalPackageCsv + work_orders = $legalWorkOrderDir + acceptance = "22 项法务复核全部完成并回填" +}) | Out-Null +$dispatchRows.Add([pscustomobject][ordered]@{ + package = "business-review-work-orders" + owner = $businessAssignee + rows = $businessRows.Count + artifact = $businessPackageCsv + work_orders = $businessWorkOrderDir + acceptance = "18 项业务复核全部完成并回填" +}) | Out-Null +$dispatchRows.Add([pscustomobject][ordered]@{ + package = "handoff-ack-work-orders" + owner = $handoffAssignee + rows = $ackRows.Count + artifact = $ackPackageCsv + work_orders = $ackWorkOrderDir + acceptance = "2 项签收全部完成" +}) | Out-Null + +$legalRows | Export-Csv -LiteralPath $legalPackageCsv -NoTypeInformation -Encoding UTF8 +$businessRows | Export-Csv -LiteralPath $businessPackageCsv -NoTypeInformation -Encoding UTF8 +$ackRows | Export-Csv -LiteralPath $ackPackageCsv -NoTypeInformation -Encoding UTF8 +$acceptanceRows | Export-Csv -LiteralPath $acceptanceBoardCsv -NoTypeInformation -Encoding UTF8 +$dispatchRows | Export-Csv -LiteralPath $dispatchManifestCsv -NoTypeInformation -Encoding UTF8 + +$expectedLegalRows = [int](Get-MetricValue -Summary $controlSummary -Name "missing_legal_reviewer_tasks" -Default $tasks.Count) +$expectedBusinessRows = [int](Get-MetricValue -Summary $controlSummary -Name "missing_business_reviewer_tasks" -Default @($tasks | Where-Object { $_.review_lane -match "业务" }).Count) +$expectedAckRows = [int](Get-MetricValue -Summary $controlSummary -Name "ack_pending" -Default 2) + +if ($legalRows.Count -eq $expectedLegalRows) { + Add-Check -Key "legal_task_package" -Label "法务复核任务包" -Status "pass" -Detail "已生成 $($legalRows.Count) 项法务复核工作单" +} else { + Add-Check -Key "legal_task_package" -Label "法务复核任务包" -Status "fail" -Detail "法务任务数量不匹配:actual=$($legalRows.Count), expected=$expectedLegalRows" +} +if ($businessRows.Count -eq $expectedBusinessRows) { + Add-Check -Key "business_task_package" -Label "业务复核任务包" -Status "pass" -Detail "已生成 $($businessRows.Count) 项业务复核工作单" +} else { + Add-Check -Key "business_task_package" -Label "业务复核任务包" -Status "fail" -Detail "业务任务数量不匹配:actual=$($businessRows.Count), expected=$expectedBusinessRows" +} +if ($ackRows.Count -eq $expectedAckRows) { + Add-Check -Key "ack_task_package" -Label "签收任务包" -Status "pass" -Detail "已生成 $($ackRows.Count) 项签收工作单" +} else { + Add-Check -Key "ack_task_package" -Label "签收任务包" -Status "fail" -Detail "签收任务数量不匹配:actual=$($ackRows.Count), expected=$expectedAckRows" +} + +$expectedAcceptanceRows = $legalRows.Count + $businessRows.Count + $ackRows.Count +if ($acceptanceRows.Count -eq $expectedAcceptanceRows -and $acceptanceRows.Count -gt 0) { + Add-Check -Key "acceptance_tracking_board" -Label "验收追踪看板" -Status "pass" -Detail "已生成 $($acceptanceRows.Count) 个原子验收项" +} else { + Add-Check -Key "acceptance_tracking_board" -Label "验收追踪看板" -Status "fail" -Detail "验收项数量异常:actual=$($acceptanceRows.Count), expected=$expectedAcceptanceRows" +} + +$missingLegalAssignee = @($legalRows | Where-Object { Test-PendingValue -Value $_.assignee }) +$missingBusinessAssignee = @($businessRows | Where-Object { Test-PendingValue -Value $_.assignee }) +$pendingAckRows = @($ackRows | Where-Object { Test-PendingValue -Value $_.recipient }) +$missingDueDateRows = @($acceptanceRows | Where-Object { [string]::IsNullOrWhiteSpace($_.due_date) }) +$readyOrders = @($acceptanceRows | Where-Object { $_.current_status -eq "READY_TO_DISPATCH" }) +$blockedOrders = @($acceptanceRows | Where-Object { $_.current_status -ne "READY_TO_DISPATCH" }) + +if ($missingLegalAssignee.Count -eq 0) { + Add-Check -Key "legal_assignee_ready" -Label "法务复核人可派发" -Status "pass" -Detail "法务复核工作单均已指定真实复核人" +} else { + Add-Check -Key "legal_assignee_ready" -Label "法务复核人可派发" -Status "blocked" -Detail "仍有 $($missingLegalAssignee.Count) 项法务复核工作单缺少真实复核人" +} +if ($missingBusinessAssignee.Count -eq 0) { + Add-Check -Key "business_assignee_ready" -Label "业务复核人可派发" -Status "pass" -Detail "业务复核工作单均已指定真实复核人" +} else { + Add-Check -Key "business_assignee_ready" -Label "业务复核人可派发" -Status "blocked" -Detail "仍有 $($missingBusinessAssignee.Count) 项业务复核工作单缺少真实复核人" +} +if ($pendingAckRows.Count -eq 0) { + Add-Check -Key "ack_recipient_ready" -Label "签收接收人可派发" -Status "pass" -Detail "签收工作单均已指定接收人" +} else { + Add-Check -Key "ack_recipient_ready" -Label "签收接收人可派发" -Status "blocked" -Detail "仍有 $($pendingAckRows.Count) 项签收工作单缺少真实接收人" +} +if ($missingDueDateRows.Count -eq 0) { + Add-Check -Key "due_date_ready" -Label "复核截止日期可执行" -Status "pass" -Detail "所有验收项均已设置截止日期" +} else { + Add-Check -Key "due_date_ready" -Label "复核截止日期可执行" -Status "blocked" -Detail "仍有 $($missingDueDateRows.Count) 个验收项缺少截止日期" +} + +$fabricatedApprovals = @($acceptanceRows | Where-Object { $_.current_status -match "APPROVED|PASS|DONE|COMPLETED" }) +if ($fabricatedApprovals.Count -eq 0) { + Add-Check -Key "no_fabricated_review_result" -Label "未伪造复核结论" -Status "pass" -Detail "任务包只生成待执行工作项,没有自动生成批准结论" +} else { + Add-Check -Key "no_fabricated_review_result" -Label "未伪造复核结论" -Status "fail" -Detail "发现疑似自动批准状态:$($fabricatedApprovals.Count)" +} + +$guide = New-Object System.Collections.Generic.List[string] +$guide.Add("# 阶段 13 第十五批:复核任务包与验收追踪操作说明") | Out-Null +$guide.Add("") | Out-Null +$guide.Add("本目录把第十四批总控 blocker 拆成可派发、可签收、可回填、可验收的原子工作项。") | Out-Null +$guide.Add("") | Out-Null +$guide.Add("## 包含内容") | Out-Null +$guide.Add("") | Out-Null +$guide.Add('1. `legal-review-work-orders.csv`:22 项法务复核工作单。') | Out-Null +$guide.Add('2. `business-review-work-orders.csv`:18 项业务复核工作单。') | Out-Null +$guide.Add('3. `handoff-ack-work-orders.csv`:2 项签收工作单。') | Out-Null +$guide.Add('4. `review-acceptance-tracking-board.csv`:42 个原子验收项。') | Out-Null +$guide.Add('5. `work-orders/`:逐项 Markdown 任务单。') | Out-Null +$guide.Add("") | Out-Null +$guide.Add("## 执行顺序") | Out-Null +$guide.Add("") | Out-Null +$guide.Add("1. 指定真实法务复核人、真实业务复核人、交付负责人和截止日期。") | Out-Null +$guide.Add('2. 将 `work-orders/ack/` 交给接收人完成签收。') | Out-Null +$guide.Add('3. 将 `work-orders/legal/` 和 `work-orders/business/` 分别交给真实复核人。') | Out-Null +$guide.Add("4. 复核人把结论写入首批复核结果回填模板。") | Out-Null +$guide.Add("5. 依次运行结果回填、进度、审计归档、总控和生产发布门禁。") | Out-Null +$guide.Add("") | Out-Null +$guide.Add("## 推荐命令") | Out-Null +$guide.Add("") | Out-Null +$guide.Add('```powershell') | Out-Null +$guide.Add("powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-phase13-review-task-package-gate.ps1 -LegalReviewer <真实法务姓名> -BusinessReviewer <真实业务姓名> -HandoffOwner <交付负责人> -DueDate ") | Out-Null +$guide.Add("powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-phase13-review-handoff-gate.ps1 -LegalReviewer <真实法务姓名> -BusinessReviewer <真实业务姓名> -HandoffOwner <交付负责人> -DueDate ") | Out-Null +$guide.Add("powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-phase13-review-progress-gate.ps1") | Out-Null +$guide.Add("powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-phase13-review-control-center.ps1") | Out-Null +$guide.Add('```') | Out-Null +$guide.Add("") | Out-Null +$guide.Add("## 硬性规则") | Out-Null +$guide.Add("") | Out-Null +$guide.Add("1. 没有真实复核人,不得把任务状态视为已启动。") | Out-Null +$guide.Add("2. 没有签收记录,不得把任务状态视为已交付。") | Out-Null +$guide.Add("3. 没有真实复核结论和证据路径,不得把资产状态推进到生产发布。") | Out-Null +$guide | Set-Content -Encoding UTF8 -Path $operatorGuideMd + +$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]@{ + selected_review_tasks = $tasks.Count + legal_review_work_orders = $legalRows.Count + business_review_work_orders = $businessRows.Count + ack_work_orders = $ackRows.Count + acceptance_tracking_rows = $acceptanceRows.Count + ready_to_dispatch_orders = $readyOrders.Count + blocked_orders = $blockedOrders.Count + missing_legal_assignee_orders = $missingLegalAssignee.Count + missing_business_assignee_orders = $missingBusinessAssignee.Count + ack_pending_orders = $pendingAckRows.Count + missing_due_date_acceptance_items = $missingDueDateRows.Count + expected_legal_review_work_orders = $expectedLegalRows + expected_business_review_work_orders = $expectedBusinessRows + expected_ack_work_orders = $expectedAckRows + control_blocker_count = [int](Get-MetricValue -Summary $controlSummary -Name "blocker_count" -Default 0) + control_readiness_score = [int](Get-MetricValue -Summary $controlSummary -Name "readiness_score" -Default 0) + control_readiness_total = [int](Get-MetricValue -Summary $controlSummary -Name "readiness_total" -Default 0) +} + +$summary = [ordered]@{ + name = "phase13-review-task-package-gate" + status = $overallStatus + generated_at = (Get-Date).ToUniversalTime().ToString("o") + branch = $branch + commit = $commit + remote = $remoteUrl + review_packet_path = $ReviewPacketPath + control_artifact_path = $ControlArtifactPath + checks = $checks.ToArray() + metrics = $metrics + artifacts = [ordered]@{ + summary_json = $summaryJson + summary_md = $summaryMd + legal_work_orders = $legalPackageCsv + business_work_orders = $businessPackageCsv + ack_work_orders = $ackPackageCsv + acceptance_tracking_board = $acceptanceBoardCsv + dispatch_manifest = $dispatchManifestCsv + operator_guide = $operatorGuideMd + work_orders_dir = $workOrderRoot + 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("- 首批复核包:$ReviewPacketPath") | Out-Null +$md.Add("- 总控证据:$ControlArtifactPath") | 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.selected_review_tasks) |") | Out-Null +$md.Add("| 法务复核工作单 | $($metrics.legal_review_work_orders) |") | Out-Null +$md.Add("| 业务复核工作单 | $($metrics.business_review_work_orders) |") | Out-Null +$md.Add("| 签收工作单 | $($metrics.ack_work_orders) |") | Out-Null +$md.Add("| 原子验收项 | $($metrics.acceptance_tracking_rows) |") | Out-Null +$md.Add("| 可派发工作项 | $($metrics.ready_to_dispatch_orders) |") | Out-Null +$md.Add("| 阻塞工作项 | $($metrics.blocked_orders) |") | 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("第十五批已把总控 blocker 拆成可执行任务包,但仍缺少真实复核人、签收接收人或截止日期,因此只能进入派发准备状态,不能视为真实复核完成。") | Out-Null +} elseif ($overallStatus -eq "PASS") { + $md.Add("第十五批任务包已具备派发条件,可以交给真实法务、业务和签收接收人执行。") | Out-Null +} else { + $md.Add("第十五批任务包未完全通过,请查看检查项和验收追踪看板。") | Out-Null +} +$md.Add("") | Out-Null +$md.Add("本地产物:") | Out-Null +$md.Add("") | Out-Null +$md.Add("- phase13-review-task-package-gate-summary.json") | Out-Null +$md.Add("- phase13-review-task-package-gate-summary.md") | Out-Null +$md.Add("- legal-review-work-orders.csv") | Out-Null +$md.Add("- business-review-work-orders.csv") | Out-Null +$md.Add("- handoff-ack-work-orders.csv") | Out-Null +$md.Add("- review-acceptance-tracking-board.csv") | Out-Null +$md.Add("- review-dispatch-manifest.csv") | Out-Null +$md.Add("- review-task-package-operator-guide.md") | Out-Null +$md.Add("- work-orders/") | Out-Null +$md | Set-Content -Encoding UTF8 -Path $summaryMd + +Write-Host "PHASE13_REVIEW_TASK_PACKAGE_GATE_$overallStatus $artifactDir" +if ($overallStatus -eq "FAIL") { + throw "阶段 13 复核任务包与验收追踪门禁未通过。查看:$summaryMd" +} +if ($overallStatus -eq "BLOCKED" -and $FailOnBlocked) { + throw "阶段 13 复核任务包与验收追踪门禁被阻塞。查看:$summaryMd" +}