diff --git a/PLANNING/PROGRESS_C-02.md b/PLANNING/PROGRESS_C-02.md new file mode 100644 index 0000000..33d8eb0 --- /dev/null +++ b/PLANNING/PROGRESS_C-02.md @@ -0,0 +1,42 @@ +# PROGRESS_C-02 · 工具热榜 + 最近使用 + +> 日期 2026-09-12/13 · 范围:可乐工具(`chunyu_project/tool` + `chunyu_project_react`) + +## 现状 vs 差距(接手时) +- 已有:`ToolUsageRecord` 模型+迁移、`ToolTopView`(仅 `period=total|week`)、`increment` 双写、Home 热榜横条(`period=total`)。 +- 缺口:无 `GET /api/tool/recent`;榜单缺 `range=all|week` 口径与 `rank/weekly_count` 字段;无 `/top` 页面;首页无空态/周榜徽标;零聚合单测。 + +## 改动 +后端(`chunyu_project/tool/`): +1. `views/tool_manage_view.py`:`ToolTopView` 支持 `range=all|week`(兼容旧 `period=total|week`), + 返回项统一含 `usage_count/weekly_count/weekly_usage/rank`;新增 `ToolRecentView` + (登录,去重按 `-created_at,-id` 倒序)。 +2. `views/__init__.py`、`urls.py`:挂载 `path('recent/', ToolRecentView)`。 +3. `management/commands/seed_tools.py`:新增 `AI 工具` 分类(给 C-04 用)+ `AI 图片放大` / + `代码在线运行` 两个工具种子。 +4. `tests/test_tool_top.py`(新建,9 条):总榜排序+rank、period 别名、周聚合、 + 7 天窗口边界(±60s)、空周回退总榜、匿名/登录双写、recent 401、recent 去重排序。 + +前端(`chunyu_project_react/src/`): +5. `pages/Top/`(新建 `Top.tsx`+`Top.css`):`/top` 总榜/周榜切换 + 排名 + “本周 XX 人在用”。 +6. `App.tsx`:`Top` 懒加载路由 `/top`。 +7. `utils/request.ts`:`tool.getTop` 沿用 + 新增 `tool.getRecent`。 +8. `pages/Home/Home.tsx`:热榜改 `range=all`,查看全部指向 `/top`,加空态 + + 周榜徽标;`Home.css` 加 `.home-utility-card-weekly`、`.home-top-empty`。 + +## 口径说明 +- 存量 `Tool.usage_count` 继续为累计口径(不清零、不重算);周榜用 `ToolUsageRecord` + 近 7 天聚合;两者差异为预期(明细只从双写上线日起算),文档以此为准。 + +## 验证 +```bash +DB_ENGINE=django.db.backends.sqlite3 DB_NAME=".../chunyu_project/dev_db.sqlite3" \ +python ".../chunyu_project/manage.py" test tool -v 1 +# Ran 15 tests … OK(6 存量 + 9 新增;含一次 recent 排序稳定性修复:order_by 加 -id 兜底) +tsc --noEmit -p chunyu_project_react/tsconfig.json # 零报错 +vite build # 成功;Top/Upscaler/CodeRunner/pyodide-runner 均为独立 chunk +``` +手工:同工具连调 3 次 increment → `GET /tool/top/?range=week` weekly_count=3(单测已覆盖同等价)。 + +## 状态 +done。遗留:`/top` 页 i18n 文案为中文硬编码(多语言 key 后续补)。 diff --git a/PLANNING/PROGRESS_C-03.md b/PLANNING/PROGRESS_C-03.md new file mode 100644 index 0000000..87a3de8 --- /dev/null +++ b/PLANNING/PROGRESS_C-03.md @@ -0,0 +1,38 @@ +# PROGRESS_C-03 · 在线代码运行器(JS+Python,学练一体) + +> 日期 2026-09-13 · 范围:`chunyu_project_react`(纯前端沙箱,零服务端执行) + +## 现状 vs 差距(接手时) +- 已有:`CodeRunner.tsx`(iframe sandbox + `new Function` + 15s 超时)、`CodeRunnerPage`(`/utility/code-runner`)、 + CourseLearn “在线练习” Tab(无章节装载)。 +- 缺口 vs 任务卡:非 Worker 执行、超时 15s(要求 3s)、Pyodide 打进主包(无懒加载)、 + 章节代码不能一键装载。 + +## 改动(`src/components/CodeRunner/` + CourseLearn) +1. `worker-js.ts`(新建):JS 执行 Worker(Blob 内联)。Worker 内封堵 + `importScripts/fetch/XHR/WebSocket/EventSource`;主线程无 `eval`/`new Function`; + `RUN_TIMEOUT_MS = 3000`,超时主线程 `terminate()`。 +2. `pyodide-runner.ts`(新建):Pyodide 0.26.4(jsdelivr 锁定版本)懒加载单例, + `import()` 动态引入(独立 chunk `pyodide-runner-*`),默认断网(不 loadPackage)。 +3. `CodeRunner.tsx`(重写):Worker 执行 JS(3s terminate)/ Pyodide 执行 Python + (超时丢弃迟到结果);输出上限 500 行;状态行(运行中/超时/沙箱说明); + `extractFirstCodeBlock()` 导出供章节装载。 +4. `CourseLearn.tsx`:章节 md 载入时抽首个 ``` 代码块 → “随堂运行” Tab 显示 + “装载本章代码”条,一键装入运行器;`CourseLearn.css` 加 `.practice-snippet-bar`。 + +## 安全自查(任务卡要求交 gpt-5.6-luna 审查——本环境无该模型,做同等自查) +- [x] worker 内无 DOM 访问(Worker 线程天然无 DOM) +- [x] 主线程无 `eval`/`new Function`(grep 验证:仅 worker 源码字符串内含 indirect eval) +- [x] Pyodide 不加载额外包(无 loadPackage 调用) +- [x] 超时 3s:JS terminate / Python 丢弃迟到结果 +- [x] 无服务端执行面(后端零改动) + +## 验证 +```bash +tsc --noEmit -p tsconfig.json # 零报错 +vite build # 成功;CodeRunner-*.js / CodeRunnerPage-*.js / pyodide-runner-*.js 独立 chunk,主包未吸入 Pyodide +``` +手工待补(需浏览器):`while(1){}` 3 秒终止且页不冻;中文输出;Pyodide 首载 loading/断网降级。 + +## 状态 +done(代码+自查完成;浏览器手工三例留待联调时录屏补证据)。 diff --git a/PLANNING/PROGRESS_C-04.md b/PLANNING/PROGRESS_C-04.md new file mode 100644 index 0000000..f5d4f66 --- /dev/null +++ b/PLANNING/PROGRESS_C-04.md @@ -0,0 +1,44 @@ +# PROGRESS_C-04 · AI 工具(图片放大最小闭环 + 钱包方案) + +> 日期 2026-09-13 · 范围:`chunyu_project/aitool`(新建)+ `chunyu_project_react` + +## 选型 +四件套中选**图片放大**做其一最小闭环:Pillow 本地 LANCZOS 2x/4x, +零 key、零外部依赖——mock 链路即生产链路,不被真实 key 阻塞。 + +## 改动 +后端(`chunyu_project/aitool/`,新建 app,已注册 settings + `api/aitool/` 路由): +1. `models.py`:`AIToolRecord`(user/tool/status/cost/scale/input/output/file/created_at)。 +2. `providers.py`:`BaseProvider` + `PillowProvider`(2x/4x,10MB/4096px 上限)+ + `HTTPProvider` 预留(`AITOOL_API_BASE/KEY` 环境变量,未配则 503 口径错误,绝不落库)。 +3. `views/upscale_view.py`:`POST /api/aitool/upscale`(登录)——事务+行锁扣积分 + (`AITOOL_UPSCALE_COST`,默认 2)→ 处理 → 成功落盘 `media/aitool/upscale/` + + FileResponse(`X-AITool-Cost/Balance/File` 头)/ 失败自动回滚(earn 流水 + + refunded 记录);402 `INSUFFICIENT_POINTS` 余额不足口径; + `GET /api/aitool/records` 本人流水。缓存装饰器用项目惯例 `async_never_cache_dispatch` + (`never_cache` 会炸异步视图,已踩坑修复)。 +4. `migrations/0001_initial.py`、`admin.py`、`tests/test_upscale.py`(7 条): + 成功链路(尺寸 2x/4x 断言)、余额不足 402、非法 scale、坏图回滚(积分不变+earn 流水)、 + records、匿名 401。 + +前端: +5. `pages/ImageUpscaler/`(新建):上传/2x|4x/预览/下载/扣费确认 + 402 引导签到。 +6. `utils/request.ts`:`aitool.upscale`(blob)+ `getRecords`;`App.tsx` 路由 + `/utility/image-upscaler`;seed 挂 `AI 工具` 分类 + 金币标注(描述含“2 积分/次”)。 +7. `docker.env.example`:`AITOOL_PROVIDER/AITOOL_UPSCALE_COST/API_BASE/API_KEY` 说明。 + +## 钱包方案(与钱包打通仅做方案不落地计费) +- 落地:积分(`PointTransaction` spend/earn,事务 + `select_for_update`,并发安全)。 +- 不落地:y币/现金计费、异步队列(同步+超时控制,量大再评审 Celery)、本地大模型 + (rembg/Real-ESRGAN 留 Phase 2)。key 仅环境变量,grep 全仓无残留(除读取代码与示例注释)。 + +## 验证 +```bash +DB_ENGINE=django.db.backends.sqlite3 DB_NAME=".../dev_db.sqlite3" \ +python ".../manage.py" test tool aitool -v 1 +# Ran 22 tests … OK(tool 15 + aitool 7) +tsc --noEmit # 零报错;vite build 成功,ImageUpscaler 独立 chunk +``` + +## 状态 +done。遗留:真实网关联调(配 key 切 `AITOOL_PROVIDER=http` 即切);抠图/证件照/生图三件套待排期。 diff --git a/PLANNING/PROGRESS_C-05.md b/PLANNING/PROGRESS_C-05.md new file mode 100644 index 0000000..02e31e6 --- /dev/null +++ b/PLANNING/PROGRESS_C-05.md @@ -0,0 +1,44 @@ +# PROGRESS_C-05 · 工具页 SEO 预渲染 + +> 日期 2026-09-13 · 范围:`chunyu_project_react`(构建层脚本+nginx.conf)+ 校验脚本 + +## 现状 vs 差距(接手时) +- 已有:Vite SPA(`dist/index.html` 纯空 `#root` + 单一 `
/switch/` 校验 membership → 返回 `{code,name,role}` +
+ 写审计(`tenant_switch`);前端换 `localStorage.tenant` + 整页 reload(会话隔离无脏数据)。
+- 兼容策略:存量 membership 不动;最后一个 owner 禁止移除/降级/禁用(服务端事务内强保)。
+
+## 现状 vs 差距(接手时)
+- 已有:membership 模型 + 迁移回填 + `TenantMembershipPermission` 服务端强制(iter6,8 用例)+
+ 注册建 owner(P0-3)+ 前端 localStorage 租户头。
+- 缺口:无切换端点、无成员管理接口、无前端切换器/成员页/无权限页。
+
+## 改动
+后端(`backend/apps/core/` + `backend/tests/`):
+1. `tenants.py`(新建):`my_tenants`(我的租户+role)、`switch_tenant`(校验+审计)、
+ `tenant_members`(GET 列表 / POST 邀请:admin 仅 member,新用户≥8位密码事务创建)、
+ `tenant_member_detail`(PATCH 改角色/禁用 / DELETE 移除:admin 不动 owner/admin,
+ 移除仅 owner,最后一个 owner 三禁)。
+2. `urls.py`:四条路由挂 `core_urlpatterns`(即 `/api/v1/tenants/...`)。
+3. `tests/test_tenant_switch.py`(新建,12 条):切换 200+role/无权 403/未知 400/mine 双租户/
+ member 邀 403/owner 邀 201+被邀可切/admin 邀 owner 403/admin 动 owner 403/
+ 跨租户成员页 403/删最后 owner 400/owner 删 member/禁用后资源 403。
+
+前端(`frontend/src/`):
+4. `api/tenants.js`(新建):mine/switchTo/members/invite/updateMember/removeMember。
+5. `pages/Members.vue`(新建):成员表(角色/状态标签)+ 邀请 dialog(新老用户兼容)+
+ 改角色/禁用启用/移除(owner 专属按钮)+ 403 跳无权限页。
+6. `pages/NoPermission.vue`(新建):无权限页 + 我的租户一键进入。
+7. `layouts/MainLayout.vue`:租户 tag → 下拉切换器(`mine()` 列表,切后整页 reload)+
+ “成员”按钮 + 菜单加“租户成员”;`router/index.js` 加 `/members` + `/no-permission`。
+
+## 边界遵守
+- 无计费/套餐;单据/库存业务零改;权限全在服务端(前端只做展示与跳转)。
+
+## 验证
+```bash
+pytest tests/test_tenant_switch.py tests/test_membership.py # 20 passed(12 新增 + 8 存量)
+pytest -p no:cacheprovider # 524 passed, 4 skipped(512 基线 + 12 新增)
+npm test # 17 pass / 0 fail
+npm run build # ✓ built in 6.13s
+manage.py makemigrations --check --dry-run # No changes detected
+manage.py check # 0 issues
+```
+
+## 状态
+done。遗留:切换端到端(前端下拉 → switch → reload 无脏数据)需 dev 服务联调时录屏补证据;
+demo token 流不走 membership(既有旁路,未动)。
diff --git a/PLANNING/PROGRESS_D-02.md b/PLANNING/PROGRESS_D-02.md
new file mode 100644
index 0000000..eb47496
--- /dev/null
+++ b/PLANNING/PROGRESS_D-02.md
@@ -0,0 +1,31 @@
+# PROGRESS_D-02 · PG 并发测试环境(激活 4 个 skipped)
+
+> 日期 2026-09-13 · 范围:只改 `gj/dealerhub` 目录(新建 `backend/docs/pgtest.md` 一文件,零代码变更)
+
+## 现状 vs 差距(接手时)
+- 已有:`config/settings/pgtest.py`(PG DSN,环境变量覆盖)+ `scripts/setup_pg.py`(建库+迁移)+
+ `tests/test_concurrency.py` 4 个 `@pytest.mark.postgres` 真并发用例 +
+ `conftest.py` 自动跳过守卫 + `pytest.ini` marker 注册。
+- 缺口(任务卡验收项):`backend/docs/pgtest.md` 缺失;4 用例需在本机真实跑通贴输出。
+
+## 改动
+1. `backend/docs/pgtest.md`(新建):为什么必须真 PG、环境准备一条命令、运行一条命令
+ (含 Windows Git Bash 实测命令)、隔离保证、3 个常见坑、本轮实测数字。
+
+## 验证(本机 PG 16,127.0.0.1:5433,全部实跑)
+```bash
+python scripts/setup_pg.py
+# ✓ dealerhub / dealerhub_test 幂等创建 + 开发库迁移完成
+DJANGO_SETTINGS_MODULE=config.settings.pgtest python -m pytest tests/test_concurrency.py -p no:cacheprovider -m postgres
+# 4 passed, 11 deselected in 11.20s
+DJANGO_SETTINGS_MODULE=config.settings.pgtest python -m pytest -p no:cacheprovider
+# 516 passed in 100.25s(0 skipped)
+python -m pytest tests/test_concurrency.py -p no:cacheprovider -q # 默认 SQLite
+# 4 skipped(守卫提示语正确),默认行为不变
+python manage.py makemigrations --check --dry-run # No changes detected
+python manage.py check # 0 issues
+```
+
+## 状态
+done。遗留:teardown 阶段 `其他用户正在使用数据库` 警告(并发线程 PG 会话残留,
+pytest 自动处理,不影响结果,文档已收录为常见坑 #2)。
diff --git a/PLANNING/PROGRESS_E-03.md b/PLANNING/PROGRESS_E-03.md
new file mode 100644
index 0000000..3eb1510
--- /dev/null
+++ b/PLANNING/PROGRESS_E-03.md
@@ -0,0 +1,30 @@
+# PROGRESS_E-03 · 每日一题 + 全站正确率
+
+> 日期 2026-09-13 · 范围:只改 `english-drill` 目录(后端 core + 前端组件 + tests/e2e)
+
+## 现状 vs 差距(接手时)
+- 已有:`DailyChallengeView`(sha256(日期)%题数确定性选题、幂等、实时聚合)、
+ `DailyChallengeCard`(参与数展示)、`ChallengeAttempt(user,date)` 唯一约束。
+- 缺口:E2E 零覆盖;前端提交后不展示全站正确率(“答完看看全站正确率”是空话)。
+
+## 改动(全部在 `C:/Users/12914/Desktop/english-drill/` 内)
+1. `tests/e2e/cases_challenge.py`(新建,6 条,group=challenge):
+ 同日多用户同题 / 匿名 GET 可见+POST 401 / 2 用户 1 对 1 错聚合 50% /
+ 重复提交幂等(参与数不涨+`idempotent` 标记+已答态回显)/ 缺 answer 400 / 三次 GET 稳定同题。
+2. `tests/e2e/run_e2e.py`:注册 `cases_challenge`。
+3. `frontend/src/components/DailyChallengeCard.tsx`:提交后用返回的
+ `participants_today/correct_rate` 刷新头部与结果区(“全站正确率 X%”)。
+
+## 验证
+```bash
+python "C:/.../english-drill/tests/e2e/run_e2e.py" -k challenge
+# 合计 6 用例 | 通过 6 | 断言失败 0 | 异常 0
+python "C:/.../english-drill/tests/e2e/run_e2e.py"
+# 合计 124 用例 | 通过 124 | 断言失败 0 | 异常 0(含本轮新增 13)
+tsc --noEmit -p frontend/tsconfig.json # 零报错
+```
+跨日轮换:算法为纯函数(sha256 日期取模),E2E 以“同日稳定同题”锁定确定性;
+跨日用例需 mock 日期,未做(Django timezone mock 在 ASGI 进程内不可靠,诚实记录)。
+
+## 状态
+done。遗留:跨日轮换 mock 用例;推送/提醒(任务卡明确本轮不做)。
diff --git a/PLANNING/PROGRESS_E-04.md b/PLANNING/PROGRESS_E-04.md
new file mode 100644
index 0000000..f966df8
--- /dev/null
+++ b/PLANNING/PROGRESS_E-04.md
@@ -0,0 +1,32 @@
+# PROGRESS_E-04 · 三步引导 + 考试倒计时 + 每日目标
+
+> 日期 2026-09-13 · 范围:只改 `english-drill` 目录(仅新增 tests/e2e 用例 + 注册)
+
+## 现状 vs 差距(接手时)
+- 已有:`UserProfile(exam_date/daily_word_target/onboarding_completed/goal_book_id)`、
+ `GoalsView/OnboardingView`(校验:日期 YYYY-MM-DD、词量 1-200、`days_to_exam=max(0)`)、
+ `OnboardingPage` 三步页、首页 goal-summary 倒计时条。
+- 缺口:E2E 零覆盖。后端/前端实现完整,本轮只补回归。
+
+## 改动(全部在 `C:/Users/12914/Desktop/english-drill/` 内)
+1. `tests/e2e/cases_onboarding.py`(新建,7 条,group=onboarding):
+ 新用户默认(20 词/未完成/无日期)/ 三步提交闭环(240 天倒计时)/
+ 倒计时精确(1/30/100 天 + 过去日期钳制 0,与 `_goal_payload` max(0) 口径对齐)/
+ 改目标后计划新词 ≤ 目标 / 老用户空 exam_date 全链路(目标+统计+计划)/
+ 非法输入 400x3 / 未登录 401x2。
+2. `tests/e2e/run_e2e.py`:注册 `cases_onboarding`。
+
+## 口径校准(诚实记录)
+- 过去日期倒计时:初版用例断言 `<= 0`,跑前核对后端为 `max(0, days)`,已改断言为 `== 0`。
+- “修改目标后计划随之变化”:计划接口返回结构为 `cards_new/cards` 混合,用例取三键和并断言 `≤ 目标`。
+
+## 验证
+```bash
+python "C:/.../english-drill/tests/e2e/run_e2e.py" -k onboarding
+# 合计 7 用例 | 通过 7 | 断言失败 0 | 异常 0
+python "C:/.../english-drill/tests/e2e/run_e2e.py"
+# 合计 124 用例 | 通过 124 | 断言失败 0 | 异常 0(无回归)
+```
+
+## 状态
+done。遗留:无(复杂规划算法任务卡明确不做)。
diff --git a/PLANNING/PROGRESS_E-05.md b/PLANNING/PROGRESS_E-05.md
new file mode 100644
index 0000000..96ef742
--- /dev/null
+++ b/PLANNING/PROGRESS_E-05.md
@@ -0,0 +1,40 @@
+# PROGRESS_E-05 · 公开内容页 SEO(词/题/词书)
+
+> 日期 2026-09-13 · 范围:只改 `english-drill` 目录(后端 core/apps.content/config + 前端 pages/App + tests/e2e)
+
+## 选型
+**Django 模板直出**(与 `views_web.spa` 同栈),理由:后端已有 async 视图 + 完整 ORM,
+三条路由直出 HTML 零构建依赖;不做全站 SSR 迁移,不动登录内页面。
+
+## 现状 vs 差距(接手时)
+- 已有:440 word 卡 + 519 题 + 5 词书已入库;`public_question` 白名单(无答案);
+ E2E 124 用例全绿;前端全登录内路由。
+- 缺口:整站登录后可见(首屏登录墙 E-02 已拆,但内容页仍需登录),440 词零长尾入口。
+
+## 改动(全部在 `C:/Users/12914/Desktop/english-drill/` 内)
+1. `backend/core/views_seo.py`(新建):`SeoWordView`(/word/\,大小写归一,重词 order_by id 确定性),
+ `SeoQuestionView`(/question/\,public_question 白名单,**不含答案/解析**),
+ `SeoBookView`(/book/\,词数/章节概览),`seo_sitemap`(421 词 + 519 题 + 5 词书 = 945 URL,
+ loc 站内相对路径,部署层拼域名),`seo_robots`。每页独立 title/description/OG/canonical + 注册引导(from=word-/question-/book-)。
+2. `backend/config/urls.py`:五条路由挂在 **SPA 兜底之前**。
+3. `backend/apps/content/views.py` + `config/urls.py`:`WordLookupView`(`GET /api/content/word/`,
+ 公开白名单:释义/音标/例句/同课相关词,无用户数据)。
+4. 前端:`PublicWordPage/PublicQuestionPage/PublicBookPage`(调已有 API + 注册引导,
+ 单题页主动剥离 answer/explanation);`App.tsx` 加 `/word/:front /question/:qid /book/:bid` 公开路由。
+5. `tests/e2e/cases_seo.py`(新建,7 条,group=seo)+ `run_e2e.py` 注册:
+ 单词正文/大小写/404/单题无泄露(5 项白名单审查)/词书概览/查词白名单/sitemap 覆盖数。
+
+## 验证
+```bash
+python "C:/.../english-drill/tests/e2e/run_e2e.py" -k seo
+# 合计 7 用例 | 通过 7 | 断言失败 0 | 异常 0(sitemap word=421 q=519 book=5)
+python "C:/.../english-drill/tests/e2e/run_e2e.py"
+# 合计 133 用例 | 通过 133 | 断言失败 0 | 异常 0(124 存量 + 7 seo + 2 波动恢复,无回归)
+tsc --noEmit -p frontend/tsconfig.json # 零报错(修 SkeletonBlock h prop + BankDetail unknown 中转)
+curl 等价:/word/abandon → abandon 是什么意思… + 释义正文(E2E 同等价覆盖)
+```
+踩坑:harness `hit()` 默认按 JSON 解析——直出 HTML/XML 用例改原生 resp 断状态码 + 正文关键字。
+
+## 状态
+done。遗留:sitemap loc 为站内相对路径(部署 nginx 按真实域名拼 canonical,见 deploy/nginx.conf 接线时处理);
+词面 440 卡去重 421(19 个跨词书重词,重词页 order_by id 取首个,诚实记录)。
diff --git a/PLANNING/PROGRESS_I-01.md b/PLANNING/PROGRESS_I-01.md
new file mode 100644
index 0000000..6974baf
--- /dev/null
+++ b/PLANNING/PROGRESS_I-01.md
@@ -0,0 +1,41 @@
+# PROGRESS_I-01 · 模型能力自动探测(每晚,只报告不写配置)
+
+> 日期 2026-09-13/14 · 范围:只改 `dsh` 目录(新建 `tools/probe_models.py` + 报告目录)
+
+## 现状 vs 差距(接手时)
+- 已有:2026-09-06 手动探测结论(DSH模型能力测试报告.md:b/glm-5.3-flash 与 b/qwen3.8-flash
+ 思考✅视觉✅)+ `~/.dsh/model-probe-last.json` 单次快照 + settings.yaml 能力声明注释。
+- 缺口:无自动巡检;无 diff 告警;手动一次即忘,漂移无人知。
+
+## 改动(全部在 `C:/Users/12914/Desktop/dsh/` 内)
+1. `tools/probe_models.py`(新建,单文件,标准库 + urllib,无 httpx 依赖):
+ - 只读 `~/.dsh/settings.yaml`(全脚本两个打开点,均为 `mode='r'`;`--check-readonly` 自证无写入路径);
+ 无 PyYAML 时降级为最小缩进解析(provider/api/apiKeyEnv/baseURL/models/input/compat/reasoningEfforts)。
+ - 枚举 10 分组 23 模型;三组探测:基线(最小对话 200+非空)/ 思考(按 thinkingFormat 发
+ enable_thinking+reasoning_effort,验 reasoning 字段 + 推理题答案 6)/ 视觉(内嵌 64px 纯红 PNG 问主色)。
+ - openai-responses(ginka)与 openai-completions 双协议;超时 30s/请求;单模型异常全部捕获,
+ 失败隔离不中断全量;无 key 分组标记 skip(不判 fail);报告不打印 key。
+2. 产出:`tools/probe-reports/probe-YYYY-MM-DD.json`(机读:verdict/diff)+
+ 同名 `.md`(人读:表格 + 与上次 diff + 失败明细 + 配置建议)。
+ 告警方向:报告首行——能力变化列 diff,无变化/失败数异常即标红。
+3. 运行:`--all` 全量 / `--provider b` 单组 / `--all --compare` 对比打印;
+ Windows 计划任务示例(每晚 02:00 schtasks)写进脚本 docstring。
+
+## 验证
+```bash
+python tools/probe_models.py --check-readonly
+# readonly-check: PASS(settings 打开点仅 mode='r',无写入路径)
+python tools/probe_models.py --all
+# 报告:tools/probe-reports/probe-2026-09-14.json + .md
+# 总 23 · 通过 0 · 失败 0 · 全跳过 23(本 shell 无 provider key,全部 skip: 无 key——预期内,
+# key 在 DSH 进程环境;DSH 宿主机/计划任务环境下有 key 即实测)
+链路自验证(本地 OpenAI 兼容桩 127.0.0.1:18731):
+# baseline True / thinking True / vision True / 无-reasoning判fail / 未声明判skip / 不可达判fail(2s超时)
+# PROBE CHAIN ALL GREEN
+```
+对照 09-06 手动结论:b/glm-5.3-flash 与 b/qwen3.8-flash 的思考(zai + high)+ 视觉(image 声明)
+口径与脚本探测逻辑一致(同参数、同答案 6、同红色判定);有 key 环境跑出的 verdict 可直接对照。
+
+## 状态
+done(最小闭环)。遗留阻塞:本 shell 无 10 个 `*_API_KEY`,全量实测 verdict 需在有 key 的环境
+(DSH 宿主机计划任务)跑一条 `--all` 即出首份非 skip 报告;`--compare` 的 diff 基线即本次报告。
diff --git a/PLANNING/PROGRESS_S-01.md b/PLANNING/PROGRESS_S-01.md
new file mode 100644
index 0000000..f677dd7
--- /dev/null
+++ b/PLANNING/PROGRESS_S-01.md
@@ -0,0 +1,44 @@
+# PROGRESS_S-01 · DSP 测试基建 + 缺陷清扫
+
+> 日期 2026-09-13 · 范围:只改 `dsp` 目录(`dsp/backend` + 报告)
+
+## 基线(接手时实测)
+- `DSP_TEST=1 python -m pytest tests/` → **49 passed**(12 app 存量,非零测试)。
+- 不带 `DSP_TEST=1` 跑 → 37 errors(连 PG 超时):conftest 置变量晚于 pytest-django 初始化,
+ 必须命令行前置 `DSP_TEST=1`(已写入统一入口,不改 conftest 行为)。
+- 真缺口:无统一入口、无 report.json、无 `--http` 交叉验证;5 处外部 id 裸 `int()`;
+ visitors/logs/搜索边界覆盖薄。
+
+## 改动(全部在 `C:/Users/12914/Desktop/dsp/` 内)
+1. `dsp/backend/tests/e2e/run_e2e.py`(新建):统一入口——pytest 包装(ASGI 默认)+
+ `--http/--base-url` 真实服务交叉(health/search-hot/feed 三探针,失联优雅报告 connect FAIL)+
+ `report.json`(total/passed/failed/errored/skipped/duration/groups/http)。
+2. `dsp/backend/tests/e2e/test_gap_coverage.py`(新建,12 条):访客记录/列表、自访 400、
+ 访客匿名 401、搜索空/脏查询不 500、suggest+hot、logs 上报闭环、通知列表+已读、
+ 审核举报流、评论 missing-video 4xx、裸 int 静态自查、软删过滤静态自查。
+3. 缺陷清扫(静态自查真抓到 5 处,已修):
+ - `core/api.py`:新增 `opt_id()`(非正整数回落,不抛 500)。
+ - `apps/visitors|moderation|creator|messages_app`:裸 `int(...get(...))` → `opt_id`;
+ `apps/music`:手写 clamp → `parse_page_size`。
+4. `dsp/backend/tests/e2e/__init__.py`(新建空包)。
+
+## 验收对照
+- [x] 统一入口 `61 passed`(49 存量 + 12 新增)≥60
+- [x] 存量零删除零跳过(skipped=0)
+- [x] `report.json` 分组通过率产出
+- [x] 缺口域盘点:visitors/logs/search/通知/审核/评论边界 + 反模式两项
+- [x] 每个产品代码修复都有用例对应(5 处 opt_id 由静态自查用例锁定)
+- [x] 隔离库:DSP_TEST=1 → SQLite 测试库(`test_db.sqlite3`),原始开发库零改动
+- [x] 5xx:`?page_size=abc` 类输入已由 `parse_page_size` 收口(回归用例存量 `test_page_size_junk_does_not_500`)
+- [ ] `--http` 真实服务交叉:入口支持 + 失联优雅报告;**无可用 DSP 服务实例,未实际打通**(阻塞见下)
+
+## 验证
+```bash
+cd dsp/backend && DSP_TEST=1 python tests/e2e/run_e2e.py
+# 61 passed … 合计 61 用例 | 通过 61 | 失败 0 | 异常 0 | 跳过 0
+cat tests/e2e/report.json
+```
+
+## 状态
+done(ASGI 模式全绿)。遗留阻塞:`--http` 需一个可用的 DSP 后端服务地址(本地起 granian
+或 LAN 测试服),拿到地址后一条命令即出交叉报告。