29 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-admin-rest | 02 | execute | 2 |
|
|
true |
|
|
Purpose:
- 端到端验收:把 02-01 写出来的 view + serializer + URL 真正跑起来,证明三处 success criteria(GET 脱敏 / PUT 覆写 + 脱敏响应 / 鉴权拒绝矩阵)在生产路径成立
- 跨项目互引:给后续在 qy-lty-admin 起 CRED-FE-01 phase 时,前端能从修改记录直接定位到本 phase 的接口契约文档;给本仓库未来回查"这个接口什么时候上的、谁在消费"留一个反向锚点
Output:
qy_lty/docs/修改记录.md顶部新增一条 Phase 2 条目(5 字段 + 跨项目联动字段)qy-lty-admin/docs/修改记录.md顶部新增一条 Phase 2 条目(5 字段 + 服务端联动字段)- VERIFICATION.md 补充端到端 curl + Django shell 验收记录(在 SUMMARY 中归档)
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/02-admin-rest/02-CONTEXT.md @.planning/phases/02-admin-rest/02-RESEARCH.md @.planning/phases/02-admin-rest/02-01-PLAN.md02-01 落地后的 SUMMARY(含端到端 verify hook)
@.planning/phases/02-admin-rest/02-01-SUMMARY.md
修改记录格式 + 已落地条目模板
@qy_lty/docs/修改记录.md @qy-lty-admin/docs/修改记录.md
项目宪法(修改记录强制 + 跨项目互引规则)
@qy_lty/CLAUDE.md
鉴权 / 工具入口(用于端到端验收脚本)
@qy_lty/userapp/utils.py @qy_lty/userapp/authentication.py @qy_lty/aiapp/models.py @qy_lty/common/utils.py
Task 1:端到端 curl + Django shell 验收(8 条 success criteria) qy_lty/.planning/phases/02-admin-rest/02-VERIFICATION.md - 必读 .planning/phases/02-admin-rest/02-01-SUMMARY.md(02-01 留给本 task 的 hook) - 必读 .planning/phases/02-admin-rest/02-CONTEXT.md `` 段(8 条验收点表格) - 必读 .planning/phases/02-admin-rest/02-RESEARCH.md `Validation Architecture` 段(test client 程序化验收脚本片段 + 采样频率) - 必读 qy_lty/userapp/utils.py:33-37(generate_token 签发 admin / user token 的方式) - 必读 qy_lty/aiapp/models.py 的 CredentialSlot.get_solo(验证 PUT 在空记录场景的 get_or_create) - 必读 .planning/phases/01-credential-data-layer/01-VERIFICATION.md(Phase 1 程序化验收风格,本 task 1:1 沿用) **目标**:在 Django shell + Daphne / runserver 两条路径都验完 8 条 success criteria,留一份证据归档进 02-VERIFICATION.md。Step 1:准备测试 token(一次性 Django shell 脚本)
cd qy_lty && python manage.py shell <<'EOF'
from userapp.models import ParadiseUser
from userapp.utils import generate_token
import json
# 取一个已有 staff 用户(必须 is_staff=True;如全库无 staff,先 createsuperuser)
admin_user = ParadiseUser.objects.filter(is_staff=True).first()
assert admin_user is not None, "需要至少一个 is_staff=True 用户;运行 python manage.py createsuperuser"
admin_token = generate_token(admin_user.id, is_admin=True)
# 取一个普通用户(is_staff=False;如不存在可创建一个 phone-only 用户用于本验收)
user = ParadiseUser.objects.filter(is_staff=False).first()
if user is None:
user = ParadiseUser.objects.create(username='phase2_verify_user_temp', is_staff=False)
user_token = generate_token(user.id, is_admin=False)
print(json.dumps({
'admin_user_id': admin_user.id,
'admin_token': admin_token,
'user_id': user.id,
'user_token': user_token,
}, ensure_ascii=False))
EOF
记录输出的 admin_token / user_token 备用。注意:token 写入 Redis 后 30 天内有效;本 task 验完无需清理(可让其自然过期)。
Step 2:Django test client 程序化验收(无需启动服务进程,全部在 shell 内跑)
cd qy_lty && python manage.py shell <<'EOF'
from django.test import Client
from aiapp.models import CredentialSlot
from common.utils import mask_token
import json
ADMIN_TOKEN = "<填入 Step 1 拿到的 admin_token>"
USER_TOKEN = "<填入 Step 1 拿到的 user_token>"
URL = "/api/v1/admin/credential-slot/"
c = Client()
# === 验收点 1:GET 携 admin token 返回脱敏 ===
r = c.get(URL, HTTP_AUTHORIZATION=f"Bearer {ADMIN_TOKEN}")
body = r.json()
assert r.status_code == 200, f"#1 status={r.status_code} body={body}"
assert body['success'] is True
assert body['code'] == 200
assert 'data' in body
assert {'app_id', 'access_token', 'updated_at'} <= set(body['data'].keys())
# access_token 必须脱敏:要么是空(DB 为空时),要么以 * 开头且末 4 位为原 token 末 4 位
slot = CredentialSlot.get_solo()
expected_masked = mask_token(slot.access_token)
assert body['data']['access_token'] == expected_masked, f"#1 mask mismatch: got={body['data']['access_token']!r} expected={expected_masked!r}"
print("#1 PASS GET admin -> 200 + masked access_token")
# === 验收点 2:PUT 携 admin token 全字段覆写 + 响应脱敏 ===
new_token = "sk-phase2_verify_secret_ABCD1234"
r = c.put(URL, data=json.dumps({"app_id": "phase2_app", "access_token": new_token}),
content_type="application/json", HTTP_AUTHORIZATION=f"Bearer {ADMIN_TOKEN}")
body = r.json()
assert r.status_code == 200, f"#2 status={r.status_code} body={body}"
assert body['success'] is True
slot = CredentialSlot.get_solo()
assert slot.app_id == "phase2_app"
assert slot.access_token == new_token # DB 中明文存储
# 响应 access_token 必须脱敏:'sk-phase2_verify_secret_ABCD1234' -> '*****************************1234'
assert body['data']['access_token'] == mask_token(new_token), f"#2 PUT response not masked: {body['data']['access_token']!r}"
assert body['data']['access_token'].endswith('1234'), "末 4 位必须为 1234"
assert body['data']['access_token'].startswith('*'), "脱敏前缀必须以 * 开头"
print("#2 PASS PUT admin -> 200 + DB updated + response masked")
# === 验收点 3:PUT 在空记录场景自动 get_or_create ===
CredentialSlot.objects.all().delete()
r = c.put(URL, data=json.dumps({"app_id": "after_delete", "access_token": "tok-XYZ9"}),
content_type="application/json", HTTP_AUTHORIZATION=f"Bearer {ADMIN_TOKEN}")
body = r.json()
assert r.status_code == 200, f"#3 status={r.status_code} body={body}"
slot = CredentialSlot.get_solo()
assert slot.app_id == "after_delete"
assert slot.access_token == "tok-XYZ9"
print("#3 PASS PUT 空记录场景 -> 200 + get_or_create 创建并写入")
# === 验收点 4:无 Authorization 头 -> 401 + 标准壳层 ===
r = c.get(URL)
body = r.json()
assert r.status_code == 401, f"#4 status={r.status_code} body={body}"
assert body['success'] is False
assert body['code'] == 401
assert 'message' in body # 中间件 / DRF 至少要给一个 message 字段
print("#4 PASS no token -> 401 + 标准壳层")
# === 验收点 5:携普通 user token -> 403 + 标准壳层 + message 含管理员关键字 ===
r = c.get(URL, HTTP_AUTHORIZATION=f"Bearer {USER_TOKEN}")
body = r.json()
assert r.status_code == 403, f"#5 status={r.status_code} body={body}"
assert body['success'] is False
assert body['code'] == 403
assert "管理员" in body['message'], f"#5 message 不含'管理员'关键字: {body['message']!r}"
print("#5 PASS user token -> 403 + 需要管理员权限")
# === 验收点 6:PUT 携 user token -> 403(同 #5 路径,验证 PUT 也走 _ensure_admin) ===
r = c.put(URL, data=json.dumps({"app_id": "should_fail"}),
content_type="application/json", HTTP_AUTHORIZATION=f"Bearer {USER_TOKEN}")
body = r.json()
assert r.status_code == 403, f"#6 PUT user token status={r.status_code}"
print("#6 PASS PUT user token -> 403")
print("\n========== 全部 6 条 test client 验收通过 ==========")
EOF
Step 3:Swagger schema 验收(启动 daphne 后做一次 curl)
# 启动服务(如已运行可跳过)
# cd qy_lty && daphne -b 0.0.0.0 -p 8000 qy_lty.asgi:application &
curl -s http://localhost:8000/swagger.json | python -c "
import json, sys
schema = json.load(sys.stdin)
paths = schema.get('paths', {})
key = '/api/v1/admin/credential-slot/'
assert key in paths, f'#7 {key} 未出现在 swagger paths 中'
methods = paths[key]
assert 'get' in methods, '#7 GET 方法缺失'
assert 'put' in methods, '#7 PUT 方法缺失'
# 验证 access_token description 含脱敏关键字
get_resp = methods['get'].get('responses', {}).get('200', {})
schema_str = json.dumps(get_resp, ensure_ascii=False)
assert '脱敏' in schema_str or '末 4 位' in schema_str or 'mask' in schema_str.lower(), '#7 access_token description 不含脱敏掩码语义'
print('#7 PASS swagger paths 含 GET/PUT 两 method + access_token 脱敏描述')
"
Step 4:把 8 条验收结果写入 .planning/phases/02-admin-rest/02-VERIFICATION.md
文件全文模板(直接 Write):
# Phase 2 Verification — 管理端读写接口端到端验收
**Verified**: 2026-05-07
**Phase**: 02-admin-rest
**Plan**: 02-02
**Coverage**: CRED-03 + CRED-04(ROADMAP Phase 2 全部 4 条 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 → 403 + 需要管理员权限 | Django test client | ✓ PASS |
| 6 | PUT 携 user token → 403(验证 PUT 也走 _ensure_admin) | Django test client | ✓ PASS |
| 7 | /swagger.json 含路径条目 + GET/PUT 两 method + 脱敏 description | curl | ✓ PASS |
| 8 | 修改记录两端互引(02-02 Task 2 落地后追加) | 文件 grep | ⏳ 待 02-02 Task 2 |
---
## 证据片段
[黏贴 Step 1 / Step 2 / Step 3 输出的关键行]
---
*由 02-02-PLAN.md Task 1 生成*
关键约束:
- 6 条 test client 验收必须全部 PASS 才能进 02-02 Task 2;任何一条 FAIL 则回到 02-01 修 view / serializer / URL
- Swagger 验收(#7)若 daphne 未启动可暂时记 "deferred to integration",但必须在 phase gate 前补做
- #8 在本 task 仅占位(标 ⏳),Task 2 落地修改记录后回写为 ✓ PASS(02-VERIFICATION.md 在 Task 2 末尾再 Edit 一次)
- 不要把 admin / user token 明文写进 02-VERIFICATION.md(Redis 30 天 TTL,落进 git 的 token 在 30 天内仍有效,是新的泄露面)
cd qy_lty && python manage.py shell -c "from aiapp.models import CredentialSlot; from common.utils import mask_token; slot = CredentialSlot.get_solo(); print('DB state ok:', slot.app_id, mask_token(slot.access_token))" && test -f .planning/phases/02-admin-rest/02-VERIFICATION.md && grep -q "PASS" .planning/phases/02-admin-rest/02-VERIFICATION.md && echo OK
<acceptance_criteria>
- Step 2 脚本输出"========== 全部 6 条 test client 验收通过 =========="
- Step 3 输出 "#7 PASS"(如 daphne 未启动则记 deferred 但必须在 SUMMARY 中列出补做计划)
- 文件
.planning/phases/02-admin-rest/02-VERIFICATION.md存在 - 文件含至少 6 处 "PASS"(验收点 1-6)
- 文件不含 admin / user token 明文(grep
Bearer应只出现在脚本模板段落、不含具体 UUID 字串) - DB 状态符合预期:
CredentialSlot.get_solo()返回的 app_id 与 access_token 是 Step 2 写入的最新值 </acceptance_criteria> 8 条 success criteria 中 6 条 test client + 1 条 swagger 已验完并归档;DB 状态稳定;02-VERIFICATION.md 是 Phase 2 收尾时 ROADMAP 标记 ✓ Complete 的证据来源。
### [2026-05-07] Phase 2 — 管理端通用凭据槽位 REST 接口(GET 脱敏 / PUT 覆写)
配套 Phase:[.planning/phases/02-admin-rest/](.planning/phases/02-admin-rest/)
覆盖需求:CRED-03 + CRED-04
设计参考:1:1 复刻 `aiapp.views.RTCChatHistoryAPIView`(`aiapp/views.py:434-555`)的单 URL 多方法 APIView 风格
- **文件路径**:
- `aiapp/serializers.py`(修改 — 顶部 import 追加 `CredentialSlot`,文件末尾追加 `CredentialSlotSerializer` ModelSerializer 类)
- `aiapp/views.py`(修改 — 顶部 import 追加 `CredentialSlot` / `CredentialSlotSerializer` / `mask_token` / `get_standardized_response_schema`;文件末尾追加 `CredentialSlotPutRequestSchema` swagger 请求体 + `_credential_slot_data_schema` 响应 data schema + `CredentialSlotAdminView` APIView 类)
- `userapp/admin_urls.py`(修改 — 追加 `from aiapp.views import CredentialSlotAdminView` 与 `path('credential-slot/', CredentialSlotAdminView.as_view(), name='admin_credential_slot')`)
- **修改类型**: 新增
- **修改内容**:
- 暴露 `GET /api/v1/admin/credential-slot/`:admin token 鉴权(`RedisTokenAuthentication` + 视图内 `is_staff` 二次校验,不发明 admin-only permission 类);返回 `{ success, code, message, data: { app_id, access_token: <末 4 位脱敏掩码>, updated_at } }`,脱敏由 view 层调 `common.utils.mask_token` 完成(serializer 不参与脱敏,避免双重责任)
- 暴露 `PUT /api/v1/admin/credential-slot/`:admin token 鉴权;接受 `{ app_id, access_token }` 全字段覆写;空记录场景自动走 `CredentialSlot.get_solo()` 的 `get_or_create(pk=1)`;写入后 `updated_at` 由 `auto_now=True` 自动刷新;响应同样脱敏 access_token(避免运营在 admin UI 看到自己刚提交的明文回显)
- 鉴权拒绝矩阵:无 token → 401(DRF NotAuthenticated → middleware 兜底标准壳层);持普通 user token(非 staff)→ 403 + `message="需要管理员权限"`
- Swagger / ReDoc 自动暴露:method-level `@swagger_auto_schema` 装饰器;响应 schema 配 `common.swagger_utils.get_standardized_response_schema()`;access_token 字段 description 显式标注「Access Token 末 4 位脱敏掩码(如 "*********1234")」,避免前端误解为明文
- 不引入新依赖(沿用 Django 4.2.13 + DRF + drf-yasg + Phase 1 落地的 `CredentialSlot.get_solo` / `mask_token`)
- **修改原因**: Milestone v1.0「通用凭据槽位(APP ID + Access Token)」Phase 2 — 给管理后台前端(qy-lty-admin)暴露受控的凭据读写入口,让运营无需进 Django Admin 也能管理凭据;GET 与 PUT 响应均脱敏,避免明文经管理端 UI / 浏览器 devtools / 阿里云日志(GET 响应体路径)泄露;为 Phase 3 客户端明文 GET 接口 + 阿里云日志 formatter 提供"接口已上线、凭据可写入"的稳定起点
- **跨项目联动**: 前端联动条目 [qy-lty-admin/docs/修改记录.md](../../qy-lty-admin/docs/修改记录.md) 同期 `[2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约(消费方文档化)`。本 phase 是 Milestone v1.0 首次跨项目接口契约落地:本仓库(服务端)暴露 `/api/v1/admin/credential-slot/` GET/PUT,前端 `qy-lty-admin` 后续 phase 将基于该契约写 API client(含 React Hooks 调用 + 表单录入 UI)。前后端各自维护独立修改记录,本条与对方条目互相引用,便于未来回查接口的双向上下游
Step 2:在 qy-lty-admin/docs/修改记录.md 顶部(紧跟第 26 行注释 <!-- 新的修改记录添加在此处下方,最新的在最前面 --> 之下、第 28 行 ### [2026-05-07] 修复 NEXT_PUBLIC_API_BASE_URL... 之上)插入条目:
### [2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约(消费方文档化)
配套服务端 Phase:[../qy_lty/.planning/phases/02-admin-rest/](../../qy_lty/.planning/phases/02-admin-rest/)
覆盖服务端需求:CRED-03 + CRED-04(本仓库消费方)
- **文件路径**: `docs/修改记录.md`(仅文档新增条目;本仓库代码未改)
- **修改类型**: 新增
- **修改内容**:
- 文档化服务端在本日落地的 `/api/v1/admin/credential-slot/` REST 接口契约(GET 脱敏读取 + PUT 全字段覆写 + admin token 鉴权),为后续 Web 管理后台前端(本仓库)的 CRED-FE-* phase 写 API client 与表单 UI 留下契约锚点
- 接口契约要点(消费方视角):
- URL:`{NEXT_PUBLIC_API_BASE_URL}/v1/admin/credential-slot/`
- 鉴权:`Authorization: Bearer <admin_token>`(来源于 `/api/v1/admin/login/` 的现有 admin 登录返回值)
- GET 响应:`{ success: boolean, code: number, message: string, data: { app_id: string, access_token: string /* 末 4 位脱敏掩码,前缀 * */, updated_at: string /* ISO 8601 */ } }`
- PUT 请求体:`{ app_id?: string, access_token?: string }`(任一字段缺省时由后端兜底保留原值;写入是全字段覆写语义,建议前端 UI 始终提交两字段全集以避免歧义)
- PUT 响应同 GET 形态(access_token 同样脱敏返回,前端**不应**用响应值回填明文输入框)
- 错误矩阵:401(无 token / token 失效)、403(持非 admin token,message 含"需要管理员权限")、400(参数无效)
- **修改原因**:
- 服务端首次为本管理后台暴露受控的凭据读写接口;本仓库即将启动 CRED-FE-01(API client) + CRED-FE-02(表单录入页面)等 phase,先把后端契约固化进本仓库修改记录便于反查
- 文档化"GET 与 PUT 响应均脱敏 access_token"避免前端工程师误以为可以从响应回填明文表单(实际明文仅存于 DB;任何回填只能保留掩码或要求运营重新输入)
- **服务端联动**: 后端联动条目 [../qy_lty/docs/修改记录.md](../../qy_lty/docs/修改记录.md) 同期 `[2026-05-07] Phase 2 — 管理端通用凭据槽位 REST 接口(GET 脱敏 / PUT 覆写)`。本仓库代码未改,仅文档侧做契约固化;待本仓库 CRED-FE-01 phase 启动落地 API client + Hook 时再补一条独立条目并互引
Step 3:互引校验(写完后立即 grep)
# 验证两端互引字段都在
grep -n "qy-lty-admin/docs/修改记录" qy_lty/docs/修改记录.md | head -3
grep -n "qy_lty/docs/修改记录" qy-lty-admin/docs/修改记录.md | head -3
# 验证两边都标了 Phase 2 + 2026-05-07
grep -n "Phase 2.*2026-05-07\|2026-05-07.*Phase 2" qy_lty/docs/修改记录.md qy-lty-admin/docs/修改记录.md
Step 4:回写 02-VERIFICATION.md 验收点 #8
把 Task 1 写入的 02-VERIFICATION.md 中验收点 #8 的状态从 ⏳ 待 02-02 Task 2 改为 ✓ PASS,并追加证据:
| 8 | 修改记录两端互引 | grep `qy-lty-admin/docs/修改记录` qy_lty/docs/修改记录.md ✓;grep `qy_lty/docs/修改记录` qy-lty-admin/docs/修改记录.md ✓ | ✓ PASS |
关键约束:
- 两条条目必须用日期
2026-05-07(与 Phase 1 / 当前 currentDate 一致;从 system context 得知) - 两条条目都必须含跨项目联动 / 服务端联动字段,且字段值必须指向对方文件路径(不能写"无",Phase 1 那样的"暂无前端"逻辑在 Phase 2 不成立 —— 本 phase 就是首次跨项目接口契约落地)
- 服务端条目中的相对路径用
../../qy-lty-admin/docs/修改记录.md(位于qy_lty/docs/,跳到qy_lty/上级Lila-Server/再进qy-lty-admin/docs/) - 前端条目中的相对路径用
../../qy_lty/docs/修改记录.md(同理对称) - 两条条目都要插在各自文件的「修改历史」段顶部(最新在最前),不要追加到末尾
- 不要修改 Phase 1 已有的两条条目(Phase 1 当时纯服务端、无前端互引是合理的,不要回头改成"互引"破坏历史归档)
- 不要在
qy-lty-admin仓库改任何代码(本 task 仅在 qy-lty-admin 仓库内动 docs 一个文件) grep -c "Phase 2 — 管理端通用凭据槽位 REST 接口" "C:\Users\admin\Desktop\Lila-Server\qy_lty\docs\修改记录.md" && grep -c "Phase 2 — 锁定后端通用凭据槽位 REST 接口契约" "C:\Users\admin\Desktop\Lila-Server\qy-lty-admin\docs\修改记录.md" && grep -q "qy-lty-admin/docs/修改记录" "C:\Users\admin\Desktop\Lila-Server\qy_lty\docs\修改记录.md" && grep -q "qy_lty/docs/修改记录" "C:\Users\admin\Desktop\Lila-Server\qy-lty-admin\docs\修改记录.md" && echo OK <acceptance_criteria>qy_lty/docs/修改记录.md顶部「修改历史」段第一条标题为### [2026-05-07] Phase 2 — 管理端通用凭据槽位 REST 接口(GET 脱敏 / PUT 覆写)- 该条目含 5 个标准字段(文件路径 / 修改类型 / 修改内容 / 修改原因 / 跨项目联动)
- 该条目"跨项目联动"字段含字串
qy-lty-admin/docs/修改记录.md(指向前端互引) qy-lty-admin/docs/修改记录.md顶部「修改历史」段第一条标题为### [2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约(消费方文档化)- 该条目含 5 个标准字段(文件路径 / 修改类型 / 修改内容 / 修改原因 / 服务端联动)
- 该条目"服务端联动"字段含字串
qy_lty/docs/修改记录.md(指向后端互引) - 两条条目互相引用对方(grep 双向均命中 → 闭环)
- Phase 1 的两条条目位置不变(位于本次新增条目之下)
- 02-VERIFICATION.md 中验收点 #8 已改为 ✓ PASS
</acceptance_criteria>
qy_lty+qy-lty-admin两端docs/修改记录.md顶部各有一条 Phase 2 条目,互相引用对方文件路径;CLAUDE.md "qy_lty 与 qy-lty-admin 是独立项目,跨项目联动两端各写一条互相引用对方"规则在本 phase 闭环;02-VERIFICATION.md 8 条全 PASS。
- 8 条 success criteria 全 PASS:02-VERIFICATION.md 表格全部 ✓
- 互引闭环:
grep "qy-lty-admin" qy_lty/docs/修改记录.md≥ 1 hit(在 Phase 2 条目内)grep "qy_lty" qy-lty-admin/docs/修改记录.md≥ 1 hit(在 Phase 2 条目内)
- ROADMAP Phase 2 4 条 success criteria 已覆盖:
- SC#1 GET 脱敏 ← 验收点 #1 ✓
- SC#2 PUT 全字段覆写 + get_or_create ← 验收点 #2 + #3 ✓
- SC#3 鉴权拒绝矩阵 ← 验收点 #4 + #5 + #6 ✓
- SC#4 Swagger / ReDoc schema 一致 ← 验收点 #7 ✓
- Phase 1 条目未被改动:
git diff qy_lty/docs/修改记录.md仅显示新增条目(在文件顶部追加),Phase 1 已有的两条[2026-05-07] Phase 1 — ...标题在 diff 内位置应该是"unchanged"
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| 端到端 verify 测试 token | 临时 admin / user token,30 天 TTL;不允许写入仓库 |
| 跨项目修改记录文件 | 文档边界;不修改对方仓库代码,仅在对方 docs/ 下追加一条文档 |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-02P2-01 | Information Disclosure | 验收脚本中的 admin / user token 误入 git | mitigate | 02-VERIFICATION.md 仅记录脚本模板与"PASS"判定;不黏贴具体 token UUID;脚本输出在 SUMMARY 中也只摘要"6/6 PASS"不贴 token |
| T-02P2-02 | Information Disclosure | 验收用真实凭据写入 DB(覆盖 Phase 1 探针) | accept | DB 中 access_token 本来就是明文存储(Phase 1 落地决策);PUT 写入的 sk-phase2_verify_secret_ABCD1234 不是真实第三方凭据,只是测试串;Task 1 Step 2 #3 还会再 delete + 重建覆盖一次 |
| T-02P2-03 | Tampering | 误修改 Phase 1 修改记录条目 | mitigate | Task 2 关键约束第 7 条明确"不要修改 Phase 1 已有的两条条目";verify 步骤 #4 用 git diff 校验 Phase 1 条目位置不变 |
| T-02P2-04 | Information Disclosure | 互引文档泄露内部路径 | accept | .planning/ 已是仓库内文档,路径暴露与本仓库 README 同等;不引入新泄露面 |
| </threat_model> |
<success_criteria> 本 plan 落地完成的标志(phase 收尾标志):
.planning/phases/02-admin-rest/02-VERIFICATION.md存在,8 条验收点 6+1+1 = 8 全 ✓qy_lty/docs/修改记录.md顶部含### [2026-05-07] Phase 2 — 管理端通用凭据槽位 REST 接口(GET 脱敏 / PUT 覆写)qy-lty-admin/docs/修改记录.md顶部含### [2026-05-07] Phase 2 — 锁定后端通用凭据槽位 REST 接口契约(消费方文档化)- 两条互引字段双向命中:
grep "qy-lty-admin/docs/修改记录" qy_lty/docs/修改记录.md≥ 1;grep "qy_lty/docs/修改记录" qy-lty-admin/docs/修改记录.md≥ 1 - DB 中 CredentialSlot 单例存在且 access_token 是 Task 1 #3 写入的最终值(
tok-XYZ9或之后被 #3 覆盖的某个值) - Phase 1 两条修改记录条目未被改动(git diff 确认)
- ROADMAP Phase 2 4 条 success criteria 在 02-VERIFICATION.md 中均找到对应的 ✓ 验收点映射 </success_criteria>