# Phase 2：RBAC 收敛 + AI 模型页入口 - Context

**Gathered**: 2026-05-08
**Status**: Ready for planning（用户选择 `--skip-ui` 跳过 UI-SPEC，直接规划）
**Source**: 用户在 `/gsd-plan-phase 2` 调用时提供的内联约束

<domain>
## Phase 边界

本 phase 是 Milestone v1.0 前端集成的**第二步**，把 Phase 1 落地的 API client 接到权限矩阵和 AI 模型页：
- 在 `lib/permissions.ts` 声明 `credential-slot` 模块 key + 配置 RBAC 矩阵
- 在 `app/ai-model/page.tsx` 加入入口控件（按钮）+ 占位空对话框
- 入口控件可见性走 `hasPermission('credential-slot')`，不直接读 localStorage

**不负责**（留给 Phase 3）：
- 编辑对话框真实内容（表单 RHF + Zod + 提交逻辑 + 留空保留旧值语义）
- Sonner toast 反馈（成功 / 失败）
- 端到端串联（admin PUT → toast → 重新打开看到新末 4 位 + updated_at）

</domain>

<decisions>
## 实现决策（锁定）

### CRED-FE-02 RBAC 模块声明

- **`PermissionModule` 类型扩充**：在 `lib/permissions.ts` 找到 `PermissionModule` 类型定义，在 union 中添加 `'credential-slot'` 字面量
- **`PERMISSION_MATRIX` 矩阵**：将 `'credential-slot'` 加入到「超级管理员」+「AI模型管理员」两个角色的模块列表（researcher 必须 read 完整矩阵给出每个角色当前包含哪些模块；这两个角色的列表末尾追加即可）
- **其他 3 个角色**（内容管理员 / 卡牌管理员 / 查看者）+ 1 个可能的「管理员」角色：**不**包含 `credential-slot`（保持原数组不变）
- **`getModuleFromPath('/ai-model')` 行为不变**：`/ai-model` 路径已映射到 `'ai-model'` 模块（researcher 确认），凭据槽位是 `/ai-model` 的子能力，不占独立路由 → 不要给 `getModuleFromPath` 加 `'/ai-model/credential-slot'` 之类映射

### CRED-FE-03 /ai-model 页面入口

- **入口控件类型**：**Button**（不是 Card）—— 最简、与现有页面风格一致；样式沿用 shadcn Button 组件（`components/ui/button.tsx`），variant="outline" 或现有页面其他按钮的 variant
- **位置**：在 `app/ai-model/page.tsx` 的 **页面顶部 / 头部 / 工具栏区域**（researcher 必须 read 现有页面结构给出具体插入点 —— 最可能是页面头部的标题旁边或现有"添加 AI 模型"之类按钮的同行右侧）
- **图标**：**KeyRound**（Lucide）—— 凭据语义最贴切；如果不可用降级到 `Lock` / `Settings`
- **文案**：按钮内文字 **"凭据槽位"**（中文）
- **可见性约束**：用 `hasPermission('credential-slot')` 包裹整个 Button JSX；不渲染时 DOM 中**完全不存在**（`{hasPermission('credential-slot') && <Button>...</Button>}`）
- **点击行为**：本 phase 触发**占位空对话框**打开（基于 `components/ui/dialog.tsx`），对话框内容仅 DialogTitle + DialogDescription（中文文案"通用凭据槽位"+ 说明"对话框真实内容由 Phase 3 落地"），无表单
- **对话框组件位置**：在 `app/ai-model/page.tsx` 内联（不抽到独立文件 —— Phase 3 会把对话框抽到 `components/ai-model/CredentialSlotDialog.tsx`）
- **状态管理**：用 `useState<boolean>` 控制 dialog open 状态

### 兼容性 / 不引入新依赖

- 沿用现有依赖：`components/ui/button.tsx`、`components/ui/dialog.tsx`、`lucide-react`、`lib/permissions.ts:hasPermission`
- 不引入新依赖（不动 lockfile，不跑 npm install）
- 不动 `lib/permissions.ts` 的其他函数（`getUserRole` / `hasPathPermission` 等）

### 修改记录

`qy-lty-admin/docs/修改记录.md` 顶部追加一条 Phase 2 条目：
- 文件路径：`lib/permissions.ts`、`app/ai-model/page.tsx`
- 修改类型：新增 / 修改
- 跨项目联动：「无 — Phase 2 是纯前端 RBAC + UI 入口落地，不引入新跨项目契约；后端 commit 46d72b8 已建立的互引仍有效；Phase 3 引入实质 PUT 调用时若涉及新契约再评估」

### Claude's Discretion

