文件
hyb-oa/deploy/trialrun/runbook.md
T

47 KiB
原始文件 Blame 文件历史

hyb-oa 试运行 Runbook(单机)

目标:按本文档在一台新机器完成 启动 → 迁移 → 初始化管理员 → 验收 → 备份 → 恢复 → 回滚 演练。

紧急恢复卡(双人密钥托管版)

适用情形:生产主机不可恢复、环境文件丢失、数据库或附件损坏,且已由负责人批准进入事故恢复。系统管理员负责技术操作,公司负责人负责密钥取用复核;preserve_original 类必须确保二人均能从仓库外共享密码库或加密封存副本取用。不得在聊天、工单、命令历史或报告中粘贴凭证、业务正文、真实目录或原始日志。

A. 先冻结现场

  1. 暂停外部入口和新业务写入,不删除故障主机、旧容器、旧数据目录或 ARM 归档;记录故障时间、最近成功批次编号类别、恢复负责人和复核人。
  2. 若旧容器仍在运行,只允许按 .env.example 键名白名单提取缺失配置并原子写入 root-only 临时文件;不输出值,不把“容器还活着”当正式恢复机制。
  3. 在 ARM 的只读归档中选择最近一个 complete 批次,复核三文件、manifest、SHA-256 与来源标识;先复制到隔离恢复工作区,不直接对归档原件操作。

B. 重建环境配置

先运行 node deploy/trialrun/validate-env-contract.js,确认 .env.example 的全部键已在 env-recovery-classification.json 中恰好分类一次。恢复配置只按以下五类处理:

类别 丢失影响与恢复方式 恢复后验证
template_rebuild 端口、路径、开关、TTL、公开地址等,从 .env.example、本 runbook 和已批准基础设施合同重填;不能从旧文档复制已过时值 配置预检、Compose config、路径/端口和功能开关基线
external_reissue 企微、腾讯云、OIDC、SMTP、S3、ERP、AI 等外部标识或凭据,由对应平台管理员重新获取或签发;不从数据库、日志或 Git 反推 external deps smoke、对应平台只读配置核对,必要时人工真机验证
direct_rotate JWT、验证码 pepper、自建 MinIO 管理口令、试运行管理员口令等生成新值;必须接受全部会话、验证码或目标账号凭据失效 新旧会话边界、登录/重置、runtime smoke;旧值不得继续使用
downtime_reset PostgreSQL 角色口令无法从 pg_authid 反推明文;保留 PGDATA 时按下述隔离停机步骤重置,不称为“找回” 原认证配置字节级恢复、数据库不暴露宿主端口、migration status、应用登录与备份
preserve_original 最高风险。FIELD_ENCRYPTION_KEY 原值丢失会使既有银行账号、身份证号和通知渠道密文永久不可读;只能从双人可取的共享密码库/加密封存副本恢复,禁止生成新值冒充恢复成功 用合成探针验证加解密能力,并由授权页面抽样确认既有密文仍可解;不得输出明文

分类描述恢复方式,不代表配置值可以公开;例如 DATABASE_URL 在数据库口令重置后按模板重建,但仍是只允许进入 root-only 环境文件的 Secret。TRIALRUN_ADMIN_PASSWORD 对既有管理员不会因改 env 自动生效,必须通过现有受控密码重置入口轮换;新初始化时才由 env 提供初始值。

FIELD_ENCRYPTION_KEY 当前密文只有 v1 格式版本,没有 key id 或 keyring;在独立轮换设计完成前必须保留原值。若封存副本无法取得,立即停止恢复并上报数据不可读影响,不要求员工重新提交敏感资料前先宣称系统已恢复。

数据库口令重置仅在 PGDATA 尚存、外部入口已暂停且负责人批准的停机窗口执行:

  1. 停止 server、worker、init 和数据库容器,确认没有进程仍挂载该 PGDATA;复制并校验当前 pg_hba.conf,记录校验状态而非文件内容。
  2. 使用相同 PostgreSQL 大版本在隔离容器中挂载该 PGDATA;不得发布 TCP 端口。优先使用 PostgreSQL 单用户/本地 socket 能力重置目标角色口令。
  3. 如环境限制必须临时调整认证,只允许增加本地 socket 的临时 trust,禁止任何 host ... trust,并通过交互式 \password <role> 输入新值,避免把口令写入命令行和日志。
  4. 停止隔离数据库,把 pg_hba.conf 按备份逐字节恢复并复核校验;同步更新恢复环境中的 POSTGRES_PASSWORD 与派生的 DATABASE_URL,文件保持 root-only。
  5. 按正常 Compose 合同启动数据库与应用,证明数据库无宿主发布端口、认证配置无临时改动、migration status、/ready、runtime/external smoke、登录和自动备份均通过。任一项失败都保持外部入口关闭。

C. 恢复数据与应用

在获批的新主机/隔离目标依次执行以下三个入口;尖括号值只从受控运维配置取得,不写回本文:

RESTORE_SCOPE=shadow RESTORE_CONFIRM_DATABASE=<shadow-db> <explicit-db-env> \
  ./scripts/restore-db.sh <verified-batch>/database.dump
RESTORE_SCOPE=shadow ./scripts/restore-storage.sh \
  <verified-batch>/storage.tar.gz <empty-shadow-storage>
curl -fsS <shadow-ready-url>

复核 /ready、运行构建标识、成功 migration 数、组织/权限/报销/付款等关键表计数、附件普通文件数及 DB/附件关联;任何一项不一致都停止切换。

真实生产恢复仍是 EXTERNAL_GATED:另建或更新获批 Trellis task,冻结新数据目录、env 外置路径、回滚点和停机窗口后才切换。恢复后保留 ARM 归档和故障主机原数据,记录 RTO/RPO 与结论。

