feat(platform): close AI expense value loop

Add tenant-safe value, telemetry, connector, commercial, and production-readiness foundations.
This commit is contained in:
caoxiaozhu
2026-07-17 14:14:08 +08:00
parent 242d68c36f
commit 787bc3a481
507 changed files with 82072 additions and 6344 deletions

View File

@@ -0,0 +1,162 @@
# 知识库多租户隔离与安全编辑 概念文档
更新时间2026-07-17
## 功能一句话
让每个企业只读写自己的知识文件、元数据和 LightRAG/Qdrant 命名空间,同时以平台制度只读层和一次性 ONLYOFFICE 会话安全地贯通知识查询、预览与编辑。
## 背景与问题
原知识库把所有企业的文件、`.index.json``.lightrag`、运行时缓存和 Qdrant workspace 放在同一全局空间。API、后台索引和定时任务还能在没有可信 `tenant_id` 时继续工作,导致同名文件覆盖、跨租户列表/详情/原文读取、索引串读和错误的默认租户归属风险。
ONLYOFFICE 原回调仅依赖可伪造或可重放的短 payload并直接下载回调 URL。匿名请求可以借此访问内部地址、跟随重定向、下载超大或错误格式内容最终覆盖知识文件。预览会话与编辑会话也没有不可变的租户、资源、文档 key、版本和一次性状态绑定。
## 目标与非目标
### 目标
- 文件、索引 JSON、LightRAG 本地状态、运行时实例和 Qdrant workspace 全部按可信租户隔离。
- 旧全局制度资料无损复制到显式 `platform` 空间,并始终以只读方式提供给租户。
- 列表、详情、原文、上传、删除、同步和查询均从认证用户或数据库 Agent Run 获取租户,不接受客户端上下文覆盖。
- ONLYOFFICE token 同时绑定 tenant、resource scope、document、key、version、editable、audience、expiry 和 JTI。
- 编辑回调以数据库状态机实现一次性消费;预览会话永不回写。
- 回调下载仅访问配置的文档服务,拒绝重定向、私网/回环/链路本地解析、DNS 重绑定、超限和非 OOXML 内容。
- 定时索引只枚举租户注册表中的 `active` 租户,不再使用默认租户。
### 非目标
- 不允许租户经普通 API 新建或修改平台制度;平台资料由受控部署流程维护。
- 不把租户知识数据合并成一个共享向量集合后再依赖过滤器补救。
- 不为私网 ONLYOFFICE 地址提供安全降级开关;不满足公网解析要求时回写必须 fail-closed。
- 不在本切片中实现知识内容质量评估、模型微调或新的知识运营前端。
## 用户与场景
- 租户管理员:上传、覆盖、删除和触发当前企业的知识同步。
- 普通员工:联合查询本企业知识与平台只读制度,预览有权限的原文。
- 知识运营人员:在受控编辑模式中修改租户 Office 文档,保存后形成新版本。
- Hermes 调度器:逐个处理活跃租户的增量知识,不创建或猜测默认租户。
- 平台管理员:通过部署资产提供跨租户可见但不可写的平台制度。
## 功能能力
- 租户目录:`storage/knowledge/tenants/{tenant_id}/`
- 平台目录:`storage/knowledge/platform/`,所有业务入口只读。
- 每个作用域独立 `.index.json``.lightrag`、workspace 和运行时缓存键。
- 租户查询采用“租户空间 + 平台只读空间”隔离检索后融合,不在存储层混库。
-`storage/knowledge/{固定目录}` 与旧索引首次启动时复制到平台空间,源文件不删除。
- ONLYOFFICE content token 默认 5 分钟callback session 默认 4 小时、最长 12 小时,并在首次写回前原子进入 `processing`
- 回写成功进入 `consumed`,失败进入 `failed`;已消费、失败、撤销或过期会话不能再次写入。
- 后台索引线程从数据库 Agent Run 的 route/ontology 双份租户上下文重新校验,线程参数只能作为一致性断言。
## 方案设计
### 前端契约
- 知识文档新增 `scope``readOnly` 字段。
- 平台文档可以查看、下载和预览,但编辑入口必须隐藏或禁用。
- ONLYOFFICE 配置默认 `mode=view`;只有租户管理员显式请求 `editable=true` 时才返回编辑配置。
- 回调和 content URL 中的 token 是文档服务专用凭证,不暴露为普通用户授权能力。
### 后端职责
- `knowledge_tenant_scope`校验租户标识生成文件路径、workspace 和缓存键,执行旧资料到平台空间的无损迁移。
- `knowledge`:编排租户/平台文档、权限、文件操作、查询融合和 ONLYOFFICE 配置。
- `knowledge_index_state`:管理当前作用域的 JSON 元数据与 ingest 状态。
- `knowledge_rag`:只使用当前作用域的 working dir、workspace、缓存实例与 Qdrant 命名空间。
- `knowledge_run_scope`:从持久化 Agent Run 的 route/ontology 双份上下文还原并校验后台任务租户。
- `knowledge_onlyoffice_security`:签发/验证会话、原子消费 JTI、执行安全网络下载与 OOXML 校验。
- `knowledge_onlyoffice_callback`:按 token 中已验证的资源作用域定位文件,校验基线后回写新版本。
- `knowledge_scheduler`:从 `tenants.status=active` 枚举租户并创建显式内部身份。
### 数据
新增 migration-owned 表 `knowledge_onlyoffice_sessions`
- 身份:`jti``tenant_id``resource_scope``document_id`
- 不可变绑定:`document_key``document_version``audience``editable``created_by``expires_at`
- 生命周期:`active → processing → consumed | failed`,另支持 `revoked`
- 平台会话仍绑定发起租户,但数据库约束要求 `editable=false`
- `tenant_id` 外键指向租户注册表;租户删除时清理其会话。
- 存在会话证据时 migration downgrade 拒绝有损删除。
### 权限与信任边界
- HTTP 文档 API 只使用 `CurrentUserContext.tenant_id`
- 同步任务只使用认证用户租户;后台线程再与数据库 Agent Run 租户交叉校验。
- 平台 scope 必须由服务端显式构造,客户端不能通过参数选择。
- 租户文档 ID 在其他租户下按不存在处理;平台文档仅作为只读 fallback。
- ONLYOFFICE token 必须同时通过签名、issuer、audience、scope、时间、数据库行和全部资源字段比较。
- callback token 只有写入状态 `2/6` 才尝试 claimkey 不匹配时不会消耗会话。
- 下载 URL 的 origin 必须等于配置白名单DNS 解析的全部地址必须为公网地址,实际连接固定到已校验 IPHTTPS 仍校验原主机证书和 SNI。
- 不跟随 3xx限制响应大小、MIME、ZIP 条目数、解压体积、路径穿越、加密条目和 OOXML 目录结构。
### 查询算法
租户查询先在两个物理隔离空间各自检索,再对候选做确定性融合:
```text
tenant_workspace = base + "__tenant_" + SHA256(tenant_id)[0:20]
platform_workspace = base + "__platform"
merged_hits = top_k(
deduplicate(tenant_hits platform_hits, by=code),
order_by=score DESC
)
```
运行时缓存同样使用租户哈希键,不把原始 tenant ID 放进 Qdrant workspace 名称。
### ONLYOFFICE 状态机
```text
issue(view) → active ── callback status 2/6 ──拒绝写入
issue(edit) → active ── atomic claim ──→ processing
├─验证/下载/写入成功→ consumed
└─任一步失败────────→ failed
active -- exp 超时 --> 验证拒绝
consumed/failed/revoked -- replay --> 409/拒绝
```
### 降级策略
- 租户缺失、格式非法或与 Agent Run 冲突:拒绝任务,不回退默认租户。
- 平台迁移源不存在:建立空平台作用域,不影响租户空间。
- 平台或租户 RAG 不可用:保留已有本地检索降级;不会改查其他租户 workspace。
- ONLYOFFICE 地址、JWT、DNS、MIME 或 OOXML 校验失败:返回错误并保持原文件不变。
- 当前容器若通过网络代理把文档域名解析为非公网保留地址,真实回写会按安全策略拒绝;部署需提供满足公网解析与 TLS 的文档服务或安全反向代理。
## 测试方案
- 服务:两租户同名文件、列表/详情/原文隔离、平台只读 fallback、无 scope fail-closed。
- RAGworkspace、缓存键、本地 chunks 和运行签名隔离。
- 后台:只枚举 active tenantworker 从 Agent Run 重取租户并拒绝 route/ontology 冲突。
- ONLYOFFICEtenant/resource/key/version/aud/exp/JTI 绑定,平台只读,会话过期,错 key 不 claim成功只写一次重放拒绝。
- SSRF错误 origin、私网 DNS、重定向、IP pinning、超限、错误 MIME、损坏 OOXML。
- 迁移revision/down_revision、表/外键/check、非 PostgreSQL 拒绝、ownership/preflight 和有证据 downgrade 拒绝。
- 所有后端验证只在 `local-x-financial-linux` 容器内执行,单命令超时不超过 60 秒。
## 指标与验收
- 100% 租户知识文件、索引、LightRAG 与 Qdrant workspace 可由可信 tenant 唯一确定。
- 跨租户文档读取、删除、同步和查询用例 100% 拒绝或不可见。
- 平台文档 `readOnly=true`,任何编辑回调均不产生文件副作用。
- 同一 callback JTI 最多一次进入 `processing`,重复回调 100% 拒绝。
- 非白名单 origin、非公网解析、重定向、超限或非 OOXML 回写 100% fail-closed。
- 定时任务只为注册表 `active` 租户建任务;无活跃租户时不生成默认数据。
- 新增/修改 Python 文件通过 Ruff 与 compileall定向知识安全、既有知识回归和迁移 ownership 测试全部通过。
## 风险与开放问题
- 当前 JSON 索引采用原子文件级写入之外的旧读改写模型;同一租户多进程高并发上传仍应在后续演进为数据库元数据或跨进程锁。
- 平台制度的发布、签名和回滚需要独立的受控平台资产流程,本切片只保证业务 API 只读。
- 部署必须确认 ONLYOFFICE 下载域名从应用容器解析为真实公网地址;不得为开发便利开放私网通配。
- 长时编辑使用 4 小时 callback session超过时限需重新打开文档生成新会话。
## 本轮实现记录
- 2026-07-17完成租户/平台文件与 RAG 命名空间拆分、旧资料无损平台迁移、API 与后台任务可信租户接线。
- 2026-07-17完成 DB-backed ONLYOFFICE 一次性会话、平台只读预览、文档 key/version 基线校验和 SSRF/大小/MIME/OOXML 防护。
- 2026-07-17完成 `20260717_0027` 迁移、模型 ownership/preflight 注册、定时器 active tenant 枚举及容器定向回归;共享 PostgreSQL 探针完成 `base → 0028``0028 → 0025 → 0028`Knowledge/AgentAsset 两类会话表均正常落库。

