From 9375012f1708a4bc0c5ee837dcdeff8e8c10c626 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Sun, 13 Sep 2026 18:34:23 +0000 Subject: [PATCH] docs(tasks): brief for unstampable probes, naked cards and stale representative time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 探针池空后判别题盖不上 probe,焦点写不进库,上一单 D3 把题干念进正文导致 同一道题反复问且回答不算数(BUG-674,本单 D1 推翻该决策,改走定向补事卡)。 快照已有 choice_card 时 persisted_question 仍只画一行裸题(BUG-675)。同一轮 receipt 的 representative_time 落在 eliminated_ids 内,先取证加守卫(BUG-676)。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu --- .venv | 1 + docs/tasks/README.md | 2 + ...-targeted-collect-spoken-focus-20260913.md | 2 +- ...stampable-probe-and-naked-card-20260914.md | 160 ++++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) create mode 120000 .venv create mode 100644 docs/tasks/TASK-rectification-unstampable-probe-and-naked-card-20260914.md diff --git a/.venv b/.venv new file mode 120000 index 00000000..3ab1ce63 --- /dev/null +++ b/.venv @@ -0,0 +1 @@ +/workspace/Jyotisha/.venv \ No newline at end of file diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 8854d6bb..568b511c 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-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` | | `TASK-rectification-house-lord-gochara-research-20260913.md` | `PROGRESS-rectification-house-lord-gochara-research-20260913.md` | 研究单:宫主触发与木星/土星过运(合冲本命宫主、罗睺紧密合、年精度、用于 block 选上升)四种放宽,20 例公开 AA 离线量 block 层与 minute 层两组指标;引擎里已有宫主/功能吉凶/受控过运,只量缺的四条 | 待验收(无收益,关闭;不立实现单) | `codex/rectification-house-lord-gochara-research-20260913` | diff --git a/docs/tasks/TASK-rectification-targeted-collect-spoken-focus-20260913.md b/docs/tasks/TASK-rectification-targeted-collect-spoken-focus-20260913.md index 2ca05b7c..c21a5275 100644 --- a/docs/tasks/TASK-rectification-targeted-collect-spoken-focus-20260913.md +++ b/docs/tasks/TASK-rectification-targeted-collect-spoken-focus-20260913.md @@ -123,7 +123,7 @@ BUG-671 只拦 `kind === "choice"` 无卡;题号以 `collect:targeted:` 开头 | --- | --- | | D1 | **定向补事题只有点选一种形态。** 写入侧禁止降级:`collection_key` / `question_id` 以 `collect:targeted:` 开头(或 `kind_hint` 以 `targeted:` 开头)时,任何口述兜底都必须先用 `buildTargetedCollectExistenceFrame` 重建 frame 再写点选 schema;重建不出来就**不写焦点**(返回 skipped),不得写采集型 schema。此处推翻"采集意图一律可降级为口述"的既有兜底惯例,执行方不得以"兜底总比没有好"为由保留。 | | D2 | **复原侧按题号识别,不再只认 `targeted_collect` 章。** `method-followup.ts:2231` 的承接分支增加一条:焦点 `intent === "collect_method_evidence"` 且(`question_id` 以 `collect:targeted:` 开头 或 `collect_kind` 以 `targeted:` 开头)且 schema 是采集型(`isCollectFocusSchema`)时,用 `schema.prompt` 当题干重建 frame。这条是**存量 case 的唯一救法**(线上已经存在这样的焦点行,只修写入侧救不回来)。 | -| D3 | **题没落库就必须进旁白。** `persistNextInterviewAfterChoice` 尾部改为显式 `persisted: false`,让 `applyStructuredChoice` 把题干拼进本轮旁白。不改成"让前端再追一轮 Agent"——那会多烧一次模型、多一次揭幕,违反"一次等待一次揭幕"。 | +| D3 | **(已被 `TASK-rectification-unstampable-probe-and-naked-card-20260914.md` 的 D1 推翻:自 2026-09-14 起只适用于采集题,判别题落不了库一律转定向补事卡)** **题没落库就必须进旁白。** `persistNextInterviewAfterChoice` 尾部改为显式 `persisted: false`,让 `applyStructuredChoice` 把题干拼进本轮旁白。不改成"让前端再追一轮 Agent"——那会多烧一次模型、多一次揭幕,违反"一次等待一次揭幕"。 | | D4 | `persistSkippedCollectFocus` 不得留下活动焦点。resolve 失败时必须把这条焦点补置 `skipped`(重试一次)或整体回滚,不允许一条 `status=active` 的口述定向焦点活到下一轮。 | | D5 | 不动数据库结构、不动迁移、不动 `deploy/**` 与 workflow。不放宽 BUG-661(定向题必须逐条点选、四选项固定)、BUG-662(不得写范围两端钟点)、BUG-669、BUG-671 的任何红线。 | diff --git a/docs/tasks/TASK-rectification-unstampable-probe-and-naked-card-20260914.md b/docs/tasks/TASK-rectification-unstampable-probe-and-naked-card-20260914.md new file mode 100644 index 00000000..88750a29 --- /dev/null +++ b/docs/tasks/TASK-rectification-unstampable-probe-and-naked-card-20260914.md @@ -0,0 +1,160 @@ +# TASK · 探针耗尽后题干进正文反复问 + 有卡仍画裸题 + 代表时间取到被淘汰候选 — 2026-09-14 + +- 基线:`origin/staging` @ `8221c612`(staging 已部署该 SHA,含 BUG-673 修复)。 +- 分支:`codex/rectification-unstampable-probe-20260914`,worktree `.worktrees/rectification-unstampable-probe-20260914`。 +- 关联:BUG-673(本单是它修复后暴露出的下一层)、BUG-670、BUG-669、BUG-671、BUG-661、BUG-559。 +- 串行:改 `answer-choice.ts`、`rectification-agentic-chat.tsx`、`candidate-separation.ts` / `build-state.ts` 一线。开工前 `git log --oneline origin/staging -20` 确认无同文件在途分支。 + +## 1. 事故实证(产品负责人 staging 真机,2026-09-14,部署 SHA `8221c612`) + +### P0-1 · 同一道判别题反复问,回答不算数(BUG-674) + +真机记录(产品负责人转述,已脱敏): + +``` +已记录,范围收到 04:48–05:07。 +2023 年 5 月前后,有没有开始一段认真关系、分手或结婚? ← 正文,无 A/B/C/D +(用户打字回答「没有」) +记下了,这方面先跳过。 +2023 年 5 月前后,有没有开始一段认真关系、分手或结婚? ← 同一句又出现一次,仍无卡 +``` + +同一次会话的 decision receipt(同一轮)关键字段: + +| 字段 | 值 | +| --- | --- | +| `prospective_probes` | `[]` | +| `precision_stage.current` | `d9_refine` | +| `oos_blind_prompts` | relationship / family / finance 三条都标「校时还没用过」 | +| `last_inference_round.probe_id` | `probe:relationship.2024.04.dasha_boundary:…`(已答) | + +链路:探针池空 → `stampChoiceSchemaWithProbe`(`inference-adapter.ts:517`)既无 `preferredKey`(精度阶段 followup 不带 `semantic_key`)又无可选探针,返回未盖章的 schema → `expectedAnswerSchemaFor` 末行 + +```ts +return state && stamped.scoring !== false && !schemaProbeId(stamped) ? null : stamped; +``` + +返回 `null` → 焦点状态 `invalid_choice_schema` → `open` 为空 → `persistNextInterviewAfterChoice` 尾部(`answer-choice.ts:1154`)把 `spokenFollowupForUser(followup)` —— 对带 `choice_frame` 的判别题就是卡片题干 —— 当正文返回。 + +`8221c612` 给这个 return 加了 `persisted: false`(上一单 T3),于是题干被拼进本轮旁白。**但焦点仍然不存在**:用户打字回答不会解析任何焦点,计划层下一轮照旧生成同一道 d9_refine 关系题,题干再被念一遍。用户看到的是「同一道题反复问、答了也不算数」。 + +### P0-2 · 快照里已经有卡,界面仍画裸题(BUG-675) + +同一屏底部:`目前范围 04:48–05:07,再说一件带年月的事就能继续` 下面一行裸题 `结过婚或订过婚吗?`(前一次是 `家里添过丁或长辈住过院吗?`),无头像、无选项、**也没有「接着问」修复入口**。 + +没有修复入口 ⇒ `interviewChoiceCardUnavailable` 判定为 false ⇒ BUG-673 的复原已经生效、`current_question.kind === "choice"` 且 `choice_card` 存在。问题在渲染: + +```ts +// frontend/src/components/rectification-agentic-chat.tsx:1579 +questionPersisted: Boolean(currentQuestion?.prompt && questionSource === "focus") && !deadChoice, +// :1868 起 +{questionGap === "persisted_question" && currentQuestion?.prompt && ( + …

{currentQuestion.prompt}

+``` + +`persisted_question` 块只画 `prompt` 一行,**不看 `choiceCard`**;而卡片只在消息内渲染(要求 `latestSettledAssistant.question.focus_id === currentQuestion.focus_id`)。焦点没有 `asked_turn_id`、没挂到任何消息上时,卡片没有渲染位置 → 数据修好了,界面照旧裸题。这是 BUG-669/671 那条线最后一个死角。 + +### P1 · 代表时间取到被淘汰候选(BUG-676,investigating) + +同一份 receipt、同一轮: + +``` +last_inference_round.winner_id = "04:53" +last_inference_round.scores_after = { "04:53": 16, "05:06": 15, "04:50": 14, "05:03": 14, + "04:59": 13, "05:15": 9, "05:08": 7, "05:14": 7, "04:46": 5 } +last_inference_round.eliminated_ids = ["05:14", "05:08", "04:46", "05:15"] +representative_time = "05:14" ← 7 分,且在 eliminated_ids 里 +``` + +界面同屏显示的范围是 `04:48–05:07`,也不含 05:14。 + +两条取值路径:`representative_time` 由 `evaluateCandidateSeparation`(`candidate-separation.ts:81`)取 `ranked[0].time`(按 `score` 降序);`evaluateConvergence`(`convergence-evaluator.ts:34`)则用 `rankActive`,先滤掉 `status === "eliminated"`、再按 `probability` / `posterior_score` 排序。一个被本轮淘汰、分数倒数的候选成为代表时间,只可能是下面之一: + +- 本轮 `eliminated_ids` 没有写回 `candidates[].status`; +- `candidates[].probability` / `posterior_score` 没跟着 `scores_after` 重算(停留在旧轮); +- 送进 `evaluateCandidateSeparation` 的候选行分数与 `scores_after` 不是同一份。 + +**证据缺口**:需要同一份 receipt 的 `inference_state.candidates`(每条的 `id` / `time` / `status` / `probability` / `posterior_score`)与 `inference_state.credible_range`。没有这段之前,本条只能立 `investigating`,不得臆断根因、不得直接改排序。 + +## 2. 根因 + +- BUG-674:焦点写不进库这件事,被上一单的 D3 当成「题目还在,只是没进消息」处理。实际上探针耗尽时这道题**根本不该再问**,正确出口是既有的 `persistExhaustionCollect` → 定向补事卡。把题干念出来既不可点、也不可答、还会每轮重复。 +- BUG-675:`persisted_question` 是纯文本兜底,从设计起就只服务口述题;BUG-673 把定向题的数据形态修成点选后,这个块成了新的裸题来源。 +- BUG-676:待定(见证据缺口)。 + +## 3. 决策记录 + +| 决策 | 内容 | +| --- | --- | +| D1 | **推翻 `TASK-rectification-targeted-collect-spoken-focus-20260913.md` 的 D3。** 原文「题没落库就必须把题干拼进本轮旁白」自本单起只适用于 `intent === "collect_method_evidence"` 的采集题。`intent === "distinguish_candidates"`(含精度阶段 d9/d10/d4 等)落不了库时**一律不得把题干写进正文**,改走 `persistExhaustionCollect`。执行方不得以「上一份任务书这么写」为由保留旧行为。 | +| D2 | 探针耗尽是一个**可见的状态流转**,不是静默降级:转出定向补事卡时按现有文案说清「现在还剩 … 个候选,能把它们分开的是这几条线」,不再让用户对着一句念出来的判别题打字。 | +| D3 | 正文里同一句题干**不得连续两轮出现**。即便将来某条路径仍要念题干,也要带去重守卫。 | +| D4 | `persisted_question` 只负责口述题。`current_question.kind === "choice"` 且有 `choice_card` 时必须渲染可点卡;有卡却画成纯文本视为缺陷。实现优先选「把该题挂到最后一条助手消息上再走既有卡片渲染」,避免第二套卡片渲染路径(§6 红线)。 | +| D5 | BUG-676 本轮**只做取证与守卫**,不动 `evaluateCandidateSeparation` / `rankActive` 的排序逻辑:补一条不变量断言 + 一条结构化日志,把「代表时间落在被淘汰候选或落在 `credible_range` 之外」变成可观测事件。根因确认后另立修复单。 | +| D6 | 不动数据库结构与迁移、不动 `deploy/**` 与 workflow、不动 `page.tsx`、不新增依赖。BUG-661/662/669/670/671/673 的红线全部维持。 | + +## 4. 硬红线 + +1. `./node_modules/.bin/tsc --noEmit` 0 错;`npm run lint` 0 error;`npm test` 失败清单与基线逐条一致(本机基线:`8221c612` 上 27 条,全部是无 Docker / DB / rsync 的既有缺口;若工作树缺 `.venv` 软链会多出 `changed staging workflows are syntactically valid YAML` 一条,补软链后应转绿)。 +2. `next build` 后 `/` 仍 `○ Static`;首屏 gzip ±2%。注意 Turbopack 拒绝软链 `node_modules`,必须在有真实 `node_modules` 的工作树里跑。 +3. 不得新增第二套选择题渲染路径(§6 既有红线:一律 `ChatComposer` / `useConversationScrollAnchor` / 单一卡片渲染)。 +4. 不得用「把裸题藏起来」来收口:藏了以后必须有卡、有修复入口,或有一句诚实的「为什么现在没有下一题」。 +5. 新增断言必须是行为断言。**不得再用 `readFileSync` + 正则匹配源码**来充当验收证据(上一单 T3 的做法,本单不接受)。 + +## 5. 任务分解 + +### 任务 1 · 判别题落不了库不得念题干(BUG-674,P0) + +- `frontend/src/lib/rectification-agentic/v9/answer-choice.ts` `persistNextInterviewAfterChoice` 尾部(l.1154 起): + - `followup.intent === "distinguish_candidates"`(或 `followup.choice_frame` 存在但 `open` 为空)→ 直接 `return persistExhaustionCollect({...})`,不再返回 `hostNarration: spoken`。 + - 仅 `intent === "collect_method_evidence"` 保留「题干进正文」的旧路径。 +- 同文件补去重守卫:本轮要写进正文的题干若与上一条助手消息正文里的题干相同,则丢弃(改为走耗尽分支或不写)。 +- 验收标准: + - 行为单测:构造 `expectedAnswerSchemaFor` 返回 `null`(探针池空)的 d9 精度阶段 followup,断言 `persistNextInterviewAfterChoice` 的返回**不含**该题干,且 `choiceReady === true`(定向补事卡)或 `followup` 为定向补事项。 + - 行为单测:连续两轮同一 followup,第二轮正文不得再出现同一句题干。 + - 行为单测:`applyStructuredChoice` 在该场景下的 `narration` 不含判别题题干,`next_user_action` 不引用未落库的题。 + +### 任务 2 · 有卡不得画裸题(BUG-675,P0) + +- 优先方案:焦点有 `choice_card` 但没有 `asked_turn_id` 时,把该题挂到最后一条已结算助手消息上(复用 `mergeTurnQuestions` / `attachQuestionsToTurns` 的既有通路),让卡片走既有渲染。 +- 兜底方案(仅当上面做不稳):`persisted_question` 分支在 `currentQuestion.kind === "choice" && choiceCard` 时渲染既有卡片组件,不得只画 `

`。 +- 验收标准: + - 行为单测:`current_question.kind === "choice"`、`choice_card` 非空、无消息携带该题 → 界面状态不得是「只有题干」;断言卡片可点(选项 4 个、`focus_id` 匹配)。 + - `rectification-surface-state.test.ts` 补:有卡的持久化选择题不得落在 `persisted_question` 纯文本分支。 + +### 任务 3 · 代表时间一致性取证与守卫(BUG-676,P1,investigating) + +- 在组装 decision receipt 的位置加不变量检查:`representative_time` 必须满足「不在本轮 `eliminated_ids` 内」且「落在 `credible_range` 之内」。违反时 `console.warn(JSON.stringify({ event: "rectification_representative_time_inconsistent", … }))`,带 `winner_id`、`representative_time`、`credible_range`、`eliminated_ids`、`scores_after` 的极值(**不得带出生资料、caseId 之外的身份信息**)。 +- 不改排序逻辑。 +- 在 `docs/BUG_HISTORY.md` 以 `investigating` 立 BUG-676,写明已确认事实与证据缺口(需要 `inference_state.candidates` 与 `credible_range`)。 +- 验收标准:单测覆盖「代表时间在 eliminated 列表里」与「代表时间在 credible_range 外」两种输入各触发一次告警;正常输入不告警。 + +### 任务 4 · 记录 + +- `docs/BUG_HISTORY.md`:BUG-674、BUG-675 写 `resolved`(含验证与防复发),BUG-676 写 `investigating`。BUG-674 的防复发必须写明:**判别题落不了库时不得把题干写进正文;探针耗尽必须转定向补事卡。** +- `CHANGELOG.md` 加 2026-09-14 一行。 +- `docs/tasks/PROGRESS-rectification-unstampable-probe-20260914.md`;真机清单进 `docs/testing/`。 +- 上一单 `TASK-rectification-targeted-collect-spoken-focus-20260913.md` 的 D3 处补一行「已被 TASK-rectification-unstampable-probe-and-naked-card-20260914 的 D1 推翻」。 + +## 6. 让步顺序 + +1. 任务 1、任务 2 必做,二者缺一用户都还会看到「问题反复出现 / 点不动」。 +2. 任务 2 的优先方案(挂到消息上)若在时限内做不稳,退兜底方案(`persisted_question` 里渲染卡片),但不得新增第二套卡片组件。 +3. 任务 3 可只做告警与 `investigating` 记录;不得为了「让数字看起来对」而顺手改排序。 + +## 7. 开工前置命令 + +```bash +git fetch origin --prune +git worktree add -b codex/rectification-unstampable-probe-20260914 \ + .worktrees/rectification-unstampable-probe-20260914 origin/staging +cd .worktrees/rectification-unstampable-probe-20260914 +ln -s /workspace/Jyotisha/.venv .venv # 否则 workflow YAML 合同测试因缺 PyYAML 假红 +cd frontend && npm ci +``` + +按 `AGENTS.md` §5,开工前用「探针」「probe」「invalid_choice_schema」「代表时间」「representative_time」「裸题」检索 `docs/BUG_HISTORY.md`,至少读完 BUG-673、BUG-671、BUG-669、BUG-559 四条。 + +## 8. BUG 编号起点 + +- 起点 **BUG-674**(当前最大号 673,开工时以 `docs/BUG_HISTORY.md` 实际最大号为准)。