Files
Jyotisha/docs/tasks/TASK-rectification-validation-integrity-20260920.md
T
Jesse_ChenandClaude Opus 5 5049e7847b docs(tasks): 生时校正验证体系补缺任务书(BUG-978~980)
会议要求把「推断真实出生时间」与「用户认可的参考盘」分开证明。逐条核对
后:产品口径侧四条已落地(accepted≠confirmed 两条写入路径、确认门
fail-closed、采用不写 reported_birth_time、无把采用率当准确率的指标),
缺口全在评测本身。

三条实证:封存契约里三个打分哈希互不相同且 official_eval_trial_count 为
0,当前实现从未产出过有效官方盲测;全部离线评测的候选窗以真值为圆心而
生产以申报时间为圆心,真值掉出窗外这一失败模式从不可见;v3 封存但每例
仅 3 事件、v4 事件够却已被看过并用于调参,六题回放的高分是真值方向最优
答的上界。

任务分解:申报偏差敏感性 sweep、封存盲测有效重跑 + 契约对齐 + 防复发
测试、v5 采集协议(只出协议)、记录。硬红线:不改 12 个打分文件、不用
封存集调参、不得把 status 改 ready。家庭信息在拿到基线数字前不开工。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
2026-09-20 11:05:46 +08:00

18 KiB
Raw Blame History

TASK · 生时校正验证体系补缺:申报偏差评测与封存盲测有效重跑(2026-09-20)

状态:待领取。纯离线评测与契约对齐,不改任何打分逻辑、不改产品行为、不动确认门。 起因:产品会议对「我们究竟在证明什么」提出边界要求(真实出生时间 vs 用户认可的参考盘必须分开证明)。Claude 2026-09-20 对代码、评测数据与线上口径做了逐条核对,结论见 §1。 产品口径侧的四条要求(accepted≠confirmed、不承诺精确分钟、不覆盖原始资料、不拿用户认可当验证)核对下来已经落地,本单不碰那一层;缺口全部在评测本身。

