Files
Jyotisha/docs/tasks/TASK-rectification-targeted-collect-persist-20260911.md
T

74 lines
12 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-11)
- 基线:`origin/staging` @ `6c9a0896`(已部署;含 BUG-653/654Skill 10.0.24
- 分支:`codex/rectification-targeted-collect-persist-20260911`,基于 `origin/staging`
- 执行方:coding agent;验收:Claude
- BUG 编号起点:**BUG-655**(开工时 `grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1` 核对,应为 654
- 不改确认门、不改 `SCORE_DELTA`、不改 `MIN_ACCEPTANCE_*`;不升 Skill
## 1. 事故实证(2026-09-11 staging 真机,代码 6c9a0896
同一序列(两件学业 + 实习入职/离职 + 六道带年月选择题 C/C/C/B/A/C)。第六题答完,助手只回「已记录,范围收到 04:48–05:07。」,没有新题、没有定向补事句、没有卡;界面「没有拿到下一个问题。」。BUG-653/654 想做的「先刷新探针 → 再定向补事 → 再出卡」三步一步都没有在用户面前出现,而且比 BUG-651 之前更糟:连卡也不出了。
### 1b. 同一 Case 的 GET 快照(产品负责人 2026-09-11 晚补充,折叠视图)
| 字段 | 值 | 含义 |
| --- | --- | --- |
| `interview.type / session_outcome` | `ask_candidate_discriminator` / `discriminate_candidates` | GET 的决策层认为**还有一道可问的区分题** |
| `next_user_action.id` | `ask_candidate_discriminator` | 同上 |
| `current_question / choice_card` | null / null | 库里没有活动焦点:这道题从未落下 |
| `latest_result.candidates` | `[]` | 公开候选投影为空。答第六题前同一 Case 的快照里候选是 5 个,说明第六题之后写入的那一行推理状态(BUG-653 的 `persistRefreshedInference``reason=supersede``rawAnswer=refresh_probes`)让 `authoritativeCandidateProjection` 判为不一致或取不到候选 |
| `step_state` | 第 1 步·收集经历 | 与决策层「出区分题」自相矛盾 |
这说明 §2.3 之外还有第二条死路:刷新之后决策层拿到了一道「可问」的题(最可能是 `eventProbeFromInference``inference_state.probes` 里已答过或同域同年的题重新并入 `eventProbes`,或刷新并入的探针 `id` 与已答 `probe_id` 不一致而被当成未答),持久化层按 `probe_already_answered` / 同年去重拒绝落焦点,于是「决策有题、库里无题」——与 BUG-651 前的漏洞同型。T0 必须同时打印 GET 侧 `decision.probe.semanticKey``persistServerOwnedFocus` 的拒绝状态,并核对刷新写入的 `inference_state.candidates``candidate_set_id`
## 2. 根因(按 6c9a0896 源码定位,三段同时成立)
1. **定向补事焦点写不进库(BUG-655)**`method-followup.ts` `followupFromPoolItem` 给定向题 `kind_hint = "targeted:<domain>"`(锚定题 `anchor:<kind>:<year>`、通用题 `generic:<domain>` 同理)。答题事务里的服务端落焦点 `server-focus.ts` `persistCollectFocus` 把它原样写成 `p_target_kind``agentic_rectification_conversation_focuses.target_kind` 的 CHECK`frontend/supabase/migrations/20260814020000_rectification_v10_runtime.sql` L124–131)只允许证据种类枚举,插入抛错 → 不是幂等冲突 → 返回 `skipped` → 重试一次仍 `skipped`。对照:Agent 工具路径 `rectification-v9-tools.ts` `rectification-set-focus``evidenceKindSchema` 校验并对 collect 焦点写 `targetKind: null`,所以开场邀请题(`collect:invite:more`)能落,而答题事务之后的定向题永远落不了。dd8f35f7 的锚定 / 通用题在真机同样落不了,只是之前训练门一开就跳过了它们,没暴露。
2. **探针刷新在引擎侧是空转(BUG-656)**`refresh-discriminator-probes.ts` 把剩余 5 个候选放在 `columnTimes` 传给引擎;`api_service.py` 只把 `refresh_probes` 加进白名单;`refinement_packet.py` L707 给探针生成器的 `candidate_times``grid_times = built.candidate_times`(整个 31 分钟网格),`column_times` 只用于三列对照。于是 `_discriminating_event_probe_lists``remaining = _remaining_contexts(built, 全网格)` = 全部,簇还是最初 9 簇,生成的仍是那 7 道已答题;`mergeConflictProbes` 并入 0 道。`refresh_count` 记成 1,刷新被判「已用尽」。
3. **决策层把「定向补事池非空」当成「还没问完」,形成无载体死循环(BUG-657)**`decision-from-dossier.ts` `narrowingExhaustion``targetedCollectPool(...).length === 0 || isTargetedCollectDeclined(...)``targetedCollectExhausted`。定向题因 1 落不了库,既不会被答也不会被拒 → 池永远非空 → `stillNeedNarrowing` 恒真 → `decideRectification``collect(..., waitToNarrowCapability)``nextAction=ask_fact_collection``canAdopt=false``canOfferRange=false``stopReason=null`。GET 无活动焦点、无卡;客户端 `interviewCollectWaiting` 要求 `stopReason ∈ insufficient_*`,不满足 → `questionGap=unavailable` → 「没有拿到下一个问题」;「接着问」走同一条路,同样失败。BUG-652 的载体不变量只覆盖了 `persistNextInterviewAfterChoice` 末尾的空正文,没覆盖「决策要求等待、载体却写不进去」这一种。
4. **待核实(T0**`persistExhaustionCollect` 在焦点 `skipped` 时会把定向题干作为 `hostNarration` 返回,`persistApplied` 应把它拼在「已记录…」之后写进确定性 turn;真机正文里没有这句。怀疑 `persistV9DeterministicTurn(requestId = actionId)` 与选择动作共用 request id 被幂等吞掉,或 `spoken` 为空。用 staging 容器日志核实:`[rectification-v9] persist next focus failed``refresh discriminator probes failed``persist refreshed probes failed` 三条 warn 的 `reason`
## 3. 决策记录(产品负责人 2026-09-11
1. 定向补事 / 锚定 / 通用三类采集题必须能在答题事务里落库:`target_kind` 只写枚举内的证据种类(按领域映射:family→`family_event`、finance→`finance_gain`、relocation→`relocation`、relationship→`relationship_change`、career→`career_change`、education→`education_milestone`、health→`self_health_event`),采集种类提示(`targeted:` / `anchor:` / `generic:`)放进 `expected_answer_schema.collect_kind`,不改表结构、不加迁移。
2. 探针刷新必须真的按剩余候选生成:`refresh_probes=true` 时引擎探针生成器的 `candidate_times``column_times`;刷新后若公开探针与已答集合无差集,回执写 `refresh_result="no_new_probes"`
3. 「等待收窄」的决策不得没有载体:`targetedCollectExhausted` 必须由**已问过**focus 存在且 resolved/declined/skipped)来判定,不是由池非空判定;定向题落库失败时,同一事务内把 `collect:targeted:*` 记为 `skipped` 话题并直接交付卡(BUG-651 口径),不得停在 `ask_fact_collection` 且无焦点。
4. 客户端:`stopReason=null``sessionOutcome=collect_evidence` 且无焦点无卡时,不再显示「没有拿到下一个问题」,显示缺口句并保持输入框可用(与 `collect_waiting` 同形)。
## 4. 硬红线
- 不加数据库迁移;不放宽 `target_kind` CHECK。
- 刷新不改 `candidate_set_id` / 已答题(BUG-587 / BUG-594);同域同年去重照旧(BUG-559)。
- 任何 `nextAction` 在事务结束时必须满足:有活动焦点,或 `can_offer_range=true` 并带 `range_delivery`,或正文含精确缺口句(BUG-646 / 652 / 657 合并不变量,写成一条合同测试)。
- 既有测试总数不降;改断言写原值 / 新值 / 原因;记录只用脱敏领域 + 年月。
## 5. 任务分解
- **T0 取证**staging 容器日志抓 §2.4 三条 warn 的 `reason`;对 §1b 那个 Case 直接查库:最后一行 `agentic_rectification_inference_transitions`reason / raw_answer / inference_state.candidates 数量 / candidate_set_id / refresh_count)与 `latest_result` 的候选投影为何为空;用 §1 序列写回放测试,断言第六题答完后 `persistServerOwnedFocus` 的 status、`refresh` 返回的新探针数、决策 `nextAction / canOfferRange / stopReason`,把三者写进进度记录第一段。
- **T1 采集焦点 kind 映射(BUG-655**`server-focus.ts` `persistCollectFocus``persistSpokenChoiceFallback``targetKind` 前经 `collectFocusTargetKind(followup)`(枚举内或 null),`kind_hint` 原值写入 `schema.collect_kind``answer-choice.ts` / `agent-run.ts``collect_kind` 判定 invite / anchor / generic / targeted。验收:定向、锚定、通用三类 followup 在 `rectification-server-focus.test.ts` 里用真实 CHECK 列表断言 `targetKind` 合法且 `status=created``npm run test:db` 若有 Docker 则跑焦点插入。
- **T2 引擎按剩余候选刷新(BUG-656)**:`api_service.py` / `refinement_packet.py``refresh_probes` 为真时把 `column_times` 作为探针生成器的 `candidate_times``event_probes.py``refresh and remaining` 分支才真正命中。`refresh-discriminator-probes.ts` 若引擎返回的公开探针键全在已答集合内,`refresh_result="no_new_probes"` 并记 `refresh_count`。验收:`tests/test_candidate_discriminator_contract.py` 加「31 格 + column_times=5 个」夹具,断言簇按 5 个候选重算、至少出现一道家人或财务月级边界题(若确无边界则断言 `no_new_probes`);TS 合同测试断言刷新后 `probes` 增量且 `candidate_set_id` 不变。
- **T3 等待收窄必须有载体(BUG-657)**:`persistRefreshedInference` 只在引擎真的返回新探针时写库,写入前断言 `candidates` 非空且 `candidate_set_id` 不变,否则不写;刷新并入的探针 `id` 必须与 `answered_probes[].probe_id` 同一命名规则(带 split hash 后缀),并入前按 `semantic_key` 与同域同年去重;`narrowingExhaustion``targetedCollectExhausted` 改为「`collect:targeted:*` 焦点已存在且非 active」或池空;`persistNextInterviewAfterChoice` / `persistNextInterviewIfIdle` 在定向题落库 `skipped` 时写 `collect:targeted:<domain>``skipped` 话题并按 BUG-651 出卡;`decideRectification` 加断言:`collect``waitToNarrowCapability` 只允许在 `targetedCollectExhausted=false` 且**本事务已成功落下焦点**时返回。客户端 `rectificationQuestionGapState``sessionOutcome=collect_evidence && !stopReason && 无焦点无卡``collect_waiting`。验收:§1 序列第六题答完 → 出定向题「还有 N 条线能把 04:48 和 05:07 分开:…」;答一件家人事 → 重算回 S2;答「没有了」→ 出「目前范围」卡。模拟落库失败 → 同一事务出卡,不出现「没有拿到下一个问题」。
- **T4 记录**BUG-655657 入历史(655 关联 BUG-648、BUG-442「覆盖判定不得依赖模型 domain」;656 关联 BUG-653657 关联 BUG-651/652/646);CHANGELOGPROGRESS`docs/testing/rectification-scenarios-20260907.md` 场景 0b 补「定向题必须出现」一条。
## 6. 让步顺序
T2 若引擎侧改动大,可先只做 T1 + T3(定向补事能问、问完必出卡),刷新留下一单;T1 + T3 不可省。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-targeted-collect-persist-20260911 .worktrees/rectification-targeted-collect-persist-20260911 origin/staging
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 654
grep -n "target_kind" frontend/supabase/migrations/20260814020000_rectification_v10_runtime.sql | head -3
cd frontend && npm test -- tests/rectification-server-focus.test.ts tests/rectification-probe-pool-exhausted-20260911.test.ts tests/rectification-replay-20260911.test.ts tests/rectification-collection-question-pool.test.ts
cd .. && .venv/bin/python -m pytest tests/test_candidate_discriminator_contract.py tests/test_rectification_event_probes.py -q
```
## 8. 验收口径
- 前端 `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build``/` 仍 Static、首屏 gzip ±2%;有 Docker 则 `npm run test:db`
- Python:上述两个测试文件 + `run_quality_gate.py --profile quick`
- 部署后真机:§1 序列第六题答完必须看到定向补事题;说「没有了」后看到「目前范围」卡;全程不出现「没有拿到下一个问题」。