25 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-rbac-ai | 02 | execute | 2 |
|
|
true |
|
|
Purpose:满足 CLAUDE.md 项目宪法「修改记录强制」规则;按 Phase 1 已建立的双重验证模式(tsc + grep)封盘 Phase 2,让 STATE.md 可推进到 Phase 3 待启动状态。
Output:docs/修改记录.md(修改,仅顶部追加;不动其他历史条目)。
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02-rbac-ai/02-CONTEXT.md @.planning/phases/02-rbac-ai/02-RESEARCH.md @.planning/phases/02-rbac-ai/02-01-PLAN.md @CLAUDE.md @docs/修改记录.md @lib/permissions.ts @app/ai-model/page.tsx### [日期] 修改简述
- **文件路径**: 相对于项目根目录的文件路径
- **修改类型**: 新增 / 修改 / 删除 / 重构 / 修复Bug
- **修改内容**: 具体修改了什么
- **修改原因**: 为什么要做这个修改
Phase 1 条目结构(直接抄此 7 字段结构):
- 元信息行(标题下方紧跟):「配套服务端 Phase: ...」+「覆盖前端需求: CRED-FE-XX」
- 文件路径:列出实际改动的所有文件相对路径
- 修改类型:新增 / 修改 / 删除 / 重构 / 修复Bug
- 修改内容:分点 bullet 描述每个文件做了什么
- 修改原因:解释为什么做、对后续 phase 的支撑作用
- 跨项目联动(项目惯用扩展字段,CONTEXT.md 已锁定本期文案)
- 服务端联动(项目惯用扩展字段,可与「跨项目联动」复用文案或简短交叉引用)
docs/修改记录.md line 26 是注释 <!-- 新的修改记录添加在此处下方,最新的在最前面 -->,line 28 是当前最顶 Phase 1 条目 ### [2026-05-08] Phase 1(前端)凭据槽位 API 客户端。新条目必须插入到 line 26 的注释之后**、line 28 的 Phase 1 条目之前。
<read_first>
1. 必读:docs/修改记录.md 完整 line 1-50(确认头部「修改格式说明」+ line 26 锚点注释 + line 28-48 Phase 1 条目作为模板)
2. 必读:.planning/phases/02-rbac-ai/02-CONTEXT.md 「修改记录」段(CONTEXT D-XX 锁定的「跨项目联动」字段精确文案)
3. 必读:.planning/phases/02-rbac-ai/02-01-PLAN.md(确认 Phase 2 实际改动文件 = lib/permissions.ts + app/ai-model/page.tsx)
4. 必读:CLAUDE.md 「项目修改记录规则(重要 — 自动执行)」段 + 「qy-lty-admin 与 qy_lty 是独立项目,各自维护」段
</read_first>
**old_string**(精确匹配 line 26-28,含中间空行):
```
<!-- 新的修改记录添加在此处下方,最新的在最前面 -->
### [2026-05-08] Phase 1(前端)凭据槽位 API 客户端
```
**new_string**(在锚点与 Phase 1 之间插入完整 Phase 2 条目;Phase 2 条目结尾 → 1 个空行 → Phase 1 条目原行):
```
<!-- 新的修改记录添加在此处下方,最新的在最前面 -->
### [2026-05-08] Phase 2(前端)RBAC 收敛 + AI 模型页凭据槽位入口
配套服务端 Phase:本 phase **不**触达服务端;与服务端 v1.0 Phase 2「管理端读写接口」commit `46d72b8` 既有契约保持兼容(不引入新契约)
覆盖前端需求:CRED-FE-02、CRED-FE-03
- **文件路径**:
- `lib/permissions.ts`(修改)
- `app/ai-model/page.tsx`(修改)
- **修改类型**: 修改(前端 RBAC 矩阵扩展 + 页面入口控件 + 占位 Dialog;纯前端,无新依赖、不动 lockfile)
- **修改内容**:
- `lib/permissions.ts`:
- `PermissionModule` union 末尾追加 `"credential-slot"`,扩为 14 项
- `PERMISSION_MATRIX["超级管理员"]` 数组末尾追加 `"credential-slot"`
- `PERMISSION_MATRIX["AI模型管理员"]` 数组末尾追加 `"credential-slot"`
- 其他 4 个角色(内容管理员 / 卡牌管理员 / 查看者 / 管理员)数组**逐字不变**
- `getModuleFromPath` 函数体完全不动(凭据槽位是 `/ai-model` 子能力,不占独立路由)
- 顶部「权限矩阵对照表」注释新增一行「凭据槽位」与代码同步
- `app/ai-model/page.tsx`:
- 文件 line 1 顶部新增 `"use client"` 指令,从 Server Component 转为 Client Component
- 新增 import:`useState` / `useEffect`(react)+ `Dialog` / `DialogContent` / `DialogDescription` / `DialogHeader` / `DialogTitle`(@/components/ui/dialog)+ `KeyRound`(lucide-react)+ `hasPermission`(@/lib/permissions)
- 函数体顶部新增 `mounted` + `isCredentialDialogOpen` 两个 `useState` + 1 个 `useEffect` 设 `mounted` 为 true(复用 `components/sidebar.tsx` mounted 守卫模式避免 SSR 水合不匹配)
- `DashboardHeader` 内部用 `<div className="flex items-center gap-2">` 包两个 Button:保留原有「添加新模型」+ 新增 `{mounted && hasPermission("credential-slot") && <Button variant="outline" onClick={() => setIsCredentialDialogOpen(true)}><KeyRound /> 凭据槽位</Button>}`
- `</Tabs>` 之后、`</DashboardShell>` 之前新增 controlled mode `<Dialog open={isCredentialDialogOpen} onOpenChange={setIsCredentialDialogOpen}>`,内含 `DialogTitle`「通用凭据槽位」+ `DialogDescription`「对话框真实内容由 Phase 3 落地」(占位,无表单)
- Tabs / TabsContent / Card / 卡片内的现有按钮等所有内容(line 18-441)逐字不变
- **修改原因**:
- 推进 Milestone v1.0「通用凭据槽位前端集成」第二步:让授权运营立即看到入口(已就位的 UX 收敛),未授权角色彻底看不到(DOM 中完全不存在的安全前置)
- 沿用 RBAC 单一来源原则(`lib/permissions.ts:hasPermission`)+ shadcn Dialog primitive,不重复造轮子
- 为 Phase 3 真实表单(CRED-FE-04 + CRED-FE-05)预留 Dialog 挂载点;Dialog 用 controlled mode 让 Phase 3 可在打开瞬间触发 `getCredentialSlot()`
- 注意:前端 RBAC 仅是 UI 礼貌,最终安全闭环依赖后端 `/v1/admin/credential-slot/` 的 admin 鉴权(PERM-06 / `qy_lty` 后端);本 phase 不消化该闭环
- **跨项目联动**: 无 — Phase 2 是纯前端 RBAC + UI 入口落地,不引入新跨项目契约;后端 commit 46d72b8 已建立的互引仍有效;Phase 3 引入实质 PUT 调用时若涉及新契约再评估
- **服务端联动**: 同上「跨项目联动」字段;后端 commit `46d72b8` 已建立互引闭环,本 phase 无需再次互引
### [2026-05-08] Phase 1(前端)凭据槽位 API 客户端
```
**明确不要做的事**:
- 不要动 line 1-26 的头部说明 / 修改格式说明 / 修改历史标记
- 不要动 line 28 之后任何已有条目(Phase 1 / 2026-05-07 的两个条目 / 2026-04-30 初始化条目)
- 不要把「跨项目联动」字段文案缩短或重写(CONTEXT.md 已逐字锁定)
- 不要漏掉 Phase 2 条目的「覆盖前端需求」元信息行(必须含 CRED-FE-02 + CRED-FE-03)
- 不要在「文件路径」中列入 docs/修改记录.md 自身(CLAUDE.md 适用范围说「单纯的 typo 修复 / 注释微调可省略」,但本条目作为 phase 收尾骨干条目,遵循 Phase 1 模板的写法 = 仅列被修改的代码文件)
<acceptance_criteria>
- grep -n "\\[2026-05-08\\] Phase 2" docs/修改记录.md 命中 1 行(标题行)
- grep -n "\\[2026-05-08\\] Phase 1" docs/修改记录.md 仍命中 1 行(Phase 1 条目未被破坏)
- head -n 30 docs/修改记录.md 输出含「修改格式说明」+「修改历史」+ <!-- 新的修改记录添加在此处下方,最新的在最前面 --> 锚点(line 1-26 完全不变)
- awk '/### \\[2026-05-08\\]/{count++} count==1 && /Phase 2/{print "PASS"; exit} count==1 && /Phase 1/{print "FAIL: Phase 1 在最前面"; exit}' docs/修改记录.md 输出 PASS(确保 Phase 2 在 Phase 1 之上)
- grep -nE "CRED-FE-(02|03)" docs/修改记录.md | head -n 5 命中至少 1 行包含 CRED-FE-02 或 CRED-FE-03 的元信息行(在 Phase 2 条目之内)
- grep -n "credential-slot" docs/修改记录.md 命中 ≥1 行(Phase 2 条目里描述 PermissionModule 字面量)
- grep -n "46d72b8" docs/修改记录.md 命中 ≥2 行(Phase 1 条目已有的 + Phase 2 条目新增的,证明跨项目联动文案已嵌入)
- grep -n "凭据槽位" docs/修改记录.md 命中 ≥1 行(Phase 2 条目内容描述)
- grep -n "通用凭据槽位" docs/修改记录.md 命中 ≥1 行(Phase 2 条目 DialogTitle 描述)
- git diff --stat docs/修改记录.md 显示仅 +N 行(无 -行;纯追加)
</acceptance_criteria>
<read_first>
1. 必读:.planning/phases/02-rbac-ai/02-01-PLAN.md 的 <verification> 段(4 条整体校验列表)
2. 必读:.planning/phases/02-rbac-ai/02-CONTEXT.md 「specifics」表(11 条验证点)
3. 必读:.planning/STATE.md Phase 1 收尾段(line 80-81)确认 Phase 1 已建立的 tsc 双重验证模式(整体退出码 2 但过滤后零指向新文件)
4. 必读:本 plan 任务 1 落地后的 docs/修改记录.md 顶部(确认 Phase 2 条目结构正确)
</read_first>
按以下顺序执行**4 大类**验证命令,逐条记录退出码与命中数:
### 验证 A:TypeScript 编译(双重判定,与 Phase 1 一致)
```bash
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin
npx tsc --noEmit > /tmp/tsc-phase2.log 2>&1
echo "tsc exit code: $?"
# 整体错误数(预期与 Phase 1 的 67 条相近 ± 微小波动;不应大幅增加)
grep -cE "error TS" /tmp/tsc-phase2.log
# 反向断言:本 phase 改动文件零类型错误
grep -E "(lib/permissions\.ts|app/ai-model/page\.tsx)" /tmp/tsc-phase2.log
# 预期:0 行输出(grep 退出码 1 也算 PASS)
```
**判定**:
- tsc 整体退出码可能为 2(存量错误),允许
- 反向 grep 必须 0 行输出(无任何错误指向 `lib/permissions.ts` 或 `app/ai-model/page.tsx`)
- 整体错误数与 Phase 1 收尾时(67 条)相比浮动 ≤ 3 条,否则人工核查是否有新引入
### 验证 B:specifics 11 条 grep 全命中(CONTEXT.md 锁定)
```bash
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin
# specifics 1:PermissionModule union 含 credential-slot
grep -cE "['\"]credential-slot['\"]" lib/permissions.ts # 期望 ≥3
# specifics 2-3:超管 + AI模型管理员含;其他 4 角色不含
awk '/超级管理员: \[/,/\],/' lib/permissions.ts | grep -c "credential-slot" # 期望 1
awk '/AI模型管理员: \[/,/\],/' lib/permissions.ts | grep -c "credential-slot" # 期望 1
awk '/内容管理员: \[/,/\],/' lib/permissions.ts | grep -c "credential-slot" # 期望 0
awk '/卡牌管理员: \[/,/\],/' lib/permissions.ts | grep -c "credential-slot" # 期望 0
awk '/查看者: \[/,/\],/' lib/permissions.ts | grep -c "credential-slot" # 期望 0
# 「管理员」(最末位)匹配,避免被「AI模型管理员」前缀干扰:用末尾段
awk '/^ 管理员: \[/,/\],/' lib/permissions.ts | grep -c "credential-slot" # 期望 0
# specifics 4:getModuleFromPath 行为不变
grep -c '"ai-model": "ai-model"' lib/permissions.ts # 期望 1
# specifics 5:app/ai-model/page.tsx 第 1 行是 "use client"
head -n 1 app/ai-model/page.tsx | grep -c '"use client"' # 期望 1
# specifics 6:含「凭据槽位」文案
grep -c "凭据槽位" app/ai-model/page.tsx # 期望 ≥2(Button + DialogTitle)
# specifics 7:含 KeyRound
grep -c "KeyRound" app/ai-model/page.tsx # 期望 ≥2
# specifics 8:含 hasPermission("credential-slot")
grep -cE "hasPermission\\([\"']credential-slot[\"']\\)" app/ai-model/page.tsx # 期望 ≥1
# specifics 9:含 useState + onClick + Dialog 三连
grep -c "useState" app/ai-model/page.tsx # 期望 ≥3(import + 2 调用)
grep -c "setIsCredentialDialogOpen" app/ai-model/page.tsx # 期望 ≥3
# specifics 10:占位 Dialog 含「通用凭据槽位」DialogTitle
grep -c "通用凭据槽位" app/ai-model/page.tsx # 期望 1
# specifics 11:修改记录顶部含 Phase 2 条目
grep -c "\[2026-05-08\] Phase 2" docs/修改记录.md # 期望 1
```
**判定**:以上 14 条 grep 全部满足预期值 → 11 条 specifics 全命中。
### 验证 C:不引入新依赖
```bash
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin
git diff --name-only -- package.json yarn.lock package-lock.json pnpm-lock.yaml 2>/dev/null
# 期望:0 行输出(4 个文件均未改动)
git diff --name-only HEAD~1 -- package.json yarn.lock package-lock.json pnpm-lock.yaml 2>/dev/null
# 期望:0 行输出(与上一 commit 比较仍无 manifest 改动)
```
**判定**:4 个文件均未出现在 diff → 不引入新依赖。
### 验证 D:(跳过)next lint
`npm run lint`(即 `next lint`)在 Phase 1 已确认会进入「ESLint 未 bootstrap → 交互式 prompt」状态(无 `.eslintrc*` / `eslint-config-next`),按 STATE.md line 81「ESLint 基础设施补齐留给 PERM-06 候选 #3」的判定 → **本 phase 跳过 lint**,与 Phase 1 一致。
在 SUMMARY 中显式记录:「next lint 因项目未 bootstrap ESLint 跳过;判定与 Phase 1 一致;不指向本 phase 改动文件」。
### 失败处理
任一条验证失败 → **不要继续**,立即返回 STATE 报告问题,不可静默继续 SUMMARY 写入;按以下优先级处理:
1. tsc 出现指向 `lib/permissions.ts` 或 `app/ai-model/page.tsx` 的新错误 → 回到 Plan 02-01 任务 1 或 2 修补
2. 反向 grep 在「内容管理员 / 卡牌管理员 / 查看者 / 管理员」数组中命中 `credential-slot` → 回到 Plan 02-01 任务 1 重新核对
3. specifics grep 数量不足 → 回到 Plan 02-01 任务 2 检查 import / JSX
4. package.json / lockfile 出现 diff → 回滚 lockfile(`git checkout HEAD -- package.json yarn.lock package-lock.json pnpm-lock.yaml`)
<acceptance_criteria>
- 验证 A:tsc 整体退出码 2 容许;过滤后 0 行指向 lib/permissions.ts / app/ai-model/page.tsx
- 验证 B:14 条 grep 全部满足预期值(specifics 11 条 + 反向断言 3 条)
- 验证 C:git diff 对 4 个 manifest/lockfile 文件输出 0 行
- 验证 D:在 SUMMARY 中显式记录「next lint 跳过原因」
</acceptance_criteria>
执行完两个任务后,确认以下3 条整体校验通过:
1. 修改记录顶部条目格式
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin
sed -n '/<!-- 新的修改记录添加在此处下方/,/### \[2026-05-08\] Phase 1/p' docs/修改记录.md | head -n 60
判定:输出含完整 Phase 2 条目结构(标题 / 元信息行 / 文件路径 / 修改类型 / 修改内容 / 修改原因 / 跨项目联动 / 服务端联动)+ 紧跟 Phase 1 标题行作为下一条。
2. RBAC 矩阵 5+1 角色逐一确认
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin
echo "应含 credential-slot 的 2 角色:"
awk '/超级管理员: \[/,/\],/' lib/permissions.ts | grep "credential-slot"
awk '/AI模型管理员: \[/,/\],/' lib/permissions.ts | grep "credential-slot"
echo "应不含 credential-slot 的 4 角色:"
for ROLE in 内容管理员 卡牌管理员 查看者; do
HIT=$(awk -v role="$ROLE" '$0 ~ role"\\: \\[" {flag=1} flag {print} /\],/ {flag=0}' lib/permissions.ts | grep -c "credential-slot")
echo "$ROLE: $HIT 次(期望 0)"
done
# 「管理员」单独取末尾段(避免「AI模型管理员」前缀污染)
awk '/^ 管理员: \[/,/\],/' lib/permissions.ts | grep -c "credential-slot"
判定:超管 + AI模型管理员各命中 1 次;其他 4 角色各 0 次。
3. Phase 2 入口控件 + 占位 Dialog 端到端就位
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin
# 整段验证:1) "use client" 顶置;2) 入口 Button 受 mounted + hasPermission 守卫;3) Dialog 在 DashboardShell 末尾
echo "=== line 1 ==="
head -n 1 app/ai-model/page.tsx
echo "=== mounted 守卫 + hasPermission(credential-slot) ==="
grep -nE "mounted &&" app/ai-model/page.tsx
grep -nE "hasPermission\\([\"']credential-slot" app/ai-model/page.tsx
echo "=== Dialog 占位 ==="
grep -nE "<DialogTitle>通用凭据槽位</DialogTitle>" app/ai-model/page.tsx
grep -nE "对话框真实内容由 Phase 3 落地" app/ai-model/page.tsx
判定:所有 5 段输出非空且行号合理("use client" 在 line 1;mounted + hasPermission 守卫在 DashboardHeader 区域;Dialog 在文件末尾区域)。
整体判定:3 条全部通过 → Phase 2 全部交付,可推进 STATE.md → Phase 3 待启动。
<success_criteria> ROADMAP.md Phase 2 Success Criteria 4 条最终确认(前 3 条由 Plan 02-01 主体落地、本 Plan 02-02 验证;第 4 条由 Plan 02-01 落地、本 Plan 02-02 验证;CLAUDE.md 修改记录强制由本 Plan 02-02 落地):
- ✅ 验证:
hasPermission('credential-slot')对超管 + AI模型管理员返回 true、其他角色返回 false(通过本 plan 验证 B 段反向断言确认) - ✅ 验证:
getModuleFromPath('/ai-model')行为不变(通过 grep'ai-model': 'ai-model'仍命中 1 行确认) - ✅ 验证:
/ai-model页面 DashboardHeader 含「凭据槽位」入口控件,未授权角色 DOM 中不存在(通过 mounted + hasPermission 守卫的 grep 命中确认) - ✅ 验证:可见性走
hasPermission('credential-slot')、点击触发占位 Dialog 打开(通过 grepsetIsCredentialDialogOpen(true)+<Dialog open=命中确认) - ✅ CLAUDE.md 修改记录强制:
docs/修改记录.md顶部含 Phase 2 条目,跨项目联动字段写「无」(CONTEXT.md 锁定文案) </success_criteria>
并由后续 /gsd-verify-phase 2(如启用)或人工触发把 STATE.md 推进到 Phase 3 待启动状态。