系统管理员与公司负责人必须每季度共同确认 preserve_original 封存项仍可由二人分别取用;至少一名操作人按本卡在 ARM 隔离目标完成人工影子恢复。只记录角色、时间和 PASS/FAIL,不记录密码库地址、账号、恢复码或 Secret。自动 timer 只提醒,不自动恢复。

0A. 如果官网 hyb-web 已在同机运行(推荐方案)

  • 官网继续保持 hyinda.com / www.hyinda.com 静态托管,不要改官网目录和配置。
  • OA 新增独立二级域名,例如 oa.hyinda.com。
  • OA 部署目录由受控环境中的 REMOTE_DIR 指定,不在仓库固化机器路径。
  • OA 推荐访问方式:Caddy(443) -> 127.0.0.1:<APP_HOST_PORT> -> docker-compose(server:3000)。
  • Caddy 必须覆盖客户端传入的 X-Forwarded-For。OA 站点的 reverse_proxy 中应设置 header_up X-Forwarded-For {remote_host},不得保留不可信客户端链。
  • 首次初始化时:
    1. 服务器以 root 执行 bash deploy/trialrun/setup-server.sh
    2. 手工创建 deploy/trialrun/.env
    3. 手工更新 OA 的 Caddyfile 站点块并确认 TLS 正常
    4. 本地通过受控配置显式提供 DEPLOY_SERVER、REMOTE_DIR、绝对数据目录和 DEPLOY_DATA_MODE,再执行 bash scripts/qa/run-release-gate.sh all(或至少执行 bash deploy/trialrun/deploy.sh)

0B. 历史部署基线(基础设施信息已脱敏)

  • SSH 目标、部署账号和机器地址只保存在受控运维配置中,通过 DEPLOY_SERVER 显式传入。
  • 反向代理: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 配置目录。
  • 远端容器编排当前实际为 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
  • 企业微信与 OCR 已进入公网试运行配置。核查时只检查开关、长度和 smoke 结果,不在日志、Issue 或文档中输出 secret 明文。

0. 前置条件

  • Docker + Docker Compose(v1/v2 任一可用)
  • 机器时间正确(影响 token / cron / OCR)

1. 首次启动(推荐:单域托管)

1.1 准备环境变量

cd /path/to/hyb-oa
cp deploy/trialrun/.env.example deploy/trialrun/.env

