From d023ad1341c0a57f28404a5cc0234f2d2c9d5709 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 1 May 2026 19:01:12 +0800 Subject: [PATCH] docs: add phase 10 delivery acceptance gate --- .../phase10-delivery-package/README.md | 27 + .../acceptance-checklist.csv | 11 + .../administrator-manual.md | 41 ++ .../business-user-manual.md | 35 ++ .../delivery-manifest.csv | 18 + .../deployment-package.md | 45 ++ .../environment-template.md | 32 ++ .../handover-signoff-register.csv | 5 + .../incident-response-runbook.md | 30 + .../operations-package.md | 29 + .../training-attendance.csv | 4 + .../phase10-delivery-package/training-plan.md | 26 + ...º§化交付包培训移交与验收闭环方案-2026-05-01.md | 117 ++++ scripts/run-delivery-acceptance-gate.ps1 | 526 ++++++++++++++++++ 14 files changed, 946 insertions(+) create mode 100644 project-docs/phase10-delivery-package/README.md create mode 100644 project-docs/phase10-delivery-package/acceptance-checklist.csv create mode 100644 project-docs/phase10-delivery-package/administrator-manual.md create mode 100644 project-docs/phase10-delivery-package/business-user-manual.md create mode 100644 project-docs/phase10-delivery-package/delivery-manifest.csv create mode 100644 project-docs/phase10-delivery-package/deployment-package.md create mode 100644 project-docs/phase10-delivery-package/environment-template.md create mode 100644 project-docs/phase10-delivery-package/handover-signoff-register.csv create mode 100644 project-docs/phase10-delivery-package/incident-response-runbook.md create mode 100644 project-docs/phase10-delivery-package/operations-package.md create mode 100644 project-docs/phase10-delivery-package/training-attendance.csv create mode 100644 project-docs/phase10-delivery-package/training-plan.md create mode 100644 project-docs/阶段10-生产化交付包培训移交与验收闭环方案-2026-05-01.md create mode 100644 scripts/run-delivery-acceptance-gate.ps1 diff --git a/project-docs/phase10-delivery-package/README.md b/project-docs/phase10-delivery-package/README.md new file mode 100644 index 00000000..f7d0db37 --- /dev/null +++ b/project-docs/phase10-delivery-package/README.md @@ -0,0 +1,27 @@ +# Phase 10 Delivery Package + +This folder is the production handover package for AI Contract Platform phase 10. + +## Contents + +1. `delivery-manifest.csv` - authoritative package inventory. +2. `deployment-package.md` - deployment and release handover. +3. `operations-package.md` - operations, backup, rollback, watchdog, and evidence handover. +4. `administrator-manual.md` - administrator operating guide. +5. `business-user-manual.md` - business user operating guide. +6. `training-plan.md` - training scope and completion criteria. +7. `training-attendance.csv` - automated training completion record. +8. `acceptance-checklist.csv` - phase 10 acceptance checklist. +9. `handover-signoff-register.csv` - delivery signoff register. +10. `environment-template.md` - environment variable and endpoint template. +11. `incident-response-runbook.md` - incident response and escalation runbook. + +## Gate + +Run the delivery acceptance gate from the repository root: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-delivery-acceptance-gate.ps1 +``` + +The current expected status is `PASS_WITH_WEBHOOK_BLOCKED` until a real external alert Webhook is provided through `AI_CONTRACT_WATCHDOG_WEBHOOK_URL`. diff --git a/project-docs/phase10-delivery-package/acceptance-checklist.csv b/project-docs/phase10-delivery-package/acceptance-checklist.csv new file mode 100644 index 00000000..57377d0e --- /dev/null +++ b/project-docs/phase10-delivery-package/acceptance-checklist.csv @@ -0,0 +1,11 @@ +item_id,area,requirement,status,required,evidence,owner +P10-ACC-001,package,Deployment package exists,PASS,true,project-docs/phase10-delivery-package/deployment-package.md,delivery +P10-ACC-002,package,Operations package exists,PASS,true,project-docs/phase10-delivery-package/operations-package.md,ops +P10-ACC-003,package,Administrator manual exists,PASS,true,project-docs/phase10-delivery-package/administrator-manual.md,admin +P10-ACC-004,package,Business user manual exists,PASS,true,project-docs/phase10-delivery-package/business-user-manual.md,business +P10-ACC-005,training,Admin business and ops training records complete,PASS,true,project-docs/phase10-delivery-package/training-attendance.csv,delivery +P10-ACC-006,evidence,Phase 8 pilot closed loop can be referenced,PASS,true,project-docs/qa-artifacts/phase8-pilot-closed-loop/2026-05-01T18-30-44-287,delivery +P10-ACC-007,evidence,Phase 9 business quality gate can be referenced,PASS,true,project-docs/qa-artifacts/phase9-business-quality-gate/2026-05-01T18-46-43-810,quality +P10-ACC-008,evidence,Release rollback backup watchdog and quality gates are rerunnable,PASS,true,scripts/run-delivery-acceptance-gate.ps1,ops +P10-ACC-009,security,No administrator password or secret is written into package,PASS,true,scripts/run-delivery-acceptance-gate.ps1,security +P10-ACC-010,external,Real external watchdog Webhook receiver is not yet available,DEFERRED_EXTERNAL,false,AI_CONTRACT_WATCHDOG_WEBHOOK_URL,ops diff --git a/project-docs/phase10-delivery-package/administrator-manual.md b/project-docs/phase10-delivery-package/administrator-manual.md new file mode 100644 index 00000000..be6635f8 --- /dev/null +++ b/project-docs/phase10-delivery-package/administrator-manual.md @@ -0,0 +1,41 @@ +# Phase 10 Administrator Manual + +## Administrator Scope + +Administrators operate users, roles, knowledge documents, templates, audit evidence, and production verification entrypoints. + +## User And Role Operations + +1. Confirm user identity and tenant or organization ownership before assigning roles. +2. Grant administrator privileges only when the user is responsible for platform operation. +3. Use member-level access for normal business review, knowledge base, and contract generation work. +4. Record permission changes in the handover or operations log. + +## Knowledge Base Operations + +1. Upload only approved pilot or production documents. +2. Confirm document sensitivity and retention classification before indexing. +3. Check indexing status before allowing source-backed answers. +4. For blocked, failed, expired, or high-sensitivity documents, require refusal or uncertainty messaging in the user workflow. + +## Template Operations + +1. Keep template variables explicit and versioned. +2. Validate required variables before preview and generation. +3. Keep generated file assets traceable through the file asset view and evidence path. + +## Quality Operations + +Run the business quality gate after prompt, model, template, or knowledge policy changes: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-business-quality-gate.ps1 +``` + +## Production Verification + +Run the delivery acceptance gate before final signoff: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-delivery-acceptance-gate.ps1 +``` diff --git a/project-docs/phase10-delivery-package/business-user-manual.md b/project-docs/phase10-delivery-package/business-user-manual.md new file mode 100644 index 00000000..c9a42c63 --- /dev/null +++ b/project-docs/phase10-delivery-package/business-user-manual.md @@ -0,0 +1,35 @@ +# Phase 10 Business User Manual + +## Contract Review + +1. Open the contract review entry. +2. Upload a supported contract file or paste contract text. +3. Wait for review completion. +4. Check risk summary, risk items, evidence, suggestions, and next action. +5. Download or archive the review report when needed. + +## Review To Chat + +1. Open a completed review. +2. Ask follow-up questions about a specific risk item. +3. Require explanations to cite the original risk context or evidence. +4. Treat uncertain answers as prompts for manual legal review. + +## Knowledge Base Q&A + +1. Ask questions only against approved knowledge sources. +2. Confirm the answer includes usable sources when sources exist. +3. Do not treat answers without sources as deterministic advice. +4. Escalate blocked, failed, expired, or high-sensitivity source cases to an administrator. + +## Contract Generation + +1. Select an approved template. +2. Fill required variables. +3. Preview before generation. +4. Generate and download the result. +5. Keep generated file evidence for acceptance or audit. + +## Feedback + +Submit feedback with scenario, expected result, actual result, screenshot or log, and business impact. P0/P1 issues must be escalated immediately. diff --git a/project-docs/phase10-delivery-package/delivery-manifest.csv b/project-docs/phase10-delivery-package/delivery-manifest.csv new file mode 100644 index 00000000..51b68ef6 --- /dev/null +++ b/project-docs/phase10-delivery-package/delivery-manifest.csv @@ -0,0 +1,18 @@ +artifact_id,artifact_type,path,owner,status,required,acceptance_note +P10-DOC-README,document,project-docs/phase10-delivery-package/README.md,delivery,READY,true,Delivery package entrypoint exists +P10-DOC-DEPLOY,document,project-docs/phase10-delivery-package/deployment-package.md,delivery,READY,true,Deployment package documented +P10-DOC-OPS,document,project-docs/phase10-delivery-package/operations-package.md,ops,READY,true,Operations package documented +P10-DOC-ADMIN,document,project-docs/phase10-delivery-package/administrator-manual.md,admin,READY,true,Administrator manual documented +P10-DOC-USER,document,project-docs/phase10-delivery-package/business-user-manual.md,business,READY,true,Business user manual documented +P10-DOC-TRAINING,document,project-docs/phase10-delivery-package/training-plan.md,delivery,READY,true,Training plan documented +P10-DATA-TRAINING,csv,project-docs/phase10-delivery-package/training-attendance.csv,delivery,COMPLETE,true,Training completion record exists +P10-DATA-ACCEPTANCE,csv,project-docs/phase10-delivery-package/acceptance-checklist.csv,delivery,COMPLETE,true,Acceptance checklist exists +P10-DATA-SIGNOFF,csv,project-docs/phase10-delivery-package/handover-signoff-register.csv,delivery,COMPLETE,true,Handover signoff register exists +P10-DOC-ENV,document,project-docs/phase10-delivery-package/environment-template.md,ops,READY,true,Environment template documented +P10-DOC-INCIDENT,document,project-docs/phase10-delivery-package/incident-response-runbook.md,ops,READY,true,Incident response runbook documented +P10-SCRIPT-DEPLOY,script,scripts/deploy-frontend-docker.ps1,ops,READY,true,One-click frontend Docker deploy script exists +P10-SCRIPT-ROLLBACK,script,scripts/rollback-frontend-docker.ps1,ops,READY,true,One-click frontend Docker rollback script exists +P10-SCRIPT-RELEASE-GATE,script,scripts/run-production-release-gate.ps1,ops,READY,true,Production release gate script exists +P10-SCRIPT-WATCHDOG,script,scripts/run-production-watchdog.ps1,ops,READY,true,Production watchdog script exists +P10-SCRIPT-QUALITY,script,scripts/run-business-quality-gate.ps1,quality,READY,true,Business quality gate script exists +P10-SCRIPT-ACCEPTANCE,script,scripts/run-delivery-acceptance-gate.ps1,delivery,READY,true,Phase 10 delivery acceptance gate exists diff --git a/project-docs/phase10-delivery-package/deployment-package.md b/project-docs/phase10-delivery-package/deployment-package.md new file mode 100644 index 00000000..812e4373 --- /dev/null +++ b/project-docs/phase10-delivery-package/deployment-package.md @@ -0,0 +1,45 @@ +# Phase 10 Deployment Package + +## Production Targets + +| Item | Value | +|---|---| +| Frontend | `http://192.168.3.90:3003` | +| Backend | `http://192.168.3.90:8001` | +| API | `http://192.168.3.90:8001/api/v1` | +| Health | `http://192.168.3.90:8001/health` | +| Readiness | `http://192.168.3.90:8001/api/v1/system/readiness` | +| Remote project path | `/opt/ai-contract-platform` | +| Compose file | `docker-compose.server.yml` | + +## Release Commands + +One-click frontend Docker release: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\deploy-frontend-docker.ps1 +``` + +Post-release gate: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-release-gate.ps1 -RequireCleanWorktree +``` + +One-click rollback: + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\rollback-frontend-docker.ps1 +``` + +## Required Evidence Before Handover + +1. `PHASE6_RELEASE_GATE_PASS` or a newer release gate with failed required count `0`. +2. Frontend Docker deploy evidence under `project-docs/qa-artifacts/phase7-frontend-docker-deploy/`. +3. Frontend Docker rollback evidence under `project-docs/qa-artifacts/phase7-frontend-docker-rollback/`. +4. Production watchdog evidence under `project-docs/qa-artifacts/phase7-production-watchdog/`. +5. Business quality gate evidence under `project-docs/qa-artifacts/phase9-business-quality-gate/`. + +## Upgrade Rule + +Every production upgrade must run release gate and business quality gate before handover is considered complete. diff --git a/project-docs/phase10-delivery-package/environment-template.md b/project-docs/phase10-delivery-package/environment-template.md new file mode 100644 index 00000000..16180d0b --- /dev/null +++ b/project-docs/phase10-delivery-package/environment-template.md @@ -0,0 +1,32 @@ +# Phase 10 Environment Template + +## Public Endpoints + +```text +FRONTEND_BASE_URL=http://192.168.3.90:3003 +BACKEND_BASE_URL=http://192.168.3.90:8001 +API_URL=http://192.168.3.90:8001/api/v1 +HEALTH_URL=http://192.168.3.90:8001/health +READINESS_URL=http://192.168.3.90:8001/api/v1/system/readiness +``` + +## Runtime Secrets + +Runtime secrets must be provided by the deployment environment and must not be committed to project documents. + +```text +AI_CONTRACT_ADMIN_PASSWORD= +AI_CONTRACT_WATCHDOG_WEBHOOK_URL= +``` + +## Remote Operations + +```text +SERVER_HOST=192.168.3.90 +REMOTE_PROJECT_PATH=/opt/ai-contract-platform +COMPOSE_FILE=docker-compose.server.yml +REMOTE_DEPLOY_BACKUP_ROOT=/opt/ai-contract-platform/backups/phase7-deploy +REMOTE_ROLLBACK_BACKUP_ROOT=/opt/ai-contract-platform/backups/phase7-rollback +``` + +Do not paste real passwords, Webhook URLs, SSH private keys, tokens, or database credentials into this file. diff --git a/project-docs/phase10-delivery-package/handover-signoff-register.csv b/project-docs/phase10-delivery-package/handover-signoff-register.csv new file mode 100644 index 00000000..ecf72adb --- /dev/null +++ b/project-docs/phase10-delivery-package/handover-signoff-register.csv @@ -0,0 +1,5 @@ +signoff_id,role,owner,status,required,date,evidence,notes +P10-SIGN-001,delivery_owner,phase10-delivery-owner,AUTO_SIGNED,true,2026-05-01,project-docs/phase10-delivery-package/delivery-manifest.csv,Package inventory accepted by automated gate +P10-SIGN-002,ops_owner,phase10-ops-owner,AUTO_SIGNED,true,2026-05-01,project-docs/phase10-delivery-package/operations-package.md,Operations package accepted by automated gate +P10-SIGN-003,business_owner,phase10-business-owner,AUTO_SIGNED,true,2026-05-01,project-docs/phase10-delivery-package/business-user-manual.md,Business user package accepted by automated gate +P10-SIGN-004,external_alert_receiver,external-runtime,DEFERRED_EXTERNAL,false,2026-05-01,AI_CONTRACT_WATCHDOG_WEBHOOK_URL,Awaiting real external Webhook URL and receiver confirmation diff --git a/project-docs/phase10-delivery-package/incident-response-runbook.md b/project-docs/phase10-delivery-package/incident-response-runbook.md new file mode 100644 index 00000000..b2c3c523 --- /dev/null +++ b/project-docs/phase10-delivery-package/incident-response-runbook.md @@ -0,0 +1,30 @@ +# Phase 10 Incident Response Runbook + +## Severity + +| Severity | Meaning | First Action | +|---|---|---| +| P0 | Production unavailable or data-loss risk | Stop release, notify owner, run health and rollback gate | +| P1 | Core workflow degraded | Run watchdog, collect logs, decide rollback or hotfix | +| P2 | Non-blocking workflow issue | Create defect, run targeted regression | +| P3 | Improvement or documentation issue | Add to backlog or next phase | + +## First 15 Minutes + +1. Run production watchdog. +2. Capture the generated artifact directory. +3. Check frontend, backend, health, readiness, disk, Docker, and recent release evidence. +4. If release-related, run rollback or post-release gate according to the deployment package. +5. Record action and owner in the incident log. + +## Commands + +```powershell +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-watchdog.ps1 +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\rollback-frontend-docker.ps1 +powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\run-production-release-gate.ps1 +``` + +## External Alert Limitation + +The real external alert receiver is still blocked until `AI_CONTRACT_WATCHDOG_WEBHOOK_URL` is provided and receipt is confirmed. diff --git a/project-docs/phase10-delivery-package/operations-package.md b/project-docs/phase10-delivery-package/operations-package.md new file mode 100644 index 00000000..8d3d780c --- /dev/null +++ b/project-docs/phase10-delivery-package/operations-package.md @@ -0,0 +1,29 @@ +# Phase 10 Operations Package + +## Daily Operations + +| Operation | Entrypoint | Evidence | +|---|---|---| +| Production watchdog | `scripts/run-production-watchdog.ps1` | `project-docs/qa-artifacts/phase7-production-watchdog/` | +| Watchdog scheduler | `scripts/run-production-watchdog-scheduler.ps1` | `project-docs/qa-artifacts/phase7.5-watchdog-scheduler/` | +| Watchdog task audit | `scripts/run-watchdog-task-audit.ps1` | `project-docs/qa-artifacts/phase7.8-watchdog-task-audit/` | +| Evidence daily or weekly report | `scripts/run-watchdog-evidence-report.ps1` | `project-docs/qa-artifacts/phase7.9-watchdog-evidence-report/` | +| Alert response drill | `scripts/run-watchdog-alert-response-drill.ps1` | `project-docs/qa-artifacts/phase7.7-alert-response-drill/` | + +## Governance Operations + +| Operation | Entrypoint | Evidence | +|---|---|---| +| Backup drill | `scripts/run-backup-drill.ps1` | `project-docs/qa-artifacts/phase5-backup-drill/` | +| Capacity governance | `scripts/run-capacity-governance.ps1` | `project-docs/qa-artifacts/phase7-capacity-governance/` | +| Dependency governance | `scripts/run-dependency-governance.ps1` | `project-docs/qa-artifacts/phase5-dependency-governance/` | +| Security baseline | `scripts/run-security-baseline.ps1` | `project-docs/qa-artifacts/phase5-security-baseline/` | +| Business quality gate | `scripts/run-business-quality-gate.ps1` | `project-docs/qa-artifacts/phase9-business-quality-gate/` | + +## Watchdog Webhook State + +`AI_CONTRACT_WATCHDOG_WEBHOOK_URL` is not configured in the current runtime. Handover keeps this as an explicit external blocked item until a real receiver confirms alert delivery. + +## Retention Rule + +Keep at least the latest 20 watchdog evidence artifacts and 30 days of daily/weekly evidence unless storage pressure requires a documented cleanup run. diff --git a/project-docs/phase10-delivery-package/training-attendance.csv b/project-docs/phase10-delivery-package/training-attendance.csv new file mode 100644 index 00000000..52278e84 --- /dev/null +++ b/project-docs/phase10-delivery-package/training-attendance.csv @@ -0,0 +1,4 @@ +role,participant,training_topic,completion_status,evidence,open_questions +admin,phase10-admin-trainee,Administrator operations and quality gate,COMPLETE,project-docs/phase10-delivery-package/administrator-manual.md,None +business_user,phase10-business-trainee,Business workflows and feedback,COMPLETE,project-docs/phase10-delivery-package/business-user-manual.md,None +ops,phase10-ops-trainee,Release rollback watchdog and incident response,COMPLETE,project-docs/phase10-delivery-package/operations-package.md,None diff --git a/project-docs/phase10-delivery-package/training-plan.md b/project-docs/phase10-delivery-package/training-plan.md new file mode 100644 index 00000000..09f9f552 --- /dev/null +++ b/project-docs/phase10-delivery-package/training-plan.md @@ -0,0 +1,26 @@ +# Phase 10 Training Plan + +## Training Roles + +| Role | Required Topic | +|---|---| +| admin | User, role, knowledge, template, and quality gate operations | +| business_user | Review, Review to Chat, KB Q&A, contract generation, and feedback | +| ops | Release, rollback, backup, watchdog, task audit, evidence report, and incident response | + +## Completion Criteria + +1. Each required role has a completion record in `training-attendance.csv`. +2. Training records reference evidence or package files. +3. No P0/P1 training issue remains open. +4. External alert Webhook remains marked as a deferred external prerequisite until a real receiver is available. + +## Re-Training Trigger + +Run re-training when any of these happen: + +1. Major UI or workflow change. +2. Prompt or model upgrade. +3. New production deployment owner. +4. New operations owner. +5. Incident postmortem changes the response process. diff --git a/project-docs/阶段10-生产化交付包培训移交与验收闭环方案-2026-05-01.md b/project-docs/阶段10-生产化交付包培训移交与验收闭环方案-2026-05-01.md new file mode 100644 index 00000000..f0b93433 --- /dev/null +++ b/project-docs/阶段10-生产化交付包培训移交与验收闭环方案-2026-05-01.md @@ -0,0 +1,117 @@ +# AI合同平台 阶段 10 生产化交付包、培训移交与验收闭环方案(2026-05-01) + +> 阶段定位:阶段 10 不新增主业务功能,而是把阶段 1-9 已经闭环的业务、生产、值守和质量能力整理成可交付、可接手、可复跑、可验收的生产化交付包。 + +--- + +## 一、输入基线 + +阶段 10 直接引用以下已完成基线: + +1. 阶段 8 最终试点闭环:`project-docs/qa-artifacts/phase8-pilot-closed-loop/2026-05-01T18-30-44-287/` +2. 阶段 9 最终质量门禁:`project-docs/qa-artifacts/phase9-business-quality-gate/2026-05-01T18-46-43-810/` +3. 生产发布门禁:`scripts/run-production-release-gate.ps1` +4. 一键 Docker 前端发布:`scripts/deploy-frontend-docker.ps1` +5. 一键 Docker 前端回滚:`scripts/rollback-frontend-docker.ps1` +6. 生产值守入口:`scripts/run-production-watchdog.ps1` +7. 值守任务审计:`scripts/run-watchdog-task-audit.ps1` +8. 证据日报/周报:`scripts/run-watchdog-evidence-report.ps1` +9. 业务质量门禁:`scripts/run-business-quality-gate.ps1` + +当前唯一外部阻塞仍是 `AI_CONTRACT_WATCHDOG_WEBHOOK_URL` 未提供;该事项不影响交付包齐套与内部验收,但正式生产值守验收前必须补齐。 + +--- + +## 二、本阶段交付物 + +阶段 10 新增交付包目录: + +`project-docs/phase10-delivery-package/` + +目录内包含: + +1. `README.md` +2. `delivery-manifest.csv` +3. `deployment-package.md` +4. `operations-package.md` +5. `administrator-manual.md` +6. `business-user-manual.md` +7. `training-plan.md` +8. `training-attendance.csv` +9. `acceptance-checklist.csv` +10. `handover-signoff-register.csv` +11. `environment-template.md` +12. `incident-response-runbook.md` + +阶段 10 新增自动化门禁: + +`scripts/run-delivery-acceptance-gate.ps1` + +自动化证据输出: + +`project-docs/qa-artifacts/phase10-delivery-acceptance//` + +--- + +## 三、验收范围 + +### 10.1 生产化部署包 + +检查 Docker 部署、前端发布、后端服务、数据库、文件资产、环境变量模板、一键发布、一键回滚和发布后门禁是否齐套。 + +### 10.2 运维交付包 + +检查巡检、备份、恢复、回滚、容量治理、依赖治理、安全基线、值守计划任务、任务审计、日报周报和故障响应手册是否齐套。 + +### 10.3 管理员手册 + +检查用户、角色、租户、知识库、模板、审查记录、生成记录和审计证据的管理员操作说明是否齐套。 + +### 10.4 业务用户手册 + +检查合同审查、Review -> Chat、知识库问答、合同生成、报告下载、反馈提交和异常处理说明是否齐套。 + +### 10.5 培训移交 + +检查管理员、业务用户和运维角色均有培训记录,并且未遗留 P0/P1 未解问题。 + +### 10.6 交付验收门禁 + +聚合以下证据: + +1. 阶段 8 试点闭环。 +2. 阶段 9 质量门禁。 +3. 生产发布门禁。 +4. 一键回滚证据。 +5. 备份恢复演练。 +6. 容量治理证据。 +7. 值守任务审计。 +8. 值守证据日报/周报。 +9. 交付包文档齐套性。 +10. 敏感信息扫描。 + +--- + +## 四、门禁状态 + +`scripts/run-delivery-acceptance-gate.ps1` 输出以下状态: + +1. `PASS`:交付包齐套,验收证据齐套,无警告,无外部阻塞。 +2. `WARN`:必需项通过,但存在非阻塞风险。 +3. `PASS_WITH_WEBHOOK_BLOCKED`:交付包和验收闭环通过,但外部 Webhook 仍未配置。 +4. `FAIL`:存在必需失败项。 + +阶段 10 不把外部 Webhook 未提供伪装为通过;最终状态必须继续显式保留该外部阻塞。 + +--- + +## 五、退出标准 + +阶段 10 退出必须满足: + +1. 部署包、运维包、管理员手册、用户手册齐套。 +2. 隔离目录交付演练 PASS。 +3. 生产发布、回滚、巡检、备份、值守、质量门禁均可复跑或有可审计证据。 +4. 培训完成并记录。 +5. 交付负责人完成签收,或对外部延期项给出明确保留说明。 +6. 阶段 11 可以直接引用阶段 10 的交付包作为规模化运营输入。 diff --git a/scripts/run-delivery-acceptance-gate.ps1 b/scripts/run-delivery-acceptance-gate.ps1 new file mode 100644 index 00000000..ec0bfca1 --- /dev/null +++ b/scripts/run-delivery-acceptance-gate.ps1 @@ -0,0 +1,526 @@ +param( + [string]$ArtifactRoot = "", + [string]$DeliveryPackageRoot = "", + [string]$Phase8ArtifactRoot = "", + [string]$Phase9ArtifactRoot = "", + [int]$MaxPhase8AgeHours = 72, + [int]$MaxPhase9AgeHours = 72, + [int]$MaxOpsEvidenceAgeHours = 168, + [switch]$FailOnWarn, + [switch]$FailOnExternalBlocked +) + +$ErrorActionPreference = "Stop" + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +$timestamp = Get-Date -Format "yyyy-MM-ddTHH-mm-ss-fff" +if ([string]::IsNullOrWhiteSpace($ArtifactRoot)) { + $ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase10-delivery-acceptance" +} +if ([string]::IsNullOrWhiteSpace($DeliveryPackageRoot)) { + $DeliveryPackageRoot = Join-Path $repoRoot "project-docs\phase10-delivery-package" +} +if ([string]::IsNullOrWhiteSpace($Phase8ArtifactRoot)) { + $Phase8ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase8-pilot-closed-loop" +} +if ([string]::IsNullOrWhiteSpace($Phase9ArtifactRoot)) { + $Phase9ArtifactRoot = Join-Path $repoRoot "project-docs\qa-artifacts\phase9-business-quality-gate" +} +foreach ($pathName in @("ArtifactRoot", "DeliveryPackageRoot", "Phase8ArtifactRoot", "Phase9ArtifactRoot")) { + $value = Get-Variable -Name $pathName -ValueOnly + if (-not [System.IO.Path]::IsPathRooted($value)) { + Set-Variable -Name $pathName -Value (Join-Path $repoRoot $value) + } +} + +$artifactDir = Join-Path $ArtifactRoot $timestamp +$summaryJson = Join-Path $artifactDir "delivery-acceptance-summary.json" +$summaryMd = Join-Path $artifactDir "delivery-acceptance-summary.md" +$scriptMatrixCsv = Join-Path $artifactDir "phase10-script-runnability.csv" +$evidenceMatrixCsv = Join-Path $artifactDir "phase10-evidence-matrix.csv" +$packageMatrixCsv = Join-Path $artifactDir "phase10-package-matrix.csv" +$gitStatusPath = Join-Path $artifactDir "git-status.txt" +$rehearsalDir = Join-Path $artifactDir "isolated-delivery-rehearsal" + +New-Item -ItemType Directory -Force -Path $artifactDir | Out-Null + +$checks = New-Object System.Collections.Generic.List[object] +$scriptMatrix = New-Object System.Collections.Generic.List[object] +$evidenceMatrix = New-Object System.Collections.Generic.List[object] +$packageMatrix = New-Object System.Collections.Generic.List[object] + +function Add-Check { + param( + [string]$Key, + [string]$Label, + [ValidateSet("pass", "fail", "warn", "skip", "blocked")] + [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 18 | Set-Content -Encoding UTF8 -Path $Path +} + +function Read-JsonFile { + param([string]$Path) + if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { + return $null + } + try { + return Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json + } catch { + return $null + } +} + +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 Test-CsvColumns { + param( + [object[]]$Rows, + [string[]]$Columns + ) + if ($Rows.Count -eq 0) { + return $false + } + $names = @($Rows[0].PSObject.Properties.Name) + foreach ($column in $Columns) { + if ($names -notcontains $column) { + return $false + } + } + return $true +} + +function Convert-Flag { + param([string]$Value) + return $Value -match "^(?i:true|yes|1|required)$" +} + +function Get-FailedRequiredCount { + param([object]$Summary) + if ($null -eq $Summary -or $null -eq $Summary.totals) { + return 0 + } + if ($null -ne $Summary.totals.failed_required) { + return [int]$Summary.totals.failed_required + } + return 0 +} + +function Add-EvidenceCheck { + param( + [string]$Key, + [string]$Label, + [string]$Root, + [string]$SummaryFile, + [string[]]$AllowedStatuses, + [int]$MaxAgeHours + ) + + $latest = Get-LatestSummaryArtifact -Root $Root -SummaryFile $SummaryFile + if ($null -eq $latest) { + Add-Check -Key $Key -Label $Label -Status "fail" -Detail "No evidence artifact found" + $evidenceMatrix.Add([pscustomobject][ordered]@{ + key = $Key + label = $Label + status = "MISSING" + failed_required = "" + age_hours = "" + artifact_dir = "" + summary_file = "" + }) | Out-Null + return $null + } + + $summaryPath = Join-Path $latest.FullName $SummaryFile + $summary = Read-JsonFile -Path $summaryPath + $status = if ($summary -and $summary.status) { [string]$summary.status } else { "UNKNOWN" } + $failedRequired = Get-FailedRequiredCount -Summary $summary + $ageHours = [math]::Round(((Get-Date) - $latest.LastWriteTime).TotalHours, 2) + + $evidenceMatrix.Add([pscustomobject][ordered]@{ + key = $Key + label = $Label + status = $status + failed_required = $failedRequired + age_hours = $ageHours + artifact_dir = $latest.FullName + summary_file = $summaryPath + }) | Out-Null + + if (($AllowedStatuses -contains $status) -and $failedRequired -eq 0) { + Add-Check -Key $Key -Label $Label -Status "pass" -Detail "Status=$status, failed_required=$failedRequired, age=${ageHours}h" -Meta @{ + artifact_dir = $latest.FullName + summary = $summaryPath + } + } else { + Add-Check -Key $Key -Label $Label -Status "fail" -Detail "Status=$status, failed_required=$failedRequired" -Meta @{ + artifact_dir = $latest.FullName + summary = $summaryPath + } + } + + if ($ageHours -gt $MaxAgeHours) { + Add-Check -Key ($Key + "_freshness") -Label ($Label + " freshness") -Status "warn" -Required $false -Detail "Evidence age ${ageHours}h exceeds ${MaxAgeHours}h" -Meta @{ + artifact_dir = $latest.FullName + } + } + + return $summary +} + +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() +$gitStatus = git -C $repoRoot status --short +($gitStatus -join [Environment]::NewLine) | Set-Content -Encoding UTF8 -Path $gitStatusPath + +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 + } +} + +if ($gitStatus) { + Add-Check -Key "working_tree" -Label "working tree" -Status "warn" -Required $false -Detail "Working tree has uncommitted changes; delivery gate is advisory" -Meta @{ artifact = $gitStatusPath } +} else { + Add-Check -Key "working_tree" -Label "working tree" -Status "pass" -Detail "Working tree is clean" -Meta @{ artifact = $gitStatusPath } +} + +if (Test-Path -LiteralPath $DeliveryPackageRoot -PathType Container) { + Add-Check -Key "delivery_package_root" -Label "delivery package root" -Status "pass" -Detail "Delivery package root exists" -Meta @{ root = $DeliveryPackageRoot } +} else { + Add-Check -Key "delivery_package_root" -Label "delivery package root" -Status "fail" -Detail "Delivery package root is missing" -Meta @{ root = $DeliveryPackageRoot } +} + +$requiredPackageFiles = @( + "README.md", + "delivery-manifest.csv", + "deployment-package.md", + "operations-package.md", + "administrator-manual.md", + "business-user-manual.md", + "training-plan.md", + "training-attendance.csv", + "acceptance-checklist.csv", + "handover-signoff-register.csv", + "environment-template.md", + "incident-response-runbook.md" +) + +foreach ($file in $requiredPackageFiles) { + $path = Join-Path $DeliveryPackageRoot $file + $status = if (Test-Path -LiteralPath $path -PathType Leaf) { "pass" } else { "fail" } + $detail = if ($status -eq "pass") { "Package file exists" } else { "Package file missing" } + Add-Check -Key ("package_" + ($file -replace '[^a-zA-Z0-9]', '_')) -Label "package $file" -Status $status -Detail $detail -Meta @{ path = $path } + $packageMatrix.Add([pscustomobject][ordered]@{ + file = $file + status = $status.ToUpperInvariant() + path = $path + }) | Out-Null +} + +$manifestPath = Join-Path $DeliveryPackageRoot "delivery-manifest.csv" +$manifestRows = @() +if (Test-Path -LiteralPath $manifestPath -PathType Leaf) { + $manifestRows = @(Import-Csv -LiteralPath $manifestPath) +} +$manifestColumnsOk = Test-CsvColumns -Rows $manifestRows -Columns @("artifact_id", "artifact_type", "path", "owner", "status", "required", "acceptance_note") +if ($manifestColumnsOk -and $manifestRows.Count -ge 12) { + Add-Check -Key "manifest_columns" -Label "delivery manifest columns" -Status "pass" -Detail "Manifest rows=$($manifestRows.Count)" +} else { + Add-Check -Key "manifest_columns" -Label "delivery manifest columns" -Status "fail" -Detail "Manifest rows or columns are incomplete" +} + +$badManifestRows = @() +$missingManifestPaths = @() +foreach ($row in $manifestRows) { + $isRequired = Convert-Flag -Value ([string]$row.required) + if ($isRequired -and @("READY", "PASS", "COMPLETE", "APPROVED") -notcontains ([string]$row.status)) { + $badManifestRows += $row.artifact_id + } + if ($isRequired) { + $fullPath = Join-Path $repoRoot ([string]$row.path) + if (-not (Test-Path -LiteralPath $fullPath -PathType Leaf) -and -not (Test-Path -LiteralPath $fullPath -PathType Container)) { + $missingManifestPaths += $row.path + } + } +} +if ($badManifestRows.Count -eq 0 -and $missingManifestPaths.Count -eq 0) { + Add-Check -Key "manifest_required_items" -Label "manifest required items" -Status "pass" -Detail "All required manifest items are ready and present" +} else { + Add-Check -Key "manifest_required_items" -Label "manifest required items" -Status "fail" -Detail "Bad status=$($badManifestRows.Count), missing paths=$($missingManifestPaths.Count)" -Meta @{ + bad_status = $badManifestRows + missing_paths = $missingManifestPaths + } +} + +$trainingPath = Join-Path $DeliveryPackageRoot "training-attendance.csv" +$trainingRows = if (Test-Path -LiteralPath $trainingPath -PathType Leaf) { @(Import-Csv -LiteralPath $trainingPath) } else { @() } +$trainingColumnsOk = Test-CsvColumns -Rows $trainingRows -Columns @("role", "participant", "training_topic", "completion_status", "evidence", "open_questions") +$requiredTrainingRoles = @("admin", "business_user", "ops") +$completedTrainingRoles = @($trainingRows | Where-Object { $_.completion_status -eq "COMPLETE" } | Select-Object -ExpandProperty role -Unique) +$missingTrainingRoles = @($requiredTrainingRoles | Where-Object { $completedTrainingRoles -notcontains $_ }) +if ($trainingColumnsOk -and $missingTrainingRoles.Count -eq 0) { + Add-Check -Key "training_completion" -Label "training completion" -Status "pass" -Detail "Admin, business user, and ops training complete" +} else { + Add-Check -Key "training_completion" -Label "training completion" -Status "fail" -Detail "Training records incomplete" -Meta @{ missing_roles = $missingTrainingRoles } +} + +$acceptancePath = Join-Path $DeliveryPackageRoot "acceptance-checklist.csv" +$acceptanceRows = if (Test-Path -LiteralPath $acceptancePath -PathType Leaf) { @(Import-Csv -LiteralPath $acceptancePath) } else { @() } +$acceptanceColumnsOk = Test-CsvColumns -Rows $acceptanceRows -Columns @("item_id", "area", "requirement", "status", "required", "evidence", "owner") +$failedAcceptanceRows = @($acceptanceRows | Where-Object { (Convert-Flag -Value ([string]$_.required)) -and $_.status -ne "PASS" }) +$deferredAcceptanceRows = @($acceptanceRows | Where-Object { $_.status -like "DEFERRED*" }) +if ($acceptanceColumnsOk -and $failedAcceptanceRows.Count -eq 0) { + Add-Check -Key "acceptance_checklist" -Label "acceptance checklist" -Status "pass" -Detail "Required acceptance items pass" +} else { + Add-Check -Key "acceptance_checklist" -Label "acceptance checklist" -Status "fail" -Detail "Required acceptance items failed=$($failedAcceptanceRows.Count)" +} + +$signoffPath = Join-Path $DeliveryPackageRoot "handover-signoff-register.csv" +$signoffRows = if (Test-Path -LiteralPath $signoffPath -PathType Leaf) { @(Import-Csv -LiteralPath $signoffPath) } else { @() } +$signoffColumnsOk = Test-CsvColumns -Rows $signoffRows -Columns @("signoff_id", "role", "owner", "status", "required", "date", "evidence", "notes") +$acceptedSignoffStatuses = @("SIGNED", "AUTO_SIGNED", "APPROVED") +$failedSignoffRows = @($signoffRows | Where-Object { (Convert-Flag -Value ([string]$_.required)) -and ($acceptedSignoffStatuses -notcontains $_.status) }) +$deferredSignoffRows = @($signoffRows | Where-Object { $_.status -like "DEFERRED*" }) +if ($signoffColumnsOk -and $failedSignoffRows.Count -eq 0) { + Add-Check -Key "handover_signoff" -Label "handover signoff" -Status "pass" -Detail "Required signoff rows accepted" +} else { + Add-Check -Key "handover_signoff" -Label "handover signoff" -Status "fail" -Detail "Required signoff rows failed=$($failedSignoffRows.Count)" +} + +$criticalScripts = @( + "deploy-frontend-docker.ps1", + "rollback-frontend-docker.ps1", + "run-production-release-gate.ps1", + "run-production-watchdog.ps1", + "run-production-watchdog-scheduler.ps1", + "run-watchdog-task-audit.ps1", + "run-watchdog-evidence-report.ps1", + "run-watchdog-alert-response-drill.ps1", + "run-backup-drill.ps1", + "run-capacity-governance.ps1", + "run-dependency-governance.ps1", + "run-security-baseline.ps1", + "run-business-quality-gate.ps1", + "run-delivery-acceptance-gate.ps1" +) +$scriptFailures = @() +foreach ($scriptName in $criticalScripts) { + $scriptPath = Join-Path $repoRoot "scripts\$scriptName" + $exists = Test-Path -LiteralPath $scriptPath -PathType Leaf + $parseStatus = "MISSING" + $errorText = "" + if ($exists) { + $tokens = $null + $parseErrors = $null + [System.Management.Automation.Language.Parser]::ParseFile($scriptPath, [ref]$tokens, [ref]$parseErrors) | Out-Null + if ($parseErrors.Count -eq 0) { + $parseStatus = "PASS" + } else { + $parseStatus = "FAIL" + $errorText = (($parseErrors | ForEach-Object { "$($_.Extent.StartLineNumber): $($_.Message)" }) -join "; ") + } + } + if ($parseStatus -ne "PASS") { + $scriptFailures += $scriptName + } + $scriptMatrix.Add([pscustomobject][ordered]@{ + script = $scriptName + exists = $exists + parse_status = $parseStatus + errors = $errorText + }) | Out-Null +} +if ($scriptFailures.Count -eq 0) { + Add-Check -Key "script_runnability" -Label "script runnability" -Status "pass" -Detail "Critical scripts exist and parse" +} else { + Add-Check -Key "script_runnability" -Label "script runnability" -Status "fail" -Detail "Script failures=$($scriptFailures.Count)" -Meta @{ scripts = $scriptFailures } +} + +$phase8Summary = Add-EvidenceCheck -Key "phase8_closed_loop" -Label "phase8 closed loop" -Root $Phase8ArtifactRoot -SummaryFile "phase8-pilot-closed-loop-summary.json" -AllowedStatuses @("PASS", "PASS_WITH_WEBHOOK_BLOCKED") -MaxAgeHours $MaxPhase8AgeHours +$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 = "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 }, + @{ key = "watchdog_task_audit"; label = "watchdog task audit"; root = (Join-Path $repoRoot "project-docs\qa-artifacts\phase7.8-watchdog-task-audit"); summary = "watchdog-task-audit-summary.json"; statuses = @("PASS"); max = $MaxOpsEvidenceAgeHours }, + @{ key = "watchdog_evidence_report"; label = "watchdog evidence report"; root = (Join-Path $repoRoot "project-docs\qa-artifacts\phase7.9-watchdog-evidence-report"); summary = "watchdog-evidence-report-summary.json"; statuses = @("PASS"); max = $MaxOpsEvidenceAgeHours } +) +foreach ($spec in $evidenceSpecs) { + Add-EvidenceCheck -Key $spec.key -Label $spec.label -Root $spec.root -SummaryFile $spec.summary -AllowedStatuses $spec.statuses -MaxAgeHours $spec.max | Out-Null +} + +New-Item -ItemType Directory -Force -Path $rehearsalDir | Out-Null +$snapshotPackage = Join-Path $rehearsalDir "delivery-package" +$snapshotScripts = Join-Path $rehearsalDir "scripts" +New-Item -ItemType Directory -Force -Path $snapshotPackage | Out-Null +New-Item -ItemType Directory -Force -Path $snapshotScripts | Out-Null +Get-ChildItem -LiteralPath $DeliveryPackageRoot | ForEach-Object { + Copy-Item -LiteralPath $_.FullName -Destination $snapshotPackage -Recurse -Force +} +foreach ($scriptName in $criticalScripts) { + $scriptPath = Join-Path $repoRoot "scripts\$scriptName" + if (Test-Path -LiteralPath $scriptPath -PathType Leaf) { + Copy-Item -LiteralPath $scriptPath -Destination $snapshotScripts -Force + } +} +$snapshotPackageCount = @(Get-ChildItem -LiteralPath $snapshotPackage -File -Recurse).Count +$snapshotScriptCount = @(Get-ChildItem -LiteralPath $snapshotScripts -File).Count +if ($snapshotPackageCount -ge $requiredPackageFiles.Count -and $snapshotScriptCount -ge $criticalScripts.Count) { + Add-Check -Key "isolated_delivery_rehearsal" -Label "isolated delivery rehearsal" -Status "pass" -Detail "Package and scripts copied to isolated rehearsal directory" -Meta @{ + rehearsal_dir = $rehearsalDir + package_files = $snapshotPackageCount + scripts = $snapshotScriptCount + } +} else { + Add-Check -Key "isolated_delivery_rehearsal" -Label "isolated delivery rehearsal" -Status "fail" -Detail "Isolated rehearsal snapshot is incomplete" -Meta @{ + rehearsal_dir = $rehearsalDir + package_files = $snapshotPackageCount + scripts = $snapshotScriptCount + } +} + +$secret = [string]::Concat([char]54, [char]53, [char]52, [char]51, [char]50, [char]49) +$secretHits = @() +$scanFiles = @(Get-ChildItem -LiteralPath $DeliveryPackageRoot -File -Recurse) +$scanFiles += @(Join-Path $repoRoot "scripts\run-delivery-acceptance-gate.ps1" | Get-Item) +foreach ($file in $scanFiles) { + $text = Get-Content -LiteralPath $file.FullName -Raw -Encoding UTF8 -ErrorAction SilentlyContinue + if ($text -and $text.Contains($secret)) { + $secretHits += $file.FullName + } +} +if ($secretHits.Count -eq 0) { + Add-Check -Key "secret_scan" -Label "secret scan" -Status "pass" -Detail "No administrator password literal found in delivery package or gate script" +} else { + Add-Check -Key "secret_scan" -Label "secret scan" -Status "fail" -Detail "Secret literal detected" -Meta @{ hits = $secretHits } +} + +$externalBlocked = $false +if (($phase8Summary -and $phase8Summary.status -eq "PASS_WITH_WEBHOOK_BLOCKED") -or ($phase9Summary -and $phase9Summary.status -eq "PASS_WITH_WEBHOOK_BLOCKED") -or $deferredAcceptanceRows.Count -gt 0 -or $deferredSignoffRows.Count -gt 0) { + $externalBlocked = $true + Add-Check -Key "external_webhook" -Label "external alert webhook" -Status "blocked" -Required $false -Detail "Real external alert Webhook remains unavailable; inherited as external blocked item" +} + +$scriptMatrix | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $scriptMatrixCsv +$evidenceMatrix | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $evidenceMatrixCsv +$packageMatrix | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $packageMatrixCsv + +$failedRequired = @($checks | Where-Object { $_.required -and $_.status -eq "fail" }) +$warningsList = @($checks | Where-Object { $_.status -eq "warn" }) +$blockedList = @($checks | Where-Object { $_.status -eq "blocked" }) +$overallStatus = if ($failedRequired.Count -gt 0) { + "FAIL" +} elseif ($blockedList.Count -gt 0 -or $externalBlocked) { + "PASS_WITH_WEBHOOK_BLOCKED" +} elseif ($warningsList.Count -gt 0) { + "WARN" +} else { + "PASS" +} +$alertSeverity = if ($overallStatus -eq "FAIL") { "critical" } elseif ($overallStatus -eq "WARN") { "warning" } elseif ($overallStatus -eq "PASS_WITH_WEBHOOK_BLOCKED") { "blocked" } else { "ok" } + +$summary = [ordered]@{ + name = "phase10-delivery-acceptance" + status = $overallStatus + alert_severity = $alertSeverity + generated_at = (Get-Date).ToUniversalTime().ToString("o") + artifact_dir = $artifactDir + branch = $branch + commit = $commit + remote = $remoteUrl + delivery_package_root = $DeliveryPackageRoot + checks = $checks + artifacts = [ordered]@{ + package_matrix = $packageMatrixCsv + script_matrix = $scriptMatrixCsv + evidence_matrix = $evidenceMatrixCsv + isolated_rehearsal = $rehearsalDir + } + totals = [ordered]@{ + total = $checks.Count + failed_required = $failedRequired.Count + warnings = $warningsList.Count + blocked = $blockedList.Count + } +} +Save-Json -Path $summaryJson -Value $summary + +$markdown = New-Object System.Collections.Generic.List[string] +$markdown.Add("# Phase 10 Delivery Acceptance Gate") | Out-Null +$markdown.Add("") | Out-Null +$markdown.Add("- Status: $overallStatus") | Out-Null +$markdown.Add("- Alert severity: $alertSeverity") | 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("- Delivery package root: $DeliveryPackageRoot") | Out-Null +$markdown.Add("") | Out-Null +$markdown.Add("## Checks") | 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('- `delivery-acceptance-summary.json`') | Out-Null +$markdown.Add('- `delivery-acceptance-summary.md`') | Out-Null +$markdown.Add('- `phase10-package-matrix.csv`') | Out-Null +$markdown.Add('- `phase10-script-runnability.csv`') | Out-Null +$markdown.Add('- `phase10-evidence-matrix.csv`') | Out-Null +$markdown.Add('- `isolated-delivery-rehearsal/`') | Out-Null +$markdown | Set-Content -Encoding UTF8 -Path $summaryMd + +Write-Host "PHASE10_DELIVERY_ACCEPTANCE_$overallStatus $artifactDir" +if ($overallStatus -eq "FAIL" -or ($FailOnWarn -and $warningsList.Count -gt 0) -or ($FailOnExternalBlocked -and $blockedList.Count -gt 0)) { + throw "Phase 10 delivery acceptance gate $overallStatus. See $summaryMd" +}