From dacd40448054d9f1a1fad731f511ad0823a7ec8d Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Sun, 20 Sep 2026 12:23:08 +0800 Subject: [PATCH] =?UTF-8?q?docs(tasks):=20=E8=B7=A8=E5=8D=88=E5=A4=9C?= =?UTF-8?q?=E5=80=99=E9=80=89=20Dasha=20=E8=BE=B9=E7=95=8C=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E4=B9=A6=EF=BC=88BUG-981=EF=BC=89+=20=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E8=A1=A5=E7=BC=BA=E5=8D=95=E9=AA=8C=E6=94=B6=E7=BB=93?= =?UTF-8?q?=E8=AE=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 验收 932f2fff:T1 完整重跑 900 组合逐例 0 差异、45 格表逐位相同;T2 独立复跑 五项指标与哈希全序全部一致;12 个打分文件字节未动、确认门仍关闭。执行方正确 推翻任务书 T2.3「首次口径干净的官方盲测」——与 BUG-428 防复发冲突,已核原文。 遗留立单:scoring_service.py 调 merge_transition_proximity() 只传一个 birth_date,该函数用它算全部候选的 dasha 起始日期,候选只靠 HH:MM 区分。 窗口跨午夜时午夜后候选边界整体错一天。生产调用链独立复现:23:50→00:10 的 21 个候选里,恰好 11 个跨日候选分数错、10 个同日候选逐位相同。 连带发现 calculation_spec() 不含打分实现身份,修复后同一 spec hash 对应 不同分数。本单会改生产打分,三个待决点未由产品填答不得开工。 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe --- docs/tasks/README.md | 3 +- ...ification-cross-midnight-dasha-20260920.md | 170 ++++++++++++++++++ 2 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 docs/tasks/TASK-rectification-cross-midnight-dasha-20260920.md diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 27cfd7d5..82452181 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -290,7 +290,8 @@ | — (产品口头拍板,无任务书) | `PROGRESS-settings-ui-20260919.md` | **设置面板布局与资料入口整理**:基线 `4f4cd684`;四分区继续共用固定 `.settings-modal`,桌面导航 176px→200px 并加分隔,内容区增加内边距,表单 cap 440px→560px,导航移除误导性右箭头;账户头像 48px→56px;“添加其他人”移到分组标题操作区。已同步 `frontend/DESIGN.md` 与合同测试;tsc 0、lint 0 error、定向测试 21/21;build 被 Windows Skill runtime symlink 权限阻塞,浏览器走查待受控环境;BUG-970 保持 `investigating` | 待验收 | `codex/settings-ui-20260919` | | `TASK-owner-case-purge-20260919.md` | `PROGRESS-owner-case-purge-20260919.md` | **上游库主案例与本机路径残留清除(只含本仓)**:镜像同步带进库主本人案例(敏感案例标识与本机路径)并被 `origin/staging` 命中,涉及无引用的 `versions/` 三快照、前端 fixture、Python 测试、整机扫描台账、会话转录及上游 SKILL 快照。运行时无特判不用动。产品拍板整体删除不留匿名版;fixture 统一虚构常量;`import_yinduzhanxing.py` 加隐私排除项 + 新增仓库级隐私守卫测试;上游 SKILL 快照等库主清完再重导入(BLOCKED 记录)。上游仓的清理指令另见 `UPSTREAM-INSTRUCTION-owner-case-purge-20260919.md`(交给库主,不在本仓执行)。BUG-972/973 | **已验收通过(2026-09-20,`497798ac`);未合入 staging,等产品放行** | 两轮:首轮未通过(1 个隐私守卫冲突:新增路径规则与答案键守卫冲突,该守卫在快速门 glob 内,合入会让门禁红)→ 修复单 `TASK-owner-case-purge-fix2-20260919.md` → `497798ac` 通过。Claude 在 Linux 全依赖环境独立复验:快速门 Python 步 859 passed / 0 failed(上一轮就是这步红),Python 全量 63 红与基线逐条相同、0 新红,收集数与删除清单已记录;tsc 0 / lint 0 error、120 warning 同基线 / `npm test` 失败清单为基线子集(少 1 条工作流 YAML,非回归)/ `○ /` Static、首屏 gzip 与基线字节相同(前端自首轮提交起零改动)。上游指令文件已逐字节还原为基线原文;12 个已删测试名与计数已落进度记录;BLOCKED 两条已划掉。校正 Skill 哈希包、注册表、上游快照、`frontend/src`、主 API 全程 0 改动;库主本机用户名 0 命中。遗留 P3:进度记录和 BUG 状态文字待后续对账修正 | | `TASK-consult-smalltalk-fastpath-20260920.md` | — | **普通对话寒暄轮快速通道**:真机一句「你好」触发完整窗口排盘(活动面板「已完成 4 步」)+ `## 先回答你的问题` + 400 字判词 + 扣 1 点。三层叠加:`index.ts` 两处「every turn 必调排盘」(BUG-922)与 `contractReady()` 的 `requireTool` 把排盘变成硬合同;`product-voice.ts` OPENER SHAPE 标「三种模式共用」;唯一的 chit-chat 豁免句只在 `natalSpokenReportContract` 里、只拼进本命 Agent(BUG-977)。计费侧「写回复」与「扣点」绑在 `complete_consultation_response` 同一次调用,`cancel` 只退款不写消息,所以今天没有「不扣点但保留对话」的通道。**产品拍板 a:不扣点、不排盘、回一句白话**;**分流不得用正则/关键词/长度阈值**,改为进 Agent 之前一次极短的结构化模型调用(复用本轮已选模型),fail-open 一律落回完整路径;BUG-922/923 的三处合同一个字不改;新增 `complete_consultation_free` 迁移。BUG-976/977 | 待领取 | — | -| `TASK-rectification-validation-integrity-20260920.md` | `PROGRESS-rectification-validation-20260920.md` | **生时校正验证体系补缺(纯离线评测,不改打分不改产品)**:会议要求把「推断真实出生时间」与「用户认可的参考盘」分开证明。核对结论——**产品口径侧四条已落地**(`accepted`≠`confirmed` 两条写入路径、确认门 fail-closed 且 `holdout` 为 `not_ready` 使 `confirmation_allowed` 不可能为真、采用不写 `reported_birth_time`、无任何把采用率当准确率的指标;运行时也无按生日走捷径的分支);**缺口全在评测本身**。三条:① 封存契约 `rectification_sealed_holdout.v1.json` 的三个打分哈希互不相同(封存 `f41c298d` / 契约记录 `99730c84` / 基线实测 `b15d9ea1`),`official_eval_trial_count: 0`——当前实现**从未产出过一次有效官方盲测**,唯一跑过那次已被资料审计作废(top-1 `0.15`,发布门要 `0.60`),可见的 `0.45` 自带「不得当发布指标」标记(BUG-978);② 全部离线评测的候选窗**以真值为圆心**(`_candidate_moments()`、`request_from_case()` 的 `true_time`),生产以申报时间为圆心(`ENGINE_SEARCH_RADIUS_MINUTES = 15`)——「真值掉出窗外」这一失败模式从不可见(BUG-979);③ v3 封存但每例仅 3 事件、v4 有 7+ 事件却已被看过并用于调参,**无口径干净又贴近真实会话的封存集**;六题回放的 `0.80/0.55/0.35` 是「真值方向最优答」的上帝视角上界,±30/±60 仍低于发布门(BUG-980)。T1 申报偏差敏感性 sweep、T2 有效重跑 + 契约对齐 + **防复发新测试**、T3 只出 v5 采集协议、T4 记录。**硬红线:不得改 12 个打分文件、不得用封存集调参、不得把 `status` 改 `ready`。** 家庭信息(父母职业/兄弟姐妹)在拿到基线数字前不开工——现有七领域全是带日期事件,静态属性没有输入口。前置:owner-case-purge 三提交仍未合入 staging。BUG-978~980 | 已合入(本批远端 SHA 核对后生效);独立盲测仍 blocked,门禁/部署待核验 | `codex/rectification-validation-20260920`;900 组合、当前冻结重跑、v5 协议完成;新增/桥接 28 pass,相关总集49 pass/1基线失败,确认门不变;2026-09-20 产品授权 push staging,同批交付,详见进度 | +| `TASK-rectification-validation-integrity-20260920.md` | `PROGRESS-rectification-validation-20260920.md` | **生时校正验证体系补缺(纯离线评测,不改打分不改产品)**:会议要求把「推断真实出生时间」与「用户认可的参考盘」分开证明。核对结论——**产品口径侧四条已落地**(`accepted`≠`confirmed` 两条写入路径、确认门 fail-closed 且 `holdout` 为 `not_ready` 使 `confirmation_allowed` 不可能为真、采用不写 `reported_birth_time`、无任何把采用率当准确率的指标;运行时也无按生日走捷径的分支);**缺口全在评测本身**。三条:① 封存契约 `rectification_sealed_holdout.v1.json` 的三个打分哈希互不相同(封存 `f41c298d` / 契约记录 `99730c84` / 基线实测 `b15d9ea1`),`official_eval_trial_count: 0`——当前实现**从未产出过一次有效官方盲测**,唯一跑过那次已被资料审计作废(top-1 `0.15`,发布门要 `0.60`),可见的 `0.45` 自带「不得当发布指标」标记(BUG-978);② 全部离线评测的候选窗**以真值为圆心**(`_candidate_moments()`、`request_from_case()` 的 `true_time`),生产以申报时间为圆心(`ENGINE_SEARCH_RADIUS_MINUTES = 15`)——「真值掉出窗外」这一失败模式从不可见(BUG-979);③ v3 封存但每例仅 3 事件、v4 有 7+ 事件却已被看过并用于调参,**无口径干净又贴近真实会话的封存集**;六题回放的 `0.80/0.55/0.35` 是「真值方向最优答」的上帝视角上界,±30/±60 仍低于发布门(BUG-980)。T1 申报偏差敏感性 sweep、T2 有效重跑 + 契约对齐 + **防复发新测试**、T3 只出 v5 采集协议、T4 记录。**硬红线:不得改 12 个打分文件、不得用封存集调参、不得把 `status` 改 `ready`。** 家庭信息(父母职业/兄弟姐妹)在拿到基线数字前不开工——现有七领域全是带日期事件,静态属性没有输入口。前置:owner-case-purge 三提交仍未合入 staging。BUG-978~980 | **已验收通过(2026-09-20,Claude 独立复算)**;独立盲测仍 blocked;门禁/部署待核验 | `932f2fff`。Claude 独立复核:T1 完整重跑 900 组合**逐例 8 字段 0 差异**、summary/specification 全等,另从 900 条逐例重算 45 格表与发布表逐位相同;T2 独立复跑 0.45/0.50/6.45/0/1.0/0.0 与哈希全序 0.05/0.10 全部一致;12 个打分文件字节未动、`status=not_ready`、`confirmation_coverage_rate=0`、实跑 `holdout_passed()`=False;新增/触及 Python 测试本机 38 条全绿;freshness 测试比对时排除 `frozen_at_utc`(BUG-693/694 教训已用上)。**执行方正确推翻任务书 T2.3**:「首次口径干净的官方盲测」与 BUG-428 防复发(已看过结果的案例不得再计入盲测)冲突,已核 BUG-428 原文,是任务书写错。环境缺口:本机 frontend 无 node_modules,tsc/lint/npm test/build 未能复核,采信执行方自报。遗留 → `TASK-rectification-cross-midnight-dasha-20260920.md`(BUG-981) | +| `TASK-rectification-cross-midnight-dasha-20260920.md` | — | **跨午夜候选的 Dasha 边界错一天(生产打分)**:`scoring_service.py` 调 `merge_transition_proximity()` 只传**一个** `birth_date`,该函数用它算全部候选的 Vimshottari / Narayana 起始日期,候选之间只靠 `_context_time()` 的 `HH:MM` 区分、**日期被丢掉**。窗口跨午夜时午夜后候选的 dasha 边界整体错一天。Claude 验收时在生产调用链独立复现:窗口 `23:50→00:10`、21 个候选,**恰好那 11 个跨日候选分数错、10 个同日候选逐位相同**(幅度 +0.0267 / −0.0133,本例头名未变)。算术上界 = `cap/kernel_width`,day 精度 0.067 分/件,18 件可累计约 1.2 分,而随分钟变化项总量仅约 2.1 分 —— **上界是推的不是实测,真实幅度本单必须实测**。踩中路径:`late_night` 时段 `23:00–03:59`、`unknown` `00:00–23:59`、23:45 后或 00:15 前申报的 ±15 窗。连带发现:`calculation_spec()` 不含打分实现身份,修复后同一 spec hash 对应不同分数,历史 Case 静默失去可复现性(同 BUG-427 类型)。**本单会改生产打分,与验证补缺单的红线相反,§3 三个待决点(修不修 / 要不要重新冻结重跑 / 历史 Case 怎么办)未由产品填答不得开工。**硬红线:只改「按候选日期取 dasha 起始」,不得动 kernel/cap/share 任一常数;确认门不变。BUG-981 | **待产品拍板** | — | ## 命名与归档 diff --git a/docs/tasks/TASK-rectification-cross-midnight-dasha-20260920.md b/docs/tasks/TASK-rectification-cross-midnight-dasha-20260920.md new file mode 100644 index 00000000..4589efbf --- /dev/null +++ b/docs/tasks/TASK-rectification-cross-midnight-dasha-20260920.md @@ -0,0 +1,170 @@ +# TASK · 跨午夜候选的 Dasha 边界错一天(生产打分,2026-09-20) + +> 状态:**待产品拍板后才可开工**。本单会**改动生产打分结果**,与 `TASK-rectification-validation-integrity-20260920` 的「不改打分」红线相反,必须由产品显式放行(§3 有三个待决点)。 +> 起因:执行 BUG-979 偏差评测时,独立审查在离线适配层发现该问题;Claude 2026-09-20 验收时在**生产调用链上独立复现**。 + +## 0. 基线与交付 + +- 基线:`origin/staging` = `932f2fff`(2026-09-20 实测核对)。 +- worktree `.worktrees/rectification-cross-midnight-20260920`,分支 `codex/rectification-cross-midnight-20260920`。 +- 改动落点:`scripts/rectification/**`、`tests/**`,可能含 `references/rectification_sealed_holdout.v1.json` 与 `docs/research/**`(取决于 §3 决策 B)。全部在 `deploy/gated-paths.txt` 内,推 staging 会触发门禁并重新发布镜像。 +- **串行依赖**:本单改的 `scripts/rectification/scoring_service.py` 与 `dasha_transition_proximity.py` 是 `frozen_scoring.files` 之外的文件,但改动会让 `932f2fff` 落盘的 T1/T2 数字失效(§3 决策 B)。开工前确认没有别的会话正在改同两个文件。 + +## 1. 事故实证 + +基线 `932f2fff`,符号定位。 + +### 1.1 缺陷位置 + +`scripts/rectification/scoring_service.py` 的 `build_event_contribution_matrix()` 在调用 `merge_transition_proximity()` 时,只传**一个** `scoring_request["birth_date"]`: + +``` +merge_transition_proximity(matrix_payload, scoring_request["events"], + static_contexts, scoring_request["birth_date"], ...) +``` + +`scripts/rectification/dasha_transition_proximity.py` 的 `merge_transition_proximity()` 把这个值用于**全部候选**:`vim_key = (birth_date, moon, lo, hi)`、`_vim_start_dates(birth_date, ...)`、`_narayana_start_dates(..., birth_date, ...)`。候选之间只靠 `_context_time()` 取出的 `HH:MM` 区分,**日期被丢掉**。 + +因此当搜索窗跨午夜时,午夜之后的候选仍按窗口起始日计算 Vimshottari / Narayana 起始日期,**全部 dasha 边界整体错一天**,事件邻近度得分随之错。 + +对照:同模块的静态星盘层读 `candidate_at`,日期是对的。**只有 transition-proximity 这一个 helper 吃单一 birth_date**(`scripts/research/reported_offset_sweep.py` 的 `score_window()` docstring 已记录这一边界)。 + +### 1.2 实测复现(Claude 验收时独立执行,基线 `932f2fff`) + +取公开 AA 案例,把窗口人为挪到 `23:50 → 00:10`(21 个候选,其中 11 个跨日),同一组 `static_contexts` 分别走生产路径与按候选日期分组的正确路径: + +| 项 | 实测 | +| --- | --- | +| 分数不同的候选数 | **11 / 21** | +| 分数相同的候选 | 10 个,全部是同日候选 | +| 偏移幅度 | `00:00`–`00:04` 为 `+0.0267`;`00:05`–`00:10` 为 `-0.0133` | +| 本例头名是否改变 | 否(`00:10` 两侧一致) | + +**错的恰好就是那 11 个跨日候选,同日候选一个都不差** —— 诊断精确坐实,不是浮点噪声。 + +### 1.3 上界是算术推的,不是实测 + +`dasha_transition_proximity.py` 常数:`DAY_KERNEL_DAYS = 15` / `DAY_MAX_POINTS = 1.0`,`MONTH_KERNEL_DAYS = 45` / `MONTH_MAX_POINTS = 0.35`,`VIM_SHARE = 0.6` / `NARAYANA_SHARE = 0.4`。 + +差一天导致的单事件最大偏移 = `cap / kernel_width`:day 精度 `1.0/15 ≈ 0.067` 分,month 精度 `0.35/45 ≈ 0.008` 分。 + +真实会话每例 5–18 件事。若多数为 day 精度且同向偏移,累计上界可达 **≈1.2 分**。对照 `docs/research/rectification_minute_resolution_closure_2026_09_14.md` 实测的**随分钟变化项总量约 2.1 分**,这个量级足以改变头名与并列关系。**1.2 分是算术上界,不是实测值**;1.2 节的实测样本只有 3 件事、以 month/year 精度为主,所以只错了 0.03。真实影响幅度**本单必须实测,不得沿用任何一侧的数字**。 + +### 1.4 谁会踩到 + +窗口跨午夜的真实路径(均已在 Bug 历史登记): + +- `period_only` 的 `late_night` 时段映射为 `23:00–03:59`(见 `BUG-3315` 所在记录)。 +- `unknown` 映射为 `00:00–23:59`。 +- 申报时间在 23:45 之后、或 00:15 之前,默认 ±15 分钟窗即跨午夜(`frontend/src/lib/rectification-agentic/v9/search-window.ts` 的 `ENGINE_SEARCH_RADIUS_MINUTES = 15`);放宽到 ±30/60/120 后覆盖面更大。 + +### 1.5 记录现状 + +该事实目前**只写在 `BUG-979` 的「独立审查追加」一行**与 `BLOCKED.md` 里,没有独立编号、没有 `investigating` 状态。`BUG-979` 自身是 `resolved`,其正文已声明「resolved 仅指离线缺少偏差维度的缺口……不代表生产跨日问题已修」。按 `AGENTS.md` §5.4,已确认事实必须有自己的记录与状态。 + +### 1.6 历史结果不可复现(连带发现) + +`scripts/rectification/scoring_service.py` 的 `calculation_spec()` 写入 `version` / `birthDate` / `candidateRange` / 经纬度 / 时区 / ayanamsa / nodeMode / minuteStep,**不含打分实现身份**。`api_service.py` 用它算 `calculation_spec_hash` 并随结果落库。 + +因此修复打分后,**同一个 `calculation_spec_hash` 会对应不同的分数**,历史 Case 静默失去可复现性。这与 `BUG-427` 的防复发条(「评测指标必须与产生它的 `implementation_sha256` 绑定记录」)是同一类问题,只是发生在生产结果侧而非评测侧。 + +## 2. 根因 + +1. `merge_transition_proximity()` 的接口把「出生日期」当成窗口级常量,而不是候选级属性。候选身份在 `_context_time()` 处被压成 `HH:MM`,日期信息在进入该函数时就已丢失。 +2. 全仓没有一条跨午夜窗口的**打分层**回归。`BUG-098` 记录里「跨午夜兼容」指的是**逐分钟枚举**,transition proximity 是后加的层,没有被那条覆盖。`tests/` 下无任何 transition / proximity 命名的测试文件。 +3. 生产结果的可复现性只绑定输入规格,不绑定打分实现身份,所以打分变更不会触发任何告警。 + +## 3. 决策记录(**三个待决点,未拍板不得开工**) + +- **A. 修不修?** 修复会改变跨午夜窗口下所有候选的分数,可能改变头名、并列关系与交付区间。不修则该缺陷继续存在于 `late_night`、`unknown` 与深夜申报路径。 + - Claude 建议:**修**。它是确定性错误,不是精度取舍。 +- **B. 修完要不要重新冻结并重跑 T1/T2?** `932f2fff` 的 900 组合与 v3 重跑成绩是用**当前(有缺陷的)**实现算的。修复后那些数字与实现不再对应。 + - Claude 建议:**要**。否则刚刚建立的「成绩必须可归属到实现哈希」这条纪律当场破功。重跑成本已实测:T2 约 10 秒,T1 全量 900 组合约 4 分钟。 +- **C. 历史已交付 Case 怎么处理?** 见 §1.6。三条路:(c1) 在 `calculation_spec` 里加打分实现身份并 bump `INPUT_CONTRACT_VERSION`,让新旧结果显式可区分;(c2) 只加身份字段不 bump,老结果标为「按旧实现产出」;(c3) 不动,接受静默分歧。 + - Claude 建议:**c1**。但它会让所有历史 Case 的 spec hash 变化,需确认不会影响历史会话打开(参照 `BUG-621` 的教训:版本绑定改动曾让历史校正打不开)。 + +以上三点由产品负责人在领取前填答;未填答的项,执行方**不得**自行假设。 + +## 4. 硬红线 + +1. **不得为了让修复看起来无影响而缩小窗口、改时段映射或回避跨午夜路径。** +2. 修复只允许改「按候选日期取 dasha 起始」这一件事。不得顺带调 `DAY_KERNEL_DAYS` / `MONTH_KERNEL_DAYS` / `*_MAX_POINTS` / `VIM_SHARE` / `NARAYANA_SHARE` 中的任何一个常数——那是打分权重,属 `rectification_minute_resolution_closure_2026_09_14.md` 已证伪的改法方向。 +3. **不得触碰确认门**:`status` 保持 `not_ready`、`confirmation_coverage_rate` 保持 `0.0`、`holdout_passed()` 保持 `False`。 +4. 不得把修复后的重跑成绩写成「独立官方盲测」——`BUG-428` 的红线不因换实现而解除(`official_valid_independent_blind` 保持 `false`、官方试次保持 `0`)。 +5. 缓存键必须跟着改:`vim_key` / `narayana_key` 现在以 `birth_date` 为首元素,改成按候选日期后,**必须确认缓存不会跨日期串用**,否则修了接口没修行为。 +6. 不得写入真实用户资料;实证只用已登记的公开 AA 案例。 +7. 不顺手升级依赖、不修不在本单内的 warning。 + +## 5. 任务分解 + +### T1 · 先写失败测试(BUG-981) + +在改实现之前,新增 `tests/test_dasha_transition_proximity_cross_midnight.py`: + +- 构造跨午夜窗口(如 `23:50–00:10`),断言**每个跨日候选**的 dasha 起始日期等于**该候选自己的日期**推出的值,而不是窗口起始日。 +- 断言同日候选的分数不受修复影响(回归保护)。 +- 断言缓存不跨日期串用(红线 5)。 + +验收标准:该测试在**修复前必须红**、修复后转绿;进度记录里贴出修复前的失败输出。 + +### T2 · 修实现 + +把候选日期传到底。参考实现是 `scripts/research/reported_offset_sweep.py` 的 `score_window()`(按候选日期分组、每组一次矩阵、合并后全窗排序),但**那是离线适配层,不要照搬结构**——生产侧应在 `merge_transition_proximity()` 内部按候选日期取 dasha 起始,避免多次重建矩阵带来的性能回退。 + +验收标准: +- T1 全绿;`tests/test_rectification_*.py`、`tests/test_active_rectification_*.py`、`tests/test_minute_rectification_*.py` 与基线逐条比对,0 新增失败。 +- **非跨午夜窗口的分数逐位不变**:取至少 3 个公开 AA 案例,修复前后分数字节相同。这是本单最重要的一条回归。 +- 性能不回退:同窗口打分耗时与基线相比不超过 +20%(基线实测:20 例 ±60 档 8.6 秒)。 + +### T3 · 按决策 B 重新冻结与重跑 + +仅在产品对 §3-B 答「要」时执行: + +- 重新计算 12 个 `frozen_scoring.files` 的实现哈希(修复若落在这 12 个文件内,哈希会变;若落在之外,需在报告里说明为何成绩仍变)。 +- 重跑 `scripts/research/sealed_holdout_rerun.py` 与 `scripts/research/reported_offset_sweep.py`,更新两份研究文档与 JSON,**旧数字保留并标注作废原因**,不删除历史。 +- 更新 `references/rectification_sealed_holdout.v1.json` 的 `current_tree_scorer`,`status` 仍为 `not_ready`。 + +验收标准:`tests/test_sealed_holdout_contract_freshness.py` 绿(它会因哈希漂移而红,这正是它存在的意义);两份研究文档的口径块含新哈希;`official_valid_independent_blind` 仍 `false`。 + +### T4 · 按决策 C 处理历史可复现性 + +仅在产品对 §3-C 答 c1 或 c2 时执行。c1 需额外验证:历史校正会话仍能打开(参照 `BUG-621`)。 + +验收标准:按所选方案给出对应证据;c1 必须有「历史 Case 可打开」的定向测试。 + +### T5 · 记录 + +- `docs/BUG_HISTORY.md` 新增 `BUG-981`,关联 `BUG-098`、`BUG-427`、`BUG-979`;说明 `BUG-098` 的「跨午夜兼容」只覆盖逐分钟枚举、不覆盖后加的 transition proximity 层。 +- 修正 `BUG-979`:在正文补一行指向 `BUG-981`,不改其 `resolved` 状态(它的 resolved 本就只指离线缺口)。 +- 顺带修正 `BUG-978` 的状态字符串 `resolved/partial` —— `AGENTS.md` §5 只认 `investigating` / `blocked` / `resolved` 三种,按实际改成其中之一并把「partial」的含义写进正文。 +- `BLOCKED.md` 里那条跨午夜条目改为指向 `BUG-981`,不删除。 +- `docs/tasks/README.md` 状态板加一行;实现合入 staging 的同一次推送里改状态。 +- `CHANGELOG.md`:若决策 A 为修,则记一行(跨午夜出生时段的候选打分修正,用户可感知为候选排序变化)。 + +## 6. 让步顺序 + +1. **最先保 T1 + T2**:失败测试与修复本身。没有 T1 就没有证据证明修对了。 +2. 其次 T3(重跑)。若产品对 B 答「暂不重跑」,则必须在两份研究文档顶部加一行「本文数字产出自已修复前的实现」,不得留着不说。 +3. T4 可以独立成单往后排,但 §1.6 必须进 `BUG-981` 正文,不得因为不做就不记。 +4. **不得砍掉 T2 的「非跨午夜窗口分数逐位不变」回归。** 砍了它,这次修复就无法与打分权重变更区分开。 + +## 7. 开工前置命令 + +```bash +git fetch origin --prune +git worktree add -b codex/rectification-cross-midnight-20260920 \ + .worktrees/rectification-cross-midnight-20260920 origin/staging +cd .worktrees/rectification-cross-midnight-20260920 +git log --oneline -1 # 必须是 932f2fff 或其后代 +python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45 +``` + +本单属 `AGENTS.md` §9 的引擎类任务,`pre_work_check.py` 必跑,并先读 `docs/research/pre_work_error_ledger.md`。 + +**必读**:`docs/research/rectification_minute_resolution_closure_2026_09_14.md`(尤其第 6 节三条提醒与「不得调打分权重」的结论)、`docs/research/reported_offset_2026_09_20.md` 的「初扫作废与独立复核」一节(离线侧是怎么修的、为什么不能照搬)。 + +环境备忘:本机无 `.venv`,系统 `python3`(3.13)可 `import swisseph`;frontend **无 `node_modules`**,前端侧检查需在有依赖的环境做。 + +## 8. BUG 编号起点 + +`docs/BUG_HISTORY.md` 当前最大号 **980**。本单从 **BUG-981** 起。开工时重新核对最大号,若已被占用则顺延并在进度记录里说明。