feat(platform): close AI expense value loop
Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
## 修复记录
|
||||
|
||||
- 18:14:记录 bug 修复:Agent Run 详情绕过财务看板权限暴露跨租户快照与工具响应。
|
||||
- Git 提交检查:18:13 执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交,本地比 `origin/main` ahead 17 个提交,范围为 `661990b2 feat(expenses): add transactional expense case events` 至 `242d68c3 feat(approval): add task workflow and waiver decisions`,本次未合并或改写这些提交。
|
||||
- 修改:新增 `agent_run_access_policy.py`,识别 `finance_dashboard_snapshot` 运行记录并同时核对 route/ontology 中的 `tenant_id`、`data_scope` 及当前用户财务角色;两份范围标签缺失、不一致、跨租户或与当前数据范围不符时一律 fail-closed。
|
||||
- 修改:`agent_runs.py` 在列表返回前过滤无权查看的财务快照,在详情返回 `snapshot_payload`、工具请求和工具响应前执行同一访问策略;跨租户(包括其他租户 admin)按 404 处理,同租户普通用户按 403 处理,只有同租户 `finance`、`executive` 或 admin 能读取完整快照。
|
||||
- 操作:在 `test_finance_dashboard_tenant_security.py` 构造 tenant-a、tenant-b、无租户旧快照和损坏 data scope 快照,覆盖列表、详情、普通用户、财务用户和跨租户管理员路径;所有命令均在 `local-x-financial-linux` 容器执行。
|
||||
- 验证:Ruff 检查与格式检查通过;财务看板、Agent Run 服务和 Ontology 端点定向回归共 17 个测试通过,证明合法同租户详情仍可读取,跨租户载荷、无范围旧记录和损坏范围记录均不可见。
|
||||
- 影响:已登录用户不能再通过猜测或复用 `run_id` 绕过财务看板领域权限读取其他租户的报销金额快照和工具调用明细,列表入口也不会泄露这些快照的摘要记录。
|
||||
@@ -0,0 +1,8 @@
|
||||
## 修复记录
|
||||
|
||||
- 22:33:记录 bug 修复:Agent Run 轻量列表遗漏语义解析结果。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..@{u}` 为空,未发现 upstream 新提交;本地 `main` ahead 17。审批链相关提交为 `242d68c3`、`28b834ed`、`4940ebc4`,AI 报销学习与申请链相关提交为 `ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`,费用事件与事务链相关提交为 `5ed34c2b`、`1347366b`、`22669a90`、`a616b30c`、`661990b2`,另有迁移安全 `11275e4b` 与会话鉴权 `653eda05`;这些均为当前任务开始前已有的本地提交,本次没有改写或合并。
|
||||
- 修改:在 `agent_run.py` 的轻量仓储查询中按列表已经筛选出的 run_id 批量读取每个 Run 的首条语义解析;在 `agent_runs.py` 中恢复 `AgentRunRead.semantic_parse` 的既有列表契约,同时保留工具调用和大 JSON 字段的轻量预览策略。
|
||||
- 操作:先在容器复现 seeded trace 用例失败,再核对 foundation seed、详情序列化和前端消费字段;没有修改 seed fixture 来掩盖列表序列化断层。
|
||||
- 验证:容器内 `test_agent_runs_service.py`、`test_agent_run_tenant_security.py`、`test_agent_trace_service.py` 与 OnlyOffice 定向测试共 11 项通过;`test_agent_asset_service.py` 全部 28 项通过;相关实现文件 Ruff 与 `git diff --check` 通过。
|
||||
- 影响:Agent Run 列表重新携带真实语义解析摘要,seeded trace、运行轨迹界面和依赖 `semantic_parse` 的流程可继续使用;补充查询仅以租户过滤后的 run_id 为输入,不扩大数据作用域。
|
||||
@@ -0,0 +1,11 @@
|
||||
## 修复记录
|
||||
|
||||
- 22:10:记录 bug 修复:Agent Run 普通日志跨租户读取与统计泄露。
|
||||
- Git 提交检查:22:10 执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交,本地比 `origin/main` ahead 17 个提交,范围为 `661990b2 feat(expenses): add transactional expense case events` 至 `242d68c3 feat(approval): add task workflow and waiver decisions`,本次未合并或改写这些既有提交。
|
||||
- 修改:`agent_run.py` repository 将 `route_json.tenant_id` 与 `ontology_json.tenant_id` 的双重一致性条件下推到 SQL,在排序和 `limit` 前完成租户过滤;任一标记缺失、空作用域或两处标记冲突的记录均 fail-closed,详情也在数据库查询阶段按租户收窄。
|
||||
- 修改:`agent_runs.py` service 新增显式的租户级列表、统计和详情入口,保留带注释的受信任内部跨作用域入口;`agent_run_access_policy.py` 增加返回前二次租户校验,并把财务快照的角色与 `data_scope` 门禁同步下推,防止不可见快照挤占列表和统计窗口。
|
||||
- 修改:Agent Run API 的列表、统计和详情统一使用当前认证租户;普通 run 的跨租户详情、无作用域旧记录和冲突标记记录均返回 404,跨租户管理员也没有旁路,工具 `request_json`/`response_json`、语义 `raw_query` 与错误统计不会跨租户暴露。
|
||||
- 修改:`create_run` 支持显式 `tenant_id` 并同时写入 route/ontology,后续整体更新或 route 合并会保留已验证的双重标记,发现调用方已有冲突标记时拒绝写入;Orchestrator、Ontology、知识同步和财务快照的认证/租户感知创建路径已传入可信 tenant,知识同步的活动任务复用也改为租户内查询。
|
||||
- 操作:新增 `test_agent_run_tenant_security.py`,构造 tenant-a、tenant-b、无作用域、冲突作用域和同租户财务快照,覆盖 limit 前过滤、列表、统计、详情、跨租户 admin 及敏感载荷反向断言;全部命令均在 `local-x-financial-linux` 容器内以 60 秒超时执行。
|
||||
- 验证:Agent Run、财务快照、Ontology 端点、Orchestrator 认证和知识服务定向回归 31 个测试通过;核心变更文件 Ruff、`git diff --check`、PostgreSQL 方言 SQL 编译与代码行数检查通过,最大核心文件 `agent_runs.py` 为 760 行,低于 800 行硬上限。额外 Ontology 全文件回归共 82 个通过、4 个既有业务信号识别用例失败,失败堆栈位于未由本次改动触碰的 `_has_supported_business_signal` 判定。
|
||||
- 影响:Agent Run 日志现在以认证租户为强边界,其他租户、历史无归属记录及损坏作用域记录不再进入列表、统计或详情响应,同时保留内部后台任务按明确可信入口读取的兼容性。
|
||||
@@ -0,0 +1,8 @@
|
||||
## 修复记录
|
||||
|
||||
- 19:19:修复 AI 报销申请预检的阻断提示与既有交互文案契约不一致问题。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||
- 修改:`aiApplicationPrecheckModel.js` 将受限状态和证据快照提示统一为“请先检查”“请先核对”,恢复动作前置关系并与页面测试契约一致。
|
||||
- 操作:检查在 `local-x-financial-linux` 容器内执行,未修改财务规则 XLSX。
|
||||
- 验证:AI 申请预检模型定向前端回归 `4 passed`。
|
||||
- 影响:用户在提交前能清楚理解必须先完成的检查动作,避免因提示语义弱化而误以为可直接继续。
|
||||
@@ -0,0 +1,9 @@
|
||||
## 修复记录
|
||||
|
||||
- 19:01:修复一个 AI 决策关联多条工作流结果后,原申请动作幂等重放可能命中多行的问题。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,从 `661990b2 feat(expenses): add transactional expense case events` 到 `242d68c3 feat(approval): add task workflow and waiver decisions`,其中包含认证、费用 Case、AI 反馈/记忆、迁移安全、预审、风险处置和审批任务能力;本次未拉取、合并或改写这些历史。
|
||||
- 修改:`ExpenseApplicationLearningService._find_existing()` 不再按 `decision_id` 使用可返回多行的无序 scalar 查询,而是根据原始动作的租户、幂等键和稳定 UUID 精确取回首次 Feedback/Outcome,并二次校验租户与 Decision 关联。
|
||||
- 修改:工作流结果桥接只关联事件发生前最新的已提交 AI Decision;原动作重放只回填同 correlation 的预审结论,不会把后续重新提交的退回或审批结果污染到旧决策。
|
||||
- 操作:在 `local-x-financial-linux` 容器中使用 `/tmp/x-financial-server-venv` 运行 Ruff 和 AI 预览/工作流学习回归;未在宿主机执行 Python 或 pytest。
|
||||
- 验证:`test_expense_application_preview_decisions.py` 与 `test_expense_workflow_learning.py` 组合回归 `17 passed`;新用例验证第二次提交后的退回仅关联最新 Decision,多 Outcome/Feedback 不影响原请求重放。
|
||||
- 影响:申请动作重试不再因后续付款、退回或审计结果增多而出现多行异常,也不会将新一轮流程结果错标到历史 AI 建议上。
|
||||
@@ -0,0 +1,9 @@
|
||||
## 修复记录
|
||||
|
||||
- 19:09:修复 CFO 价值看板允许发起无证据手工实际结果、与后端证据门禁冲突的问题。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||
- 修改:`CfoValueOpportunityDrawer.vue` 过滤 `record_realization` 空证据入口,并明确提示实际结果必须来自平台付款事件或可追溯的支付、银行、ERP 凭证;`CfoValueActionDialog.vue` 删除不可履行证据要求的手工金额表单;`useCfoValueDashboard.js` 增加防御性拒绝,避免旁路重新提交空证据。
|
||||
- 修改:`cfo-value-dashboard.test.mjs` 将序列化样例改为带外部凭证的请求,并增加前端不暴露空证据登记入口的回归断言。
|
||||
- 操作:全部检查均在 `local-x-financial-linux` 容器内执行,未修改财务规则 XLSX 或历史开发文档。
|
||||
- 验证:CFO/应用壳/财务看板组合前端回归 `22 passed`;Vite 生产构建成功(`2227 modules transformed`,`built in 5.08s`);`git diff --check -- web` 通过。
|
||||
- 影响:用户不会再遇到“页面允许登记、后端必然拒绝”的假动作;在真实连接器或凭证上传入口接入前,现金节省仍只能依靠可信付款事件落账并由独立财务确认。
|
||||
@@ -0,0 +1,8 @@
|
||||
## 修复记录
|
||||
|
||||
- 21:40:记录 bug 修复:定价毛利率边界舍入越过后端上限。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新增提交;本地 `main` ahead 17 个既有提交,分别为 `242d68c3` 审批任务流、`28b834ed` 不可变动作回放、`4940ebc4` 风险处置、`ee88a36b` 分层费用学习、`6bdf65bc` 权威预审、`ae3f02c3` 零录入票据关联、`54754b55` 个人申请记忆、`211f85d9` 统一申请流、`5b246307` 申请预览决策、`a662cfe6` 申请反馈台账、`5ed34c2b` 历史申请回填、`11275e4b` 迁移归属校验、`1347366b` 费用时间线与草稿事件、`22669a90` 统一费用事件时间线、`a616b30c` AI 申请事务、`653eda05` bearer 会话和 `661990b2` 费用案例事件;均非本次修复产生。
|
||||
- 修改:`commercialWorkspaceModel.js` 先把百分比量化为后端允许的六位小数,再校验目标贡献毛利率严格小于 `0.95`;`CommercialPricingScenarioPanel.vue` 同步把可输入上限收紧到 `94.9999%`,并在模型测试中覆盖舍入临界值。
|
||||
- 操作:在容器 `local-x-financial-linux` 内执行边界探针、商业模型与组件定向测试、全量前端测试、code-size 门禁和 Vite production build。
|
||||
- 验证:临界探针确认 `94.9999%` 序列化为 `0.949999`,`94.999999%` 在请求前被拒绝;商业定向测试 35 项通过,修复后的模型与组件回归 28 项通过,全量前端 802 项通过,code-size 通过,Vite 完成 2246 个模块转换。
|
||||
- 影响:管理员无法再提交一个表面小于 95%、但六位小数量化后等于后端禁值 `0.95` 的场景,避免无意义的 422 往返,同时不改变合法目标毛利率的精度。
|
||||
@@ -0,0 +1,8 @@
|
||||
## 修复记录
|
||||
|
||||
- 21:09:记录 bug 修复:未配置硬配额被前端误显示为剩余 0。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新增提交;本地 `main` ahead 17 个既有提交,分别为 `242d68c3` 审批任务流、`28b834ed` 不可变动作回放、`4940ebc4` 风险处置、`ee88a36b` 分层费用学习、`6bdf65bc` 权威预审、`ae3f02c3` 零录入票据关联、`54754b55` 个人申请记忆、`211f85d9` 统一申请流、`5b246307` 申请预览决策、`a662cfe6` 申请反馈台账、`5ed34c2b` 历史申请回填、`11275e4b` 迁移归属校验、`1347366b` 费用时间线与草稿事件、`22669a90` 统一费用事件时间线、`a616b30c` AI 申请事务、`653eda05` bearer 会话和 `661990b2` 费用案例事件;均非本次修复产生。
|
||||
- 修改:`commercialWorkspaceModel.js` 的数值规范化显式把 `null`、`undefined` 和空字符串保留为“未知”,不再依赖 JavaScript 的 `Number(null) === 0` 隐式转换。
|
||||
- 操作:在容器 `local-x-financial-linux` 内运行商业服务、模型和 Vue 组件定向 Node 测试。
|
||||
- 验证:商业服务、模型和组件定向测试共 28 项通过;全量 `web/tests/*.test.mjs` 共 795 项通过;`code-size-limits` 通过;Vite production build 完成 2233 个模块转换。权益测试同时覆盖不限量、未配置硬上限和失败关闭状态。
|
||||
- 影响:真实硬配额为 0 时仍显示 0;未配置硬配额时显示“未配置硬上限”,避免管理员把未知配置误判为已耗尽配额。
|
||||
@@ -0,0 +1,11 @@
|
||||
## 修复记录
|
||||
|
||||
- 19:37:修复商业分析 ROI 口径、商业合同可见角色和已消费权益历史可变三类问题。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||
- 修改:`commercial_analytics.py` 将客户现金 ROI 统一为“(财务确认现金节省-客户合同收费代理值)/ 客户合同收费代理值”,并把 ratio numerator 改为净收益;禁止截止时间早于窗口开始及无时区分析时间。
|
||||
- 修改:`commercial_access_policy.py` 移除直属经理对套餐、订阅和配额的默认读取权限,仅保留财务、executive 和平台管理员。
|
||||
- 修改:`commercial_admin.py` 在权益已有用量后禁止回改配额、计价配置和有效期,只允许暂停/恢复;完全相同的 PUT 不再无意义增加版本。
|
||||
- 修改:`commercial.py` 强制套餐、订阅、权益、用量和成本事实时间显式带时区,避免跨时区账期歧义。
|
||||
- 操作:全部检查均在 `local-x-financial-linux` 容器内执行,未修改财务规则 XLSX 或历史开发文档。
|
||||
- 验证:商业模型、服务与 HTTP 定向回归 `10 passed`;相关 Ruff 检查通过。
|
||||
- 影响:商业 ROI 与既定合同口径一致,普通直属经理不能查看敏感商业合同,历史用量不会因事后回改权益配置而改变含义。
|
||||
@@ -0,0 +1,23 @@
|
||||
## 修复记录
|
||||
|
||||
- 21:56:记录 bug 修复:非成功 Agent 工具调用可能被商业账本误计量。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新增提交;本地 `main` ahead 17 个既有提交,分别为 `242d68c3` 审批任务流、`28b834ed` 不可变动作回放、`4940ebc4` 风险处置、`ee88a36b` 分层费用学习、`6bdf65bc` 权威预审、`ae3f02c3` 零录入票据关联、`54754b55` 个人申请记忆、`211f85d9` 统一申请流、`5b246307` 申请预览决策、`a662cfe6` 申请反馈台账、`5ed34c2b` 历史申请回填、`11275e4b` 迁移归属校验、`1347366b` 费用时间线与草稿事件、`22669a90` 统一费用事件时间线、`a616b30c` AI 申请事务、`653eda05` bearer 会话和 `661990b2` 费用案例事件;均非本次修复产生。
|
||||
- 修改:`commercial_runtime_policy.py` 把工具调用状态明确分为可计量、采集中和不可计量;`commercial_runtime_metering.py` 只允许 `succeeded/success/ok/completed` 的真实终态调用进入用量与成本账本,`running/pending/queued` 保留待终态补偿语义,`blocked/failed/skipped/cancelled` 不再产生商业事实。同步用 `commercial_runtime_bridge.py` 将未配置租户兼容放行、显式配置后的执行前配额门禁、成功调用后的幂等追加和故障补偿接入真实 `AgentToolCall` 生命周期。
|
||||
- 操作:在 `AgentRunService` 的创建与终态更新后触发桥接计量;在中央工具执行器调用真实 executor 前执行权益预检;将工具执行职责拆到 `orchestrator_tool_execution.py`,让核心编排文件回落到 762 行;所有命令均在 `local-x-financial-linux` 容器内运行并设置 60 秒超时。
|
||||
- 验证:商业运行计量 17 项通过,商业模型/服务/接口/运行计量合计 28 项通过,AgentRun 与 Orchestrator 鉴权相关回归合计 34 项通过;范围内 Ruff、`compileall`、diff 检查和类级 800 行检查通过。全库 code-size 门禁仍被本次范围外的 `RiskRuleGenerationService` 817 行阻断,未在本修复中改动该类。
|
||||
- 影响:未成功完成的工具调用不会再消耗客户配额或形成内部成本;未配置商业计量的既有租户继续执行;已配置租户在执行前受配额约束。计量系统故障时真实工具调用记录保持不变,返回 `requires_reconciliation` 并可按同一工具调用 ID 幂等重试,避免把计量失败伪装成已入账。
|
||||
|
||||
- 22:31:修复执行前只读配额在并发下可超卖、直接调用绕过预占及补偿误判问题。
|
||||
- Git 提交检查:再次执行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`;上游无新增提交,本地仍 ahead 17 个既有提交:`242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`,均非本次修复产生。
|
||||
- 修改:新增 `commercial_runtime_reservations` 运营占位及 `0019` 迁移;在订阅、权益行锁内按“已用量 + 有效预占 + 本次预占”原子校验硬配额。中央 Orchestrator 先生成稳定 tool call ID 并预占,再执行真实工具;成功且真实量不超过预占才追加用量/成本并提交,失败或阻断释放,变量基准没有执行器 hard max 时失败关闭。
|
||||
- 修改:新增持久 `reconciliation_required` 和过期补偿器。缺少执行前预占的旧直接调用不再静默补写正常用量,而是冻结对应容量;补偿器仅在真实工具成功、失败或运行已终止且无调用时结算/释放,运行中或来源不确定继续保留。修复历史过期订阅/权益错误启用门禁、无租户旧运行错误进入补偿、相同预占重试被自身占位判定为额度耗尽三个边界。
|
||||
- 操作:拆出运行时成本解析、周期键、标量校验、预占和补偿模块,相关核心类均低于 800 行;更新模型注册、迁移所有权、前置检查、商业配额投影、AgentRun/中央工具调用点和迁移/并发/补偿测试。所有 Python、Alembic 和 PostgreSQL 验证均在 `local-x-financial-linux` 容器内以 60 秒超时执行。
|
||||
- 验证:运行时定向 25 项通过;商业、Agent、权限、迁移组合 140 项通过;一次性 PostgreSQL 17 商业并发 4 项通过,两个线程竞争一份硬配额时只有一个预占成功;全新 PostgreSQL 0019→0020 完整 Alembic 升降级循环 1 项通过;范围内 Ruff、`compileall`、`git diff --check` 和相关类 800 行检查通过。Orchestrator review 套件保持既有 5 项本体/申请流失败、11 项通过;全库 code-size 仍仅被范围外 `RiskRuleGenerationService` 817 行阻断。
|
||||
- 影响:商业硬配额从“执行前提示”升级为数据库原子 permit,真实工具并发不能再穿透上限;成功事实可幂等结算,计量或成本故障保留可补偿状态且不伪造成功;未配置 runtime meter 的路径继续兼容,历史失效配置不会误拦截用户。
|
||||
|
||||
- 22:39:修复“用量已提交、成本写入失败”缺少持久补偿身份的问题。
|
||||
- Git 提交检查:再次执行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 和 `git log @{u}..HEAD`;上游仍无新增提交,本地仍 ahead 17 个既有提交:`242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`,均非本次修复产生。
|
||||
- 修改:为运行时预占增加 `committed_reconciliation_required` 状态。真实用量已经追加但内部成本失败时,保留真实数量、结算时间和失败原因并进入补偿队列;按同一 tool call 重试只幂等补写成本,成功后恢复 committed。该状态不再计入有效预占,避免真实用量和冻结量重复消耗配额。
|
||||
- 修改:直连调用发生后若当前唯一匹配合同已暂停,允许补偿记录绑定原订阅/权益快照并进入 `reconciliation_required`;正常执行前 reserve 仍严格要求 active/trialing 订阅和 active 权益,不放宽真实执行许可。
|
||||
- 验证:容器内运行时定向 27 项、商业/Agent/权限/迁移组合 183 项通过,另 1 项因未显式配置外部迁移库跳过;全新 PostgreSQL 完整迁移循环 1 项、商业并发 4 项通过。成本故障用例验证 `used=1`、`reserved=0`,重试后用量仍为 1 且只新增 1 条成本;范围内 Ruff 通过。
|
||||
- 影响:成本账本短暂故障不再只依赖日志发现,也不会让客户额度被重复扣减;暂停发生与工具终态竞态时,已发生业务仍有持久、租户隔离的补偿证据。
|
||||
@@ -0,0 +1,17 @@
|
||||
## 修复记录
|
||||
|
||||
- 17:11:记录 bug 修复:财务看板跨租户聚合、快照串租户复用与普通用户越权读取。
|
||||
- Git 提交检查:执行 `git fetch --all --prune` 后未发现 `HEAD..origin/main` 新提交;本地比 `origin/main` ahead 17 个提交,最新为 `242d68c3 feat(approval): add task workflow and waiver decisions`,其余为 `28b834ed` 至 `661990b2` 的审批、费用闭环、AI 记忆、迁移安全和认证能力提交,本次没有合并或改写这些历史。
|
||||
- 修改:`finance_dashboard.py` 通过 `ExpenseClaimTenantScopeMixin` 按 Expense Case Link 聚合当前租户报销单;缺少 `tenant_id` 的旧预算表仅允许 `default` 租户读取,其他租户返回带原因的明确空预算;新增 `finance_dashboard_scope.py` 统一声明报销与预算数据范围。
|
||||
- 修改:`finance_dashboard_snapshot.py` 把 `tenant_id`、数据范围和完整时间参数纳入无歧义缓存键,并在 SQL 查询、Agent Run 路由及工具请求中同时校验租户和范围;`finance_dashboard_scheduler.py` 显式固定 `default` 系统租户,非默认租户不能调用默认定时快照入口。
|
||||
- 修改:新增 `finance_dashboard_access_policy.py`,`analytics.py` 显式接收 `CurrentUserContext`,仅允许 `finance`、`executive` 或 admin 只读访问财务看板,普通用户和仅有 `budget_monitor` 角色的用户返回 403。
|
||||
- 操作:所有检查均在 `local-x-financial-linux` 容器内执行;新增租户 A/B、default 历史单、旧预算、快照缓存与接口权限测试,没有触碰受保护的财务规则 XLSX 和历史开发文档。
|
||||
- 验证:Ruff 对本次 7 个 Python 模块及新增测试检查通过;`test_finance_dashboard_tenant_security.py` 与 `test_finance_dashboard_service.py` 共 8 个测试通过。补充运行财务报告与数字员工回归时 4 个测试通过、1 个既有财务周报用例失败,原因是用例将“当前时间减 2 天”的数据断言进“上一完整周”窗口,和本次租户过滤无关。
|
||||
- 影响:财务看板不再读取其他租户的报销单或把 default 旧预算暴露给非默认租户;相同时间参数的租户快照不会互相命中,后台默认快照和前台读取权限也有了可审计的显式边界。
|
||||
|
||||
- 18:14:补齐财务看板租户 fail-closed 边界与模块拆分验证。
|
||||
- Git 提交检查:18:13 再次执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交,本地仍比 `origin/main` ahead 17 个提交,范围为 `661990b2 feat(expenses): add transactional expense case events` 至 `242d68c3 feat(approval): add task workflow and waiver decisions`,未合并、改写或覆盖这些提交及工作区内其他智能体改动。
|
||||
- 修改:`finance_dashboard_access_policy.py` 对空白租户上下文直接拒绝,避免异常认证上下文回落到 default;将预算摘要、预算卡片和预算瓶颈投影提取到 `finance_dashboard_budget.py`,`finance_dashboard.py` 从 920 行降至 746 行,保持租户过滤和原 API 不变。
|
||||
- 操作:只在 `local-x-financial-linux` 容器内执行 Ruff、财务看板服务与接口测试、Agent Run 服务回归和 Ontology 端点回归;未触碰财务规则 XLSX、Savings 迁移或前端文件。
|
||||
- 验证:Ruff 检查与格式检查通过;财务看板、Agent Run 服务和 Ontology 端点定向回归共 17 个测试通过。另跑数字员工、系统看板和财务报告回归时 6 个通过、1 个既有周报窗口用例失败;该用例在周四写入“当前时间减 2 天”的单据,却断言它属于“上一完整周”,失败与本次改动无关。
|
||||
- 影响:租户身份缺失时财务看板不再隐式读取 default 数据;预算展示职责被独立封装,后续继续扩展财务指标时不会把核心聚合模块推过项目 800 行硬上限。
|
||||
@@ -0,0 +1,11 @@
|
||||
## 修复记录
|
||||
|
||||
- 22:29:记录 bug 修复:非生产财务回执会进入核心付款状态机,连接器配置缺少版本化生命周期审计,normalized payload 冗余保存完整单号。
|
||||
- Git 提交检查:执行 `git fetch --all --prune` 后未发现 `HEAD..origin/main` 新提交;当前 `main` 相对 `origin/main` ahead 17,范围为 `661990b2..242d68c3`,包含认证、费用事件、AI 学习、审批任务/风险处置和迁移所有权等既有基础提交,本轮未合并或改写这些提交。
|
||||
- 修改:`financial_connector_ingestion.py` 与新增 `financial_connector_simulation.py` 将 test/mock/staging 六类事件收口为 `simulation_only` 只读事实,禁止修改 Claim、对账、ERP、Business Event、归档和 Savings;生产 origin 查询只接受同来源生产事实,不能引用模拟结算触发冲回。
|
||||
- 修改:`financial_connector_config_lifecycle.py`、`financial_connector_config_audit.py`、配置 schema/API 和 `FinancialConnectorConfigEvent` 新增带 expected version、认证 actor、request ID、reason 的 activate/disable/rotate 状态机;新配置只能 disabled 创建,激活/轮换前解析服务端 `secret_ref` 并校验 HMAC 密钥强度,审计前后快照不保存密钥引用或明文。
|
||||
- 修改:`20260716_0020_financial_connector_config_lifecycle.py` 基于 0019 增加配置 version、复合租户约束和 PostgreSQL append-only 审计 trigger;受控移除历史 connector event normalized payload 中的完整 `claim_reference`,保留金额/币种、必要尾号和内容指纹,并把可能已有旧非生产副作用的响应标记为 `legacy_nonproduction_effect_unknown`,不伪装为新策略下的无副作用模拟事实。
|
||||
- 修改:保留并验证 HMAC v2 对 tenant/provider/key version/timestamp/method/path 的绑定,拒绝共享密钥跨来源重放;ERP 回执按协议只要求 origin、Claim、金额和币种,不再错误强制重复支付参考号;空白密钥即使长度足够也按强度不足失败关闭。
|
||||
- 操作:在独立一次性 PostgreSQL 17 容器完成空库升级到 0020、schema/约束/trigger 探针、并发激活单版本胜者、0019→0020 历史脱敏探针和完整升降级循环;验证完成后删除临时容器。同步更新模型注册、迁移所有权、preflight、HEAD revision、迁移断言及连接器 CONCEPT/TODO。
|
||||
- 验证:容器内连接器/配置/费用价值链/迁移组合回归 `140 passed, 1 skipped`;PostgreSQL 连接器并发 `3 passed`;全新 PostgreSQL 完整迁移循环 `1 passed`,商业迁移 agent 在另一空库复跑同样 `1 passed`;Ruff、全树 `git diff --check` 均通过,连接器核心文件最大 509 行,低于 800 行硬上限。
|
||||
- 影响:模拟、测试和预发布环境现在可以安全演练完整事件协议而不会改账;只有 `production_verified` 且精确匹配的回执能够推进付款、ERP 与 Savings。管理员可以安全激活、停用和轮换密钥,所有配置动作可追溯且不泄露密钥或完整单号。
|
||||
@@ -0,0 +1,9 @@
|
||||
## 修复记录
|
||||
|
||||
- 19:01:修复风险规则 Golden 评测的 FP/FN 统计颠倒、修订版本未进门禁以及异常/空用例默认放行问题。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||
- 修改:`risk_rule_golden_evaluator.py` 将 false positive 改为“期望不命中但实际命中”,false negative 改为“期望命中但实际未命中”,Precision/Recall 分母恢复正确。
|
||||
- 修改:初次发布和 revision 发布均强制执行 Golden 门禁;缺规则文档、缺 rule code、缺 active Golden case 或评测异常均 fail-closed。仅当 `GOLDEN_SET_GATE_ENABLED=false` 被显式配置时允许跳过,且仍写入 status=skipped 的 `AgentAssetTestRun`。
|
||||
- 操作:在 `local-x-financial-linux` 容器中运行 Golden、发布、修订和安全自动化定向回归,并执行 Ruff;未修改财务规则 XLSX。
|
||||
- 验证:Golden、release guard 和自动化资格组合回归 `29 passed`;并行定向发布/修订回归纳入总计 `62 passed`,评测异常、空用例和缺配置均留下 failed 记录并拦截发布。
|
||||
- 影响:误报不再被错计为漏报,Precision/Recall 可用于可信的 Canary 与回滚判断;新规则和修订规则不能因评测器失败或没有黄金用例而静默上线。
|
||||
@@ -0,0 +1,8 @@
|
||||
## 修复记录
|
||||
|
||||
- 22:33:记录 bug 修复:OnlyOffice 回调测试仍 patch 已拆分前的网络符号。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..@{u}` 为空,未发现 upstream 新提交;本地 `main` ahead 17。审批链相关提交为 `242d68c3`、`28b834ed`、`4940ebc4`,AI 报销学习与申请链相关提交为 `ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`,费用事件与事务链相关提交为 `5ed34c2b`、`1347366b`、`22669a90`、`a616b30c`、`661990b2`,另有迁移安全 `11275e4b` 与会话鉴权 `653eda05`;这些均为当前任务开始前已有的本地提交,本次没有改写或合并。
|
||||
- 修改:把 `test_onlyoffice_callback_summary.py` 的网络 patch 目标切换到实际查找符号的 `agent_asset_onlyoffice` 模块,并删除对旧版本元数据方法及“回调自行生成 change_note”的过期假设;测试现在验证下载内容、回调用户和 `onlyoffice` 来源被完整委托给统一上传流程。
|
||||
- 操作:没有在 `agent_assets` 中恢复底层 `urlopen` 兼容导出,因为该符号不是公共 API,且兼容别名也无法拦截拆分模块中的真实调用;差异摘要仍由 `upload_rule_spreadsheet` 统一生成和审计,已有服务测试覆盖其工作表/单元格统计。
|
||||
- 验证:容器内回调定向测试通过;包含该测试的 Agent Run/租户/轨迹组合共 11 项通过,`test_agent_asset_service.py` 全部 28 项通过;测试文件 Ruff 与 `git diff --check` 通过。
|
||||
- 影响:OnlyOffice 回调测试重新拦截真实网络边界,不会发出外部请求,也不会因内部模块拆分误报;生产回调与摘要生成职责保持不变。
|
||||
@@ -0,0 +1,12 @@
|
||||
# 报销审批非参与者错误映射与陈旧测试契约
|
||||
|
||||
日期:2026-07-16
|
||||
文档路径:document/development/2026-07-16/dev-logs/bugs/reimbursement-approval-task-access-error-mapping.md
|
||||
|
||||
## 修复记录
|
||||
- 21:38:记录 bug 修复:报销审批非参与者错误映射与陈旧测试契约。(bug-log:242d68c3)
|
||||
- Git 提交检查:已手工执行 `git fetch --all --prune`,upstream `origin/main` 无本地尚未包含的新提交;当前分支 ahead 17 且工作区已有其他智能体和用户的未提交改动,因此未自动合并或变基。ahead 包括审批任务与风险链 `242d68c3`、`28b834ed`、`4940ebc4`,AI/费用学习与预审链 `ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`,Expense Case/时间线与事务链 `5ed34c2b`、`1347366b`、`22669a90`、`a616b30c`、`661990b2`,以及迁移安全 `11275e4b`、认证会话 `653eda05`。
|
||||
- 修改:`reimbursement_approval_actions.py` 将审批任务访问策略抛出的 `LookupError` 显式映射为 404,避免非任务参与者通过报销审批/退回接口触发 500;`test_expense_claim_service.py` 与 `test_reimbursement_endpoints.py` 同步到正式审批任务的资源隐藏与任务冲突契约,并增加申请人审批、退回均无状态和动作账本副作用的 HTTP 回归。
|
||||
- 操作:完整读取 `agent-change-log` Skill,沿 `ExpenseClaimActionProtocolMixin → ApprovalTaskLifecycleService → ApprovalTaskAccessPolicy` 诊断调用链;保留“非参与者不可读取任务”的权限语义,没有放宽审批人、管理员、申请人或租户边界;随后运行日志 helper 创建本记录并补齐实际证据。
|
||||
- 验证:在 `local-x-financial-linux` 容器中,原失败用例与新增 HTTP 用例 2 项通过;审批任务、审批路由和报销接口组合 65 项通过;费用服务审批、退回和付款相关筛选回归 30 项通过;相关 Python 文件 Ruff F/I 检查通过。
|
||||
- 影响:申请人或其他非任务参与者调用审批动作时稳定返回 404,不再出现服务端 500,也不会创建动作账本、审批事件或修改 Claim;可见但不可操作的任务仍按既有策略返回 403,状态/版本冲突继续返回 409。
|
||||
@@ -0,0 +1,27 @@
|
||||
# AI 发布门禁全局资产越权与快照损坏静默放行
|
||||
|
||||
## 修复记录
|
||||
|
||||
- 19:48:记录 AI 分阶段发布的租户越权与运行时完整性失效修复。
|
||||
- Git 提交检查:执行 `git fetch --all --prune` 后未发现 `HEAD..origin/main` 上游新提交;本地 `main` 比上游 ahead 17,最新为 `242d68c3 feat(approval): add task workflow and waiver decisions`,其余为审批安全、AI 费用学习、报销预审、迁移安全与会话认证等既有检查点,本次未合并或改写这些提交。
|
||||
- 修改:`agent_asset_release_guard.py` 为所有发布查询、启动、评测、晋级和回滚入口增加平台全局资产管理边界;未绑定租户的共享资产只允许平台管理员管理,租户级 manager 统一返回不可见。`agent_asset_releases.py` 与既有风险规则发布入口只从认证上下文传递平台管理员权限,不能由请求体或自报 actor 绕过。
|
||||
- 修改:`expense_claim_risk_rule_loader.py` 在候选快照与稳定快照均无法通过 SHA-256 完整性校验时生成强制阻断信号,`expense_claim_platform_risk.py` 将该信号转换为 critical/block 风险,而不是把损坏规则当成“未命中”静默跳过。
|
||||
- 操作:仅通过 `apply_patch` 修改源码和测试;保护现有财务规则 XLSX、历史未跟踪开发目录及其他智能体改动,未执行提交、推送或数据库破坏操作。
|
||||
- 验证:在 `local-x-financial-linux` 容器内运行 Ruff 定向检查通过;`test_agent_asset_release_guard.py` 与 `test_agent_asset_release_runtime.py` 共 12 项测试全部通过,新增覆盖租户 manager 无权管理全局发布资产、平台 admin 可管理,以及双快照损坏后报销自动流转被阻断。
|
||||
- 影响:单租户管理员不再能影响所有企业共享的风险规则;受控发布元数据损坏时系统优先停流并提示平台恢复稳定版本,避免风险规则失效后继续自动审批。
|
||||
|
||||
- 19:53:继续修复发布质量指标可由管理人员手工伪造的问题。
|
||||
- Git 提交检查:再次执行 `git fetch --all --prune`,`HEAD..origin/main` 仍无上游新提交;本地仍 ahead 17,提交范围与 19:48 检查一致,未自动合并、变基或覆盖共享工作区改动。
|
||||
- 修改:新增 `agent_asset_release_monitor_auth.py`,评测请求必须使用至少 32 字节独立密钥,对时间戳、租户、资产、release ID、当前阶段和规范化请求体摘要做 HMAC-SHA256 签名;仅允许 5 分钟时钟窗口并使用常量时间比较。`agent_asset_releases.py` 在写入评测记录前验证签名,密钥未配置返回 503,缺失、过期或错误签名返回 401。
|
||||
- 操作:保留服务层直接写入能力供同进程可信监控使用,但关闭普通 HTTP manager 仅凭自报数字写入“通过”证据的路径;签名绑定 release ID 和阶段,旧阶段请求不能在晋级后重复使用。
|
||||
- 验证:容器内 Ruff 定向检查通过;发布 guard/runtime 共 12 项测试通过,HTTP 用例新增未签名评测返回 401,同时签名监控数据仍可触发 shadow→Canary→active 和指标越界自动回滚。
|
||||
- 影响:人工管理权限与机器监控证据分离,发布门禁不再把未经认证的手工数字当成可信质量结果。
|
||||
|
||||
- 21:51:补齐“签名合法但指标仍可伪造”的第二层真实性修复,并接通真实证据自动回滚。
|
||||
- Git 提交检查:执行 `git fetch --all --prune`、`git status -sb`、`git log HEAD..@{u}` 与 `git log @{u}..HEAD`;`origin/main` 无新提交,本地仍 ahead 17,最新提交仍为 `242d68c3 feat(approval): add task workflow and waiver decisions`,其余 ahead 提交为既有审批、AI 费用学习、报销预审、迁移安全和会话认证检查点。本次未合并、变基、提交或推送,也未触碰受保护财务规则 XLSX。
|
||||
- 修改:Release Monitor HTTP 契约改为禁止额外字段的空触发请求,HMAC 继续绑定 tenant/asset/release/stage,但 `total`、`precision` 等质量数字只能由服务端查询 append-only observation/label 聚合;即使签名正确,携带伪造汇总字段也返回 422。
|
||||
- 修改:真实 shadow/Canary/active manifest 执行追加脱敏 observation,类型化 `RiskDispositionEvent` 追加可信标签并即时触发 Guard;相同 release 聚合快照复用同一测试运行,低 precision 或结构化运行失败自动回滚 stable。即时监控失败由租户周期调度补偿,`collecting` 不写虚假 passed。
|
||||
- 修改:新增 `0018` 两张发布遥测表、复合租户/release 外键、幂等唯一约束和 PostgreSQL append-only 触发器,并登记主 metadata、迁移所有权与启动前置检查;将风险处置发布同步拆到独立服务,使核心 `risk_dispositions.py` 保持 800 行以内。
|
||||
- 操作:全部源码与文档通过 `apply_patch` 修改;用独立 `pgvector/pgvector:pg17` 一次性数据库验证完整迁移链及数据库约束,未连接或变更生产数据库。
|
||||
- 验证:容器内发布 Guard/Runtime/Telemetry/Monitor/风险处置组合 44 项通过;调度器、Monitor 与风险处置定向 24 项通过;迁移/schema owner 静态回归 112 项、启动前置检查 77 项、一次性 PostgreSQL 完整迁移循环 1 项通过;PostgreSQL savings/commercial/financial connector/approval 并发探针共 14 项通过。Ruff 定向检查与 `git diff --check` 通过(文档回填后仍需最终全量复验)。
|
||||
- 影响:发布质量门禁不再信任“会签名的调用方”提交的汇总数字,而是由数据库真实执行与可信人工结论生成;遥测暂时失败只会阻止晋级,不会撤销已成功人工处置或关闭现有 stable 保护。
|
||||
@@ -0,0 +1,9 @@
|
||||
## 修复记录
|
||||
|
||||
- 19:19:修复商业计量迁移上线后,标准调整 Savings 回填脚本因精确锁定旧版本而错误拒绝执行的问题。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||
- 修改:`backfill_standard_adjustment_savings.py` 不再要求当前 revision 必须精确等于 `20260716_0015`,改为遍历 Alembic `down_revision` 祖先链,只有当前迁移确实包含 Savings 数据契约时才允许回填。
|
||||
- 修改:`test_standard_adjustment_savings_backfill.py` 覆盖所需版本本身、后继商业迁移 `0016`、更旧版本和未迁移数据库四类边界。
|
||||
- 操作:全部检查均在 `local-x-financial-linux` 容器内执行,未修改财务规则 XLSX 或历史开发文档。
|
||||
- 验证:Ruff 通过;标准调整 Savings 回填定向回归 `12 passed`。
|
||||
- 影响:数据库升级到 `0016` 及未来合法后继版本后仍可安全执行历史节省回填;更旧、未迁移或不包含目标契约的版本仍会 fail-closed。
|
||||
@@ -0,0 +1,17 @@
|
||||
## 修复记录
|
||||
|
||||
- 19:24:修复历史费用偏离分析可能读取分析截止时间之后才冻结的基线、形成时间穿越的问题。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||
- 修改:`savings_insight_analysis.py` 在历史基线查询中增加 `frozen_at <= as_of`,确保候选分析只能使用截止时间当时已经存在的冻结快照。
|
||||
- 修改:`test_savings_baseline_insights.py` 新增未来冻结基线回归;确认这类快照不会产生历史价格偏离候选,并返回基线不可用的数据质量提示。
|
||||
- 操作:全部检查均在 `local-x-financial-linux` 容器内执行,未修改财务规则 XLSX 或历史开发文档。
|
||||
- 验证:Savings 基线/洞察、端点与 CFO 组合回归 `10 passed`;相关 Ruff 检查通过。
|
||||
- 影响:`as_of` 历史回放不再引用未来才生成的知识,CFO 候选洞察和审计结果保持时间一致性。
|
||||
|
||||
- 22:50:继续修复预算预测读取分析窗口或 `as_of` 之后预算配置与核销流水的问题。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地仍相对 upstream ahead 17 个既有提交(`242d68c3` 至 `661990b2`,内容为审批任务/风险处置、AI 学习与预审、Expense Case、迁移安全和认证等共享能力),未发现新的上游提交,也未合并或改写共享工作树。
|
||||
- 修改:`savings_insight_budget.py` 以 `min(as_of, window_end)` 作为预算分析截止点,只读取当时已经创建且最后更新的预算配置,以及预算期间开始至截止点的核销/回滚事实;部门范围优先使用稳定部门 ID,缺 ID 才使用部门名称或成本中心。
|
||||
- 修改:`test_savings_baseline_insights.py` 增加窗口后核销、截止时间后配置、稳定重放与零机会副作用回归。
|
||||
- 操作:所有 pytest 和 Ruff 均在 `local-x-financial-linux` 容器内以 60 秒超时执行;未接触财务规则 XLSX、商业/连接器/发布迁移或迁移 HEAD。
|
||||
- 验证:Savings/CFO 定向组合 `34 passed, 6 skipped`(6 项为未配置 PostgreSQL 专用测试 URL 的预期跳过),相关 Ruff 检查通过。
|
||||
- 影响:历史预算预测不再使用报告窗口之后才出现的配置或交易,异常归因、政策模拟候选与 CFO 审计回放保持同一时间边界。
|
||||
@@ -0,0 +1,9 @@
|
||||
## 修复记录
|
||||
|
||||
- 18:11:记录 bug 修复:接受住宿职级标准调整时不再信任客户端金额、天数或金额快照。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 未发现 upstream 新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3 feat(approval): add task workflow and waiver decisions`、`28b834ed fix(approval): replay immutable action responses`、`4940ebc4 feat(approval): add safe risk disposition workflow`、`ee88a36b feat(ai): add tenant-safe hierarchical expense learning`、`6bdf65bc feat(expenses): add authoritative pre-review workflow`、`ae3f02c3 feat(expense): add persistent zero-entry receipt association`、`54754b55 feat(ai): add personal expense application memory`、`211f85d9 feat(ai): unify verified expense application workflow`、`5b246307 feat(ai): issue verified application preview decisions`、`a662cfe6 feat(ai): add expense application feedback ledger`、`5ed34c2b feat(expenses): backfill historical claims into expense cases`、`11275e4b fix(migrations): enforce schema ownership safety`、`1347366b feat(expenses): secure timeline and draft events`、`22669a90 feat(expenses): show unified expense event timeline`、`a616b30c fix(expenses): unify AI application submission transaction`、`653eda05 feat(auth): add opaque bearer sessions`、`661990b2 feat(expenses): add transactional expense case events`;这些提交均早于本次未提交修复,本次没有拉取、合并或覆盖共享工作树。
|
||||
- 修改:`expense_claim_standard_adjustment.py` 只从已锁定的 `ExpenseClaimItem.item_amount` 读取原始金额,只接受服务端差旅规则计算出的最终可报金额;客户端携带的 `application_days`、`original_amount`、`reimbursable_amount` 仅作为旧界面兼容展示字段,不参与计算。服务端快照新增规则名、规则版本(无发布版本时使用内容指纹)、地点、匹配城市、职级、职级档、天数、每日住宿标准、住宿标准总额及计算指纹。
|
||||
- 修改:为标准调整增加 PostgreSQL advisory lock + Claim/Item 行锁和非 PostgreSQL 进程内串行锁;请求支持 `request_id` 幂等键与 `expected_updated_at` 乐观前置条件。相同请求直接重放且不改 `created_at`/计算快照,同请求号改选其他明细会被拒绝;单次调整只替换被选明细的快照,不再误删其他明细已接受的标准调整。
|
||||
- 操作:在 `local-x-financial-linux` 容器及 `/tmp/x-financial-server-venv` 中运行定向、接口全量和服务全量测试;运行 scoped Ruff 与 `git diff --check`,未在宿主机运行 Python/pytest,也未修改规则表或其他用户文件。
|
||||
- 验证:标准调整定向回归(含非 PostgreSQL 同租户同单据锁竞争)`9 passed`;`test_reimbursement_endpoints.py` 全量 `22 passed`;Scoped Ruff 与 `git diff --check` 通过。`test_expense_claim_service.py` 全量为 `112 passed, 8 failed`,8 个失败均位于既有审批任务配置/旧错误文案断言(直属领导任务、费用申请提交、本人审批、重复退回),不经过标准调整实现;本次新增及关联标准调整用例全部通过。
|
||||
- 影响:伪造低原金额、任意可报金额或超长住宿天数不能降低或抬高实际报销额;规则缺失时整次操作失败关闭且不改金额。审批人看到的原额、可报额和差额均可追溯到数据库明细与服务端规则证据,重复点击和并发请求不会重写金额证据。
|
||||
@@ -0,0 +1,9 @@
|
||||
## 修复记录
|
||||
|
||||
- 19:37:修复 Workbench AI 超大运行时导致职责耦合,以及前端回归测试仍绑定旧单体文件和宿主机缺失 Pillow 的问题。
|
||||
- Git 提交检查:已执行 `git fetch --all --prune`;`HEAD..origin/main` 无新提交;本地相对 upstream ahead 17 个既有提交,依次为 `242d68c3`、`28b834ed`、`4940ebc4`、`ee88a36b`、`6bdf65bc`、`ae3f02c3`、`54754b55`、`211f85d9`、`5b246307`、`a662cfe6`、`5ed34c2b`、`11275e4b`、`1347366b`、`22669a90`、`a616b30c`、`653eda05`、`661990b2`;本次未合并、覆盖或改写共享工作树中的其他变更。
|
||||
- 修改:将会话滚动、流式响应、持久化/重置提取到 `useWorkbenchAiConversationRuntime.js`,把模型意图规划、低置信确认和多任务衔接提取到 `useWorkbenchAiIntentExecution.js`;`usePersonalWorkbenchAiMode.js` 降至 768 行。
|
||||
- 修改:Workbench、会话删除、报销关联与快速申请预览测试改为联合审计入口与职责模块;视觉验证复用容器已有 ImageMagick,保留像素和动画断言,不再依赖未安装的 Pillow。
|
||||
- 操作:全部 node 测试和构建均在 `local-x-financial-linux` 容器内执行,未修改后端或财务规则 XLSX。
|
||||
- 验证:Workbench AI/会话组合 `85 passed`,快速申请预览 `67 passed`;Vite 生产构建成功(2229 modules);`git diff --check` 通过。
|
||||
- 影响:会话清理、详情智能录入、关联门禁和申请预览的回归测试不再因内部职责迁移误报,核心运行时恢复到项目 800 行硬上限内。
|
||||
@@ -0,0 +1,333 @@
|
||||
# AI 分阶段发布真实遥测 概念文档
|
||||
|
||||
更新时间:2026-07-17
|
||||
|
||||
## 功能一句话
|
||||
|
||||
把真实 shadow/Canary 规则执行、正负样本盲审真值和服务端保守聚合串成可审计证据链,只有精确率、召回率下界与运行质量同时满足门禁时才允许晋级,越界时自动回滚到稳定版本。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
风险规则已经具备 Golden Case、`shadow → canary → active → rolled_back` 状态机、稳定流量路由和自动回滚能力,但线上评测仍有一个关键证据缺口:`ReleaseEvaluationInput` 由外部调用方直接提交 `total`、`failure_count`、`precision` 和 `baseline_precision` 汇总数字,Release Guard 无法证明这些数字来自哪一次真实规则执行、哪一个租户、哪一个 release,也无法证明精度分子和分母来自可信人工结论。
|
||||
|
||||
现有运行与学习链路的事实边界如下:
|
||||
|
||||
- `ExpenseClaimRiskRuleLoader` 能按租户和稳定路由键选择 stable、shadow 或 Canary 候选版本,并在快照损坏时保守阻断。
|
||||
- `evaluate_platform_risk_rules()` 会返回 shadow 候选的 `asset_id / rule_code / rule_version / release_stage / hit / severity`;Canary 命中会进入带版本和阶段的风险 flag,但当前返回值不会持久化成发布样本。
|
||||
- `RiskDispositionEvent` 是类型化、租户化、只追加的人工处置事实,`confirm` 和 `false_positive` 可作为正例命中的可信真值来源。
|
||||
- `RiskObservationFeedback` 的自由评论、`AIDecisionFeedback` 和 `WorkflowOutcome` 可以支持业务学习,但它们没有同时绑定 `asset_id + release_id + stage + version`,不能直接作为某次发布的精度标签。
|
||||
- Release Monitor 的 HMAC 能认证请求来自持有共享密钥的调用方,并限制传输重放;它不能证明请求体中的汇总数字由真实数据库 observation 和人工 label 计算得出。
|
||||
|
||||
因此,线上门禁必须从“相信外部汇总数字”改为“服务端从只追加事实计算指标”。本能力是 `ai-data-flywheel` 在线质量闭环和 `ai-expense-closed-loop-and-value-proof` 分阶段发布目标的证据层,不替代离线 Golden Case。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- [G1] 为每次真实候选规则执行记录租户、资产、release、阶段、版本、规则、命中、基线命中和结构化运行状态。
|
||||
- [G2] 将专用发布复核或数据库中可信 `RiskDispositionEvent` 转换成只追加的正例判断;将独立盲审转换成与模型预测语义分离的 `risk_present / risk_absent` 真值。
|
||||
- [G3] 使用稳定幂等键处理同一执行或人工动作的安全重放,不同内容复用同一来源时拒绝冲突。
|
||||
- [G4] 从真实 observation 和最新可信 label 聚合运行总量、运行失败、候选 precision 和基线 precision。
|
||||
- [G5] 未标注候选命中保持 `collecting`,不得把“没有人工结论”当成成功或正确。
|
||||
- [G6] 聚合结果可转换成现有 `ReleaseEvaluationInput`,供 shadow/Canary 门禁、自动晋级和自动回滚使用。
|
||||
- [G7] 全链路租户隔离、数据脱敏、append-only,并拒绝跨租户和陈旧 release/stage/version 标签。
|
||||
- [G8] 对候选未命中人群实施分层盲审:基线命中分歧样本全量复核,其余负样本按不可预测稳定分数随机抽检;证据不足时 recall/FN 仍显式不可用。
|
||||
- [G9] 使用抽样漏检率估计总体 FN,并以 Wilson 上界反推保守召回率下界;发布门禁只使用下界,不把点估计冒充确定事实。
|
||||
|
||||
### 非目标
|
||||
|
||||
- [NG1] 不用线上遥测替代离线 Golden Case;前者验证真实分布,后者验证覆盖明确预期的回归集合。
|
||||
- [NG2] 不把候选未命中直接判为 false negative,也不从“后续未退回”反推规则正确。
|
||||
- [NG3] 不保存报销事由、票据内容、人工评论、单号、操作者账号原值或原始风险 payload。
|
||||
- [NG4] 不接受客户端、浏览器或普通管理员提交的 precision/recall 作为发布真值。
|
||||
- [NG5] 不因为 HMAC 校验通过就信任汇总数字;HMAC 只解决传输来源和重放,不解决数据生成真实性。
|
||||
- [NG6] 不让通过质量门禁的规则绕过审批、风险处置或资金动作的人类控制。
|
||||
- [NG7] 不把遥测或自动回滚做成绕开共享风险循环、迁移所有权、审批控制或稳定版本保护的旁路。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
### 用户
|
||||
|
||||
1. 风险运营/规则管理员:查看某个 release 的真实样本数、待标注数、误报率和基线比较,决定是否继续采集或人工回滚。
|
||||
2. 财务审计/审批人:在既有风险处置或专用发布复核队列中给出类型化确认/误报结论,不接触发布汇总公式。
|
||||
3. Release Monitor:只从数据库聚合当前 release,满足证据条件后把服务端计算结果交给 Release Guard。
|
||||
4. 平台审计员:按租户、release、版本和来源指纹回放 observation、label、评测与状态转换,不读取业务正文。
|
||||
|
||||
### 核心场景
|
||||
|
||||
1. shadow 阶段同时执行 stable 和候选规则;记录候选 hit/miss,并记录同一单据上 stable 是否命中。
|
||||
2. 候选或 stable 命中进入人工复核;`confirm` 表示风险事实成立,`false_positive` 表示该次正向命中是误报。
|
||||
3. 每条成功 observation 同步决定盲审层:候选命中全量入队、候选未命中但基线命中全量入队、双方均未命中按发布时冻结比例随机入队。
|
||||
4. 复核队列混排正负样本,只提供业务单据、规则和业务阶段,不返回 candidate/baseline hit;复核人只回答“存在真实风险/确认无该风险”。
|
||||
5. 所有候选未命中样本必须由两个不同复核人给出一致结论;同一人的重复动作不增加法定票数,冲突时继续 collecting。
|
||||
6. 候选命中仍有任何未标注项时,聚合状态保持 `collecting`,Release Monitor 不提交通过评测。
|
||||
7. 候选正例全部标注后,服务端计算 candidate precision;基线正例也全部标注时才计算 baseline precision。
|
||||
8. 负样本达到最小独立复核量后,服务端分别披露实际观察 FN、总体估计 FN、FN 置信上界、recall 点估计和 recall 置信下界。
|
||||
9. Canary 路由中的候选 hit、miss 和结构化运行失败继续追加;错误率、precision 或 recall 下界越界时 Release Guard 自动回滚稳定快照。
|
||||
10. release 已晋级、回滚或被新 release 替代后,旧 observation/sample/label 仍保留,但不能再接收新标签或作为当前晋级输入。
|
||||
|
||||
## 功能能力
|
||||
|
||||
- [C1] 运行样本生产:消费现有 `shadow_evaluations`、Canary/active flag,并提供 manifest 执行循环级 hook 记录 Canary 未命中和结构化失败。
|
||||
- [C2] 可信标签:支持认证发布复核动作和数据库中真实 `RiskDispositionEvent`;不接受评论文本作为标签。
|
||||
- [C3] 保守聚合:分别计算 observation、completed、runtime failure、candidate hit/labeled/pending、baseline hit/labeled/pending。
|
||||
- [C4] 门禁转换:仅 `ready` 聚合可生成 `ReleaseEvaluationInput`;`collecting` 调用转换时明确拒绝。
|
||||
- [C5] 证据隔离:运行来源、标签来源、操作者均只保存租户内、带密钥版本的
|
||||
HMAC-SHA-256 指纹;原始 claim、事件和账号值不进入遥测表。
|
||||
- [C6] 幂等与冲突:同一租户、release、阶段、版本、规则和来源形成稳定键;相同重放复用首次记录,不同载荷冲突。
|
||||
- [C7] 历史不可变:observation 与 label 均只追加;标签纠正追加新 label,聚合取最新可信标签,不原地覆盖旧事实。
|
||||
- [C8] 盲审抽样:候选正例和候选/基线分歧样本全量入队,其余负样本按发布策略的 `negative_sample_percent` 与 HMAC 来源伪名生成稳定随机分数。
|
||||
- [C9] 真值盲化:样本表不保存明文单据 ID,业务来源加密保存;API 不返回 candidate/baseline hit,前端也拒绝接收预测字段。
|
||||
- [C10] 保守召回:证据未满足时 `false_negative_count / estimated_false_negative_count / recall / recall_lower_bound` 保持 `null`;满足后分别披露,不用点估计替代下界。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 证据链与自动判定
|
||||
|
||||
```text
|
||||
[真实 stable / candidate 执行]
|
||||
│
|
||||
▼
|
||||
[append-only Observation]
|
||||
tenant + asset + release + stage + version + hit + runtime status
|
||||
│
|
||||
▼
|
||||
[append-only Audit Sample]
|
||||
正例全量 + 分歧全量 + 其余负例稳定随机抽样
|
||||
│
|
||||
▼
|
||||
[盲化可信 Label]
|
||||
typed disposition / release review / blind release review
|
||||
│
|
||||
▼
|
||||
[服务端 Aggregate]
|
||||
runtime + precision + baseline + sampled FN + recall lower bound
|
||||
│
|
||||
┌───────┴────────┐
|
||||
│ collecting │ ready
|
||||
▼ ▼
|
||||
[继续采集/告警] [ReleaseEvaluationInput]
|
||||
│
|
||||
▼
|
||||
[Release Guard 判定]
|
||||
shadow → canary → active
|
||||
或自动 rolled_back
|
||||
```
|
||||
|
||||
该链路中的每一层只消费上一层可验证的结构化事实。离线 Golden Case 仍是进入 shadow 前的回归门禁;线上 telemetry 是进入 Canary、active 及运行中回滚的分布证据,两者不能互相替代。
|
||||
|
||||
### 前端
|
||||
|
||||
- 发布控制台分别展示运行样本、候选待标注、候选/基线 precision、运行失败、负样本池/抽样/积压、实际 FN、估计 FN、FN 上界、recall 点估计和置信下界。
|
||||
- 盲审队列只接收服务端安全字段;`candidate_hit / baseline_hit` 即使误入响应也不会进入页面状态。
|
||||
- 复核按钮使用中性业务语义“存在真实风险 / 确认无该风险”,不使用“模型命中/误报”暗示预测;来源单据在新标签页打开并隔离 opener。
|
||||
- `null` 指标统一显示“证据不足/暂不可用”,不得渲染成 0;`collecting`、`ready`、`failed/rolled_back` 使用不同状态。
|
||||
- `collecting`、`ready`、`failed/rolled_back` 使用不同状态,不把空样本或缺标签显示为 100%。
|
||||
|
||||
### 后端
|
||||
|
||||
- `AgentAssetReleaseTelemetryService.record_expense_risk_result()` 可从当前风险评测返回值生产 shadow 样本和 Canary/active 命中样本。
|
||||
- `record_manifest_evaluation()` 设计为风险 manifest 执行循环 hook,可记录 Candidate 未命中及 `evaluator_error / artifact_integrity_error / unsupported_evaluator / timeout` 等结构化失败。
|
||||
- `record_review_label()` 只接收类型化 label、认证 actor ID 和 request ID;actor 与 request 进入表前被指纹化。
|
||||
- `AgentAssetReleaseSamplingService` 在 observation 同一事务内完成分层选择与来源加密;加密失败会连同 observation 一起回滚,避免留下无法复核的孤立事实。
|
||||
- `AgentAssetReleaseReviewService` 只查询当前租户、当前 release 的样本,混排后输出去预测队列;发布发起人不可自审,负样本强制两个不同 actor。
|
||||
- `agent_asset_release_aggregation` 将精度和召回证据拆开聚合;`agent_asset_release_recall` 使用随机层漏检率与 Wilson 上界生成总体 FN 估计和 recall 下界。
|
||||
- `record_risk_disposition_label()` 会重新查询数据库中的同租户 `RiskDispositionEvent` 和 `RiskObservation`,校验 action、claim/rule 来源指纹和版本,不信任调用方提供的人工结论副本。
|
||||
- `aggregate()` 只读取同租户、同资产、同 release、同阶段、同版本事实,并验证它仍是资产当前 release。
|
||||
- `to_release_evaluation_input()` 只允许 `ready` 聚合转换;未标注、无候选正例或空样本会抛出 collecting 错误。
|
||||
- `AgentAssetReleaseMonitor` 与周期调度器只在服务端查询事实并调用上述方法;HTTP 入口只接受签名触发,不再接收外部汇总数字。
|
||||
- Agent 资产基础 CRUD/表格/版本接口与风险规则生成、测试、启停和发布子路由分离;
|
||||
资产版本只读投影由独立序列化 mixin 承担,Release Guard 只编排状态与持久化,
|
||||
阈值归一化和质量门禁计算下沉为无数据库副作用的纯策略模块。
|
||||
|
||||
### 算法/规则
|
||||
|
||||
- shadow 同时保留候选与 stable 的命中信息,用同一人工真值分别估计 candidate precision 和 baseline precision。
|
||||
- Canary 使用稳定路由键分流;必须在 manifest 执行循环记录候选 miss,否则只从最终 flag 采集会产生“只有命中样本”的选择偏差。
|
||||
- candidate 正向命中使用 `confirmed / false_positive` 计算 precision;盲审统一使用 `risk_present / risk_absent` 描述业务真值,再在聚合层规范化,不向复核人暴露预测结论。
|
||||
- candidate miss 只有进入服务端抽样表并完成独立双人盲审后才可形成 FN/TN 证据;未抽中的个体不能直接被标签,也不能由“后续无退回”反推正确。
|
||||
- 运行失败与业务误报分开:`failure_count` 表示 evaluator/快照/超时等结构化运行失败,`false_positive_count` 只进入 precision。
|
||||
- 阶段最小样本数、最大错误率、最低 precision 和最大 precision drop 仍由 `ReleaseGuardPolicy` 统一判定。
|
||||
- `ReleaseGuardPolicy.reviewer_quorum` 以 `1..2` 的受控整数随 release 策略固化,
|
||||
telemetry 只统计不同 actor 指纹的最新票;票数不足或不同复核人结论冲突时保持
|
||||
`collecting`,不会把部分意见交给 Release Guard。
|
||||
- 新发布默认开启 recall 门禁,并冻结 `negative_sample_percent / negative_min_reviewed / min_recall / recall_confidence_level`;旧 release 未携带开关时保持兼容,不追溯伪造历史抽样事实。
|
||||
- recall 门禁只比较 `recall_lower_bound` 与 `min_recall`;点估计再高,只要保守下界不足也不能晋级。明显低 precision 可直接失败,不必等待召回样本凑齐。
|
||||
|
||||
### 数据
|
||||
|
||||
#### `agent_asset_release_observations`
|
||||
|
||||
- 身份:`tenant_id / asset_id / release_id / stage / version / rule_code`。
|
||||
- 运行事实:`candidate_hit / baseline_hit / runtime_status / failure_code / business_stage`。
|
||||
- 脱敏来源:`source_kind / source_fingerprint`;不保存 claim ID、单号或业务正文。
|
||||
- 一致性:租户幂等键唯一,保存 payload fingerprint;同一来源不同内容冲突。
|
||||
|
||||
#### `agent_asset_release_labels`
|
||||
|
||||
- 身份复制:tenant、observation、asset、release、stage、version,并通过复合外键绑定原 observation。
|
||||
- 标签:正例判断使用 `confirmed / false_positive`,盲审真值使用 `risk_present / risk_absent`。
|
||||
- 来源:`typed_risk_disposition / release_review / blind_release_review`;数据库组合约束禁止标签语义与来源交叉使用。
|
||||
- 历史:只追加;纠正写新行,不修改或删除旧标签。
|
||||
|
||||
#### `agent_asset_release_audit_samples`
|
||||
|
||||
- 身份复制:tenant、observation、asset、release、stage、version,通过复合外键绑定原 observation。
|
||||
- 分层:`candidate_positive_census / candidate_disagreement_census / candidate_negative_random`。
|
||||
- 抽样事实:保存入样概率和稳定选择分数;同租户 observation 最多一条样本,重放必须匹配 payload fingerprint。
|
||||
- 来源保护:原始单据引用使用 SecretBox 加密,表中不保存明文;只有通过租户与复核角色检查的队列读取才解密。
|
||||
|
||||
三类模型同时具备 ORM 层 UPDATE/DELETE 拒绝。`0018` 建立 observation/label,后继 `0023` 建立 audit sample、扩展标签约束并复用 PostgreSQL append-only 触发器;存在盲审事实或新标签语义时拒绝有损降级。
|
||||
|
||||
### 权限
|
||||
|
||||
- 所有写入、读取和聚合以 `tenant_id` 为第一条件;租户绑定资产不允许其他租户观察或标签。
|
||||
- 平台共享资产可为不同租户分别保存 observation/label,但各租户样本和 precision 不混算。
|
||||
- 标签前重新校验资产当前 `release_id + stage + candidate_version`;旧 release、已晋级阶段或已回滚阶段拒绝新增标签。
|
||||
- 类型化处置标签必须来自数据库中真实存在且同租户的 `RiskDispositionEvent`,action 只允许 `confirm / false_positive`。
|
||||
- 专用复核队列只允许 `manager` 或 `admin`;租户和 actor 全部来自认证上下文,
|
||||
跨租户资产返回 404,非复核角色返回 403,发布发起人自审返回 400。
|
||||
- 标签写入必须携带 `X-Request-Id`;新客户端只发送 `risk_present / risk_absent`,旧客户端的 `confirmed / false_positive` 仅在复核 API 边界映射为盲审真值。标签、
|
||||
actor 指纹和 request 来源只追加保存,客户端不能覆盖 tenant、release 或 actor。
|
||||
- `reviewer_quorum=2` 时必须由两个不同复核人给出相同结论;同一人的重复提交不增加票数,
|
||||
冲突结论进入待仲裁状态并继续阻止晋级。
|
||||
|
||||
### HMAC 与数据真实性边界
|
||||
|
||||
- HMAC 可以证明传输请求由持有密钥的一方生成、请求在允许时间窗口内且签名未被修改。
|
||||
- HMAC 不能证明调用方提交的 `total=100`、`precision=0.99` 真的来自 100 条数据库 observation,也不能证明人工标签存在。
|
||||
- 因此 HMAC 只保留为自动 Monitor 的传输认证和防重放手段;指标必须由接收端使用当前数据库 observation/label 重新计算。
|
||||
- 最终 Monitor 请求应只携带受控 release 触发信息或聚合作业游标,而非可被签名后照单采用的质量汇总数字。
|
||||
- 即使 HMAC 认证失败,也不得影响 stable 规则继续保护业务;应停止晋级、记录安全告警并保持 `collecting`。
|
||||
|
||||
### 降级策略
|
||||
|
||||
- 遥测表或写入暂不可用:不阻断已生效 stable 风险规则和报销主流程,但当前 release 不能晋级,状态保持 collecting 并告警。
|
||||
- 人工标签迟到:保留 observation,待标签追加后重新聚合;不使用默认正确值填补。
|
||||
- 运营端同时展示待标注数量和最早积压时长;超过 24 小时生成结构化逾期告警。
|
||||
- 处置事件与 observation 无法安全关联:拒绝标签,不按相似文本、姓名或评论做模糊匹配。
|
||||
- baseline 标签不完整:`baseline_precision = null`;候选指标可继续采集,但不能声称已完成可靠基线比较。
|
||||
- 负样本未抽中、未完成双人复核或未达到最小复核量:recall、估计 FN 和置信下界保持不可用,继续 collecting 并告警;不会用零填充。
|
||||
- 聚合或 Guard 判定越界:按现有冻结快照恢复 stable;回滚不删除候选 observation 和 label。
|
||||
- 周期聚合异常形成 `release_aggregation_failed` 告警并隔离到单资产;运行失败率、
|
||||
precision 下降、baseline 不可用和自动回滚分别使用独立告警码,稳定版本继续服务。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
### 候选精确率
|
||||
|
||||
```text
|
||||
candidate_precision = candidate_confirmed / (
|
||||
candidate_confirmed + candidate_false_positive
|
||||
)
|
||||
```
|
||||
|
||||
- 分母只包含候选 `candidate_hit=true` 且已有可信最新标签的 observation。
|
||||
- 任一候选正向命中仍未标注时,聚合保持 `collecting`,不得将部分 precision 交给 Release Guard 作为通过证据。
|
||||
|
||||
### 基线精确率
|
||||
|
||||
```text
|
||||
baseline_precision = baseline_confirmed / (
|
||||
baseline_confirmed + baseline_false_positive
|
||||
)
|
||||
```
|
||||
|
||||
- 只使用同一 shadow 样本上 `baseline_hit=true` 的可信标签。
|
||||
- 任一基线正向命中待标注时,baseline precision 显式不可用,不用部分样本制造有利比较。
|
||||
|
||||
### 运行错误率
|
||||
|
||||
```text
|
||||
runtime_error_rate = runtime_failure_count / observed_count
|
||||
```
|
||||
|
||||
- `observed_count` 是该 release/stage/version 的真实运行 observation 数。
|
||||
- `runtime_failure_count` 只统计结构化执行失败,不把业务误报混成技术错误。
|
||||
- 误报通过 precision 体现;运行错误通过 `ReleaseGuardPolicy.max_error_rate` 体现。
|
||||
|
||||
### Recall 与 false negative
|
||||
|
||||
```text
|
||||
recall = TP / (TP + FN)
|
||||
```
|
||||
|
||||
线上负样本分为两层,不能把抽检样本数直接当总体 FN:
|
||||
|
||||
```text
|
||||
disagreement_FN = 全量复核(candidate_hit=false, baseline_hit=true)中的真实风险数
|
||||
random_FN_rate = 随机盲审层真实风险数 / 已完成双人复核的随机样本数
|
||||
estimated_FN = disagreement_FN + random_FN_rate * random_negative_population
|
||||
recall_point = TP / (TP + estimated_FN)
|
||||
|
||||
random_FN_rate_upper = WilsonUpper(random_FN, reviewed, confidence)
|
||||
FN_upper = disagreement_FN + random_FN_rate_upper * random_negative_population
|
||||
recall_lower_bound = TP / (TP + FN_upper)
|
||||
```
|
||||
|
||||
- `false_negative_count` 仅表示已完成法定复核样本中实际观察到的 FN,不等于总体 FN。
|
||||
- `estimated_false_negative_count` 是总体点估计,`false_negative_upper_bound` 是保守上界,二者必须分字段展示。
|
||||
- 随机层存在但尚无已复核样本、仍有抽中样本待审或未达到最小复核量时,上述估计统一保持 `null`。
|
||||
- 没有随机负例人群时可使用全量复核的精确 recall;否则发布门禁只消费 `recall_lower_bound`。
|
||||
- 离线 Golden Case recall 只证明测试集表现,不能冒充线上 recall;线上抽样也不能替代 Golden Case 的边界覆盖。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 模型:租户/release 复合身份、sample/label 到 observation 复合外键、标签来源组合约束和 append-only。
|
||||
- 样本生产:shadow hit/miss、stable baseline hit、Canary hit、manifest 循环 Canary miss 和结构化运行失败。
|
||||
- 标签:专用复核、真实 `RiskDispositionEvent`、盲审语义、负样本双人法定票、来源/actor 脱敏。
|
||||
- 幂等:相同 observation/label 稳定重放;同一来源不同载荷返回冲突。
|
||||
- 租户与时效:跨租户隐藏、陈旧 release/stage/version 拒绝、错误规则/单据来源拒绝。
|
||||
- 聚合:无样本、无候选正例、无标签、部分标签、完整标签、baseline 部分标签、运行失败和 precision drop。
|
||||
- 运营告警:待标注数量/最早时长、24 小时积压、运行失败率、聚合失败、baseline
|
||||
不可用、precision 下降和自动回滚使用去敏结构化告警。
|
||||
- 证据边界:未抽中负样本拒绝标签;预测字段不进入队列/API;抽样不足时 recall/FN 为 null,充分时点估计与下界分离。
|
||||
- 组合回归:Telemetry 生成的 `ReleaseEvaluationInput` 可被现有 Release Guard 消费,且不改变 shadow/Canary/回滚状态机。
|
||||
- PostgreSQL:0018/0023 upgrade/downgrade、复合外键、标签组合约束、数据库 append-only、并发同幂等键单赢家、同 actor 去重和标签/阶段竞争。
|
||||
- 所有验证在 `local-x-financial-linux` 容器内执行,单条命令最大 60 秒。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- [A1] 每条线上发布样本可追溯到 tenant、asset、release、stage、version、rule 和来源指纹,且不含业务正文。
|
||||
- [A2] 相同运行/标签重放只保留一条事实;不同载荷复用同一来源 100% 拒绝。
|
||||
- [A3] 任一候选正向命中未标注时状态为 collecting,不能生成 Release Guard 通过输入。
|
||||
- [A4] precision 和 baseline precision 只由真实 observation 与可信类型化标签计算,外部汇总值不作为权威事实。
|
||||
- [A5] recall/FN 证据不足时明确 unavailable;充分时同时披露实际 FN、估计 FN、FN 上界、recall 点估计和置信下界,门禁只使用下界。
|
||||
- [A6] 跨租户、陈旧 release/stage/version、错误处置来源和纯负样本伪标签均被拒绝。
|
||||
- [A7] 质量越界时自动回滚 stable,遥测故障或 HMAC 故障时停止晋级但不关闭既有稳定保护。
|
||||
- [A8] PostgreSQL 迁移、append-only、并发、后端组合回归、Ruff 和 `git diff --check` 全部在容器内通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 模型注册、真实 manifest hook、类型化处置标签、盲审抽样、服务端聚合、即时 Guard、
|
||||
租户周期调度、发布复核队列和运营控制台已经接通;`0023` 后继迁移与完整 PostgreSQL
|
||||
循环仍须完成最终验证后才能关闭本能力。
|
||||
- 租户调度器只自动处理租户绑定资产。平台共享资产可以按租户保存隔离样本,但在建设跨租户、加权且可审计的聚合口径前,不能由单租户样本自动回滚全局版本。
|
||||
- 线上标签可能集中在高风险或有争议样本,precision 仍可能受人工复核选择偏差影响;控制台必须同时披露 hit、labeled 和 pending 数量。
|
||||
- 规则稀有时可能长期没有候选正例;不能为了晋级降低为“零命中等于 100% precision”,需要延长 shadow 或补充经审核 Golden Case。
|
||||
- 标签纠正采用追加新事实和“每个 actor 最新票”聚合;数据库索引、复合外键和只追加
|
||||
约束已在 PostgreSQL 验证,同 observation/label 的并发单赢家、阶段晋级竞争和调度器
|
||||
advisory leader lease 均有 PostgreSQL 并发验证。
|
||||
- HMAC 密钥泄露会让攻击者通过传输认证,但仍不应允许其伪造数据库 observation/label;服务端重算是不可省略的第二道边界。
|
||||
- 线上 recall 已有分层抽样、预测盲化、双人复核、冲突保持 collecting、最小样本量和置信下界;仍需用试点数据校准抽样比例、人工一致率与业务风险容忍度,不能把默认阈值当行业通用真理。
|
||||
|
||||
## 本轮实现记录
|
||||
|
||||
- 2026-07-16:完成现有 Loader、平台风险评测、shadow_evaluations、风险处置和 AI workflow feedback/outcome 的只读审计,确认 `RiskDispositionEvent` 是当前最可信线上人工标签源,通用学习结果缺少 release 身份不能直接用于门禁。
|
||||
- 2026-07-16:新增独立 observation/label 模型与服务,完成脱敏、append-only、稳定幂等、租户/陈旧 release 拒绝、shadow/Canary 样本生产、可信标签和保守聚合。
|
||||
- 2026-07-16:独立切片阶段新增遥测测试 7 项,与当时 Release Guard/Runtime 组合共 19 项通过;该阶段留下的共享注册、迁移和运行 hook 已在后续记录中完成。
|
||||
- 2026-07-16:完成主模型注册、迁移所有权与 `0018`;一次性 PostgreSQL 17 完整迁移循环、复合外键和数据库 append-only 探针通过。
|
||||
- 2026-07-16:真实 shadow/Canary/active manifest 执行已写 observation;类型化风险处置自动追加标签并即时触发 Guard,相同聚合快照幂等复用测试运行,低 precision 或运行失败自动恢复 stable。
|
||||
- 2026-07-16:Release Monitor HTTP 改为空触发 + HMAC,禁止调用方提交 precision/total;新增租户级周期调度作为即时监控失败的补偿链。发布组合回归 44 项、调度/风险定向回归 24 项通过。
|
||||
- 2026-07-16:完成后端大文件职责拆分:`agent_assets.py` endpoint 降至 714 行、
|
||||
`AgentAssetService` 降至 675 行、`AgentAssetReleaseGuardService` 降至 636 行;
|
||||
风险规则子路由、资产序列化和发布纯策略分别独立,旧路由路径、公开类型导入和状态机行为保持兼容。
|
||||
- 2026-07-16:发布纯策略新增 `reviewer_quorum`(默认 1、范围 1..2)并随 release state 保存,
|
||||
专用去敏复核队列按独立 actor 计票,禁止发布人自审,双人同意前或结论冲突时保持 collecting。
|
||||
- 2026-07-16:发布控制台展示真实样本、命中、待审、最早积压时长、运行失败率、
|
||||
candidate/baseline precision 和 recall 不可用;新增积压、超时、运行故障、聚合失败、
|
||||
precision 下降、baseline 不可用和自动回滚结构化告警。
|
||||
- 2026-07-16:新增 append-only 盲审样本、正例/分歧全量与其余负例稳定随机抽样;来源引用加密保存,observation 与 sample 同事务写入,保护失败整体回滚。
|
||||
- 2026-07-16:发布复核队列改为预测盲化混排,负样本强制两个不同复核人;新增实际/估计 FN、Wilson FN 上界、recall 点估计与保守下界,Release Guard 只消费下界。
|
||||
- 2026-07-16:前端拒绝 prediction hit 字段,使用中性业务真值动作,并显示负样本池、抽样进度、积压及置信方法;证据缺失保持不可用,不渲染为零。
|
||||
- 2026-07-16:新增 `0023` 后继迁移和标签来源组合约束;最终 PostgreSQL 全链升级/降级、并发和全量回归结果待验证后回填。
|
||||
@@ -0,0 +1,120 @@
|
||||
# AI 分阶段发布真实遥测 开发 TODO
|
||||
|
||||
更新时间:2026-07-17
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 每项必须回链 `CONCEPT.md` 对应章节;没有代码、迁移、接口或容器证据不得勾选。
|
||||
- observation、label、聚合和 Release Guard 输入必须按证据层分开,不能用 HMAC 请求或客户端汇总值替代数据库事实。
|
||||
- `collecting` 不得包装成通过;recall/false-negative 没有达到独立盲审证据阈值时必须保持 unavailable。
|
||||
- 所有后端、迁移和并发验证只在 `local-x-financial-linux` 容器内执行,单条命令最长 60 秒。
|
||||
|
||||
## 1. 调研与边界
|
||||
|
||||
- [x] [CONCEPT: 背景与问题] 审计 Loader、平台风险评测、shadow_evaluations、风险处置和 AI workflow feedback/outcome 链路,确认线上发布评测缺少持久真实样本。
|
||||
证据:`expense_claim_risk_rule_loader.py`、`expense_claim_platform_risk.py`、`risk_dispositions.py`、`expense_workflow_learning.py`、`ai_learning.py` 只读审计。
|
||||
- [x] [CONCEPT: 背景与问题] 确认 `RiskDispositionEvent confirm/false_positive` 是当前可绑定风险命中的可信人工结论,通用 feedback/outcome 缺少完整 release 身份。
|
||||
证据:`risk_disposition.py`、`risk_dispositions.py` 与 `agent_asset_release_telemetry.py` 的可信事件查询和关联校验。
|
||||
- [x] [CONCEPT: HMAC 与数据真实性边界] 冻结 HMAC 只负责传输认证、防篡改和防重放,不证明汇总指标的数据真实性。
|
||||
证据:`CONCEPT.md`“HMAC 与数据真实性边界”;现有 `agent_asset_release_monitor_auth.py` 与 `ReleaseEvaluationInput` 契约对照审计。
|
||||
- [x] [CONCEPT: 目标与非目标] 明确未标注 collecting、敏感数据不落遥测表、线上 recall/FN 证据不足不可用。
|
||||
证据:`CONCEPT.md`“目标与非目标”“Recall 与 false negative”。
|
||||
|
||||
## 2. 契约与设计
|
||||
|
||||
- [x] [CONCEPT: 数据] 定义 observation 与 label 的 tenant/asset/release/stage/version 复合身份、稳定幂等键和只追加边界。
|
||||
证据:`server/src/app/models/agent_asset_release_telemetry.py`。
|
||||
- [x] [CONCEPT: 证据链与自动判定] 定义 observation → trusted label → aggregate → ReleaseEvaluationInput → Release Guard 的证据链。
|
||||
证据:`CONCEPT.md`“证据链与自动判定”、`ReleaseTelemetryAggregate.to_release_evaluation_input()`。
|
||||
- [x] [CONCEPT: 算法/规则] 分开定义运行失败、业务误报、candidate precision、baseline precision 和缺失负样本真值。
|
||||
证据:`agent_asset_release_telemetry.py` 的 `aggregate()`;`test_agent_asset_release_telemetry.py` 的 collecting、baseline 和 FN 不可用断言。
|
||||
- [x] [CONCEPT: 权限] 冻结专用发布复核接口的角色矩阵、双人复核阈值和跨租户 HTTP 错误契约。
|
||||
证据:`require_rule_reviewer_user` 只允许 manager/admin;跨租户 404、非角色 403、
|
||||
发布人自审 400;`reviewer_quorum` 限制 1..2 且按不同 actor 指纹计票。
|
||||
|
||||
## 3. 独立模型与服务
|
||||
|
||||
- [x] [CONCEPT: 数据] 新增 append-only observation/label ORM 模型、复合租户/release 外键和更新/删除拒绝。
|
||||
证据:`server/src/app/models/agent_asset_release_telemetry.py`。
|
||||
- [x] [CONCEPT: 后端] 实现真实 shadow 结果、Canary/active 命中和 manifest 执行循环样本生产器。
|
||||
证据:`AgentAssetReleaseTelemetryService.record_expense_risk_result()`、`record_manifest_evaluation()`。
|
||||
- [x] [CONCEPT: 后端] 实现专用发布复核标签和可信 `RiskDispositionEvent` 标签转换,不接收自由评论。
|
||||
证据:`record_review_label()`、`record_risk_disposition_label()`。
|
||||
- [x] [CONCEPT: 后端] 实现租户隔离、陈旧 release/stage/version 拒绝、来源关联校验和稳定幂等冲突。
|
||||
证据:`_require_current_release()`、`_observation_replay()`、`_label_replay()` 及对应测试。
|
||||
- [x] [CONCEPT: 算法与公式] 实现保守聚合;未标注候选命中保持 collecting,基线标签不完整时 baseline precision 不可用。
|
||||
证据:`ReleaseTelemetryAggregate`、`aggregate()`、`to_release_evaluation_input()`。
|
||||
- [x] [CONCEPT: 数据] 实现 claim、事件、actor 来源指纹化,不保存业务正文、评论和账号原值。
|
||||
证据:模型无自由文本业务字段;`_fingerprint()`;脱敏测试断言。
|
||||
|
||||
## 4. 共享注册、迁移与运行接入
|
||||
|
||||
- [x] [CONCEPT: 数据] 在 `db/base.py` 与 `models/__init__.py` 注册 `AgentAssetReleaseObservation` 和 `AgentAssetReleaseLabel`,保证主应用 metadata 与迁移所有权检查可见。
|
||||
证据:`db/base.py`、`models/__init__.py`、`schema_ownership.py`、`migration_preflight.py` 已登记两张迁移自有表;前置检查 77 项通过。
|
||||
- [x] [CONCEPT: 数据] 新增后继 `20260716_0018` Alembic 迁移,创建两张表、复合租户/release 约束、检查约束、索引和数据库级 append-only UPDATE/DELETE 触发器。
|
||||
证据:`20260716_0018_agent_asset_release_telemetry.py`;一次性 PostgreSQL 17 完整升级/降级循环 1 项通过,静态迁移/schema owner 回归 112 项通过。
|
||||
- [x] [CONCEPT: 算法/规则] 在真实候选 manifest 执行循环接入 `record_manifest_evaluation()`,完整记录 shadow/Canary hit、miss 和结构化执行失败。
|
||||
证据:`expense_claim_platform_risk.py`、`expense_claim_release_telemetry.py`;`test_agent_asset_release_runtime.py` 覆盖 shadow、Canary、active 和损坏快照,`test_agent_asset_release_telemetry.py` 覆盖结构化运行失败。
|
||||
- [x] [CONCEPT: 后端] 在类型化风险处置事务接入 release label,按同租户、同 claim/rule、当前 release 精确关联 observation;关联失败保守拒绝。
|
||||
证据:`agent_asset_release_disposition_labels.py`、`risk_disposition_release_sync.py`;风险确认/误报会追加标签,误报越界自动回滚 stable。
|
||||
- [x] [CONCEPT: 后端] 将现有 Release Monitor 从“提交外部汇总数字”改为“触发服务端聚合”,只在 aggregate ready 时构造 `ReleaseEvaluationInput`。
|
||||
证据:`AgentAssetReleaseMonitor.evaluate_current()` 只接受 release 身份;HTTP body 为禁止额外字段的空触发契约,真实聚合未 ready 时不调用 Guard。
|
||||
- [x] [CONCEPT: HMAC 与数据真实性边界] 保留 HMAC 作为 Monitor 传输认证,但禁止签名请求覆盖数据库聚合结果,并补充签名通过但汇总伪造的反向测试。
|
||||
证据:`agent_asset_releases.py`、`AgentAssetReleaseMonitorTriggerWrite`;带有效签名的伪造 `precision/total` 请求仍返回 422。
|
||||
- [x] [CONCEPT: 降级策略] 遥测持久化或聚合失败时保持 stable 规则、停止晋级并记录结构化告警,不让观测故障阻断正常报销。
|
||||
证据:`ExpenseClaimReleaseTelemetryRecorder`、`risk_disposition_release_sync.py` 将遥测/标签/监控故障隔离并记录日志;未获得 ready 真实聚合不会写 passed,也不会改变 stable 路由。
|
||||
- [x] [CONCEPT: 后端] 按职责拆分 Agent 资产接口、版本只读投影和 Release Guard 纯策略计算,保持公开 API 与状态机行为稳定。
|
||||
证据:`agent_assets.py` endpoint 714 行、`agent_asset_risk_rules.py` 534 行、`agent_assets.py` service 675 行、`agent_asset_serialization.py` 217 行、`agent_asset_release_guard.py` 636 行、`agent_asset_release_policy.py` 201 行,相关核心文件均低于 800 行;纯策略模块保存范围为 1..2 的 `reviewer_quorum`,第 5 节已完成复核执行与运营闭环。
|
||||
|
||||
## 5. 自动聚合、告警与运营闭环
|
||||
|
||||
- [x] [CONCEPT: 证据链与自动判定] 实现按 tenant/asset/release/stage/version 的周期聚合作业与幂等快照。
|
||||
证据:`agent_asset_release_scheduler.py` 按租户有界扫描;`AgentAssetReleaseMonitor._existing_evaluation()` 复用相同 release 聚合快照,不重复生成测试运行。
|
||||
- [x] [CONCEPT: 证据链与自动判定] aggregate ready 后自动调用 Release Guard;collecting 只更新采集状态,不写虚假 passed test run。
|
||||
证据:人工标签提交后即时触发 monitor,后台 scheduler 提供失败补偿;`test_agent_asset_release_monitor.py` 覆盖 collecting 不调用 Guard、低 precision/运行失败自动回滚与相同快照幂等。
|
||||
- [x] [CONCEPT: 降级策略] 增加待标注数量/时长、运行失败率、precision 下降、baseline 不可用、聚合失败和自动回滚告警。
|
||||
证据:`agent_asset_release_alerts.py`、`AgentAssetReleaseMonitor._metrics()`;24 小时
|
||||
待审逾期和单资产聚合失败均返回结构化告警,定向 Monitor/Telemetry/Runtime 29 项通过。
|
||||
- [x] [CONCEPT: 前端] 在发布控制台展示 observed/hit/labeled/pending、候选/基线 precision、运行失败和召回证据。
|
||||
证据:`AuditReleaseMonitorPanel.vue` 展示负样本池、抽样进度、积压、实际/估计 FN、FN 上界、recall 点估计/下界/置信方法;空值不渲染为零。
|
||||
- [x] [CONCEPT: 权限] 实现认证发布复核队列、操作审计和需要时的双人复核,不允许规则发布人独自伪造所有标签。
|
||||
证据:`agent_asset_release_review.py`、`agent_asset_release_label_votes.py`、专用 GET/POST
|
||||
API;`X-Request-Id`、append-only label、actor HMAC 指纹、发布人隔离和独立双人同意均有测试。
|
||||
- [x] [CONCEPT: Recall 与 false negative] 实现独立分层负样本抽样、预测盲化、双人标注、冲突保持 collecting 和保守召回估计。
|
||||
证据:`agent_asset_release_sampling.py`、`agent_asset_release_review.py`、
|
||||
`agent_asset_release_aggregation.py`、`agent_asset_release_recall.py`;前端只发送
|
||||
`risk_present / risk_absent`,负样本要求两个不同 actor,门禁只读取 recall 下界。
|
||||
- [x] [CONCEPT: 数据] 完成 `0023` audit sample 迁移注册、数据库标签组合约束、append-only 触发器和无损降级验证。
|
||||
证据:`20260716_0023_agent_asset_release_blind_audit.py`、`release_telemetry_migration_assertions.py`;fresh PostgreSQL 完整迁移链、约束、触发器及降级/再升级均通过。
|
||||
|
||||
## 6. 测试与验证
|
||||
|
||||
- [x] [CONCEPT: 测试方案] 独立模型/服务测试覆盖 shadow、Canary、baseline、真实 typed disposition、脱敏、幂等、租户、陈旧 release、append-only、collecting 和 FN 不可用。
|
||||
证据:容器内 `test_agent_asset_release_telemetry.py` 7 项通过。
|
||||
- [x] [CONCEPT: 测试方案] 与现有 Release Guard 和 Runtime 组合回归通过。
|
||||
证据:容器内 `test_agent_asset_release_guard.py`、`test_agent_asset_release_runtime.py`、`test_agent_asset_release_telemetry.py` 共 19 项通过;Ruff 通过,`git diff --check` 通过。
|
||||
- [x] [CONCEPT: 测试方案] 新增 0018 upgrade/downgrade、schema owner、复合外键和数据库 append-only PostgreSQL 验证。
|
||||
证据:一次性 `pgvector/pgvector:pg17` 数据库中完整迁移循环 1 项通过;迁移运行探针验证复合租户外键、幂等唯一约束和两张表的数据库级 UPDATE/DELETE 拒绝。
|
||||
- [x] [CONCEPT: 测试方案] 新增 PostgreSQL 并发同 observation、同 label、标签与阶段晋级竞争和幂等冲突测试。
|
||||
证据:一次性 PostgreSQL 17 中 `test_agent_asset_release_telemetry_concurrency_postgres.py` 4 项通过;相同重放只保留一条,不同载荷单赢家,阶段转换持锁后旧标签保守拒绝。
|
||||
- [x] [CONCEPT: 测试方案] 验证 audit sample 并发单赢家、同 actor 重复不增加负样本法定票、第二独立 actor 完成双人复核,以及预测字段不进入前端队列状态。
|
||||
证据:`test_agent_asset_release_telemetry_concurrency_postgres.py` 覆盖单赢家和独立双人票;`test_agent_asset_release_telemetry.py` 与 `agent-release-monitor-panel.test.mjs` 验证队列不暴露 candidate/baseline 命中预测。
|
||||
- [x] [CONCEPT: 测试方案] 跑通真实风险循环 → observation → disposition label → aggregate → Release Guard 回滚端到端。
|
||||
证据:`test_agent_asset_release_runtime.py` 与 `test_risk_dispositions.py` 覆盖真实执行样本、类型化处置标签、服务端聚合、低 precision 自动恢复 stable;发布组合回归 44 项通过。
|
||||
- [x] [CONCEPT: 测试方案] 验证职责拆分后的资产服务、风险规则子路由、Release Guard/Runtime、Monitor/Scheduler/Telemetry 和 API schema 兼容性。
|
||||
证据:容器内资产服务 27 项、Guard/Runtime 13 项、Monitor/Scheduler/Telemetry 22 项、风险规则生成/解释 30 项、修订/反馈 14 项通过;迁移后的既有 publish HTTP 防绕过用例通过,Ruff 与 `git diff --check` 通过。
|
||||
- [x] [CONCEPT: 指标与验收] 验证 HMAC 失败、遥测库失败、标签积压和聚合失败均停止晋级但不破坏 stable 业务保护。
|
||||
证据:`test_agent_asset_release_runtime.py` 覆盖签名失败;`test_agent_asset_release_monitor.py` 覆盖 collecting、积压和聚合失败;`ExpenseClaimReleaseTelemetryRecorder` 隔离遥测持久化异常,stable 报销路径继续服务。
|
||||
- [x] [CONCEPT: 指标与验收] 完成相关后端全量回归、Ruff、迁移完整循环和 `git diff --check`,逐项回填 A1-A8 证据。
|
||||
证据:遥测组合 45 项通过;fresh PostgreSQL 总探针 `87 passed / 0 skipped / 0 failed`,其中发布遥测并发 7 项;Web 全量 815 项及 Vite build 通过;新增 Python 文件 Ruff 与 `git diff --check` 通过。全仓既有 Ruff 基线债不伪装为本轮新增错误。
|
||||
|
||||
## 7. 文档收尾
|
||||
|
||||
- [x] [CONCEPT: 本轮实现记录] 新建独立 CONCEPT/TODO,记录证据边界、已实现切片和共享集成缺口。
|
||||
证据:`document/development/2026-07-16/feature/ai-release-real-telemetry/CONCEPT.md`、`TODO.md`。
|
||||
- [x] [CONCEPT: 风险与开放问题] 共享集成完成后回填 0018、运行 hook、自动作业、PostgreSQL 和端到端证据。
|
||||
证据:本 TODO 第 4-6 节与 `CONCEPT.md`“本轮实现记录”已回填;发布面板已展示积压、失败、precision 和 recall,生产运营效果由下一条真实流量验收单独保留。
|
||||
- [x] [CONCEPT: 指标与验收] 与上位 AI 闭环 TODO 对齐代码与容器验证状态。
|
||||
证据:`document/development/2026-07-13/feature/ai-expense-closed-loop-and-value-proof/TODO.md` 已回填 Golden、Canary、盲审和回滚的工程证据。
|
||||
- [ ] [CONCEPT: 指标与验收] 使用生产真实流量、独立复核样本和试点阈值验证 Golden/Canary/自动回滚运营效果。
|
||||
证据要求:目标企业生产 observation/label、盲审样本、阈值签字和真实回滚演练;本地测试不得替代。
|
||||
@@ -0,0 +1,237 @@
|
||||
# 商业计量、客户 ROI 与可持续定价 概念文档
|
||||
|
||||
更新时间:2026-07-17
|
||||
|
||||
## 功能一句话
|
||||
|
||||
把套餐、订阅、权益、真实用量、内部成本、客户确认价值和平台毛利拆成可审计事实,并只在成本与价值证据同时成立时给出可持续定价走廊。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
- 平台要成为可长期经营的产品,既要证明客户省了钱,也要知道每个租户消耗了多少 OCR、AI、存储、连接器和支持成本。
|
||||
- “风险金额”“预计节省”“流程耗时”不能直接作为客户 ROI;平台收入、平台内部成本和客户价值也不能混在一个指标里。
|
||||
- 只有套餐创建而没有暂停、取消、历史查询和配额硬门禁,商业后台无法真正运营。
|
||||
- 仅在执行前读取 `SUM(usage)` 再放行无法抵抗并发;两个工具可同时看到剩余额度并一起执行,事后计量再准确也已形成不可逆超卖。
|
||||
- 固定拍一个价格无法适配客户规模、真实成本和价值覆盖。需要先计算平台可持续下限,再计算客户价值可接受上限;没有交集时不能强行报价。
|
||||
- 试点期通常缺少 30/90 天真实成本和财务确认收益,因此系统必须显示“采集中”,而不是用 mock 或估计值伪造单位经济性。
|
||||
|
||||
本能力是 `2026-07-13/feature/ai-expense-closed-loop-and-value-proof` 中商业模式与价值证明部分的实施拆分。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- [G1] 建立租户隔离、版本化的套餐、订阅和权益配置。
|
||||
- [G2] 建立追加式用量与内部成本事实,支持幂等、冲回、配额和并发门禁。
|
||||
- [G3] 把商业权益门禁与安全门禁做收紧式合并,付费不能绕过高风险人工审核。
|
||||
- [G4] 严格分开客户收费、内部成本、平台贡献毛利、财务确认现金节省、客户 ROI 和工时价值。
|
||||
- [G5] 支持套餐/订阅/权益生命周期、历史查询、用量成本查询和数据质量状态。
|
||||
- [G6] 根据真实成本和财务确认节省给出基础费下限、价值上限和可封顶成功费,不自动改合同。
|
||||
- [G7] 形成“试点采集 → 基础订阅 → 基础费 + 封顶成功费”的可验证商业演进路径。
|
||||
- [G8] 用不可变账期承载收费、用量、成本与配额历史,并对商业配置和自动续期保留脱敏追加式审计。
|
||||
|
||||
### 非目标
|
||||
|
||||
- [NG1] 不在代码中硬编码某个客户的最终价格、税率、折扣或合同条款。
|
||||
- [NG2] 不把风险暴露、预计机会、未确认结果、未锁定汇率或未经客户认可的工时估值计入价值定价。
|
||||
- [NG3] 不让套餐或配额放宽审批、风险、租户和人工确认门禁。
|
||||
- [NG4] 不自建开票、税务、收款或第三方订阅扣费网络;只保存受控外部订阅引用。
|
||||
- [NG5] 不跨币种直接求和,也不在缺少同币种成本/价值时生成综合 ROI。
|
||||
- [NG6] 不把定价场景结果自动写成生效套餐,最终合同仍需平台商业负责人审批。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
### 用户
|
||||
|
||||
1. 平台商业管理员:维护套餐版本、订阅、权益、状态和定价场景。
|
||||
2. 平台运营/财务:查看用量、成本、毛利和异常数据质量。
|
||||
3. 客户 CFO/财务负责人:查看自己租户的当前套餐、用量、配额和客户价值口径。
|
||||
4. 产品运行时:在执行 OCR、AI 或连接器能力前检查配额,并写入真实用量。
|
||||
|
||||
### 核心场景
|
||||
|
||||
1. 试点客户先配置 `pilot` 套餐和明确的合同周期,开始采集真实用量、成本与已确认价值。
|
||||
2. 运行时检查某项权益;只有商业配额允许且安全决策为 allow 时才最终允许。
|
||||
3. 同一用量事件重试返回首次结果;不同内容复用幂等键返回冲突。
|
||||
4. 客户暂停、逾期、取消或过期时,商业权益即时失败关闭,历史用量和成本不被删除。
|
||||
5. 商业负责人选择 90 天窗口,输入目标贡献毛利率和客户最大价值分享比例,系统按币种输出定价走廊。
|
||||
6. 成本下限高于价值上限时,系统建议先优化单位经济性或扩大可信价值,不生成强行报价。
|
||||
|
||||
## 功能能力
|
||||
|
||||
- [C1] 套餐版本:subscription、usage、hybrid、pilot、custom,支持生效区间和旧版本退役。
|
||||
- [C2] 订阅快照:合同周期、计费周期、席位、基础费、外部订阅引用和状态历史。
|
||||
- [C3] 权益与配额:feature、metered、unlimited,包含量、硬上限、重置周期和超额策略。
|
||||
- [C4] 用量事实:usage、credit、adjustment、reversal,保存主体、来源、correlation 和首次请求指纹。
|
||||
- [C5] 成本事实:AI、OCR、存储、连接器、支持、实施、基础设施、支付等分类及汇率快照。
|
||||
- [C6] 商业分析:收费、成本、贡献毛利、确认节省、客户 ROI 和工时价值分账展示。
|
||||
- [C7] 定价走廊:最低可持续收费、最高价值对齐收费、最大成功费和证据状态。
|
||||
- [C8] 生命周期与查询:暂停、恢复、逾期、取消、过期,以及套餐/订阅/权益/用量/成本历史。
|
||||
- [C9] 账期与审计:月/季/年自动续期、合同边界失败关闭、账期历史和商业管理追加审计。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 前端
|
||||
|
||||
- 商业工作台分为“当前账户”“套餐与订阅”“权益与配额”“用量与成本”“价值与定价”五块。
|
||||
- 客户 ROI 与平台贡献毛利必须使用不同卡片、不同说明,不允许用一个“综合收益”混合展示。
|
||||
- 多币种按币种分行;缺成本、缺确认价值、仅有试点数据和证据冲突使用不同状态。
|
||||
- 暂停、取消和定价场景均需要确认;终态操作明确提示不能原地恢复。
|
||||
- 普通客户财务只能查看本租户账户;平台级配置、成本、毛利和定价仅平台管理员可见。
|
||||
|
||||
### 后端
|
||||
|
||||
- `CommercialAdminService` 管理套餐、订阅、权益和状态转换。
|
||||
- `CommercialBillingPeriodService` 签发和定位不可变账期;用量、成本和运行时预占必须绑定真实账期编号。
|
||||
- `CommercialSubscriptionRolloverService` 在订阅行锁内按月/季/年边界幂等续期;合同制或跨越 `ends_at` 时失败关闭。
|
||||
- `CommercialRolloverScheduler` 以 PostgreSQL advisory lock 选举单一执行者,再按订阅行锁串行签发到期账期。
|
||||
- `CommercialAdminAuditService` 只记录字段白名单快照,排除合同正文、配置、外部订阅编号、元数据和凭证类字段。
|
||||
- `CommercialQueryService` 负责租户范围内历史与追加事实查询。
|
||||
- `CommercialEntitlementService` 计算配额和商业/安全合并门禁。
|
||||
- `CommercialMeteringService` 写追加式用量和成本、执行幂等与冲回。
|
||||
- `CommercialRuntimeReservationService` 在订阅/权益锁内完成执行前额度预占,管理 reserved、committed、released、expired、reconciliation_required 和 committed_reconciliation_required 状态。
|
||||
- `CommercialRuntimeBridge` 把可信 AgentRun、中央工具执行、真实 AgentToolCall 和商业事实接成预占—执行—结算链;未配置商业计量时保持兼容。
|
||||
- `CommercialRuntimeReconciler` 只根据可验证的工具/运行终态补偿过期预占,不按超时猜测业务是否发生。
|
||||
- `CommercialAnalyticsService` 按窗口和币种分账聚合,不伪造缺失数据。
|
||||
- `CommercialPricingService` 只读分析真实成本和确认节省,输出价格区间,不写套餐。
|
||||
- HTTP 管理入口仅平台管理员可用;租户账户读取仅 finance、executive 或平台管理员可用。
|
||||
|
||||
### 算法/规则
|
||||
|
||||
- 用量硬配额在数据库锁内计算,最终用量超过限制时整个写入失败。
|
||||
- 真实工具执行前按 `已用量 + 有效预占 + 本次预占 <= 硬上限` 原子判断;成功时真实量不得超过预占,失败/阻断只释放预占,不写用量或成本。
|
||||
- call 基准可直接预占一次;token、duration 等变量基准必须由执行器声明并强制最大量。缺少可信 hard max 时拒绝执行,不用正文长度或估算值代替。
|
||||
- 已有相同 tool call 的预占请求按指纹稳定重放;真实 AgentToolCall 的用量/成本继续使用追加式幂等键。用量已写入但成本失败时进入 `committed_reconciliation_required`,重试只补缺失成本,不重复占用额度或追加用量。
|
||||
- 权益已有用量后,配额、定价和有效期不可回改,只允许暂停/恢复;结构变化必须新建订阅版本。
|
||||
- 客户 ROI 只使用财务确认 canonical 现金节省与客户收费。
|
||||
- 贡献毛利只使用平台收费与内部成本,不混入客户节省。
|
||||
- 定价只在相同币种内计算,并保留成本/价值证据状态。
|
||||
|
||||
### 数据
|
||||
|
||||
- `tenant_commercial_plans`:租户、套餐编码、版本、价格模型、基础费、币种、生效期和合同条款摘要。
|
||||
- `tenant_subscriptions`:租户、套餐、周期、基础费快照、状态、席位、外部引用和版本。
|
||||
- `commercial_entitlements`:租户、订阅、权益键、计量键、配额、状态和有效期。
|
||||
- `usage_meter_events`:追加式用量、冲回引用、幂等键、请求指纹和关联链。
|
||||
- `commercial_cost_events`:追加式内部成本、原币/报告币、汇率、分摊键和冲回引用。
|
||||
- `commercial_runtime_reservations`:工具执行前的可变运营占位,保存 tenant/subscription/entitlement/run/tool call、基准、预占量、真实量、周期、配置快照、状态和补偿原因;它参与配额但不是客户用量事实。
|
||||
- `commercial_billing_periods`:不可变账期签发事实,保存订阅/套餐引用、窗口、顺序、币种、基础费、席位、计价模式和来源快照;PostgreSQL 禁止更新和删除。
|
||||
- `commercial_admin_events`:套餐、订阅、权益、账期和续期动作的脱敏追加审计,保存 tenant、actor、`X-Request-Id`、原因、动作、资源版本和白名单 before/after。
|
||||
- `usage_meter_events.period_key` 表示真实账期键,`quota_period_key` 独立表示权益重置周期;成本与运行时预占同样通过 `billing_period_id` 绑定账期,避免订阅当前周期滚动后污染历史。
|
||||
- 事实表在 PostgreSQL 使用触发器禁止 UPDATE/DELETE,租户复合外键防止跨租户引用。
|
||||
|
||||
运行时占位状态如下:
|
||||
|
||||
```text
|
||||
reserved -> committed # 成功且 actual <= reserved
|
||||
reserved -> released # 工具失败或执行前阻断
|
||||
reserved -> expired # 运行已终止且没有真实工具调用
|
||||
reserved -> reconciliation_required # 已发生调用但缺少执行前预占等人工补偿场景
|
||||
committed -> committed_reconciliation_required -> committed
|
||||
# 用量已提交、成本等后续事实失败,幂等补齐后恢复
|
||||
```
|
||||
|
||||
过期但运行仍在进行、运行记录缺失或工具终态不确定时继续保留额度,不允许补偿器仅因 TTL 到期释放后造成超卖。
|
||||
`committed_reconciliation_required` 已有真实用量事实,因此不再计入有效预占;配额只消费一次,同时保留明确的待补偿队列。
|
||||
|
||||
### 权限
|
||||
|
||||
- 平台管理员可配置所有租户商业账户,但商业配置不能授予财务确认或风险审批能力。
|
||||
- finance/executive 只读本租户当前账户,不读取平台内部成本或其他租户数据。
|
||||
- manager、employee 默认无商业账户与商业分析权限。
|
||||
- 所有管理接口从认证上下文判断平台管理员,目标租户来自受控路径参数。
|
||||
|
||||
### 降级策略
|
||||
|
||||
- 无订阅:返回 unavailable 和明确说明,不自动赠送无限权益。
|
||||
- 订阅非 active/trialing:配额保留展示但最终消费失败关闭。
|
||||
- 缺成本:贡献毛利和可持续价格下限不可用。
|
||||
- 缺财务确认价值:客户 ROI、价值上限和成功费不可用,建议继续试点采集。
|
||||
- 多币种缺少共同币种:分别展示,禁止跨币种净额。
|
||||
- 并发或幂等冲突:返回 409,不覆盖首次事实。
|
||||
- 生产中央工具路径未配置 runtime meter:兼容执行且不生成商业事实;配置只在当前订阅周期和权益有效期内参与门禁,历史过期配置不会误触发 enforcement。
|
||||
- 已发生的旧直接工具路径缺少执行前预占:不补写为正常用量,持久化 `reconciliation_required` 并冻结对应容量,等待受控补偿;即使当前合同已暂停,也保留其唯一可验证的租户、订阅和权益归属。
|
||||
- 用量已追加但内部成本写入失败:持久化 `committed_reconciliation_required`,配额以真实用量为准且预占归零;按相同 tool call 重试只补成本,完成后回到 committed。
|
||||
- 自动续期只处理 `trialing/active + auto_renew`;月、季、年按自然月边界滚动。合同制、缺少可推导边界或下一完整账期越过 `ends_at` 时失败关闭,不创建部分账期或猜测续约。
|
||||
- 调度器即使发生重复扫描或多进程竞争,也先获取 leader lease,再锁定订阅;账期窗口和幂等键的唯一约束保证同一周期最多签发一次。数据库触发器还会按租户与订阅获取事务级 advisory lock,并拒绝任何半开区间重叠账期,防止绕过服务层直接写入破坏时间线。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
### 客户 ROI
|
||||
|
||||
```text
|
||||
customer_roi = (verified_cash_savings - customer_charges) / customer_charges
|
||||
```
|
||||
|
||||
- `verified_cash_savings` 只包含独立财务确认、canonical、已计冲回的现金节省。
|
||||
- `customer_charges` 来自订阅基础费快照和有可信同币种单价的用量计费。
|
||||
- 分母必须大于 0;否则状态为 unavailable。
|
||||
|
||||
### 平台贡献毛利
|
||||
|
||||
```text
|
||||
contribution_margin = customer_charges - internal_costs
|
||||
contribution_margin_rate = contribution_margin / customer_charges
|
||||
```
|
||||
|
||||
- 内部成本来自追加式成本账本,不使用估算页面数字。
|
||||
|
||||
### 可持续定价走廊
|
||||
|
||||
```text
|
||||
minimum_sustainable_charge = internal_costs / (1 - target_margin_rate)
|
||||
maximum_value_aligned_charge = verified_cash_savings * max_value_share
|
||||
maximum_success_fee = max(0, maximum_value_aligned_charge - minimum_sustainable_charge)
|
||||
```
|
||||
|
||||
- 当 `minimum_sustainable_charge <= maximum_value_aligned_charge` 时,建议 hybrid:基础费不低于成本下限,成功费封顶为剩余价值空间。
|
||||
- 只有成本证据时建议 subscription;成本与价值都不足时建议 pilot_collecting。
|
||||
- 成本下限高于价值上限时建议 optimize_unit_economics,不自动提高客户报价。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 模型/迁移:租户复合外键、状态检查、金额符号、冲回引用和 append-only。
|
||||
- 服务:套餐版本、订阅终态、权益不可回改、配额硬门禁、幂等、冲回和多币种。
|
||||
- 权限:平台管理员、租户财务、manager、employee 与跨租户访问。
|
||||
- 分析:收费/成本/节省/毛利/ROI 分账,缺证据状态和 `as_of` 回放。
|
||||
- 定价:可行区间、成本高于价值、仅成本、完全无证据和多币种。
|
||||
- 前端:状态、权限、操作确认、空态、错误态、币种分组与生产构建。
|
||||
- PostgreSQL:并发配额、同幂等键、成本冲回单赢家和迁移完整性。
|
||||
- PostgreSQL 账期:0020→0021→0020 升降级、账期/审计 UPDATE/DELETE 拒绝、重叠账期 INSERT 拒绝、历史事实账期绑定和双线程续期单赢家。
|
||||
- 运行时预占:无配置兼容、成功结算、失败释放、变量 hard max、真实量超预占、幂等重放、历史配置隔离、直接路径补偿和过期补偿。
|
||||
- 所有命令在 `local-x-financial-linux` 容器内执行,单次最长 60 秒。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- [A1] 每个商业消费可追溯到租户、订阅、权益、用量事件、来源和 correlation。
|
||||
- [A2] 并发不能突破硬配额;重复事件稳定重放,冲突内容被拒绝。
|
||||
- [A3] 付费状态不能绕过安全或人工审核门禁。
|
||||
- [A4] 客户 ROI、平台毛利、客户节省和工时价值在 API/UI 中不混算。
|
||||
- [A5] 暂停、恢复、取消、过期和历史查询可操作,终态不可原地复活。
|
||||
- [A6] 定价场景只使用真实同币种成本与确认节省;证据不足时不输出虚假价格。
|
||||
- [A7] 相关后端、PostgreSQL、前端、构建、Ruff 与迁移验证在容器内通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 真实计费仍需把 OCR、LLM、存储、连接器和支持运行事件自动接入用量/成本账本;手工管理员写入只能用于校验,不是最终生产采集。
|
||||
- 中央 Orchestrator 工具已经执行前预占;绕过中央执行器的其他生产入口仍须逐一迁移到 permit 契约,当前只会形成可见补偿积压,不会伪装成正常计量。
|
||||
- 合同制自动续期仍不推断新合同窗口;管理员或外部订阅连接器必须先提供显式续约事实,再创建后继合同/订阅版本。
|
||||
- 首个客户的实际套餐金额、席位、包含量、毛利目标、价值分享比例和折扣需要商业负责人确认。
|
||||
- 发票、税率、回款、坏账、渠道分成与收入确认尚未接入,当前 `customer_charges` 是合同/用量计费基准,不等同已收现金。
|
||||
- 价值分享合同必须定义基线、排除项、冲回、确认人、封顶和争议期。
|
||||
- 工时价值默认不进入现金 ROI,只有客户确认活跃工时基线、角色成本和可释放比例后才单独披露。
|
||||
|
||||
## 本轮实现记录
|
||||
|
||||
- 2026-07-16:完成五张商业事实表与 0016 迁移、套餐/订阅/权益、用量/成本、配额门禁、幂等和冲回服务。
|
||||
- 2026-07-16:完成客户收费、内部成本、贡献毛利、财务确认现金节省、客户 ROI 和工时价值分账分析;修正 ROI 为净收益口径并收紧角色、时区和权益历史不可变边界。
|
||||
- 2026-07-16:补齐订阅暂停/逾期/取消/过期、恢复和套餐/订阅/权益/用量/成本历史查询。
|
||||
- 2026-07-16:完成基于真实成本下限与确认价值上限的定价场景,禁止风险暴露、预计节省或跨币种混入报价。
|
||||
- 2026-07-16:完成商业工作台五块能力、订阅生命周期确认、用量/成本与价值分账、按币种定价场景两步确认;全量前端 802 项、code-size 和生产构建通过。
|
||||
- 2026-07-16:一次性 PostgreSQL 17 并发配额、幂等和成本冲回 3 项通过;真实开票、回款与客户合同参数继续保留为外部试点边界,不以 mock 标记完成。
|
||||
- 2026-07-16:新增 `0019` 运行时预占迁移和中央 Agent 工具 permit;在订阅/权益锁内按已用量加有效预占原子控额,成功按真实 AgentToolCall 结算,失败/阻断释放,变量用量超过预占拒绝写入。
|
||||
- 2026-07-16:新增持久补偿状态和过期补偿器;旧直接调用缺少预占时冻结容量并进入 reconciliation_required,运行终态不确定时不按 TTL 误释放;用量已提交但成本失败进入 committed_reconciliation_required,相同预占与用量/成本均可幂等重试。
|
||||
- 2026-07-16:新增 `0021` 不可变账期和商业管理审计;订阅创建即签发首期,月/季/年到期由 leader 调度器和订阅行锁幂等滚动,合同制与越界周期失败关闭。
|
||||
- 2026-07-16:用量、成本、运行时预占和配额查询改为绑定 `billing_period_id`,并以独立 `quota_period_key` 保留权益重置语义;客户收费基础费改从账期快照聚合,不再读取可变订阅当前周期。
|
||||
- 2026-07-16:容器内商业/迁移前置定向 131 项、前端商业 35 项和 Vite 构建通过;一次性 PostgreSQL 17 商业并发 5 项通过。0021 的升级、模型/约束/触发器(含账期重叠阻断)/运行不变量及 0021→0020 降级通过;全链 44/45 唯一失败来自后继 0023 尚未接管的盲审临时表,不属于 0021。
|
||||
- 2026-07-16:容器内商业/Agent/迁移组合 183 项通过、1 项因未显式配置外部迁移库跳过;运行时定向 27 项、PostgreSQL 商业并发 4 项和 0019→0020 完整迁移循环通过;全局 code-size 仅剩共享 `RiskRuleGenerationService` 817 行既有门禁失败,本轮所有相关核心类低于 800 行。
|
||||
@@ -0,0 +1,87 @@
|
||||
# 商业计量、客户 ROI 与可持续定价 开发 TODO
|
||||
|
||||
更新时间:2026-07-17
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 每项必须回链 `CONCEPT.md` 对应章节。
|
||||
- 只有代码、接口或容器验证提供证据后才能勾选。
|
||||
- 客户价值、平台收入、内部成本和工时估值必须分账;mock 与手工事件不得标记为生产事实。
|
||||
|
||||
## 1. 调研与边界
|
||||
|
||||
- [x] [CONCEPT: 背景与问题] 明确商业权益、用量、成本、客户 ROI、平台毛利和定价不是同一事实。
|
||||
证据:`CONCEPT.md`“背景与问题”“目标与非目标”。
|
||||
- [x] [CONCEPT: 目标与非目标] 确认不硬编码客户价格、不混算风险暴露、不跨币种求和、不让付费绕过安全门禁。
|
||||
证据:`CONCEPT.md`“目标与非目标”。
|
||||
|
||||
## 2. 契约与设计
|
||||
|
||||
- [x] [CONCEPT: 数据] 定义套餐、订阅、权益、用量和内部成本五类事实及状态。
|
||||
证据:`commercial.py` 模型与 schema、`20260716_0016_commercial_metering.py`。
|
||||
- [x] [CONCEPT: 算法与公式] 定义客户 ROI、贡献毛利和可持续定价走廊公式。
|
||||
证据:`CONCEPT.md`“算法与公式”、`commercial_analytics.py`、`commercial_pricing.py`。
|
||||
- [x] [CONCEPT: 权限] 定义平台配置、租户只读和商业/安全门禁分离。
|
||||
证据:`commercial_access_policy.py`、`commercial_entitlements.py`。
|
||||
|
||||
## 3. 后端实现
|
||||
|
||||
- [x] [CONCEPT: 数据] 新增五张商业表、复合租户约束、幂等、冲回和 append-only 迁移。
|
||||
证据:`models/commercial.py`、`20260716_0016_commercial_metering.py`、迁移/模型测试。
|
||||
- [x] [CONCEPT: 数据] 新增运行时预占运营表、状态约束、复合租户外键、全局 tool call 幂等和迁移所有权。
|
||||
证据:`models/commercial_runtime.py`、`20260716_0019_commercial_runtime_reservations.py`、`commercial_migration_assertions.py`;0019→0020 一次性 PostgreSQL 完整升降级循环通过。
|
||||
- [x] [CONCEPT: 后端] 实现套餐版本、订阅、权益、配额、用量、成本与商业分析服务。
|
||||
证据:`commercial_admin.py`、`commercial_entitlements.py`、`commercial_metering.py`、`commercial_analytics.py`。
|
||||
- [x] [CONCEPT: 生命周期与查询] 实现暂停、逾期、取消、过期、恢复与五类历史查询。
|
||||
证据:`CommercialAdminService.transition_subscription()`、`commercial_queries.py`、`/commercial/admin/tenants/{tenant_id}/...` 分资源接口。
|
||||
- [x] [CONCEPT: 定价走廊] 实现成本下限、确认价值上限、成功费封顶和商业模式建议。
|
||||
证据:`commercial_pricing.py`、`POST /commercial/admin/tenants/{tenant_id}/pricing-scenarios`。
|
||||
- [x] [CONCEPT: 后端] 把中央 Orchestrator 工具接入执行前预占、真实 AgentToolCall 结算和失败释放。
|
||||
证据:`orchestrator_tool_execution.py`、`agent_runs.py`、`commercial_runtime_bridge.py`;无配置兼容,配置后先 reserve 再执行,成功只追加真实用量/成本,失败和阻断不计量。
|
||||
- [x] [CONCEPT: 降级策略] 持久化缺预占和计量故障补偿状态,并安全处理过期预占。
|
||||
证据:`commercial_runtime_reservations.py`、`commercial_runtime_reconciler.py`;直接调用形成 reconciliation_required,运行中/未知终态继续持有额度,终态无调用才过期释放;用量成功但成本失败形成 committed_reconciliation_required,重试只补成本且不重复冻结额度。
|
||||
- [x] [CONCEPT: 风险与开放问题] 把已识别的权威运行入口迁移到 permit 契约。
|
||||
证据:中央 Orchestrator、`ocr_commercial.py`、`runtime_chat_commercial.py`、`financial_connector_commercial.py` 和 `expense_claim_attachment_commercial.py` 已接通预占/结算/释放;资源组合 63 项通过。
|
||||
- [ ] [CONCEPT: 风险与开放问题] 对后续新增的知识库/ONLYOFFICE 存储、实施和支持等资源入口持续执行 meter 盘点,不允许绕过 permit。
|
||||
证据要求:新增真实资源入口时提供权威数量口径、事务边界、成本来源和回归测试;当前不把未发生的未来入口伪装成已计量。
|
||||
- [x] [CONCEPT: 风险与开放问题] 通过不可变 billing period 和幂等 rollover 实现 `auto_renew` 周期滚动。
|
||||
证据:`20260716_0021_commercial_billing_periods.py`、`commercial_billing_periods.py`、`commercial_subscription_rollover.py`、`commercial_rollover_scheduler.py`;月/季/年自动滚动,合同制和 `ends_at` 越界失败关闭,PostgreSQL 双线程仅生成一个账期,数据库触发器拒绝重叠账期。
|
||||
- [x] [CONCEPT: 数据] 让用量、成本、运行时预占和配额历史绑定不可变账期,并分离配额重置键。
|
||||
证据:`UsageMeterEvent`、`CommercialCostEvent`、`CommercialRuntimeReservation` 的 `billing_period_id`,以及 usage/reservation 的 `quota_period_key`;收费分析从账期快照读取基础费和币种。
|
||||
- [x] [CONCEPT: 权限] 为套餐、订阅、权益和续期建立脱敏追加式审计与租户安全历史 API。
|
||||
证据:`commercial_admin_events`、`commercial_admin_audit.py`、`commercial_billing.py`;管理写接口强制 `X-Request-Id` 和原因,普通 finance/executive 只能读本租户账期,审计仅平台管理员可读。
|
||||
- [ ] [CONCEPT: 非目标] 对接真实开票/收款/订阅提供商并区分合同计费、已开票与已收现金。
|
||||
证据:等待目标客户和 provider 选择,不使用 mock 冒充完成。
|
||||
|
||||
## 4. 前端实现
|
||||
|
||||
- [x] [CONCEPT: 前端] 完成商业工作台的账户、套餐/订阅、权益/配额、用量/成本、价值/定价五块。
|
||||
证据:`CommercialWorkspace.vue` 组合账户、生命周期、权益、用量成本、价值分析与定价场景面板。
|
||||
- [x] [CONCEPT: 前端] 接入订阅暂停/取消/恢复、历史查询和操作确认。
|
||||
证据:`useCommercialWorkspace.js`、`CommercialSubscriptionLifecyclePanel.vue`;终态和定价均有确认步骤,操作后按租户重新加载。
|
||||
- [x] [CONCEPT: 前端] 严格分开展示客户 ROI 与平台毛利,并支持多币种和证据缺口状态。
|
||||
证据:`CommercialValueAnalysisPanel.vue`、`CommercialPricingScenarioPanel.vue`、`commercialWorkspaceModel.js`;按币种分组,null/unavailable 显示“不可用”,不跨币种合计。
|
||||
- [x] [CONCEPT: 前端] 接入现有应用入口、权限态、移动端和生产构建。
|
||||
证据:`OverviewView.vue`/顶部导航已接入“商业化管理”;商业定向 35 项、全量 web 802 项、code-size 与 Vite 2246 modules 构建通过。
|
||||
|
||||
## 5. 测试与验证
|
||||
|
||||
- [x] [CONCEPT: 测试方案] 后端模型、服务、HTTP、权限、生命周期、查询和定价回归通过。
|
||||
证据:容器内 Ruff 通过;`test_commercial_models.py`、`test_commercial_services.py`、`test_commercial_endpoints.py` 当前 12 项通过。
|
||||
- [x] [CONCEPT: 测试方案] 一次性 PostgreSQL 并发用量、原子预占、硬配额和成本冲回验证通过并记录当前命令结果。
|
||||
证据:一次性 PostgreSQL 17 中 `test_commercial_concurrency_postgres.py` 4 项通过;两个并发工具竞争 1 份额度时仅一个 reservation 成功。
|
||||
- [x] [CONCEPT: 测试方案] 运行时预占、结算、释放、幂等、变量上限、历史配置和补偿回归通过。
|
||||
证据:`test_commercial_runtime_metering.py`、`test_commercial_runtime_reservations.py` 27 项通过;商业/Agent/权限/迁移相关组合 183 项通过、1 项因未显式配置外部迁移库跳过,Ruff、compileall 和相关类 800 行检查通过。
|
||||
- [x] [CONCEPT: 测试方案] 前端行为测试、全量 web 测试、code-size 门禁和 Vite 构建通过。
|
||||
证据:商业定向 35 项、全量 web 802 项通过;code-size 通过;Vite production build 转换 2246 个模块。
|
||||
- [x] [CONCEPT: 测试方案] 不可变账期、脱敏审计、自动续期和调度器验证通过。
|
||||
证据:容器内商业/迁移前置定向 131 项、前端商业 35 项及 Vite build 通过;PostgreSQL 17 商业并发 5 项通过,含双线程续期单赢家;0021 升级、重叠账期阻断、运行不变量和 0021→0020 降级通过。
|
||||
- [x] [CONCEPT: 指标与验收] 逐项核对 A1-A7,并回填最终文件、接口和容器证据。
|
||||
证据:商业模型/服务/API/前端、硬配额、账期、生命周期、ROI/毛利分账和定价走廊均有回归;资源边界组合 63 项、PostgreSQL 商业并发 5 项、Web 全量 815 项及 Vite build 通过。
|
||||
|
||||
## 6. 商业与试点收尾
|
||||
|
||||
- [ ] [CONCEPT: 风险与开放问题] 用真实试点 30/90 天数据冻结目标毛利率、最大价值分享、包含量、超额策略和封顶。
|
||||
- [ ] [CONCEPT: 风险与开放问题] 确认发票、税率、回款、坏账、渠道和收入确认边界。
|
||||
- [x] [CONCEPT: 本轮实现记录] 同步更新上位闭环文档与工程验收手册,不删除证据不足项。
|
||||
证据:上位 AI 闭环 TODO 与 `engineering-closure-and-production-readiness` CONCEPT/TODO 已区分工程完成、生产上线和真实试点。
|
||||
@@ -0,0 +1,226 @@
|
||||
# 财务连接器与支付对账闭环 概念文档
|
||||
|
||||
更新时间:2026-07-17
|
||||
|
||||
## 功能一句话
|
||||
|
||||
把经过租户、来源、密钥版本和请求路径共同认证的 production-mode 财务事件契约接入付款、ERP、对账与 Savings 闭环,同时让所有非生产回执严格停留在只读模拟事实层;真实外部现金仍以目标 provider 联调为准。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
当前报销单可以由财务人员在平台内确认“已付款”,并能联动申请归档和 Savings 实现记录,但这只是内部业务状态,不是银行、支付平台或 ERP 的外部现金事实。若继续把单一状态当成真实回执,会留下重复付款、金额/币种错配、回执伪造、凭证缺失、对账异常未处置和虚假现金节省等风险。
|
||||
|
||||
本功能把外部财务系统接入收敛成统一、可审计的连接器契约。首期允许使用 mock adapter 验证协议和流程,但 mock 必须完整模拟签名、幂等、失败、重试、乱序、退款、凭证和对账差异,不以直接写“已付款”代替连接器事实。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- 建立租户隔离、来源可验证、追加式的支付/银行/ERP 事件账本。
|
||||
- 支持支付批次、结算成功、支付失败、退款/冲回、ERP 入账凭证和对账结果。
|
||||
- 只有单据、金额、币种、外部引用和签名均通过校验时,才推进报销付款状态。
|
||||
- 把外部事件与 Expense Case、Business Event、Savings Evidence、归档事件用同一 correlation 链关联。
|
||||
- 对重复、冲突、乱序和不完整事件 fail-closed,并提供可人工处理的对账异常。
|
||||
- 以版本化 activate/disable/rotate 状态机管理连接器密钥,所有配置动作形成不含密钥材料的追加式审计。
|
||||
|
||||
### 非目标
|
||||
|
||||
- 不自建银行清算、企业支付网络、税务开票网络或 ERP 总账。
|
||||
- 不保存银行卡号、银行流水原文、完整付款人账号或连接器密钥明文。
|
||||
- 不允许连接器绕过报销审批、风险门禁、租户权限或财务确认。
|
||||
- 不把 mock 回执标记为生产级外部现金证据;运行环境和证据等级必须显式区分。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 财务付款员:提交或查看付款批次,处理失败与待匹配回执。
|
||||
- 财务复核员:复核金额/币种/收款主体和对账异常,确认或拒绝处置。
|
||||
- 财务负责人/CFO:查看已匹配、待对账、失败、退款和未入账金额。
|
||||
- 平台管理员:配置连接器公钥/密钥版本、来源白名单和健康状态,但不能代替财务确认业务结果。
|
||||
- 外部连接器:按租户和来源签名推送支付、银行或 ERP 事件,安全重试并读取幂等结果。
|
||||
|
||||
## 功能能力
|
||||
|
||||
- 连接器注册与密钥版本:来源、环境、允许事件、时钟偏差、启停和轮换状态。
|
||||
- 统一事件信封:tenant、provider、event ID、event type、occurred at、payload hash、correlation、signature version。
|
||||
- 支付批次与回执:批次创建、提交、受理、成功、失败和部分成功。
|
||||
- ERP 入账:凭证号、会计期间、入账时间、受控摘要和原始内容哈希。
|
||||
- 对账:按单据、金额、币种和外部引用自动匹配;差异进入人工处置。
|
||||
- 冲回:退款、撤销和补付使用新事件,不更新或删除原事件。
|
||||
- 可观测性:最近成功时间、失败率、重试次数、积压、签名失败和对账差异。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 模块职责
|
||||
|
||||
- `financial_connector_auth`:验证来源、签名、时间戳、密钥版本和重放窗口。
|
||||
- `financial_connector_ingestion`:规范化事件、计算指纹、幂等写入、生产/模拟分流和冲突检测。
|
||||
- `financial_connector_simulation`:只读校验 test/mock/staging 回执,不创建或修改 Claim、对账、ERP、Business Event 或 Savings。
|
||||
- `financial_connector_mock_adapter`:平台管理员显式触发的非生产场景适配器;用已激活配置的真实签名链确定性生成成功、失败、乱序、重复、冲突、退款和 ERP 回执,但不提供任意 payload 注入能力。
|
||||
- `financial_connector_observability`:按租户和配置聚合最近成功、失败率、重试、积压、签名失败、冲突和对账异常;只读取最小化事实与追加式运行事件。
|
||||
- `financial_connector_payment_evidence`:从 Claim 的付款事实中提取统一证据 DTO,明确区分 production-mode 外部回执分类、非生产模拟回执和人工付款内部状态;分类本身不证明真实 provider 已接通。
|
||||
- `financial_connector_config_lifecycle`:执行带 expected version 的激活、停用和原子密钥轮换。
|
||||
- `payment_reconciliation`:匹配 Claim、金额、币种和状态,生成 matched / exception 结果。
|
||||
- `financial_connector_actions`:在可信匹配后调用现有付款动作;支付失败、退款和 ERP 入账分别旁写事件。
|
||||
- `financial_connector_projection`:为财务工作台提供脱敏列表、详情、差异和健康度。
|
||||
- 供应商 adapter 只负责供应商字段映射,不直接修改 Claim、Savings 或预算。
|
||||
|
||||
### 数据与契约
|
||||
|
||||
首期新增以下 migration-owned 表,均带 `tenant_id`:
|
||||
|
||||
- `financial_connector_configs`:provider、environment、allowed event types、secret/key version、status、last success/error;只保存密钥引用或不可逆验证材料。
|
||||
- `financial_connector_config_events`:created、activated、disabled、rotation started/replacement created 的追加式配置审计;保存 actor、request、reason、expected version 和脱敏前后状态,不保存 `secret_ref`。
|
||||
- `financial_connector_events`:方向、事件类型、外部事件 ID、请求指纹、原始内容哈希、发生/接收时间、验证等级、处理状态、关联单据/Case 和错误码;UPDATE/DELETE 禁止。
|
||||
- `payment_reconciliation_cases`:Claim、期望/实际金额与币种、匹配状态、差异、处置版本、负责人和最后事件;作为可变投影,历史动作另存事件。
|
||||
- `payment_reconciliation_events`:创建、自动匹配、人工确认、拒绝、重开、退款和关闭的追加式审计事件,保存请求指纹和首次响应。
|
||||
- `financial_connector_operational_events`(`0022`):保存 `replay / auth_failure / payload_conflict` 三类运行事实的 tenant/config/provider/environment、受控原因码、两类 HMAC 指纹、幂等键和发生时间;不保存原始 payload、签名、外部事件 ID、Claim 引用、correlation 或密钥材料。复合租户外键防止跨租户归属,PostgreSQL 触发器禁止 UPDATE/DELETE;存在运行事实时拒绝有损降级。
|
||||
|
||||
关键约束:
|
||||
|
||||
- `(tenant_id, provider, external_event_id)` 唯一。
|
||||
- 配置从 `disabled/version=1` 创建;激活和停用必须命中 expected version,轮换原子地产生新 active key version 并把旧版本置为 rotating。
|
||||
- 同幂等键不同 payload hash 返回冲突,不能覆盖首次事件。
|
||||
- 同一个运行事实 candidate 的补偿写入按 `(tenant_id, idempotency_key)` 幂等;不同 HTTP 尝试即使请求内容相同,也因可信发生时间不同而分别计数,避免把真实重放次数永久压成一次。
|
||||
- Claim、Case、配置和对账记录使用复合租户外键。
|
||||
- 退款/冲回必须引用同租户原结算事件。
|
||||
- 生产事件必须通过已激活密钥验证;test/mock/staging 即使签名和业务字段全部匹配,也只能生成 `projection_scope=simulation_only` 的连接器事实,绝不进入核心财务状态机。
|
||||
|
||||
### 接口
|
||||
|
||||
- `POST /api/v1/integrations/financial-events`:连接器签名事件入口;返回稳定接收/重放结果。
|
||||
- `POST /api/v1/financial-connectors/admin/tenants/{tenant}/configs/{id}/activate|disable|rotate`:带版本、actor、request ID 和 reason 的配置状态机。
|
||||
- `POST /api/v1/financial-connectors/admin/tenants/{tenant}/configs/{id}/simulate`:平台管理员运行确定性非生产场景;production 配置和未激活配置 fail-closed。
|
||||
- `GET /api/v1/financial-connectors/admin/tenants/{tenant}/config-events`:读取不含密钥材料的配置审计时间线。
|
||||
- `GET /api/v1/financial-connectors/admin/tenants/{tenant}/observability`:平台管理员读取目标租户脱敏运行指标;从 config/event/reconciliation/operational event 聚合指定窗口真实值。
|
||||
- `GET /api/v1/financial-connectors/observability`:财务角色读取当前租户脱敏运行指标;返回 `window_started_at / as_of / generated_at / source_revision`,以及 replay、认证失败、签名失败、payload conflict 的真实计数与最近发生时间。`0022` 起四类指标均标记 `available`,没有事实时真实返回零而不是“待采集”占位。
|
||||
- `GET /api/v1/financial-connectors/payment-evidence/{claim}`:有单据读取权限的当前租户用户读取付款证据等级,不返回原始连接器内容。
|
||||
- `GET /api/v1/financial-reconciliation/cases`:财务角色分页查看匹配与异常。
|
||||
- `GET /api/v1/financial-reconciliation/cases/{id}`:查看脱敏证据和追加式时间线。
|
||||
- `POST /api/v1/financial-reconciliation/cases/{id}/confirm`:独立财务确认差异或人工匹配。
|
||||
- `POST /api/v1/financial-reconciliation/cases/{id}/reject`:拒绝错误回执并记录原因。
|
||||
- 连接器配置管理接口仅平台管理员可用,业务确认接口仅财务角色可用,两类权限不互相继承。
|
||||
|
||||
### 匹配算法
|
||||
|
||||
1. 验证 tenant/provider/key version/timestamp/signature 和事件类型;HMAC canonical request 同时绑定固定 HTTP method/path,禁止共享密钥跨 provider 或 key version 重放。
|
||||
2. 以 canonical JSON 生成 payload hash;按外部事件 ID 与幂等键检查首次请求。
|
||||
3. 通过受控引用解析 Claim,不允许仅凭模糊姓名或备注自动匹配。
|
||||
4. 校验 Claim 已完成审批且处于待付款;比较金额、币种、外部业务引用和事件方向。
|
||||
5. 只有 `production_verified` 且完全一致时才自动 matched;非生产来源只生成模拟投影,任何生产差异进入 exception 且不改变 Claim。
|
||||
6. matched 事件在同事务调用付款动作、记录 Business Event,并把外部事件哈希作为 Savings 证据。
|
||||
7. ERP posted 只表示入账,不重复触发付款;refund/reversal 追加负向业务与 Savings 冲回候选。
|
||||
|
||||
### 权限与安全
|
||||
|
||||
- 连接器入口不使用普通用户会话,使用租户绑定的签名认证;普通 Bearer token 不能伪装连接器。
|
||||
- HMAC/签名比较使用常量时间函数,限制时间窗口并记录 nonce/外部事件 ID 防重放。
|
||||
- 激活和轮换前由服务端解析 `secret_ref` 并校验至少 128-bit HMAC 密钥;数据库和审计 DTO 均不返回引用或明文。
|
||||
- 日志、响应和 DTO 最多暴露外部引用后八位或不可逆摘要,不返回原始 payload、签名或密钥。
|
||||
- replay 随成功重放事务提交;认证失败与 payload 冲突先回滚失败业务事务,再独立提交脱敏运行事实,审计写入异常不得覆盖原始 401/409 响应。
|
||||
- 只有服务端按 tenant/provider/key version 解析出 active 配置,并成功解析该配置的服务端密钥后,才构造可归属的 operational context。缺认证头、请求 tenant 不一致、未知/未激活配置或密钥不可用均不接受客户端自报租户,只写不带租户归属的结构化警告;签名、时间窗和事件白名单失败才可安全归入已解析配置。
|
||||
- 运行表只保存 `hmac-sha256:` 请求/外部事件指纹和 `sha256:` 幂等键。HMAC 输入可以包含外部 ID 和业务引用,但这些原值不会进入表、DTO 或错误日志。
|
||||
- 不可变连接器事实的 normalized payload 不再保存完整 `claim_reference`;`0020` 受控迁移会删除历史冗余值,保留内容哈希、受控 Claim ID 与必要尾号。
|
||||
- 跨租户资源统一按 404 隐藏;配置管理员不能确认对账,付款申请人不能确认自己的异常。
|
||||
- 连接器故障、未知密钥、签名异常、金额/币种不一致和数据库异常均 fail-closed。
|
||||
|
||||
### 状态转换
|
||||
|
||||
- 连接器事件:`received → verified → processed`,失败进入 `rejected`;事实本身追加只读。
|
||||
- 连接器配置:`disabled → active → rotating → disabled`;正常启用走 `disabled → active`,轮换时旧 active 原子进入 rotating、新 key version 以 active 创建。
|
||||
- 对账记录:`pending → matched | exception → confirmed | rejected`;退款可从 confirmed 进入 `reopened`,重新处置后关闭。
|
||||
- Claim 仅在可信 `payment_settled + matched` 后从 `pending_payment` 进入 `paid`。
|
||||
- ERP 凭证从 `pending_posting` 进入 `posted | posting_failed`,不反向伪造支付成功。
|
||||
|
||||
### 降级策略
|
||||
|
||||
- 连接器离线:保留待付款,不自动标记已付;显示积压和最后成功时间。
|
||||
- 回执乱序:先保存事实,等待前置事件或进入 pending,不猜测状态。
|
||||
- 回执冲突:保留首次事实并返回 409,生成对账异常。
|
||||
- ERP 未接入:付款事实可进入已付,但“已入账/凭证号”保持待采集。
|
||||
- test/mock/staging:返回 `simulation_only`,只保留追加式 connector fact/response projection,不创建对账 Case、不修改 Claim/ERP/归档/Savings,也不进入生产现金证明。
|
||||
- 显式 mock adapter 只能选择预定义场景和当前租户 Claim;事件 ID、correlation 和受控外部引用由 tenant/config/scenario/request ID 确定性派生。重复执行同一请求只产生稳定重放,不能借模拟接口注入生产配置或任意字段。
|
||||
- 升级前若已有非生产事件且响应曾关联对账 Case,`0020` 将其标记为 `legacy_nonproduction_effect_unknown` 供审计,不伪装成新策略下的无副作用模拟事实,也不在迁移中猜测性冲回历史财务状态。
|
||||
|
||||
### 兼容策略
|
||||
|
||||
- 保留现有人工“确认已付款”作为低等级内部证据;付款证据 DTO 使用 `internal_manual_payment`,生产连接器使用 `external_cash`,非生产连接器使用 `simulated_connector`/`staging_connector`。UI 必须同时展示来源标签和可信等级,不能只显示“已付款”。
|
||||
- 新连接器路径复用现有幂等付款动作、Case 时间线和 Savings 实现服务,不复制第二套状态机。
|
||||
- 现有 `risk_flags_json` 付款摘要继续只读兼容,新连接器事实进入正式事件表。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 单元:签名、时间窗口、canonical hash、幂等重放、冲突、乱序和字段白名单。
|
||||
- 权限:跨租户、普通用户伪造、管理员越权、申请人自证和密钥停用。
|
||||
- PostgreSQL:复合外键、唯一键、append-only、并发同事件、不同 payload 冲突和安全降级。
|
||||
- 集成:申请 → 票据 → 报销 → 预审 → 审批 → 外部付款 → 对账 → ERP 入账 → 归档 → Savings 待确认。
|
||||
- 反向:支付失败不推进、金额/币种错配不推进、重复回执只写一次、退款追加冲回。
|
||||
- adapter:逐场景验证确定性结果、重复请求、跨租户 Claim 隐藏、production 拒绝,以及 Claim/对账/ERP/Business Event/Savings 零副作用。
|
||||
- 可观测性:验证租户隔离、财务/管理员权限、窗口边界、签名失败与重放计数,并断言 DTO/日志无原始 payload、签名和密钥。
|
||||
- 运行事件:验证同 candidate 并发/补偿重试单赢家、不同请求尝试分别计数、冲突回滚后独立持久化、复合租户外键、HMAC 格式检查和数据库 append-only。
|
||||
- 所有验证只在 `local-x-financial-linux` 容器内执行,每条命令最长 60 秒。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
本能力不做概率预测,核心是确定性门禁:
|
||||
|
||||
```text
|
||||
canonical_effect_allowed = (
|
||||
verification_level == production_verified
|
||||
AND signature_valid
|
||||
AND tenant_provider_key_path_bound
|
||||
AND claim_amount_currency_reference_match
|
||||
)
|
||||
```
|
||||
|
||||
任一条件为假都不得产生核心财务副作用;非生产环境无论其他条件是否为真,`canonical_effect_allowed` 固定为 false。
|
||||
|
||||
运行指标使用确定性窗口聚合,不从应用日志估算:
|
||||
|
||||
```text
|
||||
operational_count(type, window) = COUNT(
|
||||
tenant_id = current_tenant
|
||||
AND event_type = type
|
||||
AND window_started_at <= occurred_at <= as_of
|
||||
)
|
||||
|
||||
operational_idempotency_key = SHA256(
|
||||
source_revision, tenant, config, type, reason,
|
||||
HMAC(request), HMAC(external_event), occurred_at
|
||||
)
|
||||
```
|
||||
|
||||
`source_revision=20260716_0022` 表示当前运行指标的数据源与聚合契约版本;它不是 provider 协议版本。发生时间属于本次接收尝试,因此同一 candidate 重试仍稳定,而新尝试会形成新的真实计数。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 100% 外部结算事件具有租户、来源、签名版本、payload hash、外部 ID 和接收时间。
|
||||
- 重复相同事件稳定重放,冲突 payload 100% 拒绝。
|
||||
- 任何金额/币种/Claim/审批状态不一致均不会推进已付款。
|
||||
- 外部支付、ERP 凭证、对账处置、归档和 Savings 证据可由 correlation 链回放。
|
||||
- 连接器离线或异常时不出现虚假“已付款”“已入账”或现金节省。
|
||||
- observability 响应 100% 标明窗口起止和 source revision;三类运行事实按租户真实计数,并提供各类最近发生时间。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 首期真实 provider、签名算法、事件字段和 SLA 需要目标客户确认。
|
||||
- 部分 ERP 只有批次级凭证,需要明确批次到单据的拆分和舍入规则。
|
||||
- 多币种付款需要锁定汇率来源和会计期间;本功能不自行猜测汇率。
|
||||
- 退款、补付、员工自担调整是否进入现金节省,仍需客户财务签字口径。
|
||||
- 对账大额阈值及双人复核需按企业策略配置。
|
||||
|
||||
## 本轮实现记录
|
||||
|
||||
- 2026-07-16:完成现有内部付款、申请归档、Business Event、Savings 实现链路盘点,并冻结统一连接器、安全认证、对账状态和完整 E2E 方案;实现与容器证据保留在同目录 TODO 中继续执行。
|
||||
- 2026-07-16:完成 `0017` 四表迁移、HMAC/密钥版本/时间窗认证、租户隔离、追加式事件、幂等冲突和对账投影;一次性 PostgreSQL 17 迁移循环与 2 项并发探针通过。
|
||||
- 2026-07-16:支付结算只在金额、币种、Claim、审批状态与来源全部匹配时复用正式付款动作;ERP、失败、退款/冲回、Savings 失效和财务处置均进入同一 correlation 审计链,连接器服务/HTTP 7 项通过。
|
||||
- 2026-07-16:真实 provider、批次拆分、汇率、会计期间和大额双人复核仍由目标客户确认;当前 mock/test 证据始终标记 simulated,不冒充生产现金事实。
|
||||
- 2026-07-16:补齐 `0020` 配置版本与追加式审计迁移;新配置只能停用创建,激活前校验服务端密钥,轮换原子切换 key version,HTTP 时间线不暴露 `secret_ref`。
|
||||
- 2026-07-16:把 test/mock/staging 六类事件收口到 `simulation_only` 只读投影;生产事件继续完成付款、ERP、归档、Savings 和冲回,生产冲回也不能引用模拟原事件。
|
||||
- 2026-07-16:normalized payload 删除完整 `claim_reference`,历史冗余值由 `0020` 受控脱敏,HMAC v2 继续以内容哈希和 tenant/provider/key version/path 证明请求边界。
|
||||
- 2026-07-16:容器内连接器/配置/费用价值链组合 16 项、PostgreSQL 并发 3 项、迁移静态 124 项及全新 PostgreSQL 17 完整升降级循环通过;独立 0019→0020 探针确认版本回填、历史脱敏和 legacy 不确定性标记符合契约。
|
||||
- 2026-07-16:新增显式非生产 adapter,以 tenant/config/claim/scenario/request ID 派生稳定签名回执,覆盖成功、失败、乱序、重复、冲突、退款和 ERP 场景;HTTP 与服务测试确认 simulation-only 且 Claim、对账、Business Event、ERP 与 Savings 零副作用。
|
||||
- 2026-07-16:新增租户级脱敏可观测性 API 与财务看板面板,现有 config/event/reconciliation 表提供最后成功、失败率、积压和对账异常真实值;retry/auth_failure 在 `0022` 运行事件落地前明确显示 unavailable,不伪造零值。
|
||||
- 2026-07-16:新增付款证据等级 DTO 和 UI 口径,通过 production-mode 签名契约的回执分类为 `external_cash`,人工确认标为低等级 `internal_manual_payment`,模拟/预发布回执明确不进入核心账;当前本地自签测试不作为真实现金或 provider 接通证据。
|
||||
- 2026-07-17:完成 `0022` 追加式运行事实迁移与服务接入,耐久记录 replay、可信归属后的 auth failure 和 payload conflict;请求与外部事件只保存 HMAC 指纹,失败事务回滚后独立补偿提交。
|
||||
- 2026-07-17:可观测性改为返回 `20260716_0022` source revision、明确窗口、真实计数和最近时间;同一 candidate 补偿重试幂等,不同 HTTP 尝试分别计数。迁移总头继续串到既有 `0023`,`0022` 不越界创建后继 AI 表。
|
||||
- 2026-07-17:全新一次性 PostgreSQL 17 完整迁移循环 51 项、连接器并发 4 项、后继盲审并发 7 项通过;验证租户复合外键、HMAC 格式、运行事实 append-only 和并发单赢家。
|
||||
@@ -0,0 +1,76 @@
|
||||
# 财务连接器与支付对账闭环 TODO
|
||||
|
||||
更新时间:2026-07-17
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 每项必须回链 `CONCEPT.md`;只有代码、迁移、接口或容器验证提供证据后才能勾选。
|
||||
- 外部回执、内部付款状态、ERP 入账和财务确认必须分开,mock 不得伪装成生产现金事实。
|
||||
|
||||
## 1. 契约与安全
|
||||
|
||||
- [x] [CONCEPT: 背景与问题] 盘点内部付款、申请归档、Business Event、Savings 实现和证据边界。
|
||||
证据:`expense_claim_approval_flow.py`、`expense_claim_application_handoff.py`、`expense_cases.py`、`savings_realization.py` 只读审计。
|
||||
- [x] [CONCEPT: 目标与非目标] 冻结签名事件、幂等、对账、ERP 凭证、冲回和 mock 环境边界。
|
||||
证据:`CONCEPT.md`“目标与非目标”“数据与契约”“匹配算法”“降级策略”。
|
||||
- [x] [CONCEPT: 权限与安全] 实现连接器签名认证、密钥版本、时间窗口、来源白名单和防重放。
|
||||
证据:`financial_connector_auth.py`、`financial_connector_ingestion.py`;HMAC 使用常量时间比较,tenant/provider/key version/timestamp/method/path 均进入签名边界,共享密钥跨 provider/key version 重放被拒绝,相同事件幂等重放、冲突 payload 返回 409。
|
||||
- [x] [CONCEPT: 权限与安全] 实现平台配置权限与财务处置权限分离、跨租户 404 和申请人自证拒绝。
|
||||
证据:`financial_connectors.py`、`financial_connector_projection.py`;HTTP/服务回归覆盖普通用户、平台管理员、财务角色、跨租户隐藏与申请人自证拒绝。
|
||||
|
||||
## 2. 数据与迁移
|
||||
|
||||
- [x] [CONCEPT: 数据与契约] 新增配置、外部事件、对账投影和对账事件模型。
|
||||
证据:`models/financial_connector.py`、`schemas/financial_connector.py`。
|
||||
- [x] [CONCEPT: 数据与契约] 新增后继 Alembic 迁移、迁移所有权、复合租户外键、唯一/检查约束和 append-only 触发器。
|
||||
证据:`20260716_0017_financial_connector_reconciliation.py`、`schema_ownership.py`、`migration_preflight.py`;一次性 PostgreSQL 17 完整迁移循环通过。
|
||||
- [x] [CONCEPT: 数据与契约] 实现外部事件与退款/冲回引用、首次响应和 payload 指纹冲突。
|
||||
证据:`financial_connector_ingestion.py`、`payment_reconciliation.py`;同外部事件不同 payload 拒绝,退款/冲回必须绑定同租户已处理结算原事件。
|
||||
- [x] [CONCEPT: 数据与契约] 新增配置 version、追加式配置审计和历史 normalized payload 脱敏迁移。
|
||||
证据:`20260716_0020_financial_connector_config_lifecycle.py`、`FinancialConnectorConfigEvent`;配置审计使用 PostgreSQL append-only trigger,历史 `claim_reference` 从不可变事件的 normalized payload 中受控移除,内容哈希继续保留;全新 PostgreSQL 17 完整升降级循环 1 项通过,另一个独立库验证 0019→0020 历史数据脱敏与 legacy 标识。
|
||||
|
||||
## 3. 服务与接口
|
||||
|
||||
- [x] [CONCEPT: 模块职责] 拆分认证、ingestion、reconciliation、action 和 projection 服务,核心文件不超过 800 行。
|
||||
证据:`financial_connector_auth.py`、`financial_connector_ingestion.py`、`payment_reconciliation.py`、`financial_connector_actions.py`、`financial_connector_projection.py` 职责独立,最大核心文件低于 800 行。
|
||||
- [x] [CONCEPT: 接口] 实现统一事件入口、连接器配置、对账列表/详情、确认和拒绝接口。
|
||||
证据:`api/v1/endpoints/financial_connectors.py`。
|
||||
- [x] [CONCEPT: 接口] 实现带 expected version、actor、request ID、reason 的 activate/disable/rotate 状态机与脱敏审计查询。
|
||||
证据:`financial_connector_config_lifecycle.py`、`financial_connector_config_audit.py`、`FinancialConnectorConfigLifecycleAction`、`FinancialConnectorConfigRotateAction`;激活/轮换前解析服务端密钥并校验强度,轮换原子切换新旧 key version。
|
||||
- [x] [CONCEPT: 匹配算法] 完全匹配时复用现有幂等付款动作;任何金额、币种、单据或审批状态差异不产生付款副作用。
|
||||
证据:`FinancialConnectorActionService` 复用 `ExpenseClaimService.mark_claim_paid_from_connector()`;反向测试验证 mismatch/failure/conflict 无付款副作用。
|
||||
- [x] [CONCEPT: 证据与审计] 把外部事件哈希写入 Expense Case/Business Event/Savings 证据 correlation 链。
|
||||
证据:连接器结算动作写入脱敏内容哈希、verification/evidence classification 与 correlation;服务 E2E 可回放付款、Case、Business Event 和 Savings evidence。
|
||||
- [x] [CONCEPT: 状态转换] 实现支付失败、ERP posted/posting_failed、退款/冲回和对账重开。
|
||||
证据:`PaymentReconciliationService` 对六类生产事件分流;ERP 不重复付款,生产退款/冲回恢复 Claim 并追加 Savings 冲回事实。
|
||||
- [x] [CONCEPT: 降级策略] test/mock/staging 六类事件只写 simulation-only connector fact/response projection,不修改核心财务状态。
|
||||
证据:`financial_connector_simulation.py`、`financial_connector_ingestion.py`;`test_financial_connector_services.py` 参数化覆盖三类非生产环境和六类事件,Claim、申请归档、对账、Business Event、ERP 与 Savings 均保持不变。
|
||||
|
||||
## 4. Mock 与可观测性
|
||||
|
||||
- [x] [CONCEPT: 降级策略] 实现明确标识 test/mock 的 adapter,覆盖成功、失败、乱序、重复、冲突、退款和 ERP 回执。
|
||||
证据:`financial_connector_mock_adapter.py`、`FinancialConnectorSimulationCreate/Read` 与平台管理员 simulate API;仅 active test/mock/staging 可运行,tenant/config/claim/scenario/request ID 确定性派生事件,production、disabled 和跨租户请求 fail-closed;7 场景服务/HTTP 回归通过。
|
||||
- [x] [CONCEPT: 可观测性] 从现有事实输出最后成功时间、失败率、积压和对账异常,并在安全 DTO/UI 声明未采集指标。
|
||||
证据:`financial_connector_observability.py`、当前租户/管理员 observability API、`FinancialConnectorHealthPanel.vue`;所有查询先绑定 tenant,仅聚合 config/event/reconciliation 最小化事实,不返回原始 payload、签名和密钥。
|
||||
- [x] [CONCEPT: 可观测性] 用后继 `0022` 追加式运行事件补齐 replay、auth_failure 和 payload conflict 耐久计数。
|
||||
证据:`20260716_0022_financial_connector_operational_events.py`、`financial_connector_operational_events.py`、`financial_connector_auth.py`、`financial_connector_ingestion.py`、`financial_connector_observability.py`;只有可信配置与服务端密钥解析后才归属认证失败,表内只保存 HMAC 指纹;同 candidate 重试幂等、不同接收尝试分别计数,API 返回窗口、source revision、真实数量和最近时间。
|
||||
- [x] [CONCEPT: 兼容策略] 保留人工付款为低等级内部证据,并在 DTO/UI 区分外部回执和内部确认。
|
||||
证据:`financial_connector_payment_evidence.py`、payment-evidence API、`FinancialPaymentEvidenceRead` 与面板证据口径;人工付款=`internal_manual_payment`,通过 production-mode 契约验证的外部回执分类=`external_cash`,非生产回执=`simulated_connector|staging_connector`。真实 provider 仍待联调。
|
||||
|
||||
## 5. 测试与验收
|
||||
|
||||
- [x] [CONCEPT: 测试方案] 签名、防重放、幂等、冲突、字段白名单和错误恢复单元测试通过。
|
||||
证据:`test_financial_connector_services.py`、`test_financial_connector_endpoints.py`、`test_financial_connector_config_lifecycle.py` 与费用价值链 E2E 共 16 项通过;乱序原事件缺失保守进入 exception,不推进付款。
|
||||
- [x] [CONCEPT: 测试方案] PostgreSQL 迁移、复合租户约束、append-only、并发和安全降级验证通过。
|
||||
证据:fresh PostgreSQL 17 最终迁移总探针 62 项通过;`financial_connector_migration_assertions.py` 验证配置/事件/运行事实 trigger、租户约束、HMAC 格式和 version check;`test_financial_connector_concurrency_postgres.py` 4 项通过,覆盖同事件、冲突补偿事实、配置单版本胜者和 operational candidate 单赢家;最终 head 为 `20260717_0028`。
|
||||
- [x] [CONCEPT: 测试方案] 申请 → 票据 → 报销 → 预审 → 审批 → 外部付款 → 对账 → ERP 入账 → 归档端到端通过。
|
||||
证据:`test_expense_financial_value_chain_e2e.py` 使用测试密钥自签 production-mode HMAC 事件,覆盖申请审批、报销审批、ERP 入账、独立财务确认、Savings、商业价值与退款冲回契约;与连接器定向组合共 16 项通过,不代表真实 provider 回执或现金。
|
||||
- [x] [CONCEPT: 测试方案] 支付失败、金额/币种错配、重复回执和退款反向链路通过。
|
||||
证据:`test_financial_connector_services.py` 覆盖 production-mode 失败/错配无副作用、稳定重放、ERP、reversal/refund 追加 Savings 冲回,以及非生产回执完全不创建 Savings。
|
||||
- [x] [CONCEPT: 容器验证] 相关 pytest、Ruff、前端测试和构建均在 `local-x-financial-linux` 内通过。
|
||||
证据:历史连接器/配置/费用价值链/迁移组合 `140 passed, 1 skipped`;adapter/观测/证据 DTO 与既有连接器回归 `21 passed, 3 skipped`。2026-07-17 的 `0022` 收尾在容器内新增/定向回归 `115 passed`,全新 PostgreSQL 17 完整迁移循环 `51 passed`,连接器并发 `4 passed`,后继 `0023` 并发 `7 passed`;相关 Ruff、文件行数和全树 `git diff --check` 通过。历史连接器前端 3 项与 Vite 生产构建已通过;共享前端曾有 5 项旧路径测试失败,已单独记录且不属于本切片。
|
||||
|
||||
## 6. 客户配置待确认
|
||||
|
||||
- [ ] [CONCEPT: 风险与开放问题] 确认首个 provider、签名算法、字段映射、事件 SLA 和重试窗口。
|
||||
- [ ] [CONCEPT: 风险与开放问题] 确认批次到单据映射、多币种汇率、会计期间、大额双人复核和退款口径。
|
||||
@@ -0,0 +1,373 @@
|
||||
# 节省事实账本与 CFO 经营价值看板 概念文档
|
||||
|
||||
更新时间:2026-07-17
|
||||
|
||||
## 功能一句话
|
||||
|
||||
把费用优化从“发现风险和预计能省”推进为“执行、实际结果、独立财务确认、可回放冲回”的事实账本,并让 CFO 只看到有来源、有基线、有证据、可去重的企业价值。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
- 现有财务看板能够回答支出、单量、待付款、预算使用和风险分布,但不能可信回答企业已经节省了多少钱。
|
||||
- 风险观察金额、暂缓付款、未使用预算和未执行建议都不是现金节省;如果直接汇总,会形成虚假 ROI。
|
||||
- 当前最接近真实节省的链路是“住宿超标准 → 用户接受职级标准重算 → 审批 → 付款”。它已经真实降低报销金额,但尚未形成独立机会、付款后结果、财务签字、去重和冲回记录。
|
||||
- `ExpenseClaim`、预算和应付旧表没有结构化租户键,Claim 只能通过 `ExpenseCaseLink` 判断租户;旧 `FinanceDashboardService` 仍有全表读取和跨租户快照复用风险,不能作为客户 ROI 的事实源。
|
||||
- 现有 `accept_standard_adjustment` 优先使用客户端传入的原金额,客户端理论上可以放大差额;任何节省计算必须改为只使用服务端锁定的明细金额和政策计算快照。
|
||||
- 当前“已付款”是财务人员在系统内确认的业务状态,尚没有银行流水、支付回执或 ERP 凭证。因此付款只能把机会推进到“实际结果待确认”,不能直接进入财务确认 KPI。
|
||||
- 报销提交时间到同租户首个 `payment_completed` 业务事件可以形成可审计的端到端流程周期,但它包含等待与系统处理,不是人工活跃工时;在没有人工计时基线、角色成本和客户认可释放比例前,工时价值必须显示“待采集”,不能伪造为 0 或现金节省。
|
||||
|
||||
本方案是 `2026-07-13/feature/ai-expense-closed-loop-and-value-proof` 中 P2“费用经营与价值证明”的实施拆分。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- [G1] 建立租户安全的 Savings Ledger,完整区分风险暴露、预计机会、执行中、实际结果、财务确认和冲回。
|
||||
- [G2] 建立不可变基线和证据链,所有金额都能追溯到费用事件、单据明细、政策版本、执行动作、付款事件和确认人。
|
||||
- [G3] 建立经济收益去重键和归因约束,避免同一单据、付款义务或政策差额被多个风险重复计入。
|
||||
- [G4] 建立严格状态机、乐观版本、幂等响应和数据库并发约束,防止重复确认、陈旧操作和跨租户访问。
|
||||
- [G5] 建立 CFO 价值看板,分开展示财务确认现金节省、可释放工时价值、安全智能直通率、经营漏斗和风险护栏。
|
||||
- [G6] 支持部门、项目、费用类型、供应商、城市、时间、负责人、来源和单据下钻,并显示数据覆盖、口径和新鲜度。
|
||||
- [G7] 持久化费用基线快照,记录窗口、样本量、算法版本、政策版本、来源指纹和数据质量。
|
||||
- [G8] 修复旧财务聚合与快照的租户边界,禁止真实接口失败时回退成看似真实的演示数字。
|
||||
|
||||
### 非目标
|
||||
|
||||
- [NG1] 不把风险关联金额、暂缓付款金额、未采纳建议、未使用预算或预计金额计入已确认节省。
|
||||
- [NG2] 首个切片不宣称已具备外部银行或 ERP 付款凭证;后续通过连接器补齐。
|
||||
- [NG3] 首个切片不使用缺少租户、合同价、采购数量和付款凭证的 `AccountsPayableRecord` 计算供应商节省。
|
||||
- [NG4] 不把申请金额与最终报销差额默认归因给 AI;缺少具体 AI 决策、采纳动作和结果链时,AI 归因金额为 0。
|
||||
- [NG5] 不把流程经过时长换算为人工工时,不直接暴露个人薪酬或个人成本。
|
||||
- [NG6] 不跨币种直接求和;没有锁定汇率的金额只按原币展示并进入数据质量提醒。
|
||||
- [NG7] 不删除、覆盖已确认收益;补付、退款、申诉或归因修正使用追加负向冲回事件。
|
||||
- [NG8] 不在本阶段重写整个 Overview,也不把不可信的预算中心模拟数据接入价值看板。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
### 目标用户
|
||||
|
||||
1. CFO/管理层:查看企业已经确认的现金价值、价值兑现速度和风险护栏。
|
||||
2. 财务运营:复核机会、实际结果、重复归因、凭证和冲回事项。
|
||||
3. 费用治理负责人:领取机会、执行动作、补充结果和跟进逾期。
|
||||
4. 预算负责人:只在授权部门或成本中心范围内查看机会与驱动。
|
||||
5. 审计/风控:回放基线、政策、执行、付款、确认、冲回和操作事件。
|
||||
6. 普通员工:仅在自己的费用事件中看到与本人相关的调整说明,不访问企业 CFO 汇总。
|
||||
|
||||
### 核心场景
|
||||
|
||||
1. 员工接受住宿职级标准重算。服务端锁定明细原金额、城市、天数、职级、政策版本和可报销上限,同事务生成唯一节省机会。
|
||||
2. 机会进入执行后仍只展示预计金额;审批未通过、单据取消或超过期限时保留失败/到期事实,不从兑现率分母中消失。
|
||||
3. 单据完成付款业务事件后,系统根据冻结差额记录实际结果,但不进入财务确认 KPI。
|
||||
4. 与机会负责人和结果填报人不同的财务人员检查证据、去重、币种和成本后确认;确认后才计入 CFO 现金节省。
|
||||
5. 后续发生例外补付或申诉时,追加负向冲回并保留原确认,历史月报按报告 `as_of` 可回放。
|
||||
6. CFO 从价值总览下钻到部门、项目、费用类型、城市、负责人和具体单据,查看基线、建议、执行、实际、确认人和证据。
|
||||
7. 费用治理负责人查看异常集中维度和只读政策模拟准备项;历史中位数只能作为异常信号,缺少正式政策反事实时不显示预计节省,也不自动创建机会。
|
||||
|
||||
### 异常场景
|
||||
|
||||
- 服务端政策无法计算、明细金额缺失或差额不为正:原报销流程可继续,但不创建可货币化节省机会。
|
||||
- Claim 没有合法 `ExpenseCaseLink` 或租户不一致:fail-closed,不自动归入默认租户。
|
||||
- 相同请求重复发送:返回首次不可变响应;相同请求 ID 内容不同:409 拒绝。
|
||||
- 陈旧版本、重复付款事件或重复经济收益:通过版本锁、事件唯一键和收益去重键拒绝。
|
||||
- 缺少付款/凭证、汇率、独立确认或证据不完整:停留在实际待确认,不进入主 KPI。
|
||||
- 看板接口失败、无权限、无数据、基线不足或快照过期:分别展示明确状态,绝不使用模拟数字伪装真实指标。
|
||||
|
||||
## 功能能力
|
||||
|
||||
- [C1] 机会发现:从服务端核验的政策调整、后续分析洞察或风险复核创建机会。
|
||||
- [C2] 状态管理:支持 identified、accepted、in_progress、realized、verified、rejected、expired 和 reversed 事实。
|
||||
- [C3] 实现记录:保存实际毛收益、新增执行成本、净收益、发生时间、币种和结果证据。
|
||||
- [C4] 财务确认:独立确认人复核去重、证据、汇率、成本和归因后签字。
|
||||
- [C5] 证据与审计:只追加事件、内容指纹、before/after、首次响应和 correlation 全链路回放。
|
||||
- [C6] 基线快照:按员工、部门、费用类型、供应商、城市、项目和流程持久化窗口、样本量和版本。
|
||||
- [C7] 价值分析:经营漏斗、兑现率、周期、逾期、来源、责任人和数据质量。
|
||||
- [C8] CFO 看板:真实指标、全局筛选、URL 恢复、下钻、移动端和口径抽屉。
|
||||
- [C9] 安全边界:租户、角色、数据范围、自证禁止、管理员业务权限分离和字段白名单。
|
||||
- [C10] 冲回能力:补付、退款、申诉或归因修正只能追加负向记录,不改历史。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 前端
|
||||
|
||||
- 在现有“分析看板”增加 `value` / “经营价值看板”,复用统一时间筛选,不新增一级导航。
|
||||
- `OverviewView.vue` 只负责挂载独立 `CfoValueDashboard.vue`;价值加载、筛选和展示模型拆到 `useCfoValueDashboard.js`、`cfoValueDashboardModel.js` 与 `analyticsValue.js`,避免继续扩大接近 800 行的 `useOverviewView.js`。
|
||||
- 默认视图从上到下为:主 KPI 与护栏、价值漏斗、现金节省趋势、来源/组织驱动、机会执行表、数据质量和口径说明。
|
||||
- 全局筛选只保留时间、部门、费用类型和价值类型;项目、供应商、城市、负责人、状态和置信度进入高级筛选。
|
||||
- 看板状态同步到 URL query;当前机会使用 `value_opportunity` 保存,刷新、浏览器前进/后退和分享链接能够恢复同一抽屉。非法 ID、403/404、跨租户不可见或不再符合当前筛选/时间窗口的机会会 fail-closed 清理,避免残留上一租户详情。
|
||||
- 机会详情展示基线、建议、执行、实际结果、财务确认、去重与证据时间线;证据只使用服务端可见性 DTO。来源动作由独立 helper 根据 Claim、Expense Case、AI Decision、维度和 Evidence Resource 构造,不在抽屉组件内拼接路由规则。
|
||||
- 单据来源进入 `app-document-detail`;风险来源优先进入关联单据,并只携带风险 focus、观察/决策 ID 与现有锚点。详情返回动作恢复 `dashboard=value`、时间窗口和 `value_*` 查询。
|
||||
- 预算来源进入 `app-budget` 的“预算配置视图”,按授权范围应用部门和费用类型焦点;页面明确说明配置、阈值及当前演示金额不是该机会的真实预算事实。未配置的费用科目显示“未找到配置”,不得解释为预算为零。
|
||||
- 部门、项目、费用类型、供应商、城市、负责人和来源维度可返回 CFO 看板相应筛选;切换维度时移除旧机会 ID,避免筛选与抽屉详情不一致。
|
||||
- 实际结果登记必须具备真实付款事件或可追溯外部凭证;外部凭证上传/连接器尚未接入时,前端隐藏无证据手工登记并解释下一步,不发送必然失败或可能污染价值账本的空证据请求。
|
||||
- 真实为 0、无数据、基线不足、无权限、接口失败、部分数据和快照过期使用不同状态。
|
||||
- 禁止复用 `data/metrics.js`、`BudgetCenterView` 静态种子或遗留 `demoTotals` 作为 CFO 真实回退。
|
||||
|
||||
### 后端
|
||||
|
||||
- `SavingsDiscoveryService` 只负责从可信业务事实发现/创建机会,不提交事务。
|
||||
- `SavingsActionService` 负责机会状态动作、版本、权限、幂等和事件。
|
||||
- `SavingsRealizationService` 负责付款后实际结果、财务确认、拒绝和冲回。
|
||||
- `SavingsQueryService` 负责租户安全分页、详情和可见动作投影。
|
||||
- `SavingsFactScopeReader` 只读取当前租户与授权部门中的已归档报销事实,并以报告窗口和 `as_of` 排除未来单据、修改和完成事件。
|
||||
- `SavingsBaselineGenerationService` 分开冻结金额中位数与流程历时中位数;流程只使用提交时间和首个付款完成业务事件,指标固定为 elapsed minutes。
|
||||
- `SavingsAnomalyAttributionAnalyzer` 只生成描述性异常集中归因和政策模拟准备项,不声称因果,不写 `SavingsOpportunity`。
|
||||
- `CfoValueAnalyticsService` 只从 Savings Ledger、风险事实和明确资格快照聚合,不从 UI mock 或风险金额推导节省。
|
||||
- 标准重算只使用数据库行锁中的 `ExpenseClaimItem.item_amount` 作为原金额;客户端原金额和可报销金额只可作为展示输入,不能成为节省事实。
|
||||
- 标准重算在同一事务内写 Claim 调整、机会、证据、Savings 事件和 `saving_opportunity_created` 业务事件;API 边界统一提交。
|
||||
- 付款动作在 Claim → Opportunity 的固定锁顺序中创建 actual realization 和 `saving_action_completed`,与 `payment_completed` 同事务。
|
||||
- 财务确认写 `saving_confirmed`,拒绝和冲回写对应只追加事件;相同请求安全重放。
|
||||
- 旧 `/analytics/finance-dashboard` 必须接收可信 `CurrentUserContext`,Claim 通过 `ExpenseCaseLink` 限定租户;快照键至少包含租户与数据权限范围,后台任务必须显式指定租户。
|
||||
|
||||
### 算法与规则
|
||||
|
||||
#### 第一条可信机会
|
||||
|
||||
```text
|
||||
server_original_amount = locked ExpenseClaimItem.item_amount
|
||||
policy_target_amount = server policy calculator result
|
||||
estimated_net_saving = max(0, server_original_amount - policy_target_amount)
|
||||
```
|
||||
|
||||
- 仅当政策计算成功、输入快照完整、币种一致、差额大于 0 时创建可货币化机会。
|
||||
- 机会唯一键首期为 `tenant + claim + item + policy_version + policy_input_fingerprint`。
|
||||
- 接受重算表示建议已采纳,机会进入 `in_progress`;付款完成后进入 `realized`,独立财务确认后进入 `verified`。
|
||||
- 员工自行承担差额同时是员工体验护栏,必须跟踪申诉和例外补付率,防止通过不合理转嫁美化节省。
|
||||
|
||||
#### 流程基线、异常归因与政策模拟
|
||||
|
||||
```text
|
||||
workflow_elapsed_minutes
|
||||
= first_tenant_payment_completed_event.occurred_at - claim.submitted_at
|
||||
```
|
||||
|
||||
- 流程窗口按首个付款完成事件归属;提交时间缺失、完成早于提交、跨租户事件、`as_of` 之后完成或截止后被修改的单据全部排除。
|
||||
- 流程快照使用 `median_submission_to_payment_elapsed_minutes`、`minutes` 单位、独立算法版本和来源指纹;证据元数据固定声明 `elapsed_cycle_not_active_labor`。
|
||||
- 异常归因按部门、费用类型、城市和项目聚合质量合格的历史偏离候选,只表示异常集中度,不表示该维度导致支出。
|
||||
- 政策模拟候选只输出版本化政策、生效期、适用范围、限额和例外规则等必需输入;历史中位数不是政策反事实,缺少正式反事实时 `estimated_savings=None`。
|
||||
- 预算预测复用现有预算分配和核销事实,以 `min(as_of, window_end)` 为截止点;旧预算表没有租户字段时仅允许 default 租户,部门权限优先按稳定部门 ID 收紧。
|
||||
- 供应商缺少核验 ID、数量和单位价格时继续返回 unavailable,不读取 `AccountsPayableRecord` 演示或应付种子。
|
||||
|
||||
#### 收益去重
|
||||
|
||||
- `benefit_key` 表达同一个经济结果,不表达同一个风险观察。
|
||||
- 多条风险可指向一个机会;同一发票、付款义务、报销明细或价格变化只能有一个 canonical 确认收益。
|
||||
- 同一收益多个动作的归因比例之和不得超过 1。
|
||||
- 确认后大额、超预计、手工基线、缺外部凭证和归因异常进入二次复核或数据质量队列。
|
||||
|
||||
#### 状态转换
|
||||
|
||||
```text
|
||||
identified -> accepted -> in_progress -> realized -> verified -> reversed
|
||||
| | | |
|
||||
+-------- rejected -------+------------+
|
||||
+-------- expired --------+
|
||||
```
|
||||
|
||||
- `identified`:冻结基线、方法、价值类型、币种、预计净值、负责人、截止时间、去重键和来源证据。
|
||||
- `accepted`:负责人明确采纳。
|
||||
- `in_progress`:保存执行动作、执行人、开始时间和动作证据;预计值不得静默上调。
|
||||
- `realized`:保存实际结果、净值、发生时间、归因和付款/结果证据,但不计主 KPI。
|
||||
- `verified`:完成去重、币种、成本、证据和独立财务确认。
|
||||
- `reversed`:追加负向冲回,原确认不可删除。
|
||||
- `rejected/expired`:保留失败事实,防止只保留成功机会美化兑现率。
|
||||
|
||||
### 数据与契约
|
||||
|
||||
#### `profile_baseline_snapshots`
|
||||
|
||||
- 租户、基线类型、稳定维度 ID、指标、单位和原币。
|
||||
- 基线值、窗口开始/结束、样本量、方法、查询指纹和数据质量。
|
||||
- 算法版本、政策版本、冻结时间/人和有效期。
|
||||
- 历史群组基线强制窗口与样本量;政策反事实基线强制政策版本、生效区间和目标明细。
|
||||
- 金额基线按员工、部门、费用类型、城市和项目分组;流程基线按稳定流程键分组,使用独立 metric/unit,不能与币种金额比较或求和。
|
||||
|
||||
#### `savings_opportunities`
|
||||
|
||||
- 租户、费用事件、Claim 软引用、来源类型/ID、类别和价值类型。
|
||||
- 风险暴露只作护栏;基线、目标、预计毛收益、预计成本、预计净收益和区间分开保存。
|
||||
- 原币、报告币、负责人、截止时间、状态、版本、`benefit_key` 和去重组。
|
||||
- 部门、项目、费用类型、供应商、城市和流程维度使用明确快照字段或受控 JSON。
|
||||
- 唯一约束至少覆盖 `(tenant_id, opportunity_key)`。
|
||||
|
||||
#### `savings_realizations`
|
||||
|
||||
- 租户、机会、费用事件、Claim、BusinessEvent 和实际发生时间。
|
||||
- 实际毛收益、新增执行成本、实际净收益、原币、报告金额和汇率快照。
|
||||
- 归因方法/比例、`benefit_key`、去重状态和 canonical realization。
|
||||
- 财务确认/拒绝/冲回人、时间、说明和证据。
|
||||
- 只追加金额事实;确认投影可更新,但每次变更必须有不可变事件。
|
||||
|
||||
#### `savings_evidence_links` 与 `savings_events`
|
||||
|
||||
- 证据保存实体、证据角色、资源类型/ID、来源系统、外部事件 ID、内容哈希、发生/采集时间和验证状态。
|
||||
- 事件保存动作、请求 ID、操作人、版本、指纹、before/after、首次响应、correlation 和时间。
|
||||
- PostgreSQL 触发器禁止修改或删除 `savings_events`。
|
||||
|
||||
### 权限
|
||||
|
||||
- `finance`、`executive` 可读取租户 CFO 汇总;预算负责人仅看被授权部门/成本中心。
|
||||
- 普通员工、普通经理不得读取 CFO 汇总;只能看到本人费用事件中的最小调整说明。
|
||||
- 机会接受、拒绝和指派需要 finance/executive 或明确负责人权限。
|
||||
- 财务确认必须是 finance/executive,且不能是机会负责人或实际结果填报人。
|
||||
- 只有 `admin` 而没有财务角色时允许运维只读,不允许业务确认。
|
||||
- 所有 API、聚合、快照、导出和后台任务强制 `tenant_id` 与数据范围;不允许默认全表扫描。
|
||||
- Claim 通过 `ExpenseCaseLink` 校验租户;缺 Link 的非默认历史数据不自动猜测归属。
|
||||
|
||||
### 降级策略
|
||||
|
||||
- 政策或基线服务失败:不创建货币化机会,原报销主流程保留人工处理。
|
||||
- 外部付款/ERP 连接器未接入:付款业务事件只能推进到 realized,必须人工财务确认。
|
||||
- 汇率缺失:保留原币明细,不进入跨币种总计。
|
||||
- 工时基线缺失:显示“待采集”,不显示 0,不计扩展 ROI。
|
||||
- 流程历时可用但活跃工时缺失:只展示 elapsed cycle 驱动指标,CFO 工时价值仍保持 collecting。
|
||||
- CFO 聚合失败:显示错误和重试,不加载演示值;旧财务支出看板独立可用。
|
||||
- 快照过期:展示过期提示并触发受控刷新,不能跨租户复用旧快照。
|
||||
|
||||
## 算法与公式
|
||||
|
||||
### 主 KPI 1:财务确认净现金节省
|
||||
|
||||
```text
|
||||
verified_net_cash_savings
|
||||
= sum(actual_gross_saving - incremental_execution_cost + reversal_amount)
|
||||
where value_kind = cash
|
||||
and confirmation_status = finance_confirmed
|
||||
and dedupe_status = canonical
|
||||
and confirmed_at <= report_as_of
|
||||
```
|
||||
|
||||
- 按 `realized_at` 归属业务期间,按 `confirmed_at` 和报告 `as_of` 保证历史可回放。
|
||||
- 风险暴露、预计金额、执行中金额和未确认实际金额不得进入。
|
||||
- 多币种只有存在锁定汇率时才折算;否则按原币分组。
|
||||
|
||||
### 主 KPI 2:财务确认可释放工时价值
|
||||
|
||||
```text
|
||||
verified_releasable_labor_value
|
||||
= max(0, baseline_active_minutes_per_unit - actual_active_minutes_per_unit)
|
||||
* eligible_units
|
||||
* approved_role_cost_per_minute
|
||||
* approved_releasable_ratio
|
||||
```
|
||||
|
||||
- 现金与工时分账、分卡、分报告,默认不相加。
|
||||
- 缺少上线前后活跃分钟、角色完全成本、生效期或客户认可释放比例时不可计算。
|
||||
|
||||
### 主 KPI 3:安全智能直通率
|
||||
|
||||
```text
|
||||
safe_straight_through_rate
|
||||
= qualified_completed_cases_without_manual_correction_or_return
|
||||
and no_major_post_audit_issue
|
||||
/ eligible_completed_cases_frozen_at_creation
|
||||
```
|
||||
|
||||
- 必须保存 eligibility 快照、策略版本、必要审批完成和事后抽检结果。
|
||||
|
||||
### 驱动指标
|
||||
|
||||
- 现金兑现率:同一成熟机会队列的财务确认净现金 / 冻结预计净现金。
|
||||
- 机会到财务确认 P50 天数、逾期负责人占比。
|
||||
- 提交至首个付款完成的端到端 P50 elapsed minutes;它与每单人工活跃分钟分开,后者在采集前保持不可用。
|
||||
- 人工触点、首次提交完整率和 AI 字段采纳率。
|
||||
|
||||
### 风险护栏
|
||||
|
||||
- 开放且已确认的高危/重大风险暴露,按单据或经济义务去重;它不是节省。
|
||||
- 重大风险漏检率、事后审计重大问题率、误报率和人工覆盖率。
|
||||
- 财务确认后冲回率、实际超过预计异常率、去重待复核金额和证据不完整金额。
|
||||
- 标准重算员工申诉/例外补付率。
|
||||
|
||||
## 测试方案
|
||||
|
||||
### 后端
|
||||
|
||||
- 状态机合法/非法转换、确认人独立性、管理员只读和角色权限。
|
||||
- 机会/实现/确认/冲回幂等、请求内容冲突、陈旧版本和租户隔离。
|
||||
- 服务端明细金额锁定、客户端放大原金额无效、政策快照完整性。
|
||||
- 付款事件重复、经济收益去重、跨币种、成本扣除、冲回和归因上限。
|
||||
- 看板按租户、时间、部门、项目、费用类型、城市、来源和负责人聚合对账。
|
||||
- 六维基线验证员工、部门、费用类型、城市、项目和流程的窗口、样本量、算法版本、来源指纹、租户/部门范围与稳定重放。
|
||||
- 洞察验证预算截止点、描述性归因、政策模拟准备项、供应商 unavailable、所有无反事实候选 `estimated_savings=None` 且不会创建机会。
|
||||
- 旧财务看板 Claim、预算和缓存键租户隔离回归。
|
||||
- Alembic 空库升级、重复升级、约束、append-only 触发器、无损降级和 PostgreSQL 并发。
|
||||
|
||||
### 前端
|
||||
|
||||
- API snake/camel 归一化、部分数据和过期快照。
|
||||
- verified、realized、estimated 和 risk exposure 严格分区,不得混算。
|
||||
- loading/error/empty/partial/stale/permission-denied/baseline-missing 状态。
|
||||
- 时间、部门、费用类型、价值类型筛选与 URL 恢复。
|
||||
- 机会详情证据链、可用动作、版本冲突、幂等重放和确认反馈。
|
||||
- 单据、风险、预算和维度下钻参数。
|
||||
- 机会 `value_opportunity` 的恢复、关闭清理、非法格式、403/404、跨筛选和时间窗口清理。
|
||||
- 风险来源最小定位参数、单据返回 CFO、预算配置焦点和“非真实预算金额”口径。
|
||||
- 响应式、键盘操作、44px 触控目标和生产构建。
|
||||
|
||||
### 集成
|
||||
|
||||
- 住宿超标准 → 服务端重算 → 机会 → 审批 → 付款 → actual realization → 独立财务确认 → CFO 看板。
|
||||
- 相同重算/付款/确认并发只产生一个经济收益和一条对应版本事件。
|
||||
- 确认后补付/申诉 → 负向冲回 → 历史报告 `as_of` 可回放。
|
||||
- 多租户同单号、同员工名、相同 request ID 和快照缓存隔离。
|
||||
|
||||
### 容器验证
|
||||
|
||||
所有 pytest、Alembic、PostgreSQL 并发、前端测试和构建必须在 `local-x-financial-linux` 容器内完成,单条命令超时不超过 60 秒。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- [A1] 任一 verified cash saving 可追溯到费用事件、明细原金额、政策快照、执行动作、付款事件、确认人、去重键和证据。
|
||||
- [A2] 风险暴露、预计、执行中、实际待确认、财务确认和冲回在 API、数据库和 UI 中均不混算。
|
||||
- [A3] 客户端伪造原金额、跨租户访问、陈旧版本、自证确认和重复经济收益被服务端拒绝。
|
||||
- [A4] CFO 三个主 KPI 有口径、时间窗口、来源、新鲜度、数据覆盖和护栏;缺数据时明确“待采集”。
|
||||
- [A4.1] 流程 elapsed cycle 有独立 metric/unit/算法版本/来源指纹,且不会进入工时价值或现金节省。
|
||||
- [A5] 价值看板支持部门、项目、费用类型、供应商、城市、时间、负责人、来源和单据下钻。
|
||||
- [A6] 真实接口失败不出现演示数字;零值、无数据、无权限、错误和过期可区分。
|
||||
- [A7] 迁移在一次性 PostgreSQL 空库完成升级、重复升级、约束验证和安全降级边界测试。
|
||||
- [A8] 相关后端、前端、Ruff、构建、端到端和并发测试全部在容器内通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
### 风险
|
||||
|
||||
- 员工承担差额不一定等同企业创造价值;需要跟踪申诉、补付和政策公平性,避免激励扭曲。
|
||||
- 当前付款是人工状态,不是外部现金事实;确认页必须清晰披露证据等级。
|
||||
- 旧 Claim/预算缺租户列,读取必须经过 Case Link 或正式迁移,不能依赖默认租户猜测。
|
||||
- 数据稀疏且包含模拟种子,试点目标值必须在真实基线采集后冻结。
|
||||
- 当前预算中心仍是配置演示视图;CFO 来源入口只用于定位部门/费用类型配置,页面和价值计算均不得把其中金额当成预算事实或节省事实。
|
||||
- 多币种、分摊收益和跨期冲回会显著增加财务口径复杂度,必须保留原始事实和版本。
|
||||
- 工时价值若没有活跃时间采集与客户认可成本率,容易被夸大,因此默认不计现金 ROI。
|
||||
- 历史异常集中不是因果,历史中位数也不是政策反事实;模拟候选必须在正式政策版本和客户确认适用口径补齐后才可货币化。
|
||||
|
||||
### 已处理决策
|
||||
|
||||
- 首条闭环选择“住宿职级标准重算”,不选择缺少付款事实的重复支付阻止。
|
||||
- 价值看板进入现有分析看板,独立拆组件和 composable。
|
||||
- 只设 3 个主 KPI,其余作为驱动和护栏;现金与工时分开披露。
|
||||
- Savings Ledger 直接带结构化租户键,不复用旧财务快照作为价值事实源。
|
||||
- 流程基线采用提交至首个付款完成的端到端 elapsed cycle;人工活跃工时继续作为独立待采集事实。
|
||||
- 异常归因仅做描述性集中度,政策模拟候选保持只读,不自动写入节省机会。
|
||||
|
||||
### 待后续真实客户确认
|
||||
|
||||
- 独立财务确认是否要求双人复核及大额阈值。
|
||||
- 报告币、汇率来源和月末汇率锁定规则。
|
||||
- 工时价值是否进入扩展 ROI、角色成本口径和可释放比例。
|
||||
- 标准重算差额在客户会计政策中属于现金节省、成本避免还是员工自担调整。
|
||||
- 试点 30/90 天目标、CFO 月报签字人和节省分成合同边界。
|
||||
|
||||
## 本轮实现记录
|
||||
|
||||
- 2026-07-16:完成现有财务、预算、风险、付款、标准重算、前端入口与 KPI 口径盘点;确认旧财务聚合租户边界和客户端原金额信任问题。
|
||||
- 2026-07-16:冻结 Savings Ledger、CFO KPI、状态机、权限、去重、证据和首条纵向闭环方案;尚未完成的代码与验证全部保留在同目录 TODO 中继续执行。
|
||||
- 2026-07-16:完成 Savings Ledger 五表与 0015 迁移、租户/权限/幂等/并发/证据/冲回契约,并把住宿标准重算和付款动作接入同事务价值链。
|
||||
- 2026-07-16:完成独立财务确认、证据复核留痕、canonical 收益去重、负向冲回、`as_of` 回放和历史标准调整安全回填。
|
||||
- 2026-07-16:完成 CFO 价值分析 API;只将已确认 canonical 现金结果计入主 KPI,多币种分组,工时、安全直通率和审计事实缺口显式披露,不使用演示数据回退。
|
||||
- 2026-07-16:完成 CFO 经营价值前端入口、URL 筛选恢复、主 KPI、漏斗、趋势、驱动维度、护栏、数据质量、机会证据链与响应式状态;详情明确展示财务确认人、时间和说明。
|
||||
- 2026-07-16:收紧无证据手工登记边界。真实付款事件仍可自动形成待确认实际结果;在支付/银行/ERP 凭证连接器接入前,页面不再提交空证据,并向用户解释必须先完成付款或等待外部回执。
|
||||
- 2026-07-16:完成租户隔离的员工、部门、费用类型、城市和项目五维历史中位数冻结基线,以及预算预测偏差、重复小额模式和历史中位数偏离候选;非政策反事实信号只披露暴露,不生成节省金额,并补齐 `as_of` 基线时间一致性门禁。
|
||||
- 2026-07-16:补齐第六维流程周期基线,以同租户首个付款完成业务事件冻结提交到完成的 elapsed minutes;窗口、样本量、算法版本、来源指纹、质量状态和证据完整保存,明确禁止推算人工活跃工时。
|
||||
- 2026-07-16:新增部门/费用类型/城市/项目异常集中归因和版本化政策模拟准备项;复用预算预测并收紧配置/交易截止点,所有缺少反事实的候选保持 `estimated_savings=None`、零机会副作用,供应商分析继续明确 unavailable。
|
||||
- 2026-07-16:完成机会抽屉 `value_opportunity` 深链、刷新/前进/后退恢复和非法/越权/跨筛选安全清理;来源动作可跳关联单据、风险证据位置、预算配置视图及 CFO 维度筛选,单据返回时恢复经营价值查询状态。
|
||||
- 2026-07-16:预算来源只应用授权部门与费用类型配置焦点;未覆盖科目显示“未找到配置”,所有入口和页面均明确演示预算金额不是当前机会的真实预算事实。
|
||||
@@ -0,0 +1,141 @@
|
||||
# 节省事实账本与 CFO 经营价值看板 开发 TODO
|
||||
|
||||
更新时间:2026-07-17
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 每条 TODO 必须回链 `CONCEPT.md` 的章节或语义段落。
|
||||
- 只有代码、接口或容器验证提供真实证据后才能勾选 `[x]`。
|
||||
- 现金节省、工时价值、风险暴露和预计机会始终分开,不以演示数据代替缺失事实。
|
||||
- 实施顺序为:安全前置 → 账本 → 首条闭环 → 基线/分析 → CFO 前端 → PostgreSQL/E2E → 文档收口。
|
||||
|
||||
## 1. 调研与边界
|
||||
|
||||
- [x] [CONCEPT: 背景与问题] 盘点财务看板、预算、Claim、Expense Case、Business Event、风险、审批、付款和标准重算事实源。
|
||||
证据:`finance_dashboard.py`、`finance_dashboard_snapshot.py`、`financial_record.py`、`expense_cases.py`、`expense_claim_standard_adjustment.py`、`expense_claim_approval_flow.py` 的只读审计。
|
||||
- [x] [CONCEPT: 目标与非目标] 确认风险金额、未用预算、预计机会和未确认结果不得进入已确认节省。
|
||||
证据:CONCEPT「目标与非目标」「算法与公式」。
|
||||
- [x] [CONCEPT: 用户与场景] 选择住宿职级标准重算作为首条纵向闭环,不选择证据不足的重复支付或供应商议价。
|
||||
证据:现有标准重算和付款业务事件可形成最短可信证据链;`AccountsPayableRecord` 已有租户字段,但仍缺合同价、数量、单位价格和外部付款事实。
|
||||
- [x] [CONCEPT: 风险与开放问题] 识别旧财务聚合全表读取、快照键缺租户、接口缺角色限制和标准重算信任客户端原金额问题。
|
||||
证据:`FinanceDashboardMetricMixin._fetch_claims()`、`_fetch_budget_allocations()`、`FinanceDashboardSnapshotService._cache_key()`、`accept_standard_adjustment()`。
|
||||
|
||||
## 2. 契约与设计
|
||||
|
||||
- [x] [CONCEPT: 数据与契约] 定义 baseline、opportunity、realization、evidence 和 append-only event 的职责与关键字段。
|
||||
证据:CONCEPT「数据与契约」。
|
||||
- [x] [CONCEPT: 算法与规则] 定义 identified → accepted → in_progress → realized → verified → reversed 状态和 rejected/expired 终态。
|
||||
证据:CONCEPT「状态转换」。
|
||||
- [x] [CONCEPT: 算法与规则] 定义 benefit key、canonical realization、归因比例和追加冲回规则。
|
||||
证据:CONCEPT「收益去重」。
|
||||
- [x] [CONCEPT: 权限] 定义租户、finance/executive、预算范围、管理员只读、自证禁止和数据范围。
|
||||
证据:CONCEPT「权限」。
|
||||
- [x] [CONCEPT: 算法与公式] 定义 3 个主 KPI、驱动指标、风险护栏和缺数据边界。
|
||||
证据:CONCEPT「算法与公式」。
|
||||
|
||||
## 3. 安全前置
|
||||
|
||||
- [x] [CONCEPT: 后端] 让旧财务看板显式接收可信租户与数据范围,Claim/预算查询不得全表混算。
|
||||
证据:`finance_dashboard_access_policy.py`、`finance_dashboard_scope.py`、`finance_dashboard_budget.py`、`finance_dashboard.py`;Claim 直接按结构化 `ExpenseClaim.tenant_id` 首层过滤,预算历史兼容仅限 default 租户。
|
||||
- [x] [CONCEPT: 后端] 给财务快照缓存键和后台任务加入租户/数据范围,禁止跨租户复用。
|
||||
证据:`finance_dashboard_snapshot.py`、`finance_dashboard_scheduler.py`;快照键包含 tenant 与 scope fingerprint,调度入口必须显式携带租户。
|
||||
- [x] [CONCEPT: 权限] 给财务与 CFO 分析接口增加后端角色和范围校验,管理员身份不自动获得业务确认权。
|
||||
证据:`finance_dashboard_access_policy.py`、`savings_access_policy.py`、`agent_run_access_policy.py`、`GET /api/v1/analytics/cfo-value`;容器租户/缓存/角色回归 17 项通过。
|
||||
- [x] [CONCEPT: 第一条可信机会] 标准重算只使用锁定数据库明细原金额,客户端原金额不能影响结果和节省。
|
||||
证据:`expense_claim_standard_adjustment.py`;原金额只读锁定 `ExpenseClaimItem.item_amount`,政策结果只由服务端重算,陈旧版本使用内容指纹并降低基线质量等级。
|
||||
- [x] [CONCEPT: 后端] 将标准重算、审计和账本写入收口到同一事务边界。
|
||||
证据:`expense_claim_standard_adjustment.py`以 `commit=False` 写审计,同一 API 事务写 Claim、Baseline、Opportunity、Evidence、SavingsEvent 和 BusinessEvent;事务失败整体回滚。
|
||||
|
||||
## 4. Savings Ledger 后端
|
||||
|
||||
- [x] [CONCEPT: 数据与契约] 新增 `profile_baseline_snapshots`、`savings_opportunities`、`savings_realizations`、`savings_evidence_links` 和 `savings_events` 模型。
|
||||
证据:`savings.py`(模型)及 `test_savings_models.py`;五类事实分离保存基线、机会、结果、证据和不可变操作。
|
||||
- [x] [CONCEPT: 数据与契约] 新增 Alembic 0015、迁移所有权、复合租户外键、唯一键、检查约束、索引和 append-only 触发器。
|
||||
证据:`20260716_0015_savings_value_ledger.py`、`migration_preflight.py`、`schema_ownership.py`;一次性 PostgreSQL 17 空库完整迁移循环通过。
|
||||
- [x] [CONCEPT: 后端] 实现 discovery、action、realization、query、access policy 和 response builder 独立服务。
|
||||
证据:`savings_discovery.py`、`savings_actions.py`、`savings_realization.py`、`savings_query.py`、`savings_access_policy.py`、`savings_read_projection.py`、`savings_protocol.py`。
|
||||
- [x] [CONCEPT: 后端] 实现分页、筛选、排序、详情、可用动作、证据和事件 DTO。
|
||||
证据:`GET /api/v1/savings/opportunities`、`GET /api/v1/savings/opportunities/{id}`、`savings.py`(schema/API)、`test_savings_endpoints.py`。
|
||||
- [x] [CONCEPT: 算法与规则] 实现版本锁、请求指纹、不可变响应重放、收益去重和归因比例约束。
|
||||
证据:`SavingsRequestProtocol`、数据库唯一/检查约束与 `test_savings_concurrency_postgres.py`;同 request 并发仅一次写入,同 benefit 双确认仅一个 canonical winner。
|
||||
- [x] [CONCEPT: 状态转换] 实现 accept/start/record/verify/reject/expire/reverse 合法与非法转换。
|
||||
证据:`savings_actions.py`、`savings_realization.py`、`test_savings_ledger_services.py`;负向 reversal 只追加,不改写原已确认金额。
|
||||
- [x] [CONCEPT: 证据与审计] 把 Savings 关键动作写入同 Case 的 `BusinessEvent` 并保留 correlation/causation。
|
||||
证据:`ExpenseCaseService` 资源链接与 `savings_*` 服务;端到端测试核验 opportunity/payment/action/confirm/reverse 事件同 Case 可回放。
|
||||
- [x] [CONCEPT: 后端] 新增历史标准重算机会 dry-run/apply 回填脚本;缺 Case/租户/政策证据只输出数据质量报告。
|
||||
证据:`savings_standard_adjustment_backfill.py`、`backfill_standard_adjustment_savings.py`;显式租户/目标库、指纹重算、批次锁、稳定键重放,容器直接测试 8 项通过。
|
||||
|
||||
## 5. 首条真实纵向闭环
|
||||
|
||||
- [x] [CONCEPT: 第一条可信机会] 接受住宿标准重算时冻结服务端原金额、政策输入/版本、目标金额、差额、维度和证据。
|
||||
证据:`SavingsDiscoveryService`冻结 `ProfileBaselineSnapshot` 与内容指纹,政式发布版本为 complete,内容指纹版本显式标记 partial。
|
||||
- [x] [CONCEPT: 第一条可信机会] 同事务以稳定 opportunity key 创建或重放机会,并进入 in_progress。
|
||||
证据:稳定键由 tenant + claim + item + policy version + calculation fingerprint 生成;重试返回原机会。
|
||||
- [x] [CONCEPT: 后端] 付款业务事件后同事务创建 actual realization,重复付款事件不重复计入。
|
||||
证据:`expense_claim_approval_flow.py`调用 `realize_paid_claim()`;PostgreSQL 同付款事件并发结果为 `[0, 1]`,仅一条 realization 和一条完成事件。
|
||||
- [x] [CONCEPT: 财务确认] 实现独立财务 verify/reject,未确认实际结果不得进入主 KPI。
|
||||
证据:所有人工实际结果必须至少一条可追溯证据;独立确认人会固化证据复核人/时间,pending 结果在 CFO 口径中为 0。
|
||||
- [x] [CONCEPT: 冲回能力] 实现补付/申诉/归因修正的负向 reversal,原确认不可删除。
|
||||
证据:原 actual 保留 `finance_confirmed`,新增负向 canonical reversal;`as_of` 可回放冲回前结果。
|
||||
- [x] [CONCEPT: 权限] 验证申请人、机会负责人、结果填报人、纯管理员和跨租户用户不能自证或越权。
|
||||
证据:`SavingsAccessPolicy`与 PostgreSQL 并发安全测试;owner/recorder/admin-only 自证拒绝,独立 finance 可确认,跨租户无副作用。
|
||||
|
||||
## 6. 费用基线与经营分析
|
||||
|
||||
- [x] [CONCEPT: 基线快照] 按员工、部门、费用类型、城市、项目和流程从租户安全真实数据生成基线窗口、样本量和版本。
|
||||
证据:`savings_fact_scope.py`、`savings_baseline_generation.py`、`savings_insights.py`;金额五维使用已归档明细中位数,流程维度只使用同租户报销提交时间与首个 `payment_completed` 业务事件,冻结 elapsed minutes、窗口、样本量、算法版本、查询指纹、质量、证据和审计事件;`source_workflow_cycle_count` 明确披露覆盖,活跃工时保持 unavailable。
|
||||
- [x] [CONCEPT: 基线快照] 供应商数据缺少租户/合同事实时显示 coverage gap,不从应付模拟种子生成可信基线。
|
||||
证据:基线和洞察 API 均返回 `supplier_dimension_unavailable` / `supplier_price_drift_unavailable`,要求核验供应商、数量和单位价格;不会读取 `AccountsPayableRecord` 模拟事实或创建货币化机会。
|
||||
- [x] [CONCEPT: 算法与规则] 实现预算预测、描述性异常归因、重复小额浪费、历史偏离和只读政策模拟准备项;证据不足时不自动货币化。
|
||||
证据:`savings_insight_budget.py` 复用预算配置/核销事实并以 `min(as_of, window_end)` 截止;`savings_insight_analysis.py`、`savings_insight_attribution.py` 输出历史偏离、部门/费用类型/城市/项目异常集中和版本化政策模拟必需输入。所有缺少正式反事实的候选 `estimated_savings=None`、`created_opportunity_ids=[]`,稳定重放不写 `SavingsOpportunity`。
|
||||
- [x] [CONCEPT: 后端] 实现 `GET /analytics/cfo-value`,统一时间、维度、币种、状态和 `as_of` 口径。
|
||||
证据:`cfo_value.py`(API/schema)、`cfo_value_analytics.py`;租户、角色、预算范围、时间和维度过滤均在服务端执行。
|
||||
- [x] [CONCEPT: 算法与公式] 实现确认现金、工时待采集、安全直通率资格、漏斗、兑现周期、逾期、来源和护栏聚合。
|
||||
证据:确认现金只求和 finance_confirmed + canonical + cash;工时和安全直通率显式返回 collecting;漏斗、趋势、驱动、逾期、冲回和数据质量不与主 KPI 混算。
|
||||
- [x] [CONCEPT: 降级策略] 多币种、工时、外部付款和审计结果缺失时返回明确数据质量状态,不伪造 0。
|
||||
证据:多币种按原币分组;无人工活跃时间/审计事实时返回 collecting/unavailable 和 coverage gap,不使用 mock 回退。
|
||||
|
||||
## 7. CFO 前端
|
||||
|
||||
- [x] [CONCEPT: 前端] 在分析看板新增 `value` 入口,并把 dashboard 状态同步 URL。
|
||||
证据:`useTopBarOverviewRange.js`、`AppShellRouteView.vue`、`OverviewView.vue`;`dashboard=value` 与价值筛选/页码写入 query,刷新和返回可恢复。
|
||||
- [x] [CONCEPT: 前端] 新增独立 CFO 组件、composable、API service 和展示模型,不继续扩大 `useOverviewView.js`。
|
||||
证据:`CfoValueDashboard.vue`、`CfoValueTrendChart.vue`、`CfoValueOpportunityDrawer.vue`、`CfoValueActionDialog.vue`、`useCfoValueDashboard.js`、`analyticsValue.js`、`cfoValueDashboardModel.js`;业务组件和状态职责已拆分,核心文件均低于 800 行。
|
||||
- [x] [CONCEPT: CFO 看板] 实现主 KPI、护栏、价值漏斗、趋势、来源/组织驱动、机会表和数据质量。
|
||||
证据:`CfoValueDashboard.vue` 与 `cfo-value-dashboard.css`;现金、工时和直通率分卡,趋势按币种切换,预计/实际/确认/冲回不混算,缺数据显式展示。
|
||||
- [x] [CONCEPT: 前端] 实现时间、部门、费用类型、价值类型筛选和项目/供应商/城市/负责人高级筛选。
|
||||
证据:顶部时间窗口与价值 query 联动,`createEmptyValueFilters`、`readValueFiltersFromQuery`、`writeValueFiltersToQuery` 和 API query 白名单覆盖全部筛选字段。
|
||||
- [x] [CONCEPT: 前端] 实现基线、建议、执行、实际、确认、去重和证据详情。
|
||||
证据:`CfoValueOpportunityDrawer.vue` 展示冻结基线、机会状态、实际净值、记录人、canonical 去重、财务确认人/时间/说明、证据索引和不可变事件;无可追溯凭证时不开放手工实际结果登记。
|
||||
- [x] [CONCEPT: 前端] 实现单据、风险、预算、维度下钻与返回状态恢复。
|
||||
证据:`cfoValueSourceLinks.js` 统一构造来源路由;Claim 进入 `app-document-detail`,风险携带最小 focus/观察/决策参数和现有锚点,预算进入 `app-budget` 配置视图并应用部门/费用类型焦点,维度返回 `app-overview?dashboard=value` 相应筛选。`useCfoValueDashboard.js` 以 `value_opportunity` 恢复抽屉,并在非法 ID、403/404、跨租户不可见或不符合当前筛选/时间窗口时安全清除;`useAppShell.js` 从单据详情恢复 CFO 查询。限制:预算中心仍是演示配置视图,页面明确金额不是当前机会的真实预算事实;未覆盖费用科目显示未配置而不是零预算。
|
||||
- [x] [CONCEPT: 降级策略] 区分零、无数据、基线不足、无权限、失败、部分数据和快照过期;删除 CFO 演示回退。
|
||||
证据:`classifyCfoDashboardState`、`buildValueKpis` 与页面状态区;接口失败不读取 `data/metrics.js` 或 demo/fallback 数字。
|
||||
- [x] [CONCEPT: 前端] 完成移动端、键盘、焦点、44px 触控和无障碍状态提示。
|
||||
证据:CFO 样式移动断点、44px 按钮、语义化 `label`/`role=alert`/`aria-live`、抽屉关闭标签及趋势表格降级;生产构建通过。
|
||||
|
||||
## 8. 测试与验证
|
||||
|
||||
- [x] [CONCEPT: 测试方案] 后端状态、金额、权限、租户、幂等、证据、去重、冲回和看板聚合单测通过。
|
||||
证据:容器组合回归 84 项通过;本轮 Savings/CFO 基线、洞察、端点、账本、回填与 E2E 组合 `34 passed, 6 skipped`,6 项为未配置 PostgreSQL 专用 URL 的预期跳过。
|
||||
- [x] [CONCEPT: 测试方案] 标准重算客户端金额伪造、政策失败、Case 缺失和事务回滚测试通过。
|
||||
证据:`test_expense_claim_service.py -k standard_adjustment` 8 项加付款集成 1 项通过,HTTP 标准重算 1 项通过。
|
||||
- [x] [CONCEPT: 测试方案] 前端数据归一化、状态、筛选、URL、证据动作和响应式测试通过。
|
||||
证据:容器内 `cfo-value-dashboard.test.mjs` 14 项通过,新增机会 URL 恢复/清理、筛选上下文、风险/单据/预算/维度链接和预算非事实口径断言;与 App Shell 返回链、路由加载和筛选样式组合回归 37 项通过;带凭证序列化和无证据入口 fail-closed 均有断言。
|
||||
- [x] [CONCEPT: 测试方案] 一次性 PostgreSQL 空库迁移、重复升级、约束、append-only、无损降级和并发测试通过。
|
||||
证据:一次性 PostgreSQL 17 迁移循环通过;`test_savings_concurrency_postgres.py` 6 项通过,覆盖重放、canonical 竞态、跨租户、独立确认、付款单事实和 append-only DB 触发器。
|
||||
- [x] [CONCEPT: 集成] 住宿标准重算 → 机会 → 审批 → 付款 → 实际 → 财务确认 → CFO 看板 E2E 通过。
|
||||
证据:`test_savings_value_e2e.py` 从服务端政策差额、付款动作、待确认排除、独立财务确认到 CFO 金额对账单项通过。
|
||||
- [x] [CONCEPT: 集成] 确认后负向冲回和报告 `as_of` 回放 E2E 通过。
|
||||
证据:`test_savings_value_e2e.py`与 `test_cfo_value_analytics.py`同时验证当前净值归零和冲回前历史金额回放。
|
||||
- [x] [CONCEPT: 容器验证] 相关 pytest、Ruff、前端测试和生产构建均在 `local-x-financial-linux` 内通过。
|
||||
证据:Savings/CFO 相关切片与端到端均通过;fresh PostgreSQL 总探针 `87 passed / 0 skipped / 0 failed`,其中 Savings 并发 6 项;Web 全量 `815 passed / 0 failed` 与 Vite build 通过;新增 Python 文件 Ruff 和 `git diff --check` 通过。
|
||||
|
||||
## 9. 文档收尾
|
||||
|
||||
- [x] [CONCEPT: 指标与验收] 逐项核对 A1-A8,并把文件、接口、迁移、测试和运行结果写回证据。
|
||||
证据:A1 纵向闭环由 `test_savings_value_e2e.py`;A2-A4/A4.1 由 Savings schema、`cfo_value_analytics.py`、基线/分析测试;A5-A6 由 CFO 组件、来源下钻和前端状态测试;A7 由 0015 迁移与 PostgreSQL 并发;A8 由本节最终容器汇总证明。
|
||||
- [ ] [CONCEPT: 风险与开放问题] 记录付款证据等级、汇率、双人复核、工时口径和试点目标的最终边界。
|
||||
证据:
|
||||
- [x] [CONCEPT: 本轮实现记录] 同步更新上位 `ai-expense-closed-loop-and-value-proof` 文档,不删除历史证据。
|
||||
证据:上位 TODO 已回填 Savings Ledger、状态机、CFO 看板、下钻、证据详情和 E2E;历史证据原样保留。
|
||||
Reference in New Issue
Block a user