pmc 20036eeb2f docs(01-01): 完成凭据槽位数据层 plan,落地 SUMMARY 与 state 更新
- 新增 SUMMARY.md:3 task / 3 commit / 32+42+26 行代码 / mask_token + CredentialSlot + 0004 迁移
- STATE.md:completed_plans 0→1(50%),下一步切到 Plan 01-02
- ROADMAP.md:Plan 01-01 勾选完成,进度表 1/2 In progress
- REQUIREMENTS.md:CRED-01 勾选完成,traceability 状态 Pending→Done
- 探针数据契约固化:DB pk=1 / access_token='probe_secret_xxxx' 留给 Plan 01-02 浏览器 checkpoint
2026-05-07 17:39:53 +08:00

195 lines
9.6 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: 01
subsystem: aiapp / common
tags: [credential, singleton, migration, mask, masking-util]
requires: []
provides:
- "aiapp.models.CredentialSlot单例模型 + get_solo + save 钩子)"
- "common.utils.mask_token(token, visible_tail=4, mask_char='*')"
- "aiapp 迁移 0004_credentialslot.pyCreateModel"
- "DB 探针数据契约pk=1 / app_id='probe_app' / access_token='probe_secret_xxxx'(供 Plan 02 浏览器 checkpoint 验证脱敏显示)"
affects:
- "Plan 01-02 Admin 注册 / 列表页脱敏将复用 mask_token 与 CredentialSlot.get_solo()"
- "Phase 2 / Phase 3 REST 视图统一从 CredentialSlot.get_solo() 取数"
- "Phase 3 阿里云日志 formatter 复用 common.utils.mask_token"
tech_stack:
added: []
patterns:
- "Django 单例 = pk=1 + get_or_create + save() 钩子重定向1:1 复刻 userapp.models.AffinitySetting 247-314 行)"
- "中文字面量 verbose_name与仓库 14 个模型实操一致;不引入 gettext_lazy"
key_files:
created:
- common/utils.py
- aiapp/migrations/0004_credentialslot.py
modified:
- aiapp/models.py
decisions:
- "字段集合最小化app_id(128) / access_token(512) / updated_at不加 created_at单例无创建语义"
- "单例靠 save() 钩子 + pk=1 静默重定向(不抛异常),与 AffinitySetting 一致ROADMAP success criterion #1 解读为 count 守恒为 1非异常拒绝"
- "mask_token 短输入len <= visible_tail走全脱敏分支防长度信号泄露"
- "探针数据 probe_secret_xxxx 写入 DB 后保留不清理Plan 02 浏览器 checkpoint 依赖)"
metrics:
duration_seconds: 184
tasks_completed: 3
tasks_total: 3
files_created: 2
files_modified: 1
commits: 3
completed_at: "2026-05-07T09:36Z"
requirements:
- CRED-01
---
# Phase 1 Plan 01-01凭据槽位数据层 Summary
**一句话**:落地 Milestone v1.0「通用凭据槽位」的数据基础——`CredentialSlot` 单例 Django 模型pk=1 + save 钩子 + get_solo 三件套1:1 复刻 AffinitySetting+ 自动生成的 0004 迁移文件 + 通用 `mask_token` 工具函数(供 Phase 1 Admin / Phase 3 阿里云日志双方复用)。
## 完成的 Tasks
| Task | 名称 | Commit | 文件 |
|------|------|--------|------|
| 1 | 新建 common/utils.py 落地 mask_token 工具函数 | `a9c25eb` | common/utils.py新建32 行) |
| 2 | 在 aiapp/models.py 末尾追加 CredentialSlot 单例模型 | `30c7caf` | aiapp/models.py+42/-1 |
| 3 | 自动生成迁移文件并执行 migrate | `a475fe4` | aiapp/migrations/0004_credentialslot.py自动生成26 行) |
## 实际新增 / 修改的代码行数
- `common/utils.py`:新建 32 行(含 docstring
- `aiapp/models.py`:从 52 行增至 93 行(+42 / -1末尾追加 `CredentialSlot` 类含 4 字段 + Meta + `__str__` + `save` 钩子 + `get_solo` 类方法)
- `aiapp/migrations/0004_credentialslot.py`:自动生成 26 行(依赖 `0003_create_rtc_bot`
## 迁移文件实际名称
确认为 **`aiapp/migrations/0004_credentialslot.py`**,与 PLAN 期望一致。
依赖:`('aiapp', '0003_create_rtc_bot')`
operations`migrations.CreateModel(name='CredentialSlot', fields=[id BigAutoField, app_id CharField(128), access_token CharField(512), updated_at DateTimeField(auto_now=True)])`
## 自检 Shell 脚本输出
PLAN Task 3 `<action>` 段规定的探针 + 单例自检:
```text
created= True app_id= '' access_token= '' pk= 1
after second save count= 1 obj2.pk= 1
```
PLAN Task 2 acceptance #9 规定的 N 次 save 守恒断言4 次 save 验 count 恒为 1
```text
count_invariant_OK
```
PLAN `<verification>` 段完整脚本assert 全部通过):
```text
Plan 01 verification PASS
```
`showmigrations aiapp` 输出确认 `[X] 0004_credentialslot`
```text
aiapp
[X] 0001_initial
[X] 0002_initial
[X] 0003_create_rtc_bot
[X] 0004_credentialslot
```
`makemigrations aiapp --check --dry-run` 输出 `No changes detected in app 'aiapp'`,退出码 0`CHECK_OK`
## mask_token 验证结果
```text
mask_token('sk-abcdef1234') == '*********1234' ✓ (末 4 位 '1234' 明文,前 9 字符脱敏)
mask_token('') == '' ✓ (空串短路)
mask_token(None) == '' ✓ (None 短路)
mask_token('abc') == '***' ✓ (短输入全脱敏)
mask_token('abcd') == '****' ✓ (恰等 visible_tail 全脱敏)
mask_token('abcde') == '*bcde' ✓ (长 1 位露 4 位)
mask_token('probe_secret_xxxx') == '*************xxxx' ✓ (与 Plan 02 浏览器 checkpoint 期望串一致)
```
## 探针数据当前值确认
`SELECT app_id, access_token FROM aiapp_credentialslot WHERE pk=1` 通过 ORM 等价:
```text
pk=1, app_id='probe_app', access_token='probe_secret_xxxx', count=1
```
**Plan 02 Task 2 浏览器 checkpoint mask 期望值算法**
- 原 token`probe_secret_xxxx`(长度 17
- `mask_token(...)` 返回:`*************xxxx`13 个 `*` + 末 4 位 `xxxx`,总长 17
- 故 Admin 列表页 `access_token_masked` 列应渲染为 `*************xxxx`
## 给下游的 Hand-off
| 下游 | 公开入口 / 契约 |
|------|----------------|
| Plan 01-02Admin 注册) | `from aiapp.models import CredentialSlot` 取模型;`from common.utils import mask_token``access_token_masked(self, obj)`;用 `CredentialSlot.objects.exists()` 判断是否禁用「新增」按钮 |
| Plan 01-02 浏览器 checkpoint | 依赖 DB 中 `pk=1, access_token='probe_secret_xxxx'` 探针;列表页脱敏期望串 `*************xxxx` |
| Phase 2 管理端 REST | 单例统一访问入口:`CredentialSlot.get_solo()`(不要直接 `CredentialSlot.objects.first()` 防止空 DB 时拿 NoneGET 响应序列化时调用 `mask_token(obj.access_token)` |
| Phase 3 客户端 REST | 同样用 `CredentialSlot.get_solo()` 取数;客户端 GET 返回明文(不调 mask_token |
| Phase 3 日志脱敏 | 阿里云日志 formatter 用 `from common.utils import mask_token` 直接复用,签名兼容 |
## Deviations from Plan
### 自动调整(无需用户介入)
**1. [Rule 3 - Blocking] verify 段 findstr /R 在 PowerShell + GBK 编码下不可靠**
- **发现位置**Task 3 verify 命令 `python manage.py showmigrations aiapp | findstr /R "0004.*\[X\]"`
- **现象**findstr 在 PowerShell 中报 `OSError: [Errno 22]` + 中文 `findstr: 无法` 乱码pipe 因 stderr 警告污染失败
- **修复**:改用 `python manage.py showmigrations aiapp 2>nul` 直接看输出,逐行肉眼+grep 校验 `[X] 0004_credentialslot` 命中
- **影响**:仅影响 verify 显示方式,不影响功能 acceptanceshowmigrations 输出已确认 `[X]` 标记到位
- **文件**:无代码改动,纯 verify 流程调整
### 观察(不阻塞)
**2. 迁移文件头部注释显示 `Generated by Django 5.2.12`**
- **PLAN / PROJECT.md / CLAUDE.md 记录的版本是 Django 4.2.13**
- **实际表现**:本机 Python 环境的 `django` 包是 5.2.12pip 装的全局包),但项目代码是按 4.2 写的迁移格式、字段属性、Meta 选项均跨 4.x/5.x 兼容,无破坏)
- **未阻塞**:迁移成功生成 + 成功应用;模型行为完全符合 acceptance`CredentialSlot` 类与 `AffinitySetting` 在 4.2 / 5.2 下行为等价
- **建议**:本仓库部署使用 Docker 镜像CLAUDE.md 写明Docker 内才是 4.2.13;本地开发版本漂移属于已知现象,不在本 plan 范围。可考虑在 Phase 3 收尾时由独立运维 plan 或 deferred-items 处理(建议加固 venv / requirements.txt 锁版本,但 PROJECT.md 已说"不锁版本,靠 Docker")。
## 不在本 Plan 范围(按 PLAN 约束严格执行)
- **未写 docs/修改记录.md 条目**PLAN 与执行 prompt 显式说明:本 Plan 不写修改记录,由 Plan 01-02 Task 3 一并写两条 — 避免重复条目)
- **未注册 Django Admin**CRED-02由 Plan 01-02 落地)
- **未写 REST 接口**Phase 2 / Phase 3
- **未引入新依赖**(沿用 Django 4.2 / Python 3.8 已有栈)
## 覆盖的需求与 ROADMAP Success Criteria
-**CRED-01**:单例 `CredentialSlot` 模型 + 迁移落地DB 层 count 守恒为 1save 钩子保证);含 app_id / access_token / updated_at 三字段
-**ROADMAP Phase 1 Success Criterion #1**DB / 模型层强制最多一条save() 钩子静默重定向 pkcount 守恒,与 AffinitySetting 等价语义)
-**ROADMAP Phase 1 Success Criterion #2**:迁移落地 + schema 字段齐全migrate 退出码 0showmigrations 显示 `[X]`CreateModel 含 4 列)
-**ROADMAP Phase 1 Success Criterion #3**`get_or_create(pk=1)` 首访拿到 `created=True / app_id='' / access_token=''` 空记录
- — Phase 1 Success Criterion #4 / #5 / #6Admin 列表 / 编辑 / 禁删)→ Plan 01-02 负责
## Self-Check: PASSED
文件存在确认:
```text
common/utils.py -> FOUND
aiapp/models.py含 class CredentialSlot -> FOUND
aiapp/migrations/0004_credentialslot.py -> FOUND
.planning/phases/01-credential-data-layer/01-01-SUMMARY.md -> FOUND本文件
```
Commit 存在确认(`git log --oneline` 命中):
```text
a9c25eb feat(01-01): 新增 common/utils.py 含 mask_token 工具函数 -> FOUND
30c7caf feat(01-01): aiapp 新增 CredentialSlot 单例模型 -> FOUND
a475fe4 feat(01-01): 自动生成并应用 0004_credentialslot 迁移 -> FOUND
```
DB 状态确认:`aiapp_credentialslot` 表存在 pk=1 单条记录,`access_token='probe_secret_xxxx'` 探针就绪供 Plan 01-02 使用。
---
*由 /gsd-execute-phase 顺序执行器于 2026-05-07 生成*