docs: add phase 10 delivery acceptance gate
This commit is contained in:
@@ -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`.
|
||||
@@ -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
|
||||
|
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
@@ -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.
|
||||
@@ -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=<runtime only>
|
||||
AI_CONTRACT_WATCHDOG_WEBHOOK_URL=<runtime only, currently blocked until provided>
|
||||
```
|
||||
|
||||
## 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.
|
||||
@@ -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
|
||||
|
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
@@ -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.
|
||||
@@ -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/<timestamp>/`
|
||||
|
||||
---
|
||||
|
||||
## 三、验收范围
|
||||
|
||||
### 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 的交付包作为规模化运营输入。
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user