# sundynix-agentix · 多租户 SaaS 化设计（收敛版）

> 版本：v1 ｜ 日期：2026-07-06
> 前提（已确认）：**① 目标是真·多租户商业化 ② 桌面端仍是主产品**（不做 Web-first 重写）
> 基线：`ARCHITECTURE_DESIGN.md`（当前架构）+ `ARCHITECTURE_REVIEW.md`（评审）
> 立场：这是对 `sundynix-agentix-saas-refactor-plan.md`（GPT 方案）的**收敛替代**——保留其正确诊断，
> 砍掉与本项目阶段/定位不符的过度设计。原则：**保架构、按需求拉动、每步可回退，不一次大重构。**

---

## 0. 一句话设计

**不改架构骨架，只往里穿一条"多租户 + 计费"的脊柱。** 桌面端保持主产品、变租户感知；加一个很薄的
Web 面做注册/计费/团队（桌面端不适合放这些）。后端按"地基→用量→enforcement→支付"分阶段长出 SaaS 能力，
**每一块都由验证过的需求拉动，而不是提前把 7 个服务的底座全建出来。**

---

## 1. 与 GPT 方案的取舍（明确保留 / 砍掉）

| GPT 方案主张 | 我的判断 | 理由 |
|---|---|---|
| **tenant_id 全链路前置** | ✅ **采纳，最高优先** | 现在便宜、以后要命；多租户地基 |
| **Usage Meter 先落事件（不接支付）** | ✅ **采纳** | 事件溯源式用量→再计费，顺序对；现 billing 太薄 |
| **NATS subject 规范化 + Envelope(带 tenant_id)** | ✅ **采纳（轻量）** | 做多租户本就要给消息带 tenant，顺手；但**不拆多根总线** |
| **AutoMigrate → migration 工具** | ✅ **采纳** | 生产必须、可控变更 |
| Web App 主产品化（重写前端） | ❌ **砍掉** | **你定了桌面端为主**；改为桌面端租户感知 + 薄 Web 面 |
| 拆 7 个物理服务（task-runtime/model-gateway/tool-policy 独立服务） | ⚠️ **降级为"包/边界"，不拆服务** | 单团队扛不动 7 服务运维；先做成 dispatcher 内的清晰模块，真扛不住再拆 |
| Task Runtime 独立层 + Temporal | ❌ **砍掉（现阶段）** | 现有 FSM+JetStream+checkpoint 够用；加 cancel/retry 收口即可，别上 Temporal |
| Remote MCP Gateway（OAuth/registry 整套） | ❌ **砍掉（现阶段）** | 远程 MCP 市场是远期产品赌注，无近期需求不建 |
| Tool Policy 强制拦每次工具调用 | ⚠️ **降级** | 内部工具先只做 tenant 作用域 + 用量计数；策略引擎等有不可信工具再上 |

**核心分歧**：GPT 把它当"推翻重来做成标准 SaaS 平台"；我把它当"给现有平台穿一条多租户脊柱"。

---

## 2. 多租户数据模型（Phase 1 地基）

### 2.1 层级
```
Tenant(组织/账户，计费单位)
  └── Workspace(工作区)
        └── Project(项目)
              └── Task / KB / Agent / ...
User ── (via TenantMember, 带 role) ── Tenant
```
桌面端主产品下，多数个人用户 = 一个单人 Tenant（免费档）；团队/企业 = 多成员 Tenant。

### 2.2 新增表（雪花 id + BaseModel，沿用 `sundynix_` 前缀）
- `sundynix_tenant`(id, name, slug, plan, status)
- `sundynix_tenant_member`(tenant_id, user_id, role, status) — 唯一(tenant_id,user_id)
- `sundynix_workspace`(id, tenant_id, name, created_by)
- `sundynix_project`(id, tenant_id, workspace_id, name)

### 2.3 tenant_id 全链路（这是重点，也是最大工作量）
现有 owner_id 隔离 → 升级为 tenant 作用域。**所有核心表加 `tenant_id`**（Task/Eval/KB/Doc/DocLink/Agent/Prompt/AuditLog/GuardrailEvent…）+ 索引。隔离规则：

| 层 | 现状 | 加 tenant |
|---|---|---|
| PostgreSQL | owner 过滤 | 全表加 tenant_id，**所有查询默认带 tenant_id**（用 gorm scope 统一强制，防漏） |
| Redis key | 无前缀 | `sundynix:{tenant_id}:...`（限流/流/会话） |
| NATS envelope | 裸 payload | 统一 Envelope 带 tenant_id（见 §3） |
| MinIO path | 平铺 | `tenants/{tenant_id}/workspaces/{ws}/projects/{proj}/...` |
| Milvus | kb 字段 | collection 不变，加 tenant 到 metadata filter（或 partition） |
| Neo4j | kb 属性 | 节点/关系加 `tenant_id` 属性 |

> ⚠️ 最易漏的是"查询默认带 tenant_id"。用一个**统一的 gorm scope + code review 清单**强制，别靠人肉记。

### 2.4 RBAC（Phase 1 同期）
角色：`owner / admin / member / viewer / billing_admin`。权限动作（示例）：
`tenant.manage / billing.manage / workspace.create / project.create / task.run / task.approve / model.manage / tool.use / kb.write / audit.read`。
实现：中间件从 JWT 取 user → 查 TenantMember 得 role → 按 role→permission 表判权。
`RequireAdmin` 升级为按 `tenant.manage` 权限判定（替代现在的开发期放行 + env 白名单）。

---

