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

11 KiB
Raw Blame History

TASK · 专业报告溯源绑定:绑最终交付包,并让同输入可复现 — 2026-09-15

  • 基线:origin/staging @ 81f8183c
  • 分支:codex/report-provenance-binding-20260915worktree .worktrees/report-provenance-binding-20260915
  • 关联:BUG-576(MD-only 长报告链路已证明引擎调用 26–32 秒,不是哈希慢)、scripts/full_report_quality_gate.pyprovenance: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 冻在 a6f47abd09-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_packetscripts/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_bindingprofile.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_secondsinput_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_gateresult_hash 才能产出,shared_full_report_authorityscripts/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_keysexcluded_paths 两个字段。允许排除的只有三类:自引用(report_quality_gateshared_full_report_authority)、生成时刻(generated_at)、墙钟耗时(ai_and_audit.summary.elapsed_seconds)。排除项只能加在这三类里,加第四类要回来改本任务书。
D3 不改 attach_calculation_profile 的通用早退语义。 scripts/jyotish_engine.py:135471367016692 等调用方依赖它。专业参考链末尾改成显式调用 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.pytests/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_profileprofile 取已挂在包上的 calculation_profile),最后返回。scripts/jyotish_engine.py:17778 那次 attach_calculation_profile 相应去掉或降级为只补 calculation_profile_id
  • 顺序上注意:report_quality_gateshared_full_report_authority 仍按现在的时机生成(它们要读 result_hash),靠 D2 的排除集处理,不要为它们调整生成顺序。
  • 验收标准:定向测试断言——交付包顶层键集合减去 binding_scope.excluded_top_keys 后,每一个键都参与了哈希(用重算一遍哈希并比对 result_hash 的方式验证,不是比对键名清单);full_report_packchart_identitytiming_precision_contractbirth_provenancerectification_evidence_contract 五个键必须在覆盖集里。

任务 2 · 同输入可复现(P0,BUG-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_mismatchbinding_scope 缺失 → blocked,理由 provenance_binding_scope_absent
  • 排除集出现 D2 三类之外的键 → blocked,理由 provenance_binding_scope_unexpected_exclusion
  • 验收标准:三个 blocked 分支各一个用例;修好之后的正常包这条判据 passedtests/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_scopeexcluded_paths 并在 Bug 记录里说明。
  2. 仍超预算:只做任务 2 + 任务 3,任务 1 写进 BLOCKED.md,附实测耗时数字和被迫放弃的键清单。
  3. 任何情况下都不得靠放宽质量门判据来收工。

8. 开工前置命令

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-690docs/tasks/ 里 BUG-691、BUG-692 已被 TASK-rectification-record-conflict-copy-20260914.mdTASK-rectification-invite-copy-and-assertions-fix-20260915.md 占用。本单用 BUG-693 / BUG-694,开工时复核一次最大号。