chore(audit): 补追 96 份未入仓审计孤本 — 覆盖 2026-02-26 ~ 2026-04-08

这些审计记录原本堆积在 docs/audit/changes/changes/ 嵌套误产物目录下(由开发机迁移
79d3c2e 前后的不明批量操作产生)。由于同期 .gitignore 屏蔽了 docs/audit/ 全目录,
它们从未入过 git 任何分支 history。删除即永久丢失。

按 docs/specs/audit-gap-recovery/tasks.md 阶段 1 执行,将全部 96 份 D 类孤本
(主目录无同名、git history 亦无记录)复制到 docs/audit/changes/ 主目录入仓。

涵盖主题: P1-P18 全栈集成 / 多模块累积变更 / ETL bug 修复 / 业务日切 /
   召回与任务引擎改造 / 租户管理与审批 / 董事会财务 / 客户与助教详情 /
   DDL 基线合并 / Kiro 到 Claude Code 迁移

阶段 2(B 类内容漂移 1 份)和阶段 4(嵌套目录删除)独立推进。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Neo
2026-04-20 06:35:42 +08:00
parent 80bda9b991
commit 14a12342b5
96 changed files with 9521 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
# 变更审计记录:根治 tenant_admin 的 managed_site_ids 限制(跨模块权限验证改造)
| 字段 | 值 |
|------|-----|
| 日期 | 2026-03-23 21:00:00 |
| Prompt-ID | P20260323-210000 |
| Session-ID | f0a03585 |
| Session 路径 | docs/audit/session_logs/2026-03/23/21_28b7ab84_173223 |
## 操作摘要
根治 tenant_admin 的 JWT `managed_site_ids` 静态签发问题。tenant_admin 新建店铺后 JWT 中不含新店铺 ID导致所有使用 `verify_site_access``site_filter_clause` 的端点无法访问新店铺。方案tenant_admin 按 `tenant_id` 实时查 `biz.sites` 获取有效 site_idssite_admin 仍用 JWT 中的 `managed_site_ids`。改造涉及 5 个文件、跨 4 个路由模块。
## 核心方案
`tenant_admins.py` 新增两个函数:
- `get_tenant_site_ids(tenant_id)` — 通过 `biz.tenants.tenant_id``biz.tenants.id``biz.sites.tenant_id` 关联查询
- `get_effective_site_ids(admin)` — 统一入口,自动区分 admin_typetenant_admin 查库 / site_admin 用 JWT
改造 `site_filter_clause``verify_site_access` 支持 `admin=` keyword-only 参数(向后兼容旧的 positional `managed_site_ids` 签名)。
## 受影响文件
| 文件 | 改动内容 |
|------|----------|
| `apps/backend/app/auth/tenant_admins.py` | 新增 `get_tenant_site_ids``get_effective_site_ids`;改造 `site_filter_clause``verify_site_access` |
| `apps/backend/app/routers/tenant_users.py` | 所有调用点改用 `admin=admin``list_my_sites` 简化为 `get_effective_site_ids``list_applications` 回退头痛医头代码 |
| `apps/backend/app/routers/tenant_clues.py` | `_get_clue_with_site_check` 签名改为 `admin: CurrentTenantAdmin``search_customers``get_effective_site_ids``list_customer_clues``site_filter_clause(admin=admin)` |
| `apps/backend/app/routers/tenant_excel.py` | 两个 `verify_site_access` 改用 `admin=admin``list_upload_logs``site_filter_clause` 改用 `admin=admin` |
| `apps/backend/app/routers/tenant_site_admins.py` | `create_site_admin``edit_site_admin` 的权限子集校验改用 `get_effective_site_ids(admin)` |
## 影响范围
- 所有 `/api/tenant/*` 端点的门店权限验证
- tenant_admin 新建店铺后无需重新登录即可访问
- site_admin 行为不变(仍用 JWT managed_site_ids
## 改动注解
### `apps/backend/app/auth/tenant_admins.py`
- 变更类型:修改
- 原始原因JWT `managed_site_ids` 是登录时静态签发的,新建店铺后不在列表中,导致 tenant_admin 无法访问新店铺的任何端点。需要一个统一的动态获取机制。
- 思路分析:新增 `get_tenant_site_ids()` 通过 `biz.tenants.tenant_id`(外部标识)→ `biz.tenants.id`(内部 PK`biz.sites.tenant_id` 三表关联查询活跃店铺。`get_effective_site_ids()` 作为统一入口,按 `admin_type` 分流tenant_admin 走查库路径site_admin 仍用 JWT。`site_filter_clause``verify_site_access` 新增 `admin=` keyword-only 参数,优先使用 admin 参数,也兼容旧的 positional `managed_site_ids` 直传方式,实现向后兼容。
- 修改结果:所有下游路由只需传 `admin=admin` 即可自动获得正确的 site_ids无需关心 admin_type 差异。biz.sites 数据量极小(几条),无需缓存。
### `apps/backend/app/routers/tenant_users.py`
- 变更类型:修改
- 原始原因该路由的所有端点my-sites、applications、users、approve、reject、edit、binding都直接使用 `admin.managed_site_ids`,新建店铺后全部受限。此前 `list_my_sites``list_applications` 已有针对 tenant_admin 的特殊处理(头痛医头),需要统一回退。
- 思路分析:所有 `verify_site_access(site_id, admin.managed_site_ids)` 改为 `verify_site_access(site_id, admin=admin)`;所有 `site_filter_clause(admin.managed_site_ids)` 改为 `site_filter_clause(admin=admin)``list_my_sites` 简化为直接调用 `get_effective_site_ids(admin)` 查库,删除之前针对 tenant_admin 的特殊 SQL 分支。`list_applications` 回退之前的 tenant_admin 特殊处理代码,统一走 `site_filter_clause(admin=admin)`
- 修改结果10 个端点的权限验证统一收口代码更简洁。tenant_admin 新建店铺后所有用户管理功能立即可用。
### `apps/backend/app/routers/tenant_clues.py`
- 变更类型:修改
- 原始原因:维客线索管理的 `_get_clue_with_site_check``search_customers``list_customer_clues` 都直接使用 `admin.managed_site_ids`,新建店铺的线索无法查看和管理。
- 思路分析:`_get_clue_with_site_check` 签名从接受 `managed_site_ids: list[int]` 改为接受 `admin: CurrentTenantAdmin`,内部调用 `verify_site_access(site_id, admin=admin)``search_customers``get_effective_site_ids(admin)` 构建 IN 子句。`list_customer_clues``site_filter_clause(admin=admin)`。三个调用点edit_clue、delete_clue、toggle_visibility改传 admin 对象。
- 修改结果:维客线索管理覆盖新建店铺,签名更语义化。
### `apps/backend/app/routers/tenant_excel.py`
- 变更类型:修改
- 原始原因Excel 上传/确认/日志三个端点的权限验证使用静态 `managed_site_ids`,新建店铺的 Excel 数据无法上传和查看。
- 思路分析:`upload_excel``confirm_excel` 中的 `verify_site_access(site_id, admin.managed_site_ids)` 改为 `verify_site_access(site_id, admin=admin)``list_upload_logs``site_filter_clause(admin.managed_site_ids)` 改为 `site_filter_clause(admin=admin)`
- 修改结果Excel 上传/确认/日志覆盖新建店铺。
### `apps/backend/app/routers/tenant_site_admins.py`
- 变更类型:修改
- 原始原因创建和编辑店铺管理员时权限子集校验site_admin 的 managed_site_ids 必须是 tenant_admin 管辖范围的子集)使用静态 `admin.managed_site_ids`,导致无法为新建店铺分配管理员。
- 思路分析:`create_site_admin``edit_site_admin` 中的子集校验从 `set(body.managed_site_ids) <= set(admin.managed_site_ids)` 改为 `set(body.managed_site_ids) <= set(get_effective_site_ids(admin))`
- 修改结果tenant_admin 可以为新建店铺创建和编辑店铺管理员。
## 合规检查
| 检查项 | 状态 |
|--------|------|
| 新增迁移 SQL | 无 |
| DDL 基线 | 不涉及 |
| OpenAPI spec | ✅ 已重新导出137 paths, 194 schemas。附带修复`tenant_admins.py``AI_CHANGELOG` 文档字符串移至 `from __future__` 之后(消除 SyntaxError |
| BD 手册 | 不涉及 |
## 回滚方案
将 5 个文件的 `admin=admin` 参数改回 `admin.managed_site_ids`positional删除 `tenant_admins.py` 中新增的三个函数。旧签名向后兼容,无需修改函数定义。
## 验证 SQL
```sql
-- 验证 tenant_admin 的 tenant_id 能查到所有店铺(含新建)
SELECT s.site_id, s.site_name, s.site_code
FROM biz.sites s
JOIN biz.tenants t ON t.id = s.tenant_id
WHERE t.tenant_id = 'LLZQ' AND t.is_active = true AND s.is_active = true;
```