From e20bb7650d6f9a16752ea3e30446301e3aebca55 Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 2 May 2026 10:48:16 +0800 Subject: [PATCH] docs: add phase 13 review ops pipeline --- .../phase13-review-ops-pipeline-workflow.md | 51 ++++ ...¸Ž合同模板库建设闭环第十九批执行记录-2026-05-02.md | 61 +++++ scripts/run-legal-knowledge-library-gate.ps1 | 5 + scripts/run-phase13-review-ops-pipeline.ps1 | 222 ++++++++++++++++++ 4 files changed, 339 insertions(+) create mode 100644 project-docs/phase13-legal-knowledge-template-library/evidence/phase13-review-ops-pipeline-workflow.md create mode 100644 project-docs/阶段13-法律知识库与合同模板库建设闭环第十九批执行记录-2026-05-02.md create mode 100644 scripts/run-phase13-review-ops-pipeline.ps1 diff --git a/project-docs/phase13-legal-knowledge-template-library/evidence/phase13-review-ops-pipeline-workflow.md b/project-docs/phase13-legal-knowledge-template-library/evidence/phase13-review-ops-pipeline-workflow.md new file mode 100644 index 00000000..a763652a --- /dev/null +++ b/project-docs/phase13-legal-knowledge-template-library/evidence/phase13-review-ops-pipeline-workflow.md @@ -0,0 +1,51 @@ +# 阶段 13 复核运营自动流水线工作流 + +本文档说明阶段 13 复核相关门禁的一键顺序执行方式。 + +## 一、目标 + +阶段 13 的复核链路包含多个依赖前置产物的门禁。并行执行会出现总控读取到半写入目录的竞态,因此需要一个顺序流水线入口。 + +## 二、自动化脚本 + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-phase13-review-ops-pipeline.ps1 +``` + +如已经准备好真实复核输入文件: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-phase13-review-ops-pipeline.ps1 -ReviewerIntakePath <正式输入文件> +``` + +## 三、执行顺序 + +1. 首批复核包就绪。 +2. 真实复核输入接入。 +3. 复核任务包与验收追踪。 +4. 复核派发包导出。 +5. 回收结果预检与合并计划。 +6. 复核交付回收。 +7. 复核进度催办。 +8. 复核结果回填 DryRun。 +9. 复核执行门禁。 +10. 复核审计归档。 +11. 资料库门禁。 +12. 生产试点发布。 +13. 复核总控驾驶舱。 + +## 四、输出产物 + +1. `phase13-review-ops-pipeline-summary.json`。 +2. `phase13-review-ops-pipeline-summary.md`。 +3. `review-ops-pipeline-steps.csv`。 +4. `review-ops-pipeline-output.log`。 +5. `git-status.txt`。 + +## 五、状态说明 + +1. 任一步骤失败为 `FAIL`。 +2. 任一步骤阻塞为 `BLOCKED`。 +3. 只有全部步骤通过才为 `PASS`。 + +当前没有真实复核输入时,流水线应完整跑完并保持 `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..722b0bc8 --- /dev/null +++ b/project-docs/阶段13-法律知识库与合同模板库建设闭环第十九批执行记录-2026-05-02.md @@ -0,0 +1,61 @@ +# AI合同平台 阶段 13 法律知识库与合同模板库建设闭环第十九批执行记录(2026-05-02) + +## 一、阶段结论 + +本批次完成 `复核运营自动流水线编排器`,当前状态为 `BLOCKED_ON_REAL_REVIEW_INPUT`。 + +第十六至第十八批已经补齐真实复核输入、派发包、回收预检和合并计划。执行过程中发现总控与资料库门禁并行运行时可能读到半写入目录。本批次新增顺序流水线入口,把阶段 13 复核链路按依赖顺序串行执行,避免竞态。 + +## 二、本批次交付 + +新增或更新内容: + +1. `scripts/run-phase13-review-ops-pipeline.ps1`。 +2. `scripts/run-legal-knowledge-library-gate.ps1`,纳入新增流水线脚本检查。 +3. `project-docs/phase13-legal-knowledge-template-library/evidence/phase13-review-ops-pipeline-workflow.md`。 + +## 三、流水线范围 + +流水线串行执行 13 个步骤: + +1. 首批复核包就绪。 +2. 真实复核输入接入。 +3. 复核任务包与验收追踪。 +4. 复核派发包导出。 +5. 回收结果预检与合并计划。 +6. 复核交付回收。 +7. 复核进度催办。 +8. 复核结果回填 DryRun。 +9. 复核执行门禁。 +10. 复核审计归档。 +11. 资料库门禁。 +12. 生产试点发布。 +13. 复核总控驾驶舱。 + +## 四、当前状态 + +流水线的目标不是伪造真实复核输入,而是保证自动化链路能稳定、顺序、可复跑地执行。当前缺少真实法务、真实业务、真实签收和真实回收结果,因此流水线应保持 `BLOCKED`。 + +## 五、自动化验证结果 + +```text +PHASE13_REVIEW_OPS_PIPELINE_BLOCKED +``` + +关键指标: + +| 指标 | 结果 | +|---|---:| +| 总步骤 | 13 | +| FAIL 步骤 | 0 | +| BLOCKED 步骤 | 13 | +| WARN 步骤 | 0 | +| PASS 步骤 | 0 | + +本次流水线无失败步骤,全部阻塞均来自真实复核输入、签收和回收结果尚未发生。 + +## 六、下一步 + +下一步可以继续自动推进“受保护回填与资产状态迁移预案”,也可以在真实复核输入到位后直接运行本流水线验证全链路。 + +阶段 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 a9b6267f..e73fd0cc 100644 --- a/scripts/run-legal-knowledge-library-gate.ps1 +++ b/scripts/run-legal-knowledge-library-gate.ps1 @@ -325,6 +325,10 @@ function Test-SecretLeak { if (Test-Path -LiteralPath $reviewReturnPreflightScriptPath -PathType Leaf) { $files += Get-Item -LiteralPath $reviewReturnPreflightScriptPath } + $reviewOpsPipelineScriptPath = Join-Path $repoRoot "scripts\run-phase13-review-ops-pipeline.ps1" + if (Test-Path -LiteralPath $reviewOpsPipelineScriptPath -PathType Leaf) { + $files += Get-Item -LiteralPath $reviewOpsPipelineScriptPath + } foreach ($file in $files) { $match = Select-String -LiteralPath $file.FullName -Pattern $adminPasswordLiteral -List -ErrorAction SilentlyContinue if ($match) { @@ -407,6 +411,7 @@ $commonDocFiles = @( "scripts\run-phase13-review-intake-gate.ps1", "scripts\export-phase13-reviewer-kit.ps1", "scripts\run-phase13-review-return-preflight.ps1", + "scripts\run-phase13-review-ops-pipeline.ps1", "project-docs\phase13-legal-knowledge-template-library\review-intake\reviewer-intake-template.csv", "project-docs\phase13-legal-knowledge-template-library\review-intake\README.md" ) diff --git a/scripts/run-phase13-review-ops-pipeline.ps1 b/scripts/run-phase13-review-ops-pipeline.ps1 new file mode 100644 index 00000000..f3d488d4 --- /dev/null +++ b/scripts/run-phase13-review-ops-pipeline.ps1 @@ -0,0 +1,222 @@ +param( + [string]$ArtifactRoot = "", + [string]$ReviewerIntakePath = "", + [switch]$FailOnFailedStep +) + +$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-ops-pipeline" +} +if (-not [System.IO.Path]::IsPathRooted($ArtifactRoot)) { + $ArtifactRoot = Join-Path $repoRoot $ArtifactRoot +} +if (-not [string]::IsNullOrWhiteSpace($ReviewerIntakePath) -and -not [System.IO.Path]::IsPathRooted($ReviewerIntakePath)) { + $ReviewerIntakePath = Join-Path $repoRoot $ReviewerIntakePath +} + +$artifactDir = Join-Path $ArtifactRoot $timestamp +New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null + +$summaryJson = Join-Path $artifactDir "phase13-review-ops-pipeline-summary.json" +$summaryMd = Join-Path $artifactDir "phase13-review-ops-pipeline-summary.md" +$stepsCsv = Join-Path $artifactDir "review-ops-pipeline-steps.csv" +$outputLog = Join-Path $artifactDir "review-ops-pipeline-output.log" +$gitStatusPath = Join-Path $artifactDir "git-status.txt" + +$stepRows = New-Object System.Collections.Generic.List[object] +$logLines = New-Object System.Collections.Generic.List[string] + +function Save-Json { + param([string]$Path, [object]$Value) + $Value | ConvertTo-Json -Depth 50 | Set-Content -Encoding UTF8 -Path $Path +} + +function Convert-OverallStatusText { + param([string]$Status) + switch ($Status) { + "PASS" { return "通过" } + "WARN" { return "通过但有警告" } + "FAIL" { return "未通过" } + "BLOCKED" { return "阻塞" } + default { return $Status } + } +} + +function Invoke-PipelineStep { + param( + [string]$Key, + [string]$Label, + [string]$Script, + [string[]]$Arguments = @() + ) + + $started = Get-Date + $scriptPath = Join-Path $repoRoot $Script + $argList = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $scriptPath) + $Arguments + $logLines.Add("===== $Key $Label START $($started.ToString('o')) =====") | Out-Null + $output = & powershell.exe @argList 2>&1 + $exitCode = $LASTEXITCODE + $ended = Get-Date + foreach ($line in $output) { + $logLines.Add([string]$line) | Out-Null + } + $logLines.Add("===== $Key $Label END $($ended.ToString('o')) exit=$exitCode =====") | Out-Null + + $status = if ($exitCode -ne 0) { "FAIL" } else { "PASS" } + $artifact = "" + $marker = @($output | Where-Object { [string]$_ -match "^(PHASE13|phase13|review_|ingestion_).*" } | Select-Object -Last 1) + if ($marker.Count -gt 0) { + $markerText = [string]$marker[-1] + if ($markerText -match "_BLOCKED\b") { $status = "BLOCKED" } + elseif ($markerText -match "_FAIL\b") { $status = "FAIL" } + elseif ($markerText -match "_WARN\b") { $status = "WARN" } + elseif ($markerText -match "PASS_WITH_LEGAL_REVIEW_REQUIRED") { $status = "BLOCKED" } + elseif ($markerText -match "_PASS\b") { $status = "PASS" } + $parts = $markerText -split "\s+", 2 + if ($parts.Count -gt 1) { + $artifact = $parts[1] + } + } + + $stepRows.Add([pscustomobject][ordered]@{ + key = $Key + label = $Label + script = $Script + status = $status + exit_code = $exitCode + started_at = $started.ToUniversalTime().ToString("o") + ended_at = $ended.ToUniversalTime().ToString("o") + duration_seconds = [math]::Round(($ended - $started).TotalSeconds, 2) + artifact = $artifact + }) | Out-Null +} + +if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) { + throw "gstack is required for all AI-assisted work in this repo." +} + +$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 + +$intakeArgs = @() +$taskPackageArgs = @() +if (-not [string]::IsNullOrWhiteSpace($ReviewerIntakePath)) { + $intakeArgs = @("-ReviewerIntakePath", $ReviewerIntakePath) + $taskPackageArgs = @("-ReviewerIntakePath", $ReviewerIntakePath) +} + +$steps = @( + [pscustomobject]@{ key = "first_wave"; label = "首批复核包就绪"; script = "scripts\run-phase13-first-wave-review-readiness.ps1"; args = @() }, + [pscustomobject]@{ key = "review_intake"; label = "真实复核输入接入"; script = "scripts\run-phase13-review-intake-gate.ps1"; args = $intakeArgs }, + [pscustomobject]@{ key = "task_package"; label = "复核任务包与验收追踪"; script = "scripts\run-phase13-review-task-package-gate.ps1"; args = $taskPackageArgs }, + [pscustomobject]@{ key = "reviewer_kit"; label = "复核派发包导出"; script = "scripts\export-phase13-reviewer-kit.ps1"; args = @() }, + [pscustomobject]@{ key = "return_preflight"; label = "回收结果预检与合并计划"; script = "scripts\run-phase13-review-return-preflight.ps1"; args = @() }, + [pscustomobject]@{ key = "handoff"; label = "复核交付回收"; script = "scripts\run-phase13-review-handoff-gate.ps1"; args = @() }, + [pscustomobject]@{ key = "progress"; label = "复核进度催办"; script = "scripts\run-phase13-review-progress-gate.ps1"; args = @() }, + [pscustomobject]@{ key = "result_apply"; label = "复核结果回填 DryRun"; script = "scripts\apply-phase13-review-results.ps1"; args = @() }, + [pscustomobject]@{ key = "execution"; label = "复核执行门禁"; script = "scripts\run-phase13-review-execution-gate.ps1"; args = @() }, + [pscustomobject]@{ key = "audit_archive"; label = "复核审计归档"; script = "scripts\run-phase13-review-audit-archive-gate.ps1"; args = @() }, + [pscustomobject]@{ key = "library"; label = "资料库门禁"; script = "scripts\run-legal-knowledge-library-gate.ps1"; args = @() }, + [pscustomobject]@{ key = "release"; label = "生产试点发布"; script = "scripts\run-phase13-production-pilot-release.ps1"; args = @() }, + [pscustomobject]@{ key = "control_center"; label = "复核总控驾驶舱"; script = "scripts\run-phase13-review-control-center.ps1"; args = @() } +) + +foreach ($step in $steps) { + Invoke-PipelineStep -Key $step.key -Label $step.label -Script $step.script -Arguments $step.args +} + +$stepRows | Export-Csv -LiteralPath $stepsCsv -NoTypeInformation -Encoding UTF8 +$logLines | Set-Content -Encoding UTF8 -Path $outputLog + +$failedSteps = @($stepRows | Where-Object { $_.status -eq "FAIL" }).Count +$blockedSteps = @($stepRows | Where-Object { $_.status -eq "BLOCKED" }).Count +$warnSteps = @($stepRows | Where-Object { $_.status -eq "WARN" }).Count + +$overallStatus = if ($failedSteps -gt 0) { + "FAIL" +} elseif ($blockedSteps -gt 0) { + "BLOCKED" +} elseif ($warnSteps -gt 0 -or $gitStatus) { + "WARN" +} else { + "PASS" +} + +$metrics = [ordered]@{ + total_steps = $stepRows.Count + failed_steps = $failedSteps + blocked_steps = $blockedSteps + warn_steps = $warnSteps + pass_steps = @($stepRows | Where-Object { $_.status -eq "PASS" }).Count +} + +$summary = [ordered]@{ + name = "phase13-review-ops-pipeline" + status = $overallStatus + generated_at = (Get-Date).ToUniversalTime().ToString("o") + branch = $branch + commit = $commit + remote = $remoteUrl + reviewer_intake_path = $ReviewerIntakePath + metrics = $metrics + steps = $stepRows.ToArray() + artifacts = [ordered]@{ + summary_json = $summaryJson + summary_md = $summaryMd + steps = $stepsCsv + output_log = $outputLog + git_status = $gitStatusPath + } +} +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("- 产物目录:$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.total_steps) |") | Out-Null +$md.Add("| PASS | $($metrics.pass_steps) |") | Out-Null +$md.Add("| WARN | $($metrics.warn_steps) |") | Out-Null +$md.Add("| BLOCKED | $($metrics.blocked_steps) |") | Out-Null +$md.Add("| FAIL | $($metrics.failed_steps) |") | Out-Null +$md.Add("") | Out-Null +$md.Add("## 步骤") | Out-Null +$md.Add("") | Out-Null +$md.Add("| 步骤 | 状态 | 产物 |") | Out-Null +$md.Add("|---|---|---|") | Out-Null +foreach ($row in $stepRows) { + $md.Add("| $($row.label) | $($row.status) | $($row.artifact) |") | Out-Null +} +$md.Add("") | Out-Null +$md.Add("## 当前结论") | Out-Null +$md.Add("") | Out-Null +if ($overallStatus -eq "BLOCKED") { + $md.Add("流水线已按顺序跑完并避免半写入目录竞态;当前阻塞来自真实复核输入、真实签收和真实回收结果尚未发生。") | Out-Null +} elseif ($overallStatus -eq "PASS") { + $md.Add("流水线全部通过,可进入下一阶段发布或生产资产批准。") | Out-Null +} else { + $md.Add("流水线未完全通过,请查看步骤清单和输出日志。") | Out-Null +} +$md | Set-Content -Encoding UTF8 -Path $summaryMd + +Write-Host "PHASE13_REVIEW_OPS_PIPELINE_$overallStatus $artifactDir" +if ($overallStatus -eq "FAIL" -and $FailOnFailedStep) { + throw "阶段 13 复核运营自动流水线失败。查看:$summaryMd" +}