@@ -0,0 +1,638 @@
param (
[ ValidateSet ( " DryRun " , " Review " , " Production " ) ]
[ string ] $ReleaseMode = " DryRun " ,
[ string ] $ApiBaseUrl = " http://127.0.0.1:8000/api/v1 " ,
[ string ] $AdminEmail = " admin@example.com " ,
[ string ] $AdminPassword = " " ,
[ string ] $LibraryRoot = " " ,
[ string ] $ArtifactRoot = " " ,
[ switch ] $AllowApiRelease ,
[ switch ] $ForceReupload ,
[ switch ] $SkipTemplates ,
[ switch ] $SkipReviewRules ,
[ switch ] $FailOnBlocked
)
$ErrorActionPreference = " Stop "
$repoRoot = ( Resolve-Path ( Join-Path $PSScriptRoot " .. " ) ) . Path
$timestamp = Get-Date -Format " yyyy-MM-ddTHH-mm-ss-fff "
if ( [ string ] :: IsNullOrWhiteSpace ( $LibraryRoot ) ) {
$LibraryRoot = Join-Path $repoRoot " project-docs\phase13-legal-knowledge-template-library "
}
if ( -not [ System.IO.Path ] :: IsPathRooted ( $LibraryRoot ) ) {
$LibraryRoot = Join-Path $repoRoot $LibraryRoot
}
if ( [ string ] :: IsNullOrWhiteSpace ( $ArtifactRoot ) ) {
$ArtifactRoot = Join-Path $repoRoot " project-docs\qa-artifacts\phase13-production-pilot-release "
}
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-production-pilot-release-summary.json "
$summaryMd = Join-Path $artifactDir " phase13-production-pilot-release-summary.md "
$gitStatusPath = Join-Path $artifactDir " git-status.txt "
$candidateCsv = Join-Path $artifactDir " release-candidates.csv "
$blockedCsv = Join-Path $artifactDir " blocked-release-items.csv "
$ApiBaseUrl = $ApiBaseUrl . TrimEnd ( " / " )
$checks = New-Object System . Collections . Generic . List [ object ]
$childRuns = New-Object System . Collections . Generic . List [ object ]
$releaseCandidates = 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 40 | 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 Split-SemicolonValues {
param ( [ string ] $Value )
if ( [ string ] :: IsNullOrWhiteSpace ( $Value ) ) {
return @ ( )
}
return @ ( $Value -split " ; " | ForEach-Object { $_ . Trim ( ) } | Where-Object { $_ } )
}
function Get-FieldValue {
param ( [ object ] $Row , [ string ] $Name )
if ( $null -eq $Row ) {
return " "
}
$property = $Row . PSObject . Properties [ $Name ]
if ( $null -eq $property -or $null -eq $property . Value ) {
return " "
}
return [ string ] $property . Value
}
function Test-RequiredFlag {
param ( [ string ] $Value )
if ( [ string ] :: IsNullOrWhiteSpace ( $Value ) ) {
return $false
}
return @ ( " true " , " yes " , " 1 " , " required " ) -contains $Value . ToLowerInvariant ( )
}
function Test-PendingReviewer {
param ( [ string ] $Value )
if ( [ string ] :: IsNullOrWhiteSpace ( $Value ) ) {
return $true
}
return @ ( " pending " , " 待法务复核 " , " 待业务复核 " , " 待复核 " ) -contains $Value . Trim ( )
}
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-ReleaseModeImportMode {
if ( $ReleaseMode -eq " Production " ) {
return " Production "
}
if ( $ReleaseMode -eq " Review " ) {
return " Review "
}
return " Validate "
}
function Get-ChildSummaryPath {
param ( [ string ] $ChildArtifactDir , [ string ] $SummaryFileName )
if ( [ string ] :: IsNullOrWhiteSpace ( $ChildArtifactDir ) -or [ string ] :: IsNullOrWhiteSpace ( $SummaryFileName ) ) {
return " "
}
$candidate = Join-Path $ChildArtifactDir $SummaryFileName
if ( Test-Path -LiteralPath $candidate -PathType Leaf ) {
return $candidate
}
return " "
}
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 Invoke-StageScript {
param (
[ string ] $Key ,
[ string ] $Label ,
[ string ] $ScriptRelativePath ,
[ string[] ] $Arguments ,
[ string ] $ExpectedPrefix ,
[ string ] $SummaryFileName
)
$scriptPath = Join-Path $repoRoot $ScriptRelativePath
$safeKey = $Key -replace " [^A-Za-z0-9_-]+ " , " - "
$logPath = Join-Path $artifactDir " $safeKey -output.log "
if ( -not ( Test-Path -LiteralPath $scriptPath -PathType Leaf ) ) {
Add-Check -Key $Key -Label $Label -Status " fail " -Detail " 脚本缺失: $ScriptRelativePath " -Meta @ { script = $scriptPath ; output_log = $logPath }
return [ pscustomobject ] @ { key = $Key ; label = $Label ; child_status = " MISSING " ; step_status = " fail " ; artifact_dir = " " ; summary_path = " " ; output_log = $logPath ; exit_code = 1 ; summary = $null }
}
$argumentList = @ ( " -NoProfile " , " -ExecutionPolicy " , " Bypass " , " -File " , $scriptPath )
$argumentList + = $Arguments
$output = & powershell . exe @argumentList 2 > & 1
$exitCode = if ( $null -eq $LASTEXITCODE ) { 0 } else { $LASTEXITCODE }
$outputText = ( @ ( $output | ForEach-Object { [ string ] $_ } ) -join [ Environment ] :: NewLine )
$outputText | Set-Content -Encoding UTF8 -Path $logPath
$status = " UNKNOWN "
$childArtifactDir = " "
$pattern = " (?m)^ " + [ regex ] :: Escape ( $ExpectedPrefix ) + " (?<status>[A-Z_]+)\s+(?<artifact>.+?)\s* $ "
$match = [ regex ] :: Match ( $outputText , $pattern )
if ( $match . Success ) {
$status = $match . Groups [ " status " ] . Value
$childArtifactDir = $match . Groups [ " artifact " ] . Value . Trim ( )
}
$summaryPath = Get-ChildSummaryPath -ChildArtifactDir $childArtifactDir -SummaryFileName $SummaryFileName
$childSummary = Read-JsonFile -Path $summaryPath
$stepStatus = " pass "
$detail = " 子门禁状态= $status "
if ( $exitCode -ne 0 ) {
$stepStatus = " fail "
$detail = " 子脚本退出码= $exitCode ,状态= $status "
} elseif ( -not $match . Success ) {
$stepStatus = " fail "
$detail = " 未识别到子门禁输出前缀: $ExpectedPrefix "
} elseif ( $status -eq " FAIL " ) {
$stepStatus = " fail "
$detail = " 子门禁状态=FAIL "
} elseif ( $status -eq " WARN " ) {
$stepStatus = " warn "
$detail = " 子门禁状态=WARN "
} elseif ( $status -eq " BLOCKED " ) {
$stepStatus = " blocked "
$detail = " 子门禁状态=BLOCKED "
}
Add-Check -Key $Key -Label $Label -Status $stepStatus -Detail $detail -Meta @ {
child_status = $status
script = $scriptPath
artifact_dir = $childArtifactDir
summary_path = $summaryPath
output_log = $logPath
exit_code = $exitCode
}
$result = [ pscustomobject ] @ {
key = $Key
label = $Label
child_status = $status
step_status = $stepStatus
artifact_dir = $childArtifactDir
summary_path = $summaryPath
output_log = $logPath
exit_code = $exitCode
summary = $childSummary
}
$childRuns . Add ( $result ) | Out-Null
return $result
}
function New-ReleaseCandidate {
param (
[ string ] $AssetType ,
[ object ] $Row ,
[ string ] $IdField ,
[ string ] $NameField ,
[ bool ] $RequireProductionReady = $true ,
[ bool ] $RequireLegalReviewer = $true ,
[ bool ] $RequireBusinessReviewer = $false ,
[ bool ] $RequireValidityConfirmed = $false
)
$assetId = Get-FieldValue -Row $Row -Name $IdField
$assetName = Get-FieldValue -Row $Row -Name $NameField
$status = Get-FieldValue -Row $Row -Name " status "
$productionReady = Test-RequiredFlag -Value ( Get-FieldValue -Row $Row -Name " production_ready " )
$legalReviewer = Get-FieldValue -Row $Row -Name " legal_reviewer "
$businessReviewer = Get-FieldValue -Row $Row -Name " business_reviewer "
$validityStatus = Get-FieldValue -Row $Row -Name " validity_status "
$reasons = New-Object System . Collections . Generic . List [ string ]
if ( @ ( " APPROVED " , " PUBLISHED " ) -notcontains $status ) {
$reasons . Add ( " status_not_approved: $status " ) | Out-Null
}
if ( $RequireProductionReady -and -not $productionReady ) {
$reasons . Add ( " production_ready_false " ) | Out-Null
}
if ( $RequireLegalReviewer -and ( Test-PendingReviewer -Value $legalReviewer ) ) {
$reasons . Add ( " legal_review_pending " ) | Out-Null
}
if ( $RequireBusinessReviewer -and ( Test-PendingReviewer -Value $businessReviewer ) ) {
$reasons . Add ( " business_review_pending " ) | Out-Null
}
if ( $RequireValidityConfirmed -and ( [ string ] :: IsNullOrWhiteSpace ( $validityStatus ) -or $validityStatus -eq " VERIFY_REQUIRED " ) ) {
$reasons . Add ( " validity_status_unconfirmed " ) | Out-Null
}
return [ pscustomobject][ordered ] @ {
asset_type = $AssetType
asset_id = $assetId
asset_name = $assetName
status = $status
production_ready = $productionReady
legal_reviewer = $legalReviewer
business_reviewer = $businessReviewer
validity_status = $validityStatus
release_ready = ( $reasons . Count -eq 0 )
block_reasons = ( $reasons -join " ; " )
}
}
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 $LibraryRoot -PathType Container ) {
Add-Check -Key " library_root " -Label " 阶段 13 资料包根目录 " -Status " pass " -Detail " 资料包根目录存在 " -Meta @ { root = $LibraryRoot }
} else {
Add-Check -Key " library_root " -Label " 阶段 13 资料包根目录 " -Status " fail " -Detail " 资料包根目录缺失 " -Meta @ { root = $LibraryRoot }
}
$inventoryRoot = Join-Path $LibraryRoot " inventories "
$legalSources = Import-CsvRows -Path ( Join-Path $inventoryRoot " legal-source-inventory.csv " )
$knowledgeCards = Import-CsvRows -Path ( Join-Path $inventoryRoot " legal-knowledge-card-inventory.csv " )
$reviewRules = Import-CsvRows -Path ( Join-Path $inventoryRoot " review-risk-rule-inventory.csv " )
$clauses = Import-CsvRows -Path ( Join-Path $inventoryRoot " clause-library-inventory.csv " )
$templates = Import-CsvRows -Path ( Join-Path $inventoryRoot " contract-template-inventory.csv " )
$reviewSamples = Import-CsvRows -Path ( Join-Path $inventoryRoot " review-golden-sample-inventory.csv " )
$generationSamples = Import-CsvRows -Path ( Join-Path $inventoryRoot " generation-golden-sample-inventory.csv " )
$kbQaSamples = Import-CsvRows -Path ( Join-Path $inventoryRoot " kb-qa-golden-sample-inventory.csv " )
foreach ( $source in $legalSources ) {
$releaseCandidates . Add ( ( New-ReleaseCandidate -AssetType " legal_source " -Row $source -IdField " source_id " -NameField " title " -RequireProductionReady $false -RequireLegalReviewer $true -RequireValidityConfirmed $true ) ) | Out-Null
}
foreach ( $card in $knowledgeCards ) {
$releaseCandidates . Add ( ( New-ReleaseCandidate -AssetType " knowledge_card " -Row $card -IdField " knowledge_card_id " -NameField " title " ) ) | Out-Null
}
foreach ( $rule in $reviewRules ) {
$releaseCandidates . Add ( ( New-ReleaseCandidate -AssetType " review_rule " -Row $rule -IdField " rule_id " -NameField " risk_name " ) ) | Out-Null
}
foreach ( $clause in $clauses ) {
$releaseCandidates . Add ( ( New-ReleaseCandidate -AssetType " clause " -Row $clause -IdField " clause_id " -NameField " clause_name " -RequireBusinessReviewer $true ) ) | Out-Null
}
foreach ( $template in $templates ) {
$releaseCandidates . Add ( ( New-ReleaseCandidate -AssetType " contract_template " -Row $template -IdField " template_id " -NameField " template_name " -RequireBusinessReviewer $true ) ) | Out-Null
}
foreach ( $sample in $reviewSamples ) {
$releaseCandidates . Add ( ( New-ReleaseCandidate -AssetType " review_golden_sample " -Row $sample -IdField " sample_id " -NameField " contract_type " ) ) | Out-Null
}
foreach ( $sample in $generationSamples ) {
$releaseCandidates . Add ( ( New-ReleaseCandidate -AssetType " generation_golden_sample " -Row $sample -IdField " sample_id " -NameField " contract_type " -RequireBusinessReviewer $true ) ) | Out-Null
}
foreach ( $sample in $kbQaSamples ) {
$releaseCandidates . Add ( ( New-ReleaseCandidate -AssetType " kb_qa_golden_sample " -Row $sample -IdField " sample_id " -NameField " question " ) ) | Out-Null
}
$releaseCandidates | Export-Csv -LiteralPath $candidateCsv -NoTypeInformation -Encoding UTF8
$blockedItems = @ ( $releaseCandidates | Where-Object { -not $_ . release_ready } )
$blockedItems | Export-Csv -LiteralPath $blockedCsv -NoTypeInformation -Encoding UTF8
$importMode = Get-ReleaseModeImportMode
$ingestionArgs = @ (
" -ImportMode " , $importMode ,
" -LibraryRoot " , $LibraryRoot ,
" -ApiBaseUrl " , $ApiBaseUrl ,
" -AdminEmail " , $AdminEmail
)
if ( $ReleaseMode -ne " DryRun " -and $AllowApiRelease ) {
$ingestionArgs + = " -AllowApiImport "
}
if ( -not [ string ] :: IsNullOrWhiteSpace ( $AdminPassword ) ) {
$ingestionArgs + = @ ( " -AdminPassword " , $AdminPassword )
}
if ( $ForceReupload ) {
$ingestionArgs + = " -ForceReupload "
}
if ( $SkipTemplates ) {
$ingestionArgs + = " -SkipTemplates "
}
if ( $SkipReviewRules ) {
$ingestionArgs + = " -SkipReviewRules "
}
$ingestionRun = Invoke-StageScript `
-Key " ingestion_closed_loop " `
-Label " 入库与自动化门禁闭环 " `
-ScriptRelativePath " scripts\run-phase13-ingestion-closed-loop.ps1 " `
-Arguments $ingestionArgs `
-ExpectedPrefix " PHASE13_INGESTION_CLOSED_LOOP_ " `
-SummaryFileName " phase13-ingestion-closed-loop-summary.json "
if ( $ReleaseMode -eq " DryRun " ) {
Add-Check -Key " api_release_guard " -Label " 真实 API 发布保护 " -Status " skip " -Required $false -Detail " DryRun 模式不写入系统 API "
} elseif ( $AllowApiRelease ) {
Add-Check -Key " api_release_guard " -Label " 真实 API 发布保护 " -Status " pass " -Detail " 已显式允许 $ReleaseMode 模式写入 API " -Meta @ { release_mode = $ReleaseMode ; api_base_url = $ApiBaseUrl }
} else {
Add-Check -Key " api_release_guard " -Label " 真实 API 发布保护 " -Status " blocked " -Detail " Review/Production 发布必须显式传入 -AllowApiRelease " -Meta @ { release_mode = $ReleaseMode ; api_base_url = $ApiBaseUrl }
}
$readyByType = @ { }
foreach ( $type in @ ( " legal_source " , " knowledge_card " , " review_rule " , " clause " , " contract_template " , " review_golden_sample " , " generation_golden_sample " , " kb_qa_golden_sample " ) ) {
$readyByType [ $type ] = @ ( $releaseCandidates | Where-Object { $_ . asset_type -eq $type -and $_ . release_ready } ) . Count
}
if ( $templates . Count -gt 0 ) {
Add-Check -Key " template_inventory " -Label " 合同模板清单 " -Status " pass " -Detail " 合同模板数量= $( $templates . Count ) "
} else {
Add-Check -Key " template_inventory " -Label " 合同模板清单 " -Status " blocked " -Detail " 合同模板清单为空,无法完成模板预览、生成、下载和审核验证 " -Meta @ { inventory = ( Join-Path $inventoryRoot " contract-template-inventory.csv " ) }
}
if ( $readyByType [ " legal_source " ] -gt 0 ) {
Add-Check -Key " approved_legal_sources " -Label " 可发布法源 " -Status " pass " -Detail " 可发布法源数量= $( $readyByType [ " legal_source " ] ) "
} else {
Add-Check -Key " approved_legal_sources " -Label " 可发布法源 " -Status " blocked " -Detail " 没有已复核并可发布的法源 "
}
if ( $readyByType [ " knowledge_card " ] -gt 0 ) {
Add-Check -Key " approved_knowledge_cards " -Label " 可发布知识卡 " -Status " pass " -Detail " 可发布知识卡数量= $( $readyByType [ " knowledge_card " ] ) "
} else {
Add-Check -Key " approved_knowledge_cards " -Label " 可发布知识卡 " -Status " blocked " -Detail " 没有已复核并可发布的知识卡 "
}
if ( $readyByType [ " review_rule " ] -gt 0 ) {
Add-Check -Key " approved_review_rules " -Label " 可发布审核规则 " -Status " pass " -Detail " 可发布审核规则数量= $( $readyByType [ " review_rule " ] ) "
} else {
Add-Check -Key " approved_review_rules " -Label " 可发布审核规则 " -Status " blocked " -Detail " 没有已复核并可发布的审核规则 "
}
if ( $readyByType [ " clause " ] -gt 0 ) {
Add-Check -Key " approved_clauses " -Label " 可发布标准条款 " -Status " pass " -Detail " 可发布标准条款数量= $( $readyByType [ " clause " ] ) "
} else {
Add-Check -Key " approved_clauses " -Label " 可发布标准条款 " -Status " blocked " -Detail " 没有已复核并可发布的标准条款 "
}
if ( $readyByType [ " contract_template " ] -gt 0 ) {
Add-Check -Key " approved_templates " -Label " 可发布合同模板 " -Status " pass " -Detail " 可发布合同模板数量= $( $readyByType [ " contract_template " ] ) "
} else {
Add-Check -Key " approved_templates " -Label " 可发布合同模板 " -Status " blocked " -Detail " 没有已复核并可发布的合同模板 "
}
$templateIds = @ { }
foreach ( $template in $templates ) {
$templateId = Get-FieldValue -Row $template -Name " template_id "
if ( -not [ string ] :: IsNullOrWhiteSpace ( $templateId ) ) {
$templateIds [ $templateId ] = $true
}
}
$generationSampleTemplateIds = @ { }
foreach ( $sample in $generationSamples ) {
$sampleTemplateId = Get-FieldValue -Row $sample -Name " template_id "
if ( -not [ string ] :: IsNullOrWhiteSpace ( $sampleTemplateId ) ) {
$generationSampleTemplateIds [ $sampleTemplateId ] = $true
}
}
$templatesWithoutGenerationSamples = @ ( $templateIds . Keys | Where-Object { -not $generationSampleTemplateIds . ContainsKey ( $_ ) } )
if ( $templates . Count -eq 0 ) {
Add-Check -Key " template_generation_coverage " -Label " 模板生成样本覆盖 " -Status " blocked " -Detail " 模板清单为空,无法验证生成样本覆盖 "
} elseif ( $templatesWithoutGenerationSamples . Count -eq 0 ) {
Add-Check -Key " template_generation_coverage " -Label " 模板生成样本覆盖 " -Status " pass " -Detail " 所有模板均绑定生成样本 "
} else {
Add-Check -Key " template_generation_coverage " -Label " 模板生成样本覆盖 " -Status " blocked " -Detail " 以下模板缺少生成样本: $( $templatesWithoutGenerationSamples -join ',' ) " -Meta @ { templates = $templatesWithoutGenerationSamples }
}
$reviewRulesWithoutSamples = @ ( $reviewRules | Where-Object { ( Split-SemicolonValues -Value ( Get-FieldValue -Row $_ -Name " review_sample_ids " ) ) . Count -eq 0 } | ForEach-Object { Get-FieldValue -Row $_ -Name " rule_id " } )
if ( $reviewRules . Count -gt 0 -and $reviewRulesWithoutSamples . Count -eq 0 ) {
Add-Check -Key " review_rule_sample_coverage " -Label " 审核规则样本覆盖 " -Status " pass " -Detail " 所有审核规则均绑定审核黄金样本 "
} elseif ( $reviewRules . Count -eq 0 ) {
Add-Check -Key " review_rule_sample_coverage " -Label " 审核规则样本覆盖 " -Status " blocked " -Detail " 审核规则清单为空 "
} else {
Add-Check -Key " review_rule_sample_coverage " -Label " 审核规则样本覆盖 " -Status " blocked " -Detail " 以下审核规则缺少审核样本: $( $reviewRulesWithoutSamples -join ',' ) " -Meta @ { rules = $reviewRulesWithoutSamples }
}
$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 ] @ {
release_mode = $ReleaseMode
legal_sources = $legalSources . Count
knowledge_cards = $knowledgeCards . Count
review_rules = $reviewRules . Count
clauses = $clauses . Count
templates = $templates . Count
review_samples = $reviewSamples . Count
generation_samples = $generationSamples . Count
kb_qa_samples = $kbQaSamples . Count
release_candidates = $releaseCandidates . Count
release_ready_candidates = @ ( $releaseCandidates | Where-Object { $_ . release_ready } ) . Count
blocked_candidates = $blockedItems . Count
ready_legal_sources = $readyByType [ " legal_source " ]
ready_knowledge_cards = $readyByType [ " knowledge_card " ]
ready_review_rules = $readyByType [ " review_rule " ]
ready_clauses = $readyByType [ " clause " ]
ready_templates = $readyByType [ " contract_template " ]
}
$summary = [ ordered ] @ {
name = " phase13-production-pilot-release "
status = $overallStatus
generated_at = ( Get-Date ) . ToUniversalTime ( ) . ToString ( " o " )
branch = $branch
commit = $commit
remote = $remoteUrl
release_mode = $ReleaseMode
api_base_url = $ApiBaseUrl
allow_api_release = [ bool ] $AllowApiRelease
artifact_dir = $artifactDir
library_root = $LibraryRoot
checks = $checks . ToArray ( )
child_runs = @ ( $childRuns | ForEach-Object {
[ ordered ] @ {
key = $_ . key
label = $_ . label
child_status = $_ . child_status
step_status = $_ . step_status
artifact_dir = $_ . artifact_dir
summary_path = $_ . summary_path
output_log = $_ . output_log
exit_code = $_ . exit_code
}
} )
metrics = $metrics
artifacts = [ ordered ] @ {
summary_json = $summaryJson
summary_md = $summaryMd
release_candidates_csv = $candidateCsv
blocked_items_csv = $blockedCsv
git_status = $gitStatusPath
}
totals = [ ordered ] @ {
total_checks = $checks . Count
failed_required = $failedRequired
blocked_required = $blockedRequired
warnings = $warnings
}
}
Save-Json -Path $summaryJson -Value $summary
$overallStatusText = Convert-OverallStatusText -Status $overallStatus
$md = New-Object System . Collections . Generic . List [ string ]
$md . Add ( " # 阶段 13 生产试点发布闭环报告 " ) | Out-Null
$md . Add ( " " ) | Out-Null
$md . Add ( " - 发布状态: $overallStatusText ( $overallStatus ) " ) | Out-Null
$md . Add ( " - 发布模式: $ReleaseMode " ) | Out-Null
$md . Add ( " - 允许 API 发布: $( [ bool ] $AllowApiRelease ) " ) | Out-Null
$md . Add ( " - 生成时间: $( $summary . generated_at ) " ) | Out-Null
$md . Add ( " - 分支: $branch " ) | Out-Null
$md . Add ( " - 提交: $commit " ) | Out-Null
$md . Add ( " - API 地址: $ApiBaseUrl " ) | Out-Null
$md . Add ( " - 资料包: $LibraryRoot " ) | 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
foreach ( $run in $childRuns ) {
$childArtifact = if ( [ string ] :: IsNullOrWhiteSpace ( $run . artifact_dir ) ) { " " } else { $run . artifact_dir . Replace ( " | " , " / " ) }
$md . Add ( " | $( $run . label ) | $( $run . child_status ) | $childArtifact | " ) | 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_sources ) | " ) | Out-Null
$md . Add ( " | 知识卡 | $( $metrics . knowledge_cards ) | " ) | Out-Null
$md . Add ( " | 审核规则 | $( $metrics . review_rules ) | " ) | Out-Null
$md . Add ( " | 标准条款 | $( $metrics . clauses ) | " ) | Out-Null
$md . Add ( " | 合同模板 | $( $metrics . templates ) | " ) | Out-Null
$md . Add ( " | 审核样本 | $( $metrics . review_samples ) | " ) | Out-Null
$md . Add ( " | 生成样本 | $( $metrics . generation_samples ) | " ) | Out-Null
$md . Add ( " | KB 问答样本 | $( $metrics . kb_qa_samples ) | " ) | Out-Null
$md . Add ( " | 发布候选资产 | $( $metrics . release_candidates ) | " ) | Out-Null
$md . Add ( " | 可发布资产 | $( $metrics . release_ready_candidates ) | " ) | Out-Null
$md . Add ( " | 阻塞资产 | $( $metrics . blocked_candidates ) | " ) | Out-Null
$md . Add ( " | 可发布模板 | $( $metrics . ready_templates ) | " ) | 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 { " 否 " }
$statusText = Convert-CheckStatusText -Status $check . status
$detail = ( [ string ] $check . detail ) . Replace ( " | " , " / " )
$md . Add ( " | $( $check . label ) | $requiredText | $statusText | $detail | " ) | 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 . Add ( " " ) | Out-Null
$md . Add ( " 本地产物: " ) | Out-Null
$md . Add ( " " ) | Out-Null
$md . Add ( '- `phase13-production-pilot-release-summary.json`' ) | Out-Null
$md . Add ( '- `phase13-production-pilot-release-summary.md`' ) | Out-Null
$md . Add ( '- `release-candidates.csv`' ) | Out-Null
$md . Add ( '- `blocked-release-items.csv`' ) | Out-Null
$md . Add ( '- `*-output.log`' ) | Out-Null
$md | Set-Content -Encoding UTF8 -Path $summaryMd
Write-Host " PHASE13_PRODUCTION_PILOT_RELEASE_ $overallStatus $artifactDir "
if ( $overallStatus -eq " FAIL " ) {
throw " 阶段 13 生产试点发布闭环未通过。查看: $summaryMd "
}
if ( $overallStatus -eq " BLOCKED " -and $FailOnBlocked ) {
throw " 阶段 13 生产试点发布闭环被阻塞。查看: $summaryMd "
}