文件
wangsli 46a70aa552 merge: 合入候选分支(09-17借款/ERP退役、09-18编号、09-19预算并发、完整10-05错误码迁移,均经确认)
# Conflicts:
#	.agents/skills/trellis-before-dev/SKILL.md
#	.agents/skills/trellis-brainstorm/SKILL.md
#	.agents/skills/trellis-continue/SKILL.md
#	.agents/skills/trellis-start/SKILL.md
#	.trellis/tasks/08-23-isolated-backup-restore-drill/design.md
#	.trellis/tasks/08-23-isolated-backup-restore-drill/prd.md
#	.trellis/tasks/09-05-error-explainability-payment-readiness/prd.md
#	.trellis/tasks/09-05-error-explainability-payment-readiness/task.json
#	.trellis/tasks/09-09-existing-deploy-seed-separation/implement.md
#	.trellis/tasks/archive/2026-09/09-05-trellis-upgrade-rule-cleanup/task.json
#	.trellis/workflow.md
#	scripts/qa/error-reason-legacy-baseline.json
#	server/src/routes/apOperations.js
#	server/src/routes/portal.js
#	server/src/routes/todos.js
#	server/tests/unit/fixtures/error-reason-messages.json
#	server/tests/unit/routes/apOperations.permissions.test.js
2026-09-26 07:54:10 +08:00

23 KiB

隔离备份恢复演练

2026-09-19 K0 独立静态复核与范围澄清

K0 加解密合成合同经 Claude 定向静态复核 PASS,未新增运行时或真实恢复结果。它不覆盖 maskBankAccount、maskIdNumber、maskKeepEnds 的脱敏正确性,不能作为“敏感字段完整闸门”引用;本轮不扩建脱敏测试或修改产品函数。

server/src/scripts/migrate-notification-channel-secrets.js 会读取配置、连接目标数据库并批量改写通知渠道密钥字段;它属于数据迁移工具,明确排除在 K0 以及 K1/K2 的默认执行范围之外,不得为恢复验证运行或导入该入口。未来 K2 若选择通知类加密字段,只按另批绑定方案只读验证适用密文;无适用样本记 NOT_TESTED。迁移、轮换、明文回写或真实通知均需独立方案与明确授权,不由恢复批准附带取得。

2026-09-17 原密钥保管与敏感恢复后续规划

本增量的K0本地合同检查与合成验证已于本日另获批准并完成;K1/K2仍仅规划,原受限演练已经结束且不可重跑。目标从“追查密钥何时生成”改为“原密钥是否有受控保管及能否恢复对应敏感字段”;来源UNKNOWN可以继续如实记录,不能用生成新密钥替代原密钥或把格式合法等同于高熵来源已证实。

范围分三段:K0仅本地源码/环境分类合同与合成密文验证;K1另批后只核实际保管可用性;K2再绑定具体新鲜源、制品、隔离目标及受限原密钥注入方案并另批实际恢复。已有环境恢复分类与双角色保管规约复用,不新建密钥平台、不将.env装进普通数据备份。

新增验收(不替代原AC4/AC5):

  • K0:本地缺密钥、错误密钥、篡改密文均停止;正确合成密钥成功,输出不含密钥/密钥摘要/密文/明文或原始异常,生产未访问。30项定向测试及7类合成检查通过;只证明本地合同,不证明真实密钥或数据可恢复。
  • K1:管理员与公司责任人均有受控取用原密钥的渠道,非仅依赖原主机或单人记忆;记录角色/渠道类别/验证时间/可用状态,不保存值或保管位置细节。
  • K2:在另批的隔离范围验证选定备份中实际存在的加密字段,原密钥匹配、适用字段解密检查和清理分列有据;没有适用密文时记NOT_TESTED,不拿合成通过替代。

任何保管、原密钥取用/转移、真实备份读取和恢复均非本次K0授权。无法安全取得原密钥时停止真实验证,报告影响与待批方案,不读取shell历史、不索要聊天明文、不擅自轮换。实际执行方案必须重新独立只读复核;本次主会话检查不计为独立复核。

2026-09-17 本次受限演练实际结果

已获批准的一次R3绑定恢复于01:33Z完成:DB及影子认证只读应用PASS;附件62引用中仅精确已接受历史缺件1、未知不一致0;清理及源/生产/监控后验PASS。整体PARTIAL_KNOWN_MISSING_AND_SENSITIVE_DECRYPTION_NOT_TESTED,RPO7小时2分52秒。原始全量附件标准及完整计时指标未因此豁免,敏感字段解密未验;本次无需再执行,task保留未覆盖范围,不自动归档。详情与三摘要绑定证据见implement最新节。

2026-09-17 单条历史缺件例外(已批准)

本日后续另获固定b1d25d3c源码的ARM64镜像构建批准,仅构建及元数据验证,不部署、不启动应用、不恢复、不读生产密钥。匹配制品已构建不代表R3独立复核或最终恢复源绑定完成,详见design/implement最新节;其他版本/范围不自动授权。

用户批准按design的精确身份/七类零关联条件修订验收、测试并独立复核后再执行一次受限恢复。本增量仅允许将指定历史缺件作为已接受但仍未恢复的事实,逐项报告原始不一致数、已接受数、未知数;其他缺件/大小/摘要/路径异常仍阻断,不能把任意归档件排除。原R2与NO_GO不改,最终最高结论保留历史缺件及解密未验的PARTIAL。生产零写入、资源/网络/清理边界不变。

09-15旧源现已超过24小时,禁止重用旧入口或放宽新鲜度;先完成本地修订和只读选源事实,实际重跑必须重新绑定新鲜源与同来源镜像并经复核。源/平台/风险实质变化时说明差异,未解决之前不生成可执行入口。当前批准不授权任意镜像构建、生产密钥读取或新备份写入。

2026-09-16 配对恢复规划增量(当前优先合同)

关联09-08总任务P1/P2/P3。先读取08-28的新自然备份/异机完整性事实,再冻结单次演练输入;本轮只更新规划,没有恢复授权。优先ARM最新合格正式日归档,不把09-15固定8d历史方案或09-16生产同机手工备份当默认源;若需使用后者,必须另外明确向ARM复制的范围并重新核源副本。未知/超龄/损坏不得静默换源。

恢复覆盖分为DB_RESTORE、STORAGE_REFERENCES、APP_READONLY三个独立结果;下文旧“不启动OA server”仅属于最初数据恢复阶段,本增量允许将匹配应用的一次性启动纳入拟申请范围,但未批准之前不启动。应用镜像必须绑定源build并核平台;生产镜像能归档不代表在ARM可运行,不默认仿真或随意换最新镜像。

应用只读验收的访问身份、字段解密能力、禁后台任务/外联配置必须先可证明安全。临时登录/数据库凭据只用于隔离环境且不得复用生产.env;若恢复数据的密钥不可安全提供或身份条件不满足,则记录APP_BLOCKED或APP_PARTIAL及原因,不用随机替换业务加密密钥、仅/ready成功或DB PASS代替全应用可恢复。密钥来源未知与是否获批安全使用为不同问题,不要求用户把值发给代理。

本task只有三项实际验收与清理/生产无影响证据齐全才可宣布本次配对演练完成;是否符合业务RTO另按预先确认的目标判断,不能仅因记录了耗时就认定满足SLA。历史缺失附件保留原引用,不在恢复演练中代做生产治理或删除。

2026-09-09 最小验真阶段(本地准备已批准,远端执行另批)

用户批准综合规划开始本地实施。本 task 不再把安装器/固定 runner 的完善当作下一次数据库恢复验真的前置;新增受控管理员人工最小路径。只在既有授权通道操作,不能以"不用 sudoers"解释为绕过现有读取权限。未收到本次 ARM 真实备份恢复授权,本轮不连接远端。

本阶段数据库结果和配对附件结果分开:数据库退出 0、来源 migration 与关键表检查通过,才可记 DB_RESTORE_PASS;附件未执行记 STORAGE_NOT_TESTED;不启动应用,不宣称应用或完整灾备 RTO 已验证。原整体 Goal、数据库/附件 AC 和历史 NO_GO 保留;最小阶段通过不自动勾选原整体 AC。原 runner 渲染/安装及分段 RTO 不作为本次最小阶段新增工程前置,仅适用于保留的正式演练合同。