- 入口 Button 的具体 `variant`（outline / default / secondary）—— planner 看现有页面其他按钮风格选最一致的
- 入口 Button 的 `size`（默认 / sm / lg）—— 同上
- 占位对话框的 DialogContent `className` 大小 —— 用默认即可
- 是否给 Button 加 tooltip（KeyRound 图标语义不够明显时）—— 推荐加，让运营秒懂

</decisions>

<canonical_refs>
## Canonical References

**下游 agent 必读**：

### 项目宪法
- `qy-lty-admin/CLAUDE.md` — 沟通中文 / 修改记录强制 / 包管理器不混用
- `qy-lty-admin/.planning/PROJECT.md` — Milestone v1.0「关键约束」段（本 phase 重点：RBAC 收敛在 lib/permissions.ts + 入口走 hasPermission）
- `qy-lty-admin/.planning/REQUIREMENTS.md` — Active 段 CRED-FE-02 + CRED-FE-03
- `qy-lty-admin/.planning/ROADMAP.md` — Phase 2 详情段（4 条 Success Criteria）

### Phase 1 已交付（必读，作为消费 contract）
- `qy-lty-admin/lib/api/credential-slot.ts` — Phase 1 落地的 API client（本 phase 不直接调用，但 Phase 3 会）
- `qy-lty-admin/.planning/phases/01-credential-slot-api/01-01-SUMMARY.md` — Phase 1 收尾摘要

### RBAC + 现有页面（必读，1:1 模板候选）
- `qy-lty-admin/lib/permissions.ts` — RBAC 矩阵 + `hasPermission` 工具（本 phase 主要改动文件之一）
- `qy-lty-admin/app/ai-model/page.tsx` — `/ai-model` 页面（本 phase 主要改动文件之一）
- `qy-lty-admin/components/sidebar.tsx` — 现有 `hasPermission` 调用样板（怎么用、怎么过滤）
- `qy-lty-admin/components/dashboard-shell.tsx` — 路径级权限校验样板

### UI 组件
- `qy-lty-admin/components/ui/button.tsx` — shadcn Button
- `qy-lty-admin/components/ui/dialog.tsx` — shadcn Dialog（含 DialogTrigger / DialogContent / DialogHeader / DialogTitle / DialogDescription / DialogFooter）

### 修改记录
- `qy-lty-admin/docs/修改记录.md` — 头部「修改格式说明」+ Phase 1 / Phase 2（后端联动）条目作模板

</canonical_refs>

<specifics>
## 具体要点（Success Criteria 显式化）

| # | 验证点 | 检查方式 |
|---|--------|----------|
| 1 | `PermissionModule` 类型含 `'credential-slot'` | grep `lib/permissions.ts` 含 `'credential-slot'` 命中 ≥1 次（在 union 类型中） |
| 2 | `PERMISSION_MATRIX` 中超级管理员 + AI模型管理员两个角色含 `'credential-slot'` | grep + 结构化解析 |
| 3 | 其他角色不含 `'credential-slot'` | grep 反向：只检查特定 5 角色名 + 排除分隔，确保 'credential-slot' 仅出现 2 次 |
| 4 | `hasPermission('credential-slot')` 在两类账户下返回 true，其他角色 false | 单元测试式调用 |
| 5 | `getModuleFromPath('/ai-model')` 行为不变 | grep 函数定义 + 对照原值 |
| 6 | `app/ai-model/page.tsx` 含"凭据槽位"按钮 | grep "凭据槽位" 命中 + grep `KeyRound`（或备选图标）+ grep `<Button` |
| 7 | 按钮被 `hasPermission('credential-slot') &&` 包裹 | grep 该模式命中 |
| 8 | 占位对话框存在（DialogTitle 含中文"通用凭据槽位"）| grep + 结构化解析 |
| 9 | 点击按钮触发 `setIsCredentialDialogOpen(true)` 或类似 useState setter | grep `useState` + `onClick` 关联 |
| 10 | `npx tsc --noEmit` 在新增/修改文件零错误（Phase 1 已确认存量错误零指向新文件） | shell exit + filter |
| 11 | 修改记录顶部新增 Phase 2 条目 | grep `[2026-05-08] Phase 2 (前端)` 命中 |

</specifics>

<deferred>
## 推迟事项（不在 Phase 2 范围）

- **编辑对话框真实表单**（RHF + Zod + 提交逻辑） — Phase 3 / CRED-FE-04
- **Sonner toast 反馈** — Phase 3 / CRED-FE-05
- **`components/ai-model/CredentialSlotDialog.tsx` 抽离** — Phase 3
- **真实后端 PUT 调用** — Phase 3
- **端到端联调** — Phase 3 后

</deferred>

---

*Phase: 02-rbac-ai*
*Context gathered: 2026-05-08 via inline PRD（用户在 /gsd-plan-phase 2 调用时提供完整约束 + 选择 --skip-ui）*
