chore: add production release gate automation

This commit is contained in:
Codex
2026-05-01 09:31:15 +08:00
parent 384de1e687
commit 934a90b523
6 changed files with 595 additions and 3 deletions
+42 -1
View File
@@ -206,7 +206,48 @@ project-docs/qa-artifacts/phase5-production-regression/<timestamp>/
---
## 七、常见故障处理
## 七、发布门禁口径
默认发布门禁:
```powershell
$env:AI_CONTRACT_ADMIN_PASSWORD = '<由管理员输入的实际密码>'
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-release-gate.ps1
Remove-Item Env:\AI_CONTRACT_ADMIN_PASSWORD
```
完整发布门禁:
```powershell
$env:AI_CONTRACT_ADMIN_PASSWORD = '<由管理员输入的实际密码>'
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-release-gate.ps1 -IncludeBackupDrill
Remove-Item Env:\AI_CONTRACT_ADMIN_PASSWORD
```
严格发布门禁:
```powershell
$env:AI_CONTRACT_ADMIN_PASSWORD = '<由管理员输入的实际密码>'
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-release-gate.ps1 -RequireCleanWorktree
Remove-Item Env:\AI_CONTRACT_ADMIN_PASSWORD
```
默认产物目录:
```text
project-docs/qa-artifacts/phase6-release-gate/<timestamp>/
```
门禁说明:
1. 默认模式允许 dirty worktree,但会记录 WARN。
2. 正式发布前必须使用 `-RequireCleanWorktree`。
3. 默认模式不跑备份恢复演练;周度或完整发布使用 `-IncludeBackupDrill`。
4. 任何 required 检查失败时不得发布。
---
## 八、常见故障处理
查看容器状态:
@@ -15,10 +15,11 @@
| 阶段 3:试点交付准备 | PASS | `project-docs/阶段3-试点交付准备执行记录-2026-05-01.md` |
| 阶段 4:试点运行监控与安全加固 | PASS | `project-docs/阶段4-试点运行监控与安全加固执行记录-2026-05-01.md` |
| 阶段 5:生产运行、安全、监控、备份与依赖治理 | PASS | `project-docs/阶段5-生产运行安全监控备份与依赖治理执行记录-2026-05-01.md` |
| 阶段 6:生产运营自动化与发布门禁固化 | PASS_WITH_WORKTREE_WARN | `project-docs/阶段6-生产运营自动化与发布门禁固化执行记录-2026-05-01.md` |
阶段 1 已完成源码闭环与生产入口闭环,生产无人 E2E `18/18` 通过。阶段 2 已完成 Review 重试、Knowledge Base 文档重试、Review -> Chat -> Contract 上下文联动、首页交付工作台和阶段 2 自动化无人闭环,本地与生产 E2E 均 `7/7` 通过。阶段 3 已完成系统 readiness、试点交付准备页、默认账号说明、演示样本数据、依赖健康门禁和阶段 3 自动化无人闭环,本地与生产 E2E 均 `5/5` 通过。
当前后续重点为“生产运行、安全、监控、备份与依赖治理”:默认密码轮换已完成,阶段 5 Step 1 生产健康巡检、Step 2 备份恢复演练、Step 3 依赖治理、Step 4 安全基线、Step 5 生产回归均已 PASS,阶段 5 已形成生产运行治理闭环。
当前后续重点已从“生产运行治理”推进到“生产运营自动化与发布门禁固化”:阶段 6 已完成前端视觉改动梳理、发布门禁聚合脚本、默认发布门禁实跑和严格门禁探针。默认发布门禁 required 项全部 PASS,当前唯一 WARN 是工作区保留未提交前端视觉刷新。
---
@@ -218,4 +219,4 @@ AI合同平台当前已经不是从零建设阶段。登录、合同审查、Rev
## 六、当前一句话结论
AI合同平台阶段 1、阶段 2、阶段 3、阶段 4、阶段 5 均已完成闭环交付验证。当前系统已具备主业务闭环、试点交付准备、默认管理员密码轮换、生产健康巡检、备份恢复演练、依赖治理、安全基线和生产关键 E2E 回归证据。
AI合同平台阶段 1、阶段 2、阶段 3、阶段 4、阶段 5 均已完成闭环交付验证,阶段 6 首批发布门禁自动化也已形成闭环。当前系统具备主业务闭环、试点交付准备、生产健康巡检、备份恢复演练、依赖治理、安全基线、生产关键 E2E 回归和统一发布门禁证据;正式发布前需先处理当前未提交前端视觉刷新并复跑严格门禁。
@@ -0,0 +1,62 @@
# 阶段 6 前置:当前未提交前端视觉改动梳理(2026-05-01)
> 目的:阶段 6 的主线是生产运营自动化与发布门禁固化。当前工作区已有一组未提交前端视觉改动,本清单先把它们和阶段 6 运维自动化解耦,避免混入同一提交。
---
## 一、当前结论
当前未提交前端改动属于“视觉刷新与信息架构压缩”,不是阶段 6 运维门禁的必要改动。
处理建议:
1. 阶段 6 自动化脚本和文档单独提交。
2. 前端视觉改动保留在工作区,不在阶段 6 运维提交中暂存。
3. 后续单独进入“视觉刷新验收”批次,至少跑 `npm run lint`、`npm run build`、生产关键 E2E 和浏览器截图检查。
4. 若视觉刷新确认要上线,应单独形成提交和验收记录。
---
## 二、改动分组
| 分组 | 文件 | 变化性质 | 风险判断 |
|---|---|---|---|
| 忽略规则 | `.gitignore` | 新增 `.gstack/` 忽略项 | 可独立提交,但不属于视觉刷新 |
| 全局主题 | `frontend/src/app/globals.css` | 新增约 1400+ 行视觉主题、卡片、导航、页面布局样式 | 高影响,需要全站截图和移动端检查 |
| 顶部导航 | `frontend/src/components/layout/app-topbar.tsx` | 增加 Ant Design 图标,将 admin 入口合并为下拉菜单 | 中高影响,需要验证 admin/member/public 导航可达性 |
| 首页 | `frontend/src/app/page.tsx` | 公共首页视觉场景、登录后工作台摘要、优先级/入口重组 | 中高影响,需要确认首页信息不遮挡、不丢入口 |
| 登录页 | `frontend/src/app/login/page.tsx` | 增加安全工作台预览,减少提示 Alert | 中影响,需要确认注册/登录/重定向提示仍清晰 |
| 发起审查 | `frontend/src/app/review/page.tsx` | 提交流程视觉前置,移除部分重复说明卡片 | 中影响,需要确认上传、文本补充、提交按钮路径不变 |
| 审查详情 | `frontend/src/app/reviews/[id]/page.tsx` | Review Copilot 与 Quick Actions 合并为行动区 | 中高影响,需要确认 Review -> Chat 上下文入口仍可用 |
| 合同详情 | `frontend/src/app/contracts/[id]/page.tsx` | 变量/导出选项两栏,预览/生成内容网格化 | 中影响,需要确认长文本不溢出,下载/回填可用 |
| 管理和列表页 | audit logs、chat、contracts、files、knowledge bases、profile、rules、templates、tenant settings、users 等页面 | 多为 className、文案、容器样式调整 | 中影响,需要跑全页面 smoke |
---
## 三、阶段 6 处理边界
本阶段只接入以下内容:
1. 发布门禁聚合脚本。
2. 阶段 6 方案文档。
3. 阶段 6 执行记录。
4. 运维手册中的发布门禁用法。
本阶段不接入以下内容:
1. 前端视觉刷新代码。
2. `.gitignore` 的 `.gstack/` 变更。
3. 页面布局、文案和导航交互变更。
---
## 四、后续视觉刷新验收建议
建议后续单独开启“前端视觉刷新验收”:
1. 先确认 `.gstack/` 是否应作为仓库忽略规则提交。
2. 跑 `npm run lint` 和 `npm run build`。
3. 跑阶段 1/2/3 生产关键 E2E。
4. 用浏览器检查 public、member、admin 三类视角。
5. 重点检查移动端顶部导航、admin 下拉菜单、合同详情长文本、审查详情行动区、登录页提示缺失风险。
6. 形成独立视觉刷新执行记录,再决定是否提交。
@@ -0,0 +1,119 @@
# 阶段 6:生产运营自动化与发布门禁固化执行记录(2026-05-01)
> 当前目标:把阶段 5 已验证的生产运维脚本固化成统一发布门禁,并先隔离当前未提交前端视觉刷新。
---
## 一、当前执行结论
| 主线 | 状态 | 备注 |
|---|---|---|
| gstack 安装校验 | PASS | 自动化执行前已返回 `GSTACK_OK` |
| 前端视觉改动梳理 | PASS | 已生成阶段 6 前置清单,判定不纳入本轮运维提交 |
| 发布门禁聚合脚本 | READY | 已新增 `scripts/run-production-release-gate.ps1` |
| 发布门禁实跑 | WARN | 默认门禁已跑通,required 项全部 PASS,dirty worktree 为 advisory WARN |
| 严格门禁探针 | PASS | `-RequireCleanWorktree` 已按预期把未提交视觉刷新升级为 FAIL |
| 阶段 6 首批闭环 | PASS_WITH_WORKTREE_WARN | 发布门禁自动化已闭环,正式发布前仍需处理当前前端视觉刷新 |
---
## 二、前端视觉改动处理结论
当前工作区存在未提交前端视觉刷新,包含全局主题、顶部导航、首页、登录页、审查页、审查详情、合同详情和多个管理页样式调整。
处理口径:
1. 本轮阶段 6 运维自动化提交不纳入这些前端视觉文件。
2. 发布门禁默认模式会记录 dirty worktree 为 WARN。
3. 正式发布前必须使用 `-RequireCleanWorktree`,把 dirty worktree 升级为 FAIL。
4. 前端视觉刷新后续单独做浏览器验收和提交。
清单文档:
```text
project-docs/阶段6-前端视觉改动梳理-2026-05-01.md
```
---
## 三、发布门禁脚本
新增脚本:
```powershell
scripts\run-production-release-gate.ps1
```
默认覆盖:
1. gstack preflight。
2. origin remote 指向 `192.168.3.90`。
3. 工作区状态记录。
4. 依赖治理:`run-dependency-governance.ps1`。
5. 生产健康巡检:`run-production-health-check.ps1`。
6. 安全基线:`run-security-baseline.ps1`。
7. 生产阶段 1/2/3 回归:`run-production-regression.ps1`。
可选覆盖:
1. `-IncludeBackupDrill`:增加备份与恢复演练。
2. `-RequireCleanWorktree`:要求工作区干净。
3. `-SkipBuild`:依赖治理跳过前端 build,用于快速巡检。
4. `-SkipProductionRegression`:跳过生产 E2E,只用于临时诊断,不可作为正式发布门禁。
---
## 四、待执行
已执行默认发布门禁:
```powershell
$env:AI_CONTRACT_ADMIN_PASSWORD = '<由管理员输入的实际密码>'
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-release-gate.ps1
Remove-Item Env:\AI_CONTRACT_ADMIN_PASSWORD
```
默认发布门禁产物:
```text
project-docs/qa-artifacts/phase6-release-gate/2026-05-01T09-27-52/
```
默认发布门禁结果:
| 检查项 | 结果 | 说明 |
|---|---|---|
| gstack preflight | PASS | `GSTACK_OK` |
| origin remote | PASS | origin 指向 `192.168.3.90` |
| working tree | WARN | 存在未提交前端视觉刷新,默认模式只告警 |
| dependency governance | PASS | 依赖治理通过 |
| production health | PASS | 生产健康巡检通过 |
| security baseline | PASS | 安全基线通过 |
| production regression | PASS | 阶段 1/2/3 生产关键 E2E 通过 |
| backup drill | SKIP | 默认不跑,完整门禁使用 `-IncludeBackupDrill` |
已执行严格门禁探针:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-release-gate.ps1 -RequireCleanWorktree -SkipBuild -SkipProductionRegression
```
严格门禁探针产物:
```text
project-docs/qa-artifacts/phase6-release-gate/2026-05-01T09-29-45/
```
严格门禁探针结果:`FAIL`,失败点为 `working tree`。这符合预期,说明正式发布前 dirty worktree 会被阻断。
---
## 五、阶段 6 首批结论
阶段 6 首批自动化已经形成闭环:
1. 视觉刷新已被识别为独立批次,不混入运维门禁提交。
2. 发布门禁脚本可复跑,并生成统一汇总报告。
3. 默认门禁 required 项全部 PASS。
4. 严格门禁可以阻断未提交工作区。
5. 正式发布前下一步应先处理或提交当前前端视觉刷新。
@@ -0,0 +1,113 @@
# 阶段 6:生产运营自动化与发布门禁固化方案(2026-05-01)
> 前置状态:阶段 1-5 已完成业务闭环、深度体验、试点交付准备、安全加固、生产健康巡检、备份恢复、依赖治理、安全基线和生产回归。阶段 6 的目标是把这些能力从“能手动复跑”推进到“发布和运营动作必须经过统一门禁”。
---
## 一、阶段目标
阶段 6 聚焦四件事:
1. 发布门禁固化:把依赖治理、生产健康、安全基线、生产回归串成一个统一入口。
2. 运营巡检自动化:为日常巡检、重启后复验、发布后复验形成固定脚本和产物结构。
3. 风险隔离:把当前未提交前端视觉刷新和运维门禁提交分离。
4. 证据统一:每次门禁生成统一汇总报告,并保留子检查产物路径。
---
## 二、阶段 6 范围
| 主线 | 目标 | 交付物 |
|---|---|---|
| A. 前置梳理 | 梳理当前未提交前端视觉改动,明确不混入运维门禁 | `阶段6-前端视觉改动梳理-2026-05-01.md` |
| B. 发布门禁 | 聚合依赖治理、健康巡检、安全基线、生产回归、可选备份演练 | `scripts/run-production-release-gate.ps1` |
| C. 证据汇总 | 生成统一 JSON/Markdown 报告,记录子产物目录 | `project-docs/qa-artifacts/phase6-release-gate/<timestamp>/` |
| D. 运维文档 | 更新发布前、重启后、依赖升级后的固定执行口径 | 运维手册与执行记录 |
---
## 三、发布门禁分层
| 场景 | 命令口径 | 必须项 |
|---|---|---|
| 日常巡检 | 健康巡检 + 安全基线 | 服务可用、readiness、默认密码拒绝、强密钥、权限边界 |
| 发布前门禁 | 发布门禁脚本默认模式 | 依赖治理、生产健康、安全基线、生产阶段 1/2/3 E2E |
| 完整发布门禁 | 发布门禁脚本 + 备份演练 | 默认门禁 + PostgreSQL/MinIO/配置备份和隔离恢复 |
| 严格发布门禁 | 发布门禁脚本 + clean worktree | 默认门禁 + 工作区必须干净 |
---
## 四、自动化入口
默认发布门禁:
```powershell
$env:AI_CONTRACT_ADMIN_PASSWORD = '<由管理员输入的实际密码>'
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-release-gate.ps1
Remove-Item Env:\AI_CONTRACT_ADMIN_PASSWORD
```
完整发布门禁:
```powershell
$env:AI_CONTRACT_ADMIN_PASSWORD = '<由管理员输入的实际密码>'
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-release-gate.ps1 -IncludeBackupDrill
Remove-Item Env:\AI_CONTRACT_ADMIN_PASSWORD
```
严格发布门禁:
```powershell
$env:AI_CONTRACT_ADMIN_PASSWORD = '<由管理员输入的实际密码>'
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-release-gate.ps1 -RequireCleanWorktree
Remove-Item Env:\AI_CONTRACT_ADMIN_PASSWORD
```
说明:
1. 管理员真实密码只允许通过当前进程环境变量临时传入。
2. 脚本、文档、Git、`.env`、产物日志均不得保存管理员真实密码。
3. 当前工作区存在未提交前端视觉刷新时,可使用默认模式产生 WARN;正式发布前应使用 `-RequireCleanWorktree`。
---
## 五、验收标准
阶段 6 首批闭环完成必须满足:
1. 当前未提交前端视觉改动已形成清单。
2. 发布门禁脚本可复跑。
3. 发布门禁至少覆盖依赖治理、生产健康、安全基线和生产阶段 1/2/3 E2E。
4. 发布门禁能在存在未提交视觉改动时给出 working tree WARN。
5. 发布门禁能在严格模式下把未提交改动升级为 FAIL。
6. 发布门禁产物包含统一汇总和子产物目录。
7. 运维手册更新发布门禁口径。
---
## 六、风险与处理
| 风险 | 等级 | 处理 |
|---|---|---|
| 当前视觉刷新影响 build 或 E2E | P1 | 阶段 6 不混入视觉刷新提交,后续单独验收 |
| 发布门禁运行时间较长 | P2 | 默认不跑备份演练,完整门禁通过 `-IncludeBackupDrill` 显式开启 |
| 工作区不干净导致无法发布 | P1 | 默认 advisory WARN,正式发布使用 `-RequireCleanWorktree` |
| 管理员密码泄露 | P0 | 仅当前进程环境变量,运行后立即移除 |
| 子脚本失败难定位 | P1 | 聚合报告保留每个子脚本日志和产物目录 |
---
## 七、立即执行顺序
1. 完成前端视觉改动梳理。已完成。
2. 新增 `scripts/run-production-release-gate.ps1`。已完成。
3. 更新运维手册发布门禁章节。已完成。
4. 执行默认发布门禁,当前 dirty worktree 应为 WARN,其他 required 项必须 PASS。已完成,产物:`project-docs/qa-artifacts/phase6-release-gate/2026-05-01T09-27-52/`。
5. 验证严格门禁会阻断 dirty worktree。已完成,产物:`project-docs/qa-artifacts/phase6-release-gate/2026-05-01T09-29-45/`。
6. 生成阶段 6 执行记录。已完成。
---
## 八、当前阶段结论
阶段 6 首批生产运营自动化与发布门禁固化已完成。默认发布门禁状态为 `WARN`,原因是当前工作区保留了未提交前端视觉刷新;依赖治理、生产健康、安全基线和生产回归 required 项均已 PASS。正式发布前必须先处理视觉刷新工作区,并使用 `-RequireCleanWorktree` 复跑严格发布门禁。
+256
View File
@@ -0,0 +1,256 @@
param(
[string]$ServerHost = "192.168.3.90",
[string]$BackendBaseUrl = "http://192.168.3.90:8001",
[string]$FrontendBaseUrl = "http://192.168.3.90:3003",
[string]$ApiUrl = "http://192.168.3.90:8001/api/v1",
[string]$HealthUrl = "http://192.168.3.90:8001/health",
[string]$ReadinessUrl = "http://192.168.3.90:8001/api/v1/system/readiness",
[string]$ArtifactRoot = "",
[switch]$RequireCleanWorktree,
[switch]$IncludeBackupDrill,
[switch]$SkipBuild,
[switch]$SkipProductionRegression
)
$ErrorActionPreference = "Stop"
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..")
$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss"
if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) {
$ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase6-release-gate"
}
$artifactDir = Join-Path $ArtifactRoot $timestamp
$summaryJson = Join-Path $artifactDir "release-gate-summary.json"
$summaryMd = Join-Path $artifactDir "release-gate-summary.md"
$gitStatusPath = Join-Path $artifactDir "git-status.txt"
New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null
$checks = New-Object System.Collections.Generic.List[object]
function Add-Check {
param(
[string]$Key,
[string]$Label,
[ValidateSet("pass", "fail", "warn", "skip")]
[string]$Status,
[string]$Detail,
[bool]$Required = $true,
[hashtable]$Meta = @{}
)
$checks.Add([ordered]@{
key = $Key
label = $Label
status = $Status
required = $Required
detail = $Detail
meta = $Meta
}) | Out-Null
}
function Save-Json {
param(
[string]$Path,
[object]$Value
)
$Value | ConvertTo-Json -Depth 12 | Set-Content -Encoding UTF8 -Path $Path
}
function Invoke-GateStep {
param(
[string]$Key,
[string]$Label,
[string]$ScriptName,
[string[]]$Arguments
)
$scriptPath = Join-Path $repoRoot "scripts\$ScriptName"
$logPath = Join-Path $artifactDir "$Key.log"
$previousErrorActionPreference = $ErrorActionPreference
$ErrorActionPreference = "Continue"
try {
$output = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $scriptPath @Arguments 2>&1
$exitCode = $LASTEXITCODE
} finally {
$ErrorActionPreference = $previousErrorActionPreference
}
$outputText = ($output -join [Environment]::NewLine)
$outputText | Set-Content -Encoding UTF8 -Path $logPath
$childArtifact = $null
$artifactMatch = [regex]::Match($outputText, '([A-Z]:\\[^\r\n]+)$', [System.Text.RegularExpressions.RegexOptions]::Multiline)
if ($artifactMatch.Success) {
$childArtifact = $artifactMatch.Groups[1].Value.Trim()
}
if ($exitCode -eq 0 -and $outputText -match "_WARN") {
Add-Check -Key $Key -Label $Label -Status "warn" -Required $false -Detail "$Label completed with warnings" -Meta @{
log = $logPath
artifact_dir = $childArtifact
}
} elseif ($exitCode -eq 0) {
Add-Check -Key $Key -Label $Label -Status "pass" -Detail "$Label passed" -Meta @{
log = $logPath
artifact_dir = $childArtifact
}
} else {
Add-Check -Key $Key -Label $Label -Status "fail" -Detail "$Label failed" -Meta @{
exit_code = $exitCode
log = $logPath
artifact_dir = $childArtifact
}
}
}
if (-not (Test-Path "$HOME\.Codex\skills\gstack\bin")) {
Add-Check -Key "gstack" -Label "gstack preflight" -Status "fail" -Detail "gstack is missing"
} else {
Add-Check -Key "gstack" -Label "gstack preflight" -Status "pass" -Detail "GSTACK_OK"
}
$branch = (git -C $repoRoot rev-parse --abbrev-ref HEAD).Trim()
$commit = (git -C $repoRoot rev-parse HEAD).Trim()
$remoteUrl = (git -C $repoRoot remote get-url origin).Trim()
if ($remoteUrl -match "192\.168\.3\.90") {
Add-Check -Key "origin_remote" -Label "origin remote" -Status "pass" -Detail "origin points to 192.168.3.90" -Meta @{
branch = $branch
commit = $commit
remote = $remoteUrl
}
} else {
Add-Check -Key "origin_remote" -Label "origin remote" -Status "fail" -Detail "origin does not point to 192.168.3.90" -Meta @{
branch = $branch
commit = $commit
remote = $remoteUrl
}
}
$gitStatus = git -C $repoRoot status --short
($gitStatus -join [Environment]::NewLine) | Set-Content -Encoding UTF8 -Path $gitStatusPath
if ($gitStatus) {
if ($RequireCleanWorktree) {
Add-Check -Key "working_tree" -Label "working tree" -Status "fail" -Detail "Working tree has uncommitted changes and -RequireCleanWorktree is set" -Meta @{ artifact = $gitStatusPath }
} else {
Add-Check -Key "working_tree" -Label "working tree" -Status "warn" -Required $false -Detail "Working tree has uncommitted changes; release gate is running in advisory mode" -Meta @{ artifact = $gitStatusPath }
}
} else {
Add-Check -Key "working_tree" -Label "working tree" -Status "pass" -Detail "Working tree is clean" -Meta @{ artifact = $gitStatusPath }
}
$dependencyArgs = @("-ArtifactRoot", (Join-Path $artifactDir "dependency-governance"))
if ($SkipBuild) {
$dependencyArgs += "-SkipBuild"
}
Invoke-GateStep -Key "dependency_governance" -Label "dependency governance" -ScriptName "run-dependency-governance.ps1" -Arguments $dependencyArgs
Invoke-GateStep -Key "production_health" -Label "production health" -ScriptName "run-production-health-check.ps1" -Arguments @(
"-ServerHost", $ServerHost,
"-BackendBaseUrl", $BackendBaseUrl,
"-FrontendBaseUrl", $FrontendBaseUrl,
"-ArtifactRoot", (Join-Path $artifactDir "production-health")
)
Invoke-GateStep -Key "security_baseline" -Label "security baseline" -ScriptName "run-security-baseline.ps1" -Arguments @(
"-ServerHost", $ServerHost,
"-BackendBaseUrl", $BackendBaseUrl,
"-FrontendBaseUrl", $FrontendBaseUrl,
"-ArtifactRoot", (Join-Path $artifactDir "security-baseline")
)
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 {
Invoke-GateStep -Key "production_regression" -Label "production regression" -ScriptName "run-production-regression.ps1" -Arguments @(
"-WebUrl", $FrontendBaseUrl,
"-ApiUrl", $ApiUrl,
"-HealthUrl", $HealthUrl,
"-ReadinessUrl", $ReadinessUrl,
"-ArtifactRoot", (Join-Path $artifactDir "production-regression")
)
}
if ($IncludeBackupDrill) {
Invoke-GateStep -Key "backup_drill" -Label "backup drill" -ScriptName "run-backup-drill.ps1" -Arguments @(
"-ServerHost", $ServerHost,
"-ArtifactRoot", (Join-Path $artifactDir "backup-drill"),
"-RemoteBackupRoot", "/opt/ai-contract-platform/backups/phase6-release-gate"
)
} else {
Add-Check -Key "backup_drill" -Label "backup drill" -Status "skip" -Required $false -Detail "Skipped by default; pass -IncludeBackupDrill for weekly/full release gate"
}
$failedRequired = @($checks | Where-Object { $_.required -and $_.status -eq "fail" })
$warningsList = @($checks | Where-Object { $_.status -eq "warn" })
$skippedList = @($checks | Where-Object { $_.status -eq "skip" })
$overallStatus = if ($failedRequired.Count -gt 0) { "FAIL" } elseif ($warningsList.Count -gt 0) { "WARN" } else { "PASS" }
$summary = [ordered]@{
name = "phase6-release-gate"
status = $overallStatus
generated_at = (Get-Date).ToUniversalTime().ToString("o")
artifact_dir = $artifactDir
branch = $branch
commit = $commit
remote = $remoteUrl
targets = [ordered]@{
server_host = $ServerHost
frontend_base_url = $FrontendBaseUrl
backend_base_url = $BackendBaseUrl
api_url = $ApiUrl
health_url = $HealthUrl
readiness_url = $ReadinessUrl
}
options = [ordered]@{
require_clean_worktree = [bool]$RequireCleanWorktree
include_backup_drill = [bool]$IncludeBackupDrill
skip_build = [bool]$SkipBuild
skip_production_regression = [bool]$SkipProductionRegression
}
checks = $checks
totals = [ordered]@{
total = $checks.Count
failed_required = $failedRequired.Count
warnings = $warningsList.Count
skipped = $skippedList.Count
}
}
Save-Json -Path $summaryJson -Value $summary
$markdown = New-Object System.Collections.Generic.List[string]
$markdown.Add("# Phase 6 Release Gate") | Out-Null
$markdown.Add("") | Out-Null
$markdown.Add("- Status: $overallStatus") | Out-Null
$markdown.Add("- Generated at: $($summary.generated_at)") | Out-Null
$markdown.Add("- Branch: $branch") | Out-Null
$markdown.Add("- Commit: $commit") | Out-Null
$markdown.Add("- Artifact dir: $artifactDir") | Out-Null
$markdown.Add("") | Out-Null
$markdown.Add("| Check | Required | Status | Detail |") | Out-Null
$markdown.Add("|---|---:|---|---|") | Out-Null
foreach ($check in $checks) {
$markdown.Add("| $($check.label) | $($check.required) | $($check.status.ToUpperInvariant()) | $($check.detail) |") | Out-Null
}
$markdown.Add("") | Out-Null
$markdown.Add("Child artifacts:") | Out-Null
$markdown.Add("") | Out-Null
foreach ($check in $checks) {
if ($check.meta.artifact_dir) {
$markdown.Add(('- {0}: `{1}`' -f $check.label, $check.meta.artifact_dir)) | Out-Null
}
}
$markdown.Add("") | Out-Null
$markdown.Add("Local artifacts:") | Out-Null
$markdown.Add("") | Out-Null
$markdown.Add('- `release-gate-summary.json`') | Out-Null
$markdown.Add('- `release-gate-summary.md`') | Out-Null
$markdown.Add('- `git-status.txt`') | Out-Null
$markdown.Add('- `*.log`') | Out-Null
$markdown | Set-Content -Encoding UTF8 -Path $summaryMd
Write-Host "PHASE6_RELEASE_GATE_$overallStatus $artifactDir"
if ($overallStatus -eq "FAIL") {
throw "Phase 6 release gate failed. See $summaryMd"
}