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

12 KiB
Raw Blame History

TASK · 生时校正顺带审计:同年去重误杀发挥质量题、双轨冲突按分钟误判、不可分宽度少算、两套代表分钟(2026-09-10)

  • 基线:origin/staging @ a15fc3ef(代码与 d96b24c2 相同;staging 已部署 d96b24c2
  • 分支:codex/rectification-latent-audit-20260910,基于 origin/staging
  • 串行:与 TASK-rectification-unwritten-evidence-claim-20260910.mdBUG-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_probeseducation.2016.known_event_quality reason=same_year_askedIG 0.8,而 answered_probes 里没有任何 education 题
BUG-638 P2 Python 双轨一致性按 31 个网格分钟逐分比较,同一簇内两个峰值就报「冲突」并降置信度;TS 报告另算一套说「只作观察」 dasha_agreement.status=conflictvimshottari_top=04:54narayana_top=04:59,两者同在簇 04:5404:59reasonsvimshottari_narayana_conflictverification_markdown 写「双轨…只作观察」
BUG-639 P3 引擎「不可分宽度」用簇代表分钟算跨度,少算两端簇的延伸;它是确认门 adjacent_passed 的输入 indistinguishable_width_minutes=2904:47→05:15),credible_range=04:4505:15,报告写「不可分宽度:31 分钟」
BUG-640 P3 引擎回执与推断层各有一个代表分钟;本例两者正好跨 D24 换升,回执里「已按该分钟重算」的宫位表描述的不是卡片上的那一分钟 回执 representative_time=04:54house_table.time=04:54natal_recast.time=04:54representative_candidate_id→04:54latest_result.representativeTime=04:53range_delivery.representative_time=04:53window_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_qualitychoice_kind=event_qualitysource=known_event_quality)不受账本派生键约束,仍受回执已答键约束(同一道不问两遍)。
  2. 不改 SCORE_DELTA、不改 BUG-592 对存在性探针的硬排除。

任务与验收

  • 在三处判定点按 source / choice_kind 分流,或给 sameYearProbeAskedledgerKeysaskedKeys 两个参数,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分钟不相等即 conflictdecision_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 写进 reasonsconfirmation_reasons
  • TS 侧 dashaAgreementAmongActiverefinement-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*.pyrefinement_packet 定向测试:构造两轨峰值落在同簇不同分钟 → status=agree;落在不同簇 → conflict;只有一轨有分 → partial
  • TS 合同测试:回执 dasha_agreement.statusskill_verification_report 的双轨句一致(用 golden 回执,不手造形状)。
  • docs/BUG_HISTORY.md BUG-593 补正实现描述。

3. BUG-639(P3):不可分宽度按簇代表分钟算

  • decision_policy.py L154 indistinguishable_width_minutesspan = 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) + 1tied_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_timelatest_result.representativeTimerange_delivery.representative_timenext_user_action.representative_timeturn-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_inferencetechnique_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=timeoutexternal_validation_not_passed 长期在 reasons 里。已知环境缺口,不在本单。
  8. run diagnostic 不落库:模型为什么跳过工具,事后只能查容器日志(BUG-633 已记)。

6. 让步顺序

BUG-640 → BUG-639 → 可延后;BUG-637 与 BUG-638 是本单最低交付。观察项不做。

7. 开工前置命令

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=0run_quality_gate.py --profile quick 通过。
  • 前端:tsc --noEmit 0 错、npm run lint 0 error、相关套件 fail=0、测试总数 ≥ 基线;合同测试 fixture 来自真实引擎回执。
  • 推 staging 后 /api/healthdeployment.gitCommit 等于最新含代码改动的提交。