docs(multi-tenant): 据审计对齐 ADR-002/003/004/005 与 phase-0 计划

非 spike 驱动的对齐改动:
- ADR-002/004/005:头部加现状提示 + 链接审计报告(沙箱出网/资源缺位、
  token_version 已存在 MembershipCache 全新建、ObjectStorage/7 表/KMS
  全部从 0 起)
- ADR-003 LLM 计费:修正 TokenUsageMiddleware 当前只 log 不持久化的描述;
  补充 create_chat_model sync→async 改造的连带影响说明
- phase-0 计划:新增 §3.5 底座先行(Postgres 测试夹具 / ObjectStorage
  Protocol / KMS 抽象 3 件并行做),时间盒 2 → 3 周;ADR-006/007 摘要
  对齐;DoD 加底座骨架检查项

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1445043649
2026-05-09 21:03:32 +08:00
parent 27c4f14233
commit 8bdd308ea0
5 changed files with 38 additions and 12 deletions
@@ -6,6 +6,7 @@
| 决策日期 | TBD |
| 决策者 | 安全 + 架构 + SRE |
| 关联 ADR | ADR-001 数据隔离、ADR-005 存储拓扑 |
| 关联审计 | [adr-vs-code-audit](./adr-vs-code-audit.zh-CN.md) — 注意:现有 `AioSandboxProvider` 出网/资源/cosign 缺位;K8sSandboxProvider 几乎从零开工(实际工作量大于本 ADR §5 估算) |
---
@@ -2,10 +2,11 @@
| 项目 | 内容 |
|---|---|
| 状态 | 草稿(Draft |
| 状态 | 草稿(Draft · 2026-05-09 据审计修订 §4.2 / §4.3 / §4.4.2`create_chat_model` 是 sync、`TokenUsageMiddleware` 不持久化) |
| 决策日期 | TBD |
| 决策者 | 产品 + CTO + 财务 |
| 关联 ADR | ADR-001 数据隔离、ADR-005 存储拓扑、ADR-006 运行时与渠道 |
| 关联审计 | [adr-vs-code-audit](./adr-vs-code-audit.zh-CN.md) |
---
@@ -115,6 +116,8 @@ async def create_chat_model(
return reflect(model_config.use)(api_key=api_key, ...)
```
> **sync → async 的连带影响**:当前 `create_chat_model` 是同步函数(`models/factory.py:50`)。改成 async 后所有调用点(lead_agent factory、`MemoryMiddleware` / `TitleMiddleware` / `SummarizationMiddleware` 等)都要同步改 await——这是一次跨多个文件的改动,不是单点 patch。phase-1 实现时按"factory 改 async + 一次性扫所有调用点 await"作为单个 PR 落地,不要分批,避免中间态不可运行。
### 4.3 Quota 表与 Usage 表(ADR-005 已含)
```sql
@@ -138,7 +141,7 @@ tenant_usage_daily (
写入时机:
- token现有 `TokenUsageMiddleware` 改造,写入按 (tenant_id, model, date) 累加
- token**当前 `TokenUsageMiddleware` 只 log 不持久化**`agents/middlewares/token_usage_middleware.py:268-275`);本 ADR 要求新增持久化路径,按 (tenant_id, model, date) 累加,并配合 `usage_category` 区分主对话 / 内部任务(参 ADR-006 §2.5
- 沙箱 CPU 秒:K8s metrics-server / Prometheus 抓取,每 5 min 聚合写入
- 并发 runs`RunManager` 启动/结束时增减计数器
@@ -188,7 +191,7 @@ tenant_usage_daily (
#### 4.4.2 流式 token 的提交时机
LangGraph SDK 的 `messages-tuple` 流模式按 chunk 推 delta。当前 `TokenUsageMiddleware``after_model` 一次性提交——多租户后这有两个隐患:
LangGraph SDK 的 `messages-tuple` 流模式按 chunk 推 delta。当前 `TokenUsageMiddleware``after_model` 一次性 **log**(不持久化)——多租户加上持久化后,这有两个隐患:
1. **客户端 abort 流时漏记**:用户关浏览器、SSE 断开 → middleware 没收到 `after_model` → token 漏算
2. **provider 本身的 usage 帧晚到**OpenAI/Anthropic 把 usage 放在最后一个 chunkStreamBridge 必须在 finalizing 时强制等这一帧
@@ -6,6 +6,7 @@
| 决策日期 | TBD |
| 决策者 | 产品 + 后端 lead |
| 关联 ADR | ADR-001 数据隔离、ADR-003 LLM Key 与计费 |
| 关联审计 | [adr-vs-code-audit](./adr-vs-code-audit.zh-CN.md) — 现状:`users.token_version` + JWT `ver` claim 已存在;`@require_permission` 装饰器存在但 `owner_check` 是 bool 需扩为 enum`MembershipCache` 30s LRU 全新建 |
---
@@ -6,6 +6,7 @@
| 决策日期 | TBD |
| 决策者 | 架构 + 后端 lead + SRE |
| 关联 ADR | ADR-001 数据隔离、ADR-002 沙箱隔离、ADR-006 运行时与渠道 |
| 关联审计 | [adr-vs-code-audit](./adr-vs-code-audit.zh-CN.md) — 现状:`ObjectStorage` Protocol、7 张新表、KMS 抽象**全部不存在**,本 ADR 描述的是从 0 起的设计;phase-0 §3.5 已加"底座先行"骨架要求 |
---
@@ -14,7 +14,9 @@
| [ADR-004 租户 ↔ 用户层级](./adr-004-tenant-rbac.zh-CN.md) | ADR | 产品 | 锁定二级 RBAC + JWT/cache 一致性策略 |
| [ADR-005 存储拓扑](./adr-005-storage-topology.zh-CN.md) | ADR + ObjectStorage 接口签名 | 架构 + SRE | 锁定 DB / 对象存储 / 临时区分层 |
| [ADR-006 运行时与渠道租户化](./adr-006-runtime-channel-tenancy.zh-CN.md) | ADR | 后端 lead + 渠道 owner | 锁定 checkpointer / MCP cache / skills loader / 内部 LLM 计费 / IM 渠道 ↔ 租户 |
| [ADR-007 路由与前端租户化](./adr-007-routing-frontend.zh-CN.md) | ADR | 前端 lead + 后端 lead | 锁定 URL 形态 / cookie / Better Auth / SDK 切换 |
| [ADR-007 路由与前端租户化](./adr-007-routing-frontend.zh-CN.md) | ADR | 前端 lead + 后端 lead | 锁定 URL 形态 / cookie / 自签 JWT 扩字段 / SDK 切换 |
| [adr-vs-code-audit](./adr-vs-code-audit.zh-CN.md) | 审计报告 | 架构 | 7 份 ADR 对照现状代码的差异清单(已据其修订 ADR-001/006/007 |
| [adr-spike-langgraph-postgres](./adr-spike-langgraph-postgres.zh-CN.md) | spike 报告 | 架构 + 后端 lead | 验证 `langgraph-checkpoint-postgres==3.0.5` 的注入能力,结论改写 ADR-001 §4.1 / ADR-006 §2.1 |
| Tenant 数据模型设计 | DB schema 草稿 + ER 图 | 后端 lead | 第 1 阶段直接落地用 |
| 多租户改造代码盘点 | 表格 / spreadsheet | 后端 lead | 估工 + 拆 PR 用 |
@@ -91,8 +93,8 @@
要点:
- **LangGraph checkpointer**:保留原表结构,靠 `thread_id ∈ threads_meta(tenant_id=...)` 子查询 RLS 兜底;连接注入 `SET LOCAL app.tenant_id` 走自定义 connection factory
- **MCP 工具缓存**:模块级单例 → per-tenant LRUOAuth token 从文件挪`tenant_secrets`
- **LangGraph checkpointer**:保留原表结构、**不挂 RLS、不 ALTER 表**——`langgraph-checkpoint-postgres==3.0.5` 不存在 `connection_factory`spike 验证),改用入口路由(`threads.py` + `thread_runs.py`)强校验 + `threads_meta` `UNIQUE(tenant_id, thread_id)` 兜底。详见 ADR-001 §4.1.1
- **MCP 工具缓存**:模块级单例 → per-tenant LRUOAuth token 当前**进程内存无持久化**,要直接做"持久化 + 加密 + 失败回退"三步并发`tenant_secrets`
- **Skills loader**:拆 platform 共享只读 + tenant 私有;按 `tenant_skill_state.enabled` 过滤工具。
- **Sandbox provider**:实例单例,`acquire(thread_id)` 内按 tenant 路由到 K8s namespaceprewarm 池按 plan 大小。
- **内部 LLM 计费**Memory / Title / Summarization 调用都算 tenant 用量,分 `usage_category` 报表展示。
@@ -107,7 +109,7 @@
- **URL 形态**`/{slug}/...`,子域名留给 v2 自定义域名。
- **Cookie**`Path=/` + JWT 内 `tid`;切换 tenant 重签 JWT + 硬刷新。
- **前端**`app/(tenant)/[slug]/layout.tsx` 注入 `TenantProvider`SDK 实例单例但调用读 `useTenant()`;切换时 `cancelAllStreams + window.location.assign`
- **Better Auth**登录后跳 picker / 直进 / onboardingsession 注入 `{tid, role, tv}`
- **Auth**:扩现有 `app/gateway/auth/jwt.py` `TokenPayload``{tid, role}` 字段(沿用已有 `ver` 失效机制),不引入 Better Auth登录后跳 picker / 直进 / onboarding。
要点:
@@ -218,6 +220,22 @@ grep -rn "extensions_config\|skills/public\|skills/custom" backend/
---
## 3.5 底座先行(Phase-0 之前 / 并行的基础设施)
> 来源:[adr-vs-code-audit](./adr-vs-code-audit.zh-CN.md) cross-cutting risk #2 — ADR-001 RLS、ADR-003 secret vault、ADR-005 三层存储、ADR-006 OAuth 持久化共用同一组缺失底座。这组**必须先于任何业务改造落地**,否则各 ADR 互为前置条件死锁。
| 底座 | 缺失现状 | 为何阻塞 ADR | Phase-0 内必须产出 |
|---|---|---|---|
| **Postgres 测试夹具**testcontainers + RLS smoke 测试基础设施) | 仓库当前以 SQLite 为默认后端,`tests/` 下无 Postgres fixtureSQLite 不支持 RLS | ADR-001 / 004 / 005 的所有租户隔离测试都要 Postgres | testcontainers 集成 + 至少 1 个 RLS 冒烟测试模板 + CI 跑通 |
| **ObjectStorage Protocol + 实现** | `backend/packages/harness/deerflow/` 内 grep 不到 `ObjectStorage` 类;当前 memory/uploads/artifacts 全走文件系统 | ADR-005 §2 的三层拓扑、ADR-006 §2.2 的 OAuth 持久化都依赖它 | Protocol 接口 + LocalObjectStorage 骨架(可不实现 S3,留接口) |
| **KMS / Secret Vault 抽象** | 当前没有 secret 加密层;`mcp/oauth.py` 的 token 是明文进程内存 | ADR-003 §4.6 BYO key、ADR-006 §2.2 MCP OAuth、ADR-007 channel binding token 共用 | 抽象接口(envelope encryption pattern+ 本地 dev 实现(明文 fallback + 警告日志),生产实现可推迟 |
**时间盒**:3 项底座**与 ADR 评审并行做**,加 1 周到 Phase-0(总计 3 周封顶)。完成验证标准是这 3 件事**至少有可 CI 验证的最小骨架**——不要求 100% 实现,但接口 + 1 个测试用例必须跑通。
> 这部分原稿没列。审计后补的。如果跳过这步直接做业务改造,ADR-001/003/005/006 实现时会发现互相依赖、谁都跑不起来。
---
## 4. 第 0 阶段的"完成定义" (DoD)
走完这阶段,团队应该能回答:
@@ -229,6 +247,7 @@ grep -rn "extensions_config\|skills/public\|skills/custom" backend/
- [ ] Skill / agent / 上传 / 产物 / memory 各自存哪、丢失怎么办 → ADR-005 给出
- [ ] LangGraph / MCP / 内部 LLM / IM 渠道这些"夹层"怎么按租户隔离 → ADR-006 给出
- [ ] 浏览器地址栏长什么样、Cookie 怎么 scope、租户切换怎么走 → ADR-007 给出
- [ ] **3 项底座**Postgres 测试夹具 / ObjectStorage Protocol / KMS 抽象)有可 CI 验证的最小骨架 → §3.5 给出
- [ ] 第一阶段 PR 怎么拆、估几人周 → 代码盘点给出
- [ ] 第一个内测客户长什么样、什么时候能上 → 项目经理排期
@@ -236,12 +255,13 @@ grep -rn "extensions_config\|skills/public\|skills/custom" backend/
## 5. 时间盒与节奏
第 0 阶段 **周封顶**,再长就是过度设计。
第 0 阶段 **周封顶**(原稿两周,加 §3.5 底座先行的 1 周),再长就是过度设计。
- **第 1 周**:写 ADR-001/002/003/004 草稿,团队读、challenge、收敛
- **第 2 周**:定 schema、做代码盘点、估工、定第一阶段范围与 design partner 客户
- **第 1 周**:写 ADR-001 ~ 007 草稿,团队读、challenge、收敛
- **第 2 周**:定 schema、做代码盘点、估工、定第一阶段范围与 design partner 客户;同时启动 §3.5 底座 spikePostgres testcontainers / ObjectStorage Protocol / KMS 抽象)
- **第 3 周**:底座骨架 PR 合入 + ADR 据实测结果定稿(这一周已经在审计 + spike 中部分提前消耗,参见 [adr-vs-code-audit](./adr-vs-code-audit.zh-CN.md) 与 [adr-spike-langgraph-postgres](./adr-spike-langgraph-postgres.zh-CN.md)
如果周后还有 ADR 定不下来,**绝大多数情况是因为缺一个真实客户做参照**——这时候应该先去签一个 design partner(哪怕免费),用他们的合同和合规要求来反推决策。
如果周后还有 ADR 定不下来,**绝大多数情况是因为缺一个真实客户做参照**——这时候应该先去签一个 design partner(哪怕免费),用他们的合同和合规要求来反推决策。
---
@@ -251,7 +271,7 @@ grep -rn "extensions_config\|skills/public\|skills/custom" backend/
| 决策 | 默认值 | 选它的理由 |
|---|---|---|
| 数据隔离 | 行级 + Postgres RLS含 LangGraph 表 subquery RLS | 改造成本低,DeerFlow 现状几乎平行扩展 |
| 数据隔离 | 行级 + Postgres RLS仅 DeerFlow 自有表)+ LangGraph 表应用层强校验 | 改造成本低;LangGraph 表无 RLS hookspike 已验证),应用层兜底 |
| 沙箱隔离 | K8s namespace + gVisor + NetworkPolicy 默认禁出网 | 强度足够 + 运维可控 |
| LLM Key | 混合:默认平台 key + 限额,premium 切 BYO;悲观预扣防超额 | 体验与成本兼顾 |
| 租户层级 | 二级 RBACowner/admin/member+ JWT/cache 双层 | 为 SSO 和企业销售留口 |