Files
Jyotisha/docs/tasks/TASK-rectification-dup-question-20260926.md
T

54 lines
6.8 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 · 生时校正:同一轮问题出现两次 + 同一道选择题连画两张(2026-09-26)
## 基线
- `origin/staging` 当前 head(`a57c4310` 代码;BUG-1042 修复单合入后以新 head 为准)。分支 `codex/rectification-dup-question-20260926`。
- 与 `TASK-rectification-latency-20260926`(改 route / agent-run / 校正面活动状态)**冲突**:本单先做,那单后做。与 `TASK-scroll-anchor-hook-fixes-20260926` 不交叉。
## 事故实证(产品 09-26 真机,staging)
**A · 同一轮里问题出现两次**:用户打字回答后,助手这一轮先是正文「已记录,范围没变;14:58–14:59 领先,14:40–14:43 落后。」「2024 年 3 月前后,有没有收入明显变化、大笔支出或欠债?」,下面又是加粗的同一句问题块和选项。刷新后只剩一遍。
- `frontend/src/lib/rectification-agentic/v9/answer-choice.ts` `persistApplied`:下一问是选择题时 `hostNarration = composeCollectSpokenAssistantText(body, choiceStem)`,把题干拼进 `assistant_message` 并作为 `applied.narration` 返回;`persistCollectDenialTurn` 同样。
- `frontend/src/app/api/rectification/agent/route.ts`:打字回答命中待答选择题(`action === "message"` + `choice` 分支)→ `applyRectificationChoice` → `completedMessageResponse(applied.narration)` 整段作为一个 `answer.delta` 下发;不经模型 runner,`agent-run.ts` 里 BUG-585 的 `stripQuestionSentences(...)` 不执行。
- `rectification-agentic-chat.tsx` `send` 结算后保留原始流式正文;`loadCaseSnapshot` → `frontend/src/lib/rectification-snapshot-messages.ts` `mergeTurnQuestions` 只拷 `question` / `candidateOffer`,**不替换 `text`**。`rectification-message-entry.tsx` 先渲染 `message.text` 再渲染 `question.prompt` → 两遍。去重只在 GET 组装的 `attachQuestionsToTurns`(`turn-question.ts`)里,所以刷新后消失。
- 排查已在 tsx 里复现:合并快照后正文仍含题干;`stripQuestionSentences` 对同一文本能正确去掉。
- **复发**:BUG-585(修复 `9d1c08ca`,防复发"不得恢复把题干拼进 assistant_message")被 **BUG-969 第③项**(`824ecff0`,2026-09-18)带回:为防卡片加载失败时看不到题而拼回题干,声称"挂卡后去重",但测试(`account-dialog-inert-20260918.test.ts`)只覆盖刷新路径,并把 `rectification-answer-choice.test.ts` 断言改成期望题干在正文里。
**B · 同一道选择题连画两张**:用户在第 N 轮点选 **B 发生过但程度较弱**(高亮),其下是操作按钮、「目前范围 14:35-15:05,还在核对」,再下面又出现完全相同的题干和四个选项(未选中)。
- 第二张不是新一轮助手回答,而是独立的"持久化问题块":`rectification-agentic-chat.tsx` 中 `questionGap === "persisted_question"` 时渲染 `currentQuestion.prompt` + 新的 `RectificationChoiceCard`(`selectedKey=""`)。条件是 `rectificationQuestionGapState` 判定"没有任何消息挂着与 `currentQuestion.focus_id` 匹配的活题,但快照里仍有持久化问题"。同类:BUG-675 / 678(「目前范围」下挂裸题);BUG-678 意图"只在没有助手消息时兜底",但闸门没强制。
- 两个假设(需真实会话区分):
- **H1(更可能)**:`submitStructuredChoice` 先 `markQuestionAnswered`(本地标 B)再 POST `/api/rectification/agent`(`action: "answer_choice"`);`!response.ok` / `catch` 只移除占位、`choiceNonce++`、`setError`,**不撤回已答标记、不重载快照** → 活题判定落空 → 兜底块再画一张。服务端可能的错误:`stale_question` / `revision_conflict` / `stale_probe` / 500。
- **H2**:POST 成功但 `nextInterviewPersisted` 为假且 `nextAction` 为 `ask_candidate_discriminator` 的 `willContinue` 分支只跑 `mergeTurnQuestions`、没跑 `appendUnseenAssistantTurns`(BUG-685 在 `requestTieBreak` 修过同型缺口),承载下一问的那轮没进客户端,兜底块替它画了出来;若题目完全相同,还说明选题重复了(BUG-540 / 559 / 592 守卫仍在代码里,需确认)。
## 根因
A:服务端为兜底把题干拼进正文,客户端在实时路径上没有"挂卡即去重"。B:选择题提交失败不回滚本地状态 + 兜底问题块的出现条件过宽(H2 另有漏收回合)。
## 决策记录
- D1 保留 BUG-969 的兜底意图(卡片没挂上时用户仍能在正文看到题),但**卡片一挂上就去掉正文里的同一句**:`mergeTurnQuestions` 在快照回合带问题时,用服务端已去重的 `turn.text` 覆盖,或对 `message.text` 执行 `stripQuestionSentences(text, question.prompt)`;实时流里若同一轮已收到问题卡,也立即去重。
- D2 选择题提交失败:撤回本地已答标记(题目回到 active、无 `answer_option`)、重载快照、只保留**一张**可点的卡 + 错误提示「这次没提交上,请再点一次。」(对照 VOICE)。
- D3 兜底问题块只在**没有任何助手消息**时渲染;已有助手消息时把问题挂到最新助手消息上(BUG-678 原意),且同 `focus_id` 的题永远只画一张。
- D4 H2 分支:`willContinue` 与 `send()` 结算后的快照合并同时执行 `appendUnseenAssistantTurns`(同 BUG-685)。若复现确认服务端真出了重复题,另开 BUG 关联 BUG-540 / 592,不在本单改选题。
## 硬红线
1. 回归测试必须走**实时路径**:流式收到"确认句 + 题干"→ 合并快照 → 断言题干在正文与问题块合计只出现一次;打字回答命中选择题、采集拒答两条路径各一条。
2. 提交失败的回滚测试:模拟 409 `stale_question` 与网络错误,断言页面只有一张可点卡 + 错误提示,已答标记被撤回。
3. 不改服务端选题与打分;不改 BUG-969 的服务端兜底文本(只改客户端去重)——若必须改服务端,写明理由。
4. 全量测试与基线逐条一致、新增 0;改动断言写三栏(含把 BUG-969 改成"期望题干在正文"的那条,改回并说明);真实浏览器截图(CDP 假数据)验证 A、B 两个场景。
5. 不改 `useConversationScrollAnchor`(另一单在改)。
## 任务分解
- T1 实时去重(D1)+ 测试。
- T2 提交失败回滚(D2)+ 兜底块闸门(D3)+ 测试。
- T3 H2 漏收回合(D4)+ 测试。
- T4 记录:BUG-1045(A,**复发自 BUG-585**,写明 BUG-969 如何绕过防复发、为何测试没拦住);BUG-1046(B,关联 BUG-675 / 678 / 685);PROGRESS;`docs/testing/` 真机清单(打字回答后问题只出现一遍;选择题提交失败时只有一张卡 + 提示;刷新前后一致)。
## BUG 编号
写单时最大 BUG-1044(1042–1044 由按钮间距单预留);本单 **BUG-1045 / 1046**,开工时核对。