---
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_token；pattern 是正则；用 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 改动正式归档到修改记录。

Output：1 个文件追加（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.py（Plan 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.py（Plan 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 名误用 BotAdmin，executor 不要顺手改 — 不在 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 UX（success 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` 实际值替代下文的样板字符串。

    **验收 1（success 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）

    **验收 2（success criterion #5 — 列表页无「增加」按钮）**：
    - 当前列表页右上角应**无**「增加 凭据槽位」按钮（DB 中已有 1 条记录，`has_add_permission` 返回 False）
    - **期望**：手动访问 `http://localhost:8000/admin/aiapp/credentialslot/add/` 应返回 403 Forbidden 或被重定向到列表页（Django 默认行为）

    **验收 3（success 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>