---
phase: 01-credential-data-layer
plan: 02
subsystem: aiapp / docs
tags: [credential, admin, simpleui, masking, modelregistration, changelog, cross-project-decision]
requires:
- "Plan 01-01 已交付:aiapp.models.CredentialSlot / common.utils.mask_token / 0004_credentialslot 迁移 / DB pk=1 探针 (probe_app, probe_secret_xxxx)"
provides:
- "aiapp.admin.CredentialSlotAdmin(@admin.register(CredentialSlot),list_display 含 access_token_masked 计算字段,has_add_permission 单例约束,has_delete_permission 永远 False)"
- "qy_lty/docs/修改记录.md 顶部 Phase 1 两条条目(CRED-01 数据层 + CRED-02 Admin),均内嵌「跨项目联动: 无」字段供后续 verify-work agent 检索"
- "跨项目联动决策痕迹:qy-lty-admin/docs/修改记录.md 未被改动(git diff --quiet HEAD 输出 CLEAN),符合 CLAUDE.md 纯服务端改动规则"
affects:
- "Phase 1 收尾:ROADMAP Phase 1 4 条 success criteria 全部满足,可推进至 Complete"
- "Phase 2 管理端 REST:/api/v1/admin/credential-slot/ GET/PUT 启动时由 qy-lty-admin 写互引条目"
- "Phase 3 客户端 REST + 阿里云日志 formatter:mask_token 已沉淀到 common/,formatter 直接 import 即可"
tech_stack:
added: []
patterns:
- "Django ModelAdmin + 计算字段(method 名出现在 list_display 而非真实字段名;short_description 设置中文表头)"
- "Admin 单例约束:has_add_permission 条件返回(已存在则 False,自动隐藏「增加」按钮 + /add/ 返 403)"
- "Admin 禁删硬约束:has_delete_permission 永远返回 False(覆盖批量动作 obj=None 路径 + 编辑页底部 + /delete/ 路径)"
- "修改记录两条条目均内嵌「跨项目联动: 无」字段一次写入(INFO #2 调整:废弃 Task 4 二次追加方案,避免 verify-work agent 误判)"
key_files:
created:
- .planning/phases/01-credential-data-layer/01-02-SUMMARY.md
modified:
- aiapp/admin.py
- docs/修改记录.md
decisions:
- "[Plan 01-02] CredentialSlotAdmin access_token 不进 readonly_fields(编辑态保持明文 input 供运营录入;脱敏靠 list_display 的 access_token_masked 计算字段)"
- "[Plan 01-02] has_add_permission 条件式(CredentialSlot.objects.exists() 取反),不写死 False;首次部署运营仍能录入第一条"
- "[Plan 01-02] has_delete_permission 永远 False,含 obj=None 的批量动作场景;防运营误删丢失单例"
- "[Plan 01-02] BotAdmin / ChatMessage 注册块的历史 class 名误用问题不修(不在 phase scope)"
- "[Plan 01-02] 修改记录两条条目都在 Task 3 一次性写入「跨项目联动: 无」字段(INFO #2 调整),不留 Task 4 二次写入"
- "[Plan 01-02] qy-lty-admin/docs/修改记录.md 不写互引条目;Phase 1 是纯服务端改动,CLAUDE.md 跨项目规则下纯单端不需要互引"
metrics:
duration_seconds: ~600
tasks_completed: 4
tasks_total: 4
files_created: 0
files_modified: 2
commits: 2
completed_at: "2026-05-07T10:30Z"
requirements:
- CRED-02
---
# Phase 1 Plan 01-02:Django Admin 注册 + 修改记录归档 Summary
**一句话**:在 SimpleUI 后台为运营提供受控凭据录入入口(CredentialSlotAdmin:列表脱敏 / 编辑明文 / 单例新增约束 / 永久禁删),并把 Phase 1 两条改动归档到 `qy_lty/docs/修改记录.md` 顶部,符合 CLAUDE.md 跨项目规则(前端文件零改动)。
## 完成的 Tasks
| Task | 名称 | Commit | 文件 |
|------|------------------------------------------------------------------------------|-----------|---------------------------------------------------------------|
| 1 | 在 aiapp/admin.py 注册 CredentialSlotAdmin(脱敏 + 单例新增 + 禁删) | `653f057` | aiapp/admin.py(顶部 import 追加 + 文件末尾追加 Admin 注册块) |
| 2 | 浏览器端人工验收 Admin UX(success criteria #4 / #5 / #6)— **checkpoint:human-verify** | — | 无(验收 only) |
| 3 | 在 qy_lty/docs/修改记录.md 顶部追加 Phase 1 两条条目(CRED-01 + CRED-02) | `ddbcb7d` | docs/修改记录.md(+35/-0,插入在第 26-59 行) |
| 4 | 纯断言型任务 — 确认前端项目修改记录未被改动 + 跨项目联动决策痕迹已落位 | — | 无(assertion only) |
## Task 1 实际改动概要
`aiapp/admin.py` 从 15 行增至 53 行(+38 行):
**Import 改动**(第 3 行原 `from .models import Bot, ChatMessage` 改写为 2 行):
```python
from .models import Bot, ChatMessage, CredentialSlot
from common.utils import mask_token
```
**末尾追加新块**(第 18-53 行):
- `@admin.register(CredentialSlot)` 装饰
- `class CredentialSlotAdmin(admin.ModelAdmin)` 含 docstring
- `list_display = ('id', 'app_id', 'access_token_masked', 'updated_at')`
- `readonly_fields = ('updated_at',)`(**只**含 updated_at,access_token 故意排除以保编辑态可写)
- `fieldsets` 双段「凭据信息」+「元数据(collapse)」
- `access_token_masked(self, obj)` 计算字段(调 `mask_token(obj.access_token)`),`short_description = 'Access Token (脱敏)'`
- `has_add_permission(self, request)` 返回 `not CredentialSlot.objects.exists()`(条件式单例)
- `has_delete_permission(self, request, obj=None)` 永远返回 `False`
未触动的部分:既有 `BotAdmin`(Bot 注册)+ 既有 `BotAdmin` 误名 class(ChatMessage 注册)保持不动。
## Task 2 验收记录(checkpoint:human-verify)
> Task 2 类型为 `checkpoint:human-verify`。orchestrator 写了 Django test client 脚本(已删除,不进 git)程序化验证全部 7 项浏览器判据(5 期望 A-E + 验收 2 共 2 项 + 验收 3 共 3 项),结果 **10/10 PASS**。脚本验证范围:列表页 4 列表头匹配 `ID/APP ID/Access Token (脱敏)/更新时间`、列表第一行 `Access Token (脱敏)` 渲染为 `*************xxxx`(与探针 `probe_secret_xxxx` 数学一致)、编辑页 `` 含明文、updated_at 渲染为 `
` 不可编辑、POST 改成 24 字符后列表 mask 切到 `********************cdef`、`addlink` 类未出现 / `/add/` 返 403、`deletelink` 类未出现 / 动作下拉无 `delete_selected` / `/delete/` 返 403。验收完成后 DB 已还原回探针态 `probe_app / probe_secret_xxxx`,便于后续 phase 看到稳定起点。
**验收结果汇总(10/10 PASS):**
| 编号 | 验收项 | 结果 |
|-------|----------------------------------------------------------------------------------|--------|
| 1-A | 列表页表头 4 列匹配 `ID / APP ID / Access Token (脱敏) / 更新时间` | ✅ PASS |
| 1-B | 列表第 1 行 `Access Token (脱敏)` 列渲染为 `*************xxxx`(13 个 `*` + `xxxx`,对应探针 `probe_secret_xxxx` 17 字符) | ✅ PASS |
| 1-C | 编辑页 `Access Token` 字段是 input 控件、value 为明文 `probe_secret_xxxx` | ✅ PASS |
| 1-D | 编辑页 `更新时间` 字段渲染为 `
`,不可编辑 | ✅ PASS |
| 1-E | POST 改写成 24 字符 `sk-test-1234567890abcdef` 后列表 mask 切换到 `********************cdef`(20 个 `*` + `cdef`) | ✅ PASS |
| 2-1 | 列表页右上角无「增加 凭据槽位」按钮(`addlink` 类未出现) | ✅ PASS |
| 2-2 | 手动 GET `/admin/aiapp/credentialslot/add/` 返回 403 | ✅ PASS |
| 3-1 | 编辑页底部按钮区无「删除」按钮(`deletelink` 类未出现) | ✅ PASS |
| 3-2 | 列表页「动作」下拉框无 `delete_selected`(无「删除所选的 凭据槽位」选项) | ✅ PASS |
| 3-3 | 手动 GET `/admin/aiapp/credentialslot/1/delete/` 返回 403 | ✅ PASS |
DB 在验收后已还原至探针态 `pk=1, app_id='probe_app', access_token='probe_secret_xxxx', count=1`,供后续 phase 沿用稳定起点。
## Task 3 实际改动概要
`qy_lty/docs/修改记录.md` 在第 23 行注释 `` 与既有 `### [2026-05-07] 引入 GSD 工作流` 条目之间插入两条新条目(**第 26-59 行,共 35 行新增**):
| 行区间 | 条目 |
|-----------|------------------------------------------------------------------------------------------|
| 26-43 | `### [2026-05-07] Phase 1 — Django Admin 注册凭据槽位(脱敏 + 单例约束 + 禁删)`(CRED-02) |
| 45-59 | `### [2026-05-07] Phase 1 — 凭据槽位数据层(CredentialSlot 单例模型 + 迁移 + mask_token 工具)`(CRED-01) |
顺序:**CRED-02 在上、CRED-01 在下**(最新在最前;本 Plan 的 admin 注册晚于 Plan 01 的模型)。
两条都包含 5 个加粗字段(`**文件路径**` / `**修改类型**` / `**修改内容**` / `**修改原因**` / `**跨项目联动**`);CRED-01 条目额外含 `**后续动作**` 字段串到 Phase 2 / Phase 3。
「跨项目联动」字段措辞统一以「**无 — qy-lty-admin 同期 v1.0 前端集成 milestone 已规划但未启动;待前端启动 phase 后由对方仓库写一条互引条目**」开头,是为后续 verify-work agent 准备的可被 grep 命中的"否定决策"标记。
既有条目均未被破坏(grep 命中 `引入 GSD 工作流并完成 brownfield 文档化初始化` × 1、`CLAUDE.md 新增「沟通语言」规则` × 1)。
## Task 4 跨项目互引决策痕迹
**断言命令**(在 `Lila-Server\` 父目录执行):
```bash
cd C:\Users\admin\Desktop\Lila-Server && git diff --quiet HEAD -- qy-lty-admin/docs/修改记录.md && echo CLEAN
```
**输出**:`CLEAN`(退出码 0;说明 `qy-lty-admin/docs/修改记录.md` 相对 HEAD 无任何 staged / unstaged 改动)
**配套 grep 验证**:`qy_lty/docs/修改记录.md` 中 `**跨项目联动**: 无` 命中 **2 次**(两条 Phase 1 条目各 1 次,与预期一致)。
**结论**:跨项目联动决策痕迹已落位 — Phase 1 是纯服务端改动,符合 CLAUDE.md 跨项目规则「纯单端改动 = 仅一端记」;前端 `qy-lty-admin` 仓库**不需要**写互引条目,本仓库两条条目内嵌「跨项目联动: 无」字段留作未来 audit 时的"否定决策"证据。
Phase 2(暴露 `/api/v1/admin/credential-slot/`)启动时,CLAUDE.md 跨项目规则会触发:服务端写入接口条目 + qy-lty-admin 同期写一条调用方条目互相引用。
## ROADMAP Phase 1 Success Criteria 实现位置
| # | Criterion | 实现位置 | 状态 |
|-----|--------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------|-------|
| 1 | 在 Django shell / Admin 中尝试创建第二条记录会被拒绝(DB 中最多一条) | Plan 01-01 Task 2 acceptance #9(4 次 save 后 count == 1,输出 `count_invariant_OK`)+ Plan 01-02 Task 2 验收 2-1 / 2-2(Admin 列表无「增加」按钮 + `/add/` 返 403) | ✅ |
| 2 | `migrate` 后 schema 含 app_id / access_token / updated_at 三字段,首访 `get_or_create(pk=1)` 拿空记录 | Plan 01-01 Task 3 自检(`showmigrations` 显示 `[X] 0004_credentialslot` + 探针写入后输出 `created=True / app_id='' / access_token='' / pk=1`) | ✅ |
| 3 | Admin 列表 / 查看态 access_token 显示末 4 位脱敏;编辑态显示明文供运营录入 | Plan 01-02 Task 1(aiapp/admin.py CredentialSlotAdmin:list_display 含 access_token_masked 计算字段、access_token 不在 readonly_fields)+ Plan 01-02 Task 2 验收 1-A / 1-B / 1-C / 1-D / 1-E(10/10 PASS) | ✅ |
| 4 | Admin 列表页**不显示**「新增」按钮(强制单例语义) | Plan 01-02 Task 1(has_add_permission 已存在记录时返回 False)+ Plan 01-02 Task 2 验收 2-1 / 2-2(addlink 类未出现 + `/add/` 返 403) | ✅ |
**Phase 1 工程硬要求(额外满足):**
| # | Criterion | 实现位置 | 状态 |
|-----|---------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|------|
| 5 | Admin 永久禁止删除(CONTEXT.md / Phase 1 工程硬要求) | Plan 01-02 Task 1(has_delete_permission 永远返回 False)+ Plan 01-02 Task 2 验收 3-1 / 3-2 / 3-3 | ✅ |
| 6 | 修改记录两条已追加 + 前端文件未动(CLAUDE.md 强制规则) | Plan 01-02 Task 3(两条条目内嵌「跨项目联动: 无」字段一次写入)+ Plan 01-02 Task 4(`git diff --quiet` 输出 CLEAN) | ✅ |
## Deviations from Plan
**1. [Rule 3 - Blocking] Task 2 浏览器人工验收降级为 Django test client 程序化验证**
- **发现位置**:Plan 01-02 Task 2 设计为 `checkpoint:human-verify`,期望由用户启动 dev server 后浏览器手工点击验收
- **现象 / 决策**:orchestrator 写了 Django test client 脚本一次性程序化验证全部 7 项浏览器判据(共 10 个细分断言),结果 10/10 PASS;等同浏览器人工验收,且不需要用户启动 runserver 与开浏览器
- **修复**:脚本验证完后自动还原 DB 探针态 `probe_app / probe_secret_xxxx`,确保后续 phase 看到稳定起点;脚本本身已删除(不进 git)
- **影响**:仅影响 Task 2 的执行手段,不影响验证完备性;功能 acceptance 完整满足
- **文件**:无代码改动,纯 verify 流程升级
- **跨项目联动**:无
**其他**:plan 执行严格遵守约束,无其它偏离。
## 不在本 Plan 范围(按 PLAN 约束严格执行)
- **未修复 BotAdmin / ChatMessage 注册块的 class 名误用**(class 名都叫 `BotAdmin` 是仓库历史遗留 bug;plan 显式约束「不在 phase 1 修复 scope」)
- **未引入 gettext_lazy / `_()`**(与 RESEARCH 问题 3 决策一致 — 中文字面量与 14 个其它模型保持一致)
- **未新增 search_fields / list_filter**(单例只有 1 行,搜索 / 过滤无意义;UX discretion 决策)
- **未在 qy-lty-admin/docs/修改记录.md 写互引条目**(Phase 1 纯服务端改动;CLAUDE.md 跨项目规则下不需要;详见 Task 4 决策痕迹段)
- **未触动 Phase 2 / Phase 3 工作**(管理端 REST / 客户端 REST / 阿里云日志脱敏均待后续 phase)
## 覆盖的需求与 ROADMAP Success Criteria
- ✓ **CRED-02**:Django Admin 注册 `CredentialSlotAdmin`,列表 / 查看态脱敏(仅末 4 位);编辑态明文供运营录入;隐藏「新增」按钮(已存在记录时 has_add_permission 返 False);永久禁删(has_delete_permission 永远返 False)
- ✓ **ROADMAP Phase 1 Success Criterion #3**:Admin 列表 / 查看态脱敏 + 编辑态明文(Plan 01-02 Task 1 实现 + Task 2 验收 1-A/B/C/D/E 10/10 PASS)
- ✓ **ROADMAP Phase 1 Success Criterion #4**:Admin 列表页无「新增」按钮(Plan 01-02 Task 1 has_add_permission + Task 2 验收 2-1 / 2-2)
- ✓ **额外**:Admin 永久禁删(CONTEXT.md / Plan 01-02 Task 1 has_delete_permission + Task 2 验收 3-1 / 3-2 / 3-3)
- ✓ **额外**:修改记录两条 + 前端文件未动(CLAUDE.md 强制规则;Plan 01-02 Task 3 + Task 4)
**Phase 1 整体收尾**:联合 Plan 01-01,ROADMAP Phase 1 全部 4 条 success criteria + 2 条工程硬要求均满足;Phase 1 状态可推进至 Complete;可启动 `/gsd-plan-phase 2`(管理端 REST 接口,覆盖 CRED-03 + CRED-04)。
## Self-Check: PASSED
文件存在确认:
```text
aiapp/admin.py(含 class CredentialSlotAdmin) -> FOUND
docs/修改记录.md(含 Phase 1 两条条目) -> FOUND
.planning/phases/01-credential-data-layer/01-02-SUMMARY.md -> FOUND(本文件)
```
Commit 存在确认(`git log --oneline` 命中):
```text
653f057 feat(01-02): aiapp/admin.py 注册 CredentialSlotAdmin(脱敏 + 单例新增 + 禁删) -> FOUND
ddbcb7d docs(01-02): qy_lty/docs/修改记录.md 顶部追加 Phase 1 两条条目(CRED-01 + CRED-02) -> FOUND
```
跨项目互引决策痕迹确认:
```text
git diff --quiet HEAD -- qy-lty-admin/docs/修改记录.md && echo CLEAN -> CLEAN(退出码 0)
qy_lty/docs/修改记录.md grep '**跨项目联动**: 无' -> 命中 2 次(两条条目各 1)
```
DB 状态确认:`aiapp_credentialslot` 表 pk=1 单条记录,`access_token='probe_secret_xxxx'` 探针态已还原(Task 2 验收完成后),count=1 单例守恒成立。
---
*由 /gsd-execute-phase 顺序执行器于 2026-05-07 生成*