From 384de1e687d9cbd14897afc19a27dba18b9cf379 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 1 May 2026 09:12:35 +0800 Subject: [PATCH] chore: close production operations automation --- frontend/package-lock.json | 48 +++++ frontend/package.json | 1 + project-docs/技术部署指南.md | 42 ++++ project-docs/运维手册-2026-05-01.md | 35 +++- .../闭环交付与深度开发方案-2026-05-01.md | 18 +- ...�行安全监控备份与依赖治理执行记录-2026-05-01.md | 50 +++-- ...º§运行安全监控备份与依赖治理方案-2026-05-01.md | 31 +-- scripts/run-production-regression.ps1 | 196 ++++++++++++++++++ scripts/run-security-baseline.ps1 | 8 +- 9 files changed, 398 insertions(+), 31 deletions(-) create mode 100644 scripts/run-production-regression.ps1 diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ceca3e05..0c6204bd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,6 +23,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.2.4", + "playwright": "1.59.1", "typescript": "^5" } }, @@ -4312,6 +4313,21 @@ "node": ">= 6" } }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5744,6 +5760,38 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 935236d6..e0276a24 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,6 +30,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "16.2.4", + "playwright": "1.59.1", "typescript": "^5" } } diff --git a/project-docs/技术部署指南.md b/project-docs/技术部署指南.md index 93ed36c8..ef102dd8 100644 --- a/project-docs/技术部署指南.md +++ b/project-docs/技术部署指南.md @@ -187,6 +187,48 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\rotate-admin-pas curl http://192.168.3.90:8001/api/v1/system/readiness ``` +## 6.6 阶段 5 生产运维门禁 + +生产健康巡检: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-health-check.ps1 +``` + +备份与恢复演练: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-backup-drill.ps1 +``` + +依赖治理: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-dependency-governance.ps1 +``` + +安全基线: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-security-baseline.ps1 +``` + +生产阶段 1/2/3 关键 E2E 回归: + +```powershell +$env:AI_CONTRACT_ADMIN_PASSWORD = '<由管理员输入的实际密码>' +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-regression.ps1 +Remove-Item Env:\AI_CONTRACT_ADMIN_PASSWORD +``` + +当前阶段 5 已完成闭环验证: + +- 生产健康巡检:PASS +- 备份恢复演练:PASS +- 依赖治理:PASS +- 安全基线:PASS +- 生产阶段 1/2/3 关键 E2E:PASS + ## 7. 生产部署建议(当前阶段) 当前试点服务器部署口径: diff --git a/project-docs/运维手册-2026-05-01.md b/project-docs/运维手册-2026-05-01.md index ca07158d..b4e81256 100644 --- a/project-docs/运维手册-2026-05-01.md +++ b/project-docs/运维手册-2026-05-01.md @@ -174,7 +174,39 @@ backend\.venv\Scripts\python.exe -m pytest -q backend/tests --- -## 六、常见故障处理 +## 六、生产回归口径 + +执行生产阶段 1/2/3 关键 E2E 回归: + +```powershell +$env:AI_CONTRACT_ADMIN_PASSWORD = '<由管理员输入的实际密码>' +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-regression.ps1 +Remove-Item Env:\AI_CONTRACT_ADMIN_PASSWORD +``` + +默认产物目录: + +```text +project-docs/qa-artifacts/phase5-production-regression// +``` + +回归内容: + +1. 阶段 1 生产关键 E2E。 +2. 阶段 2 生产关键 E2E。 +3. 阶段 3 生产关键 E2E。 +4. 每个阶段保留独立日志和产物目录。 + +门禁: + +1. 管理员真实密码只允许在当前终端进程中临时存在。 +2. 密码不得写入脚本、文档、Git、`.env` 或日志。 +3. 任一阶段 E2E 失败时,本次生产回归失败。 +4. 依赖升级、服务重启、密钥轮换、部署变更后必须复跑生产健康巡检、安全基线和生产回归。 + +--- + +## 七、常见故障处理 查看容器状态: @@ -205,3 +237,4 @@ docker compose -f docker-compose.server.yml up -d backend frontend ``` 重启后必须重新执行生产健康巡检。 +如 backend 被重新创建后前端出现 API 代理解析失败,应同时重启 frontend,并重新执行生产健康巡检、安全基线和生产回归。 diff --git a/project-docs/闭环交付与深度开发方案-2026-05-01.md b/project-docs/闭环交付与深度开发方案-2026-05-01.md index 01f00098..65c48368 100644 --- a/project-docs/闭环交付与深度开发方案-2026-05-01.md +++ b/project-docs/闭环交付与深度开发方案-2026-05-01.md @@ -14,11 +14,11 @@ | 阶段 2:深度开发与交付体验补强 | PASS | `project-docs/阶段2-深度开发与交付体验补强执行记录-2026-05-01.md` | | 阶段 3:试点交付准备 | PASS | `project-docs/阶段3-试点交付准备执行记录-2026-05-01.md` | | 阶段 4:试点运行监控与安全加固 | PASS | `project-docs/阶段4-试点运行监控与安全加固执行记录-2026-05-01.md` | -| 阶段 5:生产运行、安全、监控、备份与依赖治理 | IN PROGRESS | `project-docs/阶段5-生产运行安全监控备份与依赖治理执行记录-2026-05-01.md` | +| 阶段 5:生产运行、安全、监控、备份与依赖治理 | PASS | `project-docs/阶段5-生产运行安全监控备份与依赖治理执行记录-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 安全基线均已 PASS,下一步进入生产回归闭环。 +当前后续重点为“生产运行、安全、监控、备份与依赖治理”:默认密码轮换已完成,阶段 5 Step 1 生产健康巡检、Step 2 备份恢复演练、Step 3 依赖治理、Step 4 安全基线、Step 5 生产回归均已 PASS,阶段 5 已形成生产运行治理闭环。 --- @@ -174,7 +174,7 @@ AI合同平台当前已经不是从零建设阶段。登录、合同审查、Rev --- -## 四、阶段 5 立即执行清单 +## 四、阶段 5 已执行清单 第一批先建立生产运行基线: @@ -184,7 +184,7 @@ AI合同平台当前已经不是从零建设阶段。登录、合同审查、Rev 4. 验证默认密码不可登录,管理员真实密码只通过运行时环境变量注入。 5. 生成 `production-health-summary.json` 和 `production-health-summary.md`。 -第二批进入治理演练: +第二批治理演练: 1. 新增或完善备份恢复演练脚本。 2. 运行前端 `npm audit --json` 和后端 `pip check`。 @@ -192,6 +192,14 @@ AI合同平台当前已经不是从零建设阶段。登录、合同审查、Rev 4. 复跑生产阶段 1/2/3 核心 E2E。 5. 更新运维手册和阶段 5 执行记录。 +当前结果: + +1. 生产健康巡检 PASS:`project-docs/qa-artifacts/phase5-production-health/2026-05-01T09-10-20/` +2. 备份恢复演练 PASS:`project-docs/qa-artifacts/phase5-backup-drill/2026-05-01T08-40-59/` +3. 依赖治理 PASS:`project-docs/qa-artifacts/phase5-dependency-governance/2026-05-01T09-08-52/` +4. 安全基线 PASS:`project-docs/qa-artifacts/phase5-security-baseline/2026-05-01T09-10-28/` +5. 生产回归 PASS:`project-docs/qa-artifacts/phase5-production-regression/2026-05-01T09-10-34/` + --- ## 五、交付完成定义 @@ -210,4 +218,4 @@ AI合同平台当前已经不是从零建设阶段。登录、合同审查、Rev ## 六、当前一句话结论 -AI合同平台阶段 1、阶段 2 和阶段 3 均已完成闭环交付验证,阶段 4 已完成默认管理员密码轮换闭环,阶段 5 已启动并完成生产健康巡检、备份恢复演练、依赖治理和安全基线,下一步进入生产回归闭环。 +AI合同平台阶段 1、阶段 2、阶段 3、阶段 4、阶段 5 均已完成闭环交付验证。当前系统已具备主业务闭环、试点交付准备、默认管理员密码轮换、生产健康巡检、备份恢复演练、依赖治理、安全基线和生产关键 E2E 回归证据。 diff --git a/project-docs/阶段5-生产运行安全监控备份与依赖治理执行记录-2026-05-01.md b/project-docs/阶段5-生产运行安全监控备份与依赖治理执行记录-2026-05-01.md index 5f5dcc41..f64f5688 100644 --- a/project-docs/阶段5-生产运行安全监控备份与依赖治理执行记录-2026-05-01.md +++ b/project-docs/阶段5-生产运行安全监控备份与依赖治理执行记录-2026-05-01.md @@ -14,9 +14,9 @@ | Step 2:备份与恢复演练 | PASS | `scripts/run-backup-drill.ps1` 已实现并完成生产备份/隔离恢复验证 | | Step 3:依赖治理专项 | PASS | Next/PostCSS moderate 漏洞已清零,前后端依赖门禁通过 | | Step 4:安全基线检查 | PASS | 生产 `SECRET_KEY` 已改为远端 `.env` 注入,安全基线脚本 PASS | -| Step 5:生产回归闭环 | PENDING | 下一步进入,复跑阶段 1/2/3 关键 E2E | +| Step 5:生产回归闭环 | PASS | `scripts/run-production-regression.ps1` 已实现,生产阶段 1/2/3 关键 E2E 全部通过 | -阶段 5 当前判定:Step 1、Step 2、Step 3、Step 4 均已 PASS。 +阶段 5 当前判定:Step 1、Step 2、Step 3、Step 4、Step 5 均已 PASS,已形成生产运行治理闭环。 --- @@ -84,17 +84,43 @@ default-password-login.json --- -## 五、下一步 +## 五、Step 5 生产回归闭环 -阶段 5 下一步进入 Step 5:生产回归闭环。 +新增脚本: -优先交付: +```powershell +scripts\run-production-regression.ps1 +``` -1. 阶段 1 生产关键 E2E -2. 阶段 2 生产关键 E2E -3. 阶段 3 生产关键 E2E -4. 生产健康巡检 -5. 最终阶段 5 闭环报告 +本次回归产物: + +```text +project-docs/qa-artifacts/phase5-production-regression/2026-05-01T09-10-34/ +``` + +最终复验产物: + +```text +project-docs/qa-artifacts/phase5-production-health/2026-05-01T09-10-20/ +project-docs/qa-artifacts/phase5-security-baseline/2026-05-01T09-10-28/ +``` + +回归结果: + +| 检查项 | 结果 | 说明 | +|---|---|---| +| 阶段 1 生产关键 E2E | PASS | 生产入口登录、合同审查、报告、合同生成等核心闭环通过 | +| 阶段 2 生产关键 E2E | PASS | Review 重试、Knowledge Base 重试、Review -> Chat -> Contract 联动通过 | +| 阶段 3 生产关键 E2E | PASS | Readiness、试点交付准备、默认账号说明、演示数据与生产门禁通过 | +| 生产健康巡检复验 | PASS | 容器、端口、`/health`、readiness、关键页面、默认密码拒绝通过 | +| 安全基线复验 | PASS | 强密钥、默认密码拒绝、member 权限边界、敏感文件扫描通过 | + +执行口径: + +1. 管理员真实密码只通过当前进程环境变量读取。 +2. 脚本、文档、Git、`.env` 均不保存管理员真实密码。 +3. 生产回归执行完成后应移除当前终端进程中的密码环境变量。 +4. 任何阶段 E2E 失败时,脚本退出失败并保留阶段级日志与产物目录。 --- @@ -167,7 +193,7 @@ scripts\run-dependency-governance.ps1 本次依赖治理产物: ```text -project-docs/qa-artifacts/phase5-dependency-governance/2026-05-01T08-46-29/ +project-docs/qa-artifacts/phase5-dependency-governance/2026-05-01T09-08-52/ ``` 验证记录: @@ -203,7 +229,7 @@ scripts\run-security-baseline.ps1 安全基线产物: ```text -project-docs/qa-artifacts/phase5-security-baseline/2026-05-01T08-55-24/ +project-docs/qa-artifacts/phase5-security-baseline/2026-05-01T09-10-28/ ``` 生产健康复验产物: diff --git a/project-docs/阶段5-生产运行安全监控备份与依赖治理方案-2026-05-01.md b/project-docs/阶段5-生产运行安全监控备份与依赖治理方案-2026-05-01.md index 097d9eae..1ceadb19 100644 --- a/project-docs/阶段5-生产运行安全监控备份与依赖治理方案-2026-05-01.md +++ b/project-docs/阶段5-生产运行安全监控备份与依赖治理方案-2026-05-01.md @@ -156,21 +156,25 @@ $env:AI_CONTRACT_ADMIN_PASSWORD = '<管理员实际密码>' --- -## 六、建议立即进入的第一步 +## 六、阶段闭环状态 -优先执行 Step 1:生产健康巡检自动化。 +阶段 5 已完成闭环。后续运维应把生产健康巡检、安全基线和生产回归作为上线、重启、依赖升级和密钥轮换后的固定门禁。 -理由: +原因: 1. 风险低,不改业务逻辑。 -2. 可以先建立阶段 5 的观测基线。 -3. 后续备份、依赖治理、安全基线和回归都可以复用同一套健康产物。 +2. 可以快速确认生产入口、readiness、默认密码拒绝和关键页面状态。 +3. 可以在安全、依赖、部署变更后立即发现回归。 -第一批文件建议: +当前阶段 5 已交付文件: 1. `scripts/run-production-health-check.ps1` -2. `project-docs/阶段5-生产运行安全监控备份与依赖治理执行记录-2026-05-01.md` -3. `project-docs/运维手册-2026-05-01.md` +2. `scripts/run-backup-drill.ps1` +3. `scripts/run-dependency-governance.ps1` +4. `scripts/run-security-baseline.ps1` +5. `scripts/run-production-regression.ps1` +6. `project-docs/阶段5-生产运行安全监控备份与依赖治理执行记录-2026-05-01.md` +7. `project-docs/运维手册-2026-05-01.md` 当前执行状态: @@ -182,14 +186,17 @@ $env:AI_CONTRACT_ADMIN_PASSWORD = '<管理员实际密码>' - 备份演练产物:`project-docs/qa-artifacts/phase5-backup-drill/2026-05-01T08-40-59/` - Step 3 已完成:`scripts/run-dependency-governance.ps1` - 依赖治理结果:PASS,`npm audit` 0 vulnerabilities,后端 `pip check` PASS -- 依赖治理产物:`project-docs/qa-artifacts/phase5-dependency-governance/2026-05-01T08-46-29/` +- 依赖治理产物:`project-docs/qa-artifacts/phase5-dependency-governance/2026-05-01T09-08-52/` - Step 4 已完成:`scripts/run-security-baseline.ps1` - 安全基线结果:PASS,生产 `SECRET_KEY` 已改为远端 `.env` 强密钥注入 -- 安全基线产物:`project-docs/qa-artifacts/phase5-security-baseline/2026-05-01T08-55-24/` -- 下一步:Step 5 生产回归闭环 +- 安全基线最终复验产物:`project-docs/qa-artifacts/phase5-security-baseline/2026-05-01T09-10-28/` +- Step 5 已完成:`scripts/run-production-regression.ps1` +- 生产回归结果:PASS,阶段 1/2/3 生产关键 E2E 全部通过 +- 生产回归产物:`project-docs/qa-artifacts/phase5-production-regression/2026-05-01T09-10-34/` +- 阶段 5 最终状态:PASS,生产运行、安全、监控、备份、依赖治理与回归已闭环 --- ## 七、一句话结论 -阶段 5 的核心不是再证明“业务能跑”,而是证明“系统能长期、安全、可恢复地跑”。下一步直接进入生产健康巡检自动化,先把运行状态变成每日可复查的事实。 +阶段 5 的核心不是再证明“业务能跑”,而是证明“系统能长期、安全、可恢复地跑”。当前阶段 5 已完成闭环,后续应进入常态化巡检、告警接入和发布前门禁固化。 diff --git a/scripts/run-production-regression.ps1 b/scripts/run-production-regression.ps1 new file mode 100644 index 00000000..5696f2f6 --- /dev/null +++ b/scripts/run-production-regression.ps1 @@ -0,0 +1,196 @@ +param( + [string]$WebUrl = "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 = "" +) + +$ErrorActionPreference = "Stop" + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +$frontendDir = Join-Path $repoRoot "frontend" +$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss" +if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) { + $ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase5-production-regression" +} +$artifactDir = Join-Path $ArtifactRoot $timestamp +$summaryJson = Join-Path $artifactDir "production-regression-summary.json" +$summaryMd = Join-Path $artifactDir "production-regression-summary.md" + +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] + +function Add-Check { + param( + [string]$Key, + [string]$Label, + [ValidateSet("pass", "fail")] + [string]$Status, + [string]$Detail, + [hashtable]$Meta = @{} + ) + + $checks.Add([ordered]@{ + key = $Key + label = $Label + status = $Status + required = $true + 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-NodePhase { + param( + [string]$Phase, + [string]$ScriptName + ) + + $phaseArtifactDir = Join-Path $artifactDir $Phase + New-Item -ItemType Directory -Force -Path $phaseArtifactDir | Out-Null + $logPath = Join-Path $artifactDir "$Phase.log" + + $previous = @{ + PHASE1_WEB_URL = $env:PHASE1_WEB_URL + PHASE1_API_URL = $env:PHASE1_API_URL + PHASE1_HEALTH_URL = $env:PHASE1_HEALTH_URL + PHASE1_ARTIFACT_DIR = $env:PHASE1_ARTIFACT_DIR + PHASE2_WEB_URL = $env:PHASE2_WEB_URL + PHASE2_API_URL = $env:PHASE2_API_URL + PHASE2_HEALTH_URL = $env:PHASE2_HEALTH_URL + PHASE2_ARTIFACT_DIR = $env:PHASE2_ARTIFACT_DIR + PHASE3_WEB_URL = $env:PHASE3_WEB_URL + PHASE3_API_URL = $env:PHASE3_API_URL + PHASE3_HEALTH_URL = $env:PHASE3_HEALTH_URL + PHASE3_READINESS_URL = $env:PHASE3_READINESS_URL + PHASE3_ARTIFACT_DIR = $env:PHASE3_ARTIFACT_DIR + } + + if ($Phase -eq "phase1") { + $env:PHASE1_WEB_URL = $WebUrl + $env:PHASE1_API_URL = $ApiUrl + $env:PHASE1_HEALTH_URL = $HealthUrl + $env:PHASE1_ARTIFACT_DIR = $phaseArtifactDir + } elseif ($Phase -eq "phase2") { + $env:PHASE2_WEB_URL = $WebUrl + $env:PHASE2_API_URL = $ApiUrl + $env:PHASE2_HEALTH_URL = $HealthUrl + $env:PHASE2_ARTIFACT_DIR = $phaseArtifactDir + } elseif ($Phase -eq "phase3") { + $env:PHASE3_WEB_URL = $WebUrl + $env:PHASE3_API_URL = $ApiUrl + $env:PHASE3_HEALTH_URL = $HealthUrl + $env:PHASE3_READINESS_URL = $ReadinessUrl + $env:PHASE3_ARTIFACT_DIR = $phaseArtifactDir + } + + Push-Location $frontendDir + $previousErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = "Continue" + try { + $output = & node ".\scripts\$ScriptName" 2>&1 + $exitCode = $LASTEXITCODE + } finally { + $ErrorActionPreference = $previousErrorActionPreference + Pop-Location + foreach ($key in $previous.Keys) { + Set-Item -Path "env:$key" -Value $previous[$key] -ErrorAction SilentlyContinue + } + } + + ($output -join [Environment]::NewLine) | Set-Content -Encoding UTF8 -Path $logPath + return [ordered]@{ + exit_code = $exitCode + log = $logPath + artifact_dir = $phaseArtifactDir + } +} + +$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 } +} else { + Add-Check -Key "phase1_production_e2e" -Label "phase1 production E2E" -Status "fail" -Detail "phase1 production E2E failed" -Meta @{ log = $phase1.log; artifact_dir = $phase1.artifact_dir } +} + +$phase2 = Invoke-NodePhase -Phase "phase2" -ScriptName "phase2-closed-loop-e2e.mjs" +if ($phase2.exit_code -eq 0) { + Add-Check -Key "phase2_production_e2e" -Label "phase2 production E2E" -Status "pass" -Detail "phase2 production E2E passed" -Meta @{ log = $phase2.log; artifact_dir = $phase2.artifact_dir } +} else { + Add-Check -Key "phase2_production_e2e" -Label "phase2 production E2E" -Status "fail" -Detail "phase2 production E2E failed" -Meta @{ log = $phase2.log; artifact_dir = $phase2.artifact_dir } +} + +$phase3 = Invoke-NodePhase -Phase "phase3" -ScriptName "phase3-closed-loop-e2e.mjs" +if ($phase3.exit_code -eq 0) { + Add-Check -Key "phase3_production_e2e" -Label "phase3 production E2E" -Status "pass" -Detail "phase3 production E2E passed" -Meta @{ log = $phase3.log; artifact_dir = $phase3.artifact_dir } +} else { + Add-Check -Key "phase3_production_e2e" -Label "phase3 production E2E" -Status "fail" -Detail "phase3 production E2E failed" -Meta @{ log = $phase3.log; artifact_dir = $phase3.artifact_dir } +} + +$failedRequired = @($checks | Where-Object { $_.status -eq "fail" }) +$overallStatus = if ($failedRequired.Count -gt 0) { "FAIL" } else { "PASS" } + +$summary = [ordered]@{ + name = "phase5-production-regression" + status = $overallStatus + generated_at = (Get-Date).ToUniversalTime().ToString("o") + artifact_dir = $artifactDir + targets = [ordered]@{ + web_url = $WebUrl + api_url = $ApiUrl + health_url = $HealthUrl + readiness_url = $ReadinessUrl + } + checks = $checks + totals = [ordered]@{ + total = $checks.Count + failed_required = $failedRequired.Count + } +} +Save-Json -Path $summaryJson -Value $summary + +$markdown = New-Object System.Collections.Generic.List[string] +$markdown.Add("# Phase 5 Production Regression") | Out-Null +$markdown.Add("") | Out-Null +$markdown.Add("- Status: $overallStatus") | Out-Null +$markdown.Add("- Generated at: $($summary.generated_at)") | Out-Null +$markdown.Add("- Artifact dir: $artifactDir") | Out-Null +$markdown.Add("- Web URL: $WebUrl") | Out-Null +$markdown.Add("- API URL: $ApiUrl") | 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("Local artifacts:") | Out-Null +$markdown.Add("") | Out-Null +$markdown.Add('- `production-regression-summary.json`') | Out-Null +$markdown.Add('- `phase1/`') | Out-Null +$markdown.Add('- `phase2/`') | Out-Null +$markdown.Add('- `phase3/`') | Out-Null +$markdown.Add('- `phase*.log`') | Out-Null +$markdown | Set-Content -Encoding UTF8 -Path $summaryMd + +Write-Host "PHASE5_PRODUCTION_REGRESSION_$overallStatus $artifactDir" +if ($overallStatus -eq "FAIL") { + throw "Phase 5 production regression failed. See $summaryMd" +} diff --git a/scripts/run-security-baseline.ps1 b/scripts/run-security-baseline.ps1 index d93fada7..80715b9c 100644 --- a/scripts/run-security-baseline.ps1 +++ b/scripts/run-security-baseline.ps1 @@ -278,7 +278,13 @@ if ($trackedSensitive) { } $secretScanPath = Join-Path $artifactDir "source-secret-scan.txt" -$secretScan = git -C $repoRoot grep -n -I -E 'BEGIN (RSA|OPENSSH|PRIVATE) KEY|zmfCSR|AI_CONTRACT_ADMIN_PASSWORD[[:space:]]*=' -- backend frontend scripts docker-compose.server.yml 2>$null +$secretPatterns = @( + "BEGIN (RSA|OPENSSH|PRIVATE) KEY", + ("z" + "mfCSR"), + ("AI_CONTRACT_ADMIN_PASSWORD" + "[[:space:]]*=") +) +$secretRegex = $secretPatterns -join "|" +$secretScan = git -C $repoRoot grep -n -I -E $secretRegex -- backend frontend scripts docker-compose.server.yml 2>$null if ($secretScan) { ($secretScan | ForEach-Object { $_ }) | Set-Content -Encoding UTF8 -Path $secretScanPath } else {