# ADR-0001 · 集团租户模型重构

> 品牌从 tenant 槽搬到 source_unit;orgScope 作为数据权限唯一入口

| | |
|---|---|
| **状态** | Accepted |
| **日期** | 2026-06 |
| **关联** | 登录授权(`orgScope` / `tenant` 的对外契约):`apps/pac-docs/content/docs/integration/auth-login.mdx` |

## 背景(Context)

早期 `tenant_id` 装的是**品牌**(同一集团下的多个品牌),数据权限则让 host 直接传
`source_units` + `clinic_ids`。两个问题:

1. 同一**集团**下的多品牌被切成互不相通的租户,集团级视角无处安放。
2. `source_units` / `clinic_ids` 是 PAC 的**内部过滤维**,却泄漏进了对外契约 ——
   host 得理解 PAC 的 schema 词汇才能接入,违反第一性。

## 决策(Decision)

- **`tenant_id` = 集团**(稳定 slug,如 `<group>-grp`),进每张表唯一键 + 每条查询 where。
- **品牌 = `source_unit`** —— 事实的发行命名空间,留在唯一键里,不再当租户。
- **数据权限唯一入口 = `org_scope`** —— host 只传「用户挂在 org 树的哪些节点」(任意层级:
  集团 / 区域 / 品牌 / 诊所)。PAC 按该 host 的 org 树 `expandOrgScope` 展开成内部两维
  过滤(`source_units` + `clinic_ids`)。host **不再传**内部过滤维。
- org 树**从摄入数据派生**,零硬编码 id,跨环境自适应。
- 展开**fail-closed**:品牌型租户遇到树外 ref → 落 `DENY_SOURCE_UNIT` 哨兵,
  绝不退化成「不限品牌」(防跨品牌泄漏,因同集团多品牌共享 tenantId)。

## 后果(Consequences)

- ✅ 集团级视角天然成立;跨品牌隔离由唯一键 + where 硬保证。
- ✅ 契约只暴露 host 唯一知道的东西,接新 host = 写一层薄适配器。
- ✅ JWT 只装通用 `orgScope`,解码出来与契约一致,不泄漏内部 schema。
- ⚠️ 每请求需展开 orgScope(`tenant-scope` 拦截器 / MCP 鉴权),有缓存(10min TTL)。
- ⚠️ 旧数据需清空重摄迁移(已完成:全量患者迁入新模型并验证隔离)。