## 3. NATS Envelope + subject（Phase 1，轻量）
**不拆总线**（保持单 NATS，HA 是 T3）。只做两件卫生：
- 统一 `Envelope{id,type,version,tenant_id,workspace_id,user_id,task_id,trace_id,created_at,payload}` 包裹消息。
- subject 按语义规范前缀（现已半规范）：`cmd.* / events.* / streams.* / tools.* / control.* / health.*`。
命令/事件区分先做最小：任务提交=command，状态变更落 `task_events`（见 §4）。

---

## 4. 任务运行收口（Phase 1，轻量——不做独立服务）
不建 Task Runtime 服务、不上 Temporal。现有 FSM + JetStream + checkpoint 上补：
- 加 `task_events`(tenant_id, task_id, run_id, event_type, payload, trace_id) 事件日志（可回放 + 计费溯源）。
- 补 **cancel / retry**（现有 resume/超时已在）——在 dispatcher 消费侧加取消信号 + 幂等重试。
- 这些是 dispatcher 内的能力增强，不新增服务。

---

## 5. Usage & Billing（Phase 2，事件优先、不急接支付）
### 5.1 先落用量事件
`sundynix_usage_event`(tenant_id, user_id, task_id, run_id, resource_type, quantity, unit, unit_price, cost, currency, created_at)。
计量点：**模型 token（in/out/cached）· 工具调用次数 · 沙箱时长 · 解析页数 · 入库量 · 存储 · 任务时长**。
### 5.2 聚合
`sundynix_usage_daily_rollup`(tenant_id, date, resource_type, quantity, cost) —— 按天汇总，admin 展示成本。
### 5.3 分阶段
`①仅记 usage_event → ②按天 rollup + 后台成本可视 → ③套餐 quota + 超额限制 → ④接支付 → ⑤企业账单`。
**现在只做 ①②**（把已有的 Redis token 计数升级为落库的 usage_event）。

---

## 6. Model Gateway（Phase 4，扩展现有、不重建服务）
不拆独立服务。把现有 `llm.Pool`（已有 failover/熔断/缓存/热配置）**扩成 model runtime**：
- 每次调用 emit `usage_event`（token + 成本，按 tenant）。
- 按 tenant 套餐限制可用模型 + quota；BYOK（租户自带 key）。
- Provider Adapter 接口化（当前是 openai-compatible 单形态）便于接 vLLM/Ollama/未来 Claude。
真扛不住/要独立扩缩容再拆服务。**先在包边界内做清楚。**

---

## 7. Tool Policy（Phase 4，降级版）
不建独立 Policy 服务、不强制拦内部工具。分阶段：
- **现在**：工具调用带 tenant 作用域 + 计 usage_event（复用 §5）。
- **有不可信工具时**（远程 MCP / shell / 本地连接器）：再上 allow/deny/approval/quota 的策略前置 + `tool_call_event` 审计。

---

## 8. 客户端（Phase 3，桌面端为主 + 薄 Web 面）
**不重写 Web App。**
- **桌面端（主产品）**：变**租户感知**——登录后带 tenant 上下文；所有数据 tenant 作用域；工作区/项目切换；团队成员看到共享资源。原生能力（文件/另存为）不变。
- **管理端（现有 React）**：长出**租户/成员/套餐/用量账单/审计**管理（运维 + 商业后台合一）。
- **新增薄 Web 面**（只做桌面端不适合的）：注册/登录/组织创建、**订阅计费、团队邀请**——因为这些要在装桌面端**之前**就能用，且计费/组织管理放桌面端别扭。技术上可以是管理端的一部分，或一个极简独立页，**不是完整产品重写**。

---

## 9. 分阶段落地（我的收敛排序 vs GPT 的全家桶）

| 阶段 | 内容 | 为什么这个顺序 |
|---|---|---|
| **P1 地基**（先做，最关键） | tenant/workspace/project 表 + **tenant_id 全链路** + RBAC + 各存储隔离 + migration 工具 | 没这个，多租户什么都无从谈；schema 越小越便宜 |
| **P2 用量** | usage_event + daily rollup（模型/工具/存储计量），admin 成本可视 | 计费前必须先有可信用量；低风险 |
| **P3 客户端租户化** | 桌面端 tenant 上下文 + 管理端长出租户/账单页 + 薄 Web 注册/计费面 | 有了后端隔离才谈得上前端多租户；不重写 |
| **P4 enforcement** | 套餐 quota + model runtime 成本/quota/BYOK + 工具 tenant 作用域 | 有用量数据后才谈限额/套餐 |
| **P5 支付 + 硬化** | Stripe 接入 + NATS 集群 + DB HA + TLS（= T3） | 有真实付费流量再付分布式硬化的税 |

**明确不做（现阶段）**：Web 前端重写、7 服务物理拆分、Temporal、Remote MCP Gateway、独立 Tool Policy 服务。
这些等"验证过的需求"拉动再上——不提前建地基。

---

## 10. 现在就能起手的第一刀

**P1 的 tenant_id 是整件事的地基,也是唯一"越早越省"的。建议第一步只做最小闭环：**
1. 建 `tenant` + `tenant_member` 表；注册时给每个新用户建一个单人默认 tenant。
2. `contract` + 核心表加 `tenant_id`；migration 脚本给存量数据回填一个默认 tenant。
3. 一个统一的 gorm scope 强制所有查询带 tenant_id + 鉴权中间件注入 tenant 上下文。
4. 先只覆盖 PG（Redis/MinIO/向量/图谱的 tenant 前缀随后一层层加），跑通"注册→建库→提交任务"在 tenant 作用域下端到端。

做完这一刀，多租户地基就立住了，后面 P2–P5 都能增量长上去。

---

*本设计对应「做什么」；对当前架构本身的评价见 `ARCHITECTURE_REVIEW.md`，当前架构描述见 `ARCHITECTURE_DESIGN.md`。*
