# 第三阶段：租户隔离、共享题库与前端运行时

状态：已完成主线设计和实现。

## 目标

- 普通业务代码默认只能读取和写入当前租户数据。
- 公共题库由平台主体拥有，有效租户可访问。
- 租户私题只属于本租户。
- 公共题和私题可混合组卷、答题、收藏、错题和统计。
- 租户自定义域名安全解析到统一前端运行时配置。

## 结果

- `ITenantContext` 只读化，请求租户由中间件解析。
- EF Core Query Filter 自动按租户过滤。
- SaveChanges 拦截器自动写入当前租户并拒绝跨租户写入。
- DbContext 使用 scoped `AddDbContext`，避免池化串租户状态。
- `TenantResolutionMiddleware` 在认证前按可信 Host 解析租户。
- JWT tenant claim 与 Host 解析不一致时返回 403。
- 未知、Pending、禁用域名在业务前返回 404。
- 唯一 `PlatformOwned` 租户拥有公共题库、公共题、公共题版本和公共分类主干。
- `TenantQuestionReference` 作为公共题/私题消费引用。
- `PracticeSessionQuestion` 锁定题目版本，历史答题按原版本回放。
- `TenantFrontendConfig` 支持 Draft / Preview / Publish。
- `GET /api/runtime/bootstrap` 仅根据当前 Host 返回公开配置。

## 数据库边界

EF Core 负责实体、索引、外键和普通约束。以下跨表租户不变量由集中 PostgreSQL guard 管理：

- `TenantQuestionReference` 只能引用平台公共题或当前租户私题。
- `TaxonomyNode` 父节点只能属于平台主体或当前租户。

维护规则见根目录 README 的“数据库与 ORM 分工”。

## 验收

- A 租户不能读取、修改、删除或 Attach B 租户数据。
- A 可访问公共题和 A 私题，不能构造 B 私题引用。
- 公共题 V1 创建会话后发布 V2，旧会话仍使用 V1，新会话使用 V2。
- Host A 携带 Tenant B JWT 返回 403。
- Pending/未知域名和 Suspended 租户无法获取 runtime bootstrap。
- 新增租户实体漏写 filter、组合外键或租户索引时模型测试失败。

```bash
dotnet build TIKU-BACKEND.slnx --no-restore
dotnet test Tiku.UnitTests/Tiku.UnitTests.csproj --no-build
dotnet test Tiku.IntegrationTests/Tiku.IntegrationTests.csproj --no-build
dotnet ef migrations script --project Tiku.Infrastructure --startup-project Tiku.DbMigrator
git diff --check
```
