424 lines
32 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: 01-credential-data-layer
plan: 02
type: execute
wave: 2
depends_on:
- 01-01
files_modified:
- aiapp/admin.py
- docs/修改记录.md
autonomous: false
requirements:
- CRED-02
must_haves:
truths:
- "Django Admin 列表页 /admin/aiapp/credentialslot/ 中 access_token 列显示为末 4 位掩码(如 '*********1234'"
- "Admin 编辑页 /admin/aiapp/credentialslot/1/change/ 中 access_token 字段是 input 控件,预填明文(运营可改写)"
- "已存在 1 条记录时Admin 列表页右上角无「增加 凭据槽位」按钮"
- "Admin 编辑页底部无「删除」按钮;批量动作下拉无「删除所选的 凭据槽位」选项"
- "qy_lty/docs/修改记录.md 顶部存在两条 [日期] Phase 1 — ... 条目CRED-01 数据层 + CRED-02 Admin 各一条),位于第 23 行注释 `<!-- 新的修改记录添加在此处下方,最新的在最前面 -->` 之下;两条都包含『跨项目联动: 无』字段"
- "qy-lty-admin/docs/修改记录.md 不被改动(本 phase 是纯服务端改动CLAUDE.md 跨项目规则下,纯服务端不需要在前端写互引条目)"
artifacts:
- path: aiapp/admin.py
provides: "CredentialSlotAdmin脱敏 + 单例新增约束 + 禁删)"
contains: "class CredentialSlotAdmin(admin.ModelAdmin)"
- path: docs/修改记录.md
provides: "Phase 1 两条修改记录条目CRED-01 + CRED-02均含『跨项目联动: 无』"
contains: "Phase 1 — 凭据槽位数据层"
key_links:
- from: aiapp/admin.py CredentialSlotAdmin.access_token_masked
to: common.utils.mask_token
via: "from common.utils import mask_tokenpattern 是正则;用 grep -E 匹配fixed string 也可命中)"
pattern: "from common.utils import mask_token"
- from: aiapp/admin.py CredentialSlotAdmin
to: aiapp.models.CredentialSlot
via: "@admin.register(CredentialSlot)pattern 是正则,括号已转义;用 grep -E 匹配,**不要**用 grep -F 或纯字面量匹配(否则反斜杠会被当字面量)"
pattern: "@admin.register\\(CredentialSlot\\)"
- from: aiapp/admin.py CredentialSlotAdmin.has_add_permission
to: 单例语义
via: "已存在记录时返回 False按钮自动隐藏pattern 是正则;用 grep -E 匹配"
pattern: "return not CredentialSlot.objects.exists"
---
<objective>
完成 Milestone v1.0 / Phase 1 的 Admin 接入与文档归档:
- 在 aiapp/admin.py 注册 `CredentialSlotAdmin`:列表 / 查看态脱敏(仅末 4 位);编辑态明文供运营录入;隐藏「增加」按钮;禁止删除(覆盖 CRED-02
- 浏览器端通过 SimpleUI 主题做一次人工 checkpoint验收 ROADMAP Phase 1 success criteria 第 4、5、6 条
- 在 qy_lty/docs/修改记录.md 顶部追加 2 条修改记录条目CRED-01 数据层 + CRED-02 Admin含『跨项目联动: 无』字段,满足 CLAUDE.md 强制规则
Purpose让运营能在 SimpleUI 后台安全地录入第三方服务凭据,且 DB 单例语义不会被运营误操作破坏;同时把 Phase 1 的 codebase 改动正式归档到修改记录。
Output1 个文件追加aiapp/admin.py 末尾追加 CredentialSlotAdmin 与新 import+ 1 个文件追加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/REQUIREMENTS.md
@.planning/phases/01-credential-data-layer/01-CONTEXT.md
@.planning/phases/01-credential-data-layer/01-RESEARCH.md
@CLAUDE.md
@aiapp/admin.py
@aiapp/models.py
@common/utils.py
@docs/修改记录.md
<interfaces>
<!-- 关键已存在符号 / 文件状态executor 不需要再去 grep -->
来自 Plan 01 已交付前置依赖executor 应能直接 import
```python
# aiapp/models.pyPlan 01 追加)
class CredentialSlot(models.Model):
app_id = models.CharField('APP ID', max_length=128, blank=True, default='')
access_token = models.CharField('Access Token', max_length=512, blank=True, default='')
updated_at = models.DateTimeField('更新时间', auto_now=True)
@classmethod
def get_solo(cls): ...
# common/utils.pyPlan 01 新建)
def mask_token(token: str, visible_tail: int = 4, mask_char: str = '*') -> str: ...
```
**Plan 01 探针数据契约**Plan 01 Task 3 在 DB 留下的探针记录):
- pk=1`app_id='probe_app'``access_token='probe_secret_xxxx'`17 字符)
- 经 mask_token(visible_tail=4) 处理后,列表页应显示 `*************xxxx`13 个 `*` + `xxxx`
- 注:跨 session 执行可能此值已被改写Task 2 浏览器 checkpoint 前置准备段会用 shell 探针读出实际 access_token 再按实际值算 mask 期望串
来自 aiapp/admin.py 当前 15 行内容executor 必须保留这两个既有 ModelAdmin、仅追加不重写
```python
from django.contrib import admin
from .models import Bot, ChatMessage
@admin.register(Bot)
class BotAdmin(admin.ModelAdmin):
list_display = ('id', 'name', 'description')
search_fields = ('id', 'name', 'description')
@admin.register(ChatMessage)
class BotAdmin(admin.ModelAdmin): # 注:仓库现状的 class 名误用 BotAdminexecutor 不要顺手改 — 不在 phase scope
list_display = ('id', 'user', 'bot', 'message', 'timestamp', 'sender', 'message_type')
search_fields = ('id', 'user', 'bot', 'message', 'timestamp', 'sender', 'message_type')
```
来自 docs/修改记录.md 文件结构(追加位置定位):
```
第 1 行: # 服务器端代码修改记录
第 7-19 行:## 修改格式说明(含模板代码块)
第 22 行: ## 修改历史
第 23 行: <!-- 新的修改记录添加在此处下方,最新的在最前面 -->
第 24 行: (空行)
第 26 行: ### [2026-05-07] 引入 GSD 工作流并完成 brownfield 文档化初始化
```
新条目必须插在第 23 行注释下、第 26 行既有最新条目之上(即变成新的"最前")。
来自 qy-lty-admin/docs/修改记录.md路径 ../qy-lty-admin/docs/修改记录.md**本 plan 不动**
- 已存在该文件,格式与本仓库一致
- 本 phase 是纯服务端改动(无前端联动),按 CLAUDE.md 规则**不需要**在前端写互引条目
- Task 3 在两条新条目中各加一行『跨项目联动: 无 — ...』字段留下决策痕迹INFO #2 调整:本字段从原 Task 4 合并到 Task 3 模板,避免对刚写入产物的二次修改)
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1在 aiapp/admin.py 注册 CredentialSlotAdmin脱敏 + 单例新增 + 禁删)</name>
<files>aiapp/admin.py</files>
<read_first>
- aiapp/admin.py当前 15 行executor 须保留 BotAdmin 与 ChatMessage 注册不动,仅追加)
- aiapp/models.py确认 CredentialSlot 已由 Plan 01 落地)
- common/utils.py确认 mask_token 可 import
- userapp/admin.py看本仓库 ModelAdmin fieldsets / readonly_fields 写法风格)
- .planning/phases/01-credential-data-layer/01-RESEARCH.md「问题 3」「问题 4」「陷阱 2」「陷阱 3」段脱敏字段必须在 list_display 用方法名而非真实字段名access_token 不可放 readonly_fields
</read_first>
<action>
修改 `aiapp/admin.py` 两处:
**(1) 第 3 行原 import**
```python
from .models import Bot, ChatMessage
```
改写为:
```python
from .models import Bot, ChatMessage, CredentialSlot
from common.utils import mask_token
```
**(2) 在文件末尾(第 15 行 ChatMessage 注册块之后)追加完整新块**
```python
@admin.register(CredentialSlot)
class CredentialSlotAdmin(admin.ModelAdmin):
"""通用凭据槽位 Admin单例— Milestone v1.0 / Phase 1
UX 行为:
- 列表 / 查看态 access_token 显示末 4 位掩码
- 编辑表单 access_token 明文(运营录入需要)
- 已存在记录时隐藏「增加」按钮
- 永远禁止删除(防运营误操作丢失单例)
"""
list_display = ('id', 'app_id', 'access_token_masked', 'updated_at')
readonly_fields = ('updated_at',)
fieldsets = (
('凭据信息', {
'fields': ('app_id', 'access_token'),
'description': '第三方服务商分配的 APP ID + Access Token保存后立即对手机端 / 设备端生效',
}),
('元数据', {
'fields': ('updated_at',),
'classes': ('collapse',),
}),
)
def access_token_masked(self, obj):
return mask_token(obj.access_token)
access_token_masked.short_description = 'Access Token (脱敏)'
def has_add_permission(self, request):
# 已存在记录时隐藏「增加」,配合 has_delete_permission 强制单例
return not CredentialSlot.objects.exists()
def has_delete_permission(self, request, obj=None):
# 永远禁止删除(含批量动作)
return False
```
关键约束(违反任一即失败):
- **不**把 `access_token` 放进 `readonly_fields`(否则编辑表单也会变只读,运营无法录入;见 RESEARCH 陷阱 2
- **不**把 `access_token_masked` 放进 `fields` / `fieldsets`(计算字段只用于 list_display见 RESEARCH 模式 2
- `has_add_permission` 必须是"已存在则 False"的条件式写法,**不**得永远返回 False否则首次部署运营无法录入第一条
- `has_delete_permission` 必须永远返回 False含 obj=None 的批量动作场景;见 RESEARCH 陷阱 3
- **不**重写 BotAdmin / ChatMessage 注册块(仓库现状的 class 名 `class BotAdmin(admin.ModelAdmin):` 用于 ChatMessage 是历史遗留,**不在 phase 1 修复 scope**
- **不**用 `gettext_lazy` / `_()`(与 RESEARCH 问题 3 决策一致 — 中文字面量与 14 个其它模型保持一致)
- **不**新增 search_fields / list_filter单例只有 1 行,搜索 / 过滤无意义UX discretion 决策)
</action>
<verify>
<automated>cd C:\Users\admin\Desktop\Lila-Server\qy_lty && python -c "import django, os; os.environ.setdefault('DJANGO_SETTINGS_MODULE','qy_lty.settings'); django.setup(); from django.contrib import admin; from aiapp.models import CredentialSlot; ma = admin.site._registry[CredentialSlot]; assert 'access_token_masked' in ma.list_display; assert 'access_token' not in ma.readonly_fields; assert ma.has_delete_permission(None, None) is False; from aiapp.admin import CredentialSlotAdmin; print('OK')"</automated>
</verify>
<acceptance_criteria>
- grep `class CredentialSlotAdmin(admin.ModelAdmin):` 在 `aiapp/admin.py` 命中 1 次
- grep `from common.utils import mask_token` 在 `aiapp/admin.py` 命中 1 次
- grep `from .models import Bot, ChatMessage, CredentialSlot` 在 `aiapp/admin.py` 命中 1 次
- grep `return not CredentialSlot.objects.exists` 在 `aiapp/admin.py` 命中 1 次
- grep `def has_delete_permission` 在 `aiapp/admin.py` 命中 1 次,函数体含 `return False`
- grep `'access_token'` 在 `readonly_fields = ` 同一行 0 命中access_token 不在 readonly
- grep `access_token_masked.short_description` 在 `aiapp/admin.py` 命中 1 次
- 上面 verify 命令退出码 0、输出 OK`has_delete_permission(None, None)` 显式传入 request=None 与 obj=None 两个位置参数,避免依赖默认值,更鲁棒)
- aiapp/admin.py 仍然包含 `@admin.register(Bot)` 与 `@admin.register(ChatMessage)`grep 各 1 命中,旧注册未被破坏)
</acceptance_criteria>
<done>verify 命令打印 OK以上 9 条 acceptance criteria 全部满足。</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 2浏览器端人工验收 Admin UXsuccess criteria #4 / #5 / #6</name>
<what-built>
Plan 01 已落地 CredentialSlot 单例模型 + 迁移 + mask_token
本 Plan 上一 task 已注册 CredentialSlotAdmin脱敏 + 单例新增约束 + 禁删)。
现在需要人工在浏览器中验收 ROADMAP Phase 1 的三个 UI 行为类 success criteria。
</what-built>
<how-to-verify>
**前置准备**
1. 确保已有 superuser如无cd 到仓库根目录跑 `python manage.py createsuperuser` 临时建一个)
2. 启动开发服务器:`cd C:\Users\admin\Desktop\Lila-Server\qy_lty && python manage.py runserver` 或 `./run.sh`(生产用 daphne
3. 浏览器访问 `http://localhost:8000/admin/`,用 superuser 登录
4. **读取探针数据当前实际值并算出脱敏期望串**(避免硬编码字符串与 DB 实际状态不符):
```bash
python manage.py shell -c "from aiapp.models import CredentialSlot; from common.utils import mask_token; obj = CredentialSlot.objects.filter(pk=1).first(); print('CURRENT app_id =', repr(obj.app_id) if obj else None); print('CURRENT access_token =', repr(obj.access_token) if obj else None); print('EXPECTED masked =', repr(mask_token(obj.access_token)) if obj else None)"
```
期望输出Plan 01 Task 3 探针未被改写时):
```
CURRENT app_id = 'probe_app'
CURRENT access_token = 'probe_secret_xxxx'
EXPECTED masked = '*************xxxx'
```
后续验收 1 「期望 B」「期望 C」「期望 E」用此处打印的 `EXPECTED masked` / `CURRENT access_token` 实际值替代下文的样板字符串。
**验收 1success criterion #4 — 列表/编辑态脱敏行为)**
- 访问 `http://localhost:8000/admin/aiapp/credentialslot/`
- **期望 A**:列表页表头含 `Id / APP ID / Access Token (脱敏) / 更新时间` 四列
- **期望 B**:列表页第 1 行的 `Access Token (脱敏)` 列显示**应等于**上面前置准备打印的 `EXPECTED masked` 值。
- 默认场景探针未被改写access_token 为 `probe_secret_xxxx`17 字符mask_token(visible_tail=4) 返回 `*************xxxx`13 个 `*` + `xxxx`)。
- 校验方法:数显示串的字符总数应等于 `len(CURRENT access_token)`(即 17其中末 4 位为明文 `xxxx`、前 13 位全为 `*`。**严禁**与"15 星 + xxxx"或"11 星 + xxxx"等任何与实际探针长度不符的字符串比对。
- 点击该行进入编辑页 `http://localhost:8000/admin/aiapp/credentialslot/1/change/`
- **期望 C**:编辑表单中 `Access Token` 字段是普通 input**预填明文**(值等于前置准备打印的 `CURRENT access_token`,默认场景下为 `probe_secret_xxxx`)— 不是脱敏文本
- **期望 D**:编辑表单中 `更新时间` 字段为只读(不可编辑)
- 在编辑表单把 `APP ID` 改为 `kimi_test`、`Access Token` 改为 `sk-test-1234567890abcdef`,点保存
- **期望 E**:保存后回到列表页,`Access Token (脱敏)` 列显示 `********************cdef`(共 24 字符 = 20 个 `*` + 末 4 位 `cdef`;因为 `sk-test-1234567890abcdef` 长度为 24
**验收 2success criterion #5 — 列表页无「增加」按钮)**
- 当前列表页右上角应**无**「增加 凭据槽位」按钮DB 中已有 1 条记录,`has_add_permission` 返回 False
- **期望**:手动访问 `http://localhost:8000/admin/aiapp/credentialslot/add/` 应返回 403 Forbidden 或被重定向到列表页Django 默认行为)
**验收 3success criterion #6 — 编辑页无「删除」按钮 + 批量动作无「删除所选」)**
- 在编辑页 `http://localhost:8000/admin/aiapp/credentialslot/1/change/` 底部按钮区域应**无**「删除」按钮(仅有「保存」「保存并继续编辑」「保存并增加另一个」之类的保存按钮族)
- 回到列表页,顶部「动作」下拉框应**无**「删除所选的 凭据槽位」选项(应只有空选项或其它非删除动作)
- **期望**:手动访问 `http://localhost:8000/admin/aiapp/credentialslot/1/delete/` 应返回 403 Forbidden
**如发现任一期望不满足**,把现象 + 截图 / 错误信息描述清楚后回报executor 会回到 Task 1 修复后再次进入本 checkpoint。
</how-to-verify>
<resume-signal>输入 "approved"5 条期望全部通过);或描述未通过的期望 + 现象 + 截图(驳回到 Task 1 修复)</resume-signal>
</task>
<task type="auto">
<name>Task 3在 qy_lty/docs/修改记录.md 顶部追加 Phase 1 两条条目CRED-01 + CRED-02含『跨项目联动』字段</name>
<files>docs/修改记录.md</files>
<read_first>
- docs/修改记录.md 第 1-50 行(确认追加位置 = 第 23 行注释 `<!-- 新的修改记录添加在此处下方,最新的在最前面 -->` 之下;确认格式 = 第 7-19 行说明段;确认风格 = 第 26 行 / 第 47 行的既有条目示例)
- .planning/phases/01-credential-data-layer/01-RESEARCH.md「问题 6」段修改记录格式 + Phase 1 推荐两条条目模板)
- CLAUDE.md 第 256-281 行(修改记录硬规则 + 跨项目联动规则)
</read_first>
<action>
**当前日期请用 `${TODAY}`**executor 在落地时替换为实际日期 `YYYY-MM-DD`(系统时间今日为 `2026-05-07`,若 executor 执行日期不同请用执行当日)。
在 `docs/修改记录.md` 第 23 行注释 `<!-- 新的修改记录添加在此处下方,最新的在最前面 -->` 与第 26 行既有最新条目 `### [2026-05-07] 引入 GSD 工作流` 之间,插入以下两条新条目(**条目顺序CRED-02 在最上方CRED-01 紧随其后;这样从上往下读最新在最前**,与文件约定一致 — Plan 02 是更新的提交)。
**重要INFO #2 调整)**:两条条目都必须直接包含 `- **跨项目联动**: 无 — ...` 字段(合在本 Task 一次写入;原计划由 Task 4 二次追加该字段,已废弃 — 那种二次写入会让 verify-work agent 误判 Task 3 失败)。
模板:
```markdown
### [${TODAY}] Phase 1 — Django Admin 注册凭据槽位(脱敏 + 单例约束 + 禁删)
配套 Phase[.planning/phases/01-credential-data-layer/](.planning/phases/01-credential-data-layer/)
覆盖需求CRED-02
- **文件路径**: `aiapp/admin.py`(修改 — 顶部 import 追加 `CredentialSlot` 与 `mask_token`,文件末尾追加 `CredentialSlotAdmin` 注册)
- **修改类型**: 新增
- **修改内容**:
- 注册 `CredentialSlotAdmin``list_display = ('id', 'app_id', 'access_token_masked', 'updated_at')`,其中 `access_token_masked` 是计算字段(调 `common.utils.mask_token` 仅显示末 4 位掩码)
- `fieldsets` 分「凭据信息」(`app_id` / `access_token` 明文可写)+「元数据」(`updated_at` 只读、可折叠)
- 重写 `has_add_permission`:已存在记录时返回 `False`Admin 列表页隐藏「增加」按钮,强制单例语义)
- 重写 `has_delete_permission`:永远返回 `False`(含批量动作;防运营误删丢失单例)
- 不修改既有 `BotAdmin` / `ChatMessageAdmin` 注册块
- **修改原因**: CRED-02 — 在 SimpleUI 后台为运营提供受控的凭据录入入口;列表 / 查看态脱敏防截图 / 录屏泄露;编辑态保留明文供录入;新增 / 删除按钮隐藏强制单例语义不被运营误操作破坏
- **跨项目联动**: 无 — 本改动仅触及服务端 Django Admin运营访问 `/admin/aiapp/credentialslot/` 直接录入),与 `qy-lty-admin/`Web 管理后台前端)无 API 联动CLAUDE.md 跨项目规则下纯服务端改动不需要在 `qy-lty-admin/docs/修改记录.md` 写互引条目。Phase 2 暴露 `/api/v1/admin/credential-slot/` 接口时再做前后端联动。
### [${TODAY}] Phase 1 — 凭据槽位数据层CredentialSlot 单例模型 + 迁移 + mask_token 工具)
配套 Phase[.planning/phases/01-credential-data-layer/](.planning/phases/01-credential-data-layer/)
覆盖需求CRED-01
设计参考1:1 复刻 `userapp.models.AffinitySetting``userapp/models.py:247-314`)的 pk=1 + `save()` 钩子 + `get_solo()` 单例三件套
- **文件路径**:
- `common/utils.py`(新增 — `mask_token(token, visible_tail=4)` 工具函数,供本 Phase Admin 与 Phase 3 阿里云日志 formatter 共用)
- `aiapp/models.py`(修改 — 文件末尾追加 `CredentialSlot` 模型3 字段 + save 钩子 + `get_solo` 类方法)
- `aiapp/migrations/0004_credentialslot.py`(新增 — `python manage.py makemigrations aiapp` 自动生成)
- **修改类型**: 新增
- **修改内容**:
- 新增 `CredentialSlot` 模型aiapp app`app_id` CharField(128, blank=True, default='')、`access_token` CharField(512, blank=True, default='')、`updated_at` DateTimeField(auto_now=True)`save()` 钩子在已有记录时把新对象 pk 改为现有那条;`get_solo()` 类方法走 `get_or_create(pk=1)`
- 新增 `common.utils.mask_token(token, visible_tail=4, mask_char='*')`:空输入返回 `''`;短于 visible_tail 时全脱敏不暴露长度;其余保留末 N 位明文
- 自动生成迁移 `aiapp/migrations/0004_credentialslot.py``python manage.py migrate` 通过;首次访问 `CredentialSlot.objects.get_or_create(pk=1)` 拿到一条空记录
- **修改原因**: Milestone v1.0「通用凭据槽位APP ID + Access Token」Phase 1 — 在 DB 层落地全局单例的凭据存储槽位,为 Phase 2 管理端 REST、Phase 3 客户端 REST + 日志脱敏奠基mask_token 抽到 `common/` 让 Phase 3 阿里云日志 formatter 直接复用,避免重复实现
- **后续动作**: Phase 2 暴露 `/api/v1/admin/credential-slot/` GET脱敏 / PUT覆写Phase 3 暴露 `/api/credential-slot/` GET 明文 + 阿里云日志 formatter 用 `mask_token` 过滤 `access_token` 字段
- **跨项目联动**: 无 — 本改动是纯数据层 + 工具函数,无任何 HTTP / WebSocket 接口暴露,`qy-lty-admin` 与 Unity 客户端均无感知;不需要在前端写互引条目。
```
(执行规则:上面整段连同两条条目一起插入;条目之间保留一个空行;最后一条条目与既有 `### [2026-05-07] 引入 GSD 工作流` 条目之间也保留一个空行)
关键约束(违反任一即失败):
- **不**修改 / 删除 / 重排第 1-23 行的文件头说明与 `## 修改历史` 标题与定位注释
- **不**修改 / 删除 / 重排已有的 `### [2026-05-07] 引入 GSD 工作流...` 与 `### [2026-05-07] CLAUDE.md 新增...` 与 `### [2026-04-24] 好感度系统 P1...` 等任何既有条目
- 两条新条目顺序:**CRED-02 在上、CRED-01 在下**(最新在最前;本 Plan 的 admin 注册晚于 Plan 01 的模型)
- 日期格式严格 `[YYYY-MM-DD]` — 与既有条目一致(**不**用 `[2026-5-7]` 单数字月日)
- 「修改类型」必须是说明段第 13 行预设值之一(新增 / 修改 / 删除 / 重构 / 修复Bug本 phase 用「新增」
- 不在条目里塞图片 / base64 / 大段代码块 — 这是变更日志,不是设计文档
- 两条条目都必须包含 `- **跨项目联动**: 无 — ...` 字段(**本 Task 一次写入完整模板**;不要预留为空让 Task 4 后补)
- 「跨项目联动」字段措辞与上面模板一致,不要意译 / 简化(这段措辞是为后续 verify-work agent 准备的、可被 grep 命中的"否定决策"标记)
</action>
<verify>
<automated>cd C:\Users\admin\Desktop\Lila-Server\qy_lty && python -c "t = open('docs/修改记录.md', encoding='utf-8').read(); assert 'Phase 1 — Django Admin 注册凭据槽位' in t; assert 'Phase 1 — 凭据槽位数据层' in t; assert 'CRED-01' in t; assert 'CRED-02' in t; assert '引入 GSD 工作流' in t; assert t.index('Django Admin 注册凭据槽位') < t.index('凭据槽位数据层CredentialSlot'); assert t.index('凭据槽位数据层CredentialSlot') < t.index('引入 GSD 工作流'); admin_block = t[t.index('Phase 1 — Django Admin'):t.index('Phase 1 — 凭据槽位数据层')]; data_block = t[t.index('Phase 1 — 凭据槽位数据层'):t.index('引入 GSD 工作流')]; assert '**跨项目联动**: 无' in admin_block, 'Admin 条目缺少跨项目联动'; assert '**跨项目联动**: 无' in data_block, '数据层条目缺少跨项目联动'; assert 'qy-lty-admin' in admin_block; assert 'Phase 2 暴露' in admin_block; print('OK')"</automated>
</verify>
<acceptance_criteria>
- grep `Phase 1 — Django Admin 注册凭据槽位(脱敏 + 单例约束 + 禁删)` 在 `docs/修改记录.md` 命中 1 次
- grep `Phase 1 — 凭据槽位数据层CredentialSlot 单例模型 + 迁移 + mask_token 工具)` 在 `docs/修改记录.md` 命中 1 次
- grep `引入 GSD 工作流并完成 brownfield 文档化初始化` 在 `docs/修改记录.md` 仍命中 1 次(旧条目未被破坏)
- grep `CLAUDE.md 新增「沟通语言」规则` 在 `docs/修改记录.md` 仍命中 1 次(旧条目未被破坏)
- grep `**跨项目联动**: 无` 在 `docs/修改记录.md` 命中 2 次(两条 Phase 1 条目各 1
- grep `qy-lty-admin` 在 Admin 条目内命中至少 1 次(出现在跨项目联动字段措辞中)
- grep `Phase 2 暴露` 在 Admin 条目内命中 1 次
- 上面 verify 的 Python 一行命令退出码 0、输出 OK含顺序断言Admin 条目在 数据层 条目之上、数据层 条目在 GSD 条目之上;以及两条都含『跨项目联动: 无』)
- 文件首行仍为 `# 服务器端代码修改记录`
- 文件第 23 行附近仍含注释 `<!-- 新的修改记录添加在此处下方,最新的在最前面 -->`
- 两条新条目都包含 `- **文件路径**:` `- **修改类型**:` `- **修改内容**:` `- **修改原因**:` `- **跨项目联动**:` 五个加粗字段
</acceptance_criteria>
<done>verify 命令打印 OK以上 11 条 acceptance criteria 全部满足;既有条目与文件头说明区均未被破坏;两条新条目都内嵌『跨项目联动: 无』字段(无需 Task 4 二次追加)。</done>
</task>
<task type="auto">
<name>Task 4纯断言型任务 — 确认前端项目修改记录未被改动 + 跨项目联动决策痕迹已落位</name>
<files></files>
<read_first>
- CLAUDE.md 第 269-281 行(「跨项目联动」规则:服务端接口 + 管理后台调用 = 两端各写一条相互引用;纯单端改动 = 仅一端记)
- .planning/phases/01-credential-data-layer/01-CONTEXT.md `<domain>` 段(明确 Phase 1 仅数据层 + Admin**无任何前端联动**
- docs/修改记录.md确认 Task 3 已落地的两条条目里**已经**含 `- **跨项目联动**: 无 — ...` 字段,无需补写)
</read_first>
<action>
**本任务为纯断言型 task — 不修改任何文件**INFO #2 调整:跨项目联动字段已合并进 Task 3 模板一次写入;本 task 只负责断言落位 + 验证前端文件未动)。
步骤:
1. 用 Read 查看 `../qy-lty-admin/docs/修改记录.md` 顶部 30 行,**确认其内容未被本会话改动**(仅做读取,**不写**
2. 在仓库根目录跑 `git diff --quiet HEAD -- qy-lty-admin/docs/修改记录.md && echo CLEAN`(这是单条命令组合:`git diff --quiet` 在文件无 unstaged 改动时退出码 0配合 `&& echo CLEAN` 仅在干净时打印 `CLEAN`;与之前 `cd ... && git status --short ... 输出为空` 的脆弱判式相比,本写法对 staged/unstaged 都鲁棒,且 PowerShell 5.x 与 bash 都可执行 — 因为 `&&` 是 git 自身命令链的语法,而非 shell pipeline
3. 用 grep 验证 `qy_lty/docs/修改记录.md` 中两条 Phase 1 条目都已含 `- **跨项目联动**: 无` 字段(应在 Task 3 一次性写入;本 task 不应触发任何写入)
关键约束:
- **本 task 完全不修改任何文件**(含 `qy_lty/docs/修改记录.md` 与 `../qy-lty-admin/docs/修改记录.md`
- 若发现 Task 3 写入的条目缺『跨项目联动』字段,**回到 Task 3** 修复,**不**在本 task 补写
- 若发现 `qy-lty-admin/docs/修改记录.md` 已被改动立刻报错CLAUDE.md 强制规则违反)
</action>
<verify>
<automated>cd C:\Users\admin\Desktop\Lila-Server && git diff --quiet HEAD -- qy-lty-admin/docs/修改记录.md && echo CLEAN</automated>
</verify>
<acceptance_criteria>
- 上面 verify 命令退出码 0 且输出 `CLEAN`(说明 `qy-lty-admin/docs/修改记录.md` 相对 HEAD 无任何改动 — staged 与 unstaged 都干净)
- 在 `qy_lty/docs/修改记录.md` 中 grep `**跨项目联动**: 无` 命中 2 次Task 3 已写入;本 task 不应改变此计数)
- 本 task 的 git status 应无新增 / 修改文件(即 Task 4 是纯只读断言)
- 跨项目联动决策痕迹已落位:可被后续 verify-work agent 通过 grep `**跨项目联动**: 无 —` 命中
</acceptance_criteria>
<done>verify 命令打印 CLEAN 且 git diff 退出码 0以上 4 条 acceptance criteria 全部满足;前端修改记录文件未被本 plan 任意 task 改动。</done>
</task>
</tasks>
<verification>
本 plan 完成 + Plan 01 完成后,整个 Phase 1 必须满足 ROADMAP「Phase 1: 凭据槽位数据层」的全部 4 条 success criteria
1. ✅ DB / 模型层最多 1 条记录 → 由 Plan 01 Task 2 acceptance 中 N 次 save 后 count == 1 的 shell 断言显式验证(注:实现方式是 save() 钩子静默重定向 pk非异常拒绝详见 Plan 01 verification 段说明)+ Plan 02 Task 2 浏览器尝试新增(应被拒)
2. ✅ migrate 后 schema 含 app_id / access_token / updated_at + 首访 get_or_create 拿空记录 → Plan 01 Task 3 自检 + showmigrations 0004_credentialslot 行带 `[X]` 标记
3. ✅ Admin 列表/查看态脱敏 + 编辑态明文 → 本 Plan Task 1 + Task 2 浏览器验收(验收 1 期望 B/C/E 用前置准备 shell 探针读出实际 access_token 算出的脱敏期望串作比对,避免硬编码字符串与 DB 状态不符)
4. ✅ Admin 列表页无「增加」按钮 → 本 Plan Task 2 浏览器验收
**额外满足**Phase 工程硬要求):
5. ✅ Admin 禁止删除CONTEXT.md / Success criterion #6→ 本 Plan Task 1 + Task 2 浏览器验收
6. ✅ 修改记录两条已追加到 qy_lty/docs/修改记录.md 顶部、qy-lty-admin/docs/修改记录.md 未被改动CLAUDE.md 强制规则)→ 本 Plan Task 3 一次性写入两条含『跨项目联动: 无』字段的条目Task 4 纯断言确认前端文件未动
</verification>
<success_criteria>
- aiapp/admin.py 注册 CredentialSlotAdmin覆盖 REQ CRED-02 完整语义(脱敏 + 单例新增 + 禁删)
- ROADMAP Phase 1 success criteria #4 / #5 / #6 由人工 checkpoint 显式验收(验收 1 「期望 B」用前置准备 shell 探针读出的实际 access_token 算 mask 期望串,默认场景下为 `*************xxxx`13 个 `*` + `xxxx`,对应 `probe_secret_xxxx` 17 字符)
- qy_lty/docs/修改记录.md 顶部增加 2 条 Phase 1 条目顺序Admin 在上、数据层在下;最新在最前);两条都在 Task 3 一次性写入时即含『跨项目联动: 无』字段INFO #2 调整:不再由 Task 4 二次追加)
- qy-lty-admin/docs/修改记录.md **未被改动**CLAUDE.md 跨项目规则 + 本 phase 是纯服务端;由 Task 4 `git diff --quiet HEAD -- qy-lty-admin/docs/修改记录.md && echo CLEAN` 鲁棒断言)
- 与 Plan 01 联合交付Phase 1 整体收尾ROADMAP Phase 1 状态可推进至 Complete可启动 `/gsd-plan-phase 2`(管理端 REST 接口)
</success_criteria>
<output>
完成后由 /gsd-execute-phase 自动生成 `.planning/phases/01-credential-data-layer/01-02-SUMMARY.md`,须含:
- aiapp/admin.py 实际新增行数与 import 改动 diff 概要
- 浏览器 checkpoint 5 条期望的实际验收结果(含截图路径或描述;验收 1 期望 B/C/E 须记录前置准备 shell 输出的 `CURRENT access_token` 实际值与 `EXPECTED masked` 串以备审计)
- docs/修改记录.md 新条目落地行号区间(如「插入在第 24-72 行」);两条都内嵌『跨项目联动: 无』字段
- 跨项目互引决策痕迹Task 4 `git diff --quiet` 输出 CLEAN确认 qy-lty-admin/docs/修改记录.md 未被改动)
- Phase 1 整体推进状态(建议触发 STATE.md 更新 + 启动 Phase 2 规划)
</output>
</content>
</invoke>