Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
78 lines
8.5 KiB
Markdown
78 lines
8.5 KiB
Markdown
# 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),再按本案序列真机走一遍,第四件事说完必须出选择题。
|