保留生产零写入/零重启、影子目标确认、无宿主端口、批次来源与24小时新鲜度、配对摘要、敏感输出限制、非零失败关闭及本轮精确清理。若本地脚本在实际最新批次上失败,保留脱敏诊断并停止;不得现场放宽条件、转用旧批次或自动重试。Attempt 3 底层原因仍未知,不追认所有失败均为工具问题。

2026-09-11 本轮执行口径

用户认可在当前无业务关联、影响较低的前提下删除或标记。选择非破坏性标记:复核仍为 archived/manual、原件缺失且无当前业务关联后,在现有任务/恢复缺口记录登记"已知历史附件缺失,保留记录,暂不继续追查"。当前票据接口不允许编辑 archived 记录,不为添加备注绕过状态守卫改库,不新增缺失状态或删除记录。"可能是测试数据"仍是推测,不写成已确认事实。此处置不把恢复 AC4 改为通过,不改校验器、不排除缺失项,也不自动授权部署。

该处置已完成:再次只读核对无当前报销/明细/差旅及同路径附件/回单关联,记录和源文件缺失状态未变;已在本 task 和恢复证据中标记,生产数据保持原样。按用户决定暂不追查,不阻塞其他业务开发/验收;技术恢复仍如实记录 1 条附件引用缺失,发布例外及独立复核不由该标记替代。

用户批准对此前明确提供的单份发票 PDF 读取字节计算 SHA-256,并与唯一缺失记录登记的文件摘要、大小比对。不展示文件内容或原始文件摘要,不遍历 input 目录、不读其他文件,不上传原件、不补回或改生产。只读返回匹配/不匹配;匹配不代表原件已补回、备份已更新或恢复已通过,不匹配则停止,不自动扩查。

本次单文件比对已执行:大小和 SHA-256 均不匹配,不能将它补作登记原件。已按约定停止搜索,未补文件、改库或部署;不推断 PDF 业务内容必然不同。当前仍保留单条历史附件缺失,恢复 AC4 未通过;后续需新的明确原件线索或另批缺失处置方案,不重复恢复或自动扩大搜索。

用户另行批准只读核对唯一缺失记录的创建/归档来源元数据。本轮只定位既有记录 SHA-256 与路径 SHA-256,查询该行的时间、状态、归档原因枚举、创建/上传/更新身份关系布尔值及必要技术元数据;不读取人员账号、个人访问历史、审计 payload、票据正文、金额、备注或银行字段,不改数据。结合本地已发布源码解释字段含义;没有确证不得归因为测试数据或手工删除。

此元数据核对已完成:记录创建于 2026-07-27,约 9 分钟后标记 manual 归档,PDF 技术类型,存有 mock OCR 处理记录;当前无报销/明细/差旅关联。没有读取 OCR 结果或文件内容;不因 mock 或手工归档标记认定为测试数据,也不推断是谁删除了文件。来源入口与缺失原因仍未证实,AC4 未通过;后续内容摘要比对必须单独明确读取范围,任何修复仍另批。

用户进一步批准围绕该唯一缺失引用,只读查历史备份目录项和文件元数据以寻找原件。仅在已有生产/ARM 管理渠道可读的正式备份目录内做有界搜索,按已记录的规范化路径 SHA-256 匹配;不读票据正文、不恢复数据库、不解包落盘、不补文件或改引用。找到目录项只算候选原件,内容一致性与可恢复性仍须另验;权限拒绝、覆盖范围不足和未命中必须分开,不以未找到推断永久丢失。

该限定搜索已完成:生产 7 份、ARM 日归档 7 份及周检查点 4 份均未命中;ARM 另 1 个非规范目录项未纳入,不能称为所有历史备份都已检查。管理员已执行且主会话 SSH 回读结果,不再等待该次密码或执行。缺失引用未修复,AC4 继续未通过,不扩大为永久丢失或测试数据结论。

第三次诊断已确认发票池 1 条引用缺文件。用户另行批准生产侧限定只读核对:最小引用/关联状态和源文件存在性,可对照已固定附件归档目录项;不读票据正文、金额或银行资料,不改数据或生产配置。此限定读取是下文历史"生产仅健康探测"范围的显式补充,不授权恢复生产或修复记录。

