d4627a5019
Adopted reverse-verify repeated already-asked collect stems, treated 「这题跳过」 as stopping the case, and promised holdout/OOS checks that never ran. Collect spoken stems no longer prefix a year. Co-authored-by: Cursor <cursoragent@cursor.com>
111 lines
15 KiB
Markdown
111 lines
15 KiB
Markdown
# TASK · 采用后核对线:不重复已问题、「这题跳过」只跳本题、核对结束有收尾、采集题去年份前缀(2026-09-04)
|
||
|
||
## 0. 基线
|
||
|
||
- 基线:`origin/staging` @ `e27d5dc5`(含 `0be51e65` 补采集顺序修复、`a793edb1` BUG-533)。
|
||
- 开工分支:`codex/rectification-post-adopt-verify-20260904`,worktree `.worktrees/rectification-post-adopt-verify-20260904`。
|
||
- 串行关系:本任务书改 `method-followup.ts`、`answer-choice.ts`、`rectification-agentic-chat.tsx`,与当天其他校正任务书无并行;`TASK-rectification-collect-direction-fix-20260904.md` 已合入基线,不再冲突。
|
||
- BUG 编号起点:任务书写 **BUG-535**;交付前 `origin/staging` 已占用该号(报告 chart-cap `61194690`),本单实落 **BUG-536~539**。
|
||
|
||
## 1. 事故实证(staging,真实案例,资料已抽象)
|
||
|
||
同一案例在采用代表分钟之后,四个现象连着出现。以下只写形状,不写年份、事件与候选分钟。
|
||
|
||
| # | 用户看到的 | 期望 | 定位(符号) |
|
||
| --- | --- | --- | --- |
|
||
| A | 采用后第一道"核对题",是采集阶段**已经问过并回答过**的带年份职业题,题干几乎逐字重复 | 采用后核对只问**还没问过**的可计分前事 | `v9/method-followup.ts` `remainingReverseVerifyProbes`(L548–571)过滤了拒答领域、`probeYearAlreadyCovered`、成年下限,**没有**用 `askedKeys`(L554 起同函数的 `remainingConflictProbes` 用了 L612)。`if (input.accepted)` 分支(L1722)直接取第 0 条 |
|
||
| B | 点「这题跳过」后,机器人又说"剩下的题分不开当前候选……可以从下面选一个先用着。",随后界面出现"没有拿到下一个问题。"和重新加载按钮 | 跳过这一题 → 出下一道核对题;没有了 → 一句收尾,界面安静 | `v9/choice-card.ts` L402/L549 与 `components/rectification-agentic-chat.tsx` L326:核对卡的 stop 按钮只是**改了标签**为 `CHOICE_SKIP_QUESTION_LABEL`,动作仍是 `STOP_ACTION`;`v9/answer-choice.ts` L265–289 `optionId==="stop"` → `focusStatus:"skipped"` + `userStopped:true`;`core/rectification-decision.ts` L276 `userStopped` → `completeWithRange` → `publicCanAdopt` 仍 true → `answer-choice.ts` L1035–1061 走 `adoptionNarration`(带 `adoptCue`),不看 `case.acceptedTime`;无下一焦点 → `lib/rectification-surface-state.ts` L250 `rectificationQuestionGapState` 走 preparing → unavailable |
|
||
| C | 采用卡旁白承诺"采用后会拿 X 年的教育题以及家庭、财务、健康压力三类域外题来核对",采用后一道都没问 | 旁白只承诺服务端真的会问的题 | `v9/adopt-narration.ts` L161–173 `post_adopt_verification` = holdout 事件 + `oos_blind_prompts`;而采用后的计划(A 行)用的是 `remainingReverseVerifyProbes`,两者是两套数据源。这一段是 `TASK-rectification-adopt-narration-20260904.md` §4 我方规格错误,见 §3 决策 3 |
|
||
| D | 家庭采集题写成"YYYY 年前后,家里如果有结婚、添丁或住院这类事,记得大概哪年就行。"——既指定了年份又让用户报年份;答"没有"后直接收口 | 采集题不带年份前缀;"没有"后收口本身是**设计如此**(见 §2 D) | `method-followup.ts` L1941–1954 家庭轮转分支塞入 `collectionYearFields(familyCollect)`;`spokenFollowupForUser` L999–1001 把 `year_label`/`probe_year` 拼成"YYYY 年前后,"前缀到 `USER_COLLECT_QUESTION.family` |
|
||
|
||
## 2. 根因
|
||
|
||
- **A**:采用前的探针去重(`askedProbeKeysFromReceipt` → `askedKeys`)只接到了冲突探针与轮转,反向核对分支是 BUG-350 单独写的,漏接。引擎的 `eventProbes` 里同一 (domain, year) 的题在采集阶段已按 `semantic_key` 问过,采用后又被当"未核对"取出。
|
||
- **B**:两层叠加。(1) 核对卡的「这题跳过」是纯标签替换,语义仍是"整案停止";(2) 结构化答案的旁白选择只看 `nextAction.can_adopt`,已采用后 `publicCanAdopt` 依旧 true,于是又念一遍"选一个先用着"。服务端没有"核对结束"这个状态,前端也没收到 `next_user_action`(仅 Agent 工具投影 `mastra/rectification-v9-tools.ts` L265/L316),只能把"无焦点 + 可续"当成"问题还没准备好"。
|
||
- **C**:采用旁白的事实包按我方任务书 §4 用 holdout + OOS 生成,但 BUG-350 已定"采用后不得把不计分盘外核对当默认下一步",采用后真正会问的是 `remainingReverseVerifyProbes` 的结果。两个规格互相矛盾,旁白说的是不会发生的事。
|
||
- **D(前半,年份前缀)**:`68f0759e` 为了满足 BUG-414/418"带年份的口语问、不出无年份 D24 卡",把 dated 探针的年份塞进家庭采集题;`USER_COLLECT_QUESTION.family` 本身以"记得大概哪年就行"结尾,两者拼在一起就自相矛盾。
|
||
- **D(后半,答"没有"即收口)**:**不是缺陷**。该案例已答满 5 轮探针预算,`relatives` 是 `BLOCKING_COVERAGE_IDS` 里最后一条未覆盖线;用户答"没有"记为该领域已回应,覆盖闭合、预算耗尽、剩余探针分不开候选,`decideRectification` 走 `completeWithRange` → 采用卡。这道题本来就是"开放报事"而不是选择题:用户若报出一件带年份的事,会作为新证据进入账本重算并可能再出区分题;答"没有"就等于这条线没有可用证据。本任务书**不改**这个收口逻辑,只把这个解释写进 `docs/testing/` 清单,让真人验收知道预期。
|
||
|
||
## 3. 决策记录(产品负责人 2026-09-04 授权)
|
||
|
||
1. **保留 BUG-350 的采用后设计**:采用后默认只问最多 `MAX_REVERSE_VERIFY` 道**计分**核对题,不把 holdout / OOS 盘外核对当默认下一步。本任务书不推翻它。
|
||
2. **反向核对必须做已问去重**:`remainingReverseVerifyProbes` 增加 `askedKeys` 过滤(`semantic_key` / `candidate_split_hash` / `probe_id` 三种键,与 `remainingConflictProbes` L612、L765–767 同口径)。已问过的题不得再作为核对题出现,无论它在采集阶段是被回答还是被跳过。
|
||
3. **推翻 `TASK-rectification-adopt-narration-20260904.md` §4 的 `post_adopt_verification` 规格**(该规格是我方错误):`post_adopt_verification` 改为由**与采用后计划完全相同**的过滤结果生成(同一函数、同一 `askedKeys`、同一 `declined`),输出 `{domain, year_label}`;当结果为空,事实包必须给出 `post_adopt_verification: []`,旁白改说"采用后没有还能核对的前事,之后新建对话即按此时间排盘,对不上可改选。"(模板兜底与 Agent 版都要遵守;校验器把"会拿……核对"类承诺在空列表时判为 `promise` 失败)。holdout / OOS 只保留在决策收据里,不再进旁白。
|
||
4. **核对卡的「这题跳过」只跳本题**:新增 `SKIP_PROBE_ACTION`(或等价的 `optionId:"skip_probe"`),语义 = 该焦点 `skipped` + 该探针进 `answered_probes`(记 declined-for-verify,不计分)+ **不置 `userStopped`**;随后按第 2 条重新取下一道核对题。采用前的区分题/采集题的停止按钮语义**不变**(仍是 `STOP_ACTION`)。
|
||
5. **核对结束是显式状态**:服务端在 GET 案例投影与结构化答题返回里都暴露 `next_user_action`(现在只有 Agent 工具投影有);已采用且没有下一道核对题时 `next_user_action.id === "start_consultation"`。前端收到该状态:不进 `rectificationQuestionGapState` 的 preparing/unavailable,不出重载按钮,只显示一行收尾文案(见第 6 条),状态栏保持 BUG-502 的"已采用 … · 改选 / 之后新建对话即按此时间排盘。",**不加**任何新按钮、不出 `rectification-consult-handoff`(BUG-499/502 红线)。
|
||
6. **已采用后的旁白不得再念采用提示**:`answer-choice.ts` 的旁白选择必须先看 `case.acceptedTime`;已采用时禁止 `adoptionNarration` / `adoptCue` / `completedRangeNarration`,改用一条收尾文案(放进 `RECTIFICATION_USER_COPY`,新键 `postAdoptVerifyDone`,措辞对照 `frontend/docs/VOICE.md`,建议:"前事核对到这里。之后新建对话即按已采用时间排盘;对不上随时改选。")。核对题被跳过但还有下一题时,旁白就是下一题,不加收尾。
|
||
7. **采集题去掉年份前缀(产品决定,可否决)**:`collectionYearFields` 不再喂给采集题的口语期间;`spokenFollowupForUser` 对 `collect_method_evidence` 不拼"YYYY 年前后,"。BUG-418 的另一半(不出无年份 D24 选择卡)**保留**:dated 探针的年份仍写进 `probe_year` 供计分与去重,只是不进用户题干。若产品负责人否决此条,执行方把本条标记 skipped 并保留现状,其余任务不受影响。
|
||
8. 答"没有"即收口的逻辑**不改**(§2 D 后半);只补真人验收清单的说明。
|
||
|
||
## 4. 硬红线
|
||
|
||
- BUG-350(采用后不默认 OOS/holdout)、BUG-463(holdout 只在 `not_started && !canAdopt`)、BUG-499/502(无 handoff 组件、无「用这个时间看盘」按钮、状态栏文案不变)、BUG-518、BUG-520(`declinedDomains` 只认 collect 意图)全部保留,任何一条被改都要先回到本任务书决策记录。
|
||
- `frontend/src/app/page.tsx` 不增长;无第二套加载动画;核对结束状态不得出现 spinner / 骨架 / "正在加载"。
|
||
- 不得把真实案例的年份、事件、候选分钟、案例 ID 写进测试、Bug 历史、进度记录、CHANGELOG;测试用抽象形状或公开名人数据。
|
||
- 改任何既有断言写"原值 / 新值 / 原因"三栏;测试总数不低于基线实测。
|
||
- 不顺手改 `.gitea/workflows/**`、不动迁移、不升级依赖。
|
||
- Agent 旁白(adopt narration)的校验器只能收紧不能放宽。
|
||
|
||
## 5. 任务分解
|
||
|
||
### 5.1 反向核对去重(决策 2)
|
||
|
||
- 改 `remainingReverseVerifyProbes` 增加 `askedKeys` 参数并在 `if (input.accepted)` 分支传入 L1554 的 `askedKeys`。
|
||
- 验收:新测试 `tests/rectification-post-adopt-verify-20260904.test.ts`:
|
||
- 给 `accepted:true`、`eventProbes` 两条(domain X/year Y1,domain Z/year Y2),`askedProbeKeys` 含第一条的 `semantic_key` → `next_followup.domain === Z`;
|
||
- 两条都在 `askedProbeKeys` → `next_followup === null` 且 `buildNextUserAction(...).id === "start_consultation"`;
|
||
- `askedProbeKeys` 只含 `candidate_split_hash` 也能去重。
|
||
- `tests/rectification-eight-method.test.ts` L1750/L1790 既有断言不变。
|
||
|
||
### 5.2 旁白事实包与采用后计划同源(决策 3)
|
||
|
||
- `adoptDeliveryFacts` 的 `post_adopt_verification` 改由 `remainingReverseVerifyProbes`(同 `askedKeys`、`declined`)生成;类型改为 `{kind:"reverse_verify"; domain; year_label}`;模板兜底与 Agent prompt 相应改口。
|
||
- `tests/rectification-adopt-narration-20260904.test.ts` L684–688 三条断言改写,三栏说明:原值"holdout/oos 出现在 post_adopt_verification";新值"仅出现采用后真会问的 reverse_verify 项,且与 `buildMethodFollowupPlan({accepted:true})` 首题一致";原因"决策 3 推翻原任务书 §4"。
|
||
- 验收:新增用例——探针全部已问 → `post_adopt_verification` 为 `[]`,`validateAdoptNarration` 对含"会拿……核对"的文本返回 `promise` 失败;模板兜底输出含"没有还能核对的前事"。
|
||
|
||
### 5.3 「这题跳过」只跳本题(决策 4)
|
||
|
||
- `choice-action.ts` 新增动作;`choice-card.ts` 核对卡(`kind==="reverse_verify"`)的 stop 槽位改绑新动作,标签沿用 `CHOICE_SKIP_QUESTION_LABEL`;`rectification-agentic-chat.tsx` L326 同步;`answer-choice.ts` 新分支:`focusStatus:"skipped"`、探针写入 `answered_probes`(标记 skipped,不计分)、`userStopped` 不置位、随后跑 `persistNextInterviewIfIdle` 取下一题。
|
||
- 验收:
|
||
- 采用后有两道核对题 → 跳第一道 → 返回的下一焦点是第二道,`sessionOutcome` 不变为 `user_stopped`,旁白不含 `adoptCue`;
|
||
- 跳最后一道 → 无新焦点,`next_user_action.id === "start_consultation"`,旁白等于 `RECTIFICATION_USER_COPY.postAdoptVerifyDone`;
|
||
- 采用前区分题的停止按钮仍触发 `STOP_ACTION`(既有测试不改)。
|
||
|
||
### 5.4 核对结束显式状态 + 前端安静收尾(决策 5、6)
|
||
|
||
- GET 案例投影与结构化答题返回增加 `next_user_action`;`rectificationQuestionGapState` 增加输入 `nextUserActionId`:为 `start_consultation` 时返回新态 `"verified_idle"`(不 preparing、不 unavailable、不重试);组件在该态渲染一行 `postAdoptVerifyDone`,不渲染重载按钮。`frontend/DESIGN.md` 记录该态;`VOICE.md` 对照文案。
|
||
- 验收:
|
||
- `tests/rectification-surface-state*.test.ts`:已采用 + 无焦点 + `start_consultation` → `verified_idle`;已采用 + 无焦点 + 无 `next_user_action`(旧数据)→ 仍走原路径(不回归);
|
||
- 组件测试:`verified_idle` 下 DOM 无 `rectification-question-gap__copy`、无重载按钮、无 `rectification-consult-handoff`;状态栏文案与 BUG-502 断言一致。
|
||
|
||
### 5.5 采集题去年份前缀(决策 7,可否决)
|
||
|
||
- `spokenFollowupForUser` 对 `collect_method_evidence` 不再拼期间前缀;家庭轮转分支保留 `probe_year` 但不填 `year_label`。
|
||
- 验收:`tests/rectification-collect-direction-20260904.test.ts` 中断言家庭题以"YYYY 年前后,"开头的用例(若有)三栏改写;新增:家庭采集题文本等于 `USER_COLLECT_QUESTION.family`,同时 `probe_year > 0`;BUG-418 的"不出无年份 D24 卡"测试保持通过。
|
||
|
||
### 5.6 记录
|
||
|
||
- `docs/BUG_HISTORY.md` 从 BUG-536 起:A(重复核对题)、B(跳过=停止 + 已采用仍念采用提示 + 前端 unavailable)、C(旁白承诺与计划不同源,关联 BUG-519~523 并写明原任务书 §4 是根因)、D 前半(年份前缀,关联 BUG-414/418)。每条含现象、触发、根因、修复、验证、防复发。
|
||
- `docs/testing/rectification-post-adopt-verify-20260904.md`:真人清单——采用后核对题不重复;「这题跳过」出下一题;最后一题跳过后一行收尾且无重载按钮;家庭题无年份前缀;**答"没有"后直接出采用卡属预期**(写明原因:预算已满、家庭是最后一条必答线)。
|
||
- `CHANGELOG.md`、`docs/tasks/PROGRESS-rectification-post-adopt-verify-20260904.md`(含 tsc / lint / 测试六行汇总 + 退出码、无 Docker 时失败清单与基线逐条比对)。
|
||
|
||
## 6. 让步顺序
|
||
|
||
5.1 → 5.2 → 5.3 → 5.4 → 5.6 必做;5.5 可被产品否决或最后做。5.4 若前端投影改动超预期,允许先只做服务端 `next_user_action` 暴露 + 旁白收尾(决策 6),前端 `verified_idle` 拆到修复单,但必须在进度记录里写明,且 B 现象中的"没有拿到下一个问题"在此情况下**不得**写成已修。
|
||
|
||
## 7. 开工前置命令
|
||
|
||
```bash
|
||
git status -sb | head -1
|
||
git fetch origin --prune
|
||
git worktree add -b codex/rectification-post-adopt-verify-20260904 .worktrees/rectification-post-adopt-verify-20260904 origin/staging
|
||
cd .worktrees/rectification-post-adopt-verify-20260904/frontend && ln -s /workspace/Jyotisha/frontend/node_modules node_modules
|
||
grep -o "BUG-5[0-9][0-9]" ../docs/BUG_HISTORY.md | sort -u | tail -1
|
||
grep -n "BUG-350\|BUG-463\|BUG-499\|BUG-502\|BUG-518\|BUG-520" ../docs/BUG_HISTORY.md | head
|
||
npx tsx --test tests/*.test.ts 2>&1 | tail -8 # 记基线失败清单
|
||
```
|
||
|
||
## 8. BUG 编号起点
|
||
|
||
BUG-536~539(交付前 `origin/staging` 已占用 BUG-535;基线任务书最大号曾是 BUG-534)。
|