docs(tasks): index 09-11 rectification briefs; fix brief for BUG-656~658 after reviewing 512be9b7
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
e59dd47cdf
commit
2b6d28e7a7
@@ -169,6 +169,11 @@
|
||||
| `TASK-upstream-sync-fix-20260903.md` | `PROGRESS-upstream-sync-fix-20260903.md` | 非原生主题恢复 `degraded`、finance Yogi/D11/confidence cap、模板注册表引用、VedAstro/MCP finance 路由与 quick CORE 扩列 | 已验收 | `4ee79210`(BUG-515);staging health 已到该 SHA;十主题 e2e 合同全对,Python 红 90→69 无新增 |
|
||||
| `TASK-upstream-sync2-20260909.md` | `PROGRESS-upstream-sync2-20260909.md` | 上游活跃分支 `b9a0ef8f`(未进 main):婚恋三层触发模型(punarphoo / event_class_split 进证据链)+ 十套条件大运接 full reading 与长报告时间系统表;快照锁推进;Skill 6.9.16;不取 shadbala profile / PL9 用户版 / 校正泄漏项 | 已验收(04ad3325):定向 176 通过、quick 门 708/1 env 失败、pl9 时间系统表 5+10 行;1 P1(网页咨询路径 `dasha_info` 无 MD/AD,事件类拆分空转)→ 修复单 `TASK-upstream-sync2-fix-20260909.md` | 分支 `codex/upstream-sync2-20260909`(BUG-608) |
|
||||
| `TASK-upstream-sync2-fix-20260909.md` | `PROGRESS-upstream-sync2-fix-20260909.md` | 修复单:`_thematic_dasha_info` 改读 `dasha_sub_periods.current`、婚恋 activation 句按事件类出、`_render_south_chart` 围栏失败不打断报告、health 版本读 `__version__` | 待验收 | `7d3bb0c5`(BUG-609~611) |
|
||||
| `TASK-rectification-occupation-dated-answer-20260911.md` | `PROGRESS-rectification-occupation-dated-answer-20260911.md` | 09-11 真机:职业题答出的年月被 `applyOccupationCollectLedgerNorm` 抹成 unknown 不计分(BUG-649)+ 工具轮缺口句未并入正文(BUG-650) | 已实现 `1fa994ea`,已部署 | `codex/rectification-occupation-dated-answer-20260911` |
|
||||
| `TASK-rectification-probe-pool-exhausted-exit-20260911.md` | `PROGRESS-rectification-probe-pool-exhausted-exit-20260911.md` | 09-11 真机:六道带年月题问完后决策仍判「出区分题」(性格题被当区分题)而持久化无载体 → 「没有拿到下一个问题」(BUG-651/652) | 已实现 `66f63c76` + `f870d3d7`,已部署 | `codex/rectification-probe-pool-exhausted-exit-20260911` |
|
||||
| `TASK-rectification-narrow-before-deliver-20260911.md` | `PROGRESS-rectification-narrow-before-deliver-20260911.md` | 09-11 真机:池空即出卡但范围仍 20 分钟——探针只在引擎跑时按初始簇生成一次、家人题被 0.85 先验丢弃、交付前无定向补事(BUG-653/654,Skill 10.0.24) | 已实现 `6c9a0896`,已部署;验收见修复单 | `codex/rectification-narrow-before-deliver-20260911` |
|
||||
| `TASK-rectification-targeted-collect-persist-20260911.md` | `PROGRESS-rectification-targeted-collect-persist-20260911.md` | 09-11 真机:6c9a0896 后六题答完又无题无卡——定向/锚定/通用采集题 `kind_hint` 违反 `target_kind` CHECK 落不了库、引擎刷新用整网格空转、`targetedCollectExhausted` 按池非空判定成永久等待。执行方只拿到 T0/T3 片段,做成 BUG-655(刷新空题不写库) | 部分实现 `512be9b7`;余项见修复单 | `codex/rectification-targeted-collect-persist-20260911` |
|
||||
| `TASK-rectification-targeted-collect-persist-fix-20260911.md` | `PROGRESS-rectification-targeted-collect-persist-fix-20260911.md` | 修复单:BUG-656 采集焦点 kind 映射到 CHECK 枚举;BUG-657 引擎刷新按 `column_times` 取剩余候选;BUG-658 刷新尝试持久化 + 等待收窄以已问过判定(512be9b7 让无新题时 GET 永远等待且每轮重调引擎) | 待执行 | `codex/rectification-targeted-collect-persist-fix-20260911` |
|
||||
|
||||
## 命名与归档
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# TASK · 带年月题问完不等于收敛:交付前先按剩余候选再出题、再定向补事(2026-09-11)
|
||||
|
||||
- 基线:`origin/staging` @ `9941c34d`(已部署;代码 `f870d3d7`,含 BUG-651/652)
|
||||
- 分支:`codex/rectification-narrow-before-deliver-20260911`,基于 `origin/staging`
|
||||
- 执行方:coding agent;验收:Claude
|
||||
- BUG 编号起点:**BUG-653**(开工时 `grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1` 核对)
|
||||
- 不改确认门、不改 `SCORE_DELTA`、不改 `MIN_ACCEPTANCE_*`;Skill 若改用户可见流程升 10.0.24
|
||||
|
||||
## 1. 事故实证(2026-09-11 staging 真机,代码 f870d3d7)
|
||||
|
||||
同一序列(两件学业 + 实习入职/离职 + 六道带年月选择题 C/C/C/B/A/C)跑完后,系统按 BUG-651 直接出了三列区间卡:范围 04:48–05:07(20 分钟,5 个簇),头名 04:53 只有 29%,第二名 05:06 22%,第三名 04:50 18%。产品负责人原话:「没到出卡的时候就开始出卡了」「范围没缩短多少,这咋能算结束」。
|
||||
|
||||
回执事实:
|
||||
|
||||
| 字段 | 值 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `latest_result.createdAt` | 08:10:54(记完实习那一轮) | 之后六道题都是 TS 侧推理,**引擎没有再跑** |
|
||||
| `inference_state.probes` | 7 道带年月(事业 3、感情 2、迁居 1、学业质量 1)+ 7 道无年月 + 月宿 | 引擎一次性生成,`MAX_PROBES=8`、每域 ≤3、每域每年 1 道 |
|
||||
| 家人 / 财务 / 健康三条线 | 0 道带年月题;`oos_blind_prompts` 列出感情、家人、财务「校时还没用过」 | `("family","existence")` 先验 yes=0.85 > `DOMINANT_ANSWER_PRIOR=0.8`,家人存在题全部被 `dominant_answer_prior` 丢弃;财务无有效换运边界 |
|
||||
| 剩余 5 簇的结构差异 | 04:52 D9 换升;05:00 D10 / D4 / D12 / D24 / D2 同时换升;05:06 D24;05:07 D11 | 能切开 04:48–04:59 与 05:00–05:07 的是事业 / 迁居 / 家人 / 学业 / 财务 这些线上的**带年月**事件,不是性格题 |
|
||||
| 收敛门 | lead = 16 vs 15,top share 29% | 远未收敛;`rounds` 六轮全 informative,无平台期 |
|
||||
|
||||
## 2. 根因
|
||||
|
||||
1. **探针池是静态的**:`event_probes.py` 的区分探针只在引擎跑(账本变化 → `rescoreStaleMinuteSnapshotIfNeeded` → `rescoreMinuteAfterWindowChange`)时按**初始 9 簇**生成一次;选择题答完后 TS 侧只更新候选分数,不会拿**剩余候选**去引擎再生成一批。六道题一问完池就空,BUG-651 于是交付。
|
||||
2. **生成上限与先验把可用题掐掉了**:`MAX_PROBES=8`、`MAX_PROBES_PER_DOMAIN=3`、`_best_probe_per_year` 每域每年只留一道;家人存在题被 0.85 的先验整体丢弃(没有任何家人题曾出现过)。
|
||||
3. **交付前没有定向补事**:重设计单 S1 的锚定追问在训练门开后就停了;S2 池空直接 S3。可是回执里 `oos_blind_prompts` 已经写明感情 / 家人 / 财务三条线没用过,而这三条线正是 05:00 换升边界所需要的。
|
||||
4. **卡片文案把「当前范围」写成「结束」**:「这次给出的范围 04:48–05:07 · 对照了 4 件经历」+ 三个「更像这个」按钮,没有一句「还能再收窄:…」。BUG-651 单 T5 的「再补什么」句在真机卡片上没有出现(待 T0 核实是没落还是被裁)。
|
||||
|
||||
## 3. 决策记录(产品负责人 2026-09-11)
|
||||
|
||||
1. **带年月题池空 ≠ 结束**。池空后先做两步再交付:(a) 拿**剩余活跃候选**去引擎刷新一批带年月探针(保持候选集与已答题,`keepAnswers`);(b) 刷新后仍无题,则按剩余候选的换升层做**定向补事**(只问能切开剩余候选的那几条线,带具体例子,不带推算年份)。用户给了新事 → 重算 → 回到 S2;用户说「没有了 / 就这些」→ S3 交付。
|
||||
2. **交付条件改为**:收敛门开,或(刷新后带年月池空 **且** 定向补事被拒/池空),或用户主动停。BUG-651 的「池空即交付」收窄为「刷新与定向补事都用尽后交付」。「永远给结果」不变:任何时候用户说停都出卡。
|
||||
3. **家人存在题的先验丢弃只对无年月 / 宽窗题生效**:带具体年月的家人换运边界题(`dasha_boundary`,月级)不再因 0.85 先验被整体丢弃;先验只用于排序。
|
||||
4. **卡片文案**:标题「目前范围 04:48–05:07(对照了 4 件经历)」;卡下必有一句「还能再收窄:如果记得 …(来自定向补事池的首条)」;「更像这个」按钮保留。禁用「这次给出」「结束」「最终」。
|
||||
5. 不承诺分钟级:4 件月精度经历本身分辨力有限,本单目标是把「引擎已生成的 8 道题问完」变成「剩余候选能问的都问完、能补的都补完」,不是保证 5 分钟。
|
||||
|
||||
## 4. 硬红线
|
||||
|
||||
- 探针刷新不得改变 `candidate_set_id` / 已答题;重算必须走现有 `rescoreMinuteAfterWindowChange(keepAnswers: true)` 的回放路径(BUG-587 / BUG-594 防复发)。
|
||||
- 刷新时 `asked_probe_keys` 必须带上全部已答键,同域同年去重(BUG-559)照旧。
|
||||
- 定向补事题干不得带出生年推算年份(重设计单 §4);例子只从「能切开剩余候选的层」对应的领域取。
|
||||
- 性格题仍按 BUG-651 决策 2:只作卡后可选,不进本单的刷新池。
|
||||
- 不得静默返回空载体(BUG-652 不变量)。
|
||||
- 既有测试总数不降;改断言写原值 / 新值 / 原因。
|
||||
|
||||
## 5. 任务分解
|
||||
|
||||
- **T0 核实**:回放 §1 序列,打印交付时 `range_delivery` 与 host 正文,确认 BUG-651 T5 的「再补什么」句是否落下;未落则并入 T4 修。
|
||||
- **T1 探针刷新(BUG-653)**:新增 `refreshDiscriminatorProbes(caseId)`:带年月池空且未收敛时,用 `inference_state.candidates[status=active]` 作 `candidate_times`、全部 `answered_probes` 作 `asked_probe_keys` 调引擎的探针生成(`build_discriminating_event_probes` 走 `_remaining_contexts` 分支),把新探针并入 `inference_state.probes`(不动 candidates / rounds / answered)。触发点:`persistNextInterviewIfIdle` 与 `persistNextInterviewAfterChoice` 在 `discriminatorProbe=null` 时先刷新一次再决策;每个 `candidate_set_id` 最多刷新 2 次(写进 `inference_state.refresh_count`)。验收:§1 序列第六题答完后先出现新的带年月题(预期落在家人 / 财务 / 迁居 2015–2026 的换运边界),`rounds` 继续为 informative;刷新不改 `candidate_set_id`。
|
||||
- **T2 引擎侧放开(BUG-653)**:`event_probes.py` (a) 剩余候选 ≤5 时 `MAX_PROBES_PER_DOMAIN` 提到 4、`MAX_PROBES` 提到 12(只在 `candidate_times` 给定的刷新调用生效,首轮不变);(b) `_dominant_existence_prior` 对 `source=dasha_boundary` 且带月份的家人题不丢弃,只降序。验收:`tests/test_candidate_discriminator_contract.py` 新增剩余 5 簇夹具,家人月级边界题出现在 public 里。
|
||||
- **T3 定向补事(BUG-654)**:刷新后仍无带年月题 → `collection-question-pool.ts` 新增 `targetedCollectPool(remainingLayers, evidence, declined)`:按 `LAYER_DOMAIN` 把剩余候选的换升层映射到领域,排除已拒答 / 已覆盖,生成一条带 ≥2 个具体例子的口述题(例如「还有两条线能把 04:50 和 05:06 分开:家里哪年添丁或长辈住院、哪年收入明显变过。记得哪件说哪件,年月大概就行。」),焦点 `collect:targeted:<domain>`;答「没有了」→ 移除并进 S3。验收:§1 序列在刷新池空后先出这条题;答一件家人事 → 引擎重算、回到 S2;答「没有了」→ 出卡。
|
||||
- **T4 交付条件与卡片文案(BUG-654)**:`decideRectification` 的 `!separation.sufficient && !probe` 分支在 `refreshExhausted && targetedCollectExhausted` 之前不得走 `completeWithRange / finish`;卡片标题改「目前范围」,卡下「还能再收窄」句取 `targetedCollectPool` 首条;`agent-voice-copy-contract` 加禁词「这次给出」「最终」。
|
||||
- **T5 记录**:BUG-653/654 入历史(关联 BUG-651、BUG-629、BUG-559、BUG-587);CHANGELOG;PROGRESS;`docs/testing/rectification-scenarios-20260907.md` 加「六道题问完必须先刷新再补事再出卡」。
|
||||
|
||||
## 6. 让步顺序
|
||||
|
||||
T2(a) 的上限放宽可先只做刷新(T1)看真机效果;T2(b)、T3、T4 不可省。
|
||||
|
||||
## 7. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/rectification-narrow-before-deliver-20260911 .worktrees/rectification-narrow-before-deliver-20260911 origin/staging
|
||||
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 652
|
||||
cd frontend && npm test -- tests/rectification-probe-pool-exhausted-20260911.test.ts tests/rectification-replay-20260911.test.ts tests/rectification-probe-replay-loss-20260908.test.ts tests/rectification-collection-question-pool.test.ts
|
||||
cd .. && .venv/bin/python -m pytest tests/test_candidate_discriminator_contract.py -q
|
||||
```
|
||||
|
||||
## 8. 验收口径
|
||||
|
||||
- 前端 `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build` 后 `/` 仍 Static、首屏 gzip ±2%。
|
||||
- Python:`tests/test_candidate_discriminator_contract.py`、`tests/test_event_probes*.py`;`run_quality_gate.py --profile quick`。
|
||||
- 部署后真机:按 §1 序列走,第六题答完不得直接出卡;先出新的带年月题或定向补事题;说「没有了」后出卡,卡片标题为「目前范围」且带「还能再收窄」句。
|
||||
@@ -0,0 +1,77 @@
|
||||
# TASK · 职业题答出的年月不得被抹掉 + 采集重设计(dd8f35f7)用真机序列回放验收(2026-09-11)
|
||||
|
||||
- 基线:`origin/staging` @ `e36bda53`(代码 `dd8f35f7`,Skill 10.0.23,含 BUG-646~648;**尚未部署**,staging `/api/health` 仍是 `fff6da68`)
|
||||
- 分支:`codex/rectification-occupation-dated-answer-20260911`,基于 `origin/staging`
|
||||
- 执行方:coding agent;验收:Claude
|
||||
- BUG 编号起点:**BUG-649**(646~648 已被 dd8f35f7 占用;开工时 `grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1` 核对)
|
||||
- 不改引擎计分、不改确认门、不升 Skill(10.0.23 文案已写「有日期按 D10 + 本命 10 宫辅助评分」,本单是让代码兑现这句话)
|
||||
|
||||
## 1. 事故实证(2026-09-11 staging 真机,Skill 10.0.22 / 代码 52db714b)
|
||||
|
||||
流程实录(脱敏):开场 → 用户给两件学业(2016-09 入学、2020-06 毕业)→ 五道带反推年份的题全答「没有」→ 健康题答出 2024-10 事故 → 职业题被 Agent 写成「大概哪一年开始干这一行」,用户答「2024 年 4 月」→ 助手只回「记下了」,`rectification-set-focus` 失败 `no_pending_question`,无问题、无卡、无缺口句,Case 停在 `collecting_evidence`,区间 04:45–05:15 不动。
|
||||
|
||||
回执证据:
|
||||
|
||||
| 字段 | 值 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `gates.event_quality` | `scoreable_event_count=2 / minimum=3` | 训练门关 |
|
||||
| `gates.domain_diversity` | `domains=["education"]` | 只剩学业 |
|
||||
| `inference_state.events[2024 health]` | `usage=holdout` | 唯一非学业事件被留作 holdout(dd8f35f7 T4 已改成 ≥4 件才留) |
|
||||
| 账本职业行 | `eventKind=occupation_note`、`datePrecision=unknown`、`occurredFrom=2024-04-01` | 用户给的年月被抹成不计分,且精度与日期自相矛盾 |
|
||||
| 最后一轮 `tool_activities` | `record-evidence-batch` methods=[]、`set-focus` failed `no_pending_question` | 第四件事没进引擎,工具轮无出口 |
|
||||
|
||||
## 2. 根因
|
||||
|
||||
1. **职业焦点下带年月的回答被归一成无日期备注**:`frontend/src/lib/rectification-agentic/v9/evidence-model.ts` `applyOccupationCollectLedgerNorm`(BUG-442 P0)对 occupation 采集焦点下任何 `career/occupation` 条目一律改成 `domain=occupation`、`eventKind=occupation_note`、`datePrecision=unknown`、日期置空。`contracts.py` `AUXILIARY_EVENT_KINDS` 与 `isPrimaryScoreableEvidence` 都不计 `occupation_note`,所以这件事永远进不了训练门。
|
||||
2. **`??` 回落**:`frontend/src/mastra/rectification-v9-tools.ts` 两处(batch 约 L1464、propose 约 L1618)写 `occurredFrom: normalized?.occurredFrom ?? item.occurredFrom`,归一后的 `null` 回落成原日期,账本出现 unknown 精度却带日期的行。
|
||||
3. **题干与落库口径不一致**:题干模板 `USER_COLLECT_QUESTION.occupation` 是「你平时主要做什么工作?」,Agent 在真机里改问「哪一年开始干这一行」,用户按要求给了年份,系统丢掉。
|
||||
4. 工具轮无出口、反推年份、holdout 吃掉第三件:已由 dd8f35f7(BUG-646~648)处理,本单只做回放验收(§5 T3),不重复实现。
|
||||
|
||||
## 3. 决策记录(产品负责人 2026-09-11)
|
||||
|
||||
1. **收窄 BUG-442 P0**:occupation 采集焦点下,**无年月**的职业描述仍归一成 `occupation_note`(覆盖判定不依赖模型选对 domain,红线不变);**带年月**的回答在照常写 note 关闭覆盖的同时,**另记一条主评分事件** `domain=career`、`eventKind=career_entry`(模型给出 `career_change / promotion / business_start` 等 career kind 则沿用)、精度按用户所说(年 / 月 / 日)。
|
||||
2. 「不得把 `occupation_note` 算进 3 件」(BUG-356 / BUG-389 / BUG-442 防复发)不变:计入训练门的是 `career_entry`,note 仍不计分。
|
||||
3. 职业题不问年份:generic 层只问「你平时主要做什么工作?」;开始年份的追问走 dd8f35f7 的锚定模板(例如「做程序员是哪年开始的?」),焦点 domain 必须是 `career`,不是 `occupation`。
|
||||
4. 用户的产品口径不变:先等用户说、不用生日推年份、不说「领域」、永远给结果。
|
||||
|
||||
## 4. 硬红线
|
||||
|
||||
- 不得改 `MIN_ACCEPTANCE_EVENTS / MIN_ACCEPTANCE_DOMAINS`、不得开 `confirmation_allowed`、不得改已哈希 Skill 10.0.23。
|
||||
- 归一后的 `null` 就是 `null`:不得再用 `??` 让日期回落。
|
||||
- 覆盖判定仍不得读正文、不得用关键词、不得依赖模型 domain(BUG-442 防复发)。
|
||||
- 既有测试总数不降;改动断言逐条写「原值 / 新值 / 原因」。
|
||||
- Bug 历史、测试、进度记录不得含真实用户资料,例子只用脱敏的领域 + 年月。
|
||||
|
||||
## 5. 任务分解
|
||||
|
||||
- **T1 带年月的职业回答另记 career_entry(BUG-649)**
|
||||
- `applyOccupationCollectLedgerNorm`:输入带 `occurredFrom` 或 `occurredTo` 且精度非 unknown 时,返回两条:note(unknown、日期 null)+ 原条目改为 `domain=career`、`eventKind` 为模型给的 career kind(非 career kind 时用 `career_entry`)。无日期时行为同今天。
|
||||
- `rectification-v9-tools.ts` batch 与 propose 两处改成按归一结果逐条写入,删掉 `?? item.occurredFrom / ?? occurredFrom` 回落。
|
||||
- 验收:焦点 `collect:occupation:*` + 「2024 年 4 月开始做程序员」→ 账本一条 `occupation_note`(unknown、无日期)+ 一条 `career_entry`(month、2024-04-01);`record-evidence-batch` 回执 `methods` 非空(进引擎);回执 `event_quality.scoreable_event_count` +1、`domains` 含 `career`。只答「程序员」→ 只有 note。新增 `frontend/tests/rectification-occupation-dated-answer-20260911.test.ts`;`rectification-occupation-coverage-exit.test.ts` 若改断言写三栏。
|
||||
- **T2 职业题干与锚定焦点 domain**
|
||||
- 检查 `collection-question-pool.ts`:generic 职业题不含「哪年 / 哪一年 / 年份」;「毕业后第一份工作哪年开始」类锚定题的焦点 `targetDomain=career`,不落 `collect:occupation:`,`isOccupationCollectFocus` 对它返回 false。
|
||||
- Agent 系统提示(`agent-run.ts` / `agentic-rectification.ts`)加一句:职业题不得自行追加年份问句;要问年份走锚定题。
|
||||
- 验收:合同测试锁定上述两点。
|
||||
- **T3 用本案序列回放验收 dd8f35f7**(不新增 BUG,除非失败)
|
||||
- 写端到端合同测试 `frontend/tests/rectification-replay-20260911.test.ts`:开场 → 两件学业 → 「没有了」→ 健康 2024-10 → 职业「2024 年 4 月开始做程序员」。断言:不出现带出生年反推的「N 年前后」题干;四件带月事件中留 1 件 holdout、训练集 3 件 2 类、训练门开、`next_action` 为区分选择题(`ask_candidate_discriminator`)且有可渲染 `choice_card`。
|
||||
- 变体:去掉职业年月(只答「程序员」)→ 训练门关 → **工具轮结束时**(`persistNextInterviewIfIdle` 路径,不是点选路径)正文含精确缺口句、例子 ≥2 且来自事业 / 感情 / 家人,无「领域 / 做不了 / 还差 N 件」,客户端状态 `collect_waiting`。若该路径没落缺口句,编 **BUG-650** 修 `agent-run.ts` 工具轮出口并记录。
|
||||
- **T4 记录**:BUG-649(及可能的 650)写入 `docs/BUG_HISTORY.md`;`CHANGELOG.md` 一行;`docs/tasks/PROGRESS-rectification-occupation-dated-answer-20260911.md`;`docs/testing/rectification-scenarios-20260907.md` 加「职业题答年月」一条真人走查。
|
||||
|
||||
## 6. 让步顺序
|
||||
|
||||
T2 的系统提示句可放进度记录待下一单;T1 + T3 不可省。
|
||||
|
||||
## 7. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/rectification-occupation-dated-answer-20260911 .worktrees/rectification-occupation-dated-answer-20260911 origin/staging
|
||||
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 648
|
||||
cd frontend && npm test -- tests/rectification-occupation-coverage-exit.test.ts tests/rectification-collection-question-pool.test.ts tests/rectification-spoken-collect.test.ts tests/rectification-exhaustion-exit-20260906.test.ts
|
||||
```
|
||||
|
||||
## 8. 验收口径
|
||||
|
||||
- 前端 `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build` 后 `/` 仍 Static、首屏 gzip ±2%(worktree 里 Turbopack 拒外部软链时在主检出跑)。
|
||||
- Python:`.venv/bin/python -m pytest tests/test_candidate_discriminator_contract.py`;`run_quality_gate.py --profile quick`。
|
||||
- 部署后:先打开一个绑定 10.0.22 的历史校正(BUG-621),再按本案序列真机走一遍,第四件事说完必须出选择题。
|
||||
@@ -0,0 +1,73 @@
|
||||
# TASK · 带年月选择题问完后必须交付结果,不得停在「没有拿到下一个问题」(2026-09-11)
|
||||
|
||||
- 基线:`origin/staging` @ `1fa994ea`(已部署 staging;Skill 10.0.23;含 BUG-646~650)
|
||||
- 分支:`codex/rectification-probe-pool-exhausted-exit-20260911`,基于 `origin/staging`
|
||||
- 执行方:coding agent;验收:Claude
|
||||
- BUG 编号起点:**BUG-651**(650 已被 1fa994ea 占用;开工时 `grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1` 核对)
|
||||
- 不改引擎计分、不改确认门、不动 `SCORE_DELTA`;Skill 只在改用户可见口径时升 10.0.24(本单预计不用升)
|
||||
|
||||
## 1. 事故实证(2026-09-11 staging 真机,代码 1fa994ea)
|
||||
|
||||
流程(脱敏):开场 → 两件学业(2016-09 / 2020-06)→ 邀请「还有吗」→ 事业两件(2020-04 入职实习、2020-10 离职)→ 训练门开(3 件训练 + 1 件 holdout,2 类)→ 连续六道带年月选择题:事业 2023-05 C、感情 2023-05 C、迁居 2015-05 C、学业 2016 质量 B、事业 2024-04 A、感情 2024-04 C → 助手只回「已记录,范围没变;04:54–04:59 领先,05:06–05:07 落后。」→ 时间轴写「目前范围 04:48–05:07,选择题已问完,再补带年月的经历才会变」→ 界面「没有拿到下一个问题。」。没有结果卡、没有采用入口、没有缺口句。
|
||||
|
||||
回执关键字段(第六题答前快照,`case_revision=6`):
|
||||
|
||||
| 字段 | 值 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| `gates.event_quality / domain_diversity` | 3/3 通过、career+education 通过 | 训练门开 |
|
||||
| `decisionReceipt.acceptance_allowed / selection_allowed / propose_allowed` | 全 true | 引擎已允许交付区间 |
|
||||
| `inference_state.rounds` | 5 轮 informative,无 low_information | 平台期计数为 0,收敛门未开(04:53=14 vs 05:06=13,margin 2.8%) |
|
||||
| `inference_state.probes` 剩余 | `career.2023.dasha_activation`(与已答 2023-05 同域同年,BUG-559 去重)、5 道无年月存在题(`yearless_ungrounded_contrast` 已 drop)、D9/D10 两道 `varga_style` 性格题、月宿边界题(IG 0.01) | 第六题答完后**带年月的区分题池已空** |
|
||||
| `interview.session_outcome`(答后) | `discriminate_candidates`(时间轴据此写「选择题已问完」) | 决策层仍判「继续出区分题」 |
|
||||
| `current_question / choice_card`(答后) | null | 没有落下任何载体 |
|
||||
|
||||
## 2. 根因(按 1fa994ea 源码定位)
|
||||
|
||||
1. **决策层把无年月性格题当成「下一道区分题」**。`rectification-decision.ts` `decideRectification` 在 `!separation.sufficient` 分支里先看 `probe`;`decision-from-dossier.ts` `discriminatorProbeIfFollowupCanAsk` 用 `buildMethodFollowupPlan(sessionOutcome="discriminate_candidates")` 取 `next_followup`;`method-followup.ts` 区分分支在带年月探针为空时走 `firstRenderableYearlessFollowup()`(BUG-629 允许:带年月题空且 ≥2 候选未分开),把 D9 性格题包成 `distinguish_candidates + choice_frame` 返回。于是决策 = `discriminate`(`nextAction=ask_candidate_discriminator`,`sessionOutcome=discriminate_candidates`),而不是重设计单 S3 的「探针池空 → 交付」。
|
||||
2. **持久化层没有把这张卡落下来,也没有任何兜底**。`answer-choice.ts` `persistNextInterviewIfIdle`(约 L1362)显式豁免:`!followup && nextAction !== "ask_candidate_discriminator" && nextAction !== "ask_holdout_validation"` 才走 `persistExhaustionCollect`;否则进 `persistNextInterviewAfterChoice`(L774 起)。后者对 `distinguish_candidates` 的 followup 调 `persistFocusAfterChoice → persistServerOwnedFocus`(`server-focus.ts` L470 起),返回 `skipped / zero_information_gain / probe_already_answered / invalid_choice_schema / duplicate_focus` 任一时,`openQuestionFromPersistedFocus` 为 null,函数最终 `return { hostNarration: spokenFollowupForUser(followup) ?? hostNarrationFallback, choiceReady: false }`——没有焦点、没有卡;`spokenFollowupForUser` 对区分题返回空串时 `??` 不兜底,正文也为空(本案正文确实只有「已记录…」一句)。
|
||||
3. **选择题路径与 GET 路径一致地「有决策、无载体」**:GET 用 `decideFromDossier` 重算同样得到 `discriminate_candidates`,`current_question` 因无活动焦点为 null,客户端 `rectificationQuestionGapState` 走到 `unavailable` → 「没有拿到下一个问题」。BUG-627 的 `ensureNonTerminalTurnExit` 只在点「接着问」时触发,不在答题事务里。
|
||||
4. 具体是哪一个 persist 状态让性格卡没落下来,本单开工时用 §5 T0 的回放测试打印 `persist_status` 定死,不得猜。候选:`expectedAnswerSchemaFor` 在 `state && scoring !== false && !schemaProbeId(stamped)` 时返回 null(`candidate_split_hash` 形如 `04:45-05:15:04:46,…:varga.d9.…`,需与 `inference_state.probes[].candidate_split_hash` 逐字相等才能盖 `probe_id`);`shouldSkipDiscriminatorFollowup`;`already_open`/`duplicate_focus` 撞旧焦点。
|
||||
5. 时间轴文案 `rectification-surface-state.ts` L391「选择题已问完,再补带年月的经历才会变」在没有结果卡时等于告诉用户「做不了」,违反 2026-09-10 口径 3。
|
||||
|
||||
## 3. 决策记录(产品负责人 2026-09-11)
|
||||
|
||||
1. **带年月区分题池空 = 进 S3 交付**(重设计单 §2 S3 的「探针池空」以带年月题为准)。训练门开、引擎 `acceptance_allowed` 为真、带年月探针无可问时,决策必须是 `offer_provisional_range` / `adopt_representative`(按 `capability.canAdopt`),先出区间三列卡 + 一句定性 + 一句「再补什么能收窄」。
|
||||
2. **无年月性格题(D9 / D10 `varga_style`、月宿边界)不得挡在结果前面**。它们仍按 BUG-629 只作 ±1 参考:交付卡之后可作为可选追问(卡下一行「还可以再答两道参考题微调排序」,点了才问),不再作为 `ask_candidate_discriminator` 的唯一探针。BUG-629 防复发「不得把 varga_style / nakshatra_boundary 加回带年月区分池」不变,本单是把它们从「必答」降为「可选」。
|
||||
3. **决策有动作就必须有载体**:任何 `nextAction` 在持久化后既无可渲染焦点又无正文时,同一事务内必须回落到 `persistExhaustionCollect`(门关 → 精确缺口句;门开 → 交付卡)。撤回 `persistNextInterviewIfIdle` 对 `ask_candidate_discriminator / ask_holdout_validation` 的豁免;`persistNextInterviewAfterChoice` 末尾不得返回空正文。
|
||||
4. 时间轴文案:有结果卡时写「选择题已问完,下面是当前范围」;只有训练门关时才写「再补一件带年月的经历」。禁词沿用重设计单 §2 末。
|
||||
|
||||
## 4. 硬红线
|
||||
|
||||
- 不改 `MIN_ACCEPTANCE_EVENTS / MIN_ACCEPTANCE_DOMAINS`、`SCORE_DELTA`、收敛门常量;不开 `confirmation_allowed`。
|
||||
- 不得把性格题重新提权或让它参与淘汰(BUG-629)。
|
||||
- 不得靠客户端「接着问」按钮补救;答题事务本身必须留下载体。
|
||||
- 既有测试总数不降;改断言逐条写原值 / 新值 / 原因。
|
||||
- 记录与测试只用脱敏的领域 + 年月。
|
||||
|
||||
## 5. 任务分解
|
||||
|
||||
- **T0 回放定位**(不编号):用 §1 序列写 `frontend/tests/rectification-probe-pool-exhausted-20260911.test.ts`(可复用 `rectification-replay-20260911.test.ts` 的装置),在第六题答后断言并打印:`decision.nextAction`、`plan.next_followup.semantic_key / choice_kind`、`persistServerOwnedFocus` 返回的 `status`。把结果写进进度记录第一段。
|
||||
- **T1 决策层:带年月池空即交付**(BUG-651):`decideRectification` 的 `!separation.sufficient` 分支里,`probe` 只认带年月探针(`probe_year > 0` 或 `source=event_probe` 且有 period);`discriminatorProbeIfFollowupCanAsk` / `decideAfterInferenceChange` 不再把 `varga_style` / `nakshatra_boundary` 当 `discriminatorProbe` 传入。池空时按现有顺序落到 `completeWithRange(..., "offer")` 或 `finish("adopt_representative", stopReason="probe_pool_exhausted")`。验收:§1 序列第六题答后 `next_user_action.id ∈ {offer_provisional_range, adopt_representative}`,`can_offer_range=true`,快照带 `range_delivery` 三列,正文含一句「再补什么」且无禁词。
|
||||
- **T2 性格题改可选追问**(BUG-651):交付卡渲染后,若 `yearlessPersonalityCanAsk` 为真,卡下给一个「再答两道参考题」入口;点击后才 `set-focus` 性格卡,答完按 BUG-629 ±1 重排并刷新卡,不改 `can_adopt`。验收:不点入口时不出性格卡;点了出 D9 卡,答后 `rounds.kind=tie_break`。
|
||||
- **T3 载体不变量**(BUG-652):`persistNextInterviewIfIdle` 删除对 `ask_candidate_discriminator / ask_holdout_validation` 的豁免;`persistNextInterviewAfterChoice` 在 `open` 不可渲染且 `spokenFollowupForUser` 为空时调 `persistExhaustionCollect`;`persistServerOwnedFocus` 非 `created/already_open` 的状态一律 `console.warn` 一条 `rectification_discriminator_persist_skipped {case_id, status, semantic_key}`。验收:合同测试构造 `persistServerOwnedFocus` 返回 `skipped` 的区分 followup,断言答题事务结束后 host 正文非空且(门开时)`can_offer_range=true`。
|
||||
- **T4 时间轴文案**(BUG-652):`rectificationReadonlyRangeCopy` 按决策分支写:有卡「选择题已问完,下面是当前范围」;门关「再说一件带年月的事就能继续」;其余「还在核对」。`agent-voice-copy-contract` 加禁词「才会变」。
|
||||
- **T5 记录**:BUG-651/652 入 `docs/BUG_HISTORY.md`(651 关联 BUG-629、BUG-442「训练门已开且无可渲染区分卡时必须给用户出路」、BUG-627);`CHANGELOG.md`;`docs/tasks/PROGRESS-rectification-probe-pool-exhausted-exit-20260911.md`;`docs/testing/rectification-scenarios-20260907.md` 加「六道选择题答完必须出卡」一条。
|
||||
|
||||
## 6. 让步顺序
|
||||
|
||||
T2 的入口可先只做「卡后不再问性格题」(即性格题完全不问),入口留下一单;T0 + T1 + T3 不可省。
|
||||
|
||||
## 7. 开工前置命令
|
||||
|
||||
```bash
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/rectification-probe-pool-exhausted-exit-20260911 .worktrees/rectification-probe-pool-exhausted-exit-20260911 origin/staging
|
||||
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | sort -t- -k2 -n | tail -1 # 应为 650
|
||||
cd frontend && npm test -- tests/rectification-replay-20260911.test.ts tests/rectification-yearless-probe-downgrade-20260909.test.ts tests/rectification-exhaustion-exit-20260906.test.ts tests/rectification-decide-next-action.test.ts tests/rectification-server-focus.test.ts
|
||||
```
|
||||
|
||||
## 8. 验收口径
|
||||
|
||||
- 前端 `tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;`next build` 后 `/` 仍 Static、首屏 gzip ±2%。
|
||||
- Python 不动则只跑 `run_quality_gate.py --profile quick`。
|
||||
- 部署后真机:按 §1 序列走一遍,第六题答完必须看到三列区间卡与「再补什么」句,不出现「没有拿到下一个问题」。
|
||||
@@ -0,0 +1,73 @@
|
||||
# TASK · 定向补事题落不了库、探针刷新是空转:六题答完又停在「没有拿到下一个问题」(2026-09-11)
|
||||
|
||||
- 基线:`origin/staging` @ `6c9a0896`(已部署;含 BUG-653/654,Skill 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-655~657 入历史(655 关联 BUG-648、BUG-442「覆盖判定不得依赖模型 domain」;656 关联 BUG-653;657 关联 BUG-651/652/646);CHANGELOG;PROGRESS;`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 序列第六题答完必须看到定向补事题;说「没有了」后看到「目前范围」卡;全程不出现「没有拿到下一个问题」。
|
||||
@@ -0,0 +1,58 @@
|
||||
# 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,补一条)
|
||||
|
||||
5. 「刷新已尝试且无新题」必须是**持久化事实**:写进 `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.ts` `persistCollectFocus` / `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.py` `refresh 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. 开工前置命令
|
||||
|
||||
```bash
|
||||
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` 至多一条)。
|
||||
Reference in New Issue
Block a user