Files
Jyotisha/docs/tasks/TASK-session-title-guard-fix-20260906.md
T

64 lines
4.9 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.
# TASK · 会话标题守卫写库恒不命中(BUG-553 修复单)(2026-09-06
- 基线:`origin/staging` @ `e2f4b55c`(含 BUG-553 实现 `a1956deb`
- 分支:`codex/session-title-guard-fix-20260906`
- 执行方:coding agent;验收:Claude
- 涉及文件:`frontend/src/app/api/consult/route.ts`(只改标题守卫段)、`frontend/tests/application-billing-contract.test.ts` 或新 `frontend/tests/session-title-persist-contract.test.ts`;不动迁移、不动客户端、不动 `session-title-agent.ts`
- BUG 编号起点:**BUG-557**(开工时 `grep -o "^## BUG-5[0-9][0-9]" docs/BUG_HISTORY.md | tail -1` 复核)
- 串行:与 `TASK-consultation-context-and-cache``route.ts` 改动已合入,无并行冲突
## 1. 事故实证(代码验收,未上线)
`frontend/src/app/api/consult/route.ts`
| 行 | 事实 |
| --- | --- |
| ~L282 | `chatSession` select 读到会话标题(新会话为空或「新对话」) |
| L531 | `accounting.rpc("append_consultation_question")` —— 迁移 `20260901010000_append_consultation_question.sql` L84–90 在标题为空 / 「新对话」时把标题改写成 `left(question,14) || '…'`(≤14 字时为整句) |
| L693 | `const expectedTitle = chatSession.title`RPC **之前**的值) |
| L710 | `update({ title }).eq("id").eq("user_id").eq("title", expectedTitle)` |
对任何新会话,L710 的 `title = expectedTitle` 都比不上 L531 已改写的值,更新影响 0 行、Supabase 不报错、`console.warn` 不触发。模型标题只在客户端结算时通过 `persistSession` 的 PATCH 落库;用户中途刷新、关标签、换设备时 `api/consult/status` 回读的是问题截断标题,起名白做。`tests/session-title-agent.test.ts``application-billing-contract.test.ts` 都没有断言这条守卫会命中。
## 2. 根因
任务书 §3 决策 3「只在标题仍等于本轮开始时读到的值时更新」写的是「本轮开始」,而本轮真正改标题的是 RPC;执行方按字面取了 RPC 前的快照。守卫本身设计没错,取值时机错。
## 3. 决策记录
1. 起名触发条件(`shouldGenerateSessionTitle(expectedTitle-before-RPC, storedHistory)`)**不变**:仍用 RPC 之前的标题判断「是否自动派生」,否则 ≤14 字的问题会被当成用户手改标题而不起名。
2. 守卫的比较值改为 **RPC 之后的真实标题**RPC 成功后再 `select("title")` 一次(一次主键查询,可接受),把该值作为 `expectedTitle` 传给守卫。不用 TS 重算 SQL 规则(`safeQuestionTitle` 与 SQL 的标点处理不同,重算会再次错配)。
3. 用户中途改名以用户为准这条语义保持:守卫仍是 `.eq("title", <RPC 后标题>)`
4. 守卫更新要 `.select("id")` 回读影响行数,0 行时 `console.warn("session title guard missed")`,不再静默。
## 4. 硬红线
- 不改 RPC、不改迁移、不改客户端 `use-consultation-run.ts`
- 不得把守卫放宽成无条件写入。
- 不得为测试重写路由结构;用现有 `application-billing-contract.test.ts` 的源码合同方式或 mock Supabase 链的方式二选一。
## 5. 任务分解
### 5.1 守卫取 RPC 后标题
- `route.ts``appendedQuestion` 成功后 `const { data: titleRow } = await supabase.from("chat_sessions").select("title").eq("id", sessionId).eq("user_id", userId).maybeSingle()``shouldGenerateSessionTitle` 仍用 RPC 前标题;守卫 `.eq("title", titleRow?.title ?? expectedTitle)`;更新后 `.select("id")`,长度 0 → `console.warn`
- 验收:测试——模拟 RPC 前标题「新对话」、RPC 后标题「问题前 14 字…」、模型标题「X」:断言 update 链的 `eq("title", …)` 收到的是 RPC 后标题;模拟用户中途把标题改成「我的标题」(回读值与守卫值不等)→ 更新 0 行且 `console.warn` 被调用一次、返回的流事件仍是 `session.title`
### 5.2 记录
- `docs/BUG_HISTORY.md` BUG-557(关联 BUG-553;说明为何 a1956deb 的测试没拦住:无守卫命中断言);`CHANGELOG.md` 一条;`docs/tasks/PROGRESS-session-title-guard-fix-20260906.md``docs/testing/session-list-title-and-order-20260906.md` 补一条「首轮生成中刷新页面,回来后标题仍是模型标题」。
## 6. 让步顺序
5.1 不可拆;5.2 不可省。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/session-title-guard-fix-20260906 .worktrees/session-title-guard-fix-20260906 origin/staging
cd .worktrees/session-title-guard-fix-20260906/frontend
ln -s /workspace/Jyotisha/frontend/node_modules node_modules # 注意:不要在已有 node_modules 的目录里再建,会产生 node_modules/node_modules 双份 React
npx tsx --test tests/session-title-agent.test.ts tests/application-billing-contract.test.ts 2>&1 | grep -E "^# (tests|pass|fail)|^not ok"
```
收尾同一条命令 fail=0,再 `tsc --noEmit``npm run lint`