Files
LineWalker b8ad65f7de feat(F012-v2.5.1): 叶子 Tenant 派生 + JWT token_version + sync 主部门变更
v2.5.0 的扁平多租户模型没有"用户归属到哪个 Tenant"的源头——登录时 tenant_id 仍走 DEFAULT_TENANT_ID=1 兜底,主部门调岗后旧 JWT 继续有效。F011 已把 Tenant 树结构(Root + 0~N Child)落地但缺用户侧映射;F012 补齐这一层,同时为 F013/F016/F019/F020 铺好 ContextVar 契约。

核心变更:
- 新增 TenantResolver:沿主部门 path 反向找最近 is_tenant_root=1 的挂载点,返回其 Child Tenant;disabled/archived/orphaned 跳过;最终回落 Root(1);带循环保护
- 新增 UserTenantSyncService.sync_user:resolve → 比对当前 leaf → 计数旧 Tenant 下资源 → 切归属 + 递增 token_version + 重写 FGA tenant#member 元组(crash_safe=True)+ DEL Redis 缓存 + 写 user.tenant_relocated audit
- 新增 UserDepartmentService.change_primary_department:事务内 demote/promote + 触发 sync(trigger=dept_change)
- user 表加 token_version INT NOT NULL DEFAULT 0(Alembic v2_5_1_f012_user_token_version,down=f011_tenant_tree)
- UserDao.aget_token_version (Redis 缓存 300s) + aincrement_token_version (原子 UPDATE) + alist_users_paginated
- LoginUser.token_version 字段 + create_access_token/init_login_user/get_login_user/get_login_user_from_ws 全链路透传
- 登录流程 (user_login) 集成 sync_user(LOGIN) + aget_token_version 刷新版本
- core/context/tenant.py 扩展 4 ContextVar:visible_tenant_ids / _strict_tenant_filter / _admin_scope_tenant_id / _is_management_api;新 strict_tenant_filter() CM;get_current_tenant_id() 优先级 admin_scope>current_tenant_id(v2.5.0 签名保留不变)
- CustomMiddleware 原地增强:_validate_token_version(401 + 19103)+ _check_is_global_super(FGA + Redis 缓存)+ _compute_visible_tenant_ids 计算 {leaf,1} / {1} / None 三档
- GET /api/v1/user/current-tenant:返 {leaf_tenant_id, is_child, mounted_department_id, root_tenant_id}(handler 抽到 current_tenant.py 便于单测)
- Celery 6h reconcile:worker.tenant_reconcile.tasks.reconcile_user_tenant_assignments,分页 batch=500 扫全量 user 表,beat crontab 0 */6 * * *
- UserTenantSyncConf 新配置类(enforce_transfer_before_relocate,默认 False)注册到 Settings
- TenantAuditAction 枚举加 USER_TENANT_RELOCATED / USER_TENANT_RELOCATE_BLOCKED;新 UserTenantSyncTrigger 枚举(LOGIN/DEPT_CHANGE/CELERY_RECONCILE/MANUAL)
- 错误码模块 191 tenant_resolver (19101-19104): TenantRelocateBlockedError / TenantResolveFailedError / TokenVersionMismatchError / TenantCycleDetectedError

F011/F012 共用重构:
- 抽取 tenant/domain/services/inbox_helper.py:集中 send_inbox_notice + list_global_super_admin_ids,替代 F011 department_deletion_handler 内的 _send_inbox_notice/_list_global_super_admin_ids;F011 handler 改为从 helper 导入(保留 __all__ re-export 给 F011 旧测试兼容)
- F012 UserTenantSyncService._notify_resource_owner_relocation 复用 send_inbox_notice

关键不变量:
- 资源不随归属迁移(PRD Review P0-C,INV-T4)——主部门跨 Tenant 变更后 sync_user 仅更新 user_tenant + JWT,资源 tenant_id 保持不动
- enforce_transfer_before_relocate=true 时名下有资源即阻断(409 + 19101 + audit user.tenant_relocate_blocked),降低误操作成本
- FGA 元组重写走 crash_safe failed_tuples 补偿;主事务不因 FGA 失败回滚(归属切换优先成功)
- visible_tenant_ids 为 F013 IN-list 过滤的契约;F019 AdminScopeMiddleware 之后通过 _admin_scope_tenant_id 覆盖 get_current_tenant_id()

测试(87 passed / 10 files):
- test_user_token_version_dao.py (6): ORM 默认 / NOT NULL / UPDATE 原子 / 重复 +1
- test_tenant_context_vars.py (16): 4 ContextVar 默认 + set/reset + 优先级 + strict CM 嵌套/异常安全 + v2.5.0 签名守护
- test_tenant_resolver.py (12): 路径派生 / disabled 跳过 / 多层取最近 / 无主部门 / cycle 检测 / Tenant 缺失回落
- test_user_tenant_sync_service.py (10): 无变化直返 / 阻断 / 告警不阻断 / FGA 重写 delete+write / token_version 递增 / Redis DEL / 首次同步无 delete / FGA 故障容错 / reason=no_primary_department
- test_auth_jwt_token_version.py (7): payload 含字段 / classmethod 读 User.token_version / 兼容 v2.5.0 无字段 User / 显式参数覆盖
- test_middleware_token_version.py (13): visible 计算 5 档 / token_version 匹配/不匹配/fail-open / apply 401 / super None / child {leaf,1}
- test_user_department_service.py (5): 同主部门 no-op / 跨 Tenant 触发 sync(DEPT_CHANGE) / 首次 primary / Blocked 透传 / 枚举值
- test_current_tenant_api.py (3): Root/Child 两档 + switch-tenant 410 回归
- test_tenant_reconcile_task.py (5): 单批/多批分页 / Blocked 吞掉 / 泛型异常继续 / 空表 no-op
- test_department_deletion_handler.py (10) [F011 回归]: handler 使用 inbox_helper 后原测试全绿

