From 3edb628cb53958cdee7bd1e1ad4c538c097a21b5 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Sat, 5 Sep 2026 09:41:20 +0000 Subject: [PATCH] docs(tasks): collect-vs-offer consistency brief (BUG-550) --- docs/tasks/README.md | 1 + ...n-collect-vs-offer-consistency-20260905.md | 85 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 docs/tasks/TASK-rectification-collect-vs-offer-consistency-20260905.md diff --git a/docs/tasks/README.md b/docs/tasks/README.md index f7be7a32..bed0a247 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -55,6 +55,7 @@ | — | `PROGRESS-rectification-family-collect-continue-20260905.md` | 家人答「没有」后直接给结果,未继续收能区分分钟的经历 | 已由 cd704a92 修正并验收通过 | `codex/rectification-quality-probe-dedupe-20260904`(BUG-546) | | `TASK-rectification-family-collect-continue-fix-20260905.md` | `PROGRESS-rectification-family-collect-continue-20260905.md` | `998406ce` 修复单:家人之后的 dated 补采集无条件抢在现成区分卡之前(choice-card A/B 卡用例回归),另两条顺序断言缺三栏说明 | 已验收通过(浏览器走查欠) | `cd704a92`(BUG-547) | | `TASK-rectification-probe-answer-covers-domain-20260905.md` | `PROGRESS-rectification-probe-answer-covers-domain-20260905.md` | 区分卡答 A/B 后紧接着又问同领域"哪年…"采集题:点选答案只进推断层不写账本,计划层领域覆盖只看账本;改为 yes/weak_yes 视为该领域采集已覆盖(不写账本、不动门) | 待验收 | `988d97ae`(BUG-549) | +| `TASK-rectification-collect-vs-offer-consistency-20260905.md` | `PROGRESS-rectification-collect-vs-offer-consistency-20260905.md` | 带年份采集没问完就出采用卡 + 报告,同一轮又被搬家采集题把卡挤掉:决策层判 `adopt_representative` 而计划层仍有 dated 采集(BUG-546 只修了一半);改为剩余采集未完保持 `collect_evidence`,出牌轮才出卡写报告 | 待执行 | `codex/rectification-collect-vs-offer-consistency-20260905`(BUG-550 起) | | `TASK-api-not-configured-mislabel-20260904.md` | — | 16 处路由把数据库瞬断(部署切换窗口)兜底翻译成 503「服务尚未配置」;改为仅配置错误用该文案,其余 `service_unavailable`,收敛为共享 helper | 待执行 | `codex/api-not-configured-mislabel-20260904`(BUG-542 起) | | `TASK-rectification-ux-20260902.md` | `PROGRESS-rectification-ux-20260903.md` | 会话面空白假死与交互摩擦 | 已验收 | `d159f08e`(09-03 在新基线重做后合入,BUG-505~509) | diff --git a/docs/tasks/TASK-rectification-collect-vs-offer-consistency-20260905.md b/docs/tasks/TASK-rectification-collect-vs-offer-consistency-20260905.md new file mode 100644 index 00000000..a373009a --- /dev/null +++ b/docs/tasks/TASK-rectification-collect-vs-offer-consistency-20260905.md @@ -0,0 +1,85 @@ +# TASK · 采集没问完就出采用卡、出卡后又被采集题挤掉(BUG-546 的另一半)(2026-09-05) + +- 基线:`origin/staging` @ `1052a027`(含 BUG-546/547/549) +- 分支:`codex/rectification-collect-vs-offer-consistency-20260905` +- 执行方:coding agent;验收:Claude +- 涉及文件:`frontend/src/lib/rectification-agentic/core/rectification-decision.ts`、`frontend/src/lib/rectification-agentic/v9/decision-from-dossier.ts`;测试见 §5。不改客户端隐藏规则(BUG-544)、不改 Skill 正文、不改引擎。 +- BUG 编号起点:**BUG-550**(开工时 `grep -o "^## BUG-5[0-9][0-9]" docs/BUG_HISTORY.md | tail -1` 复核;BUG-542 仍为 `TASK-api-not-configured-mislabel-20260904.md` 预留) +- 串行:与 `TASK-rectification-probe-answer-covers-domain-20260905.md`(已合入 `988d97ae`)无冲突;若 `TASK-api-not-configured-mislabel` 同日开工,两者不改同一文件。 + +## 1. 事故实证(staging,2026-09-05,部署 `afd14948`) + +同一个 Case,用户连续经历: + +| 步 | 用户看到 | 期望 | +| --- | --- | --- | +| 1 | 家人题答「没有」→「记下了,这方面先跳过。」→ 财务口述采集题「钱的方面…」 | 只问财务题 | +| 2 | 财务题还没答,**候选时间卡(9 个候选 + 相对支持)已经挂在下面** | 采集没完不出卡 | +| 3 | 用户答完财务题 → Agent 调 `rectification-offer-candidates`,正文写出整份八法「生时校正验证报告」,含「唯一分钟确认 · 本会话以代表性时间收口」「当前可以采用 HH:MM」 | 出牌轮才写报告 | +| 4 | 报告末尾紧跟搬家采集题「有没有哪年搬家,或开始长期住在外地?」,**时间卡消失** | 出了牌就不再追问;或者压根不出牌 | + +## 2. 根因 + +一句话:**决策层已经判成「可采用」,计划层却还有没问完的带年份采集,两层各说各的,四个消费者听了不同的一层。** + +1. `core/rectification-decision.ts::decideRectification`:候选分离度够(`separation.sufficient`)时直接 `finish("adopt_representative")`。`datedMethodCollectOpen` 只在 `!separation.sufficient` 分支被看(约 L307),而且 `v9/method-followup.ts::datedMethodCollectOpen`(L377)只数 `dasha_events / d9_relationship / d10_career / relatives` 四个阻塞方法,**不数** `DATED_COLLECT_ORDER` 里的学业、财务、搬家、健康与职业口述。事业 + 家人一过,四个方法都 covered,决策就成了 `adopt_representative`。 +2. `v9/method-followup.ts::buildMethodFollowupPlan` 尾部(约 L2360–2371):BUG-546 让 `isRemainingEvidenceCollect(next)` 的采集**豁免** `deferAdoption`,于是 `next_followup = 搬家采集` 与 `session_outcome = adopt_representative` 同时成立。 +3. 四个消费者: + - `buildNextUserAction`(约 L1434):只看 `sessionOutcome`,输出 `adopt_representative`「请用户从下方时间卡片选择」→ 模型照 Skill 10.0.14 L119「出牌/采用轮正文按八法写出验证报告」写了整份报告,并调 `rectification-offer-candidates`。 + - `mastra/rectification-v9-tools.ts` offer 工具(约 L1961):门只看 `offerSessionKinds()` 含 `adopt_representative` → 放行,Case 转 `candidate_ready`。 + - `answer-choice.ts::shouldSkipFollowupPersist`(L135):`isRemainingEvidenceCollect` → 不跳过 → `persistNextInterviewIfIdle` 在同一轮把搬家采集写成焦点、挂到本轮 turn。 + - 客户端 `rectification-agentic-chat.tsx`(约 L1374–1383):`canShowRectificationSelectionCards` 看 `sessionOutcome` 为真 → 卡挂在最后一条已结算助手消息;唯一的挡板是「某条消息挂着未答问题」(BUG-544)。步 2 卡出现时那条财务题没有把卡挡住(焦点绑 turn 是 best-effort,`linkFocusAskedTurn` 失败只 warn),步 4 搬家题绑上了 turn 就把卡挡掉——同一条规则,一次没挡住、一次挡住了,看起来就是「卡先来、再丢」。 + +所以步 2 与步 4 是一个病:状态本身自相矛盾。步 3 的长报告不是模型自作主张,是 `next_user_action` 告诉它「这是出牌轮」。 + +## 3. 决策记录 + +1. **采集优先,与 BUG-546 的既定意图一致**:只要采集计划(`decideFromDossier` 里的 `collecting` 计划)的 `next_followup` 仍是 `isRemainingEvidenceCollect`(学业 / 财务 / 搬家 / 健康 / 职业口述,未覆盖且未拒答),且 Case 未 accepted、用户未停止、未 exhausted,决策就保持 `collect_evidence / ask_fact_collection`,**不得**给出 `adopt_representative`。带年份域问完(覆盖、拒答或 BUG-549 的 yes 探针)之后,才进 `adopt_representative`,此时出牌 + 报告 + 卡片一次到位,且不再追问。 +2. 由决策层一处改,四个消费者自然一致:`next_user_action` 变 `ask_fact_collection`;offer 工具因 kind 不在 `offerSessionKinds` 而拒绝(`offer_not_allowed`);客户端 `canShowRectificationSelectionCards` 为假,改走 `canShowRectificationReadonlyRange`(只读区间,`collect_evidence` 已允许);`shouldSkipFollowupPersist` 行为不变。**不改** BUG-544 的隐藏规则,不改 offer 工具描述,不改 Skill 正文。 +3. 产品可否决项:若产品更想「卡先给、问题照问」(用户可随时选,也可再补一件事),改为放宽客户端 BUG-544 对 `collect_spoken` 的隐藏而**不动**决策层——两条路只能选一条,执行方按本单默认走 1;产品改主意时在进度记录写明。 +4. 报告里「本会话以代表性时间收口」这句来自 Skill 10.0.14 `SKILL.md` L41「本会话以此收口」与 L112 的转述,BUG 历史 L6151 删掉的是宿主机械文案,没删 Skill。本单不动 Skill;产品若要模型也不说这句,另开 Skill 10.0.15 单(词表禁用 + Skill 措辞)。 +5. 步 3 之前用户看到一段原始流事件 JSON(`{"type":"skill.bound"}`…)短暂出现又消失:本次代码通读没有找到把流事件当正文渲染的路径(客户端只把 `answer.delta.text` 进正文,服务端 `answer.delta` 只来自模型 `text-delta`,事件不持久化、模型看不到)。**不在本单**,见 §7。 + +## 4. 硬红线 + +- BUG-546(无区分卡时继续补采集)、BUG-547(有卡先问卡)、BUG-549(yes 探针视为覆盖)、BUG-544(未答问题在场时隐藏采用卡)的用例原样通过。 +- `accepted` 早退路径(采用后只走 `remainingReverseVerifyProbes`)与 BUG-536–539 不受影响:`input.accepted` 为真时本单新增判断不得生效。 +- 用户停止(`userStopped` / `paused`)与 `exhausted` 的现有出口不变:用户说「没有更多了」仍按现有规则给区间或代表时间,不得因本单变成继续追问。 +- 不放宽 `meetsAcceptanceEventQuality`、`buildConfirmationGate`;不写账本;不改 `DATED_COLLECT_ORDER`。 +- 任务书 / 进度 / Bug 历史只用抽象年份与抽象分钟。 + +## 5. 任务分解 + +### 5.1 决策层:带年份采集未完不判「可采用」 +- `decision-from-dossier.ts::decideFromDossier` 与 `decideAfterInferenceChange`:把传给 `decideRectification` 的 `datedMethodCollectOpen` 改为 `datedMethodCollectOpen(collecting.methods) || isRemainingEvidenceCollect(collecting.next_followup)`(两处同改;`collecting` 计划已用 `sessionOutcome: "collect_evidence"` 构建,`next_followup` 现成)。 +- `core/rectification-decision.ts::decideRectification`:`separation.sufficient` 分支在 `finish("adopt_representative")` 之前(放在 `input.accepted` 判断之后、`confirmationAllowed` 之前),若 `input.datedMethodCollectOpen === true && !input.userStopped && stopClass?.kind !== "exhausted"` → `collect(separation, holdout, range, null, capability, stopReason)`。`accepted` 与 `awaiting_confirmation`(`confirmationAllowed`)路径不受影响。 +- 验收:`tests/rectification-decide-next-action.test.ts` 新增——分离度足够、`datedMethodCollectOpen: true`、未 accepted、未 stopped → `sessionOutcome === "collect_evidence"`、`nextAction === "ask_fact_collection"`、`can_adopt === false`;同输入 `userStopped: true` → 现有停止出口不变;同输入 `accepted: true` → `adopt_representative`。 + +### 5.2 端到端:出卡与追问不再同轮 +- `tests/rectification-collect-direction-20260904.test.ts` 或 `rectification-candidate-offer-anchor.test.ts` 新增用例(用现有 dossier fixture 的组装方式):学业 / 感情 / 事业有带年份证据、家人已拒答、财务未问、候选分离度足够 → `decideFromDossier` 给 `collect_evidence`,`buildNextUserAction` 给 `ask_fact_collection`,`persistNextInterviewIfIdle` 的计划 `next_followup.domain === "finance"`;把财务、搬家、健康都补齐(或拒答)后同一 dossier → `adopt_representative`,`next_followup === null`。 +- offer 工具:在 `tests/rectification-v9-tools*.test.ts`(或现有 offer 工具测试)加一条——上面「财务未问」的 dossier 调 `rectification-offer-candidates` 抛 `offer_not_allowed`。 +- 验收:`npx tsx --test tests/rectification-*.test.ts tests/agent-voice-copy-contract.test.ts` fail=0,总数不低于开工时。 + +### 5.3 记录 +- `docs/BUG_HISTORY.md`:BUG-550(决策层「可采用」与计划层「还要采集」并存;关联 BUG-544、546、547、549,说明 BUG-546 为何只修了一半)。 +- `CHANGELOG.md` 一条;`docs/tasks/PROGRESS-rectification-collect-vs-offer-consistency-20260905.md`;`docs/testing/rectification-collect-vs-offer-consistency-20260905.md`(真实环境:家人「没有」之后到财务 / 搬家 / 健康问完之前,页面只有只读区间、没有可点的时间卡、Agent 不写验证报告;带年份域问完后一轮内同时出现报告 + 卡片,且下面不再跟采集题)。 + +## 6. 让步顺序 + +5.1 与 5.2 一起做,不可拆;5.3 不可省。产品若选决策记录 3 的另一条路,5.1 整段作废、改客户端,须先在进度记录写明并通知验收方。 + +## 7. 不在本单(待定位) + +- 原始流事件 JSON 短暂出现在对话区:需要产品下次复现时截图(是出现在助手气泡里,还是「正在…」工作行里)并记下当时选的模型;执行方顺手查该轮服务端日志是否有 `attempt.reset` / 重试。定位前不得加「防御性」过滤代码。 + +## 8. 开工前置命令 + +```bash +git fetch origin --prune +git worktree add -b codex/rectification-collect-vs-offer-consistency-20260905 .worktrees/rectification-collect-vs-offer-consistency-20260905 origin/staging +cd .worktrees/rectification-collect-vs-offer-consistency-20260905 +ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules +cd frontend && npx tsx --test tests/rectification-*.test.ts tests/agent-voice-copy-contract.test.ts 2>&1 | grep -E "^# (tests|pass|fail)|^not ok" +``` + +收尾跑同一条命令 fail=0,再 `tsc --noEmit`、`npm run lint`。