必须修改:

  • JWT_SECRET
  • AUTH_CODE_PEPPER(独立随机秘密,至少 32 字节,不得沿用示例占位符或 JWT secret)
  • TRUST_PROXY_HOPS=1(当前单跳 Caddy/Nginx 部署固定为 1)
  • APP_ORIGIN(建议与对外域名一致,例如 https://oa.example.com)

环境变量约束:

  • deploy/trialrun/.env 现在是 trialrun 部署事实源,server / worker / init 统一通过 env_file 注入。
  • BUILD_SHA / VITE_BUILD_SHA 由发布入口按已批准提交临时注入,不写入长期 .env;前者供后端运行时核对,后者只允许 12 位短 SHA 进入公网 bundle。
  • 推荐使用无引号值,例如 COOKIE_SECURE=true、PORT=3000。
  • PORT=3000 是容器内端口;APP_HOST_PORT 是宿主回环端口。发布脚本通过 Compose 动态解析后者,Caddy 必须指向相同值。
  • POSTGRES_DATA_DIR 和 STORAGE_HOST_DIR 是宿主数据事实源。示例相对值仅供本地直接 Compose;正式 deploy.sh 要求在命令环境中显式提供两个绝对路径和 DEPLOY_DATA_MODE=existing|initialize。
  • existing 要求 PGDATA 已存在且包含 PG_VERSION,附件目录也必须已存在;initialize 才会创建目录。生产环境更换项目名或任一数据目录只能在同一次获批停机窗口完成,禁止两个 PostgreSQL 实例同时挂载同一 PGDATA。
  • 历史上已经写成带引号的 .env 当前仍兼容,但建议后续逐步改成无引号。
  • 正式部署前可先执行 node deploy/trialrun/validate-env-contract.js 做静态契约检查。
  • EXTERNAL_GATED:涉及认证安全迁移时,发布或回滚前必须由操作人先在边缘暂停 /api/auth/forgot-password、/api/auth/reset-password、/api/auth/email-login/code、/api/auth/email-login/verify 四个 challenge 端点,再执行“新镜像配置预检 -> additive migration -> 新应用切换”。部署脚本不会自动修改边缘配置;未人工确认四端点已关闭不得发布或回滚开放。仅在安全版本及 smoke 全部通过后人工恢复;若回滚到旧认证代码,四端点必须继续关闭。

作业系统建议:

  • 试运行默认 JOB_DRIVER=pgboss(基于 Postgres 的队列调度,单机/多进程语义更稳)
  • 如需快速回滚作业调度:将 JOB_DRIVER 改回 cron
  • 可选 Worker 拆分(F20):将 JOBS_ENABLED=false 并启用 --profile worker(worker 进程会强制跑作业)

存储建议:

  • 默认 STORAGE_DRIVER=local(票据文件落到 deploy/trialrun/data/storage)
  • 可选启用 MinIO(F21):STORAGE_DRIVER=s3 并启用 --profile minio

1.2 启动数据库与服务

cd /path/to/hyb-oa
node deploy/trialrun/validate-env-contract.js
export BUILD_SHA="<approved-full-commit>"
export VITE_BUILD_SHA="<matching-12-char-commit>"
export COMPOSE_PROJECT_NAME="<approved-compose-project>"
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d db
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env build init
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env run --rm init
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server

以上直接 Compose 命令仅用于首次初始化或受控应急;正式发布优先使用 deploy/trialrun/deploy.sh,由脚本从 clean、已进入 upstream 的提交自动注入两个构建标识。缺少任一标识时 Compose 必须拒绝解析。

可选:启用 MinIO(对象存储,F21):

cd /path/to/hyb-oa
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env --profile minio up -d minio minio_init

可选:启用 Worker 进程(推荐在试运行/多进程模式使用):

cd /path/to/hyb-oa
# 1) 在 deploy/trialrun/.env 中设置:JOBS_ENABLED="false"
# 2) 启用 worker profile(会额外启动 worker 容器)
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env --profile worker up -d --build server worker

1.3 初始化(迁移 + 配置 seed + 试运行管理员)

cd /path/to/hyb-oa
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env run --rm init

说明:

  • init 是一次性任务:migrate:deploy + seed:config + seed:trialrun。seed:config 仅在审批规则表为空时创建默认三档;已有任意审批规则时必须全部保留,生产规则变更另走获批的原子事务。
  • 初始化必须发生在依赖新增列的新 server 切换之前;正式部署脚本会先用新 init 镜像校验生产配置并执行迁移,再替换 server。
  • 首次创建试运行管理员前必须通过受控 .env 提供 TRIALRUN_ADMIN_PASSWORD;seed 不生成或输出口令,登录标识也不进入部署日志。
  • 正式启用前,推荐先保持“空基线 + 默认种子”,不要直接导入开发库、测试库或旧 OA 整库备份。
  • 如确需导入,仅导入最小必要主数据:部门、员工、角色、在用资产、活跃项目、合同元数据。
  • 历史审批单、通知历史、审计日志、聊天记录以及涉密正文/附件继续保留在原系统、文件盘或离线档案中。

1.4 验收

SERVER_HOST_PORT="$(docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env port server 3000)"
SERVER_HOST_PORT="${SERVER_HOST_PORT##*:}"
curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/health"
curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/metrics" | head
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

说明:

  • deploy/trialrun/deploy.sh 会先拒绝脏工作树、确认提交已进入 upstream,再从该提交生成 Git archive;随后自动执行环境契约、带 build SHA 的 /ready 核对、external deps smoke 与 runtime smoke。
  • 标准发布会先刷新当前 branch 的 upstream remote-tracking ref,再判断目标提交是否已发布。
  • 只有明确的离线灾备或已批准远端故障场景才可设置 DEPLOY_ALLOW_UNPUBLISHED_COMMIT=1,并同时提供 allowlist 原因码 DEPLOY_UNPUBLISHED_REASON=offline-disaster-recovery|remote-outage-approved|controlled-local-recovery;部署日志会醒目标记 override,该开关不放宽 clean-tree 要求。
  • 若本地存在完整 LIVE_SMOKE_* 环境变量,部署脚本会继续执行 client npm run test:e2e:live,用于公网只读 smoke。
  • 推荐直接执行 bash scripts/qa/run-release-gate.sh all,让 static / local smoke / full-local / deploy / live smoke 形成单一放行链。
  • 若再出现“数据库开了但功能被环境硬关”的情况,部署会直接失败,不再等到手工点页面才发现。
  • 2026-05-04 公网只读巡检显示:smoke-external-deps.js 可以通过,但前提是 WECOM_* 未启用且 OCR_PROVIDER=mock;smoke-trialrun-runtime.js 当前会因 announcements_v1 为 force_off 失败。
  • 2026-05-05 公网复核显示:OCR_PROVIDER=tencent 可用,真实发票上传后进入 available,provider 为 tencent;后续发布不能再把 OCR 回退到 mock 而不记录原因。

浏览器访问:

  • docker-compose -p "$COMPOSE_PROJECT_NAME" ... port server 3000 返回的宿主回环端口(单域托管前端)。

1.4.1 腾讯 OCR 试运行核查

远端 .env 是 OCR 配置事实源,禁止提交或复制密钥明文。只做以下脱敏检查:

cd /opt/hyb-oa-trialrun
set -a
source deploy/trialrun/.env
set +a

printf 'OCR_PROVIDER=%s\n' "$OCR_PROVIDER"
printf 'TENCENT_SECRET_ID length=%s\n' "${#TENCENT_SECRET_ID}"
printf 'TENCENT_SECRET_KEY length=%s\n' "${#TENCENT_SECRET_KEY}"
printf 'TENCENT_OCR_REGION=%s\n' "$TENCENT_OCR_REGION"

期望:

  • OCR_PROVIDER=tencent
  • TENCENT_SECRET_ID 和 TENCENT_SECRET_KEY 长度大于 0
  • TENCENT_OCR_REGION 与腾讯 OCR 开通地域一致,例如 ap-beijing

发布后 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
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

真实 OCR 辅助复核(本地执行,不提交发票文件):

cd client
PLAYWRIGHT_MANUAL=1 \
PLAYWRIGHT_OCR_LIVE=1 \
PLAYWRIGHT_OCR_LIVE_FILE=/path/to/invoice.pdf \
E2E_BASE_URL=https://oa.hyinda.com \
npx playwright test tests/e2e/manual/live-invoice-ocr.spec.ts --workers=1 --reporter=list --timeout=360000

真实 OCR 不可用时降级到 off;mock 仅用于本地或明确隔离的合成测试:

  1. 在远端 .env 将 OCR_PROVIDER=tencent 改为 OCR_PROVIDER=off。
  2. 按现场作业拓扑重建服务:JOBS_ENABLED=true 且无独立 worker 时只重建 server;已有独立 worker 时同时重建 server 和 worker,不能凭空新增第二个作业执行器。
  3. 验证新 PDF 直接进入 available、业务字段为空且 OCR 尝试为 0,并确认财务改为查看 PDF 原件人工核对。
  4. 在当前 Trellis task 或发布记录中写明降级原因、预计恢复时间和影响范围。

1.5 备份与恢复演练(正式启用前必须完成)

数据库和文件存储必须使用同一批次成对备份;只有两份产物、完整性检查和 manifest 全部成功,才算一个可用恢复点。

1.5.1 备份

cd /path/to/hyb-oa
set -a
source deploy/trialrun/.env
set +a

mapfile -t DB_CONTAINERS < <(docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env ps -q db)
test "${#DB_CONTAINERS[@]}" -eq 1
DB_CONTAINER="${DB_CONTAINERS[0]}"
SOURCE_IMAGE_DIGEST="$(docker inspect --format '{{.Image}}' <verified-active-server-container>)"

DB_TOOL_MODE=docker \
DB_CONTAINER="$DB_CONTAINER" \
STORAGE_DIR_HOST="<verified-active-storage-host-directory>" \
SOURCE_IMAGE_DIGEST="$SOURCE_IMAGE_DIGEST" \
./scripts/backup-all.sh

backup-all.sh 可使用宿主 Node.js 或 Python 3 生成同结构 manifest;Docker-only 主机没有 Node.js 时会自动使用 Python 3,两者都没有则失败且不发布完整批次。DB_CONTAINER 和 STORAGE_DIR_HOST 均无隐式默认值:前者必须由已核验 Compose 项目的 ps -q db 解析,后者必须与活动 server 的附件挂载源一致。真实运行环境执行前,不得只相信可能漂移的 .env:先脱敏确认所选 PostgreSQL 容器的数据库/用户与至少一个活动应用连接一致,再把已验证的运行参数传给脚本;不在日志中输出参数值。

manifest 必须至少记录一个真实来源标识:活动 /ready 有构建标识时显式传 BUILD_SHA;没有时,从活动 server 容器 docker inspect --format '{{.Image}}' 取得 sha256: 镜像 ID并传 SOURCE_IMAGE_DIGEST。不得用待部署提交填充旧实例的 buildSha。

输出默认在 deploy/trialrun/backups/hyb-oa-backup-<run-id>/,其中包含:

  • database.dump
  • storage.tar.gz
  • manifest.json(相对文件名、大小和 SHA-256,不含连接串或源目录)

整批复制到获批的异机/异盘位置并记录保留周期;不能只复制 DB 或附件中的一份。

每日自动异机备份(可靠启用闭环仍为 EXTERNAL_GATED)

  • 每日 02:30 由生产主机解析唯一活动 Compose project 和真实 DB/附件挂载,生成同批三文件,ARM 校验并原子归档成功后才写成功状态和执行源端轮转。
  • 生产保留 7 个自动日批次;ARM 保留 7 个日批次和 4 个周检查点。07:10 源端 watchdog 检查最近 24 小时异机成功记录;ARM 独立 dead-man 检查最近 26 小时归档,生产整机或 OA server 停止时仍能告警;季度 timer 只发送人工影子恢复提醒。
  • ARM 使用专用密码锁定账号和 forced-command key:rsync 只能写 inbox,不能读取/删除 archive,也没有普通 shell。生产固定专用 key 和人工核对的 known-hosts,禁止 accept-new。
  • 同一 UTC 日重试使用新 run id 时,ARM 必须先完整校验候选,再原子取代当日旧批次并重建该周硬链接检查点;坏候选或发布失败不得触碰原有效批次。
  • 生产与 ARM 都直接使用独立企微运维群机器人,不依赖 OA server、数据库或个人工号。Webhook 正文只由用户手工写入两台主机的 root-only 0600 文件;仓库、普通 .env、systemd unit、日志和聊天只能出现受控文件路径,不能出现 URL 或 key。
  • 失败告警只包含阶段、状态、finding 数和类别建议;原始命令诊断只保留在源端状态目录的 0600 最近失败日志。同一失败降噪;首次失败、状态恶化、新 finding、恢复通知和每季度首次恢复提醒才发送。新部署构建与最近恢复点 build SHA 不同只记 finding,缺失、损坏、超龄、未来时间或 Compose project 不同仍硬失败。

自动备份 unit 会冻结安装时的 Compose project、build、PGDATA 和附件宿主挂载。已有部署入口在写前聚合核对这些身份,并在应用发布后通过 root-owned forced-command 只刷新 automatic-backup 三个 service 与 WARCH3 service 的 build drop-in,再验证四个 timer;路径、project、PGDATA、附件或完整 unit 合同变化仍必须用原安装器人工刷新,不能扩张 forced-command 的写入面。phase_stale_check_failed 是 watchdog 外层阶段,它也可能表示读取成功状态前的运行身份检查失败;应先看安全子类别和 unit/活动容器身份,不能仅凭该阶段认定归档已经超龄或损坏。

创建机器人后,由用户分别登录两台主机,在 root shell 中用静默输入把完整 Webhook 写入受控路径;不要把值作为命令参数、环境文件内容或聊天消息交给 Codex:

umask 077
read -rsp 'WeCom operations robot Webhook: ' HYB_OA_ALERT_WEBHOOK
printf '\n'
install -o root -g root -m 0600 /dev/null <controlled-webhook-file>
printf '%s\n' "$HYB_OA_ALERT_WEBHOOK" > <controlled-webhook-file>
unset HYB_OA_ALERT_WEBHOOK

安装顺序必须是:先获批 ARM 端安装 rsync、受限身份和 receiver,用合成批次验证;再获批生产端安装 units,手工完成首个真实批次;最后显式执行生产安装器的 enable。两个安装器默认 install 均不代表已启用,不得把本地合同通过写成远端完成。

# ARM:参数从受控配置取得;install 会写账号/authorized_keys/receiver,需单独批准。
BACKUP_ROOT=<absolute-backup-root> PUBLIC_KEY_FILE=<dedicated-public-key> \
RECEIVER_SOURCE=<repo-receiver-script> ALERT_WEBHOOK_FILE=<controlled-webhook-file> \
ALERT_HELPER_SOURCE=<repo-group-alert-helper> DEADMAN_SOURCE=<repo-deadman-script> \
  bash deploy/trialrun/install-offhost-backup-receiver.sh install

# 生产:先只安装 disabled units;路径、项目、构建和 SSH 身份必须绑定活动实例。
<explicit-project-data-build-ssh-and-webhook-file-env> \
  bash deploy/trialrun/install-automatic-backup-timer.sh install

# 首批真实备份、ARM 哈希和脱敏告警均经单独确认后,才允许启用三组 timer。
<same-arm-explicit-env> bash deploy/trialrun/install-offhost-backup-receiver.sh enable
<same-explicit-env> bash deploy/trialrun/install-automatic-backup-timer.sh enable

BACKUP_RUN_AT 使用生产主机本地时区,ARM 归档按 UTC 日分桶。调整执行时刻的当天可能出现预期空窗;改动后必须人工确认次日 UTC 分桶、下一次 daily 运行和 26 小时 dead-man 窗口。真实告警验收必须真正停止一次源端 timer 或故意让一次传输失败,再确认 ARM 首次告警、重复降噪和恢复通知;只测试机器人能发消息不算通过。

卸载只撤销 timer/receiver 授权,保留源端批次、状态、ARM archive 和凭证文件;专用账号/密钥的最终销毁另走获批清理。

1.5.2 本地隔离恢复演练

开发机具备 Docker、PostgreSQL 客户端,以及 Node.js 或 Python 3 时,使用合成数据运行:

./scripts/qa/run-backup-restore-drill.sh

该命令创建一次性 PostgreSQL 容器和临时附件目录,验证 DB/附件往返、manifest 校验和、错误数据库确认、损坏备份、非空目标目录和危险 tar 路径,结束后只清理本次创建的明确目标。它不读取 deploy/trialrun/.env,也不接触真实数据。

手工影子恢复必须使用独立数据库和空附件目录,并显式确认实际数据库名:

RESTORE_SCOPE=shadow \
RESTORE_CONFIRM_DATABASE=<shadow_database_name> \
DB_TOOL_MODE=docker \
DB_CONTAINER=<shadow_postgres_container> \
DATABASE_URL=<shadow_database_url> \
POSTGRES_DB=<shadow_database_name> \
POSTGRES_USER=<shadow_database_user> \
POSTGRES_PASSWORD=<shadow_database_password> \
./scripts/restore-db.sh deploy/trialrun/backups/<batch>/database.dump
RESTORE_SCOPE=shadow \
./scripts/restore-storage.sh \
  deploy/trialrun/backups/<batch>/storage.tar.gz \
  <empty_shadow_storage_directory>

restore-db.sh 和 restore-storage.sh 默认只接受 shadow。影子库名必须包含独立的 shadow、drill 或 restore_test 标记;数据库确认不匹配、库名不安全、附件目录非空、目标为默认试运行附件目录或归档路径危险时均在写入前退出。

真实格式影子恢复的验收不仅是 /ready:恢复后的 _prisma_migrations 成功项必须等于执行前现场冻结基线(当前生产证据为 61 项;本地 62 项仅表示下一次部署预计新增 1 项),预先列出的关键业务表计数必须逐表一致,附件解包后的普通文件数必须与活动源目录一致。任一项不一致即判定批次不可作为切换回滚点。

1.5.3 生产切换前冻结记录

执行正确备份和影子恢复前,先用只读命令记录:

  • docker compose ls 实测的旧 Compose project 名;不得按目录名推断,后续停止旧栈必须使用该实测值。
  • 活动 server/db 容器 ID、活动镜像 ID(sha256:)及可用的 repo digest;回滚至少保留不可变镜像 ID。
  • 活动 DB/附件真实宿主挂载源、Caddy 目标端口和环境文件摘要,不输出环境变量正文。
  • 成功 migration 数量和固定关键表计数基线。至少覆盖组织人员、角色、采购、报销、发票池、支付批次、合同、项目和附件关联表。

活动数据复制策略在停机窗口前冻结:旧 PostgreSQL 完全停止后,把 PGDATA 和附件复制到预先确认的空绝对目录,并保留所有权、权限、链接和扩展属性;旧目录留在原位且观察期内不再挂载写入。不得移动或改名后只留下单份已迁移数据,也不得复制运行中的 PGDATA。

1.5.4 真实事故恢复

真实库/附件恢复不是普通脚本路径。必须另建或更新获批 Trellis task,记录停机窗口、恢复批次、实际目标、两名负责人、当前备份、回滚点和验证清单。当前恢复脚本不会接受 production 模式,不得通过修改环境变量或脚本临时绕过。恢复后必须验证 /ready、运行 commit、核心权限和 DB/附件关联完整性。

1.5.5 小团队上线前演练记录

上线前至少记录一次演练结果:

  • 演练日期:
  • 执行人:
  • DB 备份文件:
  • 文件存储备份文件:
  • manifest 与 SHA-256 校验:
  • 恢复目标机器或目录:
  • 本地合成数据 RTO:
  • 恢复后 /ready 结果:
  • 上个可回滚 tag:
  • 回滚负责人:

需要人工配合的事项:

  • 提供或确认真实 deploy/trialrun/.env,特别是 JWT_SECRET、APP_ORIGIN、COOKIE_SECURE、OPENAPI_DOCS_ENABLED。
  • 确认备份文件保存位置和保留周期。
  • 确认完整批次已经复制到异机/异盘,而不是仍只保存在应用主机。
  • 准备独立 smoke 账号,避免使用老板、管理员或真实员工账号跑自动化。

1.6 ARM 公网应用健康监控(已启用)

应用健康监控与自动备份 dead-man 共用受控企微群机器人入口,但使用独立探测器、systemd units、probe state 和 alert state。它从 ARM 每 5 分钟访问公网 HTTPS /ready;连续两次失败才告警,一次成功即恢复。不得把它并入备份 receiver 的安装/卸载生命周期,也不得以本地测试通过代替 ARM 已启用证据。

2026-07-27 已完成 ARM disabled 安装、手工公网健康基线、timer 启用及不影响 OA 的真实故障/降噪/恢复演练;自动链路显示一条故障和一条恢复发送成功,用户确认群内实际收到两条消息。timer 最终 enabled/active、最近 service 成功,备份 dead-man 状态未变,临时上传和临时 sudoers 已删除。

先在仓库运行无真实消息合同:

bash scripts/qa/run-app-health-monitor-contract.sh

获得 ARM systemd 写入批准后,使用受控配置中的绝对路径安装 disabled units。命令中的 webhook 正文不得作为参数、环境变量或聊天内容出现:

PROBE_SOURCE=<repo-app-health-probe> \
ALERT_WEBHOOK_FILE=<controlled-root-only-webhook-file> \
ALERT_HELPER_SOURCE=<repo-group-alert-helper> \
ALERT_HELPER_TARGET=<installed-shared-alert-helper> \
  bash deploy/trialrun/install-offhost-app-health-monitor.sh install

安装后先人工执行一次 oneshot 并核对只含稳定状态/类别的输出;另获真实消息与 timer 启用批准后再执行同参数的 enable。检查 enabled/active、最近结果和下一次运行:

systemctl is-enabled hyb-oa-public-app-health.timer
systemctl is-active hyb-oa-public-app-health.timer
systemctl list-timers hyb-oa-public-app-health.timer --no-pager
systemctl status hyb-oa-public-app-health.service --no-pager

真实验收需受控制造持续失败,确认首次失败不发、第二次失败只发一条、相同失败降噪、恢复只通知一次。只测试机器人能发送不算健康监控通过。卸载前先记录脱敏状态,随后执行安装器 uninstall;该操作只删除应用健康 units 和探测器,保留状态、共用 helper、webhook、备份 receiver、dead-man 和归档。

2. 升级流程(试运行)

建议版本化方式:

  • 每次发布打 tag,例如 trialrun-2026-02-08-01

2.1 网页自动化门禁(R28)

发布前后统一使用三层网页自动化:

  • bash scripts/qa/run-web-gates.sh smoke-local:本地/PR 快速门禁,只覆盖登录、Portal、导航、企微入口 UI。
  • bash scripts/qa/run-web-gates.sh full-local:本地/main/夜间全量门禁,串联 server 回归、client build 与全部 Playwright E2E。
  • bash scripts/qa/run-web-gates.sh smoke-live:发布后公网只读 smoke,只检查 /ready、登录、Portal、团队、请假、项目、公告、个人设置、管理页只读入口与企微配置状态。

约束:

  • live smoke 必须使用独立账号:smoke-admin、smoke-employee。
  • live smoke 禁止新建报销、提交审批、付款、导入、资产流转、项目写操作。
  • 若本地已配置完整 LIVE_SMOKE_*,bash deploy/trialrun/deploy.sh 会在远端部署完成后自动补跑 live smoke。

步骤:

  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。
    • 正式使用新版入口前,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。
    • deploy.sh 只同步已验证提交的 Git archive,不依赖远端 git pull,也不会把本地 ignored 文件带入发布。
    • 在任何 rsync 前,远端 preflight 必须确认 existing 目标同时含 server/package.json 与 Compose 文件、PGDATA 含 PG_VERSION、storage 存在,且 SYNC_ENV=0 的 .env 是普通文件。失败即停止,不能先同步再验证。
    • REMOTE_DIR 必须与 PostgreSQL、附件宿主目录完全分离;位于同步树内的 REMOTE_ENV_FILE 按精确相对路径动态保护。
    • 在现网目录统一任务中,新的 COMPOSE_PROJECT_NAME、POSTGRES_DATA_DIR、STORAGE_HOST_DIR 必须与活动数据目录复制在同一停机窗口启用;本地提交本身不授权切换。
    • 旧栈停止命令必须使用冻结阶段由 docker compose ls 实测的旧 project;不得拿新 project 名去停止旧容器。
    • artifacts/ 和 output/ 是运行期证据/截图目录,不作为源码发布输入,也不由 rsync --delete 清理远端历史证据。
    • 应用、/ready、smoke 和公网检查成功后,wrapper 才调用 forced-command refresh,以活动容器 build 原子刷新四个固定 service 的 systemd drop-in并验证四个 timer。失败时固定输出 DEPLOYED_BUT_CONTRACT_REFRESH_FAILED、退出非零,并明确表示应用已发布但合同未收口;不自动回滚应用。
  3. 构建并重启:
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" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env run --rm init

2.2 前端部署一致性排障

oa.hyinda.com 由 Caddy 反代到 .env 中 APP_HOST_PORT 对应的宿主回环端口,前端静态资源由 server 容器内的 /app/client/dist 提供,不是 /etc/caddy/Caddyfile 里的独立静态目录。

如果出现“本地源码已有功能,但公网仍显示旧 UI”,先按以下顺序核查:

# 1) 确认 Caddy 指向 Compose 实际发布的宿主回环端口
sudo sed -n '/oa.hyinda.com/,/}/p' /etc/caddy/Caddyfile
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env port server 3000

# 2) 比对公网首页引用的 asset hash
curl -fsS https://oa.hyinda.com/ | grep -o '/assets/[^" ]*' | head

# 3) 在当前 Compose 项目的 server 容器内查构建产物是否包含关键 test id
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env exec -T server sh -lc \
  'for pat in invoice-pool-mobile-list reimbursements-mobile-list mobile-capture-file multiple:!0; do printf "%s " "$pat"; grep -Rao "$pat" /app/client/dist/assets /app/client/dist/index.html 2>/dev/null | wc -l; done'

若远端源码已更新但容器内 /app/client/dist 仍旧,重建并重启:

cd /opt/hyb-oa-trialrun
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" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env run --rm -T init </dev/null
SERVER_HOST_PORT="$(docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env port server 3000)"
curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT##*:}/ready"

重建后必须补做:

  • smoke-external-deps.js
  • smoke-trialrun-runtime.js
  • 已有部署由 wrapper 自动核对 Compose project、build、PGDATA、附件宿主挂载,并在发布后刷新 automatic-backup 三个 service 与 WARCH3 service 的 build drop-in,复核四个 timer。路径或完整 unit 合同变化仍用原 installer 人工修复,不让 forced-command 扩权处理
  • 公网页面视觉检查:至少覆盖 /invoice-pool、/reimbursements、/mobile/capture 的 390px 视口

发布/回滚 Checklist:

  • deploy/trialrun/checklist.md

3. 回滚流程(试运行)

回滚分两类:仅回滚代码(无迁移),或需要 DB 回滚(恢复备份)。

3.1 仅回滚代码

# 远端目录不是 git 工作树,推荐在本地切到目标 tag 后重新执行 deploy.sh
# 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

3.2 需要 DB 回滚(推荐做法)

  1. 停止服务:
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env stop server
  1. 按 E14 恢复 DB + 文件存储备份
  2. 启动服务并验收 /ready 与登录

4. 日志与排障

  • server 容器日志:
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env logs -f server
  • /ready 失败通常为 DB 不可达或连接串错误。

5. 优雅停机(E21)

说明:

  • docker-compose stop server 会发送 SIGTERM,server 会尝试在 10s 内完成:
    • 停止 OCR/委派回收定时任务
    • 关闭 HTTP 监听
    • 断开 Prisma/PG 连接
  • 超时会强制退出(避免卡死导致升级失败)。

6. R12-lite 试点切换 Runbook

6.1 打开本轮功能前置条件

  • 执行 cd server && DATABASE_URL=<local-postgresql-url> npm run seed:config,确保 R12-lite 开关与默认配置落库。
  • 在运行环境启用对应 env:BUDGET_PRECHECK_ENABLED=true、PAYMENT_BATCH_ENABLED=true、PORTAL_HOME_ENABLED=true、GLOBAL_SEARCH_ENABLED=true、ANNOUNCEMENTS_ENABLED=true、WORKFLOW_DEFINITION_ADMIN_ENABLED=true、CUTOVER_CONSOLE_ENABLED=true。
  • 登录系统后在“功能开关”页将对应开关切到 inherit 或 force_on;仅设置 env 不会自动放开 DB 中的 force_off 记录。

6.2 旧 OA 主数据导出与 dry-run

  1. 从旧 OA 导出人员、部门、预算或主数据 CSV。
  2. 进入“切换控制台”,按 employees | departments | budgets | masterdata_validation 上传 CSV 执行 dry-run。
  3. 只接受 dry_run_ok 结果进入下一步;若为 dry_run_failed,先修正列头和差异。
  4. 以 docs/migration-mapping.md 为准完成人工差异核对。

6.3 试点切换步骤

  1. 确认旧 OA 未完结事项已人工清空或冻结。
  2. 在旧 OA 宣布“新单停用时间点”。
  3. 在新系统创建 CutoverScope,粒度仅允许 department | legal_entity | domain。
  4. 按试点范围打开对应 CutoverScope,先开 portal,再按域逐步打开 expense / leave / procurement / travel / asset。
  5. 观察 Portal、待办、支付批次、AP 运营和消息深链是否正常。

6.4 验收证据

  • 保留 dry-run 结果截图或导出记录。
  • 保留切换范围配置、启停时间和操作人审计。
  • 保留回归结果:server/scripts/regression-all.sh、client npm run build、client npm run test:e2e(如环境可用)。
  • 保留试点部门首批新单在新系统流转与付款的样例单据编号。

6.5 回退步骤

  1. 在“切换控制台”关闭目标 CutoverScope。
  2. 恢复旧 OA 对应入口并重新开放新单。
  3. 若需要代码/数据回退,按本文第 3 节恢复到最近备份。
  4. 新系统中试点期间新建单据保留审计,不做 destructive 回写到旧 OA。

7. R17 小团队内部管理上线 Runbook

7.1 默认目标形态

  • 工作区模式:small_team_internal
  • 主导航:Portal、团队、请假、报销、资产、周报、待办、公告
  • 入口策略:账号密码主入口,站内通知主通道,桌面优先

7.2 推荐环境开关

  • 建议打开:PORTAL_HOME_ENABLED=true、GLOBAL_SEARCH_ENABLED=true、ANNOUNCEMENTS_ENABLED=true、PRESENCE_BOARD_ENABLED=true、WEEKLY_REPORT_ENABLED=true、EMPLOYEE_LIFECYCLE_ENABLED=true、TODO_BOARD_ENABLED=true、PAYMENT_BATCH_ENABLED=true
  • 建议保留备用:CUTOVER_CONSOLE_ENABLED=false
  • OCR 回归环境建议:OCR_PROVIDER=mock

7.2A 本地 Playwright 辅助复核门禁

  1. 本地 HTTP 辅助复核先执行:
cd server
TRIALRUN_RUNTIME_MODE=local node src/scripts/smoke-trialrun-runtime.js
  1. 该命令会同时检查 trialrun 所需 env、feature flag、workspace setting 与导航基线。
  2. local 模式允许 APP_ORIGIN 为空、COOKIE_SECURE 为空或 false;HTTPS / 真实部署仍按 deploy 模式要求 APP_ORIGIN=https://... 且 COOKIE_SECURE=true。

7.2B 本地深层辅助回归命令

cd client
PLAYWRIGHT_MANUAL=1 \
R70_TRIALRUN_DEEP_ASSIST=1 \
E2E_BASE_URL=http://127.0.0.1:3001 \
npm run test:e2e -- tests/e2e/manual/r70-trialrun-deep-assist.spec.ts

7.2C 批量真实腾讯 OCR 辅助回归命令

cd client
PLAYWRIGHT_MANUAL=1 \
PLAYWRIGHT_OCR_LIVE=1 \
PLAYWRIGHT_OCR_LIVE_BATCH=1 \
PLAYWRIGHT_OCR_LIVE_DIR=/abs/path/to/invoices \
E2E_BASE_URL=http://127.0.0.1:3001 \
npm run test:e2e -- tests/e2e/manual/live-invoice-ocr-batch.spec.ts

7.3 上线前整理事项

  1. 补齐员工账号、部门、直属上级与员工档案。
  2. 核对资产卡片与实际领用关系。
  3. 为老板 / 管理员 / 主管 / 员工分配推荐角色预设。
  4. 在 工作区设置 确认模块显隐,收敛主导航。
  5. 准备一条公告,验证全员登录后 Portal 可见。

7.4 首周启用顺序

  1. 打开 Portal + 公告 + 团队状态
  2. 第二天开始要求全员更新今日状态
  3. 当周启用 周报 + 待办
  4. 同步启用 入职 / 离职 / 资产交接
  5. 报销和请假保持可用,但不再作为首页主叙事

7.5 4 周观测建议

  • 每周记录:
    • 周报提交人数
    • 团队状态更新人数
    • 公告阅读反馈
    • 入职/离职/资产交接演练情况
  • 建议统一使用 deploy/trialrun/observation-template.md 填写每周结论与阻塞。
  • 可执行 cd server && npm run report:small-team-observation -- --out ../deploy/trialrun/observations/week-01.md 导出当周快照,再补充人工结论。
  • 若连续两周无人使用某入口,考虑继续降级模块可见性。

R18-R22 公网 + 企微 + 项目域上线补充

A. 公网安全基线

  1. 为 OA 准备 HTTPS 域名并反向代理到 server。
  2. 在 deploy/trialrun/.env 中至少设置:
    • APP_ORIGIN=https://oa.example.com
    • COOKIE_SECURE=true
    • PUBLIC_SECURITY_HARDENING_ENABLED=true
    • OPENAPI_DOCS_ENABLED=false
  3. 重新执行:
docker-compose -p "$COMPOSE_PROJECT_NAME" -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server
  1. 验证浏览器和企业微信 WebView 都能正常登录、刷新 token 与退出登录。

B. 企业微信工作台入口

  1. 在企业微信自建应用中配置工作台地址,默认落到:https://oa.example.com/portal。
  2. 在 deploy/trialrun/.env 中补齐:
    • WECOM_ENABLED=true
    • WECOM_ENTRY_ENABLED=true
    • WECOM_PC_WEB_LOGIN_ENABLED=false(默认关闭;确认企微授权登录和可信域名后再独立开启)
    • WECOM_CORP_ID
    • WECOM_AGENT_ID
    • WECOM_AGENT_SECRET
    • WECOM_OAUTH_ENABLED=true
    • WECOM_REDIRECT_URI=https://oa.example.com/api/auth/wecom/callback
    • WECOM_ALLOWED_RETURN_HOSTS=oa.example.com
  3. 管理员先在个人设置页完成企微绑定,再拉试点用户验证免登。
  4. 如需普通 PC 浏览器扫码登录,先在企微自建应用开启“企业微信授权登录”并核对 OAuth 可信域名或 Web 网页授权回调域名,再将 WECOM_PC_WEB_LOGIN_ENABLED=true;回滚时只需恢复为 false,不影响工作台免登、密码和邮箱验证码登录。
  5. 企微消息只用于提醒与打开 OA,不新增第二套录入页面。

C. 项目与合同最小导入

  1. 先整理项目台账、项目成员、里程碑、风险、合同登记 CSV。
  2. 按 docs/migration-mapping.md 的最小列准备字段。
  3. 先在测试环境做 dry-run,再在试点环境 apply。
  4. 涉密正文、附件、报价成本继续保留在内网资料库或离线档案。

D. 试点验收新增观察点

  • 企业微信进入 OA 的成功率
  • 项目任务与里程碑更新率
  • 报销 / 差旅 / 周报挂项目覆盖率
  • 是否还存在第二套项目台账或 Excel 主表
  • 企业微信通知是否发生敏感字段泄露

7. R23 试点收口准入步骤

  1. 打开以下功能开关并确认 DB 记录不是 force_off:
    • project_domain_v1
    • project_linking_v1
    • project_communication_log_v1
    • project_closure_checklist_v1
    • confidential_doc_refs_v1
    • global_search_v1
  2. 抽样创建或选择 1 个真实项目,验证:
    • 可以新增客户沟通回填
    • 项目收口清单会自动生成
    • 项目改为 completed/archived 时出现软告警而非硬阻断
  3. 抽样验证 1 条涉密合同或资料引用:
    • 敏感摘要关键字无法通过全局搜索命中
    • 合同编号或资料编号可以命中
    • 企业微信消息只发“有项目待跟进 / 有项目待收口”脱敏提醒
  4. 向全员宣导两条试点纪律:
    • 客户沟通影响项目时 24h 内回填
    • 私聊可继续发生,但项目状态不能只留在聊天里
  5. 进入真实 4 周试点前,至少保留一次通过记录:后端测试、前端 build、E2E 全绿截图或日志。

8. R29 前端设计系统与工作台体验验收

8.1 视觉与交互核对顺序

  1. 登录页:先确认当前场景只有一个主 CTA,并验证错误提示、企微回退文案可理解。
  2. Portal:确认员工与管理员两套第一屏都先回答“今天要做什么”。
  3. 导航:确认主导航 9 项稳定、管理入口独立、备用模块默认折叠。
  4. 个人设置:确认只展示员工可理解的信息,企微绑定卡状态清晰。

8.2 自动化建议

  • 本地快速验收:bash scripts/qa/run-web-gates.sh smoke-local
  • 发版前完整验收:bash scripts/qa/run-web-gates.sh full-local
  • 公网只读验收:bash scripts/qa/run-web-gates.sh smoke-live

8.3 设计 QA 口径

  • 一个场景只允许一个主 CTA。
  • 首页先动作,后报表;公告与最近访问降到第二阅读层。
  • 空状态必须给下一步建议,不只显示“暂无数据”。
  • 移动 / WebView 首屏避免横向滚动和过宽表格。
  • 状态色只表达语义,不把品牌色滥用成所有强调色。