612 lines
31 KiB
PowerShell
612 lines
31 KiB
PowerShell
param(
|
|
[string]$ArtifactRoot = "",
|
|
[int]$MinReviewTasks = 90,
|
|
[int]$MinApprovedReviewTasks = 31,
|
|
[int]$MinApprovedMaterials = 7,
|
|
[int]$MinApprovedTemplates = 5,
|
|
[int]$MinApprovedKnowledgeCards = 10,
|
|
[int]$MinApprovedReviewRules = 8,
|
|
[int]$MinApprovedClauses = 8,
|
|
[string]$TenantId = "tenant-demo-001",
|
|
[string]$DatabaseUrl = "",
|
|
[ValidateSet("none", "sqlite_governance")]
|
|
[string]$DatabaseBootstrapMode = "none",
|
|
[switch]$SkipPreparationGate,
|
|
[switch]$SkipDatabaseState,
|
|
[switch]$RequireDatabaseTruth,
|
|
[switch]$FailOnBlocked
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function Get-RepoRoot {
|
|
$root = (& git rev-parse --show-toplevel 2>$null)
|
|
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($root)) {
|
|
throw "unable to resolve git repository root"
|
|
}
|
|
return $root.Trim()
|
|
}
|
|
|
|
function New-Utf8NoBomEncoding {
|
|
return New-Object System.Text.UTF8Encoding($false)
|
|
}
|
|
|
|
function Write-Utf8File {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Path,
|
|
[Parameter(Mandatory = $true)][AllowEmptyString()][string]$Text
|
|
)
|
|
[System.IO.File]::WriteAllText($Path, $Text, (New-Utf8NoBomEncoding))
|
|
}
|
|
|
|
function Import-CsvRows {
|
|
param([Parameter(Mandatory = $true)][string]$Path)
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
return @()
|
|
}
|
|
return @(Import-Csv -LiteralPath $Path)
|
|
}
|
|
|
|
function Test-TrueValue {
|
|
param([AllowNull()][string]$Value)
|
|
return @("true", "1", "yes", "y") -contains ([string]$Value).Trim().ToLowerInvariant()
|
|
}
|
|
|
|
function Test-ApprovedValue {
|
|
param([AllowNull()][string]$Value)
|
|
$normalized = ([string]$Value).Trim().ToUpperInvariant()
|
|
return @("APPROVED", "SIGNED", "PASS", "PRODUCTION_READY") -contains $normalized
|
|
}
|
|
|
|
function Test-AssignedReviewer {
|
|
param([AllowNull()][string]$Value)
|
|
$normalized = ([string]$Value).Trim().ToUpperInvariant()
|
|
if ([string]::IsNullOrWhiteSpace($normalized)) {
|
|
return $false
|
|
}
|
|
return -not (@("TBD", "TBD_REAL_REVIEWER", "PENDING", "UNASSIGNED") -contains $normalized)
|
|
}
|
|
|
|
function Test-EvidenceAvailable {
|
|
param([AllowNull()][string]$Value)
|
|
$path = ([string]$Value).Trim()
|
|
if ([string]::IsNullOrWhiteSpace($path)) {
|
|
return $false
|
|
}
|
|
if ($path -match "^https?://") {
|
|
return $true
|
|
}
|
|
if ([System.IO.Path]::IsPathRooted($path)) {
|
|
if (-not (Test-Path -LiteralPath $path)) {
|
|
return $false
|
|
}
|
|
return (Test-EvidenceContentFinal -Path $path)
|
|
}
|
|
$resolvedPath = Join-Path $repoRoot $path
|
|
if (-not (Test-Path -LiteralPath $resolvedPath)) {
|
|
return $false
|
|
}
|
|
return (Test-EvidenceContentFinal -Path $resolvedPath)
|
|
}
|
|
|
|
function Test-EvidenceContentFinal {
|
|
param([Parameter(Mandatory = $true)][string]$Path)
|
|
$item = Get-Item -LiteralPath $Path -ErrorAction Stop
|
|
if ($item.PSIsContainer) {
|
|
return $false
|
|
}
|
|
$content = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
|
|
if ($content -match "EVIDENCE_STATUS:\s*TEMPLATE") {
|
|
return $false
|
|
}
|
|
if ($content -match "TODO_REAL_REVIEWER") {
|
|
return $false
|
|
}
|
|
return $true
|
|
}
|
|
|
|
function Get-NumericPropertyValue {
|
|
param(
|
|
[object]$Object,
|
|
[string]$PropertyName,
|
|
[int]$Default = 0
|
|
)
|
|
if ($null -eq $Object) {
|
|
return $Default
|
|
}
|
|
$property = $Object.PSObject.Properties[$PropertyName]
|
|
if ($null -eq $property) {
|
|
return $Default
|
|
}
|
|
return [int]$property.Value
|
|
}
|
|
|
|
function Add-Check {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Key,
|
|
[Parameter(Mandatory = $true)][string]$Label,
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateSet("pass", "fail", "warn", "skip", "blocked")]
|
|
[string]$Status,
|
|
[string]$Detail = "",
|
|
[bool]$Required = $true,
|
|
[hashtable]$Meta = @{}
|
|
)
|
|
|
|
$script:checks.Add([ordered]@{
|
|
key = $Key
|
|
label = $Label
|
|
status = $Status
|
|
required = $Required
|
|
detail = $Detail
|
|
meta = $Meta
|
|
}) | Out-Null
|
|
}
|
|
|
|
function Invoke-PreparationGate {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$RepoRoot,
|
|
[Parameter(Mandatory = $true)][string]$ArtifactDir
|
|
)
|
|
|
|
$scriptPath = Join-Path $RepoRoot "scripts\run-formal-launch-preparation-gate.ps1"
|
|
$logPath = Join-Path $ArtifactDir "formal-launch-preparation-gate.log"
|
|
$childArtifact = Join-Path $ArtifactDir "formal-launch-preparation"
|
|
|
|
if (-not (Test-Path -LiteralPath $scriptPath)) {
|
|
Add-Check -Key "preparation_gate" -Label "formal launch preparation gate" -Status "fail" -Detail "missing preparation gate script" -Meta @{ log = $logPath }
|
|
return
|
|
}
|
|
|
|
$output = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $scriptPath -ArtifactRoot $childArtifact 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
Write-Utf8File -Path $logPath -Text ($output -join [Environment]::NewLine)
|
|
|
|
$statusLine = @($output | Where-Object { $_ -match "^FORMAL_LAUNCH_PREPARATION_GATE_" } | Select-Object -Last 1)
|
|
$status = "UNKNOWN"
|
|
if ($statusLine.Count -gt 0 -and $statusLine[-1] -match "^FORMAL_LAUNCH_PREPARATION_GATE_([A-Z_]+)") {
|
|
$status = $Matches[1]
|
|
}
|
|
|
|
if ($exitCode -ne 0) {
|
|
Add-Check -Key "preparation_gate" -Label "formal launch preparation gate" -Status "fail" -Detail "child failed: status=$status exit=$exitCode" -Meta @{ log = $logPath; artifact = $childArtifact }
|
|
} elseif ($status -eq "PASS") {
|
|
Add-Check -Key "preparation_gate" -Label "formal launch preparation gate" -Status "pass" -Detail "preparation gate passed" -Meta @{ log = $logPath; artifact = $childArtifact }
|
|
} elseif ($status -eq "WARN") {
|
|
Add-Check -Key "preparation_gate" -Label "formal launch preparation gate" -Status "warn" -Detail "preparation gate warned" -Meta @{ log = $logPath; artifact = $childArtifact }
|
|
} elseif ($status -eq "BLOCKED") {
|
|
Add-Check -Key "preparation_gate" -Label "formal launch preparation gate" -Status "blocked" -Detail "preparation gate blocked" -Meta @{ log = $logPath; artifact = $childArtifact }
|
|
} else {
|
|
Add-Check -Key "preparation_gate" -Label "formal launch preparation gate" -Status "fail" -Detail "unexpected preparation status=$status exit=$exitCode" -Meta @{ log = $logPath; artifact = $childArtifact }
|
|
}
|
|
}
|
|
|
|
function Invoke-DatabaseSnapshot {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$RepoRoot,
|
|
[Parameter(Mandatory = $true)][string]$ArtifactDir,
|
|
[Parameter(Mandatory = $true)][string]$TenantId,
|
|
[string]$DatabaseUrl = "",
|
|
[string]$BootstrapMode = "none"
|
|
)
|
|
|
|
$scriptPath = Join-Path $RepoRoot "scripts\export-formal-launch-governance-db-state.ps1"
|
|
$logPath = Join-Path $ArtifactDir "formal-launch-governance-db-state.log"
|
|
$childArtifact = Join-Path $ArtifactDir "formal-launch-governance-db-state"
|
|
|
|
if (-not (Test-Path -LiteralPath $scriptPath)) {
|
|
return [ordered]@{
|
|
available = $false
|
|
exit_code = 1
|
|
status = "FAIL"
|
|
log = $logPath
|
|
artifact = $childArtifact
|
|
payload = $null
|
|
reason = "missing database state export script"
|
|
}
|
|
}
|
|
|
|
$arguments = @(
|
|
"-ArtifactRoot", $childArtifact,
|
|
"-TenantId", $TenantId,
|
|
"-DisplayName", "formal-launch-approval-gate",
|
|
"-BootstrapMode", $BootstrapMode
|
|
)
|
|
if (-not [string]::IsNullOrWhiteSpace($DatabaseUrl)) {
|
|
$arguments += @("-DatabaseUrl", $DatabaseUrl)
|
|
}
|
|
|
|
$previousErrorActionPreference = $ErrorActionPreference
|
|
$ErrorActionPreference = "Continue"
|
|
try {
|
|
$output = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $scriptPath @arguments 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
} finally {
|
|
$ErrorActionPreference = $previousErrorActionPreference
|
|
}
|
|
|
|
Write-Utf8File -Path $logPath -Text ($output -join [Environment]::NewLine)
|
|
$statusLine = @($output | Where-Object { $_ -match "^FORMAL_LAUNCH_GOVERNANCE_DB_STATE_" } | Select-Object -Last 1)
|
|
$status = "FAIL"
|
|
if ($statusLine.Count -gt 0 -and $statusLine[-1] -match "^FORMAL_LAUNCH_GOVERNANCE_DB_STATE_([A-Z]+)\s+(.+)$") {
|
|
$status = $Matches[1]
|
|
$childArtifact = $Matches[2].Trim()
|
|
}
|
|
|
|
$summaryPath = Join-Path $childArtifact "formal-launch-governance-db-state-summary.json"
|
|
$payload = $null
|
|
if (Test-Path -LiteralPath $summaryPath) {
|
|
$payload = Get-Content -LiteralPath $summaryPath -Raw | ConvertFrom-Json
|
|
}
|
|
|
|
return [ordered]@{
|
|
available = ($exitCode -eq 0 -and $null -ne $payload)
|
|
exit_code = $exitCode
|
|
status = $status
|
|
log = $logPath
|
|
artifact = $childArtifact
|
|
payload = $payload
|
|
reason = $null
|
|
}
|
|
}
|
|
|
|
$repoRoot = Get-RepoRoot
|
|
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss-fff"
|
|
if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) {
|
|
$ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\formal-launch-approval-gate\$timestamp"
|
|
}
|
|
|
|
New-Item -ItemType Directory -Force -Path $ArtifactRoot | Out-Null
|
|
|
|
$checks = New-Object System.Collections.Generic.List[object]
|
|
$checksCsv = Join-Path $ArtifactRoot "formal-launch-approval-checks.csv"
|
|
$summaryJson = Join-Path $ArtifactRoot "formal-launch-approval-summary.json"
|
|
$summaryMd = Join-Path $ArtifactRoot "formal-launch-approval-summary.md"
|
|
|
|
$reviewTaskPath = Join-Path $repoRoot "project-docs\formal-launch-ops\reviewers\real-review-task-package.csv"
|
|
$materialSignoffPath = Join-Path $repoRoot "project-docs\formal-launch-ops\signoff\production-material-signoff-register.csv"
|
|
$templateSignoffPath = Join-Path $repoRoot "project-docs\formal-launch-ops\signoff\production-template-signoff-register.csv"
|
|
$knowledgeSignoffPath = Join-Path $repoRoot "project-docs\formal-launch-ops\signoff\production-knowledge-signoff-register.csv"
|
|
|
|
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
|
|
Add-Check -Key "gstack" -Label "gstack preflight" -Status "fail" -Detail "gstack missing"
|
|
} else {
|
|
Add-Check -Key "gstack" -Label "gstack preflight" -Status "pass" -Detail "GSTACK_OK"
|
|
}
|
|
|
|
$requiredInputs = @(
|
|
@{ key = "review_task_package"; label = "real review task package"; path = $reviewTaskPath },
|
|
@{ key = "material_signoff_register"; label = "material signoff register"; path = $materialSignoffPath },
|
|
@{ key = "template_signoff_register"; label = "template signoff register"; path = $templateSignoffPath },
|
|
@{ key = "knowledge_signoff_register"; label = "knowledge signoff register"; path = $knowledgeSignoffPath }
|
|
)
|
|
|
|
if ($SkipPreparationGate) {
|
|
Add-Check -Key "preparation_gate" -Label "formal launch preparation gate" -Status "skip" -Required $false -Detail "skipped by parameter"
|
|
} else {
|
|
Invoke-PreparationGate -RepoRoot $repoRoot -ArtifactDir $ArtifactRoot
|
|
}
|
|
|
|
$dataSource = "csv"
|
|
$databaseSnapshot = $null
|
|
$databaseSnapshotUsable = $false
|
|
|
|
if ($SkipDatabaseState) {
|
|
Add-Check -Key "database_truth_source" -Label "database truth source" -Status "skip" -Required $false -Detail "skipped by parameter"
|
|
} else {
|
|
$databaseSnapshot = Invoke-DatabaseSnapshot -RepoRoot $repoRoot -ArtifactDir $ArtifactRoot -TenantId $TenantId -DatabaseUrl $DatabaseUrl -BootstrapMode $DatabaseBootstrapMode
|
|
if ($databaseSnapshot.available) {
|
|
$dbTotalAssets = Get-NumericPropertyValue -Object $databaseSnapshot.payload.data.summary -PropertyName "total_assets"
|
|
if ($dbTotalAssets -gt 0) {
|
|
$databaseSnapshotUsable = $true
|
|
$dataSource = "database"
|
|
Add-Check -Key "database_truth_source" -Label "database truth source" -Status "pass" -Detail "database snapshot available; source=database; snapshot_status=$($databaseSnapshot.status)" -Meta @{ log = $databaseSnapshot.log; artifact = $databaseSnapshot.artifact }
|
|
} elseif ($RequireDatabaseTruth) {
|
|
Add-Check -Key "database_truth_source" -Label "database truth source" -Status "blocked" -Detail "database snapshot has no imported governance assets" -Meta @{ log = $databaseSnapshot.log; artifact = $databaseSnapshot.artifact }
|
|
} else {
|
|
Add-Check -Key "database_truth_source" -Label "database truth source" -Status "warn" -Required $false -Detail "database snapshot has no imported governance assets; fallback=csv" -Meta @{ log = $databaseSnapshot.log; artifact = $databaseSnapshot.artifact }
|
|
}
|
|
} elseif ($RequireDatabaseTruth) {
|
|
Add-Check -Key "database_truth_source" -Label "database truth source" -Status "fail" -Detail "database snapshot unavailable and -RequireDatabaseTruth is set" -Meta @{ log = $databaseSnapshot.log; artifact = $databaseSnapshot.artifact; exit_code = $databaseSnapshot.exit_code }
|
|
} else {
|
|
Add-Check -Key "database_truth_source" -Label "database truth source" -Status "warn" -Required $false -Detail "database snapshot unavailable; fallback=csv" -Meta @{ log = $databaseSnapshot.log; artifact = $databaseSnapshot.artifact; exit_code = $databaseSnapshot.exit_code }
|
|
}
|
|
}
|
|
|
|
$reviewTasks = @()
|
|
$materialSignoffs = @()
|
|
$templateSignoffs = @()
|
|
$knowledgeSignoffs = @()
|
|
|
|
if ($dataSource -eq "database") {
|
|
foreach ($input in $requiredInputs) {
|
|
Add-Check -Key $input.key -Label $input.label -Status "skip" -Required $false -Detail "database source active; csv input check skipped" -Meta @{ path = $input.path }
|
|
}
|
|
} else {
|
|
foreach ($input in $requiredInputs) {
|
|
if (Test-Path -LiteralPath $input.path) {
|
|
Add-Check -Key $input.key -Label $input.label -Status "pass" -Detail "input exists" -Meta @{ path = $input.path }
|
|
} else {
|
|
Add-Check -Key $input.key -Label $input.label -Status "fail" -Detail "input missing" -Meta @{ path = $input.path }
|
|
}
|
|
}
|
|
$reviewTasks = Import-CsvRows -Path $reviewTaskPath
|
|
$materialSignoffs = Import-CsvRows -Path $materialSignoffPath
|
|
$templateSignoffs = Import-CsvRows -Path $templateSignoffPath
|
|
$knowledgeSignoffs = Import-CsvRows -Path $knowledgeSignoffPath
|
|
}
|
|
|
|
$gateMetrics = if ($dataSource -eq "database") {
|
|
$approvalMetrics = $databaseSnapshot.payload.data.approval_metrics
|
|
$assetTypes = $databaseSnapshot.payload.data.asset_types
|
|
[ordered]@{
|
|
review_tasks = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "review_tasks"
|
|
required_review_tasks = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "required_review_tasks"
|
|
missing_reviewer_tasks = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "missing_reviewer_tasks"
|
|
approved_review_tasks = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_review_tasks"
|
|
approved_review_tasks_missing_evidence = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_review_tasks_missing_evidence"
|
|
material_signoffs = Get-NumericPropertyValue -Object $assetTypes -PropertyName "production_material"
|
|
approved_materials = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_materials"
|
|
approved_materials_missing_evidence = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_materials_missing_evidence"
|
|
approved_public_materials = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_public_materials"
|
|
approved_public_materials_missing_evidence = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_public_materials_missing_evidence"
|
|
template_signoffs = Get-NumericPropertyValue -Object $assetTypes -PropertyName "contract_template"
|
|
approved_templates = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_templates"
|
|
approved_templates_missing_evidence = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_templates_missing_evidence"
|
|
knowledge_signoffs = (Get-NumericPropertyValue -Object $assetTypes -PropertyName "legal_knowledge_card") + (Get-NumericPropertyValue -Object $assetTypes -PropertyName "review_risk_rule") + (Get-NumericPropertyValue -Object $assetTypes -PropertyName "clause")
|
|
approved_knowledge_cards = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_knowledge_cards"
|
|
approved_knowledge_cards_missing_evidence = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_knowledge_cards_missing_evidence"
|
|
approved_review_rules = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_review_rules"
|
|
approved_review_rules_missing_evidence = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_review_rules_missing_evidence"
|
|
approved_clauses = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_clauses"
|
|
approved_clauses_missing_evidence = Get-NumericPropertyValue -Object $approvalMetrics -PropertyName "approved_clauses_missing_evidence"
|
|
}
|
|
} else {
|
|
$requiredReviewTasks = @($reviewTasks | Where-Object { Test-TrueValue $_.blocking_before_launch })
|
|
$missingReviewerTasks = @($requiredReviewTasks | Where-Object { -not (Test-AssignedReviewer $_.assigned_reviewer) })
|
|
$approvedReviewTasks = @($requiredReviewTasks | Where-Object { (Test-ApprovedValue $_.review_status) -and (Test-ApprovedValue $_.approval_result) })
|
|
$approvedMaterials = @($materialSignoffs | Where-Object { (Test-ApprovedValue $_.signoff_result) -and (Test-ApprovedValue $_.status) })
|
|
$approvedTemplates = @($templateSignoffs | Where-Object { (Test-ApprovedValue $_.legal_signoff_result) -and (Test-ApprovedValue $_.business_signoff_result) -and (Test-ApprovedValue $_.production_approval_result) })
|
|
$approvedKnowledgeCards = @($knowledgeSignoffs | Where-Object { $_.asset_type -eq "legal_knowledge_card" -and (Test-ApprovedValue $_.legal_signoff_result) -and (Test-ApprovedValue $_.production_approval_result) })
|
|
$approvedReviewRules = @($knowledgeSignoffs | Where-Object { $_.asset_type -eq "review_risk_rule" -and (Test-ApprovedValue $_.legal_signoff_result) -and (Test-ApprovedValue $_.production_approval_result) })
|
|
$approvedClauses = @($knowledgeSignoffs | Where-Object { $_.asset_type -eq "clause" -and (Test-ApprovedValue $_.legal_signoff_result) -and (Test-ApprovedValue $_.production_approval_result) })
|
|
$approvedReviewTasksMissingEvidence = @($approvedReviewTasks | Where-Object { -not (Test-EvidenceAvailable $_.evidence_path) })
|
|
$approvedMaterialsMissingEvidence = @($approvedMaterials | Where-Object { -not (Test-EvidenceAvailable $_.evidence_path) })
|
|
$approvedTemplatesMissingEvidence = @($approvedTemplates | Where-Object { -not (Test-EvidenceAvailable $_.evidence_path) })
|
|
$approvedKnowledgeCardsMissingEvidence = @($approvedKnowledgeCards | Where-Object { -not (Test-EvidenceAvailable $_.evidence_path) })
|
|
$approvedReviewRulesMissingEvidence = @($approvedReviewRules | Where-Object { -not (Test-EvidenceAvailable $_.evidence_path) })
|
|
$approvedClausesMissingEvidence = @($approvedClauses | Where-Object { -not (Test-EvidenceAvailable $_.evidence_path) })
|
|
$publicMaterialIds = @("MAT-USER-AGREEMENT-001", "MAT-PRIVACY-POLICY-001", "MAT-AI-DISCLAIMER-001")
|
|
$approvedPublicMaterials = @($materialSignoffs | Where-Object { $publicMaterialIds -contains $_.material_id -and (Test-ApprovedValue $_.signoff_result) -and (Test-ApprovedValue $_.status) })
|
|
$approvedPublicMaterialsMissingEvidence = @($approvedPublicMaterials | Where-Object { -not (Test-EvidenceAvailable $_.evidence_path) })
|
|
[ordered]@{
|
|
review_tasks = $reviewTasks.Count
|
|
required_review_tasks = $requiredReviewTasks.Count
|
|
missing_reviewer_tasks = $missingReviewerTasks.Count
|
|
approved_review_tasks = $approvedReviewTasks.Count
|
|
approved_review_tasks_missing_evidence = $approvedReviewTasksMissingEvidence.Count
|
|
material_signoffs = $materialSignoffs.Count
|
|
approved_materials = $approvedMaterials.Count
|
|
approved_materials_missing_evidence = $approvedMaterialsMissingEvidence.Count
|
|
approved_public_materials = $approvedPublicMaterials.Count
|
|
approved_public_materials_missing_evidence = $approvedPublicMaterialsMissingEvidence.Count
|
|
template_signoffs = $templateSignoffs.Count
|
|
approved_templates = $approvedTemplates.Count
|
|
approved_templates_missing_evidence = $approvedTemplatesMissingEvidence.Count
|
|
knowledge_signoffs = $knowledgeSignoffs.Count
|
|
approved_knowledge_cards = $approvedKnowledgeCards.Count
|
|
approved_knowledge_cards_missing_evidence = $approvedKnowledgeCardsMissingEvidence.Count
|
|
approved_review_rules = $approvedReviewRules.Count
|
|
approved_review_rules_missing_evidence = $approvedReviewRulesMissingEvidence.Count
|
|
approved_clauses = $approvedClauses.Count
|
|
approved_clauses_missing_evidence = $approvedClausesMissingEvidence.Count
|
|
}
|
|
}
|
|
|
|
if ($gateMetrics.review_tasks -ge $MinReviewTasks) {
|
|
Add-Check -Key "review_task_package_size" -Label "review task package size" -Status "pass" -Detail "review_tasks=$($gateMetrics.review_tasks); min=$MinReviewTasks"
|
|
} else {
|
|
Add-Check -Key "review_task_package_size" -Label "review task package size" -Status "blocked" -Detail "review_tasks=$($gateMetrics.review_tasks); min=$MinReviewTasks"
|
|
}
|
|
|
|
if ($gateMetrics.missing_reviewer_tasks -eq 0 -and $gateMetrics.required_review_tasks -gt 0) {
|
|
Add-Check -Key "real_reviewer_assignment" -Label "real reviewer assignment" -Status "pass" -Detail "all blocking review tasks have assigned reviewers"
|
|
} else {
|
|
Add-Check -Key "real_reviewer_assignment" -Label "real reviewer assignment" -Status "blocked" -Detail "missing_assigned_reviewers=$($gateMetrics.missing_reviewer_tasks)"
|
|
}
|
|
|
|
if ($gateMetrics.approved_review_tasks -ge $MinApprovedReviewTasks) {
|
|
Add-Check -Key "real_review_execution" -Label "real review execution" -Status "pass" -Detail "approved_review_tasks=$($gateMetrics.approved_review_tasks); min=$MinApprovedReviewTasks"
|
|
} else {
|
|
Add-Check -Key "real_review_execution" -Label "real review execution" -Status "blocked" -Detail "approved_review_tasks=$($gateMetrics.approved_review_tasks); min=$MinApprovedReviewTasks"
|
|
}
|
|
|
|
if ($gateMetrics.approved_review_tasks_missing_evidence -eq 0) {
|
|
Add-Check -Key "real_review_evidence" -Label "real review evidence" -Status "pass" -Detail "approved review tasks have evidence"
|
|
} else {
|
|
Add-Check -Key "real_review_evidence" -Label "real review evidence" -Status "blocked" -Detail "approved_review_tasks_missing_evidence=$($gateMetrics.approved_review_tasks_missing_evidence)"
|
|
}
|
|
|
|
if ($gateMetrics.approved_materials -ge $MinApprovedMaterials) {
|
|
Add-Check -Key "material_signoff" -Label "production material signoff" -Status "pass" -Detail "approved_materials=$($gateMetrics.approved_materials); min=$MinApprovedMaterials"
|
|
} else {
|
|
Add-Check -Key "material_signoff" -Label "production material signoff" -Status "blocked" -Detail "approved_materials=$($gateMetrics.approved_materials); min=$MinApprovedMaterials"
|
|
}
|
|
|
|
if ($gateMetrics.approved_materials_missing_evidence -eq 0) {
|
|
Add-Check -Key "material_signoff_evidence" -Label "material signoff evidence" -Status "pass" -Detail "approved materials have evidence"
|
|
} else {
|
|
Add-Check -Key "material_signoff_evidence" -Label "material signoff evidence" -Status "blocked" -Detail "approved_materials_missing_evidence=$($gateMetrics.approved_materials_missing_evidence)"
|
|
}
|
|
|
|
if ($gateMetrics.approved_public_materials -eq 3) {
|
|
Add-Check -Key "public_legal_document_signoff" -Label "public legal document signoff" -Status "pass" -Detail "approved_public_materials=$($gateMetrics.approved_public_materials)"
|
|
} else {
|
|
Add-Check -Key "public_legal_document_signoff" -Label "public legal document signoff" -Status "blocked" -Detail "approved_public_materials=$($gateMetrics.approved_public_materials); required=3"
|
|
}
|
|
|
|
if ($gateMetrics.approved_public_materials_missing_evidence -eq 0) {
|
|
Add-Check -Key "public_legal_document_evidence" -Label "public legal document evidence" -Status "pass" -Detail "approved public materials have evidence"
|
|
} else {
|
|
Add-Check -Key "public_legal_document_evidence" -Label "public legal document evidence" -Status "blocked" -Detail "approved_public_materials_missing_evidence=$($gateMetrics.approved_public_materials_missing_evidence)"
|
|
}
|
|
|
|
if ($gateMetrics.approved_templates -ge $MinApprovedTemplates) {
|
|
Add-Check -Key "template_signoff" -Label "production template signoff" -Status "pass" -Detail "approved_templates=$($gateMetrics.approved_templates); min=$MinApprovedTemplates"
|
|
} else {
|
|
Add-Check -Key "template_signoff" -Label "production template signoff" -Status "blocked" -Detail "approved_templates=$($gateMetrics.approved_templates); min=$MinApprovedTemplates"
|
|
}
|
|
|
|
if ($gateMetrics.approved_templates_missing_evidence -eq 0) {
|
|
Add-Check -Key "template_signoff_evidence" -Label "template signoff evidence" -Status "pass" -Detail "approved templates have evidence"
|
|
} else {
|
|
Add-Check -Key "template_signoff_evidence" -Label "template signoff evidence" -Status "blocked" -Detail "approved_templates_missing_evidence=$($gateMetrics.approved_templates_missing_evidence)"
|
|
}
|
|
|
|
if ($gateMetrics.approved_knowledge_cards -ge $MinApprovedKnowledgeCards) {
|
|
Add-Check -Key "knowledge_card_signoff" -Label "knowledge card signoff" -Status "pass" -Detail "approved_knowledge_cards=$($gateMetrics.approved_knowledge_cards); min=$MinApprovedKnowledgeCards"
|
|
} else {
|
|
Add-Check -Key "knowledge_card_signoff" -Label "knowledge card signoff" -Status "blocked" -Detail "approved_knowledge_cards=$($gateMetrics.approved_knowledge_cards); min=$MinApprovedKnowledgeCards"
|
|
}
|
|
|
|
if ($gateMetrics.approved_knowledge_cards_missing_evidence -eq 0) {
|
|
Add-Check -Key "knowledge_card_evidence" -Label "knowledge card evidence" -Status "pass" -Detail "approved knowledge cards have evidence"
|
|
} else {
|
|
Add-Check -Key "knowledge_card_evidence" -Label "knowledge card evidence" -Status "blocked" -Detail "approved_knowledge_cards_missing_evidence=$($gateMetrics.approved_knowledge_cards_missing_evidence)"
|
|
}
|
|
|
|
if ($gateMetrics.approved_review_rules -ge $MinApprovedReviewRules) {
|
|
Add-Check -Key "review_rule_signoff" -Label "review rule signoff" -Status "pass" -Detail "approved_review_rules=$($gateMetrics.approved_review_rules); min=$MinApprovedReviewRules"
|
|
} else {
|
|
Add-Check -Key "review_rule_signoff" -Label "review rule signoff" -Status "blocked" -Detail "approved_review_rules=$($gateMetrics.approved_review_rules); min=$MinApprovedReviewRules"
|
|
}
|
|
|
|
if ($gateMetrics.approved_review_rules_missing_evidence -eq 0) {
|
|
Add-Check -Key "review_rule_evidence" -Label "review rule evidence" -Status "pass" -Detail "approved review rules have evidence"
|
|
} else {
|
|
Add-Check -Key "review_rule_evidence" -Label "review rule evidence" -Status "blocked" -Detail "approved_review_rules_missing_evidence=$($gateMetrics.approved_review_rules_missing_evidence)"
|
|
}
|
|
|
|
if ($gateMetrics.approved_clauses -ge $MinApprovedClauses) {
|
|
Add-Check -Key "clause_signoff" -Label "clause signoff" -Status "pass" -Detail "approved_clauses=$($gateMetrics.approved_clauses); min=$MinApprovedClauses"
|
|
} else {
|
|
Add-Check -Key "clause_signoff" -Label "clause signoff" -Status "blocked" -Detail "approved_clauses=$($gateMetrics.approved_clauses); min=$MinApprovedClauses"
|
|
}
|
|
|
|
if ($gateMetrics.approved_clauses_missing_evidence -eq 0) {
|
|
Add-Check -Key "clause_evidence" -Label "clause evidence" -Status "pass" -Detail "approved clauses have evidence"
|
|
} else {
|
|
Add-Check -Key "clause_evidence" -Label "clause evidence" -Status "blocked" -Detail "approved_clauses_missing_evidence=$($gateMetrics.approved_clauses_missing_evidence)"
|
|
}
|
|
|
|
$checks |
|
|
ForEach-Object {
|
|
[pscustomobject]@{
|
|
key = $_.key
|
|
label = $_.label
|
|
required = $_.required
|
|
status = $_.status
|
|
detail = $_.detail
|
|
}
|
|
} |
|
|
Export-Csv -LiteralPath $checksCsv -NoTypeInformation -Encoding UTF8
|
|
|
|
$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"
|
|
}
|
|
|
|
$summary = [ordered]@{
|
|
status = $overallStatus
|
|
generated_at = (Get-Date).ToString("o")
|
|
repo_root = $repoRoot
|
|
source_mode = $dataSource
|
|
metrics = [ordered]@{
|
|
review_tasks = $gateMetrics.review_tasks
|
|
required_review_tasks = $gateMetrics.required_review_tasks
|
|
missing_reviewer_tasks = $gateMetrics.missing_reviewer_tasks
|
|
approved_review_tasks = $gateMetrics.approved_review_tasks
|
|
approved_review_tasks_missing_evidence = $gateMetrics.approved_review_tasks_missing_evidence
|
|
material_signoffs = $gateMetrics.material_signoffs
|
|
approved_materials = $gateMetrics.approved_materials
|
|
approved_materials_missing_evidence = $gateMetrics.approved_materials_missing_evidence
|
|
approved_public_materials = $gateMetrics.approved_public_materials
|
|
approved_public_materials_missing_evidence = $gateMetrics.approved_public_materials_missing_evidence
|
|
template_signoffs = $gateMetrics.template_signoffs
|
|
approved_templates = $gateMetrics.approved_templates
|
|
approved_templates_missing_evidence = $gateMetrics.approved_templates_missing_evidence
|
|
knowledge_signoffs = $gateMetrics.knowledge_signoffs
|
|
approved_knowledge_cards = $gateMetrics.approved_knowledge_cards
|
|
approved_knowledge_cards_missing_evidence = $gateMetrics.approved_knowledge_cards_missing_evidence
|
|
approved_review_rules = $gateMetrics.approved_review_rules
|
|
approved_review_rules_missing_evidence = $gateMetrics.approved_review_rules_missing_evidence
|
|
approved_clauses = $gateMetrics.approved_clauses
|
|
approved_clauses_missing_evidence = $gateMetrics.approved_clauses_missing_evidence
|
|
failed_required = $failedRequired
|
|
blocked_required = $blockedRequired
|
|
warnings = $warnings
|
|
}
|
|
checks = $checks.ToArray()
|
|
artifacts = [ordered]@{
|
|
checks = $checksCsv
|
|
summary_json = $summaryJson
|
|
summary_md = $summaryMd
|
|
}
|
|
inputs = [ordered]@{
|
|
review_task_package = $reviewTaskPath
|
|
material_signoff_register = $materialSignoffPath
|
|
template_signoff_register = $templateSignoffPath
|
|
knowledge_signoff_register = $knowledgeSignoffPath
|
|
database_snapshot = if ($null -ne $databaseSnapshot) { $databaseSnapshot.artifact } else { $null }
|
|
}
|
|
}
|
|
|
|
$summary | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $summaryJson -Encoding UTF8
|
|
|
|
$md = New-Object System.Collections.Generic.List[string]
|
|
$md.Add("# Formal Launch Approval Gate Summary") | Out-Null
|
|
$md.Add("") | Out-Null
|
|
$md.Add("- status: $overallStatus") | Out-Null
|
|
$md.Add("- generated_at: $($summary.generated_at)") | Out-Null
|
|
$md.Add("- repo_root: $repoRoot") | Out-Null
|
|
$md.Add("- source_mode: $dataSource") | Out-Null
|
|
$md.Add("") | Out-Null
|
|
$md.Add("## Metrics") | Out-Null
|
|
$md.Add("") | Out-Null
|
|
$md.Add("| metric | value |") | Out-Null
|
|
$md.Add("|---|---:|") | Out-Null
|
|
foreach ($property in $summary.metrics.GetEnumerator()) {
|
|
$md.Add("| $($property.Key) | $($property.Value) |") | 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("PASS means the PC formal-launch candidate has the required preparation gate, real reviewer assignments, completed review evidence, and production signoff registers. BLOCKED is expected until real reviewers complete and sign the required assets.") | Out-Null
|
|
Write-Utf8File -Path $summaryMd -Text ($md -join [Environment]::NewLine)
|
|
|
|
Write-Output "FORMAL_LAUNCH_APPROVAL_GATE_$overallStatus $ArtifactRoot"
|
|
|
|
if ($overallStatus -eq "FAIL") {
|
|
exit 1
|
|
}
|
|
|
|
if ($overallStatus -eq "BLOCKED" -and $FailOnBlocked) {
|
|
throw "formal launch approval gate blocked: $summaryMd"
|
|
}
|