# TASK · 补漏单:把绑定排除集钉成字面量 + 记录 timezone 环境缺口 — 2026-09-15 - 基线:`origin/staging` @ `1d2aeffa`(BUG-693/694 实现已合入并**已由我验收通过**)。 - 分支:`codex/report-binding-scope-lock-20260915`,worktree `.worktrees/report-binding-scope-lock-20260915`。 - 关联:`TASK-report-provenance-binding-20260915.md`(本单是它的验收补漏)、BUG-694(本单补它的防复发条)。 - 串行:只加一个合同测试 + 改 `BLOCKED.md`。**不改任何生产代码行为**,与在途分支无重叠。 ## 1. 缘起(验收实证) BUG-693/694 的实现验收通过:三次 CLI 加三个 `PYTHONHASHSEED` 哈希全同(`784cdda5…`),变异法独立验证覆盖 33/40 顶层键,耗时中位数 2737 ms(修复前 2769 ms)。本单不推翻其中任何一条。 验收时发现两个**没有被任何机制挡住**的洞: ### 1.1 排除集的"只能三类"目前是纸面规矩 BUG-694 的防复发条写的是「排除集只能是自引用 / 生成时刻 / 墙钟三类……加第四类要改任务书」。实际约束链是: - 生产者:`scripts/calculation_profile_contract.py:81` / `:86` 定义 `ALLOWED_BINDING_EXCLUDED_TOP_KEYS` / `ALLOWED_BINDING_EXCLUDED_PATHS`,`:458` / `:459` 把它们原样写进回执。 - 质量门:`scripts/full_report_quality_gate.py:14-21` **import 同一对常量**,`:197` / `:200` 拿回执里的排除集减去这对常量判「意外排除」。 - 测试:`tests/test_calculation_profile_contract.py:585-586` 断言 `set(scope[...]) == set(ALLOWED_...)`。 三处引用的是同一个常量,所以**谁往常量里再加一条,门自动放行、测试自动变绿**。这条防复发现在只能挡住外来或手工篡改的包,挡不住它真正想挡的那件事(排除集悄悄变宽)。 本轮那次扩集就是例证:D2 原文只批了 `ai_and_audit.summary.elapsed_seconds` 一条墙钟路径,落地扩成了 `**.elapsed_seconds` 通配并新增 `**.called_at` / `**.cache_created_at` / `**.cache_expires_at`。理由(跨进程可复现)成立、也写进了 BUG-694,但任务书没有被改,也没有任何东西要求改。 ### 1.2 `--profile quick` 在本机必红一条,没人记 ``` FAILED tests/test_api_server_security.py::test_shadbala_endpoint_returns_ranked_planet_strength WARNING [api_server] shadbala advanced layer failed: timezone inference dependency unavailable E assert ['core_sixfold'] == ['core_sixfold', 'advanced_evidence'] ``` `739 passed, 1 failed`。同一条在修复前的 `039b0a26` 上**同样红**,与 BUG-693/694 无关。`BLOCKED.md` 里没有这条;实现方进度记录里写的是自己机器缺 `mcp`,也没进 `BLOCKED.md`。结果是下一个人跑快速门会重新查一遍同一个环境问题。 ## 2. 决策记录 | 决策 | 内容 | | --- | --- | | D1 | **不动现有排除集的内容。** 四条墙钟/时间戳路径按 BUG-694 的记录保留,本单不重新开会讨论它们该不该在里面。 | | D2 | **把当前允许集的字面量钉进合同测试**:常量被改动(增、删、改名)即测试红,由人决定放不放行,而不是自动通过。这是本单唯一的行为约束。 | | D3 | **不把这条做成运行时校验**(不在生产代码里再写死一份清单)——两份清单会漂移,且门在运行期要挡的是外来包,语义不同。锁在测试层。 | | D4 | **`BLOCKED.md` 如实记录 timezone 缺口**,写明失败的测试名、warning 原文、以及"修复前同样红"这个对照证据。不得写成通过,也不得为了让门变绿去改那条断言。 | | D5 | 不新增 BUG 编号。本单是 BUG-694 防复发条的补强,落在原记录上。 | ## 3. 硬红线 1. **不改生产代码。** `scripts/calculation_profile_contract.py`、`scripts/full_report_quality_gate.py`、`scripts/jyotish_engine.py` 一行不动。若发现不改生产代码就锁不住,停下来写进度记录说明,不要自行改。 2. 不得放宽或删除 BUG-693/694 的既有判据与断言。 3. `tests/test_calculation_profile_contract.py` + `tests/test_full_report_quality_gate.py` 保持全绿;`--profile quick` 除 §1.2 那条 timezone 缺口外不得新增失败。 4. 不改 `.gitea/workflows/**`,不提升 `main`。 ## 4. 任务分解 ### 任务 1 · 允许集字面量锁(P1) - 在 `tests/test_calculation_profile_contract.py` 新增一条合同测试,断言两个常量的**完整字面量与顺序**等于当前值: - `ALLOWED_BINDING_EXCLUDED_TOP_KEYS == ("generated_at", "report_quality_gate", "shared_full_report_authority")` - `ALLOWED_BINDING_EXCLUDED_PATHS == ("ai_and_audit.summary.elapsed_seconds", "**.elapsed_seconds", "**.called_at", "**.cache_created_at", "**.cache_expires_at")` - 测试里写明三类归属与"改这里要先改任务书并在 BUG-694 补记"的注释,不要只留一行断言。 - 验收标准:本地把常量任意加一条(例如 `**.generated_by`)→ 该测试红;恢复后绿。进度记录贴这两次的输出。 ### 任务 2 · 环境缺口入账(P1) - `BLOCKED.md` 新增一节,按 D4 写清:失败测试名、warning 原文、`739 passed, 1 failed` 的数字、以及"在 `039b0a26`(修复前)上同样红"这条对照证据,并注明**这不是 BUG-693/694 的回归**。 - 同时写明它对验收的影响:本机 `--profile quick` 的通过口径是"除这一条外全绿",谁要宣称快速门全绿,必须先补上 timezone 推断依赖。 - 验收标准:条目里能直接复制出复现命令;不出现"通过"字样。 ### 任务 3 · 记录(P2) - `docs/BUG_HISTORY.md` 的 **BUG-694**:更新「最近更新」日期,防复发条补一句——允许集有字面量合同测试守门,改动必须先过测试再改任务书。不新增编号、不改原有的现象/根因/修复正文。 - `docs/tasks/PROGRESS-report-binding-scope-lock-20260915.md`:贴任务 1 的红/绿两次输出与 `--profile quick` 的失败清单。 - `CHANGELOG.md` 不写(对用户不可感知)。 ## 5. 不在本单范围(验收时记下的观察项,不要顺手改) - `report_quality_gate` / `shared_full_report_authority` 现在挂在 `sanitize_professional_report_reference` 之后,不再过脱敏。实测差异只有 `limitation_reference: null` 这类空值被保留,无路径或内部字段泄漏,前端不消费这两个键。**本单不动**;要改需单独一单并说明"交付对象最后必过一遍 sanitize"这条保证要不要恢复。 - `cmd_yoga` 的 `list(set(...))` → `sorted(...)` 是 BUG-694 的第二根因修复,已记录在案,**不要回退**。 - BUG-692 那条线:候选数 = 1 时会出现「还剩 … 里 1 个候选。按现有信息分不开。」自相矛盾(修复前说「这两分钟」,同样错,不是回归)。属于 `TASK-rectification-invite-copy-and-assertions-fix-20260915.md` 的后续,不在本单。 ## 6. 让步顺序 1. 若字面量锁与将来某次合法扩集冲突:先改任务书/BUG 记录,再改测试字面量,顺序不得颠倒。 2. 若 timezone 依赖能在本机装上(需要联网或系统包),**优先装上并让那条测试真绿**,`BLOCKED.md` 只保留"曾经缺、现在已补"的一行说明;装不上再按任务 2 入账。 3. 任何情况下不得通过修改 `test_shadbala_endpoint_returns_ranked_planet_strength` 的断言来让门变绿。 ## 7. 开工前置命令 ```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-binding-scope-lock-20260915 \ .worktrees/report-binding-scope-lock-20260915 origin/staging cd .worktrees/report-binding-scope-lock-20260915 /workspace/Jyotisha/.venv/bin/python -m pytest \ tests/test_calculation_profile_contract.py tests/test_full_report_quality_gate.py /workspace/Jyotisha/.venv/bin/python scripts/run_quality_gate.py --profile quick # 预期:739 passed, 1 failed(timezone) ``` ## 8. BUG 编号起点 **本单不新增编号**(D5)。`docs/BUG_HISTORY.md` 当前最大号 BUG-694;只更新 BUG-694,不要占 695。若执行中发现真实回归,再按当时最大号 +1 新开。