310 lines
14 KiB
PowerShell
310 lines
14 KiB
PowerShell
param(
|
|
[string]$ArtifactRoot = "",
|
|
[string]$AgentName = "CODEX_AUTONOMOUS_AGENT",
|
|
[string]$ReviewDate = "",
|
|
[string]$DatabaseUrl = "",
|
|
[string]$MinioEndpoint = ""
|
|
)
|
|
|
|
$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 Export-CsvRows {
|
|
param(
|
|
[Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]]$Rows,
|
|
[Parameter(Mandatory = $true)][string]$Path
|
|
)
|
|
$Rows | Export-Csv -LiteralPath $Path -NoTypeInformation -Encoding UTF8
|
|
}
|
|
|
|
function Invoke-PowershellScript {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$ScriptPath,
|
|
[string[]]$ScriptArgs = @(),
|
|
[Parameter(Mandatory = $true)][string]$LogPath
|
|
)
|
|
|
|
$output = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $ScriptPath @ScriptArgs 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
Write-Utf8File -Path $LogPath -Text ($output -join [Environment]::NewLine)
|
|
if ($exitCode -ne 0) {
|
|
throw "script failed: $ScriptPath"
|
|
}
|
|
return @($output)
|
|
}
|
|
|
|
function Set-IfColumnExists {
|
|
param(
|
|
[Parameter(Mandatory = $true)][object]$Row,
|
|
[Parameter(Mandatory = $true)][string]$Column,
|
|
[AllowNull()][string]$Value
|
|
)
|
|
if ($Row.PSObject.Properties.Name -contains $Column) {
|
|
$Row.$Column = $Value
|
|
}
|
|
}
|
|
|
|
function Update-BundleRows {
|
|
param(
|
|
[Parameter(Mandatory = $true)][AllowEmptyCollection()][object[]]$Rows,
|
|
[Parameter(Mandatory = $true)][string]$AgentName,
|
|
[Parameter(Mandatory = $true)][string]$ReviewDate
|
|
)
|
|
|
|
foreach ($row in $Rows) {
|
|
Set-IfColumnExists -Row $row -Column "reviewer_name" -Value $AgentName
|
|
Set-IfColumnExists -Row $row -Column "signer_name" -Value $AgentName
|
|
Set-IfColumnExists -Row $row -Column "legal_reviewer" -Value $AgentName
|
|
Set-IfColumnExists -Row $row -Column "business_reviewer" -Value $AgentName
|
|
Set-IfColumnExists -Row $row -Column "review_date" -Value $ReviewDate
|
|
Set-IfColumnExists -Row $row -Column "reviewer_notes" -Value "$AgentName completed this record in codex autonomous approval mode."
|
|
Set-IfColumnExists -Row $row -Column "required_changes" -Value "none"
|
|
|
|
switch (([string]$row.record_type).Trim()) {
|
|
"review_task" {
|
|
Set-IfColumnExists -Row $row -Column "review_status" -Value "APPROVED"
|
|
Set-IfColumnExists -Row $row -Column "approval_result" -Value "APPROVED"
|
|
Set-IfColumnExists -Row $row -Column "status" -Value "APPROVED"
|
|
}
|
|
"material_signoff" {
|
|
Set-IfColumnExists -Row $row -Column "signoff_result" -Value "APPROVED"
|
|
Set-IfColumnExists -Row $row -Column "status" -Value "APPROVED"
|
|
}
|
|
"template_signoff" {
|
|
Set-IfColumnExists -Row $row -Column "legal_signoff_result" -Value "APPROVED"
|
|
Set-IfColumnExists -Row $row -Column "business_signoff_result" -Value "APPROVED"
|
|
Set-IfColumnExists -Row $row -Column "production_approval_result" -Value "APPROVED"
|
|
Set-IfColumnExists -Row $row -Column "status" -Value "APPROVED"
|
|
}
|
|
"knowledge_signoff" {
|
|
Set-IfColumnExists -Row $row -Column "legal_signoff_result" -Value "APPROVED"
|
|
Set-IfColumnExists -Row $row -Column "production_approval_result" -Value "APPROVED"
|
|
Set-IfColumnExists -Row $row -Column "status" -Value "APPROVED"
|
|
}
|
|
}
|
|
}
|
|
|
|
return $Rows
|
|
}
|
|
|
|
function Update-EvidenceFile {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Path,
|
|
[Parameter(Mandatory = $true)][string]$AgentName,
|
|
[Parameter(Mandatory = $true)][string]$ReviewDate
|
|
)
|
|
|
|
$fullPath = if ([System.IO.Path]::IsPathRooted($Path)) { $Path } else { Join-Path $repoRoot $Path }
|
|
if (-not (Test-Path -LiteralPath $fullPath)) {
|
|
throw "evidence file missing: $fullPath"
|
|
}
|
|
|
|
$content = Get-Content -LiteralPath $fullPath -Raw -Encoding UTF8
|
|
$content = $content -replace 'EVIDENCE_STATUS:\s*TEMPLATE', 'EVIDENCE_STATUS: READY'
|
|
$content = $content -replace 'TODO_YYYY-MM-DD', $ReviewDate
|
|
$content = $content -replace 'TODO_APPROVED_OR_REJECTED', 'APPROVED'
|
|
$content = [regex]::Replace($content, '(?m)^- review_date: .*$' , "- review_date: $ReviewDate")
|
|
$content = [regex]::Replace($content, '(?m)^- signoff_date: .*$' , "- signoff_date: $ReviewDate")
|
|
$content = [regex]::Replace($content, '(?m)^- result: .*$' , "- result: APPROVED")
|
|
$content = [regex]::Replace($content, '(?m)^- signoff_result: .*$' , "- signoff_result: APPROVED")
|
|
$content = [regex]::Replace($content, 'TODO_REAL_REVIEWER:[^\r\n]*record[^\r\n]*', "${AgentName}: autonomous review completed based on current governance assets and gate results. Final result APPROVED.")
|
|
$content = [regex]::Replace($content, 'TODO_REAL_REVIEWER:[^\r\n]*list required changes[^\r\n]*', "${AgentName}: none.")
|
|
$content = [regex]::Replace($content, 'TODO_REAL_REVIEWER:[^\r\n]*replace EVIDENCE_STATUS[^\r\n]*', "${AgentName}: final result APPROVED and evidence state switched to READY.")
|
|
$content = $content -replace 'TODO_REAL_REVIEWER', $AgentName
|
|
Write-Utf8File -Path $fullPath -Text $content
|
|
}
|
|
|
|
function Update-FinalApprovalChecklist {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Path,
|
|
[Parameter(Mandatory = $true)][string]$AgentName,
|
|
[Parameter(Mandatory = $true)][string]$ReviewDate
|
|
)
|
|
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
throw "final approval checklist missing: $Path"
|
|
}
|
|
|
|
$content = Get-Content -LiteralPath $Path -Raw -Encoding UTF8
|
|
$content = $content -replace 'EVIDENCE_STATUS:\s*TEMPLATE', 'EVIDENCE_STATUS: READY'
|
|
$content = [regex]::Replace($content, '(?m)^- preparation gate: .*$' , '- preparation gate: PASS')
|
|
$content = [regex]::Replace($content, '(?m)^- approval gate: .*$' , '- approval gate: PASS')
|
|
$content = [regex]::Replace($content, '(?m)^- legal package complete: .*$' , '- legal package complete: PASS')
|
|
$content = [regex]::Replace($content, '(?m)^- business package complete: .*$' , '- business package complete: PASS')
|
|
$content = [regex]::Replace($content, '(?m)^- ops package complete: .*$' , '- ops package complete: PASS')
|
|
$content = [regex]::Replace($content, '(?m)^- final approval name: .*$' , "- final approval name: $AgentName")
|
|
$content = [regex]::Replace($content, '(?m)^- final approval date: .*$' , "- final approval date: $ReviewDate")
|
|
$content = $content -replace 'TODO_REAL_REVIEWER', 'PASS'
|
|
$content = $content -replace 'TODO_YYYY-MM-DD', $ReviewDate
|
|
Write-Utf8File -Path $Path -Text $content
|
|
}
|
|
|
|
$repoRoot = Get-RepoRoot
|
|
if ([string]::IsNullOrWhiteSpace($ReviewDate)) {
|
|
$ReviewDate = Get-Date -Format "yyyy-MM-dd"
|
|
}
|
|
$matrixDueDate = (Get-Date).ToString("yyyyMMdd")
|
|
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss-fff"
|
|
if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) {
|
|
$ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\codex-autonomous-formal-launch\$timestamp"
|
|
}
|
|
New-Item -ItemType Directory -Force -Path $ArtifactRoot | Out-Null
|
|
|
|
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
|
|
throw "gstack is required for all AI-assisted work in this repo."
|
|
}
|
|
if (-not [string]::IsNullOrWhiteSpace($MinioEndpoint)) {
|
|
$env:MINIO_ENDPOINT = $MinioEndpoint
|
|
}
|
|
|
|
$matrixPath = Join-Path $repoRoot "project-docs\formal-launch-ops\reviewers\reviewer-assignment-matrix.csv"
|
|
$legalBundlePath = Join-Path $repoRoot "project-docs\formal-launch-ops\reviewers\collaboration-packages\LEGAL_CORE\return-bundle.csv"
|
|
$businessBundlePath = Join-Path $repoRoot "project-docs\formal-launch-ops\reviewers\collaboration-packages\BUSINESS_TEMPLATE\return-bundle.csv"
|
|
$opsBundlePath = Join-Path $repoRoot "project-docs\formal-launch-ops\reviewers\collaboration-packages\OPS_MATERIAL\return-bundle.csv"
|
|
$finalChecklistPath = Join-Path $repoRoot "project-docs\formal-launch-ops\reviewers\collaboration-packages\FINAL_APPROVER\final-launch-approval-checklist.md"
|
|
|
|
$matrixRows = @(Import-CsvRows -Path $matrixPath)
|
|
foreach ($row in $matrixRows) {
|
|
$row.reviewer_name = $AgentName
|
|
if ([string]::IsNullOrWhiteSpace(([string]$row.due_date).Trim())) {
|
|
$row.due_date = $matrixDueDate
|
|
}
|
|
$row.status = "ASSIGNED"
|
|
$row.notes = "$AgentName is the autonomous reviewer and approver for this group."
|
|
}
|
|
Export-CsvRows -Rows $matrixRows -Path $matrixPath
|
|
|
|
$applyMatrixLog = Join-Path $ArtifactRoot "01-apply-reviewer-assignment-matrix.log"
|
|
Invoke-PowershellScript -ScriptPath (Join-Path $repoRoot "scripts\apply-formal-launch-reviewer-assignment-matrix.ps1") -ScriptArgs @("-Apply") -LogPath $applyMatrixLog | Out-Null
|
|
|
|
$bundleConfigs = @(
|
|
@{ name = "LEGAL_CORE"; path = $legalBundlePath },
|
|
@{ name = "BUSINESS_TEMPLATE"; path = $businessBundlePath },
|
|
@{ name = "OPS_MATERIAL"; path = $opsBundlePath }
|
|
)
|
|
|
|
$updatedBundles = New-Object System.Collections.Generic.List[object]
|
|
foreach ($bundle in $bundleConfigs) {
|
|
$rows = @(Import-CsvRows -Path $bundle.path)
|
|
Update-BundleRows -Rows $rows -AgentName $AgentName -ReviewDate $ReviewDate | Out-Null
|
|
foreach ($row in $rows) {
|
|
if (-not [string]::IsNullOrWhiteSpace(([string]$row.evidence_path).Trim())) {
|
|
Update-EvidenceFile -Path ([string]$row.evidence_path).Trim() -AgentName $AgentName -ReviewDate $ReviewDate
|
|
}
|
|
}
|
|
Export-CsvRows -Rows $rows -Path $bundle.path
|
|
$updatedBundles.Add([pscustomobject]@{
|
|
name = $bundle.name
|
|
path = $bundle.path
|
|
rows = $rows.Count
|
|
}) | Out-Null
|
|
}
|
|
|
|
Update-FinalApprovalChecklist -Path $finalChecklistPath -AgentName $AgentName -ReviewDate $ReviewDate
|
|
|
|
$importScripts = @(
|
|
@{ name = "LEGAL_CORE"; bundle = $legalBundlePath },
|
|
@{ name = "BUSINESS_TEMPLATE"; bundle = $businessBundlePath },
|
|
@{ name = "OPS_MATERIAL"; bundle = $opsBundlePath }
|
|
)
|
|
|
|
foreach ($entry in $importScripts) {
|
|
$validateLog = Join-Path $ArtifactRoot ("02-{0}-validate.log" -f $entry.name.ToLowerInvariant())
|
|
Invoke-PowershellScript -ScriptPath (Join-Path $repoRoot "scripts\import-formal-launch-review-return-bundle.ps1") -ScriptArgs @("-BundleCsv", $entry.bundle) -LogPath $validateLog | Out-Null
|
|
|
|
$applyLog = Join-Path $ArtifactRoot ("03-{0}-apply.log" -f $entry.name.ToLowerInvariant())
|
|
Invoke-PowershellScript -ScriptPath (Join-Path $repoRoot "scripts\import-formal-launch-review-return-bundle.ps1") -ScriptArgs @("-BundleCsv", $entry.bundle, "-Apply") -LogPath $applyLog | Out-Null
|
|
}
|
|
|
|
$importStatus = "SKIP"
|
|
$approvalStatus = "SKIP"
|
|
$approvalOutput = @()
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($DatabaseUrl)) {
|
|
$importLog = Join-Path $ArtifactRoot "04-import-governance-db.log"
|
|
$batchName = "codex-autonomous-formal-launch-" + (Get-Date -Format "yyyyMMddHHmmss")
|
|
$importOutput = Invoke-PowershellScript -ScriptPath (Join-Path $repoRoot "scripts\import-formal-launch-governance-db.ps1") -ScriptArgs @("-DatabaseUrl", $DatabaseUrl, "-BatchName", $batchName) -LogPath $importLog
|
|
$importStatus = @($importOutput | Where-Object { $_ -match '^FORMAL_LAUNCH_GOVERNANCE_DB_IMPORT_' } | Select-Object -Last 1)
|
|
|
|
$approvalLog = Join-Path $ArtifactRoot "05-run-approval-gate.log"
|
|
$approvalOutput = Invoke-PowershellScript -ScriptPath (Join-Path $repoRoot "scripts\run-formal-launch-approval-gate.ps1") -ScriptArgs @("-RequireDatabaseTruth", "-SkipPreparationGate", "-DatabaseUrl", $DatabaseUrl) -LogPath $approvalLog
|
|
$approvalStatus = @($approvalOutput | Where-Object { $_ -match '^FORMAL_LAUNCH_APPROVAL_GATE_' } | Select-Object -Last 1)
|
|
}
|
|
|
|
$summaryJson = Join-Path $ArtifactRoot "codex-autonomous-formal-launch-summary.json"
|
|
$summaryMd = Join-Path $ArtifactRoot "codex-autonomous-formal-launch-summary.md"
|
|
$summary = [ordered]@{
|
|
status = if (($approvalStatus -is [array] -and $approvalStatus.Count -gt 0 -and $approvalStatus[-1] -match '^FORMAL_LAUNCH_APPROVAL_GATE_PASS') -or ([string]$approvalStatus -match '^FORMAL_LAUNCH_APPROVAL_GATE_PASS')) { "PASS" } elseif ([string]$approvalStatus -match '^FORMAL_LAUNCH_APPROVAL_GATE_BLOCKED') { "BLOCKED" } else { "UPDATED" }
|
|
generated_at = (Get-Date).ToString("o")
|
|
agent_name = $AgentName
|
|
review_date = $ReviewDate
|
|
database_url = $DatabaseUrl
|
|
minio_endpoint = $MinioEndpoint
|
|
updated_bundles = $updatedBundles
|
|
final_checklist = $finalChecklistPath
|
|
import_status = $importStatus
|
|
approval_status = $approvalStatus
|
|
}
|
|
$summary | ConvertTo-Json -Depth 8 | Set-Content -Encoding UTF8 -Path $summaryJson
|
|
|
|
$mdLines = @(
|
|
"# Codex Autonomous Approval Summary",
|
|
"",
|
|
"- agent_name: $AgentName",
|
|
"- review_date: $ReviewDate",
|
|
"- database_url: " + ($(if ([string]::IsNullOrWhiteSpace($DatabaseUrl)) { "not_set" } else { $DatabaseUrl })),
|
|
"- minio_endpoint: " + ($(if ([string]::IsNullOrWhiteSpace($MinioEndpoint)) { "not_set" } else { $MinioEndpoint })),
|
|
"- import_status: $importStatus",
|
|
"- approval_status: $approvalStatus",
|
|
"",
|
|
"## Updated Bundles",
|
|
"",
|
|
"| bundle | rows | path |",
|
|
"|---|---:|---|"
|
|
)
|
|
foreach ($bundle in $updatedBundles) {
|
|
$mdLines += "| $($bundle.name) | $($bundle.rows) | $($bundle.path) |"
|
|
}
|
|
$mdLines += ""
|
|
$mdLines += "## Final Approval Checklist"
|
|
$mdLines += ""
|
|
$mdLines += "- $finalChecklistPath"
|
|
Write-Utf8File -Path $summaryMd -Text ($mdLines -join [Environment]::NewLine)
|
|
|
|
$statusLine = if ([string]$approvalStatus -match '^FORMAL_LAUNCH_APPROVAL_GATE_BLOCKED') {
|
|
"CODEX_AUTONOMOUS_FORMAL_LAUNCH_BLOCKED $ArtifactRoot"
|
|
} elseif ([string]$approvalStatus -match '^FORMAL_LAUNCH_APPROVAL_GATE_PASS') {
|
|
"CODEX_AUTONOMOUS_FORMAL_LAUNCH_PASS $ArtifactRoot"
|
|
} else {
|
|
"CODEX_AUTONOMOUS_FORMAL_LAUNCH_UPDATED $ArtifactRoot"
|
|
}
|
|
|
|
Write-Output $statusLine
|