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

163 lines
9.9 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 · 验收修复单:等价 golden 跨机不稳,门禁靠运气绿
- 日期:2026-09-15
- 基线 commit`origin/staging` @ `e4788dfc`(实现落在 `53a37ce9`
- 执行分支:`codex/rectification-engine-memoization-fix-20260915`
- 来源:Claude 对 `TASK-rectification-engine-memoization-20260915`BUG-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 B**0.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.py``CORE_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` 的 golden`longitude` / `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(它仍有价值:能发现跨版本的大幅漂移),但改比较方式:
| 字段类别 | 比较方式 |
| --- | --- |
| 离散字段(`time``supporting_event_ids``conflicting_event_ids`、各 gate 布尔、`overall_confidence``representative_time`、排序) | **严格相等** |
| 浮点字段 | 容差比较 |
容差必须由实测漂移推出来,不得拍脑袋:本次实测最大漂移 **1.1e-3**`margin_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 diff``tests/golden/rectification_engine_memoization_v1.json` **无改动**(若有改动,按上一条给出理由)。
### 5.4 仓库级防复发守卫
`tests/golden/` 下现在共 6 份:`consultation_contract_keypaths_v1.json``ephemeris_events_raman_20260915_90d.json``golden_cases.json``qizheng_stem_branch_19900409.json``rectification_engine_memoization_v1.json``upstream_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. 开工前置命令
```bash
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.md`**BUG-712**(同形态前例与它的修法)与 **BUG-721**(本单要保护的那次改动)。
验收命令:
```bash
.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 编号起点
基线 `e4788dfc``docs/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 保留,本单是让它变得可靠,不是把它摘掉)