Files
Jyotisha/docs/tasks/TASK-rectification-engine-memoization-fix-20260915.md
T
Jesse_ChenandClaude Opus 5 7227b1ed8d docs(tasks): R1 验收修复单——等价 golden 跨机不稳,门禁靠运气绿
对 BUG-721(引擎记忆化)的验收:实现通过,等价性我独立复核过——改前
6b3248bf 与改后 e4788dfc 在同一台机器上跑同一 payload,candidate_scores
逐字相同(8.6273 / 8.6226 / 8.1701),9 条计数断言全过;tsc 0、lint 0
error、全量前端 3291(fail 31) → 3304(fail 31) 失败清单逐条一致、next
build 绿且 / 仍 Static、首屏 gzip 0.00% 变化。

唯一一条红的是 test_score_candidates_matches_baseline_golden:golden 与
本机实际输出有 4 处浮点尾数差(score 1.0e-4 ×2、margin_percent 1.1e-3
×2),改前改后都对不上,说明是 golden 存全精度浮点跨机不稳,不是实现
改坏了。复发自 BUG-712。而 tests/test_rectification_*.py 在
CORE_PYTEST_TARGETS 里,staging 自动门禁会跑它——今天绿只是因为 CI 那台
机器的舍入和执行方一致。

修复单只改测试:主证据换成同进程差分(四个缓存键置 None 即回退旧路径,
A/B 严格相等,不经过任何跨机变量),golden 降为离散字段严格相等 + 浮点
带容差;禁止用重建 golden 来修;防复发升级为仓库级并排查另外五份 golden。

纯文档推送,不触发门禁、不发布镜像、不部署。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-15 23:39:30 +00:00

9.9 KiB
Raw Blame History

TASK · 验收修复单:等价 golden 跨机不稳,门禁靠运气绿

  • 日期:2026-09-15
  • 基线 commitorigin/staging @ e4788dfc(实现落在 53a37ce9
  • 执行分支:codex/rectification-engine-memoization-fix-20260915
  • 来源:Claude 对 TASK-rectification-engine-memoization-20260915BUG-721)的验收
  • 落点:tests/test_rectification_engine_memoization.py、必要时 tests/golden/docs/BUG_HISTORY.md
  • 规模:只改测试。一行实现代码都不许动。

1. 验收结论摘要

BUG-721 的实现是对的,等价性成立,我独立复核过。门禁实测(基线 11893c7f → 头 e4788dfc,含 R1/R2/R3 三个提交):

结果
tsc --noEmit 0 错
npm run lint 0 error / 119 warning(全部既有)
全量前端套件 基线 3291fail 31)→ 头 3304fail 31),失败清单逐条一致
next build exit 0/○ Static
首屏 JS gzip-9 基线 130,872 B → 头 130,872 B0.00%(两侧同一种量法)
tests/test_rectification_*.py 1 failed, 181 passed

唯一那条失败就是本单要修的:test_score_candidates_matches_baseline_golden

它不是实现改坏了。 我在改前(6b3248bf,与 golden 自称的 a8d29d1b 代码相同)和改后(e4788dfc)两个工作树上跑同一份 payload:

12:00 12:01 12:02
本机 改前 8.6273 8.6226 8.1701
本机 改后 8.6273 8.6226 8.1701

改前改后逐字相同。 本单的其余 9 条断言(shadbala / ashtakavarga / vimshottari / narayana 各等于候选分钟数;过境盘等于去重后事件日期数;探针默认 1 次、refresh 时 2 次)全部通过。

2. 事故实证

失败来自 golden 文件与任何一台机器的实际输出对不上:

字段 本机(改前=改后) tests/golden/rectification_engine_memoization_v1.json
candidate_scores[0].score 8.6273 8.6274 1.0e-4
candidate_scores[1].score 8.6226 8.6227 1.0e-4
decision_receipt.margin_percent 5.2995 5.3006 1.1e-3
decision_receipt.gates.diagnostic_quality.margin_percent 5.2995 5.3006 1.1e-3

