Files
Jyotisha/docs/tasks/TASK-rectification-post-adopt-verify-20260904.md
T
Jesse_Chen d4627a5019
Independent Staging Quality Gate / validate (push) Successful in 14m31s
Independent Staging Quality Gate / publish (push) Successful in 1m59s
fix(rectification): dedupe post-adopt verify and skip this probe only
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>
2026-09-04 21:51:59 +08:00

111 lines
15 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-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-536539**
## 1. 事故实证(staging,真实案例,资料已抽象)
同一案例在采用代表分钟之后,四个现象连着出现。以下只写形状,不写年份、事件与候选分钟。
| # | 用户看到的 | 期望 | 定位(符号) |
| --- | --- | --- | --- |
| A | 采用后第一道"核对题",是采集阶段**已经问过并回答过**的带年份职业题,题干几乎逐字重复 | 采用后核对只问**还没问过**的可计分前事 | `v9/method-followup.ts` `remainingReverseVerifyProbes`L548571)过滤了拒答领域、`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` L265289 `optionId==="stop"``focusStatus:"skipped"` + `userStopped:true``core/rectification-decision.ts` L276 `userStopped``completeWithRange``publicCanAdopt` 仍 true → `answer-choice.ts` L10351061 走 `adoptionNarration`(带 `adoptCue`),不看 `case.acceptedTime`;无下一焦点 → `lib/rectification-surface-state.ts` L250 `rectificationQuestionGapState` 走 preparing → unavailable |
| C | 采用卡旁白承诺"采用后会拿 X 年的教育题以及家庭、财务、健康压力三类域外题来核对",采用后一道都没问 | 旁白只承诺服务端真的会问的题 | `v9/adopt-narration.ts` L161173 `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` L9991001 把 `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-463holdout 只在 `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 Y1domain 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-536539(交付前 `origin/staging` 已占用 BUG-535;基线任务书最大号曾是 BUG-534)。