test(rectification): compare same-day scores against legacy path in process
Replace machine-specific float hashes with strict score and matrix byte comparisons. Keep quick bridge coverage and document duplicate collection. Verify Windows/Linux float behavior and in-memory candidate-date reversal; record the separate-machine acceptance gap and BUG-984 end-to-end blocker. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
# BLOCKED
|
||||
|
||||
## 跨午夜门禁修复:独立机器补验与旧缓存闭环(2026-09-20)
|
||||
|
||||
- BUG-985 的同进程对照已在 Windows 与同机隔离 Linux 容器通过;Linux 原测试 ordinal 2/3 确实失败,证明浮点环境不同。但不是两台独立机器,严格按任务书保留第二台 Linux 机器/runner 的本轮提交复验缺口,不将同机容器算成另一台机器,也不据此推 staging。
|
||||
- Windows quick 基线与修改版均因缺 mcp 停在 source inventory;Linux 完整依赖的 quick 结果另见本轮进度。开工 focused 的历史镜像路径断言仍是已知环境失败,不制造目录、不削弱断言。
|
||||
- F3 已证实时段真实重算经过已修 helper;旧跨午夜时段缓存证据未变时会绕过新算法,BUG-984 升为 BUG-981 端到端验收阻塞项。只限缓存命中,不能声称所有时段新算都未修;unknown 全天初始窗同日,不把它作为跨日复现。
|
||||
- 证据与补验:`docs/tasks/PROGRESS-rectification-cross-midnight-gate-fix-20260920.md`、`docs/testing/rectification-cross-midnight-gate-fix-20260920.md`。本单不越界实施缓存/SQL,也未部署或改 main。
|
||||
|
||||
## 跨午夜修复:既有上下游日期与回执缺口(2026-09-20)
|
||||
|
||||
- BUG-981 本轮只修 `candidate_at` 到 Dasha 辅助评分的日期一致性。独立审查确认两个更上/下游既有缺口,不应扩大“已修复”口径:跨午夜同簇成员按钟点排序会把短簇记成全天宽度(BUG-982);凌晨申报生成跨午夜窗时,生产枚举将起点绑定申报日,中心候选可能错到次日(BUG-983)。都不在本单仅修 helper 日期的授权内,未顺改开窗、枚举、聚类或交付策略。
|
||||
|
||||
+20
-1
@@ -12960,6 +12960,7 @@
|
||||
- 修复:按产品已批准的任务书,先红测再改候选级日期与缓存键,不调权重;冻结重跑和 V9 版本标记同步实施。进度见 `docs/tasks/PROGRESS-rectification-cross-midnight-20260920.md`。
|
||||
- 验证:原实现新增测试 4 failed / 4 passed,修复后 9 项与 quick 桥接重复执行共 18 passed;另 3 项既有版本/枚举回归通过。19 个公开 AA 同日窗口、2299 个候选分数与矩阵字节不变;实跨日窗口全候选等于逐候选日期正确独算。性能配对中位 -0.87%,满足不超 +20%;口径 v3、raman/mean、±60、步长 1,旧 12 文件身份 `b15d9ea15227cd58`、新扩展生产身份 `7fffd1db612af1f2`。独立主会话日期/隐私补验 80 项通过。尚无部署证据,保持 investigating。
|
||||
- 验收边界:已有时段缓存与聚合回执身份导致 T4 未完整通过,见 BUG-984;另发现 BUG-982/983,不把候选级日期一致夸大为所有跨午夜路径端到端正确。
|
||||
- 独立 review:`aa46da10` 核心修复本身通过;新增回归的门禁级跨机浮点哈希问题单独立号 BUG-985。F3 已确认时段重算经过已修 helper,证据未变的历史跨午夜时段缓存却会绕过新算法,故 BUG-984 是本项端到端验收阻塞项,不以测试修复代替缓存或部署闭环。
|
||||
- 防复发:新增生产打分层跨午夜回归并接入 quick 收集;候选枚举正确不再被视为下游日期正确的充分证据。保留 BUG-427/428 的实现身份与已曝光边界。
|
||||
- 相关记录:BUG-098、BUG-427、BUG-428、BUG-621、BUG-978、BUG-979。
|
||||
- 复发自:BUG-098 的跨午夜兼容只覆盖逐分钟枚举,未覆盖后加的 transition-proximity 层;BUG-979 已在离线适配发现,但其范围禁止改生产,因而没有消除此生产缺陷。
|
||||
@@ -13006,10 +13007,28 @@
|
||||
- 影响面:V9 `block_scan` 缓存、工具活动回执与 turn 级聚合。
|
||||
- 用户现象:升级算法后,同证据的时段模式仍可能直接返回旧分数;开始活动显示新版、成功结果实际属于旧版,聚合字段又可能展示新版。
|
||||
- 触发条件:历史时段缓存证据指纹未变;本次运行默认版本已升,而缓存算法身份仍旧。
|
||||
- 根因:`scoreAndPersistV9Candidates()` 的时段分支在版本探测之前按证据指纹提前返回;成功回执使用旧结果身份,但 SQL 将不同阶段的 `engine_version` 取字符串最大值,而不是成功结果来源。分钟模式另有环境覆盖/探测失败回退边界,不能宣称身份无条件一致。
|
||||
- 根因:`scoreAndPersistCurrentEvidence()` 的时段分支在版本探测之前按证据指纹提前返回;成功回执使用旧结果身份,但 SQL 将不同阶段的 `engine_version` 取字符串最大值,而不是成功结果来源。分钟模式另有环境覆盖/探测失败回退边界,不能宣称身份无条件一致。
|
||||
- F3 调用链查证(2026-09-20):前端时段分支 → `runV9BlockScan()` → POST `/api/rectification/v5/block_scan` → `_compute_rectification_v5_block_scan()` → `api_service.block_scan()` → `score_candidates()` → `build_event_contribution_matrix()` → `merge_transition_proximity()`;时段支持率确实消费该候选分数。故本项升级为 BUG-981 端到端验收阻塞项。受影响的是证据未变、未触发三轮转分钟的历史跨午夜时段缓存命中;新建/无缓存/证据变化仍走已修路径。`late_night` 299 分钟跨日;`unknown` 1439 分钟虽同样走时段扫描,其首轮 `00:00–23:59` 本身同日,不能据此声称有跨日偏移,选中跨午夜时段后才触发。逐层行号见 `docs/tasks/PROGRESS-rectification-cross-midnight-gate-fix-20260920.md`。
|
||||
- 修复:未实施,当前 Dasha 单不顺带改缓存政策或 SQL;补单 `docs/tasks/TASK-rectification-cross-midnight-dasha-fix-20260920.md` 待批准后执行。
|
||||
- 验证:独立纯虚构 TS 探针使用替身 RPC/fetch,旧时段缓存返回 `cached:true`、算法尾号 -7、fetch 次数 0;真实工具调用路径记录 started 尾号 -8 / completed 尾号 -7。聚合 SQL 的 `max(engine_version)` 已核源码,未真跑 DB,因此不伪称数据库端到端通过。
|
||||
- 防复发:后续必须分别覆盖分钟/时段缓存、实际版本接口、部分环境覆盖与接口失败,成功回执必须绑定实际结果来源;历史打开与 Skill 绑定不变,不得重标或删除旧结果来掩盖问题。
|
||||
- 相关记录:BUG-427、BUG-621、BUG-981。
|
||||
- 复发自:未发现同症状既有记录;既有分钟算法身份缓存门不覆盖提前返回的时段分支,回执测试未组合新版 started 与旧缓存 completed。
|
||||
- 修复版本:未实施;本轮 T4 仅完成默认及后端身份同步,整体验收未通过。
|
||||
|
||||
## BUG-985 | 同日不变性回归写死浮点分数哈希导致跨机门禁失败
|
||||
|
||||
- 状态:blocked
|
||||
- 首次发现:2026-09-20
|
||||
- 最近更新:2026-09-20
|
||||
- 影响面:`tests/test_dasha_transition_proximity_cross_midnight.py` 及快速门 bridge;阻塞 `aa46da10` 的合入验收,不是打分修复本身的回归。
|
||||
- 用户现象:执行机器测试绿,换到不同浮点环境后 ordinal 2/3 红;选择源文件与 bridge 时重复为四项失败,门禁无法通过。
|
||||
- 触发条件:同日 121 个浮点分数的 canonical JSON SHA-256 与某台机器写死的历史字面量比较。
|
||||
- 根因:把“同环境新旧实现相同”的相对不变量写成跨环境绝对不变量。四位分数本身就可能不同,不是序列化尾噪。BUG-733 的既有防复发与同机差分测试仍在,但没有阻止新文件重引绝对浮点哈希,属同形复发。
|
||||
- 修复:保留三个 ordinal 和 121 个候选;A 使用真实 contexts,B 仅在 transition helper 边界去掉 `candidate_at` 进入既有 legacy 回退,主事件引擎继续使用完整 contexts;同机分数与矩阵 canonical 字节均严格相等,不加容差、不删测试、不 skip/xfail。bridge 顶部记录重复收集原因与独立用例数。
|
||||
- 验证:Windows 原测试 18 通过;隔离 Linux 原测试 14 通过/4 失败,复现跨环境差异。修改后两环境定向(含 memoization)均 32 通过、校正 glob 均 216 通过;内存回退候选日期后两环境均同日三项绿、真实跨午夜一项预期红。生产红线文件与历史 golden 本轮不改。
|
||||
- 验收缺口:Linux 是同一物理机上的容器,不冒充任务书要求的第二台独立机器;另一台 Linux 机器/runner 的本轮提交绿测未取得,因此不提前标 resolved。Windows quick 两侧缺 mcp;完整门禁结论见进度记录。
|
||||
- 防复发:同日/不变性类回归一律用同机相对比对,不得写死跨机浮点字面量;参照 `tests/test_rectification_engine_memoization.py` docstring 的跨机舍入教训。同日不变性与跨午夜正确性必须分开,以日期回退探针分别验证绿/红;桥接覆盖和重复计数必须明示。
|
||||
- 相关记录:BUG-981、BUG-733、BUG-712;F3 同步确认 BUG-984 为 BUG-981 端到端阻塞项。
|
||||
- 复发自:BUG-733(同进程可证的等价关系被跨机 golden 代替)。
|
||||
- 修复版本:`codex/rectification-cross-midnight-gate-fix-20260920` 本地实施,待独立机器验收及交付;详见 `docs/tasks/PROGRESS-rectification-cross-midnight-gate-fix-20260920.md`。
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
|
||||
Purpose: read this file before substantial project work. It exists to stop repeat mistakes caused by multiple Codex windows, WorkBuddy mirrors, local drafts, backup folders, and partial cloud-git visibility.
|
||||
|
||||
## 2026-09-20 · 跨午夜门禁回归的异环境复现
|
||||
|
||||
- BUG-985 将同日不变性写成跨机浮点哈希,Windows 原测试全绿,Python 3.13/glibc Linux 容器 ordinal 2/3 红;同进程新路径 vs legacy 路径逐位对照后两环境定向与校正 glob 通过。不得以更新本机哈希或容差消红。
|
||||
- Windows 原生 `python3` launcher 不可用、无项目 `.venv`,改用已验证的 Python 3.11.7;quick 两侧均缺 mcp。Docker 实际可用,独立 `python:3.13-bookworm` 容器按仓库要求安装依赖补验,不修改业务代码/依赖声明适配本机。
|
||||
- 同一物理机 Windows 与 Linux 容器虽浮点行为已实证不同,仍不等于任务书“两台机器”;独立 runner 补验未到位时必须保留 blocked。预检 focused 仍因历史镜像路径缺失失败,不伪造 `.workbuddy` 目录。
|
||||
- F3 查证不可跳过缓存命中边界:`block_scan` 重算会进修复 helper,但旧缓存可提前返回。unknown 的全日初始窗同日,不能由“跨度大于 120”推断“跨日期”。证据见本轮进度记录。
|
||||
|
||||
## 2026-09-20 · 跨午夜生产修复的身份与本机预检
|
||||
|
||||
- 任务书的“V9 `engine_version` 全仓只写不比”不完整:成功回执来自实际 `algorithmVersion`,已有 score cache 通过版本接口/环境覆盖比较算法身份。只 bump 前端默认串不能保证成功结果与旧缓存可区分;本单同步 bump 既有后端算法身份,不新增历史会话打开门,不重标旧缓存。
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# PROGRESS · 跨午夜门禁浮点断言修复(2026-09-20)
|
||||
|
||||
## 基线、范围与交付
|
||||
|
||||
- 执行单:`TASK-rectification-cross-midnight-gate-fix-20260920.md`(F1–F4)。用户要求同步代码执行该单,不扩大到 BUG-984 缓存实施。
|
||||
- fetch 后远端 `origin/staging = 1b646659e7c0dc07051b00d7e5c48633dee8498c`;按单从 `aa46da1016ffda65e4224af444a23710e084474a` 建独立工作树 `codex/rectification-cross-midnight-gate-fix-20260920`,再合并最新 staging 得到验证基线 `777bd53d`。仅四份文档有冲突,保留双方记录及最新 review 结论;没有覆盖主检出的未跟踪文件,没有 stash,也没有重写别的分支。
|
||||
- BUG 编号核对:远端 staging 最大 980,原实现最大 984;本轮使用 985。
|
||||
- 本轮仅改两个 Python 测试文件及任务/验收记录;相对 `777bd53d`,`scripts/`、前端、数据库、Skill、历史 golden、冻结报告与打分常数均不改。同步提交带入的其他前端改动来自已在 staging 的提交,不是本单改造。
|
||||
- 尚未推送 staging、未部署;BUG-981 端到端仍受 BUG-984 阻塞。不能用本单测试结果宣布生产跨午夜问题全部解决。
|
||||
|
||||
## F1 · 同进程新旧路径逐位对照
|
||||
|
||||
- 保留原测试名和三个 ordinal,每例仍覆盖 121 分钟。
|
||||
- A 使用真实引擎正常 contexts;B 复用同一批 contexts,只在 `scoring_service.merge_transition_proximity` 调用边界去掉 `candidate_at`,交给生产 helper 既有 legacy 回退路径。主事件引擎本身需要该字段,不能在主矩阵入口直接删除。
|
||||
- 检查候选完整时间序列与窗口一致、所有候选日期等于请求日期、legacy 边界被调用且收到 121 项。最终分数及贡献矩阵均做 canonical JSON 字节严格相等,不使用容差或硬编码浮点哈希。
|
||||
- 既有防复发核查:BUG-733 的 memoization docstring、同进程四层缓存差分和分档 golden 比较仍在;BUG-712 的坐标量化及缺 golden 失败测试仍在。此次是 BUG-733 同形复发,原规则只约束既有测试,没有阻止新文件引入绝对哈希。
|
||||
|
||||
### 既有断言变更三栏
|
||||
|
||||
| 原值 / 方式 | 新值 / 方式 | 原因 |
|
||||
| --- | --- | --- |
|
||||
| 三个 ordinal 各等于某台机器预先生成的分数 SHA-256 | 同一进程 A 新路径与 B 生产 legacy 路径,121 个分数 canonical 字节严格相同;另加整份贡献矩阵字节相同 | 要守的是“同日新旧不变性”,不是跨机器浮点字节一致;没有加容差、skip 或删除场景 |
|
||||
| 窗口内只有一个日期 | 该日期集合恰等于请求 `birth_date`,且真实 contexts 时间序列等于窗口枚举 | 防止单日但锚点错误时 A/B 无意义 |
|
||||
|
||||
## F2 · 快速门桥接
|
||||
|
||||
保留 re-export 并在文件顶部说明用途与重复计数:单独运行源文件或 `test_rectification_*.py` 中的 bridge,各为 9 项;两者同时选择为 18 项,但只有 9 个独立场景。未移除 quick glob 的覆盖,也不把重复项记成新增测试。
|
||||
|
||||
## F3 · 时段扫描调用链与 BUG-984 定级
|
||||
|
||||
**确认可达,因此 BUG-984 是 BUG-981 端到端验收阻塞项。** 限定为证据未变且尚未触发三轮转分钟的历史跨午夜时段缓存命中;新建、无缓存或证据改变会进入已修路径。未修改缓存策略或 SQL。
|
||||
|
||||
| 层 | 符号及基线行号 | 证据 |
|
||||
| --- | --- | --- |
|
||||
| 前端分支 | `frontend/src/lib/rectification-agentic/v9/score-persist.ts:154,177–192`,`scoreAndPersistCurrentEvidence` | 旧缓存只比较证据指纹即可返回;未命中调 `runV9BlockScan`;版本探测在分钟分支 266 行 |
|
||||
| HTTP 客户端 | `frontend/src/lib/rectification-agentic/v9/engine-client.ts:1021–1032,685–720` | POST `/api/rectification/v5/block_scan`,携带日期、时间窗、事件和步长 |
|
||||
| API 注册/入口 | `scripts/jyotish_api_server.py:3562–3563,9100–9105` | `_compute_rectification_v5_block_scan` 规范化后调用服务 |
|
||||
| 时段扫描 | `scripts/rectification/api_service.py:449–461,467–508` | `block_scan` 调 `score_candidates`,段支持率消费其候选分数 |
|
||||
| 评分 | `scripts/rectification/api_service.py:211–214` | 调 `build_event_contribution_matrix` 和 `score_from_matrix` |
|
||||
| 矩阵 | `scripts/rectification/scoring_service.py:237–250,295–303` | 生成真实 contexts,并调 `merge_transition_proximity` |
|
||||
| 日期来源/应用 | `scripts/active_rectification_event_engine.py:90–103,593,608–614`;`scripts/rectification/dasha_transition_proximity.py:155–182` | 跨日枚举保留 `candidate_at`,已修 helper 从它取日期用于计算及缓存键 |
|
||||
|
||||
窗口核对:`declared-birth-window.ts:19–35,131–145`、`search-window.ts:16,38–60,193–206`、`case-service.ts:133–172`。
|
||||
|
||||
| 无申报钟点的路径 | 窗口 | exclusive span | 阶段 | 本身跨日期 |
|
||||
| --- | --- | ---: | --- | --- |
|
||||
| late_night | 23:00–03:59 | 299 分钟 | block_scan | 是 |
|
||||
| unknown | 00:00–23:59 | 1439 分钟 | block_scan | 否 |
|
||||
|
||||
`unknown` 首轮的 late_night 分组只筛同日钟点,不把候选移到次日(`api_service.py:416–431,476–480`);后续选中跨午夜窗再重算才进入跨日触发范围。原修复单把两者均称作容易跨午夜的论据不够精确,本轮按源码明确此边界。
|
||||
|
||||
分钟防线仍在 `engine-client.ts:425–454`,既有回归 `rectification-engine-convergence.test.ts:94–124`、`rectification-engine-version-cross-midnight.test.ts:68–109`;它们不覆盖时段提前返回。回执 started/completed 的来源不同,聚合 SQL 仍取 `max(engine_version)`,本轮仅源码核查,未作数据库实测声明。已把结论写入 BUG-984 与缓存补单 §0。
|
||||
|
||||
## 验证
|
||||
|
||||
### 环境与可复现边界
|
||||
|
||||
- Windows 主机:Python 3.11.7,pyswisseph `20230604`,无项目 `.venv`,使用系统 `python`;原 `python3` launcher 不可用。
|
||||
- 同主机隔离 Linux Docker:Python 3.13.15、glibc 2.36,pyswisseph `20230604`,镜像 `python:3.13-bookworm`。从验证基线 Git archive 解包两份 `/work/base`、`/work/fix`,只向后者覆盖两个测试文件。按仓库 `requirements.txt` / `requirements-dev.txt` 安装完整 Python 依赖;没有改仓库依赖、workflow,没有发布端口或挂载宿主 Docker socket。
|
||||
- Linux 原测试 ordinal 2/3 失败,Windows 原测试全绿,已实证两个环境的浮点行为不同。**但这是同一物理机器上的 Windows/Linux 两个运行环境,不冒充任务书要求的两台独立机器。另一台 Linux 机器/runner 的同 SHA 复验仍属环境缺口。**
|
||||
|
||||
| 验证 | Windows 基线 | Windows 修复 | Linux 基线 | Linux 修复 |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| 原文件 + bridge | 18 通过 | 纳入下行 | 14 通过 / 4 失败(ordinal 2/3 × bridge) | 纳入下行 |
|
||||
| 原文件 + bridge + memoization | — | 32 通过 | — | 32 通过 |
|
||||
| `tests/test_rectification_*.py` | 216 通过 | 216 通过 | 214 通过 / 2 失败 | 216 通过 |
|
||||
| 内存回退日期修复 | — | 3 同日通过 / 1 跨午夜失败(预期红) | — | 3 同日通过 / 1 跨午夜失败(预期红) |
|
||||
| quick(Linux 为其中 Python 步,未进入前端步骤) | 缺 mcp,退出 1 | 同一失败 | 881 通过 / 6 失败 / 5 跳过 | 883 通过 / 4 失败 / 5 跳过 |
|
||||
| Linux 归档补 Git 索引后定向复验隐私/七政 | — | — | 65 通过 / 1 失败 | 65 通过 / 1 失败 |
|
||||
|
||||
- Windows quick 两侧均在 `interpretation_source_inventory_gate.py` 导入处报 `ModuleNotFoundError: No module named 'mcp'`;缺失引用输出逐项相同,不标通过。
|
||||
- Linux quick 两侧均退出 1,未进入前端步骤。基线独有两个失败正是 ordinal 2/3;修改版的四个失败均属于基线,**新增失败 0**。共同失败逐项为:`test_qizheng_api_productization.py::test_qizheng_endpoint_returns_eleven_bodies_and_twelve_palaces`(400 != 200)、`test_repo_privacy_markers.py::test_tracked_repository_has_no_privacy_markers`、`::test_local_python_venv_is_not_tracked`、`::test_gitignore_covers_venv_symlink_name`(后三项为 archive 未带 Git 元数据)。
|
||||
- 为核实归档缺口,在两份隔离目录初始化仅本地 Git 索引、按宿主真实 `git ls-files -z` 清单追踪,不提交/推送,重跑隐私及七政文件:两侧均 65 通过/1 失败,三项 Git 缺口解除,七政 400 的失败仍逐条相同。没有修改测试或越界修七政;未重跑完整 quick,不能把这次定向补验合成为“快速门全绿”。
|
||||
- 独立只读审查未发现阻塞代码问题;审查方另实跑原文件 9 passed、bridge 9 passed,确认 quick 的校正 glob 收集 216 项,本组仅 9 项;原文件与 bridge 同时选择才重复到 18,不将旧任务书所述“同 glob 重复”当成本轮实测事实。
|
||||
- 开工预检:远端 verified;focused 的历史镜像路径断言失败,与错误台账已知缺口一致,不制造 `.workbuddy` 目录消红。
|
||||
- 回退验收通过独立进程内编译替换 helper 的日期表达式实现,仅内存中令 `candidate_date = birth_date`,没有编辑红线生产文件。两环境均三个同日 ordinal 绿、真实跨午夜矩阵断言红。
|
||||
- 定向日志、Windows quick/glob 基线对照日志保留在本机会话临时目录;Linux `/tmp/{base,fix}-*.log` 已复制到宿主临时目录后停止本轮容器,容器保留供复核,不占用运行资源。只落统计、符号和环境信息,不复制请求体或出生资料。
|
||||
- 最终将本轮文件加入 Git 索引后实跑 `python -m pytest tests/test_repo_privacy_markers.py -ra`:62 passed。`git diff --check` 通过;生产红线、历史 golden 与 references 相对 `777bd53d` 零差异,测试文件硬编码 64 位分数哈希为零。
|
||||
|
||||
## 验收状态
|
||||
|
||||
| 项目 | 结论 |
|
||||
| --- | --- |
|
||||
| F1 断言实现、双浮点环境、回退对照 | 本地验证通过;两台独立机器要求仍为环境缺口 |
|
||||
| F2 快速门覆盖与重复计数说明 | 通过 |
|
||||
| F3 调用链查证与定级 | 通过;BUG-984 另单实施,未关闭 |
|
||||
| F4 记录与独立复核 | 通过:BUG-981/984/985、补单 §0、状态板、BLOCKED 与补验清单已更新;独立审查无阻塞发现且两个文件分别 9 项通过 |
|
||||
| staging 合入、门禁、部署 | 尚未执行,不声称已交付 |
|
||||
|
||||
补验清单:`docs/testing/rectification-cross-midnight-gate-fix-20260920.md`。不新增用户可感知行为,本轮不新增 CHANGELOG 条目。
|
||||
@@ -292,7 +292,7 @@
|
||||
| `TASK-consult-smalltalk-fastpath-20260920.md` | `PROGRESS-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 | 本地实现及独立审查完成,两项 SDK 隐私/成本问题已修复;Linux 最终全量 3530/3530 → 3566/3566、标准 DB 39/39 → 40/40、tsc 0、lint 0 error/120 既有 warning;两侧首页 Static,首屏 gzip +0.0493%;**已验收通过(2026-09-20,Claude 独立复验 `539d4dae`)**:同机两侧对照 tsc 0 / lint 0 error·120 warning / npm test 基线 3530 与候选 3563 失败名单逐条相同(31 条均为无 Docker)/ 两侧 `○ /` Static、首屏 gzip +299 B(+0.0483%)/ 部署 SHA 与迁移已核对;红线四文件零改动。剩真人模型语义、延迟 P50、余额与刷新走查(无受控账号) | `codex/consult-smalltalk-fastpath-20260920`;实际基线 `fcad06372`;逐项测试对照见 PROGRESS,真人清单见 `docs/testing/consult-smalltalk-fastpath-20260920.md` |
|
||||
| `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 类型)。**产品 2026-09-20 已就三点拍板:A 修、B 修完重新冻结并重跑 T1/T2、C 让新旧结果可区分。** C 的做法经查证已修正:`calculation_spec_hash` 全在 **V4** 链路、**V9 零引用**,原提案 bump `INPUT_CONTRACT_VERSION` 对真实历史无效已作废;改为随修复 bump `engine_version`(`v9EngineVersion()` 缺省串用于 started/failed,成功回执与 minute 缓存身份实际来自后端;已同步 `ALGORITHM_VERSION`,旧「只写不比」假设作废),**不得动 `skill_version`**(BUG-621:open RPC 要求绑定 Skill 等于当前版本,bump 会让历史校正打不开)。硬红线:只改「按候选日期取 dasha 起始」,不得动 kernel/cap/share 任一常数;确认门不变。BUG-981 | **已 review:核心修复通过,但暂不合入** —— 阻塞于 `TASK-rectification-cross-midnight-gate-fix-20260920.md`(BUG-985) | 实现 `aa46da10`(分支 `codex/rectification-cross-midnight-20260920`,未合入 staging)。Claude 独立 review:修复后整窗打分与**逐候选独立重算 21/21 一致**;非跨午夜分数基线 vs 修复**逐位不变**(我测半径 10 六例 + 半径 60 三例,执行方证据覆盖 19 例 / 2299 候选全部 `bytes_equal`);kernel/cap/share/`PRECISION_WEIGHTS` 一个未动;`status=not_ready`、coverage 0、`holdout_passed()` False、官方试次 0;决策 C 执行正确(`ALGORITHM_VERSION` 7→8,`INPUT_CONTRACT_VERSION` 与 `skill_version` 未动,golden 里 `calculation_spec_hash` 不变可证);既有断言改动均带三栏说明且为加强。**P1 阻塞**:新增的 `test_same_day_public_aa_scores_keep_pre_fix_bytes` 写死 121 个浮点分数的 SHA-256,在 Claude 机器上 ordinal 2/3 红、**基线与修复分支同样红**(非修复所致),且经 `test_rectification_cross_midnight_gate.py` re-export 落入快速门 glob —— 同 glob 基线 214 passed/0 failed、`aa46da10` 4 failed,推 staging 会让门禁红。执行方自报未通过的 BUG-984 经独立确认成立且更重:`block_scan` 只比 evidence 指纹不读打分身份,而 `late_night`(299 min)/`unknown`(1439 min) 均 >120 走该分支、又恰是最易跨午夜的窗口 |
|
||||
| `TASK-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 | 待领取 | — |
|
||||
| `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 | **本地实现完成,待补验,未合入 staging** | 同机 Windows/Linux 浮点行为实证不同,修复后两环境定向各 32 通过、校正 glob 各 216 通过,回退探针同日 3 绿/跨午夜 1 预期红;容器不是第二台独立机器,该条保持 blocked。F3 已证实 BUG-984 阻塞 BUG-981 端到端验收;unknown 全天初始窗本身同日,纠正原推断。完整门禁与独立复核结果见 PROGRESS。 |
|
||||
|
||||
### 跨午夜执行轮独立验收补单
|
||||
|
||||
|
||||
@@ -4,13 +4,17 @@
|
||||
|
||||
## 0. 基线与串行依赖
|
||||
|
||||
- **2026-09-20 F3 已查证:BUG-984 是 BUG-981 端到端验收阻塞项。** 真实调用链为 `scoreAndPersistCurrentEvidence()` → `runV9BlockScan()` → POST `/api/rectification/v5/block_scan` → `_compute_rectification_v5_block_scan()` → `api_service.block_scan()` → `score_candidates()` → `build_event_contribution_matrix()` → `merge_transition_proximity()`。后端时段支持率消费这条链的候选分数,不是另一套打分。证据行号及边界见 `PROGRESS-rectification-cross-midnight-gate-fix-20260920.md` 的 F3。
|
||||
- 影响限定为证据未变的历史跨午夜时段缓存命中,且尚未触发三轮转分钟;新 Case、无缓存或证据变化会进入已修 helper。因此不能说整个时段路径都没有修复,也不能在旧缓存缺口未闭环前宣称 BUG-981 端到端完成。`late_night` 的 `23:00–03:59` 跨日;`unknown` 的 `00:00–23:59` 虽超过 120 分钟但本身同日,后续选择跨午夜时段才进入跨日触发范围。
|
||||
- 串行顺序维持:门禁修复单(BUG-985)→ 原核心修复合入 staging → 本缓存补单。上述查证不构成缓存策略或 SQL 实施授权。
|
||||
|
||||
- 原任务基线 `origin/staging = 03cba478`,前轮代码 `932f2fff`;待原任务最终提交后以其 SHA 为本单执行基线,不以未经确认的部署状态推导基线。
|
||||
- 依赖 `TASK-rectification-cross-midnight-dasha-20260920.md` 完成后串行执行。将涉及 `frontend/src/lib/rectification-agentic/v9/score-persist.ts`、回执查询及测试;不得与原任务 `engine-client.ts` 版本更新并行改同一文件。
|
||||
- 建议 worktree `.worktrees/rectification-cross-midnight-fix-20260920`,分支 `codex/rectification-cross-midnight-fix-20260920`。
|
||||
|
||||
## 1. 事故实证(按符号定位)
|
||||
|
||||
1. `scoreAndPersistV9Candidates()` 的 `block_scan` 分支先比较 `evidenceLedgerFingerprint`,命中即返回 `cached:true` 与缓存原 `algorithmVersion`;该返回发生在 `readV9EngineScoringIdentity()` 之前。跨午夜 late-night 时段可进入此分支,后端真实重算也确实会走本轮修复的 helper。故即使版本接口正常、后端已升级,旧时段分数仍可能被复用。
|
||||
1. `scoreAndPersistCurrentEvidence()` 的 `block_scan` 分支先比较 `evidenceLedgerFingerprint`,命中即返回 `cached:true` 与缓存原 `algorithmVersion`;该返回发生在 `readV9EngineScoringIdentity()` 之前。跨午夜 late-night 时段可进入此分支,后端真实重算也确实会走本轮修复的 helper。故即使版本接口正常、后端已升级,旧时段分数仍可能被复用。
|
||||
2. `rectification-v9-tools.ts` 的 compare started 回执使用默认 engineVersion,成功回执使用实际 `persisted.algorithmVersion`。现有 `20260902020000_rectification_tool_activity_timing.sql` 的回执聚合用 `max(tr.engine_version)`,不是成功结果的身份。新版开始标记与旧缓存成功标记并存时,聚合可显示新版,不能证明新版重新算过。
|
||||
3. minute 分支已有算法身份相等缓存门,但 `readV9EngineScoringIdentity()` 可优先环境覆盖;仅有部分配置或版本接口失败时,不能保证实际旧缓存失效。不得把这些既有降级条件写成无条件安全。
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# TASK · 跨午夜修复 review 修复单:门禁级浮点哈希断言(2026-09-20)
|
||||
|
||||
> 状态:待领取。**这是 `aa46da10` 的合入阻塞项**:核心修复本身已验收通过,卡住的是随它新增的一条测试会让快速门在别的机器上红。
|
||||
> 状态:本地实现完成,待第二台独立机器验收,未合入 staging。执行记录见 `PROGRESS-rectification-cross-midnight-gate-fix-20260920.md`。**这是 `aa46da10` 的合入阻塞项**:核心修复本身已验收通过,卡住的是随它新增的一条测试会让快速门在别的机器上红。
|
||||
> 本单只改测试断言方式,**不改被修的打分代码**。
|
||||
|
||||
## 0. 基线与交付
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# 跨午夜门禁断言修复补验(2026-09-20)
|
||||
|
||||
## 当前边界
|
||||
|
||||
Windows 与同机隔离 Linux 容器的浮点行为确实不同:原测试在 Windows 通过,Linux 的 ordinal 2/3 失败;修复后两环境全绿。这不是两台独立机器,任务书 F1 的另一台 Linux 机器复验仍待完成。不要把旧 review 的 Linux 红测当成修复后绿测。
|
||||
|
||||
## 另一台 Linux 机器 / 受控 runner
|
||||
|
||||
1. 使用本轮最终提交的独立检出;先 `git status -sb`,记录 `git rev-parse HEAD`、Python、pyswisseph 与平台版本。不要借用他人账号或密钥。
|
||||
2. 用项目 `.venv`,按现有 `requirements.txt`、`requirements-dev.txt` 准备依赖,不改版本约束或测试断言。
|
||||
3. 执行:
|
||||
```bash
|
||||
.venv/bin/python -m pytest tests/test_dasha_transition_proximity_cross_midnight.py -ra
|
||||
.venv/bin/python -m pytest tests/test_rectification_cross_midnight_gate.py -ra
|
||||
.venv/bin/python -m pytest tests/test_rectification_*.py -ra
|
||||
.venv/bin/python scripts/run_quality_gate.py --profile quick
|
||||
```
|
||||
4. 原文件与 bridge 分别应为 9 项;两者一起跑 18 项但仅 9 个独立场景。三个 same-day ordinal 必须通过,不能 skip/xfail 或换回另一份硬编码哈希。
|
||||
5. 记录命令退出码、通过/失败数、脱敏失败标题。不附真实出生资料、完整请求或模型原文。
|
||||
6. 如复做回退验收,在隔离进程内临时令 helper 的 `candidate_date = birth_date`,不提交生产文件修改:三个同日用例仍通过,`test_real_cross_midnight_all_candidates_match_independent_dated_calculation` 必须失败。
|
||||
|
||||
## 合入与产品边界
|
||||
|
||||
- 双机验收及完整快速门通过后,按授权交付 staging 并核对远端 SHA;不自行提升 main。健康检查只证明版本部署,不能证明旧缓存重新计算。
|
||||
- BUG-984 是 BUG-981 端到端验收阻塞项。缓存补单需明确策略后单独执行;本轮不改缓存/SQL,不标 BUG-981 resolved。
|
||||
- `unknown` 全天初始窗同日;需要验证跨午夜时,用真实受控的 late_night 场景或选择后形成的跨午夜窗,不能把全天路径本身当作跨日复现证据。
|
||||
- 无登录态时不冒称受控会话端到端通过;沿既有 `rectification-cross-midnight-20260920.md` 补验,禁止借用凭据。
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Candidate-local dasha dates; public AA replay and synthetic cache boundaries."""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
@@ -100,20 +99,39 @@ def test_legacy_context_without_candidate_at_retains_request_date(monkeypatch):
|
||||
assert calls == ["2000-01-01", "2000-01-01"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ordinal,score_sha256", [
|
||||
(1, "2aabdda6bb56baf6a9d0b119964ee1ab8023fede9ea8703f6d265207c490bec2"),
|
||||
(2, "6585d895aeb79e26257b1002696c16b5e252f9a702f2a5702f929fd80486abb9"),
|
||||
(3, "246296903d915e4886229530fa554428d7f65fb686f70cc369711347ebaf2610"),
|
||||
])
|
||||
def test_same_day_public_aa_scores_keep_pre_fix_bytes(ordinal, score_sha256):
|
||||
# Golden hashes captured from the unmodified production path, 121 minutes each.
|
||||
# Only ordinal and score bytes are retained; no birth data or coordinates.
|
||||
@pytest.mark.parametrize("ordinal", [1, 2, 3])
|
||||
def test_same_day_public_aa_scores_keep_pre_fix_bytes(monkeypatch, ordinal):
|
||||
from scripts.rectification import scoring_service
|
||||
|
||||
# BUG-985 / BUG-733: equivalence is same-process, not a cross-machine float golden.
|
||||
request, moments = shifted_window(_cases()[ordinal - 1], 0, 60)
|
||||
assert len({moment.date() for moment in moments}) == 1
|
||||
built = build_event_contribution_matrix(request)
|
||||
assert {moment.date().isoformat() for moment in moments} == {request["birth_date"]}
|
||||
contexts = compute_candidate_static_contexts(request)
|
||||
assert [context["candidate_at"] for context in contexts] == moments
|
||||
built = build_event_contribution_matrix(request, static_contexts=contexts)
|
||||
scores = [row["score"] for row in score_from_matrix(request, built)]
|
||||
assert len(scores) == 121
|
||||
assert hashlib.sha256(_canonical(scores)).hexdigest() == score_sha256
|
||||
legacy_calls = []
|
||||
|
||||
def legacy_merge(matrix, events, static_contexts, birth_date, **kwargs):
|
||||
# Strip only at the helper boundary; the main event engine needs candidate_at.
|
||||
assert static_contexts is contexts
|
||||
legacy_contexts = [
|
||||
{key: value for key, value in context.items() if key != "candidate_at"}
|
||||
for context in static_contexts
|
||||
]
|
||||
legacy_calls.append(len(legacy_contexts))
|
||||
return proximity.merge_transition_proximity(
|
||||
matrix, events, legacy_contexts, birth_date, **kwargs,
|
||||
)
|
||||
|
||||
with monkeypatch.context() as legacy:
|
||||
legacy.setattr(scoring_service, "merge_transition_proximity", legacy_merge)
|
||||
legacy_built = build_event_contribution_matrix(request, static_contexts=contexts)
|
||||
legacy_scores = [row["score"] for row in score_from_matrix(request, legacy_built)]
|
||||
assert legacy_calls == [121]
|
||||
assert len(scores) == len(legacy_scores) == 121
|
||||
assert _canonical(scores) == _canonical(legacy_scores)
|
||||
assert _canonical(built["matrix"]) == _canonical(legacy_built["matrix"])
|
||||
|
||||
|
||||
def test_real_cross_midnight_all_candidates_match_independent_dated_calculation(monkeypatch):
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
"""Collect candidate-date regressions through quick's test_rectification_*.py glob."""
|
||||
"""Collect candidate-date regressions through quick's test_rectification_*.py glob.
|
||||
|
||||
The re-export deliberately keeps all nine cases inside the quick gate. If both
|
||||
this bridge and the source file are selected (e.g. the full suite), pytest runs
|
||||
them twice: eighteen collected items are nine independent cases, not eighteen.
|
||||
"""
|
||||
|
||||
from tests.test_dasha_transition_proximity_cross_midnight import ( # noqa: F401
|
||||
test_every_candidate_uses_own_date_and_caches_do_not_cross_dates,
|
||||
|
||||
Reference in New Issue
Block a user