13 lines
3.4 KiB
Markdown
13 lines
3.4 KiB
Markdown
# 零录入票据归集跨租户隔离与失败回滚
|
||
|
||
日期:2026-07-16
|
||
文档路径:document/development/2026-07-16/dev-logs/bugs/zero-entry-receipt-tenant-and-rollback.md
|
||
|
||
## 修复记录
|
||
- 09:35:记录 bug 修复:零录入票据归集跨租户隔离与失败回滚。(bug-log:54754b55)
|
||
- Git 提交检查:2026-07-16 09:35 CST 执行 `git fetch --all --prune` 成功;`origin/main` 没有远端新提交;本地 ahead 11 条,依次是 `54754b55` 个人费用申请记忆、`211f85d9` 统一核验申请工作流、`5b246307` 申请预览决策、`a662cfe6` 申请反馈账本、`5ed34c2b` 历史费用单回填、`11275e4b` 迁移所有权安全、`1347366b` 费用时间线与草稿事件安全、`22669a90` 统一费用事件时间线、`a616b30c` AI 申请提交事务、`653eda05` Bearer 会话、`661990b2` 事务化费用事件;这些提交均为本轮开始前已有的本地检查点,本次未合并或改写历史。
|
||
- 修改:`receipt_folder.py` 把票据命名空间从仅用户名收口为租户与用户联合边界,并用稳定摘要消除有损清洗碰撞;`attachment_association_jobs.py` 同时校验任务租户和 owner,平台管理员也不能跨租户读取。`expense_receipt_matcher.py` 改用纯只读本人 Claim 查询并要求每份票据独立满足证据门槛。`expense_receipt_association.py` 将 Claim、Case、Link、业务事件、票据元数据和新旧附件目录纳入统一失败补偿,避免事件或文件中途异常留下半关联状态。
|
||
- 操作:新增持久化 `attachment_association_jobs` 模型、仓储和 `20260716_0006` 迁移;任务按租户、owner、票据集合和 generation 去重,运行态使用租约、`attempt_count + running` 栅栏、进程锁和 PostgreSQL advisory lock。同票据与同 Claim 分别串行化,Claim 锁内清理旧事务并重新匹配;待确认或失败任务保留原代审计历史,以新 generation 重新评估,自动关联成功代继续幂等复用。低置信、相近候选、混入无关票据、无草稿和仅有申请统一返回需要确认的零写入结果;同步扩展前端任务协议、会话恢复、候选卡片、幂等重放和成功风险复核提示,并更新功能 CONCEPT/TODO。
|
||
- 验证:所有后端验证均在项目主容器内执行并设置 60 秒超时。后端归集专项 20 项、归集与相邻服务及迁移所有权组合回归 71 项通过,覆盖纯只读确认、逐票据证据、事件失败、文件元数据失败、既有申请 Case 保留、同票据并发、不同票据并发同 Claim、租约过期恢复、旧 worker/迟到回调栅栏、待确认重评估、失败代际历史保留、普通用户及管理员跨租户隔离;前端关联链路组合回归 29 项通过;相关 Python 文件 `ruff --select F,I,UP` 通过;Vite 生产构建通过。一次性 tmpfs PostgreSQL 17 中完整迁移循环 4 项通过,临时数据库已清理,持久开发库未修改。
|
||
- 影响:同用户名在不同租户的票据、任务和候选 Claim 不再互相可见;自动归集中途失败不会留下已改 Claim、孤立 Case/Link/Event、错误票据状态或被覆盖的旧附件目录;低证据和混合票据批次只提示确认,不扩大自动化权限;任务重启或多 worker 后仍可查询和恢复。非默认租户原先落在旧用户名目录中的历史票据改为 fail-closed,不做未经确认的跨目录迁移。
|