完善借款、编号、付款与预算并发及验收(09-06-release-gate-integrity)
汇总已落盘的借款本金与还款、兼容历史编号的周期化编号、付款责任与回单核验、报销进度和排序、PWA/采购事务、错误原因与日期校验及ERP退役。 统一共享预算池锁序和锁后判断,恢复差旅原预算选择,补原因码固定夹具及反向完整性检查。 纳入配套迁移、测试、受管QA、分层验收合同、规约及任务记录;原始证据留在ignored artifacts。 关联任务:09-17-company-loan-principal-repayment、09-17-erp-exclusive-retirement、09-18-reimbursement-periodic-numbering、09-08-reimbursement-payment-closeout、09-18-local-concurrency-product-fixes、09-19-budget-pool-concurrency、09-05-error-explainability-payment-readiness。 预算补审由Claude定向验证2文件928项通过,原4446/28及r6结果仍仅对应各自历史输入;本轮未重跑测试。 完整新候选门禁、R1真实入口接入与12项公网义务仍开放。生产编号未启用、差旅仍冻结;不推送、不部署,不宣称发布资格。
这个提交包含在:
@@ -1,5 +1,110 @@
|
||||
# 隔离备份恢复演练设计
|
||||
|
||||
## 2026-09-19 关联消费者边界(文档澄清,无执行)
|
||||
|
||||
K0 的7类输出仅验证加解密合同,不验证脱敏显示,也不等于所有 crypto 消费者的异常/null 分支均安全。Claude 的静态 PASS 按此范围接收;原合成运行和冻结包保持。
|
||||
|
||||
通知迁移脚本 `server/src/scripts/migrate-notification-channel-secrets.js` 顶层加载 dotenv/config,创建 Prisma 并连接库;--encrypt/--decrypt 都会逐条 update,后者还会将解密值写回旧明文字段。因此仅可只读源码盘点,禁止在 K0 或默认只读 K2 中 import/require/运行,也不能把其执行当作密钥验证。K1只证明保管可用性,不涉及该工具。
|
||||
|
||||
未来 K2 的字段绑定若包含通知渠道,需按来源 schema 明确 config_json 中的适用加密字段和只读样本上限,验证出口仍仅类别/数量/通过-失败-不适用;旧明文、空字段或不适用字段不能冒充解密通过。不修改通知渠道记录、不启动发送服务;真实迁移/轮换另立经审阅和批准的操作方案,不与只读恢复验收捆绑。
|
||||
|
||||
## 2026-09-17 K0/K1/K2:原密钥恢复最小设计(K0已批准,K1/K2另批)
|
||||
|
||||
源码依据:`server/src/config.js:143`字段密钥缺省空;`server/src/services/crypto/index.js:8`解析base64后要求32字节,原格式为AES-256-GCM的`v1:`载荷。此格式不含key id,不能由数据库推导原密钥;32字节只证明长度,不证明生成方式或随机性。沿用`deploy/trialrun/env-recovery-classification.json`的preserve_original及现有runbook,不改分类、生产密钥或产品加密协议。
|
||||
|
||||
K0:只加载生产加密函数与本地模板/分类,不加载真实.env。用临时合成密钥/密文覆盖正确、缺失、错误、篡改、空字段和不可解析输入;验证脚本捕获异常后只报固定类别,不调用可能带原始异常的日志分支。保管合同只记录双角色可用状态;本地模拟保管成功不能写成K1通过。不改已冻R3/app-check的“非空密钥拒绝”,新增验证只在新独立补件中实现并复核,禁止覆盖旧包使旧批准扩大。
|
||||
|
||||
本地落点:`scripts/qa/check-sensitive-recovery-contract.cjs`与`server/tests/unit/services/sensitiveRecoveryContract.test.js`。入口仅在内存生成合成材料,拒绝命令行参数,不读取环境密钥/配置/备份;直接复用生产`resolveFieldEncryptionKey`、`encryptStringOrThrow`与`decryptStringOrThrow`。生产解密返回null不等于成功:空字段是NOT_TESTED,畸形非空值是STOP;错误密钥或认证篡改异常只转固定DECRYPTION_FAILED,不使用带原始Error日志的safe分支。PASS要求合成明文精确匹配,报告保留真实来源UNKNOWN、custody/realDataDecryption为NOT_TESTED。此补件不是K2真实输入接口;不增加产品加密协议、严格编码或业务字段格式的覆盖声明。临时Buffer清零不代表JavaScript字符串或进程内存已获安全擦除保证,真实秘密载体仍须另审。
|
||||
|
||||
K1:实际保管渠道优先复用既有仓库外共享保管或加密封存,由管理员与公司责任人取用;若无渠道,只提交最小建立方案,不能先创建或导出Secret。原值如需在受控环境内比对,只输出match/mismatch/unavailable,不输出独立密钥摘要、密文示例、账户或保管路径。未批准前只整理空白检查清单,不搜索秘密值或历史命令。记录保管能力,不要求用户证明未知生成历史。
|
||||
|
||||
K2准入:K0通过、K1具备可用原密钥、安全注入方式经过独立只读审查,并得到具体一次操作批准后才执行。重新核最新合格源/镜像/平台/时效/容量;上次影子库已删除,不为省事复用已消费入口。若需要重建影子库,明确计入新批准范围,不称为只补一个无需恢复的按钮。
|
||||
|
||||
拟定隔离边界:不发布宿主端口,非root/只读根/无外网、禁后台任务和持久日志;不把Secret放进命令参数、Docker环境元数据、源码、输出或普通备份。经批准由管理员通过短时只读秘密挂载或等价受保护内存输入提供原密钥,最终选择及跨主机传输方式必须在K2绑定审查中明确,不能将这两种机制当作都已获批。关闭xtrace/core dump,失败也精确清理本轮秘密载体和影子资源,不删除原保管副本。
|
||||
|
||||
从恢复库只读选取批准字段类别/数量的密文;验证认证解密及既有格式约束,只输出类别级数量、通过/失败/不适用,不回显明文、尾号或原始异常。只读DB身份、禁止通知/OCR/外联与停止条件沿原合同;未纳入字段类别保持NOT_TESTED。历史缺件、真实密码登录及完整RTO边界与解密结果分开,不因K2成功把全部灾备改为PASS。
|
||||
|
||||
回退不是换密钥:本计划不改生产配置/密文,失败处置为停止、销毁本轮受控临时载体并保留脱敏失败原因;旧备份、旧钥和旧制品不动。若必须修改产品注入接口或密文协议才能安全执行,则暂停K2并另审该变更,不在演练现场临时补代码。
|
||||
|
||||
## 2026-09-17 K1操作提案与K2绑定清单(准备,未执行)
|
||||
|
||||
K1先由系统管理员和公司责任人在既有受控保管界面分别核验该保管项是否可取用;代理不接触密钥值,不打开真实配置、不搜索shell历史、不建立新传输通道。进入前列明两类操作者、既有渠道类别、核验时间窗口、仅可用性检查范围和暴露风险;取得该具体范围批准后才实际访问。每个角色只回传AVAILABLE/UNAVAILABLE及时间,双角色同一原值的匹配仍为NOT_TESTED,K1可用不等于备份可解密。若仅有一人/原主机保管或无法取得,则停止并提出仓库外共享保管或加密封存的另审建立方案,不能自行复制或换钥。
|
||||
|
||||
K2先填下列绑定项;目前均未取得新现场事实,不能将旧R3参数或过期源填入当作现成方案。
|
||||
|
||||
| 绑定项 | 必需事实与进入条件 | 当前状态 |
|
||||
| --- | --- | --- |
|
||||
| 备份与制品 | 最新合格成对源、manifest/来源版本/实际目录、同源镜像及平台、新鲜度与容量 | NOT_BOUND |
|
||||
| 适用字段 | 按来源schema列出实际存在的加密字段类别及只读样本上限;银行/身份/通知/核对材料分别说明,零适用样本为NOT_TESTED | NOT_BOUND |
|
||||
| 读取身份 | 专属只读DB权限;不启动任务、OCR、通知或业务写入,无外网、无宿主端口 | NOT_BOUND |
|
||||
| 秘密输入 | 管理员提供短时只读挂载或受保护内存输入,选择一种并审查传输;禁argv、Docker环境元数据、持久日志、core dump | NOT_BOUND |
|
||||
| 验证出口 | 仅类别/数量/PASS-FAIL-NOT_TESTED;错钥/损坏停止且无原异常/密文/明文/密钥摘要 | NOT_BOUND |
|
||||
| 资源与清理 | 一次性资源精确身份、异常清理、秘密载体销毁与原保管副本保留;清理失败保留未完成状态 | NOT_BOUND |
|
||||
| 审阅与授权 | 候选摘要绑定独立只读意见、具体一次新恢复范围与批准;重建影子库计入范围 | PENDING |
|
||||
|
||||
本地源码显示存在银行账户、身份、通知与付款核对相关加密用途;具体源schema和实际非空字段尚未核实,不把当前HEAD字段集合当作未来备份覆盖证明。完整附件、历史缺件、原密码登录与完整RTO仍分别验收。
|
||||
|
||||
## 2026-09-17 b1新源匹配ARM64镜像准备(已获构建批准)
|
||||
|
||||
用户已明确批准本节固定b1源码的ARM64镜像构建。批准仅涵盖构建及输入/产物元数据验证,不启动应用、不部署、不恢复、不读生产密钥、不删除旧制品;不再重复索取同范围批准。08:45 CST构建及元数据回读已完成,详情见implement最新回执。
|
||||
|
||||
最新已核ARM自然源manifest d9a23d59…、来源提交b1d25d3c9939c0020e7b4f75868cd97c35c16ba8/树a9f1c8abd16b260d23ee38508b3ff3c51710371e;生产原image9a9d2dfd…为amd64,ARM没有对应可证明制品,不能以旧b7的arm64镜像替代。两源码的实际部署Dockerfile、依赖锁、BIFF8 requirements与71项迁移集合/校验和相同,应用源码仍不同。
|
||||
|
||||
本轮批准一次b1原生ARM64镜像构建:仅导出该固定提交的client/server/scripts/部署Dockerfile/.dockerignore,不含工作树差异、.env、密钥、真实备份或业务资料;沿前次已验方式,将唯一FROM固定到已在ARM的node@sha256:c610fcdfb1d5b4740dd70c284ed3cb16bb857e0f7166196e36a5501df7a3aa32,其余Dockerfile不改。执行前再核base身份/平台和可用资源,不擅自拉另一base或启用仿真;构建所需依赖网络与后续恢复禁外联网络分开。
|
||||
|
||||
只创建本轮专属b1镜像/构建缓存及必要临时源码上下文,固定BUILD_SHA/VITE_BUILD_SHA与OCI revision;登记源码逐件/锁文件/构建上下文/镜像摘要,构建上限30分钟,失败保留脱敏回执、不自动重试。保留旧b7镜像/正式归档/共享缓存,不prune。产物只代表同源码跨平台重建,不是原amd64字节复原;不启动OA服务、不恢复数据库、不部署、不读生产字段密钥或修改业务状态。
|
||||
|
||||
成功后按新制品身份完成源/目录/应用配置重绑及补充验证;R3独立内容复核和执行时fresh preflight仍不可跳过。本节固定新版本构建已经另获批准;不把构建批准延伸为任意版本构建、应用启动或立即恢复,原一次受限恢复仍受复核/绑定/现场门禁约束。
|
||||
|
||||
## 2026-09-17 已批准:已确认历史缺件单列验收
|
||||
|
||||
当前R2实际恢复在附件引用检查停止,数据库/清理/后验PASS。现已找到09-11原处置证据和当前只读回读:精确一条manual归档发票,无七类业务关联;旧决定仅“文档标记并保留”。09-17用户已明确批准以下最小验收增量;不把当前生产扫描冒充冻结副本的最终归因,独立复核仍在执行前。
|
||||
|
||||
1. R2包与本次NO_GO回执永久保留。新候选需重新冻结并做增量复核,不直接修改已审包或复用已消费入口;源/镜像/生产版本及24小时门禁仍执行时核验,源变化或过期停止重新绑定。
|
||||
2. 历史例外只绑定recordIdentity 363834359af7b655648a2562b80559fa132093ffefaabcddb8cc827a7505a3aa和pathIdentity b22a116257e8b2e3922ee32f366935260c16bebc7fd93dcd672febb7d1fdbc5f,以及原处置证据SHA256;仅承认原件不存在,不接受替换成不同大小/哈希的文件、外部存储、非法路径或重复目标。
|
||||
3. 在本轮恢复副本重新验证恰好一条目标、manual归档与归档时间、报销/明细/差旅关联为空,以及其他四类附件表均未引用同路径;任何条件不成立仍NO_GO。其他所有引用继续安全路径/存在/适用大小/SHA检查;不按archived泛化过滤、不剔除检查行、不修补数据库或伪造文件。
|
||||
4. 输出总引用数、原始不一致数、明确已接受缺件数与未知不一致数,并保留原校验结果;只有已接受缺件恰好为这一条且未知不一致为0才允许继续既有影子应用只读验收。已有哈希的其余文件必须实际核完,不以当前生产61条元数据正常替代。
|
||||
5. 附件结果记为“存在一条已接受历史缺件”,不得写完整性PASS;即使DB/其余附件/app均成功,整体只给包含历史缺件与解密未验的PARTIAL。不新增生产写入、真实密码登录、云调用或敏感解密范围;无hash字段及请假附件覆盖限制保留。
|
||||
6. 本地定向验证覆盖精确例外通过、身份/归档/关联漂移拒绝、新缺件/大小/摘要不一致拒绝、未知路径或多个目标拒绝、保留原始计数及失败停止、清理/后验;独立增量复核后才生成新的单次管理员入口。此提案批准可合并本地实施与上述一次受限恢复,不为相同范围反复索取批准;实际sudo仍需管理员终端交互。
|
||||
|
||||
## 2026-09-16 单次隔离恢复审批清单(待批准,非执行回执)
|
||||
|
||||
本节将P2收敛为有限范围的一次演练,不包含生产回滚/部署。来源为已核ARM自然批次manifest 0913782f84bf7253a8ea1a24e727f8c20a6a637c58737b5df767331809dd2f4e,build b7d2285d、树0ef8f7d5597b1fc425888596241d041585aaccab;应用固定ARM64 image 1d4d70f843976e327106d53d13d45118e22372ac6e71cbe7db96953315b4fed3,PostgreSQL固定已核PG_MAJOR=16的ARM64 image c05eced0bdb41ea9b95a656472a6aa4d50cad0d8a2e33d14eb1c53fd6204f2ae。两者启动前再核身份,不用tag代替digest。源创建于09-15T18:30:05Z,执行时超过24小时则停止并重新绑定最新源/镜像,不自动回退或套用旧批准。
|
||||
|
||||
### 一次批准的允许动作
|
||||
|
||||
1. 由具备ARM归档读取权限的执行方式复核固定根身份、三文件集合/摘要、最新性、生产ready及ARM监控;读取归档头计算解压体积,不向开发机传输真实备份。允许本轮私有副本、影子DB/storage、两个隔离运行容器及必要的一次性验证工具;禁止访问其他业务目录或挂载Docker socket到容器。
|
||||
2. 创建带唯一runId标签的内部网络和专属空卷,均不发布宿主端口、不加现有网络。DB恢复使用原restore-db.sh的shadow/数据库精确名称/容器ID门禁;附件使用原restore-storage.sh和引用校验器,恢复失败或引用不一致即停,不排除“测试件”。来源迁移名称/校验和从固定提交生成,核固定12类表及附件聚合,不输出业务字段。
|
||||
3. 恢复完成后,仅在影子库创建临时只读LOGIN角色:非superuser/createdb/createrole/replication,授CONNECT、必要schema USAGE和SELECT,不授序列使用或DML;影子库撤销PUBLIC的CREATE/TEMP权限并设置角色默认只读事务。此处权限DDL属于明确允许的影子环境准备,不声称“恢复后全部零DDL”;不改任何员工、角色业务表、付款状态或原备份。
|
||||
4. 应用环境从空白allowlist组装,使用本轮随机且互不相同的JWT_SECRET、AUTH_CODE_PEPPER和影子DB口令;不读取生产.env/密钥。NODE_ENV=production、TRUST_PROXY_HOPS=1、JOBS_ENABLED=false、OCR_PROVIDER=off、STORAGE_DRIVER=local;EMAIL/WECOM/ERP_SYNC/TRAVEL_SYNC/IM_NOTIFIER/OIDC/OTEL_TRACES/AI_ASSIST等启用项均关闭,未列出的外部Secret/URL不传入。FIELD_ENCRYPTION_KEY保持缺省空值,不用随机密钥伪装可解历史数据。internal网络为主要禁外联防线,不改宿主防火墙/DNS。
|
||||
5. 原镜像默认入口启动应用;根文件系统只读、附件卷只读并预建storage目录,专属/tmp可写;不运行migrate/seed/worker。应用使用非root、cap-drop=ALL、no-new-privileges,DB按官方入口所需权限独立配置。日志不持久化真实请求/响应,docker log-driver=none,探针只输出状态/计数;若需诊断,仅脱敏错误类别,不回传原件、账户或完整payload。
|
||||
6. 在隔离容器内访问/ready和实际GET /api/reimbursements?scope=own&page=1&pageSize=1:用影子JWT签名秘密生成不超过120秒的测试token,subject/tokenVersion取恢复库中确有报销及expense.view_own资格的active身份,不改其密码/角色,不授管理员通配。只读比较响应数量/选中记录与同身份DB期望,不打印记录内容;匿名请求应401。此项明确是“影子签发凭证+真实认证中间件/权限+恢复库读取”,不是员工真实密码登录、SSO、浏览器验收或生产JWT链路。没有合格身份则APP_READ_BLOCKED,不造账号或扩权。
|
||||
7. 完成或失败均删除本轮精确ID/标签匹配的运行容器、临时凭证、私有副本/卷和网络,保留正式归档、构建镜像及共享缓存;不prune、不改sudoers、不长期安装runner。复核源摘要/元数据、生产ready和ARMtimer,清理失败记录cleanupPending,不出整体PASS。清理为逻辑移除,不宣称物理安全擦除。
|
||||
|
||||
### 资源和停止门禁
|
||||
|
||||
目标新测可用内存10.24GiB、磁盘87.91GiB、2 CPU,仅为规划参考。启动前要求MemAvailable至少6GiB、磁盘至少20GiB。采用专属容量受限tmpfs卷:DB数据2GiB、附件512MiB、归档副本128MiB;先核tar普通文件展开总量不超过附件限额,DB超容直接失败不自动扩容。DB容器memory=2560MiB、memory-swap同值、cpu=1、pids=256;应用memory=1024MiB、memory-swap同值、cpu=1、pids=256;临时工具顺序运行,memory=512MiB、cpu=1、pids=128。不能实现容量上限则前置停止,不把容量监测误写成硬限制。实际宿主swap及tmpfs支持须预检,既有宿主配置不改。
|
||||
|
||||
总时限30分钟(超时停止本轮任务并清理),DB恢复最多10分钟、应用就绪最多120秒、单个只读SQL最多20秒/锁等待5秒。资源达到上限、OOM、镜像/源身份变化、归档不安全、出现外部存储引用/缺件或任何未计划写入时停止;不为了通过而扩大范围。RTO分别记录复制/DB/附件/应用/清理耗时,RPO取恢复开始减备份createdAt,不以镜像构建耗时冒充恢复耗时。
|
||||
|
||||
### 结果边界与批准前本地验证
|
||||
|
||||
输出DB_RESTORE、STORAGE_REFERENCES、APP_HEALTH、APP_AUTHENTICATED_READ、SENSITIVE_DECRYPTION、cleanup分别状态。因本清单明确不使用生产字段密钥,SENSITIVE_DECRYPTION固定NOT_TESTED,整项完整应用灾备验收保留PARTIAL,不把其余阶段PASS合成为全量恢复通过;后续敏感字段恢复要独立设计受控密钥使用,不再次向用户索要密钥值。
|
||||
|
||||
原版本源码静态核实:认证只读取employee/roles并检查token_version,报销列表及委托读取为SELECT链;index→buildApp入口有storage mkdir、startJobs,JOBS_ENABLED=false才提前退出,tracing关闭才不启动SDK。已用原config源码和全新合成秘密执行纯配置验证,9项后台/外部开关关闭、OCR off、字段密钥空值PASS;此验证未初始化应用或数据库。实际网络隔离、资源/DB权限、匿名拒绝、带凭证读取与异常清理仍须先做本地合成合同验证,再冻结执行器摘要供已批准范围执行,不能将当前文档称为已可直接运行的恢复脚本。
|
||||
|
||||
## 2026-09-16 设计接续与前置决策
|
||||
|
||||
本节优先于旧单纯DB/storage阶段“不启动OA server”的约束。方案仍以ARM隔离为首选,不迁往个人开发机或生产宿主;应用容器是单次恢复申请中的第三阶段,不是当前已获授权的操作。
|
||||
|
||||
1. P1先核源build/image/platform和ARM平台。镜像与源提交不匹配、缺少匹配平台镜像或ARM不能执行时停止应用阶段;不自动启用跨架构仿真。需要同提交匹配平台构建或其他隔离目标时另列来源/依赖/构建证据及批准范围。
|
||||
2. 用原backup-all/restore-db/restore-storage工具及现有校验器,不新建通用runner。固定私有临时副本、专属空卷/网络/容器名,无宿主端口、无生产数据挂载、不接现有业务网络;应用仅连本轮影子库与只读附件副本。采用专属internal网络及已核应用配置同时阻止OCR/邮件/企微/ERP/支付/队列/cron等出站或后台写入,不改宿主共享防火墙、DNS或生产服务。
|
||||
3. 隔离库恢复阶段可写,完成后应用验证使用最小权限只读连接;禁migrate/seed/任务启动。逐一确认启动入口没有隐式写入前再启动。需要身份/解密的读取检查先设计受控方式、纳入审批;缺条件时只报已验证阶段,不伪造APP_PASS。
|
||||
4. 资源预算基线为已有“批次总字节数5倍+2GiB”,再计镜像归档/解包空间、实际storage解压体积及DB膨胀估计;取保守较大值,不能把压缩包5倍当容量保证。申请时给出CPU/内存/磁盘/超时硬上限、停止水位,精确数值由目标只读测量后冻结,不套用旧89GiB。达到上限先停止本轮写入,再按精确资源清单处置,不清旧备份腾空间。
|
||||
5. 清理仅指本轮创建、标签/ID均匹配的容器、网络、卷和临时副本;禁止prune/通配删除、删除正式源归档、既有镜像或共享卷。失败也走清理,若清理未完成记录cleanupPending并停止,不把资源残留当演练通过。
|
||||
|
||||
无副作用只读核验与恢复写操作分开申请,最终源/目标身份仅进受控ignored操作材料;任务文档只存脱敏标识、摘要和结论。
|
||||
|
||||
## 设计结论
|
||||
|
||||
采用“ARM 最新正式日归档 + 本轮私有临时副本 + 无宿主端口的一次性 PostgreSQL 16 + 空附件目录”的数据恢复方案。腾讯云生产只做演练前后公网 `/ready` 读取,不执行任何远端生产写操作;开发机不接收真实备份。
|
||||
|
||||
在新工单中引用
屏蔽一个用户