docs(08-08-production-deployment-validation): 补齐部署验证规范与证据边界
这个提交包含在:
@@ -1442,7 +1442,7 @@ bash scripts/qa/run-automatic-backup-contract.sh
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- `bash -n deploy/trialrun/deploy.sh deploy/trialrun/deploy-existing.sh deploy/trialrun/preflight-remote.sh`.
|
||||
- `bash -n deploy/trialrun/deploy.sh deploy/trialrun/deploy-existing.sh deploy/trialrun/preflight-remote.sh deploy/trialrun/production-preflight-remote.sh`.
|
||||
- `bash scripts/qa/run-deployment-contract-refresh-contract.sh` must prove multi-failure aggregation reaches the final item, output redaction, forced-command exact matching, four failure categories, four build drop-ins and four timer checks.
|
||||
- `node deploy/trialrun/validate-env-contract.js`.
|
||||
- `cd server && npm test -- tests/unit/scripts/deployment-safety-contract.test.js`.
|
||||
@@ -1455,8 +1455,8 @@ bash scripts/qa/run-automatic-backup-contract.sh
|
||||
|
||||
```bash
|
||||
# working_dir may be the Compose config directory, not the repository root.
|
||||
REMOTE_DIR="$(docker inspect <server> --format '<working_dir-label>')" \
|
||||
bash deploy/trialrun/deploy.sh
|
||||
REMOTE_DIR="$(docker inspect <server> --format '<working_dir-label>')" \
|
||||
bash deploy/trialrun/deploy.sh
|
||||
```
|
||||
|
||||
#### Correct
|
||||
@@ -1472,6 +1472,86 @@ DEPLOY_SERVER=<approved-ssh-target> \
|
||||
# recovery runbook to prove one complete backup and off-host freshness.
|
||||
```
|
||||
|
||||
## Scenario: Production Migration-First Target Gate
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: an approved existing-host release whose candidate commit contains a
|
||||
Prisma migration, especially the contract `terminated` workspace migration.
|
||||
- Purpose: prove the remote target identity and migration history before any
|
||||
schema write, then preserve the state-based rollback boundary.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Wrapper: `deploy/trialrun/deploy-existing.sh`.
|
||||
- Local orchestrator: `deploy/trialrun/production-preflight.mjs`.
|
||||
- Remote read-only helper: `deploy/trialrun/production-preflight-remote.sh`.
|
||||
- Required process inputs: `EXPECTED_PRODUCTION_ORIGIN`,
|
||||
`EXPECTED_PRODUCTION_DB_NAME`, and `EXPECTED_PENDING_MIGRATIONS`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Production R2 is the inverse of the local migration task: the gate must prove
|
||||
the target is the expected production HTTPS origin and database identity, and
|
||||
reject loopback, local regression, staging and trialrun identities. It must
|
||||
never infer production from an SSH alias, Compose label or directory name.
|
||||
- The strict production-validation wrapper rejects unpublished-commit overrides;
|
||||
disaster-recovery exceptions use a separately approved recovery flow.
|
||||
- Expected target values remain process-only. The local orchestrator sends only
|
||||
one-way digests to the remote helper and records stable codes, not values,
|
||||
credentials, paths or raw environment content.
|
||||
- The candidate manifest is generated from the verified commit. Before
|
||||
`deploy.sh`, the remote helper confirms the runtime `DATABASE_URL` host and
|
||||
database identity, then runs `prisma migrate status`. Raw stdout/stderr stays
|
||||
in the controlled execution environment; the local report contains only a
|
||||
redacted status copy. The helper compares `_prisma_migrations` with the
|
||||
candidate manifest. Pending must exactly equal the explicit allowlist;
|
||||
pending IDs reported by status must belong to the candidate manifest;
|
||||
failed, rolled-back, unfinished, diverged or unknown history fails closed.
|
||||
- The preflight helper is read-only: it does not run `migrate dev`, reset,
|
||||
`db push`, `migrate resolve`, seed, DDL or business writes. `deploy.sh` is
|
||||
reached only after target and pre-status gates pass.
|
||||
- The post-deploy status is a separate evidence item and must be manually read;
|
||||
the wrapper re-runs the read-only helper with an empty pending allowlist
|
||||
before backup-contract refresh, and a successful exit code alone is
|
||||
insufficient.
|
||||
- Each preflight run removes only its two known report files before SSH; a
|
||||
reused report directory must never leave an old PASS status artifact in place.
|
||||
- When the strict wrapper invokes `deploy.sh`, it sets
|
||||
`DEPLOY_STATUS_OUTPUT_MODE=quiet`; the legacy status check must not echo raw
|
||||
datasource output. Detailed evidence comes from the controlled pre/post
|
||||
helper artifacts.
|
||||
- `terminated` count defines a state window: zero rows means `OPEN`; the first
|
||||
committed row closes it. Once closed, an old application that does not know
|
||||
`terminated` cannot be restored and the new columns are never auto-dropped;
|
||||
recovery is roll-forward or a compatible hotfix.
|
||||
- The target identity must remain unchanged between the read-only preflight and
|
||||
the schema write; if a controlled change freeze cannot be established, stop
|
||||
before `deploy.sh` rather than relying on post-status to repair a wrong-target
|
||||
write.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Missing or invalid expected target input -> stop before SSH mutation.
|
||||
- Target hash, runtime mode, cookie security or DB identity mismatch -> stop
|
||||
before `deploy.sh`.
|
||||
- Status output missing/unreadable, failed/rolled-back/diverged history or an
|
||||
unknown pending migration -> `PAUSED`; do not reset or resolve in place.
|
||||
- Pending set differs from the approved allowlist -> `PAUSED` and retain the
|
||||
status artifact for human review.
|
||||
- `terminated` count greater than zero -> record `rollbackWindow=CLOSED` and
|
||||
disallow compatibility rollback; this task never creates such a row.
|
||||
|
||||
### 5. Tests Required
|
||||
|
||||
- `node --check deploy/trialrun/production-preflight.mjs`.
|
||||
- `bash -n deploy/trialrun/production-preflight-remote.sh`.
|
||||
- `node deploy/trialrun/validate-env-contract.js`.
|
||||
- `cd server && npm test -- tests/unit/scripts/deployment-safety-contract.test.js`.
|
||||
- Static ordering must prove production preflight precedes `deploy.sh`, and the
|
||||
remote status marker precedes migration-history reconciliation. Fixtures must
|
||||
reject loopback/staging target identities without invoking SSH.
|
||||
|
||||
## Scenario: Environment Recovery Classification And Secret Custody
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,106 @@
|
||||
# 生产部署验证与迁移门禁:设计说明
|
||||
|
||||
## 1. 边界与目标
|
||||
|
||||
本任务是一次受控生产发布与验证合同,不改变合同业务实现。部署 tooling 只在必要时增加生产 target/migration preflight 及静态测试;应用代码、生产 `.env`、真实业务数据和企业微信不在本任务改动范围。
|
||||
|
||||
核心状态序列:
|
||||
|
||||
```text
|
||||
candidate/source
|
||||
-> production target identity (read-only)
|
||||
-> existing deployment aggregate preflight (read-only)
|
||||
-> candidate migration manifest + pre-status (read-only)
|
||||
-> terminated row count / rollback window snapshot (read-only)
|
||||
-> approved deploy-existing (schema/app write)
|
||||
-> post-status applied (read-only)
|
||||
-> /ready + build + smoke + permission-negative (read-only)
|
||||
-> closeout / PAUSED classification
|
||||
```
|
||||
|
||||
任何箭头左侧门禁失败都不得进入右侧动作。尤其 `migrate deploy` 之前不得 seed、切换应用或执行业务写入。
|
||||
|
||||
## 2. 生产目标识别
|
||||
|
||||
### 2.1 输入
|
||||
|
||||
- `EXPECTED_PRODUCTION_ORIGIN`:执行时提供的完整 HTTPS origin。
|
||||
- `EXPECTED_PRODUCTION_DB_NAME`:执行时提供的预期生产数据库名;仅用于比较,不输出。
|
||||
- Existing deployment contract:由 `deployment-contract-lib.sh` 从受控 systemd unit 读取六个 allowlist 参数。
|
||||
- 本任务 wrapper 不接受未发布提交 override;灾备/远端故障恢复保持为另一条需单独批准的流程。
|
||||
|
||||
### 2.2 只读判据
|
||||
|
||||
远端 helper 读取 `.env` 的键名和值只在内存中比较:
|
||||
|
||||
- `NODE_ENV=production`、`COOKIE_SECURE=true`。
|
||||
- `APP_ORIGIN` 与 `EXPECTED_PRODUCTION_ORIGIN` 完全一致,scheme 为 HTTPS,host 不是 `localhost`、`127.0.0.1`、`::1`,也不是示例/测试/暂存 origin。
|
||||
- `POSTGRES_DB` 与 `EXPECTED_PRODUCTION_DB_NAME` 一致;数据库 URL 的 database 部分一致,host 不得是回环地址或本地回归库标识。
|
||||
- 生产目标的 compose/path 名称不能单独作为身份依据;`trialrun` 只描述部署脚本族,不能被误当作生产身份或排除条件。生产/非生产判定以显式 origin + DB identity 为准。
|
||||
|
||||
失败只输出稳定 code,例如 `PROD_TARGET_ORIGIN_MISMATCH`、`PROD_TARGET_DB_MISMATCH`,不输出实际值。
|
||||
|
||||
## 3. 迁移 preflight 设计
|
||||
|
||||
### 3.1 Candidate manifest
|
||||
|
||||
从候选 commit 的 `server/prisma/migrations/*` 生成排序后的 migration ID 清单。ID 只允许 Prisma migration 目录安全字符。`EXPECTED_PENDING_MIGRATIONS` 是执行时的显式 allowlist;本轮预期是合同 termination migration,若已提前应用则 pending 为空。
|
||||
|
||||
### 3.2 Production read-only comparison
|
||||
|
||||
在当前生产 Compose 数据库容器上只读查询 `_prisma_migrations`:
|
||||
|
||||
- 已应用集合:`finished_at IS NOT NULL AND rolled_back_at IS NULL`。
|
||||
- 异常集合:`rolled_back_at IS NOT NULL`、失败日志/未完成行或 applied ID 不在 candidate manifest。
|
||||
- pending 集合:candidate manifest − applied 集合。
|
||||
- 输出仅为计数、migration ID 和状态,不输出 `DATABASE_URL`、密码或原始日志。
|
||||
|
||||
同时运行 `npx prisma migrate status`。原始 stdout/stderr 只保留在受控执行现场;任务 artifacts 写入去除 datasource、数据库 URL、目标 origin/DB 值的脱敏副本,人工核对副本是否与集合比较一致。退出码不能替代原始输出。
|
||||
|
||||
### 3.3 Gate rules
|
||||
|
||||
- 异常集合非空 -> `PAUSED`。
|
||||
- pending 集合不是 `EXPECTED_PENDING_MIGRATIONS` 的精确集合 -> `PAUSED`。
|
||||
- candidate manifest 无法读取、status 输出无法人工解释或数据库连接目标不确定 -> `PAUSED`。
|
||||
- 只读查询不得调用 `migrate deploy`、seed、reset、resolve、db push 或手工 SQL DDL。
|
||||
|
||||
## 4. 回滚窗口
|
||||
|
||||
预部署只读查询:
|
||||
|
||||
```sql
|
||||
SELECT count(*) FROM contract_registry WHERE status = 'terminated';
|
||||
```
|
||||
|
||||
- count = 0:`rollbackWindow=OPEN`;部署失败可在不 drop schema 的前提下回退到兼容应用。
|
||||
- count > 0:`rollbackWindow=CLOSED`;旧应用回退被禁止,只允许 roll-forward/兼容 hotfix。
|
||||
- 窗口关闭事件是第一条已提交 `terminated` 记录产生的瞬间,而非发布时间、迁移时间或人工计时。
|
||||
- 本任务不执行终止写入,因此不会主动把窗口从 OPEN 变为 CLOSED。
|
||||
|
||||
## 5. 发布与只读验收
|
||||
|
||||
- 只用 `deploy-existing.sh`,固定 `SYNC_ENV=0` 与 `DEPLOY_DATA_MODE=existing`。
|
||||
- wrapper 调用既有 `deploy.sh` 时设置 `DEPLOY_STATUS_OUTPUT_MODE=quiet`,避免其历史 status 检查把 datasource 原文打到部署终端;详细 pre/post 证据由受控 helper 生成。
|
||||
- 部署脚本内部 `migrate deploy` 是唯一允许的 schema 写入;其后必须在受控现场保留 post-status 原始输出,并生成脱敏证据副本,确认全部 applied。
|
||||
- `/ready` build SHA、容器 build、公开短 SHA和候选 commit 一致。
|
||||
- 基础 smoke、external deps、runtime smoke、公开资源扫描及权限负向路径均只读;登录态/企微/真实 OCR 缺少批准凭据时记录 `PAUSED/SKIP`。
|
||||
- 权限负向路径(未登录、不具备合同管理权限、详情不存在/无权)必须排在任何未来真实终止写入之前;当前任务没有该写入。
|
||||
|
||||
## 6. 失败分流与证据
|
||||
|
||||
| 阶段 | 失败处理 | 是否可重复部署 |
|
||||
| --- | --- | --- |
|
||||
| target/preflight/status | 停止,不同步/不写 DB | 修正输入后重新预检 |
|
||||
| migrate deploy/init | 保留现场,禁止 reset/resolve/drop;按批准决定兼容回退或 roll-forward | 未确认现场前不可重复 |
|
||||
| `/ready`/runtime smoke | 记录应用失败;窗口 OPEN 时按既有发布合同回退,否则 roll-forward | 需复核窗口状态 |
|
||||
| backup refresh | 标记 `DEPLOYED_BUT_CONTRACT_REFRESH_FAILED`,不重复发布应用 | 只修复合同 |
|
||||
| public/live/permission negative | 不宣称可测,记录 PAUSED/FAIL | 不自动重跑业务写入 |
|
||||
|
||||
Preflight 与 `deploy.sh` 之间要求远端配置/数据库身份变更冻结;若无法保证该窗口不可变,停止在 preflight 阶段。post-status 只能确认结果,不能补救并发期间写入错误目标的风险。
|
||||
|
||||
## 7. 证据与隐私
|
||||
|
||||
- 任务只保存 commit、状态码、计数、migration ID、报告引用和脱敏原因。
|
||||
- 每次 pre/post 运行开始先清理报告目录中两个已知 evidence 文件,避免重试复用目录时旧 PASS 证据残留;不做目录级递归删除。
|
||||
- 不保存 SSH target、用户名、绝对远端路径、数据库 URL、凭据、业务合同正文、终止原因或真实响应 payload。
|
||||
- 生产 target gate 和 pre/post status 的原始输出若包含环境敏感内容,先做脱敏副本;原始日志留在受控执行环境,不进 Git。
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,61 @@
|
||||
# 生产部署验证与迁移门禁:实施计划
|
||||
|
||||
## 1. 规划与激活
|
||||
|
||||
- [x] 用户已批准本最终规划摘要,已执行 `task.py start`;未连接生产或执行生产 migration。
|
||||
- [ ] 确认候选 commit、上游包含关系和工作区 clean;记录本次发布候选。
|
||||
- [ ] 确认执行时的 `EXPECTED_PRODUCTION_ORIGIN`、`EXPECTED_PRODUCTION_DB_NAME`、`EXPECTED_PENDING_MIGRATIONS` 来自受控渠道,值不写入任务文件。
|
||||
|
||||
## 2. 部署 tooling 加固(仅必要时)
|
||||
|
||||
- [x] 新增只读 production target/migration preflight,由 `deploy-existing.sh` 在 `deploy.sh` 前调用。
|
||||
- [x] preflight 验证生产 origin/DB identity(含运行时 `DATABASE_URL` host/DB)、candidate migration manifest、`_prisma_migrations` 集合和可解释的 `prisma migrate status` 输出;13 项既有部署体检仍由 wrapper 先执行。
|
||||
- [x] 明确保证 pre-status 在 `migrate deploy`、seed、应用切换和业务写入之前;未知 pending、failed、rolled-back、diverged 或目标不明均停止。
|
||||
- [x] 增加部署安全静态/fixture 测试,证明 target gate 不接受回环/本地目标,status 空/未知文案、日志失败和未知 pending fail-closed,报告不落原始 datasource,且 preflight 顺序早于 `deploy.sh`。
|
||||
- [x] 更新 deployment spec/runbook 记录生产目标判据与首条 `terminated` 回滚窗口语义;不记录真实环境值。
|
||||
- [x] 在应用部署成功、备份合同 refresh 之前,以空 pending allowlist 复用只读 preflight,生成独立 post-status evidence。
|
||||
- [x] 严格 wrapper 调用既有 `deploy.sh` 时启用 `DEPLOY_STATUS_OUTPUT_MODE=quiet`,避免历史 status 检查把 datasource 原文回显;详情由受控 pre/post helper evidence 提供。
|
||||
|
||||
## 3. 本地门禁
|
||||
|
||||
- [x] `node deploy/trialrun/validate-env-contract.js`
|
||||
- [x] `bash -n` 检查新增/修改的部署 shell(含 `production-preflight-remote.sh`)
|
||||
- [x] 聚焦 deployment safety tests
|
||||
- [x] `python3 ./.trellis/scripts/task.py validate 08-08-production-deployment-validation`
|
||||
- [x] `node scripts/qa/check-process-contract.mjs --baseline --json`
|
||||
- [x] `node scripts/qa/audit-docs.mjs --check-registry`(0 errors;保留仓库既有 1 条 unregistered docs warning)
|
||||
- [x] `node scripts/qa/check-trellis-governance.mjs --json`
|
||||
- [x] `node scripts/qa/check-sensitive-content.mjs`
|
||||
- [x] `git diff --check`
|
||||
|
||||
## 4. 生产执行(需明确批准)
|
||||
|
||||
- [ ] 先运行 target gate 和 `check-existing-deployment.sh`;只保存脱敏 PASS/FAIL。(待单独生产批准)
|
||||
- [ ] 先运行 candidate migration pre-status,确认运行时 `DATABASE_URL` 身份后在受控现场人工阅读原始输出,并核对脱敏副本、pending allowlist、异常集合和 `terminated` count;任一不满足即 `PAUSED`。
|
||||
- [ ] 只有第 4.2 全部 PASS 后,才调用 `DEPLOY_SERVER=<...> DEPLOY_REFRESH_SERVER=<...> bash deploy/trialrun/deploy-existing.sh`,保持 `SYNC_ENV=0`/existing 数据合同。(待单独生产批准)
|
||||
- [ ] 在受控现场保留 post-status 原始输出并保存脱敏副本,确认全部 migration applied/schema up to date;不能只看退出码。
|
||||
- [ ] 验证 `/ready`、build SHA、公开 bundle、基础 smoke、external/runtime smoke 和权限负向路径;这些全部排在任何真实终止写入之前。
|
||||
- [ ] 本任务不执行真实 `terminated` 写入、合同编辑/归档/恢复或任何付款/审批/企微写入。
|
||||
|
||||
## 5. 回滚与收口
|
||||
|
||||
- [ ] 记录 `rollbackWindow=OPEN|CLOSED` 及判据;第一条已提交 `terminated` 产生即关闭窗口。
|
||||
- [ ] 应用/迁移失败按 design 分流,禁止 reset、resolve、drop;窗口 CLOSED 时只 roll-forward。
|
||||
- [ ] 记录发布 commit、pre/post status、target gate、`/ready`、smoke、权限负向、PAUSED/SKIP 和业务写入计数。
|
||||
- [ ] 只有部署、公网和获批真机检查全部通过时,才允许在最终答复中告知用户可以测试;否则明确 PAUSED。
|
||||
- [ ] 归档任务并记录会话日志;生产部署不与本任务之外的功能开发混做。
|
||||
|
||||
## Rollback Points
|
||||
|
||||
- target/preflight/status 失败:尚未同步/写库,修正输入后重新预检。
|
||||
- `migrate deploy`/init 中途失败:保留现场,禁止就地 reset/resolve/drop,等待确认。
|
||||
- `/ready` 或 smoke 失败:窗口 OPEN 时按既有发布合同回退兼容应用;窗口 CLOSED 时 roll-forward。
|
||||
- backup refresh 失败:标记已部署但合同刷新失败,不重复发布应用。
|
||||
|
||||
## Evidence Notes
|
||||
|
||||
- 规划阶段未执行生产 SSH、migration、部署、真实业务写入或 live smoke。
|
||||
- 本地验证:`node --check deploy/trialrun/production-preflight.mjs`、相关部署 shell `bash -n`、`node deploy/trialrun/validate-env-contract.js` 均 PASS。
|
||||
- 聚焦 Vitest:`server/tests/unit/scripts/deployment-safety-contract.test.js` 13 tests passed;包含受控 SSH 脱敏证据 fixture 和 synthetic Docker remote gate happy path。
|
||||
- 任务/process/docs/governance/sensitive/diff 门禁均已执行并通过;`audit-docs` 仅报告仓库既有 unregistered docs warning,不属于本任务新增。
|
||||
- 生产执行段全部保持未勾选:没有生产 SSH、`migrate deploy`、真实 `terminated` 写入、业务数据写入、公网 smoke 或真机验证;因此不得据此告知用户“可以测试”。
|
||||
@@ -0,0 +1,102 @@
|
||||
# 生产部署验证与迁移门禁
|
||||
|
||||
## Goal
|
||||
|
||||
在不写入真实业务数据的前提下,将包含合同 `terminated` 三列迁移的当前发布提交安全部署到预期生产环境,并用可追溯、脱敏的证据证明生产迁移状态、运行版本、只读权限边界和回滚窗口边界。
|
||||
|
||||
## Background and Confirmed Facts
|
||||
|
||||
- 当前既有生产发布入口是 `deploy/trialrun/deploy-existing.sh`;它会先调用 `check-existing-deployment.sh` 的 13 项聚合体检,再调用 `deploy.sh`,默认 `SYNC_ENV=0`。
|
||||
- 当前 `deploy.sh` 在远端先执行 `prisma migrate deploy`(4.4),之后才执行 `prisma migrate status`(4.6)。这只能证明写入后的状态,不能作为本轮要求的部署前迁移门禁。
|
||||
- 当前发布提交包含合同迁移 `20260807193000_contract_registry_termination_workspace`:三个可空列、`ON DELETE RESTRICT` 外键和索引;本轮不应执行任何真实 `terminated` 业务写入。
|
||||
- 既有部署合同从受控 systemd unit 读取六个 allowlist 参数,13 项聚合体检覆盖部署身份、仓库、环境文件、数据目录、运行实例、挂载、build 和 refresh 身份;不得从 Compose 标签猜路径或选择 SSH 身份。
|
||||
- 合同设计规定:生产出现第一条已提交 `terminated` 记录的瞬间关闭应用回滚窗口;此后不得回退到不识别该状态的旧应用,也不自动 drop 三列,只能 roll-forward。
|
||||
- 本任务当前尚未执行生产部署、生产 migration、真实合同写入、企业微信写入或付款/审批操作。
|
||||
|
||||
## Scope
|
||||
|
||||
- 为既有生产部署入口增加/启用生产目标识别与迁移 preflight,使 `migrate status` 成为任何 schema 写入前的硬门禁。
|
||||
- 在获批窗口内部署包含合同 `terminated` 三列迁移的明确候选 commit,并完成迁移、运行版本、只读权限和公网 readiness 验证。
|
||||
- 记录首条 `terminated` 记录前后的回滚窗口判据、脱敏证据和失败分流;不主动制造任何真实终止记录。
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1 任务来源与部署身份
|
||||
|
||||
- 发布候选必须是干净、已提交且已进入上游的明确 commit;来源校验和 13 项既有部署体检通过后才可继续。
|
||||
- 本任务的 `deploy-existing.sh` 拒绝 `DEPLOY_ALLOW_UNPUBLISHED_COMMIT`/`DEPLOY_UNPUBLISHED_REASON` override;离线灾备或远端故障的未发布提交必须走独立、另行批准的恢复流程。
|
||||
- 生产目标必须通过独立 target gate:执行时显式提供 `EXPECTED_PRODUCTION_ORIGIN` 与 `EXPECTED_PRODUCTION_DB_NAME`(仅进程环境,不写入仓库、日志或报告),与远端 `.env` 的 `APP_ORIGIN`、数据库身份逐项比对。
|
||||
- target gate 必须确认 `NODE_ENV=production`、`COOKIE_SECURE=true`、公网 origin 为 HTTPS 且与预期值一致;拒绝回环地址、本地数据库(含 `hyb_oa_regression`)、占位符或 staging/trialrun 身份标识。不要把本地任务中“必须是回环地址”的 R2 复用于生产。
|
||||
- 目标、账号、路径、数据库名和环境值不得回显;只记录 PASS/FAIL 及脱敏原因。
|
||||
|
||||
### R2 部署前迁移只读门禁
|
||||
|
||||
- 在任何 `migrate deploy`、seed、应用切换或真实业务写入之前,针对已确认身份的预期生产数据库执行只读 migration status 检查;原始 stdout/stderr 只留在受控执行现场,任务 artifacts 仅保存脱敏副本供人工核对。
|
||||
- 预检必须明确区分 `applied`、pending、failed、rolled back、diverged;失败/回滚/分叉或无法人工解释的输出立即 `PAUSED`。
|
||||
- 候选迁移目录与生产 `_prisma_migrations` 的已应用集合必须可对账;pending 集合只能是执行时显式批准的 allowlist(本轮预期最多为合同迁移),出现未知 pending 必须停止。
|
||||
- 禁止 `prisma migrate dev`、`migrate reset`、`db push`、`migrate resolve`、手工 DDL、修改 `_prisma_migrations` 或以退出码代替原始输出核对。
|
||||
|
||||
### R3 受控发布
|
||||
|
||||
- 仅使用既有 `deploy-existing.sh`,保持 `SYNC_ENV=0`、`DEPLOY_DATA_MODE=existing` 和既有 PGDATA/附件目录;不得覆盖生产 `.env`。
|
||||
- 预检通过后才允许部署脚本执行已批准的 `migrate deploy`、构建、服务切换、运行时 smoke 和备份合同 refresh。
|
||||
- 本任务不创建、编辑、终止、归档或恢复真实合同,不写报销、付款、审批、银行、ERP 或企业微信数据。
|
||||
|
||||
### R4 发布后迁移与运行验证
|
||||
|
||||
- 部署后再次保存并人工核对 `migrate status` 的脱敏证据副本,必须显示所有候选迁移已 `applied`/数据库 schema up to date;原始 stdout/stderr 仍只留在受控执行现场,不能只看命令退出码。
|
||||
- `/ready`、运行时完整 build SHA、公开短 SHA、基础 smoke、external deps、runtime smoke 和公开 bundle 扫描必须与发布 commit 一致。
|
||||
- 所有权限负向路径、详情只读检查和迁移状态复核必须排在任何真实 `terminated` 写入之前;本任务不执行真实终止写入。缺少专用凭据时记录 `PAUSED/SKIP`,不伪装成通过。
|
||||
|
||||
### R5 回滚窗口与结果证据
|
||||
|
||||
- 部署前只读查询 `terminated` 行数量并记录窗口状态:数量为 0 记为 `OPEN`,大于 0 记为 `CLOSED`;窗口按首条已提交记录的产生时刻关闭,不按时间计算。
|
||||
- 在窗口 `OPEN` 时,应用健康失败可按既有发布合同回退到兼容版本,但不 drop 新列;窗口一旦 `CLOSED`,任何失败只允许 roll-forward 或兼容 hotfix,不回退到旧状态机。
|
||||
- 结果记录发布 commit、target gate、pre/post migration 原始证据引用、`/ready`/smoke、权限负向路径、窗口状态、可解释 SKIP/PAUSED 和是否发生真实写入;不记录凭据、原始环境或业务正文。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] AC1:候选 commit 干净、已进入上游,来源校验与 13 项部署体检通过。
|
||||
- [ ] AC2:生产 target gate 证明目标是预期生产 origin/数据库,明确排除本地、回环和 staging/trialrun 身份,且没有泄露环境值。
|
||||
- [ ] AC3:任何 schema 写入前完成 pre-migration status;受控现场原始输出与 artifacts 脱敏副本均可核对,failed/rolled-back/diverged 为 0,`_prisma_migrations` 的 pending 集合与批准 allowlist 完全一致,status 文案中出现的 pending ID 不得超出候选 manifest。
|
||||
- [ ] AC4:`deploy-existing.sh` 仅在 AC1–AC3 通过后执行,保持 `SYNC_ENV=0`/existing 数据合同,未执行 reset、db push、resolve、手工 DDL 或真实业务写入。
|
||||
- [ ] AC5:部署后 migration status 脱敏证据副本显示全部候选迁移 applied/schema up to date;`/ready`、build SHA、运行时 smoke、external deps 和公开 bundle 与候选 commit 一致。
|
||||
- [ ] AC6:权限负向路径和只读合同详情检查在任何真实终止写入前完成;本任务无真实 `terminated` 写入,缺凭据项明确记录 PAUSED/SKIP。
|
||||
- [ ] AC7:以首条已提交 `terminated` 记录定义回滚窗口 OPEN/CLOSED,并记录兼容回滚或 roll-forward 判据;不自动 drop 三列。
|
||||
- [ ] AC8:任务证据、Trellis/process/docs/sensitive/diff 门禁通过;只有部署、公网和必要真机检查全部获批并通过后,才可告知用户可以测试。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 真实 `terminated` 写入、合同业务编辑/归档/恢复、报销/付款/审批/银行/ERP/企业微信写入。
|
||||
- 生产数据库 reset、db push、resolve、手工 DDL、删除或修改 `_prisma_migrations`。
|
||||
- 修改合同业务代码、权限模型、状态机或生产环境变量;如部署 preflight 需要工具加固,只改部署/QA 脚本与其测试。
|
||||
- 以本地或试运行数据库代替预期生产目标;以临时 runner、删除断言或未授权凭据冒充公网通过。
|
||||
|
||||
## Accepted Risk and Deferred Items
|
||||
|
||||
- 登录态 smoke、企业微信真机和真实 OCR 样本可能因专用凭据/样本缺失而 `PAUSED/SKIP`;这不等同于生产部署或业务功能通过。
|
||||
- 首条 `terminated` 写入后的旧版本回退风险由产品与合同 task 已接受;本任务只验证窗口判据,不主动关闭窗口。
|
||||
- preflight 到 `deploy.sh` 的受控窗口内,远端 `.env`、Compose project 和数据库连接目标必须保持不变;并发改动无法由 post-status 追溯性补救。若无法取得该变更冻结,任务应在写入前 `PAUSED`,不得把 post gate 当作事后纠错。
|
||||
|
||||
## Operational Inputs (not committed)
|
||||
|
||||
- `EXPECTED_PRODUCTION_ORIGIN`
|
||||
- `EXPECTED_PRODUCTION_DB_NAME`
|
||||
- `EXPECTED_PENDING_MIGRATIONS`(本轮候选迁移 allowlist)
|
||||
- 受控 `DEPLOY_SERVER`、`DEPLOY_REFRESH_SERVER` 与 live smoke 凭据(如获批提供)
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 无;目标识别、迁移门禁顺序、回滚窗口定义和真实写入边界均已确认。
|
||||
|
||||
## Test Plan
|
||||
|
||||
- 本地静态:`node deploy/trialrun/validate-env-contract.js`、shell syntax、相关部署安全测试、task validate、process contract、docs registry、Trellis governance、sensitive scan、`git diff --check`。
|
||||
- 部署前:生产 target gate、`check-existing-deployment.sh` 13 项体检、candidate migration manifest 对账、pre-status 原始输出(仅受控现场)及其脱敏副本和 `terminated` 计数。
|
||||
- 受控发布:`DEPLOY_SERVER=<...> DEPLOY_REFRESH_SERVER=<...> bash deploy/trialrun/deploy-existing.sh`;仅在前置门禁全 PASS 后执行。
|
||||
- 部署后:post-status 原始输出(仅受控现场)及脱敏副本、`/ready`/build SHA、`scripts/qa/run-live-smoke-readiness.sh`、external/runtime smoke、公开 bundle 扫描和权限负向路径。
|
||||
- 失败分流:target/migration/preflight 失败不触碰远端应用;应用发布后 refresh 失败不重复发布;首条 `terminated` 出现后只允许 roll-forward。
|
||||
|
||||
## Evidence Notes
|
||||
|
||||
- 规划阶段未执行生产 SSH、migration、部署、真实数据写入或 live smoke。
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"id": "production-deployment-validation",
|
||||
"name": "production-deployment-validation",
|
||||
"title": "生产部署验证与迁移门禁",
|
||||
"description": "以迁移状态为第一道生产部署门禁,验证预期生产目标、合同 terminated 三列迁移、运行版本、只读权限边界与首条 terminated 记录后的回滚窗口;不执行真实终止或其他业务写入。",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": "deployment-production-migration-gate-and-readonly-verification",
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "shunli",
|
||||
"assignee": "shunli",
|
||||
"createdAt": "2026-08-08",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [
|
||||
"deploy/trialrun/deploy-existing.sh",
|
||||
"deploy/trialrun/deploy.sh",
|
||||
"deploy/trialrun/check-existing-deployment.sh",
|
||||
"deploy/trialrun/deployment-contract-lib.sh",
|
||||
"deploy/trialrun/validate-env-contract.js",
|
||||
"deploy/trialrun/production-preflight.mjs",
|
||||
"deploy/trialrun/production-preflight-remote.sh",
|
||||
".trellis/spec/hyb-oa/deployment-trialrun.md",
|
||||
"server/prisma/migrations/20260807193000_contract_registry_termination_workspace"
|
||||
],
|
||||
"notes": "部署前必须先确认预期生产目标与运行时 DATABASE_URL 身份,并在受控现场读取 migration status 原始输出;任务证据只保存脱敏副本。首条已提交 terminated 记录产生时关闭回滚窗口。生产部署、真实终止写入和企业微信/付款/审批写入均需分别受控;本 wrapper 不接受未发布提交 override。",
|
||||
"meta": {}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户