docs: add phase 13 review ops pipeline
This commit is contained in:
+51
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user