Skip to content

[Feature]: Multi-Tenant Phase 2 - 存储层隔离实现 #159

@qin-ctx

Description

@qin-ctx

背景

Phase 1(#158)已完成 API 层多租户能力定义:身份类型、API Key 管理、认证中间件、RBAC、Admin API、Router 依赖注入迁移。

当前 RequestContext 已在 Router 层可用,但未向下传递到 Service / VikingFS / VectorDB。所有请求仍然共享同一存储空间,数据无隔离。

Phase 2 的目标是将 RequestContext 贯穿到存储层,实现 account 级路径隔离、user/agent 级权限过滤、VectorDB 租户过滤。

设计文档:docs/design/multi-tenant-design.md
RFC 讨论:#157

设计方案

AGFS 文件系统隔离(设计文档 5.3 节)

VikingFS 保持单例,不持有租户状态。多租户通过参数传递实现:

  1. 公开方法(lsreadwrite 等)接收 ctx: RequestContext
  2. ctx.account_id 提取 account_id,传给内部方法
  3. _uri_to_path 加 account_id 前缀:viking://resources/doc.md + account_id="acme"/local/acme/resources/doc.md
  4. _path_to_uri 去前缀还原,对用户透明

单租户模式(multi_tenant=false)下 VikingFS 忽略 account_id,保持旧扁平路径(见设计文档第 8 节)。

逐层权限过滤(设计文档 5.4 节)

user/agent 级隔离通过 _is_accessible(uri, ctx) 实现:

  • ROOT / ACCOUNT_ADMIN:可见所有条目
  • USER:只能看到自己的 user_spaceagent_space 目录
  • 列举操作(ls、tree、glob):AGFS 返回全量结果后过滤
  • 读写操作(read、write、mkdir 等):执行前校验,无权限则拒绝

VectorDB 租户过滤(设计文档 5.5 节)

context collection,schema 新增 account_idowner_space 字段。

查询过滤策略:

角色 过滤条件
ROOT
ACCOUNT_ADMIN account_id = ctx.account_id
USER account_id = ctx.account_id AND owner_space IN (user_space, agent_space)

写入时 Context 对象携带 account_idowner_space,通过 EmbeddingMsgConverter 透传到 VectorDB。

Service 层适配(设计文档 T9)

  • 去除 OpenVikingService 的单例 _user
  • 所有 sub-service 方法新增 ctx: RequestContext 参数
  • Router 层将 Phase 1 已注入的 ctx 传递给 service 方法

目录初始化(设计文档 5.6 节)

  • 创建新 account 时:初始化公共根目录(viking://userviking://agentviking://resources 等)
  • 用户首次访问时:懒初始化 viking://user/{user_space}/memories/preferences
  • Agent 首次使用时:懒初始化 viking://agent/{agent_space}/memories/cases

任务拆解

实施顺序:T6/T7 并行 → T8 → T9 → T10-P2(ctx 传递)→ T12-P2(嵌入模式)→ T13 → T14-P2

T6: VikingFS 多租户改造

修改 openviking/storage/viking_fs.py

  • 新增 _multi_tenant: bool 标志
  • 所有公开方法新增 ctx: RequestContext 参数
  • _uri_to_path / _path_to_uri:多租户模式加 account_id 前缀,单租户模式保持旧路径
  • 新增 _is_accessible(uri, ctx) 方法,列举操作过滤、读写操作校验
  • 内部方法统一加 account_id: str 参数

T7: VectorDB schema 扩展

修改 openviking/storage/collection_schemas.py

  • context_collection() Fields 新增 account_id(string)字段

T8: 检索层与数据写入的租户过滤

修改 openviking/retrieve/hierarchical_retriever.pyopenviking/core/context.py

  • Context 类新增 account_idowner_space 字段
  • HierarchicalRetriever.retrieve() 新增 ctx 参数,按角色注入 account_id + owner_space 过滤条件

T9: Service 层适配

修改 openviking/service/core.py 及所有 sub-service:

  • 去除 _user 单例
  • 各 service 方法新增 ctx 参数,传递给 VikingFS / Retriever
  • 涉及:FSService、SearchService、SessionService、ResourceService、RelationService、PackService、DebugService

T10-P2: Router ctx 传递

修改所有 router:

  • Phase 1 已完成依赖注入(Depends(get_request_context)),Phase 2 将 ctx 传给 service 方法
  • await service.fs.ls(uri)await service.fs.ls(uri, ctx=ctx)

T12-P2: 嵌入模式适配

修改 openviking/client/local.py

  • 嵌入模式不做多租户,构造固定默认 RequestContext(user=default, role=ROOT) 传给 service

T13: 目录初始化适配

修改 openviking/core/directories.py

  • 拆分为 initialize_account_directories / initialize_user_directories / initialize_agent_directories
  • 方法签名接受 ctx: RequestContext
  • 构造 Context 时填入 account_idowner_space

T14-P2: 隔离与可见性测试

  • 存储隔离测试_uri_to_path 前缀正确性、_is_accessible 行为、VectorDB 多级过滤
  • 端到端集成测试:Root 创建 account → Account 注册 user → User 写数据 → 跨 account 不可见、同 account 跨 user 不可见
  • 单租户模式测试(若采用设计文档第 8 节方案 A):multi_tenant=false 时扁平路径、Admin API 返回 404

关联

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

Status

In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions