docs: add phase 9 quality gate

This commit is contained in:
Codex
2026-05-01 18:46:35 +08:00
parent 081a67556d
commit 50f32d4ab9
10 changed files with 706 additions and 0 deletions
@@ -0,0 +1,20 @@
# Phase 9 Quality Fixture Pack
This folder defines the repeatable phase 9 business quality baseline.
Files:
1. `quality-metric-dictionary.csv`: metric definitions and gate thresholds.
2. `golden-review-samples.csv`: review quality golden samples.
3. `golden-kb-question-set.csv`: knowledge base question quality set.
4. `golden-contract-generation-set.csv`: contract generation quality set.
5. `prompt-version-register.csv`: prompt and model governance register.
6. `ai-output-governance.md`: AI output governance rules.
7. `data-retention-policy.md`: data retention, privacy, and audit policy.
Rules:
1. Do not store raw sensitive contracts here.
2. Use sample IDs and evidence artifact paths.
3. Do not write passwords, secrets, or Webhook URLs into fixture files.
4. Treat this pack as the input for `scripts/run-business-quality-gate.ps1`.
@@ -0,0 +1,17 @@
# Phase 9 AI Output Governance
## Required Rules
1. Review output must preserve structured fields for risk summary, risk items, evidence, suggestions, and next action.
2. Knowledge Base answers must cite usable sources when sources exist.
3. Knowledge Base answers must refuse or explain uncertainty when sources are missing, blocked, failed, high sensitivity, or expired.
4. Contract generation must validate required variables before preview or generation.
5. Contract generation must keep generated file assets traceable.
6. Prompt changes require a version entry in `prompt-version-register.csv`.
7. Model upgrades require a quality gate run and rollback note.
8. Every active prompt must declare an expected schema, uncertainty policy, rollback policy, owner, and active version.
## Non-Goals
1. This governance file does not certify legal correctness.
2. This governance file defines product quality controls and regression requirements.
@@ -0,0 +1,20 @@
# Phase 9 Data Retention, Privacy, and Audit Policy
## Data Classes
| Data Type | Retention | Privacy Rule | Audit Rule |
|---|---|---|---|
| Contract source text | Pilot duration plus agreed retention window | Use sample IDs in reports; do not copy raw sensitive text into docs | Upload, review, delete, and download events must be auditable |
| Review result | Pilot duration plus quality baseline window | Reference review IDs and sample IDs | Review creation and report access must be auditable |
| Review report | Pilot duration plus agreed retention window | Do not publish report content outside authorized users | Download must be auditable |
| Chat messages | Pilot duration plus agreed retention window | Avoid raw sensitive contract clauses in reports | Conversation access and generation should remain traceable |
| KB documents | Until document expiry or owner removal | High sensitivity documents are local-only unless approved | Upload, index, retry, delete must be auditable |
| Generated contracts | Pilot duration plus agreed retention window | Use generated contract IDs, not raw content, in quality docs | Preview and download must be auditable |
| Gate artifacts | 30 days default or project policy | Redact secrets, passwords, and Webhook URLs | Keep summary JSON/MD for review |
## Rules
1. Real passwords, secrets, and Webhook URLs must not be written to repository files.
2. Real sensitive contract text must not be copied into stage documents.
3. Feedback records must use sample IDs and artifact paths.
4. Data deletion and export requirements must be reviewed before production handover.
@@ -0,0 +1,4 @@
case_id,template_id,scenario_ids,required_variables,optional_variables,expected_preview,expected_download,expected_file_asset,sensitivity_level,automation_level,notes
QC-GEN-001,TPL-PILOT-001,PILOT-GENERATE-001,party_a;party_b;amount;payment_cycle,delivery_location;contact,true,true,true,low,automated,Maps to procurement generation path
QC-GEN-002,TPL-PILOT-002,PILOT-GENERATE-001,service_provider;client;service_period;service_fee,acceptance_method;breach_liability,true,true,true,low,automated,Template quality baseline for service contracts
QC-GEN-NEG-001,TPL-PILOT-003,PILOT-NEG-002,disclosing_party;receiving_party;confidentiality_period,exceptions,false,false,false,medium,policy_only,Disabled template must not enter pilot scope until enabled
1 case_id template_id scenario_ids required_variables optional_variables expected_preview expected_download expected_file_asset sensitivity_level automation_level notes
2 QC-GEN-001 TPL-PILOT-001 PILOT-GENERATE-001 party_a;party_b;amount;payment_cycle delivery_location;contact true true true low automated Maps to procurement generation path
3 QC-GEN-002 TPL-PILOT-002 PILOT-GENERATE-001 service_provider;client;service_period;service_fee acceptance_method;breach_liability true true true low automated Template quality baseline for service contracts
4 QC-GEN-NEG-001 TPL-PILOT-003 PILOT-NEG-002 disclosing_party;receiving_party;confidentiality_period exceptions false false false medium policy_only Disabled template must not enter pilot scope until enabled
@@ -0,0 +1,4 @@
question_id,document_id,scenario_ids,question_intent,expected_source_policy,expected_answer_behavior,sensitivity_level,automation_level,notes
QK-KB-001,KB-DOC-001,PILOT-KB-001,indexed_document_status,source_trace_required,document_becomes_usable_or_failed_with_retry_reason,low,automated,Maps to KB upload and indexing
QK-KB-002,KB-DOC-001,PILOT-KB-002,source_backed_answer,source_trace_required,answer_cites_usable_source_or_refuses,low,automated,Maps to KB question and source control
QK-KB-NEG-001,KB-DOC-003,PILOT-NEG-002,no_source_or_high_sensitive,refusal_required,do_not_output_deterministic_advice,high,policy_only,High sensitive source must not be uploaded to production
1 question_id document_id scenario_ids question_intent expected_source_policy expected_answer_behavior sensitivity_level automation_level notes
2 QK-KB-001 KB-DOC-001 PILOT-KB-001 indexed_document_status source_trace_required document_becomes_usable_or_failed_with_retry_reason low automated Maps to KB upload and indexing
3 QK-KB-002 KB-DOC-001 PILOT-KB-002 source_backed_answer source_trace_required answer_cites_usable_source_or_refuses low automated Maps to KB question and source control
4 QK-KB-NEG-001 KB-DOC-003 PILOT-NEG-002 no_source_or_high_sensitive refusal_required do_not_output_deterministic_advice high policy_only High sensitive source must not be uploaded to production
@@ -0,0 +1,4 @@
sample_id,contract_type,scenario_ids,expected_risk_types,expected_evidence,expected_actionable_suggestion,sensitivity_level,automation_level,notes
QR-REVIEW-001,procurement,PILOT-REVIEW-001;PILOT-REVIEW-002,payment_terms;liability;auto_renewal,clause_location_required,revision_or_negotiation_suggestion_required,medium,automated,Maps to phase8 review production regression
QR-REVIEW-002,service,PILOT-REVIEW-002;PILOT-CHAT-001,acceptance;termination;breach,review_context_required,chat_followup_suggestion_required,low,automated,Maps to Review to Chat continuity
QR-REVIEW-NEG-001,negative,PILOT-NEG-002,invalid_input;failure_explanation,next_action_required,retry_or_reupload_required,low,automated,Maps to failed and blocked state explanation
1 sample_id contract_type scenario_ids expected_risk_types expected_evidence expected_actionable_suggestion sensitivity_level automation_level notes
2 QR-REVIEW-001 procurement PILOT-REVIEW-001;PILOT-REVIEW-002 payment_terms;liability;auto_renewal clause_location_required revision_or_negotiation_suggestion_required medium automated Maps to phase8 review production regression
3 QR-REVIEW-002 service PILOT-REVIEW-002;PILOT-CHAT-001 acceptance;termination;breach review_context_required chat_followup_suggestion_required low automated Maps to Review to Chat continuity
4 QR-REVIEW-NEG-001 negative PILOT-NEG-002 invalid_input;failure_explanation next_action_required retry_or_reupload_required low automated Maps to failed and blocked state explanation
@@ -0,0 +1,4 @@
prompt_id,domain,version,status,owner,expected_schema,uncertainty_policy,rollback_policy,notes
PROMPT-REVIEW-001,review,v1,active,engineering,review_result_schema,show_uncertainty_and_next_action,restore_previous_prompt_version,Used by review quality baseline
PROMPT-KB-001,kb,v1,active,engineering,kb_answer_with_sources,refuse_when_source_missing,restore_previous_prompt_version,Used by KB source-backed answers
PROMPT-CONTRACT-001,contract_generation,v1,active,engineering,contract_generation_schema,require_missing_variable_prompt,restore_previous_prompt_version,Used by contract generation baseline
1 prompt_id domain version status owner expected_schema uncertainty_policy rollback_policy notes
2 PROMPT-REVIEW-001 review v1 active engineering review_result_schema show_uncertainty_and_next_action restore_previous_prompt_version Used by review quality baseline
3 PROMPT-KB-001 kb v1 active engineering kb_answer_with_sources refuse_when_source_missing restore_previous_prompt_version Used by KB source-backed answers
4 PROMPT-CONTRACT-001 contract_generation v1 active engineering contract_generation_schema require_missing_variable_prompt restore_previous_prompt_version Used by contract generation baseline
@@ -0,0 +1,11 @@
metric_id,domain,metric_name,description,threshold,blocking_level,evidence_source
REVIEW-RISK-COVERAGE,review,risk coverage,Golden review samples map to expected risk categories,100%,P1,golden-review-samples.csv
REVIEW-EVIDENCE-COVERAGE,review,evidence coverage,Each review sample includes expected evidence and expected action,100%,P1,golden-review-samples.csv
REVIEW-SCENARIO-PASS,review,scenario pass rate,Phase 8 review and review-chat mapped scenarios pass,100%,P0,phase8 scenario record
KB-SOURCE-POLICY,kb,source policy coverage,Golden KB questions define source behavior and refusal policy,100%,P1,golden-kb-question-set.csv
KB-SCENARIO-PASS,kb,scenario pass rate,Phase 8 KB mapped scenarios pass,100%,P0,phase8 scenario record
CONTRACT-VARIABLE-COVERAGE,contract_generation,variable coverage,Golden generation cases define required variables and download expectations,100%,P1,golden-contract-generation-set.csv
CONTRACT-SCENARIO-PASS,contract_generation,scenario pass rate,Phase 8 contract generation mapped scenario passes,100%,P0,phase8 scenario record
AI-PROMPT-VERSIONED,ai_governance,prompt versioning,Prompt register contains versioned entries and rollback notes,100%,P1,prompt-version-register.csv
DATA-RETENTION-POLICY,data_governance,retention policy,Data retention and privacy policy exists and covers core data types,100%,P1,data-retention-policy.md
OPEN-P0-P1,quality_ops,open P0/P1 feedback,Open P0/P1 quality feedback is zero,0,P0,phase8 feedback register
1 metric_id domain metric_name description threshold blocking_level evidence_source
2 REVIEW-RISK-COVERAGE review risk coverage Golden review samples map to expected risk categories 100% P1 golden-review-samples.csv
3 REVIEW-EVIDENCE-COVERAGE review evidence coverage Each review sample includes expected evidence and expected action 100% P1 golden-review-samples.csv
4 REVIEW-SCENARIO-PASS review scenario pass rate Phase 8 review and review-chat mapped scenarios pass 100% P0 phase8 scenario record
5 KB-SOURCE-POLICY kb source policy coverage Golden KB questions define source behavior and refusal policy 100% P1 golden-kb-question-set.csv
6 KB-SCENARIO-PASS kb scenario pass rate Phase 8 KB mapped scenarios pass 100% P0 phase8 scenario record
7 CONTRACT-VARIABLE-COVERAGE contract_generation variable coverage Golden generation cases define required variables and download expectations 100% P1 golden-contract-generation-set.csv
8 CONTRACT-SCENARIO-PASS contract_generation scenario pass rate Phase 8 contract generation mapped scenario passes 100% P0 phase8 scenario record
9 AI-PROMPT-VERSIONED ai_governance prompt versioning Prompt register contains versioned entries and rollback notes 100% P1 prompt-version-register.csv
10 DATA-RETENTION-POLICY data_governance retention policy Data retention and privacy policy exists and covers core data types 100% P1 data-retention-policy.md
11 OPEN-P0-P1 quality_ops open P0/P1 feedback Open P0/P1 quality feedback is zero 0 P0 phase8 feedback register
@@ -0,0 +1,109 @@
# AI合同平台 阶段 9 业务质量、AI 输出质量与数据治理闭环方案(2026-05-01)
> 阶段定位:阶段 9 不继续扩展主流程,而是把阶段 8 的试点闭环转化为可衡量、可复跑、可追踪的业务质量基线。
> 第一目标:让合同审查、知识库问答、合同生成、AI 输出治理和数据治理都具备明确指标、黄金样本、评测报告和统一质量门禁。
---
## 一、当前输入
阶段 9 的直接输入来自阶段 8:
1. 阶段 8 完整执行记录:`project-docs/阶段8-试点真实使用与业务反馈闭环完整执行记录-2026-05-01.md`
2. 阶段 8 最终证据:`project-docs/qa-artifacts/phase8-pilot-closed-loop/2026-05-01T18-30-44-287/`
3. 阶段 8 试点模板包:`project-docs/phase8-pilot-templates/`
4. 阶段 8 最终结论:试点场景 `10/10 PASS`,Open P0/P1 为 `0`,外部 Webhook 仍为 `BLOCKED_EXTERNAL_WEBHOOK`。
---
## 二、阶段 9 交付物
本阶段新增质量基线包:
1. `project-docs/phase9-quality-fixtures/quality-metric-dictionary.csv`
2. `project-docs/phase9-quality-fixtures/golden-review-samples.csv`
3. `project-docs/phase9-quality-fixtures/golden-kb-question-set.csv`
4. `project-docs/phase9-quality-fixtures/golden-contract-generation-set.csv`
5. `project-docs/phase9-quality-fixtures/prompt-version-register.csv`
6. `project-docs/phase9-quality-fixtures/ai-output-governance.md`
7. `project-docs/phase9-quality-fixtures/data-retention-policy.md`
8. `scripts/run-business-quality-gate.ps1`
自动化证据目录:
`project-docs/qa-artifacts/phase9-business-quality-gate/<timestamp>/`
---
## 三、评测范围
### 9.1 合同审查质量
评测目标:
1. 黄金样本具备风险类型、期望证据、期望建议和自动化映射场景。
2. 阶段 8 的 Review 场景全部通过。
3. Review -> Chat 的上下文承接通过。
4. 失败态/异常态解释场景通过。
5. P0/P1 质量反馈为 0。
### 9.2 知识库问答质量
评测目标:
1. 黄金问题具备来源策略、回答策略和拒答规则。
2. 知识库上传、索引、失败重试和问答入口控制通过。
3. 无来源或来源不足时不能输出确定性建议。
4. 过期文档和高敏文档有治理策略。
### 9.3 合同生成质量
评测目标:
1. 黄金模板变量具备必填变量、可选变量、预期下载和文件资产要求。
2. 模板、预览、生成、详情和下载场景通过。
3. 变量缺失、模板停用和文件资产追踪有治理规则。
### 9.4 AI 输出治理
评测目标:
1. Prompt 有版本登记。
2. 关键输出有 schema 或字段要求。
3. 不确定回答和无来源回答有拒答/提示策略。
4. 模型升级必须经过评测和回滚说明。
### 9.5 数据治理
评测目标:
1. 合同原文、审查结果、报告、聊天记录、生成文件有保留周期。
2. 试点样本使用编号和敏感级别,不在文档中直接复制真实敏感条款。
3. 反馈池引用样本编号和证据路径。
4. 阶段 9 质量文档和脚本不写入管理员密码、Webhook URL 或密钥。
---
## 四、门禁口径
`scripts/run-business-quality-gate.ps1` 输出以下状态:
1. `PASS`:所有必需质量门禁通过,无警告。
2. `WARN`:必需项通过,但存在非阻塞质量改进项。
3. `PASS_WITH_WEBHOOK_BLOCKED`:业务质量闭环通过,但继承阶段 8 的外部 Webhook 阻塞。
4. `FAIL`:任一必需质量门禁失败。
阶段 9 不把外部 Webhook 当成业务质量失败,但必须继续保留为外部阻塞,不能写成真实到人 PASS。
---
## 五、阶段退出标准
阶段 9 退出必须满足:
1. 合同审查、知识库问答、合同生成均有质量指标和黄金样本。
2. 黄金样本可由 `run-business-quality-gate.ps1` 读取并评测。
3. Review、KB、Contract Generation 三个质量子报告均生成。
4. AI 输出治理和数据治理均生成检查摘要。
5. Open P0/P1 质量反馈为 0。
6. 阶段 10 可直接引用阶段 9 的质量结论作为交付包质量基线。
+513
View File
@@ -0,0 +1,513 @@
param(
[string]$ArtifactRoot = "",
[string]$FixtureRoot = "",
[string]$Phase8ArtifactRoot = "",
[int]$MaxPhase8AgeHours = 48,
[switch]$FailOnWarn,
[switch]$FailOnExternalBlocked
)
$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\phase9-business-quality-gate"
}
if ([string]::IsNullOrWhiteSpace($FixtureRoot)) {
$FixtureRoot = Join-Path $repoRoot "project-docs\phase9-quality-fixtures"
}
if ([string]::IsNullOrWhiteSpace($Phase8ArtifactRoot)) {
$Phase8ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase8-pilot-closed-loop"
}
foreach ($pathName in @("ArtifactRoot", "FixtureRoot", "Phase8ArtifactRoot")) {
$value = Get-Variable -Name $pathName -ValueOnly
if (-not [System.IO.Path]::IsPathRooted($value)) {
Set-Variable -Name $pathName -Value (Join-Path $repoRoot $value)
}
}
$artifactDir = Join-Path $ArtifactRoot $timestamp
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$summaryJson = Join-Path $artifactDir "business-quality-gate-summary.json"
$summaryMd = Join-Path $artifactDir "business-quality-gate-summary.md"
$scorecardCsv = Join-Path $artifactDir "phase9-quality-scorecard.csv"
$defectCsv = Join-Path $artifactDir "phase9-quality-defect-register.csv"
$gitStatusPath = Join-Path $artifactDir "git-status.txt"
$checks = 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 18 | Set-Content -Encoding UTF8 -Path $Path
}
function Read-JsonFile {
param([string]$Path)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
return $null
}
try {
return Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json
} catch {
return $null
}
}
function Get-LatestSummaryArtifact {
param(
[string]$Root,
[string]$SummaryFile
)
if (-not (Test-Path -LiteralPath $Root -PathType Container)) {
return $null
}
return Get-ChildItem -LiteralPath $Root -Directory |
Sort-Object LastWriteTime -Descending |
Where-Object { Test-Path -LiteralPath (Join-Path $_.FullName $SummaryFile) } |
Select-Object -First 1
}
function Test-CsvColumns {
param(
[object[]]$Rows,
[string[]]$Columns
)
if ($Rows.Count -eq 0) {
return $false
}
$names = @($Rows[0].PSObject.Properties.Name)
foreach ($column in $Columns) {
if ($names -notcontains $column) {
return $false
}
}
return $true
}
function Get-Scenario {
param(
[object[]]$Rows,
[string]$ScenarioId
)
return @($Rows | Where-Object { $_.scenario_id -eq $ScenarioId } | Select-Object -First 1)[0]
}
function Test-ScenariosPass {
param(
[object[]]$Rows,
[string[]]$ScenarioIds
)
foreach ($scenarioId in $ScenarioIds) {
$item = Get-Scenario -Rows $Rows -ScenarioId $scenarioId
if ($null -eq $item -or $item.status -ne "PASS") {
return $false
}
}
return $true
}
function Write-DomainReport {
param(
[string]$Domain,
[string]$Title,
[object[]]$FixtureRows,
[string[]]$ScenarioIds,
[object[]]$ScenarioRows,
[string[]]$RequiredColumns,
[string]$OutputPrefix,
[string]$ExtraDetail = ""
)
$columnsOk = Test-CsvColumns -Rows $FixtureRows -Columns $RequiredColumns
$fixturesReady = $FixtureRows.Count -gt 0 -and $columnsOk
$scenariosPass = Test-ScenariosPass -Rows $ScenarioRows -ScenarioIds $ScenarioIds
$score = if ($fixturesReady -and $scenariosPass) { 100 } elseif ($fixturesReady -or $scenariosPass) { 50 } else { 0 }
$status = if ($score -eq 100) { "PASS" } else { "FAIL" }
$jsonPath = Join-Path $artifactDir "$OutputPrefix.json"
$mdPath = Join-Path $artifactDir "$OutputPrefix.md"
$record = [ordered]@{
domain = $Domain
title = $Title
status = $status
score = $score
fixture_count = $FixtureRows.Count
required_columns = $RequiredColumns
scenario_ids = $ScenarioIds
scenarios_pass = $scenariosPass
fixtures_ready = $fixturesReady
extra_detail = $ExtraDetail
}
Save-Json -Path $jsonPath -Value $record
$markdown = New-Object System.Collections.Generic.List[string]
$markdown.Add("# $Title") | Out-Null
$markdown.Add("") | Out-Null
$markdown.Add("- Status: $status") | Out-Null
$markdown.Add("- Score: $score") | Out-Null
$markdown.Add("- Fixture count: $($FixtureRows.Count)") | Out-Null
$markdown.Add("- Fixtures ready: $fixturesReady") | Out-Null
$markdown.Add("- Scenarios pass: $scenariosPass") | Out-Null
if (-not [string]::IsNullOrWhiteSpace($ExtraDetail)) {
$markdown.Add("- Detail: $ExtraDetail") | Out-Null
}
$markdown.Add("") | Out-Null
$markdown.Add("| Scenario | Status |") | Out-Null
$markdown.Add("|---|---|") | Out-Null
foreach ($scenarioId in $ScenarioIds) {
$scenario = Get-Scenario -Rows $ScenarioRows -ScenarioId $scenarioId
$scenarioStatus = if ($scenario) { $scenario.status } else { "MISSING" }
$markdown.Add("| $scenarioId | $scenarioStatus |") | Out-Null
}
$markdown | Set-Content -Encoding UTF8 -Path $mdPath
return [pscustomobject][ordered]@{
domain = $Domain
status = $status
score = $score
fixture_count = $FixtureRows.Count
scenarios = ($ScenarioIds -join ";")
report_json = $jsonPath
report_md = $mdPath
}
}
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
Add-Check -Key "gstack" -Label "gstack preflight" -Status "fail" -Detail "gstack is missing"
} else {
Add-Check -Key "gstack" -Label "gstack preflight" -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 ($gitStatus) {
Add-Check -Key "working_tree" -Label "working tree" -Status "warn" -Required $false -Detail "Working tree has uncommitted changes; quality gate is advisory" -Meta @{ artifact = $gitStatusPath }
} else {
Add-Check -Key "working_tree" -Label "working tree" -Status "pass" -Detail "Working tree is clean" -Meta @{ artifact = $gitStatusPath }
}
if ($remoteUrl -match "192\.168\.3\.90") {
Add-Check -Key "origin_remote" -Label "origin remote" -Status "pass" -Detail "origin points to 192.168.3.90" -Meta @{ branch = $branch; commit = $commit; remote = $remoteUrl }
} else {
Add-Check -Key "origin_remote" -Label "origin remote" -Status "fail" -Detail "origin does not point to 192.168.3.90" -Meta @{ branch = $branch; commit = $commit; remote = $remoteUrl }
}
if (Test-Path -LiteralPath $FixtureRoot -PathType Container) {
Add-Check -Key "fixture_root" -Label "quality fixture root" -Status "pass" -Detail "Fixture root exists" -Meta @{ root = $FixtureRoot }
} else {
Add-Check -Key "fixture_root" -Label "quality fixture root" -Status "fail" -Detail "Fixture root missing" -Meta @{ root = $FixtureRoot }
}
$requiredFixtureFiles = @(
"quality-metric-dictionary.csv",
"golden-review-samples.csv",
"golden-kb-question-set.csv",
"golden-contract-generation-set.csv",
"prompt-version-register.csv",
"ai-output-governance.md",
"data-retention-policy.md"
)
foreach ($fixtureFile in $requiredFixtureFiles) {
$fixturePath = Join-Path $FixtureRoot $fixtureFile
if (Test-Path -LiteralPath $fixturePath -PathType Leaf) {
Add-Check -Key ("fixture_" + ($fixtureFile -replace '[^a-zA-Z0-9]', '_')) -Label "fixture $fixtureFile" -Status "pass" -Detail "Fixture exists" -Meta @{ path = $fixturePath }
} else {
Add-Check -Key ("fixture_" + ($fixtureFile -replace '[^a-zA-Z0-9]', '_')) -Label "fixture $fixtureFile" -Status "fail" -Detail "Fixture missing" -Meta @{ path = $fixturePath }
}
}
$metrics = @(Import-Csv -LiteralPath (Join-Path $FixtureRoot "quality-metric-dictionary.csv"))
$reviewFixtures = @(Import-Csv -LiteralPath (Join-Path $FixtureRoot "golden-review-samples.csv"))
$kbFixtures = @(Import-Csv -LiteralPath (Join-Path $FixtureRoot "golden-kb-question-set.csv"))
$contractFixtures = @(Import-Csv -LiteralPath (Join-Path $FixtureRoot "golden-contract-generation-set.csv"))
$promptRegister = @(Import-Csv -LiteralPath (Join-Path $FixtureRoot "prompt-version-register.csv"))
if ($metrics.Count -ge 10) {
Add-Check -Key "metric_dictionary" -Label "quality metric dictionary" -Status "pass" -Detail "Metric count=$($metrics.Count)"
} else {
Add-Check -Key "metric_dictionary" -Label "quality metric dictionary" -Status "fail" -Detail "Metric count is too low: $($metrics.Count)"
}
$latestPhase8 = Get-LatestSummaryArtifact -Root $Phase8ArtifactRoot -SummaryFile "phase8-pilot-closed-loop-summary.json"
$phase8Summary = $null
$scenarioRows = @()
$feedbackRows = @()
$phase8AgeHours = $null
if ($latestPhase8) {
$phase8SummaryPath = Join-Path $latestPhase8.FullName "phase8-pilot-closed-loop-summary.json"
$phase8Summary = Read-JsonFile -Path $phase8SummaryPath
$phase8AgeHours = [math]::Round(((Get-Date) - $latestPhase8.LastWriteTime).TotalHours, 2)
if ($phase8Summary -and ($phase8Summary.status -eq "PASS" -or $phase8Summary.status -eq "PASS_WITH_WEBHOOK_BLOCKED") -and [int]$phase8Summary.totals.failed_required -eq 0) {
Add-Check -Key "phase8_input" -Label "phase8 closed-loop input" -Status "pass" -Detail "Phase8 status=$($phase8Summary.status), age=${phase8AgeHours}h" -Meta @{ artifact_dir = $latestPhase8.FullName; summary = $phase8SummaryPath }
} elseif ($phase8Summary) {
Add-Check -Key "phase8_input" -Label "phase8 closed-loop input" -Status "fail" -Detail "Phase8 status=$($phase8Summary.status), failed_required=$($phase8Summary.totals.failed_required)" -Meta @{ artifact_dir = $latestPhase8.FullName; summary = $phase8SummaryPath }
} else {
Add-Check -Key "phase8_input" -Label "phase8 closed-loop input" -Status "fail" -Detail "Could not parse phase8 summary" -Meta @{ artifact_dir = $latestPhase8.FullName }
}
if ($phase8AgeHours -le $MaxPhase8AgeHours) {
Add-Check -Key "phase8_freshness" -Label "phase8 evidence freshness" -Status "pass" -Detail "Phase8 artifact age=${phase8AgeHours}h"
} else {
Add-Check -Key "phase8_freshness" -Label "phase8 evidence freshness" -Status "warn" -Required $false -Detail "Phase8 artifact age ${phase8AgeHours}h exceeds $MaxPhase8AgeHours"
}
$scenarioPath = Join-Path $latestPhase8.FullName "pilot-scenario-execution-record.csv"
$feedbackPath = Join-Path $latestPhase8.FullName "pilot-feedback-register.csv"
if (Test-Path -LiteralPath $scenarioPath -PathType Leaf) {
$scenarioRows = @(Import-Csv -LiteralPath $scenarioPath)
}
if (Test-Path -LiteralPath $feedbackPath -PathType Leaf) {
$feedbackRows = @(Import-Csv -LiteralPath $feedbackPath)
}
} else {
Add-Check -Key "phase8_input" -Label "phase8 closed-loop input" -Status "fail" -Detail "No phase8 closed-loop artifact found"
}
if ($scenarioRows.Count -gt 0 -and @($scenarioRows | Where-Object { $_.status -eq "PASS" }).Count -eq $scenarioRows.Count) {
Add-Check -Key "phase8_scenarios" -Label "phase8 scenarios" -Status "pass" -Detail "Phase8 scenarios passed=$($scenarioRows.Count)/$($scenarioRows.Count)"
} else {
Add-Check -Key "phase8_scenarios" -Label "phase8 scenarios" -Status "fail" -Detail "Phase8 scenario rows missing or failed"
}
$openP0P1 = @($feedbackRows | Where-Object { ($_.severity -eq "P0" -or $_.severity -eq "P1") -and ($_.status -ne "VERIFIED" -and $_.status -ne "WONT_FIX") })
if ($openP0P1.Count -eq 0) {
Add-Check -Key "open_p0_p1_feedback" -Label "open P0/P1 feedback" -Status "pass" -Detail "Open P0/P1 feedback=0"
} else {
Add-Check -Key "open_p0_p1_feedback" -Label "open P0/P1 feedback" -Status "fail" -Detail "Open P0/P1 feedback=$($openP0P1.Count)"
}
$scorecard = New-Object System.Collections.Generic.List[object]
$scorecard.Add((Write-DomainReport -Domain "review" -Title "Phase 9 Review Quality Evaluation" -FixtureRows $reviewFixtures -ScenarioIds @("PILOT-REVIEW-001", "PILOT-REVIEW-002", "PILOT-CHAT-001", "PILOT-NEG-002") -ScenarioRows $scenarioRows -RequiredColumns @("sample_id", "expected_risk_types", "expected_evidence", "expected_actionable_suggestion", "scenario_ids") -OutputPrefix "review-quality-eval" -ExtraDetail "Review quality uses phase8 review, review detail, chat context, and failure explanation scenarios.")) | Out-Null
$scorecard.Add((Write-DomainReport -Domain "kb" -Title "Phase 9 Knowledge Base Quality Evaluation" -FixtureRows $kbFixtures -ScenarioIds @("PILOT-KB-001", "PILOT-KB-002", "PILOT-NEG-002") -ScenarioRows $scenarioRows -RequiredColumns @("question_id", "expected_source_policy", "expected_answer_behavior", "scenario_ids") -OutputPrefix "kb-quality-eval" -ExtraDetail "KB quality uses indexed document, source policy, and refusal behavior fixtures.")) | Out-Null
$scorecard.Add((Write-DomainReport -Domain "contract_generation" -Title "Phase 9 Contract Generation Quality Evaluation" -FixtureRows $contractFixtures -ScenarioIds @("PILOT-GENERATE-001", "PILOT-NEG-002") -ScenarioRows $scenarioRows -RequiredColumns @("case_id", "required_variables", "expected_preview", "expected_download", "expected_file_asset", "scenario_ids") -OutputPrefix "contract-generation-quality-eval" -ExtraDetail "Contract generation quality uses template variable, preview, download, and file asset expectations.")) | Out-Null
foreach ($domain in $scorecard) {
if ($domain.status -eq "PASS") {
Add-Check -Key ("domain_" + $domain.domain) -Label ($domain.domain + " quality") -Status "pass" -Detail "Score=$($domain.score)" -Meta @{ report = $domain.report_md }
} else {
Add-Check -Key ("domain_" + $domain.domain) -Label ($domain.domain + " quality") -Status "fail" -Detail "Score=$($domain.score)" -Meta @{ report = $domain.report_md }
}
}
$promptColumnsOk = Test-CsvColumns -Rows $promptRegister -Columns @("prompt_id", "domain", "version", "status", "expected_schema", "uncertainty_policy", "rollback_policy")
$activePromptDomains = @($promptRegister | Where-Object { $_.status -eq "active" } | Select-Object -ExpandProperty domain -Unique)
$aiGovernanceText = Get-Content -LiteralPath (Join-Path $FixtureRoot "ai-output-governance.md") -Raw -Encoding UTF8
$aiGovernanceOk = $promptColumnsOk -and ($activePromptDomains -contains "review") -and ($activePromptDomains -contains "kb") -and ($activePromptDomains -contains "contract_generation") -and $aiGovernanceText.Contains("refuse") -and $aiGovernanceText.Contains("schema")
$aiGovernanceJson = Join-Path $artifactDir "ai-output-governance-summary.json"
$aiGovernanceMd = Join-Path $artifactDir "ai-output-governance-summary.md"
$aiGovernanceSummary = [ordered]@{
status = if ($aiGovernanceOk) { "PASS" } else { "FAIL" }
prompt_count = $promptRegister.Count
active_domains = $activePromptDomains
prompt_columns_ok = $promptColumnsOk
governance_rules_present = $aiGovernanceText.Contains("Required Rules")
}
Save-Json -Path $aiGovernanceJson -Value $aiGovernanceSummary
@(
"# Phase 9 AI Output Governance Summary",
"",
"- Status: $($aiGovernanceSummary.status)",
"- Prompt count: $($promptRegister.Count)",
"- Active domains: $($activePromptDomains -join ', ')",
"- Prompt columns OK: $promptColumnsOk"
) | Set-Content -Encoding UTF8 -Path $aiGovernanceMd
if ($aiGovernanceOk) {
Add-Check -Key "ai_output_governance" -Label "AI output governance" -Status "pass" -Detail "Prompt register and governance policy are complete" -Meta @{ report = $aiGovernanceMd }
} else {
Add-Check -Key "ai_output_governance" -Label "AI output governance" -Status "fail" -Detail "AI output governance is incomplete" -Meta @{ report = $aiGovernanceMd }
}
$retentionText = Get-Content -LiteralPath (Join-Path $FixtureRoot "data-retention-policy.md") -Raw -Encoding UTF8
$secret = [string]::Concat([char]54, [char]53, [char]52, [char]51, [char]50, [char]49)
$qualityFiles = Get-ChildItem -LiteralPath $FixtureRoot -File
$secretHits = @()
foreach ($file in $qualityFiles) {
$content = Get-Content -LiteralPath $file.FullName -Raw -Encoding UTF8
if ($content.Contains($secret)) {
$secretHits += $file.FullName
}
}
$phase8SampleInventory = Join-Path $repoRoot "project-docs\phase8-pilot-templates\pilot-sample-inventory.csv"
$sampleRows = if (Test-Path -LiteralPath $phase8SampleInventory -PathType Leaf) { @(Import-Csv -LiteralPath $phase8SampleInventory) } else { @() }
$sampleColumnsOk = Test-CsvColumns -Rows $sampleRows -Columns @("sample_id", "sensitivity_level", "allowed_environment", "desensitized")
$highProductionSamples = @($sampleRows | Where-Object { $_.sensitivity_level -eq "high" -and $_.allowed_environment -eq "production" })
$dataGovernanceOk = $retentionText.Contains("Data Classes") -and $sampleColumnsOk -and $highProductionSamples.Count -eq 0 -and $secretHits.Count -eq 0
$dataGovernanceJson = Join-Path $artifactDir "data-governance-summary.json"
$dataGovernanceMd = Join-Path $artifactDir "data-governance-summary.md"
$dataGovernanceSummary = [ordered]@{
status = if ($dataGovernanceOk) { "PASS" } else { "FAIL" }
sample_count = $sampleRows.Count
sample_columns_ok = $sampleColumnsOk
high_production_samples = $highProductionSamples.Count
secret_hits = $secretHits
retention_policy_present = $retentionText.Contains("Data Classes")
}
Save-Json -Path $dataGovernanceJson -Value $dataGovernanceSummary
@(
"# Phase 9 Data Governance Summary",
"",
"- Status: $($dataGovernanceSummary.status)",
"- Sample count: $($sampleRows.Count)",
"- Sample columns OK: $sampleColumnsOk",
"- High sensitivity production samples: $($highProductionSamples.Count)",
"- Secret hits: $($secretHits.Count)"
) | Set-Content -Encoding UTF8 -Path $dataGovernanceMd
if ($dataGovernanceOk) {
Add-Check -Key "data_governance" -Label "data governance" -Status "pass" -Detail "Retention, privacy, and sample governance passed" -Meta @{ report = $dataGovernanceMd }
} else {
Add-Check -Key "data_governance" -Label "data governance" -Status "fail" -Detail "Data governance check failed" -Meta @{ report = $dataGovernanceMd }
}
$scorecard | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $scorecardCsv
$defects = New-Object System.Collections.Generic.List[object]
foreach ($feedback in $feedbackRows) {
if ($feedback.status -eq "DEFERRED") {
$defects.Add([pscustomobject][ordered]@{
defect_id = "QDEF-" + $feedback.feedback_id
source_feedback_id = $feedback.feedback_id
severity = $feedback.severity
status = "DEFERRED"
domain = if ($feedback.scenario_id -like "*WEBHOOK*") { "ops_external" } elseif ($feedback.scenario_id -like "*ROLLBACK*") { "ops_evidence" } else { "quality" }
owner = $feedback.owner
evidence_path = $feedback.evidence_path
notes = $feedback.notes
}) | Out-Null
}
}
if ($defects.Count -eq 0) {
$defects.Add([pscustomobject][ordered]@{
defect_id = "QDEF-P9-000"
source_feedback_id = ""
severity = "P3"
status = "VERIFIED"
domain = "quality"
owner = "phase9-auto"
evidence_path = $artifactDir
notes = "No deferred quality defects"
}) | Out-Null
}
$defects | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $defectCsv
$failedRequired = @($checks | Where-Object { $_.required -and $_.status -eq "fail" })
$warningsList = @($checks | Where-Object { $_.status -eq "warn" })
$blockedList = @()
if ($phase8Summary -and $phase8Summary.status -eq "PASS_WITH_WEBHOOK_BLOCKED") {
$blockedList += [pscustomobject]@{ key = "external_webhook"; detail = "Inherited BLOCKED_EXTERNAL_WEBHOOK from phase8" }
}
$overallStatus = if ($failedRequired.Count -gt 0) {
"FAIL"
} elseif ($blockedList.Count -gt 0) {
"PASS_WITH_WEBHOOK_BLOCKED"
} elseif ($warningsList.Count -gt 0) {
"WARN"
} else {
"PASS"
}
$alertSeverity = if ($failedRequired.Count -gt 0) { "critical" } elseif ($blockedList.Count -gt 0) { "blocked" } elseif ($warningsList.Count -gt 0) { "warning" } else { "ok" }
$summary = [ordered]@{
name = "phase9-business-quality-gate"
status = $overallStatus
alert_severity = $alertSeverity
generated_at = (Get-Date).ToUniversalTime().ToString("o")
artifact_dir = $artifactDir
branch = $branch
commit = $commit
remote = $remoteUrl
phase8 = [ordered]@{
artifact_dir = if ($latestPhase8) { $latestPhase8.FullName } else { $null }
status = if ($phase8Summary) { $phase8Summary.status } else { $null }
age_hours = $phase8AgeHours
scenarios_total = $scenarioRows.Count
feedback_total = $feedbackRows.Count
open_p0_p1 = $openP0P1.Count
}
scorecard = $scorecard
checks = $checks
artifacts = [ordered]@{
scorecard_csv = $scorecardCsv
defect_register = $defectCsv
ai_output_governance_summary = $aiGovernanceMd
data_governance_summary = $dataGovernanceMd
}
totals = [ordered]@{
total = $checks.Count
failed_required = $failedRequired.Count
warnings = $warningsList.Count
blocked = $blockedList.Count
}
}
Save-Json -Path $summaryJson -Value $summary
$markdown = New-Object System.Collections.Generic.List[string]
$markdown.Add("# Phase 9 Business Quality Gate") | Out-Null
$markdown.Add("") | Out-Null
$markdown.Add("- Status: $overallStatus") | Out-Null
$markdown.Add("- Alert severity: $alertSeverity") | Out-Null
$markdown.Add("- Generated at: $($summary.generated_at)") | Out-Null
$markdown.Add("- Branch: $branch") | Out-Null
$markdown.Add("- Commit: $commit") | Out-Null
$markdown.Add("- Artifact dir: $artifactDir") | Out-Null
$markdown.Add("- Phase8 input: $($summary.phase8.artifact_dir)") | Out-Null
$markdown.Add("") | Out-Null
$markdown.Add("## Scorecard") | Out-Null
$markdown.Add("") | Out-Null
$markdown.Add("| Domain | Status | Score | Fixture Count |") | Out-Null
$markdown.Add("|---|---|---:|---:|") | Out-Null
foreach ($domain in $scorecard) {
$markdown.Add("| $($domain.domain) | $($domain.status) | $($domain.score) | $($domain.fixture_count) |") | Out-Null
}
$markdown.Add("") | Out-Null
$markdown.Add("## Checks") | Out-Null
$markdown.Add("") | Out-Null
$markdown.Add("| Check | Required | Status | Detail |") | Out-Null
$markdown.Add("|---|---:|---|---|") | Out-Null
foreach ($check in $checks) {
$markdown.Add("| $($check.label) | $($check.required) | $($check.status.ToUpperInvariant()) | $($check.detail) |") | Out-Null
}
$markdown.Add("") | Out-Null
$markdown.Add("Local artifacts:") | Out-Null
$markdown.Add("") | Out-Null
$markdown.Add('- `business-quality-gate-summary.json`') | Out-Null
$markdown.Add('- `business-quality-gate-summary.md`') | Out-Null
$markdown.Add('- `phase9-quality-scorecard.csv`') | Out-Null
$markdown.Add('- `phase9-quality-defect-register.csv`') | Out-Null
$markdown.Add('- `review-quality-eval.*`') | Out-Null
$markdown.Add('- `kb-quality-eval.*`') | Out-Null
$markdown.Add('- `contract-generation-quality-eval.*`') | Out-Null
$markdown.Add('- `ai-output-governance-summary.*`') | Out-Null
$markdown.Add('- `data-governance-summary.*`') | Out-Null
$markdown | Set-Content -Encoding UTF8 -Path $summaryMd
Write-Host "PHASE9_BUSINESS_QUALITY_GATE_$overallStatus $artifactDir"
if ($overallStatus -eq "FAIL" -or ($FailOnWarn -and $overallStatus -eq "WARN") -or ($FailOnExternalBlocked -and $overallStatus -eq "PASS_WITH_WEBHOOK_BLOCKED")) {
throw "Phase 9 business quality gate $overallStatus. See $summaryMd"
}