归档(08-08-production-deployment-execution): 完成生产部署验收
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-08-08 22:09:59 +08:00
父节点 d5fb2eca23
当前提交 ff46794a79
共修改 6 个文件,包含 4 行新增和 4 行删除
@@ -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,116 @@
# 生产部署执行与验收:设计说明
## 1. 设计边界
这是一次受控运维执行,不是新的产品开发。已归档的 `08-08-production-deployment-validation` 提供部署 tooling、target gate、迁移 reconciliation 和报告脱敏实现;本 task 只负责在明确批准的生产窗口调用它们、读取结果、分流失败并形成证据。
不修改应用代码、Prisma schema、生产环境变量或业务数据模型;本次规划修订允许仅调整部署/QA tooling 及其安全测试。若运行中发现实现缺陷,保留现场并另建 task,不在部署窗口临时修复。
拓扑事实:没有独立预发环境。历史名称为 `hyb-oa-trialrun` 的既有部署就是承载公网 OA 的唯一环境;`trialrun` 只是遗留命名。当前 target gate 的名称启发式与真实拓扑不一致,必须先校准为精确身份匹配,再连接生产。
## 2. 状态序列与硬门禁
```text
候选发布来源
-> 历史身份恢复 + refresh exact probe(只读)
-> systemd contract 结构读取 + 公网 origin 双取样精确核对(只读)
-> origin PASS 后冻结 contract REMOTE_DIR 为进程基线(只读)
-> 远端 POSTGRES_DB 只读引导(同一进程、不回显)
-> 上游/工作区/13 项部署体检
-> 生产 target identity(只读)
-> Prisma pre-status + _prisma_migrations 对账(只读)
-> terminated 行数与 rollbackWindow(只读)
-> 变更冻结确认
-> deploy-existing(唯一允许的迁移/发布写入)
-> post-status(只读)
-> /ready、SHA、smoke、权限负向(只读)
-> 证据收口 / PAUSED 分类
```
每一条箭头都是 fail-closed 闸门。任何闸门失败都不得执行下一步;特别是 pre-status、窗口判定和权限负向检查必须先于任何真实终止写入(本 task 不执行该写入)。
## 3. 候选来源与发布授权
- 先在本地完成 task 规划提交,使工作区 clean;再以明确的已提交 commit 作为候选。
- 运行 `check-source-provenance.mjs`,要求候选 commit 已进入配置的上游远端且 remote verification 为 true。
- `deploy-existing.sh` 拒绝 `DEPLOY_ALLOW_UNPUBLISHED_COMMIT` 和 `DEPLOY_UNPUBLISHED_REASON`;不得通过环境变量绕过。
- 规划修订前的候选已发布并通过 provenance;修订会产生新的候选 commit,实施前仍须重新提交、推送并确认 `published=true`、`remoteVerified=true`。
## 4. 目标身份与迁移读取
`deploy-existing.sh` 从受控 systemd unit 读取六个部署合同值,不从 Compose 标签猜路径或 SSH 身份。现有实现只校验这些值的格式并把它们传给 preflight,没有将 `DEPLOY_SERVER`、`REMOTE_DIR`、`COMPOSE_PROJECT_NAME` 与独立预期值精确对账;同时远端 helper 对 origin/数据库/host 使用 `trialrun` 名称排除。规划修订要求先补齐独立身份引导与精确比较,再执行生产连接。
### 4.1 只读身份引导
1. 从已审查历史提交 `34181e3` 恢复既有 `DEPLOY_SERVER`,从首次显式冻结 Compose project 的提交 `5e2d03893826` 恢复 `COMPOSE_PROJECT_NAME`;不输出任何字面值。两份脚本里的 `REMOTE_DIR` 只是可被显式输入覆盖的 fallback,不作为生产目录基线。
2. 从本机受控 SSH 配置无回显探测 refresh 候选,只有 exact forced-command `probe` 唯一 PASS 时才采用;零个或多个候选均停止。
3. 使用恢复的部署目标只读读取受控 systemd contract,先校验六项存在、路径/Compose 格式、`REMOTE_ENV_FILE` 必须位于 contract `REMOTE_DIR` 的固定相对位置、Compose file 固定且 project 与历史基线一致;此时不把历史目录 fallback 与 contract 比较。
4. 从 OPS7 当前公网检查基准取得预期 HTTPS origin;以 contract 仅作为定位信息,只读比较远端 `.env` 和运行容器的 `APP_ORIGIN`。两个独立取样均一致后才确认目标主机,并把 contract `REMOTE_DIR` 冻结为当前进程 `EXPECTED_REMOTE_DIR`;origin 不一致时不得采信或输出该目录。
5. 身份确认复核模式在第 4 步成功后立即停止,不读取数据库名、不进入 13 项体检或 Prisma。获得继续批准后,重新执行第 4 步,并从同一远端 `.env` 读取 `POSTGRES_DB`、在远端转换为 SHA-256 比较基线;数据库名明文不离开远端、不打印、不写文件。该基线用于后续 env、`current_database()` 和运行时 `DATABASE_URL` 三方一致性核对。
数据库名读取不是“连接任意目标后相信其自述”:它必须排在历史 target/project、contract 结构约束和公网 origin 双取样之后。任一前置失败时不得读取或进入 Prisma status。
### 4.2 完整 target/pre-status
校准后的 helper 顺序固定为:
1. 精确比较执行时恢复的部署目标、远端目录、Compose project、受控 contract、公网 origin 和引导出的数据库身份;不再因合法身份值含 `trialrun` 而拒绝。
2. 继续拒绝 HTTPS 以外、回环/零地址、local regression、staging、shadow、测试占位符,以及任何精确身份组不一致。
3. 检查唯一 server/db 容器、运行时 `DATABASE_URL` host/database、当前数据库名、`NODE_ENV=production` 和 `COOKIE_SECURE=true`。
4. 执行 `npx prisma migrate status`,在 marker 内输出原始结果。
5. 只读查询 `_prisma_migrations`,计算 applied、异常和 pending 集合,与候选 manifest 和批准 allowlist 精确对账。
6. 先以 `_prisma_migrations` 判断 termination migration 是否已应用:未应用时不查询尚未启用的终止状态,显式输出 `rollbackWindow=OPEN ... basis=migration_not_applied`;已应用时必须真实查询 `contract_registry.status = 'terminated'` 并输出 `basis=terminated_status_count`,查询错误一律 fail-closed。
status 原文仅存在受控 SSH/执行现场;本地 helper 写入 `prisma-migrate-status.txt` 时去除 datasource、连接串和预期身份值,并将稳定摘要写入 `preflight-output.txt`。报告目录复用时只删除两个已知文件,防止旧 PASS 污染新证据。
## 5. 发布与 post gate
只有身份引导、target/pre-status、窗口和冻结全部通过,才调用:
```bash
DEPLOY_SERVER=<历史身份恢复,仅进程内> \
DEPLOY_REFRESH_SERVER=<exact probe 唯一身份,仅进程内> \
EXPECTED_PRODUCTION_ORIGIN=<OPS7 公网基准,仅进程内> \
EXPECTED_PRODUCTION_DB_NAME_SHA256=<只读引导所得无明文比较基线,仅进程内> \
EXPECTED_PENDING_MIGRATIONS=<明确 allowlist> \
bash deploy/trialrun/deploy-existing.sh
```
仅执行身份引导、target/pre-status 与回滚窗口检查时,顶层引导器向该入口传入 `PRODUCTION_READ_ONLY_PREFLIGHT=1`;preflight 成功后必须在 `deploy.sh` 前退出。该开关不跳过任何只读门禁,也不允许进入同步、迁移或应用切换。
包装器固定 `SYNC_ENV=0`、`DEPLOY_DATA_MODE=existing` 和 `DEPLOY_STATUS_OUTPUT_MODE=quiet`。`deploy.sh` 内部的 `prisma migrate deploy` 是本 task 唯一允许的 schema 写入;不允许 seed、业务 API 写入、reset、resolve 或手工 DDL。
远端 `build init`、`run init` 和 `up --build server` 三个长阶段必须把完整 stdout/stderr 写入受控 `artifacts/deployment-runtime/`:目录 `0700`、日志 `0600`,文件名由固定阶段名和已验证候选 12 位 SHA 组成。普通输出只返回 `stage`、真实退出码和仓库相对 `logRef`;SSH 通道中断时已写入内容仍留在远端,后续以只读方式取尾部。原始日志不进 Git/task/普通对话,审查摘要必须脱敏。
部署后 helper 以空 pending allowlist重新执行 target/status/reconciliation,要求 schema up to date;随后才执行 refresh 和只读 readiness。refresh 失败分类为 `DEPLOYED_BUT_CONTRACT_REFRESH_FAILED`,不重复发布应用。
## 6. 回滚窗口
部署前窗口按 termination migration 的 applied 状态分流:
- migration 未应用 → 终止能力尚未生效,不执行状态查询,记录 `rollbackWindow=OPEN`、`terminatedRows=0`、`basis=migration_not_applied`。
- migration 已应用且 `SELECT count(*) FROM contract_registry WHERE status = 'terminated'` 返回 `0` → 记录 `OPEN` 与 `basis=terminated_status_count`;应用健康失败可按既有发布合同回退兼容版本,但不得 drop 三列。
- migration 已应用且返回 `>0` → 记录 `CLOSED` 与 `basis=terminated_status_count`;旧应用不识别 `terminated`,只能 roll-forward 或兼容 hotfix。
- migration 已应用但查询失败或返回不可解析值 → 停止,不得用异常兜底为 `OPEN`。
窗口在第一条已提交记录产生的事件时刻关闭,不按时间计算。当前 task 不写入该记录,因此不主动改变窗口状态。任何 rollback 决策必须引用部署前实际读数,而不是部署后补查。
由于没有同构预发环境,`OPEN` 窗口下的兼容应用回退是本轮唯一可用的旧版本退路;一旦窗口 CLOSED,所有恢复只能在唯一公网环境 roll-forward 或部署兼容 hotfix。不得把本地 synthetic fixture 当作预发替代证据。
## 7. 证据与失败分流
| 阶段 | 成功证据 | 失败处理 |
| --- | --- | --- |
| 历史身份/refresh/bootstrap | 历史 commit 引用、exact probe 计数、contract/origin 匹配和 DB 引导状态码 | 任一不唯一/不一致即停止,不进入 Prisma status |
| 来源/13 项体检 | commit、published/remoteVerified、13 项 PASS 计数 | 停止,不执行部署写入 |
| target/pre-status/窗口 | 原始受控输出引用、脱敏副本、pending/异常计数、窗口状态 | `PAUSED`,不进入 deploy |
| build/init/server switch | 固定阶段 PASS、相对 `logRef`、候选短 SHA | 远端 `0600` 日志保留,读取尾部后再定性;不得仅凭 SSH 流或盲重试 |
| migrate deploy | deploy wrapper 返回、候选 migration ID | 保留现场,不 reset/resolve/drop/重复部署 |
| post-status/readiness | applied、schema up to date、`/ready`/SHA/smoke | 按窗口状态决定回退或 roll-forward |
| refresh | exact forced-command PASS | `DEPLOYED_BUT_CONTRACT_REFRESH_FAILED`,只修复合同 |
缺少生产凭据、真机或公网条件的项目必须标 `PAUSED/SKIP`,不能转写为 PASS。所有结果都要记录“是否发生真实业务写入=否”。
## 8. 只读复核与收口
本任务涉及生产部署、迁移和回滚规则,实施后启动只读 reviewer;reviewer 只检查门禁顺序、原始/脱敏证据、失败分流和回滚窗口,不修改代码或生产状态。最终只有在所有获批生产、公网和必要真机检查通过时才可告知用户可以测试,否则保留 PAUSED。
@@ -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,197 @@
# 生产部署执行与验收:实施清单
## 0. 规划与启动门禁
- [x] 已向用户呈现本 task 的最终规划摘要,并取得对该摘要的最新明确批准。
- [x] 已解决候选发布是否可进入上游的外部授权;未获授权不得使用 unpublished override。
- [x] 任务从 `planning` 切换到 `in_progress` 前,确认本清单、PRD、design 与 Open Questions 已收敛。
- [x] **规划修订复核:**确认唯一公网环境拓扑、精确身份匹配和数据库名只读引导的修订摘要,并重新批准后再改 tooling/连接生产。
## 1. 候选与本地证据
- [x] 提交本 task 的规划 artifacts,确认工作区 clean。
- [x] 记录候选完整 commit、短 SHA、分支和上游包含关系;不把连接串、SSH target 或环境值写入文档。
- [x] 运行 `node deploy/trialrun/check-source-provenance.mjs`,要求 `published=true`、`remoteVerified=true`。
- [x] 运行既有本地合同门禁:
- [x] `node deploy/trialrun/validate-env-contract.js`
- [x] 相关部署 shell `bash -n`
- [x] `node --check deploy/trialrun/production-preflight.mjs`
- [x] 聚焦 `deployment-safety-contract.test.js`(既有提交证据为 13/13,本轮未重复触发生产动作)
## 1.1 拓扑校准与 tooling 修订(生产前)
- [x] **刻意次序调整:**本轮 target gate 不再是第一条生产命令;历史身份恢复、refresh exact probe、systemd contract/公网 origin 核对、远端 `POSTGRES_DB` 只读引导四步探测排在其之前。四步全程只读、逐步 fail-closed,任一失败均不得进入 target gate 或 Prisma status。
- [x] 从 `34181e3` 只读恢复历史 `DEPLOY_SERVER`,从 `5e2d03893826` 恢复首次显式冻结的 `COMPOSE_PROJECT_NAME`;确认历史 `REMOTE_DIR` 只是未验证 fallback,不作为身份基线。值仅保存在当前进程,不写入 task、日志或对话。
- [x] 无回显探测 `DEPLOY_REFRESH_SERVER`,要求 exact forced-command `probe` 唯一 PASS;多个候选或零候选均 PAUSED。
- [x] 修改 production preflight:取消对合法 `trialrun` 命名的单独拒绝,增加部署目标、远端目录、Compose project、systemd contract、公网 origin 的精确身份对账;保留回环/零地址/local regression/staging/shadow/placeholder 防线。
- [x] 明确 `DEPLOY_SERVER` 验证责任:真实来源在上游历史身份解析;引导器向 preflight 同时传 actual/expected 只用于防进程内漂移,不构成第二个独立身份来源。若重构上游,必须保留独立 target 来源校验,不能把下游恒等比较当作替代。
- [x] 增加只读数据库身份 bootstrap:身份组匹配后读取远端 `.env` 的 `POSTGRES_DB`,在远端转换为 SHA-256 比较基线后仅在内存中传给 target gate;数据库名明文禁止离开远端、回显或落盘,读取失败或身份组不一致时不得进入 Prisma status。
- [x] 增加 synthetic fixture:合法 `hyb-oa-trialrun` 身份通过;错误目录/project/host/db、回环、staging、占位符和多 refresh 候选 fail-closed;断言 bootstrap 不泄露值且顺序早于 status。
- [x] 增加 `PRODUCTION_READ_ONLY_PREFLIGHT=1` 停止点:完成 target/pre-status 与回滚窗口后、进入 `deploy.sh` 前退出;只读证据复核通过前不得运行写入路径。
- [x] 增加 `PRODUCTION_IDENTITY_ONLY=1` 停止点:以 contract 定位远端 `.env`/容器并完成 origin 双取样后,冻结 contract `REMOTE_DIR` 为进程内精确基线并立即退出;不得读取数据库名、运行 13 项体检或 Prisma。
- [x] 重新运行部署安全测试、shell/Node syntax、task/governance/sensitive/diff 门禁,提交并推送新的候选后重新通过 provenance。
## 2. 生产 preflight(全部只读)
- [x] 在同一进程内加载现行 OPS7 origin、历史身份恢复结果、exact refresh identity、只读 DB bootstrap 结果和 `EXPECTED_PENDING_MIGRATIONS`;不把任何值写入仓库或对话。
- [x] 先运行 `check-existing-deployment.sh`,记录 13 项真实 PASS/FAIL 数字;任何 FAIL 停止。
- [x] 运行 `deploy-existing.sh` 内置的 source/target/preflight,或按同一 wrapper 的只读阶段执行;人工阅读 target gate 原始输出和 Prisma status marker 原文。
- [x] 核对 candidate migration manifest、`_prisma_migrations` applied/异常/pending 集合和批准 allowlist;未知 pending、failed、rolled-back、unfinished、diverged 或目标身份不确定均 `PAUSED`。
- [x] 同一只读窗口查询 `terminated` 行数,记录 `rollbackWindow=OPEN|CLOSED`、实际 count 和读取时间;确认这是部署前快照。
- [x] 确认 preflight 到 deploy 之间有配置/数据库变更冻结;无法保证则停止。
## 3. 获批发布(唯一写入阶段)
- [x] 再次确认 preflight 所有门禁仍为 PASS,且没有新的远端配置/候选变化。
- [x] 仅调用 `deploy/trialrun/deploy-existing.sh`,固定 `SYNC_ENV=0`、`DEPLOY_DATA_MODE=existing`、`DEPLOY_STATUS_OUTPUT_MODE=quiet`。
- [x] 允许的 schema 写入仅为候选 migration;未执行 `migrate dev`、`reset`、`db push`、`resolve`、手工 DDL、额外 seed 或业务 API 写入。既有 wrapper 的 init 阶段按固定合同执行幂等 config/trialrun seed,不属于额外现场操作,真实结果单独记入证据。
- [x] SSH 通道中断后没有再次部署、回滚或清理;先读取三个远端 `0600` 阶段日志,再按已落地状态走只读 post-status 和合同 refresh 恢复。
## 4. 发布后只读验收
- [x] 保存 post-status 原始输出(仅受控现场)及脱敏副本;确认全部候选 migration applied / schema up to date。
- [x] 核对 `/ready`、完整 build SHA、公开短 SHA 与候选 commit。
- [x] 运行基础 smoke、external deps smoke、runtime smoke 和公开 bundle 扫描,记录真实数字和输出摘要。
- [x] 在任何真实终止写入前完成匿名合同列表/详情/终止端点 3/3 鉴权负向检查;登录态无权限与合同详情只读检查因仓库外管理员、员工凭据均返回 401 而标记 `PAUSED`,未修改生产账号或凭据。
- [x] 初次只读复核发现 `backup_build FAIL` 后按 `DEPLOYED_BUT_CONTRACT_REFRESH_FAILED` 分流;没有重复发布应用,只执行 exact forced-command refresh,随后聚合体检恢复 `PASS=13 FAIL=0`。
## 5. 失败分流、review 与收口
- [x] 将每个失败归入 target/migration、应用健康、refresh、public/live/permission 之一;不得把迁移失败与产品回归混为一类。
- [x] 记录本 task 主动发起的真实业务记录写入为 0,`terminated` 行数为 0;schema 写入仅有候选 migration,固定 init 阶段执行了幂等配置 seed。
- [x] 对照部署前/后窗口快照确认均为 `OPEN`;没有执行应用回退或 drop 三列。
- [x] 启动只读 reviewer,确认门禁顺序、原始证据可追溯、脱敏副本无敏感信息、失败后没有继续推进。
- [x] 运行最终 Trellis/process/docs/governance/sensitive/diff 门禁;task/process/governance/sensitive/diff 均 PASS,docs audit 为 0 errors 并保留仓库既有 22 个未注册文件 warning。
- [x] 只有生产、公网和必要真机全部获批并通过时才可告知用户可以测试;本轮登录态与企业微信真机仍 `PAUSED`,最终答复不得告知“可以测试”。
- [x] task 已归档;会话日志由本次 finish-work 收口记录,报告保留 pre/post status、target gate、窗口判定和所有 `PAUSED/SKIP` 原因。
## Rollback Points
1. 来源或 preflight 失败:尚未写入,停止并修正授权/输入后重新检查。
2. `migrate deploy`/init 失败:保留现场,禁止 reset/resolve/drop,等待明确处置。
3. `/ready`/smoke 失败:窗口 OPEN 可按既有合同回退兼容应用;窗口 CLOSED 只 roll-forward。
4. refresh 失败:应用已部署但合同刷新失败,不重复发布应用,只走合同修复流程。
## Evidence Notes
- 原始 target gate 和 Prisma status stdout/stderr 不进 Git;只在受控执行现场留存。
- 任务 artifacts 只保存脱敏副本、commit、状态码、migration ID、计数、窗口状态和证据引用。
- 不保存生产 origin、数据库名、SSH target、用户名、绝对远端路径、连接串、凭据、业务正文或原始 payload。
## Initial Input-gate Pause Snapshot (2026-08-08)
- 规划提交:`4596ed0`;暂停证据提交:`73f1467`;当前候选已推送到配置的 `origin/main`,`check-source-provenance.mjs` 返回 `published=true`、`remoteVerified=true`。
- 本地门禁:env contract、shell/Node syntax、task validate、process contract、docs audit(0 errors,保留仓库既有 warning)、Trellis governance(0 errors/0 warnings)、sensitive scan(0 findings)均通过。
- 生产输入存在性检查:`EXPECTED_PRODUCTION_ORIGIN`、`EXPECTED_PRODUCTION_DB_NAME`、`EXPECTED_PENDING_MIGRATIONS`、`DEPLOY_SERVER`、`DEPLOY_REFRESH_SERVER` 五个进程输入均未注入;受控 contract 路径按设计应在连接目标后由 systemd unit 读取,未把它们误当作本地输入缺失。
- 生产动作:未执行 SSH、target gate、`prisma migrate status`、`migrate deploy`、数据库写入、refresh、公网 smoke 或真机验证;AC1 的生产体检及 AC2–AC7 保持未验收并标记 PAUSED。
- 失败分流:停止在生产输入门禁,未出现“门禁失败但继续推进”。后续必须在同一受控执行环境注入输入后,从 target gate 重新开始。
## Input Traceback Evidence (2026-08-08)
- 当前 OPS7 运维交接 Runbook 仍提供公网 origin 作为只读检查基准;它没有提供生产数据库名或 SSH 身份。
- 仓库外的三份验收 env 文件仍存在且为受控用户、`0600` 权限;它们只包含 live smoke、SMTP 或一次性账号键,不包含生产部署身份/数据库输入,未被本 task 读取或 source。
- Git 历史没有 `deploy/trialrun/.env`、`deploy.env` 或上述验收 env 文件的删除记录;`.gitignore` 从始终将 `.env` 排除在版本库之外。
- shell 历史没有五个部署变量的字面赋值记录。结论:部署输入不是被本 task 删除,而是按运维合同设计为每次执行时临时注入。
## Planning Revision Evidence (2026-08-08)
- 历史提交 `34181e3` 的旧 `deploy.sh` 确有默认部署 target/目录,但当时尚未显式设置 Compose project;`5e2d03893826` 首次冻结 project 且沿用同一远端目录。两份证据共同证明该遗留命名与公网 origin 属于同一唯一运行环境,不能因 `trialrun` 字样单独拒绝。
- 现行 gate 尚未对 `DEPLOY_SERVER`、`REMOTE_DIR`、`COMPOSE_PROJECT_NAME` 与独立预期身份做精确对账;本节修订只允许先改部署/QA tooling,不改业务代码或生产数据。
- 修订后的第一条远端动作仍是只读身份 bootstrap;数据库名读取排在 exact identity 匹配之后,任何 bootstrap 或 target gate FAIL 都不得继续。
## Read-only Identity Bootstrap Evidence (2026-08-08)
- tooling 提交 `f11e2f2` 已进入 `origin/main`;执行前 provenance 为 `published=true`、`remoteVerified=true`,工作区 clean。
- 使用 `PRODUCTION_READ_ONLY_PREFLIGHT=1` 启动只读身份引导。历史 target/目录与首次显式 project 记录解析成功;refresh forced-command `probe` 恰好一个候选精确 PASS,候选身份未回显。
- systemd deployment contract 读取成功且六项结构校验通过,但当前 `REMOTE_DIR` 与已批准历史精确基线不一致;稳定停止码为 `SYSTEMD_REMOTE_DIR_MISMATCH`。
- 门禁在数据库身份引导前停止:未读取或生成 `POSTGRES_DB` 比较基线,未运行 13 项聚合体检、target gate、`prisma migrate status`、`_prisma_migrations` 对账或 `terminated` 行数查询,因此 rollback window 仍为 `UNKNOWN`,不得推断 OPEN/CLOSED。
- 未调用 `deploy-existing.sh` 的 preflight/deploy 阶段,未进入 `deploy.sh`、rsync、`migrate deploy`、应用切换、refresh 或公网 smoke;生产 schema 写入与业务写入均为 0。
- 失败后未改用当前 contract 自述覆盖历史预期、未跳过目录精确比较、未继续推进。后续必须先独立确认当前 contract 目录变更的来源与公网环境归属,再修订批准基线或恢复正确 contract。
## Origin-first Identity Recalibration Evidence (2026-08-08)
- 修订提交 `bf2631c` 已进入 `origin/main`;执行前 provenance 为 `published=true`、`remoteVerified=true`,工作区 clean。
- Git 全历史复核确认 `REMOTE_DIR="${REMOTE_DIR:-...}"` 始终是可被运维显式输入覆盖的 fallback,未形成其曾在生产生效的证据;本轮不再把该默认值作为目录权威,也未修改线上 contract。
- 使用 `PRODUCTION_IDENTITY_ONLY=1` 重新执行:历史 SSH target 与首次显式 Compose project 基线解析成功,refresh forced-command `probe` 仍恰好一个候选精确 PASS,systemd contract 六项结构和 contract-relative env/Compose 约束通过。
- 远端 `.env` 的 `APP_ORIGIN` 与运行中 server 容器的 `APP_ORIGIN` 分别独立取样,二者均与 OPS7 当前 HTTPS 公网基准精确匹配;稳定结果为 `origin_verified_remote_dir_baseline=systemd_contract`。
- 只有上述 origin 双取样通过后,当前 systemd contract 的 `REMOTE_DIR` 才被冻结为本进程 `EXPECTED_REMOTE_DIR`;目录字面值未回显、未写入 task、日志或证据文件。
- identity-only 在目录基线冻结后立即退出:未读取 `POSTGRES_DB`、未生成 DB hash、未运行 13 项聚合体检、target gate、`prisma migrate status`、迁移表对账或 `terminated` 行数查询,rollback window 仍为 `UNKNOWN`。
- 未调用 `deploy-existing.sh`、`deploy.sh`、rsync、`migrate deploy`、应用切换、refresh 或公网 smoke;生产 schema 写入与业务写入均为 0。进入数据库身份和 pre-status 前仍需用户复核本节证据。
## Read-only Pre-status Attempt Evidence (2026-08-08)
- 责任边界记录提交 `bf23664` 已进入 `origin/main`;执行前 provenance 为 `published=true`、`remoteVerified=true`,工作区 clean。
- 使用 `PRODUCTION_READ_ONLY_PREFLIGHT=1` 执行。历史 target/project、refresh exact probe、origin 双取样、contract 目录冻结和远端 DB hash 引导通过,稳定输出 `exact_identity_group`;数据库名、目标、路径均未回显。
- `check-existing-deployment.sh` 的 13 项全部真实 PASS:`PASS=13 FAIL=0`,包括部署身份、六项 contract、仓库/环境/数据目录、唯一 runtime、build、storage/PGDATA mount、backup build 和 refresh program。
- production preflight 在 Prisma status marker 产生前停止,外层稳定码为 `PRISMA_STATUS_OUTPUT_MISSING`;脱敏 `preflight-output.txt` 为空,未生成 `prisma-migrate-status.txt`。因此没有可审查的 status 原文,不能判定 pending、applied、failed、diverged 或 schema up to date。
- 代码复核确认 `production-preflight.mjs` 先在 `rawStatus == null` 时抛 `PRISMA_STATUS_OUTPUT_MISSING`,之后才在 `result.status !== 0` 分支解析远端 stderr 安全码;本次真实远端停止码被该顺序遮蔽,不能从现存 evidence 恢复,也不得猜测。
- DB hash 引导成功只证明远端 `.env` 数据库名已形成比较基线,不证明后续 `.env` / `current_database()` / runtime `DATABASE_URL` 三方一致性;远端 preflight 未输出 `target_identity_and_database_identity` PASS,故该验收项保持未通过。
- 未获得 migration reconciliation、pending allowlist 对账或 `terminated` count;rollback window 仍为 `UNKNOWN`,不得写作 OPEN/CLOSED。
- `PRODUCTION_READ_ONLY_PREFLIGHT=1` 未进入 `deploy.sh`;未执行 rsync、`migrate deploy`、应用切换、contract refresh 或业务 API,生产 schema 写入与业务写入均为 0。失败后未重跑、未跳过门禁。
## Pre-status Diagnostic Wrapper Repair Evidence (2026-08-08)
- 已确认外层 `production-preflight.mjs` 在远端非零退出且 Prisma marker 缺失时,先抛出 `PRISMA_STATUS_OUTPUT_MISSING`,遮蔽了远端 helper 已输出的稳定安全码;该结论来自源码顺序与既有脱敏 evidence,不反推或猜测真实远端原因。
- 最小修复保持稳定摘要与 `reportRef` 在失败判断前输出,并把远端非零退出判断提前:可解析 `code=...` 时传播原码,不可解析时固定失败为 `PRODUCTION_PREFLIGHT_REMOTE_FAILED`;只有远端退出 0 且 marker 缺失时才返回 `PRISMA_STATUS_OUTPUT_MISSING`。
- 聚焦回归覆盖四种组合:非零/有安全码/marker 缺失,非零/有安全码/marker 存在,非零/无可解析码/marker 缺失,以及退出 0/marker 缺失;`deployment-safety-contract.test.js` 为 19/19 PASS。
- 本地 Node/shell syntax、env contract、task validate、process contract、docs audit、Trellis governance、sensitive scan 与 `git diff --check` 均通过;docs audit 仅保留仓库既有的未注册文档 warning。
- 本节修复阶段未连接生产、未运行 target/status、未执行 rsync、migration、应用切换或业务 API;生产 schema 写入与业务写入仍为 0。候选提交进入上游且 provenance 重新通过后,才允许重跑只读 pre-status;取得真实远端错误码后必须立即停止并复核原因。
## Read-only Candidate Compose Attempt Evidence (2026-08-08)
- 错误优先级修复提交 `83af754` 已进入 `origin/main`;执行前 provenance 为 `published=true`、`remoteVerified=true`,工作区 clean。
- 使用 `PRODUCTION_READ_ONLY_PREFLIGHT=1` 重跑:精确身份组通过,既有部署聚合体检仍为 `PASS=13 FAIL=0`;production preflight 在 Compose 配置校验处以真实稳定码 `PROD_TARGET_COMPOSE_CONFIG_INVALID` 停止,外层 `DEPLOY_EXISTING_FAILED` 只表示受控 wrapper 已停止。
- 源码核对确认 Compose 文件以 `${BUILD_SHA:?...}` / `${VITE_BUILD_SHA:?...}` 强制要求构建标识;正式 `deploy.sh` 从已验证 provenance 注入完整 commit 与 12 位短 commit,而只读 preflight 未注入两值,因此失败属于 preflight 候选配置输入缺口,不是生产配置漂移。
- 本次未生成 Prisma status marker 或脱敏 status 文件,未完成 `.env` / `current_database()` / runtime `DATABASE_URL` 三方一致性、pending 对账或 `terminated` count;rollback window 仍为 `UNKNOWN`。
- 失败后立即停止,未进入 `deploy.sh`、rsync、`migrate deploy`、应用切换、refresh 或业务 API;生产 schema 写入与业务写入均为 0。
## Candidate Compose Contract Repair Evidence (2026-08-08)
- `deploy-existing.sh` 仍是候选来源唯一入口:它先运行 `check-source-provenance.mjs`,只在 `published=true` / `remoteVerified=true` 后把 `source_commit` 传为 `CANDIDATE_COMMIT`。`production-preflight.mjs` 已移除 `git rev-parse HEAD` fallback,候选缺失直接 fail-closed。
- preflight 仅从该候选派生 `BUILD_SHA=<完整 commit>` 与 `VITE_BUILD_SHA=<前 12 位>`,并传给远端 Compose;不使用 7 位 Git 默认短 hash。远端 helper 在 `docker compose config` 前校验完整/短 SHA 格式以及 `VITE_BUILD_SHA === BUILD_SHA.slice(0, 12)`。
- 此处 `docker compose config` 验证的是候选发布后将生效的配置,不代表当前运行容器已经使用该 build SHA;运行态仍须由后续只读 runtime/SHA 检查独立证明。禁止用 `--no-interpolate` 绕过 Compose 的必填变量合同。
- 聚焦测试保持 19/19 PASS,并新增候选完整/12 位注入、7 位短 SHA 拒绝、合法但不匹配 SHA 拒绝、禁止 HEAD fallback 的覆盖;Node/shell syntax 与 env contract PASS。
## Empty-Allowlist Migration Discovery Evidence (2026-08-08)
- 候选配置修复提交 `8b2ed5b` 已进入 `origin/main`;执行前 provenance 为 `published=true`、`remoteVerified=true`,工作区 clean。
- 使用空 `EXPECTED_PENDING_MIGRATIONS` 重跑只读 pre-status:精确身份组、13 项聚合体检和 `.env` / `current_database()` / runtime `DATABASE_URL` 三方数据库身份均 PASS;脱敏 Prisma status 显示运行镜像包含 64 条 migration 且对自身为 `Database schema is up to date!`。
- 候选 manifest 包含 65 条 migration;独立候选清单与生产 `_prisma_migrations` 差集发现唯一 pending 为 `20260807193000_contract_registry_termination_workspace`,因此稳定停止码为 `PROD_MIGRATION_PENDING_ALLOWLIST_MISMATCH`。这证明运行镜像 status 不能替代候选差集门禁。
- 该 migration SQL 已复核为三个可空列、一个索引和一个 `ON DELETE RESTRICT` 外键,无 drop/truncate/delete 或数据改写;用户已明确批准把它加入下一次只读 pre-status 的 allowlist。
- 空 allowlist 停止发生在窗口查询前,故 `terminated` count 未读取、rollback window 仍为 `UNKNOWN`;未进入 `deploy.sh`、rsync、migration、应用切换、refresh 或业务 API,生产 schema 写入与业务写入均为 0。
## Explicit Rollback-window Basis Repair Evidence (2026-08-08)
- termination migration 尚未应用时,终止能力不可能由本轮应用生效;helper 现以 `_prisma_migrations` applied set 走显式分支,不发送 `contract_registry` 状态查询,固定输出 `rollbackWindow=OPEN terminatedRows=0 basis=migration_not_applied`。
- migration 已应用时继续执行真实 `status = 'terminated'` 计数,输出 `basis=terminated_status_count`;查询失败或结果不可解析分别返回固定错误,不使用 catch/default 把未知状态误报为 OPEN。
- 聚焦 fixture 覆盖:未应用且状态查询被设置为必失败仍应 OPEN(证明未查询)、已应用 0 行 OPEN、已应用 2 行 CLOSED,以及已应用查询失败必须 `PROD_TERMINATED_COUNT_UNAVAILABLE`。聚焦测试 19/19 PASS。
## First Production Deployment Attempt Evidence (2026-08-08)
- 窗口依据修复提交 `b94afcf` 已进入 `origin/main`;发布前 preflight 再次通过:精确身份组、13 项聚合体检、数据库三方身份、唯一 pending allowlist 和 `rollbackWindow=OPEN ... basis=migration_not_applied` 全部 PASS。
- 唯一生产入口随后完成已验证源码同步并进入远端 `build init`;编排进程在该长阶段退出,未到 `run init`。代码顺序固定为先 `build init`、后 `prisma:migrate:deploy`,只读复核同时证明 termination migration 仍 pending、公开 `/ready` 仍为旧 build、新 init 镜像仍为旧 build,故 schema 写入和应用切换均为 0。
- 同一候选在本地 `client` typecheck/build PASS;远端当时仍有可用磁盘和内存,未取得明确 OOM 证据,不能把环境猜测写成根因。原裸调用只把输出写入 SSH 流,通道证据不完整,未能恢复真实末尾错误。
- 失败后未重试部署、未运行 reset/resolve/drop、未改生产配置。额外只读检查确认现有 13 项合同仍健康、回滚窗口仍 OPEN;用户随后只批准一次在远端持久化日志机制落地后的受控重试。
## Persistent Remote Stage Log Repair Evidence (2026-08-08)
- `deploy.sh` 为 `build-init`、`run-init` 和 `build-switch-server` 三个长阶段统一增加远端日志包装器;完整 stdout/stderr 直接写入仓库相对 `artifacts/deployment-runtime/`,目录 `0700`、文件 `0600`,文件名只含固定阶段名和已验证候选 12 位 SHA。
- 普通输出仅记录 stage、真实 child exit code 和相对 `logRef`。失败状态在 Bash `else` 分支中立即捕获并返回,随后由 `set -e` 停止,不会因 `fi` 后读取 `$?` 而丢失原码。
- 原始 build/init 日志不写入 Git、task 或普通对话;若受控重试失败,先只读取对应远端日志尾部并脱敏定性,再决定后续动作。任何下一次 rsync 可能清除旧 runtime artifacts,因此读取必须发生在重试之前。
- 聚焦静态回归证明三个裸调用均已替换、重定向和权限合同存在、失败分支立即捕获退出码;shell syntax、env contract 与部署安全测试 PASS。
## Controlled Retry, Partial-success Recovery and Post-status Evidence (2026-08-08)
- 日志修复提交 `0224ad8` 已进入 `origin/main`;执行前本地与上游一致、工作区 clean,provenance 为 `published=true`、`remoteVerified=true`。唯一受控重试的生产 preflight 再次通过:聚合体检 `PASS=13 FAIL=0`、唯一 pending 与批准 allowlist 精确一致、`rollbackWindow=OPEN terminatedRows=0 basis=migration_not_applied`。
- SSH 流在 `build-init` 期间被远端关闭,外层稳定停止码为 `DEPLOY_EXISTING_FAILED`。没有发起第二次部署;在任何再次同步之前,只读取得三个候选阶段日志,三者均存在且 mode `0600`。日志证明 `build-init` 完成、`run-init` 成功应用 `20260807193000_contract_registry_termination_workspace`、`build-switch-server` 完成 server 重建,故本次属于远端继续完成后的部分成功态,不是构建失败。
- init 固定阶段显示 migration 成功,并执行既有幂等 config/trialrun seed:银行、审批规则等既有记录保持,试运行管理员未新建;没有调用合同终止、付款、审批或其他业务 API。原始阶段日志只留远端,task 仅保存本段脱敏结论。
- 首次断线后聚合体检为 `PASS=12 FAIL=1`,唯一失败为 `backup_build`,与应用已切换但外层尚未执行合同 refresh 一致。独立 post-status 在不受该 refresh 前置项遮蔽的只读路径完成:数据库三方身份 PASS、`pending=none`、65 条 migration 的脱敏 status 为 `Database schema is up to date!`,窗口为 `OPEN terminatedRows=0 basis=terminated_status_count`。
- 运行容器完整 SHA 与候选一致,公网 `/ready` 为 200 且短 SHA 一致;external deps smoke 四个域 PASS,runtime smoke 通过 Prisma、12 个环境开关、13 项工作区显隐、12 个 feature flag 与导航检查;db/server 两个服务运行。公网 bundle 扫描 `scanned=101 focused=2 buildMarker=PASS findings=0`。
- 按部分成功恢复合同,只调用 exact forced-command `refresh`,没有重复发布应用。refresh 返回精确 PASS;随后完整只读复核恢复 `PASS=13 FAIL=0`,`pending=none`,窗口仍为 `OPEN terminatedRows=0 basis=terminated_status_count`。
- Playwright 初次因本机缺少 lockfile 对应 Chromium 未触达公网;补齐用户级浏览器缓存后,公网基础探活与企微配置 1/1 PASS。管理员和员工只读 smoke 均在登录处稳定返回 401,说明仓库外验收凭据已失效;登录态权限负向和合同详情只读检查保持 `PAUSED`,未据此宣称产品失败或修改生产账号。
- 匿名合同守卫使用不可命中占位 ID 验证列表 GET、详情 GET、终止 POST 均在鉴权层返回 401,`passed=3/3`;POST 未取得身份,未进入业务处理。企业微信真机仍为 `PAUSED`,本 task 不宣称全部用户侧验收完成。
## Read-only Reviewer Evidence (2026-08-08)
- reviewer 结论为 `PASS_WITH_FINDINGS`,无阻塞性缺陷:写入前门禁、64→65 migration、唯一 pending→none、窗口依据切换、运行/公网 SHA、12/1→refresh→13/13 和匿名鉴权 3/3 均自洽;未发现第二次部署迹象或敏感值泄漏。
- reviewer 指出的两处过期未来时已改为实际部署状态,早期未注入输入的段落已明确标记为初次输入门禁暂停快照;登录态与真机 `PAUSED` 继续保留。
@@ -0,0 +1,129 @@
# 生产部署执行与验收
## Goal
在已完成生产目标与迁移门禁实现的基础上,于明确批准的生产窗口内执行一次可审计的既有部署,验证合同 `terminated` 三列迁移已经安全落地,并完成部署后只读 readiness 检查。任何门禁失败都必须停止,不得以退出码、部分输出或后续部署结果替代人工核对。
本任务不主动制造真实 `terminated` 记录,也不执行其他业务写入;它只在生产侧执行获批的迁移/发布动作,并保留足以产出审查的脱敏证据。
## Background and Confirmed Facts
- 生产部署入口是 `deploy/trialrun/deploy-existing.sh`。它读取受控 systemd deployment contract,固定 `SYNC_ENV=0`、`DEPLOY_DATA_MODE=existing`,并在 `deploy.sh` 前调用生产 target/pre-migration gate,部署后再做 post-status gate。
- 当前没有独立预发环境:历史命名为 `hyb-oa-trialrun` 的既有部署就是承载公网 OA 的唯一运行环境。`trialrun` 是遗留命名,不代表另一套非生产实例;这次 schema 迁移无法先在同构预发环境演练。
- Git 历史中的 `REMOTE_DIR="${REMOTE_DIR:-...}"` 是未证明曾生效的 fallback,不是生产目录事实源。只有历史 SSH target 与 Compose project 继续作为独立基线;`REMOTE_DIR` 必须在远端 `.env` 和运行容器 origin 双取样一致后,从该生产主机的受控 systemd contract 冻结为当前进程精确基线。
- 生产 target gate 必须确认 HTTPS 公网 origin、`NODE_ENV=production`、`COOKIE_SECURE=true`、数据库身份和既有部署合同。不得再以名称中含 `trialrun` 作为拒绝依据;改为精确匹配已确认的部署身份组,并继续拒绝回环、本地回归库、staging、shadow、测试占位符等真实非生产目标。
- pre-status 的原始 stdout/stderr 只能留在受控执行现场;任务目录和报告只保存脱敏副本、稳定状态码、迁移 ID、计数和证据引用。
- 候选发布必须是干净、已提交并已进入上游的 commit。规划阶段曾发现 `origin/main` 落后并已通过受控提交解决;严格 wrapper 仍拒绝未发布提交 override,任何规划修订都必须重新提交、推送并复核 provenance。
- 当前候选迁移为 `20260807193000_contract_registry_termination_workspace`。部署前须从候选 commit 生成 manifest,并以生产 `_prisma_migrations` 与 `prisma migrate status` 原始输出共同核对 pending、failed、rolled-back、unfinished 和 diverged 状态。
- `contract_registry.status = 'terminated'` 的第一条已提交记录会关闭回滚窗口。窗口按事件定义,不按发布时间或时间窗口定义;本任务不执行终止写入。
- 既有生产部署、真实合同写入、企业微信、付款、审批和其他业务数据操作均需受控;本任务明确只授权本节列出的发布迁移和只读验证。
## Scope
1. 在候选发布进入上游且工作区干净后,先从已审查的历史 Git 事实和受控远端合同完成只读身份引导,再执行生产 target gate、既有 13 项部署体检、pre-migration status 与 `terminated` 行数查询。
2. 在所有 pre-gate 通过且变更冻结成立后,使用唯一获批入口执行既有部署;允许的 schema 写入仅为候选 commit 已声明的 Prisma migration。
3. 部署后执行 post-migration status、`/ready`、候选 build SHA、基础/external/runtime smoke、公开 bundle 扫描和权限负向/详情只读检查,并生成脱敏证据包。
4. 按失败阶段记录 `PAUSED`、`FAIL` 或 `DEPLOYED_BUT_CONTRACT_REFRESH_FAILED`,完成只读 review、收口和归档。
## Requirements
### R1 发布来源与生产目标
- 候选 commit 必须干净、已提交、已进入配置的上游远端;严格 wrapper 不接受 `DEPLOY_ALLOW_UNPUBLISHED_COMMIT` 或 `DEPLOY_UNPUBLISHED_REASON`。
- 公网 origin 从当前 OPS7 运维交接 Runbook 的现行公网检查基准取得;部署 SSH 目标从已审查历史提交 `34181e3` 恢复,Compose project 从首次显式冻结该值的历史提交 `5e2d03893826` 恢复。历史 `REMOTE_DIR` fallback 不参与生产身份判断。
- `DEPLOY_REFRESH_SERVER` 通过本机受控 SSH 配置的 exact forced-command `probe` 唯一识别;零个或多个候选均立即停止,不按别名、顺序或连接速度猜测。
- 使用 structurally valid 的 systemd contract 定位远端 `.env` 和运行容器,只读比较两个 `APP_ORIGIN` 取样;两者均与 OPS7 基准一致后,才确认该主机承载生产 origin,并把 contract 的 `REMOTE_DIR` 冻结为当前进程预期值。origin 不一致立即停止,不得采信 contract 目录。
- 生产数据库名只允许在部署目标、Compose project、受控 systemd contract 和公网 origin 已确认后,通过一次获批的远端 `.env` 只读读取取得;值仅保留在远端并转换为 target gate 所需的比较输入,不回显、不落盘。
- target gate 必须精确验证 `DEPLOY_SERVER`、origin 验证后冻结的 `REMOTE_DIR`、`COMPOSE_PROJECT_NAME`、公网 origin 与读取后的 `POSTGRES_DB` 身份组,并继续核对运行时 `DATABASE_URL` host/database、当前数据库、`NODE_ENV` 和 cookie 安全配置;任一身份不确定均 fail-closed。
- `EXPECTED_PENDING_MIGRATIONS` 可先以空集合运行只读 pre-status 发现实际 pending;只要实际集合非空或不等于批准 allowlist,就必须停止并逐条复核 SQL,不能自动进入部署。
### R2 pre-status 与回滚窗口先行
- 在任何 `migrate deploy`、seed、应用切换或业务写入前,人工阅读 target gate 和 `prisma migrate status` 原始输出,并核对脱敏副本。
- pending 集合必须与批准 allowlist 精确一致;未知 migration、failed/rolled-back/unfinished/diverged、不可解释 status 或数据库目标漂移均立即停止。
- 同一只读阶段查询 `terminated` 行数:0 记 `rollbackWindow=OPEN`,大于 0 记 `CLOSED`。必须在权限负向路径与任何未来真实终止写入前完成。
### R3 受控发布
- 只使用 `DEPLOY_SERVER=<受控目标> DEPLOY_REFRESH_SERVER=<受控刷新目标> bash deploy/trialrun/deploy-existing.sh`。
- 保持 `SYNC_ENV=0`、`DEPLOY_DATA_MODE=existing`,不得覆盖生产 `.env`,不得选择其他部署脚本或 SSH 身份。
- 只有 R1/R2 全部 PASS 且能证明 preflight 到 deploy 期间目标与配置冻结,才允许进入 schema/app 写入阶段。
### R4 发布后只读验收
- post-status 必须显示候选迁移全部 applied / schema up to date;不能只看命令退出码。
- `/ready`、完整 build SHA、公开短 SHA、基础 smoke、external/runtime smoke、公开 bundle 扫描和权限负向路径必须与候选 commit 一致。
- 登录态、企业微信真机、真实 OCR 或其他缺少专用凭据/样本的检查只能记 `PAUSED/SKIP`,不得伪装成 PASS。
### R5 失败与回滚边界
- target/preflight/status 失败:不部署、不写库,保留现场并返回 `PAUSED`。
- migrate deploy/init 中途失败:保留现场,禁止 reset、resolve、drop、手工 DDL 或重复部署,等待明确决策。
- `OPEN` 窗口下应用健康失败可按既有合同回退兼容应用但不 drop 三列;`CLOSED` 窗口下只允许 roll-forward 或兼容 hotfix。
- refresh 失败标记 `DEPLOYED_BUT_CONTRACT_REFRESH_FAILED`,不重复发布应用。
## Acceptance Criteria
- [x] AC1:候选 commit 已提交、工作区 clean、已进入上游;来源校验和既有 13 项体检均 PASS。若候选未发布,任务在此处 PAUSED,不使用 override。
- [x] AC2:只读身份引导证明历史部署目标、Compose project、受控 systemd contract 与公网 origin 属于同一唯一运行环境;远端 `.env` 与运行容器 origin 双取样均匹配后,contract `REMOTE_DIR` 已冻结为进程内精确基线,数据库名只在该前提下读取。target gate 的原始输出在受控现场可供人工阅读,脱敏副本只证明 PASS/FAIL 和原因,未泄露任何身份值。
- [x] AC3:任何 schema 写入前完成 pre-status;原始 status 输出与脱敏副本可对账,异常集合为空,pending 与 allowlist 精确一致,`terminated` 行数和 `rollbackWindow=OPEN|CLOSED` 判定已记录。
- [x] AC4:仅在 AC1–AC3 全部 PASS 后调用 `deploy-existing.sh`;未执行 reset、db push、resolve、手工 DDL、seed 之外的业务写入或真实 `terminated` 写入。
- [x] AC5:部署后 status 显示全部候选迁移 applied/schema up to date,`/ready`、build SHA、smoke、external/runtime smoke、bundle scan 结果与候选 commit 一致。
- [x] AC6:权限负向路径和合同详情只读检查排在任何真实终止写入之前;匿名鉴权负向 3/3 PASS,登录态与合同详情因仓库外验收凭据失效明确标记 `PAUSED`,企业微信真机仍为 `PAUSED`。
- [x] AC7:结果证据包含候选 commit、target gate、pre/post status 引用、窗口状态、各检查真实数字/输出摘要、失败分流和业务写入计数;不含凭据、原始环境或合同正文。
- [x] AC8:Trellis task validate、process contract、docs audit、governance、sensitive scan 和 diff check 通过;登录态与企业微信真机仍 `PAUSED`,最终答复不得告知“可以测试”。
## Out of Scope
- 不执行真实 `terminated` 写入、合同编辑/归档/恢复、报销/付款/审批/银行/ERP/企业微信写入。
- 不执行 `prisma migrate dev`、`migrate reset`、`db push`、`migrate resolve`、手工 DDL、删除/修改 `_prisma_migrations` 或自动 drop 新列。
- 不修改合同业务代码、权限模型、状态机、生产 `.env` 或应用配置;如发现缺陷,登记后另建 task。
- 不以本地、staging、shadow、回归库或临时 runner 代替预期生产目标;不得仅凭环境名称包含或不包含 `trialrun` 判定身份,必须使用精确身份组。
- 不把从历史 Git 或远端 `.env` 读取到的身份值写回仓库、任务文档、普通日志或对话。
## Accepted Risk and Deferred Items
- 首条已提交 `terminated` 记录出现后,旧应用回退窗口永久关闭;本任务只读取并记录窗口状态,不主动关闭它。
- 当前没有同构预发环境;13 项体检、target/pre-status、回滚窗口和 `/ready` 是唯一运行环境上的主要安全网。窗口 OPEN 时的兼容回退是本轮唯一一次可用的旧版本回退机会。
- 生产公网 smoke、企业微信真机、登录态或真实 OCR 可能因专用凭据/样本缺失而 `PAUSED/SKIP`,不等同于部署通过。
- preflight 与 deploy 之间无法建立配置/数据库变更冻结时,必须在写入前暂停;post-status 不能事后补救错误目标写入风险。
- 任何非迁移产品失败不得在本任务内顺手修复,应登记为独立后续任务。
## Operational Inputs (not committed)
- `EXPECTED_PRODUCTION_ORIGIN`(从现行 OPS7 公网基准在进程内取得)
- `PRODUCTION_IDENTITY_ONLY=1`(只完成 origin 双取样与 contract 目录基线冻结,随后在数据库读取和 pre-status 前退出)
- `EXPECTED_PRODUCTION_DB_NAME_SHA256`(仅在精确身份引导通过后,于远端从 `.env` 读取数据库名并转换为不含明文的比较基线;数据库名明文本身不离开远端,也不进入日志或证据文件)
- `EXPECTED_PENDING_MIGRATIONS`
- 受控 `DEPLOY_SERVER`(从已审查历史入口恢复)、`DEPLOY_REFRESH_SERVER`(exact probe 唯一识别)、systemd contract 与 live smoke 凭据(如获批提供)
## Open Questions
- 无。唯一公网环境拓扑、精确身份匹配和数据库名只读引导方式均已确认并按批准执行;最终登录态与真机边界见 AC6 和实施证据中的 `PAUSED` 记录。
## Test Plan
### 规划/本地
- `node deploy/trialrun/validate-env-contract.js`
- 新增执行 task 后运行 `python3 ./.trellis/scripts/task.py validate 08-08-production-deployment-execution`
- `node scripts/qa/check-process-contract.mjs --baseline --json`
- `node scripts/qa/audit-docs.mjs --check-registry`
- `node scripts/qa/check-trellis-governance.mjs --json`
- `node scripts/qa/check-sensitive-content.mjs`
- `git diff --check`
### 生产执行窗口
- 先验证历史身份恢复与 exact refresh probe;只读核对远端 systemd contract 和公网 origin,再在不回显值的同一进程中读取数据库名。
- 保存 target gate、13 项体检、candidate provenance、pre-status 原始输出(受控现场)及脱敏副本。
- 核对 migration manifest、`_prisma_migrations`、pending allowlist、异常集合和 `terminated` 行数;任一失败即停止。
- 仅在前置证据全部 PASS 后运行 `deploy-existing.sh`。
- 部署后保存 post-status 原始输出及脱敏副本,运行 `/ready`、build/SHA、基础/external/runtime smoke、bundle scan 和权限负向检查。
- 失败按阶段分流;不重复部署、不 reset、不 resolve、不 drop。
## Evidence Rules
- 原始 target/status stdout/stderr 只留在受控执行现场;任务 artifacts 只保存脱敏副本、短 ID、计数、状态码和引用。
- 不记录生产 origin、数据库名、SSH target、用户名、路径、连接串、凭据、业务正文或原始 payload。
- 报告目录重用时仅清理已知 evidence 文件,不能保留旧 PASS。
@@ -0,0 +1,36 @@
{
"id": "production-deployment-execution",
"name": "production-deployment-execution",
"title": "生产部署执行与验收",
"description": "在已完成生产目标与迁移门禁实现的基础上,按独立批准窗口执行生产 target/pre-status、合同终止回滚窗口检查、受控部署、post-status 与只读 readiness 验证;不执行真实 terminated 或其他业务写入,失败即停并保留脱敏证据。",
"status": "completed",
"dev_type": null,
"scope": "deployment-production-execution-and-readonly-acceptance",
"package": null,
"priority": "P1",
"creator": "shunli",
"assignee": "shunli",
"createdAt": "2026-08-08",
"completedAt": "2026-08-08",
"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/check-source-provenance.mjs",
"deploy/trialrun/production-preflight.mjs",
"deploy/trialrun/production-preflight-remote.sh",
"deploy/trialrun/deployment-contract-lib.sh",
".trellis/spec/hyb-oa/deployment-trialrun.md",
"server/prisma/migrations/20260807193000_contract_registry_termination_workspace"
],
"notes": "获批生产发布已完成:候选 termination migration 已应用,post-status 为 65 条 migration 全部 applied,运行/公网 build 与候选一致,备份合同 refresh 后聚合体检恢复 13/13。部署前后 rollback window 均为 OPEN,terminatedRows=0;未执行真实 terminated 或其他业务 API 写入。登录态只读 smoke 因仓库外管理员、员工凭据均返回 401 而 PAUSED,企业微信真机仍 PAUSED;不得据此宣称全部用户侧验收完成。原始 status 与阶段日志仅留受控现场,任务证据脱敏。",
"meta": {}
}