Files
Jyotisha/docs/tasks/TASK-rectification-collect-vs-offer-consistency-20260905.md
T

11 KiB
Raw Blame History

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.tsfrontend/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. 事故实证(staging2026-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::datedMethodCollectOpenL377)只数 dasha_events / d9_relationship / d10_career / relatives 四个阻塞方法,不数 DATED_COLLECT_ORDER 里的学业、财务、搬家、健康与职业口述。事业 + 家人一过,四个方法都 covered,决策就成了 adopt_representative
  2. v9/method-followup.ts::buildMethodFollowupPlan 尾部(约 L23602371):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::shouldSkipFollowupPersistL135):isRemainingEvidenceCollect → 不跳过 → persistNextInterviewIfIdle 在同一轮把搬家采集写成焦点、挂到本轮 turn。
    • 客户端 rectification-agentic-chat.tsx(约 L13741383):canShowRectificationSelectionCardssessionOutcome 为真 → 卡挂在最后一条已结算助手消息;唯一的挡板是「某条消息挂着未答问题」(BUG-544)。步 2 卡出现时那条财务题没有把卡挡住(焦点绑 turn 是 best-effortlinkFocusAskedTurn 失败只 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_actionask_fact_collectionoffer 工具因 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-549yes 探针视为覆盖)、BUG-544(未答问题在场时隐藏采用卡)的用例原样通过。
  • accepted 早退路径(采用后只走 remainingReverseVerifyProbes)与 BUG-536539 不受影响:input.accepted 为真时本单新增判断不得生效。
  • 用户停止(userStopped / paused)与 exhausted 的现有出口不变:用户说「没有更多了」仍按现有规则给区间或代表时间,不得因本单变成继续追问。
  • 不放宽 meetsAcceptanceEventQualitybuildConfirmationGate;不写账本;不改 DATED_COLLECT_ORDER
  • 任务书 / 进度 / Bug 历史只用抽象年份与抽象分钟。

5. 任务分解

5.1 决策层:带年份采集未完不判「可采用」

  • decision-from-dossier.ts::decideFromDossierdecideAfterInferenceChange:把传给 decideRectificationdatedMethodCollectOpen 改为 datedMethodCollectOpen(collecting.methods) || isRemainingEvidenceCollect(collecting.next_followup)(两处同改;collecting 计划已用 sessionOutcome: "collect_evidence" 构建,next_followup 现成)。
  • core/rectification-decision.ts::decideRectificationseparation.sufficient 分支在 finish("adopt_representative") 之前(放在 input.accepted 判断之后、confirmationAllowed 之前),若 input.datedMethodCollectOpen === true && !input.userStopped && stopClass?.kind !== "exhausted"collect(separation, holdout, range, null, capability, stopReason)acceptedawaiting_confirmationconfirmationAllowed)路径不受影响。
  • 验收:tests/rectification-decide-next-action.test.ts 新增——分离度足够、datedMethodCollectOpen: true、未 accepted、未 stopped → sessionOutcome === "collect_evidence"nextAction === "ask_fact_collection"can_adopt === false;同输入 userStopped: true → 现有停止出口不变;同输入 accepted: trueadopt_representative

5.2 端到端:出卡与追问不再同轮

  • tests/rectification-collect-direction-20260904.test.tsrectification-candidate-offer-anchor.test.ts 新增用例(用现有 dossier fixture 的组装方式):学业 / 感情 / 事业有带年份证据、家人已拒答、财务未问、候选分离度足够 → decideFromDossiercollect_evidencebuildNextUserActionask_fact_collectionpersistNextInterviewIfIdle 的计划 next_followup.domain === "finance";把财务、搬家、健康都补齐(或拒答)后同一 dossier → adopt_representativenext_followup === null
  • offer 工具:在 tests/rectification-v9-tools*.test.ts(或现有 offer 工具测试)加一条——上面「财务未问」的 dossier 调 rectification-offer-candidatesoffer_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.mddocs/testing/rectification-collect-vs-offer-consistency-20260905.md(真实环境:家人「没有」之后到财务 / 搬家 / 健康问完之前,页面只有只读区间、没有可点的时间卡、Agent 不写验证报告;带年份域问完后一轮内同时出现报告 + 卡片,且下面不再跟采集题)。

6. 让步顺序

5.1 与 5.2 一起做,不可拆;5.3 不可省。产品若选决策记录 3 的另一条路,5.1 整段作废、改客户端,须先在进度记录写明并通知验收方。

7. 不在本单(待定位)

  • 原始流事件 JSON 短暂出现在对话区:需要产品下次复现时截图(是出现在助手气泡里,还是「正在…」工作行里)并记下当时选的模型;执行方顺手查该轮服务端日志是否有 attempt.reset / 重试。定位前不得加「防御性」过滤代码。

8. 开工前置命令

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 --noEmitnpm run lint