Files
Jyotisha/docs/tasks/TASK-report-provenance-binding-20260915.md
T
Jesse_ChenandClaude Opus 5 416b9a693c docs(tasks): bind the professional report receipt to what is actually delivered
上游 2026-09-14 的 4 个新提交里 SKILL.md 与校正代码零改动,核"要不要抄
它的 PL9 绑定投影"时量出我们自己两个问题:result_binding 绑的是中间态包
(18/36 个顶层键、6.24 MB,交付 10.04 MB),质量门只比自洽所以恒 passed;
被哈希的载荷含墙钟 elapsed_seconds,同一输入两次跑出不同 result_hash。
任务书写末尾按交付对象重绑、排除集显式进回执、质量门补覆盖校验;上游那条
投影优化实测收益 < 0.1 秒,记为不抄。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193vBv6w5MV2cifdTUu9H5P
2026-09-15 01:29:00 +00:00

135 lines
11 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 · 专业报告溯源绑定:绑最终交付包,并让同输入可复现 — 2026-09-15
- 基线:`origin/staging` @ `81f8183c`
- 分支:`codex/report-provenance-binding-20260915`worktree `.worktrees/report-provenance-binding-20260915`
- 关联:BUG-576(MD-only 长报告链路已证明引擎调用 26–32 秒,不是哈希慢)、`scripts/full_report_quality_gate.py``provenance:result_binding` 判据。
- 串行:只改 Python 报告链(`calculation_profile_contract` / `jyotish_engine` 专业参考链 / `full_report_quality_gate`)。不碰前端、不碰校正流、不动数据库、不动迁移。与任何改 `frontend/` 的轮次无冲突。
## 1. 缘起
2026-09-15 核上游 `732642856/yinduzhanxing` 是否更新:**SKILL.md 与我们 09-09 镜像逐字节相同(sha256 `6bed459b…`),`main` 冻在 `a6f47abd`09-03),校正分支 09-14 的 4 个新提交里校正代码零行、skill 零行。** 上游这轮没有我们要接的东西。
但上游那 4 个提交顺手改了 `calculation_profile_contract.py`(给 PL9 大包的绑定换成有界投影)。逐条核这条要不要抄时,量出**我们自己**两个真问题。上游那条不抄,理由见 D0。
## 2. 事故实证
实测环境:`.worktrees/staging-docs` @ `81f8183c``.venv/bin/python`,虚构盘 `1990-05-17 09:26 +08 / 31.23,121.47``--target-year 2026 --pack full --format json`(全文不含真实出生资料)。
### 2.1 绑定只覆盖中间态(一半交付内容没被绑)
`build_professional_report_reference_packet``scripts/jyotish_engine.py:17762`)里:
- `scripts/jyotish_engine.py:17770` 第一次 `attach_calculation_profile`,此刻被哈希的是 **6,239,839 字节 / 18 个顶层键**
- 之后依次挂年度 Tajika 包、治理合同、基础盘、星盘包、包选择、`full_report_pack`、星轨语言桥。
- `scripts/jyotish_engine.py:17778` 第二次 `attach_calculation_profile` 因为 `calculation_profile` 已存在,命中 `scripts/calculation_profile_contract.py:423` 的早退 `return result`**不重绑**。
- 实际交付对象:**10,040,453 字节 / 36 个顶层键**。
没有进 `result_hash` 的 18 个顶层键(实测列出,不是推断):
```
birth_provenance, birth_time_sensitivity, chart_identity, event_replay,
full_report_pack, generated_at, module_execution_audit, personal_report_producer,
raw_module_index, raw_module_usage_map, reader_engine_boundary_notice,
rectification_evidence_contract, report_quality_gate, selected_report_pack_ids,
selected_report_scope, shared_full_report_authority, technique_audit_table,
timing_boundary_attribution, timing_precision_contract
```
质量门抓不到:`scripts/full_report_quality_gate.py:164-171` 只比 `result_binding``profile.input_hash` / `packet.result_hash` 是否自洽。实测该次运行里 `provenance:result_binding` = **passed**,同时绑定只覆盖 18/36 个键。
### 2.2 同一份输入两次运行,`result_hash` 不同
```
run1 result_hash f4f65216dfd686e9… input_hash c6e284cfbd9f4efa ai_and_audit.summary.elapsed_seconds 0.9448
run2 result_hash d841a4b34d65dfab… input_hash c6e284cfbd9f4efa ai_and_audit.summary.elapsed_seconds 0.9003
```
被哈希的载荷里含墙钟字段 `ai_and_audit.summary.elapsed_seconds``input_hash` 相同、`result_hash` 每次不同,所以"同输入同结果"这件事**现在根本验不了**,回执只是自洽而已。
### 2.3 性能(用来否掉上游那条)
同一次运行里 PL9 那次绑定:`json.dumps` **90.2 ms** + `sha256` **8.0 ms** ≈ 0.1 秒;CLI 全链 **2.6 秒**。另一次 2.70 MB 的绑定 40 ms。上游的投影优化对我们省不到 0.1 秒。BUG-576 已经查明长报告的 26–32 秒来自引擎本身。
## 3. 根因
三件事叠在一起,都只关乎"绑定发生在什么对象上":
1. `attach_calculation_profile` 的早退语义(对只想补 `calculation_profile_id` 的调用方是对的)被专业参考链当成了"末尾重绑"用,于是末尾那次调用什么都没做。
2. 被哈希的载荷里混进墙钟字段。
3. 有两个字段天然不能进哈希:`report_quality_gate``result_hash` 才能产出,`shared_full_report_authority``scripts/shared_full_report_authority.py:44`)把 `result_hash` 抄进自己。自引用字段必须显式排除,而不是靠"绑在它们生成之前"这种巧合来回避。
## 4. 决策记录
| 决策 | 内容 |
| --- | --- |
| D0 | **不抄上游的有界投影。** 实测收益 < 0.1 秒(§2.3)。上游那版也没解决覆盖问题(它的早退条件是"已有 hash 就返回",末尾照样不重绑)。抄进来只会多一份与我们不同的哈希口径。 |
| D1 | **绑定对象改成最终交付对象**`build_professional_report_reference_packet` 的返回值(即 `sanitize_professional_report_reference(final_packet)` 之后的那个 dict),不再是中间态包。 |
| D2 | **排除集必须显式、必须写进回执。** `result_binding` 增加 `binding_scope`,至少含 `excluded_top_keys``excluded_paths` 两个字段。允许排除的只有三类:自引用(`report_quality_gate``shared_full_report_authority`)、生成时刻(`generated_at`)、墙钟耗时(`ai_and_audit.summary.elapsed_seconds`)。**排除项只能加在这三类里,加第四类要回来改本任务书。** |
| D3 | **不改 `attach_calculation_profile` 的通用早退语义。** `scripts/jyotish_engine.py:13547``13670``16692` 等调用方依赖它。专业参考链末尾改成显式调用 `bind_result_to_profile`。 |
| D4 | **不动业务字段本身。** `elapsed_seconds` 照常输出给用户/审计,只是不进哈希。不得为了让哈希稳定而删业务数据。 |
| D5 | 报告正文、Skill、前端、数据库、迁移一律不动。本单对用户不可感知,因此 `CHANGELOG.md` 不写。 |
## 5. 硬红线
1. `scripts/jyotish_api_server.py` 不得增长(`AGENTS.md` §6,合同测试 `tests/test_api_server_growth_contract.py`)。
2. `.venv/bin/python scripts/run_quality_gate.py --profile quick` 通过;定向 `tests/test_calculation_profile_contract.py``tests/test_full_report_quality_gate.py` 全绿。
3. **质量门判据只增不减。** 既有 `provenance:result_binding` 自洽校验保留,新判据加在它旁边;不得把任何现有 `blocked` 改成 `warning` 来让门变绿。
4. 专业参考链整体耗时增量 ≤ 0.3 秒(用 §8 的复现命令前后各跑三次取中位数,数字进进度记录)。
5. 任务书、测试、进度记录、Bug 历史里只用本单的虚构盘,不得出现真实出生资料。
## 6. 任务分解
### 任务 1 · 末尾按最终交付对象重绑(P0,BUG-693)
- `build_professional_report_reference_packet` 末尾:先 `sanitize_professional_report_reference(final_packet)` 得到交付对象,再对它显式 `bind_result_to_profile`profile 取已挂在包上的 `calculation_profile`),最后返回。`scripts/jyotish_engine.py:17778` 那次 `attach_calculation_profile` 相应去掉或降级为只补 `calculation_profile_id`
- 顺序上注意:`report_quality_gate``shared_full_report_authority` 仍按现在的时机生成(它们要读 `result_hash`),靠 D2 的排除集处理,不要为它们调整生成顺序。
- 验收标准:定向测试断言——交付包顶层键集合减去 `binding_scope.excluded_top_keys` 后,**每一个键都参与了哈希**(用重算一遍哈希并比对 `result_hash` 的方式验证,不是比对键名清单);`full_report_pack``chart_identity``timing_precision_contract``birth_provenance``rectification_evidence_contract` 五个键必须在覆盖集里。
### 任务 2 · 同输入可复现(P0BUG-694)
- `scripts/calculation_profile_contract.py` 的哈希载荷构造按 D2 的排除集裁剪;`result_binding` 写入 `binding_scope`
- 验收标准:同一进程内用同一份输入构造两次交付包,`result_hash` 相同;`elapsed_seconds` 被人为改成不同值时 `result_hash` 仍相同;改任一业务字段(例如 `coverage` 里任一项)后 `result_hash` 必须变。三条各一个断言。
### 任务 3 · 质量门补覆盖校验(P1)
- `scripts/full_report_quality_gate.py` 在既有自洽校验之后,增加一条 `provenance:result_binding_scope`:重新按 `binding_scope` 裁剪交付包并重算哈希,与 `result_hash` 不一致 → `blocked`,理由 `provenance_binding_scope_mismatch``binding_scope` 缺失 → `blocked`,理由 `provenance_binding_scope_absent`
- 排除集出现 D2 三类之外的键 → `blocked`,理由 `provenance_binding_scope_unexpected_exclusion`
- 验收标准:三个 `blocked` 分支各一个用例;修好之后的正常包这条判据 `passed``tests/test_full_report_quality_gate.py` 既有用例不改断言(若必须改,按 §7 前端红线同款写"原值 / 新值 / 原因"三栏)。
### 任务 4 · 记录(P1
- `docs/BUG_HISTORY.md` 新增 BUG-693、BUG-694 两条,现象/触发条件/根因/修复/验证/防复发照 `AGENTS.md` §5 写全。防复发至少写明:专业参考链的绑定必须发生在返回对象上;排除集必须显式落在回执里并被质量门校验。
- `docs/tasks/PROGRESS-report-provenance-binding-20260915.md`:贴 §2 三组实证的**修复后**复跑数字(两次 `result_hash` 一致、覆盖键数、耗时中位数前后对比)。
- `CHANGELOG.md` 不写(D5)。
## 7. 让步顺序
1. 若末尾重绑让链路耗时超过 §5 第 4 条预算:先保覆盖,把 `raw_full_reading` 换成它自己的 `result_hash` 引用参与哈希(上游投影思路的最小版,只对这一个键用),重测;这条要写进 `binding_scope``excluded_paths` 并在 Bug 记录里说明。
2. 仍超预算:只做任务 2 + 任务 3,任务 1 写进 `BLOCKED.md`,附实测耗时数字和被迫放弃的键清单。
3. 任何情况下都不得靠放宽质量门判据来收工。
## 8. 开工前置命令
```bash
git -C /workspace/Jyotisha status -sb | head -1
git -C /workspace/Jyotisha fetch origin --prune
git -C /workspace/Jyotisha worktree add -b codex/report-provenance-binding-20260915 \
.worktrees/report-provenance-binding-20260915 origin/staging
# 基线:先复现 §2.2(两行 result_hash 必须不同,否则说明基线已变,回来改任务书)
cd .worktrees/report-provenance-binding-20260915
for i in 1 2; do /workspace/Jyotisha/.venv/bin/python scripts/jyotish_engine.py pl9-export \
--year 1990 --month 5 --day 17 --hour 9 --minute 26 --lat 31.23 --lon 121.47 --tz 8 \
--target-year 2026 --pack full --format json 2>/dev/null \
| python3 -c "import json,sys;d=json.load(sys.stdin);print(d['result_hash'], d['calculation_profile']['input_hash'][:16], len(d))"; done
# 基线测试
/workspace/Jyotisha/.venv/bin/python -m pytest tests/test_calculation_profile_contract.py tests/test_full_report_quality_gate.py
```
`AGENTS.md` §9 的预检(`scripts/pre_work_check.py`)本单要跑:动的是引擎运行入口与发布门禁判据。
## 9. BUG 编号起点
`docs/BUG_HISTORY.md` 当前最大号 **BUG-690**`docs/tasks/` 里 BUG-691、BUG-692 已被 `TASK-rectification-record-conflict-copy-20260914.md``TASK-rectification-invite-copy-and-assertions-fix-20260915.md` 占用。本单用 **BUG-693 / BUG-694**,开工时复核一次最大号。