From a5acd535d566a3ea861be8c75bd7d7afa7a1a927 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Mon, 14 Sep 2026 03:47:30 +0000 Subject: [PATCH] docs(tasks): brief for spoken-question orphans and engine representative time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BUG-675 的挂回规则只覆盖选择题,定向补事的年份追问仍掉进 persisted_question 纯文本块,无头像裸行(BUG-678)。同屏两个范围口径:采集文案用活跃候选首尾、 时间轴用 credible_range。BUG-676 依产品提供的 candidates 收敛:前端推理自洽, 05:14 来自引擎自身排序,界面未显示,但它被 case-receipt-projection 算进活跃 时间,已淘汰分钟的宫位表因此进了模型上下文。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu --- docs/tasks/README.md | 2 + ...phan-and-engine-representative-20260914.md | 153 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 docs/tasks/TASK-rectification-spoken-orphan-and-engine-representative-20260914.md diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 0c20c563..935c455f 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -184,6 +184,8 @@ | `TASK-rectification-targeted-card-dead-20260913.md` | `PROGRESS-rectification-targeted-card-dead-20260913.md` | **P0**:定向补事卡在快照投影里拿不到 `choice_card`(承接焦点分支不重建 `choice_frame`),卡片看得见点不动、流程停在采集等待态;模型还会把定向题改写成口述题(BUG-669~671)。先于 tie-break 修复单执行 | 待验收 | `codex/rectification-targeted-card-dead-20260913` | +| `TASK-rectification-spoken-orphan-and-engine-representative-20260914.md` | `PROGRESS-rectification-spoken-orphan-20260914.md` | **P0**:口述题(定向补事年份追问)没有 `askedTurnId` 时仍是无头像裸行——BUG-675 的挂回规则只覆盖选择题(BUG-678);同屏两个范围口径(旁白用活跃候选首尾、时间轴用 credible_range);引擎 `representative_time` 把已淘汰分钟的宫位表带进模型上下文(BUG-676 收敛为 resolved) | 待执行 | `codex/rectification-spoken-orphan-20260914` | + | `TASK-rectification-unstampable-probe-and-naked-card-20260914.md` | `PROGRESS-rectification-unstampable-probe-20260914.md` | **P0**:探针池空后判别题盖不上 probe → 焦点写不进 → 题干被念进正文、同一题反复问且答了不算数(BUG-674,推翻前一单 D3);快照已有 `choice_card` 但 `persisted_question` 仍只画一行裸题(BUG-675);同一轮 receipt 里 `representative_time` 取到 `eliminated_ids` 里的候选(BUG-676,investigating) | 待验收 | `codex/rectification-unstampable-probe-20260914` | | `TASK-rectification-targeted-collect-spoken-focus-20260913.md` | `PROGRESS-rectification-targeted-collect-spoken-focus-20260913.md` | **P0**:定向补事题以采集型 schema 存进焦点,前端只能裸画一行题干(无头像无卡无修复入口),本轮计划的 D9 题被静默吞掉;BUG-670 的识别条件只认 `targeted_collect` 章,复原不了(BUG-673,复发自 BUG-670) | 待验收 | `codex/rectification-targeted-collect-spoken-focus-20260913` | diff --git a/docs/tasks/TASK-rectification-spoken-orphan-and-engine-representative-20260914.md b/docs/tasks/TASK-rectification-spoken-orphan-and-engine-representative-20260914.md new file mode 100644 index 00000000..b39fc582 --- /dev/null +++ b/docs/tasks/TASK-rectification-spoken-orphan-and-engine-representative-20260914.md @@ -0,0 +1,153 @@ +# TASK · 口述题孤儿裸行 + 同屏两个范围口径 + 引擎代表时间进模型上下文 — 2026-09-14 + +- 基线:`origin/staging` @ `d021bca5`(staging 已部署该 SHA,含 `69ffa07d` 的 BUG-674/675 修复与 `8221c612` 的 BUG-673 修复)。 +- 分支:`codex/rectification-spoken-orphan-20260914`,worktree `.worktrees/rectification-spoken-orphan-20260914`。 +- 关联:BUG-675(本单是它的补漏:同一块兜底,只修了选择题)、BUG-673、BUG-671、BUG-669、BUG-662、BUG-676(本单把它从 `investigating` 收敛)。 +- 串行:改 `turn-question.ts`、`case-receipt-projection.ts`、文案一处。与 BUG-674/675 实现分支无重叠文件冲突;开工前 `git log --oneline origin/staging -20` 复核。 + +## 1. 事故实证(产品负责人 staging 真机,2026-09-14,部署 SHA `d021bca5`) + +### P0 · 口述题仍是没有头像的孤儿裸行(BUG-678) + +同一屏(截图)自上而下: + +1. 定向补事卡「结过婚或订过婚吗?」四选一 —— 正常(BUG-673 已生效) +2. 选 B 之后:`记下了,这方面先跳过。现在还剩 04:50–05:06 里 5 个候选,能把它们分开的是这几条线:哪年…` + 下一张定向卡「收入明显变过或有过大笔进出吗?」 —— 正常(BUG-674 已生效,不再重复念判别题题干) +3. 选 A「有过这件事」之后:助手气泡只有 `记下了。`,随后时间轴 `目前范围 04:48–05:07,再说一件带年月的事就能继续`,**再下面一行没有头像的裸题 `大概哪年几月?`** + +根因在 `69ffa07d` 的挂回规则只覆盖选择题: + +```ts +// frontend/src/lib/rectification-agentic/v9/turn-question.ts:104 起 +for (const focus of focuses) { + if (!focus.askedTurnId) { + if (focus.status === "active" && turnQuestionKind(focus) === "choice") { + hanging.push(focus); // ← 只有选择题会挂回最后一条助手消息 + } + continue; + } + byTurn.set(focus.askedTurnId, focus); +} +``` + +定向补事答 A 之后的年份追问题号是 `collect:targeted::year`,按设计就是口述题(`targetedCollectYearFollowup`,`method-followup.ts:1420`),`turnQuestionKind` 不是 `choice` → 不挂回 → 落进 `persisted_question` 兜底块(`rectification-agentic-chat.tsx:1869`),只画一个 `

