Files
Jyotisha/docs/tasks/TASK-rectification-latent-audit-20260910.md
T

103 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK · 生时校正顺带审计:同年去重误杀发挥质量题、双轨冲突按分钟误判、不可分宽度少算、两套代表分钟(2026-09-10)
- 基线:`origin/staging` @ `a15fc3ef`(代码与 `d96b24c2` 相同;staging 已部署 `d96b24c2`
- 分支:`codex/rectification-latent-audit-20260910`,基于 `origin/staging`
- 串行:与 `TASK-rectification-unwritten-evidence-claim-20260910.md`BUG-635/636**不改同一文件**,可并行;本单 BUG 编号从 **BUG-637** 起,开工时核对 `docs/BUG_HISTORY.md` 最大号(若 635/636 尚未写入,仍从 637 起并在进度记录说明)。
- 执行方:coding agent;验收:Claude
- 来源:同一份真实 Case JSONSkill 10.0.212026-09-10)里几处互相对不上的数字,逐一回到代码核实。只写结构,不写用户资料。
- 不改 Skill 版本、不改计分 `SCORE_DELTA`、不改四选项合同、不打开确认门。
## 0. 结论总表
| 编号 | 级别 | 一句话 | 证据(Case JSON |
| --- | --- | --- | --- |
| BUG-637 | P2 | 「已记年份的发挥质量题」永远被同年去重丢掉,BUG-389 的修复被 BUG-592 的硬排除覆盖 | `dropped_probes``education.2016.known_event_quality` reason=`same_year_asked`IG 0.8,而 `answered_probes` 里没有任何 education 题 |
| BUG-638 | P2 | Python 双轨一致性按 31 个网格分钟逐分比较,同一簇内两个峰值就报「冲突」并降置信度;TS 报告另算一套说「只作观察」 | `dasha_agreement.status=conflict``vimshottari_top=04:54``narayana_top=04:59`,两者同在簇 `04:5404:59``reasons``vimshottari_narayana_conflict``verification_markdown` 写「双轨…只作观察」 |
| BUG-639 | P3 | 引擎「不可分宽度」用簇代表分钟算跨度,少算两端簇的延伸;它是确认门 `adjacent_passed` 的输入 | `indistinguishable_width_minutes=29`04:47→05:15),`credible_range=04:4505:15`,报告写「不可分宽度:31 分钟」 |
| BUG-640 | P3 | 引擎回执与推断层各有一个代表分钟;本例两者正好跨 D24 换升,回执里「已按该分钟重算」的宫位表描述的不是卡片上的那一分钟 | 回执 `representative_time=04:54``house_table.time=04:54``natal_recast.time=04:54``representative_candidate_id→04:54``latest_result.representativeTime=04:53``range_delivery.representative_time=04:53``window_scan` D24 在 04:54 换升 |
另有 8 条观察项(§5),是设计口径问题,不算 Bug,留给产品决定。
## 1. BUG-637P2):同年去重把 `known_event_quality` 一起杀了
### 实证与根因
- BUG-389(已解决)的口径:用户已记下某年学业事件后,**针对这件已知事件**出「如愿 / 将就 / 失常」的 `event_quality` 点选卡,不要求采用门。
- BUG-592(09-08)加了同域同年硬排除:`sameYearProbeAsked(askedKeys, domain, year)``probe-question-contract.ts` L320)对 `askedKeys` 里任何 `<domain>.<year>` 前缀匹配就丢题。
- `askedKeys` 来自 `askedDiscriminatorKeys(receipt, evidence)``inference-adapter.ts` L82= 回执里已答探针键 **+** `askedEventProbeKeysFromLedgerEvidence(evidence)``core/candidate-contrast-packet.ts` L242)。后者把账本里每件事件变成 `domain.year` 键。
- `known_event_quality` 的语义键形如 `education.2016.known_event_quality`,它**必然**与账本里那件 2016 学业事件同域同年——这正是它存在的前提。于是 `method-followup.ts` L881 / L957 与 `candidate-contrast-packet.ts` L480 三处都把它判成 `same_year_asked`。BUG-389 的路径自 BUG-592 起形同虚设,但 BUG-389 的测试没有覆盖「账本已有同年事件」这一输入,所以没红。
### 决策
1. 账本派生的 `domain.year` 键只用于拦 **存在性探针**`dasha_boundary` / `dasha_activation`,即「那年有没有发生 X」——用户已经说了)。`known_event_quality``choice_kind=event_quality``source=known_event_quality`)不受账本派生键约束,仍受回执已答键约束(同一道不问两遍)。
2. 不改 `SCORE_DELTA`、不改 BUG-592 对存在性探针的硬排除。
### 任务与验收
- 在三处判定点按 `source` / `choice_kind` 分流,或给 `sameYearProbeAsked``ledgerKeys``askedKeys` 两个参数,`known_event_quality` 只查后者。
- 测试:账本有 2016 学业事件 + 回执含 `education.2016.known_event_quality`(IG 0.8)→ 进入候选题且可渲染为 `event_quality` 卡;同输入下 `education.2016.09.dasha_boundary` 仍被 `same_year_asked` 丢;已答过 `education.2016.known_event_quality` 后再次出现 → 丢(reason 不变)。补到 BUG-389 与 BUG-592 各自的测试文件,并在 Bug 历史里把 BUG-389 标「复发自 BUG-592」。
## 2. BUG-638(P2):双轨一致性在分钟粒度上判冲突,且与报告的说法不一致
### 实证与根因
- `refinement_packet.py` `dasha_agreement(built, candidate_times)` L383:对 `candidate_times` 逐分钟累加 Vimshottari / Narayana 两轨分数,取各自 argmax;**分钟不相等即 `conflict`**。`decision_policy.py` L591 传入的是 `built["candidate_times"]`,即整窗 31 个网格分钟。
- 本例两轨峰值 04:54 与 04:59 同属一个簇(`cluster_range` 04:54–04:59),对用户而言是同一个候选,却被判「主限更偏向 04:54,分盘大运更偏向 04:59。冲突时不能按更高把握收口」;`decision_policy.py` L610 据此把 `overall_confidence` 降一级,并把 `vimshottari_narayana_conflict` 写进 `reasons``confirmation_reasons`
- TS 侧 `dashaAgreementAmongActive``refinement-packet.ts` L479BUG-593)只在推断层 active 候选上重算,报告因此写「只作观察」(partial)。同一个问题两套答案,门禁用的是错的那套。BUG-593 记录里说引擎原值改名 `dasha_agreement_pre_inference` 供审计——代码里**不存在**这个标识符,该条记录与实现不一致。
### 决策
1. Python 侧按簇比较:两轨各自取 argmax 后映射到所在簇(`cluster_startcluster_end`),同簇即 `agree`;或把比较对象换成簇代表分钟列表(L600 的 `candidate_decisions` 时间)。两种选一种写进进度记录。
2. `reasons` / `confirmation_reasons` 里的 `vimshottari_narayana_conflict` 与置信度降级必须与报告使用的同一判定同源;回执保留引擎原值时字段名用 `dasha_agreement_pre_inference`,把 BUG-593 的记录补正为实际实现。
3. 不放宽确认门:`agree` 不会让任何门从关到开(确认门仍被其它原因关闭),只是不再凭假冲突降级。
### 任务与验收
- `tests/test_rectification_decision_policy*.py``refinement_packet` 定向测试:构造两轨峰值落在同簇不同分钟 → `status=agree`;落在不同簇 → `conflict`;只有一轨有分 → `partial`
- TS 合同测试:回执 `dasha_agreement.status``skill_verification_report` 的双轨句一致(用 golden 回执,不手造形状)。
- `docs/BUG_HISTORY.md` BUG-593 补正实现描述。
## 3. BUG-639(P3):不可分宽度按簇代表分钟算
- `decision_policy.py` L154 `indistinguishable_width_minutes``span = max(rep) - min(rep) + 1`,rep 是各簇代表分钟。本例 04:47 与 05:15 → 29;真实候选集是 04:4505:15 → 31。BUG-593 已让**报告**改用 `credible_range`,但引擎这个值仍进 `adjacent_passed = unique_top and width <= MAX_CONFIRMATION_WIDTH_MINUTES(5)`。两簇 04:5004:55(代表 04:53)与 04:5605:02(代表 04:59)会被算成 7 而真实是 13;极端情形代表分钟相邻、簇各延伸数分钟时,可能把 ≤5 的门误开。
- 决策:改为 `max(cluster_end) - min(cluster_start) + 1``tied_minute_count` 逻辑不动。
- 验收:Python 定向测试覆盖上述两簇例子;回执 `indistinguishable_width_minutes` 与报告 `width_minutes` 在无推断层时相等。
## 4. BUG-640(P3):两套代表分钟,回执事实描述的不是卡片那一分钟
- 引擎回执的 `representative_time` / `representative_candidate_id` / `house_table` / `natal_recast` / `technique_audit_table`「已按该分钟重算」来自 Python 先验排序(本例 04:54,簇 04:5404:59);推断层 `inference_state.representative_time``latest_result.representativeTime``range_delivery.representative_time``next_user_action.representative_time``turn-decision.ts` L191)来自后验排序(本例 04:53)。采用、卡片、旁白走后者;模型读到的回执事实走前者。
- 本例两者正好隔着 D24 04:54 换升(04:53 金牛 / 04:54 双子),`natal_recast.user_meaning`「本命宫位已按 04:54 重算」与卡片首列 04:53 不是同一张盘。本次上升都是巨蟹所以肉眼看不出;若代表分钟跨 D1 或 D9 换升,报告里的「本命上升(该分钟)」就会写错。
- 决策:有推断层时,回执投影把 `representative_time` / `house_table` / `natal_recast` 按推断代表分钟重取(`house_tables_by_time` 已按候选分钟给了表,直接选用即可),引擎原值改名 `*_pre_inference``technique_audit_table` 的「该分钟」措辞跟随。不改引擎。
- 验收:golden 回执测试:推断代表分钟 ≠ 引擎代表分钟时,投影后 `house_table.time === latest_result.representativeTime`,且 `natal_recast.user_meaning` 里的分钟与卡片一致。
## 5. 观察项(不算 Bug,产品决定是否立单)
1. **Skill 文案与服务器行为不一致**Skill 10.0.21 写「财务与健康只有用户主动说才问」,服务器自 BUG-462 起在采集耗尽轮转里主动问财务(`DATED_COLLECT_ORDER` 含 finance)。模型两边都读,可能是 BUG-635 里它不肯记财务事件的一个诱因。下次升 Skill 版本时对齐。
2. **家人带年探针被无年采集题吞掉**`probe:family.2021` 按 BUG-539 的决策去掉年份后成了普通家人采集题;用户答「不记得了」→ 家人线标 skipped → 那道原本能分候选的 2021 年家人点选题再也不会问。可考虑「无年采集被跳过后,带年家人探针仍可作点选卡」。
3. **`precision_stage` 两个口径**:回执 `d9_refine`(文案「可再补一件感情变化」)与 `interview.precision_stage=collect_events` 同时存在,模型读到的是前者。
4. **「记不清」只匹配原字**`isCollectSkipUtterance` 只认精确的「记不清」,「不记得了 / 记不得 / 忘了」走 LLM 分类器,而采集题的分类器只有 `no` 没有 skip 类。本例靠 Agent 自己调 resolve-focus 兜住了,属软肋。
5. **步骤计数倒退**:点选题问完回到采集,顶部从「第 2 步·区分候选」退回「第 1 步·收集经历」,用户会以为流程回滚。
6. **事件吻合率不区分候选**:9 个候选的「4 件经历里 4 件对得上」完全相同,却是卡片最醒目的一行;它描述拟合不描述区分,考虑弱化或改成相对表述。
7. **VedAstro 分钟级校验每轮超时**`vedastro_event_validation.failure.code=timeout``external_validation_not_passed` 长期在 reasons 里。已知环境缺口,不在本单。
8. **run diagnostic 不落库**:模型为什么跳过工具,事后只能查容器日志(BUG-633 已记)。
## 6. 让步顺序
BUG-640 → BUG-639 → 可延后;BUG-637 与 BUG-638 是本单最低交付。观察项不做。
## 7. 开工前置命令
```bash
git fetch origin --prune
git worktree add -b codex/rectification-latent-audit-20260910 .worktrees/rectification-latent-audit-20260910 origin/staging
grep -o "^## BUG-[0-9]*" docs/BUG_HISTORY.md | tail -1 # 核对编号起点
.venv/bin/python -m pytest tests -k "decision_policy or refinement_packet" -q # 记基线
cd frontend && npm test -- tests/rectification-method-followup*.test.ts tests/rectification-delivery-report-facts.test.ts # 记基线
```
## 8. 验收口径
- Python:定向测试 fail=0`run_quality_gate.py --profile quick` 通过。
- 前端:`tsc --noEmit` 0 错、`npm run lint` 0 error、相关套件 fail=0、测试总数 ≥ 基线;合同测试 fixture 来自真实引擎回执。
- 推 staging 后 `/api/health``deployment.gitCommit` 等于最新含代码改动的提交。