docs: start phase 13 legal knowledge library
This commit is contained in:
@@ -0,0 +1,466 @@
|
||||
param(
|
||||
[string]$ArtifactRoot = "",
|
||||
[string]$LibraryRoot = "",
|
||||
[int]$MinLegalSources = 20,
|
||||
[switch]$FailOnWarn,
|
||||
[switch]$FailOnLegalReviewRequired
|
||||
)
|
||||
|
||||
$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-legal-knowledge-library-gate"
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($LibraryRoot)) {
|
||||
$LibraryRoot = Join-Path $repoRoot "project-docs\phase13-legal-knowledge-template-library"
|
||||
}
|
||||
foreach ($pathName in @("ArtifactRoot", "LibraryRoot")) {
|
||||
$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 "legal-knowledge-library-gate-summary.json"
|
||||
$summaryMd = Join-Path $artifactDir "legal-knowledge-library-gate-summary.md"
|
||||
$gitStatusPath = Join-Path $artifactDir "git-status.txt"
|
||||
$sourceReportCsv = Join-Path $artifactDir "phase13-legal-source-report.csv"
|
||||
$inventoryReportCsv = Join-Path $artifactDir "phase13-inventory-schema-report.csv"
|
||||
|
||||
$checks = New-Object System.Collections.Generic.List[object]
|
||||
$inventoryReport = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
$allowedDomains = @(
|
||||
"flk.npc.gov.cn",
|
||||
"www.npc.gov.cn",
|
||||
"www.court.gov.cn",
|
||||
"gongbao.court.gov.cn",
|
||||
"www.gov.cn",
|
||||
"www.moj.gov.cn",
|
||||
"www.samr.gov.cn",
|
||||
"www.cac.gov.cn",
|
||||
"www.mohrss.gov.cn",
|
||||
"www.cnipa.gov.cn"
|
||||
)
|
||||
|
||||
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 20 | Set-Content -Encoding UTF8 -Path $Path
|
||||
}
|
||||
|
||||
function Get-CsvHeader {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
||||
return @()
|
||||
}
|
||||
$firstLine = Get-Content -LiteralPath $Path -Encoding UTF8 -TotalCount 1
|
||||
if ([string]::IsNullOrWhiteSpace($firstLine)) {
|
||||
return @()
|
||||
}
|
||||
return @($firstLine.Split(",") | ForEach-Object { $_.Trim() })
|
||||
}
|
||||
|
||||
function Test-CsvHeader {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string[]]$Columns
|
||||
)
|
||||
$header = Get-CsvHeader -Path $Path
|
||||
$missing = @($Columns | Where-Object { $header -notcontains $_ })
|
||||
return [pscustomobject]@{
|
||||
ok = ($missing.Count -eq 0)
|
||||
missing = $missing
|
||||
header = $header
|
||||
}
|
||||
}
|
||||
|
||||
function Import-CsvRows {
|
||||
param([string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
||||
return @()
|
||||
}
|
||||
return @(Import-Csv -LiteralPath $Path)
|
||||
}
|
||||
|
||||
function Test-RequiredFlag {
|
||||
param([string]$Value)
|
||||
if ([string]::IsNullOrWhiteSpace($Value)) {
|
||||
return $false
|
||||
}
|
||||
return @("true", "yes", "1", "required") -contains $Value.ToLowerInvariant()
|
||||
}
|
||||
|
||||
function Test-AllowedOfficialUrl {
|
||||
param([string]$Url)
|
||||
if ([string]::IsNullOrWhiteSpace($Url)) {
|
||||
return $false
|
||||
}
|
||||
try {
|
||||
$uri = [System.Uri]$Url
|
||||
return $allowedDomains -contains $uri.Host.ToLowerInvariant()
|
||||
} catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Add-RequiredFileCheck {
|
||||
param(
|
||||
[string]$Key,
|
||||
[string]$Label,
|
||||
[string]$RelativePath,
|
||||
[string[]]$Columns
|
||||
)
|
||||
$path = Join-Path $repoRoot $RelativePath
|
||||
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) {
|
||||
Add-Check -Key $Key -Label $Label -Status "fail" -Detail "Missing file: $RelativePath" -Meta @{ path = $path }
|
||||
$inventoryReport.Add([pscustomobject][ordered]@{
|
||||
key = $Key
|
||||
label = $Label
|
||||
path = $RelativePath
|
||||
exists = $false
|
||||
schema_ok = $false
|
||||
rows = ""
|
||||
missing_columns = "file_missing"
|
||||
}) | Out-Null
|
||||
return @()
|
||||
}
|
||||
|
||||
$schema = Test-CsvHeader -Path $path -Columns $Columns
|
||||
$rows = Import-CsvRows -Path $path
|
||||
$inventoryReport.Add([pscustomobject][ordered]@{
|
||||
key = $Key
|
||||
label = $Label
|
||||
path = $RelativePath
|
||||
exists = $true
|
||||
schema_ok = $schema.ok
|
||||
rows = $rows.Count
|
||||
missing_columns = ($schema.missing -join ";")
|
||||
}) | Out-Null
|
||||
|
||||
if ($schema.ok) {
|
||||
Add-Check -Key $Key -Label $Label -Status "pass" -Detail "Schema OK, rows=$($rows.Count)" -Meta @{ path = $path }
|
||||
} else {
|
||||
Add-Check -Key $Key -Label $Label -Status "fail" -Detail "Missing columns: $($schema.missing -join ',')" -Meta @{ path = $path; missing = $schema.missing }
|
||||
}
|
||||
return $rows
|
||||
}
|
||||
|
||||
function Test-SecretLeak {
|
||||
param([string]$Root)
|
||||
$adminPasswordLiteral = [string]::Concat([char]54, [char]53, [char]52, [char]51, [char]50, [char]49)
|
||||
$hits = New-Object System.Collections.Generic.List[object]
|
||||
$files = @(Get-ChildItem -LiteralPath $Root -Recurse -File -ErrorAction SilentlyContinue)
|
||||
$scriptPath = Join-Path $repoRoot "scripts\run-legal-knowledge-library-gate.ps1"
|
||||
if (Test-Path -LiteralPath $scriptPath -PathType Leaf) {
|
||||
$files += Get-Item -LiteralPath $scriptPath
|
||||
}
|
||||
foreach ($file in $files) {
|
||||
$match = Select-String -LiteralPath $file.FullName -Pattern $adminPasswordLiteral -List -ErrorAction SilentlyContinue
|
||||
if ($match) {
|
||||
$hits.Add([pscustomobject]@{ path = $file.FullName; pattern = "admin_password_literal" }) | Out-Null
|
||||
}
|
||||
}
|
||||
return $hits.ToArray()
|
||||
}
|
||||
|
||||
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 ($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 ($gitStatus) {
|
||||
Add-Check -Key "working_tree" -Label "working tree" -Status "warn" -Required $false -Detail "Working tree has uncommitted changes; 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 (Test-Path -LiteralPath $LibraryRoot -PathType Container) {
|
||||
Add-Check -Key "library_root" -Label "phase13 library root" -Status "pass" -Detail "Library root exists" -Meta @{ root = $LibraryRoot }
|
||||
} else {
|
||||
Add-Check -Key "library_root" -Label "phase13 library root" -Status "fail" -Detail "Library root is missing" -Meta @{ root = $LibraryRoot }
|
||||
}
|
||||
|
||||
$requiredDirs = @(
|
||||
"project-docs\phase13-legal-knowledge-template-library\inventories",
|
||||
"project-docs\phase13-legal-knowledge-template-library\legal-sources\civil-code",
|
||||
"project-docs\phase13-legal-knowledge-template-library\legal-sources\judicial-interpretations",
|
||||
"project-docs\phase13-legal-knowledge-template-library\legal-sources\company-commercial",
|
||||
"project-docs\phase13-legal-knowledge-template-library\legal-sources\labor-employment",
|
||||
"project-docs\phase13-legal-knowledge-template-library\legal-sources\data-privacy",
|
||||
"project-docs\phase13-legal-knowledge-template-library\legal-sources\intellectual-property",
|
||||
"project-docs\phase13-legal-knowledge-template-library\legal-sources\procurement-tendering",
|
||||
"project-docs\phase13-legal-knowledge-template-library\clause-library\common",
|
||||
"project-docs\phase13-legal-knowledge-template-library\contract-templates\purchase",
|
||||
"project-docs\phase13-legal-knowledge-template-library\qa-samples\review",
|
||||
"project-docs\phase13-legal-knowledge-template-library\evidence"
|
||||
)
|
||||
|
||||
$missingDirs = @($requiredDirs | Where-Object { -not (Test-Path -LiteralPath (Join-Path $repoRoot $_) -PathType Container) })
|
||||
if ($missingDirs.Count -eq 0) {
|
||||
Add-Check -Key "directory_structure" -Label "directory structure" -Status "pass" -Detail "Required directories exist"
|
||||
} else {
|
||||
Add-Check -Key "directory_structure" -Label "directory structure" -Status "fail" -Detail "Missing directories: $($missingDirs -join ',')" -Meta @{ missing = $missingDirs }
|
||||
}
|
||||
|
||||
$commonDocFiles = @(
|
||||
"project-docs\phase13-legal-knowledge-template-library\README.md",
|
||||
"project-docs\phase13-legal-knowledge-template-library\governance-rules.md",
|
||||
"project-docs\phase13-legal-knowledge-template-library\field-dictionary.md"
|
||||
)
|
||||
foreach ($docPath in $commonDocFiles) {
|
||||
$absoluteDocPath = Join-Path $repoRoot $docPath
|
||||
$key = "doc_" + ([System.IO.Path]::GetFileNameWithoutExtension($docPath) -replace "[^A-Za-z0-9]+", "_")
|
||||
if (Test-Path -LiteralPath $absoluteDocPath -PathType Leaf) {
|
||||
Add-Check -Key $key -Label $docPath -Status "pass" -Detail "Document exists" -Meta @{ path = $absoluteDocPath }
|
||||
} else {
|
||||
Add-Check -Key $key -Label $docPath -Status "fail" -Detail "Document missing" -Meta @{ path = $absoluteDocPath }
|
||||
}
|
||||
}
|
||||
|
||||
$legalSourceColumns = @("source_id", "title", "source_type", "issuer", "official_domain", "source_url", "retrieval_hint", "effective_date", "validity_status", "contract_scope", "knowledge_domain", "review_required", "status", "owner", "legal_reviewer", "collection_date", "notes")
|
||||
$knowledgeCardColumns = @("knowledge_card_id", "title", "source_ids", "legal_question", "applicable_scenarios", "non_applicable_scenarios", "summary_path", "original_reference", "answer_boundary", "status", "owner", "legal_reviewer", "review_required", "production_ready", "notes")
|
||||
$riskRuleColumns = @("rule_id", "risk_category", "risk_name", "severity", "source_ids", "clause_ids", "trigger_condition", "review_output_template", "modification_suggestion_path", "human_review_required", "status", "owner", "legal_reviewer", "review_sample_ids", "production_ready", "notes")
|
||||
$clauseColumns = @("clause_id", "clause_name", "contract_type", "clause_category", "source_ids", "applicable_conditions", "excluded_conditions", "variant_group", "risk_notes_path", "clause_path", "status", "owner", "legal_reviewer", "business_reviewer", "review_required", "production_ready", "notes")
|
||||
$templateColumns = @("template_id", "template_name", "contract_type", "template_path", "variable_schema_path", "required_variables", "optional_variables", "clause_ids", "review_rule_ids", "generation_sample_ids", "status", "owner", "legal_reviewer", "business_reviewer", "review_required", "production_ready", "notes")
|
||||
$reviewSampleColumns = @("sample_id", "contract_type", "risk_categories", "source_ids", "input_sample_path", "expected_findings_path", "expected_severity", "expected_source_citations", "status", "owner", "legal_reviewer", "review_required", "production_ready", "notes")
|
||||
$generationSampleColumns = @("sample_id", "template_id", "contract_type", "variable_payload_path", "expected_document_path", "expected_review_rule_ids", "expected_source_citations", "status", "owner", "legal_reviewer", "business_reviewer", "review_required", "production_ready", "notes")
|
||||
$kbQaSampleColumns = @("sample_id", "question", "expected_answer_boundary", "expected_source_ids", "expected_citation_required", "expected_refusal_required", "status", "owner", "legal_reviewer", "review_required", "production_ready", "notes")
|
||||
$expertReviewColumns = @("review_id", "asset_type", "asset_id", "reviewer", "reviewer_role", "review_date", "review_result", "required_changes", "follow_up_owner", "evidence_path", "status", "notes")
|
||||
|
||||
$legalSourceRows = Add-RequiredFileCheck -Key "inventory_legal_sources" -Label "legal source inventory" -RelativePath "project-docs\phase13-legal-knowledge-template-library\inventories\legal-source-inventory.csv" -Columns $legalSourceColumns
|
||||
Add-RequiredFileCheck -Key "inventory_knowledge_cards" -Label "legal knowledge card inventory" -RelativePath "project-docs\phase13-legal-knowledge-template-library\inventories\legal-knowledge-card-inventory.csv" -Columns $knowledgeCardColumns | Out-Null
|
||||
Add-RequiredFileCheck -Key "inventory_risk_rules" -Label "review risk rule inventory" -RelativePath "project-docs\phase13-legal-knowledge-template-library\inventories\review-risk-rule-inventory.csv" -Columns $riskRuleColumns | Out-Null
|
||||
Add-RequiredFileCheck -Key "inventory_clauses" -Label "clause library inventory" -RelativePath "project-docs\phase13-legal-knowledge-template-library\inventories\clause-library-inventory.csv" -Columns $clauseColumns | Out-Null
|
||||
Add-RequiredFileCheck -Key "inventory_templates" -Label "contract template inventory" -RelativePath "project-docs\phase13-legal-knowledge-template-library\inventories\contract-template-inventory.csv" -Columns $templateColumns | Out-Null
|
||||
Add-RequiredFileCheck -Key "inventory_review_samples" -Label "review golden sample inventory" -RelativePath "project-docs\phase13-legal-knowledge-template-library\inventories\review-golden-sample-inventory.csv" -Columns $reviewSampleColumns | Out-Null
|
||||
Add-RequiredFileCheck -Key "inventory_generation_samples" -Label "generation golden sample inventory" -RelativePath "project-docs\phase13-legal-knowledge-template-library\inventories\generation-golden-sample-inventory.csv" -Columns $generationSampleColumns | Out-Null
|
||||
Add-RequiredFileCheck -Key "inventory_kb_qa_samples" -Label "KB QA golden sample inventory" -RelativePath "project-docs\phase13-legal-knowledge-template-library\inventories\kb-qa-golden-sample-inventory.csv" -Columns $kbQaSampleColumns | Out-Null
|
||||
Add-RequiredFileCheck -Key "inventory_expert_reviews" -Label "expert review register" -RelativePath "project-docs\phase13-legal-knowledge-template-library\inventories\expert-review-register.csv" -Columns $expertReviewColumns | Out-Null
|
||||
|
||||
if ($legalSourceRows.Count -ge $MinLegalSources) {
|
||||
Add-Check -Key "legal_source_minimum" -Label "legal source minimum" -Status "pass" -Detail "Legal sources=$($legalSourceRows.Count), minimum=$MinLegalSources"
|
||||
} else {
|
||||
Add-Check -Key "legal_source_minimum" -Label "legal source minimum" -Status "fail" -Detail "Legal sources=$($legalSourceRows.Count), minimum=$MinLegalSources"
|
||||
}
|
||||
|
||||
$duplicateSourceIds = @($legalSourceRows | Group-Object source_id | Where-Object { $_.Count -gt 1 } | Select-Object -ExpandProperty Name)
|
||||
if ($duplicateSourceIds.Count -eq 0) {
|
||||
Add-Check -Key "legal_source_ids_unique" -Label "legal source IDs unique" -Status "pass" -Detail "No duplicate legal source IDs"
|
||||
} else {
|
||||
Add-Check -Key "legal_source_ids_unique" -Label "legal source IDs unique" -Status "fail" -Detail "Duplicate IDs: $($duplicateSourceIds -join ',')" -Meta @{ duplicates = $duplicateSourceIds }
|
||||
}
|
||||
|
||||
$missingRequiredFields = @($legalSourceRows | Where-Object {
|
||||
[string]::IsNullOrWhiteSpace($_.source_id) -or
|
||||
[string]::IsNullOrWhiteSpace($_.title) -or
|
||||
[string]::IsNullOrWhiteSpace($_.issuer) -or
|
||||
[string]::IsNullOrWhiteSpace($_.source_url) -or
|
||||
[string]::IsNullOrWhiteSpace($_.retrieval_hint) -or
|
||||
[string]::IsNullOrWhiteSpace($_.status) -or
|
||||
[string]::IsNullOrWhiteSpace($_.owner)
|
||||
} | Select-Object -ExpandProperty source_id)
|
||||
if ($missingRequiredFields.Count -eq 0) {
|
||||
Add-Check -Key "legal_source_required_fields" -Label "legal source required fields" -Status "pass" -Detail "Required fields are populated"
|
||||
} else {
|
||||
Add-Check -Key "legal_source_required_fields" -Label "legal source required fields" -Status "fail" -Detail "Rows missing required fields: $($missingRequiredFields -join ',')" -Meta @{ rows = $missingRequiredFields }
|
||||
}
|
||||
|
||||
$nonOfficialUrls = @($legalSourceRows | Where-Object { -not (Test-AllowedOfficialUrl -Url $_.source_url) } | Select-Object -ExpandProperty source_id)
|
||||
if ($nonOfficialUrls.Count -eq 0) {
|
||||
Add-Check -Key "official_source_urls" -Label "official source URLs" -Status "pass" -Detail "All legal source URLs use official allowlisted domains"
|
||||
} else {
|
||||
Add-Check -Key "official_source_urls" -Label "official source URLs" -Status "fail" -Detail "Non-allowlisted source URLs: $($nonOfficialUrls -join ',')" -Meta @{ rows = $nonOfficialUrls; allowed_domains = $allowedDomains }
|
||||
}
|
||||
|
||||
$unknownValidityRows = @($legalSourceRows | Where-Object { $_.validity_status -eq "VERIFY_REQUIRED" -or $_.effective_date -eq "VERIFY_REQUIRED" } | Select-Object -ExpandProperty source_id)
|
||||
if ($unknownValidityRows.Count -gt 0) {
|
||||
Add-Check -Key "legal_review_required" -Label "legal review required" -Status "warn" -Required $false -Detail "Legal source validity still requires legal review: $($unknownValidityRows.Count) rows" -Meta @{ rows = $unknownValidityRows }
|
||||
} else {
|
||||
Add-Check -Key "legal_review_required" -Label "legal review required" -Status "pass" -Detail "Legal source validity is verified"
|
||||
}
|
||||
|
||||
$publishedWithoutReview = @($legalSourceRows | Where-Object {
|
||||
@("APPROVED", "PUBLISHED") -contains $_.status -and
|
||||
([string]::IsNullOrWhiteSpace($_.legal_reviewer) -or $_.legal_reviewer -eq "pending" -or $_.validity_status -eq "VERIFY_REQUIRED")
|
||||
} | Select-Object -ExpandProperty source_id)
|
||||
if ($publishedWithoutReview.Count -eq 0) {
|
||||
Add-Check -Key "no_unreviewed_publication" -Label "no unreviewed publication" -Status "pass" -Detail "No legal source is published without review"
|
||||
} else {
|
||||
Add-Check -Key "no_unreviewed_publication" -Label "no unreviewed publication" -Status "fail" -Detail "Published/approved rows missing review: $($publishedWithoutReview -join ',')" -Meta @{ rows = $publishedWithoutReview }
|
||||
}
|
||||
|
||||
$statusValues = @("DRAFT", "LEGAL_REVIEW", "BUSINESS_REVIEW", "APPROVED", "PUBLISHED", "DEPRECATED")
|
||||
$invalidStatuses = @($legalSourceRows | Where-Object { $statusValues -notcontains $_.status } | Select-Object -ExpandProperty source_id)
|
||||
if ($invalidStatuses.Count -eq 0) {
|
||||
Add-Check -Key "legal_source_status_values" -Label "legal source status values" -Status "pass" -Detail "Legal source statuses are allowed"
|
||||
} else {
|
||||
Add-Check -Key "legal_source_status_values" -Label "legal source status values" -Status "fail" -Detail "Invalid statuses: $($invalidStatuses -join ',')" -Meta @{ rows = $invalidStatuses; allowed = $statusValues }
|
||||
}
|
||||
|
||||
$domainsCovered = @($legalSourceRows | Select-Object -ExpandProperty knowledge_domain -Unique)
|
||||
$requiredDomains = @("civil-code", "judicial-interpretations", "company-commercial", "labor-employment", "data-privacy", "intellectual-property", "procurement-tendering")
|
||||
$missingKnowledgeDomains = @($requiredDomains | Where-Object { $domainsCovered -notcontains $_ })
|
||||
if ($missingKnowledgeDomains.Count -eq 0) {
|
||||
Add-Check -Key "knowledge_domain_coverage" -Label "knowledge domain coverage" -Status "pass" -Detail "Domains covered: $($domainsCovered -join ',')"
|
||||
} else {
|
||||
Add-Check -Key "knowledge_domain_coverage" -Label "knowledge domain coverage" -Status "fail" -Detail "Missing knowledge domains: $($missingKnowledgeDomains -join ',')" -Meta @{ missing = $missingKnowledgeDomains }
|
||||
}
|
||||
|
||||
$secretHits = Test-SecretLeak -Root $LibraryRoot
|
||||
if ($secretHits.Count -eq 0) {
|
||||
Add-Check -Key "secret_scan" -Label "secret scan" -Status "pass" -Detail "No administrator password literal found in Phase 13 package or gate script"
|
||||
} else {
|
||||
Add-Check -Key "secret_scan" -Label "secret scan" -Status "fail" -Detail "Sensitive literal found" -Meta @{ hits = $secretHits }
|
||||
}
|
||||
|
||||
$legalSourceRows |
|
||||
Select-Object source_id,title,issuer,official_domain,source_url,effective_date,validity_status,status,legal_reviewer,knowledge_domain |
|
||||
Export-Csv -LiteralPath $sourceReportCsv -NoTypeInformation -Encoding UTF8
|
||||
$inventoryReport | Export-Csv -LiteralPath $inventoryReportCsv -NoTypeInformation -Encoding UTF8
|
||||
|
||||
$failedRequired = @($checks | Where-Object { $_.required -and $_.status -eq "fail" }).Count
|
||||
$warnings = @($checks | Where-Object { $_.status -eq "warn" }).Count
|
||||
$legalReviewRequired = $unknownValidityRows.Count
|
||||
|
||||
$overallStatus = if ($failedRequired -gt 0) {
|
||||
"FAIL"
|
||||
} elseif ($legalReviewRequired -gt 0) {
|
||||
"PASS_WITH_LEGAL_REVIEW_REQUIRED"
|
||||
} elseif ($warnings -gt 0) {
|
||||
"WARN"
|
||||
} else {
|
||||
"PASS"
|
||||
}
|
||||
$alertSeverity = if ($overallStatus -eq "FAIL") {
|
||||
"critical"
|
||||
} elseif ($overallStatus -eq "PASS_WITH_LEGAL_REVIEW_REQUIRED") {
|
||||
"review_required"
|
||||
} elseif ($overallStatus -eq "WARN") {
|
||||
"warning"
|
||||
} else {
|
||||
"none"
|
||||
}
|
||||
|
||||
$summary = [ordered]@{
|
||||
name = "phase13-legal-knowledge-library-gate"
|
||||
status = $overallStatus
|
||||
alert_severity = $alertSeverity
|
||||
generated_at = (Get-Date).ToUniversalTime().ToString("o")
|
||||
artifact_dir = $artifactDir
|
||||
branch = $branch
|
||||
commit = $commit
|
||||
remote = $remoteUrl
|
||||
library_root = $LibraryRoot
|
||||
checks = $checks.ToArray()
|
||||
metrics = [ordered]@{
|
||||
legal_sources = $legalSourceRows.Count
|
||||
allowed_domains = $allowedDomains.Count
|
||||
knowledge_domains = $domainsCovered.Count
|
||||
legal_review_required = $legalReviewRequired
|
||||
inventory_files = $inventoryReport.Count
|
||||
}
|
||||
artifacts = [ordered]@{
|
||||
legal_source_report = $sourceReportCsv
|
||||
inventory_schema_report = $inventoryReportCsv
|
||||
}
|
||||
totals = [ordered]@{
|
||||
total = $checks.Count
|
||||
failed_required = $failedRequired
|
||||
warnings = $warnings
|
||||
legal_review_required = $legalReviewRequired
|
||||
}
|
||||
}
|
||||
Save-Json -Path $summaryJson -Value $summary
|
||||
|
||||
$md = New-Object System.Collections.Generic.List[string]
|
||||
$md.Add("# Phase 13 Legal Knowledge Library Gate") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("- Status: $overallStatus") | Out-Null
|
||||
$md.Add("- Alert severity: $alertSeverity") | Out-Null
|
||||
$md.Add("- Generated at: $($summary.generated_at)") | Out-Null
|
||||
$md.Add("- Branch: $branch") | Out-Null
|
||||
$md.Add("- Commit: $commit") | Out-Null
|
||||
$md.Add("- Artifact dir: $artifactDir") | Out-Null
|
||||
$md.Add("- Library root: $LibraryRoot") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("## Metrics") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("| Metric | Count |") | Out-Null
|
||||
$md.Add("|---|---:|") | Out-Null
|
||||
$md.Add("| Legal sources | $($legalSourceRows.Count) |") | Out-Null
|
||||
$md.Add("| Allowed official domains | $($allowedDomains.Count) |") | Out-Null
|
||||
$md.Add("| Knowledge domains | $($domainsCovered.Count) |") | Out-Null
|
||||
$md.Add("| Inventory files | $($inventoryReport.Count) |") | Out-Null
|
||||
$md.Add("| Legal review required rows | $legalReviewRequired |") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("## Checks") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("| Check | Required | Status | Detail |") | Out-Null
|
||||
$md.Add("|---|---:|---|---|") | Out-Null
|
||||
foreach ($check in $checks) {
|
||||
$detail = ([string]$check.detail).Replace("|", "/")
|
||||
$md.Add("| $($check.label) | $($check.required) | $($check.status.ToUpperInvariant()) | $detail |") | Out-Null
|
||||
}
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add("Local artifacts:") | Out-Null
|
||||
$md.Add("") | Out-Null
|
||||
$md.Add('- `legal-knowledge-library-gate-summary.json`') | Out-Null
|
||||
$md.Add('- `legal-knowledge-library-gate-summary.md`') | Out-Null
|
||||
$md.Add('- `phase13-legal-source-report.csv`') | Out-Null
|
||||
$md.Add('- `phase13-inventory-schema-report.csv`') | Out-Null
|
||||
$md | Set-Content -Encoding UTF8 -Path $summaryMd
|
||||
|
||||
if ($overallStatus -eq "FAIL") {
|
||||
Write-Host "PHASE13_LEGAL_KNOWLEDGE_LIBRARY_GATE_FAIL $artifactDir"
|
||||
throw "Phase 13 legal knowledge library gate failed. See $summaryMd"
|
||||
}
|
||||
if ($overallStatus -eq "WARN" -and $FailOnWarn) {
|
||||
Write-Host "PHASE13_LEGAL_KNOWLEDGE_LIBRARY_GATE_WARN $artifactDir"
|
||||
throw "Phase 13 legal knowledge library gate has warnings. See $summaryMd"
|
||||
}
|
||||
if ($overallStatus -eq "PASS_WITH_LEGAL_REVIEW_REQUIRED" -and $FailOnLegalReviewRequired) {
|
||||
Write-Host "PHASE13_LEGAL_KNOWLEDGE_LIBRARY_GATE_PASS_WITH_LEGAL_REVIEW_REQUIRED $artifactDir"
|
||||
throw "Phase 13 legal knowledge library gate requires legal review. See $summaryMd"
|
||||
}
|
||||
|
||||
Write-Host "PHASE13_LEGAL_KNOWLEDGE_LIBRARY_GATE_$overallStatus $artifactDir"
|
||||
Reference in New Issue
Block a user