这些审计记录原本堆积在 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>
7.0 KiB
7.0 KiB
变更审计记录:根治 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_ids,site_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_type(tenant_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 参数,也兼容旧的 positionalmanaged_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
-- 验证 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;