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
This commit is contained in:
Jesse_Chen
2026-09-15 01:29:00 +00:00
co-authored by Claude Opus 5
parent 81f8183cc2
commit 416b9a693c
2 changed files with 135 additions and 0 deletions
+1
View File
@@ -136,6 +136,7 @@
| — | `PROGRESS-report-progress-20260909.md` | 生成等待屏只有 spinner 与秒表:后端 `progressPercent` / `progressPhase` 与分章行已产出,前端解析后一字未渲染,且分章行在 `generating` 时根本不出服务端。改为按章分格进度条 + 章节清单,停滞 90 秒改「用时较长,仍在写」;不画百分比条、不做插值动画、不报预计剩余 | 已验收 | `848e39e6``5565b632`BUG-601 |
| `TASK-report-chart-render-20260909.md` | `PROGRESS-report-chart-render-20260909.md` | 报告页所有分盘标题下空白:引擎 22 张 `<svg>``skipHtml` 丢弃(09-06 Markdown 直渲回归)。引擎旁加 ```jyotish-chart 围栏 JSON,前端 zod 校验后用自绘北印式组件全部渲染;不放开原始 HTML | 已验收(2fdcb14f):tsc/lint 0 error、chart 测试 29/29、`/` Static、gzip +0.96%;围栏构造在 try 外的 P2 并入 `TASK-upstream-sync2-fix-20260909.md` 任务 3 | `codex/report-chart-render-20260909`BUG-607 |
| `TASK-report-chart-layout-fix-20260909.md` | `PROGRESS-report-chart-layout-fix-20260909.md` | 星盘渲染回归修复:22 张北印盘全部重叠(浮动 + 负 margin 凑两栏对连续多对无效)+ 滚动时 `components` 每渲染新建导致整篇报告卸载重建、页面卡;改 rehype 成组进既有两栏 grid、文章树 `useMemo`、目录状态下沉 | 待验收 | `87daffe2`BUG-616/617);已 merge origin/staging `df090fd5` |
| `TASK-report-provenance-binding-20260915.md` | `PROGRESS-report-provenance-binding-20260915.md` | 专业报告溯源绑定:`result_binding` 只绑到中间态包(实测 18/36 个顶层键、6.24 MB / 交付 10.04 MB),`full_report_pack`、`chart_identity`、`timing_precision_contract` 等全在覆盖外,而质量门只比自洽所以恒 passed(BUG-693);被哈希的载荷含墙钟 `ai_and_audit.summary.elapsed_seconds`,同一输入两次跑出不同 `result_hash`(BUG-694)。末尾按交付对象重绑 + 显式排除集写进回执 + 质量门补覆盖校验;上游 09-14 的有界投影实测收益 < 0.1 秒,不抄 | 待领取 | — |
### 前端基础与工程
@@ -0,0 +1,134 @@
# 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**,开工时复核一次最大号。