# 变更审计记录：禁用用户改为移除用户 + 小程序鉴权两层模型修复

| 字段 | 值 |
|------|-----|
| 日期 | 2026-03-23 21:42:26 |
| Prompt-ID | P20260323-190012 |
| Session-ID | 01d3f04b |
| Session 路径 | docs/audit/session_logs/2026-03/23/32_834c940b_190011 |

## 操作摘要

用户指出小程序鉴权模型混淆了两层：第一层微信身份（JWT）不应因业务状态 disabled 而拒绝；租户不应有全局禁用用户的权限，只能从店铺关系中移除。本次改动移除了 login/refresh 接口对 disabled 用户的 403 拦截，使 disabled 用户可正常获取受限令牌，由前端状态路由处理业务层逻辑。

## 本次对话文件变更

### 新增文件
- `docs/audit/prompt_logs/prompt_log_20260323_190012.md`
- `docs/audit/session_logs/2026-03/23/31_d178bd1a_185322/main_01_6e26cf47.md`

### 删除文件
- `docs/audit/session_logs/2026-03/23/31_d178bd1a_185322/main_01_b213fe92.md`（session log 替换）

## 改动注解

### `apps/backend/app/routers/xcx_auth.py`
- 变更类型：修改
- 原始原因：用户被禁用后跳转登录页报 401/403 错误。根因是鉴权模型混淆了两层——第一层微信身份（JWT）不应因业务状态 disabled 而拒绝登录/刷新；租户管理后台的"禁用"操作直接改 auth.users.status='disabled' 是全局操作，不符合多租户隔离。
- 思路分析：
  1. **login 接口**：移除 `if user_status == "disabled": raise HTTPException(403)` 拦截。disabled/new/pending/rejected 统一签发受限令牌，由前端状态路由处理。
  2. **refresh 接口**：同样移除 disabled 用户的 403 拦截。第一层（微信身份）始终有效，disabled 只影响第二层（业务状态路由）。
  3. **附带改动**：login 时补全 unionid 幂等更新逻辑（CHANGE 2026-03-22）；login/dev-login 返回 role 字段；/me 接口查询当前门店角色并返回；新增 cancel-application 接口；dev-switch-role 角色列表更新为小程序端 4 角色（coach/staff/head_coach/manager）；site_code_mapping 引用改为 biz.sites。
  4. **trace 装饰器**：所有路由函数添加 `@trace_service` 装饰器用于调用链追踪。
- 修改结果：disabled 用户可正常登录并获取受限令牌，前端根据 user_status 路由到对应页面（申请页/审核中/被拒绝/被禁用提示）。租户管理后台后续将"禁用"改为"移除"（删 user_site_roles + 条件重置 status=new）。

## 合规检查

- ✅ OpenAPI spec 已重新导出（137 paths, 194 schemas）
- ⚠️ `apps/backend/docs/API-REFERENCE.md` 待同步更新
- 💡 请重连 OpenAPI Power 的 MCP server 以加载新 spec
- ✅ 无新增迁移 SQL
- ⚠️ DDL 基线待确认（`has_ddl_baseline: false`）

## 风险提示

- 已有 disabled 状态的用户需要手动处理（当前测试库 id=8778 已恢复为 approved）
- 第三次拒绝自动禁用逻辑保持不变（系统级行为）
- dev-switch-role 角色列表从 `(coach, staff, site_admin, tenant_admin)` 改为 `(coach, staff, head_coach, manager)`，需确认开发调试场景覆盖
