feat(approval): add safe risk disposition workflow
This commit is contained in:
@@ -133,6 +133,12 @@
|
||||
- 以风险、金额、预算影响、等待时长和证据完整度排序。
|
||||
- 支持批量处理低风险事项,行内保留真实按钮和键盘可访问入口。
|
||||
- 高风险审批展示模型版本、规则版本、政策依据、证据和人工覆盖原因。
|
||||
- 当前首个可用切片由 `/api/v1/approval-workbench/items` 返回统一投影,优先级由风险、SLA、预算、金额和证据完整度共同计算,并返回每个分项、排序原因和仅供参考的 AI 建议;前端展示状态与服务端业务状态分离,避免乐观并发前置条件被中文标签覆盖。
|
||||
- 批准、退回和付款均使用动作协议:客户端在确认时冻结 `request_id`、预期状态和预期审批节点;服务端以租户 + 操作人 + 请求 ID 唯一账本、请求指纹、PostgreSQL advisory lock、Claim 行锁和同事务业务事件确保相同请求可安全重放,不同内容或陈旧快照以 409 拒绝。
|
||||
- 高风险门禁同时读取持久化风险观察和尚未落表的原始风险标记;未处置的严重/高危可行动风险在路由、预算和状态修改前阻断,原始重大风险持久化失败时 fail-closed。仅明确标记为 `route_review` 的路由型风险进入对应审批节点而不冒充已解决。
|
||||
- 风险处置采用判定与生命周期双状态:支持确认风险、误报、补件、开始整改、申请豁免和完成处置;每个动作绑定请求指纹、预期版本、操作人和只追加事件。完整证据只对管理员或当前审批人开放,风险池对财务/管理/预算角色开放,处置写入由管理员或锁内重新确认的当前审批人完成。
|
||||
- 并发锁顺序固定为 Claim → RiskObservation → RiskDisposition,审批动作、人工处置和 Hermes 扫描共用 Claim 锁。扫描在锁外计算、锁内校验状态与更新时间,过期快照丢弃并等待下一轮,避免旧图结果覆盖刚完成的人工处置。
|
||||
- 尚未完成边界:工作台当前仍为服务内排序后截断;SLA 仍基于申请提交时间而不是当前节点进入时间;批量审批、委托/转交、加签/会签、豁免批准/拒绝和原始响应快照重放将在本阶段后续切片完成。
|
||||
|
||||
#### CFO 价值看板
|
||||
|
||||
@@ -540,6 +546,10 @@ docker exec -w /app -e SERVER_VENV_DIR=/tmp/x-financial-server-venv \
|
||||
|
||||
## 本轮实现记录
|
||||
|
||||
- 2026-07-16(审批动作协议):批准、退回和付款统一接入租户化幂等账本、请求指纹、乐观状态/节点前置条件、advisory lock 与 Claim 行锁;完全相同的网络重试不重复扣减预算、写审批、生成事件或付款,不同内容和陈旧状态稳定返回 409。前端确认流在用户确认时冻结请求和前置条件,并保持服务端状态与展示标签分离。
|
||||
- 2026-07-16(风险门禁与处置):高危/严重可行动风险在任何审批副作用前阻断;类型化风险处置覆盖确认、误报、补件、整改、豁免申请和完成处置,具备乐观版本、权限守卫和只追加审计。门禁合并持久观察与未匹配原始风险,重大观察落库失败 fail-closed;审批、处置和 Hermes 扫描通过 Claim 公共锁协调,扫描过期快照不会覆盖新事实。
|
||||
- 2026-07-16(审批工作台):审批中心接入可解释优先级投影,展示风险、预算、金额、等待时长、证据完整度和 advisory-only AI 建议;风险证据卡读取真实持久字段、显示生命周期和明确动作,但不提供绕过风险门禁的直接审批入口。
|
||||
- 2026-07-16(审批与风险验证):容器内审批/风险/费用服务组合 174 项、迁移/所有权 54 项、前端 73 项及 Vite 生产构建通过;一次性 tmpfs PostgreSQL 17 完成 13 项迁移和 1 项真实并发验证,临时数据库自动清理且持久开发库未修改。变更 Python 文件 Ruff F/I 与 `git diff --check` 通过。
|
||||
- 2026-07-16(分层组织记忆):`travel_application.transport_mode` 已形成当前输入/规则 > 企业 > 部门 > 个人的解析链。租户管理员可维护 30-365 天有效的企业/部门低敏记忆;同级冲突 fail-closed,低级覆盖、过期和撤销均返回可解释但脱敏的状态。创建、更新、撤销使用作用域锁、稳定幂等键和请求指纹,响应丢失可安全重放,请求内容变化返回冲突。
|
||||
- 2026-07-16(租户化历史案例):风险观测、人工反馈、few-shot 关系数据和 Qdrant 向量统一绑定租户、场景、制度和规则版本;Hermes 按租户分别构建图与历史,风险规则生成不再回落到默认租户。报销预审与 AI 助手接入只读 `historical_case_evidence`,旧版本显式标 stale,检索故障降级为空证据。
|
||||
- 2026-07-16(历史证据隐私边界):公开协议仅返回“历史已确认/历史误报,仅供复核”的固定摘要,不暴露样本 ID、费用单号、人工评论和历史结论原文;历史命中不改变 review ID、规则 findings、passed、blocking count、预算复核或审批路由。
|
||||
|
||||
@@ -166,9 +166,16 @@
|
||||
|
||||
## 7. P1 前端实现:审批例外与 AI 记忆
|
||||
|
||||
- [ ] [CONCEPT: 审批例外工作台] 按风险、金额、预算影响、等待时长和证据完整度排序审批事项。
|
||||
- [ ] [CONCEPT: 审批例外工作台] 展示必要性、预算、政策、相似单、风险证据和 AI 意见草稿。
|
||||
- [x] [CONCEPT: 审批例外工作台] 按风险、金额、预算影响、等待时长和证据完整度排序审批事项。
|
||||
证据:`GET /api/v1/approval-workbench/items` 由 `ApprovalWorkbenchService` 统一生成可解释优先级、风险/SLA/预算/金额/证据分项和排序原因;审批中心使用该投影,不再用前端临时状态覆盖服务端乐观并发状态。
|
||||
- [x] [CONCEPT: 审批例外工作台] 展示必要性、预算、政策、相似单、风险证据和 AI 意见草稿。
|
||||
证据:工作台返回证据完整度、缺失证据、预算影响、风险摘要、历史确认标签及 `advisory_only` AI 建议;详情页风险卡读取持久化证据、政策依据、贡献度和决策轨迹,并明确禁止从风险卡直接审批。
|
||||
- [x] [CONCEPT: 审批例外工作台] 为批准、退回和付款建立幂等动作协议与乐观前置条件。
|
||||
证据:动作请求冻结 `request_id`、`expected_status` 和 `expected_approval_stage`;服务端以租户 + 操作人 + 请求 ID 唯一账本、请求指纹、advisory lock、Claim 行锁和同事务事件保证同请求安全重放,不同内容或过期快照返回 409。
|
||||
- [x] [CONCEPT: 审批例外工作台] 建立高风险门禁与类型化风险处置生命周期。
|
||||
证据:严重/高危可行动风险在任何审批副作用前阻断;确认风险、误报、补件、整改、豁免申请和完成处置具有乐观版本、请求指纹、租户权限、只追加事件和 Claim 共用锁。未持久化的原始高风险仍保守阻断,重大风险持久化失败 fail-closed。
|
||||
- [ ] [CONCEPT: 审批例外工作台] 补齐批量审批、委托、转交、加签、会签和超时升级交互。
|
||||
当前进度:单笔批准/退回/付款协议、优先级工作台、风险证据和处置状态已完成;批量动作、委托/转交、加签/会签、当前节点进入时间 SLA、豁免批准/拒绝及真正的数据库分页尚未实现,不以现有单笔基础冒充完成。
|
||||
- [ ] [CONCEPT: 审批例外工作台] 接入真实企微/钉钉/邮件触达和处理结果回写。
|
||||
- [ ] [CONCEPT: AI 记忆与自动化设置] 新增“我的 AI 记忆”,支持来源解释、修改、忘记和关闭个性化。
|
||||
- [x] [CONCEPT: AI 记忆与自动化设置] 在申请核对表展示常用出行方式的记忆来源、证据数量和“忘记此偏好”,并在保存/提交后区分候选记录与已应用回执。
|
||||
@@ -215,7 +222,9 @@
|
||||
证据:`test_expense_case_endpoints.py` 容器内 8 项通过,覆盖跨租户、无 Case、申请与报销关联摘要以及内部字段递归过滤。
|
||||
- [ ] [CONCEPT: 测试方案] 为 Alembic baseline、升级、旧数据迁移和回滚边界补充 Postgres 集成测试。
|
||||
- [x] [CONCEPT: 测试方案] 为当前 migration-owned schema 切片补充一次性 PostgreSQL 集成测试和危险 URL 防误连门禁。
|
||||
证据:`test_alembic_migrations.py` 默认无显式 URL 时跳过,主机和库名必须带 disposable 标记;当前 0009 Head 在 tmpfs PostgreSQL 17 中 9 项通过,覆盖空库升级、重复升级、关键约束/索引、组织 active 脏数据升级前拒绝、版本化 few-shot 无损降级拒绝、外键级联、base 降级、legacy 哨兵保留、漂移拒绝和再次升级;临时容器自动清理,持久化开发库未被修改。完整 legacy baseline 仍保留在上一条未完成项中。
|
||||
证据:`test_alembic_migrations.py` 默认无显式 URL 时跳过,主机和库名必须带 disposable 标记;当前 0011 Head 在 tmpfs PostgreSQL 17 中 13 项通过,覆盖空库升级、重复升级、审批动作账本、风险处置复合租户约束、只追加事件触发器、非空降级保护、组织 active 脏数据升级前拒绝、版本化 few-shot 无损降级拒绝、外键级联、base 降级、legacy 哨兵保留、漂移拒绝和再次升级;临时容器自动清理,持久化开发库未被修改。完整 legacy baseline 仍保留在上一条未完成项中。
|
||||
- [x] [CONCEPT: 测试方案] 验证审批动作幂等、风险门禁、风险处置权限和共同锁顺序。
|
||||
证据:容器内审批/风险/费用服务组合 174 项、迁移/所有权 54 项、前端审批/风险 73 项通过;一次性 PostgreSQL 17 上 13 项迁移和 1 项真实并发用例通过,证明风险重新打开持有 Claim 锁时审批等待,随后按最新风险阻断且不留下动作账本或审批副作用。
|
||||
- [ ] [CONCEPT: 测试方案] 为连接器幂等、重试、回执、失败恢复、重复付款和对账补充测试。
|
||||
- [ ] [CONCEPT: 测试方案] 跑通申请 → 票据 → 报销 → 预审 → 审批 → 付款 → 入账 → 归档端到端。
|
||||
当前进度:申请批准 → 自动报销草稿 → 票据归集 → 预审 → 报销提交已在同一 Case 中跑通;付款回执、ERP 入账和对账仍未接入。
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
## 修复记录
|
||||
|
||||
- 2026-07-16 15:10:42 CST:修复审批、退回和付款动作缺少稳定请求标识、预期状态与预期审批节点校验的问题。新增租户 + 操作人 + 请求 ID 唯一账本、请求指纹、PostgreSQL advisory lock 与 Claim 行锁;完全相同的重试返回原结果,请求内容变化或状态已过期返回 409,不重复扣减预算、写审批记录、生成业务事件或付款结果。
|
||||
- 2026-07-16 15:10:42 CST:修复高风险观察仅展示但不阻止审批的问题。审批流程在任何路由、预算和业务状态变更之前检查同租户高危/严重风险;只有已判定误报或完成处置的观察允许继续,阻断响应返回稳定错误码和观察状态,整笔动作账本随事务回滚。
|
||||
- 2026-07-16 15:10:42 CST:前端确认弹窗冻结请求 ID、预期单据状态和审批节点,网络结果不确定时复用同一请求,避免用户重试造成重复业务副作用。
|
||||
- 2026-07-16 15:10:42 CST:验证在 `local-x-financial-linux` 容器完成:费用服务 116 项、审批动作/风险门禁/风险处置组合 26 项、报销接口回归 22 项;一次性 tmpfs PostgreSQL 17 迁移循环 13 项通过。变更 Python 文件 Ruff F/I 与格式检查通过。
|
||||
- 2026-07-16 15:10:42 CST:Git 拉取检查已执行;上游没有本地尚未包含的新提交,当前分支相对上游 ahead 14。已有本地提交包括 `ee88a36b` 分层费用学习、`6bdf65bc` 权威预审、`ae3f02c3` 零录入票据闭环等,本次修复在这些能力之上继续演进。
|
||||
- 2026-07-16 15:33:03 CST:交叉审查后修复三项并发绕过:审批动作不再调用会隐式提交的读取修复逻辑,列表/分页 GET 也不再修改审批节点;审批中心保留服务端原始状态并单独生成展示标签;门禁将持久化风险观察与未匹配原始高风险合并,重大观察持久化失败时 fail-closed。
|
||||
- 2026-07-16 15:33:03 CST:审批、风险处置和 Hermes 扫描统一以 Claim 行锁为公共协调点,锁顺序固定为 Claim → Observation → Disposition;Hermes 锁外计算、锁内刷新并核对状态和更新时间,旧快照被丢弃。一次性 PostgreSQL 并发用例证明风险重新打开与审批竞争时,审批等待后读取最新风险并阻断,Claim 节点和动作账本均保持无副作用。
|
||||
- 2026-07-16 15:33:03 CST:最终容器回归为审批/风险/费用服务 174 项、迁移/所有权 54 项、前端审批/风险 73 项通过,Vite 生产构建通过;一次性 tmpfs PostgreSQL 17 上 13 项迁移和 1 项真实并发测试通过,临时数据库自动清理。再次执行 Git 拉取检查,上游仍无新增提交,本地 ahead 14。
|
||||
@@ -0,0 +1,10 @@
|
||||
## 修复记录
|
||||
|
||||
- 2026-07-16 15:10:42 CST:修复风险观察列表和完整证据链读取范围过宽的问题。风险池仅允许管理员、财务、管理层和预算监控角色访问;单据完整风险证据只允许管理员或当前审批人查看,申请人和无权主体统一以 404 隐藏资源存在性,所有查询继续绑定可信租户。
|
||||
- 2026-07-16 15:10:42 CST:修复风险反馈只有自由文本状态、无法可靠表达处置过程的问题。新增类型化判定与生命周期投影,区分确认风险、误报、补件、整改、豁免申请和完成处置;使用乐观版本、请求指纹、行锁和仅追加事件表保证并发与审计一致性,已完成状态不可重新判定。
|
||||
- 2026-07-16 15:10:42 CST:修复前端读取错误 JSON 字段名导致证据、政策依据、贡献度和决策轨迹可能丢失的问题;风险证据卡改用服务端持久字段并展示处置状态、版本和明确动作,高风险卡不提供直接审批入口。
|
||||
- 2026-07-16 15:10:42 CST:验证在 `local-x-financial-linux` 容器完成:风险观察、风险处置、审批工作台与风险门禁组合 26 项,前端审批/风险/动作协议定向 72 项,Vite 生产构建通过;一次性 PostgreSQL 验证了复合租户外键、唯一约束、仅追加触发器和非空降级保护。
|
||||
- 2026-07-16 15:10:42 CST:Git 拉取检查已执行;上游没有本地尚未包含的新提交,当前分支相对上游 ahead 14。已有 ahead 提交均保留在修复上下文中,未修改用户正在维护的财务规则工作簿和历史未跟踪日志。
|
||||
- 2026-07-16 15:33:03 CST:交叉审查后收紧处置动作语义:误报、补件和豁免申请必须提供服务端校验的说明,完成处置必须提供处理结果;版本冲突返回明确消息并触发前端刷新,已完成生命周期不再展示重复动作。
|
||||
- 2026-07-16 15:33:03 CST:处置服务在 Claim 公共锁内重新确认当前审批人,Observation 和 Disposition 使用租户范围行锁及固定顺序;风险观察写入同样获取 Claim 锁。完整证据、风险池和处置权限分别执行最小授权,跨租户和无权资源继续以 404 隐藏。
|
||||
- 2026-07-16 15:33:03 CST:最终验证全部在 `local-x-financial-linux` 容器完成并设置 60 秒超时;相关后端 174 项、迁移/所有权 54 项、前端 73 项与生产构建通过,一次性 PostgreSQL 17 的结构和并发验证 14 项通过。规则工作簿和历史未跟踪日志未被改动或纳入提交。
|
||||
Reference in New Issue
Block a user