`:无头像、位置在时间轴下方、与其他所有问题长得都不一样。 + +`persistNextInterviewAfterChoice` 的 `open.kind === "collect_spoken"` 分支(`answer-choice.ts:1070`)返回 `hostNarration = open.prompt` 且 `persisted: true`,而 `applyStructuredChoice` 只在「accepted / skipThisProbe / skippedNextInterview / 定向卡就绪」四种情况下才把它并进旁白 —— 所以旁白只剩 `记下了。`,题目既不在消息里也没挂上消息。这是 BUG-673 当初那条链的最后一段,本单收口。 + +### P1 · 同一屏两个范围口径 + +同屏旁白写「现在还剩 **04:50–05:06** 里 5 个候选」,时间轴写「目前范围 **04:48–05:07**」。两者都不是 bug 计算,但口径不同: + +| 文案 | 来源 | 含义 | +| --- | --- | --- | +| `现在还剩 04:50–05:06 里 5 个候选` | `remainingCandidatesLine`(`collection-question-pool.ts:754`)+ `remainingSplitTimes`(同文件 :561,取活跃候选**代表分钟**的首尾) | 5 个活跃候选分钟的最小/最大值 | +| `目前范围 04:48–05:07` | `rectificationReadonlyRangeCopy`(`rectification-surface-state.ts:380`)读 `credible_range` | 活跃候选**簇范围**的并集 | + +同一份 receipt 实证:活跃候选 04:50 / 04:53 / 04:59 / 05:03 / 05:06,其 `cluster_range` 并集是 04:48–05:07。用户在同一屏看到两个不同的"还剩多少",会以为范围忽大忽小。 + +### P1 · 引擎代表时间把已淘汰分钟塞进模型上下文(BUG-676 收敛) + +同一份 receipt(同一轮)实证: + +``` +inference_state.candidates: 04:53 active probability 0.289 posterior 16 ← 前端推理第一 + 05:06 active probability 0.225 posterior 15 + 05:14 eliminated probability 0 posterior 7 +inference_state.credible_range: ["04:48","05:07"] +receipt.representative_time: "05:14" +``` + +前端推理自洽;`05:14` 来自 Python 引擎自己的排序: + +```python +# scripts/rectification/decision_policy.py:597 +representative = candidate_decisions[0] if candidate_decisions else None +# :688 "representative_time": representative["time"] if representative else None +``` + +引擎按自身事件拟合排整张网格,不吃前端累积的问答。界面侧 `overlayPublicDecision`(`decision-from-dossier.ts:1047`)在 `projection.fromInference` 时用推理值覆盖,所以**用户界面不会显示 05:14**——上一份任务书里「代表时间取错」的说法就此更正,不是取错,是两套代表时间并存未对账。 + +但有一条真实副作用: + +```ts +// frontend/src/lib/rectification-agentic/v9/case-receipt-projection.ts:16 +for (const value of [inference?.representative_time, dossier.latestResult?.representativeTime, dossier.latestResult?.selectedTime]) { + if (typeof value === "string" && value.trim()) activeTimes.add(value.slice(0, 5)); +} +``` + +`latestResult.representativeTime` 就是引擎写进结果行的 05:14,于是**已被淘汰分钟的 `house_tables_by_time["05:14"]` 仍留在送给模型的 receipt 里**,模型有机会照着一个淘汰候选解读。 + +## 2. 根因 + +- BUG-678:`69ffa07d` 修 BUG-675 时把挂回规则限定成 `turnQuestionKind(focus) === "choice"`。口述题没有卡可画,但同样需要头像与消息位置;`persisted_question` 这个纯文本块从此只该服务"确实没有任何消息可挂"的极端情况。 +- 两个范围口径:采集文案用活跃候选分钟首尾,时间轴用 credible_range,两处各写各的。 +- BUG-676:引擎代表时间与前端推理代表时间是两套,界面已覆盖,但 `case-receipt-projection` 的活跃时间集合把引擎值也算了进去。 + +## 3. 决策记录 + +| 决策 | 内容 | +| --- | --- | +| D1 | **挂回规则对口述题一视同仁。** `attachQuestionsToTurns` 的 `hanging` 不再限定 `choice`:任何 `status === "active"`、没有 `askedTurnId` 的焦点都可挂到最后一条尚无题的助手消息上;选择题画卡,口述题画题干,都带头像、都在消息内。`persisted_question` 纯文本块只保留给"一条助手消息都没有"的兜底。 | +| D2 | **同屏只允许一个范围口径。** 采集文案(`remainingCandidatesLine`)改用与时间轴同一个 `credible_range`,候选个数照旧;不得再出现两个不同的起止分钟。若 `credible_range` 缺失才退回活跃候选首尾。此项推翻 `remainingSplitTimes` 作为文案口径的既有做法,但**不改** `remainingSplitTimes` 在排序/去重逻辑里的用途。 | +| D3 | **引擎代表时间不得进活跃时间集合。** `case-receipt-projection` 只用前端推理的活跃候选与 `inference.representative_time`;`latestResult.representativeTime`(引擎值)与 `selectedTime` 中,只有 `selectedTime`(用户已采用的时间)可以保留。BUG-676 由此从 `investigating` 收敛为 `resolved`,根因写「引擎与前端两套代表时间并存,未对账」,不改任何排序逻辑。 | +| D4 | 不动数据库结构与迁移、不动 `deploy/**` 与 workflow、不动 `page.tsx`、不新增依赖。BUG-661/662/669/670/671/673/674/675 的红线全部维持。 | + +## 4. 硬红线 + +1. `tsc --noEmit` 0 错;`npm run lint` 0 error;`npm test` 失败清单与基线逐条一致(本机基线:`8221c612` 上 27 条,全是无 Docker / DB / rsync 缺口;工作树缺 `.venv` 软链会多一条 workflow YAML 假红,补软链即绿);`next build` 通过且 `/` 仍 `○ Static`(Turbopack 拒软链 `node_modules`,须在真实 `node_modules` 的工作树里跑);首屏 gzip ±2%。 +2. 新增断言必须是行为断言,**不得用 `readFileSync` + 正则匹配源码**充当验收。 +3. 不得新增第二套问题渲染路径;卡片一律走既有 `RectificationChoiceCard`。 +4. 不得把引擎的 `representative_time` 显示给用户,也不得反过来用它覆盖前端推理值。 +5. 年份追问仍是口述题,不得为了"统一"把它做成点选卡(BUG-673 决策维持)。 + +## 5. 任务分解 + +### 任务 1 · 口述题也挂回消息(BUG-678,P0) + +- `frontend/src/lib/rectification-agentic/v9/turn-question.ts`:去掉 `turnQuestionKind(focus) === "choice"` 限制;保留「只挂一条」「最后一条助手消息且它还没有题」两个约束。 +- 复核 `rectification-agentic-chat.tsx` 的 `liveQuestionOnMessages` / `questionGap` 在口述题挂回后走 `idle`(题在消息里),不再进 `persisted_question`。 +- 验收标准: + - 行为单测:一个 `kind = collect_spoken`、`askedTurnId = null`、`status = active` 的焦点 + 一条已结算助手消息 → `attachQuestionsToTurns` 把题挂在该消息上;`rectificationQuestionGapState` 返回 `idle`,不是 `persisted_question`。 + - 行为单测:题号 `collect:targeted:finance:year`、题干「大概哪年几月?」这条真实形状必须覆盖。 + - 行为单测:最后一条助手消息**已经**带题时,不得覆盖它(保持现有 `!byTurn.has(lastAssistant.id)` 语义)。 + +### 任务 2 · 同屏一个范围口径(P1) + +- `remainingCandidatesLine` 改为接收 credible range;调用点(`method-followup.ts:1503` 一带、`targetedCollectPool`、`rangeNarrowHint`)同步传入。 +- 文案仍是「现在还剩 X–Y 里 N 个候选,能把它们分开的是这几条线:…」,其中 X–Y 与时间轴 `目前范围` 完全一致。 +- 验收标准:单测——给定活跃候选 04:50/04:53/04:59/05:03/05:06、`credible_range = ["04:48","05:07"]`,文案里的起止必须是 04:48–05:07,候选数仍是 5;`credible_range` 缺失时才退回 04:50–05:06。 + +### 任务 3 · 引擎代表时间不进模型上下文(BUG-676 收敛,P1) + +- `frontend/src/lib/rectification-agentic/v9/case-receipt-projection.ts`:活跃时间集合去掉 `dossier.latestResult?.representativeTime`。 +- 保留 `69ffa07d` 已加的 `warnRepresentativeTimeInconsistency`(`core/representative-time-guard.ts`)。 +- 验收标准:行为单测——`latestResult.representativeTime = "05:14"`、推理里 05:14 为 `eliminated` 时,投影后的 receipt 的 `house_tables_by_time` **不含** `05:14`;`selectedTime` 指向的时间仍保留。 + +### 任务 4 · 记录 + +- `docs/BUG_HISTORY.md`:新增 BUG-678(`resolved`);把 BUG-676 从 `investigating` 更新为 `resolved`,根因改写为「引擎与前端两套代表时间并存未对账,引擎值被算进活跃时间集合」,并写明**界面从未显示过 05:14**(原记录里若有"代表时间取错"的措辞,按事实更正,不得留下未经证实的结论)。 +- `CHANGELOG.md` 加一行;`PROGRESS-rectification-spoken-orphan-20260914.md`;真机清单进 `docs/testing/`(要覆盖:定向补事答 A 之后的年份追问带头像出现在消息里、同屏两处范围数字一致)。 + +## 6. 让步顺序 + +1. 任务 1 必做(用户可见的"UI 不一样"就剩这一处)。 +2. 任务 3 必做(模型上下文正确性)。 +3. 任务 2 若牵动的调用点过多,可只改定向补事那一条文案路径,其余写进进度记录;但不得留下同屏两个数字。 + +## 7. 开工前置命令 + +```bash +git fetch origin --prune +git worktree add -b codex/rectification-spoken-orphan-20260914 \ + .worktrees/rectification-spoken-orphan-20260914 origin/staging +cd .worktrees/rectification-spoken-orphan-20260914 +ln -s /workspace/Jyotisha/.venv .venv # 否则 workflow YAML 合同测试因缺 PyYAML 假红 +cd frontend && npm ci +``` + +按 `AGENTS.md` §5,开工前用「裸题」「persisted_question」「askedTurnId」「代表时间」「representative_time」检索 `docs/BUG_HISTORY.md`,至少读完 BUG-675、BUG-674、BUG-673、BUG-671、BUG-676 五条。 + +## 8. BUG 编号起点 + +- 起点 **BUG-678**(当前最大号 677,开工时以 `docs/BUG_HISTORY.md` 实际最大号为准)。