docs(tasks): P0 brief for dead targeted collect cards (BUG-669~671)

Reproduced: with a collect_method_evidence focus open, the plan rebuilds an
active_focus followup without a choice_frame, so GET projects no choice_card and
the card renders disabled while the page waits for a dated event.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
Jesse_Chen
2026-09-13 12:19:10 +00:00
co-authored by Claude Opus 5
parent 0b7d43e622
commit fe5a54e015
2 changed files with 67 additions and 0 deletions
+2
View File
@@ -182,6 +182,8 @@
| `TASK-rectification-tie-break-entry-fix-20260913.md` | — | 修复单:参考题入口只读 `window_scan` 标志位 → 两道答完后仍显示、再点弹「现在没有可答的参考题」;并按产品拍板改成点一次连出 D9+D10(BUG-666668 | 待执行 | `codex/rectification-tie-break-entry-fix-20260913` |
| `TASK-rectification-targeted-card-dead-20260913.md` | — | **P0**:定向补事卡在快照投影里拿不到 `choice_card`(承接焦点分支不重建 `choice_frame`),卡片看得见点不动、流程停在采集等待态;模型还会把定向题改写成口述题(BUG-669~671)。先于 tie-break 修复单执行 | 待执行 | `codex/rectification-targeted-card-dead-20260913` |
## 命名与归档
- 文件名:`TASK-<kebab-主题>-<YYYYMMDD>.md`;同主题的修复单加 `-fix`;进度记录同名换前缀。
@@ -0,0 +1,65 @@
# TASK · 定向补事卡刷新后点不动(P02026-09-13
- 基线:`origin/staging` @ `ab1ade59`(已部署)
- 分支:`codex/rectification-targeted-card-dead-20260913`,基于 `origin/staging`
- 执行方:coding agent;验收:Claude
- BUG 编号起点:**BUG-669**(当前最大号 665666668 已被 `TASK-rectification-tie-break-entry-fix-20260913.md` 占用)
- 串行:**本单先做**`TASK-rectification-tie-break-entry-fix-20260913.md` 排在其后(两单都改 `projectRectificationChoiceCard` 相邻逻辑)
- 上游:`TASK-rectification-targeted-collect-cards-20260913.md`BUG-661663`530f260f`)引入的新形态
## 1. 事故实证(2026-09-13 真机,`ab1ade59`
用户六道带年月题答完后:
1. 先冒出一道「和页面上其它题 UI 都不一样」的问题,回答后问题消失;
2. 接着出现 `记下了,这方面先跳过。` + `现在还剩 04:51–05:06 里 5 个候选,能把它们分开的是这几条线:…` + 定向卡「收入明显变过或有过大笔进出吗?A/B/C/D」;
3. 这张卡**点不动**,时间轴显示「目前范围 04:48–05:07,再说一件带年月的事就能继续」,流程停住。搬家、添丁两条线再也没被问。
## 2. 根因(已用脚本复现,非推测)
`buildMethodFollowupPlan` 里的「承接当前焦点」分支(`method-followup.ts``method_id: "active_focus"` 那段)对 `intent = collect_method_evidence` 的活动焦点重建的 followup **没有 `choice_frame`**——该分支只为 `reverse_verify` / `out_of_sample_check` / `distinguish_candidates` 重建点选。定向补事的存在题是**史上第一个「采集焦点 + 点选卡」**的组合,正好落进这个洞。
于是:
- `projectRectificationChoiceCard` 走到 `if (!frame) return persistedVerifyCard()`,而 `persistedVerifyCard()` 只认 `reverse_verify` / `out_of_sample_check`,返回 `null` → GET 的 `choice_card` 为空。
- `projectCurrentQuestion` 仍按已持久化 schema 给出 `kind: "choice"``current_question` → 前端 `liveQuestion` 要求 `choiceCard.focus_id === question.focus_id`,拿不到卡 → `RectificationChoiceCard``disabled` 渲染。**卡看得见、点不动**,与用户描述一致。
- 第一次能答,是因为 POST 应答里带着刚落库的焦点直接渲染;一旦页面重新拉快照(轮询/刷新/再进一次会话),卡就死了。
- 同一分支还要求模型「用 spokenPrompt 自己写题干」,这就是第 1 步里「UI 不一样的那道题」——模型把定向题重写成了口述题。
复现(`tsx` 直接调用,`ab1ade59`):活动焦点 = `collect:targeted:relocation`schema 为服务端四点选、`targeted_collect: true`)时,`plan.next_followup` 返回 `{"method_id":"active_focus", … "choice_frame":null}``projectRectificationChoiceCard` 返回 `null`;换成 `:next` 后缀或换成别的线,同样 `null`
## 3. 硬红线
- 卡片文案仍是服务端自有(`serverOwnedChoiceCopy`),模型不得改写定向题题干与选项。
- 不得放宽区分题(`distinguish_candidates`)的身份校验:`semantic_key` / `candidate_split_hash` 全等判定保持不变(BUG-559 / BUG-581 防复发)。
- 不改 `SCORE_DELTA`、确认门;定向存在题仍 `scoring: false`
- 采集焦点撞号仍可落 `:next`BUG-654),因此投影不得再用「`questionId` 与计划层 frame 全等」做定向卡的唯一身份判据。
## 4. 任务分解
- **T1(BUG-669)投影按已持久化文案出卡**:`projectRectificationChoiceCard` 增加一条与 `persistedVerifyCard()` 平行的路径——活动焦点 schema 满足「`targeted_collect === true` 且能 `parseAgentChoiceCopy` 出四点选」时,直接按持久化文案投影 `choice_card``question_id` 用焦点的、`focus_id` 用焦点的、`scoring: false``stop_label` 用「这题跳过」),不依赖计划层重建 frame,也不做 `questionId` 全等比较(`:next` 后缀必须照样出卡)。
验收:GET 快照在「定向存在题为活动焦点」时 `choice_card.focus_id === current_question.focus_id`、四个选项、`scoring:false``collect:targeted:finance:next` 同样出卡。
- **T2(BUG-670)承接分支别让模型改写定向题**:`buildMethodFollowupPlan` 的承接焦点分支,识别 `targeted_collect` schema 时重建 `choice_frame``buildTargetedCollectExistenceFrame` + 持久化文案),`user_prompt_hint` 改成「照发服务端卡片,不要自己写题干」。
验收:活动焦点为定向存在题时 `plan.next_followup.choice_frame` 非空且 `question_id` 等于焦点题号;不再出现模型自写的口述版定向题。
- **T3BUG-671)死角要能自救**`current_question``choice``choice_card` 拿不到时,前端不得静默停在采集等待态——进入 `unavailable` 分支给出修复按钮(现有 `repair-exit` 路径),或由服务端重发焦点。
验收:人为让 `choice_card` 为空时,界面出现可点的修复入口,不是一条「再说一件带年月的事就能继续」。
- **T4 回归与记录**:新增 `frontend/tests/rectification-targeted-card-live-20260913.test.ts` 锁 T1/T2(含 `:next`);BUG-669671 入历史(关联 BUG-661、BUG-654、BUG-591、reverse_verify 投影那条);`docs/testing/rectification-scenarios-20260907.md` 场景 0d 加:**答完一张定向卡后刷新页面,卡片仍可点**、**整轮不得出现看得见点不动的卡**。
## 5. 让步顺序
T1 不可省(没有它流程仍然死)。T2 次之(不做会继续出现模型自写的口述定向题)。T3 可拆下一单。
## 6. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-targeted-card-dead-20260913 .worktrees/rectification-targeted-card-dead-20260913 origin/staging
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 665
cd frontend && npm test -- tests/rectification-targeted-collect-cards-20260913.test.ts tests/rectification-question-ownership.test.ts tests/rectification-v9-contracts.test.ts
```
## 7. 验收口径
- `tsc --noEmit` 0 错;`npm run lint` 0 error;相关套件 fail=0;测试总数 ≥ `ab1ade59` 实测 3139(其中 27 项为无 Docker 的数据库/部署套件)。
- `next build``/``○ Static`;首屏 `rootMainFiles` gzip-9 与 `ab1ade59` 的 130872 B 相差 ±2% 内。
- 部署后真机按场景 0d 走一遍,含刷新页面后继续答。