Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
6.5 KiB
6.5 KiB
TASK · 定向补事卡刷新后点不动(P0,2026-09-13)
- 基线:
origin/staging@ab1ade59(已部署) - 分支:
codex/rectification-targeted-card-dead-20260913,基于origin/staging - 执行方:coding agent;验收:Claude
- BUG 编号起点:BUG-669(当前最大号 665;666~668 已被
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-661~663,530f260f)引入的新形态
1. 事故实证(2026-09-13 真机,ab1ade59)
用户六道带年月题答完后:
- 先冒出一道「和页面上其它题 UI 都不一样」的问题,回答后问题消失;
- 接着出现
记下了,这方面先跳过。+现在还剩 04:51–05:06 里 5 个候选,能把它们分开的是这几条线:…+ 定向卡「收入明显变过或有过大笔进出吗?A/B/C/D」; - 这张卡点不动,时间轴显示「目前范围 04:48–05:07,再说一件带年月的事就能继续」,流程停住。搬家、添丁两条线再也没被问。
- 2026-09-13 产品负责人复测:刷新页面后选项依旧是灰的——不是渲染时序,是快照投影本身拿不到卡。
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_collectschema 时重建choice_frame(buildTargetedCollectExistenceFrame+ 持久化文案),user_prompt_hint改成「照发服务端卡片,不要自己写题干」。 验收:活动焦点为定向存在题时plan.next_followup.choice_frame非空且question_id等于焦点题号;不再出现模型自写的口述版定向题。 - T3(BUG-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-669~671 入历史(关联 BUG-661、BUG-654、BUG-591、reverse_verify 投影那条);docs/testing/rectification-scenarios-20260907.md场景 0d 加:答完一张定向卡后刷新页面,卡片仍可点、整轮不得出现看得见点不动的卡。
5. 让步顺序
T1 不可省(没有它流程仍然死)。T2 次之(不做会继续出现模型自写的口述定向题)。T3 可拆下一单。
6. 开工前置命令
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 --noEmit0 错;npm run lint0 error;相关套件 fail=0;测试总数 ≥ab1ade59实测 3139(其中 27 项为无 Docker 的数据库/部署套件)。next build后/仍○ Static;首屏rootMainFilesgzip-9 与ab1ade59的 130872 B 相差 ±2% 内。- 部署后真机按场景 0d 走一遍,含刷新页面后继续答。