* 新增 README.zh-CN.md 汇总索引:ADR 状态表 + Stage 0-4 业务目标 / 技术路径 / 验证方式 + Stage↔ADR 对照矩阵 + 不可逆决策一览 + 用语映射 + FAQ + 阅读路径 * 新增 workspace-schema-design.zh-CN.md(Stage 0 schema 锁定版)一并入库 * 7 份 ADR 顶部加"代码命名"映射行(tenant_id ↔ workspace_id) * ADR-002 §1 加分期落地提示,明确 K8s 推迟到 Stage 3 * ADR-005 §5"第 1/2 阶段"补出与 rollout Stage 2/3 的映射 * ADR-007 §4 加 /api/v1/ 反向链接;§8 加 tid → wid 字段名映射 * headless-api §0/§7 把"SaaS + on-prem 双主线"改为"SaaS 主线、schema 兼容 on-prem" * phased-rollout 去除重复的"Go/No-Go 进入 Stage 2"段 Postgres 切换从 Stage 1 提前到 Stage 0:Stage 0 已要 ALTER 4 张表加 workspace_id,先 SQLite 再 PG 是纯返工;Stage 0 没有生产数据,迁移阻力最小。 同步调整 phased-rollout / headless-api / phase-0-plan / workspace-schema-design / README 中的时间盒(Stage 0: 3-4→4-5 周;Stage 1: 10-15→8-13 周)、不可逆决策 清单、PR 顺序、轨道前置依赖。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
28 KiB
Headless API · 业务系统集成轨道
写于 2026-05-09。承接 phased-rollout-by-scale.zh-CN.md。
触发:现已有 1-2 个明确的业务系统集成需求,1-3 个月内要 demo / 调通。集成形态包括 IM channels(已支持)+ 业务系统自研 web 页面。 商业形态:与 phased-rollout-by-scale §0 一致——中心化 SaaS 主线;schema / auth / quota 设计对 on-prem 友好(
workspaces.id映射到 self-host 安装),但 on-prem 不作为产品主线,仅按客户合同启用。 身份模式:service account 折叠 + external_user_id 透传,两种都支持,按 endpoint 选。 集成 pattern:Pattern A(业务系统 backend 代理)+ Pattern B(浏览器直连 + 短期 JWT)。不做嵌入式 widget。
0. 现状评估
DeerFlow 架构已经接近"可被业务系统调用的后端"——证据:backend/app/channels/ 下的 IM 集成(Slack / 飞书 / 钉钉 / Telegram)就是这种用法的活体范例。它们通过 langgraph-sdk 调 Gateway HTTP API + 把响应转给 IM 平台,根本不经过 frontend/ 工程。
已经具备(约 80%)
| 能力 | 实现位置 |
|---|---|
| 完整 REST API | backend/app/gateway/routers/(threads / runs / messages / events / feedback / models / skills / mcp / memory / uploads / artifacts) |
| LangGraph SDK 兼容路径 | /api/langgraph/* —— 任何 langgraph-sdk 客户端可直接接 |
| Streaming(SSE) | runs/stream + messages-tuple delta + values + custom |
| 嵌入式 Python 客户端 | packages/harness/deerflow/client.py DeerFlowClient |
| 现成的"无 web 前端"调用证明 | app/channels/manager.py 完全不依赖 frontend |
Stage 0 完成后还差什么
Stage 0 把 workspace 概念立起来了,但 auth 模式仍是 cookie + JWT + CSRF——这是为 web 前端设计的,不适合 server-to-server,更不适合"业务系统自研 web 页面浏览器直连"的场景。要让业务系统调,必须补两条平行 auth 路径 + 一组管理能力,详见 §1。
集成 pattern 速览
| Pattern | 链路 | 适用 | 优先级 |
|---|---|---|---|
| A. Backend 代理(默认) | browser → 业务系统 backend → DeerFlow API key → DeerFlow | 业务系统已有 backend;不在乎多一跳 | Stage 1 必做 |
| B. Browser 直连(streaming 友好) | 业务系统 backend 颁短期 JWT → browser 直连 DeerFlow /api/v1/*(含 SSE) |
chat / agent 类、对 token 流延迟敏感、自研 web 页面 | Stage 1 末必做 |
| DeerFlow 托管 chat widget URL,业务系统 embed | 集成方零前端开发 | 不做(产品决定) |
Pattern A 是"server-to-server",Pattern B 是"browser-to-server"。两者共用同一组 service account / API key 数据模型,只是 auth 路径不同:
- Pattern A:
Authorization: Bearer dfk_live_<long-lived API key> - Pattern B:
Authorization: Bearer eyJ<short-lived JWT>
1. 改造清单(按 MVP 优先级)
| # | 能力 | 是否 MVP | 缺它会怎么样 | 工作量 |
|---|---|---|---|---|
| 1 | API Key 认证(Pattern A) | 必须 | 业务系统 backend 没法调,跨域 + CSRF 灾难 | M |
| 2 | Service Account 概念 | 必须 | API key 必须挂在某个"账户"上做归属、计费、quota | M |
| 3 | CSRF bypass on bearer | 必须 | bearer 路径走 CSRF middleware 直接 403 | XS |
| 4 | External User ID 透传 | 必须 | 业务系统的"小明"在 DeerFlow 内不能体现,memory / 个性化失效 | M |
| 5 | API 版本化 /api/v1/ |
必须(早做便宜) | 演进时业务系统要全量改 | S(早做)/ L(晚做) |
| 6 | Rate limit per API key | 必须(基础版) | 业务系统 bug 把 DeerFlow 打爆 | S(基础)/ M(分层) |
| 7 | Token Exchange(Pattern B) | 必须 | 浏览器只能走 backend 代理,自研 web 页面延迟差 | S |
| 8 | CORS 中间件 + per-workspace allowed_origins | 必须(与 Pattern B 配套) | 浏览器请求被同源策略挡 | M |
| 9 | 短期 JWT 验证路径(AuthMiddleware 第三条) | 必须(与 Pattern B 配套) | 短期 JWT 没法验 | S |
| 10 | Idempotency keys | 推荐 | 业务系统重试时重复建 thread/run | S |
| 11 | Webhook outbound | 推迟 | 业务系统轮询事件,多调几次 SSE 而已 | M(推到 Stage 2) |
MVP 包(前 9 项)= 4-5 周;放进 Stage 1 并行做。Pattern B(7-9)依赖 1-3 完成,建议 Stage 1 末(最后 1-2 周)。
2. 核心设计:API Key + Service Account
数据模型
service_accounts (
id UUID PK,
workspace_id UUID FK NOT NULL, -- 必属于一个 workspace
name VARCHAR(64), -- "X 业务系统集成"
role VARCHAR(16), -- 在 workspace 内的 role:member / admin
identity_mode VARCHAR(16), -- collapsed | external_passthrough | both
status VARCHAR(16), -- active / suspended / revoked
created_by UUID, -- 哪个 user 创建的(必须是 workspace owner/admin)
created_at, updated_at
)
api_keys (
id UUID PK,
service_account_id UUID FK NOT NULL,
key_prefix VARCHAR(16) UNIQUE, -- 前 16 字符明文(dfk_live_abc123...)UI 可显示
key_hash BYTEA NOT NULL, -- 完整 key 的 sha256,比对用
name VARCHAR(64), -- "生产环境 key" / "灰度 key"
scopes TEXT[], -- ["threads:read", "runs:create", "uploads:write"...]
rate_limit_rpm INT NULL, -- 每分钟请求数;NULL=用 workspace plan 默认
expires_at TIMESTAMP NULL, -- 可选过期时间
last_used_at TIMESTAMP NULL,
revoked_at TIMESTAMP NULL,
created_at
)
external_users ( -- ghost user,按需建(identity_mode=external_passthrough 时)
id UUID PK,
workspace_id UUID FK NOT NULL,
service_account_id UUID FK NOT NULL,
external_id VARCHAR(128) NOT NULL, -- 业务系统传过来的 ID,原样存
display_name VARCHAR(128) NULL,
metadata JSONB, -- 可选业务字段
created_at, last_active_at,
UNIQUE (workspace_id, service_account_id, external_id)
)
Key 格式约定
dfk_live_<24 字符随机> # 生产 key
dfk_test_<24 字符随机> # 测试 key
- 前缀
dfk_live_/dfk_test_让一眼区分环境(防止把测试 key 投进生产) - 写入 DB 时只存
sha256(key),明文创建后只能在 UI 显示一次 key_prefix列存前 16 字符(dfk_live_abc12345)—— UI 列表 + 审计日志可识别但不能用
认证流程
请求 → AuthMiddleware → 检测 Authorization header
│
├── "Bearer dfk_..."
│ ↓
│ APIKeyAuthBackend.authenticate
│ ↓
│ SELECT api_keys WHERE key_hash = sha256(token)
│ ↓
│ load service_account + workspace
│ ↓
│ set_current_workspace(workspace_id)
│ set_current_service_account(account)
│ set_current_user(None) # 没有真人 user
│ ↓
│ CSRFMiddleware skip(bearer 路径不要 CSRF)
│ ↓
│ 如有 X-External-User-Id header 且 identity_mode 允许:
│ → upsert external_users → set_current_external_user
│
└── "Cookie: access_token=..." → 走现有 web 前端流程
@require_permission 装饰器升级
# 现有签名(cookie 模式)
@require_permission("threads", "read", owner_check=True)
# 改为同时支持 service account 路径
@require_permission(
resource="threads",
action="read",
scopes=["threads:read"], # API key 必须有此 scope
owner_check="workspace_or_user", # SA: 校验 workspace 归属;user: 校验 user_id
)
3. 核心设计:两种身份模式
模式 A:service account 折叠(identity_mode=collapsed)
业务系统调一切操作都归到该 service account 名下。适合工具类集成(CRM 自动总结、安全面板分析)。
POST /api/v1/threads
Authorization: Bearer dfk_live_xxx
→ 创建 thread,所有归属都是 service_account_id
threads_meta.user_id = NULL
threads_meta.service_account_id = <SA.id>
threads_meta.workspace_id = <SA.workspace_id>
memory 也是 service account 共享的(workspaces/{wid}/service_accounts/{sa_id}/memory.json)。
模式 B:external_user_id 透传(identity_mode=external_passthrough)
业务系统的"小明"在 DeerFlow 内独立。适合 chatbot / 助手类集成。
POST /api/v1/threads
Authorization: Bearer dfk_live_xxx
X-External-User-Id: bizsys_user_42 # 业务系统的用户 ID
→ AuthMiddleware:
1. 解 API key → load SA
2. 看 SA.identity_mode 允许 external_passthrough
3. SELECT external_users WHERE (workspace_id, service_account_id, external_id="bizsys_user_42")
4. 没找到 → 自动建 ghost external_user
5. set_current_external_user(...)
→ 创建 thread:
threads_meta.workspace_id = <SA.workspace_id>
threads_meta.service_account_id = <SA.id>
threads_meta.external_user_id = <external_users.id>
memory 是 per external_user 的(workspaces/{wid}/service_accounts/{sa_id}/external_users/{eu_id}/memory.json)。
模式选择策略
SA.identity_mode 三态:
collapsed:忽略所有X-External-User-Idheaderexternal_passthrough:必须传X-External-User-Id,缺失时 400both:传了走透传、不传走折叠(最灵活但最复杂;建议默认不开放)
业务系统接入时由 workspace owner 创建 SA 时选定。
这影响哪些 endpoints
| endpoint | service account 折叠 | external_user_id 透传 |
|---|---|---|
POST /threads |
thread 归 SA | thread 归 external_user |
GET /threads |
列出 SA 的所有 thread | 仅列出该 external_user 的 thread |
| memory 注入 | SA 共享 memory | 该 external_user 的 memory |
usage_daily 写入 |
(workspace_id, SA_id, "main", ...) |
同上 + external_user_id 维度 |
| feedback | feedback.user_id 留空,标 SA | feedback.external_user_id 标人 |
3.5 核心设计:Pattern B(Browser 直连 + 短期 JWT 交换)
为什么需要
业务系统自研的 web 页面如果走 Pattern A(Backend 代理),他们 backend 必须实现 SSE 流式转发——这是个不小的工程量,且每个 token 多一跳延迟。Pattern B 把 streaming 直接交给浏览器,业务系统 backend 只做一次性 token 颁发。
数据模型扩展
-- workspaces 表加列
workspaces.allowed_origins TEXT[] -- ["https://app.partner.com", "https://staging.partner.com"]
-- 不需要新表;短期 JWT 不持久化(足够短就不需要 revoke list)
Token Exchange 流程
[业务系统 backend] [DeerFlow] [浏览器]
│ │ │
│ 1. 用户在业务系统登录 │ │
│ ◄────────────────────────────────│────────────────────────────────│
│ │ │
│ 2. 业务 backend 鉴权后调 │ │
│ POST /api/v1/auth/exchange-token │
│ Authorization: Bearer dfk_live_xxx │
│ { external_user_id: "ming_42", expires_in: 600 } │
│ ────────────────────────────────►│ │
│ │ │
│ │ 3. 校验 API key + SA 状态 │
│ │ upsert external_users 行 │
│ │ 签短期 JWT │
│ │ │
│ { access_token: "eyJ...", │ │
│ expires_at: "..." } │ │
│ ◄────────────────────────────────│ │
│ │ │
│ 4. 把 access_token 发给浏览器 │ │
│ ─────────────────────────────────────────────────────────────────►│
│ │ │
│ │ 5. browser 直连 DeerFlow │
│ │ GET /api/v1/threads/.../events│
│ │ Authorization: Bearer eyJ...│
│ │ Origin: https://app.partner.com│
│ │ ◄──────────────────────────────│
│ │ │
│ │ 6. CORS preflight 通过 │
│ │ SSE stream 200 │
│ │ ──────────────────────────────►│
短期 JWT 设计
# 不复用现有 cookie JWT 的 TokenPayload,新增 ServiceTokenPayload
class ServiceTokenPayload(BaseModel):
sub: str # external_user_id(DeerFlow 内部 id,不是业务方原始 id)
sa: str # service_account_id
wid: str # workspace_id
eid: str # external_id(业务方原始 id,传给 audit log)
scopes: list[str] # 从 api_key.scopes 继承(不能放大)
exp: int # 5-15 min(默认 10 min)
iat: int
iss: "deerflow" # 区分自签 vs 业务方签
typ: "service" # 区分 cookie JWT(typ=user)
Endpoint 规格
POST /api/v1/auth/exchange-token
Authorization: Bearer dfk_live_xxx (API key)
Content-Type: application/json
Request:
{
"external_user_id": "ming_42", // 必填(identity_mode=external_passthrough)
"expires_in": 600, // 可选,默认 600s,最大 3600s
"scopes": ["threads:write", "runs:read"] // 可选;省略则继承 API key 全部 scopes
}
Response 200:
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_at": "2026-05-09T15:20:00Z"
}
Errors:
- 401 invalid API key
- 403 SA suspended / API key revoked
- 400 identity_mode 不允许 external_user_id(collapsed 模式)
- 400 scopes 超出 API key 授权
AuthMiddleware 第三条路径
请求 → AuthMiddleware → 检测 Authorization
│
├── "Bearer dfk_..." → APIKeyAuthBackend (Pattern A)
├── "Bearer eyJ...typ=service" → ServiceTokenAuthBackend (Pattern B) ← 新增
└── "Cookie: access_token=..." → CookieAuthBackend (web 前端)
ServiceTokenAuthBackend.authenticate:
- 验 JWT 签名(DeerFlow 自签私钥;HS256 即可,不需要 RSA)
- 检
iss=deerflow, typ=service(防止把 cookie JWT 误用) - 校
saservice account 状态(可能在签发后被 suspend) - 校 scope 子集合法(不能超过 SA 当前 scopes)
set_current_workspace(wid)+set_current_service_account(sa)+set_current_external_user(sub)
CORS 设计
# CORSMiddleware 在 AuthMiddleware 之前加载(FastAPI 中间件顺序)
class WorkspaceAwareCORSMiddleware:
async def dispatch(self, request, call_next):
origin = request.headers.get("origin")
if not origin:
return await call_next(request) # 非浏览器请求
# 解析当前 workspace(从 token 或 query string)
# CORS preflight (OPTIONS) 没有 token,要从其他维度推
# 简化方案:所有 /api/v1/* 路径在 OPTIONS 时回 wildcard,但不带 credentials;
# 真请求时按 token 上的 wid 查 workspaces.allowed_origins 做精确匹配
...
关键安全点:
Access-Control-Allow-Credentials: false(短期 JWT 不依赖 cookie,不需要 credentials;防止意外打开 cookie 跨域)Access-Control-Allow-Origin精确匹配,不用通配Access-Control-Max-Age短一点(5 分钟),方便切换 origin 时不被缓存卡
与现有 CSRF 的关系
CSRFMiddleware 检测到 Authorization: Bearer ... 直接 skip——Pattern A 和 Pattern B 都走同一条 skip 逻辑。CSRF 仅对 cookie 路径生效。
Token revoke 策略
短期 JWT 默认不显式 revoke(5-15 min 过期,自然失效)。但有三个例外:
- SA
status=suspended→ ServiceTokenAuthBackend 在 step 3 检测时直接拒 - API key 被
revoked_at→ 同上(短期 JWT 验证时关联回sa.api_key) - 如果客户提需要立即 revoke 用户访问 → 业务系统调 DeerFlow
POST /api/v1/external-users/{id}/revoke-tokens把external_users.token_versionbump(exchange-token签发时把它写进 JWT,验证时比对)
第 3 条是 nice-to-have;MVP 不做,等业务方明确提需求再加。
Pattern B 不做的事(重要)
- 不让浏览器直接持有 API key(
dfk_live_*)——再短的 TTL 也不行;API key 必须留在业务方 backend - 不支持 OAuth 2.0 完整 flow(authorize / consent / refresh)——太重,业务方 backend 自己做完用户认证再换 token 即可
- 不做客户端 SDK——给一份 OpenAPI + 浏览器原生 fetch / EventSource 例子,业务方自己接
4. 核心设计:API 版本化
早做的成本:仅是 app/gateway/routers/__init__.py 里 mount prefix 改 /api/v1。
晚做的成本:所有业务系统 client 全量改地址。
设计
# 现状
app.include_router(threads_router, prefix="/api/threads")
# Stage 1 改造
app.include_router(threads_router, prefix="/api/v1/threads")
# 同时保留 /api/threads → 转发到 v1(前端用),sunset 在 Stage 3
deprecation 策略:
/api/*(无版本)路径在 response 加X-API-Deprecated: 2027-01-01header- frontend 同步迁到
/api/v1 - LangGraph SDK 兼容路径
/api/langgraph/不带版本(跟随上游 SDK 约定)
版本演进规则:
- 加字段:v1 内做,不升 v2
- 改语义、删字段、改默认值:v2
- 整体 endpoints 重组:v2
5. 核心设计:Rate Limit + Idempotency
Rate Limit(基础版)
api_keys.rate_limit_rpm 设了值:用 key 自己的
没设:用 workspace plan 默认(free=60, pro=600, team=3000,可配)
实现:进程内 sliding window(依赖 Postgres 即可,不需要 Redis):
rate_limit_log(api_key_id, minute_bucket, count) 单独小表
进阶(Stage 2):分维度(per endpoint、per LLM、per sandbox quota)的复合 rate limit;引入 Redis。
Idempotency Keys
请求带 Idempotency-Key: <client-generated-uuid>
↓
SELECT idempotency_records WHERE (api_key_id, key=...) AND created_at > NOW() - 24h
↓
命中 → 直接返回上次 response(200 + body 原样)
未命中 → 处理请求 → 写 idempotency_records + 返回
只在写操作(POST / PUT / PATCH / DELETE)支持;GET 不需要。
6. 与 Stage 1 的整合
把 headless API MVP 包并入 Stage 1,时间盒 8-13 周(Postgres 切换已前移到 Stage 0,原"6-10 周 + 4-5 周 headless = 10-15"减去 Postgres 的 ~2 周)。
修订后 Stage 1 必做项
| 改动 | 类型 | 估工 |
|---|---|---|
| Quota 系统 + TokenUsage 持久化 | 原 Stage 1(Postgres 已就绪) | M+ |
| Stripe 基础订阅 | 原 Stage 1 | M |
| AioSandbox 出网/资源收紧 | 原 Stage 1 | M |
| API Key + Service Account 数据模型 + 仓储 | 新增(Pattern A) | M |
| APIKeyAuthBackend + AuthMiddleware 双路径 | 新增(Pattern A) | M |
| CSRF skip on bearer | 新增 | XS |
| External User ID 透传 + ghost user | 新增 | M |
/api/v1/ 版本化 |
新增(早做便宜) | S |
| Per-API-key rate limit(基础版) | 新增 | S |
@require_permission 装饰器升级支持 SA 路径 |
新增 | S |
| API key 管理 UI(workspace settings 内) | 新增(最低限:CLI 也行) | S(CLI)/ M(UI) |
POST /api/v1/auth/exchange-token endpoint |
新增(Pattern B) | S |
ServiceTokenAuthBackend(短期 JWT 验证) |
新增(Pattern B) | S |
workspaces.allowed_origins + CORS 中间件 |
新增(Pattern B) | M |
| Idempotency keys | 可选(推荐) | S |
合计原 Stage 1 不含 Postgres(M+M+M=3M)+ 新增 Pattern A(M+M+XS+M+S+S+S+S=4M)+ 新增 Pattern B(S+S+M=2M)= 约 9M ≈ 8-13 周。Pattern B 依赖 Pattern A 完成,建议放 Stage 1 末。
修订后 Stage 1 PR 顺序
轨道一:付费 SaaS 基础(Postgres 已在 Stage 0 切完,本轨道直接从 quota 起)
workspace_quotas/workspace_usage_daily表 + 仓储TokenUsageMiddleware升级为持久化QuotaMiddleware加入中间件链- Stripe webhook + 订阅状态同步
- AioSandbox 收紧
- 基础监控
轨道二:Headless API Pattern A(与轨道一并行;无前置依赖)
service_accounts+api_keys+external_users仓储(先于业务路径)APIKeyAuthBackend+AuthMiddleware双路径(cookie + bearer)- CSRF middleware skip on bearer
/api/v1/mount prefix 切换 + 旧路径兼容转发external_user_id透传机制(依赖 1-4)service_accounts.identity_mode三态行为分支@require_permission升级 + scope 校验- 基础 rate limit
- API key 管理 CLI + UI
- Idempotency keys(可选)
轨道三:Headless API Pattern B(依赖轨道二的 1-7 完成;建议 Stage 1 最后 1-2 周)
workspaces.allowed_origins列 + workspace settings UI 的 origin 管理WorkspaceAwareCORSMiddleware(在 AuthMiddleware 之前)POST /api/v1/auth/exchange-tokenendpoint +ServiceTokenPayload设计ServiceTokenAuthBackend(AuthMiddleware 第三条路径)- SSE 在 CORS 跨域下的 streaming 验证(写一个 fixture web 页面测)
- 业务方文档:从换 token 到浏览器直连的端到端示例(HTML + JS)
7. SaaS vs on-prem 差异(SaaS 是主线)
口径:phased-rollout §0 已明确"中心化 SaaS 主线,不做 self-host 主线"。本节列出如果未来按客户合同启用 on-prem 时的差异点——目的是让 Stage 0/1 的 schema 与 auth 设计不阻塞 on-prem,而不是把 on-prem 当作并行产品线投入资源。
| 能力 | SaaS 形态 | on-prem 形态(按合同启用) |
|---|---|---|
| API key 管理 | workspace settings UI + CLI | CLI 必须;UI 可选;env var 注入预置 key 也合理 |
| 配额 / 计费 | 按 plan,Stripe 同步 | 配额作为容量管理(防内部失控),不接 Stripe |
| KMS | AWS / 阿里云 KMS(Stage 2 后落) | 客户自带 KMS(HashiCorp Vault / 客户自有);env var 兜底(明文) |
| 监控 | 你们运维的 Grafana | 客户自有;DeerFlow 暴露 /metrics Prometheus 端点(Stage 2 加) |
| Webhook | 平台默认 | 客户内网回调;要求 url 可配置 |
| Rate limit | 平台分档强制 | 客户自定义;默认放宽 |
| 升级路径 | 你们灰度发布 | docker image tag + 升级文档;schema migration 跑通 |
重要:Stage 0 的 schema 设计已经兼容两者(workspace 是 self-hosted 时也 1 对 1 对应一个安装)。不需要为 on-prem 单独建分支。
on-prem 专属工作(Stage 1+ 一次性)
- docker compose 模板(已部分有,加 service_accounts seed 流程)
- "首次安装预置 1 个 workspace + 1 个 admin + 1 个 platform key" 的 init script
- 升级脚本(schema migrations 自动跑)
- 部署文档(一份就够)
工作量约 1 人周,可以在 Stage 1 末或 Stage 2 头做。
8. 不可逆决策(动手前想清楚)
| 决策 | 难回头的原因 |
|---|---|
API Key 格式(前缀 dfk_ / 长度 / 加密路径) |
业务系统接入后改格式所有 key 全失效 |
service_accounts 表是否能跨 workspace(暂定不允许) |
改了所有 quota / 计费归属逻辑 |
X-External-User-Id header 名 |
业务系统集成后改 header 名要全部联调 |
identity_mode 三态语义(collapsed / external / both) |
改语义要联调所有业务系统 |
/api/v1/ 是不是从 Stage 1 第 5 步开始;具体 deprecation 时间 |
业务系统接了之后再要求换 prefix 很不友好 |
| ghost user 自动创建策略(X-External-User-Id 缺失时拒绝还是 fallback collapsed) | 改了业务系统接入测试要重跑 |
| memory 隔离粒度(SA 共享 vs per external_user) | 一旦客户用上,迁移 memory 数据极麻烦 |
| 短期 JWT TTL 默认值(5 / 10 / 15 min)和 max | 业务系统集成后调短会断当前会话;调长会留更大被偷窃的窗口 |
ServiceTokenPayload 字段集(claim 名 / 是否带 eid) |
改 claim 名所有签名校验失败;早一点把审计需要的字段都设计进去 |
workspaces.allowed_origins 是 workspace 级还是 SA 级 |
暂定 workspace 级(更简单);改成 SA 级要拆数据 |
9. 推荐推进路径
第 0-1 周:
- 找现有需要集成的业务系统聊一下,把"两种身份模式哪个适合 / 是否有 webhook 需求 / 是否有 rate limit 偏好"问清楚
- 写一份 API key 创建 + 第一次 hello-world 调用的快速指南草稿(不写代码,纯设计验证)
第 2-3 周(Stage 0 末):
- Stage 0 收尾时把
service_accounts/api_keys/external_usersschema 也加上(schema 加上,路径不接)
第 4-13 周(Stage 1):
- 按上面的 13 步 PR 顺序推进
- 第 6-8 周可以拉一个业务系统做内测集成(用真实 API key 调真实 endpoint)
- 第 12-13 周收尾,给业务系统正式 go-live
与 Stage 1 业务客户上线的关系:
- Stage 1 一头是付费 SaaS 客户、一头是业务系统集成客户。两条产品线共用同一套 workspace + auth + quota,只是认证模式不同(cookie vs bearer)
- 因此架构上没有冲突,团队可以并行推
10. 与现有 ADR / rollout 的关系
| 引用 | 关系 |
|---|---|
| adr-007 §6 路由 | 路由层假设 cookie + JWT;本文档加了 bearer + API key 平行路径 |
| adr-007 §8 Auth | TokenPayload 扩 tid/role 已对齐;本文档新增 API key 路径不复用 JWT |
| adr-004 RBAC | service_accounts.role 复用 owner/admin/member;不另建 role 体系 |
| adr-003 §4.4 quota | quota 写入按 SA 归属时只看 workspace_id;按 external_user 归属时多一维 |
| phased-rollout-by-scale Stage 1 | 本文档 §6 给出 Stage 1 修订后必做项与 PR 顺序 |
| stage-0-code-map | Stage 0 schema 时把 service_accounts 也加上不阻塞 |