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
+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)"
```