Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
12 KiB
12 KiB
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 JSON(Skill 10.0.21,2026-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:54–04:59;reasons 含 vimshottari_narayana_conflict;verification_markdown 写「双轨…只作观察」 |
| BUG-639 | P3 | 引擎「不可分宽度」用簇代表分钟算跨度,少算两端簇的延伸;它是确认门 adjacent_passed 的输入 |
indistinguishable_width_minutes=29(04:47→05:15),credible_range=04:45–05: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-637(P2):同年去重把 known_event_quality 一起杀了
实证与根因
- BUG-389(已解决)的口径:用户已记下某年学业事件后,针对这件已知事件出「如愿 / 将就 / 失常」的
event_quality点选卡,不要求采用门。 - BUG-592(09-08)加了同域同年硬排除:
sameYearProbeAsked(askedKeys, domain, year)(probe-question-contract.tsL320)对askedKeys里任何<domain>.<year>前缀匹配就丢题。 askedKeys来自askedDiscriminatorKeys(receipt, evidence)(inference-adapter.tsL82)= 回执里已答探针键 +askedEventProbeKeysFromLedgerEvidence(evidence)(core/candidate-contrast-packet.tsL242)。后者把账本里每件事件变成domain.year键。known_event_quality的语义键形如education.2016.known_event_quality,它必然与账本里那件 2016 学业事件同域同年——这正是它存在的前提。于是method-followup.tsL881 / L957 与candidate-contrast-packet.tsL480 三处都把它判成same_year_asked。BUG-389 的路径自 BUG-592 起形同虚设,但 BUG-389 的测试没有覆盖「账本已有同年事件」这一输入,所以没红。
决策
- 账本派生的
domain.year键只用于拦 存在性探针(dasha_boundary/dasha_activation,即「那年有没有发生 X」——用户已经说了)。known_event_quality(choice_kind=event_quality、source=known_event_quality)不受账本派生键约束,仍受回执已答键约束(同一道不问两遍)。 - 不改
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.pydasha_agreement(built, candidate_times)L383:对candidate_times逐分钟累加 Vimshottari / Narayana 两轨分数,取各自 argmax;分钟不相等即conflict。decision_policy.pyL591 传入的是built["candidate_times"],即整窗 31 个网格分钟。- 本例两轨峰值 04:54 与 04:59 同属一个簇(
cluster_range04:54–04:59),对用户而言是同一个候选,却被判「主限更偏向 04:54,分盘大运更偏向 04:59。冲突时不能按更高把握收口」;decision_policy.pyL610 据此把overall_confidence降一级,并把vimshottari_narayana_conflict写进reasons与confirmation_reasons。 - TS 侧
dashaAgreementAmongActive(refinement-packet.tsL479,BUG-593)只在推断层 active 候选上重算,报告因此写「只作观察」(partial)。同一个问题两套答案,门禁用的是错的那套。BUG-593 记录里说引擎原值改名dasha_agreement_pre_inference供审计——代码里不存在这个标识符,该条记录与实现不一致。
决策
- Python 侧按簇比较:两轨各自取 argmax 后映射到所在簇(
cluster_start–cluster_end),同簇即agree;或把比较对象换成簇代表分钟列表(L600 的candidate_decisions时间)。两种选一种写进进度记录。 reasons/confirmation_reasons里的vimshottari_narayana_conflict与置信度降级必须与报告使用的同一判定同源;回执保留引擎原值时字段名用dasha_agreement_pre_inference,把 BUG-593 的记录补正为实际实现。- 不放宽确认门:
agree不会让任何门从关到开(确认门仍被其它原因关闭),只是不再凭假冲突降级。
任务与验收
tests/test_rectification_decision_policy*.py或refinement_packet定向测试:构造两轨峰值落在同簇不同分钟 →status=agree;落在不同簇 →conflict;只有一轨有分 →partial。- TS 合同测试:回执
dasha_agreement.status与skill_verification_report的双轨句一致(用 golden 回执,不手造形状)。 docs/BUG_HISTORY.mdBUG-593 补正实现描述。
3. BUG-639(P3):不可分宽度按簇代表分钟算
decision_policy.pyL154indistinguishable_width_minutes:span = max(rep) - min(rep) + 1,rep 是各簇代表分钟。本例 04:47 与 05:15 → 29;真实候选集是 04:45–05:15 → 31。BUG-593 已让报告改用credible_range,但引擎这个值仍进adjacent_passed = unique_top and width <= MAX_CONFIRMATION_WIDTH_MINUTES(5)。两簇 04:50–04:55(代表 04:53)与 04:56–05: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:54–04:59);推断层inference_state.representative_time、latest_result.representativeTime、range_delivery.representative_time、next_user_action.representative_time(turn-decision.tsL191)来自后验排序(本例 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,产品决定是否立单)
- Skill 文案与服务器行为不一致:Skill 10.0.21 写「财务与健康只有用户主动说才问」,服务器自 BUG-462 起在采集耗尽轮转里主动问财务(
DATED_COLLECT_ORDER含 finance)。模型两边都读,可能是 BUG-635 里它不肯记财务事件的一个诱因。下次升 Skill 版本时对齐。 - 家人带年探针被无年采集题吞掉:
probe:family.2021按 BUG-539 的决策去掉年份后成了普通家人采集题;用户答「不记得了」→ 家人线标 skipped → 那道原本能分候选的 2021 年家人点选题再也不会问。可考虑「无年采集被跳过后,带年家人探针仍可作点选卡」。 precision_stage两个口径:回执d9_refine(文案「可再补一件感情变化」)与interview.precision_stage=collect_events同时存在,模型读到的是前者。- 「记不清」只匹配原字:
isCollectSkipUtterance只认精确的「记不清」,「不记得了 / 记不得 / 忘了」走 LLM 分类器,而采集题的分类器只有no没有 skip 类。本例靠 Agent 自己调 resolve-focus 兜住了,属软肋。 - 步骤计数倒退:点选题问完回到采集,顶部从「第 2 步·区分候选」退回「第 1 步·收集经历」,用户会以为流程回滚。
- 事件吻合率不区分候选:9 个候选的「4 件经历里 4 件对得上」完全相同,却是卡片最醒目的一行;它描述拟合不描述区分,考虑弱化或改成相对表述。
- VedAstro 分钟级校验每轮超时:
vedastro_event_validation.failure.code=timeout,external_validation_not_passed长期在 reasons 里。已知环境缺口,不在本单。 - 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=0;
run_quality_gate.py --profile quick通过。 - 前端:
tsc --noEmit0 错、npm run lint0 error、相关套件 fail=0、测试总数 ≥ 基线;合同测试 fixture 来自真实引擎回执。 - 推 staging 后
/api/health的deployment.gitCommit等于最新含代码改动的提交。