gsd-verifier goal-backward 验证: - ROADMAP 4 条 success criteria + 2 条工程硬要求全部达成 - 跨 plan contract 一致性(mask_token / 字段集 / 单例语义 / 探针 mask 串)MATCH - 元数据同步(REQUIREMENTS / ROADMAP / STATE)SYNCED 含已合并的 CONTEXT.md 修正版(i18n 跟仓库字面量约定 + app 归属锁定 aiapp + 单例复刻 AffinitySetting)。
24 KiB
phase, verified, status, score, overrides_applied, re_verification
| phase | verified | status | score | overrides_applied | re_verification | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 01-credential-data-layer | 2026-05-07T11:05:00Z | passed | 6/6 must-haves verified | 0 |
|
Phase 1:凭据槽位数据层 Verification Report
Phase Goal:在数据库层落地全局单例的凭据槽位,并通过 Django Admin 提供受控录入入口(写入态可见、查看态脱敏、不可新增多条)。 Verified:2026-05-07T11:05:00Z Status:✓ passed Re-verification:No — initial verification. Verification Style:goal-backward —— 不信 SUMMARY,直接以 Django shell + Django test client 在真实代码上断言所有 Success Criteria。
Goal Achievement
Observable Truths(ROADMAP Phase 1 Success Criteria + 硬要求)
| # | Truth | Status | Evidence |
|---|---|---|---|
| 1 | DB / 模型层强制最多一条记录(在已有 1 条的情况下 CredentialSlot(...).save() 不会创建第二行) |
✓ VERIFIED | 实测:连续 CredentialSlot(app_id='probe2', access_token='xxx').save() 后 CredentialSlot.objects.count() == 1;save() 钩子(aiapp/models.py:82-87)将新对象 pk 重定向到 existing.pk 走 UPDATE。语义等价 ROADMAP "DB 最多一条"。 |
| 2 | python manage.py migrate 后 schema 含 app_id / access_token / updated_at,首次访问 get_or_create(pk=1) 拿到记录 |
✓ VERIFIED | python manage.py showmigrations aiapp 实际输出含 [X] 0004_credentialslot;模型反射 _meta.get_fields() 三字段都在,max_length 分别 128/512;get_or_create(pk=1) 返回 obj.pk=1。迁移文件 0004_credentialslot.py 含 migrations.CreateModel(name='CredentialSlot', ...)。 |
| 3 | Admin 列表 / 查看态 access_token 仅末 4 位掩码;编辑态明文 |
✓ VERIFIED | 用 force_login(superuser) + Django test client 实测:GET /admin/aiapp/credentialslot/ 200,HTML body 中含 *************xxxx(13 * + xxxx,对应探针 probe_secret_xxxx 17 字符);GET /admin/aiapp/credentialslot/1/change/ 200,HTML body 含 value="probe_secret_xxxx" 明文 input。 |
| 4 | Admin 列表页不显示「新增」按钮(强制单例) | ✓ VERIFIED | force_login 后 GET /admin/aiapp/credentialslot/ 实测 HTML body 中不含 addlink 类(Django Admin 「增加」按钮专用 CSS class);GET /admin/aiapp/credentialslot/add/ 实测返回 HTTP 403,与 has_add_permission 在 DB 已有记录时返 False 的逻辑(aiapp/admin.py:47-49)一致。 |
| 5 | Admin 永久禁止删除(CONTEXT.md 硬要求 #6) | ✓ VERIFIED | has_delete_permission(request, obj=None) 永远返 False(aiapp/admin.py:51-53);test client 实测 GET /admin/aiapp/credentialslot/1/delete/ → HTTP 403;编辑页 HTML body 不含 deletelink 类。ma.has_delete_permission(None, None) is False 在 admin._registry 上反射也成立。 |
| 6 | 修改记录两条已追加到 qy_lty/docs/修改记录.md 顶部、qy-lty-admin/docs/修改记录.md 未被改动(CLAUDE.md 强制规则) |
✓ VERIFIED | qy_lty/docs/修改记录.md 第 26 / 42 行各一条 Phase 1 条目(CRED-02 在上、CRED-01 在下,最新在最前),均含 **跨项目联动**: 无 字段(grep 命中 2 次);git status --short -- qy-lty-admin/docs/修改记录.md 输出为空(且 git diff --quiet HEAD -- 退出码 0)。 |
Score:6 / 6 truths verified(4 条 ROADMAP success criteria + 2 条工程硬要求 #5 #6)
Required Artifacts(三层 + 数据流第四层)
| Artifact | Expected | Status | Details |
|---|---|---|---|
common/utils.py |
mask_token(token, visible_tail=4, mask_char='*') 工具函数;纯 Python 不依赖 Django;仅 1 个 mask_* 函数 |
✓ VERIFIED | 文件 33 行(含 docstring);grep '^def mask' 仅 1 命中;grep 'import django|from django' 0 命中;7 个表驱动用例全部满足(含 'probe_secret_xxxx' -> '*************xxxx')。 |
aiapp/models.py |
class CredentialSlot(models.Model) + save 钩子重定向 + get_solo 类方法;3 字段 |
✓ VERIFIED | 文件第 55-93 行落地 CredentialSlot;save() 含 if not self.pk and CredentialSlot.objects.exists() 重定向(grep 1 命中);get_solo 类方法 1 命中;不含 gettext_lazy / created_at / null=True。Bot / ChatMessage 既有类未被破坏。 |
aiapp/migrations/0004_credentialslot.py |
migrations.CreateModel(name='CredentialSlot', fields=[...]);依赖 0003_create_rtc_bot;可被 migrate 应用 |
✓ VERIFIED | 27 行迁移文件落地;name='CredentialSlot' 1 命中;含 4 字段(id/app_id/access_token/updated_at);showmigrations aiapp 输出 [X] 0004_credentialslot,确认已应用到 PostgreSQL。 |
aiapp/admin.py |
@admin.register(CredentialSlot) + class CredentialSlotAdmin(admin.ModelAdmin) |
✓ VERIFIED | 第 18-53 行落地;@admin.register(CredentialSlot) 1 命中;list_display 含 access_token_masked;readonly_fields 仅 ('updated_at',),不含 access_token;fieldsets 双段(凭据信息 + 元数据 collapse)。 |
qy_lty/docs/修改记录.md(顶部 Phase 1 两条条目) |
顺序:Admin 在上、数据层在下,最新在最前;均含 **跨项目联动**: 无 字段 |
✓ VERIFIED | 第 26-40 行 CRED-02 条目、第 42-59 行 CRED-01 条目;既有 GSD bootstrap 条目位于第 61 行(顺序正确);两条均含 5 个加粗字段(文件路径 / 修改类型 / 修改内容 / 修改原因 / 跨项目联动)。 |
Level 4(Data-Flow Trace):在 Admin 列表页的 access_token_masked 计算字段是渲染动态数据的唯一可疑点 —— 经实测,DB pk=1 实际有 access_token='probe_secret_xxxx'(来源 Plan 01-01 Task 3 探针写入),通过 mask_token(obj.access_token) 流到 list_display;HTML body 实际渲染 *************xxxx(与 mask_token 数学一致)。Data flowing:✓ FLOWING。
Key Link Verification(wiring)
| From | To | Via | Status | Details |
|---|---|---|---|---|
aiapp/models.py (CredentialSlot) |
aiapp/migrations/0004_credentialslot.py |
makemigrations 自动生成;含 name='CredentialSlot' |
✓ WIRED | 模型字段与迁移 CreateModel 字段集合一一对应(id BigAuto / app_id / access_token / updated_at),max_length 一致。 |
aiapp/models.py CredentialSlot.save |
userapp/models.py AffinitySetting.save 模式 |
1:1 复刻 save 钩子,pattern if not self.pk and CredentialSlot.objects.exists |
✓ WIRED | grep 1 命中;语义与 AffinitySetting 247-314 等价;count 守恒断言通过。 |
aiapp/admin.py CredentialSlotAdmin.access_token_masked |
common.utils.mask_token |
from common.utils import mask_token import |
✓ WIRED | grep from common.utils import mask_token 1 命中;test client 实测渲染串与 mask_token 输出一致(*************xxxx)。 |
aiapp/admin.py CredentialSlotAdmin |
aiapp.models.CredentialSlot |
@admin.register(CredentialSlot) |
✓ WIRED | grep @admin\.register\(CredentialSlot\) 1 命中;admin.site._registry[CredentialSlot] 反射可拿到 CredentialSlotAdmin 实例。 |
aiapp/admin.py has_add_permission |
单例语义 | return not CredentialSlot.objects.exists() |
✓ WIRED | grep 1 命中;test client 实测 /add/ 在已有记录时返 403;addlink CSS class 在列表页不出现。 |
aiapp/admin.py has_delete_permission |
永远禁删硬约束 | return False(含 obj=None) |
✓ WIRED | test client 实测 /delete/ 返 403;编辑页 deletelink 类不出现;ma.has_delete_permission(None, None) is False。 |
Behavioral Spot-Checks(Step 7b)
| Behavior | Command | Result | Status |
|---|---|---|---|
mask_token 7 个边界用例 |
python -c "from common.utils import mask_token; ..." |
7/7 全 PASS(含 'probe_secret_xxxx' 探针) |
✓ PASS |
| 模型字段反射 | python -c "...CredentialSlot._meta.get_fields()..." |
app_id(128) / access_token(512) / updated_at 齐全 | ✓ PASS |
get_or_create(pk=1) 拿到 pk=1 |
python -c "...obj, created = CredentialSlot.objects.get_or_create(pk=1)..." |
obj.pk=1,DB pk=1 探针 probe_app/probe_secret_xxxx |
✓ PASS |
| 单例守恒 | 连续 CredentialSlot(...).save() 后 count() == 1 |
count=1 不变 | ✓ PASS |
| 迁移已应用 | python manage.py showmigrations aiapp |
输出含 [X] 0004_credentialslot |
✓ PASS |
| Admin list 渲染脱敏 | Django test client GET /admin/aiapp/credentialslot/ |
200;body 含 *************xxxx;不含 addlink |
✓ PASS |
| Admin add 阻断 | Django test client GET /admin/aiapp/credentialslot/add/ |
403 | ✓ PASS |
| Admin delete 阻断 | Django test client GET /admin/aiapp/credentialslot/1/delete/ |
403 | ✓ PASS |
| Admin edit 明文 + 无 deletelink | Django test client GET /admin/aiapp/credentialslot/1/change/ |
200;body 含 value="probe_secret_xxxx" 明文,不含 deletelink |
✓ PASS |
全部 9 项行为级 spot-check 通过。注:本机 superuser 已存在,故 admin smoke 走 force_login 而非 runserver,不需要启动外部服务。
Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|---|---|---|---|---|
| CRED-01 | 01-01-PLAN.md | 单例 CredentialSlot Django 模型 + 迁移;DB 层强制最多一条;含 app_id / access_token / updated_at 字段 |
✓ SATISFIED | 模型在 aiapp/models.py:55-93;迁移 0004_credentialslot.py 已应用;count 守恒断言通过;REQUIREMENTS.md 已标 [x] Done。 |
| CRED-02 | 01-02-PLAN.md | Django Admin 注册:列表 / 查看态脱敏;编辑态明文;隐藏「新增」按钮(强制单例) | ✓ SATISFIED | Admin 在 aiapp/admin.py:18-53;列表页 mask、/add/ 返 403、/delete/ 返 403、编辑页明文 input — 全部由 Django test client 实测确认;REQUIREMENTS.md 已标 [x] Done。 |
孤儿需求检查:REQUIREMENTS.md → Active 段映射到 Phase 1 的需求仅 CRED-01 + CRED-02;两条均被 plan 声明并落地,无孤儿。
Anti-Patterns Scan
对本 phase 修改的 5 个文件(common/utils.py / aiapp/models.py / aiapp/admin.py / aiapp/migrations/0004_credentialslot.py / docs/修改记录.md)做反模式扫描:
| File | Line | Pattern | Severity | Impact |
|---|---|---|---|---|
| — | — | TODO / FIXME / HACK | — | grep 0 命中 — 无遗留待办标记 |
| — | — | placeholder / 占位 / 待实现 | — | grep 0 命中 |
| — | — | return null/{}/[] 静默兜底 |
— | grep 0 命中(mask_token 的 return '' 是文档化兜底分支,不是 stub) |
| — | — | console.log only impl |
— | N/A(Python 项目) |
| — | — | hardcoded empty data 流到渲染 | — | 0 — list_display 由 ORM 取真实记录;access_token 默认 '' 是模型字段 default,不是 stub 渲染兜底(首次部署运营录入后即被覆盖;探针 probe_secret_xxxx 已写入)。 |
aiapp/admin.py |
12-15 | 既有 class BotAdmin(admin.ModelAdmin): 重名(实际注册 ChatMessage) |
ℹ️ Info | 仓库历史遗留(不在 Phase 1 修复 scope,PLAN 显式约束「不动 Bot/ChatMessage 注册块」);不影响 Phase 1 goal 与 success criteria。 |
总评:无 🛑 Blocker、无 ⚠️ Warning、仅 1 项 ℹ️ Info(继承自上游、PLAN 已显式 defer)。
Cross-Plan Contract Sanity(goal-backward 关键检查)
跨 plan 的 contract 是 stub 最常藏的地方,单独抽查:
| Contract | Plan 01-01 提供 | Plan 01-02 调用 | Match? |
|---|---|---|---|
mask_token(token: str, visible_tail: int=4, mask_char: str='*') -> str |
common/utils.py:10 签名一致;返回脱敏 str |
aiapp/admin.py:44 return mask_token(obj.access_token) 单参调用,使用默认 visible_tail=4 |
✓ MATCH |
CredentialSlot._meta.fields 含 access_token |
aiapp/models.py:69-72 CharField(512, blank, default='') |
aiapp/admin.py:34 fieldsets 显式列 access_token 进编辑表单 |
✓ MATCH |
CredentialSlot.objects.exists() 用于单例语义 |
模型本身无方法依赖 | aiapp/admin.py:49 has_add_permission 调用 CredentialSlot.objects.exists(),DB 有探针记录时返 False |
✓ MATCH |
| 探针数据契约 | Plan 01-01 Task 3 写入 pk=1, access_token='probe_secret_xxxx' |
Plan 01-02 Task 2 浏览器 checkpoint 期望串 *************xxxx(mask_token 长度 17 → 13 * + 末 4 xxxx,数学一致) |
✓ MATCH |
所有跨 plan contract 一致:mask_token 签名 / CredentialSlot 字段集合 / 单例 exists 语义 / 探针数据 → 脱敏期望串数学一致。
元数据同步检查
| 文件 | 同步项 | 状态 |
|---|---|---|
.planning/REQUIREMENTS.md |
CRED-01、CRED-02 标 [x] Done + 含 commit hash + Traceability 表更新 |
✓ SYNCED |
.planning/ROADMAP.md |
Phase 1 标 [x] 完成、Plans 表 2/2 Complete、Progress 100% |
✓ SYNCED |
.planning/STATE.md |
status: executing、stopped_at: Phase 1 完成、completed_phases: 1、下一步指向 /gsd-plan-phase 2 |
✓ SYNCED |
qy_lty/docs/修改记录.md |
两条 Phase 1 条目顺序正确(Admin 在上、数据层在下),既有条目未被破坏 | ✓ SYNCED |
qy-lty-admin/docs/修改记录.md |
未被改动(git status --short 输出空) | ✓ SYNCED |
| Commit hash | a9c25eb / 30c7caf / a475fe4 / 20036ee(Plan 01-01)+ 653f057 / ddbcb7d / f88df92(Plan 01-02)全部存在于 git log --oneline |
✓ SYNCED |
观察项(不影响 Phase 1 goal)
- 迁移文件头部
Generated by Django 5.2.12:本机 Python 全局环境装的是 Django 5.2.12(PROJECT.md / CLAUDE.md 写的是 4.2.13)。SUMMARY 已注明,本仓库部署走 Docker 镜像(4.2.13),4.x/5.x 跨版本字段属性 / Meta 选项兼容,迁移文件运行 OK;不在本 phase 修复 scope。 aiapp/admin.py的class BotAdmin(admin.ModelAdmin):重名(第 12 行实际给 ChatMessage 注册):仓库历史遗留 bug,PLAN 显式约束「不在 Phase 1 修复 scope」;不影响 Phase 1 goal。- Task 2 checkpoint:human-verify 由 orchestrator 用 Django test client 程序化验收(10/10 PASS):本验证再次以同样手法独立复算 9 项关键判据(list/add/delete/edit),全部通过。SUMMARY 自述与代码实际行为一致。
Human Verification Required
无。
理由:本 phase 全部 6 条 Observable Truths 均可通过 Django shell + Django test client 在内存中程序化验证;test client 已覆盖列表 / 编辑 / add / delete 全部 HTTP 路径(含 CSS class 检查、HTTP 403 阻断、明文渲染断言)。SimpleUI 主题视觉细节(中文表头、按钮颜色等)不属于 ROADMAP success criteria,故不需要浏览器人工二次复核。
Gaps Summary
无 gap。
Phase 1 的 4 条 ROADMAP success criteria + 2 条工程硬要求全部由代码 / 配置 / 行为级断言三重证实达成:
- ✅ DB 单例(save 钩子 count 守恒,语义等价 ROADMAP "DB 最多一条",已在 PLAN/SUMMARY 显式说明并由 verify-work 复算认可)
- ✅ 迁移落地 + 字段齐全 + get_or_create 首访
- ✅ Admin 列表脱敏 + 编辑明文(test client HTML body 实测)
- ✅ Admin 无新增按钮 + /add/ 返 403
- ✅ Admin 永久禁删 + /delete/ 返 403
- ✅ 修改记录两条已落顶 + 前端文件未动
跨 plan contract 一致,元数据同步完成,commit hash 均存在。Phase 1 状态可推进至 Complete,可启动 /gsd-plan-phase 2(管理端 REST 接口,覆盖 CRED-03 + CRED-04)。
VERIFICATION PASSED
所有 success criteria + 硬要求全部达成。
Verified: 2026-05-07T11:05:00Z Verifier: Claude (gsd-verifier, goal-backward style)