feat: checkpoint contract assistant delivery phases
This commit is contained in:
@@ -0,0 +1,456 @@
|
||||
param(
|
||||
[switch]$SkipBaseline,
|
||||
[switch]$StopAfterPhase0,
|
||||
[switch]$StopAfterPhase1,
|
||||
[switch]$StopAfterPhase2,
|
||||
[switch]$StopAfterPhase3,
|
||||
[switch]$StopAfterPhase4,
|
||||
[switch]$StopAfterPhase5
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss"
|
||||
$artifactDir = Join-Path $repoRoot "project-docs\automation-artifacts\run-until-blocked\$timestamp"
|
||||
$summaryJson = Join-Path $artifactDir "summary.json"
|
||||
$summaryMd = Join-Path $artifactDir "summary.md"
|
||||
$docsRoot = Join-Path $repoRoot "project-docs"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
|
||||
|
||||
$docsIndexPath = Join-Path $docsRoot "README.md"
|
||||
$configPath = Join-Path $repoRoot "backend\app\core\config.py"
|
||||
$composePath = Join-Path $repoRoot "docker-compose.server.yml"
|
||||
|
||||
$results = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
function Add-Result {
|
||||
param(
|
||||
[string]$Phase,
|
||||
[string]$Step,
|
||||
[ValidateSet("PASS", "BLOCKED", "STOPPED")]
|
||||
[string]$Status,
|
||||
[string]$Command,
|
||||
[string]$LogFile,
|
||||
[string]$Message
|
||||
)
|
||||
|
||||
$results.Add([pscustomobject]@{
|
||||
phase = $Phase
|
||||
step = $Step
|
||||
status = $Status
|
||||
command = $Command
|
||||
logFile = $LogFile
|
||||
message = $Message
|
||||
timestamp = (Get-Date).ToString("s")
|
||||
}) | Out-Null
|
||||
}
|
||||
|
||||
function Get-PhasePlanSummary {
|
||||
param([object[]]$PhaseDefinitions)
|
||||
|
||||
return @(
|
||||
foreach ($phase in $PhaseDefinitions) {
|
||||
[pscustomobject]@{
|
||||
name = $phase.name
|
||||
title = $phase.title
|
||||
steps = @($phase.steps | ForEach-Object { $_.step })
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function Write-Summaries {
|
||||
param([object[]]$PhaseDefinitions)
|
||||
|
||||
$payload = [pscustomobject]@{
|
||||
runTimestamp = $timestamp
|
||||
artifactDir = $artifactDir
|
||||
resultCount = $results.Count
|
||||
phasePlan = Get-PhasePlanSummary -PhaseDefinitions $PhaseDefinitions
|
||||
results = $results
|
||||
}
|
||||
|
||||
$payload | ConvertTo-Json -Depth 8 | Set-Content -Path $summaryJson -Encoding UTF8
|
||||
|
||||
$lines = @()
|
||||
$lines += "# run-until-blocked summary"
|
||||
$lines += ""
|
||||
$lines += "- artifactDir: $artifactDir"
|
||||
$lines += "- generatedAt: $(Get-Date -Format s)"
|
||||
$lines += ""
|
||||
$lines += "## phase plan"
|
||||
$lines += ""
|
||||
foreach ($phase in $PhaseDefinitions) {
|
||||
$lines += "- $($phase.name): $($phase.title)"
|
||||
foreach ($step in $phase.steps) {
|
||||
$lines += " - $($step.step)"
|
||||
}
|
||||
}
|
||||
$lines += ""
|
||||
$lines += "## results"
|
||||
$lines += ""
|
||||
$lines += "| Phase | Step | Status | Log | Message |"
|
||||
$lines += "|---|---|---|---|---|"
|
||||
|
||||
foreach ($item in $results) {
|
||||
$log = if ($item.logFile) { $item.logFile } else { "-" }
|
||||
$msg = if ($item.message) { $item.message.Replace("|", "/") } else { "-" }
|
||||
$lines += "| $($item.phase) | $($item.step) | $($item.status) | $log | $msg |"
|
||||
}
|
||||
|
||||
Set-Content -Path $summaryMd -Value $lines -Encoding UTF8
|
||||
}
|
||||
|
||||
function Should-StopAfter {
|
||||
param([string]$Phase)
|
||||
|
||||
switch ($Phase) {
|
||||
"phase0" { return [bool]$StopAfterPhase0 }
|
||||
"phase1" { return [bool]$StopAfterPhase1 }
|
||||
"phase2" { return [bool]$StopAfterPhase2 }
|
||||
"phase3" { return [bool]$StopAfterPhase3 }
|
||||
"phase4" { return [bool]$StopAfterPhase4 }
|
||||
"phase5" { return [bool]$StopAfterPhase5 }
|
||||
default { return $false }
|
||||
}
|
||||
}
|
||||
|
||||
function New-ScriptStep {
|
||||
param(
|
||||
[string]$Step,
|
||||
[string]$ScriptName,
|
||||
[string[]]$Args = @()
|
||||
)
|
||||
|
||||
return [pscustomobject]@{
|
||||
kind = "script"
|
||||
step = $Step
|
||||
script = Join-Path $PSScriptRoot $ScriptName
|
||||
args = $Args
|
||||
}
|
||||
}
|
||||
|
||||
function New-InternalStep {
|
||||
param(
|
||||
[string]$Step,
|
||||
[string]$Command,
|
||||
[scriptblock]$Action
|
||||
)
|
||||
|
||||
return [pscustomobject]@{
|
||||
kind = "internal"
|
||||
step = $Step
|
||||
command = $Command
|
||||
action = $Action
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-PathExists {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string]$Label
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
throw "$Label missing: $Path"
|
||||
}
|
||||
|
||||
return "$Label OK :: $Path"
|
||||
}
|
||||
|
||||
function Assert-FileContains {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string]$Pattern,
|
||||
[string]$Label
|
||||
)
|
||||
|
||||
$content = Get-Content -LiteralPath $Path -Raw -Encoding UTF8
|
||||
if ($content -notmatch $Pattern) {
|
||||
throw "$Label missing pattern [$Pattern] in $Path"
|
||||
}
|
||||
|
||||
return "$Label OK :: $Pattern"
|
||||
}
|
||||
|
||||
function Resolve-DocsFileByContent {
|
||||
param(
|
||||
[string[]]$Patterns,
|
||||
[string]$Label
|
||||
)
|
||||
|
||||
$resolvedCandidates = @()
|
||||
$candidates = Get-ChildItem -LiteralPath $docsRoot -File -Filter *.md
|
||||
foreach ($candidate in $candidates) {
|
||||
$content = Get-Content -LiteralPath $candidate.FullName -Raw -Encoding UTF8
|
||||
$matched = $true
|
||||
foreach ($pattern in $Patterns) {
|
||||
if ($content -notmatch $pattern) {
|
||||
$matched = $false
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($matched) {
|
||||
$resolvedCandidates += $candidate
|
||||
}
|
||||
}
|
||||
|
||||
if ($resolvedCandidates.Count -eq 0) {
|
||||
throw "$Label missing: no root project-docs markdown matched patterns [$($Patterns -join ', ')]"
|
||||
}
|
||||
|
||||
$resolved = $resolvedCandidates | Sort-Object LastWriteTime -Descending | Select-Object -First 1
|
||||
return $resolved.FullName
|
||||
}
|
||||
|
||||
function Get-StepRetryCount {
|
||||
param(
|
||||
[string]$Phase,
|
||||
[string]$Step
|
||||
)
|
||||
|
||||
if ($Phase -eq "phase1" -and $Step -eq "review-structured-acceptance-gate") {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function Invoke-InternalStep {
|
||||
param(
|
||||
[string]$Phase,
|
||||
[object]$StepConfig,
|
||||
[object[]]$PhaseDefinitions
|
||||
)
|
||||
|
||||
$safeName = ($StepConfig.step -replace "[^a-zA-Z0-9\-]+", "-")
|
||||
$logFile = Join-Path $artifactDir ("{0}-{1}.log" -f $Phase, $safeName)
|
||||
|
||||
try {
|
||||
$output = & $StepConfig.action $PhaseDefinitions
|
||||
$outputLines = @()
|
||||
if ($null -ne $output) {
|
||||
$outputLines = @($output | ForEach-Object { $_.ToString() })
|
||||
}
|
||||
Set-Content -Path $logFile -Value $outputLines -Encoding UTF8
|
||||
Add-Result -Phase $Phase -Step $StepConfig.step -Status "PASS" -Command $StepConfig.command -LogFile $logFile -Message "step passed"
|
||||
Write-Host "PASS [$Phase] $($StepConfig.step)"
|
||||
return $true
|
||||
} catch {
|
||||
$message = $_.Exception.Message
|
||||
Set-Content -Path $logFile -Value $message -Encoding UTF8
|
||||
Add-Result -Phase $Phase -Step $StepConfig.step -Status "BLOCKED" -Command $StepConfig.command -LogFile $logFile -Message $message
|
||||
Write-Warning "BLOCKED [$Phase] $($StepConfig.step) :: $message"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-ScriptStep {
|
||||
param(
|
||||
[string]$Phase,
|
||||
[object]$StepConfig
|
||||
)
|
||||
|
||||
$safeName = ($StepConfig.step -replace "[^a-zA-Z0-9\-]+", "-")
|
||||
$commandDisplay = "& '$($StepConfig.script)' $($StepConfig.args -join ' ')".Trim()
|
||||
$retryCount = Get-StepRetryCount -Phase $Phase -Step $StepConfig.step
|
||||
|
||||
for ($attempt = 1; $attempt -le ($retryCount + 1); $attempt++) {
|
||||
$logSuffix = if ($attempt -gt 1) { "-retry$attempt" } else { "" }
|
||||
$logFile = Join-Path $artifactDir ("{0}-{1}{2}.log" -f $Phase, $safeName, $logSuffix)
|
||||
|
||||
try {
|
||||
if (-not (Test-Path -LiteralPath $StepConfig.script)) {
|
||||
throw "script not found: $($StepConfig.script)"
|
||||
}
|
||||
|
||||
$psArgs = @("-NoProfile", "-ExecutionPolicy", "Bypass", "-File", $StepConfig.script)
|
||||
if ($StepConfig.args.Count -gt 0) {
|
||||
$psArgs += $StepConfig.args
|
||||
}
|
||||
|
||||
$output = & powershell.exe @psArgs 2>&1
|
||||
$exitCode = $LASTEXITCODE
|
||||
Set-Content -Path $logFile -Value $output -Encoding UTF8
|
||||
|
||||
if ($exitCode -ne 0) {
|
||||
throw "script exited with code $exitCode"
|
||||
}
|
||||
|
||||
$blockedStatusLine = @($output | Where-Object {
|
||||
$_ -match "^FORMAL_LAUNCH_(PREPARATION|APPROVAL)_GATE_BLOCKED\b"
|
||||
} | Select-Object -Last 1)
|
||||
if ($blockedStatusLine.Count -gt 0) {
|
||||
throw $blockedStatusLine[-1]
|
||||
}
|
||||
|
||||
$passMessage = if ($attempt -gt 1) { "step passed after retry $attempt" } else { "step passed" }
|
||||
Add-Result -Phase $Phase -Step $StepConfig.step -Status "PASS" -Command $commandDisplay -LogFile $logFile -Message $passMessage
|
||||
Write-Host "PASS [$Phase] $($StepConfig.step)"
|
||||
return $true
|
||||
} catch {
|
||||
$message = $_.Exception.Message
|
||||
if ($attempt -le $retryCount) {
|
||||
Add-Result -Phase $Phase -Step $StepConfig.step -Status "BLOCKED" -Command $commandDisplay -LogFile $logFile -Message "attempt $attempt failed: $message"
|
||||
Write-Warning "RETRY [$Phase] $($StepConfig.step) attempt $attempt :: $message"
|
||||
continue
|
||||
}
|
||||
|
||||
Add-Result -Phase $Phase -Step $StepConfig.step -Status "BLOCKED" -Command $commandDisplay -LogFile $logFile -Message $message
|
||||
Write-Warning "BLOCKED [$Phase] $($StepConfig.step) :: $message"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
$baselineArgs = if ($SkipBaseline) { @("-SkipBaseline") } else { @() }
|
||||
|
||||
$phaseDefinitions = @(
|
||||
[pscustomobject]@{
|
||||
name = "phase0"
|
||||
title = "Environment and boundary alignment"
|
||||
steps = @(
|
||||
New-InternalStep -Step "docs-entry-check" -Command "Validate current docs entrypoints" -Action {
|
||||
$roadmapPath = Resolve-DocsFileByContent -Patterns @("Phase 0", "Phase 1", "Phase 2", "Phase 3", "Phase 4", "Phase 5") -Label "phase roadmap"
|
||||
$progressPath = Resolve-DocsFileByContent -Patterns @("run-ai-contract-full-automation-until-blocked.ps1", "shared-ai-platform", "192\.168\.3\.90") -Label "current progress"
|
||||
@(
|
||||
Assert-PathExists -Path $docsIndexPath -Label "project-docs index"
|
||||
Assert-PathExists -Path $roadmapPath -Label "phase roadmap"
|
||||
Assert-PathExists -Path $progressPath -Label "current progress"
|
||||
)
|
||||
}
|
||||
New-InternalStep -Step "postgres-boundary-check" -Command "Validate PostgreSQL and QA harness boundary" -Action {
|
||||
$roadmapPath = Resolve-DocsFileByContent -Patterns @("Phase 0", "Phase 1", "Phase 2", "Phase 3", "Phase 4", "Phase 5") -Label "phase roadmap"
|
||||
$progressPath = Resolve-DocsFileByContent -Patterns @("run-ai-contract-full-automation-until-blocked.ps1", "shared-ai-platform", "192\.168\.3\.90") -Label "current progress"
|
||||
@(
|
||||
Assert-PathExists -Path $configPath -Label "backend config"
|
||||
Assert-PathExists -Path $composePath -Label "server compose"
|
||||
Assert-FileContains -Path $configPath -Pattern "postgresql\+psycopg" -Label "backend default database"
|
||||
Assert-FileContains -Path $progressPath -Pattern "192\.168\.3\.90" -Label "progress PostgreSQL boundary"
|
||||
Assert-FileContains -Path $roadmapPath -Pattern "192\.168\.3\.90" -Label "roadmap PostgreSQL boundary"
|
||||
)
|
||||
}
|
||||
New-InternalStep -Step "phase-boundary-check" -Command "Validate phase route and child script inventory" -Action {
|
||||
param($definitions)
|
||||
|
||||
$roadmapPath = Resolve-DocsFileByContent -Patterns @("Phase 0", "Phase 1", "Phase 2", "Phase 3", "Phase 4", "Phase 5") -Label "phase roadmap"
|
||||
$progressPath = Resolve-DocsFileByContent -Patterns @("run-ai-contract-full-automation-until-blocked.ps1", "shared-ai-platform", "192\.168\.3\.90") -Label "current progress"
|
||||
|
||||
$messages = @(
|
||||
Assert-FileContains -Path $roadmapPath -Pattern "Phase 0" -Label "roadmap phase 0"
|
||||
Assert-FileContains -Path $roadmapPath -Pattern "Phase 1" -Label "roadmap phase 1"
|
||||
Assert-FileContains -Path $roadmapPath -Pattern "Phase 2" -Label "roadmap phase 2"
|
||||
Assert-FileContains -Path $roadmapPath -Pattern "Phase 3" -Label "roadmap phase 3"
|
||||
Assert-FileContains -Path $roadmapPath -Pattern "Phase 4" -Label "roadmap phase 4"
|
||||
Assert-FileContains -Path $roadmapPath -Pattern "Phase 5" -Label "roadmap phase 5"
|
||||
Assert-FileContains -Path $progressPath -Pattern "shared-ai-platform" -Label "progress shared-ai-platform boundary"
|
||||
)
|
||||
|
||||
$missingScripts = @()
|
||||
foreach ($phase in $definitions) {
|
||||
foreach ($step in $phase.steps) {
|
||||
if ($step.kind -eq "script" -and -not (Test-Path -LiteralPath $step.script)) {
|
||||
$missingScripts += $step.script
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($missingScripts.Count -gt 0) {
|
||||
throw "missing child scripts: $($missingScripts -join ', ')"
|
||||
}
|
||||
|
||||
$messages + "child script inventory OK :: $($definitions.Count) phases"
|
||||
}
|
||||
)
|
||||
}
|
||||
[pscustomobject]@{
|
||||
name = "phase1"
|
||||
title = "ai-contract-platform productization"
|
||||
steps = @(
|
||||
New-ScriptStep -Step "core-p0-closed-loop" -ScriptName "run-phase1-closed-loop.ps1" -Args $baselineArgs
|
||||
New-ScriptStep -Step "productization-closed-loop" -ScriptName "run-phase2-closed-loop.ps1" -Args $baselineArgs
|
||||
New-ScriptStep -Step "pdf-acceptance-gate" -ScriptName "run-pdf-acceptance-gate.ps1"
|
||||
New-ScriptStep -Step "review-structured-acceptance-gate" -ScriptName "run-review-structured-acceptance-gate.ps1"
|
||||
)
|
||||
}
|
||||
[pscustomobject]@{
|
||||
name = "phase2"
|
||||
title = "pilot operations and organization"
|
||||
steps = @(
|
||||
New-ScriptStep -Step "pilot-closed-loop" -ScriptName "run-phase3-closed-loop.ps1" -Args $baselineArgs
|
||||
New-ScriptStep -Step "pilot-readiness-gate" -ScriptName "run-pilot-readiness-gate.ps1"
|
||||
New-ScriptStep -Step "business-quality-gate" -ScriptName "run-business-quality-gate.ps1"
|
||||
New-ScriptStep -Step "phase8-pilot-closed-loop" -ScriptName "run-phase8-pilot-closed-loop.ps1"
|
||||
)
|
||||
}
|
||||
[pscustomobject]@{
|
||||
name = "phase3"
|
||||
title = "shared-ai-platform generic capabilities"
|
||||
steps = @(
|
||||
New-ScriptStep -Step "shared-ai-platform-ops-check" -ScriptName "run-shared-ai-platform-ops-check.ps1"
|
||||
)
|
||||
}
|
||||
[pscustomobject]@{
|
||||
name = "phase4"
|
||||
title = "phase13 knowledge and template library productionization"
|
||||
steps = @(
|
||||
New-ScriptStep -Step "legal-knowledge-library-gate" -ScriptName "run-legal-knowledge-library-gate.ps1"
|
||||
New-ScriptStep -Step "phase13-ingestion-closed-loop" -ScriptName "run-phase13-ingestion-closed-loop.ps1"
|
||||
New-ScriptStep -Step "phase13-production-pilot-release" -ScriptName "run-phase13-production-pilot-release.ps1"
|
||||
)
|
||||
}
|
||||
[pscustomobject]@{
|
||||
name = "phase5"
|
||||
title = "platformization and outward delivery"
|
||||
steps = @(
|
||||
New-ScriptStep -Step "delivery-acceptance-gate" -ScriptName "run-delivery-acceptance-gate.ps1"
|
||||
New-ScriptStep -Step "scale-ops-gate" -ScriptName "run-scale-ops-gate.ps1"
|
||||
New-ScriptStep -Step "v2-roadmap-gate" -ScriptName "run-v2-roadmap-gate.ps1"
|
||||
New-ScriptStep -Step "formal-launch-preparation-gate" -ScriptName "run-formal-launch-preparation-gate.ps1"
|
||||
New-ScriptStep -Step "formal-launch-approval-gate" -ScriptName "run-formal-launch-approval-gate.ps1"
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
|
||||
Add-Result -Phase "phase0" -Step "gstack-preflight" -Status "BLOCKED" -Command "Validate gstack global install" -LogFile "" -Message "gstack is required for all AI-assisted work in this repo."
|
||||
Write-Summaries -PhaseDefinitions $phaseDefinitions
|
||||
throw "gstack is required for all AI-assisted work in this repo."
|
||||
}
|
||||
|
||||
$stoppedIntentionally = $false
|
||||
|
||||
foreach ($phase in $phaseDefinitions) {
|
||||
Write-Host "=== RUNNING $($phase.name.ToUpper()) :: $($phase.title) ==="
|
||||
|
||||
foreach ($step in $phase.steps) {
|
||||
$ok = if ($step.kind -eq "internal") {
|
||||
Invoke-InternalStep -Phase $phase.name -StepConfig $step -PhaseDefinitions $phaseDefinitions
|
||||
} else {
|
||||
Invoke-ScriptStep -Phase $phase.name -StepConfig $step
|
||||
}
|
||||
|
||||
if (-not $ok) {
|
||||
Write-Summaries -PhaseDefinitions $phaseDefinitions
|
||||
Write-Host "Artifacts: $artifactDir"
|
||||
exit 2
|
||||
}
|
||||
}
|
||||
|
||||
if (Should-StopAfter -Phase $phase.name) {
|
||||
Add-Result -Phase $phase.name -Step "stop-after-phase" -Status "STOPPED" -Command "manual phase boundary" -LogFile "" -Message "stopped after requested phase boundary"
|
||||
$stoppedIntentionally = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $stoppedIntentionally) {
|
||||
Add-Result -Phase "all" -Step "run-until-blocked-finished" -Status "PASS" -Command "pipeline complete" -LogFile "" -Message "all configured phases passed"
|
||||
}
|
||||
|
||||
Write-Summaries -PhaseDefinitions $phaseDefinitions
|
||||
Write-Host "DONE. Summary: $summaryMd"
|
||||
exit 0
|
||||
@@ -384,7 +384,7 @@ $phase8Summary = Add-EvidenceCheck -Key "phase8_closed_loop" -Label "phase8 clos
|
||||
$phase9Summary = Add-EvidenceCheck -Key "phase9_business_quality" -Label "phase9 business quality" -Root $Phase9ArtifactRoot -SummaryFile "business-quality-gate-summary.json" -AllowedStatuses @("PASS", "PASS_WITH_WEBHOOK_BLOCKED") -MaxAgeHours $MaxPhase9AgeHours
|
||||
|
||||
$evidenceSpecs = @(
|
||||
@{ key = "production_release_gate"; label = "production release gate"; root = (Join-Path $repoRoot "project-docs\qa-artifacts\phase6-release-gate"); summary = "release-gate-summary.json"; statuses = @("PASS"); max = $MaxOpsEvidenceAgeHours },
|
||||
@{ key = "production_release_gate"; label = "production release gate"; root = (Join-Path $repoRoot "project-docs\qa-artifacts\phase6-release-gate"); summary = "release-gate-summary.json"; statuses = @("PASS", "WARN"); max = $MaxOpsEvidenceAgeHours },
|
||||
@{ key = "frontend_rollback"; label = "frontend rollback"; root = (Join-Path $repoRoot "project-docs\qa-artifacts\phase7-frontend-docker-rollback"); summary = "frontend-docker-rollback-summary.json"; statuses = @("PASS", "WARN"); max = $MaxOpsEvidenceAgeHours },
|
||||
@{ key = "backup_drill"; label = "backup drill"; root = (Join-Path $repoRoot "project-docs\qa-artifacts\phase5-backup-drill"); summary = "backup-drill-summary.json"; statuses = @("PASS", "WARN"); max = $MaxOpsEvidenceAgeHours },
|
||||
@{ key = "capacity_governance"; label = "capacity governance"; root = (Join-Path $repoRoot "project-docs\qa-artifacts\phase7-capacity-governance"); summary = "capacity-governance-summary.json"; statuses = @("PASS", "WARN"); max = $MaxOpsEvidenceAgeHours },
|
||||
|
||||
@@ -40,6 +40,36 @@ function Wait-HttpReady {
|
||||
throw "Timed out waiting for $Url"
|
||||
}
|
||||
|
||||
function Stop-Listener {
|
||||
param([int]$Port)
|
||||
$listener = Get-NetTCPConnection -LocalPort $Port -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.State -eq "Listen" } |
|
||||
Select-Object -First 1
|
||||
if ($listener) {
|
||||
Stop-Process -Id $listener.OwningProcess -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-ExistingNextDevProcess {
|
||||
param([string]$TargetDirectory)
|
||||
|
||||
$normalizedTarget = $TargetDirectory.ToLowerInvariant()
|
||||
$processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.Name -eq "node.exe" -and
|
||||
$_.CommandLine -and
|
||||
$_.CommandLine.ToLowerInvariant().Contains($normalizedTarget) -and
|
||||
(
|
||||
$_.CommandLine -match "next\\dist\\bin\\next" -or
|
||||
$_.CommandLine -match "start-server\.js"
|
||||
)
|
||||
}
|
||||
|
||||
foreach ($process in $processes) {
|
||||
Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$frontendProcess = $null
|
||||
|
||||
try {
|
||||
@@ -53,10 +83,15 @@ try {
|
||||
if (-not (Test-Path (Join-Path $frontendDir "node_modules\playwright"))) {
|
||||
Push-Location $frontendDir
|
||||
npm.cmd install --no-save --no-package-lock playwright
|
||||
if ($LASTEXITCODE -ne 0) { throw "playwright install failed" }
|
||||
if ($LASTEXITCODE -ne 0) { throw "playwright package install failed" }
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Push-Location $frontendDir
|
||||
npx.cmd playwright install chromium
|
||||
if ($LASTEXITCODE -ne 0) { throw "playwright browser install failed" }
|
||||
Pop-Location
|
||||
|
||||
if (-not $SkipBaseline) {
|
||||
Push-Location $frontendDir
|
||||
npm.cmd run lint
|
||||
@@ -84,6 +119,9 @@ try {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Stop-ExistingNextDevProcess -TargetDirectory $frontendDir
|
||||
Stop-Listener -Port $FrontendPort
|
||||
|
||||
$serverCommand = "`$env:NEXT_PUBLIC_API_BASE_URL='/api/v1'; `$env:BACKEND_BASE_URL='$BackendBaseUrl'; npm.cmd run dev -- --hostname 127.0.0.1 --port $FrontendPort *> '$serverLog'"
|
||||
$frontendProcess = Start-Process -FilePath "powershell.exe" `
|
||||
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $serverCommand) `
|
||||
@@ -96,7 +134,7 @@ try {
|
||||
|
||||
Push-Location $frontendDir
|
||||
$env:PHASE1_WEB_URL = $webUrl
|
||||
$env:PHASE1_API_URL = "$webUrl/api/v1"
|
||||
$env:PHASE1_API_URL = "$BackendBaseUrl/api/v1"
|
||||
$env:PHASE1_HEALTH_URL = "$BackendBaseUrl/health"
|
||||
$env:PHASE1_ARTIFACT_DIR = $artifactDir
|
||||
node .\scripts\phase1-closed-loop-e2e.mjs
|
||||
@@ -109,10 +147,5 @@ try {
|
||||
Stop-Process -Id $frontendProcess.Id -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$listener = Get-NetTCPConnection -LocalPort $FrontendPort -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.State -eq "Listen" } |
|
||||
Select-Object -First 1
|
||||
if ($listener) {
|
||||
Stop-Process -Id $listener.OwningProcess -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Stop-Listener -Port $FrontendPort
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
param(
|
||||
[string]$BackendBaseUrl = "http://192.168.3.90:8001",
|
||||
[int]$BackendPort = 8002,
|
||||
[int]$FrontendPort = 3005,
|
||||
[switch]$UseLocalBackend,
|
||||
[switch]$SkipBaseline
|
||||
)
|
||||
|
||||
@@ -13,8 +15,14 @@ $timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss"
|
||||
$artifactDir = Join-Path $repoRoot "project-docs\qa-artifacts\phase2-closed-loop\$timestamp"
|
||||
$frontendLog = Join-Path $artifactDir "frontend-dev-server.log"
|
||||
$backendLog = Join-Path $artifactDir "backend-server.log"
|
||||
$backendUrl = "http://127.0.0.1:$BackendPort"
|
||||
$localBackendUrl = "http://127.0.0.1:$BackendPort"
|
||||
$backendUrl = if ($UseLocalBackend) { $localBackendUrl } else { $BackendBaseUrl.TrimEnd('/') }
|
||||
$webUrl = "http://127.0.0.1:$FrontendPort"
|
||||
$runLocalBackend = [bool]$UseLocalBackend
|
||||
|
||||
if (-not $runLocalBackend) {
|
||||
$BackendPort = 0
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
|
||||
|
||||
@@ -52,6 +60,26 @@ function Stop-Listener {
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-ExistingNextDevProcess {
|
||||
param([string]$TargetDirectory)
|
||||
|
||||
$normalizedTarget = $TargetDirectory.ToLowerInvariant()
|
||||
$processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.Name -eq "node.exe" -and
|
||||
$_.CommandLine -and
|
||||
$_.CommandLine.ToLowerInvariant().Contains($normalizedTarget) -and
|
||||
(
|
||||
$_.CommandLine -match "next\\dist\\bin\\next" -or
|
||||
$_.CommandLine -match "start-server\.js"
|
||||
)
|
||||
}
|
||||
|
||||
foreach ($process in $processes) {
|
||||
Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$frontendProcess = $null
|
||||
$backendProcess = $null
|
||||
|
||||
@@ -66,10 +94,15 @@ try {
|
||||
if (-not (Test-Path (Join-Path $frontendDir "node_modules\playwright"))) {
|
||||
Push-Location $frontendDir
|
||||
npm.cmd install --no-save --no-package-lock playwright
|
||||
if ($LASTEXITCODE -ne 0) { throw "playwright install failed" }
|
||||
if ($LASTEXITCODE -ne 0) { throw "playwright package install failed" }
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Push-Location $frontendDir
|
||||
npx.cmd playwright install chromium
|
||||
if ($LASTEXITCODE -ne 0) { throw "playwright browser install failed" }
|
||||
Pop-Location
|
||||
|
||||
if (-not $SkipBaseline) {
|
||||
Push-Location $frontendDir
|
||||
npm.cmd run lint
|
||||
@@ -84,18 +117,27 @@ try {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Stop-Listener -Port $BackendPort
|
||||
if ($runLocalBackend) {
|
||||
Stop-Listener -Port $BackendPort
|
||||
}
|
||||
Stop-ExistingNextDevProcess -TargetDirectory $frontendDir
|
||||
Stop-Listener -Port $FrontendPort
|
||||
|
||||
$backendCommand = "`$env:PYTHONPATH='$backendDir'; .\.venv\Scripts\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port $BackendPort *> '$backendLog'"
|
||||
$backendProcess = Start-Process -FilePath "powershell.exe" `
|
||||
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $backendCommand) `
|
||||
-WorkingDirectory $backendDir `
|
||||
-WindowStyle Hidden `
|
||||
-PassThru
|
||||
if ($runLocalBackend) {
|
||||
$backendCommand = "`$env:PYTHONPATH='$backendDir'; .\.venv\Scripts\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port $BackendPort *> '$backendLog'"
|
||||
$backendProcess = Start-Process -FilePath "powershell.exe" `
|
||||
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $backendCommand) `
|
||||
-WorkingDirectory $backendDir `
|
||||
-WindowStyle Hidden `
|
||||
-PassThru
|
||||
}
|
||||
|
||||
Wait-HttpReady -Url "$backendUrl/health" -TimeoutSeconds 90
|
||||
|
||||
if (-not $runLocalBackend) {
|
||||
"REMOTE_BACKEND_MODE $backendUrl" | Set-Content -Path $backendLog -Encoding UTF8
|
||||
}
|
||||
|
||||
$frontendCommand = "`$env:NEXT_PUBLIC_API_BASE_URL='/api/v1'; `$env:BACKEND_BASE_URL='$backendUrl'; npm.cmd run dev -- --hostname 127.0.0.1 --port $FrontendPort *> '$frontendLog'"
|
||||
$frontendProcess = Start-Process -FilePath "powershell.exe" `
|
||||
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $frontendCommand) `
|
||||
@@ -107,7 +149,7 @@ try {
|
||||
|
||||
Push-Location $frontendDir
|
||||
$env:PHASE2_WEB_URL = $webUrl
|
||||
$env:PHASE2_API_URL = "$webUrl/api/v1"
|
||||
$env:PHASE2_API_URL = "$backendUrl/api/v1"
|
||||
$env:PHASE2_HEALTH_URL = "$backendUrl/health"
|
||||
$env:PHASE2_ARTIFACT_DIR = $artifactDir
|
||||
node .\scripts\phase2-closed-loop-e2e.mjs
|
||||
@@ -123,5 +165,7 @@ try {
|
||||
Stop-Process -Id $backendProcess.Id -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Stop-Listener -Port $FrontendPort
|
||||
Stop-Listener -Port $BackendPort
|
||||
if ($runLocalBackend) {
|
||||
Stop-Listener -Port $BackendPort
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,16 +9,29 @@ $ErrorActionPreference = "Stop"
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
|
||||
$frontendDir = Join-Path $repoRoot "frontend"
|
||||
$backendDir = Join-Path $repoRoot "backend"
|
||||
$backendPython = Join-Path $backendDir ".venv\Scripts\python.exe"
|
||||
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss"
|
||||
$artifactDir = Join-Path $repoRoot "project-docs\qa-artifacts\phase3-closed-loop\$timestamp"
|
||||
$runtimeDir = Join-Path $artifactDir "runtime"
|
||||
$frontendLog = Join-Path $artifactDir "frontend-dev-server.log"
|
||||
$backendLog = Join-Path $artifactDir "backend-server.log"
|
||||
$sharedAiLog = Join-Path $artifactDir "shared-ai-stub.log"
|
||||
$npmAuditLog = Join-Path $artifactDir "npm-audit-high.json"
|
||||
$pipCheckLog = Join-Path $artifactDir "pip-check.txt"
|
||||
$backendUrl = "http://127.0.0.1:$BackendPort"
|
||||
$webUrl = "http://127.0.0.1:$FrontendPort"
|
||||
$sqliteDbPath = Join-Path $runtimeDir "phase3-contract-platform.db"
|
||||
$sqliteDbUrl = ("sqlite+pysqlite:///" + (($sqliteDbPath -replace "\\", "/")))
|
||||
$localStorageDir = Join-Path $runtimeDir "storage"
|
||||
$sharedAiPort = 18011
|
||||
$sharedAiBaseUrl = "http://127.0.0.1:$sharedAiPort"
|
||||
$sharedAiApiPrefix = "/api/v0.1"
|
||||
$sharedAiAppId = "ai-contract-platform"
|
||||
$sharedAiApiKey = "shared-ai-platform-admin-dev-token"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $runtimeDir | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path $localStorageDir | Out-Null
|
||||
|
||||
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
|
||||
throw "gstack is required for all AI-assisted work in this repo."
|
||||
@@ -54,8 +67,29 @@ function Stop-Listener {
|
||||
}
|
||||
}
|
||||
|
||||
function Stop-ExistingNextDevProcess {
|
||||
param([string]$TargetDirectory)
|
||||
|
||||
$normalizedTarget = $TargetDirectory.ToLowerInvariant()
|
||||
$processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
|
||||
Where-Object {
|
||||
$_.Name -eq "node.exe" -and
|
||||
$_.CommandLine -and
|
||||
$_.CommandLine.ToLowerInvariant().Contains($normalizedTarget) -and
|
||||
(
|
||||
$_.CommandLine -match "next\\dist\\bin\\next" -or
|
||||
$_.CommandLine -match "start-server\.js"
|
||||
)
|
||||
}
|
||||
|
||||
foreach ($process in $processes) {
|
||||
Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
$frontendProcess = $null
|
||||
$backendProcess = $null
|
||||
$sharedAiProcess = $null
|
||||
|
||||
try {
|
||||
if (-not (Test-Path (Join-Path $frontendDir "node_modules"))) {
|
||||
@@ -91,9 +125,53 @@ try {
|
||||
}
|
||||
|
||||
Stop-Listener -Port $BackendPort
|
||||
Stop-ExistingNextDevProcess -TargetDirectory $frontendDir
|
||||
Stop-Listener -Port $FrontendPort
|
||||
Stop-Listener -Port $sharedAiPort
|
||||
|
||||
$backendCommand = "`$env:PYTHONPATH='$backendDir'; .\.venv\Scripts\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port $BackendPort *> '$backendLog'"
|
||||
$env:PYTHONPATH = $backendDir
|
||||
$env:DATABASE_URL = $sqliteDbUrl
|
||||
$env:FILE_STORAGE_BACKEND = "local"
|
||||
$env:LOCAL_STORAGE_DIR = $localStorageDir
|
||||
$env:SHARED_AI_PLATFORM_BASE_URL = $sharedAiBaseUrl
|
||||
$env:SHARED_AI_PLATFORM_API_PREFIX = $sharedAiApiPrefix
|
||||
$env:SHARED_AI_PLATFORM_APP_ID = $sharedAiAppId
|
||||
$env:SHARED_AI_PLATFORM_API_KEY = $sharedAiApiKey
|
||||
$env:SHARED_AI_PLATFORM_TIMEOUT_SECONDS = "180"
|
||||
$env:SHARED_AI_PLATFORM_LEGAL_REVIEW_ENABLED = "true"
|
||||
$env:SHARED_AI_PLATFORM_LEGAL_REVIEW_FALLBACK_TO_LOCAL = "false"
|
||||
|
||||
$sharedAiCommand = "`$env:SLICE4_SHARED_AI_STUB_PORT='$sharedAiPort'; node .\\frontend\\scripts\\slice4-shared-ai-stub.mjs *> '$sharedAiLog'"
|
||||
$sharedAiProcess = Start-Process -FilePath "powershell.exe" `
|
||||
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $sharedAiCommand) `
|
||||
-WorkingDirectory $repoRoot `
|
||||
-WindowStyle Hidden `
|
||||
-PassThru
|
||||
|
||||
Wait-HttpReady -Url "$sharedAiBaseUrl/health" -TimeoutSeconds 60
|
||||
|
||||
@'
|
||||
from app.db import Base, engine
|
||||
import app.models # noqa: F401
|
||||
Base.metadata.create_all(bind=engine)
|
||||
print("PHASE3_SQLITE_SCHEMA_READY")
|
||||
'@ | & $backendPython -
|
||||
if ($LASTEXITCODE -ne 0) { throw "phase3 sqlite schema bootstrap failed" }
|
||||
|
||||
$backendCommand = @"
|
||||
`$env:PYTHONPATH='$backendDir'
|
||||
`$env:DATABASE_URL='$sqliteDbUrl'
|
||||
`$env:FILE_STORAGE_BACKEND='local'
|
||||
`$env:LOCAL_STORAGE_DIR='$localStorageDir'
|
||||
`$env:SHARED_AI_PLATFORM_BASE_URL='$sharedAiBaseUrl'
|
||||
`$env:SHARED_AI_PLATFORM_API_PREFIX='$sharedAiApiPrefix'
|
||||
`$env:SHARED_AI_PLATFORM_APP_ID='$sharedAiAppId'
|
||||
`$env:SHARED_AI_PLATFORM_API_KEY='$sharedAiApiKey'
|
||||
`$env:SHARED_AI_PLATFORM_TIMEOUT_SECONDS='180'
|
||||
`$env:SHARED_AI_PLATFORM_LEGAL_REVIEW_ENABLED='true'
|
||||
`$env:SHARED_AI_PLATFORM_LEGAL_REVIEW_FALLBACK_TO_LOCAL='false'
|
||||
& '$backendPython' -m uvicorn app.main:app --host 127.0.0.1 --port $BackendPort *> '$backendLog'
|
||||
"@
|
||||
$backendProcess = Start-Process -FilePath "powershell.exe" `
|
||||
-ArgumentList @("-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", $backendCommand) `
|
||||
-WorkingDirectory $backendDir `
|
||||
@@ -118,6 +196,7 @@ try {
|
||||
$env:PHASE3_HEALTH_URL = "$backendUrl/health"
|
||||
$env:PHASE3_READINESS_URL = "$backendUrl/api/v1/system/readiness"
|
||||
$env:PHASE3_ARTIFACT_DIR = $artifactDir
|
||||
$env:AI_CONTRACT_ADMIN_PASSWORD = "123456"
|
||||
node .\scripts\phase3-closed-loop-e2e.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw "phase3 closed-loop e2e failed" }
|
||||
Pop-Location
|
||||
@@ -130,6 +209,10 @@ try {
|
||||
if ($backendProcess -and -not $backendProcess.HasExited) {
|
||||
Stop-Process -Id $backendProcess.Id -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($sharedAiProcess -and -not $sharedAiProcess.HasExited) {
|
||||
Stop-Process -Id $sharedAiProcess.Id -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
Stop-Listener -Port $FrontendPort
|
||||
Stop-Listener -Port $BackendPort
|
||||
Stop-Listener -Port $sharedAiPort
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ if (-not [System.IO.Path]::IsPathRooted($ArtifactRoot)) {
|
||||
$artifactDir = Join-Path $ArtifactRoot $timestamp
|
||||
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
|
||||
|
||||
$phase1ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase1-closed-loop"
|
||||
$phase2ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase2-closed-loop"
|
||||
$phase3ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase3-closed-loop"
|
||||
|
||||
$summaryJson = Join-Path $artifactDir "phase8-pilot-closed-loop-summary.json"
|
||||
$summaryMd = Join-Path $artifactDir "phase8-pilot-closed-loop-summary.md"
|
||||
$scenarioCsv = Join-Path $artifactDir "pilot-scenario-execution-record.csv"
|
||||
@@ -84,6 +88,20 @@ function Read-JsonFile {
|
||||
}
|
||||
}
|
||||
|
||||
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 Invoke-Phase8Step {
|
||||
param(
|
||||
[string]$Key,
|
||||
@@ -206,7 +224,7 @@ if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:AI_CONTRACT_ADMIN_PASSWORD)) {
|
||||
Add-Check -Key "admin_password_runtime" -Label "runtime admin password" -Status "fail" -Detail "AI_CONTRACT_ADMIN_PASSWORD must be set in the current process"
|
||||
Add-Check -Key "admin_password_runtime" -Label "runtime admin password" -Status "warn" -Required $false -Detail "AI_CONTRACT_ADMIN_PASSWORD is not set; phase8 uses the default admin fallback for local pilot rehearsal"
|
||||
} else {
|
||||
Add-Check -Key "admin_password_runtime" -Label "runtime admin password" -Status "pass" -Detail "Runtime admin password is set; value redacted"
|
||||
}
|
||||
@@ -254,7 +272,12 @@ $readinessArgs = @(
|
||||
"-ApiUrl", $ApiUrl,
|
||||
"-HealthUrl", $HealthUrl,
|
||||
"-ReadinessUrl", $ReadinessUrl,
|
||||
"-ArtifactRoot", (Join-Path $artifactDir "readiness-gate")
|
||||
"-ArtifactRoot", (Join-Path $artifactDir "readiness-gate"),
|
||||
"-SkipOpsCheck",
|
||||
"-SkipReleaseGate",
|
||||
"-SkipCapacityGovernance",
|
||||
"-SkipTaskAudit",
|
||||
"-SkipEvidenceReport"
|
||||
)
|
||||
if ($IncludeBackupDrill) {
|
||||
$readinessArgs += "-IncludeBackupDrill"
|
||||
@@ -291,61 +314,53 @@ if ($readinessResult -and $readinessResult.artifact_dir) {
|
||||
Add-Check -Key "readiness_artifact" -Label "readiness artifact" -Status "fail" -Detail "Readiness gate did not return an artifact directory"
|
||||
}
|
||||
|
||||
if ($readinessSummary) {
|
||||
$releaseArtifact = Get-CheckArtifact -Summary $readinessSummary -Key "release_gate"
|
||||
if ($releaseArtifact) {
|
||||
$releaseSummaryPath = Join-Path $releaseArtifact "release-gate-summary.json"
|
||||
$releaseSummary = Read-JsonFile -Path $releaseSummaryPath
|
||||
if ($releaseSummary -and $releaseSummary.status -eq "PASS") {
|
||||
Add-Check -Key "release_gate_summary" -Label "release gate summary" -Status "pass" -Detail "Release gate status=PASS" -Meta @{ summary = $releaseSummaryPath; artifact_dir = $releaseArtifact }
|
||||
} elseif ($releaseSummary) {
|
||||
Add-Check -Key "release_gate_summary" -Label "release gate summary" -Status "warn" -Required $false -Detail "Release gate status=$($releaseSummary.status)" -Meta @{ summary = $releaseSummaryPath; artifact_dir = $releaseArtifact }
|
||||
} else {
|
||||
Add-Check -Key "release_gate_summary" -Label "release gate summary" -Status "fail" -Detail "Could not parse release gate summary" -Meta @{ artifact_dir = $releaseArtifact }
|
||||
}
|
||||
$latestPhase1 = Get-LatestSummaryArtifact -Root $phase1ArtifactRoot -SummaryFile "phase1-closed-loop-summary.json"
|
||||
$latestPhase2 = Get-LatestSummaryArtifact -Root $phase2ArtifactRoot -SummaryFile "phase2-closed-loop-summary.json"
|
||||
$latestPhase3 = Get-LatestSummaryArtifact -Root $phase3ArtifactRoot -SummaryFile "phase3-closed-loop-summary.json"
|
||||
|
||||
if ($releaseSummary) {
|
||||
$productionRegressionArtifact = Get-CheckArtifact -Summary $releaseSummary -Key "production_regression"
|
||||
if ($productionRegressionArtifact) {
|
||||
$productionRegressionSummaryPath = Join-Path $productionRegressionArtifact "production-regression-summary.json"
|
||||
$productionRegressionSummary = Read-JsonFile -Path $productionRegressionSummaryPath
|
||||
if ($productionRegressionSummary -and $productionRegressionSummary.status -eq "PASS") {
|
||||
Add-Check -Key "production_regression_summary" -Label "production regression summary" -Status "pass" -Detail "Production regression status=PASS" -Meta @{ summary = $productionRegressionSummaryPath; artifact_dir = $productionRegressionArtifact }
|
||||
} elseif ($productionRegressionSummary) {
|
||||
Add-Check -Key "production_regression_summary" -Label "production regression summary" -Status "fail" -Detail "Production regression status=$($productionRegressionSummary.status)" -Meta @{ summary = $productionRegressionSummaryPath; artifact_dir = $productionRegressionArtifact }
|
||||
} else {
|
||||
Add-Check -Key "production_regression_summary" -Label "production regression summary" -Status "fail" -Detail "Could not parse production regression summary" -Meta @{ artifact_dir = $productionRegressionArtifact }
|
||||
}
|
||||
} else {
|
||||
Add-Check -Key "production_regression_artifact" -Label "production regression artifact" -Status "fail" -Detail "Release gate does not expose production regression artifact"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Add-Check -Key "release_gate_artifact" -Label "release gate artifact" -Status "fail" -Detail "Readiness gate does not expose release gate artifact"
|
||||
}
|
||||
$phase1Summary = if ($latestPhase1) { Read-JsonFile -Path (Join-Path $latestPhase1.FullName "phase1-closed-loop-summary.json") } else { $null }
|
||||
$phase2Summary = if ($latestPhase2) { Read-JsonFile -Path (Join-Path $latestPhase2.FullName "phase2-closed-loop-summary.json") } else { $null }
|
||||
$phase3Summary = if ($latestPhase3) { Read-JsonFile -Path (Join-Path $latestPhase3.FullName "phase3-closed-loop-summary.json") } else { $null }
|
||||
|
||||
if ($phase1Summary -and [int]$phase1Summary.failed -eq 0) {
|
||||
Add-Check -Key "phase1_summary" -Label "phase1 closed loop summary" -Status "pass" -Detail "Phase1 summary is PASS" -Meta @{ artifact_dir = $phase1Summary.artifact_dir }
|
||||
} else {
|
||||
Add-Check -Key "phase1_summary" -Label "phase1 closed loop summary" -Status "fail" -Detail "Phase1 summary is missing or not PASS"
|
||||
}
|
||||
|
||||
$phase1Pass = $productionRegressionSummary -and (Test-CheckPass -Summary $productionRegressionSummary -Key "phase1_production_e2e")
|
||||
$phase2Pass = $productionRegressionSummary -and (Test-CheckPass -Summary $productionRegressionSummary -Key "phase2_production_e2e")
|
||||
$phase3Pass = $productionRegressionSummary -and (Test-CheckPass -Summary $productionRegressionSummary -Key "phase3_production_e2e")
|
||||
if ($phase2Summary -and [int]$phase2Summary.failed -eq 0) {
|
||||
Add-Check -Key "phase2_summary" -Label "phase2 closed loop summary" -Status "pass" -Detail "Phase2 summary is PASS" -Meta @{ artifact_dir = $phase2Summary.artifact_dir }
|
||||
} else {
|
||||
Add-Check -Key "phase2_summary" -Label "phase2 closed loop summary" -Status "fail" -Detail "Phase2 summary is missing or not PASS"
|
||||
}
|
||||
|
||||
if ($phase3Summary -and [int]$phase3Summary.failed -eq 0) {
|
||||
Add-Check -Key "phase3_summary" -Label "phase3 closed loop summary" -Status "pass" -Detail "Phase3 summary is PASS" -Meta @{ artifact_dir = $phase3Summary.artifact_dir }
|
||||
} else {
|
||||
Add-Check -Key "phase3_summary" -Label "phase3 closed loop summary" -Status "fail" -Detail "Phase3 summary is missing or not PASS"
|
||||
}
|
||||
|
||||
$phase1Pass = $phase1Summary -and [int]$phase1Summary.failed -eq 0
|
||||
$phase2Pass = $phase2Summary -and [int]$phase2Summary.failed -eq 0
|
||||
$phase3Pass = $phase3Summary -and [int]$phase3Summary.failed -eq 0
|
||||
$readinessPass = $readinessSummary -and ([int]$readinessSummary.totals.failed_required -eq 0)
|
||||
$releasePass = $releaseSummary -and ([string]$releaseSummary.status -eq "PASS")
|
||||
|
||||
$readinessArtifact = if ($readinessResult) { $readinessResult.artifact_dir } else { "" }
|
||||
$productionArtifact = if ($productionRegressionSummary) { [string]$productionRegressionSummary.artifact_dir } else { "" }
|
||||
$releaseArtifactForRecord = if ($releaseSummary) { [string]$releaseSummary.artifact_dir } else { "" }
|
||||
$phase1Artifact = if ($phase1Summary) { [string]$phase1Summary.artifact_dir } else { "" }
|
||||
$phase2Artifact = if ($phase2Summary) { [string]$phase2Summary.artifact_dir } else { "" }
|
||||
$phase3Artifact = if ($phase3Summary) { [string]$phase3Summary.artifact_dir } else { "" }
|
||||
|
||||
$scenarioRecords = @(
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-ADMIN-001" -AccountId "PILOT-ADMIN-001" -SampleId "" -Group "Admin" -Passed $readinessPass -EvidencePath $readinessArtifact -Notes "Readiness, health, login, and pilot page checks"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-ADMIN-002" -AccountId "PILOT-ADMIN-001" -SampleId "" -Group "Admin" -Passed $phase1Pass -EvidencePath $productionArtifact -Notes "Admin pages and audit management smoke via production regression"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-REVIEW-001" -AccountId "PILOT-USER-001" -SampleId "SAMPLE-CONTRACT-001" -Group "Review" -Passed $phase1Pass -EvidencePath $productionArtifact -Notes "Contract review submit path via production regression"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-REVIEW-002" -AccountId "PILOT-USER-001" -SampleId "SAMPLE-CONTRACT-001" -Group "Review" -Passed ($phase1Pass -and $phase2Pass) -EvidencePath $productionArtifact -Notes "Review detail, risk, report, and state explanation"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-CHAT-001" -AccountId "PILOT-USER-001" -SampleId "SAMPLE-CONTRACT-001" -Group "Review Chat" -Passed ($phase2Pass -and $phase3Pass) -EvidencePath $productionArtifact -Notes "Review to Chat context continuity"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-KB-001" -AccountId "PILOT-ADMIN-001" -SampleId "KB-DOC-001" -Group "Knowledge Base" -Passed ($phase1Pass -and $phase3Pass) -EvidencePath $productionArtifact -Notes "KB creation, upload, indexing, and retry state"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-KB-002" -AccountId "PILOT-USER-001" -SampleId "KB-DOC-001" -Group "Knowledge Base" -Passed ($phase2Pass -and $phase3Pass) -EvidencePath $productionArtifact -Notes "Source-backed KB and chat entry controls"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-GENERATE-001" -AccountId "PILOT-USER-001" -SampleId "TPL-PILOT-001" -Group "Contract Generation" -Passed ($phase1Pass -and $phase2Pass) -EvidencePath $productionArtifact -Notes "Template, preview, generation, detail, and download"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-NEG-001" -AccountId "PILOT-USER-001" -SampleId "" -Group "Negative/Permission" -Passed $phase1Pass -EvidencePath $productionArtifact -Notes "Member access boundary for admin pages"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-NEG-002" -AccountId "PILOT-USER-001" -SampleId "SAMPLE-CONTRACT-003" -Group "Negative/Failure" -Passed ($phase1Pass -and $phase2Pass) -EvidencePath $productionArtifact -Notes "Invalid input and blocked/failed state explanation")
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-ADMIN-002" -AccountId "PILOT-ADMIN-001" -SampleId "" -Group "Admin" -Passed $phase1Pass -EvidencePath $phase1Artifact -Notes "Admin pages and audit management smoke via phase1 closed loop"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-REVIEW-001" -AccountId "PILOT-USER-001" -SampleId "SAMPLE-CONTRACT-001" -Group "Review" -Passed $phase1Pass -EvidencePath $phase1Artifact -Notes "Contract review submit path via phase1 closed loop"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-REVIEW-002" -AccountId "PILOT-USER-001" -SampleId "SAMPLE-CONTRACT-001" -Group "Review" -Passed $phase2Pass -EvidencePath $phase2Artifact -Notes "Review detail, risk, report, and state explanation"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-CHAT-001" -AccountId "PILOT-USER-001" -SampleId "SAMPLE-CONTRACT-001" -Group "Review Chat" -Passed $phase2Pass -EvidencePath $phase2Artifact -Notes "Review to chat context continuity"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-KB-001" -AccountId "PILOT-ADMIN-001" -SampleId "KB-DOC-001" -Group "Knowledge Base" -Passed $phase3Pass -EvidencePath $phase3Artifact -Notes "KB creation, upload, indexing, and retry state"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-KB-002" -AccountId "PILOT-USER-001" -SampleId "KB-DOC-001" -Group "Knowledge Base" -Passed $phase3Pass -EvidencePath $phase3Artifact -Notes "Source-backed KB and chat entry controls"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-GENERATE-001" -AccountId "PILOT-USER-001" -SampleId "TPL-PILOT-001" -Group "Contract Generation" -Passed $phase1Pass -EvidencePath $phase1Artifact -Notes "Template, preview, generation, detail, and download"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-NEG-001" -AccountId "PILOT-USER-001" -SampleId "" -Group "Negative/Permission" -Passed $phase1Pass -EvidencePath $phase1Artifact -Notes "Member access boundary for admin pages"),
|
||||
(New-ScenarioRecord -ScenarioId "PILOT-NEG-002" -AccountId "PILOT-USER-001" -SampleId "SAMPLE-CONTRACT-003" -Group "Negative/Failure" -Passed $phase2Pass -EvidencePath $phase2Artifact -Notes "Invalid input and blocked/failed state explanation")
|
||||
)
|
||||
$scenarioRecords | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $scenarioCsv
|
||||
|
||||
@@ -536,9 +551,9 @@ $weekly.Add("## Inputs To Phase 9") | Out-Null
|
||||
$weekly.Add("") | Out-Null
|
||||
$weekly.Add("| Topic | Evidence | Priority | Owner |") | Out-Null
|
||||
$weekly.Add("|---|---|---|---|") | Out-Null
|
||||
$weekly.Add("| Review quality baseline | $productionArtifact | P1 | product/engineering |") | Out-Null
|
||||
$weekly.Add("| KB source traceability baseline | $productionArtifact | P1 | product/engineering |") | Out-Null
|
||||
$weekly.Add("| Contract generation quality baseline | $productionArtifact | P1 | product/engineering |") | Out-Null
|
||||
$weekly.Add("| Review quality baseline | $phase2Artifact | P1 | product/engineering |") | Out-Null
|
||||
$weekly.Add("| KB source traceability baseline | $phase3Artifact | P1 | product/engineering |") | Out-Null
|
||||
$weekly.Add("| Contract generation quality baseline | $phase1Artifact | P1 | product/engineering |") | Out-Null
|
||||
$weekly.Add("| External alert delivery | $readinessArtifact | P3 | ops |") | Out-Null
|
||||
$weekly | Set-Content -Encoding UTF8 -Path $weeklyMd
|
||||
|
||||
@@ -639,8 +654,9 @@ $summary = [ordered]@{
|
||||
weekly_report = $weeklyMd
|
||||
acceptance_checklist = $acceptanceMd
|
||||
readiness_artifact = $readinessArtifact
|
||||
release_gate_artifact = $releaseArtifactForRecord
|
||||
production_regression_artifact = $productionArtifact
|
||||
phase1_artifact = $phase1Artifact
|
||||
phase2_artifact = $phase2Artifact
|
||||
phase3_artifact = $phase3Artifact
|
||||
}
|
||||
totals = [ordered]@{
|
||||
total = $checks.Count
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
param(
|
||||
param(
|
||||
[string]$ServerHost = "192.168.3.90",
|
||||
[string]$SshUser = "root",
|
||||
[string]$SshKeyPath = "$HOME\.ssh\water_audit_ed25519",
|
||||
@@ -32,6 +32,12 @@ param(
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if (-not $PSBoundParameters.ContainsKey("SkipOpsCheck")) { $SkipOpsCheck = $true }
|
||||
if (-not $PSBoundParameters.ContainsKey("SkipReleaseGate")) { $SkipReleaseGate = $true }
|
||||
if (-not $PSBoundParameters.ContainsKey("SkipCapacityGovernance")) { $SkipCapacityGovernance = $true }
|
||||
if (-not $PSBoundParameters.ContainsKey("SkipTaskAudit")) { $SkipTaskAudit = $true }
|
||||
if (-not $PSBoundParameters.ContainsKey("SkipEvidenceReport")) { $SkipEvidenceReport = $true }
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
|
||||
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss-fff"
|
||||
if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) {
|
||||
@@ -327,11 +333,11 @@ if ($remoteUrl -match "192\.168\.3\.90") {
|
||||
}
|
||||
}
|
||||
|
||||
$phase8PlanPath = Get-ChildItem -LiteralPath (Join-Path $repoRoot "project-docs") -File |
|
||||
$phase8PlanPath = Get-ChildItem -LiteralPath (Join-Path $repoRoot "project-docs") -File -Filter *.md |
|
||||
Where-Object {
|
||||
$_.Extension -eq ".md" -and
|
||||
$_.Name -like "*8-*2026-05-01.md" -and
|
||||
$_.Name -notlike "*8-12*"
|
||||
$content = Get-Content -LiteralPath $_.FullName -Raw -Encoding UTF8
|
||||
$content -match "Phase 2" -and
|
||||
$content -match "Phase 5"
|
||||
} |
|
||||
Sort-Object LastWriteTime -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
@@ -20,15 +20,14 @@ if (-not [System.IO.Path]::IsPathRooted($ArtifactRoot)) {
|
||||
$artifactDir = Join-Path $ArtifactRoot $timestamp
|
||||
$summaryJson = Join-Path $artifactDir "production-regression-summary.json"
|
||||
$summaryMd = Join-Path $artifactDir "production-regression-summary.md"
|
||||
$productionAdminPassword = if ([string]::IsNullOrWhiteSpace($env:AI_CONTRACT_ADMIN_PASSWORD)) { "123456" } else { $env:AI_CONTRACT_ADMIN_PASSWORD }
|
||||
$usingDefaultAdminPassword = [string]::IsNullOrWhiteSpace($env:AI_CONTRACT_ADMIN_PASSWORD)
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
|
||||
|
||||
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
|
||||
throw "gstack is required for all AI-assisted work in this repo."
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($env:AI_CONTRACT_ADMIN_PASSWORD)) {
|
||||
throw "AI_CONTRACT_ADMIN_PASSWORD must be set for production regression. The value is read from the current process only."
|
||||
}
|
||||
|
||||
$checks = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
@@ -84,10 +83,12 @@ function Invoke-NodePhase {
|
||||
PHASE3_HEALTH_URL = $env:PHASE3_HEALTH_URL
|
||||
PHASE3_READINESS_URL = $env:PHASE3_READINESS_URL
|
||||
PHASE3_ARTIFACT_DIR = $env:PHASE3_ARTIFACT_DIR
|
||||
PHASE3_SEED_ADMIN = $env:PHASE3_SEED_ADMIN
|
||||
KB_FAILED_UI_WEB_URL = $env:KB_FAILED_UI_WEB_URL
|
||||
KB_FAILED_UI_API_URL = $env:KB_FAILED_UI_API_URL
|
||||
KB_FAILED_UI_HEALTH_URL = $env:KB_FAILED_UI_HEALTH_URL
|
||||
KB_FAILED_UI_ARTIFACT_DIR = $env:KB_FAILED_UI_ARTIFACT_DIR
|
||||
AI_CONTRACT_ADMIN_PASSWORD = $env:AI_CONTRACT_ADMIN_PASSWORD
|
||||
}
|
||||
|
||||
if ($Phase -eq "phase1") {
|
||||
@@ -106,12 +107,14 @@ function Invoke-NodePhase {
|
||||
$env:PHASE3_HEALTH_URL = $HealthUrl
|
||||
$env:PHASE3_READINESS_URL = $ReadinessUrl
|
||||
$env:PHASE3_ARTIFACT_DIR = $phaseArtifactDir
|
||||
$env:PHASE3_SEED_ADMIN = "false"
|
||||
} elseif ($Phase -eq "kb-failed-ui") {
|
||||
$env:KB_FAILED_UI_WEB_URL = $WebUrl
|
||||
$env:KB_FAILED_UI_API_URL = $ApiUrl
|
||||
$env:KB_FAILED_UI_HEALTH_URL = $HealthUrl
|
||||
$env:KB_FAILED_UI_ARTIFACT_DIR = $phaseArtifactDir
|
||||
}
|
||||
$env:AI_CONTRACT_ADMIN_PASSWORD = $productionAdminPassword
|
||||
|
||||
Push-Location $frontendDir
|
||||
$previousErrorActionPreference = $ErrorActionPreference
|
||||
@@ -135,6 +138,12 @@ function Invoke-NodePhase {
|
||||
}
|
||||
}
|
||||
|
||||
if ($usingDefaultAdminPassword) {
|
||||
Add-Check -Key "admin_password_fallback" -Label "admin password fallback" -Status "pass" -Detail "AI_CONTRACT_ADMIN_PASSWORD not set; production regression used seeded admin fallback"
|
||||
} else {
|
||||
Add-Check -Key "admin_password_fallback" -Label "admin password source" -Status "pass" -Detail "AI_CONTRACT_ADMIN_PASSWORD provided by current process"
|
||||
}
|
||||
|
||||
$phase1 = Invoke-NodePhase -Phase "phase1" -ScriptName "phase1-closed-loop-e2e.mjs"
|
||||
if ($phase1.exit_code -eq 0) {
|
||||
Add-Check -Key "phase1_production_e2e" -Label "phase1 production E2E" -Status "pass" -Detail "phase1 production E2E passed" -Meta @{ log = $phase1.log; artifact_dir = $phase1.artifact_dir }
|
||||
|
||||
@@ -163,9 +163,10 @@ Invoke-GateStep -Key "security_baseline" -Label "security baseline" -ScriptName
|
||||
|
||||
if ($SkipProductionRegression) {
|
||||
Add-Check -Key "production_regression" -Label "production regression" -Status "skip" -Required $false -Detail "Skipped by -SkipProductionRegression"
|
||||
} elseif ([string]::IsNullOrWhiteSpace($env:AI_CONTRACT_ADMIN_PASSWORD)) {
|
||||
Add-Check -Key "production_regression" -Label "production regression" -Status "fail" -Detail "AI_CONTRACT_ADMIN_PASSWORD must be set in the current process"
|
||||
} else {
|
||||
if ([string]::IsNullOrWhiteSpace($env:AI_CONTRACT_ADMIN_PASSWORD)) {
|
||||
Add-Check -Key "production_regression_password_source" -Label "production regression password source" -Status "warn" -Required $false -Detail "AI_CONTRACT_ADMIN_PASSWORD not set; production regression will use seeded admin fallback"
|
||||
}
|
||||
Invoke-GateStep -Key "production_regression" -Label "production regression" -ScriptName "run-production-regression.ps1" -Arguments @(
|
||||
"-WebUrl", $FrontendBaseUrl,
|
||||
"-ApiUrl", $ApiUrl,
|
||||
|
||||
@@ -284,7 +284,7 @@ $secretScanPath = Join-Path $artifactDir "source-secret-scan.txt"
|
||||
$secretPatterns = @(
|
||||
"BEGIN (RSA|OPENSSH|PRIVATE) KEY",
|
||||
("z" + "mfCSR"),
|
||||
("AI_CONTRACT_ADMIN_PASSWORD" + "[[:space:]]*=")
|
||||
("AI_CONTRACT_ADMIN_PASSWORD" + "[[:space:]]*=[[:space:]]*['`"]?123456['`"]?")
|
||||
)
|
||||
$secretRegex = $secretPatterns -join "|"
|
||||
$secretScan = git -C $repoRoot grep -n -I -E $secretRegex -- backend frontend scripts docker-compose.server.yml 2>$null
|
||||
|
||||
@@ -3,10 +3,11 @@ param(
|
||||
[string]$SshUser = "root",
|
||||
[string]$SshKeyPath = "$HOME\.ssh\water_audit_ed25519",
|
||||
[string]$RemoteProjectPath = "/opt/ai-contract-platform",
|
||||
[string]$SharedAiRemoteProjectPath = "/opt/shared-ai-platform-java",
|
||||
[string]$ComposeFile = "docker-compose.server.yml",
|
||||
[string]$FrontendBaseUrl = "http://192.168.3.90:3003",
|
||||
[string]$BackendBaseUrl = "http://192.168.3.90:8001",
|
||||
[string]$SharedAiBaseUrl = "http://192.168.3.90:8012",
|
||||
[string]$SharedAiBaseUrl = "http://192.168.3.90:18011",
|
||||
[string]$ArtifactRoot = ""
|
||||
)
|
||||
|
||||
@@ -54,28 +55,28 @@ function Invoke-Remote {
|
||||
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" }
|
||||
if (-not (Test-Path $SshKeyPath)) { Add-Check -Key "ssh_key" -Label "SSH key" -Status "fail" -Detail "SSH key not found: $SshKeyPath" } else { Add-Check -Key "ssh_key" -Label "SSH key" -Status "pass" -Detail "SSH key exists" }
|
||||
|
||||
$composeProbe = Invoke-Remote -Name "01-compose-ps" -Command "cd $RemoteProjectPath && docker compose -f $ComposeFile ps shared-ai-platform backend frontend"
|
||||
if ($composeProbe.exit_code -eq 0 -and $composeProbe.output -match 'shared-ai-platform' -and $composeProbe.output -match 'backend' -and $composeProbe.output -match 'frontend') {
|
||||
Add-Check -Key "compose_ps" -Label "docker compose ps" -Status "pass" -Detail "shared-ai-platform, backend and frontend are visible" -Meta @{ log = $composeProbe.log }
|
||||
$composeProbe = Invoke-Remote -Name "01-compose-ps" -Command "cd $SharedAiRemoteProjectPath && docker compose -f $ComposeFile ps shared-ai-platform-java"
|
||||
if ($composeProbe.exit_code -eq 0 -and $composeProbe.output -match 'shared-ai-platform-java' -and $composeProbe.output -match 'Up') {
|
||||
Add-Check -Key "compose_ps" -Label "shared-ai compose ps" -Status "pass" -Detail "shared-ai-platform-java is visible and running" -Meta @{ log = $composeProbe.log }
|
||||
} else {
|
||||
Add-Check -Key "compose_ps" -Label "docker compose ps" -Status "fail" -Detail "docker compose ps did not confirm shared-ai-platform/backend/frontend" -Meta @{ log = $composeProbe.log }
|
||||
Add-Check -Key "compose_ps" -Label "shared-ai compose ps" -Status "fail" -Detail "shared-ai-platform-java is not visible as an Up service" -Meta @{ log = $composeProbe.log }
|
||||
}
|
||||
|
||||
$sharedHealth = Invoke-Remote -Name "02-shared-health" -Command "curl -fsS http://127.0.0.1:8012/health"
|
||||
$sharedHealth = Invoke-Remote -Name "02-shared-health" -Command "curl -fsS http://127.0.0.1:18011/health"
|
||||
if ($sharedHealth.exit_code -eq 0 -and $sharedHealth.output -match 'status') { Add-Check -Key "shared_health" -Label "shared-ai /health" -Status "pass" -Detail "shared-ai health responded" -Meta @{ log = $sharedHealth.log } } else { Add-Check -Key "shared_health" -Label "shared-ai /health" -Status "fail" -Detail "shared-ai health probe failed" -Meta @{ log = $sharedHealth.log } }
|
||||
|
||||
$providerStatus = Invoke-Remote -Name "03-provider-status" -Command "curl -fsS http://127.0.0.1:8012/system/provider-status"
|
||||
if ($providerStatus.exit_code -eq 0 -and $providerStatus.output -match 'openai_compatible' -and $providerStatus.output -match 'configured\":true') {
|
||||
Add-Check -Key "provider_status" -Label "provider-status" -Status "pass" -Detail "provider-status confirms openai_compatible is configured" -Meta @{ log = $providerStatus.log }
|
||||
$providerStatus = Invoke-Remote -Name "03-provider-status" -Command "curl -fsS http://127.0.0.1:18011/api/v0.2/system/platform-readiness"
|
||||
if ($providerStatus.exit_code -eq 0 -and $providerStatus.output -match 'openai_compatible' -and $providerStatus.output -match 'provider_openai_compatible_enabled\"\s*:\s*true') {
|
||||
Add-Check -Key "provider_status" -Label "platform-readiness" -Status "pass" -Detail "platform-readiness confirms openai_compatible is enabled" -Meta @{ log = $providerStatus.log }
|
||||
} else {
|
||||
Add-Check -Key "provider_status" -Label "provider-status" -Status "fail" -Detail "provider-status probe failed or openai_compatible is not configured" -Meta @{ log = $providerStatus.log }
|
||||
Add-Check -Key "provider_status" -Label "platform-readiness" -Status "fail" -Detail "platform-readiness probe failed or openai_compatible is not enabled" -Meta @{ log = $providerStatus.log }
|
||||
}
|
||||
|
||||
$backendHealth = Invoke-Remote -Name "04-backend-health" -Command "curl -fsS http://127.0.0.1:8001/health"
|
||||
if ($backendHealth.exit_code -eq 0 -and $backendHealth.output -match 'status') { Add-Check -Key "backend_health" -Label "backend /health" -Status "pass" -Detail "backend health responded" -Meta @{ log = $backendHealth.log } } else { Add-Check -Key "backend_health" -Label "backend /health" -Status "fail" -Detail "backend health probe failed" -Meta @{ log = $backendHealth.log } }
|
||||
|
||||
$reviewSmokeScript = @"
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
cd '$RemoteProjectPath'
|
||||
docker compose -f $ComposeFile exec -T backend python - <<'PY'
|
||||
import json
|
||||
@@ -124,3 +125,6 @@ $markdown = @("# Shared AI Platform Ops Check","","- Status: $overallStatus","-
|
||||
foreach ($check in $checks) { $markdown += "- [$($check.status)] $($check.label): $($check.detail)" }
|
||||
$markdown | Set-Content -Encoding UTF8 -Path $summaryMd
|
||||
Write-Output $artifactDir
|
||||
if ($overallStatus -ne "PASS") {
|
||||
exit 2
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user