419 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
phase: 02-rbac-ai
plan: 02
type: execute
wave: 2
depends_on:
- "02-01"
files_modified:
- docs/修改记录.md
autonomous: true
requirements:
- CRED-FE-02
- CRED-FE-03
must_haves:
truths:
- "docs/修改记录.md 顶部含一条 [2026-05-08] Phase 2 条目最新在最前紧跟「修改历史」标记之后、Phase 1 [2026-05-08] 条目之前)"
- "Phase 2 条目含完整 5 字段:日期、文件路径、修改类型、修改内容、修改原因,外加跨项目联动 + 服务端联动两个项目惯用扩展字段"
- "Phase 2 条目「文件路径」字段列出本期实际改动的 2 个文件lib/permissions.ts、app/ai-model/page.tsx"
- "「跨项目联动」字段写「无 — Phase 2 是纯前端 RBAC + UI 入口落地,不引入新跨项目契约;后端 commit 46d72b8 已建立的互引仍有效Phase 3 引入实质 PUT 调用时若涉及新契约再评估」CONTEXT.md D-XX 锁定文案)"
- "Phase 2 条目覆盖前端需求CRED-FE-02 + CRED-FE-03在条目元信息行明确列出"
- "整体类型检查 npx tsc --noEmit 整体退出码可能为 2存量错误但 grep 过滤后 0 条指向本 phase 改动的 lib/permissions.ts 与 app/ai-model/page.tsxPhase 1 已建立的判定模式)"
- "package.json / yarn.lock / package-lock.json / pnpm-lock.yaml 4 个 manifest/lockfile 均未改动(不引入新依赖)"
artifacts:
- path: "docs/修改记录.md"
provides: "顶部 Phase 2 修改记录条目"
contains: "[2026-05-08] Phase 2"
min_lines: 100
key_links:
- from: "docs/修改记录.md Phase 2 条目"
to: "Plan 02-01 改动的 lib/permissions.ts + app/ai-model/page.tsx"
via: "「文件路径」字段精确列出"
pattern: "lib/permissions\\.ts|app/ai-model/page\\.tsx"
- from: "docs/修改记录.md Phase 2 条目「服务端联动」字段"
to: "qy_lty/.planning/phases/02-admin-rest commit 46d72b8"
via: "文本引用 commit hash"
pattern: "46d72b8"
---
<objective>
完成 Phase 2 收尾:在 `docs/修改记录.md` 顶部追加 Phase 2 条目CLAUDE.md 强制要求),并执行 plan 级整体验证(双重类型检查 + grep 11 条 specifics 全命中 + 不引入新依赖)。
Purpose满足 CLAUDE.md 项目宪法「修改记录强制」规则;按 Phase 1 已建立的双重验证模式tsc + grep封盘 Phase 2让 STATE.md 可推进到 Phase 3 待启动状态。
Output`docs/修改记录.md`(修改,仅顶部追加;不动其他历史条目)。
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<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
</context>
<interfaces>
<!-- 修改记录头部「修改格式说明」VERIFIED docs/修改记录.md line 9-20 -->
```
### [日期] 修改简述
- **文件路径**: 相对于项目根目录的文件路径
- **修改类型**: 新增 / 修改 / 删除 / 重构 / 修复Bug
- **修改内容**: 具体修改了什么
- **修改原因**: 为什么要做这个修改
```
<!-- Phase 1 条目VERIFIED docs/修改记录.md line 28-48作为本期模板关键扩展字段 -->
Phase 1 条目结构(**直接抄此 7 字段结构**
1. **元信息行**(标题下方紧跟):「配套服务端 Phase: ...」+「覆盖前端需求: CRED-FE-XX」
2. **文件路径**:列出实际改动的所有文件相对路径
3. **修改类型**:新增 / 修改 / 删除 / 重构 / 修复Bug
4. **修改内容**:分点 bullet 描述每个文件做了什么
5. **修改原因**:解释为什么做、对后续 phase 的支撑作用
6. **跨项目联动**项目惯用扩展字段CONTEXT.md 已锁定本期文案)
7. **服务端联动**(项目惯用扩展字段,可与「跨项目联动」复用文案或简短交叉引用)
<!-- 顶部插入位置 -->
`docs/修改记录.md` line 26 是注释 `<!-- 新的修改记录添加在此处下方,最新的在最前面 -->`line 28 是当前最顶 Phase 1 条目 `### [2026-05-08] Phase 1前端凭据槽位 API 客户端`。**新条目必须插入到 line 26 的注释**之后**、line 28 的 Phase 1 条目**之前**。
</interfaces>
<tasks>
<task type="auto" tdd="false">
<name>任务 1docs/修改记录.md 顶部追加 Phase 2 条目</name>
<files>docs/修改记录.md</files>
<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>
<action>
用 Edit 工具对 `docs/修改记录.md` 做**1 处**精确插入:在 line 26 的锚点注释 `<!-- 新的修改记录添加在此处下方,最新的在最前面 -->` 之后、line 28 的 `### [2026-05-08] Phase 1前端凭据槽位 API 客户端` 之前,插入完整 Phase 2 条目。
**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 模板的写法 = 仅列被修改的代码文件)
</action>
<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>
<verify>
<automated>
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin && grep -c "\[2026-05-08\] Phase 2" docs/修改记录.md
# 预期1
</automated>
<automated>
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin && grep -c "\[2026-05-08\] Phase 1" docs/修改记录.md
# 预期1Phase 1 条目仍存在)
</automated>
<automated>
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin && awk '/### \[2026-05-08\] Phase 2/{p2=NR} /### \[2026-05-08\] Phase 1/{p1=NR} END{ if(p2>0 && p1>0 && p2<p1) print "PASS"; else print "FAIL p2="p2" p1="p1 }' docs/修改记录.md
# 预期PASS
</automated>
<automated>
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin && grep -c "CRED-FE-02" docs/修改记录.md
# 预期≥1
</automated>
</verify>
<done>
- `docs/修改记录.md` 顶部新增 Phase 2 条目(最新在最前)
- 条目含 5 个标准字段(文件路径 / 修改类型 / 修改内容 / 修改原因)+ 2 个项目惯用扩展字段(跨项目联动 / 服务端联动)+ 元信息行(覆盖前端需求 + 配套服务端 Phase
- 「跨项目联动」字段文案逐字与 CONTEXT.md 锁定一致
- Phase 1 条目以及更早的所有历史条目逐字不变
</done>
</task>
<task type="auto" tdd="false">
<name>任务 2plan 级整体双重验证tsc + grep + 不引入新依赖)</name>
<files></files>
<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>
<action>
本任务**不修改任何代码或配置文件**,仅执行验证命令并把结果汇总写入 `02-02-SUMMARY.md`(在 Plan 02-02 收尾时由 execute-plan 流程统一写)。
按以下顺序执行**4 大类**验证命令,逐条记录退出码与命中数:
### 验证 ATypeScript 编译(双重判定,与 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 条,否则人工核查是否有新引入
### 验证 Bspecifics 11 条 grep 全命中CONTEXT.md 锁定)
```bash
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin
# specifics 1PermissionModule 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 4getModuleFromPath 行为不变
grep -c '"ai-model": "ai-model"' lib/permissions.ts # 期望 1
# specifics 5app/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 # 期望 ≥2Button + 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 # 期望 ≥3import + 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`
</action>
<acceptance_criteria>
- 验证 Atsc 整体退出码 2 容许;过滤后 0 行指向 `lib/permissions.ts` / `app/ai-model/page.tsx`
- 验证 B14 条 grep 全部满足预期值specifics 11 条 + 反向断言 3 条)
- 验证 C`git diff` 对 4 个 manifest/lockfile 文件输出 0 行
- 验证 D在 SUMMARY 中显式记录「next lint 跳过原因」
</acceptance_criteria>
<verify>
<automated>
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin && npx tsc --noEmit 2>&1 | grep -cE "(lib/permissions\.ts|app/ai-model/page\.tsx)"
# 预期0
</automated>
<automated>
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin && (awk '/超级管理员: \[/,/\],/' lib/permissions.ts | grep -c "credential-slot") && (awk '/AI模型管理员: \[/,/\],/' lib/permissions.ts | grep -c "credential-slot")
# 预期:两条命令各输出 1
</automated>
<automated>
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin && for ROLE in 内容管理员 卡牌管理员 查看者; do echo "$ROLE: $(awk -v role="$ROLE" '$0 ~ role"\\: \\[" {flag=1} flag {print} /\],/ {flag=0}' lib/permissions.ts | grep -c "credential-slot")"; done
# 预期3 行各为 X: 0
</automated>
<automated>
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin && git diff --name-only -- package.json yarn.lock package-lock.json pnpm-lock.yaml | wc -l
# 预期0
</automated>
<automated>
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin && head -n 1 app/ai-model/page.tsx
# 预期:含 "use client"
</automated>
<automated>
cd C:\Users\admin\Desktop\Lila-Server\qy-lty-admin && grep -c "\[2026-05-08\] Phase 2" docs/修改记录.md
# 预期1
</automated>
</verify>
<done>
- 4 大类验证全部通过A / B / C / D
- 11 条 specifics 全命中,反向断言(其他 4 角色数组不含 `credential-slot`)通过
- tsc 不引入指向本 phase 改动文件的新错误
- 不引入新依赖4 个 manifest/lockfile 均未改动)
- SUMMARY 中明确记录 next lint 跳过原因(与 Phase 1 一致)
</done>
</task>
</tasks>
<verification>
## Plan 级整体验证(覆盖整个 Phase 2
执行完两个任务后,确认以下**3 条**整体校验通过:
### 1. 修改记录顶部条目格式
```bash
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 角色逐一确认
```bash
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 端到端就位
```bash
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 1mounted + hasPermission 守卫在 DashboardHeader 区域Dialog 在文件末尾区域)。
**整体判定**3 条全部通过 → Phase 2 全部交付,可推进 STATE.md → Phase 3 待启动。
</verification>
<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 落地):**
1. ✅ 验证:`hasPermission('credential-slot')` 对超管 + AI模型管理员返回 true、其他角色返回 false通过本 plan 验证 B 段反向断言确认)
2. ✅ 验证:`getModuleFromPath('/ai-model')` 行为不变(通过 grep `'ai-model': 'ai-model'` 仍命中 1 行确认)
3. ✅ 验证:`/ai-model` 页面 DashboardHeader 含「凭据槽位」入口控件,未授权角色 DOM 中不存在(通过 mounted + hasPermission 守卫的 grep 命中确认)
4. ✅ 验证:可见性走 `hasPermission('credential-slot')`、点击触发占位 Dialog 打开(通过 grep `setIsCredentialDialogOpen(true)` + `<Dialog open=` 命中确认)
5. ✅ CLAUDE.md 修改记录强制:`docs/修改记录.md` 顶部含 Phase 2 条目跨项目联动字段写「无」CONTEXT.md 锁定文案)
</success_criteria>
<output>
完成后由 execute-plan 自动创建:
- `.planning/phases/02-rbac-ai/02-01-SUMMARY.md`Plan 02-01 收尾摘要RBAC 矩阵 + 入口控件 + Dialog 落地)
- `.planning/phases/02-rbac-ai/02-02-SUMMARY.md`Plan 02-02 收尾摘要:修改记录追加 + 双重验证结果,含 next lint 跳过说明)
并由后续 `/gsd-verify-phase 2`如启用或人工触发把 STATE.md 推进到 Phase 3 待启动状态
</output>