用户已同意继续做 ARM 一次性 DB+附件隔离配对恢复;不是生产恢复或发布授权。本轮复用已有 shadow 恢复脚本,以一次管理员终端操作完成前置验证、临时恢复、校验与清理,不再安装历史 runner、sudoers 入口或增加分段计时装置。按原 R1–R4 保留输入身份、时效、隔离、迁移和附件配对边界,R5 记录总耗时与安全失败阶段即可。实际配对恢复已执行:DB 通过、附件引用失败,清理完成;不以本地合成通过替代,也不原样重跑已知失败过程。

固定输入来自今日最新源端校验通过的 fe025e65 备份;若执行时 ARM 最新批次、manifest、来源 build/image 或现存 PostgreSQL 镜像变化,暂停刷新证据,不自动取旧批次或拉镜像。恢复不启动 OA 应用,app 必须标 NOT_TESTED;镜像回退和 DB/附件恢复仍是不同能力。09-09 管理员回传的最小 DB_RESTORE_PASS 已有效记录,不再沿用"从未成功恢复数据库"的旧结论。

Goal

在不覆盖、不暂停腾讯云生产 OA 的前提下,使用 ARM 备份机上最新的完整异机批次完成一次数据库与附件的隔离影子恢复,证明“备份存在”进一步达到“最新成对数据备份可恢复”,并记录实测数据恢复 RTO、RPO 和脱敏证据。

Scope

  • 在 ARM 上选择并校验最新正式日归档,使用本轮临时副本恢复数据库与附件。
  • 验证 migration、固定关键表、附件数量与 DB/附件引用一致性,记录数据恢复 RTO/RPO。
  • 复核生产前后 /ready、ARM 监控状态和临时对象清理结果,形成当前 task 内脱敏证据并更新既有事实源摘要。
  • 不修改产品运行代码;允许把演练 runner 模板、附件校验器和可复跑合同收敛到 scripts/qa/。生产、正式归档、企业微信和现有监控均保持只读或零变更。

Background

  • 腾讯云服务器是真实 OA 生产;ARM 只承担异机备份、外部健康监控和受控恢复演练,不是 OA 生产服务器。
  • 2026-08-23 只读复核确认:生产最近 14 天每日异机备份和新鲜度 watchdog 均成功,ARM dead-man 最近成功;企业微信中的“恢复通知”来自公网健康状态恢复,不代表执行过数据恢复。
  • 2026-07-27 曾在 ARM 使用当时的正确异机批次完成真实格式影子恢复,但不能替代对当前最新自动备份的可恢复性验证。
  • 首次演练时生产产品构建为 7b86ffb,已通过公网 /ready 和 migration pending=none;当时仓库与来源构建 ec682565 均有 65 个 Prisma migration 目录,二者之间 migration 目录无变化。该数字是本次现场事实,不再作为跨构建硬编码合同。
  • 现有恢复脚本默认只接受带 shadow、drill 或 restore_test 标记的数据库名,并拒绝生产式目标、确认值不一致、非空附件目标和危险归档路径。

Requirements

R1. 恢复点选择与前置门禁

  • 只选择 ARM 正式日归档中按 UTC 时间最新的一个完整批次,不触碰 inbox、临时事务目录、旧错误批次或周检查点。
  • 批次必须恰含 database.dump、storage.tar.gz、manifest.json 三个普通文件;manifest 必须为 schema v1、complete,相对文件名、字节数、SHA-256 和来源构建身份全部一致。
  • 批次年龄必须不超过 24 小时。manifest buildSha 必须唯一解析为本仓库已知提交,且该提交是演练开始时公网 /ready 实测当前构建的祖先或与其相同;未知提交、旁支/后继提交、超龄、损坏或文件集歧义判定 NO_GO,磁盘空间不足判定 PAUSE。两者都不得自动改选更旧批次掩盖问题。
  • 构建相等只表示“当前发布后已经形成同源恢复点”,不再作为数据备份可恢复性的必要条件。若来源是当前构建的祖先,必须单独记录发布后同源恢复点滞后 finding,不能把它误报为归档损坏。
  • 执行前后各做一次公网 /ready 只读检查,仅用于证明生产未受影响;该结果不得充当影子恢复后的应用启动证据。

