fix: tighten formal launch approval handoff
This commit is contained in:
@@ -34,6 +34,17 @@ function Import-CsvRows {
|
||||
return @(Import-Csv -LiteralPath $Path)
|
||||
}
|
||||
|
||||
function ConvertTo-RepoRelativePath {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
|
||||
$repoFullPath = [System.IO.Path]::GetFullPath($repoRoot).TrimEnd([char[]]@("\", "/"))
|
||||
$targetFullPath = [System.IO.Path]::GetFullPath($Path)
|
||||
if ($targetFullPath.StartsWith($repoFullPath, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
return ($targetFullPath.Substring($repoFullPath.Length).TrimStart([char[]]@("\", "/")) -replace "\\", "/")
|
||||
}
|
||||
return ($targetFullPath -replace "\\", "/")
|
||||
}
|
||||
|
||||
function Get-ReviewGroup {
|
||||
param([Parameter(Mandatory = $true)][object]$Row)
|
||||
|
||||
@@ -160,6 +171,9 @@ if ([string]::IsNullOrWhiteSpace($ReturnBundleCsv)) {
|
||||
if ([string]::IsNullOrWhiteSpace($OutputRoot)) {
|
||||
$OutputRoot = Join-Path $repoRoot "project-docs\formal-launch-ops\reviewers\collaboration-packages"
|
||||
}
|
||||
if (-not [System.IO.Path]::IsPathRooted($OutputRoot)) {
|
||||
$OutputRoot = Join-Path $repoRoot $OutputRoot
|
||||
}
|
||||
|
||||
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss-fff"
|
||||
if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) {
|
||||
@@ -191,8 +205,8 @@ foreach ($row in $rows) {
|
||||
New-Item -ItemType Directory -Force -Path $evidenceDir | Out-Null
|
||||
|
||||
$safeRecordId = ([string]$row.record_id) -replace '[^A-Za-z0-9_.-]', '_'
|
||||
$relativeEvidencePath = "project-docs/formal-launch-ops/reviewers/collaboration-packages/$group/evidence/$safeRecordId.md"
|
||||
$fullEvidencePath = Join-Path $repoRoot $relativeEvidencePath
|
||||
$fullEvidencePath = Join-Path $evidenceDir "$safeRecordId.md"
|
||||
$relativeEvidencePath = ConvertTo-RepoRelativePath -Path $fullEvidencePath
|
||||
|
||||
$row.evidence_path = $relativeEvidencePath
|
||||
$placeholder = Get-ReviewerPlaceholder -Group $group
|
||||
@@ -240,14 +254,15 @@ foreach ($group in $groups) {
|
||||
package_group = $group
|
||||
rows = $groupRows.Count
|
||||
evidence_templates = $evidenceCount
|
||||
return_bundle = "project-docs/formal-launch-ops/reviewers/collaboration-packages/$group/return-bundle.csv"
|
||||
handoff = "project-docs/formal-launch-ops/reviewers/collaboration-packages/$group/handoff.md"
|
||||
return_bundle = ConvertTo-RepoRelativePath -Path $csvPath
|
||||
handoff = ConvertTo-RepoRelativePath -Path $handoffPath
|
||||
}) | Out-Null
|
||||
}
|
||||
|
||||
$finalApproverDir = Join-Path $OutputRoot "FINAL_APPROVER"
|
||||
New-Item -ItemType Directory -Force -Path $finalApproverDir | Out-Null
|
||||
$finalChecklist = Join-Path $finalApproverDir "final-launch-approval-checklist.md"
|
||||
$finalHandoff = Join-Path $finalApproverDir "handoff.md"
|
||||
$finalLines = New-Object System.Collections.Generic.List[string]
|
||||
$finalLines.Add("# Final Launch Approval Checklist") | Out-Null
|
||||
$finalLines.Add("") | Out-Null
|
||||
@@ -264,6 +279,31 @@ $finalLines.Add("- final approval name: TODO_REAL_REVIEWER") | Out-Null
|
||||
$finalLines.Add("- final approval date: TODO_YYYY-MM-DD") | Out-Null
|
||||
Write-Utf8File -Path $finalChecklist -Text ($finalLines -join [Environment]::NewLine)
|
||||
|
||||
$finalHandoffLines = New-Object System.Collections.Generic.List[string]
|
||||
$finalHandoffLines.Add("# Review Collaboration Package: FINAL_APPROVER") | Out-Null
|
||||
$finalHandoffLines.Add("") | Out-Null
|
||||
$finalHandoffLines.Add("This package is for the final launch approver. It is intentionally not imported by the review return bundle script because final approval depends on all real review and signoff registers first.") | Out-Null
|
||||
$finalHandoffLines.Add("") | Out-Null
|
||||
$finalHandoffLines.Add("## How To Complete") | Out-Null
|
||||
$finalHandoffLines.Add("") | Out-Null
|
||||
$finalHandoffLines.Add("1. Confirm the preparation gate, approval gate checks, legal package, business package, ops package, and production signoff registers.") | Out-Null
|
||||
$finalHandoffLines.Add("2. Update final-launch-approval-checklist.md with the real approver name and approval date.") | Out-Null
|
||||
$finalHandoffLines.Add("3. Change EVIDENCE_STATUS: TEMPLATE to EVIDENCE_STATUS: READY only after real final approval.") | Out-Null
|
||||
$finalHandoffLines.Add("4. Re-run scripts/run-formal-launch-approval-gate.ps1. The gate will reject this checklist while it is still TEMPLATE or contains TODO placeholders.") | Out-Null
|
||||
$finalHandoffLines.Add("") | Out-Null
|
||||
$finalHandoffLines.Add("## Safety Rule") | Out-Null
|
||||
$finalHandoffLines.Add("") | Out-Null
|
||||
$finalHandoffLines.Add("Do not use scripts/import-formal-launch-review-return-bundle.ps1 for this folder. Use the approval gate itself to validate the final checklist.") | Out-Null
|
||||
Write-Utf8File -Path $finalHandoff -Text ($finalHandoffLines -join [Environment]::NewLine)
|
||||
|
||||
$summaryRows.Add([pscustomobject]@{
|
||||
package_group = "FINAL_APPROVER"
|
||||
rows = 1
|
||||
evidence_templates = 1
|
||||
return_bundle = ""
|
||||
handoff = ConvertTo-RepoRelativePath -Path $finalHandoff
|
||||
}) | Out-Null
|
||||
|
||||
$summaryCsv = Join-Path $OutputRoot "package-summary.csv"
|
||||
$summaryRows | Export-Csv -LiteralPath $summaryCsv -NoTypeInformation -Encoding UTF8
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ function Test-EvidenceContentFinal {
|
||||
|
||||
function Require-Columns {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][object[]]$Rows,
|
||||
[Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]]$Rows,
|
||||
[Parameter(Mandatory = $true)][string[]]$Columns
|
||||
)
|
||||
if ($Rows.Count -eq 0) {
|
||||
@@ -187,11 +187,12 @@ $materialSignoffPath = Join-Path $repoRoot "project-docs\formal-launch-ops\signo
|
||||
$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"
|
||||
|
||||
$bundleRows = Import-CsvRows -Path $BundleCsv
|
||||
$reviewTasks = Import-CsvRows -Path $reviewTaskPath
|
||||
$materialSignoffs = Import-CsvRows -Path $materialSignoffPath
|
||||
$templateSignoffs = Import-CsvRows -Path $templateSignoffPath
|
||||
$knowledgeSignoffs = Import-CsvRows -Path $knowledgeSignoffPath
|
||||
$bundleCsvExists = Test-Path -LiteralPath $BundleCsv
|
||||
$bundleRows = @(Import-CsvRows -Path $BundleCsv)
|
||||
$reviewTasks = @(Import-CsvRows -Path $reviewTaskPath)
|
||||
$materialSignoffs = @(Import-CsvRows -Path $materialSignoffPath)
|
||||
$templateSignoffs = @(Import-CsvRows -Path $templateSignoffPath)
|
||||
$knowledgeSignoffs = @(Import-CsvRows -Path $knowledgeSignoffPath)
|
||||
|
||||
$requiredColumns = @(
|
||||
"record_type",
|
||||
@@ -215,6 +216,9 @@ $requiredColumns = @(
|
||||
|
||||
$errors = New-Object System.Collections.Generic.List[string]
|
||||
$warnings = New-Object System.Collections.Generic.List[string]
|
||||
if (-not $bundleCsvExists) {
|
||||
$errors.Add("bundle csv not found: $BundleCsv") | Out-Null
|
||||
}
|
||||
foreach ($errorText in (Require-Columns -Rows $bundleRows -Columns $requiredColumns)) {
|
||||
$errors.Add($errorText) | Out-Null
|
||||
}
|
||||
|
||||
@@ -105,6 +105,41 @@ function Test-EvidenceContentFinal {
|
||||
return $true
|
||||
}
|
||||
|
||||
function Get-FinalApprovalChecklistStatus {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
return [ordered]@{
|
||||
status = "fail"
|
||||
detail = "final approval checklist missing"
|
||||
}
|
||||
}
|
||||
|
||||
$content = Get-Content -LiteralPath $Path -Raw -ErrorAction Stop
|
||||
if ($content -match "EVIDENCE_STATUS:\s*TEMPLATE") {
|
||||
return [ordered]@{
|
||||
status = "blocked"
|
||||
detail = "final approval checklist is still marked TEMPLATE"
|
||||
}
|
||||
}
|
||||
if ($content -match "TODO_REAL_REVIEWER" -or $content -match "TODO_YYYY-MM-DD") {
|
||||
return [ordered]@{
|
||||
status = "blocked"
|
||||
detail = "final approval checklist still contains reviewer placeholders"
|
||||
}
|
||||
}
|
||||
if ($content -notmatch "EVIDENCE_STATUS:\s*READY") {
|
||||
return [ordered]@{
|
||||
status = "blocked"
|
||||
detail = "final approval checklist is not marked READY"
|
||||
}
|
||||
}
|
||||
|
||||
return [ordered]@{
|
||||
status = "pass"
|
||||
detail = "final approval checklist is READY"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-NumericPropertyValue {
|
||||
param(
|
||||
[object]$Object,
|
||||
@@ -267,6 +302,7 @@ $reviewTaskPath = Join-Path $repoRoot "project-docs\formal-launch-ops\reviewers\
|
||||
$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"
|
||||
$finalApprovalChecklistPath = Join-Path $repoRoot "project-docs\formal-launch-ops\reviewers\collaboration-packages\FINAL_APPROVER\final-launch-approval-checklist.md"
|
||||
|
||||
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
|
||||
Add-Check -Key "gstack" -Label "gstack preflight" -Status "fail" -Detail "gstack missing"
|
||||
@@ -499,6 +535,9 @@ if ($gateMetrics.approved_clauses_missing_evidence -eq 0) {
|
||||
Add-Check -Key "clause_evidence" -Label "clause evidence" -Status "blocked" -Detail "approved_clauses_missing_evidence=$($gateMetrics.approved_clauses_missing_evidence)"
|
||||
}
|
||||
|
||||
$finalApprovalChecklistStatus = Get-FinalApprovalChecklistStatus -Path $finalApprovalChecklistPath
|
||||
Add-Check -Key "final_launch_approval_checklist" -Label "final launch approval checklist" -Status $finalApprovalChecklistStatus.status -Detail $finalApprovalChecklistStatus.detail -Meta @{ path = $finalApprovalChecklistPath }
|
||||
|
||||
$checks |
|
||||
ForEach-Object {
|
||||
[pscustomobject]@{
|
||||
@@ -566,6 +605,7 @@ $summary = [ordered]@{
|
||||
material_signoff_register = $materialSignoffPath
|
||||
template_signoff_register = $templateSignoffPath
|
||||
knowledge_signoff_register = $knowledgeSignoffPath
|
||||
final_approval_checklist = $finalApprovalChecklistPath
|
||||
database_snapshot = if ($null -ne $databaseSnapshot) { $databaseSnapshot.artifact } else { $null }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user