--- phase: 02-admin-rest plan: 02 subsystem: verification + cross-project docs tags: [verification, e2e-test, django-test-client, drf-yasg, swagger, cross-project-link, modification-log] requirements_completed: - CRED-03 - CRED-04 dependency_graph: requires: - phase: 02-admin-rest / Plan 01(serializer + view + URL + Swagger 全部就位) provides: CredentialSlotSerializer / CredentialSlotAdminView / 路由 admin_credential_slot provides: - 02-VERIFICATION.md:8 条 success criteria 验收证据归档 - qy_lty/docs/修改记录.md Phase 2 条目(含跨项目联动 → qy-lty-admin) - qy-lty-admin/docs/修改记录.md Phase 2 互引条目(含服务端联动 → qy_lty) affects: - Phase 3(CRED-05 客户端 GET 明文 / CRED-06 阿里云日志脱敏):以 Phase 2 收尾后的 DB 探针态 + 已上线接口为起点 - 后续 qy-lty-admin CRED-FE-01 phase:以本互引条目锁定的接口契约为消费方依据 tech_stack: added: [] patterns: - "端到端验收走 Django test client(in-process),不启 daphne,避免端口占用 / 脏环境" - "drf-yasg schema 验收:通过 /swagger.json/ 拿 OpenAPI;本仓库 StandardResponseMiddleware 会把 schema 包进 data 字段,验证脚本需 unwrap" - "跨项目修改记录互引:两端各写一条,跨项目联动 / 服务端联动字段相互引用对方文件路径(CLAUDE.md 强制规则首次落地)" - "验收用临时 token 不入仓库(仅记长度 + PASS 判定,Redis 30 天 TTL 攻击面控制)" key_files: created: - qy_lty/.planning/phases/02-admin-rest/02-VERIFICATION.md modified: - qy_lty/docs/修改记录.md - qy-lty-admin/docs/修改记录.md decisions: - "[Plan 02-02] 端到端验收走 Django test client(in-process),不启 daphne:内存调用更快、可重复、零端口占用;本仓库鉴权 / 标准壳层 middleware 都是 Django MIDDLEWARE 而非 ASGI 层,test client 路径与生产路径功能等价" - "[Plan 02-02] Swagger 验收走 /swagger.json/(带 trailing slash,url 模式 swagger/):本仓库 StandardResponseMiddleware 也会包 OpenAPI schema 进 {success, code, message, data},验证脚本需 unwrap data;basePath=/api 所以 paths key 是 /v1/admin/credential-slot/(去掉 /api 前缀)" - "[Plan 02-02] 测试 token 明文不入仓库:02-VERIFICATION.md 仅记录 token 长度(length=36)+ PASS 判定,不黏贴 UUID 字串;脚本结束自动 cache.delete 释放 Redis admin_token / token key" - "[Plan 02-02] 验收脚本 _phase2_verify.py / _phase2_swagger_verify.py 验完即删:是一次性证据生成器,证据落地 02-VERIFICATION.md 后无需保留;如需复跑参考 SUMMARY 中的脚本片段" - "[Plan 02-02] DB 探针态主动还原:app_id='probe_app' / access_token='probe_secret_xxxx' 是 Phase 1 留下的契约值;Phase 2 验收过程中临时写入 phase2_app / sk-phase2_verify_secret_ABCD1234 / after_delete 等值,验完必须还原以免污染 Phase 3 起点" - "[Plan 02-02] qy-lty-admin 改动通过父级 Lila-Server/.git 仓库提交:qy-lty-admin/ 没有自己的 .git;条目相对路径 ../../qy_lty/docs/修改记录.md(位于 qy-lty-admin/docs/,跳上级 qy-lty-admin/ 再跳上级 Lila-Server/,然后进 qy_lty/docs/)" metrics: duration_seconds: 720 tasks_completed: 2 files_modified: 3 files_created: 1 commits: - 3cfd481 test(02-02): 端到端验收 8 条 success criteria 全 PASS(qy_lty 仓库) - 46d72b8 docs(02-02): 两端修改记录互引 Phase 2 接口契约(父 Lila-Server 仓库;qy_lty + qy-lty-admin 同时入库) completed_date: 2026-05-07 --- # Phase 2 Plan 02-02:端到端验收 + 两端修改记录互引 Summary Phase 2 收尾:把 Plan 02-01 落地的 GET/PUT 接口端到端验完(8 条 success criteria 全 PASS),并在 qy_lty + qy-lty-admin 两端 docs/修改记录.md 各写一条 Phase 2 互引条目,闭合 Milestone v1.0 首次跨项目接口契约的双向锚点。 ## 一句话概述 Django test client 程序化跑 6 大验收点(28 项独立断言全 PASS)+ /swagger.json/ schema 验证暴露完整 + 两端修改记录互引闭环,Phase 2 整体 Complete,可进 Phase 3。 ## Performance - **Duration:** ~12 min - **Started:** 2026-05-07T14:55:54Z(接到 02-02 prompt 时) - **Completed:** 2026-05-07T15:07:54Z(SUMMARY 落地时) - **Tasks:** 2 - **Files Created:** 1(02-VERIFICATION.md) - **Files Modified:** 2(qy_lty + qy-lty-admin 各一份 docs/修改记录.md) - **Commits:** 2 个 task 原子 commit(不计本 SUMMARY 落地的 metadata commit) ## Accomplishments - **8/8 success criteria 全 PASS**:6 条 Django test client 验收(GET 脱敏 / PUT 全字段覆写 + 响应脱敏 / PUT 空记录 get_or_create / 401 无 token / 403 user token GET / 403 user token PUT)+ 1 条 Swagger schema 验收 + 1 条修改记录互引验收 - **DB 探针态主动还原**:Phase 1 留下的 probe_app / probe_secret_xxxx 在验收过程中被写穿(phase2_app / sk-phase2_verify_secret_ABCD1234 / after_delete 等),脚本结束主动 `slot.save()` 还原,给 Phase 3 留下稳定起点 - **跨项目互引闭环**:CLAUDE.md「跨项目联动两端各写一条互相引用」规则首次落地(Phase 2 是 Milestone v1.0 首次跨项目接口契约暴露);两端 grep 双向命中 - **临时验收脚本不入仓库**:`_phase2_verify.py` / `_phase2_swagger_verify.py` 验完即删;token 明文不入 02-VERIFICATION.md(Redis 30 天 TTL 攻击面控制) ## Task Commits 每个 task 原子提交: 1. **Task 1:端到端 8 条 success criteria 验收** — `3cfd481` (test) — qy_lty 仓库 dev 分支 2. **Task 2:两端修改记录互引 Phase 2 条目** — `46d72b8` (docs) — 父 Lila-Server 仓库 dev 分支(qy_lty + qy-lty-admin 同时入库) ## Files Created / Modified | 文件 | 类型 | 描述 | 所属仓库 | |------|------|------|---------| | `qy_lty/.planning/phases/02-admin-rest/02-VERIFICATION.md` | 新增 | 8 条 success criteria 验收摘要 + ROADMAP SC 映射 + 28 项独立断言 PASS 日志 + Swagger schema 验收输出 + DB 终态记录 | qy_lty | | `qy_lty/docs/修改记录.md` | 修改(顶部新增条目) | Phase 2 服务端条目(5 字段 + 跨项目联动字段引用 qy-lty-admin) | qy_lty(父 Lila-Server 提交) | | `qy-lty-admin/docs/修改记录.md` | 修改(顶部新增条目) | Phase 2 前端互引条目(5 字段 + 服务端联动字段引用 qy_lty) | qy-lty-admin(父 Lila-Server 提交) | ## DB 终态 Phase 2 收尾后 `aiapp_credentialslot` 表唯一一条记录(pk=1)的字段值: | 字段 | 值 | | -------------- | --------------------------------------------------- | | pk | 1(单例) | | app_id | `probe_app` | | access_token | `probe_secret_xxxx`(明文)/ `*************xxxx`(脱敏返回) | | updated_at | 2026-05-07(验收脚本最后一次 `slot.save()` 触发) | **Phase 1 探针契约保持有效**,Phase 3 / 后续测试脚本可直接基于此继续。 ## 8 条 Success Criteria 验收结果 | # | 验收点 | 方法 | 结果 | | --- | ------------------------------------------------------- | --------------------------------------------- | --------- | | 1 | GET 携 admin token 返回脱敏壳层 | Django test client | ✓ PASS | | 2 | PUT 携 admin token 全字段覆写 + 响应脱敏 | Django test client | ✓ PASS | | 3 | PUT 在空记录场景自动 `get_or_create` | Django test client(手动 delete + PUT) | ✓ PASS | | 4 | 无 `Authorization` 头 → 401 + 标准壳层 | Django test client | ✓ PASS | | 5 | 携普通 user token GET → 403 + `message` 含 "管理员" | Django test client | ✓ PASS | | 6 | PUT 携 user token → 403(PUT 也走 `_ensure_admin`) | Django test client | ✓ PASS | | 7 | `/swagger.json/` 含路径 + GET/PUT + 脱敏 description | Django test client(命中 drf-yasg schema) | ✓ PASS | | 8 | 修改记录两端互引(qy_lty + qy-lty-admin 各一条) | grep 双向命中 | ✓ PASS | 完整 28 项独立断言日志见 [02-VERIFICATION.md](./02-VERIFICATION.md)。 ## ROADMAP Phase 2 Success Criteria 映射 | ROADMAP SC | 内容 | 对应验收点 | | ---------- | ------------------------------- | ------------------- | | SC#1 | GET 脱敏(admin token) | #1 | | SC#2 | PUT 全字段覆写 + `get_or_create`| #2 + #3 | | SC#3 | 鉴权拒绝矩阵(无 token / user token) | #4 + #5 + #6 | | SC#4 | Swagger / ReDoc schema 一致 | #7 | ROADMAP Phase 2 4 条 SC 全部覆盖。 ## Decisions Made 见 frontmatter `decisions:` 段;关键决策汇总: 1. 走 Django test client 而非 daphne / runserver — in-process 调用、零端口占用、可重复 2. /swagger.json/ schema 在 StandardResponseMiddleware 的 `data` 字段内 — basePath=/api,paths key 去掉 /api 前缀 3. 测试 token 不入仓库 — 仅记长度 + PASS 判定 4. 临时验收脚本验完即删 — 一次性证据生成器 5. DB 探针态主动还原 — Phase 1 留下的契约不能被破坏 6. qy-lty-admin 改动走父级 Lila-Server/.git — 子目录无独立 .git ## Deviations from Plan ### 偏差 1:/swagger.json URL 形态调整(Rule 1 等价 — 现实修正) - **Found during:** Task 1 Step 3 - **Plan 假设:** `curl http://localhost:8000/swagger.json` 直返 OpenAPI JSON - **实际仓库状态:** 两点偏差: 1. urls.py 中 schema-json 路径模式是 `swagger/`(trailing slash),所以正确路径是 `/swagger.json/`(带尾斜线);不带尾斜线 → 301 redirect → follow 后变成 swagger UI 页面(HTML) 2. 本仓库 `StandardResponseMiddleware` 也会把 drf-yasg 返回的 OpenAPI JSON 包进 `{success, code, message, data}` 壳层,真正的 OpenAPI schema 在 `data` 字段内(basePath=/api,所以 paths key 是去掉 /api 前缀的 `/v1/admin/credential-slot/`) - **Adjustment:** 验证脚本(1)改用 `/swagger.json/` 带尾斜线(2)unwrap `body['data']` 取真正 schema(3)匹配 `/v1/admin/credential-slot/` 而非 `/api/v1/admin/credential-slot/` - **Reason:** Plan 假设的 URL 形态没考虑本仓库 StandardResponseMiddleware 对 drf-yasg JSON 也会包壳;这是仓库自有的 middleware 行为,不是 plan 错;验证脚本即时调整即可 - **Files modified:** 无(仅一次性验收脚本,已删) - **Commit:** 包含在 `3cfd481` 的 02-VERIFICATION.md "Step 4 关键发现" 段中说明 ### 偏差 2:Plan 的 verify auto 命令在裸 python 下需要手动 setup(Rule 3 等价) - **Found during:** Task 1 Step 2 / Step 3 - **Issue:** Plan 写的验收脚本片段(直接 `from django.test import Client` 这种 top-level import)在裸 `python -c` 中会触发 `ImproperlyConfigured`;需要 `os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'qy_lty.settings'); django.setup()` - **Adjustment:** 临时验收脚本顶部加 `os.environ.setdefault + django.setup()` 头部 boilerplate;功能等价 - **Reason:** Plan 假设了 `python manage.py shell <<'EOF'` 这种隐式 setup 环境;裸 python 调用需显式 setup(Plan 02-01 偏差 2 同款问题,已知) - **Files modified:** 无(仅一次性脚本,已删) ### 其余偏差 无。Plan 两个 task 的核心目标、acceptance criteria、修改记录条目模板(含跨项目联动字段)全部按 Plan 1:1 落地。 **Total deviations:** 2 处现实修正(均不涉及代码改动,仅一次性验收脚本调整) **Impact on plan:** 零功能影响 — 验收点 #7 + 全部断言 PASS;无 Plan 漂移、无 scope creep ## Issues Encountered 无。 ## Next Phase Readiness **Phase 2 整体 Complete**: - ROADMAP Phase 2 4 条 SC 全部 ✓ - CRED-03 + CRED-04 已 done(REQUIREMENTS.md 待 STATE 更新阶段标记) - 02-VERIFICATION.md 是 Phase 2 收尾时的证据来源 - DB 探针态稳定(probe_app / probe_secret_xxxx) **进 Phase 3 的准备**: - Phase 3 目标:CRED-05(客户端 GET `/api/credential-slot/`,user token 鉴权,**明文**返回)+ CRED-06(阿里云日志 formatter 用 `mask_token` 过滤 access_token) - 依赖:本 phase 已落地的 `CredentialSlot.get_solo()` / `mask_token` 工具 / `RedisTokenAuthentication` 鉴权 - 区别:客户端 GET 必须返回**明文**(手机端/设备端实际调用第三方需要),与 Phase 2 管理端 GET 脱敏正交;service 层数据流不同(直接走 `instance.access_token`,不走 `mask_token`) ## 与 02-01-SUMMARY 的关系 | Plan | 交付物 | 验收方式 | |------|--------|---------| | 02-01 | serializer + view + URL + swagger 装饰器 | 落地后单元级 import / reverse / Django check(Plan 内自验证据) | | 02-02 | 端到端 8 条 SC + 互引 | E2E 验收(Django test client 跑真实 HTTP 路径)+ 跨项目互引 | 两个 plan 联合构成 Phase 2 完整交付证据:02-01 证明"代码就位",02-02 证明"接口跑通 + 跨项目锚点闭环"。 ## Threat Flags 无。本 plan 改动严格落在 02-02-PLAN 的 `` 4 条已声明威胁内(T-02P2-01 ~ T-02P2-04): - T-02P2-01(验收 token 误入 git)→ mitigated:02-VERIFICATION.md 仅记长度,临时脚本验完即删 - T-02P2-02(验收数据覆盖探针)→ accepted + mitigated:脚本主动还原 probe_app / probe_secret_xxxx - T-02P2-03(误改 Phase 1 修改记录条目)→ mitigated:git diff 校验 Phase 1 两条 [2026-05-07] Phase 1 — ... 标题位置不变,仅顶部追加新条目 - T-02P2-04(互引文档泄露内部路径)→ accepted:与本仓库 README 同等暴露面 ## Self-Check: PASSED ### Files - FOUND: `.planning/phases/02-admin-rest/02-VERIFICATION.md`(本 plan 创建,Task 1 commit 3cfd481) - FOUND: `qy_lty/docs/修改记录.md`(顶部 line 26 新增 `### [2026-05-07] Phase 2 — 管理端通用凭据槽位 REST 接口(GET 脱敏 / PUT 覆写)`,Task 2 commit 46d72b8) - FOUND: `qy-lty-admin/docs/修改记录.md`(顶部 line 28 新增 `### [2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约(消费方文档化)`,Task 2 commit 46d72b8) ### Commits - FOUND: `3cfd481` test(02-02): 端到端验收 8 条 success criteria 全 PASS(qy_lty 仓库) - FOUND: `46d72b8` docs(02-02): 两端修改记录互引 Phase 2 接口契约(父 Lila-Server 仓库) ### 互引闭环 - FOUND: `qy_lty/docs/修改记录.md` line 44 含 `qy-lty-admin/docs/修改记录.md`(指向前端互引) - FOUND: `qy-lty-admin/docs/修改记录.md` line 47 含 `qy_lty/docs/修改记录.md`(指向后端互引) - 双向 grep 命中 → 闭环 ### Phase 1 条目位置不变 - VERIFIED: `git diff qy_lty/docs/修改记录.md` 仅显示新增条目(顶部追加 20 行),Phase 1 已有的两条 `[2026-05-07] Phase 1 — ...` 标题位置 unchanged --- *Phase: 02-admin-rest / Plan: 02* *Executed: 2026-05-07 by gsd-executor(顺序执行模式,无 worktree 隔离;qy-lty-admin 改动走父级 Lila-Server/.git 提交)*