10 KiB
生产部署执行与验收:设计说明
1. 设计边界
这是一次受控运维执行,不是新的产品开发。已归档的 08-08-production-deployment-validation 提供部署 tooling、target gate、迁移 reconciliation 和报告脱敏实现;本 task 只负责在明确批准的生产窗口调用它们、读取结果、分流失败并形成证据。
不修改应用代码、Prisma schema、生产环境变量或业务数据模型;本次规划修订允许仅调整部署/QA tooling 及其安全测试。若运行中发现实现缺陷,保留现场并另建 task,不在部署窗口临时修复。
拓扑事实:没有独立预发环境。历史名称为 hyb-oa-trialrun 的既有部署就是承载公网 OA 的唯一环境;trialrun 只是遗留命名。当前 target gate 的名称启发式与真实拓扑不一致,必须先校准为精确身份匹配,再连接生产。
2. 状态序列与硬门禁
候选发布来源
-> 历史身份恢复 + 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 只读身份引导
- 从已审查历史提交
34181e3恢复既有DEPLOY_SERVER,从首次显式冻结 Compose project 的提交5e2d03893826恢复COMPOSE_PROJECT_NAME;不输出任何字面值。两份脚本里的REMOTE_DIR只是可被显式输入覆盖的 fallback,不作为生产目录基线。 - 从本机受控 SSH 配置无回显探测 refresh 候选,只有 exact forced-command
probe唯一 PASS 时才采用;零个或多个候选均停止。 - 使用恢复的部署目标只读读取受控 systemd contract,先校验六项存在、路径/Compose 格式、
REMOTE_ENV_FILE必须位于 contractREMOTE_DIR的固定相对位置、Compose file 固定且 project 与历史基线一致;此时不把历史目录 fallback 与 contract 比较。 - 从 OPS7 当前公网检查基准取得预期 HTTPS origin;以 contract 仅作为定位信息,只读比较远端
.env和运行容器的APP_ORIGIN。两个独立取样均一致后才确认目标主机,并把 contractREMOTE_DIR冻结为当前进程EXPECTED_REMOTE_DIR;origin 不一致时不得采信或输出该目录。 - 身份确认复核模式在第 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 顺序固定为:
- 精确比较执行时恢复的部署目标、远端目录、Compose project、受控 contract、公网 origin 和引导出的数据库身份;不再因合法身份值含
trialrun而拒绝。 - 继续拒绝 HTTPS 以外、回环/零地址、local regression、staging、shadow、测试占位符,以及任何精确身份组不一致。
- 检查唯一 server/db 容器、运行时
DATABASE_URLhost/database、当前数据库名、NODE_ENV=production和COOKIE_SECURE=true。 - 执行
npx prisma migrate status,在 marker 内输出原始结果。 - 只读查询
_prisma_migrations,计算 applied、异常和 pending 集合,与候选 manifest 和批准 allowlist 精确对账。 - 先以
_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、窗口和冻结全部通过,才调用:
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。