Merge origin/staging into consultation family-contract fix.
Independent Staging Quality Gate / validate (push) Successful in 11m16s
Independent Staging Quality Gate / publish (push) Successful in 34m6s

Keep BUG-628 collect-chip history and add BUG-630 natal homepage contract fix.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-09-09 22:49:14 +08:00
co-authored by Cursor
11 changed files with 171 additions and 64 deletions
+20 -4
View File
@@ -9393,8 +9393,8 @@
- 根因:BUG-595 删掉输入框上方「先这样」后没有替代采集快捷回答。产品不要示例骨架条,只要「没有 / 记不清」。
- 修复:composer-wrap 内两个 44px 次要按钮,类名 `rectification-collect-reply`,不复用 `rectification-step-state`。「没有」与打字「没有」同一 POST,走 declined,回执「记下了,这方面先跳过。」「记不清」走 skipped,回执「记下了,这题先放着。」精确这两句不调模型。skipped 领域本会话不再采集;采用后核对仍可碰到 skipped,不碰 declined。
- 验证:`frontend/tests/rectification-spoken-collect.test.ts`(按钮只在 collect_spoken、精确「没有 / 记不清」短路分类器、skipped 不再采集、reverse-verify 仍可碰)。
- 防复发:采集快捷回答不得画成「先这样」,不得用 `rectification-step-state`。不得在回答条预填年份。
- 相关记录:BUG-546
- 防复发:采集快捷回答不得画成「先这样」,不得用 `rectification-step-state`。不得在回答条预填年份。后续产品否决该芯片,见 BUG-628。
- 相关记录:BUG-546、BUG-618、BUG-628
- 复发自:无
- 修复版本:`aa7ccb30`
@@ -9599,8 +9599,8 @@
- 根因:BUG-605 把快捷回答挂在 `composer-wrap` 输入框上方,没有跟选择题卡一样嵌进同一条助手气泡。
- 修复:两个按钮改到问题 `afterAnswer` 里,紧挨题干;composer 不再渲染它们。点选仍走原来的「没有 / 记不清」短路。
- 验证:`frontend/tests/rectification-spoken-collect.test.ts`(按钮在 afterAnswer,不在 composer-wrap)。
- 防复发:采集快捷回答不得回到 composer。选择题卡继续用嵌入卡,口述采集用 `rectification-collect-reply`
- 相关记录:BUG-605
- 防复发:采集快捷回答不得回到 composer。后续产品否决该芯片,见 BUG-628;不得按本条把「没有 / 记不清」按钮加回
- 相关记录:BUG-605、BUG-628
- 复发自:无
- 修复版本:待发布
@@ -9716,6 +9716,22 @@
- 复发自:无
- 修复版本:待发布
## BUG-628 | 口述采集「没有 / 记不清」按钮被用户差评
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:口述采集快捷回答、`rectification-agentic-chat.tsx``globals.css`
- 用户现象:生时校正口述采集题下面挂着「没有」「记不清」,刚进入开场和后续分领域追问都会出现;用户一致觉得像在鼓励关掉采集。
- 触发条件:任意 live `collect_spoken` 题。
- 根因:BUG-605 / BUG-618 把拒答芯片做成默认采集 UI。开场是开放叙述,后续口述题也不是点选题;芯片把「没有」做成最显眼动作。
- 修复:删除 `CollectSpokenReplies` 及其样式。打字「没有」仍走 declined,「记不清」仍走 skipped。选择题卡选项不动。
- 验证:`frontend/tests/rectification-spoken-collect.test.ts`(组件、样式、composer 均无芯片;打字短路仍在)。
- 防复发:口述采集不得再渲染「没有」「记不清」按钮,也不得挂回 composer。
- 相关记录:BUG-605、BUG-618
- 复发自:无
- 修复版本:待发布
## BUG-630 | 初始化后点首页「家庭」报运行合同未完成(runtime_contract_incomplete
- 状态:resolved
+2
View File
@@ -91,6 +91,8 @@
| `TASK-rectification-history-open-20260909.md` | `PROGRESS-rectification-history-open-20260909.md` | 历史生时校正打不开:`open_agentic_rectification_case_v2` 把 Case 绑定 Skill 身份与当前注册版本做相等比较,两天内 10.0.15→10.0.19 五次 bump 让所有旧 Case 被拒;错误码未映射成 500;错误只画在首页卡下 → 表现为点了没反应。决策:session 打开用绑定身份(v1 取 Case → 读身份 → 注册表核验 → v2),不改迁移 | 待验收 | `a31a5e24`BUG-621 |
| `TASK-rectification-window-cluster-cap-20260909.md` | `PROGRESS-rectification-window-cluster-cap-20260909.md` | **P0** 真实用户:一小时窗口分 17 个签名簇,`select_signature_representatives` 按时间取前 12 个,14:40 之后整簇丢弃(本机复现表);区间按代表分钟跨度而非簇覆盖;中途说出真实时段时助手口头答应却未改 → 改为固定回复"范围开始时按资料定、中途不改"(产品否决口头改范围);intake 自定义范围等产品答复 | 待验收 | `codex/rectification-window-cluster-cap-20260909`BUG-623625Skill 10.0.20);走查 `docs/testing/rectification-window-cluster-cap-20260909.md` |
| `TASK-rectification-skipped-health-deadend-20260909.md` | `PROGRESS-rectification-skipped-health-deadend-20260909.md` | 真实用户:健康题「记不清」后职业答完即断(『没有拿到下一个问题』):holdout 的 `declined` 未归并 health/health_pressure,把跳过的健康线再问一次,撞同 id 焦点 `duplicate_focus` 静默;出口闸门把 `exhausted` 当已交付不修复;『重新加载』只重取快照 | 待执行 | `codex/rectification-skipped-health-deadend-20260909`BUG-626627 |
| `TASK-rectification-domain-alias-audit-20260909.md` | `PROGRESS-rectification-domain-alias-audit-20260909.md` | 领域命名审计:健康线 `health`(账本/焦点)与 `health_pressure`(计划/引擎)在十处比较里六处未归并(holdout declined、reverse-verify、conflict probes、probeYearAlreadyCovered、引擎 oos_blind_prompts / _event_years / volunteered);职业线焦点存为 `other` 只靠 questionId 兜底。其余六领域三层同名无问题。决策:TS/Python 各一个归并函数 + 源码合同禁字面量比较 | 待执行 | `codex/rectification-domain-alias-audit-20260909`BUG-628 |
| `TASK-rectification-yearless-probe-downgrade-20260909.md` | `PROGRESS-rectification-yearless-probe-downgrade-20260909.md` | 产品拍板:无年月性格题(D9/D10 风格、月宿边界)降级为平局裁决——带年月题问完且候选仍分不开才问,分值减半(±1)、不计淘汰,报告标「参考」;三列卡性格描述不动;新增离线命中率测量脚本 + 导出 SQL(有出生证用户,聚合);Skill 10.0.21 | 待执行 | `codex/rectification-yearless-probe-downgrade-20260909`BUG-629 |
### 聊天主链路与首页
@@ -0,0 +1,74 @@
# TASK · 领域命名审计:health / health_pressure 与 occupation / other 两组别名在十处比较里各走各的,统一成一处归并(2026-09-09)
- 基线:`origin/staging` @ `4067aff8`
- 分支:`codex/rectification-domain-alias-audit-20260909`,基于 `origin/staging`;与 `codex/rectification-skipped-health-deadend-20260909`BUG-626/627)串行——**本单包含它的决策 1**,若它先合入则本单只补其余站点
- 执行方:coding agent;验收:Claude
- 涉及文件:`frontend/src/lib/rectification-agentic/v9/method-followup.ts``core/candidate-contrast-packet.ts``v9/server-focus.ts``scripts/rectification/event_probes.py``scripts/rectification/refinement_packet.py``scripts/rectification/contracts.py`
- BUG 编号起点:**BUG-628**`docs/BUG_HISTORY.md` 当前最大 BUG-627
- 优先级:**P1**(产品追问"其他领域会不会出现同样的问题":健康线还有六处同类漏洞;职业线一处;其余五个领域全线一致,无问题)
## 1. 审计结论
三套词汇并存:
| 层 | 健康 | 职业 |
| --- | --- | --- |
| 证据账本(`EVIDENCE_DOMAINS`、引擎事件、`contracts.py` | `health``health_pressure` 仅作 v1 兼容) | `occupation` |
| 焦点 / 拒答记录(DB `target_domain` 约束,`persistableFocusDomain` | `health` | **`other`** |
| 计划层、引擎探针 / 分盘层 / 盘外提示、题干与关键词表 | `health_pressure` | `occupation` |
教育、事业、感情、迁居、财务、家人六个领域三层同名,逐站核过,**没有同类问题**。
## 2. 逐站清单(健康)
| # | 位置 | 比较的两边 | 后果 | 现状 |
| --- | --- | --- | --- | --- |
| H1 | `method-followup.ts::holdoutFollowupFor` `!declined.has(item.domain)` | 拒答记录 `health` vs 盘外提示 `health_pressure` | 拒答 / 跳过的健康线被当作"没用过"再问,撞同 id 焦点静默 | BUG-626 |
| H2 | 同函数 `occupied` | 账本 `health` vs 提示 `health_pressure` | 已修(BUG-590 决策 4 | 已修 |
| H3 | `remainingReverseVerifyProbes` L718 `declined.has(probe.domain)` | 拒答 `health` vs 引擎探针 `health_pressure` | 采用后核对仍会问拒答过的健康线 | 未修 |
| H4 | `remainingConflictProbes` L789 同上 | 同上 | 区分题仍会问拒答过的健康线 | 未修 |
| H5 | `probeYearAlreadyCovered` L427 `item.domain !== domain` | 账本 `health` vs 探针 `health_pressure` | 用户已报 2024-10 受伤,仍可能被问"2024 年 10 月前后有没有生病受伤" | 未修 |
| H6 | 引擎 `oos_blind_prompts` `covered` 用事件域 | 事件 `health` vs 目录 `health_pressure` | 有健康事件时仍下发"还没用过健康压力这条线"(真实 Case JSON 已见) | 未修 |
| H7 | 引擎 `_event_years(events, "health_pressure")``_existence_blocked_years` | 同上 | 已报年份不阻断健康探针,出重复 / 引导题 | 未修 |
| H8 | 引擎 `volunteered` 集合(`VOLUNTEER_ONLY``health_pressure` | 事件 `health` 永远不等于 `health_pressure` | 用户主动报了健康事件,D30 层仍被当"未主动提及"永不进入探针顺序——健康线被系统性低用 | 未修 |
| H9 | `candidate-contrast-packet.ts` L237 `item.domain === "health_pressure" \|\| HEALTH_RE.test(summary)` | 用摘要正则补别名 | 能工作,但是靠文案猜;摘要不含关键词就漏 | 改为归并 |
| H10 | `datedCollectDomainBlocked` / `hasConfirmedHealth` / `declinedHealth` | 已归并 | — | 已修 |
## 3. 逐站清单(职业)
| # | 位置 | 后果 | 现状 |
| --- | --- | --- | --- |
| O1 | `declinedDomains` 对职业焦点得到 `other``declined.has("occupation")` 永远为假(L1348 / L1386 / L1957 三处) | 靠 `occupationCollectFocusClosed``questionId` 前缀兜底;L1957 的兜底还额外要求 `careerCovered`——事业未覆盖时拒答职业会被再问 | 部分修 |
| O2 | `declinedDomains` 把职业拒答记成 `other` 塞进集合 | 目前无人查 `declined.has("other")`,无害 | 顺手清理 |
## 4. 决策记录
1. **一个归并函数,两侧各一份,语义相同。** TS`canonicalCollectDomain(raw, questionId?)``health``health_pressure``target_domain === "other"``questionId``collect:occupation:` 开头 → `occupation`;其余原样。放在 `v9/domain-alias.ts``method-followup.ts``candidate-contrast-packet.ts` 全部经它比较;`hasConfirmedHealth` / `declinedHealth` 改为它的薄封装并保留。Python`event_probes.py``canonical_domain(raw)``health``health_pressure`),`_event_years``oos_blind_prompts.covered``volunteered` 三处经它;`contracts.py` 的 v1 兼容项不动。
2. **DB 不改**`target_domain` 约束仍是 `health` / `other`;归并只发生在读取侧。`persistableFocusDomain` 不变。
3. **H8 单独确认产品口径**:健康线本来就是"只有用户主动说才问"(SKILL.md §5),修完归并后主动报过健康事件的用户会开始收到 D30 层探针——这是原设计意图,不是新行为;进度记录写明。
4. 不动门槛、四选项合同、`SCORE_DELTA`Skill 不 bump。
## 5. 任务分解
- 5.1 TS 归并函数 + H1/H3/H4/H5/H9/O1 六站改用;用例——每站一条:账本 `health` + 拒答 `health` + 探针 `health_pressure` → 不再问;职业拒答(topic `other` + `collect:occupation:` id)→ `declined.has("occupation")` 语义成立且事业未覆盖时也不再问职业。
- 5.2 Python 归并 + H6/H7/H8;用例(`tests/test_rectification_event_probes.py`)——事件域 `health` 2024-10 → `oos_blind_prompts` 不含 `health_pressure``_existence_blocked_years("health_pressure")` 含 2024`volunteered``health_pressure`D30 进入顺序。
- 5.3 源码合同:`agent-voice-copy-contract` 或新合同测试锁"`method-followup.ts` 里除归并函数外不得出现字面量 `"health"` 比较"`grep -n '"health"' ` 只允许在 `domain-alias.ts`)。
- 5.4 记录:BUG-628(关联 BUG-590 决策 4、BUG-626、BUG-586 决策 2);`CHANGELOG.md``PROGRESS-…``CONTEXT.md` 术语表补一条"领域别名:health ≡ health_pressureoccupation 在焦点层存为 other"。
## 6. 让步顺序
5.1 与 5.2 必做同一提交;5.3 必做(防第三次复发);5.4 不可省。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-domain-alias-audit-20260909 .worktrees/rectification-domain-alias-audit-20260909 origin/staging
cd .worktrees/rectification-domain-alias-audit-20260909
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
ln -s /workspace/Jyotisha/.venv .venv
grep -n '"health"' frontend/src/lib/rectification-agentic/v9/method-followup.ts frontend/src/lib/rectification-agentic/core/candidate-contrast-packet.ts scripts/rectification/event_probes.py scripts/rectification/refinement_packet.py
cd frontend && ./node_modules/.bin/tsc --noEmit; npm run lint 2>&1 | tail -1
ls tests/rectification-*.test.ts | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)"
cd .. && .venv/bin/python -m pytest tests/test_rectification_event_probes.py tests/test_rectification_v5_services.py -q
```
@@ -0,0 +1,52 @@
# TASK · 无年月的性格题降级为平局裁决:带年月题问完且候选仍分不开时才问,分值减半,永不参与淘汰,报告标「参考」;并加一条离线测量(2026-09-09)
- 基线:`origin/staging` @ `4eea9c10`
- 分支:`codex/rectification-yearless-probe-downgrade-20260909`,基于 `origin/staging`;与 BUG-626~628 无文件重叠(本单不碰 `holdoutFollowupFor` / 别名归并)
- 执行方:coding agent;验收:Claude
- 涉及文件:`frontend/src/lib/rectification-agentic/core/types.ts``SCORE_DELTA``STRONG_CONFLICT_ELIMINATION_COUNT` 不改,新增 `PROBE_WEIGHT`)、`core/apply-probe-outcome.ts`L70 `varga_style` 识别、`directionFor`)、`core/build-state.ts`(淘汰计数)、`v9/method-followup.ts`(L1142 的 ×0.8、无年份题在计划里的位置)、`v9/decision-from-dossier.ts``nakshatraProbeIfFollowupCanAsk``discriminatorProbeIfFollowupCanAsk`)、`v9/skill-verification-report.ts`(技法审计表)、`v9/divergence-panel.ts`(三列卡不动)、`scripts/rectification/varga_style_calibration_report.py`(新,离线测量)、`skills/jyotish-birth-time-rectification/SKILL.md` §5(一句口径)
- BUG 编号起点:**BUG-629**`docs/BUG_HISTORY.md` 当前最大 BUG-628
- 优先级:**P1**(产品 2026-09-09 拍板:无年月性格题的分辨力从未验证,不能与带年月题同权,也不能单独淘汰分钟)
## 1. 现状
| 题型 | 来源 | 现在的地位 |
| --- | --- | --- |
| 婚恋风格(D9 上升星座 → 相处方式三选一) | `varga_style`,需有带年月感情事件锚点(BUG-559),排序分 ×0.8 | 与带年月题同样 ±2,冲突计入淘汰(3 次淘汰) |
| 做事风格(D10) | 同上,需事业事件锚点 | 同上 |
| 月宿边界两组性格 | `nakshatra_boundary` | 同上(只偏置窗口,但分值机制相同) |
| 其他分盘的无年份对照(D24/D12/D11/D7/D2/D4/D5 | `yearless_ungrounded_contrast` | 已丢弃,不问 |
分盘星座是确定计算;"星座 ↔ 性格"与"用户自评 ↔ 类型"两层从未在任何数据上验证。20 例公开集只测带年月事件的打分。真实用户案例里头两道题就是性格题,参与了把真实分钟排除的过程。
## 2. 决策记录(产品同意 2026-09-09
1. **只做平局裁决。** 计划层:`varga_style``nakshatra_boundary` 题只在「可问的带年月区分题(`dasha_boundary / dasha_activation / known_event_quality`)为空」**且**「active 候选 ≥ 2 且未分开」时才成为 `next_followup`;否则记 `dropped_probes(reason="yearless_deferred")`。锚点要求(BUG-559)保留。
2. **分值减半,不淘汰。** 新增 `PROBE_WEIGHT: { dated: 1, yearless: 0.5 }``applyProbeOutcome``varga_style` / `nakshatra_boundary` 的 delta 乘 0.5(±1),且**不递增 `strong_conflict_count`**`STRONG_CONFLICT_ELIMINATION_COUNT = 3` 不变,只有带年月题的 conflict 计数)。`rounds.kind` 对这类题记 `tie_break`
3. **报告与旁白标「参考」。** 技法审计表里 `D9 / D10 类型对照``月宿边界` 的 status 写 `reference`,note"性格自评,只作排序参考,不参与淘汰";点选后旁白维持"已记录,范围…"。Skill §5 加一句:"性格类点选题只在带年月题问完仍分不开时出现,分值减半、不淘汰。"(bump 10.0.21,一句)。
4. **三列对比卡的性格描述不动**:那是"若是这一分钟你会怎样"的展示,不是证据;边界句已在。
5. **离线测量。** 新脚本 `scripts/rectification/varga_style_calibration_report.py`:输入一份由管理员从数据库导出的匿名 JSON(字段:`case_id` 哈希、`birth_time_source`、记录分钟、每道 `varga_style` / `nakshatra` 答案的 `answer_class` 与该题各选项对应星座),只对 `birth_time_source = hospital_record``uncertainty ≤ 2 分钟` 的 Case,计算"用户所选选项的星座 == 记录分钟的分盘星座"的命中率,按 D9 / D10 / 月宿分组,输出 n 与命中率,**不输出任何单人行**。导出 SQL 写在 `docs/operations/varga-style-calibration-export.md`,由产品负责人在有数据库权限的环境跑。命中率 ≈ 1/3(三选一)即随机,届时按产品决定删题。
6. 不动 `SCORE_DELTA` 数值、四选项合同(BUG-390)、采用/确认门、`MIN_SEPARATION_LEAD``_relative_support`
## 3. 任务分解
- 3.1 决策 1:计划层排序与守卫;用例——存在可问的 2023.05 事业题时 D9 题不出;带年月题问完、剩 2 候选 → D9 题出;剩 1 候选 → 不出且 `yearless_deferred` 记录。
- 3.2 决策 2:核心层权重;用例——D9 答 B:命中候选 +1、冲突候选 −1、`strong_conflict_count` 不变;三道性格题全冲突也不淘汰;带年月题行为与今天逐字节相同(`rectification-probe-replay-loss-20260908.test.ts` 不改断言)。三栏:原值"±2、计淘汰"→ 新值"±1、不计"→ 决策 2。
- 3.3 决策 3:报告行 + Skill 10.0.21(快照、registry、`skill-registry.test.ts`)。
- 3.4 决策 5:脚本 + 单元测试(合成 JSON:命中率 0.33 与 0.9 两组)+ 导出 SQL 文档。
- 3.5 记录:BUG-629(关联 BUG-559 锚点、BUG-560 校准、BUG-623 真实用户案例);`CHANGELOG.md``PROGRESS-…``docs/testing/rectification-scenarios-20260907.md` 加"性格题只在带年月题问完后出现;报告里标参考"。
## 4. 让步顺序
3.2 最先(一处改动即止损);3.1 必做;3.3 必做;3.4 可后置为 P2 但导出 SQL 文档不可省;3.5 不可省。
## 5. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-yearless-probe-downgrade-20260909 .worktrees/rectification-yearless-probe-downgrade-20260909 origin/staging
cd .worktrees/rectification-yearless-probe-downgrade-20260909
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
ln -s /workspace/Jyotisha/.venv .venv
cd frontend && ./node_modules/.bin/tsc --noEmit; npm run lint 2>&1 | tail -1
ls tests/rectification-*.test.ts tests/skill-registry.test.ts | grep -v database | xargs npx tsx --test 2>&1 | grep -E "^# (tests|pass|fail)"
```
@@ -168,6 +168,7 @@
- 这一条消息只触发一次候选比较,账本写入三件
- 下一问不再是已经说过的那三个领域
- 只报一件时也只比较一次,下一问是既有逐领域采集,正文没有「还有吗 / 还能想起别的吗」;第一道逐领域题干末尾可以有一次「想到别的也可以一起说」
- 口述采集问题下方有「没有」「记不清」没有「先这样」输入框上方不再挂这两个按钮
- 口述采集有「没有」「记不清」按钮,也没有「先这样」输入框上方不再挂快捷回答
- 想跳过可以打字「没有」或「记不清」
- 每轮助手正文一句复述(可另接一句范围变化);气泡里没有「本轮对照了…」,也没有「很有帮助 / 很有价值 / 很有分量 / 特别有用」