一共 4 处,全是浮点尾数。candidate_scores[*].score 本身已经被引擎 round(..., 4) 过,差值正好是那一位的 1 ulp——也就是求和顺序/libm 舍入的跨机差异,不是算法差异。

后果是实打实的tests/test_rectification_*.py 这个 glob 在 scripts/run_quality_gate.pyCORE_PYTEST_TARGETS 里(注释原文:「Auto staging gate is --profile quick…so a stale window_scan assertion in this glob stayed red on origin/staging until listed here」)。也就是说 staging 自动门禁会跑这条断言。今天它在 CI 上绿,只是因为那台机器的舍入和执行方的机器一致;换一台机器、换一个基础镜像、换一次 libm 版本就会红。这条门禁现在是靠运气绿的。

3. 根因

golden 存了全精度浮点并做整体 ==。这正是 BUG-712 的形状,那条记录的防复发一字不差地写着:

不得对全精度浮点做整体 ==。测试不得写 golden。

BUG-712 修的是 ephemeris_events 的 goldenlongitude / speed_longitude 量化到 6 位再比,非浮点字段保持严格相等)。那一单的范围写死在星历端点上,新写的这份 rectification golden 是同样的写法,没被扫到。

更深一层:golden 本来就不是证明「记忆化没改结果」的合适工具。 golden 证明的是「今天的输出等于某台机器某一天的输出」,中间夹了一个与被测命题无关的变量(机器)。被测命题其实可以在同一个进程里证明——见 §5.1。

4. 决策记录

产品 2026-09-15 授权本单:

  1. 不回滚 BUG-721 的实现。 等价性已由改前/改后同机对比独立证实,实现留在 staging 上。
  2. 本单只改测试。 一行 scripts/ 下的实现代码都不许动——不得为了让断言通过去改业务代码(这是产品的既定偏好)。
  3. 不得用「重新生成 golden」来修。 在本机重跑一次 write_golden() 能让测试变绿,但那只是把不稳定性换个方向藏起来,下一台机器照样红。这条是硬红线,见 §5.3。
  4. 防复发升级为仓库级。 BUG-712 的那句话从单点措施提升成对所有 golden 生效的规则,并配一条能自动发现违例的守卫。

5. 任务分解

5.1 把主证据换成同进程差分(首要,也是本单真正的价值)

实现已经把四层不变量放进 static context 的具名键(ashtakavarga_result / shadbala_result / vimshottari_timeline / narayana_periods),而每个消费点都有 if result is None: 的回退分支。这意味着记忆化可以在同一个进程里关掉:把 context 里那四个键置 None,消费点就会退回逐次计算的老路径。

新增一条断言:同一份请求、同一个进程,

  • A:正常的 static contexts(带四层缓存)
  • B:把那四个键全部置 None 的同一批 contexts(回退到逐次计算)

断言 compute_event_candidate_rows 在 A / B 下的输出严格逐字相等

这条证明不经过任何跨机变量,是比 golden 强得多的等价证据。

  • 验收:新断言通过;人为把某一层的缓存值替换成错误对象后必须红(贴反向验证,证明这条断言不是恒为真)。
  • 验收:A / B 两条路径确实走了不同分支(用调用计数确认 B 的 calc_shadbala 次数显著高于 A),否则等于什么都没测。

5.2 golden 断言改成分档比较

保留 golden(它仍有价值:能发现跨版本的大幅漂移),但改比较方式:

字段类别 比较方式
离散字段(timesupporting_event_idsconflicting_event_ids、各 gate 布尔、overall_confidencerepresentative_time、排序) 严格相等
浮点字段 容差比较

