docs: 回填跨午夜日期锚点的合入状态与验收结论

b85c4a68 已由 Claude 独立验收后快进合入 staging。BUG-982/983 的「修复版本」
原写「本地未提交实现,未推送/部署」,与实际不符,改为记录实现 SHA 与合入
事实;两条状态仍保持 investigating,因为部署与真人验收未完成。

验收证据回填状态板:用当初复现两条 Bug 的同一探针重跑——983 的申报分钟
落回申报日、late_night 300 个候选全在同一日两段;982 的跨午夜三分钟簇由
1440 变 3,同日两段为 300 未糊成一段。非跨午夜同机 A/B 6/6 逐位相同,新
测试基线 6 红修复后全绿,Python 组 216→272 零失败,tsc 0。

同时记下遗留待确认项:无 cluster_intervals 的行仍走旧按钟点算法,需确认
除修复前旧缓存外没有别的生产路径会产生无日期行。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
This commit is contained in:
Jesse_Chen
2026-09-21 09:25:56 +08:00
co-authored by Claude Opus 5
parent b85c4a686a
commit 27a6c39b94
2 changed files with 3 additions and 3 deletions
+2 -2
View File
@@ -12982,7 +12982,7 @@
- 防复发:后续需完整日期/相对窗序号回归贯穿签名聚类、候选决策与交付范围,不能只测试辅助 `_primary_cluster`
- 相关记录:BUG-623、BUG-624、BUG-639、BUG-981。
- 复发自:未发现同症状既有记录;BUG-624/639 的范围断言仍在但均是同日样本,跨午夜诊断测试覆盖的是另一条聚类路径,未覆盖本路径。
- 修复版本:`codex/rectification-midnight-date-anchor-20260920` 本地未提交实现,未推送/部署;证据及待验项见 `docs/tasks/PROGRESS-rectification-midnight-date-anchor-20260920.md`
- 修复版本:实现 `b85c4a68`2026-09-21 经 Claude 独立验收后快进合入 `staging`;**部署与真人验收尚未完成**,故状态保持 `investigating`证据及待验项见 `docs/tasks/PROGRESS-rectification-midnight-date-anchor-20260920.md`
## BUG-983 | 凌晨申报窗口的候选日期锚点可能偏到次日
@@ -13000,7 +13000,7 @@
- 防复发:必须从申报日/分钟经实际前端请求构建到后端枚举端到端校验日期,而非只验证钟点范围与“支持跨午夜”;扩窗不得重猜日期或将未知缩窄结果伪作用户选侧。
- 相关记录:BUG-098、BUG-198、BUG-979、BUG-981。
- 复发自:未发现同症状既有记录;BUG-198 时段开窗与 BUG-098 枚举回归仍在但不校验申报日期中心。离线 holdout 已有前日中心保护,未覆盖生产请求链。
- 修复版本:`codex/rectification-midnight-date-anchor-20260920` 本地未提交实现,未推送/部署;证据及待验项见 `docs/tasks/PROGRESS-rectification-midnight-date-anchor-20260920.md`
- 修复版本:实现 `b85c4a68`2026-09-21 经 Claude 独立验收后快进合入 `staging`;**部署与真人验收尚未完成**,故状态保持 `investigating`证据及待验项见 `docs/tasks/PROGRESS-rectification-midnight-date-anchor-20260920.md`
## BUG-984 | 时段旧分数缓存不校验算法身份且聚合回执可能显示新版本
+1 -1
View File
@@ -295,7 +295,7 @@
| `TASK-rectification-cross-midnight-gate-fix-20260920.md` | `PROGRESS-rectification-cross-midnight-gate-fix-20260920.md` | **review 修复单:门禁级浮点哈希断言(`aa46da10` 的合入阻塞项)**:新增回归把 121 个分数的 SHA-256 写死成字面量,于是断言范围把跨机 libm/pyswisseph 差异也圈了进去。实测该组 121 个分数**全无浮点尾噪**(`repr(s)==repr(round(s,4))` 121/121),所以差异是第 4 位真的不同,靠改序列化消不掉。同一提交刚改过的 `test_rectification_engine_memoization.py` docstring 正好写着「跨机舍入已漂 1.1e-3,不得整体 `==` 比较」——教训被引用了又被踩。执行方自己的证据 JSON 用的却是正确做法(`scores_bytes_equal` = 同机基线 vs 当前)。**根因**:要证的是相对不变量(同机基线 vs 当前),却实现成绝对不变量(当前 vs 某台机器的历史哈希)。F1 改用生产代码已有的 legacy 回退路径做同机 A/B 对照(去掉 `candidate_at` 即修复前行为),验收要求**两台浮点环境不同的机器各跑一次**、且回退核心修复后该测试仍绿(证明它守的是同日不变性而非跨午夜回归的替身);F2 bridge 重复收集(4 failed = 2×2);F3 查证 `block_scan` 重算是否经过被修 helper —— 会则 BUG-981 在 late_night/unknown 路径等于没上线、BUG-984 升为阻塞项;F4 记录。**硬红线:不得删测试/skip/把哈希改成本机当前值消红,不得改已验收的打分代码。** 串行:本单 → `aa46da10` 合入 → BUG-984 补单。BUG-985 | **已 review 通过;BUG-985 resolved;合入推送被 Gitea 认证阻塞,远端仍 a3577ce2** | 实现 `25232ce4`(分支 `codex/rectification-cross-midnight-gate-fix-20260920`)。F1 改为同进程 A/B:在 `scoring_service.merge_transition_proximity` 调用边界剥掉 `candidate_at` 走生产既有 legacy 回退,对比 121 个分数与整份贡献矩阵的 canonical 字节;另加 `legacy_calls == [121]` 与 `static_contexts is contexts` 两道防空转保险。Claude 复核:写死哈希字面量 **0 残留**;**第三套环境(Linux + Python 3.13,与执行方 Windows 3.11.7 不同)定向 18 条全绿** → 两机验收闭环;**回退日期修复探针:同日 3 条全绿、跨午夜相关 4 条转红**,证明同日不变性与跨午夜正确性已真正分开;快速门 glob staging `1b646659` 200 passed/0 failed → `25232ce4` 216 passed/0 failed+16,零新增失败,总数未降);相对合并点仅动 2 个测试文件 + 文档,`scripts/`、前端、golden、打分常数零改动。BUG-985 记录含我要求的防复发条,并正确认定为 BUG-733 同形复发。**建议 BUG-985 由 `blocked` 改 `resolved`**(证据即第三环境复跑)。环境缺口:完整快速门在 Claude 机器 120 秒超时被杀,6→4 那组数以执行方记录为准 |
| `TASK-rectification-cross-midnight-dasha-fix-20260920.md` | `PROGRESS-rectification-cross-midnight-fix-20260920.md` | **BUG-984 缓存与结果身份补单(BUG-981 的端到端阻塞项)**:`scoreAndPersistCurrentEvidence()` 的 `block_scan` 分支只比 `evidenceLedgerFingerprint` 即返回 `cached:true` 与旧 `algorithmVersion`,该返回发生在 `readV9EngineScoringIdentity()` **之前**`minute` 分支则有身份门。F3 已查证完整调用链到 `merge_transition_proximity()`,故核心修复合入后,**证据未变的历史跨午夜时段缓存命中仍返回修复前分数**——在本单闭环前不得声称跨午夜问题已修。边界已按源码收窄:`late_night`(23:0003:59) 跨日;`unknown`(00:0023:59) 虽 >120 分钟但**本身同日**,选中跨午夜子时段后才触发(此处修正了 Claude 先前把两者并列的说法)。产品 2026-09-20 放行且**同日拍板 F3 策略选 b**:版本接口取不到可信身份时,旧缓存**只读展示 + 显著标注「按旧算法产出」**,否决 a(重算,会把接口抖动放大成长等待,时段扫描受 `JYOTISH_HEAVY_COMPUTE_CONCURRENCY=2` 限流)与 c(照常复用,与已定原则冲突)。b 的三条边界:只读结果**服务端拒绝采用/确认**(靠删不靠藏,须有定向用例)、标注必须用户可见并对照 `VOICE.md`(涉界面同提交更新 `DESIGN.md`)、回执来源身份仍是产出它的版本。**F2 的 SQL 问题已查清并定序(A 先上 / B 兜底 / 第 10 版前必须解决)**:`engine_version` 一个字段被「部署声称的版本」(started/failed 行,取前端常量)与「实际产出结果的版本」(completed 行,命中旧缓存即旧版本)共用,聚合却用与版本先后无关的字符串 `max`。**该缺陷此前一直撞对,`aa46da10` 之后才变真错**:它把 `v9EngineVersion()` 缺省由 `rectification-v5` 改为 `…scoring-8`,started 行遂在字符串序上反超 completed 行 → 回执显示第 8 版而分数来自第 7 版缓存;已核 `deploy/`、`.gitea/` 未设 `RECTIFICATION_ENGINE_VERSION`,走缺省,**是真实行为**。第二个缺陷:实跑 `max("…-10","…-9") = "…-9"`,**该聚合在第 10 版静默反向**(现为第 8 版)。Astarted/failed 不再写版本(应用层,必做);A 的漏洞(同 turn 多个 completed 行版本不同)**必须实测取证,不得以「应该不会」结案**;B=聚合改取成功结果那一行(只改函数体,向后兼容);C=拆列本单不做。**四项全部可开工。**硬红线:不重标/不删历史结果,不 bump Skill,不改 V4 input contract,不引入按 `engineVersion` 拒绝打开历史会话。串行:BUG-985 合入 → 本单。BUG-984 | **本地完成:diagnostics追加修复后全量3575→3588全绿,标准DB40/40Static/完整28资源gzip+0.040085%tsc/lint通过;主会话独立DB40与最终定向40全绿。未推送,真人/部署待主会话** | `codex/rectification-cross-midnight-fix-20260920`;前置认证已解除,代码基线 `3f39bafc`、文档基线 `f09f3d80`;本轮未推送 |
| `TASK-rectification-midnight-final-grok-20260921.md` | `PROGRESS-rectification-midnight-date-anchor-20260920.md` | **日期锚点最终验收接续(Grok)**:测试契约复验、扩展身份四模块、真实新 freeze 与 20/900、final-3 全门。不 commit/push/deploy。 | **待验收**:隔离 Linux 门通过(tsc/lint0、前端3649、DB56、quick、AA 21/21、0 新增 Python 失败);四既存失败仍在;真人/部署缺口见 BLOCKED | `codex/rectification-midnight-date-anchor-20260920`,未commit/push |
| `TASK-rectification-midnight-date-anchor-20260920.md` | `PROGRESS-rectification-midnight-date-anchor-20260920.md` | **跨午夜的日期锚点与簇跨度(BUG-982 + BUG-983 合并一单)**:两条同根——线上契约只传钟点、日期不在契约里,后端按钟点大小**猜**哪端跨日。**BUG-983 是静默算错盘**`_candidate_datetimes()` 把起始钟点无条件绑在申报日期上,前端零补偿。Claude 实测(申报日 2000-06-15):申报 `00:10` → 候选 `06-15 23:55`…`06-16 00:25`**申报分钟本身落在 `06-16 00:10`+1 天**`00:02` 同样 +1 天;`12:00` 对照正确。受影响:申报落在午夜后半径内者(±15≈1.0%、±30≈2.1%、±60≈4.2%、±120≈8.3%);`late_night`(23:0003:59) 更重,其 `00:00``03:59` 共 240 分钟**全部落在用户没申报过的日期上**。**BUG-982 影响面比原记录大**:同缺陷在链路上出现三处(`_cluster_span` / `unionStillValidRange` / `indistinguishableWidthMinutes`),原记录只写了 Python 两处。实测跨午夜簇 `23:58/23:59/00:00` 报告宽度 **1440 分钟**(真实 3)、`23:50/23:55/00:05` 报 **1431**(真实 16);该链经 `reportWidth` 直达 **交付卡「范围 X 分钟」**,深夜用户会看到候选其实差几分钟却被告知范围一千多分钟。**与 BUG-981 的关系**:981 让每个候选按自己日期算 Dasha 是对的,但 983 给的日期本身就错,**983 不修则 981 在申报近午夜的场景等于没上线**。**产品 2026-09-20 三点全部拍板**D1 `late_night` **先问用户「午夜前还是午夜后」,答不上退「同日两段」`[D 00:0003:59] [D 23:0023:59]`,一律不得跨到 `D+1`**(连锁范围:退路产生**不连续候选集**,枚举/聚类/簇跨度/交付区间/宽度/交付卡都要支持两段,**不得用「取两段最小到最大」糊成一段**——那正是 982 的同型错误);D2 **允许窗口跨到前一天**,但交付层必须说明「若落在 23:5x 则出生日期是前一天」,不得静默改用户的出生日期(依据 Part B B4 真值覆盖率优先);D3 **加窗口相对序号**,排序/取首尾/算跨度一律用序号、钟点只作展示 —— 序号入契约会改候选身份与缓存指纹,**必须核对 BUG-984 的 `scoringIdentityMatches` 并确认历史结果只读不被重标**。硬红线:不调打分常数/确认门阈值;不得以排除真值换窄宽度;不得只修 `_cluster_span` 就宣称 982 已修;非跨午夜必须逐位不变且用同机 A/B(不得写死跨机浮点哈希,见 BUG-985)。沿用 BUG-982/983,不新开号 | **待验收(Grok 接续全门已跑)**:隔离 Linux final-3 tsc/lint0、前端3649、DB56、quick、AA 21/21、Python 广域 0 新增失败;未commit/push/deployBUG-982/983 仍 investigating | `codex/rectification-midnight-date-anchor-20260920`,未commit/push/部署 |
| `TASK-rectification-midnight-date-anchor-20260920.md` | `PROGRESS-rectification-midnight-date-anchor-20260920.md` | **跨午夜的日期锚点与簇跨度(BUG-982 + BUG-983 合并一单)**:两条同根——线上契约只传钟点、日期不在契约里,后端按钟点大小**猜**哪端跨日。**BUG-983 是静默算错盘**`_candidate_datetimes()` 把起始钟点无条件绑在申报日期上,前端零补偿。Claude 实测(申报日 2000-06-15):申报 `00:10` → 候选 `06-15 23:55`…`06-16 00:25`**申报分钟本身落在 `06-16 00:10`+1 天**`00:02` 同样 +1 天;`12:00` 对照正确。受影响:申报落在午夜后半径内者(±15≈1.0%、±30≈2.1%、±60≈4.2%、±120≈8.3%);`late_night`(23:0003:59) 更重,其 `00:00``03:59` 共 240 分钟**全部落在用户没申报过的日期上**。**BUG-982 影响面比原记录大**:同缺陷在链路上出现三处(`_cluster_span` / `unionStillValidRange` / `indistinguishableWidthMinutes`),原记录只写了 Python 两处。实测跨午夜簇 `23:58/23:59/00:00` 报告宽度 **1440 分钟**(真实 3)、`23:50/23:55/00:05` 报 **1431**(真实 16);该链经 `reportWidth` 直达 **交付卡「范围 X 分钟」**,深夜用户会看到候选其实差几分钟却被告知范围一千多分钟。**与 BUG-981 的关系**:981 让每个候选按自己日期算 Dasha 是对的,但 983 给的日期本身就错,**983 不修则 981 在申报近午夜的场景等于没上线**。**产品 2026-09-20 三点全部拍板**D1 `late_night` **先问用户「午夜前还是午夜后」,答不上退「同日两段」`[D 00:0003:59] [D 23:0023:59]`,一律不得跨到 `D+1`**(连锁范围:退路产生**不连续候选集**,枚举/聚类/簇跨度/交付区间/宽度/交付卡都要支持两段,**不得用「取两段最小到最大」糊成一段**——那正是 982 的同型错误);D2 **允许窗口跨到前一天**,但交付层必须说明「若落在 23:5x 则出生日期是前一天」,不得静默改用户的出生日期(依据 Part B B4 真值覆盖率优先);D3 **加窗口相对序号**,排序/取首尾/算跨度一律用序号、钟点只作展示 —— 序号入契约会改候选身份与缓存指纹,**必须核对 BUG-984 的 `scoringIdentityMatches` 并确认历史结果只读不被重标**。硬红线:不调打分常数/确认门阈值;不得以排除真值换窄宽度;不得只修 `_cluster_span` 就宣称 982 已修;非跨午夜必须逐位不变且用同机 A/B(不得写死跨机浮点哈希,见 BUG-985)。沿用 BUG-982/983,不新开号 | **已验收通过并合入 staging**2026-09-21`b85c4a68`);部署与真人验收待完成 | Claude 独立复核(用当初复现两条 Bug 的同一探针重跑):**BUG-983** 新契约下申报 `00:10`/`00:02` 的申报分钟落在**申报日**(+0 天),`late_night` 300 个候选**全在同一日**、两段;**BUG-982** 跨午夜 3 分钟簇 → **3 分钟**(原 1440),同日两段 → **300 分钟**(未糊成 1440)。三处(`decision_policy.py`/`credible-range.ts`/`candidate-plateau.ts`)均已改。**非跨午夜分数同机 A/B 6/6 逐位相同**;新测试在基线 6 条红、修复后全绿;Python `test_rectification_*` 基线 216 → **272 passed / 0 failed**`tsc --noEmit` 退出 0。红线全清:打分常数零改动、`status=not_ready`、coverage 0、`holdout_passed()` False、Skill 未动、`official_eval_trial_count` 仍 0。D1 追问 4 选项含「不知道」「跳过」,均退同日两段、从不去 `D+1`,明写不计分可跳过;D2 交付层显示「(前一天)」;D3 用 `window_index`/`segment_index``interval_union_width` 按段求和(注释 “never fill a declared segment gap”)。超出要求的设计:`candidate_intervals` 拒绝重复钟点;engine-client 对 dated 路径 fail-closed 抛 `engine_invalid_candidate_decisions`。环境缺口:3 条前端测试在 Claude 机器红,但**同样的 `@/` 别名错误在既有测试上一模一样复现**(`ephemeris-route`、`api-service-unavailable`),非回归;无 Docker 故 `test:db` 未跑。**遗留待确认**:无 `cluster_intervals` 的行仍走旧按钟点算法(宽度仍 1440),应确认除修复前旧缓存外没有别的生产路径会产生无日期行 |
## 命名与归档