上游 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
11 KiB
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. 根因
三件事叠在一起,都只关乎"绑定发生在什么对象上":
attach_calculation_profile的早退语义(对只想补calculation_profile_id的调用方是对的)被专业参考链当成了"末尾重绑"用,于是末尾那次调用什么都没做。- 被哈希的载荷里混进墙钟字段。
- 有两个字段天然不能进哈希:
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. 硬红线
scripts/jyotish_api_server.py不得增长(AGENTS.md§6,合同测试tests/test_api_server_growth_contract.py)。.venv/bin/python scripts/run_quality_gate.py --profile quick通过;定向tests/test_calculation_profile_contract.py、tests/test_full_report_quality_gate.py全绿。- 质量门判据只增不减。 既有
provenance:result_binding自洽校验保留,新判据加在它旁边;不得把任何现有blocked改成warning来让门变绿。 - 专业参考链整体耗时增量 ≤ 0.3 秒(用 §8 的复现命令前后各跑三次取中位数,数字进进度记录)。
- 任务书、测试、进度记录、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 · 同输入可复现(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_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. 让步顺序
- 若末尾重绑让链路耗时超过 §5 第 4 条预算:先保覆盖,把
raw_full_reading换成它自己的result_hash引用参与哈希(上游投影思路的最小版,只对这一个键用),重测;这条要写进binding_scope的excluded_paths并在 Bug 记录里说明。 - 仍超预算:只做任务 2 + 任务 3,任务 1 写进
BLOCKED.md,附实测耗时数字和被迫放弃的键清单。 - 任何情况下都不得靠放宽质量门判据来收工。
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-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,开工时复核一次最大号。