回归:
- F011 85 tests passed(含 department_deletion_handler 10 条,重构后无损)
- v2.5.0 基线维持(553 passed + 16 pre-existing failures 皆与 F012 无关)

SDD 产物:
- features/v2.5.1/012-tenant-resolver/tasks.md(12 任务全部完成 + 实际偏差记录)
- features/v2.5.1/012-tenant-resolver/ac-verification.md(AC-01~AC-11 → 测试映射 + spec §8 手工 QA 清单)

下游解锁:
- F013-tenant-fga-tree:读 get_visible_tenant_ids() 做 IN-list FGA check
- F014-sso-org-realtime-sync:直接调 UserTenantSyncService.sync_user(trigger=DEPT_CHANGE)
- F016-tenant-quota-hierarchy:strict_tenant_filter() 精确配额计数
- F019-admin-tenant-scope:set_admin_scope_tenant_id() + set_is_management_api() 已就位
- F020-llm-tenant-isolation:LLMDao 查询读 visible_tenant_ids

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:27:46 +08:00
..

SDD (Spec-Driven Development) — BiSheng 适配版

完整方法论指南: docs/SDD-Guide.md

本目录存放 SDD 产物——版本契约、Feature 规格和任务清单。


工作流(9 步)

0. release-contract.md          版本开始时,一次性
   ↓
1. Spec Discovery               架构师提问,识别 PRD 不确定性
   ↓ ★ 手动暂停点:用户确认
2. 编写 spec.md                 合并需求规范 + 技术设计
   ↓
3. /sdd-review <dir> spec       审查 spec(11 项检查)
   ↓ ★ 手动暂停点:用户确认
4. 编写 tasks.md                拆解为原子任务
   ↓
5. /sdd-review <dir> tasks      审查 tasks17 项,自动推进)
   ↓
6. 创建 Feature 分支            feat/v2.5.0/{NNN}-{name},基于 2.5.0-PM
   ↓
7. 逐任务执行                   实现 → 测试 → /task-review → 打勾
   ↓
7.5. /e2e-test <dir>            E2E 测试(强制)
   ↓
8. /code-review --base 2.5.0-PM 多维度代码审查(自动)
   ↓
9. 合并回 2.5.0-PM

核心约束:

  • 每步只产出该步骤的文件,不提前执行后续步骤
  • 两个 ★ 手动暂停点必须等待用户确认
  • 实现偏差必须记录在 tasks.md §实际偏差记录

目录结构

features/
├── README.md                    # 本文件
├── _templates/                  # 可复用模板
│   ├── release-contract.md      # 版本契约模板
│   ├── spec.md                  # 规格文档模板(BiSheng 适配版)
│   └── tasks.md                 # 任务清单模板(BiSheng 适配版)
└── v2.5.0/                      # v2.5.0 版本产物
    ├── release-contract.md      # 版本契约(预填)
    ├── README.md                # Feature 索引
    ├── 001-feature-name/
    │   ├── spec.md
    │   └── tasks.md
    └── ...

命名规范

Feature 目录

{NNN}-{kebab-case-name}
  • NNN — 零补齐三位数字(000, 001, 002, ...
  • Name — 小写、连字符分隔、描述性名称
  • 示例:000-test-infrastructure001-multi-tenant004-rebac-core

Feature 分支

feat/v2.5.0/{NNN}-{short-name}
  • 基于 2.5.0-PM 拉出
  • 合并回 2.5.0-PMgit merge --no-ff
  • 示例:feat/v2.5.0/004-rebac-core

审查命令

命令 时机 说明
/sdd-review <dir> spec spec.md 编写后 11 项需求+架构检查
/sdd-review <dir> tasks tasks.md 编写后 17 项拆解质量检查(自动)
/task-review <dir> <task_id> 每个任务完成后 L1 约定合规(6 项)
/code-review --base 2.5.0-PM Feature 全部完成后 L2 多维度深度审查
/e2e-test <dir> 全部任务完成后 生成并运行 E2E 测试

快速开始

新建 Feature

# 1. 复制模板
cp features/_templates/spec.md features/v2.5.0/NNN-feature-name/spec.md
cp features/_templates/tasks.md features/v2.5.0/NNN-feature-name/tasks.md

# 2. 按工作流执行:Discovery → spec → review → tasks → review → 实现

新建版本

# 1. 创建版本目录
mkdir features/vX.Y.Z

# 2. 复制版本契约模板
cp features/_templates/release-contract.md features/vX.Y.Z/release-contract.md

# 3. 填写领域对象归属、不变量、依赖图