文件
hyb-oa/.trellis/tasks/archive/2026-09/09-09-existing-deploy-seed-separation/design.md
T
wangsli和Claude Sonnet 5 b9415330e5 归档日常发布与首次seed初始化分离任务 [09-09-existing-deploy-seed-separation]
全部 AC 完成:部署脚本与 Compose init 消除无条件 seed 旁路,existing 对
已修改配置不重写、重复执行稳定,缺项检查不误判、切应用前停止不自动
seed;数据库生命周期隔离回归不退步。2026-09-09 已完成 b87cc55 真实发布
(本轮回填补齐了完整执行记录:845 镜像清理、备份验真、发布后复核)。

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-25 20:52:12 +08:00

9.6 KiB
原始文件 Blame 文件历史

已批准实施方案:初始化与日常升级分离

已核事实

  • 当前本地基线 b918530。deploy/trialrun/deploy.sh 的 run-init 无条件执行配置预检、migrate deploy、seed:config、seed:trialrun。
  • deploy/trialrun/docker-compose.yml 的 init 默认命令也执行 migrate 与两类 seed;只改 deploy.sh 不能覆盖默认 init 使用路径。
  • seed-config 包括工作流取消激活后 upsert、假期条目删除重建等写入;seed-trialrun 对既有开关强制更新。不能用“本次值与默认相同”替代发布模式隔离。
  • 原生命周期隔离 task 已约定 existing 不操作 db 生命周期、init 和应用采用依赖隔离;本轮保留这些合同。

拟采用路径

使用既有 existing/initialize 模式,不新增第三个部署模式。拟在 server/src/scripts 增加一个小型 init 命令,由 server/package.json 暴露统一入口,供 deploy.sh 和 Compose init 默认 command 共用;不复制两套分支。当前 Dockerfile 已 COPY server,无需为入口修改构建拓扑。模式必须显式传入,空值或非法值在迁移/seed 前失败;不默认 initialize。deploy.sh 用容器环境参数显式传递已验证的模式,Compose 直接调用则要求调用者提供模式。保持 --no-deps;受支持的直接调用文档同样明确依赖隔离,不改变 Compose 服务拓扑。

顺序:配置预检 → migrate deploy → initialize 才执行 seed:config 和 seed:trialrun → 两种模式均运行只读基础配置检查 → 成功才进入应用构建/切换。迁移和 seed 子命令非零即停,不重试、不自动改模式。未明确模式的旧直接 init 调用将失败,这是刻意的安全兼容变化,需在交审及操作手册写明。

只读配置检查是本项目报销主链路的最低配置门禁,不声称这些都是服务启动必需项。使用短只读事务和错误即停,只输出检查代码/数量及维护入口,不输出记录内容。拟阻断条件限定为以下三类完全缺失:至少一个 enabled expenseType、至少一个启用的 approvalRule、至少一个 entity_type=reimbursement 且 is_active 的 workflowDefinition(与当前提交入口选择条件一致)。不要求固定名称、固定 v1、固定角色、固定金额阈值或内容等于 seed 默认值。

这只是“非空”最低门禁,不证明金额区间无空洞、审批人都可解析或全部单据可提交。空库冒充 existing 因缺三类基础配置失败,不自动初始化。管理员故意关闭全部报销基础配置的环境也会停止发布,而不是被强制恢复;这是本方案新增的明确兼容约束,须用户批准。

确需新增配置的数据升级另设可审查的版本化变更;本轮不借默认 seed 自动补齐。发现缺项则报告维护路径,由负责人另批初始化或数据修复,不能在 existing 中静默写入。

风险及回滚

停止 seed 不等于停止 migrate,也不保证旧镜像能撤销数据变化。应用回滚、迁移兼容、数据库恢复分开说明。初始化 seed 仍有写入风险,非法模式和 init 失败必须停止切换。最终实施须在独立工作树进行,避免与尚未提交的 Trellis 修复混合。

已核源码与配置裁决

对象 证据 方案裁决
部署入口 deploy-existing.sh 显式设置 existing;deploy.sh 本地及远端都校验模式,run-init 覆盖 Compose 默认 command 两条 init 路径统一入口,模式传入容器;保留 V1/V2 Compose 支持
报销审批规则 reimbursements.js 的 loadApprovalRuleForAmount 无匹配时 409 仅查启用规则非空;不宣称覆盖全部金额
报销工作流 reimbursements.js 提交读取 active reimbursement definition;缺失时报业务错误 查 active 定义存在,不改审批链,不强制 v1
费用类型 reimbursements.js 的 resolveEnabledExpenseTypesByIds 只接收 enabled 类型 查至少一个 enabled,不重建默认类型,不验证每种差旅费用
工作区 workspaceSettings.js 的 mapWorkspaceSetting(null) 返回 buildDefaultWorkspaceSetting 非发布硬阻断,不创建记录
编号 documentSequence.js 的 resolveDocumentSequenceConfig 及 issueDocumentNumber 有解析兜底和业务时 upsert 本检查不调用发号函数,不要求初始化序列存在,不更改业务发号写入
开关 featureFlags/index.js 结合环境门禁与 DB 记录评估 不强制 force_on 或默认 rollout,不把管理员关闭视为缺配置
日历、预算、部门、用户/主体/银行卡及其他模块 属于各自业务就绪与配置范围 不扩为全站发布门禁,不读账号/银行资料,不在发布时自动维护

