互客鱼 返回主站

架构

多租户

多租户是代码库中最重要的不变量。 跨工作区边界泄漏数据的错误是安全事件。 强制执行是分层的:traits、全局范围、策略和使构建失败的回归测试。

Trait 模式

两个 traits 承担繁重的工作:

  • App\Concerns\BelongsToWorkspace — 对于具有直接 workspace_id 列的模型。
  • App\Concerns\BelongsToAgent — 对于属于智能体的模型(并间接属于该智能体的工作区)。trait 的全局范围通过 agents 连接以按 agents.workspace_id = current 过滤。

两个 traits 都添加一个应用于模型的每个 Eloquent 查询的全局范围。 它们还在创建时自动填充 workspace_id,因此您不能意外地将行插入到错误的工作区。

CurrentWorkspace

当前工作区由中间件按请求解析:

  • 认证请求 — 读取 users.default_workspace_id,验证成员资格,设置单例。
  • 小部件请求 — 从 JWT 的 agent_id 声明派生,查找智能体,从 agents.workspace_id 设置单例。
  • 系统/队列作业 — 每个作业显式设置,从不继承。

解析器位于 App\Support\CurrentWorkspace。 请求后,中间件的 finally 块清除它,因此状态不能在 Octane 请求之间泄漏。

绕过范围(罕见)

某些查询确实需要跨工作区查看 — 管理员报告、模拟、系统作业。 绕过是显式的:

// System-level operation: rolling up usage across all workspaces
Workspace::withoutWorkspaceScope()->each(...)

约定是每个 withoutWorkspaceScope() 调用必须在其正上方有正当理由注释。 代码审查强制执行此操作;/tenancy 审计斜杠命令标记违规。

切换工作区

具有多个成员资格的用户使用侧边栏中的工作区切换器。 切换 POST 到 /workspaces/{id}/select, 更新 users.default_workspace_id 并重定向。 下一页加载解析新工作区。

切换器仅在用户有两个或多个成员资格时呈现 — 单工作区用户看到安静的标签而不是菜单,因为在一个选项中“切换”只是杂乱。

每工作区数据

租户范围的表(每个带有 trait 的 Eloquent 模型):

  • 直接 workspace_idagentsintegration_connectionsplan_subscriptionsusage_eventsaudit_logsinvitations
  • 通过智能体:agent_versionsbehavior_rulescta_rulescurated_answersexperimentssourcesdocumentschunksvisitorsconversationsmessagesleadscontent_gaps

跨工作区表(无范围):

  • users — 全局。任何工作区的成员资格在枢轴中。
  • workspaces — 全局。工作区本身不范围到工作区。
  • plans — 全局,平台管理员管理。
  • jobs / failed_jobs / notifications — Laravel 基础设施。
  • app_settings — 单例,平台范围。

向量存储隔离

向量存储元数据镜像租户契约 — 每个点都有 agent_idworkspace_id 标签, 每个查询都按 agent_id 过滤。 Cloudflare Vectorize 和 Qdrant 都原生支持此功能(Qdrant 的有效负载过滤器 / Vectorize 的元数据过滤器)。

仅按 agent_id 过滤但不按智能体的实际工作区过滤的错误仍然是安全的 — 智能体 ID 是 ULID,全局唯一。 根本不过滤的错误将是泄漏。 Retriever 始终显式过滤。

授权(策略)

租户是“这行在我的工作区中吗?”。授权是“我可以用我的工作区中的行做什么?”。 策略位于 app/Policies/

  • WorkspacePolicy — 查看/更新/删除工作区本身,转移所有权。
  • AgentPolicy — viewAny / view / create / update / delete / publish / rollback。
  • SourcePolicy — 管理智能体内的来源。
  • LeadPolicy — 读取/更新/删除潜在客户。
  • IntegrationConnectionPolicy — 连接/断开连接。

检查模式是一致的:$user->can('update', $agent)abort_if(! $user->can(...))。 策略使用 Tenancy 帮助器解析用户在资源工作区中的角色, 然后调用 WorkspaceRole 枚举的能力方法。

测试

回归测试是 tests/Feature/ 下的 MultiTenancyTest。 它用重叠的数据种子两个工作区,并断言每个工作区的查询只能看到自己的行。 它还遍历 app/Models/ 中的每个模型,以确认任何具有 workspace_id 的模型都使用 trait。

运行:

php artisan test --filter=MultiTenancyTest

每次 CI 构建都必须通过。破坏它的 PR 不能合并。