R2. ARM 隔离和数据保护

  • 演练仅在 ARM 上创建本轮专属、权限 0700 的临时工作区、一次性 PostgreSQL 16 容器和空附件目录;不得在开发机下载真实备份。
  • PostgreSQL 不发布宿主端口,不加入现有 OA/监控网络,不复用任何现有容器名、数据库名、volume 或目录;影子数据库名必须含 drill 标记。
  • 先把所选批次复制到临时工作区并重新校验 manifest 与三文件 SHA-256,再对临时副本执行恢复;正式 ARM 归档始终只读,不直接解包或改名。
  • 不读取或复用生产 .env、JWT、Webhook、企业微信 Secret、真实数据库口令或业务账号;影子数据库只使用本轮随机临时凭据,凭据不进入聊天、仓库、报告或命令参数证据。
  • 含现场路径、命名或构建值的私有渲染 JSON 和渲染后候选不得进入 Git 历史;若位于仓库内,必须置于专用 ignored 目录,合同脚本须在创建候选前机械验证该 ignore 状态,未忽略即失败关闭。

R3. 数据库恢复验证

  • 使用现有 scripts/restore-db.sh 的 RESTORE_SCOPE=shadow 和精确数据库名确认门禁,将最新 database.dump 恢复到一次性 PostgreSQL 16 容器。
  • pg_restore --list 和实际 restore 必须成功。从 manifest 来源提交动态冻结其 Prisma migration 目录集合;恢复后 _prisma_migrations 中成功且未回滚的 migration 名称必须与该来源集合一致,并且不存在失败、回滚中或来源未知的 migration。不得用硬编码数量替代集合核对。
  • 同时比较来源提交与当前构建的 migration 目录集合:集合相同则记录“当前 schema 无额外升级步骤”;当前构建新增 migration 时,原始备份恢复仍可继续验证,但“升级到当前 schema”标为 PAUSE/未验证,另行设计仅针对影子库的 migration 演练,不在本轮临时执行。
  • 固定核对组织、人员、角色、采购、报销、发票池、付款批次、合同、项目及三类核心附件表的存在性和行数;只记录表名、数量和 PASS/FAIL,不记录业务字段或正文。
  • 所有验证 SQL 必须只读并开启错误即停;不执行 migration、seed、DDL、数据修补或业务写入。

R4. 附件恢复与关联验证

  • 使用现有 scripts/restore-storage.sh 的 shadow 门禁恢复 storage.tar.gz 到本轮空目录;归档路径、链接类型和目标空目录检查必须通过。
  • 归档普通文件数必须与解包后普通文件数一致;对数据库中本地存储型的发票、采购附件、报销附件、付款回单和请假附件引用,验证相对路径安全、目标文件存在,并在已有 SHA-256/大小字段时核对哈希或大小。
  • 发现 s3: 引用、缺失文件、路径逃逸、哈希/大小不一致或 DB 与附件批次不配对时判定 NO_GO;不输出文件名、原始路径、票据内容或附件内容。

R5. 计时、证据和清理

  • RPO 定义为演练开始 UTC 时间减 manifest createdAt;必须小于等于 24 小时才通过。本次实测值只用于当前恢复演练,不形成 SLA 承诺。
  • “数据恢复 RTO”从通过前置门禁后、开始复制批次到临时工作区之前计时,到 DB/附件恢复和全部数据一致性检查通过时结束;不得把历史合成演练耗时或生产 /ready 计入本轮 RTO。
  • 另行记录“同源恢复点可用滞后”:当前发布合同刷新时间到首个相同构建的 ARM 完整批次到达时间。该指标与数据 RPO、数据恢复 RTO 分开,不形成 SLA;即使恢复未执行,观测到的发布后空窗仍是有效演练发现。
  • 证据写入当前 Trellis task,只记录日期、角色、批次脱敏标识、构建短标识、年龄、耗时、数量、状态、finding 类别和清理结果;不保存远端地址、完整目录、凭据、真实账号、业务正文或原始 payload。
  • 成功或失败后只清理本轮明确创建的一次性容器、临时工作区和临时凭据;清理后确认无容器、端口、临时目录残留。任何目标身份不明确时停止清理并人工复核,绝不删除正式归档。

Acceptance Criteria

  • AC1:ARM 最新正式日批次通过文件集、manifest、字节数和 SHA-256 校验,来源构建是当前构建的祖先或相同,且实测 RPO 不超过 24 小时;是否已形成当前构建同源恢复点单独记录。
  • AC2:腾讯云生产在演练前后 /ready 均正常,本次操作对生产容器、数据库、附件、配置、timer 和业务数据零写入、零重启、零切换。
  • AC3:一次性 PostgreSQL 16 影子库无宿主端口,数据库恢复成功;恢复 migration 集合与 manifest 来源提交一致且无失败/未知项,来源与当前 migration 差异已明确;固定 12 类关键表均可读并形成仅含数量的脱敏摘要。
  • AC4:附件安全恢复成功,归档与解包普通文件数一致;所有纳入范围的本地附件引用均安全且存在,适用的哈希/大小核对无不一致。
  • AC5:记录本轮数据恢复 RTO、RPO、同源恢复点可用滞后、开始/结束时间、失败点或 finding、Go/Pause/No-Go 结论;即使恢复未开始也保留已观测空窗,不把任何指标表述为完整应用灾备 RTO 或 SLA。
  • AC6:演练结束后本轮容器、端口、网络和临时数据全部清理,ARM 正式归档与现有 dead-man/健康监控不受影响。
  • AC7:任务校验、Trellis 治理、流程合同、文档审计、敏感扫描和 git diff --check 通过;证据不含敏感信息。

Out of Scope

  • 不在腾讯云生产执行 restore、停机、重启、数据库 reset、migration、DDL、seed、配置修改、timer 修改或业务写入。
  • 不触发新的生产备份,不删除、替换或轮转 ARM 正式归档,不发送企业微信测试消息。
  • 不把真实备份下载到开发机,不把 ARM 变成 OA 部署目标或长期运行 OA server。
  • 本轮不启动隔离 OA 应用容器、不做登录、报销详情或附件下载页面验证,也不对恢复库执行 Prisma migration;因此只形成“原始数据恢复 RTO”和静态 migration 兼容判断,完整应用恢复、schema 升级和切换 RTO 仍需未来灾难恢复专项验证。
  • 不修改备份/恢复产品代码;若现有脚本在真实最新批次上暴露缺陷,先停止并另行回到规划,不现场放宽安全门禁。

Blocking Open Questions

  • 本次受限演练已批准并完成,入口已消费,不再申请同范围重跑。完整密钥保管/敏感解密恢复、历史缺件及完整计时覆盖仍按顶部结果保留;未来新增实质恢复范围需另行规划,不复用本次单次授权。

Test Plan

  • 校验最新批次 manifest、三文件集合、字节数、SHA-256、构建身份和 24 小时新鲜度。
  • 通过现有 shadow 恢复脚本完成 PostgreSQL 16 和附件恢复,验证来源提交的 migration 集合、与当前构建的 migration 差异、固定 12 类表及附件引用聚合。
  • 复核无宿主端口、生产前后 /ready、ARM 监控未受影响和本轮临时对象零残留。
  • 运行 task validate、Trellis governance、process contract、docs registry、敏感扫描和 git diff --check。

2026-09-09 数据库最小恢复执行授权

用户对“通过ARM既有合法管理员通道,核对当时最新正式备份,在一次性隔离PostgreSQL中执行数据库恢复、检查结果并清理本轮临时资源”明确回复“是”。本次只批准数据库,不解包附件、不启动OA、不触碰生产、不改权限或sudoers、不删除正式备份。权限不足或恢复失败停止并报告;不需再次征询同一范围的授权。

最初代理非交互管理员通道检查未通过;随后用户通过既有 root 交互会话执行获批操作。2026-09-09 管理员回传本批次 DB_RESTORE_PASS、66 项 migration 集合匹配、12 类关键表可读和本轮资源清理完成。该结果并非代理独立远端验证;附件与应用仍未验,整体任务保持未完成。详见 research/restore-drill-evidence.md 同日记录。