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,188 @@
|
||||
# Agent 资产多租户隔离与安全规则编辑 概念文档
|
||||
|
||||
更新时间:2026-07-17
|
||||
|
||||
## 功能一句话
|
||||
|
||||
让规则、技能、MCP、任务及其版本、审核、测试和编辑会话都拥有可验证的租户归属,并以“企业资产可写、平台资产只读”的双层模型安全贯通规则生成、真实场景验证、审核、发布和 ONLYOFFICE 编辑。
|
||||
|
||||
## 背景与问题
|
||||
|
||||
原 AgentAsset 数据模型没有结构化 `tenant_id` 与 `scope`,部分读取和内部查询可以在没有用户上下文时返回全局资产。版本、审核、测试、反馈和发布链路主要依赖 `asset_id` 或业务约定关联,无法由数据库阻止跨租户子记录注入。同编码资产也不能由不同企业独立维护。
|
||||
|
||||
规则场景测试可以在未声明目标企业时抽取费用数据,审核主体还可能使用客户端传入的 actor/reviewer 字段,导致测试证据和盲审身份缺乏稳定、可追溯的企业边界。
|
||||
|
||||
规则表的 ONLYOFFICE 内容与回调接口原先缺少持久化的一次性会话。回调下载地址、文档 key、版本、租户和编辑权限之间没有不可变绑定,存在匿名读取、跨资产回写、身份伪造、重放和服务端请求伪造风险。
|
||||
|
||||
## 目标与非目标
|
||||
|
||||
### 目标
|
||||
|
||||
- AgentAsset、Version、Review、TestRun、RuleFeedback 和 ONLYOFFICE Session 均保存结构化租户作用域。
|
||||
- 租户用户只能看到本企业资产和平台资产;同编码时企业资产优先覆盖平台默认资产。
|
||||
- 跨租户详情、版本、审核、发布、测试和反馈统一表现为不存在,避免泄露资源存在性。
|
||||
- 平台资产对所有租户只读,仅平台管理员可新增、修改、发布或编辑。
|
||||
- 所有 HTTP 入口使用认证会话中的 `CurrentUserContext.tenant_id`,不接受客户端覆盖租户。
|
||||
- 写入、版本和审核操作使用 RuleEditor、RuleReviewer 或平台管理员权限,并以稳定身份写入审计证据。
|
||||
- 真实风险场景必须显式声明当前企业 `target_tenant_id`,费用样本的第一层 SQL 条件就是该租户。
|
||||
- ONLYOFFICE 内容和回调使用不同 audience/scope 的签名 token,并绑定数据库一次性会话。
|
||||
- 回调下载拒绝错误 origin、非公网解析、DNS 重绑定、重定向、超限和非安全 OOXML 文件。
|
||||
|
||||
### 非目标
|
||||
|
||||
- 不允许普通企业用户创建或修改平台资产。
|
||||
- 不把所有企业资产放在全局结果集中后仅靠前端过滤。
|
||||
- 不允许平台管理员借普通企业会话修改其他企业的私有资产;跨企业运维需要独立受控流程。
|
||||
- 不提供允许私网、回环或任意下载地址的 ONLYOFFICE 安全降级开关。
|
||||
- 不在本切片中重做 Agent 资产管理前端视觉或商业定价页面。
|
||||
|
||||
## 用户与场景
|
||||
|
||||
- 企业规则编辑者:维护本企业规则资产、上传规则表并创建新版本。
|
||||
- 企业规则审核者:以稳定登录身份进行盲审、驳回或批准规则版本。
|
||||
- 企业风控人员:用本企业真实费用申请生成测试样本和质量证据。
|
||||
- 普通企业用户:读取本企业资产和平台只读资产,但不能写入。
|
||||
- 平台管理员:维护跨企业可见的平台基础规则和模板。
|
||||
- 运行时与调度器:在显式租户范围内加载、测试、监控和召回资产,不进行全局扫描。
|
||||
- ONLYOFFICE 文档服务:使用资源专用 token 读取一次文档,并通过单次 callback session 回写允许编辑的当前版本。
|
||||
|
||||
## 功能能力
|
||||
|
||||
- 双层可见性:`tenant:{tenant_id}` 与 `platform:platform`。
|
||||
- 企业内唯一编码:数据库唯一键为 `(tenant_id, scope, code)`,不同企业可拥有同编码资产。
|
||||
- 确定性覆盖:按编码加载时先找当前企业资产,再回退平台资产。
|
||||
- 服务端可信租户:HTTP 服务由登录会话构造 `AgentAssetAccessScope`;无用户上下文的内部读取只允许平台作用域。
|
||||
- 稳定审计主体:优先记录 `employee:{employee_id}`,没有员工 ID 时记录大小写归一的 `username:{username}`。
|
||||
- 真实样本隔离:场景请求必须传 `target_tenant_id`,且必须等于登录企业;TestRun 记录样本所属企业。
|
||||
- 平台资产可在企业场景中验证,但测试证据仍归目标企业,不能变成平台或其他企业事实。
|
||||
- ONLYOFFICE content token 有效期 15 分钟,callback session 有效期 4 小时。
|
||||
- 回调状态机保证同一个 JTI 最多一次进入写入阶段。
|
||||
|
||||
## 方案设计
|
||||
|
||||
### 前端契约
|
||||
|
||||
- AgentAsset DTO 提供 `tenantId` 与 `scope`,前端可明确标识企业资产和平台资产。
|
||||
- 平台资产在非平台管理员会话中必须隐藏或禁用修改、发布、上传和 ONLYOFFICE 编辑动作。
|
||||
- 场景测试请求必须携带 `targetTenantId`;它是目标企业的显式确认,不是可切换企业的授权参数。
|
||||
- 旧 `X-Actor`/reviewer 头仅为兼容保留,服务端忽略其身份值并使用登录会话主体。
|
||||
- ONLYOFFICE 配置根据权限返回 `view` 或 `edit`;内容和回调 token 只供文档服务使用。
|
||||
|
||||
### 后端职责
|
||||
|
||||
- `agent_asset_scope`:定义 platform/tenant 常量和合法作用域基础规则。
|
||||
- `agent_asset_access`:从可信用户构造访问范围、生成稳定主体并提供可见/可写谓词。
|
||||
- `agent_asset` repository:所有列表、详情、版本、审核、测试和反馈查询都注入结构化租户条件。
|
||||
- `agent_assets`:编排资产 CRUD、版本、审核与序列化,不通过未过滤 ORM 关系返回子记录。
|
||||
- `agent_asset_risk_rule_testing`:校验目标企业、先按租户过滤 ExpenseClaim,再生成测试证据。
|
||||
- 发布、监控、召回、调度、遥测和风险运行时服务:沿资产租户作用域读取和写入,禁止全局 asset id 查询。
|
||||
- `agent_asset_onlyoffice_security`:签发/验证持久化会话并原子消费 callback JTI。
|
||||
- `agent_asset_onlyoffice`:按会话中的租户、资产、key、版本和指纹定位文档,安全下载后创建新版本。
|
||||
- `knowledge_onlyoffice_security`:复用统一安全下载器,执行 origin、DNS/IP、响应和 OOXML 校验。
|
||||
|
||||
### 数据
|
||||
|
||||
`20260717_0026_agent_asset_tenant_security.py` 负责以下结构:
|
||||
|
||||
- `agent_assets`:新增 `tenant_id`、`scope`,建立作用域约束、租户外键和企业内 code 唯一键。
|
||||
- `agent_asset_versions`、`agent_asset_reviews`:新增租户作用域,并以 `(tenant_id, scope, asset_id)` 复合外键绑定父资产。
|
||||
- `agent_asset_test_runs`、`agent_asset_rule_feedback`:保存证据所属企业;平台资产的企业测试/反馈也归企业事实域。
|
||||
- `agent_asset_onlyoffice_sessions`:保存 JTI、租户、资源 scope、asset、document key/version/fingerprint、audience、权限、actor、过期时间和状态。
|
||||
- 旧资产默认回填为平台资产;能从父资产确定的历史版本、审核和证据同步回填。
|
||||
- 无法确认租户的历史数据或不完整旧表结构会 fail-closed,不猜测企业归属。
|
||||
|
||||
### 权限与信任边界
|
||||
|
||||
- 当前企业只来自 `CurrentUserContext.tenant_id`;空值、`platform` 伪企业或请求参数不能构造企业访问范围。
|
||||
- 租户读取谓词是“当前企业或平台”,写入谓词只允许当前企业;平台写入还要求 `is_admin=true`。
|
||||
- 跨租户资源统一返回 404;权限不足的本作用域操作返回受控错误。
|
||||
- RuleEditor 可维护规则和版本,RuleReviewer 执行审核;平台资产的任意写操作额外要求平台管理员。
|
||||
- 版本 created_by、审核 reviewer、规则表变更 actor 都由稳定登录主体生成。
|
||||
- 后台 bootstrap/foundation 按平台作用域精确查找种子资产,不能误改同编码企业资产。
|
||||
- 风险运行时按“企业优先、平台回退”加载发布规则,不扫描其他企业版本。
|
||||
- ORM 关系不是授权边界;对外响应必须经过带 scope 的 repository/service 查询。
|
||||
|
||||
### 资产解析顺序
|
||||
|
||||
```text
|
||||
find_by_code(code, current_tenant):
|
||||
1. tenant_id = current_tenant AND scope = tenant
|
||||
2. tenant_id = platform AND scope = platform
|
||||
3. not found
|
||||
```
|
||||
|
||||
该顺序让企业能够在不修改平台模板的情况下覆盖默认规则,同时保持其他企业和平台资产不受影响。
|
||||
|
||||
### 风险场景测试
|
||||
|
||||
```text
|
||||
认证用户 tenant
|
||||
→ 校验 target_tenant_id == tenant
|
||||
→ 校验目标资产为 tenant 自有或 platform 只读资产
|
||||
→ SQL 第一层条件 ExpenseClaim.tenant_id == target_tenant_id
|
||||
→ 应用时间、费用类型、城市等业务筛选
|
||||
→ 创建 tenant-scoped TestRun
|
||||
```
|
||||
|
||||
没有目标企业、目标企业不一致或资产属于其他企业时均拒绝,不使用 mock 的默认企业或全局样本补齐。
|
||||
|
||||
### ONLYOFFICE 状态机
|
||||
|
||||
```text
|
||||
issue(view) → active ── callback status 2/6 ──拒绝写入
|
||||
issue(edit) → active ── atomic claim ──→ processing
|
||||
├─校验/下载/写入成功→ consumed
|
||||
└─任一步失败────────→ failed
|
||||
|
||||
active -- exp 超时 --> 验证拒绝
|
||||
processing/consumed/failed/revoked -- replay --> 409/拒绝
|
||||
```
|
||||
|
||||
token 同时绑定 issuer、audience、scope、JTI、tenant、resource scope、asset、document key、version、fingerprint、writable、actor、iat/nbf/exp。回调 payload 只能提供状态和下载位置,不能覆盖这些授权事实。
|
||||
|
||||
### 降级与回滚策略
|
||||
|
||||
- 无可信租户:HTTP 请求拒绝;无用户上下文的内部服务只看平台资产,绝不回退全局查询。
|
||||
- 跨租户资产、版本或测试证据:按不存在处理,不尝试平台管理员越权兼容。
|
||||
- 场景样本为空:返回空样本测试事实,不改查其他企业数据。
|
||||
- ONLYOFFICE token、key、版本、指纹、DNS、MIME 或 OOXML 校验失败:拒绝回写并保持原文件不变。
|
||||
- 生产文档服务必须使用配置白名单中的公网 TLS origin,或经满足相同约束的安全代理访问。
|
||||
- migration downgrade 在存在企业资产/证据或 ONLYOFFICE 会话时拒绝有损回滚;必须先通过受控数据迁移清理事实。
|
||||
|
||||
## 测试方案
|
||||
|
||||
- 可见性:两企业同编码资产、企业覆盖平台、平台只读、跨企业详情 404、无上下文仅平台。
|
||||
- 数据完整性:scope/tenant check、企业内 code 唯一、Version/Review 复合父子外键、TestRun 证据企业。
|
||||
- 权限与身份:RuleEditor/RuleReviewer、平台管理员、伪造 actor/reviewer 无效、稳定 employee/username 主体。
|
||||
- 风险场景:必须目标企业、目标不一致拒绝、SQL 租户首过滤、平台资产的企业 TestRun。
|
||||
- 发布链路:发布门禁、监控、召回、运行时、调度和遥测均使用显式租户。
|
||||
- ONLYOFFICE:content/callback scope、tenant/asset/key/version/fingerprint 绑定、平台只读、原子 claim、失败终态和重放拒绝。
|
||||
- SSRF/文件:白名单 origin、全量公网 DNS、固定已校验 IP、拒绝重定向、大小/MIME/ZIP/OOXML 限制。
|
||||
- 迁移:旧平台数据回填、同编码多企业、非法 scope/跨租户外键拒绝、事实存在时 downgrade 拒绝、清理后可回滚。
|
||||
- 所有后端验证只在 `local-x-financial-linux` 容器中执行,单条命令限制 60 秒。
|
||||
|
||||
## 指标与验收
|
||||
|
||||
- 所有 AgentAsset 业务读取都能由 `tenant_id + scope` 确定结果范围。
|
||||
- 跨企业资产、版本、审核、测试、反馈和回调用例 100% 不可见或拒绝。
|
||||
- 平台资产对非平台管理员的写入和回调 100% 无副作用。
|
||||
- 真实费用样本查询 100% 具有服务端校验的企业首过滤条件。
|
||||
- 审计主体 100% 来自稳定登录身份,客户端 actor/reviewer 不能改变事实。
|
||||
- 同一 ONLYOFFICE callback JTI 最多一次进入 `processing`。
|
||||
- 相关 Python 文件通过 Ruff、py_compile 和 ORM mapper 配置;定向发布、安全与风险规则回归通过。
|
||||
|
||||
## 风险与开放问题
|
||||
|
||||
- 旧测试或内部调用若直接构造没有 `tenant_id` 的 `CurrentUserContext`,会按新信任边界 fail-closed;应由对应调用方补齐真实租户,而不是放宽服务契约。
|
||||
- 两个旧费用风险测试使用尚未持久化、没有 claim tenant 的对象;共享租户作用域已要求先保存并确认归属,需由费用申请切片统一调整测试夹具。
|
||||
- 两个旧风险发布测试手工注入 aggregate 后直接 promote,与当前必须有真实质量 TestRun 的发布门禁不一致;需由发布门禁切片统一口径。
|
||||
- ONLYOFFICE 实际回写依赖部署环境提供公网可解析、可信 TLS 的文档服务或安全代理;开发网络解析到保留/私网地址时会按设计拒绝。
|
||||
- 企业间受控复制、平台资产签名发布和跨企业运维审计属于后续独立能力,不应通过放宽本轮隔离实现。
|
||||
|
||||
## 本轮实现记录
|
||||
|
||||
- 2026-07-17:完成 AgentAsset、Version、Review、TestRun、Feedback 的结构化租户作用域、企业覆盖平台读取和跨企业 fail-closed。
|
||||
- 2026-07-17:完成可信目标企业风险场景、真实费用样本 SQL 首过滤、企业 TestRun 证据和稳定盲审身份。
|
||||
- 2026-07-17:完成 DB-backed ONLYOFFICE 一次性会话、平台只读、文档基线绑定与安全下载。
|
||||
- 2026-07-17:完成 `20260717_0026` 迁移及一次性 PostgreSQL `base → head`、`head → 0025 → head` 验证。
|
||||
- 2026-07-17:完成发布、监控、召回、运行时、调度、遥测、foundation 和风险规则生成链路的租户接线与容器回归。
|
||||
@@ -0,0 +1,67 @@
|
||||
# Agent 资产多租户隔离与安全规则编辑 开发 TODO
|
||||
|
||||
更新时间:2026-07-17
|
||||
|
||||
关联方案:[CONCEPT.md](./CONCEPT.md)
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 任务边界、信任模型、数据归属和上线约束以 CONCEPT 对应章节为准。
|
||||
- `[x]` 只表示已有代码或容器验证证据;生产环境尚未验证的项目保持 `[ ]`。
|
||||
- 所有后端测试必须在 `local-x-financial-linux` 容器内执行,单命令最长 60 秒。
|
||||
|
||||
## 1. 调研与边界
|
||||
|
||||
- [x] [CONCEPT: 背景与问题] 盘点 AgentAsset、版本、审核、测试、反馈、发布和规则表编辑中的无租户/弱租户查询。证据:repository、service、endpoint 和 release 全链路调用扫描。
|
||||
- [x] [CONCEPT: 目标与非目标] 冻结“企业资产可写 + 平台资产只读 + 企业覆盖平台”的双层模型。证据:`AgentAssetAccessScope` 与两企业同编码测试。
|
||||
- [x] [CONCEPT: 权限与信任边界] 冻结可信租户只来自登录会话、客户端 tenant/actor/reviewer 不能覆盖事实。证据:认证依赖、稳定主体函数和伪造头测试。
|
||||
|
||||
## 2. 契约与设计
|
||||
|
||||
- [x] [CONCEPT: 数据] 为 Asset、Version、Review、TestRun、Feedback 定义 `tenant_id + scope`、约束和索引。证据:模型与 `20260717_0026`。
|
||||
- [x] [CONCEPT: 资产解析顺序] 定义企业同编码资产优先、平台资产回退的确定性加载顺序。证据:scoped repository 和 runtime loader 测试。
|
||||
- [x] [CONCEPT: ONLYOFFICE 状态机] 定义 `active → processing → consumed|failed`,以及过期、撤销和重放拒绝。证据:Session 模型、迁移与 callback 测试。
|
||||
- [x] [CONCEPT: 风险场景测试] 定义显式 `target_tenant_id` 与 TestRun 证据企业,不允许默认或全局样本。证据:scenario schema/service 测试。
|
||||
|
||||
## 3. 后端实现
|
||||
|
||||
- [x] [CONCEPT: 后端职责] 所有 AgentAsset 列表、详情和 code 查询接入结构化可见谓词。证据:`agent_asset.py` repository 与 `agent_assets.py`。
|
||||
- [x] [CONCEPT: 权限与信任边界] 资产写入、版本和审核接入 RuleEditor/RuleReviewer/平台管理员依赖。证据:AgentAsset 和风险规则 endpoints。
|
||||
- [x] [CONCEPT: 权限与信任边界] 用 `employee:{id}` 或归一化 `username:{name}` 替代请求 actor/reviewer。证据:`stable_user_principal()` 与审计断言。
|
||||
- [x] [CONCEPT: 后端职责] 发布门禁、评审、监控、召回、遥测、调度和处置标签均接入租户作用域。证据:8 个 release 定向测试文件。
|
||||
- [x] [CONCEPT: 后端职责] foundation/bootstrap 只查平台 seed,避免修改同编码企业资产。证据:foundation helper 拆分与回归测试。
|
||||
- [x] [CONCEPT: 后端职责] 风险运行时按企业优先、平台回退加载规则,禁止跨企业版本。证据:expense runtime/loader 改造。
|
||||
- [x] [CONCEPT: 风险场景测试] 场景请求校验目标企业,并将 `ExpenseClaim.tenant_id` 作为 SQL 第一层谓词。证据:真实样本场景与 TestRun 断言。
|
||||
- [x] [CONCEPT: ONLYOFFICE 状态机] 新增持久化 content/callback token、原子 claim 和终态处理。证据:`agent_asset_onlyoffice_security.py`。
|
||||
- [x] [CONCEPT: 权限与信任边界] content/callback 按会话重建 machine scope,拒绝跨企业、跨资产、跨版本和平台只读回写。证据:ONLYOFFICE 安全测试。
|
||||
- [x] [CONCEPT: 降级与回滚策略] 复用安全下载器,拒绝错误 origin、非公网 DNS、重定向、超限及异常 OOXML。证据:下载器单测与 callback 回归。
|
||||
- [x] [CONCEPT: 数据] 新增 `20260717_0026`,完成旧平台数据回填、约束、索引和有事实 downgrade 保护。证据:迁移文件与 PostgreSQL 探针。
|
||||
|
||||
## 4. 代码结构
|
||||
|
||||
- [x] [CONCEPT: 后端职责] 将访问策略、ONLYOFFICE 安全和序列化从大型 AgentAsset service 中拆出。证据:新增小职责模块,核心文件均低于 800 行。
|
||||
- [x] [CONCEPT: 后端职责] 将风险规则字段推断/草稿对齐从生成主服务抽到独立模块。证据:`risk_rule_generation.py` 619 行、`risk_rule_generation_fields.py` 272 行。
|
||||
- [x] [CONCEPT: 后端职责] foundation 按资产 helper、seed、topup、财务规则和电子员工任务拆分。证据:拆分模块与 28 项定向回归。
|
||||
|
||||
## 5. 前端契约
|
||||
|
||||
- [x] [CONCEPT: 前端契约] 后端 DTO 已返回 `tenantId/scope`,客户端可识别平台只读资产。证据:schema 与 AgentAsset API 回归。
|
||||
- [x] [CONCEPT: 前端契约] ONLYOFFICE 配置按权限返回 view/edit,平台资产仅平台管理员可编辑。证据:config 单测与平台回写拒绝测试。
|
||||
- [x] [CONCEPT: 目标与非目标] 本切片不做 Agent 资产管理页面视觉重构。证据:CONCEPT 非目标。
|
||||
|
||||
## 6. 测试与验证
|
||||
|
||||
- [x] [CONCEPT: 测试方案] 完成租户可见性、企业覆盖平台、无上下文平台只读、跨租户 404、目标企业场景和稳定身份测试。证据:`test_agent_asset_tenant_security.py`。
|
||||
- [x] [CONCEPT: 测试方案] 完成发布门禁、监控、召回、运行时、调度、遥测及 ONLYOFFICE callback 汇总回归。证据:容器内 58 项通过。
|
||||
- [x] [CONCEPT: 测试方案] 完成 AgentAsset service 与 foundation 兼容回归。证据:容器内 28 项通过、4 项无关差旅计算器用例主动排除。
|
||||
- [x] [CONCEPT: 测试方案] 完成风险生成、修订和 golden evaluator 回归。证据:容器内 49 项通过,2 项旧发布门禁口径由对应切片处理。
|
||||
- [x] [CONCEPT: 测试方案] 完成费用风险租户接线回归。证据:13 项通过;2 项旧测试使用未保存 claim,已记录为共享测试夹具问题。
|
||||
- [x] [CONCEPT: 测试方案] 完成一次性 PostgreSQL 迁移验证。证据:旧数据升级、同编码多企业、非法约束、跨租户 FK、有事实 downgrade 拒绝,以及 `base → head`、`head → 0025 → head` 均通过。
|
||||
- [x] [CONCEPT: 指标与验收] 相关文件 Ruff 通过、py_compile 通过、ORM mapper 84 张表完成配置、`git diff --check` 通过。
|
||||
|
||||
## 7. 文档与上线
|
||||
|
||||
- [x] [CONCEPT: 本轮实现记录] 完成 CONCEPT、分阶段 TODO 和安全 bug 修复日志。证据:本目录及 `dev-logs/bugs/agent-asset-tenant-isolation-and-onlyoffice-security.md`。
|
||||
- [x] [CONCEPT: 风险与开放问题] 记录无 tenant 旧调用、未保存 claim 测试、旧发布 aggregate 口径和企业间受控复制边界。证据:CONCEPT 风险章节。
|
||||
- [ ] [CONCEPT: 降级与回滚策略] 上线前确认 ONLYOFFICE 下载 origin 在应用容器内解析为公网地址并使用可信 TLS。证据要求:生产白名单配置、容器 DNS/TLS 与实际编辑回写验证。
|
||||
- [ ] [CONCEPT: 降级与回滚策略] 上线前在备份副本执行 `0025 → head`,确认不存在无法归属的历史记录,并演练有事实情况下的受控回滚流程。
|
||||
Reference in New Issue
Block a user