文件
hyb-oa/deploy/trialrun/checklist.md
T
wangsli 35948729cf
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s
修正CEO报销财务兜底与部署护栏(07-28-ceo-finance-fallback)
2026-07-28 13:42:48 +08:00

204 行
19 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# hyb-oa 试运行发布 Checklist(E19)
> 目标:一次发布可追踪、可回归、可回滚(10 分钟内恢复到上个版本)。
## 0. 同机官网部署最小步骤
- [ ] 已确认官网继续走 `hyinda.com / www.hyinda.com`,OA 使用独立二级域名(建议 `oa.hyinda.com`)
- [ ] 服务器已执行 `bash deploy/trialrun/setup-server.sh`
- [ ] 已在服务器创建 OA 目录(默认 `/opt/hyb-oa-trialrun`)
- [ ] 已确认常规 SSH 用户为 `deploy`,系统配置用户为 `root`;当前不依赖 `shunli` 登录
- [ ] 已准备 `deploy/trialrun/.env`,并将 `APP_ORIGIN` 改为 OA 公网域名
- [ ] 已配置独立、至少 32 字节的 `AUTH_CODE_PEPPER`,且不是仓库示例占位符
- [ ] 已将 `TRUST_PROXY_HOPS` 固定为 `1`
- [ ] 已将 `COOKIE_SECURE` 设为 `true`
- [ ] 已确认 `deploy/trialrun/.env` 作为部署事实源,新增环境变量优先补到该文件
- [ ] 已尽量使用无引号 env 值;历史带引号值当前兼容,但建议逐步清理
- [ ] 已将 OA `Caddyfile` 站点块配置到 `/etc/caddy/Caddyfile`
- [ ] 已确认 Caddy 反代到 `.env` 中 `APP_HOST_PORT` 对应的宿主回环端口,并用 `header_up X-Forwarded-For {remote_host}` 覆盖客户端 XFF
- [ ] 已执行 `systemctl reload caddy`
- [ ] 已显式提供 `DEPLOY_SERVER`、`REMOTE_DIR`、绝对 `POSTGRES_DATA_DIR`、绝对 `STORAGE_HOST_DIR` 和 `DEPLOY_DATA_MODE`,并确认源码同步根目录与两个数据目录任一方向均不嵌套,再从本地执行发布入口
- [ ] 已通过 `https://<oa-domain>/ready` 或首页登录完成最小探活
- [ ] 首次上线先执行系统初始化(migration + seed),不要直接导入旧库整库备份
- [ ] 若要导数据,仅准备最小主数据:部门、员工、角色、在用资产、活跃项目、合同元数据
- [ ] 不导入历史审批单、通知历史、审计日志、聊天记录和涉密正文附件
- [ ] 已确认远端部署目录不是 git 工作树,发布优先走 Git archive 模式的 `bash deploy/trialrun/deploy.sh`
- [ ] 已确认 OA 前端静态资源由 server 容器内 `/app/client/dist` 提供,Caddy 只反代到 `127.0.0.1:<APP_HOST_PORT>`
## A. 发布前(Pre-flight)
- [ ] 已冻结唯一 `COMPOSE_PROJECT_NAME`,所有 deploy、timer、runbook 命令均显式使用 `-p`,未依赖目录名或固定容器名
- [ ] 已用 `docker compose ls` 实测旧 Compose project;停止旧栈命令使用旧 project,新栈使用批准的新 project
- [ ] 已核对 `POSTGRES_DATA_DIR` 与 `STORAGE_HOST_DIR` 指向活动数据;项目名与数据目录只在同一次获批停机窗口切换
- [ ] 正式 deploy 使用绝对数据路径;既有实例使用 `DEPLOY_DATA_MODE=existing`,PGDATA 已存在且含 `PG_VERSION`,附件目录已存在
- [ ] 已证明没有两个 PostgreSQL 实例同时挂载同一 PGDATA;任何不一致均停止发布
- [ ] 已记录活动 server/db 容器、不可变镜像 ID(及可用 repo digest)、Caddy 目标、挂载源、环境摘要,不记录 env 正文
- [ ] 已现场只读冻结成功 migration 数量和关键业务表逐表计数基线;当前生产证据为 61 项、本地为 62 项,本次待上线只应新增 1 项,现场结果不符立即停止
- [ ] 确认当前版本已打 tag:`git tag -a trialrun-YYYYMMDD-NN -m "trialrun release"`
- [ ] 本地门禁:`./scripts/check-minimal.sh`
- [ ] 部署契约校验:`node deploy/trialrun/validate-env-contract.js`
- [ ] 网页 smoke 门禁:`bash scripts/qa/run-web-gates.sh smoke-local`
- [ ] 网页 full 门禁(建议发版前执行):`bash scripts/qa/run-web-gates.sh full-local`
- [ ] 全量回归(建议):`cd server && RUN_AUDIT_REGRESSION=1 RUN_OCR_REGRESSION=1 ./scripts/regression-all.sh`
- [ ] 备份(DB + 文件存储):
- [ ] `set -a && source deploy/trialrun/.env && set +a`
- [ ] `mapfile` 读取 `docker-compose -p "$COMPOSE_PROJECT_NAME" ... ps -q db` 后,容器数量严格等于 1
- [ ] `DB_TOOL_MODE=docker DB_CONTAINER="$DB_CONTAINER" STORAGE_DIR_HOST=<verified-active-storage-host-directory> SOURCE_IMAGE_DIGEST=<verified-sha256-image-id> ./scripts/backup-all.sh`
- [ ] `DB_CONTAINER` 或 `STORAGE_DIR_HOST` 缺失时备份硬失败,没有回退到历史容器名或仓库默认附件目录
- [ ] manifest 至少含真实 `buildSha` 或活动 `sourceImageDigest`;旧实例无 build SHA 时未填写待部署提交
- [ ] 同一批次包含 `database.dump`、`storage.tar.gz`、`manifest.json`,两份 SHA-256 校验通过
- [ ] 完整批次已复制到获批异机/异盘,并记录保留周期
- [ ] 同批次已在隔离影子目标恢复:migration 数、关键业务表逐表计数、附件普通文件数均与冻结基线一致,兼容镜像 `/ready` 通过
- [ ] 自动异机备份(未完成远端门禁前保持未勾选):
- [ ] **EXTERNAL_GATED**:用户已在独立企微运维群创建机器人,并分别把 Webhook 写入生产、ARM 的 root-only `0600` 文件;未向 Codex、仓库、普通 `.env`、日志或聊天提供正文
- [ ] **EXTERNAL_GATED**:ARM 已获批安装 rsync、密码锁定专用账号、forced-command key、独立 inbox/archive/weekly 和 26 小时 dead-man;普通 shell、rsync sender/read/delete 均被拒绝
- [ ] **EXTERNAL_GATED**:生产专用私钥为 `0600`,known-hosts 指纹已人工核对,安装器显式绑定活动 project、PGDATA、附件、完整/短构建 SHA、备份/状态目录、远端身份和 Webhook 文件路径
- [ ] `install-automatic-backup-timer.sh install` 只安装 disabled units;首批真实批次及 ARM manifest/SHA-256 核对通过后才显式执行 `enable`
- [ ] 生产 daily/watchdog/drill-reminder 与 ARM dead-man 四组 timer enabled/active,各 oneshot service 当前 inactive;未通过只调用机器人发送入口来伪造验证
- [ ] 最近一次异机成功不超过 24 小时;生产自动日批次为 7 个,ARM 自动日批次为 7 个、周检查点为 4 个,历史手工批次未被轮转
- [ ] **EXTERNAL_GATED**:已真正停止一次源端 timer 或故意破坏一次传输,ARM dead-man 仅产生一条脱敏企微告警;恢复 finalize 后收到一次恢复通知,同一失败重复运行已降噪
- [ ] 若调整过 `BACKUP_RUN_AT`,已人工确认调整后的次日 UTC 分桶和 26 小时窗口,未把一次预期空窗误判为长期故障
- [ ] 本季度已由第二名内部人员按 runbook 首部恢复卡在 ARM 隔离目标完成影子恢复走查;timer 未自动恢复生产
- [x] ARM 公网应用健康监控(2026-07-27 自动链路与群内收件验收通过):
- [x] 本地 `bash scripts/qa/run-app-health-monitor-contract.sh` 通过,且自动备份告警契约回归通过
- [x] 已批准 ARM systemd 写入,并确认复用 root-owned `0600` webhook;安装的是已通过自动备份回归的共用 helper 修订,未复制或输出 webhook 正文
- [x] `install-offhost-app-health-monitor.sh install` 只安装 disabled units;应用健康 state/alert state 与备份 dead-man 完全独立
- [x] 手工 oneshot 健康执行通过,输出不含地址、路径、响应 payload、凭证或原始异常
- [x] 已执行 `enable`,timer 为 enabled/active,oneshot 最近执行成功,下一次运行不超过 5 分钟
- [x] 持续失败演练中第一次仅 pending、第二次发送、重复失败降噪、正式恢复发送、重复健康降噪;用户确认群内收到一条故障和一条恢复通知
- [x] 已记录剩余风险:ARM 自身失效会使探测失声,首版没有第三方 dead-man
- [ ] 停机复制方案已冻结:旧 PostgreSQL 完全停止后复制到空新目录并保留所有权/权限/链接/扩展属性;旧目录原位保留且观察期内不再写入
## B. 发布(Deploy)
- [ ] 本地工作树 clean,目标 commit 已进入 upstream;部署日志中的 commit 与 `/ready.data.buildSha` 一致
- [ ] **EXTERNAL_GATED**:操作人已在边缘暂停 `/api/auth/forgot-password`、`/api/auth/reset-password`、`/api/auth/email-login/code`、`/api/auth/email-login/verify`;部署脚本不会自动修改边缘配置,未人工确认四端点关闭不得发布,配置预检、迁移、新应用和 smoke 全部通过后才人工恢复
- [ ] 标准路径已成功刷新 upstream;若因受控离线恢复使用 unpublished override,日志包含批准原因码和 `UNPUBLISHED-OVERRIDE` 标识
- [ ] 标准发布通过 `bash deploy/trialrun/deploy.sh` 执行;脚本自动注入完整后端 SHA 与 12 位前端 SHA,并完成构建、init 与 smoke
- [ ] 发布命令显式携带批准的新 project、两个绝对数据路径和 `DEPLOY_DATA_MODE=existing`;未依赖 `.env.example` 相对默认值
- [ ] 若为首次初始化或受控应急而直接使用 Compose,已显式设置 `BUILD_SHA=<approved-full-commit>` 与 `VITE_BUILD_SHA=<matching-12-char-commit>`;未使用 `unknown`
- [ ] 探活:
- [ ] 已用 `docker-compose -p "$COMPOSE_PROJECT_NAME" ... port server 3000` 解析实际宿主端口,并对该端口执行 `/ready`,未依赖当前 shell 中可能缺失的 `APP_HOST_PORT`
- [ ] `/ready` 的 JSON 在 server 容器内用 Node 核对 build SHA;宿主机无需 Node.js
- [ ] Runtime smoke:
- [ ] `docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env exec -T server node src/scripts/smoke-trialrun-runtime.js`
- [ ] 若 runtime smoke 报 `announcements_v1` 或 `global_search_v1` 为 `force_off`,已记录为发布阻塞,不忽略通过
- [ ] external deps smoke:
- [ ] `docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env exec -T server node src/scripts/smoke-external-deps.js`
- [ ] 若 external deps smoke 通过,已确认通过前提不是“企微关闭 + OCR mock”误通过
- [ ] 若启用腾讯 OCR,已脱敏核查 `OCR_PROVIDER=tencent`、`TENCENT_SECRET_ID` 长度、`TENCENT_SECRET_KEY` 长度和 `TENCENT_OCR_REGION`
- [ ] 若真实 OCR 未配置,已使用 `OCR_PROVIDER=off` 而不是 mock,并确认实际作业拓扑中的 server/worker 已重建;真实票据由财务查看 PDF 原件人工核对
- [ ] 若本次包含前端体验修复,已在容器内确认 `/app/client/dist` 含关键 test id 或公网 asset hash 已更新
- [ ] 公网 bundle 扫描确认包含匹配的 12 位构建标识,且不包含完整提交 SHA
- [ ] 登录验证已由 live smoke 与 release gate 覆盖;仅首次接入或第三方后台配置变更时补做人工确认
## C. 发布后(Post-check)
- [ ] 公网 live smoke(只读,不做写操作):`bash scripts/qa/run-web-gates.sh smoke-live`
- [ ] 若本地已配置 `LIVE_SMOKE_*`,确认 `bash deploy/trialrun/deploy.sh` 输出中包含 live smoke 结果
- [ ] 已确认 release gate / live smoke 结果为通过;关键链路不再作为每次发布的手工必做项
- [ ] 若自动备份 units 已安装,已脱敏核对其冻结的 Compose project、build、PGDATA、附件宿主挂载与当前唯一活动 server/db;任一项变化均已用现有安装器按完整合同刷新,三个 timer 仍为 enabled/active,未直接手改 unit
- [ ] 查看 server 日志(无异常错误风暴):
- [ ] `docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env logs --tail=200 server`
- [ ] 已确认 smoke 账号为独立账号(`smoke-admin` / `smoke-employee`),且不参与真实业务
- [ ] 已确认 live smoke 仅检查 `/ready`、登录、Portal、项目、公告、个人设置和企微配置状态
- [ ] 若涉及票据/OCR,已执行真实 OCR 辅助复核或记录跳过原因:`PLAYWRIGHT_MANUAL=1 PLAYWRIGHT_OCR_LIVE=1 ... npx playwright test tests/e2e/manual/live-invoice-ocr.spec.ts`
- [ ] 若涉及移动端体验,已用 Playwright 390px 视口检查 `/invoice-pool`、`/reimbursements`、`/mobile/capture`,确认无桌面表格、无出屏按钮,上传 input 的 `multiple` 与预期一致
## D. 回滚(Rollback)
> 原则:如果涉及迁移,最快回滚通常是 “恢复备份 + 回到上个 tag”。
- [ ] **EXTERNAL_GATED**:若回滚目标仍包含旧认证挑战逻辑,人工确认边缘继续关闭 `/api/auth/forgot-password`、`/api/auth/reset-password`、`/api/auth/email-login/code`、`/api/auth/email-login/verify`;脚本不会自动改边缘,未确认不得回滚开放,直到安全版本恢复
- [ ] 停止 server:`docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env stop server`
- [ ] 恢复 DB/存储属于获批事故操作;当前脚本仅允许 `RESTORE_SCOPE=shadow`,不得绕过后直接指向真实目标
- [ ] 已先在独立数据库和空附件目录完成同批次影子恢复,数据库名精确确认且 manifest/SHA-256 匹配
- [ ] 回到上个版本:`git checkout <previous-tag>`
- [ ] 重启并探活:
- [ ] `docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server`
- [ ] 已重新用 `docker-compose -p "$COMPOSE_PROJECT_NAME" ... port server 3000` 解析实际宿主端口并执行 `/ready`
- [ ] 若仅为 OCR 应急降级,已把远端 `.env` 的 `OCR_PROVIDER` 切为 `off`,按实际作业拓扑重建 server/worker,并记录人工核对责任;不得把 mock 用于真实票据
## E. R12-lite 试点切换检查
- [ ] 已执行 `cd server && DATABASE_URL=<local-postgresql-url> npm run seed:config`
- [ ] 运行环境已打开 R12-lite 所需 env 开关
- [ ] 功能开关页已将 `budget_precheck_v1`、`payment_batch_v1`、`portal_home_v1`、`global_search_v1`、`announcements_v1`、`workflow_definition_admin_v1`、`cutover_console_v1` 切到 `inherit` 或 `force_on`
- [ ] 旧 OA 主数据 CSV 已完成 dry-run,结果为 `dry_run_ok`
- [ ] `docs/migration-mapping.md` 已按试点范围人工核对
- [ ] 旧 OA 新单停用时间已公告,未完结事项已人工清空或冻结
- [ ] 仅按部门 / 法体 / 域粒度创建 `CutoverScope`
- [ ] 先开 `portal`,再逐域灰度打开业务入口
- [ ] Portal、待办、预算预校验、支付批次、公告、搜索已在试点部门可用
- [ ] 回退方案已演练:关闭 `CutoverScope` + 恢复旧入口
## F. R17 小团队上线检查
- [ ] 工作区模式已确认为 `small_team_internal`
- [ ] 主导航已收敛为 `Portal / 团队 / 请假 / 报销 / 资产 / 周报 / 待办 / 公告 / 项目`
- [ ] 考勤 / 采购 / 差旅 / 预算 / 支付批次 / AP 运营 / ERP / AI 已降级为隐藏或管理员备用入口
- [ ] 已打开 `portal_home_v1`、`announcements_v1`、`presence_board_v1`、`weekly_report_v1`、`employee_lifecycle_v1`、`todo_board_v1`
- [ ] 员工账号、档案、部门、直属上级已补齐
- [ ] 资产台账与员工领用关系已核对
- [ ] 至少 1 条公告已发布并在 `Portal` 可见
- [ ] 至少 1 次入职/离职/资产交接已演练
- [ ] 全员已知晓每周周报和今日状态使用要求
- [ ] 已建立连续 4 周观测记录方式(建议直接使用 `deploy/trialrun/observation-template.md`)
## G. R66 小团队试点收敛检查
- [ ] 普通员工账号登录后,主导航只出现高频主路径,不出现考勤、采购、差旅、预算、AP、ERP、AI。
- [ ] 经理或老板账号登录后,Portal 首屏优先看到审批、团队状态、周报缺口、需协助员工和项目风险。
- [ ] 管理员账号仍可进入工作区设置、人员、组织、流程定义、系统设置、功能开关和审计日志。
- [ ] 已确认不删除备用模块数据;恢复某模块只通过工作区设置或功能开关完成。
- [ ] 已生成 `week-01.md` 到 `week-04.md` 的观测计划,至少完成第 1 周导出。
- [ ] 已完成 DB + 文件存储同批次备份,manifest/SHA-256 通过并复制到异机/异盘。
- [ ] 已运行 `./scripts/qa/run-backup-restore-drill.sh` 完成本地合成数据演练。
- [ ] 已在获批临时环境完成真实格式影子恢复,恢复后 `/ready` 通过。
- [ ] 已核对管理员账号清单和 smoke 账号,自动化不使用真实业务账号。
- [ ] 已执行 `cd server && npm audit --omit=dev --audit-level=high` 并完成高危/严重漏洞评审;若仍失败,不进入公网正式试点。
- [ ] 已执行 `bash scripts/qa/run-release-gate.sh r66-trialrun`;若数据库或公网账号条件不足,已记录手工替代步骤。
## R18-R22 上线补充检查项
- [ ] `APP_ORIGIN` 已切到公网 HTTPS 域名
- [ ] `COOKIE_SECURE=true`
- [ ] `PUBLIC_SECURITY_HARDENING_ENABLED=true`
- [ ] `OPENAPI_DOCS_ENABLED=false`
- [ ] 企业微信应用已配置工作台入口和回调地址
- [ ] 至少 1 个管理员已完成企微绑定
- [ ] 项目模块、项目关联、合同台账已在试点环境可见
- [ ] 涉密正文与附件未上传到 OA
- [ ] 企业微信提醒仅包含脱敏标题 / 编号 / deeplink
- [ ] 员工可从企业微信进入 `Portal`,并继续完成周报、待办、项目更新
## R23 试点收口 Checklist
- [ ] `project_communication_log_v1` 已开启并可在项目详情页使用
- [ ] `project_closure_checklist_v1` 已开启并可自动 bootstrap
- [ ] 管理员已知晓“软约束”策略,不把收口面板误认为硬阻断审批
- [ ] 员工已知晓“24h 回填”规则
- [ ] 至少 1 个项目已验证:沟通回填 -> Portal 待跟进统计生效
- [ ] 至少 1 个项目已验证:收口清单 -> Portal 待收口统计生效
- [ ] 至少 1 条涉密合同或资料引用已验证搜索边界
- [ ] 每周五复盘模板中已加入“聊天/表格补流程案例”字段
## R27 企业微信正式接入与交互优化检查
- [ ] 登录页已区分普通浏览器与企业微信 WebView 场景
- [ ] 企业微信工作台入口 URL 已配置为 `https://oa.hyinda.com/api/auth/wecom/login?returnTo=%2Fmobile`
- [ ] OAuth 回调地址已配置为 `https://oa.hyinda.com/api/auth/wecom/callback`
- [ ] `deploy/trialrun/.env` 已补齐 `WECOM_ENABLED / WECOM_ENTRY_ENABLED / WECOM_OAUTH_ENABLED / WECOM_CORP_ID / WECOM_AGENT_ID / WECOM_AGENT_SECRET / WECOM_REDIRECT_URI / WECOM_ALLOWED_RETURN_HOSTS`
- [ ] 至少 1 个管理员已在 `个人设置` 成功完成企微绑定
- [ ] 已验证:已绑定员工从企业微信工作台进入后可直接落到 `/mobile`
- [ ] 已验证:未绑定员工会回退到登录页并看到明确绑定提示
- [ ] 已验证:Portal 首屏第一排卡片顺序符合员工 / 管理员视角设计
- [ ] 已验证:备用模块默认折叠,不干扰主导航
- [ ] 已验证:企业微信通知仍保持脱敏,不包含涉密正文与金额明细
## R29 前端设计系统与工作台体验检查
- [ ] 主导航已稳定收敛为 `Portal / 团队 / 请假 / 报销 / 资产 / 周报 / 待办 / 公告 / 项目`
- [ ] 管理入口已单独分区,`备用模块` 默认折叠
- [ ] 登录页在普通浏览器与企业微信 WebView 场景下都只保留一个主 CTA
- [ ] 登录页未再展示测试账号暗示,错误提示贴近操作区
- [ ] `Portal` 员工第一屏已优先显示:待办 / 今日状态 / 周报 / 项目
- [ ] `Portal` 管理第一屏已优先显示:审批 / 团队状态 / 周报缺口 / 需协助员工
- [ ] `个人设置` 不再直出 `部门 ID / 上级 ID` 等内部标识
- [ ] `topbar` 仅在企业微信 WebView 场景显示“当前通过企业微信进入”轻提示
- [ ] 已执行 `cd client && npm run typecheck`
- [ ] 已执行 `cd client && npm run build`
- [ ] 已执行 `bash scripts/qa/run-web-gates.sh smoke-local`
- [ ] 发版前建议执行 `bash scripts/qa/run-web-gates.sh full-local`