维护提示沿用费用类型、审批规则、流程定义的既有管理入口;若初始化数据根本未建,由运维另审 initialize 或版本化数据修复,不建议在 existing 重跑整套 seed。

实施文件与验证

预计涉及 deploy/trialrun/deploy.sh、docker-compose.yml、server/package.json、新的小型 init/只读检查脚本及定向测试,原 deployment spec/手册和本 task。复用 server/tests/unit/scripts/deployment-db-lifecycle.test.js、deployment-safety-contract.test.js;新测试覆盖真实统一入口执行顺序,不只匹配脚本文本。

矩阵:existing/initialize × 成功/非法或缺模式/配置预检失败/迁移失败/seed失败/只读缺项或查询失败。existing 中 seed 调用次数恒为零且成功路径必须真正到达应用步骤;initialize 的两个 seed 必须按序执行,失败不得到达应用步骤。直接 Compose 默认 command 使用同一入口,已有依赖隔离的变异测试继续生效。

合成库测试与生产完全隔离:验证 initialize 空库建立配置;人为修改已有开关、工作流、日历及其他 seed 管理表后运行 existing 两次,比较配置字段、版本/时间戳不变,迁移记录单列。只用测试身份/临时数据库,不能以无 Docker 或初始化失败冒充通过。环境前置不足先报告,不借用 ARM 正式归档或生产库。

审批边界

新候选发布准备草案(提交后,未授权部署)

  • 候选固定为 b87cc55aca0003130cf8a5bbe38d0dd15528389c,已推远端。执行来源为干净的 seed-preflight-integration 副本,不使用仍在旧基线且有其他治理差异的主工作树。此前候选和例外不自动转移。
  • 本地已有证据:已审两包、顺序补丁集成与 110/110 定向回归。尚无完整镜像构建或完整候选门禁通过证据;不将定向回归冒充普通发布资格,不自动启动暂停的 R1、阶段 B 或修复 58 条红项。
  • 下一步申请本候选的一次生产只读预检:运行版本/镜像身份、候选迁移集合与数据库迁移状态、三类 enabled/active 最低配置计数、磁盘余量、最近备份时效及已有回滚制品元数据。只输出脱敏摘要;不读取个人访问历史、单据明细或银行信息,不执行 migrate/seed,不写配置,不切容器。现有审批记录不替代本轮明确边界。
  • 预检必须执行已修的状态解析逻辑;如远端仍为旧脚本,应先设计获准的调用方式,不以“只读”名义自行上传或安装文件。读取受限则标未验证,不绕权限。
  • 回滚目标以本轮确认的生产运行版本为准,不默认仍是历史 f223bfb。旧镜像必须保持独立可用;备份完整性与恢复能力分别记录。已知 DB 最小恢复成功不代表附件或应用恢复成功,也不证明新备份已恢复演练;不沿用历史“从未成功”的过期结论。
  • 若最低配置缺失,报告管理员,不自动 seed/启用配置。existing 仍会执行迁移,配置检查失败可能发生在迁移落库之后,镜像回滚不能撤销数据变化;任何数据恢复还涉及恢复点之后业务数据的处置,另行审批。
  • 汇总本轮证据后再提交发布决定:普通路径需要完整候选验收;若仍未具备,明确列出缺口与回滚验收,用户对本 SHA 单独批准例外和部署。未获批准不执行真实发布,不拿旧例外填空。
  • 部署获批后按既有 existing 路径执行,检查日志无 seed 阶段、版本/健康一致;AP 验收覆盖仅 ceo/manager 拒绝、finance/finance_mgr 可读、兼财务多角色可读、审批/复核入口保留。真人制单/付款流程由授权人员验收,下载不等于付款;不代操作真实银行业务。
  • 失败停止并报告,不自动扩大写入或恢复范围;成功也不删除独立回滚制品。磁盘治理、恢复附件/应用验证、统一待办和错误豁免期限沿原任务处理,不混进本次发布写入。

用户已同意本 task 规划方向;以上统一入口、直接调用显式模式和三类非空硬门禁为本轮收敛的待审实施方案。获批前保持 planning;不把此前“好的”解释成批准尚未呈现的兼容变化。实施只在独立工作树,不叠加未提交的治理修复。提交、推送、生产部署另批。

后续授权与实施:用户对收敛方案明确回复“是”,已进入独立工作树实施。共享入口为 server/src/scripts/deploy-init.js。只读检查通过 Prisma repeatable-read 事务显式 SET TRANSACTION READ ONLY;费用类型没有专用管理页面,改为提示管理员核对既有 /api/expense-types,缺失数据另批修复,不新增 UI。

环境契约同步限定 init 可注入且必须声明 DEPLOY_DATA_MODE,并要求 Compose 缺省展开为空,拒绝默认 initialize;server/worker 仍不允许该 override。此为已批准的模式传递配套,不扩大业务环境变量或放宽校验器。