# 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 和风险规则生成链路的租户接线与容器回归。
