docs: add phase 13 review ops pipeline

This commit is contained in:
Codex
2026-05-02 10:48:16 +08:00
parent 3617fc17f9
commit e20bb7650d
4 changed files with 339 additions and 0 deletions
@@ -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`,这表示自动化链路可运行,但生产批准仍等待真实法务和业务输入。
@@ -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`。
@@ -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"
)
+222
View File
@@ -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"
}