docs(08-08-production-deployment-validation): 补齐部署验证规范与证据边界

这个提交包含在:
2026-08-08 11:11:31 +08:00
父节点 5e2d038938
当前提交 65e22c60c7
共修改 9 个文件,包含 445 行新增和 6 行删除
+47 -2
查看文件
@@ -71,7 +71,7 @@ curl -fsS <shadow-ready-url>
- 反向代理:`caddy` 正在运行,`oa.hyinda.com` 应反代到 `.env` 中 `APP_HOST_PORT` 对应的宿主回环端口。
- 部署目录通过 `REMOTE_DIR` 显式传入。
- 远端目录当前不是 git 工作树;发布应优先从本地执行 `bash deploy/trialrun/deploy.sh`,由脚本 `rsync` 到服务器
- 已有部署优先执行 `DEPLOY_SERVER=<deploy-ssh-target> DEPLOY_REFRESH_SERVER=<forced-command-ssh-target> bash deploy/trialrun/deploy-existing.sh`;包装脚本只从受控 automatic-backup systemd unit 读取 allowlist 参数且不回显路径。两个 SSH target 是独立身份:前者按部署用户执行同步/Compose,后者只能执行 root-owned forced-command 的 `probe/refresh`。不得把 Compose `com.docker.compose.project.working_dir` 当仓库根目录,该标签可能指向 `deploy/trialrun` 配置目录。
- 已有部署优先执行 `deploy/trialrun/deploy-existing.sh`;涉及生产迁移时按 §2.0 额外提供三个 `EXPECTED_*` 进程变量。包装脚本只从受控 automatic-backup systemd unit 读取 allowlist 参数且不回显路径。两个 SSH target 是独立身份:前者按部署用户执行同步/Compose,后者只能执行 root-owned forced-command 的 `probe/refresh`。不得把 Compose `com.docker.compose.project.working_dir` 当仓库根目录,该标签可能指向 `deploy/trialrun` 配置目录。
- 远端容器编排当前实际为 `docker-compose 1.29.2`;本 runbook 命令以 `docker-compose` 为准
- 所有 Compose 命令必须显式使用同一个 `COMPOSE_PROJECT_NAME` 和 `-p`;不得依赖目录名推导项目身份,也不得写死容器名。
- 当前公网 `.env` 已确认:`APP_ORIGIN=https://oa.hyinda.com`、`COOKIE_SECURE=true`、`PUBLIC_SECURITY_HARDENING_ENABLED=true`
@@ -411,6 +411,51 @@ systemctl status hyb-oa-public-app-health.service --no-pager
建议版本化方式:
- 每次发布打 tag,例如 `trialrun-2026-02-08-01`
### 2.0 生产迁移先行门禁(08-08)
涉及生产 schema 迁移的既有部署,必须先使用 `deploy-existing.sh` 的只读
production preflight,再进入 `deploy.sh`。执行时由受控进程环境显式提供:
```bash
EXPECTED_PRODUCTION_ORIGIN=<approved-https-origin> \
EXPECTED_PRODUCTION_DB_NAME=<approved-production-db-name> \
EXPECTED_PENDING_MIGRATIONS=<approved-comma-separated-migration-ids-or-empty> \
DEPLOY_SERVER=<deploy-ssh-target> \
DEPLOY_REFRESH_SERVER=<forced-command-ssh-target> \
bash deploy/trialrun/deploy-existing.sh
```
该生产验证 wrapper 明确拒绝 `DEPLOY_ALLOW_UNPUBLISHED_COMMIT=1` 及其原因码;离线
灾备或远端故障恢复若需未发布提交,必须退出本门禁并走另行批准的恢复流程。
这里的生产目标判据与本地迁移检查相反:必须确认目标是预期生产 origin 和
数据库身份,且不是回环、本地回归库、staging 或 trialrun 身份。目标值只留在
进程环境和受控执行现场,不写入仓库、任务记录或输出;脚本只记录稳定的
PASS/FAIL code。
门禁顺序固定为:候选 commit/13 项既有部署体检 → target identity(含运行时
`DATABASE_URL` host/DB 校验)→
`prisma migrate status` 原始输出(只在受控现场保留、人工阅读其脱敏副本)→ `_prisma_migrations`
与候选 manifest 对账 → `terminated` 行计数 → `deploy.sh`。pending 集合必须
与 `EXPECTED_PENDING_MIGRATIONS` 精确一致;failed、rolled-back、diverged、
未知 migration 或无法解释的 status 立即 PAUSED。preflight 不执行
`migrate dev`、reset、db push、resolve、seed、手工 DDL 或业务写入。
从 preflight 到 `deploy.sh` 的执行窗口必须有远端 `.env`、Compose project 和数据库
身份变更冻结;无法保证冻结时应在写入前 PAUSED,不能把部署后的 status 当作并发
改目标的补救。
部署过程中 wrapper 以 `DEPLOY_STATUS_OUTPUT_MODE=quiet` 调用既有 `deploy.sh`,不把
历史 status 命令的 datasource 原文打到终端;部署完成后 wrapper 会在合同 refresh 前复用只读 preflight,并以空 pending allowlist
生成独立的 post artifact;仍须人工核对第二份 `prisma migrate status` 脱敏证据副本,确认
候选 migration 已 applied/schema up to date;原始输出只留在受控现场,退出码不能替代原始输出。权限负向
路径、合同详情只读检查和 `/ready`/smoke 必须排在任何真实终止写入之前。
回滚窗口按状态而非时间定义:部署前 `terminated` 行数为 0 时记为 `OPEN`;
第一条已提交 `terminated` 记录产生的瞬间关闭窗口。窗口关闭后不得回退到不认识
该状态的旧应用,也不自动 drop 三列,只能 roll-forward 或兼容 hotfix。本任务
不主动制造终止记录。
### 2.1 网页自动化门禁(R28)
发布前后统一使用三层网页自动化:
- `bash scripts/qa/run-web-gates.sh smoke-local`:本地/PR 快速门禁,只覆盖登录、Portal、导航、企微入口 UI。
@@ -424,7 +469,7 @@ systemctl status hyb-oa-public-app-health.service --no-pager
步骤:
1) 先做备份(见 E14 脚本)
2) 已有部署从本地仓库执行 `DEPLOY_SERVER=<deploy-ssh-target> DEPLOY_REFRESH_SERVER=<forced-command-ssh-target> bash deploy/trialrun/deploy-existing.sh`(推荐);它从受控 systemd unit 读取既有 `REMOTE_DIR`、env、Compose project 与数据目录合同,不从 Docker 标签猜路径,也不回显参数值。首次初始化或 unit 不存在时,才使用完整显式参数执行 `deploy.sh`。
2) 已有部署从本地仓库执行 `deploy/trialrun/deploy-existing.sh`(推荐;生产迁移按 §2.0 提供 target/pending 进程变量);它从受控 systemd unit 读取既有 `REMOTE_DIR`、env、Compose project 与数据目录合同,不从 Docker 标签猜路径,也不回显参数值。首次初始化或 unit 不存在时,才使用完整显式参数执行 `deploy.sh`。
- 正式使用新版入口前,root 在单独获批窗口执行 `DEPLOY_REFRESH_PUBLIC_KEY_FILE=<dedicated-public-key-file> bash deploy/trialrun/install-deployment-contract-refresh.sh install`。专用 key 不得同时存在不受限 root 授权;installer 会安装 root-owned 自包含 target 和 forced-command 行,但 installer 本身不进入免密授权。
- 新版入口先运行 `check-existing-deployment.sh` 的 13 项只读聚合体检。报告只含固定项名与 PASS/FAIL;任何失败都在 mkdir/rsync 前阻断,且前项失败不会跳过后续独立检查。
- `DEPLOY_REFRESH_SERVER` 必须是只携带专用 forced-command key 的 SSH alias。不得用 `sudo -n true` 代替该身份检查,也不得复用仍具有不受限 root 登录能力的同一 key。