0. 基线与交付

  • 基线:origin/staging = fcad0637(2026-09-20 实测核对)。
  • worktree .worktrees/rectification-validation-20260920,分支 codex/rectification-validation-20260920。
  • 改动落点:scripts/research/**、tests/**、references/rectification_sealed_holdout.v1.json、docs/**。前三项在 deploy/gated-paths.txt 内,推 staging 会触发门禁并重新发布镜像。
  • 前置依赖(不在本单,不阻塞开工):TASK-owner-case-purge-20260919 的三个提交 fe9489e6 / 36c443bf / 497798ac 已验收通过但尚未合入 staging(已核:三者均不是 origin/staging 的祖先)。那条分支带着仓库级隐私守卫 tests/test_repo_privacy_markers.py。本单不依赖它即可执行,但在它合入之前,任何「拿自己的案例试一下」的结果都不具备独立性,不得当作验证证据。放行由产品负责人决定。

1. 事故实证

基线 fcad0637,行号按符号定位。

1.1 三个打分实现哈希互不相同,任何成绩都无法归属(BUG-978)

references/real_case_calibration/minute_rectification_holdout_v3.json 的 frozen_scoring 对 12 个打分文件做 SHA-256 封存。三处哈希实测:

来源 哈希前缀
封存值 frozen_scoring.implementation_sha256 f41c298dd6cdcebe
契约文件记录的当前树 current_tree_scorer.implementation_sha256 99730c84c6434f52
基线树实测(implementation_sha256(frozen_scoring.files)) b15d9ea15227cd58

三者两两不等——契约文件 references/rectification_sealed_holdout.v1.json 里的 current_tree_scorer 块本身已经过期。同一文件另有两处与现实不符:source_audit_status 仍写 invalidated_after_replay,而 v3 数据集在 2026-09-14 已改为 corrected_known_date_errors;evaluated_on 仍停在 2026-07-21。

后果记在该文件自己的字段里:official_eval_implementation_hash_matches: false、official_eval_trial_count: 0——当前打分实现从未在封存集上产出过一次有效的官方盲测成绩。

  • 唯一跑过的那次(2026-07-21):top-1 0.15、top-3 0.25、MAE 6.95 分钟、确认覆盖率 0.0,报告状态 invalidated_after_source_audit(资料审计发现两处错年,已在 docs/research/holdout_v4_build_2026_09_14.md 更正)。
  • 现在唯一能看到的较新数字(top-1 0.45 / top-3 0.50 / MAE 5.8)来自 current_tree_unfrozen_diagnostic 块,该块自带 is_blind_evaluation: false、results_already_seen: true、must_not_claim_as_release_metrics: true、must_not_use_for_tuning: true。

预注册发布门(minute_rectification_holdout_v3.json 的 release_metrics):top-1 ≥ 0.60、top-3 ≥ 0.85、MAE ≤ 2.0 分钟、误确认率 ≤ 0.05、信息不足正确拒绝率 ≥ 0.90。

计算成本不是障碍:基线树实测,20 例全量打分 ±10 档 1.6 s、±60 档 8.6 s。

1.2 所有离线评测的候选窗以真值为圆心,生产以申报时间为圆心(BUG-979)

  • scripts/minute_rectification_blind_eval.py 的 _candidate_moments() 以 case["birth"]["time"](Astro-Databank AA 记录的真实分钟)为中心,向两侧各取 candidate_radius_minutes 分钟。
  • scripts/minute_rectification_fact_blind_eval_v4.py 直接 import 复用同一函数。
  • scripts/research/probe_supply_after_six.py 的 request_from_case() 同样以真值为心,局部变量名就叫 true_time,start_time / end_time 由它 ± 半径得到;scripts/research/minute_resolution_sweep.py 的 scoring_request_for() 调的就是它。

生产不是这样:frontend/src/lib/rectification-agentic/v9/search-window.ts 的 ENGINE_SEARCH_RADIUS_MINUTES = 15,窗以用户申报时间为中心(frontend/src/lib/rectification-agentic/birth-time-provenance.ts 文件头注释明写 "Scoring still uses reportedBirthTime as the search-window centre"),可放宽到 30 / 60 / 120。

后果:离线评测里真值永远在窗正中心、永远在候选集里。「申报偏差大于半径 → 真值根本不在候选里」这一失败模式在现有全部评测中不可见,其发生率从未被测量。全仓检索偏移窗相关键在评测脚本内 0 命中。

1.3 现无「口径干净且事件数贴近真实会话」的封存集(BUG-980)

数据集 是否封存 每例事件 每例领域 可否当发布门
minute_rectification_holdout_v3.json 是(truth_hidden_from_ranker: true) 3 2–3 可,但事件数远低于真实会话
minute_rectification_holdout_v4.json 否(truth_hidden_from_ranker: false) ≥7 ≥4 不可,结果已被看过且用于 R1–R5 调参

docs/research/holdout_v4_build_2026_09_14.md 自己写明 v4「是开放评价集,用来看尺度,不是第二份封存盲测」;真实会话每例 5–18 件事。

另:docs/research/minute_resolution_2026_09_14.md 记载六题回放用「真值方向最优答」。因此 0.80 / 0.55 / 0.35(±10 / ±30 / ±60)是上帝视角上界,不是产品表现;其中 ±30 与 ±60 两档仍低于 0.60 的发布门,而生产默认半径 ±15 正落在两档之间。

1.4 产品口径侧已经达标(不构成 BUG,记此以免重复排查)

  • accepted 与 confirmed 是两个状态、两条写入路径(frontend/supabase/migrations/20260813010000_agentic_rectification_v9_agent_api.sql 的 accept_agentic_rectification_candidate_for_case 与 confirm 函数),selection_kind 区分 user_accepted / engine_confirmed。
  • 采用候选写 active_birth_time / birth_time,不写 reported_birth_time——原始申报保留,且仍是搜索窗圆心。
  • 确认门 fail-closed:scripts/rectification/decision_policy.py 的 apply_confirmation_decision() 要求四闸同时通过,其一是 scripts/rectification/sealed_holdout.py 的 holdout_passed()。封存契约 status: not_ready 且 confirmation_coverage_rate: 0.0,因此 confirmation_allowed 今天在任何情况下都不可能为真。
  • 交付卡自带标签 candidate_range_not_birth_time_truth 与「采用只会把代表性时间写入当前排盘,不是已确认的唯一出生分钟」。
  • 全仓无「把采用率 / 满意度当准确率」的指标;无任何按特定生日走捷径的运行时分支(上游 PL9_BAV_CELL_OVERRIDES、_known_case_regression_hint 在本仓 0 命中)。

关联记录:BUG-098(公开候选门禁未明确独立 holdout 边界)。其防复发条写明「任何 holdout 完成声明必须先有稳定分区持久化与校准验收证据」。本单三条与之同源:那条措施仍然有效,但从未被执行到底,且没有任何测试守着它。

2. 根因

  1. 封存契约文件 references/rectification_sealed_holdout.v1.json 是人工维护的(scripts/rectification/sealed_holdout.py 文件头注释:「A human updates ... after a valid passing evaluation」),没有任何测试断言它与数据集、与当前树哈希一致 —— 打分代码每改一次,它就更旧一次,而没有人会被提醒。
  2. 评测脚本是从「给定真实出生时间,能否把它排到第一」出发写的;产品解决的是「给定用户申报时间,真实时间在哪」。两者差一个偏移量,建评测时没有人把这个差异写成一项指标。
  3. v4 建立时就定性为开放评价集,此后没有人补上对应的封存孪生集,于是「事件数真实」与「口径干净」只能二选一。

3. 决策记录

  • 2026-09-20 产品授权出本单,覆盖核对结论中的两条行动:补申报偏差评测、重跑有效封存盲测;外加一条只出协议的封存集规划。
  • 本单只产出数字与结论,不改打分逻辑、不调参、不改产品行为。 即便重跑成绩过了发布门,执行方不得把 status 改成 ready、不得让确认门打开 —— 过门是产品决策,另立单。
  • 家庭信息(父母职业、父母出生年份、兄弟姐妹)在本单产出基线数字之前不开工。 核对已确认:现有七个领域全部是带日期事件,打分输入只认 {id, domain, date, precision} 四个字段(见 scripts/minute_rectification_blind_eval.py 的 _request()),静态家庭属性没有输入口 —— 加它不是多问一道题,是新开一条打分通道。若产品另有决定,请在领取前更新本段。
  • 上游清除分支的放行是产品动作,不在本单(§0)。

4. 硬红线

  1. 不得修改 frozen_scoring.files 里那 12 个打分文件中的任何一个,也不得修改 scripts/rectification/decision_policy.py、scripts/rectification/sealed_holdout.py 的判定逻辑。本单是量,不是改。
  2. 不得把封存集 v3 用于任何调参;不得因成绩难看而更换数据集、更换半径或剔除案例。剔除只允许发生在 minute_rectification_holdout_validator.validate() 判定为 invalid_cases 的情况下,且必须列出被剔案例与理由。
  3. 不得让 references/rectification_sealed_holdout.v1.json 的 status 变成 ready,不得抬高 confirmation_coverage_rate。
  4. 新增的偏差评测必须与现有评测并存;不得改写 _candidate_moments() 与 request_from_case() 的现有行为(改了会让两轮历史结论不可比)。
  5. 任何数字进文档必须带口径:ayanamsa / node mode / 半径 / 步长 / 数据集 / 打分实现哈希前 16 位。不得把非盲诊断写成发布指标。
  6. 不得写入真实用户资料;案例只用已登记的公开 AA 名人;不得在任何文档里写出生分钟或坐标。
  7. 不顺手升级依赖、不修不在本单内的 warning。
  8. frontend/src/app/page.tsx 与 scripts/jyotish_api_server.py 均不得增长(本单本不该碰前端与主 API,碰了说明跑偏)。

5. 任务分解

T1 · 申报偏差评测(BUG-979)

新增 scripts/research/reported_offset_sweep.py:在现有取候选方式之外,增加一个「模拟申报时间」的圆心。

  • 对每个案例,把窗心从真值平移 offset ∈ {0, ±3, ±5, ±8, ±10, ±15, ±20, ±30} 分钟,半径取产品档位 {15, 30, 60}。
  • 每个(案例, 偏移, 半径)组合输出四项:真值是否落在窗内、真值在候选中的排名、头名与真值的分钟距离、交付区间是否覆盖真值。
  • 汇总成一张表:横轴偏移、纵轴半径,格子里是「真值在窗内的比例 / 头名命中 / 区间覆盖」。
  • 真值仍然排完名之后才揭晓;并列沿用现有哈希打破方式(_opaque_winner),不得按接近真值挑选。

验收标准:

  • python3 scripts/research/reported_offset_sweep.py --json 在基线树跑通,输出含上述四项与完整口径块(含打分实现哈希前 16 位)。
  • 新增 tests/test_reported_offset_research.py,至少断言:(a) 偏移为 0 时窗心等于真值且「真值在窗内」为 1.0;(b) 偏移绝对值大于半径时「真值在窗内」为 0;(c) 口径块字段齐全;(d) 并列打破不依赖真值。
  • 结论写进 docs/research/reported_offset_2026_09_20.md,必须明确回答一句:在半径 ±15 下,申报偏差多大时真值开始掉出窗外;以及现有放宽路径(30 / 60 / 120)能把这个比例救回多少。
  • 本任务不需要知道真实用户的申报偏差分布;它给的是「如果偏差是 X,会怎样」的敏感性曲线。真实分布怎么拿,进 T4 的遗留项。

T2 · 封存盲测有效重跑与契约对齐(BUG-978)

  1. 冻结当前打分实现:把基线树实测哈希(b15d9ea15227cd58…,执行方自己重算确认)连同 12 个文件清单写进一次新的评测记录;不改 v3 的 frozen_scoring 原值(那是历史封存值,要留着做对照)。
  2. 在 v3 封存集上跑一次完整盲测,产出五项预注册指标:top-1、top-3、MAE、误确认率、信息不足正确拒绝率。
  3. 把 references/rectification_sealed_holdout.v1.json 更新到与现实一致:source_audit_status 取 v3 数据集当前值;current_tree_scorer 刷新到本次实测哈希与成绩;新增一个块记录本次重跑,注明它是首次口径干净的官方盲测,以及「v3 每例仅 3 件事,是下限口径,不代表真实会话」。status 保持 not_ready(§4.3)。
  4. 新增 tests/test_sealed_holdout_contract_freshness.py:断言契约文件的 current_tree_scorer.implementation_sha256 等于当前树实测值、source_audit_status 等于 v3 数据集里的值。这条测试就是防复发 —— 它一红就说明打分代码改了而契约没跟上。

验收标准:

  • .venv/bin/python -m pytest tests/test_sealed_holdout_contract_freshness.py tests/test_minute_rectification_holdout_validator.py tests/test_minute_rectification_fact_blind_eval_v4.py -q 通过。
  • 契约文件三处过期字段全部对齐,且 status 仍为 not_ready、confirmation_coverage_rate 仍为 0.0。
  • 五项指标与发布门逐条对照写进 docs/research/sealed_holdout_rerun_2026_09_20.md,每项标「通过 / 未通过 / 不适用」。
  • 确认门行为零变化:跑 frontend/tests/rectification-confirmation-gate.test.ts 与相关 pytest,结果与基线逐条相同。
  • 快速门 python3 scripts/run_quality_gate.py --profile quick 与基线逐条比对。

T3 · 封存集 v5 采集协议(BUG-980,只出协议、不采集)

写 docs/research/sealed_holdout_v5_protocol_2026_09_20.md:

  • v5 目标:封存 + 每例 ≥7 件带年月事件 + ≥4 个领域,即 v4 的口径、v3 的纪律。
  • 人物名单必须与 v3 / v4 的 20 例不重叠(那 20 例成绩已被看过)。列出 ≥25 个候选公开 AA 人物作为备选池,只写人物与资料来源类型,不写出生分钟或坐标。
  • 标注协议沿用 holdout_v4_build_2026_09_14.md 的四条(AA 记录、事件来自公开页且 independent_of_birth_source=true、工作日期须核实、三档半径写在数据集上),并补一条:v5 建成后,在产出第一份成绩之前不得被任何人读取事件内容做调参。
  • 工作量估算:按每例 7 件事、每件需一处可核实公开来源计,给出人工小时数区间。
  • 明确写出:「在 v5 就绪之前,任何对外的分钟级准确率说法都没有依据」,并指明 T2 的数字是下限口径、T1 的数字是敏感性而非准确率。

验收标准:文档含上述全部要素;候选池 ≥25 人且与现有 20 例无交集;不含任何出生分钟、坐标或真实用户资料。

T4 · 记录

  • docs/BUG_HISTORY.md 新增 BUG-978 / BUG-979 / BUG-980,状态按实际填(T1 / T2 完成的可写 resolved 并附证据;BUG-980 只出协议,保持 investigating 直到 v5 就绪)。三条都必须关联 BUG-098,并说明那条防复发措施为何没拦住。
  • BLOCKED.md 记一条:真实用户的申报偏差分布目前拿不到(需要有独立出生记录的真实用户样本,无受控账号),T1 只能给敏感性曲线。
  • docs/research/ACTIVE_FRONTS.md 加一节索引本单三份文档,并指回 rectification_minute_resolution_closure_2026_09_14.md。
  • docs/tasks/README.md 状态板加一行;实现合入 staging 的同一次推送里改状态。
  • CHANGELOG.md 不写(无用户可感知行为变化)。

6. 让步顺序

时间不够时按此顺序砍,砍掉的写进进度记录:

  1. 最先保 T2 —— 它最便宜(±10 档全量 1.6 s),且产出的是第一份能对着发布门看的数字。
  2. 其次保 T1 的 ±15 一档与 {0, ±5, ±10, ±15, ±20} 五个偏移;其余半径与偏移可后补。
  3. T3 候选池可从 25 人降到 15 人,但协议正文与那句「在 v5 就绪之前没有依据」不得删。
  4. 不得为赶工跳过 T2.4 那条防复发测试 —— 没有它,这三条明天会重新长出来(BUG-098 就是这么回来的)。

7. 开工前置命令

git fetch origin --prune
git worktree add -b codex/rectification-validation-20260920 \
  .worktrees/rectification-validation-20260920 origin/staging
cd .worktrees/rectification-validation-20260920
git log --oneline -1        # 必须是 fcad0637 或其后代
python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45

本单属 AGENTS.md §9 的引擎 / 评测类任务,pre_work_check.py 必跑,并先读 docs/research/pre_work_error_ledger.md。

另必读 docs/research/rectification_minute_resolution_closure_2026_09_14.md —— 它已经证伪了两条假设(加权重能拉开候选、簇合并把候选并没了),本单不得重走;它的第 6 节三条提醒(先确认指标口径、不要从 docstring 推断行为、真值覆盖率优先于区间宽度)对本单全部适用。

环境备忘:核对机上无 .venv,系统 python3(3.13)可直接 import swisseph;若执行方环境有 .venv,按 AGENTS.md §10 用 .venv/bin/python。

8. BUG 编号起点

docs/BUG_HISTORY.md 当前最大号 975;TASK-consult-smalltalk-fastpath-20260920.md 已占用 976 / 977。本单从 BUG-978 起,连续三条。开工时重新核对最大号,若已被占用则顺延并在进度记录里说明。