容差必须由实测漂移推出来,不得拍脑袋:本次实测最大漂移 1.1e-3margin_percent)。取一个有余量但仍能发现真回归的值(建议绝对 2e-3 与相对 5e-4 取更宽者),并把「这个数怎么来的」写进测试注释。

  • 验收:把 golden 里任意一个浮点改动 1e-2,断言必须红(贴反向验证)。
  • 验收:把 golden 里任意一个离散字段改掉,断言必须红。
  • 验收:.venv/bin/python -m pytest tests/test_rectification_*.py 在本机 0 failed

5.3 不得重建 golden

write_golden() 这个辅助函数留着没问题,但本单不得调用它更新那份 golden 文件。若执行方认为必须重建,要在进度记录里写明理由并说明为什么不是在掩盖跨机不稳——默认答案是「不重建」。

  • 验收:git difftests/golden/rectification_engine_memoization_v1.json 无改动(若有改动,按上一条给出理由)。

5.4 仓库级防复发守卫

tests/golden/ 下现在共 6 份:consultation_contract_keypaths_v1.jsonephemeris_events_raman_20260915_90d.jsongolden_cases.jsonqizheng_stem_branch_19900409.jsonrectification_engine_memoization_v1.jsonupstream_sync2/

  • 逐份检查有没有「全精度浮点 + 整体 ==」的比较方式,结果列进进度记录(每份写明:有/无、在哪个测试里比的、怎么比的)。

  • 发现同类问题的,本单只记录不修(各自另开单),除非改动小到一眼可见。

  • 验收:六份的检查结论在进度记录里,一份不漏。

5.5 Bug 历史

同一变更内写进 docs/BUG_HISTORY.md,预占 BUG-733(721–732 已被两轮审计的七单占用,避让)。必须写明:

  • 复发自 BUG-712,并说明为什么没拦住:BUG-712 的防复发只落在 ephemeris_events 那一处,没有仓库级守卫,新写的 golden 重蹈覆辙。
  • 状态可写 resolved,但证据必须是 §5.1 的同进程差分 + §5.2 的两次反向验证,不能只写「现在绿了」。
  • 防复发升级为:任何 golden 比较都不得对浮点做整体 ==;浮点必须量化或带容差,容差数值要有实测依据并写在注释里;能在同进程内做差分证明的命题,不得用跨机 golden 代替。

6. 让步顺序

  1. 5.1 不得砍——它才是这一单的意义;没有它,5.2 只是把红灯调成绿灯。
  2. 5.2 必须做。
  3. 5.4 可以只做「检查并记录」,修留到后续单。
  4. 5.3、5.5 不得砍。

7. 开工前置命令

git fetch origin --prune
git worktree add -b codex/rectification-engine-memoization-fix-20260915 \
  .worktrees/rectification-engine-memoization-fix-20260915 origin/staging
cd .worktrees/rectification-engine-memoization-fix-20260915
git status -sb | head -1
# 先复现:这一条应当是红的
.venv/bin/python -m pytest tests/test_rectification_engine_memoization.py -q

开工前必读:docs/BUG_HISTORY.mdBUG-712(同形态前例与它的修法)与 BUG-721(本单要保护的那次改动)。

验收命令:

.venv/bin/python -m pytest tests/test_rectification_engine_memoization.py
.venv/bin/python -m pytest tests/test_rectification_*.py          # 门禁 glob,须 0 failed
.venv/bin/python scripts/run_quality_gate.py --profile quick

8. BUG 编号起点

基线 e4788dfcdocs/BUG_HISTORY.md 最大号为 BUG-725。721–732 已被两轮审计七单预占(726–732 尚未落库),本单避让,预占 BUG-733。开工时核对实际最大号。

9. 不在本单范围

  • BUG-721 的实现(已验收等价,不动)
  • 其余五份 golden 的修复(5.4 只检查并记录)
  • CORE_PYTEST_TARGETS 的构成(tests/test_rectification_*.py 这条 glob 保留,本单是让它变得可靠,不是把它摘掉)