Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
7.2 KiB
7.2 KiB
TASK · 修复单:BUG-655(512be9b7)验收后仍未闭合的三处——采集焦点 kind 违反表约束、引擎刷新用整网格、无新题时 GET 永远「等待收窄」(2026-09-12)
- 基线:
origin/staging@e59dd47c(已部署;代码512be9b7) - 分支:
codex/rectification-targeted-collect-persist-fix-20260911,基于origin/staging - 执行方:coding agent;验收:Claude
- BUG 编号起点:BUG-656(开工时核对
docs/BUG_HISTORY.md最大号,应为 655) - 本单是
TASK-rectification-targeted-collect-persist-20260911.md的修复单。执行方当时只拿到该单 T0/T3 的片段(见其 PROGRESS「任务单不在本仓」),原单 §2.1 / §2.2 / §2.3 三条根因一条都没改;原单本次已随本单一并入库。
1. 验收结论(Claude,2026-09-12,基于 e59dd47c)
| 项 | 结论 | 依据 |
|---|---|---|
512be9b7 T3「空刷新不写库、候选集变更不写库、并入探针 id 与已答同名」 |
通过 | refresh-discriminator-probes.ts refreshWriteBlockedReason / alignedProbeId;tsc --noEmit 0 错;相关测试 fail=0 |
| 原单 §2.1 定向 / 锚定 / 通用采集焦点写不进库 | 未通过(未做) | server-focus.ts L346 仍 targetKind: input.followup.kind_hint ?? null;targeted:<domain> 违反 agentic_rectification_conversation_focuses.target_kind CHECK(20260814020000_rectification_v10_runtime.sql L124–131),无后续迁移放宽 |
| 原单 §2.2 引擎刷新按剩余候选生成 | 未通过(未做) | refinement_packet.py L707 仍 grid_times = built.candidate_times,column_times 只喂三列对照;refresh_probes 分支永远拿到全网格 |
| 原单 §2.3 「等待收窄」必须有载体 | 未通过(未做),且 512be9b7 让它变成永久状态 |
见 §2 |
| 真机 | 未做(无登录态) | 需部署后按场景 0c 走 |
2. 512be9b7 引入的新死角(BUG-658)
刷新没有新题时现在什么都不写(rectification_refresh_persist_skipped reason=no_new_probes)。于是库里 refresh_count 恒为 0:
- GET
decideFromDossier→narrowingExhaustion:refreshed=false且剩余换升层非空 →refreshExhausted=false→stillNeedNarrowing恒真 →decideRectification走collect(..., waitToNarrowCapability):无题、无卡、can_adopt=false。GET 路由本身不调刷新,这个状态永远翻不过来。 - 答题事务 / 空闲路径每次都会再调一次引擎(
runV9CandidateScore,真机单次约 20 s),因为shouldRefreshDatedPool只看库里的refresh_count。 - 由于 §1 的 §2.2 未改,刷新在本案永远是
no_new_probes,所以本案在e59dd47c上的表现与6c9a0896相同:第六题答完后「没有拿到下一个问题」,且不再出卡。
3. 决策记录(沿用原单 §3,补一条)
- 「刷新已尝试且无新题」必须是持久化事实:写进
inference_state.refresh_attempts[]({candidate_set_id, answer_count, result:"no_new_probes"|"new_probes", at}),不新增推理 transition 行也可以(允许走persistV9InferenceState之外的轻量字段,例如 latest_result.decisionReceipt 的refresh_attempts);refreshExhausted由「同一候选集、同一已答数下已有一次尝试记录」判定,shouldRefreshDatedPool同源。
4. 硬红线
沿用原单 §4;另加:不得让 GET 的决策依赖只存在于内存的刷新结果;同一候选集、同一已答数下引擎刷新最多调用一次。
5. 任务分解
- T1 采集焦点 kind 映射(BUG-656 = 原单 §2.1 / T1):
server-focus.tspersistCollectFocus/persistSpokenChoiceFallback写targetKind前经collectFocusTargetKind(followup)(按领域映射到 CHECK 枚举:family→family_event、finance→finance_gain、relocation→relocation、relationship→relationship_change、career→career_change、education→education_milestone、health→self_health_event,其余 null);kind_hint原值写入expected_answer_schema.collect_kind;读取侧(answer-choice.ts/agent-run.ts/collection-question-pool.ts的 invite / anchor / generic / targeted 判定)改读collect_kind,找不到时回退旧字段。验收:rectification-server-focus.test.ts用真实 CHECK 列表断言三类 followup 的targetKind合法、status=created;回放测试第六题答完 → 定向题「还有 N 条线能把 04:48 和 05:07 分开:…」成为活动焦点。 - T2 引擎按剩余候选刷新(BUG-657 = 原单 §2.2 / T2):
refinement_packet.py在request.refresh_probes为真且column_times非空时,把column_times作为_discriminating_event_probe_lists的candidate_times;event_probes.pyrefresh and remaining分支据此命中。验收:tests/test_candidate_discriminator_contract.py加「31 格 + column_times=5」夹具,断言簇按 5 个候选重算且探针candidate_ids只含这 5 个;若确无边界则public=[]并在回执写refresh_result="no_new_probes"。 - T3 刷新尝试持久化 + 等待收窄以「已问过」判定(BUG-658 = 原单 §2.3 / T3 + 本单 §2):按 §3.5 写
refresh_attempts;narrowingExhaustion.refreshExhausted= 当前候选集与已答数下已有尝试记录 或 剩余换升层为空;targetedCollectExhausted=collect:targeted:*焦点已存在且非 active 或池空;定向题落库skipped时同一事务把collect:targeted:<domain>记为skipped话题并按 BUG-651 出卡。客户端rectificationQuestionGapState:sessionOutcome=collect_evidence && !stopReason && 无焦点无卡→collect_waiting。验收:模拟引擎恒返回旧题 → 第一次刷新后refresh_attempts有记录、第二次不再调引擎、GET 决策进入定向补事或出卡;模拟定向题落库失败 → 同一事务出「目前范围」卡,不出现「没有拿到下一个问题」。 - T4 记录:BUG-656~658 入历史(656 关联 BUG-648 / 442;657 关联 BUG-653;658 关联 BUG-655 / 651 / 652);CHANGELOG;PROGRESS;场景 0c 补「定向题必须出现、无新题不重复调引擎」。
6. 让步顺序
T1 + T3 不可省(它们决定用户能不能继续);T2 可拆下一单,但拆出去时 T3 必须让「无新题」直接进定向补事。
7. 开工前置命令
git fetch origin --prune
git worktree add -b codex/rectification-targeted-collect-persist-fix-20260911 .worktrees/rectification-targeted-collect-persist-fix-20260911 origin/staging
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 655
cd frontend && npm test -- tests/rectification-server-focus.test.ts tests/rectification-probe-pool-exhausted-20260911.test.ts tests/rectification-replay-20260911.test.ts
cd .. && .venv/bin/python -m pytest tests/test_candidate_discriminator_contract.py tests/test_rectification_event_probes.py -q
8. 验收口径
沿用原单 §8;真机:§1 序列第六题答完必须看到定向补事题;说「没有了」后看到「目前范围」卡;全程不出现「没有拿到下一个问题」;同一候选集下引擎刷新只调用一次(容器日志 rectification_refresh_persist_skipped 至多一条)。