View File

@@ -0,0 +1,63 @@
# 知识库多租户隔离与安全编辑 开发 TODO
更新时间2026-07-17
关联方案:[CONCEPT.md](./CONCEPT.md)
## 使用规则
- 任务边界、信任模型和上线约束以 CONCEPT 对应章节为准。
- `[x]` 只表示已有代码或容器验证证据;运维环境尚未验证的项目保持 `[ ]`
- 所有后端测试必须在 `local-x-financial-linux` 容器内运行,单命令最长 60 秒。
## 1. 调研与边界
- [x] [CONCEPT: 背景与问题] 盘点旧文件、index、LightRAG、运行时缓存和 Qdrant 的全局共享路径。证据:`knowledge.py``knowledge_rag.py` 调用点扫描与两租户隔离测试。
- [x] [CONCEPT: 目标与非目标] 冻结“租户可写 + 平台只读”的双层资源模型,不提供客户端 scope 选择或私网下载降级。证据:`KnowledgeStorageScope` 与 platform 只读反向测试。
- [x] [CONCEPT: 用户与场景] 明确 API 用户、平台资料、Hermes 调度器和 ONLYOFFICE 文档服务四类主体。证据认证端点、scheduler 和 session service 的显式调用契约。
## 2. 契约与设计
- [x] [CONCEPT: 功能能力] 定义 `tenants/{tenant}/``platform/`、workspace 和 runtime cache key。证据`knowledge_tenant_scope.py`
- [x] [CONCEPT: 方案设计] 为文档 DTO 增加 `scope/readOnly`,平台文档只能只读 fallback。证据`schemas/knowledge.py` 与平台文档测试。
- [x] [CONCEPT: ONLYOFFICE 状态机] 定义 `active → processing → consumed|failed` 和过期/重放拒绝。证据:`KnowledgeOnlyOfficeSession` 模型、`20260717_0027` 迁移与 replay 测试。
## 3. 后端实现
- [x] [CONCEPT: 后端职责] 建立 tenant/platform 文件、index 与 LightRAG 存储作用域。证据:`knowledge_tenant_scope.py``knowledge_index_state.py``knowledge_rag.py`
- [x] [CONCEPT: 后端职责] 将列表、详情、原文、上传、删除、同步和查询接入可信租户。证据:`knowledge.py``endpoints/knowledge.py``knowledge_sync.py`
- [x] [CONCEPT: 权限与信任边界] 让 Orchestrator 从数据库 Agent Run 取 tenant并让索引 worker 比对 route/ontology tenant。证据`orchestrator_execution.py``knowledge_run_scope.py``knowledge_index_tasks.py`、Agent Run 冲突测试。
- [x] [CONCEPT: 后端职责] 定时器只枚举 `Tenant.status=active`,没有活跃租户时跳过。证据:`knowledge_scheduler.py` 与 active/suspended 调度测试。
- [x] [CONCEPT: 后端职责] 迁移旧全局制度到 platform 空间且不删除源文件。证据:`migrate_legacy_library_to_platform()` 与 legacy migration 测试。
- [x] [CONCEPT: 权限与信任边界] 新增带数据库状态的 ONLYOFFICE token、平台只读预览和一次性 callback。证据`knowledge_onlyoffice_security.py``knowledge_onlyoffice_callback.py`
- [x] [CONCEPT: 权限与信任边界] 加固下载 origin、DNS/IP pinning、重定向、大小、MIME、ZIP 和 OOXML 校验。证据:`download_onlyoffice_document()` 与 SSRF/格式测试。
- [x] [CONCEPT: 数据] 新增 `20260717_0027`注册模型、schema ownership 和 migration preflight。证据迁移文件、`db/base.py``models/__init__.py``migration_preflight.py`
- [x] [CONCEPT: 后端职责] 删除知识文件工具中已废弃的无状态弱 token 实现。证据:`knowledge_file_utils.py` 差异与全仓引用扫描。
## 4. 算法/规则实现
- [x] [CONCEPT: 查询算法] 以租户哈希生成不可冲突的 workspace/cache key再隔离检索 tenant 与 platform 候选。证据workspace/cache/local chunks 两租户测试。
- [x] [CONCEPT: 查询算法] 对两个隔离结果按 code 去重、score 排序并截取 top-k。证据`_merge_scoped_search_results()`
- [x] [CONCEPT: ONLYOFFICE 状态机] content token 默认 5 分钟callback session 默认 4 小时且最长 12 小时。证据JWT exp 差值断言与会话测试。
## 5. 前端实现
- [x] [CONCEPT: 前端契约] 后端 DTO 已提供 `scope/readOnly`现有列表可安全识别平台只读资源。证据OpenAPI 回归与 `KnowledgeDocumentRead`
- [x] [CONCEPT: 前端契约] ONLYOFFICE 默认返回 view 模式,只有管理员显式 `editable=true` 才获得 edit 权限。证据config 单元测试与平台编辑拒绝测试。
- [x] [CONCEPT: 目标与非目标] 本切片不新增知识运营页面或视觉改版。证据CONCEPT 非目标;本轮无知识前端文件变更。
## 6. 测试与验证
- [x] [CONCEPT: 测试方案] 完成租户文件、平台只读、RAG namespace、scheduler 与 worker trust 测试。证据:容器内 `test_knowledge_tenant_security.py` 6 项通过。
- [x] [CONCEPT: 测试方案] 完成 token 绑定、平台只读、错 key、过期、单次回写、重放和 SSRF/OOXML 测试。证据:容器内 `test_knowledge_onlyoffice_tenant_security.py` 8 项通过。
- [x] [CONCEPT: 测试方案] 完成既有知识服务、RAG、同步、配置、解析、runtime 与 OpenAPI 回归。证据:容器内 8 个测试文件 31 项通过。
- [x] [CONCEPT: 测试方案] 完成 migration、preflight 与 ownership 静态回归。证据:容器内 163 项通过、1 项因无 PostgreSQL 测试 DSN 跳过。
- [x] [CONCEPT: 测试方案] 完成相关 Agent Run 与鉴权回归。证据:容器内 20 项通过。
- [x] [CONCEPT: 指标与验收] 完成目标 Python 文件 Ruff 和 compileall。证据容器命令均退出码 0。
- [x] [CONCEPT: 测试方案] 在一次性 PostgreSQL 测试数据库执行迁移链。证据:共享迁移探针完成 `base → 0028``0028 → 0025 → 0028``knowledge_onlyoffice_sessions``agent_asset_onlyoffice_sessions` 均正常落库;有事实 downgrade 仍由各迁移显式拒绝。
## 7. 文档收尾
- [x] [CONCEPT: 本轮实现记录] 更新 CONCEPT、分阶段 TODO 和三份 bug 修复日志。证据:本目录两份文档及 `dev-logs/bugs/knowledge-*.md`
- [x] [CONCEPT: 风险与开放问题] 记录 JSON index 多进程竞争、平台发布流程和长时编辑边界。证据CONCEPT 风险章节。
- [ ] [CONCEPT: 降级策略] 上线前确认 ONLYOFFICE 下载域名从应用容器解析为公网地址并使用可信 TLS。证据要求生产 `ONLYOFFICE_DOWNLOAD_ALLOWED_ORIGINS` 配置与容器 DNS/TLS 验证;当前开发网络解析到 `198.18.0.0/15` 时会按设计拒绝真实回写。